llmist 6.1.0 → 7.0.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 +6 -1
- package/dist/{chunk-7BJX376V.js → chunk-5KEZ7SQX.js} +13 -25
- package/dist/chunk-5KEZ7SQX.js.map +1 -0
- package/dist/{chunk-VAJLPRJ6.js → chunk-SFZIL2VR.js} +410 -493
- package/dist/chunk-SFZIL2VR.js.map +1 -0
- package/dist/cli.cjs +11533 -11843
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5528 -5751
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +5779 -5872
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -299
- package/dist/index.d.ts +75 -299
- package/dist/index.js +5 -3
- package/dist/{mock-stream-Cq1Sxezz.d.cts → mock-stream-r5vjy2Iq.d.cts} +1103 -739
- package/dist/{mock-stream-Cq1Sxezz.d.ts → mock-stream-r5vjy2Iq.d.ts} +1103 -739
- package/dist/testing/index.cjs +401 -486
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-7BJX376V.js.map +0 -1
- package/dist/chunk-VAJLPRJ6.js.map +0 -1
package/dist/testing/index.cjs
CHANGED
|
@@ -52,6 +52,9 @@ function parseEnvBoolean(value) {
|
|
|
52
52
|
if (normalized === "false" || normalized === "0") return false;
|
|
53
53
|
return void 0;
|
|
54
54
|
}
|
|
55
|
+
function stripAnsi(str) {
|
|
56
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
57
|
+
}
|
|
55
58
|
function createLogger(options = {}) {
|
|
56
59
|
const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
|
|
57
60
|
const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
|
|
@@ -60,36 +63,62 @@ function createLogger(options = {}) {
|
|
|
60
63
|
const defaultType = options.type ?? "pretty";
|
|
61
64
|
const name = options.name ?? "llmist";
|
|
62
65
|
const logReset = options.logReset ?? envLogReset ?? false;
|
|
63
|
-
|
|
64
|
-
let finalType = defaultType;
|
|
65
|
-
if (envLogFile) {
|
|
66
|
+
if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
|
|
66
67
|
try {
|
|
68
|
+
if (sharedLogFileStream) {
|
|
69
|
+
sharedLogFileStream.end();
|
|
70
|
+
sharedLogFileStream = void 0;
|
|
71
|
+
}
|
|
67
72
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(envLogFile), { recursive: true });
|
|
68
73
|
const flags = logReset ? "w" : "a";
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
sharedLogFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags });
|
|
75
|
+
sharedLogFilePath = envLogFile;
|
|
76
|
+
logFileInitialized = true;
|
|
77
|
+
writeErrorCount = 0;
|
|
78
|
+
writeErrorReported = false;
|
|
79
|
+
sharedLogFileStream.on("error", (error) => {
|
|
80
|
+
writeErrorCount++;
|
|
81
|
+
if (!writeErrorReported) {
|
|
82
|
+
console.error(`[llmist] Log file write error: ${error.message}`);
|
|
83
|
+
writeErrorReported = true;
|
|
84
|
+
}
|
|
85
|
+
if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
|
|
86
|
+
console.error(
|
|
87
|
+
`[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
|
|
88
|
+
);
|
|
89
|
+
sharedLogFileStream?.end();
|
|
90
|
+
sharedLogFileStream = void 0;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
71
93
|
} catch (error) {
|
|
72
94
|
console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
|
|
73
95
|
}
|
|
74
96
|
}
|
|
97
|
+
const useFileLogging = Boolean(sharedLogFileStream);
|
|
75
98
|
const logger = new import_tslog.Logger({
|
|
76
99
|
name,
|
|
77
100
|
minLevel,
|
|
78
|
-
type:
|
|
79
|
-
//
|
|
80
|
-
hideLogPositionForProduction:
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
type: useFileLogging ? "pretty" : defaultType,
|
|
102
|
+
// Hide log position for file logging and non-pretty types
|
|
103
|
+
hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
|
|
104
|
+
prettyLogTemplate: LOG_TEMPLATE,
|
|
105
|
+
// Use overwrite to redirect tslog's formatted output to file instead of console
|
|
106
|
+
overwrite: useFileLogging ? {
|
|
107
|
+
transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
|
|
108
|
+
if (!sharedLogFileStream) return;
|
|
109
|
+
const meta = stripAnsi(logMetaMarkup);
|
|
110
|
+
const args = logArgs.map(
|
|
111
|
+
(arg) => typeof arg === "string" ? stripAnsi(arg) : JSON.stringify(arg)
|
|
112
|
+
);
|
|
113
|
+
const line = `${meta}${args.join(" ")}
|
|
114
|
+
`;
|
|
115
|
+
sharedLogFileStream.write(line);
|
|
116
|
+
}
|
|
117
|
+
} : void 0
|
|
83
118
|
});
|
|
84
|
-
if (logFileStream) {
|
|
85
|
-
logger.attachTransport((logObj) => {
|
|
86
|
-
logFileStream?.write(`${JSON.stringify(logObj)}
|
|
87
|
-
`);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
119
|
return logger;
|
|
91
120
|
}
|
|
92
|
-
var import_node_fs, import_node_path, import_tslog, LEVEL_NAME_TO_ID, defaultLogger;
|
|
121
|
+
var import_node_fs, import_node_path, import_tslog, LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
|
|
93
122
|
var init_logger = __esm({
|
|
94
123
|
"src/logging/logger.ts"() {
|
|
95
124
|
"use strict";
|
|
@@ -105,6 +134,11 @@ var init_logger = __esm({
|
|
|
105
134
|
error: 5,
|
|
106
135
|
fatal: 6
|
|
107
136
|
};
|
|
137
|
+
logFileInitialized = false;
|
|
138
|
+
writeErrorCount = 0;
|
|
139
|
+
writeErrorReported = false;
|
|
140
|
+
MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
|
|
141
|
+
LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
|
|
108
142
|
defaultLogger = createLogger();
|
|
109
143
|
}
|
|
110
144
|
});
|
|
@@ -3795,9 +3829,8 @@ var init_cost_reporting_client = __esm({
|
|
|
3795
3829
|
*/
|
|
3796
3830
|
reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
|
|
3797
3831
|
if (inputTokens === 0 && outputTokens === 0) return;
|
|
3798
|
-
const modelName = model.includes(":") ? model.split(":")[1] : model;
|
|
3799
3832
|
const estimate = this.client.modelRegistry.estimateCost(
|
|
3800
|
-
|
|
3833
|
+
model,
|
|
3801
3834
|
inputTokens,
|
|
3802
3835
|
outputTokens,
|
|
3803
3836
|
cachedInputTokens,
|
|
@@ -4080,18 +4113,64 @@ var init_parser = __esm({
|
|
|
4080
4113
|
}
|
|
4081
4114
|
});
|
|
4082
4115
|
|
|
4116
|
+
// src/gadgets/typed-gadget.ts
|
|
4117
|
+
function Gadget(config) {
|
|
4118
|
+
class GadgetBase extends AbstractGadget {
|
|
4119
|
+
description = config.description;
|
|
4120
|
+
parameterSchema = config.schema;
|
|
4121
|
+
name = config.name;
|
|
4122
|
+
timeoutMs = config.timeoutMs;
|
|
4123
|
+
examples = config.examples;
|
|
4124
|
+
/**
|
|
4125
|
+
* Type helper property for accessing inferred parameter type.
|
|
4126
|
+
* This is used in the execute method signature: `execute(params: this['params'])`
|
|
4127
|
+
*
|
|
4128
|
+
* Note: This is just for type inference - the actual params in execute()
|
|
4129
|
+
* will be Record<string, unknown> which you can safely cast to this['params']
|
|
4130
|
+
*/
|
|
4131
|
+
params;
|
|
4132
|
+
}
|
|
4133
|
+
return GadgetBase;
|
|
4134
|
+
}
|
|
4135
|
+
var init_typed_gadget = __esm({
|
|
4136
|
+
"src/gadgets/typed-gadget.ts"() {
|
|
4137
|
+
"use strict";
|
|
4138
|
+
init_gadget();
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
|
|
4083
4142
|
// src/gadgets/executor.ts
|
|
4084
|
-
|
|
4143
|
+
function getHostExportsInternal() {
|
|
4144
|
+
if (!cachedHostExports) {
|
|
4145
|
+
cachedHostExports = {
|
|
4146
|
+
AgentBuilder,
|
|
4147
|
+
Gadget,
|
|
4148
|
+
createGadget,
|
|
4149
|
+
ExecutionTree,
|
|
4150
|
+
LLMist,
|
|
4151
|
+
z: import_zod2.z
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
return cachedHostExports;
|
|
4155
|
+
}
|
|
4156
|
+
var import_fast_deep_equal, import_zod2, cachedHostExports, GadgetExecutor;
|
|
4085
4157
|
var init_executor = __esm({
|
|
4086
4158
|
"src/gadgets/executor.ts"() {
|
|
4087
4159
|
"use strict";
|
|
4160
|
+
import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
|
|
4161
|
+
import_zod2 = require("zod");
|
|
4162
|
+
init_builder();
|
|
4163
|
+
init_client();
|
|
4088
4164
|
init_constants();
|
|
4165
|
+
init_execution_tree();
|
|
4089
4166
|
init_logger();
|
|
4090
4167
|
init_block_params();
|
|
4091
4168
|
init_cost_reporting_client();
|
|
4169
|
+
init_create_gadget();
|
|
4092
4170
|
init_error_formatter();
|
|
4093
4171
|
init_exceptions();
|
|
4094
4172
|
init_parser();
|
|
4173
|
+
init_typed_gadget();
|
|
4095
4174
|
GadgetExecutor = class {
|
|
4096
4175
|
constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
|
|
4097
4176
|
this.registry = registry;
|
|
@@ -4189,7 +4268,7 @@ var init_executor = __esm({
|
|
|
4189
4268
|
try {
|
|
4190
4269
|
const cleanedRaw = stripMarkdownFences(call.parametersRaw);
|
|
4191
4270
|
const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
|
|
4192
|
-
const parametersWereModified = !
|
|
4271
|
+
const parametersWereModified = !(0, import_fast_deep_equal.default)(rawParameters, initialParse);
|
|
4193
4272
|
if (parametersWereModified) {
|
|
4194
4273
|
this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
|
|
4195
4274
|
gadgetName: call.gadgetName
|
|
@@ -4264,7 +4343,11 @@ var init_executor = __esm({
|
|
|
4264
4343
|
// Tree context for subagent support - use gadget's own node ID
|
|
4265
4344
|
tree: this.tree,
|
|
4266
4345
|
nodeId: gadgetNodeId,
|
|
4267
|
-
depth: gadgetDepth
|
|
4346
|
+
depth: gadgetDepth,
|
|
4347
|
+
// Host exports for external gadgets to use host's llmist classes
|
|
4348
|
+
hostExports: getHostExportsInternal(),
|
|
4349
|
+
// Logger for structured logging (respects CLI's log level/file config)
|
|
4350
|
+
logger: this.logger
|
|
4268
4351
|
};
|
|
4269
4352
|
let rawResult;
|
|
4270
4353
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -4434,27 +4517,6 @@ var init_executor = __esm({
|
|
|
4434
4517
|
async executeAll(calls) {
|
|
4435
4518
|
return Promise.all(calls.map((call) => this.execute(call)));
|
|
4436
4519
|
}
|
|
4437
|
-
/**
|
|
4438
|
-
* Deep equality check for objects/arrays.
|
|
4439
|
-
* Used to detect if parameters were modified by an interceptor.
|
|
4440
|
-
*/
|
|
4441
|
-
deepEquals(a, b) {
|
|
4442
|
-
if (a === b) return true;
|
|
4443
|
-
if (a === null || b === null) return a === b;
|
|
4444
|
-
if (typeof a !== typeof b) return false;
|
|
4445
|
-
if (typeof a !== "object") return a === b;
|
|
4446
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
4447
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4448
|
-
if (a.length !== b.length) return false;
|
|
4449
|
-
return a.every((val, i) => this.deepEquals(val, b[i]));
|
|
4450
|
-
}
|
|
4451
|
-
const aObj = a;
|
|
4452
|
-
const bObj = b;
|
|
4453
|
-
const aKeys = Object.keys(aObj);
|
|
4454
|
-
const bKeys = Object.keys(bObj);
|
|
4455
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
4456
|
-
return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
|
|
4457
|
-
}
|
|
4458
4520
|
};
|
|
4459
4521
|
}
|
|
4460
4522
|
});
|
|
@@ -4492,6 +4554,11 @@ var init_stream_processor = __esm({
|
|
|
4492
4554
|
inFlightExecutions = /* @__PURE__ */ new Map();
|
|
4493
4555
|
/** Queue of completed gadget results ready to be yielded (for real-time streaming) */
|
|
4494
4556
|
completedResultsQueue = [];
|
|
4557
|
+
// Cross-iteration dependency tracking
|
|
4558
|
+
/** Invocation IDs completed in previous iterations (read-only reference from Agent) */
|
|
4559
|
+
priorCompletedInvocations;
|
|
4560
|
+
/** Invocation IDs that failed in previous iterations (read-only reference from Agent) */
|
|
4561
|
+
priorFailedInvocations;
|
|
4495
4562
|
constructor(options) {
|
|
4496
4563
|
this.iteration = options.iteration;
|
|
4497
4564
|
this.registry = options.registry;
|
|
@@ -4500,6 +4567,8 @@ var init_stream_processor = __esm({
|
|
|
4500
4567
|
this.tree = options.tree;
|
|
4501
4568
|
this.parentNodeId = options.parentNodeId ?? null;
|
|
4502
4569
|
this.baseDepth = options.baseDepth ?? 0;
|
|
4570
|
+
this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
|
|
4571
|
+
this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
|
|
4503
4572
|
this.parser = new GadgetCallParser({
|
|
4504
4573
|
startPrefix: options.gadgetStartPrefix,
|
|
4505
4574
|
endPrefix: options.gadgetEndPrefix,
|
|
@@ -4698,62 +4767,11 @@ var init_stream_processor = __esm({
|
|
|
4698
4767
|
}
|
|
4699
4768
|
return [{ type: "text", content }];
|
|
4700
4769
|
}
|
|
4701
|
-
/**
|
|
4702
|
-
* Process a gadget call through the full lifecycle, handling dependencies.
|
|
4703
|
-
*
|
|
4704
|
-
* Gadgets without dependencies (or with all dependencies satisfied) execute immediately.
|
|
4705
|
-
* Gadgets with unsatisfied dependencies are queued for later execution.
|
|
4706
|
-
* After each execution, pending gadgets are checked to see if they can now run.
|
|
4707
|
-
*/
|
|
4708
|
-
async processGadgetCall(call) {
|
|
4709
|
-
const events = [];
|
|
4710
|
-
events.push({ type: "gadget_call", call });
|
|
4711
|
-
if (call.dependencies.length > 0) {
|
|
4712
|
-
if (call.dependencies.includes(call.invocationId)) {
|
|
4713
|
-
this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
|
|
4714
|
-
gadgetName: call.gadgetName,
|
|
4715
|
-
invocationId: call.invocationId
|
|
4716
|
-
});
|
|
4717
|
-
this.failedInvocations.add(call.invocationId);
|
|
4718
|
-
const skipEvent = {
|
|
4719
|
-
type: "gadget_skipped",
|
|
4720
|
-
gadgetName: call.gadgetName,
|
|
4721
|
-
invocationId: call.invocationId,
|
|
4722
|
-
parameters: call.parameters ?? {},
|
|
4723
|
-
failedDependency: call.invocationId,
|
|
4724
|
-
failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
|
|
4725
|
-
};
|
|
4726
|
-
events.push(skipEvent);
|
|
4727
|
-
return events;
|
|
4728
|
-
}
|
|
4729
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
4730
|
-
if (failedDep) {
|
|
4731
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
4732
|
-
events.push(...skipEvents);
|
|
4733
|
-
return events;
|
|
4734
|
-
}
|
|
4735
|
-
const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
4736
|
-
if (unsatisfied.length > 0) {
|
|
4737
|
-
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
4738
|
-
gadgetName: call.gadgetName,
|
|
4739
|
-
invocationId: call.invocationId,
|
|
4740
|
-
waitingOn: unsatisfied
|
|
4741
|
-
});
|
|
4742
|
-
this.gadgetsAwaitingDependencies.set(call.invocationId, call);
|
|
4743
|
-
return events;
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
const executeEvents = await this.executeGadgetWithHooks(call);
|
|
4747
|
-
events.push(...executeEvents);
|
|
4748
|
-
const triggeredEvents = await this.processPendingGadgets();
|
|
4749
|
-
events.push(...triggeredEvents);
|
|
4750
|
-
return events;
|
|
4751
|
-
}
|
|
4752
4770
|
/**
|
|
4753
4771
|
* Process a gadget call, yielding events in real-time.
|
|
4754
4772
|
*
|
|
4755
|
-
*
|
|
4756
|
-
*
|
|
4773
|
+
* Yields gadget_call event IMMEDIATELY when parsed (before execution),
|
|
4774
|
+
* enabling real-time UI feedback.
|
|
4757
4775
|
*/
|
|
4758
4776
|
async *processGadgetCallGenerator(call) {
|
|
4759
4777
|
yield { type: "gadget_call", call };
|
|
@@ -4784,7 +4802,9 @@ var init_stream_processor = __esm({
|
|
|
4784
4802
|
yield skipEvent;
|
|
4785
4803
|
return;
|
|
4786
4804
|
}
|
|
4787
|
-
const failedDep = call.dependencies.find(
|
|
4805
|
+
const failedDep = call.dependencies.find(
|
|
4806
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
4807
|
+
);
|
|
4788
4808
|
if (failedDep) {
|
|
4789
4809
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
4790
4810
|
for (const evt of skipEvents) {
|
|
@@ -4792,7 +4812,9 @@ var init_stream_processor = __esm({
|
|
|
4792
4812
|
}
|
|
4793
4813
|
return;
|
|
4794
4814
|
}
|
|
4795
|
-
const unsatisfied = call.dependencies.filter(
|
|
4815
|
+
const unsatisfied = call.dependencies.filter(
|
|
4816
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
4817
|
+
);
|
|
4796
4818
|
if (unsatisfied.length > 0) {
|
|
4797
4819
|
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
4798
4820
|
gadgetName: call.gadgetName,
|
|
@@ -4814,143 +4836,9 @@ var init_stream_processor = __esm({
|
|
|
4814
4836
|
this.inFlightExecutions.set(call.invocationId, executionPromise);
|
|
4815
4837
|
}
|
|
4816
4838
|
/**
|
|
4817
|
-
* Execute a gadget through the full hook lifecycle.
|
|
4818
|
-
*
|
|
4819
|
-
*
|
|
4820
|
-
*/
|
|
4821
|
-
async executeGadgetWithHooks(call) {
|
|
4822
|
-
const events = [];
|
|
4823
|
-
if (call.parseError) {
|
|
4824
|
-
this.logger.warn("Gadget has parse error", {
|
|
4825
|
-
gadgetName: call.gadgetName,
|
|
4826
|
-
error: call.parseError,
|
|
4827
|
-
rawParameters: call.parametersRaw
|
|
4828
|
-
});
|
|
4829
|
-
}
|
|
4830
|
-
let parameters = call.parameters ?? {};
|
|
4831
|
-
if (this.hooks.interceptors?.interceptGadgetParameters) {
|
|
4832
|
-
const context = {
|
|
4833
|
-
iteration: this.iteration,
|
|
4834
|
-
gadgetName: call.gadgetName,
|
|
4835
|
-
invocationId: call.invocationId,
|
|
4836
|
-
logger: this.logger
|
|
4837
|
-
};
|
|
4838
|
-
parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
|
|
4839
|
-
}
|
|
4840
|
-
call.parameters = parameters;
|
|
4841
|
-
let shouldSkip = false;
|
|
4842
|
-
let syntheticResult;
|
|
4843
|
-
if (this.hooks.controllers?.beforeGadgetExecution) {
|
|
4844
|
-
const context = {
|
|
4845
|
-
iteration: this.iteration,
|
|
4846
|
-
gadgetName: call.gadgetName,
|
|
4847
|
-
invocationId: call.invocationId,
|
|
4848
|
-
parameters,
|
|
4849
|
-
logger: this.logger
|
|
4850
|
-
};
|
|
4851
|
-
const action = await this.hooks.controllers.beforeGadgetExecution(context);
|
|
4852
|
-
validateBeforeGadgetExecutionAction(action);
|
|
4853
|
-
if (action.action === "skip") {
|
|
4854
|
-
shouldSkip = true;
|
|
4855
|
-
syntheticResult = action.syntheticResult;
|
|
4856
|
-
this.logger.info("Controller skipped gadget execution", {
|
|
4857
|
-
gadgetName: call.gadgetName
|
|
4858
|
-
});
|
|
4859
|
-
}
|
|
4860
|
-
}
|
|
4861
|
-
const startObservers = [];
|
|
4862
|
-
if (this.hooks.observers?.onGadgetExecutionStart) {
|
|
4863
|
-
startObservers.push(async () => {
|
|
4864
|
-
const context = {
|
|
4865
|
-
iteration: this.iteration,
|
|
4866
|
-
gadgetName: call.gadgetName,
|
|
4867
|
-
invocationId: call.invocationId,
|
|
4868
|
-
parameters,
|
|
4869
|
-
logger: this.logger
|
|
4870
|
-
};
|
|
4871
|
-
await this.hooks.observers?.onGadgetExecutionStart?.(context);
|
|
4872
|
-
});
|
|
4873
|
-
}
|
|
4874
|
-
await this.runObserversInParallel(startObservers);
|
|
4875
|
-
let result;
|
|
4876
|
-
if (shouldSkip) {
|
|
4877
|
-
result = {
|
|
4878
|
-
gadgetName: call.gadgetName,
|
|
4879
|
-
invocationId: call.invocationId,
|
|
4880
|
-
parameters,
|
|
4881
|
-
result: syntheticResult ?? "Execution skipped",
|
|
4882
|
-
executionTimeMs: 0
|
|
4883
|
-
};
|
|
4884
|
-
} else {
|
|
4885
|
-
result = await this.executor.execute(call);
|
|
4886
|
-
}
|
|
4887
|
-
const originalResult = result.result;
|
|
4888
|
-
if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
|
|
4889
|
-
const context = {
|
|
4890
|
-
iteration: this.iteration,
|
|
4891
|
-
gadgetName: result.gadgetName,
|
|
4892
|
-
invocationId: result.invocationId,
|
|
4893
|
-
parameters,
|
|
4894
|
-
executionTimeMs: result.executionTimeMs,
|
|
4895
|
-
logger: this.logger
|
|
4896
|
-
};
|
|
4897
|
-
result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
|
|
4898
|
-
}
|
|
4899
|
-
if (this.hooks.controllers?.afterGadgetExecution) {
|
|
4900
|
-
const context = {
|
|
4901
|
-
iteration: this.iteration,
|
|
4902
|
-
gadgetName: result.gadgetName,
|
|
4903
|
-
invocationId: result.invocationId,
|
|
4904
|
-
parameters,
|
|
4905
|
-
result: result.result,
|
|
4906
|
-
error: result.error,
|
|
4907
|
-
executionTimeMs: result.executionTimeMs,
|
|
4908
|
-
logger: this.logger
|
|
4909
|
-
};
|
|
4910
|
-
const action = await this.hooks.controllers.afterGadgetExecution(context);
|
|
4911
|
-
validateAfterGadgetExecutionAction(action);
|
|
4912
|
-
if (action.action === "recover" && result.error) {
|
|
4913
|
-
this.logger.info("Controller recovered from gadget error", {
|
|
4914
|
-
gadgetName: result.gadgetName,
|
|
4915
|
-
originalError: result.error
|
|
4916
|
-
});
|
|
4917
|
-
result = {
|
|
4918
|
-
...result,
|
|
4919
|
-
error: void 0,
|
|
4920
|
-
result: action.fallbackResult
|
|
4921
|
-
};
|
|
4922
|
-
}
|
|
4923
|
-
}
|
|
4924
|
-
const completeObservers = [];
|
|
4925
|
-
if (this.hooks.observers?.onGadgetExecutionComplete) {
|
|
4926
|
-
completeObservers.push(async () => {
|
|
4927
|
-
const context = {
|
|
4928
|
-
iteration: this.iteration,
|
|
4929
|
-
gadgetName: result.gadgetName,
|
|
4930
|
-
invocationId: result.invocationId,
|
|
4931
|
-
parameters,
|
|
4932
|
-
originalResult,
|
|
4933
|
-
finalResult: result.result,
|
|
4934
|
-
error: result.error,
|
|
4935
|
-
executionTimeMs: result.executionTimeMs,
|
|
4936
|
-
breaksLoop: result.breaksLoop,
|
|
4937
|
-
cost: result.cost,
|
|
4938
|
-
logger: this.logger
|
|
4939
|
-
};
|
|
4940
|
-
await this.hooks.observers?.onGadgetExecutionComplete?.(context);
|
|
4941
|
-
});
|
|
4942
|
-
}
|
|
4943
|
-
await this.runObserversInParallel(completeObservers);
|
|
4944
|
-
this.completedResults.set(result.invocationId, result);
|
|
4945
|
-
if (result.error) {
|
|
4946
|
-
this.failedInvocations.add(result.invocationId);
|
|
4947
|
-
}
|
|
4948
|
-
events.push({ type: "gadget_result", result });
|
|
4949
|
-
return events;
|
|
4950
|
-
}
|
|
4951
|
-
/**
|
|
4952
|
-
* Execute a gadget and yield the result event.
|
|
4953
|
-
* Generator version that yields gadget_result immediately when execution completes.
|
|
4839
|
+
* Execute a gadget through the full hook lifecycle and yield events.
|
|
4840
|
+
* Handles parameter interception, before/after controllers, observers,
|
|
4841
|
+
* execution, result interception, and tree tracking.
|
|
4954
4842
|
*/
|
|
4955
4843
|
async *executeGadgetGenerator(call) {
|
|
4956
4844
|
if (call.parseError) {
|
|
@@ -5216,8 +5104,9 @@ var init_stream_processor = __esm({
|
|
|
5216
5104
|
invocationId: call.invocationId,
|
|
5217
5105
|
failedDependency: failedDep
|
|
5218
5106
|
});
|
|
5219
|
-
const
|
|
5220
|
-
|
|
5107
|
+
for await (const evt of this.executeGadgetGenerator(call)) {
|
|
5108
|
+
events.push(evt);
|
|
5109
|
+
}
|
|
5221
5110
|
} else if (action.action === "use_fallback") {
|
|
5222
5111
|
const fallbackResult = {
|
|
5223
5112
|
gadgetName: call.gadgetName,
|
|
@@ -5238,90 +5127,9 @@ var init_stream_processor = __esm({
|
|
|
5238
5127
|
}
|
|
5239
5128
|
/**
|
|
5240
5129
|
* Process pending gadgets whose dependencies are now satisfied.
|
|
5241
|
-
*
|
|
5242
|
-
*/
|
|
5243
|
-
async processPendingGadgets() {
|
|
5244
|
-
const events = [];
|
|
5245
|
-
let progress = true;
|
|
5246
|
-
while (progress && this.gadgetsAwaitingDependencies.size > 0) {
|
|
5247
|
-
progress = false;
|
|
5248
|
-
const readyToExecute = [];
|
|
5249
|
-
const readyToSkip = [];
|
|
5250
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5251
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
5252
|
-
if (failedDep) {
|
|
5253
|
-
readyToSkip.push({ call, failedDep });
|
|
5254
|
-
continue;
|
|
5255
|
-
}
|
|
5256
|
-
const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
|
|
5257
|
-
if (allSatisfied) {
|
|
5258
|
-
readyToExecute.push(call);
|
|
5259
|
-
}
|
|
5260
|
-
}
|
|
5261
|
-
for (const { call, failedDep } of readyToSkip) {
|
|
5262
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
5263
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
5264
|
-
events.push(...skipEvents);
|
|
5265
|
-
progress = true;
|
|
5266
|
-
}
|
|
5267
|
-
if (readyToExecute.length > 0) {
|
|
5268
|
-
this.logger.debug("Executing ready gadgets in parallel", {
|
|
5269
|
-
count: readyToExecute.length,
|
|
5270
|
-
invocationIds: readyToExecute.map((c) => c.invocationId)
|
|
5271
|
-
});
|
|
5272
|
-
for (const call of readyToExecute) {
|
|
5273
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
5274
|
-
}
|
|
5275
|
-
const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
|
|
5276
|
-
const results = await Promise.all(executePromises);
|
|
5277
|
-
for (const executeEvents of results) {
|
|
5278
|
-
events.push(...executeEvents);
|
|
5279
|
-
}
|
|
5280
|
-
progress = true;
|
|
5281
|
-
}
|
|
5282
|
-
}
|
|
5283
|
-
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
5284
|
-
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
5285
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5286
|
-
const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
5287
|
-
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
5288
|
-
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
5289
|
-
let errorMessage;
|
|
5290
|
-
let logLevel = "warn";
|
|
5291
|
-
if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
|
|
5292
|
-
errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
|
|
5293
|
-
logLevel = "error";
|
|
5294
|
-
} else if (circularDeps.length > 0) {
|
|
5295
|
-
errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
|
|
5296
|
-
} else {
|
|
5297
|
-
errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
|
|
5298
|
-
}
|
|
5299
|
-
this.logger[logLevel]("Gadget has unresolvable dependencies", {
|
|
5300
|
-
gadgetName: call.gadgetName,
|
|
5301
|
-
invocationId,
|
|
5302
|
-
circularDependencies: circularDeps,
|
|
5303
|
-
missingDependencies: trulyMissingDeps
|
|
5304
|
-
});
|
|
5305
|
-
this.failedInvocations.add(invocationId);
|
|
5306
|
-
const skipEvent = {
|
|
5307
|
-
type: "gadget_skipped",
|
|
5308
|
-
gadgetName: call.gadgetName,
|
|
5309
|
-
invocationId,
|
|
5310
|
-
parameters: call.parameters ?? {},
|
|
5311
|
-
failedDependency: missingDeps[0],
|
|
5312
|
-
failedDependencyError: errorMessage
|
|
5313
|
-
};
|
|
5314
|
-
events.push(skipEvent);
|
|
5315
|
-
}
|
|
5316
|
-
this.gadgetsAwaitingDependencies.clear();
|
|
5317
|
-
}
|
|
5318
|
-
return events;
|
|
5319
|
-
}
|
|
5320
|
-
/**
|
|
5321
|
-
* Process pending gadgets, yielding events in real-time.
|
|
5322
|
-
* Generator version that yields events as gadgets complete.
|
|
5130
|
+
* Yields events in real-time as gadgets complete.
|
|
5323
5131
|
*
|
|
5324
|
-
*
|
|
5132
|
+
* Gadgets are executed in parallel for efficiency,
|
|
5325
5133
|
* but results are yielded as they become available.
|
|
5326
5134
|
*/
|
|
5327
5135
|
async *processPendingGadgetsGenerator() {
|
|
@@ -5331,12 +5139,16 @@ var init_stream_processor = __esm({
|
|
|
5331
5139
|
const readyToExecute = [];
|
|
5332
5140
|
const readyToSkip = [];
|
|
5333
5141
|
for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5334
|
-
const failedDep = call.dependencies.find(
|
|
5142
|
+
const failedDep = call.dependencies.find(
|
|
5143
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
5144
|
+
);
|
|
5335
5145
|
if (failedDep) {
|
|
5336
5146
|
readyToSkip.push({ call, failedDep });
|
|
5337
5147
|
continue;
|
|
5338
5148
|
}
|
|
5339
|
-
const allSatisfied = call.dependencies.every(
|
|
5149
|
+
const allSatisfied = call.dependencies.every(
|
|
5150
|
+
(dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
|
|
5151
|
+
);
|
|
5340
5152
|
if (allSatisfied) {
|
|
5341
5153
|
readyToExecute.push(call);
|
|
5342
5154
|
}
|
|
@@ -5377,7 +5189,9 @@ var init_stream_processor = __esm({
|
|
|
5377
5189
|
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
5378
5190
|
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
5379
5191
|
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5380
|
-
const missingDeps = call.dependencies.filter(
|
|
5192
|
+
const missingDeps = call.dependencies.filter(
|
|
5193
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
5194
|
+
);
|
|
5381
5195
|
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
5382
5196
|
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
5383
5197
|
let errorMessage;
|
|
@@ -5431,10 +5245,27 @@ var init_stream_processor = __esm({
|
|
|
5431
5245
|
*/
|
|
5432
5246
|
async runObserversInParallel(observers) {
|
|
5433
5247
|
if (observers.length === 0) return;
|
|
5434
|
-
|
|
5248
|
+
await Promise.allSettled(
|
|
5435
5249
|
observers.map((observer) => this.safeObserve(observer))
|
|
5436
5250
|
);
|
|
5437
5251
|
}
|
|
5252
|
+
// ==========================================================================
|
|
5253
|
+
// Public accessors for cross-iteration dependency tracking
|
|
5254
|
+
// ==========================================================================
|
|
5255
|
+
/**
|
|
5256
|
+
* Get all invocation IDs that completed successfully in this iteration.
|
|
5257
|
+
* Used by Agent to accumulate completed IDs across iterations.
|
|
5258
|
+
*/
|
|
5259
|
+
getCompletedInvocationIds() {
|
|
5260
|
+
return new Set(this.completedResults.keys());
|
|
5261
|
+
}
|
|
5262
|
+
/**
|
|
5263
|
+
* Get all invocation IDs that failed in this iteration.
|
|
5264
|
+
* Used by Agent to accumulate failed IDs across iterations.
|
|
5265
|
+
*/
|
|
5266
|
+
getFailedInvocationIds() {
|
|
5267
|
+
return new Set(this.failedInvocations);
|
|
5268
|
+
}
|
|
5438
5269
|
};
|
|
5439
5270
|
}
|
|
5440
5271
|
});
|
|
@@ -5497,6 +5328,9 @@ var init_agent = __esm({
|
|
|
5497
5328
|
onSubagentEvent;
|
|
5498
5329
|
// Counter for generating synthetic invocation IDs for wrapped text content
|
|
5499
5330
|
syntheticInvocationCounter = 0;
|
|
5331
|
+
// Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
|
|
5332
|
+
completedInvocationIds = /* @__PURE__ */ new Set();
|
|
5333
|
+
failedInvocationIds = /* @__PURE__ */ new Set();
|
|
5500
5334
|
// Execution Tree - first-class model for nested subagent support
|
|
5501
5335
|
tree;
|
|
5502
5336
|
parentNodeId;
|
|
@@ -5806,96 +5640,22 @@ var init_agent = __esm({
|
|
|
5806
5640
|
maxIterations: this.maxIterations
|
|
5807
5641
|
});
|
|
5808
5642
|
while (currentIteration < this.maxIterations) {
|
|
5809
|
-
if (this.
|
|
5810
|
-
this.logger.info("Agent loop terminated by abort signal", {
|
|
5811
|
-
iteration: currentIteration,
|
|
5812
|
-
reason: this.signal.reason
|
|
5813
|
-
});
|
|
5814
|
-
await this.safeObserve(async () => {
|
|
5815
|
-
if (this.hooks.observers?.onAbort) {
|
|
5816
|
-
const context = {
|
|
5817
|
-
iteration: currentIteration,
|
|
5818
|
-
reason: this.signal?.reason,
|
|
5819
|
-
logger: this.logger
|
|
5820
|
-
};
|
|
5821
|
-
await this.hooks.observers.onAbort(context);
|
|
5822
|
-
}
|
|
5823
|
-
});
|
|
5643
|
+
if (await this.checkAbortAndNotify(currentIteration)) {
|
|
5824
5644
|
return;
|
|
5825
5645
|
}
|
|
5826
5646
|
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
5827
5647
|
try {
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
currentIteration
|
|
5832
|
-
);
|
|
5833
|
-
if (compactionEvent) {
|
|
5834
|
-
this.logger.info("Context compacted", {
|
|
5835
|
-
strategy: compactionEvent.strategy,
|
|
5836
|
-
tokensBefore: compactionEvent.tokensBefore,
|
|
5837
|
-
tokensAfter: compactionEvent.tokensAfter
|
|
5838
|
-
});
|
|
5839
|
-
yield { type: "compaction", event: compactionEvent };
|
|
5840
|
-
await this.safeObserve(async () => {
|
|
5841
|
-
if (this.hooks.observers?.onCompaction) {
|
|
5842
|
-
await this.hooks.observers.onCompaction({
|
|
5843
|
-
iteration: currentIteration,
|
|
5844
|
-
event: compactionEvent,
|
|
5845
|
-
// biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
|
|
5846
|
-
stats: this.compactionManager.getStats(),
|
|
5847
|
-
logger: this.logger
|
|
5848
|
-
});
|
|
5849
|
-
}
|
|
5850
|
-
});
|
|
5851
|
-
}
|
|
5648
|
+
const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
|
|
5649
|
+
if (compactionEvent) {
|
|
5650
|
+
yield compactionEvent;
|
|
5852
5651
|
}
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
};
|
|
5860
|
-
await this.safeObserve(async () => {
|
|
5861
|
-
if (this.hooks.observers?.onLLMCallStart) {
|
|
5862
|
-
const context = {
|
|
5863
|
-
iteration: currentIteration,
|
|
5864
|
-
options: llmOptions,
|
|
5865
|
-
logger: this.logger
|
|
5866
|
-
};
|
|
5867
|
-
await this.hooks.observers.onLLMCallStart(context);
|
|
5868
|
-
}
|
|
5869
|
-
});
|
|
5870
|
-
if (this.hooks.controllers?.beforeLLMCall) {
|
|
5871
|
-
const context = {
|
|
5872
|
-
iteration: currentIteration,
|
|
5873
|
-
maxIterations: this.maxIterations,
|
|
5874
|
-
options: llmOptions,
|
|
5875
|
-
logger: this.logger
|
|
5876
|
-
};
|
|
5877
|
-
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
5878
|
-
validateBeforeLLMCallAction(action);
|
|
5879
|
-
if (action.action === "skip") {
|
|
5880
|
-
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
5881
|
-
this.conversation.addAssistantMessage(action.syntheticResponse);
|
|
5882
|
-
yield { type: "text", content: action.syntheticResponse };
|
|
5883
|
-
break;
|
|
5884
|
-
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
5885
|
-
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
5886
|
-
}
|
|
5652
|
+
const prepared = await this.prepareLLMCall(currentIteration);
|
|
5653
|
+
const llmOptions = prepared.options;
|
|
5654
|
+
if (prepared.skipWithSynthetic !== void 0) {
|
|
5655
|
+
this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
|
|
5656
|
+
yield { type: "text", content: prepared.skipWithSynthetic };
|
|
5657
|
+
break;
|
|
5887
5658
|
}
|
|
5888
|
-
await this.safeObserve(async () => {
|
|
5889
|
-
if (this.hooks.observers?.onLLMCallReady) {
|
|
5890
|
-
const context = {
|
|
5891
|
-
iteration: currentIteration,
|
|
5892
|
-
maxIterations: this.maxIterations,
|
|
5893
|
-
options: llmOptions,
|
|
5894
|
-
logger: this.logger
|
|
5895
|
-
};
|
|
5896
|
-
await this.hooks.observers.onLLMCallReady(context);
|
|
5897
|
-
}
|
|
5898
|
-
});
|
|
5899
5659
|
this.logger.info("Calling LLM", { model: this.model });
|
|
5900
5660
|
this.logger.silly("LLM request details", {
|
|
5901
5661
|
model: llmOptions.model,
|
|
@@ -5931,7 +5691,10 @@ var init_agent = __esm({
|
|
|
5931
5691
|
tree: this.tree,
|
|
5932
5692
|
parentNodeId: currentLLMNodeId,
|
|
5933
5693
|
// Gadgets are children of this LLM call
|
|
5934
|
-
baseDepth: this.baseDepth
|
|
5694
|
+
baseDepth: this.baseDepth,
|
|
5695
|
+
// Cross-iteration dependency tracking
|
|
5696
|
+
priorCompletedInvocations: this.completedInvocationIds,
|
|
5697
|
+
priorFailedInvocations: this.failedInvocationIds
|
|
5935
5698
|
});
|
|
5936
5699
|
let streamMetadata = null;
|
|
5937
5700
|
let gadgetCallCount = 0;
|
|
@@ -5954,6 +5717,12 @@ var init_agent = __esm({
|
|
|
5954
5717
|
if (!streamMetadata) {
|
|
5955
5718
|
throw new Error("Stream processing completed without metadata event");
|
|
5956
5719
|
}
|
|
5720
|
+
for (const id of processor.getCompletedInvocationIds()) {
|
|
5721
|
+
this.completedInvocationIds.add(id);
|
|
5722
|
+
}
|
|
5723
|
+
for (const id of processor.getFailedInvocationIds()) {
|
|
5724
|
+
this.failedInvocationIds.add(id);
|
|
5725
|
+
}
|
|
5957
5726
|
const result = streamMetadata;
|
|
5958
5727
|
this.logger.info("LLM response completed", {
|
|
5959
5728
|
finishReason: result.finishReason,
|
|
@@ -5977,81 +5746,21 @@ var init_agent = __esm({
|
|
|
5977
5746
|
await this.hooks.observers.onLLMCallComplete(context);
|
|
5978
5747
|
}
|
|
5979
5748
|
});
|
|
5980
|
-
this.
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
logger: this.logger
|
|
5996
|
-
};
|
|
5997
|
-
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
5998
|
-
validateAfterLLMCallAction(action);
|
|
5999
|
-
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
6000
|
-
finalMessage = action.modifiedMessage;
|
|
6001
|
-
}
|
|
6002
|
-
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
6003
|
-
for (const msg of action.messages) {
|
|
6004
|
-
if (msg.role === "user") {
|
|
6005
|
-
this.conversation.addUserMessage(msg.content);
|
|
6006
|
-
} else if (msg.role === "assistant") {
|
|
6007
|
-
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
6008
|
-
} else if (msg.role === "system") {
|
|
6009
|
-
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
6010
|
-
}
|
|
6011
|
-
}
|
|
6012
|
-
}
|
|
6013
|
-
}
|
|
6014
|
-
if (result.didExecuteGadgets) {
|
|
6015
|
-
if (this.textWithGadgetsHandler) {
|
|
6016
|
-
const textContent = textOutputs.join("");
|
|
6017
|
-
if (textContent.trim()) {
|
|
6018
|
-
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
6019
|
-
const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
|
|
6020
|
-
this.conversation.addGadgetCallResult(
|
|
6021
|
-
gadgetName,
|
|
6022
|
-
parameterMapping(textContent),
|
|
6023
|
-
resultMapping ? resultMapping(textContent) : textContent,
|
|
6024
|
-
syntheticId
|
|
6025
|
-
);
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
6028
|
-
for (const output of gadgetResults) {
|
|
6029
|
-
if (output.type === "gadget_result") {
|
|
6030
|
-
const gadgetResult = output.result;
|
|
6031
|
-
this.conversation.addGadgetCallResult(
|
|
6032
|
-
gadgetResult.gadgetName,
|
|
6033
|
-
gadgetResult.parameters,
|
|
6034
|
-
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
6035
|
-
gadgetResult.invocationId,
|
|
6036
|
-
gadgetResult.media,
|
|
6037
|
-
gadgetResult.mediaIds
|
|
6038
|
-
);
|
|
6039
|
-
}
|
|
6040
|
-
}
|
|
6041
|
-
} else {
|
|
6042
|
-
if (finalMessage.trim()) {
|
|
6043
|
-
const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
|
|
6044
|
-
this.conversation.addGadgetCallResult(
|
|
6045
|
-
"TellUser",
|
|
6046
|
-
{ message: finalMessage, done: false, type: "info" },
|
|
6047
|
-
`\u2139\uFE0F ${finalMessage}`,
|
|
6048
|
-
syntheticId
|
|
6049
|
-
);
|
|
6050
|
-
}
|
|
6051
|
-
const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
|
|
6052
|
-
if (shouldBreak) {
|
|
6053
|
-
break;
|
|
6054
|
-
}
|
|
5749
|
+
this.completeLLMCallInTree(currentLLMNodeId, result);
|
|
5750
|
+
const finalMessage = await this.processAfterLLMCallController(
|
|
5751
|
+
currentIteration,
|
|
5752
|
+
llmOptions,
|
|
5753
|
+
result,
|
|
5754
|
+
gadgetCallCount
|
|
5755
|
+
);
|
|
5756
|
+
const shouldBreakFromTextOnly = await this.updateConversationWithResults(
|
|
5757
|
+
result.didExecuteGadgets,
|
|
5758
|
+
textOutputs,
|
|
5759
|
+
gadgetResults,
|
|
5760
|
+
finalMessage
|
|
5761
|
+
);
|
|
5762
|
+
if (shouldBreakFromTextOnly) {
|
|
5763
|
+
break;
|
|
6055
5764
|
}
|
|
6056
5765
|
if (result.shouldBreakLoop) {
|
|
6057
5766
|
this.logger.info("Loop terminated by gadget or processor");
|
|
@@ -6204,6 +5913,210 @@ var init_agent = __esm({
|
|
|
6204
5913
|
}
|
|
6205
5914
|
};
|
|
6206
5915
|
}
|
|
5916
|
+
// ==========================================================================
|
|
5917
|
+
// Agent Loop Helper Methods (extracted from run() for readability)
|
|
5918
|
+
// ==========================================================================
|
|
5919
|
+
/**
|
|
5920
|
+
* Check abort signal and notify observers if aborted.
|
|
5921
|
+
* @returns true if agent should terminate
|
|
5922
|
+
*/
|
|
5923
|
+
async checkAbortAndNotify(iteration) {
|
|
5924
|
+
if (!this.signal?.aborted) return false;
|
|
5925
|
+
this.logger.info("Agent loop terminated by abort signal", {
|
|
5926
|
+
iteration,
|
|
5927
|
+
reason: this.signal.reason
|
|
5928
|
+
});
|
|
5929
|
+
await this.safeObserve(async () => {
|
|
5930
|
+
if (this.hooks.observers?.onAbort) {
|
|
5931
|
+
const context = {
|
|
5932
|
+
iteration,
|
|
5933
|
+
reason: this.signal?.reason,
|
|
5934
|
+
logger: this.logger
|
|
5935
|
+
};
|
|
5936
|
+
await this.hooks.observers.onAbort(context);
|
|
5937
|
+
}
|
|
5938
|
+
});
|
|
5939
|
+
return true;
|
|
5940
|
+
}
|
|
5941
|
+
/**
|
|
5942
|
+
* Check and perform context compaction if needed.
|
|
5943
|
+
* @returns compaction stream event if compaction occurred, null otherwise
|
|
5944
|
+
*/
|
|
5945
|
+
async checkAndPerformCompaction(iteration) {
|
|
5946
|
+
if (!this.compactionManager) return null;
|
|
5947
|
+
const compactionEvent = await this.compactionManager.checkAndCompact(
|
|
5948
|
+
this.conversation,
|
|
5949
|
+
iteration
|
|
5950
|
+
);
|
|
5951
|
+
if (!compactionEvent) return null;
|
|
5952
|
+
this.logger.info("Context compacted", {
|
|
5953
|
+
strategy: compactionEvent.strategy,
|
|
5954
|
+
tokensBefore: compactionEvent.tokensBefore,
|
|
5955
|
+
tokensAfter: compactionEvent.tokensAfter
|
|
5956
|
+
});
|
|
5957
|
+
await this.safeObserve(async () => {
|
|
5958
|
+
if (this.hooks.observers?.onCompaction) {
|
|
5959
|
+
await this.hooks.observers.onCompaction({
|
|
5960
|
+
iteration,
|
|
5961
|
+
event: compactionEvent,
|
|
5962
|
+
// biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
|
|
5963
|
+
stats: this.compactionManager.getStats(),
|
|
5964
|
+
logger: this.logger
|
|
5965
|
+
});
|
|
5966
|
+
}
|
|
5967
|
+
});
|
|
5968
|
+
return { type: "compaction", event: compactionEvent };
|
|
5969
|
+
}
|
|
5970
|
+
/**
|
|
5971
|
+
* Prepare LLM call options and process beforeLLMCall controller.
|
|
5972
|
+
* @returns options and optional skipWithSynthetic response if controller wants to skip
|
|
5973
|
+
*/
|
|
5974
|
+
async prepareLLMCall(iteration) {
|
|
5975
|
+
let llmOptions = {
|
|
5976
|
+
model: this.model,
|
|
5977
|
+
messages: this.conversation.getMessages(),
|
|
5978
|
+
temperature: this.temperature,
|
|
5979
|
+
maxTokens: this.defaultMaxTokens,
|
|
5980
|
+
signal: this.signal
|
|
5981
|
+
};
|
|
5982
|
+
await this.safeObserve(async () => {
|
|
5983
|
+
if (this.hooks.observers?.onLLMCallStart) {
|
|
5984
|
+
const context = {
|
|
5985
|
+
iteration,
|
|
5986
|
+
options: llmOptions,
|
|
5987
|
+
logger: this.logger
|
|
5988
|
+
};
|
|
5989
|
+
await this.hooks.observers.onLLMCallStart(context);
|
|
5990
|
+
}
|
|
5991
|
+
});
|
|
5992
|
+
if (this.hooks.controllers?.beforeLLMCall) {
|
|
5993
|
+
const context = {
|
|
5994
|
+
iteration,
|
|
5995
|
+
maxIterations: this.maxIterations,
|
|
5996
|
+
options: llmOptions,
|
|
5997
|
+
logger: this.logger
|
|
5998
|
+
};
|
|
5999
|
+
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
6000
|
+
validateBeforeLLMCallAction(action);
|
|
6001
|
+
if (action.action === "skip") {
|
|
6002
|
+
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
6003
|
+
return { options: llmOptions, skipWithSynthetic: action.syntheticResponse };
|
|
6004
|
+
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
6005
|
+
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
await this.safeObserve(async () => {
|
|
6009
|
+
if (this.hooks.observers?.onLLMCallReady) {
|
|
6010
|
+
const context = {
|
|
6011
|
+
iteration,
|
|
6012
|
+
maxIterations: this.maxIterations,
|
|
6013
|
+
options: llmOptions,
|
|
6014
|
+
logger: this.logger
|
|
6015
|
+
};
|
|
6016
|
+
await this.hooks.observers.onLLMCallReady(context);
|
|
6017
|
+
}
|
|
6018
|
+
});
|
|
6019
|
+
return { options: llmOptions };
|
|
6020
|
+
}
|
|
6021
|
+
/**
|
|
6022
|
+
* Calculate cost and complete LLM call in execution tree.
|
|
6023
|
+
*/
|
|
6024
|
+
completeLLMCallInTree(nodeId, result) {
|
|
6025
|
+
const llmCost = this.client.modelRegistry?.estimateCost?.(
|
|
6026
|
+
this.model,
|
|
6027
|
+
result.usage?.inputTokens ?? 0,
|
|
6028
|
+
result.usage?.outputTokens ?? 0,
|
|
6029
|
+
result.usage?.cachedInputTokens ?? 0,
|
|
6030
|
+
result.usage?.cacheCreationInputTokens ?? 0
|
|
6031
|
+
)?.totalCost;
|
|
6032
|
+
this.tree.completeLLMCall(nodeId, {
|
|
6033
|
+
response: result.rawResponse,
|
|
6034
|
+
usage: result.usage,
|
|
6035
|
+
finishReason: result.finishReason,
|
|
6036
|
+
cost: llmCost
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
/**
|
|
6040
|
+
* Process afterLLMCall controller and return modified final message.
|
|
6041
|
+
*/
|
|
6042
|
+
async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
|
|
6043
|
+
let finalMessage = result.finalMessage;
|
|
6044
|
+
if (!this.hooks.controllers?.afterLLMCall) {
|
|
6045
|
+
return finalMessage;
|
|
6046
|
+
}
|
|
6047
|
+
const context = {
|
|
6048
|
+
iteration,
|
|
6049
|
+
maxIterations: this.maxIterations,
|
|
6050
|
+
options: llmOptions,
|
|
6051
|
+
finishReason: result.finishReason,
|
|
6052
|
+
usage: result.usage,
|
|
6053
|
+
finalMessage: result.finalMessage,
|
|
6054
|
+
gadgetCallCount,
|
|
6055
|
+
logger: this.logger
|
|
6056
|
+
};
|
|
6057
|
+
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
6058
|
+
validateAfterLLMCallAction(action);
|
|
6059
|
+
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
6060
|
+
finalMessage = action.modifiedMessage;
|
|
6061
|
+
}
|
|
6062
|
+
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
6063
|
+
for (const msg of action.messages) {
|
|
6064
|
+
if (msg.role === "user") {
|
|
6065
|
+
this.conversation.addUserMessage(msg.content);
|
|
6066
|
+
} else if (msg.role === "assistant") {
|
|
6067
|
+
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
6068
|
+
} else if (msg.role === "system") {
|
|
6069
|
+
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
return finalMessage;
|
|
6074
|
+
}
|
|
6075
|
+
/**
|
|
6076
|
+
* Update conversation history with gadget results or text-only response.
|
|
6077
|
+
* @returns true if loop should break (text-only handler requested termination)
|
|
6078
|
+
*/
|
|
6079
|
+
async updateConversationWithResults(didExecuteGadgets, textOutputs, gadgetResults, finalMessage) {
|
|
6080
|
+
if (didExecuteGadgets) {
|
|
6081
|
+
if (this.textWithGadgetsHandler) {
|
|
6082
|
+
const textContent = textOutputs.join("");
|
|
6083
|
+
if (textContent.trim()) {
|
|
6084
|
+
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
6085
|
+
const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
|
|
6086
|
+
this.conversation.addGadgetCallResult(
|
|
6087
|
+
gadgetName,
|
|
6088
|
+
parameterMapping(textContent),
|
|
6089
|
+
resultMapping ? resultMapping(textContent) : textContent,
|
|
6090
|
+
syntheticId
|
|
6091
|
+
);
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
for (const output of gadgetResults) {
|
|
6095
|
+
if (output.type === "gadget_result") {
|
|
6096
|
+
const gadgetResult = output.result;
|
|
6097
|
+
this.conversation.addGadgetCallResult(
|
|
6098
|
+
gadgetResult.gadgetName,
|
|
6099
|
+
gadgetResult.parameters,
|
|
6100
|
+
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
6101
|
+
gadgetResult.invocationId,
|
|
6102
|
+
gadgetResult.media,
|
|
6103
|
+
gadgetResult.mediaIds
|
|
6104
|
+
);
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
return false;
|
|
6108
|
+
}
|
|
6109
|
+
if (finalMessage.trim()) {
|
|
6110
|
+
const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
|
|
6111
|
+
this.conversation.addGadgetCallResult(
|
|
6112
|
+
"TellUser",
|
|
6113
|
+
{ message: finalMessage, done: false, type: "info" },
|
|
6114
|
+
`\u2139\uFE0F ${finalMessage}`,
|
|
6115
|
+
syntheticId
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
return await this.handleTextOnlyResponse(finalMessage);
|
|
6119
|
+
}
|
|
6207
6120
|
/**
|
|
6208
6121
|
* Run agent with named event handlers (syntactic sugar).
|
|
6209
6122
|
*
|
|
@@ -9736,11 +9649,13 @@ var init_model_registry = __esm({
|
|
|
9736
9649
|
}
|
|
9737
9650
|
/**
|
|
9738
9651
|
* Get model specification by model ID
|
|
9739
|
-
* @param modelId - Full model identifier
|
|
9652
|
+
* @param modelId - Full model identifier, optionally with provider prefix
|
|
9653
|
+
* (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929', 'anthropic:claude-sonnet-4-5')
|
|
9740
9654
|
* @returns ModelSpec if found, undefined otherwise
|
|
9741
9655
|
*/
|
|
9742
9656
|
getModelSpec(modelId) {
|
|
9743
|
-
|
|
9657
|
+
const normalizedId = modelId.includes(":") ? modelId.split(":")[1] : modelId;
|
|
9658
|
+
return this.modelSpecs.find((model) => model.modelId === normalizedId);
|
|
9744
9659
|
}
|
|
9745
9660
|
/**
|
|
9746
9661
|
* List all models, optionally filtered by provider
|