la-machina-engine 0.7.2 → 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
  }
@@ -7866,6 +7901,9 @@ async function collectSkills(storage, skillsDir) {
7866
7901
  // src/engine/jsonOutput.ts
7867
7902
  init_cjs_shims();
7868
7903
  var import_zod_to_json_schema2 = require("zod-to-json-schema");
7904
+ function isZodSchema(s) {
7905
+ return s !== null && typeof s === "object" && "_def" in s && typeof s.safeParse === "function";
7906
+ }
7869
7907
  function buildSchemaPrompt(schema) {
7870
7908
  const lines = [
7871
7909
  "# Output Format",
@@ -7875,11 +7913,18 @@ function buildSchemaPrompt(schema) {
7875
7913
  "Do NOT wrap in ```json ... ```. Just raw JSON."
7876
7914
  ];
7877
7915
  if (schema) {
7878
- const jsonSchema2 = (0, import_zod_to_json_schema2.zodToJsonSchema)(schema, {
7879
- target: "jsonSchema7",
7880
- $refStrategy: "none"
7881
- });
7882
- const { $schema: _, ...clean } = jsonSchema2;
7916
+ let clean;
7917
+ if (isZodSchema(schema)) {
7918
+ const jsonSchema2 = (0, import_zod_to_json_schema2.zodToJsonSchema)(schema, {
7919
+ target: "jsonSchema7",
7920
+ $refStrategy: "none"
7921
+ });
7922
+ const { $schema: _z, ...rest } = jsonSchema2;
7923
+ clean = rest;
7924
+ } else {
7925
+ const { $schema: _j, ...rest } = schema;
7926
+ clean = rest;
7927
+ }
7883
7928
  lines.push("", "The JSON MUST conform to this schema:", JSON.stringify(clean, null, 2));
7884
7929
  } else {
7885
7930
  lines.push("", "Return a JSON object with the relevant data.");
@@ -7915,6 +7960,9 @@ function tryParseJSON2(text2) {
7915
7960
  return { ok: false, error: "No valid JSON found in response" };
7916
7961
  }
7917
7962
  function validateOutput(value, schema) {
7963
+ if (!isZodSchema(schema)) {
7964
+ return { ok: true, data: value };
7965
+ }
7918
7966
  const result = schema.safeParse(value);
7919
7967
  if (result.success) {
7920
7968
  return { ok: true, data: result.data };
@@ -9644,6 +9692,35 @@ var TranscriptReader = class {
9644
9692
  }
9645
9693
  };
9646
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
+
9647
9724
  // src/engine/rehydrate.ts
9648
9725
  init_cjs_shims();
9649
9726
  function rebuildMessagesFromEntries(entries) {
@@ -10120,6 +10197,7 @@ var Engine = class {
10120
10197
  ...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
10121
10198
  ...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
10122
10199
  });
10200
+ applyRunToolFilter(registry, options);
10123
10201
  const writer = new TranscriptWriter({
10124
10202
  storage: storage.workspace,
10125
10203
  logPath,
@@ -10174,7 +10252,14 @@ var Engine = class {
10174
10252
  ...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
10175
10253
  ...gate !== void 0 ? { gateBeforeTool: gate } : {},
10176
10254
  ..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
10177
- ...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" } : {}
10178
10263
  });
10179
10264
  const result = await this.finalizeResult(loopResult, writer, logPath, {
10180
10265
  ...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},