agent-accelerator 0.5.8 → 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 +26 -5
- 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 +374 -167
- package/src/agent/architecture.md +14 -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 +449 -373
- 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 +58 -28
- 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 +46 -49
- package/src/providers/architecture.md +10 -8
- package/src/providers/google.ts +150 -62
- package/src/providers/openai-compat.ts +60 -58
- package/src/providers/openai.ts +57 -39
- package/src/providers/openrouter.ts +87 -32
- package/src/providers/registry.ts +12 -26
- package/src/providers/shared.ts +89 -39
- package/src/providers.ts +56 -115
- package/src/scheduler/architecture.md +9 -8
- 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 +50 -44
- 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 +17 -31
- 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,32 +56,50 @@ 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 };
|
|
63
99
|
return { enabled: true, level: overrideLevel as ThinkingConfig["level"] };
|
|
64
100
|
}
|
|
65
101
|
|
|
66
|
-
import { ConfigError, SessionLoadError, isAbortError } from "../types/errors.ts";
|
|
102
|
+
import { ConfigError, SessionLoadError, ValidationError, isAbortError } from "../types/errors.ts";
|
|
67
103
|
|
|
68
104
|
/**
|
|
69
105
|
* Disposers for the manual user-signal forward listeners (fallback when
|
|
@@ -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" +
|
|
@@ -697,16 +780,22 @@ export class Agent {
|
|
|
697
780
|
return out || undefined;
|
|
698
781
|
}
|
|
699
782
|
|
|
700
|
-
/**
|
|
783
|
+
/** Starts a fresh conversation: clears messages, signatures, run marks and the run's saved tool results, keeps config and spending totals. */
|
|
701
784
|
reset(): void {
|
|
702
785
|
const sid = this.sessionId;
|
|
703
786
|
this.context.messages = [];
|
|
704
787
|
this.context.thoughtSignatures = [];
|
|
705
788
|
this.context.systemPrompt = this.getFullInstructions();
|
|
706
789
|
this.runMarks.length = 0;
|
|
790
|
+
this.toolRecords.length = 0; // a cleared conversation has no run to resume, so its finished tool results go too
|
|
791
|
+
this.activeMark = undefined;
|
|
707
792
|
// Reset also drops session-scoped provider chaining/routing.
|
|
708
|
-
try {
|
|
709
|
-
|
|
793
|
+
try {
|
|
794
|
+
clearInteractionChains(sid);
|
|
795
|
+
} catch {}
|
|
796
|
+
try {
|
|
797
|
+
clearSessionRouting(sid);
|
|
798
|
+
} catch {}
|
|
710
799
|
}
|
|
711
800
|
|
|
712
801
|
/**
|
|
@@ -725,17 +814,27 @@ export class Agent {
|
|
|
725
814
|
/** Drops one named session. No-op for unknown ids. */
|
|
726
815
|
disposeSession(id: string): void {
|
|
727
816
|
unregisterSessionFolder(id);
|
|
728
|
-
try {
|
|
729
|
-
|
|
730
|
-
|
|
817
|
+
try {
|
|
818
|
+
this.sessions.delete(id);
|
|
819
|
+
} catch {}
|
|
820
|
+
try {
|
|
821
|
+
clearInteractionChains(id);
|
|
822
|
+
} catch {}
|
|
823
|
+
try {
|
|
824
|
+
clearSessionRouting(id);
|
|
825
|
+
} catch {}
|
|
731
826
|
}
|
|
732
827
|
|
|
733
828
|
/** Drops all named sessions except the default context. */
|
|
734
829
|
clearSessions(): void {
|
|
735
830
|
try {
|
|
736
831
|
for (const id of [...this.sessions.keys()]) {
|
|
737
|
-
try {
|
|
738
|
-
|
|
832
|
+
try {
|
|
833
|
+
clearInteractionChains(id);
|
|
834
|
+
} catch {}
|
|
835
|
+
try {
|
|
836
|
+
clearSessionRouting(id);
|
|
837
|
+
} catch {}
|
|
739
838
|
}
|
|
740
839
|
this.sessions.clear();
|
|
741
840
|
} catch {}
|
|
@@ -751,8 +850,12 @@ export class Agent {
|
|
|
751
850
|
for (const [key, entry] of [...this.sessions.entries()]) {
|
|
752
851
|
if (now - entry.lastAccess > SESSION_TTL_MS) {
|
|
753
852
|
this.sessions.delete(key);
|
|
754
|
-
try {
|
|
755
|
-
|
|
853
|
+
try {
|
|
854
|
+
clearInteractionChains(key);
|
|
855
|
+
} catch {}
|
|
856
|
+
try {
|
|
857
|
+
clearSessionRouting(key);
|
|
858
|
+
} catch {}
|
|
756
859
|
}
|
|
757
860
|
}
|
|
758
861
|
} catch {}
|
|
@@ -769,12 +872,19 @@ export class Agent {
|
|
|
769
872
|
let oldest: string | undefined;
|
|
770
873
|
let oldestTs = Infinity;
|
|
771
874
|
for (const [key, entry] of this.sessions) {
|
|
772
|
-
if (entry.lastAccess < oldestTs) {
|
|
875
|
+
if (entry.lastAccess < oldestTs) {
|
|
876
|
+
oldestTs = entry.lastAccess;
|
|
877
|
+
oldest = key;
|
|
878
|
+
}
|
|
773
879
|
}
|
|
774
880
|
if (!oldest) break;
|
|
775
881
|
this.sessions.delete(oldest);
|
|
776
|
-
try {
|
|
777
|
-
|
|
882
|
+
try {
|
|
883
|
+
clearInteractionChains(oldest);
|
|
884
|
+
} catch {}
|
|
885
|
+
try {
|
|
886
|
+
clearSessionRouting(oldest);
|
|
887
|
+
} catch {}
|
|
778
888
|
}
|
|
779
889
|
} catch {}
|
|
780
890
|
return fresh;
|
|
@@ -787,8 +897,12 @@ export class Agent {
|
|
|
787
897
|
const err = Object.assign(new Error("Queue cleared"), { name: "AbortError" });
|
|
788
898
|
const pending = this.pendingQueue.splice(0);
|
|
789
899
|
for (const entry of pending) {
|
|
790
|
-
try {
|
|
791
|
-
|
|
900
|
+
try {
|
|
901
|
+
entry.reject(err);
|
|
902
|
+
} catch {}
|
|
903
|
+
try {
|
|
904
|
+
entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err)));
|
|
905
|
+
} catch {}
|
|
792
906
|
}
|
|
793
907
|
this.persistNow();
|
|
794
908
|
return n;
|
|
@@ -798,7 +912,7 @@ export class Agent {
|
|
|
798
912
|
private assertSteerRoom(inbox: SteerEntry[]): void {
|
|
799
913
|
const max = this.maxSteers ?? 20;
|
|
800
914
|
if (inbox.length >= max) {
|
|
801
|
-
throw new
|
|
915
|
+
throw new ValidationError(
|
|
802
916
|
`[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
|
|
803
917
|
);
|
|
804
918
|
}
|
|
@@ -837,7 +951,7 @@ export class Agent {
|
|
|
837
951
|
// Throws synchronously on an empty content-part array so run() and stream() fail at the same moment; in: any prompt entry point, out: nothing.
|
|
838
952
|
private assertPromptValid(prompt: string | ContentPart[]): void {
|
|
839
953
|
if (Array.isArray(prompt) && prompt.length === 0) {
|
|
840
|
-
throw new
|
|
954
|
+
throw new ValidationError(
|
|
841
955
|
"[Agent Accelerator] Prompt cannot be empty: pass a non-empty string or at least one content part."
|
|
842
956
|
);
|
|
843
957
|
}
|
|
@@ -856,7 +970,9 @@ export class Agent {
|
|
|
856
970
|
interrupt(arg?: Error | string | ContentPart[], options?: AgentRunOptions): void | Promise<AgentResponse> {
|
|
857
971
|
if (typeof arg === "string" || Array.isArray(arg)) {
|
|
858
972
|
if (options?.stream === true) {
|
|
859
|
-
throw new
|
|
973
|
+
throw new ValidationError(
|
|
974
|
+
'[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.'
|
|
975
|
+
);
|
|
860
976
|
}
|
|
861
977
|
return this.run(arg, { ...options, enqueue: "interrupt" });
|
|
862
978
|
}
|
|
@@ -898,12 +1014,12 @@ export class Agent {
|
|
|
898
1014
|
steer(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
899
1015
|
this.assertPromptValid(prompt);
|
|
900
1016
|
if (options?.stream === true) {
|
|
901
|
-
throw new
|
|
1017
|
+
throw new ValidationError(
|
|
902
1018
|
'[Agent Accelerator] steer() is non-streaming. Use stream(prompt, { enqueue: "steer" }) for a streaming steer.'
|
|
903
1019
|
);
|
|
904
1020
|
}
|
|
905
1021
|
if (this.midSessionConfig.mode === "queue") {
|
|
906
|
-
throw new
|
|
1022
|
+
throw new ConfigError(
|
|
907
1023
|
'[Agent Accelerator] steer() is disabled by midSession.mode "queue". Use queue() or set mode to "auto"/"steer".'
|
|
908
1024
|
);
|
|
909
1025
|
}
|
|
@@ -956,19 +1072,19 @@ export class Agent {
|
|
|
956
1072
|
queue(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
957
1073
|
this.assertPromptValid(prompt);
|
|
958
1074
|
if (options?.stream === true) {
|
|
959
|
-
throw new
|
|
1075
|
+
throw new ValidationError(
|
|
960
1076
|
'[Agent Accelerator] queue() is non-streaming. Use stream(prompt, { enqueue: "queue" }) for a streaming follow-up.'
|
|
961
1077
|
);
|
|
962
1078
|
}
|
|
963
1079
|
if (this.midSessionConfig.mode === "steer") {
|
|
964
|
-
throw new
|
|
1080
|
+
throw new ConfigError(
|
|
965
1081
|
'[Agent Accelerator] queue() is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
966
1082
|
);
|
|
967
1083
|
}
|
|
968
1084
|
if (!this.currentRun) return this.run(prompt, options) as Promise<AgentResponse>;
|
|
969
1085
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
970
1086
|
if (this.pendingQueue.length >= max) {
|
|
971
|
-
throw new
|
|
1087
|
+
throw new ValidationError(
|
|
972
1088
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
973
1089
|
);
|
|
974
1090
|
}
|
|
@@ -999,8 +1115,12 @@ export class Agent {
|
|
|
999
1115
|
reason instanceof Error ? reason : new Error("Queued run aborted before start"),
|
|
1000
1116
|
{ name: "AbortError" }
|
|
1001
1117
|
);
|
|
1002
|
-
try {
|
|
1003
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
next.reject(abortErr);
|
|
1120
|
+
} catch {}
|
|
1121
|
+
try {
|
|
1122
|
+
next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
|
|
1123
|
+
} catch {}
|
|
1004
1124
|
queueMicrotask(() => this.pumpQueue());
|
|
1005
1125
|
return;
|
|
1006
1126
|
}
|
|
@@ -1009,30 +1129,48 @@ export class Agent {
|
|
|
1009
1129
|
const deferred = next.deferredStream;
|
|
1010
1130
|
try {
|
|
1011
1131
|
const real = this.startStreamingRun(next.prompt, next.options);
|
|
1012
|
-
try {
|
|
1132
|
+
try {
|
|
1133
|
+
(deferred as any).__piped = true;
|
|
1134
|
+
} catch {}
|
|
1013
1135
|
const forward = (e: StreamEvent) => {
|
|
1014
|
-
try {
|
|
1136
|
+
try {
|
|
1137
|
+
deferred.push(e);
|
|
1138
|
+
} catch {}
|
|
1015
1139
|
};
|
|
1016
1140
|
real.on("*", forward as any);
|
|
1017
1141
|
try {
|
|
1018
1142
|
deferred.onCancel(() => {
|
|
1019
|
-
try {
|
|
1143
|
+
try {
|
|
1144
|
+
real.cancel();
|
|
1145
|
+
} catch {}
|
|
1020
1146
|
});
|
|
1021
1147
|
} catch {}
|
|
1022
1148
|
real.result().then(
|
|
1023
1149
|
(res) => {
|
|
1024
|
-
try {
|
|
1025
|
-
|
|
1150
|
+
try {
|
|
1151
|
+
next.resolve(res);
|
|
1152
|
+
} catch {}
|
|
1153
|
+
try {
|
|
1154
|
+
deferred.end(res);
|
|
1155
|
+
} catch {}
|
|
1026
1156
|
},
|
|
1027
1157
|
(err) => {
|
|
1028
|
-
try {
|
|
1158
|
+
try {
|
|
1159
|
+
next.reject(err);
|
|
1160
|
+
} catch {}
|
|
1029
1161
|
// Pipe failures into the deferred stream so for-await sees them.
|
|
1030
|
-
try {
|
|
1162
|
+
try {
|
|
1163
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1164
|
+
} catch {}
|
|
1031
1165
|
}
|
|
1032
1166
|
);
|
|
1033
1167
|
} catch (err) {
|
|
1034
|
-
try {
|
|
1035
|
-
|
|
1168
|
+
try {
|
|
1169
|
+
next.reject(err);
|
|
1170
|
+
} catch {}
|
|
1171
|
+
try {
|
|
1172
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1173
|
+
} catch {}
|
|
1036
1174
|
// Keep draining even if starting failed.
|
|
1037
1175
|
queueMicrotask(() => this.pumpQueue());
|
|
1038
1176
|
}
|
|
@@ -1045,7 +1183,11 @@ export class Agent {
|
|
|
1045
1183
|
private finishCurrentRun(): void {
|
|
1046
1184
|
// Leftover steer entries become queued follow-ups instead of dropping.
|
|
1047
1185
|
let leftover: SteerEntry[] = [];
|
|
1048
|
-
try {
|
|
1186
|
+
try {
|
|
1187
|
+
leftover = this.currentRun?.steerInbox?.splice(0) ?? [];
|
|
1188
|
+
} catch {
|
|
1189
|
+
leftover = [];
|
|
1190
|
+
}
|
|
1049
1191
|
this.currentRun = null;
|
|
1050
1192
|
if (leftover.length > 0) {
|
|
1051
1193
|
try {
|
|
@@ -1055,12 +1197,18 @@ export class Agent {
|
|
|
1055
1197
|
for (const entry of leftover) {
|
|
1056
1198
|
try {
|
|
1057
1199
|
if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
|
|
1058
|
-
entry.settle?.reject(
|
|
1200
|
+
entry.settle?.reject(
|
|
1201
|
+
Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
|
|
1202
|
+
);
|
|
1059
1203
|
continue;
|
|
1060
1204
|
}
|
|
1061
|
-
} catch {
|
|
1205
|
+
} catch {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1062
1208
|
if (this.pendingQueue.length + lateSteers.length >= max) {
|
|
1063
|
-
entry.settle?.reject(
|
|
1209
|
+
entry.settle?.reject(
|
|
1210
|
+
new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
|
|
1211
|
+
);
|
|
1064
1212
|
continue;
|
|
1065
1213
|
}
|
|
1066
1214
|
// The steer was never injected: it now runs as a follow-up, and the
|
|
@@ -1089,7 +1237,9 @@ export class Agent {
|
|
|
1089
1237
|
try {
|
|
1090
1238
|
const dispose = mergeDisposers.get(runController);
|
|
1091
1239
|
if (dispose) {
|
|
1092
|
-
try {
|
|
1240
|
+
try {
|
|
1241
|
+
dispose();
|
|
1242
|
+
} catch {}
|
|
1093
1243
|
mergeDisposers.delete(runController);
|
|
1094
1244
|
}
|
|
1095
1245
|
} catch {}
|
|
@@ -1103,7 +1253,9 @@ export class Agent {
|
|
|
1103
1253
|
try {
|
|
1104
1254
|
runController.abort((userSignal as any).reason);
|
|
1105
1255
|
} catch {
|
|
1106
|
-
try {
|
|
1256
|
+
try {
|
|
1257
|
+
runController.abort();
|
|
1258
|
+
} catch {}
|
|
1107
1259
|
}
|
|
1108
1260
|
return runController.signal;
|
|
1109
1261
|
}
|
|
@@ -1119,13 +1271,19 @@ export class Agent {
|
|
|
1119
1271
|
try {
|
|
1120
1272
|
runController.abort((userSignal as any).reason);
|
|
1121
1273
|
} catch {
|
|
1122
|
-
try {
|
|
1274
|
+
try {
|
|
1275
|
+
runController.abort();
|
|
1276
|
+
} catch {}
|
|
1123
1277
|
}
|
|
1124
1278
|
};
|
|
1125
|
-
try {
|
|
1279
|
+
try {
|
|
1280
|
+
userSignal.addEventListener("abort", forward, { once: true });
|
|
1281
|
+
} catch {}
|
|
1126
1282
|
try {
|
|
1127
1283
|
mergeDisposers.set(runController, () => {
|
|
1128
|
-
try {
|
|
1284
|
+
try {
|
|
1285
|
+
userSignal.removeEventListener("abort", forward);
|
|
1286
|
+
} catch {}
|
|
1129
1287
|
});
|
|
1130
1288
|
} catch {}
|
|
1131
1289
|
return runController.signal;
|
|
@@ -1221,9 +1379,7 @@ export class Agent {
|
|
|
1221
1379
|
const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
|
|
1222
1380
|
const retry = { ...d.retry, ...runOptions?.retry };
|
|
1223
1381
|
const env = { ...d.env, ...runOptions?.env };
|
|
1224
|
-
const stop = normalizeStop(runOptions?.stop ?? d.stop);
|
|
1225
1382
|
const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
|
|
1226
|
-
const seed = runOptions?.seed ?? d.seed;
|
|
1227
1383
|
const fetchImpl = runOptions?.fetch ?? d.fetch;
|
|
1228
1384
|
return {
|
|
1229
1385
|
apiKey: this.apiKey,
|
|
@@ -1237,8 +1393,6 @@ export class Agent {
|
|
|
1237
1393
|
...(this.googleTier ? { googleTier: this.googleTier } : {}),
|
|
1238
1394
|
...(toolChoice !== undefined ? { toolChoice } : {}),
|
|
1239
1395
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1240
|
-
...(stop ? { stop } : {}),
|
|
1241
|
-
...(seed !== undefined ? { seed } : {}),
|
|
1242
1396
|
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
1243
1397
|
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
1244
1398
|
...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
|
|
@@ -1312,13 +1466,18 @@ export class Agent {
|
|
|
1312
1466
|
const failedAttempts: FailedAttempt[] = [];
|
|
1313
1467
|
for (let attempt = 0; ; attempt++) {
|
|
1314
1468
|
try {
|
|
1315
|
-
res = await runAgentLoop(
|
|
1469
|
+
res = await runAgentLoop(
|
|
1470
|
+
attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } }
|
|
1471
|
+
);
|
|
1316
1472
|
break;
|
|
1317
1473
|
} catch (err) {
|
|
1318
1474
|
if (attempt >= this.runRetries || runOptions?.signal?.aborted || !isRetryableRun(err)) throw err;
|
|
1319
1475
|
const partial = (err as { partial?: FailedAttempt }).partial;
|
|
1320
1476
|
if (partial?.usage) failedAttempts.push(partial);
|
|
1321
|
-
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
|
+
);
|
|
1322
1481
|
telemetryBus.emitWarning(
|
|
1323
1482
|
"agent.run_retry",
|
|
1324
1483
|
`[Agent Accelerator] Run failed (${err instanceof Error ? err.message : String(err)}); retry ${attempt + 1} of ${this.runRetries} in ${delayMs} ms.`,
|
|
@@ -1343,7 +1502,9 @@ export class Agent {
|
|
|
1343
1502
|
(err) => this.endRun(mark, isAbortError(err) ? "aborted" : "failed")
|
|
1344
1503
|
);
|
|
1345
1504
|
const cleanup = () => {
|
|
1346
|
-
try {
|
|
1505
|
+
try {
|
|
1506
|
+
this.cleanupMerge(runController);
|
|
1507
|
+
} catch {}
|
|
1347
1508
|
if (this.currentRun?.runPromise === task) this.finishCurrentRun();
|
|
1348
1509
|
};
|
|
1349
1510
|
task.then(cleanup, cleanup);
|
|
@@ -1351,10 +1512,7 @@ export class Agent {
|
|
|
1351
1512
|
}
|
|
1352
1513
|
|
|
1353
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.
|
|
1354
|
-
private startStreamingRun(
|
|
1355
|
-
prompt: string | ContentPart[],
|
|
1356
|
-
options?: AgentRunOptions
|
|
1357
|
-
): AssistantMessageEventStream {
|
|
1515
|
+
private startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1358
1516
|
const resolved = resolveModel(this.modelStringOrSpec);
|
|
1359
1517
|
const effectiveLevel = normalizeThinkingV2(options?.thinking, options?.thinkingLevel) || this.thinkingConfig?.level;
|
|
1360
1518
|
if (effectiveLevel) {
|
|
@@ -1406,7 +1564,9 @@ export class Agent {
|
|
|
1406
1564
|
this.mergeSubagentTraces(res.subagents as any);
|
|
1407
1565
|
this.persistNow();
|
|
1408
1566
|
} catch {}
|
|
1409
|
-
try {
|
|
1567
|
+
try {
|
|
1568
|
+
this.cleanupMerge(runController);
|
|
1569
|
+
} catch {}
|
|
1410
1570
|
if (this.stateless) {
|
|
1411
1571
|
targetContext.messages = [];
|
|
1412
1572
|
targetContext.thoughtSignatures = [];
|
|
@@ -1415,7 +1575,9 @@ export class Agent {
|
|
|
1415
1575
|
},
|
|
1416
1576
|
(err) => {
|
|
1417
1577
|
this.endRun(mark, isAbortError(err) ? "aborted" : "failed");
|
|
1418
|
-
try {
|
|
1578
|
+
try {
|
|
1579
|
+
this.cleanupMerge(runController);
|
|
1580
|
+
} catch {}
|
|
1419
1581
|
if (this.currentRun?.outerStream === s) this.finishCurrentRun();
|
|
1420
1582
|
}
|
|
1421
1583
|
);
|
|
@@ -1532,12 +1694,24 @@ export class Agent {
|
|
|
1532
1694
|
else if (cfg.file) saveSessionFile(cfg.file, this as any, null);
|
|
1533
1695
|
} catch (err) {
|
|
1534
1696
|
// Surface via hook instead of swallowing silently.
|
|
1535
|
-
try {
|
|
1697
|
+
try {
|
|
1698
|
+
(cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err);
|
|
1699
|
+
} catch {}
|
|
1536
1700
|
}
|
|
1537
1701
|
}
|
|
1538
1702
|
|
|
1539
1703
|
/** Merges completed worker metadata (incl. steps) into this agent's trace registry. */
|
|
1540
|
-
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 {
|
|
1541
1715
|
if (!subagents || subagents.length === 0) return;
|
|
1542
1716
|
const rawOf = (id?: string): string | undefined => {
|
|
1543
1717
|
if (!id) return undefined;
|
|
@@ -1639,15 +1813,19 @@ export class Agent {
|
|
|
1639
1813
|
try {
|
|
1640
1814
|
const g: any = globalThis as any;
|
|
1641
1815
|
this.context.messages = Array.isArray(data.messages)
|
|
1642
|
-
?
|
|
1643
|
-
|
|
1644
|
-
|
|
1816
|
+
? typeof g.structuredClone === "function"
|
|
1817
|
+
? g.structuredClone(data.messages)
|
|
1818
|
+
: JSON.parse(JSON.stringify(data.messages))
|
|
1645
1819
|
: [];
|
|
1646
1820
|
} catch {
|
|
1647
1821
|
this.context.messages = Array.isArray(data.messages) ? [...data.messages] : [];
|
|
1648
1822
|
}
|
|
1649
1823
|
this.context.thoughtSignatures = [];
|
|
1650
|
-
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
|
+
);
|
|
1651
1829
|
this.restoreUsage(data.usage);
|
|
1652
1830
|
if (data.systemPrompt) {
|
|
1653
1831
|
this.context.systemPrompt = data.systemPrompt;
|
|
@@ -1656,10 +1834,18 @@ export class Agent {
|
|
|
1656
1834
|
}
|
|
1657
1835
|
// Importing a snapshot invalidates prior provider chaining/routing
|
|
1658
1836
|
// for both the previous and the restored session.
|
|
1659
|
-
try {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
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 {}
|
|
1663
1849
|
}
|
|
1664
1850
|
|
|
1665
1851
|
/**
|
|
@@ -1689,11 +1875,15 @@ export class Agent {
|
|
|
1689
1875
|
}
|
|
1690
1876
|
|
|
1691
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.
|
|
1692
|
-
private prepareTurn(
|
|
1878
|
+
private prepareTurn(
|
|
1879
|
+
prompt: string | ContentPart[],
|
|
1880
|
+
options?: AgentRunOptions,
|
|
1881
|
+
target?: AgentContext
|
|
1882
|
+
): SessionRunMark | undefined {
|
|
1693
1883
|
// Prompt validation throws synchronously in both run() and
|
|
1694
1884
|
// stream() (same timing; documented here).
|
|
1695
1885
|
if (Array.isArray(prompt) && prompt.length === 0) {
|
|
1696
|
-
throw new
|
|
1886
|
+
throw new ValidationError(
|
|
1697
1887
|
"[Agent Accelerator] Prompt cannot be empty: pass a non-empty string or at least one content part."
|
|
1698
1888
|
);
|
|
1699
1889
|
}
|
|
@@ -1748,23 +1938,14 @@ export class Agent {
|
|
|
1748
1938
|
run(
|
|
1749
1939
|
prompt: string | ContentPart[],
|
|
1750
1940
|
options: AgentRunOptions & { stream: true } & (
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1941
|
+
| { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
|
|
1942
|
+
| { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
|
|
1943
|
+
| { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
|
|
1944
|
+
)
|
|
1755
1945
|
): Promise<AgentResponse> & AsyncIterable<StreamEvent>;
|
|
1756
|
-
run(
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
): AssistantMessageEventStream;
|
|
1760
|
-
run(
|
|
1761
|
-
prompt: string | ContentPart[],
|
|
1762
|
-
options?: AgentRunOptions
|
|
1763
|
-
): Promise<AgentResponse>;
|
|
1764
|
-
run(
|
|
1765
|
-
prompt: string | ContentPart[],
|
|
1766
|
-
options?: AgentRunOptions
|
|
1767
|
-
): 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 {
|
|
1768
1949
|
// Prompt validation throws synchronously in both run() and
|
|
1769
1950
|
// stream() (same timing; async work starts only after validation).
|
|
1770
1951
|
this.assertPromptValid(prompt);
|
|
@@ -1793,12 +1974,14 @@ export class Agent {
|
|
|
1793
1974
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn), or stops the active run and starts this prompt (interrupt).
|
|
1794
1975
|
if (this.currentRun) {
|
|
1795
1976
|
if (options?.enqueue === "interrupt") {
|
|
1796
|
-
return new Promise<AgentResponse>((resolve, reject) =>
|
|
1977
|
+
return new Promise<AgentResponse>((resolve, reject) =>
|
|
1978
|
+
this.queueUrgent({ prompt, options, isStream: false, resolve, reject })
|
|
1979
|
+
);
|
|
1797
1980
|
}
|
|
1798
1981
|
const enforced = this.midSessionConfig.mode;
|
|
1799
1982
|
const requested = options?.enqueue;
|
|
1800
1983
|
if (requested && (enforced === "steer" || enforced === "queue") && requested !== enforced) {
|
|
1801
|
-
throw new
|
|
1984
|
+
throw new ConfigError(
|
|
1802
1985
|
`[Agent Accelerator] enqueue "${requested}" is disabled by midSession.mode "${enforced}". Use ${enforced}() or set mode to "auto"/"${requested}".`
|
|
1803
1986
|
);
|
|
1804
1987
|
}
|
|
@@ -1808,14 +1991,14 @@ export class Agent {
|
|
|
1808
1991
|
return this.steerInto(this.currentRun, prompt, options);
|
|
1809
1992
|
} else {
|
|
1810
1993
|
if (this.midSessionConfig.mode === "steer") {
|
|
1811
|
-
throw new
|
|
1994
|
+
throw new ConfigError(
|
|
1812
1995
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1813
1996
|
);
|
|
1814
1997
|
}
|
|
1815
1998
|
this.assertPromptValid(prompt);
|
|
1816
1999
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1817
2000
|
if (this.pendingQueue.length >= max) {
|
|
1818
|
-
throw new
|
|
2001
|
+
throw new ValidationError(
|
|
1819
2002
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1820
2003
|
);
|
|
1821
2004
|
}
|
|
@@ -1844,13 +2027,9 @@ export class Agent {
|
|
|
1844
2027
|
prompt: string | ContentPart[],
|
|
1845
2028
|
optionsOrStream?: boolean | AgentRunOptions
|
|
1846
2029
|
): Promise<AgentResponse> & AsyncIterable<string | StreamEvent> {
|
|
1847
|
-
const isStream =
|
|
1848
|
-
typeof optionsOrStream === "boolean"
|
|
1849
|
-
? optionsOrStream
|
|
1850
|
-
: optionsOrStream?.stream === true;
|
|
2030
|
+
const isStream = typeof optionsOrStream === "boolean" ? optionsOrStream : optionsOrStream?.stream === true;
|
|
1851
2031
|
|
|
1852
|
-
const runOpts: AgentRunOptions =
|
|
1853
|
-
typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
2032
|
+
const runOpts: AgentRunOptions = typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
1854
2033
|
|
|
1855
2034
|
if (isStream) {
|
|
1856
2035
|
const stream = this.stream(prompt, runOpts);
|
|
@@ -1858,7 +2037,8 @@ export class Agent {
|
|
|
1858
2037
|
if (runOpts.onDelta || runOpts.onThinkingDelta || runOpts.onEvent) {
|
|
1859
2038
|
if (runOpts.onEvent) stream.on("*", runOpts.onEvent as any);
|
|
1860
2039
|
if (runOpts.onDelta) stream.on("text_delta", (e: any) => runOpts.onDelta!(e.delta!, e));
|
|
1861
|
-
if (runOpts.onThinkingDelta)
|
|
2040
|
+
if (runOpts.onThinkingDelta)
|
|
2041
|
+
stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
|
|
1862
2042
|
}
|
|
1863
2043
|
const stringStream: any = {
|
|
1864
2044
|
[Symbol.asyncIterator]: async function* () {
|
|
@@ -1899,10 +2079,7 @@ export class Agent {
|
|
|
1899
2079
|
* }
|
|
1900
2080
|
* ```
|
|
1901
2081
|
*/
|
|
1902
|
-
stream(
|
|
1903
|
-
prompt: string | ContentPart[],
|
|
1904
|
-
options?: AgentRunOptions
|
|
1905
|
-
): AssistantMessageEventStream {
|
|
2082
|
+
stream(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1906
2083
|
// Same sync validation timing as run().
|
|
1907
2084
|
this.assertPromptValid(prompt);
|
|
1908
2085
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn).
|
|
@@ -1910,8 +2087,12 @@ export class Agent {
|
|
|
1910
2087
|
const enforcedStream = this.midSessionConfig.mode;
|
|
1911
2088
|
const urgent = options?.enqueue === "interrupt"; // stops the active run and goes first, in every mode and whatever the queue holds
|
|
1912
2089
|
const requestedStream = options?.enqueue === "interrupt" ? undefined : options?.enqueue;
|
|
1913
|
-
if (
|
|
1914
|
-
|
|
2090
|
+
if (
|
|
2091
|
+
requestedStream &&
|
|
2092
|
+
(enforcedStream === "steer" || enforcedStream === "queue") &&
|
|
2093
|
+
requestedStream !== enforcedStream
|
|
2094
|
+
) {
|
|
2095
|
+
throw new ConfigError(
|
|
1915
2096
|
`[Agent Accelerator] enqueue "${requestedStream}" is disabled by midSession.mode "${enforcedStream}". Use ${enforcedStream}() or set mode to "auto"/"${requestedStream}".`
|
|
1916
2097
|
);
|
|
1917
2098
|
}
|
|
@@ -1935,28 +2116,40 @@ export class Agent {
|
|
|
1935
2116
|
activePromise.then(
|
|
1936
2117
|
(res) => {
|
|
1937
2118
|
try {
|
|
1938
|
-
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);
|
|
1939
2129
|
} catch {}
|
|
1940
|
-
try { stub.end(res); } catch {}
|
|
1941
2130
|
},
|
|
1942
2131
|
(err) => {
|
|
1943
|
-
try {
|
|
2132
|
+
try {
|
|
2133
|
+
stub.fail(err instanceof Error ? err : new Error(String(err)));
|
|
2134
|
+
} catch {}
|
|
1944
2135
|
}
|
|
1945
2136
|
);
|
|
1946
2137
|
} else {
|
|
1947
|
-
try {
|
|
2138
|
+
try {
|
|
2139
|
+
stub.fail(new ValidationError("[Agent Accelerator] No active run to steer."));
|
|
2140
|
+
} catch {}
|
|
1948
2141
|
}
|
|
1949
2142
|
return stub;
|
|
1950
2143
|
}
|
|
1951
2144
|
if (!urgent && this.midSessionConfig.mode === "steer") {
|
|
1952
|
-
throw new
|
|
2145
|
+
throw new ConfigError(
|
|
1953
2146
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1954
2147
|
);
|
|
1955
2148
|
}
|
|
1956
2149
|
this.assertPromptValid(prompt);
|
|
1957
2150
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1958
2151
|
if (!urgent && this.pendingQueue.length >= max) {
|
|
1959
|
-
throw new
|
|
2152
|
+
throw new ValidationError(
|
|
1960
2153
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1961
2154
|
);
|
|
1962
2155
|
}
|
|
@@ -2003,7 +2196,9 @@ export class Agent {
|
|
|
2003
2196
|
if (idx >= 0) {
|
|
2004
2197
|
this.pendingQueue.splice(idx, 1);
|
|
2005
2198
|
queuedEntry.reject(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2006
|
-
try {
|
|
2199
|
+
try {
|
|
2200
|
+
deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2201
|
+
} catch {}
|
|
2007
2202
|
}
|
|
2008
2203
|
} catch {}
|
|
2009
2204
|
});
|
|
@@ -2021,7 +2216,13 @@ export class Agent {
|
|
|
2021
2216
|
// Otherwise (edge), end it here.
|
|
2022
2217
|
try {
|
|
2023
2218
|
if ((deferred as any).__piped !== true) {
|
|
2024
|
-
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);
|
|
2025
2226
|
deferred.end(res);
|
|
2026
2227
|
}
|
|
2027
2228
|
} catch {}
|
|
@@ -2043,10 +2244,7 @@ export class Agent {
|
|
|
2043
2244
|
}
|
|
2044
2245
|
|
|
2045
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.
|
|
2046
|
-
private wireStreamCallbacks(
|
|
2047
|
-
s: AssistantMessageEventStream,
|
|
2048
|
-
options?: AgentRunOptions
|
|
2049
|
-
): void {
|
|
2247
|
+
private wireStreamCallbacks(s: AssistantMessageEventStream, options?: AgentRunOptions): void {
|
|
2050
2248
|
// Wire one-liner callbacks so `stream:true` + onDelta is enough — no manual for-await needed
|
|
2051
2249
|
if (options?.wrapThinking) {
|
|
2052
2250
|
// Auto-wrap reasoning as <think>…</think> per-turn — clean boundaries across multi-turn agent runs.
|
|
@@ -2123,7 +2321,9 @@ export class Agent {
|
|
|
2123
2321
|
const raw: string = e.delta ?? "";
|
|
2124
2322
|
if (!justClosedThink) return raw;
|
|
2125
2323
|
const stripped = raw.replace(/^\n+/, "");
|
|
2126
|
-
try {
|
|
2324
|
+
try {
|
|
2325
|
+
e.delta = stripped;
|
|
2326
|
+
} catch {}
|
|
2127
2327
|
if (!stripped) return "";
|
|
2128
2328
|
justClosedThink = false;
|
|
2129
2329
|
return stripped;
|
|
@@ -2220,11 +2420,13 @@ export class Agent {
|
|
|
2220
2420
|
closeSynthetic();
|
|
2221
2421
|
if (justClosedThink) {
|
|
2222
2422
|
const stripped: string = (e.delta ?? "").replace(/^\n+/, "");
|
|
2223
|
-
try {
|
|
2423
|
+
try {
|
|
2424
|
+
e.delta = stripped;
|
|
2425
|
+
} catch {}
|
|
2224
2426
|
if (!stripped) return;
|
|
2225
2427
|
justClosedThink = false;
|
|
2226
2428
|
}
|
|
2227
|
-
needThinkNewline = !(
|
|
2429
|
+
needThinkNewline = !(e.delta ?? "").endsWith("\n");
|
|
2228
2430
|
});
|
|
2229
2431
|
s.on("tool_call_start" as any, () => {
|
|
2230
2432
|
closeSynthetic();
|
|
@@ -2254,7 +2456,7 @@ export class Agent {
|
|
|
2254
2456
|
/** What a failed run attempt got done before it failed (the `partial` the loop attaches to its error). */
|
|
2255
2457
|
type FailedAttempt = Partial<AgentResponse> & { usage: TokenUsage };
|
|
2256
2458
|
|
|
2257
|
-
// Adds what failed attempts already did (tool calls, results, turns, token usage) to the response of the attempt that finally worked, so a retry never hides side effects or cost; in: the final response + each failed attempt's `err.partial`, out: the run's AgentResponse.
|
|
2459
|
+
// Adds what failed attempts already did (tool calls, results, workers, turns, token usage) to the response of the attempt that finally worked, so a retry never hides side effects or cost; in: the final response + each failed attempt's `err.partial`, out: the run's AgentResponse.
|
|
2258
2460
|
function withFailedAttempts(res: AgentResponse, failed: FailedAttempt[]): AgentResponse {
|
|
2259
2461
|
const usage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
2260
2462
|
for (const f of failed) accumulateUsage(usage, f.usage);
|
|
@@ -2266,7 +2468,7 @@ function withFailedAttempts(res: AgentResponse, failed: FailedAttempt[]): AgentR
|
|
|
2266
2468
|
thoughtSignature: res.thoughtSignature,
|
|
2267
2469
|
toolCalls: [...failed.flatMap((f) => f.toolCalls ?? []), ...res.toolCalls],
|
|
2268
2470
|
toolResults: [...failed.flatMap((f) => f.toolResults ?? []), ...res.toolResults],
|
|
2269
|
-
subagents: res.subagents,
|
|
2471
|
+
subagents: [...failed.flatMap((f) => f.subagents ?? []), ...res.subagents],
|
|
2270
2472
|
usage,
|
|
2271
2473
|
responseId: res.responseId,
|
|
2272
2474
|
model: res.model,
|
|
@@ -2302,7 +2504,12 @@ export function normalizeThinking(config: AgentConfig, fallbackLevel?: string):
|
|
|
2302
2504
|
try {
|
|
2303
2505
|
const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
|
|
2304
2506
|
const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
|
|
2305
|
-
const provStr =
|
|
2507
|
+
const provStr =
|
|
2508
|
+
typeof rawModel === "object" && rawModel?.provider
|
|
2509
|
+
? rawModel.provider
|
|
2510
|
+
: modelStr.includes("/")
|
|
2511
|
+
? modelStr.split("/")[0]
|
|
2512
|
+
: "";
|
|
2306
2513
|
const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
|
|
2307
2514
|
if (actualModelId) {
|
|
2308
2515
|
validateModelThinking(provStr || "openrouter", actualModelId, level);
|