agent-accelerator 0.5.1 → 0.5.3
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 +98 -8
- package/package.json +1 -1
- package/src/agent/agent.ts +283 -15
- package/src/agent/context.ts +7 -2
- package/src/agent/loop.ts +137 -12
- package/src/index.ts +4 -1
- package/src/models/catalog-cache.ts +3 -10
- package/src/providers/google.ts +378 -120
- package/src/providers/openai-compat.ts +2 -0
- package/src/providers/openai.ts +2 -0
- package/src/providers/openrouter.ts +2 -0
- package/src/providers.ts +16 -0
- package/src/scheduler/scheduler.ts +2 -0
- package/src/scheduler/store.ts +34 -21
- package/src/session/journal.ts +201 -0
- package/src/session/store.ts +181 -82
- package/src/session/tool-log.ts +62 -0
- package/src/telemetry-file.ts +4 -1
- package/src/tools/executor.ts +72 -15
- package/src/tools/tool.ts +0 -6
- package/src/types/agent.ts +26 -0
- package/src/types/message.ts +19 -0
- package/src/types/model.ts +5 -1
- package/src/types/response.ts +7 -0
- package/src/types/tool.ts +18 -6
- package/src/utils/durable.ts +218 -0
- package/src/utils/headers.ts +1 -1
- package/src/utils/retry.ts +17 -2
package/README.md
CHANGED
|
@@ -131,7 +131,9 @@ An `Agent` holds:
|
|
|
131
131
|
| `apiKey` | `string` | Overrides environment-based API-key lookup for this agent. |
|
|
132
132
|
| `baseUrl` | `string` | Overrides the default endpoint for this agent. |
|
|
133
133
|
| `stateless` | `boolean` | When `true`, history is cleared before and after each `run`. Useful for one-shot evaluators. Defaults to `false`. |
|
|
134
|
-
| `
|
|
134
|
+
| `runRetries` | `number` | A run that FAILED for a transient reason (provider down or overloaded, timeouts, a cut stream, after `retry` gave up) is tried again from where it stopped, waiting 5 s, 15 s, 45 s, then 60 s. Default `3`, `0` = off. Finished tool calls are not repeated; the response reports the failed attempts' tool calls and usage. Never for aborts, 400/401/403/404, bad config, budgets or limits. `run()` and queued runs only, not `stream()`. |
|
|
135
|
+
| `resume` | `"automatic" \| "explicit"` | Crash recovery (needs `persist: { dir }` + the same `sessionId`): `automatic` (default) continues a cut-off run when the Agent is constructed, `explicit` waits for `agent.resume()`. Finished tool calls are never run again. `agent.interrupted()` lists cut-off runs. |
|
|
136
|
+
| `persist` | `{ dir?: string; file?: string }` | Durable sessions, saved after every step (user/assistant/tool/sub-agent). `{ dir }` keeps `sessions/<sessionId>/session.json` (snapshot) plus an append-only `journal.ndjson` (one line per step, so a long chat costs the same per step as a short one), `{ file }` a single `.session.json`, `{}` uses `<home>/sessions`. Writes are durable (fsync, `.bak` of the last good file, atomic rename) and never fail a turn. |
|
|
135
137
|
| `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. |
|
|
136
138
|
|
|
137
139
|
When `dynamicSubagents.enabled` is set without a resolvable worker model, construction throws `SubAgentModelError`.
|
|
@@ -364,7 +366,7 @@ telemetryBus.onEvent((e) => {
|
|
|
364
366
|
|
|
365
367
|
**How it behaves**
|
|
366
368
|
|
|
367
|
-
* **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.
|
|
369
|
+
* **One local clock.** A single timer per home folder (guarded by a one-process lock, `<home>/.lock`) 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.
|
|
368
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.
|
|
369
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`.
|
|
370
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`.
|
|
@@ -406,6 +408,34 @@ const agent = new Agent({
|
|
|
406
408
|
|
|
407
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.
|
|
408
410
|
|
|
411
|
+
|
|
412
|
+
### Asking permission: `ctx.stop`
|
|
413
|
+
|
|
414
|
+
The SDK has no approval modes; you write the check inside `execute` (your own function that asks your user) and call `ctx.stop(reason)` when the answer is no:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
const pay = tool({
|
|
418
|
+
description: "Pay an invoice.",
|
|
419
|
+
input: z.object({ invoiceId: z.string(), amount: z.number() }),
|
|
420
|
+
execute: async ({ invoiceId, amount }, ctx) => {
|
|
421
|
+
if (!(await getPermission(`Pay $${amount} for ${invoiceId}?`))) ctx.stop("user declined the payment");
|
|
422
|
+
return await payInvoice(invoiceId, amount); // never reached after a stop
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`ctx.stop` throws, so the code after it never runs (even if you forget `return`). The reason is saved as that call's result, calls that were running in parallel in the same batch still run to completion (none is skipped or aborted), and then the run ends with `finishReason: "stopped"` and **no further model turn**. The agent waits for the next user message; the model then sees the reason as the tool's result. A steer typed while the batch ran becomes the next run. The stop is saved with the call, so a crash cannot turn a "no" into a "yes": after a restart the run is closed as stopped without calling the model. A tool's `timeoutMs` counts the time spent waiting for your check, so leave it off for tools that ask a person.
|
|
428
|
+
|
|
429
|
+
To guard a built-in tool such as `spawn_subagents` or `scheduled_actions`, wrap it on the agent after construction:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const inner = agent.tools["spawn_subagents"]!;
|
|
433
|
+
agent.tools["spawn_subagents"] = {
|
|
434
|
+
...inner,
|
|
435
|
+
execute: async (input, ctx) => ((await getPermission("Spawn workers?")) ? inner.execute(input, ctx) : ctx.stop("spawning was not approved")),
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
409
439
|
### Tool Failures and Tracing
|
|
410
440
|
|
|
411
441
|
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 } })`.
|
|
@@ -429,6 +459,20 @@ Every call also emits `tool.*` events on `telemetryBus` (nothing is written unle
|
|
|
429
459
|
telemetryBus.onEvent((e) => e.code.startsWith("tool.") && appendFileSync("tools.ndjson", JSON.stringify(e) + "\n"));
|
|
430
460
|
```
|
|
431
461
|
|
|
462
|
+
**Tool logs.** With `persist: { dir }` every tool call also gets its own log file in the session folder, `tool-logs/<n>-<tool>.ndjson` (numbered in the order calls start, continuing after restarts). The SDK writes the input, each attempt, retry, the full result or error (stack included) and the duration; your tool adds its own lines with `ctx.log(message, data?)`. Every line is flushed to disk as it is written, so after a crash the file shows how far the call got:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
execute: async ({ files }, ctx) => {
|
|
466
|
+
for (const [i, f] of files.entries()) {
|
|
467
|
+
await upload(f);
|
|
468
|
+
ctx.log(`uploaded ${i + 1} of ${files.length}`, { file: f });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The call's result carries `logFile` (relative to the session folder), and the saved session keeps only that reference. The log is for you and your harness: it is never sent to the model, nothing in the SDK reads it back (a tool that lets the agent read its own logs is yours to build), and it is not size-capped. Without `persist: { dir }` (or for `stateless` agents) nothing is written and `ctx.log` is a no-op.
|
|
474
|
+
|
|
475
|
+
|
|
432
476
|
### Tool Timing and Deadlines
|
|
433
477
|
|
|
434
478
|
Every `ToolResultRecord` includes `durationMs`, measured in whole milliseconds with a minimum displayed value of `1ms`. The value covers the tool execution attempt (including retry/backoff time), but excludes queue wait and input-schema validation.
|
|
@@ -520,6 +564,24 @@ Model resolution never silently selects a default model: an empty model throws.
|
|
|
520
564
|
|
|
521
565
|
An unknown prefix with no catalog match still routes to a custom provider, allowing private model IDs and endpoints.
|
|
522
566
|
|
|
567
|
+
### Google long decode (Gemini 4)
|
|
568
|
+
|
|
569
|
+
Google caps one request at 65,536 output tokens. Set `maxOutputTokens` above
|
|
570
|
+
that on a `gemini-4-*` model and the SDK runs the request as a background
|
|
571
|
+
interaction automatically (no extra option): up to 1,048,576 tokens minus the
|
|
572
|
+
input, for up to 24 hours. `run()` polls until it finishes (2 s, then every
|
|
573
|
+
5 s); `stream()` reconnects after a dropped or silent connection
|
|
574
|
+
(`last_event_id`, 15 min of silence counts as dropped). Aborting cancels the
|
|
575
|
+
interaction on Google's side, so it stops billing. Each HTTP request is still
|
|
576
|
+
timed by `limits.requestTimeoutMs`, but the whole run is not. Any other Google
|
|
577
|
+
model with `maxOutputTokens` above 65,536 fails before the network with a
|
|
578
|
+
`ConfigError`. Use it for hard reasoning, not chat: you pay for every token of
|
|
579
|
+
every 64K hop.
|
|
580
|
+
|
|
581
|
+
A custom `Provider` can opt into the same treatment with the optional
|
|
582
|
+
`isLongRunning(model, options)` method: when it returns `true`, the loop skips
|
|
583
|
+
its whole-call, first-byte and idle timers and leaves timing to the adapter.
|
|
584
|
+
|
|
523
585
|
### Registry
|
|
524
586
|
|
|
525
587
|
* `resolveModel("google/x" | ModelSpec)` → `{ provider, modelId, modelSpec? }`. Use to inspect routing before execution.
|
|
@@ -909,7 +971,7 @@ SubAgentExecutionMetadata {
|
|
|
909
971
|
}
|
|
910
972
|
```
|
|
911
973
|
|
|
912
|
-
`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).
|
|
974
|
+
`finishReason` is `"stopped"` when a tool ended the run with `ctx.stop(reason)` (see *Asking permission*). It 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).
|
|
913
975
|
|
|
914
976
|
### SubAgentStep
|
|
915
977
|
|
|
@@ -1118,10 +1180,19 @@ Message {
|
|
|
1118
1180
|
role: "system" | "user" | "assistant" | "tool",
|
|
1119
1181
|
content: string | ContentPart[],
|
|
1120
1182
|
name?,
|
|
1121
|
-
thoughtSignature
|
|
1183
|
+
thoughtSignature?,
|
|
1184
|
+
providerSteps? // { provider, steps }: raw output steps of an assistant turn
|
|
1122
1185
|
}
|
|
1123
1186
|
```
|
|
1124
1187
|
|
|
1188
|
+
`providerSteps` keeps a provider's raw output for one assistant turn (today:
|
|
1189
|
+
Google's `steps`, including thought signatures). When Google needs the full
|
|
1190
|
+
history again (after a restart, an expired chain or a provider switch), those
|
|
1191
|
+
turns are resent exactly as received, which keeps reasoning continuity and
|
|
1192
|
+
the implicit-cache prefix intact. Other providers ignore it. Adapters return
|
|
1193
|
+
it on `ProviderGenerateResult` / per-turn stream `AgentResponse`; it is never
|
|
1194
|
+
part of `toJSON()`.
|
|
1195
|
+
|
|
1125
1196
|
### ProviderContext
|
|
1126
1197
|
|
|
1127
1198
|
```ts
|
|
@@ -1140,9 +1211,9 @@ Supported content parts include:
|
|
|
1140
1211
|
* `ThinkingPart { type:"thinking", thinking, thoughtSignature? }` — preserved reasoning.
|
|
1141
1212
|
* `ToolCallPart { type:"tool_call", id, name, arguments, rawArguments?, thoughtSignature? }`
|
|
1142
1213
|
* `ToolResultPart { type:"tool_result", id, name, result, isError? }`
|
|
1143
|
-
* `ImagePart { type:"image", image, mimeType? }`
|
|
1214
|
+
* `ImagePart { type:"image", image, mimeType?, resolution? }`
|
|
1144
1215
|
* `AudioPart { type:"audio", audio, mimeType? }`
|
|
1145
|
-
* `VideoPart { type:"video", video, mimeType? }`
|
|
1216
|
+
* `VideoPart { type:"video", video, mimeType?, resolution? }`
|
|
1146
1217
|
* `FilePart { type:"file", file, mimeType?, filename? }` — PDFs/documents.
|
|
1147
1218
|
|
|
1148
1219
|
`image`, `audio`, `video`, and `file` inputs accept:
|
|
@@ -1171,6 +1242,20 @@ Media normalization is handled automatically by providers (mapped to provider-na
|
|
|
1171
1242
|
Provider matrix: text + image + wav/mp3 audio + PDF work on all supported
|
|
1172
1243
|
providers. Video works on Gemini only.
|
|
1173
1244
|
|
|
1245
|
+
On Google, links Google fetches itself go as a `uri` without downloading:
|
|
1246
|
+
YouTube links, Files API links, `gs://` objects and public `https://` URLs of
|
|
1247
|
+
URL-supported types (PDF, CSV, text, JSON, PNG/JPEG/WebP/BMP, common video;
|
|
1248
|
+
max 15 MB). Audio URLs and `http://` links are downloaded and inlined. Inline
|
|
1249
|
+
types are checked before the network; Google reads only PDF and CSV
|
|
1250
|
+
documents. The supported lists are the `GOOGLE_*_MIME` constants at the top of
|
|
1251
|
+
`src/providers/google.ts`.
|
|
1252
|
+
|
|
1253
|
+
`resolution` (`MediaResolution`: `"low" | "medium" | "high" | "ultra_high"`)
|
|
1254
|
+
sets how much detail Google spends on one image or video, and so how many
|
|
1255
|
+
input tokens it costs (image: 280 / 560 / 1120 / 2240; video per frame:
|
|
1256
|
+
`low` and `medium` 70, `high` 280). Omit it for Google's default. Other
|
|
1257
|
+
providers ignore it with one `provider_warning`.
|
|
1258
|
+
|
|
1174
1259
|
Before any network call, the executor checks `image` / `audio` / `video` /
|
|
1175
1260
|
`file`-as-PDF parts against the catalog's `modalities.input` for that
|
|
1176
1261
|
provider/model and fails fast with a one-line error naming the gap
|
|
@@ -1189,6 +1274,7 @@ never in the gitignored snapshot.
|
|
|
1189
1274
|
* `getEnv(key, fallback?)` — resolves an environment variable.
|
|
1190
1275
|
* `getApiKey(provider, explicit?, env?)` — resolves API keys using the configured environment lookup order.
|
|
1191
1276
|
* `buildSessionHeaders(provider, cache?, custom?, sessionId?)` — builds provider-specific affinity headers. Normally handled automatically.
|
|
1277
|
+
* Two retry layers: `retry` repeats one failed provider CALL (seconds); `runRetries` repeats a whole failed RUN from where it stopped (minutes, `agent.run_retry` on the bus) while the agent stays busy, so queued prompts wait.
|
|
1192
1278
|
* `withRetries(fn, { maxRetries?, maxRetryDelayMs?, signal?, label? })` / `isTransientError(err)` — bounded retries for transient provider failures (defaults: 2 retries, 5s cap, aborts never retried).
|
|
1193
1279
|
|
|
1194
1280
|
---
|
|
@@ -1234,6 +1320,10 @@ bun run examples/13-structured-output.ts
|
|
|
1234
1320
|
bun run examples/14-scheduled-actions.ts [--quick]
|
|
1235
1321
|
# scheduled actions walkthrough on your computer's clock: starts working ~1 minute from now,
|
|
1236
1322
|
# delivers ~2 minutes from now (code-scheduled and model-scheduled); --quick skips the waiting
|
|
1323
|
+
|
|
1324
|
+
bun run examples/15-durable-agent.ts
|
|
1325
|
+
# a durable agent: killed mid-tool (SIGKILL), picked up by a new process (interrupted()/resume()),
|
|
1326
|
+
# finished tools not repeated, run retry, ctx.stop approval, tool logs (about 5 s of waiting)
|
|
1237
1327
|
```
|
|
1238
1328
|
|
|
1239
1329
|
Every example ends its `run()` with `.catch(fail)` (`examples/_shared.ts`),
|
|
@@ -1285,10 +1375,10 @@ saveSessionFile(".session.json", agent, telemetry);
|
|
|
1285
1375
|
|
|
1286
1376
|
* `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.
|
|
1287
1377
|
* `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.
|
|
1288
|
-
* Files are JSON with one line per top-level field and one line per turn (2-space indent), written
|
|
1378
|
+
* Files are JSON with one line per top-level field and one line per turn (2-space indent), written durably (temp file, fsync, `.bak` of the last good version, atomic rename; a damaged file falls back to its `.bak` on load). Google turns also store their raw `providerSteps`, so a resumed session replays them exactly.
|
|
1289
1379
|
* 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.
|
|
1290
1380
|
* 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).
|
|
1291
|
-
* With `persist: { dir }` or `{ file }
|
|
1381
|
+
* With `persist: { dir }` each step appends one line to `journal.ndjson` (fsynced); the journal is folded into a fresh `session.json` after 500 lines or 4 MB, or when history changes another way (rollback, `reset()`, `importSession`). `loadSessionDir` replays the journal over the snapshot; a torn last line is ignored, damage earlier stops the replay with a `session.journal_corrupt` warning. A run that a crash cut off is picked up again by a new Agent with the same `sessionId` (see `resume`): the conversation is reloaded, tool calls with a saved result are not repeated, the rest run, then waiting prompts follow. The journal also records where each run began (`runs`: id, index of its user message, time) and the prompts still waiting in the steer inbox and follow-up queue (`pending`, read back by `loadSessionDir` as `session.pending`; `importSession` restores `runs`). Embedded media is written once as `media/<kind>-h<hash>.<ext>`. With `persist: { file }` the single file is rewritten after every step. Either way a crash loses at most the in-flight step. Do not call `saveSessionDir` on a folder an Agent persists to.
|
|
1292
1382
|
|
|
1293
1383
|
---
|
|
1294
1384
|
## Scripts and Structure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-accelerator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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",
|