@prestyj/ai 5.28.1 → 5.29.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.js CHANGED
@@ -448,7 +448,11 @@ function zodToJsonSchema(schema) {
448
448
  return normalized;
449
449
  }
450
450
  function resolveToolSchema(tool) {
451
- return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
451
+ const schema = tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
452
+ if (schema.type === "object" && schema.properties === void 0) {
453
+ return { ...schema, properties: {} };
454
+ }
455
+ return schema;
452
456
  }
453
457
  function normalizeRootForAnthropic(schema) {
454
458
  const branches = schema.oneOf ?? schema.anyOf;
@@ -655,6 +659,10 @@ var ANTHROPIC_INPUT_BLOCK_TYPES = /* @__PURE__ */ new Set([
655
659
  "connector_text",
656
660
  "container_upload",
657
661
  "document",
662
+ // Server-side refusal fallback marker. Only reaches the wire when the request
663
+ // carries the server-side-fallback beta; otherwise applyServerFallbackReplay
664
+ // strips it first (see toAnthropicMessages' `fallbackBlocks` option).
665
+ "fallback",
658
666
  "image",
659
667
  "mid_conv_system",
660
668
  "redacted_thinking",
@@ -676,6 +684,42 @@ function isPositionSensitiveThinking(part) {
676
684
  if (part.type === "thinking") return hasValidThinkingSignature(part);
677
685
  return isRawThinking(part);
678
686
  }
687
+ function isServerFallbackBlock(part) {
688
+ return part.type === "raw" && part.data.type === "fallback";
689
+ }
690
+ function applyServerFallbackReplay(content, keepMarkers, droppedToolCallIds) {
691
+ let lastFallbackIdx = -1;
692
+ content.forEach((part, idx) => {
693
+ if (isServerFallbackBlock(part)) lastFallbackIdx = idx;
694
+ });
695
+ if (lastFallbackIdx === -1) return content;
696
+ const resultIds = /* @__PURE__ */ new Set();
697
+ for (const part of content) {
698
+ if (part.type === "server_tool_result") resultIds.add(part.toolUseId);
699
+ else if (part.type === "raw" && typeof part.data.tool_use_id === "string")
700
+ resultIds.add(part.data.tool_use_id);
701
+ }
702
+ const out = [];
703
+ content.forEach((part, idx) => {
704
+ if (isServerFallbackBlock(part)) {
705
+ if (keepMarkers) out.push(part);
706
+ return;
707
+ }
708
+ if (idx < lastFallbackIdx) {
709
+ if (part.type === "thinking" || isRawThinking(part)) return;
710
+ if (part.type === "raw" && part.data.type === "connector_text") return;
711
+ if (part.type === "tool_call") {
712
+ droppedToolCallIds?.add(part.id);
713
+ return;
714
+ }
715
+ if (part.type === "server_tool_call" && !resultIds.has(part.id)) return;
716
+ if (part.type === "raw" && part.data.type === "server_tool_use" && !resultIds.has(String(part.data.id)))
717
+ return;
718
+ }
719
+ out.push(part);
720
+ });
721
+ return out.every(isServerFallbackBlock) ? [] : out;
722
+ }
679
723
  function toAnthropicAssistantPart(part, idMap) {
680
724
  if (part.type === "text") return { type: "text", text: part.text };
681
725
  if (part.type === "thinking") {
@@ -743,10 +787,17 @@ function countContextImages(messages) {
743
787
  }
744
788
  return count;
745
789
  }
790
+ var IMAGE_DROP_BATCH = 30;
791
+ function providerImageDropCount(imageCount2, budget) {
792
+ const overflow = imageCount2 - budget;
793
+ if (overflow <= 0) return 0;
794
+ const batch = Math.max(1, Math.min(IMAGE_DROP_BATCH, Math.floor(budget / 3)));
795
+ return Math.min(imageCount2, Math.ceil(overflow / batch) * batch);
796
+ }
746
797
  function clampProviderContextImages(messages, provider, supportsImages) {
747
798
  if (supportsImages === false) return messages;
748
799
  const budget = PROVIDER_IMAGE_BUDGETS[provider] ?? 5;
749
- let remainingToRemove = countContextImages(messages) - budget;
800
+ let remainingToRemove = providerImageDropCount(countContextImages(messages), budget);
750
801
  if (remainingToRemove <= 0) return messages;
751
802
  return messages.map((message) => {
752
803
  if (message.role === "user" && Array.isArray(message.content)) {
@@ -780,6 +831,144 @@ function clampProviderContextImages(messages, provider, supportsImages) {
780
831
  return message;
781
832
  });
782
833
  }
834
+ var TOOL_CALL_NAME_RULES = {
835
+ // Anthropic Messages API: tool / tool_use name `^[a-zA-Z0-9_-]{1,128}$`.
836
+ anthropic: { id: "anthropic", maxLength: 128, pattern: /^[a-zA-Z0-9_-]{1,128}$/ },
837
+ // OpenAI Chat Completions: function name a-z A-Z 0-9 _ -, max length 64.
838
+ "openai-chat": { id: "openai-chat", maxLength: 64, pattern: /^[a-zA-Z0-9_-]{1,64}$/ },
839
+ // OpenAI Responses (Codex): same charset; replayed `input[N].name` over 128
840
+ // chars is rejected with `string_above_max_length`.
841
+ "openai-responses": {
842
+ id: "openai-responses",
843
+ maxLength: 128,
844
+ pattern: /^[a-zA-Z0-9_-]{1,128}$/
845
+ },
846
+ // Gemini FunctionDeclaration name: starts with a letter or underscore, then
847
+ // a-z A-Z 0-9 _ . : -, max length 128.
848
+ gemini: { id: "gemini", maxLength: 128, pattern: /^[a-zA-Z_][a-zA-Z0-9_.:-]{0,127}$/ },
849
+ // OpenAI-compatible third parties (GLM, Kimi, DeepSeek, OpenRouter, xAI,
850
+ // local servers, MiniMax, …) don't share one documented charset, so only
851
+ // reject what no declared tool name can contain: blank, >128 chars, or any
852
+ // whitespace / control character (the signature of invocation text).
853
+ generic: { id: "generic", maxLength: 128 }
854
+ };
855
+ var TOOL_NAME_FORBIDDEN_CHARS = /[\s\p{Cc}]/u;
856
+ function isValidToolCallName(name, rule) {
857
+ if (typeof name !== "string" || name.trim().length === 0) return false;
858
+ if (name.length > rule.maxLength) return false;
859
+ if (TOOL_NAME_FORBIDDEN_CHARS.test(name)) return false;
860
+ return rule.pattern ? rule.pattern.test(name) : true;
861
+ }
862
+ function isDefaultOrHost(baseUrl, host) {
863
+ if (!baseUrl) return true;
864
+ try {
865
+ return new URL(baseUrl).hostname === host;
866
+ } catch {
867
+ return false;
868
+ }
869
+ }
870
+ function toolCallNameRuleFor(provider, options) {
871
+ switch (provider) {
872
+ case "anthropic":
873
+ return TOOL_CALL_NAME_RULES.anthropic;
874
+ case "openai":
875
+ if (options?.accountId) return TOOL_CALL_NAME_RULES["openai-responses"];
876
+ return isDefaultOrHost(options?.baseUrl, "api.openai.com") ? TOOL_CALL_NAME_RULES["openai-chat"] : TOOL_CALL_NAME_RULES.generic;
877
+ case "gemini":
878
+ return TOOL_CALL_NAME_RULES.gemini;
879
+ default:
880
+ return TOOL_CALL_NAME_RULES.generic;
881
+ }
882
+ }
883
+ function isValidToolCallId(id) {
884
+ return typeof id === "string" && id.trim().length > 0;
885
+ }
886
+ function isRawReasoning(part) {
887
+ return part.type === "raw" && part.data.type === "reasoning";
888
+ }
889
+ function isDanglingReasoning(parts, idx) {
890
+ for (let i = idx + 1; i < parts.length; i++) {
891
+ const next = parts[i];
892
+ if (isRawReasoning(next)) return true;
893
+ if (next.type === "text" || next.type === "tool_call") return false;
894
+ }
895
+ return true;
896
+ }
897
+ function hasReplayableAssistantContent(parts) {
898
+ return parts.some((part) => {
899
+ if (part.type === "text") return part.text.length > 0;
900
+ if (part.type === "thinking") return false;
901
+ if (part.type === "raw") {
902
+ const t = part.data.type;
903
+ return !(t === "thinking" || t === "redacted_thinking" || t === "reasoning" || t === "fallback");
904
+ }
905
+ return true;
906
+ });
907
+ }
908
+ function dropInvalidToolCalls(messages, rule) {
909
+ const isInvalid = (part) => part.type === "tool_call" && (!isValidToolCallName(part.name, rule) || !isValidToolCallId(part.id));
910
+ const hasInvalid = messages.some(
911
+ (m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(isInvalid)
912
+ );
913
+ if (!hasInvalid) return messages;
914
+ const out = [];
915
+ let pending = /* @__PURE__ */ new Map();
916
+ let prunedAssistant = null;
917
+ for (const msg of messages) {
918
+ if (msg.role === "user" || msg.role === "system") {
919
+ if (msg.role === "user") pending = /* @__PURE__ */ new Map();
920
+ out.push(msg);
921
+ continue;
922
+ }
923
+ if (msg.role === "assistant") {
924
+ pending = /* @__PURE__ */ new Map();
925
+ if (typeof msg.content === "string") {
926
+ out.push(msg);
927
+ continue;
928
+ }
929
+ const original = msg.content;
930
+ let dropped = false;
931
+ const kept = [];
932
+ for (const part of original) {
933
+ if (part.type === "tool_call") {
934
+ const keep = !isInvalid(part);
935
+ const queue = pending.get(part.id) ?? [];
936
+ queue.push(keep);
937
+ pending.set(part.id, queue);
938
+ if (!keep) {
939
+ dropped = true;
940
+ continue;
941
+ }
942
+ }
943
+ kept.push(part);
944
+ }
945
+ if (!dropped) {
946
+ out.push(msg);
947
+ continue;
948
+ }
949
+ const content = kept.filter(
950
+ (part, idx) => !isRawReasoning(part) || !isDanglingReasoning(kept, idx) || isDanglingReasoning(original, original.indexOf(part))
951
+ );
952
+ if (hasReplayableAssistantContent(content)) {
953
+ const pruned = { ...msg, content };
954
+ out.push(pruned);
955
+ prunedAssistant = pruned;
956
+ }
957
+ continue;
958
+ }
959
+ let changed = false;
960
+ const results = msg.content.filter((result) => {
961
+ const queue = pending.get(result.toolCallId);
962
+ const keep = queue && queue.length > 0 ? queue.shift() : true;
963
+ if (!keep) changed = true;
964
+ return keep;
965
+ });
966
+ if (!changed) out.push(msg);
967
+ else if (results.length > 0) out.push({ ...msg, content: results });
968
+ }
969
+ if (prunedAssistant && out[out.length - 1] === prunedAssistant) out.pop();
970
+ return out;
971
+ }
783
972
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
784
973
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
785
974
  var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
@@ -886,10 +1075,12 @@ function remapAnthropicToolCallId(id, idMap) {
886
1075
  idMap.set(id, mapped);
887
1076
  return mapped;
888
1077
  }
889
- function toAnthropicMessages(messages, cacheControl) {
1078
+ function toAnthropicMessages(messages, cacheControl, options) {
890
1079
  let systemText;
891
1080
  const out = [];
892
1081
  const idMap = /* @__PURE__ */ new Map();
1082
+ const keepFallbackBlocks = options?.fallbackBlocks === true;
1083
+ const droppedToolCallIds = /* @__PURE__ */ new Set();
893
1084
  const trajectoryStartIdx = messages.reduce(
894
1085
  (last, m, i) => m.role === "user" ? i : last,
895
1086
  -1
@@ -935,17 +1126,23 @@ function toAnthropicMessages(messages, cacheControl) {
935
1126
  }
936
1127
  if (msg.role === "assistant") {
937
1128
  if (typeof msg.content === "string" && msg.content === "") continue;
938
- const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx > trajectoryStartIdx, idMap);
1129
+ const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(
1130
+ applyServerFallbackReplay(msg.content, keepFallbackBlocks, droppedToolCallIds),
1131
+ msgIdx > trajectoryStartIdx,
1132
+ idMap
1133
+ );
939
1134
  if (Array.isArray(content) && content.length === 0) continue;
940
1135
  out.push({ role: "assistant", content });
941
1136
  continue;
942
1137
  }
943
1138
  if (msg.role === "tool") {
1139
+ const results = droppedToolCallIds.size ? msg.content.filter((r) => !droppedToolCallIds.has(r.toolCallId)) : msg.content;
1140
+ if (results.length === 0) continue;
944
1141
  out.push({
945
1142
  role: "user",
946
1143
  // Cast covers the video block (used by the Anthropic-compatible MiniMax
947
1144
  // API), which isn't in the first-party Anthropic tool_result types.
948
- content: msg.content.map((result) => ({
1145
+ content: results.map((result) => ({
949
1146
  type: "tool_result",
950
1147
  tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
951
1148
  content: toAnthropicToolResultContent(result.content),
@@ -1289,6 +1486,48 @@ function fineGrainedToolStreamingEnabled() {
1289
1486
  const v = raw.trim().toLowerCase();
1290
1487
  return v === "1" || v === "true" || v === "yes" || v === "on";
1291
1488
  }
1489
+ var SERVER_FALLBACK_BETA = "server-side-fallback-2026-07-01";
1490
+ var anthropicServerFallback = {
1491
+ disabled: /* @__PURE__ */ new Set(),
1492
+ reset() {
1493
+ this.disabled.clear();
1494
+ }
1495
+ };
1496
+ function serverFallbackKey(options) {
1497
+ const auth = options.apiKey?.startsWith("sk-ant-oat") ? "oauth" : "key";
1498
+ return `${options.baseUrl ?? process.env.ANTHROPIC_BASE_URL ?? ""}|${auth}`;
1499
+ }
1500
+ function isDirectAnthropicApi(baseUrl) {
1501
+ const effective = baseUrl ?? process.env.ANTHROPIC_BASE_URL;
1502
+ if (!effective) return true;
1503
+ try {
1504
+ const url = new URL(effective);
1505
+ return url.protocol === "https:" && url.hostname === "api.anthropic.com";
1506
+ } catch {
1507
+ return false;
1508
+ }
1509
+ }
1510
+ function isServerFallbackRejection(err) {
1511
+ const status = err?.status;
1512
+ if (status !== 400) return false;
1513
+ const message = err instanceof Error ? err.message : String(err);
1514
+ return /fallbacks|server-side-fallback/i.test(message);
1515
+ }
1516
+ function sumUsageIterations(usage) {
1517
+ const iterations = usage?.iterations;
1518
+ if (!Array.isArray(iterations) || iterations.length === 0) return null;
1519
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1520
+ const totals = { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0 };
1521
+ for (const it of iterations) {
1522
+ if (!it || typeof it !== "object") continue;
1523
+ const rec = it;
1524
+ totals.inputTokens += num(rec.input_tokens);
1525
+ totals.outputTokens += num(rec.output_tokens);
1526
+ totals.cacheRead += num(rec.cache_read_input_tokens);
1527
+ totals.cacheWrite += num(rec.cache_creation_input_tokens);
1528
+ }
1529
+ return totals;
1530
+ }
1292
1531
  function createClient(options) {
1293
1532
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
1294
1533
  const userAgent = isOAuth ? options.userAgent ?? "claude-cli/2.1.75 (external, cli)" : "";
@@ -1383,12 +1622,16 @@ function streamAnthropic(options) {
1383
1622
  async function* runStream(options) {
1384
1623
  const client = createClient(options);
1385
1624
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
1386
- const useStreaming = options.streaming !== false;
1625
+ const useStreaming = options.streaming !== false && !options.prewarm;
1387
1626
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
1388
1627
  const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
1389
1628
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1390
1629
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1391
- const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
1630
+ const fallbackKey = serverFallbackKey(options);
1631
+ const useServerFallback = options.provider !== "minimax" && isDirectAnthropicApi(options.baseUrl) && !anthropicServerFallback.disabled.has(fallbackKey);
1632
+ const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl, {
1633
+ fallbackBlocks: useServerFallback
1634
+ });
1392
1635
  const system = isOAuth ? [
1393
1636
  {
1394
1637
  type: "text",
@@ -1407,6 +1650,17 @@ async function* runStream(options) {
1407
1650
  outputConfig = t.outputConfig;
1408
1651
  }
1409
1652
  }
1653
+ if (options.prewarm) {
1654
+ const budget = thinking?.budget_tokens;
1655
+ if (budget != null && budget >= 1) {
1656
+ return {
1657
+ message: { role: "assistant", content: [] },
1658
+ stopReason: "end_turn",
1659
+ usage: { inputTokens: 0, outputTokens: 0 }
1660
+ };
1661
+ }
1662
+ maxTokens = 1;
1663
+ }
1410
1664
  const params = {
1411
1665
  model: options.model,
1412
1666
  max_tokens: maxTokens,
@@ -1447,6 +1701,7 @@ async function* runStream(options) {
1447
1701
  ];
1448
1702
  return contextEdits.length ? { context_management: { edits: contextEdits } } : {};
1449
1703
  })(),
1704
+ ...useServerFallback ? { fallbacks: "default" } : {},
1450
1705
  stream: useStreaming
1451
1706
  };
1452
1707
  const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
@@ -1463,18 +1718,39 @@ async function* runStream(options) {
1463
1718
  // it Anthropic silently ignores ttl:"1h" and falls back to the 5-min default,
1464
1719
  // so a pre-warmed cache expires before the user's first turn. cacheControl.ttl
1465
1720
  // is only "1h" on the first-party endpoint (see toAnthropicCacheControl).
1466
- ...cacheControl?.ttl === "1h" ? ["extended-cache-ttl-2025-04-11"] : []
1721
+ ...cacheControl?.ttl === "1h" ? ["extended-cache-ttl-2025-04-11"] : [],
1722
+ ...useServerFallback ? [SERVER_FALLBACK_BETA] : []
1467
1723
  ];
1468
- const requestOptions = {
1724
+ const toRequestOptions = (betas) => ({
1469
1725
  signal: options.signal ?? void 0,
1470
- ...betaHeaders.length ? { headers: { "anthropic-beta": betaHeaders.join(",") } } : {}
1726
+ ...betas.length ? { headers: { "anthropic-beta": betas.join(",") } } : {}
1727
+ });
1728
+ const requestOptions = toRequestOptions(betaHeaders);
1729
+ const send = async (create) => {
1730
+ try {
1731
+ return await create(params, requestOptions);
1732
+ } catch (err) {
1733
+ if (!useServerFallback || !isServerFallbackRejection(err)) throw err;
1734
+ anthropicServerFallback.disabled.add(fallbackKey);
1735
+ const { fallbacks: _dropped, ...rest } = params;
1736
+ const retryParams = {
1737
+ ...rest,
1738
+ messages: toAnthropicMessages(downgradedMessages, cacheControl, { fallbackBlocks: false }).messages
1739
+ };
1740
+ return create(
1741
+ retryParams,
1742
+ toRequestOptions(betaHeaders.filter((b) => b !== SERVER_FALLBACK_BETA))
1743
+ );
1744
+ }
1471
1745
  };
1472
1746
  if (!useStreaming) {
1473
1747
  const nonStreamingClient = client.withOptions({ timeout: NON_STREAMING_TIMEOUT_MS });
1474
1748
  try {
1475
- const message = await nonStreamingClient.messages.create(
1476
- { ...params, stream: false },
1477
- requestOptions
1749
+ const message = await send(
1750
+ (p, o) => nonStreamingClient.messages.create(
1751
+ { ...p, stream: false },
1752
+ o
1753
+ )
1478
1754
  );
1479
1755
  yield* synthesizeEventsFromMessage(message);
1480
1756
  return messageToResponse(message);
@@ -1492,9 +1768,8 @@ async function* runStream(options) {
1492
1768
  const keepalive = { type: "keepalive" };
1493
1769
  let receivedAnyEvent = false;
1494
1770
  try {
1495
- const stream2 = await client.messages.create(
1496
- params,
1497
- requestOptions
1771
+ const stream2 = await send(
1772
+ (p, o) => client.messages.create(p, o)
1498
1773
  );
1499
1774
  for await (const event of stream2) {
1500
1775
  receivedAnyEvent = true;
@@ -1672,6 +1947,13 @@ async function* runStream(options) {
1672
1947
  if (usage?.output_tokens != null) {
1673
1948
  outputTokens = usage.output_tokens;
1674
1949
  }
1950
+ const totals = sumUsageIterations(usage);
1951
+ if (totals) {
1952
+ inputTokens = totals.inputTokens;
1953
+ outputTokens = totals.outputTokens;
1954
+ cacheRead = totals.cacheRead;
1955
+ cacheWrite = totals.cacheWrite;
1956
+ }
1675
1957
  yield keepalive;
1676
1958
  break;
1677
1959
  }
@@ -1801,10 +2083,11 @@ function messageToResponse(message) {
1801
2083
  }
1802
2084
  }
1803
2085
  const usage = message.usage;
1804
- const inputTokens = usage.input_tokens ?? 0;
1805
- const outputTokens = usage.output_tokens ?? 0;
1806
- const cacheRead = usage.cache_read_input_tokens;
1807
- const cacheWrite = usage.cache_creation_input_tokens;
2086
+ const totals = sumUsageIterations(usage);
2087
+ const inputTokens = totals?.inputTokens ?? usage.input_tokens ?? 0;
2088
+ const outputTokens = totals?.outputTokens ?? usage.output_tokens ?? 0;
2089
+ const cacheRead = totals?.cacheRead ?? usage.cache_read_input_tokens;
2090
+ const cacheWrite = totals?.cacheWrite ?? usage.cache_creation_input_tokens;
1808
2091
  return {
1809
2092
  message: {
1810
2093
  role: "assistant",
@@ -2074,7 +2357,7 @@ async function* runStream2(options) {
2074
2357
  ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
2075
2358
  ...options.tools?.length ? {
2076
2359
  tools: toOpenAITools(options.tools, {
2077
- strict: supportsStrictToolSampling(options.provider)
2360
+ strict: supportsStrictToolSampling(options.provider) && (options.strictTools ?? true)
2078
2361
  })
2079
2362
  } : {},
2080
2363
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
@@ -2592,6 +2875,7 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
2592
2875
  const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2593
2876
  const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
2594
2877
  const responsesLite = usesResponsesLite(options.model);
2878
+ const liteShape = options.responsesLite ?? responsesLite;
2595
2879
  const body = {
2596
2880
  model: options.model,
2597
2881
  store: false,
@@ -2599,11 +2883,11 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
2599
2883
  instructions: system,
2600
2884
  input,
2601
2885
  tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
2602
- parallel_tool_calls: !responsesLite,
2886
+ parallel_tool_calls: !liteShape,
2603
2887
  include: ["reasoning.encrypted_content"]
2604
2888
  };
2605
2889
  if (options.tools?.length) {
2606
- body.tools = toCodexTools(options.tools);
2890
+ body.tools = toCodexTools(options.tools, options.strictTools ?? true);
2607
2891
  }
2608
2892
  body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
2609
2893
  if (options.temperature != null && !options.thinking) {
@@ -2615,7 +2899,7 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
2615
2899
  // `ultra` is a client orchestration preset, not a Codex API effort.
2616
2900
  effort: options.thinking === "ultra" ? "max" : options.thinking ?? (responsesLite ? "low" : "none"),
2617
2901
  summary: "auto",
2618
- ...responsesLite ? { context: "all_turns" } : {}
2902
+ ...liteShape ? { context: "all_turns" } : {}
2619
2903
  };
2620
2904
  if (responsesLite) {
2621
2905
  body.text = { verbosity: "low" };
@@ -2627,10 +2911,8 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
2627
2911
  "OpenAI-Beta": "responses=experimental",
2628
2912
  originator: responsesLite ? "codex_cli_rs" : "ezcoder",
2629
2913
  "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ezcoder (${os.platform()} ${os.release()}; ${os.arch()})`,
2630
- ...responsesLite ? {
2631
- version: CODEX_CLIENT_VERSION,
2632
- "X-OpenAI-Internal-Codex-Responses-Lite": "true"
2633
- } : {}
2914
+ ...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
2915
+ ...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
2634
2916
  };
2635
2917
  if (options.accountId) {
2636
2918
  headers["chatgpt-account-id"] = options.accountId;
@@ -3094,15 +3376,17 @@ function toCodexInput(messages, options) {
3094
3376
  }
3095
3377
  return { system, input };
3096
3378
  }
3097
- function toCodexTools(tools) {
3379
+ function toCodexTools(tools, strictTools) {
3098
3380
  return tools.map((tool) => {
3099
3381
  let parameters = resolveToolSchema(tool);
3100
3382
  let strict = null;
3101
- try {
3102
- parameters = makeStrictToolSchema(parameters);
3103
- strict = true;
3104
- } catch (error) {
3105
- if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
3383
+ if (strictTools) {
3384
+ try {
3385
+ parameters = makeStrictToolSchema(parameters);
3386
+ strict = true;
3387
+ } catch (error) {
3388
+ if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
3389
+ }
3106
3390
  }
3107
3391
  return {
3108
3392
  type: "function",
@@ -3339,6 +3623,17 @@ function stripUnsupportedSchemaFields(value) {
3339
3623
  }
3340
3624
  delete value.$schema;
3341
3625
  delete value.additionalProperties;
3626
+ for (const [key, target, step] of [
3627
+ ["exclusiveMinimum", "minimum", 1],
3628
+ ["exclusiveMaximum", "maximum", -1]
3629
+ ]) {
3630
+ const bound = value[key];
3631
+ if (bound === void 0) continue;
3632
+ delete value[key];
3633
+ if (typeof bound === "number" && value[target] === void 0) {
3634
+ value[target] = value.type === "integer" ? bound + step : bound;
3635
+ }
3636
+ }
3342
3637
  for (const item of Object.values(value)) {
3343
3638
  if (isJsonObject(item) || Array.isArray(item)) {
3344
3639
  stripUnsupportedSchemaFields(item);
@@ -3838,6 +4133,41 @@ function sanitizeMessagesForWire(messages) {
3838
4133
  return sanitized ?? messages;
3839
4134
  }
3840
4135
 
4136
+ // src/utils/context-observation.ts
4137
+ function imageCount(message) {
4138
+ if (!message || !Array.isArray(message.content)) return 0;
4139
+ if (message.role === "user")
4140
+ return message.content.filter((part) => part.type === "image").length;
4141
+ if (message.role !== "tool") return 0;
4142
+ return message.content.reduce(
4143
+ (count, result) => count + (Array.isArray(result.content) ? result.content.filter((part) => part.type === "image").length : 0),
4144
+ 0
4145
+ );
4146
+ }
4147
+ function observePreparedContext(before, after, tools) {
4148
+ let imagesBefore = 0;
4149
+ let imagesAfter = 0;
4150
+ let firstImageDropMessage = null;
4151
+ for (let index = 0; index < before.length; index++) {
4152
+ const oldCount = imageCount(before[index]);
4153
+ const newCount = imageCount(after[index]);
4154
+ imagesBefore += oldCount;
4155
+ imagesAfter += newCount;
4156
+ if (firstImageDropMessage === null && newCount < oldCount) firstImageDropMessage = index;
4157
+ }
4158
+ return {
4159
+ messages: after,
4160
+ tools: tools.map((tool) => ({
4161
+ name: tool.name,
4162
+ description: tool.description,
4163
+ parameters: resolveToolSchema(tool)
4164
+ })),
4165
+ imagesBefore,
4166
+ imagesAfter,
4167
+ firstImageDropMessage
4168
+ };
4169
+ }
4170
+
3841
4171
  // src/stream.ts
3842
4172
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
3843
4173
  var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
@@ -3986,11 +4316,27 @@ function stream(options) {
3986
4316
  throw new VideoUnsupportedError();
3987
4317
  }
3988
4318
  const wireMessages = stripMessageProvenance(options.messages);
3989
- const messages = clampProviderContextImages(
3990
- sanitizeMessagesForWire(wireMessages),
3991
- options.provider,
3992
- options.supportsImages
4319
+ const sanitized = sanitizeMessagesForWire(wireMessages);
4320
+ const replayable = dropInvalidToolCalls(
4321
+ sanitized,
4322
+ toolCallNameRuleFor(options.provider, {
4323
+ accountId: options.accountId,
4324
+ baseUrl: options.baseUrl
4325
+ })
3993
4326
  );
4327
+ const messages = clampProviderContextImages(replayable, options.provider, options.supportsImages);
4328
+ if (options.onContextPrepared) {
4329
+ try {
4330
+ options.onContextPrepared(
4331
+ observePreparedContext(
4332
+ replayable === sanitized ? wireMessages : replayable,
4333
+ messages,
4334
+ options.tools ?? []
4335
+ )
4336
+ );
4337
+ } catch {
4338
+ }
4339
+ }
3994
4340
  return entry.stream(messages === options.messages ? options : { ...options, messages });
3995
4341
  }
3996
4342
  function stripMessageProvenance(messages) {
@@ -4401,14 +4747,17 @@ export {
4401
4747
  ProviderError,
4402
4748
  REDACTED as REDACTION_MARKER,
4403
4749
  StreamResult,
4750
+ TOOL_CALL_NAME_RULES,
4404
4751
  clampProviderContextImages,
4405
4752
  classifyProviderError,
4753
+ dropInvalidToolCalls,
4406
4754
  environmentSecrets,
4407
4755
  formatError,
4408
4756
  formatErrorForDisplay,
4409
4757
  hasLoneSurrogate,
4410
4758
  isHardBillingMessage,
4411
4759
  isUsageLimitError,
4760
+ isValidToolCallName,
4412
4761
  localWireModelId,
4413
4762
  palsuAssistantMessage,
4414
4763
  palsuText,
@@ -4427,6 +4776,8 @@ export {
4427
4776
  stream,
4428
4777
  toAnthropicMessages,
4429
4778
  toOpenAIMessages,
4430
- toWellFormedText
4779
+ toWellFormedText,
4780
+ toolCallNameRuleFor,
4781
+ usesResponsesLite
4431
4782
  };
4432
4783
  //# sourceMappingURL=index.js.map