agent-accelerator 0.5.6 → 0.5.8
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 +10 -17
- package/package.json +1 -1
- package/src/agent/agent.ts +92 -57
- package/src/agent/architecture.md +25 -0
- package/src/agent/context.ts +7 -3
- package/src/agent/delegation.ts +30 -96
- package/src/agent/loop.ts +54 -56
- package/src/agent/subagent.ts +4 -4
- package/src/agent/worker.ts +1 -3
- package/src/architecture.md +28 -0
- package/src/index.ts +13 -80
- package/src/models/architecture.md +14 -0
- package/src/models/catalog-cache.ts +10 -7
- package/src/models/catalog.ts +20 -13
- package/src/providers/architecture.md +24 -0
- package/src/providers/google.ts +24 -4
- package/src/providers/openai-compat.ts +29 -7
- package/src/providers/openai.ts +22 -6
- package/src/providers/openrouter.ts +14 -2
- package/src/providers/registry.ts +1 -0
- package/src/providers/shared.ts +4 -4
- package/src/providers.ts +9 -53
- package/src/scheduler/architecture.md +22 -0
- package/src/scheduler/scheduler.ts +131 -56
- package/src/scheduler/store.ts +274 -54
- package/src/scheduler/time.ts +2 -0
- package/src/session/architecture.md +19 -0
- package/src/session/store.ts +37 -22
- package/src/session/usage.ts +1 -0
- package/src/streaming/architecture.md +13 -0
- package/src/streaming/event-stream.ts +16 -11
- package/src/telemetry-file.ts +74 -24
- package/src/telemetry.ts +3 -2
- package/src/tools/architecture.md +19 -0
- package/src/tools/executor.ts +14 -1
- package/src/tools/schema.ts +1 -0
- package/src/tools/subagent-result.ts +1 -0
- package/src/tools/tool.ts +8 -8
- package/src/types/agent.ts +22 -20
- package/src/types/architecture.md +20 -0
- package/src/types/response.ts +8 -8
- package/src/types/scheduler.ts +2 -2
- package/src/types/tool.ts +5 -5
- package/src/update-models.ts +2 -2
- package/src/utils/architecture.md +25 -0
- package/src/utils/base64.ts +2 -0
- package/src/utils/cache.ts +6 -4
- package/src/utils/durable.ts +78 -3
- package/src/utils/env.ts +1 -1
- package/src/utils/errors.ts +4 -0
- package/src/utils/headers.ts +7 -4
- package/src/utils/home.ts +1 -1
- package/src/utils/media.ts +1 -0
- package/src/utils/raw-capture.ts +3 -0
- package/src/utils/retry.ts +2 -1
- package/src/utils/safe-io.ts +4 -0
- package/src/utils/session.ts +10 -166
package/src/agent/delegation.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { tool } from "../tools/tool.ts";
|
|
3
|
-
import { escapeXml } from "../utils/serialization.ts";
|
|
4
3
|
import type { ToolDefinition } from "../types/tool.ts";
|
|
5
4
|
import type { AgentResponse, SubAgentExecutionMetadata, SubAgentStep } from "../types/response.ts";
|
|
6
5
|
import type { TokenUsage } from "../types/core.ts";
|
|
@@ -17,8 +16,7 @@ import { newRunId } from "../utils/session.ts";
|
|
|
17
16
|
import { newTurnId } from "../utils/session.ts";
|
|
18
17
|
import { sanitizeToolName, createLiveWorker } from "./worker.ts";
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/** Builds a random child session id that fits the 64-char provider limit, embedding a hash of the parent when it must be cut; in: a parent id + tag from scheduled runs, out: the worker's session id. */
|
|
22
20
|
export function createChildSessionId(parentId: string, tag: string): string {
|
|
23
21
|
let rand: string;
|
|
24
22
|
try {
|
|
@@ -76,7 +74,7 @@ export function isSessionDescendant(child: string, parent: string): boolean {
|
|
|
76
74
|
if (!child || !parent) return false;
|
|
77
75
|
if (child === parent) return true;
|
|
78
76
|
if (child.startsWith(parent + "-")) return true;
|
|
79
|
-
//
|
|
77
|
+
// Hash includes() is collision-prone; require delimited match to reduce spoofing. Explicit {rootSessionId,parentSessionId} lineage is future work.
|
|
80
78
|
const h = hashSessionPart(parent);
|
|
81
79
|
return child.includes(`-${h}-`) || child.endsWith(`-${h}`);
|
|
82
80
|
}
|
|
@@ -103,18 +101,19 @@ export interface SubAgentTrace {
|
|
|
103
101
|
const subagentTraces = new Map<string, SubAgentTrace>();
|
|
104
102
|
const subagentListeners = new Map<string, Set<(trace: SubAgentTrace) => void>>();
|
|
105
103
|
|
|
106
|
-
/** Max trace keys held (LRU, evicts oldest whole trace on insert)
|
|
107
|
-
|
|
108
|
-
/** Trace TTL: 1h wall-clock, lazily expired on read
|
|
109
|
-
|
|
110
|
-
/** Max steps retained per trace; oldest steps are dropped beyond this
|
|
111
|
-
|
|
112
|
-
/** Live subscriptions auto-expire after 5min
|
|
113
|
-
|
|
104
|
+
/** Max trace keys held (LRU, evicts oldest whole trace on insert). */
|
|
105
|
+
const MAX_SUBAGENT_TRACES = 500;
|
|
106
|
+
/** Trace TTL: 1h wall-clock, lazily expired on read. */
|
|
107
|
+
const SUBAGENT_TRACE_TTL_MS = 60 * 60 * 1000;
|
|
108
|
+
/** Max steps retained per trace; oldest steps are dropped beyond this. */
|
|
109
|
+
const MAX_SUBAGENT_STEPS_PER_TRACE = 100;
|
|
110
|
+
/** Live subscriptions auto-expire after 5min. */
|
|
111
|
+
const SUBAGENT_SUBSCRIBE_TTL_MS = 5 * 60 * 1000;
|
|
114
112
|
|
|
115
113
|
/** Last-write wall-clock per canonical trackingId (drives TTL; reads do not extend it). */
|
|
116
114
|
const traceTouchedAt = new Map<string, number>();
|
|
117
115
|
|
|
116
|
+
// Removes a trace under all its keys plus its listeners and TTL stamp; in: pruneTraces/lookupTrace, out: the trace store.
|
|
118
117
|
function deleteTraceByCanonical(canonical: string): void {
|
|
119
118
|
for (const [key, t] of [...subagentTraces]) {
|
|
120
119
|
if (t.trackingId === canonical) subagentTraces.delete(key);
|
|
@@ -123,22 +122,24 @@ function deleteTraceByCanonical(canonical: string): void {
|
|
|
123
122
|
subagentListeners.delete(canonical);
|
|
124
123
|
}
|
|
125
124
|
|
|
125
|
+
// True once a trace was not written for SUBAGENT_TRACE_TTL_MS; in: lookup and prune, out: their expiry decision.
|
|
126
126
|
function isTraceExpired(canonical: string, now = Date.now()): boolean {
|
|
127
127
|
const touched = traceTouchedAt.get(canonical);
|
|
128
128
|
if (touched === undefined) return false;
|
|
129
129
|
return now - touched > SUBAGENT_TRACE_TTL_MS;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Stamps a trace as just written, which restarts its TTL; in: insert and the live worker hooks, out: traceTouchedAt.
|
|
132
133
|
function touchTrace(canonical: string, now = Date.now()): void {
|
|
133
134
|
traceTouchedAt.set(canonical, now);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
/**
|
|
137
|
-
* Evicts expired traces, then oldest whole traces while over budget
|
|
138
|
+
* Evicts expired traces, then oldest whole traces while over budget.
|
|
138
139
|
* Called on every insert. Whole-trace eviction removes both the canonical
|
|
139
140
|
* key and its display-id alias.
|
|
140
141
|
*/
|
|
141
|
-
|
|
142
|
+
function pruneTraces(now = Date.now()): void {
|
|
142
143
|
for (const canonical of [...new Set([...subagentTraces.values()].map((t) => t.trackingId))]) {
|
|
143
144
|
if (isTraceExpired(canonical, now)) deleteTraceByCanonical(canonical);
|
|
144
145
|
}
|
|
@@ -154,13 +155,14 @@ export function pruneTraces(now = Date.now()): void {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
/** Drops oldest steps beyond {@link MAX_SUBAGENT_STEPS_PER_TRACE}
|
|
158
|
-
|
|
158
|
+
/** Drops oldest steps beyond {@link MAX_SUBAGENT_STEPS_PER_TRACE}. */
|
|
159
|
+
function trimTraceSteps(trace: SubAgentTrace): void {
|
|
159
160
|
if (trace.steps.length > MAX_SUBAGENT_STEPS_PER_TRACE) {
|
|
160
161
|
trace.steps.splice(0, trace.steps.length - MAX_SUBAGENT_STEPS_PER_TRACE);
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
// Stores a trace under its tracking id and display id in LRU order, pruning expired and excess traces; in: openWorkerTrace, out: the trace store.
|
|
164
166
|
function insertTrace(trace: SubAgentTrace, displayId: string, now = Date.now()): void {
|
|
165
167
|
pruneTraces(now);
|
|
166
168
|
trimTraceSteps(trace);
|
|
@@ -181,6 +183,7 @@ function insertTrace(trace: SubAgentTrace, displayId: string, now = Date.now()):
|
|
|
181
183
|
pruneTraces(now);
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
// Finds a trace by tracking id, display id or case-insensitive id, dropping expired ones and refreshing LRU order; in: the scoped getters and subscribe, out: the live trace or undefined.
|
|
184
187
|
function lookupTrace(id: string): SubAgentTrace | undefined {
|
|
185
188
|
if (!id) return undefined;
|
|
186
189
|
const direct = subagentTraces.get(id);
|
|
@@ -209,10 +212,12 @@ function lookupTrace(id: string): SubAgentTrace | undefined {
|
|
|
209
212
|
return undefined;
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
// Deep-enough copy of a trace (steps and usage) so callers never see live mutation; in: the getters and notifyTrace, out: a detached SubAgentTrace.
|
|
212
216
|
function snapshotTrace(t: SubAgentTrace): SubAgentTrace {
|
|
213
217
|
return { ...t, steps: t.steps.map((s) => ({ ...s })), usage: t.usage ? { ...t.usage } : undefined };
|
|
214
218
|
}
|
|
215
219
|
|
|
220
|
+
// Sends a snapshot of a trace to its live subscribers, ignoring listener errors; in: the live worker hooks, out: subscriber callbacks.
|
|
216
221
|
function notifyTrace(trackingId: string): void {
|
|
217
222
|
const t = subagentTraces.get(trackingId);
|
|
218
223
|
if (!t) return;
|
|
@@ -224,45 +229,6 @@ function notifyTrace(trackingId: string): void {
|
|
|
224
229
|
}
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
/**
|
|
228
|
-
* Returns a snapshot of one worker's trace by tracking id
|
|
229
|
-
* (`SUBAGENT-NAME-{32hex}` or the raw 32-hex suffix). Used by
|
|
230
|
-
* `agent.track(id)` for realtime inspection.
|
|
231
|
-
*
|
|
232
|
-
* Prefer {@link getSubAgentTraceScoped} when the parent session is known:
|
|
233
|
-
* this compat lookup matches any parent and can confuse sibling workers
|
|
234
|
-
* whose display names collide across sessions.
|
|
235
|
-
*/
|
|
236
|
-
export function getSubAgentTrace(id: string): SubAgentTrace | undefined {
|
|
237
|
-
const t = lookupTrace(id);
|
|
238
|
-
return t ? snapshotTrace(t) : undefined;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Scoped trace lookup (Q-30, preferred): like {@link getSubAgentTrace} but
|
|
243
|
-
* only matches when `trace.parentSessionId === parentSessionId`. Use this
|
|
244
|
-
* from `Agent.track` (or any parent-bound caller) so truncated/aliased ids
|
|
245
|
-
* cannot leak across sessions.
|
|
246
|
-
*/
|
|
247
|
-
export function getSubAgentTraceScoped(id: string, parentSessionId: string): SubAgentTrace | undefined {
|
|
248
|
-
if (!id || !parentSessionId) return undefined;
|
|
249
|
-
const t = lookupTrace(id);
|
|
250
|
-
if (!t) return undefined;
|
|
251
|
-
if (t.parentSessionId !== parentSessionId) return undefined;
|
|
252
|
-
return snapshotTrace(t);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Lists tracking ids of EVERY worker trace in the process (all agents, all
|
|
257
|
-
* sessions). Diagnostics only: `Agent` never uses it, because in a
|
|
258
|
-
* multi-tenant host it would expose other tenants' workers. Use
|
|
259
|
-
* {@link listSubAgentTraceIdsForSessions} for agent-bound callers.
|
|
260
|
-
*/
|
|
261
|
-
export function listSubAgentTraceIds(): string[] {
|
|
262
|
-
pruneTraces();
|
|
263
|
-
return [...subagentTraces.keys()];
|
|
264
|
-
}
|
|
265
|
-
|
|
266
232
|
/** Scoped lookup across a set of parent session ids (an agent's default + named sessions). */
|
|
267
233
|
export function getSubAgentTraceForSessions(
|
|
268
234
|
id: string,
|
|
@@ -302,7 +268,7 @@ export function subscribeToSubAgentForSessions(
|
|
|
302
268
|
|
|
303
269
|
/**
|
|
304
270
|
* Subscribes to live updates of one worker trace. Returns an unsubscribe fn.
|
|
305
|
-
* Subscriptions auto-expire after 5min to avoid listener leaks
|
|
271
|
+
* Subscriptions auto-expire after 5min to avoid listener leaks.
|
|
306
272
|
*/
|
|
307
273
|
export function subscribeToSubAgent(id: string, fn: (trace: SubAgentTrace) => void): () => void {
|
|
308
274
|
const t = lookupTrace(id);
|
|
@@ -326,21 +292,6 @@ export function subscribeToSubAgent(id: string, fn: (trace: SubAgentTrace) => vo
|
|
|
326
292
|
};
|
|
327
293
|
}
|
|
328
294
|
|
|
329
|
-
/** Appends a step to a worker trace and notifies live subscribers. */
|
|
330
|
-
export function appendSubAgentStep(trackingId: string, step: SubAgentStep): void {
|
|
331
|
-
const t = subagentTraces.get(trackingId) ?? lookupTrace(trackingId);
|
|
332
|
-
if (!t) return;
|
|
333
|
-
if (isTraceExpired(t.trackingId)) {
|
|
334
|
-
deleteTraceByCanonical(t.trackingId);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
t.steps.push(step);
|
|
338
|
-
trimTraceSteps(t);
|
|
339
|
-
t.turns = Math.max(t.turns, step.turn);
|
|
340
|
-
touchTrace(t.trackingId);
|
|
341
|
-
notifyTrace(trackingId);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
295
|
// ---------------------------------------------------------------------------
|
|
345
296
|
// One worker run: live trace + execution metadata (shared by fixed workers and the worker registry)
|
|
346
297
|
// ---------------------------------------------------------------------------
|
|
@@ -518,15 +469,17 @@ export function workerMetadata(a: {
|
|
|
518
469
|
}
|
|
519
470
|
|
|
520
471
|
// ---------------------------------------------------------------------------
|
|
521
|
-
// Budgets
|
|
472
|
+
// Budgets: counters + guards. The loop charges usage per turn; the
|
|
522
473
|
// worker registry enforces `maxTotalSubagents` through `reserveSpawn`. Durations use the monotonic `nowMs()`
|
|
523
474
|
// clock; TTL timestamps above use wall-clock `Date.now()`.
|
|
524
475
|
// ---------------------------------------------------------------------------
|
|
525
476
|
|
|
477
|
+
// Reads the agent's configured budget; in: budget helpers, out: Budget or undefined.
|
|
526
478
|
function readBudget(parentAgent: Agent): Budget | undefined {
|
|
527
479
|
return (parentAgent as unknown as { budget?: Budget }).budget;
|
|
528
480
|
}
|
|
529
481
|
|
|
482
|
+
// Returns the agent's per-run budget counters, creating them on first use; in: charge and reserve helpers, out: BudgetState.
|
|
530
483
|
function ensureBudgetState(parentAgent: Agent): BudgetState {
|
|
531
484
|
const host = parentAgent as unknown as { budgetState?: BudgetState };
|
|
532
485
|
if (!host.budgetState) {
|
|
@@ -535,6 +488,7 @@ function ensureBudgetState(parentAgent: Agent): BudgetState {
|
|
|
535
488
|
return host.budgetState;
|
|
536
489
|
}
|
|
537
490
|
|
|
491
|
+
// Total tokens of a usage value, defensively (total, else input + output); in: budget helpers, out: a non-negative integer.
|
|
538
492
|
function usageTokens(usage: unknown): number {
|
|
539
493
|
const u = usage as { totalTokens?: unknown; inputTokens?: unknown; outputTokens?: unknown } | undefined;
|
|
540
494
|
if (!u || typeof u !== "object") return 0;
|
|
@@ -544,6 +498,7 @@ function usageTokens(usage: unknown): number {
|
|
|
544
498
|
return Math.max(0, Math.floor(inp + out));
|
|
545
499
|
}
|
|
546
500
|
|
|
501
|
+
// Reported total cost of a usage value in USD, 0 when absent; in: budget helpers, out: a non-negative number.
|
|
547
502
|
function usageCostUsd(usage: unknown): number {
|
|
548
503
|
const cost = (usage as { cost?: { totalCost?: unknown } } | undefined)?.cost;
|
|
549
504
|
const v = cost?.totalCost;
|
|
@@ -597,7 +552,7 @@ export function checkBudget(
|
|
|
597
552
|
}
|
|
598
553
|
|
|
599
554
|
/**
|
|
600
|
-
* Adds worker usage to the parent's budget counters
|
|
555
|
+
* Adds worker usage to the parent's budget counters. Never throws;
|
|
601
556
|
* call {@link checkBudget} afterwards to enforce.
|
|
602
557
|
*/
|
|
603
558
|
export function chargeBudgetUsage(parentAgent: Agent, usage: unknown): BudgetState {
|
|
@@ -608,7 +563,7 @@ export function chargeBudgetUsage(parentAgent: Agent, usage: unknown): BudgetSta
|
|
|
608
563
|
}
|
|
609
564
|
|
|
610
565
|
/**
|
|
611
|
-
* Reserves `count` spawns against `maxTotalSubagents
|
|
566
|
+
* Reserves `count` spawns against `maxTotalSubagents`. Increments the
|
|
612
567
|
* parent's `budgetState.spawned` and throws {@link BudgetExceededError} when
|
|
613
568
|
* the reservation would exceed the budget. Call before spawning.
|
|
614
569
|
*/
|
|
@@ -634,27 +589,6 @@ export function reserveSpawn(parentAgent: Agent, count = 1): BudgetState {
|
|
|
634
589
|
return state;
|
|
635
590
|
}
|
|
636
591
|
|
|
637
|
-
// ---------------------------------------------------------------------------
|
|
638
|
-
// Single core loop note + streaming-loop guard (Q-44, partial).
|
|
639
|
-
// The canonical run/stream loops live in `loop.ts` + `agent.ts` (other
|
|
640
|
-
// owners) and are intentionally NOT duplicated here. This guard exists so
|
|
641
|
-
// future consolidation can branch on streaming handles without importing
|
|
642
|
-
// the loop.
|
|
643
|
-
// ---------------------------------------------------------------------------
|
|
644
|
-
|
|
645
|
-
/** Minimal shape of a streaming run handle (has subscribe + terminal promise). */
|
|
646
|
-
export interface StreamingLoopHandle {
|
|
647
|
-
on: (...args: unknown[]) => unknown;
|
|
648
|
-
result: (...args: unknown[]) => Promise<unknown>;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/** Type guard for streaming-loop handles (Q-44). */
|
|
652
|
-
export function isStreamingLoop(value: unknown): value is StreamingLoopHandle {
|
|
653
|
-
if (!value || typeof value !== "object") return false;
|
|
654
|
-
const r = value as Record<string, unknown>;
|
|
655
|
-
return typeof r["on"] === "function" && typeof r["result"] === "function";
|
|
656
|
-
}
|
|
657
|
-
|
|
658
592
|
/** Optional name/description wrapper for converting an Agent into a tool. */
|
|
659
593
|
export interface AgentAsToolTarget {
|
|
660
594
|
name?: string;
|
|
@@ -689,7 +623,7 @@ export function agentToTool(
|
|
|
689
623
|
}),
|
|
690
624
|
execute: async ({ task }, ctx) => {
|
|
691
625
|
const startTime = nowMs();
|
|
692
|
-
//
|
|
626
|
+
// Lineage for fixed-delegation calls.
|
|
693
627
|
const callRunId = newRunId();
|
|
694
628
|
const callTurnId = newTurnId();
|
|
695
629
|
const parentSessionId = ctx?.sessionId || agentInstance.sessionId || "session";
|