@agentclientprotocol/claude-agent-acp 0.81.2 → 0.81.3-preview.2
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.
- package/README.md +8 -5
- package/dist/acp-agent.d.ts +32 -40
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +557 -577
- package/dist/acp-subagents.d.ts +6 -0
- package/dist/acp-subagents.d.ts.map +1 -1
- package/dist/air-extension.d.ts +31 -1
- package/dist/air-extension.d.ts.map +1 -1
- package/dist/air-extension.js +35 -1
- package/dist/async-tasks.d.ts +39 -2
- package/dist/async-tasks.d.ts.map +1 -1
- package/dist/async-tasks.js +147 -44
- package/dist/context-compaction-meta.d.ts +6 -5
- package/dist/context-compaction-meta.d.ts.map +1 -1
- package/dist/context-compaction-meta.js +10 -10
- package/dist/context-compaction.d.ts +3 -0
- package/dist/context-compaction.d.ts.map +1 -1
- package/dist/context-compaction.js +23 -57
- package/dist/diff.d.ts +73 -5
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +435 -64
- package/dist/elicitation.d.ts +1 -1
- package/dist/elicitation.d.ts.map +1 -1
- package/dist/elicitation.js +12 -14
- package/dist/hide-claude-auth.d.ts +2 -6
- package/dist/hide-claude-auth.d.ts.map +1 -1
- package/dist/hide-claude-auth.js +3 -21
- package/dist/native-subagents.d.ts +58 -0
- package/dist/native-subagents.d.ts.map +1 -1
- package/dist/native-subagents.js +186 -34
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +6 -0
- package/dist/permissions/presentation.d.ts +13 -2
- package/dist/permissions/presentation.d.ts.map +1 -1
- package/dist/permissions/presentation.js +49 -44
- package/dist/resumed-session.d.ts +11 -0
- package/dist/resumed-session.d.ts.map +1 -1
- package/dist/resumed-session.js +106 -4
- package/dist/session-failure-extension.js +1 -1
- package/dist/session-mode.d.ts +2 -0
- package/dist/session-mode.d.ts.map +1 -1
- package/dist/session-mode.js +12 -5
- package/dist/settings.js +2 -2
- package/dist/subagent-history.d.ts +20 -0
- package/dist/subagent-history.d.ts.map +1 -0
- package/dist/subagent-history.js +46 -0
- package/dist/tool-calls/background.d.ts +20 -0
- package/dist/tool-calls/background.d.ts.map +1 -0
- package/dist/tool-calls/background.js +41 -0
- package/dist/tool-calls/changed-meta-filter.d.ts +27 -0
- package/dist/tool-calls/changed-meta-filter.d.ts.map +1 -0
- package/dist/tool-calls/changed-meta-filter.js +102 -0
- package/dist/tool-calls/client-capabilities.d.ts +51 -0
- package/dist/tool-calls/client-capabilities.d.ts.map +1 -0
- package/dist/tool-calls/client-capabilities.js +43 -0
- package/dist/tool-calls/content.d.ts +59 -0
- package/dist/tool-calls/content.d.ts.map +1 -0
- package/dist/tool-calls/content.js +194 -0
- package/dist/tool-calls/facts.d.ts +116 -0
- package/dist/tool-calls/facts.d.ts.map +1 -0
- package/dist/tool-calls/facts.js +1 -0
- package/dist/tool-calls/field-tracker.d.ts +58 -0
- package/dist/tool-calls/field-tracker.d.ts.map +1 -0
- package/dist/tool-calls/field-tracker.js +167 -0
- package/dist/tool-calls/renderer.d.ts +216 -0
- package/dist/tool-calls/renderer.d.ts.map +1 -0
- package/dist/tool-calls/renderer.js +487 -0
- package/dist/tool-calls/reporters/agent.d.ts +7 -0
- package/dist/tool-calls/reporters/agent.d.ts.map +1 -0
- package/dist/tool-calls/reporters/agent.js +190 -0
- package/dist/tool-calls/reporters/bash.d.ts +9 -0
- package/dist/tool-calls/reporters/bash.d.ts.map +1 -0
- package/dist/tool-calls/reporters/bash.js +83 -0
- package/dist/tool-calls/reporters/compaction.d.ts +24 -0
- package/dist/tool-calls/reporters/compaction.d.ts.map +1 -0
- package/dist/tool-calls/reporters/compaction.js +66 -0
- package/dist/tool-calls/reporters/file-edit.d.ts +46 -0
- package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -0
- package/dist/tool-calls/reporters/file-edit.js +180 -0
- package/dist/tool-calls/reporters/index.d.ts +4 -0
- package/dist/tool-calls/reporters/index.d.ts.map +1 -0
- package/dist/tool-calls/reporters/index.js +40 -0
- package/dist/tool-calls/reporters/interaction.d.ts +71 -0
- package/dist/tool-calls/reporters/interaction.d.ts.map +1 -0
- package/dist/tool-calls/reporters/interaction.js +222 -0
- package/dist/tool-calls/reporters/read.d.ts +7 -0
- package/dist/tool-calls/reporters/read.d.ts.map +1 -0
- package/dist/tool-calls/reporters/read.js +64 -0
- package/dist/tool-calls/reporters/search.d.ts +10 -0
- package/dist/tool-calls/reporters/search.d.ts.map +1 -0
- package/dist/tool-calls/reporters/search.js +50 -0
- package/dist/tool-calls/reporters/web.d.ts +11 -0
- package/dist/tool-calls/reporters/web.d.ts.map +1 -0
- package/dist/tool-calls/reporters/web.js +42 -0
- package/dist/tools.d.ts +32 -37
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +48 -970
- package/package.json +4 -1
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { AIR_COMMAND_TITLE_KEY, AIR_SKILL_KEY, AIR_SUBAGENT_KEY, withAirMeta, } from "../air-extension.js";
|
|
2
|
+
import { exitPlanModeRawOutput } from "../exit-plan.js";
|
|
3
|
+
import { ClientCapabilities } from "./client-capabilities.js";
|
|
4
|
+
import { resultText, textContent, toAcpContentUpdate, toolResponseMarkers } from "./content.js";
|
|
5
|
+
import { reporterFor } from "./reporters/index.js";
|
|
6
|
+
import { resolveSkillPath } from "./reporters/interaction.js";
|
|
7
|
+
/** The id suffix of the call that clears the plan file of an approved plan for AIR. */
|
|
8
|
+
const PLAN_FILE_CLEAR_SUFFIX = ":plan-file-clear";
|
|
9
|
+
/**
|
|
10
|
+
* Turns tool facts into the fields of the standard ACP tool call report, as
|
|
11
|
+
* `docs/air-extensions.md#tool-call-contract` defines: each fact goes in one field.
|
|
12
|
+
*
|
|
13
|
+
* The {@link ToolReporter} of the tool reads the SDK data. The renderer
|
|
14
|
+
* decides the fields from the facts and the {@link ClientCapabilities}. The
|
|
15
|
+
* {@link ToolCallFieldTracker} runs after it and drops the fields that an
|
|
16
|
+
* earlier report of the same tool call already sent.
|
|
17
|
+
*/
|
|
18
|
+
export class AcpToolCallRenderer {
|
|
19
|
+
capabilities;
|
|
20
|
+
replay;
|
|
21
|
+
constructor(capabilities = new ClientCapabilities(),
|
|
22
|
+
/** True when the renderer reports the history of a loaded session. */
|
|
23
|
+
replay = false) {
|
|
24
|
+
this.capabilities = capabilities;
|
|
25
|
+
this.replay = replay;
|
|
26
|
+
}
|
|
27
|
+
static for(capabilities, replay = false) {
|
|
28
|
+
return new AcpToolCallRenderer(ClientCapabilities.from(capabilities), replay);
|
|
29
|
+
}
|
|
30
|
+
/** The facts of a tool use. */
|
|
31
|
+
facts(toolUse, cwd) {
|
|
32
|
+
return reporterFor(toolUse.name).toolUse(toolUse.input, {
|
|
33
|
+
cwd,
|
|
34
|
+
capabilities: this.capabilities,
|
|
35
|
+
replay: this.replay,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** The standard fields of a tool use: the title, the kind, the content, and the locations. */
|
|
39
|
+
toolInfo(toolUse, cwd) {
|
|
40
|
+
const facts = this.facts(toolUse, cwd);
|
|
41
|
+
return {
|
|
42
|
+
title: facts.title,
|
|
43
|
+
kind: facts.kind,
|
|
44
|
+
content: this.toolUseContent(toolUse.id, facts),
|
|
45
|
+
...(facts.locations !== undefined ? { locations: facts.locations } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The first report of a tool call. `rawInput` is left out while the input
|
|
50
|
+
* still streams: the consolidated message sends it once it is complete.
|
|
51
|
+
*/
|
|
52
|
+
toolCall(toolUse, options = {}) {
|
|
53
|
+
const facts = this.facts(toolUse, options.cwd);
|
|
54
|
+
const isMcp = toolUse.name.startsWith("mcp__");
|
|
55
|
+
return {
|
|
56
|
+
_meta: {
|
|
57
|
+
...this.toolUseMeta(toolUse, options.cwd),
|
|
58
|
+
...(facts.command && this.capabilities.terminalOutput
|
|
59
|
+
? { terminal_info: { terminal_id: toolUse.id } }
|
|
60
|
+
: {}),
|
|
61
|
+
// Only AIR gets the MCP flag. The upstream adapter does not send it.
|
|
62
|
+
...(isMcp && this.capabilities.air.client ? { is_mcp_tool_call: true } : {}),
|
|
63
|
+
},
|
|
64
|
+
toolCallId: toolUse.id,
|
|
65
|
+
sessionUpdate: "tool_call",
|
|
66
|
+
name: toolUse.name,
|
|
67
|
+
// AIR gets `rawInput` once it is complete. Every other client gets the
|
|
68
|
+
// input as it stands, also the empty input at the stream start.
|
|
69
|
+
...(options.inputComplete === false && this.capabilities.air.client
|
|
70
|
+
? {}
|
|
71
|
+
: { rawInput: this.rawInput(facts, toolUse.input) }),
|
|
72
|
+
status: "pending",
|
|
73
|
+
title: facts.title,
|
|
74
|
+
kind: facts.kind,
|
|
75
|
+
content: options.previewContent ?? this.toolUseContent(toolUse.id, facts),
|
|
76
|
+
...(facts.locations !== undefined ? { locations: facts.locations } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** The report of a tool call whose input is complete now. */
|
|
80
|
+
refinement(toolUse, cwd) {
|
|
81
|
+
const facts = this.facts(toolUse, cwd);
|
|
82
|
+
return {
|
|
83
|
+
_meta: this.toolUseMeta(toolUse, cwd),
|
|
84
|
+
toolCallId: toolUse.id,
|
|
85
|
+
sessionUpdate: "tool_call_update",
|
|
86
|
+
rawInput: this.rawInput(facts, toolUse.input),
|
|
87
|
+
title: facts.title,
|
|
88
|
+
kind: facts.kind,
|
|
89
|
+
content: this.toolUseContent(toolUse.id, facts),
|
|
90
|
+
...(facts.locations !== undefined ? { locations: facts.locations } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The report of a tool call from the complete top-level fields of its still
|
|
95
|
+
* streaming input. It carries no content: content built from partial input
|
|
96
|
+
* is misleading (an Edit without its `new_string` renders as a deletion) or
|
|
97
|
+
* invalid. AIR gets no `rawInput`, which goes out once it is complete. Every
|
|
98
|
+
* other client gets the partial input as `rawInput`.
|
|
99
|
+
*/
|
|
100
|
+
partialRefinement(toolUse, input, cwd) {
|
|
101
|
+
const facts = this.facts({ name: toolUse.name, input }, cwd);
|
|
102
|
+
return {
|
|
103
|
+
_meta: this.toolUseMeta({ name: toolUse.name, input }, cwd),
|
|
104
|
+
toolCallId: toolUse.id,
|
|
105
|
+
sessionUpdate: "tool_call_update",
|
|
106
|
+
...(this.capabilities.air.client ? {} : { rawInput: input }),
|
|
107
|
+
title: facts.title,
|
|
108
|
+
kind: facts.kind,
|
|
109
|
+
...(facts.locations ? { locations: facts.locations } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The tool call of a permission request: `toolCallId`, `title`, and
|
|
114
|
+
* `rawInput`. The client already holds the rest. The request adds only what
|
|
115
|
+
* it shows new: an exact preview patch, and a location that the tool call
|
|
116
|
+
* does not have.
|
|
117
|
+
*/
|
|
118
|
+
permissionToolCall(toolUse, options = {}) {
|
|
119
|
+
const facts = this.facts(toolUse, options.cwd);
|
|
120
|
+
if (!this.capabilities.air.client) {
|
|
121
|
+
// The upstream shape: the whole tool call again.
|
|
122
|
+
const content = this.toolUseContent(toolUse.id, facts);
|
|
123
|
+
const locations = [...(facts.locations ?? []), ...(options.extraLocations ?? [])];
|
|
124
|
+
return {
|
|
125
|
+
toolCallId: toolUse.id,
|
|
126
|
+
name: toolUse.name,
|
|
127
|
+
status: "pending",
|
|
128
|
+
rawInput: toolUse.input,
|
|
129
|
+
title: options.title ?? facts.title,
|
|
130
|
+
kind: facts.kind,
|
|
131
|
+
content: content.length === 0 && options.fallbackContent ? options.fallbackContent : content,
|
|
132
|
+
...(facts.locations !== undefined || options.extraLocations?.length ? { locations } : {}),
|
|
133
|
+
...(options.meta ? { _meta: options.meta } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
toolCallId: toolUse.id,
|
|
138
|
+
title: options.title ?? facts.title,
|
|
139
|
+
rawInput: this.rawInput(facts, toolUse.input),
|
|
140
|
+
...(options.previewContent ? { content: options.previewContent } : {}),
|
|
141
|
+
...(options.extraLocations?.length
|
|
142
|
+
? { locations: [...(facts.locations ?? []), ...options.extraLocations] }
|
|
143
|
+
: {}),
|
|
144
|
+
...(options.meta ? { _meta: options.meta } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** The facts of a tool result. */
|
|
148
|
+
resultFacts(toolUse, result, structured) {
|
|
149
|
+
const reporter = reporterFor(toolUse.name);
|
|
150
|
+
const context = {
|
|
151
|
+
toolUse,
|
|
152
|
+
result,
|
|
153
|
+
structured,
|
|
154
|
+
capabilities: this.capabilities,
|
|
155
|
+
};
|
|
156
|
+
const content = result.content;
|
|
157
|
+
const hasErrorText = result.is_error === true &&
|
|
158
|
+
content !== undefined &&
|
|
159
|
+
content !== null &&
|
|
160
|
+
(typeof content === "string" || Array.isArray(content)) &&
|
|
161
|
+
content.length > 0;
|
|
162
|
+
if (hasErrorText) {
|
|
163
|
+
return reporter.errorResult?.(context) ?? toAcpContentUpdate(content, true);
|
|
164
|
+
}
|
|
165
|
+
return reporter.toolResult ? reporter.toolResult(context) : resultText(result);
|
|
166
|
+
}
|
|
167
|
+
/** The result fields of a tool result, before the status and the tool name. */
|
|
168
|
+
resultFields(toolUse, result, structured) {
|
|
169
|
+
const facts = this.resultFacts(toolUse, result, structured);
|
|
170
|
+
const { command, ...fields } = facts;
|
|
171
|
+
if (!command)
|
|
172
|
+
return fields;
|
|
173
|
+
// The terminal was announced under the tool_use id (see `toolCall`), so the
|
|
174
|
+
// output keys off that id. A non-string id is no id at all.
|
|
175
|
+
const idOf = (id) => (typeof id === "string" && id.length > 0 ? id : undefined);
|
|
176
|
+
const terminalId = idOf(toolUse.id) ?? idOf(result.tool_use_id);
|
|
177
|
+
// Without a terminal id nothing can match the output. A client that
|
|
178
|
+
// buffers output for an unknown terminal would hold it forever, so the
|
|
179
|
+
// output becomes a code block instead.
|
|
180
|
+
if (this.capabilities.terminalOutput && terminalId !== undefined) {
|
|
181
|
+
const output = { terminal_id: terminalId, data: command.output };
|
|
182
|
+
return {
|
|
183
|
+
...fields,
|
|
184
|
+
// A client that is not AIR gets the terminal content again, like
|
|
185
|
+
// upstream: it replaces a denial text that an earlier update showed.
|
|
186
|
+
...(this.capabilities.air.client
|
|
187
|
+
? {}
|
|
188
|
+
: { content: [{ type: "terminal", terminalId }] }),
|
|
189
|
+
_meta: {
|
|
190
|
+
terminal_info: { terminal_id: terminalId },
|
|
191
|
+
...(this.capabilities.terminalOutputDelta
|
|
192
|
+
? { terminal_output_delta: output }
|
|
193
|
+
: { terminal_output: output }),
|
|
194
|
+
terminal_exit: { terminal_id: terminalId, exit_code: command.exitCode, signal: null },
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return command.output.trim()
|
|
199
|
+
? {
|
|
200
|
+
...fields,
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "content",
|
|
204
|
+
content: {
|
|
205
|
+
type: "text",
|
|
206
|
+
text: `\`\`\`console\n${command.output.trimEnd()}\n\`\`\``,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}
|
|
211
|
+
: fields;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The reports of a tool result. A command sends its output as a separate
|
|
215
|
+
* report first, like codex-acp: the output, then the exit and the status.
|
|
216
|
+
* The output travels once: the raw tool_result goes to `rawOutput` only
|
|
217
|
+
* when no other field carries the result.
|
|
218
|
+
*/
|
|
219
|
+
result(toolUse, result, options = {}) {
|
|
220
|
+
const { _meta: resultMeta, ...fields } = this.resultFields(toolUse, result, options.structured);
|
|
221
|
+
// AIR shows a read or a search that names a path as the list of viewed
|
|
222
|
+
// files. That view does not show the text of the result, so it stays out.
|
|
223
|
+
const viewedFiles = this.capabilities.air.client && result.is_error !== true && this.namesViewedFile(toolUse);
|
|
224
|
+
if (viewedFiles) {
|
|
225
|
+
const content = fields.content?.filter((item) => !(item.type === "content" && item.content.type === "text"));
|
|
226
|
+
if (content?.length)
|
|
227
|
+
fields.content = content;
|
|
228
|
+
else
|
|
229
|
+
delete fields.content;
|
|
230
|
+
}
|
|
231
|
+
const updates = [];
|
|
232
|
+
const terminalOutput = resultMeta?.terminal_output_delta ?? resultMeta?.terminal_output;
|
|
233
|
+
if (terminalOutput) {
|
|
234
|
+
updates.push({
|
|
235
|
+
_meta: resultMeta?.terminal_output_delta
|
|
236
|
+
? { terminal_output_delta: terminalOutput }
|
|
237
|
+
: { terminal_output: terminalOutput },
|
|
238
|
+
toolCallId: toolUse.id,
|
|
239
|
+
sessionUpdate: "tool_call_update",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// AIR gets the raw tool_result only when no other field carries the
|
|
243
|
+
// result. Every other client gets it unless the terminal carried it.
|
|
244
|
+
const rawOutput = !this.capabilities.air.client
|
|
245
|
+
? terminalOutput
|
|
246
|
+
? undefined
|
|
247
|
+
: exitPlanModeRawOutput(toolUse.name, result.content)
|
|
248
|
+
: "rawOutput" in fields
|
|
249
|
+
? fields.rawOutput
|
|
250
|
+
: viewedFiles || terminalOutput || fields.content !== undefined
|
|
251
|
+
? undefined
|
|
252
|
+
: result.content;
|
|
253
|
+
delete fields.rawOutput;
|
|
254
|
+
// A plan file that the result names goes out also when the input did not
|
|
255
|
+
// name it. The field tracker drops a path that the client holds.
|
|
256
|
+
const planFilePath = fields.planFilePath;
|
|
257
|
+
delete fields.planFilePath;
|
|
258
|
+
const planFileReleased = fields.planFileReleased === true;
|
|
259
|
+
delete fields.planFileReleased;
|
|
260
|
+
updates.push({
|
|
261
|
+
_meta: {
|
|
262
|
+
claudeCode: { toolName: toolUse.name, ...(options.nonExecution ?? {}) },
|
|
263
|
+
...(resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}),
|
|
264
|
+
},
|
|
265
|
+
toolCallId: toolUse.id,
|
|
266
|
+
sessionUpdate: "tool_call_update",
|
|
267
|
+
status: result.is_error === true ? "failed" : "completed",
|
|
268
|
+
...(rawOutput !== undefined ? { rawOutput } : {}),
|
|
269
|
+
...(planFilePath ? { rawInput: planFileInput(toolUse.input, planFilePath) } : {}),
|
|
270
|
+
...fields,
|
|
271
|
+
});
|
|
272
|
+
// AIR keeps the plan file button while the last reported path names a
|
|
273
|
+
// file, and a blank path is its clear signal. The clear goes as a call of
|
|
274
|
+
// its own, so the plan card of this call keeps its file.
|
|
275
|
+
if (planFilePath && planFileReleased && result.is_error !== true) {
|
|
276
|
+
updates.push({
|
|
277
|
+
toolCallId: `${toolUse.id}${PLAN_FILE_CLEAR_SUFFIX}`,
|
|
278
|
+
sessionUpdate: "tool_call",
|
|
279
|
+
title: "Exited Plan Mode",
|
|
280
|
+
kind: "switch_mode",
|
|
281
|
+
status: "completed",
|
|
282
|
+
rawInput: { planFilePath: "" },
|
|
283
|
+
content: [],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return updates;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Whether AIR shows [toolUse] as the list of viewed files: a read or a
|
|
290
|
+
* search with a path in its locations or in the `path` of its input.
|
|
291
|
+
*/
|
|
292
|
+
namesViewedFile(toolUse) {
|
|
293
|
+
const facts = this.facts(toolUse);
|
|
294
|
+
if (facts.kind !== "read" && facts.kind !== "search")
|
|
295
|
+
return false;
|
|
296
|
+
if (facts.locations?.some((location) => location.path))
|
|
297
|
+
return true;
|
|
298
|
+
const path = toolUse.input?.path;
|
|
299
|
+
return typeof path === "string" && path.length > 0;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* The report of a PostToolUse hook: the final change of an edit tool, and
|
|
303
|
+
* the `status` and `isAsync` markers of the `tool_response`. The rest of the
|
|
304
|
+
* `tool_response` repeats output that the content already carries.
|
|
305
|
+
*/
|
|
306
|
+
async hookResult(toolUse, toolResponse, cwd) {
|
|
307
|
+
const reporter = reporterFor(toolUse.name);
|
|
308
|
+
const change = reporter.hookResult
|
|
309
|
+
? await reporter.hookResult(toolResponse, { cwd, capabilities: this.capabilities })
|
|
310
|
+
: {};
|
|
311
|
+
if (!this.capabilities.air.client) {
|
|
312
|
+
// The upstream shape: the whole tool_response, on every hook.
|
|
313
|
+
return {
|
|
314
|
+
_meta: { claudeCode: { toolResponse, toolName: toolUse.name } },
|
|
315
|
+
toolCallId: toolUse.id,
|
|
316
|
+
sessionUpdate: "tool_call_update",
|
|
317
|
+
...(change.content ? { content: change.content } : {}),
|
|
318
|
+
...(change.locations ? { locations: change.locations } : {}),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const markers = toolResponseMarkers(toolResponse);
|
|
322
|
+
if (!markers && !change.content && !change.locations)
|
|
323
|
+
return undefined;
|
|
324
|
+
return {
|
|
325
|
+
_meta: {
|
|
326
|
+
claudeCode: { ...(markers ? { toolResponse: markers } : {}), toolName: toolUse.name },
|
|
327
|
+
},
|
|
328
|
+
toolCallId: toolUse.id,
|
|
329
|
+
sessionUpdate: "tool_call_update",
|
|
330
|
+
...(change.content ? { content: change.content } : {}),
|
|
331
|
+
...(change.locations ? { locations: change.locations } : {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The report of a memory recall: a completed read tool call. A synthesis
|
|
336
|
+
* shows the recalled text, a plain recall shows the memory files.
|
|
337
|
+
*/
|
|
338
|
+
memoryRecall(recall) {
|
|
339
|
+
const isSynthesis = recall.mode === "synthesize";
|
|
340
|
+
const locations = isSynthesis ? [] : recall.memories.map((memory) => ({ path: memory.path }));
|
|
341
|
+
const content = isSynthesis
|
|
342
|
+
? recall.memories.flatMap((memory) => typeof memory.content === "string" ? [textContent(memory.content)] : [])
|
|
343
|
+
: [];
|
|
344
|
+
const count = recall.memories.length;
|
|
345
|
+
return {
|
|
346
|
+
sessionUpdate: "tool_call",
|
|
347
|
+
toolCallId: recall.uuid,
|
|
348
|
+
title: isSynthesis
|
|
349
|
+
? "Recalled synthesized memory"
|
|
350
|
+
: `Recalled ${count} ${count === 1 ? "memory" : "memories"}`,
|
|
351
|
+
kind: "read",
|
|
352
|
+
status: "completed",
|
|
353
|
+
...(locations.length > 0 && { locations }),
|
|
354
|
+
...(content.length > 0 && { content }),
|
|
355
|
+
_meta: {
|
|
356
|
+
claudeCode: { toolName: "memory_recall", toolResponse: { mode: recall.mode } },
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* The report of a tool call that a rule, the classifier, or a mode denied
|
|
362
|
+
* before it ran. The reason is the result to show, so `toolResponse` keeps
|
|
363
|
+
* only the reason type, and the SDK message when it differs from the reason.
|
|
364
|
+
*/
|
|
365
|
+
permissionDenied(denial) {
|
|
366
|
+
const reason = denial.decisionReason ?? denial.message;
|
|
367
|
+
// AIR gets the SDK message only when it differs from the reason, which
|
|
368
|
+
// the content shows. Every other client gets the upstream toolResponse.
|
|
369
|
+
const extraMessage = !this.capabilities.air.client
|
|
370
|
+
? { decisionReason: denial.decisionReason, message: denial.message }
|
|
371
|
+
: denial.decisionReason !== undefined &&
|
|
372
|
+
denial.message !== undefined &&
|
|
373
|
+
denial.message !== denial.decisionReason
|
|
374
|
+
? { message: denial.message }
|
|
375
|
+
: {};
|
|
376
|
+
return {
|
|
377
|
+
sessionUpdate: "tool_call_update",
|
|
378
|
+
toolCallId: denial.toolCallId,
|
|
379
|
+
status: "failed",
|
|
380
|
+
content: [textContent(`Permission denied: ${reason}`)],
|
|
381
|
+
_meta: {
|
|
382
|
+
claudeCode: {
|
|
383
|
+
toolName: denial.toolName,
|
|
384
|
+
...(denial.parentToolUseId ? { parentToolUseId: denial.parentToolUseId } : {}),
|
|
385
|
+
toolResponse: { decisionReasonType: denial.decisionReasonType, ...extraMessage },
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The report of a tool progress beat. The field tracker sends the
|
|
392
|
+
* `in_progress` status once, so a later beat carries only the progress.
|
|
393
|
+
*/
|
|
394
|
+
progress(beat) {
|
|
395
|
+
return {
|
|
396
|
+
sessionUpdate: "tool_call_update",
|
|
397
|
+
toolCallId: beat.toolCallId,
|
|
398
|
+
status: "in_progress",
|
|
399
|
+
_meta: {
|
|
400
|
+
claudeCode: {
|
|
401
|
+
...(beat.toolName !== undefined ? { toolName: beat.toolName } : {}),
|
|
402
|
+
...(beat.parentToolUseId ? { parentToolUseId: beat.parentToolUseId } : {}),
|
|
403
|
+
toolResponse: {
|
|
404
|
+
elapsedTimeSeconds: beat.elapsedTimeSeconds,
|
|
405
|
+
// For Agent/Task calls: the subagent type, and the SDK retry
|
|
406
|
+
// counters while the subagent waits out an API rate limit, so a
|
|
407
|
+
// client can show why a spawn looks stalled.
|
|
408
|
+
...(beat.subagentType !== undefined && { subagentType: beat.subagentType }),
|
|
409
|
+
...(beat.subagentRetry !== undefined && { subagentRetry: beat.subagentRetry }),
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* `rawInput`. For AIR, without the file text that a diff holds, and with
|
|
417
|
+
* the path of the plan file in place of the plan text.
|
|
418
|
+
*/
|
|
419
|
+
rawInput(facts, rawInput) {
|
|
420
|
+
if (facts.planFilePath)
|
|
421
|
+
return planFileInput(rawInput, facts.planFilePath);
|
|
422
|
+
if (!this.capabilities.air.client ||
|
|
423
|
+
!facts.fileTextKeys ||
|
|
424
|
+
!rawInput ||
|
|
425
|
+
typeof rawInput !== "object" ||
|
|
426
|
+
Array.isArray(rawInput)) {
|
|
427
|
+
return rawInput;
|
|
428
|
+
}
|
|
429
|
+
const input = { ...rawInput };
|
|
430
|
+
for (const key of facts.fileTextKeys)
|
|
431
|
+
delete input[key];
|
|
432
|
+
return input;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The content of a tool use. A command shows the terminal marker. An edit
|
|
436
|
+
* shows its change. A display copy of the input goes only to a client that
|
|
437
|
+
* does not render `rawInput` itself, and never next to a terminal.
|
|
438
|
+
*/
|
|
439
|
+
toolUseContent(toolCallId, facts) {
|
|
440
|
+
const terminal = facts.command === true && this.capabilities.terminalOutput;
|
|
441
|
+
return [
|
|
442
|
+
...(terminal ? [{ type: "terminal", terminalId: toolCallId }] : []),
|
|
443
|
+
...(facts.change ?? []),
|
|
444
|
+
...(!terminal && !this.capabilities.air.rawInputRendering ? (facts.display ?? []) : []),
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The `_meta` of a tool use report: the tool name, and for AIR the tool
|
|
449
|
+
* flags under `_meta.jetbrains.air`. A shell description stays out of the
|
|
450
|
+
* standard `title`, which clients use as the command preview. Every other
|
|
451
|
+
* client gets only the upstream tool name.
|
|
452
|
+
*/
|
|
453
|
+
toolUseMeta(toolUse, cwd) {
|
|
454
|
+
const input = toolUse.input !== null && typeof toolUse.input === "object"
|
|
455
|
+
? toolUse.input
|
|
456
|
+
: {};
|
|
457
|
+
const description = (toolUse.name === "Bash" || toolUse.name === "PowerShell") &&
|
|
458
|
+
typeof input.description === "string"
|
|
459
|
+
? input.description
|
|
460
|
+
: undefined;
|
|
461
|
+
const skillName = toolUse.name === "Skill" && typeof input.skill === "string" ? input.skill : undefined;
|
|
462
|
+
const subagent = toolUse.name === "Agent" || toolUse.name === "Task";
|
|
463
|
+
if (!this.capabilities.air.client)
|
|
464
|
+
return { claudeCode: { toolName: toolUse.name } };
|
|
465
|
+
let meta = { claudeCode: { toolName: toolUse.name } };
|
|
466
|
+
if (description)
|
|
467
|
+
meta = withAirMeta(meta, AIR_COMMAND_TITLE_KEY, description);
|
|
468
|
+
if (subagent)
|
|
469
|
+
meta = withAirMeta(meta, AIR_SUBAGENT_KEY, true);
|
|
470
|
+
if (skillName) {
|
|
471
|
+
const skillPath = resolveSkillPath(skillName, cwd);
|
|
472
|
+
meta = withAirMeta(meta, AIR_SKILL_KEY, {
|
|
473
|
+
name: skillName,
|
|
474
|
+
...(skillPath ? { path: skillPath } : {}),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return meta;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/** The input with the path of the plan file in place of the plan text. */
|
|
481
|
+
function planFileInput(rawInput, planFilePath) {
|
|
482
|
+
const input = rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)
|
|
483
|
+
? { ...rawInput }
|
|
484
|
+
: {};
|
|
485
|
+
delete input.plan;
|
|
486
|
+
return { ...input, planFilePath };
|
|
487
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ToolReporter, ToolResultContext, ToolResultFacts, ToolUseFacts } from "../facts.js";
|
|
2
|
+
/** Agent and Task: a subagent. Its prompt is input that the user reads. */
|
|
3
|
+
export declare class AgentReporter implements ToolReporter {
|
|
4
|
+
toolUse(input: unknown): ToolUseFacts;
|
|
5
|
+
toolResult({ result, structured }: ToolResultContext): ToolResultFacts;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAElG,2EAA2E;AAC3E,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IASrC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,GAAG,eAAe;CA2BvE"}
|