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.
Files changed (57) hide show
  1. package/README.md +10 -17
  2. package/package.json +1 -1
  3. package/src/agent/agent.ts +92 -57
  4. package/src/agent/architecture.md +25 -0
  5. package/src/agent/context.ts +7 -3
  6. package/src/agent/delegation.ts +30 -96
  7. package/src/agent/loop.ts +54 -56
  8. package/src/agent/subagent.ts +4 -4
  9. package/src/agent/worker.ts +1 -3
  10. package/src/architecture.md +28 -0
  11. package/src/index.ts +13 -80
  12. package/src/models/architecture.md +14 -0
  13. package/src/models/catalog-cache.ts +10 -7
  14. package/src/models/catalog.ts +20 -13
  15. package/src/providers/architecture.md +24 -0
  16. package/src/providers/google.ts +24 -4
  17. package/src/providers/openai-compat.ts +29 -7
  18. package/src/providers/openai.ts +22 -6
  19. package/src/providers/openrouter.ts +14 -2
  20. package/src/providers/registry.ts +1 -0
  21. package/src/providers/shared.ts +4 -4
  22. package/src/providers.ts +9 -53
  23. package/src/scheduler/architecture.md +22 -0
  24. package/src/scheduler/scheduler.ts +131 -56
  25. package/src/scheduler/store.ts +274 -54
  26. package/src/scheduler/time.ts +2 -0
  27. package/src/session/architecture.md +19 -0
  28. package/src/session/store.ts +37 -22
  29. package/src/session/usage.ts +1 -0
  30. package/src/streaming/architecture.md +13 -0
  31. package/src/streaming/event-stream.ts +16 -11
  32. package/src/telemetry-file.ts +74 -24
  33. package/src/telemetry.ts +3 -2
  34. package/src/tools/architecture.md +19 -0
  35. package/src/tools/executor.ts +14 -1
  36. package/src/tools/schema.ts +1 -0
  37. package/src/tools/subagent-result.ts +1 -0
  38. package/src/tools/tool.ts +8 -8
  39. package/src/types/agent.ts +22 -20
  40. package/src/types/architecture.md +20 -0
  41. package/src/types/response.ts +8 -8
  42. package/src/types/scheduler.ts +2 -2
  43. package/src/types/tool.ts +5 -5
  44. package/src/update-models.ts +2 -2
  45. package/src/utils/architecture.md +25 -0
  46. package/src/utils/base64.ts +2 -0
  47. package/src/utils/cache.ts +6 -4
  48. package/src/utils/durable.ts +78 -3
  49. package/src/utils/env.ts +1 -1
  50. package/src/utils/errors.ts +4 -0
  51. package/src/utils/headers.ts +7 -4
  52. package/src/utils/home.ts +1 -1
  53. package/src/utils/media.ts +1 -0
  54. package/src/utils/raw-capture.ts +3 -0
  55. package/src/utils/retry.ts +2 -1
  56. package/src/utils/safe-io.ts +4 -0
  57. package/src/utils/session.ts +10 -166
@@ -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
- export { escapeXml };
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
- // Q-34: hash includes() is collision-prone; require delimited match to reduce spoofing. Explicit {rootSessionId,parentSessionId} lineage is Q-61 future.
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) (Q-30). */
107
- export const MAX_SUBAGENT_TRACES = 500;
108
- /** Trace TTL: 1h wall-clock, lazily expired on read (Q-30). */
109
- export const SUBAGENT_TRACE_TTL_MS = 60 * 60 * 1000;
110
- /** Max steps retained per trace; oldest steps are dropped beyond this (Q-30). */
111
- export const MAX_SUBAGENT_STEPS_PER_TRACE = 100;
112
- /** Live subscriptions auto-expire after 5min (Q-30). */
113
- export const SUBAGENT_SUBSCRIBE_TTL_MS = 5 * 60 * 1000;
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 (Q-30).
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
- export function pruneTraces(now = Date.now()): void {
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} (Q-30). */
158
- export function trimTraceSteps(trace: SubAgentTrace): void {
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 (Q-30).
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 (Q-39): counters + guards. The loop charges usage per turn; the
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 (Q-39). Never throws;
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` (Q-39). Increments the
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
- // Q-42 lineage for fixed-delegation calls.
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";