agent-accelerator 0.4.5 → 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.
Files changed (45) hide show
  1. package/README.md +149 -57
  2. package/bunfig.toml +1 -0
  3. package/package.json +2 -3
  4. package/src/agent/agent.ts +401 -120
  5. package/src/agent/context.ts +11 -16
  6. package/src/agent/delegation.ts +111 -274
  7. package/src/agent/loop.ts +253 -98
  8. package/src/agent/subagent.ts +4 -12
  9. package/src/agent/worker.ts +435 -0
  10. package/src/index.ts +39 -5
  11. package/src/models/catalog-cache.ts +65 -25
  12. package/src/models/catalog.ts +21 -2
  13. package/src/providers/google.ts +282 -74
  14. package/src/providers/openai-compat.ts +61 -41
  15. package/src/providers/openai.ts +46 -31
  16. package/src/providers/openrouter.ts +42 -31
  17. package/src/providers/shared.ts +110 -16
  18. package/src/providers.ts +119 -6
  19. package/src/scheduler/scheduled-actions.md +41 -0
  20. package/src/scheduler/scheduler.ts +814 -0
  21. package/src/scheduler/store.ts +263 -0
  22. package/src/scheduler/time.ts +148 -0
  23. package/src/scheduler/tool.ts +151 -0
  24. package/src/session/store.ts +211 -104
  25. package/src/streaming/event-stream.ts +6 -4
  26. package/src/telemetry-file.ts +31 -0
  27. package/src/telemetry.ts +65 -0
  28. package/src/tools/executor.ts +163 -38
  29. package/src/tools/subagent-result.ts +60 -0
  30. package/src/types/agent.ts +57 -20
  31. package/src/types/model.ts +18 -0
  32. package/src/types/response.ts +2 -0
  33. package/src/types/scheduler.ts +81 -0
  34. package/src/types/tool.ts +15 -0
  35. package/src/utils/cache.ts +14 -5
  36. package/src/utils/documents.ts +112 -52
  37. package/src/utils/errors.ts +11 -0
  38. package/src/utils/headers.ts +15 -15
  39. package/src/utils/home.ts +30 -0
  40. package/src/utils/media.ts +57 -12
  41. package/src/utils/raw-capture.ts +76 -0
  42. package/src/utils/retry.ts +11 -3
  43. package/src/utils/safe-io.ts +252 -0
  44. package/src/utils/session.ts +7 -8
  45. package/src/data/README.md +0 -84
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![GitHub](https://img.shields.io/badge/GitHub-sashvat--bharat%2Fagent--accelerator-blue?logo=github)](https://github.com/sashvat-bharat/agent-accelerator)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- > **Stability notice:** Agent Accelerator is pre-1.0 and under active development. Public APIs, provider transports, and configuration options may still change in breaking ways between releases. For production use, please pin your dependency to an exact version.
7
+ > **Stability notice:** Pre-1.0 and under active development. Public APIs, provider transports and configuration options may change in breaking ways between releases. Pin an exact version for production.
8
8
 
9
9
  A thin, typed transport SDK for calling LLMs through a single `Agent` interface.
10
10
 
@@ -71,9 +71,7 @@ SUB_AGENT_MODEL="google/gemini-3.5-flash-lite"
71
71
  # OLLAMA_BASE_URL="http://localhost:11434/v1"
72
72
  ```
73
73
 
74
- `MODEL` and `SUB_AGENT_MODEL` are used when `model` or the dynamic worker model are omitted.
75
-
76
- Explicit configuration always takes precedence over environment variables.
74
+ `MODEL` and `SUB_AGENT_MODEL` are fallbacks, used only when `model` or the dynamic worker model are omitted. Explicit configuration always takes precedence over environment variables.
77
75
 
78
76
  ---
79
77
 
@@ -95,9 +93,7 @@ console.log(res.usage); // input / output / cached / thinking / cost
95
93
  console.log(res.raw.request.headers); // wire audit
96
94
  ```
97
95
 
98
- Use `instructions` for the system prompt.
99
-
100
- Keep `instructions` stable across turns to improve prefix-cache reuse.
96
+ `instructions` is the system prompt. Keep it stable across turns to improve prefix-cache reuse.
101
97
 
102
98
  ---
103
99
 
@@ -105,7 +101,7 @@ Keep `instructions` stable across turns to improve prefix-cache reuse.
105
101
 
106
102
  `new Agent(config: AgentConfig)` is the main entry point.
107
103
 
108
- An `Agent` holds :
104
+ An `Agent` holds:
109
105
 
110
106
  * `instructions`
111
107
  * `tools`
@@ -124,9 +120,9 @@ An `Agent` holds :
124
120
  | `instructions` | `string` | System prompt. Keep it stable; place per-turn additions in `additionalContext`. |
125
121
  | `tools` | `Record<string, ToolDefinition> \| ToolDefinition[]` | Deterministic functions the model can call. See [Tools](#tools). |
126
122
  | `subagents` | `SubAgent[] \| Agent[]` | Pre-defined workers. Each worker becomes a callable tool. Useful for fixed roles such as researcher or critic. |
127
- | `subagentModel` | `string \| ModelSpec \| ModelProviderInstance` | Developer-only model for dynamically spawned workers. Never choosable by the Main Agent. Overridden by `dynamicSubagents.model`. Falls back to `SUB_AGENT_MODEL`. |
128
- | `dynamicSubagents` | `DynamicSubagentsConfig` | Enables and constrains LLM-spawned stateless workers: `{ enabled, model?, maxSpawn?, thinkingLevel?, tools?, timeout? }`. See [Dynamic delegation](#dynamic-delegation). |
129
- | `thinkingLevel` | `ThinkingLevel` | `none \| dynamic \| minimal \| low \| medium \| high \| xhigh \| max`. Validated against the model catalog before a request. |
123
+ | `subagentModel` | `string \| ModelSpec \| ModelProviderInstance` | Developer-only model for dynamically spawned workers. Never choosable by the Main Agent. Deprecated alias of `dynamicSubagents.model` (which wins). Falls back to `SUB_AGENT_MODEL`. |
124
+ | `dynamicSubagents` | `DynamicSubagentsConfig` | Enables and constrains LLM-spawned stateless workers: `{ enabled, model?, maxSpawn?, thinkingLevel?, tools?, timeout?, allowScheduledActions? }`. See [Pre-defined vs Dynamic Delegation](#pre-defined-vs-dynamic-delegation) and [Scheduled actions](#scheduled-actions). |
125
+ | `thinking` | `ThinkingOption` | Canonical reasoning option: `none \| dynamic \| minimal \| low \| medium \| high \| xhigh \| max`, or `{ level, budgetTokens }`. Legacy `thinkingLevel` still works (canonical wins). Validated against the model catalog before a request. |
130
126
  | `cache` | `CacheConfig` | `{ retention, sessionId, cachedContentId, ttlSeconds }`. Controls cache reuse. |
131
127
  | `serviceTier` | `"flex" \| "priority"` | Cost / priority routing where supported. Omit for standard routing. |
132
128
  | `bypassInputFileModality` | `boolean` | When `true`, `file` parts are converted client-side to Markdown for models lacking native support. Capable models still receive files natively. Defaults to `false`. |
@@ -136,7 +132,8 @@ An `Agent` holds :
136
132
  | `apiKey` | `string` | Overrides environment-based API-key lookup for this agent. |
137
133
  | `baseUrl` | `string` | Overrides the default endpoint for this agent. |
138
134
  | `stateless` | `boolean` | When `true`, history is cleared before and after each `run`. Useful for one-shot evaluators. Defaults to `false`. |
139
- | `persist` | `{ dir?: string; file?: string }` | Durable sessions: rewrites the session file after every step (user/assistant/tool/sub-agent). `{ dir }` uses `sessions/<sessionId>/session.json`, `{ file }` a single `.session.json`. Writes are atomic and never fail a turn. |
135
+ | `persist` | `{ dir?: string; file?: string }` | Durable sessions: rewrites the session file after every step (user/assistant/tool/sub-agent). `{ dir }` uses `sessions/<sessionId>/session.json`, `{ file }` a single `.session.json`, `{}` uses `<home>/sessions`. Writes are atomic and never fail a turn. |
136
+ | `home` | `string` | Folder for everything the SDK writes to disk (`sessions/`, `scheduled_actions/`, `telemetry/`). Defaults to `setHome(dir)`, then the `AGENT_ACCEL_HOME` env var, then `.agent-accel/` in the working directory. An explicit `persist.dir` / `persist.file` still wins. |
140
137
 
141
138
  When `dynamicSubagents.enabled` is set without a resolvable worker model, construction throws `SubAgentModelError`.
142
139
 
@@ -144,7 +141,7 @@ When `dynamicSubagents.enabled` is set without a resolvable worker model, constr
144
141
 
145
142
  ```ts
146
143
  await agent.run(prompt, opts?) // non-streaming, or streaming when opts.stream is true
147
- agent.ask(prompt, optsOrBool?) // string deltas when streaming, otherwise same as run
144
+ agent.ask(prompt, optsOrBool?) // deprecated (use run): string deltas when streaming, otherwise same as run
148
145
  agent.stream(prompt, opts?) // AssistantMessageEventStream
149
146
  agent.reset() // clears messages + signatures, keeps config
150
147
  agent.exportSession(telemetry?) // storable snapshot for saveSessionFile
@@ -167,7 +164,7 @@ Use `ContentPart[]` when sending images, audio, or video alongside text.
167
164
  | `onDelta(delta, event)` | Called for each text chunk. Use to render answers live. |
168
165
  | `onThinkingDelta(delta, event)` | Called for each reasoning chunk. Use to render reasoning separately. |
169
166
  | `onEvent(event)` | Called for every event: `text_delta`, `thinking_delta`, `tool_call_complete`, `tool_result`, `subagent_complete`, `subagent_delta`, `usage`, `done`, and `error`. |
170
- | `wrapThinking` | Wraps reasoning as `<think>\n...\n</think>\n\n`, allowing UIs to render it without maintaining separate state. |
167
+ | `wrapThinking` | Wraps reasoning as `<think>\n...\n</think>\n`, allowing UIs to render it without maintaining separate state. |
171
168
  | `onTurn(turn)` | Called after every model/tool turn with `{ turns, text?, thinking?, toolCalls?, toolResults? }`. Drives sub-agent step logs and persistence. |
172
169
  | `additionalContext` | Added only to the current user turn. Keeps the system prompt stable for better caching. |
173
170
  | `sessionId` | Per-run session override. |
@@ -176,6 +173,8 @@ Use `ContentPart[]` when sending images, audio, or video alongside text.
176
173
 
177
174
  ### Streaming
178
175
 
176
+ `run(prompt, { stream: true })` is the compat form; `agent.stream()` (below) is canonical.
177
+
179
178
  ```ts
180
179
  const res = await agent.run("Design an append-only log", {
181
180
  stream: true,
@@ -219,7 +218,7 @@ stream.off("text_delta", handler);
219
218
 
220
219
  ## SubAgent
221
220
 
222
- `SubAgent extends Agent` and is designed for fixed worker roles.
221
+ `SubAgent extends Agent` for fixed worker roles. It is a deprecated compat alias: prefer a plain `Agent` exposed via `subagents: [...]`, `agentToTool()` or `buildAgentTools()`.
223
222
 
224
223
  ```ts
225
224
  import { Agent, SubAgent } from "agent-accelerator";
@@ -260,8 +259,7 @@ Each subagent is automatically registered as an internal tool on the parent that
260
259
 
261
260
  **Dynamic delegation**
262
261
 
263
- `subagents` lists pre-defined workers (each becomes a callable tool). For
264
- LLM-spawned workers, configure `dynamicSubagents`:
262
+ For LLM-spawned workers, configure `dynamicSubagents`:
265
263
 
266
264
  ```ts
267
265
  const agent = new Agent({
@@ -313,6 +311,69 @@ console.log(res.subagents.map((s) => s.name));
313
311
  * `createSubagentSpawnTool(parentAgent)` — builds the dynamic subagent spawner. Rarely needed directly.
314
312
  * `DynamicSubagentTask` — `{ name, role?, instructions, task, tools?, timeoutMs? }`, the shape produced for dynamic delegation. No `model` — workers run on the developer-configured model.
315
313
 
314
+ ---
315
+ ## Scheduled actions
316
+
317
+ A scheduled action is a dynamic sub-agent that runs **later** (once or on repeat), on its own, and writes its answer to a file instead of replying in the chat: a research brief every day at 7:00 AM, a health report every Monday. It is off by default: without the flag no tool, clock or timer exists.
318
+
319
+ ```ts
320
+ const agent = new Agent({
321
+ name: "Main Agent",
322
+ model: "google/gemini-3.8-flash",
323
+ dynamicSubagents: {
324
+ model: "google/gemini-3.5-flash-lite", // worker model (required)
325
+ tools: { get_weather, recent_news }, // worker tool pool
326
+ allowScheduledActions: true, // default false
327
+ },
328
+ });
329
+ ```
330
+
331
+ The Main Agent then gets two tools, fixed by config so the prompt cache stays stable: `scheduled_actions` (one tool, `command` = `create | list | get | update | kill | run_now | skip`) and `get_datetime` (the machine's local date and time, also exported as `getDatetimeTool`). The tool description is exactly [`src/scheduler/scheduled-actions.md`](src/scheduler/scheduled-actions.md) (the worker pool names are filled in), so you can read what the model is told.
332
+
333
+ The same code is available to your program:
334
+
335
+ ```ts
336
+ import { createScheduledAction, telemetryBus } from "agent-accelerator";
337
+
338
+ createScheduledAction(agent, {
339
+ instructions: "You write a short, sourced AI research brief.",
340
+ task: "Summarize the most important AI news of the last day in 5 bullets.",
341
+ tools: ["recent_news"], // names from the worker pool
342
+ scheduled_action: {
343
+ name: "Daily AI Research Brief",
344
+ id: "daily-ai-brief", // lowercase kebab-case; the SDK appends 6 chars: daily-ai-brief-d64aj2
345
+ timeToDeliver: "01:07:00", // "DD:HH:MM" - DD days from today (01 = tomorrow), 12-hour clock
346
+ meridiem: "AM",
347
+ startCookingInAdvance: "00:00:30", // optional, a "DD:HH:MM" duration (default 10 minutes)
348
+ repeatEvery: "01:00:00", // optional; omit to run once
349
+ retries: 1, // optional; used only while time remains before delivery
350
+ },
351
+ });
352
+
353
+ telemetryBus.onEvent((e) => {
354
+ if (e.code === "scheduled_action.delivered") console.log(`${e.data.name} is ready: ${e.data.file}`);
355
+ });
356
+ ```
357
+
358
+ | Function | What it does |
359
+ | --- | --- |
360
+ | `createScheduledAction(agent, input)` | Schedule a new action. Throws one `ValidationError` listing every problem. |
361
+ | `listScheduledActions({ home? })` | Every action with its metadata, state (`waiting`, `cooking`, `missed`, `done`), `lastRun` and a `running` flag. |
362
+ | `updateScheduledAction(id, patch, { home? })` | Change instructions, task, tools, timeout, role or any schedule field. Schedule changes are refused while it is cooking. |
363
+ | `runScheduledAction(id, { home? })` | Run now, outside the schedule (also resolves a missed run). Needs the creating `Agent` alive. |
364
+ | `skipScheduledAction(id, { home? })` | Ignore a missed run and wait for the next scheduled time. |
365
+ | `killScheduledAction(id, { force?, purge?, home? })` | Delete for good. While it is cooking nothing changes unless `force: true`; `purge: true` also deletes past run files. |
366
+
367
+ **How it behaves**
368
+
369
+ * **One local clock.** A single timer per home folder ticks once a second against the machine's own time and only asks what is overdue, so drift or sleep cannot skip a run. There is no timezone or daylight-saving logic. The timer is `unref`'d and runs only while something is pending, so actions run only while your process is alive.
370
+ * **The creating `Agent` runs it.** The task stores the Agent's `name`; each run goes to the live Agent with that name (latest wins), using its worker model and tool pool. The scheduler holds only a weak reference, so keep your `Agent` referenced; if none is alive, the run counts as missed.
371
+ * **Each run is fresh.** A new stateless dynamic sub-agent built from the saved fields, on your fixed worker model and tool pool, never touching the Main Agent's session. It inherits the parent's limits, retry policy, `fileAccess` and `captureRaw`.
372
+ * **Files are the truth.** Everything lives in `<home>/scheduled_actions/`: `tasks.json` (every action), `deliveries.json` (saved results waiting for their delivery time) and one folder per action with one `.md` file per run (a small header, then the result). The file is re-read and validated before each run, so editing `instructions` or `task` there takes effect on the next run. API keys, the model and tool code are never stored. Don't put secrets in `instructions`.
373
+ * **Ready, then delivered.** A finished run is saved immediately (`scheduled_action.ready`); at `timeToDeliver` the SDK emits `scheduled_action.delivered` on `telemetryBus` with `{ id, name, status, file, readyAt, deliverAt, late }`. There is no notification system: what happens next is your code. Other events: `.created`, `.updated`, `.started`, `.failed`, `.missed`, `.skipped`, `.killed`, plus the warnings `.invalid` and `.store_corrupt`.
374
+ * **Overlap and failure.** A run still cooking when the next one is due is skipped, never stacked. A failed run still writes a file saying why and is delivered like any result (`status: "failed"`).
375
+ * **Missed runs.** If the process was down when a run was due, the SDK emits `scheduled_action.missed` once and runs nothing by itself: pick `run_now` or `skip`. A result saved before the process exited is simply delivered (marked `late`) on the first tick after start.
376
+
316
377
  ---
317
378
  ## Tools
318
379
 
@@ -338,14 +399,37 @@ const agent = new Agent({
338
399
 
339
400
  ### Tool Helpers
340
401
 
341
- * `tool({ name?, description, input?, parameters?, strict?, timeoutMs?, maxTries?, maxConcurrency?, execute })` — creates a `ToolDefinition`. Provide either a Zod `input` schema or raw JSON `parameters`. `execute(input, ctx)` may return arbitrary values; results are converted safely for model context. `timeoutMs` defaults to `0` (no time limit) and is a best-effort event-loop deadline; `ctx.signal` enables cooperative cancellation. `maxTries` accepts a number or numeric string; a positive value is the total attempt limit, while `0`/omitted falls back to 3 attempts. `maxConcurrency` limits simultaneous calls for that tool (default pool: 8).
402
+ * `tool({ name?, description, input?, parameters?, strict?, timeoutMs?, maxTries?, maxConcurrency?, execute })` — creates a `ToolDefinition`. Provide either a Zod `input` schema or raw JSON `parameters`. `execute(input, ctx)` may return arbitrary values; results are converted safely for model context. `timeoutMs` defaults to `0` (no time limit) and is a best-effort event-loop deadline; `ctx.signal` enables cooperative cancellation. `maxAttempts` is the canonical name for `maxTries` (number or numeric string). A positive value is the total attempt limit; `0`/omitted means 3 attempts for `idempotent` tools and 1 for all others. `maxConcurrency` limits simultaneous calls for that tool (default pool: 8).
342
403
  * `toStandardToolDeclarations(record | array)` — converts tools to `{ name, description, parameters }` for providers.
343
404
  * `zodToJsonSchema(schema)` — converts Zod schemas using native conversion with a fallback extractor.
344
405
  * `cleanJsonSchema(schema)` — removes `$schema`, `$defs`, and `definitions`, resolves `$ref`, and preserves explicit `additionalProperties`.
345
406
  * `executeToolCalls({ tools, toolCalls, agentName?, parallel?, signal?, sessionId? })` — executes tool calls. Calls validate Zod input, use bounded parallelism, enforce per-tool timeouts, retry transient failures, recover namespace/camel-case tool-name aliases, and return `ToolResultRecord[]`. Missing tools and aborts are represented as error results rather than thrown.
346
- * `convert_document_to_markdown` — built-in tool that converts document files (PDF/Word/PowerPoint/Excel/OpenDocument/RTF/EPUB/CSV, local path or URL) to Markdown for models without native document parsing. Requires the optional `@firecrawl/anydoc` peer.
407
+ * `convert_document_to_markdown` — built-in, model-callable version of `convertDocumentToMarkdown` (PDF/Word/PowerPoint/Excel/OpenDocument/RTF/EPUB/CSV, local path or URL) for models without native document parsing. Returns a `<Document>` envelope, is auto-registered with `bypassInputFileModality: true`, and requires the optional `@firecrawl/anydoc` peer.
408
+
409
+ The agent loop answers the 3rd consecutive identical call (same name and arguments) with a `repeat_blocked` error, so the model can reuse the prior result or change its arguments. Tools marked `repeatable: true`, or `idempotent: true` unless `repeatable` is set, are exempt.
410
+
411
+ ### Tool Failures and Tracing
347
412
 
348
- The agent loop also blocks an identical tool name and argument set when the model requests it in the immediately following turn. The synthetic error is returned to the model so it can reuse the prior result or change its arguments.
413
+ When a tool throws, the model gets the error line plus a JSON block: error `name`/`code`/`status`, `attempts`, `timedOut`, the last progress the tool reported, and a hint not to repeat finished work. Report progress from inside a tool with `ctx.emit({ type: "progress", ... })`, or throw `new ToolError(msg, { context: { progress } })`.
414
+
415
+ ```ts
416
+ execute: async ({ recipients }, ctx) => {
417
+ const sent: string[] = [];
418
+ for (const to of recipients) {
419
+ await sendMail(to); // throws on the 5th
420
+ sent.push(to);
421
+ ctx.emit?.({ type: "progress", sent: [...sent] });
422
+ }
423
+ }
424
+ // model sees: Error executing send_mails: SMTP 550 ...
425
+ // {"error":{...},"attempts":1,"progress":{"sent":["a","b","c","d"]},"hint":"..."}
426
+ ```
427
+
428
+ Every call also emits `tool.*` events on `telemetryBus` (nothing is written unless you attach a sink): `call_started`, `attempt_started`, `progress`, `attempt_failed`, `retry_scheduled`, `call_succeeded`, `call_failed`, `call_timed_out`, `call_aborted`, `call_blocked` (schema, truncated args, unknown tool, repeat guard) and `call_interrupted` (repaired after a crash). Each carries `toolCallId`, `toolName`, `agentName`, `sessionId`; failures add the full error (stack, cause chain; only URL credentials redacted). Delivery is synchronous, so a sink that appends to disk survives a crash: a `call_started` with no ending event was in flight, and the last `progress` shows how far it got.
429
+
430
+ ```ts
431
+ telemetryBus.onEvent((e) => e.code.startsWith("tool.") && appendFileSync("tools.ndjson", JSON.stringify(e) + "\n"));
432
+ ```
349
433
 
350
434
  ### Tool Timing and Deadlines
351
435
 
@@ -434,9 +518,7 @@ Any other provider prefix is treated as an OpenAI-compatible custom provider.
434
518
  * `"groq/<id>"`, `"ollama/<id>"`, etc. → custom providers using `{PREFIX}_API_KEY` and `{PREFIX}_BASE_URL`. The API key is optional for local endpoints.
435
519
  * A bare `"some-model"` uses `OPENAI_BASE_URL` when configured. Otherwise, catalog lookup is attempted, followed by an `openrouter` fallback.
436
520
 
437
- Model resolution never silently selects a default model.
438
-
439
- An empty model throws.
521
+ Model resolution never silently selects a default model: an empty model throws.
440
522
 
441
523
  An unknown prefix with no catalog match still routes to a custom provider, allowing private model IDs and endpoints.
442
524
 
@@ -505,7 +587,7 @@ Google-specific handling includes:
505
587
 
506
588
  * Isolating `thoughtSignature` per part for prefix stability.
507
589
  * Converting JSON Schema to OpenAPI 3.0 through `stripSchemaForGoogle`.
508
- * Implicit/automatic caching only — explicit retention and `cachedContentId` are warned about and dropped (the Interactions API defines no explicit cache primitives).
590
+ * Implicit/automatic caching only; explicit retention and `cachedContentId` are dropped (see [Cache](#cache)).
509
591
 
510
592
  Helpers:
511
593
 
@@ -533,7 +615,7 @@ It also:
533
615
  * passes `service_tier` (`flex` | `priority`);
534
616
  * pins cache affinity with `prompt_cache_key` (clamped to 64 chars);
535
617
  * rejects `video` parts up front with a one-line error (the Responses wire carries text/image/file only);
536
- * never sends `temperature`, token caps, or background/conversation fields.
618
+ * sends `max_output_tokens` when set; never sends `temperature`, `top_p`, or background/conversation fields.
537
619
 
538
620
  ### OpenRouter
539
621
 
@@ -543,11 +625,7 @@ Every turn is stateless: the full `messages[]` array is sent explicitly — no c
543
625
 
544
626
  Session affinity is a top-level body `session_id` plus the `x-session-id` header fallback (body takes precedence per OpenRouter docs).
545
627
 
546
- Thinking levels map to `reasoning.effort` verbatim:
547
-
548
- ```text
549
- none | minimal | low | medium | high | xhigh | max (dynamic omits — server default)
550
- ```
628
+ Thinking levels map to `reasoning.effort` verbatim, the same as OpenAI (`dynamic` omits, server default).
551
629
 
552
630
  It also:
553
631
 
@@ -558,6 +636,13 @@ It also:
558
636
 
559
637
  `serviceTier` values other than `flex` / `priority` are omitted (standard routing).
560
638
 
639
+ OpenRouter app attribution (`HTTP-Referer` / `X-Title`) is **opt-in**; nothing is sent by default:
640
+
641
+ ```ts
642
+ import { setAttribution } from "agent-accelerator";
643
+ setAttribution({ referer: "https://my.app", title: "My App" });
644
+ ```
645
+
561
646
  ### Custom
562
647
 
563
648
  `OpenAICompatibleProvider` is also exposed through the `CustomProvider`, `createCustomProvider`, and `createOpenAICompatibleProvider(prefix, opts?)` aliases.
@@ -578,7 +663,7 @@ It also:
578
663
  Provider wire types are exported for inspecting raw requests and responses through:
579
664
 
580
665
  ```ts
581
- res.raw.request.body
666
+ res.raw.request.body // full body only with `captureRaw: true`
582
667
  res.raw.response.body
583
668
  ```
584
669
 
@@ -591,7 +676,7 @@ Examples include:
591
676
  ---
592
677
  ## Thinking
593
678
 
594
- Thinking uses a single `thinkingLevel` flag:
679
+ Thinking is set with the canonical `thinking` option (`thinkingLevel` is a legacy alias that still works). The level type is:
595
680
 
596
681
  ```ts
597
682
  ThinkingLevel =
@@ -611,9 +696,7 @@ ThinkingLevel =
611
696
  * `dynamic` — allows the model to decide.
612
697
  * `minimal`, `low`, `medium`, `high`, `xhigh`, `max` — request increasing levels of reasoning where supported.
613
698
 
614
- Thinking can be configured on the `Agent` or through `ModelProvider.*(..., { thinkingLevel })`.
615
-
616
- The per-run `thinkingLevel` option can override the agent default for one request.
699
+ Set it on the `Agent`, through `ModelProvider.*(..., { thinkingLevel })`, or per run: the per-run `thinking` option overrides the agent default for one request.
617
700
 
618
701
  Internally, thinking is normalized to:
619
702
 
@@ -784,11 +867,7 @@ Use:
784
867
  * `usage` for interim usage counts.
785
868
  * `done` for final usage and completion information.
786
869
 
787
- Breaking out of `for await` auto-cancels. Cancellation joins your
788
- `AbortSignal` and stream cancellation into one linked controller, so
789
- `controller.abort()` and `stream.cancel()` both stop the HTTP request.
790
- Abort rejects with `AbortError` (never retried) and never resolves partial
791
- results. `agent.run(prompt, { stream: true, signal })` exposes the same
870
+ Breaking out of `for await` cancels the stream. Your `AbortSignal` and stream cancellation share one linked controller, so `controller.abort()` and `stream.cancel()` both stop the HTTP request. Abort rejects with `AbortError` (never retried) and never resolves partial results. `agent.run(prompt, { stream: true, signal })` exposes the same
792
871
  `cancel` handle.
793
872
 
794
873
  `SSEParser` provides:
@@ -858,7 +937,7 @@ SubAgentExecutionMetadata {
858
937
  }
859
938
  ```
860
939
 
861
- `finishReason` is `"max_turns"` when a finite `maxTurns` limit stopped the run with tool calls still pending (plus a console warning naming the dropped tools).
940
+ `finishReason` is `"max_turns"` when a finite `maxTurns` limit stopped the run with tool calls still pending (with an `agent.max_turns_truncated` warning on `telemetryBus` naming the dropped tools).
862
941
 
863
942
  ### SubAgentStep
864
943
 
@@ -920,7 +999,15 @@ ProviderRawData {
920
999
  }
921
1000
  ```
922
1001
 
923
- Sensitive keys are redacted.
1002
+ Credential headers are always redacted (`Authorization`, `x-goog-api-key`, anything named `*key*` / `*token*` / `*secret*` / `cookie`, and credential-looking values).
1003
+
1004
+ **`captureRaw`** controls how much of the wire exchange `res.raw` keeps:
1005
+
1006
+ | Value | `request.body` / `response.body` |
1007
+ | :-- | :-- |
1008
+ | `"redacted"` (default) | content-free summary (`{ redacted: true, bytes, model, items, tools }`): prompts, files and model output never reach your logs via `toJSON()` |
1009
+ | `true` | full bodies (for debugging; handle with care) |
1010
+ | `false` | `null`; only URL, method and HTTP status are kept |
924
1011
 
925
1012
  ---
926
1013
  ## Errors
@@ -951,10 +1038,11 @@ const res = await agent.run([...]).catch(fail);
951
1038
  ---
952
1039
  ## Model Catalog & Dynamic 12-Hour TTL Sync
953
1040
 
954
- Agent Accelerator features an adaptive, dynamically synchronized model catalog powered by [models.dev](https://models.dev). To avoid shipping a bloated 4.4 MB static JSON file with production builds, the SDK employs a high-performance **12-hour TTL local caching architecture**:
1041
+ The model catalog is synced from [models.dev](https://models.dev) into a local cache, so the multi-megabyte snapshot never ships in the package. The cache has a **12-hour TTL**:
955
1042
 
956
- - **Automated 12-Hour Cache Validation**: When `.run()`, `.ask()`, or `.stream()` executes, the runtime checks `src/data/models.dev.json`. If the cache timestamp is within 12 hours, it reads from disk with zero network delay. When the TTL expires, it transparently synchronizes with `https://models.dev/api.json`.
957
- - **Git & Package Safety**: The dynamic cache file (`src/data/models.dev.json`) is gitignored and excluded from production packages.
1043
+ - **Automated 12-hour validation**: `.run()`, `.ask()` and `.stream()` check `<home>/catalog/models.dev.json`. Within 12 hours it reads from disk with no network delay. After that the expired catalog is served immediately and refreshed from `https://models.dev/api.json` in the background (8 s timeout). Only a cold start, with nothing loaded yet, waits for the download. A failed refresh pauses automatic refreshes for 10 minutes.
1044
+ - **Git & Package Safety**: The cache lives in the single Agent Accelerator home (`<home>/catalog/models.dev.json`, `.agent-accel/` by default and gitignored), never inside the package or scattered in the project.
1045
+ - **Environment**: `setHome(dir)` / `AGENT_ACCEL_HOME` move the whole home; `AGENT_CACHE_DIR` moves only the cache (for read-only or serverless filesystems). `AGENT_CATALOG_REFRESH=off` disables automatic refreshes; `bun run update-models` and `refreshModelCatalog()` still work.
958
1046
 
959
1047
  ### Developer Catalog Controls
960
1048
 
@@ -995,7 +1083,6 @@ directly, a refresh would wipe it.
995
1083
 
996
1084
  * `getModelFromCatalog(provider, modelId)` → `ModelSpec | undefined`, including alias and global fallback handling. Use it for context-window, pricing, and modality checks.
997
1085
  * `getModelsForProvider(provider)` → returns `ModelSpec[]`.
998
- * `getModelThinkingInfo(provider, modelId)` → inspect permitted thinking levels.
999
1086
  * `refreshModelCatalog(options?)` → programmatically sync with upstream models.dev.
1000
1087
  * `getCatalogStatus()` → diagnostic info on active catalog, provider count, and TTL expiry.
1001
1088
 
@@ -1125,7 +1212,7 @@ never in the gitignored snapshot.
1125
1212
  ---
1126
1213
  ## Utils
1127
1214
 
1128
- * `createSessionId(prefix="accel")` — creates a UUID-based session ID clamped to 64 characters for affinity.
1215
+ * `createSessionId(prefix?)` — creates a random UUID session ID (`${prefix}-<uuid>` when a prefix is given), clamped to 64 characters for affinity.
1129
1216
  * `createTrackingId()` — system-generated 32-char TrackingID for one sub-agent worker (`NAME-{TrackingID}`).
1130
1217
  * `createChildSessionId(parentId, tag)` / `createFixedChildSessionId(parentId, name)` / `isSessionDescendant(child, parent)` — provider-safe (≤64 chars) child session IDs with parent-hash lineage.
1131
1218
  * `getEnv(key, fallback?)` — resolves an environment variable.
@@ -1133,7 +1220,6 @@ never in the gitignored snapshot.
1133
1220
  * `buildSessionHeaders(provider, cache?, custom?, sessionId?)` — builds provider-specific affinity headers. Normally handled automatically.
1134
1221
  * `withRetries(fn, { maxRetries?, maxRetryDelayMs?, signal?, label? })` / `isTransientError(err)` — bounded retries for transient provider failures (defaults: 2 retries, 5s cap, aborts never retried).
1135
1222
  * `convertDocumentToMarkdown(input, { filename?, format?, mimeType?, maxChars? })` — converts PDF/Word/PowerPoint/Excel/OpenDocument/RTF/EPUB/CSV to Markdown via the optional `@firecrawl/anydoc` peer. Throws `DocumentConversionError` on failure.
1136
- * `convert_document_to_markdown` — built-in model-callable version of the above (returns a `<Document>` envelope); auto-registered with `bypassInputFileModality: true`.
1137
1223
  * `z` — re-exported from Zod so tools do not require a separate Zod import.
1138
1224
 
1139
1225
  ---
@@ -1172,6 +1258,16 @@ bun run examples/10-document_markdown.ts
1172
1258
 
1173
1259
  bun run examples/11-session-identity.ts
1174
1260
  # sub-agent TrackingIDs, live worker boxes, lineage, track(), durable sessions
1261
+
1262
+ bun run examples/12-mid-session.ts
1263
+ # steer, queue and stream while a turn is running
1264
+
1265
+ bun run examples/13-structured-output.ts
1266
+ # JSON Schema structured outputs on every provider
1267
+
1268
+ bun run examples/14-scheduled-actions.ts [--quick]
1269
+ # scheduled actions walkthrough on your computer's clock: starts working ~1 minute from now,
1270
+ # delivers ~2 minutes from now (code-scheduled and model-scheduled); --quick skips the waiting
1175
1271
  ```
1176
1272
 
1177
1273
  Every example ends its `run()` with `.catch(fail)` (`examples/_shared.ts`),
@@ -1186,7 +1282,7 @@ sessions/<sessionId>/session.json (+ media/ for images, audio, video, files)
1186
1282
  It resumes the most recent session on next launch (legacy `.session.json`/`.session.jsonl` files still load) and prints a resume banner:
1187
1283
 
1188
1284
  ```text
1189
- ↺ Previous session loaded • accel-1a2b… (.session.json) • 12 messages • google/gemini-3.5-flash-lite • total-CH82.4% • $0.013 total
1285
+ ↺ Previous session loaded • 1a2b3c4d… (.session.json) • 12 messages • google/gemini-3.5-flash-lite • total-CH82.4% • $0.013 total
1190
1286
  ```
1191
1287
 
1192
1288
  It also displays per-turn and session totals on one line:
@@ -1222,8 +1318,8 @@ saveSessionFile(".session.json", agent, telemetry);
1222
1318
  ```
1223
1319
 
1224
1320
  * `agent.exportSession(telemetry)` / `agent.importSession(saved)` round-trip messages, system prompt, model, thinking level, instructions, cache, worker model, and sub-agent traces without touching `AgentContext` internals.
1225
- * `SessionTelemetry` accumulates `input / output / cacheRead / cacheWrite / reasoning / cost`, with clamped `turnHitRate()` / `totalHitRate()`, a dual `formatBar(res)`, and `formatSessionBanner(saved, telemetry, path)` for startup.
1226
- * Files are pretty-printed JSON (2-space indent), written atomically (temp + rename). Cost prefers provider-reported totals and falls back to catalog pricing.
1321
+ * `SessionTelemetry` accumulates `input / output / cacheRead / cacheWrite / reasoning / cost`, with clamped `turnHitRate()` / `totalHitRate()`, a dual `formatBar(res)`, and `formatSessionBanner(saved, telemetry, path)` for startup. Cost follows the [TokenUsage](#response-usage-and-raw-data) rules.
1322
+ * Files are JSON with one line per top-level field and one line per turn (2-space indent), written atomically (temp + rename).
1227
1323
  * Binary media (bytes, data URLs, base64) is extracted to `media/` on save via `saveSessionDir("sessions", agent, telemetry)` → `sessions/<sessionId>/{session.json, media/*}`; remote URLs and local paths stay references. `loadSessionDir(dir)` resolves `media/…` refs back to absolute paths, and `findLatestSessionDir("sessions")` resumes the most recent session.
1228
1324
  * Sub-agent traces persist in a dedicated `subagents` section keyed by TrackingID (`{ trackingId, name, sessionId, parentSessionId, status, task, turns, usage, steps, text }`). Files stay lean: redundant `rawArguments` and repeated step text are dropped on save (rebuilt/kept live in memory).
1229
1325
  * With `persist: { dir }` or `{ file }`, the session file is rewritten after every step, so a crash loses at most the in-flight step.
@@ -1243,6 +1339,7 @@ bun run update-models # refresh model catalog cache (supports --force, --ttl=24h
1243
1339
  ```text
1244
1340
  src/
1245
1341
  ├── agent/ # Agent, context, loop, delegation, subagent
1342
+ ├── scheduler/ # Scheduled actions: clock, store, tool + its manual (scheduled-actions.md)
1246
1343
  ├── session/ # Session persistence + telemetry (snapshots, hit rates, .session.json store)
1247
1344
  ├── providers/ # native REST adapters (google/openai/openrouter/openai-compat) + registry + canonical contract
1248
1345
  ├── models/ # Dynamic catalog cache, parser, verified overrides
@@ -1251,12 +1348,7 @@ src/
1251
1348
  ├── streaming/ # event stream, SSE parser
1252
1349
  ├── types/ # agent, core, message, model, response, tool
1253
1350
  └── utils/ # base64, cache, env, headers, media, serialization, session, thought-signature, documents, retry, errors
1254
- examples/
1255
- ├── 01-metadata.ts 02-function_calling.ts 03-multi_agent.ts
1256
- ├── 04-sub-agents.ts 05-chat.ts 06-multimodal_image.ts
1257
- ├── 07-multimodal_audio.ts 08-multimodal_video.ts
1258
- ├── 09-multimodal_document.ts 10-document_markdown.ts
1259
- ├── files/ prompts/ research-agent.ts _shared.ts
1351
+ examples/ # live demos 01-14 (see Examples), plus files/, prompts/, research-agent.ts, _shared.ts
1260
1352
  test/ # unit + mocked-provider tests mirroring src/
1261
1353
  ```
1262
1354
 
package/bunfig.toml CHANGED
@@ -1,2 +1,3 @@
1
1
  [test]
2
2
  root = "test"
3
+ preload = ["./test/setup.ts"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-accelerator",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "description": "High-performance SDK for AI agents with a unified API across AI providers. Optimized for high cache hit rates, observability, and seamless agent orchestration. Built to power production-grade agentic systems and harnesses for teams at any scale.",
5
5
  "license": "MIT",
6
6
  "author": "Sashvat Bharat",
@@ -30,8 +30,7 @@
30
30
  "bunfig.toml",
31
31
  "bun.lock",
32
32
  "README.md",
33
- "LICENSE",
34
- "!src/data/models.dev.json"
33
+ "LICENSE"
35
34
  ],
36
35
  "main": "./src/index.ts",
37
36
  "module": "./src/index.ts",