@sandagent/runner-cli 0.1.2-beta.1 → 0.1.2-beta.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 +39 -4
- package/dist/__tests__/runner-cli.integration.test.d.ts +6 -0
- package/dist/__tests__/runner-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/runner-cli.integration.test.js +76 -0
- package/dist/__tests__/runner-cli.integration.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +7 -3
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/bundle.mjs +334 -198
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +31 -14
- package/dist/runner.js.map +1 -1
- package/package.json +16 -1
- package/dist/bundle.js +0 -377
package/dist/bundle.mjs
CHANGED
|
@@ -3,6 +3,243 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
|
|
6
|
+
// ../../packages/runner-claude/dist/ai-sdk-stream.js
|
|
7
|
+
import { writeFile } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
var UNKNOWN_TOOL_NAME = "unknown-tool";
|
|
10
|
+
function formatDataStream(data) {
|
|
11
|
+
return `data: ${JSON.stringify(data)}
|
|
12
|
+
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
15
|
+
function generateId() {
|
|
16
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
17
|
+
}
|
|
18
|
+
function convertUsageToAISDK(usage) {
|
|
19
|
+
const inputTokens = usage.input_tokens ?? 0;
|
|
20
|
+
const outputTokens = usage.output_tokens ?? 0;
|
|
21
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
22
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
23
|
+
return {
|
|
24
|
+
inputTokens: {
|
|
25
|
+
total: inputTokens + cacheWrite + cacheRead,
|
|
26
|
+
noCache: inputTokens,
|
|
27
|
+
cacheRead,
|
|
28
|
+
cacheWrite
|
|
29
|
+
},
|
|
30
|
+
outputTokens: { total: outputTokens },
|
|
31
|
+
raw: usage
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function mapFinishReason(subtype, isError) {
|
|
35
|
+
if (isError)
|
|
36
|
+
return "error";
|
|
37
|
+
switch (subtype) {
|
|
38
|
+
case "success":
|
|
39
|
+
return "stop";
|
|
40
|
+
case "error_max_turns":
|
|
41
|
+
return "length";
|
|
42
|
+
case "error_during_execution":
|
|
43
|
+
case "error_max_structured_output_retries":
|
|
44
|
+
return "error";
|
|
45
|
+
case void 0:
|
|
46
|
+
return "stop";
|
|
47
|
+
default:
|
|
48
|
+
return "other";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function extractToolUses(content) {
|
|
52
|
+
if (!Array.isArray(content))
|
|
53
|
+
return [];
|
|
54
|
+
return content.filter((item) => typeof item === "object" && item !== null && "type" in item && item.type === "tool_use").map((item) => ({
|
|
55
|
+
id: item.id || generateId(),
|
|
56
|
+
name: item.name || UNKNOWN_TOOL_NAME,
|
|
57
|
+
input: item.input
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
var AISDKStreamConverter = class {
|
|
61
|
+
systemMessage;
|
|
62
|
+
hasEmittedStart = false;
|
|
63
|
+
accumulatedText = "";
|
|
64
|
+
textPartId;
|
|
65
|
+
streamedTextLength = 0;
|
|
66
|
+
// Track text already emitted via stream_events
|
|
67
|
+
hasReceivedStreamEvents = false;
|
|
68
|
+
sessionId;
|
|
69
|
+
partIdMap = /* @__PURE__ */ new Map();
|
|
70
|
+
/**
|
|
71
|
+
* Get the current session ID from the stream
|
|
72
|
+
*/
|
|
73
|
+
get currentSessionId() {
|
|
74
|
+
if (!this.sessionId) {
|
|
75
|
+
throw new Error("Session ID is not set");
|
|
76
|
+
}
|
|
77
|
+
return this.sessionId;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Helper to emit SSE data
|
|
81
|
+
*/
|
|
82
|
+
emit(data) {
|
|
83
|
+
return formatDataStream(data);
|
|
84
|
+
}
|
|
85
|
+
setPartId(index, partId) {
|
|
86
|
+
const partIdKey = `${this.currentSessionId}-${index}`;
|
|
87
|
+
this.partIdMap.set(partIdKey, partId);
|
|
88
|
+
}
|
|
89
|
+
getPartId(index) {
|
|
90
|
+
const partIdKey = `${this.currentSessionId}-${index}`;
|
|
91
|
+
if (this.partIdMap.has(partIdKey)) {
|
|
92
|
+
return this.partIdMap.get(partIdKey) ?? "";
|
|
93
|
+
}
|
|
94
|
+
throw new Error("Part ID not found");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Helper to emit tool call
|
|
98
|
+
*/
|
|
99
|
+
*emitToolCall(message) {
|
|
100
|
+
const event = message.event;
|
|
101
|
+
if (event.type === "content_block_start" && event.content_block.type === "tool_use") {
|
|
102
|
+
const toolCallId = event.content_block.id;
|
|
103
|
+
this.setPartId(event.index, toolCallId);
|
|
104
|
+
yield this.emit({
|
|
105
|
+
type: "tool-input-start",
|
|
106
|
+
toolCallId,
|
|
107
|
+
toolName: event.content_block.name,
|
|
108
|
+
dynamic: true,
|
|
109
|
+
providerExecuted: true
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta") {
|
|
113
|
+
yield this.emit({
|
|
114
|
+
type: "tool-input-delta",
|
|
115
|
+
toolCallId: this.getPartId(event.index),
|
|
116
|
+
inputTextDelta: event.delta.partial_json
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
*emitTextBlockEvent(event) {
|
|
121
|
+
if (event.type === "content_block_start" && event.content_block.type === "text") {
|
|
122
|
+
const partId = `text_${generateId()}`;
|
|
123
|
+
this.setPartId(event.index, partId);
|
|
124
|
+
yield this.emit({
|
|
125
|
+
type: "text-start",
|
|
126
|
+
id: partId
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (event.type === "content_block_delta" && event.delta?.type === "text_delta") {
|
|
130
|
+
yield this.emit({
|
|
131
|
+
type: "text-delta",
|
|
132
|
+
id: this.getPartId(event.index),
|
|
133
|
+
delta: event.delta.text
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (event.type === "content_block_stop") {
|
|
137
|
+
const partId = this.getPartId(event.index);
|
|
138
|
+
if (partId.startsWith("text_")) {
|
|
139
|
+
yield this.emit({ type: "text-end", id: partId });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Stream SDK messages and convert to AI SDK UI Data Stream format
|
|
145
|
+
*/
|
|
146
|
+
async *stream(messageIterator, options) {
|
|
147
|
+
const debugMessages = [];
|
|
148
|
+
const debugFile = `ai-sdk-stream-debug-${Date.now()}.json`;
|
|
149
|
+
try {
|
|
150
|
+
for await (const message of messageIterator) {
|
|
151
|
+
debugMessages.push(message);
|
|
152
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
153
|
+
this.systemMessage = message;
|
|
154
|
+
this.sessionId = this.systemMessage.session_id;
|
|
155
|
+
}
|
|
156
|
+
if (message.type === "stream_event") {
|
|
157
|
+
const streamEvent = message;
|
|
158
|
+
const event = streamEvent.event;
|
|
159
|
+
if (event.type === "message_start" && !this.hasEmittedStart) {
|
|
160
|
+
this.hasEmittedStart = true;
|
|
161
|
+
yield this.emit({ type: "start", messageId: event.message.id });
|
|
162
|
+
yield this.emit({
|
|
163
|
+
type: "message-metadata",
|
|
164
|
+
messageMetadata: {
|
|
165
|
+
tools: this.systemMessage?.tools,
|
|
166
|
+
model: this.systemMessage?.model,
|
|
167
|
+
sessionId: this.systemMessage?.session_id,
|
|
168
|
+
agents: this.systemMessage?.agents,
|
|
169
|
+
skills: this.systemMessage?.skills
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
yield* this.emitTextBlockEvent(event);
|
|
174
|
+
yield* this.emitToolCall(streamEvent);
|
|
175
|
+
}
|
|
176
|
+
if (message.type === "assistant") {
|
|
177
|
+
const assistantMsg = message;
|
|
178
|
+
const content = assistantMsg.message?.content;
|
|
179
|
+
if (!content)
|
|
180
|
+
continue;
|
|
181
|
+
const tools = extractToolUses(content);
|
|
182
|
+
for (const tool of tools) {
|
|
183
|
+
yield this.emit({
|
|
184
|
+
type: "tool-input-available",
|
|
185
|
+
toolCallId: tool.id,
|
|
186
|
+
toolName: tool.name,
|
|
187
|
+
input: tool.input,
|
|
188
|
+
dynamic: true,
|
|
189
|
+
providerExecuted: true
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (message.type === "user") {
|
|
194
|
+
const userMsg = message;
|
|
195
|
+
const content = userMsg.message?.content;
|
|
196
|
+
for (const part of content) {
|
|
197
|
+
if (part.type === "tool_result") {
|
|
198
|
+
yield this.emit({
|
|
199
|
+
type: "tool-output-available",
|
|
200
|
+
toolCallId: part.tool_use_id,
|
|
201
|
+
output: message.tool_use_result || part.content,
|
|
202
|
+
dynamic: true,
|
|
203
|
+
providerExecuted: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (message.type === "result") {
|
|
209
|
+
const resultMsg = message;
|
|
210
|
+
const finishTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
211
|
+
console.error(`[AISDKStream] Processing result message at ${finishTime}`);
|
|
212
|
+
const finishEvent = this.emit({
|
|
213
|
+
type: "finish",
|
|
214
|
+
finishReason: mapFinishReason(resultMsg.subtype, resultMsg.is_error),
|
|
215
|
+
messageMetadata: {
|
|
216
|
+
usage: convertUsageToAISDK(resultMsg.usage ?? {})
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
console.error(`[AISDKStream] Emitting finish event at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
220
|
+
yield finishEvent;
|
|
221
|
+
console.error(`[AISDKStream] Emitted [DONE] at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
debugMessages.push(error);
|
|
226
|
+
} finally {
|
|
227
|
+
if (debugMessages.length > 0) {
|
|
228
|
+
writeFile(join(process.cwd(), debugFile), JSON.stringify(debugMessages, null, 2), "utf-8").catch((writeError) => {
|
|
229
|
+
console.error(`[AISDKStream] Failed to write debug file:`, writeError);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
options?.onCleanup?.();
|
|
233
|
+
yield `data: [DONE]
|
|
234
|
+
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
function streamSDKMessagesToAISDKUI(messageIterator, options) {
|
|
240
|
+
return new AISDKStreamConverter().stream(messageIterator, options);
|
|
241
|
+
}
|
|
242
|
+
|
|
6
243
|
// ../../packages/runner-claude/dist/claude-runner.js
|
|
7
244
|
function createCanUseToolCallback(claudeOptions) {
|
|
8
245
|
return async (toolName, input, options) => {
|
|
@@ -43,7 +280,7 @@ function createCanUseToolCallback(claudeOptions) {
|
|
|
43
280
|
}
|
|
44
281
|
};
|
|
45
282
|
}
|
|
46
|
-
} catch
|
|
283
|
+
} catch {
|
|
47
284
|
}
|
|
48
285
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
49
286
|
}
|
|
@@ -78,22 +315,19 @@ var OPTIONAL_MODULES = {
|
|
|
78
315
|
};
|
|
79
316
|
function createClaudeRunner(options) {
|
|
80
317
|
return {
|
|
81
|
-
async *run(userInput
|
|
82
|
-
if (signal?.aborted) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
318
|
+
async *run(userInput) {
|
|
85
319
|
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AWS_BEARER_TOKEN_BEDROCK;
|
|
86
320
|
if (!apiKey) {
|
|
87
321
|
console.error("[SandAgent] Warning: ANTHROPIC_API_KEY or AWS_BEARER_TOKEN_BEDROCK not set. Using mock response.\nTo use the real Claude Agent SDK:\n1. Set ANTHROPIC_API_KEY or AWS_BEARER_TOKEN_BEDROCK environment variable\n2. Install the SDK: npm install @anthropic-ai/claude-agent-sdk");
|
|
88
|
-
yield* runMockAgent(options, userInput, signal);
|
|
322
|
+
yield* runMockAgent(options, userInput, options.abortController?.signal);
|
|
89
323
|
return;
|
|
90
324
|
}
|
|
91
325
|
const sdk = await loadClaudeAgentSDK();
|
|
92
326
|
if (sdk) {
|
|
93
|
-
yield* runWithClaudeAgentSDK(sdk, options, userInput
|
|
327
|
+
yield* runWithClaudeAgentSDK(sdk, options, userInput);
|
|
94
328
|
} else {
|
|
95
329
|
console.error("[SandAgent] Warning: @anthropic-ai/claude-agent-sdk not installed. Using mock response.\nInstall the SDK: npm install @anthropic-ai/claude-agent-sdk");
|
|
96
|
-
yield* runMockAgent(options, userInput, signal);
|
|
330
|
+
yield* runMockAgent(options, userInput, options.abortController?.signal);
|
|
97
331
|
}
|
|
98
332
|
}
|
|
99
333
|
};
|
|
@@ -110,21 +344,21 @@ async function loadClaudeAgentSDK() {
|
|
|
110
344
|
return null;
|
|
111
345
|
}
|
|
112
346
|
}
|
|
113
|
-
async function* runWithClaudeAgentSDK(sdk, options, userInput
|
|
347
|
+
async function* runWithClaudeAgentSDK(sdk, options, userInput) {
|
|
114
348
|
const outputFormat = options.outputFormat || "stream-json";
|
|
115
349
|
switch (outputFormat) {
|
|
116
350
|
case "text":
|
|
117
|
-
yield* runWithTextOutput(sdk, options, userInput
|
|
351
|
+
yield* runWithTextOutput(sdk, options, userInput);
|
|
118
352
|
break;
|
|
119
353
|
case "json":
|
|
120
|
-
yield* runWithJSONOutput(sdk, options, userInput
|
|
354
|
+
yield* runWithJSONOutput(sdk, options, userInput);
|
|
121
355
|
break;
|
|
122
356
|
case "stream-json":
|
|
123
|
-
yield* runWithStreamJSONOutput(sdk, options, userInput
|
|
357
|
+
yield* runWithStreamJSONOutput(sdk, options, userInput);
|
|
124
358
|
break;
|
|
125
359
|
// case "stream":
|
|
126
360
|
default:
|
|
127
|
-
yield* runWithAISDKUIOutput(sdk, options, userInput
|
|
361
|
+
yield* runWithAISDKUIOutput(sdk, options, userInput);
|
|
128
362
|
break;
|
|
129
363
|
}
|
|
130
364
|
}
|
|
@@ -133,7 +367,12 @@ function createSDKOptions(options) {
|
|
|
133
367
|
model: options.model,
|
|
134
368
|
systemPrompt: options.systemPrompt,
|
|
135
369
|
maxTurns: options.maxTurns,
|
|
136
|
-
allowedTools: [
|
|
370
|
+
allowedTools: [
|
|
371
|
+
...options.allowedTools ?? [],
|
|
372
|
+
"Skill",
|
|
373
|
+
"WebSearch",
|
|
374
|
+
"WebFetch"
|
|
375
|
+
],
|
|
137
376
|
cwd: options.cwd,
|
|
138
377
|
env: options.env,
|
|
139
378
|
resume: options.resume,
|
|
@@ -141,13 +380,16 @@ function createSDKOptions(options) {
|
|
|
141
380
|
canUseTool: createCanUseToolCallback(options),
|
|
142
381
|
// Bypass all permission checks for automated execution
|
|
143
382
|
permissionMode: "bypassPermissions",
|
|
144
|
-
allowDangerouslySkipPermissions: true
|
|
383
|
+
allowDangerouslySkipPermissions: true,
|
|
384
|
+
// Enable partial messages for streaming
|
|
385
|
+
includePartialMessages: options.includePartialMessages
|
|
145
386
|
};
|
|
146
387
|
}
|
|
147
388
|
function setupAbortHandler(queryIterator, signal) {
|
|
148
389
|
const abortHandler = async () => {
|
|
149
|
-
console.error("[ClaudeRunner]
|
|
390
|
+
console.error("[ClaudeRunner] Abort signal received, will call query.interrupt()...");
|
|
150
391
|
await queryIterator.interrupt();
|
|
392
|
+
console.error("[ClaudeRunner] query.interrupt() completed");
|
|
151
393
|
};
|
|
152
394
|
if (signal) {
|
|
153
395
|
console.error("[ClaudeRunner] Signal provided, adding abort listener");
|
|
@@ -163,7 +405,7 @@ function setupAbortHandler(queryIterator, signal) {
|
|
|
163
405
|
async function* runWithTextOutput(sdk, options, userInput, signal) {
|
|
164
406
|
const sdkOptions = createSDKOptions(options);
|
|
165
407
|
const queryIterator = sdk.query({ prompt: userInput, options: sdkOptions });
|
|
166
|
-
const abortHandler = setupAbortHandler(queryIterator, signal);
|
|
408
|
+
const abortHandler = setupAbortHandler(queryIterator, options.abortController?.signal);
|
|
167
409
|
try {
|
|
168
410
|
let resultText = "";
|
|
169
411
|
for await (const message of queryIterator) {
|
|
@@ -215,170 +457,14 @@ async function* runWithStreamJSONOutput(sdk, options, userInput, signal) {
|
|
|
215
457
|
}
|
|
216
458
|
}
|
|
217
459
|
}
|
|
218
|
-
async function* runWithAISDKUIOutput(sdk, options, userInput
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
460
|
+
async function* runWithAISDKUIOutput(sdk, options, userInput) {
|
|
461
|
+
const sdkOptions = createSDKOptions({
|
|
462
|
+
...options,
|
|
463
|
+
includePartialMessages: true
|
|
464
|
+
});
|
|
223
465
|
const queryIterator = sdk.query({ prompt: userInput, options: sdkOptions });
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for await (const message of queryIterator) {
|
|
227
|
-
if (message.type === "system" && message.subtype === "init") {
|
|
228
|
-
systemMessage = message;
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (message.type === "assistant" && !messageId && systemMessage) {
|
|
232
|
-
messageId = message.message.id;
|
|
233
|
-
yield formatDataStream({
|
|
234
|
-
type: "start",
|
|
235
|
-
messageId
|
|
236
|
-
});
|
|
237
|
-
yield formatDataStream({
|
|
238
|
-
type: "message-metadata",
|
|
239
|
-
messageMetadata: {
|
|
240
|
-
tools: systemMessage.tools,
|
|
241
|
-
model: systemMessage.model,
|
|
242
|
-
sessionId: systemMessage.session_id
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
const chunks = convertSDKMessageToAISDKUI(message);
|
|
247
|
-
for (const chunk of chunks) {
|
|
248
|
-
yield chunk;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
253
|
-
console.error("[ClaudeRunner] Error:", errorMessage);
|
|
254
|
-
yield formatDataStream({ type: "error", errorText: errorMessage });
|
|
255
|
-
yield formatDataStream({
|
|
256
|
-
type: "finish",
|
|
257
|
-
finishReason: "error",
|
|
258
|
-
usage: {
|
|
259
|
-
promptTokens: usage.inputTokens,
|
|
260
|
-
completionTokens: usage.outputTokens
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
} finally {
|
|
264
|
-
if (signal) {
|
|
265
|
-
signal.removeEventListener("abort", abortHandler);
|
|
266
|
-
}
|
|
267
|
-
yield `data: [DONE]
|
|
268
|
-
|
|
269
|
-
`;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function convertSDKMessageToAISDKUI(message) {
|
|
273
|
-
const chunks = [];
|
|
274
|
-
switch (message.type) {
|
|
275
|
-
case "assistant": {
|
|
276
|
-
const assistantMsg = message;
|
|
277
|
-
if (assistantMsg.error) {
|
|
278
|
-
const errorDetail = message.message.content.map((c) => c.text).join("\n");
|
|
279
|
-
chunks.push(formatDataStream({
|
|
280
|
-
type: "error",
|
|
281
|
-
errorText: `${assistantMsg.error}: ${errorDetail}`
|
|
282
|
-
}));
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
if (assistantMsg.message) {
|
|
286
|
-
if (typeof assistantMsg.message === "string") {
|
|
287
|
-
const textId = generateId();
|
|
288
|
-
chunks.push(formatDataStream({ type: "text-start", id: textId }));
|
|
289
|
-
chunks.push(formatDataStream({
|
|
290
|
-
type: "text-delta",
|
|
291
|
-
id: textId,
|
|
292
|
-
delta: assistantMsg.message
|
|
293
|
-
}));
|
|
294
|
-
chunks.push(formatDataStream({ type: "text-end", id: textId }));
|
|
295
|
-
} else if (assistantMsg.message.content && Array.isArray(assistantMsg.message.content)) {
|
|
296
|
-
for (const block of assistantMsg.message.content) {
|
|
297
|
-
if (block.type === "text" && block.text) {
|
|
298
|
-
const textId = generateId();
|
|
299
|
-
chunks.push(formatDataStream({ type: "text-start", id: textId }));
|
|
300
|
-
chunks.push(formatDataStream({
|
|
301
|
-
type: "text-delta",
|
|
302
|
-
id: textId,
|
|
303
|
-
delta: block.text
|
|
304
|
-
}));
|
|
305
|
-
chunks.push(formatDataStream({ type: "text-end", id: textId }));
|
|
306
|
-
} else if (block.type === "tool_use") {
|
|
307
|
-
const toolCallId = block.id || generateId();
|
|
308
|
-
chunks.push(formatDataStream({
|
|
309
|
-
type: "tool-input-start",
|
|
310
|
-
toolCallId,
|
|
311
|
-
toolName: block.name,
|
|
312
|
-
dynamic: true
|
|
313
|
-
}));
|
|
314
|
-
if (block.input) {
|
|
315
|
-
chunks.push(formatDataStream({
|
|
316
|
-
type: "tool-input-available",
|
|
317
|
-
toolCallId,
|
|
318
|
-
toolName: block.name,
|
|
319
|
-
dynamic: true,
|
|
320
|
-
input: block.input
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
case "result": {
|
|
330
|
-
const resultMsg = message;
|
|
331
|
-
chunks.push(formatDataStream({
|
|
332
|
-
type: "finish",
|
|
333
|
-
finishReason: resultMsg.is_error ? "error" : "stop",
|
|
334
|
-
messageMetadata: {
|
|
335
|
-
usage: resultMsg.usage,
|
|
336
|
-
duration_ms: resultMsg.duration_ms,
|
|
337
|
-
num_turns: resultMsg.num_turns,
|
|
338
|
-
total_cost_usd: resultMsg.total_cost_usd
|
|
339
|
-
}
|
|
340
|
-
}));
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
case "user": {
|
|
344
|
-
const usrMsg = message;
|
|
345
|
-
if (usrMsg.isSynthetic) {
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
const contentArray = usrMsg.message.content;
|
|
349
|
-
if (usrMsg.tool_use_result || Array.isArray(contentArray) && contentArray.some((c) => c.type === "tool_result")) {
|
|
350
|
-
for (const tool of contentArray) {
|
|
351
|
-
if (tool.is_error) {
|
|
352
|
-
chunks.push(formatDataStream({
|
|
353
|
-
type: "tool-output-error",
|
|
354
|
-
toolCallId: tool.tool_use_id,
|
|
355
|
-
errorText: tool.content,
|
|
356
|
-
dynamic: true
|
|
357
|
-
}));
|
|
358
|
-
} else {
|
|
359
|
-
chunks.push(formatDataStream({
|
|
360
|
-
type: "tool-output-available",
|
|
361
|
-
toolCallId: tool.tool_use_id,
|
|
362
|
-
output: usrMsg.tool_use_result || tool.content,
|
|
363
|
-
dynamic: true
|
|
364
|
-
}));
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
default:
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
return chunks;
|
|
374
|
-
}
|
|
375
|
-
function formatDataStream(data) {
|
|
376
|
-
return `data: ${JSON.stringify(data)}
|
|
377
|
-
|
|
378
|
-
`;
|
|
379
|
-
}
|
|
380
|
-
function generateId() {
|
|
381
|
-
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
466
|
+
setupAbortHandler(queryIterator, options.abortController?.signal);
|
|
467
|
+
yield* streamSDKMessagesToAISDKUI(queryIterator);
|
|
382
468
|
}
|
|
383
469
|
async function* runMockAgent(options, userInput, signal) {
|
|
384
470
|
if (signal?.aborted) {
|
|
@@ -386,6 +472,11 @@ async function* runMockAgent(options, userInput, signal) {
|
|
|
386
472
|
return;
|
|
387
473
|
}
|
|
388
474
|
try {
|
|
475
|
+
const messageId = generateId();
|
|
476
|
+
yield formatDataStream({
|
|
477
|
+
type: "start",
|
|
478
|
+
messageId
|
|
479
|
+
});
|
|
389
480
|
const response = `I received your request: "${userInput}"
|
|
390
481
|
|
|
391
482
|
Model: ${options.model}
|
|
@@ -413,7 +504,13 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
|
|
|
413
504
|
yield formatDataStream({ type: "text-end", id: textId });
|
|
414
505
|
yield formatDataStream({
|
|
415
506
|
type: "finish",
|
|
416
|
-
finishReason: "
|
|
507
|
+
finishReason: mapFinishReason("success"),
|
|
508
|
+
usage: convertUsageToAISDK({
|
|
509
|
+
input_tokens: 0,
|
|
510
|
+
output_tokens: 0,
|
|
511
|
+
cache_creation_input_tokens: 0,
|
|
512
|
+
cache_read_input_tokens: 0
|
|
513
|
+
})
|
|
417
514
|
});
|
|
418
515
|
yield `data: [DONE]
|
|
419
516
|
|
|
@@ -424,7 +521,13 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
|
|
|
424
521
|
yield formatDataStream({ type: "error", errorText: errorMessage });
|
|
425
522
|
yield formatDataStream({
|
|
426
523
|
type: "finish",
|
|
427
|
-
finishReason: "
|
|
524
|
+
finishReason: mapFinishReason("error_during_execution", true),
|
|
525
|
+
usage: convertUsageToAISDK({
|
|
526
|
+
input_tokens: 0,
|
|
527
|
+
output_tokens: 0,
|
|
528
|
+
cache_creation_input_tokens: 0,
|
|
529
|
+
cache_read_input_tokens: 0
|
|
530
|
+
})
|
|
428
531
|
});
|
|
429
532
|
yield `data: [DONE]
|
|
430
533
|
|
|
@@ -435,29 +538,47 @@ Documentation: https://platform.claude.com/docs/en/agent-sdk/typescript-v2-previ
|
|
|
435
538
|
// src/runner.ts
|
|
436
539
|
async function runAgent(options) {
|
|
437
540
|
const abortController = new AbortController();
|
|
438
|
-
const signalHandler = () => {
|
|
541
|
+
const signalHandler = async () => {
|
|
439
542
|
console.error("[Runner] Received termination signal, stopping...");
|
|
440
543
|
abortController.abort();
|
|
441
|
-
console.error(
|
|
544
|
+
console.error(
|
|
545
|
+
"[Runner] AbortController.abort() completed (listeners triggered)"
|
|
546
|
+
);
|
|
442
547
|
};
|
|
443
548
|
process.on("SIGTERM", signalHandler);
|
|
444
549
|
process.on("SIGINT", signalHandler);
|
|
445
550
|
console.error("[Runner] Signal handlers registered");
|
|
446
551
|
try {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
552
|
+
let runner;
|
|
553
|
+
switch (options.runner) {
|
|
554
|
+
case "claude": {
|
|
555
|
+
const runnerOptions = {
|
|
556
|
+
model: options.model,
|
|
557
|
+
systemPrompt: options.systemPrompt,
|
|
558
|
+
maxTurns: options.maxTurns,
|
|
559
|
+
allowedTools: options.allowedTools,
|
|
560
|
+
resume: options.resume,
|
|
561
|
+
approvalDir: options.approvalDir,
|
|
562
|
+
outputFormat: options.outputFormat,
|
|
563
|
+
abortController
|
|
564
|
+
};
|
|
565
|
+
runner = createClaudeRunner(runnerOptions);
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "codex":
|
|
569
|
+
throw new Error(
|
|
570
|
+
"Codex runner not yet implemented. Use --runner=claude for now."
|
|
571
|
+
);
|
|
572
|
+
case "copilot":
|
|
573
|
+
throw new Error(
|
|
574
|
+
"Copilot runner not yet implemented. Use --runner=claude for now."
|
|
575
|
+
);
|
|
576
|
+
default:
|
|
577
|
+
throw new Error(
|
|
578
|
+
`Unknown runner: ${options.runner}. Supported runners: claude, codex, copilot`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
for await (const chunk of runner.run(options.userInput)) {
|
|
461
582
|
process.stdout.write(chunk);
|
|
462
583
|
}
|
|
463
584
|
} finally {
|
|
@@ -470,6 +591,11 @@ async function runAgent(options) {
|
|
|
470
591
|
function parseCliArgs() {
|
|
471
592
|
const { values, positionals } = parseArgs({
|
|
472
593
|
options: {
|
|
594
|
+
runner: {
|
|
595
|
+
type: "string",
|
|
596
|
+
short: "r",
|
|
597
|
+
default: "claude"
|
|
598
|
+
},
|
|
473
599
|
model: {
|
|
474
600
|
type: "string",
|
|
475
601
|
short: "m",
|
|
@@ -532,6 +658,13 @@ function parseCliArgs() {
|
|
|
532
658
|
console.error('Usage: sandagent run [options] -- "<user input>"');
|
|
533
659
|
process.exit(1);
|
|
534
660
|
}
|
|
661
|
+
const runner = values.runner;
|
|
662
|
+
if (!["claude", "codex", "copilot"].includes(runner)) {
|
|
663
|
+
console.error(
|
|
664
|
+
'Error: --runner must be one of: "claude", "codex", "copilot"'
|
|
665
|
+
);
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
535
668
|
const outputFormat = values["output-format"];
|
|
536
669
|
if (outputFormat && !["text", "json", "stream-json", "stream"].includes(outputFormat)) {
|
|
537
670
|
console.error(
|
|
@@ -540,6 +673,7 @@ function parseCliArgs() {
|
|
|
540
673
|
process.exit(1);
|
|
541
674
|
}
|
|
542
675
|
return {
|
|
676
|
+
runner,
|
|
543
677
|
model: values.model,
|
|
544
678
|
cwd: values.cwd,
|
|
545
679
|
systemPrompt: values["system-prompt"],
|
|
@@ -562,6 +696,7 @@ Usage:
|
|
|
562
696
|
sandagent run [options] -- "<user input>"
|
|
563
697
|
|
|
564
698
|
Options:
|
|
699
|
+
-r, --runner <runner> Runner to use: claude, codex, copilot (default: claude)
|
|
565
700
|
-m, --model <model> Model to use (default: claude-sonnet-4-20250514)
|
|
566
701
|
-c, --cwd <path> Working directory (default: current directory)
|
|
567
702
|
-s, --system-prompt <prompt> Custom system prompt
|
|
@@ -592,6 +727,7 @@ async function main() {
|
|
|
592
727
|
const args = parseCliArgs();
|
|
593
728
|
process.chdir(args.cwd);
|
|
594
729
|
await runAgent({
|
|
730
|
+
runner: args.runner,
|
|
595
731
|
model: args.model,
|
|
596
732
|
userInput: args.userInput,
|
|
597
733
|
systemPrompt: args.systemPrompt,
|