@kenkaiiii/gg-ai 4.3.237 → 4.3.239

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
@@ -1,4 +1,18 @@
1
1
  // src/errors.ts
2
+ function readHeader(headers, ...names) {
3
+ if (!headers) return void 0;
4
+ const getter = typeof headers.get === "function" ? (name) => headers.get(name) ?? void 0 : typeof headers === "object" ? (name) => {
5
+ const rec = headers;
6
+ const value = rec[name] ?? rec[name.toLowerCase()];
7
+ return typeof value === "string" ? value : void 0;
8
+ } : void 0;
9
+ if (!getter) return void 0;
10
+ for (const name of names) {
11
+ const value = getter(name);
12
+ if (value != null) return value;
13
+ }
14
+ return void 0;
15
+ }
2
16
  var GGAIError = class extends Error {
3
17
  source;
4
18
  requestId;
@@ -51,6 +65,10 @@ function isUsageLimitError(err) {
51
65
  if (!(err instanceof Error)) return false;
52
66
  return /usage limit reached/i.test(err.message);
53
67
  }
68
+ function isHardBillingMessage(message) {
69
+ const lower = message.toLowerCase();
70
+ 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");
71
+ }
54
72
  function formatResetTime(resetsAt) {
55
73
  const when = new Date(resetsAt * 1e3);
56
74
  const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
@@ -300,6 +318,9 @@ function zodToJsonSchema(schema) {
300
318
  const { $schema: _schema, ...rest } = jsonSchema;
301
319
  return normalizeRootForAnthropic(rest);
302
320
  }
321
+ function resolveToolSchema(tool) {
322
+ return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
323
+ }
303
324
  function normalizeRootForAnthropic(schema) {
304
325
  const branches = schema.oneOf ?? schema.anyOf;
305
326
  if (!branches || branches.length === 0) {
@@ -413,6 +434,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
413
434
  }
414
435
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
415
436
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
437
+ var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
416
438
  function stripImages(content, placeholder) {
417
439
  const out = [];
418
440
  let lastWasPlaceholder = false;
@@ -423,10 +445,33 @@ function stripImages(content, placeholder) {
423
445
  continue;
424
446
  }
425
447
  out.push(block);
426
- lastWasPlaceholder = block.text === placeholder;
448
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
427
449
  }
428
450
  return out;
429
451
  }
452
+ function stripVideos(content, placeholder) {
453
+ const out = [];
454
+ let lastWasPlaceholder = false;
455
+ for (const block of content) {
456
+ if (block.type === "video") {
457
+ if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
458
+ lastWasPlaceholder = true;
459
+ continue;
460
+ }
461
+ out.push(block);
462
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
463
+ }
464
+ return out;
465
+ }
466
+ function downgradeUnsupportedVideos(messages, supportsVideo) {
467
+ if (supportsVideo === true) return messages;
468
+ return messages.map((msg) => {
469
+ if (msg.role === "user" && Array.isArray(msg.content)) {
470
+ return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
471
+ }
472
+ return msg;
473
+ });
474
+ }
430
475
  function downgradeUnsupportedImages(messages, supportsImages) {
431
476
  if (supportsImages !== false) return messages;
432
477
  return messages.map((msg) => {
@@ -503,6 +548,16 @@ function toAnthropicMessages(messages, cacheControl) {
503
548
  role: "user",
504
549
  content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
505
550
  if (part.type === "text") return { type: "text", text: part.text };
551
+ if (part.type === "video") {
552
+ return {
553
+ type: "video",
554
+ source: {
555
+ type: "base64",
556
+ media_type: part.mediaType,
557
+ data: part.data
558
+ }
559
+ };
560
+ }
506
561
  return {
507
562
  type: "image",
508
563
  source: {
@@ -602,11 +657,11 @@ function toAnthropicToolChoice(choice) {
602
657
  if (choice === "required") return { type: "any" };
603
658
  return { type: "tool", name: choice.name };
604
659
  }
605
- function supportsAdaptiveThinking(model) {
606
- return /opus-4-8|opus-4-7|opus-4-6|sonnet-4-6/.test(model);
660
+ function isAdaptiveThinkingModel(model) {
661
+ return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6/.test(model);
607
662
  }
608
663
  function toAnthropicThinking(level, maxTokens, model) {
609
- if (supportsAdaptiveThinking(model)) {
664
+ if (isAdaptiveThinkingModel(model)) {
610
665
  let effort = level;
611
666
  if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
612
667
  effort = "high";
@@ -663,6 +718,14 @@ function toOpenAIMessages(messages, options) {
663
718
  content: msg.content.map(
664
719
  (part) => {
665
720
  if (part.type === "text") return { type: "text", text: part.text };
721
+ if (part.type === "video") {
722
+ return {
723
+ type: "video_url",
724
+ video_url: {
725
+ url: `data:${part.mediaType};base64,${part.data}`
726
+ }
727
+ };
728
+ }
666
729
  return {
667
730
  type: "image_url",
668
731
  image_url: {
@@ -740,7 +803,7 @@ function toOpenAITools(tools) {
740
803
  function: {
741
804
  name: tool.name,
742
805
  description: tool.description,
743
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters)
806
+ parameters: resolveToolSchema(tool)
744
807
  }
745
808
  }));
746
809
  }
@@ -782,10 +845,22 @@ function normalizeOpenAIStopReason(reason) {
782
845
  }
783
846
  }
784
847
 
785
- // src/providers/anthropic.ts
848
+ // src/utils/json.ts
786
849
  function isJsonObject(value) {
787
850
  return value != null && typeof value === "object" && !Array.isArray(value);
788
851
  }
852
+ function parseToolArguments(argsJson) {
853
+ if (!argsJson) return {};
854
+ try {
855
+ const parsed = JSON.parse(argsJson);
856
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
857
+ return isJsonObject(unwrapped) ? unwrapped : {};
858
+ } catch {
859
+ return {};
860
+ }
861
+ }
862
+
863
+ // src/providers/anthropic.ts
789
864
  function createClient(options) {
790
865
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
791
866
  return new Anthropic({
@@ -816,7 +891,8 @@ async function* runStream(options) {
816
891
  const useStreaming = options.streaming !== false;
817
892
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
818
893
  const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
819
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
894
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
895
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
820
896
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
821
897
  const system = isOAuth ? [
822
898
  {
@@ -878,7 +954,7 @@ async function* runStream(options) {
878
954
  })(),
879
955
  stream: useStreaming
880
956
  };
881
- 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");
957
+ const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
882
958
  const betaHeaders = [
883
959
  ...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
884
960
  ...options.compaction ? ["compact-2026-01-12"] : [],
@@ -1022,11 +1098,18 @@ async function* runStream(options) {
1022
1098
  args: tc.args
1023
1099
  };
1024
1100
  } else if (accum.type === "server_tool_use") {
1101
+ let input = accum.input;
1102
+ if (accum.argsJson) {
1103
+ try {
1104
+ input = JSON.parse(accum.argsJson);
1105
+ } catch {
1106
+ }
1107
+ }
1025
1108
  const stc = {
1026
1109
  type: "server_tool_call",
1027
1110
  id: accum.toolId,
1028
1111
  name: accum.toolName,
1029
- input: accum.input
1112
+ input
1030
1113
  };
1031
1114
  contentParts.push(stc);
1032
1115
  yield {
@@ -1203,19 +1286,13 @@ function messageToResponse(message) {
1203
1286
  };
1204
1287
  }
1205
1288
  function readUnifiedRateLimit(headers) {
1206
- const get = (name) => {
1207
- if (headers && typeof headers.get === "function") {
1208
- return headers.get(name);
1209
- }
1210
- if (headers && typeof headers === "object") {
1211
- const rec = headers;
1212
- const value = rec[name] ?? rec[name.toLowerCase()];
1213
- return typeof value === "string" ? value : null;
1214
- }
1215
- return null;
1216
- };
1217
- const status = get("anthropic-ratelimit-unified-status");
1218
- const resetRaw = get("anthropic-ratelimit-unified-reset") ?? get("anthropic-ratelimit-unified-5h-reset") ?? get("anthropic-ratelimit-unified-7d-reset");
1289
+ const status = readHeader(headers, "anthropic-ratelimit-unified-status");
1290
+ const resetRaw = readHeader(
1291
+ headers,
1292
+ "anthropic-ratelimit-unified-reset",
1293
+ "anthropic-ratelimit-unified-5h-reset",
1294
+ "anthropic-ratelimit-unified-7d-reset"
1295
+ );
1219
1296
  const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
1220
1297
  const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
1221
1298
  return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
@@ -1240,6 +1317,14 @@ function toError(err) {
1240
1317
  });
1241
1318
  }
1242
1319
  }
1320
+ if (isHardBillingMessage(message)) {
1321
+ const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
1322
+ return new ProviderError("anthropic", usageMessage, {
1323
+ statusCode: err.status,
1324
+ ...requestId ? { requestId } : {},
1325
+ cause: err
1326
+ });
1327
+ }
1243
1328
  return new ProviderError("anthropic", message, {
1244
1329
  statusCode: err.status,
1245
1330
  ...requestId ? { requestId } : {},
@@ -1272,19 +1357,30 @@ function fnv1aHash(value) {
1272
1357
  return (hash >>> 0).toString(16).padStart(8, "0");
1273
1358
  }
1274
1359
 
1275
- // src/providers/openai.ts
1276
- function isJsonObject2(value) {
1277
- return value != null && typeof value === "object" && !Array.isArray(value);
1360
+ // src/utils/env.ts
1361
+ function getEnvironment() {
1362
+ return globalThis.process?.env;
1278
1363
  }
1279
- function parseToolArguments(argsJson) {
1280
- if (!argsJson) return {};
1281
- try {
1282
- const parsed = JSON.parse(argsJson);
1283
- const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1284
- return isJsonObject2(unwrapped) ? unwrapped : {};
1285
- } catch {
1286
- return {};
1364
+
1365
+ // src/providers/openai.ts
1366
+ function extractOpenAIUsage(usage) {
1367
+ let cacheRead = 0;
1368
+ const details = usage.prompt_tokens_details;
1369
+ if (details?.cached_tokens) {
1370
+ cacheRead = details.cached_tokens;
1371
+ }
1372
+ const usageAny = usage;
1373
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1374
+ cacheRead = usageAny.cached_tokens;
1287
1375
  }
1376
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1377
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1378
+ }
1379
+ return {
1380
+ inputTokens: usage.prompt_tokens - cacheRead,
1381
+ outputTokens: usage.completion_tokens,
1382
+ cacheRead
1383
+ };
1288
1384
  }
1289
1385
  function createClient2(options) {
1290
1386
  return new OpenAI({
@@ -1301,7 +1397,8 @@ async function* runStream2(options) {
1301
1397
  const useStreaming = options.streaming !== false;
1302
1398
  const client = createClient2(options);
1303
1399
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1304
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1400
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1401
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1305
1402
  const messages = toOpenAIMessages(downgradedMessages, {
1306
1403
  provider: options.provider,
1307
1404
  thinking: !!options.thinking,
@@ -1340,7 +1437,7 @@ async function* runStream2(options) {
1340
1437
  params.thinking = { type: "disabled" };
1341
1438
  }
1342
1439
  }
1343
- if (globalThis.process && globalThis.process.env?.GGAI_DUMP_REQUEST) {
1440
+ if (getEnvironment()?.GGAI_DUMP_REQUEST) {
1344
1441
  const fs = await import("fs");
1345
1442
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1346
1443
  const dumpPath = `/tmp/ggai-request-${ts}.json`;
@@ -1381,19 +1478,7 @@ async function* runStream2(options) {
1381
1478
  for await (const chunk of stream2) {
1382
1479
  const choice = chunk.choices?.[0];
1383
1480
  if (chunk.usage) {
1384
- outputTokens = chunk.usage.completion_tokens;
1385
- const details = chunk.usage.prompt_tokens_details;
1386
- if (details?.cached_tokens) {
1387
- cacheRead = details.cached_tokens;
1388
- }
1389
- const usageAny = chunk.usage;
1390
- if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1391
- cacheRead = usageAny.cached_tokens;
1392
- }
1393
- if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1394
- cacheRead = usageAny.prompt_cache_hit_tokens;
1395
- }
1396
- inputTokens = chunk.usage.prompt_tokens - cacheRead;
1481
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1397
1482
  }
1398
1483
  if (!choice) continue;
1399
1484
  if (choice.finish_reason) {
@@ -1539,17 +1624,7 @@ function completionToResponse(completion) {
1539
1624
  let outputTokens = 0;
1540
1625
  let cacheRead = 0;
1541
1626
  if (completion.usage) {
1542
- outputTokens = completion.usage.completion_tokens;
1543
- const details = completion.usage.prompt_tokens_details;
1544
- if (details?.cached_tokens) cacheRead = details.cached_tokens;
1545
- const usageAny = completion.usage;
1546
- if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1547
- cacheRead = usageAny.cached_tokens;
1548
- }
1549
- if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1550
- cacheRead = usageAny.prompt_cache_hit_tokens;
1551
- }
1552
- inputTokens = completion.usage.prompt_tokens - cacheRead;
1627
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
1553
1628
  }
1554
1629
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1555
1630
  return {
@@ -1561,6 +1636,16 @@ function completionToResponse(completion) {
1561
1636
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1562
1637
  };
1563
1638
  }
1639
+ function classifyOpenAICompatLimit(args) {
1640
+ const { status, code, type, message } = args;
1641
+ const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
1642
+ const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
1643
+ if (isHard) return "hard";
1644
+ if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
1645
+ return "transient";
1646
+ }
1647
+ return null;
1648
+ }
1564
1649
  function toError2(err, provider = "openai") {
1565
1650
  if (err instanceof OpenAI.APIError) {
1566
1651
  const body = err.error;
@@ -1572,6 +1657,35 @@ function toError2(err, provider = "openai") {
1572
1657
  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.";
1573
1658
  }
1574
1659
  const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1660
+ const code = typeof err.code === "string" ? err.code : void 0;
1661
+ const type = typeof err.type === "string" ? err.type : void 0;
1662
+ const limit = classifyOpenAICompatLimit({
1663
+ status: err.status,
1664
+ code,
1665
+ type,
1666
+ message: cleanMessage
1667
+ });
1668
+ if (limit === "hard") {
1669
+ const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
1670
+ return new ProviderError(provider, message, {
1671
+ statusCode: err.status,
1672
+ ...requestId ? { requestId } : {},
1673
+ ...hint ? { hint } : {},
1674
+ cause: err
1675
+ });
1676
+ }
1677
+ if (limit === "transient") {
1678
+ const retryAfterRaw = readHeader(err.headers, "retry-after");
1679
+ const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
1680
+ const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
1681
+ return new ProviderError(provider, cleanMessage, {
1682
+ statusCode: err.status,
1683
+ ...requestId ? { requestId } : {},
1684
+ ...hint ? { hint } : {},
1685
+ ...resetsAt ? { resetsAt } : {},
1686
+ cause: err
1687
+ });
1688
+ }
1575
1689
  return new ProviderError(provider, cleanMessage, {
1576
1690
  statusCode: err.status,
1577
1691
  ...requestId ? { requestId } : {},
@@ -1597,21 +1711,59 @@ function providerDiag(phase, data) {
1597
1711
  _diagFn?.(phase, data);
1598
1712
  }
1599
1713
 
1600
- // src/providers/openai-codex.ts
1601
- var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1602
- function isJsonObject3(value) {
1603
- return value != null && typeof value === "object" && !Array.isArray(value);
1714
+ // src/utils/sse.ts
1715
+ function parseSseBuffer(buffer) {
1716
+ const events = [];
1717
+ let cursor = 0;
1718
+ while (true) {
1719
+ const next = buffer.indexOf("\n\n", cursor);
1720
+ if (next === -1) break;
1721
+ const raw = buffer.slice(cursor, next);
1722
+ cursor = next + 2;
1723
+ let eventName;
1724
+ const dataLines = [];
1725
+ for (const line of raw.split("\n")) {
1726
+ if (line.startsWith("event:")) {
1727
+ eventName = line.slice("event:".length).trim();
1728
+ } else if (line.startsWith("data:")) {
1729
+ dataLines.push(line.slice("data:".length).trimStart());
1730
+ }
1731
+ }
1732
+ if (dataLines.length > 0) {
1733
+ events.push({ event: eventName, data: dataLines.join("\n") });
1734
+ }
1735
+ }
1736
+ return { events, remaining: buffer.slice(cursor) };
1604
1737
  }
1605
- function parseToolArguments2(argsJson) {
1606
- if (!argsJson) return {};
1738
+ async function* readSseStream(body) {
1739
+ const reader = body.getReader();
1740
+ const decoder = new TextDecoder();
1741
+ let buffer = "";
1607
1742
  try {
1608
- const parsed = JSON.parse(argsJson);
1609
- const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1610
- return isJsonObject3(unwrapped) ? unwrapped : {};
1611
- } catch {
1612
- return {};
1743
+ while (true) {
1744
+ const { done, value } = await reader.read();
1745
+ if (done) break;
1746
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
1747
+ const parsed2 = parseSseBuffer(buffer);
1748
+ buffer = parsed2.remaining;
1749
+ yield* parsed2.events;
1750
+ }
1751
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
1752
+ const parsed = parseSseBuffer(buffer + "\n\n");
1753
+ yield* parsed.events;
1754
+ } finally {
1755
+ reader.releaseLock();
1613
1756
  }
1614
1757
  }
1758
+
1759
+ // src/utils/request-id.ts
1760
+ function extractRequestIdFromMessage(message) {
1761
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1762
+ return match?.[1];
1763
+ }
1764
+
1765
+ // src/providers/openai-codex.ts
1766
+ var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1615
1767
  function outputTextKey(itemId, contentIndex) {
1616
1768
  return `${itemId ?? ""}:${contentIndex ?? 0}`;
1617
1769
  }
@@ -1624,7 +1776,8 @@ function streamOpenAICodex(options) {
1624
1776
  async function* runStream3(options) {
1625
1777
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1626
1778
  const url = `${baseUrl}/codex/responses`;
1627
- const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1779
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1780
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1628
1781
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
1629
1782
  const body = {
1630
1783
  model: options.model,
@@ -1676,7 +1829,7 @@ async function* runStream3(options) {
1676
1829
  const text = await response.text().catch(() => "");
1677
1830
  const parsed = parseCodexErrorBody(text);
1678
1831
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1679
- 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;
1832
+ const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
1680
1833
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
1681
1834
  if (usageLimit) throw usageLimit;
1682
1835
  let hint;
@@ -1720,7 +1873,7 @@ async function* runStream3(options) {
1720
1873
  const nested = event.error ?? void 0;
1721
1874
  const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1722
1875
  const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1723
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1876
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1724
1877
  const usageLimit = codexUsageLimitError(
1725
1878
  nested ?? event,
1726
1879
  void 0,
@@ -1735,7 +1888,7 @@ async function* runStream3(options) {
1735
1888
  if (type === "response.failed") {
1736
1889
  const nested = event.error;
1737
1890
  const message = nested?.message ?? "Codex response failed.";
1738
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1891
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1739
1892
  throw new ProviderError("openai", message, {
1740
1893
  ...requestId != null ? { requestId } : {}
1741
1894
  });
@@ -1854,7 +2007,7 @@ async function* runStream3(options) {
1854
2007
  const id = `${callId}|${itemId}`;
1855
2008
  const tc = toolCalls.get(id);
1856
2009
  if (tc) {
1857
- const args = parseToolArguments2(tc.argsJson);
2010
+ const args = parseToolArguments(tc.argsJson);
1858
2011
  yield {
1859
2012
  type: "toolcall_done",
1860
2013
  id: tc.id,
@@ -1878,7 +2031,7 @@ async function* runStream3(options) {
1878
2031
  contentParts.push({ type: "text", text: textAccum });
1879
2032
  }
1880
2033
  for (const [, tc] of toolCalls) {
1881
- const args = parseToolArguments2(tc.argsJson);
2034
+ const args = parseToolArguments(tc.argsJson);
1882
2035
  const toolCall = {
1883
2036
  type: "tool_call",
1884
2037
  id: tc.id,
@@ -1901,33 +2054,13 @@ async function* runStream3(options) {
1901
2054
  return streamResponse;
1902
2055
  }
1903
2056
  async function* parseSSE(body) {
1904
- const reader = body.getReader();
1905
- const decoder = new TextDecoder();
1906
- let buffer = "";
1907
- try {
1908
- while (true) {
1909
- const { done, value } = await reader.read();
1910
- if (done) break;
1911
- buffer += decoder.decode(value, { stream: true });
1912
- let idx = buffer.indexOf("\n\n");
1913
- while (idx !== -1) {
1914
- const chunk = buffer.slice(0, idx);
1915
- buffer = buffer.slice(idx + 2);
1916
- const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
1917
- if (dataLines.length > 0) {
1918
- const data = dataLines.join("\n").trim();
1919
- if (data && data !== "[DONE]") {
1920
- try {
1921
- yield JSON.parse(data);
1922
- } catch {
1923
- }
1924
- }
1925
- }
1926
- idx = buffer.indexOf("\n\n");
1927
- }
2057
+ for await (const event of readSseStream(body)) {
2058
+ const data = event.data.trim();
2059
+ if (!data || data === "[DONE]") continue;
2060
+ try {
2061
+ yield JSON.parse(data);
2062
+ } catch {
1928
2063
  }
1929
- } finally {
1930
- reader.releaseLock();
1931
2064
  }
1932
2065
  }
1933
2066
  function remapCodexId(id, idMap) {
@@ -1938,10 +2071,6 @@ function remapCodexId(id, idMap) {
1938
2071
  idMap.set(id, mapped);
1939
2072
  return mapped;
1940
2073
  }
1941
- function codexToolResultText(content) {
1942
- if (typeof content === "string") return content;
1943
- return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
1944
- }
1945
2074
  function toCodexInput(messages, options) {
1946
2075
  let system;
1947
2076
  const input = [];
@@ -1998,7 +2127,7 @@ function toCodexInput(messages, options) {
1998
2127
  const toolImages = [];
1999
2128
  for (const result of msg.content) {
2000
2129
  const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
2001
- const text = codexToolResultText(result.content);
2130
+ const text = toolResultText(result.content);
2002
2131
  input.push({
2003
2132
  type: "function_call_output",
2004
2133
  call_id: remapCodexId(callId, idMap),
@@ -2033,14 +2162,10 @@ function toCodexTools(tools) {
2033
2162
  type: "function",
2034
2163
  name: tool.name,
2035
2164
  description: tool.description,
2036
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
2165
+ parameters: resolveToolSchema(tool),
2037
2166
  strict: null
2038
2167
  }));
2039
2168
  }
2040
- function extractCodexRequestId(message) {
2041
- const match = message.match(/request ID ([a-z0-9-]{8,})/i);
2042
- return match?.[1];
2043
- }
2044
2169
  function parseCodexErrorBody(text) {
2045
2170
  if (!text) return {};
2046
2171
  try {
@@ -2048,7 +2173,7 @@ function parseCodexErrorBody(text) {
2048
2173
  const error = parsed.error;
2049
2174
  const detail = parsed.detail;
2050
2175
  const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2051
- const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
2176
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2052
2177
  const errorObj = error ?? parsed;
2053
2178
  return {
2054
2179
  ...message ? { message } : {},
@@ -2098,12 +2223,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
2098
2223
  "gemma-4-31b-it",
2099
2224
  "gemma-4-26b-a4b-it"
2100
2225
  ]);
2101
- function isJsonObject4(value) {
2102
- return value != null && typeof value === "object" && !Array.isArray(value);
2103
- }
2104
- function getEnvironment() {
2105
- return globalThis.process?.env;
2106
- }
2107
2226
  function getGoogleProject(options) {
2108
2227
  const env = getEnvironment();
2109
2228
  return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
@@ -2125,6 +2244,20 @@ ${formatUnsupportedModelMessage(model)}`;
2125
2244
  }
2126
2245
  return `Gemini API error (${status}): ${body}`;
2127
2246
  }
2247
+ function parseRetryDelaySeconds(body) {
2248
+ const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
2249
+ if (!match) return void 0;
2250
+ const seconds = Number(match[1]);
2251
+ return Number.isFinite(seconds) ? seconds : void 0;
2252
+ }
2253
+ function parseGeminiQuota(status, body) {
2254
+ if (status !== 429) return null;
2255
+ const lower = body.toLowerCase();
2256
+ if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
2257
+ const retryDelaySeconds = parseRetryDelaySeconds(body);
2258
+ const exhausted = retryDelaySeconds === void 0;
2259
+ return { exhausted, retryDelaySeconds };
2260
+ }
2128
2261
  function toSystemAndContents(messages) {
2129
2262
  let systemText = "";
2130
2263
  const contents = [];
@@ -2203,7 +2336,7 @@ function toGeminiTools(tools) {
2203
2336
  functionDeclarations: tools.map((tool) => ({
2204
2337
  name: tool.name,
2205
2338
  description: tool.description,
2206
- parameters: sanitizeSchema(tool.rawInputSchema ?? zodToJsonSchema(tool.parameters))
2339
+ parameters: sanitizeSchema(resolveToolSchema(tool))
2207
2340
  }))
2208
2341
  }
2209
2342
  ];
@@ -2214,7 +2347,7 @@ function sanitizeSchema(schema) {
2214
2347
  return clone;
2215
2348
  }
2216
2349
  function stripUnsupportedSchemaFields(value) {
2217
- if (!isJsonObject4(value)) {
2350
+ if (!isJsonObject(value)) {
2218
2351
  if (Array.isArray(value)) {
2219
2352
  for (const item of value) stripUnsupportedSchemaFields(item);
2220
2353
  }
@@ -2223,7 +2356,7 @@ function stripUnsupportedSchemaFields(value) {
2223
2356
  delete value.$schema;
2224
2357
  delete value.additionalProperties;
2225
2358
  for (const item of Object.values(value)) {
2226
- if (isJsonObject4(item) || Array.isArray(item)) {
2359
+ if (isJsonObject(item) || Array.isArray(item)) {
2227
2360
  stripUnsupportedSchemaFields(item);
2228
2361
  }
2229
2362
  }
@@ -2276,7 +2409,8 @@ function toThinkingConfig(model, level) {
2276
2409
  };
2277
2410
  }
2278
2411
  function buildGenerateRequest(options) {
2279
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2412
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2413
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2280
2414
  const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
2281
2415
  const tools = toGeminiTools(options.tools);
2282
2416
  const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
@@ -2338,54 +2472,11 @@ function normalizeGeminiStopReason(reason) {
2338
2472
  return "end_turn";
2339
2473
  }
2340
2474
  }
2341
- function parseSseEvents(buffer) {
2342
- const events = [];
2343
- let cursor = 0;
2344
- while (true) {
2345
- const next = buffer.indexOf("\n\n", cursor);
2346
- if (next === -1) break;
2347
- const raw = buffer.slice(cursor, next);
2348
- cursor = next + 2;
2349
- let eventName;
2350
- const dataLines = [];
2351
- for (const line of raw.split("\n")) {
2352
- if (line.startsWith("event:")) {
2353
- eventName = line.slice("event:".length).trim();
2354
- } else if (line.startsWith("data:")) {
2355
- dataLines.push(line.slice("data:".length).trimStart());
2356
- }
2357
- }
2358
- if (dataLines.length > 0) {
2359
- events.push({ event: eventName, data: dataLines.join("\n") });
2360
- }
2361
- }
2362
- return { events, remaining: buffer.slice(cursor) };
2363
- }
2364
2475
  async function* streamSse(response) {
2365
2476
  if (!response.body) return;
2366
- const reader = response.body.getReader();
2367
- const decoder = new TextDecoder();
2368
- let buffer = "";
2369
- try {
2370
- while (true) {
2371
- const { done, value } = await reader.read();
2372
- if (done) break;
2373
- buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
2374
- const parsed2 = parseSseEvents(buffer);
2375
- buffer = parsed2.remaining;
2376
- for (const event of parsed2.events) {
2377
- if (event.data === "[DONE]") continue;
2378
- yield JSON.parse(event.data);
2379
- }
2380
- }
2381
- buffer += decoder.decode().replace(/\r\n/g, "\n");
2382
- const parsed = parseSseEvents(buffer + "\n\n");
2383
- for (const event of parsed.events) {
2384
- if (event.data === "[DONE]") continue;
2385
- yield JSON.parse(event.data);
2386
- }
2387
- } finally {
2388
- reader.releaseLock();
2477
+ for await (const event of readSseStream(response.body)) {
2478
+ if (event.data === "[DONE]") continue;
2479
+ yield JSON.parse(event.data);
2389
2480
  }
2390
2481
  }
2391
2482
  function candidatesFromResponse(response) {
@@ -2408,7 +2499,7 @@ function readFunctionCallPart(part) {
2408
2499
  return {
2409
2500
  ...part.functionCall.id ? { id: part.functionCall.id } : {},
2410
2501
  name: part.functionCall.name,
2411
- args: isJsonObject4(part.functionCall.args) ? part.functionCall.args : {}
2502
+ args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
2412
2503
  };
2413
2504
  }
2414
2505
  function makeToolCallId(index, providerId) {
@@ -2447,8 +2538,17 @@ async function fetchCodeAssist(plan, options) {
2447
2538
  });
2448
2539
  if (!response.ok) {
2449
2540
  const text = await response.text().catch(() => "");
2450
- throw new ProviderError("gemini", formatErrorMessage(response.status, text, options.model), {
2451
- statusCode: response.status
2541
+ const quota = parseGeminiQuota(response.status, text);
2542
+ let message = formatErrorMessage(response.status, text, options.model);
2543
+ let resetsAt;
2544
+ if (quota?.exhausted) {
2545
+ message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
2546
+ } else if (quota?.retryDelaySeconds !== void 0) {
2547
+ resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
2548
+ }
2549
+ throw new ProviderError("gemini", message, {
2550
+ statusCode: response.status,
2551
+ ...resetsAt !== void 0 ? { resetsAt } : {}
2452
2552
  });
2453
2553
  }
2454
2554
  return response;
@@ -2839,6 +2939,7 @@ export {
2839
2939
  StreamResult,
2840
2940
  formatError,
2841
2941
  formatErrorForDisplay,
2942
+ isHardBillingMessage,
2842
2943
  isUsageLimitError,
2843
2944
  palsuAssistantMessage,
2844
2945
  palsuText,