Skip to content

feat(harness-server): serve AI SDK agents as a harness#544

Open
goksu wants to merge 2 commits into
mainfrom
gt/claude/ai-sdk-harness-adapter
Open

feat(harness-server): serve AI SDK agents as a harness#544
goksu wants to merge 2 commits into
mainfrom
gt/claude/ai-sdk-harness-adapter

Conversation

@goksu

@goksu goksu commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the Vercel AI SDK as a fourth harness in crates/harness-server, peer to Codex, Claude Code, and Amp. Anything written against the AI SDK — any provider, custom tools — becomes a selectable harness=ai-sdk value for everything above the protocol (api-rs, Slack, workflows, SSE event stream), with zero changes to those layers.

harness-server ai-sdk --mode jsonrpc
        │  spawns (AISDK_BRIDGE_BIN / CENTAUR_AISDK_BRIDGE_COMMAND)
        ▼
centaur-aisdk-bridge (packages/ai-sdk)     ── streamText() agent loop, tools,
        │  stdout: claude-CLI stream-json      steering between steps
        ▼
any AI SDK model/provider

Design

  • Zero new wire format. The Node bridge emits the same claude-CLI stream-json dialect claude.rs already consumes, so aisdk.rs is ~80 lines reusing AnthropicEventNormalizer wholesale: streaming deltas, tool projection, phase handling (tool_use → commentary, end_turn → final answer) all come for free.
  • Steering parity. The bridge runs a hand-rolled step loop (streamText with stopWhen: stepCountIs(1)) and drains turn/steer input between steps — AI SDK v6 loops steps internally by default, which would have made mid-turn steering invisible.
  • Projection-aware tools. The shell tool is named Bash so the turn normalizer promotes it to first-class commandExecution items; ReadFile exercises the dynamicToolCall path.
  • Session model mirrors the Claude harness: long-lived bridge process per thread, in-memory history, --session-id / --resume flags.

Note on history: the first commit added an AI-SDK-as-client ChatTransport; the second replaces it with this inverse (AI-SDK-as-harness) direction. The net diff is the harness only.

Testing

  • cargo test -p harness-server — 32 pass, including new aisdk::tests (stdin shape, CLI flags, full parse→normalize→notification pipeline).
  • pnpm test in packages/ai-sdk — bridge loop against MockLanguageModelV3 (scripted tool-call + answer turn, error surfacing).
  • node test/e2e-jsonrpc.mjs <bin> — offline end-to-end: JSON-RPC → Rust server → bridge → mock model, asserting the normalized turn contains a commandExecution item (output + exit 0) and a final_answer agent message.
  • Not exercised: a live @ai-sdk/anthropic call (no ANTHROPIC_API_KEY in the dev environment); AISDK_MODEL=mock covers the full pipeline offline.

🤖 Generated with Claude Code

goksu and others added 2 commits June 12, 2026 14:45
Add @centaur/ai-sdk, a Vercel AI SDK adapter on top of the Rust harness
layer: HarnessChatTransport spawns `harness-server <harness> --mode jsonrpc`
per chat, drives thread/turn over App Server V2 JSON-RPC, and converts the
normalized notifications into AI SDK UIMessageChunk streams, so useChat /
Chat apps can run Codex, Claude Code, or Amp through the existing normalizer.

- UIMessageChunkConverter: notification -> chunk mapping (text, reasoning
  incl. codex summaries, dynamic tool calls with streamed preliminary
  output, plans as data parts, declined -> tool-output-denied)
- HarnessSession: thread lifecycle + runTurn / steer / interrupt / resume
- Tests: converter units, transport e2e against a scripted fake
  harness-server, and assembly through the AI SDK's readUIMessageStream
- examples/chat.ts: terminal chat over a real harness CLI (verified
  end-to-end against claude-code)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the AI-SDK-as-client ChatTransport with the inverse direction: a
Vercel AI SDK agent loop served as a fourth harness next to Codex, Claude
Code, and Amp, so api-rs, Slack, and workflows can run any AI SDK agent via
harness=ai-sdk with no changes above the protocol.

- crates/harness-server: AiSdkHarness (`harness-server ai-sdk`) spawns a
  Node bridge and reuses the Anthropic event normalizer wholesale — the
  bridge emits the same claude-CLI stream-json the Claude harness consumes
- packages/ai-sdk: centaur-aisdk-bridge — hand-rolled streamText step loop
  (stopWhen stepCountIs(1)) so turn/steer input injects between steps;
  Bash tool named to project as commandExecution items; in-memory thread
  history; AISDK_MODEL=mock runs a scripted offline model
- tests: cargo unit tests for the adapter, vitest for the bridge loop
  against MockLanguageModelV3, and an offline e2e (test/e2e-jsonrpc.mjs)
  driving JSON-RPC -> Rust server -> bridge -> mock model

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant