agent-accelerator 0.5.9 → 0.6.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 +3 -1
- package/examples/prompts/SYSTEM_PROMPT.md +2 -0
- package/examples/prompts/SYSTEM_PROMPT_AGENT.md +4 -0
- package/examples/prompts/SYSTEM_PROMPT_TOOLS.md +1 -0
- package/package.json +4 -1
- package/src/agent/agent.ts +366 -161
- package/src/agent/architecture.md +13 -12
- package/src/agent/config-blocks.ts +39 -12
- package/src/agent/context.ts +8 -3
- package/src/agent/delegation.ts +82 -29
- package/src/agent/loop.ts +444 -365
- package/src/agent/subagent.ts +1 -3
- package/src/agent/subagents-tool.ts +45 -7
- package/src/agent/subagents.md +3 -0
- package/src/agent/turns.ts +3 -1
- package/src/agent/worker.ts +56 -27
- package/src/agent/workers.ts +113 -20
- package/src/architecture.md +16 -15
- package/src/index.ts +34 -10
- package/src/models/architecture.md +4 -3
- package/src/models/catalog-cache.ts +6 -5
- package/src/models/catalog.ts +39 -30
- package/src/providers/architecture.md +10 -8
- package/src/providers/google.ts +147 -59
- package/src/providers/openai-compat.ts +57 -49
- package/src/providers/openai.ts +54 -37
- package/src/providers/openrouter.ts +85 -30
- package/src/providers/registry.ts +6 -21
- package/src/providers/shared.ts +87 -33
- package/src/providers.ts +56 -115
- package/src/scheduler/architecture.md +7 -6
- package/src/scheduler/scheduled-actions.md +4 -0
- package/src/scheduler/scheduler.ts +186 -60
- package/src/scheduler/store.ts +23 -10
- package/src/scheduler/time.ts +17 -3
- package/src/scheduler/tool.ts +33 -7
- package/src/session/architecture.md +7 -6
- package/src/session/journal.ts +38 -12
- package/src/session/store.ts +122 -61
- package/src/session/usage.ts +58 -9
- package/src/streaming/architecture.md +4 -3
- package/src/streaming/event-stream.ts +26 -12
- package/src/telemetry.ts +6 -2
- package/src/tools/architecture.md +8 -7
- package/src/tools/executor.ts +50 -48
- package/src/tools/schema.ts +10 -6
- package/src/tools/subagent-result.ts +6 -1
- package/src/tools/tool.ts +9 -11
- package/src/types/agent.ts +2 -21
- package/src/types/architecture.md +11 -10
- package/src/types/core.ts +1 -9
- package/src/types/errors.ts +26 -4
- package/src/types/model.ts +0 -4
- package/src/types/tool.ts +12 -2
- package/src/update-models.ts +5 -5
- package/src/utils/architecture.md +16 -15
- package/src/utils/cache.ts +1 -3
- package/src/utils/durable.ts +17 -5
- package/src/utils/env.ts +9 -7
- package/src/utils/errors.ts +6 -14
- package/src/utils/headers.ts +3 -10
- package/src/utils/media.ts +23 -8
- package/src/utils/raw-capture.ts +9 -2
- package/src/utils/retry.ts +15 -3
- package/src/utils/safe-io.ts +29 -7
- package/src/utils/serialization.ts +1 -4
- package/src/utils/session.ts +1 -2
package/src/agent/agent.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AgentConfig,
|
|
3
|
+
EditableAgentConfig,
|
|
4
|
+
AgentRunOptions,
|
|
5
|
+
MidSessionConfig,
|
|
6
|
+
ToolChoiceOption,
|
|
7
|
+
RetryPolicyConfig,
|
|
8
|
+
Budget,
|
|
9
|
+
BudgetState,
|
|
10
|
+
InterruptedRun,
|
|
11
|
+
} from "../types/agent.ts";
|
|
2
12
|
import type { TokenUsage } from "../types/core.ts";
|
|
3
13
|
import type { ProviderRequestOptions } from "../types/model.ts";
|
|
4
14
|
import { normalizeThinkingV2, resolveWhenBusy } from "../tools/tool.ts";
|
|
5
15
|
import type { ToolDefinition, ToolResultRecord } from "../types/tool.ts";
|
|
6
16
|
import { isSubagentResult } from "../tools/subagent-result.ts";
|
|
7
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
CACHE_OPTIONS,
|
|
19
|
+
type ThinkingLevel,
|
|
20
|
+
type ThinkingConfig,
|
|
21
|
+
type CacheOption,
|
|
22
|
+
type ServiceTier,
|
|
23
|
+
} from "../types/core.ts";
|
|
8
24
|
import type { ContentPart } from "../types/message.ts";
|
|
9
25
|
import { AgentResponse, type StreamEvent } from "../types/response.ts";
|
|
10
26
|
import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
|
|
@@ -13,21 +29,23 @@ import { resolveModel } from "../providers/registry.ts";
|
|
|
13
29
|
import { buildAgentTools } from "./delegation.ts";
|
|
14
30
|
import { createSubagentsTool } from "./subagents-tool.ts";
|
|
15
31
|
import { WorkerRegistry } from "./workers.ts";
|
|
16
|
-
import {
|
|
17
|
-
DEFAULT_MAX_DEPTH,
|
|
18
|
-
normalizeToolMap,
|
|
19
|
-
resolveDynamicModelInput,
|
|
20
|
-
resolveMainModel,
|
|
21
|
-
} from "./worker.ts";
|
|
32
|
+
import { DEFAULT_MAX_DEPTH, normalizeToolMap, resolveDynamicModelInput, resolveMainModel } from "./worker.ts";
|
|
22
33
|
import * as path from "node:path";
|
|
23
34
|
import { registerAgent } from "../scheduler/scheduler.ts";
|
|
24
|
-
import { resolveHome } from "../
|
|
35
|
+
import { resolveHome } from "../utils/home.ts";
|
|
25
36
|
import { persistTelemetry, registerSessionFolder, unregisterSessionFolder } from "../telemetry-file.ts";
|
|
26
37
|
import { lockFolder } from "../utils/durable.ts";
|
|
27
38
|
import { createScheduledActionsTool, getDatetimeTool } from "../scheduler/tool.ts";
|
|
28
39
|
import { applyEditable, assertModelAllowed, modelKey, toEditableConfig } from "./config-blocks.ts";
|
|
29
40
|
import { assertNoToolCollisions } from "../tools/executor.ts";
|
|
30
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
runAgentLoop,
|
|
43
|
+
streamAgentLoop,
|
|
44
|
+
unfinishedToolCalls,
|
|
45
|
+
endsWithStop,
|
|
46
|
+
accumulateUsage,
|
|
47
|
+
type SteerEntry,
|
|
48
|
+
} from "./loop.ts";
|
|
31
49
|
import { isRetryableRun, readRetryAfterMs, sleep } from "../utils/retry.ts";
|
|
32
50
|
import { createSessionId, nowMs, newRunId, previewPrompt } from "../utils/session.ts";
|
|
33
51
|
import {
|
|
@@ -38,25 +56,43 @@ import {
|
|
|
38
56
|
listSubAgentTraceIdsForSessions,
|
|
39
57
|
type SubAgentTrace,
|
|
40
58
|
} from "./delegation.ts";
|
|
41
|
-
import {
|
|
42
|
-
|
|
59
|
+
import {
|
|
60
|
+
addTurn,
|
|
61
|
+
buildUsageReport,
|
|
62
|
+
emptyUsage,
|
|
63
|
+
mergeUsage,
|
|
64
|
+
modelName,
|
|
65
|
+
readUsage,
|
|
66
|
+
type UsageNode,
|
|
67
|
+
type UsageReport,
|
|
68
|
+
type UsageTotals,
|
|
69
|
+
} from "../session/usage.ts";
|
|
70
|
+
import {
|
|
71
|
+
saveSessionFile,
|
|
72
|
+
sessionDirFor,
|
|
73
|
+
loadSessionDir,
|
|
74
|
+
findInvalidMessage,
|
|
75
|
+
priceUsage,
|
|
76
|
+
telemetryBus,
|
|
77
|
+
type SessionRunMark,
|
|
78
|
+
type SessionRunEnd,
|
|
79
|
+
type SessionPending,
|
|
80
|
+
type SessionToolRecord,
|
|
81
|
+
} from "../session/store.ts";
|
|
43
82
|
import { SessionJournal } from "../session/journal.ts";
|
|
44
83
|
import { ToolLogs, type ToolLogWriter } from "../session/tool-log.ts";
|
|
45
84
|
import { isLocalPathInput } from "../utils/safe-io.ts";
|
|
46
|
-
import { clearSessionRouting, normalizeStructuredOutput
|
|
85
|
+
import { clearSessionRouting, normalizeStructuredOutput } from "../providers.ts";
|
|
47
86
|
import type { StructuredOutputSpec } from "../types/model.ts";
|
|
48
87
|
import { clearInteractionChains } from "../providers/google.ts";
|
|
49
88
|
import { validateModelThinking, ensureModelCatalogFresh } from "../models/catalog.ts";
|
|
50
|
-
import { buildSessionData, type PersistedAgentSession, type SessionTotals
|
|
89
|
+
import { buildSessionData, type PersistedAgentSession, type SessionTotals } from "../session/store.ts";
|
|
51
90
|
import { SessionTelemetry } from "../session/store.ts";
|
|
52
91
|
|
|
53
92
|
/**
|
|
54
93
|
* Resolves a per-run thinking override without mutating agent config.
|
|
55
94
|
*/
|
|
56
|
-
export function resolveEffectiveThinking(
|
|
57
|
-
base?: ThinkingConfig,
|
|
58
|
-
overrideLevel?: string
|
|
59
|
-
): ThinkingConfig | undefined {
|
|
95
|
+
export function resolveEffectiveThinking(base?: ThinkingConfig, overrideLevel?: string): ThinkingConfig | undefined {
|
|
60
96
|
if (!overrideLevel) return base;
|
|
61
97
|
if (overrideLevel === "none") return { enabled: false, level: "none", budgetTokens: 0 };
|
|
62
98
|
if (overrideLevel === "dynamic") return { enabled: true, level: "dynamic", budgetTokens: -1 };
|
|
@@ -94,8 +130,7 @@ export interface AgentRunLimits {
|
|
|
94
130
|
/** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
|
|
95
131
|
function resolveAgentLimits(config: { limits?: Record<string, unknown> }): AgentRunLimits {
|
|
96
132
|
const raw = (config as { limits?: Record<string, unknown> }).limits ?? {};
|
|
97
|
-
const num = (v: unknown): number | undefined =>
|
|
98
|
-
typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
133
|
+
const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
|
|
99
134
|
// A network timeout: `0` or `Infinity` turns it off, any other number is milliseconds, anything else is unset (the default applies).
|
|
100
135
|
const timeout = (v: unknown): number | undefined => (typeof v === "number" && !Number.isNaN(v) ? v : undefined);
|
|
101
136
|
return {
|
|
@@ -104,7 +139,9 @@ function resolveAgentLimits(config: { limits?: Record<string, unknown> }): Agent
|
|
|
104
139
|
...(num(raw.maxToolCallsPerRun) !== undefined
|
|
105
140
|
? { maxToolCallsPerRun: Math.max(1, Math.floor(num(raw.maxToolCallsPerRun)!)) }
|
|
106
141
|
: {}),
|
|
107
|
-
...(num(raw.maxTotalTokens) !== undefined
|
|
142
|
+
...(num(raw.maxTotalTokens) !== undefined
|
|
143
|
+
? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) }
|
|
144
|
+
: {}),
|
|
108
145
|
requestTimeoutMs: timeout(raw.requestTimeoutMs) ?? 300_000,
|
|
109
146
|
firstByteTimeoutMs: timeout(raw.firstByteTimeoutMs) ?? 120_000,
|
|
110
147
|
streamIdleTimeoutMs: timeout(raw.streamIdleTimeoutMs) ?? 300_000,
|
|
@@ -216,8 +253,6 @@ export class Agent {
|
|
|
216
253
|
private readonly callDefaults: {
|
|
217
254
|
toolChoice?: ToolChoiceOption;
|
|
218
255
|
maxOutputTokens?: number;
|
|
219
|
-
stop?: string | string[];
|
|
220
|
-
seed?: number;
|
|
221
256
|
retry?: RetryPolicyConfig;
|
|
222
257
|
env?: Record<string, string>;
|
|
223
258
|
fetch?: typeof fetch;
|
|
@@ -325,34 +360,50 @@ export class Agent {
|
|
|
325
360
|
this.subagentModel = dynModel as string | any | undefined;
|
|
326
361
|
const dynEnabled = dynRaw ? (dynRaw.enabled ?? true) : false;
|
|
327
362
|
if (dynRaw) {
|
|
328
|
-
const dynTools = normalizeToolMap(
|
|
363
|
+
const dynTools = normalizeToolMap(
|
|
364
|
+
(dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools
|
|
365
|
+
);
|
|
329
366
|
const rawDepth = (dynRaw as { maxDepth?: unknown }).maxDepth;
|
|
330
367
|
// Depth is unlimited unless the developer sets a cap (a whole number >= 1; Infinity also means unlimited).
|
|
331
|
-
if (
|
|
332
|
-
|
|
368
|
+
if (
|
|
369
|
+
rawDepth !== undefined &&
|
|
370
|
+
rawDepth !== Infinity &&
|
|
371
|
+
!(Number.isInteger(rawDepth) && (rawDepth as number) >= 1)
|
|
372
|
+
) {
|
|
373
|
+
throw new ConfigError(
|
|
374
|
+
`[Agent Accelerator] Invalid dynamicSubagents.maxDepth ${JSON.stringify(rawDepth)}: expected a whole number >= 1, or omit it for no limit; set dynamicSubagents.enabled: false to turn workers off.`
|
|
375
|
+
);
|
|
333
376
|
}
|
|
334
377
|
const rawBudget = (dynRaw as { workerBudget?: { maxCostUsd?: unknown; maxTotalTokens?: unknown } }).workerBudget;
|
|
335
378
|
for (const key of ["maxCostUsd", "maxTotalTokens"] as const) {
|
|
336
379
|
const v = rawBudget?.[key];
|
|
337
380
|
if (v !== undefined && !(typeof v === "number" && Number.isFinite(v) && v > 0)) {
|
|
338
|
-
throw new ConfigError(
|
|
381
|
+
throw new ConfigError(
|
|
382
|
+
`[Agent Accelerator] Invalid dynamicSubagents.workerBudget.${key} ${JSON.stringify(v)}: expected a number greater than 0, or omit it for no limit.`
|
|
383
|
+
);
|
|
339
384
|
}
|
|
340
385
|
}
|
|
341
386
|
// Open workers are unlimited unless the developer sets a cap (a number >= 1; Infinity also means unlimited).
|
|
342
387
|
const rawMax = (dynRaw as { maxSpawn?: unknown }).maxSpawn;
|
|
343
388
|
if (rawMax !== undefined && (typeof rawMax !== "number" || Number.isNaN(rawMax))) {
|
|
344
|
-
throw new ConfigError(
|
|
389
|
+
throw new ConfigError(
|
|
390
|
+
`[Agent Accelerator] Invalid dynamicSubagents.maxSpawn ${JSON.stringify(rawMax)}: expected a whole number >= 1, or omit it for no limit.`
|
|
391
|
+
);
|
|
345
392
|
}
|
|
346
393
|
const maxSpawn = rawMax === undefined || rawMax === Infinity ? Infinity : Math.max(1, Math.floor(rawMax));
|
|
347
394
|
// Only scheduled actions are timed: no limit unless the developer sets one (0 and Infinity mean none).
|
|
348
395
|
for (const removed of ["timeout", "modelMaySetTimeout"]) {
|
|
349
396
|
if ((dynRaw as Record<string, unknown>)[removed] !== undefined) {
|
|
350
|
-
throw new ConfigError(
|
|
397
|
+
throw new ConfigError(
|
|
398
|
+
`[Agent Accelerator] dynamicSubagents.${removed} was removed: set dynamicSubagents.workerTimeoutMs (the longest a scheduled run may take, optional) and let each scheduled action carry its own timeoutMs.`
|
|
399
|
+
);
|
|
351
400
|
}
|
|
352
401
|
}
|
|
353
402
|
const rawTimeout = (dynRaw as { workerTimeoutMs?: unknown }).workerTimeoutMs;
|
|
354
403
|
if (rawTimeout !== undefined && (typeof rawTimeout !== "number" || Number.isNaN(rawTimeout) || rawTimeout < 0)) {
|
|
355
|
-
throw new ConfigError(
|
|
404
|
+
throw new ConfigError(
|
|
405
|
+
`[Agent Accelerator] Invalid dynamicSubagents.workerTimeoutMs ${JSON.stringify(rawTimeout)}: expected a number of milliseconds > 0, or omit it (0 and Infinity mean no limit).`
|
|
406
|
+
);
|
|
356
407
|
}
|
|
357
408
|
const workerTimeoutMs = !rawTimeout ? Infinity : Math.max(1, Math.floor(rawTimeout));
|
|
358
409
|
this.dynamicSubagents = {
|
|
@@ -419,17 +470,20 @@ export class Agent {
|
|
|
419
470
|
}
|
|
420
471
|
this.cache = config.cache;
|
|
421
472
|
if (config.resume !== undefined && config.resume !== "automatic" && config.resume !== "explicit") {
|
|
422
|
-
throw new ConfigError(
|
|
473
|
+
throw new ConfigError(
|
|
474
|
+
`[Agent Accelerator] Invalid resume ${JSON.stringify(config.resume)}: expected "automatic" (default) or "explicit".`
|
|
475
|
+
);
|
|
423
476
|
}
|
|
424
477
|
this.resumeMode = config.resume ?? "automatic";
|
|
425
478
|
if (config.runRetries !== undefined && !(Number.isInteger(config.runRetries) && config.runRetries >= 0)) {
|
|
426
|
-
throw new ConfigError(
|
|
479
|
+
throw new ConfigError(
|
|
480
|
+
`[Agent Accelerator] Invalid runRetries ${JSON.stringify(config.runRetries)}: expected a whole number >= 0 (default 3, 0 turns it off).`
|
|
481
|
+
);
|
|
427
482
|
}
|
|
428
483
|
this.runRetries = config.runRetries ?? 3;
|
|
429
484
|
|
|
430
485
|
this.serviceTier = config.serviceTier;
|
|
431
|
-
this.onFailure =
|
|
432
|
-
config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
|
|
486
|
+
this.onFailure = config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
|
|
433
487
|
this.forkConfig = { ...config };
|
|
434
488
|
this.captureRaw = config.captureRaw;
|
|
435
489
|
this.budget = config.budget ? { ...config.budget } : undefined;
|
|
@@ -448,8 +502,6 @@ export class Agent {
|
|
|
448
502
|
this.callDefaults = {
|
|
449
503
|
toolChoice: config.toolChoice,
|
|
450
504
|
maxOutputTokens: config.maxOutputTokens,
|
|
451
|
-
stop: config.stop,
|
|
452
|
-
seed: config.seed,
|
|
453
505
|
retry: config.retry,
|
|
454
506
|
env: config.env,
|
|
455
507
|
fetch: config.fetch,
|
|
@@ -466,7 +518,6 @@ export class Agent {
|
|
|
466
518
|
Object.assign(this.tools, normalizeToolMap(config.tools));
|
|
467
519
|
}
|
|
468
520
|
|
|
469
|
-
|
|
470
521
|
if (Array.isArray(config.subagents)) {
|
|
471
522
|
const subagentTools = buildAgentTools(config.subagents);
|
|
472
523
|
for (const [toolName, toolDef] of Object.entries(subagentTools)) {
|
|
@@ -574,7 +625,11 @@ export class Agent {
|
|
|
574
625
|
|
|
575
626
|
// Records one model call of THIS agent (priced per turn, with the service tier); in: the loop's onTurnUsage, out: this.usageTotals (saved by the next persist).
|
|
576
627
|
private recordTurnUsage(usage: TokenUsage, spec: Parameters<typeof priceUsage>[1]): void {
|
|
577
|
-
addTurn(
|
|
628
|
+
addTurn(
|
|
629
|
+
this.usageTotals,
|
|
630
|
+
usage,
|
|
631
|
+
priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined)
|
|
632
|
+
);
|
|
578
633
|
}
|
|
579
634
|
|
|
580
635
|
/** Adds another agent's spending to this one (a fixed worker's per-call copy into its template). @internal */
|
|
@@ -589,7 +644,10 @@ export class Agent {
|
|
|
589
644
|
* below it. Spending of one-shot scheduled-action workers is in their run files, not here.
|
|
590
645
|
*/
|
|
591
646
|
usageReport(): UsageReport {
|
|
592
|
-
return buildUsageReport(
|
|
647
|
+
return buildUsageReport(
|
|
648
|
+
{ id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals },
|
|
649
|
+
[...this.workers.usageNodes(), ...this.fixedWorkerNodes()]
|
|
650
|
+
);
|
|
593
651
|
}
|
|
594
652
|
|
|
595
653
|
// The `subagents: [...]` workers as usage nodes (their per-call copies' spending is merged into them); in: the constructor config, out: usageReport.
|
|
@@ -598,14 +656,25 @@ export class Agent {
|
|
|
598
656
|
for (const entry of this.forkConfig.subagents ?? []) {
|
|
599
657
|
const worker = "agent" in entry ? entry.agent : entry;
|
|
600
658
|
const name = "agent" in entry ? entry.name : worker.name;
|
|
601
|
-
nodes.push({
|
|
659
|
+
nodes.push({
|
|
660
|
+
id: name,
|
|
661
|
+
name,
|
|
662
|
+
model: modelName(worker.modelStringOrSpec),
|
|
663
|
+
status: "fixed",
|
|
664
|
+
totals: worker.usageTotals,
|
|
665
|
+
workers: [],
|
|
666
|
+
});
|
|
602
667
|
}
|
|
603
668
|
return nodes;
|
|
604
669
|
}
|
|
605
670
|
|
|
606
671
|
// Says on the telemetry bus that resuming (or a prompt that was waiting) failed; in: a rejected resume, out: telemetryBus.
|
|
607
672
|
private reportResumeFailure(err: unknown): void {
|
|
608
|
-
telemetryBus.emitWarning(
|
|
673
|
+
telemetryBus.emitWarning(
|
|
674
|
+
"agent.resume_failed",
|
|
675
|
+
`[Agent Accelerator] Resuming session ${this.sessionId} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
676
|
+
{ sessionId: this.sessionId }
|
|
677
|
+
);
|
|
609
678
|
}
|
|
610
679
|
|
|
611
680
|
/** Runs that a crash cut off (they began, never ended and are not running now), oldest first. Needs `persist: { dir }` and the same `sessionId` as before the restart. */
|
|
@@ -629,7 +698,8 @@ export class Agent {
|
|
|
629
698
|
*/
|
|
630
699
|
resume(): Promise<AgentResponse | undefined> {
|
|
631
700
|
if (this.resuming) return this.resuming;
|
|
632
|
-
if (this.currentRun)
|
|
701
|
+
if (this.currentRun)
|
|
702
|
+
throw new ValidationError("[Agent Accelerator] resume() needs an idle agent: a run is already active.");
|
|
633
703
|
const mark = this.runMarks.filter((m) => !m.end && m !== this.activeMark).pop();
|
|
634
704
|
const last = this.context.messages[this.context.messages.length - 1];
|
|
635
705
|
const nothingOpen = unfinishedToolCalls(this.context.messages).length === 0;
|
|
@@ -642,7 +712,11 @@ export class Agent {
|
|
|
642
712
|
telemetryBus.emit({
|
|
643
713
|
code: "agent.resumed",
|
|
644
714
|
message: `[Agent Accelerator] resuming run ${mark.id} of session ${this.sessionId}`,
|
|
645
|
-
data: {
|
|
715
|
+
data: {
|
|
716
|
+
sessionId: this.sessionId,
|
|
717
|
+
runId: mark.id,
|
|
718
|
+
pendingToolCalls: unfinishedToolCalls(this.context.messages).map((c) => c.name),
|
|
719
|
+
},
|
|
646
720
|
});
|
|
647
721
|
const run = this.startNonStreamingRun("", undefined, mark);
|
|
648
722
|
const clear = (): void => {
|
|
@@ -655,7 +729,18 @@ export class Agent {
|
|
|
655
729
|
|
|
656
730
|
// The finished tool calls of the current run as results the loop can reuse; in: toolRecords, out: loopConfig.resume.stored.
|
|
657
731
|
private storedToolResults(): Map<string, ToolResultRecord> {
|
|
658
|
-
return new Map(
|
|
732
|
+
return new Map(
|
|
733
|
+
this.toolRecords.map((t) => [
|
|
734
|
+
t.id,
|
|
735
|
+
{
|
|
736
|
+
id: t.id,
|
|
737
|
+
name: t.name,
|
|
738
|
+
result: t.result,
|
|
739
|
+
...(t.isError ? { isError: true } : {}),
|
|
740
|
+
...(t.stopped ? { stopped: true } : {}),
|
|
741
|
+
} as ToolResultRecord,
|
|
742
|
+
])
|
|
743
|
+
);
|
|
659
744
|
}
|
|
660
745
|
|
|
661
746
|
// Closes a run's mark with how it ended and saves that at once (not on the next tick), so the disk is up to date when `await run()` returns; in: the run's promise handlers, out: runMarks + journal.
|
|
@@ -683,9 +768,7 @@ export class Agent {
|
|
|
683
768
|
const base = this.instructions || "";
|
|
684
769
|
let out = base;
|
|
685
770
|
const isThinkingEnabled =
|
|
686
|
-
this.thinkingConfig?.enabled !== false &&
|
|
687
|
-
this.thinkingConfig?.level &&
|
|
688
|
-
this.thinkingConfig?.level !== "none";
|
|
771
|
+
this.thinkingConfig?.enabled !== false && this.thinkingConfig?.level && this.thinkingConfig?.level !== "none";
|
|
689
772
|
if (isThinkingEnabled && !out.includes("[Reasoning Directive]")) {
|
|
690
773
|
const guidance =
|
|
691
774
|
"[Reasoning Directive]\n" +
|
|
@@ -707,8 +790,12 @@ export class Agent {
|
|
|
707
790
|
this.toolRecords.length = 0; // a cleared conversation has no run to resume, so its finished tool results go too
|
|
708
791
|
this.activeMark = undefined;
|
|
709
792
|
// Reset also drops session-scoped provider chaining/routing.
|
|
710
|
-
try {
|
|
711
|
-
|
|
793
|
+
try {
|
|
794
|
+
clearInteractionChains(sid);
|
|
795
|
+
} catch {}
|
|
796
|
+
try {
|
|
797
|
+
clearSessionRouting(sid);
|
|
798
|
+
} catch {}
|
|
712
799
|
}
|
|
713
800
|
|
|
714
801
|
/**
|
|
@@ -727,17 +814,27 @@ export class Agent {
|
|
|
727
814
|
/** Drops one named session. No-op for unknown ids. */
|
|
728
815
|
disposeSession(id: string): void {
|
|
729
816
|
unregisterSessionFolder(id);
|
|
730
|
-
try {
|
|
731
|
-
|
|
732
|
-
|
|
817
|
+
try {
|
|
818
|
+
this.sessions.delete(id);
|
|
819
|
+
} catch {}
|
|
820
|
+
try {
|
|
821
|
+
clearInteractionChains(id);
|
|
822
|
+
} catch {}
|
|
823
|
+
try {
|
|
824
|
+
clearSessionRouting(id);
|
|
825
|
+
} catch {}
|
|
733
826
|
}
|
|
734
827
|
|
|
735
828
|
/** Drops all named sessions except the default context. */
|
|
736
829
|
clearSessions(): void {
|
|
737
830
|
try {
|
|
738
831
|
for (const id of [...this.sessions.keys()]) {
|
|
739
|
-
try {
|
|
740
|
-
|
|
832
|
+
try {
|
|
833
|
+
clearInteractionChains(id);
|
|
834
|
+
} catch {}
|
|
835
|
+
try {
|
|
836
|
+
clearSessionRouting(id);
|
|
837
|
+
} catch {}
|
|
741
838
|
}
|
|
742
839
|
this.sessions.clear();
|
|
743
840
|
} catch {}
|
|
@@ -753,8 +850,12 @@ export class Agent {
|
|
|
753
850
|
for (const [key, entry] of [...this.sessions.entries()]) {
|
|
754
851
|
if (now - entry.lastAccess > SESSION_TTL_MS) {
|
|
755
852
|
this.sessions.delete(key);
|
|
756
|
-
try {
|
|
757
|
-
|
|
853
|
+
try {
|
|
854
|
+
clearInteractionChains(key);
|
|
855
|
+
} catch {}
|
|
856
|
+
try {
|
|
857
|
+
clearSessionRouting(key);
|
|
858
|
+
} catch {}
|
|
758
859
|
}
|
|
759
860
|
}
|
|
760
861
|
} catch {}
|
|
@@ -771,12 +872,19 @@ export class Agent {
|
|
|
771
872
|
let oldest: string | undefined;
|
|
772
873
|
let oldestTs = Infinity;
|
|
773
874
|
for (const [key, entry] of this.sessions) {
|
|
774
|
-
if (entry.lastAccess < oldestTs) {
|
|
875
|
+
if (entry.lastAccess < oldestTs) {
|
|
876
|
+
oldestTs = entry.lastAccess;
|
|
877
|
+
oldest = key;
|
|
878
|
+
}
|
|
775
879
|
}
|
|
776
880
|
if (!oldest) break;
|
|
777
881
|
this.sessions.delete(oldest);
|
|
778
|
-
try {
|
|
779
|
-
|
|
882
|
+
try {
|
|
883
|
+
clearInteractionChains(oldest);
|
|
884
|
+
} catch {}
|
|
885
|
+
try {
|
|
886
|
+
clearSessionRouting(oldest);
|
|
887
|
+
} catch {}
|
|
780
888
|
}
|
|
781
889
|
} catch {}
|
|
782
890
|
return fresh;
|
|
@@ -789,8 +897,12 @@ export class Agent {
|
|
|
789
897
|
const err = Object.assign(new Error("Queue cleared"), { name: "AbortError" });
|
|
790
898
|
const pending = this.pendingQueue.splice(0);
|
|
791
899
|
for (const entry of pending) {
|
|
792
|
-
try {
|
|
793
|
-
|
|
900
|
+
try {
|
|
901
|
+
entry.reject(err);
|
|
902
|
+
} catch {}
|
|
903
|
+
try {
|
|
904
|
+
entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err)));
|
|
905
|
+
} catch {}
|
|
794
906
|
}
|
|
795
907
|
this.persistNow();
|
|
796
908
|
return n;
|
|
@@ -800,7 +912,7 @@ export class Agent {
|
|
|
800
912
|
private assertSteerRoom(inbox: SteerEntry[]): void {
|
|
801
913
|
const max = this.maxSteers ?? 20;
|
|
802
914
|
if (inbox.length >= max) {
|
|
803
|
-
throw new
|
|
915
|
+
throw new ValidationError(
|
|
804
916
|
`[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
|
|
805
917
|
);
|
|
806
918
|
}
|
|
@@ -858,7 +970,9 @@ export class Agent {
|
|
|
858
970
|
interrupt(arg?: Error | string | ContentPart[], options?: AgentRunOptions): void | Promise<AgentResponse> {
|
|
859
971
|
if (typeof arg === "string" || Array.isArray(arg)) {
|
|
860
972
|
if (options?.stream === true) {
|
|
861
|
-
throw new
|
|
973
|
+
throw new ValidationError(
|
|
974
|
+
'[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.'
|
|
975
|
+
);
|
|
862
976
|
}
|
|
863
977
|
return this.run(arg, { ...options, enqueue: "interrupt" });
|
|
864
978
|
}
|
|
@@ -900,12 +1014,12 @@ export class Agent {
|
|
|
900
1014
|
steer(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
901
1015
|
this.assertPromptValid(prompt);
|
|
902
1016
|
if (options?.stream === true) {
|
|
903
|
-
throw new
|
|
1017
|
+
throw new ValidationError(
|
|
904
1018
|
'[Agent Accelerator] steer() is non-streaming. Use stream(prompt, { enqueue: "steer" }) for a streaming steer.'
|
|
905
1019
|
);
|
|
906
1020
|
}
|
|
907
1021
|
if (this.midSessionConfig.mode === "queue") {
|
|
908
|
-
throw new
|
|
1022
|
+
throw new ConfigError(
|
|
909
1023
|
'[Agent Accelerator] steer() is disabled by midSession.mode "queue". Use queue() or set mode to "auto"/"steer".'
|
|
910
1024
|
);
|
|
911
1025
|
}
|
|
@@ -958,19 +1072,19 @@ export class Agent {
|
|
|
958
1072
|
queue(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
959
1073
|
this.assertPromptValid(prompt);
|
|
960
1074
|
if (options?.stream === true) {
|
|
961
|
-
throw new
|
|
1075
|
+
throw new ValidationError(
|
|
962
1076
|
'[Agent Accelerator] queue() is non-streaming. Use stream(prompt, { enqueue: "queue" }) for a streaming follow-up.'
|
|
963
1077
|
);
|
|
964
1078
|
}
|
|
965
1079
|
if (this.midSessionConfig.mode === "steer") {
|
|
966
|
-
throw new
|
|
1080
|
+
throw new ConfigError(
|
|
967
1081
|
'[Agent Accelerator] queue() is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
968
1082
|
);
|
|
969
1083
|
}
|
|
970
1084
|
if (!this.currentRun) return this.run(prompt, options) as Promise<AgentResponse>;
|
|
971
1085
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
972
1086
|
if (this.pendingQueue.length >= max) {
|
|
973
|
-
throw new
|
|
1087
|
+
throw new ValidationError(
|
|
974
1088
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
975
1089
|
);
|
|
976
1090
|
}
|
|
@@ -1001,8 +1115,12 @@ export class Agent {
|
|
|
1001
1115
|
reason instanceof Error ? reason : new Error("Queued run aborted before start"),
|
|
1002
1116
|
{ name: "AbortError" }
|
|
1003
1117
|
);
|
|
1004
|
-
try {
|
|
1005
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
next.reject(abortErr);
|
|
1120
|
+
} catch {}
|
|
1121
|
+
try {
|
|
1122
|
+
next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
|
|
1123
|
+
} catch {}
|
|
1006
1124
|
queueMicrotask(() => this.pumpQueue());
|
|
1007
1125
|
return;
|
|
1008
1126
|
}
|
|
@@ -1011,30 +1129,48 @@ export class Agent {
|
|
|
1011
1129
|
const deferred = next.deferredStream;
|
|
1012
1130
|
try {
|
|
1013
1131
|
const real = this.startStreamingRun(next.prompt, next.options);
|
|
1014
|
-
try {
|
|
1132
|
+
try {
|
|
1133
|
+
(deferred as any).__piped = true;
|
|
1134
|
+
} catch {}
|
|
1015
1135
|
const forward = (e: StreamEvent) => {
|
|
1016
|
-
try {
|
|
1136
|
+
try {
|
|
1137
|
+
deferred.push(e);
|
|
1138
|
+
} catch {}
|
|
1017
1139
|
};
|
|
1018
1140
|
real.on("*", forward as any);
|
|
1019
1141
|
try {
|
|
1020
1142
|
deferred.onCancel(() => {
|
|
1021
|
-
try {
|
|
1143
|
+
try {
|
|
1144
|
+
real.cancel();
|
|
1145
|
+
} catch {}
|
|
1022
1146
|
});
|
|
1023
1147
|
} catch {}
|
|
1024
1148
|
real.result().then(
|
|
1025
1149
|
(res) => {
|
|
1026
|
-
try {
|
|
1027
|
-
|
|
1150
|
+
try {
|
|
1151
|
+
next.resolve(res);
|
|
1152
|
+
} catch {}
|
|
1153
|
+
try {
|
|
1154
|
+
deferred.end(res);
|
|
1155
|
+
} catch {}
|
|
1028
1156
|
},
|
|
1029
1157
|
(err) => {
|
|
1030
|
-
try {
|
|
1158
|
+
try {
|
|
1159
|
+
next.reject(err);
|
|
1160
|
+
} catch {}
|
|
1031
1161
|
// Pipe failures into the deferred stream so for-await sees them.
|
|
1032
|
-
try {
|
|
1162
|
+
try {
|
|
1163
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1164
|
+
} catch {}
|
|
1033
1165
|
}
|
|
1034
1166
|
);
|
|
1035
1167
|
} catch (err) {
|
|
1036
|
-
try {
|
|
1037
|
-
|
|
1168
|
+
try {
|
|
1169
|
+
next.reject(err);
|
|
1170
|
+
} catch {}
|
|
1171
|
+
try {
|
|
1172
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1173
|
+
} catch {}
|
|
1038
1174
|
// Keep draining even if starting failed.
|
|
1039
1175
|
queueMicrotask(() => this.pumpQueue());
|
|
1040
1176
|
}
|
|
@@ -1047,7 +1183,11 @@ export class Agent {
|
|
|
1047
1183
|
private finishCurrentRun(): void {
|
|
1048
1184
|
// Leftover steer entries become queued follow-ups instead of dropping.
|
|
1049
1185
|
let leftover: SteerEntry[] = [];
|
|
1050
|
-
try {
|
|
1186
|
+
try {
|
|
1187
|
+
leftover = this.currentRun?.steerInbox?.splice(0) ?? [];
|
|
1188
|
+
} catch {
|
|
1189
|
+
leftover = [];
|
|
1190
|
+
}
|
|
1051
1191
|
this.currentRun = null;
|
|
1052
1192
|
if (leftover.length > 0) {
|
|
1053
1193
|
try {
|
|
@@ -1057,12 +1197,18 @@ export class Agent {
|
|
|
1057
1197
|
for (const entry of leftover) {
|
|
1058
1198
|
try {
|
|
1059
1199
|
if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
|
|
1060
|
-
entry.settle?.reject(
|
|
1200
|
+
entry.settle?.reject(
|
|
1201
|
+
Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
|
|
1202
|
+
);
|
|
1061
1203
|
continue;
|
|
1062
1204
|
}
|
|
1063
|
-
} catch {
|
|
1205
|
+
} catch {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1064
1208
|
if (this.pendingQueue.length + lateSteers.length >= max) {
|
|
1065
|
-
entry.settle?.reject(
|
|
1209
|
+
entry.settle?.reject(
|
|
1210
|
+
new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
|
|
1211
|
+
);
|
|
1066
1212
|
continue;
|
|
1067
1213
|
}
|
|
1068
1214
|
// The steer was never injected: it now runs as a follow-up, and the
|
|
@@ -1091,7 +1237,9 @@ export class Agent {
|
|
|
1091
1237
|
try {
|
|
1092
1238
|
const dispose = mergeDisposers.get(runController);
|
|
1093
1239
|
if (dispose) {
|
|
1094
|
-
try {
|
|
1240
|
+
try {
|
|
1241
|
+
dispose();
|
|
1242
|
+
} catch {}
|
|
1095
1243
|
mergeDisposers.delete(runController);
|
|
1096
1244
|
}
|
|
1097
1245
|
} catch {}
|
|
@@ -1105,7 +1253,9 @@ export class Agent {
|
|
|
1105
1253
|
try {
|
|
1106
1254
|
runController.abort((userSignal as any).reason);
|
|
1107
1255
|
} catch {
|
|
1108
|
-
try {
|
|
1256
|
+
try {
|
|
1257
|
+
runController.abort();
|
|
1258
|
+
} catch {}
|
|
1109
1259
|
}
|
|
1110
1260
|
return runController.signal;
|
|
1111
1261
|
}
|
|
@@ -1121,13 +1271,19 @@ export class Agent {
|
|
|
1121
1271
|
try {
|
|
1122
1272
|
runController.abort((userSignal as any).reason);
|
|
1123
1273
|
} catch {
|
|
1124
|
-
try {
|
|
1274
|
+
try {
|
|
1275
|
+
runController.abort();
|
|
1276
|
+
} catch {}
|
|
1125
1277
|
}
|
|
1126
1278
|
};
|
|
1127
|
-
try {
|
|
1279
|
+
try {
|
|
1280
|
+
userSignal.addEventListener("abort", forward, { once: true });
|
|
1281
|
+
} catch {}
|
|
1128
1282
|
try {
|
|
1129
1283
|
mergeDisposers.set(runController, () => {
|
|
1130
|
-
try {
|
|
1284
|
+
try {
|
|
1285
|
+
userSignal.removeEventListener("abort", forward);
|
|
1286
|
+
} catch {}
|
|
1131
1287
|
});
|
|
1132
1288
|
} catch {}
|
|
1133
1289
|
return runController.signal;
|
|
@@ -1223,9 +1379,7 @@ export class Agent {
|
|
|
1223
1379
|
const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
|
|
1224
1380
|
const retry = { ...d.retry, ...runOptions?.retry };
|
|
1225
1381
|
const env = { ...d.env, ...runOptions?.env };
|
|
1226
|
-
const stop = normalizeStop(runOptions?.stop ?? d.stop);
|
|
1227
1382
|
const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
|
|
1228
|
-
const seed = runOptions?.seed ?? d.seed;
|
|
1229
1383
|
const fetchImpl = runOptions?.fetch ?? d.fetch;
|
|
1230
1384
|
return {
|
|
1231
1385
|
apiKey: this.apiKey,
|
|
@@ -1239,8 +1393,6 @@ export class Agent {
|
|
|
1239
1393
|
...(this.googleTier ? { googleTier: this.googleTier } : {}),
|
|
1240
1394
|
...(toolChoice !== undefined ? { toolChoice } : {}),
|
|
1241
1395
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1242
|
-
...(stop ? { stop } : {}),
|
|
1243
|
-
...(seed !== undefined ? { seed } : {}),
|
|
1244
1396
|
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
1245
1397
|
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
1246
1398
|
...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
|
|
@@ -1314,13 +1466,18 @@ export class Agent {
|
|
|
1314
1466
|
const failedAttempts: FailedAttempt[] = [];
|
|
1315
1467
|
for (let attempt = 0; ; attempt++) {
|
|
1316
1468
|
try {
|
|
1317
|
-
res = await runAgentLoop(
|
|
1469
|
+
res = await runAgentLoop(
|
|
1470
|
+
attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } }
|
|
1471
|
+
);
|
|
1318
1472
|
break;
|
|
1319
1473
|
} catch (err) {
|
|
1320
1474
|
if (attempt >= this.runRetries || runOptions?.signal?.aborted || !isRetryableRun(err)) throw err;
|
|
1321
1475
|
const partial = (err as { partial?: FailedAttempt }).partial;
|
|
1322
1476
|
if (partial?.usage) failedAttempts.push(partial);
|
|
1323
|
-
const delayMs = Math.max(
|
|
1477
|
+
const delayMs = Math.max(
|
|
1478
|
+
RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!,
|
|
1479
|
+
readRetryAfterMs(err) ?? 0
|
|
1480
|
+
);
|
|
1324
1481
|
telemetryBus.emitWarning(
|
|
1325
1482
|
"agent.run_retry",
|
|
1326
1483
|
`[Agent Accelerator] Run failed (${err instanceof Error ? err.message : String(err)}); retry ${attempt + 1} of ${this.runRetries} in ${delayMs} ms.`,
|
|
@@ -1345,7 +1502,9 @@ export class Agent {
|
|
|
1345
1502
|
(err) => this.endRun(mark, isAbortError(err) ? "aborted" : "failed")
|
|
1346
1503
|
);
|
|
1347
1504
|
const cleanup = () => {
|
|
1348
|
-
try {
|
|
1505
|
+
try {
|
|
1506
|
+
this.cleanupMerge(runController);
|
|
1507
|
+
} catch {}
|
|
1349
1508
|
if (this.currentRun?.runPromise === task) this.finishCurrentRun();
|
|
1350
1509
|
};
|
|
1351
1510
|
task.then(cleanup, cleanup);
|
|
@@ -1353,10 +1512,7 @@ export class Agent {
|
|
|
1353
1512
|
}
|
|
1354
1513
|
|
|
1355
1514
|
// Starts one streaming run: validates the model and thinking level synchronously, adds the prompt, builds the loop config and returns the loop's event stream, recording the run ending when it settles; in: stream()/pumpQueue, out: an AssistantMessageEventStream.
|
|
1356
|
-
private startStreamingRun(
|
|
1357
|
-
prompt: string | ContentPart[],
|
|
1358
|
-
options?: AgentRunOptions
|
|
1359
|
-
): AssistantMessageEventStream {
|
|
1515
|
+
private startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1360
1516
|
const resolved = resolveModel(this.modelStringOrSpec);
|
|
1361
1517
|
const effectiveLevel = normalizeThinkingV2(options?.thinking, options?.thinkingLevel) || this.thinkingConfig?.level;
|
|
1362
1518
|
if (effectiveLevel) {
|
|
@@ -1408,7 +1564,9 @@ export class Agent {
|
|
|
1408
1564
|
this.mergeSubagentTraces(res.subagents as any);
|
|
1409
1565
|
this.persistNow();
|
|
1410
1566
|
} catch {}
|
|
1411
|
-
try {
|
|
1567
|
+
try {
|
|
1568
|
+
this.cleanupMerge(runController);
|
|
1569
|
+
} catch {}
|
|
1412
1570
|
if (this.stateless) {
|
|
1413
1571
|
targetContext.messages = [];
|
|
1414
1572
|
targetContext.thoughtSignatures = [];
|
|
@@ -1417,7 +1575,9 @@ export class Agent {
|
|
|
1417
1575
|
},
|
|
1418
1576
|
(err) => {
|
|
1419
1577
|
this.endRun(mark, isAbortError(err) ? "aborted" : "failed");
|
|
1420
|
-
try {
|
|
1578
|
+
try {
|
|
1579
|
+
this.cleanupMerge(runController);
|
|
1580
|
+
} catch {}
|
|
1421
1581
|
if (this.currentRun?.outerStream === s) this.finishCurrentRun();
|
|
1422
1582
|
}
|
|
1423
1583
|
);
|
|
@@ -1534,12 +1694,24 @@ export class Agent {
|
|
|
1534
1694
|
else if (cfg.file) saveSessionFile(cfg.file, this as any, null);
|
|
1535
1695
|
} catch (err) {
|
|
1536
1696
|
// Surface via hook instead of swallowing silently.
|
|
1537
|
-
try {
|
|
1697
|
+
try {
|
|
1698
|
+
(cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err);
|
|
1699
|
+
} catch {}
|
|
1538
1700
|
}
|
|
1539
1701
|
}
|
|
1540
1702
|
|
|
1541
1703
|
/** Merges completed worker metadata (incl. steps) into this agent's trace registry. */
|
|
1542
|
-
private mergeSubagentTraces(
|
|
1704
|
+
private mergeSubagentTraces(
|
|
1705
|
+
subagents: Array<{
|
|
1706
|
+
trackingId?: string;
|
|
1707
|
+
name: string;
|
|
1708
|
+
sessionId?: string;
|
|
1709
|
+
parentSessionId?: string;
|
|
1710
|
+
task: string;
|
|
1711
|
+
steps?: SubAgentTrace["steps"];
|
|
1712
|
+
[k: string]: unknown;
|
|
1713
|
+
}>
|
|
1714
|
+
): void {
|
|
1543
1715
|
if (!subagents || subagents.length === 0) return;
|
|
1544
1716
|
const rawOf = (id?: string): string | undefined => {
|
|
1545
1717
|
if (!id) return undefined;
|
|
@@ -1641,15 +1813,19 @@ export class Agent {
|
|
|
1641
1813
|
try {
|
|
1642
1814
|
const g: any = globalThis as any;
|
|
1643
1815
|
this.context.messages = Array.isArray(data.messages)
|
|
1644
|
-
?
|
|
1645
|
-
|
|
1646
|
-
|
|
1816
|
+
? typeof g.structuredClone === "function"
|
|
1817
|
+
? g.structuredClone(data.messages)
|
|
1818
|
+
: JSON.parse(JSON.stringify(data.messages))
|
|
1647
1819
|
: [];
|
|
1648
1820
|
} catch {
|
|
1649
1821
|
this.context.messages = Array.isArray(data.messages) ? [...data.messages] : [];
|
|
1650
1822
|
}
|
|
1651
1823
|
this.context.thoughtSignatures = [];
|
|
1652
|
-
this.runMarks.splice(
|
|
1824
|
+
this.runMarks.splice(
|
|
1825
|
+
0,
|
|
1826
|
+
this.runMarks.length,
|
|
1827
|
+
...(data.runs ?? []).filter((r) => r.i < this.context.messages.length)
|
|
1828
|
+
);
|
|
1653
1829
|
this.restoreUsage(data.usage);
|
|
1654
1830
|
if (data.systemPrompt) {
|
|
1655
1831
|
this.context.systemPrompt = data.systemPrompt;
|
|
@@ -1658,10 +1834,18 @@ export class Agent {
|
|
|
1658
1834
|
}
|
|
1659
1835
|
// Importing a snapshot invalidates prior provider chaining/routing
|
|
1660
1836
|
// for both the previous and the restored session.
|
|
1661
|
-
try {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
try {
|
|
1837
|
+
try {
|
|
1838
|
+
clearInteractionChains(prevSid);
|
|
1839
|
+
} catch {}
|
|
1840
|
+
try {
|
|
1841
|
+
clearSessionRouting(prevSid);
|
|
1842
|
+
} catch {}
|
|
1843
|
+
try {
|
|
1844
|
+
clearInteractionChains(this.sessionId);
|
|
1845
|
+
} catch {}
|
|
1846
|
+
try {
|
|
1847
|
+
clearSessionRouting(this.sessionId);
|
|
1848
|
+
} catch {}
|
|
1665
1849
|
}
|
|
1666
1850
|
|
|
1667
1851
|
/**
|
|
@@ -1691,7 +1875,11 @@ export class Agent {
|
|
|
1691
1875
|
}
|
|
1692
1876
|
|
|
1693
1877
|
// Adds the user prompt to the target context (fresh context when stateless, stable system prompt, additionalContext as a user prefix) and marks the run start for the default session; in: a run start, out: the new SessionRunMark.
|
|
1694
|
-
private prepareTurn(
|
|
1878
|
+
private prepareTurn(
|
|
1879
|
+
prompt: string | ContentPart[],
|
|
1880
|
+
options?: AgentRunOptions,
|
|
1881
|
+
target?: AgentContext
|
|
1882
|
+
): SessionRunMark | undefined {
|
|
1695
1883
|
// Prompt validation throws synchronously in both run() and
|
|
1696
1884
|
// stream() (same timing; documented here).
|
|
1697
1885
|
if (Array.isArray(prompt) && prompt.length === 0) {
|
|
@@ -1750,23 +1938,14 @@ export class Agent {
|
|
|
1750
1938
|
run(
|
|
1751
1939
|
prompt: string | ContentPart[],
|
|
1752
1940
|
options: AgentRunOptions & { stream: true } & (
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1941
|
+
| { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
|
|
1942
|
+
| { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
|
|
1943
|
+
| { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
|
|
1944
|
+
)
|
|
1757
1945
|
): Promise<AgentResponse> & AsyncIterable<StreamEvent>;
|
|
1758
|
-
run(
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
): AssistantMessageEventStream;
|
|
1762
|
-
run(
|
|
1763
|
-
prompt: string | ContentPart[],
|
|
1764
|
-
options?: AgentRunOptions
|
|
1765
|
-
): Promise<AgentResponse>;
|
|
1766
|
-
run(
|
|
1767
|
-
prompt: string | ContentPart[],
|
|
1768
|
-
options?: AgentRunOptions
|
|
1769
|
-
): Promise<AgentResponse> | AssistantMessageEventStream {
|
|
1946
|
+
run(prompt: string | ContentPart[], options: AgentRunOptions & { stream: true }): AssistantMessageEventStream;
|
|
1947
|
+
run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
|
|
1948
|
+
run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> | AssistantMessageEventStream {
|
|
1770
1949
|
// Prompt validation throws synchronously in both run() and
|
|
1771
1950
|
// stream() (same timing; async work starts only after validation).
|
|
1772
1951
|
this.assertPromptValid(prompt);
|
|
@@ -1795,12 +1974,14 @@ export class Agent {
|
|
|
1795
1974
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn), or stops the active run and starts this prompt (interrupt).
|
|
1796
1975
|
if (this.currentRun) {
|
|
1797
1976
|
if (options?.enqueue === "interrupt") {
|
|
1798
|
-
return new Promise<AgentResponse>((resolve, reject) =>
|
|
1977
|
+
return new Promise<AgentResponse>((resolve, reject) =>
|
|
1978
|
+
this.queueUrgent({ prompt, options, isStream: false, resolve, reject })
|
|
1979
|
+
);
|
|
1799
1980
|
}
|
|
1800
1981
|
const enforced = this.midSessionConfig.mode;
|
|
1801
1982
|
const requested = options?.enqueue;
|
|
1802
1983
|
if (requested && (enforced === "steer" || enforced === "queue") && requested !== enforced) {
|
|
1803
|
-
throw new
|
|
1984
|
+
throw new ConfigError(
|
|
1804
1985
|
`[Agent Accelerator] enqueue "${requested}" is disabled by midSession.mode "${enforced}". Use ${enforced}() or set mode to "auto"/"${requested}".`
|
|
1805
1986
|
);
|
|
1806
1987
|
}
|
|
@@ -1810,14 +1991,14 @@ export class Agent {
|
|
|
1810
1991
|
return this.steerInto(this.currentRun, prompt, options);
|
|
1811
1992
|
} else {
|
|
1812
1993
|
if (this.midSessionConfig.mode === "steer") {
|
|
1813
|
-
throw new
|
|
1994
|
+
throw new ConfigError(
|
|
1814
1995
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1815
1996
|
);
|
|
1816
1997
|
}
|
|
1817
1998
|
this.assertPromptValid(prompt);
|
|
1818
1999
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1819
2000
|
if (this.pendingQueue.length >= max) {
|
|
1820
|
-
throw new
|
|
2001
|
+
throw new ValidationError(
|
|
1821
2002
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1822
2003
|
);
|
|
1823
2004
|
}
|
|
@@ -1846,13 +2027,9 @@ export class Agent {
|
|
|
1846
2027
|
prompt: string | ContentPart[],
|
|
1847
2028
|
optionsOrStream?: boolean | AgentRunOptions
|
|
1848
2029
|
): Promise<AgentResponse> & AsyncIterable<string | StreamEvent> {
|
|
1849
|
-
const isStream =
|
|
1850
|
-
typeof optionsOrStream === "boolean"
|
|
1851
|
-
? optionsOrStream
|
|
1852
|
-
: optionsOrStream?.stream === true;
|
|
2030
|
+
const isStream = typeof optionsOrStream === "boolean" ? optionsOrStream : optionsOrStream?.stream === true;
|
|
1853
2031
|
|
|
1854
|
-
const runOpts: AgentRunOptions =
|
|
1855
|
-
typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
2032
|
+
const runOpts: AgentRunOptions = typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
1856
2033
|
|
|
1857
2034
|
if (isStream) {
|
|
1858
2035
|
const stream = this.stream(prompt, runOpts);
|
|
@@ -1860,7 +2037,8 @@ export class Agent {
|
|
|
1860
2037
|
if (runOpts.onDelta || runOpts.onThinkingDelta || runOpts.onEvent) {
|
|
1861
2038
|
if (runOpts.onEvent) stream.on("*", runOpts.onEvent as any);
|
|
1862
2039
|
if (runOpts.onDelta) stream.on("text_delta", (e: any) => runOpts.onDelta!(e.delta!, e));
|
|
1863
|
-
if (runOpts.onThinkingDelta)
|
|
2040
|
+
if (runOpts.onThinkingDelta)
|
|
2041
|
+
stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
|
|
1864
2042
|
}
|
|
1865
2043
|
const stringStream: any = {
|
|
1866
2044
|
[Symbol.asyncIterator]: async function* () {
|
|
@@ -1901,10 +2079,7 @@ export class Agent {
|
|
|
1901
2079
|
* }
|
|
1902
2080
|
* ```
|
|
1903
2081
|
*/
|
|
1904
|
-
stream(
|
|
1905
|
-
prompt: string | ContentPart[],
|
|
1906
|
-
options?: AgentRunOptions
|
|
1907
|
-
): AssistantMessageEventStream {
|
|
2082
|
+
stream(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1908
2083
|
// Same sync validation timing as run().
|
|
1909
2084
|
this.assertPromptValid(prompt);
|
|
1910
2085
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn).
|
|
@@ -1912,8 +2087,12 @@ export class Agent {
|
|
|
1912
2087
|
const enforcedStream = this.midSessionConfig.mode;
|
|
1913
2088
|
const urgent = options?.enqueue === "interrupt"; // stops the active run and goes first, in every mode and whatever the queue holds
|
|
1914
2089
|
const requestedStream = options?.enqueue === "interrupt" ? undefined : options?.enqueue;
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
2090
|
+
if (
|
|
2091
|
+
requestedStream &&
|
|
2092
|
+
(enforcedStream === "steer" || enforcedStream === "queue") &&
|
|
2093
|
+
requestedStream !== enforcedStream
|
|
2094
|
+
) {
|
|
2095
|
+
throw new ConfigError(
|
|
1917
2096
|
`[Agent Accelerator] enqueue "${requestedStream}" is disabled by midSession.mode "${enforcedStream}". Use ${enforcedStream}() or set mode to "auto"/"${requestedStream}".`
|
|
1918
2097
|
);
|
|
1919
2098
|
}
|
|
@@ -1937,28 +2116,40 @@ export class Agent {
|
|
|
1937
2116
|
activePromise.then(
|
|
1938
2117
|
(res) => {
|
|
1939
2118
|
try {
|
|
1940
|
-
stub.push({
|
|
2119
|
+
stub.push({
|
|
2120
|
+
type: "done",
|
|
2121
|
+
delta: "",
|
|
2122
|
+
usage: res.usage,
|
|
2123
|
+
finishReason: res.finishReason,
|
|
2124
|
+
responseId: res.responseId,
|
|
2125
|
+
} as any);
|
|
2126
|
+
} catch {}
|
|
2127
|
+
try {
|
|
2128
|
+
stub.end(res);
|
|
1941
2129
|
} catch {}
|
|
1942
|
-
try { stub.end(res); } catch {}
|
|
1943
2130
|
},
|
|
1944
2131
|
(err) => {
|
|
1945
|
-
try {
|
|
2132
|
+
try {
|
|
2133
|
+
stub.fail(err instanceof Error ? err : new Error(String(err)));
|
|
2134
|
+
} catch {}
|
|
1946
2135
|
}
|
|
1947
2136
|
);
|
|
1948
2137
|
} else {
|
|
1949
|
-
try {
|
|
2138
|
+
try {
|
|
2139
|
+
stub.fail(new ValidationError("[Agent Accelerator] No active run to steer."));
|
|
2140
|
+
} catch {}
|
|
1950
2141
|
}
|
|
1951
2142
|
return stub;
|
|
1952
2143
|
}
|
|
1953
2144
|
if (!urgent && this.midSessionConfig.mode === "steer") {
|
|
1954
|
-
throw new
|
|
2145
|
+
throw new ConfigError(
|
|
1955
2146
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1956
2147
|
);
|
|
1957
2148
|
}
|
|
1958
2149
|
this.assertPromptValid(prompt);
|
|
1959
2150
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1960
2151
|
if (!urgent && this.pendingQueue.length >= max) {
|
|
1961
|
-
throw new
|
|
2152
|
+
throw new ValidationError(
|
|
1962
2153
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1963
2154
|
);
|
|
1964
2155
|
}
|
|
@@ -2005,7 +2196,9 @@ export class Agent {
|
|
|
2005
2196
|
if (idx >= 0) {
|
|
2006
2197
|
this.pendingQueue.splice(idx, 1);
|
|
2007
2198
|
queuedEntry.reject(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2008
|
-
try {
|
|
2199
|
+
try {
|
|
2200
|
+
deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2201
|
+
} catch {}
|
|
2009
2202
|
}
|
|
2010
2203
|
} catch {}
|
|
2011
2204
|
});
|
|
@@ -2023,7 +2216,13 @@ export class Agent {
|
|
|
2023
2216
|
// Otherwise (edge), end it here.
|
|
2024
2217
|
try {
|
|
2025
2218
|
if ((deferred as any).__piped !== true) {
|
|
2026
|
-
deferred.push({
|
|
2219
|
+
deferred.push({
|
|
2220
|
+
type: "done",
|
|
2221
|
+
delta: "",
|
|
2222
|
+
usage: res.usage,
|
|
2223
|
+
finishReason: res.finishReason,
|
|
2224
|
+
responseId: res.responseId,
|
|
2225
|
+
} as any);
|
|
2027
2226
|
deferred.end(res);
|
|
2028
2227
|
}
|
|
2029
2228
|
} catch {}
|
|
@@ -2045,10 +2244,7 @@ export class Agent {
|
|
|
2045
2244
|
}
|
|
2046
2245
|
|
|
2047
2246
|
// Attaches onEvent/onDelta/onThinkingDelta to a stream and, with wrapThinking, adds display-only <think> tags and whitespace hygiene on the outer events; in: stream()/pumpQueue, out: listeners on the stream.
|
|
2048
|
-
private wireStreamCallbacks(
|
|
2049
|
-
s: AssistantMessageEventStream,
|
|
2050
|
-
options?: AgentRunOptions
|
|
2051
|
-
): void {
|
|
2247
|
+
private wireStreamCallbacks(s: AssistantMessageEventStream, options?: AgentRunOptions): void {
|
|
2052
2248
|
// Wire one-liner callbacks so `stream:true` + onDelta is enough — no manual for-await needed
|
|
2053
2249
|
if (options?.wrapThinking) {
|
|
2054
2250
|
// Auto-wrap reasoning as <think>…</think> per-turn — clean boundaries across multi-turn agent runs.
|
|
@@ -2125,7 +2321,9 @@ export class Agent {
|
|
|
2125
2321
|
const raw: string = e.delta ?? "";
|
|
2126
2322
|
if (!justClosedThink) return raw;
|
|
2127
2323
|
const stripped = raw.replace(/^\n+/, "");
|
|
2128
|
-
try {
|
|
2324
|
+
try {
|
|
2325
|
+
e.delta = stripped;
|
|
2326
|
+
} catch {}
|
|
2129
2327
|
if (!stripped) return "";
|
|
2130
2328
|
justClosedThink = false;
|
|
2131
2329
|
return stripped;
|
|
@@ -2222,11 +2420,13 @@ export class Agent {
|
|
|
2222
2420
|
closeSynthetic();
|
|
2223
2421
|
if (justClosedThink) {
|
|
2224
2422
|
const stripped: string = (e.delta ?? "").replace(/^\n+/, "");
|
|
2225
|
-
try {
|
|
2423
|
+
try {
|
|
2424
|
+
e.delta = stripped;
|
|
2425
|
+
} catch {}
|
|
2226
2426
|
if (!stripped) return;
|
|
2227
2427
|
justClosedThink = false;
|
|
2228
2428
|
}
|
|
2229
|
-
needThinkNewline = !(
|
|
2429
|
+
needThinkNewline = !(e.delta ?? "").endsWith("\n");
|
|
2230
2430
|
});
|
|
2231
2431
|
s.on("tool_call_start" as any, () => {
|
|
2232
2432
|
closeSynthetic();
|
|
@@ -2304,7 +2504,12 @@ export function normalizeThinking(config: AgentConfig, fallbackLevel?: string):
|
|
|
2304
2504
|
try {
|
|
2305
2505
|
const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
|
|
2306
2506
|
const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
|
|
2307
|
-
const provStr =
|
|
2507
|
+
const provStr =
|
|
2508
|
+
typeof rawModel === "object" && rawModel?.provider
|
|
2509
|
+
? rawModel.provider
|
|
2510
|
+
: modelStr.includes("/")
|
|
2511
|
+
? modelStr.split("/")[0]
|
|
2512
|
+
: "";
|
|
2308
2513
|
const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
|
|
2309
2514
|
if (actualModelId) {
|
|
2310
2515
|
validateModelThinking(provStr || "openrouter", actualModelId, level);
|