@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.
- package/README.md +3 -1
- package/dist/agent-mux-remote-adapter.d.ts.map +1 -1
- package/dist/agent-mux-remote-adapter.js +4 -0
- package/dist/agent-mux-remote-adapter.js.map +1 -1
- package/dist/amp-adapter.d.ts +2 -1
- package/dist/amp-adapter.d.ts.map +1 -1
- package/dist/amp-adapter.js +93 -17
- package/dist/amp-adapter.js.map +1 -1
- package/dist/babysitter-adapter.d.ts +29 -0
- package/dist/babysitter-adapter.d.ts.map +1 -0
- package/dist/babysitter-adapter.js +338 -0
- package/dist/babysitter-adapter.js.map +1 -0
- package/dist/base-adapter.d.ts +7 -0
- package/dist/base-adapter.d.ts.map +1 -1
- package/dist/base-adapter.js +113 -1
- package/dist/base-adapter.js.map +1 -1
- package/dist/claude-adapter.d.ts +14 -3
- package/dist/claude-adapter.d.ts.map +1 -1
- package/dist/claude-adapter.js +222 -25
- package/dist/claude-adapter.js.map +1 -1
- package/dist/claude-agent-sdk-adapter.d.ts +21 -34
- package/dist/claude-agent-sdk-adapter.d.ts.map +1 -1
- package/dist/claude-agent-sdk-adapter.js +629 -397
- package/dist/claude-agent-sdk-adapter.js.map +1 -1
- package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts +12 -0
- package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts.map +1 -0
- package/dist/claude-code/runtime-hooks/ephemeral-config.js +143 -0
- package/dist/claude-code/runtime-hooks/ephemeral-config.js.map +1 -0
- package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts +10 -0
- package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts.map +1 -0
- package/dist/claude-code/runtime-hooks/hook-socket-server.js +79 -0
- package/dist/claude-code/runtime-hooks/hook-socket-server.js.map +1 -0
- package/dist/claude-code/runtime-hooks/lifecycle.d.ts +3 -0
- package/dist/claude-code/runtime-hooks/lifecycle.d.ts.map +1 -0
- package/dist/claude-code/runtime-hooks/lifecycle.js +24 -0
- package/dist/claude-code/runtime-hooks/lifecycle.js.map +1 -0
- package/dist/claude-remote-control-adapter.d.ts +43 -0
- package/dist/claude-remote-control-adapter.d.ts.map +1 -0
- package/dist/claude-remote-control-adapter.js +505 -0
- package/dist/claude-remote-control-adapter.js.map +1 -0
- package/dist/codex-adapter.d.ts.map +1 -1
- package/dist/codex-adapter.js +64 -41
- package/dist/codex-adapter.js.map +1 -1
- package/dist/codex-sdk-adapter.d.ts.map +1 -1
- package/dist/codex-sdk-adapter.js +6 -2
- package/dist/codex-sdk-adapter.js.map +1 -1
- package/dist/codex-websocket-adapter.d.ts +11 -18
- package/dist/codex-websocket-adapter.d.ts.map +1 -1
- package/dist/codex-websocket-adapter.js +199 -90
- package/dist/codex-websocket-adapter.js.map +1 -1
- package/dist/codex-websocket-connection.d.ts +60 -40
- package/dist/codex-websocket-connection.d.ts.map +1 -1
- package/dist/codex-websocket-connection.js +692 -203
- package/dist/codex-websocket-connection.js.map +1 -1
- package/dist/copilot-adapter.d.ts.map +1 -1
- package/dist/copilot-adapter.js +4 -0
- package/dist/copilot-adapter.js.map +1 -1
- package/dist/cursor-adapter.d.ts +3 -1
- package/dist/cursor-adapter.d.ts.map +1 -1
- package/dist/cursor-adapter.js +5 -1
- package/dist/cursor-adapter.js.map +1 -1
- package/dist/droid-adapter.d.ts +4 -2
- package/dist/droid-adapter.d.ts.map +1 -1
- package/dist/droid-adapter.js +12 -11
- package/dist/droid-adapter.js.map +1 -1
- package/dist/gemini-adapter.d.ts +3 -1
- package/dist/gemini-adapter.d.ts.map +1 -1
- package/dist/gemini-adapter.js +5 -1
- package/dist/gemini-adapter.js.map +1 -1
- package/dist/hermes-adapter.d.ts.map +1 -1
- package/dist/hermes-adapter.js +4 -0
- package/dist/hermes-adapter.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp-plugins.d.ts +5 -3
- package/dist/mcp-plugins.d.ts.map +1 -1
- package/dist/mcp-plugins.js +58 -24
- package/dist/mcp-plugins.js.map +1 -1
- package/dist/omp-adapter.d.ts.map +1 -1
- package/dist/omp-adapter.js +4 -0
- package/dist/omp-adapter.js.map +1 -1
- package/dist/openclaw-adapter.d.ts +3 -1
- package/dist/openclaw-adapter.d.ts.map +1 -1
- package/dist/openclaw-adapter.js +5 -1
- package/dist/openclaw-adapter.js.map +1 -1
- package/dist/opencode-adapter.d.ts +3 -1
- package/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js +7 -3
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/opencode-http-adapter.d.ts +3 -1
- package/dist/opencode-http-adapter.d.ts.map +1 -1
- package/dist/opencode-http-adapter.js +5 -1
- package/dist/opencode-http-adapter.js.map +1 -1
- package/dist/pi-adapter.d.ts.map +1 -1
- package/dist/pi-adapter.js +4 -0
- package/dist/pi-adapter.js.map +1 -1
- package/dist/pi-sdk-adapter.d.ts.map +1 -1
- package/dist/pi-sdk-adapter.js +4 -0
- package/dist/pi-sdk-adapter.js.map +1 -1
- package/dist/provider-translation.d.ts +10 -0
- package/dist/provider-translation.d.ts.map +1 -0
- package/dist/provider-translation.js +2 -0
- package/dist/provider-translation.js.map +1 -0
- package/dist/qwen-adapter.d.ts +3 -1
- package/dist/qwen-adapter.d.ts.map +1 -1
- package/dist/qwen-adapter.js +5 -1
- package/dist/qwen-adapter.js.map +1 -1
- package/dist/session-fs.d.ts +15 -5
- package/dist/session-fs.d.ts.map +1 -1
- package/dist/session-fs.js +249 -0
- package/dist/session-fs.js.map +1 -1
- package/dist/shared/runtime-hooks-virtual.d.ts +3 -0
- package/dist/shared/runtime-hooks-virtual.d.ts.map +1 -0
- package/dist/shared/runtime-hooks-virtual.js +13 -0
- package/dist/shared/runtime-hooks-virtual.js.map +1 -0
- package/dist/translate-for-harness.d.ts +6 -0
- package/dist/translate-for-harness.d.ts.map +1 -0
- package/dist/translate-for-harness.js +36 -0
- package/dist/translate-for-harness.js.map +1 -0
- package/dist/translations/claude-translation.d.ts +4 -0
- package/dist/translations/claude-translation.d.ts.map +1 -0
- package/dist/translations/claude-translation.js +50 -0
- package/dist/translations/claude-translation.js.map +1 -0
- package/dist/translations/codex-translation.d.ts +4 -0
- package/dist/translations/codex-translation.d.ts.map +1 -0
- package/dist/translations/codex-translation.js +32 -0
- package/dist/translations/codex-translation.js.map +1 -0
- package/dist/translations/gemini-translation.d.ts +4 -0
- package/dist/translations/gemini-translation.d.ts.map +1 -0
- package/dist/translations/gemini-translation.js +20 -0
- package/dist/translations/gemini-translation.js.map +1 -0
- package/dist/translations/generic-openai-translation.d.ts +4 -0
- package/dist/translations/generic-openai-translation.d.ts.map +1 -0
- package/dist/translations/generic-openai-translation.js +19 -0
- package/dist/translations/generic-openai-translation.js.map +1 -0
- package/dist/translations/opencode-translation.d.ts +4 -0
- package/dist/translations/opencode-translation.d.ts.map +1 -0
- package/dist/translations/opencode-translation.js +51 -0
- package/dist/translations/opencode-translation.js.map +1 -0
- package/package.json +4 -2
- package/dist/mocks/index.d.ts +0 -60
- package/dist/mocks/index.d.ts.map +0 -1
- package/dist/mocks/index.js +0 -203
- package/dist/mocks/index.js.map +0 -1
- package/dist/mocks/mock-types.d.ts +0 -260
- package/dist/mocks/mock-types.d.ts.map +0 -1
- package/dist/mocks/mock-types.js +0 -12
- package/dist/mocks/mock-types.js.map +0 -1
- package/dist/mocks/programmatic-mocks.d.ts +0 -50
- package/dist/mocks/programmatic-mocks.d.ts.map +0 -1
- package/dist/mocks/programmatic-mocks.js +0 -330
- package/dist/mocks/programmatic-mocks.js.map +0 -1
- package/dist/mocks/remote-mocks.d.ts +0 -52
- package/dist/mocks/remote-mocks.d.ts.map +0 -1
- package/dist/mocks/remote-mocks.js +0 -436
- 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.
|
|
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:
|
|
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: '
|
|
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:
|
|
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:
|
|
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
|
-
|
|
192
|
+
execute(options) {
|
|
136
193
|
this.validateRunOptions(options);
|
|
137
194
|
const runId = this.generateRunId();
|
|
138
195
|
const modelId = this.resolveModel(options);
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
340
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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: '
|
|
389
|
-
command: 'claude
|
|
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:
|
|
394
|
-
{ name: 'CLAUDE_AGENT_API_KEY', description: '
|
|
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://
|
|
397
|
-
loginCommand: 'claude
|
|
398
|
-
verifyCommand: 'claude
|
|
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', '
|
|
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:
|
|
424
|
-
void
|
|
425
|
-
void
|
|
426
|
-
void
|
|
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
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
582
|
-
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
598
|
-
const
|
|
599
|
-
const
|
|
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:
|
|
602
|
-
|
|
603
|
-
|
|
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
|
}
|