@bike4mind/cli 3.4.0 → 4.0.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.
@@ -1,19 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { F as generateCliTools, G as setWebSocketToolExecutor, J as DeferredToolRegistry, U as wrapTools, V as buildSystemPrompt, Y as ReActAgent, a as loadProjectContext, c as createSseBackend, i as buildProjectAgentStore, l as ServerLlmBackend, n as BackgroundAgentManager, o as McpManager, q as getPlanModeFilePath, r as SubagentOrchestrator, t as AgentHistoryStore, ut as Logger } from "./AgentHistoryStore-ClO2zOn6.mjs";
3
- import { A as ARTIFACT_REMOVED_PLACEHOLDER, I as CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS, Nt as stripDeliveredArtifactBlocks, Pt as stripToolArtifactMarkup, R as ChatModels, Tt as isUserInitiatedAbort, Y as MODEL_INFO_FIELD_GROUP_OF, Z as ModelBackend, b as logger, gt as filterToolArtifactMarkup, ht as escapeThinkMarkers, k as ARTIFACT_DELIVERED_PLACEHOLDER, mt as createThinkMarkerEscaper, nt as PermissionDeniedError, xt as hasDeliverablePinnedArtifact, yt as getQuestErrorCode, z as CompletionReasoningEffortSchema } from "./ConfigStore-CbVFLXMH.mjs";
2
+ import { F as generateCliTools, G as createToolSearchTool, U as DeferredToolRegistry, V as buildSystemPrompt, W as ReActAgent, Y as setWebSocketToolExecutor, Z as getPlanModeFilePath, a as loadProjectContext, c as createSseBackend, i as buildProjectAgentStore, l as ServerLlmBackend, n as BackgroundAgentManager, o as McpManager, q as wrapTools, r as SubagentOrchestrator, st as Logger, t as AgentHistoryStore } from "./AgentHistoryStore-DgqmcVLN.mjs";
3
+ import { $ as ModelBackend, A as ARTIFACT_REMOVED_PLACEHOLDER, At as isUserInitiatedAbort, B as ChatModels, Et as hasDeliverablePinnedArtifact, Gt as visibleReplyText, P as AudioMimeType, R as CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS, Rt as stripDeliveredArtifactBlocks, V as CompletionReasoningEffortSchema, Z as MODEL_INFO_FIELD_GROUP_OF, b as logger, bt as escapeThinkMarkers, dt as TTS_MAX_INPUT_CHARS, it as PermissionDeniedError, k as ARTIFACT_DELIVERED_PLACEHOLDER, lt as SupportedFabFileMimeTypes, ut as TTS_DEFAULT_MODEL, vt as completionsSupportsToolSearch, wt as getQuestErrorCode, xt as filterToolArtifactMarkup, yt as createThinkMarkerEscaper, zt as stripToolArtifactMarkup } from "./ConfigStore-BwqsQ0U9.mjs";
4
4
  import "crypto";
5
+ import "path";
5
6
  import { v4 } from "uuid";
6
7
  import "axios";
8
+ import "node:crypto";
7
9
  import "@aws-sdk/client-bedrock-runtime";
8
10
  import "openai";
11
+ import "file-type";
9
12
  import "@anthropic-ai/sdk";
10
13
  import "@aws-sdk/client-cloudwatch";
14
+ import "@anthropic-ai/sdk/core/streaming";
11
15
  import "@google/genai";
12
16
  import "lodash/pick.js";
13
17
  import "openai/streaming";
14
18
  import { Ollama } from "ollama";
15
19
  import { Agent } from "undici";
20
+ import "@aws-sdk/client-sqs";
21
+ import "@aws-sdk/client-apigatewaymanagementapi";
22
+ import "@aws-sdk/client-lambda";
16
23
  import WebSocket from "ws";
24
+ ChatModels.KIMI_K2_THINKING_BEDROCK, ChatModels.KIMI_K2_5_BEDROCK, ChatModels.DEEPSEEK_R1_BEDROCK, ChatModels.DEEPSEEK_FLASH, ChatModels.DEEPSEEK_V4_PRO, ChatModels.KIMI_K3;
25
+ ChatModels.CLAUDE_4_7_OPUS, ChatModels.CLAUDE_4_7_OPUS_BEDROCK, ChatModels.CLAUDE_4_8_OPUS, ChatModels.CLAUDE_4_8_OPUS_BEDROCK, ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK;
26
+ ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK, ChatModels.CLAUDE_FABLE_5, ChatModels.CLAUDE_FABLE_5_BEDROCK, ChatModels.CLAUDE_FABLE_5_1_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_OPUS, ChatModels.CLAUDE_5_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK;
27
+ ModelBackend.Anthropic, ModelBackend.Gemini, ModelBackend.XAI, ModelBackend.Ollama, ModelBackend.BFL, ModelBackend.LocalImage, ModelBackend.AWS;
28
+ //#endregion
17
29
  //#region ../../b4m-core/llm-adapters/dist/index.mjs
18
30
  /**
19
31
  * A tool failure that must end the turn rather than be fed back to the model as a
@@ -38,7 +50,7 @@ function isTurnEndingTool(name, tools) {
38
50
  */
39
51
  function shouldEndTurnAfterTools(calledToolNames, tools, roundText) {
40
52
  const names = calledToolNames.filter((name) => Boolean(name));
41
- if (names.length === 0 || !roundText?.trim()) return false;
53
+ if (names.length === 0 || !visibleReplyText(roundText).trim()) return false;
42
54
  return names.every((name) => isTurnEndingTool(name, tools));
43
55
  }
44
56
  /**
@@ -668,12 +680,36 @@ var NoOpCachingAdapter = class {
668
680
  extractCacheStats() {}
669
681
  };
670
682
  ModelBackend.Anthropic, new AnthropicCachingAdapter(), ModelBackend.OpenAI, new OpenAICachingAdapter(), ModelBackend.Gemini, new GeminiCachingAdapter(), ModelBackend.Bedrock, new AnthropicCachingAdapter(), ModelBackend.XAI, new XAICachingAdapter(), ModelBackend.Kimi, new KimiCachingAdapter(), ModelBackend.DeepSeek, new DeepSeekCachingAdapter(), ModelBackend.Ollama, new NoOpCachingAdapter(), ModelBackend.BFL, new NoOpCachingAdapter(), ModelBackend.VoyageAI, new NoOpCachingAdapter(), ModelBackend.AWS, new NoOpCachingAdapter(), ModelBackend.LocalImage, new NoOpCachingAdapter();
671
- ChatModels.KIMI_K2_THINKING_BEDROCK, ChatModels.KIMI_K2_5_BEDROCK, ChatModels.DEEPSEEK_R1_BEDROCK, ChatModels.DEEPSEEK_FLASH, ChatModels.DEEPSEEK_V4_PRO, ChatModels.KIMI_K3;
672
- ChatModels.CLAUDE_4_7_OPUS, ChatModels.CLAUDE_4_7_OPUS_BEDROCK, ChatModels.CLAUDE_4_8_OPUS, ChatModels.CLAUDE_4_8_OPUS_BEDROCK, ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK;
673
- ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK, ChatModels.CLAUDE_FABLE_5, ChatModels.CLAUDE_FABLE_5_BEDROCK, ChatModels.CLAUDE_FABLE_5_1_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_OPUS, ChatModels.CLAUDE_5_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK;
674
683
  new Logger();
675
684
  ChatModels.CLAUDE_4_5_SONNET, ChatModels.CLAUDE_4_1_OPUS, ChatModels.CLAUDE_4_5_HAIKU, ChatModels.CLAUDE_4_5_OPUS, ChatModels.CLAUDE_4_6_SONNET, ChatModels.CLAUDE_4_6_OPUS;
676
685
  ChatModels.CLAUDE_4_5_SONNET_BEDROCK, ChatModels.CLAUDE_4_5_HAIKU_BEDROCK, ChatModels.CLAUDE_4_5_OPUS_BEDROCK, ChatModels.CLAUDE_4_6_SONNET_BEDROCK, ChatModels.CLAUDE_4_6_OPUS_BEDROCK;
686
+ /**
687
+ * Models served by ConverseBedrockBackend. Adding a Converse-compatible Bedrock model is a
688
+ * `ChatModels` entry plus a record here: routing (getLlmByModel's Bedrock branch), the
689
+ * Bedrock listing (UndifferentiatedBedrockBackend) and request shaping all read this table.
690
+ */
691
+ const CONVERSE_BEDROCK_MODELS = [{
692
+ id: ChatModels.NOVA_PRO_BEDROCK,
693
+ type: "text",
694
+ name: "Amazon Nova Pro",
695
+ backend: ModelBackend.Bedrock,
696
+ contextWindow: 3e5,
697
+ max_tokens: 1e4,
698
+ can_stream: true,
699
+ pricing: { 3e5: {
700
+ input: 8e-7,
701
+ output: 32e-7
702
+ } },
703
+ inlineReasoningTag: "thinking",
704
+ supportsVision: true,
705
+ supportsTools: true,
706
+ supportsImageVariation: false,
707
+ releaseDate: "2024-12-03",
708
+ rank: 5,
709
+ description: "Amazon's Nova Pro via AWS Bedrock - multimodal (text and images), tool use, and a 300K-token context window."
710
+ }];
711
+ new Set(CONVERSE_BEDROCK_MODELS.map((m) => String(m.id)));
712
+ ChatModels.DEEPSEEK_R1_BEDROCK, ModelBackend.Bedrock, ChatModels.DEEPSEEK_V3_1, ModelBackend.Bedrock;
677
713
  /** Ollama `done_reason` (chat/generate responses). */
678
714
  function normalizeOllamaDoneReason(reason) {
679
715
  if (!reason) return void 0;
@@ -741,8 +777,8 @@ function convertMessageToOpenAIFormat(msg, options = {}) {
741
777
  tool_call_id: block.tool_use_id
742
778
  }));
743
779
  }
744
- if (msg.requiresTool !== void 0) {
745
- const { requiresTool: _requiresTool, ...providerSafe } = msg;
780
+ if (msg.requiresTool !== void 0 || msg.cache !== void 0 || msg.perTurn !== void 0) {
781
+ const { requiresTool: _requiresTool, cache: _cache, perTurn: _perTurn, ...providerSafe } = msg;
746
782
  return [providerSafe];
747
783
  }
748
784
  return [msg];
@@ -818,41 +854,35 @@ var OllamaBackend = class OllamaBackend {
818
854
  fetch: fetchWithTimeout
819
855
  });
820
856
  }
857
+ /** Rejects when Ollama is unreachable; getAvailableModels needs a down host to differ from an empty one. */
821
858
  async getModelInfo() {
822
- try {
823
- const models = await this._api.list();
824
- const isSelfHost = process.env.B4M_SELF_HOST === "true";
825
- return await Promise.all(models.models.map(async (model) => {
826
- const { capabilities, contextWindow: reportedContextWindow } = await this.getModelDetailsCached(model.name);
827
- const contextWindow = OllamaBackend.effectiveContextWindow(reportedContextWindow);
828
- return {
829
- id: model.name,
830
- type: "text",
831
- name: model.name,
832
- backend: ModelBackend.Ollama,
833
- contextWindow,
834
- max_tokens: OllamaBackend.advertisedOutputCap(contextWindow),
835
- supportsImageVariation: false,
836
- pricing: { [contextWindow]: {
837
- input: 0,
838
- output: 0
839
- } },
840
- freeToRun: true,
841
- supportsVision: capabilities.includes("vision"),
842
- supportsTools: capabilities.includes("tools"),
843
- can_think: capabilities.includes("thinking"),
844
- can_stream: true,
845
- logoFile: "Ollama_Logo.svg",
846
- rank: 1,
847
- description: isSelfHost ? "Runs locally on your own hardware via Ollama. No API key required, and nothing leaves your machine. Performance and capabilities vary by model." : `This model is served from ${process.env.APP_NAME ? `${process.env.APP_NAME}'s` : "the platform"} Ollama servers using publicly available open-source models. Performance and capabilities vary by model.`
848
- };
849
- }));
850
- } catch (error) {
851
- let errorMessage = error instanceof Error ? error.message : String(error);
852
- if (error instanceof Error && error.message.includes("503 Service Temporarily Unavailable")) errorMessage = "Ollama server is temporarily unavailable. Please try again later.";
853
- this._logger.warn("[OllamaBackend] Error fetching model info from Ollama:", errorMessage);
854
- return [];
855
- }
859
+ const models = await this._api.list();
860
+ const isSelfHost = process.env.B4M_SELF_HOST === "true";
861
+ return await Promise.all(models.models.map(async (model) => {
862
+ const { capabilities, contextWindow: reportedContextWindow } = await this.getModelDetailsCached(model.name);
863
+ const contextWindow = OllamaBackend.effectiveContextWindow(reportedContextWindow);
864
+ return {
865
+ id: model.name,
866
+ type: "text",
867
+ name: model.name,
868
+ backend: ModelBackend.Ollama,
869
+ contextWindow,
870
+ max_tokens: OllamaBackend.advertisedOutputCap(contextWindow),
871
+ supportsImageVariation: false,
872
+ pricing: { [contextWindow]: {
873
+ input: 0,
874
+ output: 0
875
+ } },
876
+ freeToRun: true,
877
+ supportsVision: capabilities.includes("vision"),
878
+ supportsTools: capabilities.includes("tools"),
879
+ can_think: capabilities.includes("thinking"),
880
+ can_stream: true,
881
+ logoFile: "Ollama_Logo.svg",
882
+ rank: 1,
883
+ description: isSelfHost ? "Runs locally on your own hardware via Ollama. No API key required, and nothing leaves your machine. Performance and capabilities vary by model." : `This model is served from ${process.env.APP_NAME ? `${process.env.APP_NAME}'s` : "the platform"} Ollama servers using publicly available open-source models. Performance and capabilities vary by model.`
884
+ };
885
+ }));
856
886
  }
857
887
  /** Ollama's default when a model doesn't report a context length. */
858
888
  static DEFAULT_CONTEXT_WINDOW = 8192;
@@ -1404,9 +1434,96 @@ ChatModels.GPT5_6_SOL, ChatModels.GPT5_6_LUNA, ChatModels.GPT5_6_TERRA;
1404
1434
  ModelBackend.Bedrock, ModelBackend.AWS, ModelBackend.OpenAI, ModelBackend.Anthropic, ModelBackend.Gemini, ModelBackend.Ollama, ModelBackend.BFL, ModelBackend.XAI, ModelBackend.Kimi, ModelBackend.DeepSeek, ModelBackend.LocalImage, ModelBackend.VoyageAI;
1405
1435
  Object.values(ModelBackend);
1406
1436
  Object.entries(MODEL_INFO_FIELD_GROUP_OF);
1407
- ModelBackend.Anthropic, ModelBackend.Gemini, ModelBackend.XAI, ModelBackend.Ollama, ModelBackend.BFL, ModelBackend.LocalImage, ModelBackend.AWS;
1408
1437
  CompletionReasoningEffortSchema.options;
1409
1438
  ModelBackend.Ollama, ModelBackend.LocalImage;
1439
+ Object.assign(Object.create(null), {
1440
+ txt: SupportedFabFileMimeTypes.TXT_PLAIN,
1441
+ ini: SupportedFabFileMimeTypes.TXT_PLAIN,
1442
+ env: SupportedFabFileMimeTypes.TXT_PLAIN,
1443
+ conf: SupportedFabFileMimeTypes.TXT_PLAIN,
1444
+ log: SupportedFabFileMimeTypes.TXT_PLAIN,
1445
+ sql: SupportedFabFileMimeTypes.TXT_PLAIN,
1446
+ text: SupportedFabFileMimeTypes.TXT_PLAIN,
1447
+ md: SupportedFabFileMimeTypes.TXT_MARKDOWN,
1448
+ mdx: SupportedFabFileMimeTypes.TXT_MARKDOWN,
1449
+ mmd: SupportedFabFileMimeTypes.TXT_PLAIN,
1450
+ html: SupportedFabFileMimeTypes.HTML,
1451
+ htm: SupportedFabFileMimeTypes.HTML,
1452
+ shtml: SupportedFabFileMimeTypes.HTML,
1453
+ csv: SupportedFabFileMimeTypes.CSV,
1454
+ jpg: SupportedFabFileMimeTypes.JPG,
1455
+ jpeg: SupportedFabFileMimeTypes.JPG,
1456
+ jfif: SupportedFabFileMimeTypes.JPG,
1457
+ jpe: SupportedFabFileMimeTypes.JPG,
1458
+ png: SupportedFabFileMimeTypes.PNG,
1459
+ gif: SupportedFabFileMimeTypes.GIF,
1460
+ svg: SupportedFabFileMimeTypes.SVG,
1461
+ webp: SupportedFabFileMimeTypes.WEBP,
1462
+ pdf: SupportedFabFileMimeTypes.PDF,
1463
+ json: SupportedFabFileMimeTypes.JSON,
1464
+ xml: SupportedFabFileMimeTypes.XML,
1465
+ docx: SupportedFabFileMimeTypes.DOCX,
1466
+ pptx: SupportedFabFileMimeTypes.PPTX,
1467
+ xlsx: SupportedFabFileMimeTypes.XLSX,
1468
+ xls: SupportedFabFileMimeTypes.XLS,
1469
+ js: SupportedFabFileMimeTypes.JS,
1470
+ mjs: SupportedFabFileMimeTypes.JS,
1471
+ cjs: SupportedFabFileMimeTypes.JS,
1472
+ jsx: SupportedFabFileMimeTypes.JSX,
1473
+ ts: SupportedFabFileMimeTypes.TS,
1474
+ tsx: SupportedFabFileMimeTypes.TS,
1475
+ py: SupportedFabFileMimeTypes.PY,
1476
+ java: SupportedFabFileMimeTypes.JAVA,
1477
+ cpp: SupportedFabFileMimeTypes.CPP,
1478
+ c: SupportedFabFileMimeTypes.CPP,
1479
+ h: SupportedFabFileMimeTypes.CPP,
1480
+ cs: SupportedFabFileMimeTypes.CS,
1481
+ php: SupportedFabFileMimeTypes.PHP,
1482
+ rb: SupportedFabFileMimeTypes.RUBY,
1483
+ go: SupportedFabFileMimeTypes.GO,
1484
+ swift: SupportedFabFileMimeTypes.SWIFT,
1485
+ kt: SupportedFabFileMimeTypes.KOTLIN,
1486
+ rs: SupportedFabFileMimeTypes.RUST,
1487
+ css: SupportedFabFileMimeTypes.CSS,
1488
+ less: SupportedFabFileMimeTypes.LESS,
1489
+ sass: SupportedFabFileMimeTypes.SASS,
1490
+ scss: SupportedFabFileMimeTypes.SCSS,
1491
+ yaml: SupportedFabFileMimeTypes.YAML,
1492
+ yml: SupportedFabFileMimeTypes.YAML,
1493
+ toml: SupportedFabFileMimeTypes.TOML,
1494
+ sh: SupportedFabFileMimeTypes.SH,
1495
+ bash: SupportedFabFileMimeTypes.BASH,
1496
+ mp3: AudioMimeType.MP3,
1497
+ wav: AudioMimeType.WAV,
1498
+ opus: AudioMimeType.OPUS,
1499
+ aac: AudioMimeType.AAC,
1500
+ flac: AudioMimeType.FLAC,
1501
+ pcm: AudioMimeType.PCM,
1502
+ ogg: AudioMimeType.OGG,
1503
+ webm: AudioMimeType.WEBM
1504
+ });
1505
+ /**
1506
+ * Coerce any thrown value into a human-readable string.
1507
+ *
1508
+ * Handles the three common shapes:
1509
+ * - Error -> .message
1510
+ * - string -> as-is
1511
+ * - other -> JSON.stringify with a safe fallback for circular/unstringifiable values
1512
+ */
1513
+ function extractErrorMessage(error) {
1514
+ if (error instanceof Error) return error.message;
1515
+ if (typeof error === "string") return error;
1516
+ try {
1517
+ return JSON.stringify(error);
1518
+ } catch {
1519
+ return "Unknown error";
1520
+ }
1521
+ }
1522
+ ChatModels.GPT5, ChatModels.GPT5_MINI, ChatModels.GPT5_NANO, ChatModels.GPT5_1, ChatModels.GPT5_2, ChatModels.GPT5_4, ChatModels.GPT5_4_MINI, ChatModels.GPT5_4_NANO, ChatModels.GPT5_5, ChatModels.GPT5_6_SOL, ChatModels.GPT5_6_LUNA, ChatModels.GPT5_6_TERRA;
1523
+ TTS_DEFAULT_MODEL.openai;
1524
+ TTS_MAX_INPUT_CHARS.openai;
1525
+ TTS_MAX_INPUT_CHARS.elevenlabs;
1526
+ TTS_DEFAULT_MODEL.elevenlabs;
1410
1527
  //#endregion
1411
1528
  //#region src/llm/NotifyingLlmBackend.ts
1412
1529
  /**
@@ -1827,9 +1944,19 @@ async function buildLlmBackend(input, deps = defaultLlmBackendDeps) {
1827
1944
  let models;
1828
1945
  if (ollamaHost) {
1829
1946
  const ollamaBackend = deps.createOllamaBackend(ollamaHost);
1830
- const [serverModels, ollamaModels] = await Promise.all([llm.getModelInfo(), ollamaBackend.getModelInfo()]);
1831
- if (serverModels.length === 0 && ollamaModels.length === 0) throw new Error(`No models available from server or Ollama at ${ollamaHost}.\nPull a model: ollama pull qwen3.5`);
1832
- if (ollamaModels.length === 0) startupLog.push(`⚠️ No models found in Ollama at ${ollamaHost}. Pull one with: ollama pull qwen3.5`);
1947
+ const ollamaListing = ollamaBackend.getModelInfo().then((listed) => ({ models: listed }), (error) => {
1948
+ const message = extractErrorMessage(error);
1949
+ logger.warn(`Error fetching model info from Ollama: ${message}`);
1950
+ return {
1951
+ models: [],
1952
+ error: message
1953
+ };
1954
+ });
1955
+ const [serverModels, ollama] = await Promise.all([llm.getModelInfo(), ollamaListing]);
1956
+ const ollamaModels = ollama.models;
1957
+ if (serverModels.length === 0 && ollamaModels.length === 0) throw new Error(ollama.error !== void 0 ? `No models available from server, and Ollama at ${ollamaHost} is unreachable: ${ollama.error}` : `No models available from server or Ollama at ${ollamaHost}.\nPull a model: ollama pull qwen3.5`);
1958
+ if (ollama.error !== void 0) startupLog.push(`\u26a0\ufe0f Ollama at ${ollamaHost} is unreachable: ${ollama.error}`);
1959
+ else if (ollamaModels.length === 0) startupLog.push(`⚠️ No models found in Ollama at ${ollamaHost}. Pull one with: ollama pull qwen3.5`);
1833
1960
  const serverBackend = llm;
1834
1961
  llm = deps.createMultiBackend(serverBackend, ollamaBackend, serverModels, ollamaModels, config.defaultModel);
1835
1962
  models = await llm.getModelInfo();
@@ -1988,5 +2115,237 @@ function buildAgent(input) {
1988
2115
  buildPromptForMode
1989
2116
  };
1990
2117
  }
2118
+ /**
2119
+ * The `tool_reference` blocks a `toolReference` tool_search result serializes to, or null for any
2120
+ * other text (a no-match hint, a mutateTools result, or a result with an appended warning).
2121
+ * Extra keys are tolerated so a field added to the core serializer does not silently disable the
2122
+ * native path, and dropped because the provider rejects a `tool_reference` block with any other field.
2123
+ */
2124
+ function parseToolReferences(text) {
2125
+ let parsed;
2126
+ try {
2127
+ parsed = JSON.parse(text);
2128
+ } catch {
2129
+ return null;
2130
+ }
2131
+ if (!Array.isArray(parsed) || parsed.length === 0) return null;
2132
+ const isReference = (block) => typeof block === "object" && block !== null && block.type === "tool_reference" && typeof block.tool_name === "string";
2133
+ return parsed.every(isReference) ? parsed.map(({ type, tool_name }) => ({
2134
+ type,
2135
+ tool_name
2136
+ })) : null;
2137
+ }
2138
+ /**
2139
+ * The CLI's `tool_search`: one schema, with the load strategy picked per call so a `/model` switch or a
2140
+ * fallback model takes effect on the next search. Both strategies share the registry's search and
2141
+ * ranking. With `toolReference` the matched tools are still added to the live list, because the agent
2142
+ * executes only tools it holds; ToolSearchLlmBackend keeps them deferred on the wire.
2143
+ */
2144
+ function createCliToolSearchTool(options) {
2145
+ const { registry, logger, toolListAccessor, useToolReference } = options;
2146
+ const mutating = createToolSearchTool({
2147
+ registry,
2148
+ logger,
2149
+ toolListAccessor
2150
+ });
2151
+ const referencing = createToolSearchTool({
2152
+ registry,
2153
+ logger,
2154
+ loadStrategy: "toolReference"
2155
+ });
2156
+ return {
2157
+ toolSchema: mutating.toolSchema,
2158
+ toolFn: async (params) => {
2159
+ if (!useToolReference()) return mutating.toolFn(params);
2160
+ const result = await referencing.toolFn(params);
2161
+ const references = parseToolReferences(result);
2162
+ if (references) {
2163
+ const liveTools = toolListAccessor();
2164
+ const liveNames = new Set(liveTools.map((t) => t.toolSchema.name));
2165
+ for (const tool of registry.getByNames(references.map((r) => r.tool_name))) {
2166
+ if (liveNames.has(tool.toolSchema.name)) continue;
2167
+ liveNames.add(tool.toolSchema.name);
2168
+ liveTools.push(tool);
2169
+ }
2170
+ }
2171
+ return result;
2172
+ }
2173
+ };
2174
+ }
2175
+ //#endregion
2176
+ //#region src/llm/ToolSearchLlmBackend.ts
2177
+ /**
2178
+ * Same gate the completions endpoint applies (cliCompletionsToolSearch.ts), so the CLI never sends
2179
+ * `tool_reference` blocks the server would refuse. A model missing from `models` reads false.
2180
+ */
2181
+ function modelServesToolReference(models, modelId) {
2182
+ const model = models.find((m) => m.id === modelId);
2183
+ return model !== void 0 && completionsSupportsToolSearch(model);
2184
+ }
2185
+ function toolSearchCallIds(messages) {
2186
+ const ids = /* @__PURE__ */ new Set();
2187
+ for (const message of messages) {
2188
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
2189
+ for (const block of message.content) if (block?.type === "tool_use" && block.name === "tool_search" && typeof block.id === "string") ids.add(block.id);
2190
+ }
2191
+ return ids;
2192
+ }
2193
+ /**
2194
+ * Rewrites the content of each tool_search result that holds serialized references. `rewrite`
2195
+ * returning undefined leaves that result as is. Returns `messages` itself when nothing changed.
2196
+ */
2197
+ function rewriteToolSearchResults(messages, rewrite) {
2198
+ const callIds = toolSearchCallIds(messages);
2199
+ if (callIds.size === 0) return messages;
2200
+ let changed = false;
2201
+ const result = messages.map((message) => {
2202
+ if (message.role !== "user" || !Array.isArray(message.content)) return message;
2203
+ let messageChanged = false;
2204
+ const content = message.content.map((block) => {
2205
+ if (block?.type !== "tool_result" || typeof block.content !== "string") return block;
2206
+ if (typeof block.tool_use_id !== "string" || !callIds.has(block.tool_use_id)) return block;
2207
+ const references = parseToolReferences(block.content);
2208
+ const replacement = references ? rewrite(references) : void 0;
2209
+ if (replacement === void 0) return block;
2210
+ messageChanged = true;
2211
+ return {
2212
+ ...block,
2213
+ content: replacement
2214
+ };
2215
+ });
2216
+ if (!messageChanged) return message;
2217
+ changed = true;
2218
+ return {
2219
+ ...message,
2220
+ content
2221
+ };
2222
+ });
2223
+ return changed ? result : messages;
2224
+ }
2225
+ function asDeferred(tool) {
2226
+ const { cache_control: _cacheControl, ...toolSchema } = tool.toolSchema;
2227
+ return {
2228
+ ...tool,
2229
+ toolSchema: {
2230
+ ...toolSchema,
2231
+ defer_loading: true
2232
+ }
2233
+ };
2234
+ }
2235
+ /**
2236
+ * Shapes one request's tools and tool_search results for the model it is sent to. History keeps
2237
+ * tool_search results as the serialized text the tool returned, so it stays valid across a model switch.
2238
+ *
2239
+ * - Native tool search: the live tools other than registry ones, then every registry tool in registry
2240
+ * order with `defer_loading: true`, so the array stays byte-identical across a tool load and the
2241
+ * prompt cache holds. Serialized references become `tool_reference` blocks. A registry tool the live
2242
+ * list holds with no reference left in history (loaded by mutateTools before a model switch, or by
2243
+ * the unknown-tool resolver) is sent non-deferred so it stays callable.
2244
+ * - Otherwise: tools unchanged, and any references from an earlier native turn become text, since
2245
+ * the endpoint refuses `tool_reference` there. Referenced tools are already in the live list.
2246
+ *
2247
+ * Requests without `tool_search` (sub-agents, summaries) pass through untouched.
2248
+ */
2249
+ function prepareToolSearchRequest(request, registry, native) {
2250
+ const { messages, tools } = request;
2251
+ if (registry.size() === 0 || !tools.some((t) => t.toolSchema.name === "tool_search")) return request;
2252
+ if (!native) {
2253
+ const textMessages = rewriteToolSearchResults(messages, (references) => `Loaded ${references.length} tool schema(s): ${references.map((r) => r.tool_name).join(", ")}. These are now callable in your next message.`);
2254
+ return textMessages === messages ? request : {
2255
+ messages: textMessages,
2256
+ tools
2257
+ };
2258
+ }
2259
+ const registryTools = registry.getAll();
2260
+ const wireTools = tools.filter((t) => !registry.has(t.toolSchema.name));
2261
+ const wireNames = new Set([...wireTools, ...registryTools].map((t) => t.toolSchema.name));
2262
+ const referenced = /* @__PURE__ */ new Set();
2263
+ const wireMessages = rewriteToolSearchResults(messages, (references) => {
2264
+ const resolved = references.filter((r) => wireNames.has(r.tool_name));
2265
+ if (resolved.length === 0) return void 0;
2266
+ for (const reference of resolved) referenced.add(reference.tool_name);
2267
+ return resolved.length === references.length ? references : resolved;
2268
+ });
2269
+ const liveNames = new Set(tools.map((t) => t.toolSchema.name));
2270
+ for (const tool of registryTools) {
2271
+ const name = tool.toolSchema.name;
2272
+ wireTools.push(liveNames.has(name) && !referenced.has(name) ? tool : asDeferred(tool));
2273
+ }
2274
+ if (wireTools.every((t) => t.toolSchema.defer_loading === true)) return request;
2275
+ return {
2276
+ messages: wireMessages,
2277
+ tools: wireTools
2278
+ };
2279
+ }
2280
+ /**
2281
+ * Decorator that applies prepareToolSearchRequest per call, for the model each call actually goes to.
2282
+ * Sits inside FallbackLlmBackend so a fallback model is shaped for itself.
2283
+ */
2284
+ var ToolSearchLlmBackend = class {
2285
+ constructor(inner, registry, models) {
2286
+ this.inner = inner;
2287
+ this.registry = registry;
2288
+ this.models = models;
2289
+ this.lastToolSearchRequestNative = false;
2290
+ }
2291
+ get currentModel() {
2292
+ return this.inner.currentModel;
2293
+ }
2294
+ set currentModel(model) {
2295
+ this.inner.currentModel = model;
2296
+ }
2297
+ /** Whether the last request offering tool_search went to a native tool search model. */
2298
+ servesToolReference() {
2299
+ return this.lastToolSearchRequestNative;
2300
+ }
2301
+ async complete(model, messages, options, callback) {
2302
+ const tools = options.tools ?? [];
2303
+ if (!tools.some((t) => t.toolSchema.name === "tool_search")) return this.inner.complete(model, messages, options, callback);
2304
+ const native = modelServesToolReference(this.models, model);
2305
+ this.lastToolSearchRequestNative = native;
2306
+ const prepared = prepareToolSearchRequest({
2307
+ messages,
2308
+ tools
2309
+ }, this.registry, native);
2310
+ const preparedOptions = prepared.tools === tools ? options : {
2311
+ ...options,
2312
+ tools: prepared.tools
2313
+ };
2314
+ return this.inner.complete(model, prepared.messages, preparedOptions, callback);
2315
+ }
2316
+ pushToolMessages(messages, tool, result, thinkingBlocks) {
2317
+ this.inner.pushToolMessages(messages, tool, result, thinkingBlocks);
2318
+ }
2319
+ async getModelInfo() {
2320
+ return this.inner.getModelInfo();
2321
+ }
2322
+ };
2323
+ //#endregion
2324
+ //#region src/bootstrap/buildToolSearch.ts
2325
+ /**
2326
+ * The main agent's tool_search and the backend that shapes its requests per model: native tool search
2327
+ * (`toolReference` + `defer_loading`) where the model serves it, `mutateTools` everywhere else. With no
2328
+ * deferred tools there is no tool_search and the backend is returned unwrapped.
2329
+ */
2330
+ function buildToolSearch(input) {
2331
+ const { llm, models, registry, logger, agentToolsRef } = input;
2332
+ if (registry.size() === 0) return {
2333
+ llm,
2334
+ toolSearchTool: null
2335
+ };
2336
+ const toolSearchLlm = new ToolSearchLlmBackend(llm, registry, models);
2337
+ return {
2338
+ llm: toolSearchLlm,
2339
+ toolSearchTool: createCliToolSearchTool({
2340
+ registry,
2341
+ logger,
2342
+ toolListAccessor: () => {
2343
+ if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
2344
+ return agentToolsRef.current;
2345
+ },
2346
+ useToolReference: () => toolSearchLlm.servesToolReference()
2347
+ })
2348
+ };
2349
+ }
1991
2350
  //#endregion
1992
- export { NotifyingLlmBackend as a, deferredToolRegistry as i, buildSupportingStores as n, buildLlmBackend as r, buildAgent as t };
2351
+ export { deferredToolRegistry as a, buildLlmBackend as i, buildAgent as n, NotifyingLlmBackend as o, buildSupportingStores as r, buildToolSearch as t };
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, G as setWebSocketToolExecutor, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, X as createToolSearchTool, b as createGetFileStructureTool, j as FallbackLlmBackend, k as createBackgroundAgentTools, nt as CheckpointStore, rt as SessionStore, s as buildSandbox, w as createWriteTodosTool } from "../AgentHistoryStore-ClO2zOn6.mjs";
3
- import { b as logger, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-CbVFLXMH.mjs";
4
- import { a as NotifyingLlmBackend, i as deferredToolRegistry, n as buildSupportingStores, r as buildLlmBackend, t as buildAgent } from "../buildAgent-BeaVbpzH.mjs";
5
- import { a as ApiClient } from "../dist-DlBq3wHu.mjs";
2
+ import { A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as CustomCommandStore, S as createFindDefinitionTool, T as createSkillTool, Y as setWebSocketToolExecutor, b as createGetFileStructureTool, j as FallbackLlmBackend, k as createBackgroundAgentTools, nt as SessionStore, s as buildSandbox, tt as CheckpointStore, w as createWriteTodosTool } from "../AgentHistoryStore-DgqmcVLN.mjs";
3
+ import { b as logger, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-BwqsQ0U9.mjs";
4
+ import { t as RemoteSkillSource } from "../RemoteSkillSource-qTvRkaNA.mjs";
5
+ import { a as deferredToolRegistry, i as buildLlmBackend, n as buildAgent, o as NotifyingLlmBackend, r as buildSupportingStores, t as buildToolSearch } from "../buildToolSearch-D9GEK91u.mjs";
6
+ import { t as ApiClient } from "../ApiClient-_JrM_xWw.mjs";
6
7
  import { randomUUID } from "crypto";
7
8
  import { existsSync, realpathSync, statSync } from "fs";
8
9
  import { isAbsolute } from "path";
@@ -592,7 +593,7 @@ var AcpServer = class {
592
593
  const tokenGetter = async () => (await this.configStore.getAuthTokens())?.accessToken ?? null;
593
594
  await this.mergeRemoteSkills(config, apiClient);
594
595
  const startupLog = [];
595
- const { llm, wsManager, modelInfo } = await buildLlmBackend({
596
+ const { llm, wsManager, models, modelInfo } = await buildLlmBackend({
596
597
  config,
597
598
  apiClient,
598
599
  tokenGetter,
@@ -628,17 +629,16 @@ var AcpServer = class {
628
629
  onBackgroundStatusChange: () => {},
629
630
  onGroupCompletion: () => {}
630
631
  });
631
- const llmWithFallback = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(llm, config.fallbackModels, (from, to, error) => logger.debug(`[acp] Model "${from}" failed (${error.message}); falling back to "${to}"`)) : llm;
632
- const notifyingLlm = new NotifyingLlmBackend(llmWithFallback, backgroundManager);
633
632
  const agentToolsRef = { current: null };
634
- const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool({
633
+ const { llm: toolSearchLlm, toolSearchTool } = buildToolSearch({
634
+ llm,
635
+ models,
635
636
  registry: deferredToolRegistry,
636
637
  logger,
637
- toolListAccessor: () => {
638
- if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
639
- return agentToolsRef.current;
640
- }
641
- }) : null;
638
+ agentToolsRef
639
+ });
640
+ const llmWithFallback = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(toolSearchLlm, config.fallbackModels, (from, to, error) => logger.debug(`[acp] Model "${from}" failed (${error.message}); falling back to "${to}"`)) : toolSearchLlm;
641
+ const notifyingLlm = new NotifyingLlmBackend(llmWithFallback, backgroundManager);
642
642
  const cliTools = this.buildCliTools({
643
643
  config,
644
644
  orchestrator,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { m as resolveApiEndpoint, t as ConfigStore, y as parseApiUrl } from "../ConfigStore-CbVFLXMH.mjs";
2
+ import { m as resolveApiEndpoint, t as ConfigStore, y as parseApiUrl } from "../ConfigStore-BwqsQ0U9.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-MoDZ9cGj.mjs";
2
+ import { t as version } from "../package-CQaam4Dq.mjs";
3
3
  import { c as isNpmPrefixWritable, i as fetchLatestVersion, r as compareSemver } from "../updateChecker-CCLVVdH0.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { existsSync } from "fs";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CbVFLXMH.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-BwqsQ0U9.mjs";
3
3
  //#region src/commands/envCommand.ts
4
4
  /**
5
5
  * Environment switching for the `--dev` / `--prod` launch flags.
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as generateCliTools, G as setWebSocketToolExecutor, K as classifyCommandRisk, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, U as wrapTools, V as buildSystemPrompt, Y as ReActAgent, a as loadProjectContext, b as createGetFileStructureTool, c as createSseBackend, i as buildProjectAgentStore, j as FallbackLlmBackend, k as createBackgroundAgentTools, n as BackgroundAgentManager, nt as CheckpointStore, o as McpManager, r as SubagentOrchestrator, rt as SessionStore, s as buildSandbox, t as AgentHistoryStore, w as createWriteTodosTool, x as createWorkItemTools } from "../AgentHistoryStore-ClO2zOn6.mjs";
3
- import { a as commandTextOf, b as logger, c as toolCategory, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-CbVFLXMH.mjs";
4
- import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-SeNbzvEw.mjs";
5
- import { a as ApiClient } from "../dist-DlBq3wHu.mjs";
2
+ import { A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as generateCliTools, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as CustomCommandStore, S as createFindDefinitionTool, T as createSkillTool, V as buildSystemPrompt, W as ReActAgent, X as classifyCommandRisk, Y as setWebSocketToolExecutor, a as loadProjectContext, b as createGetFileStructureTool, c as createSseBackend, i as buildProjectAgentStore, j as FallbackLlmBackend, k as createBackgroundAgentTools, n as BackgroundAgentManager, nt as SessionStore, o as McpManager, q as wrapTools, r as SubagentOrchestrator, s as buildSandbox, t as AgentHistoryStore, tt as CheckpointStore, w as createWriteTodosTool, x as createWorkItemTools } from "../AgentHistoryStore-DgqmcVLN.mjs";
3
+ import { a as commandTextOf, b as logger, c as toolCategory, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-BwqsQ0U9.mjs";
4
+ import { t as RemoteSkillSource } from "../RemoteSkillSource-qTvRkaNA.mjs";
5
+ import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-CERTDrhT.mjs";
6
+ import { t as ApiClient } from "../ApiClient-_JrM_xWw.mjs";
6
7
  import { randomBytes } from "crypto";
7
8
  import { v4 } from "uuid";
8
9
  import { readFile } from "fs/promises";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CbVFLXMH.mjs";
3
- import { t as version } from "../package-MoDZ9cGj.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-BwqsQ0U9.mjs";
3
+ import { t as version } from "../package-CQaam4Dq.mjs";
4
4
  //#region src/commands/mcpCommand.ts
5
5
  /**
6
6
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -70,12 +70,13 @@ async function handleMcpCommand(subcommand, argv) {
70
70
  await handleDisable(config, argv.name, configStore);
71
71
  break;
72
72
  case "serve": {
73
- const { handleMcpServeCommand } = await import("../serve-zi-B2iUT.mjs");
73
+ const { handleMcpServeCommand } = await import("../serve-CFxUhjml.mjs");
74
74
  await handleMcpServeCommand({
75
75
  http: Boolean(argv.http),
76
76
  port: typeof argv.port === "number" ? argv.port : void 0,
77
77
  apiKey: typeof argv.apiKey === "string" ? argv.apiKey : void 0,
78
78
  apiUrl: typeof argv.apiUrl === "string" ? argv.apiUrl : void 0,
79
+ token: typeof argv.token === "string" ? argv.token : void 0,
79
80
  version
80
81
  });
81
82
  break;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-CbVFLXMH.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-BwqsQ0U9.mjs";
3
3
  import { n as getDefaultPluginsDir, r as isFeatureEnabled, t as PluginStore } from "../PluginStore-DwvOJ-G3.mjs";
4
4
  import { promises } from "fs";
5
5
  import path from "path";