agent-accelerator 0.6.0 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +62 -18
  2. package/package.json +1 -1
  3. package/src/agent/agent.ts +192 -695
  4. package/src/agent/architecture.md +19 -13
  5. package/src/agent/context.ts +1 -0
  6. package/src/agent/defaults.ts +88 -0
  7. package/src/agent/delegation.ts +8 -3
  8. package/src/agent/loop.ts +242 -226
  9. package/src/agent/named-sessions.ts +93 -0
  10. package/src/agent/options.ts +166 -0
  11. package/src/agent/run-control.ts +248 -0
  12. package/src/agent/subagents-tool.ts +14 -21
  13. package/src/agent/usage-limit.ts +92 -0
  14. package/src/agent/worker.ts +17 -3
  15. package/src/agent/workers.ts +13 -1
  16. package/src/architecture.md +1 -0
  17. package/src/index.ts +11 -11
  18. package/src/prompts/additional-context.md +2 -0
  19. package/src/prompts/architecture.md +19 -0
  20. package/src/prompts/prompts.ts +81 -0
  21. package/src/prompts/reasoning-directive.md +5 -0
  22. package/src/prompts/structured-output.md +2 -0
  23. package/src/prompts/tool-bridge.md +1 -0
  24. package/src/prompts/tool-interrupted.md +1 -0
  25. package/src/prompts/tool-not-found.md +1 -0
  26. package/src/prompts/tool-repeat-blocked.md +1 -0
  27. package/src/prompts/tool-schema-error.md +1 -0
  28. package/src/prompts/tool-truncated-arguments.md +1 -0
  29. package/src/prompts/worker-failed.md +1 -0
  30. package/src/prompts/worker-replied.md +2 -0
  31. package/src/providers/architecture.md +8 -8
  32. package/src/providers/google.ts +83 -13
  33. package/src/providers/openai-compat.ts +17 -7
  34. package/src/providers/openai.ts +10 -4
  35. package/src/providers/openrouter.ts +185 -18
  36. package/src/providers.ts +59 -9
  37. package/src/scheduler/architecture.md +7 -7
  38. package/src/scheduler/scheduler.ts +40 -0
  39. package/src/scheduler/tool.ts +5 -9
  40. package/src/session/journal.ts +1 -0
  41. package/src/session/store.ts +4 -0
  42. package/src/streaming/event-stream.ts +1 -1
  43. package/src/tools/architecture.md +7 -6
  44. package/src/tools/executor.ts +60 -15
  45. package/src/tools/tool-content.ts +32 -0
  46. package/src/types/agent.ts +40 -8
  47. package/src/types/core.ts +4 -0
  48. package/src/types/message.ts +7 -0
  49. package/src/types/model.ts +4 -1
  50. package/src/types/scheduler.ts +4 -4
  51. package/src/types/tool.ts +2 -0
  52. package/src/utils/errors.ts +3 -3
  53. package/src/utils/headers.ts +1 -1
  54. /package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +0 -0
  55. /package/src/{agent/subagents.md → prompts/subagents-tool.md} +0 -0
@@ -2,9 +2,8 @@
2
2
  * The two model-facing tools of scheduled actions: `scheduled_actions` (one
3
3
  * flat tool with a `command` field, a thin wrapper over the developer
4
4
  * functions) and `get_datetime`. The tool manual lives in
5
- * `scheduled-actions.md`, not in code.
5
+ * `src/prompts/scheduled-actions-tool.md`, not in code.
6
6
  */
7
- import { readFileSync } from "node:fs";
8
7
  import { z } from "zod";
9
8
  import type { Agent } from "../agent/agent.ts";
10
9
  import { tool } from "../tools/tool.ts";
@@ -23,15 +22,10 @@ import {
23
22
  } from "./scheduler.ts";
24
23
  import { readRun } from "./store.ts";
25
24
  import { formatDatetime } from "./time.ts";
25
+ import { fillPrompt } from "../prompts/prompts.ts";
26
26
 
27
27
  const COMMANDS = ["create", "list", "get", "update", "delete", "run_now", "skip"] as const;
28
28
 
29
- let manual: string | undefined;
30
- /** Read on first use (not at import), so a program that never enables scheduling never touches the file. */
31
- function loadManual(): string {
32
- return (manual ??= readFileSync(new URL("./scheduled-actions.md", import.meta.url), "utf8"));
33
- }
34
-
35
29
  /** Returns the machine's local date and time. Exported for any agent; auto-registered with the scheduling tools. */
36
30
  export const getDatetimeTool: ToolDefinition = tool({
37
31
  name: "get_datetime",
@@ -83,7 +77,9 @@ export function createScheduledActionsTool(agent: Agent): ToolDefinition {
83
77
 
84
78
  return tool({
85
79
  name: "scheduled_actions",
86
- description: loadManual().replace("{{TOOLS}}", poolNames.length > 0 ? poolNames.join(", ") : "none (omit tools)"),
80
+ description: fillPrompt(agent.prompts.scheduledActionsTool, {
81
+ TOOLS: poolNames.length > 0 ? poolNames.join(", ") : "none (omit tools)",
82
+ }),
87
83
  input: z.object({
88
84
  command: z.enum(COMMANDS),
89
85
  id: z.string().optional().describe(describedId),
@@ -39,6 +39,7 @@ function toolLine(t: SessionToolRecord): string {
39
39
  result: t.result,
40
40
  ...(t.isError ? { isError: true } : {}),
41
41
  ...(t.stopped ? { stopped: true } : {}),
42
+ ...(t.parts && t.parts.length > 0 ? { parts: t.parts } : {}),
42
43
  });
43
44
  }
44
45
 
@@ -83,6 +83,8 @@ export interface SessionToolRecord {
83
83
  isError?: boolean;
84
84
  /** The tool ended the run with `ctx.stop`. */
85
85
  stopped?: boolean;
86
+ /** Media the tool returned (inline data URLs). */
87
+ parts?: ContentPart[];
86
88
  }
87
89
 
88
90
  /** A prompt that was waiting when the process stopped (its options other than `additionalContext` are not saved). */
@@ -1053,6 +1055,7 @@ function replayJournal(snapshot: PersistedAgentSession, text: string, file: stri
1053
1055
  result?: unknown;
1054
1056
  isError?: unknown;
1055
1057
  stopped?: unknown;
1058
+ parts?: unknown;
1056
1059
  };
1057
1060
  try {
1058
1061
  rec = JSON.parse(line);
@@ -1092,6 +1095,7 @@ function replayJournal(snapshot: PersistedAgentSession, text: string, file: stri
1092
1095
  result: rec.result,
1093
1096
  ...(rec.isError ? { isError: true } : {}),
1094
1097
  ...(rec.stopped ? { stopped: true } : {}),
1098
+ ...(Array.isArray(rec.parts) ? { parts: rec.parts as ContentPart[] } : {}),
1095
1099
  },
1096
1100
  ];
1097
1101
  } else if (rec.t === "usage" && rec.u && typeof rec.u === "object") {
@@ -10,7 +10,7 @@ export interface EventStreamOptions {
10
10
  /**
11
11
  * When false, `partialText`/`partialThinking` cumulative fields are stripped
12
12
  * from events before delivery. Default true for compat (providers attach
13
- * cumulative partials; consumers like wrapThinking rely on them).
13
+ * cumulative partials).
14
14
  */
15
15
  includePartial?: boolean;
16
16
  /** Max buffered events when no iterator is attached. Default 1000. Oldest dropped. */
@@ -2,12 +2,13 @@
2
2
 
3
3
  Defining tools and running them.
4
4
 
5
- | File | What it is |
6
- | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
- | `tool.ts` | `tool()` factory, name validation, `toStandardToolDeclarations`, option normalizers. |
8
- | `schema.ts` | Zod to JSON Schema, `$ref` resolution, `stripSchemaForGoogle`. |
9
- | `executor.ts` | `executeToolCalls`: validation, concurrency (8 global + per-tool), attempts, timeout, result cap, error scrubbing, `ctx.stop`, `tool.*` events, per-call logs. |
10
- | `subagent-result.ts` | The private-Symbol brand that marks a result as made by the SDK's delegation tools. |
5
+ | File | What it is |
6
+ | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | `tool.ts` | `tool()` factory, name validation, `toStandardToolDeclarations`, option normalizers. |
8
+ | `schema.ts` | Zod to JSON Schema, `$ref` resolution, `stripSchemaForGoogle`. |
9
+ | `executor.ts` | `executeToolCalls`: validation, concurrency (8 global + per-tool), attempts, timeout, result cap, error scrubbing, `ctx.stop`, `tool.*` events, per-call logs. |
10
+ | `tool-content.ts` | `toolContent([...])`: a branded (private Symbol) tool result of text and media. The executor turns it into `result` (text) plus `parts` (inline data URLs), so only a tool's own code can produce media. |
11
+ | `subagent-result.ts` | The private-Symbol brand that marks a result as made by the SDK's delegation tools. |
11
12
 
12
13
  ## Invariants
13
14
 
@@ -3,6 +3,10 @@ import { toJsonSafe } from "../utils/serialization.ts";
3
3
  import { ConfigError } from "../types/errors.ts";
4
4
  import { isTransientError } from "../utils/errors.ts";
5
5
  import { isSubagentResult } from "./subagent-result.ts";
6
+ import { isToolContent, type ToolContent } from "./tool-content.ts";
7
+ import { normalizeMediaInput } from "../utils/media.ts";
8
+ import type { ContentPart } from "../types/message.ts";
9
+ import { DEFAULT_PROMPTS, fillPrompt, type Prompts } from "../prompts/prompts.ts";
6
10
  import { isTruncatedArguments } from "../types/tool.ts";
7
11
  import { telemetryBus } from "../telemetry.ts";
8
12
  import type { ToolLogWriter } from "../session/tool-log.ts";
@@ -14,6 +18,8 @@ export interface ExecuteToolsOptions {
14
18
  parallel?: boolean;
15
19
  signal?: AbortSignal;
16
20
  sessionId?: string;
21
+ /** The refusal texts shown to the model (`AgentConfig.prompts`); the shipped ones when unset. */
22
+ prompts?: Pick<Prompts, "toolTruncatedArguments" | "toolNotFound" | "toolSchemaError">;
17
23
  /** Called the moment EACH call finishes (before the whole batch does), so a caller can save a result before the next tool is done. Never throws into the run. */
18
24
  onResult?: (result: ToolResultRecord) => void;
19
25
  /** Opens the log file of one call (only when the Agent persists to a folder); the executor writes its steps into it and `ctx.log` writes through it. */
@@ -232,10 +238,11 @@ export function shouldBlockRepeat(
232
238
  * machine-readable code lives at `(result as any).errorCode` while the human
233
239
  * prefix keeps plain-string consumers working.
234
240
  */
235
- export function makeRepeatBlockedResult(call: ToolCallRecord): ToolResultRecord {
236
- const message =
237
- `repeat_blocked: Tool '${call.name}' was called again with identical arguments. ` +
238
- "The previous result is already available; reuse it or call the tool with different arguments.";
241
+ export function makeRepeatBlockedResult(
242
+ call: ToolCallRecord,
243
+ template = DEFAULT_PROMPTS.toolRepeatBlocked
244
+ ): ToolResultRecord {
245
+ const message = fillPrompt(template, { tool: call.name });
239
246
  return {
240
247
  id: call.id,
241
248
  name: call.name,
@@ -477,6 +484,36 @@ async function executeAttempt(
477
484
  }
478
485
  }
479
486
 
487
+ // Turns a `toolContent` result into its text (what every provider sees) and its media as inline data URLs (what Google also sends); in: runSingle for a branded tool result, out: the result record's `result` and `parts`.
488
+ async function resolveToolContent(
489
+ content: ToolContent,
490
+ charLimit: number
491
+ ): Promise<{ text: string; parts: ContentPart[] }> {
492
+ const texts: string[] = [];
493
+ const parts: ContentPart[] = [];
494
+ const kinds: string[] = [];
495
+ for (const raw of content.parts) {
496
+ const part = typeof raw === "string" ? ({ type: "text", text: raw } as const) : raw;
497
+ if (part.type === "text") {
498
+ texts.push(part.text);
499
+ continue;
500
+ }
501
+ const value = (part as unknown as Record<string, string | Uint8Array | ArrayBuffer>)[part.type]!;
502
+ const media = await normalizeMediaInput(value, (part as { mimeType?: string }).mimeType);
503
+ const filename = (part as { filename?: string }).filename;
504
+ parts.push({
505
+ type: part.type,
506
+ [part.type]: media.dataUrl,
507
+ mimeType: media.mimeType,
508
+ ...(filename ? { filename } : {}),
509
+ ...((part.type === "image" || part.type === "video") && part.resolution ? { resolution: part.resolution } : {}),
510
+ } as unknown as ContentPart);
511
+ kinds.push(media.mimeType);
512
+ }
513
+ const text = texts.join("\n") || (kinds.length > 0 ? `[media result: ${kinds.join(", ")}]` : "null");
514
+ return { text: truncateResultString(text, charLimit), parts };
515
+ }
516
+
480
517
  /**
481
518
  * Executes one or more tool calls in parallel or sequence, handling errors safely
482
519
  */
@@ -496,6 +533,7 @@ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<To
496
533
  onSubagentEvent,
497
534
  onResult,
498
535
  openToolLog,
536
+ prompts = DEFAULT_PROMPTS,
499
537
  } = options;
500
538
  assertNoToolCollisions(tools);
501
539
  const globalSemaphore = new Semaphore(DEFAULT_TOOL_CONCURRENCY);
@@ -534,9 +572,7 @@ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<To
534
572
  return {
535
573
  id: call.id,
536
574
  name: call.name,
537
- result:
538
- `Error: Tool '${call.name}' arguments are truncated or incomplete and could not be parsed. ` +
539
- "Call the tool again with the complete JSON arguments. Do not explain the error, just retry the call.",
575
+ result: fillPrompt(prompts.toolTruncatedArguments, { tool: call.name }),
540
576
  isError: true,
541
577
  durationMs: elapsedMs(startTime),
542
578
  };
@@ -547,11 +583,11 @@ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<To
547
583
 
548
584
  if (!toolDef) {
549
585
  trace("call_blocked", "warn", { reason: "tool_not_found", suggestion: resolved.suggestion });
550
- const suffix = resolved.suggestion ? ` Did you mean '${resolved.suggestion}'?` : "";
586
+ const suggestion = resolved.suggestion ? ` Did you mean '${resolved.suggestion}'?` : "";
551
587
  return {
552
588
  id: call.id,
553
589
  name: call.name,
554
- result: `Error: Tool '${call.name}' not found.${suffix}`,
590
+ result: fillPrompt(prompts.toolNotFound, { tool: call.name, suggestion }),
555
591
  isError: true,
556
592
  durationMs: elapsedMs(startTime),
557
593
  };
@@ -614,16 +650,12 @@ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<To
614
650
  ? JSON.stringify(toolDef.parameters).slice(0, 800)
615
651
  : undefined;
616
652
  const detail = parseRes.error.message;
617
- const hint = expectedParams ? ` Expected parameters: ${expectedParams}.` : "";
653
+ const expected = expectedParams ? ` Expected parameters: ${expectedParams}.` : "";
618
654
  trace("call_blocked", "warn", { reason: "schema_validation", detail: parseRes.error.message });
619
655
  return {
620
656
  id: call.id,
621
657
  name: call.name,
622
- result:
623
- `Schema validation error for tool '${call.name}': ${detail}` +
624
- ` Received argument keys: [${receivedKeys}].` +
625
- hint +
626
- ` Fix the arguments and call the tool again with corrected JSON. Do not explain the error, just retry the call.`,
658
+ result: fillPrompt(prompts.toolSchemaError, { tool: call.name, detail, keys: receivedKeys, expected }),
627
659
  isError: true,
628
660
  durationMs: elapsedMs(startTime),
629
661
  };
@@ -692,6 +724,19 @@ export async function executeToolCalls(options: ExecuteToolsOptions): Promise<To
692
724
  log?.write({ phase: "result", result: isSubagentResult(rawResult) ? rawResult.xml : toJsonSafe(rawResult) });
693
725
  const finishOk = (resultChars: number): void =>
694
726
  trace("call_succeeded", "info", { attempts: attempt, durationMs: elapsedMs(executionStartTime), resultChars });
727
+ // Text and media from `toolContent`: the text is the result, the media rides in `parts` (inline data, set only here).
728
+ if (isToolContent(rawResult)) {
729
+ const { text, parts } = await resolveToolContent(rawResult, resultCharLimit(toolDef));
730
+ finishOk(text.length);
731
+ return {
732
+ id: call.id,
733
+ name: resolved.name,
734
+ result: text,
735
+ ...(parts.length > 0 ? { parts } : {}),
736
+ isError: false,
737
+ durationMs: elapsedMs(executionStartTime),
738
+ };
739
+ }
695
740
  if (rawResult === undefined) {
696
741
  finishOk(4);
697
742
  return {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A tool result made of text and media. A tool returns `toolContent([...])` when the model should SEE a file (a chart
3
+ * image, a scanned page, a recording) and not only read text about it. The brand is a private Symbol, so only the
4
+ * tool's own code can produce it: JSON a tool fetched from the web can never become media for the provider.
5
+ */
6
+ import type { ContentPart } from "../types/message.ts";
7
+
8
+ /** Parts a tool may return: plain strings (text) and the usual text, image, audio, video and file parts. */
9
+ export type ToolContentPart = string | Extract<ContentPart, { type: "text" | "image" | "audio" | "video" | "file" }>;
10
+
11
+ const TOOL_CONTENT_BRAND: unique symbol = Symbol("agent-accelerator.tool-content") as never;
12
+
13
+ /** What `toolContent` returns; the executor turns it into a tool result with `parts`. */
14
+ export interface ToolContent {
15
+ readonly parts: ToolContentPart[];
16
+ readonly [TOOL_CONTENT_BRAND]: true;
17
+ }
18
+
19
+ /**
20
+ * Wraps text and media for a tool's return value, e.g.
21
+ * `return toolContent(["instrument.jpg", { type: "image", image: bytes, mimeType: "image/jpeg" }])`.
22
+ * Media may be bytes, a data URL, a path or a URL (the tool's own code is trusted with these). Google passes the media
23
+ * to the model inside the function result; other providers get the text only, with one warning.
24
+ */
25
+ export function toolContent(parts: ToolContentPart[]): ToolContent {
26
+ return { parts: [...parts], [TOOL_CONTENT_BRAND]: true };
27
+ }
28
+
29
+ /** True only for values made by {@link toolContent}. */
30
+ export function isToolContent(value: unknown): value is ToolContent {
31
+ return !!value && typeof value === "object" && (value as Record<symbol, unknown>)[TOOL_CONTENT_BRAND] === true;
32
+ }
@@ -4,6 +4,7 @@ import type { ThinkingLevel, CacheOption, ServiceTier } from "./core.ts";
4
4
  import type { ModelProviderInstance } from "../providers/registry.ts";
5
5
  import type { Agent } from "../agent/agent.ts";
6
6
  import type { z } from "zod";
7
+ import type { PromptOverrides } from "../prompts/prompts.ts";
7
8
 
8
9
  // ---------------------------------------------------------------------------
9
10
  // Canonical option types (all additive; nothing renamed).
@@ -43,7 +44,12 @@ export interface AgentLogger {
43
44
  }
44
45
 
45
46
  /** Canonical tool-selection control. String form is the portable subset. */
46
- export type ToolChoiceOption = "auto" | "none" | "required" | { mode?: "auto" | "none" | "required"; tool?: string };
47
+ export type ToolChoiceOption =
48
+ | "auto"
49
+ | "none"
50
+ | "required"
51
+ | "validated"
52
+ | { mode?: "auto" | "none" | "required" | "validated"; tool?: string };
47
53
 
48
54
  /** Canonical structured-output input. Zod, JSON Schema, or explicit form. */
49
55
  export type StructuredOutputOption =
@@ -149,7 +155,10 @@ export interface AgentConfig {
149
155
  * Canonical tool-selection control (`"auto"` default). `"none"` applies to
150
156
  * every turn of a run; `"required"` and a pinned tool (`{ tool: "name" }`)
151
157
  * apply to the FIRST model turn only, since forcing a tool call on every
152
- * turn would make a run unable to finish.
158
+ * turn would make a run unable to finish. `"validated"` (Google only) lets the
159
+ * model decide whether to call a tool but makes it keep every call valid against
160
+ * the tool's schema; it holds for the whole run. Other providers ignore it
161
+ * with one warning.
153
162
  */
154
163
  toolChoice?: ToolChoiceOption;
155
164
  /** Google Interactions storage tier for the chain-TTL gate: `"free"` (23h, default) or `"paid"` (54d). */
@@ -215,12 +224,29 @@ export interface AgentConfig {
215
224
  budget?: Budget;
216
225
  /** Run + network limits. Turn/budget caps unlimited by default; network timeouts finite. */
217
226
  limits?: AgentRunLimits;
227
+ /**
228
+ * A spending cap for the WHOLE chat, for its whole life: what the agent, all its workers (the `subagents` tool,
229
+ * `agent.workers` at any depth, fixed `subagents`) and their model calls cost, added up. It survives restarts: a
230
+ * persisted agent starts from what its saved sessions already spent. Once the total reaches `maxCostUsd` or
231
+ * `maxTotalTokens`, the next model call anywhere in the chat throws a `BudgetExceededError` ("Usage limit reached").
232
+ * Unlike `budget` (per run) it never resets. Scheduled actions are separate and never count. Models with unknown
233
+ * pricing add tokens but $0 to the cost. Unset = no cap.
234
+ */
235
+ usageLimit?: Pick<Budget, "maxCostUsd" | "maxTotalTokens">;
218
236
  /**
219
237
  * The only models this agent may use, as `provider/model` strings compared as written. Unset (default) = any model.
220
238
  * Checked when the Agent is built (also for a config an agent edited through {@link Agent.fromConfig}); a model
221
239
  * outside the list throws a `ConfigError`. Workers and `subagentModel` are not affected.
222
240
  */
223
241
  allowedModels?: string[];
242
+ /**
243
+ * Replaces single prompts the SDK puts in front of the model (the reasoning directive, tool refusals, the worker
244
+ * headers, the tool manuals ...). Every prompt you leave out keeps its default; see `DEFAULT_PROMPTS` for the names, the
245
+ * shipped text and the `{{placeholders}}` the SDK fills in. Keeping the original text is recommended: it is what the SDK
246
+ * is tested with, and a good reference for writing your own. Workers inherit it. A prompt is part of the cached prompt
247
+ * prefix when it is in the system prompt or a tool description, so change it once, not per run.
248
+ */
249
+ prompts?: PromptOverrides;
224
250
  }
225
251
 
226
252
  /**
@@ -301,12 +327,20 @@ export interface DynamicSubagentsConfig {
301
327
  /** Fixed reasoning level for all dynamic workers. The Main Agent cannot override it. Falls back to the parent level when omitted. */
302
328
  thinkingLevel?: ThinkingLevel;
303
329
  /**
304
- * Spending cap of EACH worker (the `subagents` tool, `agent.workers` and scheduled actions), set as that worker's own
305
- * per-run `budget`: a spawn, a message or a scheduled run is one run, and the usage of the worker's own workers counts
306
- * toward it. Exceeding it fails that call with a budget error and leaves the worker open. Default: unlimited. Models
307
- * with unknown pricing count $0 toward `maxCostUsd`. Passed down to the workers' own workers.
330
+ * Spending cap of EACH chat worker (the `subagents` tool and `agent.workers`), set as that worker's own per-run
331
+ * `budget`: a spawn or a message is one run, and the usage of the worker's own workers counts toward it. Exceeding it
332
+ * fails that call with a budget error and leaves the worker open. Default: unlimited. Models with unknown pricing count
333
+ * $0 toward `maxCostUsd`. Passed down to the workers' own workers. Scheduled actions have their own cap:
334
+ * `scheduledActionBudget`.
308
335
  */
309
336
  workerBudget?: Pick<Budget, "maxCostUsd" | "maxTotalTokens">;
337
+ /**
338
+ * Spending cap of EACH scheduled-action run, set as that run's own per-run `budget` (attempts included: a retry is part
339
+ * of the same run). Scheduled actions are detached from the chat, so neither `workerBudget` nor `usageLimit` applies to
340
+ * them. Exceeding it records a failed run. Default: unlimited. Models with unknown pricing count $0 toward
341
+ * `maxCostUsd`.
342
+ */
343
+ scheduledActionBudget?: Pick<Budget, "maxCostUsd" | "maxTotalTokens">;
310
344
  /**
311
345
  * How many levels of workers may exist below this agent. Default: no limit. `1` = workers cannot start workers of their
312
346
  * own; `3` = a worker may start workers that start workers. A worker with levels left gets its own `subagents` tool, and
@@ -358,8 +392,6 @@ export interface AgentRunOptions {
358
392
  onThinkingDelta?: (delta: string, event: import("./response.ts").StreamEvent) => void;
359
393
  /** Called for every stream event (text_delta, thinking_delta, tool_call_complete, subagent_complete, etc.). */
360
394
  onEvent?: (event: import("./response.ts").StreamEvent) => void;
361
- /** When true, wraps reasoning stream as <think>\n...\n</think>\n\n — no manual isThinking needed. */
362
- wrapThinking?: boolean;
363
395
  /** Called after every model/tool turn with a turn summary (also drives sub-agent step logs + durable persistence). */
364
396
  onTurn?: (turn: AgentTurnEvent) => void;
365
397
  // Per-run knobs (all optional, additive; `output` intentionally
package/src/types/core.ts CHANGED
@@ -42,6 +42,10 @@ export interface TokenUsage {
42
42
  cacheReadTokens?: number;
43
43
  cacheWriteTokens?: number;
44
44
  thinkingTokens?: number;
45
+ /** Media part of `inputTokens` (a subset, report only: pricing never reads it). OpenRouter reports audio and video. */
46
+ inputDetails?: { audioTokens?: number; videoTokens?: number };
47
+ /** Media part of `outputTokens` (a subset, report only). OpenRouter reports audio and image. */
48
+ outputDetails?: { audioTokens?: number; imageTokens?: number };
45
49
  cost?: {
46
50
  inputCost?: number;
47
51
  outputCost?: number;
@@ -12,6 +12,9 @@ export interface TextPart {
12
12
  /** Detail level a provider spends on one image or video (more detail = more input tokens). Google (images and videos) and OpenRouter (images, as `image_url.detail`) honour it; other providers ignore it with a warning. */
13
13
  export type MediaResolution = "low" | "medium" | "high" | "ultra_high";
14
14
 
15
+ /** How a provider reads a video: `"static"` (fixed-rate frames, the default) or `"agentic"` (the model navigates the timeline and loads only what the prompt needs). Only Google honours it, and agentic mode only on some Gemini models; other providers ignore it with a warning. */
16
+ export type VideoProcessing = "static" | "agentic";
17
+
15
18
  /** Image content accepted as a URL, data, path, or binary value. */
16
19
  export interface ImagePart {
17
20
  type: "image";
@@ -55,6 +58,8 @@ export interface VideoPart {
55
58
  mimeType?: string;
56
59
  /** Detail level per frame (Google: `low`/`medium` 70, `high` 280 tokens). */
57
60
  resolution?: MediaResolution;
61
+ /** How the video is read (Google: `"agentic"` lets the model explore a long video instead of loading every frame). */
62
+ processing?: VideoProcessing;
58
63
  }
59
64
 
60
65
  /** Assistant-generated structured tool invocation. */
@@ -89,6 +94,8 @@ export interface ToolResultPart {
89
94
  logFile?: string;
90
95
  /** The tool ended the run with `ctx.stop(reason)` (see `ToolExecutionContext.stop`). */
91
96
  stopped?: boolean;
97
+ /** Media the tool returned with `toolContent` (inline data URLs; `result` holds its text). Google sends it to the model. */
98
+ parts?: ContentPart[];
92
99
  }
93
100
 
94
101
  /** Provider reasoning/thinking content. */
@@ -2,6 +2,7 @@ import type { ThinkingConfig, CacheOption, ServiceTier, TokenUsage } from "./cor
2
2
  import type { ProviderContext, ProviderSteps } from "./message.ts";
3
3
  import type { StandardToolDeclaration, ToolCallRecord } from "./tool.ts";
4
4
  import type { AssistantMessageEventStream } from "../streaming/event-stream.ts";
5
+ import type { Prompts } from "../prompts/prompts.ts";
5
6
 
6
7
  // ---------------------------------------------------------------------------
7
8
  // Provider identity — battle-tested: matches models.dev provider keys
@@ -169,7 +170,7 @@ export interface ProviderRequestOptions {
169
170
  cache?: CacheOption;
170
171
  serviceTier?: ServiceTier;
171
172
  tools?: StandardToolDeclaration[];
172
- toolChoice?: "auto" | "none" | "required" | { type: "function"; function: { name: string } };
173
+ toolChoice?: "auto" | "none" | "required" | "validated" | { type: "function"; function: { name: string } };
173
174
  signal?: AbortSignal;
174
175
  sessionId?: string;
175
176
  env?: Record<string, string>;
@@ -187,6 +188,8 @@ export interface ProviderRequestOptions {
187
188
  maxOutputTokens?: number;
188
189
  /** `fetch` implementation used for the provider request (defaults to global `fetch`). */
189
190
  fetch?: typeof fetch;
191
+ /** The SDK prompts adapters put in a request (`AgentConfig.prompts`); defaults when unset. */
192
+ prompts?: Pick<Prompts, "structuredOutput" | "toolBridge">;
190
193
  /** Base backoff delay in ms for provider retries (default 250). */
191
194
  retryBaseDelayMs?: number;
192
195
  /** Total time budget in ms for provider retries (default 30000). */
@@ -18,12 +18,12 @@ export interface ScheduleSpec {
18
18
  /**
19
19
  * What happens when the host program was not running at a delivery time. `"skip"`: move on to the next time (a one-shot
20
20
  * is done). `"run"`: run it as soon as the program is back. Omitted: the action is marked `missed` and waits for
21
- * `runScheduledAction` or `skipScheduledAction`.
21
+ * `agent.scheduled.run` or `agent.scheduled.skip`.
22
22
  */
23
23
  onMissed?: "skip" | "run";
24
24
  }
25
25
 
26
- /** What `createScheduledAction` receives: dynamic sub-agent fields plus the schedule. */
26
+ /** What `agent.scheduled.create` receives: dynamic sub-agent fields plus the schedule. */
27
27
  export interface ScheduledActionInput {
28
28
  role?: string;
29
29
  instructions: string;
@@ -40,7 +40,7 @@ export interface ScheduledActionInput {
40
40
  };
41
41
  }
42
42
 
43
- /** What `updateScheduledAction` may change. `id` and the creating agent are immutable. */
43
+ /** What `agent.scheduled.update` may change. `id` and the creating agent are immutable. */
44
44
  export type ScheduledActionPatch = Partial<Omit<ScheduledActionInput, "scheduled_action">> & {
45
45
  scheduled_action?: Partial<Omit<ScheduledActionInput["scheduled_action"], "id">>;
46
46
  };
@@ -81,7 +81,7 @@ export interface ScheduledDelivery {
81
81
  late: boolean;
82
82
  }
83
83
 
84
- /** Result of `deleteScheduledAction`. */
84
+ /** Result of `agent.scheduled.delete`. */
85
85
  export type DeleteResult =
86
86
  | { ok: true }
87
87
  | { ok: false; reason: "not_found" }
package/src/types/tool.ts CHANGED
@@ -153,6 +153,8 @@ export interface ToolResultRecord {
153
153
  logFile?: string;
154
154
  /** The tool ended the run with `ctx.stop(reason)`; `result` is the reason. */
155
155
  stopped?: boolean;
156
+ /** Media returned with `toolContent`, as inline data URLs (`result` is its text). Set only by the executor. */
157
+ parts?: import("./message.ts").ContentPart[];
156
158
  }
157
159
 
158
160
  /**
@@ -181,9 +181,9 @@ export function assertModalitiesSupported(context: ProviderContext, providerId:
181
181
  * That wire has no video shape — only `input_text`/`input_image`/`input_file`
182
182
  * exist in either vendor's Responses docs — so a video part sent as
183
183
  * `input_file` lands in the router's document parser and 400s confusingly
184
- * (`Failed to parse the file ... Provide a PDF document`, observed live on
185
- * `openrouter/stealth/space-bunny-alpha` under the old Responses skin despite
186
- * its catalog `video` flag, which is chat-transport oriented). Fail fast with
184
+ * (`Failed to parse the file ... Provide a PDF document`, observed live under
185
+ * the old OpenRouter Responses skin despite the catalog `video` flag, which is
186
+ * chat-transport oriented). Fail fast with
187
187
  * a clear one-liner instead; video stays Gemini-only, and OpenRouter Chat
188
188
  * Completions carries `video_url` natively (no guard there). Throws a `ValidationError`.
189
189
  */
@@ -24,7 +24,7 @@ export function getPackageVersion(): string {
24
24
  const v = (globalThis as any).process?.env?.npm_package_version;
25
25
  if (typeof v === "string" && v) return v;
26
26
  } catch {}
27
- return "0.6.0";
27
+ return "0.6.6";
28
28
  }
29
29
 
30
30
  // User-Agent string with version and platform; in: AGENT_ACCEL_USER_AGENT, out: a string.