@kenkaiiii/gg-ai 5.94.0 → 5.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/dist/index.cjs +211 -314
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -21
- package/dist/index.d.ts +17 -21
- package/dist/index.js +210 -313
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
CODEX_CLIENT_VERSION: () => CODEX_CLIENT_VERSION,
|
|
34
33
|
EventStream: () => EventStream,
|
|
35
34
|
FAST_MODE_SUFFIX: () => FAST_MODE_SUFFIX,
|
|
36
35
|
GGAIError: () => GGAIError,
|
|
36
|
+
GG_CODER_USER_AGENT: () => GG_CODER_USER_AGENT,
|
|
37
37
|
LOCAL_NO_API_KEY: () => LOCAL_NO_API_KEY,
|
|
38
38
|
PRECONNECT_INTERVAL_MS: () => PRECONNECT_INTERVAL_MS,
|
|
39
39
|
ProviderError: () => ProviderError,
|
|
@@ -333,6 +333,18 @@ function formatError(err) {
|
|
|
333
333
|
}
|
|
334
334
|
const miniMax = err.provider === "minimax" ? formatMiniMaxError(err, cleanMessage) : void 0;
|
|
335
335
|
if (miniMax) return miniMax;
|
|
336
|
+
if (isUsageLimitError(err) && err.provider === "openai" && /chatgpt/i.test(err.message)) {
|
|
337
|
+
return {
|
|
338
|
+
headline: "ChatGPT plan usage limit reached.",
|
|
339
|
+
source: "provider",
|
|
340
|
+
message: "GG Coder has used what your ChatGPT plan allows for now. That can be your plan's limit or one you set for GG Coder.",
|
|
341
|
+
provider: err.provider,
|
|
342
|
+
statusCode: err.statusCode,
|
|
343
|
+
...err.requestId ? { requestId: err.requestId } : {},
|
|
344
|
+
...err.resetsAt ? { resetsAt: err.resetsAt } : {},
|
|
345
|
+
guidance: err.hint ?? "Check or raise it at chatgpt.com/settings/usage, or add an OpenAI API key to keep going."
|
|
346
|
+
};
|
|
347
|
+
}
|
|
336
348
|
if (isUsageLimitError(err)) {
|
|
337
349
|
const resetClause = err.resetsAt ? ` It resets at ${formatResetTime(err.resetsAt)}.` : "";
|
|
338
350
|
return {
|
|
@@ -1233,7 +1245,7 @@ function toolCallNameRuleFor(provider, options) {
|
|
|
1233
1245
|
return TOOL_CALL_NAME_RULES.anthropic;
|
|
1234
1246
|
case "openai":
|
|
1235
1247
|
if (options?.accountId) return TOOL_CALL_NAME_RULES["openai-responses"];
|
|
1236
|
-
return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-
|
|
1248
|
+
return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-responses"] : TOOL_CALL_NAME_RULES.generic;
|
|
1237
1249
|
case "gemini":
|
|
1238
1250
|
return TOOL_CALL_NAME_RULES.gemini;
|
|
1239
1251
|
default:
|
|
@@ -1431,8 +1443,8 @@ function usesDeferredToolLoading(options) {
|
|
|
1431
1443
|
const firstParty = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
|
|
1432
1444
|
return firstParty && supportsAnthropicDeferredTools(options.model);
|
|
1433
1445
|
}
|
|
1434
|
-
if (options.provider === "openai" && options.accountId) {
|
|
1435
|
-
return supportsResponsesToolSearch(options.model);
|
|
1446
|
+
if (options.provider === "openai" && !options.accountId) {
|
|
1447
|
+
return isDefaultOrHost(options.baseUrl, "api.openai.com") && supportsResponsesToolSearch(options.model);
|
|
1436
1448
|
}
|
|
1437
1449
|
return false;
|
|
1438
1450
|
}
|
|
@@ -2007,12 +2019,12 @@ function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {
|
|
|
2007
2019
|
};
|
|
2008
2020
|
const dispatcher = useEnvProxy ? new import_undici.EnvHttpProxyAgent(timeouts) : new import_undici.Agent(timeouts);
|
|
2009
2021
|
const builtinFetch = globalThis.fetch;
|
|
2010
|
-
const providerFetch2 = async (input,
|
|
2011
|
-
if (globalThis.fetch !== builtinFetch || input instanceof Request ||
|
|
2012
|
-
return globalThis.fetch(input,
|
|
2022
|
+
const providerFetch2 = async (input, init) => {
|
|
2023
|
+
if (globalThis.fetch !== builtinFetch || input instanceof Request || init?.body instanceof FormData) {
|
|
2024
|
+
return globalThis.fetch(input, init);
|
|
2013
2025
|
}
|
|
2014
2026
|
const response = await (0, import_undici.fetch)(input, {
|
|
2015
|
-
...
|
|
2027
|
+
...init,
|
|
2016
2028
|
dispatcher
|
|
2017
2029
|
});
|
|
2018
2030
|
return response;
|
|
@@ -2020,8 +2032,8 @@ function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {
|
|
|
2020
2032
|
return Object.assign(providerFetch2, { Response: globalThis.Response });
|
|
2021
2033
|
}
|
|
2022
2034
|
function withBodyActivity(fetchImpl, onActivity) {
|
|
2023
|
-
return async (input,
|
|
2024
|
-
const response = await fetchImpl(input,
|
|
2035
|
+
return async (input, init) => {
|
|
2036
|
+
const response = await fetchImpl(input, init);
|
|
2025
2037
|
if (!response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
2026
2038
|
return response;
|
|
2027
2039
|
}
|
|
@@ -3336,10 +3348,6 @@ function toError2(err, provider = "openai") {
|
|
|
3336
3348
|
return new ProviderError(provider, String(err));
|
|
3337
3349
|
}
|
|
3338
3350
|
|
|
3339
|
-
// src/providers/openai-codex.ts
|
|
3340
|
-
var import_node_os = __toESM(require("os"), 1);
|
|
3341
|
-
var zstd = __toESM(require("@bokuweb/zstd-wasm"), 1);
|
|
3342
|
-
|
|
3343
3351
|
// src/utils/sse.ts
|
|
3344
3352
|
function parseSseBuffer(buffer) {
|
|
3345
3353
|
const events = [];
|
|
@@ -3391,6 +3399,9 @@ function extractRequestIdFromMessage(message) {
|
|
|
3391
3399
|
return match?.[1];
|
|
3392
3400
|
}
|
|
3393
3401
|
|
|
3402
|
+
// src/user-agent.ts
|
|
3403
|
+
var GG_CODER_USER_AGENT = "ggcoder (+https://github.com/KenKaiii/gg-coder)";
|
|
3404
|
+
|
|
3394
3405
|
// src/providers/responses-stream.ts
|
|
3395
3406
|
function isEncryptedReasoning(data) {
|
|
3396
3407
|
return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
|
|
@@ -3438,9 +3449,11 @@ async function* consumeResponsesEvents(events, ctx) {
|
|
|
3438
3449
|
});
|
|
3439
3450
|
}
|
|
3440
3451
|
if (type === "response.failed") {
|
|
3441
|
-
const nested = event.error;
|
|
3452
|
+
const nested = event.error ?? event.response?.error;
|
|
3442
3453
|
const message = nested?.message ?? `${ctx.label} response failed.`;
|
|
3443
3454
|
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
3455
|
+
const usageLimit = nested ? ctx.errorChunk?.(nested, requestId) : null;
|
|
3456
|
+
if (usageLimit) throw usageLimit;
|
|
3444
3457
|
throw new ProviderError(ctx.provider, message, {
|
|
3445
3458
|
...requestId != null ? { requestId } : {}
|
|
3446
3459
|
});
|
|
@@ -3790,6 +3803,7 @@ function toResponsesInput(messages, options) {
|
|
|
3790
3803
|
...sendItemId ? { id: mapId(itemId ?? callId, idMap) } : {},
|
|
3791
3804
|
call_id: mapId(callId, idMap),
|
|
3792
3805
|
name: part.name,
|
|
3806
|
+
...options.toolNamespace ? { namespace: options.toolNamespace } : {},
|
|
3793
3807
|
arguments: JSON.stringify(part.args)
|
|
3794
3808
|
});
|
|
3795
3809
|
}
|
|
@@ -3853,85 +3867,46 @@ function toResponsesInput(messages, options) {
|
|
|
3853
3867
|
return { system, input };
|
|
3854
3868
|
}
|
|
3855
3869
|
|
|
3856
|
-
// src/providers/openai-
|
|
3857
|
-
var DEFAULT_BASE_URL = "https://
|
|
3858
|
-
var
|
|
3859
|
-
var
|
|
3860
|
-
var zstdInitPromise;
|
|
3861
|
-
async function encodeCodexRequest(body) {
|
|
3862
|
-
const json = JSON.stringify(body);
|
|
3863
|
-
const raw = new TextEncoder().encode(json);
|
|
3864
|
-
if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
|
|
3865
|
-
return {
|
|
3866
|
-
body: json,
|
|
3867
|
-
compressed: false,
|
|
3868
|
-
rawBytes: raw.byteLength,
|
|
3869
|
-
encodedBytes: raw.byteLength
|
|
3870
|
-
};
|
|
3871
|
-
}
|
|
3872
|
-
try {
|
|
3873
|
-
zstdInitPromise ??= zstd.init();
|
|
3874
|
-
await zstdInitPromise;
|
|
3875
|
-
const compressed = Uint8Array.from(zstd.compress(raw));
|
|
3876
|
-
if (compressed.byteLength >= raw.byteLength) {
|
|
3877
|
-
return {
|
|
3878
|
-
body: json,
|
|
3879
|
-
compressed: false,
|
|
3880
|
-
rawBytes: raw.byteLength,
|
|
3881
|
-
encodedBytes: raw.byteLength
|
|
3882
|
-
};
|
|
3883
|
-
}
|
|
3884
|
-
return {
|
|
3885
|
-
body: compressed,
|
|
3886
|
-
compressed: true,
|
|
3887
|
-
rawBytes: raw.byteLength,
|
|
3888
|
-
encodedBytes: compressed.byteLength
|
|
3889
|
-
};
|
|
3890
|
-
} catch (error) {
|
|
3891
|
-
providerDiag("codex_request_compression_failed", {
|
|
3892
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3893
|
-
rawBytes: raw.byteLength
|
|
3894
|
-
});
|
|
3895
|
-
return {
|
|
3896
|
-
body: json,
|
|
3897
|
-
compressed: false,
|
|
3898
|
-
rawBytes: raw.byteLength,
|
|
3899
|
-
encodedBytes: raw.byteLength
|
|
3900
|
-
};
|
|
3901
|
-
}
|
|
3902
|
-
}
|
|
3870
|
+
// src/providers/openai-responses.ts
|
|
3871
|
+
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
3872
|
+
var CHATGPT_PLAN_TOOL_NAMESPACE = "gg";
|
|
3873
|
+
var CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
|
|
3903
3874
|
function usesResponsesLite(model) {
|
|
3904
3875
|
return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
|
|
3905
3876
|
}
|
|
3906
|
-
function
|
|
3877
|
+
function isChatGPTPlanRoute(options) {
|
|
3878
|
+
return Boolean(options.accountId);
|
|
3879
|
+
}
|
|
3880
|
+
function toResponsesToolChoice(choice, tools) {
|
|
3907
3881
|
const resolved = choice ?? "auto";
|
|
3908
3882
|
if (typeof resolved === "object") {
|
|
3909
3883
|
throw new GGAIError(
|
|
3910
|
-
`OpenAI
|
|
3884
|
+
`OpenAI Responses does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
|
|
3911
3885
|
{ source: "capability" }
|
|
3912
3886
|
);
|
|
3913
3887
|
}
|
|
3914
3888
|
if (resolved === "required" && !tools?.length) {
|
|
3915
|
-
throw new GGAIError("OpenAI
|
|
3889
|
+
throw new GGAIError("OpenAI cannot require a tool call when no tools are configured.", {
|
|
3916
3890
|
source: "capability"
|
|
3917
3891
|
});
|
|
3918
3892
|
}
|
|
3919
3893
|
return resolved;
|
|
3920
3894
|
}
|
|
3921
|
-
function
|
|
3895
|
+
function streamOpenAIResponses(options) {
|
|
3922
3896
|
return new StreamResult(runStream3(options), options.signal);
|
|
3923
3897
|
}
|
|
3924
3898
|
async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
3899
|
+
const plan = isChatGPTPlanRoute(options);
|
|
3925
3900
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
3926
|
-
const url = `${baseUrl}/
|
|
3901
|
+
const url = `${baseUrl}/responses`;
|
|
3927
3902
|
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
3928
3903
|
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
3929
|
-
const deferredPlan = supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
|
|
3904
|
+
const deferredPlan = !plan && supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
|
|
3930
3905
|
const strictTools = options.strictTools ?? true;
|
|
3931
3906
|
const toolSearchLoads = deferredPlan ? new Map(
|
|
3932
3907
|
[...deferredPlan.loaded].map((name) => {
|
|
3933
3908
|
const tool = deferredPlan.byName.get(name);
|
|
3934
|
-
const [wire] = tool ?
|
|
3909
|
+
const [wire] = tool ? toResponsesTools([tool], strictTools) : [];
|
|
3935
3910
|
return [name, { ...wire, defer_loading: true }];
|
|
3936
3911
|
})
|
|
3937
3912
|
) : void 0;
|
|
@@ -3939,28 +3914,36 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3939
3914
|
const { system, input } = toResponsesInput(downgraded, {
|
|
3940
3915
|
supportsImages: options.supportsImages,
|
|
3941
3916
|
toolSearchLoads,
|
|
3942
|
-
mapId:
|
|
3917
|
+
mapId: remapCallId,
|
|
3918
|
+
...plan ? { toolNamespace: CHATGPT_PLAN_TOOL_NAMESPACE } : {},
|
|
3943
3919
|
// Stamped reasoning belongs to another transport (e.g. Grok's) and would
|
|
3944
|
-
// be rejected here;
|
|
3920
|
+
// be rejected here; OpenAI's own items are never stamped.
|
|
3945
3921
|
keepReasoning: (part) => part.origin === void 0
|
|
3946
3922
|
});
|
|
3947
|
-
const
|
|
3948
|
-
const liteShape = options.responsesLite ?? responsesLite;
|
|
3923
|
+
const gpt6 = usesResponsesLite(options.model);
|
|
3949
3924
|
const body = {
|
|
3950
3925
|
model: options.model,
|
|
3951
3926
|
store: false,
|
|
3952
3927
|
stream: true,
|
|
3953
3928
|
instructions: system,
|
|
3954
3929
|
input,
|
|
3955
|
-
tool_choice:
|
|
3956
|
-
parallel_tool_calls:
|
|
3930
|
+
tool_choice: toResponsesToolChoice(options.toolChoice, liveTools),
|
|
3931
|
+
parallel_tool_calls: true,
|
|
3957
3932
|
include: ["reasoning.encrypted_content"]
|
|
3958
3933
|
};
|
|
3959
3934
|
if (liveTools?.length) {
|
|
3960
|
-
|
|
3935
|
+
const wire = toResponsesTools(liveTools, strictTools);
|
|
3936
|
+
body.tools = plan ? [
|
|
3937
|
+
{
|
|
3938
|
+
type: "namespace",
|
|
3939
|
+
name: CHATGPT_PLAN_TOOL_NAMESPACE,
|
|
3940
|
+
description: "GG Coder's tools for working in the user's project.",
|
|
3941
|
+
tools: wire
|
|
3942
|
+
}
|
|
3943
|
+
] : wire;
|
|
3961
3944
|
}
|
|
3962
3945
|
body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
|
|
3963
|
-
if (options.temperature != null && !options.thinking) {
|
|
3946
|
+
if (!plan && options.temperature != null && !options.thinking) {
|
|
3964
3947
|
body.temperature = options.temperature;
|
|
3965
3948
|
}
|
|
3966
3949
|
if (options.speed === "fast") {
|
|
@@ -3968,57 +3951,38 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3968
3951
|
}
|
|
3969
3952
|
body.reasoning = {
|
|
3970
3953
|
// GPT-5.6/6 require at least low; older models still support thinking off.
|
|
3971
|
-
//
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
summary: "auto",
|
|
3975
|
-
...liteShape ? { context: "all_turns" } : {}
|
|
3954
|
+
// `ultra` is a client orchestration preset, not an API effort.
|
|
3955
|
+
effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : gpt6 ? "low" : "none",
|
|
3956
|
+
summary: "auto"
|
|
3976
3957
|
};
|
|
3977
|
-
if (
|
|
3958
|
+
if (gpt6) {
|
|
3978
3959
|
body.text = { verbosity: "low" };
|
|
3979
3960
|
}
|
|
3980
3961
|
const headers = {
|
|
3981
3962
|
"Content-Type": "application/json",
|
|
3982
3963
|
Accept: "text/event-stream",
|
|
3983
3964
|
Authorization: `Bearer ${options.apiKey}`,
|
|
3984
|
-
"
|
|
3985
|
-
originator: responsesLite ? "codex_cli_rs" : "ggcoder",
|
|
3986
|
-
"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()})`,
|
|
3987
|
-
...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
|
|
3988
|
-
...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
|
|
3965
|
+
"User-Agent": GG_CODER_USER_AGENT
|
|
3989
3966
|
};
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
}
|
|
3993
|
-
if (options.transportSessionId) {
|
|
3994
|
-
const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
|
|
3995
|
-
headers["session_id"] = transportSessionId;
|
|
3996
|
-
headers["x-client-request-id"] = transportSessionId;
|
|
3997
|
-
}
|
|
3998
|
-
const encodedRequest = await encodeCodexRequest(
|
|
3999
|
-
mergeProviderOptions(body, options.providerOptions)
|
|
4000
|
-
);
|
|
4001
|
-
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
4002
|
-
providerDiag("codex_request_body", {
|
|
4003
|
-
rawBytes: encodedRequest.rawBytes,
|
|
4004
|
-
encodedBytes: encodedRequest.encodedBytes,
|
|
4005
|
-
compressed: encodedRequest.compressed
|
|
4006
|
-
});
|
|
3967
|
+
const json = JSON.stringify(mergeProviderOptions(body, options.providerOptions));
|
|
3968
|
+
providerDiag("openai_responses_request_body", { rawBytes: json.length, plan });
|
|
4007
3969
|
const response = await providerFetch(url, {
|
|
4008
3970
|
method: "POST",
|
|
4009
3971
|
headers,
|
|
4010
|
-
body:
|
|
3972
|
+
body: json,
|
|
4011
3973
|
signal: options.signal
|
|
4012
3974
|
});
|
|
4013
3975
|
if (!response.ok) {
|
|
4014
3976
|
const text = await response.text().catch(() => "");
|
|
4015
|
-
const parsed =
|
|
4016
|
-
const message = parsed.message ?? `
|
|
3977
|
+
const parsed = parseErrorBody(text, response.status);
|
|
3978
|
+
const message = parsed.message ?? `OpenAI returned HTTP ${response.status}.`;
|
|
4017
3979
|
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
4018
3980
|
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(
|
|
4019
3981
|
(msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
|
|
4020
3982
|
)) {
|
|
4021
|
-
providerDiag("
|
|
3983
|
+
providerDiag("openai_responses_retry_without_encrypted_reasoning", {
|
|
3984
|
+
status: response.status
|
|
3985
|
+
});
|
|
4022
3986
|
const messages = options.messages.map(
|
|
4023
3987
|
(msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
|
|
4024
3988
|
...msg,
|
|
@@ -4029,13 +3993,19 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
4029
3993
|
);
|
|
4030
3994
|
return yield* runStream3({ ...options, messages }, true);
|
|
4031
3995
|
}
|
|
4032
|
-
|
|
4033
|
-
|
|
3996
|
+
if (plan) {
|
|
3997
|
+
const planError = chatGPTPlanError(
|
|
3998
|
+
parsed.errorObj,
|
|
3999
|
+
response.status,
|
|
4000
|
+
requestId,
|
|
4001
|
+
message,
|
|
4002
|
+
options.model
|
|
4003
|
+
);
|
|
4004
|
+
if (planError) throw planError;
|
|
4005
|
+
}
|
|
4034
4006
|
let hint;
|
|
4035
|
-
if (response.status ===
|
|
4036
|
-
hint = "This model
|
|
4037
|
-
} else if (response.status === 404 && text.includes("does not exist")) {
|
|
4038
|
-
hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
|
|
4007
|
+
if (response.status === 404 && text.includes("does not exist")) {
|
|
4008
|
+
hint = plan ? "This model isn't available on your ChatGPT plan. Choose another model using the model selector." : "This model isn't available to your OpenAI API key. Choose another model using the model selector.";
|
|
4039
4009
|
}
|
|
4040
4010
|
throw new ProviderError("openai", message, {
|
|
4041
4011
|
statusCode: response.status,
|
|
@@ -4044,14 +4014,14 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
4044
4014
|
});
|
|
4045
4015
|
}
|
|
4046
4016
|
if (!response.body) {
|
|
4047
|
-
throw new ProviderError("openai", "No response body from
|
|
4017
|
+
throw new ProviderError("openai", "No response body from OpenAI");
|
|
4048
4018
|
}
|
|
4049
4019
|
return yield* consumeResponsesEvents(parseSSE(response.body), {
|
|
4050
4020
|
provider: "openai",
|
|
4051
|
-
label: "
|
|
4052
|
-
diagPrefix: "
|
|
4021
|
+
label: "OpenAI",
|
|
4022
|
+
diagPrefix: "openai_responses",
|
|
4053
4023
|
thinking: !!options.thinking,
|
|
4054
|
-
errorChunk: (errorObj, requestId) =>
|
|
4024
|
+
errorChunk: (errorObj, requestId) => plan ? chatGPTPlanError(errorObj, void 0, requestId, void 0, options.model) : null
|
|
4055
4025
|
});
|
|
4056
4026
|
}
|
|
4057
4027
|
async function* parseSSE(body) {
|
|
@@ -4064,7 +4034,7 @@ async function* parseSSE(body) {
|
|
|
4064
4034
|
}
|
|
4065
4035
|
}
|
|
4066
4036
|
}
|
|
4067
|
-
function
|
|
4037
|
+
function remapCallId(id, idMap) {
|
|
4068
4038
|
const existing = idMap.get(id);
|
|
4069
4039
|
if (existing) return existing;
|
|
4070
4040
|
const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
|
|
@@ -4078,7 +4048,7 @@ function remapCodexId(id, idMap) {
|
|
|
4078
4048
|
idMap.set(id, mapped);
|
|
4079
4049
|
return mapped;
|
|
4080
4050
|
}
|
|
4081
|
-
function
|
|
4051
|
+
function toResponsesTools(tools, strictTools) {
|
|
4082
4052
|
return tools.map((tool) => {
|
|
4083
4053
|
let parameters = resolveToolSchema(tool);
|
|
4084
4054
|
let strict = null;
|
|
@@ -4099,7 +4069,7 @@ function toCodexTools(tools, strictTools) {
|
|
|
4099
4069
|
};
|
|
4100
4070
|
});
|
|
4101
4071
|
}
|
|
4102
|
-
function
|
|
4072
|
+
function parseErrorBody(text, statusCode) {
|
|
4103
4073
|
if (!text) return {};
|
|
4104
4074
|
try {
|
|
4105
4075
|
const parsed = JSON.parse(text);
|
|
@@ -4123,23 +4093,70 @@ function parseCodexErrorBody(text, statusCode) {
|
|
|
4123
4093
|
return bounded ? { message: bounded } : {};
|
|
4124
4094
|
}
|
|
4125
4095
|
}
|
|
4126
|
-
|
|
4127
|
-
var CODEX_RATE_LIMIT_CODE = /rate_limit_exceeded/i;
|
|
4128
|
-
function codexUsageLimitError(errorObj, statusCode, requestId) {
|
|
4096
|
+
function chatGPTPlanError(errorObj, statusCode, requestId, message, model) {
|
|
4129
4097
|
const code = String(errorObj?.code ?? errorObj?.type ?? "");
|
|
4130
|
-
const
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4098
|
+
const base = { ...requestId ? { requestId } : {}, cause: errorObj };
|
|
4099
|
+
switch (code) {
|
|
4100
|
+
// The plan's usage — or the limit the user set for GG Coder — is spent, or
|
|
4101
|
+
// can't be checked right now. Both stop plan requests; the canonical
|
|
4102
|
+
// "usage limit reached" message lets the session fall back to an API key.
|
|
4103
|
+
case "subscription_sharing_usage_limit_exceeded":
|
|
4104
|
+
case "subscription_sharing_usage_unavailable":
|
|
4105
|
+
return new ProviderError("openai", "ChatGPT usage limit reached", {
|
|
4106
|
+
...base,
|
|
4107
|
+
statusCode: statusCode ?? (code.endsWith("unavailable") ? 503 : 429),
|
|
4108
|
+
hint: code === "subscription_sharing_usage_unavailable" ? "OpenAI couldn't check your ChatGPT plan usage just now. Try again shortly, or add an OpenAI API key to keep going." : `Check or raise what GG Coder may use at ${CHATGPT_USAGE_URL}, or add an OpenAI API key to keep going.`
|
|
4109
|
+
});
|
|
4110
|
+
case "subscription_sharing_user_not_eligible":
|
|
4111
|
+
return new ProviderError(
|
|
4112
|
+
"openai",
|
|
4113
|
+
"ChatGPT plan use isn't available for this account or workspace.",
|
|
4114
|
+
{
|
|
4115
|
+
...base,
|
|
4116
|
+
statusCode: statusCode ?? 403,
|
|
4117
|
+
hint: "Add an OpenAI API key, or ask your workspace admin to allow ChatGPT plan use in other apps."
|
|
4118
|
+
}
|
|
4119
|
+
);
|
|
4120
|
+
// The account's plan doesn't include this model (live: 400 model_not_found,
|
|
4121
|
+
// "…not supported when using Codex with a ChatGPT account").
|
|
4122
|
+
case "model_not_found":
|
|
4123
|
+
return new ProviderError(
|
|
4124
|
+
"openai",
|
|
4125
|
+
`${model ?? "This model"} isn't included in your ChatGPT plan.`,
|
|
4126
|
+
{
|
|
4127
|
+
...base,
|
|
4128
|
+
statusCode: statusCode ?? 400,
|
|
4129
|
+
hint: "Pick a model your plan includes (the model picker marks the others), or use an OpenAI API key for this one."
|
|
4130
|
+
}
|
|
4131
|
+
);
|
|
4132
|
+
case "subscription_sharing_unsupported_capability": {
|
|
4133
|
+
const param = typeof errorObj?.param === "string" ? errorObj.param : void 0;
|
|
4134
|
+
if (param?.includes("service_tier")) {
|
|
4135
|
+
return new ProviderError("openai", "Fast mode isn't included in your ChatGPT plan.", {
|
|
4136
|
+
...base,
|
|
4137
|
+
statusCode: statusCode ?? 400,
|
|
4138
|
+
hint: "Switch to the standard model, or use an OpenAI API key for Fast mode."
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
return new ProviderError(
|
|
4142
|
+
"openai",
|
|
4143
|
+
message ?? `The ChatGPT plan route doesn't support ${param ?? "part of this request"}.`,
|
|
4144
|
+
{
|
|
4145
|
+
...base,
|
|
4146
|
+
statusCode: statusCode ?? 400,
|
|
4147
|
+
hint: `${param ? `\`${param}\` isn't` : "Part of this request isn't"} available on the ChatGPT plan. Choose another model, or add an OpenAI API key.`
|
|
4148
|
+
}
|
|
4149
|
+
);
|
|
4150
|
+
}
|
|
4151
|
+
case "subscription_sharing_invalid_user":
|
|
4152
|
+
return new ProviderError("openai", message ?? "ChatGPT couldn't validate your sign-in.", {
|
|
4153
|
+
...base,
|
|
4154
|
+
statusCode: statusCode ?? 401,
|
|
4155
|
+
hint: "Continue with ChatGPT again to reconnect."
|
|
4156
|
+
});
|
|
4157
|
+
default:
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4143
4160
|
}
|
|
4144
4161
|
|
|
4145
4162
|
// src/providers/xai-responses.ts
|
|
@@ -4273,92 +4290,17 @@ async function* synthesizeResponseEvents(response) {
|
|
|
4273
4290
|
}
|
|
4274
4291
|
|
|
4275
4292
|
// src/providers/gemini.ts
|
|
4276
|
-
var
|
|
4277
|
-
var
|
|
4278
|
-
var
|
|
4279
|
-
var GEMINI_CLI_API_CLIENT = "gemini-cli/0.0.0";
|
|
4280
|
-
var CODE_ASSIST_NON_STREAMING_RETRIES = 3;
|
|
4281
|
-
var CODE_ASSIST_NON_STREAMING_RETRY_DELAY_MS = 1e3;
|
|
4293
|
+
var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com/v1beta";
|
|
4294
|
+
var NON_STREAMING_RETRIES = 3;
|
|
4295
|
+
var NON_STREAMING_RETRY_DELAY_MS = 1e3;
|
|
4282
4296
|
var SYNTHETIC_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
|
|
4283
|
-
var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
|
|
4284
|
-
"gemini-3-pro-preview",
|
|
4285
|
-
"gemini-3.1-pro-preview",
|
|
4286
|
-
"gemini-3.1-pro-preview-customtools",
|
|
4287
|
-
"gemini-3-flash-preview",
|
|
4288
|
-
"gemini-3.5-flash",
|
|
4289
|
-
"gemini-3-flash",
|
|
4290
|
-
"gemini-3.1-flash-lite",
|
|
4291
|
-
"gemini-3.7-flash",
|
|
4292
|
-
"gemini-3.8-flash",
|
|
4293
|
-
"gemini-3.5-flash-lite",
|
|
4294
|
-
"gemini-2.5-pro",
|
|
4295
|
-
"gemini-2.5-flash",
|
|
4296
|
-
"gemma-4-31b-it",
|
|
4297
|
-
"gemma-4-26b-a4b-it"
|
|
4298
|
-
]);
|
|
4299
|
-
function isGeminiApiKeyRoute(options) {
|
|
4300
|
-
return options.baseUrl?.includes("generativelanguage.googleapis.com") === true;
|
|
4301
|
-
}
|
|
4302
|
-
function getGoogleProject(options) {
|
|
4303
|
-
const env = getEnvironment();
|
|
4304
|
-
return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
|
|
4305
|
-
}
|
|
4306
|
-
function getCodeAssistEndpoint(method) {
|
|
4307
|
-
const env = getEnvironment();
|
|
4308
|
-
const endpoint = env?.CODE_ASSIST_ENDPOINT ?? DEFAULT_CODE_ASSIST_BASE_URL;
|
|
4309
|
-
const version = env?.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
|
|
4310
|
-
return new URL(`${endpoint}/${version}:${method}`);
|
|
4311
|
-
}
|
|
4312
|
-
function formatUnsupportedModelMessage(model) {
|
|
4313
|
-
return `Gemini OAuth is configured to use the Gemini Code Assist subscription endpoint only. That endpoint does not currently expose model "${model}".`;
|
|
4314
|
-
}
|
|
4315
|
-
var ACCOUNT_GATED_MODELS = /* @__PURE__ */ new Set([
|
|
4316
|
-
"gemini-3-flash",
|
|
4317
|
-
"gemini-3.5-flash",
|
|
4318
|
-
"gemini-3.1-pro-preview",
|
|
4319
|
-
"gemini-3.1-pro-preview-customtools",
|
|
4320
|
-
"gemini-3.7-flash",
|
|
4321
|
-
"gemini-3.8-flash",
|
|
4322
|
-
"gemini-3.5-flash-lite"
|
|
4323
|
-
]);
|
|
4324
|
-
function accountGatedMessage(model) {
|
|
4325
|
-
if (model === "gemini-3.8-flash" || model === "gemini-3.5-flash-lite") {
|
|
4326
|
-
return `${model} is not available through Code Assist for this account. Public Gemini API availability does not guarantee Code Assist OAuth access.`;
|
|
4327
|
-
}
|
|
4328
|
-
return `Your Google account isn't entitled to "${model}" over Gemini Code Assist OAuth, so the API reports it as not found. This is an account-access limit, not a ggcoder bug.`;
|
|
4329
|
-
}
|
|
4330
|
-
function accountGatedHint(model) {
|
|
4331
|
-
if (model === "gemini-3.8-flash" || model === "gemini-3.5-flash-lite") {
|
|
4332
|
-
return "Use /model to select Gemini 3.1 Flash Lite, or retry once Google enables this model through Code Assist for your account.";
|
|
4333
|
-
}
|
|
4334
|
-
return `Newer Gemini models (3.5 Flash, 3.1 Pro Preview) are available only to Code Assist Standard/Enterprise accounts with preview/GA access enabled by a cloud admin. Try Gemini 3.1 Flash Lite with /model, or sign in with a Code Assist Standard/Enterprise account that has preview access.`;
|
|
4335
|
-
}
|
|
4336
|
-
function isCodeAssistLicenseRequired(status, body) {
|
|
4337
|
-
if (status !== 403) return false;
|
|
4338
|
-
try {
|
|
4339
|
-
const parsed = JSON.parse(body);
|
|
4340
|
-
const details = typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error?.details : void 0;
|
|
4341
|
-
return Array.isArray(details) && details.some(
|
|
4342
|
-
(detail) => typeof detail === "object" && detail !== null && detail.reason === "SUBSCRIPTION_REQUIRED"
|
|
4343
|
-
);
|
|
4344
|
-
} catch {
|
|
4345
|
-
return false;
|
|
4346
|
-
}
|
|
4347
|
-
}
|
|
4348
4297
|
function isInvalidGeminiApiKey(status, body) {
|
|
4349
4298
|
if (status !== 400 && status !== 401 && status !== 403) return false;
|
|
4350
4299
|
return /"reason"\s*:\s*"API_KEY_INVALID"/.test(body) || /API key not valid/i.test(body);
|
|
4351
4300
|
}
|
|
4352
4301
|
var INVALID_API_KEY_MESSAGE = "Google rejected the Gemini API key: it is not valid.";
|
|
4353
4302
|
var INVALID_API_KEY_HINT = "Create a key at aistudio.google.com/apikey and add it again under Gemini in Providers (or `ggcoder login`).";
|
|
4354
|
-
|
|
4355
|
-
var LICENSE_REQUIRED_HINT = "Sign in to Gemini with a Google account that has a Gemini Code Assist Standard or Enterprise licence, or pick a model from another provider with /model.";
|
|
4356
|
-
function formatErrorMessage(status, body, model) {
|
|
4357
|
-
if (status === 404 && !CODE_ASSIST_SUPPORTED_MODELS.has(model)) {
|
|
4358
|
-
return `Gemini API error (404): ${body}
|
|
4359
|
-
|
|
4360
|
-
${formatUnsupportedModelMessage(model)}`;
|
|
4361
|
-
}
|
|
4303
|
+
function formatErrorMessage(status, body) {
|
|
4362
4304
|
return `Gemini API error (${status}): ${body}`;
|
|
4363
4305
|
}
|
|
4364
4306
|
function parseRetryDelaySeconds(body) {
|
|
@@ -4564,27 +4506,15 @@ function buildGenerateRequest(options) {
|
|
|
4564
4506
|
...systemInstruction ? { systemInstruction } : {},
|
|
4565
4507
|
...tools ? { tools } : {},
|
|
4566
4508
|
...toolConfig ? { toolConfig } : {},
|
|
4567
|
-
...Object.keys(generationConfig).length > 0 ? { generationConfig } : {}
|
|
4568
|
-
...options.promptCacheKey ? { session_id: options.promptCacheKey } : {}
|
|
4569
|
-
};
|
|
4570
|
-
}
|
|
4571
|
-
function buildCodeAssistRequest(options, request, projectId) {
|
|
4572
|
-
return {
|
|
4573
|
-
model: options.model,
|
|
4574
|
-
...projectId ? { project: projectId } : {},
|
|
4575
|
-
user_prompt_id: crypto.randomUUID(),
|
|
4576
|
-
request
|
|
4509
|
+
...Object.keys(generationConfig).length > 0 ? { generationConfig } : {}
|
|
4577
4510
|
};
|
|
4578
4511
|
}
|
|
4579
4512
|
var API_KEY_MODEL_IDS = {
|
|
4580
4513
|
"gemini-3-flash": "gemini-3.5-flash"
|
|
4581
4514
|
};
|
|
4582
|
-
function
|
|
4583
|
-
const
|
|
4584
|
-
|
|
4585
|
-
options.providerOptions
|
|
4586
|
-
);
|
|
4587
|
-
const base = (options.baseUrl ?? "").replace(/\/+$/, "");
|
|
4515
|
+
function buildRequestPlan(options, method) {
|
|
4516
|
+
const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
|
|
4517
|
+
const base = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
4588
4518
|
return {
|
|
4589
4519
|
url: new URL(
|
|
4590
4520
|
`${base}/models/${encodeURIComponent(API_KEY_MODEL_IDS[options.model] ?? options.model)}:${method}`
|
|
@@ -4593,27 +4523,7 @@ function buildApiKeyRequestPlan(options, method) {
|
|
|
4593
4523
|
"Content-Type": "application/json",
|
|
4594
4524
|
"x-goog-api-key": options.apiKey ?? ""
|
|
4595
4525
|
},
|
|
4596
|
-
body: request
|
|
4597
|
-
route: "api-key"
|
|
4598
|
-
};
|
|
4599
|
-
}
|
|
4600
|
-
function buildRequestPlan(options, method) {
|
|
4601
|
-
if (isGeminiApiKeyRoute(options)) return buildApiKeyRequestPlan(options, method);
|
|
4602
|
-
if (!CODE_ASSIST_SUPPORTED_MODELS.has(options.model)) {
|
|
4603
|
-
throw new ProviderError("gemini", formatUnsupportedModelMessage(options.model));
|
|
4604
|
-
}
|
|
4605
|
-
const projectId = getGoogleProject(options);
|
|
4606
|
-
const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
|
|
4607
|
-
return {
|
|
4608
|
-
url: getCodeAssistEndpoint(method),
|
|
4609
|
-
headers: {
|
|
4610
|
-
Authorization: `Bearer ${options.apiKey}`,
|
|
4611
|
-
"Content-Type": "application/json",
|
|
4612
|
-
"User-Agent": GEMINI_CLI_USER_AGENT,
|
|
4613
|
-
"X-Goog-Api-Client": GEMINI_CLI_API_CLIENT
|
|
4614
|
-
},
|
|
4615
|
-
body: buildCodeAssistRequest(options, request, projectId),
|
|
4616
|
-
route: "code-assist"
|
|
4526
|
+
body: request
|
|
4617
4527
|
};
|
|
4618
4528
|
}
|
|
4619
4529
|
function normalizeGeminiStopReason(reason) {
|
|
@@ -4640,10 +4550,10 @@ async function* streamSse(response) {
|
|
|
4640
4550
|
}
|
|
4641
4551
|
}
|
|
4642
4552
|
function candidatesFromResponse(response) {
|
|
4643
|
-
return response.
|
|
4553
|
+
return response.candidates;
|
|
4644
4554
|
}
|
|
4645
4555
|
function usageFromResponse(response) {
|
|
4646
|
-
return response.
|
|
4556
|
+
return response.usageMetadata;
|
|
4647
4557
|
}
|
|
4648
4558
|
function partsFromResponse(response) {
|
|
4649
4559
|
return candidatesFromResponse(response)?.[0]?.content?.parts ?? [];
|
|
@@ -4665,7 +4575,7 @@ function readFunctionCallPart(part) {
|
|
|
4665
4575
|
function makeToolCallId(index, providerId) {
|
|
4666
4576
|
return providerId ?? `gemini_call_${index}_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
4667
4577
|
}
|
|
4668
|
-
function
|
|
4578
|
+
function shouldRetryStatus(status) {
|
|
4669
4579
|
return status === 429 || status === 499 || status >= 500 && status <= 599;
|
|
4670
4580
|
}
|
|
4671
4581
|
function isAbortError(err) {
|
|
@@ -4688,7 +4598,7 @@ async function sleep(ms, signal) {
|
|
|
4688
4598
|
if (signal?.aborted) onAbort();
|
|
4689
4599
|
});
|
|
4690
4600
|
}
|
|
4691
|
-
async function
|
|
4601
|
+
async function fetchGemini(plan, options) {
|
|
4692
4602
|
try {
|
|
4693
4603
|
const response = await providerFetch(plan.url, {
|
|
4694
4604
|
method: "POST",
|
|
@@ -4699,11 +4609,8 @@ async function fetchCodeAssist(plan, options) {
|
|
|
4699
4609
|
if (!response.ok) {
|
|
4700
4610
|
const text = await response.text().catch(() => "");
|
|
4701
4611
|
const quota = parseGeminiQuota(response.status, text);
|
|
4702
|
-
const
|
|
4703
|
-
|
|
4704
|
-
const licenseRequired = codeAssist && isCodeAssistLicenseRequired(response.status, text);
|
|
4705
|
-
const invalidKey = !codeAssist && isInvalidGeminiApiKey(response.status, text);
|
|
4706
|
-
let message = invalidKey ? INVALID_API_KEY_MESSAGE : licenseRequired ? LICENSE_REQUIRED_MESSAGE : accountGated ? accountGatedMessage(options.model) : formatErrorMessage(response.status, text, options.model);
|
|
4612
|
+
const invalidKey = isInvalidGeminiApiKey(response.status, text);
|
|
4613
|
+
let message = invalidKey ? INVALID_API_KEY_MESSAGE : formatErrorMessage(response.status, text);
|
|
4707
4614
|
let resetsAt;
|
|
4708
4615
|
if (quota?.exhausted) {
|
|
4709
4616
|
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
@@ -4713,7 +4620,7 @@ async function fetchCodeAssist(plan, options) {
|
|
|
4713
4620
|
throw new ProviderError("gemini", message, {
|
|
4714
4621
|
statusCode: response.status,
|
|
4715
4622
|
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
4716
|
-
...invalidKey ? { hint: INVALID_API_KEY_HINT, source: "auth" } :
|
|
4623
|
+
...invalidKey ? { hint: INVALID_API_KEY_HINT, source: "auth" } : {}
|
|
4717
4624
|
});
|
|
4718
4625
|
}
|
|
4719
4626
|
return response;
|
|
@@ -4721,26 +4628,26 @@ async function fetchCodeAssist(plan, options) {
|
|
|
4721
4628
|
throw toError3(err);
|
|
4722
4629
|
}
|
|
4723
4630
|
}
|
|
4724
|
-
async function
|
|
4631
|
+
async function fetchGeminiWithRetry(plan, options) {
|
|
4725
4632
|
let lastError;
|
|
4726
|
-
for (let attempt = 0; attempt <=
|
|
4633
|
+
for (let attempt = 0; attempt <= NON_STREAMING_RETRIES; attempt++) {
|
|
4727
4634
|
try {
|
|
4728
|
-
return await
|
|
4635
|
+
return await fetchGemini(plan, options);
|
|
4729
4636
|
} catch (err) {
|
|
4730
4637
|
const error = toError3(err);
|
|
4731
4638
|
const statusCode = error instanceof ProviderError ? error.statusCode : void 0;
|
|
4732
|
-
if (options.signal?.aborted || isAbortError(error) || attempt ===
|
|
4639
|
+
if (options.signal?.aborted || isAbortError(error) || attempt === NON_STREAMING_RETRIES || statusCode != null && !shouldRetryStatus(statusCode)) {
|
|
4733
4640
|
throw error;
|
|
4734
4641
|
}
|
|
4735
4642
|
lastError = error;
|
|
4736
4643
|
}
|
|
4737
4644
|
try {
|
|
4738
|
-
await sleep(
|
|
4645
|
+
await sleep(NON_STREAMING_RETRY_DELAY_MS, options.signal);
|
|
4739
4646
|
} catch (err) {
|
|
4740
4647
|
throw toError3(err);
|
|
4741
4648
|
}
|
|
4742
4649
|
}
|
|
4743
|
-
throw lastError ?? new ProviderError("gemini", "Gemini
|
|
4650
|
+
throw lastError ?? new ProviderError("gemini", "Gemini request failed.");
|
|
4744
4651
|
}
|
|
4745
4652
|
function streamGemini(options) {
|
|
4746
4653
|
return new StreamResult(runStream5(options), options.signal);
|
|
@@ -4750,7 +4657,7 @@ async function* runStream5(options) {
|
|
|
4750
4657
|
const method = useStreaming ? "streamGenerateContent" : "generateContent";
|
|
4751
4658
|
const plan = buildRequestPlan(options, method);
|
|
4752
4659
|
if (useStreaming) plan.url.searchParams.set("alt", "sse");
|
|
4753
|
-
const response = useStreaming ? await
|
|
4660
|
+
const response = useStreaming ? await fetchGemini(plan, options) : await fetchGeminiWithRetry(plan, options);
|
|
4754
4661
|
const contentParts = [];
|
|
4755
4662
|
const pendingToolCalls = [];
|
|
4756
4663
|
let textAccum = "";
|
|
@@ -5061,7 +4968,7 @@ async function postJson(request) {
|
|
|
5061
4968
|
}
|
|
5062
4969
|
function httpError(provider, response, text, headerRequestId, clean) {
|
|
5063
4970
|
const status = response.status;
|
|
5064
|
-
const parsed =
|
|
4971
|
+
const parsed = parseErrorBody2(text);
|
|
5065
4972
|
const rawMessage = parsed.message ?? (parsed.isJson ? "" : text.trim());
|
|
5066
4973
|
const message = !rawMessage ? emptyProviderErrorMessage(status) : isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(status) : clean(rawMessage);
|
|
5067
4974
|
const requestId = headerRequestId ?? parsed.requestId;
|
|
@@ -5087,7 +4994,7 @@ function httpError(provider, response, text, headerRequestId, clean) {
|
|
|
5087
4994
|
}
|
|
5088
4995
|
return new ProviderError(provider, message, base);
|
|
5089
4996
|
}
|
|
5090
|
-
function
|
|
4997
|
+
function parseErrorBody2(text) {
|
|
5091
4998
|
let parsed;
|
|
5092
4999
|
try {
|
|
5093
5000
|
parsed = JSON.parse(text);
|
|
@@ -5519,10 +5426,7 @@ var OPENAI_EMBED_BATCH_SIZE = 2048;
|
|
|
5519
5426
|
var OPENROUTER_EMBED_BATCH_SIZE = 64;
|
|
5520
5427
|
var LOCAL_EMBED_BATCH_SIZE = 64;
|
|
5521
5428
|
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
5522
|
-
var
|
|
5523
|
-
var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
|
|
5524
|
-
var GROK_CLI_PROXY_HOST = "cli-chat-proxy.grok.com";
|
|
5525
|
-
var GROK_CLI_VERSION = process.env.GROK_CLI_VERSION ?? "1.0.67";
|
|
5429
|
+
var GLM_BASE_URL = "https://api.z.ai/api/paas/v4";
|
|
5526
5430
|
providerRegistry.register("anthropic", {
|
|
5527
5431
|
stream: (options) => streamAnthropic(options)
|
|
5528
5432
|
});
|
|
@@ -5535,12 +5439,12 @@ providerRegistry.register("xiaomi", {
|
|
|
5535
5439
|
});
|
|
5536
5440
|
providerRegistry.register("openai", {
|
|
5537
5441
|
stream: (options) => {
|
|
5538
|
-
if (options.accountId) {
|
|
5539
|
-
return
|
|
5442
|
+
if (options.accountId || isOpenAIHost(options.baseUrl)) {
|
|
5443
|
+
return streamOpenAIResponses(options);
|
|
5540
5444
|
}
|
|
5541
5445
|
return streamOpenAI(options);
|
|
5542
5446
|
},
|
|
5543
|
-
// OpenAI API-key auth only; ChatGPT
|
|
5447
|
+
// OpenAI API-key auth only; the ChatGPT plan route has no embeddings API.
|
|
5544
5448
|
// OpenAI has no task/input-type parameter, so `inputType` is ignored.
|
|
5545
5449
|
embed: (request) => embedOpenAICompatible(request, {
|
|
5546
5450
|
provider: "openai",
|
|
@@ -5550,22 +5454,24 @@ providerRegistry.register("openai", {
|
|
|
5550
5454
|
});
|
|
5551
5455
|
providerRegistry.register("gemini", {
|
|
5552
5456
|
stream: (options) => streamGemini(options),
|
|
5553
|
-
// Native Gemini API with an AI Studio key
|
|
5457
|
+
// Native Gemini API with an AI Studio key.
|
|
5554
5458
|
embed: (request) => embedGemini(request),
|
|
5555
5459
|
embedBatchSize: GEMINI_EMBED_BATCH_SIZE
|
|
5556
5460
|
});
|
|
5557
5461
|
providerRegistry.register("glm", {
|
|
5558
5462
|
stream: (options) => streamOpenAI({
|
|
5559
5463
|
...options,
|
|
5560
|
-
baseUrl: options.baseUrl ??
|
|
5464
|
+
baseUrl: options.baseUrl ?? GLM_BASE_URL
|
|
5561
5465
|
})
|
|
5562
5466
|
});
|
|
5563
5467
|
providerRegistry.register("moonshot", {
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5468
|
+
// A Moonshot key goes to the public API; a Kimi Code plan key carries its
|
|
5469
|
+
// own baseUrl (api.kimi.com / api.kimi.ai). Both get GG Coder's own identity.
|
|
5470
|
+
stream: (options) => streamOpenAI({
|
|
5471
|
+
...options,
|
|
5472
|
+
baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1",
|
|
5473
|
+
defaultHeaders: { "User-Agent": GG_CODER_USER_AGENT, ...options.defaultHeaders }
|
|
5474
|
+
})
|
|
5569
5475
|
});
|
|
5570
5476
|
providerRegistry.register("deepseek", {
|
|
5571
5477
|
stream: (options) => streamOpenAI({
|
|
@@ -5614,27 +5520,10 @@ providerRegistry.register("sakana", {
|
|
|
5614
5520
|
})
|
|
5615
5521
|
});
|
|
5616
5522
|
providerRegistry.register("xai", {
|
|
5617
|
-
//
|
|
5618
|
-
//
|
|
5619
|
-
//
|
|
5620
|
-
|
|
5621
|
-
// Subscription OAuth (SuperGrok / X Premium) routes to the Grok CLI chat proxy
|
|
5622
|
-
// instead, which serves the same `/v1/responses` surface but gates on Grok-CLI
|
|
5623
|
-
// client identity. Inject those headers centrally here — exactly as the Kimi
|
|
5624
|
-
// endpoint above — so EVERY stream (agent loop, compaction, title-gen,
|
|
5625
|
-
// sub-agents) is accepted rather than depending on each call site to thread
|
|
5626
|
-
// headers. Caller-provided headers still win on collision.
|
|
5627
|
-
stream: (options) => {
|
|
5628
|
-
const baseUrl = options.baseUrl ?? "https://api.x.ai/v1";
|
|
5629
|
-
const defaultHeaders = baseUrl.includes(GROK_CLI_PROXY_HOST) ? {
|
|
5630
|
-
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
5631
|
-
"x-grok-client-version": GROK_CLI_VERSION,
|
|
5632
|
-
"x-grok-client-identifier": "ggcoder",
|
|
5633
|
-
"x-grok-model-override": options.model,
|
|
5634
|
-
...options.defaultHeaders
|
|
5635
|
-
} : options.defaultHeaders;
|
|
5636
|
-
return streamXaiResponses({ ...options, baseUrl, defaultHeaders });
|
|
5637
|
-
}
|
|
5523
|
+
// xAI API key on xAI's Responses API (see providers/xai-responses.ts): Chat
|
|
5524
|
+
// Completions is deprecated for Grok reasoning models and can't carry the
|
|
5525
|
+
// encrypted reasoning that keeps long tool loops coherent.
|
|
5526
|
+
stream: (options) => streamXaiResponses({ ...options, baseUrl: options.baseUrl ?? "https://api.x.ai/v1" })
|
|
5638
5527
|
});
|
|
5639
5528
|
providerRegistry.register("minimax", {
|
|
5640
5529
|
stream: (options) => streamAnthropic({
|
|
@@ -5685,6 +5574,14 @@ providerRegistry.register("local", {
|
|
|
5685
5574
|
}
|
|
5686
5575
|
)
|
|
5687
5576
|
});
|
|
5577
|
+
function isOpenAIHost(baseUrl) {
|
|
5578
|
+
if (!baseUrl) return true;
|
|
5579
|
+
try {
|
|
5580
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
5581
|
+
} catch {
|
|
5582
|
+
return false;
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5688
5585
|
function requireLocalBaseUrl(baseUrl) {
|
|
5689
5586
|
if (!baseUrl) {
|
|
5690
5587
|
throw new GGAIError(
|
|
@@ -5875,16 +5772,16 @@ function l2Normalize(vector) {
|
|
|
5875
5772
|
|
|
5876
5773
|
// src/utils/preconnect.ts
|
|
5877
5774
|
var PRECONNECT_INTERVAL_MS = 3e4;
|
|
5878
|
-
function defaultBaseUrl(provider
|
|
5775
|
+
function defaultBaseUrl(provider) {
|
|
5879
5776
|
switch (provider) {
|
|
5880
5777
|
case "anthropic":
|
|
5881
5778
|
return "https://api.anthropic.com";
|
|
5882
5779
|
case "openai":
|
|
5883
|
-
return
|
|
5780
|
+
return "https://api.openai.com/v1";
|
|
5884
5781
|
case "gemini":
|
|
5885
|
-
return "https://
|
|
5782
|
+
return "https://generativelanguage.googleapis.com/v1beta";
|
|
5886
5783
|
case "glm":
|
|
5887
|
-
return "https://api.z.ai/api/
|
|
5784
|
+
return "https://api.z.ai/api/paas/v4";
|
|
5888
5785
|
case "moonshot":
|
|
5889
5786
|
return "https://api.moonshot.ai/v1";
|
|
5890
5787
|
case "deepseek":
|
|
@@ -5917,7 +5814,7 @@ function isLocalHost(hostname) {
|
|
|
5917
5814
|
return a === 127 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
|
|
5918
5815
|
}
|
|
5919
5816
|
function preconnectTarget(options) {
|
|
5920
|
-
const raw = options.baseUrl ?? defaultBaseUrl(options.provider
|
|
5817
|
+
const raw = options.baseUrl ?? defaultBaseUrl(options.provider);
|
|
5921
5818
|
if (!raw || options.provider === "local" || options.provider === "palsu") return void 0;
|
|
5922
5819
|
let url;
|
|
5923
5820
|
try {
|
|
@@ -6432,10 +6329,10 @@ function palsuRerankScore(query, document) {
|
|
|
6432
6329
|
}
|
|
6433
6330
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6434
6331
|
0 && (module.exports = {
|
|
6435
|
-
CODEX_CLIENT_VERSION,
|
|
6436
6332
|
EventStream,
|
|
6437
6333
|
FAST_MODE_SUFFIX,
|
|
6438
6334
|
GGAIError,
|
|
6335
|
+
GG_CODER_USER_AGENT,
|
|
6439
6336
|
LOCAL_NO_API_KEY,
|
|
6440
6337
|
PRECONNECT_INTERVAL_MS,
|
|
6441
6338
|
ProviderError,
|