@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.
Files changed (54) hide show
  1. package/cli/CHANGELOG.md +56 -0
  2. package/cli/dist/codebuddy.js +6 -6
  3. package/cli/package.json +1 -1
  4. package/cli/product.cloudhosted.json +20 -7
  5. package/cli/product.internal.json +20 -7
  6. package/cli/product.ioa.json +28 -6
  7. package/cli/product.json +49 -8
  8. package/cli/product.selfhosted.json +16 -3
  9. package/lib/index.d.ts +1 -11
  10. package/lib/index.d.ts.map +1 -1
  11. package/lib/index.js +1 -18
  12. package/lib/index.js.map +1 -1
  13. package/lib/query.d.ts.map +1 -1
  14. package/lib/query.js +7 -21
  15. package/lib/query.js.map +1 -1
  16. package/lib/session.d.ts +7 -9
  17. package/lib/session.d.ts.map +1 -1
  18. package/lib/session.js +28 -25
  19. package/lib/session.js.map +1 -1
  20. package/lib/transport/process-transport.d.ts.map +1 -1
  21. package/lib/transport/process-transport.js +9 -0
  22. package/lib/transport/process-transport.js.map +1 -1
  23. package/lib/types.d.ts +8 -9
  24. package/lib/types.d.ts.map +1 -1
  25. package/lib/types.js.map +1 -1
  26. package/lib/utils/stream.d.ts +19 -0
  27. package/lib/utils/stream.d.ts.map +1 -1
  28. package/lib/utils/stream.js +31 -0
  29. package/lib/utils/stream.js.map +1 -1
  30. package/package.json +1 -1
  31. package/lib/acp/agent.d.ts +0 -427
  32. package/lib/acp/agent.d.ts.map +0 -1
  33. package/lib/acp/agent.js +0 -835
  34. package/lib/acp/agent.js.map +0 -1
  35. package/lib/acp/converter.d.ts +0 -179
  36. package/lib/acp/converter.d.ts.map +0 -1
  37. package/lib/acp/converter.js +0 -1094
  38. package/lib/acp/converter.js.map +0 -1
  39. package/lib/acp/index.d.ts +0 -11
  40. package/lib/acp/index.d.ts.map +0 -1
  41. package/lib/acp/index.js +0 -20
  42. package/lib/acp/index.js.map +0 -1
  43. package/lib/acp/server.d.ts +0 -70
  44. package/lib/acp/server.d.ts.map +0 -1
  45. package/lib/acp/server.js +0 -364
  46. package/lib/acp/server.js.map +0 -1
  47. package/lib/acp/session-manager.d.ts +0 -33
  48. package/lib/acp/session-manager.d.ts.map +0 -1
  49. package/lib/acp/session-manager.js +0 -106
  50. package/lib/acp/session-manager.js.map +0 -1
  51. package/lib/acp/session.d.ts +0 -67
  52. package/lib/acp/session.d.ts.map +0 -1
  53. package/lib/acp/session.js +0 -263
  54. package/lib/acp/session.js.map +0 -1
@@ -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