@alisio/core 0.1.0-alpha.22 → 0.1.0-alpha.23
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/application.js +2 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +19 -2
- package/dist/core/runner.d.ts +19 -0
- package/dist/core/runner.js +162 -32
- package/dist/core/timeout.d.ts +15 -0
- package/dist/core/timeout.js +61 -0
- package/package.json +2 -2
package/dist/application.js
CHANGED
|
@@ -983,6 +983,8 @@ export async function createApplication(options = {}) {
|
|
|
983
983
|
case "limits.maxTurns":
|
|
984
984
|
case "limits.maxOutputTokens":
|
|
985
985
|
case "limits.maxContextChars":
|
|
986
|
+
case "limits.firstTokenTimeoutMs":
|
|
987
|
+
case "limits.firstTokenRetries":
|
|
986
988
|
case "limits.timeoutMs": {
|
|
987
989
|
const patch = limitLeaf(key.slice("limits.".length));
|
|
988
990
|
config.limits = { ...config.limits, ...patch };
|
package/dist/config.d.ts
CHANGED
|
@@ -175,6 +175,8 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
175
175
|
limits: z.ZodDefault<z.ZodObject<{
|
|
176
176
|
maxTurns: z.ZodDefault<z.ZodNumber>;
|
|
177
177
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
178
|
+
firstTokenTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
179
|
+
firstTokenRetries: z.ZodDefault<z.ZodNumber>;
|
|
178
180
|
maxContextChars: z.ZodDefault<z.ZodNumber>;
|
|
179
181
|
maxOutputTokens: z.ZodDefault<z.ZodNumber>;
|
|
180
182
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
@@ -270,6 +272,8 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
270
272
|
limits: {
|
|
271
273
|
maxTurns: number;
|
|
272
274
|
timeoutMs: number;
|
|
275
|
+
firstTokenTimeoutMs: number;
|
|
276
|
+
firstTokenRetries: number;
|
|
273
277
|
maxContextChars: number;
|
|
274
278
|
maxOutputTokens: number;
|
|
275
279
|
maxTokens?: number | undefined;
|
|
@@ -442,6 +446,8 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
442
446
|
limits: {
|
|
443
447
|
maxTurns: number;
|
|
444
448
|
timeoutMs: number;
|
|
449
|
+
firstTokenTimeoutMs: number;
|
|
450
|
+
firstTokenRetries: number;
|
|
445
451
|
maxContextChars: number;
|
|
446
452
|
maxOutputTokens: number;
|
|
447
453
|
maxTokens?: number | undefined;
|
|
@@ -555,6 +561,8 @@ declare const SETTABLE_KEYS: {
|
|
|
555
561
|
readonly "limits.maxOutputTokens": z.ZodDefault<z.ZodNumber>;
|
|
556
562
|
readonly "limits.maxContextChars": z.ZodDefault<z.ZodNumber>;
|
|
557
563
|
readonly "limits.timeoutMs": z.ZodDefault<z.ZodNumber>;
|
|
564
|
+
readonly "limits.firstTokenTimeoutMs": z.ZodDefault<z.ZodNumber>;
|
|
565
|
+
readonly "limits.firstTokenRetries": z.ZodDefault<z.ZodNumber>;
|
|
558
566
|
readonly "pluginHooks.timeoutMs": z.ZodDefault<z.ZodNumber>;
|
|
559
567
|
readonly "tui.paddingX": z.ZodDefault<z.ZodNumber>;
|
|
560
568
|
readonly "tui.contentPaddingX": z.ZodDefault<z.ZodNumber>;
|
package/dist/config.js
CHANGED
|
@@ -173,7 +173,20 @@ const configObjectSchema = z
|
|
|
173
173
|
limits: z
|
|
174
174
|
.object({
|
|
175
175
|
maxTurns: z.number().int().min(1).max(100).default(100),
|
|
176
|
-
timeoutMs: z.number().int().min(100).default(
|
|
176
|
+
timeoutMs: z.number().int().min(100).default(600000),
|
|
177
|
+
/**
|
|
178
|
+
* Stop a model request that stays completely silent (no text, reasoning or tool-call
|
|
179
|
+
* delta) this long, so a queued or stuck provider is cut after 90 s instead of the
|
|
180
|
+
* whole-run `timeoutMs`. 0 disables it (models that do not stream their reasoning can stay
|
|
181
|
+
* silent for minutes: raise or disable it for those).
|
|
182
|
+
*/
|
|
183
|
+
firstTokenTimeoutMs: z.number().int().min(0).default(90000),
|
|
184
|
+
/**
|
|
185
|
+
* How many times the SAME request is sent again after it stayed silent for
|
|
186
|
+
* `firstTokenTimeoutMs` (a stalled connection usually answers on the next try). 0
|
|
187
|
+
* disables the retry. Never counts as a turn.
|
|
188
|
+
*/
|
|
189
|
+
firstTokenRetries: z.number().int().min(0).max(3).default(1),
|
|
177
190
|
maxContextChars: z.number().int().positive().default(800000),
|
|
178
191
|
/**
|
|
179
192
|
* Per-call output token budget for one agent turn. 4096 starves reasoning-heavy models,
|
|
@@ -185,7 +198,9 @@ const configObjectSchema = z
|
|
|
185
198
|
})
|
|
186
199
|
.default(() => ({
|
|
187
200
|
maxTurns: 100,
|
|
188
|
-
timeoutMs:
|
|
201
|
+
timeoutMs: 600000,
|
|
202
|
+
firstTokenTimeoutMs: 90000,
|
|
203
|
+
firstTokenRetries: 1,
|
|
189
204
|
maxContextChars: 800000,
|
|
190
205
|
maxOutputTokens: 16384,
|
|
191
206
|
})),
|
|
@@ -686,6 +701,8 @@ const SETTABLE_KEYS = {
|
|
|
686
701
|
"limits.maxOutputTokens": SETTABLE_SECTIONS.limits.shape.maxOutputTokens,
|
|
687
702
|
"limits.maxContextChars": SETTABLE_SECTIONS.limits.shape.maxContextChars,
|
|
688
703
|
"limits.timeoutMs": SETTABLE_SECTIONS.limits.shape.timeoutMs,
|
|
704
|
+
"limits.firstTokenTimeoutMs": SETTABLE_SECTIONS.limits.shape.firstTokenTimeoutMs,
|
|
705
|
+
"limits.firstTokenRetries": SETTABLE_SECTIONS.limits.shape.firstTokenRetries,
|
|
689
706
|
"pluginHooks.timeoutMs": SETTABLE_SECTIONS.pluginHooks.shape.timeoutMs,
|
|
690
707
|
"tui.paddingX": SETTABLE_SECTIONS.tui.shape.paddingX,
|
|
691
708
|
"tui.contentPaddingX": SETTABLE_SECTIONS.tui.shape.contentPaddingX,
|
package/dist/core/runner.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export interface RunnerOptions {
|
|
|
24
24
|
workspace: string;
|
|
25
25
|
maxTurns?: number;
|
|
26
26
|
timeoutMs?: number;
|
|
27
|
+
/** See `RunnerSettingsPatch.firstTokenTimeoutMs`. */
|
|
28
|
+
firstTokenTimeoutMs?: number;
|
|
29
|
+
/** See `RunnerSettingsPatch.firstTokenRetries`. */
|
|
30
|
+
firstTokenRetries?: number;
|
|
27
31
|
maxContextChars?: number;
|
|
28
32
|
maxOutputTokens?: number;
|
|
29
33
|
maxTokens?: number;
|
|
@@ -70,6 +74,17 @@ export interface RunnerSettingsPatch {
|
|
|
70
74
|
maxContextChars?: number;
|
|
71
75
|
/** Per-run timeout in milliseconds; the next run honors it. */
|
|
72
76
|
timeoutMs?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Stop a model request that stays completely silent (no text, reasoning or tool-call delta)
|
|
79
|
+
* for this many milliseconds. `0` or unset disables it: only the run timeout applies.
|
|
80
|
+
*/
|
|
81
|
+
firstTokenTimeoutMs?: number;
|
|
82
|
+
/**
|
|
83
|
+
* How many times the same request is sent again after a silent timeout (`firstTokenTimeoutMs`).
|
|
84
|
+
* `0` or unset disables it. A retry is not a turn; it only happens while nothing at all was
|
|
85
|
+
* received for the request.
|
|
86
|
+
*/
|
|
87
|
+
firstTokenRetries?: number;
|
|
73
88
|
}
|
|
74
89
|
/**
|
|
75
90
|
* Per-run overrides used by embedders and child sessions. Callers must only NARROW: `policy`
|
|
@@ -97,6 +112,10 @@ export interface RunOptions {
|
|
|
97
112
|
/** Per-call output token budget for this run; beats the runner-level budget when set. */
|
|
98
113
|
maxOutputTokens?: number;
|
|
99
114
|
timeoutMs?: number;
|
|
115
|
+
/** Silent-request limit for this run; beats the runner-level `firstTokenTimeoutMs`. */
|
|
116
|
+
firstTokenTimeoutMs?: number;
|
|
117
|
+
/** Silent-request retries for this run; beats the runner-level `firstTokenRetries`. */
|
|
118
|
+
firstTokenRetries?: number;
|
|
100
119
|
/** Reasoning effort level for this run; beats the runner-level default when set. */
|
|
101
120
|
reasoningEffort?: string;
|
|
102
121
|
/** Run id to use for this run's events (e.g. preassigned by an embedder); a UUID otherwise. */
|
package/dist/core/runner.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { isEphemeralRunEventType, textProjection, textResult, } from "@alisio/sdk";
|
|
2
2
|
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, reduceMessagesToBudget, shouldCompactContext, summarize, summaryMessage, } from "./compaction.js";
|
|
3
|
+
import { isTimeoutReason, providerLabel, RunTimeoutError } from "./timeout.js";
|
|
4
|
+
/** Pause before a silent request is sent again; short, and cut off by a stop or the run limit. */
|
|
5
|
+
const FIRST_TOKEN_RETRY_PAUSE_MS = 250;
|
|
6
|
+
function abortableDelay(ms, signal) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
if (signal.aborted)
|
|
9
|
+
return resolve();
|
|
10
|
+
const done = () => {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
signal.removeEventListener("abort", done);
|
|
13
|
+
resolve();
|
|
14
|
+
};
|
|
15
|
+
const timer = setTimeout(done, ms);
|
|
16
|
+
signal.addEventListener("abort", done, { once: true });
|
|
17
|
+
});
|
|
18
|
+
}
|
|
3
19
|
/**
|
|
4
20
|
* Default cumulative token budget for one run: several context windows (each turn re-sends the
|
|
5
21
|
* context), clamped; 1M tokens when the window is unknown.
|
|
@@ -593,7 +609,8 @@ export class AgentRunner {
|
|
|
593
609
|
/* A journal write failure cannot invalidate an executed run. */
|
|
594
610
|
}
|
|
595
611
|
};
|
|
596
|
-
const
|
|
612
|
+
const runTimeoutMs = options.timeoutMs ?? o.timeoutMs ?? 300_000;
|
|
613
|
+
const timeout = AbortSignal.timeout(runTimeoutMs);
|
|
597
614
|
const combined = AbortSignal.any([
|
|
598
615
|
controller.signal,
|
|
599
616
|
timeout,
|
|
@@ -656,6 +673,17 @@ export class AgentRunner {
|
|
|
656
673
|
return { runId, status: "completed", result };
|
|
657
674
|
}
|
|
658
675
|
catch (error) {
|
|
676
|
+
if (combined.aborted && isTimeoutReason(combined.reason)) {
|
|
677
|
+
const failure = new RunTimeoutError({
|
|
678
|
+
kind: "run",
|
|
679
|
+
ms: runTimeoutMs,
|
|
680
|
+
stage: "tool",
|
|
681
|
+
tool: name,
|
|
682
|
+
});
|
|
683
|
+
emit("run_failed", { error: failure.message, code: "timeout", timeout: failure.info });
|
|
684
|
+
finish("failed", failure.message);
|
|
685
|
+
throw failure;
|
|
686
|
+
}
|
|
659
687
|
const cause = combined.aborted ? (combined.reason ?? error) : error;
|
|
660
688
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
661
689
|
emit(combined.aborted ? "run_cancelled" : "run_failed", { error: message });
|
|
@@ -684,8 +712,21 @@ export class AgentRunner {
|
|
|
684
712
|
const limit = o.maxContextChars ?? 800_000;
|
|
685
713
|
let tokens = 0, lastText = "", acquired = false;
|
|
686
714
|
const usageTotal = { input: 0, output: 0 };
|
|
687
|
-
const
|
|
715
|
+
const runTimeoutMs = options.timeoutMs ?? o.timeoutMs ?? 300_000;
|
|
716
|
+
const firstTokenMs = options.firstTokenTimeoutMs ?? o.firstTokenTimeoutMs ?? 0;
|
|
717
|
+
const firstTokenRetries = firstTokenMs > 0 ? Math.max(0, options.firstTokenRetries ?? o.firstTokenRetries ?? 0) : 0;
|
|
718
|
+
const runStartedAt = Date.now();
|
|
719
|
+
const timeout = AbortSignal.timeout(runTimeoutMs);
|
|
688
720
|
const combined = AbortSignal.any([controller.signal, timeout, ...(signal ? [signal] : [])]);
|
|
721
|
+
// What the run was doing, so a timeout can say where it was stuck (see `RunTimeoutError`).
|
|
722
|
+
let stage = "other";
|
|
723
|
+
let stageTool;
|
|
724
|
+
let modelId;
|
|
725
|
+
let providerName;
|
|
726
|
+
let requests = 0;
|
|
727
|
+
let silentTimedOut = false;
|
|
728
|
+
/** Sends of the request that is (or was last) waiting for its first token. */
|
|
729
|
+
let silentAttempts = 0;
|
|
689
730
|
const context = options.context ?? o.context;
|
|
690
731
|
const workspace = options.workspace ?? o.workspace;
|
|
691
732
|
const pathAccess = o.pathAccess;
|
|
@@ -712,6 +753,8 @@ export class AgentRunner {
|
|
|
712
753
|
throw new Error("Session provider/workspace mismatch");
|
|
713
754
|
// The session records the model; switching models is explicit via setModel.
|
|
714
755
|
const model = session.model;
|
|
756
|
+
modelId = model;
|
|
757
|
+
providerName = providerLabel(provider.id);
|
|
715
758
|
combined.throwIfAborted();
|
|
716
759
|
if (o.extensions && o.store.messages(sessionId).length === 0) {
|
|
717
760
|
// New session: extensions may inject budgeted context once, persisted with the session.
|
|
@@ -822,39 +865,104 @@ export class AgentRunner {
|
|
|
822
865
|
// display-only extension of the persisted transcript and never reach a model prompt
|
|
823
866
|
// (raw image bytes included). The store keeps the rich parts for TUI replay.
|
|
824
867
|
const providerMessages = messages.map((m) => m.role === "tool" ? { ...m, result: textProjection(m.result) } : m);
|
|
825
|
-
|
|
868
|
+
let requested = Date.now();
|
|
826
869
|
let firstDelta;
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
870
|
+
stage = "waiting_model";
|
|
871
|
+
requests++;
|
|
872
|
+
silentAttempts = 0;
|
|
873
|
+
// The same request is sent again only while the provider stayed completely silent until
|
|
874
|
+
// `firstTokenTimeoutMs`. Nothing was received, appended or accounted for the aborted
|
|
875
|
+
// attempt, so the retry reuses the exact same inputs inside this turn.
|
|
876
|
+
for (;;) {
|
|
877
|
+
silentAttempts++;
|
|
878
|
+
silentTimedOut = false;
|
|
879
|
+
requested = Date.now();
|
|
880
|
+
// A request that stays completely silent can be stopped early (`firstTokenTimeoutMs`).
|
|
881
|
+
const silent = new AbortController();
|
|
882
|
+
let silentTimer = firstTokenMs > 0
|
|
883
|
+
? setTimeout(() => {
|
|
884
|
+
silentTimedOut = true;
|
|
885
|
+
silent.abort();
|
|
886
|
+
}, firstTokenMs)
|
|
887
|
+
: undefined;
|
|
888
|
+
const stopSilentTimer = () => {
|
|
889
|
+
if (silentTimer)
|
|
890
|
+
clearTimeout(silentTimer);
|
|
891
|
+
silentTimer = undefined;
|
|
892
|
+
};
|
|
893
|
+
let failure;
|
|
894
|
+
try {
|
|
895
|
+
for await (const e of provider.stream({
|
|
896
|
+
instructions,
|
|
897
|
+
messages: providerMessages,
|
|
898
|
+
tools,
|
|
899
|
+
maxOutputTokens: options.maxOutputTokens ?? o.maxOutputTokens ?? 4096,
|
|
900
|
+
signal: firstTokenMs > 0 ? AbortSignal.any([combined, silent.signal]) : combined,
|
|
901
|
+
model,
|
|
902
|
+
sessionId,
|
|
903
|
+
...(o.nativeTools?.length ? { nativeTools: o.nativeTools } : {}),
|
|
904
|
+
...((options.reasoningEffort ?? o.reasoningEffort)
|
|
905
|
+
? { reasoningEffort: options.reasoningEffort ?? o.reasoningEffort }
|
|
906
|
+
: {}),
|
|
907
|
+
})) {
|
|
908
|
+
combined.throwIfAborted();
|
|
909
|
+
stopSilentTimer();
|
|
910
|
+
if (stage === "waiting_model")
|
|
911
|
+
stage = "streaming";
|
|
912
|
+
if (e.type !== "completed")
|
|
913
|
+
firstDelta ??= Date.now();
|
|
914
|
+
if (e.type === "text_delta")
|
|
915
|
+
emit("text_delta", { delta: e.delta });
|
|
916
|
+
else if (e.type === "reasoning_delta")
|
|
917
|
+
emit("reasoning_delta", { delta: e.delta });
|
|
918
|
+
else {
|
|
919
|
+
if (completion)
|
|
920
|
+
throw new Error("Provider emitted multiple completions");
|
|
921
|
+
completion = e.message;
|
|
922
|
+
truncated = e.message.truncated === true;
|
|
923
|
+
usage = e.usage;
|
|
924
|
+
tokens += (e.usage?.input ?? 0) + (e.usage?.output ?? 0);
|
|
925
|
+
usageTotal.input += e.usage?.input ?? 0;
|
|
926
|
+
usageTotal.output += e.usage?.output ?? 0;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
856
929
|
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
failure = error;
|
|
932
|
+
}
|
|
933
|
+
finally {
|
|
934
|
+
stopSilentTimer();
|
|
935
|
+
}
|
|
936
|
+
const silentFailure = silentTimedOut &&
|
|
937
|
+
firstDelta === undefined &&
|
|
938
|
+
!completion &&
|
|
939
|
+
!combined.aborted &&
|
|
940
|
+
silentAttempts <= firstTokenRetries;
|
|
941
|
+
if (!silentFailure) {
|
|
942
|
+
if (failure !== undefined)
|
|
943
|
+
throw failure;
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
// Do not start an attempt the whole-run limit could not let finish.
|
|
947
|
+
const retryPauseMs = FIRST_TOKEN_RETRY_PAUSE_MS;
|
|
948
|
+
if (runTimeoutMs - (Date.now() - runStartedAt) < retryPauseMs + firstTokenMs) {
|
|
949
|
+
if (failure !== undefined)
|
|
950
|
+
throw failure;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
// From here the silence is handled: a later stop or run timeout is judged on its own.
|
|
954
|
+
silentTimedOut = false;
|
|
955
|
+
emit("request_retry", {
|
|
956
|
+
attempt: silentAttempts,
|
|
957
|
+
of: firstTokenRetries,
|
|
958
|
+
reason: "first_token_timeout",
|
|
959
|
+
afterMs: Date.now() - requested,
|
|
960
|
+
});
|
|
961
|
+
await abortableDelay(retryPauseMs, combined);
|
|
962
|
+
combined.throwIfAborted();
|
|
963
|
+
stage = "waiting_model";
|
|
857
964
|
}
|
|
965
|
+
stage = "other";
|
|
858
966
|
if (!completion)
|
|
859
967
|
throw new Error("Provider stream ended without a completed response");
|
|
860
968
|
const durationMs = Date.now() - requested;
|
|
@@ -944,6 +1052,8 @@ export class AgentRunner {
|
|
|
944
1052
|
};
|
|
945
1053
|
},
|
|
946
1054
|
};
|
|
1055
|
+
stage = "tool";
|
|
1056
|
+
stageTool = prepared[0]?.call.name;
|
|
947
1057
|
const execute = (p) => this.executeCall(scope, p);
|
|
948
1058
|
const results = [];
|
|
949
1059
|
// Bounded parallel batches for consecutive read or explicitly concurrent tools.
|
|
@@ -974,6 +1084,8 @@ export class AgentRunner {
|
|
|
974
1084
|
if (c && r)
|
|
975
1085
|
o.store.append(sessionId, { role: "tool", callId: c.id, result: r });
|
|
976
1086
|
}
|
|
1087
|
+
stage = "other";
|
|
1088
|
+
stageTool = undefined;
|
|
977
1089
|
if (tokens >= budget)
|
|
978
1090
|
throw new Error("Token budget exhausted");
|
|
979
1091
|
}
|
|
@@ -986,6 +1098,24 @@ export class AgentRunner {
|
|
|
986
1098
|
return { sessionId, text: lastText, status: "turns-exceeded", usage: usageTotal };
|
|
987
1099
|
}
|
|
988
1100
|
catch (error) {
|
|
1101
|
+
const timedOut = silentTimedOut
|
|
1102
|
+
? { kind: "first_token", ms: firstTokenMs, attempts: Math.max(1, silentAttempts) }
|
|
1103
|
+
: combined.aborted && isTimeoutReason(combined.reason)
|
|
1104
|
+
? { kind: "run", ms: runTimeoutMs }
|
|
1105
|
+
: undefined;
|
|
1106
|
+
if (timedOut && !(combined.aborted && !isTimeoutReason(combined.reason))) {
|
|
1107
|
+
const failure = new RunTimeoutError({
|
|
1108
|
+
...timedOut,
|
|
1109
|
+
...(modelId ? { model: modelId } : {}),
|
|
1110
|
+
...(providerName ? { provider: providerName } : {}),
|
|
1111
|
+
stage,
|
|
1112
|
+
...(stage === "tool" && stageTool ? { tool: stageTool } : {}),
|
|
1113
|
+
...(requests <= 1 ? { firstRequest: true } : {}),
|
|
1114
|
+
});
|
|
1115
|
+
emit("run_failed", { error: failure.message, code: "timeout", timeout: failure.info });
|
|
1116
|
+
finish("failed", failure.message);
|
|
1117
|
+
throw failure;
|
|
1118
|
+
}
|
|
989
1119
|
// On cancellation report the abort reason, not the transport's secondary error.
|
|
990
1120
|
const cause = combined.aborted ? (combined.reason ?? error) : error;
|
|
991
1121
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RunTimeoutInfo } from "@alisio/sdk";
|
|
2
|
+
/** A run stopped by a time limit; the message says which limit, which model and what to do. */
|
|
3
|
+
export declare class RunTimeoutError extends Error {
|
|
4
|
+
readonly info: RunTimeoutInfo;
|
|
5
|
+
constructor(info: RunTimeoutInfo);
|
|
6
|
+
}
|
|
7
|
+
/** `AbortSignal.timeout` aborts with a `TimeoutError`; an explicit `abort()` does not. */
|
|
8
|
+
export declare function isTimeoutReason(reason: unknown): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* A short, human label for a provider id: the host of an OpenAI-compatible endpoint
|
|
11
|
+
* (`openai-compatible:chat:https://api.example.com/v1` → `api.example.com`), else the id itself.
|
|
12
|
+
*/
|
|
13
|
+
export declare function providerLabel(id: string | undefined): string | undefined;
|
|
14
|
+
/** Plain-text message of a timeout (TUI, headless, logs, the `runs.error` column). English only. */
|
|
15
|
+
export declare function describeTimeout(info: RunTimeoutInfo): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A run stopped by a time limit; the message says which limit, which model and what to do. */
|
|
2
|
+
export class RunTimeoutError extends Error {
|
|
3
|
+
info;
|
|
4
|
+
constructor(info) {
|
|
5
|
+
super(describeTimeout(info));
|
|
6
|
+
this.name = "RunTimeoutError";
|
|
7
|
+
this.info = info;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** `AbortSignal.timeout` aborts with a `TimeoutError`; an explicit `abort()` does not. */
|
|
11
|
+
export function isTimeoutReason(reason) {
|
|
12
|
+
return (typeof reason === "object" &&
|
|
13
|
+
reason !== null &&
|
|
14
|
+
reason.name === "TimeoutError");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A short, human label for a provider id: the host of an OpenAI-compatible endpoint
|
|
18
|
+
* (`openai-compatible:chat:https://api.example.com/v1` → `api.example.com`), else the id itself.
|
|
19
|
+
*/
|
|
20
|
+
export function providerLabel(id) {
|
|
21
|
+
if (!id)
|
|
22
|
+
return undefined;
|
|
23
|
+
const url = /(https?:\/\/[^\s]+)/.exec(id)?.[1];
|
|
24
|
+
if (url)
|
|
25
|
+
try {
|
|
26
|
+
return new URL(url).host;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* fall through to the raw id */
|
|
30
|
+
}
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
const seconds = (ms) => Math.round(ms / 1000);
|
|
34
|
+
/** Plain-text message of a timeout (TUI, headless, logs, the `runs.error` column). English only. */
|
|
35
|
+
export function describeTimeout(info) {
|
|
36
|
+
const who = info.model
|
|
37
|
+
? `The model ${info.model}${info.provider ? ` (${info.provider})` : ""}`
|
|
38
|
+
: "The model";
|
|
39
|
+
const limit = `${seconds(info.ms)} s`;
|
|
40
|
+
if (info.kind === "first_token" && info.attempts && info.attempts > 1)
|
|
41
|
+
return (`${who} did not respond after ${info.attempts} attempts of ${limit} each: no tokens were received, so the run was stopped. ` +
|
|
42
|
+
"Retry, switch to another model, or raise or disable limits.firstTokenTimeoutMs (0 disables it); " +
|
|
43
|
+
"limits.firstTokenRetries sets how many times a silent request is sent again.");
|
|
44
|
+
if (info.kind === "first_token")
|
|
45
|
+
return (`${who} sent nothing within ${limit} of the request, so the run was stopped. ` +
|
|
46
|
+
"Retry, switch to another model, or raise or disable limits.firstTokenTimeoutMs (0 disables it).");
|
|
47
|
+
if (info.stage === "waiting_model")
|
|
48
|
+
return info.firstRequest
|
|
49
|
+
? `${who} did not respond within ${limit}: no tokens were received. ` +
|
|
50
|
+
"Retry, switch to another model, or raise limits.timeoutMs."
|
|
51
|
+
: `The run reached its ${limit} limit while waiting for ${info.model ? `the model ${info.model}` : "the model"}` +
|
|
52
|
+
`${info.provider ? ` (${info.provider})` : ""}. ` +
|
|
53
|
+
"Everything produced so far is kept: prompt again to continue, switch model, or raise limits.timeoutMs.";
|
|
54
|
+
const doing = info.stage === "tool" && info.tool
|
|
55
|
+
? `while running ${info.tool}`
|
|
56
|
+
: info.stage === "streaming"
|
|
57
|
+
? "while the model was answering"
|
|
58
|
+
: "";
|
|
59
|
+
return (`The run reached its ${limit} limit${doing ? ` ${doing}` : ""}. ` +
|
|
60
|
+
"Everything produced so far is kept: prompt again to continue, or raise limits.timeoutMs.");
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.23",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=22.16"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@alisio/sdk": "0.1.0-alpha.
|
|
44
|
+
"@alisio/sdk": "0.1.0-alpha.19",
|
|
45
45
|
"@mixmark-io/domino": "2.2.0",
|
|
46
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
47
47
|
"ajv": "8.20.0",
|