feat: add Agent SDK adapter for claude:agent-sdk connected mode#107
feat: add Agent SDK adapter for claude:agent-sdk connected mode#107
Conversation
Implements BackendAdapter using @anthropic-ai/claude-agent-sdk as an alternative connected mode for Claude. The SDK is dynamically imported only when the adapter is actually used, keeping the default bundle lean. Key components: - AgentSdkSession: BackendSession with AsyncMessageQueue, multi-turn input iterable, and resume support via backend session ID capture - PermissionBridge: converts SDK's Promise-based canUseTool callback to BeamCode's message-based permission flow (2-min timeout) - SDK message translator: thin wrapper delegating shared types to existing Claude translator, handling SDK-only types directly - Widen CliAdapterName to include "claude:agent-sdk" 57 new tests across 5 test files including compliance suite.
Summary of ChangesHello @teng-lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the system's ability to interact with the Claude AI by integrating the Anthropic Claude Agent SDK. This new integration method operates in-process, offering a more direct and potentially faster communication channel compared to traditional WebSocket-based connections. It includes robust session management, a mechanism for handling tool-use permissions, and efficient message translation, all designed to be dynamically loaded to minimize impact on other adapter functionalities. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new claude:agent-sdk adapter, which is a significant and well-implemented feature. The code is well-structured, with clear separation of concerns between the adapter, session, permission bridge, and message translator. The use of dynamic imports for the SDK is a great performance consideration. The test coverage is extensive, including unit tests, compliance tests, and integration tests, which provides high confidence in the changes. I have a couple of minor suggestions to improve test determinism and code readability, but overall this is an excellent contribution.
| // Wait a tick for the stream processing to capture the ID | ||
| await new Promise((r) => setTimeout(r, 10)); |
There was a problem hiding this comment.
Using setTimeout in tests can introduce flakiness. The backendSessionId is set within the consumeStream method just before the message is enqueued. Since await iter.next() waits for a message to be enqueued, the backendSessionId should be available immediately after, without needing to wait for another event loop tick. This setTimeout appears unnecessary. Removing it will make the test more robust and deterministic.
References
- To prevent flakiness, tests should avoid using
setTimeoutfor synchronization and instead use deterministic helpers or wait for specific asynchronous events.
There was a problem hiding this comment.
Fixed in 690017f — removed the setTimeout. The backendSessionId is set before queue.enqueue(), so it's available immediately after iter.next() resolves.
| const sessionId = (sdkMsg as Record<string, unknown>).session_id as string; | ||
| if (sessionId) { | ||
| // Store the backend session ID for resume support | ||
| (this as { backendSessionId?: string }).backendSessionId = sessionId; |
There was a problem hiding this comment.
For better code clarity and maintainability, class properties should be declared at the top of the class definition. The backendSessionId property is currently declared at the end of the class, and a type assertion is used here to assign to it. Moving the declaration to the top of the AgentSdkSession class with the other properties would make the code easier to read and remove the need for this cast.
| (this as { backendSessionId?: string }).backendSessionId = sessionId; | |
| this.backendSessionId = sessionId; |
There was a problem hiding this comment.
Fixed in 690017f — moved backendSessionId declaration to the top of the class with other properties and removed the type assertion cast.
19 tests covering full session lifecycle: streaming, multi-turn, permission flow (approve/deny/updatedInput), interrupt, error handling, close, SDK-only system messages, backend session ID capture, and resume.
Add live backend E2E tests exercising the full Agent SDK session
lifecycle via SessionCoordinator.createSession({ adapterName: "claude:agent-sdk" }).
- 7 smoke tests (SDK package + Claude CLI, no API key needed):
createSession, consumer session_init, multi-consumer, disconnect/reconnect,
deleteSession, concurrent sessions, non-existent session delete
- 5 full tests (require CLI auth):
user_message → assistant reply, streaming events, multi-turn,
broadcast to two consumers, interrupt mid-turn
- Add getAgentSdkPrereqState() and isSdkPackageAvailable() to prereqs.ts
- Replace 12 hand-written tests with registerSharedSmokeTests + registerSharedFullTests, matching gemini/opencode pattern (22 tests) - Remove ANTHROPIC_API_KEY fallback from agent-sdk prereqs — SDK uses CLI auth only, no separate API key needed
…Id declaration - Remove unnecessary setTimeout in backendSessionId test — the ID is set before enqueue, so it's available after iter.next() resolves - Move backendSessionId declaration to class top with other properties, remove type assertion cast
Add test:e2e:real:agent-sdk and test:e2e:real:agent-sdk:smoke scripts matching the pattern of other adapters.
- Add pnpm test:e2e:real:agent-sdk to per-backend commands - Add agent-sdk row to prerequisites table (CLI auth, no API key) - Add Claude Agent SDK row to adapter comparison table
Summary
claude:agent-sdkas a new connected mode using@anthropic-ai/claude-agent-sdk(in-process, no child process)CliAdapterNameunion to include the new specifier; all downstream consumers (factory, resolver, validator) pick it up automaticallyclaude:agent-sdkis actually used — zero cost for other adaptersKey components
BackendSessionwithAsyncMessageQueue, multi-turn input iterable, resume support via backend session ID capture fromsystem:initcanUseToolcallback to BeamCode's message-basedpermission_request/permission_responseflow (2-min timeout, cancelAll on close)BackendAdapter, forward-connection (notInvertedConnectionAdapter)Design decisions
import()in asyncAgentSdkSession.create()factory — heavy SDK only loaded when usedsendRaw()throws (same pattern as CodexSession)Test plan
npx tsc --noEmit— compiles cleanlybeamcode --adapter claude:agent-sdkstarts session, web UI works