@kenkaiiii/gg-ai 4.3.236 → 4.3.238

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  StreamResult: () => StreamResult,
37
37
  formatError: () => formatError,
38
38
  formatErrorForDisplay: () => formatErrorForDisplay,
39
+ isHardBillingMessage: () => isHardBillingMessage,
39
40
  isUsageLimitError: () => isUsageLimitError,
40
41
  palsuAssistantMessage: () => palsuAssistantMessage,
41
42
  palsuText: () => palsuText,
@@ -51,6 +52,20 @@ __export(index_exports, {
51
52
  module.exports = __toCommonJS(index_exports);
52
53
 
53
54
  // src/errors.ts
55
+ function readHeader(headers, ...names) {
56
+ if (!headers) return void 0;
57
+ const getter = typeof headers.get === "function" ? (name) => headers.get(name) ?? void 0 : typeof headers === "object" ? (name) => {
58
+ const rec = headers;
59
+ const value = rec[name] ?? rec[name.toLowerCase()];
60
+ return typeof value === "string" ? value : void 0;
61
+ } : void 0;
62
+ if (!getter) return void 0;
63
+ for (const name of names) {
64
+ const value = getter(name);
65
+ if (value != null) return value;
66
+ }
67
+ return void 0;
68
+ }
54
69
  var GGAIError = class extends Error {
55
70
  source;
56
71
  requestId;
@@ -103,6 +118,10 @@ function isUsageLimitError(err) {
103
118
  if (!(err instanceof Error)) return false;
104
119
  return /usage limit reached/i.test(err.message);
105
120
  }
121
+ function isHardBillingMessage(message) {
122
+ const lower = message.toLowerCase();
123
+ return lower.includes("insufficient balance") || lower.includes("insufficient credits") || lower.includes("more credits") || lower.includes("insufficient_quota") || lower.includes("exceeded your current quota") || lower.includes("quota exceeded") || lower.includes("no resource package") || lower.includes("recharge") || lower.includes("balance is too low") || lower.includes("out of credits") || lower.includes("arrears") || lower.includes("arrearage") || lower.includes("token quota") || lower.includes("exceeded_current_quota_error") || lower.includes("check your account balance") || lower.includes("does not yet include access") || lower.includes("subscription plan") || lower.includes("billing");
124
+ }
106
125
  function formatResetTime(resetsAt) {
107
126
  const when = new Date(resetsAt * 1e3);
108
127
  const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
@@ -352,6 +371,9 @@ function zodToJsonSchema(schema) {
352
371
  const { $schema: _schema, ...rest } = jsonSchema;
353
372
  return normalizeRootForAnthropic(rest);
354
373
  }
374
+ function resolveToolSchema(tool) {
375
+ return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
376
+ }
355
377
  function normalizeRootForAnthropic(schema) {
356
378
  const branches = schema.oneOf ?? schema.anyOf;
357
379
  if (!branches || branches.length === 0) {
@@ -465,6 +487,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
465
487
  }
466
488
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
467
489
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
490
+ var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
468
491
  function stripImages(content, placeholder) {
469
492
  const out = [];
470
493
  let lastWasPlaceholder = false;
@@ -475,10 +498,33 @@ function stripImages(content, placeholder) {
475
498
  continue;
476
499
  }
477
500
  out.push(block);
478
- lastWasPlaceholder = block.text === placeholder;
501
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
502
+ }
503
+ return out;
504
+ }
505
+ function stripVideos(content, placeholder) {
506
+ const out = [];
507
+ let lastWasPlaceholder = false;
508
+ for (const block of content) {
509
+ if (block.type === "video") {
510
+ if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
511
+ lastWasPlaceholder = true;
512
+ continue;
513
+ }
514
+ out.push(block);
515
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
479
516
  }
480
517
  return out;
481
518
  }
519
+ function downgradeUnsupportedVideos(messages, supportsVideo) {
520
+ if (supportsVideo === true) return messages;
521
+ return messages.map((msg) => {
522
+ if (msg.role === "user" && Array.isArray(msg.content)) {
523
+ return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
524
+ }
525
+ return msg;
526
+ });
527
+ }
482
528
  function downgradeUnsupportedImages(messages, supportsImages) {
483
529
  if (supportsImages !== false) return messages;
484
530
  return messages.map((msg) => {
@@ -555,6 +601,16 @@ function toAnthropicMessages(messages, cacheControl) {
555
601
  role: "user",
556
602
  content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
557
603
  if (part.type === "text") return { type: "text", text: part.text };
604
+ if (part.type === "video") {
605
+ return {
606
+ type: "video",
607
+ source: {
608
+ type: "base64",
609
+ media_type: part.mediaType,
610
+ data: part.data
611
+ }
612
+ };
613
+ }
558
614
  return {
559
615
  type: "image",
560
616
  source: {
@@ -654,11 +710,11 @@ function toAnthropicToolChoice(choice) {
654
710
  if (choice === "required") return { type: "any" };
655
711
  return { type: "tool", name: choice.name };
656
712
  }
657
- function supportsAdaptiveThinking(model) {
658
- return /opus-4-8|opus-4-7|opus-4-6|sonnet-4-6/.test(model);
713
+ function isAdaptiveThinkingModel(model) {
714
+ return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6/.test(model);
659
715
  }
660
716
  function toAnthropicThinking(level, maxTokens, model) {
661
- if (supportsAdaptiveThinking(model)) {
717
+ if (isAdaptiveThinkingModel(model)) {
662
718
  let effort = level;
663
719
  if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
664
720
  effort = "high";
@@ -715,6 +771,14 @@ function toOpenAIMessages(messages, options) {
715
771
  content: msg.content.map(
716
772
  (part) => {
717
773
  if (part.type === "text") return { type: "text", text: part.text };
774
+ if (part.type === "video") {
775
+ return {
776
+ type: "video_url",
777
+ video_url: {
778
+ url: `data:${part.mediaType};base64,${part.data}`
779
+ }
780
+ };
781
+ }
718
782
  return {
719
783
  type: "image_url",
720
784
  image_url: {
@@ -792,7 +856,7 @@ function toOpenAITools(tools) {
792
856
  function: {
793
857
  name: tool.name,
794
858
  description: tool.description,
795
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters)
859
+ parameters: resolveToolSchema(tool)
796
860
  }
797
861
  }));
798
862
  }
@@ -834,10 +898,22 @@ function normalizeOpenAIStopReason(reason) {
834
898
  }
835
899
  }
836
900
 
837
- // src/providers/anthropic.ts
901
+ // src/utils/json.ts
838
902
  function isJsonObject(value) {
839
903
  return value != null && typeof value === "object" && !Array.isArray(value);
840
904
  }
905
+ function parseToolArguments(argsJson) {
906
+ if (!argsJson) return {};
907
+ try {
908
+ const parsed = JSON.parse(argsJson);
909
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
910
+ return isJsonObject(unwrapped) ? unwrapped : {};
911
+ } catch {
912
+ return {};
913
+ }
914
+ }
915
+
916
+ // src/providers/anthropic.ts
841
917
  function createClient(options) {
842
918
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
843
919
  return new import_sdk.default({
@@ -868,7 +944,8 @@ async function* runStream(options) {
868
944
  const useStreaming = options.streaming !== false;
869
945
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
870
946
  const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
871
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
947
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
948
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
872
949
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
873
950
  const system = isOAuth ? [
874
951
  {
@@ -930,7 +1007,7 @@ async function* runStream(options) {
930
1007
  })(),
931
1008
  stream: useStreaming
932
1009
  };
933
- const hasAdaptiveThinking = options.model.includes("opus-4-8") || options.model.includes("opus-4.8") || options.model.includes("opus-4-7") || options.model.includes("opus-4.7") || options.model.includes("opus-4-6") || options.model.includes("opus-4.6") || options.model.includes("sonnet-4-6") || options.model.includes("sonnet-4.6");
1010
+ const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
934
1011
  const betaHeaders = [
935
1012
  ...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
936
1013
  ...options.compaction ? ["compact-2026-01-12"] : [],
@@ -1255,19 +1332,13 @@ function messageToResponse(message) {
1255
1332
  };
1256
1333
  }
1257
1334
  function readUnifiedRateLimit(headers) {
1258
- const get = (name) => {
1259
- if (headers && typeof headers.get === "function") {
1260
- return headers.get(name);
1261
- }
1262
- if (headers && typeof headers === "object") {
1263
- const rec = headers;
1264
- const value = rec[name] ?? rec[name.toLowerCase()];
1265
- return typeof value === "string" ? value : null;
1266
- }
1267
- return null;
1268
- };
1269
- const status = get("anthropic-ratelimit-unified-status");
1270
- const resetRaw = get("anthropic-ratelimit-unified-reset") ?? get("anthropic-ratelimit-unified-5h-reset") ?? get("anthropic-ratelimit-unified-7d-reset");
1335
+ const status = readHeader(headers, "anthropic-ratelimit-unified-status");
1336
+ const resetRaw = readHeader(
1337
+ headers,
1338
+ "anthropic-ratelimit-unified-reset",
1339
+ "anthropic-ratelimit-unified-5h-reset",
1340
+ "anthropic-ratelimit-unified-7d-reset"
1341
+ );
1271
1342
  const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
1272
1343
  const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
1273
1344
  return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
@@ -1292,6 +1363,14 @@ function toError(err) {
1292
1363
  });
1293
1364
  }
1294
1365
  }
1366
+ if (isHardBillingMessage(message)) {
1367
+ const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
1368
+ return new ProviderError("anthropic", usageMessage, {
1369
+ statusCode: err.status,
1370
+ ...requestId ? { requestId } : {},
1371
+ cause: err
1372
+ });
1373
+ }
1295
1374
  return new ProviderError("anthropic", message, {
1296
1375
  statusCode: err.status,
1297
1376
  ...requestId ? { requestId } : {},
@@ -1324,19 +1403,30 @@ function fnv1aHash(value) {
1324
1403
  return (hash >>> 0).toString(16).padStart(8, "0");
1325
1404
  }
1326
1405
 
1327
- // src/providers/openai.ts
1328
- function isJsonObject2(value) {
1329
- return value != null && typeof value === "object" && !Array.isArray(value);
1406
+ // src/utils/env.ts
1407
+ function getEnvironment() {
1408
+ return globalThis.process?.env;
1330
1409
  }
1331
- function parseToolArguments(argsJson) {
1332
- if (!argsJson) return {};
1333
- try {
1334
- const parsed = JSON.parse(argsJson);
1335
- const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1336
- return isJsonObject2(unwrapped) ? unwrapped : {};
1337
- } catch {
1338
- return {};
1410
+
1411
+ // src/providers/openai.ts
1412
+ function extractOpenAIUsage(usage) {
1413
+ let cacheRead = 0;
1414
+ const details = usage.prompt_tokens_details;
1415
+ if (details?.cached_tokens) {
1416
+ cacheRead = details.cached_tokens;
1417
+ }
1418
+ const usageAny = usage;
1419
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1420
+ cacheRead = usageAny.cached_tokens;
1339
1421
  }
1422
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1423
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1424
+ }
1425
+ return {
1426
+ inputTokens: usage.prompt_tokens - cacheRead,
1427
+ outputTokens: usage.completion_tokens,
1428
+ cacheRead
1429
+ };
1340
1430
  }
1341
1431
  function createClient2(options) {
1342
1432
  return new import_openai.default({
@@ -1353,7 +1443,8 @@ async function* runStream2(options) {
1353
1443
  const useStreaming = options.streaming !== false;
1354
1444
  const client = createClient2(options);
1355
1445
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1356
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1446
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1447
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1357
1448
  const messages = toOpenAIMessages(downgradedMessages, {
1358
1449
  provider: options.provider,
1359
1450
  thinking: !!options.thinking,
@@ -1392,7 +1483,7 @@ async function* runStream2(options) {
1392
1483
  params.thinking = { type: "disabled" };
1393
1484
  }
1394
1485
  }
1395
- if (globalThis.process && globalThis.process.env?.GGAI_DUMP_REQUEST) {
1486
+ if (getEnvironment()?.GGAI_DUMP_REQUEST) {
1396
1487
  const fs = await import("fs");
1397
1488
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1398
1489
  const dumpPath = `/tmp/ggai-request-${ts}.json`;
@@ -1433,19 +1524,7 @@ async function* runStream2(options) {
1433
1524
  for await (const chunk of stream2) {
1434
1525
  const choice = chunk.choices?.[0];
1435
1526
  if (chunk.usage) {
1436
- outputTokens = chunk.usage.completion_tokens;
1437
- const details = chunk.usage.prompt_tokens_details;
1438
- if (details?.cached_tokens) {
1439
- cacheRead = details.cached_tokens;
1440
- }
1441
- const usageAny = chunk.usage;
1442
- if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1443
- cacheRead = usageAny.cached_tokens;
1444
- }
1445
- if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1446
- cacheRead = usageAny.prompt_cache_hit_tokens;
1447
- }
1448
- inputTokens = chunk.usage.prompt_tokens - cacheRead;
1527
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1449
1528
  }
1450
1529
  if (!choice) continue;
1451
1530
  if (choice.finish_reason) {
@@ -1591,17 +1670,7 @@ function completionToResponse(completion) {
1591
1670
  let outputTokens = 0;
1592
1671
  let cacheRead = 0;
1593
1672
  if (completion.usage) {
1594
- outputTokens = completion.usage.completion_tokens;
1595
- const details = completion.usage.prompt_tokens_details;
1596
- if (details?.cached_tokens) cacheRead = details.cached_tokens;
1597
- const usageAny = completion.usage;
1598
- if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1599
- cacheRead = usageAny.cached_tokens;
1600
- }
1601
- if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1602
- cacheRead = usageAny.prompt_cache_hit_tokens;
1603
- }
1604
- inputTokens = completion.usage.prompt_tokens - cacheRead;
1673
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
1605
1674
  }
1606
1675
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1607
1676
  return {
@@ -1613,6 +1682,16 @@ function completionToResponse(completion) {
1613
1682
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1614
1683
  };
1615
1684
  }
1685
+ function classifyOpenAICompatLimit(args) {
1686
+ const { status, code, type, message } = args;
1687
+ const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
1688
+ const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
1689
+ if (isHard) return "hard";
1690
+ if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
1691
+ return "transient";
1692
+ }
1693
+ return null;
1694
+ }
1616
1695
  function toError2(err, provider = "openai") {
1617
1696
  if (err instanceof import_openai.default.APIError) {
1618
1697
  const body = err.error;
@@ -1624,6 +1703,35 @@ function toError2(err, provider = "openai") {
1624
1703
  hint = "codex-mini-latest requires an OpenAI Pro or Max subscription. Your account currently has access to GPT-5.4 and GPT-5.4 Mini.";
1625
1704
  }
1626
1705
  const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1706
+ const code = typeof err.code === "string" ? err.code : void 0;
1707
+ const type = typeof err.type === "string" ? err.type : void 0;
1708
+ const limit = classifyOpenAICompatLimit({
1709
+ status: err.status,
1710
+ code,
1711
+ type,
1712
+ message: cleanMessage
1713
+ });
1714
+ if (limit === "hard") {
1715
+ const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
1716
+ return new ProviderError(provider, message, {
1717
+ statusCode: err.status,
1718
+ ...requestId ? { requestId } : {},
1719
+ ...hint ? { hint } : {},
1720
+ cause: err
1721
+ });
1722
+ }
1723
+ if (limit === "transient") {
1724
+ const retryAfterRaw = readHeader(err.headers, "retry-after");
1725
+ const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
1726
+ const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
1727
+ return new ProviderError(provider, cleanMessage, {
1728
+ statusCode: err.status,
1729
+ ...requestId ? { requestId } : {},
1730
+ ...hint ? { hint } : {},
1731
+ ...resetsAt ? { resetsAt } : {},
1732
+ cause: err
1733
+ });
1734
+ }
1627
1735
  return new ProviderError(provider, cleanMessage, {
1628
1736
  statusCode: err.status,
1629
1737
  ...requestId ? { requestId } : {},
@@ -1649,21 +1757,59 @@ function providerDiag(phase, data) {
1649
1757
  _diagFn?.(phase, data);
1650
1758
  }
1651
1759
 
1652
- // src/providers/openai-codex.ts
1653
- var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1654
- function isJsonObject3(value) {
1655
- return value != null && typeof value === "object" && !Array.isArray(value);
1760
+ // src/utils/sse.ts
1761
+ function parseSseBuffer(buffer) {
1762
+ const events = [];
1763
+ let cursor = 0;
1764
+ while (true) {
1765
+ const next = buffer.indexOf("\n\n", cursor);
1766
+ if (next === -1) break;
1767
+ const raw = buffer.slice(cursor, next);
1768
+ cursor = next + 2;
1769
+ let eventName;
1770
+ const dataLines = [];
1771
+ for (const line of raw.split("\n")) {
1772
+ if (line.startsWith("event:")) {
1773
+ eventName = line.slice("event:".length).trim();
1774
+ } else if (line.startsWith("data:")) {
1775
+ dataLines.push(line.slice("data:".length).trimStart());
1776
+ }
1777
+ }
1778
+ if (dataLines.length > 0) {
1779
+ events.push({ event: eventName, data: dataLines.join("\n") });
1780
+ }
1781
+ }
1782
+ return { events, remaining: buffer.slice(cursor) };
1656
1783
  }
1657
- function parseToolArguments2(argsJson) {
1658
- if (!argsJson) return {};
1784
+ async function* readSseStream(body) {
1785
+ const reader = body.getReader();
1786
+ const decoder = new TextDecoder();
1787
+ let buffer = "";
1659
1788
  try {
1660
- const parsed = JSON.parse(argsJson);
1661
- const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1662
- return isJsonObject3(unwrapped) ? unwrapped : {};
1663
- } catch {
1664
- return {};
1789
+ while (true) {
1790
+ const { done, value } = await reader.read();
1791
+ if (done) break;
1792
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
1793
+ const parsed2 = parseSseBuffer(buffer);
1794
+ buffer = parsed2.remaining;
1795
+ yield* parsed2.events;
1796
+ }
1797
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
1798
+ const parsed = parseSseBuffer(buffer + "\n\n");
1799
+ yield* parsed.events;
1800
+ } finally {
1801
+ reader.releaseLock();
1665
1802
  }
1666
1803
  }
1804
+
1805
+ // src/utils/request-id.ts
1806
+ function extractRequestIdFromMessage(message) {
1807
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1808
+ return match?.[1];
1809
+ }
1810
+
1811
+ // src/providers/openai-codex.ts
1812
+ var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1667
1813
  function outputTextKey(itemId, contentIndex) {
1668
1814
  return `${itemId ?? ""}:${contentIndex ?? 0}`;
1669
1815
  }
@@ -1676,7 +1822,8 @@ function streamOpenAICodex(options) {
1676
1822
  async function* runStream3(options) {
1677
1823
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1678
1824
  const url = `${baseUrl}/codex/responses`;
1679
- const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1825
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1826
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1680
1827
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
1681
1828
  const body = {
1682
1829
  model: options.model,
@@ -1728,7 +1875,7 @@ async function* runStream3(options) {
1728
1875
  const text = await response.text().catch(() => "");
1729
1876
  const parsed = parseCodexErrorBody(text);
1730
1877
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1731
- const requestId = parsed.requestId ?? response.headers.get("x-request-id") ?? response.headers.get("openai-request-id") ?? response.headers.get("x-oai-request-id") ?? void 0;
1878
+ const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
1732
1879
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
1733
1880
  if (usageLimit) throw usageLimit;
1734
1881
  let hint;
@@ -1772,7 +1919,7 @@ async function* runStream3(options) {
1772
1919
  const nested = event.error ?? void 0;
1773
1920
  const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1774
1921
  const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1775
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1922
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1776
1923
  const usageLimit = codexUsageLimitError(
1777
1924
  nested ?? event,
1778
1925
  void 0,
@@ -1787,7 +1934,7 @@ async function* runStream3(options) {
1787
1934
  if (type === "response.failed") {
1788
1935
  const nested = event.error;
1789
1936
  const message = nested?.message ?? "Codex response failed.";
1790
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1937
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1791
1938
  throw new ProviderError("openai", message, {
1792
1939
  ...requestId != null ? { requestId } : {}
1793
1940
  });
@@ -1906,7 +2053,7 @@ async function* runStream3(options) {
1906
2053
  const id = `${callId}|${itemId}`;
1907
2054
  const tc = toolCalls.get(id);
1908
2055
  if (tc) {
1909
- const args = parseToolArguments2(tc.argsJson);
2056
+ const args = parseToolArguments(tc.argsJson);
1910
2057
  yield {
1911
2058
  type: "toolcall_done",
1912
2059
  id: tc.id,
@@ -1930,7 +2077,7 @@ async function* runStream3(options) {
1930
2077
  contentParts.push({ type: "text", text: textAccum });
1931
2078
  }
1932
2079
  for (const [, tc] of toolCalls) {
1933
- const args = parseToolArguments2(tc.argsJson);
2080
+ const args = parseToolArguments(tc.argsJson);
1934
2081
  const toolCall = {
1935
2082
  type: "tool_call",
1936
2083
  id: tc.id,
@@ -1953,33 +2100,13 @@ async function* runStream3(options) {
1953
2100
  return streamResponse;
1954
2101
  }
1955
2102
  async function* parseSSE(body) {
1956
- const reader = body.getReader();
1957
- const decoder = new TextDecoder();
1958
- let buffer = "";
1959
- try {
1960
- while (true) {
1961
- const { done, value } = await reader.read();
1962
- if (done) break;
1963
- buffer += decoder.decode(value, { stream: true });
1964
- let idx = buffer.indexOf("\n\n");
1965
- while (idx !== -1) {
1966
- const chunk = buffer.slice(0, idx);
1967
- buffer = buffer.slice(idx + 2);
1968
- const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
1969
- if (dataLines.length > 0) {
1970
- const data = dataLines.join("\n").trim();
1971
- if (data && data !== "[DONE]") {
1972
- try {
1973
- yield JSON.parse(data);
1974
- } catch {
1975
- }
1976
- }
1977
- }
1978
- idx = buffer.indexOf("\n\n");
1979
- }
2103
+ for await (const event of readSseStream(body)) {
2104
+ const data = event.data.trim();
2105
+ if (!data || data === "[DONE]") continue;
2106
+ try {
2107
+ yield JSON.parse(data);
2108
+ } catch {
1980
2109
  }
1981
- } finally {
1982
- reader.releaseLock();
1983
2110
  }
1984
2111
  }
1985
2112
  function remapCodexId(id, idMap) {
@@ -1990,10 +2117,6 @@ function remapCodexId(id, idMap) {
1990
2117
  idMap.set(id, mapped);
1991
2118
  return mapped;
1992
2119
  }
1993
- function codexToolResultText(content) {
1994
- if (typeof content === "string") return content;
1995
- return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
1996
- }
1997
2120
  function toCodexInput(messages, options) {
1998
2121
  let system;
1999
2122
  const input = [];
@@ -2050,7 +2173,7 @@ function toCodexInput(messages, options) {
2050
2173
  const toolImages = [];
2051
2174
  for (const result of msg.content) {
2052
2175
  const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
2053
- const text = codexToolResultText(result.content);
2176
+ const text = toolResultText(result.content);
2054
2177
  input.push({
2055
2178
  type: "function_call_output",
2056
2179
  call_id: remapCodexId(callId, idMap),
@@ -2085,14 +2208,10 @@ function toCodexTools(tools) {
2085
2208
  type: "function",
2086
2209
  name: tool.name,
2087
2210
  description: tool.description,
2088
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
2211
+ parameters: resolveToolSchema(tool),
2089
2212
  strict: null
2090
2213
  }));
2091
2214
  }
2092
- function extractCodexRequestId(message) {
2093
- const match = message.match(/request ID ([a-z0-9-]{8,})/i);
2094
- return match?.[1];
2095
- }
2096
2215
  function parseCodexErrorBody(text) {
2097
2216
  if (!text) return {};
2098
2217
  try {
@@ -2100,7 +2219,7 @@ function parseCodexErrorBody(text) {
2100
2219
  const error = parsed.error;
2101
2220
  const detail = parsed.detail;
2102
2221
  const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2103
- const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
2222
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2104
2223
  const errorObj = error ?? parsed;
2105
2224
  return {
2106
2225
  ...message ? { message } : {},
@@ -2150,12 +2269,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
2150
2269
  "gemma-4-31b-it",
2151
2270
  "gemma-4-26b-a4b-it"
2152
2271
  ]);
2153
- function isJsonObject4(value) {
2154
- return value != null && typeof value === "object" && !Array.isArray(value);
2155
- }
2156
- function getEnvironment() {
2157
- return globalThis.process?.env;
2158
- }
2159
2272
  function getGoogleProject(options) {
2160
2273
  const env = getEnvironment();
2161
2274
  return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
@@ -2177,6 +2290,20 @@ ${formatUnsupportedModelMessage(model)}`;
2177
2290
  }
2178
2291
  return `Gemini API error (${status}): ${body}`;
2179
2292
  }
2293
+ function parseRetryDelaySeconds(body) {
2294
+ const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
2295
+ if (!match) return void 0;
2296
+ const seconds = Number(match[1]);
2297
+ return Number.isFinite(seconds) ? seconds : void 0;
2298
+ }
2299
+ function parseGeminiQuota(status, body) {
2300
+ if (status !== 429) return null;
2301
+ const lower = body.toLowerCase();
2302
+ if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
2303
+ const retryDelaySeconds = parseRetryDelaySeconds(body);
2304
+ const exhausted = retryDelaySeconds === void 0;
2305
+ return { exhausted, retryDelaySeconds };
2306
+ }
2180
2307
  function toSystemAndContents(messages) {
2181
2308
  let systemText = "";
2182
2309
  const contents = [];
@@ -2255,7 +2382,7 @@ function toGeminiTools(tools) {
2255
2382
  functionDeclarations: tools.map((tool) => ({
2256
2383
  name: tool.name,
2257
2384
  description: tool.description,
2258
- parameters: sanitizeSchema(tool.rawInputSchema ?? zodToJsonSchema(tool.parameters))
2385
+ parameters: sanitizeSchema(resolveToolSchema(tool))
2259
2386
  }))
2260
2387
  }
2261
2388
  ];
@@ -2266,7 +2393,7 @@ function sanitizeSchema(schema) {
2266
2393
  return clone;
2267
2394
  }
2268
2395
  function stripUnsupportedSchemaFields(value) {
2269
- if (!isJsonObject4(value)) {
2396
+ if (!isJsonObject(value)) {
2270
2397
  if (Array.isArray(value)) {
2271
2398
  for (const item of value) stripUnsupportedSchemaFields(item);
2272
2399
  }
@@ -2275,7 +2402,7 @@ function stripUnsupportedSchemaFields(value) {
2275
2402
  delete value.$schema;
2276
2403
  delete value.additionalProperties;
2277
2404
  for (const item of Object.values(value)) {
2278
- if (isJsonObject4(item) || Array.isArray(item)) {
2405
+ if (isJsonObject(item) || Array.isArray(item)) {
2279
2406
  stripUnsupportedSchemaFields(item);
2280
2407
  }
2281
2408
  }
@@ -2328,7 +2455,8 @@ function toThinkingConfig(model, level) {
2328
2455
  };
2329
2456
  }
2330
2457
  function buildGenerateRequest(options) {
2331
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2458
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2459
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2332
2460
  const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
2333
2461
  const tools = toGeminiTools(options.tools);
2334
2462
  const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
@@ -2390,54 +2518,11 @@ function normalizeGeminiStopReason(reason) {
2390
2518
  return "end_turn";
2391
2519
  }
2392
2520
  }
2393
- function parseSseEvents(buffer) {
2394
- const events = [];
2395
- let cursor = 0;
2396
- while (true) {
2397
- const next = buffer.indexOf("\n\n", cursor);
2398
- if (next === -1) break;
2399
- const raw = buffer.slice(cursor, next);
2400
- cursor = next + 2;
2401
- let eventName;
2402
- const dataLines = [];
2403
- for (const line of raw.split("\n")) {
2404
- if (line.startsWith("event:")) {
2405
- eventName = line.slice("event:".length).trim();
2406
- } else if (line.startsWith("data:")) {
2407
- dataLines.push(line.slice("data:".length).trimStart());
2408
- }
2409
- }
2410
- if (dataLines.length > 0) {
2411
- events.push({ event: eventName, data: dataLines.join("\n") });
2412
- }
2413
- }
2414
- return { events, remaining: buffer.slice(cursor) };
2415
- }
2416
2521
  async function* streamSse(response) {
2417
2522
  if (!response.body) return;
2418
- const reader = response.body.getReader();
2419
- const decoder = new TextDecoder();
2420
- let buffer = "";
2421
- try {
2422
- while (true) {
2423
- const { done, value } = await reader.read();
2424
- if (done) break;
2425
- buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
2426
- const parsed2 = parseSseEvents(buffer);
2427
- buffer = parsed2.remaining;
2428
- for (const event of parsed2.events) {
2429
- if (event.data === "[DONE]") continue;
2430
- yield JSON.parse(event.data);
2431
- }
2432
- }
2433
- buffer += decoder.decode().replace(/\r\n/g, "\n");
2434
- const parsed = parseSseEvents(buffer + "\n\n");
2435
- for (const event of parsed.events) {
2436
- if (event.data === "[DONE]") continue;
2437
- yield JSON.parse(event.data);
2438
- }
2439
- } finally {
2440
- reader.releaseLock();
2523
+ for await (const event of readSseStream(response.body)) {
2524
+ if (event.data === "[DONE]") continue;
2525
+ yield JSON.parse(event.data);
2441
2526
  }
2442
2527
  }
2443
2528
  function candidatesFromResponse(response) {
@@ -2460,7 +2545,7 @@ function readFunctionCallPart(part) {
2460
2545
  return {
2461
2546
  ...part.functionCall.id ? { id: part.functionCall.id } : {},
2462
2547
  name: part.functionCall.name,
2463
- args: isJsonObject4(part.functionCall.args) ? part.functionCall.args : {}
2548
+ args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
2464
2549
  };
2465
2550
  }
2466
2551
  function makeToolCallId(index, providerId) {
@@ -2499,8 +2584,17 @@ async function fetchCodeAssist(plan, options) {
2499
2584
  });
2500
2585
  if (!response.ok) {
2501
2586
  const text = await response.text().catch(() => "");
2502
- throw new ProviderError("gemini", formatErrorMessage(response.status, text, options.model), {
2503
- statusCode: response.status
2587
+ const quota = parseGeminiQuota(response.status, text);
2588
+ let message = formatErrorMessage(response.status, text, options.model);
2589
+ let resetsAt;
2590
+ if (quota?.exhausted) {
2591
+ message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
2592
+ } else if (quota?.retryDelaySeconds !== void 0) {
2593
+ resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
2594
+ }
2595
+ throw new ProviderError("gemini", message, {
2596
+ statusCode: response.status,
2597
+ ...resetsAt !== void 0 ? { resetsAt } : {}
2504
2598
  });
2505
2599
  }
2506
2600
  return response;
@@ -2892,6 +2986,7 @@ function registerPalsuProvider(config) {
2892
2986
  StreamResult,
2893
2987
  formatError,
2894
2988
  formatErrorForDisplay,
2989
+ isHardBillingMessage,
2895
2990
  isUsageLimitError,
2896
2991
  palsuAssistantMessage,
2897
2992
  palsuText,