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/index.cjs
CHANGED
|
@@ -1877,6 +1877,9 @@ function parseEnvBoolean(value) {
|
|
|
1877
1877
|
if (normalized === "false" || normalized === "0") return false;
|
|
1878
1878
|
return void 0;
|
|
1879
1879
|
}
|
|
1880
|
+
function stripAnsi(str) {
|
|
1881
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1882
|
+
}
|
|
1880
1883
|
function createLogger(options = {}) {
|
|
1881
1884
|
const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
|
|
1882
1885
|
const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
|
|
@@ -1885,36 +1888,62 @@ function createLogger(options = {}) {
|
|
|
1885
1888
|
const defaultType = options.type ?? "pretty";
|
|
1886
1889
|
const name = options.name ?? "llmist";
|
|
1887
1890
|
const logReset = options.logReset ?? envLogReset ?? false;
|
|
1888
|
-
|
|
1889
|
-
let finalType = defaultType;
|
|
1890
|
-
if (envLogFile) {
|
|
1891
|
+
if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
|
|
1891
1892
|
try {
|
|
1893
|
+
if (sharedLogFileStream) {
|
|
1894
|
+
sharedLogFileStream.end();
|
|
1895
|
+
sharedLogFileStream = void 0;
|
|
1896
|
+
}
|
|
1892
1897
|
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(envLogFile), { recursive: true });
|
|
1893
1898
|
const flags = logReset ? "w" : "a";
|
|
1894
|
-
|
|
1895
|
-
|
|
1899
|
+
sharedLogFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags });
|
|
1900
|
+
sharedLogFilePath = envLogFile;
|
|
1901
|
+
logFileInitialized = true;
|
|
1902
|
+
writeErrorCount = 0;
|
|
1903
|
+
writeErrorReported = false;
|
|
1904
|
+
sharedLogFileStream.on("error", (error) => {
|
|
1905
|
+
writeErrorCount++;
|
|
1906
|
+
if (!writeErrorReported) {
|
|
1907
|
+
console.error(`[llmist] Log file write error: ${error.message}`);
|
|
1908
|
+
writeErrorReported = true;
|
|
1909
|
+
}
|
|
1910
|
+
if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
|
|
1911
|
+
console.error(
|
|
1912
|
+
`[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
|
|
1913
|
+
);
|
|
1914
|
+
sharedLogFileStream?.end();
|
|
1915
|
+
sharedLogFileStream = void 0;
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1896
1918
|
} catch (error) {
|
|
1897
1919
|
console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
|
|
1898
1920
|
}
|
|
1899
1921
|
}
|
|
1922
|
+
const useFileLogging = Boolean(sharedLogFileStream);
|
|
1900
1923
|
const logger = new import_tslog.Logger({
|
|
1901
1924
|
name,
|
|
1902
1925
|
minLevel,
|
|
1903
|
-
type:
|
|
1904
|
-
//
|
|
1905
|
-
hideLogPositionForProduction:
|
|
1906
|
-
|
|
1907
|
-
|
|
1926
|
+
type: useFileLogging ? "pretty" : defaultType,
|
|
1927
|
+
// Hide log position for file logging and non-pretty types
|
|
1928
|
+
hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
|
|
1929
|
+
prettyLogTemplate: LOG_TEMPLATE,
|
|
1930
|
+
// Use overwrite to redirect tslog's formatted output to file instead of console
|
|
1931
|
+
overwrite: useFileLogging ? {
|
|
1932
|
+
transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
|
|
1933
|
+
if (!sharedLogFileStream) return;
|
|
1934
|
+
const meta = stripAnsi(logMetaMarkup);
|
|
1935
|
+
const args = logArgs.map(
|
|
1936
|
+
(arg) => typeof arg === "string" ? stripAnsi(arg) : JSON.stringify(arg)
|
|
1937
|
+
);
|
|
1938
|
+
const line = `${meta}${args.join(" ")}
|
|
1939
|
+
`;
|
|
1940
|
+
sharedLogFileStream.write(line);
|
|
1941
|
+
}
|
|
1942
|
+
} : void 0
|
|
1908
1943
|
});
|
|
1909
|
-
if (logFileStream) {
|
|
1910
|
-
logger.attachTransport((logObj) => {
|
|
1911
|
-
logFileStream?.write(`${JSON.stringify(logObj)}
|
|
1912
|
-
`);
|
|
1913
|
-
});
|
|
1914
|
-
}
|
|
1915
1944
|
return logger;
|
|
1916
1945
|
}
|
|
1917
|
-
var import_node_fs, import_node_path2, import_tslog, LEVEL_NAME_TO_ID, defaultLogger;
|
|
1946
|
+
var import_node_fs, import_node_path2, import_tslog, LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
|
|
1918
1947
|
var init_logger = __esm({
|
|
1919
1948
|
"src/logging/logger.ts"() {
|
|
1920
1949
|
"use strict";
|
|
@@ -1930,6 +1959,11 @@ var init_logger = __esm({
|
|
|
1930
1959
|
error: 5,
|
|
1931
1960
|
fatal: 6
|
|
1932
1961
|
};
|
|
1962
|
+
logFileInitialized = false;
|
|
1963
|
+
writeErrorCount = 0;
|
|
1964
|
+
writeErrorReported = false;
|
|
1965
|
+
MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
|
|
1966
|
+
LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
|
|
1933
1967
|
defaultLogger = createLogger();
|
|
1934
1968
|
}
|
|
1935
1969
|
});
|
|
@@ -5900,11 +5934,13 @@ var init_model_registry = __esm({
|
|
|
5900
5934
|
}
|
|
5901
5935
|
/**
|
|
5902
5936
|
* Get model specification by model ID
|
|
5903
|
-
* @param modelId - Full model identifier
|
|
5937
|
+
* @param modelId - Full model identifier, optionally with provider prefix
|
|
5938
|
+
* (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929', 'anthropic:claude-sonnet-4-5')
|
|
5904
5939
|
* @returns ModelSpec if found, undefined otherwise
|
|
5905
5940
|
*/
|
|
5906
5941
|
getModelSpec(modelId) {
|
|
5907
|
-
|
|
5942
|
+
const normalizedId = modelId.includes(":") ? modelId.split(":")[1] : modelId;
|
|
5943
|
+
return this.modelSpecs.find((model) => model.modelId === normalizedId);
|
|
5908
5944
|
}
|
|
5909
5945
|
/**
|
|
5910
5946
|
* List all models, optionally filtered by provider
|
|
@@ -7146,9 +7182,8 @@ var init_cost_reporting_client = __esm({
|
|
|
7146
7182
|
*/
|
|
7147
7183
|
reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
|
|
7148
7184
|
if (inputTokens === 0 && outputTokens === 0) return;
|
|
7149
|
-
const modelName = model.includes(":") ? model.split(":")[1] : model;
|
|
7150
7185
|
const estimate = this.client.modelRegistry.estimateCost(
|
|
7151
|
-
|
|
7186
|
+
model,
|
|
7152
7187
|
inputTokens,
|
|
7153
7188
|
outputTokens,
|
|
7154
7189
|
cachedInputTokens,
|
|
@@ -7471,10 +7506,11 @@ function getHostExportsInternal() {
|
|
|
7471
7506
|
}
|
|
7472
7507
|
return cachedHostExports;
|
|
7473
7508
|
}
|
|
7474
|
-
var import_zod2, cachedHostExports, GadgetExecutor;
|
|
7509
|
+
var import_fast_deep_equal, import_zod2, cachedHostExports, GadgetExecutor;
|
|
7475
7510
|
var init_executor = __esm({
|
|
7476
7511
|
"src/gadgets/executor.ts"() {
|
|
7477
7512
|
"use strict";
|
|
7513
|
+
import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
|
|
7478
7514
|
import_zod2 = require("zod");
|
|
7479
7515
|
init_builder();
|
|
7480
7516
|
init_client();
|
|
@@ -7585,7 +7621,7 @@ var init_executor = __esm({
|
|
|
7585
7621
|
try {
|
|
7586
7622
|
const cleanedRaw = stripMarkdownFences(call.parametersRaw);
|
|
7587
7623
|
const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
|
|
7588
|
-
const parametersWereModified = !
|
|
7624
|
+
const parametersWereModified = !(0, import_fast_deep_equal.default)(rawParameters, initialParse);
|
|
7589
7625
|
if (parametersWereModified) {
|
|
7590
7626
|
this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
|
|
7591
7627
|
gadgetName: call.gadgetName
|
|
@@ -7662,7 +7698,9 @@ var init_executor = __esm({
|
|
|
7662
7698
|
nodeId: gadgetNodeId,
|
|
7663
7699
|
depth: gadgetDepth,
|
|
7664
7700
|
// Host exports for external gadgets to use host's llmist classes
|
|
7665
|
-
hostExports: getHostExportsInternal()
|
|
7701
|
+
hostExports: getHostExportsInternal(),
|
|
7702
|
+
// Logger for structured logging (respects CLI's log level/file config)
|
|
7703
|
+
logger: this.logger
|
|
7666
7704
|
};
|
|
7667
7705
|
let rawResult;
|
|
7668
7706
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -7832,27 +7870,6 @@ var init_executor = __esm({
|
|
|
7832
7870
|
async executeAll(calls) {
|
|
7833
7871
|
return Promise.all(calls.map((call) => this.execute(call)));
|
|
7834
7872
|
}
|
|
7835
|
-
/**
|
|
7836
|
-
* Deep equality check for objects/arrays.
|
|
7837
|
-
* Used to detect if parameters were modified by an interceptor.
|
|
7838
|
-
*/
|
|
7839
|
-
deepEquals(a, b) {
|
|
7840
|
-
if (a === b) return true;
|
|
7841
|
-
if (a === null || b === null) return a === b;
|
|
7842
|
-
if (typeof a !== typeof b) return false;
|
|
7843
|
-
if (typeof a !== "object") return a === b;
|
|
7844
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
7845
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
7846
|
-
if (a.length !== b.length) return false;
|
|
7847
|
-
return a.every((val, i) => this.deepEquals(val, b[i]));
|
|
7848
|
-
}
|
|
7849
|
-
const aObj = a;
|
|
7850
|
-
const bObj = b;
|
|
7851
|
-
const aKeys = Object.keys(aObj);
|
|
7852
|
-
const bKeys = Object.keys(bObj);
|
|
7853
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
7854
|
-
return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
|
|
7855
|
-
}
|
|
7856
7873
|
};
|
|
7857
7874
|
}
|
|
7858
7875
|
});
|
|
@@ -7890,6 +7907,11 @@ var init_stream_processor = __esm({
|
|
|
7890
7907
|
inFlightExecutions = /* @__PURE__ */ new Map();
|
|
7891
7908
|
/** Queue of completed gadget results ready to be yielded (for real-time streaming) */
|
|
7892
7909
|
completedResultsQueue = [];
|
|
7910
|
+
// Cross-iteration dependency tracking
|
|
7911
|
+
/** Invocation IDs completed in previous iterations (read-only reference from Agent) */
|
|
7912
|
+
priorCompletedInvocations;
|
|
7913
|
+
/** Invocation IDs that failed in previous iterations (read-only reference from Agent) */
|
|
7914
|
+
priorFailedInvocations;
|
|
7893
7915
|
constructor(options) {
|
|
7894
7916
|
this.iteration = options.iteration;
|
|
7895
7917
|
this.registry = options.registry;
|
|
@@ -7898,6 +7920,8 @@ var init_stream_processor = __esm({
|
|
|
7898
7920
|
this.tree = options.tree;
|
|
7899
7921
|
this.parentNodeId = options.parentNodeId ?? null;
|
|
7900
7922
|
this.baseDepth = options.baseDepth ?? 0;
|
|
7923
|
+
this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
|
|
7924
|
+
this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
|
|
7901
7925
|
this.parser = new GadgetCallParser({
|
|
7902
7926
|
startPrefix: options.gadgetStartPrefix,
|
|
7903
7927
|
endPrefix: options.gadgetEndPrefix,
|
|
@@ -8096,62 +8120,11 @@ var init_stream_processor = __esm({
|
|
|
8096
8120
|
}
|
|
8097
8121
|
return [{ type: "text", content }];
|
|
8098
8122
|
}
|
|
8099
|
-
/**
|
|
8100
|
-
* Process a gadget call through the full lifecycle, handling dependencies.
|
|
8101
|
-
*
|
|
8102
|
-
* Gadgets without dependencies (or with all dependencies satisfied) execute immediately.
|
|
8103
|
-
* Gadgets with unsatisfied dependencies are queued for later execution.
|
|
8104
|
-
* After each execution, pending gadgets are checked to see if they can now run.
|
|
8105
|
-
*/
|
|
8106
|
-
async processGadgetCall(call) {
|
|
8107
|
-
const events = [];
|
|
8108
|
-
events.push({ type: "gadget_call", call });
|
|
8109
|
-
if (call.dependencies.length > 0) {
|
|
8110
|
-
if (call.dependencies.includes(call.invocationId)) {
|
|
8111
|
-
this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
|
|
8112
|
-
gadgetName: call.gadgetName,
|
|
8113
|
-
invocationId: call.invocationId
|
|
8114
|
-
});
|
|
8115
|
-
this.failedInvocations.add(call.invocationId);
|
|
8116
|
-
const skipEvent = {
|
|
8117
|
-
type: "gadget_skipped",
|
|
8118
|
-
gadgetName: call.gadgetName,
|
|
8119
|
-
invocationId: call.invocationId,
|
|
8120
|
-
parameters: call.parameters ?? {},
|
|
8121
|
-
failedDependency: call.invocationId,
|
|
8122
|
-
failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
|
|
8123
|
-
};
|
|
8124
|
-
events.push(skipEvent);
|
|
8125
|
-
return events;
|
|
8126
|
-
}
|
|
8127
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
8128
|
-
if (failedDep) {
|
|
8129
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
8130
|
-
events.push(...skipEvents);
|
|
8131
|
-
return events;
|
|
8132
|
-
}
|
|
8133
|
-
const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
8134
|
-
if (unsatisfied.length > 0) {
|
|
8135
|
-
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
8136
|
-
gadgetName: call.gadgetName,
|
|
8137
|
-
invocationId: call.invocationId,
|
|
8138
|
-
waitingOn: unsatisfied
|
|
8139
|
-
});
|
|
8140
|
-
this.gadgetsAwaitingDependencies.set(call.invocationId, call);
|
|
8141
|
-
return events;
|
|
8142
|
-
}
|
|
8143
|
-
}
|
|
8144
|
-
const executeEvents = await this.executeGadgetWithHooks(call);
|
|
8145
|
-
events.push(...executeEvents);
|
|
8146
|
-
const triggeredEvents = await this.processPendingGadgets();
|
|
8147
|
-
events.push(...triggeredEvents);
|
|
8148
|
-
return events;
|
|
8149
|
-
}
|
|
8150
8123
|
/**
|
|
8151
8124
|
* Process a gadget call, yielding events in real-time.
|
|
8152
8125
|
*
|
|
8153
|
-
*
|
|
8154
|
-
*
|
|
8126
|
+
* Yields gadget_call event IMMEDIATELY when parsed (before execution),
|
|
8127
|
+
* enabling real-time UI feedback.
|
|
8155
8128
|
*/
|
|
8156
8129
|
async *processGadgetCallGenerator(call) {
|
|
8157
8130
|
yield { type: "gadget_call", call };
|
|
@@ -8182,7 +8155,9 @@ var init_stream_processor = __esm({
|
|
|
8182
8155
|
yield skipEvent;
|
|
8183
8156
|
return;
|
|
8184
8157
|
}
|
|
8185
|
-
const failedDep = call.dependencies.find(
|
|
8158
|
+
const failedDep = call.dependencies.find(
|
|
8159
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
8160
|
+
);
|
|
8186
8161
|
if (failedDep) {
|
|
8187
8162
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
8188
8163
|
for (const evt of skipEvents) {
|
|
@@ -8190,7 +8165,9 @@ var init_stream_processor = __esm({
|
|
|
8190
8165
|
}
|
|
8191
8166
|
return;
|
|
8192
8167
|
}
|
|
8193
|
-
const unsatisfied = call.dependencies.filter(
|
|
8168
|
+
const unsatisfied = call.dependencies.filter(
|
|
8169
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
8170
|
+
);
|
|
8194
8171
|
if (unsatisfied.length > 0) {
|
|
8195
8172
|
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
8196
8173
|
gadgetName: call.gadgetName,
|
|
@@ -8212,143 +8189,9 @@ var init_stream_processor = __esm({
|
|
|
8212
8189
|
this.inFlightExecutions.set(call.invocationId, executionPromise);
|
|
8213
8190
|
}
|
|
8214
8191
|
/**
|
|
8215
|
-
* Execute a gadget through the full hook lifecycle.
|
|
8216
|
-
*
|
|
8217
|
-
*
|
|
8218
|
-
*/
|
|
8219
|
-
async executeGadgetWithHooks(call) {
|
|
8220
|
-
const events = [];
|
|
8221
|
-
if (call.parseError) {
|
|
8222
|
-
this.logger.warn("Gadget has parse error", {
|
|
8223
|
-
gadgetName: call.gadgetName,
|
|
8224
|
-
error: call.parseError,
|
|
8225
|
-
rawParameters: call.parametersRaw
|
|
8226
|
-
});
|
|
8227
|
-
}
|
|
8228
|
-
let parameters = call.parameters ?? {};
|
|
8229
|
-
if (this.hooks.interceptors?.interceptGadgetParameters) {
|
|
8230
|
-
const context = {
|
|
8231
|
-
iteration: this.iteration,
|
|
8232
|
-
gadgetName: call.gadgetName,
|
|
8233
|
-
invocationId: call.invocationId,
|
|
8234
|
-
logger: this.logger
|
|
8235
|
-
};
|
|
8236
|
-
parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
|
|
8237
|
-
}
|
|
8238
|
-
call.parameters = parameters;
|
|
8239
|
-
let shouldSkip = false;
|
|
8240
|
-
let syntheticResult;
|
|
8241
|
-
if (this.hooks.controllers?.beforeGadgetExecution) {
|
|
8242
|
-
const context = {
|
|
8243
|
-
iteration: this.iteration,
|
|
8244
|
-
gadgetName: call.gadgetName,
|
|
8245
|
-
invocationId: call.invocationId,
|
|
8246
|
-
parameters,
|
|
8247
|
-
logger: this.logger
|
|
8248
|
-
};
|
|
8249
|
-
const action = await this.hooks.controllers.beforeGadgetExecution(context);
|
|
8250
|
-
validateBeforeGadgetExecutionAction(action);
|
|
8251
|
-
if (action.action === "skip") {
|
|
8252
|
-
shouldSkip = true;
|
|
8253
|
-
syntheticResult = action.syntheticResult;
|
|
8254
|
-
this.logger.info("Controller skipped gadget execution", {
|
|
8255
|
-
gadgetName: call.gadgetName
|
|
8256
|
-
});
|
|
8257
|
-
}
|
|
8258
|
-
}
|
|
8259
|
-
const startObservers = [];
|
|
8260
|
-
if (this.hooks.observers?.onGadgetExecutionStart) {
|
|
8261
|
-
startObservers.push(async () => {
|
|
8262
|
-
const context = {
|
|
8263
|
-
iteration: this.iteration,
|
|
8264
|
-
gadgetName: call.gadgetName,
|
|
8265
|
-
invocationId: call.invocationId,
|
|
8266
|
-
parameters,
|
|
8267
|
-
logger: this.logger
|
|
8268
|
-
};
|
|
8269
|
-
await this.hooks.observers?.onGadgetExecutionStart?.(context);
|
|
8270
|
-
});
|
|
8271
|
-
}
|
|
8272
|
-
await this.runObserversInParallel(startObservers);
|
|
8273
|
-
let result;
|
|
8274
|
-
if (shouldSkip) {
|
|
8275
|
-
result = {
|
|
8276
|
-
gadgetName: call.gadgetName,
|
|
8277
|
-
invocationId: call.invocationId,
|
|
8278
|
-
parameters,
|
|
8279
|
-
result: syntheticResult ?? "Execution skipped",
|
|
8280
|
-
executionTimeMs: 0
|
|
8281
|
-
};
|
|
8282
|
-
} else {
|
|
8283
|
-
result = await this.executor.execute(call);
|
|
8284
|
-
}
|
|
8285
|
-
const originalResult = result.result;
|
|
8286
|
-
if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
|
|
8287
|
-
const context = {
|
|
8288
|
-
iteration: this.iteration,
|
|
8289
|
-
gadgetName: result.gadgetName,
|
|
8290
|
-
invocationId: result.invocationId,
|
|
8291
|
-
parameters,
|
|
8292
|
-
executionTimeMs: result.executionTimeMs,
|
|
8293
|
-
logger: this.logger
|
|
8294
|
-
};
|
|
8295
|
-
result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
|
|
8296
|
-
}
|
|
8297
|
-
if (this.hooks.controllers?.afterGadgetExecution) {
|
|
8298
|
-
const context = {
|
|
8299
|
-
iteration: this.iteration,
|
|
8300
|
-
gadgetName: result.gadgetName,
|
|
8301
|
-
invocationId: result.invocationId,
|
|
8302
|
-
parameters,
|
|
8303
|
-
result: result.result,
|
|
8304
|
-
error: result.error,
|
|
8305
|
-
executionTimeMs: result.executionTimeMs,
|
|
8306
|
-
logger: this.logger
|
|
8307
|
-
};
|
|
8308
|
-
const action = await this.hooks.controllers.afterGadgetExecution(context);
|
|
8309
|
-
validateAfterGadgetExecutionAction(action);
|
|
8310
|
-
if (action.action === "recover" && result.error) {
|
|
8311
|
-
this.logger.info("Controller recovered from gadget error", {
|
|
8312
|
-
gadgetName: result.gadgetName,
|
|
8313
|
-
originalError: result.error
|
|
8314
|
-
});
|
|
8315
|
-
result = {
|
|
8316
|
-
...result,
|
|
8317
|
-
error: void 0,
|
|
8318
|
-
result: action.fallbackResult
|
|
8319
|
-
};
|
|
8320
|
-
}
|
|
8321
|
-
}
|
|
8322
|
-
const completeObservers = [];
|
|
8323
|
-
if (this.hooks.observers?.onGadgetExecutionComplete) {
|
|
8324
|
-
completeObservers.push(async () => {
|
|
8325
|
-
const context = {
|
|
8326
|
-
iteration: this.iteration,
|
|
8327
|
-
gadgetName: result.gadgetName,
|
|
8328
|
-
invocationId: result.invocationId,
|
|
8329
|
-
parameters,
|
|
8330
|
-
originalResult,
|
|
8331
|
-
finalResult: result.result,
|
|
8332
|
-
error: result.error,
|
|
8333
|
-
executionTimeMs: result.executionTimeMs,
|
|
8334
|
-
breaksLoop: result.breaksLoop,
|
|
8335
|
-
cost: result.cost,
|
|
8336
|
-
logger: this.logger
|
|
8337
|
-
};
|
|
8338
|
-
await this.hooks.observers?.onGadgetExecutionComplete?.(context);
|
|
8339
|
-
});
|
|
8340
|
-
}
|
|
8341
|
-
await this.runObserversInParallel(completeObservers);
|
|
8342
|
-
this.completedResults.set(result.invocationId, result);
|
|
8343
|
-
if (result.error) {
|
|
8344
|
-
this.failedInvocations.add(result.invocationId);
|
|
8345
|
-
}
|
|
8346
|
-
events.push({ type: "gadget_result", result });
|
|
8347
|
-
return events;
|
|
8348
|
-
}
|
|
8349
|
-
/**
|
|
8350
|
-
* Execute a gadget and yield the result event.
|
|
8351
|
-
* Generator version that yields gadget_result immediately when execution completes.
|
|
8192
|
+
* Execute a gadget through the full hook lifecycle and yield events.
|
|
8193
|
+
* Handles parameter interception, before/after controllers, observers,
|
|
8194
|
+
* execution, result interception, and tree tracking.
|
|
8352
8195
|
*/
|
|
8353
8196
|
async *executeGadgetGenerator(call) {
|
|
8354
8197
|
if (call.parseError) {
|
|
@@ -8614,8 +8457,9 @@ var init_stream_processor = __esm({
|
|
|
8614
8457
|
invocationId: call.invocationId,
|
|
8615
8458
|
failedDependency: failedDep
|
|
8616
8459
|
});
|
|
8617
|
-
const
|
|
8618
|
-
|
|
8460
|
+
for await (const evt of this.executeGadgetGenerator(call)) {
|
|
8461
|
+
events.push(evt);
|
|
8462
|
+
}
|
|
8619
8463
|
} else if (action.action === "use_fallback") {
|
|
8620
8464
|
const fallbackResult = {
|
|
8621
8465
|
gadgetName: call.gadgetName,
|
|
@@ -8636,90 +8480,9 @@ var init_stream_processor = __esm({
|
|
|
8636
8480
|
}
|
|
8637
8481
|
/**
|
|
8638
8482
|
* Process pending gadgets whose dependencies are now satisfied.
|
|
8639
|
-
*
|
|
8640
|
-
*/
|
|
8641
|
-
async processPendingGadgets() {
|
|
8642
|
-
const events = [];
|
|
8643
|
-
let progress = true;
|
|
8644
|
-
while (progress && this.gadgetsAwaitingDependencies.size > 0) {
|
|
8645
|
-
progress = false;
|
|
8646
|
-
const readyToExecute = [];
|
|
8647
|
-
const readyToSkip = [];
|
|
8648
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
8649
|
-
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
8650
|
-
if (failedDep) {
|
|
8651
|
-
readyToSkip.push({ call, failedDep });
|
|
8652
|
-
continue;
|
|
8653
|
-
}
|
|
8654
|
-
const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
|
|
8655
|
-
if (allSatisfied) {
|
|
8656
|
-
readyToExecute.push(call);
|
|
8657
|
-
}
|
|
8658
|
-
}
|
|
8659
|
-
for (const { call, failedDep } of readyToSkip) {
|
|
8660
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
8661
|
-
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
8662
|
-
events.push(...skipEvents);
|
|
8663
|
-
progress = true;
|
|
8664
|
-
}
|
|
8665
|
-
if (readyToExecute.length > 0) {
|
|
8666
|
-
this.logger.debug("Executing ready gadgets in parallel", {
|
|
8667
|
-
count: readyToExecute.length,
|
|
8668
|
-
invocationIds: readyToExecute.map((c) => c.invocationId)
|
|
8669
|
-
});
|
|
8670
|
-
for (const call of readyToExecute) {
|
|
8671
|
-
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
8672
|
-
}
|
|
8673
|
-
const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
|
|
8674
|
-
const results = await Promise.all(executePromises);
|
|
8675
|
-
for (const executeEvents of results) {
|
|
8676
|
-
events.push(...executeEvents);
|
|
8677
|
-
}
|
|
8678
|
-
progress = true;
|
|
8679
|
-
}
|
|
8680
|
-
}
|
|
8681
|
-
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
8682
|
-
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
8683
|
-
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
8684
|
-
const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
8685
|
-
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
8686
|
-
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
8687
|
-
let errorMessage;
|
|
8688
|
-
let logLevel = "warn";
|
|
8689
|
-
if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
|
|
8690
|
-
errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
|
|
8691
|
-
logLevel = "error";
|
|
8692
|
-
} else if (circularDeps.length > 0) {
|
|
8693
|
-
errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
|
|
8694
|
-
} else {
|
|
8695
|
-
errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
|
|
8696
|
-
}
|
|
8697
|
-
this.logger[logLevel]("Gadget has unresolvable dependencies", {
|
|
8698
|
-
gadgetName: call.gadgetName,
|
|
8699
|
-
invocationId,
|
|
8700
|
-
circularDependencies: circularDeps,
|
|
8701
|
-
missingDependencies: trulyMissingDeps
|
|
8702
|
-
});
|
|
8703
|
-
this.failedInvocations.add(invocationId);
|
|
8704
|
-
const skipEvent = {
|
|
8705
|
-
type: "gadget_skipped",
|
|
8706
|
-
gadgetName: call.gadgetName,
|
|
8707
|
-
invocationId,
|
|
8708
|
-
parameters: call.parameters ?? {},
|
|
8709
|
-
failedDependency: missingDeps[0],
|
|
8710
|
-
failedDependencyError: errorMessage
|
|
8711
|
-
};
|
|
8712
|
-
events.push(skipEvent);
|
|
8713
|
-
}
|
|
8714
|
-
this.gadgetsAwaitingDependencies.clear();
|
|
8715
|
-
}
|
|
8716
|
-
return events;
|
|
8717
|
-
}
|
|
8718
|
-
/**
|
|
8719
|
-
* Process pending gadgets, yielding events in real-time.
|
|
8720
|
-
* Generator version that yields events as gadgets complete.
|
|
8483
|
+
* Yields events in real-time as gadgets complete.
|
|
8721
8484
|
*
|
|
8722
|
-
*
|
|
8485
|
+
* Gadgets are executed in parallel for efficiency,
|
|
8723
8486
|
* but results are yielded as they become available.
|
|
8724
8487
|
*/
|
|
8725
8488
|
async *processPendingGadgetsGenerator() {
|
|
@@ -8729,12 +8492,16 @@ var init_stream_processor = __esm({
|
|
|
8729
8492
|
const readyToExecute = [];
|
|
8730
8493
|
const readyToSkip = [];
|
|
8731
8494
|
for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
8732
|
-
const failedDep = call.dependencies.find(
|
|
8495
|
+
const failedDep = call.dependencies.find(
|
|
8496
|
+
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
8497
|
+
);
|
|
8733
8498
|
if (failedDep) {
|
|
8734
8499
|
readyToSkip.push({ call, failedDep });
|
|
8735
8500
|
continue;
|
|
8736
8501
|
}
|
|
8737
|
-
const allSatisfied = call.dependencies.every(
|
|
8502
|
+
const allSatisfied = call.dependencies.every(
|
|
8503
|
+
(dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
|
|
8504
|
+
);
|
|
8738
8505
|
if (allSatisfied) {
|
|
8739
8506
|
readyToExecute.push(call);
|
|
8740
8507
|
}
|
|
@@ -8775,7 +8542,9 @@ var init_stream_processor = __esm({
|
|
|
8775
8542
|
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
8776
8543
|
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
8777
8544
|
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
8778
|
-
const missingDeps = call.dependencies.filter(
|
|
8545
|
+
const missingDeps = call.dependencies.filter(
|
|
8546
|
+
(dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
|
|
8547
|
+
);
|
|
8779
8548
|
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
8780
8549
|
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
8781
8550
|
let errorMessage;
|
|
@@ -8829,10 +8598,27 @@ var init_stream_processor = __esm({
|
|
|
8829
8598
|
*/
|
|
8830
8599
|
async runObserversInParallel(observers) {
|
|
8831
8600
|
if (observers.length === 0) return;
|
|
8832
|
-
|
|
8601
|
+
await Promise.allSettled(
|
|
8833
8602
|
observers.map((observer) => this.safeObserve(observer))
|
|
8834
8603
|
);
|
|
8835
8604
|
}
|
|
8605
|
+
// ==========================================================================
|
|
8606
|
+
// Public accessors for cross-iteration dependency tracking
|
|
8607
|
+
// ==========================================================================
|
|
8608
|
+
/**
|
|
8609
|
+
* Get all invocation IDs that completed successfully in this iteration.
|
|
8610
|
+
* Used by Agent to accumulate completed IDs across iterations.
|
|
8611
|
+
*/
|
|
8612
|
+
getCompletedInvocationIds() {
|
|
8613
|
+
return new Set(this.completedResults.keys());
|
|
8614
|
+
}
|
|
8615
|
+
/**
|
|
8616
|
+
* Get all invocation IDs that failed in this iteration.
|
|
8617
|
+
* Used by Agent to accumulate failed IDs across iterations.
|
|
8618
|
+
*/
|
|
8619
|
+
getFailedInvocationIds() {
|
|
8620
|
+
return new Set(this.failedInvocations);
|
|
8621
|
+
}
|
|
8836
8622
|
};
|
|
8837
8623
|
}
|
|
8838
8624
|
});
|
|
@@ -8895,6 +8681,9 @@ var init_agent = __esm({
|
|
|
8895
8681
|
onSubagentEvent;
|
|
8896
8682
|
// Counter for generating synthetic invocation IDs for wrapped text content
|
|
8897
8683
|
syntheticInvocationCounter = 0;
|
|
8684
|
+
// Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
|
|
8685
|
+
completedInvocationIds = /* @__PURE__ */ new Set();
|
|
8686
|
+
failedInvocationIds = /* @__PURE__ */ new Set();
|
|
8898
8687
|
// Execution Tree - first-class model for nested subagent support
|
|
8899
8688
|
tree;
|
|
8900
8689
|
parentNodeId;
|
|
@@ -9204,96 +8993,22 @@ var init_agent = __esm({
|
|
|
9204
8993
|
maxIterations: this.maxIterations
|
|
9205
8994
|
});
|
|
9206
8995
|
while (currentIteration < this.maxIterations) {
|
|
9207
|
-
if (this.
|
|
9208
|
-
this.logger.info("Agent loop terminated by abort signal", {
|
|
9209
|
-
iteration: currentIteration,
|
|
9210
|
-
reason: this.signal.reason
|
|
9211
|
-
});
|
|
9212
|
-
await this.safeObserve(async () => {
|
|
9213
|
-
if (this.hooks.observers?.onAbort) {
|
|
9214
|
-
const context = {
|
|
9215
|
-
iteration: currentIteration,
|
|
9216
|
-
reason: this.signal?.reason,
|
|
9217
|
-
logger: this.logger
|
|
9218
|
-
};
|
|
9219
|
-
await this.hooks.observers.onAbort(context);
|
|
9220
|
-
}
|
|
9221
|
-
});
|
|
8996
|
+
if (await this.checkAbortAndNotify(currentIteration)) {
|
|
9222
8997
|
return;
|
|
9223
8998
|
}
|
|
9224
8999
|
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
9225
9000
|
try {
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
currentIteration
|
|
9230
|
-
);
|
|
9231
|
-
if (compactionEvent) {
|
|
9232
|
-
this.logger.info("Context compacted", {
|
|
9233
|
-
strategy: compactionEvent.strategy,
|
|
9234
|
-
tokensBefore: compactionEvent.tokensBefore,
|
|
9235
|
-
tokensAfter: compactionEvent.tokensAfter
|
|
9236
|
-
});
|
|
9237
|
-
yield { type: "compaction", event: compactionEvent };
|
|
9238
|
-
await this.safeObserve(async () => {
|
|
9239
|
-
if (this.hooks.observers?.onCompaction) {
|
|
9240
|
-
await this.hooks.observers.onCompaction({
|
|
9241
|
-
iteration: currentIteration,
|
|
9242
|
-
event: compactionEvent,
|
|
9243
|
-
// biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
|
|
9244
|
-
stats: this.compactionManager.getStats(),
|
|
9245
|
-
logger: this.logger
|
|
9246
|
-
});
|
|
9247
|
-
}
|
|
9248
|
-
});
|
|
9249
|
-
}
|
|
9001
|
+
const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
|
|
9002
|
+
if (compactionEvent) {
|
|
9003
|
+
yield compactionEvent;
|
|
9250
9004
|
}
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
};
|
|
9258
|
-
await this.safeObserve(async () => {
|
|
9259
|
-
if (this.hooks.observers?.onLLMCallStart) {
|
|
9260
|
-
const context = {
|
|
9261
|
-
iteration: currentIteration,
|
|
9262
|
-
options: llmOptions,
|
|
9263
|
-
logger: this.logger
|
|
9264
|
-
};
|
|
9265
|
-
await this.hooks.observers.onLLMCallStart(context);
|
|
9266
|
-
}
|
|
9267
|
-
});
|
|
9268
|
-
if (this.hooks.controllers?.beforeLLMCall) {
|
|
9269
|
-
const context = {
|
|
9270
|
-
iteration: currentIteration,
|
|
9271
|
-
maxIterations: this.maxIterations,
|
|
9272
|
-
options: llmOptions,
|
|
9273
|
-
logger: this.logger
|
|
9274
|
-
};
|
|
9275
|
-
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
9276
|
-
validateBeforeLLMCallAction(action);
|
|
9277
|
-
if (action.action === "skip") {
|
|
9278
|
-
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
9279
|
-
this.conversation.addAssistantMessage(action.syntheticResponse);
|
|
9280
|
-
yield { type: "text", content: action.syntheticResponse };
|
|
9281
|
-
break;
|
|
9282
|
-
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
9283
|
-
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
9284
|
-
}
|
|
9005
|
+
const prepared = await this.prepareLLMCall(currentIteration);
|
|
9006
|
+
const llmOptions = prepared.options;
|
|
9007
|
+
if (prepared.skipWithSynthetic !== void 0) {
|
|
9008
|
+
this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
|
|
9009
|
+
yield { type: "text", content: prepared.skipWithSynthetic };
|
|
9010
|
+
break;
|
|
9285
9011
|
}
|
|
9286
|
-
await this.safeObserve(async () => {
|
|
9287
|
-
if (this.hooks.observers?.onLLMCallReady) {
|
|
9288
|
-
const context = {
|
|
9289
|
-
iteration: currentIteration,
|
|
9290
|
-
maxIterations: this.maxIterations,
|
|
9291
|
-
options: llmOptions,
|
|
9292
|
-
logger: this.logger
|
|
9293
|
-
};
|
|
9294
|
-
await this.hooks.observers.onLLMCallReady(context);
|
|
9295
|
-
}
|
|
9296
|
-
});
|
|
9297
9012
|
this.logger.info("Calling LLM", { model: this.model });
|
|
9298
9013
|
this.logger.silly("LLM request details", {
|
|
9299
9014
|
model: llmOptions.model,
|
|
@@ -9329,7 +9044,10 @@ var init_agent = __esm({
|
|
|
9329
9044
|
tree: this.tree,
|
|
9330
9045
|
parentNodeId: currentLLMNodeId,
|
|
9331
9046
|
// Gadgets are children of this LLM call
|
|
9332
|
-
baseDepth: this.baseDepth
|
|
9047
|
+
baseDepth: this.baseDepth,
|
|
9048
|
+
// Cross-iteration dependency tracking
|
|
9049
|
+
priorCompletedInvocations: this.completedInvocationIds,
|
|
9050
|
+
priorFailedInvocations: this.failedInvocationIds
|
|
9333
9051
|
});
|
|
9334
9052
|
let streamMetadata = null;
|
|
9335
9053
|
let gadgetCallCount = 0;
|
|
@@ -9352,6 +9070,12 @@ var init_agent = __esm({
|
|
|
9352
9070
|
if (!streamMetadata) {
|
|
9353
9071
|
throw new Error("Stream processing completed without metadata event");
|
|
9354
9072
|
}
|
|
9073
|
+
for (const id of processor.getCompletedInvocationIds()) {
|
|
9074
|
+
this.completedInvocationIds.add(id);
|
|
9075
|
+
}
|
|
9076
|
+
for (const id of processor.getFailedInvocationIds()) {
|
|
9077
|
+
this.failedInvocationIds.add(id);
|
|
9078
|
+
}
|
|
9355
9079
|
const result = streamMetadata;
|
|
9356
9080
|
this.logger.info("LLM response completed", {
|
|
9357
9081
|
finishReason: result.finishReason,
|
|
@@ -9375,81 +9099,21 @@ var init_agent = __esm({
|
|
|
9375
9099
|
await this.hooks.observers.onLLMCallComplete(context);
|
|
9376
9100
|
}
|
|
9377
9101
|
});
|
|
9378
|
-
this.
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
logger: this.logger
|
|
9394
|
-
};
|
|
9395
|
-
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
9396
|
-
validateAfterLLMCallAction(action);
|
|
9397
|
-
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
9398
|
-
finalMessage = action.modifiedMessage;
|
|
9399
|
-
}
|
|
9400
|
-
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
9401
|
-
for (const msg of action.messages) {
|
|
9402
|
-
if (msg.role === "user") {
|
|
9403
|
-
this.conversation.addUserMessage(msg.content);
|
|
9404
|
-
} else if (msg.role === "assistant") {
|
|
9405
|
-
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
9406
|
-
} else if (msg.role === "system") {
|
|
9407
|
-
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
9408
|
-
}
|
|
9409
|
-
}
|
|
9410
|
-
}
|
|
9411
|
-
}
|
|
9412
|
-
if (result.didExecuteGadgets) {
|
|
9413
|
-
if (this.textWithGadgetsHandler) {
|
|
9414
|
-
const textContent = textOutputs.join("");
|
|
9415
|
-
if (textContent.trim()) {
|
|
9416
|
-
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
9417
|
-
const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
|
|
9418
|
-
this.conversation.addGadgetCallResult(
|
|
9419
|
-
gadgetName,
|
|
9420
|
-
parameterMapping(textContent),
|
|
9421
|
-
resultMapping ? resultMapping(textContent) : textContent,
|
|
9422
|
-
syntheticId
|
|
9423
|
-
);
|
|
9424
|
-
}
|
|
9425
|
-
}
|
|
9426
|
-
for (const output of gadgetResults) {
|
|
9427
|
-
if (output.type === "gadget_result") {
|
|
9428
|
-
const gadgetResult = output.result;
|
|
9429
|
-
this.conversation.addGadgetCallResult(
|
|
9430
|
-
gadgetResult.gadgetName,
|
|
9431
|
-
gadgetResult.parameters,
|
|
9432
|
-
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
9433
|
-
gadgetResult.invocationId,
|
|
9434
|
-
gadgetResult.media,
|
|
9435
|
-
gadgetResult.mediaIds
|
|
9436
|
-
);
|
|
9437
|
-
}
|
|
9438
|
-
}
|
|
9439
|
-
} else {
|
|
9440
|
-
if (finalMessage.trim()) {
|
|
9441
|
-
const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
|
|
9442
|
-
this.conversation.addGadgetCallResult(
|
|
9443
|
-
"TellUser",
|
|
9444
|
-
{ message: finalMessage, done: false, type: "info" },
|
|
9445
|
-
`\u2139\uFE0F ${finalMessage}`,
|
|
9446
|
-
syntheticId
|
|
9447
|
-
);
|
|
9448
|
-
}
|
|
9449
|
-
const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
|
|
9450
|
-
if (shouldBreak) {
|
|
9451
|
-
break;
|
|
9452
|
-
}
|
|
9102
|
+
this.completeLLMCallInTree(currentLLMNodeId, result);
|
|
9103
|
+
const finalMessage = await this.processAfterLLMCallController(
|
|
9104
|
+
currentIteration,
|
|
9105
|
+
llmOptions,
|
|
9106
|
+
result,
|
|
9107
|
+
gadgetCallCount
|
|
9108
|
+
);
|
|
9109
|
+
const shouldBreakFromTextOnly = await this.updateConversationWithResults(
|
|
9110
|
+
result.didExecuteGadgets,
|
|
9111
|
+
textOutputs,
|
|
9112
|
+
gadgetResults,
|
|
9113
|
+
finalMessage
|
|
9114
|
+
);
|
|
9115
|
+
if (shouldBreakFromTextOnly) {
|
|
9116
|
+
break;
|
|
9453
9117
|
}
|
|
9454
9118
|
if (result.shouldBreakLoop) {
|
|
9455
9119
|
this.logger.info("Loop terminated by gadget or processor");
|
|
@@ -9602,6 +9266,210 @@ var init_agent = __esm({
|
|
|
9602
9266
|
}
|
|
9603
9267
|
};
|
|
9604
9268
|
}
|
|
9269
|
+
// ==========================================================================
|
|
9270
|
+
// Agent Loop Helper Methods (extracted from run() for readability)
|
|
9271
|
+
// ==========================================================================
|
|
9272
|
+
/**
|
|
9273
|
+
* Check abort signal and notify observers if aborted.
|
|
9274
|
+
* @returns true if agent should terminate
|
|
9275
|
+
*/
|
|
9276
|
+
async checkAbortAndNotify(iteration) {
|
|
9277
|
+
if (!this.signal?.aborted) return false;
|
|
9278
|
+
this.logger.info("Agent loop terminated by abort signal", {
|
|
9279
|
+
iteration,
|
|
9280
|
+
reason: this.signal.reason
|
|
9281
|
+
});
|
|
9282
|
+
await this.safeObserve(async () => {
|
|
9283
|
+
if (this.hooks.observers?.onAbort) {
|
|
9284
|
+
const context = {
|
|
9285
|
+
iteration,
|
|
9286
|
+
reason: this.signal?.reason,
|
|
9287
|
+
logger: this.logger
|
|
9288
|
+
};
|
|
9289
|
+
await this.hooks.observers.onAbort(context);
|
|
9290
|
+
}
|
|
9291
|
+
});
|
|
9292
|
+
return true;
|
|
9293
|
+
}
|
|
9294
|
+
/**
|
|
9295
|
+
* Check and perform context compaction if needed.
|
|
9296
|
+
* @returns compaction stream event if compaction occurred, null otherwise
|
|
9297
|
+
*/
|
|
9298
|
+
async checkAndPerformCompaction(iteration) {
|
|
9299
|
+
if (!this.compactionManager) return null;
|
|
9300
|
+
const compactionEvent = await this.compactionManager.checkAndCompact(
|
|
9301
|
+
this.conversation,
|
|
9302
|
+
iteration
|
|
9303
|
+
);
|
|
9304
|
+
if (!compactionEvent) return null;
|
|
9305
|
+
this.logger.info("Context compacted", {
|
|
9306
|
+
strategy: compactionEvent.strategy,
|
|
9307
|
+
tokensBefore: compactionEvent.tokensBefore,
|
|
9308
|
+
tokensAfter: compactionEvent.tokensAfter
|
|
9309
|
+
});
|
|
9310
|
+
await this.safeObserve(async () => {
|
|
9311
|
+
if (this.hooks.observers?.onCompaction) {
|
|
9312
|
+
await this.hooks.observers.onCompaction({
|
|
9313
|
+
iteration,
|
|
9314
|
+
event: compactionEvent,
|
|
9315
|
+
// biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
|
|
9316
|
+
stats: this.compactionManager.getStats(),
|
|
9317
|
+
logger: this.logger
|
|
9318
|
+
});
|
|
9319
|
+
}
|
|
9320
|
+
});
|
|
9321
|
+
return { type: "compaction", event: compactionEvent };
|
|
9322
|
+
}
|
|
9323
|
+
/**
|
|
9324
|
+
* Prepare LLM call options and process beforeLLMCall controller.
|
|
9325
|
+
* @returns options and optional skipWithSynthetic response if controller wants to skip
|
|
9326
|
+
*/
|
|
9327
|
+
async prepareLLMCall(iteration) {
|
|
9328
|
+
let llmOptions = {
|
|
9329
|
+
model: this.model,
|
|
9330
|
+
messages: this.conversation.getMessages(),
|
|
9331
|
+
temperature: this.temperature,
|
|
9332
|
+
maxTokens: this.defaultMaxTokens,
|
|
9333
|
+
signal: this.signal
|
|
9334
|
+
};
|
|
9335
|
+
await this.safeObserve(async () => {
|
|
9336
|
+
if (this.hooks.observers?.onLLMCallStart) {
|
|
9337
|
+
const context = {
|
|
9338
|
+
iteration,
|
|
9339
|
+
options: llmOptions,
|
|
9340
|
+
logger: this.logger
|
|
9341
|
+
};
|
|
9342
|
+
await this.hooks.observers.onLLMCallStart(context);
|
|
9343
|
+
}
|
|
9344
|
+
});
|
|
9345
|
+
if (this.hooks.controllers?.beforeLLMCall) {
|
|
9346
|
+
const context = {
|
|
9347
|
+
iteration,
|
|
9348
|
+
maxIterations: this.maxIterations,
|
|
9349
|
+
options: llmOptions,
|
|
9350
|
+
logger: this.logger
|
|
9351
|
+
};
|
|
9352
|
+
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
9353
|
+
validateBeforeLLMCallAction(action);
|
|
9354
|
+
if (action.action === "skip") {
|
|
9355
|
+
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
9356
|
+
return { options: llmOptions, skipWithSynthetic: action.syntheticResponse };
|
|
9357
|
+
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
9358
|
+
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
9359
|
+
}
|
|
9360
|
+
}
|
|
9361
|
+
await this.safeObserve(async () => {
|
|
9362
|
+
if (this.hooks.observers?.onLLMCallReady) {
|
|
9363
|
+
const context = {
|
|
9364
|
+
iteration,
|
|
9365
|
+
maxIterations: this.maxIterations,
|
|
9366
|
+
options: llmOptions,
|
|
9367
|
+
logger: this.logger
|
|
9368
|
+
};
|
|
9369
|
+
await this.hooks.observers.onLLMCallReady(context);
|
|
9370
|
+
}
|
|
9371
|
+
});
|
|
9372
|
+
return { options: llmOptions };
|
|
9373
|
+
}
|
|
9374
|
+
/**
|
|
9375
|
+
* Calculate cost and complete LLM call in execution tree.
|
|
9376
|
+
*/
|
|
9377
|
+
completeLLMCallInTree(nodeId, result) {
|
|
9378
|
+
const llmCost = this.client.modelRegistry?.estimateCost?.(
|
|
9379
|
+
this.model,
|
|
9380
|
+
result.usage?.inputTokens ?? 0,
|
|
9381
|
+
result.usage?.outputTokens ?? 0,
|
|
9382
|
+
result.usage?.cachedInputTokens ?? 0,
|
|
9383
|
+
result.usage?.cacheCreationInputTokens ?? 0
|
|
9384
|
+
)?.totalCost;
|
|
9385
|
+
this.tree.completeLLMCall(nodeId, {
|
|
9386
|
+
response: result.rawResponse,
|
|
9387
|
+
usage: result.usage,
|
|
9388
|
+
finishReason: result.finishReason,
|
|
9389
|
+
cost: llmCost
|
|
9390
|
+
});
|
|
9391
|
+
}
|
|
9392
|
+
/**
|
|
9393
|
+
* Process afterLLMCall controller and return modified final message.
|
|
9394
|
+
*/
|
|
9395
|
+
async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
|
|
9396
|
+
let finalMessage = result.finalMessage;
|
|
9397
|
+
if (!this.hooks.controllers?.afterLLMCall) {
|
|
9398
|
+
return finalMessage;
|
|
9399
|
+
}
|
|
9400
|
+
const context = {
|
|
9401
|
+
iteration,
|
|
9402
|
+
maxIterations: this.maxIterations,
|
|
9403
|
+
options: llmOptions,
|
|
9404
|
+
finishReason: result.finishReason,
|
|
9405
|
+
usage: result.usage,
|
|
9406
|
+
finalMessage: result.finalMessage,
|
|
9407
|
+
gadgetCallCount,
|
|
9408
|
+
logger: this.logger
|
|
9409
|
+
};
|
|
9410
|
+
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
9411
|
+
validateAfterLLMCallAction(action);
|
|
9412
|
+
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
9413
|
+
finalMessage = action.modifiedMessage;
|
|
9414
|
+
}
|
|
9415
|
+
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
9416
|
+
for (const msg of action.messages) {
|
|
9417
|
+
if (msg.role === "user") {
|
|
9418
|
+
this.conversation.addUserMessage(msg.content);
|
|
9419
|
+
} else if (msg.role === "assistant") {
|
|
9420
|
+
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
9421
|
+
} else if (msg.role === "system") {
|
|
9422
|
+
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
return finalMessage;
|
|
9427
|
+
}
|
|
9428
|
+
/**
|
|
9429
|
+
* Update conversation history with gadget results or text-only response.
|
|
9430
|
+
* @returns true if loop should break (text-only handler requested termination)
|
|
9431
|
+
*/
|
|
9432
|
+
async updateConversationWithResults(didExecuteGadgets, textOutputs, gadgetResults, finalMessage) {
|
|
9433
|
+
if (didExecuteGadgets) {
|
|
9434
|
+
if (this.textWithGadgetsHandler) {
|
|
9435
|
+
const textContent = textOutputs.join("");
|
|
9436
|
+
if (textContent.trim()) {
|
|
9437
|
+
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
9438
|
+
const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
|
|
9439
|
+
this.conversation.addGadgetCallResult(
|
|
9440
|
+
gadgetName,
|
|
9441
|
+
parameterMapping(textContent),
|
|
9442
|
+
resultMapping ? resultMapping(textContent) : textContent,
|
|
9443
|
+
syntheticId
|
|
9444
|
+
);
|
|
9445
|
+
}
|
|
9446
|
+
}
|
|
9447
|
+
for (const output of gadgetResults) {
|
|
9448
|
+
if (output.type === "gadget_result") {
|
|
9449
|
+
const gadgetResult = output.result;
|
|
9450
|
+
this.conversation.addGadgetCallResult(
|
|
9451
|
+
gadgetResult.gadgetName,
|
|
9452
|
+
gadgetResult.parameters,
|
|
9453
|
+
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
9454
|
+
gadgetResult.invocationId,
|
|
9455
|
+
gadgetResult.media,
|
|
9456
|
+
gadgetResult.mediaIds
|
|
9457
|
+
);
|
|
9458
|
+
}
|
|
9459
|
+
}
|
|
9460
|
+
return false;
|
|
9461
|
+
}
|
|
9462
|
+
if (finalMessage.trim()) {
|
|
9463
|
+
const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
|
|
9464
|
+
this.conversation.addGadgetCallResult(
|
|
9465
|
+
"TellUser",
|
|
9466
|
+
{ message: finalMessage, done: false, type: "info" },
|
|
9467
|
+
`\u2139\uFE0F ${finalMessage}`,
|
|
9468
|
+
syntheticId
|
|
9469
|
+
);
|
|
9470
|
+
}
|
|
9471
|
+
return await this.handleTextOnlyResponse(finalMessage);
|
|
9472
|
+
}
|
|
9605
9473
|
/**
|
|
9606
9474
|
* Run agent with named event handlers (syntactic sugar).
|
|
9607
9475
|
*
|