agent-accelerator 0.5.3 → 0.5.4

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.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * What one agent spent on ITS OWN model calls, kept exactly: tokens as the provider reported them, cost in integer
3
+ * micro-USD priced per turn (so cache hits, cache writes and price tiers are right), plus where each turn's price came
4
+ * from. Every agent records its own turns and saves them in its session; `usageReport()` then adds the agent and its
5
+ * workers up WITHOUT counting anything twice (a parent's `AgentResponse.usage` already contains its workers' usage, so
6
+ * responses are never summed). Pure functions, no Agent import.
7
+ */
8
+ import type { TokenUsage } from "../types/core.ts";
9
+ import type { PricedCost } from "./store.ts";
10
+
11
+ /** Raw totals of one agent's own model calls, as saved in its session (`usage`). */
12
+ export interface UsageTotals {
13
+ /** Model calls made. */
14
+ turns: number;
15
+ /** Input tokens; they INCLUDE the cache reads and writes. */
16
+ inputTokens: number;
17
+ cacheReadTokens: number;
18
+ cacheWriteTokens: number;
19
+ /** Output tokens; they INCLUDE the thinking tokens. */
20
+ outputTokens: number;
21
+ thinkingTokens: number;
22
+ /** Integer micro-USD, summed over the turns that had a price. */
23
+ costMicroUSD: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
24
+ /** Turns by where their price came from: the provider's reported cost, the catalog, or nowhere (unknown pricing). */
25
+ turnsBy: { provider: number; catalog: number; unpriced: number };
26
+ }
27
+
28
+ /** A zeroed {@link UsageTotals}. */
29
+ export function emptyUsage(): UsageTotals {
30
+ return {
31
+ turns: 0,
32
+ inputTokens: 0,
33
+ cacheReadTokens: 0,
34
+ cacheWriteTokens: 0,
35
+ outputTokens: 0,
36
+ thinkingTokens: 0,
37
+ costMicroUSD: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
38
+ turnsBy: { provider: 0, catalog: 0, unpriced: 0 },
39
+ };
40
+ }
41
+
42
+ const count = (n: unknown): number => (typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : 0);
43
+
44
+ /** A model reference (string or catalog spec) as a plain string for a report line. */
45
+ export function modelName(model: unknown): string {
46
+ return typeof model === "string" ? model : String((model as { id?: unknown } | null | undefined)?.id ?? "unknown");
47
+ }
48
+
49
+ /** Adds one model call (`priced` from `priceUsage`; undefined = unknown pricing, which is counted but never turned into $0). */
50
+ export function addTurn(totals: UsageTotals, usage: TokenUsage, priced: PricedCost | undefined): void {
51
+ const input = count(usage.inputTokens);
52
+ const output = count(usage.outputTokens);
53
+ totals.turns += 1;
54
+ totals.inputTokens += input;
55
+ totals.outputTokens += output;
56
+ totals.cacheReadTokens += Math.min(count(usage.cachedTokens ?? usage.cacheReadTokens), input);
57
+ totals.cacheWriteTokens += Math.min(count(usage.cacheWriteTokens), input);
58
+ totals.thinkingTokens += Math.min(count(usage.thinkingTokens), output);
59
+ if (!priced) {
60
+ totals.turnsBy.unpriced += 1;
61
+ return;
62
+ }
63
+ totals.turnsBy[priced.source === "provider" ? "provider" : "catalog"] += 1;
64
+ const b = priced.breakdown;
65
+ totals.costMicroUSD.input += b.inputMicroUSD;
66
+ totals.costMicroUSD.output += b.outputMicroUSD + b.audioMicroUSD;
67
+ totals.costMicroUSD.cacheRead += b.cacheReadMicroUSD;
68
+ totals.costMicroUSD.cacheWrite += b.cacheWriteMicroUSD;
69
+ totals.costMicroUSD.total += b.totalMicroUSD;
70
+ }
71
+
72
+ /** Adds `other` into `into` (workers into a total). */
73
+ export function mergeUsage(into: UsageTotals, other: UsageTotals): void {
74
+ for (const k of ["turns", "inputTokens", "cacheReadTokens", "cacheWriteTokens", "outputTokens", "thinkingTokens"] as const) into[k] += other[k];
75
+ for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) into.costMicroUSD[k] += other.costMicroUSD[k];
76
+ for (const k of ["provider", "catalog", "unpriced"] as const) into.turnsBy[k] += other.turnsBy[k];
77
+ }
78
+
79
+ /** Reads saved totals back defensively (a hand-edited or older file never breaks a load); unusable input gives zeros. */
80
+ export function readUsage(saved: unknown): UsageTotals {
81
+ const out = emptyUsage();
82
+ const s = saved as Partial<UsageTotals> | null | undefined;
83
+ if (!s || typeof s !== "object") return out;
84
+ for (const k of ["turns", "inputTokens", "cacheReadTokens", "cacheWriteTokens", "outputTokens", "thinkingTokens"] as const) out[k] = count(s[k]);
85
+ for (const k of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) out.costMicroUSD[k] = count(s.costMicroUSD?.[k]);
86
+ for (const k of ["provider", "catalog", "unpriced"] as const) out.turnsBy[k] = count(s.turnsBy?.[k]);
87
+ return out;
88
+ }
89
+
90
+ /** Where a total's price came from: `provider` (the provider's reported cost), `catalog`, `mixed` (both), or `unknown` (no turn had a price). */
91
+ export type CostSource = "provider" | "catalog" | "mixed" | "unknown";
92
+
93
+ /** One agent's (or the whole session's) spending in a form made to be read: tokens, cache hit rate, cost. */
94
+ export interface UsageLine {
95
+ /** Model calls made. */
96
+ turns: number;
97
+ tokens: {
98
+ /** Everything sent: `freshInput + cacheRead + cacheWrite`. */
99
+ input: number;
100
+ /** Input that was neither read from nor written to the cache. */
101
+ freshInput: number;
102
+ cacheRead: number;
103
+ cacheWrite: number;
104
+ /** Everything generated, thinking included. */
105
+ output: number;
106
+ /** The part of `output` that was thinking. */
107
+ thinking: number;
108
+ total: number;
109
+ };
110
+ /** `cacheRead / input` from 0 to 1; `null` when nothing was sent. */
111
+ cacheHitRate: number | null;
112
+ cost: {
113
+ source: CostSource;
114
+ /** Turns with no known price: when above 0 the totals are a lower bound. */
115
+ unpricedTurns: number;
116
+ /** USD; `null` when no turn had a price (unknown is never shown as 0). */
117
+ totalUsd: number | null;
118
+ /** The parts, in USD. For turns whose cost the provider reported, only the total is exact: the parts then add up to less than `totalUsd`. Absent when unknown. */
119
+ parts?: { inputUsd: number; outputUsd: number; cacheReadUsd: number; cacheWriteUsd: number };
120
+ };
121
+ }
122
+
123
+ /** Turns raw totals into a readable {@link UsageLine}. */
124
+ export function usageLine(t: UsageTotals): UsageLine {
125
+ const priced = t.turnsBy.provider + t.turnsBy.catalog;
126
+ const source: CostSource = priced === 0 ? "unknown" : t.turnsBy.provider > 0 && t.turnsBy.catalog > 0 ? "mixed" : t.turnsBy.provider > 0 ? "provider" : "catalog";
127
+ const usd = (micro: number): number => micro / 1_000_000;
128
+ const c = t.costMicroUSD;
129
+ return {
130
+ turns: t.turns,
131
+ tokens: {
132
+ input: t.inputTokens,
133
+ freshInput: Math.max(0, t.inputTokens - t.cacheReadTokens - t.cacheWriteTokens),
134
+ cacheRead: t.cacheReadTokens,
135
+ cacheWrite: t.cacheWriteTokens,
136
+ output: t.outputTokens,
137
+ thinking: t.thinkingTokens,
138
+ total: t.inputTokens + t.outputTokens,
139
+ },
140
+ cacheHitRate: t.inputTokens > 0 ? t.cacheReadTokens / t.inputTokens : null,
141
+ cost: {
142
+ source,
143
+ unpricedTurns: t.turnsBy.unpriced,
144
+ totalUsd: priced === 0 ? null : usd(c.total),
145
+ ...(priced === 0 ? {} : { parts: { inputUsd: usd(c.input), outputUsd: usd(c.output), cacheReadUsd: usd(c.cacheRead), cacheWriteUsd: usd(c.cacheWrite) } }),
146
+ },
147
+ };
148
+ }
149
+
150
+ /** One worker in the usage tree, with raw totals (the report turns them into lines). */
151
+ export interface UsageNode {
152
+ id: string;
153
+ name: string;
154
+ role?: string;
155
+ model: string;
156
+ status: "open" | "closed" | "fixed";
157
+ totals: UsageTotals;
158
+ workers: UsageNode[];
159
+ }
160
+
161
+ /** An agent or worker line of a {@link UsageReport}. */
162
+ export interface AgentUsage extends UsageLine {
163
+ id: string;
164
+ name: string;
165
+ model: string;
166
+ }
167
+
168
+ /** A worker line, with the workers it started (`open` and `closed` are `subagents`/`agent.workers` workers, `fixed` is a `subagents: [...]` worker). */
169
+ export interface WorkerUsage extends AgentUsage {
170
+ role?: string;
171
+ status: "open" | "closed" | "fixed";
172
+ workers: WorkerUsage[];
173
+ }
174
+
175
+ /** What `agent.usageReport()` returns. `agent` is the agent's own model calls only; `total` is the agent plus every worker below it. */
176
+ export interface UsageReport {
177
+ agent: AgentUsage;
178
+ workers: WorkerUsage[];
179
+ total: UsageLine;
180
+ }
181
+
182
+ // Adds a node and everything below it into `into`; in: a usage tree, out: the whole-session total.
183
+ function sumTree(into: UsageTotals, nodes: UsageNode[]): void {
184
+ for (const n of nodes) {
185
+ mergeUsage(into, n.totals);
186
+ sumTree(into, n.workers);
187
+ }
188
+ }
189
+
190
+ // One node as a report line, children included; in: a usage node, out: UsageReport.workers.
191
+ function nodeLine(n: UsageNode): WorkerUsage {
192
+ return { id: n.id, name: n.name, ...(n.role ? { role: n.role } : {}), model: n.model, status: n.status, ...usageLine(n.totals), workers: n.workers.map(nodeLine) };
193
+ }
194
+
195
+ /** Builds the report from the agent's own totals and its worker tree. */
196
+ export function buildUsageReport(own: { id: string; name: string; model: string; totals: UsageTotals }, workers: UsageNode[]): UsageReport {
197
+ const total = emptyUsage();
198
+ mergeUsage(total, own.totals);
199
+ sumTree(total, workers);
200
+ return { agent: { id: own.id, name: own.name, model: own.model, ...usageLine(own.totals) }, workers: workers.map(nodeLine), total: usageLine(total) };
201
+ }
@@ -2,7 +2,7 @@ import type { SubAgentExecutionMetadata } from "../types/response.ts";
2
2
 
3
3
  /**
4
4
  * Module-private brand proving a tool result was produced by the SDK's own
5
- * delegation tools (`agentToTool` / `spawn_subagents`). Symbols cannot be
5
+ * delegation tools (`agentToTool` / the `subagents` tool). Symbols cannot be
6
6
  * forged by JSON a tool fetched from the web, so only branded results get
7
7
  * their `_subagentMetadata` trusted (usage/cost rolled up, XML shown to the
8
8
  * model).
@@ -278,7 +278,7 @@ export interface MidSessionConfig {
278
278
 
279
279
  /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
280
280
  export interface DynamicSubagentsConfig {
281
- /** Enables the automatic `spawn_subagents` tool. Defaults to true when the object is present. */
281
+ /** Adds the `subagents` tool (spawn, message, close, list persistent workers). Defaults to true when the object is present; `agent.workers` works either way. */
282
282
  enabled?: boolean;
283
283
  /**
284
284
  * Lets the Main Agent schedule dynamic sub-agents to run later (once or on
@@ -288,22 +288,35 @@ export interface DynamicSubagentsConfig {
288
288
  allowScheduledActions?: boolean;
289
289
  /** Developer-only worker model override. Never choosable by the Main Agent LLM. Falls back to top-level `subagentModel`, then `SUB_AGENT_MODEL` env, then the parent model. */
290
290
  model?: string | ModelSpec | ModelProviderInstance;
291
- /** Max workers the Main Agent may spawn in a single `spawn_subagents` call. Extras are trimmed safely. Defaults to 4. */
291
+ /** Max workers that may be open at once on this agent (the model must close one before spawning another). Defaults to 4. */
292
292
  maxSpawn?: number;
293
293
  /** Fixed reasoning level for all dynamic workers. The Main Agent cannot override it. Falls back to the parent level when omitted. */
294
294
  thinkingLevel?: ThinkingLevel;
295
+ /**
296
+ * Spending cap of EACH worker (the `subagents` tool, `agent.workers` and scheduled actions), set as that worker's own
297
+ * per-run `budget`: a spawn, a message or a scheduled run is one run, and the usage of the worker's own workers counts
298
+ * toward it. Exceeding it fails that call with a budget error and leaves the worker open. Default: unlimited. Models
299
+ * with unknown pricing count $0 toward `maxCostUsd`. Passed down to the workers' own workers.
300
+ */
301
+ workerBudget?: Pick<Budget, "maxCostUsd" | "maxTotalTokens">;
302
+ /**
303
+ * How many levels of workers may exist below this agent (default 3). `1` = workers cannot start workers of their own;
304
+ * `3` = a worker may start workers that start workers. A worker with levels left gets its own `subagents` tool, and
305
+ * its children can only be granted the tools the worker itself was granted. Whole number >= 1.
306
+ */
307
+ maxDepth?: number;
295
308
  /** Tool pool available to dynamic workers. Workers receive zero tools unless the Main Agent grants a per-task `tools` subset by name. */
296
309
  tools?: Record<string, ToolDefinition> | ToolDefinition[];
297
- /** Per-worker timeout in ms. `0` (default) = no limit. `-1` = the Main Agent sets a per-task `timeoutMs`. `>0` = fixed timeout for every worker. */
310
+ /** Per-run timeout in ms of a SCHEDULED ACTION's worker (the `subagents` tool and `agent.workers` have no timeouts). `0` (default) = no limit. `-1` = the action sets its own `timeoutMs`. `>0` = fixed. */
298
311
  timeout?: number;
299
312
  /**
300
- * Canonical per-worker timeout in ms (Q-52, preferred over `timeout`).
313
+ * Canonical timeout in ms for scheduled-action workers (Q-52, preferred over `timeout`).
301
314
  * Maps to `timeout`: `workerTimeoutMs ?? timeout ?? 0`. See
302
315
  * `normalizeWorkerTimeout` (re-exported from the root entry).
303
316
  */
304
317
  workerTimeoutMs?: number;
305
318
  /**
306
- * When true, the Main Agent sets a per-task `timeoutMs` (maps to
319
+ * Scheduled actions only: when true, each action sets its own `timeoutMs` (maps to
307
320
  * `timeout: -1`). When both are set, an explicit `timeout`/`workerTimeoutMs`
308
321
  * wins over `modelMaySetTimeout`.
309
322
  */
@@ -389,7 +402,7 @@ export interface Budget {
389
402
  maxTotalTokens?: number;
390
403
  /** Maximum wall/monotonic duration in ms since budget tracking started. */
391
404
  maxDurationMs?: number;
392
- /** Maximum total dynamic sub-agents spawned via `spawn_subagents`. */
405
+ /** Maximum total dynamic sub-agents spawned in one run (`subagents` tool or `agent.workers.spawn`). */
393
406
  maxTotalSubagents?: number;
394
407
  }
395
408
 
package/src/types/tool.ts CHANGED
@@ -20,7 +20,7 @@ export interface ToolExecutionContext {
20
20
  signal?: AbortSignal;
21
21
  /** Session identifier associated with this invocation. */
22
22
  sessionId?: string;
23
- /** Forwards live sub-agent deltas (used by `spawn_subagents` for realtime tracking). */
23
+ /** Forwards live sub-agent deltas (used by the `subagents` tool for realtime tracking). */
24
24
  onSubagentEvent?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
25
25
  // Q-55 Tool API v2 context additions (all optional, additive).
26
26
  /** Stable id for this run (`newRunId`). Mirrors SubAgentRunIds.runId. */
@@ -23,7 +23,7 @@ export function getPackageVersion(): string {
23
23
  const v = (globalThis as any).process?.env?.npm_package_version;
24
24
  if (typeof v === "string" && v) return v;
25
25
  } catch {}
26
- return "0.5.3";
26
+ return "0.5.4";
27
27
  }
28
28
 
29
29
  function getAgentAccelUserAgent(): string {