@tencent-ai/agent-sdk 0.3.41 → 0.3.43
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/cli/CHANGELOG.md +56 -0
- package/cli/dist/codebuddy.js +6 -6
- package/cli/package.json +1 -1
- package/cli/product.cloudhosted.json +20 -7
- package/cli/product.internal.json +20 -7
- package/cli/product.ioa.json +28 -6
- package/cli/product.json +49 -8
- package/cli/product.selfhosted.json +16 -3
- package/lib/index.d.ts +1 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -18
- package/lib/index.js.map +1 -1
- package/lib/query.d.ts.map +1 -1
- package/lib/query.js +7 -21
- package/lib/query.js.map +1 -1
- package/lib/session.d.ts +7 -9
- package/lib/session.d.ts.map +1 -1
- package/lib/session.js +28 -25
- package/lib/session.js.map +1 -1
- package/lib/transport/process-transport.d.ts.map +1 -1
- package/lib/transport/process-transport.js +9 -0
- package/lib/transport/process-transport.js.map +1 -1
- package/lib/types.d.ts +8 -9
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/stream.d.ts +19 -0
- package/lib/utils/stream.d.ts.map +1 -1
- package/lib/utils/stream.js +31 -0
- package/lib/utils/stream.js.map +1 -1
- package/package.json +1 -1
- package/lib/acp/agent.d.ts +0 -427
- package/lib/acp/agent.d.ts.map +0 -1
- package/lib/acp/agent.js +0 -835
- package/lib/acp/agent.js.map +0 -1
- package/lib/acp/converter.d.ts +0 -179
- package/lib/acp/converter.d.ts.map +0 -1
- package/lib/acp/converter.js +0 -1094
- package/lib/acp/converter.js.map +0 -1
- package/lib/acp/index.d.ts +0 -11
- package/lib/acp/index.d.ts.map +0 -1
- package/lib/acp/index.js +0 -20
- package/lib/acp/index.js.map +0 -1
- package/lib/acp/server.d.ts +0 -70
- package/lib/acp/server.d.ts.map +0 -1
- package/lib/acp/server.js +0 -364
- package/lib/acp/server.js.map +0 -1
- package/lib/acp/session-manager.d.ts +0 -33
- package/lib/acp/session-manager.d.ts.map +0 -1
- package/lib/acp/session-manager.js +0 -106
- package/lib/acp/session-manager.js.map +0 -1
- package/lib/acp/session.d.ts +0 -67
- package/lib/acp/session.d.ts.map +0 -1
- package/lib/acp/session.js +0 -263
- package/lib/acp/session.js.map +0 -1
package/lib/acp/converter.js
DELETED
|
@@ -1,1094 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AcpConverter = void 0;
|
|
4
|
-
// ============= Lookup Tables =============
|
|
5
|
-
const TOOL_KIND_MAP = {
|
|
6
|
-
read: 'read',
|
|
7
|
-
notebookread: 'read',
|
|
8
|
-
notebook_read: 'read',
|
|
9
|
-
glob: 'read',
|
|
10
|
-
write: 'edit',
|
|
11
|
-
edit: 'edit',
|
|
12
|
-
multiedit: 'edit',
|
|
13
|
-
notebookedit: 'edit',
|
|
14
|
-
notebook_edit: 'edit',
|
|
15
|
-
bash: 'execute',
|
|
16
|
-
bashoutput: 'execute',
|
|
17
|
-
bash_output: 'execute',
|
|
18
|
-
killshell: 'execute',
|
|
19
|
-
kill_shell: 'execute',
|
|
20
|
-
taskoutput: 'execute',
|
|
21
|
-
task_output: 'execute',
|
|
22
|
-
grep: 'search',
|
|
23
|
-
websearch: 'search',
|
|
24
|
-
web_search: 'search',
|
|
25
|
-
webfetch: 'fetch',
|
|
26
|
-
web_fetch: 'fetch',
|
|
27
|
-
task: 'think',
|
|
28
|
-
todowrite: 'think',
|
|
29
|
-
todo_write: 'think',
|
|
30
|
-
enterplanmode: 'think',
|
|
31
|
-
enter_plan_mode: 'think',
|
|
32
|
-
exitplanmode: 'think',
|
|
33
|
-
exit_plan_mode: 'think',
|
|
34
|
-
};
|
|
35
|
-
const TOOL_TITLE_GENERATORS = {
|
|
36
|
-
read: input => {
|
|
37
|
-
const filePath = input.file_path;
|
|
38
|
-
const offset = input.offset;
|
|
39
|
-
const limit = input.limit;
|
|
40
|
-
if (!filePath) {
|
|
41
|
-
return 'Read';
|
|
42
|
-
}
|
|
43
|
-
if (limit) {
|
|
44
|
-
const startLine = (offset !== null && offset !== void 0 ? offset : 0) + 1;
|
|
45
|
-
const endLine = startLine + limit - 1;
|
|
46
|
-
return `Read ${filePath} (${startLine}-${endLine})`;
|
|
47
|
-
}
|
|
48
|
-
else if (offset) {
|
|
49
|
-
return `Read ${filePath} (from line ${(offset !== null && offset !== void 0 ? offset : 0) + 1})`;
|
|
50
|
-
}
|
|
51
|
-
return `Read ${filePath}`;
|
|
52
|
-
},
|
|
53
|
-
write: input => {
|
|
54
|
-
const filePath = input.file_path;
|
|
55
|
-
return filePath ? `Write ${filePath}` : 'Write';
|
|
56
|
-
},
|
|
57
|
-
edit: input => {
|
|
58
|
-
const filePath = input.file_path;
|
|
59
|
-
return filePath ? `Edit \`${filePath}\`` : 'Edit';
|
|
60
|
-
},
|
|
61
|
-
multiedit: input => {
|
|
62
|
-
const filePath = input.file_path;
|
|
63
|
-
return filePath ? `Edit \`${filePath}\`` : 'Edit';
|
|
64
|
-
},
|
|
65
|
-
bash: input => {
|
|
66
|
-
const command = input.command;
|
|
67
|
-
if (!command) {
|
|
68
|
-
return 'Terminal';
|
|
69
|
-
}
|
|
70
|
-
const escaped = command.split('`').join('\\`');
|
|
71
|
-
return `\`${escaped}\``;
|
|
72
|
-
},
|
|
73
|
-
bashoutput: () => 'Tail Logs',
|
|
74
|
-
bash_output: () => 'Tail Logs',
|
|
75
|
-
killshell: () => 'Kill Process',
|
|
76
|
-
kill_shell: () => 'Kill Process',
|
|
77
|
-
glob: input => {
|
|
78
|
-
let label = 'Find';
|
|
79
|
-
const path = input.path;
|
|
80
|
-
const pattern = input.pattern;
|
|
81
|
-
if (path) {
|
|
82
|
-
label += ` \`${path}\``;
|
|
83
|
-
}
|
|
84
|
-
if (pattern) {
|
|
85
|
-
label += ` \`${pattern}\``;
|
|
86
|
-
}
|
|
87
|
-
return label;
|
|
88
|
-
},
|
|
89
|
-
grep: input => {
|
|
90
|
-
let label = 'grep';
|
|
91
|
-
if (input['-i']) {
|
|
92
|
-
label += ' -i';
|
|
93
|
-
}
|
|
94
|
-
if (input['-n']) {
|
|
95
|
-
label += ' -n';
|
|
96
|
-
}
|
|
97
|
-
if (input['-C'] !== undefined) {
|
|
98
|
-
label += ` -C ${input['-C']}`;
|
|
99
|
-
}
|
|
100
|
-
if (input['-A'] !== undefined) {
|
|
101
|
-
label += ` -A ${input['-A']}`;
|
|
102
|
-
}
|
|
103
|
-
if (input['-B'] !== undefined) {
|
|
104
|
-
label += ` -B ${input['-B']}`;
|
|
105
|
-
}
|
|
106
|
-
if (input.multiline) {
|
|
107
|
-
label += ' -P';
|
|
108
|
-
}
|
|
109
|
-
if (input.glob) {
|
|
110
|
-
label += ` --include="${input.glob}"`;
|
|
111
|
-
}
|
|
112
|
-
if (input.type) {
|
|
113
|
-
label += ` --type=${input.type}`;
|
|
114
|
-
}
|
|
115
|
-
const pattern = input.pattern;
|
|
116
|
-
const path = input.path;
|
|
117
|
-
if (pattern) {
|
|
118
|
-
label += ` "${pattern}"`;
|
|
119
|
-
}
|
|
120
|
-
if (path) {
|
|
121
|
-
label += ` ${path}`;
|
|
122
|
-
}
|
|
123
|
-
return label;
|
|
124
|
-
},
|
|
125
|
-
webfetch: input => {
|
|
126
|
-
const url = input.url;
|
|
127
|
-
return url ? `Fetch ${url}` : 'Fetch';
|
|
128
|
-
},
|
|
129
|
-
web_fetch: input => {
|
|
130
|
-
const url = input.url;
|
|
131
|
-
return url ? `Fetch ${url}` : 'Fetch';
|
|
132
|
-
},
|
|
133
|
-
websearch: input => {
|
|
134
|
-
const query = input.query;
|
|
135
|
-
if (!query) {
|
|
136
|
-
return 'Search';
|
|
137
|
-
}
|
|
138
|
-
let label = `"${query}"`;
|
|
139
|
-
const allowedDomains = input.allowed_domains;
|
|
140
|
-
const blockedDomains = input.blocked_domains;
|
|
141
|
-
if (allowedDomains === null || allowedDomains === void 0 ? void 0 : allowedDomains.length) {
|
|
142
|
-
label += ` (allowed: ${allowedDomains.join(', ')})`;
|
|
143
|
-
}
|
|
144
|
-
if (blockedDomains === null || blockedDomains === void 0 ? void 0 : blockedDomains.length) {
|
|
145
|
-
label += ` (blocked: ${blockedDomains.join(', ')})`;
|
|
146
|
-
}
|
|
147
|
-
return label;
|
|
148
|
-
},
|
|
149
|
-
web_search: input => {
|
|
150
|
-
const query = input.query;
|
|
151
|
-
if (!query) {
|
|
152
|
-
return 'Search';
|
|
153
|
-
}
|
|
154
|
-
let label = `"${query}"`;
|
|
155
|
-
const allowedDomains = input.allowed_domains;
|
|
156
|
-
const blockedDomains = input.blocked_domains;
|
|
157
|
-
if (allowedDomains === null || allowedDomains === void 0 ? void 0 : allowedDomains.length) {
|
|
158
|
-
label += ` (allowed: ${allowedDomains.join(', ')})`;
|
|
159
|
-
}
|
|
160
|
-
if (blockedDomains === null || blockedDomains === void 0 ? void 0 : blockedDomains.length) {
|
|
161
|
-
label += ` (blocked: ${blockedDomains.join(', ')})`;
|
|
162
|
-
}
|
|
163
|
-
return label;
|
|
164
|
-
},
|
|
165
|
-
notebookread: input => {
|
|
166
|
-
const path = input.notebook_path;
|
|
167
|
-
return path ? `Read Notebook ${path}` : 'Read Notebook';
|
|
168
|
-
},
|
|
169
|
-
notebook_read: input => {
|
|
170
|
-
const path = input.notebook_path;
|
|
171
|
-
return path ? `Read Notebook ${path}` : 'Read Notebook';
|
|
172
|
-
},
|
|
173
|
-
notebookedit: input => {
|
|
174
|
-
const path = input.notebook_path;
|
|
175
|
-
return path ? `Edit Notebook ${path}` : 'Edit Notebook';
|
|
176
|
-
},
|
|
177
|
-
notebook_edit: input => {
|
|
178
|
-
const path = input.notebook_path;
|
|
179
|
-
return path ? `Edit Notebook ${path}` : 'Edit Notebook';
|
|
180
|
-
},
|
|
181
|
-
task: input => {
|
|
182
|
-
const description = input.description;
|
|
183
|
-
return description || 'Task';
|
|
184
|
-
},
|
|
185
|
-
todowrite: () => 'Update TODOs',
|
|
186
|
-
todo_write: () => 'Update TODOs',
|
|
187
|
-
askuserquestion: () => 'Ask User',
|
|
188
|
-
ask_user_question: () => 'Ask User',
|
|
189
|
-
slashcommand: input => {
|
|
190
|
-
const command = input.command;
|
|
191
|
-
return command ? `Run: ${command}` : 'Slash Command';
|
|
192
|
-
},
|
|
193
|
-
slash_command: input => {
|
|
194
|
-
const command = input.command;
|
|
195
|
-
return command ? `Run: ${command}` : 'Slash Command';
|
|
196
|
-
},
|
|
197
|
-
skill: input => {
|
|
198
|
-
const command = input.command;
|
|
199
|
-
return command ? `Skill: ${command}` : 'Skill';
|
|
200
|
-
},
|
|
201
|
-
lsp: input => {
|
|
202
|
-
const operation = input.operation;
|
|
203
|
-
return operation ? `LSP: ${operation}` : 'LSP';
|
|
204
|
-
},
|
|
205
|
-
enterplanmode: () => 'Enter Plan Mode',
|
|
206
|
-
enter_plan_mode: () => 'Enter Plan Mode',
|
|
207
|
-
exitplanmode: () => 'Exit Plan Mode',
|
|
208
|
-
exit_plan_mode: () => 'Exit Plan Mode',
|
|
209
|
-
taskoutput: () => 'Get Task Output',
|
|
210
|
-
task_output: () => 'Get Task Output',
|
|
211
|
-
};
|
|
212
|
-
// ============= AcpConverter =============
|
|
213
|
-
class AcpConverter {
|
|
214
|
-
constructor() {
|
|
215
|
-
this.todoWriteToolUseIds = new Set();
|
|
216
|
-
this.taskToolUseIds = new Set();
|
|
217
|
-
// Map from content block index to actual tool call ID
|
|
218
|
-
this.contentBlockIndexToToolCallId = new Map();
|
|
219
|
-
// Map from content block index to tool name (for skipping TodoWrite streaming updates)
|
|
220
|
-
this.contentBlockIndexToToolName = new Map();
|
|
221
|
-
}
|
|
222
|
-
reset() {
|
|
223
|
-
this.todoWriteToolUseIds.clear();
|
|
224
|
-
this.taskToolUseIds.clear();
|
|
225
|
-
this.contentBlockIndexToToolCallId.clear();
|
|
226
|
-
this.contentBlockIndexToToolName.clear();
|
|
227
|
-
}
|
|
228
|
-
isTodoWriteToolUse(toolName) {
|
|
229
|
-
return toolName === 'TodoWrite' || toolName === 'todo_write';
|
|
230
|
-
}
|
|
231
|
-
isTaskToolWithPlan(toolName) {
|
|
232
|
-
return AcpConverter.TASK_TOOLS_WITH_PLAN.has(toolName);
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Check if a tool should only generate plan updates (no tool_call/tool_call_update).
|
|
236
|
-
*/
|
|
237
|
-
isPlanOnlyTool(toolName) {
|
|
238
|
-
return this.isTodoWriteToolUse(toolName) || this.isTaskToolWithPlan(toolName);
|
|
239
|
-
}
|
|
240
|
-
shouldSkipToolResult(toolUseId) {
|
|
241
|
-
if (this.todoWriteToolUseIds.has(toolUseId)) {
|
|
242
|
-
// Remove after checking to free memory
|
|
243
|
-
this.todoWriteToolUseIds.delete(toolUseId);
|
|
244
|
-
return true;
|
|
245
|
-
}
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Add requestId to SessionUpdateWithMeta if provided.
|
|
250
|
-
* Merges with existing _meta if present.
|
|
251
|
-
*/
|
|
252
|
-
withRequestId(update, requestId) {
|
|
253
|
-
var _a, _b;
|
|
254
|
-
if (!requestId) {
|
|
255
|
-
return update;
|
|
256
|
-
}
|
|
257
|
-
const existingMeta = (_b = (_a = update._meta) === null || _a === void 0 ? void 0 : _a['codebuddy.ai']) !== null && _b !== void 0 ? _b : {};
|
|
258
|
-
return {
|
|
259
|
-
...update,
|
|
260
|
-
_meta: {
|
|
261
|
-
'codebuddy.ai': {
|
|
262
|
-
...existingMeta,
|
|
263
|
-
requestId,
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
// ============= Public API =============
|
|
269
|
-
/**
|
|
270
|
-
* Convert SDK message to SessionUpdate objects.
|
|
271
|
-
*
|
|
272
|
-
* This is the unified conversion method for both streaming and history modes.
|
|
273
|
-
* The only difference between modes is whether to include user input text.
|
|
274
|
-
*
|
|
275
|
-
* @param message - SDK message to convert
|
|
276
|
-
* @param options - Conversion options
|
|
277
|
-
* @param options.includeUserInput - Whether to include user text content (default: false)
|
|
278
|
-
* Set to true for history replay, false for live streaming
|
|
279
|
-
*
|
|
280
|
-
* Returns updates for:
|
|
281
|
-
* - stream_event → agent_message_chunk, tool_call, tool_call_update (streaming deltas)
|
|
282
|
-
* - content_block_start with tool_use → tool_call (status: pending)
|
|
283
|
-
* - content_block_delta with text_delta → agent_message_chunk
|
|
284
|
-
* - content_block_delta with input_json_delta → tool_call_update (status: pending, streaming params)
|
|
285
|
-
* - user messages → user_message_chunk (if includeUserInput), tool_call_update
|
|
286
|
-
* - assistant messages → agent_message_chunk, tool_call
|
|
287
|
-
* - result/error → ignored (handled by prompt() return value)
|
|
288
|
-
*/
|
|
289
|
-
convertToUpdates(message, options = { mode: 'stream' }) {
|
|
290
|
-
const updates = [];
|
|
291
|
-
if (!('type' in message)) {
|
|
292
|
-
return updates;
|
|
293
|
-
}
|
|
294
|
-
// Extract _requestId from message (experimental)
|
|
295
|
-
const requestId = message._requestId;
|
|
296
|
-
switch (message.type) {
|
|
297
|
-
case 'stream_event':
|
|
298
|
-
updates.push(...this.convertStreamEventToUpdates(message, requestId));
|
|
299
|
-
break;
|
|
300
|
-
case 'user':
|
|
301
|
-
updates.push(...this.convertUserMessageToUpdates(message, options.mode, requestId));
|
|
302
|
-
break;
|
|
303
|
-
case 'assistant':
|
|
304
|
-
updates.push(...this.convertAssistantMessageToUpdates(message, options.mode, requestId));
|
|
305
|
-
break;
|
|
306
|
-
case 'topic':
|
|
307
|
-
updates.push(...this.convertTopicMessageToUpdates(message, requestId));
|
|
308
|
-
break;
|
|
309
|
-
case 'system':
|
|
310
|
-
updates.push(...this.convertSystemMessageToUpdates(message, requestId));
|
|
311
|
-
break;
|
|
312
|
-
// result/error are not sent as session updates (handled by prompt() return value)
|
|
313
|
-
}
|
|
314
|
-
return updates;
|
|
315
|
-
}
|
|
316
|
-
// ============= Private: Message Converters =============
|
|
317
|
-
/**
|
|
318
|
-
* Convert stream_event (partial assistant message) to updates.
|
|
319
|
-
* Handles content_block_start, content_block_delta, and input_json_delta events.
|
|
320
|
-
*/
|
|
321
|
-
convertStreamEventToUpdates(partialMsg, requestId) {
|
|
322
|
-
const updates = [];
|
|
323
|
-
const streamEvent = partialMsg.event;
|
|
324
|
-
if (streamEvent.type === 'content_block_start' && 'content_block' in streamEvent) {
|
|
325
|
-
const block = streamEvent.content_block;
|
|
326
|
-
const index = streamEvent.index;
|
|
327
|
-
if (block.type === 'tool_use') {
|
|
328
|
-
const toolUse = block;
|
|
329
|
-
// Store mapping from index to tool call ID and tool name
|
|
330
|
-
if (typeof index === 'number') {
|
|
331
|
-
this.contentBlockIndexToToolCallId.set(index, toolUse.id);
|
|
332
|
-
this.contentBlockIndexToToolName.set(index, toolUse.name);
|
|
333
|
-
}
|
|
334
|
-
updates.push(...this.convertToolUseToUpdates(toolUse, requestId));
|
|
335
|
-
}
|
|
336
|
-
else if (block.type === 'text') {
|
|
337
|
-
updates.push(this.withRequestId({
|
|
338
|
-
update: {
|
|
339
|
-
sessionUpdate: 'agent_message_chunk',
|
|
340
|
-
content: { type: 'text', text: block.text || '' },
|
|
341
|
-
},
|
|
342
|
-
}, requestId));
|
|
343
|
-
}
|
|
344
|
-
else if (block.type === 'thinking') {
|
|
345
|
-
// Thinking block start - send initial agent_thought_chunk
|
|
346
|
-
updates.push(this.withRequestId({
|
|
347
|
-
update: {
|
|
348
|
-
sessionUpdate: 'agent_thought_chunk',
|
|
349
|
-
content: { type: 'text', text: block.thinking || '' },
|
|
350
|
-
},
|
|
351
|
-
}, requestId));
|
|
352
|
-
}
|
|
353
|
-
// redacted_thinking - skip (ACP doesn't have equivalent)
|
|
354
|
-
}
|
|
355
|
-
else if (streamEvent.type === 'content_block_delta') {
|
|
356
|
-
if ('delta' in streamEvent) {
|
|
357
|
-
const delta = streamEvent.delta;
|
|
358
|
-
// Handle text delta
|
|
359
|
-
if (delta.type === 'text_delta') {
|
|
360
|
-
updates.push(this.withRequestId({
|
|
361
|
-
update: {
|
|
362
|
-
sessionUpdate: 'agent_message_chunk',
|
|
363
|
-
content: { type: 'text', text: delta.text },
|
|
364
|
-
},
|
|
365
|
-
}, requestId));
|
|
366
|
-
}
|
|
367
|
-
// Handle input_json_delta for tool calls
|
|
368
|
-
else if (delta.type === 'input_json_delta') {
|
|
369
|
-
const toolCallUpdate = this.convertInputJsonDeltaToUpdate(streamEvent, requestId);
|
|
370
|
-
if (toolCallUpdate) {
|
|
371
|
-
updates.push(toolCallUpdate);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// Handle thinking delta
|
|
375
|
-
else if (delta.type === 'thinking_delta') {
|
|
376
|
-
updates.push(this.withRequestId({
|
|
377
|
-
update: {
|
|
378
|
-
sessionUpdate: 'agent_thought_chunk',
|
|
379
|
-
content: { type: 'text', text: delta.thinking },
|
|
380
|
-
},
|
|
381
|
-
}, requestId));
|
|
382
|
-
}
|
|
383
|
-
// Handle signature delta - skip (ACP doesn't support signatures)
|
|
384
|
-
// else if (delta.type === 'signature_delta') { }
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return updates;
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Convert user message to updates.
|
|
391
|
-
* Includes user text and image content.
|
|
392
|
-
* Always includes tool results.
|
|
393
|
-
*/
|
|
394
|
-
convertUserMessageToUpdates(userMsg, _mode, requestId) {
|
|
395
|
-
const updates = [];
|
|
396
|
-
// Extract all user content (text and images)
|
|
397
|
-
const contentUpdates = this.extractUserContentUpdates(userMsg, requestId);
|
|
398
|
-
updates.push(...contentUpdates);
|
|
399
|
-
// Always extract tool results
|
|
400
|
-
updates.push(...this.extractToolResultUpdates(userMsg, requestId));
|
|
401
|
-
return updates;
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Convert assistant message to updates.
|
|
405
|
-
* Extracts text content and tool calls.
|
|
406
|
-
*/
|
|
407
|
-
convertAssistantMessageToUpdates(assistantMsg, mode, requestId) {
|
|
408
|
-
var _a;
|
|
409
|
-
const updates = [];
|
|
410
|
-
const content = (_a = assistantMsg.message) === null || _a === void 0 ? void 0 : _a.content;
|
|
411
|
-
if (!content || !Array.isArray(content)) {
|
|
412
|
-
return updates;
|
|
413
|
-
}
|
|
414
|
-
// Extract tool calls and thinking blocks
|
|
415
|
-
for (const block of content) {
|
|
416
|
-
if (block.type === 'tool_use') {
|
|
417
|
-
updates.push(...this.convertToolUseToUpdates(block, requestId));
|
|
418
|
-
}
|
|
419
|
-
else if (block.type === 'thinking') {
|
|
420
|
-
// Non-streaming thinking content
|
|
421
|
-
const thinkingBlock = block;
|
|
422
|
-
if (thinkingBlock.thinking) {
|
|
423
|
-
updates.push(this.withRequestId({
|
|
424
|
-
update: {
|
|
425
|
-
sessionUpdate: 'agent_thought_chunk',
|
|
426
|
-
content: { type: 'text', text: thinkingBlock.thinking },
|
|
427
|
-
},
|
|
428
|
-
}, requestId));
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// redacted_thinking - skip (ACP doesn't have equivalent)
|
|
432
|
-
}
|
|
433
|
-
if (mode === 'history') {
|
|
434
|
-
// Extract text content
|
|
435
|
-
const textContent = AcpConverter.extractTextContent(content);
|
|
436
|
-
if (textContent) {
|
|
437
|
-
updates.push(this.withRequestId({
|
|
438
|
-
update: {
|
|
439
|
-
sessionUpdate: 'agent_message_chunk',
|
|
440
|
-
content: { type: 'text', text: textContent },
|
|
441
|
-
},
|
|
442
|
-
}, requestId));
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return updates;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Convert topic message to session_info_update.
|
|
449
|
-
* Updates the session's human-readable title.
|
|
450
|
-
*/
|
|
451
|
-
convertTopicMessageToUpdates(topicMsg, requestId) {
|
|
452
|
-
return [this.withRequestId({
|
|
453
|
-
update: {
|
|
454
|
-
sessionUpdate: 'session_info_update',
|
|
455
|
-
title: topicMsg.topic
|
|
456
|
-
},
|
|
457
|
-
}, requestId)];
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Convert system init message.
|
|
461
|
-
* Note: System messages are NOT converted to SessionUpdate.
|
|
462
|
-
* The model/permissionMode should be sent via custom notification instead.
|
|
463
|
-
* See AcpAgent.handleSystemInitNotification() for sending custom notifications.
|
|
464
|
-
*/
|
|
465
|
-
convertSystemMessageToUpdates(_sysMsg, _requestId) {
|
|
466
|
-
// System messages are handled separately - not via session updates
|
|
467
|
-
// The AcpAgent will send custom notifications for model/permissionMode
|
|
468
|
-
return [];
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Convert tool_use block to updates.
|
|
472
|
-
* Handles TodoWrite specially (converts to plan update from input.newTodos).
|
|
473
|
-
* Handles Task tools specially (skips tool_call, plan generated from tool_result).
|
|
474
|
-
*/
|
|
475
|
-
convertToolUseToUpdates(toolUse, requestId) {
|
|
476
|
-
// TodoWrite: generate plan from input.newTodos
|
|
477
|
-
if (this.isTodoWriteToolUse(toolUse.name)) {
|
|
478
|
-
const planUpdate = this.createPlanUpdate(toolUse);
|
|
479
|
-
if (planUpdate) {
|
|
480
|
-
return [this.withRequestId({
|
|
481
|
-
update: planUpdate,
|
|
482
|
-
_meta: { 'codebuddy.ai': { toolName: toolUse.name } },
|
|
483
|
-
}, requestId)];
|
|
484
|
-
}
|
|
485
|
-
return [];
|
|
486
|
-
}
|
|
487
|
-
// Task tools: skip tool_call, plan will be generated from tool_result
|
|
488
|
-
if (this.isTaskToolWithPlan(toolUse.name)) {
|
|
489
|
-
// Mark for skipping tool_result as well (plan will be generated there)
|
|
490
|
-
this.taskToolUseIds.add(toolUse.id);
|
|
491
|
-
return [];
|
|
492
|
-
}
|
|
493
|
-
// Regular tools: generate tool_call as usual
|
|
494
|
-
// When rawInput exists (input is complete), status should be 'in_progress' (tool is running)
|
|
495
|
-
// 'pending' is only for streaming input or awaiting approval
|
|
496
|
-
const hasCompleteInput = toolUse.input && Object.keys(toolUse.input).length > 0;
|
|
497
|
-
return [this.withRequestId({
|
|
498
|
-
update: {
|
|
499
|
-
sessionUpdate: 'tool_call',
|
|
500
|
-
toolCallId: toolUse.id,
|
|
501
|
-
status: (hasCompleteInput ? 'in_progress' : 'pending'),
|
|
502
|
-
title: AcpConverter.generateToolTitle(toolUse.name, toolUse.input),
|
|
503
|
-
kind: AcpConverter.getToolKind(toolUse.name),
|
|
504
|
-
rawInput: toolUse.input,
|
|
505
|
-
content: AcpConverter.generateToolContent(toolUse.name, toolUse.input),
|
|
506
|
-
locations: AcpConverter.extractToolLocations(toolUse.name, toolUse.input),
|
|
507
|
-
},
|
|
508
|
-
_meta: { 'codebuddy.ai': { toolName: toolUse.name } },
|
|
509
|
-
}, requestId)];
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Extract user content updates (text and images) from user message.
|
|
513
|
-
* Converts SDK content blocks to ACP user_message_chunk updates.
|
|
514
|
-
*/
|
|
515
|
-
extractUserContentUpdates(userMsg, requestId) {
|
|
516
|
-
var _a;
|
|
517
|
-
const updates = [];
|
|
518
|
-
const content = (_a = userMsg.message) === null || _a === void 0 ? void 0 : _a.content;
|
|
519
|
-
if (!content) {
|
|
520
|
-
return updates;
|
|
521
|
-
}
|
|
522
|
-
// Handle string content (simple text)
|
|
523
|
-
if (typeof content === 'string') {
|
|
524
|
-
if (content) {
|
|
525
|
-
updates.push(this.withRequestId({
|
|
526
|
-
update: {
|
|
527
|
-
sessionUpdate: 'user_message_chunk',
|
|
528
|
-
content: { type: 'text', text: content },
|
|
529
|
-
},
|
|
530
|
-
}, requestId));
|
|
531
|
-
}
|
|
532
|
-
return updates;
|
|
533
|
-
}
|
|
534
|
-
// Handle array of content blocks
|
|
535
|
-
if (Array.isArray(content)) {
|
|
536
|
-
for (const block of content) {
|
|
537
|
-
if (!block || typeof block !== 'object' || !('type' in block)) {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
if (block.type === 'text') {
|
|
541
|
-
const textBlock = block;
|
|
542
|
-
if (textBlock.text) {
|
|
543
|
-
updates.push(this.withRequestId({
|
|
544
|
-
update: {
|
|
545
|
-
sessionUpdate: 'user_message_chunk',
|
|
546
|
-
content: { type: 'text', text: textBlock.text },
|
|
547
|
-
},
|
|
548
|
-
}, requestId));
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
else if (block.type === 'image') {
|
|
552
|
-
// Convert SDK ImageContentBlock to ACP ImageContent
|
|
553
|
-
const imageBlock = block;
|
|
554
|
-
const acpImage = AcpConverter.convertSdkImageToAcp(imageBlock);
|
|
555
|
-
if (acpImage) {
|
|
556
|
-
updates.push(this.withRequestId({
|
|
557
|
-
update: {
|
|
558
|
-
sessionUpdate: 'user_message_chunk',
|
|
559
|
-
content: acpImage,
|
|
560
|
-
},
|
|
561
|
-
}, requestId));
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
// Skip tool_result blocks - handled separately by extractToolResultUpdates
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
return updates;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Extract tool result updates from user message.
|
|
571
|
-
*/
|
|
572
|
-
extractToolResultUpdates(userMsg, requestId) {
|
|
573
|
-
var _a;
|
|
574
|
-
const updates = [];
|
|
575
|
-
const content = (_a = userMsg.message) === null || _a === void 0 ? void 0 : _a.content;
|
|
576
|
-
if (!content || !Array.isArray(content)) {
|
|
577
|
-
return updates;
|
|
578
|
-
}
|
|
579
|
-
for (const block of content) {
|
|
580
|
-
if (block.type === 'tool_result') {
|
|
581
|
-
const toolResult = block;
|
|
582
|
-
// Skip TodoWrite results
|
|
583
|
-
if (this.shouldSkipToolResult(toolResult.tool_use_id)) {
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
// Task tools: generate plan from rawResponse.todos
|
|
587
|
-
if (this.taskToolUseIds.has(toolResult.tool_use_id)) {
|
|
588
|
-
this.taskToolUseIds.delete(toolResult.tool_use_id);
|
|
589
|
-
const planUpdate = this.tryCreatePlanUpdateFromTaskResult(toolResult);
|
|
590
|
-
if (planUpdate) {
|
|
591
|
-
updates.push(this.withRequestId({ update: planUpdate }, requestId));
|
|
592
|
-
}
|
|
593
|
-
continue; // Skip tool_call_update
|
|
594
|
-
}
|
|
595
|
-
// Regular tools: generate tool_call_update as usual
|
|
596
|
-
const toolCallContent = AcpConverter.extractToolCallContent(toolResult.content);
|
|
597
|
-
updates.push(this.withRequestId({
|
|
598
|
-
update: {
|
|
599
|
-
sessionUpdate: 'tool_call_update',
|
|
600
|
-
toolCallId: toolResult.tool_use_id,
|
|
601
|
-
status: toolResult.is_error ? 'failed' : 'completed',
|
|
602
|
-
content: toolCallContent.length > 0 ? toolCallContent : undefined,
|
|
603
|
-
rawOutput: toolResult.content,
|
|
604
|
-
},
|
|
605
|
-
}, requestId));
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
return updates;
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Convert input_json_delta to tool_call_update.
|
|
612
|
-
* Sends streaming updates of tool call parameters as they arrive.
|
|
613
|
-
* Skips TodoWrite and Task tool streaming updates (plan updates are sent separately).
|
|
614
|
-
*/
|
|
615
|
-
convertInputJsonDeltaToUpdate(deltaEvent, requestId) {
|
|
616
|
-
var _a;
|
|
617
|
-
try {
|
|
618
|
-
const index = deltaEvent.index;
|
|
619
|
-
// Skip TodoWrite and Task tools streaming updates (plan updates are handled separately)
|
|
620
|
-
const toolName = this.contentBlockIndexToToolName.get(index);
|
|
621
|
-
if (toolName && this.isPlanOnlyTool(toolName)) {
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
const partialJson = (_a = deltaEvent.delta) === null || _a === void 0 ? void 0 : _a.partial_json;
|
|
625
|
-
if (typeof partialJson !== 'string' || !partialJson) {
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
// Try to get the actual tool call ID from the mapping
|
|
629
|
-
// If not yet available (delta arrives before content_block_start), use temporary ID
|
|
630
|
-
const toolCallId = this.contentBlockIndexToToolCallId.get(index) || `temp_${index}`;
|
|
631
|
-
return this.withRequestId({
|
|
632
|
-
update: {
|
|
633
|
-
sessionUpdate: 'tool_call_update',
|
|
634
|
-
toolCallId,
|
|
635
|
-
status: 'pending',
|
|
636
|
-
content: [{
|
|
637
|
-
type: 'content',
|
|
638
|
-
content: { type: 'text', text: partialJson },
|
|
639
|
-
}],
|
|
640
|
-
},
|
|
641
|
-
}, requestId);
|
|
642
|
-
}
|
|
643
|
-
catch (_b) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Create plan update from TodoWrite tool use.
|
|
649
|
-
*
|
|
650
|
-
* agent-cli format: { oldTodos: [...], newTodos: [...] }
|
|
651
|
-
* - newTodos: The current/updated todo list
|
|
652
|
-
* - oldTodos: The previous todo list (for reference)
|
|
653
|
-
*
|
|
654
|
-
* Behavior (consistent with claude-code-acp):
|
|
655
|
-
* - Always use newTodos for the plan entries
|
|
656
|
-
* - When newTodos is empty, send empty entries to clear the plan
|
|
657
|
-
*/
|
|
658
|
-
createPlanUpdate(toolUse) {
|
|
659
|
-
try {
|
|
660
|
-
const input = toolUse.input;
|
|
661
|
-
// Only use newTodos (consistent with claude-code-acp behavior)
|
|
662
|
-
if (!(input === null || input === void 0 ? void 0 : input.newTodos) || !Array.isArray(input.newTodos)) {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
const entries = input.newTodos.map((todo, index) => ({
|
|
666
|
-
content: todo.content,
|
|
667
|
-
status: todo.status,
|
|
668
|
-
priority: 'medium',
|
|
669
|
-
_meta: { 'codebuddy.ai': { id: `todo-${index}`, activeForm: todo.activeForm } },
|
|
670
|
-
}));
|
|
671
|
-
this.todoWriteToolUseIds.add(toolUse.id);
|
|
672
|
-
return { sessionUpdate: 'plan', entries };
|
|
673
|
-
}
|
|
674
|
-
catch (_a) {
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Create plan update from Task tool result.
|
|
680
|
-
* Extracts todos from _meta.rawResponse.todos or content text (full task list).
|
|
681
|
-
*/
|
|
682
|
-
tryCreatePlanUpdateFromTaskResult(toolResult) {
|
|
683
|
-
var _a, _b, _c;
|
|
684
|
-
try {
|
|
685
|
-
// First, check _meta.rawResponse.todos (preferred, set by stream-json-view)
|
|
686
|
-
const meta = toolResult._meta;
|
|
687
|
-
let todos = (_a = meta === null || meta === void 0 ? void 0 : meta.rawResponse) === null || _a === void 0 ? void 0 : _a.todos;
|
|
688
|
-
// Fallback: try to parse from content text
|
|
689
|
-
if (!todos || !Array.isArray(todos)) {
|
|
690
|
-
let resultData;
|
|
691
|
-
if (typeof toolResult.content === 'string') {
|
|
692
|
-
resultData = JSON.parse(toolResult.content);
|
|
693
|
-
}
|
|
694
|
-
else if (Array.isArray(toolResult.content)) {
|
|
695
|
-
for (const block of toolResult.content) {
|
|
696
|
-
if ('text' in block && block.text) {
|
|
697
|
-
try {
|
|
698
|
-
const parsed = JSON.parse(block.text);
|
|
699
|
-
if ((_b = parsed.rawResponse) === null || _b === void 0 ? void 0 : _b.todos) {
|
|
700
|
-
resultData = parsed;
|
|
701
|
-
break;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
catch (_d) {
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
todos = (_c = resultData === null || resultData === void 0 ? void 0 : resultData.rawResponse) === null || _c === void 0 ? void 0 : _c.todos;
|
|
711
|
-
}
|
|
712
|
-
if (!todos || !Array.isArray(todos)) {
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
const entries = todos.map((todo, index) => ({
|
|
716
|
-
content: todo.content,
|
|
717
|
-
status: todo.status,
|
|
718
|
-
priority: 'medium',
|
|
719
|
-
_meta: { 'codebuddy.ai': { id: `task-${index}`, activeForm: todo.activeForm } },
|
|
720
|
-
}));
|
|
721
|
-
return { sessionUpdate: 'plan', entries };
|
|
722
|
-
}
|
|
723
|
-
catch (_e) {
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
// ============= Static: Tool Conversion =============
|
|
728
|
-
static extractToolCallContent(content) {
|
|
729
|
-
const result = [];
|
|
730
|
-
if (!content) {
|
|
731
|
-
return result;
|
|
732
|
-
}
|
|
733
|
-
if (typeof content === 'string') {
|
|
734
|
-
result.push({
|
|
735
|
-
type: 'content',
|
|
736
|
-
content: { type: 'text', text: content },
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
else if (Array.isArray(content)) {
|
|
740
|
-
for (const block of content) {
|
|
741
|
-
if ('type' in block && block.type === 'text') {
|
|
742
|
-
const textBlock = block;
|
|
743
|
-
result.push({
|
|
744
|
-
type: 'content',
|
|
745
|
-
content: { type: 'text', text: textBlock.text },
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
return result;
|
|
751
|
-
}
|
|
752
|
-
static convertToolResultToUpdate(toolResult) {
|
|
753
|
-
const content = AcpConverter.extractToolCallContent(toolResult.content);
|
|
754
|
-
return {
|
|
755
|
-
toolCallId: toolResult.tool_use_id,
|
|
756
|
-
status: toolResult.is_error ? 'failed' : 'completed',
|
|
757
|
-
content: content.length > 0 ? content : undefined,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
static convertAcpToolResultToSdk(toolCallId, acpResult) {
|
|
761
|
-
const isError = acpResult.status === 'failed';
|
|
762
|
-
const content = acpResult.content ? AcpConverter.formatAcpContentForSdk(acpResult.content) : '';
|
|
763
|
-
return {
|
|
764
|
-
type: 'tool_result',
|
|
765
|
-
tool_use_id: toolCallId,
|
|
766
|
-
content: content,
|
|
767
|
-
is_error: isError,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
static convertSdkToolResultToToolResult(toolResult) {
|
|
771
|
-
const content = AcpConverter.extractToolCallContent(toolResult.content);
|
|
772
|
-
return {
|
|
773
|
-
toolCallId: toolResult.tool_use_id,
|
|
774
|
-
status: toolResult.is_error ? 'failed' : 'completed',
|
|
775
|
-
content: content.length > 0 ? content : undefined,
|
|
776
|
-
rawOutput: toolResult.content,
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
// ============= Private: Lookup-based Utilities =============
|
|
780
|
-
static generateToolTitle(toolName, input) {
|
|
781
|
-
const generator = TOOL_TITLE_GENERATORS[toolName.toLowerCase()];
|
|
782
|
-
return generator ? generator(input) : (toolName || 'Unknown Tool');
|
|
783
|
-
}
|
|
784
|
-
static getToolKind(toolName) {
|
|
785
|
-
var _a;
|
|
786
|
-
return (_a = TOOL_KIND_MAP[toolName.toLowerCase()]) !== null && _a !== void 0 ? _a : 'other';
|
|
787
|
-
}
|
|
788
|
-
static generateToolContent(toolName, input) {
|
|
789
|
-
const content = [];
|
|
790
|
-
const lowerName = toolName.toLowerCase();
|
|
791
|
-
if (lowerName === 'edit' || lowerName === 'multiedit') {
|
|
792
|
-
const filePath = input.file_path;
|
|
793
|
-
const oldString = input.old_string;
|
|
794
|
-
const newString = input.new_string;
|
|
795
|
-
if (filePath && oldString !== undefined && newString !== undefined) {
|
|
796
|
-
content.push({
|
|
797
|
-
type: 'diff',
|
|
798
|
-
path: filePath,
|
|
799
|
-
oldText: oldString || null,
|
|
800
|
-
newText: newString,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
else if (lowerName === 'bash') {
|
|
805
|
-
const description = input.description;
|
|
806
|
-
if (description) {
|
|
807
|
-
content.push({
|
|
808
|
-
type: 'content',
|
|
809
|
-
content: { type: 'text', text: description },
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
else if (lowerName === 'task') {
|
|
814
|
-
const prompt = input.prompt;
|
|
815
|
-
if (prompt) {
|
|
816
|
-
content.push({
|
|
817
|
-
type: 'content',
|
|
818
|
-
content: { type: 'text', text: prompt },
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
else if (lowerName === 'webfetch' || lowerName === 'web_fetch') {
|
|
823
|
-
const prompt = input.prompt;
|
|
824
|
-
if (prompt) {
|
|
825
|
-
content.push({
|
|
826
|
-
type: 'content',
|
|
827
|
-
content: { type: 'text', text: prompt },
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
return content.length > 0 ? content : undefined;
|
|
832
|
-
}
|
|
833
|
-
static extractToolLocations(toolName, input) {
|
|
834
|
-
const locations = [];
|
|
835
|
-
const lowerName = toolName.toLowerCase();
|
|
836
|
-
const filePath = input.file_path;
|
|
837
|
-
const notebookPath = input.notebook_path;
|
|
838
|
-
const path = input.path;
|
|
839
|
-
if (filePath) {
|
|
840
|
-
locations.push({ path: filePath });
|
|
841
|
-
}
|
|
842
|
-
else if (notebookPath) {
|
|
843
|
-
locations.push({ path: notebookPath });
|
|
844
|
-
}
|
|
845
|
-
else if (path && lowerName === 'glob') {
|
|
846
|
-
locations.push({ path });
|
|
847
|
-
}
|
|
848
|
-
return locations.length > 0 ? locations : undefined;
|
|
849
|
-
}
|
|
850
|
-
static formatAcpContentForSdk(toolCallContent) {
|
|
851
|
-
var _a, _b;
|
|
852
|
-
if (toolCallContent.length === 0) {
|
|
853
|
-
return '';
|
|
854
|
-
}
|
|
855
|
-
const blocks = [];
|
|
856
|
-
for (const item of toolCallContent) {
|
|
857
|
-
if (item.type === 'diff') {
|
|
858
|
-
const diff = item;
|
|
859
|
-
const diffText = `--- ${diff.path}\n+++ ${diff.path}\n${AcpConverter.formatDiffHunk((_a = diff.oldText) !== null && _a !== void 0 ? _a : null, diff.newText)}`;
|
|
860
|
-
blocks.push({ type: 'text', text: diffText });
|
|
861
|
-
}
|
|
862
|
-
else if (item.type === 'content') {
|
|
863
|
-
const contentItem = item;
|
|
864
|
-
if (((_b = contentItem.content) === null || _b === void 0 ? void 0 : _b.type) === 'text' && contentItem.content.text) {
|
|
865
|
-
blocks.push({ type: 'text', text: contentItem.content.text });
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
else if (item.type === 'terminal') {
|
|
869
|
-
const terminalItem = item;
|
|
870
|
-
if (terminalItem.terminalId) {
|
|
871
|
-
blocks.push({ type: 'text', text: `[Terminal: ${terminalItem.terminalId}]` });
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
if (blocks.length === 1 && blocks[0].type === 'text') {
|
|
876
|
-
return blocks[0].text;
|
|
877
|
-
}
|
|
878
|
-
return blocks.length > 0 ? blocks : '';
|
|
879
|
-
}
|
|
880
|
-
static formatDiffHunk(oldText, newText) {
|
|
881
|
-
const oldLines = oldText ? oldText.split('\n') : [];
|
|
882
|
-
const newLines = newText.split('\n');
|
|
883
|
-
const hunks = [];
|
|
884
|
-
hunks.push(`@@ -1,${oldLines.length} +1,${newLines.length} @@`);
|
|
885
|
-
for (const line of oldLines) {
|
|
886
|
-
hunks.push(`-${line}`);
|
|
887
|
-
}
|
|
888
|
-
for (const line of newLines) {
|
|
889
|
-
hunks.push(`+${line}`);
|
|
890
|
-
}
|
|
891
|
-
return hunks.join('\n');
|
|
892
|
-
}
|
|
893
|
-
static extractTextContent(contentBlocks) {
|
|
894
|
-
if (!Array.isArray(contentBlocks)) {
|
|
895
|
-
return '';
|
|
896
|
-
}
|
|
897
|
-
return contentBlocks
|
|
898
|
-
.filter((block) => typeof block === 'object' &&
|
|
899
|
-
block !== null &&
|
|
900
|
-
'type' in block &&
|
|
901
|
-
block.type === 'text')
|
|
902
|
-
.map(block => block.text)
|
|
903
|
-
.join('\n');
|
|
904
|
-
}
|
|
905
|
-
// ============= ACP Prompt to SDK Conversion =============
|
|
906
|
-
/**
|
|
907
|
-
* Convert ACP ContentBlock[] to SDK ContentBlock[].
|
|
908
|
-
*
|
|
909
|
-
* Type mapping (following Zed claude-code-acp pattern):
|
|
910
|
-
* - ACP text -> SDK TextContentBlock (with MCP command format conversion)
|
|
911
|
-
* - ACP image -> SDK ImageContentBlock (base64 or URL)
|
|
912
|
-
* - ACP resource (text) -> SDK TextContentBlock ([@name](uri) link + content)
|
|
913
|
-
* - ACP resource (blob/image) -> SDK ImageContentBlock
|
|
914
|
-
* - ACP resource_link -> SDK TextContentBlock ([@name](uri) Markdown link)
|
|
915
|
-
* - ACP audio -> ignored (not supported by SDK)
|
|
916
|
-
*/
|
|
917
|
-
static convertAcpPromptToSdk(blocks) {
|
|
918
|
-
const content = [];
|
|
919
|
-
const context = [];
|
|
920
|
-
for (const block of blocks) {
|
|
921
|
-
AcpConverter.convertAcpContentBlock(block, content, context);
|
|
922
|
-
}
|
|
923
|
-
// Append context blocks at the end (following Zed pattern)
|
|
924
|
-
return [...content, ...context];
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Convert a single ACP ContentBlock and push to content/context arrays.
|
|
928
|
-
* Following Zed claude-code-acp pattern for content/context separation.
|
|
929
|
-
*/
|
|
930
|
-
static convertAcpContentBlock(block, content, context) {
|
|
931
|
-
switch (block.type) {
|
|
932
|
-
case 'text': {
|
|
933
|
-
const textBlock = block;
|
|
934
|
-
let text = textBlock.text;
|
|
935
|
-
// MCP command format conversion: /mcp:server:command -> /server:command (MCP)
|
|
936
|
-
const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
|
|
937
|
-
if (mcpMatch) {
|
|
938
|
-
const [, server, command, args] = mcpMatch;
|
|
939
|
-
text = `/${server}:${command} (MCP)${args || ''}`;
|
|
940
|
-
}
|
|
941
|
-
content.push({ type: 'text', text });
|
|
942
|
-
break;
|
|
943
|
-
}
|
|
944
|
-
case 'image': {
|
|
945
|
-
const imageBlock = block;
|
|
946
|
-
if (imageBlock.data) {
|
|
947
|
-
// Base64 image
|
|
948
|
-
content.push({
|
|
949
|
-
type: 'image',
|
|
950
|
-
source: {
|
|
951
|
-
type: 'base64',
|
|
952
|
-
media_type: AcpConverter.normalizeImageMimeType(imageBlock.mimeType),
|
|
953
|
-
data: imageBlock.data,
|
|
954
|
-
},
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
else if (imageBlock.uri && imageBlock.uri.startsWith('http')) {
|
|
958
|
-
// URL image (only HTTP/HTTPS)
|
|
959
|
-
content.push({
|
|
960
|
-
type: 'image',
|
|
961
|
-
source: {
|
|
962
|
-
type: 'url',
|
|
963
|
-
url: imageBlock.uri,
|
|
964
|
-
},
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
break;
|
|
968
|
-
}
|
|
969
|
-
case 'resource': {
|
|
970
|
-
// EmbeddedResource - following Zed pattern: link in content, text in context
|
|
971
|
-
const resourceBlock = block;
|
|
972
|
-
const resource = resourceBlock.resource;
|
|
973
|
-
if ('text' in resource && resource.text !== undefined) {
|
|
974
|
-
const textResource = resource;
|
|
975
|
-
// Text resource: add formatted link to content, text to context
|
|
976
|
-
const formattedUri = AcpConverter.formatUriAsLink(textResource.uri);
|
|
977
|
-
content.push({ type: 'text', text: formattedUri });
|
|
978
|
-
context.push({ type: 'text', text: `\n\n${textResource.text}\n` });
|
|
979
|
-
}
|
|
980
|
-
else if ('blob' in resource && resource.blob) {
|
|
981
|
-
const blobResource = resource;
|
|
982
|
-
// Blob resource - check if it's an image
|
|
983
|
-
const mimeType = blobResource.mimeType || '';
|
|
984
|
-
if (mimeType.startsWith('image/')) {
|
|
985
|
-
content.push({
|
|
986
|
-
type: 'image',
|
|
987
|
-
source: {
|
|
988
|
-
type: 'base64',
|
|
989
|
-
media_type: AcpConverter.normalizeImageMimeType(mimeType),
|
|
990
|
-
data: blobResource.blob,
|
|
991
|
-
},
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
// Non-image blobs are ignored (no good way to represent in text)
|
|
995
|
-
}
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
998
|
-
case 'resource_link': {
|
|
999
|
-
// ResourceLink - format as Markdown link [@name](uri)
|
|
1000
|
-
const linkBlock = block;
|
|
1001
|
-
const formattedUri = AcpConverter.formatUriAsLink(linkBlock.uri, linkBlock.name);
|
|
1002
|
-
content.push({ type: 'text', text: formattedUri });
|
|
1003
|
-
break;
|
|
1004
|
-
}
|
|
1005
|
-
case 'audio': {
|
|
1006
|
-
// Audio is not supported by SDK - silently ignore (following Zed pattern)
|
|
1007
|
-
break;
|
|
1008
|
-
}
|
|
1009
|
-
default: {
|
|
1010
|
-
// Unknown block type - silently ignore
|
|
1011
|
-
break;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Format URI as Markdown link [@name](uri).
|
|
1017
|
-
* Following Zed claude-code-acp formatUriAsLink pattern.
|
|
1018
|
-
*/
|
|
1019
|
-
static formatUriAsLink(uri, name) {
|
|
1020
|
-
try {
|
|
1021
|
-
if (uri.startsWith('file://')) {
|
|
1022
|
-
// Extract filename from file:// URI
|
|
1023
|
-
const path = uri.slice(7);
|
|
1024
|
-
const fileName = name || path.split('/').pop() || path;
|
|
1025
|
-
return `[@${fileName}](${uri})`;
|
|
1026
|
-
}
|
|
1027
|
-
// For other URIs, use provided name or the URI itself
|
|
1028
|
-
const displayName = name || uri;
|
|
1029
|
-
return `[@${displayName}](${uri})`;
|
|
1030
|
-
}
|
|
1031
|
-
catch (_a) {
|
|
1032
|
-
return uri;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Normalize MIME type to SDK-supported image types.
|
|
1037
|
-
*/
|
|
1038
|
-
static normalizeImageMimeType(mimeType) {
|
|
1039
|
-
const normalized = mimeType.toLowerCase();
|
|
1040
|
-
if (normalized === 'image/jpeg' || normalized === 'image/jpg') {
|
|
1041
|
-
return 'image/jpeg';
|
|
1042
|
-
}
|
|
1043
|
-
if (normalized === 'image/png') {
|
|
1044
|
-
return 'image/png';
|
|
1045
|
-
}
|
|
1046
|
-
if (normalized === 'image/gif') {
|
|
1047
|
-
return 'image/gif';
|
|
1048
|
-
}
|
|
1049
|
-
if (normalized === 'image/webp') {
|
|
1050
|
-
return 'image/webp';
|
|
1051
|
-
}
|
|
1052
|
-
// Default to jpeg for unsupported types
|
|
1053
|
-
return 'image/jpeg';
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Convert SDK ImageContentBlock to ACP ImageContent format.
|
|
1057
|
-
* Returns null if conversion is not possible.
|
|
1058
|
-
*
|
|
1059
|
-
* SDK format: { type: 'image', source: { type: 'base64', media_type, data } | { type: 'url', url } }
|
|
1060
|
-
* ACP format: { type: 'image', data, mimeType, uri? }
|
|
1061
|
-
*/
|
|
1062
|
-
static convertSdkImageToAcp(imageBlock) {
|
|
1063
|
-
if (!imageBlock.source) {
|
|
1064
|
-
return null;
|
|
1065
|
-
}
|
|
1066
|
-
if (imageBlock.source.type === 'base64') {
|
|
1067
|
-
return {
|
|
1068
|
-
type: 'image',
|
|
1069
|
-
data: imageBlock.source.data,
|
|
1070
|
-
mimeType: imageBlock.source.media_type,
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
else if (imageBlock.source.type === 'url') {
|
|
1074
|
-
// For URL-based images, we cannot convert to ACP format directly
|
|
1075
|
-
// as ACP ImageContent requires base64 data
|
|
1076
|
-
// Return with empty data and uri for reference
|
|
1077
|
-
return {
|
|
1078
|
-
type: 'image',
|
|
1079
|
-
data: '', // ACP requires data field, but we don't have base64 for URL images
|
|
1080
|
-
mimeType: 'image/jpeg', // Default MIME type
|
|
1081
|
-
uri: imageBlock.source.url,
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
return null;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
exports.AcpConverter = AcpConverter;
|
|
1088
|
-
// Task tools that should generate plan updates instead of tool_call/tool_call_update
|
|
1089
|
-
AcpConverter.TASK_TOOLS_WITH_PLAN = new Set([
|
|
1090
|
-
'TaskCreate', 'task_create',
|
|
1091
|
-
'TaskUpdate', 'task_update',
|
|
1092
|
-
'TaskList', 'task_list',
|
|
1093
|
-
]);
|
|
1094
|
-
//# sourceMappingURL=converter.js.map
|