@agentclientprotocol/claude-agent-acp 0.81.3-preview.1 → 0.82.0

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 (95) hide show
  1. package/README.md +8 -5
  2. package/dist/acp-agent.d.ts +31 -37
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +546 -534
  5. package/dist/acp-subagents.d.ts +6 -0
  6. package/dist/acp-subagents.d.ts.map +1 -1
  7. package/dist/air-extension.d.ts +31 -1
  8. package/dist/air-extension.d.ts.map +1 -1
  9. package/dist/air-extension.js +35 -1
  10. package/dist/async-tasks.d.ts +39 -2
  11. package/dist/async-tasks.d.ts.map +1 -1
  12. package/dist/async-tasks.js +147 -44
  13. package/dist/context-compaction-meta.d.ts +6 -5
  14. package/dist/context-compaction-meta.d.ts.map +1 -1
  15. package/dist/context-compaction-meta.js +10 -10
  16. package/dist/context-compaction.d.ts +3 -0
  17. package/dist/context-compaction.d.ts.map +1 -1
  18. package/dist/context-compaction.js +23 -57
  19. package/dist/diff.d.ts +73 -5
  20. package/dist/diff.d.ts.map +1 -1
  21. package/dist/diff.js +435 -64
  22. package/dist/elicitation.d.ts +1 -1
  23. package/dist/elicitation.d.ts.map +1 -1
  24. package/dist/elicitation.js +12 -14
  25. package/dist/native-subagents.d.ts +58 -0
  26. package/dist/native-subagents.d.ts.map +1 -1
  27. package/dist/native-subagents.js +186 -34
  28. package/dist/paths.d.ts +3 -0
  29. package/dist/paths.d.ts.map +1 -0
  30. package/dist/paths.js +6 -0
  31. package/dist/permissions/presentation.d.ts +13 -2
  32. package/dist/permissions/presentation.d.ts.map +1 -1
  33. package/dist/permissions/presentation.js +49 -44
  34. package/dist/resumed-session.d.ts +11 -0
  35. package/dist/resumed-session.d.ts.map +1 -1
  36. package/dist/resumed-session.js +106 -4
  37. package/dist/session-mode.d.ts +2 -0
  38. package/dist/session-mode.d.ts.map +1 -1
  39. package/dist/session-mode.js +12 -5
  40. package/dist/settings.js +2 -2
  41. package/dist/subagent-history.d.ts +20 -0
  42. package/dist/subagent-history.d.ts.map +1 -0
  43. package/dist/subagent-history.js +46 -0
  44. package/dist/tool-calls/background.d.ts +20 -0
  45. package/dist/tool-calls/background.d.ts.map +1 -0
  46. package/dist/tool-calls/background.js +41 -0
  47. package/dist/tool-calls/changed-meta-filter.d.ts +27 -0
  48. package/dist/tool-calls/changed-meta-filter.d.ts.map +1 -0
  49. package/dist/tool-calls/changed-meta-filter.js +102 -0
  50. package/dist/tool-calls/client-capabilities.d.ts +51 -0
  51. package/dist/tool-calls/client-capabilities.d.ts.map +1 -0
  52. package/dist/tool-calls/client-capabilities.js +43 -0
  53. package/dist/tool-calls/content.d.ts +59 -0
  54. package/dist/tool-calls/content.d.ts.map +1 -0
  55. package/dist/tool-calls/content.js +194 -0
  56. package/dist/tool-calls/facts.d.ts +116 -0
  57. package/dist/tool-calls/facts.d.ts.map +1 -0
  58. package/dist/tool-calls/facts.js +1 -0
  59. package/dist/tool-calls/field-tracker.d.ts +58 -0
  60. package/dist/tool-calls/field-tracker.d.ts.map +1 -0
  61. package/dist/tool-calls/field-tracker.js +167 -0
  62. package/dist/tool-calls/renderer.d.ts +216 -0
  63. package/dist/tool-calls/renderer.d.ts.map +1 -0
  64. package/dist/tool-calls/renderer.js +487 -0
  65. package/dist/tool-calls/reporters/agent.d.ts +7 -0
  66. package/dist/tool-calls/reporters/agent.d.ts.map +1 -0
  67. package/dist/tool-calls/reporters/agent.js +190 -0
  68. package/dist/tool-calls/reporters/bash.d.ts +9 -0
  69. package/dist/tool-calls/reporters/bash.d.ts.map +1 -0
  70. package/dist/tool-calls/reporters/bash.js +83 -0
  71. package/dist/tool-calls/reporters/compaction.d.ts +24 -0
  72. package/dist/tool-calls/reporters/compaction.d.ts.map +1 -0
  73. package/dist/tool-calls/reporters/compaction.js +66 -0
  74. package/dist/tool-calls/reporters/file-edit.d.ts +46 -0
  75. package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -0
  76. package/dist/tool-calls/reporters/file-edit.js +180 -0
  77. package/dist/tool-calls/reporters/index.d.ts +4 -0
  78. package/dist/tool-calls/reporters/index.d.ts.map +1 -0
  79. package/dist/tool-calls/reporters/index.js +40 -0
  80. package/dist/tool-calls/reporters/interaction.d.ts +71 -0
  81. package/dist/tool-calls/reporters/interaction.d.ts.map +1 -0
  82. package/dist/tool-calls/reporters/interaction.js +222 -0
  83. package/dist/tool-calls/reporters/read.d.ts +7 -0
  84. package/dist/tool-calls/reporters/read.d.ts.map +1 -0
  85. package/dist/tool-calls/reporters/read.js +64 -0
  86. package/dist/tool-calls/reporters/search.d.ts +10 -0
  87. package/dist/tool-calls/reporters/search.d.ts.map +1 -0
  88. package/dist/tool-calls/reporters/search.js +50 -0
  89. package/dist/tool-calls/reporters/web.d.ts +11 -0
  90. package/dist/tool-calls/reporters/web.d.ts.map +1 -0
  91. package/dist/tool-calls/reporters/web.js +42 -0
  92. package/dist/tools.d.ts +32 -37
  93. package/dist/tools.d.ts.map +1 -1
  94. package/dist/tools.js +48 -970
  95. package/package.json +4 -1
@@ -1,5 +1,10 @@
1
1
  import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
2
+ import { access, open, readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { claudeConfigDir } from "./paths.js";
2
5
  import { SessionTiming } from "./session-timing.js";
6
+ /** The size of one backward read of a transcript. */
7
+ const TAIL_CHUNK_BYTES = 64 * 1024;
3
8
  /** Return the concrete model recorded by the last real assistant response.
4
9
  * Claude Code restores a resumed query from this same transcript field.
5
10
  * Synthetic assistant records use angle-bracket placeholders and do not
@@ -14,13 +19,19 @@ export function resumedModelFromTranscript(messages) {
14
19
  typeof entry.message !== "object") {
15
20
  continue;
16
21
  }
17
- const model = entry.message.model;
18
- if (typeof model === "string" && model.trim().length > 0 && !/^<[^>]+>$/.test(model.trim())) {
19
- return model.trim();
20
- }
22
+ const model = concreteModel(entry.message.model);
23
+ if (model)
24
+ return model;
21
25
  }
22
26
  return undefined;
23
27
  }
28
+ /** The model name, or undefined for a missing or a synthetic `<…>` model. */
29
+ function concreteModel(model) {
30
+ if (typeof model !== "string")
31
+ return undefined;
32
+ const name = model.trim();
33
+ return name.length > 0 && !/^<[^>]+>$/.test(name) ? name : undefined;
34
+ }
24
35
  /** Read the resume model from the local transcript without starting a Claude
25
36
  * control request. This is intentionally on the load critical path. */
26
37
  export async function readResumedSession(sessionId, logger) {
@@ -40,3 +51,94 @@ export async function readResumedSession(sessionId, logger) {
40
51
  return {};
41
52
  }
42
53
  }
54
+ /**
55
+ * Read the resume model of a session from the end of its local transcript.
56
+ *
57
+ * A resume needs only the model, not the messages. The transcript file is
58
+ * read backwards until the last real assistant record of the main thread, so
59
+ * the cost does not grow with the length of the session. A session without a
60
+ * local transcript file falls back to {@link readResumedSession}.
61
+ */
62
+ export async function readResumedModel(sessionId, logger) {
63
+ const timing = new SessionTiming(logger, "models", sessionId);
64
+ try {
65
+ const filePath = await findTranscript(sessionId);
66
+ if (filePath) {
67
+ const model = await lastAssistantModel(filePath);
68
+ timing.phase("read-transcript-tail", ` model=${model ?? "unknown"}`);
69
+ return model;
70
+ }
71
+ }
72
+ catch (error) {
73
+ timing.phase("read-transcript-tail", " outcome=error");
74
+ logger?.error(`Failed to read the transcript tail of resumed session ${sessionId}:`, error);
75
+ }
76
+ return (await readResumedSession(sessionId, logger)).model;
77
+ }
78
+ /** The local transcript of a session in any project directory, as the SDK looks it up. */
79
+ export async function findTranscript(sessionId) {
80
+ const projects = path.join(claudeConfigDir(), "projects");
81
+ let directories;
82
+ try {
83
+ directories = await readdir(projects);
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ for (const directory of directories) {
89
+ const candidate = path.join(projects, directory, `${sessionId}.jsonl`);
90
+ try {
91
+ await access(candidate);
92
+ return candidate;
93
+ }
94
+ catch {
95
+ // Not in this project directory.
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+ /** The model of the last real assistant record of the main thread in a JSONL transcript. */
101
+ async function lastAssistantModel(filePath) {
102
+ const handle = await open(filePath, "r");
103
+ try {
104
+ let end = (await handle.stat()).size;
105
+ // The bytes of the line whose start is not read yet, in file order.
106
+ let pending = [];
107
+ while (end > 0) {
108
+ const start = Math.max(0, end - TAIL_CHUNK_BYTES);
109
+ let chunk = Buffer.alloc(end - start);
110
+ await handle.read(chunk, 0, chunk.length, start);
111
+ for (let at = chunk.lastIndexOf(0x0a); at >= 0; at = chunk.lastIndexOf(0x0a)) {
112
+ const model = assistantModelOfLine(Buffer.concat([chunk.subarray(at + 1), ...pending]));
113
+ if (model)
114
+ return model;
115
+ pending = [];
116
+ chunk = chunk.subarray(0, at);
117
+ }
118
+ pending.unshift(chunk);
119
+ end = start;
120
+ }
121
+ return assistantModelOfLine(Buffer.concat(pending));
122
+ }
123
+ finally {
124
+ await handle.close();
125
+ }
126
+ }
127
+ function assistantModelOfLine(line) {
128
+ // Most lines are not assistant records. The check skips their JSON parse.
129
+ if (line.indexOf('"assistant"') < 0)
130
+ return undefined;
131
+ let entry;
132
+ try {
133
+ entry = JSON.parse(line.toString("utf8"));
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ if (!entry || typeof entry !== "object")
139
+ return undefined;
140
+ const record = entry;
141
+ if (record.type !== "assistant" || record.isSidechain === true)
142
+ return undefined;
143
+ return concreteModel(record.message?.model);
144
+ }
@@ -27,6 +27,8 @@ export type SessionModeManagerOptions<S extends SessionMode> = {
27
27
  * known after `initialize`. */
28
28
  supportsNotices?(): boolean;
29
29
  logError(...args: unknown[]): void;
30
+ /** Whether the client is AIR. Only AIR gets the mode kind, under `_meta.jetbrains.air`. */
31
+ airClient?(): boolean;
30
32
  };
31
33
  type ModeConfigSession = SessionMode & {
32
34
  configOptions: SessionConfigOption[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-mode.d.ts","sourceRoot":"","sources":["../src/session-mode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-mode.d.ts","sourceRoot":"","sources":["../src/session-mode.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { RequestError, } from "@agentclientprotocol/sdk";
2
+ import { AIR_KIND_KEY, airOnlyMeta } from "./air-extension.js";
2
3
  import { noticeOrTranscriptUpdate, noticeTranscriptText, sentenceCase, } from "./session-notices.js";
3
4
  export const MODE_CONFIG_ID = "mode";
4
5
  export const AUTO_MODE_FALLBACK = "acceptEdits";
@@ -201,30 +202,36 @@ export class SessionModeManager {
201
202
  }
202
203
  }
203
204
  buildAvailableModes(allowBypass) {
205
+ // Only AIR gets the mode kind.
206
+ const air = this.options.airClient?.() ?? false;
207
+ const kind = (value) => {
208
+ const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
209
+ return meta ? { _meta: meta } : {};
210
+ };
204
211
  const modes = [
205
212
  {
206
213
  id: "default",
207
214
  name: "Manual",
208
215
  description: "Always ask before making changes",
209
- _meta: { kind: "standard" },
216
+ ...kind("standard"),
210
217
  },
211
218
  {
212
219
  id: "acceptEdits",
213
220
  name: "Accept edits",
214
221
  description: "Automatically accept all file edits",
215
- _meta: { kind: "standard" },
222
+ ...kind("standard"),
216
223
  },
217
224
  {
218
225
  id: "plan",
219
226
  name: "Plan",
220
227
  description: "Create a plan before making changes",
221
- _meta: { kind: "plan" },
228
+ ...kind("plan"),
222
229
  },
223
230
  {
224
231
  id: "auto",
225
232
  name: "Auto",
226
233
  description: "Claude handles permission decisions",
227
- _meta: { kind: "auto_review" },
234
+ ...kind("auto_review"),
228
235
  },
229
236
  ];
230
237
  if (allowBypass) {
@@ -232,7 +239,7 @@ export class SessionModeManager {
232
239
  id: "bypassPermissions",
233
240
  name: "Bypass permissions",
234
241
  description: "Accepts all permissions",
235
- _meta: { kind: "full_access" },
242
+ ...kind("full_access"),
236
243
  });
237
244
  }
238
245
  return modes;
package/dist/settings.js CHANGED
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  // resolveSettings and filterEscalatingDefaultMode are marked @alpha in the
4
4
  // SDK; API may shift in a future release.
5
5
  import { filterEscalatingDefaultMode, resolveSettings, } from "@anthropic-ai/claude-agent-sdk";
6
- import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
6
+ import { claudeConfigDir } from "./paths.js";
7
7
  /**
8
8
  * Permission rule format examples:
9
9
  * - "Read" - matches all Read tool calls
@@ -75,7 +75,7 @@ export class SettingsManager {
75
75
  */
76
76
  getWatchedPaths() {
77
77
  return [
78
- path.join(CLAUDE_CONFIG_DIR, "settings.json"),
78
+ path.join(claudeConfigDir(), "settings.json"),
79
79
  path.join(this.cwd, ".claude", "settings.json"),
80
80
  path.join(this.cwd, ".claude", "settings.local.json"),
81
81
  getManagedSettingsPath(),
@@ -0,0 +1,20 @@
1
+ /** Where the subagent transcripts of a session are. */
2
+ export interface SubagentHistory {
3
+ /** The subagent id of each Agent or Task tool use that launched a subagent. */
4
+ ids: Map<string, string>;
5
+ /**
6
+ * The project directory of the session, as the SDK takes it in `dir`. With
7
+ * it, the SDK reads a subagent transcript without a search of every project.
8
+ */
9
+ dir?: string;
10
+ }
11
+ /**
12
+ * Finds the subagent transcripts of a session.
13
+ *
14
+ * Claude keeps the history of a subagent in its own transcript,
15
+ * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
16
+ * The `agent-<id>.meta.json` file beside it names the launching tool use.
17
+ * The ids are empty for a session without a local transcript.
18
+ */
19
+ export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
20
+ //# sourceMappingURL=subagent-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAKA,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA6BjF"}
@@ -0,0 +1,46 @@
1
+ import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { findTranscript } from "./resumed-session.js";
5
+ /**
6
+ * Finds the subagent transcripts of a session.
7
+ *
8
+ * Claude keeps the history of a subagent in its own transcript,
9
+ * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
10
+ * The `agent-<id>.meta.json` file beside it names the launching tool use.
11
+ * The ids are empty for a session without a local transcript.
12
+ */
13
+ export async function subagentHistory(sessionId) {
14
+ const ids = new Map();
15
+ const transcript = await findTranscript(sessionId);
16
+ if (!transcript)
17
+ return { ids };
18
+ const directory = path.join(path.dirname(transcript), sessionId, "subagents");
19
+ let names;
20
+ try {
21
+ names = await readdir(directory);
22
+ }
23
+ catch {
24
+ return { ids };
25
+ }
26
+ await Promise.all(names.map(async (name) => {
27
+ const match = /^agent-(.+)\.meta\.json$/.exec(name);
28
+ if (!match)
29
+ return;
30
+ try {
31
+ const meta = JSON.parse(await readFile(path.join(directory, name), "utf8"));
32
+ const toolUseId = meta?.toolUseId;
33
+ if (typeof toolUseId === "string")
34
+ ids.set(toolUseId, match[1]);
35
+ }
36
+ catch {
37
+ // A damaged meta file leaves its subagent without history.
38
+ }
39
+ }));
40
+ if (ids.size === 0)
41
+ return { ids };
42
+ // The folder name encodes the project directory, and the encoding loses
43
+ // characters. The session info reads it back from the transcript.
44
+ const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
45
+ return dir ? { ids, dir } : { ids };
46
+ }
@@ -0,0 +1,20 @@
1
+ import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ import type { AsyncTaskStarted } from "../async-tasks.js";
3
+ /**
4
+ * Marks the Bash `tool_call_update` whose command detached into the background.
5
+ *
6
+ * A backgrounded Bash call returns as soon as the command is handed off, so the
7
+ * card reaches `completed` while the command itself runs on for minutes. ACP has
8
+ * no tool-call status for "still running elsewhere", so this marker is what lets
9
+ * a client render the card as backgrounded work instead of finished work. It
10
+ * rides the update the tool result already emits, so it costs no extra
11
+ * notification and cannot arrive out of order.
12
+ *
13
+ * The command's own lifecycle -- progress, completion, the stop control -- is
14
+ * published separately as an async task; this says only that the card has one.
15
+ * Hence the AIR namespace rather than `claudeCode`: to a client without the
16
+ * `asyncTasks` capability, which is never sent that lifecycle, the marker would
17
+ * promise a card state it has no way to ever resolve.
18
+ */
19
+ export declare function backgroundedBashToolCall(notification: SessionNotification, task: AsyncTaskStarted | undefined, asyncTasksSupported: boolean): SessionNotification;
20
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/tool-calls/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,mBAAmB,EACjC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAClC,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAkBrB"}
@@ -0,0 +1,41 @@
1
+ import { AIR_ASYNC_TASKS_CAPABILITY, withAirMeta } from "../air-extension.js";
2
+ /**
3
+ * Marks the Bash `tool_call_update` whose command detached into the background.
4
+ *
5
+ * A backgrounded Bash call returns as soon as the command is handed off, so the
6
+ * card reaches `completed` while the command itself runs on for minutes. ACP has
7
+ * no tool-call status for "still running elsewhere", so this marker is what lets
8
+ * a client render the card as backgrounded work instead of finished work. It
9
+ * rides the update the tool result already emits, so it costs no extra
10
+ * notification and cannot arrive out of order.
11
+ *
12
+ * The command's own lifecycle -- progress, completion, the stop control -- is
13
+ * published separately as an async task; this says only that the card has one.
14
+ * Hence the AIR namespace rather than `claudeCode`: to a client without the
15
+ * `asyncTasks` capability, which is never sent that lifecycle, the marker would
16
+ * promise a card state it has no way to ever resolve.
17
+ */
18
+ export function backgroundedBashToolCall(notification, task, asyncTasksSupported) {
19
+ const update = notification.update;
20
+ const toolCallId = task ? nonBlankTaskField(task.toolCallId ?? task.tool_use_id) : undefined;
21
+ if (!asyncTasksSupported ||
22
+ !toolCallId ||
23
+ update.sessionUpdate !== "tool_call_update" ||
24
+ update.toolCallId !== toolCallId) {
25
+ return notification;
26
+ }
27
+ return {
28
+ ...notification,
29
+ update: {
30
+ ...update,
31
+ _meta: withAirMeta(update._meta, AIR_ASYNC_TASKS_CAPABILITY, { backgrounded: true }),
32
+ },
33
+ };
34
+ }
35
+ /** The task fields arrive as `unknown` off the wire; only non-blank strings carry a link. */
36
+ function nonBlankTaskField(value) {
37
+ if (typeof value !== "string")
38
+ return undefined;
39
+ const trimmed = value.trim();
40
+ return trimmed ? trimmed : undefined;
41
+ }
@@ -0,0 +1,27 @@
1
+ import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ type SessionUpdate = SessionNotification["update"];
3
+ /**
4
+ * Removes the `_meta` keys of a `tool_call_update` that an earlier report of
5
+ * the same tool call already sent with the same value.
6
+ *
7
+ * Only an AIR client gets the filtered updates, because AIR merges these
8
+ * `_meta` keys. ACP does not define a merge for `_meta` keys, so another
9
+ * client gets every key on every update.
10
+ *
11
+ * It runs on the final notification, after the native subagent routing,
12
+ * because the routing reads `claudeCode.toolName` and
13
+ * `claudeCode.parentToolUseId` on every update. It compares the keys of
14
+ * `claudeCode`, of `jetbrains.air`, and the flags `is_mcp_tool_call` and
15
+ * `terminal_info`. It never compares `terminal_output`,
16
+ * `terminal_output_delta`, `terminal_exit`, or `mcp_output_delta`: a client
17
+ * appends their data. An update with nothing left is dropped.
18
+ */
19
+ export declare class ChangedMetaFilter {
20
+ private readonly sent;
21
+ /** Returns the update without the unchanged keys, or null when nothing is left. */
22
+ apply(update: SessionUpdate): SessionUpdate | null;
23
+ /** Stores the entry as the most recent one, and forgets the oldest one past the limit. */
24
+ private remember;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=changed-meta-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changed-meta-filter.d.ts","sourceRoot":"","sources":["../../src/tool-calls/changed-meta-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,KAAK,aAAa,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAWnD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0C;IAE/D,mFAAmF;IACnF,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;IAuBlD,0FAA0F;IAC1F,OAAO,CAAC,QAAQ;CAQjB"}
@@ -0,0 +1,102 @@
1
+ /** The `_meta` namespaces whose keys a client merges one by one. */
2
+ const MERGED_NAMESPACES = ["claudeCode"];
3
+ /** The top-level `_meta` flags that stay the same for a tool call. */
4
+ const FLAGS = ["is_mcp_tool_call", "terminal_info"];
5
+ /** The number of tool calls whose `_meta` the filter remembers. */
6
+ const TOOL_CALL_LIMIT = 2048;
7
+ /**
8
+ * Removes the `_meta` keys of a `tool_call_update` that an earlier report of
9
+ * the same tool call already sent with the same value.
10
+ *
11
+ * Only an AIR client gets the filtered updates, because AIR merges these
12
+ * `_meta` keys. ACP does not define a merge for `_meta` keys, so another
13
+ * client gets every key on every update.
14
+ *
15
+ * It runs on the final notification, after the native subagent routing,
16
+ * because the routing reads `claudeCode.toolName` and
17
+ * `claudeCode.parentToolUseId` on every update. It compares the keys of
18
+ * `claudeCode`, of `jetbrains.air`, and the flags `is_mcp_tool_call` and
19
+ * `terminal_info`. It never compares `terminal_output`,
20
+ * `terminal_output_delta`, `terminal_exit`, or `mcp_output_delta`: a client
21
+ * appends their data. An update with nothing left is dropped.
22
+ */
23
+ export class ChangedMetaFilter {
24
+ sent = new Map();
25
+ /** Returns the update without the unchanged keys, or null when nothing is left. */
26
+ apply(update) {
27
+ if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
28
+ return update;
29
+ }
30
+ let held = this.sent.get(update.toolCallId);
31
+ if (update.sessionUpdate === "tool_call" || !held) {
32
+ held = new Map();
33
+ this.remember(update.toolCallId, held);
34
+ recordAndStrip(held, update._meta, false);
35
+ return update;
36
+ }
37
+ this.remember(update.toolCallId, held);
38
+ if (!update._meta)
39
+ return update;
40
+ const meta = structuredClone(update._meta);
41
+ recordAndStrip(held, meta, true);
42
+ const filtered = { ...update, _meta: meta };
43
+ if (Object.keys(meta).length === 0)
44
+ delete filtered._meta;
45
+ const carriesSomething = Object.keys(filtered).some((key) => key !== "sessionUpdate" && key !== "toolCallId");
46
+ return carriesSomething ? filtered : null;
47
+ }
48
+ /** Stores the entry as the most recent one, and forgets the oldest one past the limit. */
49
+ remember(toolCallId, held) {
50
+ this.sent.delete(toolCallId);
51
+ this.sent.set(toolCallId, held);
52
+ if (this.sent.size > TOOL_CALL_LIMIT) {
53
+ const oldest = this.sent.keys().next().value;
54
+ if (oldest !== undefined)
55
+ this.sent.delete(oldest);
56
+ }
57
+ }
58
+ }
59
+ /** Records the compared keys of `meta`. With `strip`, deletes the unchanged ones. */
60
+ function recordAndStrip(held, meta, strip) {
61
+ if (!meta)
62
+ return;
63
+ const keep = (key, value) => {
64
+ const json = JSON.stringify(value);
65
+ if (strip && held.get(key) === json)
66
+ return false;
67
+ held.set(key, json);
68
+ return true;
69
+ };
70
+ const mergeKeys = (prefix, values) => {
71
+ for (const [key, value] of Object.entries(values)) {
72
+ if (!keep(prefix + key, value))
73
+ delete values[key];
74
+ }
75
+ };
76
+ for (const namespace of MERGED_NAMESPACES) {
77
+ const values = asRecord(meta[namespace]);
78
+ if (!values)
79
+ continue;
80
+ mergeKeys(`${namespace}.`, values);
81
+ if (Object.keys(values).length === 0)
82
+ delete meta[namespace];
83
+ }
84
+ const jetbrains = asRecord(meta.jetbrains);
85
+ const air = asRecord(jetbrains?.air);
86
+ if (jetbrains && air) {
87
+ mergeKeys("jetbrains.air.", air);
88
+ if (Object.keys(air).length === 0)
89
+ delete jetbrains.air;
90
+ if (Object.keys(jetbrains).length === 0)
91
+ delete meta.jetbrains;
92
+ }
93
+ for (const flag of FLAGS) {
94
+ if (meta[flag] !== undefined && !keep(flag, meta[flag]))
95
+ delete meta[flag];
96
+ }
97
+ }
98
+ function asRecord(value) {
99
+ return value && typeof value === "object" && !Array.isArray(value)
100
+ ? value
101
+ : undefined;
102
+ }
@@ -0,0 +1,51 @@
1
+ import type { ClientCapabilities as AcpClientCapabilities } from "@agentclientprotocol/sdk";
2
+ /**
3
+ * The client choices that decide the shape of a tool call report.
4
+ *
5
+ * The agent reads them once from `initialize.clientCapabilities`. The public
6
+ * `toAcpNotifications` functions take the ACP capabilities and read them on
7
+ * each call. The {@link AcpToolCallRenderer} reads nothing else, so one object
8
+ * holds every capability choice of a tool call report.
9
+ */
10
+ export declare class ClientCapabilities {
11
+ /** The client renders a terminal from `_meta.terminal_info`, `terminal_output`, and `terminal_exit`. */
12
+ readonly terminalOutput: boolean;
13
+ /** The client appends `_meta.terminal_output_delta` instead of `terminal_output` chunks. */
14
+ readonly terminalOutputDelta: boolean;
15
+ /** The client accepts an exact git patch in a diff (`jetbrains.air` `diffPatch`). */
16
+ readonly diffPatch: boolean;
17
+ /** The capabilities that only JetBrains AIR declares. */
18
+ readonly air: AirCapabilities;
19
+ constructor(
20
+ /** The client renders a terminal from `_meta.terminal_info`, `terminal_output`, and `terminal_exit`. */
21
+ terminalOutput?: boolean,
22
+ /** The client appends `_meta.terminal_output_delta` instead of `terminal_output` chunks. */
23
+ terminalOutputDelta?: boolean,
24
+ /** The client accepts an exact git patch in a diff (`jetbrains.air` `diffPatch`). */
25
+ diffPatch?: boolean,
26
+ /** The capabilities that only JetBrains AIR declares. */
27
+ air?: AirCapabilities);
28
+ static from(capabilities: AcpClientCapabilities | null | undefined): ClientCapabilities;
29
+ }
30
+ /** The capabilities of JetBrains AIR, read from `_meta.jetbrains.air`. */
31
+ export interface AirCapabilities {
32
+ /**
33
+ * The client declared `_meta.jetbrains.air`. Only then does a report follow
34
+ * the tool call contract of `docs/air-extensions.md`. Every other client
35
+ * gets the fields of the upstream adapter, and only the unchanged fields of
36
+ * an update are left out.
37
+ */
38
+ readonly client: boolean;
39
+ /**
40
+ * AIR renders `rawInput` itself. A tool call report for AIR then carries no
41
+ * display copy of the input in `content`.
42
+ */
43
+ readonly rawInputRendering: boolean;
44
+ /**
45
+ * AIR reads the plan of an ExitPlanMode from the file that
46
+ * `rawInput.planFilePath` names. A report then carries the path and not the
47
+ * plan text, when the plan file exists.
48
+ */
49
+ readonly planFile: boolean;
50
+ }
51
+ //# sourceMappingURL=client-capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-capabilities.d.ts","sourceRoot":"","sources":["../../src/tool-calls/client-capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAS5F;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAE3B,wGAAwG;IACxG,QAAQ,CAAC,cAAc,EAAE,OAAO;IAChC,4FAA4F;IAC5F,QAAQ,CAAC,mBAAmB,EAAE,OAAO;IACrC,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,OAAO;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,eAAe;;IAP7B,wGAAwG;IAC/F,cAAc,GAAE,OAAe;IACxC,4FAA4F;IACnF,mBAAmB,GAAE,OAAe;IAC7C,qFAAqF;IAC5E,SAAS,GAAE,OAAe;IACnC,yDAAyD;IAChD,GAAG,GAAE,eAAwB;IAGxC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB;CAiBxF;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B"}
@@ -0,0 +1,43 @@
1
+ import { AIR_DIFF_PATCH_CAPABILITY, AIR_PLAN_FILE_CAPABILITY, AIR_RAW_INPUT_RENDERING_CAPABILITY, clientSupportsAirCapability, isAirClient, } from "../air-extension.js";
2
+ /**
3
+ * The client choices that decide the shape of a tool call report.
4
+ *
5
+ * The agent reads them once from `initialize.clientCapabilities`. The public
6
+ * `toAcpNotifications` functions take the ACP capabilities and read them on
7
+ * each call. The {@link AcpToolCallRenderer} reads nothing else, so one object
8
+ * holds every capability choice of a tool call report.
9
+ */
10
+ export class ClientCapabilities {
11
+ terminalOutput;
12
+ terminalOutputDelta;
13
+ diffPatch;
14
+ air;
15
+ constructor(
16
+ /** The client renders a terminal from `_meta.terminal_info`, `terminal_output`, and `terminal_exit`. */
17
+ terminalOutput = false,
18
+ /** The client appends `_meta.terminal_output_delta` instead of `terminal_output` chunks. */
19
+ terminalOutputDelta = false,
20
+ /** The client accepts an exact git patch in a diff (`jetbrains.air` `diffPatch`). */
21
+ diffPatch = false,
22
+ /** The capabilities that only JetBrains AIR declares. */
23
+ air = NO_AIR) {
24
+ this.terminalOutput = terminalOutput;
25
+ this.terminalOutputDelta = terminalOutputDelta;
26
+ this.diffPatch = diffPatch;
27
+ this.air = air;
28
+ }
29
+ static from(capabilities) {
30
+ const meta = capabilities?._meta;
31
+ const terminalOutputDelta = meta?.["terminal_output_delta"] === true;
32
+ return new ClientCapabilities(terminalOutputDelta || meta?.["terminal_output"] === true, terminalOutputDelta, clientSupportsAirCapability(capabilities, AIR_DIFF_PATCH_CAPABILITY), {
33
+ client: isAirClient(capabilities),
34
+ rawInputRendering: clientSupportsAirCapability(capabilities, AIR_RAW_INPUT_RENDERING_CAPABILITY),
35
+ planFile: clientSupportsAirCapability(capabilities, AIR_PLAN_FILE_CAPABILITY),
36
+ });
37
+ }
38
+ }
39
+ const NO_AIR = {
40
+ client: false,
41
+ rawInputRendering: false,
42
+ planFile: false,
43
+ };