@kenkaiiii/gg-ai 4.3.237 → 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.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;
449
+ }
450
+ return out;
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;
427
463
  }
428
464
  return out;
429
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"] : [],
@@ -1203,19 +1279,13 @@ function messageToResponse(message) {
1203
1279
  };
1204
1280
  }
1205
1281
  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");
1282
+ const status = readHeader(headers, "anthropic-ratelimit-unified-status");
1283
+ const resetRaw = readHeader(
1284
+ headers,
1285
+ "anthropic-ratelimit-unified-reset",
1286
+ "anthropic-ratelimit-unified-5h-reset",
1287
+ "anthropic-ratelimit-unified-7d-reset"
1288
+ );
1219
1289
  const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
1220
1290
  const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
1221
1291
  return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
@@ -1240,6 +1310,14 @@ function toError(err) {
1240
1310
  });
1241
1311
  }
1242
1312
  }
1313
+ if (isHardBillingMessage(message)) {
1314
+ const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
1315
+ return new ProviderError("anthropic", usageMessage, {
1316
+ statusCode: err.status,
1317
+ ...requestId ? { requestId } : {},
1318
+ cause: err
1319
+ });
1320
+ }
1243
1321
  return new ProviderError("anthropic", message, {
1244
1322
  statusCode: err.status,
1245
1323
  ...requestId ? { requestId } : {},
@@ -1272,19 +1350,30 @@ function fnv1aHash(value) {
1272
1350
  return (hash >>> 0).toString(16).padStart(8, "0");
1273
1351
  }
1274
1352
 
1275
- // src/providers/openai.ts
1276
- function isJsonObject2(value) {
1277
- return value != null && typeof value === "object" && !Array.isArray(value);
1353
+ // src/utils/env.ts
1354
+ function getEnvironment() {
1355
+ return globalThis.process?.env;
1278
1356
  }
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 {};
1357
+
1358
+ // src/providers/openai.ts
1359
+ function extractOpenAIUsage(usage) {
1360
+ let cacheRead = 0;
1361
+ const details = usage.prompt_tokens_details;
1362
+ if (details?.cached_tokens) {
1363
+ cacheRead = details.cached_tokens;
1364
+ }
1365
+ const usageAny = usage;
1366
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1367
+ cacheRead = usageAny.cached_tokens;
1287
1368
  }
1369
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1370
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1371
+ }
1372
+ return {
1373
+ inputTokens: usage.prompt_tokens - cacheRead,
1374
+ outputTokens: usage.completion_tokens,
1375
+ cacheRead
1376
+ };
1288
1377
  }
1289
1378
  function createClient2(options) {
1290
1379
  return new OpenAI({
@@ -1301,7 +1390,8 @@ async function* runStream2(options) {
1301
1390
  const useStreaming = options.streaming !== false;
1302
1391
  const client = createClient2(options);
1303
1392
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1304
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1393
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1394
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1305
1395
  const messages = toOpenAIMessages(downgradedMessages, {
1306
1396
  provider: options.provider,
1307
1397
  thinking: !!options.thinking,
@@ -1340,7 +1430,7 @@ async function* runStream2(options) {
1340
1430
  params.thinking = { type: "disabled" };
1341
1431
  }
1342
1432
  }
1343
- if (globalThis.process && globalThis.process.env?.GGAI_DUMP_REQUEST) {
1433
+ if (getEnvironment()?.GGAI_DUMP_REQUEST) {
1344
1434
  const fs = await import("fs");
1345
1435
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1346
1436
  const dumpPath = `/tmp/ggai-request-${ts}.json`;
@@ -1381,19 +1471,7 @@ async function* runStream2(options) {
1381
1471
  for await (const chunk of stream2) {
1382
1472
  const choice = chunk.choices?.[0];
1383
1473
  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;
1474
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1397
1475
  }
1398
1476
  if (!choice) continue;
1399
1477
  if (choice.finish_reason) {
@@ -1539,17 +1617,7 @@ function completionToResponse(completion) {
1539
1617
  let outputTokens = 0;
1540
1618
  let cacheRead = 0;
1541
1619
  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;
1620
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
1553
1621
  }
1554
1622
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1555
1623
  return {
@@ -1561,6 +1629,16 @@ function completionToResponse(completion) {
1561
1629
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1562
1630
  };
1563
1631
  }
1632
+ function classifyOpenAICompatLimit(args) {
1633
+ const { status, code, type, message } = args;
1634
+ const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
1635
+ const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
1636
+ if (isHard) return "hard";
1637
+ if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
1638
+ return "transient";
1639
+ }
1640
+ return null;
1641
+ }
1564
1642
  function toError2(err, provider = "openai") {
1565
1643
  if (err instanceof OpenAI.APIError) {
1566
1644
  const body = err.error;
@@ -1572,6 +1650,35 @@ function toError2(err, provider = "openai") {
1572
1650
  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
1651
  }
1574
1652
  const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1653
+ const code = typeof err.code === "string" ? err.code : void 0;
1654
+ const type = typeof err.type === "string" ? err.type : void 0;
1655
+ const limit = classifyOpenAICompatLimit({
1656
+ status: err.status,
1657
+ code,
1658
+ type,
1659
+ message: cleanMessage
1660
+ });
1661
+ if (limit === "hard") {
1662
+ const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
1663
+ return new ProviderError(provider, message, {
1664
+ statusCode: err.status,
1665
+ ...requestId ? { requestId } : {},
1666
+ ...hint ? { hint } : {},
1667
+ cause: err
1668
+ });
1669
+ }
1670
+ if (limit === "transient") {
1671
+ const retryAfterRaw = readHeader(err.headers, "retry-after");
1672
+ const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
1673
+ const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
1674
+ return new ProviderError(provider, cleanMessage, {
1675
+ statusCode: err.status,
1676
+ ...requestId ? { requestId } : {},
1677
+ ...hint ? { hint } : {},
1678
+ ...resetsAt ? { resetsAt } : {},
1679
+ cause: err
1680
+ });
1681
+ }
1575
1682
  return new ProviderError(provider, cleanMessage, {
1576
1683
  statusCode: err.status,
1577
1684
  ...requestId ? { requestId } : {},
@@ -1597,21 +1704,59 @@ function providerDiag(phase, data) {
1597
1704
  _diagFn?.(phase, data);
1598
1705
  }
1599
1706
 
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);
1707
+ // src/utils/sse.ts
1708
+ function parseSseBuffer(buffer) {
1709
+ const events = [];
1710
+ let cursor = 0;
1711
+ while (true) {
1712
+ const next = buffer.indexOf("\n\n", cursor);
1713
+ if (next === -1) break;
1714
+ const raw = buffer.slice(cursor, next);
1715
+ cursor = next + 2;
1716
+ let eventName;
1717
+ const dataLines = [];
1718
+ for (const line of raw.split("\n")) {
1719
+ if (line.startsWith("event:")) {
1720
+ eventName = line.slice("event:".length).trim();
1721
+ } else if (line.startsWith("data:")) {
1722
+ dataLines.push(line.slice("data:".length).trimStart());
1723
+ }
1724
+ }
1725
+ if (dataLines.length > 0) {
1726
+ events.push({ event: eventName, data: dataLines.join("\n") });
1727
+ }
1728
+ }
1729
+ return { events, remaining: buffer.slice(cursor) };
1604
1730
  }
1605
- function parseToolArguments2(argsJson) {
1606
- if (!argsJson) return {};
1731
+ async function* readSseStream(body) {
1732
+ const reader = body.getReader();
1733
+ const decoder = new TextDecoder();
1734
+ let buffer = "";
1607
1735
  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 {};
1736
+ while (true) {
1737
+ const { done, value } = await reader.read();
1738
+ if (done) break;
1739
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
1740
+ const parsed2 = parseSseBuffer(buffer);
1741
+ buffer = parsed2.remaining;
1742
+ yield* parsed2.events;
1743
+ }
1744
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
1745
+ const parsed = parseSseBuffer(buffer + "\n\n");
1746
+ yield* parsed.events;
1747
+ } finally {
1748
+ reader.releaseLock();
1613
1749
  }
1614
1750
  }
1751
+
1752
+ // src/utils/request-id.ts
1753
+ function extractRequestIdFromMessage(message) {
1754
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1755
+ return match?.[1];
1756
+ }
1757
+
1758
+ // src/providers/openai-codex.ts
1759
+ var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1615
1760
  function outputTextKey(itemId, contentIndex) {
1616
1761
  return `${itemId ?? ""}:${contentIndex ?? 0}`;
1617
1762
  }
@@ -1624,7 +1769,8 @@ function streamOpenAICodex(options) {
1624
1769
  async function* runStream3(options) {
1625
1770
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1626
1771
  const url = `${baseUrl}/codex/responses`;
1627
- const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1772
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1773
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1628
1774
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
1629
1775
  const body = {
1630
1776
  model: options.model,
@@ -1676,7 +1822,7 @@ async function* runStream3(options) {
1676
1822
  const text = await response.text().catch(() => "");
1677
1823
  const parsed = parseCodexErrorBody(text);
1678
1824
  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;
1825
+ const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
1680
1826
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
1681
1827
  if (usageLimit) throw usageLimit;
1682
1828
  let hint;
@@ -1720,7 +1866,7 @@ async function* runStream3(options) {
1720
1866
  const nested = event.error ?? void 0;
1721
1867
  const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1722
1868
  const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1723
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1869
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1724
1870
  const usageLimit = codexUsageLimitError(
1725
1871
  nested ?? event,
1726
1872
  void 0,
@@ -1735,7 +1881,7 @@ async function* runStream3(options) {
1735
1881
  if (type === "response.failed") {
1736
1882
  const nested = event.error;
1737
1883
  const message = nested?.message ?? "Codex response failed.";
1738
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1884
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1739
1885
  throw new ProviderError("openai", message, {
1740
1886
  ...requestId != null ? { requestId } : {}
1741
1887
  });
@@ -1854,7 +2000,7 @@ async function* runStream3(options) {
1854
2000
  const id = `${callId}|${itemId}`;
1855
2001
  const tc = toolCalls.get(id);
1856
2002
  if (tc) {
1857
- const args = parseToolArguments2(tc.argsJson);
2003
+ const args = parseToolArguments(tc.argsJson);
1858
2004
  yield {
1859
2005
  type: "toolcall_done",
1860
2006
  id: tc.id,
@@ -1878,7 +2024,7 @@ async function* runStream3(options) {
1878
2024
  contentParts.push({ type: "text", text: textAccum });
1879
2025
  }
1880
2026
  for (const [, tc] of toolCalls) {
1881
- const args = parseToolArguments2(tc.argsJson);
2027
+ const args = parseToolArguments(tc.argsJson);
1882
2028
  const toolCall = {
1883
2029
  type: "tool_call",
1884
2030
  id: tc.id,
@@ -1901,33 +2047,13 @@ async function* runStream3(options) {
1901
2047
  return streamResponse;
1902
2048
  }
1903
2049
  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
- }
2050
+ for await (const event of readSseStream(body)) {
2051
+ const data = event.data.trim();
2052
+ if (!data || data === "[DONE]") continue;
2053
+ try {
2054
+ yield JSON.parse(data);
2055
+ } catch {
1928
2056
  }
1929
- } finally {
1930
- reader.releaseLock();
1931
2057
  }
1932
2058
  }
1933
2059
  function remapCodexId(id, idMap) {
@@ -1938,10 +2064,6 @@ function remapCodexId(id, idMap) {
1938
2064
  idMap.set(id, mapped);
1939
2065
  return mapped;
1940
2066
  }
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
2067
  function toCodexInput(messages, options) {
1946
2068
  let system;
1947
2069
  const input = [];
@@ -1998,7 +2120,7 @@ function toCodexInput(messages, options) {
1998
2120
  const toolImages = [];
1999
2121
  for (const result of msg.content) {
2000
2122
  const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
2001
- const text = codexToolResultText(result.content);
2123
+ const text = toolResultText(result.content);
2002
2124
  input.push({
2003
2125
  type: "function_call_output",
2004
2126
  call_id: remapCodexId(callId, idMap),
@@ -2033,14 +2155,10 @@ function toCodexTools(tools) {
2033
2155
  type: "function",
2034
2156
  name: tool.name,
2035
2157
  description: tool.description,
2036
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
2158
+ parameters: resolveToolSchema(tool),
2037
2159
  strict: null
2038
2160
  }));
2039
2161
  }
2040
- function extractCodexRequestId(message) {
2041
- const match = message.match(/request ID ([a-z0-9-]{8,})/i);
2042
- return match?.[1];
2043
- }
2044
2162
  function parseCodexErrorBody(text) {
2045
2163
  if (!text) return {};
2046
2164
  try {
@@ -2048,7 +2166,7 @@ function parseCodexErrorBody(text) {
2048
2166
  const error = parsed.error;
2049
2167
  const detail = parsed.detail;
2050
2168
  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);
2169
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2052
2170
  const errorObj = error ?? parsed;
2053
2171
  return {
2054
2172
  ...message ? { message } : {},
@@ -2098,12 +2216,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
2098
2216
  "gemma-4-31b-it",
2099
2217
  "gemma-4-26b-a4b-it"
2100
2218
  ]);
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
2219
  function getGoogleProject(options) {
2108
2220
  const env = getEnvironment();
2109
2221
  return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
@@ -2125,6 +2237,20 @@ ${formatUnsupportedModelMessage(model)}`;
2125
2237
  }
2126
2238
  return `Gemini API error (${status}): ${body}`;
2127
2239
  }
2240
+ function parseRetryDelaySeconds(body) {
2241
+ const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
2242
+ if (!match) return void 0;
2243
+ const seconds = Number(match[1]);
2244
+ return Number.isFinite(seconds) ? seconds : void 0;
2245
+ }
2246
+ function parseGeminiQuota(status, body) {
2247
+ if (status !== 429) return null;
2248
+ const lower = body.toLowerCase();
2249
+ if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
2250
+ const retryDelaySeconds = parseRetryDelaySeconds(body);
2251
+ const exhausted = retryDelaySeconds === void 0;
2252
+ return { exhausted, retryDelaySeconds };
2253
+ }
2128
2254
  function toSystemAndContents(messages) {
2129
2255
  let systemText = "";
2130
2256
  const contents = [];
@@ -2203,7 +2329,7 @@ function toGeminiTools(tools) {
2203
2329
  functionDeclarations: tools.map((tool) => ({
2204
2330
  name: tool.name,
2205
2331
  description: tool.description,
2206
- parameters: sanitizeSchema(tool.rawInputSchema ?? zodToJsonSchema(tool.parameters))
2332
+ parameters: sanitizeSchema(resolveToolSchema(tool))
2207
2333
  }))
2208
2334
  }
2209
2335
  ];
@@ -2214,7 +2340,7 @@ function sanitizeSchema(schema) {
2214
2340
  return clone;
2215
2341
  }
2216
2342
  function stripUnsupportedSchemaFields(value) {
2217
- if (!isJsonObject4(value)) {
2343
+ if (!isJsonObject(value)) {
2218
2344
  if (Array.isArray(value)) {
2219
2345
  for (const item of value) stripUnsupportedSchemaFields(item);
2220
2346
  }
@@ -2223,7 +2349,7 @@ function stripUnsupportedSchemaFields(value) {
2223
2349
  delete value.$schema;
2224
2350
  delete value.additionalProperties;
2225
2351
  for (const item of Object.values(value)) {
2226
- if (isJsonObject4(item) || Array.isArray(item)) {
2352
+ if (isJsonObject(item) || Array.isArray(item)) {
2227
2353
  stripUnsupportedSchemaFields(item);
2228
2354
  }
2229
2355
  }
@@ -2276,7 +2402,8 @@ function toThinkingConfig(model, level) {
2276
2402
  };
2277
2403
  }
2278
2404
  function buildGenerateRequest(options) {
2279
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2405
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2406
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2280
2407
  const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
2281
2408
  const tools = toGeminiTools(options.tools);
2282
2409
  const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
@@ -2338,54 +2465,11 @@ function normalizeGeminiStopReason(reason) {
2338
2465
  return "end_turn";
2339
2466
  }
2340
2467
  }
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
2468
  async function* streamSse(response) {
2365
2469
  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();
2470
+ for await (const event of readSseStream(response.body)) {
2471
+ if (event.data === "[DONE]") continue;
2472
+ yield JSON.parse(event.data);
2389
2473
  }
2390
2474
  }
2391
2475
  function candidatesFromResponse(response) {
@@ -2408,7 +2492,7 @@ function readFunctionCallPart(part) {
2408
2492
  return {
2409
2493
  ...part.functionCall.id ? { id: part.functionCall.id } : {},
2410
2494
  name: part.functionCall.name,
2411
- args: isJsonObject4(part.functionCall.args) ? part.functionCall.args : {}
2495
+ args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
2412
2496
  };
2413
2497
  }
2414
2498
  function makeToolCallId(index, providerId) {
@@ -2447,8 +2531,17 @@ async function fetchCodeAssist(plan, options) {
2447
2531
  });
2448
2532
  if (!response.ok) {
2449
2533
  const text = await response.text().catch(() => "");
2450
- throw new ProviderError("gemini", formatErrorMessage(response.status, text, options.model), {
2451
- statusCode: response.status
2534
+ const quota = parseGeminiQuota(response.status, text);
2535
+ let message = formatErrorMessage(response.status, text, options.model);
2536
+ let resetsAt;
2537
+ if (quota?.exhausted) {
2538
+ message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
2539
+ } else if (quota?.retryDelaySeconds !== void 0) {
2540
+ resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
2541
+ }
2542
+ throw new ProviderError("gemini", message, {
2543
+ statusCode: response.status,
2544
+ ...resetsAt !== void 0 ? { resetsAt } : {}
2452
2545
  });
2453
2546
  }
2454
2547
  return response;
@@ -2839,6 +2932,7 @@ export {
2839
2932
  StreamResult,
2840
2933
  formatError,
2841
2934
  formatErrorForDisplay,
2935
+ isHardBillingMessage,
2842
2936
  isUsageLimitError,
2843
2937
  palsuAssistantMessage,
2844
2938
  palsuText,