@prestyj/ai 4.3.239 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -77,6 +78,12 @@ var EZCoderAIError = class extends Error {
77
78
  this.hint = options?.hint;
78
79
  }
79
80
  };
81
+ var VideoUnsupportedError = class extends EZCoderAIError {
82
+ constructor() {
83
+ super("This model can't analyze video.", { source: "capability" });
84
+ this.name = "VideoUnsupportedError";
85
+ }
86
+ };
80
87
  var ProviderError = class extends EZCoderAIError {
81
88
  provider;
82
89
  statusCode;
@@ -117,6 +124,10 @@ function isUsageLimitError(err) {
117
124
  if (!(err instanceof Error)) return false;
118
125
  return /usage limit reached/i.test(err.message);
119
126
  }
127
+ function isHardBillingMessage(message) {
128
+ const lower = message.toLowerCase();
129
+ 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");
130
+ }
120
131
  function formatResetTime(resetsAt) {
121
132
  const when = new Date(resetsAt * 1e3);
122
133
  const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
@@ -188,6 +199,14 @@ function finaliseBySource(source, message, requestId, hint) {
188
199
  guidance: hint ?? providerGuidance(void 0, message, void 0),
189
200
  ...requestId ? { requestId } : {}
190
201
  };
202
+ case "capability":
203
+ return {
204
+ headline: message,
205
+ source,
206
+ message: "",
207
+ guidance: hint ?? "Only Kimi, Gemini, MiniMax, and MiMo-V2.5 can analyze video. Switch with /model.",
208
+ ...requestId ? { requestId } : {}
209
+ };
191
210
  case "ezcoder":
192
211
  return {
193
212
  headline: "ezcoder hit an unexpected error.",
@@ -462,8 +481,8 @@ function toAnthropicAssistantPart(part, idMap) {
462
481
  if (part.type === "raw") return part.data;
463
482
  return null;
464
483
  }
465
- function toAnthropicAssistantContent(content, isLatest, idMap) {
466
- if (!isLatest) {
484
+ function toAnthropicAssistantContent(content, preserveThinking, idMap) {
485
+ if (!preserveThinking) {
467
486
  return content.filter((part) => {
468
487
  if (part.type === "thinking" || isRawThinking(part)) return false;
469
488
  if (part.type === "text" && !part.text) return false;
@@ -482,6 +501,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
482
501
  }
483
502
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
484
503
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
504
+ var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
485
505
  function stripImages(content, placeholder) {
486
506
  const out = [];
487
507
  let lastWasPlaceholder = false;
@@ -492,10 +512,33 @@ function stripImages(content, placeholder) {
492
512
  continue;
493
513
  }
494
514
  out.push(block);
495
- lastWasPlaceholder = block.text === placeholder;
515
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
496
516
  }
497
517
  return out;
498
518
  }
519
+ function stripVideos(content, placeholder) {
520
+ const out = [];
521
+ let lastWasPlaceholder = false;
522
+ for (const block of content) {
523
+ if (block.type === "video") {
524
+ if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
525
+ lastWasPlaceholder = true;
526
+ continue;
527
+ }
528
+ out.push(block);
529
+ lastWasPlaceholder = block.type === "text" && block.text === placeholder;
530
+ }
531
+ return out;
532
+ }
533
+ function downgradeUnsupportedVideos(messages, supportsVideo) {
534
+ if (supportsVideo === true) return messages;
535
+ return messages.map((msg) => {
536
+ if (msg.role === "user" && Array.isArray(msg.content)) {
537
+ return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
538
+ }
539
+ return msg;
540
+ });
541
+ }
499
542
  function downgradeUnsupportedImages(messages, supportsImages) {
500
543
  if (supportsImages !== false) return messages;
501
544
  return messages.map((msg) => {
@@ -524,6 +567,10 @@ function toolResultImages(content) {
524
567
  if (typeof content === "string") return [];
525
568
  return content.filter((b) => b.type === "image");
526
569
  }
570
+ function toolResultVideos(content) {
571
+ if (typeof content === "string") return [];
572
+ return content.filter((b) => b.type === "video");
573
+ }
527
574
  function toAnthropicCacheControl(retention, baseUrl) {
528
575
  const resolved = retention ?? "short";
529
576
  if (resolved === "none") return void 0;
@@ -534,6 +581,12 @@ function toAnthropicToolResultContent(content) {
534
581
  if (typeof content === "string") return content;
535
582
  return content.map((block) => {
536
583
  if (block.type === "text") return { type: "text", text: block.text };
584
+ if (block.type === "video") {
585
+ return {
586
+ type: "video",
587
+ source: { type: "base64", media_type: block.mediaType, data: block.data }
588
+ };
589
+ }
537
590
  return {
538
591
  type: "image",
539
592
  source: {
@@ -556,8 +609,8 @@ function toAnthropicMessages(messages, cacheControl) {
556
609
  let systemText;
557
610
  const out = [];
558
611
  const idMap = /* @__PURE__ */ new Map();
559
- const lastAssistantIdx = messages.reduce(
560
- (last, m, i) => m.role === "assistant" ? i : last,
612
+ const trajectoryStartIdx = messages.reduce(
613
+ (last, m, i) => m.role === "user" ? i : last,
561
614
  -1
562
615
  );
563
616
  let msgIdx = -1;
@@ -572,6 +625,16 @@ function toAnthropicMessages(messages, cacheControl) {
572
625
  role: "user",
573
626
  content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
574
627
  if (part.type === "text") return { type: "text", text: part.text };
628
+ if (part.type === "video") {
629
+ return {
630
+ type: "video",
631
+ source: {
632
+ type: "base64",
633
+ media_type: part.mediaType,
634
+ data: part.data
635
+ }
636
+ };
637
+ }
575
638
  return {
576
639
  type: "image",
577
640
  source: {
@@ -585,7 +648,7 @@ function toAnthropicMessages(messages, cacheControl) {
585
648
  continue;
586
649
  }
587
650
  if (msg.role === "assistant") {
588
- const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx === lastAssistantIdx, idMap);
651
+ const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx > trajectoryStartIdx, idMap);
589
652
  if (Array.isArray(content) && content.length === 0) continue;
590
653
  out.push({ role: "assistant", content });
591
654
  continue;
@@ -593,6 +656,8 @@ function toAnthropicMessages(messages, cacheControl) {
593
656
  if (msg.role === "tool") {
594
657
  out.push({
595
658
  role: "user",
659
+ // Cast covers the video block (used by the Anthropic-compatible MiniMax
660
+ // API), which isn't in the first-party Anthropic tool_result types.
596
661
  content: msg.content.map((result) => ({
597
662
  type: "tool_result",
598
663
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
@@ -732,6 +797,13 @@ function toOpenAIMessages(messages, options) {
732
797
  content: msg.content.map(
733
798
  (part) => {
734
799
  if (part.type === "text") return { type: "text", text: part.text };
800
+ if (part.type === "video") {
801
+ const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
802
+ return {
803
+ type: "video_url",
804
+ video_url: videoUrl
805
+ };
806
+ }
735
807
  return {
736
808
  type: "image_url",
737
809
  image_url: {
@@ -774,29 +846,56 @@ function toOpenAIMessages(messages, options) {
774
846
  continue;
775
847
  }
776
848
  if (msg.role === "tool") {
777
- const imageBlocks = [];
849
+ const isMoonshot = options?.provider === "moonshot";
850
+ const followUpMediaBlocks = [];
851
+ let followUpHasVideo = false;
778
852
  for (const result of msg.content) {
779
853
  const text = toolResultText(result.content);
780
854
  const images = toolResultImages(result.content);
855
+ const videos = toolResultVideos(result.content);
781
856
  const hasText = text.length > 0;
857
+ if (isMoonshot && videos.length > 0) {
858
+ const parts = [];
859
+ if (hasText) parts.push({ type: "text", text });
860
+ const videoParts = videos.map((v) => {
861
+ const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
862
+ return { type: "video_url", video_url: videoUrl };
863
+ });
864
+ out.push({
865
+ role: "tool",
866
+ tool_call_id: remapToolCallId(result.toolCallId, idMap),
867
+ content: [...parts, ...videoParts]
868
+ });
869
+ continue;
870
+ }
782
871
  out.push({
783
872
  role: "tool",
784
873
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
785
- content: hasText ? text : "(see attached image)"
874
+ content: hasText ? text : "(see attached media)"
786
875
  });
787
876
  if (images.length > 0 && options?.supportsImages !== false) {
788
877
  for (const img of images) {
789
- imageBlocks.push({
878
+ followUpMediaBlocks.push({
790
879
  type: "image_url",
791
880
  image_url: { url: `data:${img.mediaType};base64,${img.data}` }
792
881
  });
793
882
  }
794
883
  }
884
+ if (!isMoonshot && videos.length > 0) {
885
+ for (const v of videos) {
886
+ followUpMediaBlocks.push({
887
+ type: "video_url",
888
+ video_url: { url: `data:${v.mediaType};base64,${v.data}` }
889
+ });
890
+ followUpHasVideo = true;
891
+ }
892
+ }
795
893
  }
796
- if (imageBlocks.length > 0) {
894
+ if (followUpMediaBlocks.length > 0) {
895
+ const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
797
896
  out.push({
798
897
  role: "user",
799
- content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
898
+ content: [{ type: "text", text: label }, ...followUpMediaBlocks]
800
899
  });
801
900
  }
802
901
  }
@@ -897,7 +996,8 @@ async function* runStream(options) {
897
996
  const useStreaming = options.streaming !== false;
898
997
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
899
998
  const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
900
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
999
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1000
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
901
1001
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
902
1002
  const system = isOAuth ? [
903
1003
  {
@@ -1103,11 +1203,18 @@ async function* runStream(options) {
1103
1203
  args: tc.args
1104
1204
  };
1105
1205
  } else if (accum.type === "server_tool_use") {
1206
+ let input = accum.input;
1207
+ if (accum.argsJson) {
1208
+ try {
1209
+ input = JSON.parse(accum.argsJson);
1210
+ } catch {
1211
+ }
1212
+ }
1106
1213
  const stc = {
1107
1214
  type: "server_tool_call",
1108
1215
  id: accum.toolId,
1109
1216
  name: accum.toolName,
1110
- input: accum.input
1217
+ input
1111
1218
  };
1112
1219
  contentParts.push(stc);
1113
1220
  yield {
@@ -1315,6 +1422,14 @@ function toError(err) {
1315
1422
  });
1316
1423
  }
1317
1424
  }
1425
+ if (isHardBillingMessage(message)) {
1426
+ const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
1427
+ return new ProviderError("anthropic", usageMessage, {
1428
+ statusCode: err.status,
1429
+ ...requestId ? { requestId } : {},
1430
+ cause: err
1431
+ });
1432
+ }
1318
1433
  return new ProviderError("anthropic", message, {
1319
1434
  statusCode: err.status,
1320
1435
  ...requestId ? { requestId } : {},
@@ -1347,6 +1462,75 @@ function fnv1aHash(value) {
1347
1462
  return (hash >>> 0).toString(16).padStart(8, "0");
1348
1463
  }
1349
1464
 
1465
+ // src/utils/diag.ts
1466
+ var _diagFn = null;
1467
+ function setProviderDiagnostic(fn) {
1468
+ _diagFn = fn;
1469
+ }
1470
+ function providerDiag(phase, data) {
1471
+ _diagFn?.(phase, data);
1472
+ }
1473
+
1474
+ // src/providers/moonshot-video.ts
1475
+ async function uploadMoonshotVideos(client, messages, signal) {
1476
+ for (const msg of messages) {
1477
+ if (typeof msg.content === "string") continue;
1478
+ for (const part of msg.content) {
1479
+ if (part.type === "video") {
1480
+ await ensureUploaded(client, part, signal);
1481
+ continue;
1482
+ }
1483
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
1484
+ for (const inner of part.content) {
1485
+ if (inner.type === "video") {
1486
+ await ensureUploaded(client, inner, signal);
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ async function ensureUploaded(client, video, signal) {
1494
+ if (video.fileId) {
1495
+ providerDiag("moonshot_video_cached", { fileId: video.fileId });
1496
+ return;
1497
+ }
1498
+ if (!video.data) {
1499
+ providerDiag("moonshot_video_skipped_no_data", {});
1500
+ return;
1501
+ }
1502
+ providerDiag("moonshot_video_upload_start", {
1503
+ mediaType: video.mediaType,
1504
+ bytes: Math.floor(video.data.length * 3 / 4)
1505
+ });
1506
+ video.fileId = await uploadOne(client, video, signal);
1507
+ providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
1508
+ }
1509
+ async function uploadOne(client, video, signal) {
1510
+ const bytes = Buffer.from(video.data, "base64");
1511
+ const mediaType = video.mediaType || "video/mp4";
1512
+ const filename = `upload.${extForMime(mediaType)}`;
1513
+ const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
1514
+ const uploaded = await client.files.create(
1515
+ { file, purpose: "video" },
1516
+ signal ? { signal } : void 0
1517
+ );
1518
+ return uploaded.id;
1519
+ }
1520
+ var MIME_TO_EXT = {
1521
+ "video/mp4": "mp4",
1522
+ "video/mpeg": "mpeg",
1523
+ "video/quicktime": "mov",
1524
+ "video/webm": "webm",
1525
+ "video/x-matroska": "mkv",
1526
+ "video/x-msvideo": "avi",
1527
+ "video/x-flv": "flv",
1528
+ "video/3gpp": "3gp"
1529
+ };
1530
+ function extForMime(mediaType) {
1531
+ return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
1532
+ }
1533
+
1350
1534
  // src/utils/env.ts
1351
1535
  function getEnvironment() {
1352
1536
  return globalThis.process?.env;
@@ -1376,7 +1560,8 @@ function createClient2(options) {
1376
1560
  return new import_openai.default({
1377
1561
  apiKey: options.apiKey,
1378
1562
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1379
- ...options.fetch ? { fetch: options.fetch } : {}
1563
+ ...options.fetch ? { fetch: options.fetch } : {},
1564
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1380
1565
  });
1381
1566
  }
1382
1567
  function streamOpenAI(options) {
@@ -1387,7 +1572,15 @@ async function* runStream2(options) {
1387
1572
  const useStreaming = options.streaming !== false;
1388
1573
  const client = createClient2(options);
1389
1574
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1390
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1575
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1576
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1577
+ if (options.provider === "moonshot") {
1578
+ try {
1579
+ await uploadMoonshotVideos(client, downgradedMessages, options.signal);
1580
+ } catch (err) {
1581
+ throw toError2(err, providerName);
1582
+ }
1583
+ }
1391
1584
  const messages = toOpenAIMessages(downgradedMessages, {
1392
1585
  provider: options.provider,
1393
1586
  thinking: !!options.thinking,
@@ -1625,6 +1818,16 @@ function completionToResponse(completion) {
1625
1818
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1626
1819
  };
1627
1820
  }
1821
+ function classifyOpenAICompatLimit(args) {
1822
+ const { status, code, type, message } = args;
1823
+ const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
1824
+ const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
1825
+ if (isHard) return "hard";
1826
+ if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
1827
+ return "transient";
1828
+ }
1829
+ return null;
1830
+ }
1628
1831
  function toError2(err, provider = "openai") {
1629
1832
  if (err instanceof import_openai.default.APIError) {
1630
1833
  const body = err.error;
@@ -1636,6 +1839,35 @@ function toError2(err, provider = "openai") {
1636
1839
  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.";
1637
1840
  }
1638
1841
  const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1842
+ const code = typeof err.code === "string" ? err.code : void 0;
1843
+ const type = typeof err.type === "string" ? err.type : void 0;
1844
+ const limit = classifyOpenAICompatLimit({
1845
+ status: err.status,
1846
+ code,
1847
+ type,
1848
+ message: cleanMessage
1849
+ });
1850
+ if (limit === "hard") {
1851
+ const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
1852
+ return new ProviderError(provider, message, {
1853
+ statusCode: err.status,
1854
+ ...requestId ? { requestId } : {},
1855
+ ...hint ? { hint } : {},
1856
+ cause: err
1857
+ });
1858
+ }
1859
+ if (limit === "transient") {
1860
+ const retryAfterRaw = readHeader(err.headers, "retry-after");
1861
+ const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
1862
+ const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
1863
+ return new ProviderError(provider, cleanMessage, {
1864
+ statusCode: err.status,
1865
+ ...requestId ? { requestId } : {},
1866
+ ...hint ? { hint } : {},
1867
+ ...resetsAt ? { resetsAt } : {},
1868
+ cause: err
1869
+ });
1870
+ }
1639
1871
  return new ProviderError(provider, cleanMessage, {
1640
1872
  statusCode: err.status,
1641
1873
  ...requestId ? { requestId } : {},
@@ -1652,15 +1884,6 @@ function toError2(err, provider = "openai") {
1652
1884
  // src/providers/openai-codex.ts
1653
1885
  var import_node_os = __toESM(require("os"), 1);
1654
1886
 
1655
- // src/utils/diag.ts
1656
- var _diagFn = null;
1657
- function setProviderDiagnostic(fn) {
1658
- _diagFn = fn;
1659
- }
1660
- function providerDiag(phase, data) {
1661
- _diagFn?.(phase, data);
1662
- }
1663
-
1664
1887
  // src/utils/sse.ts
1665
1888
  function parseSseBuffer(buffer) {
1666
1889
  const events = [];
@@ -1726,7 +1949,8 @@ function streamOpenAICodex(options) {
1726
1949
  async function* runStream3(options) {
1727
1950
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1728
1951
  const url = `${baseUrl}/codex/responses`;
1729
- const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1952
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1953
+ const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1730
1954
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
1731
1955
  const body = {
1732
1956
  model: options.model,
@@ -1803,6 +2027,7 @@ async function* runStream3(options) {
1803
2027
  const contentParts = [];
1804
2028
  let textAccum = "";
1805
2029
  const toolCalls = /* @__PURE__ */ new Map();
2030
+ const orderedItems = [];
1806
2031
  const outputItemTypes = /* @__PURE__ */ new Map();
1807
2032
  const outputTextByPart = /* @__PURE__ */ new Map();
1808
2033
  const pendingOutputTextByPart = /* @__PURE__ */ new Map();
@@ -1950,12 +2175,26 @@ async function* runStream3(options) {
1950
2175
  }
1951
2176
  if (type === "response.output_item.done") {
1952
2177
  const item = event.item;
2178
+ if (item?.type === "reasoning") {
2179
+ const encrypted = item.encrypted_content;
2180
+ const reasoningId = item.id;
2181
+ if (encrypted && reasoningId) {
2182
+ orderedItems.push({
2183
+ kind: "reasoning",
2184
+ part: {
2185
+ type: "raw",
2186
+ data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] }
2187
+ }
2188
+ });
2189
+ }
2190
+ }
1953
2191
  if (item?.type === "function_call") {
1954
2192
  const callId = item.call_id;
1955
2193
  const itemId = item.id;
1956
2194
  const id = `${callId}|${itemId}`;
1957
2195
  const tc = toolCalls.get(id);
1958
2196
  if (tc) {
2197
+ orderedItems.push({ kind: "tool", id });
1959
2198
  const args = parseToolArguments(tc.argsJson);
1960
2199
  yield {
1961
2200
  type: "toolcall_done",
@@ -1976,19 +2215,41 @@ async function* runStream3(options) {
1976
2215
  }
1977
2216
  }
1978
2217
  }
1979
- if (textAccum) {
1980
- contentParts.push({ type: "text", text: textAccum });
1981
- }
1982
- for (const [, tc] of toolCalls) {
1983
- const args = parseToolArguments(tc.argsJson);
2218
+ const seenTool = /* @__PURE__ */ new Set();
2219
+ let textInserted = false;
2220
+ for (const entry of orderedItems) {
2221
+ if (entry.kind === "reasoning") {
2222
+ contentParts.push(entry.part);
2223
+ continue;
2224
+ }
2225
+ if (textAccum && !textInserted) {
2226
+ contentParts.push({ type: "text", text: textAccum });
2227
+ textInserted = true;
2228
+ }
2229
+ const tc = toolCalls.get(entry.id);
2230
+ if (!tc || seenTool.has(entry.id)) continue;
2231
+ seenTool.add(entry.id);
1984
2232
  const toolCall = {
1985
2233
  type: "tool_call",
1986
2234
  id: tc.id,
1987
2235
  name: tc.name,
1988
- args
2236
+ args: parseToolArguments(tc.argsJson)
1989
2237
  };
1990
2238
  contentParts.push(toolCall);
1991
2239
  }
2240
+ if (textAccum && !textInserted) {
2241
+ contentParts.push({ type: "text", text: textAccum });
2242
+ }
2243
+ for (const [id, tc] of toolCalls) {
2244
+ if (seenTool.has(id)) continue;
2245
+ seenTool.add(id);
2246
+ contentParts.push({
2247
+ type: "tool_call",
2248
+ id: tc.id,
2249
+ name: tc.name,
2250
+ args: parseToolArguments(tc.argsJson)
2251
+ });
2252
+ }
1992
2253
  const hasToolCalls = contentParts.some((p) => p.type === "tool_call");
1993
2254
  const stopReason = hasToolCalls ? "tool_use" : "end_turn";
1994
2255
  const streamResponse = {
@@ -2020,6 +2281,9 @@ function remapCodexId(id, idMap) {
2020
2281
  idMap.set(id, mapped);
2021
2282
  return mapped;
2022
2283
  }
2284
+ function isEncryptedReasoning(data) {
2285
+ return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
2286
+ }
2023
2287
  function toCodexInput(messages, options) {
2024
2288
  let system;
2025
2289
  const input = [];
@@ -2052,7 +2316,9 @@ function toCodexInput(messages, options) {
2052
2316
  continue;
2053
2317
  }
2054
2318
  for (const part of msg.content) {
2055
- if (part.type === "text") {
2319
+ if (part.type === "raw" && isEncryptedReasoning(part.data)) {
2320
+ input.push(part.data);
2321
+ } else if (part.type === "text") {
2056
2322
  input.push({
2057
2323
  type: "message",
2058
2324
  role: "assistant",
@@ -2193,6 +2459,20 @@ ${formatUnsupportedModelMessage(model)}`;
2193
2459
  }
2194
2460
  return `Gemini API error (${status}): ${body}`;
2195
2461
  }
2462
+ function parseRetryDelaySeconds(body) {
2463
+ const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
2464
+ if (!match) return void 0;
2465
+ const seconds = Number(match[1]);
2466
+ return Number.isFinite(seconds) ? seconds : void 0;
2467
+ }
2468
+ function parseGeminiQuota(status, body) {
2469
+ if (status !== 429) return null;
2470
+ const lower = body.toLowerCase();
2471
+ if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
2472
+ const retryDelaySeconds = parseRetryDelaySeconds(body);
2473
+ const exhausted = retryDelaySeconds === void 0;
2474
+ return { exhausted, retryDelaySeconds };
2475
+ }
2196
2476
  function toSystemAndContents(messages) {
2197
2477
  let systemText = "";
2198
2478
  const contents = [];
@@ -2252,6 +2532,13 @@ ${msg.content}` : msg.content;
2252
2532
  }
2253
2533
  }
2254
2534
  });
2535
+ if (typeof result.content !== "string") {
2536
+ for (const block of result.content) {
2537
+ if (block.type === "video") {
2538
+ parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
2539
+ }
2540
+ }
2541
+ }
2255
2542
  }
2256
2543
  if (parts.length > 0) contents.push({ role: "user", parts });
2257
2544
  }
@@ -2344,7 +2631,8 @@ function toThinkingConfig(model, level) {
2344
2631
  };
2345
2632
  }
2346
2633
  function buildGenerateRequest(options) {
2347
- const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2634
+ const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
2635
+ const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2348
2636
  const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
2349
2637
  const tools = toGeminiTools(options.tools);
2350
2638
  const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
@@ -2472,8 +2760,17 @@ async function fetchCodeAssist(plan, options) {
2472
2760
  });
2473
2761
  if (!response.ok) {
2474
2762
  const text = await response.text().catch(() => "");
2475
- throw new ProviderError("gemini", formatErrorMessage(response.status, text, options.model), {
2476
- statusCode: response.status
2763
+ const quota = parseGeminiQuota(response.status, text);
2764
+ let message = formatErrorMessage(response.status, text, options.model);
2765
+ let resetsAt;
2766
+ if (quota?.exhausted) {
2767
+ message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
2768
+ } else if (quota?.retryDelaySeconds !== void 0) {
2769
+ resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
2770
+ }
2771
+ throw new ProviderError("gemini", message, {
2772
+ statusCode: response.status,
2773
+ ...resetsAt !== void 0 ? { resetsAt } : {}
2477
2774
  });
2478
2775
  }
2479
2776
  return response;
@@ -2639,6 +2936,7 @@ var providerRegistry = new ProviderRegistryImpl();
2639
2936
 
2640
2937
  // src/stream.ts
2641
2938
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
2939
+ var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
2642
2940
  providerRegistry.register("anthropic", {
2643
2941
  stream: (options) => streamAnthropic(options)
2644
2942
  });
@@ -2667,10 +2965,11 @@ providerRegistry.register("glm", {
2667
2965
  })
2668
2966
  });
2669
2967
  providerRegistry.register("moonshot", {
2670
- stream: (options) => streamOpenAI({
2671
- ...options,
2672
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2673
- })
2968
+ stream: (options) => {
2969
+ const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
2970
+ const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
2971
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
2972
+ }
2674
2973
  });
2675
2974
  providerRegistry.register("deepseek", {
2676
2975
  stream: (options) => streamOpenAI({
@@ -2703,8 +3002,23 @@ function stream(options) {
2703
3002
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2704
3003
  );
2705
3004
  }
3005
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
3006
+ throw new VideoUnsupportedError();
3007
+ }
2706
3008
  return entry.stream(options);
2707
3009
  }
3010
+ function messagesContainVideo(messages) {
3011
+ for (const msg of messages) {
3012
+ if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
3013
+ for (const part of msg.content) {
3014
+ if (part.type === "video") return true;
3015
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
3016
+ if (part.content.some((block) => block.type === "video")) return true;
3017
+ }
3018
+ }
3019
+ }
3020
+ return false;
3021
+ }
2708
3022
 
2709
3023
  // src/providers/palsu.ts
2710
3024
  function palsuText(text) {
@@ -2865,6 +3179,7 @@ function registerPalsuProvider(config) {
2865
3179
  StreamResult,
2866
3180
  formatError,
2867
3181
  formatErrorForDisplay,
3182
+ isHardBillingMessage,
2868
3183
  isUsageLimitError,
2869
3184
  palsuAssistantMessage,
2870
3185
  palsuText,