mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-02 15:31:50 +00:00
fix: update to @agentclientprotocol/sdk 2.2.0 (#1491)
Moves to ACP schema v1.28.0 and v2.0.0-alpha.11.
- v2 dropped JSON-RPC batching, so `AnyWireMessage` is gone. The test
harnesses use `AnyMessage`, and a request id can now be `null` in the
types.
- The v2 elicitation schema now requires `properties`. A forwarded MCP
schema without them, such as `{ "type": "object" }`, gets an empty map,
so clients on SDK 2.2.0 don't reject it.
Runtime changes that come with the SDK and need no agent code:
- The SDK handles a repeated `initialize`, rejects batches, and rejects
messages sent before initialization. A failed `initialize` now leaves
the connection open.
- v2 `usage_update.cost` is now a patch field, where omitted means
unchanged. The agent sends `cost` only on the result update, so the
client keeps showing the last cost during a turn.
Follow-up PRs for the spec changes in this range are stacked on this
branch.
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.99.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.1.0",
|
||||
"@agentclientprotocol/sdk": "2.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
@@ -38,9 +38,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.1.0.tgz",
|
||||
"integrity": "sha512-IuKjJmHLPzbmbg26eK9Jopd8AVxgLuu8fX114YzYAU6tLYclCo2DUzLL4vbCpYGr9mzvs2T0kigE3ZCueL06tg==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.2.0.tgz",
|
||||
"integrity": "sha512-o1+eDQVb05yzPY+5v54awHyONU1oeLsbLVavkwrMQaZDuJDh/38pQiXJS4ix4d6QA3Y4bU0lHFRjwg99KKKSaQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/server": "^2.4.0",
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.1.0",
|
||||
"@agentclientprotocol/sdk": "2.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
|
||||
@@ -377,7 +377,9 @@ export function applyAskElicitationResponse(
|
||||
/**
|
||||
* Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
|
||||
* The two are structurally compatible JSON Schemas; we just guarantee the
|
||||
* `type: "object"` discriminator is present.
|
||||
* `type: "object"` discriminator and a `properties` map are present. The v2
|
||||
* schema requires both, so a client on SDK 2.2.0 or later rejects an MCP
|
||||
* schema such as `{ type: "object" }` that has no `properties`.
|
||||
*/
|
||||
function normalizeElicitationSchema(
|
||||
schema: Record<string, unknown> | undefined,
|
||||
@@ -385,7 +387,8 @@ function normalizeElicitationSchema(
|
||||
if (!schema || typeof schema !== "object") {
|
||||
return { type: "object", properties: {} };
|
||||
}
|
||||
return { ...(schema as ElicitationSchema), type: "object" };
|
||||
const { properties } = schema as Partial<ElicitationSchema>;
|
||||
return { ...(schema as ElicitationSchema), type: "object", properties: properties ?? {} };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -500,21 +500,17 @@ export async function runScenarioV2(
|
||||
const idles: unknown[] = [];
|
||||
const failures: string[] = [];
|
||||
|
||||
const each = (message: v2.AnyWireMessage) =>
|
||||
(Array.isArray(message) ? message : [message]) as Record<string, any>[];
|
||||
const toAgent = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>({
|
||||
const toAgent = new TransformStream<v2.AnyMessage, v2.AnyMessage>({
|
||||
transform(message, controller) {
|
||||
for (const m of each(message)) {
|
||||
if ("method" in m && "id" in m) methodsOfClientRequests.set(m.id, m.method);
|
||||
if ("method" in message && "id" in message && message.id !== null) {
|
||||
methodsOfClientRequests.set(message.id, message.method);
|
||||
}
|
||||
controller.enqueue(message);
|
||||
},
|
||||
});
|
||||
const toClient = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>({
|
||||
const toClient = new TransformStream<v2.AnyMessage, v2.AnyMessage>({
|
||||
transform(message, controller) {
|
||||
for (const m of each(message)) {
|
||||
if (recording) raw.push(wireRecord(m, methodsOfClientRequests));
|
||||
}
|
||||
if (recording) raw.push(wireRecord(message, methodsOfClientRequests));
|
||||
controller.enqueue(message);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -162,8 +162,8 @@ afterEach(async () => {
|
||||
|
||||
/** Connects a fresh router to a client, and returns the client end of the stream. */
|
||||
function connectRouter(protocols?: AcpProtocols): v2.Stream {
|
||||
const toAgent = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>();
|
||||
const toClient = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>();
|
||||
const toAgent = new TransformStream<v2.AnyMessage, v2.AnyMessage>();
|
||||
const toClient = new TransformStream<v2.AnyMessage, v2.AnyMessage>();
|
||||
acpProtocolRouter(undefined, (agent) => agents.push(agent), protocols).connect({
|
||||
readable: toAgent.readable,
|
||||
writable: toClient.writable,
|
||||
@@ -333,11 +333,11 @@ describe("ACP protocol routing", () => {
|
||||
params: { protocolVersion: v2.PROTOCOL_VERSION, info: V2_CLIENT_INFO, capabilities: {} },
|
||||
});
|
||||
const reader = stream.readable.getReader();
|
||||
let response: v2.AnyWireMessage | undefined;
|
||||
let response: v2.AnyMessage | undefined;
|
||||
while (!response) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
if (!Array.isArray(value) && "id" in value && value.id === 1) response = value;
|
||||
if ("id" in value && value.id === 1) response = value;
|
||||
}
|
||||
await writer.close();
|
||||
|
||||
|
||||
@@ -60,6 +60,19 @@ describe("mcpElicitationToCreateRequest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("adds an empty properties map when the MCP schema has none", () => {
|
||||
const request: ElicitationRequest = {
|
||||
serverName: "server",
|
||||
message: "Confirm?",
|
||||
mode: "form",
|
||||
requestedSchema: { type: "object" },
|
||||
};
|
||||
|
||||
const result = mcpElicitationToCreateRequest(request, SESSION_ID);
|
||||
|
||||
expect(result).toMatchObject({ requestedSchema: { type: "object", properties: {} } });
|
||||
});
|
||||
|
||||
it("maps a url-mode request and preserves the elicitation id", () => {
|
||||
const request: ElicitationRequest = {
|
||||
serverName: "server",
|
||||
|
||||
@@ -750,8 +750,8 @@ describe("provider updates preserve session configuration", { timeout: 10_000 },
|
||||
|
||||
it("publishes the restored configuration through the v2 protocol router", async () => {
|
||||
await agent.dispose();
|
||||
const toAgent = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>();
|
||||
const toClient = new TransformStream<v2.AnyWireMessage, v2.AnyWireMessage>();
|
||||
const toAgent = new TransformStream<v2.AnyMessage, v2.AnyMessage>();
|
||||
const toClient = new TransformStream<v2.AnyMessage, v2.AnyMessage>();
|
||||
acpProtocolRouter({ log() {}, error() {} }, (created) => (agent = created)).connect({
|
||||
readable: toAgent.readable,
|
||||
writable: toClient.writable,
|
||||
|
||||
Reference in New Issue
Block a user