@kenkaiiii/gg-ai 4.5.0 → 4.6.1

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
@@ -205,7 +205,7 @@ function finaliseBySource(source, message, requestId, hint) {
205
205
  headline: message,
206
206
  source,
207
207
  message: "",
208
- guidance: hint ?? "Only Kimi, Gemini, and MiniMax can analyze video. Switch with /model.",
208
+ guidance: hint ?? "Only Kimi, Gemini, MiniMax, and MiMo-V2.5 can analyze video. Switch with /model.",
209
209
  ...requestId ? { requestId } : {}
210
210
  };
211
211
  case "ggcoder":
@@ -328,21 +328,21 @@ var StreamResult = class {
328
328
  resolveResponse;
329
329
  rejectResponse;
330
330
  resolveWait = null;
331
- constructor(generator) {
331
+ constructor(generator, signal) {
332
332
  this.response = new Promise((resolve, reject) => {
333
333
  this.resolveResponse = resolve;
334
334
  this.rejectResponse = reject;
335
335
  });
336
- this.pump(generator);
336
+ this.pump(generator, signal);
337
337
  }
338
- async pump(generator) {
338
+ async pump(generator, signal) {
339
339
  try {
340
- let next = await generator.next();
340
+ let next = await this._nextWithAbort(generator, signal);
341
341
  while (!next.done) {
342
342
  this.buffer.push(next.value);
343
343
  this.resolveWait?.();
344
344
  this.resolveWait = null;
345
- next = await generator.next();
345
+ next = await this._nextWithAbort(generator, signal);
346
346
  }
347
347
  this.done = true;
348
348
  this.resolveResponse(next.value);
@@ -357,6 +357,28 @@ var StreamResult = class {
357
357
  this.resolveWait = null;
358
358
  }
359
359
  }
360
+ async _nextWithAbort(generator, signal) {
361
+ if (!signal) {
362
+ return generator.next();
363
+ }
364
+ if (signal.aborted) {
365
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
366
+ }
367
+ let onAbort;
368
+ const abortPromise = new Promise((_, reject) => {
369
+ onAbort = () => {
370
+ generator.return?.(void 0).catch(() => {
371
+ });
372
+ reject(new DOMException("Aborted", "AbortError"));
373
+ };
374
+ signal.addEventListener("abort", onAbort, { once: true });
375
+ });
376
+ try {
377
+ return await Promise.race([generator.next(), abortPromise]);
378
+ } finally {
379
+ if (onAbort) signal.removeEventListener("abort", onAbort);
380
+ }
381
+ }
360
382
  async *[Symbol.asyncIterator]() {
361
383
  let index = 0;
362
384
  while (true) {
@@ -848,13 +870,14 @@ function toOpenAIMessages(messages, options) {
848
870
  }
849
871
  if (msg.role === "tool") {
850
872
  const isMoonshot = options?.provider === "moonshot";
851
- const imageBlocks = [];
873
+ const followUpMediaBlocks = [];
874
+ let followUpHasVideo = false;
852
875
  for (const result of msg.content) {
853
876
  const text = toolResultText(result.content);
854
877
  const images = toolResultImages(result.content);
855
- const videos = isMoonshot ? toolResultVideos(result.content) : [];
878
+ const videos = toolResultVideos(result.content);
856
879
  const hasText = text.length > 0;
857
- if (videos.length > 0) {
880
+ if (isMoonshot && videos.length > 0) {
858
881
  const parts = [];
859
882
  if (hasText) parts.push({ type: "text", text });
860
883
  const videoParts = videos.map((v) => {
@@ -871,21 +894,31 @@ function toOpenAIMessages(messages, options) {
871
894
  out.push({
872
895
  role: "tool",
873
896
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
874
- content: hasText ? text : "(see attached image)"
897
+ content: hasText ? text : "(see attached media)"
875
898
  });
876
899
  if (images.length > 0 && options?.supportsImages !== false) {
877
900
  for (const img of images) {
878
- imageBlocks.push({
901
+ followUpMediaBlocks.push({
879
902
  type: "image_url",
880
903
  image_url: { url: `data:${img.mediaType};base64,${img.data}` }
881
904
  });
882
905
  }
883
906
  }
907
+ if (!isMoonshot && videos.length > 0) {
908
+ for (const v of videos) {
909
+ followUpMediaBlocks.push({
910
+ type: "video_url",
911
+ video_url: { url: `data:${v.mediaType};base64,${v.data}` }
912
+ });
913
+ followUpHasVideo = true;
914
+ }
915
+ }
884
916
  }
885
- if (imageBlocks.length > 0) {
917
+ if (followUpMediaBlocks.length > 0) {
918
+ const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
886
919
  out.push({
887
920
  role: "user",
888
- content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
921
+ content: [{ type: "text", text: label }, ...followUpMediaBlocks]
889
922
  });
890
923
  }
891
924
  }
@@ -962,10 +995,10 @@ function createClient(options) {
962
995
  ...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
963
996
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
964
997
  ...options.fetch ? { fetch: options.fetch } : {},
965
- // Allow SDK retries for connection-level failures (socket hang up, 500s,
966
- // connection refused). Our stall detection handles abort-initiated retries
967
- // separately SDK retries only fire on genuine transport errors.
968
- maxRetries: 2,
998
+ // Disable SDK retries the agent loop has its own stall/overload retry
999
+ // logic that surfaces errors properly. SDK retries on 429s can cause
1000
+ // multi-minute hangs when the provider stops responding mid-retry.
1001
+ maxRetries: 0,
969
1002
  ...isOAuth ? {
970
1003
  defaultHeaders: {
971
1004
  // Anthropic's OAuth edge validates the claude-cli version. Callers
@@ -978,7 +1011,7 @@ function createClient(options) {
978
1011
  });
979
1012
  }
980
1013
  function streamAnthropic(options) {
981
- return new StreamResult(runStream(options));
1014
+ return new StreamResult(runStream(options), options.signal);
982
1015
  }
983
1016
  async function* runStream(options) {
984
1017
  const client = createClient(options);
@@ -1073,7 +1106,6 @@ async function* runStream(options) {
1073
1106
  throw toError(err);
1074
1107
  }
1075
1108
  }
1076
- const stream2 = client.messages.stream(params, requestOptions);
1077
1109
  const contentParts = [];
1078
1110
  const blocks = /* @__PURE__ */ new Map();
1079
1111
  let inputTokens = 0;
@@ -1082,8 +1114,14 @@ async function* runStream(options) {
1082
1114
  let cacheWrite;
1083
1115
  let stopReason = null;
1084
1116
  const keepalive = { type: "keepalive" };
1117
+ let receivedAnyEvent = false;
1085
1118
  try {
1119
+ const stream2 = await client.messages.create(
1120
+ params,
1121
+ requestOptions
1122
+ );
1086
1123
  for await (const event of stream2) {
1124
+ receivedAnyEvent = true;
1087
1125
  switch (event.type) {
1088
1126
  case "message_start": {
1089
1127
  const usage = event.message.usage;
@@ -1120,7 +1158,7 @@ async function* runStream(options) {
1120
1158
  accum.toolId = block.id;
1121
1159
  accum.toolName = block.name;
1122
1160
  accum.input = block.input;
1123
- } else if (block.type === "redacted_thinking") {
1161
+ } else if (block.type !== "text" && block.type !== "thinking") {
1124
1162
  accum.raw = block;
1125
1163
  }
1126
1164
  blocks.set(idx, accum);
@@ -1217,8 +1255,7 @@ async function* runStream(options) {
1217
1255
  contentParts.push({ type: "raw", data: accum.raw });
1218
1256
  yield keepalive;
1219
1257
  } else {
1220
- const msg = stream2.currentMessage;
1221
- const rawBlock = msg?.content[event.index];
1258
+ const rawBlock = accum.raw;
1222
1259
  if (rawBlock) {
1223
1260
  const blockType = rawBlock.type;
1224
1261
  if (blockType === "web_search_tool_result") {
@@ -1264,6 +1301,11 @@ async function* runStream(options) {
1264
1301
  } catch (err) {
1265
1302
  throw toError(err);
1266
1303
  }
1304
+ if (!receivedAnyEvent) {
1305
+ throw new ProviderError("anthropic", "Stream ended without producing any events.", {
1306
+ statusCode: 504
1307
+ });
1308
+ }
1267
1309
  const normalizedStop = normalizeAnthropicStopReason(stopReason);
1268
1310
  const response = {
1269
1311
  message: {
@@ -1555,7 +1597,7 @@ function createClient2(options) {
1555
1597
  });
1556
1598
  }
1557
1599
  function streamOpenAI(options) {
1558
- return new StreamResult(runStream2(options));
1600
+ return new StreamResult(runStream2(options), options.signal);
1559
1601
  }
1560
1602
  async function* runStream2(options) {
1561
1603
  const providerName = options.provider ?? "openai";
@@ -1647,51 +1689,62 @@ async function* runStream2(options) {
1647
1689
  let outputTokens = 0;
1648
1690
  let cacheRead = 0;
1649
1691
  let finishReason = null;
1650
- for await (const chunk of stream2) {
1651
- const choice = chunk.choices?.[0];
1652
- if (chunk.usage) {
1653
- ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1654
- }
1655
- if (!choice) continue;
1656
- if (choice.finish_reason) {
1657
- finishReason = choice.finish_reason;
1658
- }
1659
- const delta = choice.delta;
1660
- const reasoningContent = delta.reasoning_content;
1661
- if (typeof reasoningContent === "string" && reasoningContent) {
1662
- thinkingAccum += reasoningContent;
1663
- if (options.thinking) {
1664
- yield { type: "thinking_delta", text: reasoningContent };
1692
+ let receivedAnyChunk = false;
1693
+ try {
1694
+ for await (const chunk of stream2) {
1695
+ receivedAnyChunk = true;
1696
+ const choice = chunk.choices?.[0];
1697
+ if (chunk.usage) {
1698
+ ({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
1665
1699
  }
1666
- }
1667
- if (delta.content) {
1668
- textAccum += delta.content;
1669
- yield { type: "text_delta", text: delta.content };
1670
- }
1671
- if (delta.tool_calls) {
1672
- for (const tc of delta.tool_calls) {
1673
- let accum = toolCallAccum.get(tc.index);
1674
- if (!accum) {
1675
- accum = {
1676
- id: tc.id ?? "",
1677
- name: tc.function?.name ?? "",
1678
- argsJson: ""
1679
- };
1680
- toolCallAccum.set(tc.index, accum);
1700
+ if (!choice) continue;
1701
+ if (choice.finish_reason) {
1702
+ finishReason = choice.finish_reason;
1703
+ }
1704
+ const delta = choice.delta;
1705
+ const reasoningContent = delta.reasoning_content;
1706
+ if (typeof reasoningContent === "string" && reasoningContent) {
1707
+ thinkingAccum += reasoningContent;
1708
+ if (options.thinking) {
1709
+ yield { type: "thinking_delta", text: reasoningContent };
1681
1710
  }
1682
- if (tc.id) accum.id = tc.id;
1683
- if (tc.function?.name) accum.name = tc.function.name;
1684
- if (tc.function?.arguments) {
1685
- accum.argsJson += tc.function.arguments;
1686
- yield {
1687
- type: "toolcall_delta",
1688
- id: accum.id,
1689
- name: accum.name,
1690
- argsJson: tc.function.arguments
1691
- };
1711
+ }
1712
+ if (delta.content) {
1713
+ textAccum += delta.content;
1714
+ yield { type: "text_delta", text: delta.content };
1715
+ }
1716
+ if (delta.tool_calls) {
1717
+ for (const tc of delta.tool_calls) {
1718
+ let accum = toolCallAccum.get(tc.index);
1719
+ if (!accum) {
1720
+ accum = {
1721
+ id: tc.id ?? "",
1722
+ name: tc.function?.name ?? "",
1723
+ argsJson: ""
1724
+ };
1725
+ toolCallAccum.set(tc.index, accum);
1726
+ }
1727
+ if (tc.id) accum.id = tc.id;
1728
+ if (tc.function?.name) accum.name = tc.function.name;
1729
+ if (tc.function?.arguments) {
1730
+ accum.argsJson += tc.function.arguments;
1731
+ yield {
1732
+ type: "toolcall_delta",
1733
+ id: accum.id,
1734
+ name: accum.name,
1735
+ argsJson: tc.function.arguments
1736
+ };
1737
+ }
1692
1738
  }
1693
1739
  }
1694
1740
  }
1741
+ } catch (err) {
1742
+ throw toError2(err, providerName);
1743
+ }
1744
+ if (!receivedAnyChunk) {
1745
+ throw new ProviderError(providerName, "Stream ended without producing any chunks.", {
1746
+ statusCode: 504
1747
+ });
1695
1748
  }
1696
1749
  if (thinkingAccum) {
1697
1750
  contentParts.push({ type: "thinking", text: thinkingAccum });
@@ -1934,7 +1987,7 @@ function isVisibleOutputItem(itemType) {
1934
1987
  return itemType === "message";
1935
1988
  }
1936
1989
  function streamOpenAICodex(options) {
1937
- return new StreamResult(runStream3(options));
1990
+ return new StreamResult(runStream3(options), options.signal);
1938
1991
  }
1939
1992
  async function* runStream3(options) {
1940
1993
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -2264,10 +2317,16 @@ async function* parseSSE(body) {
2264
2317
  }
2265
2318
  }
2266
2319
  function remapCodexId(id, idMap) {
2267
- if (id.startsWith("fc_") || id.startsWith("fc-")) return id;
2268
2320
  const existing = idMap.get(id);
2269
2321
  if (existing) return existing;
2270
- const mapped = `fc_${id.replace(/^toolu_/, "")}`;
2322
+ const withPrefix = id.startsWith("fc_") || id.startsWith("fc-") ? id : `fc_${id.replace(/^toolu_/, "")}`;
2323
+ const sanitized = withPrefix.replace(/[^A-Za-z0-9_-]/g, "_");
2324
+ let mapped = sanitized;
2325
+ let suffix = 2;
2326
+ const used = new Set(idMap.values());
2327
+ while (used.has(mapped)) {
2328
+ mapped = `${sanitized}_${suffix++}`;
2329
+ }
2271
2330
  idMap.set(id, mapped);
2272
2331
  return mapped;
2273
2332
  }
@@ -2790,7 +2849,7 @@ async function fetchCodeAssistWithRetry(plan, options) {
2790
2849
  throw lastError ?? new ProviderError("gemini", "Gemini Code Assist request failed.");
2791
2850
  }
2792
2851
  function streamGemini(options) {
2793
- return new StreamResult(runStream4(options));
2852
+ return new StreamResult(runStream4(options), options.signal);
2794
2853
  }
2795
2854
  async function* runStream4(options) {
2796
2855
  const useStreaming = options.streaming !== false;
@@ -3246,7 +3305,7 @@ function registerPalsuProvider(config) {
3246
3305
  const stopReason = explicitStop ?? (hasToolCalls ? "tool_use" : "end_turn");
3247
3306
  return yield* simulateStream(message, stopReason, options.signal, cacheUsage);
3248
3307
  })();
3249
- return new StreamResult(gen);
3308
+ return new StreamResult(gen, options.signal);
3250
3309
  }
3251
3310
  });
3252
3311
  return handle;