agent-accelerator 0.4.0 → 0.5.0
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 +149 -57
- package/bunfig.toml +1 -0
- package/package.json +2 -3
- package/src/agent/agent.ts +414 -118
- package/src/agent/context.ts +11 -16
- package/src/agent/delegation.ts +111 -274
- package/src/agent/loop.ts +327 -99
- package/src/agent/subagent.ts +4 -12
- package/src/agent/worker.ts +435 -0
- package/src/index.ts +58 -5
- package/src/models/catalog-cache.ts +65 -25
- package/src/models/catalog.ts +47 -2
- package/src/providers/google.ts +338 -81
- package/src/providers/openai-compat.ts +171 -61
- package/src/providers/openai.ts +174 -44
- package/src/providers/openrouter.ts +129 -44
- package/src/providers/shared.ts +169 -17
- package/src/providers.ts +375 -11
- package/src/scheduler/scheduled-actions.md +41 -0
- package/src/scheduler/scheduler.ts +814 -0
- package/src/scheduler/store.ts +263 -0
- package/src/scheduler/time.ts +148 -0
- package/src/scheduler/tool.ts +151 -0
- package/src/session/store.ts +229 -113
- package/src/streaming/event-stream.ts +6 -4
- package/src/telemetry-file.ts +31 -0
- package/src/telemetry.ts +65 -0
- package/src/tools/executor.ts +163 -38
- package/src/tools/subagent-result.ts +60 -0
- package/src/types/agent.ts +74 -22
- package/src/types/model.ts +39 -0
- package/src/types/response.ts +9 -0
- package/src/types/scheduler.ts +81 -0
- package/src/types/tool.ts +15 -0
- package/src/utils/cache.ts +14 -5
- package/src/utils/documents.ts +112 -52
- package/src/utils/env.ts +21 -7
- package/src/utils/errors.ts +26 -1
- package/src/utils/headers.ts +15 -15
- package/src/utils/home.ts +30 -0
- package/src/utils/media.ts +57 -12
- package/src/utils/raw-capture.ts +76 -0
- package/src/utils/retry.ts +11 -3
- package/src/utils/safe-io.ts +252 -0
- package/src/utils/session.ts +7 -8
- package/src/data/README.md +0 -84
package/src/agent/subagent.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { Agent } from "./agent.ts";
|
|
|
2
2
|
import type { AgentConfig } from "../types/agent.ts";
|
|
3
3
|
import type { ToolDefinition } from "../types/tool.ts";
|
|
4
4
|
import { agentToTool } from "./delegation.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveSubAgentModel } from "./worker.ts";
|
|
6
|
+
import { getModel, getSubModel } from "../utils/env.ts";
|
|
6
7
|
|
|
7
8
|
/** AgentConfig alias for a delegated worker agent. */
|
|
8
9
|
export interface SubAgentConfig extends AgentConfig {}
|
|
@@ -76,17 +77,8 @@ export class SubAgent extends Agent {
|
|
|
76
77
|
* ```
|
|
77
78
|
*/
|
|
78
79
|
constructor(config: SubAgentConfig) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
getSubModel() ??
|
|
82
|
-
getModel();
|
|
83
|
-
|
|
84
|
-
if (!resolvedModel) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
`[Agent Accelerator] SubAgent '${config.name || "subagent"}' requires a model. ` +
|
|
87
|
-
`Specify 'model' in SubAgent config, or set SUB_AGENT_MODEL or MODEL in environment variables.`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
80
|
+
// Unified model chain (shared with Agent): explicit > SUB_AGENT_MODEL > MODEL.
|
|
81
|
+
const resolvedModel = resolveSubAgentModel(config);
|
|
90
82
|
|
|
91
83
|
super({
|
|
92
84
|
...config,
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import type { ToolDefinition } from "../types/tool.ts";
|
|
2
|
+
import type { SubAgentStep } from "../types/response.ts";
|
|
3
|
+
import type { AgentConfig } from "../types/agent.ts";
|
|
4
|
+
import type { Agent } from "./agent.ts";
|
|
5
|
+
import { normalizeToolName } from "../tools/executor.ts";
|
|
6
|
+
import { escapeXml } from "../utils/serialization.ts";
|
|
7
|
+
import { getModel, getSubModel } from "../utils/env.ts";
|
|
8
|
+
import { resolveModel } from "../providers/registry.ts";
|
|
9
|
+
|
|
10
|
+
export { escapeXml };
|
|
11
|
+
|
|
12
|
+
/** Minimal live-trace shape shared by fixed + dynamic workers (structural subset of `SubAgentTrace`). */
|
|
13
|
+
export interface LiveWorkerTrace {
|
|
14
|
+
trackingId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
parentSessionId: string;
|
|
18
|
+
status: "running" | "done" | "error";
|
|
19
|
+
task: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
turns: number;
|
|
22
|
+
steps: SubAgentStep[];
|
|
23
|
+
text?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Shared identity helpers (single home for Agent / SubAgent / Dynamic workers)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/** Sanitizes a worker name into an XML tag (`UPPER-KEBAB`, 64 chars). */
|
|
32
|
+
export function sanitizeXmlTag(raw: string): string {
|
|
33
|
+
let s = raw.toUpperCase().replace(/[^A-Z0-9.-]/g, "-");
|
|
34
|
+
if (!/^[A-Z]/.test(s)) s = `AGENT-${s}`;
|
|
35
|
+
s = s.replace(/[.]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
36
|
+
return s.slice(0, 64) || "SUBAGENT";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Sanitizes a worker name into a tool name (`lower-kebab`, 64 chars). */
|
|
40
|
+
export function sanitizeToolName(raw: string): string {
|
|
41
|
+
const tag = sanitizeXmlTag(raw);
|
|
42
|
+
return tag.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").slice(0, 64) || "sub-agent";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Stable worker ids: `{sanitizedName}-{32hex}` display id + raw tracking id. */
|
|
46
|
+
export function makeWorkerIds(sanitizedName: string, trackingId: string): { trackingId: string; displayId: string } {
|
|
47
|
+
return { trackingId, displayId: `${sanitizedName}-${trackingId}` };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Shared Agent / SubAgent config normalization (behavior-preserving)
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Unwraps `config.model` exactly like `Agent` ctor:
|
|
56
|
+
* `ModelProviderInstance ({model})` -> inner `{model,apiKey,...}`,
|
|
57
|
+
* otherwise the value as-is.
|
|
58
|
+
*/
|
|
59
|
+
function unwrapModelInput(configModel: unknown): unknown {
|
|
60
|
+
const raw = configModel as { model?: unknown } | null | undefined;
|
|
61
|
+
if (raw && typeof raw === "object" && "model" in (raw as Record<string, unknown>)) {
|
|
62
|
+
return raw;
|
|
63
|
+
}
|
|
64
|
+
return configModel;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves the main model exactly like `Agent` ctor.
|
|
69
|
+
* Returns both the stored `modelStringOrSpec` and the `rawModel` used for
|
|
70
|
+
* `apiKey` / `baseUrl` / `thinkingLevel` inheritance.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveMainModel(configModel: AgentConfig["model"]): {
|
|
73
|
+
modelStringOrSpec: string | Record<string, unknown> | undefined;
|
|
74
|
+
rawModel: unknown;
|
|
75
|
+
} {
|
|
76
|
+
const rawModel = unwrapModelInput(configModel) as
|
|
77
|
+
| { model?: unknown; id?: unknown; provider?: unknown }
|
|
78
|
+
| string
|
|
79
|
+
| undefined;
|
|
80
|
+
let modelStringOrSpec: string | Record<string, unknown> | undefined;
|
|
81
|
+
if (rawModel && typeof rawModel === "object" && "model" in (rawModel as Record<string, unknown>)) {
|
|
82
|
+
modelStringOrSpec = (rawModel as { model: string }).model as string;
|
|
83
|
+
} else if (
|
|
84
|
+
rawModel &&
|
|
85
|
+
typeof rawModel === "object" &&
|
|
86
|
+
"id" in (rawModel as Record<string, unknown>) &&
|
|
87
|
+
"provider" in (rawModel as Record<string, unknown>)
|
|
88
|
+
) {
|
|
89
|
+
modelStringOrSpec = rawModel as Record<string, unknown>;
|
|
90
|
+
} else {
|
|
91
|
+
modelStringOrSpec = (rawModel as string | undefined) || getModel();
|
|
92
|
+
}
|
|
93
|
+
return { modelStringOrSpec: modelStringOrSpec as string | Record<string, unknown> | undefined, rawModel };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolves the dynamic worker model exactly like `Agent` ctor:
|
|
98
|
+
* `dynamicSubagents.model ?? subagentModel ?? SUB_AGENT_MODEL`.
|
|
99
|
+
* Unwraps `ModelProviderInstance` / `ModelSpec` the same way.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveDynamicWorkerModel(
|
|
102
|
+
dynModelRawInput: unknown,
|
|
103
|
+
subagentModelInput: unknown
|
|
104
|
+
): string | Record<string, unknown> | undefined {
|
|
105
|
+
const dynModelRaw = dynModelRawInput as
|
|
106
|
+
| { model?: unknown; id?: unknown; provider?: unknown }
|
|
107
|
+
| string
|
|
108
|
+
| undefined;
|
|
109
|
+
let dynModel: string | Record<string, unknown> | undefined;
|
|
110
|
+
if (dynModelRaw && typeof dynModelRaw === "object" && "model" in (dynModelRaw as Record<string, unknown>)) {
|
|
111
|
+
dynModel = (dynModelRaw as { model: string }).model as unknown as string | Record<string, unknown>;
|
|
112
|
+
} else if (
|
|
113
|
+
dynModelRaw &&
|
|
114
|
+
typeof dynModelRaw === "object" &&
|
|
115
|
+
"id" in (dynModelRaw as Record<string, unknown>) &&
|
|
116
|
+
"provider" in (dynModelRaw as Record<string, unknown>)
|
|
117
|
+
) {
|
|
118
|
+
dynModel = dynModelRaw as Record<string, unknown>;
|
|
119
|
+
} else {
|
|
120
|
+
dynModel = (dynModelRaw as string | undefined) || getSubModel() as string | undefined;
|
|
121
|
+
}
|
|
122
|
+
void subagentModelInput;
|
|
123
|
+
return dynModel;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolves the combined dynamic worker model input
|
|
128
|
+
* (`dynamicSubagents.model ?? subagentModel`) exactly like `Agent` ctor,
|
|
129
|
+
* including the `SUB_AGENT_MODEL` fallback.
|
|
130
|
+
*/
|
|
131
|
+
export function resolveDynamicModelInput(
|
|
132
|
+
dynamicSubagents: unknown,
|
|
133
|
+
subagentModel: unknown
|
|
134
|
+
): string | Record<string, unknown> | undefined {
|
|
135
|
+
const dynModelRaw = (dynamicSubagents as { model?: unknown } | undefined)?.model ?? subagentModel;
|
|
136
|
+
return resolveDynamicWorkerModel(dynModelRaw, subagentModel);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Resolves a `SubAgent` model exactly like `SubAgent` ctor:
|
|
141
|
+
* `config.model ?? SUB_AGENT_MODEL ?? MODEL`, throwing with the same message
|
|
142
|
+
* when nothing resolves. Kept here so `Agent` / `SubAgent` share one chain.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveSubAgentModel(config: { name?: string; model?: AgentConfig["model"] }): NonNullable<AgentConfig["model"]> {
|
|
145
|
+
const resolvedModel = config.model ?? getSubModel() ?? getModel();
|
|
146
|
+
if (!resolvedModel) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`[Agent Accelerator] SubAgent '${config.name || "subagent"}' requires a model. ` +
|
|
149
|
+
`Specify 'model' in SubAgent config, or set SUB_AGENT_MODEL or MODEL in environment variables.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return resolvedModel as NonNullable<AgentConfig["model"]>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normalizes a `tools` record|array into a record with `name` defaults,
|
|
157
|
+
* exactly like `Agent` ctor (both main tools and dynamic pool).
|
|
158
|
+
*/
|
|
159
|
+
export function normalizeToolMap(
|
|
160
|
+
rawTools: Record<string, ToolDefinition> | ToolDefinition[] | undefined
|
|
161
|
+
): Record<string, ToolDefinition> {
|
|
162
|
+
const out: Record<string, ToolDefinition> = {};
|
|
163
|
+
if (!rawTools) return out;
|
|
164
|
+
if (Array.isArray(rawTools)) {
|
|
165
|
+
for (const t of rawTools) {
|
|
166
|
+
const tName = (t as { name?: string })?.name || `tool_${Object.keys(out).length}`;
|
|
167
|
+
out[tName] = { ...(t as ToolDefinition), name: tName } as ToolDefinition;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
for (const [key, def] of Object.entries(rawTools)) {
|
|
171
|
+
out[key] = { ...(def as ToolDefinition), name: (def as { name?: string })?.name || key } as ToolDefinition;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Shared dynamic-worker helpers (pure, no store access)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/** Resolves per-worker timeout: `>0` fixed, `0` unlimited, `-1` per-task. */
|
|
182
|
+
export function normalizeWorkerTimeoutValue(cfgTimeout: number, taskTimeoutMs: unknown): number {
|
|
183
|
+
return cfgTimeout > 0 ? cfgTimeout : cfgTimeout === -1 && (taskTimeoutMs as number) > 0 ? Math.floor(taskTimeoutMs as number) : 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Grants ONLY the Main Agent-selected subset from the developer pool.
|
|
188
|
+
* Unknown names are dropped. Matching is case/format-insensitive.
|
|
189
|
+
*/
|
|
190
|
+
export function grantWorkerTools(
|
|
191
|
+
toolPool: Record<string, ToolDefinition>,
|
|
192
|
+
requested: unknown
|
|
193
|
+
): Record<string, ToolDefinition> {
|
|
194
|
+
const poolByNormalized = new Map<string, ToolDefinition>();
|
|
195
|
+
for (const [key, def] of Object.entries(toolPool)) {
|
|
196
|
+
poolByNormalized.set(normalizeToolName(key), def);
|
|
197
|
+
const declared = (def as ToolDefinition)?.name;
|
|
198
|
+
if (declared) poolByNormalized.set(normalizeToolName(declared), def);
|
|
199
|
+
}
|
|
200
|
+
const grantedTools: Record<string, ToolDefinition> = {};
|
|
201
|
+
for (const toolName of ((requested as string[] | undefined) ?? [])) {
|
|
202
|
+
const pooled = (toolPool as Record<string, ToolDefinition>)[toolName] ?? poolByNormalized.get(normalizeToolName(String(toolName)));
|
|
203
|
+
if (pooled) grantedTools[(pooled as ToolDefinition).name || toolName] = pooled;
|
|
204
|
+
}
|
|
205
|
+
return grantedTools;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Resolves a provider id for credential reuse / error metadata (shared). */
|
|
209
|
+
export function providerOfWorkerModel(modelStr: string | unknown | undefined): string | undefined {
|
|
210
|
+
return providerOf(modelStr);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function providerOf(modelStr: string | unknown | undefined): string | undefined {
|
|
214
|
+
if (!modelStr) return undefined;
|
|
215
|
+
try {
|
|
216
|
+
const m =
|
|
217
|
+
typeof modelStr === "string"
|
|
218
|
+
? modelStr
|
|
219
|
+
: (modelStr as { model?: unknown; id?: unknown })?.model ??
|
|
220
|
+
(modelStr as { id?: unknown })?.id ??
|
|
221
|
+
String(modelStr);
|
|
222
|
+
return resolveModel(m as string).provider.id;
|
|
223
|
+
} catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolves worker credentials: reuses parent `apiKey`/`baseUrl` ONLY when the
|
|
230
|
+
* worker provider matches the parent provider (otherwise env resolves).
|
|
231
|
+
*/
|
|
232
|
+
export function resolveWorkerCredentials(
|
|
233
|
+
parentAgent: { modelStringOrSpec?: unknown; apiKey?: string; baseUrl?: string },
|
|
234
|
+
chosenModel: unknown
|
|
235
|
+
): { apiKeyToUse: string | undefined; baseUrlToUse: string | undefined; attemptedProvider: string | undefined } {
|
|
236
|
+
const chosenModelStrForProvider =
|
|
237
|
+
typeof chosenModel === "string"
|
|
238
|
+
? chosenModel
|
|
239
|
+
: (chosenModel as { model?: unknown; id?: unknown })?.model ??
|
|
240
|
+
(chosenModel as { id?: unknown })?.id ??
|
|
241
|
+
String(chosenModel);
|
|
242
|
+
const attemptedProvider = providerOf(chosenModelStrForProvider);
|
|
243
|
+
const parentProvider = providerOf(parentAgent.modelStringOrSpec as string);
|
|
244
|
+
const targetProviderFinal = providerOf(chosenModelStrForProvider);
|
|
245
|
+
if (targetProviderFinal === parentProvider) {
|
|
246
|
+
return { apiKeyToUse: parentAgent.apiKey, baseUrlToUse: parentAgent.baseUrl, attemptedProvider };
|
|
247
|
+
}
|
|
248
|
+
return { apiKeyToUse: undefined, baseUrlToUse: undefined, attemptedProvider };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Config of a stateless dynamic worker (`spawn_subagents` and scheduled
|
|
253
|
+
* actions): the developer's fixed worker model, level and limits, never the
|
|
254
|
+
* LLM's choice. Credentials carry over only when the providers match, and
|
|
255
|
+
* `inheritableConfig()` keeps the worker no less constrained than its parent.
|
|
256
|
+
*/
|
|
257
|
+
export function buildDynamicWorkerConfig(
|
|
258
|
+
parent: Agent,
|
|
259
|
+
worker: { name: string; role?: string; instructions: string; sessionId: string; tools: Record<string, ToolDefinition> }
|
|
260
|
+
): AgentConfig {
|
|
261
|
+
const model = parent.subagentModel;
|
|
262
|
+
const { apiKeyToUse, baseUrlToUse } = resolveWorkerCredentials(parent, model);
|
|
263
|
+
return {
|
|
264
|
+
name: worker.name,
|
|
265
|
+
description: worker.role || `Sub-agent ${worker.name}`,
|
|
266
|
+
instructions: worker.instructions,
|
|
267
|
+
model,
|
|
268
|
+
apiKey: apiKeyToUse,
|
|
269
|
+
baseUrl: baseUrlToUse,
|
|
270
|
+
thinkingLevel: parent.dynamicSubagents?.thinkingLevel ?? parent.thinkingConfig?.level,
|
|
271
|
+
sessionId: worker.sessionId,
|
|
272
|
+
tools: worker.tools,
|
|
273
|
+
// Stateless by design: one task in, one result out, then shut down. No history, no recursion.
|
|
274
|
+
stateless: true,
|
|
275
|
+
serviceTier: parent.serviceTier,
|
|
276
|
+
headers: parent.customHeaders,
|
|
277
|
+
maxTurns: parent.maxTurns,
|
|
278
|
+
...parent.inheritableConfig(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Builds one `<NAME>...text...</NAME>` block inside `<SUB-AGENTS-RESPONSE>`. */
|
|
283
|
+
export function buildWorkerXmlBlock(name: string, text: string): string {
|
|
284
|
+
const escaped = escapeXml(text);
|
|
285
|
+
const indentedText = escaped.split("\n").map((line) => ` ${line}`).join("\n");
|
|
286
|
+
return ` <${name}>\n${indentedText}\n </${name}>`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Builds the `<SUB-AGENTS-RESPONSE>` envelope for dynamic workers. */
|
|
290
|
+
export function buildSubagentsXml(results: Array<{ name: string; text: string }>): string {
|
|
291
|
+
return `<SUB-AGENTS-RESPONSE>\n${results.map((r) => buildWorkerXmlBlock(r.name, r.text)).join("\n")}\n</SUB-AGENTS-RESPONSE>`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Shared live worker state machine (single home for fixed + dynamic workers)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
/** Turn summary fed into the live worker trace (mirrors `AgentRunOptions.onTurn`). */
|
|
299
|
+
export interface LiveWorkerTurn {
|
|
300
|
+
turns: number;
|
|
301
|
+
text?: string;
|
|
302
|
+
thinking?: string;
|
|
303
|
+
toolCalls?: Array<{ name: string }>;
|
|
304
|
+
toolResults?: Array<{ name: string; isError?: boolean }>;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Deltas forwarded to the parent stream via `onSubagentEvent`. */
|
|
308
|
+
export interface LiveWorkerEvent {
|
|
309
|
+
trackingId: string;
|
|
310
|
+
delta?: string;
|
|
311
|
+
thinkingDelta?: string;
|
|
312
|
+
partialText?: string;
|
|
313
|
+
partialThinking?: string;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Hooks owned by the trace store (`delegation.ts`); worker core stays pure. */
|
|
317
|
+
export interface LiveWorkerHooks {
|
|
318
|
+
trim?: (trace: LiveWorkerTrace) => void;
|
|
319
|
+
touch?: (trackingId: string) => void;
|
|
320
|
+
notify?: (trackingId: string) => void;
|
|
321
|
+
persist?: () => void;
|
|
322
|
+
emit?: (event: LiveWorkerEvent) => void;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Single live worker state machine shared by fixed (`agentToTool`) and
|
|
327
|
+
* dynamic (`spawn_subagents`) workers. Owns `liveTurn` / `partialText` /
|
|
328
|
+
* `partialThinking`, finalizes partial streaming entries on `onTurn`, and
|
|
329
|
+
* forwards deltas to the parent stream. The trace object is mutated in place;
|
|
330
|
+
* trimming / touching / notifying / persisting are delegated to hooks so this
|
|
331
|
+
* module never touches the trace store directly.
|
|
332
|
+
*/
|
|
333
|
+
export function createLiveWorker(
|
|
334
|
+
trace: LiveWorkerTrace,
|
|
335
|
+
trackingId: string,
|
|
336
|
+
displayId: string,
|
|
337
|
+
hooks: LiveWorkerHooks = {}
|
|
338
|
+
): {
|
|
339
|
+
onWorkerTurn: (turn: LiveWorkerTurn) => void;
|
|
340
|
+
onWorkerDelta: (delta?: string, thinkingDelta?: string) => void;
|
|
341
|
+
attachToStream: (workerStream: {
|
|
342
|
+
on: (event: string, listener: (e: { delta?: string; thinkingDelta?: string }) => void) => void;
|
|
343
|
+
off: (event: string, listener: (e: { delta?: string; thinkingDelta?: string }) => void) => void;
|
|
344
|
+
}) => () => void;
|
|
345
|
+
} {
|
|
346
|
+
let liveTurn = 1;
|
|
347
|
+
let partialText = "";
|
|
348
|
+
let partialThinking = "";
|
|
349
|
+
|
|
350
|
+
const findPartial = (turn: number): SubAgentStep | undefined => {
|
|
351
|
+
for (let i = trace.steps.length - 1; i >= 0; i--) {
|
|
352
|
+
const s = trace.steps[i]!;
|
|
353
|
+
if (s.turn === turn && s.partial) return s;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const onWorkerTurn = (turn: LiveWorkerTurn): void => {
|
|
359
|
+
try {
|
|
360
|
+
liveTurn = turn.turns;
|
|
361
|
+
const timestamp = Date.now();
|
|
362
|
+
const partialEntry = findPartial(liveTurn);
|
|
363
|
+
if (partialEntry) {
|
|
364
|
+
if (turn.text) partialEntry.text = turn.text;
|
|
365
|
+
if (turn.thinking) partialEntry.thinking = turn.thinking;
|
|
366
|
+
delete (partialEntry as { partial?: boolean }).partial;
|
|
367
|
+
} else if (turn.text) {
|
|
368
|
+
trace.steps.push({ turn: turn.turns, type: "assistant", text: turn.text.slice(0, 2000), timestamp });
|
|
369
|
+
}
|
|
370
|
+
for (const tc of turn.toolCalls ?? []) {
|
|
371
|
+
trace.steps.push({ turn: turn.turns, type: "tool_call", name: tc.name, timestamp });
|
|
372
|
+
}
|
|
373
|
+
for (const tr of turn.toolResults ?? []) {
|
|
374
|
+
trace.steps.push({ turn: turn.turns, type: "tool_result", name: tr.name, isError: tr.isError, timestamp });
|
|
375
|
+
}
|
|
376
|
+
trace.turns = Math.max(trace.turns, turn.turns);
|
|
377
|
+
try { hooks.trim?.(trace); } catch {}
|
|
378
|
+
try { hooks.touch?.(trace.trackingId); } catch {}
|
|
379
|
+
try { hooks.notify?.(trackingId); } catch {}
|
|
380
|
+
try { hooks.persist?.(); } catch {}
|
|
381
|
+
} catch {}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const onWorkerDelta = (delta?: string, thinkingDelta?: string): void => {
|
|
385
|
+
try {
|
|
386
|
+
if (delta) {
|
|
387
|
+
partialText = (partialText + delta).slice(-10000);
|
|
388
|
+
let entry = findPartial(liveTurn);
|
|
389
|
+
if (!entry) {
|
|
390
|
+
entry = { turn: liveTurn, type: "assistant", text: "", partial: true, timestamp: Date.now() };
|
|
391
|
+
trace.steps.push(entry);
|
|
392
|
+
}
|
|
393
|
+
entry.text = partialText;
|
|
394
|
+
entry.timestamp = Date.now();
|
|
395
|
+
}
|
|
396
|
+
if (thinkingDelta) {
|
|
397
|
+
partialThinking = (partialThinking + thinkingDelta).slice(-10000);
|
|
398
|
+
let entry = findPartial(liveTurn);
|
|
399
|
+
if (!entry) {
|
|
400
|
+
entry = { turn: liveTurn, type: "assistant", text: "", partial: true, timestamp: Date.now() };
|
|
401
|
+
trace.steps.push(entry);
|
|
402
|
+
}
|
|
403
|
+
entry.thinking = partialThinking;
|
|
404
|
+
entry.timestamp = Date.now();
|
|
405
|
+
}
|
|
406
|
+
try { hooks.trim?.(trace); } catch {}
|
|
407
|
+
try { hooks.notify?.(trackingId); } catch {}
|
|
408
|
+
try {
|
|
409
|
+
hooks.emit?.({
|
|
410
|
+
trackingId: displayId,
|
|
411
|
+
delta,
|
|
412
|
+
thinkingDelta,
|
|
413
|
+
partialText: partialText || undefined,
|
|
414
|
+
partialThinking: partialThinking || undefined,
|
|
415
|
+
});
|
|
416
|
+
} catch {}
|
|
417
|
+
} catch {}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const attachToStream = (workerStream: {
|
|
421
|
+
on: (event: string, listener: (e: { delta?: string; thinkingDelta?: string }) => void) => void;
|
|
422
|
+
off: (event: string, listener: (e: { delta?: string; thinkingDelta?: string }) => void) => void;
|
|
423
|
+
}): (() => void) => {
|
|
424
|
+
const textListener = (e: { delta?: string }): void => onWorkerDelta(e.delta, undefined);
|
|
425
|
+
const thinkingListener = (e: { thinkingDelta?: string }): void => onWorkerDelta(undefined, e.thinkingDelta);
|
|
426
|
+
try { workerStream.on("text_delta", textListener as (e: { delta?: string; thinkingDelta?: string }) => void); } catch {}
|
|
427
|
+
try { workerStream.on("thinking_delta", thinkingListener as (e: { delta?: string; thinkingDelta?: string }) => void); } catch {}
|
|
428
|
+
return () => {
|
|
429
|
+
try { workerStream.off("text_delta", textListener as (e: { delta?: string; thinkingDelta?: string }) => void); } catch {}
|
|
430
|
+
try { workerStream.off("thinking_delta", thinkingListener as (e: { delta?: string; thinkingDelta?: string }) => void); } catch {}
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
return { onWorkerTurn, onWorkerDelta, attachToStream };
|
|
435
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export {
|
|
|
61
61
|
convertDocumentToMarkdown,
|
|
62
62
|
convertDocumentInput,
|
|
63
63
|
convert_document_to_markdown,
|
|
64
|
+
createConvertDocumentTool,
|
|
64
65
|
isAnydocAvailable,
|
|
65
66
|
resolveDocumentFormat,
|
|
66
67
|
truncateMarkdown,
|
|
@@ -71,7 +72,8 @@ export {
|
|
|
71
72
|
DEFAULT_DOCUMENT_MAX_CHARS,
|
|
72
73
|
MAX_DOCUMENT_FETCH_BYTES,
|
|
73
74
|
} from "./utils/documents.ts";
|
|
74
|
-
export type { ConvertDocumentOptions, ConvertedDocument } from "./utils/documents.ts";
|
|
75
|
+
export type { ConvertDocumentOptions, ConvertedDocument, ConvertDocumentToolOptions } from "./utils/documents.ts";
|
|
76
|
+
export { assertPathAllowed, enforceFileAccess, fetchRemoteBytes, isPrivateAddress, RemoteFetchError, MAX_REMOTE_FETCH_BYTES } from "./utils/safe-io.ts";
|
|
75
77
|
|
|
76
78
|
// Multi-Agent Delegation & Tools
|
|
77
79
|
export {
|
|
@@ -83,8 +85,11 @@ export {
|
|
|
83
85
|
isSessionDescendant,
|
|
84
86
|
getSubAgentTrace,
|
|
85
87
|
getSubAgentTraceScoped,
|
|
88
|
+
getSubAgentTraceForSessions,
|
|
86
89
|
listSubAgentTraceIds,
|
|
90
|
+
listSubAgentTraceIdsForSessions,
|
|
87
91
|
subscribeToSubAgent,
|
|
92
|
+
subscribeToSubAgentForSessions,
|
|
88
93
|
appendSubAgentStep,
|
|
89
94
|
pruneTraces,
|
|
90
95
|
trimTraceSteps,
|
|
@@ -100,6 +105,28 @@ export {
|
|
|
100
105
|
} from "./agent/delegation.ts";
|
|
101
106
|
export type { DynamicSubagentTask, AgentAsToolTarget, SubAgentTrace, StreamingLoopHandle } from "./agent/delegation.ts";
|
|
102
107
|
|
|
108
|
+
// Scheduled actions: dynamic sub-agents that run later and save their answer to a file
|
|
109
|
+
export {
|
|
110
|
+
createScheduledAction,
|
|
111
|
+
listScheduledActions,
|
|
112
|
+
updateScheduledAction,
|
|
113
|
+
runScheduledAction,
|
|
114
|
+
skipScheduledAction,
|
|
115
|
+
killScheduledAction,
|
|
116
|
+
} from "./scheduler/scheduler.ts";
|
|
117
|
+
export { getDatetimeTool } from "./scheduler/tool.ts";
|
|
118
|
+
export { setHome, resolveHome } from "./utils/home.ts";
|
|
119
|
+
export type {
|
|
120
|
+
ScheduledActionInput,
|
|
121
|
+
ScheduledActionPatch,
|
|
122
|
+
ScheduledActionRecord,
|
|
123
|
+
ScheduledDelivery,
|
|
124
|
+
ScheduledState,
|
|
125
|
+
ScheduledRunStatus,
|
|
126
|
+
ScheduleSpec,
|
|
127
|
+
KillResult,
|
|
128
|
+
} from "./types/scheduler.ts";
|
|
129
|
+
|
|
103
130
|
// Providers & Registry — unified multi-provider layer, all native REST
|
|
104
131
|
export {
|
|
105
132
|
getProvider,
|
|
@@ -210,6 +237,16 @@ export {
|
|
|
210
237
|
mapToolChoiceToOpenAI,
|
|
211
238
|
normalizeToolChoice,
|
|
212
239
|
parseStreamedToolArguments,
|
|
240
|
+
selectRawArguments,
|
|
241
|
+
normalizeStructuredOutput,
|
|
242
|
+
extractOutputValidator,
|
|
243
|
+
parseStructuredOutput,
|
|
244
|
+
mapOutputToOpenAI,
|
|
245
|
+
mapOutputToGoogle,
|
|
246
|
+
mapOutputToOpenRouterChat,
|
|
247
|
+
mapOutputToCompatChat,
|
|
248
|
+
buildStructuredOutputInstruction,
|
|
249
|
+
augmentSystemPromptWithStructuredOutput,
|
|
213
250
|
} from "./providers.ts";
|
|
214
251
|
export type {
|
|
215
252
|
ProviderCapabilityStatus,
|
|
@@ -217,13 +254,21 @@ export type {
|
|
|
217
254
|
OpenRouterToolChoice,
|
|
218
255
|
OpenRouterChatToolChoice,
|
|
219
256
|
OpenAIToolChoice,
|
|
257
|
+
ChatCompletionsResponseFormat,
|
|
220
258
|
} from "./providers.ts";
|
|
221
259
|
|
|
222
260
|
export {
|
|
223
261
|
GOOGLE_MODELS,
|
|
224
262
|
OPENAI_MODELS,
|
|
225
263
|
OPENROUTER_MODELS,
|
|
264
|
+
modelSupportsStructuredOutput,
|
|
226
265
|
} from "./models/catalog.ts";
|
|
266
|
+
export {
|
|
267
|
+
hasAuthHeader,
|
|
268
|
+
clampToolCallId,
|
|
269
|
+
hasTruncatedToolArguments,
|
|
270
|
+
shortHashId,
|
|
271
|
+
} from "./providers/shared.ts";
|
|
227
272
|
|
|
228
273
|
export {
|
|
229
274
|
isValidThoughtSignature,
|
|
@@ -274,6 +319,10 @@ export type { AgentAccelErrorCode, AgentAccelErrorContext } from "./types/errors
|
|
|
274
319
|
// Normalized run limits (Q-22; sub-agents inherit unless overridden)
|
|
275
320
|
export type { AgentRunLimits } from "./agent/agent.ts";
|
|
276
321
|
|
|
322
|
+
// Telemetry bus: warnings, anomalies and per-turn thinking traces (attach with `telemetryBus.onEvent`)
|
|
323
|
+
export { telemetryBus } from "./telemetry.ts";
|
|
324
|
+
export type { TelemetryEvent, TelemetryListener } from "./telemetry.ts";
|
|
325
|
+
|
|
277
326
|
// Session persistence & telemetry (conversation history + cost rollup)
|
|
278
327
|
export {
|
|
279
328
|
SessionTelemetry,
|
|
@@ -289,6 +338,8 @@ export {
|
|
|
289
338
|
buildSessionData,
|
|
290
339
|
serializeSession,
|
|
291
340
|
deserializeSession,
|
|
341
|
+
inspectSessionPayload,
|
|
342
|
+
findInvalidMessage,
|
|
292
343
|
getSessionContextWindow,
|
|
293
344
|
computeSessionTurnCost,
|
|
294
345
|
formatSessionTokens,
|
|
@@ -296,7 +347,7 @@ export {
|
|
|
296
347
|
formatSessionBanner,
|
|
297
348
|
emptyTotals,
|
|
298
349
|
} from "./session/store.ts";
|
|
299
|
-
export type { PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace } from "./session/store.ts";
|
|
350
|
+
export type { SessionLimits, SessionPayloadStatus, PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace } from "./session/store.ts";
|
|
300
351
|
|
|
301
352
|
// Utilities
|
|
302
353
|
/**
|
|
@@ -311,8 +362,9 @@ export type { ParsedModelRef, ResolvedAgentConfig } from "./utils/session.ts";
|
|
|
311
362
|
* `/internal` entry (Q-50).
|
|
312
363
|
*/
|
|
313
364
|
export { getApiKey, getEnv } from "./utils/env.ts";
|
|
314
|
-
export { buildSessionHeaders } from "./utils/headers.ts";
|
|
315
|
-
export {
|
|
365
|
+
export { buildSessionHeaders, setAttribution, getAttribution } from "./utils/headers.ts";
|
|
366
|
+
export type { AttributionConfig } from "./utils/headers.ts";
|
|
367
|
+
export { normalizeMediaInput, inferMimeType, sniffMimeType } from "./utils/media.ts";
|
|
316
368
|
export { base64ToBytes, bytesToBase64 } from "./utils/base64.ts";
|
|
317
369
|
export { toJsonSafe, safeStringify, escapeXml } from "./utils/serialization.ts";
|
|
318
370
|
|
|
@@ -368,6 +420,7 @@ export type {
|
|
|
368
420
|
ProviderRequestOptions,
|
|
369
421
|
ProviderGenerateResult,
|
|
370
422
|
ProviderRawData,
|
|
423
|
+
StructuredOutputSpec,
|
|
371
424
|
} from "./types/model.ts";
|
|
372
425
|
|
|
373
426
|
export type {
|
|
@@ -388,7 +441,7 @@ export type {
|
|
|
388
441
|
WhenBusyMode,
|
|
389
442
|
ModelRef,
|
|
390
443
|
ThinkingOption,
|
|
391
|
-
|
|
444
|
+
StructuredOutputOption,
|
|
392
445
|
RetryPolicyConfig,
|
|
393
446
|
AgentLogger,
|
|
394
447
|
ToolChoiceOption,
|