agentfootprint 2.11.1 → 2.11.3
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/core/Agent.js +81 -1238
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +489 -0
- package/dist/core/agent/AgentBuilder.js.map +1 -0
- package/dist/core/agent/buildAgentChart.js +227 -0
- package/dist/core/agent/buildAgentChart.js.map +1 -0
- package/dist/core/agent/buildToolRegistry.js +115 -0
- package/dist/core/agent/buildToolRegistry.js.map +1 -0
- package/dist/core/agent/stages/breakFinal.js +28 -0
- package/dist/core/agent/stages/breakFinal.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +129 -0
- package/dist/core/agent/stages/callLLM.js.map +1 -0
- package/dist/core/agent/stages/iterationStart.js +24 -0
- package/dist/core/agent/stages/iterationStart.js.map +1 -0
- package/dist/core/agent/stages/prepareFinal.js +45 -0
- package/dist/core/agent/stages/prepareFinal.js.map +1 -0
- package/dist/core/agent/stages/route.js +36 -0
- package/dist/core/agent/stages/route.js.map +1 -0
- package/dist/core/agent/stages/seed.js +95 -0
- package/dist/core/agent/stages/seed.js.map +1 -0
- package/dist/core/agent/stages/toolCalls.js +250 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -0
- package/dist/esm/core/Agent.js +83 -1239
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.js +485 -0
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -0
- package/dist/esm/core/agent/buildAgentChart.js +223 -0
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -0
- package/dist/esm/core/agent/buildToolRegistry.js +111 -0
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -0
- package/dist/esm/core/agent/stages/breakFinal.js +24 -0
- package/dist/esm/core/agent/stages/breakFinal.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.js +125 -0
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -0
- package/dist/esm/core/agent/stages/iterationStart.js +20 -0
- package/dist/esm/core/agent/stages/iterationStart.js.map +1 -0
- package/dist/esm/core/agent/stages/prepareFinal.js +41 -0
- package/dist/esm/core/agent/stages/prepareFinal.js.map +1 -0
- package/dist/esm/core/agent/stages/route.js +32 -0
- package/dist/esm/core/agent/stages/route.js.map +1 -0
- package/dist/esm/core/agent/stages/seed.js +91 -0
- package/dist/esm/core/agent/stages/seed.js.map +1 -0
- package/dist/esm/core/agent/stages/toolCalls.js +246 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -0
- package/dist/esm/events/dispatcher.js +5 -2
- package/dist/esm/events/dispatcher.js.map +1 -1
- package/dist/esm/memory/define.types.js.map +1 -1
- package/dist/esm/reliability/buildReliabilityGateChart.js +6 -1
- package/dist/esm/reliability/buildReliabilityGateChart.js.map +1 -1
- package/dist/esm/strategies/attach.js +4 -2
- package/dist/esm/strategies/attach.js.map +1 -1
- package/dist/esm/strategies/compose.js +4 -0
- package/dist/esm/strategies/compose.js.map +1 -1
- package/dist/events/dispatcher.js +5 -2
- package/dist/events/dispatcher.js.map +1 -1
- package/dist/memory/define.types.js.map +1 -1
- package/dist/reliability/buildReliabilityGateChart.js +6 -1
- package/dist/reliability/buildReliabilityGateChart.js.map +1 -1
- package/dist/strategies/attach.js +4 -2
- package/dist/strategies/attach.js.map +1 -1
- package/dist/strategies/compose.js +4 -0
- package/dist/strategies/compose.js.map +1 -1
- package/dist/types/core/Agent.d.ts +5 -333
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +348 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -0
- package/dist/types/core/agent/buildAgentChart.d.ts +74 -0
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts +62 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -0
- package/dist/types/core/agent/stages/breakFinal.d.ts +23 -0
- package/dist/types/core/agent/stages/breakFinal.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts +54 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -0
- package/dist/types/core/agent/stages/iterationStart.d.ts +16 -0
- package/dist/types/core/agent/stages/iterationStart.d.ts.map +1 -0
- package/dist/types/core/agent/stages/prepareFinal.d.ts +20 -0
- package/dist/types/core/agent/stages/prepareFinal.d.ts.map +1 -0
- package/dist/types/core/agent/stages/route.d.ts +19 -0
- package/dist/types/core/agent/stages/route.d.ts.map +1 -0
- package/dist/types/core/agent/stages/seed.d.ts +54 -0
- package/dist/types/core/agent/stages/seed.d.ts.map +1 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts +50 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -0
- package/dist/types/events/dispatcher.d.ts.map +1 -1
- package/dist/types/memory/define.types.d.ts +3 -1
- package/dist/types/memory/define.types.d.ts.map +1 -1
- package/dist/types/reliability/buildReliabilityGateChart.d.ts.map +1 -1
- package/dist/types/strategies/attach.d.ts.map +1 -1
- package/dist/types/strategies/compose.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/Agent.js
CHANGED
|
@@ -15,23 +15,11 @@
|
|
|
15
15
|
* agentfootprint.context.* (via ContextRecorder)
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.
|
|
18
|
+
exports.Agent = exports.AgentBuilder = 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");
|
|
27
20
|
const CacheDecisionSubflow_js_1 = require("../cache/CacheDecisionSubflow.js");
|
|
28
21
|
const CacheGateDecider_js_1 = require("../cache/CacheGateDecider.js");
|
|
29
22
|
const strategyRegistry_js_1 = require("../cache/strategyRegistry.js");
|
|
30
|
-
const pause_js_1 = require("./pause.js");
|
|
31
|
-
const cost_js_1 = require("./cost.js");
|
|
32
|
-
const conventions_js_1 = require("../conventions.js");
|
|
33
|
-
const commentaryTemplates_js_1 = require("../recorders/observability/commentary/commentaryTemplates.js");
|
|
34
|
-
const thinkingTemplates_js_1 = require("../recorders/observability/thinking/thinkingTemplates.js");
|
|
35
23
|
const ContextRecorder_js_1 = require("../recorders/core/ContextRecorder.js");
|
|
36
24
|
const StreamRecorder_js_1 = require("../recorders/core/StreamRecorder.js");
|
|
37
25
|
const AgentRecorder_js_1 = require("../recorders/core/AgentRecorder.js");
|
|
@@ -40,21 +28,24 @@ const PermissionRecorder_js_1 = require("../recorders/core/PermissionRecorder.js
|
|
|
40
28
|
const EvalRecorder_js_1 = require("../recorders/core/EvalRecorder.js");
|
|
41
29
|
const MemoryRecorder_js_1 = require("../recorders/core/MemoryRecorder.js");
|
|
42
30
|
const SkillRecorder_js_1 = require("../recorders/core/SkillRecorder.js");
|
|
43
|
-
const typedEmit_js_1 = require("../recorders/core/typedEmit.js");
|
|
44
|
-
const define_types_js_1 = require("../memory/define.types.js");
|
|
45
|
-
const define_js_1 = require("../memory/define.js");
|
|
46
|
-
const mountMemoryPipeline_js_1 = require("../memory/wire/mountMemoryPipeline.js");
|
|
47
31
|
const buildSystemPromptSlot_js_1 = require("./slots/buildSystemPromptSlot.js");
|
|
48
32
|
const buildMessagesSlot_js_1 = require("./slots/buildMessagesSlot.js");
|
|
49
33
|
const buildToolsSlot_js_1 = require("./slots/buildToolsSlot.js");
|
|
50
34
|
const buildInjectionEngineSubflow_js_1 = require("../lib/injection-engine/buildInjectionEngineSubflow.js");
|
|
51
|
-
const skillTools_js_1 = require("../lib/injection-engine/skillTools.js");
|
|
52
|
-
const defineInstruction_js_1 = require("../lib/injection-engine/factories/defineInstruction.js");
|
|
53
35
|
const outputFallback_js_1 = require("./outputFallback.js");
|
|
54
36
|
const runCheckpoint_js_1 = require("./runCheckpoint.js");
|
|
55
37
|
const outputSchema_js_1 = require("./outputSchema.js");
|
|
56
38
|
const RunnerBase_js_1 = require("./RunnerBase.js");
|
|
57
39
|
const validators_js_1 = require("./agent/validators.js");
|
|
40
|
+
const iterationStart_js_1 = require("./agent/stages/iterationStart.js");
|
|
41
|
+
const route_js_1 = require("./agent/stages/route.js");
|
|
42
|
+
const seed_js_1 = require("./agent/stages/seed.js");
|
|
43
|
+
const callLLM_js_1 = require("./agent/stages/callLLM.js");
|
|
44
|
+
const toolCalls_js_1 = require("./agent/stages/toolCalls.js");
|
|
45
|
+
const buildAgentChart_js_1 = require("./agent/buildAgentChart.js");
|
|
46
|
+
const buildToolRegistry_js_1 = require("./agent/buildToolRegistry.js");
|
|
47
|
+
const AgentBuilder_js_1 = require("./agent/AgentBuilder.js");
|
|
48
|
+
Object.defineProperty(exports, "AgentBuilder", { enumerable: true, get: function () { return AgentBuilder_js_1.AgentBuilder; } });
|
|
58
49
|
// Public types (AgentOptions, AgentInput, AgentOutput) extracted to
|
|
59
50
|
// ./agent/types.ts and re-exported above (v2.11.1).
|
|
60
51
|
// AgentState extracted to ./agent/types.ts (v2.11.1).
|
|
@@ -203,7 +194,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
203
194
|
this.thinkingTemplates = voice.thinkingTemplates;
|
|
204
195
|
}
|
|
205
196
|
static create(opts) {
|
|
206
|
-
return new AgentBuilder(opts);
|
|
197
|
+
return new AgentBuilder_js_1.AgentBuilder(opts);
|
|
207
198
|
}
|
|
208
199
|
toFlowChart() {
|
|
209
200
|
return this.buildChart();
|
|
@@ -512,168 +503,33 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
512
503
|
const systemPromptCachePolicy = this.systemPromptCachePolicy;
|
|
513
504
|
const cachingDisabled = this.cachingDisabledValue;
|
|
514
505
|
const cacheStrategy = this.cacheStrategy;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
506
|
+
// seed extracted to ./agent/stages/seed.ts (v2.11.2). Factory takes
|
|
507
|
+
// chart-build-time constants + per-run mutable accessors so the
|
|
508
|
+
// resume side-channel and current run id remain dynamic.
|
|
509
|
+
// toolSchemas is finalized further down; pass a getter that reads
|
|
510
|
+
// the eventual const at stage-execution time.
|
|
511
|
+
let toolSchemasResolved = [];
|
|
512
|
+
const seed = (0, seed_js_1.buildSeedStage)({
|
|
513
|
+
maxIterations,
|
|
514
|
+
cachingDisabled,
|
|
515
|
+
get toolSchemas() {
|
|
516
|
+
return toolSchemasResolved;
|
|
517
|
+
},
|
|
518
|
+
consumePendingResumeHistory: () => {
|
|
519
|
+
const h = this.pendingResumeHistory;
|
|
525
520
|
this.pendingResumeHistory = undefined;
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// Permissive default — explicit cap will land when PricingTable
|
|
539
|
-
// gets a context-window field. Memory pickByBudget treats anything
|
|
540
|
-
// ≥ minimumTokens as "fits", so this just enables the budget path.
|
|
541
|
-
scope.contextTokensRemaining = 32_000;
|
|
542
|
-
scope.iteration = 1;
|
|
543
|
-
scope.maxIterations = maxIterations;
|
|
544
|
-
scope.finalContent = '';
|
|
545
|
-
scope.totalInputTokens = 0;
|
|
546
|
-
scope.totalOutputTokens = 0;
|
|
547
|
-
scope.turnStartMs = Date.now();
|
|
548
|
-
scope.systemPromptInjections = [];
|
|
549
|
-
scope.messagesInjections = [];
|
|
550
|
-
scope.toolsInjections = [];
|
|
551
|
-
scope.llmLatestContent = '';
|
|
552
|
-
scope.llmLatestToolCalls = [];
|
|
553
|
-
scope.pausedToolCallId = '';
|
|
554
|
-
scope.pausedToolName = '';
|
|
555
|
-
scope.pausedToolStartMs = 0;
|
|
556
|
-
scope.cumTokensInput = 0;
|
|
557
|
-
scope.cumTokensOutput = 0;
|
|
558
|
-
scope.cumEstimatedUsd = 0;
|
|
559
|
-
scope.costBudgetHit = false;
|
|
560
|
-
scope.activeInjections = [];
|
|
561
|
-
scope.activatedInjectionIds = [];
|
|
562
|
-
scope.dynamicToolSchemas = toolSchemas;
|
|
563
|
-
// Cache layer state (v2.6) — initialized to inert defaults.
|
|
564
|
-
// CacheDecision subflow populates `cacheMarkers` per iteration;
|
|
565
|
-
// UpdateSkillHistory + CacheGate consume `cachingDisabled`,
|
|
566
|
-
// `recentHitRate`, `skillHistory`. Empty defaults mean the
|
|
567
|
-
// CacheGate falls through to 'apply-markers' on iter 1 (no
|
|
568
|
-
// history yet → no churn detected; recentHitRate undefined →
|
|
569
|
-
// hit-rate floor doesn't fire).
|
|
570
|
-
scope.cacheMarkers = [];
|
|
571
|
-
scope.cachingDisabled = cachingDisabled;
|
|
572
|
-
scope.recentHitRate = undefined;
|
|
573
|
-
scope.skillHistory = [];
|
|
574
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.turn_start', {
|
|
575
|
-
turnIndex: 0,
|
|
576
|
-
userPrompt: args.message,
|
|
577
|
-
});
|
|
578
|
-
};
|
|
579
|
-
// Tool registry composition — three sources:
|
|
580
|
-
//
|
|
581
|
-
// 1. Static registry: tools registered via `.tool()`. Always
|
|
582
|
-
// visible to the LLM; always executable.
|
|
583
|
-
// 2. `read_skill` (auto-attached when ≥1 Skill is registered):
|
|
584
|
-
// activation tool for LLM-guided Skills.
|
|
585
|
-
// 3. Skill-supplied tools (`Skill.inject.tools[]`): visible only
|
|
586
|
-
// when the Skill is active (filtered by tools slot subflow);
|
|
587
|
-
// MUST always be in the executor registry so when the LLM
|
|
588
|
-
// calls one, the tool-calls handler can dispatch.
|
|
589
|
-
//
|
|
590
|
-
// Tool-name uniqueness is enforced across all three sources at
|
|
591
|
-
// build time. The LLM only sees `tool.schema.name` (no ids), so
|
|
592
|
-
// names ARE the runtime dispatch key — collisions break the LLM's
|
|
593
|
-
// ability to call the right tool. Throw early instead of subtly
|
|
594
|
-
// shadowing.
|
|
595
|
-
const skills = this.injections.filter((i) => i.flavor === 'skill');
|
|
596
|
-
// Collect skill tools, deduping by name when the SAME Tool reference
|
|
597
|
-
// is shared across skills. Different Tool implementations under the
|
|
598
|
-
// same name throws (already validated upstream by
|
|
599
|
-
// validateToolNameUniqueness) — we keep the runtime check as
|
|
600
|
-
// belt-and-suspenders.
|
|
601
|
-
//
|
|
602
|
-
// Block C runtime — `autoActivate: 'currentSkill'` semantics:
|
|
603
|
-
// When a skill's `defineSkill({ autoActivate: 'currentSkill' })`
|
|
604
|
-
// is set, its tools are EXCLUDED from the static registry. They
|
|
605
|
-
// flow into the LLM's tool list ONLY through `dynamicSchemas`
|
|
606
|
-
// (the buildToolsSlot path that reads activeInjections), which
|
|
607
|
-
// means they're visible ONLY on iterations after the skill is
|
|
608
|
-
// activated by `read_skill('id')`. Without this, the LLM sees
|
|
609
|
-
// every skill's tools on every iteration and the
|
|
610
|
-
// per-skill-narrowing autoActivate promised in `defineSkill`
|
|
611
|
-
// doesn't actually narrow anything. Skills WITHOUT autoActivate
|
|
612
|
-
// keep the v2.4 behavior (tools always visible) for back-compat.
|
|
613
|
-
const skillToolEntries = [];
|
|
614
|
-
const sharedSkillTools = new Map();
|
|
615
|
-
for (const skill of skills) {
|
|
616
|
-
const meta = skill.metadata;
|
|
617
|
-
const isAutoActivate = meta?.autoActivate === 'currentSkill';
|
|
618
|
-
const toolsFromSkill = skill.inject.tools ?? [];
|
|
619
|
-
for (const tool of toolsFromSkill) {
|
|
620
|
-
const name = tool.schema.name;
|
|
621
|
-
const existing = sharedSkillTools.get(name);
|
|
622
|
-
if (existing) {
|
|
623
|
-
if (existing !== tool) {
|
|
624
|
-
throw new Error(`Agent: tool name '${name}' is declared by multiple skills with different ` +
|
|
625
|
-
`Tool implementations. Skills MAY share the SAME Tool reference; they may ` +
|
|
626
|
-
`NOT register different functions under the same name.`);
|
|
627
|
-
}
|
|
628
|
-
continue; // dedupe — same reference already added
|
|
629
|
-
}
|
|
630
|
-
sharedSkillTools.set(name, tool);
|
|
631
|
-
// autoActivate skills: their tools come ONLY through
|
|
632
|
-
// dynamicSchemas (buildToolsSlot.ts pulls them from
|
|
633
|
-
// activeInjections.inject.tools when the skill is active).
|
|
634
|
-
// Don't pre-load them in the static registry.
|
|
635
|
-
if (isAutoActivate)
|
|
636
|
-
continue;
|
|
637
|
-
skillToolEntries.push({ name, tool });
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
// buildReadSkillTool returns undefined when skills is empty; the
|
|
641
|
-
// length check below short-circuits so the non-null assertion is safe.
|
|
642
|
-
const readSkillEntries = skills.length > 0 ? [{ name: 'read_skill', tool: (0, skillTools_js_1.buildReadSkillTool)(skills) }] : [];
|
|
643
|
-
const augmentedRegistry = [
|
|
644
|
-
...registry,
|
|
645
|
-
...readSkillEntries,
|
|
646
|
-
...skillToolEntries,
|
|
647
|
-
];
|
|
648
|
-
// Final cross-source name-uniqueness check: static .tool() vs
|
|
649
|
-
// read_skill vs (deduped) skill tools. After the dedupe above this
|
|
650
|
-
// catches collisions BETWEEN sources (e.g., a static .tool('foo')
|
|
651
|
-
// colliding with a Skill's foo) which are real bugs.
|
|
652
|
-
const seenNames = new Set();
|
|
653
|
-
for (const entry of augmentedRegistry) {
|
|
654
|
-
if (seenNames.has(entry.name)) {
|
|
655
|
-
throw new Error(`Agent: duplicate tool name '${entry.name}'. Tool names must be unique ` +
|
|
656
|
-
`across .tool() registrations and Skills' inject.tools (after deduping ` +
|
|
657
|
-
`same-reference shares across skills). The LLM dispatches by name; ` +
|
|
658
|
-
`collisions break tool routing.`);
|
|
659
|
-
}
|
|
660
|
-
seenNames.add(entry.name);
|
|
661
|
-
}
|
|
662
|
-
const registryByName = new Map(augmentedRegistry.map((e) => [e.name, e.tool]));
|
|
663
|
-
// Block C runtime — autoActivate skill tools live OUTSIDE the LLM-
|
|
664
|
-
// visible registry (so they don't pollute the per-iteration tool
|
|
665
|
-
// list before the skill activates), but they MUST still be findable
|
|
666
|
-
// by the dispatch handler — the LLM calls them by name once the
|
|
667
|
-
// skill is active, and dispatch looks up by name. Add them to the
|
|
668
|
-
// dispatch map so `lookupTool` resolves correctly. Using the Map
|
|
669
|
-
// backing the static registryByName means autoActivate tools share
|
|
670
|
-
// the same `.execute` wiring as normal tools — no special path.
|
|
671
|
-
for (const [name, tool] of sharedSkillTools.entries()) {
|
|
672
|
-
if (!registryByName.has(name)) {
|
|
673
|
-
registryByName.set(name, tool);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
const toolSchemas = augmentedRegistry.map((e) => e.tool.schema);
|
|
521
|
+
return h;
|
|
522
|
+
},
|
|
523
|
+
getCurrentRunId: () => this.currentRunContext?.runId,
|
|
524
|
+
});
|
|
525
|
+
// Tool registry composition extracted to ./agent/buildToolRegistry.ts.
|
|
526
|
+
// Composes static .tool() registry + auto-attached read_skill +
|
|
527
|
+
// skill-supplied tools (with autoActivate scoping); validates
|
|
528
|
+
// name uniqueness; produces the dispatch map.
|
|
529
|
+
const { registryByName, toolSchemas } = (0, buildToolRegistry_js_1.buildToolRegistry)(registry, this.injections);
|
|
530
|
+
// Late-bind toolSchemas into the seed stage's deps (the factory was
|
|
531
|
+
// built earlier with a getter; this resolves the actual value).
|
|
532
|
+
toolSchemasResolved = toolSchemas;
|
|
677
533
|
const injectionEngineSubflow = (0, buildInjectionEngineSubflow_js_1.buildInjectionEngineSubflow)({
|
|
678
534
|
injections: this.injections,
|
|
679
535
|
});
|
|
@@ -686,1066 +542,53 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
686
542
|
tools: toolSchemas,
|
|
687
543
|
...(this.externalToolProvider && { toolProvider: this.externalToolProvider }),
|
|
688
544
|
});
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
.join('\n\n');
|
|
705
|
-
// Read the LLM message stream from `scope.history` directly.
|
|
706
|
-
// The `messagesInjections` projection is for observability
|
|
707
|
-
// (ContextRecorder, Lens) — it flattens InjectionRecords for
|
|
708
|
-
// event reporting and doesn't carry the full LLM-protocol
|
|
709
|
-
// shape (assistant `toolCalls[]`, etc.). For Anthropic's API
|
|
710
|
-
// contract we need the original LLMMessage with `toolCalls`
|
|
711
|
-
// intact so tool_use → tool_result correlation survives.
|
|
712
|
-
const messages = scope.history ?? [];
|
|
713
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.llm_start', {
|
|
714
|
-
iteration,
|
|
715
|
-
provider: provider.name,
|
|
716
|
-
model,
|
|
717
|
-
systemPromptChars: systemPrompt.length,
|
|
718
|
-
messagesCount: messages.length,
|
|
719
|
-
toolsCount: toolSchemas.length,
|
|
720
|
-
...(temperature !== undefined && { temperature }),
|
|
721
|
-
});
|
|
722
|
-
const startMs = Date.now();
|
|
723
|
-
// Use dynamic schemas — registry tools + injection-supplied
|
|
724
|
-
// tools (Skills' `inject.tools` when their Injection is active).
|
|
725
|
-
// Falls back to the static schemas at startup before the tools
|
|
726
|
-
// slot has run for the first time.
|
|
727
|
-
const activeToolSchemas = scope.dynamicToolSchemas ?? toolSchemas;
|
|
728
|
-
const baseRequest = {
|
|
729
|
-
...(systemPrompt.length > 0 && { systemPrompt }),
|
|
730
|
-
messages,
|
|
731
|
-
...(activeToolSchemas.length > 0 && { tools: activeToolSchemas }),
|
|
732
|
-
model,
|
|
733
|
-
...(temperature !== undefined && { temperature }),
|
|
734
|
-
...(maxTokens !== undefined && { maxTokens }),
|
|
735
|
-
};
|
|
736
|
-
// v2.6+ — call cache strategy to attach provider-specific cache
|
|
737
|
-
// hints. CacheGate has already routed (apply-markers / no-markers)
|
|
738
|
-
// and populated scope.cacheMarkers accordingly. Strategy.prepareRequest
|
|
739
|
-
// is a pass-through for empty markers.
|
|
740
|
-
const cacheMarkers = scope.cacheMarkers ?? [];
|
|
741
|
-
const cachePrepared = await cacheStrategy.prepareRequest(baseRequest, cacheMarkers, {
|
|
742
|
-
iteration,
|
|
743
|
-
iterationsRemaining: Math.max(0, maxIterations - iteration),
|
|
744
|
-
recentHitRate: scope.recentHitRate,
|
|
745
|
-
cachingDisabled: scope.cachingDisabled ?? false,
|
|
746
|
-
});
|
|
747
|
-
const llmRequest = cachePrepared.request;
|
|
748
|
-
// Streaming-first: when the provider implements `stream()` we
|
|
749
|
-
// consume chunk-by-chunk so consumers (Lens commentary, chat
|
|
750
|
-
// UIs) see tokens as they arrive instead of waiting for the
|
|
751
|
-
// full LLM call to finish. Each non-terminal chunk fires
|
|
752
|
-
// `agentfootprint.stream.token` with the token text + index.
|
|
753
|
-
//
|
|
754
|
-
// The terminal chunk SHOULD carry the authoritative
|
|
755
|
-
// `LLMResponse` (toolCalls + usage + stopReason); when it does
|
|
756
|
-
// we use it directly. When it doesn't (older providers, partial
|
|
757
|
-
// implementations) we fall back to `complete()` for the
|
|
758
|
-
// authoritative payload — keeping the ReAct loop deterministic.
|
|
759
|
-
let response;
|
|
760
|
-
if (provider.stream) {
|
|
761
|
-
for await (const chunk of provider.stream(llmRequest)) {
|
|
762
|
-
if (chunk.done) {
|
|
763
|
-
if (chunk.response)
|
|
764
|
-
response = chunk.response;
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
if (chunk.content.length > 0) {
|
|
768
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.token', {
|
|
769
|
-
iteration,
|
|
770
|
-
tokenIndex: chunk.tokenIndex,
|
|
771
|
-
content: chunk.content,
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
if (!response) {
|
|
777
|
-
// No `stream()` OR stream finished without a response payload.
|
|
778
|
-
response = await provider.complete(llmRequest);
|
|
779
|
-
}
|
|
780
|
-
const durationMs = Date.now() - startMs;
|
|
781
|
-
scope.totalInputTokens = scope.totalInputTokens + response.usage.input;
|
|
782
|
-
scope.totalOutputTokens = scope.totalOutputTokens + response.usage.output;
|
|
783
|
-
scope.llmLatestContent = response.content;
|
|
784
|
-
scope.llmLatestToolCalls = response.toolCalls;
|
|
785
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.llm_end', {
|
|
786
|
-
iteration,
|
|
787
|
-
content: response.content,
|
|
788
|
-
toolCallCount: response.toolCalls.length,
|
|
789
|
-
usage: response.usage,
|
|
790
|
-
stopReason: response.stopReason,
|
|
791
|
-
durationMs,
|
|
792
|
-
});
|
|
793
|
-
(0, cost_js_1.emitCostTick)(scope, pricingTable, costBudget, model, response.usage);
|
|
794
|
-
};
|
|
795
|
-
/** Decides the next branch: 'tool-calls' or 'final'. */
|
|
796
|
-
const routeDecider = (scope) => {
|
|
797
|
-
const toolCalls = scope.llmLatestToolCalls;
|
|
798
|
-
const iteration = scope.iteration;
|
|
799
|
-
const chosen = toolCalls.length > 0 && iteration < scope.maxIterations ? 'tool-calls' : 'final';
|
|
800
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.route_decided', {
|
|
801
|
-
turnIndex: 0,
|
|
802
|
-
iterIndex: iteration,
|
|
803
|
-
chosen,
|
|
804
|
-
rationale: chosen === 'tool-calls'
|
|
805
|
-
? `LLM requested ${toolCalls.length} tool call(s)`
|
|
806
|
-
: iteration >= scope.maxIterations
|
|
807
|
-
? 'maxIterations reached — forcing final'
|
|
808
|
-
: 'LLM produced no tool calls — final answer',
|
|
809
|
-
});
|
|
810
|
-
return chosen;
|
|
811
|
-
};
|
|
812
|
-
/**
|
|
813
|
-
* Pausable tool-call handler.
|
|
814
|
-
*
|
|
815
|
-
* `execute` iterates the LLM-requested tool calls. If a tool throws
|
|
816
|
-
* `PauseRequest` via `pauseHere()`, we save the remaining work into
|
|
817
|
-
* scope and return the pause data — footprintjs captures a checkpoint
|
|
818
|
-
* and bubbles it up. The outer `Agent.run()` surfaces it as a
|
|
819
|
-
* `RunnerPauseOutcome`.
|
|
820
|
-
*
|
|
821
|
-
* `resume` is called when the consumer provides the human's answer.
|
|
822
|
-
* We treat that answer as the paused tool's result and append it to
|
|
823
|
-
* history, then continue the ReAct iteration loop.
|
|
824
|
-
*/
|
|
825
|
-
const toolCallsHandler = {
|
|
826
|
-
execute: async (scope) => {
|
|
827
|
-
const toolCalls = scope.llmLatestToolCalls;
|
|
828
|
-
const iteration = scope.iteration;
|
|
829
|
-
const newHistory = [...scope.history];
|
|
830
|
-
// ALWAYS push the assistant turn when there are tool calls — even
|
|
831
|
-
// if the content was empty — so providers (Anthropic, OpenAI) can
|
|
832
|
-
// round-trip the tool_use blocks via `LLMMessage.toolCalls`.
|
|
833
|
-
// Without this, the next iteration's request lacks the assistant
|
|
834
|
-
// turn that initiated the tool call, and the API rejects the
|
|
835
|
-
// following tool_result with "preceding tool_use missing".
|
|
836
|
-
if (scope.llmLatestContent || toolCalls.length > 0) {
|
|
837
|
-
newHistory.push({
|
|
838
|
-
role: 'assistant',
|
|
839
|
-
content: scope.llmLatestContent ?? '',
|
|
840
|
-
...(toolCalls.length > 0 && { toolCalls }),
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
// Resolve a tool by name, consulting the external ToolProvider
|
|
844
|
-
// if one was wired via `.toolProvider()` and the static
|
|
845
|
-
// registry doesn't carry the tool. The provider sees the same
|
|
846
|
-
// ctx the Tools slot used, so dispatch + visibility stay
|
|
847
|
-
// consistent within the iteration.
|
|
848
|
-
const externalToolProvider = this.externalToolProvider;
|
|
849
|
-
const lookupTool = (toolName) => {
|
|
850
|
-
const fromRegistry = registryByName.get(toolName);
|
|
851
|
-
if (fromRegistry)
|
|
852
|
-
return fromRegistry;
|
|
853
|
-
if (!externalToolProvider)
|
|
854
|
-
return undefined;
|
|
855
|
-
const activatedIds = scope.activatedInjectionIds ?? [];
|
|
856
|
-
const identity = scope.runIdentity;
|
|
857
|
-
const ctx = {
|
|
858
|
-
iteration: scope.iteration,
|
|
859
|
-
...(activatedIds.length > 0 && {
|
|
860
|
-
activeSkillId: activatedIds[activatedIds.length - 1],
|
|
861
|
-
}),
|
|
862
|
-
...(identity && { identity }),
|
|
863
|
-
};
|
|
864
|
-
const visible = externalToolProvider.list(ctx);
|
|
865
|
-
return visible.find((t) => t.schema.name === toolName);
|
|
866
|
-
};
|
|
867
|
-
for (const tc of toolCalls) {
|
|
868
|
-
const tool = lookupTool(tc.name);
|
|
869
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.tool_start', {
|
|
870
|
-
toolName: tc.name,
|
|
871
|
-
toolCallId: tc.id,
|
|
872
|
-
args: tc.args,
|
|
873
|
-
...(toolCalls.length > 1 && { parallelCount: toolCalls.length }),
|
|
874
|
-
});
|
|
875
|
-
const startMs = Date.now();
|
|
876
|
-
let result;
|
|
877
|
-
let error;
|
|
878
|
-
// Permission gate — when a checker is configured, evaluate BEFORE
|
|
879
|
-
// executing the tool. Emits `permission.check` with the decision.
|
|
880
|
-
// On 'deny', the tool is not executed and its result is a
|
|
881
|
-
// synthetic denial string; on 'allow'/'gate_open', execution
|
|
882
|
-
// proceeds normally (the gate is informational — the consumer's
|
|
883
|
-
// checker is responsible for any gate-open side effects).
|
|
884
|
-
let denied = false;
|
|
885
|
-
if (permissionChecker) {
|
|
886
|
-
try {
|
|
887
|
-
const decision = await permissionChecker.check({
|
|
888
|
-
capability: 'tool_call',
|
|
889
|
-
actor: 'agent',
|
|
890
|
-
target: tc.name,
|
|
891
|
-
context: tc.args,
|
|
892
|
-
});
|
|
893
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.permission.check', {
|
|
894
|
-
capability: 'tool_call',
|
|
895
|
-
actor: 'agent',
|
|
896
|
-
target: tc.name,
|
|
897
|
-
result: decision.result,
|
|
898
|
-
...(decision.policyRuleId !== undefined && { policyRuleId: decision.policyRuleId }),
|
|
899
|
-
...(decision.rationale !== undefined && { rationale: decision.rationale }),
|
|
900
|
-
});
|
|
901
|
-
if (decision.result === 'deny') {
|
|
902
|
-
denied = true;
|
|
903
|
-
result = `[permission denied: ${decision.rationale ?? 'policy'}]`;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
catch (permErr) {
|
|
907
|
-
// A checker that throws is treated as deny-by-default. The
|
|
908
|
-
// denial message records the thrown error so consumers can
|
|
909
|
-
// debug policy-adapter failures without losing the run.
|
|
910
|
-
denied = true;
|
|
911
|
-
const msg = permErr instanceof Error ? permErr.message : String(permErr);
|
|
912
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.permission.check', {
|
|
913
|
-
capability: 'tool_call',
|
|
914
|
-
actor: 'agent',
|
|
915
|
-
target: tc.name,
|
|
916
|
-
result: 'deny',
|
|
917
|
-
rationale: `permission-checker threw: ${msg}`,
|
|
918
|
-
});
|
|
919
|
-
result = `[permission denied: checker error: ${msg}]`;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
if (!denied) {
|
|
923
|
-
try {
|
|
924
|
-
if (!tool)
|
|
925
|
-
throw new Error(`Unknown tool: ${tc.name}`);
|
|
926
|
-
result = await tool.execute(tc.args, {
|
|
927
|
-
toolCallId: tc.id,
|
|
928
|
-
iteration,
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
catch (err) {
|
|
932
|
-
if ((0, pause_js_1.isPauseRequest)(err)) {
|
|
933
|
-
// Commit partial state so resume() can find history intact.
|
|
934
|
-
scope.history = newHistory;
|
|
935
|
-
scope.pausedToolCallId = tc.id;
|
|
936
|
-
scope.pausedToolName = tc.name;
|
|
937
|
-
scope.pausedToolStartMs = startMs;
|
|
938
|
-
// Returning a defined value triggers footprintjs pause —
|
|
939
|
-
// the returned object becomes the checkpoint's pauseData.
|
|
940
|
-
return {
|
|
941
|
-
toolCallId: tc.id,
|
|
942
|
-
toolName: tc.name,
|
|
943
|
-
...(typeof err.data === 'object' && err.data !== null
|
|
944
|
-
? err.data
|
|
945
|
-
: { data: err.data }),
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
error = true;
|
|
949
|
-
result = err instanceof Error ? err.message : String(err);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
const durationMs = Date.now() - startMs;
|
|
953
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.tool_end', {
|
|
954
|
-
toolCallId: tc.id,
|
|
955
|
-
result,
|
|
956
|
-
durationMs,
|
|
957
|
-
...(error === true && { error: true }),
|
|
958
|
-
});
|
|
959
|
-
const resultStr = typeof result === 'string' ? result : (0, validators_js_1.safeStringify)(result);
|
|
960
|
-
newHistory.push({
|
|
961
|
-
role: 'tool',
|
|
962
|
-
content: resultStr,
|
|
963
|
-
toolCallId: tc.id,
|
|
964
|
-
toolName: tc.name,
|
|
965
|
-
});
|
|
966
|
-
// ── Dynamic ReAct wiring ───────────────────────────────
|
|
967
|
-
//
|
|
968
|
-
// (1) `lastToolResult` drives `on-tool-return` Injection
|
|
969
|
-
// triggers — the InjectionEngine's NEXT pass will see
|
|
970
|
-
// this and activate any matching Instructions.
|
|
971
|
-
scope.lastToolResult = { toolName: tc.name, result: resultStr };
|
|
972
|
-
// (2) `read_skill` is the auto-attached activation tool.
|
|
973
|
-
// When the LLM calls it with a valid Skill id, append
|
|
974
|
-
// to `activatedInjectionIds` so the InjectionEngine's
|
|
975
|
-
// NEXT pass activates that Skill (lifetime: turn — stays
|
|
976
|
-
// active until the turn ends).
|
|
977
|
-
if (tc.name === 'read_skill' && !error && !denied) {
|
|
978
|
-
const requestedId = tc.args.id;
|
|
979
|
-
if (typeof requestedId === 'string' && requestedId.length > 0) {
|
|
980
|
-
const current = scope.activatedInjectionIds;
|
|
981
|
-
if (!current.includes(requestedId)) {
|
|
982
|
-
scope.activatedInjectionIds = [...current, requestedId];
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
scope.history = newHistory;
|
|
988
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.iteration_end', {
|
|
989
|
-
turnIndex: 0,
|
|
990
|
-
iterIndex: iteration,
|
|
991
|
-
toolCallCount: toolCalls.length,
|
|
992
|
-
history: scope.history,
|
|
993
|
-
});
|
|
994
|
-
scope.iteration = iteration + 1;
|
|
995
|
-
return undefined; // explicit: no pause, flow continues to loopTo
|
|
996
|
-
},
|
|
997
|
-
resume: (scope, input) => {
|
|
998
|
-
// Consumer-supplied resume input becomes the paused tool's result.
|
|
999
|
-
// The subflow's pre-pause scope is restored automatically by
|
|
1000
|
-
// footprintjs 4.17.0 via `checkpoint.subflowStates`, so
|
|
1001
|
-
// `scope.history` and `scope.pausedToolCallId` read back cleanly
|
|
1002
|
-
// across same-executor AND cross-executor resume.
|
|
1003
|
-
const toolCallId = scope.pausedToolCallId;
|
|
1004
|
-
const toolName = scope.pausedToolName;
|
|
1005
|
-
const startMs = scope.pausedToolStartMs;
|
|
1006
|
-
const resultStr = typeof input === 'string' ? input : (0, validators_js_1.safeStringify)(input);
|
|
1007
|
-
const newHistory = [
|
|
1008
|
-
...scope.history,
|
|
1009
|
-
{
|
|
1010
|
-
role: 'tool',
|
|
1011
|
-
content: resultStr,
|
|
1012
|
-
toolCallId,
|
|
1013
|
-
toolName,
|
|
1014
|
-
},
|
|
1015
|
-
];
|
|
1016
|
-
scope.history = newHistory;
|
|
1017
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.tool_end', {
|
|
1018
|
-
toolCallId,
|
|
1019
|
-
result: input,
|
|
1020
|
-
durationMs: Date.now() - startMs,
|
|
1021
|
-
});
|
|
1022
|
-
const iteration = scope.iteration;
|
|
1023
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.iteration_end', {
|
|
1024
|
-
turnIndex: 0,
|
|
1025
|
-
iterIndex: iteration,
|
|
1026
|
-
toolCallCount: 1,
|
|
1027
|
-
history: scope.history,
|
|
1028
|
-
});
|
|
1029
|
-
scope.iteration = iteration + 1;
|
|
1030
|
-
// Clear pause checkpoint fields.
|
|
1031
|
-
scope.pausedToolCallId = '';
|
|
1032
|
-
scope.pausedToolName = '';
|
|
1033
|
-
scope.pausedToolStartMs = 0;
|
|
1034
|
-
},
|
|
1035
|
-
};
|
|
1036
|
-
// Final branch is split so memory-write subflows can mount BETWEEN
|
|
1037
|
-
// setting `finalContent` and breaking the ReAct loop. PrepareFinal
|
|
1038
|
-
// captures the turn payload; BreakFinal terminates the loop.
|
|
1039
|
-
const prepareFinalStage = (scope) => {
|
|
1040
|
-
const iteration = scope.iteration;
|
|
1041
|
-
scope.finalContent = scope.llmLatestContent;
|
|
1042
|
-
// The turn payload memory writes persist: the user's message
|
|
1043
|
-
// paired with the agent's final answer.
|
|
1044
|
-
scope.newMessages = [
|
|
1045
|
-
{ role: 'user', content: scope.userMessage },
|
|
1046
|
-
{ role: 'assistant', content: scope.finalContent },
|
|
1047
|
-
];
|
|
1048
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.iteration_end', {
|
|
1049
|
-
turnIndex: 0,
|
|
1050
|
-
iterIndex: iteration,
|
|
1051
|
-
toolCallCount: 0,
|
|
1052
|
-
});
|
|
1053
|
-
(0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.turn_end', {
|
|
1054
|
-
turnIndex: 0,
|
|
1055
|
-
finalContent: scope.finalContent,
|
|
1056
|
-
totalInputTokens: scope.totalInputTokens,
|
|
1057
|
-
totalOutputTokens: scope.totalOutputTokens,
|
|
1058
|
-
iterationCount: iteration,
|
|
1059
|
-
durationMs: Date.now() - scope.turnStartMs,
|
|
1060
|
-
});
|
|
1061
|
-
};
|
|
1062
|
-
const breakFinalStage = (scope) => {
|
|
1063
|
-
// $break terminates the flow before loopTo fires, ending the
|
|
1064
|
-
// ReAct iteration once memory writes (if any) have persisted.
|
|
1065
|
-
scope.$break();
|
|
1066
|
-
return scope.finalContent;
|
|
1067
|
-
};
|
|
1068
|
-
// Compose the final branch as its own subflow so memory write
|
|
1069
|
-
// subflows mount as visible siblings in narrative + Lens.
|
|
1070
|
-
let finalBranchBuilder = (0, footprintjs_1.flowChart)('PrepareFinal', prepareFinalStage, 'prepare-final', undefined, 'Capture turn payload (finalContent + newMessages)');
|
|
1071
|
-
for (const m of this.memories) {
|
|
1072
|
-
if (m.write) {
|
|
1073
|
-
finalBranchBuilder = (0, mountMemoryPipeline_js_1.mountMemoryWrite)(finalBranchBuilder, {
|
|
1074
|
-
pipeline: {
|
|
1075
|
-
read: (0, define_js_1.unwrapMemoryFlowChart)(m.read),
|
|
1076
|
-
write: (0, define_js_1.unwrapMemoryFlowChart)(m.write),
|
|
1077
|
-
},
|
|
1078
|
-
identityKey: 'runIdentity',
|
|
1079
|
-
turnNumberKey: 'turnNumber',
|
|
1080
|
-
contextTokensKey: 'contextTokensRemaining',
|
|
1081
|
-
newMessagesKey: 'newMessages',
|
|
1082
|
-
writeSubflowId: `sf-memory-write-${m.id}`,
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
const finalBranchChart = finalBranchBuilder
|
|
1087
|
-
.addFunction('BreakFinal', breakFinalStage, 'break-final', 'Terminate the ReAct loop')
|
|
1088
|
-
.build();
|
|
1089
|
-
// Description prefix `Agent:` is a taxonomy marker — consumers
|
|
1090
|
-
// (Lens + FlowchartRecorder) detect Agent-primitive subflows via
|
|
1091
|
-
// this prefix and flag them as true agent boundaries (separate
|
|
1092
|
-
// from LLMCall subflows which use `LLMCall:` prefix).
|
|
1093
|
-
let builder = (0, footprintjs_1.flowChart)('Seed', seed, conventions_js_1.STAGE_IDS.SEED, undefined, 'Agent: ReAct loop');
|
|
1094
|
-
// Memory READ subflows — mounted between Seed and InjectionEngine
|
|
1095
|
-
// for TURN_START timing (default). Each memory writes to its own
|
|
1096
|
-
// scope key (`memoryInjection_${id}`) so multiple `.memory()`
|
|
1097
|
-
// registrations layer without colliding.
|
|
1098
|
-
for (const m of this.memories) {
|
|
1099
|
-
builder = (0, mountMemoryPipeline_js_1.mountMemoryRead)(builder, {
|
|
1100
|
-
pipeline: {
|
|
1101
|
-
read: (0, define_js_1.unwrapMemoryFlowChart)(m.read),
|
|
1102
|
-
...(m.write !== undefined && { write: (0, define_js_1.unwrapMemoryFlowChart)(m.write) }),
|
|
1103
|
-
},
|
|
1104
|
-
identityKey: 'runIdentity',
|
|
1105
|
-
turnNumberKey: 'turnNumber',
|
|
1106
|
-
contextTokensKey: 'contextTokensRemaining',
|
|
1107
|
-
injectionKey: (0, define_types_js_1.memoryInjectionKey)(m.id),
|
|
1108
|
-
readSubflowId: `sf-memory-read-${m.id}`,
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
builder = builder
|
|
1112
|
-
// Injection Engine — evaluates every Injection's trigger once
|
|
1113
|
-
// per iteration; writes activeInjections[] to parent scope for
|
|
1114
|
-
// the slot subflows to consume. Skipped if no injections were
|
|
1115
|
-
// registered (no observable difference, just one more no-op
|
|
1116
|
-
// subflow boundary).
|
|
1117
|
-
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.INJECTION_ENGINE, injectionEngineSubflow, 'Injection Engine', {
|
|
1118
|
-
inputMapper: (parent) => ({
|
|
1119
|
-
iteration: parent.iteration,
|
|
1120
|
-
userMessage: parent.userMessage,
|
|
1121
|
-
history: parent.history,
|
|
1122
|
-
lastToolResult: parent.lastToolResult,
|
|
1123
|
-
activatedInjectionIds: parent.activatedInjectionIds ?? [],
|
|
1124
|
-
}),
|
|
1125
|
-
outputMapper: (sf) => ({ activeInjections: sf.activeInjections }),
|
|
1126
|
-
// CRITICAL: footprintjs's default `applyOutputMapping`
|
|
1127
|
-
// CONCATENATES arrays from subflow output with the parent's
|
|
1128
|
-
// existing array values. Without `Replace`, the parent's
|
|
1129
|
-
// `activeInjections` from iter N gets CONCATENATED with the
|
|
1130
|
-
// subflow's iter N+1 fresh evaluation — producing
|
|
1131
|
-
// 8 → 16 → 24 → 32 cumulative injections per turn instead of
|
|
1132
|
-
// the intended ~8-per-iter.
|
|
1133
|
-
//
|
|
1134
|
-
// The slot subflows below (SystemPrompt, Messages, Tools) all
|
|
1135
|
-
// read `activeInjections` and render every entry, so without
|
|
1136
|
-
// Replace the system prompt grows linearly with iteration
|
|
1137
|
-
// count. This was the root-cause of Dynamic-mode costing
|
|
1138
|
-
// ~2x more input tokens than Classic in the v2.5.0 Neo
|
|
1139
|
-
// benchmarks — the InjectionEngine's intended per-iter
|
|
1140
|
-
// recomposition wasn't happening; it was per-iter ACCUMULATION.
|
|
1141
|
-
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
1142
|
-
})
|
|
1143
|
-
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.SYSTEM_PROMPT, systemPromptSubflow, 'System Prompt', {
|
|
1144
|
-
inputMapper: (parent) => ({
|
|
1145
|
-
userMessage: parent.userMessage,
|
|
1146
|
-
iteration: parent.iteration,
|
|
1147
|
-
activeInjections: parent.activeInjections,
|
|
1148
|
-
}),
|
|
1149
|
-
outputMapper: (sf) => ({ systemPromptInjections: sf.systemPromptInjections }),
|
|
1150
|
-
// See Tools-subflow comment below — same array-concat hazard.
|
|
1151
|
-
// Without Replace, iter N+1's systemPromptInjections gets
|
|
1152
|
-
// CONCATENATED with iter N's, multiplying the system prompt
|
|
1153
|
-
// each iteration.
|
|
1154
|
-
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
1155
|
-
})
|
|
1156
|
-
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.MESSAGES, messagesSubflow, 'Messages', {
|
|
1157
|
-
inputMapper: (parent) => ({
|
|
1158
|
-
messages: parent.history,
|
|
1159
|
-
iteration: parent.iteration,
|
|
1160
|
-
activeInjections: parent.activeInjections,
|
|
1161
|
-
}),
|
|
1162
|
-
outputMapper: (sf) => ({ messagesInjections: sf.messagesInjections }),
|
|
1163
|
-
// Same array-concat hazard. messagesInjections is consumer-
|
|
1164
|
-
// facing observability metadata (ContextRecorder, Lens) — must
|
|
1165
|
-
// reflect THIS iteration's history, not be appended to last
|
|
1166
|
-
// iteration's. CallLLM no longer reads this for the wire
|
|
1167
|
-
// request (uses scope.history directly), so the LLM-protocol
|
|
1168
|
-
// bug is fixed independently — but consumers of the
|
|
1169
|
-
// messagesInjections stream still expect the per-iteration
|
|
1170
|
-
// semantics.
|
|
1171
|
-
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
1172
|
-
})
|
|
1173
|
-
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.TOOLS, toolsSubflow, 'Tools', {
|
|
1174
|
-
inputMapper: (parent) => ({
|
|
1175
|
-
iteration: parent.iteration,
|
|
1176
|
-
activeInjections: parent.activeInjections,
|
|
1177
|
-
// The slot subflow reads these to build the per-iteration
|
|
1178
|
-
// ToolDispatchContext when an external `.toolProvider()` is
|
|
1179
|
-
// configured. Without them the provider sees activeSkillId
|
|
1180
|
-
// = undefined every iteration, breaking skillScopedTools etc.
|
|
1181
|
-
activatedInjectionIds: parent.activatedInjectionIds,
|
|
1182
|
-
runIdentity: parent.runIdentity,
|
|
1183
|
-
}),
|
|
1184
|
-
outputMapper: (sf) => ({
|
|
1185
|
-
toolsInjections: sf.toolsInjections,
|
|
1186
|
-
// Pass merged tool schemas (registry + injection-supplied)
|
|
1187
|
-
// back up so callLLM uses the right list for THIS iteration.
|
|
1188
|
-
dynamicToolSchemas: sf.toolSchemas,
|
|
1189
|
-
}),
|
|
1190
|
-
// CRITICAL: footprintjs's default `applyOutputMapping`
|
|
1191
|
-
// CONCATENATES arrays from subflow output with the parent's
|
|
1192
|
-
// existing array values. Without `Replace`, the parent's
|
|
1193
|
-
// `dynamicToolSchemas` (carrying the iter N value) gets
|
|
1194
|
-
// concatenated with the slot's iter N+1 deduped list,
|
|
1195
|
-
// re-introducing duplicate tool names that Anthropic's API
|
|
1196
|
-
// rejects with "tools: Tool names must be unique." The slot's
|
|
1197
|
-
// toolSchemas IS the authoritative list — replace, don't
|
|
1198
|
-
// concatenate.
|
|
1199
|
-
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
1200
|
-
})
|
|
1201
|
-
// ── Cache layer (v2.6) ─────────────────────────────────────
|
|
1202
|
-
// CacheDecision subflow walks `activeInjections` + evaluates
|
|
1203
|
-
// each `cache:` directive, emits provider-agnostic
|
|
1204
|
-
// `CacheMarker[]` to scope. Pure transform; no IO.
|
|
1205
|
-
//
|
|
1206
|
-
// CRITICAL: arrayMerge: ArrayMergeMode.Replace — same lesson
|
|
1207
|
-
// as the v2.5.1 InjectionEngine fix. The default footprintjs
|
|
1208
|
-
// behavior CONCATENATES arrays from child to parent;
|
|
1209
|
-
// `cacheMarkers` MUST replace each iteration, not accumulate.
|
|
1210
|
-
.addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.CACHE_DECISION, CacheDecisionSubflow_js_1.cacheDecisionSubflow, 'CacheDecision', {
|
|
1211
|
-
inputMapper: (parent) => ({
|
|
1212
|
-
activeInjections: parent.activeInjections ?? [],
|
|
1213
|
-
iteration: parent.iteration ?? 1,
|
|
1214
|
-
maxIterations: parent.maxIterations ?? maxIterations,
|
|
1215
|
-
userMessage: parent.userMessage ?? '',
|
|
1216
|
-
...(parent.lastToolResult !== undefined && {
|
|
1217
|
-
lastToolName: parent.lastToolResult?.toolName,
|
|
1218
|
-
}),
|
|
1219
|
-
cumulativeInputTokens: parent.totalInputTokens ?? 0,
|
|
1220
|
-
systemPromptCachePolicy,
|
|
1221
|
-
cachingDisabled: parent.cachingDisabled ?? false,
|
|
1222
|
-
}),
|
|
1223
|
-
outputMapper: (sf) => ({ cacheMarkers: sf.cacheMarkers }),
|
|
1224
|
-
arrayMerge: advanced_1.ArrayMergeMode.Replace,
|
|
1225
|
-
})
|
|
1226
|
-
.addFunction('UpdateSkillHistory', CacheGateDecider_js_1.updateSkillHistory, conventions_js_1.STAGE_IDS.UPDATE_SKILL_HISTORY, 'Update skill-history rolling window for CacheGate churn detection')
|
|
1227
|
-
.addDeciderFunction('CacheGate', CacheGateDecider_js_1.cacheGateDecide, conventions_js_1.STAGE_IDS.CACHE_GATE, 'Gate cache-marker application: kill switch / hit-rate / skill-churn')
|
|
1228
|
-
.addFunctionBranch(conventions_js_1.STAGE_IDS.APPLY_MARKERS, 'ApplyMarkers',
|
|
1229
|
-
// Pass-through stage — markers stay in scope as-is.
|
|
1230
|
-
// BuildLLMRequest (Phase 7+) reads them on the next stage.
|
|
1231
|
-
() => undefined, 'Proceed with cache markers from CacheDecision')
|
|
1232
|
-
.addFunctionBranch(conventions_js_1.STAGE_IDS.SKIP_CACHING, 'SkipCaching',
|
|
1233
|
-
// Clear markers so BuildLLMRequest sees an empty list and
|
|
1234
|
-
// makes the request unmodified.
|
|
1235
|
-
(scope) => {
|
|
1236
|
-
scope.cacheMarkers = [];
|
|
1237
|
-
}, 'Skip caching this iteration')
|
|
1238
|
-
.end()
|
|
1239
|
-
.addFunction('IterationStart', iterationStart, 'iteration-start', 'Iteration begin marker')
|
|
1240
|
-
.addFunction('CallLLM', callLLM, conventions_js_1.STAGE_IDS.CALL_LLM, 'LLM invocation')
|
|
1241
|
-
.addDeciderFunction('Route', routeDecider, conventions_js_1.SUBFLOW_IDS.ROUTE, 'ReAct routing')
|
|
1242
|
-
.addPausableFunctionBranch('tool-calls', 'ToolCalls', toolCallsHandler, 'Tool execution (pausable via pauseHere)')
|
|
1243
|
-
.addSubFlowChartBranch('final', finalBranchChart, 'Final', {
|
|
1244
|
-
// Pass through the read-only state the sub-chart needs;
|
|
1245
|
-
// OMIT keys the sub-chart writes (finalContent, newMessages)
|
|
1246
|
-
// — passing those via inputMapper would freeze them as args.
|
|
1247
|
-
inputMapper: (parent) => {
|
|
1248
|
-
const { finalContent: _f, newMessages: _nm, ...rest } = parent;
|
|
1249
|
-
void _f;
|
|
1250
|
-
void _nm;
|
|
1251
|
-
return rest;
|
|
545
|
+
// iterationStart extracted to ./agent/stages/iterationStart.ts (v2.11.2).
|
|
546
|
+
const iterationStart = iterationStart_js_1.iterationStartStage;
|
|
547
|
+
// callLLM extracted to ./agent/stages/callLLM.ts (v2.11.2). Same
|
|
548
|
+
// late-binding pattern as seed for toolSchemas (computed below).
|
|
549
|
+
const callLLM = (0, callLLM_js_1.buildCallLLMStage)({
|
|
550
|
+
provider,
|
|
551
|
+
model,
|
|
552
|
+
...(temperature !== undefined && { temperature }),
|
|
553
|
+
...(maxTokens !== undefined && { maxTokens }),
|
|
554
|
+
...(pricingTable !== undefined && { pricingTable }),
|
|
555
|
+
...(costBudget !== undefined && { costBudget }),
|
|
556
|
+
maxIterations,
|
|
557
|
+
cacheStrategy,
|
|
558
|
+
get toolSchemas() {
|
|
559
|
+
return toolSchemasResolved;
|
|
1252
560
|
},
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
561
|
+
});
|
|
562
|
+
// routeDecider extracted to ./agent/stages/route.ts (v2.11.2).
|
|
563
|
+
const routeDecider = route_js_1.routeDeciderStage;
|
|
564
|
+
// toolCallsHandler extracted to ./agent/stages/toolCalls.ts (v2.11.2).
|
|
565
|
+
const toolCallsHandler = (0, toolCalls_js_1.buildToolCallsHandler)({
|
|
566
|
+
registryByName,
|
|
567
|
+
...(this.externalToolProvider && { externalToolProvider: this.externalToolProvider }),
|
|
568
|
+
...(permissionChecker && { permissionChecker }),
|
|
569
|
+
});
|
|
570
|
+
// Chart composition extracted to ./agent/buildAgentChart.ts (v2.11.2).
|
|
571
|
+
return (0, buildAgentChart_js_1.buildAgentChart)({
|
|
572
|
+
memories: this.memories,
|
|
573
|
+
systemPromptCachePolicy,
|
|
574
|
+
maxIterations,
|
|
575
|
+
seed,
|
|
576
|
+
iterationStart,
|
|
577
|
+
callLLM,
|
|
578
|
+
routeDecider,
|
|
579
|
+
toolCallsHandler,
|
|
580
|
+
injectionEngineSubflow,
|
|
581
|
+
systemPromptSubflow,
|
|
582
|
+
messagesSubflow,
|
|
583
|
+
toolsSubflow,
|
|
584
|
+
cacheDecisionSubflow: CacheDecisionSubflow_js_1.cacheDecisionSubflow,
|
|
585
|
+
updateSkillHistoryStage: CacheGateDecider_js_1.updateSkillHistory,
|
|
586
|
+
cacheGateDecide: CacheGateDecider_js_1.cacheGateDecide,
|
|
587
|
+
});
|
|
1276
588
|
}
|
|
1277
589
|
}
|
|
1278
590
|
exports.Agent = Agent;
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
* it by its schema.name. Duplicate names throw at build time.
|
|
1282
|
-
*/
|
|
1283
|
-
class AgentBuilder {
|
|
1284
|
-
opts;
|
|
1285
|
-
systemPromptValue = '';
|
|
1286
|
-
/**
|
|
1287
|
-
* Cache policy for the base system prompt. Set via the optional
|
|
1288
|
-
* 2nd argument to `.system(text, { cache })`. Default `'always'` —
|
|
1289
|
-
* the base prompt is stable per-turn and an ideal cache anchor.
|
|
1290
|
-
*/
|
|
1291
|
-
systemPromptCachePolicy = 'always';
|
|
1292
|
-
/**
|
|
1293
|
-
* Global cache kill switch. Set via `Agent.create({ caching: 'off' })`
|
|
1294
|
-
* (handled in `AgentOptions` propagation). Defaults to `false`
|
|
1295
|
-
* (caching enabled). When `true`, the CacheGate decider routes to
|
|
1296
|
-
* `'no-markers'` every iteration regardless of other rules.
|
|
1297
|
-
*/
|
|
1298
|
-
cachingDisabledValue = false;
|
|
1299
|
-
/**
|
|
1300
|
-
* Optional explicit CacheStrategy override. Default: undefined,
|
|
1301
|
-
* which means the agent auto-resolves from
|
|
1302
|
-
* `getDefaultCacheStrategy(provider.name)` at construction. Power
|
|
1303
|
-
* users override here for custom backends or test mocks.
|
|
1304
|
-
*/
|
|
1305
|
-
cacheStrategyOverride;
|
|
1306
|
-
registry = [];
|
|
1307
|
-
injectionList = [];
|
|
1308
|
-
memoryList = [];
|
|
1309
|
-
/**
|
|
1310
|
-
* Optional terminal contract — see `outputSchema()`. Stored on the
|
|
1311
|
-
* builder, propagated to the Agent at `.build()` time.
|
|
1312
|
-
*/
|
|
1313
|
-
outputSchemaParser;
|
|
1314
|
-
/** 3-tier output fallback chain — set via `.outputFallback({...})`.
|
|
1315
|
-
* Optional; absent = current throw-on-validation-failure behavior. */
|
|
1316
|
-
outputFallbackCfg;
|
|
1317
|
-
/**
|
|
1318
|
-
* Optional `ToolProvider` set via `.toolProvider()`. Propagated to
|
|
1319
|
-
* the Agent's Tools slot subflow + tool-call dispatcher; consulted
|
|
1320
|
-
* per iteration so dynamic chains (`gatedTools`, `skillScopedTools`)
|
|
1321
|
-
* react to current activation state.
|
|
1322
|
-
*/
|
|
1323
|
-
toolProviderRef;
|
|
1324
|
-
/**
|
|
1325
|
-
* Optional override for `AgentOptions.maxIterations`. When set via
|
|
1326
|
-
* the `.maxIterations()` builder method, takes precedence over the
|
|
1327
|
-
* value passed to `Agent.create({ maxIterations })`.
|
|
1328
|
-
*/
|
|
1329
|
-
maxIterationsOverride;
|
|
1330
|
-
/**
|
|
1331
|
-
* Recorders collected via `.recorder()`. Attached to the built Agent
|
|
1332
|
-
* before `build()` returns (each via `agent.attach(rec)`).
|
|
1333
|
-
*/
|
|
1334
|
-
recorderList = [];
|
|
1335
|
-
// Voice config — defaults until the consumer calls .appName() /
|
|
1336
|
-
// .commentaryTemplates() / .thinkingTemplates(). Stored as plain
|
|
1337
|
-
// dicts (Record<string, string>) so the builder doesn't depend on
|
|
1338
|
-
// the template-engine modules at compile time; the runtime types
|
|
1339
|
-
// come from the agentfootprint barrel exports.
|
|
1340
|
-
appNameValue = 'Chatbot';
|
|
1341
|
-
commentaryOverrides = {};
|
|
1342
|
-
thinkingOverrides = {};
|
|
1343
|
-
constructor(opts) {
|
|
1344
|
-
this.opts = opts;
|
|
1345
|
-
// Cache layer: opts.caching === 'off' propagates to scope's
|
|
1346
|
-
// `cachingDisabled` kill switch read by CacheGate. opts.cacheStrategy
|
|
1347
|
-
// overrides the registry-resolved default.
|
|
1348
|
-
if (opts.caching === 'off')
|
|
1349
|
-
this.cachingDisabledValue = true;
|
|
1350
|
-
if (opts.cacheStrategy !== undefined)
|
|
1351
|
-
this.cacheStrategyOverride = opts.cacheStrategy;
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Set the base system prompt.
|
|
1355
|
-
*
|
|
1356
|
-
* @param prompt - The system prompt text. Stable per-turn.
|
|
1357
|
-
* @param options - Optional config. `cache` controls how the
|
|
1358
|
-
* CacheDecision subflow treats this prompt block:
|
|
1359
|
-
* - `'always'` (default) — cache the base prompt as a stable
|
|
1360
|
-
* prefix anchor. Highest cache-hit rate; recommended for
|
|
1361
|
-
* production agents whose system prompt rarely changes.
|
|
1362
|
-
* - `'never'` — skip caching. Use if the prompt contains volatile
|
|
1363
|
-
* content (timestamps, per-request user IDs).
|
|
1364
|
-
* - `'while-active'` — semantically equivalent to `'always'` for
|
|
1365
|
-
* the base prompt (it's always active by definition).
|
|
1366
|
-
* - `{ until }` — conditional invalidation (e.g., flush after iter 5).
|
|
1367
|
-
*/
|
|
1368
|
-
system(prompt, options) {
|
|
1369
|
-
this.systemPromptValue = prompt;
|
|
1370
|
-
if (options?.cache !== undefined) {
|
|
1371
|
-
this.systemPromptCachePolicy = options.cache;
|
|
1372
|
-
}
|
|
1373
|
-
return this;
|
|
1374
|
-
}
|
|
1375
|
-
tool(tool) {
|
|
1376
|
-
const name = tool.schema.name;
|
|
1377
|
-
if (this.registry.some((e) => e.name === name)) {
|
|
1378
|
-
throw new Error(`Agent.tool(): duplicate tool name '${name}'`);
|
|
1379
|
-
}
|
|
1380
|
-
this.registry.push({ name, tool: tool });
|
|
1381
|
-
return this;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Register many tools at once. Convenience for tool sources that
|
|
1385
|
-
* return a list (e.g., `await mcpClient(...).tools()`). Each tool
|
|
1386
|
-
* is registered via `.tool()` so duplicate-name validation still
|
|
1387
|
-
* fires per-entry.
|
|
1388
|
-
*/
|
|
1389
|
-
tools(tools) {
|
|
1390
|
-
for (const t of tools)
|
|
1391
|
-
this.tool(t);
|
|
1392
|
-
return this;
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Wire a chainable `ToolProvider` (from `agentfootprint/tool-providers`)
|
|
1396
|
-
* as the agent's per-iteration tool source.
|
|
1397
|
-
*
|
|
1398
|
-
* The provider is consulted EVERY iteration via `provider.list(ctx)`
|
|
1399
|
-
* with `ctx = { iteration, activeSkillId, identity }`. Tools the
|
|
1400
|
-
* provider emits flow into the Tools slot alongside any static
|
|
1401
|
-
* tools registered via `.tool()` / `.tools()`. The tool-call
|
|
1402
|
-
* dispatcher also consults the provider so dynamic chains
|
|
1403
|
-
* (`gatedTools`, `skillScopedTools`) dispatch correctly when their
|
|
1404
|
-
* visible-set changes mid-turn.
|
|
1405
|
-
*
|
|
1406
|
-
* Throws if called more than once on the same builder (avoids
|
|
1407
|
-
* silent override surprises).
|
|
1408
|
-
*
|
|
1409
|
-
* @example Permission-gated baseline
|
|
1410
|
-
* import { gatedTools, staticTools } from 'agentfootprint/tool-providers';
|
|
1411
|
-
* import { PermissionPolicy } from 'agentfootprint/security';
|
|
1412
|
-
*
|
|
1413
|
-
* const policy = PermissionPolicy.fromRoles({
|
|
1414
|
-
* readonly: ['lookup', 'list_skills', 'read_skill'],
|
|
1415
|
-
* admin: ['lookup', 'list_skills', 'read_skill', 'delete'],
|
|
1416
|
-
* }, 'readonly');
|
|
1417
|
-
*
|
|
1418
|
-
* const provider = gatedTools(
|
|
1419
|
-
* staticTools(allTools),
|
|
1420
|
-
* (toolName) => policy.isAllowed(toolName),
|
|
1421
|
-
* );
|
|
1422
|
-
*
|
|
1423
|
-
* const agent = Agent.create({ provider: llm, model })
|
|
1424
|
-
* .system('You answer.')
|
|
1425
|
-
* .toolProvider(provider)
|
|
1426
|
-
* .build();
|
|
1427
|
-
*/
|
|
1428
|
-
toolProvider(provider) {
|
|
1429
|
-
if (this.toolProviderRef) {
|
|
1430
|
-
throw new Error('AgentBuilder.toolProvider: already set. Each agent has at most one external ToolProvider.');
|
|
1431
|
-
}
|
|
1432
|
-
this.toolProviderRef = provider;
|
|
1433
|
-
return this;
|
|
1434
|
-
}
|
|
1435
|
-
/**
|
|
1436
|
-
* Override the ReAct iteration cap set via `Agent.create({
|
|
1437
|
-
* maxIterations })`. Convenience for builder-style code that prefers
|
|
1438
|
-
* fluent setters over constructor opts. Last call wins.
|
|
1439
|
-
*
|
|
1440
|
-
* Throws if `n` is not a positive integer or exceeds the hard cap
|
|
1441
|
-
* (`clampIterations`'s upper bound).
|
|
1442
|
-
*/
|
|
1443
|
-
maxIterations(n) {
|
|
1444
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
1445
|
-
throw new Error(`AgentBuilder.maxIterations: expected a positive integer, got ${n}.`);
|
|
1446
|
-
}
|
|
1447
|
-
this.maxIterationsOverride = n;
|
|
1448
|
-
return this;
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Attach a footprintjs `CombinedRecorder` to the built Agent. Wired
|
|
1452
|
-
* via `agent.attach(rec)` immediately after construction, so the
|
|
1453
|
-
* recorder sees every event from the very first run.
|
|
1454
|
-
*
|
|
1455
|
-
* Equivalent to calling `agent.attach(rec)` post-build; the builder
|
|
1456
|
-
* method is a convenience for codebases that prefer fully-fluent
|
|
1457
|
-
* agent assembly. Multiple recorders are supported (each gets its
|
|
1458
|
-
* own `attach()` call).
|
|
1459
|
-
*/
|
|
1460
|
-
recorder(rec) {
|
|
1461
|
-
this.recorderList.push(rec);
|
|
1462
|
-
return this;
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* Set the agent's display name — substituted as `{{appName}}` in
|
|
1466
|
-
* commentary + thinking templates. Same place to brand a tenant
|
|
1467
|
-
* ("Acme Bot"), distinguish multi-agent roles ("Triage" vs
|
|
1468
|
-
* "Reviewer"), or localize ("Asistente"). Default: `'Chatbot'`.
|
|
1469
|
-
*/
|
|
1470
|
-
appName(name) {
|
|
1471
|
-
this.appNameValue = name;
|
|
1472
|
-
return this;
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* Override agentfootprint's bundled commentary templates. Spread on
|
|
1476
|
-
* top of `defaultCommentaryTemplates`; missing keys fall back. Same
|
|
1477
|
-
* `Record<string, string>` shape with `{{vars}}` substitution as
|
|
1478
|
-
* the bundled defaults — see `defaultCommentaryTemplates` for the
|
|
1479
|
-
* full key list.
|
|
1480
|
-
*
|
|
1481
|
-
* Use cases: i18n (`'agent.turn_start': 'El usuario...'`), brand
|
|
1482
|
-
* voice ("You: {{userPrompt}}"), per-tenant customization.
|
|
1483
|
-
*/
|
|
1484
|
-
commentaryTemplates(templates) {
|
|
1485
|
-
this.commentaryOverrides = { ...this.commentaryOverrides, ...templates };
|
|
1486
|
-
return this;
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Override agentfootprint's bundled thinking templates. Same
|
|
1490
|
-
* contract shape as commentary; different vocabulary — first-person
|
|
1491
|
-
* status the chat bubble shows mid-call. Per-tool overrides go via
|
|
1492
|
-
* `tool.<toolName>` keys (e.g., `'tool.weather': 'Looking up the
|
|
1493
|
-
* weather…'`). See `defaultThinkingTemplates` for the full key list.
|
|
1494
|
-
*/
|
|
1495
|
-
thinkingTemplates(templates) {
|
|
1496
|
-
this.thinkingOverrides = { ...this.thinkingOverrides, ...templates };
|
|
1497
|
-
return this;
|
|
1498
|
-
}
|
|
1499
|
-
// ─── Injection sugar — context engineering surface ───────────
|
|
1500
|
-
//
|
|
1501
|
-
// ALL of these push into the same `injectionList`. The Injection
|
|
1502
|
-
// primitive is identical across flavors; the methods are just
|
|
1503
|
-
// narrative-friendly aliases. Duplicate ids throw at build time.
|
|
1504
|
-
/**
|
|
1505
|
-
* Register any `Injection`. Use this for power-user / custom flavors;
|
|
1506
|
-
* for built-in flavors use the typed sugar (`.skill`, `.steering`,
|
|
1507
|
-
* `.instruction`, `.fact`).
|
|
1508
|
-
*/
|
|
1509
|
-
injection(injection) {
|
|
1510
|
-
if (this.injectionList.some((i) => i.id === injection.id)) {
|
|
1511
|
-
throw new Error(`Agent.injection(): duplicate id '${injection.id}'`);
|
|
1512
|
-
}
|
|
1513
|
-
this.injectionList.push(injection);
|
|
1514
|
-
return this;
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Register a Skill — LLM-activated, system-prompt + tools.
|
|
1518
|
-
* Auto-attaches the `read_skill` activation tool to the agent.
|
|
1519
|
-
* Skill stays active for the rest of the turn once activated.
|
|
1520
|
-
*/
|
|
1521
|
-
skill(injection) {
|
|
1522
|
-
return this.injection(injection);
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Bulk-register every Skill in a `SkillRegistry`. Use for shared
|
|
1526
|
-
* skill catalogs across multiple Agents — register skills once on
|
|
1527
|
-
* the registry; attach the same registry to every consumer Agent.
|
|
1528
|
-
*
|
|
1529
|
-
* @example
|
|
1530
|
-
* const registry = new SkillRegistry();
|
|
1531
|
-
* registry.register(billingSkill).register(refundSkill);
|
|
1532
|
-
* const supportAgent = Agent.create({ provider }).skills(registry).build();
|
|
1533
|
-
* const escalationAgent = Agent.create({ provider }).skills(registry).build();
|
|
1534
|
-
*/
|
|
1535
|
-
skills(registry) {
|
|
1536
|
-
for (const skill of registry.list())
|
|
1537
|
-
this.injection(skill);
|
|
1538
|
-
return this;
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Register a Steering doc — always-on system-prompt rule.
|
|
1542
|
-
* Use for invariant guidance: output format, persona, safety policies.
|
|
1543
|
-
*/
|
|
1544
|
-
steering(injection) {
|
|
1545
|
-
return this.injection(injection);
|
|
1546
|
-
}
|
|
1547
|
-
/**
|
|
1548
|
-
* Register an Instruction — rule-based system-prompt guidance.
|
|
1549
|
-
* Predicate runs each iteration. Use for context-dependent rules
|
|
1550
|
-
* including the "Dynamic ReAct" `on-tool-return` pattern.
|
|
1551
|
-
*/
|
|
1552
|
-
instruction(injection) {
|
|
1553
|
-
return this.injection(injection);
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Bulk-register many instructions at once. Convenience for consumer
|
|
1557
|
-
* code that organizes its instruction set in a flat array (`const
|
|
1558
|
-
* instructions = [outputFormat, dataRouting, ...]`). Each element
|
|
1559
|
-
* is registered via `.instruction()` so duplicate-id checks still
|
|
1560
|
-
* fire per-entry.
|
|
1561
|
-
*/
|
|
1562
|
-
instructions(injections) {
|
|
1563
|
-
for (const i of injections)
|
|
1564
|
-
this.instruction(i);
|
|
1565
|
-
return this;
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Register a Fact — developer-supplied data the LLM should see.
|
|
1569
|
-
* User profile, env info, computed summary, current time, …
|
|
1570
|
-
* Distinct from Skills (LLM-activated guidance) and Steering
|
|
1571
|
-
* (always-on rules) in INTENT — the engine treats them all alike.
|
|
1572
|
-
*/
|
|
1573
|
-
fact(injection) {
|
|
1574
|
-
return this.injection(injection);
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Register a Memory subsystem — load/persist conversation context,
|
|
1578
|
-
* facts, narrative beats, or causal snapshots across runs.
|
|
1579
|
-
*
|
|
1580
|
-
* The `MemoryDefinition` is produced by `defineMemory({ type, strategy,
|
|
1581
|
-
* store })`. Multiple memories layer cleanly via per-id scope keys
|
|
1582
|
-
* (`memoryInjection_${id}`):
|
|
1583
|
-
*
|
|
1584
|
-
* ```ts
|
|
1585
|
-
* Agent.create({ provider })
|
|
1586
|
-
* .memory(defineMemory({ id: 'short', type: MEMORY_TYPES.EPISODIC,
|
|
1587
|
-
* strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
|
|
1588
|
-
* store }))
|
|
1589
|
-
* .memory(defineMemory({ id: 'facts', type: MEMORY_TYPES.SEMANTIC,
|
|
1590
|
-
* strategy: { kind: MEMORY_STRATEGIES.EXTRACT,
|
|
1591
|
-
* extractor: 'pattern' }, store }))
|
|
1592
|
-
* .build();
|
|
1593
|
-
* ```
|
|
1594
|
-
*
|
|
1595
|
-
* The READ subflow runs at the configured `timing` (default
|
|
1596
|
-
* `MEMORY_TIMING.TURN_START`) and writes its formatted output to the
|
|
1597
|
-
* `memoryInjection_${id}` scope key for the slot subflows to consume.
|
|
1598
|
-
*/
|
|
1599
|
-
memory(definition) {
|
|
1600
|
-
if (this.memoryList.some((m) => m.id === definition.id)) {
|
|
1601
|
-
throw new Error(`Agent.memory(): duplicate id '${definition.id}' — each memory needs a unique id ` +
|
|
1602
|
-
'to keep its scope key (`memoryInjection_${id}`) collision-free.');
|
|
1603
|
-
}
|
|
1604
|
-
this.memoryList.push(definition);
|
|
1605
|
-
return this;
|
|
1606
|
-
}
|
|
1607
|
-
/**
|
|
1608
|
-
* Register a RAG retriever — semantic search over a vector-indexed
|
|
1609
|
-
* corpus. Identical plumbing to `.memory()` (RAG resolves to a
|
|
1610
|
-
* `MemoryDefinition` produced by `defineRAG()`); this alias exists
|
|
1611
|
-
* so the consumer's intent reads clearly:
|
|
1612
|
-
*
|
|
1613
|
-
* ```ts
|
|
1614
|
-
* agent
|
|
1615
|
-
* .memory(shortTermConversation) // remembers what the USER said
|
|
1616
|
-
* .rag(productDocs) // retrieves what the CORPUS says
|
|
1617
|
-
* .build();
|
|
1618
|
-
* ```
|
|
1619
|
-
*
|
|
1620
|
-
* Both end up as memory subflows, but the alias separates "user
|
|
1621
|
-
* conversation memory" from "document corpus retrieval" in code
|
|
1622
|
-
* intent, ids, and Lens chips.
|
|
1623
|
-
*/
|
|
1624
|
-
rag(definition) {
|
|
1625
|
-
return this.memory(definition);
|
|
1626
|
-
}
|
|
1627
|
-
/**
|
|
1628
|
-
* Declarative terminal contract. The agent's final answer must be
|
|
1629
|
-
* JSON matching `parser`. Auto-injects a system-prompt instruction
|
|
1630
|
-
* telling the LLM the shape, and exposes `agent.runTyped()` /
|
|
1631
|
-
* `agent.parseOutput()` for parse + validate at the call site.
|
|
1632
|
-
*
|
|
1633
|
-
* The `parser` is duck-typed: any object with a `parse(unknown): T`
|
|
1634
|
-
* method works (Zod, Valibot, ArkType, hand-written). The optional
|
|
1635
|
-
* `description` field on the parser drives the auto-generated
|
|
1636
|
-
* instruction; consumers can also override via `opts.instruction`.
|
|
1637
|
-
*
|
|
1638
|
-
* Throws if called more than once on the same builder (avoids
|
|
1639
|
-
* silent override surprises).
|
|
1640
|
-
*
|
|
1641
|
-
* @param parser Validation strategy that throws on shape failure.
|
|
1642
|
-
* @param opts Optional `{ name, instruction }` to customize.
|
|
1643
|
-
*
|
|
1644
|
-
* @example
|
|
1645
|
-
* import { z } from 'zod';
|
|
1646
|
-
* const Output = z.object({
|
|
1647
|
-
* status: z.enum(['ok', 'err']),
|
|
1648
|
-
* items: z.array(z.string()),
|
|
1649
|
-
* }).describe('A status enum + an array of strings.');
|
|
1650
|
-
*
|
|
1651
|
-
* const agent = Agent.create({...})
|
|
1652
|
-
* .outputSchema(Output)
|
|
1653
|
-
* .build();
|
|
1654
|
-
*
|
|
1655
|
-
* const typed = await agent.runTyped({ message: '...' });
|
|
1656
|
-
* typed.status; // narrowed to 'ok' | 'err'
|
|
1657
|
-
*/
|
|
1658
|
-
outputSchema(parser, opts) {
|
|
1659
|
-
if (this.outputSchemaParser) {
|
|
1660
|
-
throw new Error('AgentBuilder.outputSchema: already set. Each agent has at most one terminal contract.');
|
|
1661
|
-
}
|
|
1662
|
-
this.outputSchemaParser = parser;
|
|
1663
|
-
const instructionText = opts?.instruction ?? (0, outputSchema_js_1.buildDefaultInstruction)(parser);
|
|
1664
|
-
const id = opts?.name ?? 'output-schema';
|
|
1665
|
-
// Always-on system-slot instruction. Activates every iteration so
|
|
1666
|
-
// long runs keep the contract present (recency-first redundancy).
|
|
1667
|
-
this.injectionList.push((0, defineInstruction_js_1.defineInstruction)({
|
|
1668
|
-
id,
|
|
1669
|
-
activeWhen: () => true,
|
|
1670
|
-
prompt: instructionText,
|
|
1671
|
-
}));
|
|
1672
|
-
return this;
|
|
1673
|
-
}
|
|
1674
|
-
/**
|
|
1675
|
-
* 3-tier degradation for output-schema validation failures. Pairs
|
|
1676
|
-
* with `.outputSchema()` — calling `.outputFallback()` without an
|
|
1677
|
-
* `outputSchema` first throws (the fallback has nothing to validate).
|
|
1678
|
-
*
|
|
1679
|
-
* Three tiers:
|
|
1680
|
-
*
|
|
1681
|
-
* 1. **Primary** — LLM emitted schema-valid JSON. Caller gets it.
|
|
1682
|
-
* 2. **Fallback** — `OutputSchemaError` thrown. The async
|
|
1683
|
-
* `fallback(error, raw)` runs; its return is re-validated.
|
|
1684
|
-
* 3. **Canned** — static safety-net value. NEVER throws when set.
|
|
1685
|
-
*
|
|
1686
|
-
* `canned` is validated against the schema at builder time —
|
|
1687
|
-
* fail-fast on misconfig (a `canned` that doesn't validate would
|
|
1688
|
-
* defeat the fail-open guarantee).
|
|
1689
|
-
*
|
|
1690
|
-
* Two typed events fire on tier transitions for observability:
|
|
1691
|
-
* - `agentfootprint.resilience.output_fallback_triggered`
|
|
1692
|
-
* - `agentfootprint.resilience.output_canned_used`
|
|
1693
|
-
*
|
|
1694
|
-
* @example
|
|
1695
|
-
* ```ts
|
|
1696
|
-
* import { z } from 'zod';
|
|
1697
|
-
* const Refund = z.object({ amount: z.number(), reason: z.string() });
|
|
1698
|
-
*
|
|
1699
|
-
* const agent = Agent.create({...})
|
|
1700
|
-
* .outputSchema(Refund)
|
|
1701
|
-
* .outputFallback({
|
|
1702
|
-
* fallback: async (err, raw) => ({ amount: 0, reason: 'manual review' }),
|
|
1703
|
-
* canned: { amount: 0, reason: 'unable to process' },
|
|
1704
|
-
* })
|
|
1705
|
-
* .build();
|
|
1706
|
-
* ```
|
|
1707
|
-
*/
|
|
1708
|
-
outputFallback(options) {
|
|
1709
|
-
if (!this.outputSchemaParser) {
|
|
1710
|
-
throw new Error('AgentBuilder.outputFallback: call .outputSchema(parser) FIRST. ' +
|
|
1711
|
-
'outputFallback supplements outputSchema; one without the other is incoherent.');
|
|
1712
|
-
}
|
|
1713
|
-
if (this.outputFallbackCfg) {
|
|
1714
|
-
throw new Error('AgentBuilder.outputFallback: already set. Each agent has at most one fallback chain.');
|
|
1715
|
-
}
|
|
1716
|
-
// Build-time validation — canned MUST satisfy the schema.
|
|
1717
|
-
if (options.canned !== undefined) {
|
|
1718
|
-
(0, outputFallback_js_1.validateCannedAgainstSchema)(options.canned, this.outputSchemaParser);
|
|
1719
|
-
}
|
|
1720
|
-
this.outputFallbackCfg = {
|
|
1721
|
-
fallback: options.fallback,
|
|
1722
|
-
...(options.canned !== undefined && { canned: options.canned }),
|
|
1723
|
-
hasCanned: options.canned !== undefined,
|
|
1724
|
-
};
|
|
1725
|
-
return this;
|
|
1726
|
-
}
|
|
1727
|
-
build() {
|
|
1728
|
-
// Resolve the voice config: bundled defaults + consumer overrides.
|
|
1729
|
-
// Templates flow through the same barrel exports the rest of the
|
|
1730
|
-
// library uses, so a future locale-pack swap is a single import.
|
|
1731
|
-
const voice = {
|
|
1732
|
-
appName: this.appNameValue,
|
|
1733
|
-
commentaryTemplates: { ...commentaryTemplates_js_1.defaultCommentaryTemplates, ...this.commentaryOverrides },
|
|
1734
|
-
thinkingTemplates: { ...thinkingTemplates_js_1.defaultThinkingTemplates, ...this.thinkingOverrides },
|
|
1735
|
-
};
|
|
1736
|
-
const opts = this.maxIterationsOverride !== undefined
|
|
1737
|
-
? { ...this.opts, maxIterations: this.maxIterationsOverride }
|
|
1738
|
-
: this.opts;
|
|
1739
|
-
const agent = new Agent(opts, this.systemPromptValue, this.registry, voice, this.injectionList, this.memoryList, this.outputSchemaParser, this.toolProviderRef, this.systemPromptCachePolicy, this.cachingDisabledValue, this.cacheStrategyOverride, this.outputFallbackCfg);
|
|
1740
|
-
// Attach builder-collected recorders so they receive events from
|
|
1741
|
-
// the very first run. Mirrors what consumers would do post-build
|
|
1742
|
-
// via `agent.attach(rec)`; the builder method is purely sugar.
|
|
1743
|
-
for (const rec of this.recorderList) {
|
|
1744
|
-
agent.attach(rec);
|
|
1745
|
-
}
|
|
1746
|
-
return agent;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
exports.AgentBuilder = AgentBuilder;
|
|
591
|
+
// AgentBuilder extracted to ./agent/AgentBuilder.ts (v2.11.2).
|
|
592
|
+
// Re-export so the 28+ existing import sites continue to work unchanged.
|
|
1750
593
|
// Validators + helpers extracted to ./agent/validators.ts (v2.11.1).
|
|
1751
594
|
//# sourceMappingURL=Agent.js.map
|