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/subagent.ts
CHANGED
|
@@ -16,9 +16,7 @@ export interface SubAgentConfig extends AgentConfig {}
|
|
|
16
16
|
* `Agent.fromEnv()` (attached below without touching `agent.ts`, owned by
|
|
17
17
|
* another audit lane) delegates here.
|
|
18
18
|
*/
|
|
19
|
-
export function agentFromEnv(
|
|
20
|
-
config?: Omit<AgentConfig, "model"> & { model?: AgentConfig["model"] }
|
|
21
|
-
): Agent {
|
|
19
|
+
export function agentFromEnv(config?: Omit<AgentConfig, "model"> & { model?: AgentConfig["model"] }): Agent {
|
|
22
20
|
return new Agent({
|
|
23
21
|
...config,
|
|
24
22
|
model: config?.model ?? getModel() ?? getSubModel(),
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The model-facing `subagents` tool: one flat tool with a `command` field (`spawn | message | close | list`), a thin
|
|
3
|
-
* wrapper over `agent.workers`. The tool manual lives in `subagents.md`, not in code. Results are plain text with one
|
|
3
|
+
* wrapper over `agent.workers`. The tool manual lives in `src/prompts/subagents-tool.md`, not in code. Results are plain text with one
|
|
4
4
|
* header line naming the worker; the branded envelope under them carries usage and lineage to the loop.
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
7
6
|
import { z } from "zod";
|
|
8
7
|
import type { Agent } from "./agent.ts";
|
|
9
8
|
import { tool } from "../tools/tool.ts";
|
|
@@ -11,18 +10,15 @@ import { makeSubagentResult } from "../tools/subagent-result.ts";
|
|
|
11
10
|
import { ValidationError } from "../types/errors.ts";
|
|
12
11
|
import type { ToolDefinition } from "../types/tool.ts";
|
|
13
12
|
import type { WorkerReply } from "./workers.ts";
|
|
13
|
+
import { fillPrompt, type Prompts } from "../prompts/prompts.ts";
|
|
14
14
|
|
|
15
15
|
const COMMANDS = ["spawn", "message", "close", "list"] as const;
|
|
16
16
|
|
|
17
|
-
let manual: string | undefined;
|
|
18
|
-
/** Read on first use (not at import), so a program that never enables workers never touches the file. */
|
|
19
|
-
function loadManual(): string {
|
|
20
|
-
return (manual ??= readFileSync(new URL("./subagents.md", import.meta.url), "utf8"));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
// One worker reply as the model sees it: a header line naming the worker, then its text; in: a WorkerReply, out: the branded result.
|
|
24
|
-
function asResult(reply: WorkerReply): ReturnType<typeof makeSubagentResult> {
|
|
25
|
-
const text = reply.error
|
|
18
|
+
function asResult(reply: WorkerReply, prompts: Prompts): ReturnType<typeof makeSubagentResult> {
|
|
19
|
+
const text = reply.error
|
|
20
|
+
? fillPrompt(prompts.workerFailed, { id: reply.id, reason: reply.error })
|
|
21
|
+
: fillPrompt(prompts.workerReplied, { id: reply.id, text: reply.text });
|
|
26
22
|
return makeSubagentResult(text, [reply.metadata]);
|
|
27
23
|
}
|
|
28
24
|
|
|
@@ -32,9 +28,12 @@ export function createSubagentsTool(agent: Agent): ToolDefinition {
|
|
|
32
28
|
const max = agent.dynamicSubagents?.maxSpawn ?? Infinity;
|
|
33
29
|
return tool({
|
|
34
30
|
name: "subagents",
|
|
35
|
-
description:
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
description: fillPrompt(agent.prompts.subagentsTool, {
|
|
32
|
+
LIMIT: Number.isFinite(max)
|
|
33
|
+
? `You can have at most ${max} open workers. Close the ones you are done with.`
|
|
34
|
+
: "There is no limit on open workers. Close the ones you are done with: each one keeps its whole conversation.",
|
|
35
|
+
TOOLS: poolNames.length > 0 ? poolNames.join(", ") : "none (omit tools)",
|
|
36
|
+
}),
|
|
38
37
|
input: z.object({
|
|
39
38
|
command: z.enum(COMMANDS),
|
|
40
39
|
id: z.string().optional().describe("The worker's full id (message, close)."),
|
|
@@ -42,15 +41,28 @@ export function createSubagentsTool(agent: Agent): ToolDefinition {
|
|
|
42
41
|
role: z.string().optional().describe("Short persona for the worker (spawn)."),
|
|
43
42
|
instructions: z.string().optional().describe("The worker's own system prompt (spawn)."),
|
|
44
43
|
task: z.string().optional().describe("The assignment (spawn) or the next message to the worker (message)."),
|
|
45
|
-
tools: z
|
|
46
|
-
|
|
44
|
+
tools: z
|
|
45
|
+
.array(z.string())
|
|
46
|
+
.optional()
|
|
47
|
+
.describe(`Worker tools to grant, from: ${poolNames.join(", ") || "none"} (spawn).`),
|
|
48
|
+
history: z
|
|
49
|
+
.number()
|
|
50
|
+
.int()
|
|
51
|
+
.min(0)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe(
|
|
54
|
+
"How many of the first finished turns of this conversation to copy into the worker; 0 or omitted = none (spawn)."
|
|
55
|
+
),
|
|
47
56
|
}),
|
|
48
57
|
execute: async (a, ctx) => {
|
|
49
58
|
const opts = { signal: ctx.signal, callId: ctx.toolCallId, onSubagentEvent: ctx.onSubagentEvent };
|
|
50
59
|
const need = (field: "id" | "name" | "task"): string => {
|
|
51
60
|
const value = a[field];
|
|
52
61
|
if (!value?.trim()) {
|
|
53
|
-
const example =
|
|
62
|
+
const example =
|
|
63
|
+
a.command === "spawn"
|
|
64
|
+
? { command: "spawn", name: "researcher", task: "Do the work." }
|
|
65
|
+
: { command: a.command, id: "researcher-1a2b3c4d", task: "Next question." };
|
|
54
66
|
throw new ValidationError(`${a.command} needs "${field}". Example: ${JSON.stringify(example)}`);
|
|
55
67
|
}
|
|
56
68
|
return value;
|
|
@@ -59,10 +71,21 @@ export function createSubagentsTool(agent: Agent): ToolDefinition {
|
|
|
59
71
|
switch (a.command) {
|
|
60
72
|
case "spawn":
|
|
61
73
|
return asResult(
|
|
62
|
-
await agent.workers.spawn(
|
|
74
|
+
await agent.workers.spawn(
|
|
75
|
+
{
|
|
76
|
+
name: need("name"),
|
|
77
|
+
role: a.role,
|
|
78
|
+
instructions: a.instructions,
|
|
79
|
+
task: need("task"),
|
|
80
|
+
tools: a.tools,
|
|
81
|
+
history: a.history,
|
|
82
|
+
},
|
|
83
|
+
opts
|
|
84
|
+
),
|
|
85
|
+
agent.prompts
|
|
63
86
|
);
|
|
64
87
|
case "message":
|
|
65
|
-
return asResult(await agent.workers.message(need("id"), need("task"), opts));
|
|
88
|
+
return asResult(await agent.workers.message(need("id"), need("task"), opts), agent.prompts);
|
|
66
89
|
case "close":
|
|
67
90
|
return `Worker ${agent.workers.close(need("id")).id} closed.`;
|
|
68
91
|
case "list":
|
|
@@ -81,7 +104,15 @@ export function createSubagentsTool(agent: Agent): ToolDefinition {
|
|
|
81
104
|
function describeWorkers(agent: Agent): string {
|
|
82
105
|
const workers = agent.workers.list();
|
|
83
106
|
const turns = agent.workers.turns();
|
|
84
|
-
const open =
|
|
107
|
+
const open =
|
|
108
|
+
workers.length === 0
|
|
109
|
+
? "none"
|
|
110
|
+
: workers
|
|
111
|
+
.map(
|
|
112
|
+
(w) =>
|
|
113
|
+
`- ${w.id} (${w.name}${w.role ? `, ${w.role}` : ""}) tools: ${w.tools.join(", ") || "none"}${w.busy ? " [busy]" : ""}`
|
|
114
|
+
)
|
|
115
|
+
.join("\n");
|
|
85
116
|
const copyable = turns.length === 0 ? "none yet" : turns.map((t) => `${t.n}. ${t.prompt}`).join("\n");
|
|
86
117
|
return `Open workers:\n${open}\n\nFinished turns of this conversation (history: N copies turns 1 to N):\n${copyable}`;
|
|
87
118
|
}
|
package/src/agent/turns.ts
CHANGED
|
@@ -53,7 +53,9 @@ export function historyUpTo(messages: Message[], marks: SessionRunMark[], n: num
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
if (n === 0) return [];
|
|
56
|
-
const first = marks
|
|
56
|
+
const first = marks
|
|
57
|
+
.filter((m) => m.i >= 0 && m.i < messages.length)
|
|
58
|
+
.reduce((lo, m) => Math.min(lo, m.i), messages.length);
|
|
57
59
|
const kept = [...messages.slice(0, first)];
|
|
58
60
|
for (const turn of turns.slice(0, n)) kept.push(...messages.slice(turn.from, turn.to));
|
|
59
61
|
return deepCloneValue(kept);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chat-wide spending cap (`AgentConfig.usageLimit`). One `UsageLimit` belongs to the root agent of a chat and is
|
|
3
|
+
* handed to every agent below it (registry workers at any depth, fixed `subagents` workers), so each model turn of the
|
|
4
|
+
* whole tree adds to one running total and every turn is checked before it starts. The total survives a restart: the
|
|
5
|
+
* first run rebuilds the starting point from the saved sessions of the agent and its workers (`usageReport()`), after
|
|
6
|
+
* which only new turns are added. Scheduled actions are separate and never count.
|
|
7
|
+
*/
|
|
8
|
+
import { BudgetExceededError } from "../types/errors.ts";
|
|
9
|
+
|
|
10
|
+
/** What the cap is made of; either part may be set, the first one reached stops the chat. */
|
|
11
|
+
export interface UsageLimitConfig {
|
|
12
|
+
maxCostUsd?: number;
|
|
13
|
+
maxTotalTokens?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A one-line problem with a `usageLimit` value, or undefined when it is valid; in: the Agent constructor, out: its ConfigError. */
|
|
17
|
+
export function checkUsageLimitConfig(raw: unknown): string | undefined {
|
|
18
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
19
|
+
return "expected an object like { maxCostUsd, maxTotalTokens }";
|
|
20
|
+
const r = raw as Record<string, unknown>;
|
|
21
|
+
const keys = Object.keys(r).filter((k) => r[k] !== undefined);
|
|
22
|
+
if (keys.length === 0) return "set maxCostUsd, maxTotalTokens or both";
|
|
23
|
+
for (const k of keys) {
|
|
24
|
+
if (k !== "maxCostUsd" && k !== "maxTotalTokens") return `unknown key "${k}" (use maxCostUsd or maxTotalTokens)`;
|
|
25
|
+
const v = r[k];
|
|
26
|
+
if (!(typeof v === "number" && Number.isFinite(v) && v > 0)) return `${k} must be a number greater than 0`;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The running total of one chat and its limit. */
|
|
32
|
+
export class UsageLimit {
|
|
33
|
+
private baseTokens = 0;
|
|
34
|
+
private baseMicroUSD = 0;
|
|
35
|
+
private addedTokens = 0;
|
|
36
|
+
private addedMicroUSD = 0;
|
|
37
|
+
private started = false;
|
|
38
|
+
|
|
39
|
+
constructor(readonly limit: UsageLimitConfig) {}
|
|
40
|
+
|
|
41
|
+
/** Takes the starting point (what the chat spent before this process) once; later calls do nothing until `restart()`. */
|
|
42
|
+
begin(startingPoint: () => { tokens: number; microUSD: number }): void {
|
|
43
|
+
if (this.started) return;
|
|
44
|
+
this.started = true;
|
|
45
|
+
const s = startingPoint();
|
|
46
|
+
this.baseTokens = s.tokens;
|
|
47
|
+
this.baseMicroUSD = s.microUSD;
|
|
48
|
+
this.addedTokens = 0;
|
|
49
|
+
this.addedMicroUSD = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Forgets the total (a session was imported over this agent): the next `begin` takes a new starting point. */
|
|
53
|
+
restart(): void {
|
|
54
|
+
this.started = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Adds one model turn of any agent in the chat. Unpriced turns add tokens only (unknown pricing is never a made-up $0 of cost). */
|
|
58
|
+
add(tokens: number, microUSD: number): void {
|
|
59
|
+
this.addedTokens += tokens;
|
|
60
|
+
this.addedMicroUSD += microUSD;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** What the chat has spent so far. */
|
|
64
|
+
spent(): { tokens: number; costUsd: number } {
|
|
65
|
+
return {
|
|
66
|
+
tokens: this.baseTokens + this.addedTokens,
|
|
67
|
+
costUsd: (this.baseMicroUSD + this.addedMicroUSD) / 1_000_000,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Throws a `BudgetExceededError` ("Usage limit reached") once the chat has spent its whole limit. */
|
|
72
|
+
check(): void {
|
|
73
|
+
const { tokens, costUsd } = this.spent();
|
|
74
|
+
const { maxCostUsd, maxTotalTokens } = this.limit;
|
|
75
|
+
const byCost = maxCostUsd !== undefined && costUsd >= maxCostUsd;
|
|
76
|
+
const byTokens = maxTotalTokens !== undefined && tokens >= maxTotalTokens;
|
|
77
|
+
if (!byCost && !byTokens) return;
|
|
78
|
+
const parts = [
|
|
79
|
+
...(maxCostUsd !== undefined ? [`$${costUsd.toFixed(6)} of $${maxCostUsd}`] : []),
|
|
80
|
+
...(maxTotalTokens !== undefined ? [`${tokens} of ${maxTotalTokens} tokens`] : []),
|
|
81
|
+
];
|
|
82
|
+
throw new BudgetExceededError(
|
|
83
|
+
`[Agent Accelerator] Usage limit reached: ${parts.join(", ")} spent by this chat (the agent and all its workers).`,
|
|
84
|
+
{
|
|
85
|
+
context: {
|
|
86
|
+
limit: "usageLimit",
|
|
87
|
+
partial: { usage: { inputTokens: 0, outputTokens: 0, totalTokens: tokens }, turns: 0 },
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/agent/worker.ts
CHANGED
|
@@ -30,14 +30,23 @@ export interface LiveWorkerTrace {
|
|
|
30
30
|
export function sanitizeXmlTag(raw: string): string {
|
|
31
31
|
let s = raw.toUpperCase().replace(/[^A-Z0-9.-]/g, "-");
|
|
32
32
|
if (!/^[A-Z]/.test(s)) s = `AGENT-${s}`;
|
|
33
|
-
s = s
|
|
33
|
+
s = s
|
|
34
|
+
.replace(/[.]/g, "-")
|
|
35
|
+
.replace(/-{2,}/g, "-")
|
|
36
|
+
.replace(/^-+|-+$/g, "");
|
|
34
37
|
return s.slice(0, 64) || "SUBAGENT";
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/** Sanitizes a worker name into a tool name (`lower-kebab`, 64 chars). */
|
|
38
41
|
export function sanitizeToolName(raw: string): string {
|
|
39
42
|
const tag = sanitizeXmlTag(raw);
|
|
40
|
-
return
|
|
43
|
+
return (
|
|
44
|
+
tag
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
47
|
+
.replace(/-{2,}/g, "-")
|
|
48
|
+
.slice(0, 64) || "sub-agent"
|
|
49
|
+
);
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
// ---------------------------------------------------------------------------
|
|
@@ -91,13 +100,8 @@ export function resolveMainModel(configModel: AgentConfig["model"]): {
|
|
|
91
100
|
* `dynamicSubagents.model ?? subagentModel ?? SUB_AGENT_MODEL`.
|
|
92
101
|
* Unwraps `ModelProviderInstance` / `ModelSpec` the same way.
|
|
93
102
|
*/
|
|
94
|
-
export function resolveDynamicWorkerModel(
|
|
95
|
-
dynModelRawInput
|
|
96
|
-
): string | Record<string, unknown> | undefined {
|
|
97
|
-
const dynModelRaw = dynModelRawInput as
|
|
98
|
-
| { model?: unknown; id?: unknown; provider?: unknown }
|
|
99
|
-
| string
|
|
100
|
-
| undefined;
|
|
103
|
+
export function resolveDynamicWorkerModel(dynModelRawInput: unknown): string | Record<string, unknown> | undefined {
|
|
104
|
+
const dynModelRaw = dynModelRawInput as { model?: unknown; id?: unknown; provider?: unknown } | string | undefined;
|
|
101
105
|
let dynModel: string | Record<string, unknown> | undefined;
|
|
102
106
|
if (dynModelRaw && typeof dynModelRaw === "object" && "model" in (dynModelRaw as Record<string, unknown>)) {
|
|
103
107
|
dynModel = (dynModelRaw as { model: string }).model as unknown as string | Record<string, unknown>;
|
|
@@ -109,7 +113,7 @@ export function resolveDynamicWorkerModel(
|
|
|
109
113
|
) {
|
|
110
114
|
dynModel = dynModelRaw as Record<string, unknown>;
|
|
111
115
|
} else {
|
|
112
|
-
dynModel = (dynModelRaw as string | undefined) || getSubModel() as string | undefined;
|
|
116
|
+
dynModel = (dynModelRaw as string | undefined) || (getSubModel() as string | undefined);
|
|
113
117
|
}
|
|
114
118
|
return dynModel;
|
|
115
119
|
}
|
|
@@ -132,7 +136,10 @@ export function resolveDynamicModelInput(
|
|
|
132
136
|
* `config.model ?? SUB_AGENT_MODEL ?? MODEL`, throwing with the same message
|
|
133
137
|
* when nothing resolves. Kept here so `Agent` / `SubAgent` share one chain.
|
|
134
138
|
*/
|
|
135
|
-
export function resolveSubAgentModel(config: {
|
|
139
|
+
export function resolveSubAgentModel(config: {
|
|
140
|
+
name?: string;
|
|
141
|
+
model?: AgentConfig["model"];
|
|
142
|
+
}): NonNullable<AgentConfig["model"]> {
|
|
136
143
|
const resolvedModel = config.model ?? getSubModel() ?? getModel();
|
|
137
144
|
if (!resolvedModel) {
|
|
138
145
|
throw new ConfigError(
|
|
@@ -191,8 +198,10 @@ export function grantWorkerTools(
|
|
|
191
198
|
if (declared) poolByNormalized.set(normalizeToolName(declared), def);
|
|
192
199
|
}
|
|
193
200
|
const grantedTools: Record<string, ToolDefinition> = {};
|
|
194
|
-
for (const toolName of (
|
|
195
|
-
const pooled =
|
|
201
|
+
for (const toolName of (requested as string[] | undefined) ?? []) {
|
|
202
|
+
const pooled =
|
|
203
|
+
(toolPool as Record<string, ToolDefinition>)[toolName] ??
|
|
204
|
+
poolByNormalized.get(normalizeToolName(String(toolName)));
|
|
196
205
|
if (pooled) grantedTools[(pooled as ToolDefinition).name || toolName] = pooled;
|
|
197
206
|
}
|
|
198
207
|
return grantedTools;
|
|
@@ -205,9 +214,9 @@ function providerOf(modelStr: string | unknown | undefined): string | undefined
|
|
|
205
214
|
const m =
|
|
206
215
|
typeof modelStr === "string"
|
|
207
216
|
? modelStr
|
|
208
|
-
: (modelStr as { model?: unknown; id?: unknown })?.model ??
|
|
217
|
+
: ((modelStr as { model?: unknown; id?: unknown })?.model ??
|
|
209
218
|
(modelStr as { id?: unknown })?.id ??
|
|
210
|
-
String(modelStr);
|
|
219
|
+
String(modelStr));
|
|
211
220
|
return resolveModel(m as string).provider.id;
|
|
212
221
|
} catch {
|
|
213
222
|
return undefined;
|
|
@@ -225,9 +234,9 @@ export function resolveWorkerCredentials(
|
|
|
225
234
|
const chosenModelStrForProvider =
|
|
226
235
|
typeof chosenModel === "string"
|
|
227
236
|
? chosenModel
|
|
228
|
-
: (chosenModel as { model?: unknown; id?: unknown })?.model ??
|
|
237
|
+
: ((chosenModel as { model?: unknown; id?: unknown })?.model ??
|
|
229
238
|
(chosenModel as { id?: unknown })?.id ??
|
|
230
|
-
String(chosenModel);
|
|
239
|
+
String(chosenModel));
|
|
231
240
|
const attemptedProvider = providerOf(chosenModelStrForProvider);
|
|
232
241
|
const parentProvider = providerOf(parentAgent.modelStringOrSpec as string);
|
|
233
242
|
const targetProviderFinal = providerOf(chosenModelStrForProvider);
|
|
@@ -237,6 +246,14 @@ export function resolveWorkerCredentials(
|
|
|
237
246
|
return { apiKeyToUse: undefined, baseUrlToUse: undefined, attemptedProvider };
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
// Configs built by `buildDynamicWorkerConfig`: the Agent constructor checks it so SDK-built workers never take `configure()` or env defaults.
|
|
250
|
+
const workerConfigs = new WeakSet<object>();
|
|
251
|
+
|
|
252
|
+
/** True for a config made by `buildDynamicWorkerConfig` (a chat worker or a scheduled run), not by the developer. */
|
|
253
|
+
export function isWorkerConfig(config: object): boolean {
|
|
254
|
+
return workerConfigs.has(config);
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
/** How many levels of workers may exist below an agent unless `dynamicSubagents.maxDepth` says otherwise: unlimited. */
|
|
241
258
|
export const DEFAULT_MAX_DEPTH = Infinity;
|
|
242
259
|
|
|
@@ -247,7 +264,7 @@ export const DEFAULT_MAX_DEPTH = Infinity;
|
|
|
247
264
|
* `persistent` makes a multi-turn worker that keeps its history (saved under `persistent.dir` when given, and
|
|
248
265
|
* continued by `resume()` after a crash, never automatically). A persistent worker with levels left below it
|
|
249
266
|
* (`maxDepth` of the parent, minus one for itself) gets its own `subagents` tool; its children can only be granted
|
|
250
|
-
* the tools it was granted. One-shot workers never spawn. `dynamicSubagents.workerBudget` becomes each worker's own
|
|
267
|
+
* the tools it was granted. One-shot workers never spawn. `dynamicSubagents.workerBudget` becomes each chat worker's own
|
|
251
268
|
* per-run `budget` (and is passed to its children).
|
|
252
269
|
*/
|
|
253
270
|
export function buildDynamicWorkerConfig(
|
|
@@ -265,7 +282,7 @@ export function buildDynamicWorkerConfig(
|
|
|
265
282
|
const { apiKeyToUse, baseUrlToUse } = resolveWorkerCredentials(parent, model);
|
|
266
283
|
const dyn = parent.dynamicSubagents;
|
|
267
284
|
const levelsBelow = (dyn?.maxDepth ?? DEFAULT_MAX_DEPTH) - 1;
|
|
268
|
-
|
|
285
|
+
const config: AgentConfig = {
|
|
269
286
|
name: worker.name,
|
|
270
287
|
description: worker.role || `Sub-agent ${worker.name}`,
|
|
271
288
|
instructions: worker.instructions,
|
|
@@ -275,7 +292,11 @@ export function buildDynamicWorkerConfig(
|
|
|
275
292
|
thinkingLevel: parent.dynamicSubagents?.thinkingLevel ?? parent.thinkingConfig?.level,
|
|
276
293
|
sessionId: worker.sessionId,
|
|
277
294
|
tools: worker.tools,
|
|
278
|
-
|
|
295
|
+
// A chat worker (persistent) is capped by `workerBudget`; a one-shot scheduled run only by `scheduledActionBudget`.
|
|
296
|
+
...(() => {
|
|
297
|
+
const cap = worker.persistent ? dyn?.workerBudget : dyn?.scheduledActionBudget;
|
|
298
|
+
return cap ? { budget: { ...cap } } : {};
|
|
299
|
+
})(),
|
|
279
300
|
stateless: !worker.persistent,
|
|
280
301
|
...(worker.persistent?.dir ? { persist: { dir: worker.persistent.dir }, resume: "explicit" as const } : {}),
|
|
281
302
|
...(worker.persistent && levelsBelow >= 1
|
|
@@ -296,6 +317,8 @@ export function buildDynamicWorkerConfig(
|
|
|
296
317
|
maxTurns: parent.maxTurns,
|
|
297
318
|
...parent.inheritableConfig(),
|
|
298
319
|
};
|
|
320
|
+
workerConfigs.add(config);
|
|
321
|
+
return config;
|
|
299
322
|
}
|
|
300
323
|
|
|
301
324
|
// ---------------------------------------------------------------------------
|
|
@@ -380,10 +403,18 @@ export function createLiveWorker(
|
|
|
380
403
|
trace.steps.push({ turn: turn.turns, type: "tool_result", name: tr.name, isError: tr.isError, timestamp });
|
|
381
404
|
}
|
|
382
405
|
trace.turns = Math.max(trace.turns, turn.turns);
|
|
383
|
-
try {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
try {
|
|
406
|
+
try {
|
|
407
|
+
hooks.trim?.(trace);
|
|
408
|
+
} catch {}
|
|
409
|
+
try {
|
|
410
|
+
hooks.touch?.(trace.trackingId);
|
|
411
|
+
} catch {}
|
|
412
|
+
try {
|
|
413
|
+
hooks.notify?.(trackingId);
|
|
414
|
+
} catch {}
|
|
415
|
+
try {
|
|
416
|
+
hooks.persist?.();
|
|
417
|
+
} catch {}
|
|
387
418
|
} catch {}
|
|
388
419
|
};
|
|
389
420
|
|
|
@@ -409,8 +440,12 @@ export function createLiveWorker(
|
|
|
409
440
|
entry.thinking = partialThinking;
|
|
410
441
|
entry.timestamp = Date.now();
|
|
411
442
|
}
|
|
412
|
-
try {
|
|
413
|
-
|
|
443
|
+
try {
|
|
444
|
+
hooks.trim?.(trace);
|
|
445
|
+
} catch {}
|
|
446
|
+
try {
|
|
447
|
+
hooks.notify?.(trackingId);
|
|
448
|
+
} catch {}
|
|
414
449
|
try {
|
|
415
450
|
hooks.emit?.({
|
|
416
451
|
trackingId: displayId,
|
|
@@ -429,11 +464,19 @@ export function createLiveWorker(
|
|
|
429
464
|
}): (() => void) => {
|
|
430
465
|
const textListener = (e: { delta?: string }): void => onWorkerDelta(e.delta, undefined);
|
|
431
466
|
const thinkingListener = (e: { thinkingDelta?: string }): void => onWorkerDelta(undefined, e.thinkingDelta);
|
|
432
|
-
try {
|
|
433
|
-
|
|
467
|
+
try {
|
|
468
|
+
workerStream.on("text_delta", textListener as (e: { delta?: string; thinkingDelta?: string }) => void);
|
|
469
|
+
} catch {}
|
|
470
|
+
try {
|
|
471
|
+
workerStream.on("thinking_delta", thinkingListener as (e: { delta?: string; thinkingDelta?: string }) => void);
|
|
472
|
+
} catch {}
|
|
434
473
|
return () => {
|
|
435
|
-
try {
|
|
436
|
-
|
|
474
|
+
try {
|
|
475
|
+
workerStream.off("text_delta", textListener as (e: { delta?: string; thinkingDelta?: string }) => void);
|
|
476
|
+
} catch {}
|
|
477
|
+
try {
|
|
478
|
+
workerStream.off("thinking_delta", thinkingListener as (e: { delta?: string; thinkingDelta?: string }) => void);
|
|
479
|
+
} catch {}
|
|
437
480
|
};
|
|
438
481
|
};
|
|
439
482
|
|