@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.d.cts CHANGED
@@ -23,6 +23,11 @@ interface VideoContent {
23
23
  type: "video";
24
24
  mediaType: string;
25
25
  data: string;
26
+ /** Moonshot/Kimi file id (e.g. "d4f0…") after uploading via the files API.
27
+ * Moonshot rejects inline base64 video; the provider uploads the clip once
28
+ * and caches the id here so later turns reference `ms://<fileId>` instead of
29
+ * re-sending the bytes. */
30
+ fileId?: string;
26
31
  }
27
32
  interface ToolCall {
28
33
  type: "tool_call";
@@ -30,7 +35,7 @@ interface ToolCall {
30
35
  name: string;
31
36
  args: Record<string, unknown>;
32
37
  }
33
- type ToolResultContent = string | (TextContent | ImageContent)[];
38
+ type ToolResultContent = string | (TextContent | ImageContent | VideoContent)[];
34
39
  interface ToolResult {
35
40
  type: "tool_result";
36
41
  toolCallId: string;
@@ -208,6 +213,11 @@ interface StreamOptions {
208
213
  * version should pass it here. Ignored for non-Anthropic providers and for
209
214
  * Anthropic requests using a regular API key. */
210
215
  userAgent?: string;
216
+ /** Extra HTTP headers attached to every model request. Used by providers
217
+ * whose endpoint gates on client identity (e.g. Kimi For Coding requires a
218
+ * `User-Agent: kimi-code-cli/...` and `X-Msh-*` device headers). Merged
219
+ * into the underlying SDK's default headers. */
220
+ defaultHeaders?: Record<string, string>;
211
221
  }
212
222
 
213
223
  /**
@@ -330,7 +340,7 @@ declare const providerRegistry: ProviderRegistryImpl;
330
340
  * Cannot read property 'foo' of undefined
331
341
  * → This is a ggcoder bug — please report it.
332
342
  */
333
- type ErrorSource = "provider" | "ggcoder" | "network" | "auth";
343
+ type ErrorSource = "provider" | "ggcoder" | "network" | "auth" | "capability";
334
344
  interface FormattedError {
335
345
  /** Plain-English headline, e.g. "OpenAI returned an error." */
336
346
  headline: string;
package/dist/index.d.ts CHANGED
@@ -23,6 +23,11 @@ interface VideoContent {
23
23
  type: "video";
24
24
  mediaType: string;
25
25
  data: string;
26
+ /** Moonshot/Kimi file id (e.g. "d4f0…") after uploading via the files API.
27
+ * Moonshot rejects inline base64 video; the provider uploads the clip once
28
+ * and caches the id here so later turns reference `ms://<fileId>` instead of
29
+ * re-sending the bytes. */
30
+ fileId?: string;
26
31
  }
27
32
  interface ToolCall {
28
33
  type: "tool_call";
@@ -30,7 +35,7 @@ interface ToolCall {
30
35
  name: string;
31
36
  args: Record<string, unknown>;
32
37
  }
33
- type ToolResultContent = string | (TextContent | ImageContent)[];
38
+ type ToolResultContent = string | (TextContent | ImageContent | VideoContent)[];
34
39
  interface ToolResult {
35
40
  type: "tool_result";
36
41
  toolCallId: string;
@@ -208,6 +213,11 @@ interface StreamOptions {
208
213
  * version should pass it here. Ignored for non-Anthropic providers and for
209
214
  * Anthropic requests using a regular API key. */
210
215
  userAgent?: string;
216
+ /** Extra HTTP headers attached to every model request. Used by providers
217
+ * whose endpoint gates on client identity (e.g. Kimi For Coding requires a
218
+ * `User-Agent: kimi-code-cli/...` and `X-Msh-*` device headers). Merged
219
+ * into the underlying SDK's default headers. */
220
+ defaultHeaders?: Record<string, string>;
211
221
  }
212
222
 
213
223
  /**
@@ -330,7 +340,7 @@ declare const providerRegistry: ProviderRegistryImpl;
330
340
  * Cannot read property 'foo' of undefined
331
341
  * → This is a ggcoder bug — please report it.
332
342
  */
333
- type ErrorSource = "provider" | "ggcoder" | "network" | "auth";
343
+ type ErrorSource = "provider" | "ggcoder" | "network" | "auth" | "capability";
334
344
  interface FormattedError {
335
345
  /** Plain-English headline, e.g. "OpenAI returned an error." */
336
346
  headline: string;
package/dist/index.js CHANGED
@@ -25,6 +25,12 @@ var GGAIError = class extends Error {
25
25
  this.hint = options?.hint;
26
26
  }
27
27
  };
28
+ var VideoUnsupportedError = class extends GGAIError {
29
+ constructor() {
30
+ super("This model can't analyze video.", { source: "capability" });
31
+ this.name = "VideoUnsupportedError";
32
+ }
33
+ };
28
34
  var ProviderError = class extends GGAIError {
29
35
  provider;
30
36
  statusCode;
@@ -140,6 +146,14 @@ function finaliseBySource(source, message, requestId, hint) {
140
146
  guidance: hint ?? providerGuidance(void 0, message, void 0),
141
147
  ...requestId ? { requestId } : {}
142
148
  };
149
+ case "capability":
150
+ return {
151
+ headline: message,
152
+ source,
153
+ message: "",
154
+ guidance: hint ?? "Only Kimi, Gemini, MiniMax, and MiMo-V2.5 can analyze video. Switch with /model.",
155
+ ...requestId ? { requestId } : {}
156
+ };
143
157
  case "ggcoder":
144
158
  return {
145
159
  headline: "ggcoder hit an unexpected error.",
@@ -500,6 +514,10 @@ function toolResultImages(content) {
500
514
  if (typeof content === "string") return [];
501
515
  return content.filter((b) => b.type === "image");
502
516
  }
517
+ function toolResultVideos(content) {
518
+ if (typeof content === "string") return [];
519
+ return content.filter((b) => b.type === "video");
520
+ }
503
521
  function toAnthropicCacheControl(retention, baseUrl) {
504
522
  const resolved = retention ?? "short";
505
523
  if (resolved === "none") return void 0;
@@ -510,6 +528,12 @@ function toAnthropicToolResultContent(content) {
510
528
  if (typeof content === "string") return content;
511
529
  return content.map((block) => {
512
530
  if (block.type === "text") return { type: "text", text: block.text };
531
+ if (block.type === "video") {
532
+ return {
533
+ type: "video",
534
+ source: { type: "base64", media_type: block.mediaType, data: block.data }
535
+ };
536
+ }
513
537
  return {
514
538
  type: "image",
515
539
  source: {
@@ -579,6 +603,8 @@ function toAnthropicMessages(messages, cacheControl) {
579
603
  if (msg.role === "tool") {
580
604
  out.push({
581
605
  role: "user",
606
+ // Cast covers the video block (used by the Anthropic-compatible MiniMax
607
+ // API), which isn't in the first-party Anthropic tool_result types.
582
608
  content: msg.content.map((result) => ({
583
609
  type: "tool_result",
584
610
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
@@ -719,11 +745,10 @@ function toOpenAIMessages(messages, options) {
719
745
  (part) => {
720
746
  if (part.type === "text") return { type: "text", text: part.text };
721
747
  if (part.type === "video") {
748
+ const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
722
749
  return {
723
750
  type: "video_url",
724
- video_url: {
725
- url: `data:${part.mediaType};base64,${part.data}`
726
- }
751
+ video_url: videoUrl
727
752
  };
728
753
  }
729
754
  return {
@@ -768,29 +793,56 @@ function toOpenAIMessages(messages, options) {
768
793
  continue;
769
794
  }
770
795
  if (msg.role === "tool") {
771
- const imageBlocks = [];
796
+ const isMoonshot = options?.provider === "moonshot";
797
+ const followUpMediaBlocks = [];
798
+ let followUpHasVideo = false;
772
799
  for (const result of msg.content) {
773
800
  const text = toolResultText(result.content);
774
801
  const images = toolResultImages(result.content);
802
+ const videos = toolResultVideos(result.content);
775
803
  const hasText = text.length > 0;
804
+ if (isMoonshot && videos.length > 0) {
805
+ const parts = [];
806
+ if (hasText) parts.push({ type: "text", text });
807
+ const videoParts = videos.map((v) => {
808
+ const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
809
+ return { type: "video_url", video_url: videoUrl };
810
+ });
811
+ out.push({
812
+ role: "tool",
813
+ tool_call_id: remapToolCallId(result.toolCallId, idMap),
814
+ content: [...parts, ...videoParts]
815
+ });
816
+ continue;
817
+ }
776
818
  out.push({
777
819
  role: "tool",
778
820
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
779
- content: hasText ? text : "(see attached image)"
821
+ content: hasText ? text : "(see attached media)"
780
822
  });
781
823
  if (images.length > 0 && options?.supportsImages !== false) {
782
824
  for (const img of images) {
783
- imageBlocks.push({
825
+ followUpMediaBlocks.push({
784
826
  type: "image_url",
785
827
  image_url: { url: `data:${img.mediaType};base64,${img.data}` }
786
828
  });
787
829
  }
788
830
  }
831
+ if (!isMoonshot && videos.length > 0) {
832
+ for (const v of videos) {
833
+ followUpMediaBlocks.push({
834
+ type: "video_url",
835
+ video_url: { url: `data:${v.mediaType};base64,${v.data}` }
836
+ });
837
+ followUpHasVideo = true;
838
+ }
839
+ }
789
840
  }
790
- if (imageBlocks.length > 0) {
841
+ if (followUpMediaBlocks.length > 0) {
842
+ const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
791
843
  out.push({
792
844
  role: "user",
793
- content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
845
+ content: [{ type: "text", text: label }, ...followUpMediaBlocks]
794
846
  });
795
847
  }
796
848
  }
@@ -1357,6 +1409,75 @@ function fnv1aHash(value) {
1357
1409
  return (hash >>> 0).toString(16).padStart(8, "0");
1358
1410
  }
1359
1411
 
1412
+ // src/utils/diag.ts
1413
+ var _diagFn = null;
1414
+ function setProviderDiagnostic(fn) {
1415
+ _diagFn = fn;
1416
+ }
1417
+ function providerDiag(phase, data) {
1418
+ _diagFn?.(phase, data);
1419
+ }
1420
+
1421
+ // src/providers/moonshot-video.ts
1422
+ async function uploadMoonshotVideos(client, messages, signal) {
1423
+ for (const msg of messages) {
1424
+ if (typeof msg.content === "string") continue;
1425
+ for (const part of msg.content) {
1426
+ if (part.type === "video") {
1427
+ await ensureUploaded(client, part, signal);
1428
+ continue;
1429
+ }
1430
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
1431
+ for (const inner of part.content) {
1432
+ if (inner.type === "video") {
1433
+ await ensureUploaded(client, inner, signal);
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ async function ensureUploaded(client, video, signal) {
1441
+ if (video.fileId) {
1442
+ providerDiag("moonshot_video_cached", { fileId: video.fileId });
1443
+ return;
1444
+ }
1445
+ if (!video.data) {
1446
+ providerDiag("moonshot_video_skipped_no_data", {});
1447
+ return;
1448
+ }
1449
+ providerDiag("moonshot_video_upload_start", {
1450
+ mediaType: video.mediaType,
1451
+ bytes: Math.floor(video.data.length * 3 / 4)
1452
+ });
1453
+ video.fileId = await uploadOne(client, video, signal);
1454
+ providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
1455
+ }
1456
+ async function uploadOne(client, video, signal) {
1457
+ const bytes = Buffer.from(video.data, "base64");
1458
+ const mediaType = video.mediaType || "video/mp4";
1459
+ const filename = `upload.${extForMime(mediaType)}`;
1460
+ const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
1461
+ const uploaded = await client.files.create(
1462
+ { file, purpose: "video" },
1463
+ signal ? { signal } : void 0
1464
+ );
1465
+ return uploaded.id;
1466
+ }
1467
+ var MIME_TO_EXT = {
1468
+ "video/mp4": "mp4",
1469
+ "video/mpeg": "mpeg",
1470
+ "video/quicktime": "mov",
1471
+ "video/webm": "webm",
1472
+ "video/x-matroska": "mkv",
1473
+ "video/x-msvideo": "avi",
1474
+ "video/x-flv": "flv",
1475
+ "video/3gpp": "3gp"
1476
+ };
1477
+ function extForMime(mediaType) {
1478
+ return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
1479
+ }
1480
+
1360
1481
  // src/utils/env.ts
1361
1482
  function getEnvironment() {
1362
1483
  return globalThis.process?.env;
@@ -1386,7 +1507,8 @@ function createClient2(options) {
1386
1507
  return new OpenAI({
1387
1508
  apiKey: options.apiKey,
1388
1509
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1389
- ...options.fetch ? { fetch: options.fetch } : {}
1510
+ ...options.fetch ? { fetch: options.fetch } : {},
1511
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1390
1512
  });
1391
1513
  }
1392
1514
  function streamOpenAI(options) {
@@ -1399,6 +1521,13 @@ async function* runStream2(options) {
1399
1521
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1400
1522
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1401
1523
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1524
+ if (options.provider === "moonshot") {
1525
+ try {
1526
+ await uploadMoonshotVideos(client, downgradedMessages, options.signal);
1527
+ } catch (err) {
1528
+ throw toError2(err, providerName);
1529
+ }
1530
+ }
1402
1531
  const messages = toOpenAIMessages(downgradedMessages, {
1403
1532
  provider: options.provider,
1404
1533
  thinking: !!options.thinking,
@@ -1702,15 +1831,6 @@ function toError2(err, provider = "openai") {
1702
1831
  // src/providers/openai-codex.ts
1703
1832
  import os from "os";
1704
1833
 
1705
- // src/utils/diag.ts
1706
- var _diagFn = null;
1707
- function setProviderDiagnostic(fn) {
1708
- _diagFn = fn;
1709
- }
1710
- function providerDiag(phase, data) {
1711
- _diagFn?.(phase, data);
1712
- }
1713
-
1714
1834
  // src/utils/sse.ts
1715
1835
  function parseSseBuffer(buffer) {
1716
1836
  const events = [];
@@ -2359,6 +2479,13 @@ ${msg.content}` : msg.content;
2359
2479
  }
2360
2480
  }
2361
2481
  });
2482
+ if (typeof result.content !== "string") {
2483
+ for (const block of result.content) {
2484
+ if (block.type === "video") {
2485
+ parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
2486
+ }
2487
+ }
2488
+ }
2362
2489
  }
2363
2490
  if (parts.length > 0) contents.push({ role: "user", parts });
2364
2491
  }
@@ -2756,6 +2883,7 @@ var providerRegistry = new ProviderRegistryImpl();
2756
2883
 
2757
2884
  // src/stream.ts
2758
2885
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
2886
+ var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
2759
2887
  providerRegistry.register("anthropic", {
2760
2888
  stream: (options) => streamAnthropic(options)
2761
2889
  });
@@ -2784,10 +2912,11 @@ providerRegistry.register("glm", {
2784
2912
  })
2785
2913
  });
2786
2914
  providerRegistry.register("moonshot", {
2787
- stream: (options) => streamOpenAI({
2788
- ...options,
2789
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2790
- })
2915
+ stream: (options) => {
2916
+ const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
2917
+ const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
2918
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
2919
+ }
2791
2920
  });
2792
2921
  providerRegistry.register("deepseek", {
2793
2922
  stream: (options) => streamOpenAI({
@@ -2820,8 +2949,23 @@ function stream(options) {
2820
2949
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2821
2950
  );
2822
2951
  }
2952
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
2953
+ throw new VideoUnsupportedError();
2954
+ }
2823
2955
  return entry.stream(options);
2824
2956
  }
2957
+ function messagesContainVideo(messages) {
2958
+ for (const msg of messages) {
2959
+ if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
2960
+ for (const part of msg.content) {
2961
+ if (part.type === "video") return true;
2962
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
2963
+ if (part.content.some((block) => block.type === "video")) return true;
2964
+ }
2965
+ }
2966
+ }
2967
+ return false;
2968
+ }
2825
2969
 
2826
2970
  // src/error-classification.ts
2827
2971
  var CONTEXT_OVERFLOW_PATTERNS = [