@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.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;
479
502
  }
480
503
  return out;
481
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;
516
+ }
517
+ return out;
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"] : [],
@@ -1074,11 +1151,18 @@ async function* runStream(options) {
1074
1151
  args: tc.args
1075
1152
  };
1076
1153
  } else if (accum.type === "server_tool_use") {
1154
+ let input = accum.input;
1155
+ if (accum.argsJson) {
1156
+ try {
1157
+ input = JSON.parse(accum.argsJson);
1158
+ } catch {
1159
+ }
1160
+ }
1077
1161
  const stc = {
1078
1162
  type: "server_tool_call",
1079
1163
  id: accum.toolId,
1080
1164
  name: accum.toolName,
1081
- input: accum.input
1165
+ input
1082
1166
  };
1083
1167
  contentParts.push(stc);
1084
1168
  yield {
@@ -1255,19 +1339,13 @@ function messageToResponse(message) {
1255
1339
  };
1256
1340
  }
1257
1341
  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");
1342
+ const status = readHeader(headers, "anthropic-ratelimit-unified-status");
1343
+ const resetRaw = readHeader(
1344
+ headers,
1345
+ "anthropic-ratelimit-unified-reset",
1346
+ "anthropic-ratelimit-unified-5h-reset",
1347
+ "anthropic-ratelimit-unified-7d-reset"
1348
+ );
1271
1349
  const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
1272
1350
  const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
1273
1351
  return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
@@ -1292,6 +1370,14 @@ function toError(err) {
1292
1370
  });
1293
1371
  }
1294
1372
  }
1373
+ if (isHardBillingMessage(message)) {
1374
+ const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
1375
+ return new ProviderError("anthropic", usageMessage, {
1376
+ statusCode: err.status,
1377
+ ...requestId ? { requestId } : {},
1378
+ cause: err
1379
+ });
1380
+ }
1295
1381
  return new ProviderError("anthropic", message, {
1296
1382
  statusCode: err.status,
1297
1383
  ...requestId ? { requestId } : {},
@@ -1324,19 +1410,30 @@ function fnv1aHash(value) {
1324
1410
  return (hash >>> 0).toString(16).padStart(8, "0");
1325
1411
  }
1326
1412
 
1327
- // src/providers/openai.ts
1328
- function isJsonObject2(value) {
1329
- return value != null && typeof value === "object" && !Array.isArray(value);
1413
+ // src/utils/env.ts
1414
+ function getEnvironment() {
1415
+ return globalThis.process?.env;
1330
1416
  }
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 {};
1417
+
1418
+ // src/providers/openai.ts
1419
+ function extractOpenAIUsage(usage) {
1420
+ let cacheRead = 0;
1421
+ const details = usage.prompt_tokens_details;
1422
+ if (details?.cached_tokens) {
1423
+ cacheRead = details.cached_tokens;
1424
+ }
1425
+ const usageAny = usage;
1426
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1427
+ cacheRead = usageAny.cached_tokens;
1339
1428
  }
1429
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1430
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1431
+ }
1432
+ return {
1433
+ inputTokens: usage.prompt_tokens - cacheRead,
1434
+ outputTokens: usage.completion_tokens,
1435
+ cacheRead
1436
+ };
1340
1437
  }
1341
1438
  function createClient2(options) {
1342
1439
  return new import_openai.default({
@@ -1353,7 +1450,8 @@ async function* runStream2(options) {
1353
1450
  const useStreaming = options.streaming !== false;
1354
1451
  const client = createClient2(options);
1355
1452
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1356
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1453
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1454
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1357
1455
  const messages = toOpenAIMessages(downgradedMessages, {
1358
1456
  provider: options.provider,
1359
1457
  thinking: !!options.thinking,
@@ -1392,7 +1490,7 @@ async function* runStream2(options) {
1392
1490
  params.thinking = { type: "disabled" };
1393
1491
  }
1394
1492
  }
1395
- if (globalThis.process && globalThis.process.env?.GGAI_DUMP_REQUEST) {
1493
+ if (getEnvironment()?.GGAI_DUMP_REQUEST) {
1396
1494
  const fs = await import("fs");
1397
1495
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1398
1496
  const dumpPath = `/tmp/ggai-request-${ts}.json`;
@@ -1433,19 +1531,7 @@ async function* runStream2(options) {
1433
1531
  for await (const chunk of stream2) {
1434
1532
  const choice = chunk.choices?.[0];
1435
1533
  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;
1534
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1449
1535
  }
1450
1536
  if (!choice) continue;
1451
1537
  if (choice.finish_reason) {
@@ -1591,17 +1677,7 @@ function completionToResponse(completion) {
1591
1677
  let outputTokens = 0;
1592
1678
  let cacheRead = 0;
1593
1679
  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;
1680
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
1605
1681
  }
1606
1682
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1607
1683
  return {
@@ -1613,6 +1689,16 @@ function completionToResponse(completion) {
1613
1689
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1614
1690
  };
1615
1691
  }
1692
+ function classifyOpenAICompatLimit(args) {
1693
+ const { status, code, type, message } = args;
1694
+ const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
1695
+ const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
1696
+ if (isHard) return "hard";
1697
+ if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
1698
+ return "transient";
1699
+ }
1700
+ return null;
1701
+ }
1616
1702
  function toError2(err, provider = "openai") {
1617
1703
  if (err instanceof import_openai.default.APIError) {
1618
1704
  const body = err.error;
@@ -1624,6 +1710,35 @@ function toError2(err, provider = "openai") {
1624
1710
  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
1711
  }
1626
1712
  const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1713
+ const code = typeof err.code === "string" ? err.code : void 0;
1714
+ const type = typeof err.type === "string" ? err.type : void 0;
1715
+ const limit = classifyOpenAICompatLimit({
1716
+ status: err.status,
1717
+ code,
1718
+ type,
1719
+ message: cleanMessage
1720
+ });
1721
+ if (limit === "hard") {
1722
+ const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
1723
+ return new ProviderError(provider, message, {
1724
+ statusCode: err.status,
1725
+ ...requestId ? { requestId } : {},
1726
+ ...hint ? { hint } : {},
1727
+ cause: err
1728
+ });
1729
+ }
1730
+ if (limit === "transient") {
1731
+ const retryAfterRaw = readHeader(err.headers, "retry-after");
1732
+ const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
1733
+ const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
1734
+ return new ProviderError(provider, cleanMessage, {
1735
+ statusCode: err.status,
1736
+ ...requestId ? { requestId } : {},
1737
+ ...hint ? { hint } : {},
1738
+ ...resetsAt ? { resetsAt } : {},
1739
+ cause: err
1740
+ });
1741
+ }
1627
1742
  return new ProviderError(provider, cleanMessage, {
1628
1743
  statusCode: err.status,
1629
1744
  ...requestId ? { requestId } : {},
@@ -1649,21 +1764,59 @@ function providerDiag(phase, data) {
1649
1764
  _diagFn?.(phase, data);
1650
1765
  }
1651
1766
 
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);
1767
+ // src/utils/sse.ts
1768
+ function parseSseBuffer(buffer) {
1769
+ const events = [];
1770
+ let cursor = 0;
1771
+ while (true) {
1772
+ const next = buffer.indexOf("\n\n", cursor);
1773
+ if (next === -1) break;
1774
+ const raw = buffer.slice(cursor, next);
1775
+ cursor = next + 2;
1776
+ let eventName;
1777
+ const dataLines = [];
1778
+ for (const line of raw.split("\n")) {
1779
+ if (line.startsWith("event:")) {
1780
+ eventName = line.slice("event:".length).trim();
1781
+ } else if (line.startsWith("data:")) {
1782
+ dataLines.push(line.slice("data:".length).trimStart());
1783
+ }
1784
+ }
1785
+ if (dataLines.length > 0) {
1786
+ events.push({ event: eventName, data: dataLines.join("\n") });
1787
+ }
1788
+ }
1789
+ return { events, remaining: buffer.slice(cursor) };
1656
1790
  }
1657
- function parseToolArguments2(argsJson) {
1658
- if (!argsJson) return {};
1791
+ async function* readSseStream(body) {
1792
+ const reader = body.getReader();
1793
+ const decoder = new TextDecoder();
1794
+ let buffer = "";
1659
1795
  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 {};
1796
+ while (true) {
1797
+ const { done, value } = await reader.read();
1798
+ if (done) break;
1799
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
1800
+ const parsed2 = parseSseBuffer(buffer);
1801
+ buffer = parsed2.remaining;
1802
+ yield* parsed2.events;
1803
+ }
1804
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
1805
+ const parsed = parseSseBuffer(buffer + "\n\n");
1806
+ yield* parsed.events;
1807
+ } finally {
1808
+ reader.releaseLock();
1665
1809
  }
1666
1810
  }
1811
+
1812
+ // src/utils/request-id.ts
1813
+ function extractRequestIdFromMessage(message) {
1814
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1815
+ return match?.[1];
1816
+ }
1817
+
1818
+ // src/providers/openai-codex.ts
1819
+ var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1667
1820
  function outputTextKey(itemId, contentIndex) {
1668
1821
  return `${itemId ?? ""}:${contentIndex ?? 0}`;
1669
1822
  }
@@ -1676,7 +1829,8 @@ function streamOpenAICodex(options) {
1676
1829
  async function* runStream3(options) {
1677
1830
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1678
1831
  const url = `${baseUrl}/codex/responses`;
1679
- const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1832
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1833
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1680
1834
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
1681
1835
  const body = {
1682
1836
  model: options.model,
@@ -1728,7 +1882,7 @@ async function* runStream3(options) {
1728
1882
  const text = await response.text().catch(() => "");
1729
1883
  const parsed = parseCodexErrorBody(text);
1730
1884
  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;
1885
+ const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
1732
1886
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
1733
1887
  if (usageLimit) throw usageLimit;
1734
1888
  let hint;
@@ -1772,7 +1926,7 @@ async function* runStream3(options) {
1772
1926
  const nested = event.error ?? void 0;
1773
1927
  const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1774
1928
  const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1775
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1929
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1776
1930
  const usageLimit = codexUsageLimitError(
1777
1931
  nested ?? event,
1778
1932
  void 0,
@@ -1787,7 +1941,7 @@ async function* runStream3(options) {
1787
1941
  if (type === "response.failed") {
1788
1942
  const nested = event.error;
1789
1943
  const message = nested?.message ?? "Codex response failed.";
1790
- const requestId = extractCodexRequestId(message) ?? event.request_id;
1944
+ const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
1791
1945
  throw new ProviderError("openai", message, {
1792
1946
  ...requestId != null ? { requestId } : {}
1793
1947
  });
@@ -1906,7 +2060,7 @@ async function* runStream3(options) {
1906
2060
  const id = `${callId}|${itemId}`;
1907
2061
  const tc = toolCalls.get(id);
1908
2062
  if (tc) {
1909
- const args = parseToolArguments2(tc.argsJson);
2063
+ const args = parseToolArguments(tc.argsJson);
1910
2064
  yield {
1911
2065
  type: "toolcall_done",
1912
2066
  id: tc.id,
@@ -1930,7 +2084,7 @@ async function* runStream3(options) {
1930
2084
  contentParts.push({ type: "text", text: textAccum });
1931
2085
  }
1932
2086
  for (const [, tc] of toolCalls) {
1933
- const args = parseToolArguments2(tc.argsJson);
2087
+ const args = parseToolArguments(tc.argsJson);
1934
2088
  const toolCall = {
1935
2089
  type: "tool_call",
1936
2090
  id: tc.id,
@@ -1953,33 +2107,13 @@ async function* runStream3(options) {
1953
2107
  return streamResponse;
1954
2108
  }
1955
2109
  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
- }
2110
+ for await (const event of readSseStream(body)) {
2111
+ const data = event.data.trim();
2112
+ if (!data || data === "[DONE]") continue;
2113
+ try {
2114
+ yield JSON.parse(data);
2115
+ } catch {
1980
2116
  }
1981
- } finally {
1982
- reader.releaseLock();
1983
2117
  }
1984
2118
  }
1985
2119
  function remapCodexId(id, idMap) {
@@ -1990,10 +2124,6 @@ function remapCodexId(id, idMap) {
1990
2124
  idMap.set(id, mapped);
1991
2125
  return mapped;
1992
2126
  }
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
2127
  function toCodexInput(messages, options) {
1998
2128
  let system;
1999
2129
  const input = [];
@@ -2050,7 +2180,7 @@ function toCodexInput(messages, options) {
2050
2180
  const toolImages = [];
2051
2181
  for (const result of msg.content) {
2052
2182
  const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
2053
- const text = codexToolResultText(result.content);
2183
+ const text = toolResultText(result.content);
2054
2184
  input.push({
2055
2185
  type: "function_call_output",
2056
2186
  call_id: remapCodexId(callId, idMap),
@@ -2085,14 +2215,10 @@ function toCodexTools(tools) {
2085
2215
  type: "function",
2086
2216
  name: tool.name,
2087
2217
  description: tool.description,
2088
- parameters: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
2218
+ parameters: resolveToolSchema(tool),
2089
2219
  strict: null
2090
2220
  }));
2091
2221
  }
2092
- function extractCodexRequestId(message) {
2093
- const match = message.match(/request ID ([a-z0-9-]{8,})/i);
2094
- return match?.[1];
2095
- }
2096
2222
  function parseCodexErrorBody(text) {
2097
2223
  if (!text) return {};
2098
2224
  try {
@@ -2100,7 +2226,7 @@ function parseCodexErrorBody(text) {
2100
2226
  const error = parsed.error;
2101
2227
  const detail = parsed.detail;
2102
2228
  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);
2229
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2104
2230
  const errorObj = error ?? parsed;
2105
2231
  return {
2106
2232
  ...message ? { message } : {},
@@ -2150,12 +2276,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
2150
2276
  "gemma-4-31b-it",
2151
2277
  "gemma-4-26b-a4b-it"
2152
2278
  ]);
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
2279
  function getGoogleProject(options) {
2160
2280
  const env = getEnvironment();
2161
2281
  return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
@@ -2177,6 +2297,20 @@ ${formatUnsupportedModelMessage(model)}`;
2177
2297
  }
2178
2298
  return `Gemini API error (${status}): ${body}`;
2179
2299
  }
2300
+ function parseRetryDelaySeconds(body) {
2301
+ const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
2302
+ if (!match) return void 0;
2303
+ const seconds = Number(match[1]);
2304
+ return Number.isFinite(seconds) ? seconds : void 0;
2305
+ }
2306
+ function parseGeminiQuota(status, body) {
2307
+ if (status !== 429) return null;
2308
+ const lower = body.toLowerCase();
2309
+ if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
2310
+ const retryDelaySeconds = parseRetryDelaySeconds(body);
2311
+ const exhausted = retryDelaySeconds === void 0;
2312
+ return { exhausted, retryDelaySeconds };
2313
+ }
2180
2314
  function toSystemAndContents(messages) {
2181
2315
  let systemText = "";
2182
2316
  const contents = [];
@@ -2255,7 +2389,7 @@ function toGeminiTools(tools) {
2255
2389
  functionDeclarations: tools.map((tool) => ({
2256
2390
  name: tool.name,
2257
2391
  description: tool.description,
2258
- parameters: sanitizeSchema(tool.rawInputSchema ?? zodToJsonSchema(tool.parameters))
2392
+ parameters: sanitizeSchema(resolveToolSchema(tool))
2259
2393
  }))
2260
2394
  }
2261
2395
  ];
@@ -2266,7 +2400,7 @@ function sanitizeSchema(schema) {
2266
2400
  return clone;
2267
2401
  }
2268
2402
  function stripUnsupportedSchemaFields(value) {
2269
- if (!isJsonObject4(value)) {
2403
+ if (!isJsonObject(value)) {
2270
2404
  if (Array.isArray(value)) {
2271
2405
  for (const item of value) stripUnsupportedSchemaFields(item);
2272
2406
  }
@@ -2275,7 +2409,7 @@ function stripUnsupportedSchemaFields(value) {
2275
2409
  delete value.$schema;
2276
2410
  delete value.additionalProperties;
2277
2411
  for (const item of Object.values(value)) {
2278
- if (isJsonObject4(item) || Array.isArray(item)) {
2412
+ if (isJsonObject(item) || Array.isArray(item)) {
2279
2413
  stripUnsupportedSchemaFields(item);
2280
2414
  }
2281
2415
  }
@@ -2328,7 +2462,8 @@ function toThinkingConfig(model, level) {
2328
2462
  };
2329
2463
  }
2330
2464
  function buildGenerateRequest(options) {
2331
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2465
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2466
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2332
2467
  const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
2333
2468
  const tools = toGeminiTools(options.tools);
2334
2469
  const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
@@ -2390,54 +2525,11 @@ function normalizeGeminiStopReason(reason) {
2390
2525
  return "end_turn";
2391
2526
  }
2392
2527
  }
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
2528
  async function* streamSse(response) {
2417
2529
  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();
2530
+ for await (const event of readSseStream(response.body)) {
2531
+ if (event.data === "[DONE]") continue;
2532
+ yield JSON.parse(event.data);
2441
2533
  }
2442
2534
  }
2443
2535
  function candidatesFromResponse(response) {
@@ -2460,7 +2552,7 @@ function readFunctionCallPart(part) {
2460
2552
  return {
2461
2553
  ...part.functionCall.id ? { id: part.functionCall.id } : {},
2462
2554
  name: part.functionCall.name,
2463
- args: isJsonObject4(part.functionCall.args) ? part.functionCall.args : {}
2555
+ args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
2464
2556
  };
2465
2557
  }
2466
2558
  function makeToolCallId(index, providerId) {
@@ -2499,8 +2591,17 @@ async function fetchCodeAssist(plan, options) {
2499
2591
  });
2500
2592
  if (!response.ok) {
2501
2593
  const text = await response.text().catch(() => "");
2502
- throw new ProviderError("gemini", formatErrorMessage(response.status, text, options.model), {
2503
- statusCode: response.status
2594
+ const quota = parseGeminiQuota(response.status, text);
2595
+ let message = formatErrorMessage(response.status, text, options.model);
2596
+ let resetsAt;
2597
+ if (quota?.exhausted) {
2598
+ message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
2599
+ } else if (quota?.retryDelaySeconds !== void 0) {
2600
+ resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
2601
+ }
2602
+ throw new ProviderError("gemini", message, {
2603
+ statusCode: response.status,
2604
+ ...resetsAt !== void 0 ? { resetsAt } : {}
2504
2605
  });
2505
2606
  }
2506
2607
  return response;
@@ -2892,6 +2993,7 @@ function registerPalsuProvider(config) {
2892
2993
  StreamResult,
2893
2994
  formatError,
2894
2995
  formatErrorForDisplay,
2996
+ isHardBillingMessage,
2895
2997
  isUsageLimitError,
2896
2998
  palsuAssistantMessage,
2897
2999
  palsuText,