@kenkaiiii/gg-ai 5.87.3 → 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.js CHANGED
@@ -399,6 +399,26 @@ function providerGuidance(provider, message, statusCode) {
399
399
  // src/providers/anthropic.ts
400
400
  import Anthropic from "@anthropic-ai/sdk";
401
401
 
402
+ // src/providers/cache-minimums.ts
403
+ var ANTHROPIC_MIN_CACHEABLE_TOKENS = [
404
+ [/haiku-4-5|opus-4-[56]/, 4096],
405
+ [/haiku-3-5|haiku-3\b/, 2048],
406
+ [/[a-z]-(?:[5-9]|\d{2})(?:-|$)/, 512],
407
+ // 5.x and later families (family name, then major)
408
+ [/sonnet-4|opus-4|sonnet-3-7/, 1024]
409
+ ];
410
+ function anthropicMinCacheableTokens(model) {
411
+ for (const [pattern, min] of ANTHROPIC_MIN_CACHEABLE_TOKENS) {
412
+ if (pattern.test(model)) return min;
413
+ }
414
+ return 1024;
415
+ }
416
+ function estimatePrefixTokens(...parts) {
417
+ let chars = 0;
418
+ for (const part of parts) if (part !== void 0) chars += JSON.stringify(part).length;
419
+ return Math.ceil(chars / 3.5);
420
+ }
421
+
402
422
  // src/utils/event-stream.ts
403
423
  var EventStream = class {
404
424
  queue = [];
@@ -1396,6 +1416,41 @@ function toolResultVideos(content) {
1396
1416
  if (typeof content === "string") return [];
1397
1417
  return content.filter((b) => b.type === "video");
1398
1418
  }
1419
+ function planDeferredTools(tools, catalog, messages) {
1420
+ if (!catalog?.length) return void 0;
1421
+ const byName = /* @__PURE__ */ new Map();
1422
+ for (const tool of catalog) if (!byName.has(tool.name)) byName.set(tool.name, tool);
1423
+ const loaded = /* @__PURE__ */ new Set();
1424
+ for (const message of messages) {
1425
+ if (message.role !== "tool") continue;
1426
+ for (const result of message.content) {
1427
+ for (const name of result.loadedTools ?? []) if (byName.has(name)) loaded.add(name);
1428
+ }
1429
+ }
1430
+ const live = (tools ?? []).filter((tool) => !(byName.has(tool.name) && loaded.has(tool.name)));
1431
+ if (live.length === 0) return void 0;
1432
+ const liveNames = new Set(live.map((tool) => tool.name));
1433
+ const deferred = [...byName.values()].filter((tool) => !liveNames.has(tool.name));
1434
+ return { live, deferred, loaded, byName };
1435
+ }
1436
+ function supportsAnthropicDeferredTools(model) {
1437
+ return /opus-4[-.][5-9]|sonnet-4[-.][5-9]|haiku-4[-.]5|opus-5|sonnet-5|haiku-5|fable-5|mythos-5/.test(
1438
+ model
1439
+ );
1440
+ }
1441
+ function supportsResponsesToolSearch(model) {
1442
+ return /^gpt-(5\.[4-9]|[6-9])/.test(model);
1443
+ }
1444
+ function usesDeferredToolLoading(options) {
1445
+ if (options.provider === "anthropic") {
1446
+ const firstParty = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
1447
+ return firstParty && supportsAnthropicDeferredTools(options.model);
1448
+ }
1449
+ if (options.provider === "openai" && options.accountId) {
1450
+ return supportsResponsesToolSearch(options.model);
1451
+ }
1452
+ return false;
1453
+ }
1399
1454
  function toAnthropicCacheControl(retention, baseUrl) {
1400
1455
  const resolved = retention ?? "short";
1401
1456
  if (resolved === "none") return void 0;
@@ -1438,6 +1493,8 @@ function toAnthropicMessages(messages, cacheControl, options) {
1438
1493
  const keepFallbackBlocks = options?.fallbackBlocks === true;
1439
1494
  const droppedToolCallIds = /* @__PURE__ */ new Set();
1440
1495
  const unresolvedServerToolIds = unresolvedServerToolUseIds(messages);
1496
+ const toolReferences = options?.toolReferences;
1497
+ const referencedTools = /* @__PURE__ */ new Set();
1441
1498
  const trajectoryStartIdx = messages.reduce(
1442
1499
  (last, m, i) => opensTurn(m) ? i : last,
1443
1500
  -1
@@ -1500,16 +1557,43 @@ function toAnthropicMessages(messages, cacheControl, options) {
1500
1557
  if (msg.role === "tool") {
1501
1558
  const results = droppedToolCallIds.size ? msg.content.filter((r) => !droppedToolCallIds.has(r.toolCallId)) : msg.content;
1502
1559
  if (results.length === 0) continue;
1503
- out.push({
1504
- role: "user",
1505
- // Cast covers the video block (used by the Anthropic-compatible MiniMax
1506
- // API), which isn't in the first-party Anthropic tool_result types.
1507
- content: results.map((result) => ({
1560
+ const trailing = [];
1561
+ const blocks = results.map((result) => {
1562
+ const toolUseId = remapAnthropicToolCallId(result.toolCallId, idMap);
1563
+ const refs = toolReferences ? (result.loadedTools ?? []).filter(
1564
+ (name) => toolReferences.has(name) && !referencedTools.has(name)
1565
+ ) : [];
1566
+ if (refs.length === 0) {
1567
+ return {
1568
+ type: "tool_result",
1569
+ tool_use_id: toolUseId,
1570
+ content: toAnthropicToolResultContent(result.content),
1571
+ is_error: result.isError
1572
+ };
1573
+ }
1574
+ for (const name of refs) referencedTools.add(name);
1575
+ const original = toAnthropicToolResultContent(result.content);
1576
+ const parts = typeof original === "string" ? [{ type: "text", text: original }] : original;
1577
+ const first = parts.findIndex((part) => part.type === "text" && part.text !== "");
1578
+ parts.forEach((part, index) => {
1579
+ if (part.type === "text" && part.text === "") return;
1580
+ trailing.push(
1581
+ index === first && part.type === "text" ? { type: "text", text: `Result of tool call ${toolUseId}:
1582
+ ${part.text}` } : part
1583
+ );
1584
+ });
1585
+ return {
1508
1586
  type: "tool_result",
1509
- tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
1510
- content: toAnthropicToolResultContent(result.content),
1587
+ tool_use_id: toolUseId,
1588
+ content: refs.map((name) => ({ type: "tool_reference", tool_name: name })),
1511
1589
  is_error: result.isError
1512
- }))
1590
+ };
1591
+ });
1592
+ out.push({
1593
+ role: "user",
1594
+ // Cast covers the video and tool_reference blocks, which aren't in this
1595
+ // SDK's first-party tool_result types.
1596
+ content: [...blocks, ...trailing]
1513
1597
  });
1514
1598
  }
1515
1599
  }
@@ -1525,7 +1609,8 @@ function toAnthropicMessages(messages, cacheControl, options) {
1525
1609
  markAnthropicCacheBreakpoint(out, tail, cacheControl);
1526
1610
  } else {
1527
1611
  if (anchor !== void 0) markAnthropicCacheBreakpoint(out, anchor, cacheControl);
1528
- markAnthropicCacheBreakpoint(out, tail, { type: "ephemeral" });
1612
+ const longRun = isLongToolRun(messages, trajectoryStartIdx);
1613
+ markAnthropicCacheBreakpoint(out, tail, longRun ? cacheControl : { type: "ephemeral" });
1529
1614
  }
1530
1615
  } else {
1531
1616
  markAnthropicCacheBreakpoint(out, tail, cacheControl);
@@ -1555,6 +1640,28 @@ function toAnthropicMessages(messages, cacheControl, options) {
1555
1640
  }
1556
1641
  return { system, messages: out };
1557
1642
  }
1643
+ var BLOCKING_TOOL_NAMES = /* @__PURE__ */ new Set([
1644
+ "wait_agent",
1645
+ "task_output",
1646
+ "subagent",
1647
+ "ask_user"
1648
+ ]);
1649
+ var LONG_RUN_ROUNDS = 6;
1650
+ function isLongToolRun(messages, trajectoryStartIdx) {
1651
+ let rounds = 0;
1652
+ for (let i = Math.max(0, trajectoryStartIdx + 1); i < messages.length; i++) {
1653
+ const m = messages[i];
1654
+ if (m?.role !== "assistant" || typeof m.content === "string") continue;
1655
+ let called = false;
1656
+ for (const part of m.content) {
1657
+ if (part.type !== "tool_call") continue;
1658
+ called = true;
1659
+ if (BLOCKING_TOOL_NAMES.has(part.name)) return true;
1660
+ }
1661
+ if (called && ++rounds >= LONG_RUN_ROUNDS) return true;
1662
+ }
1663
+ return false;
1664
+ }
1558
1665
  function markAnthropicCacheBreakpoint(out, index, cacheControl) {
1559
1666
  const message = out[index];
1560
1667
  if (!message) return;
@@ -1575,6 +1682,18 @@ function markAnthropicCacheBreakpoint(out, index, cacheControl) {
1575
1682
  }
1576
1683
  }
1577
1684
  function toAnthropicTools(tools, options) {
1685
+ const deferred = (options?.deferred ?? []).map(
1686
+ (tool) => ({
1687
+ name: tool.name,
1688
+ description: tool.description,
1689
+ input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
1690
+ ...options?.enableFineGrainedToolStreaming ? { eager_input_streaming: true } : {},
1691
+ defer_loading: true
1692
+ })
1693
+ );
1694
+ return [...toAnthropicLiveTools(tools, options), ...deferred];
1695
+ }
1696
+ function toAnthropicLiveTools(tools, options) {
1578
1697
  return tools.map((tool, index) => {
1579
1698
  const anthropicTool = {
1580
1699
  name: tool.name,
@@ -1861,6 +1980,7 @@ function parseToolArguments(argsJson) {
1861
1980
  // src/utils/provider-fetch.ts
1862
1981
  import { Agent, EnvHttpProxyAgent, fetch as undiciFetch } from "undici";
1863
1982
  var PROVIDER_TRANSPORT_TIMEOUT_MS = 45 * 6e4;
1983
+ var PROVIDER_KEEP_ALIVE_TIMEOUT_MS = 3e4;
1864
1984
  function nodeUsesEnvProxy({
1865
1985
  env = process.env,
1866
1986
  execArgv = process.execArgv,
@@ -1873,7 +1993,11 @@ function nodeUsesEnvProxy({
1873
1993
  return major >= 24 || major === 22 && minor >= 21;
1874
1994
  }
1875
1995
  function createProviderFetch(timeoutMs, { useEnvProxy = nodeUsesEnvProxy() } = {}) {
1876
- const timeouts = { headersTimeout: timeoutMs, bodyTimeout: timeoutMs };
1996
+ const timeouts = {
1997
+ headersTimeout: timeoutMs,
1998
+ bodyTimeout: timeoutMs,
1999
+ keepAliveTimeout: PROVIDER_KEEP_ALIVE_TIMEOUT_MS
2000
+ };
1877
2001
  const dispatcher = useEnvProxy ? new EnvHttpProxyAgent(timeouts) : new Agent(timeouts);
1878
2002
  const builtinFetch = globalThis.fetch;
1879
2003
  const providerFetch2 = async (input, init2) => {
@@ -2028,6 +2152,9 @@ async function prewarmAnthropicCache(options) {
2028
2152
  // prewarmed prompt cache actually hits — both are gated by the flag.
2029
2153
  enableFineGrainedToolStreaming: fineGrainedToolStreamingEnabled()
2030
2154
  }) : void 0;
2155
+ if (estimatePrefixTokens(fullSystem, tools) < anthropicMinCacheableTokens(options.model)) {
2156
+ return;
2157
+ }
2031
2158
  await client.messages.create(
2032
2159
  {
2033
2160
  model: options.model,
@@ -2068,8 +2195,11 @@ async function* runStream(options) {
2068
2195
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
2069
2196
  const fallbackKey = serverFallbackKey(options);
2070
2197
  const useServerFallback = options.provider !== "minimax" && isDirectAnthropicApi(options.baseUrl) && !anthropicServerFallback.disabled.has(fallbackKey);
2198
+ const deferredPlan = options.provider === "anthropic" && isDirectAnthropicApi(options.baseUrl) && supportsAnthropicDeferredTools(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgradedMessages) : void 0;
2199
+ const toolReferences = deferredPlan?.loaded;
2071
2200
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl, {
2072
- fallbackBlocks: useServerFallback
2201
+ fallbackBlocks: useServerFallback,
2202
+ toolReferences
2073
2203
  });
2074
2204
  const useFastMode = options.speed === "fast" && options.provider === "anthropic" && isDirectAnthropicApi(options.baseUrl);
2075
2205
  const system = isOAuth ? [
@@ -2111,20 +2241,20 @@ async function* runStream(options) {
2111
2241
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
2112
2242
  ...options.topP != null ? { top_p: options.topP } : {},
2113
2243
  ...options.stop ? { stop_sequences: options.stop } : {},
2114
- ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
2244
+ ...(deferredPlan?.live ?? options.tools)?.length || options.serverTools?.length || options.webSearch ? (() => {
2115
2245
  const reservedServerNames = /* @__PURE__ */ new Set();
2116
2246
  if (options.webSearch) reservedServerNames.add("web_search");
2117
2247
  for (const t of options.serverTools ?? []) {
2118
2248
  const name = t.name;
2119
2249
  if (name) reservedServerNames.add(name);
2120
2250
  }
2121
- const clientTools = options.tools?.length ? toAnthropicTools(
2122
- options.tools.filter((t) => !reservedServerNames.has(t.name)),
2123
- {
2124
- ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
2125
- ...supportsFirstPartyToolExtras && fineGrainedToolStreamingEnabled() ? { enableFineGrainedToolStreaming: true } : {}
2126
- }
2127
- ) : [];
2251
+ const notReserved = (t) => !reservedServerNames.has(t.name);
2252
+ const liveTools = deferredPlan?.live ?? options.tools ?? [];
2253
+ const clientTools = liveTools.length ? toAnthropicTools(liveTools.filter(notReserved), {
2254
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
2255
+ ...supportsFirstPartyToolExtras && fineGrainedToolStreamingEnabled() ? { enableFineGrainedToolStreaming: true } : {},
2256
+ ...deferredPlan ? { deferred: deferredPlan.deferred.filter(notReserved) } : {}
2257
+ }) : [];
2128
2258
  return {
2129
2259
  tools: [
2130
2260
  ...clientTools,
@@ -2145,6 +2275,13 @@ async function* runStream(options) {
2145
2275
  ...useFastMode ? { speed: "fast" } : {},
2146
2276
  stream: useStreaming
2147
2277
  };
2278
+ if (options.prewarm && estimatePrefixTokens(baseParams.system, baseParams.tools) < anthropicMinCacheableTokens(options.model)) {
2279
+ return {
2280
+ message: { role: "assistant", content: [] },
2281
+ stopReason: "end_turn",
2282
+ usage: { inputTokens: 0, outputTokens: 0 }
2283
+ };
2284
+ }
2148
2285
  const params = mergeProviderOptions(baseParams, options.providerOptions);
2149
2286
  const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
2150
2287
  const betaHeaders = [
@@ -2178,7 +2315,10 @@ async function* runStream(options) {
2178
2315
  const { fallbacks: _dropped, ...rest } = params;
2179
2316
  const retryParams = {
2180
2317
  ...rest,
2181
- messages: toAnthropicMessages(downgradedMessages, cacheControl, { fallbackBlocks: false }).messages
2318
+ messages: toAnthropicMessages(downgradedMessages, cacheControl, {
2319
+ fallbackBlocks: false,
2320
+ toolReferences
2321
+ }).messages
2182
2322
  };
2183
2323
  return create(
2184
2324
  retryParams,
@@ -2754,6 +2894,10 @@ function createClient2(options) {
2754
2894
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
2755
2895
  // Default transport lifts undici's 300s header/body caps (see provider-fetch.ts).
2756
2896
  fetch: options.fetch ?? providerFetch,
2897
+ // The agent loop owns retries (classified backoff, Retry-After, UI retry
2898
+ // events). The SDK default of 2 silent retries stacked under it: one 5xx
2899
+ // cost up to 3 hidden requests plus SDK sleeps before the loop even saw it.
2900
+ maxRetries: 0,
2757
2901
  ...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
2758
2902
  });
2759
2903
  if (!options.fetch) {
@@ -3528,6 +3672,15 @@ async function* consumeResponsesEvents(events, ctx) {
3528
3672
  droppedToolCalls
3529
3673
  });
3530
3674
  }
3675
+ const addedOrder = [...toolCalls.keys()];
3676
+ const toolSlots = orderedItems.flatMap((e, i) => e.kind === "tool" ? [i] : []);
3677
+ const sortedTools = toolSlots.map((i) => orderedItems[i]).sort(
3678
+ (a, b) => a?.kind === "tool" && b?.kind === "tool" ? addedOrder.indexOf(a.id) - addedOrder.indexOf(b.id) : 0
3679
+ );
3680
+ toolSlots.forEach((slot, k) => {
3681
+ const entry = sortedTools[k];
3682
+ if (entry) orderedItems[slot] = entry;
3683
+ });
3531
3684
  const seenTool = /* @__PURE__ */ new Set();
3532
3685
  let textInserted = false;
3533
3686
  for (const entry of orderedItems) {
@@ -3604,6 +3757,8 @@ function toResponsesInput(messages, options) {
3604
3757
  let system;
3605
3758
  const input = [];
3606
3759
  const idMap = /* @__PURE__ */ new Map();
3760
+ const loadedTools = /* @__PURE__ */ new Set();
3761
+ let toolLoads = 0;
3607
3762
  for (const msg of messages) {
3608
3763
  if (msg.role === "system") {
3609
3764
  system = msg.content;
@@ -3657,6 +3812,28 @@ function toResponsesInput(messages, options) {
3657
3812
  call_id: mapId(callId, idMap),
3658
3813
  output: text.length > 0 ? text : "(see attached image)"
3659
3814
  });
3815
+ const loads = options.toolSearchLoads;
3816
+ const names = loads ? (result.loadedTools ?? []).filter((name) => loads.has(name) && !loadedTools.has(name)) : [];
3817
+ if (loads && names.length > 0) {
3818
+ for (const name of names) loadedTools.add(name);
3819
+ const loadId = `gg_tool_load_${++toolLoads}`;
3820
+ input.push(
3821
+ {
3822
+ type: "tool_search_call",
3823
+ call_id: loadId,
3824
+ execution: "client",
3825
+ status: "completed",
3826
+ arguments: { query: names.join(" "), limit: names.length }
3827
+ },
3828
+ {
3829
+ type: "tool_search_output",
3830
+ call_id: loadId,
3831
+ execution: "client",
3832
+ status: "completed",
3833
+ tools: names.map((name) => loads.get(name))
3834
+ }
3835
+ );
3836
+ }
3660
3837
  if (options.supportsImages !== false && Array.isArray(result.content)) {
3661
3838
  for (const block of result.content) {
3662
3839
  if (block.type === "image") toolImages.push(block);
@@ -3755,8 +3932,19 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3755
3932
  const url = `${baseUrl}/codex/responses`;
3756
3933
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
3757
3934
  const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
3935
+ const deferredPlan = supportsResponsesToolSearch(options.model) ? planDeferredTools(options.tools, options.deferredTools, downgraded) : void 0;
3936
+ const strictTools = options.strictTools ?? true;
3937
+ const toolSearchLoads = deferredPlan ? new Map(
3938
+ [...deferredPlan.loaded].map((name) => {
3939
+ const tool = deferredPlan.byName.get(name);
3940
+ const [wire] = tool ? toCodexTools([tool], strictTools) : [];
3941
+ return [name, { ...wire, defer_loading: true }];
3942
+ })
3943
+ ) : void 0;
3944
+ const liveTools = deferredPlan?.live ?? options.tools;
3758
3945
  const { system, input } = toResponsesInput(downgraded, {
3759
3946
  supportsImages: options.supportsImages,
3947
+ toolSearchLoads,
3760
3948
  mapId: remapCodexId,
3761
3949
  // Stamped reasoning belongs to another transport (e.g. Grok's) and would
3762
3950
  // be rejected here; Codex's own items are never stamped.
@@ -3770,12 +3958,12 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
3770
3958
  stream: true,
3771
3959
  instructions: system,
3772
3960
  input,
3773
- tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
3961
+ tool_choice: toCodexToolChoice(options.toolChoice, liveTools),
3774
3962
  parallel_tool_calls: !liteShape,
3775
3963
  include: ["reasoning.encrypted_content"]
3776
3964
  };
3777
- if (options.tools?.length) {
3778
- body.tools = toCodexTools(options.tools, options.strictTools ?? true);
3965
+ if (liveTools?.length) {
3966
+ body.tools = toCodexTools(liveTools, strictTools);
3779
3967
  }
3780
3968
  body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ggcoder");
3781
3969
  if (options.temperature != null && !options.thinking) {
@@ -5517,7 +5705,7 @@ function stream(requested) {
5517
5705
  observePreparedContext(
5518
5706
  replayable === sanitized ? wireMessages : replayable,
5519
5707
  messages,
5520
- options.tools ?? []
5708
+ observedTools(options, messages)
5521
5709
  )
5522
5710
  );
5523
5711
  } catch {
@@ -5525,6 +5713,11 @@ function stream(requested) {
5525
5713
  }
5526
5714
  return entry.stream(messages === options.messages ? options : { ...options, messages });
5527
5715
  }
5716
+ function observedTools(options, messages) {
5717
+ if (!usesDeferredToolLoading(options)) return options.tools ?? [];
5718
+ const plan = planDeferredTools(options.tools, options.deferredTools, messages);
5719
+ return plan ? [...plan.live, ...plan.deferred] : options.tools ?? [];
5720
+ }
5528
5721
  function stripMessageProvenance(messages) {
5529
5722
  let stripped;
5530
5723
  for (let index = 0; index < messages.length; index++) {
@@ -5664,6 +5857,85 @@ function l2Normalize(vector) {
5664
5857
  return vector.map((value) => value / norm);
5665
5858
  }
5666
5859
 
5860
+ // src/utils/preconnect.ts
5861
+ var PRECONNECT_INTERVAL_MS = 3e4;
5862
+ function defaultBaseUrl(provider, accountId) {
5863
+ switch (provider) {
5864
+ case "anthropic":
5865
+ return "https://api.anthropic.com";
5866
+ case "openai":
5867
+ return accountId ? "https://chatgpt.com/backend-api" : "https://api.openai.com/v1";
5868
+ case "gemini":
5869
+ return "https://cloudcode-pa.googleapis.com";
5870
+ case "glm":
5871
+ return "https://api.z.ai/api/coding/paas/v4";
5872
+ case "moonshot":
5873
+ return "https://api.moonshot.ai/v1";
5874
+ case "deepseek":
5875
+ return "https://api.deepseek.com/v1";
5876
+ case "openrouter":
5877
+ return "https://openrouter.ai/api/v1";
5878
+ case "xiaomi":
5879
+ return "https://token-plan-sgp.xiaomimimo.com/v1";
5880
+ case "minimax":
5881
+ return "https://api.minimax.io/anthropic";
5882
+ case "xai":
5883
+ return "https://api.x.ai/v1";
5884
+ case "huggingface":
5885
+ return "https://router.huggingface.co/v1";
5886
+ case "sakana":
5887
+ return "https://api.sakana.ai/v1";
5888
+ case "local":
5889
+ case "palsu":
5890
+ return void 0;
5891
+ }
5892
+ }
5893
+ function isLocalHost(hostname) {
5894
+ const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
5895
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
5896
+ if (host === "::1" || host === "0.0.0.0") return true;
5897
+ if (host.includes(":")) return /^(f[cd]|fe[89ab])/.test(host);
5898
+ const octets = host.split(".").map(Number);
5899
+ if (octets.length !== 4 || octets.some((n) => !Number.isInteger(n))) return false;
5900
+ const [a = 0, b = 0] = octets;
5901
+ return a === 127 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
5902
+ }
5903
+ function preconnectTarget(options) {
5904
+ const raw = options.baseUrl ?? defaultBaseUrl(options.provider, options.accountId);
5905
+ if (!raw || options.provider === "local" || options.provider === "palsu") return void 0;
5906
+ let url;
5907
+ try {
5908
+ url = new URL(raw);
5909
+ } catch {
5910
+ return void 0;
5911
+ }
5912
+ if (url.protocol !== "https:" || isLocalHost(url.hostname)) return void 0;
5913
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
5914
+ url.search = "";
5915
+ url.hash = "";
5916
+ return url;
5917
+ }
5918
+ function preconnectOrigin(options) {
5919
+ return preconnectTarget(options)?.origin;
5920
+ }
5921
+ function preconnectProvider(options) {
5922
+ const target = preconnectTarget(options);
5923
+ if (!target) return void 0;
5924
+ const origin = target.origin;
5925
+ const now = options.now ?? Date.now();
5926
+ const last = options.warmedAt.get(origin);
5927
+ if (last !== void 0 && now - last < PRECONNECT_INTERVAL_MS) return void 0;
5928
+ options.warmedAt.set(origin, now);
5929
+ const fetchImpl = options.fetch ?? providerFetch;
5930
+ void fetchImpl(target.href, {
5931
+ method: "HEAD",
5932
+ redirect: "manual",
5933
+ signal: AbortSignal.timeout(1e4)
5934
+ }).then((response) => response.body?.cancel()).catch(() => {
5935
+ });
5936
+ return origin;
5937
+ }
5938
+
5667
5939
  // src/chat-error.ts
5668
5940
  function causeCode(error) {
5669
5941
  if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") return "";
@@ -6141,6 +6413,7 @@ export {
6141
6413
  EventStream,
6142
6414
  FAST_MODE_SUFFIX,
6143
6415
  GGAIError,
6416
+ PRECONNECT_INTERVAL_MS,
6144
6417
  ProviderError,
6145
6418
  REDACTED as REDACTION_MARKER,
6146
6419
  ResponseStalledError,
@@ -6172,6 +6445,8 @@ export {
6172
6445
  palsuToolCall,
6173
6446
  parseFastModeRejection,
6174
6447
  pendingClaudeCodeVersionRefresh,
6448
+ preconnectOrigin,
6449
+ preconnectProvider,
6175
6450
  prewarmAnthropicCache,
6176
6451
  providerRegistry,
6177
6452
  redactText,