la-machina-engine 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1817,7 +1817,11 @@ var AnthropicClient = class {
1817
1817
  messages: request.messages,
1818
1818
  stream: true,
1819
1819
  ...request.system !== void 0 ? { system: request.system } : {},
1820
- ...request.tools !== void 0 ? { tools: request.tools } : {}
1820
+ ...request.tools !== void 0 ? { tools: request.tools } : {},
1821
+ // Plan 025 — `'required'` maps to Anthropic's `tool_choice: { type: 'any' }`,
1822
+ // which forces the model to call SOME tool but doesn't pin which.
1823
+ // `'auto'` is the SDK default — omit to let it through unchanged.
1824
+ ...request.toolChoice === "required" ? { tool_choice: { type: "any" } } : {}
1821
1825
  };
1822
1826
  const requestOptions = {};
1823
1827
  if (betas.length > 0) {
@@ -1999,6 +2003,9 @@ var AISdkAdapter = class {
1999
2003
  tools,
2000
2004
  ...request.maxTokens !== void 0 ? { maxOutputTokens: request.maxTokens } : {},
2001
2005
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
2006
+ // Plan 025 — pass through `'required'` so the AI SDK forwards it
2007
+ // to the provider as that provider's "force tool call" flag.
2008
+ ...request.toolChoice === "required" ? { toolChoice: "required" } : {},
2002
2009
  maxRetries: this.options.maxRetries ?? 2
2003
2010
  });
2004
2011
  for await (const event of result.fullStream) {
@@ -2618,6 +2625,17 @@ function ensureToolResultPairing(messages) {
2618
2625
  return messages;
2619
2626
  }
2620
2627
 
2628
+ // src/engine/lastTurnGuard.ts
2629
+ init_cjs_shims();
2630
+ var LAST_TURN_INSTRUCTION_JSON = "SYSTEM NOTE: This is your final allowed turn. Emit ONLY the JSON object that satisfies the output schema in the system prompt. Do not call any more tools. Do not write any explanation, narration, or markdown \u2014 only the raw JSON.";
2631
+ var LAST_TURN_INSTRUCTION_TEXT = "SYSTEM NOTE: This is your final allowed turn. Stop calling tools and deliver your final answer now. The next turn will not happen.";
2632
+ function lastTurnInstruction(outputFormat) {
2633
+ return outputFormat === "json" ? LAST_TURN_INSTRUCTION_JSON : LAST_TURN_INSTRUCTION_TEXT;
2634
+ }
2635
+ function shouldInjectLastTurnInstruction(opts) {
2636
+ return opts.turnCount + 1 === opts.maxTurns;
2637
+ }
2638
+
2621
2639
  // src/compact/compactor.ts
2622
2640
  init_cjs_shims();
2623
2641
 
@@ -3109,15 +3127,29 @@ async function agentLoop(options) {
3109
3127
  const toolCalls = [];
3110
3128
  let stopReason = null;
3111
3129
  let turnUsage = { input: 0, output: 0 };
3130
+ let messagesForApi = messages;
3131
+ if (shouldInjectLastTurnInstruction({
3132
+ turnCount: ctx.getTurnCount(),
3133
+ maxTurns: ctx.getMaxTurns()
3134
+ })) {
3135
+ messagesForApi = [
3136
+ ...messages,
3137
+ {
3138
+ role: "user",
3139
+ content: [{ type: "text", text: lastTurnInstruction(options.outputFormat) }]
3140
+ }
3141
+ ];
3142
+ }
3112
3143
  const normalizedMessages = normalizeMessages(
3113
- messages
3144
+ messagesForApi
3114
3145
  );
3115
3146
  try {
3116
3147
  for await (const event of client.streamMessage({
3117
3148
  messages: normalizedMessages,
3118
3149
  system,
3119
3150
  tools: anthropicTools,
3120
- ...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {}
3151
+ ...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {},
3152
+ ...options.toolChoice !== void 0 ? { toolChoice: options.toolChoice } : {}
3121
3153
  })) {
3122
3154
  const handled = consumeEvent(event);
3123
3155
  if (handled.text !== void 0) textBlocks.push(handled.text);
@@ -3668,6 +3700,9 @@ var RunContext = class {
3668
3700
  getTurnCount() {
3669
3701
  return this.turnCount;
3670
3702
  }
3703
+ getMaxTurns() {
3704
+ return this.maxTurns;
3705
+ }
3671
3706
  getTokensUsed() {
3672
3707
  return this.tokensUsed;
3673
3708
  }
@@ -9657,6 +9692,35 @@ var TranscriptReader = class {
9657
9692
  }
9658
9693
  };
9659
9694
 
9695
+ // src/engine/runToolFilter.ts
9696
+ init_cjs_shims();
9697
+ function applyRunToolFilter(registry, options) {
9698
+ const stripAll = options.toolChoice === "none" || options.tools !== void 0 && options.tools.length === 0;
9699
+ if (stripAll) {
9700
+ if (options.toolChoice === "required") {
9701
+ throw new EngineError(
9702
+ "ERR_TOOL_CHOICE_CONFLICT",
9703
+ "toolChoice: 'required' is incompatible with an empty tool set (received tools: [] or toolChoice: 'none')."
9704
+ );
9705
+ }
9706
+ for (const tool of registry.list()) {
9707
+ registry.unregister(tool.name);
9708
+ }
9709
+ return;
9710
+ }
9711
+ if (options.tools === void 0) return;
9712
+ const allow = new Set(options.tools);
9713
+ for (const tool of registry.list()) {
9714
+ if (!allow.has(tool.name)) registry.unregister(tool.name);
9715
+ }
9716
+ if (options.toolChoice === "required" && registry.count() === 0) {
9717
+ throw new EngineError(
9718
+ "ERR_TOOL_CHOICE_CONFLICT",
9719
+ "toolChoice: 'required' was requested but no tools matched the per-run allowlist after applying config filters."
9720
+ );
9721
+ }
9722
+ }
9723
+
9660
9724
  // src/engine/rehydrate.ts
9661
9725
  init_cjs_shims();
9662
9726
  function rebuildMessagesFromEntries(entries) {
@@ -10133,6 +10197,7 @@ var Engine = class {
10133
10197
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10134
10198
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10135
10199
  });
10200
+ applyRunToolFilter(registry, options);
10136
10201
  const writer = new TranscriptWriter({
10137
10202
  storage: storage.workspace,
10138
10203
  logPath,
@@ -10187,7 +10252,14 @@ var Engine = class {
10187
10252
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10188
10253
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10189
10254
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10190
- ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {}
10255
+ ...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
10256
+ // Plan 025 — output mode + tool-choice plumbed down so the
10257
+ // last-turn guard picks the right instruction text and the
10258
+ // model adapter can pass `'required'` to the provider. `'none'`
10259
+ // is already handled above by stripping the tool list, so the
10260
+ // loop only ever sees `'auto'` or `'required'`.
10261
+ ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
10262
+ ...options.toolChoice === "required" ? { toolChoice: "required" } : {}
10191
10263
  });
10192
10264
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10193
10265
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},