@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.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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
3014
|
-
|
|
3015
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 ??
|
|
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 =
|
|
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(
|
|
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 ??
|
|
3111
|
+
const message = nested?.message ?? `${ctx.label} response failed.`;
|
|
3237
3112
|
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
3238
|
-
throw new ProviderError(
|
|
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 (
|
|
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 (
|
|
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"
|
|
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(
|
|
3412
|
-
|
|
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
|
-
|
|
3421
|
-
|
|
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(
|
|
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
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
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
|
|
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:
|
|
3567
|
-
call_id:
|
|
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:
|
|
3464
|
+
call_id: mapId(callId, idMap),
|
|
3583
3465
|
output: text.length > 0 ? text : "(see attached image)"
|
|
3584
3466
|
});
|
|
3585
|
-
if (options
|
|
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
|
|
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(
|
|
4371
|
+
return new StreamResult(runStream5(options), options.signal);
|
|
4145
4372
|
}
|
|
4146
|
-
async function*
|
|
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
|
|
5000
|
-
// Completions
|
|
5001
|
-
//
|
|
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
|
|
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
|
|
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,
|