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 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
- > [!WARNING]
42
- > **Agent SDK billing changes on June 15, 2026.** Anthropic says Agent SDK usage, `claude -p`, Claude Code GitHub Actions, and third-party Agent SDK apps will use a separate monthly Agent SDK credit instead of normal interactive Claude or Claude Code subscription limits. Because Claude Code Rust wraps the Agent SDK, treat usage through this project as Agent SDK usage. If that credit is exhausted, continued use may require enabling extra usage billed at standard API rates, or requests may pause until the credit refreshes.
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 the Agent SDK credit change noted above. 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.
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 ? { dir, includeWorktrees: true, limit } : { limit };
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) {