@kenkaiiii/gg-ai 4.4.0 → 4.6.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, MiniMax, and MiMo-V2.5 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,29 +847,56 @@ function toOpenAIMessages(messages, options) {
822
847
  continue;
823
848
  }
824
849
  if (msg.role === "tool") {
825
- const imageBlocks = [];
850
+ const isMoonshot = options?.provider === "moonshot";
851
+ const followUpMediaBlocks = [];
852
+ let followUpHasVideo = false;
826
853
  for (const result of msg.content) {
827
854
  const text = toolResultText(result.content);
828
855
  const images = toolResultImages(result.content);
856
+ const videos = toolResultVideos(result.content);
829
857
  const hasText = text.length > 0;
858
+ if (isMoonshot && videos.length > 0) {
859
+ const parts = [];
860
+ if (hasText) parts.push({ type: "text", text });
861
+ const videoParts = videos.map((v) => {
862
+ const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
863
+ return { type: "video_url", video_url: videoUrl };
864
+ });
865
+ out.push({
866
+ role: "tool",
867
+ tool_call_id: remapToolCallId(result.toolCallId, idMap),
868
+ content: [...parts, ...videoParts]
869
+ });
870
+ continue;
871
+ }
830
872
  out.push({
831
873
  role: "tool",
832
874
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
833
- content: hasText ? text : "(see attached image)"
875
+ content: hasText ? text : "(see attached media)"
834
876
  });
835
877
  if (images.length > 0 && options?.supportsImages !== false) {
836
878
  for (const img of images) {
837
- imageBlocks.push({
879
+ followUpMediaBlocks.push({
838
880
  type: "image_url",
839
881
  image_url: { url: `data:${img.mediaType};base64,${img.data}` }
840
882
  });
841
883
  }
842
884
  }
885
+ if (!isMoonshot && videos.length > 0) {
886
+ for (const v of videos) {
887
+ followUpMediaBlocks.push({
888
+ type: "video_url",
889
+ video_url: { url: `data:${v.mediaType};base64,${v.data}` }
890
+ });
891
+ followUpHasVideo = true;
892
+ }
893
+ }
843
894
  }
844
- if (imageBlocks.length > 0) {
895
+ if (followUpMediaBlocks.length > 0) {
896
+ const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
845
897
  out.push({
846
898
  role: "user",
847
- content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
899
+ content: [{ type: "text", text: label }, ...followUpMediaBlocks]
848
900
  });
849
901
  }
850
902
  }
@@ -1411,6 +1463,75 @@ function fnv1aHash(value) {
1411
1463
  return (hash >>> 0).toString(16).padStart(8, "0");
1412
1464
  }
1413
1465
 
1466
+ // src/utils/diag.ts
1467
+ var _diagFn = null;
1468
+ function setProviderDiagnostic(fn) {
1469
+ _diagFn = fn;
1470
+ }
1471
+ function providerDiag(phase, data) {
1472
+ _diagFn?.(phase, data);
1473
+ }
1474
+
1475
+ // src/providers/moonshot-video.ts
1476
+ async function uploadMoonshotVideos(client, messages, signal) {
1477
+ for (const msg of messages) {
1478
+ if (typeof msg.content === "string") continue;
1479
+ for (const part of msg.content) {
1480
+ if (part.type === "video") {
1481
+ await ensureUploaded(client, part, signal);
1482
+ continue;
1483
+ }
1484
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
1485
+ for (const inner of part.content) {
1486
+ if (inner.type === "video") {
1487
+ await ensureUploaded(client, inner, signal);
1488
+ }
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+ async function ensureUploaded(client, video, signal) {
1495
+ if (video.fileId) {
1496
+ providerDiag("moonshot_video_cached", { fileId: video.fileId });
1497
+ return;
1498
+ }
1499
+ if (!video.data) {
1500
+ providerDiag("moonshot_video_skipped_no_data", {});
1501
+ return;
1502
+ }
1503
+ providerDiag("moonshot_video_upload_start", {
1504
+ mediaType: video.mediaType,
1505
+ bytes: Math.floor(video.data.length * 3 / 4)
1506
+ });
1507
+ video.fileId = await uploadOne(client, video, signal);
1508
+ providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
1509
+ }
1510
+ async function uploadOne(client, video, signal) {
1511
+ const bytes = Buffer.from(video.data, "base64");
1512
+ const mediaType = video.mediaType || "video/mp4";
1513
+ const filename = `upload.${extForMime(mediaType)}`;
1514
+ const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
1515
+ const uploaded = await client.files.create(
1516
+ { file, purpose: "video" },
1517
+ signal ? { signal } : void 0
1518
+ );
1519
+ return uploaded.id;
1520
+ }
1521
+ var MIME_TO_EXT = {
1522
+ "video/mp4": "mp4",
1523
+ "video/mpeg": "mpeg",
1524
+ "video/quicktime": "mov",
1525
+ "video/webm": "webm",
1526
+ "video/x-matroska": "mkv",
1527
+ "video/x-msvideo": "avi",
1528
+ "video/x-flv": "flv",
1529
+ "video/3gpp": "3gp"
1530
+ };
1531
+ function extForMime(mediaType) {
1532
+ return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
1533
+ }
1534
+
1414
1535
  // src/utils/env.ts
1415
1536
  function getEnvironment() {
1416
1537
  return globalThis.process?.env;
@@ -1440,7 +1561,8 @@ function createClient2(options) {
1440
1561
  return new import_openai.default({
1441
1562
  apiKey: options.apiKey,
1442
1563
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1443
- ...options.fetch ? { fetch: options.fetch } : {}
1564
+ ...options.fetch ? { fetch: options.fetch } : {},
1565
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1444
1566
  });
1445
1567
  }
1446
1568
  function streamOpenAI(options) {
@@ -1453,6 +1575,13 @@ async function* runStream2(options) {
1453
1575
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1454
1576
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1455
1577
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1578
+ if (options.provider === "moonshot") {
1579
+ try {
1580
+ await uploadMoonshotVideos(client, downgradedMessages, options.signal);
1581
+ } catch (err) {
1582
+ throw toError2(err, providerName);
1583
+ }
1584
+ }
1456
1585
  const messages = toOpenAIMessages(downgradedMessages, {
1457
1586
  provider: options.provider,
1458
1587
  thinking: !!options.thinking,
@@ -1756,15 +1885,6 @@ function toError2(err, provider = "openai") {
1756
1885
  // src/providers/openai-codex.ts
1757
1886
  var import_node_os = __toESM(require("os"), 1);
1758
1887
 
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
1888
  // src/utils/sse.ts
1769
1889
  function parseSseBuffer(buffer) {
1770
1890
  const events = [];
@@ -2413,6 +2533,13 @@ ${msg.content}` : msg.content;
2413
2533
  }
2414
2534
  }
2415
2535
  });
2536
+ if (typeof result.content !== "string") {
2537
+ for (const block of result.content) {
2538
+ if (block.type === "video") {
2539
+ parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
2540
+ }
2541
+ }
2542
+ }
2416
2543
  }
2417
2544
  if (parts.length > 0) contents.push({ role: "user", parts });
2418
2545
  }
@@ -2810,6 +2937,7 @@ var providerRegistry = new ProviderRegistryImpl();
2810
2937
 
2811
2938
  // src/stream.ts
2812
2939
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
2940
+ var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
2813
2941
  providerRegistry.register("anthropic", {
2814
2942
  stream: (options) => streamAnthropic(options)
2815
2943
  });
@@ -2838,10 +2966,11 @@ providerRegistry.register("glm", {
2838
2966
  })
2839
2967
  });
2840
2968
  providerRegistry.register("moonshot", {
2841
- stream: (options) => streamOpenAI({
2842
- ...options,
2843
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2844
- })
2969
+ stream: (options) => {
2970
+ const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
2971
+ const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
2972
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
2973
+ }
2845
2974
  });
2846
2975
  providerRegistry.register("deepseek", {
2847
2976
  stream: (options) => streamOpenAI({
@@ -2874,8 +3003,23 @@ function stream(options) {
2874
3003
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2875
3004
  );
2876
3005
  }
3006
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
3007
+ throw new VideoUnsupportedError();
3008
+ }
2877
3009
  return entry.stream(options);
2878
3010
  }
3011
+ function messagesContainVideo(messages) {
3012
+ for (const msg of messages) {
3013
+ if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
3014
+ for (const part of msg.content) {
3015
+ if (part.type === "video") return true;
3016
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
3017
+ if (part.content.some((block) => block.type === "video")) return true;
3018
+ }
3019
+ }
3020
+ }
3021
+ return false;
3022
+ }
2879
3023
 
2880
3024
  // src/error-classification.ts
2881
3025
  var CONTEXT_OVERFLOW_PATTERNS = [