@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.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, and MiniMax 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,11 +793,27 @@ function toOpenAIMessages(messages, options) {
768
793
  continue;
769
794
  }
770
795
  if (msg.role === "tool") {
796
+ const isMoonshot = options?.provider === "moonshot";
771
797
  const imageBlocks = [];
772
798
  for (const result of msg.content) {
773
799
  const text = toolResultText(result.content);
774
800
  const images = toolResultImages(result.content);
801
+ const videos = isMoonshot ? toolResultVideos(result.content) : [];
775
802
  const hasText = text.length > 0;
803
+ if (videos.length > 0) {
804
+ const parts = [];
805
+ if (hasText) parts.push({ type: "text", text });
806
+ const videoParts = videos.map((v) => {
807
+ const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
808
+ return { type: "video_url", video_url: videoUrl };
809
+ });
810
+ out.push({
811
+ role: "tool",
812
+ tool_call_id: remapToolCallId(result.toolCallId, idMap),
813
+ content: [...parts, ...videoParts]
814
+ });
815
+ continue;
816
+ }
776
817
  out.push({
777
818
  role: "tool",
778
819
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
@@ -1357,6 +1398,75 @@ function fnv1aHash(value) {
1357
1398
  return (hash >>> 0).toString(16).padStart(8, "0");
1358
1399
  }
1359
1400
 
1401
+ // src/utils/diag.ts
1402
+ var _diagFn = null;
1403
+ function setProviderDiagnostic(fn) {
1404
+ _diagFn = fn;
1405
+ }
1406
+ function providerDiag(phase, data) {
1407
+ _diagFn?.(phase, data);
1408
+ }
1409
+
1410
+ // src/providers/moonshot-video.ts
1411
+ async function uploadMoonshotVideos(client, messages, signal) {
1412
+ for (const msg of messages) {
1413
+ if (typeof msg.content === "string") continue;
1414
+ for (const part of msg.content) {
1415
+ if (part.type === "video") {
1416
+ await ensureUploaded(client, part, signal);
1417
+ continue;
1418
+ }
1419
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
1420
+ for (const inner of part.content) {
1421
+ if (inner.type === "video") {
1422
+ await ensureUploaded(client, inner, signal);
1423
+ }
1424
+ }
1425
+ }
1426
+ }
1427
+ }
1428
+ }
1429
+ async function ensureUploaded(client, video, signal) {
1430
+ if (video.fileId) {
1431
+ providerDiag("moonshot_video_cached", { fileId: video.fileId });
1432
+ return;
1433
+ }
1434
+ if (!video.data) {
1435
+ providerDiag("moonshot_video_skipped_no_data", {});
1436
+ return;
1437
+ }
1438
+ providerDiag("moonshot_video_upload_start", {
1439
+ mediaType: video.mediaType,
1440
+ bytes: Math.floor(video.data.length * 3 / 4)
1441
+ });
1442
+ video.fileId = await uploadOne(client, video, signal);
1443
+ providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
1444
+ }
1445
+ async function uploadOne(client, video, signal) {
1446
+ const bytes = Buffer.from(video.data, "base64");
1447
+ const mediaType = video.mediaType || "video/mp4";
1448
+ const filename = `upload.${extForMime(mediaType)}`;
1449
+ const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
1450
+ const uploaded = await client.files.create(
1451
+ { file, purpose: "video" },
1452
+ signal ? { signal } : void 0
1453
+ );
1454
+ return uploaded.id;
1455
+ }
1456
+ var MIME_TO_EXT = {
1457
+ "video/mp4": "mp4",
1458
+ "video/mpeg": "mpeg",
1459
+ "video/quicktime": "mov",
1460
+ "video/webm": "webm",
1461
+ "video/x-matroska": "mkv",
1462
+ "video/x-msvideo": "avi",
1463
+ "video/x-flv": "flv",
1464
+ "video/3gpp": "3gp"
1465
+ };
1466
+ function extForMime(mediaType) {
1467
+ return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
1468
+ }
1469
+
1360
1470
  // src/utils/env.ts
1361
1471
  function getEnvironment() {
1362
1472
  return globalThis.process?.env;
@@ -1386,7 +1496,8 @@ function createClient2(options) {
1386
1496
  return new OpenAI({
1387
1497
  apiKey: options.apiKey,
1388
1498
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1389
- ...options.fetch ? { fetch: options.fetch } : {}
1499
+ ...options.fetch ? { fetch: options.fetch } : {},
1500
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1390
1501
  });
1391
1502
  }
1392
1503
  function streamOpenAI(options) {
@@ -1399,6 +1510,13 @@ async function* runStream2(options) {
1399
1510
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1400
1511
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1401
1512
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1513
+ if (options.provider === "moonshot") {
1514
+ try {
1515
+ await uploadMoonshotVideos(client, downgradedMessages, options.signal);
1516
+ } catch (err) {
1517
+ throw toError2(err, providerName);
1518
+ }
1519
+ }
1402
1520
  const messages = toOpenAIMessages(downgradedMessages, {
1403
1521
  provider: options.provider,
1404
1522
  thinking: !!options.thinking,
@@ -1702,15 +1820,6 @@ function toError2(err, provider = "openai") {
1702
1820
  // src/providers/openai-codex.ts
1703
1821
  import os from "os";
1704
1822
 
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
1823
  // src/utils/sse.ts
1715
1824
  function parseSseBuffer(buffer) {
1716
1825
  const events = [];
@@ -2359,6 +2468,13 @@ ${msg.content}` : msg.content;
2359
2468
  }
2360
2469
  }
2361
2470
  });
2471
+ if (typeof result.content !== "string") {
2472
+ for (const block of result.content) {
2473
+ if (block.type === "video") {
2474
+ parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
2475
+ }
2476
+ }
2477
+ }
2362
2478
  }
2363
2479
  if (parts.length > 0) contents.push({ role: "user", parts });
2364
2480
  }
@@ -2756,6 +2872,7 @@ var providerRegistry = new ProviderRegistryImpl();
2756
2872
 
2757
2873
  // src/stream.ts
2758
2874
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
2875
+ var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
2759
2876
  providerRegistry.register("anthropic", {
2760
2877
  stream: (options) => streamAnthropic(options)
2761
2878
  });
@@ -2784,10 +2901,11 @@ providerRegistry.register("glm", {
2784
2901
  })
2785
2902
  });
2786
2903
  providerRegistry.register("moonshot", {
2787
- stream: (options) => streamOpenAI({
2788
- ...options,
2789
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2790
- })
2904
+ stream: (options) => {
2905
+ const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
2906
+ const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
2907
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
2908
+ }
2791
2909
  });
2792
2910
  providerRegistry.register("deepseek", {
2793
2911
  stream: (options) => streamOpenAI({
@@ -2820,8 +2938,23 @@ function stream(options) {
2820
2938
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2821
2939
  );
2822
2940
  }
2941
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
2942
+ throw new VideoUnsupportedError();
2943
+ }
2823
2944
  return entry.stream(options);
2824
2945
  }
2946
+ function messagesContainVideo(messages) {
2947
+ for (const msg of messages) {
2948
+ if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
2949
+ for (const part of msg.content) {
2950
+ if (part.type === "video") return true;
2951
+ if (part.type === "tool_result" && Array.isArray(part.content)) {
2952
+ if (part.content.some((block) => block.type === "video")) return true;
2953
+ }
2954
+ }
2955
+ }
2956
+ return false;
2957
+ }
2825
2958
 
2826
2959
  // src/error-classification.ts
2827
2960
  var CONTEXT_OVERFLOW_PATTERNS = [