@kenkaiiii/gg-ai 4.4.0 → 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
@@ -79,6 +79,12 @@ var GGAIError = class extends Error {
79
79
  this.hint = options?.hint;
80
80
  }
81
81
  };
82
+ var VideoUnsupportedError = class extends GGAIError {
83
+ constructor() {
84
+ super("This model can't analyze video.", { source: "capability" });
85
+ this.name = "VideoUnsupportedError";
86
+ }
87
+ };
82
88
  var ProviderError = class extends GGAIError {
83
89
  provider;
84
90
  statusCode;
@@ -194,6 +200,14 @@ function finaliseBySource(source, message, requestId, hint) {
194
200
  guidance: hint ?? providerGuidance(void 0, message, void 0),
195
201
  ...requestId ? { requestId } : {}
196
202
  };
203
+ case "capability":
204
+ return {
205
+ headline: message,
206
+ source,
207
+ message: "",
208
+ guidance: hint ?? "Only Kimi, Gemini, and MiniMax can analyze video. Switch with /model.",
209
+ ...requestId ? { requestId } : {}
210
+ };
197
211
  case "ggcoder":
198
212
  return {
199
213
  headline: "ggcoder hit an unexpected error.",
@@ -554,6 +568,10 @@ function toolResultImages(content) {
554
568
  if (typeof content === "string") return [];
555
569
  return content.filter((b) => b.type === "image");
556
570
  }
571
+ function toolResultVideos(content) {
572
+ if (typeof content === "string") return [];
573
+ return content.filter((b) => b.type === "video");
574
+ }
557
575
  function toAnthropicCacheControl(retention, baseUrl) {
558
576
  const resolved = retention ?? "short";
559
577
  if (resolved === "none") return void 0;
@@ -564,6 +582,12 @@ function toAnthropicToolResultContent(content) {
564
582
  if (typeof content === "string") return content;
565
583
  return content.map((block) => {
566
584
  if (block.type === "text") return { type: "text", text: block.text };
585
+ if (block.type === "video") {
586
+ return {
587
+ type: "video",
588
+ source: { type: "base64", media_type: block.mediaType, data: block.data }
589
+ };
590
+ }
567
591
  return {
568
592
  type: "image",
569
593
  source: {
@@ -633,6 +657,8 @@ function toAnthropicMessages(messages, cacheControl) {
633
657
  if (msg.role === "tool") {
634
658
  out.push({
635
659
  role: "user",
660
+ // Cast covers the video block (used by the Anthropic-compatible MiniMax
661
+ // API), which isn't in the first-party Anthropic tool_result types.
636
662
  content: msg.content.map((result) => ({
637
663
  type: "tool_result",
638
664
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
@@ -773,11 +799,10 @@ function toOpenAIMessages(messages, options) {
773
799
  (part) => {
774
800
  if (part.type === "text") return { type: "text", text: part.text };
775
801
  if (part.type === "video") {
802
+ const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
776
803
  return {
777
804
  type: "video_url",
778
- video_url: {
779
- url: `data:${part.mediaType};base64,${part.data}`
780
- }
805
+ video_url: videoUrl
781
806
  };
782
807
  }
783
808
  return {
@@ -822,11 +847,27 @@ function toOpenAIMessages(messages, options) {
822
847
  continue;
823
848
  }
824
849
  if (msg.role === "tool") {
850
+ const isMoonshot = options?.provider === "moonshot";
825
851
  const imageBlocks = [];
826
852
  for (const result of msg.content) {
827
853
  const text = toolResultText(result.content);
828
854
  const images = toolResultImages(result.content);
855
+ const videos = isMoonshot ? toolResultVideos(result.content) : [];
829
856
  const hasText = text.length > 0;
857
+ if (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
+ }
830
871
  out.push({
831
872
  role: "tool",
832
873
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
@@ -1411,6 +1452,75 @@ function fnv1aHash(value) {
1411
1452
  return (hash >>> 0).toString(16).padStart(8, "0");
1412
1453
  }
1413
1454
 
1455
+ // src/utils/diag.ts
1456
+ var _diagFn = null;
1457
+ function setProviderDiagnostic(fn) {
1458
+ _diagFn = fn;
1459
+ }
1460
+ function providerDiag(phase, data) {
1461
+ _diagFn?.(phase, data);
1462
+ }
1463
+
1464
+ // src/providers/moonshot-video.ts
1465
+ async function uploadMoonshotVideos(client, messages, signal) {
1466
+ for (const msg of messages) {
1467
+ if (typeof msg.content === "string") continue;
1468
+ for (const part of msg.content) {
1469
+ if (part.type === "video") {
1470
+ await ensureUploaded(client, part, signal);
1471
+ continue;
1472
+ }
1473
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
1474
+ for (const inner of part.content) {
1475
+ if (inner.type === "video") {
1476
+ await ensureUploaded(client, inner, signal);
1477
+ }
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+ }
1483
+ async function ensureUploaded(client, video, signal) {
1484
+ if (video.fileId) {
1485
+ providerDiag("moonshot_video_cached", { fileId: video.fileId });
1486
+ return;
1487
+ }
1488
+ if (!video.data) {
1489
+ providerDiag("moonshot_video_skipped_no_data", {});
1490
+ return;
1491
+ }
1492
+ providerDiag("moonshot_video_upload_start", {
1493
+ mediaType: video.mediaType,
1494
+ bytes: Math.floor(video.data.length * 3 / 4)
1495
+ });
1496
+ video.fileId = await uploadOne(client, video, signal);
1497
+ providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
1498
+ }
1499
+ async function uploadOne(client, video, signal) {
1500
+ const bytes = Buffer.from(video.data, "base64");
1501
+ const mediaType = video.mediaType || "video/mp4";
1502
+ const filename = `upload.${extForMime(mediaType)}`;
1503
+ const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
1504
+ const uploaded = await client.files.create(
1505
+ { file, purpose: "video" },
1506
+ signal ? { signal } : void 0
1507
+ );
1508
+ return uploaded.id;
1509
+ }
1510
+ var MIME_TO_EXT = {
1511
+ "video/mp4": "mp4",
1512
+ "video/mpeg": "mpeg",
1513
+ "video/quicktime": "mov",
1514
+ "video/webm": "webm",
1515
+ "video/x-matroska": "mkv",
1516
+ "video/x-msvideo": "avi",
1517
+ "video/x-flv": "flv",
1518
+ "video/3gpp": "3gp"
1519
+ };
1520
+ function extForMime(mediaType) {
1521
+ return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
1522
+ }
1523
+
1414
1524
  // src/utils/env.ts
1415
1525
  function getEnvironment() {
1416
1526
  return globalThis.process?.env;
@@ -1440,7 +1550,8 @@ function createClient2(options) {
1440
1550
  return new import_openai.default({
1441
1551
  apiKey: options.apiKey,
1442
1552
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1443
- ...options.fetch ? { fetch: options.fetch } : {}
1553
+ ...options.fetch ? { fetch: options.fetch } : {},
1554
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1444
1555
  });
1445
1556
  }
1446
1557
  function streamOpenAI(options) {
@@ -1453,6 +1564,13 @@ async function* runStream2(options) {
1453
1564
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1454
1565
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1455
1566
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1567
+ if (options.provider === "moonshot") {
1568
+ try {
1569
+ await uploadMoonshotVideos(client, downgradedMessages, options.signal);
1570
+ } catch (err) {
1571
+ throw toError2(err, providerName);
1572
+ }
1573
+ }
1456
1574
  const messages = toOpenAIMessages(downgradedMessages, {
1457
1575
  provider: options.provider,
1458
1576
  thinking: !!options.thinking,
@@ -1756,15 +1874,6 @@ function toError2(err, provider = "openai") {
1756
1874
  // src/providers/openai-codex.ts
1757
1875
  var import_node_os = __toESM(require("os"), 1);
1758
1876
 
1759
- // src/utils/diag.ts
1760
- var _diagFn = null;
1761
- function setProviderDiagnostic(fn) {
1762
- _diagFn = fn;
1763
- }
1764
- function providerDiag(phase, data) {
1765
- _diagFn?.(phase, data);
1766
- }
1767
-
1768
1877
  // src/utils/sse.ts
1769
1878
  function parseSseBuffer(buffer) {
1770
1879
  const events = [];
@@ -2413,6 +2522,13 @@ ${msg.content}` : msg.content;
2413
2522
  }
2414
2523
  }
2415
2524
  });
2525
+ if (typeof result.content !== "string") {
2526
+ for (const block of result.content) {
2527
+ if (block.type === "video") {
2528
+ parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
2529
+ }
2530
+ }
2531
+ }
2416
2532
  }
2417
2533
  if (parts.length > 0) contents.push({ role: "user", parts });
2418
2534
  }
@@ -2810,6 +2926,7 @@ var providerRegistry = new ProviderRegistryImpl();
2810
2926
 
2811
2927
  // src/stream.ts
2812
2928
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
2929
+ var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
2813
2930
  providerRegistry.register("anthropic", {
2814
2931
  stream: (options) => streamAnthropic(options)
2815
2932
  });
@@ -2838,10 +2955,11 @@ providerRegistry.register("glm", {
2838
2955
  })
2839
2956
  });
2840
2957
  providerRegistry.register("moonshot", {
2841
- stream: (options) => streamOpenAI({
2842
- ...options,
2843
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2844
- })
2958
+ stream: (options) => {
2959
+ const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
2960
+ const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
2961
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
2962
+ }
2845
2963
  });
2846
2964
  providerRegistry.register("deepseek", {
2847
2965
  stream: (options) => streamOpenAI({
@@ -2874,8 +2992,23 @@ function stream(options) {
2874
2992
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2875
2993
  );
2876
2994
  }
2995
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
2996
+ throw new VideoUnsupportedError();
2997
+ }
2877
2998
  return entry.stream(options);
2878
2999
  }
3000
+ function messagesContainVideo(messages) {
3001
+ for (const msg of messages) {
3002
+ if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
3003
+ for (const part of msg.content) {
3004
+ if (part.type === "video") return true;
3005
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
3006
+ if (part.content.some((block) => block.type === "video")) return true;
3007
+ }
3008
+ }
3009
+ }
3010
+ return false;
3011
+ }
2879
3012
 
2880
3013
  // src/error-classification.ts
2881
3014
  var CONTEXT_OVERFLOW_PATTERNS = [