@agentclientprotocol/claude-agent-acp 0.85.2-preview.2 → 0.85.2-preview.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +1 -0
  2. package/dist/acp-agent.d.ts +100 -23
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +528 -183
  5. package/dist/air-extension.d.ts +2 -0
  6. package/dist/air-extension.d.ts.map +1 -1
  7. package/dist/air-extension.js +2 -0
  8. package/dist/clear-context-coordinator.d.ts +1 -0
  9. package/dist/clear-context-coordinator.d.ts.map +1 -1
  10. package/dist/clear-context-coordinator.js +2 -1
  11. package/dist/diff.d.ts +45 -8
  12. package/dist/diff.d.ts.map +1 -1
  13. package/dist/diff.js +111 -30
  14. package/dist/mcp-command.d.ts +51 -0
  15. package/dist/mcp-command.d.ts.map +1 -0
  16. package/dist/mcp-command.js +321 -0
  17. package/dist/permissions/presentation.d.ts +17 -2
  18. package/dist/permissions/presentation.d.ts.map +1 -1
  19. package/dist/permissions/presentation.js +4 -0
  20. package/dist/resumed-session.d.ts +16 -6
  21. package/dist/resumed-session.d.ts.map +1 -1
  22. package/dist/resumed-session.js +91 -25
  23. package/dist/session-config-ids.d.ts +1 -0
  24. package/dist/session-config-ids.d.ts.map +1 -1
  25. package/dist/session-config-ids.js +1 -0
  26. package/dist/session-mode.d.ts +14 -0
  27. package/dist/session-mode.d.ts.map +1 -1
  28. package/dist/session-mode.js +22 -0
  29. package/dist/tool-calls/client-capabilities.d.ts +20 -2
  30. package/dist/tool-calls/client-capabilities.d.ts.map +1 -1
  31. package/dist/tool-calls/client-capabilities.js +14 -4
  32. package/dist/tool-calls/renderer.d.ts.map +1 -1
  33. package/dist/tool-calls/renderer.js +3 -2
  34. package/dist/tool-calls/reporters/file-edit.d.ts +1 -1
  35. package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -1
  36. package/dist/tool-calls/reporters/file-edit.js +20 -14
  37. package/dist/turn-events.d.ts +11 -5
  38. package/dist/turn-events.d.ts.map +1 -1
  39. package/dist/usage-markdown.d.ts +2 -0
  40. package/dist/usage-markdown.d.ts.map +1 -1
  41. package/dist/usage-markdown.js +2 -1
  42. package/dist/utils.d.ts +12 -0
  43. package/dist/utils.d.ts.map +1 -1
  44. package/dist/utils.js +28 -0
  45. package/dist/v2/agent.d.ts.map +1 -1
  46. package/dist/v2/agent.js +121 -17
  47. package/dist/v2/initialize.d.ts +2 -5
  48. package/dist/v2/initialize.d.ts.map +1 -1
  49. package/dist/v2/initialize.js +23 -6
  50. package/dist/v2/permission.d.ts +25 -0
  51. package/dist/v2/permission.d.ts.map +1 -0
  52. package/dist/v2/permission.js +38 -0
  53. package/dist/v2/prompt.d.ts +47 -0
  54. package/dist/v2/prompt.d.ts.map +1 -0
  55. package/dist/v2/prompt.js +153 -0
  56. package/dist/v2/session-update.d.ts +6 -0
  57. package/dist/v2/session-update.d.ts.map +1 -1
  58. package/dist/v2/session-update.js +27 -0
  59. package/dist/v2/session.d.ts +15 -4
  60. package/dist/v2/session.d.ts.map +1 -1
  61. package/dist/v2/session.js +20 -6
  62. package/dist/v2/terminal.d.ts +42 -0
  63. package/dist/v2/terminal.d.ts.map +1 -0
  64. package/dist/v2/terminal.js +95 -0
  65. package/dist/v2/tool-call.d.ts +38 -0
  66. package/dist/v2/tool-call.d.ts.map +1 -0
  67. package/dist/v2/tool-call.js +49 -0
  68. package/package.json +2 -2
@@ -38,11 +38,8 @@ export function v1InitializeRequest(request) {
38
38
  /**
39
39
  * The v2 `initialize` response for the v1 response of the agent.
40
40
  *
41
- * It advertises no `session` capability yet. A v2 agent that advertises
42
- * `session` must serve the whole session baseline (`session/new`, `list`,
43
- * `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
44
- * the adapter does not serve it yet. Nor does it advertise the unstable
45
- * `providers` methods, which the v2 surface does not serve yet either.
41
+ * It does not advertise the unstable `providers` methods, which the v2
42
+ * surface does not serve yet.
46
43
  */
47
44
  export function v2InitializeResponse(response) {
48
45
  if (!response.agentInfo) {
@@ -54,11 +51,31 @@ export function v2InitializeResponse(response) {
54
51
  info: response.agentInfo,
55
52
  // v1 `auth.logout` has no v2 counterpart: a v2 agent that advertises auth
56
53
  // methods must serve `auth/logout`.
57
- capabilities: meta != null ? { _meta: meta } : {},
54
+ capabilities: {
55
+ session: V2_SESSION_CAPABILITIES,
56
+ ...(meta != null ? { _meta: meta } : {}),
57
+ },
58
58
  authMethods: (response.authMethods ?? []).map(v2AuthMethod),
59
59
  ...(response._meta != null ? { _meta: response._meta } : {}),
60
60
  };
61
61
  }
62
+ /**
63
+ * The session capabilities of the v2 surface, which serves each through a v1
64
+ * method of the agent.
65
+ *
66
+ * - The session baseline: `session/new`, `list`, `resume` (with replay),
67
+ * `close`, `prompt`, `cancel`, and `update`.
68
+ * - `session/delete` and `additionalDirectories`. Not `session/fork` yet.
69
+ * - The prompt content and MCP transports that the agent's v1 `initialize`
70
+ * lists: images, embedded context, and HTTP servers. Stdio servers are in the
71
+ * v1 baseline; v2 has no SSE transport.
72
+ */
73
+ const V2_SESSION_CAPABILITIES = {
74
+ prompt: { image: {}, embeddedContext: {} },
75
+ mcp: { stdio: {}, http: {} },
76
+ delete: {},
77
+ additionalDirectories: {},
78
+ };
62
79
  /** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
63
80
  function v2AuthMethod(method) {
64
81
  if ("type" in method && method.type === "terminal") {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The v2 form of the permission requests that `ClaudeAcpAgent` sends, and the
3
+ * v1 form of their answers.
4
+ */
5
+ import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
6
+ import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
7
+ import type { AcpPermissionRequest } from "../permissions/presentation.js";
8
+ /**
9
+ * The v2 form of a permission request of the agent.
10
+ *
11
+ * v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
12
+ * its own `title` and `description`, which the agent sets for a v2 client
13
+ * (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
14
+ * call, as the same patch that a `tool_call_update` carries.
15
+ */
16
+ export declare function v2PermissionRequest(request: AcpPermissionRequest): v2.RequestPermissionRequest;
17
+ /**
18
+ * The v1 form of the answer to a permission request.
19
+ *
20
+ * v2 adds outcomes that v1 does not know, which the draft says are no
21
+ * approval. They become `cancelled`, which the agent reads as an aborted tool
22
+ * use, as it does a cancelled prompt.
23
+ */
24
+ export declare function v1PermissionResponse(response: v2.RequestPermissionResponse): RequestPermissionResponse;
25
+ //# sourceMappingURL=permission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,EAAE,CAAC,wBAAwB,CAY9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,GACrC,yBAAyB,CAS3B"}
@@ -0,0 +1,38 @@
1
+ import { v2ToolCallPatch } from "./tool-call.js";
2
+ /**
3
+ * The v2 form of a permission request of the agent.
4
+ *
5
+ * v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
6
+ * its own `title` and `description`, which the agent sets for a v2 client
7
+ * (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
8
+ * call, as the same patch that a `tool_call_update` carries.
9
+ */
10
+ export function v2PermissionRequest(request) {
11
+ if (request.title === undefined) {
12
+ throw new Error("An ACP v2 permission request needs a title");
13
+ }
14
+ return {
15
+ sessionId: request.sessionId,
16
+ title: request.title,
17
+ ...(request.description ? { description: request.description } : {}),
18
+ subject: { type: "tool_call", toolCall: v2ToolCallPatch(request.toolCall) },
19
+ options: request.options,
20
+ ...(request._meta != null ? { _meta: request._meta } : {}),
21
+ };
22
+ }
23
+ /**
24
+ * The v1 form of the answer to a permission request.
25
+ *
26
+ * v2 adds outcomes that v1 does not know, which the draft says are no
27
+ * approval. They become `cancelled`, which the agent reads as an aborted tool
28
+ * use, as it does a cancelled prompt.
29
+ */
30
+ export function v1PermissionResponse(response) {
31
+ const outcome = response.outcome;
32
+ return {
33
+ outcome: outcome.outcome === "selected"
34
+ ? outcome
35
+ : { outcome: "cancelled" },
36
+ ...(response._meta != null ? { _meta: response._meta } : {}),
37
+ };
38
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ACP v2 `session/prompt`, served through the turn events of `ClaudeAcpAgent`.
3
+ *
4
+ * v2 answers the prompt once Claude Code takes it in, with the id of the user
5
+ * message it became, and reports the rest of the turn as `state_update`s.
6
+ */
7
+ import { type PromptRequest } from "@agentclientprotocol/sdk";
8
+ import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
9
+ import type { ClaudeAcpAgent } from "../acp-agent.js";
10
+ /**
11
+ * Starts a turn for `params`, and answers when Claude Code takes the prompt
12
+ * in. `send` delivers a session update of the prompt's session; it must not
13
+ * throw, and must deliver updates in the order it is called, also relative to
14
+ * the agent's own updates.
15
+ *
16
+ * - Taken in: the prompt's `user_message`, then `running`, then the answer.
17
+ * - Waiting on a permission request or a question: `requires_action`, and
18
+ * `running` once none is open.
19
+ * - Ended: `idle` with the stop reason, usage, and `_meta` of the turn.
20
+ * - Failed after it was taken in: `idle` with {@link FAILED_STOP_REASON} and
21
+ * the JSON-RPC error that v1 answers with in `_meta.claudeCode.error`, and
22
+ * an error `notice` before it that shows the failure. v2 has no error on
23
+ * `idle`, and a notice must not be the only report of a failure.
24
+ * - Ended or failed before it was taken in: the answer is a JSON-RPC error,
25
+ * `-32800` for a prompt that a cancel ended in the queue.
26
+ *
27
+ * Deferred decision: a turn whose background subagents still run ends only
28
+ * when they finish (`Turn.deferredSettle`), so `idle` waits for them too. The
29
+ * hold exists for v1, whose clients stop listening at the prompt response.
30
+ * v2 allows background updates while idle, so it could report `idle` at the
31
+ * turn's result instead, with a turn event for the hold. To be decided with
32
+ * async tasks (step 6 in `docs/acp-v2.md`).
33
+ */
34
+ export declare function v2Prompt(agent: ClaudeAcpAgent, params: v2.PromptRequest, send: (update: v2.SessionUpdate) => void): Promise<v2.PromptResponse>;
35
+ /**
36
+ * The stop reason of a turn that failed after it was taken in. v2's stop
37
+ * reasons all describe work that ended without an error, and a custom one
38
+ * starts with `_`; clients show it as a generic stop.
39
+ */
40
+ export declare const FAILED_STOP_REASON = "_error";
41
+ /**
42
+ * A v2 prompt may hold content blocks that v1 cannot express: a custom one,
43
+ * or one from a newer ACP version. Clients send only the kinds the agent
44
+ * advertised, so such a block is rejected rather than silently dropped.
45
+ */
46
+ export declare function v1PromptRequest({ prompt, ...request }: v2.PromptRequest): PromptRequest;
47
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/v2/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,EAAE,CAAC,aAAa,EACxB,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,KAAK,IAAI,GACvC,OAAO,CAAC,EAAE,CAAC,cAAc,CAAC,CA0D5B;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,WAAW,CAAC;AA2B3C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,aAAa,GAAG,aAAa,CAEvF"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ACP v2 `session/prompt`, served through the turn events of `ClaudeAcpAgent`.
3
+ *
4
+ * v2 answers the prompt once Claude Code takes it in, with the id of the user
5
+ * message it became, and reports the rest of the turn as `state_update`s.
6
+ */
7
+ import { RequestError, } from "@agentclientprotocol/sdk";
8
+ import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
9
+ import { splitNoticeText } from "../session-notices.js";
10
+ /**
11
+ * Starts a turn for `params`, and answers when Claude Code takes the prompt
12
+ * in. `send` delivers a session update of the prompt's session; it must not
13
+ * throw, and must deliver updates in the order it is called, also relative to
14
+ * the agent's own updates.
15
+ *
16
+ * - Taken in: the prompt's `user_message`, then `running`, then the answer.
17
+ * - Waiting on a permission request or a question: `requires_action`, and
18
+ * `running` once none is open.
19
+ * - Ended: `idle` with the stop reason, usage, and `_meta` of the turn.
20
+ * - Failed after it was taken in: `idle` with {@link FAILED_STOP_REASON} and
21
+ * the JSON-RPC error that v1 answers with in `_meta.claudeCode.error`, and
22
+ * an error `notice` before it that shows the failure. v2 has no error on
23
+ * `idle`, and a notice must not be the only report of a failure.
24
+ * - Ended or failed before it was taken in: the answer is a JSON-RPC error,
25
+ * `-32800` for a prompt that a cancel ended in the queue.
26
+ *
27
+ * Deferred decision: a turn whose background subagents still run ends only
28
+ * when they finish (`Turn.deferredSettle`), so `idle` waits for them too. The
29
+ * hold exists for v1, whose clients stop listening at the prompt response.
30
+ * v2 allows background updates while idle, so it could report `idle` at the
31
+ * turn's result instead, with a turn event for the hold. To be decided with
32
+ * async tasks (step 6 in `docs/acp-v2.md`).
33
+ */
34
+ export function v2Prompt(agent, params, send) {
35
+ return new Promise((resolve, reject) => {
36
+ let inserted = false;
37
+ const events = {
38
+ inserted(messageId) {
39
+ inserted = true;
40
+ send({ sessionUpdate: "user_message", messageId, content: params.prompt });
41
+ send({ sessionUpdate: "state_update", state: "running" });
42
+ resolve({ messageId });
43
+ },
44
+ awaitingUser() {
45
+ send({ sessionUpdate: "state_update", state: "requires_action" });
46
+ },
47
+ resumed() {
48
+ send({ sessionUpdate: "state_update", state: "running" });
49
+ },
50
+ ended(outcome) {
51
+ if (inserted) {
52
+ send(idle(outcome));
53
+ }
54
+ else if (outcome.stopReason === "cancelled") {
55
+ reject(RequestError.requestCancelled());
56
+ }
57
+ else {
58
+ reject(RequestError.internalError({ stopReason: outcome.stopReason }, "The turn ended before Claude Code took the prompt in"));
59
+ }
60
+ },
61
+ failed(error, title) {
62
+ if (!inserted) {
63
+ reject(error);
64
+ return;
65
+ }
66
+ const failure = jsonRpcError(error);
67
+ send({
68
+ sessionUpdate: "notice",
69
+ severity: "error",
70
+ ...splitNoticeText(title ?? failure.message, "The turn failed"),
71
+ });
72
+ send({
73
+ sessionUpdate: "state_update",
74
+ state: "idle",
75
+ stopReason: FAILED_STOP_REASON,
76
+ _meta: { claudeCode: { error: failure } },
77
+ });
78
+ },
79
+ };
80
+ let request;
81
+ try {
82
+ request = v1PromptRequest(params);
83
+ }
84
+ catch (error) {
85
+ reject(error);
86
+ return;
87
+ }
88
+ agent.startTurn(request, events).catch(reject);
89
+ });
90
+ }
91
+ /**
92
+ * The stop reason of a turn that failed after it was taken in. v2's stop
93
+ * reasons all describe work that ended without an error, and a custom one
94
+ * starts with `_`; clients show it as a generic stop.
95
+ */
96
+ export const FAILED_STOP_REASON = "_error";
97
+ /** The JSON-RPC error that v1 answers a failed prompt with. */
98
+ function jsonRpcError(error) {
99
+ if (error instanceof RequestError) {
100
+ return {
101
+ code: error.code,
102
+ message: error.message,
103
+ ...(error.data !== undefined ? { data: error.data } : {}),
104
+ };
105
+ }
106
+ return {
107
+ code: RequestError.internalError().code,
108
+ message: error instanceof Error ? error.message : String(error),
109
+ };
110
+ }
111
+ function idle({ stopReason, usage, _meta }) {
112
+ return {
113
+ sessionUpdate: "state_update",
114
+ state: "idle",
115
+ stopReason,
116
+ ...(usage ? { usage } : {}),
117
+ ...(_meta ? { _meta } : {}),
118
+ };
119
+ }
120
+ /**
121
+ * A v2 prompt may hold content blocks that v1 cannot express: a custom one,
122
+ * or one from a newer ACP version. Clients send only the kinds the agent
123
+ * advertised, so such a block is rejected rather than silently dropped.
124
+ */
125
+ export function v1PromptRequest({ prompt, ...request }) {
126
+ return { ...request, prompt: prompt.map(v1ContentBlock) };
127
+ }
128
+ function v1ContentBlock(block) {
129
+ if (v2.ContentBlock.isText(block) ||
130
+ v2.ContentBlock.isImage(block) ||
131
+ v2.ContentBlock.isAudio(block) ||
132
+ v2.ContentBlock.isResourceLink(block) ||
133
+ v2.ContentBlock.isResource(block)) {
134
+ const { annotations, ...rest } = block;
135
+ return annotations != null ? { ...rest, annotations: v1Annotations(annotations) } : rest;
136
+ }
137
+ throw RequestError.invalidParams({ type: block.type }, `Content blocks of type ${block.type} are not supported`);
138
+ }
139
+ /**
140
+ * v2 also accepts audiences of a newer ACP version; v1 knows two. An audience
141
+ * of only unknown roles is dropped rather than narrowed to nobody.
142
+ */
143
+ function v1Annotations({ audience, ...annotations }) {
144
+ if (audience == null)
145
+ return annotations;
146
+ const known = audience.filter(isV1Role);
147
+ return known.length > 0 || audience.length === 0
148
+ ? { ...annotations, audience: known }
149
+ : annotations;
150
+ }
151
+ function isV1Role(role) {
152
+ return role === "assistant" || role === "user";
153
+ }
@@ -4,6 +4,12 @@
4
4
  */
5
5
  import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
6
6
  import type { AcpSessionNotification } from "../acp-subagents.js";
7
+ /**
8
+ * The id of the plan of a session. v1 has one plan per session, which every
9
+ * plan update replaces whole, from TodoWrite and from the Task tools alike.
10
+ * v2 tracks plans by id, so that plan gets one.
11
+ */
12
+ export declare const SESSION_PLAN_ID = "plan";
7
13
  /**
8
14
  * The v2 form of a v1 session update, or `undefined` when v2 has no
9
15
  * counterpart and the update tells a v2 client nothing new.
@@ -1 +1 @@
1
- {"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CAoB9B"}
1
+ {"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CAwC9B"}
@@ -1,4 +1,11 @@
1
1
  import { v2AvailableCommands, v2ConfigOptions } from "./session.js";
2
+ import { v2ToolCallUpdate } from "./tool-call.js";
3
+ /**
4
+ * The id of the plan of a session. v1 has one plan per session, which every
5
+ * plan update replaces whole, from TodoWrite and from the Task tools alike.
6
+ * v2 tracks plans by id, so that plan gets one.
7
+ */
8
+ export const SESSION_PLAN_ID = "plan";
2
9
  /**
3
10
  * The v2 form of a v1 session update, or `undefined` when v2 has no
4
11
  * counterpart and the update tells a v2 client nothing new.
@@ -16,6 +23,26 @@ export function v2SessionUpdate(update) {
16
23
  case "usage_update":
17
24
  case "notice":
18
25
  return update;
26
+ case "agent_message_chunk":
27
+ case "agent_thought_chunk":
28
+ case "user_message_chunk": {
29
+ // v2 requires the id of the message a chunk belongs to; v1 allows none.
30
+ const { messageId, ...chunk } = update;
31
+ if (!messageId) {
32
+ throw new Error(`An ACP v2 ${update.sessionUpdate} needs a messageId`);
33
+ }
34
+ return { ...chunk, messageId };
35
+ }
36
+ case "tool_call":
37
+ case "tool_call_update":
38
+ return v2ToolCallUpdate(update);
39
+ case "plan":
40
+ // A v2 plan of items is replaced whole as well.
41
+ return {
42
+ sessionUpdate: "plan_update",
43
+ plan: { type: "items", planId: SESSION_PLAN_ID, entries: update.entries },
44
+ ...(update._meta != null ? { _meta: update._meta } : {}),
45
+ };
19
46
  case "current_mode_update":
20
47
  // v2 has no modes: the mode is the `mode` config option. Every mode
21
48
  // change also reaches the client as a `config_option_update` or in the
@@ -3,14 +3,25 @@
3
3
  * `ClaudeAcpAgent`: v2 requests become the v1 requests that the agent serves,
4
4
  * and its v1 responses become v2 responses.
5
5
  */
6
- import { type AvailableCommand, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
6
+ import { type AvailableCommand, type LoadSessionRequest, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
7
7
  import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
8
8
  export declare function v1NewSessionRequest(params: v2.NewSessionRequest): NewSessionRequest;
9
9
  /**
10
- * `replayFrom` is rejected until the v2 surface can translate the replayed
11
- * history, which needs the message and tool call updates.
10
+ * The v1 request that a v2 `session/resume` means.
11
+ *
12
+ * v2 restores a session with `session/resume` and replays its history only
13
+ * when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
14
+ * never replays, and `session/load`, which replays the whole history before it
15
+ * responds. `replayFrom: { type: "start" }` is the second; a cursor that
16
+ * starts elsewhere is rejected.
12
17
  */
13
- export declare function v1ResumeSessionRequest(params: v2.ResumeSessionRequest): ResumeSessionRequest;
18
+ export declare function v1RestoreSessionRequest(params: v2.ResumeSessionRequest): {
19
+ method: "resume";
20
+ request: ResumeSessionRequest;
21
+ } | {
22
+ method: "load";
23
+ request: LoadSessionRequest;
24
+ };
14
25
  /** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
15
26
  export declare function v2NewSessionResponse(response: NewSessionResponse): v2.NewSessionResponse;
16
27
  export declare function v2ResumeSessionResponse(response: ResumeSessionResponse): v2.ResumeSessionResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAInF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,EAAE,CAAC,oBAAoB,GAAG,oBAAoB,CAS5F;AAED,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAExF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,EAAE,CAAC,qBAAqB,CAKjG;AAED,uEAAuE;AACvE,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,EAAE,CAAC,6BAA6B,GACvC,6BAA6B,CAgB/B;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAQxF;AAWD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAKvF"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAInF;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,EAAE,CAAC,oBAAoB,GAE7B;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAkBlD;AAED,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAExF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,EAAE,CAAC,qBAAqB,CAKjG;AAED,uEAAuE;AACvE,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,EAAE,CAAC,6BAA6B,GACvC,6BAA6B,CAgB/B;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAQxF;AAWD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAKvF"}
@@ -11,15 +11,29 @@ export function v1NewSessionRequest(params) {
11
11
  return { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) };
12
12
  }
13
13
  /**
14
- * `replayFrom` is rejected until the v2 surface can translate the replayed
15
- * history, which needs the message and tool call updates.
14
+ * The v1 request that a v2 `session/resume` means.
15
+ *
16
+ * v2 restores a session with `session/resume` and replays its history only
17
+ * when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
18
+ * never replays, and `session/load`, which replays the whole history before it
19
+ * responds. `replayFrom: { type: "start" }` is the second; a cursor that
20
+ * starts elsewhere is rejected.
16
21
  */
17
- export function v1ResumeSessionRequest(params) {
22
+ export function v1RestoreSessionRequest(params) {
18
23
  const { replayFrom, mcpServers, ...request } = params;
19
- if (replayFrom != null) {
20
- throw RequestError.invalidParams({ replayFrom }, "The ACP v2 surface does not replay session history yet");
24
+ if (replayFrom == null) {
25
+ return {
26
+ method: "resume",
27
+ request: { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) },
28
+ };
21
29
  }
22
- return { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) };
30
+ if (replayFrom.type !== "start") {
31
+ throw RequestError.invalidParams({ replayFrom }, `Replaying from a ${replayFrom.type} cursor is not supported`);
32
+ }
33
+ return {
34
+ method: "load",
35
+ request: { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) },
36
+ };
23
37
  }
24
38
  /** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
25
39
  export function v2NewSessionResponse(response) {
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Agent-owned display terminals for the commands that the agent runs.
3
+ *
4
+ * The agent reports a command through the terminal extension of ACP v1
5
+ * clients, as codex-acp does: `_meta.terminal_info` names the terminal of a
6
+ * tool call, whose content references it; `terminal_output` (a snapshot) or
7
+ * `terminal_output_delta` (a chunk) carries the output, and `terminal_exit` the
8
+ * exit. ACP v2 standardizes this as display-only terminals: the same content
9
+ * reference, and `terminal_update` and `terminal_output_chunk` for the
10
+ * terminal's state. The agent reports to a v2 client as to a client of the
11
+ * extension (`ToolCallClientCapabilities.from`), and {@link V2Terminals} turns
12
+ * the extension's keys into those updates.
13
+ */
14
+ import type { ToolCall, ToolCallUpdate } from "@agentclientprotocol/sdk";
15
+ import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
16
+ type V1ToolCallReport = (ToolCall & {
17
+ sessionUpdate: "tool_call";
18
+ }) | (ToolCallUpdate & {
19
+ sessionUpdate: "tool_call_update";
20
+ });
21
+ /** The terminal updates of the tool call reports of one v2 connection. */
22
+ export declare class V2Terminals {
23
+ /** The terminal and the last reported command of each running command, by session and tool call. */
24
+ private readonly running;
25
+ /**
26
+ * The terminal updates that a tool call report carries in the extension's
27
+ * keys, and the report without them, or undefined when nothing else is left
28
+ * in it.
29
+ *
30
+ * The extension names no command, so the terminal gets the command of the
31
+ * tool call's `rawInput` once a report carries it: the first report of a
32
+ * streamed tool use has no input yet.
33
+ */
34
+ split(sessionId: string, report: V1ToolCallReport): {
35
+ terminal: v2.SessionUpdate[];
36
+ report: V1ToolCallReport | undefined;
37
+ };
38
+ /** Forgets the terminals of a session. */
39
+ forget(sessionId: string): void;
40
+ }
41
+ export {};
42
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/v2/terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAEpE,KAAK,gBAAgB,GACjB,CAAC,QAAQ,GAAG;IAAE,aAAa,EAAE,WAAW,CAAA;CAAE,CAAC,GAC3C,CAAC,cAAc,GAAG;IAAE,aAAa,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAiB7D,0EAA0E;AAC1E,qBAAa,WAAW;IACtB,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+D;IAEvF;;;;;;;;OAQG;IACH,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB;QAAE,QAAQ,EAAE,EAAE,CAAC,aAAa,EAAE,CAAC;QAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAA;KAAE;IAuCzE,0CAA0C;IAC1C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAKhC"}
@@ -0,0 +1,95 @@
1
+ const TERMINAL_KEYS = [
2
+ "terminal_info",
3
+ "terminal_output",
4
+ "terminal_output_delta",
5
+ "terminal_exit",
6
+ ];
7
+ /** The terminal updates of the tool call reports of one v2 connection. */
8
+ export class V2Terminals {
9
+ /** The terminal and the last reported command of each running command, by session and tool call. */
10
+ running = new Map();
11
+ /**
12
+ * The terminal updates that a tool call report carries in the extension's
13
+ * keys, and the report without them, or undefined when nothing else is left
14
+ * in it.
15
+ *
16
+ * The extension names no command, so the terminal gets the command of the
17
+ * tool call's `rawInput` once a report carries it: the first report of a
18
+ * streamed tool use has no input yet.
19
+ */
20
+ split(sessionId, report) {
21
+ const meta = (report._meta ?? {});
22
+ const key = `${sessionId} ${report.toolCallId}`;
23
+ if (meta.terminal_info)
24
+ this.running.set(key, { terminalId: meta.terminal_info.terminal_id });
25
+ const terminal = [];
26
+ const running = this.running.get(key);
27
+ if (running) {
28
+ const command = commandOf(report.rawInput);
29
+ const newCommand = command !== undefined && command !== running.command;
30
+ if (newCommand)
31
+ running.command = command;
32
+ const { terminal_output: output, terminal_output_delta: delta, terminal_exit: exit } = meta;
33
+ if (meta.terminal_info || newCommand || output || exit) {
34
+ terminal.push({
35
+ sessionUpdate: "terminal_update",
36
+ terminalId: running.terminalId,
37
+ ...(newCommand ? { command } : {}),
38
+ ...(output ? { output: { data: base64(output.data) } } : {}),
39
+ ...(exit
40
+ ? {
41
+ exitStatus: {
42
+ exitCode: exit.exit_code,
43
+ ...(exit.signal != null ? { signal: exit.signal } : {}),
44
+ },
45
+ }
46
+ : {}),
47
+ });
48
+ }
49
+ if (delta) {
50
+ terminal.push({
51
+ sessionUpdate: "terminal_output_chunk",
52
+ terminalId: running.terminalId,
53
+ data: base64(delta.data),
54
+ });
55
+ }
56
+ if (exit)
57
+ this.running.delete(key);
58
+ }
59
+ return { terminal, report: withoutTerminalKeys(report) };
60
+ }
61
+ /** Forgets the terminals of a session. */
62
+ forget(sessionId) {
63
+ for (const key of this.running.keys()) {
64
+ if (key.startsWith(`${sessionId} `))
65
+ this.running.delete(key);
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * The report without the extension's keys, or undefined when it carried
71
+ * nothing else but `_meta`: the output report of a command carries the output
72
+ * alone, and only the stamps that every report of the tool call repeats. In v2
73
+ * a concrete `_meta` replaces the stored one, so sending them would drop the
74
+ * other keys that the tool call holds.
75
+ */
76
+ function withoutTerminalKeys(report) {
77
+ if (!report._meta || !TERMINAL_KEYS.some((key) => key in report._meta))
78
+ return report;
79
+ const meta = Object.fromEntries(Object.entries(report._meta).filter(([key]) => !TERMINAL_KEYS.includes(key)));
80
+ const stripped = { ...report };
81
+ if (Object.keys(meta).length > 0)
82
+ stripped._meta = meta;
83
+ else
84
+ delete stripped._meta;
85
+ const fields = Object.keys(stripped).filter((key) => key !== "sessionUpdate" && key !== "toolCallId" && key !== "_meta");
86
+ return fields.length > 0 || report.sessionUpdate === "tool_call" ? stripped : undefined;
87
+ }
88
+ function commandOf(rawInput) {
89
+ const command = rawInput?.command;
90
+ return typeof command === "string" && command.length > 0 ? command : undefined;
91
+ }
92
+ /** The output bytes, base64-encoded as a v2 terminal carries them. */
93
+ function base64(text) {
94
+ return Buffer.from(text, "utf8").toString("base64");
95
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The v2 form of the tool call reports that `ClaudeAcpAgent` sends as v1
3
+ * `tool_call` and `tool_call_update` updates, and in permission requests.
4
+ */
5
+ import type { ToolCall, ToolCallUpdate } from "@agentclientprotocol/sdk";
6
+ import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
7
+ type V1ToolCallReport = (ToolCall & {
8
+ sessionUpdate: "tool_call";
9
+ }) | (ToolCallUpdate & {
10
+ sessionUpdate: "tool_call_update";
11
+ });
12
+ type V2ToolCallUpdate = v2.ToolCallUpdate & {
13
+ sessionUpdate: "tool_call_update";
14
+ };
15
+ /**
16
+ * The v2 form of a v1 tool call report.
17
+ *
18
+ * v1 reports a new tool call with `tool_call` and changes it with
19
+ * `tool_call_update`; v2 has one `tool_call_update` that upserts by
20
+ * `toolCallId`. Both leave a field that a report omits unchanged, and both
21
+ * replace `content` and `locations` as whole arrays. They differ on `null`:
22
+ * in v1 it leaves the field unchanged too, in v2 it clears the field, so `null`
23
+ * fields are left out.
24
+ *
25
+ * Diffs need what a v1 diff does not say, so the agent builds them in v2 form
26
+ * for a v2 client (`v2DiffContent`), and they pass as they are. Terminal
27
+ * content references a display terminal, as in v2: a v2 client has no client
28
+ * terminals, so the agent's only terminals are those of its terminal extension
29
+ * (see `terminal.ts`).
30
+ */
31
+ export declare function v2ToolCallUpdate(report: V1ToolCallReport): V2ToolCallUpdate;
32
+ /**
33
+ * The v2 form of the fields of a v1 tool call report, which a session update
34
+ * and the subject of a permission request carry. See {@link v2ToolCallUpdate}.
35
+ */
36
+ export declare function v2ToolCallPatch(toolCall: ToolCall | ToolCallUpdate): v2.ToolCallUpdate;
37
+ export {};
38
+ //# sourceMappingURL=tool-call.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call.d.ts","sourceRoot":"","sources":["../../src/v2/tool-call.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAmB,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1F,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAEpE,KAAK,gBAAgB,GACjB,CAAC,QAAQ,GAAG;IAAE,aAAa,EAAE,WAAW,CAAA;CAAE,CAAC,GAC3C,CAAC,cAAc,GAAG;IAAE,aAAa,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;AAE7D,KAAK,gBAAgB,GAAG,EAAE,CAAC,cAAc,GAAG;IAAE,aAAa,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAElF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAE3E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,GAAG,EAAE,CAAC,cAAc,CAUtF"}