agent-accelerator 0.5.9 → 0.6.5
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 +64 -16
- 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 +447 -172
- package/src/agent/architecture.md +16 -12
- package/src/agent/config-blocks.ts +39 -12
- package/src/agent/context.ts +9 -3
- package/src/agent/defaults.ts +88 -0
- package/src/agent/delegation.ts +86 -28
- package/src/agent/loop.ts +474 -374
- package/src/agent/subagent.ts +1 -3
- package/src/agent/subagents-tool.ts +50 -19
- package/src/agent/turns.ts +3 -1
- package/src/agent/usage-limit.ts +92 -0
- package/src/agent/worker.ts +73 -30
- package/src/agent/workers.ts +126 -21
- package/src/architecture.md +17 -15
- package/src/index.ts +43 -19
- package/src/models/architecture.md +4 -3
- package/src/models/catalog-cache.ts +6 -5
- package/src/models/catalog.ts +39 -30
- package/src/prompts/additional-context.md +2 -0
- package/src/prompts/architecture.md +19 -0
- package/src/prompts/prompts.ts +81 -0
- package/src/prompts/reasoning-directive.md +5 -0
- package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +4 -0
- package/src/prompts/structured-output.md +2 -0
- package/src/{agent/subagents.md → prompts/subagents-tool.md} +3 -0
- package/src/prompts/tool-bridge.md +1 -0
- package/src/prompts/tool-interrupted.md +1 -0
- package/src/prompts/tool-not-found.md +1 -0
- package/src/prompts/tool-repeat-blocked.md +1 -0
- package/src/prompts/tool-schema-error.md +1 -0
- package/src/prompts/tool-truncated-arguments.md +1 -0
- package/src/prompts/worker-failed.md +1 -0
- package/src/prompts/worker-replied.md +2 -0
- package/src/providers/architecture.md +10 -8
- package/src/providers/google.ts +228 -69
- package/src/providers/openai-compat.ts +74 -56
- package/src/providers/openai.ts +64 -41
- package/src/providers/openrouter.ts +270 -48
- package/src/providers/registry.ts +6 -21
- package/src/providers/shared.ts +87 -33
- package/src/providers.ts +115 -124
- package/src/scheduler/architecture.md +8 -7
- package/src/scheduler/scheduler.ts +226 -60
- package/src/scheduler/store.ts +23 -10
- package/src/scheduler/time.ts +17 -3
- package/src/scheduler/tool.ts +38 -16
- package/src/session/architecture.md +7 -6
- package/src/session/journal.ts +39 -12
- package/src/session/store.ts +126 -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 +9 -7
- package/src/tools/executor.ts +109 -62
- package/src/tools/schema.ts +10 -6
- package/src/tools/subagent-result.ts +6 -1
- package/src/tools/tool-content.ts +32 -0
- package/src/tools/tool.ts +9 -11
- package/src/types/agent.ts +37 -22
- package/src/types/architecture.md +11 -10
- package/src/types/core.ts +5 -9
- package/src/types/errors.ts +26 -4
- package/src/types/message.ts +7 -0
- package/src/types/model.ts +4 -5
- package/src/types/scheduler.ts +4 -4
- package/src/types/tool.ts +14 -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 +9 -17
- 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";
|
|
@@ -12,22 +28,33 @@ import { AgentContext } from "./context.ts";
|
|
|
12
28
|
import { resolveModel } from "../providers/registry.ts";
|
|
13
29
|
import { buildAgentTools } from "./delegation.ts";
|
|
14
30
|
import { createSubagentsTool } from "./subagents-tool.ts";
|
|
15
|
-
import { WorkerRegistry } from "./workers.ts";
|
|
31
|
+
import { WorkerRegistry, savedSpend } from "./workers.ts";
|
|
16
32
|
import {
|
|
17
33
|
DEFAULT_MAX_DEPTH,
|
|
34
|
+
isWorkerConfig,
|
|
18
35
|
normalizeToolMap,
|
|
19
36
|
resolveDynamicModelInput,
|
|
20
37
|
resolveMainModel,
|
|
21
38
|
} from "./worker.ts";
|
|
22
39
|
import * as path from "node:path";
|
|
23
|
-
import { registerAgent } from "../scheduler/scheduler.ts";
|
|
24
|
-
import { resolveHome } from "../
|
|
40
|
+
import { registerAgent, ScheduledActions } from "../scheduler/scheduler.ts";
|
|
41
|
+
import { resolveHome } from "../utils/home.ts";
|
|
25
42
|
import { persistTelemetry, registerSessionFolder, unregisterSessionFolder } from "../telemetry-file.ts";
|
|
26
43
|
import { lockFolder } from "../utils/durable.ts";
|
|
27
44
|
import { createScheduledActionsTool, getDatetimeTool } from "../scheduler/tool.ts";
|
|
45
|
+
import { fillPrompt, resolvePrompts, type Prompts } from "../prompts/prompts.ts";
|
|
46
|
+
import { UsageLimit, checkUsageLimitConfig } from "./usage-limit.ts";
|
|
47
|
+
import { mergeCap } from "./defaults.ts";
|
|
28
48
|
import { applyEditable, assertModelAllowed, modelKey, toEditableConfig } from "./config-blocks.ts";
|
|
29
49
|
import { assertNoToolCollisions } from "../tools/executor.ts";
|
|
30
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
runAgentLoop,
|
|
52
|
+
streamAgentLoop,
|
|
53
|
+
unfinishedToolCalls,
|
|
54
|
+
endsWithStop,
|
|
55
|
+
accumulateUsage,
|
|
56
|
+
type SteerEntry,
|
|
57
|
+
} from "./loop.ts";
|
|
31
58
|
import { isRetryableRun, readRetryAfterMs, sleep } from "../utils/retry.ts";
|
|
32
59
|
import { createSessionId, nowMs, newRunId, previewPrompt } from "../utils/session.ts";
|
|
33
60
|
import {
|
|
@@ -38,25 +65,43 @@ import {
|
|
|
38
65
|
listSubAgentTraceIdsForSessions,
|
|
39
66
|
type SubAgentTrace,
|
|
40
67
|
} from "./delegation.ts";
|
|
41
|
-
import {
|
|
42
|
-
|
|
68
|
+
import {
|
|
69
|
+
addTurn,
|
|
70
|
+
buildUsageReport,
|
|
71
|
+
emptyUsage,
|
|
72
|
+
mergeUsage,
|
|
73
|
+
modelName,
|
|
74
|
+
readUsage,
|
|
75
|
+
type UsageNode,
|
|
76
|
+
type UsageReport,
|
|
77
|
+
type UsageTotals,
|
|
78
|
+
} from "../session/usage.ts";
|
|
79
|
+
import {
|
|
80
|
+
saveSessionFile,
|
|
81
|
+
sessionDirFor,
|
|
82
|
+
loadSessionDir,
|
|
83
|
+
findInvalidMessage,
|
|
84
|
+
priceUsage,
|
|
85
|
+
telemetryBus,
|
|
86
|
+
type SessionRunMark,
|
|
87
|
+
type SessionRunEnd,
|
|
88
|
+
type SessionPending,
|
|
89
|
+
type SessionToolRecord,
|
|
90
|
+
} from "../session/store.ts";
|
|
43
91
|
import { SessionJournal } from "../session/journal.ts";
|
|
44
92
|
import { ToolLogs, type ToolLogWriter } from "../session/tool-log.ts";
|
|
45
93
|
import { isLocalPathInput } from "../utils/safe-io.ts";
|
|
46
|
-
import { clearSessionRouting, normalizeStructuredOutput
|
|
94
|
+
import { clearSessionRouting, normalizeStructuredOutput } from "../providers.ts";
|
|
47
95
|
import type { StructuredOutputSpec } from "../types/model.ts";
|
|
48
96
|
import { clearInteractionChains } from "../providers/google.ts";
|
|
49
97
|
import { validateModelThinking, ensureModelCatalogFresh } from "../models/catalog.ts";
|
|
50
|
-
import { buildSessionData, type PersistedAgentSession, type SessionTotals
|
|
98
|
+
import { buildSessionData, type PersistedAgentSession, type SessionTotals } from "../session/store.ts";
|
|
51
99
|
import { SessionTelemetry } from "../session/store.ts";
|
|
52
100
|
|
|
53
101
|
/**
|
|
54
102
|
* Resolves a per-run thinking override without mutating agent config.
|
|
55
103
|
*/
|
|
56
|
-
export function resolveEffectiveThinking(
|
|
57
|
-
base?: ThinkingConfig,
|
|
58
|
-
overrideLevel?: string
|
|
59
|
-
): ThinkingConfig | undefined {
|
|
104
|
+
export function resolveEffectiveThinking(base?: ThinkingConfig, overrideLevel?: string): ThinkingConfig | undefined {
|
|
60
105
|
if (!overrideLevel) return base;
|
|
61
106
|
if (overrideLevel === "none") return { enabled: false, level: "none", budgetTokens: 0 };
|
|
62
107
|
if (overrideLevel === "dynamic") return { enabled: true, level: "dynamic", budgetTokens: -1 };
|
|
@@ -94,8 +139,7 @@ export interface AgentRunLimits {
|
|
|
94
139
|
/** Resolves `(config as any).limits` with defaults (timeouts tolerate reasoning). */
|
|
95
140
|
function resolveAgentLimits(config: { limits?: Record<string, unknown> }): AgentRunLimits {
|
|
96
141
|
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;
|
|
142
|
+
const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
|
|
99
143
|
// A network timeout: `0` or `Infinity` turns it off, any other number is milliseconds, anything else is unset (the default applies).
|
|
100
144
|
const timeout = (v: unknown): number | undefined => (typeof v === "number" && !Number.isNaN(v) ? v : undefined);
|
|
101
145
|
return {
|
|
@@ -104,7 +148,9 @@ function resolveAgentLimits(config: { limits?: Record<string, unknown> }): Agent
|
|
|
104
148
|
...(num(raw.maxToolCallsPerRun) !== undefined
|
|
105
149
|
? { maxToolCallsPerRun: Math.max(1, Math.floor(num(raw.maxToolCallsPerRun)!)) }
|
|
106
150
|
: {}),
|
|
107
|
-
...(num(raw.maxTotalTokens) !== undefined
|
|
151
|
+
...(num(raw.maxTotalTokens) !== undefined
|
|
152
|
+
? { maxTotalTokens: Math.max(1, Math.floor(num(raw.maxTotalTokens)!)) }
|
|
153
|
+
: {}),
|
|
108
154
|
requestTimeoutMs: timeout(raw.requestTimeoutMs) ?? 300_000,
|
|
109
155
|
firstByteTimeoutMs: timeout(raw.firstByteTimeoutMs) ?? 120_000,
|
|
110
156
|
streamIdleTimeoutMs: timeout(raw.streamIdleTimeoutMs) ?? 300_000,
|
|
@@ -160,6 +206,8 @@ export class Agent {
|
|
|
160
206
|
readonly description: string;
|
|
161
207
|
/** Stable system instructions. */
|
|
162
208
|
instructions: string;
|
|
209
|
+
/** The SDK prompts in use: the shipped defaults with `AgentConfig.prompts` applied. */
|
|
210
|
+
readonly prompts: Prompts;
|
|
163
211
|
/** Original model string or catalog spec used for resolution. */
|
|
164
212
|
readonly modelStringOrSpec: string | any;
|
|
165
213
|
/** Configured dynamic sub-agent model, if enabled. */
|
|
@@ -180,6 +228,8 @@ export class Agent {
|
|
|
180
228
|
maxDepth: number;
|
|
181
229
|
/** Per-run spending cap of each worker (unset = unlimited). */
|
|
182
230
|
workerBudget?: { maxCostUsd?: number; maxTotalTokens?: number };
|
|
231
|
+
/** Per-run spending cap of each scheduled-action run (unset = unlimited). */
|
|
232
|
+
scheduledActionBudget?: { maxCostUsd?: number; maxTotalTokens?: number };
|
|
183
233
|
};
|
|
184
234
|
/** Home folder for everything written to disk (`sessions/`, `scheduled/`, `telemetry/`). */
|
|
185
235
|
readonly home: string;
|
|
@@ -216,8 +266,6 @@ export class Agent {
|
|
|
216
266
|
private readonly callDefaults: {
|
|
217
267
|
toolChoice?: ToolChoiceOption;
|
|
218
268
|
maxOutputTokens?: number;
|
|
219
|
-
stop?: string | string[];
|
|
220
|
-
seed?: number;
|
|
221
269
|
retry?: RetryPolicyConfig;
|
|
222
270
|
env?: Record<string, string>;
|
|
223
271
|
fetch?: typeof fetch;
|
|
@@ -289,6 +337,11 @@ export class Agent {
|
|
|
289
337
|
/** The resume in flight, so a second `resume()` returns the same promise. */
|
|
290
338
|
private resuming?: Promise<AgentResponse | undefined>;
|
|
291
339
|
private workerRegistry?: WorkerRegistry;
|
|
340
|
+
/** The chat-wide spending cap this agent counts into and checks against: its own (`usageLimit`) or the root's, handed down to workers. @internal */
|
|
341
|
+
chat?: UsageLimit;
|
|
342
|
+
/** True for the agent that created `chat` from its own `usageLimit` (the root of the chat). */
|
|
343
|
+
private ownsChat = false;
|
|
344
|
+
private scheduledActions?: ScheduledActions;
|
|
292
345
|
|
|
293
346
|
/**
|
|
294
347
|
* Creates an agent and registers its model, tools, cache, and delegation settings.
|
|
@@ -314,6 +367,18 @@ export class Agent {
|
|
|
314
367
|
this.description = config.description || "AI Agent powered by Agent Accelerator";
|
|
315
368
|
// DX1: only instructions (no systemPrompt alias)
|
|
316
369
|
this.instructions = config.instructions || "";
|
|
370
|
+
this.prompts = resolvePrompts(config.prompts);
|
|
371
|
+
// Workers and scheduled runs the SDK builds never take developer defaults (`configure()`, env vars).
|
|
372
|
+
const takesDefaults = !isWorkerConfig(config);
|
|
373
|
+
if (config.usageLimit !== undefined) {
|
|
374
|
+
const problem = checkUsageLimitConfig(config.usageLimit);
|
|
375
|
+
if (problem) throw new ConfigError(`[Agent Accelerator] Invalid usageLimit: ${problem}.`);
|
|
376
|
+
}
|
|
377
|
+
const chatCap = mergeCap("usageLimit", config.usageLimit, takesDefaults);
|
|
378
|
+
if (chatCap) {
|
|
379
|
+
this.chat = new UsageLimit(chatCap);
|
|
380
|
+
this.ownsChat = true;
|
|
381
|
+
}
|
|
317
382
|
// Unified model resolution (shared with SubAgent/workers): supports
|
|
318
383
|
// ModelProviderInstance {model,apiKey,...} and ModelSpec {id,provider}.
|
|
319
384
|
const { modelStringOrSpec, rawModel } = resolveMainModel(config.model);
|
|
@@ -325,34 +390,60 @@ export class Agent {
|
|
|
325
390
|
this.subagentModel = dynModel as string | any | undefined;
|
|
326
391
|
const dynEnabled = dynRaw ? (dynRaw.enabled ?? true) : false;
|
|
327
392
|
if (dynRaw) {
|
|
328
|
-
const dynTools = normalizeToolMap(
|
|
393
|
+
const dynTools = normalizeToolMap(
|
|
394
|
+
(dynRaw as { tools?: Record<string, ToolDefinition> | ToolDefinition[] }).tools
|
|
395
|
+
);
|
|
329
396
|
const rawDepth = (dynRaw as { maxDepth?: unknown }).maxDepth;
|
|
330
397
|
// Depth is unlimited unless the developer sets a cap (a whole number >= 1; Infinity also means unlimited).
|
|
331
|
-
if (
|
|
332
|
-
|
|
398
|
+
if (
|
|
399
|
+
rawDepth !== undefined &&
|
|
400
|
+
rawDepth !== Infinity &&
|
|
401
|
+
!(Number.isInteger(rawDepth) && (rawDepth as number) >= 1)
|
|
402
|
+
) {
|
|
403
|
+
throw new ConfigError(
|
|
404
|
+
`[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.`
|
|
405
|
+
);
|
|
333
406
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
407
|
+
type RawBudget = { maxCostUsd?: unknown; maxTotalTokens?: unknown } | undefined;
|
|
408
|
+
const checkBudgetKeys = (name: "workerBudget" | "scheduledActionBudget"): RawBudget => {
|
|
409
|
+
const raw = (dynRaw as Record<string, RawBudget>)[name];
|
|
410
|
+
for (const key of ["maxCostUsd", "maxTotalTokens"] as const) {
|
|
411
|
+
const v = raw?.[key];
|
|
412
|
+
if (v !== undefined && !(typeof v === "number" && Number.isFinite(v) && v > 0)) {
|
|
413
|
+
throw new ConfigError(
|
|
414
|
+
`[Agent Accelerator] Invalid dynamicSubagents.${name}.${key} ${JSON.stringify(v)}: expected a number greater than 0, or omit it for no limit.`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
339
417
|
}
|
|
340
|
-
|
|
418
|
+
return raw;
|
|
419
|
+
};
|
|
420
|
+
const rawBudget = checkBudgetKeys("workerBudget");
|
|
421
|
+
const rawScheduledBudget = mergeCap(
|
|
422
|
+
"scheduledActionBudget",
|
|
423
|
+
checkBudgetKeys("scheduledActionBudget") as { maxCostUsd?: number; maxTotalTokens?: number } | undefined,
|
|
424
|
+
takesDefaults
|
|
425
|
+
);
|
|
341
426
|
// Open workers are unlimited unless the developer sets a cap (a number >= 1; Infinity also means unlimited).
|
|
342
427
|
const rawMax = (dynRaw as { maxSpawn?: unknown }).maxSpawn;
|
|
343
428
|
if (rawMax !== undefined && (typeof rawMax !== "number" || Number.isNaN(rawMax))) {
|
|
344
|
-
throw new ConfigError(
|
|
429
|
+
throw new ConfigError(
|
|
430
|
+
`[Agent Accelerator] Invalid dynamicSubagents.maxSpawn ${JSON.stringify(rawMax)}: expected a whole number >= 1, or omit it for no limit.`
|
|
431
|
+
);
|
|
345
432
|
}
|
|
346
433
|
const maxSpawn = rawMax === undefined || rawMax === Infinity ? Infinity : Math.max(1, Math.floor(rawMax));
|
|
347
434
|
// Only scheduled actions are timed: no limit unless the developer sets one (0 and Infinity mean none).
|
|
348
435
|
for (const removed of ["timeout", "modelMaySetTimeout"]) {
|
|
349
436
|
if ((dynRaw as Record<string, unknown>)[removed] !== undefined) {
|
|
350
|
-
throw new ConfigError(
|
|
437
|
+
throw new ConfigError(
|
|
438
|
+
`[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.`
|
|
439
|
+
);
|
|
351
440
|
}
|
|
352
441
|
}
|
|
353
442
|
const rawTimeout = (dynRaw as { workerTimeoutMs?: unknown }).workerTimeoutMs;
|
|
354
443
|
if (rawTimeout !== undefined && (typeof rawTimeout !== "number" || Number.isNaN(rawTimeout) || rawTimeout < 0)) {
|
|
355
|
-
throw new ConfigError(
|
|
444
|
+
throw new ConfigError(
|
|
445
|
+
`[Agent Accelerator] Invalid dynamicSubagents.workerTimeoutMs ${JSON.stringify(rawTimeout)}: expected a number of milliseconds > 0, or omit it (0 and Infinity mean no limit).`
|
|
446
|
+
);
|
|
356
447
|
}
|
|
357
448
|
const workerTimeoutMs = !rawTimeout ? Infinity : Math.max(1, Math.floor(rawTimeout));
|
|
358
449
|
this.dynamicSubagents = {
|
|
@@ -365,6 +456,9 @@ export class Agent {
|
|
|
365
456
|
allowScheduledActions: dynRaw.allowScheduledActions === true,
|
|
366
457
|
maxDepth: (rawDepth as number | undefined) ?? DEFAULT_MAX_DEPTH,
|
|
367
458
|
...(rawBudget ? { workerBudget: { ...(rawBudget as { maxCostUsd?: number; maxTotalTokens?: number }) } } : {}),
|
|
459
|
+
...(rawScheduledBudget
|
|
460
|
+
? { scheduledActionBudget: { ...(rawScheduledBudget as { maxCostUsd?: number; maxTotalTokens?: number }) } }
|
|
461
|
+
: {}),
|
|
368
462
|
};
|
|
369
463
|
}
|
|
370
464
|
this.apiKey = (rawModel as any)?.apiKey || config.apiKey;
|
|
@@ -419,17 +513,20 @@ export class Agent {
|
|
|
419
513
|
}
|
|
420
514
|
this.cache = config.cache;
|
|
421
515
|
if (config.resume !== undefined && config.resume !== "automatic" && config.resume !== "explicit") {
|
|
422
|
-
throw new ConfigError(
|
|
516
|
+
throw new ConfigError(
|
|
517
|
+
`[Agent Accelerator] Invalid resume ${JSON.stringify(config.resume)}: expected "automatic" (default) or "explicit".`
|
|
518
|
+
);
|
|
423
519
|
}
|
|
424
520
|
this.resumeMode = config.resume ?? "automatic";
|
|
425
521
|
if (config.runRetries !== undefined && !(Number.isInteger(config.runRetries) && config.runRetries >= 0)) {
|
|
426
|
-
throw new ConfigError(
|
|
522
|
+
throw new ConfigError(
|
|
523
|
+
`[Agent Accelerator] Invalid runRetries ${JSON.stringify(config.runRetries)}: expected a whole number >= 0 (default 3, 0 turns it off).`
|
|
524
|
+
);
|
|
427
525
|
}
|
|
428
526
|
this.runRetries = config.runRetries ?? 3;
|
|
429
527
|
|
|
430
528
|
this.serviceTier = config.serviceTier;
|
|
431
|
-
this.onFailure =
|
|
432
|
-
config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
|
|
529
|
+
this.onFailure = config.onFailure === "rollback" || config.onFailure === "keep" ? config.onFailure : "auto";
|
|
433
530
|
this.forkConfig = { ...config };
|
|
434
531
|
this.captureRaw = config.captureRaw;
|
|
435
532
|
this.budget = config.budget ? { ...config.budget } : undefined;
|
|
@@ -448,8 +545,6 @@ export class Agent {
|
|
|
448
545
|
this.callDefaults = {
|
|
449
546
|
toolChoice: config.toolChoice,
|
|
450
547
|
maxOutputTokens: config.maxOutputTokens,
|
|
451
|
-
stop: config.stop,
|
|
452
|
-
seed: config.seed,
|
|
453
548
|
retry: config.retry,
|
|
454
549
|
env: config.env,
|
|
455
550
|
fetch: config.fetch,
|
|
@@ -466,9 +561,8 @@ export class Agent {
|
|
|
466
561
|
Object.assign(this.tools, normalizeToolMap(config.tools));
|
|
467
562
|
}
|
|
468
563
|
|
|
469
|
-
|
|
470
564
|
if (Array.isArray(config.subagents)) {
|
|
471
|
-
const subagentTools = buildAgentTools(config.subagents);
|
|
565
|
+
const subagentTools = buildAgentTools(config.subagents, { chat: this.chat });
|
|
472
566
|
for (const [toolName, toolDef] of Object.entries(subagentTools)) {
|
|
473
567
|
this.tools[toolName] = toolDef;
|
|
474
568
|
}
|
|
@@ -508,6 +602,11 @@ export class Agent {
|
|
|
508
602
|
return (this.workerRegistry ??= new WorkerRegistry(this));
|
|
509
603
|
}
|
|
510
604
|
|
|
605
|
+
/** The scheduled actions of this agent's home: `create`, `list`, `update`, `run`, `skip`, `delete` (see `ScheduledActions`). */
|
|
606
|
+
get scheduled(): ScheduledActions {
|
|
607
|
+
return (this.scheduledActions ??= new ScheduledActions(this));
|
|
608
|
+
}
|
|
609
|
+
|
|
511
610
|
// Takes the one-process lock of the folder this Agent persists to: its own session folder, or the folder of a single `persist.file`; throws ConfigError when another live process holds it; in: the constructor and importSession (a new session id is a new folder), out: a `.lock` file there.
|
|
512
611
|
private lockSessionFolder(): void {
|
|
513
612
|
const cfg = this.persistConfig;
|
|
@@ -570,11 +669,14 @@ export class Agent {
|
|
|
570
669
|
// Puts saved spending totals back after a restart or import (zeros when the file has none); in: a loaded session's `usage`, out: this.usageTotals.
|
|
571
670
|
private restoreUsage(saved: unknown): void {
|
|
572
671
|
Object.assign(this.usageTotals, readUsage(saved));
|
|
672
|
+
if (this.ownsChat) this.chat!.restart(); // a restored session changes the starting point of the chat total
|
|
573
673
|
}
|
|
574
674
|
|
|
575
675
|
// 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
676
|
private recordTurnUsage(usage: TokenUsage, spec: Parameters<typeof priceUsage>[1]): void {
|
|
577
|
-
|
|
677
|
+
const priced = priceUsage(usage, spec, this.serviceTier ? { serviceTier: this.serviceTier } : undefined);
|
|
678
|
+
addTurn(this.usageTotals, usage, priced);
|
|
679
|
+
this.chat?.add((usage.inputTokens || 0) + (usage.outputTokens || 0), priced?.breakdown.totalMicroUSD ?? 0);
|
|
578
680
|
}
|
|
579
681
|
|
|
580
682
|
/** Adds another agent's spending to this one (a fixed worker's per-call copy into its template). @internal */
|
|
@@ -589,7 +691,10 @@ export class Agent {
|
|
|
589
691
|
* below it. Spending of one-shot scheduled-action workers is in their run files, not here.
|
|
590
692
|
*/
|
|
591
693
|
usageReport(): UsageReport {
|
|
592
|
-
return buildUsageReport(
|
|
694
|
+
return buildUsageReport(
|
|
695
|
+
{ id: this.sessionId, name: this.name, model: modelName(this.modelStringOrSpec), totals: this.usageTotals },
|
|
696
|
+
[...this.workers.usageNodes(), ...this.fixedWorkerNodes()]
|
|
697
|
+
);
|
|
593
698
|
}
|
|
594
699
|
|
|
595
700
|
// The `subagents: [...]` workers as usage nodes (their per-call copies' spending is merged into them); in: the constructor config, out: usageReport.
|
|
@@ -598,14 +703,25 @@ export class Agent {
|
|
|
598
703
|
for (const entry of this.forkConfig.subagents ?? []) {
|
|
599
704
|
const worker = "agent" in entry ? entry.agent : entry;
|
|
600
705
|
const name = "agent" in entry ? entry.name : worker.name;
|
|
601
|
-
nodes.push({
|
|
706
|
+
nodes.push({
|
|
707
|
+
id: name,
|
|
708
|
+
name,
|
|
709
|
+
model: modelName(worker.modelStringOrSpec),
|
|
710
|
+
status: "fixed",
|
|
711
|
+
totals: worker.usageTotals,
|
|
712
|
+
workers: [],
|
|
713
|
+
});
|
|
602
714
|
}
|
|
603
715
|
return nodes;
|
|
604
716
|
}
|
|
605
717
|
|
|
606
718
|
// Says on the telemetry bus that resuming (or a prompt that was waiting) failed; in: a rejected resume, out: telemetryBus.
|
|
607
719
|
private reportResumeFailure(err: unknown): void {
|
|
608
|
-
telemetryBus.emitWarning(
|
|
720
|
+
telemetryBus.emitWarning(
|
|
721
|
+
"agent.resume_failed",
|
|
722
|
+
`[Agent Accelerator] Resuming session ${this.sessionId} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
723
|
+
{ sessionId: this.sessionId }
|
|
724
|
+
);
|
|
609
725
|
}
|
|
610
726
|
|
|
611
727
|
/** 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 +745,8 @@ export class Agent {
|
|
|
629
745
|
*/
|
|
630
746
|
resume(): Promise<AgentResponse | undefined> {
|
|
631
747
|
if (this.resuming) return this.resuming;
|
|
632
|
-
if (this.currentRun)
|
|
748
|
+
if (this.currentRun)
|
|
749
|
+
throw new ValidationError("[Agent Accelerator] resume() needs an idle agent: a run is already active.");
|
|
633
750
|
const mark = this.runMarks.filter((m) => !m.end && m !== this.activeMark).pop();
|
|
634
751
|
const last = this.context.messages[this.context.messages.length - 1];
|
|
635
752
|
const nothingOpen = unfinishedToolCalls(this.context.messages).length === 0;
|
|
@@ -642,7 +759,11 @@ export class Agent {
|
|
|
642
759
|
telemetryBus.emit({
|
|
643
760
|
code: "agent.resumed",
|
|
644
761
|
message: `[Agent Accelerator] resuming run ${mark.id} of session ${this.sessionId}`,
|
|
645
|
-
data: {
|
|
762
|
+
data: {
|
|
763
|
+
sessionId: this.sessionId,
|
|
764
|
+
runId: mark.id,
|
|
765
|
+
pendingToolCalls: unfinishedToolCalls(this.context.messages).map((c) => c.name),
|
|
766
|
+
},
|
|
646
767
|
});
|
|
647
768
|
const run = this.startNonStreamingRun("", undefined, mark);
|
|
648
769
|
const clear = (): void => {
|
|
@@ -655,7 +776,19 @@ export class Agent {
|
|
|
655
776
|
|
|
656
777
|
// The finished tool calls of the current run as results the loop can reuse; in: toolRecords, out: loopConfig.resume.stored.
|
|
657
778
|
private storedToolResults(): Map<string, ToolResultRecord> {
|
|
658
|
-
return new Map(
|
|
779
|
+
return new Map(
|
|
780
|
+
this.toolRecords.map((t) => [
|
|
781
|
+
t.id,
|
|
782
|
+
{
|
|
783
|
+
id: t.id,
|
|
784
|
+
name: t.name,
|
|
785
|
+
result: t.result,
|
|
786
|
+
...(t.isError ? { isError: true } : {}),
|
|
787
|
+
...(t.stopped ? { stopped: true } : {}),
|
|
788
|
+
...(t.parts && t.parts.length > 0 ? { parts: t.parts } : {}),
|
|
789
|
+
} as ToolResultRecord,
|
|
790
|
+
])
|
|
791
|
+
);
|
|
659
792
|
}
|
|
660
793
|
|
|
661
794
|
// 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.
|
|
@@ -674,6 +807,7 @@ export class Agent {
|
|
|
674
807
|
result: isSubagentResult(result.result) ? result.result.xml : result.result,
|
|
675
808
|
...(result.isError ? { isError: true } : {}),
|
|
676
809
|
...(result.stopped ? { stopped: true } : {}),
|
|
810
|
+
...(result.parts && result.parts.length > 0 ? { parts: result.parts } : {}),
|
|
677
811
|
});
|
|
678
812
|
this.doPersist();
|
|
679
813
|
}
|
|
@@ -683,15 +817,9 @@ export class Agent {
|
|
|
683
817
|
const base = this.instructions || "";
|
|
684
818
|
let out = base;
|
|
685
819
|
const isThinkingEnabled =
|
|
686
|
-
this.thinkingConfig?.enabled !== false &&
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (isThinkingEnabled && !out.includes("[Reasoning Directive]")) {
|
|
690
|
-
const guidance =
|
|
691
|
-
"[Reasoning Directive]\n" +
|
|
692
|
-
"1. Use internal reasoning strictly for private planning and step-by-step thinking.\n" +
|
|
693
|
-
"2. Never attempt to execute tools or output final user deliverables inside reasoning.\n" +
|
|
694
|
-
"3. Once your reasoning is complete, output your final response or function calls directly in the standard response output.";
|
|
820
|
+
this.thinkingConfig?.enabled !== false && this.thinkingConfig?.level && this.thinkingConfig?.level !== "none";
|
|
821
|
+
const guidance = this.prompts.reasoningDirective;
|
|
822
|
+
if (isThinkingEnabled && !out.includes(guidance)) {
|
|
695
823
|
out = out ? `${out}\n\n${guidance}` : guidance;
|
|
696
824
|
}
|
|
697
825
|
return out || undefined;
|
|
@@ -707,8 +835,12 @@ export class Agent {
|
|
|
707
835
|
this.toolRecords.length = 0; // a cleared conversation has no run to resume, so its finished tool results go too
|
|
708
836
|
this.activeMark = undefined;
|
|
709
837
|
// Reset also drops session-scoped provider chaining/routing.
|
|
710
|
-
try {
|
|
711
|
-
|
|
838
|
+
try {
|
|
839
|
+
clearInteractionChains(sid);
|
|
840
|
+
} catch {}
|
|
841
|
+
try {
|
|
842
|
+
clearSessionRouting(sid);
|
|
843
|
+
} catch {}
|
|
712
844
|
}
|
|
713
845
|
|
|
714
846
|
/**
|
|
@@ -727,17 +859,27 @@ export class Agent {
|
|
|
727
859
|
/** Drops one named session. No-op for unknown ids. */
|
|
728
860
|
disposeSession(id: string): void {
|
|
729
861
|
unregisterSessionFolder(id);
|
|
730
|
-
try {
|
|
731
|
-
|
|
732
|
-
|
|
862
|
+
try {
|
|
863
|
+
this.sessions.delete(id);
|
|
864
|
+
} catch {}
|
|
865
|
+
try {
|
|
866
|
+
clearInteractionChains(id);
|
|
867
|
+
} catch {}
|
|
868
|
+
try {
|
|
869
|
+
clearSessionRouting(id);
|
|
870
|
+
} catch {}
|
|
733
871
|
}
|
|
734
872
|
|
|
735
873
|
/** Drops all named sessions except the default context. */
|
|
736
874
|
clearSessions(): void {
|
|
737
875
|
try {
|
|
738
876
|
for (const id of [...this.sessions.keys()]) {
|
|
739
|
-
try {
|
|
740
|
-
|
|
877
|
+
try {
|
|
878
|
+
clearInteractionChains(id);
|
|
879
|
+
} catch {}
|
|
880
|
+
try {
|
|
881
|
+
clearSessionRouting(id);
|
|
882
|
+
} catch {}
|
|
741
883
|
}
|
|
742
884
|
this.sessions.clear();
|
|
743
885
|
} catch {}
|
|
@@ -753,8 +895,12 @@ export class Agent {
|
|
|
753
895
|
for (const [key, entry] of [...this.sessions.entries()]) {
|
|
754
896
|
if (now - entry.lastAccess > SESSION_TTL_MS) {
|
|
755
897
|
this.sessions.delete(key);
|
|
756
|
-
try {
|
|
757
|
-
|
|
898
|
+
try {
|
|
899
|
+
clearInteractionChains(key);
|
|
900
|
+
} catch {}
|
|
901
|
+
try {
|
|
902
|
+
clearSessionRouting(key);
|
|
903
|
+
} catch {}
|
|
758
904
|
}
|
|
759
905
|
}
|
|
760
906
|
} catch {}
|
|
@@ -771,12 +917,19 @@ export class Agent {
|
|
|
771
917
|
let oldest: string | undefined;
|
|
772
918
|
let oldestTs = Infinity;
|
|
773
919
|
for (const [key, entry] of this.sessions) {
|
|
774
|
-
if (entry.lastAccess < oldestTs) {
|
|
920
|
+
if (entry.lastAccess < oldestTs) {
|
|
921
|
+
oldestTs = entry.lastAccess;
|
|
922
|
+
oldest = key;
|
|
923
|
+
}
|
|
775
924
|
}
|
|
776
925
|
if (!oldest) break;
|
|
777
926
|
this.sessions.delete(oldest);
|
|
778
|
-
try {
|
|
779
|
-
|
|
927
|
+
try {
|
|
928
|
+
clearInteractionChains(oldest);
|
|
929
|
+
} catch {}
|
|
930
|
+
try {
|
|
931
|
+
clearSessionRouting(oldest);
|
|
932
|
+
} catch {}
|
|
780
933
|
}
|
|
781
934
|
} catch {}
|
|
782
935
|
return fresh;
|
|
@@ -789,8 +942,12 @@ export class Agent {
|
|
|
789
942
|
const err = Object.assign(new Error("Queue cleared"), { name: "AbortError" });
|
|
790
943
|
const pending = this.pendingQueue.splice(0);
|
|
791
944
|
for (const entry of pending) {
|
|
792
|
-
try {
|
|
793
|
-
|
|
945
|
+
try {
|
|
946
|
+
entry.reject(err);
|
|
947
|
+
} catch {}
|
|
948
|
+
try {
|
|
949
|
+
entry.deferredStream?.fail(err instanceof Error ? err : new Error(String(err)));
|
|
950
|
+
} catch {}
|
|
794
951
|
}
|
|
795
952
|
this.persistNow();
|
|
796
953
|
return n;
|
|
@@ -800,7 +957,7 @@ export class Agent {
|
|
|
800
957
|
private assertSteerRoom(inbox: SteerEntry[]): void {
|
|
801
958
|
const max = this.maxSteers ?? 20;
|
|
802
959
|
if (inbox.length >= max) {
|
|
803
|
-
throw new
|
|
960
|
+
throw new ValidationError(
|
|
804
961
|
`[Agent Accelerator] Steer inbox is full (${inbox.length}/${max}). Wait for the active run to drain or increase midSession.maxSteers.`
|
|
805
962
|
);
|
|
806
963
|
}
|
|
@@ -858,7 +1015,9 @@ export class Agent {
|
|
|
858
1015
|
interrupt(arg?: Error | string | ContentPart[], options?: AgentRunOptions): void | Promise<AgentResponse> {
|
|
859
1016
|
if (typeof arg === "string" || Array.isArray(arg)) {
|
|
860
1017
|
if (options?.stream === true) {
|
|
861
|
-
throw new
|
|
1018
|
+
throw new ValidationError(
|
|
1019
|
+
'[Agent Accelerator] interrupt(message) is non-streaming. Use stream(prompt, { enqueue: "interrupt" }) for a streaming interrupt.'
|
|
1020
|
+
);
|
|
862
1021
|
}
|
|
863
1022
|
return this.run(arg, { ...options, enqueue: "interrupt" });
|
|
864
1023
|
}
|
|
@@ -900,12 +1059,12 @@ export class Agent {
|
|
|
900
1059
|
steer(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
901
1060
|
this.assertPromptValid(prompt);
|
|
902
1061
|
if (options?.stream === true) {
|
|
903
|
-
throw new
|
|
1062
|
+
throw new ValidationError(
|
|
904
1063
|
'[Agent Accelerator] steer() is non-streaming. Use stream(prompt, { enqueue: "steer" }) for a streaming steer.'
|
|
905
1064
|
);
|
|
906
1065
|
}
|
|
907
1066
|
if (this.midSessionConfig.mode === "queue") {
|
|
908
|
-
throw new
|
|
1067
|
+
throw new ConfigError(
|
|
909
1068
|
'[Agent Accelerator] steer() is disabled by midSession.mode "queue". Use queue() or set mode to "auto"/"steer".'
|
|
910
1069
|
);
|
|
911
1070
|
}
|
|
@@ -958,19 +1117,19 @@ export class Agent {
|
|
|
958
1117
|
queue(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> {
|
|
959
1118
|
this.assertPromptValid(prompt);
|
|
960
1119
|
if (options?.stream === true) {
|
|
961
|
-
throw new
|
|
1120
|
+
throw new ValidationError(
|
|
962
1121
|
'[Agent Accelerator] queue() is non-streaming. Use stream(prompt, { enqueue: "queue" }) for a streaming follow-up.'
|
|
963
1122
|
);
|
|
964
1123
|
}
|
|
965
1124
|
if (this.midSessionConfig.mode === "steer") {
|
|
966
|
-
throw new
|
|
1125
|
+
throw new ConfigError(
|
|
967
1126
|
'[Agent Accelerator] queue() is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
968
1127
|
);
|
|
969
1128
|
}
|
|
970
1129
|
if (!this.currentRun) return this.run(prompt, options) as Promise<AgentResponse>;
|
|
971
1130
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
972
1131
|
if (this.pendingQueue.length >= max) {
|
|
973
|
-
throw new
|
|
1132
|
+
throw new ValidationError(
|
|
974
1133
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
975
1134
|
);
|
|
976
1135
|
}
|
|
@@ -1001,8 +1160,12 @@ export class Agent {
|
|
|
1001
1160
|
reason instanceof Error ? reason : new Error("Queued run aborted before start"),
|
|
1002
1161
|
{ name: "AbortError" }
|
|
1003
1162
|
);
|
|
1004
|
-
try {
|
|
1005
|
-
|
|
1163
|
+
try {
|
|
1164
|
+
next.reject(abortErr);
|
|
1165
|
+
} catch {}
|
|
1166
|
+
try {
|
|
1167
|
+
next.deferredStream?.fail(abortErr instanceof Error ? abortErr : new Error(String(abortErr)));
|
|
1168
|
+
} catch {}
|
|
1006
1169
|
queueMicrotask(() => this.pumpQueue());
|
|
1007
1170
|
return;
|
|
1008
1171
|
}
|
|
@@ -1011,30 +1174,48 @@ export class Agent {
|
|
|
1011
1174
|
const deferred = next.deferredStream;
|
|
1012
1175
|
try {
|
|
1013
1176
|
const real = this.startStreamingRun(next.prompt, next.options);
|
|
1014
|
-
try {
|
|
1177
|
+
try {
|
|
1178
|
+
(deferred as any).__piped = true;
|
|
1179
|
+
} catch {}
|
|
1015
1180
|
const forward = (e: StreamEvent) => {
|
|
1016
|
-
try {
|
|
1181
|
+
try {
|
|
1182
|
+
deferred.push(e);
|
|
1183
|
+
} catch {}
|
|
1017
1184
|
};
|
|
1018
1185
|
real.on("*", forward as any);
|
|
1019
1186
|
try {
|
|
1020
1187
|
deferred.onCancel(() => {
|
|
1021
|
-
try {
|
|
1188
|
+
try {
|
|
1189
|
+
real.cancel();
|
|
1190
|
+
} catch {}
|
|
1022
1191
|
});
|
|
1023
1192
|
} catch {}
|
|
1024
1193
|
real.result().then(
|
|
1025
1194
|
(res) => {
|
|
1026
|
-
try {
|
|
1027
|
-
|
|
1195
|
+
try {
|
|
1196
|
+
next.resolve(res);
|
|
1197
|
+
} catch {}
|
|
1198
|
+
try {
|
|
1199
|
+
deferred.end(res);
|
|
1200
|
+
} catch {}
|
|
1028
1201
|
},
|
|
1029
1202
|
(err) => {
|
|
1030
|
-
try {
|
|
1203
|
+
try {
|
|
1204
|
+
next.reject(err);
|
|
1205
|
+
} catch {}
|
|
1031
1206
|
// Pipe failures into the deferred stream so for-await sees them.
|
|
1032
|
-
try {
|
|
1207
|
+
try {
|
|
1208
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1209
|
+
} catch {}
|
|
1033
1210
|
}
|
|
1034
1211
|
);
|
|
1035
1212
|
} catch (err) {
|
|
1036
|
-
try {
|
|
1037
|
-
|
|
1213
|
+
try {
|
|
1214
|
+
next.reject(err);
|
|
1215
|
+
} catch {}
|
|
1216
|
+
try {
|
|
1217
|
+
deferred.fail(err instanceof Error ? err : new Error(String(err)));
|
|
1218
|
+
} catch {}
|
|
1038
1219
|
// Keep draining even if starting failed.
|
|
1039
1220
|
queueMicrotask(() => this.pumpQueue());
|
|
1040
1221
|
}
|
|
@@ -1047,7 +1228,11 @@ export class Agent {
|
|
|
1047
1228
|
private finishCurrentRun(): void {
|
|
1048
1229
|
// Leftover steer entries become queued follow-ups instead of dropping.
|
|
1049
1230
|
let leftover: SteerEntry[] = [];
|
|
1050
|
-
try {
|
|
1231
|
+
try {
|
|
1232
|
+
leftover = this.currentRun?.steerInbox?.splice(0) ?? [];
|
|
1233
|
+
} catch {
|
|
1234
|
+
leftover = [];
|
|
1235
|
+
}
|
|
1051
1236
|
this.currentRun = null;
|
|
1052
1237
|
if (leftover.length > 0) {
|
|
1053
1238
|
try {
|
|
@@ -1057,12 +1242,18 @@ export class Agent {
|
|
|
1057
1242
|
for (const entry of leftover) {
|
|
1058
1243
|
try {
|
|
1059
1244
|
if ((entry.options as { signal?: AbortSignal } | undefined)?.signal?.aborted) {
|
|
1060
|
-
entry.settle?.reject(
|
|
1245
|
+
entry.settle?.reject(
|
|
1246
|
+
Object.assign(new Error("Steer aborted before it could run"), { name: "AbortError" })
|
|
1247
|
+
);
|
|
1061
1248
|
continue;
|
|
1062
1249
|
}
|
|
1063
|
-
} catch {
|
|
1250
|
+
} catch {
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1064
1253
|
if (this.pendingQueue.length + lateSteers.length >= max) {
|
|
1065
|
-
entry.settle?.reject(
|
|
1254
|
+
entry.settle?.reject(
|
|
1255
|
+
new ValidationError("[Agent Accelerator] Steer dropped: the follow-up queue is full.")
|
|
1256
|
+
);
|
|
1066
1257
|
continue;
|
|
1067
1258
|
}
|
|
1068
1259
|
// The steer was never injected: it now runs as a follow-up, and the
|
|
@@ -1091,7 +1282,9 @@ export class Agent {
|
|
|
1091
1282
|
try {
|
|
1092
1283
|
const dispose = mergeDisposers.get(runController);
|
|
1093
1284
|
if (dispose) {
|
|
1094
|
-
try {
|
|
1285
|
+
try {
|
|
1286
|
+
dispose();
|
|
1287
|
+
} catch {}
|
|
1095
1288
|
mergeDisposers.delete(runController);
|
|
1096
1289
|
}
|
|
1097
1290
|
} catch {}
|
|
@@ -1105,7 +1298,9 @@ export class Agent {
|
|
|
1105
1298
|
try {
|
|
1106
1299
|
runController.abort((userSignal as any).reason);
|
|
1107
1300
|
} catch {
|
|
1108
|
-
try {
|
|
1301
|
+
try {
|
|
1302
|
+
runController.abort();
|
|
1303
|
+
} catch {}
|
|
1109
1304
|
}
|
|
1110
1305
|
return runController.signal;
|
|
1111
1306
|
}
|
|
@@ -1121,13 +1316,19 @@ export class Agent {
|
|
|
1121
1316
|
try {
|
|
1122
1317
|
runController.abort((userSignal as any).reason);
|
|
1123
1318
|
} catch {
|
|
1124
|
-
try {
|
|
1319
|
+
try {
|
|
1320
|
+
runController.abort();
|
|
1321
|
+
} catch {}
|
|
1125
1322
|
}
|
|
1126
1323
|
};
|
|
1127
|
-
try {
|
|
1324
|
+
try {
|
|
1325
|
+
userSignal.addEventListener("abort", forward, { once: true });
|
|
1326
|
+
} catch {}
|
|
1128
1327
|
try {
|
|
1129
1328
|
mergeDisposers.set(runController, () => {
|
|
1130
|
-
try {
|
|
1329
|
+
try {
|
|
1330
|
+
userSignal.removeEventListener("abort", forward);
|
|
1331
|
+
} catch {}
|
|
1131
1332
|
});
|
|
1132
1333
|
} catch {}
|
|
1133
1334
|
return runController.signal;
|
|
@@ -1143,6 +1344,7 @@ export class Agent {
|
|
|
1143
1344
|
const d = this.callDefaults;
|
|
1144
1345
|
return {
|
|
1145
1346
|
limits: { ...this.runLimits },
|
|
1347
|
+
...(this.forkConfig.prompts ? { prompts: { ...this.forkConfig.prompts } } : {}),
|
|
1146
1348
|
...(d.retry ? { retry: d.retry } : {}),
|
|
1147
1349
|
...(d.env ? { env: d.env } : {}),
|
|
1148
1350
|
...(d.fetch ? { fetch: d.fetch } : {}),
|
|
@@ -1194,13 +1396,34 @@ export class Agent {
|
|
|
1194
1396
|
return new Agent(applyEditable(editable, base));
|
|
1195
1397
|
}
|
|
1196
1398
|
|
|
1399
|
+
// Takes the chat total's starting point (what the saved sessions of this agent and its workers already spent) once, before the first turn of the chat's root agent; in: the run starters, out: this.chat.
|
|
1400
|
+
private beginChat(): void {
|
|
1401
|
+
if (!this.ownsChat) return;
|
|
1402
|
+
this.chat!.begin(() => {
|
|
1403
|
+
// A persisted chat starts from its saved sessions (this agent's and every worker's); a memory-only one from what it holds now.
|
|
1404
|
+
const root = this.persistConfig?.dir;
|
|
1405
|
+
if (root && !this.stateless) {
|
|
1406
|
+
try {
|
|
1407
|
+
const saved = savedSpend(sessionDirFor(root, this.sessionId));
|
|
1408
|
+
return { tokens: saved.inputTokens + saved.outputTokens, microUSD: saved.costMicroUSD.total };
|
|
1409
|
+
} catch {
|
|
1410
|
+
// not a usable session folder name: nothing was saved there
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
const total = this.usageReport().total;
|
|
1414
|
+
return { tokens: total.tokens.total, microUSD: Math.round((total.cost.totalUsd ?? 0) * 1_000_000) };
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1197
1418
|
/** Budgets are per run: counters restart whenever a new run begins. */
|
|
1198
1419
|
private resetBudgetState(): void {
|
|
1420
|
+
this.beginChat();
|
|
1199
1421
|
if (this.budget) this.budgetState = { spawned: 0, totalTokens: 0, totalCostUsd: 0, startedAtMs: nowMs() };
|
|
1200
1422
|
}
|
|
1201
1423
|
|
|
1202
1424
|
/** Loop hook: charge `usage` (when given) and throw `BudgetExceededError` once any cap is passed. */
|
|
1203
1425
|
private enforceBudget(usage?: TokenUsage): void {
|
|
1426
|
+
this.chat?.check(); // the chat-wide cap: before every turn and right after each one
|
|
1204
1427
|
if (!this.budget) return;
|
|
1205
1428
|
if (usage) chargeBudgetUsage(this as unknown as Parameters<typeof chargeBudgetUsage>[0], usage);
|
|
1206
1429
|
checkBudget(this.budget, this.budgetState);
|
|
@@ -1223,9 +1446,7 @@ export class Agent {
|
|
|
1223
1446
|
const toolChoice = normalizeToolChoiceOption(runOptions?.toolChoice ?? d.toolChoice);
|
|
1224
1447
|
const retry = { ...d.retry, ...runOptions?.retry };
|
|
1225
1448
|
const env = { ...d.env, ...runOptions?.env };
|
|
1226
|
-
const stop = normalizeStop(runOptions?.stop ?? d.stop);
|
|
1227
1449
|
const maxOutputTokens = runOptions?.maxOutputTokens ?? d.maxOutputTokens;
|
|
1228
|
-
const seed = runOptions?.seed ?? d.seed;
|
|
1229
1450
|
const fetchImpl = runOptions?.fetch ?? d.fetch;
|
|
1230
1451
|
return {
|
|
1231
1452
|
apiKey: this.apiKey,
|
|
@@ -1236,11 +1457,10 @@ export class Agent {
|
|
|
1236
1457
|
serviceTier: this.serviceTier,
|
|
1237
1458
|
sessionId,
|
|
1238
1459
|
...(this.outputSpec ? { output: this.outputSpec } : {}),
|
|
1460
|
+
prompts: { structuredOutput: this.prompts.structuredOutput, toolBridge: this.prompts.toolBridge },
|
|
1239
1461
|
...(this.googleTier ? { googleTier: this.googleTier } : {}),
|
|
1240
1462
|
...(toolChoice !== undefined ? { toolChoice } : {}),
|
|
1241
1463
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1242
|
-
...(stop ? { stop } : {}),
|
|
1243
|
-
...(seed !== undefined ? { seed } : {}),
|
|
1244
1464
|
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
1245
1465
|
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
1246
1466
|
...(retry.maxRetries !== undefined ? { maxRetries: retry.maxRetries } : {}),
|
|
@@ -1295,7 +1515,7 @@ export class Agent {
|
|
|
1295
1515
|
...(this.fileAccess ? { fileAccess: this.fileAccess } : {}),
|
|
1296
1516
|
onFailure: this.onFailure,
|
|
1297
1517
|
...(this.captureRaw !== undefined ? { captureRaw: this.captureRaw } : {}),
|
|
1298
|
-
...(this.budget ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
|
|
1518
|
+
...(this.budget || this.chat ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
|
|
1299
1519
|
onProgress: () => this.persistNow(),
|
|
1300
1520
|
onTurnUsage: (u: TokenUsage, spec: Parameters<typeof priceUsage>[1]) => this.recordTurnUsage(u, spec),
|
|
1301
1521
|
onToolResult: (r: ToolResultRecord) => this.recordTool(r),
|
|
@@ -1305,6 +1525,7 @@ export class Agent {
|
|
|
1305
1525
|
onSteerInjected: () => this.persistNow(),
|
|
1306
1526
|
// Loop enforces these (plus local timeout races).
|
|
1307
1527
|
limits: { ...this.runLimits },
|
|
1528
|
+
prompts: this.prompts,
|
|
1308
1529
|
...(this.output !== undefined ? { output: this.output } : {}),
|
|
1309
1530
|
};
|
|
1310
1531
|
|
|
@@ -1314,13 +1535,18 @@ export class Agent {
|
|
|
1314
1535
|
const failedAttempts: FailedAttempt[] = [];
|
|
1315
1536
|
for (let attempt = 0; ; attempt++) {
|
|
1316
1537
|
try {
|
|
1317
|
-
res = await runAgentLoop(
|
|
1538
|
+
res = await runAgentLoop(
|
|
1539
|
+
attempt === 0 ? loopConfig : { ...loopConfig, resume: { stored: this.storedToolResults() } }
|
|
1540
|
+
);
|
|
1318
1541
|
break;
|
|
1319
1542
|
} catch (err) {
|
|
1320
1543
|
if (attempt >= this.runRetries || runOptions?.signal?.aborted || !isRetryableRun(err)) throw err;
|
|
1321
1544
|
const partial = (err as { partial?: FailedAttempt }).partial;
|
|
1322
1545
|
if (partial?.usage) failedAttempts.push(partial);
|
|
1323
|
-
const delayMs = Math.max(
|
|
1546
|
+
const delayMs = Math.max(
|
|
1547
|
+
RUN_RETRY_DELAYS_MS[Math.min(attempt, RUN_RETRY_DELAYS_MS.length - 1)]!,
|
|
1548
|
+
readRetryAfterMs(err) ?? 0
|
|
1549
|
+
);
|
|
1324
1550
|
telemetryBus.emitWarning(
|
|
1325
1551
|
"agent.run_retry",
|
|
1326
1552
|
`[Agent Accelerator] Run failed (${err instanceof Error ? err.message : String(err)}); retry ${attempt + 1} of ${this.runRetries} in ${delayMs} ms.`,
|
|
@@ -1345,7 +1571,9 @@ export class Agent {
|
|
|
1345
1571
|
(err) => this.endRun(mark, isAbortError(err) ? "aborted" : "failed")
|
|
1346
1572
|
);
|
|
1347
1573
|
const cleanup = () => {
|
|
1348
|
-
try {
|
|
1574
|
+
try {
|
|
1575
|
+
this.cleanupMerge(runController);
|
|
1576
|
+
} catch {}
|
|
1349
1577
|
if (this.currentRun?.runPromise === task) this.finishCurrentRun();
|
|
1350
1578
|
};
|
|
1351
1579
|
task.then(cleanup, cleanup);
|
|
@@ -1353,10 +1581,7 @@ export class Agent {
|
|
|
1353
1581
|
}
|
|
1354
1582
|
|
|
1355
1583
|
// 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 {
|
|
1584
|
+
private startStreamingRun(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1360
1585
|
const resolved = resolveModel(this.modelStringOrSpec);
|
|
1361
1586
|
const effectiveLevel = normalizeThinkingV2(options?.thinking, options?.thinkingLevel) || this.thinkingConfig?.level;
|
|
1362
1587
|
if (effectiveLevel) {
|
|
@@ -1387,7 +1612,7 @@ export class Agent {
|
|
|
1387
1612
|
...(this.fileAccess ? { fileAccess: this.fileAccess } : {}),
|
|
1388
1613
|
onFailure: this.onFailure,
|
|
1389
1614
|
...(this.captureRaw !== undefined ? { captureRaw: this.captureRaw } : {}),
|
|
1390
|
-
...(this.budget ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
|
|
1615
|
+
...(this.budget || this.chat ? { enforceBudget: (u?: TokenUsage) => this.enforceBudget(u) } : {}),
|
|
1391
1616
|
onProgress: () => this.persistNow(),
|
|
1392
1617
|
onTurnUsage: (u: TokenUsage, spec: Parameters<typeof priceUsage>[1]) => this.recordTurnUsage(u, spec),
|
|
1393
1618
|
onToolResult: (r: ToolResultRecord) => this.recordTool(r),
|
|
@@ -1396,6 +1621,7 @@ export class Agent {
|
|
|
1396
1621
|
onSteerInjected: () => this.persistNow(),
|
|
1397
1622
|
// Loop enforces these (plus local timeout races).
|
|
1398
1623
|
limits: { ...this.runLimits },
|
|
1624
|
+
prompts: this.prompts,
|
|
1399
1625
|
...(this.output !== undefined ? { output: this.output } : {}),
|
|
1400
1626
|
};
|
|
1401
1627
|
|
|
@@ -1408,7 +1634,9 @@ export class Agent {
|
|
|
1408
1634
|
this.mergeSubagentTraces(res.subagents as any);
|
|
1409
1635
|
this.persistNow();
|
|
1410
1636
|
} catch {}
|
|
1411
|
-
try {
|
|
1637
|
+
try {
|
|
1638
|
+
this.cleanupMerge(runController);
|
|
1639
|
+
} catch {}
|
|
1412
1640
|
if (this.stateless) {
|
|
1413
1641
|
targetContext.messages = [];
|
|
1414
1642
|
targetContext.thoughtSignatures = [];
|
|
@@ -1417,7 +1645,9 @@ export class Agent {
|
|
|
1417
1645
|
},
|
|
1418
1646
|
(err) => {
|
|
1419
1647
|
this.endRun(mark, isAbortError(err) ? "aborted" : "failed");
|
|
1420
|
-
try {
|
|
1648
|
+
try {
|
|
1649
|
+
this.cleanupMerge(runController);
|
|
1650
|
+
} catch {}
|
|
1421
1651
|
if (this.currentRun?.outerStream === s) this.finishCurrentRun();
|
|
1422
1652
|
}
|
|
1423
1653
|
);
|
|
@@ -1534,12 +1764,24 @@ export class Agent {
|
|
|
1534
1764
|
else if (cfg.file) saveSessionFile(cfg.file, this as any, null);
|
|
1535
1765
|
} catch (err) {
|
|
1536
1766
|
// Surface via hook instead of swallowing silently.
|
|
1537
|
-
try {
|
|
1767
|
+
try {
|
|
1768
|
+
(cfg as unknown as { onPersistError?: (e: unknown) => void }).onPersistError?.(err);
|
|
1769
|
+
} catch {}
|
|
1538
1770
|
}
|
|
1539
1771
|
}
|
|
1540
1772
|
|
|
1541
1773
|
/** Merges completed worker metadata (incl. steps) into this agent's trace registry. */
|
|
1542
|
-
private mergeSubagentTraces(
|
|
1774
|
+
private mergeSubagentTraces(
|
|
1775
|
+
subagents: Array<{
|
|
1776
|
+
trackingId?: string;
|
|
1777
|
+
name: string;
|
|
1778
|
+
sessionId?: string;
|
|
1779
|
+
parentSessionId?: string;
|
|
1780
|
+
task: string;
|
|
1781
|
+
steps?: SubAgentTrace["steps"];
|
|
1782
|
+
[k: string]: unknown;
|
|
1783
|
+
}>
|
|
1784
|
+
): void {
|
|
1543
1785
|
if (!subagents || subagents.length === 0) return;
|
|
1544
1786
|
const rawOf = (id?: string): string | undefined => {
|
|
1545
1787
|
if (!id) return undefined;
|
|
@@ -1641,15 +1883,19 @@ export class Agent {
|
|
|
1641
1883
|
try {
|
|
1642
1884
|
const g: any = globalThis as any;
|
|
1643
1885
|
this.context.messages = Array.isArray(data.messages)
|
|
1644
|
-
?
|
|
1645
|
-
|
|
1646
|
-
|
|
1886
|
+
? typeof g.structuredClone === "function"
|
|
1887
|
+
? g.structuredClone(data.messages)
|
|
1888
|
+
: JSON.parse(JSON.stringify(data.messages))
|
|
1647
1889
|
: [];
|
|
1648
1890
|
} catch {
|
|
1649
1891
|
this.context.messages = Array.isArray(data.messages) ? [...data.messages] : [];
|
|
1650
1892
|
}
|
|
1651
1893
|
this.context.thoughtSignatures = [];
|
|
1652
|
-
this.runMarks.splice(
|
|
1894
|
+
this.runMarks.splice(
|
|
1895
|
+
0,
|
|
1896
|
+
this.runMarks.length,
|
|
1897
|
+
...(data.runs ?? []).filter((r) => r.i < this.context.messages.length)
|
|
1898
|
+
);
|
|
1653
1899
|
this.restoreUsage(data.usage);
|
|
1654
1900
|
if (data.systemPrompt) {
|
|
1655
1901
|
this.context.systemPrompt = data.systemPrompt;
|
|
@@ -1658,10 +1904,18 @@ export class Agent {
|
|
|
1658
1904
|
}
|
|
1659
1905
|
// Importing a snapshot invalidates prior provider chaining/routing
|
|
1660
1906
|
// for both the previous and the restored session.
|
|
1661
|
-
try {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
try {
|
|
1907
|
+
try {
|
|
1908
|
+
clearInteractionChains(prevSid);
|
|
1909
|
+
} catch {}
|
|
1910
|
+
try {
|
|
1911
|
+
clearSessionRouting(prevSid);
|
|
1912
|
+
} catch {}
|
|
1913
|
+
try {
|
|
1914
|
+
clearInteractionChains(this.sessionId);
|
|
1915
|
+
} catch {}
|
|
1916
|
+
try {
|
|
1917
|
+
clearSessionRouting(this.sessionId);
|
|
1918
|
+
} catch {}
|
|
1665
1919
|
}
|
|
1666
1920
|
|
|
1667
1921
|
/**
|
|
@@ -1691,7 +1945,11 @@ export class Agent {
|
|
|
1691
1945
|
}
|
|
1692
1946
|
|
|
1693
1947
|
// 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(
|
|
1948
|
+
private prepareTurn(
|
|
1949
|
+
prompt: string | ContentPart[],
|
|
1950
|
+
options?: AgentRunOptions,
|
|
1951
|
+
target?: AgentContext
|
|
1952
|
+
): SessionRunMark | undefined {
|
|
1695
1953
|
// Prompt validation throws synchronously in both run() and
|
|
1696
1954
|
// stream() (same timing; documented here).
|
|
1697
1955
|
if (Array.isArray(prompt) && prompt.length === 0) {
|
|
@@ -1710,7 +1968,7 @@ export class Agent {
|
|
|
1710
1968
|
if (options?.additionalContext) {
|
|
1711
1969
|
// Preserve stable instructions as systemPrompt
|
|
1712
1970
|
ctx.systemPrompt = fullInstructions;
|
|
1713
|
-
const prefix =
|
|
1971
|
+
const prefix = `${fillPrompt(this.prompts.additionalContext, { context: options.additionalContext })}\n\n`;
|
|
1714
1972
|
if (typeof prompt === "string") {
|
|
1715
1973
|
prompt = prefix + prompt;
|
|
1716
1974
|
} else if (Array.isArray(prompt)) {
|
|
@@ -1750,23 +2008,14 @@ export class Agent {
|
|
|
1750
2008
|
run(
|
|
1751
2009
|
prompt: string | ContentPart[],
|
|
1752
2010
|
options: AgentRunOptions & { stream: true } & (
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2011
|
+
| { onDelta: NonNullable<AgentRunOptions["onDelta"]> }
|
|
2012
|
+
| { onThinkingDelta: NonNullable<AgentRunOptions["onThinkingDelta"]> }
|
|
2013
|
+
| { onEvent: NonNullable<AgentRunOptions["onEvent"]> }
|
|
2014
|
+
)
|
|
1757
2015
|
): 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 {
|
|
2016
|
+
run(prompt: string | ContentPart[], options: AgentRunOptions & { stream: true }): AssistantMessageEventStream;
|
|
2017
|
+
run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse>;
|
|
2018
|
+
run(prompt: string | ContentPart[], options?: AgentRunOptions): Promise<AgentResponse> | AssistantMessageEventStream {
|
|
1770
2019
|
// Prompt validation throws synchronously in both run() and
|
|
1771
2020
|
// stream() (same timing; async work starts only after validation).
|
|
1772
2021
|
this.assertPromptValid(prompt);
|
|
@@ -1795,12 +2044,14 @@ export class Agent {
|
|
|
1795
2044
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn), or stops the active run and starts this prompt (interrupt).
|
|
1796
2045
|
if (this.currentRun) {
|
|
1797
2046
|
if (options?.enqueue === "interrupt") {
|
|
1798
|
-
return new Promise<AgentResponse>((resolve, reject) =>
|
|
2047
|
+
return new Promise<AgentResponse>((resolve, reject) =>
|
|
2048
|
+
this.queueUrgent({ prompt, options, isStream: false, resolve, reject })
|
|
2049
|
+
);
|
|
1799
2050
|
}
|
|
1800
2051
|
const enforced = this.midSessionConfig.mode;
|
|
1801
2052
|
const requested = options?.enqueue;
|
|
1802
2053
|
if (requested && (enforced === "steer" || enforced === "queue") && requested !== enforced) {
|
|
1803
|
-
throw new
|
|
2054
|
+
throw new ConfigError(
|
|
1804
2055
|
`[Agent Accelerator] enqueue "${requested}" is disabled by midSession.mode "${enforced}". Use ${enforced}() or set mode to "auto"/"${requested}".`
|
|
1805
2056
|
);
|
|
1806
2057
|
}
|
|
@@ -1810,14 +2061,14 @@ export class Agent {
|
|
|
1810
2061
|
return this.steerInto(this.currentRun, prompt, options);
|
|
1811
2062
|
} else {
|
|
1812
2063
|
if (this.midSessionConfig.mode === "steer") {
|
|
1813
|
-
throw new
|
|
2064
|
+
throw new ConfigError(
|
|
1814
2065
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1815
2066
|
);
|
|
1816
2067
|
}
|
|
1817
2068
|
this.assertPromptValid(prompt);
|
|
1818
2069
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1819
2070
|
if (this.pendingQueue.length >= max) {
|
|
1820
|
-
throw new
|
|
2071
|
+
throw new ValidationError(
|
|
1821
2072
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1822
2073
|
);
|
|
1823
2074
|
}
|
|
@@ -1846,13 +2097,9 @@ export class Agent {
|
|
|
1846
2097
|
prompt: string | ContentPart[],
|
|
1847
2098
|
optionsOrStream?: boolean | AgentRunOptions
|
|
1848
2099
|
): Promise<AgentResponse> & AsyncIterable<string | StreamEvent> {
|
|
1849
|
-
const isStream =
|
|
1850
|
-
typeof optionsOrStream === "boolean"
|
|
1851
|
-
? optionsOrStream
|
|
1852
|
-
: optionsOrStream?.stream === true;
|
|
2100
|
+
const isStream = typeof optionsOrStream === "boolean" ? optionsOrStream : optionsOrStream?.stream === true;
|
|
1853
2101
|
|
|
1854
|
-
const runOpts: AgentRunOptions =
|
|
1855
|
-
typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
2102
|
+
const runOpts: AgentRunOptions = typeof optionsOrStream === "object" ? optionsOrStream : { stream: isStream };
|
|
1856
2103
|
|
|
1857
2104
|
if (isStream) {
|
|
1858
2105
|
const stream = this.stream(prompt, runOpts);
|
|
@@ -1860,7 +2107,8 @@ export class Agent {
|
|
|
1860
2107
|
if (runOpts.onDelta || runOpts.onThinkingDelta || runOpts.onEvent) {
|
|
1861
2108
|
if (runOpts.onEvent) stream.on("*", runOpts.onEvent as any);
|
|
1862
2109
|
if (runOpts.onDelta) stream.on("text_delta", (e: any) => runOpts.onDelta!(e.delta!, e));
|
|
1863
|
-
if (runOpts.onThinkingDelta)
|
|
2110
|
+
if (runOpts.onThinkingDelta)
|
|
2111
|
+
stream.on("thinking_delta", (e: any) => runOpts.onThinkingDelta!(e.thinkingDelta!, e));
|
|
1864
2112
|
}
|
|
1865
2113
|
const stringStream: any = {
|
|
1866
2114
|
[Symbol.asyncIterator]: async function* () {
|
|
@@ -1901,10 +2149,7 @@ export class Agent {
|
|
|
1901
2149
|
* }
|
|
1902
2150
|
* ```
|
|
1903
2151
|
*/
|
|
1904
|
-
stream(
|
|
1905
|
-
prompt: string | ContentPart[],
|
|
1906
|
-
options?: AgentRunOptions
|
|
1907
|
-
): AssistantMessageEventStream {
|
|
2152
|
+
stream(prompt: string | ContentPart[], options?: AgentRunOptions): AssistantMessageEventStream {
|
|
1908
2153
|
// Same sync validation timing as run().
|
|
1909
2154
|
this.assertPromptValid(prompt);
|
|
1910
2155
|
// Mid-session: busy agent serializes via steer (same turn) or queue (next turn).
|
|
@@ -1912,8 +2157,12 @@ export class Agent {
|
|
|
1912
2157
|
const enforcedStream = this.midSessionConfig.mode;
|
|
1913
2158
|
const urgent = options?.enqueue === "interrupt"; // stops the active run and goes first, in every mode and whatever the queue holds
|
|
1914
2159
|
const requestedStream = options?.enqueue === "interrupt" ? undefined : options?.enqueue;
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
2160
|
+
if (
|
|
2161
|
+
requestedStream &&
|
|
2162
|
+
(enforcedStream === "steer" || enforcedStream === "queue") &&
|
|
2163
|
+
requestedStream !== enforcedStream
|
|
2164
|
+
) {
|
|
2165
|
+
throw new ConfigError(
|
|
1917
2166
|
`[Agent Accelerator] enqueue "${requestedStream}" is disabled by midSession.mode "${enforcedStream}". Use ${enforcedStream}() or set mode to "auto"/"${requestedStream}".`
|
|
1918
2167
|
);
|
|
1919
2168
|
}
|
|
@@ -1937,28 +2186,40 @@ export class Agent {
|
|
|
1937
2186
|
activePromise.then(
|
|
1938
2187
|
(res) => {
|
|
1939
2188
|
try {
|
|
1940
|
-
stub.push({
|
|
2189
|
+
stub.push({
|
|
2190
|
+
type: "done",
|
|
2191
|
+
delta: "",
|
|
2192
|
+
usage: res.usage,
|
|
2193
|
+
finishReason: res.finishReason,
|
|
2194
|
+
responseId: res.responseId,
|
|
2195
|
+
} as any);
|
|
2196
|
+
} catch {}
|
|
2197
|
+
try {
|
|
2198
|
+
stub.end(res);
|
|
1941
2199
|
} catch {}
|
|
1942
|
-
try { stub.end(res); } catch {}
|
|
1943
2200
|
},
|
|
1944
2201
|
(err) => {
|
|
1945
|
-
try {
|
|
2202
|
+
try {
|
|
2203
|
+
stub.fail(err instanceof Error ? err : new Error(String(err)));
|
|
2204
|
+
} catch {}
|
|
1946
2205
|
}
|
|
1947
2206
|
);
|
|
1948
2207
|
} else {
|
|
1949
|
-
try {
|
|
2208
|
+
try {
|
|
2209
|
+
stub.fail(new ValidationError("[Agent Accelerator] No active run to steer."));
|
|
2210
|
+
} catch {}
|
|
1950
2211
|
}
|
|
1951
2212
|
return stub;
|
|
1952
2213
|
}
|
|
1953
2214
|
if (!urgent && this.midSessionConfig.mode === "steer") {
|
|
1954
|
-
throw new
|
|
2215
|
+
throw new ConfigError(
|
|
1955
2216
|
'[Agent Accelerator] enqueue "queue" is disabled by midSession.mode "steer". Use steer() or set mode to "auto"/"queue".'
|
|
1956
2217
|
);
|
|
1957
2218
|
}
|
|
1958
2219
|
this.assertPromptValid(prompt);
|
|
1959
2220
|
const max = this.midSessionConfig.maxQueued ?? 20;
|
|
1960
2221
|
if (!urgent && this.pendingQueue.length >= max) {
|
|
1961
|
-
throw new
|
|
2222
|
+
throw new ValidationError(
|
|
1962
2223
|
`[Agent Accelerator] Queue is full (${this.pendingQueue.length}/${max}). Wait for the active run to finish or increase midSession.maxQueued.`
|
|
1963
2224
|
);
|
|
1964
2225
|
}
|
|
@@ -2005,7 +2266,9 @@ export class Agent {
|
|
|
2005
2266
|
if (idx >= 0) {
|
|
2006
2267
|
this.pendingQueue.splice(idx, 1);
|
|
2007
2268
|
queuedEntry.reject(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2008
|
-
try {
|
|
2269
|
+
try {
|
|
2270
|
+
deferred.fail(Object.assign(new Error("Stream aborted"), { name: "AbortError" }));
|
|
2271
|
+
} catch {}
|
|
2009
2272
|
}
|
|
2010
2273
|
} catch {}
|
|
2011
2274
|
});
|
|
@@ -2023,7 +2286,13 @@ export class Agent {
|
|
|
2023
2286
|
// Otherwise (edge), end it here.
|
|
2024
2287
|
try {
|
|
2025
2288
|
if ((deferred as any).__piped !== true) {
|
|
2026
|
-
deferred.push({
|
|
2289
|
+
deferred.push({
|
|
2290
|
+
type: "done",
|
|
2291
|
+
delta: "",
|
|
2292
|
+
usage: res.usage,
|
|
2293
|
+
finishReason: res.finishReason,
|
|
2294
|
+
responseId: res.responseId,
|
|
2295
|
+
} as any);
|
|
2027
2296
|
deferred.end(res);
|
|
2028
2297
|
}
|
|
2029
2298
|
} catch {}
|
|
@@ -2045,10 +2314,7 @@ export class Agent {
|
|
|
2045
2314
|
}
|
|
2046
2315
|
|
|
2047
2316
|
// 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 {
|
|
2317
|
+
private wireStreamCallbacks(s: AssistantMessageEventStream, options?: AgentRunOptions): void {
|
|
2052
2318
|
// Wire one-liner callbacks so `stream:true` + onDelta is enough — no manual for-await needed
|
|
2053
2319
|
if (options?.wrapThinking) {
|
|
2054
2320
|
// Auto-wrap reasoning as <think>…</think> per-turn — clean boundaries across multi-turn agent runs.
|
|
@@ -2125,7 +2391,9 @@ export class Agent {
|
|
|
2125
2391
|
const raw: string = e.delta ?? "";
|
|
2126
2392
|
if (!justClosedThink) return raw;
|
|
2127
2393
|
const stripped = raw.replace(/^\n+/, "");
|
|
2128
|
-
try {
|
|
2394
|
+
try {
|
|
2395
|
+
e.delta = stripped;
|
|
2396
|
+
} catch {}
|
|
2129
2397
|
if (!stripped) return "";
|
|
2130
2398
|
justClosedThink = false;
|
|
2131
2399
|
return stripped;
|
|
@@ -2222,11 +2490,13 @@ export class Agent {
|
|
|
2222
2490
|
closeSynthetic();
|
|
2223
2491
|
if (justClosedThink) {
|
|
2224
2492
|
const stripped: string = (e.delta ?? "").replace(/^\n+/, "");
|
|
2225
|
-
try {
|
|
2493
|
+
try {
|
|
2494
|
+
e.delta = stripped;
|
|
2495
|
+
} catch {}
|
|
2226
2496
|
if (!stripped) return;
|
|
2227
2497
|
justClosedThink = false;
|
|
2228
2498
|
}
|
|
2229
|
-
needThinkNewline = !(
|
|
2499
|
+
needThinkNewline = !(e.delta ?? "").endsWith("\n");
|
|
2230
2500
|
});
|
|
2231
2501
|
s.on("tool_call_start" as any, () => {
|
|
2232
2502
|
closeSynthetic();
|
|
@@ -2304,7 +2574,12 @@ export function normalizeThinking(config: AgentConfig, fallbackLevel?: string):
|
|
|
2304
2574
|
try {
|
|
2305
2575
|
const rawModel: any = (config.model as any)?.model ? (config.model as any).model : config.model;
|
|
2306
2576
|
const modelStr = typeof rawModel === "string" ? rawModel : rawModel?.id || "";
|
|
2307
|
-
const provStr =
|
|
2577
|
+
const provStr =
|
|
2578
|
+
typeof rawModel === "object" && rawModel?.provider
|
|
2579
|
+
? rawModel.provider
|
|
2580
|
+
: modelStr.includes("/")
|
|
2581
|
+
? modelStr.split("/")[0]
|
|
2582
|
+
: "";
|
|
2308
2583
|
const actualModelId = modelStr.includes("/") ? modelStr.split("/").slice(1).join("/") : modelStr;
|
|
2309
2584
|
if (actualModelId) {
|
|
2310
2585
|
validateModelThinking(provStr || "openrouter", actualModelId, level);
|