agentfootprint 2.4.0 → 2.5.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.
- package/README.md +21 -3
- package/dist/adapters/llm/AnthropicProvider.js +2 -2
- package/dist/adapters/llm/AnthropicProvider.js.map +1 -1
- package/dist/adapters/llm/BedrockProvider.js +2 -2
- package/dist/adapters/llm/BedrockProvider.js.map +1 -1
- package/dist/adapters/llm/BrowserAnthropicProvider.js +96 -28
- package/dist/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/adapters/llm/OpenAIProvider.js +2 -2
- package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/adapters/memory/agentcore.js +9 -3
- package/dist/adapters/memory/agentcore.js.map +1 -1
- package/dist/adapters/memory/redis.js +9 -3
- package/dist/adapters/memory/redis.js.map +1 -1
- package/dist/core/Agent.js +477 -79
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/flowchartAsTool.js +188 -0
- package/dist/core/flowchartAsTool.js.map +1 -0
- package/dist/core/outputSchema.js +119 -0
- package/dist/core/outputSchema.js.map +1 -0
- package/dist/core/slots/buildSystemPromptSlot.js +8 -0
- package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +56 -5
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/adapters/llm/AnthropicProvider.js +2 -2
- package/dist/esm/adapters/llm/AnthropicProvider.js.map +1 -1
- package/dist/esm/adapters/llm/BedrockProvider.js +2 -2
- package/dist/esm/adapters/llm/BedrockProvider.js.map +1 -1
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js +96 -28
- package/dist/esm/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
- package/dist/esm/adapters/llm/OpenAIProvider.js +2 -2
- package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/esm/adapters/memory/agentcore.js +9 -3
- package/dist/esm/adapters/memory/agentcore.js.map +1 -1
- package/dist/esm/adapters/memory/redis.js +9 -3
- package/dist/esm/adapters/memory/redis.js.map +1 -1
- package/dist/esm/core/Agent.js +476 -78
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/flowchartAsTool.js +184 -0
- package/dist/esm/core/flowchartAsTool.js.map +1 -0
- package/dist/esm/core/outputSchema.js +113 -0
- package/dist/esm/core/outputSchema.js.map +1 -0
- package/dist/esm/core/slots/buildSystemPromptSlot.js +8 -0
- package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +56 -5
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/index.js +34 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/SkillRegistry.js +98 -0
- package/dist/esm/lib/injection-engine/SkillRegistry.js.map +1 -1
- package/dist/esm/lib/injection-engine/factories/defineSkill.js +1 -0
- package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
- package/dist/esm/lib/injection-engine/index.js +7 -0
- package/dist/esm/lib/injection-engine/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillTools.js +125 -0
- package/dist/esm/lib/injection-engine/skillTools.js.map +1 -0
- package/dist/esm/lib/injection-engine/types.js +8 -0
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/esm/lib/lazyRequire.js +33 -0
- package/dist/esm/lib/lazyRequire.js.map +1 -0
- package/dist/esm/lib/mcp/mcpClient.js +4 -6
- package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
- package/dist/esm/llm-providers.js +31 -0
- package/dist/esm/llm-providers.js.map +1 -0
- package/dist/esm/locales/index.js +144 -0
- package/dist/esm/locales/index.js.map +1 -0
- package/dist/esm/memory-providers.js +44 -0
- package/dist/esm/memory-providers.js.map +1 -0
- package/dist/esm/providers.js +11 -0
- package/dist/esm/providers.js.map +1 -1
- package/dist/esm/recorders/core/contextEngineering.js +155 -0
- package/dist/esm/recorders/core/contextEngineering.js.map +1 -0
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +6 -1
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/esm/security/PermissionPolicy.js +135 -0
- package/dist/esm/security/PermissionPolicy.js.map +1 -0
- package/dist/esm/security/index.js +38 -0
- package/dist/esm/security/index.js.map +1 -0
- package/dist/esm/tool-providers/gatedTools.js +52 -0
- package/dist/esm/tool-providers/gatedTools.js.map +1 -0
- package/dist/esm/tool-providers/index.js +43 -0
- package/dist/esm/tool-providers/index.js.map +1 -0
- package/dist/esm/tool-providers/skillScopedTools.js +63 -0
- package/dist/esm/tool-providers/skillScopedTools.js.map +1 -0
- package/dist/esm/tool-providers/staticTools.js +33 -0
- package/dist/esm/tool-providers/staticTools.js.map +1 -0
- package/dist/esm/tool-providers/types.js +53 -0
- package/dist/esm/tool-providers/types.js.map +1 -0
- package/dist/index.js +55 -12
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/SkillRegistry.js +98 -0
- package/dist/lib/injection-engine/SkillRegistry.js.map +1 -1
- package/dist/lib/injection-engine/factories/defineSkill.js +1 -0
- package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
- package/dist/lib/injection-engine/index.js +11 -1
- package/dist/lib/injection-engine/index.js.map +1 -1
- package/dist/lib/injection-engine/skillTools.js +130 -0
- package/dist/lib/injection-engine/skillTools.js.map +1 -0
- package/dist/lib/injection-engine/types.js +8 -0
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/lib/lazyRequire.js +37 -0
- package/dist/lib/lazyRequire.js.map +1 -0
- package/dist/lib/mcp/mcpClient.js +4 -6
- package/dist/lib/mcp/mcpClient.js.map +1 -1
- package/dist/llm-providers.js +47 -0
- package/dist/llm-providers.js.map +1 -0
- package/dist/locales/index.js +149 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/memory-providers.js +49 -0
- package/dist/memory-providers.js.map +1 -0
- package/dist/providers.js +11 -0
- package/dist/providers.js.map +1 -1
- package/dist/recorders/core/contextEngineering.js +161 -0
- package/dist/recorders/core/contextEngineering.js.map +1 -0
- package/dist/recorders/observability/commentary/commentaryTemplates.js +6 -1
- package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/security/PermissionPolicy.js +139 -0
- package/dist/security/PermissionPolicy.js.map +1 -0
- package/dist/security/index.js +42 -0
- package/dist/security/index.js.map +1 -0
- package/dist/tool-providers/gatedTools.js +56 -0
- package/dist/tool-providers/gatedTools.js.map +1 -0
- package/dist/tool-providers/index.js +51 -0
- package/dist/tool-providers/index.js.map +1 -0
- package/dist/tool-providers/skillScopedTools.js +67 -0
- package/dist/tool-providers/skillScopedTools.js.map +1 -0
- package/dist/tool-providers/staticTools.js +37 -0
- package/dist/tool-providers/staticTools.js.map +1 -0
- package/dist/tool-providers/types.js +54 -0
- package/dist/tool-providers/types.js.map +1 -0
- package/dist/types/adapters/llm/AnthropicProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/BedrockProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/BrowserAnthropicProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
- package/dist/types/adapters/memory/agentcore.d.ts +7 -1
- package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/memory/redis.d.ts +7 -1
- package/dist/types/adapters/memory/redis.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +202 -2
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/flowchartAsTool.d.ts +161 -0
- package/dist/types/core/flowchartAsTool.d.ts.map +1 -0
- package/dist/types/core/outputSchema.d.ts +128 -0
- package/dist/types/core/outputSchema.d.ts.map +1 -0
- package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +10 -0
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/SkillRegistry.d.ts +98 -0
- package/dist/types/lib/injection-engine/SkillRegistry.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +33 -6
- package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/index.d.ts +5 -3
- package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillTools.d.ts +73 -0
- package/dist/types/lib/injection-engine/skillTools.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/types.d.ts +18 -0
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/dist/types/lib/lazyRequire.d.ts +30 -0
- package/dist/types/lib/lazyRequire.d.ts.map +1 -0
- package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/llm-providers.d.ts +31 -0
- package/dist/types/llm-providers.d.ts.map +1 -0
- package/dist/types/locales/index.d.ts +133 -0
- package/dist/types/locales/index.d.ts.map +1 -0
- package/dist/types/memory-providers.d.ts +41 -0
- package/dist/types/memory-providers.d.ts.map +1 -0
- package/dist/types/providers.d.ts +11 -0
- package/dist/types/providers.d.ts.map +1 -1
- package/dist/types/recorders/core/contextEngineering.d.ts +137 -0
- package/dist/types/recorders/core/contextEngineering.d.ts.map +1 -0
- package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
- package/dist/types/security/PermissionPolicy.d.ts +125 -0
- package/dist/types/security/PermissionPolicy.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +40 -0
- package/dist/types/security/index.d.ts.map +1 -0
- package/dist/types/tool-providers/gatedTools.d.ts +37 -0
- package/dist/types/tool-providers/gatedTools.d.ts.map +1 -0
- package/dist/types/tool-providers/index.d.ts +42 -0
- package/dist/types/tool-providers/index.d.ts.map +1 -0
- package/dist/types/tool-providers/skillScopedTools.d.ts +46 -0
- package/dist/types/tool-providers/skillScopedTools.d.ts.map +1 -0
- package/dist/types/tool-providers/staticTools.d.ts +22 -0
- package/dist/types/tool-providers/staticTools.d.ts.map +1 -0
- package/dist/types/tool-providers/types.d.ts +103 -0
- package/dist/types/tool-providers/types.d.ts.map +1 -0
- package/package.json +36 -2
package/dist/core/Agent.js
CHANGED
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.AgentBuilder = exports.Agent = void 0;
|
|
19
19
|
const footprintjs_1 = require("footprintjs");
|
|
20
|
+
// ArrayMergeMode lives on footprintjs's `advanced` subpath, not its
|
|
21
|
+
// main barrel. Used to set `arrayMerge: Replace` on subflow output
|
|
22
|
+
// mapping for the Tools slot — the slot's deduped tool list must
|
|
23
|
+
// REPLACE the parent's `dynamicToolSchemas` rather than concatenate
|
|
24
|
+
// with it (default behavior re-introduces duplicate tool names that
|
|
25
|
+
// LLM providers reject).
|
|
26
|
+
const advanced_1 = require("footprintjs/advanced");
|
|
20
27
|
const pause_js_1 = require("./pause.js");
|
|
21
28
|
const cost_js_1 = require("./cost.js");
|
|
22
29
|
const conventions_js_1 = require("../conventions.js");
|
|
@@ -38,8 +45,10 @@ const buildSystemPromptSlot_js_1 = require("./slots/buildSystemPromptSlot.js");
|
|
|
38
45
|
const buildMessagesSlot_js_1 = require("./slots/buildMessagesSlot.js");
|
|
39
46
|
const buildToolsSlot_js_1 = require("./slots/buildToolsSlot.js");
|
|
40
47
|
const buildInjectionEngineSubflow_js_1 = require("../lib/injection-engine/buildInjectionEngineSubflow.js");
|
|
48
|
+
const skillTools_js_1 = require("../lib/injection-engine/skillTools.js");
|
|
49
|
+
const defineInstruction_js_1 = require("../lib/injection-engine/factories/defineInstruction.js");
|
|
50
|
+
const outputSchema_js_1 = require("./outputSchema.js");
|
|
41
51
|
const RunnerBase_js_1 = require("./RunnerBase.js");
|
|
42
|
-
const tools_js_1 = require("./tools.js");
|
|
43
52
|
class Agent extends RunnerBase_js_1.RunnerBase {
|
|
44
53
|
name;
|
|
45
54
|
id;
|
|
@@ -74,6 +83,21 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
74
83
|
runId: 'pending',
|
|
75
84
|
compositionPath: [],
|
|
76
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Reference to the most recent executor. Set on every `createExecutor()`
|
|
88
|
+
* call (i.e., every `run()` and `resume()`); read by `getLastSnapshot()`
|
|
89
|
+
* / `getLastNarrativeEntries()` so post-run UIs (Lens Trace tab,
|
|
90
|
+
* ExplainableShell) can pull execution state without intercepting the
|
|
91
|
+
* call. `undefined` until the first run.
|
|
92
|
+
*/
|
|
93
|
+
lastExecutor;
|
|
94
|
+
/**
|
|
95
|
+
* Reference to the FlowChart compiled for the most recent run. Cached
|
|
96
|
+
* here rather than recomputed via `buildChart()` so `getSpec()` returns
|
|
97
|
+
* the SAME spec the executor traced — important when the spec is used
|
|
98
|
+
* to reconcile `getLastSnapshot()` for ExplainableShell.
|
|
99
|
+
*/
|
|
100
|
+
lastFlowChart;
|
|
77
101
|
/**
|
|
78
102
|
* Memory subsystems registered via `.memory()`. Each definition mounts
|
|
79
103
|
* its `read` subflow before the InjectionEngine on every turn; per-id
|
|
@@ -81,7 +105,24 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
81
105
|
* collision-free.
|
|
82
106
|
*/
|
|
83
107
|
memories;
|
|
84
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Optional terminal contract. Set via the builder's `.outputSchema()`.
|
|
110
|
+
* When present, `agent.runTyped()` parses + validates the final
|
|
111
|
+
* answer against this parser. `agent.run()` keeps returning the
|
|
112
|
+
* raw string; consumers opt into typed mode explicitly.
|
|
113
|
+
*/
|
|
114
|
+
outputSchemaParser;
|
|
115
|
+
/**
|
|
116
|
+
* Optional `ToolProvider` set via the builder's `.toolProvider()`.
|
|
117
|
+
* When present, the Tools slot subflow consults it per iteration
|
|
118
|
+
* (Block A5 follow-up) — the provider's tools land alongside any
|
|
119
|
+
* tools registered statically via `.tool()` / `.tools()`. The
|
|
120
|
+
* tool-call dispatcher also consults it for per-iteration execute
|
|
121
|
+
* lookup so dynamic chains (`gatedTools`, `skillScopedTools`)
|
|
122
|
+
* dispatch correctly when their visible-set changes mid-turn.
|
|
123
|
+
*/
|
|
124
|
+
externalToolProvider;
|
|
125
|
+
constructor(opts, systemPromptValue, registry, voice, injections = [], memories = [], outputSchemaParser, toolProvider) {
|
|
85
126
|
super();
|
|
86
127
|
this.provider = opts.provider;
|
|
87
128
|
this.name = opts.name ?? 'Agent';
|
|
@@ -94,6 +135,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
94
135
|
this.registry = registry;
|
|
95
136
|
this.injections = injections;
|
|
96
137
|
this.memories = memories;
|
|
138
|
+
this.outputSchemaParser = outputSchemaParser;
|
|
139
|
+
this.externalToolProvider = toolProvider;
|
|
97
140
|
// Eager validation: tool names must be unique across .tool() +
|
|
98
141
|
// every Skill.inject.tools — the LLM dispatches by name. Runs in
|
|
99
142
|
// constructor so `Agent.build()` throws immediately on collision,
|
|
@@ -118,6 +161,77 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
118
161
|
toFlowChart() {
|
|
119
162
|
return this.buildChart();
|
|
120
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* The footprintjs `RuntimeSnapshot` from the most recent `run()` /
|
|
166
|
+
* `resume()`. Feeds Lens's Trace tab (ExplainableShell `runtimeSnapshot`
|
|
167
|
+
* prop) so consumers can scrub the execution timeline post-run without
|
|
168
|
+
* threading a recorder through the call site.
|
|
169
|
+
*
|
|
170
|
+
* Returns `undefined` before the first run completes. Returns the
|
|
171
|
+
* snapshot of the most recent run on every call after — including
|
|
172
|
+
* across multiple turns of the same Agent instance.
|
|
173
|
+
*/
|
|
174
|
+
getLastSnapshot() {
|
|
175
|
+
return this.lastExecutor?.getSnapshot();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Structured narrative entries from the most recent run. Pairs with
|
|
179
|
+
* `getLastSnapshot()` for ExplainableShell's `narrativeEntries` prop.
|
|
180
|
+
* Empty array (not `undefined`) when no run has completed — matches
|
|
181
|
+
* the prop's expected shape so consumers can wire it directly without
|
|
182
|
+
* a defensive guard.
|
|
183
|
+
*/
|
|
184
|
+
getLastNarrativeEntries() {
|
|
185
|
+
return this.lastExecutor?.getNarrativeEntries() ?? [];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The FlowChart compiled for the most recent run (or a freshly-built
|
|
189
|
+
* one if no run has happened yet). Feeds ExplainableShell's `spec`
|
|
190
|
+
* prop. Returning the cached chart matters: the spec must match what
|
|
191
|
+
* `getLastSnapshot()` traced, otherwise the Trace view's stage tree
|
|
192
|
+
* desyncs from the snapshot's runtime tree.
|
|
193
|
+
*/
|
|
194
|
+
getSpec() {
|
|
195
|
+
return this.lastFlowChart ?? this.buildChart();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse + validate a raw agent answer against the agent's
|
|
199
|
+
* `outputSchema` parser. Throws `OutputSchemaError` on JSON parse
|
|
200
|
+
* or schema validation failure (the rawOutput is preserved on the
|
|
201
|
+
* error for triage). Throws a plain `Error` if the agent has no
|
|
202
|
+
* outputSchema set.
|
|
203
|
+
*
|
|
204
|
+
* Use this when you need to keep `agent.run()` returning the raw
|
|
205
|
+
* string for logging/observability and validate at a different
|
|
206
|
+
* layer; otherwise prefer `agent.runTyped()`.
|
|
207
|
+
*/
|
|
208
|
+
parseOutput(raw) {
|
|
209
|
+
if (!this.outputSchemaParser) {
|
|
210
|
+
throw new Error(`Agent.parseOutput: this agent has no outputSchema. Use ` +
|
|
211
|
+
`Agent.create({...}).outputSchema(parser).build() to enable typed output.`);
|
|
212
|
+
}
|
|
213
|
+
return (0, outputSchema_js_1.applyOutputSchema)(raw, this.outputSchemaParser);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Run the agent and return the schema-validated typed output.
|
|
217
|
+
* Convenience over `parseOutput(await agent.run({...}))`.
|
|
218
|
+
*
|
|
219
|
+
* Throws `OutputSchemaError` on parse / validation failure.
|
|
220
|
+
* Throws if the agent has no outputSchema set or if the run
|
|
221
|
+
* pauses (use `run()` directly when pauses are expected).
|
|
222
|
+
*/
|
|
223
|
+
async runTyped(input, options) {
|
|
224
|
+
if (!this.outputSchemaParser) {
|
|
225
|
+
throw new Error(`Agent.runTyped: this agent has no outputSchema. Use ` +
|
|
226
|
+
`Agent.create({...}).outputSchema(parser).build() to enable typed output.`);
|
|
227
|
+
}
|
|
228
|
+
const out = await this.run(input, options);
|
|
229
|
+
if (typeof out !== 'string') {
|
|
230
|
+
throw new Error('Agent.runTyped: run paused — typed mode does not support pauses. ' +
|
|
231
|
+
'Use agent.run() + agent.parseOutput(...) after resume.');
|
|
232
|
+
}
|
|
233
|
+
return this.parseOutput(out);
|
|
234
|
+
}
|
|
121
235
|
async run(input, options) {
|
|
122
236
|
const executor = this.createExecutor();
|
|
123
237
|
const result = await executor.run({
|
|
@@ -147,6 +261,12 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
147
261
|
};
|
|
148
262
|
const chart = this.buildChart();
|
|
149
263
|
const executor = new footprintjs_1.FlowChartExecutor(chart);
|
|
264
|
+
// Enable structured narrative so `getLastNarrativeEntries()` can
|
|
265
|
+
// hand a populated array to consumer Trace views (ExplainableShell).
|
|
266
|
+
// Cheap when no consumer reads it; the recorder accumulates only.
|
|
267
|
+
executor.enableNarrative();
|
|
268
|
+
this.lastExecutor = executor;
|
|
269
|
+
this.lastFlowChart = chart;
|
|
150
270
|
const dispatcher = this.getDispatcher();
|
|
151
271
|
const getRunCtx = () => this.currentRunContext;
|
|
152
272
|
executor.attachCombinedRecorder(new ContextRecorder_js_1.ContextRecorder({ dispatcher, getRunContext: getRunCtx }));
|
|
@@ -252,31 +372,86 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
252
372
|
// ability to call the right tool. Throw early instead of subtly
|
|
253
373
|
// shadowing.
|
|
254
374
|
const skills = this.injections.filter((i) => i.flavor === 'skill');
|
|
375
|
+
// Collect skill tools, deduping by name when the SAME Tool reference
|
|
376
|
+
// is shared across skills. Different Tool implementations under the
|
|
377
|
+
// same name throws (already validated upstream by
|
|
378
|
+
// validateToolNameUniqueness) — we keep the runtime check as
|
|
379
|
+
// belt-and-suspenders.
|
|
380
|
+
//
|
|
381
|
+
// Block C runtime — `autoActivate: 'currentSkill'` semantics:
|
|
382
|
+
// When a skill's `defineSkill({ autoActivate: 'currentSkill' })`
|
|
383
|
+
// is set, its tools are EXCLUDED from the static registry. They
|
|
384
|
+
// flow into the LLM's tool list ONLY through `dynamicSchemas`
|
|
385
|
+
// (the buildToolsSlot path that reads activeInjections), which
|
|
386
|
+
// means they're visible ONLY on iterations after the skill is
|
|
387
|
+
// activated by `read_skill('id')`. Without this, the LLM sees
|
|
388
|
+
// every skill's tools on every iteration and the
|
|
389
|
+
// per-skill-narrowing autoActivate promised in `defineSkill`
|
|
390
|
+
// doesn't actually narrow anything. Skills WITHOUT autoActivate
|
|
391
|
+
// keep the v2.4 behavior (tools always visible) for back-compat.
|
|
255
392
|
const skillToolEntries = [];
|
|
393
|
+
const sharedSkillTools = new Map();
|
|
256
394
|
for (const skill of skills) {
|
|
395
|
+
const meta = skill.metadata;
|
|
396
|
+
const isAutoActivate = meta?.autoActivate === 'currentSkill';
|
|
257
397
|
const toolsFromSkill = skill.inject.tools ?? [];
|
|
258
398
|
for (const tool of toolsFromSkill) {
|
|
259
|
-
|
|
399
|
+
const name = tool.schema.name;
|
|
400
|
+
const existing = sharedSkillTools.get(name);
|
|
401
|
+
if (existing) {
|
|
402
|
+
if (existing !== tool) {
|
|
403
|
+
throw new Error(`Agent: tool name '${name}' is declared by multiple skills with different ` +
|
|
404
|
+
`Tool implementations. Skills MAY share the SAME Tool reference; they may ` +
|
|
405
|
+
`NOT register different functions under the same name.`);
|
|
406
|
+
}
|
|
407
|
+
continue; // dedupe — same reference already added
|
|
408
|
+
}
|
|
409
|
+
sharedSkillTools.set(name, tool);
|
|
410
|
+
// autoActivate skills: their tools come ONLY through
|
|
411
|
+
// dynamicSchemas (buildToolsSlot.ts pulls them from
|
|
412
|
+
// activeInjections.inject.tools when the skill is active).
|
|
413
|
+
// Don't pre-load them in the static registry.
|
|
414
|
+
if (isAutoActivate)
|
|
415
|
+
continue;
|
|
416
|
+
skillToolEntries.push({ name, tool });
|
|
260
417
|
}
|
|
261
418
|
}
|
|
262
|
-
|
|
419
|
+
// buildReadSkillTool returns undefined when skills is empty; the
|
|
420
|
+
// length check below short-circuits so the non-null assertion is safe.
|
|
421
|
+
const readSkillEntries = skills.length > 0 ? [{ name: 'read_skill', tool: (0, skillTools_js_1.buildReadSkillTool)(skills) }] : [];
|
|
263
422
|
const augmentedRegistry = [
|
|
264
423
|
...registry,
|
|
265
424
|
...readSkillEntries,
|
|
266
425
|
...skillToolEntries,
|
|
267
426
|
];
|
|
268
|
-
//
|
|
269
|
-
//
|
|
427
|
+
// Final cross-source name-uniqueness check: static .tool() vs
|
|
428
|
+
// read_skill vs (deduped) skill tools. After the dedupe above this
|
|
429
|
+
// catches collisions BETWEEN sources (e.g., a static .tool('foo')
|
|
430
|
+
// colliding with a Skill's foo) which are real bugs.
|
|
270
431
|
const seenNames = new Set();
|
|
271
432
|
for (const entry of augmentedRegistry) {
|
|
272
433
|
if (seenNames.has(entry.name)) {
|
|
273
434
|
throw new Error(`Agent: duplicate tool name '${entry.name}'. Tool names must be unique ` +
|
|
274
|
-
`across .tool() registrations and
|
|
275
|
-
`dispatches by name;
|
|
435
|
+
`across .tool() registrations and Skills' inject.tools (after deduping ` +
|
|
436
|
+
`same-reference shares across skills). The LLM dispatches by name; ` +
|
|
437
|
+
`collisions break tool routing.`);
|
|
276
438
|
}
|
|
277
439
|
seenNames.add(entry.name);
|
|
278
440
|
}
|
|
279
441
|
const registryByName = new Map(augmentedRegistry.map((e) => [e.name, e.tool]));
|
|
442
|
+
// Block C runtime — autoActivate skill tools live OUTSIDE the LLM-
|
|
443
|
+
// visible registry (so they don't pollute the per-iteration tool
|
|
444
|
+
// list before the skill activates), but they MUST still be findable
|
|
445
|
+
// by the dispatch handler — the LLM calls them by name once the
|
|
446
|
+
// skill is active, and dispatch looks up by name. Add them to the
|
|
447
|
+
// dispatch map so `lookupTool` resolves correctly. Using the Map
|
|
448
|
+
// backing the static registryByName means autoActivate tools share
|
|
449
|
+
// the same `.execute` wiring as normal tools — no special path.
|
|
450
|
+
for (const [name, tool] of sharedSkillTools.entries()) {
|
|
451
|
+
if (!registryByName.has(name)) {
|
|
452
|
+
registryByName.set(name, tool);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
280
455
|
const toolSchemas = augmentedRegistry.map((e) => e.tool.schema);
|
|
281
456
|
const injectionEngineSubflow = (0, buildInjectionEngineSubflow_js_1.buildInjectionEngineSubflow)({
|
|
282
457
|
injections: this.injections,
|
|
@@ -286,7 +461,10 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
286
461
|
reason: 'Agent.system()',
|
|
287
462
|
});
|
|
288
463
|
const messagesSubflow = (0, buildMessagesSlot_js_1.buildMessagesSlot)();
|
|
289
|
-
const toolsSubflow = (0, buildToolsSlot_js_1.buildToolsSlot)({
|
|
464
|
+
const toolsSubflow = (0, buildToolsSlot_js_1.buildToolsSlot)({
|
|
465
|
+
tools: toolSchemas,
|
|
466
|
+
...(this.externalToolProvider && { toolProvider: this.externalToolProvider }),
|
|
467
|
+
});
|
|
290
468
|
const iterationStart = (scope) => {
|
|
291
469
|
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.iteration_start', {
|
|
292
470
|
turnIndex: 0,
|
|
@@ -295,19 +473,22 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
295
473
|
};
|
|
296
474
|
const callLLM = async (scope) => {
|
|
297
475
|
const systemPromptInjections = scope.systemPromptInjections ?? [];
|
|
298
|
-
|
|
476
|
+
// `scope.messagesInjections` is read by ContextRecorder for
|
|
477
|
+
// observability; the LLM-wire path now reads scope.history
|
|
478
|
+
// directly (see below for rationale).
|
|
299
479
|
const iteration = scope.iteration;
|
|
300
480
|
const systemPrompt = systemPromptInjections
|
|
301
481
|
.map((r) => r.rawContent ?? '')
|
|
302
482
|
.filter((s) => s.length > 0)
|
|
303
483
|
.join('\n\n');
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
484
|
+
// Read the LLM message stream from `scope.history` directly.
|
|
485
|
+
// The `messagesInjections` projection is for observability
|
|
486
|
+
// (ContextRecorder, Lens) — it flattens InjectionRecords for
|
|
487
|
+
// event reporting and doesn't carry the full LLM-protocol
|
|
488
|
+
// shape (assistant `toolCalls[]`, etc.). For Anthropic's API
|
|
489
|
+
// contract we need the original LLMMessage with `toolCalls`
|
|
490
|
+
// intact so tool_use → tool_result correlation survives.
|
|
491
|
+
const messages = scope.history ?? [];
|
|
311
492
|
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.llm_start', {
|
|
312
493
|
iteration,
|
|
313
494
|
provider: provider.name,
|
|
@@ -426,8 +607,32 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
426
607
|
...(toolCalls.length > 0 && { toolCalls }),
|
|
427
608
|
});
|
|
428
609
|
}
|
|
610
|
+
// Resolve a tool by name, consulting the external ToolProvider
|
|
611
|
+
// if one was wired via `.toolProvider()` and the static
|
|
612
|
+
// registry doesn't carry the tool. The provider sees the same
|
|
613
|
+
// ctx the Tools slot used, so dispatch + visibility stay
|
|
614
|
+
// consistent within the iteration.
|
|
615
|
+
const externalToolProvider = this.externalToolProvider;
|
|
616
|
+
const lookupTool = (toolName) => {
|
|
617
|
+
const fromRegistry = registryByName.get(toolName);
|
|
618
|
+
if (fromRegistry)
|
|
619
|
+
return fromRegistry;
|
|
620
|
+
if (!externalToolProvider)
|
|
621
|
+
return undefined;
|
|
622
|
+
const activatedIds = scope.activatedInjectionIds ?? [];
|
|
623
|
+
const identity = scope.runIdentity;
|
|
624
|
+
const ctx = {
|
|
625
|
+
iteration: scope.iteration,
|
|
626
|
+
...(activatedIds.length > 0 && {
|
|
627
|
+
activeSkillId: activatedIds[activatedIds.length - 1],
|
|
628
|
+
}),
|
|
629
|
+
...(identity && { identity }),
|
|
630
|
+
};
|
|
631
|
+
const visible = externalToolProvider.list(ctx);
|
|
632
|
+
return visible.find((t) => t.schema.name === toolName);
|
|
633
|
+
};
|
|
429
634
|
for (const tc of toolCalls) {
|
|
430
|
-
const tool =
|
|
635
|
+
const tool = lookupTool(tc.name);
|
|
431
636
|
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.tool_start', {
|
|
432
637
|
toolName: tc.name,
|
|
433
638
|
toolCallId: tc.id,
|
|
@@ -691,6 +896,11 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
691
896
|
activeInjections: parent.activeInjections,
|
|
692
897
|
}),
|
|
693
898
|
outputMapper: (sf) => ({ systemPromptInjections: sf.systemPromptInjections }),
|
|
899
|
+
// See Tools-subflow comment below — same array-concat hazard.
|
|
900
|
+
// Without Replace, iter N+1's systemPromptInjections gets
|
|
901
|
+
// CONCATENATED with iter N's, multiplying the system prompt
|
|
902
|
+
// each iteration.
|
|
903
|
+
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
694
904
|
})
|
|
695
905
|
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.MESSAGES, messagesSubflow, 'Messages', {
|
|
696
906
|
inputMapper: (parent) => ({
|
|
@@ -699,11 +909,26 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
699
909
|
activeInjections: parent.activeInjections,
|
|
700
910
|
}),
|
|
701
911
|
outputMapper: (sf) => ({ messagesInjections: sf.messagesInjections }),
|
|
912
|
+
// Same array-concat hazard. messagesInjections is consumer-
|
|
913
|
+
// facing observability metadata (ContextRecorder, Lens) — must
|
|
914
|
+
// reflect THIS iteration's history, not be appended to last
|
|
915
|
+
// iteration's. CallLLM no longer reads this for the wire
|
|
916
|
+
// request (uses scope.history directly), so the LLM-protocol
|
|
917
|
+
// bug is fixed independently — but consumers of the
|
|
918
|
+
// messagesInjections stream still expect the per-iteration
|
|
919
|
+
// semantics.
|
|
920
|
+
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
702
921
|
})
|
|
703
922
|
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.TOOLS, toolsSubflow, 'Tools', {
|
|
704
923
|
inputMapper: (parent) => ({
|
|
705
924
|
iteration: parent.iteration,
|
|
706
925
|
activeInjections: parent.activeInjections,
|
|
926
|
+
// The slot subflow reads these to build the per-iteration
|
|
927
|
+
// ToolDispatchContext when an external `.toolProvider()` is
|
|
928
|
+
// configured. Without them the provider sees activeSkillId
|
|
929
|
+
// = undefined every iteration, breaking skillScopedTools etc.
|
|
930
|
+
activatedInjectionIds: parent.activatedInjectionIds,
|
|
931
|
+
runIdentity: parent.runIdentity,
|
|
707
932
|
}),
|
|
708
933
|
outputMapper: (sf) => ({
|
|
709
934
|
toolsInjections: sf.toolsInjections,
|
|
@@ -711,6 +936,16 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
711
936
|
// back up so callLLM uses the right list for THIS iteration.
|
|
712
937
|
dynamicToolSchemas: sf.toolSchemas,
|
|
713
938
|
}),
|
|
939
|
+
// CRITICAL: footprintjs's default `applyOutputMapping`
|
|
940
|
+
// CONCATENATES arrays from subflow output with the parent's
|
|
941
|
+
// existing array values. Without `Replace`, the parent's
|
|
942
|
+
// `dynamicToolSchemas` (carrying the iter N value) gets
|
|
943
|
+
// concatenated with the slot's iter N+1 deduped list,
|
|
944
|
+
// re-introducing duplicate tool names that Anthropic's API
|
|
945
|
+
// rejects with "tools: Tool names must be unique." The slot's
|
|
946
|
+
// toolSchemas IS the authoritative list — replace, don't
|
|
947
|
+
// concatenate.
|
|
948
|
+
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
714
949
|
})
|
|
715
950
|
.addFunction('IterationStart', iterationStart, 'iteration-start', 'Iteration begin marker')
|
|
716
951
|
.addFunction('CallLLM', callLLM, conventions_js_1.STAGE_IDS.CALL_LLM, 'LLM invocation')
|
|
@@ -736,7 +971,18 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
736
971
|
})
|
|
737
972
|
.setDefault('final')
|
|
738
973
|
.end()
|
|
739
|
-
|
|
974
|
+
// Dynamic ReAct: loop back to the InjectionEngine so EVERY iteration
|
|
975
|
+
// re-evaluates triggers (rule predicates, on-tool-return, llm-activated)
|
|
976
|
+
// against the freshest context (the just-appended tool result).
|
|
977
|
+
// Without this, the InjectionEngine runs ONCE per turn and:
|
|
978
|
+
// - on-tool-return predicates never fire on iter 2+
|
|
979
|
+
// - read_skill('X') activations are never picked up next iteration
|
|
980
|
+
// - autoActivate per-skill tool gating is structurally impossible
|
|
981
|
+
// - tools / system-prompt slots stay frozen at iter 1 content
|
|
982
|
+
// The v2.4 default of loopTo(MESSAGES) bypassed all four — quietly
|
|
983
|
+
// breaking the framework's "Dynamic ReAct" claim. v2.5 restores the
|
|
984
|
+
// v1 behavior that documents promise.
|
|
985
|
+
.loopTo(conventions_js_1.SUBFLOW_IDS.INJECTION_ENGINE);
|
|
740
986
|
return builder.build();
|
|
741
987
|
}
|
|
742
988
|
}
|
|
@@ -751,6 +997,29 @@ class AgentBuilder {
|
|
|
751
997
|
registry = [];
|
|
752
998
|
injectionList = [];
|
|
753
999
|
memoryList = [];
|
|
1000
|
+
/**
|
|
1001
|
+
* Optional terminal contract — see `outputSchema()`. Stored on the
|
|
1002
|
+
* builder, propagated to the Agent at `.build()` time.
|
|
1003
|
+
*/
|
|
1004
|
+
outputSchemaParser;
|
|
1005
|
+
/**
|
|
1006
|
+
* Optional `ToolProvider` set via `.toolProvider()`. Propagated to
|
|
1007
|
+
* the Agent's Tools slot subflow + tool-call dispatcher; consulted
|
|
1008
|
+
* per iteration so dynamic chains (`gatedTools`, `skillScopedTools`)
|
|
1009
|
+
* react to current activation state.
|
|
1010
|
+
*/
|
|
1011
|
+
toolProviderRef;
|
|
1012
|
+
/**
|
|
1013
|
+
* Optional override for `AgentOptions.maxIterations`. When set via
|
|
1014
|
+
* the `.maxIterations()` builder method, takes precedence over the
|
|
1015
|
+
* value passed to `Agent.create({ maxIterations })`.
|
|
1016
|
+
*/
|
|
1017
|
+
maxIterationsOverride;
|
|
1018
|
+
/**
|
|
1019
|
+
* Recorders collected via `.recorder()`. Attached to the built Agent
|
|
1020
|
+
* before `build()` returns (each via `agent.attach(rec)`).
|
|
1021
|
+
*/
|
|
1022
|
+
recorderList = [];
|
|
754
1023
|
// Voice config — defaults until the consumer calls .appName() /
|
|
755
1024
|
// .commentaryTemplates() / .thinkingTemplates(). Stored as plain
|
|
756
1025
|
// dicts (Record<string, string>) so the builder doesn't depend on
|
|
@@ -785,6 +1054,76 @@ class AgentBuilder {
|
|
|
785
1054
|
this.tool(t);
|
|
786
1055
|
return this;
|
|
787
1056
|
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Wire a chainable `ToolProvider` (from `agentfootprint/tool-providers`)
|
|
1059
|
+
* as the agent's per-iteration tool source.
|
|
1060
|
+
*
|
|
1061
|
+
* The provider is consulted EVERY iteration via `provider.list(ctx)`
|
|
1062
|
+
* with `ctx = { iteration, activeSkillId, identity }`. Tools the
|
|
1063
|
+
* provider emits flow into the Tools slot alongside any static
|
|
1064
|
+
* tools registered via `.tool()` / `.tools()`. The tool-call
|
|
1065
|
+
* dispatcher also consults the provider so dynamic chains
|
|
1066
|
+
* (`gatedTools`, `skillScopedTools`) dispatch correctly when their
|
|
1067
|
+
* visible-set changes mid-turn.
|
|
1068
|
+
*
|
|
1069
|
+
* Throws if called more than once on the same builder (avoids
|
|
1070
|
+
* silent override surprises).
|
|
1071
|
+
*
|
|
1072
|
+
* @example Permission-gated baseline
|
|
1073
|
+
* import { gatedTools, staticTools } from 'agentfootprint/tool-providers';
|
|
1074
|
+
* import { PermissionPolicy } from 'agentfootprint/security';
|
|
1075
|
+
*
|
|
1076
|
+
* const policy = PermissionPolicy.fromRoles({
|
|
1077
|
+
* readonly: ['lookup', 'list_skills', 'read_skill'],
|
|
1078
|
+
* admin: ['lookup', 'list_skills', 'read_skill', 'delete'],
|
|
1079
|
+
* }, 'readonly');
|
|
1080
|
+
*
|
|
1081
|
+
* const provider = gatedTools(
|
|
1082
|
+
* staticTools(allTools),
|
|
1083
|
+
* (toolName) => policy.isAllowed(toolName),
|
|
1084
|
+
* );
|
|
1085
|
+
*
|
|
1086
|
+
* const agent = Agent.create({ provider: llm, model })
|
|
1087
|
+
* .system('You answer.')
|
|
1088
|
+
* .toolProvider(provider)
|
|
1089
|
+
* .build();
|
|
1090
|
+
*/
|
|
1091
|
+
toolProvider(provider) {
|
|
1092
|
+
if (this.toolProviderRef) {
|
|
1093
|
+
throw new Error('AgentBuilder.toolProvider: already set. Each agent has at most one external ToolProvider.');
|
|
1094
|
+
}
|
|
1095
|
+
this.toolProviderRef = provider;
|
|
1096
|
+
return this;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Override the ReAct iteration cap set via `Agent.create({
|
|
1100
|
+
* maxIterations })`. Convenience for builder-style code that prefers
|
|
1101
|
+
* fluent setters over constructor opts. Last call wins.
|
|
1102
|
+
*
|
|
1103
|
+
* Throws if `n` is not a positive integer or exceeds the hard cap
|
|
1104
|
+
* (`clampIterations`'s upper bound).
|
|
1105
|
+
*/
|
|
1106
|
+
maxIterations(n) {
|
|
1107
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
1108
|
+
throw new Error(`AgentBuilder.maxIterations: expected a positive integer, got ${n}.`);
|
|
1109
|
+
}
|
|
1110
|
+
this.maxIterationsOverride = n;
|
|
1111
|
+
return this;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Attach a footprintjs `CombinedRecorder` to the built Agent. Wired
|
|
1115
|
+
* via `agent.attach(rec)` immediately after construction, so the
|
|
1116
|
+
* recorder sees every event from the very first run.
|
|
1117
|
+
*
|
|
1118
|
+
* Equivalent to calling `agent.attach(rec)` post-build; the builder
|
|
1119
|
+
* method is a convenience for codebases that prefer fully-fluent
|
|
1120
|
+
* agent assembly. Multiple recorders are supported (each gets its
|
|
1121
|
+
* own `attach()` call).
|
|
1122
|
+
*/
|
|
1123
|
+
recorder(rec) {
|
|
1124
|
+
this.recorderList.push(rec);
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
788
1127
|
/**
|
|
789
1128
|
* Set the agent's display name — substituted as `{{appName}}` in
|
|
790
1129
|
* commentary + thinking templates. Same place to brand a tenant
|
|
@@ -876,6 +1215,18 @@ class AgentBuilder {
|
|
|
876
1215
|
instruction(injection) {
|
|
877
1216
|
return this.injection(injection);
|
|
878
1217
|
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Bulk-register many instructions at once. Convenience for consumer
|
|
1220
|
+
* code that organizes its instruction set in a flat array (`const
|
|
1221
|
+
* instructions = [outputFormat, dataRouting, ...]`). Each element
|
|
1222
|
+
* is registered via `.instruction()` so duplicate-id checks still
|
|
1223
|
+
* fire per-entry.
|
|
1224
|
+
*/
|
|
1225
|
+
instructions(injections) {
|
|
1226
|
+
for (const i of injections)
|
|
1227
|
+
this.instruction(i);
|
|
1228
|
+
return this;
|
|
1229
|
+
}
|
|
879
1230
|
/**
|
|
880
1231
|
* Register a Fact — developer-supplied data the LLM should see.
|
|
881
1232
|
* User profile, env info, computed summary, current time, …
|
|
@@ -936,6 +1287,53 @@ class AgentBuilder {
|
|
|
936
1287
|
rag(definition) {
|
|
937
1288
|
return this.memory(definition);
|
|
938
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Declarative terminal contract. The agent's final answer must be
|
|
1292
|
+
* JSON matching `parser`. Auto-injects a system-prompt instruction
|
|
1293
|
+
* telling the LLM the shape, and exposes `agent.runTyped()` /
|
|
1294
|
+
* `agent.parseOutput()` for parse + validate at the call site.
|
|
1295
|
+
*
|
|
1296
|
+
* The `parser` is duck-typed: any object with a `parse(unknown): T`
|
|
1297
|
+
* method works (Zod, Valibot, ArkType, hand-written). The optional
|
|
1298
|
+
* `description` field on the parser drives the auto-generated
|
|
1299
|
+
* instruction; consumers can also override via `opts.instruction`.
|
|
1300
|
+
*
|
|
1301
|
+
* Throws if called more than once on the same builder (avoids
|
|
1302
|
+
* silent override surprises).
|
|
1303
|
+
*
|
|
1304
|
+
* @param parser Validation strategy that throws on shape failure.
|
|
1305
|
+
* @param opts Optional `{ name, instruction }` to customize.
|
|
1306
|
+
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* import { z } from 'zod';
|
|
1309
|
+
* const Output = z.object({
|
|
1310
|
+
* status: z.enum(['ok', 'err']),
|
|
1311
|
+
* items: z.array(z.string()),
|
|
1312
|
+
* }).describe('A status enum + an array of strings.');
|
|
1313
|
+
*
|
|
1314
|
+
* const agent = Agent.create({...})
|
|
1315
|
+
* .outputSchema(Output)
|
|
1316
|
+
* .build();
|
|
1317
|
+
*
|
|
1318
|
+
* const typed = await agent.runTyped({ message: '...' });
|
|
1319
|
+
* typed.status; // narrowed to 'ok' | 'err'
|
|
1320
|
+
*/
|
|
1321
|
+
outputSchema(parser, opts) {
|
|
1322
|
+
if (this.outputSchemaParser) {
|
|
1323
|
+
throw new Error('AgentBuilder.outputSchema: already set. Each agent has at most one terminal contract.');
|
|
1324
|
+
}
|
|
1325
|
+
this.outputSchemaParser = parser;
|
|
1326
|
+
const instructionText = opts?.instruction ?? (0, outputSchema_js_1.buildDefaultInstruction)(parser);
|
|
1327
|
+
const id = opts?.name ?? 'output-schema';
|
|
1328
|
+
// Always-on system-slot instruction. Activates every iteration so
|
|
1329
|
+
// long runs keep the contract present (recency-first redundancy).
|
|
1330
|
+
this.injectionList.push((0, defineInstruction_js_1.defineInstruction)({
|
|
1331
|
+
id,
|
|
1332
|
+
activeWhen: () => true,
|
|
1333
|
+
prompt: instructionText,
|
|
1334
|
+
}));
|
|
1335
|
+
return this;
|
|
1336
|
+
}
|
|
939
1337
|
build() {
|
|
940
1338
|
// Resolve the voice config: bundled defaults + consumer overrides.
|
|
941
1339
|
// Templates flow through the same barrel exports the rest of the
|
|
@@ -945,7 +1343,17 @@ class AgentBuilder {
|
|
|
945
1343
|
commentaryTemplates: { ...commentaryTemplates_js_1.defaultCommentaryTemplates, ...this.commentaryOverrides },
|
|
946
1344
|
thinkingTemplates: { ...thinkingTemplates_js_1.defaultThinkingTemplates, ...this.thinkingOverrides },
|
|
947
1345
|
};
|
|
948
|
-
|
|
1346
|
+
const opts = this.maxIterationsOverride !== undefined
|
|
1347
|
+
? { ...this.opts, maxIterations: this.maxIterationsOverride }
|
|
1348
|
+
: this.opts;
|
|
1349
|
+
const agent = new Agent(opts, this.systemPromptValue, this.registry, voice, this.injectionList, this.memoryList, this.outputSchemaParser, this.toolProviderRef);
|
|
1350
|
+
// Attach builder-collected recorders so they receive events from
|
|
1351
|
+
// the very first run. Mirrors what consumers would do post-build
|
|
1352
|
+
// via `agent.attach(rec)`; the builder method is purely sugar.
|
|
1353
|
+
for (const rec of this.recorderList) {
|
|
1354
|
+
agent.attach(rec);
|
|
1355
|
+
}
|
|
1356
|
+
return agent;
|
|
949
1357
|
}
|
|
950
1358
|
}
|
|
951
1359
|
exports.AgentBuilder = AgentBuilder;
|
|
@@ -978,72 +1386,62 @@ function clampIterations(n) {
|
|
|
978
1386
|
* with consumer tools throw.
|
|
979
1387
|
*/
|
|
980
1388
|
function validateToolNameUniqueness(registry, injections) {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1389
|
+
// Static registry: unique within itself. The Agent.tool() builder
|
|
1390
|
+
// method already throws on per-call duplicates; this is the
|
|
1391
|
+
// belt-and-suspenders check at build time.
|
|
1392
|
+
const staticNames = new Set();
|
|
1393
|
+
for (const entry of registry) {
|
|
1394
|
+
if (staticNames.has(entry.name)) {
|
|
1395
|
+
throw new Error(`Agent: duplicate tool name '${entry.name}' in .tool() registry. ` +
|
|
1396
|
+
`Tool names must be unique within the static registry.`);
|
|
987
1397
|
}
|
|
988
|
-
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
|
|
1398
|
+
staticNames.add(entry.name);
|
|
1399
|
+
}
|
|
1400
|
+
// `read_skill` is reserved when any Skill is registered. Collisions
|
|
1401
|
+
// with consumer-supplied tools break the auto-attach path.
|
|
992
1402
|
const skills = injections.filter((i) => i.flavor === 'skill');
|
|
993
|
-
if (skills.length > 0)
|
|
994
|
-
|
|
1403
|
+
if (skills.length > 0 && staticNames.has('read_skill')) {
|
|
1404
|
+
throw new Error(`Agent: tool name 'read_skill' is reserved when ≥1 Skill is registered. ` +
|
|
1405
|
+
`Rename your custom 'read_skill' tool or unregister it.`);
|
|
1406
|
+
}
|
|
1407
|
+
// Per-skill check: a skill's `inject.tools` array must be internally
|
|
1408
|
+
// unique (no duplicate names within the same skill — that's a
|
|
1409
|
+
// skill authoring bug). Across skills, sharing a Tool reference is
|
|
1410
|
+
// EXPECTED and supported — common tools (e.g., a `flogi_lookup`
|
|
1411
|
+
// used by multiple investigation skills) appear in multiple skills'
|
|
1412
|
+
// tool arrays. Only one skill is active at a time (or, when several
|
|
1413
|
+
// are active, deduped by name + reference at runtime). Sharing the
|
|
1414
|
+
// same Tool object across skills is the supported pattern; sharing
|
|
1415
|
+
// a Tool NAME with a DIFFERENT execute function is the actual bug —
|
|
1416
|
+
// we detect that here too.
|
|
1417
|
+
const seenByName = new Map();
|
|
995
1418
|
for (const skill of skills) {
|
|
1419
|
+
const intraSkill = new Set();
|
|
996
1420
|
for (const tool of skill.inject.tools ?? []) {
|
|
997
|
-
|
|
1421
|
+
const name = tool.schema.name;
|
|
1422
|
+
if (intraSkill.has(name)) {
|
|
1423
|
+
throw new Error(`Agent: skill '${skill.id}' lists tool '${name}' more than once in its ` +
|
|
1424
|
+
`inject.tools array. Each skill's tools must be unique within itself.`);
|
|
1425
|
+
}
|
|
1426
|
+
intraSkill.add(name);
|
|
1427
|
+
// Skill tools collide with the static .tool() registry → ambiguous dispatch
|
|
1428
|
+
if (staticNames.has(name)) {
|
|
1429
|
+
throw new Error(`Agent: skill '${skill.id}' tool '${name}' collides with the static .tool() ` +
|
|
1430
|
+
`registry. Either rename the skill's tool or remove the static registration.`);
|
|
1431
|
+
}
|
|
1432
|
+
// Same name across skills with DIFFERENT Tool objects = ambiguous when
|
|
1433
|
+
// both skills active. Same name + SAME Tool reference = supported sharing.
|
|
1434
|
+
const prior = seenByName.get(name);
|
|
1435
|
+
if (prior && prior !== tool) {
|
|
1436
|
+
throw new Error(`Agent: tool name '${name}' is declared by multiple skills with different ` +
|
|
1437
|
+
`Tool implementations. Skills MAY share the SAME Tool reference across ` +
|
|
1438
|
+
`their inject.tools arrays (deduped at dispatch); they may NOT register ` +
|
|
1439
|
+
`different functions under the same name (ambiguous dispatch).`);
|
|
1440
|
+
}
|
|
1441
|
+
seenByName.set(name, tool);
|
|
998
1442
|
}
|
|
999
1443
|
}
|
|
1000
1444
|
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Build the auto-attached `read_skill` tool from a list of Skill
|
|
1003
|
-
* Injections. The LLM picks WHICH skill via the `id` argument.
|
|
1004
|
-
*
|
|
1005
|
-
* Tool execute() does the bookkeeping: appends the requested skill id
|
|
1006
|
-
* to `scope.activatedInjectionIds`. The next iteration's
|
|
1007
|
-
* InjectionEngine matches Skills with `trigger.kind: 'llm-activated'`
|
|
1008
|
-
* by id and includes them in the active set; slot subflows then
|
|
1009
|
-
* inject the body + tools.
|
|
1010
|
-
*
|
|
1011
|
-
* The tool's description lists each Skill's `id` + `description` so
|
|
1012
|
-
* the LLM can choose meaningfully.
|
|
1013
|
-
*/
|
|
1014
|
-
function buildReadSkillTool(skills) {
|
|
1015
|
-
const skillIds = skills.map((s) => s.id);
|
|
1016
|
-
const skillCatalog = skills
|
|
1017
|
-
.map((s) => ` - ${s.id}: ${s.description ?? '(no description)'}`)
|
|
1018
|
-
.join('\n');
|
|
1019
|
-
return (0, tools_js_1.defineTool)({
|
|
1020
|
-
name: 'read_skill',
|
|
1021
|
-
description: `Activate a skill for the next iteration. Available skills:\n${skillCatalog}\n\n` +
|
|
1022
|
-
`Pass the skill's id. The skill's body becomes part of the system prompt and any ` +
|
|
1023
|
-
`gated tools become available on the next call.`,
|
|
1024
|
-
inputSchema: {
|
|
1025
|
-
type: 'object',
|
|
1026
|
-
properties: {
|
|
1027
|
-
id: {
|
|
1028
|
-
type: 'string',
|
|
1029
|
-
enum: skillIds,
|
|
1030
|
-
description: 'The skill id to activate.',
|
|
1031
|
-
},
|
|
1032
|
-
},
|
|
1033
|
-
required: ['id'],
|
|
1034
|
-
},
|
|
1035
|
-
execute: ({ id }) => {
|
|
1036
|
-
// Bookkeeping is handled by the Agent's tool-calls subflow,
|
|
1037
|
-
// which inspects `read_skill` returns and updates
|
|
1038
|
-
// `scope.activatedInjectionIds` before the next iteration.
|
|
1039
|
-
// The tool itself returns a confirmation string for the LLM.
|
|
1040
|
-
if (!skillIds.includes(id)) {
|
|
1041
|
-
return `Unknown skill '${id}'. Available: ${skillIds.join(', ')}`;
|
|
1042
|
-
}
|
|
1043
|
-
return `Skill '${id}' activated for the next iteration.`;
|
|
1044
|
-
},
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
1445
|
/**
|
|
1048
1446
|
* JSON.stringify with circular-ref protection. Tool results are untrusted —
|
|
1049
1447
|
* a hostile/buggy tool returning a cyclic object must not crash the run.
|