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.
- package/README.md +66 -30
- package/examples/prompts/SYSTEM_PROMPT_AGENT.md +14 -20
- package/package.json +1 -1
- package/src/agent/agent.ts +73 -24
- package/src/agent/context.ts +1 -1
- package/src/agent/delegation.ts +208 -524
- package/src/agent/loop.ts +13 -11
- package/src/agent/subagents-tool.ts +87 -0
- package/src/agent/subagents.md +31 -0
- package/src/agent/turns.ts +60 -0
- package/src/agent/worker.ts +38 -30
- package/src/agent/workers.ts +405 -0
- package/src/index.ts +3 -2
- package/src/session/journal.ts +12 -1
- package/src/session/store.ts +24 -10
- package/src/session/usage.ts +201 -0
- package/src/tools/subagent-result.ts +1 -1
- package/src/types/agent.ts +19 -6
- package/src/types/tool.ts +1 -1
- package/src/utils/headers.ts +1 -1
|
@@ -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` / `
|
|
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).
|
package/src/types/agent.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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 `
|
|
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. */
|
package/src/utils/headers.ts
CHANGED
|
@@ -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.
|
|
26
|
+
return "0.5.4";
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function getAgentAccelUserAgent(): string {
|