@bike4mind/cli 2.0.0 → 3.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.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/dist/{AgentHistoryStore-DIMzvf-y.mjs → AgentHistoryStore-DVxv1kSO.mjs} +7940 -7014
  3. package/dist/{ConfigStore-DIs-K9oD.mjs → ConfigStore-51UaP9pJ.mjs} +12333 -7008
  4. package/dist/{ImageStore-kVo-oHoS.mjs → ImageStore-DuI3tgwQ.mjs} +8 -6
  5. package/dist/SandboxRuntimeAdapter-Bt1wPpld.mjs +2 -0
  6. package/dist/{WorkItemsClient-Cow6nXx7.mjs → WorkItemsClient-BMY0Qq1s.mjs} +78 -9
  7. package/dist/{buildAgent-DOvEp92a.mjs → buildAgent-CrSRKYU_.mjs} +106 -43
  8. package/dist/commands/acpCommand.mjs +26 -35
  9. package/dist/commands/apiCommand.mjs +1 -1
  10. package/dist/commands/doctorCommand.mjs +3 -3
  11. package/dist/commands/envCommand.mjs +1 -1
  12. package/dist/commands/headlessCommand.mjs +15 -24
  13. package/dist/commands/mcpCommand.mjs +3 -3
  14. package/dist/commands/pluginCommand.mjs +2 -2
  15. package/dist/commands/updateCommand.mjs +5 -15
  16. package/dist/dist-BsG2Hlp-.mjs +739 -0
  17. package/dist/index.mjs +670 -340
  18. package/dist/{package-Dl_F2Fah.mjs → package-DEsNSP54.mjs} +1 -1
  19. package/dist/serve-BaHzVbcm.mjs +1511 -0
  20. package/dist/store-BkMur6uk.mjs +2 -0
  21. package/dist/{store-CvjTpQPs.mjs → store-BxmPEI3v.mjs} +10 -0
  22. package/dist/types-I45lMh7Q.mjs +2 -0
  23. package/dist/{updateChecker-CQW8bxo6.mjs → updateChecker-CCLVVdH0.mjs} +36 -13
  24. package/package.json +27 -26
  25. package/dist/ApiClient-BTI0-m0g.mjs +0 -316
  26. package/dist/SandboxRuntimeAdapter-BmHELLuM.mjs +0 -3
  27. package/dist/serve-Y2zMbfGs.mjs +0 -938
  28. package/dist/store-BG3e54c8.mjs +0 -3
  29. package/dist/types-CdIKgWWe.mjs +0 -3
  30. package/dist/{bashExecute-CrdPpBqk-DCATrE-D.mjs → bashExecute-CrdPpBqk-CgK1gZa2.mjs} +1 -1
  31. package/dist/{grepSearch-BgoOOwGe-DtlV8Gn-.mjs → grepSearch-BgoOOwGe-B388GxRD.mjs} +2 -2
@@ -139,12 +139,14 @@ var ImageStore = class ImageStore {
139
139
  const maxDimension = 2048;
140
140
  let width = metadata.width;
141
141
  let height = metadata.height;
142
- if (width > maxDimension || height > maxDimension) if (width > height) {
143
- height = Math.round(height * maxDimension / width);
144
- width = maxDimension;
145
- } else {
146
- width = Math.round(width * maxDimension / height);
147
- height = maxDimension;
142
+ if (width > maxDimension || height > maxDimension) {
143
+ if (width > height) {
144
+ height = Math.round(height * maxDimension / width);
145
+ width = maxDimension;
146
+ } else {
147
+ width = Math.round(width * maxDimension / height);
148
+ height = maxDimension;
149
+ }
148
150
  }
149
151
  image = image.resize(width, height);
150
152
  let quality = 85;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { t as createSandboxRuntime, n as detectPlatform, r as expandPath, i as isBinaryAvailable } from "./SandboxRuntimeAdapter-BgLUVTJL.mjs";
@@ -1,25 +1,73 @@
1
1
  #!/usr/bin/env node
2
+ import { kt as tokenEstimateMultiplier } from "./ConfigStore-51UaP9pJ.mjs";
2
3
  import { v4 } from "uuid";
3
4
  import { get_encoding } from "tiktoken";
5
+ //#region ../../b4m-core/utils/dist/calibratedTokenizer.mjs
6
+ /**
7
+ * Scale a raw cl100k_base count by `multiplier`, rounded up to the nearest whole token. Paired with
8
+ * tokenEstimateMultiplier (@bike4mind/common) to bring a raw count up to a Claude model's tokenizer.
9
+ * The sync counterpart of withTokenEstimateMultiplier, for callers whose counter is synchronous.
10
+ */
11
+ function scaleTokenEstimate(count, multiplier) {
12
+ if (multiplier === 1) return count;
13
+ const hundredths = Math.round(multiplier * 100);
14
+ return Math.ceil(count * hundredths / 100);
15
+ }
16
+ //#endregion
4
17
  //#region src/utils/tokenCounter.ts
5
18
  const DEFAULT_CONTEXT_WINDOW = 2e5;
6
19
  const IMAGE_BLOCK_TOKEN_ESTIMATE = 1600;
20
+ const TOKEN_CACHE_MIN_CHARS = 256;
21
+ const DEFAULT_TOKEN_CACHE_MAX_CHARS = 4e6;
7
22
  /**
8
23
  * Token counting utility for context window management.
9
- * Uses tiktoken (cl100k_base encoding) which works for Claude and GPT-4.
24
+ * Uses tiktoken (cl100k_base encoding), the encoding GPT-4 reports. Claude's tokenizers spend more
25
+ * tokens on the same text, so call `forModel(modelId)` before comparing a count against a model's
26
+ * context window (see tokenEstimateMultiplier in @bike4mind/common).
10
27
  */
11
28
  var TokenCounter = class {
12
- constructor() {
29
+ constructor(cacheMaxChars = DEFAULT_TOKEN_CACHE_MAX_CHARS) {
30
+ this.cacheMaxChars = cacheMaxChars;
13
31
  this.encoder = null;
32
+ this.countCache = /* @__PURE__ */ new Map();
33
+ this.cachedChars = 0;
14
34
  }
15
35
  getEncoder() {
16
36
  if (!this.encoder) this.encoder = get_encoding("cl100k_base");
17
37
  return this.encoder;
18
38
  }
19
39
  /**
40
+ * A view of this counter whose counts are scaled to `modelId`'s own tokenizer, or `this` when the
41
+ * model needs no scaling (non-Claude, or a Claude version tokenEstimateMultiplier leaves at 1). Use
42
+ * it wherever a count is compared against a model's context window, so a Claude session compacts
43
+ * and reports against real token usage rather than a cl100k_base undercount.
44
+ */
45
+ forModel(modelId) {
46
+ const multiplier = tokenEstimateMultiplier(modelId);
47
+ return multiplier === 1 ? this : new CalibratedTokenCounter(this, multiplier);
48
+ }
49
+ /**
20
50
  * Count tokens in a text string
21
51
  */
22
52
  countTokens(text) {
53
+ if (text.length < TOKEN_CACHE_MIN_CHARS || text.length > this.cacheMaxChars) return this.encode(text);
54
+ const cached = this.countCache.get(text);
55
+ if (cached !== void 0) {
56
+ this.countCache.delete(text);
57
+ this.countCache.set(text, cached);
58
+ return cached;
59
+ }
60
+ const count = this.encode(text);
61
+ this.countCache.set(text, count);
62
+ this.cachedChars += text.length;
63
+ for (const [oldest] of this.countCache) {
64
+ if (this.cachedChars <= this.cacheMaxChars) break;
65
+ this.countCache.delete(oldest);
66
+ this.cachedChars -= oldest.length;
67
+ }
68
+ return count;
69
+ }
70
+ encode(text) {
23
71
  return this.getEncoder().encode_ordinary(text).length;
24
72
  }
25
73
  /**
@@ -74,12 +122,32 @@ var TokenCounter = class {
74
122
  * Free encoder resources when done
75
123
  */
76
124
  dispose() {
125
+ this.countCache.clear();
126
+ this.cachedChars = 0;
77
127
  if (this.encoder) {
78
128
  this.encoder.free();
79
129
  this.encoder = null;
80
130
  }
81
131
  }
82
132
  };
133
+ /**
134
+ * A calibrated view over a raw `TokenCounter`. `countTokens` is the one primitive every other count
135
+ * routes through, so overriding it calibrates message, session and tool-schema counts at once. The
136
+ * flat image estimate stays unscaled because it is not a tiktoken count. The view holds no encoder of
137
+ * its own - it reads through `raw` - so `dispose` is a no-op and disposing the view never frees the
138
+ * shared encoder.
139
+ */
140
+ var CalibratedTokenCounter = class extends TokenCounter {
141
+ constructor(raw, multiplier) {
142
+ super();
143
+ this.raw = raw;
144
+ this.multiplier = multiplier;
145
+ }
146
+ countTokens(text) {
147
+ return scaleTokenEstimate(this.raw.countTokens(text), this.multiplier);
148
+ }
149
+ dispose() {}
150
+ };
83
151
  let tokenCounter = null;
84
152
  /**
85
153
  * Get the singleton TokenCounter instance
@@ -151,7 +219,8 @@ var ConversationContext = class ConversationContext {
151
219
  * rendering each turn's bounded tool trace - plus the system prompt, and
152
220
  * compares against `thresholdRatio` of the context window. Rich-content aware:
153
221
  * replaces the old string-only `content` sum so a session whose weight lives
154
- * in tool traces still triggers compaction.
222
+ * in tool traces still triggers compaction. Counts are calibrated to
223
+ * `opts.model`, so `systemPromptTokens` must be too (the caller owns it).
155
224
  */
156
225
  needsCompaction(systemPromptTokens, opts, thresholdRatio = .8) {
157
226
  return this.estimateSessionTokens(systemPromptTokens, opts) >= opts.contextWindow * thresholdRatio;
@@ -162,7 +231,7 @@ var ConversationContext = class ConversationContext {
162
231
  * system prompt. Used by `needsCompaction`.
163
232
  */
164
233
  estimateSessionTokens(systemPromptTokens, opts) {
165
- return systemPromptTokens + this.messages.filter((m) => m.role === "user" || m.role === "assistant").reduce((sum, m) => sum + this.estimateTokens(this.renderHistoryMessage(m, opts)), 0);
234
+ return systemPromptTokens + this.messages.filter((m) => m.role === "user" || m.role === "assistant").reduce((sum, m) => sum + this.estimateTokens(this.renderHistoryMessage(m, opts), opts), 0);
166
235
  }
167
236
  /** Serialize back to a Session for on-disk persistence (rich content preserved). */
168
237
  toSession() {
@@ -181,12 +250,12 @@ var ConversationContext = class ConversationContext {
181
250
  */
182
251
  windowedHistory(newInput, opts) {
183
252
  const reserved = opts.reservedTokens ?? 8e3;
184
- const currentTokens = this.estimateTokens(this.inputToIMessage(newInput));
253
+ const currentTokens = this.estimateTokens(this.inputToIMessage(newInput), opts);
185
254
  const history = this.messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => this.renderHistoryMessage(m, opts));
186
255
  let remaining = opts.contextWindow - reserved - currentTokens;
187
256
  const kept = [];
188
257
  for (let i = history.length - 1; i >= 0; i--) {
189
- const cost = this.estimateTokens(history[i]);
258
+ const cost = this.estimateTokens(history[i], opts);
190
259
  if (cost > remaining) break;
191
260
  remaining -= cost;
192
261
  kept.unshift(history[i]);
@@ -250,7 +319,7 @@ var ConversationContext = class ConversationContext {
250
319
  if (b.type !== "tool_use") continue;
251
320
  const resultText = b.id && results.get(b.id) || "";
252
321
  const line = `[tool ${b.name}] ${this.brief(JSON.stringify(b.input ?? {}))} -> ${this.brief(resultText)}`;
253
- const cost = this.counter.countTokens(line);
322
+ const cost = this.counter.forModel(opts.model).countTokens(line);
254
323
  if (used + cost > budget) {
255
324
  omitted++;
256
325
  continue;
@@ -272,8 +341,8 @@ var ConversationContext = class ConversationContext {
272
341
  content: input
273
342
  };
274
343
  }
275
- estimateTokens(message) {
276
- return this.counter.countMessageContent(message.content);
344
+ estimateTokens(message, opts) {
345
+ return this.counter.forModel(opts.model).countMessageContent(message.content);
277
346
  }
278
347
  };
279
348
  /**
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { I as generateCliTools, J as setWebSocketToolExecutor, L as wrapTools, N as ReActAgent, X as buildSystemPrompt, Y as getPlanModeFilePath, a as loadProjectContext, c as createSseBackend, i as buildProjectAgentStore, it as Logger, l as ServerLlmBackend, n as BackgroundAgentManager, o as McpManager, r as SubagentOrchestrator, t as AgentHistoryStore } from "./AgentHistoryStore-DIMzvf-y.mjs";
3
- import { $ as hasDeliverablePinnedArtifact, C as ARTIFACT_REMOVED_PLACEHOLDER, E as CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS, J as filterToolArtifactMarkup, K as createThinkMarkerEscaper, M as MODEL_INFO_FIELD_GROUP_OF, O as ChatModels, P as ModelBackend, S as ARTIFACT_DELIVERED_PLACEHOLDER, Z as getQuestErrorCode, ct as stripDeliveredArtifactBlocks, lt as stripToolArtifactMarkup, n as logger, q as escapeThinkMarkers, rt as isUserInitiatedAbort, z as PermissionDeniedError } from "./ConfigStore-DIs-K9oD.mjs";
2
+ import { J as getPlanModeFilePath, L as generateCliTools, N as ReActAgent, R as wrapTools, Y as buildSystemPrompt, a as loadProjectContext, c as createSseBackend, i as buildProjectAgentStore, l as ServerLlmBackend, lt as Logger, n as BackgroundAgentManager, o as McpManager, q as setWebSocketToolExecutor, r as SubagentOrchestrator, t as AgentHistoryStore } from "./AgentHistoryStore-DVxv1kSO.mjs";
3
+ import { D as ARTIFACT_REMOVED_PLACEHOLDER, Dt as stripToolArtifactMarkup, E as ARTIFACT_DELIVERED_PLACEHOLDER, Et as stripDeliveredArtifactBlocks, F as ChatModels, G as ModelBackend, I as CompletionReasoningEffortSchema, N as CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS, U as MODEL_INFO_FIELD_GROUP_OF, X as PermissionDeniedError, _ as logger, ct as escapeThinkMarkers, ft as getQuestErrorCode, lt as filterToolArtifactMarkup, mt as hasDeliverablePinnedArtifact, st as createThinkMarkerEscaper, vt as isUserInitiatedAbort } from "./ConfigStore-51UaP9pJ.mjs";
4
4
  import "crypto";
5
5
  import { v4 } from "uuid";
6
6
  import { z as z$1 } from "zod";
@@ -20,10 +20,28 @@ import WebSocket from "ws";
20
20
  * A tool failure that must end the turn rather than be fed back to the model as a
21
21
  * recoverable observation: a declined tool (PermissionDeniedError) or an out-of-credits
22
22
  * error (tagged via `getQuestErrorCode`), neither of which any retry can satisfy.
23
+ * Also exempts these from the services tool failure guard (toolFailureGuard.ts).
23
24
  */
24
25
  function isTerminalToolError(error) {
25
26
  return error instanceof PermissionDeniedError || getQuestErrorCode(error) !== void 0;
26
27
  }
28
+ /** Whether the named tool is flagged `endsTurnAfterText` in the offered tool list. */
29
+ function isTurnEndingTool(name, tools) {
30
+ return tools?.find((tool) => tool.toolSchema.name === name)?.endsTurnAfterText === true;
31
+ }
32
+ /**
33
+ * Whether the tool loop should end the turn after this round's tool batch instead of making
34
+ * the follow-up model call. True only when the round already streamed non-empty answer text
35
+ * AND every tool it called is flagged `endsTurnAfterText`: the follow-up round would only
36
+ * repeat the answer. A tool-only round still recurses so the model gets to answer.
37
+ *
38
+ * The single decision point for every adapter's tool loop - keep them from drifting.
39
+ */
40
+ function shouldEndTurnAfterTools(calledToolNames, tools, roundText) {
41
+ const names = calledToolNames.filter((name) => Boolean(name));
42
+ if (names.length === 0 || !roundText?.trim()) return false;
43
+ return names.every((name) => isTurnEndingTool(name, tools));
44
+ }
27
45
  /**
28
46
  * Execute an array of async tasks either in parallel (with concurrency limiting)
29
47
  * or sequentially, returning outcomes in the original order.
@@ -243,6 +261,8 @@ const stripToolDependentMessages = (messages) => messages.filter((message) => !m
243
261
  * WHOLE request with `ValidationException: A maximum of 4 blocks with cache_control may be
244
262
  * provided`, which is non-retryable - so an over-budget request loses the turn outright,
245
263
  * after the user has already waited for it.
264
+ * See https://docs.claude.com/en/docs/build-with-claude/prompt-caching (tools, system and messages
265
+ * all count toward the same four).
246
266
  */
247
267
  const MAX_CACHE_CONTROL_BLOCKS = 4;
248
268
  /**
@@ -261,18 +281,44 @@ function hasMarker(block) {
261
281
  * shareable-prefix breakpoint) - so this adapter's budget is whatever they left, not the full four.
262
282
  */
263
283
  function censusMarkers(params) {
264
- const tools = Array.isArray(params.tools) ? params.tools.filter(hasMarker).length : 0;
265
- const system = Array.isArray(params.system) ? params.system.filter(hasMarker).length : 0;
266
- let messages = 0;
267
- if (Array.isArray(params.messages)) for (const message of params.messages) {
284
+ const { tools, system, messages } = markerSites(params);
285
+ return {
286
+ tools: tools.length,
287
+ system: system.length,
288
+ messages: messages.length,
289
+ total: tools.length + system.length + messages.length
290
+ };
291
+ }
292
+ function markerSites(params) {
293
+ const sitesIn = (region, value) => Array.isArray(value) ? value.flatMap((block, index) => hasMarker(block) ? [{
294
+ region,
295
+ index
296
+ }] : []) : [];
297
+ const messages = [];
298
+ if (Array.isArray(params.messages)) params.messages.forEach((message, messageIndex) => {
268
299
  const content = message?.content;
269
- if (Array.isArray(content)) messages += content.filter(hasMarker).length;
270
- }
300
+ if (!Array.isArray(content)) return;
301
+ content.forEach((block, index) => {
302
+ const inner = block?.content;
303
+ if (Array.isArray(inner)) inner.forEach((innerBlock, nested) => {
304
+ if (hasMarker(innerBlock)) messages.push({
305
+ region: "messages",
306
+ index,
307
+ message: messageIndex,
308
+ nested
309
+ });
310
+ });
311
+ if (hasMarker(block)) messages.push({
312
+ region: "messages",
313
+ index,
314
+ message: messageIndex
315
+ });
316
+ });
317
+ });
271
318
  return {
272
- tools,
273
- system,
274
- messages,
275
- total: tools + system + messages
319
+ tools: sitesIn("tools", params.tools),
320
+ system: sitesIn("system", params.system),
321
+ messages
276
322
  };
277
323
  }
278
324
  /**
@@ -371,14 +417,14 @@ var AnthropicCachingAdapter = class {
371
417
  else console.info(message, JSON.stringify(census));
372
418
  } else if (logger) logger.debug("[PromptCache] cache_control census", census);
373
419
  if (outbound.total > MAX_CACHE_CONTROL_BLOCKS) {
374
- const message = `[PromptCache] request exceeds the ${MAX_CACHE_CONTROL_BLOCKS}-block cache_control limit on arrival (${outbound.total}); the provider will reject it`;
420
+ const message = `[PromptCache] request exceeds the ${MAX_CACHE_CONTROL_BLOCKS}-block cache_control limit on arrival (${outbound.total}); capCacheControlMarkers will strip the excess before send`;
375
421
  const detail = {
376
422
  inbound,
377
423
  outbound,
378
424
  limit: MAX_CACHE_CONTROL_BLOCKS
379
425
  };
380
- if (logger) logger.error(message, detail);
381
- else console.error(message, JSON.stringify(detail));
426
+ if (logger) logger.warn(message, detail);
427
+ else console.warn(message, JSON.stringify(detail));
382
428
  } else if (dropped.length > 0) {
383
429
  const message = `[PromptCache] cache_control budget exhausted (limit ${MAX_CACHE_CONTROL_BLOCKS}); skipped breakpoints: ${dropped.join(", ")}`;
384
430
  const detail = {
@@ -479,6 +525,29 @@ var GeminiCachingAdapter = class {
479
525
  }
480
526
  };
481
527
  /**
528
+ * Cached prompt tokens from a raw provider usage object, across every spelling in use:
529
+ * OpenAI Chat Completions nests them under `prompt_tokens_details`, the OpenAI
530
+ * Responses API under `input_tokens_details`, Moonshot publishes a flat
531
+ * `cached_tokens` alongside the OpenAI-shaped nesting, and DeepSeek its own flat
532
+ * `prompt_cache_hit_tokens`. Reading only one spelling silently bills every cache
533
+ * hit on the other transports at the full input rate.
534
+ *
535
+ * DeepSeek's own spelling leads, because it is the number its invoice is computed
536
+ * from; the OpenAI-shaped ones it also sends are the fallback for a proxy that
537
+ * forwards only those.
538
+ */
539
+ function cachedTokensFromUsage(usage) {
540
+ if (!usage) return 0;
541
+ const candidates = [
542
+ usage.prompt_cache_hit_tokens,
543
+ usage.cached_tokens,
544
+ usage.prompt_tokens_details?.cached_tokens,
545
+ usage.input_tokens_details?.cached_tokens
546
+ ];
547
+ for (const value of candidates) if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
548
+ return 0;
549
+ }
550
+ /**
482
551
  * xAI Grok-specific caching adapter
483
552
  * Caching is automatic with optional conversation ID for cache affinity
484
553
  */
@@ -495,7 +564,7 @@ var XAICachingAdapter = class {
495
564
  const usage = response.usage;
496
565
  if (!usage) return void 0;
497
566
  const totalInputTokens = usage.prompt_tokens || 0;
498
- const cachedTokens = usage.cached_prompt_tokens || 0;
567
+ const cachedTokens = cachedTokensFromUsage(usage);
499
568
  const cacheHitRate = totalInputTokens > 0 ? cachedTokens / totalInputTokens * 100 : 0;
500
569
  const costSavingsPercent = cacheHitRate * .65;
501
570
  const estimatedLatencyReduction = cacheHitRate * .7;
@@ -553,29 +622,6 @@ var KimiCachingAdapter = class {
553
622
  }
554
623
  };
555
624
  /**
556
- * Cached prompt tokens from a raw provider usage object, across every spelling in use:
557
- * OpenAI Chat Completions nests them under `prompt_tokens_details`, the OpenAI
558
- * Responses API under `input_tokens_details`, Moonshot publishes a flat
559
- * `cached_tokens` alongside the OpenAI-shaped nesting, and DeepSeek its own flat
560
- * `prompt_cache_hit_tokens`. Reading only one spelling silently bills every cache
561
- * hit on the other transports at the full input rate.
562
- *
563
- * DeepSeek's own spelling leads, because it is the number its invoice is computed
564
- * from; the OpenAI-shaped ones it also sends are the fallback for a proxy that
565
- * forwards only those.
566
- */
567
- function cachedTokensFromUsage(usage) {
568
- if (!usage) return 0;
569
- const candidates = [
570
- usage.prompt_cache_hit_tokens,
571
- usage.cached_tokens,
572
- usage.prompt_tokens_details?.cached_tokens,
573
- usage.input_tokens_details?.cached_tokens
574
- ];
575
- for (const value of candidates) if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
576
- return 0;
577
- }
578
- /**
579
625
  * DeepSeek context caching. Automatic, like Moonshot's and xAI's: no parameter,
580
626
  * no header, no explicit cache-creation call. The adapter exists only to read
581
627
  * the counters back out.
@@ -617,7 +663,8 @@ var NoOpCachingAdapter = class {
617
663
  extractCacheStats() {}
618
664
  };
619
665
  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();
620
- ChatModels.KIMI_K2_THINKING_BEDROCK, ChatModels.KIMI_K2_5_BEDROCK, ChatModels.DEEPSEEK_R1_BEDROCK, ChatModels.DEEPSEEK_FLASH, ChatModels.DEEPSEEK_V4_PRO;
666
+ ChatModels.KIMI_K2_THINKING_BEDROCK, ChatModels.KIMI_K2_5_BEDROCK, ChatModels.DEEPSEEK_R1_BEDROCK, ChatModels.DEEPSEEK_FLASH, ChatModels.DEEPSEEK_V4_PRO, ChatModels.KIMI_K3;
667
+ 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;
621
668
  new Logger();
622
669
  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;
623
670
  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;
@@ -994,6 +1041,20 @@ var OllamaBackend = class OllamaBackend {
994
1041
  if (!inheritedArtifactGuard && artifactGuard) await artifactGuard.flush();
995
1042
  return;
996
1043
  }
1044
+ if (round.toolCalls.length > 0 && shouldEndTurnAfterTools(toolCalls.map((tc) => tc.name), options.tools, round.content)) {
1045
+ this._logger.info("[Tool Execution] Ending turn: answer already produced, only end-of-turn tools ran", {
1046
+ model,
1047
+ toolsExecuted: toolCalls.map((tc) => tc.name)
1048
+ });
1049
+ await (artifactGuard?.callback ?? callback)([round.content], {
1050
+ inputTokens,
1051
+ outputTokens,
1052
+ toolsUsed: executedToolsUsed,
1053
+ stopReason: "tool_use"
1054
+ });
1055
+ if (!inheritedArtifactGuard && artifactGuard) await artifactGuard.flush();
1056
+ return;
1057
+ }
997
1058
  await this.complete(model, messages, {
998
1059
  ...options,
999
1060
  _internal: {
@@ -1285,9 +1346,10 @@ var OllamaBackend = class OllamaBackend {
1285
1346
  const texts = [];
1286
1347
  const images = [];
1287
1348
  for (const block of msg.content) if (block.type === "text" && typeof block.text === "string") texts.push(block.text);
1288
- else if (block.type === "image") if (block.source.type === "base64") images.push(block.source.data);
1289
- else this._logger.debug("[OllamaBackend] Dropping non-base64 image block; Ollama requires inline base64.");
1290
- else if (block.type === "image_url") {
1349
+ else if (block.type === "image") {
1350
+ if (block.source.type === "base64") images.push(block.source.data);
1351
+ else this._logger.debug("[OllamaBackend] Dropping non-base64 image block; Ollama requires inline base64.");
1352
+ } else if (block.type === "image_url") {
1291
1353
  const dataUrl = block.image_url.url.match(/^data:[^,]*;base64,(.+)$/s);
1292
1354
  if (dataUrl) images.push(dataUrl[1]);
1293
1355
  else this._logger.debug("[OllamaBackend] Dropping non-data image_url; Ollama requires inline base64.");
@@ -1324,6 +1386,7 @@ ModelBackend.Bedrock, ModelBackend.AWS, ModelBackend.OpenAI, ModelBackend.Anthro
1324
1386
  Object.values(ModelBackend);
1325
1387
  Object.entries(MODEL_INFO_FIELD_GROUP_OF);
1326
1388
  ModelBackend.Anthropic, ModelBackend.Gemini, ModelBackend.XAI, ModelBackend.Ollama, ModelBackend.BFL, ModelBackend.LocalImage, ModelBackend.AWS;
1389
+ CompletionReasoningEffortSchema.options;
1327
1390
  ModelBackend.Ollama, ModelBackend.LocalImage;
1328
1391
  //#endregion
1329
1392
  //#region src/llm/NotifyingLlmBackend.ts
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as PermissionManager, J as setWebSocketToolExecutor, L as wrapTools, O as createResumeAgentTool, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, b as createGetFileStructureTool, et as CheckpointStore, j as FallbackLlmBackend, k as createBackgroundAgentTools, s as buildSandbox, tt as SessionStore, w as createWriteTodosTool } from "../AgentHistoryStore-DIMzvf-y.mjs";
3
- import { _ as requireApiUrl, d as isReadOnlyTool, n as logger, t as ConfigStore } from "../ConfigStore-DIs-K9oD.mjs";
4
- import { a as deferredToolRegistry, i as createToolSearchTool, n as buildSupportingStores, o as NotifyingLlmBackend, r as buildLlmBackend, t as buildAgent } from "../buildAgent-DOvEp92a.mjs";
5
- import { t as ApiClient } from "../ApiClient-BTI0-m0g.mjs";
2
+ import { A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as PermissionManager, I as buildCliToolList, O as createResumeAgentTool, Q as CustomCommandStore, S as createFindDefinitionTool, T as createSkillTool, Z as RemoteSkillSource, b as createGetFileStructureTool, j as FallbackLlmBackend, k as createBackgroundAgentTools, nt as SessionStore, q as setWebSocketToolExecutor, s as buildSandbox, tt as CheckpointStore, w as createWriteTodosTool } from "../AgentHistoryStore-DVxv1kSO.mjs";
3
+ import { _ as logger, o as isReadOnlyTool, t as ConfigStore, u as requireApiUrl } from "../ConfigStore-51UaP9pJ.mjs";
4
+ import { a as deferredToolRegistry, i as createToolSearchTool, n as buildSupportingStores, o as NotifyingLlmBackend, r as buildLlmBackend, t as buildAgent } from "../buildAgent-CrSRKYU_.mjs";
5
+ import { a as ApiClient } from "../dist-BsG2Hlp-.mjs";
6
6
  import { randomUUID } from "crypto";
7
7
  import { existsSync, realpathSync, statSync } from "fs";
8
8
  import { isAbsolute } from "path";
@@ -604,10 +604,7 @@ var AcpServer = class {
604
604
  checkpointStore
605
605
  });
606
606
  const additionalDirectories = await this.resolveAdditionalDirectories();
607
- const agentContext = {
608
- currentAgent: null,
609
- observationQueue: []
610
- };
607
+ const agentContext = { currentAgent: null };
611
608
  const { agentStore, contextResult, loadedB4mTools, orchestrator, backgroundManager, historyStore, mcpManager } = await buildSupportingStores({
612
609
  config,
613
610
  llm,
@@ -629,6 +626,11 @@ var AcpServer = class {
629
626
  });
630
627
  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;
631
628
  const notifyingLlm = new NotifyingLlmBackend(llmWithFallback, backgroundManager);
629
+ const agentToolsRef = { current: null };
630
+ const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool(() => {
631
+ if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
632
+ return agentToolsRef.current;
633
+ }) : null;
632
634
  const cliTools = this.buildCliTools({
633
635
  config,
634
636
  orchestrator,
@@ -640,18 +642,10 @@ var AcpServer = class {
640
642
  agentContext,
641
643
  apiClient,
642
644
  sandboxOrchestrator: sandboxOrchestrator ?? void 0,
643
- allowedDirectories: additionalDirectories
645
+ allowedDirectories: additionalDirectories,
646
+ toolSearchTool
644
647
  });
645
- const agentToolsRef = { current: null };
646
- const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool(() => {
647
- if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
648
- return agentToolsRef.current;
649
- }) : null;
650
- const allTools = [
651
- ...loadedB4mTools,
652
- ...toolSearchTool ? [toolSearchTool] : [],
653
- ...cliTools
654
- ];
648
+ const allTools = [...loadedB4mTools, ...cliTools];
655
649
  const { agent, buildPromptForMode } = buildAgent({
656
650
  config,
657
651
  modelId: modelInfo.id,
@@ -684,7 +678,7 @@ var AcpServer = class {
684
678
  };
685
679
  }
686
680
  buildCliTools(input) {
687
- const { config, orchestrator, agentStore, backgroundManager, historyStore, sessionId, permissionManager, agentContext, apiClient, sandboxOrchestrator, allowedDirectories } = input;
681
+ const { config, orchestrator, agentStore, backgroundManager, historyStore, sessionId, permissionManager, agentContext, apiClient, sandboxOrchestrator, allowedDirectories, toolSearchTool } = input;
688
682
  const cliWrapDeps = {
689
683
  permissionManager,
690
684
  showPermissionPrompt: this.promptFn,
@@ -694,10 +688,15 @@ var AcpServer = class {
694
688
  sandboxOrchestrator,
695
689
  allowedDirectories
696
690
  };
697
- const wrappedSecurityTools = wrapTools([
698
- createFindDefinitionTool(allowedDirectories),
699
- createGetFileStructureTool(allowedDirectories),
700
- ...config.preferences.enableSkillTool !== false ? [createSkillTool({
691
+ return buildCliToolList({
692
+ toolSearchTool,
693
+ agentDelegateTool: createAgentDelegateTool(orchestrator, agentStore, sessionId, backgroundManager),
694
+ backgroundTools: createBackgroundAgentTools(backgroundManager),
695
+ resumeAgentTool: createResumeAgentTool(orchestrator, historyStore, backgroundManager),
696
+ writeTodosTool: createWriteTodosTool(createTodoStore()),
697
+ findDefinitionTool: createFindDefinitionTool(allowedDirectories),
698
+ getFileStructureTool: createGetFileStructureTool(allowedDirectories),
699
+ skillTool: config.preferences.enableSkillTool !== false ? createSkillTool({
701
700
  customCommandStore: this.customCommandStore,
702
701
  subagentOrchestrator: orchestrator,
703
702
  sessionId,
@@ -706,17 +705,9 @@ var AcpServer = class {
706
705
  promptFn: this.promptFn
707
706
  },
708
707
  allowedDirectories
709
- })] : []
710
- ], cliWrapDeps);
711
- const tools = [
712
- createAgentDelegateTool(orchestrator, agentStore, sessionId, backgroundManager),
713
- ...createBackgroundAgentTools(backgroundManager),
714
- createResumeAgentTool(orchestrator, historyStore, backgroundManager),
715
- createWriteTodosTool(createTodoStore()),
716
- ...wrappedSecurityTools
717
- ];
718
- if (config.preferences.enableCoordinatorMode === true) tools.push(createCoordinateTaskTool(orchestrator, agentStore, sessionId));
719
- return tools;
708
+ }) : null,
709
+ coordinateTaskTool: config.preferences.enableCoordinatorMode === true ? createCoordinateTaskTool(orchestrator, agentStore, sessionId) : null
710
+ }, cliWrapDeps);
720
711
  }
721
712
  async loadCustomCommands() {
722
713
  this.customCommandStore.setProjectTrusted(this.configStore.isProjectTrusted());
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { g as parseApiUrl, t as ConfigStore, v as resolveApiEndpoint } from "../ConfigStore-DIs-K9oD.mjs";
2
+ import { d as resolveApiEndpoint, g as parseApiUrl, t as ConfigStore } from "../ConfigStore-51UaP9pJ.mjs";
3
3
  //#region src/commands/apiCommand.ts
4
4
  /**
5
5
  * External API config command (--api-url / --reset-api)
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-Dl_F2Fah.mjs";
3
- import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-CQW8bxo6.mjs";
2
+ import { t as version } from "../package-DEsNSP54.mjs";
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
- import { execSync } from "child_process";
6
5
  import { existsSync } from "fs";
7
6
  import { homedir } from "os";
8
7
  import path from "path";
8
+ import { execSync } from "child_process";
9
9
  //#region src/commands/doctorCommand.ts
10
10
  /**
11
11
  * External doctor command (b4m doctor)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-DIs-K9oD.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-51UaP9pJ.mjs";
3
3
  //#region src/commands/envCommand.ts
4
4
  /**
5
5
  * Environment switching for the `--dev` / `--prod` launch flags.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as PermissionManager, G as classifyCommandRisk, I as generateCliTools, J as setWebSocketToolExecutor, L as wrapTools, N as ReActAgent, O as createResumeAgentTool, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, X as buildSystemPrompt, a as loadProjectContext, b as createGetFileStructureTool, c as createSseBackend, et as CheckpointStore, i as buildProjectAgentStore, j as FallbackLlmBackend, k as createBackgroundAgentTools, n as BackgroundAgentManager, o as McpManager, r as SubagentOrchestrator, s as buildSandbox, t as AgentHistoryStore, tt as SessionStore, w as createWriteTodosTool, x as createWorkItemTools, z as SHELL_LIKE_TOOL_COMMAND_FIELDS } from "../AgentHistoryStore-DIMzvf-y.mjs";
3
- import { _ as requireApiUrl, d as isReadOnlyTool, n as logger, t as ConfigStore } from "../ConfigStore-DIs-K9oD.mjs";
4
- import { t as ApiClient } from "../ApiClient-BTI0-m0g.mjs";
5
- import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-Cow6nXx7.mjs";
2
+ import { A as createAgentDelegateTool, B as SHELL_LIKE_TOOL_COMMAND_FIELDS, C as createTodoStore, D as createCoordinateTaskTool, F as PermissionManager, I as buildCliToolList, K as classifyCommandRisk, L as generateCliTools, N as ReActAgent, O as createResumeAgentTool, Q as CustomCommandStore, R as wrapTools, S as createFindDefinitionTool, T as createSkillTool, Y as buildSystemPrompt, Z as RemoteSkillSource, 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 setWebSocketToolExecutor, r as SubagentOrchestrator, s as buildSandbox, t as AgentHistoryStore, tt as CheckpointStore, w as createWriteTodosTool, x as createWorkItemTools } from "../AgentHistoryStore-DVxv1kSO.mjs";
3
+ import { _ as logger, o as isReadOnlyTool, t as ConfigStore, u as requireApiUrl } from "../ConfigStore-51UaP9pJ.mjs";
4
+ import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-BMY0Qq1s.mjs";
5
+ import { a as ApiClient } from "../dist-BsG2Hlp-.mjs";
6
6
  import { randomBytes } from "crypto";
7
7
  import { v4 } from "uuid";
8
8
  import { readFile } from "fs/promises";
@@ -360,10 +360,7 @@ async function handleHeadlessCommand(options) {
360
360
  checkpointStore,
361
361
  log: headlessSandboxLog
362
362
  });
363
- const agentContext = {
364
- currentAgent: null,
365
- observationQueue: []
366
- };
363
+ const agentContext = { currentAgent: null };
367
364
  const { tools: b4mTools } = await generateCliTools(config.userId, llm, modelInfo.id, permissionManager, promptFn, agentContext, configStore, apiClient, void 0, userQuestionFn, checkpointStore, sandboxOrchestrator, additionalDirectories);
368
365
  const mcpManager = new McpManager(config);
369
366
  const builtinAgentsDir = new URL("../agents/defaults/", import.meta.url).pathname;
@@ -407,11 +404,9 @@ async function handleHeadlessCommand(options) {
407
404
  const resumeAgentTool = createResumeAgentTool(orchestrator, historyStore, backgroundManager);
408
405
  const todoStore = createTodoStore();
409
406
  const writeTodosTool = createWriteTodosTool(todoStore);
410
- const findDefinitionTool = wrapTools([createFindDefinitionTool(additionalDirectories)], cliWrapDeps)[0];
411
- const getFileStructureTool = wrapTools([createGetFileStructureTool(additionalDirectories)], cliWrapDeps)[0];
412
407
  const workItemTools = config.preferences.enableWorkItemTools ? createWorkItemTools(new WorkItemsClient(apiClient)) : [];
413
408
  const enableSkillTool = config.preferences.enableSkillTool !== false;
414
- const skillTool = enableSkillTool ? wrapTools([createSkillTool({
409
+ const skillTool = enableSkillTool ? createSkillTool({
415
410
  customCommandStore,
416
411
  subagentOrchestrator: orchestrator,
417
412
  sessionId: session.id,
@@ -420,21 +415,18 @@ async function handleHeadlessCommand(options) {
420
415
  promptFn
421
416
  },
422
417
  allowedDirectories: additionalDirectories
423
- })], cliWrapDeps)[0] : null;
424
- const cliTools = [
418
+ }) : null;
419
+ const cliTools = buildCliToolList({
425
420
  agentDelegateTool,
426
- ...backgroundTools,
421
+ backgroundTools,
427
422
  resumeAgentTool,
428
423
  writeTodosTool,
429
- findDefinitionTool,
430
- getFileStructureTool,
431
- ...workItemTools
432
- ];
433
- if (skillTool) cliTools.push(skillTool);
434
- if (config.preferences.enableCoordinatorMode === true) {
435
- const coordinateTaskTool = createCoordinateTaskTool(orchestrator, agentStore, session.id);
436
- cliTools.push(coordinateTaskTool);
437
- }
424
+ findDefinitionTool: createFindDefinitionTool(additionalDirectories),
425
+ getFileStructureTool: createGetFileStructureTool(additionalDirectories),
426
+ workItemTools,
427
+ skillTool,
428
+ coordinateTaskTool: config.preferences.enableCoordinatorMode === true ? createCoordinateTaskTool(orchestrator, agentStore, session.id) : null
429
+ }, cliWrapDeps);
438
430
  const allTools = [
439
431
  ...b4mTools,
440
432
  ...mcpTools,
@@ -461,7 +453,6 @@ async function handleHeadlessCommand(options) {
461
453
  systemPrompt
462
454
  });
463
455
  agentContext.currentAgent = agent;
464
- agent.observationQueue = agentContext.observationQueue;
465
456
  if (streaming) {
466
457
  agent.on("thought", (step) => {
467
458
  emit({