claude-code-rust 0.12.2 → 0.12.4
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 -7
- package/agent-sdk/dist/bridge/commands.js +20 -0
- package/agent-sdk/dist/bridge/events.js +15 -1
- package/agent-sdk/dist/bridge/logger.js +10 -0
- package/agent-sdk/dist/bridge/message_handlers.js +172 -1
- package/agent-sdk/dist/bridge/session_lifecycle.js +37 -0
- package/agent-sdk/dist/bridge/state_parsing.js +9 -0
- package/agent-sdk/dist/bridge/tooling.js +71 -18
- package/agent-sdk/dist/bridge.js +410 -2
- package/agent-sdk/dist/bridge.test.js +650 -5
- package/package.json +9 -4
- package/scripts/jscpd-warning-summary.mjs +132 -0
package/README.md
CHANGED
|
@@ -38,12 +38,8 @@ claude-rs
|
|
|
38
38
|
|
|
39
39
|
Full documentation is available at [srothgan.github.io/claude-code-rust](https://srothgan.github.io/claude-code-rust/).
|
|
40
40
|
|
|
41
|
-
> [!
|
|
42
|
-
> **Agent SDK billing
|
|
43
|
-
>
|
|
44
|
-
> Sources:
|
|
45
|
-
> - [Anthropic support: Use the Claude Agent SDK with your Claude plan](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan)
|
|
46
|
-
> - [ClaudeDevs announcement](https://x.com/ClaudeDevs/status/2054610152817619388)
|
|
41
|
+
> [!NOTE]
|
|
42
|
+
> **Agent SDK billing unchanged.** Anthropic has paused the previously announced Agent SDK credit change. For now nothing changes: Claude Agent SDK usage — including `claude -p` and third-party apps like this one — still draws from your normal Claude subscription limits. See [Use the Claude Agent SDK with your Claude plan](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan).
|
|
47
43
|
|
|
48
44
|
## Why
|
|
49
45
|
|
|
@@ -85,7 +81,7 @@ This project is not affiliated with, endorsed by, or supported by Anthropic.
|
|
|
85
81
|
|
|
86
82
|
A quick note on where this project stands, since I know people worry about this kind of thing: claude-code-rust is a terminal UI that I wrote from scratch in Rust. It is not a fork, copy or port of the latest Claude Code source leak -- it talks to Anthropic's official [Agent SDK](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/agent-sdk) as a runtime dependency instead, the same way any other third-party tool would. No Anthropic source code was read or used as reference at any point during development.
|
|
87
83
|
|
|
88
|
-
The project authenticates through your existing Claude Code account via the Agent SDK, and the Agent SDK's terms allow building on top of it. Billing, credits, limits, and overage behavior are controlled by Anthropic, including
|
|
84
|
+
The project authenticates through your existing Claude Code account via the Agent SDK, and the Agent SDK's terms allow building on top of it. Billing, credits, limits, and overage behavior are controlled by Anthropic, including any future changes Anthropic may make to how Agent SDK usage is metered. Other community projects do the same. As far as I can tell, using this project is fine -- but I am a single maintainer, not a lawyer. If anything changes on Anthropic's end, I will update this section and adjust the project accordingly.
|
|
89
85
|
|
|
90
86
|
This project's source code is licensed under [Apache-2.0](LICENSE). The Agent SDK itself is proprietary and governed by [Anthropic's Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms).
|
|
91
87
|
|
|
@@ -95,6 +95,13 @@ function expectEffortLevel(record, key, context) {
|
|
|
95
95
|
}
|
|
96
96
|
return value;
|
|
97
97
|
}
|
|
98
|
+
function expectRewindRestoreMode(record, key, context) {
|
|
99
|
+
const value = expectString(record, key, context);
|
|
100
|
+
if (value === "both" || value === "conversation" || value === "code") {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`${context}.${key} must be one of both, conversation, code`);
|
|
104
|
+
}
|
|
98
105
|
function expectNonEmptyStringOrNull(record, key, context) {
|
|
99
106
|
const value = record[key];
|
|
100
107
|
if (value === null) {
|
|
@@ -281,6 +288,19 @@ export function parseCommandEnvelope(line) {
|
|
|
281
288
|
command: "get_context_usage",
|
|
282
289
|
session_id: expectString(raw, "session_id", "get_context_usage"),
|
|
283
290
|
};
|
|
291
|
+
case "get_rewind_targets":
|
|
292
|
+
return {
|
|
293
|
+
command: "get_rewind_targets",
|
|
294
|
+
session_id: expectString(raw, "session_id", "get_rewind_targets"),
|
|
295
|
+
};
|
|
296
|
+
case "rewind":
|
|
297
|
+
return {
|
|
298
|
+
command: "rewind",
|
|
299
|
+
session_id: expectString(raw, "session_id", "rewind"),
|
|
300
|
+
target_user_message_id: expectString(raw, "target_user_message_id", "rewind"),
|
|
301
|
+
restore_mode: expectRewindRestoreMode(raw, "restore_mode", "rewind"),
|
|
302
|
+
launch_settings: optionalLaunchSettings(raw, "launch_settings", "rewind"),
|
|
303
|
+
};
|
|
284
304
|
case "reload_plugins":
|
|
285
305
|
return {
|
|
286
306
|
command: "reload_plugins",
|
|
@@ -6,7 +6,9 @@ import { resolveCurrentModel } from "./session_lifecycle.js";
|
|
|
6
6
|
const SESSION_LIST_LIMIT = 50;
|
|
7
7
|
let sessionListingDir;
|
|
8
8
|
export function buildSessionListOptions(dir, limit = SESSION_LIST_LIMIT) {
|
|
9
|
-
return dir
|
|
9
|
+
return dir
|
|
10
|
+
? { dir, includeProgrammatic: true, includeWorktrees: true, limit }
|
|
11
|
+
: { includeProgrammatic: true, limit };
|
|
10
12
|
}
|
|
11
13
|
export function setSessionListingDir(dir) {
|
|
12
14
|
sessionListingDir = dir;
|
|
@@ -121,6 +123,7 @@ function buildConnectBridgeEvent(session, eventName) {
|
|
|
121
123
|
available_models: session.availableModels,
|
|
122
124
|
mode: session.mode ? buildModeState(session, session.mode) : null,
|
|
123
125
|
...(historyUpdates && historyUpdates.length > 0 ? { history_updates: historyUpdates } : {}),
|
|
126
|
+
...(session.restoredInput !== undefined ? { restored_input: session.restoredInput } : {}),
|
|
124
127
|
}
|
|
125
128
|
: {
|
|
126
129
|
event: "connected",
|
|
@@ -144,6 +147,7 @@ function logConnectEventEmission(session, eventName, requestId) {
|
|
|
144
147
|
history_update_count: session.resumeUpdates?.length ?? 0,
|
|
145
148
|
available_model_count: session.availableModels.length,
|
|
146
149
|
stale_session_count: session.sessionsToCloseAfterConnect?.length ?? 0,
|
|
150
|
+
has_restored_input: session.restoredInput !== undefined,
|
|
147
151
|
},
|
|
148
152
|
});
|
|
149
153
|
}
|
|
@@ -151,10 +155,15 @@ export function emitConnectEvent(session) {
|
|
|
151
155
|
const bridgeEvent = buildConnectBridgeEvent(session, session.connectEvent);
|
|
152
156
|
logConnectEventEmission(session, session.connectEvent, session.connectRequestId);
|
|
153
157
|
writeEvent(bridgeEvent, session.connectRequestId);
|
|
158
|
+
if (session.pendingRewindResult) {
|
|
159
|
+
writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, session.connectRequestId);
|
|
160
|
+
session.pendingRewindResult = undefined;
|
|
161
|
+
}
|
|
154
162
|
session.connectRequestId = undefined;
|
|
155
163
|
session.connected = true;
|
|
156
164
|
session.authHintSent = false;
|
|
157
165
|
session.resumeUpdates = undefined;
|
|
166
|
+
session.restoredInput = undefined;
|
|
158
167
|
const staleSessions = session.sessionsToCloseAfterConnect;
|
|
159
168
|
session.sessionsToCloseAfterConnect = undefined;
|
|
160
169
|
if (!staleSessions || staleSessions.length === 0) {
|
|
@@ -180,7 +189,12 @@ export function emitSessionReplacedEvent(session, requestId) {
|
|
|
180
189
|
const bridgeEvent = buildConnectBridgeEvent(session, "session_replaced");
|
|
181
190
|
logConnectEventEmission(session, "session_replaced", requestId);
|
|
182
191
|
writeEvent(bridgeEvent, requestId);
|
|
192
|
+
if (session.pendingRewindResult) {
|
|
193
|
+
writeEvent({ ...session.pendingRewindResult, session_id: session.sessionId }, requestId);
|
|
194
|
+
session.pendingRewindResult = undefined;
|
|
195
|
+
}
|
|
183
196
|
session.resumeUpdates = undefined;
|
|
197
|
+
session.restoredInput = undefined;
|
|
184
198
|
refreshSessionsList();
|
|
185
199
|
}
|
|
186
200
|
export async function emitSessionsList(requestId) {
|
|
@@ -71,6 +71,10 @@ function commandSessionId(command) {
|
|
|
71
71
|
case "question_response":
|
|
72
72
|
case "elicitation_response":
|
|
73
73
|
case "get_status_snapshot":
|
|
74
|
+
case "get_context_usage":
|
|
75
|
+
case "get_rewind_targets":
|
|
76
|
+
case "rewind":
|
|
77
|
+
case "reload_plugins":
|
|
74
78
|
case "mcp_status":
|
|
75
79
|
case "mcp_reconnect":
|
|
76
80
|
case "mcp_toggle":
|
|
@@ -105,6 +109,8 @@ function commandToolCallId(command) {
|
|
|
105
109
|
case "elicitation_response":
|
|
106
110
|
case "get_status_snapshot":
|
|
107
111
|
case "get_context_usage":
|
|
112
|
+
case "get_rewind_targets":
|
|
113
|
+
case "rewind":
|
|
108
114
|
case "reload_plugins":
|
|
109
115
|
case "mcp_status":
|
|
110
116
|
case "mcp_reconnect":
|
|
@@ -141,6 +147,8 @@ function eventToolCallId(event) {
|
|
|
141
147
|
case "sessions_listed":
|
|
142
148
|
case "status_snapshot":
|
|
143
149
|
case "context_usage":
|
|
150
|
+
case "rewind_targets":
|
|
151
|
+
case "rewind_result":
|
|
144
152
|
case "mcp_snapshot":
|
|
145
153
|
return undefined;
|
|
146
154
|
}
|
|
@@ -183,6 +191,8 @@ function protocolEventLevel(event) {
|
|
|
183
191
|
case "sessions_listed":
|
|
184
192
|
case "status_snapshot":
|
|
185
193
|
case "context_usage":
|
|
194
|
+
case "rewind_targets":
|
|
195
|
+
case "rewind_result":
|
|
186
196
|
case "runtime_reload_completed":
|
|
187
197
|
case "mcp_set_servers_result":
|
|
188
198
|
case "mcp_snapshot":
|
|
@@ -90,6 +90,162 @@ function emitSystemNoticeUpdate(session, severity, message) {
|
|
|
90
90
|
}
|
|
91
91
|
emitSessionUpdate(session.sessionId, { type: "system_notice_update", severity, message: trimmed });
|
|
92
92
|
}
|
|
93
|
+
const MAX_INFORMATIONAL_DEDUP_KEYS = 256;
|
|
94
|
+
function shouldEmitInformationalMessage(session, level, content, toolUseId) {
|
|
95
|
+
if (!toolUseId) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const key = `${toolUseId}\u0000${level}\u0000${content}`;
|
|
99
|
+
if (session.informationalDedupKeys.has(key)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
session.informationalDedupKeys.add(key);
|
|
103
|
+
while (session.informationalDedupKeys.size > MAX_INFORMATIONAL_DEDUP_KEYS) {
|
|
104
|
+
const first = session.informationalDedupKeys.values().next().value;
|
|
105
|
+
if (typeof first !== "string") {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
session.informationalDedupKeys.delete(first);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function handleInformationalSystemMessage(session, msg) {
|
|
113
|
+
const content = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
114
|
+
if (!content) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const level = typeof msg.level === "string" ? msg.level : "info";
|
|
118
|
+
const toolUseId = typeof msg.tool_use_id === "string" ? msg.tool_use_id : "";
|
|
119
|
+
if (!shouldEmitInformationalMessage(session, level, content, toolUseId)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
switch (level) {
|
|
123
|
+
case "notice":
|
|
124
|
+
emitSystemNoticeUpdate(session, "info", content);
|
|
125
|
+
return;
|
|
126
|
+
case "suggestion":
|
|
127
|
+
emitSystemNoticeUpdate(session, "info", `Suggestion: ${content}`);
|
|
128
|
+
return;
|
|
129
|
+
case "warning":
|
|
130
|
+
emitSystemNoticeUpdate(session, "warning", content);
|
|
131
|
+
return;
|
|
132
|
+
case "info":
|
|
133
|
+
if (msg.prevent_continuation === true) {
|
|
134
|
+
emitSystemNoticeUpdate(session, "warning", content);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
default:
|
|
138
|
+
bridgeLogger.debug({
|
|
139
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
140
|
+
eventName: "sdk_informational_level_unhandled",
|
|
141
|
+
message: "SDK informational message ignored for unknown level",
|
|
142
|
+
outcome: "ignored",
|
|
143
|
+
sessionId: session.sessionId,
|
|
144
|
+
toolCallId: toolUseId || undefined,
|
|
145
|
+
fields: {
|
|
146
|
+
informational_level: level,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function trimmedStringField(msg, field) {
|
|
152
|
+
const value = msg[field];
|
|
153
|
+
if (typeof value !== "string") {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const trimmed = value.trim();
|
|
157
|
+
return trimmed ? trimmed : undefined;
|
|
158
|
+
}
|
|
159
|
+
function ensureSentencePunctuation(value) {
|
|
160
|
+
const trimmed = value.trim();
|
|
161
|
+
if (!trimmed) {
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
165
|
+
}
|
|
166
|
+
function modelRefusalNoFallbackMessage(msg) {
|
|
167
|
+
const model = trimmedStringField(msg, "original_model") ?? "the selected model";
|
|
168
|
+
const base = `Could not continue with ${model}: model refused the request and no fallback model is configured.`;
|
|
169
|
+
const explanation = trimmedStringField(msg, "api_refusal_explanation");
|
|
170
|
+
const category = trimmedStringField(msg, "api_refusal_category");
|
|
171
|
+
const content = trimmedStringField(msg, "content");
|
|
172
|
+
const detail = explanation
|
|
173
|
+
? `Reason: ${explanation}`
|
|
174
|
+
: category
|
|
175
|
+
? `Refusal category: ${category}`
|
|
176
|
+
: content;
|
|
177
|
+
const detailSentence = detail ? ensureSentencePunctuation(detail) : "";
|
|
178
|
+
return detailSentence ? `${base} ${detailSentence}` : base;
|
|
179
|
+
}
|
|
180
|
+
function handleModelRefusalNoFallbackMessage(session, msg) {
|
|
181
|
+
const message = modelRefusalNoFallbackMessage(msg);
|
|
182
|
+
emitSystemNoticeUpdate(session, "warning", message);
|
|
183
|
+
bridgeLogger.info({
|
|
184
|
+
target: LOG_TARGETS.APP_SESSION,
|
|
185
|
+
eventName: "sdk_model_refusal_no_fallback_received",
|
|
186
|
+
message: "SDK model refusal without fallback received",
|
|
187
|
+
outcome: "success",
|
|
188
|
+
sessionId: session.sessionId,
|
|
189
|
+
requestId: trimmedStringField(msg, "request_id"),
|
|
190
|
+
fields: {
|
|
191
|
+
original_model: trimmedStringField(msg, "original_model"),
|
|
192
|
+
api_refusal_category: trimmedStringField(msg, "api_refusal_category"),
|
|
193
|
+
refused_user_message_uuid: trimmedStringField(msg, "refused_user_message_uuid"),
|
|
194
|
+
sdk_message_uuid: trimmedStringField(msg, "uuid"),
|
|
195
|
+
sdk_message_session_id: trimmedStringField(msg, "session_id"),
|
|
196
|
+
has_api_refusal_explanation: trimmedStringField(msg, "api_refusal_explanation") !== undefined,
|
|
197
|
+
has_content: trimmedStringField(msg, "content") !== undefined,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function workerShutdownMessage(reason) {
|
|
202
|
+
const trimmed = reason.trim();
|
|
203
|
+
return trimmed ? `Claude worker is shutting down: ${trimmed}` : "Claude worker is shutting down.";
|
|
204
|
+
}
|
|
205
|
+
function handleWorkerShuttingDownSystemMessage(session, msg) {
|
|
206
|
+
const reason = typeof msg.reason === "string" ? msg.reason.trim() : "";
|
|
207
|
+
if (!session.connected) {
|
|
208
|
+
bridgeLogger.debug({
|
|
209
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
210
|
+
eventName: "sdk_worker_shutdown_preconnect_ignored",
|
|
211
|
+
message: "SDK worker shutdown ignored before session connect",
|
|
212
|
+
outcome: "ignored",
|
|
213
|
+
sessionId: session.sessionId,
|
|
214
|
+
fields: {
|
|
215
|
+
reason: reason || undefined,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
session.pendingWorkerShutdown = { reason };
|
|
221
|
+
}
|
|
222
|
+
function cancelPendingWorkerShutdown(session) {
|
|
223
|
+
if (!session.pendingWorkerShutdown) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
bridgeLogger.debug({
|
|
227
|
+
target: LOG_TARGETS.BRIDGE_SDK,
|
|
228
|
+
eventName: "sdk_worker_shutdown_cancelled",
|
|
229
|
+
message: "SDK worker shutdown ignored after later stream activity",
|
|
230
|
+
outcome: "ignored",
|
|
231
|
+
sessionId: session.sessionId,
|
|
232
|
+
fields: {
|
|
233
|
+
reason: session.pendingWorkerShutdown.reason || undefined,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
session.pendingWorkerShutdown = undefined;
|
|
237
|
+
}
|
|
238
|
+
export function flushPendingWorkerShutdown(session) {
|
|
239
|
+
const pending = session.pendingWorkerShutdown;
|
|
240
|
+
if (!pending) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
session.pendingWorkerShutdown = undefined;
|
|
244
|
+
if (!session.connected) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
emitSystemNoticeUpdate(session, "warning", workerShutdownMessage(pending.reason));
|
|
248
|
+
}
|
|
93
249
|
function notificationSeverity(priority) {
|
|
94
250
|
return priority === "high" || priority === "immediate" ? "warning" : "info";
|
|
95
251
|
}
|
|
@@ -675,12 +831,19 @@ function terminalReasonFromValue(value) {
|
|
|
675
831
|
export function handleSdkMessage(session, message) {
|
|
676
832
|
const msg = message;
|
|
677
833
|
const type = typeof msg.type === "string" ? msg.type : "";
|
|
834
|
+
const subtype = type === "system" && typeof msg.subtype === "string" ? msg.subtype : "";
|
|
835
|
+
if (subtype !== "worker_shutting_down") {
|
|
836
|
+
cancelPendingWorkerShutdown(session);
|
|
837
|
+
}
|
|
678
838
|
logSdkMessageOrigin(session, msg);
|
|
679
839
|
if (type === "system") {
|
|
680
|
-
const subtype = typeof msg.subtype === "string" ? msg.subtype : "";
|
|
681
840
|
if (handleFallbackRetractionMessage(session, subtype, msg)) {
|
|
682
841
|
return;
|
|
683
842
|
}
|
|
843
|
+
if (subtype === "model_refusal_no_fallback") {
|
|
844
|
+
handleModelRefusalNoFallbackMessage(session, msg);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
684
847
|
if (subtype === "commands_changed") {
|
|
685
848
|
updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
|
|
686
849
|
return;
|
|
@@ -690,6 +853,14 @@ export function handleSdkMessage(session, message) {
|
|
|
690
853
|
emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
|
|
691
854
|
return;
|
|
692
855
|
}
|
|
856
|
+
if (subtype === "informational") {
|
|
857
|
+
handleInformationalSystemMessage(session, msg);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (subtype === "worker_shutting_down") {
|
|
861
|
+
handleWorkerShuttingDownSystemMessage(session, msg);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
693
864
|
if (subtype === "mirror_error") {
|
|
694
865
|
const error = typeof msg.error === "string" ? msg.error : "";
|
|
695
866
|
const key = asRecordOrNull(msg.key);
|
|
@@ -162,6 +162,23 @@ export async function closeSessionWithLogging(session, options = {}) {
|
|
|
162
162
|
fields: { reason: options.reason ?? "unspecified" },
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
+
export async function closeSessionsBeforeRegister(replacementSession, staleSessions, requestId) {
|
|
166
|
+
if (!staleSessions || staleSessions.length === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const stale of staleSessions) {
|
|
170
|
+
if (stale === replacementSession) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (sessions.get(stale.sessionId) === stale) {
|
|
174
|
+
sessions.delete(stale.sessionId);
|
|
175
|
+
}
|
|
176
|
+
await closeSessionWithLogging(stale, {
|
|
177
|
+
reason: "stale_before_register",
|
|
178
|
+
requestId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
165
182
|
export async function closeAllSessions(options = {}) {
|
|
166
183
|
const active = Array.from(sessions.values());
|
|
167
184
|
sessions.clear();
|
|
@@ -187,6 +204,7 @@ export async function createSession(params) {
|
|
|
187
204
|
const supportsBypassPermissionsMode = startupPermissionModeOptions(params.launchSettings).allowDangerouslySkipPermissions === true;
|
|
188
205
|
const historyUpdateCount = params.resumeUpdates?.length ?? 0;
|
|
189
206
|
const staleSessionCount = params.sessionsToCloseAfterConnect?.length ?? 0;
|
|
207
|
+
const staleSessionBeforeRegisterCount = params.sessionsToCloseBeforeRegister?.length ?? 0;
|
|
190
208
|
let session;
|
|
191
209
|
const sessionIdForLogs = () => session?.sessionId ?? provisionalSessionId;
|
|
192
210
|
const canUseTool = async (toolName, inputData, options) => {
|
|
@@ -253,8 +271,10 @@ export async function createSession(params) {
|
|
|
253
271
|
cwd: params.cwd,
|
|
254
272
|
connect_event: params.connectEvent,
|
|
255
273
|
resume_requested: params.resume !== undefined,
|
|
274
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
256
275
|
history_update_count: historyUpdateCount,
|
|
257
276
|
stale_session_count: staleSessionCount,
|
|
277
|
+
stale_session_before_register_count: staleSessionBeforeRegisterCount,
|
|
258
278
|
},
|
|
259
279
|
});
|
|
260
280
|
try {
|
|
@@ -263,6 +283,7 @@ export async function createSession(params) {
|
|
|
263
283
|
options: buildQueryOptions({
|
|
264
284
|
cwd: params.cwd,
|
|
265
285
|
resume: params.resume,
|
|
286
|
+
resumeSessionAt: params.resumeSessionAt,
|
|
266
287
|
launchSettings: params.launchSettings,
|
|
267
288
|
provisionalSessionId,
|
|
268
289
|
input,
|
|
@@ -287,6 +308,7 @@ export async function createSession(params) {
|
|
|
287
308
|
fields: {
|
|
288
309
|
cwd: params.cwd,
|
|
289
310
|
resume_requested: params.resume !== undefined,
|
|
311
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
290
312
|
error_message: message,
|
|
291
313
|
},
|
|
292
314
|
});
|
|
@@ -319,12 +341,17 @@ export async function createSession(params) {
|
|
|
319
341
|
pendingQuestions: new Map(),
|
|
320
342
|
pendingUserDialogs: new Map(),
|
|
321
343
|
pendingElicitations: new Map(),
|
|
344
|
+
informationalDedupKeys: new Set(),
|
|
322
345
|
mcpStatusRevalidatedAt: new Map(),
|
|
323
346
|
hiddenToolUseIds: new Set(),
|
|
324
347
|
authHintSent: false,
|
|
325
348
|
...(params.resumeUpdates && params.resumeUpdates.length > 0
|
|
326
349
|
? { resumeUpdates: params.resumeUpdates }
|
|
327
350
|
: {}),
|
|
351
|
+
...(params.restoredInput !== undefined ? { restoredInput: params.restoredInput } : {}),
|
|
352
|
+
...(params.pendingRewindResult !== undefined
|
|
353
|
+
? { pendingRewindResult: params.pendingRewindResult }
|
|
354
|
+
: {}),
|
|
328
355
|
...(params.sessionsToCloseAfterConnect
|
|
329
356
|
? { sessionsToCloseAfterConnect: params.sessionsToCloseAfterConnect }
|
|
330
357
|
: {}),
|
|
@@ -332,6 +359,7 @@ export async function createSession(params) {
|
|
|
332
359
|
refreshCurrentModel(session);
|
|
333
360
|
const { refreshSupportedModesForSession } = await import("./commands.js");
|
|
334
361
|
refreshSupportedModesForSession(session);
|
|
362
|
+
await closeSessionsBeforeRegister(session, params.sessionsToCloseBeforeRegister, params.requestId);
|
|
335
363
|
sessions.set(provisionalSessionId, session);
|
|
336
364
|
bridgeLogger.info({
|
|
337
365
|
target: LOG_TARGETS.APP_SESSION,
|
|
@@ -344,6 +372,7 @@ export async function createSession(params) {
|
|
|
344
372
|
cwd: session.cwd,
|
|
345
373
|
connect_event: session.connectEvent,
|
|
346
374
|
resume_requested: params.resume !== undefined,
|
|
375
|
+
resume_session_at: params.resumeSessionAt ?? "<none>",
|
|
347
376
|
},
|
|
348
377
|
});
|
|
349
378
|
bridgeLogger.info({
|
|
@@ -430,6 +459,11 @@ export async function createSession(params) {
|
|
|
430
459
|
const { handleSdkMessage } = await import("./message_handlers.js");
|
|
431
460
|
handleSdkMessage(session, message);
|
|
432
461
|
}
|
|
462
|
+
{
|
|
463
|
+
// Lazy import to break circular dependency at module-evaluation time.
|
|
464
|
+
const { flushPendingWorkerShutdown } = await import("./message_handlers.js");
|
|
465
|
+
flushPendingWorkerShutdown(session);
|
|
466
|
+
}
|
|
433
467
|
if (!session.connected) {
|
|
434
468
|
bridgeLogger.error({
|
|
435
469
|
target: LOG_TARGETS.APP_SESSION,
|
|
@@ -456,6 +490,7 @@ export async function createSession(params) {
|
|
|
456
490
|
failConnection(`agent stream failed: ${message}`, params.requestId);
|
|
457
491
|
}
|
|
458
492
|
})();
|
|
493
|
+
return session;
|
|
459
494
|
}
|
|
460
495
|
function logSdkProcessSpawnStarted(options, includeArgsPreview) {
|
|
461
496
|
bridgeLogger.info({
|
|
@@ -570,6 +605,7 @@ export function buildQueryOptions(params) {
|
|
|
570
605
|
cwd: params.cwd,
|
|
571
606
|
includePartialMessages: true,
|
|
572
607
|
promptSuggestions: true,
|
|
608
|
+
enableFileCheckpointing: true,
|
|
573
609
|
executable: "node",
|
|
574
610
|
...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
|
|
575
611
|
...(settings ? { settings } : {}),
|
|
@@ -621,6 +657,7 @@ export function buildQueryOptions(params) {
|
|
|
621
657
|
// --setting-sources argument.
|
|
622
658
|
settingSources: DEFAULT_SETTING_SOURCES,
|
|
623
659
|
resume: params.resume,
|
|
660
|
+
...(params.resumeSessionAt ? { resumeSessionAt: params.resumeSessionAt } : {}),
|
|
624
661
|
canUseTool: params.canUseTool,
|
|
625
662
|
onElicitation: async (request) => {
|
|
626
663
|
const requestId = randomUUID();
|
|
@@ -62,6 +62,9 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
62
62
|
type: "rate_limit_update",
|
|
63
63
|
status,
|
|
64
64
|
};
|
|
65
|
+
if (info.errorCode === "credits_required") {
|
|
66
|
+
update.error_code = "credits_required";
|
|
67
|
+
}
|
|
65
68
|
const resetsAt = numberField(info, "resetsAt");
|
|
66
69
|
if (resetsAt !== undefined) {
|
|
67
70
|
update.resets_at = resetsAt;
|
|
@@ -94,6 +97,12 @@ export function buildRateLimitUpdate(rateLimitInfo) {
|
|
|
94
97
|
if (surpassedThreshold !== undefined) {
|
|
95
98
|
update.surpassed_threshold = surpassedThreshold;
|
|
96
99
|
}
|
|
100
|
+
if (typeof info.canUserPurchaseCredits === "boolean") {
|
|
101
|
+
update.can_user_purchase_credits = info.canUserPurchaseCredits;
|
|
102
|
+
}
|
|
103
|
+
if (typeof info.hasChargeableSavedPaymentMethod === "boolean") {
|
|
104
|
+
update.has_chargeable_saved_payment_method = info.hasChargeableSavedPaymentMethod;
|
|
105
|
+
}
|
|
97
106
|
return update;
|
|
98
107
|
}
|
|
99
108
|
export function buildApiRetryUpdate(message) {
|