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 +76 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +76 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1280,6 +1280,18 @@ interface StreamRequest {
|
|
|
1280
1280
|
readonly tools?: readonly ModelToolDefinition[] | undefined;
|
|
1281
1281
|
readonly maxTokens?: number | undefined;
|
|
1282
1282
|
readonly temperature?: number | undefined;
|
|
1283
|
+
/**
|
|
1284
|
+
* Tool-call policy (Plan 025). Default: `'auto'` (model decides).
|
|
1285
|
+
* - `'auto'` — model picks; pass-through behavior
|
|
1286
|
+
* - `'required'` — model MUST call a tool on its next response;
|
|
1287
|
+
* translated per-provider (Anthropic:
|
|
1288
|
+
* `tool_choice: { type: 'any' }`; OpenAI / AI SDK:
|
|
1289
|
+
* `toolChoice: 'required'`).
|
|
1290
|
+
*
|
|
1291
|
+
* Note: `'none'` is handled engine-side by stripping `tools` before
|
|
1292
|
+
* the request reaches the adapter, so adapters never see it.
|
|
1293
|
+
*/
|
|
1294
|
+
readonly toolChoice?: 'auto' | 'required' | undefined;
|
|
1283
1295
|
}
|
|
1284
1296
|
|
|
1285
1297
|
interface ModelAdapter {
|
|
@@ -2000,7 +2012,30 @@ interface RunOptions {
|
|
|
2000
2012
|
readonly runId?: string;
|
|
2001
2013
|
readonly nodeId: string;
|
|
2002
2014
|
readonly task: string;
|
|
2015
|
+
/**
|
|
2016
|
+
* Per-run tool allowlist (Plan 025). When undefined, the run sees
|
|
2017
|
+
* every tool registered at engine init (subject to
|
|
2018
|
+
* `config.tools.enabled / disabled`). When provided, the visible
|
|
2019
|
+
* tool set is further restricted to this exact list — names that
|
|
2020
|
+
* aren't registered are silently ignored. Pass `[]` to disable
|
|
2021
|
+
* all tools for this run; equivalent to `toolChoice: 'none'`.
|
|
2022
|
+
*/
|
|
2003
2023
|
readonly tools?: readonly string[] | undefined;
|
|
2024
|
+
/**
|
|
2025
|
+
* Tool-call policy for this run (Plan 025). Default: `'auto'`.
|
|
2026
|
+
* - `'auto'` — model decides whether to call tools
|
|
2027
|
+
* - `'none'` — model is told it has no tools; tools list is
|
|
2028
|
+
* stripped before the model adapter sees it
|
|
2029
|
+
* - `'required'` — model MUST call a tool on its next response;
|
|
2030
|
+
* plumbed to the provider as the "force tool use"
|
|
2031
|
+
* flag (Anthropic: `tool_choice: { type: 'any' }`,
|
|
2032
|
+
* OpenAI / AI SDK: `toolChoice: 'required'`)
|
|
2033
|
+
*
|
|
2034
|
+
* Precedence: `'none'` wins over any `tools: [...]` allowlist.
|
|
2035
|
+
* `'required'` paired with an empty effective tool set throws
|
|
2036
|
+
* `ERR_TOOL_CHOICE_CONFLICT` at run start.
|
|
2037
|
+
*/
|
|
2038
|
+
readonly toolChoice?: 'auto' | 'none' | 'required' | undefined;
|
|
2004
2039
|
readonly context?: Readonly<Record<string, unknown>> | undefined;
|
|
2005
2040
|
/** Override maxTurns for this run only. Used by the orchestrator for per-phase budgets. */
|
|
2006
2041
|
readonly maxTurns?: number | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -1280,6 +1280,18 @@ interface StreamRequest {
|
|
|
1280
1280
|
readonly tools?: readonly ModelToolDefinition[] | undefined;
|
|
1281
1281
|
readonly maxTokens?: number | undefined;
|
|
1282
1282
|
readonly temperature?: number | undefined;
|
|
1283
|
+
/**
|
|
1284
|
+
* Tool-call policy (Plan 025). Default: `'auto'` (model decides).
|
|
1285
|
+
* - `'auto'` — model picks; pass-through behavior
|
|
1286
|
+
* - `'required'` — model MUST call a tool on its next response;
|
|
1287
|
+
* translated per-provider (Anthropic:
|
|
1288
|
+
* `tool_choice: { type: 'any' }`; OpenAI / AI SDK:
|
|
1289
|
+
* `toolChoice: 'required'`).
|
|
1290
|
+
*
|
|
1291
|
+
* Note: `'none'` is handled engine-side by stripping `tools` before
|
|
1292
|
+
* the request reaches the adapter, so adapters never see it.
|
|
1293
|
+
*/
|
|
1294
|
+
readonly toolChoice?: 'auto' | 'required' | undefined;
|
|
1283
1295
|
}
|
|
1284
1296
|
|
|
1285
1297
|
interface ModelAdapter {
|
|
@@ -2000,7 +2012,30 @@ interface RunOptions {
|
|
|
2000
2012
|
readonly runId?: string;
|
|
2001
2013
|
readonly nodeId: string;
|
|
2002
2014
|
readonly task: string;
|
|
2015
|
+
/**
|
|
2016
|
+
* Per-run tool allowlist (Plan 025). When undefined, the run sees
|
|
2017
|
+
* every tool registered at engine init (subject to
|
|
2018
|
+
* `config.tools.enabled / disabled`). When provided, the visible
|
|
2019
|
+
* tool set is further restricted to this exact list — names that
|
|
2020
|
+
* aren't registered are silently ignored. Pass `[]` to disable
|
|
2021
|
+
* all tools for this run; equivalent to `toolChoice: 'none'`.
|
|
2022
|
+
*/
|
|
2003
2023
|
readonly tools?: readonly string[] | undefined;
|
|
2024
|
+
/**
|
|
2025
|
+
* Tool-call policy for this run (Plan 025). Default: `'auto'`.
|
|
2026
|
+
* - `'auto'` — model decides whether to call tools
|
|
2027
|
+
* - `'none'` — model is told it has no tools; tools list is
|
|
2028
|
+
* stripped before the model adapter sees it
|
|
2029
|
+
* - `'required'` — model MUST call a tool on its next response;
|
|
2030
|
+
* plumbed to the provider as the "force tool use"
|
|
2031
|
+
* flag (Anthropic: `tool_choice: { type: 'any' }`,
|
|
2032
|
+
* OpenAI / AI SDK: `toolChoice: 'required'`)
|
|
2033
|
+
*
|
|
2034
|
+
* Precedence: `'none'` wins over any `tools: [...]` allowlist.
|
|
2035
|
+
* `'required'` paired with an empty effective tool set throws
|
|
2036
|
+
* `ERR_TOOL_CHOICE_CONFLICT` at run start.
|
|
2037
|
+
*/
|
|
2038
|
+
readonly toolChoice?: 'auto' | 'none' | 'required' | undefined;
|
|
2004
2039
|
readonly context?: Readonly<Record<string, unknown>> | undefined;
|
|
2005
2040
|
/** Override maxTurns for this run only. Used by the orchestrator for per-phase budgets. */
|
|
2006
2041
|
readonly maxTurns?: number | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1716,7 +1716,11 @@ var AnthropicClient = class {
|
|
|
1716
1716
|
messages: request.messages,
|
|
1717
1717
|
stream: true,
|
|
1718
1718
|
...request.system !== void 0 ? { system: request.system } : {},
|
|
1719
|
-
...request.tools !== void 0 ? { tools: request.tools } : {}
|
|
1719
|
+
...request.tools !== void 0 ? { tools: request.tools } : {},
|
|
1720
|
+
// Plan 025 — `'required'` maps to Anthropic's `tool_choice: { type: 'any' }`,
|
|
1721
|
+
// which forces the model to call SOME tool but doesn't pin which.
|
|
1722
|
+
// `'auto'` is the SDK default — omit to let it through unchanged.
|
|
1723
|
+
...request.toolChoice === "required" ? { tool_choice: { type: "any" } } : {}
|
|
1720
1724
|
};
|
|
1721
1725
|
const requestOptions = {};
|
|
1722
1726
|
if (betas.length > 0) {
|
|
@@ -1898,6 +1902,9 @@ var AISdkAdapter = class {
|
|
|
1898
1902
|
tools,
|
|
1899
1903
|
...request.maxTokens !== void 0 ? { maxOutputTokens: request.maxTokens } : {},
|
|
1900
1904
|
...request.temperature !== void 0 ? { temperature: request.temperature } : {},
|
|
1905
|
+
// Plan 025 — pass through `'required'` so the AI SDK forwards it
|
|
1906
|
+
// to the provider as that provider's "force tool call" flag.
|
|
1907
|
+
...request.toolChoice === "required" ? { toolChoice: "required" } : {},
|
|
1901
1908
|
maxRetries: this.options.maxRetries ?? 2
|
|
1902
1909
|
});
|
|
1903
1910
|
for await (const event of result.fullStream) {
|
|
@@ -2517,6 +2524,17 @@ function ensureToolResultPairing(messages) {
|
|
|
2517
2524
|
return messages;
|
|
2518
2525
|
}
|
|
2519
2526
|
|
|
2527
|
+
// src/engine/lastTurnGuard.ts
|
|
2528
|
+
init_esm_shims();
|
|
2529
|
+
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.";
|
|
2530
|
+
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.";
|
|
2531
|
+
function lastTurnInstruction(outputFormat) {
|
|
2532
|
+
return outputFormat === "json" ? LAST_TURN_INSTRUCTION_JSON : LAST_TURN_INSTRUCTION_TEXT;
|
|
2533
|
+
}
|
|
2534
|
+
function shouldInjectLastTurnInstruction(opts) {
|
|
2535
|
+
return opts.turnCount + 1 === opts.maxTurns;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2520
2538
|
// src/compact/compactor.ts
|
|
2521
2539
|
init_esm_shims();
|
|
2522
2540
|
|
|
@@ -3008,15 +3026,29 @@ async function agentLoop(options) {
|
|
|
3008
3026
|
const toolCalls = [];
|
|
3009
3027
|
let stopReason = null;
|
|
3010
3028
|
let turnUsage = { input: 0, output: 0 };
|
|
3029
|
+
let messagesForApi = messages;
|
|
3030
|
+
if (shouldInjectLastTurnInstruction({
|
|
3031
|
+
turnCount: ctx.getTurnCount(),
|
|
3032
|
+
maxTurns: ctx.getMaxTurns()
|
|
3033
|
+
})) {
|
|
3034
|
+
messagesForApi = [
|
|
3035
|
+
...messages,
|
|
3036
|
+
{
|
|
3037
|
+
role: "user",
|
|
3038
|
+
content: [{ type: "text", text: lastTurnInstruction(options.outputFormat) }]
|
|
3039
|
+
}
|
|
3040
|
+
];
|
|
3041
|
+
}
|
|
3011
3042
|
const normalizedMessages = normalizeMessages(
|
|
3012
|
-
|
|
3043
|
+
messagesForApi
|
|
3013
3044
|
);
|
|
3014
3045
|
try {
|
|
3015
3046
|
for await (const event of client.streamMessage({
|
|
3016
3047
|
messages: normalizedMessages,
|
|
3017
3048
|
system,
|
|
3018
3049
|
tools: anthropicTools,
|
|
3019
|
-
...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {}
|
|
3050
|
+
...escalatedMaxTokens !== void 0 ? { maxTokens: escalatedMaxTokens } : {},
|
|
3051
|
+
...options.toolChoice !== void 0 ? { toolChoice: options.toolChoice } : {}
|
|
3020
3052
|
})) {
|
|
3021
3053
|
const handled = consumeEvent(event);
|
|
3022
3054
|
if (handled.text !== void 0) textBlocks.push(handled.text);
|
|
@@ -3567,6 +3599,9 @@ var RunContext = class {
|
|
|
3567
3599
|
getTurnCount() {
|
|
3568
3600
|
return this.turnCount;
|
|
3569
3601
|
}
|
|
3602
|
+
getMaxTurns() {
|
|
3603
|
+
return this.maxTurns;
|
|
3604
|
+
}
|
|
3570
3605
|
getTokensUsed() {
|
|
3571
3606
|
return this.tokensUsed;
|
|
3572
3607
|
}
|
|
@@ -9565,6 +9600,35 @@ var TranscriptReader = class {
|
|
|
9565
9600
|
}
|
|
9566
9601
|
};
|
|
9567
9602
|
|
|
9603
|
+
// src/engine/runToolFilter.ts
|
|
9604
|
+
init_esm_shims();
|
|
9605
|
+
function applyRunToolFilter(registry, options) {
|
|
9606
|
+
const stripAll = options.toolChoice === "none" || options.tools !== void 0 && options.tools.length === 0;
|
|
9607
|
+
if (stripAll) {
|
|
9608
|
+
if (options.toolChoice === "required") {
|
|
9609
|
+
throw new EngineError(
|
|
9610
|
+
"ERR_TOOL_CHOICE_CONFLICT",
|
|
9611
|
+
"toolChoice: 'required' is incompatible with an empty tool set (received tools: [] or toolChoice: 'none')."
|
|
9612
|
+
);
|
|
9613
|
+
}
|
|
9614
|
+
for (const tool of registry.list()) {
|
|
9615
|
+
registry.unregister(tool.name);
|
|
9616
|
+
}
|
|
9617
|
+
return;
|
|
9618
|
+
}
|
|
9619
|
+
if (options.tools === void 0) return;
|
|
9620
|
+
const allow = new Set(options.tools);
|
|
9621
|
+
for (const tool of registry.list()) {
|
|
9622
|
+
if (!allow.has(tool.name)) registry.unregister(tool.name);
|
|
9623
|
+
}
|
|
9624
|
+
if (options.toolChoice === "required" && registry.count() === 0) {
|
|
9625
|
+
throw new EngineError(
|
|
9626
|
+
"ERR_TOOL_CHOICE_CONFLICT",
|
|
9627
|
+
"toolChoice: 'required' was requested but no tools matched the per-run allowlist after applying config filters."
|
|
9628
|
+
);
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
|
|
9568
9632
|
// src/engine/rehydrate.ts
|
|
9569
9633
|
init_esm_shims();
|
|
9570
9634
|
function rebuildMessagesFromEntries(entries) {
|
|
@@ -10041,6 +10105,7 @@ var Engine = class {
|
|
|
10041
10105
|
...knowledgeRuntime !== void 0 ? { knowledge: knowledgeRuntime } : {},
|
|
10042
10106
|
...this.internals.fetch !== void 0 ? { fetch: this.internals.fetch } : {}
|
|
10043
10107
|
});
|
|
10108
|
+
applyRunToolFilter(registry, options);
|
|
10044
10109
|
const writer = new TranscriptWriter({
|
|
10045
10110
|
storage: storage.workspace,
|
|
10046
10111
|
logPath,
|
|
@@ -10095,7 +10160,14 @@ var Engine = class {
|
|
|
10095
10160
|
...runTimeout.signal !== void 0 ? { runSignal: runTimeout.signal, runTimeoutMs: this.config.execution.runTimeoutMs } : {},
|
|
10096
10161
|
...gate !== void 0 ? { gateBeforeTool: gate } : {},
|
|
10097
10162
|
..._internal?.handoffToRunner === true ? { handoffToRunner: true } : {},
|
|
10098
|
-
...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {}
|
|
10163
|
+
...offloadConfig !== void 0 ? { toolResultOffload: offloadConfig } : {},
|
|
10164
|
+
// Plan 025 — output mode + tool-choice plumbed down so the
|
|
10165
|
+
// last-turn guard picks the right instruction text and the
|
|
10166
|
+
// model adapter can pass `'required'` to the provider. `'none'`
|
|
10167
|
+
// is already handled above by stripping the tool list, so the
|
|
10168
|
+
// loop only ever sees `'auto'` or `'required'`.
|
|
10169
|
+
...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
|
|
10170
|
+
...options.toolChoice === "required" ? { toolChoice: "required" } : {}
|
|
10099
10171
|
});
|
|
10100
10172
|
const result = await this.finalizeResult(loopResult, writer, logPath, {
|
|
10101
10173
|
...options.outputFormat !== void 0 ? { outputFormat: options.outputFormat } : {},
|