@kenkaiiii/gg-ai 5.83.2 → 5.83.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/dist/index.js CHANGED
@@ -121,6 +121,14 @@ var VideoUnsupportedError = class extends GGAIError {
121
121
  this.name = "VideoUnsupportedError";
122
122
  }
123
123
  };
124
+ var ResponseStalledError = class extends GGAIError {
125
+ inToolCall;
126
+ constructor(message, options) {
127
+ super(message, { source: "network", hint: options.hint, cause: options.cause });
128
+ this.name = "ResponseStalledError";
129
+ this.inToolCall = options.inToolCall;
130
+ }
131
+ };
124
132
  var ProviderError = class extends GGAIError {
125
133
  provider;
126
134
  statusCode;
@@ -256,6 +264,14 @@ function formatError(err) {
256
264
  guidance: err.hint ?? providerGuidance(err.provider, cleanMessage, err.statusCode)
257
265
  };
258
266
  }
267
+ if (err instanceof ResponseStalledError) {
268
+ return {
269
+ headline: "The response kept stalling before it finished.",
270
+ source: err.source,
271
+ message: err.message,
272
+ guidance: err.hint ?? "Retry once. If it keeps happening, choose another model."
273
+ };
274
+ }
259
275
  if (err instanceof GGAIError) {
260
276
  return finaliseBySource(err.source, err.message, err.requestId, err.hint);
261
277
  }
@@ -1667,9 +1683,43 @@ function parseToolArguments(argsJson) {
1667
1683
  }
1668
1684
  }
1669
1685
 
1686
+ // src/utils/provider-fetch.ts
1687
+ import { Agent, EnvHttpProxyAgent, fetch as undiciFetch } from "undici";
1688
+ var PROVIDER_TRANSPORT_TIMEOUT_MS = 30 * 6e4;
1689
+ function nodeUsesEnvProxy({
1690
+ env = process.env,
1691
+ execArgv = process.execArgv,
1692
+ nodeVersion = process.versions.node
1693
+ } = {}) {
1694
+ const flags = [...execArgv, ...(env.NODE_OPTIONS ?? "").split(/\s+/)];
1695
+ if (flags.includes("--use-env-proxy")) return true;
1696
+ if (env.NODE_USE_ENV_PROXY !== "1") return false;
1697
+ const [major = 0, minor = 0] = nodeVersion.split(".").map(Number);
1698
+ return major >= 24 || major === 22 && minor >= 21;
1699
+ }
1700
+ function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {}) {
1701
+ const timeouts = { headersTimeout: timeoutMs, bodyTimeout: timeoutMs };
1702
+ const dispatcher = useEnvProxy ? new EnvHttpProxyAgent(timeouts) : new Agent(timeouts);
1703
+ const builtinFetch = globalThis.fetch;
1704
+ const providerFetch2 = async (input, init2) => {
1705
+ if (globalThis.fetch !== builtinFetch || input instanceof Request || init2?.body instanceof FormData) {
1706
+ return globalThis.fetch(input, init2);
1707
+ }
1708
+ const response = await undiciFetch(input, {
1709
+ ...init2,
1710
+ dispatcher
1711
+ });
1712
+ return response;
1713
+ };
1714
+ return Object.assign(providerFetch2, { Response: globalThis.Response });
1715
+ }
1716
+ var providerFetch = createProviderFetch(
1717
+ PROVIDER_TRANSPORT_TIMEOUT_MS
1718
+ );
1719
+
1670
1720
  // src/providers/anthropic.ts
1671
1721
  var anthropicClientCache = /* @__PURE__ */ new Map();
1672
- var NON_STREAMING_REQUEST_TIMEOUT_MS = 6e5;
1722
+ var NON_STREAMING_REQUEST_TIMEOUT_MS = PROVIDER_TRANSPORT_TIMEOUT_MS;
1673
1723
  function fineGrainedToolStreamingEnabled() {
1674
1724
  const raw = process.env.GG_FINE_GRAINED_TOOL_STREAMING ?? process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING;
1675
1725
  if (!raw) return false;
@@ -1730,7 +1780,8 @@ function createClient(options) {
1730
1780
  const client = new Anthropic({
1731
1781
  ...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
1732
1782
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1733
- ...options.fetch ? { fetch: options.fetch } : {},
1783
+ // Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
1784
+ fetch: options.fetch ?? providerFetch,
1734
1785
  maxRetries: 0,
1735
1786
  ...isOAuth ? {
1736
1787
  defaultHeaders: {
@@ -2508,7 +2559,8 @@ function createClient2(options) {
2508
2559
  const client = new OpenAI({
2509
2560
  apiKey: options.apiKey,
2510
2561
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
2511
- ...options.fetch ? { fetch: options.fetch } : {},
2562
+ // Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
2563
+ fetch: options.fetch ?? providerFetch,
2512
2564
  ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
2513
2565
  });
2514
2566
  if (!options.fetch) {
@@ -2630,16 +2682,13 @@ async function* runStream2(options) {
2630
2682
  `
2631
2683
  );
2632
2684
  }
2633
- const requestHeaders = options.provider === "xai" ? {
2634
- headers: {
2635
- "x-grok-conv-id": normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder")
2636
- }
2637
- } : {};
2638
2685
  if (!useStreaming) {
2639
2686
  try {
2640
2687
  const completion = await client.chat.completions.create(requestParams, {
2641
2688
  signal: options.signal ?? void 0,
2642
- ...requestHeaders
2689
+ // The whole reply arrives in one response, so the SDK's 10min default
2690
+ // would undercut the agent loop's 15min thinking cap for this call.
2691
+ timeout: PROVIDER_TRANSPORT_TIMEOUT_MS
2643
2692
  });
2644
2693
  yield* synthesizeEventsFromCompletion(completion, !!options.thinking, endpointKey);
2645
2694
  return completionToResponse(completion, endpointKey);
@@ -2650,8 +2699,7 @@ async function* runStream2(options) {
2650
2699
  let stream2;
2651
2700
  try {
2652
2701
  stream2 = await client.chat.completions.create(requestParams, {
2653
- signal: options.signal ?? void 0,
2654
- ...requestHeaders
2702
+ signal: options.signal ?? void 0
2655
2703
  });
2656
2704
  } catch (err) {
2657
2705
  throw toError2(err, providerName);
@@ -2692,6 +2740,8 @@ async function* runStream2(options) {
2692
2740
  thinkingAccum += reasoning.text;
2693
2741
  if (options.thinking) {
2694
2742
  yield { type: "thinking_delta", text: reasoning.text };
2743
+ } else {
2744
+ yield { type: "keepalive", reasoning: true };
2695
2745
  }
2696
2746
  }
2697
2747
  if (delta.content) {
@@ -3010,55 +3060,9 @@ function extractRequestIdFromMessage(message) {
3010
3060
  return match?.[1];
3011
3061
  }
3012
3062
 
3013
- // src/providers/openai-codex.ts
3014
- var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
3015
- var CODEX_CLIENT_VERSION = "0.161.0";
3016
- var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
3017
- var zstdInitPromise;
3018
- async function encodeCodexRequest(body) {
3019
- const json = JSON.stringify(body);
3020
- const raw = new TextEncoder().encode(json);
3021
- if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
3022
- return {
3023
- body: json,
3024
- compressed: false,
3025
- rawBytes: raw.byteLength,
3026
- encodedBytes: raw.byteLength
3027
- };
3028
- }
3029
- try {
3030
- zstdInitPromise ??= zstd.init();
3031
- await zstdInitPromise;
3032
- const compressed = Uint8Array.from(zstd.compress(raw));
3033
- if (compressed.byteLength >= raw.byteLength) {
3034
- return {
3035
- body: json,
3036
- compressed: false,
3037
- rawBytes: raw.byteLength,
3038
- encodedBytes: raw.byteLength
3039
- };
3040
- }
3041
- return {
3042
- body: compressed,
3043
- compressed: true,
3044
- rawBytes: raw.byteLength,
3045
- encodedBytes: compressed.byteLength
3046
- };
3047
- } catch (error) {
3048
- providerDiag("codex_request_compression_failed", {
3049
- error: error instanceof Error ? error.message : String(error),
3050
- rawBytes: raw.byteLength
3051
- });
3052
- return {
3053
- body: json,
3054
- compressed: false,
3055
- rawBytes: raw.byteLength,
3056
- encodedBytes: raw.byteLength
3057
- };
3058
- }
3059
- }
3060
- function usesResponsesLite(model) {
3061
- return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
3063
+ // src/providers/responses-stream.ts
3064
+ function isEncryptedReasoning(data) {
3065
+ return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
3062
3066
  }
3063
3067
  function outputTextKey(itemId, contentIndex) {
3064
3068
  return `${itemId ?? ""}:${contentIndex ?? 0}`;
@@ -3066,132 +3070,7 @@ function outputTextKey(itemId, contentIndex) {
3066
3070
  function isVisibleOutputItem(itemType) {
3067
3071
  return itemType === "message";
3068
3072
  }
3069
- function toCodexToolChoice(choice, tools) {
3070
- const resolved = choice ?? "auto";
3071
- if (typeof resolved === "object") {
3072
- throw new GGAIError(
3073
- `OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
3074
- { source: "capability" }
3075
- );
3076
- }
3077
- if (resolved === "required" && !tools?.length) {
3078
- throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
3079
- source: "capability"
3080
- });
3081
- }
3082
- return resolved;
3083
- }
3084
- function streamOpenAICodex(options) {
3085
- return new StreamResult(runStream3(options), options.signal);
3086
- }
3087
- async function* runStream3(options, retriedWithoutReasoning = false) {
3088
- const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
3089
- const url = `${baseUrl}/codex/responses`;
3090
- const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3091
- const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
3092
- const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
3093
- const responsesLite = usesResponsesLite(options.model);
3094
- const liteShape = options.responsesLite ?? responsesLite;
3095
- const body = {
3096
- model: options.model,
3097
- store: false,
3098
- stream: true,
3099
- instructions: system,
3100
- input,
3101
- tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
3102
- parallel_tool_calls: !liteShape,
3103
- include: ["reasoning.encrypted_content"]
3104
- };
3105
- if (options.tools?.length) {
3106
- body.tools = toCodexTools(options.tools, options.strictTools ?? true);
3107
- }
3108
- body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
3109
- if (options.temperature != null && !options.thinking) {
3110
- body.temperature = options.temperature;
3111
- }
3112
- if (options.speed === "fast") {
3113
- body.service_tier = "priority";
3114
- }
3115
- body.reasoning = {
3116
- // GPT-5.6/6 require at least low; older models still support thinking off.
3117
- // Apply the floor here for every caller, including one-off prompt rewrites.
3118
- // `ultra` is a client orchestration preset, not a Codex API effort.
3119
- effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : responsesLite ? "low" : "none",
3120
- summary: "auto",
3121
- ...liteShape ? { context: "all_turns" } : {}
3122
- };
3123
- if (responsesLite) {
3124
- body.text = { verbosity: "low" };
3125
- }
3126
- const headers = {
3127
- "Content-Type": "application/json",
3128
- Accept: "text/event-stream",
3129
- Authorization: `Bearer ${options.apiKey}`,
3130
- "OpenAI-Beta": "responses=experimental",
3131
- originator: responsesLite ? "codex_cli_rs" : "ggcoder",
3132
- "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${os.platform()} ${os.release()}; ${os.arch()})`,
3133
- ...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
3134
- ...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
3135
- };
3136
- if (options.accountId) {
3137
- headers["chatgpt-account-id"] = options.accountId;
3138
- }
3139
- if (options.transportSessionId) {
3140
- const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
3141
- headers["session_id"] = transportSessionId;
3142
- headers["x-client-request-id"] = transportSessionId;
3143
- }
3144
- const encodedRequest = await encodeCodexRequest(
3145
- mergeProviderOptions(body, options.providerOptions)
3146
- );
3147
- if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
3148
- providerDiag("codex_request_body", {
3149
- rawBytes: encodedRequest.rawBytes,
3150
- encodedBytes: encodedRequest.encodedBytes,
3151
- compressed: encodedRequest.compressed
3152
- });
3153
- const response = await fetch(url, {
3154
- method: "POST",
3155
- headers,
3156
- body: encodedRequest.body,
3157
- signal: options.signal
3158
- });
3159
- if (!response.ok) {
3160
- const text = await response.text().catch(() => "");
3161
- const parsed = parseCodexErrorBody(text, response.status);
3162
- const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
3163
- const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
3164
- if (!retriedWithoutReasoning && !options.signal?.aborted && response.status === 400 && (parsed.errorObj?.code === "invalid_encrypted_content" || /encrypted content.*could not be (?:verified|decrypted|parsed)/i.test(message)) && options.messages.some(
3165
- (msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
3166
- )) {
3167
- providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
3168
- const messages = options.messages.map(
3169
- (msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
3170
- ...msg,
3171
- content: msg.content.filter(
3172
- (part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
3173
- )
3174
- } : msg
3175
- );
3176
- return yield* runStream3({ ...options, messages }, true);
3177
- }
3178
- const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
3179
- if (usageLimit) throw usageLimit;
3180
- let hint;
3181
- if (response.status === 400 && message === `The '${options.model}' model is not supported when using Codex with a ChatGPT account.`) {
3182
- hint = "This model is not available through your ChatGPT account. Choose another available model using the model selector.";
3183
- } else if (response.status === 404 && text.includes("does not exist")) {
3184
- hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
3185
- }
3186
- throw new ProviderError("openai", message, {
3187
- statusCode: response.status,
3188
- ...requestId ? { requestId } : {},
3189
- ...hint ? { hint } : {}
3190
- });
3191
- }
3192
- if (!response.body) {
3193
- throw new ProviderError("openai", "No response body from Codex API");
3194
- }
3073
+ async function* consumeResponsesEvents(events, ctx) {
3195
3074
  const contentParts = [];
3196
3075
  let textAccum = "";
3197
3076
  const toolCalls = /* @__PURE__ */ new Map();
@@ -3208,34 +3087,30 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3208
3087
  let thinkingTextEmitted = false;
3209
3088
  const diagStart = Date.now();
3210
3089
  const diagSeen = /* @__PURE__ */ new Set();
3211
- for await (const event of parseSSE(response.body)) {
3090
+ for await (const event of events) {
3212
3091
  const type = event.type;
3213
3092
  if (!type) continue;
3214
3093
  if (!diagSeen.has(type)) {
3215
3094
  diagSeen.add(type);
3216
- providerDiag("codex_event_first", { type, sinceStartMs: Date.now() - diagStart });
3095
+ providerDiag(`${ctx.diagPrefix}_event_first`, { type, sinceStartMs: Date.now() - diagStart });
3217
3096
  }
3218
3097
  if (type === "error") {
3219
3098
  const nested = event.error ?? void 0;
3220
- const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
3099
+ const message = nested?.message ?? event.message ?? `${ctx.label} stream emitted an error chunk without a message.`;
3221
3100
  const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
3222
3101
  const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
3223
- const usageLimit = codexUsageLimitError(
3224
- nested ?? event,
3225
- void 0,
3226
- requestId
3227
- );
3102
+ const usageLimit = ctx.errorChunk?.(nested ?? event, requestId);
3228
3103
  if (usageLimit) throw usageLimit;
3229
- throw new ProviderError("openai", message, {
3104
+ throw new ProviderError(ctx.provider, message, {
3230
3105
  ...requestId != null ? { requestId } : {},
3231
3106
  ...code === "server_error" ? { statusCode: 500 } : {}
3232
3107
  });
3233
3108
  }
3234
3109
  if (type === "response.failed") {
3235
3110
  const nested = event.error;
3236
- const message = nested?.message ?? "Codex response failed.";
3111
+ const message = nested?.message ?? `${ctx.label} response failed.`;
3237
3112
  const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
3238
- throw new ProviderError("openai", message, {
3113
+ throw new ProviderError(ctx.provider, message, {
3239
3114
  ...requestId != null ? { requestId } : {}
3240
3115
  });
3241
3116
  }
@@ -3284,15 +3159,17 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3284
3159
  }
3285
3160
  }
3286
3161
  if (type === "response.reasoning_summary_part.added") {
3287
- if (options.thinking && thinkingTextEmitted) {
3162
+ if (ctx.thinking && thinkingTextEmitted) {
3288
3163
  yield { type: "thinking_delta", text: "\n\n" };
3289
3164
  }
3290
3165
  }
3291
3166
  if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
3292
3167
  const delta = event.delta;
3293
- if (options.thinking && delta) {
3168
+ if (ctx.thinking && delta) {
3294
3169
  thinkingTextEmitted = true;
3295
3170
  yield { type: "thinking_delta", text: delta };
3171
+ } else if (delta) {
3172
+ yield { type: "keepalive", reasoning: true };
3296
3173
  }
3297
3174
  }
3298
3175
  if (type === "response.output_item.added") {
@@ -3302,8 +3179,8 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3302
3179
  if (itemId && itemType) {
3303
3180
  outputItemTypes.set(itemId, itemType);
3304
3181
  }
3305
- if (itemType === "reasoning" && options.thinking) {
3306
- yield { type: "thinking_delta", text: "" };
3182
+ if (itemType === "reasoning") {
3183
+ yield ctx.thinking ? { type: "thinking_delta", text: "" } : { type: "keepalive", reasoning: true };
3307
3184
  }
3308
3185
  if (itemId && itemType) {
3309
3186
  const pending = [...pendingOutputTextByPart.entries()].filter(([, pendingPart]) => pendingPart.itemId === itemId).sort(([, a], [, b]) => a.contentIndex - b.contentIndex);
@@ -3364,7 +3241,8 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3364
3241
  kind: "reasoning",
3365
3242
  part: {
3366
3243
  type: "raw",
3367
- data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] }
3244
+ data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] },
3245
+ ...ctx.reasoningOrigin ? { origin: ctx.reasoningOrigin } : {}
3368
3246
  }
3369
3247
  });
3370
3248
  }
@@ -3373,6 +3251,13 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3373
3251
  const callId = item.call_id;
3374
3252
  const itemId = item.id;
3375
3253
  const id = `${callId}|${itemId}`;
3254
+ if (!toolCalls.has(id) && typeof item.name === "string") {
3255
+ toolCalls.set(id, {
3256
+ id,
3257
+ name: item.name,
3258
+ argsJson: typeof item.arguments === "string" ? item.arguments : ""
3259
+ });
3260
+ }
3376
3261
  const tc = toolCalls.get(id);
3377
3262
  if (tc) {
3378
3263
  finishedToolCalls.add(id);
@@ -3398,6 +3283,23 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3398
3283
  } else {
3399
3284
  terminal = { status: "completed" };
3400
3285
  }
3286
+ if (Array.isArray(resp?.output)) {
3287
+ for (const raw of resp.output) {
3288
+ const item = raw;
3289
+ if (typeof item?.id !== "string" || typeof item.type !== "string") continue;
3290
+ if (!outputItemTypes.has(item.id)) outputItemTypes.set(item.id, item.type);
3291
+ }
3292
+ const pending = [...pendingOutputTextByPart.entries()].sort(
3293
+ ([, a], [, b]) => a.contentIndex - b.contentIndex
3294
+ );
3295
+ for (const [key, pendingPart] of pending) {
3296
+ if (!isVisibleOutputItem(outputItemTypes.get(pendingPart.itemId))) continue;
3297
+ pendingOutputTextByPart.delete(key);
3298
+ if (!pendingPart.text) continue;
3299
+ textAccum += pendingPart.text;
3300
+ yield { type: "text_delta", text: pendingPart.text };
3301
+ }
3302
+ }
3401
3303
  const usage = resp?.usage;
3402
3304
  if (usage) {
3403
3305
  cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
@@ -3408,23 +3310,30 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3408
3310
  }
3409
3311
  }
3410
3312
  if (!terminal) {
3411
- throw new ProviderError("openai", "Stream ended before completion (no response.completed).", {
3412
- statusCode: 504
3413
- });
3313
+ throw new ProviderError(
3314
+ ctx.provider,
3315
+ "Stream ended before completion (no response.completed).",
3316
+ {
3317
+ statusCode: 504
3318
+ }
3319
+ );
3414
3320
  }
3415
3321
  const droppedToolCalls = [...toolCalls.keys()].filter((id) => !finishedToolCalls.has(id)).length;
3416
3322
  if (terminal.status === "completed") {
3417
3323
  for (const [id, tc] of toolCalls) {
3418
3324
  if (!finishedToolCalls.has(id)) {
3419
3325
  throw new ProviderError(
3420
- "openai",
3421
- `Codex reply completed with an unfinished tool call: ${tc.name} (${id}).`,
3326
+ ctx.provider,
3327
+ `${ctx.label} reply completed with an unfinished tool call: ${tc.name} (${id}).`,
3422
3328
  { statusCode: 502 }
3423
3329
  );
3424
3330
  }
3425
3331
  }
3426
3332
  } else {
3427
- providerDiag("codex_incomplete", { reason: terminal.reason ?? null, droppedToolCalls });
3333
+ providerDiag(`${ctx.diagPrefix}_incomplete`, {
3334
+ reason: terminal.reason ?? null,
3335
+ droppedToolCalls
3336
+ });
3428
3337
  }
3429
3338
  const seenTool = /* @__PURE__ */ new Set();
3430
3339
  let textInserted = false;
@@ -3491,34 +3400,14 @@ function incompleteStopReason(reason) {
3491
3400
  if (reason === "content_filter") return "refusal";
3492
3401
  return "error";
3493
3402
  }
3494
- async function* parseSSE(body) {
3495
- for await (const event of readSseStream(body)) {
3496
- const data = event.data.trim();
3497
- if (!data || data === "[DONE]") continue;
3498
- try {
3499
- yield JSON.parse(data);
3500
- } catch {
3501
- }
3502
- }
3503
- }
3504
- function remapCodexId(id, idMap) {
3505
- const existing = idMap.get(id);
3506
- if (existing) return existing;
3507
- const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
3508
- const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
3509
- let mapped = sanitized;
3510
- let suffix = 2;
3511
- const used = new Set(idMap.values());
3512
- while (used.has(mapped)) {
3513
- mapped = `${sanitized}_${suffix++}`;
3514
- }
3515
- idMap.set(id, mapped);
3516
- return mapped;
3517
- }
3518
- function isEncryptedReasoning(data) {
3519
- return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
3520
- }
3521
- function toCodexInput(messages, options) {
3403
+ function toResponsesInput(messages, options) {
3404
+ const mapId = options.mapId ?? ((id) => id);
3405
+ const assistantText = (text) => options.minimalAssistantItems ? { role: "assistant", content: text } : {
3406
+ type: "message",
3407
+ role: "assistant",
3408
+ content: [{ type: "output_text", text, annotations: [] }],
3409
+ status: "completed"
3410
+ };
3522
3411
  let system;
3523
3412
  const input = [];
3524
3413
  const idMap = /* @__PURE__ */ new Map();
@@ -3541,30 +3430,23 @@ function toCodexInput(messages, options) {
3541
3430
  }
3542
3431
  if (msg.role === "assistant") {
3543
3432
  if (typeof msg.content === "string") {
3544
- input.push({
3545
- type: "message",
3546
- role: "assistant",
3547
- content: [{ type: "output_text", text: msg.content, annotations: [] }],
3548
- status: "completed"
3549
- });
3433
+ input.push(assistantText(msg.content));
3550
3434
  continue;
3551
3435
  }
3552
3436
  for (const part of msg.content) {
3553
3437
  if (part.type === "raw" && isEncryptedReasoning(part.data)) {
3438
+ if (!options.keepReasoning(part)) continue;
3554
3439
  input.push(part.data);
3555
3440
  } else if (part.type === "text") {
3556
- input.push({
3557
- type: "message",
3558
- role: "assistant",
3559
- content: [{ type: "output_text", text: part.text, annotations: [] }],
3560
- status: "completed"
3561
- });
3441
+ input.push(assistantText(part.text));
3562
3442
  } else if (part.type === "tool_call") {
3563
- const [callId, itemId] = part.id.includes("|") ? part.id.split("|", 2) : [part.id, part.id];
3443
+ const native = part.id.includes("|");
3444
+ const [callId, itemId] = native ? part.id.split("|", 2) : [part.id, part.id];
3445
+ const sendItemId = !options.minimalAssistantItems || native && !!itemId && itemId !== "undefined";
3564
3446
  input.push({
3565
3447
  type: "function_call",
3566
- id: remapCodexId(itemId, idMap),
3567
- call_id: remapCodexId(callId, idMap),
3448
+ ...sendItemId ? { id: mapId(itemId ?? callId, idMap) } : {},
3449
+ call_id: mapId(callId, idMap),
3568
3450
  name: part.name,
3569
3451
  arguments: JSON.stringify(part.args)
3570
3452
  });
@@ -3579,10 +3461,10 @@ function toCodexInput(messages, options) {
3579
3461
  const text = toolResultText(result.content);
3580
3462
  input.push({
3581
3463
  type: "function_call_output",
3582
- call_id: remapCodexId(callId, idMap),
3464
+ call_id: mapId(callId, idMap),
3583
3465
  output: text.length > 0 ? text : "(see attached image)"
3584
3466
  });
3585
- if (options?.supportsImages !== false && Array.isArray(result.content)) {
3467
+ if (options.supportsImages !== false && Array.isArray(result.content)) {
3586
3468
  for (const block of result.content) {
3587
3469
  if (block.type === "image") toolImages.push(block);
3588
3470
  }
@@ -3606,6 +3488,221 @@ function toCodexInput(messages, options) {
3606
3488
  }
3607
3489
  return { system, input };
3608
3490
  }
3491
+
3492
+ // src/providers/openai-codex.ts
3493
+ var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
3494
+ var CODEX_CLIENT_VERSION = "0.161.0";
3495
+ var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
3496
+ var zstdInitPromise;
3497
+ async function encodeCodexRequest(body) {
3498
+ const json = JSON.stringify(body);
3499
+ const raw = new TextEncoder().encode(json);
3500
+ if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
3501
+ return {
3502
+ body: json,
3503
+ compressed: false,
3504
+ rawBytes: raw.byteLength,
3505
+ encodedBytes: raw.byteLength
3506
+ };
3507
+ }
3508
+ try {
3509
+ zstdInitPromise ??= zstd.init();
3510
+ await zstdInitPromise;
3511
+ const compressed = Uint8Array.from(zstd.compress(raw));
3512
+ if (compressed.byteLength >= raw.byteLength) {
3513
+ return {
3514
+ body: json,
3515
+ compressed: false,
3516
+ rawBytes: raw.byteLength,
3517
+ encodedBytes: raw.byteLength
3518
+ };
3519
+ }
3520
+ return {
3521
+ body: compressed,
3522
+ compressed: true,
3523
+ rawBytes: raw.byteLength,
3524
+ encodedBytes: compressed.byteLength
3525
+ };
3526
+ } catch (error) {
3527
+ providerDiag("codex_request_compression_failed", {
3528
+ error: error instanceof Error ? error.message : String(error),
3529
+ rawBytes: raw.byteLength
3530
+ });
3531
+ return {
3532
+ body: json,
3533
+ compressed: false,
3534
+ rawBytes: raw.byteLength,
3535
+ encodedBytes: raw.byteLength
3536
+ };
3537
+ }
3538
+ }
3539
+ function usesResponsesLite(model) {
3540
+ return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
3541
+ }
3542
+ function toCodexToolChoice(choice, tools) {
3543
+ const resolved = choice ?? "auto";
3544
+ if (typeof resolved === "object") {
3545
+ throw new GGAIError(
3546
+ `OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
3547
+ { source: "capability" }
3548
+ );
3549
+ }
3550
+ if (resolved === "required" && !tools?.length) {
3551
+ throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
3552
+ source: "capability"
3553
+ });
3554
+ }
3555
+ return resolved;
3556
+ }
3557
+ function streamOpenAICodex(options) {
3558
+ return new StreamResult(runStream3(options), options.signal);
3559
+ }
3560
+ async function* runStream3(options, retriedWithoutReasoning = false) {
3561
+ const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
3562
+ const url = `${baseUrl}/codex/responses`;
3563
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3564
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
3565
+ const { system, input } = toResponsesInput(downgraded, {
3566
+ supportsImages: options.supportsImages,
3567
+ mapId: remapCodexId,
3568
+ // Stamped reasoning belongs to another transport (e.g. Grok's) and would
3569
+ // be rejected here; Codex's own items are never stamped.
3570
+ keepReasoning: (part) => part.origin === void 0
3571
+ });
3572
+ const responsesLite = usesResponsesLite(options.model);
3573
+ const liteShape = options.responsesLite ?? responsesLite;
3574
+ const body = {
3575
+ model: options.model,
3576
+ store: false,
3577
+ stream: true,
3578
+ instructions: system,
3579
+ input,
3580
+ tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
3581
+ parallel_tool_calls: !liteShape,
3582
+ include: ["reasoning.encrypted_content"]
3583
+ };
3584
+ if (options.tools?.length) {
3585
+ body.tools = toCodexTools(options.tools, options.strictTools ?? true);
3586
+ }
3587
+ body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
3588
+ if (options.temperature != null && !options.thinking) {
3589
+ body.temperature = options.temperature;
3590
+ }
3591
+ if (options.speed === "fast") {
3592
+ body.service_tier = "priority";
3593
+ }
3594
+ body.reasoning = {
3595
+ // GPT-5.6/6 require at least low; older models still support thinking off.
3596
+ // Apply the floor here for every caller, including one-off prompt rewrites.
3597
+ // `ultra` is a client orchestration preset, not a Codex API effort.
3598
+ effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : responsesLite ? "low" : "none",
3599
+ summary: "auto",
3600
+ ...liteShape ? { context: "all_turns" } : {}
3601
+ };
3602
+ if (responsesLite) {
3603
+ body.text = { verbosity: "low" };
3604
+ }
3605
+ const headers = {
3606
+ "Content-Type": "application/json",
3607
+ Accept: "text/event-stream",
3608
+ Authorization: `Bearer ${options.apiKey}`,
3609
+ "OpenAI-Beta": "responses=experimental",
3610
+ originator: responsesLite ? "codex_cli_rs" : "ggcoder",
3611
+ "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${os.platform()} ${os.release()}; ${os.arch()})`,
3612
+ ...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
3613
+ ...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
3614
+ };
3615
+ if (options.accountId) {
3616
+ headers["chatgpt-account-id"] = options.accountId;
3617
+ }
3618
+ if (options.transportSessionId) {
3619
+ const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
3620
+ headers["session_id"] = transportSessionId;
3621
+ headers["x-client-request-id"] = transportSessionId;
3622
+ }
3623
+ const encodedRequest = await encodeCodexRequest(
3624
+ mergeProviderOptions(body, options.providerOptions)
3625
+ );
3626
+ if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
3627
+ providerDiag("codex_request_body", {
3628
+ rawBytes: encodedRequest.rawBytes,
3629
+ encodedBytes: encodedRequest.encodedBytes,
3630
+ compressed: encodedRequest.compressed
3631
+ });
3632
+ const response = await providerFetch(url, {
3633
+ method: "POST",
3634
+ headers,
3635
+ body: encodedRequest.body,
3636
+ signal: options.signal
3637
+ });
3638
+ if (!response.ok) {
3639
+ const text = await response.text().catch(() => "");
3640
+ const parsed = parseCodexErrorBody(text, response.status);
3641
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
3642
+ const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
3643
+ if (!retriedWithoutReasoning && !options.signal?.aborted && response.status === 400 && (parsed.errorObj?.code === "invalid_encrypted_content" || /encrypted content.*could not be (?:verified|decrypted|parsed)/i.test(message)) && options.messages.some(
3644
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
3645
+ )) {
3646
+ providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
3647
+ const messages = options.messages.map(
3648
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
3649
+ ...msg,
3650
+ content: msg.content.filter(
3651
+ (part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
3652
+ )
3653
+ } : msg
3654
+ );
3655
+ return yield* runStream3({ ...options, messages }, true);
3656
+ }
3657
+ const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
3658
+ if (usageLimit) throw usageLimit;
3659
+ let hint;
3660
+ if (response.status === 400 && message === `The '${options.model}' model is not supported when using Codex with a ChatGPT account.`) {
3661
+ hint = "This model is not available through your ChatGPT account. Choose another available model using the model selector.";
3662
+ } else if (response.status === 404 && text.includes("does not exist")) {
3663
+ hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
3664
+ }
3665
+ throw new ProviderError("openai", message, {
3666
+ statusCode: response.status,
3667
+ ...requestId ? { requestId } : {},
3668
+ ...hint ? { hint } : {}
3669
+ });
3670
+ }
3671
+ if (!response.body) {
3672
+ throw new ProviderError("openai", "No response body from Codex API");
3673
+ }
3674
+ return yield* consumeResponsesEvents(parseSSE(response.body), {
3675
+ provider: "openai",
3676
+ label: "Codex",
3677
+ diagPrefix: "codex",
3678
+ thinking: !!options.thinking,
3679
+ errorChunk: (errorObj, requestId) => codexUsageLimitError(errorObj, void 0, requestId)
3680
+ });
3681
+ }
3682
+ async function* parseSSE(body) {
3683
+ for await (const event of readSseStream(body)) {
3684
+ const data = event.data.trim();
3685
+ if (!data || data === "[DONE]") continue;
3686
+ try {
3687
+ yield JSON.parse(data);
3688
+ } catch {
3689
+ }
3690
+ }
3691
+ }
3692
+ function remapCodexId(id, idMap) {
3693
+ const existing = idMap.get(id);
3694
+ if (existing) return existing;
3695
+ const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
3696
+ const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
3697
+ let mapped = sanitized;
3698
+ let suffix = 2;
3699
+ const used = new Set(idMap.values());
3700
+ while (used.has(mapped)) {
3701
+ mapped = `${sanitized}_${suffix++}`;
3702
+ }
3703
+ idMap.set(id, mapped);
3704
+ return mapped;
3705
+ }
3609
3706
  function toCodexTools(tools, strictTools) {
3610
3707
  return tools.map((tool) => {
3611
3708
  let parameters = resolveToolSchema(tool);
@@ -3670,6 +3767,136 @@ function codexUsageLimitError(errorObj, statusCode, requestId) {
3670
3767
  });
3671
3768
  }
3672
3769
 
3770
+ // src/providers/xai-responses.ts
3771
+ function streamXaiResponses(options) {
3772
+ return new StreamResult(runStream4(options), options.signal);
3773
+ }
3774
+ function xaiReasoningOrigin(model) {
3775
+ return `xai:${model}`;
3776
+ }
3777
+ async function* runStream4(options, dropReasoning = false) {
3778
+ const providerName = options.provider ?? "xai";
3779
+ const client = createClient2(options);
3780
+ const origin = xaiReasoningOrigin(options.model);
3781
+ const useStreaming = options.streaming !== false;
3782
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3783
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
3784
+ const { system, input } = toResponsesInput(downgraded, {
3785
+ supportsImages: options.supportsImages,
3786
+ keepReasoning: (part) => !dropReasoning && part.origin === origin,
3787
+ minimalAssistantItems: true
3788
+ });
3789
+ const replaysReasoning = input.some(isReasoningItem);
3790
+ const params = {
3791
+ model: options.model,
3792
+ input: system ? [{ role: "system", content: system }, ...input] : input,
3793
+ store: false,
3794
+ stream: useStreaming,
3795
+ include: ["reasoning.encrypted_content"],
3796
+ // Responses' cache-routing key (Chat used the x-grok-conv-id header).
3797
+ prompt_cache_key: normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder"),
3798
+ ...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
3799
+ // Grok reasoning can't be disabled; with thinking off the server default
3800
+ // (high on Grok 4.7) applies, exactly as on the Chat path.
3801
+ ...options.thinking ? { reasoning: { effort: toOpenAIReasoningEffort(options.thinking, options.model) } } : {},
3802
+ ...options.temperature != null && !options.thinking ? { temperature: options.temperature } : {},
3803
+ ...options.topP != null ? { top_p: options.topP } : {},
3804
+ ...options.tools?.length ? { tools: toXaiTools(options.tools) } : {},
3805
+ ...options.toolChoice && options.tools?.length ? { tool_choice: toXaiToolChoice(options.toolChoice) } : {}
3806
+ };
3807
+ const body = mergeProviderOptions(params, options.providerOptions);
3808
+ const requestOptions = {
3809
+ signal: options.signal ?? void 0,
3810
+ // A non-streamed reply arrives in one response; never undercut the agent
3811
+ // loop's own (up to 15min) cap with the SDK's 10min default.
3812
+ timeout: PROVIDER_TRANSPORT_TIMEOUT_MS,
3813
+ headers: {
3814
+ "x-grok-conv-id": normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder")
3815
+ }
3816
+ };
3817
+ let events;
3818
+ try {
3819
+ if (useStreaming) {
3820
+ const stream2 = await client.responses.create(
3821
+ body,
3822
+ requestOptions
3823
+ );
3824
+ events = stream2;
3825
+ } else {
3826
+ const response = await client.responses.create(
3827
+ body,
3828
+ requestOptions
3829
+ );
3830
+ events = synthesizeResponseEvents(response);
3831
+ }
3832
+ } catch (err) {
3833
+ if (replaysReasoning && !dropReasoning && !options.signal?.aborted && err.status === 400) {
3834
+ providerDiag("xai_retry_without_encrypted_reasoning", { status: 400 });
3835
+ return yield* runStream4(options, true);
3836
+ }
3837
+ throw toError2(err, providerName);
3838
+ }
3839
+ try {
3840
+ return yield* consumeResponsesEvents(events, {
3841
+ provider: providerName,
3842
+ label: "Grok",
3843
+ diagPrefix: "xai",
3844
+ thinking: !!options.thinking,
3845
+ reasoningOrigin: origin
3846
+ });
3847
+ } catch (err) {
3848
+ throw toError2(err, providerName);
3849
+ }
3850
+ }
3851
+ function isReasoningItem(item) {
3852
+ return typeof item === "object" && item !== null && isEncryptedReasoning(item);
3853
+ }
3854
+ function toXaiTools(tools) {
3855
+ return tools.map((tool) => ({
3856
+ type: "function",
3857
+ name: tool.name,
3858
+ description: tool.description,
3859
+ parameters: resolveToolSchema(tool)
3860
+ }));
3861
+ }
3862
+ function toXaiToolChoice(choice) {
3863
+ return typeof choice === "object" ? { type: "function", name: choice.name } : choice;
3864
+ }
3865
+ async function* synthesizeResponseEvents(response) {
3866
+ const output = Array.isArray(response.output) ? response.output : [];
3867
+ for (const [index, raw] of output.entries()) {
3868
+ if (typeof raw !== "object" || raw === null) continue;
3869
+ const item = raw;
3870
+ const withId = typeof item.id === "string" ? item : { ...item, id: `item_${index}` };
3871
+ yield { type: "response.output_item.added", item: withId };
3872
+ if (withId.type === "message" && Array.isArray(withId.content)) {
3873
+ for (const [contentIndex, content] of withId.content.entries()) {
3874
+ const part = content;
3875
+ if (part?.type === "output_text" && typeof part.text === "string") {
3876
+ yield {
3877
+ type: "response.output_text.done",
3878
+ item_id: withId.id,
3879
+ content_index: contentIndex,
3880
+ text: part.text
3881
+ };
3882
+ }
3883
+ }
3884
+ }
3885
+ if (withId.type === "function_call") {
3886
+ yield {
3887
+ type: "response.function_call_arguments.done",
3888
+ item_id: withId.id,
3889
+ arguments: typeof withId.arguments === "string" ? withId.arguments : ""
3890
+ };
3891
+ }
3892
+ yield { type: "response.output_item.done", item: withId };
3893
+ }
3894
+ yield {
3895
+ type: response.status === "incomplete" ? "response.incomplete" : "response.completed",
3896
+ response
3897
+ };
3898
+ }
3899
+
3673
3900
  // src/providers/gemini.ts
3674
3901
  var DEFAULT_CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
3675
3902
  var CODE_ASSIST_API_VERSION = "v1internal";
@@ -4088,7 +4315,7 @@ async function sleep(ms, signal) {
4088
4315
  }
4089
4316
  async function fetchCodeAssist(plan, options) {
4090
4317
  try {
4091
- const response = await fetch(plan.url, {
4318
+ const response = await providerFetch(plan.url, {
4092
4319
  method: "POST",
4093
4320
  headers: plan.headers,
4094
4321
  body: JSON.stringify(plan.body),
@@ -4141,9 +4368,9 @@ async function fetchCodeAssistWithRetry(plan, options) {
4141
4368
  throw lastError ?? new ProviderError("gemini", "Gemini Code Assist request failed.");
4142
4369
  }
4143
4370
  function streamGemini(options) {
4144
- return new StreamResult(runStream4(options), options.signal);
4371
+ return new StreamResult(runStream5(options), options.signal);
4145
4372
  }
4146
- async function* runStream4(options) {
4373
+ async function* runStream5(options) {
4147
4374
  const useStreaming = options.streaming !== false;
4148
4375
  const method = useStreaming ? "streamGenerateContent" : "generateContent";
4149
4376
  const plan = buildRequestPlan(options, method);
@@ -4996,13 +5223,12 @@ providerRegistry.register("sakana", {
4996
5223
  })
4997
5224
  });
4998
5225
  providerRegistry.register("xai", {
4999
- // xAI's public API (console.x.ai key) is OpenAI-compatible — ride the Chat
5000
- // Completions transport like Moonshot/DeepSeek. Grok reasoning models take
5001
- // top-level `reasoning_effort` (low/medium/high/xhigh), which the shared thinking
5002
- // path already sends.
5226
+ // Both Grok routes ride xAI's Responses API (see providers/xai-responses.ts):
5227
+ // Chat Completions is deprecated for Grok reasoning models and can't carry
5228
+ // the encrypted reasoning that keeps long tool loops coherent.
5003
5229
  //
5004
5230
  // Subscription OAuth (SuperGrok / X Premium) routes to the Grok CLI chat proxy
5005
- // instead, which speaks the same Chat Completions wire but gates on Grok-CLI
5231
+ // instead, which serves the same `/v1/responses` surface but gates on Grok-CLI
5006
5232
  // client identity. Inject those headers centrally here — exactly as the Kimi
5007
5233
  // endpoint above — so EVERY stream (agent loop, compaction, title-gen,
5008
5234
  // sub-agents) is accepted rather than depending on each call site to thread
@@ -5016,7 +5242,7 @@ providerRegistry.register("xai", {
5016
5242
  "x-grok-model-override": options.model,
5017
5243
  ...options.defaultHeaders
5018
5244
  } : options.defaultHeaders;
5019
- return streamOpenAI({ ...options, baseUrl, defaultHeaders });
5245
+ return streamXaiResponses({ ...options, baseUrl, defaultHeaders });
5020
5246
  }
5021
5247
  });
5022
5248
  providerRegistry.register("minimax", {
@@ -5336,6 +5562,13 @@ function formatChatError(error) {
5336
5562
  "Wait for the reset, or use another provider."
5337
5563
  );
5338
5564
  }
5565
+ if (error instanceof ResponseStalledError) {
5566
+ return present(
5567
+ "network",
5568
+ "The response kept stalling",
5569
+ error.inToolCall ? "Ask for the change in smaller pieces, then try again." : "Try again, or choose another model if it keeps happening."
5570
+ );
5571
+ }
5339
5572
  if (base.source === "network") {
5340
5573
  return present(
5341
5574
  "network",
@@ -5716,6 +5949,7 @@ export {
5716
5949
  GGAIError,
5717
5950
  ProviderError,
5718
5951
  REDACTED as REDACTION_MARKER,
5952
+ ResponseStalledError,
5719
5953
  StreamResult,
5720
5954
  TOOL_CALL_NAME_RULES,
5721
5955
  clampProviderContextImages,