@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.cjs +497 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +496 -262
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
GGAIError: () => GGAIError,
|
|
37
37
|
ProviderError: () => ProviderError,
|
|
38
38
|
REDACTION_MARKER: () => REDACTED,
|
|
39
|
+
ResponseStalledError: () => ResponseStalledError,
|
|
39
40
|
StreamResult: () => StreamResult,
|
|
40
41
|
TOOL_CALL_NAME_RULES: () => TOOL_CALL_NAME_RULES,
|
|
41
42
|
clampProviderContextImages: () => clampProviderContextImages,
|
|
@@ -205,6 +206,14 @@ var VideoUnsupportedError = class extends GGAIError {
|
|
|
205
206
|
this.name = "VideoUnsupportedError";
|
|
206
207
|
}
|
|
207
208
|
};
|
|
209
|
+
var ResponseStalledError = class extends GGAIError {
|
|
210
|
+
inToolCall;
|
|
211
|
+
constructor(message, options) {
|
|
212
|
+
super(message, { source: "network", hint: options.hint, cause: options.cause });
|
|
213
|
+
this.name = "ResponseStalledError";
|
|
214
|
+
this.inToolCall = options.inToolCall;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
208
217
|
var ProviderError = class extends GGAIError {
|
|
209
218
|
provider;
|
|
210
219
|
statusCode;
|
|
@@ -340,6 +349,14 @@ function formatError(err) {
|
|
|
340
349
|
guidance: err.hint ?? providerGuidance(err.provider, cleanMessage, err.statusCode)
|
|
341
350
|
};
|
|
342
351
|
}
|
|
352
|
+
if (err instanceof ResponseStalledError) {
|
|
353
|
+
return {
|
|
354
|
+
headline: "The response kept stalling before it finished.",
|
|
355
|
+
source: err.source,
|
|
356
|
+
message: err.message,
|
|
357
|
+
guidance: err.hint ?? "Retry once. If it keeps happening, choose another model."
|
|
358
|
+
};
|
|
359
|
+
}
|
|
343
360
|
if (err instanceof GGAIError) {
|
|
344
361
|
return finaliseBySource(err.source, err.message, err.requestId, err.hint);
|
|
345
362
|
}
|
|
@@ -1751,9 +1768,43 @@ function parseToolArguments(argsJson) {
|
|
|
1751
1768
|
}
|
|
1752
1769
|
}
|
|
1753
1770
|
|
|
1771
|
+
// src/utils/provider-fetch.ts
|
|
1772
|
+
var import_undici = require("undici");
|
|
1773
|
+
var PROVIDER_TRANSPORT_TIMEOUT_MS = 30 * 6e4;
|
|
1774
|
+
function nodeUsesEnvProxy({
|
|
1775
|
+
env = process.env,
|
|
1776
|
+
execArgv = process.execArgv,
|
|
1777
|
+
nodeVersion = process.versions.node
|
|
1778
|
+
} = {}) {
|
|
1779
|
+
const flags = [...execArgv, ...(env.NODE_OPTIONS ?? "").split(/\s+/)];
|
|
1780
|
+
if (flags.includes("--use-env-proxy")) return true;
|
|
1781
|
+
if (env.NODE_USE_ENV_PROXY !== "1") return false;
|
|
1782
|
+
const [major = 0, minor = 0] = nodeVersion.split(".").map(Number);
|
|
1783
|
+
return major >= 24 || major === 22 && minor >= 21;
|
|
1784
|
+
}
|
|
1785
|
+
function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {}) {
|
|
1786
|
+
const timeouts = { headersTimeout: timeoutMs, bodyTimeout: timeoutMs };
|
|
1787
|
+
const dispatcher = useEnvProxy ? new import_undici.EnvHttpProxyAgent(timeouts) : new import_undici.Agent(timeouts);
|
|
1788
|
+
const builtinFetch = globalThis.fetch;
|
|
1789
|
+
const providerFetch2 = async (input, init2) => {
|
|
1790
|
+
if (globalThis.fetch !== builtinFetch || input instanceof Request || init2?.body instanceof FormData) {
|
|
1791
|
+
return globalThis.fetch(input, init2);
|
|
1792
|
+
}
|
|
1793
|
+
const response = await (0, import_undici.fetch)(input, {
|
|
1794
|
+
...init2,
|
|
1795
|
+
dispatcher
|
|
1796
|
+
});
|
|
1797
|
+
return response;
|
|
1798
|
+
};
|
|
1799
|
+
return Object.assign(providerFetch2, { Response: globalThis.Response });
|
|
1800
|
+
}
|
|
1801
|
+
var providerFetch = createProviderFetch(
|
|
1802
|
+
PROVIDER_TRANSPORT_TIMEOUT_MS
|
|
1803
|
+
);
|
|
1804
|
+
|
|
1754
1805
|
// src/providers/anthropic.ts
|
|
1755
1806
|
var anthropicClientCache = /* @__PURE__ */ new Map();
|
|
1756
|
-
var NON_STREAMING_REQUEST_TIMEOUT_MS =
|
|
1807
|
+
var NON_STREAMING_REQUEST_TIMEOUT_MS = PROVIDER_TRANSPORT_TIMEOUT_MS;
|
|
1757
1808
|
function fineGrainedToolStreamingEnabled() {
|
|
1758
1809
|
const raw = process.env.GG_FINE_GRAINED_TOOL_STREAMING ?? process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING;
|
|
1759
1810
|
if (!raw) return false;
|
|
@@ -1814,7 +1865,8 @@ function createClient(options) {
|
|
|
1814
1865
|
const client = new import_sdk.default({
|
|
1815
1866
|
...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
|
|
1816
1867
|
...options.baseUrl ? { baseURL: options.baseUrl } : {},
|
|
1817
|
-
|
|
1868
|
+
// Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
|
|
1869
|
+
fetch: options.fetch ?? providerFetch,
|
|
1818
1870
|
maxRetries: 0,
|
|
1819
1871
|
...isOAuth ? {
|
|
1820
1872
|
defaultHeaders: {
|
|
@@ -2592,7 +2644,8 @@ function createClient2(options) {
|
|
|
2592
2644
|
const client = new import_openai.default({
|
|
2593
2645
|
apiKey: options.apiKey,
|
|
2594
2646
|
...options.baseUrl ? { baseURL: options.baseUrl } : {},
|
|
2595
|
-
|
|
2647
|
+
// Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
|
|
2648
|
+
fetch: options.fetch ?? providerFetch,
|
|
2596
2649
|
...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
|
|
2597
2650
|
});
|
|
2598
2651
|
if (!options.fetch) {
|
|
@@ -2714,16 +2767,13 @@ async function* runStream2(options) {
|
|
|
2714
2767
|
`
|
|
2715
2768
|
);
|
|
2716
2769
|
}
|
|
2717
|
-
const requestHeaders = options.provider === "xai" ? {
|
|
2718
|
-
headers: {
|
|
2719
|
-
"x-grok-conv-id": normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder")
|
|
2720
|
-
}
|
|
2721
|
-
} : {};
|
|
2722
2770
|
if (!useStreaming) {
|
|
2723
2771
|
try {
|
|
2724
2772
|
const completion = await client.chat.completions.create(requestParams, {
|
|
2725
2773
|
signal: options.signal ?? void 0,
|
|
2726
|
-
|
|
2774
|
+
// The whole reply arrives in one response, so the SDK's 10min default
|
|
2775
|
+
// would undercut the agent loop's 15min thinking cap for this call.
|
|
2776
|
+
timeout: PROVIDER_TRANSPORT_TIMEOUT_MS
|
|
2727
2777
|
});
|
|
2728
2778
|
yield* synthesizeEventsFromCompletion(completion, !!options.thinking, endpointKey);
|
|
2729
2779
|
return completionToResponse(completion, endpointKey);
|
|
@@ -2734,8 +2784,7 @@ async function* runStream2(options) {
|
|
|
2734
2784
|
let stream2;
|
|
2735
2785
|
try {
|
|
2736
2786
|
stream2 = await client.chat.completions.create(requestParams, {
|
|
2737
|
-
signal: options.signal ?? void 0
|
|
2738
|
-
...requestHeaders
|
|
2787
|
+
signal: options.signal ?? void 0
|
|
2739
2788
|
});
|
|
2740
2789
|
} catch (err) {
|
|
2741
2790
|
throw toError2(err, providerName);
|
|
@@ -2776,6 +2825,8 @@ async function* runStream2(options) {
|
|
|
2776
2825
|
thinkingAccum += reasoning.text;
|
|
2777
2826
|
if (options.thinking) {
|
|
2778
2827
|
yield { type: "thinking_delta", text: reasoning.text };
|
|
2828
|
+
} else {
|
|
2829
|
+
yield { type: "keepalive", reasoning: true };
|
|
2779
2830
|
}
|
|
2780
2831
|
}
|
|
2781
2832
|
if (delta.content) {
|
|
@@ -3094,55 +3145,9 @@ function extractRequestIdFromMessage(message) {
|
|
|
3094
3145
|
return match?.[1];
|
|
3095
3146
|
}
|
|
3096
3147
|
|
|
3097
|
-
// src/providers/
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
|
|
3101
|
-
var zstdInitPromise;
|
|
3102
|
-
async function encodeCodexRequest(body) {
|
|
3103
|
-
const json = JSON.stringify(body);
|
|
3104
|
-
const raw = new TextEncoder().encode(json);
|
|
3105
|
-
if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
|
|
3106
|
-
return {
|
|
3107
|
-
body: json,
|
|
3108
|
-
compressed: false,
|
|
3109
|
-
rawBytes: raw.byteLength,
|
|
3110
|
-
encodedBytes: raw.byteLength
|
|
3111
|
-
};
|
|
3112
|
-
}
|
|
3113
|
-
try {
|
|
3114
|
-
zstdInitPromise ??= zstd.init();
|
|
3115
|
-
await zstdInitPromise;
|
|
3116
|
-
const compressed = Uint8Array.from(zstd.compress(raw));
|
|
3117
|
-
if (compressed.byteLength >= raw.byteLength) {
|
|
3118
|
-
return {
|
|
3119
|
-
body: json,
|
|
3120
|
-
compressed: false,
|
|
3121
|
-
rawBytes: raw.byteLength,
|
|
3122
|
-
encodedBytes: raw.byteLength
|
|
3123
|
-
};
|
|
3124
|
-
}
|
|
3125
|
-
return {
|
|
3126
|
-
body: compressed,
|
|
3127
|
-
compressed: true,
|
|
3128
|
-
rawBytes: raw.byteLength,
|
|
3129
|
-
encodedBytes: compressed.byteLength
|
|
3130
|
-
};
|
|
3131
|
-
} catch (error) {
|
|
3132
|
-
providerDiag("codex_request_compression_failed", {
|
|
3133
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3134
|
-
rawBytes: raw.byteLength
|
|
3135
|
-
});
|
|
3136
|
-
return {
|
|
3137
|
-
body: json,
|
|
3138
|
-
compressed: false,
|
|
3139
|
-
rawBytes: raw.byteLength,
|
|
3140
|
-
encodedBytes: raw.byteLength
|
|
3141
|
-
};
|
|
3142
|
-
}
|
|
3143
|
-
}
|
|
3144
|
-
function usesResponsesLite(model) {
|
|
3145
|
-
return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
|
|
3148
|
+
// src/providers/responses-stream.ts
|
|
3149
|
+
function isEncryptedReasoning(data) {
|
|
3150
|
+
return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
|
|
3146
3151
|
}
|
|
3147
3152
|
function outputTextKey(itemId, contentIndex) {
|
|
3148
3153
|
return `${itemId ?? ""}:${contentIndex ?? 0}`;
|
|
@@ -3150,132 +3155,7 @@ function outputTextKey(itemId, contentIndex) {
|
|
|
3150
3155
|
function isVisibleOutputItem(itemType) {
|
|
3151
3156
|
return itemType === "message";
|
|
3152
3157
|
}
|
|
3153
|
-
function
|
|
3154
|
-
const resolved = choice ?? "auto";
|
|
3155
|
-
if (typeof resolved === "object") {
|
|
3156
|
-
throw new GGAIError(
|
|
3157
|
-
`OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
|
|
3158
|
-
{ source: "capability" }
|
|
3159
|
-
);
|
|
3160
|
-
}
|
|
3161
|
-
if (resolved === "required" && !tools?.length) {
|
|
3162
|
-
throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
|
|
3163
|
-
source: "capability"
|
|
3164
|
-
});
|
|
3165
|
-
}
|
|
3166
|
-
return resolved;
|
|
3167
|
-
}
|
|
3168
|
-
function streamOpenAICodex(options) {
|
|
3169
|
-
return new StreamResult(runStream3(options), options.signal);
|
|
3170
|
-
}
|
|
3171
|
-
async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
3172
|
-
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
3173
|
-
const url = `${baseUrl}/codex/responses`;
|
|
3174
|
-
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
3175
|
-
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
3176
|
-
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
3177
|
-
const responsesLite = usesResponsesLite(options.model);
|
|
3178
|
-
const liteShape = options.responsesLite ?? responsesLite;
|
|
3179
|
-
const body = {
|
|
3180
|
-
model: options.model,
|
|
3181
|
-
store: false,
|
|
3182
|
-
stream: true,
|
|
3183
|
-
instructions: system,
|
|
3184
|
-
input,
|
|
3185
|
-
tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
|
|
3186
|
-
parallel_tool_calls: !liteShape,
|
|
3187
|
-
include: ["reasoning.encrypted_content"]
|
|
3188
|
-
};
|
|
3189
|
-
if (options.tools?.length) {
|
|
3190
|
-
body.tools = toCodexTools(options.tools, options.strictTools ?? true);
|
|
3191
|
-
}
|
|
3192
|
-
body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
|
|
3193
|
-
if (options.temperature != null && !options.thinking) {
|
|
3194
|
-
body.temperature = options.temperature;
|
|
3195
|
-
}
|
|
3196
|
-
if (options.speed === "fast") {
|
|
3197
|
-
body.service_tier = "priority";
|
|
3198
|
-
}
|
|
3199
|
-
body.reasoning = {
|
|
3200
|
-
// GPT-5.6/6 require at least low; older models still support thinking off.
|
|
3201
|
-
// Apply the floor here for every caller, including one-off prompt rewrites.
|
|
3202
|
-
// `ultra` is a client orchestration preset, not a Codex API effort.
|
|
3203
|
-
effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : responsesLite ? "low" : "none",
|
|
3204
|
-
summary: "auto",
|
|
3205
|
-
...liteShape ? { context: "all_turns" } : {}
|
|
3206
|
-
};
|
|
3207
|
-
if (responsesLite) {
|
|
3208
|
-
body.text = { verbosity: "low" };
|
|
3209
|
-
}
|
|
3210
|
-
const headers = {
|
|
3211
|
-
"Content-Type": "application/json",
|
|
3212
|
-
Accept: "text/event-stream",
|
|
3213
|
-
Authorization: `Bearer ${options.apiKey}`,
|
|
3214
|
-
"OpenAI-Beta": "responses=experimental",
|
|
3215
|
-
originator: responsesLite ? "codex_cli_rs" : "ggcoder",
|
|
3216
|
-
"User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${import_node_os.default.platform()} ${import_node_os.default.release()}; ${import_node_os.default.arch()})`,
|
|
3217
|
-
...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
|
|
3218
|
-
...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
|
|
3219
|
-
};
|
|
3220
|
-
if (options.accountId) {
|
|
3221
|
-
headers["chatgpt-account-id"] = options.accountId;
|
|
3222
|
-
}
|
|
3223
|
-
if (options.transportSessionId) {
|
|
3224
|
-
const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
|
|
3225
|
-
headers["session_id"] = transportSessionId;
|
|
3226
|
-
headers["x-client-request-id"] = transportSessionId;
|
|
3227
|
-
}
|
|
3228
|
-
const encodedRequest = await encodeCodexRequest(
|
|
3229
|
-
mergeProviderOptions(body, options.providerOptions)
|
|
3230
|
-
);
|
|
3231
|
-
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
3232
|
-
providerDiag("codex_request_body", {
|
|
3233
|
-
rawBytes: encodedRequest.rawBytes,
|
|
3234
|
-
encodedBytes: encodedRequest.encodedBytes,
|
|
3235
|
-
compressed: encodedRequest.compressed
|
|
3236
|
-
});
|
|
3237
|
-
const response = await fetch(url, {
|
|
3238
|
-
method: "POST",
|
|
3239
|
-
headers,
|
|
3240
|
-
body: encodedRequest.body,
|
|
3241
|
-
signal: options.signal
|
|
3242
|
-
});
|
|
3243
|
-
if (!response.ok) {
|
|
3244
|
-
const text = await response.text().catch(() => "");
|
|
3245
|
-
const parsed = parseCodexErrorBody(text, response.status);
|
|
3246
|
-
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
3247
|
-
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
3248
|
-
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(
|
|
3249
|
-
(msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
|
|
3250
|
-
)) {
|
|
3251
|
-
providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
|
|
3252
|
-
const messages = options.messages.map(
|
|
3253
|
-
(msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
|
|
3254
|
-
...msg,
|
|
3255
|
-
content: msg.content.filter(
|
|
3256
|
-
(part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
|
|
3257
|
-
)
|
|
3258
|
-
} : msg
|
|
3259
|
-
);
|
|
3260
|
-
return yield* runStream3({ ...options, messages }, true);
|
|
3261
|
-
}
|
|
3262
|
-
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
3263
|
-
if (usageLimit) throw usageLimit;
|
|
3264
|
-
let hint;
|
|
3265
|
-
if (response.status === 400 && message === `The '${options.model}' model is not supported when using Codex with a ChatGPT account.`) {
|
|
3266
|
-
hint = "This model is not available through your ChatGPT account. Choose another available model using the model selector.";
|
|
3267
|
-
} else if (response.status === 404 && text.includes("does not exist")) {
|
|
3268
|
-
hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
|
|
3269
|
-
}
|
|
3270
|
-
throw new ProviderError("openai", message, {
|
|
3271
|
-
statusCode: response.status,
|
|
3272
|
-
...requestId ? { requestId } : {},
|
|
3273
|
-
...hint ? { hint } : {}
|
|
3274
|
-
});
|
|
3275
|
-
}
|
|
3276
|
-
if (!response.body) {
|
|
3277
|
-
throw new ProviderError("openai", "No response body from Codex API");
|
|
3278
|
-
}
|
|
3158
|
+
async function* consumeResponsesEvents(events, ctx) {
|
|
3279
3159
|
const contentParts = [];
|
|
3280
3160
|
let textAccum = "";
|
|
3281
3161
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
@@ -3292,34 +3172,30 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3292
3172
|
let thinkingTextEmitted = false;
|
|
3293
3173
|
const diagStart = Date.now();
|
|
3294
3174
|
const diagSeen = /* @__PURE__ */ new Set();
|
|
3295
|
-
for await (const event of
|
|
3175
|
+
for await (const event of events) {
|
|
3296
3176
|
const type = event.type;
|
|
3297
3177
|
if (!type) continue;
|
|
3298
3178
|
if (!diagSeen.has(type)) {
|
|
3299
3179
|
diagSeen.add(type);
|
|
3300
|
-
providerDiag(
|
|
3180
|
+
providerDiag(`${ctx.diagPrefix}_event_first`, { type, sinceStartMs: Date.now() - diagStart });
|
|
3301
3181
|
}
|
|
3302
3182
|
if (type === "error") {
|
|
3303
3183
|
const nested = event.error ?? void 0;
|
|
3304
|
-
const message = nested?.message ?? event.message ??
|
|
3184
|
+
const message = nested?.message ?? event.message ?? `${ctx.label} stream emitted an error chunk without a message.`;
|
|
3305
3185
|
const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
|
|
3306
3186
|
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
3307
|
-
const usageLimit =
|
|
3308
|
-
nested ?? event,
|
|
3309
|
-
void 0,
|
|
3310
|
-
requestId
|
|
3311
|
-
);
|
|
3187
|
+
const usageLimit = ctx.errorChunk?.(nested ?? event, requestId);
|
|
3312
3188
|
if (usageLimit) throw usageLimit;
|
|
3313
|
-
throw new ProviderError(
|
|
3189
|
+
throw new ProviderError(ctx.provider, message, {
|
|
3314
3190
|
...requestId != null ? { requestId } : {},
|
|
3315
3191
|
...code === "server_error" ? { statusCode: 500 } : {}
|
|
3316
3192
|
});
|
|
3317
3193
|
}
|
|
3318
3194
|
if (type === "response.failed") {
|
|
3319
3195
|
const nested = event.error;
|
|
3320
|
-
const message = nested?.message ??
|
|
3196
|
+
const message = nested?.message ?? `${ctx.label} response failed.`;
|
|
3321
3197
|
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
3322
|
-
throw new ProviderError(
|
|
3198
|
+
throw new ProviderError(ctx.provider, message, {
|
|
3323
3199
|
...requestId != null ? { requestId } : {}
|
|
3324
3200
|
});
|
|
3325
3201
|
}
|
|
@@ -3368,15 +3244,17 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3368
3244
|
}
|
|
3369
3245
|
}
|
|
3370
3246
|
if (type === "response.reasoning_summary_part.added") {
|
|
3371
|
-
if (
|
|
3247
|
+
if (ctx.thinking && thinkingTextEmitted) {
|
|
3372
3248
|
yield { type: "thinking_delta", text: "\n\n" };
|
|
3373
3249
|
}
|
|
3374
3250
|
}
|
|
3375
3251
|
if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
|
|
3376
3252
|
const delta = event.delta;
|
|
3377
|
-
if (
|
|
3253
|
+
if (ctx.thinking && delta) {
|
|
3378
3254
|
thinkingTextEmitted = true;
|
|
3379
3255
|
yield { type: "thinking_delta", text: delta };
|
|
3256
|
+
} else if (delta) {
|
|
3257
|
+
yield { type: "keepalive", reasoning: true };
|
|
3380
3258
|
}
|
|
3381
3259
|
}
|
|
3382
3260
|
if (type === "response.output_item.added") {
|
|
@@ -3386,8 +3264,8 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3386
3264
|
if (itemId && itemType) {
|
|
3387
3265
|
outputItemTypes.set(itemId, itemType);
|
|
3388
3266
|
}
|
|
3389
|
-
if (itemType === "reasoning"
|
|
3390
|
-
yield { type: "thinking_delta", text: "" };
|
|
3267
|
+
if (itemType === "reasoning") {
|
|
3268
|
+
yield ctx.thinking ? { type: "thinking_delta", text: "" } : { type: "keepalive", reasoning: true };
|
|
3391
3269
|
}
|
|
3392
3270
|
if (itemId && itemType) {
|
|
3393
3271
|
const pending = [...pendingOutputTextByPart.entries()].filter(([, pendingPart]) => pendingPart.itemId === itemId).sort(([, a], [, b]) => a.contentIndex - b.contentIndex);
|
|
@@ -3448,7 +3326,8 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3448
3326
|
kind: "reasoning",
|
|
3449
3327
|
part: {
|
|
3450
3328
|
type: "raw",
|
|
3451
|
-
data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] }
|
|
3329
|
+
data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] },
|
|
3330
|
+
...ctx.reasoningOrigin ? { origin: ctx.reasoningOrigin } : {}
|
|
3452
3331
|
}
|
|
3453
3332
|
});
|
|
3454
3333
|
}
|
|
@@ -3457,6 +3336,13 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3457
3336
|
const callId = item.call_id;
|
|
3458
3337
|
const itemId = item.id;
|
|
3459
3338
|
const id = `${callId}|${itemId}`;
|
|
3339
|
+
if (!toolCalls.has(id) && typeof item.name === "string") {
|
|
3340
|
+
toolCalls.set(id, {
|
|
3341
|
+
id,
|
|
3342
|
+
name: item.name,
|
|
3343
|
+
argsJson: typeof item.arguments === "string" ? item.arguments : ""
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3460
3346
|
const tc = toolCalls.get(id);
|
|
3461
3347
|
if (tc) {
|
|
3462
3348
|
finishedToolCalls.add(id);
|
|
@@ -3482,6 +3368,23 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3482
3368
|
} else {
|
|
3483
3369
|
terminal = { status: "completed" };
|
|
3484
3370
|
}
|
|
3371
|
+
if (Array.isArray(resp?.output)) {
|
|
3372
|
+
for (const raw of resp.output) {
|
|
3373
|
+
const item = raw;
|
|
3374
|
+
if (typeof item?.id !== "string" || typeof item.type !== "string") continue;
|
|
3375
|
+
if (!outputItemTypes.has(item.id)) outputItemTypes.set(item.id, item.type);
|
|
3376
|
+
}
|
|
3377
|
+
const pending = [...pendingOutputTextByPart.entries()].sort(
|
|
3378
|
+
([, a], [, b]) => a.contentIndex - b.contentIndex
|
|
3379
|
+
);
|
|
3380
|
+
for (const [key, pendingPart] of pending) {
|
|
3381
|
+
if (!isVisibleOutputItem(outputItemTypes.get(pendingPart.itemId))) continue;
|
|
3382
|
+
pendingOutputTextByPart.delete(key);
|
|
3383
|
+
if (!pendingPart.text) continue;
|
|
3384
|
+
textAccum += pendingPart.text;
|
|
3385
|
+
yield { type: "text_delta", text: pendingPart.text };
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3485
3388
|
const usage = resp?.usage;
|
|
3486
3389
|
if (usage) {
|
|
3487
3390
|
cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
|
|
@@ -3492,23 +3395,30 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3492
3395
|
}
|
|
3493
3396
|
}
|
|
3494
3397
|
if (!terminal) {
|
|
3495
|
-
throw new ProviderError(
|
|
3496
|
-
|
|
3497
|
-
|
|
3398
|
+
throw new ProviderError(
|
|
3399
|
+
ctx.provider,
|
|
3400
|
+
"Stream ended before completion (no response.completed).",
|
|
3401
|
+
{
|
|
3402
|
+
statusCode: 504
|
|
3403
|
+
}
|
|
3404
|
+
);
|
|
3498
3405
|
}
|
|
3499
3406
|
const droppedToolCalls = [...toolCalls.keys()].filter((id) => !finishedToolCalls.has(id)).length;
|
|
3500
3407
|
if (terminal.status === "completed") {
|
|
3501
3408
|
for (const [id, tc] of toolCalls) {
|
|
3502
3409
|
if (!finishedToolCalls.has(id)) {
|
|
3503
3410
|
throw new ProviderError(
|
|
3504
|
-
|
|
3505
|
-
|
|
3411
|
+
ctx.provider,
|
|
3412
|
+
`${ctx.label} reply completed with an unfinished tool call: ${tc.name} (${id}).`,
|
|
3506
3413
|
{ statusCode: 502 }
|
|
3507
3414
|
);
|
|
3508
3415
|
}
|
|
3509
3416
|
}
|
|
3510
3417
|
} else {
|
|
3511
|
-
providerDiag(
|
|
3418
|
+
providerDiag(`${ctx.diagPrefix}_incomplete`, {
|
|
3419
|
+
reason: terminal.reason ?? null,
|
|
3420
|
+
droppedToolCalls
|
|
3421
|
+
});
|
|
3512
3422
|
}
|
|
3513
3423
|
const seenTool = /* @__PURE__ */ new Set();
|
|
3514
3424
|
let textInserted = false;
|
|
@@ -3575,34 +3485,14 @@ function incompleteStopReason(reason) {
|
|
|
3575
3485
|
if (reason === "content_filter") return "refusal";
|
|
3576
3486
|
return "error";
|
|
3577
3487
|
}
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
function remapCodexId(id, idMap) {
|
|
3589
|
-
const existing = idMap.get(id);
|
|
3590
|
-
if (existing) return existing;
|
|
3591
|
-
const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
|
|
3592
|
-
const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3593
|
-
let mapped = sanitized;
|
|
3594
|
-
let suffix = 2;
|
|
3595
|
-
const used = new Set(idMap.values());
|
|
3596
|
-
while (used.has(mapped)) {
|
|
3597
|
-
mapped = `${sanitized}_${suffix++}`;
|
|
3598
|
-
}
|
|
3599
|
-
idMap.set(id, mapped);
|
|
3600
|
-
return mapped;
|
|
3601
|
-
}
|
|
3602
|
-
function isEncryptedReasoning(data) {
|
|
3603
|
-
return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
|
|
3604
|
-
}
|
|
3605
|
-
function toCodexInput(messages, options) {
|
|
3488
|
+
function toResponsesInput(messages, options) {
|
|
3489
|
+
const mapId = options.mapId ?? ((id) => id);
|
|
3490
|
+
const assistantText = (text) => options.minimalAssistantItems ? { role: "assistant", content: text } : {
|
|
3491
|
+
type: "message",
|
|
3492
|
+
role: "assistant",
|
|
3493
|
+
content: [{ type: "output_text", text, annotations: [] }],
|
|
3494
|
+
status: "completed"
|
|
3495
|
+
};
|
|
3606
3496
|
let system;
|
|
3607
3497
|
const input = [];
|
|
3608
3498
|
const idMap = /* @__PURE__ */ new Map();
|
|
@@ -3625,30 +3515,23 @@ function toCodexInput(messages, options) {
|
|
|
3625
3515
|
}
|
|
3626
3516
|
if (msg.role === "assistant") {
|
|
3627
3517
|
if (typeof msg.content === "string") {
|
|
3628
|
-
input.push(
|
|
3629
|
-
type: "message",
|
|
3630
|
-
role: "assistant",
|
|
3631
|
-
content: [{ type: "output_text", text: msg.content, annotations: [] }],
|
|
3632
|
-
status: "completed"
|
|
3633
|
-
});
|
|
3518
|
+
input.push(assistantText(msg.content));
|
|
3634
3519
|
continue;
|
|
3635
3520
|
}
|
|
3636
3521
|
for (const part of msg.content) {
|
|
3637
3522
|
if (part.type === "raw" && isEncryptedReasoning(part.data)) {
|
|
3523
|
+
if (!options.keepReasoning(part)) continue;
|
|
3638
3524
|
input.push(part.data);
|
|
3639
3525
|
} else if (part.type === "text") {
|
|
3640
|
-
input.push(
|
|
3641
|
-
type: "message",
|
|
3642
|
-
role: "assistant",
|
|
3643
|
-
content: [{ type: "output_text", text: part.text, annotations: [] }],
|
|
3644
|
-
status: "completed"
|
|
3645
|
-
});
|
|
3526
|
+
input.push(assistantText(part.text));
|
|
3646
3527
|
} else if (part.type === "tool_call") {
|
|
3647
|
-
const
|
|
3528
|
+
const native = part.id.includes("|");
|
|
3529
|
+
const [callId, itemId] = native ? part.id.split("|", 2) : [part.id, part.id];
|
|
3530
|
+
const sendItemId = !options.minimalAssistantItems || native && !!itemId && itemId !== "undefined";
|
|
3648
3531
|
input.push({
|
|
3649
3532
|
type: "function_call",
|
|
3650
|
-
id:
|
|
3651
|
-
call_id:
|
|
3533
|
+
...sendItemId ? { id: mapId(itemId ?? callId, idMap) } : {},
|
|
3534
|
+
call_id: mapId(callId, idMap),
|
|
3652
3535
|
name: part.name,
|
|
3653
3536
|
arguments: JSON.stringify(part.args)
|
|
3654
3537
|
});
|
|
@@ -3663,10 +3546,10 @@ function toCodexInput(messages, options) {
|
|
|
3663
3546
|
const text = toolResultText(result.content);
|
|
3664
3547
|
input.push({
|
|
3665
3548
|
type: "function_call_output",
|
|
3666
|
-
call_id:
|
|
3549
|
+
call_id: mapId(callId, idMap),
|
|
3667
3550
|
output: text.length > 0 ? text : "(see attached image)"
|
|
3668
3551
|
});
|
|
3669
|
-
if (options
|
|
3552
|
+
if (options.supportsImages !== false && Array.isArray(result.content)) {
|
|
3670
3553
|
for (const block of result.content) {
|
|
3671
3554
|
if (block.type === "image") toolImages.push(block);
|
|
3672
3555
|
}
|
|
@@ -3690,6 +3573,221 @@ function toCodexInput(messages, options) {
|
|
|
3690
3573
|
}
|
|
3691
3574
|
return { system, input };
|
|
3692
3575
|
}
|
|
3576
|
+
|
|
3577
|
+
// src/providers/openai-codex.ts
|
|
3578
|
+
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
3579
|
+
var CODEX_CLIENT_VERSION = "0.161.0";
|
|
3580
|
+
var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
|
|
3581
|
+
var zstdInitPromise;
|
|
3582
|
+
async function encodeCodexRequest(body) {
|
|
3583
|
+
const json = JSON.stringify(body);
|
|
3584
|
+
const raw = new TextEncoder().encode(json);
|
|
3585
|
+
if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
|
|
3586
|
+
return {
|
|
3587
|
+
body: json,
|
|
3588
|
+
compressed: false,
|
|
3589
|
+
rawBytes: raw.byteLength,
|
|
3590
|
+
encodedBytes: raw.byteLength
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
try {
|
|
3594
|
+
zstdInitPromise ??= zstd.init();
|
|
3595
|
+
await zstdInitPromise;
|
|
3596
|
+
const compressed = Uint8Array.from(zstd.compress(raw));
|
|
3597
|
+
if (compressed.byteLength >= raw.byteLength) {
|
|
3598
|
+
return {
|
|
3599
|
+
body: json,
|
|
3600
|
+
compressed: false,
|
|
3601
|
+
rawBytes: raw.byteLength,
|
|
3602
|
+
encodedBytes: raw.byteLength
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
return {
|
|
3606
|
+
body: compressed,
|
|
3607
|
+
compressed: true,
|
|
3608
|
+
rawBytes: raw.byteLength,
|
|
3609
|
+
encodedBytes: compressed.byteLength
|
|
3610
|
+
};
|
|
3611
|
+
} catch (error) {
|
|
3612
|
+
providerDiag("codex_request_compression_failed", {
|
|
3613
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3614
|
+
rawBytes: raw.byteLength
|
|
3615
|
+
});
|
|
3616
|
+
return {
|
|
3617
|
+
body: json,
|
|
3618
|
+
compressed: false,
|
|
3619
|
+
rawBytes: raw.byteLength,
|
|
3620
|
+
encodedBytes: raw.byteLength
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
function usesResponsesLite(model) {
|
|
3625
|
+
return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
|
|
3626
|
+
}
|
|
3627
|
+
function toCodexToolChoice(choice, tools) {
|
|
3628
|
+
const resolved = choice ?? "auto";
|
|
3629
|
+
if (typeof resolved === "object") {
|
|
3630
|
+
throw new GGAIError(
|
|
3631
|
+
`OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
|
|
3632
|
+
{ source: "capability" }
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
if (resolved === "required" && !tools?.length) {
|
|
3636
|
+
throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
|
|
3637
|
+
source: "capability"
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
return resolved;
|
|
3641
|
+
}
|
|
3642
|
+
function streamOpenAICodex(options) {
|
|
3643
|
+
return new StreamResult(runStream3(options), options.signal);
|
|
3644
|
+
}
|
|
3645
|
+
async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
3646
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
3647
|
+
const url = `${baseUrl}/codex/responses`;
|
|
3648
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
3649
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
3650
|
+
const { system, input } = toResponsesInput(downgraded, {
|
|
3651
|
+
supportsImages: options.supportsImages,
|
|
3652
|
+
mapId: remapCodexId,
|
|
3653
|
+
// Stamped reasoning belongs to another transport (e.g. Grok's) and would
|
|
3654
|
+
// be rejected here; Codex's own items are never stamped.
|
|
3655
|
+
keepReasoning: (part) => part.origin === void 0
|
|
3656
|
+
});
|
|
3657
|
+
const responsesLite = usesResponsesLite(options.model);
|
|
3658
|
+
const liteShape = options.responsesLite ?? responsesLite;
|
|
3659
|
+
const body = {
|
|
3660
|
+
model: options.model,
|
|
3661
|
+
store: false,
|
|
3662
|
+
stream: true,
|
|
3663
|
+
instructions: system,
|
|
3664
|
+
input,
|
|
3665
|
+
tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
|
|
3666
|
+
parallel_tool_calls: !liteShape,
|
|
3667
|
+
include: ["reasoning.encrypted_content"]
|
|
3668
|
+
};
|
|
3669
|
+
if (options.tools?.length) {
|
|
3670
|
+
body.tools = toCodexTools(options.tools, options.strictTools ?? true);
|
|
3671
|
+
}
|
|
3672
|
+
body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
|
|
3673
|
+
if (options.temperature != null && !options.thinking) {
|
|
3674
|
+
body.temperature = options.temperature;
|
|
3675
|
+
}
|
|
3676
|
+
if (options.speed === "fast") {
|
|
3677
|
+
body.service_tier = "priority";
|
|
3678
|
+
}
|
|
3679
|
+
body.reasoning = {
|
|
3680
|
+
// GPT-5.6/6 require at least low; older models still support thinking off.
|
|
3681
|
+
// Apply the floor here for every caller, including one-off prompt rewrites.
|
|
3682
|
+
// `ultra` is a client orchestration preset, not a Codex API effort.
|
|
3683
|
+
effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : responsesLite ? "low" : "none",
|
|
3684
|
+
summary: "auto",
|
|
3685
|
+
...liteShape ? { context: "all_turns" } : {}
|
|
3686
|
+
};
|
|
3687
|
+
if (responsesLite) {
|
|
3688
|
+
body.text = { verbosity: "low" };
|
|
3689
|
+
}
|
|
3690
|
+
const headers = {
|
|
3691
|
+
"Content-Type": "application/json",
|
|
3692
|
+
Accept: "text/event-stream",
|
|
3693
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
3694
|
+
"OpenAI-Beta": "responses=experimental",
|
|
3695
|
+
originator: responsesLite ? "codex_cli_rs" : "ggcoder",
|
|
3696
|
+
"User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${import_node_os.default.platform()} ${import_node_os.default.release()}; ${import_node_os.default.arch()})`,
|
|
3697
|
+
...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
|
|
3698
|
+
...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
|
|
3699
|
+
};
|
|
3700
|
+
if (options.accountId) {
|
|
3701
|
+
headers["chatgpt-account-id"] = options.accountId;
|
|
3702
|
+
}
|
|
3703
|
+
if (options.transportSessionId) {
|
|
3704
|
+
const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
|
|
3705
|
+
headers["session_id"] = transportSessionId;
|
|
3706
|
+
headers["x-client-request-id"] = transportSessionId;
|
|
3707
|
+
}
|
|
3708
|
+
const encodedRequest = await encodeCodexRequest(
|
|
3709
|
+
mergeProviderOptions(body, options.providerOptions)
|
|
3710
|
+
);
|
|
3711
|
+
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
3712
|
+
providerDiag("codex_request_body", {
|
|
3713
|
+
rawBytes: encodedRequest.rawBytes,
|
|
3714
|
+
encodedBytes: encodedRequest.encodedBytes,
|
|
3715
|
+
compressed: encodedRequest.compressed
|
|
3716
|
+
});
|
|
3717
|
+
const response = await providerFetch(url, {
|
|
3718
|
+
method: "POST",
|
|
3719
|
+
headers,
|
|
3720
|
+
body: encodedRequest.body,
|
|
3721
|
+
signal: options.signal
|
|
3722
|
+
});
|
|
3723
|
+
if (!response.ok) {
|
|
3724
|
+
const text = await response.text().catch(() => "");
|
|
3725
|
+
const parsed = parseCodexErrorBody(text, response.status);
|
|
3726
|
+
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
3727
|
+
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
3728
|
+
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(
|
|
3729
|
+
(msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
|
|
3730
|
+
)) {
|
|
3731
|
+
providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
|
|
3732
|
+
const messages = options.messages.map(
|
|
3733
|
+
(msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
|
|
3734
|
+
...msg,
|
|
3735
|
+
content: msg.content.filter(
|
|
3736
|
+
(part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
|
|
3737
|
+
)
|
|
3738
|
+
} : msg
|
|
3739
|
+
);
|
|
3740
|
+
return yield* runStream3({ ...options, messages }, true);
|
|
3741
|
+
}
|
|
3742
|
+
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
3743
|
+
if (usageLimit) throw usageLimit;
|
|
3744
|
+
let hint;
|
|
3745
|
+
if (response.status === 400 && message === `The '${options.model}' model is not supported when using Codex with a ChatGPT account.`) {
|
|
3746
|
+
hint = "This model is not available through your ChatGPT account. Choose another available model using the model selector.";
|
|
3747
|
+
} else if (response.status === 404 && text.includes("does not exist")) {
|
|
3748
|
+
hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
|
|
3749
|
+
}
|
|
3750
|
+
throw new ProviderError("openai", message, {
|
|
3751
|
+
statusCode: response.status,
|
|
3752
|
+
...requestId ? { requestId } : {},
|
|
3753
|
+
...hint ? { hint } : {}
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
if (!response.body) {
|
|
3757
|
+
throw new ProviderError("openai", "No response body from Codex API");
|
|
3758
|
+
}
|
|
3759
|
+
return yield* consumeResponsesEvents(parseSSE(response.body), {
|
|
3760
|
+
provider: "openai",
|
|
3761
|
+
label: "Codex",
|
|
3762
|
+
diagPrefix: "codex",
|
|
3763
|
+
thinking: !!options.thinking,
|
|
3764
|
+
errorChunk: (errorObj, requestId) => codexUsageLimitError(errorObj, void 0, requestId)
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
async function* parseSSE(body) {
|
|
3768
|
+
for await (const event of readSseStream(body)) {
|
|
3769
|
+
const data = event.data.trim();
|
|
3770
|
+
if (!data || data === "[DONE]") continue;
|
|
3771
|
+
try {
|
|
3772
|
+
yield JSON.parse(data);
|
|
3773
|
+
} catch {
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
function remapCodexId(id, idMap) {
|
|
3778
|
+
const existing = idMap.get(id);
|
|
3779
|
+
if (existing) return existing;
|
|
3780
|
+
const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
|
|
3781
|
+
const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
3782
|
+
let mapped = sanitized;
|
|
3783
|
+
let suffix = 2;
|
|
3784
|
+
const used = new Set(idMap.values());
|
|
3785
|
+
while (used.has(mapped)) {
|
|
3786
|
+
mapped = `${sanitized}_${suffix++}`;
|
|
3787
|
+
}
|
|
3788
|
+
idMap.set(id, mapped);
|
|
3789
|
+
return mapped;
|
|
3790
|
+
}
|
|
3693
3791
|
function toCodexTools(tools, strictTools) {
|
|
3694
3792
|
return tools.map((tool) => {
|
|
3695
3793
|
let parameters = resolveToolSchema(tool);
|
|
@@ -3754,6 +3852,136 @@ function codexUsageLimitError(errorObj, statusCode, requestId) {
|
|
|
3754
3852
|
});
|
|
3755
3853
|
}
|
|
3756
3854
|
|
|
3855
|
+
// src/providers/xai-responses.ts
|
|
3856
|
+
function streamXaiResponses(options) {
|
|
3857
|
+
return new StreamResult(runStream4(options), options.signal);
|
|
3858
|
+
}
|
|
3859
|
+
function xaiReasoningOrigin(model) {
|
|
3860
|
+
return `xai:${model}`;
|
|
3861
|
+
}
|
|
3862
|
+
async function* runStream4(options, dropReasoning = false) {
|
|
3863
|
+
const providerName = options.provider ?? "xai";
|
|
3864
|
+
const client = createClient2(options);
|
|
3865
|
+
const origin = xaiReasoningOrigin(options.model);
|
|
3866
|
+
const useStreaming = options.streaming !== false;
|
|
3867
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
3868
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
3869
|
+
const { system, input } = toResponsesInput(downgraded, {
|
|
3870
|
+
supportsImages: options.supportsImages,
|
|
3871
|
+
keepReasoning: (part) => !dropReasoning && part.origin === origin,
|
|
3872
|
+
minimalAssistantItems: true
|
|
3873
|
+
});
|
|
3874
|
+
const replaysReasoning = input.some(isReasoningItem);
|
|
3875
|
+
const params = {
|
|
3876
|
+
model: options.model,
|
|
3877
|
+
input: system ? [{ role: "system", content: system }, ...input] : input,
|
|
3878
|
+
store: false,
|
|
3879
|
+
stream: useStreaming,
|
|
3880
|
+
include: ["reasoning.encrypted_content"],
|
|
3881
|
+
// Responses' cache-routing key (Chat used the x-grok-conv-id header).
|
|
3882
|
+
prompt_cache_key: normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder"),
|
|
3883
|
+
...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
|
|
3884
|
+
// Grok reasoning can't be disabled; with thinking off the server default
|
|
3885
|
+
// (high on Grok 4.7) applies, exactly as on the Chat path.
|
|
3886
|
+
...options.thinking ? { reasoning: { effort: toOpenAIReasoningEffort(options.thinking, options.model) } } : {},
|
|
3887
|
+
...options.temperature != null && !options.thinking ? { temperature: options.temperature } : {},
|
|
3888
|
+
...options.topP != null ? { top_p: options.topP } : {},
|
|
3889
|
+
...options.tools?.length ? { tools: toXaiTools(options.tools) } : {},
|
|
3890
|
+
...options.toolChoice && options.tools?.length ? { tool_choice: toXaiToolChoice(options.toolChoice) } : {}
|
|
3891
|
+
};
|
|
3892
|
+
const body = mergeProviderOptions(params, options.providerOptions);
|
|
3893
|
+
const requestOptions = {
|
|
3894
|
+
signal: options.signal ?? void 0,
|
|
3895
|
+
// A non-streamed reply arrives in one response; never undercut the agent
|
|
3896
|
+
// loop's own (up to 15min) cap with the SDK's 10min default.
|
|
3897
|
+
timeout: PROVIDER_TRANSPORT_TIMEOUT_MS,
|
|
3898
|
+
headers: {
|
|
3899
|
+
"x-grok-conv-id": normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder")
|
|
3900
|
+
}
|
|
3901
|
+
};
|
|
3902
|
+
let events;
|
|
3903
|
+
try {
|
|
3904
|
+
if (useStreaming) {
|
|
3905
|
+
const stream2 = await client.responses.create(
|
|
3906
|
+
body,
|
|
3907
|
+
requestOptions
|
|
3908
|
+
);
|
|
3909
|
+
events = stream2;
|
|
3910
|
+
} else {
|
|
3911
|
+
const response = await client.responses.create(
|
|
3912
|
+
body,
|
|
3913
|
+
requestOptions
|
|
3914
|
+
);
|
|
3915
|
+
events = synthesizeResponseEvents(response);
|
|
3916
|
+
}
|
|
3917
|
+
} catch (err) {
|
|
3918
|
+
if (replaysReasoning && !dropReasoning && !options.signal?.aborted && err.status === 400) {
|
|
3919
|
+
providerDiag("xai_retry_without_encrypted_reasoning", { status: 400 });
|
|
3920
|
+
return yield* runStream4(options, true);
|
|
3921
|
+
}
|
|
3922
|
+
throw toError2(err, providerName);
|
|
3923
|
+
}
|
|
3924
|
+
try {
|
|
3925
|
+
return yield* consumeResponsesEvents(events, {
|
|
3926
|
+
provider: providerName,
|
|
3927
|
+
label: "Grok",
|
|
3928
|
+
diagPrefix: "xai",
|
|
3929
|
+
thinking: !!options.thinking,
|
|
3930
|
+
reasoningOrigin: origin
|
|
3931
|
+
});
|
|
3932
|
+
} catch (err) {
|
|
3933
|
+
throw toError2(err, providerName);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
function isReasoningItem(item) {
|
|
3937
|
+
return typeof item === "object" && item !== null && isEncryptedReasoning(item);
|
|
3938
|
+
}
|
|
3939
|
+
function toXaiTools(tools) {
|
|
3940
|
+
return tools.map((tool) => ({
|
|
3941
|
+
type: "function",
|
|
3942
|
+
name: tool.name,
|
|
3943
|
+
description: tool.description,
|
|
3944
|
+
parameters: resolveToolSchema(tool)
|
|
3945
|
+
}));
|
|
3946
|
+
}
|
|
3947
|
+
function toXaiToolChoice(choice) {
|
|
3948
|
+
return typeof choice === "object" ? { type: "function", name: choice.name } : choice;
|
|
3949
|
+
}
|
|
3950
|
+
async function* synthesizeResponseEvents(response) {
|
|
3951
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
3952
|
+
for (const [index, raw] of output.entries()) {
|
|
3953
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
3954
|
+
const item = raw;
|
|
3955
|
+
const withId = typeof item.id === "string" ? item : { ...item, id: `item_${index}` };
|
|
3956
|
+
yield { type: "response.output_item.added", item: withId };
|
|
3957
|
+
if (withId.type === "message" && Array.isArray(withId.content)) {
|
|
3958
|
+
for (const [contentIndex, content] of withId.content.entries()) {
|
|
3959
|
+
const part = content;
|
|
3960
|
+
if (part?.type === "output_text" && typeof part.text === "string") {
|
|
3961
|
+
yield {
|
|
3962
|
+
type: "response.output_text.done",
|
|
3963
|
+
item_id: withId.id,
|
|
3964
|
+
content_index: contentIndex,
|
|
3965
|
+
text: part.text
|
|
3966
|
+
};
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
if (withId.type === "function_call") {
|
|
3971
|
+
yield {
|
|
3972
|
+
type: "response.function_call_arguments.done",
|
|
3973
|
+
item_id: withId.id,
|
|
3974
|
+
arguments: typeof withId.arguments === "string" ? withId.arguments : ""
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
yield { type: "response.output_item.done", item: withId };
|
|
3978
|
+
}
|
|
3979
|
+
yield {
|
|
3980
|
+
type: response.status === "incomplete" ? "response.incomplete" : "response.completed",
|
|
3981
|
+
response
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3757
3985
|
// src/providers/gemini.ts
|
|
3758
3986
|
var DEFAULT_CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
|
|
3759
3987
|
var CODE_ASSIST_API_VERSION = "v1internal";
|
|
@@ -4172,7 +4400,7 @@ async function sleep(ms, signal) {
|
|
|
4172
4400
|
}
|
|
4173
4401
|
async function fetchCodeAssist(plan, options) {
|
|
4174
4402
|
try {
|
|
4175
|
-
const response = await
|
|
4403
|
+
const response = await providerFetch(plan.url, {
|
|
4176
4404
|
method: "POST",
|
|
4177
4405
|
headers: plan.headers,
|
|
4178
4406
|
body: JSON.stringify(plan.body),
|
|
@@ -4225,9 +4453,9 @@ async function fetchCodeAssistWithRetry(plan, options) {
|
|
|
4225
4453
|
throw lastError ?? new ProviderError("gemini", "Gemini Code Assist request failed.");
|
|
4226
4454
|
}
|
|
4227
4455
|
function streamGemini(options) {
|
|
4228
|
-
return new StreamResult(
|
|
4456
|
+
return new StreamResult(runStream5(options), options.signal);
|
|
4229
4457
|
}
|
|
4230
|
-
async function*
|
|
4458
|
+
async function* runStream5(options) {
|
|
4231
4459
|
const useStreaming = options.streaming !== false;
|
|
4232
4460
|
const method = useStreaming ? "streamGenerateContent" : "generateContent";
|
|
4233
4461
|
const plan = buildRequestPlan(options, method);
|
|
@@ -5080,13 +5308,12 @@ providerRegistry.register("sakana", {
|
|
|
5080
5308
|
})
|
|
5081
5309
|
});
|
|
5082
5310
|
providerRegistry.register("xai", {
|
|
5083
|
-
// xAI's
|
|
5084
|
-
// Completions
|
|
5085
|
-
//
|
|
5086
|
-
// path already sends.
|
|
5311
|
+
// Both Grok routes ride xAI's Responses API (see providers/xai-responses.ts):
|
|
5312
|
+
// Chat Completions is deprecated for Grok reasoning models and can't carry
|
|
5313
|
+
// the encrypted reasoning that keeps long tool loops coherent.
|
|
5087
5314
|
//
|
|
5088
5315
|
// Subscription OAuth (SuperGrok / X Premium) routes to the Grok CLI chat proxy
|
|
5089
|
-
// instead, which
|
|
5316
|
+
// instead, which serves the same `/v1/responses` surface but gates on Grok-CLI
|
|
5090
5317
|
// client identity. Inject those headers centrally here — exactly as the Kimi
|
|
5091
5318
|
// endpoint above — so EVERY stream (agent loop, compaction, title-gen,
|
|
5092
5319
|
// sub-agents) is accepted rather than depending on each call site to thread
|
|
@@ -5100,7 +5327,7 @@ providerRegistry.register("xai", {
|
|
|
5100
5327
|
"x-grok-model-override": options.model,
|
|
5101
5328
|
...options.defaultHeaders
|
|
5102
5329
|
} : options.defaultHeaders;
|
|
5103
|
-
return
|
|
5330
|
+
return streamXaiResponses({ ...options, baseUrl, defaultHeaders });
|
|
5104
5331
|
}
|
|
5105
5332
|
});
|
|
5106
5333
|
providerRegistry.register("minimax", {
|
|
@@ -5420,6 +5647,13 @@ function formatChatError(error) {
|
|
|
5420
5647
|
"Wait for the reset, or use another provider."
|
|
5421
5648
|
);
|
|
5422
5649
|
}
|
|
5650
|
+
if (error instanceof ResponseStalledError) {
|
|
5651
|
+
return present(
|
|
5652
|
+
"network",
|
|
5653
|
+
"The response kept stalling",
|
|
5654
|
+
error.inToolCall ? "Ask for the change in smaller pieces, then try again." : "Try again, or choose another model if it keeps happening."
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5423
5657
|
if (base.source === "network") {
|
|
5424
5658
|
return present(
|
|
5425
5659
|
"network",
|
|
@@ -5801,6 +6035,7 @@ function palsuRerankScore(query, document) {
|
|
|
5801
6035
|
GGAIError,
|
|
5802
6036
|
ProviderError,
|
|
5803
6037
|
REDACTION_MARKER,
|
|
6038
|
+
ResponseStalledError,
|
|
5804
6039
|
StreamResult,
|
|
5805
6040
|
TOOL_CALL_NAME_RULES,
|
|
5806
6041
|
clampProviderContextImages,
|