@kenkaiiii/gg-ai 5.87.2 → 5.88.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
@@ -30,10 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CLAUDE_CODE_FALLBACK_VERSION: () => CLAUDE_CODE_FALLBACK_VERSION,
33
34
  CODEX_CLIENT_VERSION: () => CODEX_CLIENT_VERSION,
34
35
  EventStream: () => EventStream,
35
36
  FAST_MODE_SUFFIX: () => FAST_MODE_SUFFIX,
36
37
  GGAIError: () => GGAIError,
38
+ PRECONNECT_INTERVAL_MS: () => PRECONNECT_INTERVAL_MS,
37
39
  ProviderError: () => ProviderError,
38
40
  REDACTION_MARKER: () => REDACTED,
39
41
  ResponseStalledError: () => ResponseStalledError,
@@ -49,6 +51,8 @@ __export(index_exports, {
49
51
  formatChatError: () => formatChatError,
50
52
  formatError: () => formatError,
51
53
  formatErrorForDisplay: () => formatErrorForDisplay,
54
+ getClaudeCliUserAgent: () => getClaudeCliUserAgent,
55
+ getClaudeCodeVersion: () => getClaudeCodeVersion,
52
56
  hasLoneSurrogate: () => hasLoneSurrogate,
53
57
  isHardBillingMessage: () => isHardBillingMessage,
54
58
  isUsageLimitError: () => isUsageLimitError,
@@ -62,6 +66,9 @@ __export(index_exports, {
62
66
  palsuThinking: () => palsuThinking,
63
67
  palsuToolCall: () => palsuToolCall,
64
68
  parseFastModeRejection: () => parseFastModeRejection,
69
+ pendingClaudeCodeVersionRefresh: () => pendingClaudeCodeVersionRefresh,
70
+ preconnectOrigin: () => preconnectOrigin,
71
+ preconnectProvider: () => preconnectProvider,
65
72
  prewarmAnthropicCache: () => prewarmAnthropicCache,
66
73
  providerRegistry: () => providerRegistry,
67
74
  redactText: () => redactText,
@@ -484,6 +491,26 @@ function providerGuidance(provider, message, statusCode) {
484
491
  // src/providers/anthropic.ts
485
492
  var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
486
493
 
494
+ // src/providers/cache-minimums.ts
495
+ var ANTHROPIC_MIN_CACHEABLE_TOKENS = [
496
+ [/haiku-4-5|opus-4-[56]/, 4096],
497
+ [/haiku-3-5|haiku-3\b/, 2048],
498
+ [/[a-z]-(?:[5-9]|\d{2})(?:-|$)/, 512],
499
+ // 5.x and later families (family name, then major)
500
+ [/sonnet-4|opus-4|sonnet-3-7/, 1024]
501
+ ];
502
+ function anthropicMinCacheableTokens(model) {
503
+ for (const [pattern, min] of ANTHROPIC_MIN_CACHEABLE_TOKENS) {
504
+ if (pattern.test(model)) return min;
505
+ }
506
+ return 1024;
507
+ }
508
+ function estimatePrefixTokens(...parts) {
509
+ let chars = 0;
510
+ for (const part of parts) if (part !== void 0) chars += JSON.stringify(part).length;
511
+ return Math.ceil(chars / 3.5);
512
+ }
513
+
487
514
  // src/utils/event-stream.ts
488
515
  var EventStream = class {
489
516
  queue = [];
@@ -639,6 +666,145 @@ var StreamResult = class _StreamResult {
639
666
  }
640
667
  };
641
668
 
669
+ // src/claude-code-version.ts
670
+ var import_promises = __toESM(require("fs/promises"), 1);
671
+ var import_node_os = __toESM(require("os"), 1);
672
+ var import_node_path = __toESM(require("path"), 1);
673
+
674
+ // src/utils/diag.ts
675
+ var _diagFn = null;
676
+ function setProviderDiagnostic(fn) {
677
+ _diagFn = fn;
678
+ }
679
+ function providerDiag(phase, data) {
680
+ _diagFn?.(phase, data);
681
+ }
682
+
683
+ // src/claude-code-version.ts
684
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
685
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
686
+ var FETCH_TIMEOUT_MS = 3e3;
687
+ var CLAUDE_CODE_FALLBACK_VERSION = "2.1.316";
688
+ function isOlder(a, b) {
689
+ const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
690
+ const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
691
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
692
+ const x = pa[i] ?? 0;
693
+ const y = pb[i] ?? 0;
694
+ if (x !== y) return x < y;
695
+ }
696
+ return false;
697
+ }
698
+ function atLeastFallback(version) {
699
+ return isOlder(version, CLAUDE_CODE_FALLBACK_VERSION) ? CLAUDE_CODE_FALLBACK_VERSION : version;
700
+ }
701
+ var memoryCache = null;
702
+ var inflight = null;
703
+ function agentDir() {
704
+ return import_node_path.default.join(import_node_os.default.homedir(), ".gg");
705
+ }
706
+ function cachePath() {
707
+ return import_node_path.default.join(agentDir(), "claude-code-version.json");
708
+ }
709
+ async function readDiskCache() {
710
+ try {
711
+ const raw = await import_promises.default.readFile(cachePath(), "utf-8");
712
+ const parsed = JSON.parse(raw);
713
+ if (typeof parsed.version === "string" && typeof parsed.fetchedAt === "number") {
714
+ return parsed;
715
+ }
716
+ return null;
717
+ } catch {
718
+ return null;
719
+ }
720
+ }
721
+ async function writeDiskCache(data) {
722
+ try {
723
+ await import_promises.default.mkdir(agentDir(), { recursive: true, mode: 448 });
724
+ await import_promises.default.writeFile(cachePath(), JSON.stringify(data), { mode: 384 });
725
+ } catch (err) {
726
+ providerDiag("claude_code_version_cache_write_failed", {
727
+ error: err instanceof Error ? err.message : String(err)
728
+ });
729
+ }
730
+ }
731
+ async function fetchLatest() {
732
+ const controller = new AbortController();
733
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
734
+ try {
735
+ const response = await fetch(NPM_LATEST_URL, { signal: controller.signal });
736
+ if (!response.ok) return null;
737
+ const data = await response.json();
738
+ if (typeof data.version === "string" && /^\d/.test(data.version)) {
739
+ return data.version;
740
+ }
741
+ return null;
742
+ } catch {
743
+ return null;
744
+ } finally {
745
+ clearTimeout(timer);
746
+ }
747
+ }
748
+ var refreshing = null;
749
+ function refreshInBackground() {
750
+ refreshing ??= (async () => {
751
+ try {
752
+ const fetched = await fetchLatest();
753
+ if (!fetched) return;
754
+ await writeDiskCache({ version: fetched, fetchedAt: Date.now() });
755
+ memoryCache = { version: fetched, expiresAt: Date.now() + CACHE_TTL_MS };
756
+ } finally {
757
+ refreshing = null;
758
+ }
759
+ })();
760
+ return refreshing;
761
+ }
762
+ function pendingClaudeCodeVersionRefresh() {
763
+ return refreshing;
764
+ }
765
+ async function getClaudeCodeVersion() {
766
+ if (memoryCache && Date.now() < memoryCache.expiresAt) {
767
+ return memoryCache.version;
768
+ }
769
+ if (inflight) return inflight;
770
+ inflight = (async () => {
771
+ const disk = await readDiskCache();
772
+ const diskFresh = disk && Date.now() - disk.fetchedAt < CACHE_TTL_MS;
773
+ if (disk && diskFresh) {
774
+ const version = atLeastFallback(disk.version);
775
+ memoryCache = { version, expiresAt: Date.now() + CACHE_TTL_MS };
776
+ return version;
777
+ }
778
+ if (disk) {
779
+ const version = atLeastFallback(disk.version);
780
+ memoryCache = { version, expiresAt: Date.now() + 5 * 60 * 1e3 };
781
+ void refreshInBackground();
782
+ return version;
783
+ }
784
+ const fetched = await fetchLatest();
785
+ if (fetched) {
786
+ await writeDiskCache({ version: fetched, fetchedAt: Date.now() });
787
+ memoryCache = { version: fetched, expiresAt: Date.now() + CACHE_TTL_MS };
788
+ return fetched;
789
+ }
790
+ const resolved = CLAUDE_CODE_FALLBACK_VERSION;
791
+ memoryCache = { version: resolved, expiresAt: Date.now() + 5 * 60 * 1e3 };
792
+ providerDiag("claude_code_version_fallback", { version: resolved });
793
+ return resolved;
794
+ })();
795
+ try {
796
+ return await inflight;
797
+ } finally {
798
+ inflight = null;
799
+ }
800
+ }
801
+ function claudeCliUserAgent(version) {
802
+ return `claude-cli/${version} (external, cli)`;
803
+ }
804
+ async function getClaudeCliUserAgent() {
805
+ return claudeCliUserAgent(await getClaudeCodeVersion());
806
+ }
807
+
642
808
  // src/utils/provider-options.ts
643
809
  var RESERVED_BODY_FIELDS = /* @__PURE__ */ new Set([
644
810
  "contents",
@@ -1342,6 +1508,41 @@ function toolResultVideos(content) {
1342
1508
  if (typeof content === "string") return [];
1343
1509
  return content.filter((b) => b.type === "video");
1344
1510
  }
1511
+ function planDeferredTools(tools, catalog, messages) {
1512
+ if (!catalog?.length) return void 0;
1513
+ const byName = /* @__PURE__ */ new Map();
1514
+ for (const tool of catalog) if (!byName.has(tool.name)) byName.set(tool.name, tool);
1515
+ const loaded = /* @__PURE__ */ new Set();
1516
+ for (const message of messages) {
1517
+ if (message.role !== "tool") continue;
1518
+ for (const result of message.content) {
1519
+ for (const name of result.loadedTools ?? []) if (byName.has(name)) loaded.add(name);
1520
+ }
1521
+ }
1522
+ const live = (tools ?? []).filter((tool) => !(byName.has(tool.name) && loaded.has(tool.name)));
1523
+ if (live.length === 0) return void 0;
1524
+ const liveNames = new Set(live.map((tool) => tool.name));
1525
+ const deferred = [...byName.values()].filter((tool) => !liveNames.has(tool.name));
1526
+ return { live, deferred, loaded, byName };
1527
+ }
1528
+ function supportsAnthropicDeferredTools(model) {
1529
+ return /opus-4[-.][5-9]|sonnet-4[-.][5-9]|haiku-4[-.]5|opus-5|sonnet-5|haiku-5|fable-5|mythos-5/.test(
1530
+ model
1531
+ );
1532
+ }
1533
+ function supportsResponsesToolSearch(model) {
1534
+ return /^gpt-(5\.[4-9]|[6-9])/.test(model);
1535
+ }
1536
+ function usesDeferredToolLoading(options) {
1537
+ if (options.provider === "anthropic") {
1538
+ const firstParty = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
1539
+ return firstParty && supportsAnthropicDeferredTools(options.model);
1540
+ }
1541
+ if (options.provider === "openai" && options.accountId) {
1542
+ return supportsResponsesToolSearch(options.model);
1543
+ }
1544
+ return false;
1545
+ }
1345
1546
  function toAnthropicCacheControl(retention, baseUrl) {
1346
1547
  const resolved = retention ?? "short";
1347
1548
  if (resolved === "none") return void 0;
@@ -1384,6 +1585,8 @@ function toAnthropicMessages(messages, cacheControl, options) {
1384
1585
  const keepFallbackBlocks = options?.fallbackBlocks === true;
1385
1586
  const droppedToolCallIds = /* @__PURE__ */ new Set();
1386
1587
  const unresolvedServerToolIds = unresolvedServerToolUseIds(messages);
1588
+ const toolReferences = options?.toolReferences;
1589
+ const referencedTools = /* @__PURE__ */ new Set();
1387
1590
  const trajectoryStartIdx = messages.reduce(
1388
1591
  (last, m, i) => opensTurn(m) ? i : last,
1389
1592
  -1
@@ -1446,16 +1649,43 @@ function toAnthropicMessages(messages, cacheControl, options) {
1446
1649
  if (msg.role === "tool") {
1447
1650
  const results = droppedToolCallIds.size ? msg.content.filter((r) => !droppedToolCallIds.has(r.toolCallId)) : msg.content;
1448
1651
  if (results.length === 0) continue;
1449
- out.push({
1450
- role: "user",
1451
- // Cast covers the video block (used by the Anthropic-compatible MiniMax
1452
- // API), which isn't in the first-party Anthropic tool_result types.
1453
- content: results.map((result) => ({
1652
+ const trailing = [];
1653
+ const blocks = results.map((result) => {
1654
+ const toolUseId = remapAnthropicToolCallId(result.toolCallId, idMap);
1655
+ const refs = toolReferences ? (result.loadedTools ?? []).filter(
1656
+ (name) => toolReferences.has(name) && !referencedTools.has(name)
1657
+ ) : [];
1658
+ if (refs.length === 0) {
1659
+ return {
1660
+ type: "tool_result",
1661
+ tool_use_id: toolUseId,
1662
+ content: toAnthropicToolResultContent(result.content),
1663
+ is_error: result.isError
1664
+ };
1665
+ }
1666
+ for (const name of refs) referencedTools.add(name);
1667
+ const original = toAnthropicToolResultContent(result.content);
1668
+ const parts = typeof original === "string" ? [{ type: "text", text: original }] : original;
1669
+ const first = parts.findIndex((part) => part.type === "text" && part.text !== "");
1670
+ parts.forEach((part, index) => {
1671
+ if (part.type === "text" && part.text === "") return;
1672
+ trailing.push(
1673
+ index === first && part.type === "text" ? { type: "text", text: `Result of tool call ${toolUseId}:
1674
+ ${part.text}` } : part
1675
+ );
1676
+ });
1677
+ return {
1454
1678
  type: "tool_result",
1455
- tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
1456
- content: toAnthropicToolResultContent(result.content),
1679
+ tool_use_id: toolUseId,
1680
+ content: refs.map((name) => ({ type: "tool_reference", tool_name: name })),
1457
1681
  is_error: result.isError
1458
- }))
1682
+ };
1683
+ });
1684
+ out.push({
1685
+ role: "user",
1686
+ // Cast covers the video and tool_reference blocks, which aren't in this
1687
+ // SDK's first-party tool_result types.
1688
+ content: [...blocks, ...trailing]
1459
1689
  });
1460
1690
  }
1461
1691
  }
@@ -1471,7 +1701,8 @@ function toAnthropicMessages(messages, cacheControl, options) {
1471
1701
  markAnthropicCacheBreakpoint(out, tail, cacheControl);
1472
1702
  } else {
1473
1703
  if (anchor !== void 0) markAnthropicCacheBreakpoint(out, anchor, cacheControl);
1474
- markAnthropicCacheBreakpoint(out, tail, { type: "ephemeral" });
1704
+ const longRun = isLongToolRun(messages, trajectoryStartIdx);
1705
+ markAnthropicCacheBreakpoint(out, tail, longRun ? cacheControl : { type: "ephemeral" });
1475
1706
  }
1476
1707
  } else {
1477
1708
  markAnthropicCacheBreakpoint(out, tail, cacheControl);
@@ -1501,6 +1732,28 @@ function toAnthropicMessages(messages, cacheControl, options) {
1501
1732
  }
1502
1733
  return { system, messages: out };
1503
1734
  }
1735
+ var BLOCKING_TOOL_NAMES = /* @__PURE__ */ new Set([
1736
+ "wait_agent",
1737
+ "task_output",
1738
+ "subagent",
1739
+ "ask_user"
1740
+ ]);
1741
+ var LONG_RUN_ROUNDS = 6;
1742
+ function isLongToolRun(messages, trajectoryStartIdx) {
1743
+ let rounds = 0;
1744
+ for (let i = Math.max(0, trajectoryStartIdx + 1); i < messages.length; i++) {
1745
+ const m = messages[i];
1746
+ if (m?.role !== "assistant" || typeof m.content === "string") continue;
1747
+ let called = false;
1748
+ for (const part of m.content) {
1749
+ if (part.type !== "tool_call") continue;
1750
+ called = true;
1751
+ if (BLOCKING_TOOL_NAMES.has(part.name)) return true;
1752
+ }
1753
+ if (called && ++rounds >= LONG_RUN_ROUNDS) return true;
1754
+ }
1755
+ return false;
1756
+ }
1504
1757
  function markAnthropicCacheBreakpoint(out, index, cacheControl) {
1505
1758
  const message = out[index];
1506
1759
  if (!message) return;
@@ -1521,6 +1774,18 @@ function markAnthropicCacheBreakpoint(out, index, cacheControl) {
1521
1774
  }
1522
1775
  }
1523
1776
  function toAnthropicTools(tools, options) {
1777
+ const deferred = (options?.deferred ?? []).map(
1778
+ (tool) => ({
1779
+ name: tool.name,
1780
+ description: tool.description,
1781
+ input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
1782
+ ...options?.enableFineGrainedToolStreaming ? { eager_input_streaming: true } : {},
1783
+ defer_loading: true
1784
+ })
1785
+ );
1786
+ return [...toAnthropicLiveTools(tools, options), ...deferred];
1787
+ }
1788
+ function toAnthropicLiveTools(tools, options) {
1524
1789
  return tools.map((tool, index) => {
1525
1790
  const anthropicTool = {
1526
1791
  name: tool.name,
@@ -1807,6 +2072,7 @@ function parseToolArguments(argsJson) {
1807
2072
  // src/utils/provider-fetch.ts
1808
2073
  var import_undici = require("undici");
1809
2074
  var PROVIDER_TRANSPORT_TIMEOUT_MS = 45 * 6e4;
2075
+ var PROVIDER_KEEP_ALIVE_TIMEOUT_MS = 3e4;
1810
2076
  function nodeUsesEnvProxy({
1811
2077
  env = process.env,
1812
2078
  execArgv = process.execArgv,
@@ -1819,7 +2085,11 @@ function nodeUsesEnvProxy({
1819
2085
  return major >= 24 || major === 22 && minor >= 21;
1820
2086
  }
1821
2087
  function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {}) {
1822
- const timeouts = { headersTimeout: timeoutMs, bodyTimeout: timeoutMs };
2088
+ const timeouts = {
2089
+ headersTimeout: timeoutMs,
2090
+ bodyTimeout: timeoutMs,
2091
+ keepAliveTimeout: PROVIDER_KEEP_ALIVE_TIMEOUT_MS
2092
+ };
1823
2093
  const dispatcher = useEnvProxy ? new import_undici.EnvHttpProxyAgent(timeouts) : new import_undici.Agent(timeouts);
1824
2094
  const builtinFetch = globalThis.fetch;
1825
2095
  const providerFetch2 = async (input, init2) => {
@@ -1909,9 +2179,13 @@ function sumUsageIterations(usage) {
1909
2179
  }
1910
2180
  return totals;
1911
2181
  }
2182
+ async function withClaudeUserAgent(options) {
2183
+ if (options.userAgent || !options.apiKey?.startsWith("sk-ant-oat")) return options;
2184
+ return { ...options, userAgent: await getClaudeCliUserAgent() };
2185
+ }
1912
2186
  function createClient(options) {
1913
2187
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
1914
- const userAgent = isOAuth ? options.userAgent ?? "claude-cli/2.1.75 (external, cli)" : "";
2188
+ const userAgent = isOAuth ? options.userAgent ?? claudeCliUserAgent(CLAUDE_CODE_FALLBACK_VERSION) : "";
1915
2189
  const cacheKey = `${options.apiKey ?? ""}|${options.baseUrl ?? ""}|${userAgent}`;
1916
2190
  if (!options.fetch) {
1917
2191
  const cached = anthropicClientCache.get(cacheKey);
@@ -1941,11 +2215,13 @@ function createClient(options) {
1941
2215
  }
1942
2216
  async function prewarmAnthropicCache(options) {
1943
2217
  try {
1944
- const client = createClient({
1945
- apiKey: options.apiKey,
1946
- baseUrl: options.baseUrl,
1947
- userAgent: options.userAgent
1948
- });
2218
+ const client = createClient(
2219
+ await withClaudeUserAgent({
2220
+ apiKey: options.apiKey,
2221
+ baseUrl: options.baseUrl,
2222
+ userAgent: options.userAgent
2223
+ })
2224
+ );
1949
2225
  const cacheControl = toAnthropicCacheControl(options.cacheRetention ?? "long", options.baseUrl);
1950
2226
  const { system, messages } = toAnthropicMessages(
1951
2227
  [
@@ -1968,6 +2244,9 @@ async function prewarmAnthropicCache(options) {
1968
2244
  // prewarmed prompt cache actually hits — both are gated by the flag.
1969
2245
  enableFineGrainedToolStreaming: fineGrainedToolStreamingEnabled()
1970
2246
  }) : void 0;
2247
+ if (estimatePrefixTokens(fullSystem, tools) < anthropicMinCacheableTokens(options.model)) {
2248
+ return;
2249
+ }
1971
2250
  await client.messages.create(
1972
2251
  {
1973
2252
  model: options.model,
@@ -1999,7 +2278,7 @@ function streamAnthropic(options) {
1999
2278
  return new StreamResult(runStream(options), options.signal);
2000
2279
  }
2001
2280
  async function* runStream(options) {
2002
- const client = createClient(options);
2281
+ const client = createClient(await withClaudeUserAgent(options));
2003
2282
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
2004
2283
  const useStreaming = options.streaming !== false && !options.prewarm;
2005
2284
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
@@ -2008,8 +2287,11 @@ async function* runStream(options) {
2008
2287
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2009
2288
  const fallbackKey = serverFallbackKey(options);
2010
2289
  const useServerFallback = options.provider !== "minimax" && isDirectAnthropicApi(options.baseUrl) && !anthropicServerFallback.disabled.has(fallbackKey);
2290
+ const deferredPlan = options.provider === "anthropic" && isDirectAnthropicApi(options.baseUrl) && supportsAnthropicDeferredTools(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgradedMessages) : void 0;
2291
+ const toolReferences = deferredPlan?.loaded;
2011
2292
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl, {
2012
- fallbackBlocks: useServerFallback
2293
+ fallbackBlocks: useServerFallback,
2294
+ toolReferences
2013
2295
  });
2014
2296
  const useFastMode = options.speed === "fast" && options.provider === "anthropic" && isDirectAnthropicApi(options.baseUrl);
2015
2297
  const system = isOAuth ? [
@@ -2051,20 +2333,20 @@ async function* runStream(options) {
2051
2333
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
2052
2334
  ...options.topP != null ? { top_p: options.topP } : {},
2053
2335
  ...options.stop ? { stop_sequences: options.stop } : {},
2054
- ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
2336
+ ...(deferredPlan?.live ?? options.tools)?.length || options.serverTools?.length || options.webSearch ? (() => {
2055
2337
  const reservedServerNames = /* @__PURE__ */ new Set();
2056
2338
  if (options.webSearch) reservedServerNames.add("web_search");
2057
2339
  for (const t of options.serverTools ?? []) {
2058
2340
  const name = t.name;
2059
2341
  if (name) reservedServerNames.add(name);
2060
2342
  }
2061
- const clientTools = options.tools?.length ? toAnthropicTools(
2062
- options.tools.filter((t) => !reservedServerNames.has(t.name)),
2063
- {
2064
- ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
2065
- ...supportsFirstPartyToolExtras && fineGrainedToolStreamingEnabled() ? { enableFineGrainedToolStreaming: true } : {}
2066
- }
2067
- ) : [];
2343
+ const notReserved = (t) => !reservedServerNames.has(t.name);
2344
+ const liveTools = deferredPlan?.live ?? options.tools ?? [];
2345
+ const clientTools = liveTools.length ? toAnthropicTools(liveTools.filter(notReserved), {
2346
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
2347
+ ...supportsFirstPartyToolExtras && fineGrainedToolStreamingEnabled() ? { enableFineGrainedToolStreaming: true } : {},
2348
+ ...deferredPlan ? { deferred: deferredPlan.deferred.filter(notReserved) } : {}
2349
+ }) : [];
2068
2350
  return {
2069
2351
  tools: [
2070
2352
  ...clientTools,
@@ -2085,6 +2367,13 @@ async function* runStream(options) {
2085
2367
  ...useFastMode ? { speed: "fast" } : {},
2086
2368
  stream: useStreaming
2087
2369
  };
2370
+ if (options.prewarm && estimatePrefixTokens(baseParams.system, baseParams.tools) < anthropicMinCacheableTokens(options.model)) {
2371
+ return {
2372
+ message: { role: "assistant", content: [] },
2373
+ stopReason: "end_turn",
2374
+ usage: { inputTokens: 0, outputTokens: 0 }
2375
+ };
2376
+ }
2088
2377
  const params = mergeProviderOptions(baseParams, options.providerOptions);
2089
2378
  const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
2090
2379
  const betaHeaders = [
@@ -2118,7 +2407,10 @@ async function* runStream(options) {
2118
2407
  const { fallbacks: _dropped, ...rest } = params;
2119
2408
  const retryParams = {
2120
2409
  ...rest,
2121
- messages: toAnthropicMessages(downgradedMessages, cacheControl, { fallbackBlocks: false }).messages
2410
+ messages: toAnthropicMessages(downgradedMessages, cacheControl, {
2411
+ fallbackBlocks: false,
2412
+ toolReferences
2413
+ }).messages
2122
2414
  };
2123
2415
  return create(
2124
2416
  retryParams,
@@ -2580,15 +2872,6 @@ function fnv1aHash(value) {
2580
2872
  return (hash >>> 0).toString(16).padStart(8, "0");
2581
2873
  }
2582
2874
 
2583
- // src/utils/diag.ts
2584
- var _diagFn = null;
2585
- function setProviderDiagnostic(fn) {
2586
- _diagFn = fn;
2587
- }
2588
- function providerDiag(phase, data) {
2589
- _diagFn?.(phase, data);
2590
- }
2591
-
2592
2875
  // src/providers/moonshot-video.ts
2593
2876
  async function uploadMoonshotVideos(client, messages, signal) {
2594
2877
  for (const msg of messages) {
@@ -2703,6 +2986,10 @@ function createClient2(options) {
2703
2986
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
2704
2987
  // Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
2705
2988
  fetch: options.fetch ?? providerFetch,
2989
+ // The agent loop owns retries (classified backoff, Retry-After, UI retry
2990
+ // events). The SDK default of 2 silent retries stacked under it: one 5xx
2991
+ // cost up to 3 hidden requests plus SDK sleeps before the loop even saw it.
2992
+ maxRetries: 0,
2706
2993
  ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
2707
2994
  });
2708
2995
  if (!options.fetch) {
@@ -2814,11 +3101,11 @@ async function* runStream2(options) {
2814
3101
  }
2815
3102
  const requestParams = mergeProviderOptions(params, options.providerOptions);
2816
3103
  if (getEnvironment()?.GGAI_DUMP_REQUEST) {
2817
- const fs = await import("fs");
3104
+ const fs2 = await import("fs");
2818
3105
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2819
3106
  const dumpPath = `/tmp/ggai-request-${ts}.json`;
2820
- fs.writeFileSync(dumpPath, JSON.stringify(requestParams, null, 2));
2821
- fs.appendFileSync(
3107
+ fs2.writeFileSync(dumpPath, JSON.stringify(requestParams, null, 2));
3108
+ fs2.appendFileSync(
2822
3109
  "/tmp/ggai-requests.log",
2823
3110
  `[${ts}] ${dumpPath} messages=${requestParams.messages.length}
2824
3111
  `
@@ -3148,7 +3435,7 @@ function toError2(err, provider = "openai") {
3148
3435
  }
3149
3436
 
3150
3437
  // src/providers/openai-codex.ts
3151
- var import_node_os = __toESM(require("os"), 1);
3438
+ var import_node_os2 = __toESM(require("os"), 1);
3152
3439
  var zstd = __toESM(require("@bokuweb/zstd-wasm"), 1);
3153
3440
 
3154
3441
  // src/utils/sse.ts
@@ -3477,6 +3764,15 @@ async function* consumeResponsesEvents(events, ctx) {
3477
3764
  droppedToolCalls
3478
3765
  });
3479
3766
  }
3767
+ const addedOrder = [...toolCalls.keys()];
3768
+ const toolSlots = orderedItems.flatMap((e, i) => e.kind === "tool" ? [i] : []);
3769
+ const sortedTools = toolSlots.map((i) => orderedItems[i]).sort(
3770
+ (a, b) => a?.kind === "tool" && b?.kind === "tool" ? addedOrder.indexOf(a.id) - addedOrder.indexOf(b.id) : 0
3771
+ );
3772
+ toolSlots.forEach((slot, k) => {
3773
+ const entry = sortedTools[k];
3774
+ if (entry) orderedItems[slot] = entry;
3775
+ });
3480
3776
  const seenTool = /* @__PURE__ */ new Set();
3481
3777
  let textInserted = false;
3482
3778
  for (const entry of orderedItems) {
@@ -3553,6 +3849,8 @@ function toResponsesInput(messages, options) {
3553
3849
  let system;
3554
3850
  const input = [];
3555
3851
  const idMap = /* @__PURE__ */ new Map();
3852
+ const loadedTools = /* @__PURE__ */ new Set();
3853
+ let toolLoads = 0;
3556
3854
  for (const msg of messages) {
3557
3855
  if (msg.role === "system") {
3558
3856
  system = msg.content;
@@ -3606,6 +3904,28 @@ function toResponsesInput(messages, options) {
3606
3904
  call_id: mapId(callId, idMap),
3607
3905
  output: text.length > 0 ? text : "(see attached image)"
3608
3906
  });
3907
+ const loads = options.toolSearchLoads;
3908
+ const names = loads ? (result.loadedTools ?? []).filter((name) => loads.has(name) && !loadedTools.has(name)) : [];
3909
+ if (loads && names.length > 0) {
3910
+ for (const name of names) loadedTools.add(name);
3911
+ const loadId = `gg_tool_load_${++toolLoads}`;
3912
+ input.push(
3913
+ {
3914
+ type: "tool_search_call",
3915
+ call_id: loadId,
3916
+ execution: "client",
3917
+ status: "completed",
3918
+ arguments: { query: names.join(" "), limit: names.length }
3919
+ },
3920
+ {
3921
+ type: "tool_search_output",
3922
+ call_id: loadId,
3923
+ execution: "client",
3924
+ status: "completed",
3925
+ tools: names.map((name) => loads.get(name))
3926
+ }
3927
+ );
3928
+ }
3609
3929
  if (options.supportsImages !== false && Array.isArray(result.content)) {
3610
3930
  for (const block of result.content) {
3611
3931
  if (block.type === "image") toolImages.push(block);
@@ -3704,8 +4024,19 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3704
4024
  const url = `${baseUrl}/codex/responses`;
3705
4025
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3706
4026
  const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
4027
+ const deferredPlan = supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
4028
+ const strictTools = options.strictTools ?? true;
4029
+ const toolSearchLoads = deferredPlan ? new Map(
4030
+ [...deferredPlan.loaded].map((name) => {
4031
+ const tool = deferredPlan.byName.get(name);
4032
+ const [wire] = tool ? toCodexTools([tool], strictTools) : [];
4033
+ return [name, { ...wire, defer_loading: true }];
4034
+ })
4035
+ ) : void 0;
4036
+ const liveTools = deferredPlan?.live ?? options.tools;
3707
4037
  const { system, input } = toResponsesInput(downgraded, {
3708
4038
  supportsImages: options.supportsImages,
4039
+ toolSearchLoads,
3709
4040
  mapId: remapCodexId,
3710
4041
  // Stamped reasoning belongs to another transport (e.g. Grok's) and would
3711
4042
  // be rejected here; Codex's own items are never stamped.
@@ -3719,12 +4050,12 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3719
4050
  stream: true,
3720
4051
  instructions: system,
3721
4052
  input,
3722
- tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
4053
+ tool_choice: toCodexToolChoice(options.toolChoice, liveTools),
3723
4054
  parallel_tool_calls: !liteShape,
3724
4055
  include: ["reasoning.encrypted_content"]
3725
4056
  };
3726
- if (options.tools?.length) {
3727
- body.tools = toCodexTools(options.tools, options.strictTools ?? true);
4057
+ if (liveTools?.length) {
4058
+ body.tools = toCodexTools(liveTools, strictTools);
3728
4059
  }
3729
4060
  body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
3730
4061
  if (options.temperature != null && !options.thinking) {
@@ -3750,7 +4081,7 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3750
4081
  Authorization: `Bearer ${options.apiKey}`,
3751
4082
  "OpenAI-Beta": "responses=experimental",
3752
4083
  originator: responsesLite ? "codex_cli_rs" : "ggcoder",
3753
- "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${import_node_os.default.platform()} ${import_node_os.default.release()}; ${import_node_os.default.arch()})`,
4084
+ "User-Agent": responsesLite ? `codex_cli_rs/${CODEX_CLIENT_VERSION}` : `ggcoder (${import_node_os2.default.platform()} ${import_node_os2.default.release()}; ${import_node_os2.default.arch()})`,
3754
4085
  ...responsesLite ? { version: CODEX_CLIENT_VERSION } : {},
3755
4086
  ...liteShape ? { "X-OpenAI-Internal-Codex-Responses-Lite": "true" } : {}
3756
4087
  };
@@ -4874,8 +5205,8 @@ function bound(text) {
4874
5205
  const trimmed = text.trim();
4875
5206
  return trimmed.length > MAX_ERROR_MESSAGE_CHARS ? `${trimmed.slice(0, MAX_ERROR_MESSAGE_CHARS)}\u2026` : trimmed;
4876
5207
  }
4877
- function joinUrl(baseUrl, path) {
4878
- return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
5208
+ function joinUrl(baseUrl, path2) {
5209
+ return `${baseUrl.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
4879
5210
  }
4880
5211
 
4881
5212
  // src/providers/gemini-embeddings.ts
@@ -5466,7 +5797,7 @@ function stream(requested) {
5466
5797
  observePreparedContext(
5467
5798
  replayable === sanitized ? wireMessages : replayable,
5468
5799
  messages,
5469
- options.tools ?? []
5800
+ observedTools(options, messages)
5470
5801
  )
5471
5802
  );
5472
5803
  } catch {
@@ -5474,6 +5805,11 @@ function stream(requested) {
5474
5805
  }
5475
5806
  return entry.stream(messages === options.messages ? options : { ...options, messages });
5476
5807
  }
5808
+ function observedTools(options, messages) {
5809
+ if (!usesDeferredToolLoading(options)) return options.tools ?? [];
5810
+ const plan = planDeferredTools(options.tools, options.deferredTools, messages);
5811
+ return plan ? [...plan.live, ...plan.deferred] : options.tools ?? [];
5812
+ }
5477
5813
  function stripMessageProvenance(messages) {
5478
5814
  let stripped;
5479
5815
  for (let index = 0; index < messages.length; index++) {
@@ -5613,6 +5949,85 @@ function l2Normalize(vector) {
5613
5949
  return vector.map((value) => value / norm);
5614
5950
  }
5615
5951
 
5952
+ // src/utils/preconnect.ts
5953
+ var PRECONNECT_INTERVAL_MS = 3e4;
5954
+ function defaultBaseUrl(provider, accountId) {
5955
+ switch (provider) {
5956
+ case "anthropic":
5957
+ return "https://api.anthropic.com";
5958
+ case "openai":
5959
+ return accountId ? "https://chatgpt.com/backend-api" : "https://api.openai.com/v1";
5960
+ case "gemini":
5961
+ return "https://cloudcode-pa.googleapis.com";
5962
+ case "glm":
5963
+ return "https://api.z.ai/api/coding/paas/v4";
5964
+ case "moonshot":
5965
+ return "https://api.moonshot.ai/v1";
5966
+ case "deepseek":
5967
+ return "https://api.deepseek.com/v1";
5968
+ case "openrouter":
5969
+ return "https://openrouter.ai/api/v1";
5970
+ case "xiaomi":
5971
+ return "https://token-plan-sgp.xiaomimimo.com/v1";
5972
+ case "minimax":
5973
+ return "https://api.minimax.io/anthropic";
5974
+ case "xai":
5975
+ return "https://api.x.ai/v1";
5976
+ case "huggingface":
5977
+ return "https://router.huggingface.co/v1";
5978
+ case "sakana":
5979
+ return "https://api.sakana.ai/v1";
5980
+ case "local":
5981
+ case "palsu":
5982
+ return void 0;
5983
+ }
5984
+ }
5985
+ function isLocalHost(hostname) {
5986
+ const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
5987
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
5988
+ if (host === "::1" || host === "0.0.0.0") return true;
5989
+ if (host.includes(":")) return /^(f[cd]|fe[89ab])/.test(host);
5990
+ const octets = host.split(".").map(Number);
5991
+ if (octets.length !== 4 || octets.some((n) => !Number.isInteger(n))) return false;
5992
+ const [a = 0, b = 0] = octets;
5993
+ return a === 127 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
5994
+ }
5995
+ function preconnectTarget(options) {
5996
+ const raw = options.baseUrl ?? defaultBaseUrl(options.provider, options.accountId);
5997
+ if (!raw || options.provider === "local" || options.provider === "palsu") return void 0;
5998
+ let url;
5999
+ try {
6000
+ url = new URL(raw);
6001
+ } catch {
6002
+ return void 0;
6003
+ }
6004
+ if (url.protocol !== "https:" || isLocalHost(url.hostname)) return void 0;
6005
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
6006
+ url.search = "";
6007
+ url.hash = "";
6008
+ return url;
6009
+ }
6010
+ function preconnectOrigin(options) {
6011
+ return preconnectTarget(options)?.origin;
6012
+ }
6013
+ function preconnectProvider(options) {
6014
+ const target = preconnectTarget(options);
6015
+ if (!target) return void 0;
6016
+ const origin = target.origin;
6017
+ const now = options.now ?? Date.now();
6018
+ const last = options.warmedAt.get(origin);
6019
+ if (last !== void 0 && now - last < PRECONNECT_INTERVAL_MS) return void 0;
6020
+ options.warmedAt.set(origin, now);
6021
+ const fetchImpl = options.fetch ?? providerFetch;
6022
+ void fetchImpl(target.href, {
6023
+ method: "HEAD",
6024
+ redirect: "manual",
6025
+ signal: AbortSignal.timeout(1e4)
6026
+ }).then((response) => response.body?.cancel()).catch(() => {
6027
+ });
6028
+ return origin;
6029
+ }
6030
+
5616
6031
  // src/chat-error.ts
5617
6032
  function causeCode(error) {
5618
6033
  if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") return "";
@@ -6086,10 +6501,12 @@ function palsuRerankScore(query, document) {
6086
6501
  }
6087
6502
  // Annotate the CommonJS export names for ESM import in node:
6088
6503
  0 && (module.exports = {
6504
+ CLAUDE_CODE_FALLBACK_VERSION,
6089
6505
  CODEX_CLIENT_VERSION,
6090
6506
  EventStream,
6091
6507
  FAST_MODE_SUFFIX,
6092
6508
  GGAIError,
6509
+ PRECONNECT_INTERVAL_MS,
6093
6510
  ProviderError,
6094
6511
  REDACTION_MARKER,
6095
6512
  ResponseStalledError,
@@ -6105,6 +6522,8 @@ function palsuRerankScore(query, document) {
6105
6522
  formatChatError,
6106
6523
  formatError,
6107
6524
  formatErrorForDisplay,
6525
+ getClaudeCliUserAgent,
6526
+ getClaudeCodeVersion,
6108
6527
  hasLoneSurrogate,
6109
6528
  isHardBillingMessage,
6110
6529
  isUsageLimitError,
@@ -6118,6 +6537,9 @@ function palsuRerankScore(query, document) {
6118
6537
  palsuThinking,
6119
6538
  palsuToolCall,
6120
6539
  parseFastModeRejection,
6540
+ pendingClaudeCodeVersionRefresh,
6541
+ preconnectOrigin,
6542
+ preconnectProvider,
6121
6543
  prewarmAnthropicCache,
6122
6544
  providerRegistry,
6123
6545
  redactText,