@kenkaiiii/gg-ai 4.3.243 → 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
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  GGAIError: () => GGAIError,
35
35
  ProviderError: () => ProviderError,
36
36
  StreamResult: () => StreamResult,
37
+ classifyProviderError: () => classifyProviderError,
37
38
  formatError: () => formatError,
38
39
  formatErrorForDisplay: () => formatErrorForDisplay,
39
40
  isHardBillingMessage: () => isHardBillingMessage,
@@ -78,6 +79,12 @@ var GGAIError = class extends Error {
78
79
  this.hint = options?.hint;
79
80
  }
80
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
+ };
81
88
  var ProviderError = class extends GGAIError {
82
89
  provider;
83
90
  statusCode;
@@ -193,6 +200,14 @@ function finaliseBySource(source, message, requestId, hint) {
193
200
  guidance: hint ?? providerGuidance(void 0, message, void 0),
194
201
  ...requestId ? { requestId } : {}
195
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
+ };
196
211
  case "ggcoder":
197
212
  return {
198
213
  headline: "ggcoder hit an unexpected error.",
@@ -553,6 +568,10 @@ function toolResultImages(content) {
553
568
  if (typeof content === "string") return [];
554
569
  return content.filter((b) => b.type === "image");
555
570
  }
571
+ function toolResultVideos(content) {
572
+ if (typeof content === "string") return [];
573
+ return content.filter((b) => b.type === "video");
574
+ }
556
575
  function toAnthropicCacheControl(retention, baseUrl) {
557
576
  const resolved = retention ?? "short";
558
577
  if (resolved === "none") return void 0;
@@ -563,6 +582,12 @@ function toAnthropicToolResultContent(content) {
563
582
  if (typeof content === "string") return content;
564
583
  return content.map((block) => {
565
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
+ }
566
591
  return {
567
592
  type: "image",
568
593
  source: {
@@ -632,6 +657,8 @@ function toAnthropicMessages(messages, cacheControl) {
632
657
  if (msg.role === "tool") {
633
658
  out.push({
634
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.
635
662
  content: msg.content.map((result) => ({
636
663
  type: "tool_result",
637
664
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
@@ -772,11 +799,10 @@ function toOpenAIMessages(messages, options) {
772
799
  (part) => {
773
800
  if (part.type === "text") return { type: "text", text: part.text };
774
801
  if (part.type === "video") {
802
+ const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
775
803
  return {
776
804
  type: "video_url",
777
- video_url: {
778
- url: `data:${part.mediaType};base64,${part.data}`
779
- }
805
+ video_url: videoUrl
780
806
  };
781
807
  }
782
808
  return {
@@ -821,11 +847,27 @@ function toOpenAIMessages(messages, options) {
821
847
  continue;
822
848
  }
823
849
  if (msg.role === "tool") {
850
+ const isMoonshot = options?.provider === "moonshot";
824
851
  const imageBlocks = [];
825
852
  for (const result of msg.content) {
826
853
  const text = toolResultText(result.content);
827
854
  const images = toolResultImages(result.content);
855
+ const videos = isMoonshot ? toolResultVideos(result.content) : [];
828
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
+ }
829
871
  out.push({
830
872
  role: "tool",
831
873
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
@@ -1410,6 +1452,75 @@ function fnv1aHash(value) {
1410
1452
  return (hash >>> 0).toString(16).padStart(8, "0");
1411
1453
  }
1412
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
+
1413
1524
  // src/utils/env.ts
1414
1525
  function getEnvironment() {
1415
1526
  return globalThis.process?.env;
@@ -1439,7 +1550,8 @@ function createClient2(options) {
1439
1550
  return new import_openai.default({
1440
1551
  apiKey: options.apiKey,
1441
1552
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
1442
- ...options.fetch ? { fetch: options.fetch } : {}
1553
+ ...options.fetch ? { fetch: options.fetch } : {},
1554
+ ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
1443
1555
  });
1444
1556
  }
1445
1557
  function streamOpenAI(options) {
@@ -1452,6 +1564,13 @@ async function* runStream2(options) {
1452
1564
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1453
1565
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1454
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
+ }
1455
1574
  const messages = toOpenAIMessages(downgradedMessages, {
1456
1575
  provider: options.provider,
1457
1576
  thinking: !!options.thinking,
@@ -1755,15 +1874,6 @@ function toError2(err, provider = "openai") {
1755
1874
  // src/providers/openai-codex.ts
1756
1875
  var import_node_os = __toESM(require("os"), 1);
1757
1876
 
1758
- // src/utils/diag.ts
1759
- var _diagFn = null;
1760
- function setProviderDiagnostic(fn) {
1761
- _diagFn = fn;
1762
- }
1763
- function providerDiag(phase, data) {
1764
- _diagFn?.(phase, data);
1765
- }
1766
-
1767
1877
  // src/utils/sse.ts
1768
1878
  function parseSseBuffer(buffer) {
1769
1879
  const events = [];
@@ -2412,6 +2522,13 @@ ${msg.content}` : msg.content;
2412
2522
  }
2413
2523
  }
2414
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
+ }
2415
2532
  }
2416
2533
  if (parts.length > 0) contents.push({ role: "user", parts });
2417
2534
  }
@@ -2809,6 +2926,7 @@ var providerRegistry = new ProviderRegistryImpl();
2809
2926
 
2810
2927
  // src/stream.ts
2811
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"}`;
2812
2930
  providerRegistry.register("anthropic", {
2813
2931
  stream: (options) => streamAnthropic(options)
2814
2932
  });
@@ -2837,10 +2955,11 @@ providerRegistry.register("glm", {
2837
2955
  })
2838
2956
  });
2839
2957
  providerRegistry.register("moonshot", {
2840
- stream: (options) => streamOpenAI({
2841
- ...options,
2842
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
2843
- })
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
+ }
2844
2963
  });
2845
2964
  providerRegistry.register("deepseek", {
2846
2965
  stream: (options) => streamOpenAI({
@@ -2873,8 +2992,113 @@ function stream(options) {
2873
2992
  `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
2874
2993
  );
2875
2994
  }
2995
+ if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
2996
+ throw new VideoUnsupportedError();
2997
+ }
2876
2998
  return entry.stream(options);
2877
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
+ }
3012
+
3013
+ // src/error-classification.ts
3014
+ var CONTEXT_OVERFLOW_PATTERNS = [
3015
+ /context_length_exceeded/i,
3016
+ /context length exceeded/i,
3017
+ /context window/i,
3018
+ // OpenAI Codex / Responses
3019
+ /maximum context length/i,
3020
+ // OpenAI / OpenRouter / Mistral
3021
+ /prompt is too long/i,
3022
+ // Anthropic
3023
+ /request_too_large/i,
3024
+ // Anthropic HTTP 413
3025
+ /input is too long/i,
3026
+ // Bedrock
3027
+ /input token count.*exceeds the maximum/i,
3028
+ // Gemini
3029
+ /maximum prompt length/i,
3030
+ // xAI / Grok
3031
+ /reduce the length of the messages/i,
3032
+ // Groq
3033
+ /too large for model/i,
3034
+ // Mistral
3035
+ /token limit/i
3036
+ // generic
3037
+ ];
3038
+ var RATE_LIMIT_PATTERNS = [
3039
+ /rate[ _-]?limit/i,
3040
+ /\b429\b/,
3041
+ /too many requests/i,
3042
+ /tokens per minute/i,
3043
+ /requests per minute/i
3044
+ ];
3045
+ var PROVIDER_TRANSIENT_PATTERNS = [
3046
+ /\b5\d\d\b/,
3047
+ /api_error/i,
3048
+ /server_error/i,
3049
+ /internal server error/i,
3050
+ /bad gateway/i,
3051
+ /service unavailable/i,
3052
+ /gateway timeout/i,
3053
+ /overloaded/i,
3054
+ /\b529\b/
3055
+ ];
3056
+ var BILLING_PATTERNS = [
3057
+ /payment required/i,
3058
+ /\b402\b/,
3059
+ /quota_exceeded/i,
3060
+ // underscore variant not in isHardBillingMessage
3061
+ /credit balance/i
3062
+ ];
3063
+ var AUTH_PATTERNS = [
3064
+ /invalid[ _]api[ _]key/i,
3065
+ /unauthorized/i,
3066
+ /\b401\b/,
3067
+ /authentication[ _]failed/i,
3068
+ /please run \/login/i
3069
+ // Anthropic Claude Code-style hint
3070
+ ];
3071
+ function matchesAny(message, patterns) {
3072
+ return patterns.some((p) => p.test(message));
3073
+ }
3074
+ function classifyProviderError(message) {
3075
+ if (matchesAny(message, CONTEXT_OVERFLOW_PATTERNS)) {
3076
+ return `[context_overflow] Worker context window exceeded \u2014 the conversation is too large to continue. Recovery: call reset_worker(project) to wipe history, then re-prompt with the task. Re-prompting WITHOUT reset will fail the same way.
3077
+
3078
+ Original: ${message}`;
3079
+ }
3080
+ if (isHardBillingMessage(message) || matchesAny(message, BILLING_PATTERNS)) {
3081
+ return `[billing] Provider billing/quota issue. Recovery: surface to the user \u2014 they need to top up or switch providers. Do NOT retry.
3082
+
3083
+ Original: ${message}`;
3084
+ }
3085
+ if (matchesAny(message, AUTH_PATTERNS)) {
3086
+ return `[auth] Provider authentication failed. Recovery: surface to the user \u2014 they need to re-login. Do NOT retry.
3087
+
3088
+ Original: ${message}`;
3089
+ }
3090
+ if (matchesAny(message, RATE_LIMIT_PATTERNS)) {
3091
+ return `[rate_limited] Provider rate limit hit. Recovery: wait ~30s, then re-prompt the same worker (no reset needed).
3092
+
3093
+ Original: ${message}`;
3094
+ }
3095
+ if (matchesAny(message, PROVIDER_TRANSIENT_PATTERNS)) {
3096
+ return `[provider_transient] Provider server-side/transient error. Recovery: wait briefly, then re-prompt the same worker (no reset needed). If it keeps happening, switch models/providers or check provider status.
3097
+
3098
+ Original: ${message}`;
3099
+ }
3100
+ return message;
3101
+ }
2878
3102
 
2879
3103
  // src/providers/palsu.ts
2880
3104
  function palsuText(text) {
@@ -3033,6 +3257,7 @@ function registerPalsuProvider(config) {
3033
3257
  GGAIError,
3034
3258
  ProviderError,
3035
3259
  StreamResult,
3260
+ classifyProviderError,
3036
3261
  formatError,
3037
3262
  formatErrorForDisplay,
3038
3263
  isHardBillingMessage,