@a5c-ai/agent-mux-adapters 0.4.0 → 0.4.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.
Files changed (158) hide show
  1. package/README.md +3 -1
  2. package/dist/agent-mux-remote-adapter.d.ts.map +1 -1
  3. package/dist/agent-mux-remote-adapter.js +4 -0
  4. package/dist/agent-mux-remote-adapter.js.map +1 -1
  5. package/dist/amp-adapter.d.ts +2 -1
  6. package/dist/amp-adapter.d.ts.map +1 -1
  7. package/dist/amp-adapter.js +93 -17
  8. package/dist/amp-adapter.js.map +1 -1
  9. package/dist/babysitter-adapter.d.ts +29 -0
  10. package/dist/babysitter-adapter.d.ts.map +1 -0
  11. package/dist/babysitter-adapter.js +338 -0
  12. package/dist/babysitter-adapter.js.map +1 -0
  13. package/dist/base-adapter.d.ts +7 -0
  14. package/dist/base-adapter.d.ts.map +1 -1
  15. package/dist/base-adapter.js +113 -1
  16. package/dist/base-adapter.js.map +1 -1
  17. package/dist/claude-adapter.d.ts +14 -3
  18. package/dist/claude-adapter.d.ts.map +1 -1
  19. package/dist/claude-adapter.js +222 -25
  20. package/dist/claude-adapter.js.map +1 -1
  21. package/dist/claude-agent-sdk-adapter.d.ts +21 -34
  22. package/dist/claude-agent-sdk-adapter.d.ts.map +1 -1
  23. package/dist/claude-agent-sdk-adapter.js +629 -397
  24. package/dist/claude-agent-sdk-adapter.js.map +1 -1
  25. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts +12 -0
  26. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts.map +1 -0
  27. package/dist/claude-code/runtime-hooks/ephemeral-config.js +143 -0
  28. package/dist/claude-code/runtime-hooks/ephemeral-config.js.map +1 -0
  29. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts +10 -0
  30. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts.map +1 -0
  31. package/dist/claude-code/runtime-hooks/hook-socket-server.js +79 -0
  32. package/dist/claude-code/runtime-hooks/hook-socket-server.js.map +1 -0
  33. package/dist/claude-code/runtime-hooks/lifecycle.d.ts +3 -0
  34. package/dist/claude-code/runtime-hooks/lifecycle.d.ts.map +1 -0
  35. package/dist/claude-code/runtime-hooks/lifecycle.js +24 -0
  36. package/dist/claude-code/runtime-hooks/lifecycle.js.map +1 -0
  37. package/dist/claude-remote-control-adapter.d.ts +43 -0
  38. package/dist/claude-remote-control-adapter.d.ts.map +1 -0
  39. package/dist/claude-remote-control-adapter.js +505 -0
  40. package/dist/claude-remote-control-adapter.js.map +1 -0
  41. package/dist/codex-adapter.d.ts.map +1 -1
  42. package/dist/codex-adapter.js +64 -41
  43. package/dist/codex-adapter.js.map +1 -1
  44. package/dist/codex-sdk-adapter.d.ts.map +1 -1
  45. package/dist/codex-sdk-adapter.js +6 -2
  46. package/dist/codex-sdk-adapter.js.map +1 -1
  47. package/dist/codex-websocket-adapter.d.ts +11 -18
  48. package/dist/codex-websocket-adapter.d.ts.map +1 -1
  49. package/dist/codex-websocket-adapter.js +199 -90
  50. package/dist/codex-websocket-adapter.js.map +1 -1
  51. package/dist/codex-websocket-connection.d.ts +60 -40
  52. package/dist/codex-websocket-connection.d.ts.map +1 -1
  53. package/dist/codex-websocket-connection.js +692 -203
  54. package/dist/codex-websocket-connection.js.map +1 -1
  55. package/dist/copilot-adapter.d.ts.map +1 -1
  56. package/dist/copilot-adapter.js +4 -0
  57. package/dist/copilot-adapter.js.map +1 -1
  58. package/dist/cursor-adapter.d.ts +3 -1
  59. package/dist/cursor-adapter.d.ts.map +1 -1
  60. package/dist/cursor-adapter.js +5 -1
  61. package/dist/cursor-adapter.js.map +1 -1
  62. package/dist/droid-adapter.d.ts +4 -2
  63. package/dist/droid-adapter.d.ts.map +1 -1
  64. package/dist/droid-adapter.js +12 -11
  65. package/dist/droid-adapter.js.map +1 -1
  66. package/dist/gemini-adapter.d.ts +3 -1
  67. package/dist/gemini-adapter.d.ts.map +1 -1
  68. package/dist/gemini-adapter.js +5 -1
  69. package/dist/gemini-adapter.js.map +1 -1
  70. package/dist/hermes-adapter.d.ts.map +1 -1
  71. package/dist/hermes-adapter.js +4 -0
  72. package/dist/hermes-adapter.js.map +1 -1
  73. package/dist/index.d.ts +4 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +3 -10
  76. package/dist/index.js.map +1 -1
  77. package/dist/mcp-plugins.d.ts +5 -3
  78. package/dist/mcp-plugins.d.ts.map +1 -1
  79. package/dist/mcp-plugins.js +58 -24
  80. package/dist/mcp-plugins.js.map +1 -1
  81. package/dist/omp-adapter.d.ts.map +1 -1
  82. package/dist/omp-adapter.js +4 -0
  83. package/dist/omp-adapter.js.map +1 -1
  84. package/dist/openclaw-adapter.d.ts +3 -1
  85. package/dist/openclaw-adapter.d.ts.map +1 -1
  86. package/dist/openclaw-adapter.js +5 -1
  87. package/dist/openclaw-adapter.js.map +1 -1
  88. package/dist/opencode-adapter.d.ts +3 -1
  89. package/dist/opencode-adapter.d.ts.map +1 -1
  90. package/dist/opencode-adapter.js +7 -3
  91. package/dist/opencode-adapter.js.map +1 -1
  92. package/dist/opencode-http-adapter.d.ts +3 -1
  93. package/dist/opencode-http-adapter.d.ts.map +1 -1
  94. package/dist/opencode-http-adapter.js +5 -1
  95. package/dist/opencode-http-adapter.js.map +1 -1
  96. package/dist/pi-adapter.d.ts.map +1 -1
  97. package/dist/pi-adapter.js +4 -0
  98. package/dist/pi-adapter.js.map +1 -1
  99. package/dist/pi-sdk-adapter.d.ts.map +1 -1
  100. package/dist/pi-sdk-adapter.js +4 -0
  101. package/dist/pi-sdk-adapter.js.map +1 -1
  102. package/dist/provider-translation.d.ts +10 -0
  103. package/dist/provider-translation.d.ts.map +1 -0
  104. package/dist/provider-translation.js +2 -0
  105. package/dist/provider-translation.js.map +1 -0
  106. package/dist/qwen-adapter.d.ts +3 -1
  107. package/dist/qwen-adapter.d.ts.map +1 -1
  108. package/dist/qwen-adapter.js +5 -1
  109. package/dist/qwen-adapter.js.map +1 -1
  110. package/dist/session-fs.d.ts +15 -5
  111. package/dist/session-fs.d.ts.map +1 -1
  112. package/dist/session-fs.js +249 -0
  113. package/dist/session-fs.js.map +1 -1
  114. package/dist/shared/runtime-hooks-virtual.d.ts +3 -0
  115. package/dist/shared/runtime-hooks-virtual.d.ts.map +1 -0
  116. package/dist/shared/runtime-hooks-virtual.js +13 -0
  117. package/dist/shared/runtime-hooks-virtual.js.map +1 -0
  118. package/dist/translate-for-harness.d.ts +6 -0
  119. package/dist/translate-for-harness.d.ts.map +1 -0
  120. package/dist/translate-for-harness.js +36 -0
  121. package/dist/translate-for-harness.js.map +1 -0
  122. package/dist/translations/claude-translation.d.ts +4 -0
  123. package/dist/translations/claude-translation.d.ts.map +1 -0
  124. package/dist/translations/claude-translation.js +50 -0
  125. package/dist/translations/claude-translation.js.map +1 -0
  126. package/dist/translations/codex-translation.d.ts +4 -0
  127. package/dist/translations/codex-translation.d.ts.map +1 -0
  128. package/dist/translations/codex-translation.js +32 -0
  129. package/dist/translations/codex-translation.js.map +1 -0
  130. package/dist/translations/gemini-translation.d.ts +4 -0
  131. package/dist/translations/gemini-translation.d.ts.map +1 -0
  132. package/dist/translations/gemini-translation.js +20 -0
  133. package/dist/translations/gemini-translation.js.map +1 -0
  134. package/dist/translations/generic-openai-translation.d.ts +4 -0
  135. package/dist/translations/generic-openai-translation.d.ts.map +1 -0
  136. package/dist/translations/generic-openai-translation.js +19 -0
  137. package/dist/translations/generic-openai-translation.js.map +1 -0
  138. package/dist/translations/opencode-translation.d.ts +4 -0
  139. package/dist/translations/opencode-translation.d.ts.map +1 -0
  140. package/dist/translations/opencode-translation.js +51 -0
  141. package/dist/translations/opencode-translation.js.map +1 -0
  142. package/package.json +4 -2
  143. package/dist/mocks/index.d.ts +0 -60
  144. package/dist/mocks/index.d.ts.map +0 -1
  145. package/dist/mocks/index.js +0 -203
  146. package/dist/mocks/index.js.map +0 -1
  147. package/dist/mocks/mock-types.d.ts +0 -260
  148. package/dist/mocks/mock-types.d.ts.map +0 -1
  149. package/dist/mocks/mock-types.js +0 -12
  150. package/dist/mocks/mock-types.js.map +0 -1
  151. package/dist/mocks/programmatic-mocks.d.ts +0 -50
  152. package/dist/mocks/programmatic-mocks.d.ts.map +0 -1
  153. package/dist/mocks/programmatic-mocks.js +0 -330
  154. package/dist/mocks/programmatic-mocks.js.map +0 -1
  155. package/dist/mocks/remote-mocks.d.ts +0 -52
  156. package/dist/mocks/remote-mocks.d.ts.map +0 -1
  157. package/dist/mocks/remote-mocks.js +0 -436
  158. package/dist/mocks/remote-mocks.js.map +0 -1
@@ -1,20 +1,74 @@
1
- /**
2
- * ClaudeAgentSdkAdapter — Direct Claude Agent SDK integration.
3
- *
4
- * Uses the Claude Agent SDK directly instead of the Claude Code CLI for
5
- * better performance, more granular control, and native programmatic access
6
- * to Claude's advanced agent capabilities.
7
- */
8
1
  import * as os from 'node:os';
9
2
  import * as path from 'node:path';
3
+ import { promises as fs } from 'node:fs';
4
+ import { createRequire } from 'node:module';
10
5
  import { BaseProgrammaticAdapter } from './programmatic-adapter-base.js';
6
+ import { createVirtualRuntimeHookCapabilities } from './shared/runtime-hooks-virtual.js';
11
7
  import { mcpListPlugins, mcpInstallPlugin, mcpUninstallPlugin } from './mcp-plugins.js';
12
8
  import { listJsonlFiles, parseJsonlSessionFile, readJsonFile, writeJsonFileAtomic, } from './session-fs.js';
9
+ const require = createRequire(import.meta.url);
10
+ class AsyncQueue {
11
+ values = [];
12
+ waiters = [];
13
+ closed = false;
14
+ failure = null;
15
+ enqueue(value) {
16
+ if (this.closed) {
17
+ throw new Error('Queue is closed');
18
+ }
19
+ const waiter = this.waiters.shift();
20
+ if (waiter) {
21
+ waiter.resolve({ value, done: false });
22
+ return;
23
+ }
24
+ this.values.push(value);
25
+ }
26
+ fail(error) {
27
+ if (this.closed) {
28
+ return;
29
+ }
30
+ this.closed = true;
31
+ this.failure = error;
32
+ for (const waiter of this.waiters.splice(0)) {
33
+ waiter.reject(error);
34
+ }
35
+ }
36
+ close() {
37
+ if (this.closed) {
38
+ return;
39
+ }
40
+ this.closed = true;
41
+ for (const waiter of this.waiters.splice(0)) {
42
+ waiter.resolve({ value: undefined, done: true });
43
+ }
44
+ }
45
+ async next() {
46
+ if (this.values.length > 0) {
47
+ return { value: this.values.shift(), done: false };
48
+ }
49
+ if (this.failure != null) {
50
+ throw this.failure;
51
+ }
52
+ if (this.closed) {
53
+ return { value: undefined, done: true };
54
+ }
55
+ return new Promise((resolve, reject) => {
56
+ this.waiters.push({ resolve, reject });
57
+ });
58
+ }
59
+ async return() {
60
+ this.close();
61
+ return { value: undefined, done: true };
62
+ }
63
+ [Symbol.asyncIterator]() {
64
+ return this;
65
+ }
66
+ }
13
67
  export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
14
68
  agent = 'claude-agent-sdk';
15
69
  displayName = 'Claude (Agent SDK)';
16
- minVersion = '0.1.0';
17
- hostEnvSignals = ['ANTHROPIC_API_KEY', 'CLAUDE_AGENT_API_KEY'];
70
+ minVersion = '0.2.0';
71
+ hostEnvSignals = ['ANTHROPIC_API_KEY', 'CLAUDE_AGENT_API_KEY', 'CLAUDE_CODE_ENTRYPOINT'];
18
72
  capabilities = {
19
73
  agent: 'claude-agent-sdk',
20
74
  canResume: true,
@@ -29,11 +83,14 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
29
83
  supportsParallelToolCalls: true,
30
84
  requiresToolApproval: true,
31
85
  approvalModes: ['yolo', 'prompt', 'deny'],
86
+ runtimeHooks: createVirtualRuntimeHookCapabilities(),
32
87
  supportsThinking: true,
33
88
  thinkingEffortLevels: ['low', 'medium', 'high', 'max'],
34
89
  supportsThinkingBudgetTokens: true,
35
90
  supportsJsonMode: true,
36
91
  supportsStructuredOutput: true,
92
+ structuredSessionTransport: 'persistent',
93
+ sessionControlPlane: 'self-managed',
37
94
  supportsSkills: true,
38
95
  supportsAgentsMd: true,
39
96
  skillsFormat: 'file',
@@ -44,7 +101,7 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
44
101
  supportsStdinInjection: true,
45
102
  supportsImageInput: true,
46
103
  supportsImageOutput: false,
47
- supportsFileAttachments: true,
104
+ supportsFileAttachments: false,
48
105
  supportsPlugins: true,
49
106
  pluginFormats: ['mcp-server'],
50
107
  pluginRegistries: [{ name: 'mcp', url: 'https://modelcontextprotocol.io', searchable: false }],
@@ -53,11 +110,11 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
53
110
  requiresPty: false,
54
111
  authMethods: [
55
112
  { type: 'api_key', name: 'API Key', description: 'ANTHROPIC_API_KEY environment variable' },
56
- { type: 'oauth', name: 'OAuth', description: 'OAuth-based authentication' },
113
+ { type: 'oauth', name: 'Claude Login', description: 'Claude Code browser login or stored credentials' },
57
114
  ],
58
- authFiles: ['.claude.json', '.claude/settings.json'],
115
+ authFiles: ['.claude.json', '.claude/.credentials.json', '.claude/settings.json'],
59
116
  installMethods: [
60
- { platform: 'all', type: 'npm', command: 'npm install -g @anthropic-ai/agent-sdk' },
117
+ { platform: 'all', type: 'npm', command: 'npm install -g @anthropic-ai/claude-agent-sdk' },
61
118
  ],
62
119
  };
63
120
  models = [
@@ -85,7 +142,7 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
85
142
  supportsThinkingStreaming: true,
86
143
  supportsImageInput: true,
87
144
  supportsImageOutput: false,
88
- supportsFileInput: true,
145
+ supportsFileInput: false,
89
146
  cliArgKey: 'model',
90
147
  cliArgValue: 'claude-sonnet-4-20250514',
91
148
  lastUpdated: '2025-05-14',
@@ -115,7 +172,7 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
115
172
  supportsThinkingStreaming: true,
116
173
  supportsImageInput: true,
117
174
  supportsImageOutput: false,
118
- supportsFileInput: true,
175
+ supportsFileInput: false,
119
176
  cliArgKey: 'model',
120
177
  cliArgValue: 'claude-opus-4-20250514',
121
178
  lastUpdated: '2025-05-14',
@@ -132,213 +189,189 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
132
189
  configFormat: 'json',
133
190
  supportsProjectConfig: true,
134
191
  };
135
- async *execute(options) {
192
+ execute(options) {
136
193
  this.validateRunOptions(options);
137
194
  const runId = this.generateRunId();
138
195
  const modelId = this.resolveModel(options);
139
- const prompt = this.normalizePrompt(options.prompt);
140
- // Check authentication
141
- const authState = await this.detectAuth();
142
- if (authState.status !== 'authenticated') {
143
- yield this.createErrorEvent(runId, 'AUTH_MISSING', 'Anthropic API key not found', false);
144
- return;
145
- }
146
- try {
147
- // Emit session start
148
- yield {
149
- ...this.createBaseEvent('session_start', runId),
150
- type: 'session_start',
151
- sessionId: options.sessionId || runId,
152
- resumed: Boolean(options.sessionId),
153
- };
154
- // Create Claude Agent SDK client (in real implementation)
155
- const client = this.createClaudeAgentClient();
156
- // Build messages array
157
- const messages = [
158
- {
159
- role: 'user',
160
- content: prompt,
161
- },
162
- ];
163
- // Add system prompt if provided
164
- const systemPrompt = options.systemPrompt || this.buildDefaultSystemPrompt(options);
165
- // Define available tools for Claude agent capabilities
166
- const tools = [
167
- {
168
- name: 'read_file',
169
- description: 'Read the contents of a file',
170
- input_schema: {
171
- type: 'object',
172
- properties: {
173
- path: {
174
- type: 'string',
175
- description: 'Path to the file to read',
176
- },
177
- },
178
- required: ['path'],
179
- },
180
- },
181
- {
182
- name: 'write_file',
183
- description: 'Write content to a file',
184
- input_schema: {
185
- type: 'object',
186
- properties: {
187
- path: {
188
- type: 'string',
189
- description: 'Path to the file to write',
190
- },
191
- content: {
192
- type: 'string',
193
- description: 'Content to write to the file',
194
- },
195
- },
196
- required: ['path', 'content'],
197
- },
198
- },
199
- {
200
- name: 'execute_bash',
201
- description: 'Execute a bash command',
202
- input_schema: {
203
- type: 'object',
204
- properties: {
205
- command: {
206
- type: 'string',
207
- description: 'Bash command to execute',
208
- },
209
- },
210
- required: ['command'],
211
- },
212
- },
213
- {
214
- name: 'spawn_subagent',
215
- description: 'Spawn a subagent to handle a specific task',
216
- input_schema: {
217
- type: 'object',
218
- properties: {
219
- task: {
220
- type: 'string',
221
- description: 'Task description for the subagent',
222
- },
223
- agent_type: {
224
- type: 'string',
225
- description: 'Type of agent to spawn',
226
- enum: ['claude', 'codex', 'opencode'],
227
- },
228
- },
229
- required: ['task'],
230
- },
231
- },
232
- ];
233
- // Make streaming API call with thinking enabled
234
- const stream = await this.createClaudeStream({
235
- model: modelId,
236
- messages,
237
- system: systemPrompt,
238
- tools,
239
- max_tokens: options.maxTokens || 8192,
240
- temperature: 0.1,
241
- stream: true,
242
- thinking_enabled: true,
243
- thinking_effort: options.thinkingEffort || 'medium',
244
- thinking_budget_tokens: options.thinkingBudgetTokens,
245
- });
246
- let textAccumulated = '';
247
- let thinkingAccumulated = '';
248
- let currentToolCall = null;
249
- let inThinking = false;
250
- for await (const chunk of stream) {
251
- switch (chunk.type) {
252
- case 'message_start':
253
- // Message started - no specific action needed
254
- break;
255
- case 'content_block_start':
256
- if (chunk.content_block?.type === 'text') {
257
- // Text content block started
258
- }
259
- else if (chunk.content_block?.type === 'tool_use') {
260
- // Tool use block started
261
- currentToolCall = {
262
- id: chunk.content_block.id,
263
- name: chunk.content_block.name,
264
- input: '',
265
- };
266
- yield this.createToolCallStartEvent(runId, currentToolCall.id, currentToolCall.name, '');
267
- }
268
- break;
269
- case 'content_block_delta':
270
- if (chunk.delta?.type === 'text_delta' && chunk.delta.text) {
271
- // Check if this is thinking content
272
- if (chunk.delta.text.includes('<thinking>')) {
273
- inThinking = true;
274
- }
275
- if (inThinking) {
276
- // Thinking content
277
- thinkingAccumulated += chunk.delta.text;
278
- yield {
279
- ...this.createBaseEvent('thinking_delta', runId),
280
- type: 'thinking_delta',
281
- delta: chunk.delta.text,
282
- accumulated: thinkingAccumulated,
283
- };
284
- if (chunk.delta.text.includes('</thinking>')) {
285
- inThinking = false;
286
- }
287
- }
288
- else {
289
- // Regular text content
290
- textAccumulated += chunk.delta.text;
291
- yield this.createTextDeltaEvent(runId, chunk.delta.text, textAccumulated);
292
- }
293
- }
294
- else if (chunk.delta?.type === 'input_json_delta' && currentToolCall) {
295
- // Tool input streaming
296
- currentToolCall.input += chunk.delta.partial_json || '';
297
- yield {
298
- ...this.createBaseEvent('tool_input_delta', runId),
299
- type: 'tool_input_delta',
300
- toolCallId: currentToolCall.id,
301
- delta: chunk.delta.partial_json || '',
302
- inputAccumulated: currentToolCall.input,
303
- };
304
- }
305
- break;
306
- case 'content_block_stop':
307
- if (currentToolCall) {
308
- // Tool call ready
309
- yield {
310
- ...this.createBaseEvent('tool_call_ready', runId),
311
- type: 'tool_call_ready',
312
- toolCallId: currentToolCall.id,
313
- toolName: currentToolCall.name,
314
- input: currentToolCall.input,
315
- };
316
- // Execute the tool (mock execution)
317
- const toolResult = await this.executeMockTool(currentToolCall.name, currentToolCall.input);
318
- yield this.createToolResultEvent(runId, currentToolCall.id, currentToolCall.name, toolResult, 150 // mock duration
319
- );
320
- currentToolCall = null;
321
- }
322
- break;
323
- case 'message_delta':
324
- // Handle message-level changes
325
- break;
326
- case 'message_stop':
327
- // Message completed
328
- if (chunk.message?.usage) {
329
- const cost = this.extractCostFromUsage(chunk.message.usage, modelId);
330
- if (cost) {
331
- yield this.createCostEvent(runId, cost);
332
- }
333
- }
334
- yield this.createMessageStopEvent(runId, textAccumulated);
335
- break;
196
+ const events = new AsyncQueue();
197
+ const prompts = new AsyncQueue();
198
+ const pendingApprovals = new Map();
199
+ const pendingInputs = new Map();
200
+ const toolsByIndex = new Map();
201
+ const toolsById = new Map();
202
+ let queryHandle = null;
203
+ let closed = false;
204
+ let turnIndex = -1;
205
+ let textAccumulated = '';
206
+ let thinkingAccumulated = '';
207
+ let sessionId;
208
+ const queueError = (code, message) => {
209
+ events.enqueue(this.createErrorEvent(runId, code, message, false));
210
+ };
211
+ const rejectPending = (reason) => {
212
+ for (const pending of pendingApprovals.values()) {
213
+ pending.reject(reason);
214
+ }
215
+ pendingApprovals.clear();
216
+ for (const pending of pendingInputs.values()) {
217
+ pending.reject(reason);
218
+ }
219
+ pendingInputs.clear();
220
+ };
221
+ const closeRun = async () => {
222
+ if (closed) {
223
+ return;
224
+ }
225
+ closed = true;
226
+ prompts.close();
227
+ rejectPending(new Error('Claude Agent SDK session closed'));
228
+ queryHandle?.close();
229
+ };
230
+ const sdkPromise = (async () => {
231
+ try {
232
+ const authState = await this.detectAuth();
233
+ if (authState.status !== 'authenticated') {
234
+ queueError('AUTH_MISSING', 'Anthropic authentication not found');
235
+ return;
336
236
  }
237
+ if (options.attachments?.some((attachment) => !this.isImageAttachment(attachment.mimeType, attachment.filePath, attachment.url))) {
238
+ queueError('CAPABILITY_ERROR', 'claude-agent-sdk currently supports only image attachments through agent-mux');
239
+ return;
240
+ }
241
+ const initialMessage = await this.buildUserMessage(this.normalizePrompt(options.prompt), options.attachments);
242
+ prompts.enqueue(initialMessage);
243
+ if (options.nonInteractive) {
244
+ prompts.close();
245
+ }
246
+ const sdk = await this.loadSdkModule();
247
+ queryHandle = sdk.query({
248
+ prompt: prompts,
249
+ options: this.buildSdkOptions(options, modelId, async (toolName, input, ctx) => {
250
+ const interactionId = ctx.toolUseID || `approval-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
251
+ events.enqueue({
252
+ ...this.createBaseEvent('approval_request', runId),
253
+ type: 'approval_request',
254
+ interactionId,
255
+ action: ctx.title || `Allow ${toolName}`,
256
+ detail: ctx.description || JSON.stringify(input),
257
+ toolName,
258
+ riskLevel: this.estimateRiskLevel(toolName),
259
+ });
260
+ return await new Promise((resolve, reject) => {
261
+ pendingApprovals.set(interactionId, { resolve, reject });
262
+ });
263
+ }, async (request) => {
264
+ const interactionId = request.elicitationId || `input-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
265
+ events.enqueue({
266
+ ...this.createBaseEvent('input_required', runId),
267
+ type: 'input_required',
268
+ interactionId,
269
+ question: request.title || request.message,
270
+ context: request.description || request.url,
271
+ source: 'tool',
272
+ });
273
+ return await new Promise((resolve, reject) => {
274
+ pendingInputs.set(interactionId, { resolve, reject });
275
+ });
276
+ }),
277
+ });
278
+ for await (const message of queryHandle) {
279
+ const translated = this.translateSdkMessage({
280
+ runId,
281
+ options,
282
+ message,
283
+ modelId,
284
+ textAccumulated,
285
+ thinkingAccumulated,
286
+ toolsByIndex,
287
+ toolsById,
288
+ turnIndex,
289
+ sessionId,
290
+ });
291
+ textAccumulated = translated.textAccumulated;
292
+ thinkingAccumulated = translated.thinkingAccumulated;
293
+ turnIndex = translated.turnIndex;
294
+ sessionId = translated.sessionId;
295
+ for (const event of translated.events) {
296
+ events.enqueue(event);
297
+ }
298
+ }
299
+ }
300
+ catch (error) {
301
+ const message = error instanceof Error ? error.message : String(error);
302
+ queueError('INTERNAL', `Claude Agent SDK error: ${message}`);
303
+ }
304
+ finally {
305
+ await closeRun();
306
+ events.close();
337
307
  }
308
+ })();
309
+ void sdkPromise;
310
+ return Object.assign(events, {
311
+ send: async (text) => {
312
+ if (closed) {
313
+ throw new Error('Claude Agent SDK session is closed');
314
+ }
315
+ prompts.enqueue(await this.buildUserMessage(text, []));
316
+ },
317
+ respond: async (interactionId, response) => {
318
+ const pendingApproval = pendingApprovals.get(interactionId);
319
+ if (pendingApproval) {
320
+ pendingApprovals.delete(interactionId);
321
+ if (response.type === 'approve') {
322
+ events.enqueue({
323
+ ...this.createBaseEvent('approval_granted', runId),
324
+ type: 'approval_granted',
325
+ interactionId,
326
+ });
327
+ pendingApproval.resolve({ behavior: 'allow' });
328
+ return;
329
+ }
330
+ if (response.type === 'deny') {
331
+ events.enqueue({
332
+ ...this.createBaseEvent('approval_denied', runId),
333
+ type: 'approval_denied',
334
+ interactionId,
335
+ reason: response.reason,
336
+ });
337
+ pendingApproval.resolve({ behavior: 'deny', message: response.reason || 'Denied by user' });
338
+ return;
339
+ }
340
+ throw new Error('Approval requests require approve/deny responses');
341
+ }
342
+ const pendingInput = pendingInputs.get(interactionId);
343
+ if (pendingInput) {
344
+ pendingInputs.delete(interactionId);
345
+ if (response.type !== 'text') {
346
+ pendingInput.resolve({ action: 'decline' });
347
+ return;
348
+ }
349
+ pendingInput.resolve({ action: 'accept', content: { response: response.text } });
350
+ return;
351
+ }
352
+ throw new Error(`No pending Claude SDK interaction with id '${interactionId}'`);
353
+ },
354
+ interrupt: async () => {
355
+ await queryHandle?.interrupt();
356
+ },
357
+ close: async () => {
358
+ await closeRun();
359
+ },
360
+ });
361
+ }
362
+ async detectInstallation() {
363
+ try {
364
+ const sdkEntry = require.resolve('@anthropic-ai/claude-agent-sdk');
365
+ const pkgPath = path.join(path.dirname(sdkEntry), 'package.json');
366
+ const pkg = await readJsonFile(pkgPath);
367
+ return {
368
+ installed: true,
369
+ version: pkg?.version ?? undefined,
370
+ path: sdkEntry,
371
+ };
338
372
  }
339
- catch (error) {
340
- const message = error instanceof Error ? error.message : String(error);
341
- yield this.createErrorEvent(runId, 'INTERNAL', `SDK error: ${message}`, false);
373
+ catch {
374
+ return { installed: false };
342
375
  }
343
376
  }
344
377
  async detectAuth() {
@@ -350,21 +383,32 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
350
383
  identity: `anthropic:...${apiKey.slice(-4)}`,
351
384
  };
352
385
  }
353
- // Check Claude settings file
354
- const claudeHome = path.join(os.homedir(), '.claude');
355
- const settingsPath = path.join(claudeHome, 'settings.json');
356
- try {
357
- const settings = await readJsonFile(settingsPath);
358
- if (settings?.user?.id) {
359
- return {
360
- status: 'authenticated',
361
- method: 'oauth',
362
- identity: `claude:${settings.user.id}`,
363
- };
386
+ for (const credentialsPath of [
387
+ path.join(os.homedir(), '.claude', '.credentials.json'),
388
+ path.join(os.homedir(), '.claude.json'),
389
+ path.join(os.homedir(), '.claude', 'settings.json'),
390
+ ]) {
391
+ try {
392
+ const data = await readJsonFile(credentialsPath);
393
+ if (data) {
394
+ const email = typeof data['email'] === 'string' ? data['email'] : undefined;
395
+ const userId = typeof data['userId'] === 'string'
396
+ ? data['userId']
397
+ : typeof data['user']?.['id'] === 'string'
398
+ ? data['user']['id']
399
+ : undefined;
400
+ if (email || userId || Object.keys(data).length > 0) {
401
+ return {
402
+ status: 'authenticated',
403
+ method: 'oauth',
404
+ identity: email ? `claude:${email}` : `claude:${userId ?? 'local'}`,
405
+ };
406
+ }
407
+ }
408
+ }
409
+ catch {
410
+ // Ignore missing or invalid auth files.
364
411
  }
365
- }
366
- catch {
367
- // Settings file not found or invalid
368
412
  }
369
413
  return { status: 'unauthenticated' };
370
414
  }
@@ -375,31 +419,31 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
375
419
  steps: [
376
420
  {
377
421
  step: 1,
378
- description: 'Get an API key from https://console.anthropic.com/',
379
- url: 'https://console.anthropic.com/'
422
+ description: 'Get an API key from https://console.anthropic.com/settings/keys',
423
+ url: 'https://console.anthropic.com/settings/keys',
380
424
  },
381
425
  {
382
426
  step: 2,
383
427
  description: 'Set the ANTHROPIC_API_KEY environment variable',
384
- command: 'export ANTHROPIC_API_KEY=sk-ant-...'
428
+ command: 'export ANTHROPIC_API_KEY=sk-ant-...',
385
429
  },
386
430
  {
387
431
  step: 3,
388
- description: 'Alternatively, authenticate via Claude CLI',
389
- command: 'claude auth'
432
+ description: 'Or sign in through Claude Code and let the SDK reuse the stored credentials',
433
+ command: 'claude',
390
434
  },
391
435
  ],
392
436
  envVars: [
393
- { name: 'ANTHROPIC_API_KEY', description: 'Anthropic API key', required: true, exampleFormat: 'sk-ant-...' },
394
- { name: 'CLAUDE_AGENT_API_KEY', description: 'Claude Agent SDK API key', required: false, exampleFormat: 'sk-ant-...' },
437
+ { name: 'ANTHROPIC_API_KEY', description: 'Anthropic API key', required: false, exampleFormat: 'sk-ant-...' },
438
+ { name: 'CLAUDE_AGENT_API_KEY', description: 'Alternate Anthropic API key env var', required: false, exampleFormat: 'sk-ant-...' },
395
439
  ],
396
- documentationUrls: ['https://docs.anthropic.com/claude/docs'],
397
- loginCommand: 'claude auth',
398
- verifyCommand: 'claude --version',
440
+ documentationUrls: ['https://platform.claude.com/docs/en/agent-sdk/overview'],
441
+ loginCommand: 'claude',
442
+ verifyCommand: 'node -e "import(\'@anthropic-ai/claude-agent-sdk\').then(() => console.log(\'OK\'))"',
399
443
  };
400
444
  }
401
445
  sessionDir(_cwd) {
402
- return path.join(os.homedir(), '.claude', 'sessions');
446
+ return path.join(os.homedir(), '.claude', 'projects');
403
447
  }
404
448
  async parseSessionFile(filePath) {
405
449
  const parsed = await parseJsonlSessionFile(filePath, 'claude-agent-sdk');
@@ -420,187 +464,375 @@ export class ClaudeAgentSdkAdapter extends BaseProgrammaticAdapter {
420
464
  if (!filePath)
421
465
  return;
422
466
  const existing = (await readJsonFile(filePath)) ?? {};
423
- const { agent: _a, source: _s, filePaths: _fp, ...rest } = config;
424
- void _a;
425
- void _s;
426
- void _fp;
467
+ const { agent: _agent, source: _source, filePaths: _filePaths, ...rest } = config;
468
+ void _agent;
469
+ void _source;
470
+ void _filePaths;
427
471
  await writeJsonFileAtomic(filePath, { ...existing, ...rest });
428
472
  }
429
- // ── MCP Plugin Support ─────────────────────────────────────────────
430
473
  async listPlugins() {
431
474
  return mcpListPlugins(this.agent);
432
475
  }
433
476
  async installPlugin(pluginId, options) {
434
477
  return mcpInstallPlugin(this.agent, pluginId, options);
435
478
  }
436
- async uninstallPlugin(pluginId) {
479
+ async uninstallPlugin(pluginId, _options) {
437
480
  return mcpUninstallPlugin(this.agent, pluginId);
438
481
  }
439
- // ── Private implementation methods ─────────────────────────────────
440
- /**
441
- * Create Claude Agent SDK client (mock implementation).
442
- * In real implementation, this would return an actual Claude Agent SDK client.
443
- */
444
- createClaudeAgentClient() {
445
- // Mock client - in real implementation, this would be:
446
- // return new ClaudeAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
447
- return {};
482
+ async loadSdkModule() {
483
+ return await import('@anthropic-ai/claude-agent-sdk');
448
484
  }
449
- /**
450
- * Create Claude stream (mock implementation).
451
- * In real implementation, this would use the Claude Agent SDK.
452
- */
453
- async createClaudeStream(params) {
454
- // Mock streaming response with thinking
455
- const mockChunks = [
456
- {
457
- type: 'message_start',
458
- message: {
459
- id: 'msg_123',
460
- type: 'message',
461
- role: 'assistant',
462
- content: [],
463
- model: params.model,
464
- stop_reason: null,
465
- stop_sequence: null,
466
- usage: { input_tokens: 0, output_tokens: 0 },
467
- },
468
- },
469
- // Thinking stream
470
- {
471
- type: 'content_block_start',
472
- content_block: { type: 'text' },
473
- index: 0,
474
- },
475
- {
476
- type: 'content_block_delta',
477
- delta: { type: 'text_delta', text: '<thinking>\nLet me think about this task. ' },
478
- index: 0,
479
- },
480
- {
481
- type: 'content_block_delta',
482
- delta: { type: 'text_delta', text: 'I need to analyze the request and determine the best approach.\n</thinking>\n\n' },
483
- index: 0,
484
- },
485
- // Regular response
486
- {
487
- type: 'content_block_delta',
488
- delta: { type: 'text_delta', text: 'I\'ll help you with that task. ' },
489
- index: 0,
490
- },
491
- {
492
- type: 'content_block_delta',
493
- delta: { type: 'text_delta', text: 'Let me read a file to understand the context better.' },
494
- index: 0,
495
- },
496
- // Tool use
497
- {
498
- type: 'content_block_start',
499
- content_block: {
500
- type: 'tool_use',
501
- id: 'call_123',
502
- name: 'read_file',
503
- input: {}
504
- },
505
- index: 1,
506
- },
507
- {
508
- type: 'content_block_delta',
509
- delta: { type: 'input_json_delta', partial_json: '{"path": ' },
510
- index: 1,
511
- },
512
- {
513
- type: 'content_block_delta',
514
- delta: { type: 'input_json_delta', partial_json: '"README.md"}' },
515
- index: 1,
516
- },
517
- {
518
- type: 'content_block_stop',
519
- index: 1,
485
+ buildSdkOptions(options, modelId, canUseTool, onElicitation) {
486
+ const permissionMode = this.mapPermissionMode(options.approvalMode);
487
+ const sdkOptions = {
488
+ model: modelId,
489
+ cwd: options.cwd ?? process.cwd(),
490
+ env: options.env ? { ...process.env, ...options.env } : undefined,
491
+ resume: options.sessionId ?? options.forkSessionId,
492
+ forkSession: options.forkSessionId != null,
493
+ persistSession: options.noSession ? false : true,
494
+ permissionMode,
495
+ allowDangerouslySkipPermissions: permissionMode === 'bypassPermissions',
496
+ canUseTool,
497
+ onElicitation,
498
+ includePartialMessages: true,
499
+ includeHookEvents: true,
500
+ settingSources: ['user', 'project', 'local'],
501
+ mcpServers: this.buildMcpServers(options),
502
+ maxTurns: options.maxTurns,
503
+ systemPrompt: this.buildSystemPrompt(options),
504
+ stderr: (data) => {
505
+ if (data.trim().length > 0) {
506
+ // Surface stderr as a debug event via the regular SDK stream handling.
507
+ }
520
508
  },
521
- {
522
- type: 'message_stop',
523
- message: {
524
- id: 'msg_123',
525
- type: 'message',
526
- role: 'assistant',
527
- content: [],
528
- model: params.model,
529
- stop_reason: 'tool_use',
530
- stop_sequence: null,
531
- usage: { input_tokens: 200, output_tokens: 150 },
509
+ };
510
+ if (options.thinkingBudgetTokens != null) {
511
+ sdkOptions.thinking = {
512
+ type: 'enabled',
513
+ budgetTokens: options.thinkingBudgetTokens,
514
+ };
515
+ }
516
+ if (options.thinkingEffort) {
517
+ sdkOptions.effort = options.thinkingEffort === 'max' ? 'max' : options.thinkingEffort;
518
+ }
519
+ return sdkOptions;
520
+ }
521
+ buildMcpServers(options) {
522
+ if (!options.mcpServers || options.mcpServers.length === 0) {
523
+ return undefined;
524
+ }
525
+ const servers = {};
526
+ for (const server of options.mcpServers) {
527
+ if (server.transport === 'stdio') {
528
+ servers[server.name] = {
529
+ type: 'stdio',
530
+ command: server.command,
531
+ args: server.args,
532
+ env: server.env,
533
+ };
534
+ continue;
535
+ }
536
+ servers[server.name] = {
537
+ type: server.transport === 'streamable-http' ? 'http' : 'sse',
538
+ url: server.url,
539
+ headers: server.headers,
540
+ };
541
+ }
542
+ return servers;
543
+ }
544
+ buildSystemPrompt(options) {
545
+ if (!options.systemPrompt) {
546
+ return undefined;
547
+ }
548
+ if (options.systemPromptMode === 'append' || options.systemPromptMode == null) {
549
+ return {
550
+ type: 'preset',
551
+ preset: 'claude_code',
552
+ append: options.systemPrompt,
553
+ };
554
+ }
555
+ if (options.systemPromptMode === 'replace') {
556
+ return options.systemPrompt;
557
+ }
558
+ return {
559
+ type: 'preset',
560
+ preset: 'claude_code',
561
+ append: options.systemPrompt,
562
+ };
563
+ }
564
+ mapPermissionMode(mode) {
565
+ switch (mode) {
566
+ case 'yolo':
567
+ return 'bypassPermissions';
568
+ case 'deny':
569
+ return 'dontAsk';
570
+ default:
571
+ return 'default';
572
+ }
573
+ }
574
+ estimateRiskLevel(toolName) {
575
+ const lowered = toolName.toLowerCase();
576
+ if (lowered.includes('bash') || lowered.includes('delete') || lowered.includes('write') || lowered.includes('edit')) {
577
+ return 'high';
578
+ }
579
+ if (lowered.includes('read') || lowered.includes('grep') || lowered.includes('glob')) {
580
+ return 'low';
581
+ }
582
+ return 'medium';
583
+ }
584
+ async buildUserMessage(prompt, attachments = []) {
585
+ const content = [{ type: 'text', text: prompt }];
586
+ for (const attachment of attachments ?? []) {
587
+ const mimeType = await this.resolveMimeType(attachment.mimeType, attachment.filePath, attachment.url);
588
+ if (!mimeType || !mimeType.startsWith('image/')) {
589
+ throw new Error('Only image attachments are currently supported for claude-agent-sdk');
590
+ }
591
+ const data = await this.readAttachmentAsBase64(attachment);
592
+ content.push({
593
+ type: 'image',
594
+ source: {
595
+ type: 'base64',
596
+ media_type: mimeType,
597
+ data,
532
598
  },
533
- },
534
- ];
599
+ });
600
+ }
535
601
  return {
536
- async *[Symbol.asyncIterator]() {
537
- for (const chunk of mockChunks) {
538
- await new Promise(resolve => setTimeout(resolve, 100)); // Simulate streaming delay
539
- yield chunk;
540
- }
602
+ type: 'user',
603
+ message: {
604
+ role: 'user',
605
+ content: attachments && attachments.length > 0 ? content : prompt,
541
606
  },
607
+ parent_tool_use_id: null,
542
608
  };
543
609
  }
544
- /**
545
- * Build default system prompt with agent capabilities.
546
- */
547
- buildDefaultSystemPrompt(options) {
548
- let systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You have access to various tools and capabilities:\n\n';
549
- systemPrompt += '- read_file: Read file contents\n';
550
- systemPrompt += '- write_file: Write content to files\n';
551
- systemPrompt += '- execute_bash: Run bash commands\n';
552
- systemPrompt += '- spawn_subagent: Delegate tasks to specialized agents\n\n';
553
- if (options.approvalMode === 'yolo') {
554
- systemPrompt += 'Tool approval is disabled - you can execute tools freely.\n';
555
- }
556
- else {
557
- systemPrompt += 'Always ask for permission before executing potentially dangerous tools.\n';
558
- }
559
- systemPrompt += '\nProvide helpful, harmless, and honest responses while leveraging these capabilities effectively.';
560
- return systemPrompt;
610
+ isImageAttachment(mimeType, filePath, url) {
611
+ if (mimeType?.startsWith('image/')) {
612
+ return true;
613
+ }
614
+ const source = filePath ?? url;
615
+ if (!source) {
616
+ return false;
617
+ }
618
+ return /\.(png|jpe?g|gif|webp|bmp)$/i.test(source);
561
619
  }
562
- /**
563
- * Execute mock tool calls.
564
- */
565
- async executeMockTool(name, inputJson) {
566
- try {
567
- const input = JSON.parse(inputJson);
568
- switch (name) {
569
- case 'read_file':
570
- return `Mock file contents for: ${input.path}`;
571
- case 'write_file':
572
- return `Successfully wrote ${input.content.length} characters to ${input.path}`;
573
- case 'execute_bash':
574
- return `Executed: ${input.command}\nMock output: Command completed successfully`;
575
- case 'spawn_subagent':
576
- return `Spawned ${input.agent_type || 'claude'} subagent for task: ${input.task}`;
577
- default:
578
- return `Unknown tool: ${name}`;
620
+ async readAttachmentAsBase64(attachment) {
621
+ if (attachment.base64) {
622
+ return attachment.base64;
623
+ }
624
+ if (attachment.filePath) {
625
+ const data = await fs.readFile(attachment.filePath);
626
+ return data.toString('base64');
627
+ }
628
+ if (attachment.url) {
629
+ const response = await fetch(attachment.url);
630
+ if (!response.ok) {
631
+ throw new Error(`Failed to fetch attachment from ${attachment.url}: ${response.status}`);
579
632
  }
633
+ const data = Buffer.from(await response.arrayBuffer());
634
+ return data.toString('base64');
580
635
  }
581
- catch (error) {
582
- return `Error executing tool ${name}: ${error}`;
636
+ throw new Error('Attachment must provide base64, filePath, or url');
637
+ }
638
+ async resolveMimeType(mimeType, filePath, url) {
639
+ if (mimeType) {
640
+ return mimeType;
583
641
  }
642
+ const source = (filePath ?? url ?? '').toLowerCase();
643
+ if (source.endsWith('.png'))
644
+ return 'image/png';
645
+ if (source.endsWith('.jpg') || source.endsWith('.jpeg'))
646
+ return 'image/jpeg';
647
+ if (source.endsWith('.gif'))
648
+ return 'image/gif';
649
+ if (source.endsWith('.webp'))
650
+ return 'image/webp';
651
+ if (source.endsWith('.bmp'))
652
+ return 'image/bmp';
653
+ return undefined;
584
654
  }
585
- /**
586
- * Extract cost information from Claude usage object.
587
- */
588
- extractCostFromUsage(usage, modelId) {
589
- const model = this.models.find(m => m.modelId === modelId);
655
+ translateSdkMessage(args) {
656
+ const { runId, options, message, modelId, toolsByIndex, toolsById, } = args;
657
+ let textAccumulated = args.textAccumulated;
658
+ let thinkingAccumulated = args.thinkingAccumulated;
659
+ let turnIndex = args.turnIndex;
660
+ let sessionId = args.sessionId;
661
+ const events = [];
662
+ if (message.type === 'system' && message.subtype === 'init') {
663
+ sessionId = message.session_id;
664
+ events.push({
665
+ ...this.createBaseEvent('session_start', runId),
666
+ type: 'session_start',
667
+ sessionId,
668
+ resumed: Boolean(options.sessionId),
669
+ forkedFrom: options.forkSessionId,
670
+ });
671
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
672
+ }
673
+ if (message.type === 'system' && message.subtype === 'session_state_changed') {
674
+ if (message.state === 'running') {
675
+ textAccumulated = '';
676
+ thinkingAccumulated = '';
677
+ turnIndex += 1;
678
+ events.push({
679
+ ...this.createBaseEvent('turn_start', runId),
680
+ type: 'turn_start',
681
+ turnIndex,
682
+ });
683
+ }
684
+ else if (message.state === 'idle' && turnIndex >= 0) {
685
+ if (thinkingAccumulated.length > 0) {
686
+ events.push({
687
+ ...this.createBaseEvent('thinking_stop', runId),
688
+ type: 'thinking_stop',
689
+ thinking: thinkingAccumulated,
690
+ });
691
+ thinkingAccumulated = '';
692
+ }
693
+ if (textAccumulated.length > 0) {
694
+ events.push(this.createMessageStopEvent(runId, textAccumulated));
695
+ textAccumulated = '';
696
+ }
697
+ events.push({
698
+ ...this.createBaseEvent('turn_end', runId),
699
+ type: 'turn_end',
700
+ turnIndex,
701
+ });
702
+ }
703
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
704
+ }
705
+ if (message.type === 'stream_event') {
706
+ const event = message.event;
707
+ if (event.type === 'message_start') {
708
+ events.push({
709
+ ...this.createBaseEvent('message_start', runId),
710
+ type: 'message_start',
711
+ });
712
+ }
713
+ if (event.type === 'content_block_start' && event.content_block) {
714
+ const contentBlock = event.content_block;
715
+ if (contentBlock.type === 'tool_use' || contentBlock.type === 'server_tool_use' || contentBlock.type === 'mcp_tool_use') {
716
+ const id = String(contentBlock.id ?? `tool-${event.index}`);
717
+ const state = {
718
+ id,
719
+ name: String(contentBlock.name ?? 'tool'),
720
+ rawInput: contentBlock.input ? JSON.stringify(contentBlock.input) : '',
721
+ };
722
+ toolsByIndex.set(Number(event.index ?? 0), state);
723
+ toolsById.set(id, state);
724
+ events.push(this.createToolCallStartEvent(runId, id, state.name, state.rawInput));
725
+ }
726
+ else if (contentBlock.type === 'thinking') {
727
+ events.push({
728
+ ...this.createBaseEvent('thinking_start', runId),
729
+ type: 'thinking_start',
730
+ });
731
+ }
732
+ }
733
+ if (event.type === 'content_block_delta' && event.delta) {
734
+ const delta = event.delta;
735
+ if (delta.type === 'text_delta' && typeof delta.text === 'string') {
736
+ textAccumulated += delta.text;
737
+ events.push(this.createTextDeltaEvent(runId, delta.text, textAccumulated));
738
+ }
739
+ else if (delta.type === 'thinking_delta' && typeof delta.thinking === 'string') {
740
+ thinkingAccumulated += delta.thinking;
741
+ events.push({
742
+ ...this.createBaseEvent('thinking_delta', runId),
743
+ type: 'thinking_delta',
744
+ delta: delta.thinking,
745
+ accumulated: thinkingAccumulated,
746
+ });
747
+ }
748
+ else if (delta.type === 'input_json_delta' && typeof delta.partial_json === 'string') {
749
+ const state = toolsByIndex.get(Number(event.index ?? 0));
750
+ if (state) {
751
+ state.rawInput += delta.partial_json;
752
+ events.push({
753
+ ...this.createBaseEvent('tool_input_delta', runId),
754
+ type: 'tool_input_delta',
755
+ toolCallId: state.id,
756
+ delta: delta.partial_json,
757
+ inputAccumulated: state.rawInput,
758
+ });
759
+ }
760
+ }
761
+ }
762
+ if (event.type === 'content_block_stop') {
763
+ const state = toolsByIndex.get(Number(event.index ?? 0));
764
+ if (state) {
765
+ events.push({
766
+ ...this.createBaseEvent('tool_call_ready', runId),
767
+ type: 'tool_call_ready',
768
+ toolCallId: state.id,
769
+ toolName: state.name,
770
+ input: this.parseToolInput(state.rawInput),
771
+ });
772
+ }
773
+ }
774
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
775
+ }
776
+ if (message.type === 'user' && message.parent_tool_use_id && message.tool_use_result !== undefined) {
777
+ const toolState = toolsById.get(message.parent_tool_use_id);
778
+ events.push(this.createToolResultEvent(runId, message.parent_tool_use_id, toolState?.name ?? 'tool', message.tool_use_result));
779
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
780
+ }
781
+ if (message.type === 'result') {
782
+ const cost = this.extractCostFromResult(message, modelId);
783
+ if (cost) {
784
+ events.push(this.createCostEvent(runId, cost));
785
+ }
786
+ if (message.is_error) {
787
+ events.push(this.createErrorEvent(runId, 'INTERNAL', message.subtype === 'success' ? 'Claude Agent SDK returned an error result' : message.errors.join('; ') || message.subtype, false));
788
+ }
789
+ events.push(this.createMessageStopEvent(runId, message.subtype === 'success' ? message.result : textAccumulated));
790
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
791
+ }
792
+ if (message.type === 'system' && message.subtype === 'notification') {
793
+ events.push({
794
+ ...this.createBaseEvent('debug', runId),
795
+ type: 'debug',
796
+ level: 'info',
797
+ message: message.text,
798
+ });
799
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
800
+ }
801
+ if (message.type === 'system' && message.subtype === 'local_command_output') {
802
+ events.push(this.createTextDeltaEvent(runId, `${message.content}\n`, textAccumulated + `${message.content}\n`));
803
+ textAccumulated += `${message.content}\n`;
804
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
805
+ }
806
+ return { events, textAccumulated, thinkingAccumulated, turnIndex, sessionId };
807
+ }
808
+ parseToolInput(rawInput) {
809
+ if (!rawInput) {
810
+ return {};
811
+ }
812
+ try {
813
+ return JSON.parse(rawInput);
814
+ }
815
+ catch {
816
+ return rawInput;
817
+ }
818
+ }
819
+ extractCostFromResult(result, modelId) {
820
+ const model = this.models.find((candidate) => candidate.modelId === modelId);
590
821
  if (!model) {
591
- return {
592
- totalUsd: 0,
593
- inputTokens: usage.input_tokens,
594
- outputTokens: usage.output_tokens,
595
- };
822
+ return null;
596
823
  }
597
- const inputCost = (usage.input_tokens / 1_000_000) * model.inputPricePerMillion;
598
- const outputCost = (usage.output_tokens / 1_000_000) * model.outputPricePerMillion;
599
- const totalCost = inputCost + outputCost;
824
+ const usage = result.usage;
825
+ const inputTokens = typeof usage?.input_tokens === 'number' ? usage.input_tokens : 0;
826
+ const outputTokens = typeof usage?.output_tokens === 'number' ? usage.output_tokens : 0;
827
+ const cachedTokens = typeof usage?.cache_read_input_tokens === 'number' ? usage.cache_read_input_tokens : 0;
828
+ const thinkingTokens = typeof usage?.server_tool_use === 'number' ? usage.server_tool_use : 0;
600
829
  return {
601
- totalUsd: totalCost,
602
- inputTokens: usage.input_tokens,
603
- outputTokens: usage.output_tokens,
830
+ totalUsd: result.total_cost_usd ?? ((inputTokens / 1_000_000) * (model.inputPricePerMillion ?? 0) +
831
+ (outputTokens / 1_000_000) * (model.outputPricePerMillion ?? 0)),
832
+ inputTokens,
833
+ outputTokens,
834
+ cachedTokens,
835
+ thinkingTokens,
604
836
  };
605
837
  }
606
838
  }