@kenkaiiii/gg-ai 5.68.0 → 5.69.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
@@ -35,14 +35,17 @@ __export(index_exports, {
35
35
  ProviderError: () => ProviderError,
36
36
  REDACTION_MARKER: () => REDACTED,
37
37
  StreamResult: () => StreamResult,
38
+ TOOL_CALL_NAME_RULES: () => TOOL_CALL_NAME_RULES,
38
39
  clampProviderContextImages: () => clampProviderContextImages,
39
40
  classifyProviderError: () => classifyProviderError,
41
+ dropInvalidToolCalls: () => dropInvalidToolCalls,
40
42
  environmentSecrets: () => environmentSecrets,
41
43
  formatError: () => formatError,
42
44
  formatErrorForDisplay: () => formatErrorForDisplay,
43
45
  hasLoneSurrogate: () => hasLoneSurrogate,
44
46
  isHardBillingMessage: () => isHardBillingMessage,
45
47
  isUsageLimitError: () => isUsageLimitError,
48
+ isValidToolCallName: () => isValidToolCallName,
46
49
  localWireModelId: () => localWireModelId,
47
50
  palsuAssistantMessage: () => palsuAssistantMessage,
48
51
  palsuText: () => palsuText,
@@ -61,7 +64,8 @@ __export(index_exports, {
61
64
  stream: () => stream,
62
65
  toAnthropicMessages: () => toAnthropicMessages,
63
66
  toOpenAIMessages: () => toOpenAIMessages,
64
- toWellFormedText: () => toWellFormedText
67
+ toWellFormedText: () => toWellFormedText,
68
+ toolCallNameRuleFor: () => toolCallNameRuleFor
65
69
  });
66
70
  module.exports = __toCommonJS(index_exports);
67
71
 
@@ -515,7 +519,11 @@ function zodToJsonSchema(schema) {
515
519
  return normalized;
516
520
  }
517
521
  function resolveToolSchema(tool) {
518
- return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
522
+ const schema = tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
523
+ if (schema.type === "object" && schema.properties === void 0) {
524
+ return { ...schema, properties: {} };
525
+ }
526
+ return schema;
519
527
  }
520
528
  function normalizeRootForAnthropic(schema) {
521
529
  const branches = schema.oneOf ?? schema.anyOf;
@@ -722,6 +730,10 @@ var ANTHROPIC_INPUT_BLOCK_TYPES = /* @__PURE__ */ new Set([
722
730
  "connector_text",
723
731
  "container_upload",
724
732
  "document",
733
+ // Server-side refusal fallback marker. Only reaches the wire when the request
734
+ // carries the server-side-fallback beta; otherwise applyServerFallbackReplay
735
+ // strips it first (see toAnthropicMessages' `fallbackBlocks` option).
736
+ "fallback",
725
737
  "image",
726
738
  "mid_conv_system",
727
739
  "redacted_thinking",
@@ -743,6 +755,42 @@ function isPositionSensitiveThinking(part) {
743
755
  if (part.type === "thinking") return hasValidThinkingSignature(part);
744
756
  return isRawThinking(part);
745
757
  }
758
+ function isServerFallbackBlock(part) {
759
+ return part.type === "raw" && part.data.type === "fallback";
760
+ }
761
+ function applyServerFallbackReplay(content, keepMarkers, droppedToolCallIds) {
762
+ let lastFallbackIdx = -1;
763
+ content.forEach((part, idx) => {
764
+ if (isServerFallbackBlock(part)) lastFallbackIdx = idx;
765
+ });
766
+ if (lastFallbackIdx === -1) return content;
767
+ const resultIds = /* @__PURE__ */ new Set();
768
+ for (const part of content) {
769
+ if (part.type === "server_tool_result") resultIds.add(part.toolUseId);
770
+ else if (part.type === "raw" && typeof part.data.tool_use_id === "string")
771
+ resultIds.add(part.data.tool_use_id);
772
+ }
773
+ const out = [];
774
+ content.forEach((part, idx) => {
775
+ if (isServerFallbackBlock(part)) {
776
+ if (keepMarkers) out.push(part);
777
+ return;
778
+ }
779
+ if (idx < lastFallbackIdx) {
780
+ if (part.type === "thinking" || isRawThinking(part)) return;
781
+ if (part.type === "raw" && part.data.type === "connector_text") return;
782
+ if (part.type === "tool_call") {
783
+ droppedToolCallIds?.add(part.id);
784
+ return;
785
+ }
786
+ if (part.type === "server_tool_call" && !resultIds.has(part.id)) return;
787
+ if (part.type === "raw" && part.data.type === "server_tool_use" && !resultIds.has(String(part.data.id)))
788
+ return;
789
+ }
790
+ out.push(part);
791
+ });
792
+ return out.every(isServerFallbackBlock) ? [] : out;
793
+ }
746
794
  function toAnthropicAssistantPart(part, idMap) {
747
795
  if (part.type === "text") return { type: "text", text: part.text };
748
796
  if (part.type === "thinking") {
@@ -810,10 +858,17 @@ function countContextImages(messages) {
810
858
  }
811
859
  return count;
812
860
  }
861
+ var IMAGE_DROP_BATCH = 30;
862
+ function providerImageDropCount(imageCount2, budget) {
863
+ const overflow = imageCount2 - budget;
864
+ if (overflow <= 0) return 0;
865
+ const batch = Math.max(1, Math.min(IMAGE_DROP_BATCH, Math.floor(budget / 3)));
866
+ return Math.min(imageCount2, Math.ceil(overflow / batch) * batch);
867
+ }
813
868
  function clampProviderContextImages(messages, provider, supportsImages) {
814
869
  if (supportsImages === false) return messages;
815
870
  const budget = PROVIDER_IMAGE_BUDGETS[provider] ?? 5;
816
- let remainingToRemove = countContextImages(messages) - budget;
871
+ let remainingToRemove = providerImageDropCount(countContextImages(messages), budget);
817
872
  if (remainingToRemove <= 0) return messages;
818
873
  return messages.map((message) => {
819
874
  if (message.role === "user" && Array.isArray(message.content)) {
@@ -847,6 +902,144 @@ function clampProviderContextImages(messages, provider, supportsImages) {
847
902
  return message;
848
903
  });
849
904
  }
905
+ var TOOL_CALL_NAME_RULES = {
906
+ // Anthropic Messages API: tool / tool_use name `^[a-zA-Z0-9_-]{1,128}$`.
907
+ anthropic: { id: "anthropic", maxLength: 128, pattern: /^[a-zA-Z0-9_-]{1,128}$/ },
908
+ // OpenAI Chat Completions: function name a-z A-Z 0-9 _ -, max length 64.
909
+ "openai-chat": { id: "openai-chat", maxLength: 64, pattern: /^[a-zA-Z0-9_-]{1,64}$/ },
910
+ // OpenAI Responses (Codex): same charset; replayed `input[N].name` over 128
911
+ // chars is rejected with `string_above_max_length`.
912
+ "openai-responses": {
913
+ id: "openai-responses",
914
+ maxLength: 128,
915
+ pattern: /^[a-zA-Z0-9_-]{1,128}$/
916
+ },
917
+ // Gemini FunctionDeclaration name: starts with a letter or underscore, then
918
+ // a-z A-Z 0-9 _ . : -, max length 128.
919
+ gemini: { id: "gemini", maxLength: 128, pattern: /^[a-zA-Z_][a-zA-Z0-9_.:-]{0,127}$/ },
920
+ // OpenAI-compatible third parties (GLM, Kimi, DeepSeek, OpenRouter, xAI,
921
+ // local servers, MiniMax, …) don't share one documented charset, so only
922
+ // reject what no declared tool name can contain: blank, >128 chars, or any
923
+ // whitespace / control character (the signature of invocation text).
924
+ generic: { id: "generic", maxLength: 128 }
925
+ };
926
+ var TOOL_NAME_FORBIDDEN_CHARS = /[\s\p{Cc}]/u;
927
+ function isValidToolCallName(name, rule) {
928
+ if (typeof name !== "string" || name.trim().length === 0) return false;
929
+ if (name.length > rule.maxLength) return false;
930
+ if (TOOL_NAME_FORBIDDEN_CHARS.test(name)) return false;
931
+ return rule.pattern ? rule.pattern.test(name) : true;
932
+ }
933
+ function isDefaultOrHost(baseUrl, host) {
934
+ if (!baseUrl) return true;
935
+ try {
936
+ return new URL(baseUrl).hostname === host;
937
+ } catch {
938
+ return false;
939
+ }
940
+ }
941
+ function toolCallNameRuleFor(provider, options) {
942
+ switch (provider) {
943
+ case "anthropic":
944
+ return TOOL_CALL_NAME_RULES.anthropic;
945
+ case "openai":
946
+ if (options?.accountId) return TOOL_CALL_NAME_RULES["openai-responses"];
947
+ return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-chat"] : TOOL_CALL_NAME_RULES.generic;
948
+ case "gemini":
949
+ return TOOL_CALL_NAME_RULES.gemini;
950
+ default:
951
+ return TOOL_CALL_NAME_RULES.generic;
952
+ }
953
+ }
954
+ function isValidToolCallId(id) {
955
+ return typeof id === "string" && id.trim().length > 0;
956
+ }
957
+ function isRawReasoning(part) {
958
+ return part.type === "raw" && part.data.type === "reasoning";
959
+ }
960
+ function isDanglingReasoning(parts, idx) {
961
+ for (let i = idx + 1; i < parts.length; i++) {
962
+ const next = parts[i];
963
+ if (isRawReasoning(next)) return true;
964
+ if (next.type === "text" || next.type === "tool_call") return false;
965
+ }
966
+ return true;
967
+ }
968
+ function hasReplayableAssistantContent(parts) {
969
+ return parts.some((part) => {
970
+ if (part.type === "text") return part.text.length > 0;
971
+ if (part.type === "thinking") return false;
972
+ if (part.type === "raw") {
973
+ const t = part.data.type;
974
+ return !(t === "thinking" || t === "redacted_thinking" || t === "reasoning" || t === "fallback");
975
+ }
976
+ return true;
977
+ });
978
+ }
979
+ function dropInvalidToolCalls(messages, rule) {
980
+ const isInvalid = (part) => part.type === "tool_call" && (!isValidToolCallName(part.name, rule) || !isValidToolCallId(part.id));
981
+ const hasInvalid = messages.some(
982
+ (m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(isInvalid)
983
+ );
984
+ if (!hasInvalid) return messages;
985
+ const out = [];
986
+ let pending = /* @__PURE__ */ new Map();
987
+ let prunedAssistant = null;
988
+ for (const msg of messages) {
989
+ if (msg.role === "user" || msg.role === "system") {
990
+ if (msg.role === "user") pending = /* @__PURE__ */ new Map();
991
+ out.push(msg);
992
+ continue;
993
+ }
994
+ if (msg.role === "assistant") {
995
+ pending = /* @__PURE__ */ new Map();
996
+ if (typeof msg.content === "string") {
997
+ out.push(msg);
998
+ continue;
999
+ }
1000
+ const original = msg.content;
1001
+ let dropped = false;
1002
+ const kept = [];
1003
+ for (const part of original) {
1004
+ if (part.type === "tool_call") {
1005
+ const keep = !isInvalid(part);
1006
+ const queue = pending.get(part.id) ?? [];
1007
+ queue.push(keep);
1008
+ pending.set(part.id, queue);
1009
+ if (!keep) {
1010
+ dropped = true;
1011
+ continue;
1012
+ }
1013
+ }
1014
+ kept.push(part);
1015
+ }
1016
+ if (!dropped) {
1017
+ out.push(msg);
1018
+ continue;
1019
+ }
1020
+ const content = kept.filter(
1021
+ (part, idx) => !isRawReasoning(part) || !isDanglingReasoning(kept, idx) || isDanglingReasoning(original, original.indexOf(part))
1022
+ );
1023
+ if (hasReplayableAssistantContent(content)) {
1024
+ const pruned = { ...msg, content };
1025
+ out.push(pruned);
1026
+ prunedAssistant = pruned;
1027
+ }
1028
+ continue;
1029
+ }
1030
+ let changed = false;
1031
+ const results = msg.content.filter((result) => {
1032
+ const queue = pending.get(result.toolCallId);
1033
+ const keep = queue && queue.length > 0 ? queue.shift() : true;
1034
+ if (!keep) changed = true;
1035
+ return keep;
1036
+ });
1037
+ if (!changed) out.push(msg);
1038
+ else if (results.length > 0) out.push({ ...msg, content: results });
1039
+ }
1040
+ if (prunedAssistant && out[out.length - 1] === prunedAssistant) out.pop();
1041
+ return out;
1042
+ }
850
1043
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
851
1044
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
852
1045
  var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
@@ -953,10 +1146,12 @@ function remapAnthropicToolCallId(id, idMap) {
953
1146
  idMap.set(id, mapped);
954
1147
  return mapped;
955
1148
  }
956
- function toAnthropicMessages(messages, cacheControl) {
1149
+ function toAnthropicMessages(messages, cacheControl, options) {
957
1150
  let systemText;
958
1151
  const out = [];
959
1152
  const idMap = /* @__PURE__ */ new Map();
1153
+ const keepFallbackBlocks = options?.fallbackBlocks === true;
1154
+ const droppedToolCallIds = /* @__PURE__ */ new Set();
960
1155
  const trajectoryStartIdx = messages.reduce(
961
1156
  (last, m, i) => m.role === "user" ? i : last,
962
1157
  -1
@@ -1002,17 +1197,23 @@ function toAnthropicMessages(messages, cacheControl) {
1002
1197
  }
1003
1198
  if (msg.role === "assistant") {
1004
1199
  if (typeof msg.content === "string" && msg.content === "") continue;
1005
- const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx > trajectoryStartIdx, idMap);
1200
+ const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(
1201
+ applyServerFallbackReplay(msg.content, keepFallbackBlocks, droppedToolCallIds),
1202
+ msgIdx > trajectoryStartIdx,
1203
+ idMap
1204
+ );
1006
1205
  if (Array.isArray(content) && content.length === 0) continue;
1007
1206
  out.push({ role: "assistant", content });
1008
1207
  continue;
1009
1208
  }
1010
1209
  if (msg.role === "tool") {
1210
+ const results = droppedToolCallIds.size ? msg.content.filter((r) => !droppedToolCallIds.has(r.toolCallId)) : msg.content;
1211
+ if (results.length === 0) continue;
1011
1212
  out.push({
1012
1213
  role: "user",
1013
1214
  // Cast covers the video block (used by the Anthropic-compatible MiniMax
1014
1215
  // API), which isn't in the first-party Anthropic tool_result types.
1015
- content: msg.content.map((result) => ({
1216
+ content: results.map((result) => ({
1016
1217
  type: "tool_result",
1017
1218
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
1018
1219
  content: toAnthropicToolResultContent(result.content),
@@ -1352,6 +1553,48 @@ function fineGrainedToolStreamingEnabled() {
1352
1553
  const v = raw.trim().toLowerCase();
1353
1554
  return v === "1" || v === "true" || v === "yes" || v === "on";
1354
1555
  }
1556
+ var SERVER_FALLBACK_BETA = "server-side-fallback-2026-07-01";
1557
+ var anthropicServerFallback = {
1558
+ disabled: /* @__PURE__ */ new Set(),
1559
+ reset() {
1560
+ this.disabled.clear();
1561
+ }
1562
+ };
1563
+ function serverFallbackKey(options) {
1564
+ const auth = options.apiKey?.startsWith("sk-ant-oat") ? "oauth" : "key";
1565
+ return `${options.baseUrl ?? process.env.ANTHROPIC_BASE_URL ?? ""}|${auth}`;
1566
+ }
1567
+ function isDirectAnthropicApi(baseUrl) {
1568
+ const effective = baseUrl ?? process.env.ANTHROPIC_BASE_URL;
1569
+ if (!effective) return true;
1570
+ try {
1571
+ const url = new URL(effective);
1572
+ return url.protocol === "https:" && url.hostname === "api.anthropic.com";
1573
+ } catch {
1574
+ return false;
1575
+ }
1576
+ }
1577
+ function isServerFallbackRejection(err) {
1578
+ const status = err?.status;
1579
+ if (status !== 400) return false;
1580
+ const message = err instanceof Error ? err.message : String(err);
1581
+ return /fallbacks|server-side-fallback/i.test(message);
1582
+ }
1583
+ function sumUsageIterations(usage) {
1584
+ const iterations = usage?.iterations;
1585
+ if (!Array.isArray(iterations) || iterations.length === 0) return null;
1586
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1587
+ const totals = { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0 };
1588
+ for (const it of iterations) {
1589
+ if (!it || typeof it !== "object") continue;
1590
+ const rec = it;
1591
+ totals.inputTokens += num(rec.input_tokens);
1592
+ totals.outputTokens += num(rec.output_tokens);
1593
+ totals.cacheRead += num(rec.cache_read_input_tokens);
1594
+ totals.cacheWrite += num(rec.cache_creation_input_tokens);
1595
+ }
1596
+ return totals;
1597
+ }
1355
1598
  function createClient(options) {
1356
1599
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
1357
1600
  const userAgent = isOAuth ? options.userAgent ?? "claude-cli/2.1.75 (external, cli)" : "";
@@ -1448,7 +1691,11 @@ async function* runStream(options) {
1448
1691
  const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
1449
1692
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1450
1693
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1451
- const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
1694
+ const fallbackKey = serverFallbackKey(options);
1695
+ const useServerFallback = options.provider !== "minimax" && isDirectAnthropicApi(options.baseUrl) && !anthropicServerFallback.disabled.has(fallbackKey);
1696
+ const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl, {
1697
+ fallbackBlocks: useServerFallback
1698
+ });
1452
1699
  const system = isOAuth ? [
1453
1700
  {
1454
1701
  type: "text",
@@ -1518,6 +1765,7 @@ async function* runStream(options) {
1518
1765
  ];
1519
1766
  return contextEdits.length ? { context_management: { edits: contextEdits } } : {};
1520
1767
  })(),
1768
+ ...useServerFallback ? { fallbacks: "default" } : {},
1521
1769
  stream: useStreaming
1522
1770
  };
1523
1771
  const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
@@ -1534,20 +1782,41 @@ async function* runStream(options) {
1534
1782
  // it Anthropic silently ignores ttl:"1h" and falls back to the 5-min default,
1535
1783
  // so a pre-warmed cache expires before the user's first turn. cacheControl.ttl
1536
1784
  // is only "1h" on the first-party endpoint (see toAnthropicCacheControl).
1537
- ...cacheControl?.ttl === "1h" ? ["extended-cache-ttl-2025-04-11"] : []
1785
+ ...cacheControl?.ttl === "1h" ? ["extended-cache-ttl-2025-04-11"] : [],
1786
+ ...useServerFallback ? [SERVER_FALLBACK_BETA] : []
1538
1787
  ];
1539
- const requestOptions = {
1788
+ const toRequestOptions = (betas) => ({
1540
1789
  signal: options.signal ?? void 0,
1541
- ...betaHeaders.length ? { headers: { "anthropic-beta": betaHeaders.join(",") } } : {}
1790
+ ...betas.length ? { headers: { "anthropic-beta": betas.join(",") } } : {}
1791
+ });
1792
+ const requestOptions = toRequestOptions(betaHeaders);
1793
+ const send = async (create) => {
1794
+ try {
1795
+ return await create(params, requestOptions);
1796
+ } catch (err) {
1797
+ if (!useServerFallback || !isServerFallbackRejection(err)) throw err;
1798
+ anthropicServerFallback.disabled.add(fallbackKey);
1799
+ const { fallbacks: _dropped, ...rest } = params;
1800
+ const retryParams = {
1801
+ ...rest,
1802
+ messages: toAnthropicMessages(downgradedMessages, cacheControl, { fallbackBlocks: false }).messages
1803
+ };
1804
+ return create(
1805
+ retryParams,
1806
+ toRequestOptions(betaHeaders.filter((b) => b !== SERVER_FALLBACK_BETA))
1807
+ );
1808
+ }
1542
1809
  };
1543
1810
  if (!useStreaming) {
1544
1811
  try {
1545
1812
  const nonStreamingClient = client.withOptions({
1546
1813
  timeout: NON_STREAMING_REQUEST_TIMEOUT_MS
1547
1814
  });
1548
- const message = await nonStreamingClient.messages.create(
1549
- { ...params, stream: false },
1550
- requestOptions
1815
+ const message = await send(
1816
+ (p, o) => nonStreamingClient.messages.create(
1817
+ { ...p, stream: false },
1818
+ o
1819
+ )
1551
1820
  );
1552
1821
  yield* synthesizeEventsFromMessage(message);
1553
1822
  return messageToResponse(message);
@@ -1565,9 +1834,8 @@ async function* runStream(options) {
1565
1834
  const keepalive = { type: "keepalive" };
1566
1835
  let receivedAnyEvent = false;
1567
1836
  try {
1568
- const stream2 = await client.messages.create(
1569
- params,
1570
- requestOptions
1837
+ const stream2 = await send(
1838
+ (p, o) => client.messages.create(p, o)
1571
1839
  );
1572
1840
  for await (const event of stream2) {
1573
1841
  receivedAnyEvent = true;
@@ -1745,6 +2013,13 @@ async function* runStream(options) {
1745
2013
  if (usage?.output_tokens != null) {
1746
2014
  outputTokens = usage.output_tokens;
1747
2015
  }
2016
+ const totals = sumUsageIterations(usage);
2017
+ if (totals) {
2018
+ inputTokens = totals.inputTokens;
2019
+ outputTokens = totals.outputTokens;
2020
+ cacheRead = totals.cacheRead;
2021
+ cacheWrite = totals.cacheWrite;
2022
+ }
1748
2023
  yield keepalive;
1749
2024
  break;
1750
2025
  }
@@ -1874,10 +2149,11 @@ function messageToResponse(message) {
1874
2149
  }
1875
2150
  }
1876
2151
  const usage = message.usage;
1877
- const inputTokens = usage.input_tokens ?? 0;
1878
- const outputTokens = usage.output_tokens ?? 0;
1879
- const cacheRead = usage.cache_read_input_tokens;
1880
- const cacheWrite = usage.cache_creation_input_tokens;
2152
+ const totals = sumUsageIterations(usage);
2153
+ const inputTokens = totals?.inputTokens ?? usage.input_tokens ?? 0;
2154
+ const outputTokens = totals?.outputTokens ?? usage.output_tokens ?? 0;
2155
+ const cacheRead = totals?.cacheRead ?? usage.cache_read_input_tokens;
2156
+ const cacheWrite = totals?.cacheWrite ?? usage.cache_creation_input_tokens;
1881
2157
  return {
1882
2158
  message: {
1883
2159
  role: "assistant",
@@ -3908,6 +4184,41 @@ function sanitizeMessagesForWire(messages) {
3908
4184
  return sanitized ?? messages;
3909
4185
  }
3910
4186
 
4187
+ // src/utils/context-observation.ts
4188
+ function imageCount(message) {
4189
+ if (!message || !Array.isArray(message.content)) return 0;
4190
+ if (message.role === "user")
4191
+ return message.content.filter((part) => part.type === "image").length;
4192
+ if (message.role !== "tool") return 0;
4193
+ return message.content.reduce(
4194
+ (count, result) => count + (Array.isArray(result.content) ? result.content.filter((part) => part.type === "image").length : 0),
4195
+ 0
4196
+ );
4197
+ }
4198
+ function observePreparedContext(before, after, tools) {
4199
+ let imagesBefore = 0;
4200
+ let imagesAfter = 0;
4201
+ let firstImageDropMessage = null;
4202
+ for (let index = 0; index < before.length; index++) {
4203
+ const oldCount = imageCount(before[index]);
4204
+ const newCount = imageCount(after[index]);
4205
+ imagesBefore += oldCount;
4206
+ imagesAfter += newCount;
4207
+ if (firstImageDropMessage === null && newCount < oldCount) firstImageDropMessage = index;
4208
+ }
4209
+ return {
4210
+ messages: after,
4211
+ tools: tools.map((tool) => ({
4212
+ name: tool.name,
4213
+ description: tool.description,
4214
+ parameters: resolveToolSchema(tool)
4215
+ })),
4216
+ imagesBefore,
4217
+ imagesAfter,
4218
+ firstImageDropMessage
4219
+ };
4220
+ }
4221
+
3911
4222
  // src/stream.ts
3912
4223
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
3913
4224
  var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
@@ -4050,11 +4361,27 @@ function stream(options) {
4050
4361
  throw new VideoUnsupportedError();
4051
4362
  }
4052
4363
  const wireMessages = stripMessageProvenance(options.messages);
4053
- const messages = clampProviderContextImages(
4054
- sanitizeMessagesForWire(wireMessages),
4055
- options.provider,
4056
- options.supportsImages
4364
+ const sanitized = sanitizeMessagesForWire(wireMessages);
4365
+ const replayable = dropInvalidToolCalls(
4366
+ sanitized,
4367
+ toolCallNameRuleFor(options.provider, {
4368
+ accountId: options.accountId,
4369
+ baseUrl: options.baseUrl
4370
+ })
4057
4371
  );
4372
+ const messages = clampProviderContextImages(replayable, options.provider, options.supportsImages);
4373
+ if (options.onContextPrepared) {
4374
+ try {
4375
+ options.onContextPrepared(
4376
+ observePreparedContext(
4377
+ replayable === sanitized ? wireMessages : replayable,
4378
+ messages,
4379
+ options.tools ?? []
4380
+ )
4381
+ );
4382
+ } catch {
4383
+ }
4384
+ }
4058
4385
  return entry.stream(messages === options.messages ? options : { ...options, messages });
4059
4386
  }
4060
4387
  function stripMessageProvenance(messages) {
@@ -4466,14 +4793,17 @@ function registerPalsuProvider(config) {
4466
4793
  ProviderError,
4467
4794
  REDACTION_MARKER,
4468
4795
  StreamResult,
4796
+ TOOL_CALL_NAME_RULES,
4469
4797
  clampProviderContextImages,
4470
4798
  classifyProviderError,
4799
+ dropInvalidToolCalls,
4471
4800
  environmentSecrets,
4472
4801
  formatError,
4473
4802
  formatErrorForDisplay,
4474
4803
  hasLoneSurrogate,
4475
4804
  isHardBillingMessage,
4476
4805
  isUsageLimitError,
4806
+ isValidToolCallName,
4477
4807
  localWireModelId,
4478
4808
  palsuAssistantMessage,
4479
4809
  palsuText,
@@ -4492,6 +4822,7 @@ function registerPalsuProvider(config) {
4492
4822
  stream,
4493
4823
  toAnthropicMessages,
4494
4824
  toOpenAIMessages,
4495
- toWellFormedText
4825
+ toWellFormedText,
4826
+ toolCallNameRuleFor
4496
4827
  });
4497
4828
  //# sourceMappingURL=index.cjs.map