llmist 18.1.0 → 18.3.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/dist/index.cjs +339 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -2
- package/dist/index.d.ts +147 -2
- package/dist/index.js +333 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1305,7 +1305,8 @@ function resolveRetryConfig(config) {
|
|
|
1305
1305
|
onRetriesExhausted: config.onRetriesExhausted,
|
|
1306
1306
|
shouldRetry: config.shouldRetry,
|
|
1307
1307
|
respectRetryAfter: config.respectRetryAfter ?? DEFAULT_RETRY_CONFIG.respectRetryAfter,
|
|
1308
|
-
maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs
|
|
1308
|
+
maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs,
|
|
1309
|
+
retryOnEmpty: config.retryOnEmpty ?? DEFAULT_RETRY_CONFIG.retryOnEmpty
|
|
1309
1310
|
};
|
|
1310
1311
|
}
|
|
1311
1312
|
function getErrorStatusCode(error) {
|
|
@@ -1557,8 +1558,9 @@ var init_retry = __esm({
|
|
|
1557
1558
|
factor: 2,
|
|
1558
1559
|
randomize: true,
|
|
1559
1560
|
respectRetryAfter: true,
|
|
1560
|
-
maxRetryAfterMs: 12e4
|
|
1561
|
+
maxRetryAfterMs: 12e4,
|
|
1561
1562
|
// 2 minutes cap
|
|
1563
|
+
retryOnEmpty: true
|
|
1562
1564
|
};
|
|
1563
1565
|
}
|
|
1564
1566
|
});
|
|
@@ -2465,6 +2467,18 @@ async function runWithHandlers(agentGenerator, handlers) {
|
|
|
2465
2467
|
});
|
|
2466
2468
|
}
|
|
2467
2469
|
break;
|
|
2470
|
+
case "gadget_args_partial":
|
|
2471
|
+
if (handlers.onGadgetArgsPartial) {
|
|
2472
|
+
await handlers.onGadgetArgsPartial({
|
|
2473
|
+
gadgetName: event.gadgetName,
|
|
2474
|
+
invocationId: event.invocationId,
|
|
2475
|
+
fieldPath: event.fieldPath,
|
|
2476
|
+
value: event.value,
|
|
2477
|
+
delta: event.delta,
|
|
2478
|
+
isFieldComplete: event.isFieldComplete
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
break;
|
|
2468
2482
|
case "gadget_result":
|
|
2469
2483
|
if (handlers.onGadgetResult) {
|
|
2470
2484
|
await handlers.onGadgetResult(event.result);
|
|
@@ -3773,11 +3787,51 @@ var init_output_limit_manager = __esm({
|
|
|
3773
3787
|
}
|
|
3774
3788
|
});
|
|
3775
3789
|
|
|
3790
|
+
// src/core/errors.ts
|
|
3791
|
+
function isAbortError(error) {
|
|
3792
|
+
if (!(error instanceof Error)) return false;
|
|
3793
|
+
if (error.name === "AbortError") return true;
|
|
3794
|
+
if (error.name === "APIConnectionAbortedError") return true;
|
|
3795
|
+
if (error.name === "APIUserAbortError") return true;
|
|
3796
|
+
const message = error.message.toLowerCase();
|
|
3797
|
+
if (message.includes("abort")) return true;
|
|
3798
|
+
if (message.includes("cancelled")) return true;
|
|
3799
|
+
if (message.includes("canceled")) return true;
|
|
3800
|
+
return false;
|
|
3801
|
+
}
|
|
3802
|
+
var EmptyCompletionError;
|
|
3803
|
+
var init_errors2 = __esm({
|
|
3804
|
+
"src/core/errors.ts"() {
|
|
3805
|
+
"use strict";
|
|
3806
|
+
EmptyCompletionError = class extends Error {
|
|
3807
|
+
/** Agent iteration on which the empty completion was observed. */
|
|
3808
|
+
iteration;
|
|
3809
|
+
/** Finish reason reported alongside the empty body (often null). */
|
|
3810
|
+
finishReason;
|
|
3811
|
+
constructor(params) {
|
|
3812
|
+
super(
|
|
3813
|
+
`LLM returned an empty completion (no text, tool calls, or reasoning) on iteration ${params.iteration}`
|
|
3814
|
+
);
|
|
3815
|
+
this.name = "EmptyCompletionError";
|
|
3816
|
+
this.iteration = params.iteration;
|
|
3817
|
+
this.finishReason = params.finishReason;
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
});
|
|
3822
|
+
|
|
3776
3823
|
// src/agent/retry-orchestrator.ts
|
|
3824
|
+
function isEmptyCompletion(meta) {
|
|
3825
|
+
if (meta.didExecuteGadgets) return false;
|
|
3826
|
+
if (meta.rawResponse?.trim() || meta.finalMessage?.trim()) return false;
|
|
3827
|
+
if (meta.thinkingContent?.trim()) return false;
|
|
3828
|
+
return true;
|
|
3829
|
+
}
|
|
3777
3830
|
var RetryOrchestrator;
|
|
3778
3831
|
var init_retry_orchestrator = __esm({
|
|
3779
3832
|
"src/agent/retry-orchestrator.ts"() {
|
|
3780
3833
|
"use strict";
|
|
3834
|
+
init_errors2();
|
|
3781
3835
|
init_retry();
|
|
3782
3836
|
init_safe_observe();
|
|
3783
3837
|
init_tree_hook_bridge();
|
|
@@ -3843,6 +3897,7 @@ var init_retry_orchestrator = __esm({
|
|
|
3843
3897
|
let gadgetCallCount = 0;
|
|
3844
3898
|
const textOutputs = [];
|
|
3845
3899
|
const gadgetResults = [];
|
|
3900
|
+
let emptyFailure = null;
|
|
3846
3901
|
while (streamAttempt < maxStreamAttempts) {
|
|
3847
3902
|
streamAttempt++;
|
|
3848
3903
|
try {
|
|
@@ -3851,6 +3906,7 @@ var init_retry_orchestrator = __esm({
|
|
|
3851
3906
|
for await (const event of processor.process(stream2)) {
|
|
3852
3907
|
if (event.type === "stream_complete") {
|
|
3853
3908
|
streamMetadata = event;
|
|
3909
|
+
continue;
|
|
3854
3910
|
}
|
|
3855
3911
|
if (event.type === "llm_response_end") {
|
|
3856
3912
|
this.tree.endLLMResponse(llmNodeId, {
|
|
@@ -3872,43 +3928,44 @@ var init_retry_orchestrator = __esm({
|
|
|
3872
3928
|
for (const id of processor.getFailedInvocationIds()) {
|
|
3873
3929
|
this.failedInvocationIds.add(id);
|
|
3874
3930
|
}
|
|
3931
|
+
if (this.retryConfig.enabled && this.retryConfig.retryOnEmpty && streamMetadata !== null && !streamMetadata.finishReason && isEmptyCompletion(streamMetadata)) {
|
|
3932
|
+
const emptyError = new EmptyCompletionError({
|
|
3933
|
+
iteration,
|
|
3934
|
+
finishReason: streamMetadata.finishReason
|
|
3935
|
+
});
|
|
3936
|
+
if (streamAttempt < maxStreamAttempts) {
|
|
3937
|
+
await this.backoffBeforeRetry(
|
|
3938
|
+
emptyError,
|
|
3939
|
+
streamAttempt,
|
|
3940
|
+
maxStreamAttempts,
|
|
3941
|
+
iteration,
|
|
3942
|
+
llmNodeId
|
|
3943
|
+
);
|
|
3944
|
+
streamMetadata = null;
|
|
3945
|
+
gadgetCallCount = 0;
|
|
3946
|
+
textOutputs.length = 0;
|
|
3947
|
+
gadgetResults.length = 0;
|
|
3948
|
+
continue;
|
|
3949
|
+
}
|
|
3950
|
+
emptyFailure = emptyError;
|
|
3951
|
+
break;
|
|
3952
|
+
}
|
|
3953
|
+
if (streamMetadata !== null) {
|
|
3954
|
+
yield streamMetadata;
|
|
3955
|
+
}
|
|
3875
3956
|
break;
|
|
3876
3957
|
} catch (streamError) {
|
|
3877
3958
|
const error = streamError;
|
|
3878
3959
|
const canRetry = this.retryConfig.enabled && streamAttempt < maxStreamAttempts;
|
|
3879
3960
|
const shouldRetryError = this.retryConfig.shouldRetry ? this.retryConfig.shouldRetry(error) : isRetryableError(error);
|
|
3880
3961
|
if (canRetry && shouldRetryError) {
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
`Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
|
|
3888
|
-
{
|
|
3889
|
-
error: error.message,
|
|
3890
|
-
retriesLeft: maxStreamAttempts - streamAttempt,
|
|
3891
|
-
delayMs: Math.round(finalDelay),
|
|
3892
|
-
retryAfterMs
|
|
3893
|
-
}
|
|
3962
|
+
await this.backoffBeforeRetry(
|
|
3963
|
+
error,
|
|
3964
|
+
streamAttempt,
|
|
3965
|
+
maxStreamAttempts,
|
|
3966
|
+
iteration,
|
|
3967
|
+
llmNodeId
|
|
3894
3968
|
);
|
|
3895
|
-
this.retryConfig.onRetry?.(error, streamAttempt);
|
|
3896
|
-
await safeObserve(async () => {
|
|
3897
|
-
if (this.hooks.observers?.onRetryAttempt) {
|
|
3898
|
-
const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
|
|
3899
|
-
const hookContext = {
|
|
3900
|
-
iteration,
|
|
3901
|
-
attemptNumber: streamAttempt,
|
|
3902
|
-
retriesLeft: maxStreamAttempts - streamAttempt,
|
|
3903
|
-
error,
|
|
3904
|
-
retryAfterMs: retryAfterMs ?? void 0,
|
|
3905
|
-
logger: this.logger,
|
|
3906
|
-
subagentContext
|
|
3907
|
-
};
|
|
3908
|
-
await this.hooks.observers.onRetryAttempt(hookContext);
|
|
3909
|
-
}
|
|
3910
|
-
}, this.logger);
|
|
3911
|
-
await this.sleep(finalDelay);
|
|
3912
3969
|
streamMetadata = null;
|
|
3913
3970
|
gadgetCallCount = 0;
|
|
3914
3971
|
textOutputs.length = 0;
|
|
@@ -3925,8 +3982,55 @@ var init_retry_orchestrator = __esm({
|
|
|
3925
3982
|
throw error;
|
|
3926
3983
|
}
|
|
3927
3984
|
}
|
|
3985
|
+
if (emptyFailure !== null) {
|
|
3986
|
+
this.logger.error(`LLM returned empty completions on all ${streamAttempt} attempts`, {
|
|
3987
|
+
iteration
|
|
3988
|
+
});
|
|
3989
|
+
this.retryConfig.onRetriesExhausted?.(emptyFailure, streamAttempt);
|
|
3990
|
+
throw emptyFailure;
|
|
3991
|
+
}
|
|
3928
3992
|
return streamMetadata !== null ? { streamMetadata, textOutputs, gadgetResults, gadgetCallCount } : null;
|
|
3929
3993
|
}
|
|
3994
|
+
/**
|
|
3995
|
+
* Apply the configured backoff before a retry attempt: compute the delay
|
|
3996
|
+
* (Retry-After hint or exponential backoff, with optional jitter), emit the
|
|
3997
|
+
* retry log, fire the `onRetry` callback and `onRetryAttempt` observer, then
|
|
3998
|
+
* sleep. Shared by the error-retry and empty-completion-retry paths so both
|
|
3999
|
+
* honour identical backoff and observer semantics.
|
|
4000
|
+
*/
|
|
4001
|
+
async backoffBeforeRetry(error, streamAttempt, maxStreamAttempts, iteration, llmNodeId) {
|
|
4002
|
+
const retryAfterMs = this.retryConfig.respectRetryAfter ? extractRetryAfterMs(error) : null;
|
|
4003
|
+
const baseDelay = this.retryConfig.minTimeout * this.retryConfig.factor ** (streamAttempt - 1);
|
|
4004
|
+
const cappedBaseDelay = Math.min(baseDelay, this.retryConfig.maxTimeout);
|
|
4005
|
+
const delay = retryAfterMs !== null ? Math.min(retryAfterMs, this.retryConfig.maxRetryAfterMs) : cappedBaseDelay;
|
|
4006
|
+
const finalDelay = this.retryConfig.randomize ? delay * (0.5 + Math.random()) : delay;
|
|
4007
|
+
this.logger.warn(
|
|
4008
|
+
`Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
|
|
4009
|
+
{
|
|
4010
|
+
error: error.message,
|
|
4011
|
+
retriesLeft: maxStreamAttempts - streamAttempt,
|
|
4012
|
+
delayMs: Math.round(finalDelay),
|
|
4013
|
+
retryAfterMs
|
|
4014
|
+
}
|
|
4015
|
+
);
|
|
4016
|
+
this.retryConfig.onRetry?.(error, streamAttempt);
|
|
4017
|
+
await safeObserve(async () => {
|
|
4018
|
+
if (this.hooks.observers?.onRetryAttempt) {
|
|
4019
|
+
const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
|
|
4020
|
+
const hookContext = {
|
|
4021
|
+
iteration,
|
|
4022
|
+
attemptNumber: streamAttempt,
|
|
4023
|
+
retriesLeft: maxStreamAttempts - streamAttempt,
|
|
4024
|
+
error,
|
|
4025
|
+
retryAfterMs: retryAfterMs ?? void 0,
|
|
4026
|
+
logger: this.logger,
|
|
4027
|
+
subagentContext
|
|
4028
|
+
};
|
|
4029
|
+
await this.hooks.observers.onRetryAttempt(hookContext);
|
|
4030
|
+
}
|
|
4031
|
+
}, this.logger);
|
|
4032
|
+
await this.sleep(finalDelay);
|
|
4033
|
+
}
|
|
3930
4034
|
};
|
|
3931
4035
|
}
|
|
3932
4036
|
});
|
|
@@ -11067,7 +11171,7 @@ var init_model_registry = __esm({
|
|
|
11067
11171
|
* Register a provider and collect its model specifications
|
|
11068
11172
|
*/
|
|
11069
11173
|
registerProvider(provider) {
|
|
11070
|
-
const specs = provider.getModelSpecs?.() ?? [];
|
|
11174
|
+
const specs = [...provider.getModelSpecs?.() ?? []];
|
|
11071
11175
|
if (specs.length > 0) {
|
|
11072
11176
|
this.modelSpecs.push(...specs);
|
|
11073
11177
|
this.providerMap.set(provider.providerId, specs);
|
|
@@ -11161,7 +11265,7 @@ var init_model_registry = __esm({
|
|
|
11161
11265
|
if (!providerId) {
|
|
11162
11266
|
return [...this.modelSpecs];
|
|
11163
11267
|
}
|
|
11164
|
-
return this.providerMap.get(providerId) ?? [];
|
|
11268
|
+
return [...this.providerMap.get(providerId) ?? []];
|
|
11165
11269
|
}
|
|
11166
11270
|
/**
|
|
11167
11271
|
* Get context window and output limits for a model
|
|
@@ -13062,13 +13166,22 @@ var init_parser2 = __esm({
|
|
|
13062
13166
|
GadgetCallParser = class {
|
|
13063
13167
|
buffer = "";
|
|
13064
13168
|
lastEmittedTextOffset = 0;
|
|
13169
|
+
/** Non-null only while a single trailing gadget block is mid-stream. */
|
|
13170
|
+
currentPartial = null;
|
|
13065
13171
|
startPrefix;
|
|
13066
13172
|
endPrefix;
|
|
13067
13173
|
argPrefix;
|
|
13174
|
+
/** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
|
|
13175
|
+
maxMarkerLength;
|
|
13068
13176
|
constructor(options = {}) {
|
|
13069
13177
|
this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
|
|
13070
13178
|
this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
|
|
13071
13179
|
this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
|
|
13180
|
+
this.maxMarkerLength = Math.max(
|
|
13181
|
+
this.startPrefix.length,
|
|
13182
|
+
this.endPrefix.length,
|
|
13183
|
+
this.argPrefix.length
|
|
13184
|
+
);
|
|
13072
13185
|
}
|
|
13073
13186
|
/**
|
|
13074
13187
|
* Extract and consume text up to the given index.
|
|
@@ -13155,12 +13268,13 @@ var init_parser2 = __esm({
|
|
|
13155
13268
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
13156
13269
|
if (metadataEndIndex === -1) break;
|
|
13157
13270
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
13158
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
13271
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
13159
13272
|
const contentStartIndex = metadataEndIndex + 1;
|
|
13160
13273
|
let partEndIndex;
|
|
13161
13274
|
let endMarkerLength = 0;
|
|
13162
|
-
const
|
|
13163
|
-
const
|
|
13275
|
+
const bodySearchStart = this.currentPartial ? contentStartIndex + Math.max(0, this.currentPartial.bodyScannedLen - (this.maxMarkerLength - 1)) : contentStartIndex;
|
|
13276
|
+
const nextStartPos = this.buffer.indexOf(this.startPrefix, bodySearchStart);
|
|
13277
|
+
const endPos = this.buffer.indexOf(this.endPrefix, bodySearchStart);
|
|
13164
13278
|
if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
|
|
13165
13279
|
partEndIndex = nextStartPos;
|
|
13166
13280
|
endMarkerLength = 0;
|
|
@@ -13168,9 +13282,22 @@ var init_parser2 = __esm({
|
|
|
13168
13282
|
partEndIndex = endPos;
|
|
13169
13283
|
endMarkerLength = this.endPrefix.length;
|
|
13170
13284
|
} else {
|
|
13285
|
+
if (!this.currentPartial) {
|
|
13286
|
+
this.currentPartial = this.newPartialState(gadgetName, invocationId, dependencies);
|
|
13287
|
+
}
|
|
13288
|
+
yield* this.emitArgPartials(
|
|
13289
|
+
this.currentPartial,
|
|
13290
|
+
contentStartIndex,
|
|
13291
|
+
this.buffer.length,
|
|
13292
|
+
false
|
|
13293
|
+
);
|
|
13171
13294
|
break;
|
|
13172
13295
|
}
|
|
13173
|
-
const
|
|
13296
|
+
const rawSlice = this.buffer.substring(contentStartIndex, partEndIndex);
|
|
13297
|
+
if (this.currentPartial) {
|
|
13298
|
+
yield* this.emitArgPartials(this.currentPartial, contentStartIndex, partEndIndex, true);
|
|
13299
|
+
}
|
|
13300
|
+
const parametersRaw = rawSlice.trim();
|
|
13174
13301
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
13175
13302
|
yield {
|
|
13176
13303
|
type: "gadget_call",
|
|
@@ -13183,6 +13310,7 @@ var init_parser2 = __esm({
|
|
|
13183
13310
|
dependencies
|
|
13184
13311
|
}
|
|
13185
13312
|
};
|
|
13313
|
+
this.currentPartial = null;
|
|
13186
13314
|
startIndex = partEndIndex + endMarkerLength;
|
|
13187
13315
|
this.lastEmittedTextOffset = startIndex;
|
|
13188
13316
|
}
|
|
@@ -13191,6 +13319,117 @@ var init_parser2 = __esm({
|
|
|
13191
13319
|
this.lastEmittedTextOffset = 0;
|
|
13192
13320
|
}
|
|
13193
13321
|
}
|
|
13322
|
+
/** Create fresh partial-tracking state for a newly-started streaming gadget. */
|
|
13323
|
+
newPartialState(gadgetName, invocationId, dependencies) {
|
|
13324
|
+
return {
|
|
13325
|
+
gadgetName,
|
|
13326
|
+
invocationId,
|
|
13327
|
+
dependencies,
|
|
13328
|
+
emittedFieldLengths: /* @__PURE__ */ new Map(),
|
|
13329
|
+
completedFields: /* @__PURE__ */ new Set(),
|
|
13330
|
+
bodyScannedLen: 0,
|
|
13331
|
+
lastArgRelOffset: -1
|
|
13332
|
+
};
|
|
13333
|
+
}
|
|
13334
|
+
/**
|
|
13335
|
+
* Emit per-field "growing value" partials for an in-progress (or, when
|
|
13336
|
+
* `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd).
|
|
13337
|
+
*
|
|
13338
|
+
* Incremental by design: each call resumes the `!!!ARG:` scan near where the last
|
|
13339
|
+
* one stopped (backing off by one marker's worth so a marker split across a chunk
|
|
13340
|
+
* boundary is still found) and only re-touches the in-progress field, so a long
|
|
13341
|
+
* streamed body costs O(new bytes) per feed instead of O(body). Every field except
|
|
13342
|
+
* the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the
|
|
13343
|
+
* last field is tentative unless `allComplete`. The tentative field holds back any
|
|
13344
|
+
* suffix that is a partial prefix of a gadget marker so it never leaks into a value.
|
|
13345
|
+
*
|
|
13346
|
+
* We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence
|
|
13347
|
+
* sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call
|
|
13348
|
+
* still strips fences from the full raw parameters.
|
|
13349
|
+
*/
|
|
13350
|
+
*emitArgPartials(state, bodyStart, bodyEnd, allComplete) {
|
|
13351
|
+
const argLen = this.argPrefix.length;
|
|
13352
|
+
let searchAbs = bodyStart + Math.max(0, state.bodyScannedLen - (this.maxMarkerLength - 1));
|
|
13353
|
+
if (state.lastArgRelOffset >= 0) {
|
|
13354
|
+
searchAbs = Math.max(searchAbs, bodyStart + state.lastArgRelOffset + argLen);
|
|
13355
|
+
}
|
|
13356
|
+
while (true) {
|
|
13357
|
+
const argAbs = this.buffer.indexOf(this.argPrefix, searchAbs);
|
|
13358
|
+
if (argAbs === -1 || argAbs >= bodyEnd) break;
|
|
13359
|
+
if (state.lastArgRelOffset >= 0) {
|
|
13360
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, argAbs, true);
|
|
13361
|
+
}
|
|
13362
|
+
state.lastArgRelOffset = argAbs - bodyStart;
|
|
13363
|
+
searchAbs = argAbs + argLen;
|
|
13364
|
+
}
|
|
13365
|
+
if (state.lastArgRelOffset >= 0) {
|
|
13366
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, bodyEnd, allComplete);
|
|
13367
|
+
}
|
|
13368
|
+
state.bodyScannedLen = bodyEnd - bodyStart;
|
|
13369
|
+
}
|
|
13370
|
+
/**
|
|
13371
|
+
* Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value
|
|
13372
|
+
* runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field
|
|
13373
|
+
* semantics of the old split-based emitter: field-path line, hold-back for a
|
|
13374
|
+
* tentative value, single trailing-newline strip.
|
|
13375
|
+
*/
|
|
13376
|
+
*emitFieldRange(state, markerAbs, valueEndAbs, complete2) {
|
|
13377
|
+
const pathStart = markerAbs + this.argPrefix.length;
|
|
13378
|
+
const newlineAbs = this.buffer.indexOf("\n", pathStart);
|
|
13379
|
+
if (newlineAbs === -1 || newlineAbs >= valueEndAbs) {
|
|
13380
|
+
if (!complete2) return;
|
|
13381
|
+
const pointer = this.buffer.substring(pathStart, valueEndAbs).trim();
|
|
13382
|
+
if (pointer) yield* this.emitFieldDelta(state, pointer, "", true);
|
|
13383
|
+
return;
|
|
13384
|
+
}
|
|
13385
|
+
const fieldPath = this.buffer.substring(pathStart, newlineAbs).trim();
|
|
13386
|
+
if (!fieldPath) return;
|
|
13387
|
+
let value = this.buffer.substring(newlineAbs + 1, valueEndAbs);
|
|
13388
|
+
if (!complete2) {
|
|
13389
|
+
const hold = this.trailingPartialMarkerLength(value);
|
|
13390
|
+
if (hold > 0) value = value.slice(0, value.length - hold);
|
|
13391
|
+
}
|
|
13392
|
+
if (value.endsWith("\n")) value = value.slice(0, -1);
|
|
13393
|
+
yield* this.emitFieldDelta(state, fieldPath, value, complete2);
|
|
13394
|
+
}
|
|
13395
|
+
/**
|
|
13396
|
+
* Emit a single partial for a field, but only when its value grew or it newly
|
|
13397
|
+
* completed — keeping event volume proportional to field growth, not characters.
|
|
13398
|
+
*/
|
|
13399
|
+
*emitFieldDelta(state, fieldPath, value, fieldComplete) {
|
|
13400
|
+
const previousLength = state.emittedFieldLengths.get(fieldPath) ?? -1;
|
|
13401
|
+
const grew = value.length > previousLength;
|
|
13402
|
+
const newlyComplete = fieldComplete && !state.completedFields.has(fieldPath);
|
|
13403
|
+
if (!grew && !newlyComplete) return;
|
|
13404
|
+
const delta = previousLength < 0 ? value : value.slice(Math.min(previousLength, value.length));
|
|
13405
|
+
state.emittedFieldLengths.set(fieldPath, value.length);
|
|
13406
|
+
if (fieldComplete) state.completedFields.add(fieldPath);
|
|
13407
|
+
yield {
|
|
13408
|
+
type: "gadget_args_partial",
|
|
13409
|
+
invocationId: state.invocationId,
|
|
13410
|
+
gadgetName: state.gadgetName,
|
|
13411
|
+
fieldPath,
|
|
13412
|
+
value,
|
|
13413
|
+
delta,
|
|
13414
|
+
isFieldComplete: fieldComplete
|
|
13415
|
+
};
|
|
13416
|
+
}
|
|
13417
|
+
/**
|
|
13418
|
+
* Length of the longest suffix of `value` that is a proper prefix of any gadget
|
|
13419
|
+
* marker (start/end/arg). Used to hold back the beginning of an incoming marker
|
|
13420
|
+
* so it never appears inside a streamed value.
|
|
13421
|
+
*/
|
|
13422
|
+
trailingPartialMarkerLength(value) {
|
|
13423
|
+
const markers = [this.startPrefix, this.endPrefix, this.argPrefix];
|
|
13424
|
+
const limit = Math.min(this.maxMarkerLength - 1, value.length);
|
|
13425
|
+
for (let len = limit; len >= 1; len--) {
|
|
13426
|
+
const suffix = value.slice(value.length - len);
|
|
13427
|
+
for (const marker of markers) {
|
|
13428
|
+
if (suffix.length < marker.length && marker.startsWith(suffix)) return len;
|
|
13429
|
+
}
|
|
13430
|
+
}
|
|
13431
|
+
return 0;
|
|
13432
|
+
}
|
|
13194
13433
|
// Finalize parsing and return remaining text or incomplete gadgets
|
|
13195
13434
|
*finalize() {
|
|
13196
13435
|
const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
|
|
@@ -13203,9 +13442,18 @@ var init_parser2 = __esm({
|
|
|
13203
13442
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
13204
13443
|
if (metadataEndIndex !== -1) {
|
|
13205
13444
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
13206
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
13445
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
13207
13446
|
const contentStartIndex = metadataEndIndex + 1;
|
|
13208
|
-
const
|
|
13447
|
+
const contentRaw = this.buffer.substring(contentStartIndex);
|
|
13448
|
+
if (this.currentPartial) {
|
|
13449
|
+
yield* this.emitArgPartials(
|
|
13450
|
+
this.currentPartial,
|
|
13451
|
+
contentStartIndex,
|
|
13452
|
+
this.buffer.length,
|
|
13453
|
+
true
|
|
13454
|
+
);
|
|
13455
|
+
}
|
|
13456
|
+
const parametersRaw = contentRaw.trim();
|
|
13209
13457
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
13210
13458
|
yield {
|
|
13211
13459
|
type: "gadget_call",
|
|
@@ -13218,6 +13466,7 @@ var init_parser2 = __esm({
|
|
|
13218
13466
|
dependencies
|
|
13219
13467
|
}
|
|
13220
13468
|
};
|
|
13469
|
+
this.currentPartial = null;
|
|
13221
13470
|
return;
|
|
13222
13471
|
}
|
|
13223
13472
|
}
|
|
@@ -13230,6 +13479,7 @@ var init_parser2 = __esm({
|
|
|
13230
13479
|
reset() {
|
|
13231
13480
|
this.buffer = "";
|
|
13232
13481
|
this.lastEmittedTextOffset = 0;
|
|
13482
|
+
this.currentPartial = null;
|
|
13233
13483
|
}
|
|
13234
13484
|
};
|
|
13235
13485
|
}
|
|
@@ -14155,6 +14405,29 @@ async function notifyGadgetStart(ctx) {
|
|
|
14155
14405
|
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14156
14406
|
}
|
|
14157
14407
|
}
|
|
14408
|
+
async function notifyGadgetArgsPartial(ctx) {
|
|
14409
|
+
if (!ctx.hooks?.onGadgetArgsPartial && !ctx.parentObservers?.onGadgetArgsPartial) return;
|
|
14410
|
+
const subagentContext = ctx.tree && ctx.parentNodeId ? getSubagentContextForNode(ctx.tree, ctx.parentNodeId) : void 0;
|
|
14411
|
+
const context = {
|
|
14412
|
+
iteration: ctx.iteration,
|
|
14413
|
+
invocationId: ctx.event.invocationId,
|
|
14414
|
+
gadgetName: ctx.event.gadgetName,
|
|
14415
|
+
fieldPath: ctx.event.fieldPath,
|
|
14416
|
+
value: ctx.event.value,
|
|
14417
|
+
delta: ctx.event.delta,
|
|
14418
|
+
isFieldComplete: ctx.event.isFieldComplete,
|
|
14419
|
+
logger: ctx.logger,
|
|
14420
|
+
subagentContext
|
|
14421
|
+
};
|
|
14422
|
+
if (ctx.hooks?.onGadgetArgsPartial) {
|
|
14423
|
+
const hookFn = ctx.hooks.onGadgetArgsPartial;
|
|
14424
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14425
|
+
}
|
|
14426
|
+
if (ctx.parentObservers?.onGadgetArgsPartial) {
|
|
14427
|
+
const hookFn = ctx.parentObservers.onGadgetArgsPartial;
|
|
14428
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14429
|
+
}
|
|
14430
|
+
}
|
|
14158
14431
|
async function notifyGadgetComplete(ctx) {
|
|
14159
14432
|
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
14160
14433
|
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
@@ -14963,6 +15236,7 @@ var init_stream_processor = __esm({
|
|
|
14963
15236
|
init_gadget_dispatcher();
|
|
14964
15237
|
init_gadget_hook_lifecycle();
|
|
14965
15238
|
init_gadget_limit_guard();
|
|
15239
|
+
init_observer_notifier();
|
|
14966
15240
|
init_safe_observe();
|
|
14967
15241
|
StreamProcessor = class {
|
|
14968
15242
|
iteration;
|
|
@@ -14971,6 +15245,10 @@ var init_stream_processor = __esm({
|
|
|
14971
15245
|
parser;
|
|
14972
15246
|
// Execution Tree context
|
|
14973
15247
|
tree;
|
|
15248
|
+
/** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */
|
|
15249
|
+
parentNodeId;
|
|
15250
|
+
/** Parent agent observers (subagent visibility) — also notified for arg partials. */
|
|
15251
|
+
parentObservers;
|
|
14974
15252
|
responseText = "";
|
|
14975
15253
|
// Dependency resolution is delegated to GadgetDependencyResolver
|
|
14976
15254
|
dependencyResolver;
|
|
@@ -14984,6 +15262,8 @@ var init_stream_processor = __esm({
|
|
|
14984
15262
|
this.hooks = options.hooks ?? {};
|
|
14985
15263
|
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
14986
15264
|
this.tree = options.tree;
|
|
15265
|
+
this.parentNodeId = options.parentNodeId;
|
|
15266
|
+
this.parentObservers = options.parentObservers;
|
|
14987
15267
|
this.dependencyResolver = new GadgetDependencyResolver({
|
|
14988
15268
|
priorCompletedInvocations: options.priorCompletedInvocations,
|
|
14989
15269
|
priorFailedInvocations: options.priorFailedInvocations
|
|
@@ -15185,6 +15465,17 @@ var init_stream_processor = __esm({
|
|
|
15185
15465
|
for await (const e of this.dispatcher.dispatch(event.call)) {
|
|
15186
15466
|
yield e;
|
|
15187
15467
|
}
|
|
15468
|
+
} else if (event.type === "gadget_args_partial") {
|
|
15469
|
+
await notifyGadgetArgsPartial({
|
|
15470
|
+
tree: this.tree,
|
|
15471
|
+
hooks: this.hooks.observers,
|
|
15472
|
+
parentObservers: this.parentObservers,
|
|
15473
|
+
logger: this.logger,
|
|
15474
|
+
iteration: this.iteration,
|
|
15475
|
+
parentNodeId: this.parentNodeId,
|
|
15476
|
+
event
|
|
15477
|
+
});
|
|
15478
|
+
yield event;
|
|
15188
15479
|
} else {
|
|
15189
15480
|
yield event;
|
|
15190
15481
|
}
|
|
@@ -16279,19 +16570,7 @@ init_stream_processor();
|
|
|
16279
16570
|
// src/index.ts
|
|
16280
16571
|
init_client2();
|
|
16281
16572
|
init_constants();
|
|
16282
|
-
|
|
16283
|
-
// src/core/errors.ts
|
|
16284
|
-
function isAbortError(error) {
|
|
16285
|
-
if (!(error instanceof Error)) return false;
|
|
16286
|
-
if (error.name === "AbortError") return true;
|
|
16287
|
-
if (error.name === "APIConnectionAbortedError") return true;
|
|
16288
|
-
if (error.name === "APIUserAbortError") return true;
|
|
16289
|
-
const message = error.message.toLowerCase();
|
|
16290
|
-
if (message.includes("abort")) return true;
|
|
16291
|
-
if (message.includes("cancelled")) return true;
|
|
16292
|
-
if (message.includes("canceled")) return true;
|
|
16293
|
-
return false;
|
|
16294
|
-
}
|
|
16573
|
+
init_errors2();
|
|
16295
16574
|
|
|
16296
16575
|
// src/core/execution-events.ts
|
|
16297
16576
|
function isLLMEvent(event) {
|
|
@@ -17000,6 +17279,7 @@ export {
|
|
|
17000
17279
|
DEFAULT_RATE_LIMIT_CONFIG,
|
|
17001
17280
|
DEFAULT_RETRY_CONFIG,
|
|
17002
17281
|
DEFAULT_SUMMARIZATION_PROMPT,
|
|
17282
|
+
EmptyCompletionError,
|
|
17003
17283
|
ExecutionTree,
|
|
17004
17284
|
FALLBACK_CHARS_PER_TOKEN,
|
|
17005
17285
|
GADGET_ARG_PREFIX,
|