mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-12 20:31:15 +00:00
Stacked on #1561. Moves the modules that only the v1 agent uses into `src/v1/` with `git mv`, so the tree reads as `src/v1/` (v1 agent), `src/v2/` (v2 agent) and the rest of `src/` (shared). No behavior change. The v1-only set comes from the import graph: modules reachable from `acp-agent.ts` whose every non-test importer is itself v1-only (entry points `lib.ts`/`serve.ts` aside). Moved: - `acp-agent.ts`, `background-work.ts`, `goal-extension.ts`, `turn-events.ts` - `tool-calls/changed-meta-filter.ts` -> `v1/tool-calls/changed-meta-filter.ts` - `session-index/*` -> `v1/session-index/*`, except `session-index/explicit-title.ts`, which stays because `session-titles.ts` (shared with v2) imports it Stay shared: `clear-context-coordinator.ts`, `native-subagents.ts` and `acp-subagents.ts`. v2 and shared modules import all three. - `src/acp-agent.ts` is now `export * from "./v1/acp-agent.js"`. `dist/acp-agent.js` exports the same names, and `module-exports.test.ts` passes unchanged. `lib.ts` and `serve.ts` import `v1/acp-agent.js` directly. - Imports in `src/` and tests, path mentions in comments, and AGENTS.md are updated. Scripts, vitest config and docs referenced no moved path. - History: `git log --follow src/v1/acp-agent.ts` follows the move. Because `src/acp-agent.ts` still exists as the re-export, blame needs `git blame -C`. Test results: - `npx tsc --noEmit`, `npm run check`, `npm run build`: pass - `npx vitest run`: 3939 passed / 33 skipped, goldens unchanged - `node scripts/build-binary.mjs && node scripts/smoke-binary.mjs`: pass Co-authored-by: Claude <noreply@anthropic.com>
74 lines
2.9 KiB
TypeScript
74 lines
2.9 KiB
TypeScript
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
import { v1AgentApp, type ClaudeAcpAgent } from "./v1/acp-agent.js";
|
|
import type { Logger } from "./logger.js";
|
|
import { nodeToWebReadable, nodeToWebWritable } from "./utils.js";
|
|
import { v2AgentApp, type AgentHandle } from "./v2/agent.js";
|
|
|
|
/**
|
|
* The ACP versions that the adapter serves: `v1` or `v2` alone, or both with
|
|
* `auto`, where the client's `initialize` picks the version.
|
|
*/
|
|
export type AcpProtocols = "auto" | "v1" | "v2";
|
|
|
|
/**
|
|
* Reads `CLAUDE_AGENT_ACP_PROTOCOL`: unset, empty, or `auto` serves both
|
|
* versions, and `v1` or `v2` serves that version alone. Throws on any other
|
|
* value, so that a typo such as `CLAUDE_AGENT_ACP_PROTOCOL=1` fails at startup
|
|
* instead of silently serving both.
|
|
*/
|
|
export function acpProtocolsFromEnv(value: string | undefined): AcpProtocols {
|
|
if (value === undefined || value === "") return "auto";
|
|
if (value === "auto" || value === "v1" || value === "v2") return value;
|
|
throw new Error(
|
|
`CLAUDE_AGENT_ACP_PROTOCOL must be "auto", "v1", or "v2", not ${JSON.stringify(value)}`,
|
|
);
|
|
}
|
|
|
|
/** Serves ACP on stdio through {@link acpProtocolRouter}. */
|
|
export function serveAcp(
|
|
logger: Logger | undefined,
|
|
options: { protocols: AcpProtocols },
|
|
): { closed: Promise<void>; dispose(): Promise<void> } {
|
|
const output = nodeToWebWritable(process.stdout);
|
|
const input = nodeToWebReadable(process.stdin);
|
|
// The router opens the connection of the negotiated version only when it
|
|
// reads `initialize`, so a client that leaves before then leaves no agent.
|
|
let agent: AgentHandle | undefined;
|
|
const onAgent = (created: AgentHandle) => {
|
|
agent = created;
|
|
};
|
|
const { closed } = acpProtocolRouter(logger, onAgent, options.protocols, {
|
|
onV2Agent: onAgent,
|
|
}).connect(v2.ndJsonStream(output, input));
|
|
return {
|
|
closed,
|
|
dispose: async () => {
|
|
await agent?.dispose();
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Routes a connection to the protocol version that the client negotiates in
|
|
* `initialize`. With `auto`, a client that requests version 2 or later gets
|
|
* the draft ACP v2, and every other client gets ACP v1. With `v1`, a v2
|
|
* client is negotiated down to v1. With `v2`, the router rejects the
|
|
* `initialize` of a v1 client as an unsupported protocol version. `onAgent`
|
|
* receives the agent of a v1 connection (`ClaudeAcpAgent`) once the router
|
|
* has opened it, and `onV2Agent` the agent of a v2 connection, which the
|
|
* v2 core (`src/v2/`) serves.
|
|
*/
|
|
export function acpProtocolRouter(
|
|
logger: Logger | undefined,
|
|
onAgent: (agent: ClaudeAcpAgent) => void,
|
|
protocols: AcpProtocols = "auto",
|
|
{ onV2Agent = () => {} }: { onV2Agent?: (agent: AgentHandle) => void } = {},
|
|
): v2.AgentProtocolRouter {
|
|
const router = v2.agentProtocolRouter();
|
|
if (protocols !== "v2") router.withV1(v1AgentApp(logger, onAgent));
|
|
if (protocols !== "v1") {
|
|
router.withV2(v2AgentApp(logger, onV2Agent));
|
|
}
|
|
return router;
|
|
}
|