llmist 6.2.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-EJEP5MHQ.js → chunk-5KEZ7SQX.js} +2 -2
- package/dist/chunk-5KEZ7SQX.js.map +1 -0
- package/dist/{chunk-36YSBSGB.js → chunk-SFZIL2VR.js} +354 -486
- package/dist/chunk-SFZIL2VR.js.map +1 -0
- package/dist/cli.cjs +5920 -6275
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5528 -5751
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +354 -486
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -32
- package/dist/index.d.ts +34 -32
- package/dist/index.js +2 -2
- package/dist/{mock-stream-DG4wF-NH.d.cts → mock-stream-r5vjy2Iq.d.cts} +53 -1
- package/dist/{mock-stream-DG4wF-NH.d.ts → mock-stream-r5vjy2Iq.d.ts} +53 -1
- package/dist/testing/index.cjs +354 -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-36YSBSGB.js.map +0 -1
- package/dist/chunk-EJEP5MHQ.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,
|
|
@@ -4120,10 +4153,11 @@ function getHostExportsInternal() {
|
|
|
4120
4153
|
}
|
|
4121
4154
|
return cachedHostExports;
|
|
4122
4155
|
}
|
|
4123
|
-
var import_zod2, cachedHostExports, GadgetExecutor;
|
|
4156
|
+
var import_fast_deep_equal, import_zod2, cachedHostExports, GadgetExecutor;
|
|
4124
4157
|
var init_executor = __esm({
|
|
4125
4158
|
"src/gadgets/executor.ts"() {
|
|
4126
4159
|
"use strict";
|
|
4160
|
+
import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
|
|
4127
4161
|
import_zod2 = require("zod");
|
|
4128
4162
|
init_builder();
|
|
4129
4163
|
init_client();
|
|
@@ -4234,7 +4268,7 @@ var init_executor = __esm({
|
|
|
4234
4268
|
try {
|
|
4235
4269
|
const cleanedRaw = stripMarkdownFences(call.parametersRaw);
|
|
4236
4270
|
const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
|
|
4237
|
-
const parametersWereModified = !
|
|
4271
|
+
const parametersWereModified = !(0, import_fast_deep_equal.default)(rawParameters, initialParse);
|
|
4238
4272
|
if (parametersWereModified) {
|
|
4239
4273
|
this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
|
|
4240
4274
|
gadgetName: call.gadgetName
|
|
@@ -4311,7 +4345,9 @@ var init_executor = __esm({
|
|
|
4311
4345
|
nodeId: gadgetNodeId,
|
|
4312
4346
|
depth: gadgetDepth,
|
|
4313
4347
|
// Host exports for external gadgets to use host's llmist classes
|
|
4314
|
-
hostExports: getHostExportsInternal()
|
|
4348
|
+
hostExports: getHostExportsInternal(),
|
|
4349
|
+
// Logger for structured logging (respects CLI's log level/file config)
|
|
4350
|
+
logger: this.logger
|
|
4315
4351
|
};
|
|
4316
4352
|
let rawResult;
|
|
4317
4353
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -4481,27 +4517,6 @@ var init_executor = __esm({
|
|
|
4481
4517
|
async executeAll(calls) {
|
|
4482
4518
|
return Promise.all(calls.map((call) => this.execute(call)));
|
|
4483
4519
|
}
|
|
4484
|
-
/**
|
|
4485
|
-
* Deep equality check for objects/arrays.
|
|
4486
|
-
* Used to detect if parameters were modified by an interceptor.
|
|
4487
|
-
*/
|
|
4488
|
-
deepEquals(a, b) {
|
|
4489
|
-
if (a === b) return true;
|
|
4490
|
-
if (a === null || b === null) return a === b;
|
|
4491
|
-
if (typeof a !== typeof b) return false;
|
|
4492
|
-
if (typeof a !== "object") return a === b;
|
|
4493
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
4494
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4495
|
-
if (a.length !== b.length) return false;
|
|
4496
|
-
return a.every((val, i) => this.deepEquals(val, b[i]));
|
|
4497
|
-
}
|
|
4498
|
-
const aObj = a;
|
|
4499
|
-
const bObj = b;
|
|
4500
|
-
const aKeys = Object.keys(aObj);
|
|
4501
|
-
const bKeys = Object.keys(bObj);
|
|
4502
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
4503
|
-
return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
|
|
4504
|
-
}
|
|
4505
4520
|
};
|
|
4506
4521
|
}
|
|
4507
4522
|
});
|
|
@@ -4539,6 +4554,11 @@ var init_stream_processor = __esm({
|
|
|
4539
4554
|
inFlightExecutions = /* @__PURE__ */ new Map();
|
|
4540
4555
|
/** Queue of completed gadget results ready to be yielded (for real-time streaming) */
|
|
4541
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;
|
|
4542
4562
|
constructor(options) {
|
|
4543
4563
|
this.iteration = options.iteration;
|
|
4544
4564
|
this.registry = options.registry;
|
|
@@ -4547,6 +4567,8 @@ var init_stream_processor = __esm({
|
|
|
4547
4567
|
this.tree = options.tree;
|
|
4548
4568
|
this.parentNodeId = options.parentNodeId ?? null;
|
|
4549
4569
|
this.baseDepth = options.baseDepth ?? 0;
|
|
4570
|
+
this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
|
|
4571
|
+
this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
|
|
4550
4572
|
this.parser = new GadgetCallParser({
|
|
4551
4573
|
startPrefix: options.gadgetStartPrefix,
|
|
4552
4574
|
endPrefix: options.gadgetEndPrefix,
|
|
@@ -4745,62 +4767,11 @@ var init_stream_processor = __esm({
|
|
|
4745
4767
|
}
|
|
4746
4768
|
return [{ type: "text", content }];
|
|
4747
4769
|
}
|
|
4748
|
-
/**
|
|
4749
|
-
* Process a gadget call through the full lifecycle, handling dependencies.
|
|
4750
|
-
*
|
|
4751
|
-
* Gadgets without dependencies (or with all dependencies satisfied) execute immediately.
|
|
4752
|
-
* Gadgets with unsatisfied dependencies are queued for later execution.
|
|
4753
|
-
* After each execution, pending gadgets are checked to see if they can now run.
|
|
4754
|
-
*/
|
|
4755
|
-
async processGadgetCall(call) {
|
|
4756
|
-
const events = [];
|
|
4757
|
-
events.push({ type: "gadget_call", call });
|
|
4758
|
-
if (call.dependencies.length > 0) {
|
|
4759
|
-
if (call.dependencies.includes(call.invocationId)) {
|
|
4760
|
-
this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
|
|
4761
|
-
gadgetName: call.gadgetName,
|
|
4762
|
-
invocationId: call.invocationId
|
|
4763
|
-
});
|
|
4764
|
-
this.failedInvocations.add(call.invocationId);
|
|
4765
|
-
const skipEvent = {
|
|
4766
|
-
type: "gadget_skipped",
|
|
4767
|
-
gadgetName: call.gadgetName,
|
|
4768
|
-
invocationId: call.invocationId,
|
|
4769
|
-
parameters: call.parameters ?? {},
|
|
4770
|
-
failedDependency: call.invocationId,
|
|
4771
|
-
failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
|
|
4772
|
-
};
|
|
4773
|
-
events.push(skipEvent);
|
|
4774
|
-
return events;
|
|
4775
|
-
}
|
|
4776
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
4777
|
-
if (failedDep) {
|
|
4778
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
4779
|
-
events.push(...skipEvents);
|
|
4780
|
-
return events;
|
|
4781
|
-
}
|
|
4782
|
-
const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
4783
|
-
if (unsatisfied.length > 0) {
|
|
4784
|
-
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
4785
|
-
gadgetName: call.gadgetName,
|
|
4786
|
-
invocationId: call.invocationId,
|
|
4787
|
-
waitingOn: unsatisfied
|
|
4788
|
-
});
|
|
4789
|
-
this.gadgetsAwaitingDependencies.set(call.invocationId, call);
|
|
4790
|
-
return events;
|
|
4791
|
-
}
|
|
4792
|
-
}
|
|
4793
|
-
const executeEvents = await this.executeGadgetWithHooks(call);
|
|
4794
|
-
events.push(...executeEvents);
|
|
4795
|
-
const triggeredEvents = await this.processPendingGadgets();
|
|
4796
|
-
events.push(...triggeredEvents);
|
|
4797
|
-
return events;
|
|
4798
|
-
}
|
|
4799
4770
|
/**
|
|
4800
4771
|
* Process a gadget call, yielding events in real-time.
|
|
4801
4772
|
*
|
|
4802
|
-
*
|
|
4803
|
-
*
|
|
4773
|
+
* Yields gadget_call event IMMEDIATELY when parsed (before execution),
|
|
4774
|
+
* enabling real-time UI feedback.
|
|
4804
4775
|
*/
|
|
4805
4776
|
async *processGadgetCallGenerator(call) {
|
|
4806
4777
|
yield { type: "gadget_call", call };
|
|
@@ -4831,7 +4802,9 @@ var init_stream_processor = __esm({
|
|
|
4831
4802
|
yield skipEvent;
|
|
4832
4803
|
return;
|
|
4833
4804
|
}
|
|
4834
|
-
const failedDep = call.dependencies.find(
|
|
4805
|
+
const failedDep = call.dependencies.find(
|
|
4806
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
4807
|
+
);
|
|
4835
4808
|
if (failedDep) {
|
|
4836
4809
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
4837
4810
|
for (const evt of skipEvents) {
|
|
@@ -4839,7 +4812,9 @@ var init_stream_processor = __esm({
|
|
|
4839
4812
|
}
|
|
4840
4813
|
return;
|
|
4841
4814
|
}
|
|
4842
|
-
const unsatisfied = call.dependencies.filter(
|
|
4815
|
+
const unsatisfied = call.dependencies.filter(
|
|
4816
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
4817
|
+
);
|
|
4843
4818
|
if (unsatisfied.length > 0) {
|
|
4844
4819
|
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
4845
4820
|
gadgetName: call.gadgetName,
|
|
@@ -4861,143 +4836,9 @@ var init_stream_processor = __esm({
|
|
|
4861
4836
|
this.inFlightExecutions.set(call.invocationId, executionPromise);
|
|
4862
4837
|
}
|
|
4863
4838
|
/**
|
|
4864
|
-
* Execute a gadget through the full hook lifecycle.
|
|
4865
|
-
*
|
|
4866
|
-
*
|
|
4867
|
-
*/
|
|
4868
|
-
async executeGadgetWithHooks(call) {
|
|
4869
|
-
const events = [];
|
|
4870
|
-
if (call.parseError) {
|
|
4871
|
-
this.logger.warn("Gadget has parse error", {
|
|
4872
|
-
gadgetName: call.gadgetName,
|
|
4873
|
-
error: call.parseError,
|
|
4874
|
-
rawParameters: call.parametersRaw
|
|
4875
|
-
});
|
|
4876
|
-
}
|
|
4877
|
-
let parameters = call.parameters ?? {};
|
|
4878
|
-
if (this.hooks.interceptors?.interceptGadgetParameters) {
|
|
4879
|
-
const context = {
|
|
4880
|
-
iteration: this.iteration,
|
|
4881
|
-
gadgetName: call.gadgetName,
|
|
4882
|
-
invocationId: call.invocationId,
|
|
4883
|
-
logger: this.logger
|
|
4884
|
-
};
|
|
4885
|
-
parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
|
|
4886
|
-
}
|
|
4887
|
-
call.parameters = parameters;
|
|
4888
|
-
let shouldSkip = false;
|
|
4889
|
-
let syntheticResult;
|
|
4890
|
-
if (this.hooks.controllers?.beforeGadgetExecution) {
|
|
4891
|
-
const context = {
|
|
4892
|
-
iteration: this.iteration,
|
|
4893
|
-
gadgetName: call.gadgetName,
|
|
4894
|
-
invocationId: call.invocationId,
|
|
4895
|
-
parameters,
|
|
4896
|
-
logger: this.logger
|
|
4897
|
-
};
|
|
4898
|
-
const action = await this.hooks.controllers.beforeGadgetExecution(context);
|
|
4899
|
-
validateBeforeGadgetExecutionAction(action);
|
|
4900
|
-
if (action.action === "skip") {
|
|
4901
|
-
shouldSkip = true;
|
|
4902
|
-
syntheticResult = action.syntheticResult;
|
|
4903
|
-
this.logger.info("Controller skipped gadget execution", {
|
|
4904
|
-
gadgetName: call.gadgetName
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
|
-
}
|
|
4908
|
-
const startObservers = [];
|
|
4909
|
-
if (this.hooks.observers?.onGadgetExecutionStart) {
|
|
4910
|
-
startObservers.push(async () => {
|
|
4911
|
-
const context = {
|
|
4912
|
-
iteration: this.iteration,
|
|
4913
|
-
gadgetName: call.gadgetName,
|
|
4914
|
-
invocationId: call.invocationId,
|
|
4915
|
-
parameters,
|
|
4916
|
-
logger: this.logger
|
|
4917
|
-
};
|
|
4918
|
-
await this.hooks.observers?.onGadgetExecutionStart?.(context);
|
|
4919
|
-
});
|
|
4920
|
-
}
|
|
4921
|
-
await this.runObserversInParallel(startObservers);
|
|
4922
|
-
let result;
|
|
4923
|
-
if (shouldSkip) {
|
|
4924
|
-
result = {
|
|
4925
|
-
gadgetName: call.gadgetName,
|
|
4926
|
-
invocationId: call.invocationId,
|
|
4927
|
-
parameters,
|
|
4928
|
-
result: syntheticResult ?? "Execution skipped",
|
|
4929
|
-
executionTimeMs: 0
|
|
4930
|
-
};
|
|
4931
|
-
} else {
|
|
4932
|
-
result = await this.executor.execute(call);
|
|
4933
|
-
}
|
|
4934
|
-
const originalResult = result.result;
|
|
4935
|
-
if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
|
|
4936
|
-
const context = {
|
|
4937
|
-
iteration: this.iteration,
|
|
4938
|
-
gadgetName: result.gadgetName,
|
|
4939
|
-
invocationId: result.invocationId,
|
|
4940
|
-
parameters,
|
|
4941
|
-
executionTimeMs: result.executionTimeMs,
|
|
4942
|
-
logger: this.logger
|
|
4943
|
-
};
|
|
4944
|
-
result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
|
|
4945
|
-
}
|
|
4946
|
-
if (this.hooks.controllers?.afterGadgetExecution) {
|
|
4947
|
-
const context = {
|
|
4948
|
-
iteration: this.iteration,
|
|
4949
|
-
gadgetName: result.gadgetName,
|
|
4950
|
-
invocationId: result.invocationId,
|
|
4951
|
-
parameters,
|
|
4952
|
-
result: result.result,
|
|
4953
|
-
error: result.error,
|
|
4954
|
-
executionTimeMs: result.executionTimeMs,
|
|
4955
|
-
logger: this.logger
|
|
4956
|
-
};
|
|
4957
|
-
const action = await this.hooks.controllers.afterGadgetExecution(context);
|
|
4958
|
-
validateAfterGadgetExecutionAction(action);
|
|
4959
|
-
if (action.action === "recover" && result.error) {
|
|
4960
|
-
this.logger.info("Controller recovered from gadget error", {
|
|
4961
|
-
gadgetName: result.gadgetName,
|
|
4962
|
-
originalError: result.error
|
|
4963
|
-
});
|
|
4964
|
-
result = {
|
|
4965
|
-
...result,
|
|
4966
|
-
error: void 0,
|
|
4967
|
-
result: action.fallbackResult
|
|
4968
|
-
};
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
const completeObservers = [];
|
|
4972
|
-
if (this.hooks.observers?.onGadgetExecutionComplete) {
|
|
4973
|
-
completeObservers.push(async () => {
|
|
4974
|
-
const context = {
|
|
4975
|
-
iteration: this.iteration,
|
|
4976
|
-
gadgetName: result.gadgetName,
|
|
4977
|
-
invocationId: result.invocationId,
|
|
4978
|
-
parameters,
|
|
4979
|
-
originalResult,
|
|
4980
|
-
finalResult: result.result,
|
|
4981
|
-
error: result.error,
|
|
4982
|
-
executionTimeMs: result.executionTimeMs,
|
|
4983
|
-
breaksLoop: result.breaksLoop,
|
|
4984
|
-
cost: result.cost,
|
|
4985
|
-
logger: this.logger
|
|
4986
|
-
};
|
|
4987
|
-
await this.hooks.observers?.onGadgetExecutionComplete?.(context);
|
|
4988
|
-
});
|
|
4989
|
-
}
|
|
4990
|
-
await this.runObserversInParallel(completeObservers);
|
|
4991
|
-
this.completedResults.set(result.invocationId, result);
|
|
4992
|
-
if (result.error) {
|
|
4993
|
-
this.failedInvocations.add(result.invocationId);
|
|
4994
|
-
}
|
|
4995
|
-
events.push({ type: "gadget_result", result });
|
|
4996
|
-
return events;
|
|
4997
|
-
}
|
|
4998
|
-
/**
|
|
4999
|
-
* Execute a gadget and yield the result event.
|
|
5000
|
-
* 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.
|
|
5001
4842
|
*/
|
|
5002
4843
|
async *executeGadgetGenerator(call) {
|
|
5003
4844
|
if (call.parseError) {
|
|
@@ -5263,8 +5104,9 @@ var init_stream_processor = __esm({
|
|
|
5263
5104
|
invocationId: call.invocationId,
|
|
5264
5105
|
failedDependency: failedDep
|
|
5265
5106
|
});
|
|
5266
|
-
const
|
|
5267
|
-
|
|
5107
|
+
for await (const evt of this.executeGadgetGenerator(call)) {
|
|
5108
|
+
events.push(evt);
|
|
5109
|
+
}
|
|
5268
5110
|
} else if (action.action === "use_fallback") {
|
|
5269
5111
|
const fallbackResult = {
|
|
5270
5112
|
gadgetName: call.gadgetName,
|
|
@@ -5285,90 +5127,9 @@ var init_stream_processor = __esm({
|
|
|
5285
5127
|
}
|
|
5286
5128
|
/**
|
|
5287
5129
|
* Process pending gadgets whose dependencies are now satisfied.
|
|
5288
|
-
*
|
|
5289
|
-
*/
|
|
5290
|
-
async processPendingGadgets() {
|
|
5291
|
-
const events = [];
|
|
5292
|
-
let progress = true;
|
|
5293
|
-
while (progress && this.gadgetsAwaitingDependencies.size > 0) {
|
|
5294
|
-
progress = false;
|
|
5295
|
-
const readyToExecute = [];
|
|
5296
|
-
const readyToSkip = [];
|
|
5297
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5298
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
5299
|
-
if (failedDep) {
|
|
5300
|
-
readyToSkip.push({ call, failedDep });
|
|
5301
|
-
continue;
|
|
5302
|
-
}
|
|
5303
|
-
const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
|
|
5304
|
-
if (allSatisfied) {
|
|
5305
|
-
readyToExecute.push(call);
|
|
5306
|
-
}
|
|
5307
|
-
}
|
|
5308
|
-
for (const { call, failedDep } of readyToSkip) {
|
|
5309
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
5310
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
5311
|
-
events.push(...skipEvents);
|
|
5312
|
-
progress = true;
|
|
5313
|
-
}
|
|
5314
|
-
if (readyToExecute.length > 0) {
|
|
5315
|
-
this.logger.debug("Executing ready gadgets in parallel", {
|
|
5316
|
-
count: readyToExecute.length,
|
|
5317
|
-
invocationIds: readyToExecute.map((c) => c.invocationId)
|
|
5318
|
-
});
|
|
5319
|
-
for (const call of readyToExecute) {
|
|
5320
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
5321
|
-
}
|
|
5322
|
-
const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
|
|
5323
|
-
const results = await Promise.all(executePromises);
|
|
5324
|
-
for (const executeEvents of results) {
|
|
5325
|
-
events.push(...executeEvents);
|
|
5326
|
-
}
|
|
5327
|
-
progress = true;
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
5331
|
-
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
5332
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5333
|
-
const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
5334
|
-
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
5335
|
-
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
5336
|
-
let errorMessage;
|
|
5337
|
-
let logLevel = "warn";
|
|
5338
|
-
if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
|
|
5339
|
-
errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
|
|
5340
|
-
logLevel = "error";
|
|
5341
|
-
} else if (circularDeps.length > 0) {
|
|
5342
|
-
errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
|
|
5343
|
-
} else {
|
|
5344
|
-
errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
|
|
5345
|
-
}
|
|
5346
|
-
this.logger[logLevel]("Gadget has unresolvable dependencies", {
|
|
5347
|
-
gadgetName: call.gadgetName,
|
|
5348
|
-
invocationId,
|
|
5349
|
-
circularDependencies: circularDeps,
|
|
5350
|
-
missingDependencies: trulyMissingDeps
|
|
5351
|
-
});
|
|
5352
|
-
this.failedInvocations.add(invocationId);
|
|
5353
|
-
const skipEvent = {
|
|
5354
|
-
type: "gadget_skipped",
|
|
5355
|
-
gadgetName: call.gadgetName,
|
|
5356
|
-
invocationId,
|
|
5357
|
-
parameters: call.parameters ?? {},
|
|
5358
|
-
failedDependency: missingDeps[0],
|
|
5359
|
-
failedDependencyError: errorMessage
|
|
5360
|
-
};
|
|
5361
|
-
events.push(skipEvent);
|
|
5362
|
-
}
|
|
5363
|
-
this.gadgetsAwaitingDependencies.clear();
|
|
5364
|
-
}
|
|
5365
|
-
return events;
|
|
5366
|
-
}
|
|
5367
|
-
/**
|
|
5368
|
-
* Process pending gadgets, yielding events in real-time.
|
|
5369
|
-
* Generator version that yields events as gadgets complete.
|
|
5130
|
+
* Yields events in real-time as gadgets complete.
|
|
5370
5131
|
*
|
|
5371
|
-
*
|
|
5132
|
+
* Gadgets are executed in parallel for efficiency,
|
|
5372
5133
|
* but results are yielded as they become available.
|
|
5373
5134
|
*/
|
|
5374
5135
|
async *processPendingGadgetsGenerator() {
|
|
@@ -5378,12 +5139,16 @@ var init_stream_processor = __esm({
|
|
|
5378
5139
|
const readyToExecute = [];
|
|
5379
5140
|
const readyToSkip = [];
|
|
5380
5141
|
for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5381
|
-
const failedDep = call.dependencies.find(
|
|
5142
|
+
const failedDep = call.dependencies.find(
|
|
5143
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
5144
|
+
);
|
|
5382
5145
|
if (failedDep) {
|
|
5383
5146
|
readyToSkip.push({ call, failedDep });
|
|
5384
5147
|
continue;
|
|
5385
5148
|
}
|
|
5386
|
-
const allSatisfied = call.dependencies.every(
|
|
5149
|
+
const allSatisfied = call.dependencies.every(
|
|
5150
|
+
(dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
|
|
5151
|
+
);
|
|
5387
5152
|
if (allSatisfied) {
|
|
5388
5153
|
readyToExecute.push(call);
|
|
5389
5154
|
}
|
|
@@ -5424,7 +5189,9 @@ var init_stream_processor = __esm({
|
|
|
5424
5189
|
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
5425
5190
|
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
5426
5191
|
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
5427
|
-
const missingDeps = call.dependencies.filter(
|
|
5192
|
+
const missingDeps = call.dependencies.filter(
|
|
5193
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
5194
|
+
);
|
|
5428
5195
|
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
5429
5196
|
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
5430
5197
|
let errorMessage;
|
|
@@ -5478,10 +5245,27 @@ var init_stream_processor = __esm({
|
|
|
5478
5245
|
*/
|
|
5479
5246
|
async runObserversInParallel(observers) {
|
|
5480
5247
|
if (observers.length === 0) return;
|
|
5481
|
-
|
|
5248
|
+
await Promise.allSettled(
|
|
5482
5249
|
observers.map((observer) => this.safeObserve(observer))
|
|
5483
5250
|
);
|
|
5484
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
|
+
}
|
|
5485
5269
|
};
|
|
5486
5270
|
}
|
|
5487
5271
|
});
|
|
@@ -5544,6 +5328,9 @@ var init_agent = __esm({
|
|
|
5544
5328
|
onSubagentEvent;
|
|
5545
5329
|
// Counter for generating synthetic invocation IDs for wrapped text content
|
|
5546
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();
|
|
5547
5334
|
// Execution Tree - first-class model for nested subagent support
|
|
5548
5335
|
tree;
|
|
5549
5336
|
parentNodeId;
|
|
@@ -5853,96 +5640,22 @@ var init_agent = __esm({
|
|
|
5853
5640
|
maxIterations: this.maxIterations
|
|
5854
5641
|
});
|
|
5855
5642
|
while (currentIteration < this.maxIterations) {
|
|
5856
|
-
if (this.
|
|
5857
|
-
this.logger.info("Agent loop terminated by abort signal", {
|
|
5858
|
-
iteration: currentIteration,
|
|
5859
|
-
reason: this.signal.reason
|
|
5860
|
-
});
|
|
5861
|
-
await this.safeObserve(async () => {
|
|
5862
|
-
if (this.hooks.observers?.onAbort) {
|
|
5863
|
-
const context = {
|
|
5864
|
-
iteration: currentIteration,
|
|
5865
|
-
reason: this.signal?.reason,
|
|
5866
|
-
logger: this.logger
|
|
5867
|
-
};
|
|
5868
|
-
await this.hooks.observers.onAbort(context);
|
|
5869
|
-
}
|
|
5870
|
-
});
|
|
5643
|
+
if (await this.checkAbortAndNotify(currentIteration)) {
|
|
5871
5644
|
return;
|
|
5872
5645
|
}
|
|
5873
5646
|
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
5874
5647
|
try {
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
currentIteration
|
|
5879
|
-
);
|
|
5880
|
-
if (compactionEvent) {
|
|
5881
|
-
this.logger.info("Context compacted", {
|
|
5882
|
-
strategy: compactionEvent.strategy,
|
|
5883
|
-
tokensBefore: compactionEvent.tokensBefore,
|
|
5884
|
-
tokensAfter: compactionEvent.tokensAfter
|
|
5885
|
-
});
|
|
5886
|
-
yield { type: "compaction", event: compactionEvent };
|
|
5887
|
-
await this.safeObserve(async () => {
|
|
5888
|
-
if (this.hooks.observers?.onCompaction) {
|
|
5889
|
-
await this.hooks.observers.onCompaction({
|
|
5890
|
-
iteration: currentIteration,
|
|
5891
|
-
event: compactionEvent,
|
|
5892
|
-
// biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
|
|
5893
|
-
stats: this.compactionManager.getStats(),
|
|
5894
|
-
logger: this.logger
|
|
5895
|
-
});
|
|
5896
|
-
}
|
|
5897
|
-
});
|
|
5898
|
-
}
|
|
5648
|
+
const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
|
|
5649
|
+
if (compactionEvent) {
|
|
5650
|
+
yield compactionEvent;
|
|
5899
5651
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
};
|
|
5907
|
-
await this.safeObserve(async () => {
|
|
5908
|
-
if (this.hooks.observers?.onLLMCallStart) {
|
|
5909
|
-
const context = {
|
|
5910
|
-
iteration: currentIteration,
|
|
5911
|
-
options: llmOptions,
|
|
5912
|
-
logger: this.logger
|
|
5913
|
-
};
|
|
5914
|
-
await this.hooks.observers.onLLMCallStart(context);
|
|
5915
|
-
}
|
|
5916
|
-
});
|
|
5917
|
-
if (this.hooks.controllers?.beforeLLMCall) {
|
|
5918
|
-
const context = {
|
|
5919
|
-
iteration: currentIteration,
|
|
5920
|
-
maxIterations: this.maxIterations,
|
|
5921
|
-
options: llmOptions,
|
|
5922
|
-
logger: this.logger
|
|
5923
|
-
};
|
|
5924
|
-
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
5925
|
-
validateBeforeLLMCallAction(action);
|
|
5926
|
-
if (action.action === "skip") {
|
|
5927
|
-
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
5928
|
-
this.conversation.addAssistantMessage(action.syntheticResponse);
|
|
5929
|
-
yield { type: "text", content: action.syntheticResponse };
|
|
5930
|
-
break;
|
|
5931
|
-
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
5932
|
-
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
5933
|
-
}
|
|
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;
|
|
5934
5658
|
}
|
|
5935
|
-
await this.safeObserve(async () => {
|
|
5936
|
-
if (this.hooks.observers?.onLLMCallReady) {
|
|
5937
|
-
const context = {
|
|
5938
|
-
iteration: currentIteration,
|
|
5939
|
-
maxIterations: this.maxIterations,
|
|
5940
|
-
options: llmOptions,
|
|
5941
|
-
logger: this.logger
|
|
5942
|
-
};
|
|
5943
|
-
await this.hooks.observers.onLLMCallReady(context);
|
|
5944
|
-
}
|
|
5945
|
-
});
|
|
5946
5659
|
this.logger.info("Calling LLM", { model: this.model });
|
|
5947
5660
|
this.logger.silly("LLM request details", {
|
|
5948
5661
|
model: llmOptions.model,
|
|
@@ -5978,7 +5691,10 @@ var init_agent = __esm({
|
|
|
5978
5691
|
tree: this.tree,
|
|
5979
5692
|
parentNodeId: currentLLMNodeId,
|
|
5980
5693
|
// Gadgets are children of this LLM call
|
|
5981
|
-
baseDepth: this.baseDepth
|
|
5694
|
+
baseDepth: this.baseDepth,
|
|
5695
|
+
// Cross-iteration dependency tracking
|
|
5696
|
+
priorCompletedInvocations: this.completedInvocationIds,
|
|
5697
|
+
priorFailedInvocations: this.failedInvocationIds
|
|
5982
5698
|
});
|
|
5983
5699
|
let streamMetadata = null;
|
|
5984
5700
|
let gadgetCallCount = 0;
|
|
@@ -6001,6 +5717,12 @@ var init_agent = __esm({
|
|
|
6001
5717
|
if (!streamMetadata) {
|
|
6002
5718
|
throw new Error("Stream processing completed without metadata event");
|
|
6003
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
|
+
}
|
|
6004
5726
|
const result = streamMetadata;
|
|
6005
5727
|
this.logger.info("LLM response completed", {
|
|
6006
5728
|
finishReason: result.finishReason,
|
|
@@ -6024,81 +5746,21 @@ var init_agent = __esm({
|
|
|
6024
5746
|
await this.hooks.observers.onLLMCallComplete(context);
|
|
6025
5747
|
}
|
|
6026
5748
|
});
|
|
6027
|
-
this.
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
logger: this.logger
|
|
6043
|
-
};
|
|
6044
|
-
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
6045
|
-
validateAfterLLMCallAction(action);
|
|
6046
|
-
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
6047
|
-
finalMessage = action.modifiedMessage;
|
|
6048
|
-
}
|
|
6049
|
-
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
6050
|
-
for (const msg of action.messages) {
|
|
6051
|
-
if (msg.role === "user") {
|
|
6052
|
-
this.conversation.addUserMessage(msg.content);
|
|
6053
|
-
} else if (msg.role === "assistant") {
|
|
6054
|
-
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
6055
|
-
} else if (msg.role === "system") {
|
|
6056
|
-
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
6057
|
-
}
|
|
6058
|
-
}
|
|
6059
|
-
}
|
|
6060
|
-
}
|
|
6061
|
-
if (result.didExecuteGadgets) {
|
|
6062
|
-
if (this.textWithGadgetsHandler) {
|
|
6063
|
-
const textContent = textOutputs.join("");
|
|
6064
|
-
if (textContent.trim()) {
|
|
6065
|
-
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
6066
|
-
const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
|
|
6067
|
-
this.conversation.addGadgetCallResult(
|
|
6068
|
-
gadgetName,
|
|
6069
|
-
parameterMapping(textContent),
|
|
6070
|
-
resultMapping ? resultMapping(textContent) : textContent,
|
|
6071
|
-
syntheticId
|
|
6072
|
-
);
|
|
6073
|
-
}
|
|
6074
|
-
}
|
|
6075
|
-
for (const output of gadgetResults) {
|
|
6076
|
-
if (output.type === "gadget_result") {
|
|
6077
|
-
const gadgetResult = output.result;
|
|
6078
|
-
this.conversation.addGadgetCallResult(
|
|
6079
|
-
gadgetResult.gadgetName,
|
|
6080
|
-
gadgetResult.parameters,
|
|
6081
|
-
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
6082
|
-
gadgetResult.invocationId,
|
|
6083
|
-
gadgetResult.media,
|
|
6084
|
-
gadgetResult.mediaIds
|
|
6085
|
-
);
|
|
6086
|
-
}
|
|
6087
|
-
}
|
|
6088
|
-
} else {
|
|
6089
|
-
if (finalMessage.trim()) {
|
|
6090
|
-
const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
|
|
6091
|
-
this.conversation.addGadgetCallResult(
|
|
6092
|
-
"TellUser",
|
|
6093
|
-
{ message: finalMessage, done: false, type: "info" },
|
|
6094
|
-
`\u2139\uFE0F ${finalMessage}`,
|
|
6095
|
-
syntheticId
|
|
6096
|
-
);
|
|
6097
|
-
}
|
|
6098
|
-
const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
|
|
6099
|
-
if (shouldBreak) {
|
|
6100
|
-
break;
|
|
6101
|
-
}
|
|
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;
|
|
6102
5764
|
}
|
|
6103
5765
|
if (result.shouldBreakLoop) {
|
|
6104
5766
|
this.logger.info("Loop terminated by gadget or processor");
|
|
@@ -6251,6 +5913,210 @@ var init_agent = __esm({
|
|
|
6251
5913
|
}
|
|
6252
5914
|
};
|
|
6253
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
|
+
}
|
|
6254
6120
|
/**
|
|
6255
6121
|
* Run agent with named event handlers (syntactic sugar).
|
|
6256
6122
|
*
|
|
@@ -9783,11 +9649,13 @@ var init_model_registry = __esm({
|
|
|
9783
9649
|
}
|
|
9784
9650
|
/**
|
|
9785
9651
|
* Get model specification by model ID
|
|
9786
|
-
* @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')
|
|
9787
9654
|
* @returns ModelSpec if found, undefined otherwise
|
|
9788
9655
|
*/
|
|
9789
9656
|
getModelSpec(modelId) {
|
|
9790
|
-
|
|
9657
|
+
const normalizedId = modelId.includes(":") ? modelId.split(":")[1] : modelId;
|
|
9658
|
+
return this.modelSpecs.find((model) => model.modelId === normalizedId);
|
|
9791
9659
|
}
|
|
9792
9660
|
/**
|
|
9793
9661
|
* List all models, optionally filtered by provider
|