feat(harness-server): serve AI SDK agents as a harness#544
Open
goksu wants to merge 2 commits into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 selectableharness=ai-sdkvalue for everything above the protocol (api-rs, Slack, workflows, SSE event stream), with zero changes to those layers.Design
claude.rsalready consumes, soaisdk.rsis ~80 lines reusingAnthropicEventNormalizerwholesale: streaming deltas, tool projection, phase handling (tool_use→ commentary,end_turn→ final answer) all come for free.streamTextwithstopWhen: stepCountIs(1)) and drainsturn/steerinput between steps — AI SDK v6 loops steps internally by default, which would have made mid-turn steering invisible.Bashso the turn normalizer promotes it to first-classcommandExecutionitems;ReadFileexercises thedynamicToolCallpath.--session-id/--resumeflags.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 newaisdk::tests(stdin shape, CLI flags, full parse→normalize→notification pipeline).pnpm testinpackages/ai-sdk— bridge loop againstMockLanguageModelV3(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 acommandExecutionitem (output + exit 0) and afinal_answeragent message.@ai-sdk/anthropiccall (noANTHROPIC_API_KEYin the dev environment);AISDK_MODEL=mockcovers the full pipeline offline.🤖 Generated with Claude Code