claude-code-rust 0.6.0 → 0.7.1

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.
@@ -1,24 +1,26 @@
1
- import { randomUUID } from "node:crypto";
2
- import { spawn as spawnChild } from "node:child_process";
3
- import fs from "node:fs";
4
1
  import { createRequire } from "node:module";
5
2
  import readline from "node:readline";
6
3
  import { pathToFileURL } from "node:url";
7
- import { getSessionMessages, listSessions, query, } from "@anthropic-ai/claude-agent-sdk";
8
- import { parseCommandEnvelope, toPermissionMode, buildModeState } from "./bridge/commands.js";
9
- import { asRecordOrNull } from "./bridge/shared.js";
10
- import { looksLikeAuthRequired } from "./bridge/auth.js";
11
- import { TOOL_RESULT_TYPES, buildToolResultFields, createToolCall, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
12
- import { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.js";
13
- import { buildUsageUpdateFromResult, buildUsageUpdateFromResultForSession } from "./bridge/usage.js";
14
- import { formatPermissionUpdates, permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
15
- import { mapSdkSessions, mapSessionMessagesToUpdates, } from "./bridge/history.js";
16
- export { CACHE_SPLIT_POLICY, buildToolResultFields, buildUsageUpdateFromResult, createToolCall, mapSessionMessagesToUpdates, mapSdkSessions, looksLikeAuthRequired, normalizeToolKind, normalizeToolResultText, parseCommandEnvelope, permissionOptionsFromSuggestions, permissionResultFromOutcome, previewKilobyteLabel, unwrapToolUseResult, };
17
- const sessions = new Map();
4
+ import { getSessionMessages, listSessions, } from "@anthropic-ai/claude-agent-sdk";
5
+ import { parseCommandEnvelope, toPermissionMode } from "./bridge/commands.js";
6
+ import { writeEvent, failConnection, slashError, emitSessionUpdate, emitSessionsList, } from "./bridge/events.js";
7
+ import { textFromPrompt } from "./bridge/message_handlers.js";
8
+ import { sessions, sessionById, createSession, closeAllSessions, handlePermissionResponse, } from "./bridge/session_lifecycle.js";
9
+ import { mapSessionMessagesToUpdates } from "./bridge/history.js";
10
+ // Re-exports: all symbols that tests and external consumers import from bridge.js.
11
+ export { AsyncQueue, logPermissionDebug } from "./bridge/shared.js";
12
+ export { asRecordOrNull } from "./bridge/shared.js";
13
+ export { CACHE_SPLIT_POLICY, previewKilobyteLabel } from "./bridge/cache_policy.js";
14
+ export { buildToolResultFields, createToolCall, normalizeToolKind, normalizeToolResultText, unwrapToolUseResult, } from "./bridge/tooling.js";
15
+ export { looksLikeAuthRequired } from "./bridge/auth.js";
16
+ export { parseCommandEnvelope } from "./bridge/commands.js";
17
+ export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "./bridge/permissions.js";
18
+ export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
19
+ export { mapAvailableAgents } from "./bridge/agents.js";
20
+ export { buildQueryOptions } from "./bridge/session_lifecycle.js";
21
+ export { parseFastModeState, parseRateLimitStatus, buildRateLimitUpdate, } from "./bridge/state_parsing.js";
18
22
  const EXPECTED_AGENT_SDK_VERSION = "0.2.63";
19
- const SESSION_LIST_LIMIT = 50;
20
23
  const require = createRequire(import.meta.url);
21
- const permissionDebugEnabled = process.env.CLAUDE_RS_SDK_PERMISSION_DEBUG === "1" || process.env.CLAUDE_RS_SDK_DEBUG === "1";
22
24
  export function resolveInstalledAgentSdkVersion() {
23
25
  try {
24
26
  const pkg = require("@anthropic-ai/claude-agent-sdk/package.json");
@@ -40,1308 +42,6 @@ export function agentSdkVersionCompatibilityError() {
40
42
  return (`Unsupported @anthropic-ai/claude-agent-sdk version: expected ${EXPECTED_AGENT_SDK_VERSION}, ` +
41
43
  `found ${installed}.`);
42
44
  }
43
- function logPermissionDebug(message) {
44
- if (!permissionDebugEnabled) {
45
- return;
46
- }
47
- console.error(`[perm debug] ${message}`);
48
- }
49
- class AsyncQueue {
50
- items = [];
51
- waiters = [];
52
- closed = false;
53
- enqueue(item) {
54
- if (this.closed) {
55
- return;
56
- }
57
- const waiter = this.waiters.shift();
58
- if (waiter) {
59
- waiter({ value: item, done: false });
60
- return;
61
- }
62
- this.items.push(item);
63
- }
64
- close() {
65
- if (this.closed) {
66
- return;
67
- }
68
- this.closed = true;
69
- while (this.waiters.length > 0) {
70
- const waiter = this.waiters.shift();
71
- waiter?.({ value: undefined, done: true });
72
- }
73
- }
74
- [Symbol.asyncIterator]() {
75
- return {
76
- next: async () => {
77
- if (this.items.length > 0) {
78
- const value = this.items.shift();
79
- return { value: value, done: false };
80
- }
81
- if (this.closed) {
82
- return { value: undefined, done: true };
83
- }
84
- return await new Promise((resolve) => {
85
- this.waiters.push(resolve);
86
- });
87
- },
88
- };
89
- }
90
- }
91
- function writeEvent(event, requestId) {
92
- const envelope = {
93
- ...(requestId ? { request_id: requestId } : {}),
94
- ...event,
95
- };
96
- process.stdout.write(`${JSON.stringify(envelope)}\n`);
97
- }
98
- function failConnection(message, requestId) {
99
- writeEvent({ event: "connection_failed", message }, requestId);
100
- }
101
- function slashError(sessionId, message, requestId) {
102
- writeEvent({ event: "slash_error", session_id: sessionId, message }, requestId);
103
- }
104
- function emitSessionUpdate(sessionId, update) {
105
- writeEvent({ event: "session_update", session_id: sessionId, update });
106
- }
107
- function emitConnectEvent(session) {
108
- const historyUpdates = session.resumeUpdates;
109
- const connectEvent = session.connectEvent === "session_replaced"
110
- ? {
111
- event: "session_replaced",
112
- session_id: session.sessionId,
113
- cwd: session.cwd,
114
- model_name: session.model,
115
- mode: buildModeState(session.mode),
116
- ...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
117
- }
118
- : {
119
- event: "connected",
120
- session_id: session.sessionId,
121
- cwd: session.cwd,
122
- model_name: session.model,
123
- mode: buildModeState(session.mode),
124
- ...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
125
- };
126
- writeEvent(connectEvent, session.connectRequestId);
127
- session.connectRequestId = undefined;
128
- session.connected = true;
129
- session.authHintSent = false;
130
- session.resumeUpdates = undefined;
131
- const staleSessions = session.sessionsToCloseAfterConnect;
132
- session.sessionsToCloseAfterConnect = undefined;
133
- if (!staleSessions || staleSessions.length === 0) {
134
- refreshSessionsList();
135
- return;
136
- }
137
- void (async () => {
138
- for (const stale of staleSessions) {
139
- if (stale === session) {
140
- continue;
141
- }
142
- if (sessions.get(stale.sessionId) === stale) {
143
- sessions.delete(stale.sessionId);
144
- }
145
- await closeSession(stale);
146
- }
147
- refreshSessionsList();
148
- })();
149
- }
150
- async function emitSessionsList(requestId) {
151
- try {
152
- const sdkSessions = await listSessions({ limit: SESSION_LIST_LIMIT });
153
- writeEvent({ event: "sessions_listed", sessions: mapSdkSessions(sdkSessions, SESSION_LIST_LIMIT) }, requestId);
154
- }
155
- catch (error) {
156
- const message = error instanceof Error ? error.message : String(error);
157
- console.error(`[sdk warn] listSessions failed: ${message}`);
158
- writeEvent({ event: "sessions_listed", sessions: [] }, requestId);
159
- }
160
- }
161
- function refreshSessionsList() {
162
- void emitSessionsList().catch(() => {
163
- // Defensive no-op.
164
- });
165
- }
166
- function textFromPrompt(command) {
167
- const chunks = command.chunks ?? [];
168
- return chunks
169
- .map((chunk) => {
170
- if (chunk.kind !== "text") {
171
- return "";
172
- }
173
- return typeof chunk.value === "string" ? chunk.value : "";
174
- })
175
- .filter((part) => part.length > 0)
176
- .join("");
177
- }
178
- function sessionById(sessionId) {
179
- return sessions.get(sessionId) ?? null;
180
- }
181
- function updateSessionId(session, newSessionId) {
182
- if (session.sessionId === newSessionId) {
183
- return;
184
- }
185
- sessions.delete(session.sessionId);
186
- session.sessionId = newSessionId;
187
- sessions.set(newSessionId, session);
188
- }
189
- function emitToolCall(session, toolUseId, name, input) {
190
- const toolCall = createToolCall(toolUseId, name, input);
191
- const status = "in_progress";
192
- toolCall.status = status;
193
- const existing = session.toolCalls.get(toolUseId);
194
- if (!existing) {
195
- session.toolCalls.set(toolUseId, toolCall);
196
- emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
197
- return;
198
- }
199
- const fields = {
200
- title: toolCall.title,
201
- kind: toolCall.kind,
202
- status,
203
- raw_input: toolCall.raw_input,
204
- locations: toolCall.locations,
205
- meta: toolCall.meta,
206
- };
207
- if (toolCall.content.length > 0) {
208
- fields.content = toolCall.content;
209
- }
210
- emitSessionUpdate(session.sessionId, {
211
- type: "tool_call_update",
212
- tool_call_update: { tool_call_id: toolUseId, fields },
213
- });
214
- existing.title = toolCall.title;
215
- existing.kind = toolCall.kind;
216
- existing.status = status;
217
- existing.raw_input = toolCall.raw_input;
218
- existing.locations = toolCall.locations;
219
- existing.meta = toolCall.meta;
220
- if (toolCall.content.length > 0) {
221
- existing.content = toolCall.content;
222
- }
223
- }
224
- function ensureToolCallVisible(session, toolUseId, toolName, input) {
225
- const existing = session.toolCalls.get(toolUseId);
226
- if (existing) {
227
- return existing;
228
- }
229
- const toolCall = createToolCall(toolUseId, toolName, input);
230
- session.toolCalls.set(toolUseId, toolCall);
231
- emitSessionUpdate(session.sessionId, { type: "tool_call", tool_call: toolCall });
232
- return toolCall;
233
- }
234
- function emitPlanIfTodoWrite(session, name, input) {
235
- if (name !== "TodoWrite" || !Array.isArray(input.todos)) {
236
- return;
237
- }
238
- const entries = input.todos
239
- .map((todo) => {
240
- if (!todo || typeof todo !== "object") {
241
- return null;
242
- }
243
- const todoObj = todo;
244
- const content = typeof todoObj.content === "string" ? todoObj.content : "";
245
- const status = typeof todoObj.status === "string" ? todoObj.status : "pending";
246
- if (!content) {
247
- return null;
248
- }
249
- return { content, status, active_form: status };
250
- })
251
- .filter((entry) => entry !== null);
252
- if (entries.length > 0) {
253
- emitSessionUpdate(session.sessionId, { type: "plan", entries });
254
- }
255
- }
256
- function emitToolResultUpdate(session, toolUseId, isError, rawContent) {
257
- const base = session.toolCalls.get(toolUseId);
258
- const fields = buildToolResultFields(isError, rawContent, base);
259
- const update = { tool_call_id: toolUseId, fields };
260
- emitSessionUpdate(session.sessionId, { type: "tool_call_update", tool_call_update: update });
261
- if (base) {
262
- base.status = fields.status ?? base.status;
263
- if (fields.raw_output) {
264
- base.raw_output = fields.raw_output;
265
- }
266
- if (fields.content) {
267
- base.content = fields.content;
268
- }
269
- }
270
- }
271
- function finalizeOpenToolCalls(session, status) {
272
- for (const [toolUseId, toolCall] of session.toolCalls) {
273
- if (toolCall.status !== "pending" && toolCall.status !== "in_progress") {
274
- continue;
275
- }
276
- const fields = { status };
277
- emitSessionUpdate(session.sessionId, {
278
- type: "tool_call_update",
279
- tool_call_update: { tool_call_id: toolUseId, fields },
280
- });
281
- toolCall.status = status;
282
- }
283
- }
284
- function emitToolProgressUpdate(session, toolUseId, toolName) {
285
- const existing = session.toolCalls.get(toolUseId);
286
- if (!existing) {
287
- emitToolCall(session, toolUseId, toolName, {});
288
- return;
289
- }
290
- if (existing.status === "in_progress") {
291
- return;
292
- }
293
- const fields = { status: "in_progress" };
294
- emitSessionUpdate(session.sessionId, {
295
- type: "tool_call_update",
296
- tool_call_update: { tool_call_id: toolUseId, fields },
297
- });
298
- existing.status = "in_progress";
299
- }
300
- function emitToolSummaryUpdate(session, toolUseId, summary) {
301
- const base = session.toolCalls.get(toolUseId);
302
- if (!base) {
303
- return;
304
- }
305
- const fields = {
306
- status: base.status === "failed" ? "failed" : "completed",
307
- raw_output: summary,
308
- content: [{ type: "content", content: { type: "text", text: summary } }],
309
- };
310
- emitSessionUpdate(session.sessionId, {
311
- type: "tool_call_update",
312
- tool_call_update: { tool_call_id: toolUseId, fields },
313
- });
314
- base.status = fields.status ?? base.status;
315
- base.raw_output = summary;
316
- }
317
- function setToolCallStatus(session, toolUseId, status, message) {
318
- const base = session.toolCalls.get(toolUseId);
319
- if (!base) {
320
- return;
321
- }
322
- const fields = { status };
323
- if (message && message.length > 0) {
324
- fields.raw_output = message;
325
- fields.content = [{ type: "content", content: { type: "text", text: message } }];
326
- }
327
- emitSessionUpdate(session.sessionId, {
328
- type: "tool_call_update",
329
- tool_call_update: { tool_call_id: toolUseId, fields },
330
- });
331
- base.status = status;
332
- if (fields.raw_output) {
333
- base.raw_output = fields.raw_output;
334
- }
335
- }
336
- function resolveTaskToolUseId(session, msg) {
337
- const direct = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
338
- if (direct) {
339
- return direct;
340
- }
341
- const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
342
- if (!taskId) {
343
- return "";
344
- }
345
- return session.taskToolUseIds.get(taskId) ?? "";
346
- }
347
- function taskProgressText(msg) {
348
- const description = typeof msg.description === "string" ? msg.description : "";
349
- const lastTool = typeof msg.last_tool_name === "string" ? msg.last_tool_name : "";
350
- if (description && lastTool) {
351
- return `${description} (last tool: ${lastTool})`;
352
- }
353
- return description || lastTool;
354
- }
355
- function handleTaskSystemMessage(session, subtype, msg) {
356
- if (subtype !== "task_started" && subtype !== "task_progress" && subtype !== "task_notification") {
357
- return;
358
- }
359
- const taskId = typeof msg.task_id === "string" ? msg.task_id : "";
360
- const explicitToolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
361
- if (taskId && explicitToolUseId) {
362
- session.taskToolUseIds.set(taskId, explicitToolUseId);
363
- }
364
- const toolUseId = resolveTaskToolUseId(session, msg);
365
- if (!toolUseId) {
366
- return;
367
- }
368
- const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
369
- if (toolCall.status === "pending") {
370
- toolCall.status = "in_progress";
371
- emitSessionUpdate(session.sessionId, {
372
- type: "tool_call_update",
373
- tool_call_update: { tool_call_id: toolUseId, fields: { status: "in_progress" } },
374
- });
375
- }
376
- if (subtype === "task_started") {
377
- const description = typeof msg.description === "string" ? msg.description : "";
378
- if (!description) {
379
- return;
380
- }
381
- emitSessionUpdate(session.sessionId, {
382
- type: "tool_call_update",
383
- tool_call_update: {
384
- tool_call_id: toolUseId,
385
- fields: {
386
- status: "in_progress",
387
- raw_output: description,
388
- content: [{ type: "content", content: { type: "text", text: description } }],
389
- },
390
- },
391
- });
392
- return;
393
- }
394
- if (subtype === "task_progress") {
395
- const progress = taskProgressText(msg);
396
- if (!progress) {
397
- return;
398
- }
399
- emitSessionUpdate(session.sessionId, {
400
- type: "tool_call_update",
401
- tool_call_update: {
402
- tool_call_id: toolUseId,
403
- fields: {
404
- status: "in_progress",
405
- raw_output: progress,
406
- content: [{ type: "content", content: { type: "text", text: progress } }],
407
- },
408
- },
409
- });
410
- return;
411
- }
412
- const status = typeof msg.status === "string" ? msg.status : "";
413
- const summary = typeof msg.summary === "string" ? msg.summary : "";
414
- const finalStatus = status === "completed" ? "completed" : "failed";
415
- const fields = { status: finalStatus };
416
- if (summary) {
417
- fields.raw_output = summary;
418
- fields.content = [{ type: "content", content: { type: "text", text: summary } }];
419
- }
420
- emitSessionUpdate(session.sessionId, {
421
- type: "tool_call_update",
422
- tool_call_update: { tool_call_id: toolUseId, fields },
423
- });
424
- toolCall.status = finalStatus;
425
- if (taskId) {
426
- session.taskToolUseIds.delete(taskId);
427
- }
428
- }
429
- function handleContentBlock(session, block) {
430
- const blockType = typeof block.type === "string" ? block.type : "";
431
- if (blockType === "text") {
432
- const text = typeof block.text === "string" ? block.text : "";
433
- if (text) {
434
- emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
435
- }
436
- return;
437
- }
438
- if (blockType === "thinking") {
439
- const text = typeof block.thinking === "string" ? block.thinking : "";
440
- if (text) {
441
- emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
442
- }
443
- return;
444
- }
445
- if (blockType === "tool_use" || blockType === "server_tool_use" || blockType === "mcp_tool_use") {
446
- const toolUseId = typeof block.id === "string" ? block.id : "";
447
- const name = typeof block.name === "string" ? block.name : "Tool";
448
- const input = block.input && typeof block.input === "object" ? block.input : {};
449
- if (!toolUseId) {
450
- return;
451
- }
452
- emitPlanIfTodoWrite(session, name, input);
453
- emitToolCall(session, toolUseId, name, input);
454
- return;
455
- }
456
- if (TOOL_RESULT_TYPES.has(blockType)) {
457
- const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
458
- if (!toolUseId) {
459
- return;
460
- }
461
- const isError = Boolean(block.is_error);
462
- emitToolResultUpdate(session, toolUseId, isError, block.content);
463
- }
464
- }
465
- function handleStreamEvent(session, event) {
466
- const eventType = typeof event.type === "string" ? event.type : "";
467
- if (eventType === "content_block_start") {
468
- if (event.content_block && typeof event.content_block === "object") {
469
- handleContentBlock(session, event.content_block);
470
- }
471
- return;
472
- }
473
- if (eventType === "content_block_delta") {
474
- if (!event.delta || typeof event.delta !== "object") {
475
- return;
476
- }
477
- const delta = event.delta;
478
- const deltaType = typeof delta.type === "string" ? delta.type : "";
479
- if (deltaType === "text_delta") {
480
- const text = typeof delta.text === "string" ? delta.text : "";
481
- if (text) {
482
- emitSessionUpdate(session.sessionId, { type: "agent_message_chunk", content: { type: "text", text } });
483
- }
484
- }
485
- else if (deltaType === "thinking_delta") {
486
- const text = typeof delta.thinking === "string" ? delta.thinking : "";
487
- if (text) {
488
- emitSessionUpdate(session.sessionId, { type: "agent_thought_chunk", content: { type: "text", text } });
489
- }
490
- }
491
- }
492
- }
493
- function handleAssistantMessage(session, message) {
494
- const assistantError = typeof message.error === "string" ? message.error : "";
495
- if (assistantError.length > 0) {
496
- session.lastAssistantError = assistantError;
497
- }
498
- const messageObject = message.message && typeof message.message === "object"
499
- ? message.message
500
- : null;
501
- if (!messageObject) {
502
- return;
503
- }
504
- const content = Array.isArray(messageObject.content) ? messageObject.content : [];
505
- for (const block of content) {
506
- if (!block || typeof block !== "object") {
507
- continue;
508
- }
509
- const blockRecord = block;
510
- const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
511
- if (blockType === "tool_use" ||
512
- blockType === "server_tool_use" ||
513
- blockType === "mcp_tool_use" ||
514
- TOOL_RESULT_TYPES.has(blockType)) {
515
- handleContentBlock(session, blockRecord);
516
- }
517
- }
518
- }
519
- function handleUserToolResultBlocks(session, message) {
520
- const messageObject = message.message && typeof message.message === "object"
521
- ? message.message
522
- : null;
523
- if (!messageObject) {
524
- return;
525
- }
526
- const content = Array.isArray(messageObject.content) ? messageObject.content : [];
527
- for (const block of content) {
528
- if (!block || typeof block !== "object") {
529
- continue;
530
- }
531
- const blockRecord = block;
532
- const blockType = typeof blockRecord.type === "string" ? blockRecord.type : "";
533
- if (TOOL_RESULT_TYPES.has(blockType)) {
534
- handleContentBlock(session, blockRecord);
535
- }
536
- }
537
- }
538
- function emitAuthRequired(session, detail) {
539
- if (session.authHintSent) {
540
- return;
541
- }
542
- session.authHintSent = true;
543
- writeEvent({
544
- event: "auth_required",
545
- method_name: "Claude Login",
546
- method_description: detail && detail.trim().length > 0
547
- ? detail
548
- : "Run `claude /login` in a terminal, then retry.",
549
- });
550
- }
551
- function looksLikePlanLimitError(input) {
552
- const normalized = input.toLowerCase();
553
- return (normalized.includes("rate limit") ||
554
- normalized.includes("rate-limit") ||
555
- normalized.includes("max turns") ||
556
- normalized.includes("max budget") ||
557
- normalized.includes("quota") ||
558
- normalized.includes("plan limit") ||
559
- normalized.includes("too many requests") ||
560
- normalized.includes("insufficient quota") ||
561
- normalized.includes("429"));
562
- }
563
- function classifyTurnErrorKind(subtype, errors, assistantError) {
564
- const combined = errors.join("\n");
565
- if (subtype === "error_max_turns" ||
566
- subtype === "error_max_budget_usd" ||
567
- assistantError === "billing_error" ||
568
- assistantError === "rate_limit" ||
569
- (combined.length > 0 && looksLikePlanLimitError(combined))) {
570
- return "plan_limit";
571
- }
572
- if (assistantError === "authentication_failed" ||
573
- errors.some((entry) => looksLikeAuthRequired(entry))) {
574
- return "auth_required";
575
- }
576
- if (assistantError === "server_error") {
577
- return "internal";
578
- }
579
- return "other";
580
- }
581
- function numberField(record, ...keys) {
582
- for (const key of keys) {
583
- const value = record[key];
584
- if (typeof value === "number" && Number.isFinite(value)) {
585
- return value;
586
- }
587
- }
588
- return undefined;
589
- }
590
- export function parseFastModeState(value) {
591
- if (value === "off" || value === "cooldown" || value === "on") {
592
- return value;
593
- }
594
- return null;
595
- }
596
- export function parseRateLimitStatus(value) {
597
- if (value === "allowed" || value === "allowed_warning" || value === "rejected") {
598
- return value;
599
- }
600
- return null;
601
- }
602
- export function buildRateLimitUpdate(rateLimitInfo) {
603
- const info = asRecordOrNull(rateLimitInfo);
604
- if (!info) {
605
- return null;
606
- }
607
- const status = parseRateLimitStatus(info.status);
608
- if (!status) {
609
- return null;
610
- }
611
- const update = {
612
- type: "rate_limit_update",
613
- status,
614
- };
615
- const resetsAt = numberField(info, "resetsAt");
616
- if (resetsAt !== undefined) {
617
- update.resets_at = resetsAt;
618
- }
619
- const utilization = numberField(info, "utilization");
620
- if (utilization !== undefined) {
621
- update.utilization = utilization;
622
- }
623
- if (typeof info.rateLimitType === "string" && info.rateLimitType.length > 0) {
624
- update.rate_limit_type = info.rateLimitType;
625
- }
626
- const overageStatus = parseRateLimitStatus(info.overageStatus);
627
- if (overageStatus) {
628
- update.overage_status = overageStatus;
629
- }
630
- const overageResetsAt = numberField(info, "overageResetsAt");
631
- if (overageResetsAt !== undefined) {
632
- update.overage_resets_at = overageResetsAt;
633
- }
634
- if (typeof info.overageDisabledReason === "string" && info.overageDisabledReason.length > 0) {
635
- update.overage_disabled_reason = info.overageDisabledReason;
636
- }
637
- if (typeof info.isUsingOverage === "boolean") {
638
- update.is_using_overage = info.isUsingOverage;
639
- }
640
- const surpassedThreshold = numberField(info, "surpassedThreshold");
641
- if (surpassedThreshold !== undefined) {
642
- update.surpassed_threshold = surpassedThreshold;
643
- }
644
- return update;
645
- }
646
- function availableAgentsSignature(agents) {
647
- return JSON.stringify(agents);
648
- }
649
- function normalizeAvailableAgentName(value) {
650
- if (typeof value !== "string") {
651
- return "";
652
- }
653
- return value.trim();
654
- }
655
- export function mapAvailableAgents(value) {
656
- if (!Array.isArray(value)) {
657
- return [];
658
- }
659
- const byName = new Map();
660
- for (const entry of value) {
661
- if (!entry || typeof entry !== "object") {
662
- continue;
663
- }
664
- const record = entry;
665
- const name = normalizeAvailableAgentName(record.name);
666
- if (!name) {
667
- continue;
668
- }
669
- const description = typeof record.description === "string" ? record.description : "";
670
- const model = typeof record.model === "string" && record.model.trim().length > 0 ? record.model : undefined;
671
- const existing = byName.get(name);
672
- if (!existing) {
673
- byName.set(name, { name, description, model });
674
- continue;
675
- }
676
- if (existing.description.trim().length === 0 && description.trim().length > 0) {
677
- existing.description = description;
678
- }
679
- if (!existing.model && model) {
680
- existing.model = model;
681
- }
682
- }
683
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
684
- }
685
- function mapAvailableAgentsFromNames(value) {
686
- if (!Array.isArray(value)) {
687
- return [];
688
- }
689
- const byName = new Map();
690
- for (const entry of value) {
691
- const name = normalizeAvailableAgentName(entry);
692
- if (!name || byName.has(name)) {
693
- continue;
694
- }
695
- byName.set(name, { name, description: "" });
696
- }
697
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
698
- }
699
- function emitAvailableAgentsIfChanged(session, agents) {
700
- const signature = availableAgentsSignature(agents);
701
- if (session.lastAvailableAgentsSignature === signature) {
702
- return;
703
- }
704
- session.lastAvailableAgentsSignature = signature;
705
- emitSessionUpdate(session.sessionId, { type: "available_agents_update", agents });
706
- }
707
- function refreshAvailableAgents(session) {
708
- if (typeof session.query.supportedAgents !== "function") {
709
- return;
710
- }
711
- void session.query
712
- .supportedAgents()
713
- .then((agents) => {
714
- emitAvailableAgentsIfChanged(session, mapAvailableAgents(agents));
715
- })
716
- .catch(() => {
717
- // Best-effort only.
718
- });
719
- }
720
- function emitFastModeUpdateIfChanged(session, value) {
721
- const next = parseFastModeState(value);
722
- if (!next || next === session.fastModeState) {
723
- return;
724
- }
725
- session.fastModeState = next;
726
- emitSessionUpdate(session.sessionId, { type: "fast_mode_update", fast_mode_state: next });
727
- }
728
- function handleResultMessage(session, message) {
729
- const usageUpdate = buildUsageUpdateFromResultForSession(session, message);
730
- if (usageUpdate) {
731
- emitSessionUpdate(session.sessionId, usageUpdate);
732
- }
733
- emitFastModeUpdateIfChanged(session, message.fast_mode_state);
734
- const subtype = typeof message.subtype === "string" ? message.subtype : "";
735
- if (subtype === "success") {
736
- session.lastAssistantError = undefined;
737
- finalizeOpenToolCalls(session, "completed");
738
- writeEvent({ event: "turn_complete", session_id: session.sessionId });
739
- return;
740
- }
741
- const errors = Array.isArray(message.errors) && message.errors.every((entry) => typeof entry === "string")
742
- ? message.errors
743
- : [];
744
- const assistantError = session.lastAssistantError;
745
- const authHint = errors.find((entry) => looksLikeAuthRequired(entry));
746
- if (authHint) {
747
- emitAuthRequired(session, authHint);
748
- }
749
- if (assistantError === "authentication_failed") {
750
- emitAuthRequired(session);
751
- }
752
- finalizeOpenToolCalls(session, "failed");
753
- const errorKind = classifyTurnErrorKind(subtype, errors, assistantError);
754
- const fallback = subtype ? `turn failed: ${subtype}` : "turn failed";
755
- writeEvent({
756
- event: "turn_error",
757
- session_id: session.sessionId,
758
- message: errors.length > 0 ? errors.join("\n") : fallback,
759
- error_kind: errorKind,
760
- ...(subtype ? { sdk_result_subtype: subtype } : {}),
761
- ...(assistantError ? { assistant_error: assistantError } : {}),
762
- });
763
- session.lastAssistantError = undefined;
764
- }
765
- function handleSdkMessage(session, message) {
766
- const msg = message;
767
- const type = typeof msg.type === "string" ? msg.type : "";
768
- if (type === "system") {
769
- const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
770
- if (subtype === "init") {
771
- const previousSessionId = session.sessionId;
772
- const incomingSessionId = typeof msg.session_id === "string" ? msg.session_id : session.sessionId;
773
- updateSessionId(session, incomingSessionId);
774
- const modelName = typeof msg.model === "string" ? msg.model : session.model;
775
- session.model = modelName;
776
- const incomingMode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
777
- if (incomingMode) {
778
- session.mode = incomingMode;
779
- }
780
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
781
- if (!session.connected) {
782
- emitConnectEvent(session);
783
- }
784
- else if (previousSessionId !== session.sessionId) {
785
- const historyUpdates = session.resumeUpdates;
786
- writeEvent({
787
- event: "session_replaced",
788
- session_id: session.sessionId,
789
- cwd: session.cwd,
790
- model_name: session.model,
791
- mode: buildModeState(session.mode),
792
- ...(historyUpdates && historyUpdates.length > 0
793
- ? { history_updates: historyUpdates }
794
- : {}),
795
- });
796
- session.resumeUpdates = undefined;
797
- refreshSessionsList();
798
- }
799
- if (Array.isArray(msg.slash_commands)) {
800
- const commands = msg.slash_commands
801
- .filter((entry) => typeof entry === "string")
802
- .map((name) => ({ name, description: "", input_hint: undefined }));
803
- if (commands.length > 0) {
804
- emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands });
805
- }
806
- }
807
- if (session.lastAvailableAgentsSignature === undefined && Array.isArray(msg.agents)) {
808
- emitAvailableAgentsIfChanged(session, mapAvailableAgentsFromNames(msg.agents));
809
- }
810
- void session.query
811
- .supportedCommands()
812
- .then((commands) => {
813
- const mapped = commands.map((command) => ({
814
- name: command.name,
815
- description: command.description ?? "",
816
- input_hint: command.argumentHint ?? undefined,
817
- }));
818
- emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands: mapped });
819
- })
820
- .catch(() => {
821
- // Best-effort only; slash commands from init were already emitted.
822
- });
823
- refreshAvailableAgents(session);
824
- return;
825
- }
826
- if (subtype === "status") {
827
- const mode = typeof msg.permissionMode === "string" ? toPermissionMode(msg.permissionMode) : null;
828
- if (mode) {
829
- session.mode = mode;
830
- emitSessionUpdate(session.sessionId, { type: "current_mode_update", current_mode_id: mode });
831
- }
832
- if (msg.status === "compacting") {
833
- emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "compacting" });
834
- }
835
- else if (msg.status === null) {
836
- emitSessionUpdate(session.sessionId, { type: "session_status_update", status: "idle" });
837
- }
838
- emitFastModeUpdateIfChanged(session, msg.fast_mode_state);
839
- return;
840
- }
841
- if (subtype === "compact_boundary") {
842
- const compactMetadata = asRecordOrNull(msg.compact_metadata);
843
- if (!compactMetadata) {
844
- return;
845
- }
846
- const trigger = compactMetadata.trigger;
847
- const preTokens = numberField(compactMetadata, "pre_tokens", "preTokens");
848
- if ((trigger === "manual" || trigger === "auto") && preTokens !== undefined) {
849
- emitSessionUpdate(session.sessionId, {
850
- type: "compaction_boundary",
851
- trigger,
852
- pre_tokens: preTokens,
853
- });
854
- }
855
- return;
856
- }
857
- if (subtype === "local_command_output") {
858
- const content = typeof msg.content === "string" ? msg.content : "";
859
- if (content.trim().length > 0) {
860
- emitSessionUpdate(session.sessionId, {
861
- type: "agent_message_chunk",
862
- content: { type: "text", text: content },
863
- });
864
- }
865
- return;
866
- }
867
- if (subtype === "elicitation_complete") {
868
- // No-op: elicitation flow is auto-canceled in the onElicitation callback.
869
- return;
870
- }
871
- handleTaskSystemMessage(session, subtype, msg);
872
- return;
873
- }
874
- if (type === "auth_status") {
875
- const output = Array.isArray(msg.output)
876
- ? msg.output.filter((entry) => typeof entry === "string").join("\n")
877
- : "";
878
- const errorText = typeof msg.error === "string" ? msg.error : "";
879
- const combined = [errorText, output].filter((entry) => entry.length > 0).join("\n");
880
- if (combined && looksLikeAuthRequired(combined)) {
881
- emitAuthRequired(session, combined);
882
- }
883
- return;
884
- }
885
- if (type === "stream_event") {
886
- if (msg.event && typeof msg.event === "object") {
887
- handleStreamEvent(session, msg.event);
888
- }
889
- return;
890
- }
891
- if (type === "tool_progress") {
892
- const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
893
- const toolName = typeof msg.tool_name === "string" ? msg.tool_name : "Tool";
894
- if (toolUseId) {
895
- emitToolProgressUpdate(session, toolUseId, toolName);
896
- }
897
- return;
898
- }
899
- if (type === "tool_use_summary") {
900
- const summary = typeof msg.summary === "string" ? msg.summary : "";
901
- const toolIds = Array.isArray(msg.preceding_tool_use_ids)
902
- ? msg.preceding_tool_use_ids.filter((id) => typeof id === "string")
903
- : [];
904
- if (summary && toolIds.length > 0) {
905
- for (const toolUseId of toolIds) {
906
- emitToolSummaryUpdate(session, toolUseId, summary);
907
- }
908
- }
909
- return;
910
- }
911
- if (type === "rate_limit_event") {
912
- const update = buildRateLimitUpdate(msg.rate_limit_info);
913
- if (update) {
914
- emitSessionUpdate(session.sessionId, update);
915
- }
916
- return;
917
- }
918
- if (type === "user") {
919
- handleUserToolResultBlocks(session, msg);
920
- const toolUseId = typeof msg.parent_tool_use_id === "string" ? msg.parent_tool_use_id : "";
921
- if (toolUseId && "tool_use_result" in msg) {
922
- const parsed = unwrapToolUseResult(msg.tool_use_result);
923
- emitToolResultUpdate(session, toolUseId, parsed.isError, parsed.content);
924
- }
925
- return;
926
- }
927
- if (type === "assistant") {
928
- if (msg.error === "authentication_failed") {
929
- emitAuthRequired(session);
930
- }
931
- handleAssistantMessage(session, msg);
932
- return;
933
- }
934
- if (type === "result") {
935
- handleResultMessage(session, msg);
936
- }
937
- }
938
- const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
939
- const QUESTION_CHOICE_KIND = "question_choice";
940
- const EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
941
- const PLAN_APPROVE_KIND = "plan_approve";
942
- const PLAN_REJECT_KIND = "plan_reject";
943
- async function requestExitPlanModeApproval(session, toolUseId, inputData, baseToolCall) {
944
- const options = [
945
- {
946
- option_id: "approve",
947
- name: "Approve",
948
- description: "Approve the plan and continue",
949
- kind: PLAN_APPROVE_KIND,
950
- },
951
- {
952
- option_id: "reject",
953
- name: "Reject",
954
- description: "Reject the plan",
955
- kind: PLAN_REJECT_KIND,
956
- },
957
- ];
958
- const request = {
959
- tool_call: baseToolCall,
960
- options,
961
- };
962
- const outcome = await new Promise((resolve) => {
963
- session.pendingPermissions.set(toolUseId, {
964
- onOutcome: resolve,
965
- toolName: EXIT_PLAN_MODE_TOOL_NAME,
966
- inputData,
967
- });
968
- writeEvent({ event: "permission_request", session_id: session.sessionId, request });
969
- });
970
- if (outcome.outcome !== "selected" || outcome.option_id === "reject") {
971
- setToolCallStatus(session, toolUseId, "failed", "Plan rejected");
972
- return { behavior: "deny", message: "Plan rejected", toolUseID: toolUseId };
973
- }
974
- return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
975
- }
976
- function parseAskUserQuestionPrompts(inputData) {
977
- const rawQuestions = Array.isArray(inputData.questions) ? inputData.questions : [];
978
- const prompts = [];
979
- for (const rawQuestion of rawQuestions) {
980
- const questionRecord = asRecordOrNull(rawQuestion);
981
- if (!questionRecord) {
982
- continue;
983
- }
984
- const question = typeof questionRecord.question === "string" ? questionRecord.question.trim() : "";
985
- if (!question) {
986
- continue;
987
- }
988
- const headerRaw = typeof questionRecord.header === "string" ? questionRecord.header.trim() : "";
989
- const header = headerRaw || `Q${prompts.length + 1}`;
990
- const multiSelect = Boolean(questionRecord.multiSelect);
991
- const rawOptions = Array.isArray(questionRecord.options) ? questionRecord.options : [];
992
- const options = [];
993
- for (const rawOption of rawOptions) {
994
- const optionRecord = asRecordOrNull(rawOption);
995
- if (!optionRecord) {
996
- continue;
997
- }
998
- const label = typeof optionRecord.label === "string" ? optionRecord.label.trim() : "";
999
- const description = typeof optionRecord.description === "string" ? optionRecord.description.trim() : "";
1000
- if (!label) {
1001
- continue;
1002
- }
1003
- options.push({ label, description });
1004
- }
1005
- if (options.length < 2) {
1006
- continue;
1007
- }
1008
- prompts.push({ question, header, multiSelect, options });
1009
- }
1010
- return prompts;
1011
- }
1012
- function askUserQuestionOptions(prompt) {
1013
- return prompt.options.map((option, index) => ({
1014
- option_id: `question_${index}`,
1015
- name: option.label,
1016
- description: option.description,
1017
- kind: QUESTION_CHOICE_KIND,
1018
- }));
1019
- }
1020
- function askUserQuestionPromptToolCall(base, prompt, index, total) {
1021
- return {
1022
- ...base,
1023
- title: prompt.question,
1024
- raw_input: {
1025
- questions: [
1026
- {
1027
- question: prompt.question,
1028
- header: prompt.header,
1029
- multiSelect: prompt.multiSelect,
1030
- options: prompt.options,
1031
- },
1032
- ],
1033
- question_index: index,
1034
- total_questions: total,
1035
- },
1036
- };
1037
- }
1038
- function askUserQuestionTranscript(answers) {
1039
- return answers.map((entry) => `${entry.header}: ${entry.answer}\n ${entry.question}`).join("\n");
1040
- }
1041
- async function requestAskUserQuestionAnswers(session, toolUseId, toolName, inputData, baseToolCall) {
1042
- const prompts = parseAskUserQuestionPrompts(inputData);
1043
- if (prompts.length === 0) {
1044
- return { behavior: "allow", updatedInput: inputData, toolUseID: toolUseId };
1045
- }
1046
- const answers = {};
1047
- const transcript = [];
1048
- for (const [index, prompt] of prompts.entries()) {
1049
- const promptToolCall = askUserQuestionPromptToolCall(baseToolCall, prompt, index, prompts.length);
1050
- const fields = {
1051
- title: promptToolCall.title,
1052
- status: "in_progress",
1053
- raw_input: promptToolCall.raw_input,
1054
- };
1055
- emitSessionUpdate(session.sessionId, {
1056
- type: "tool_call_update",
1057
- tool_call_update: { tool_call_id: toolUseId, fields },
1058
- });
1059
- const tracked = session.toolCalls.get(toolUseId);
1060
- if (tracked) {
1061
- tracked.title = promptToolCall.title;
1062
- tracked.status = "in_progress";
1063
- tracked.raw_input = promptToolCall.raw_input;
1064
- }
1065
- const request = {
1066
- tool_call: promptToolCall,
1067
- options: askUserQuestionOptions(prompt),
1068
- };
1069
- const outcome = await new Promise((resolve) => {
1070
- session.pendingPermissions.set(toolUseId, {
1071
- onOutcome: resolve,
1072
- toolName,
1073
- inputData,
1074
- });
1075
- writeEvent({ event: "permission_request", session_id: session.sessionId, request });
1076
- });
1077
- if (outcome.outcome !== "selected") {
1078
- setToolCallStatus(session, toolUseId, "failed", "Question cancelled");
1079
- return { behavior: "deny", message: "Question cancelled", toolUseID: toolUseId };
1080
- }
1081
- const selected = request.options.find((option) => option.option_id === outcome.option_id);
1082
- if (!selected) {
1083
- setToolCallStatus(session, toolUseId, "failed", "Question answer was invalid");
1084
- return { behavior: "deny", message: "Question answer was invalid", toolUseID: toolUseId };
1085
- }
1086
- answers[prompt.question] = selected.name;
1087
- transcript.push({ header: prompt.header, question: prompt.question, answer: selected.name });
1088
- const summary = askUserQuestionTranscript(transcript);
1089
- const progressFields = {
1090
- status: index + 1 >= prompts.length ? "completed" : "in_progress",
1091
- raw_output: summary,
1092
- content: [{ type: "content", content: { type: "text", text: summary } }],
1093
- };
1094
- emitSessionUpdate(session.sessionId, {
1095
- type: "tool_call_update",
1096
- tool_call_update: { tool_call_id: toolUseId, fields: progressFields },
1097
- });
1098
- if (tracked) {
1099
- tracked.status = progressFields.status ?? tracked.status;
1100
- tracked.raw_output = summary;
1101
- tracked.content = progressFields.content ?? tracked.content;
1102
- }
1103
- }
1104
- return {
1105
- behavior: "allow",
1106
- updatedInput: { ...inputData, answers },
1107
- toolUseID: toolUseId,
1108
- };
1109
- }
1110
- async function closeSession(session) {
1111
- session.input.close();
1112
- session.query.close();
1113
- for (const pending of session.pendingPermissions.values()) {
1114
- pending.resolve?.({ behavior: "deny", message: "Session closed" });
1115
- pending.onOutcome?.({ outcome: "cancelled" });
1116
- }
1117
- session.pendingPermissions.clear();
1118
- }
1119
- async function closeAllSessions() {
1120
- const active = Array.from(sessions.values());
1121
- sessions.clear();
1122
- await Promise.all(active.map((session) => closeSession(session)));
1123
- }
1124
- async function createSession(params) {
1125
- const input = new AsyncQueue();
1126
- const startMode = params.yolo ? "bypassPermissions" : "default";
1127
- const provisionalSessionId = params.resume ?? randomUUID();
1128
- let session;
1129
- const canUseTool = async (toolName, inputData, options) => {
1130
- const toolUseId = options.toolUseID;
1131
- if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
1132
- const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
1133
- return await requestExitPlanModeApproval(session, toolUseId, inputData, existing);
1134
- }
1135
- logPermissionDebug(`request tool_use_id=${toolUseId} tool=${toolName} blocked_path=${options.blockedPath ?? "<none>"} ` +
1136
- `decision_reason=${options.decisionReason ?? "<none>"} suggestions=${formatPermissionUpdates(options.suggestions)}`);
1137
- const existing = ensureToolCallVisible(session, toolUseId, toolName, inputData);
1138
- if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
1139
- return await requestAskUserQuestionAnswers(session, toolUseId, toolName, inputData, existing);
1140
- }
1141
- const request = {
1142
- tool_call: existing,
1143
- options: permissionOptionsFromSuggestions(options.suggestions),
1144
- };
1145
- writeEvent({ event: "permission_request", session_id: session.sessionId, request });
1146
- return await new Promise((resolve) => {
1147
- session.pendingPermissions.set(toolUseId, {
1148
- resolve,
1149
- toolName,
1150
- inputData: inputData,
1151
- suggestions: options.suggestions,
1152
- });
1153
- });
1154
- };
1155
- const claudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
1156
- const sdkDebugFile = process.env.CLAUDE_RS_SDK_DEBUG_FILE;
1157
- const enableSdkDebug = process.env.CLAUDE_RS_SDK_DEBUG === "1" || Boolean(sdkDebugFile);
1158
- const enableSpawnDebug = process.env.CLAUDE_RS_SDK_SPAWN_DEBUG === "1";
1159
- if (claudeCodeExecutable && !fs.existsSync(claudeCodeExecutable)) {
1160
- throw new Error(`CLAUDE_CODE_EXECUTABLE does not exist: ${claudeCodeExecutable}`);
1161
- }
1162
- let queryHandle;
1163
- try {
1164
- queryHandle = query({
1165
- prompt: input,
1166
- options: {
1167
- cwd: params.cwd,
1168
- includePartialMessages: true,
1169
- executable: "node",
1170
- ...(params.resume ? {} : { sessionId: provisionalSessionId }),
1171
- ...(claudeCodeExecutable
1172
- ? { pathToClaudeCodeExecutable: claudeCodeExecutable }
1173
- : {}),
1174
- ...(enableSdkDebug ? { debug: true } : {}),
1175
- ...(sdkDebugFile ? { debugFile: sdkDebugFile } : {}),
1176
- stderr: (line) => {
1177
- if (line.trim().length > 0) {
1178
- console.error(`[sdk stderr] ${line}`);
1179
- }
1180
- },
1181
- ...(enableSpawnDebug
1182
- ? {
1183
- spawnClaudeCodeProcess: (options) => {
1184
- console.error(`[sdk spawn] command=${options.command} args=${JSON.stringify(options.args)} cwd=${options.cwd ?? "<none>"}`);
1185
- const child = spawnChild(options.command, options.args, {
1186
- cwd: options.cwd,
1187
- env: options.env,
1188
- signal: options.signal,
1189
- stdio: ["pipe", "pipe", "pipe"],
1190
- windowsHide: true,
1191
- });
1192
- child.on("error", (error) => {
1193
- console.error(`[sdk spawn error] code=${error.code ?? "<none>"} message=${error.message}`);
1194
- });
1195
- return child;
1196
- },
1197
- }
1198
- : {}),
1199
- // Match claude-agent-acp defaults to avoid emitting an empty
1200
- // --setting-sources argument.
1201
- settingSources: ["user", "project", "local"],
1202
- permissionMode: startMode,
1203
- allowDangerouslySkipPermissions: params.yolo,
1204
- resume: params.resume,
1205
- model: params.model,
1206
- canUseTool,
1207
- onElicitation: async (request) => {
1208
- const requestMode = typeof request.mode === "string" ? request.mode : "unknown";
1209
- const requestServer = typeof request.serverName === "string" && request.serverName.trim().length > 0
1210
- ? request.serverName
1211
- : "unknown";
1212
- const requestMessage = typeof request.message === "string" && request.message.trim().length > 0
1213
- ? request.message
1214
- : "<no message>";
1215
- console.error(`[sdk warn] elicitation unsupported without MCP settings UI; ` +
1216
- `auto-canceling session_id=${session.sessionId} server=${requestServer} ` +
1217
- `mode=${requestMode} message=${JSON.stringify(requestMessage)}`);
1218
- return { action: "cancel" };
1219
- },
1220
- },
1221
- });
1222
- }
1223
- catch (error) {
1224
- const message = error instanceof Error ? error.message : String(error);
1225
- throw new Error(`query() failed: node_executable=${process.execPath}; cwd=${params.cwd}; ` +
1226
- `resume=${params.resume ?? "<none>"}; model=${params.model ?? "<none>"}; ` +
1227
- `CLAUDE_CODE_EXECUTABLE=${claudeCodeExecutable ?? "<unset>"}; error=${message}`);
1228
- }
1229
- session = {
1230
- sessionId: provisionalSessionId,
1231
- cwd: params.cwd,
1232
- model: params.model ?? "default",
1233
- mode: startMode,
1234
- fastModeState: "off",
1235
- yolo: params.yolo,
1236
- query: queryHandle,
1237
- input,
1238
- connected: false,
1239
- connectEvent: params.connectEvent,
1240
- connectRequestId: params.requestId,
1241
- toolCalls: new Map(),
1242
- taskToolUseIds: new Map(),
1243
- pendingPermissions: new Map(),
1244
- authHintSent: false,
1245
- ...(params.resumeUpdates && params.resumeUpdates.length > 0
1246
- ? { resumeUpdates: params.resumeUpdates }
1247
- : {}),
1248
- ...(params.sessionsToCloseAfterConnect
1249
- ? { sessionsToCloseAfterConnect: params.sessionsToCloseAfterConnect }
1250
- : {}),
1251
- };
1252
- sessions.set(provisionalSessionId, session);
1253
- // In stream-input mode the SDK may defer init until input arrives.
1254
- // Trigger initialization explicitly so the Rust UI can receive `connected`
1255
- // before the first user prompt.
1256
- void session.query
1257
- .initializationResult()
1258
- .then((result) => {
1259
- if (!session.connected) {
1260
- emitConnectEvent(session);
1261
- }
1262
- emitFastModeUpdateIfChanged(session, result.fast_mode_state);
1263
- const commands = Array.isArray(result.commands)
1264
- ? result.commands.map((command) => ({
1265
- name: command.name,
1266
- description: command.description ?? "",
1267
- input_hint: command.argumentHint ?? undefined,
1268
- }))
1269
- : [];
1270
- if (commands.length > 0) {
1271
- emitSessionUpdate(session.sessionId, { type: "available_commands_update", commands });
1272
- }
1273
- emitAvailableAgentsIfChanged(session, mapAvailableAgents(result.agents));
1274
- refreshAvailableAgents(session);
1275
- })
1276
- .catch((error) => {
1277
- if (session.connected) {
1278
- return;
1279
- }
1280
- const message = error instanceof Error ? error.message : String(error);
1281
- failConnection(`agent initialization failed: ${message}`, session.connectRequestId);
1282
- session.connectRequestId = undefined;
1283
- });
1284
- void (async () => {
1285
- try {
1286
- for await (const message of session.query) {
1287
- handleSdkMessage(session, message);
1288
- }
1289
- if (!session.connected) {
1290
- failConnection("agent stream ended before session initialization", params.requestId);
1291
- }
1292
- }
1293
- catch (error) {
1294
- const message = error instanceof Error ? error.message : String(error);
1295
- failConnection(`agent stream failed: ${message}`, params.requestId);
1296
- }
1297
- })();
1298
- }
1299
- function handlePermissionResponse(command) {
1300
- const session = sessionById(command.session_id);
1301
- if (!session) {
1302
- logPermissionDebug(`response dropped: unknown session session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
1303
- return;
1304
- }
1305
- const resolver = session.pendingPermissions.get(command.tool_call_id);
1306
- if (!resolver) {
1307
- logPermissionDebug(`response dropped: no pending resolver session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
1308
- return;
1309
- }
1310
- session.pendingPermissions.delete(command.tool_call_id);
1311
- const outcome = command.outcome;
1312
- if (resolver.onOutcome) {
1313
- resolver.onOutcome(outcome);
1314
- return;
1315
- }
1316
- if (!resolver.resolve) {
1317
- logPermissionDebug(`response dropped: resolver missing callback session_id=${command.session_id} tool_call_id=${command.tool_call_id}`);
1318
- return;
1319
- }
1320
- const selectedOption = outcome.outcome === "selected" ? outcome.option_id : "cancelled";
1321
- logPermissionDebug(`response session_id=${command.session_id} tool_call_id=${command.tool_call_id} tool=${resolver.toolName} ` +
1322
- `selected=${selectedOption} suggestions=${formatPermissionUpdates(resolver.suggestions)}`);
1323
- if (outcome.outcome === "selected" &&
1324
- (outcome.option_id === "allow_once" ||
1325
- outcome.option_id === "allow_session" ||
1326
- outcome.option_id === "allow_always")) {
1327
- setToolCallStatus(session, command.tool_call_id, "in_progress");
1328
- }
1329
- else if (outcome.outcome === "selected") {
1330
- setToolCallStatus(session, command.tool_call_id, "failed", "Permission denied");
1331
- }
1332
- else {
1333
- setToolCallStatus(session, command.tool_call_id, "failed", "Permission cancelled");
1334
- }
1335
- const permissionResult = permissionResultFromOutcome(outcome, command.tool_call_id, resolver.inputData, resolver.suggestions, resolver.toolName);
1336
- if (permissionResult.behavior === "allow") {
1337
- logPermissionDebug(`result tool_call_id=${command.tool_call_id} behavior=allow updated_permissions=` +
1338
- `${formatPermissionUpdates(permissionResult.updatedPermissions)}`);
1339
- }
1340
- else {
1341
- logPermissionDebug(`result tool_call_id=${command.tool_call_id} behavior=deny message=${permissionResult.message}`);
1342
- }
1343
- resolver.resolve(permissionResult);
1344
- }
1345
45
  async function handleCommand(command, requestId) {
1346
46
  const sdkVersionError = agentSdkVersionCompatibilityError();
1347
47
  if (sdkVersionError && command.command !== "initialize" && command.command !== "shutdown") {
@@ -1379,9 +79,8 @@ async function handleCommand(command, requestId) {
1379
79
  case "create_session":
1380
80
  await createSession({
1381
81
  cwd: command.cwd,
1382
- yolo: command.yolo,
1383
- model: command.model,
1384
82
  resume: command.resume,
83
+ launchSettings: command.launch_settings,
1385
84
  connectEvent: "connected",
1386
85
  requestId,
1387
86
  });
@@ -1400,8 +99,8 @@ async function handleCommand(command, requestId) {
1400
99
  const hadActiveSession = staleSessions.length > 0;
1401
100
  await createSession({
1402
101
  cwd: matched.cwd ?? process.cwd(),
1403
- yolo: false,
1404
102
  resume: command.session_id,
103
+ launchSettings: command.launch_settings,
1405
104
  ...(resumeUpdates.length > 0 ? { resumeUpdates } : {}),
1406
105
  connectEvent: hadActiveSession ? "session_replaced" : "connected",
1407
106
  requestId,
@@ -1418,8 +117,7 @@ async function handleCommand(command, requestId) {
1418
117
  await closeAllSessions();
1419
118
  await createSession({
1420
119
  cwd: command.cwd,
1421
- yolo: command.yolo,
1422
- model: command.model,
120
+ launchSettings: command.launch_settings,
1423
121
  connectEvent: "session_replaced",
1424
122
  requestId,
1425
123
  });