@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/dist/index.js CHANGED
@@ -243,6 +243,18 @@ function formatError(err) {
243
243
  }
244
244
  const miniMax = err.provider === "minimax" ? formatMiniMaxError(err, cleanMessage) : void 0;
245
245
  if (miniMax) return miniMax;
246
+ if (isUsageLimitError(err) && err.provider === "openai" && /chatgpt/i.test(err.message)) {
247
+ return {
248
+ headline: "ChatGPT plan usage limit reached.",
249
+ source: "provider",
250
+ 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.",
251
+ provider: err.provider,
252
+ statusCode: err.statusCode,
253
+ ...err.requestId ? { requestId: err.requestId } : {},
254
+ ...err.resetsAt ? { resetsAt: err.resetsAt } : {},
255
+ guidance: err.hint ?? "Check or raise it at chatgpt.com/settings/usage, or add an OpenAI API key to keep going."
256
+ };
257
+ }
246
258
  if (isUsageLimitError(err)) {
247
259
  const resetClause = err.resetsAt ? ` It resets at ${formatResetTime(err.resetsAt)}.` : "";
248
260
  return {
@@ -1143,7 +1155,7 @@ function toolCallNameRuleFor(provider, options) {
1143
1155
  return TOOL_CALL_NAME_RULES.anthropic;
1144
1156
  case "openai":
1145
1157
  if (options?.accountId) return TOOL_CALL_NAME_RULES["openai-responses"];
1146
- return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-chat"] : TOOL_CALL_NAME_RULES.generic;
1158
+ return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-responses"] : TOOL_CALL_NAME_RULES.generic;
1147
1159
  case "gemini":
1148
1160
  return TOOL_CALL_NAME_RULES.gemini;
1149
1161
  default:
@@ -1341,8 +1353,8 @@ function usesDeferredToolLoading(options) {
1341
1353
  const firstParty = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
1342
1354
  return firstParty && supportsAnthropicDeferredTools(options.model);
1343
1355
  }
1344
- if (options.provider === "openai" && options.accountId) {
1345
- return supportsResponsesToolSearch(options.model);
1356
+ if (options.provider === "openai" && !options.accountId) {
1357
+ return isDefaultOrHost(options.baseUrl, "api.openai.com") && supportsResponsesToolSearch(options.model);
1346
1358
  }
1347
1359
  return false;
1348
1360
  }
@@ -1917,12 +1929,12 @@ function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {
1917
1929
  };
1918
1930
  const dispatcher = useEnvProxy ? new EnvHttpProxyAgent(timeouts) : new Agent(timeouts);
1919
1931
  const builtinFetch = globalThis.fetch;
1920
- const providerFetch2 = async (input, init2) => {
1921
- if (globalThis.fetch !== builtinFetch || input instanceof Request || init2?.body instanceof FormData) {
1922
- return globalThis.fetch(input, init2);
1932
+ const providerFetch2 = async (input, init) => {
1933
+ if (globalThis.fetch !== builtinFetch || input instanceof Request || init?.body instanceof FormData) {
1934
+ return globalThis.fetch(input, init);
1923
1935
  }
1924
1936
  const response = await undiciFetch(input, {
1925
- ...init2,
1937
+ ...init,
1926
1938
  dispatcher
1927
1939
  });
1928
1940
  return response;
@@ -1930,8 +1942,8 @@ function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {
1930
1942
  return Object.assign(providerFetch2, { Response: globalThis.Response });
1931
1943
  }
1932
1944
  function withBodyActivity(fetchImpl, onActivity) {
1933
- return async (input, init2) => {
1934
- const response = await fetchImpl(input, init2);
1945
+ return async (input, init) => {
1946
+ const response = await fetchImpl(input, init);
1935
1947
  if (!response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
1936
1948
  return response;
1937
1949
  }
@@ -3246,10 +3258,6 @@ function toError2(err, provider = "openai") {
3246
3258
  return new ProviderError(provider, String(err));
3247
3259
  }
3248
3260
 
3249
- // src/providers/openai-codex.ts
3250
- import os from "os";
3251
- import * as zstd from "@bokuweb/zstd-wasm";
3252
-
3253
3261
  // src/utils/sse.ts
3254
3262
  function parseSseBuffer(buffer) {
3255
3263
  const events = [];
@@ -3301,6 +3309,9 @@ function extractRequestIdFromMessage(message) {
3301
3309
  return match?.[1];
3302
3310
  }
3303
3311
 
3312
+ // src/user-agent.ts
3313
+ var GG_CODER_USER_AGENT = "ggcoder (+https://github.com/KenKaiii/gg-coder)";
3314
+
3304
3315
  // src/providers/responses-stream.ts
3305
3316
  function isEncryptedReasoning(data) {
3306
3317
  return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
@@ -3348,9 +3359,11 @@ async function* consumeResponsesEvents(events, ctx) {
3348
3359
  });
3349
3360
  }
3350
3361
  if (type === "response.failed") {
3351
- const nested = event.error;
3362
+ const nested = event.error ?? event.response?.error;
3352
3363
  const message = nested?.message ?? `${ctx.label} response failed.`;
3353
3364
  const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
3365
+ const usageLimit = nested ? ctx.errorChunk?.(nested, requestId) : null;
3366
+ if (usageLimit) throw usageLimit;
3354
3367
  throw new ProviderError(ctx.provider, message, {
3355
3368
  ...requestId != null ? { requestId } : {}
3356
3369
  });
@@ -3700,6 +3713,7 @@ function toResponsesInput(messages, options) {
3700
3713
  ...sendItemId ? { id: mapId(itemId ?? callId, idMap) } : {},
3701
3714
  call_id: mapId(callId, idMap),
3702
3715
  name: part.name,
3716
+ ...options.toolNamespace ? { namespace: options.toolNamespace } : {},
3703
3717
  arguments: JSON.stringify(part.args)
3704
3718
  });
3705
3719
  }
@@ -3763,85 +3777,46 @@ function toResponsesInput(messages, options) {
3763
3777
  return { system, input };
3764
3778
  }
3765
3779
 
3766
- // src/providers/openai-codex.ts
3767
- var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
3768
- var CODEX_CLIENT_VERSION = "0.166.0";
3769
- var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
3770
- var zstdInitPromise;
3771
- async function encodeCodexRequest(body) {
3772
- const json = JSON.stringify(body);
3773
- const raw = new TextEncoder().encode(json);
3774
- if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
3775
- return {
3776
- body: json,
3777
- compressed: false,
3778
- rawBytes: raw.byteLength,
3779
- encodedBytes: raw.byteLength
3780
- };
3781
- }
3782
- try {
3783
- zstdInitPromise ??= zstd.init();
3784
- await zstdInitPromise;
3785
- const compressed = Uint8Array.from(zstd.compress(raw));
3786
- if (compressed.byteLength >= raw.byteLength) {
3787
- return {
3788
- body: json,
3789
- compressed: false,
3790
- rawBytes: raw.byteLength,
3791
- encodedBytes: raw.byteLength
3792
- };
3793
- }
3794
- return {
3795
- body: compressed,
3796
- compressed: true,
3797
- rawBytes: raw.byteLength,
3798
- encodedBytes: compressed.byteLength
3799
- };
3800
- } catch (error) {
3801
- providerDiag("codex_request_compression_failed", {
3802
- error: error instanceof Error ? error.message : String(error),
3803
- rawBytes: raw.byteLength
3804
- });
3805
- return {
3806
- body: json,
3807
- compressed: false,
3808
- rawBytes: raw.byteLength,
3809
- encodedBytes: raw.byteLength
3810
- };
3811
- }
3812
- }
3780
+ // src/providers/openai-responses.ts
3781
+ var DEFAULT_BASE_URL = "https://api.openai.com/v1";
3782
+ var CHATGPT_PLAN_TOOL_NAMESPACE = "gg";
3783
+ var CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
3813
3784
  function usesResponsesLite(model) {
3814
3785
  return model.startsWith("gpt-5.6-") || model.startsWith("gpt-6-") || model.startsWith("gpt-6.");
3815
3786
  }
3816
- function toCodexToolChoice(choice, tools) {
3787
+ function isChatGPTPlanRoute(options) {
3788
+ return Boolean(options.accountId);
3789
+ }
3790
+ function toResponsesToolChoice(choice, tools) {
3817
3791
  const resolved = choice ?? "auto";
3818
3792
  if (typeof resolved === "object") {
3819
3793
  throw new GGAIError(
3820
- `OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
3794
+ `OpenAI Responses does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
3821
3795
  { source: "capability" }
3822
3796
  );
3823
3797
  }
3824
3798
  if (resolved === "required" && !tools?.length) {
3825
- throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
3799
+ throw new GGAIError("OpenAI cannot require a tool call when no tools are configured.", {
3826
3800
  source: "capability"
3827
3801
  });
3828
3802
  }
3829
3803
  return resolved;
3830
3804
  }
3831
- function streamOpenAICodex(options) {
3805
+ function streamOpenAIResponses(options) {
3832
3806
  return new StreamResult(runStream3(options), options.signal);
3833
3807
  }
3834
3808
  async function* runStream3(options, retriedWithoutReasoning = false) {
3809
+ const plan = isChatGPTPlanRoute(options);
3835
3810
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
3836
- const url = `${baseUrl}/codex/responses`;
3811
+ const url = `${baseUrl}/responses`;
3837
3812
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3838
3813
  const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
3839
- const deferredPlan = supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
3814
+ const deferredPlan = !plan && supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
3840
3815
  const strictTools = options.strictTools ?? true;
3841
3816
  const toolSearchLoads = deferredPlan ? new Map(
3842
3817
  [...deferredPlan.loaded].map((name) => {
3843
3818
  const tool = deferredPlan.byName.get(name);
3844
- const [wire] = tool ? toCodexTools([tool], strictTools) : [];
3819
+ const [wire] = tool ? toResponsesTools([tool], strictTools) : [];
3845
3820
  return [name, { ...wire, defer_loading: true }];
3846
3821
  })
3847
3822
  ) : void 0;
@@ -3849,28 +3824,36 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3849
3824
  const { system, input } = toResponsesInput(downgraded, {
3850
3825
  supportsImages: options.supportsImages,
3851
3826
  toolSearchLoads,
3852
- mapId: remapCodexId,
3827
+ mapId: remapCallId,
3828
+ ...plan ? { toolNamespace: CHATGPT_PLAN_TOOL_NAMESPACE } : {},
3853
3829
  // Stamped reasoning belongs to another transport (e.g. Grok's) and would
3854
- // be rejected here; Codex's own items are never stamped.
3830
+ // be rejected here; OpenAI's own items are never stamped.
3855
3831
  keepReasoning: (part) => part.origin === void 0
3856
3832
  });
3857
- const responsesLite = usesResponsesLite(options.model);
3858
- const liteShape = options.responsesLite ?? responsesLite;
3833
+ const gpt6 = usesResponsesLite(options.model);
3859
3834
  const body = {
3860
3835
  model: options.model,
3861
3836
  store: false,
3862
3837
  stream: true,
3863
3838
  instructions: system,
3864
3839
  input,
3865
- tool_choice: toCodexToolChoice(options.toolChoice, liveTools),
3866
- parallel_tool_calls: !liteShape,
3840
+ tool_choice: toResponsesToolChoice(options.toolChoice, liveTools),
3841
+ parallel_tool_calls: true,
3867
3842
  include: ["reasoning.encrypted_content"]
3868
3843
  };
3869
3844
  if (liveTools?.length) {
3870
- body.tools = toCodexTools(liveTools, strictTools);
3845
+ const wire = toResponsesTools(liveTools, strictTools);
3846
+ body.tools = plan ? [
3847
+ {
3848
+ type: "namespace",
3849
+ name: CHATGPT_PLAN_TOOL_NAMESPACE,
3850
+ description: "GG Coder's tools for working in the user's project.",
3851
+ tools: wire
3852
+ }
3853
+ ] : wire;
3871
3854
  }
3872
3855
  body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
3873
- if (options.temperature != null && !options.thinking) {
3856
+ if (!plan && options.temperature != null && !options.thinking) {
3874
3857
  body.temperature = options.temperature;
3875
3858
  }
3876
3859
  if (options.speed === "fast") {
@@ -3878,57 +3861,38 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3878
3861
  }
3879
3862
  body.reasoning = {
3880
3863
  // GPT-5.6/6 require at least low; older models still support thinking off.
3881
- // Apply the floor here for every caller, including one-off prompt rewrites.
3882
- // `ultra` is a client orchestration preset, not a Codex API effort.
3883
- effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : responsesLite ? "low" : "none",
3884
- summary: "auto",
3885
- ...liteShape ? { context: "all_turns" } : {}
3864
+ // `ultra` is a client orchestration preset, not an API effort.
3865
+ effort: options.thinking ? toCodexReasoningEffort(options.thinking, options.model) : gpt6 ? "low" : "none",
3866
+ summary: "auto"
3886
3867
  };
3887
- if (responsesLite) {
3868
+ if (gpt6) {
3888
3869
  body.text = { verbosity: "low" };
3889
3870
  }
3890
3871
  const headers = {
3891
3872
  "Content-Type": "application/json",
3892
3873
  Accept: "text/event-stream",
3893
3874
  Authorization: `Bearer ${options.apiKey}`,
3894
- "OpenAI-Beta": "responses=experimental",
3895
- originator: responsesLite ? "codex_cli_rs" : "ggcoder",
3896
- "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${os.platform()} ${os.release()}; ${os.arch()})`,
3897
- ...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
3898
- ...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
3875
+ "User-Agent": GG_CODER_USER_AGENT
3899
3876
  };
3900
- if (options.accountId) {
3901
- headers["chatgpt-account-id"] = options.accountId;
3902
- }
3903
- if (options.transportSessionId) {
3904
- const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
3905
- headers["session_id"] = transportSessionId;
3906
- headers["x-client-request-id"] = transportSessionId;
3907
- }
3908
- const encodedRequest = await encodeCodexRequest(
3909
- mergeProviderOptions(body, options.providerOptions)
3910
- );
3911
- if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
3912
- providerDiag("codex_request_body", {
3913
- rawBytes: encodedRequest.rawBytes,
3914
- encodedBytes: encodedRequest.encodedBytes,
3915
- compressed: encodedRequest.compressed
3916
- });
3877
+ const json = JSON.stringify(mergeProviderOptions(body, options.providerOptions));
3878
+ providerDiag("openai_responses_request_body", { rawBytes: json.length, plan });
3917
3879
  const response = await providerFetch(url, {
3918
3880
  method: "POST",
3919
3881
  headers,
3920
- body: encodedRequest.body,
3882
+ body: json,
3921
3883
  signal: options.signal
3922
3884
  });
3923
3885
  if (!response.ok) {
3924
3886
  const text = await response.text().catch(() => "");
3925
- const parsed = parseCodexErrorBody(text, response.status);
3926
- const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
3887
+ const parsed = parseErrorBody(text, response.status);
3888
+ const message = parsed.message ?? `OpenAI returned HTTP ${response.status}.`;
3927
3889
  const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
3928
3890
  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(
3929
3891
  (msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
3930
3892
  )) {
3931
- providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
3893
+ providerDiag("openai_responses_retry_without_encrypted_reasoning", {
3894
+ status: response.status
3895
+ });
3932
3896
  const messages = options.messages.map(
3933
3897
  (msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
3934
3898
  ...msg,
@@ -3939,13 +3903,19 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3939
3903
  );
3940
3904
  return yield* runStream3({ ...options, messages }, true);
3941
3905
  }
3942
- const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
3943
- if (usageLimit) throw usageLimit;
3906
+ if (plan) {
3907
+ const planError = chatGPTPlanError(
3908
+ parsed.errorObj,
3909
+ response.status,
3910
+ requestId,
3911
+ message,
3912
+ options.model
3913
+ );
3914
+ if (planError) throw planError;
3915
+ }
3944
3916
  let hint;
3945
- if (response.status === 400 && message === `The '${options.model}' model is not supported when using Codex with a ChatGPT account.`) {
3946
- hint = "This model is not available through your ChatGPT account. Choose another available model using the model selector.";
3947
- } else if (response.status === 404 && text.includes("does not exist")) {
3948
- hint = "This model is not in OpenAI's current catalog for your ChatGPT account. Choose another available model using the model selector.";
3917
+ if (response.status === 404 && text.includes("does not exist")) {
3918
+ 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.";
3949
3919
  }
3950
3920
  throw new ProviderError("openai", message, {
3951
3921
  statusCode: response.status,
@@ -3954,14 +3924,14 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3954
3924
  });
3955
3925
  }
3956
3926
  if (!response.body) {
3957
- throw new ProviderError("openai", "No response body from Codex API");
3927
+ throw new ProviderError("openai", "No response body from OpenAI");
3958
3928
  }
3959
3929
  return yield* consumeResponsesEvents(parseSSE(response.body), {
3960
3930
  provider: "openai",
3961
- label: "Codex",
3962
- diagPrefix: "codex",
3931
+ label: "OpenAI",
3932
+ diagPrefix: "openai_responses",
3963
3933
  thinking: !!options.thinking,
3964
- errorChunk: (errorObj, requestId) => codexUsageLimitError(errorObj, void 0, requestId)
3934
+ errorChunk: (errorObj, requestId) => plan ? chatGPTPlanError(errorObj, void 0, requestId, void 0, options.model) : null
3965
3935
  });
3966
3936
  }
3967
3937
  async function* parseSSE(body) {
@@ -3974,7 +3944,7 @@ async function* parseSSE(body) {
3974
3944
  }
3975
3945
  }
3976
3946
  }
3977
- function remapCodexId(id, idMap) {
3947
+ function remapCallId(id, idMap) {
3978
3948
  const existing = idMap.get(id);
3979
3949
  if (existing) return existing;
3980
3950
  const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
@@ -3988,7 +3958,7 @@ function remapCodexId(id, idMap) {
3988
3958
  idMap.set(id, mapped);
3989
3959
  return mapped;
3990
3960
  }
3991
- function toCodexTools(tools, strictTools) {
3961
+ function toResponsesTools(tools, strictTools) {
3992
3962
  return tools.map((tool) => {
3993
3963
  let parameters = resolveToolSchema(tool);
3994
3964
  let strict = null;
@@ -4009,7 +3979,7 @@ function toCodexTools(tools, strictTools) {
4009
3979
  };
4010
3980
  });
4011
3981
  }
4012
- function parseCodexErrorBody(text, statusCode) {
3982
+ function parseErrorBody(text, statusCode) {
4013
3983
  if (!text) return {};
4014
3984
  try {
4015
3985
  const parsed = JSON.parse(text);
@@ -4033,23 +4003,70 @@ function parseCodexErrorBody(text, statusCode) {
4033
4003
  return bounded ? { message: bounded } : {};
4034
4004
  }
4035
4005
  }
4036
- var CODEX_USAGE_LIMIT_CODE = /usage_limit_reached|usage_not_included/i;
4037
- var CODEX_RATE_LIMIT_CODE = /rate_limit_exceeded/i;
4038
- function codexUsageLimitError(errorObj, statusCode, requestId) {
4006
+ function chatGPTPlanError(errorObj, statusCode, requestId, message, model) {
4039
4007
  const code = String(errorObj?.code ?? errorObj?.type ?? "");
4040
- const rateLimits = errorObj?.rate_limits;
4041
- const resetsAtRaw = (typeof errorObj?.resets_at === "number" ? errorObj.resets_at : void 0) ?? rateLimits?.primary?.resets_at ?? rateLimits?.secondary?.resets_at;
4042
- const resetsInSeconds = typeof errorObj?.resets_in_seconds === "number" ? errorObj.resets_in_seconds : void 0;
4043
- const resetsAt = typeof resetsAtRaw === "number" && resetsAtRaw > 0 ? resetsAtRaw : resetsInSeconds != null && resetsInSeconds > 0 ? Math.floor(Date.now() / 1e3) + resetsInSeconds : void 0;
4044
- const isHardUsage = CODEX_USAGE_LIMIT_CODE.test(code);
4045
- const isRateOr429 = CODEX_RATE_LIMIT_CODE.test(code) || statusCode === 429;
4046
- if (!isHardUsage && !(isRateOr429 && resetsAt != null)) return null;
4047
- return new ProviderError("openai", "ChatGPT usage limit reached", {
4048
- cause: errorObj,
4049
- statusCode: statusCode ?? 429,
4050
- ...requestId ? { requestId } : {},
4051
- ...resetsAt ? { resetsAt } : {}
4052
- });
4008
+ const base = { ...requestId ? { requestId } : {}, cause: errorObj };
4009
+ switch (code) {
4010
+ // The plan's usage — or the limit the user set for GG Coder — is spent, or
4011
+ // can't be checked right now. Both stop plan requests; the canonical
4012
+ // "usage limit reached" message lets the session fall back to an API key.
4013
+ case "subscription_sharing_usage_limit_exceeded":
4014
+ case "subscription_sharing_usage_unavailable":
4015
+ return new ProviderError("openai", "ChatGPT usage limit reached", {
4016
+ ...base,
4017
+ statusCode: statusCode ?? (code.endsWith("unavailable") ? 503 : 429),
4018
+ 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.`
4019
+ });
4020
+ case "subscription_sharing_user_not_eligible":
4021
+ return new ProviderError(
4022
+ "openai",
4023
+ "ChatGPT plan use isn't available for this account or workspace.",
4024
+ {
4025
+ ...base,
4026
+ statusCode: statusCode ?? 403,
4027
+ hint: "Add an OpenAI API key, or ask your workspace admin to allow ChatGPT plan use in other apps."
4028
+ }
4029
+ );
4030
+ // The account's plan doesn't include this model (live: 400 model_not_found,
4031
+ // "…not supported when using Codex with a ChatGPT account").
4032
+ case "model_not_found":
4033
+ return new ProviderError(
4034
+ "openai",
4035
+ `${model ?? "This model"} isn't included in your ChatGPT plan.`,
4036
+ {
4037
+ ...base,
4038
+ statusCode: statusCode ?? 400,
4039
+ hint: "Pick a model your plan includes (the model picker marks the others), or use an OpenAI API key for this one."
4040
+ }
4041
+ );
4042
+ case "subscription_sharing_unsupported_capability": {
4043
+ const param = typeof errorObj?.param === "string" ? errorObj.param : void 0;
4044
+ if (param?.includes("service_tier")) {
4045
+ return new ProviderError("openai", "Fast mode isn't included in your ChatGPT plan.", {
4046
+ ...base,
4047
+ statusCode: statusCode ?? 400,
4048
+ hint: "Switch to the standard model, or use an OpenAI API key for Fast mode."
4049
+ });
4050
+ }
4051
+ return new ProviderError(
4052
+ "openai",
4053
+ message ?? `The ChatGPT plan route doesn't support ${param ?? "part of this request"}.`,
4054
+ {
4055
+ ...base,
4056
+ statusCode: statusCode ?? 400,
4057
+ 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.`
4058
+ }
4059
+ );
4060
+ }
4061
+ case "subscription_sharing_invalid_user":
4062
+ return new ProviderError("openai", message ?? "ChatGPT couldn't validate your sign-in.", {
4063
+ ...base,
4064
+ statusCode: statusCode ?? 401,
4065
+ hint: "Continue with ChatGPT again to reconnect."
4066
+ });
4067
+ default:
4068
+ return null;
4069
+ }
4053
4070
  }
4054
4071
 
4055
4072
  // src/providers/xai-responses.ts
@@ -4183,92 +4200,17 @@ async function* synthesizeResponseEvents(response) {
4183
4200
  }
4184
4201
 
4185
4202
  // src/providers/gemini.ts
4186
- var DEFAULT_CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
4187
- var CODE_ASSIST_API_VERSION = "v1internal";
4188
- var GEMINI_CLI_USER_AGENT = "google-gemini-cli";
4189
- var GEMINI_CLI_API_CLIENT = "gemini-cli/0.0.0";
4190
- var CODE_ASSIST_NON_STREAMING_RETRIES = 3;
4191
- var CODE_ASSIST_NON_STREAMING_RETRY_DELAY_MS = 1e3;
4203
+ var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com/v1beta";
4204
+ var NON_STREAMING_RETRIES = 3;
4205
+ var NON_STREAMING_RETRY_DELAY_MS = 1e3;
4192
4206
  var SYNTHETIC_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
4193
- var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
4194
- "gemini-3-pro-preview",
4195
- "gemini-3.1-pro-preview",
4196
- "gemini-3.1-pro-preview-customtools",
4197
- "gemini-3-flash-preview",
4198
- "gemini-3.5-flash",
4199
- "gemini-3-flash",
4200
- "gemini-3.1-flash-lite",
4201
- "gemini-3.7-flash",
4202
- "gemini-3.8-flash",
4203
- "gemini-3.5-flash-lite",
4204
- "gemini-2.5-pro",
4205
- "gemini-2.5-flash",
4206
- "gemma-4-31b-it",
4207
- "gemma-4-26b-a4b-it"
4208
- ]);
4209
- function isGeminiApiKeyRoute(options) {
4210
- return options.baseUrl?.includes("generativelanguage.googleapis.com") === true;
4211
- }
4212
- function getGoogleProject(options) {
4213
- const env = getEnvironment();
4214
- return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
4215
- }
4216
- function getCodeAssistEndpoint(method) {
4217
- const env = getEnvironment();
4218
- const endpoint = env?.CODE_ASSIST_ENDPOINT ?? DEFAULT_CODE_ASSIST_BASE_URL;
4219
- const version = env?.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
4220
- return new URL(`${endpoint}/${version}:${method}`);
4221
- }
4222
- function formatUnsupportedModelMessage(model) {
4223
- return `Gemini OAuth is configured to use the Gemini Code Assist subscription endpoint only. That endpoint does not currently expose model "${model}".`;
4224
- }
4225
- var ACCOUNT_GATED_MODELS = /* @__PURE__ */ new Set([
4226
- "gemini-3-flash",
4227
- "gemini-3.5-flash",
4228
- "gemini-3.1-pro-preview",
4229
- "gemini-3.1-pro-preview-customtools",
4230
- "gemini-3.7-flash",
4231
- "gemini-3.8-flash",
4232
- "gemini-3.5-flash-lite"
4233
- ]);
4234
- function accountGatedMessage(model) {
4235
- if (model === "gemini-3.8-flash" || model === "gemini-3.5-flash-lite") {
4236
- return `${model} is not available through Code Assist for this account. Public Gemini API availability does not guarantee Code Assist OAuth access.`;
4237
- }
4238
- 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.`;
4239
- }
4240
- function accountGatedHint(model) {
4241
- if (model === "gemini-3.8-flash" || model === "gemini-3.5-flash-lite") {
4242
- return "Use /model to select Gemini 3.1 Flash Lite, or retry once Google enables this model through Code Assist for your account.";
4243
- }
4244
- 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.`;
4245
- }
4246
- function isCodeAssistLicenseRequired(status, body) {
4247
- if (status !== 403) return false;
4248
- try {
4249
- const parsed = JSON.parse(body);
4250
- const details = typeof parsed === "object" && parsed !== null && "error" in parsed ? parsed.error?.details : void 0;
4251
- return Array.isArray(details) && details.some(
4252
- (detail) => typeof detail === "object" && detail !== null && detail.reason === "SUBSCRIPTION_REQUIRED"
4253
- );
4254
- } catch {
4255
- return false;
4256
- }
4257
- }
4258
4207
  function isInvalidGeminiApiKey(status, body) {
4259
4208
  if (status !== 400 && status !== 401 && status !== 403) return false;
4260
4209
  return /"reason"\s*:\s*"API_KEY_INVALID"/.test(body) || /API key not valid/i.test(body);
4261
4210
  }
4262
4211
  var INVALID_API_KEY_MESSAGE = "Google rejected the Gemini API key: it is not valid.";
4263
4212
  var INVALID_API_KEY_HINT = "Create a key at aistudio.google.com/apikey and add it again under Gemini in Providers (or `ggcoder login`).";
4264
- var LICENSE_REQUIRED_MESSAGE = "This Google account has no Gemini Code Assist licence. Google stopped serving Gemini through Code Assist to personal accounts (Google AI Pro/Ultra and the free individual tier) on June 18, 2026, so every Gemini model is refused, not just this one.";
4265
- 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.";
4266
- function formatErrorMessage(status, body, model) {
4267
- if (status === 404 && !CODE_ASSIST_SUPPORTED_MODELS.has(model)) {
4268
- return `Gemini API error (404): ${body}
4269
-
4270
- ${formatUnsupportedModelMessage(model)}`;
4271
- }
4213
+ function formatErrorMessage(status, body) {
4272
4214
  return `Gemini API error (${status}): ${body}`;
4273
4215
  }
4274
4216
  function parseRetryDelaySeconds(body) {
@@ -4474,27 +4416,15 @@ function buildGenerateRequest(options) {
4474
4416
  ...systemInstruction ? { systemInstruction } : {},
4475
4417
  ...tools ? { tools } : {},
4476
4418
  ...toolConfig ? { toolConfig } : {},
4477
- ...Object.keys(generationConfig).length > 0 ? { generationConfig } : {},
4478
- ...options.promptCacheKey ? { session_id: options.promptCacheKey } : {}
4479
- };
4480
- }
4481
- function buildCodeAssistRequest(options, request, projectId) {
4482
- return {
4483
- model: options.model,
4484
- ...projectId ? { project: projectId } : {},
4485
- user_prompt_id: crypto.randomUUID(),
4486
- request
4419
+ ...Object.keys(generationConfig).length > 0 ? { generationConfig } : {}
4487
4420
  };
4488
4421
  }
4489
4422
  var API_KEY_MODEL_IDS = {
4490
4423
  "gemini-3-flash": "gemini-3.5-flash"
4491
4424
  };
4492
- function buildApiKeyRequestPlan(options, method) {
4493
- const { session_id: _sessionId, ...request } = mergeProviderOptions(
4494
- buildGenerateRequest(options),
4495
- options.providerOptions
4496
- );
4497
- const base = (options.baseUrl ?? "").replace(/\/+$/, "");
4425
+ function buildRequestPlan(options, method) {
4426
+ const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
4427
+ const base = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
4498
4428
  return {
4499
4429
  url: new URL(
4500
4430
  `${base}/models/${encodeURIComponent(API_KEY_MODEL_IDS[options.model] ?? options.model)}:${method}`
@@ -4503,27 +4433,7 @@ function buildApiKeyRequestPlan(options, method) {
4503
4433
  "Content-Type": "application/json",
4504
4434
  "x-goog-api-key": options.apiKey ?? ""
4505
4435
  },
4506
- body: request,
4507
- route: "api-key"
4508
- };
4509
- }
4510
- function buildRequestPlan(options, method) {
4511
- if (isGeminiApiKeyRoute(options)) return buildApiKeyRequestPlan(options, method);
4512
- if (!CODE_ASSIST_SUPPORTED_MODELS.has(options.model)) {
4513
- throw new ProviderError("gemini", formatUnsupportedModelMessage(options.model));
4514
- }
4515
- const projectId = getGoogleProject(options);
4516
- const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
4517
- return {
4518
- url: getCodeAssistEndpoint(method),
4519
- headers: {
4520
- Authorization: `Bearer ${options.apiKey}`,
4521
- "Content-Type": "application/json",
4522
- "User-Agent": GEMINI_CLI_USER_AGENT,
4523
- "X-Goog-Api-Client": GEMINI_CLI_API_CLIENT
4524
- },
4525
- body: buildCodeAssistRequest(options, request, projectId),
4526
- route: "code-assist"
4436
+ body: request
4527
4437
  };
4528
4438
  }
4529
4439
  function normalizeGeminiStopReason(reason) {
@@ -4550,10 +4460,10 @@ async function* streamSse(response) {
4550
4460
  }
4551
4461
  }
4552
4462
  function candidatesFromResponse(response) {
4553
- return response.response?.candidates ?? response.candidates;
4463
+ return response.candidates;
4554
4464
  }
4555
4465
  function usageFromResponse(response) {
4556
- return response.response?.usageMetadata ?? response.usageMetadata;
4466
+ return response.usageMetadata;
4557
4467
  }
4558
4468
  function partsFromResponse(response) {
4559
4469
  return candidatesFromResponse(response)?.[0]?.content?.parts ?? [];
@@ -4575,7 +4485,7 @@ function readFunctionCallPart(part) {
4575
4485
  function makeToolCallId(index, providerId) {
4576
4486
  return providerId ?? `gemini_call_${index}_${crypto.randomUUID().replace(/-/g, "")}`;
4577
4487
  }
4578
- function shouldRetryCodeAssistStatus(status) {
4488
+ function shouldRetryStatus(status) {
4579
4489
  return status === 429 || status === 499 || status >= 500 && status <= 599;
4580
4490
  }
4581
4491
  function isAbortError(err) {
@@ -4598,7 +4508,7 @@ async function sleep(ms, signal) {
4598
4508
  if (signal?.aborted) onAbort();
4599
4509
  });
4600
4510
  }
4601
- async function fetchCodeAssist(plan, options) {
4511
+ async function fetchGemini(plan, options) {
4602
4512
  try {
4603
4513
  const response = await providerFetch(plan.url, {
4604
4514
  method: "POST",
@@ -4609,11 +4519,8 @@ async function fetchCodeAssist(plan, options) {
4609
4519
  if (!response.ok) {
4610
4520
  const text = await response.text().catch(() => "");
4611
4521
  const quota = parseGeminiQuota(response.status, text);
4612
- const codeAssist = plan.route === "code-assist";
4613
- const accountGated = codeAssist && response.status === 404 && ACCOUNT_GATED_MODELS.has(options.model);
4614
- const licenseRequired = codeAssist && isCodeAssistLicenseRequired(response.status, text);
4615
- const invalidKey = !codeAssist && isInvalidGeminiApiKey(response.status, text);
4616
- let message = invalidKey ? INVALID_API_KEY_MESSAGE : licenseRequired ? LICENSE_REQUIRED_MESSAGE : accountGated ? accountGatedMessage(options.model) : formatErrorMessage(response.status, text, options.model);
4522
+ const invalidKey = isInvalidGeminiApiKey(response.status, text);
4523
+ let message = invalidKey ? INVALID_API_KEY_MESSAGE : formatErrorMessage(response.status, text);
4617
4524
  let resetsAt;
4618
4525
  if (quota?.exhausted) {
4619
4526
  message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
@@ -4623,7 +4530,7 @@ async function fetchCodeAssist(plan, options) {
4623
4530
  throw new ProviderError("gemini", message, {
4624
4531
  statusCode: response.status,
4625
4532
  ...resetsAt !== void 0 ? { resetsAt } : {},
4626
- ...invalidKey ? { hint: INVALID_API_KEY_HINT, source: "auth" } : licenseRequired ? { hint: LICENSE_REQUIRED_HINT, source: "auth" } : accountGated ? { hint: accountGatedHint(options.model) } : {}
4533
+ ...invalidKey ? { hint: INVALID_API_KEY_HINT, source: "auth" } : {}
4627
4534
  });
4628
4535
  }
4629
4536
  return response;
@@ -4631,26 +4538,26 @@ async function fetchCodeAssist(plan, options) {
4631
4538
  throw toError3(err);
4632
4539
  }
4633
4540
  }
4634
- async function fetchCodeAssistWithRetry(plan, options) {
4541
+ async function fetchGeminiWithRetry(plan, options) {
4635
4542
  let lastError;
4636
- for (let attempt = 0; attempt <= CODE_ASSIST_NON_STREAMING_RETRIES; attempt++) {
4543
+ for (let attempt = 0; attempt <= NON_STREAMING_RETRIES; attempt++) {
4637
4544
  try {
4638
- return await fetchCodeAssist(plan, options);
4545
+ return await fetchGemini(plan, options);
4639
4546
  } catch (err) {
4640
4547
  const error = toError3(err);
4641
4548
  const statusCode = error instanceof ProviderError ? error.statusCode : void 0;
4642
- if (options.signal?.aborted || isAbortError(error) || attempt === CODE_ASSIST_NON_STREAMING_RETRIES || statusCode != null && !shouldRetryCodeAssistStatus(statusCode)) {
4549
+ if (options.signal?.aborted || isAbortError(error) || attempt === NON_STREAMING_RETRIES || statusCode != null && !shouldRetryStatus(statusCode)) {
4643
4550
  throw error;
4644
4551
  }
4645
4552
  lastError = error;
4646
4553
  }
4647
4554
  try {
4648
- await sleep(CODE_ASSIST_NON_STREAMING_RETRY_DELAY_MS, options.signal);
4555
+ await sleep(NON_STREAMING_RETRY_DELAY_MS, options.signal);
4649
4556
  } catch (err) {
4650
4557
  throw toError3(err);
4651
4558
  }
4652
4559
  }
4653
- throw lastError ?? new ProviderError("gemini", "Gemini Code Assist request failed.");
4560
+ throw lastError ?? new ProviderError("gemini", "Gemini request failed.");
4654
4561
  }
4655
4562
  function streamGemini(options) {
4656
4563
  return new StreamResult(runStream5(options), options.signal);
@@ -4660,7 +4567,7 @@ async function* runStream5(options) {
4660
4567
  const method = useStreaming ? "streamGenerateContent" : "generateContent";
4661
4568
  const plan = buildRequestPlan(options, method);
4662
4569
  if (useStreaming) plan.url.searchParams.set("alt", "sse");
4663
- const response = useStreaming ? await fetchCodeAssist(plan, options) : await fetchCodeAssistWithRetry(plan, options);
4570
+ const response = useStreaming ? await fetchGemini(plan, options) : await fetchGeminiWithRetry(plan, options);
4664
4571
  const contentParts = [];
4665
4572
  const pendingToolCalls = [];
4666
4573
  let textAccum = "";
@@ -4971,7 +4878,7 @@ async function postJson(request) {
4971
4878
  }
4972
4879
  function httpError(provider, response, text, headerRequestId, clean) {
4973
4880
  const status = response.status;
4974
- const parsed = parseErrorBody(text);
4881
+ const parsed = parseErrorBody2(text);
4975
4882
  const rawMessage = parsed.message ?? (parsed.isJson ? "" : text.trim());
4976
4883
  const message = !rawMessage ? emptyProviderErrorMessage(status) : isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(status) : clean(rawMessage);
4977
4884
  const requestId = headerRequestId ?? parsed.requestId;
@@ -4997,7 +4904,7 @@ function httpError(provider, response, text, headerRequestId, clean) {
4997
4904
  }
4998
4905
  return new ProviderError(provider, message, base);
4999
4906
  }
5000
- function parseErrorBody(text) {
4907
+ function parseErrorBody2(text) {
5001
4908
  let parsed;
5002
4909
  try {
5003
4910
  parsed = JSON.parse(text);
@@ -5429,10 +5336,7 @@ var OPENAI_EMBED_BATCH_SIZE = 2048;
5429
5336
  var OPENROUTER_EMBED_BATCH_SIZE = 64;
5430
5337
  var LOCAL_EMBED_BATCH_SIZE = 64;
5431
5338
  var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
5432
- var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
5433
- var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
5434
- var GROK_CLI_PROXY_HOST = "cli-chat-proxy.grok.com";
5435
- var GROK_CLI_VERSION = process.env.GROK_CLI_VERSION ?? "1.0.67";
5339
+ var GLM_BASE_URL = "https://api.z.ai/api/paas/v4";
5436
5340
  providerRegistry.register("anthropic", {
5437
5341
  stream: (options) => streamAnthropic(options)
5438
5342
  });
@@ -5445,12 +5349,12 @@ providerRegistry.register("xiaomi", {
5445
5349
  });
5446
5350
  providerRegistry.register("openai", {
5447
5351
  stream: (options) => {
5448
- if (options.accountId) {
5449
- return streamOpenAICodex(options);
5352
+ if (options.accountId || isOpenAIHost(options.baseUrl)) {
5353
+ return streamOpenAIResponses(options);
5450
5354
  }
5451
5355
  return streamOpenAI(options);
5452
5356
  },
5453
- // OpenAI API-key auth only; ChatGPT-subscription (Codex) tokens have no embeddings API.
5357
+ // OpenAI API-key auth only; the ChatGPT plan route has no embeddings API.
5454
5358
  // OpenAI has no task/input-type parameter, so `inputType` is ignored.
5455
5359
  embed: (request) => embedOpenAICompatible(request, {
5456
5360
  provider: "openai",
@@ -5460,22 +5364,24 @@ providerRegistry.register("openai", {
5460
5364
  });
5461
5365
  providerRegistry.register("gemini", {
5462
5366
  stream: (options) => streamGemini(options),
5463
- // Native Gemini API with an AI Studio key — not the Code Assist OAuth endpoint above.
5367
+ // Native Gemini API with an AI Studio key.
5464
5368
  embed: (request) => embedGemini(request),
5465
5369
  embedBatchSize: GEMINI_EMBED_BATCH_SIZE
5466
5370
  });
5467
5371
  providerRegistry.register("glm", {
5468
5372
  stream: (options) => streamOpenAI({
5469
5373
  ...options,
5470
- baseUrl: options.baseUrl ?? GLM_CODING_BASE_URL
5374
+ baseUrl: options.baseUrl ?? GLM_BASE_URL
5471
5375
  })
5472
5376
  });
5473
5377
  providerRegistry.register("moonshot", {
5474
- stream: (options) => {
5475
- const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
5476
- const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
5477
- return streamOpenAI({ ...options, baseUrl, defaultHeaders });
5478
- }
5378
+ // A Moonshot key goes to the public API; a Kimi Code plan key carries its
5379
+ // own baseUrl (api.kimi.com / api.kimi.ai). Both get GG Coder's own identity.
5380
+ stream: (options) => streamOpenAI({
5381
+ ...options,
5382
+ baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1",
5383
+ defaultHeaders: { "User-Agent": GG_CODER_USER_AGENT, ...options.defaultHeaders }
5384
+ })
5479
5385
  });
5480
5386
  providerRegistry.register("deepseek", {
5481
5387
  stream: (options) => streamOpenAI({
@@ -5524,27 +5430,10 @@ providerRegistry.register("sakana", {
5524
5430
  })
5525
5431
  });
5526
5432
  providerRegistry.register("xai", {
5527
- // Both Grok routes ride xAI's Responses API (see providers/xai-responses.ts):
5528
- // Chat Completions is deprecated for Grok reasoning models and can't carry
5529
- // the encrypted reasoning that keeps long tool loops coherent.
5530
- //
5531
- // Subscription OAuth (SuperGrok / X Premium) routes to the Grok CLI chat proxy
5532
- // instead, which serves the same `/v1/responses` surface but gates on Grok-CLI
5533
- // client identity. Inject those headers centrally here — exactly as the Kimi
5534
- // endpoint above — so EVERY stream (agent loop, compaction, title-gen,
5535
- // sub-agents) is accepted rather than depending on each call site to thread
5536
- // headers. Caller-provided headers still win on collision.
5537
- stream: (options) => {
5538
- const baseUrl = options.baseUrl ?? "https://api.x.ai/v1";
5539
- const defaultHeaders = baseUrl.includes(GROK_CLI_PROXY_HOST) ? {
5540
- "X-XAI-Token-Auth": "xai-grok-cli",
5541
- "x-grok-client-version": GROK_CLI_VERSION,
5542
- "x-grok-client-identifier": "ggcoder",
5543
- "x-grok-model-override": options.model,
5544
- ...options.defaultHeaders
5545
- } : options.defaultHeaders;
5546
- return streamXaiResponses({ ...options, baseUrl, defaultHeaders });
5547
- }
5433
+ // xAI API key on xAI's Responses API (see providers/xai-responses.ts): Chat
5434
+ // Completions is deprecated for Grok reasoning models and can't carry the
5435
+ // encrypted reasoning that keeps long tool loops coherent.
5436
+ stream: (options) => streamXaiResponses({ ...options, baseUrl: options.baseUrl ?? "https://api.x.ai/v1" })
5548
5437
  });
5549
5438
  providerRegistry.register("minimax", {
5550
5439
  stream: (options) => streamAnthropic({
@@ -5595,6 +5484,14 @@ providerRegistry.register("local", {
5595
5484
  }
5596
5485
  )
5597
5486
  });
5487
+ function isOpenAIHost(baseUrl) {
5488
+ if (!baseUrl) return true;
5489
+ try {
5490
+ return new URL(baseUrl).hostname === "api.openai.com";
5491
+ } catch {
5492
+ return false;
5493
+ }
5494
+ }
5598
5495
  function requireLocalBaseUrl(baseUrl) {
5599
5496
  if (!baseUrl) {
5600
5497
  throw new GGAIError(
@@ -5785,16 +5682,16 @@ function l2Normalize(vector) {
5785
5682
 
5786
5683
  // src/utils/preconnect.ts
5787
5684
  var PRECONNECT_INTERVAL_MS = 3e4;
5788
- function defaultBaseUrl(provider, accountId) {
5685
+ function defaultBaseUrl(provider) {
5789
5686
  switch (provider) {
5790
5687
  case "anthropic":
5791
5688
  return "https://api.anthropic.com";
5792
5689
  case "openai":
5793
- return accountId ? "https://chatgpt.com/backend-api" : "https://api.openai.com/v1";
5690
+ return "https://api.openai.com/v1";
5794
5691
  case "gemini":
5795
- return "https://cloudcode-pa.googleapis.com";
5692
+ return "https://generativelanguage.googleapis.com/v1beta";
5796
5693
  case "glm":
5797
- return "https://api.z.ai/api/coding/paas/v4";
5694
+ return "https://api.z.ai/api/paas/v4";
5798
5695
  case "moonshot":
5799
5696
  return "https://api.moonshot.ai/v1";
5800
5697
  case "deepseek":
@@ -5827,7 +5724,7 @@ function isLocalHost(hostname) {
5827
5724
  return a === 127 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
5828
5725
  }
5829
5726
  function preconnectTarget(options) {
5830
- const raw = options.baseUrl ?? defaultBaseUrl(options.provider, options.accountId);
5727
+ const raw = options.baseUrl ?? defaultBaseUrl(options.provider);
5831
5728
  if (!raw || options.provider === "local" || options.provider === "palsu") return void 0;
5832
5729
  let url;
5833
5730
  try {
@@ -6341,10 +6238,10 @@ function palsuRerankScore(query, document) {
6341
6238
  return hits / queryTokens.size;
6342
6239
  }
6343
6240
  export {
6344
- CODEX_CLIENT_VERSION,
6345
6241
  EventStream,
6346
6242
  FAST_MODE_SUFFIX,
6347
6243
  GGAIError,
6244
+ GG_CODER_USER_AGENT,
6348
6245
  LOCAL_NO_API_KEY,
6349
6246
  PRECONNECT_INTERVAL_MS,
6350
6247
  ProviderError,