@anvia/core 1.6.0 → 1.7.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 (96) hide show
  1. package/README.md +35 -1183
  2. package/dist/agent/agent-tool.js +4 -4
  3. package/dist/agent/agent.js +17 -17
  4. package/dist/agent/index.d.ts +8 -8
  5. package/dist/agent/index.js +21 -21
  6. package/dist/agent/resolve-options.js +7 -7
  7. package/dist/agent/resolved-agent.js +18 -18
  8. package/dist/agent/team/agent-team.js +21 -21
  9. package/dist/agent/team/index.js +21 -21
  10. package/dist/agent/tool-catalog.js +6 -6
  11. package/dist/{agent-BUc18r6A.d.ts → agent-BOoZ3jbr.d.ts} +5 -5
  12. package/dist/chunk-5H5EHU5O.js +7 -0
  13. package/dist/chunk-5H5EHU5O.js.map +1 -0
  14. package/dist/{chunk-OBTD3GPY.js → chunk-5ZS3DPPC.js} +4 -4
  15. package/dist/{chunk-MJAM7HZB.js → chunk-6EJHZEXU.js} +6 -6
  16. package/dist/{chunk-KGVDUYZV.js → chunk-7NYFTW7I.js} +3 -3
  17. package/dist/{chunk-FTK6YRLN.js → chunk-C36RVQHF.js} +25 -6
  18. package/dist/chunk-C36RVQHF.js.map +1 -0
  19. package/dist/{chunk-HDL5TXF7.js → chunk-E5MIZ6Z5.js} +2 -2
  20. package/dist/{chunk-GB4SRUT7.js → chunk-IZH2ZAE3.js} +2 -2
  21. package/dist/{chunk-7LBUXGJI.js → chunk-JJSMONFE.js} +2 -2
  22. package/dist/{chunk-4WSICA6D.js → chunk-JU3RX364.js} +2 -2
  23. package/dist/{chunk-TOJSC4GI.js → chunk-KRQ2HFXW.js} +3 -3
  24. package/dist/{chunk-IMSM4HHI.js → chunk-MYHGOXHQ.js} +2 -2
  25. package/dist/{chunk-TD2VYYUT.js → chunk-OBM7JJCK.js} +2 -2
  26. package/dist/{chunk-STEAW4JD.js → chunk-OXIK4SIH.js} +2 -2
  27. package/dist/{chunk-KITX3HDN.js → chunk-SBWSXEIH.js} +1 -1
  28. package/dist/chunk-SBWSXEIH.js.map +1 -0
  29. package/dist/{chunk-NKSXYOUM.js → chunk-VDHMFJCD.js} +2 -2
  30. package/dist/{chunk-SCFWN3T2.js → chunk-VHPT2C4P.js} +1 -1
  31. package/dist/chunk-VHPT2C4P.js.map +1 -0
  32. package/dist/{chunk-5BO4OTTK.js → chunk-WCJRGYF7.js} +2 -2
  33. package/dist/{chunk-XQTTPQWA.js → chunk-XH2CVR4V.js} +2 -2
  34. package/dist/{chunk-D6FK5ZQR.js → chunk-Y7Z5EHJD.js} +5 -5
  35. package/dist/{chunk-ZJ4T7CEX.js → chunk-ZMAF3I4H.js} +2 -2
  36. package/dist/{chunk-GRI4EFNG.js → chunk-ZRW27E32.js} +28 -21
  37. package/dist/chunk-ZRW27E32.js.map +1 -0
  38. package/dist/{dynamic-tools-BXXLc34l.d.ts → dynamic-tools-B3OjYyhC.d.ts} +1 -1
  39. package/dist/evals/index.d.ts +2 -2
  40. package/dist/guardrails/index.js +4 -4
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -28
  43. package/dist/internal/agent-runtime/agent-run.js +13 -13
  44. package/dist/internal/agent-runtime/execution.js +7 -0
  45. package/dist/internal/agent-runtime/execution.js.map +1 -0
  46. package/dist/internal/agent-runtime/prepared-tool-call.js +2 -2
  47. package/dist/internal/agent-runtime/retrieval.js +4 -4
  48. package/dist/internal/agent-runtime/run-options.js +1 -1
  49. package/dist/internal/agent-runtime/tool-execution.js +8 -8
  50. package/dist/internal/agent.d.ts +30 -8
  51. package/dist/internal/agent.js +22 -18
  52. package/dist/internal/team-runtime/run.js +20 -20
  53. package/dist/internal/team-runtime/tools.js +4 -4
  54. package/dist/mcp/index.d.ts +3 -3
  55. package/dist/{middleware-BxzML2hK.d.ts → middleware-BNxyEpUX.d.ts} +1 -1
  56. package/dist/observability/index.d.ts +2 -2
  57. package/dist/pipeline/index.d.ts +6 -6
  58. package/dist/skills/index.d.ts +3 -3
  59. package/dist/skills/index.js +9 -9
  60. package/dist/skills/load.js +9 -9
  61. package/dist/skills/tools.js +8 -8
  62. package/dist/speech-generation/index.js +1 -1
  63. package/dist/{think-tool-2unWbq1w.d.ts → think-tool-DJbtUDZm.d.ts} +1 -1
  64. package/dist/tool/create-tool.js +3 -3
  65. package/dist/tool/dynamic-tools.js +5 -5
  66. package/dist/tool/index.d.ts +4 -4
  67. package/dist/tool/index.js +7 -7
  68. package/dist/tool/question-tool.js +4 -4
  69. package/dist/tool/think-tool.js +4 -4
  70. package/dist/tool/tool.js +1 -1
  71. package/dist/{tool-D_jV3cRZ.d.ts → tool-W1cAPuRF.d.ts} +2 -0
  72. package/dist/{types-0t3BczrD.d.ts → types-94Q2mPRa.d.ts} +1 -1
  73. package/dist/{types-Cu2tEf3V.d.ts → types-CIG2loIE.d.ts} +1 -1
  74. package/dist/vector-store/index.d.ts +1 -1
  75. package/dist/vector-store/index.js +4 -4
  76. package/package.json +1 -1
  77. package/dist/chunk-FTK6YRLN.js.map +0 -1
  78. package/dist/chunk-GRI4EFNG.js.map +0 -1
  79. package/dist/chunk-KITX3HDN.js.map +0 -1
  80. package/dist/chunk-SCFWN3T2.js.map +0 -1
  81. /package/dist/{chunk-OBTD3GPY.js.map → chunk-5ZS3DPPC.js.map} +0 -0
  82. /package/dist/{chunk-MJAM7HZB.js.map → chunk-6EJHZEXU.js.map} +0 -0
  83. /package/dist/{chunk-KGVDUYZV.js.map → chunk-7NYFTW7I.js.map} +0 -0
  84. /package/dist/{chunk-HDL5TXF7.js.map → chunk-E5MIZ6Z5.js.map} +0 -0
  85. /package/dist/{chunk-GB4SRUT7.js.map → chunk-IZH2ZAE3.js.map} +0 -0
  86. /package/dist/{chunk-7LBUXGJI.js.map → chunk-JJSMONFE.js.map} +0 -0
  87. /package/dist/{chunk-4WSICA6D.js.map → chunk-JU3RX364.js.map} +0 -0
  88. /package/dist/{chunk-TOJSC4GI.js.map → chunk-KRQ2HFXW.js.map} +0 -0
  89. /package/dist/{chunk-IMSM4HHI.js.map → chunk-MYHGOXHQ.js.map} +0 -0
  90. /package/dist/{chunk-TD2VYYUT.js.map → chunk-OBM7JJCK.js.map} +0 -0
  91. /package/dist/{chunk-STEAW4JD.js.map → chunk-OXIK4SIH.js.map} +0 -0
  92. /package/dist/{chunk-NKSXYOUM.js.map → chunk-VDHMFJCD.js.map} +0 -0
  93. /package/dist/{chunk-5BO4OTTK.js.map → chunk-WCJRGYF7.js.map} +0 -0
  94. /package/dist/{chunk-XQTTPQWA.js.map → chunk-XH2CVR4V.js.map} +0 -0
  95. /package/dist/{chunk-D6FK5ZQR.js.map → chunk-Y7Z5EHJD.js.map} +0 -0
  96. /package/dist/{chunk-ZJ4T7CEX.js.map → chunk-ZMAF3I4H.js.map} +0 -0
package/README.md CHANGED
@@ -1,1219 +1,71 @@
1
1
  # @anvia/core
2
2
 
3
- Small, explicit, embeddable runtime contracts for Anvia agents, tools, structured extraction, pipelines, streaming, RAG, MCP, skills, and observability.
3
+ Build provider-independent AI agents and workflows with typed tools, streaming, structured output,
4
+ and application-owned memory. Core runs the model/tool loop while your application chooses providers,
5
+ credentials, storage, and delivery.
4
6
 
5
- This package is provider-neutral. Pair it with a provider adapter such as `@anvia/openai`, `@anvia/anthropic`, or `@anvia/gemini` to create runnable model objects, then pass those objects into agents, extractors, pipelines, or direct completion helpers.
6
-
7
- ## Design Philosophy
8
-
9
- `@anvia/core` owns the model/tool loop and the runtime contracts around it. Your application owns provider client construction, credentials, product data access, permissions, storage, deployment, observability backends, and response shape.
10
-
11
- The package is dependency-injection oriented: create provider models, typed tools, memory stores, vector indexes, observers, and services in application code, then pass the relevant objects into agents, prompt requests, runners, or adapters. Core receives those objects and coordinates the run without taking over product architecture.
12
-
13
- ## Installation
14
-
15
- Anvia 1.0 is currently published on npm under the `rc` tag. The unqualified `latest` tag remains
16
- on the maintenance-only v0 line until 1.0 reaches general availability. Keep Core and every Anvia
17
- adapter on the same release channel.
7
+ ## Install
18
8
 
19
9
  ```sh
20
- pnpm add @anvia/core@rc
10
+ pnpm add @anvia/core @anvia/openai zod
21
11
  ```
22
12
 
23
- ### Bun
13
+ This example uses OpenAI. Other provider adapters work with the same Core APIs.
24
14
 
25
- Bun 1.3.14 is the currently tested and supported runtime baseline. Install Core on the same
26
- release channel as the rest of your Anvia packages:
15
+ ## Quickstart
27
16
 
28
- ```sh
29
- bun add @anvia/core@rc
30
- ```
31
-
32
- The repository itself remains a pnpm workspace; Bun support applies to applications consuming the
33
- published package.
34
-
35
- See the repository's [1.0 release policy](../../docs/releases/v1.md) for channel and support
36
- details.
37
-
38
- In this monorepo, the package is available through the workspace:
39
-
40
- ```sh
41
- pnpm --filter @anvia/core build
42
- ```
43
-
44
- ## Usage
17
+ Set `OPENAI_API_KEY` in your server environment, then create an agent with a typed tool:
45
18
 
46
19
  ```ts
47
- import { z } from "zod";
48
20
  import { Agent, createTool } from "@anvia/core";
49
21
  import { OpenAIClient } from "@anvia/openai";
22
+ import { z } from "zod";
50
23
 
51
- const client = new OpenAIClient({
52
- apiKey,
53
- });
54
-
55
- const model = client.completionModel({ modelId: "gpt-5", api: "responses" });
56
-
24
+ const apiKey = process.env.OPENAI_API_KEY;
25
+ if (!apiKey) throw new Error("Set OPENAI_API_KEY.");
26
+ const client = new OpenAIClient({ apiKey });
57
27
  const lookupOrder = createTool({
58
28
  name: "lookup_order",
59
- description: "Look up an order by id.",
29
+ description: "Look up an order by ID.",
60
30
  inputSchema: z.object({ orderId: z.string() }),
61
31
  execute: async ({ orderId }) => ({ orderId, status: "processing" }),
62
32
  });
63
33
 
64
34
  const agent = new Agent({
65
35
  id: "support",
66
- model,
36
+ model: client.completionModel({ modelId: "gpt-5", api: "responses" }),
67
37
  instructions: "Help customers with order questions.",
68
- maxTurns: 4,
69
38
  tools: [lookupOrder],
39
+ maxTurns: 4,
70
40
  });
71
41
 
72
42
  const result = await agent.generate({ prompt: "What is happening with order A123?" });
73
43
  if (result.type === "response") console.log(result.output);
74
44
  ```
75
45
 
76
- ## Agent teams
77
-
78
- `AgentTeam` coordinates ordinary `Agent` definitions and gives each spawned instance its own
79
- conversation and inbox. The coordinator chooses when to spawn members and can create multiple
80
- instances of the same definition. Tools keep the usual `tools: []` shape; members receive only their
81
- own configured tools and the runtime's communication tools.
82
-
83
- ```ts
84
- import { Agent, AgentTeam } from "@anvia/core/agent";
85
-
86
- // model, readFileTool, and searchTool are application-owned objects.
87
- const researcher = new Agent({
88
- id: "researcher",
89
- model,
90
- description: "Research a specific question and report evidence.",
91
- instructions: "Ask your parent for clarification when needed.",
92
- tools: [readFileTool, searchTool],
93
- });
94
-
95
- const reviewer = new Agent({
96
- id: "reviewer",
97
- model,
98
- description: "Review findings for errors and unsupported claims.",
99
- tools: [readFileTool],
100
- });
101
-
102
- const team = new AgentTeam({
103
- id: "research-team",
104
- model,
105
- instructions: [
106
- "Delegate research to researcher instances.",
107
- "Send their findings to a reviewer and incorporate its feedback.",
108
- "Answer the user after the work is complete.",
109
- ].join("\n"),
110
- members: [researcher, reviewer],
111
- limits: {
112
- maxDepth: 3,
113
- maxConcurrentAgents: 4,
114
- maxAgentInstances: 12,
115
- maxTotalTurns: 100,
116
- maxBufferedEvents: 1024,
117
- },
118
- });
119
-
120
- const result = await team.generate({ prompt: "Investigate the proposed architecture." });
121
- if (result.type === "response") console.log(result.output);
122
- console.log(result.teamRunId, result.usage, result.members);
123
- ```
124
-
125
- The coordinator supports normal Agent configuration, including `outputSchema`, context, tools,
126
- guardrails, middleware, and lifecycle callbacks. Its schema determines the team's output type.
127
- Each member preserves its own configuration. Run-level options apply to the coordinator; the
128
- abort signal, interaction resolver, and team limits apply to the whole execution.
129
-
130
- The runtime adds these model-callable tools:
131
-
132
- | Tool | Available to | Input |
133
- | ------------------- | --------------------------------- | ----------------------------- |
134
- | `spawn_<member.id>` | Coordinator and permitted parents | `{ prompt, name? }` |
135
- | `send_message` | All instances | `{ to, content, replyTo? }` |
136
- | `wait_for_agent` | All instances | `{ instanceId?, timeoutMs? }` |
137
- | `list_agents` | All instances | `{}` |
138
- | `cancel_agent` | Instances with spawn permissions | `{ instanceId, reason? }` |
139
-
140
- Spawning returns an `instanceId` immediately. A definition's `agentId` stays the same across its
141
- instances; each assignment or resumed interaction has a distinct `runId`. Member IDs must contain
142
- 1–58 letters, digits, underscores, or hyphens so generated tool names are valid. Configured tools
143
- must not use reserved coordination names.
144
-
145
- Routing is between a parent and its children by default. A member can use `to: "parent"`;
146
- parents address children by instance ID. Enable sibling communication and recursive spawning
147
- with explicit permissions:
148
-
149
- ```ts
150
- const team = new AgentTeam({
151
- id: "research-team",
152
- model,
153
- members: [researcher, reviewer],
154
- communication: { siblings: true },
155
- spawning: [{ from: researcher, to: [researcher, reviewer] }],
156
- limits: { maxDepth: 3, maxConcurrentAgents: 4, maxAgentInstances: 12, maxTotalTurns: 100 },
157
- });
158
- ```
159
-
160
- `members` is the definition catalog available to the coordinator. A spawn rule allows every
161
- instance of `from` to create only the definitions in `to`, using their generated spawn tools.
162
- Use the same Agent objects registered in `members`; duplicate rules or targets are rejected.
163
- Rules and communication options are snapshotted at construction. Without rules, only the
164
- coordinator can spawn. A self-reference enables recursion; each child has its own conversation
165
- and its definition's tools, without inheriting the parent's tools.
166
-
167
- With `communication.siblings: true`, instances sharing the same parent instance can discover,
168
- message, and wait for one another. Cousins and other branches remain inaccessible. `list_agents`
169
- includes self, parent, direct children, and enabled siblings, with `parentInstanceId` and `depth`.
170
- Sibling access does not grant cancellation or approval authority. Only a parent can cancel its
171
- direct child; cancellation also stops that child's entire subtree, including idle descendants.
172
-
173
- `send_message` returns a queued receipt, not a reply. Messages arrive at safe turn/tool boundaries,
174
- are explicitly attributed to their sender, and cannot satisfy a tool approval. Sending to an idle
175
- member starts a follow-up run with its retained conversation. Failed or cancelled members cannot
176
- receive follow-ups. Each inbox accepts at most 128 pending inputs.
177
-
178
- `wait_for_agent` wakes on a relevant message, outcome, cancellation, or timeout (default 30 seconds,
179
- maximum 5 minutes). Waiting agents and agents awaiting application input release their concurrency
180
- slot. Every parent waits automatically for its own children before finalizing and receives their
181
- outcomes as attributed messages. A member failure is reported to its immediate parent; failure or
182
- blocking cancels its descendants. A coordinator error or team cancellation rejects the execution.
183
- Guardrail blocking remains a `blocked` outcome. The final `members` array includes all descendants,
184
- with `parentInstanceId` and `depth` to reconstruct the tree.
185
-
186
- The limits in the first example are the defaults. `maxDepth` is a positive integer; the coordinator
187
- is depth 0 and direct children are depth 1. Concurrent and total instance limits include the coordinator.
188
- The instance limit counts all instances created during this execution, including completed or
189
- cancelled ones. The turn budget covers all instances and follow-ups; provider retries remain
190
- governed by normal retry settings. Reaching the turn budget fails the team. Reaching the instance
191
- or depth limit rejects the spawn tool call so the parent can use existing members. All budgets are
192
- shared across the whole tree; spawning does not reset them. Tools within one
193
- instance execute sequentially; different instances can execute concurrently.
194
-
195
- ### Team streaming and steering
196
-
197
- ```ts
198
- const stream = team.stream({ prompt: "Investigate the proposed architecture." });
199
-
200
- // Call from the application's user-input handler while the team is running:
201
- // stream.steer({ prompt: "Also consider deployment complexity." });
202
- // stream.cancel("User cancelled.");
203
-
204
- for await (const event of stream.events) {
205
- if (event.type === "agent_event" && event.event.type === "text_delta") {
206
- console.log(event.instanceId, event.event.delta);
207
- }
208
- }
209
- const outcome = await stream.result;
210
- ```
211
-
212
- `events` is an attributed union of agent events, member state changes, queued/delivered messages,
213
- interactions, and the terminal team outcome. New members emit `agent_queued` before acquiring a
214
- concurrency slot. `textStream` yields coordinator text only, including
215
- provisional turns before its final answer. `text` resolves to the final text. Choose one event or text
216
- consumer; final promises can be read alongside it. Reading only a final promise consumes the run
217
- without buffering unused events. Non-streaming models also work and emit complete response text
218
- instead of provider deltas. Closing the event iterator cancels unfinished work.
219
-
220
- Streaming buffers at most `limits.maxBufferedEvents` unread events (default 1,024). If a consumer
221
- falls behind and fills that buffer, the team cancels its running work, discards unread events, and
222
- rejects both the iterator and final promises with `AgentTeamLimitError` whose `limit` is
223
- `"maxBufferedEvents"`. This bounds queued event count, not the byte size of individual events.
224
- Increase the limit when your application needs to tolerate longer pauses between reads.
225
- Result-only consumption does not buffer events and is unaffected by this limit.
226
-
227
- ### Team approvals and questions
228
-
229
- Only the application resolves interactions. A coordinator or reviewer cannot approve another
230
- member's protected tool through a message. Existing tool `requiresApproval` and question tools use
231
- the same interaction contracts as `Agent`:
232
-
233
- ```ts
234
- import type { AgentTeamInteraction } from "@anvia/core/agent";
235
- import type { AgentInteractionResponse } from "@anvia/core/agent/interactions";
236
-
237
- // Implement this with your application's authenticated approval/question UI.
238
- declare function askUser(request: AgentTeamInteraction): Promise<AgentInteractionResponse>;
239
-
240
- const result = await team.generate({
241
- prompt: "Investigate and propose changes.",
242
- resolveInteraction: (request) => askUser(request),
243
- });
244
- ```
245
-
246
- The resolver receives `teamRunId`, `instanceId`, `runId`, the interaction request, and an abort
247
- signal. A missing, throwing, or invalid resolver fails the team with `AgentTeamInteractionError`
248
- before the protected action executes. Approval denial follows the ordinary Agent behavior: the
249
- tool is skipped and its member can continue. Other members may continue while an interaction is
250
- pending. The application must decide who may approve; agent-generated content is not authorization.
251
-
252
- Teams currently live in one process for one execution. Each `generate` or `stream` call creates
253
- fresh instances. Pass `messages` instead of `prompt` to supply coordinator history. Member
254
- conversations are retained only within that execution; team session persistence and durable
255
- checkpoint/resume are not provided. Cancellation signals reach running models, tools, and
256
- resolvers; work owned by the application must honor its signal to stop external activity.
257
- Studio provides a team playground with member activity, messages, and approval/question controls.
258
-
259
- ## Direct Completions
46
+ Replace the sample tool body with your application's data access. Agent outcomes distinguish a
47
+ response, a guardrail block, and an interaction that needs application input.
260
48
 
261
- Use `generateCompletion` for one provider call without Agent turns, memory, or local tool
262
- execution. The model and input are part of one options object:
263
-
264
- ```ts
265
- import { generateCompletion } from "@anvia/core";
266
- import { OpenAIClient } from "@anvia/openai";
267
-
268
- const model = new OpenAIClient({ apiKey }).completionModel({ modelId: "gpt-5", api: "responses" });
269
-
270
- const result = await generateCompletion({
271
- model,
272
- prompt: "Summarize Anvia in one sentence.",
273
- instructions: "Answer clearly and concisely.",
274
- });
275
-
276
- console.log(result.output); // string
277
- ```
49
+ ## What you can build
278
50
 
279
- Use `messages` when the application already owns the transcript. Exactly one of `prompt` or
280
- `messages` is required:
281
-
282
- ```ts
283
- import { generateCompletion, type Message } from "@anvia/core";
284
-
285
- const result = await generateCompletion({
286
- model,
287
- messages: [
288
- { role: "system", content: "You are concise." },
289
- { role: "user", content: "Explain Anvia." },
290
- ] satisfies readonly Message[],
291
- maxTokens: 300,
292
- providerOptions: {
293
- reasoning: { effort: "low" },
294
- },
295
- });
296
- ```
297
-
298
- Messages are plain, readonly `{ role, content }` objects. Use structural literals with
299
- `satisfies Message` or `satisfies readonly Message[]`; there is no message factory namespace.
300
- Multimodal user content uses `text`, `image`, and `file` parts, while assistant tool calls use
301
- `{ type: "tool-call", toolCallId, toolName, input }`. At external boundaries, validate with
302
- `parseMessage`, `parseMessages`, `messageSchema`, or an application-specific
303
- `createMessageSchema({ metadataSchema })`.
304
-
305
- Add `outputSchema` to the same function for typed, schema-validated output:
306
-
307
- ```ts
308
- import { generateCompletion } from "@anvia/core";
309
- import { z } from "zod";
51
+ - Agents with typed tools, approvals, questions, guardrails, and streaming responses.
52
+ - Agent teams with isolated conversations, delegation, and coordinated cancellation.
53
+ - Direct completions and schema-validated extraction without an agent loop.
54
+ - Sequential or parallel pipelines and evaluations with built-in or custom metrics.
55
+ - Stateful conversations using memory adapters and explicit compaction policies.
56
+ - RAG with document chunking, embeddings, and vector-store contracts.
57
+ - Image generation, speech, transcription, MCP tools, and local Agent Skills.
310
58
 
311
- const result = await generateCompletion({
312
- model,
313
- prompt: "Extract: Acme reports a high-priority checkout failure.",
314
- outputSchema: z.object({
315
- customer: z.string(),
316
- priority: z.enum(["low", "medium", "high"]),
317
- }),
318
- });
319
-
320
- console.log(result.output.priority); // fully typed
321
- ```
322
-
323
- `outputSchema` accepts any [Standard Schema](https://standardschema.dev) — Zod, Valibot, ArkType, and
324
- other implementing libraries, including transformed schemas whose validation input and output
325
- differ (for example `string -> number`). Zod uses its built-in JSON Schema conversion. Valibot
326
- requires the optional converter `@valibot/to-json-schema` (install it alongside `valibot`);
327
- unsupported Valibot actions surface as conversion errors before any model call. Other libraries
328
- must either implement the Standard JSON Schema interface (`~standard.jsonSchema`) or convert to a
329
- supported library. Providers receive the schema's input representation — the raw response is what
330
- `~standard.validate` accepts — while the validated, possibly transformed value becomes
331
- `result.output`. For Zod, the output representation is used only when it describes the same
332
- accepted values as the input side, so its strict-object refinements (`additionalProperties:
333
- false`, required by provider strict modes such as OpenAI structured outputs) are preserved;
334
- otherwise the input representation is sent. Object nodes without an explicit
335
- `additionalProperties` are completed with `false` in every provider payload for the same reason.
336
- Core copies converter data before refinement, so cached and deeply frozen schemas remain reusable.
337
- Refinement follows schema-bearing keywords only. Literal objects under `const`, `enum`, `default`,
338
- `examples`, and extensions retain their values, even when the converter shares them with schema nodes.
339
- Explicit `additionalProperties` values remain intact, with schema-valued entries refined recursively.
340
-
341
- Converter output must have a plain or null-prototype record root and contain finite JSON data.
342
- Core copies own enumerable record data and dense array elements. It ignores non-enumerable metadata
343
- and removes root `$schema` without reading its value. Nested `$schema` values remain intact.
344
- Cycles, accessors in copied positions, sparse arrays, custom instances, and unsupported JSON values
345
- cause a `TypeError` before a model call. Core does not stringify-coerce converter data.
346
- Schema validation of the provider output is synchronous; asynchronous schemas are rejected with a
347
- `CompletionStructuredOutputError` in the `schema` phase.
348
-
349
- `CompletionResult` consistently contains `output`, the original `text`, normalized `content`,
350
- `usage`, and `rawResponse`, plus optional message, context, source, provider-tool, and finish-reason
351
- metadata. First-party adapters normalize provider termination into `finishReason` (`stop`, `length`,
352
- `content-filter`, `tool-calls`, or `other`) while preserving the provider value separately as
353
- `providerFinishReason`.
354
-
355
- Schema-backed completion checks termination before JSON parsing. `finishReason: "length"` throws
356
- `CompletionStructuredOutputError` with `phase: "truncated"`; `finishReason: "content-filter"`
357
- throws it with `phase: "content-filter"` and is not retried by the default policy. Ordinary text
358
- completion remains intentional: partial text is returned unchanged with its finish reason so the
359
- application decides whether to display, continue, or discard it. A direct structured stream can
360
- similarly end with one typed error event after any already-emitted deltas; it does not silently
361
- retract public stream progress.
362
-
363
- Use `streamCompletion` for the streaming form:
364
-
365
- ```ts
366
- import { streamCompletion } from "@anvia/core";
367
-
368
- for await (const event of streamCompletion({
369
- model,
370
- prompt: "Write a short launch note.",
371
- })) {
372
- if (event.type === "text_delta") process.stdout.write(event.delta);
373
- if (event.type === "final") console.log(event.result.usage);
374
- if (event.type === "error") console.error(event.error);
375
- }
376
- ```
377
-
378
- Tool-call deltas are always emitted when a provider supplies them; there is no opt-in flag. A
379
- high-level stream emits at most one terminal `error` event and then closes. Provider model adapters
380
- use the lower-level `CompletionModelStreamEvent`, whose terminal event is `{ type: "final",
381
- response }`; `streamCompletion` normalizes it to `{ type: "final", result }`.
382
-
383
- The terminal response owns completion metadata, including `contextUsage`, finish reasons, and
384
- `rawResponse`. An empty terminal choice uses accumulated content without replacing that metadata.
385
- A nonempty terminal choice must agree with streamed content. A terminal message ID takes precedence.
386
- If absent, the streamed ID supplies the fallback. Sources merge by URL and text offsets, and
387
- provider-tool calls merge by ID. Terminal duplicates replace streamed entries without duplication.
388
- Retry usage includes failed attempts, but context usage and the raw response remain those of the
389
- successful terminal response.
390
-
391
- Client requests carry core `Message[]`, so an endpoint can validate and pass them directly:
392
-
393
- ```ts
394
- import { completionToClientStream, parseClientStreamRequest } from "@anvia/client";
395
- import { streamCompletion } from "@anvia/core";
396
- import { createClientStreamResponse } from "@anvia/server";
397
-
398
- const body = parseClientStreamRequest(await request.json());
399
- const events = completionToClientStream({
400
- events: streamCompletion({ model, messages: body.messages }),
401
- });
402
- return createClientStreamResponse({ events });
403
- ```
404
-
405
- Core does not own UI messages, HTTP transports, or the public wire protocol. Those boundaries live
406
- in `@anvia/client`, `@anvia/server`, and framework packages such as `@anvia/react`.
407
-
408
- ## Retries, Provider Options, and Cancellation
409
-
410
- Direct completion and media calls accept `retries?: RetryOptions | false`. An omitted or `false`
411
- value makes one provider attempt; `{}` enables the default retry policy. Only retry-safe provider
412
- calls are repeated.
413
-
414
- ```ts
415
- const controller = new AbortController();
416
-
417
- const result = await generateCompletion({
418
- model,
419
- prompt: "Summarize this incident.",
420
- retries: { maxAttempts: 3, initialDelayMs: 100, maxDelayMs: 1_000 },
421
- abortSignal: controller.signal,
422
- controls: { reasoningEffort: "medium" },
423
- });
424
- ```
425
-
426
- Completion models can advertise typed, provider-neutral `controls`. Reasoning-capable adapters use
427
- the `reasoningEffort` select control, so supported values are inferred from the model returned by
428
- `completionModel()`. An Agent can set control defaults, and a generate or stream call can override
429
- them for that run:
430
-
431
- ```ts
432
- const agent = new Agent({ model, controls: { reasoningEffort: "medium" } });
433
- await agent.generate({ prompt: "Solve this.", controls: { reasoningEffort: "high" } });
434
- ```
435
-
436
- Omitting a control means “Default”: the Agent default applies when configured, otherwise the
437
- provider chooses. Invalid control names and values are rejected before the provider call.
438
-
439
- `providerOptions` remains the escape hatch for a strict JSON object passed to an adapter. Runtime values such as
440
- `undefined`, non-finite numbers, cycles, a top-level array, and class instances are rejected rather
441
- than coerced. Nested arrays are valid JSON. Canonical Anvia fields such as model, input,
442
- temperature, tools, controls, dimensions, text, and voice take precedence over conflicting provider keys.
443
- Cancellation is forwarded to provider SDK calls and is never retried.
444
-
445
- ## Terminal error JSONL serialization
446
-
447
- `toReadableStream` from `@anvia/core/streaming` converts an async iterable to JSONL.
448
- By default, yielded values use ordinary `JSON.stringify`, so a yielded `Error` becomes `{}`.
449
- The existing generic serialization and thrown-error behavior remain the default.
450
-
451
- ```ts
452
- import { toReadableStream } from "@anvia/core/streaming";
453
-
454
- const body = toReadableStream(events, { errorSerialization: "anvia" });
455
- ```
456
-
457
- The opt-in `"anvia"` policy projects top-level `type: "error"` events and iterator failures
458
- into a fresh terminal envelope containing `type`, `error`, and optional `usage`.
459
- It preserves string `name` and `message` diagnostics and finite scalar `code` values,
460
- including data properties inherited from an error prototype. Recognized completion-provider-output
461
- errors also retain their `kind`, `toolCallId`, and normalized `finishReason`.
462
- It omits stack, cause, arbitrary details, raw provider payloads, and extra envelope fields.
463
- It does not call diagnostic getters, `toJSON`, or object coercion methods.
464
-
465
- Strings, finite numbers, booleans, and `null` remain primitive error values.
466
- A BigInt becomes a message object, so `42n` becomes `{ message: "42" }`.
467
- Other values without supported diagnostics become `{ message: "Unknown error" }`.
468
- Valid usage preserves all five token counters and optional finite numeric `details` fields.
469
- Invalid or inaccessible usage is omitted without replacing the error diagnostic.
470
-
471
- An opt-in terminal error emits one line, closes the stream, and requests iterator cleanup once.
472
- Cancellation suppresses pending iterator results and requests cleanup once.
473
- A noncooperative iterator can still retain its own pending work or delay cleanup.
474
- Nested data and successful events retain their ordinary JSON serialization.
475
- In-process events and errors are not mutated.
476
-
477
- This policy minimizes exposed fields. It does not redact secrets from allowed messages or diagnostics.
478
-
479
- ## Agents
480
-
481
- Agents own their default retry policy. A run with no `retries` value inherits the Agent setting;
482
- `false` disables it for that run; an object replaces it for that run. Retries apply to the current
483
- completion only, so completed tools and earlier turns are never replayed. `maxAttempts` is the
484
- total number of model attempts for that completion, including the initial attempt; it is not the
485
- number of additional retries.
486
-
487
- ```ts
488
- const agent = new Agent({
489
- id: "support",
490
- model,
491
- retries: { maxAttempts: 3 },
492
- });
493
-
494
- await agent.generate({ prompt: "Try normally." });
495
- await agent.generate({ prompt: "Do not retry this run.", retries: false });
496
- await agent.generate({
497
- prompt: "Use one custom policy.",
498
- retries: { maxAttempts: 2, initialDelayMs: 0, maxDelayMs: 0 },
499
- });
500
- ```
501
-
502
- When an Agent has `outputSchema`, truncation, JSON parsing, and schema-validation failures use the
503
- same retry budget when retries are explicitly enabled. Each retry starts from the original request
504
- rather than recursively accumulating failed attempts. Truncated output and reasoning are omitted;
505
- parse and schema repairs include only a bounded, text-only preview of the latest failed response.
506
- The correction asks for shorter raw JSON matching the schema, without Markdown or commentary.
507
- Transport failures and structured-output failures therefore cannot exceed `maxAttempts` in total.
508
- Agent response outcomes expose the last generation's `finishReason` and `providerFinishReason`, and
509
- the same fields remain attached to each assistant message's Anvia generation metadata.
510
-
511
- Malformed, incomplete, or non-JSON provider tool arguments fail with
512
- `CompletionProviderOutputError`. The default policy retries only explicitly retry-safe provider
513
- output failures when retries are enabled; it does not make arbitrary `SyntaxError` instances
514
- retryable. Tool calls are validated as a complete set before any tool executes, and filtered tool
515
- calls are never retried by default. Raw provider arguments are not retained in the error or retry
516
- metadata. A no-argument tool call must still contain the JSON value `{}`; blank argument text is
517
- malformed provider output and is never replaced with invented input.
518
-
519
- Streaming attempts are retried only before any provider event has been exposed. Once progress has
520
- been emitted, Anvia returns the failure without starting another provider attempt, because replaying
521
- the stream could duplicate text, tool-call deltas, or other observable events. Applications that
522
- want post-progress recovery must buffer attempts and define their own reset or resume protocol.
523
-
524
- Structured output remains strict: Core trims surrounding whitespace, parses JSON, and validates the
525
- result with the configured Zod schema. As a compatibility fallback for OpenAI-compatible providers
526
- that violate strict JSON mode, Core also accepts a response consisting entirely of one lowercase
527
- `json` Markdown fence or one unlabeled Markdown fence. It does not search prose for JSON, accept
528
- content before or after a fence, or bypass schema validation. `AgentStructuredOutputError` reports
529
- the `truncated`, `content-filter`, `parse`, or `schema` phase, attempt counts, output length/format
530
- metadata, per-attempt and cumulative usage, finish reasons, and the original `cause` when one
531
- exists; its message never contains the rejected model response. The `completion.retry` observer
532
- event records the same safe diagnostics and whether failed output was omitted or previewed, never
533
- the model output itself.
534
-
535
- Agent outcomes are discriminated by `type`. Successful responses include typed `output` and `text`;
536
- guardrail blocks return `type: "blocked"`, `stage`, `reason`, and an optional `message`; tool
537
- approvals and first-class questions return a JSON-safe `type: "interaction"` outcome.
538
-
539
- ```ts
540
- const outcome = await agent.generate({ prompt: "Help with this request." });
541
-
542
- switch (outcome.type) {
543
- case "response":
544
- console.log(outcome.output);
545
- break;
546
- case "blocked":
547
- console.log(outcome.stage, outcome.reason);
548
- break;
549
- case "interaction": {
550
- const response =
551
- outcome.interaction.type === "tool-approval"
552
- ? { type: "tool-approval" as const, approved: true }
553
- : {
554
- type: "tool-question" as const,
555
- answers: outcome.interaction.questions.map((question) => ({
556
- questionId: question.id,
557
- value: "application-provided answer",
558
- })),
559
- };
560
- const resumed = await agent.resume(outcome.continuation, response);
561
- console.log(resumed.type, resumed.resumedFrom);
562
- break;
563
- }
564
- }
565
- ```
566
-
567
- Keep continuations server-side. A resumed phase receives a new `runId`; Core validates the
568
- continuation and current Agent/tool registration but does not provide a durable continuation store
569
- or exactly-once execution. Use `createQuestionTool({ name, description })` when a model must ask for
570
- structured free-text or choice answers.
571
-
572
- Import JSON-safe interaction contracts and parsers from their browser-safe subpath. This entrypoint
573
- does not load the Agent runtime, MCP clients, or Node infrastructure:
574
-
575
- ```ts
576
- import {
577
- type AgentInteractionResponse,
578
- parseAgentInteractionRequest,
579
- parseAgentInteractionResponse,
580
- } from "@anvia/core/agent/interactions";
581
- ```
582
-
583
- An Agent with `outputSchema` carries that output type through `generate`, `stream`, `asTool`, and
584
- Pipeline Agent stages. `stream()` returns a handle with one event consumer plus final-value
585
- promises, steering, and cancellation. Terminal agent events are the flattened outcomes themselves;
586
- there is no extra `final.result` wrapper:
587
-
588
- ```ts
589
- const stream = agent.stream({ prompt: "Help with this request." });
590
-
591
- for await (const event of stream.events) {
592
- if (event.type === "text_delta") process.stdout.write(event.delta);
593
- if (event.type === "response") console.log(event.output);
594
- if (event.type === "interaction") console.log(event.interaction);
595
- if (event.type === "blocked") console.log(event.reason);
596
- }
597
-
598
- const outcome = await stream.result;
599
- console.log(outcome.type, await stream.text);
600
- ```
601
-
602
- Use `stream.textStream` instead of `stream.events` when only text deltas are needed. Event and text
603
- iteration share one underlying stream and therefore cannot be consumed independently.
604
-
605
- Pass `abortSignal` on a run to cancel the active provider call, tools, and nested Agent tools.
606
- Run observers receive terminal errors with `status: "cancelled"` for cancellation and
607
- `status: "failed"` for other failures.
608
-
609
- ### Prompt attribution
610
-
611
- Set `trace.promptRef` on `generate()` or `stream()` to identify the prompt used by a run and
612
- default its generation attribution. The core `AgentRunPromptRef` contract is
613
- `{ readonly name: string; readonly version?: number }`; use the registry's resolved numeric version
614
- when available, not the requested label. This identity does not contain templates or variables.
615
-
616
- For runs that change prompts between turns, completion-request middleware is the explicit
617
- generation override boundary:
618
-
619
- ```ts
620
- await agent.generate({
621
- prompt: "Summarize the ticket",
622
- trace: { promptRef: { name: "support", version: 7 } },
623
- middlewares: [
624
- {
625
- onCompletionRequest: ({ request, turn }) => {
626
- if (turn === 0) {
627
- return { request, promptRef: { name: "planner", version: 3 } };
628
- }
629
- return { request, promptRef: null };
630
- },
631
- },
632
- ],
633
- });
634
- ```
59
+ Use `@anvia/client`, `@anvia/server`, and `@anvia/react` to deliver agent output to an application UI.
60
+ Use provider, memory, vector-store, and observability adapters for the infrastructure you choose.
635
61
 
636
- The middleware receives the current `promptRef`. Returning a reference overrides that generation;
637
- `null` clears it, while omission preserves it. Each turn starts again with the run default, and
638
- later middleware sees earlier overrides. Set attribution alongside any request changes that select
639
- another prompt: attribution itself does not change the model input. It is a runtime side channel,
640
- not a provider request field or global current-prompt setting.
62
+ For recoverable execution, register a supported agent with the experimental
63
+ [`@anvia/durable`](../durable/README.md) runtime. Durable tools receive an optional stable
64
+ `ToolCallContext.operationId` for external idempotency. Direct execution does not supply it.
641
65
 
642
- ## Memory
66
+ ## Learn more
643
67
 
644
- Configure durable conversation memory on the Agent, then run through a session:
645
-
646
- ```ts
647
- import { Agent, type MemoryStore, type Message } from "@anvia/core";
648
- import type { MemoryAppendOptions, MemoryScope } from "@anvia/core/memory";
649
-
650
- class AppMemoryStore implements MemoryStore {
651
- private readonly sessions = new Map<string, Message[]>();
652
-
653
- async load({ scope }: { scope: MemoryScope }): Promise<Message[]> {
654
- return [...(this.sessions.get(scope.sessionId) ?? [])];
655
- }
656
-
657
- async append(input: MemoryAppendOptions): Promise<void> {
658
- const current = this.sessions.get(input.scope.sessionId) ?? [];
659
- this.sessions.set(input.scope.sessionId, [...current, ...input.messages]);
660
- }
661
-
662
- async clear({ scope }: { scope: MemoryScope }): Promise<void> {
663
- this.sessions.delete(scope.sessionId);
664
- }
665
- }
666
-
667
- const memory = new AppMemoryStore();
668
- const agent = new Agent({
669
- id: "support",
670
- model: model,
671
- memory: { store: memory },
672
- });
673
-
674
- const session = { sessionId: "thread_123", userId: "user_456" };
675
- await agent.generate({ prompt: "Remember my plan.", session });
676
- await agent.generate({ prompt: "What is my plan?", session });
677
- ```
678
-
679
- Memory defaults to `savePolicy: "message"`, which saves the user prompt, each completed assistant message, and each completed tool result as soon as they are ready. You can choose `"turn"` or `"run"` at configuration time:
680
-
681
- ```ts
682
- new Agent({
683
- id: "support",
684
- model,
685
- memory: { store: memory, savePolicy: "turn" },
686
- });
687
- ```
688
-
689
- Without `session`, the same Agent is stateless. Pass `{ messages }` when the caller already owns a
690
- complete transcript; transcripts cannot be combined with persisted sessions.
691
-
692
- Compaction is an explicit Agent policy over a store capability. The store preserves the canonical
693
- message history for `load()` and inspectors, and persists one latest compaction checkpoint
694
- separately. Future model calls receive the checkpoint summary (a system message tagged with
695
- `metadata.anvia.memoryCompaction`) plus the unsummarized tail, not the individual messages already
696
- covered by that summary:
697
-
698
- ```ts
699
- import { createSummaryMemoryCompactor } from "@anvia/core/memory";
700
-
701
- const compactor = createSummaryMemoryCompactor({
702
- model: summaryModel,
703
- maxTokens: 1024,
704
- retries: { maxAttempts: 2 },
705
- });
706
-
707
- const agent = new Agent({
708
- id: "support",
709
- model,
710
- memory: {
711
- store: memory,
712
- savePolicy: "message",
713
- compaction: {
714
- trigger: { afterTokens: 32_000 },
715
- retention: { recentTurns: 4 },
716
- compactor,
717
- conflictRetries: false,
718
- },
719
- },
720
- });
721
-
722
- for await (const event of agent.stream({ prompt: "What did we decide?", session })) {
723
- if (event.type === "memory_compaction") {
724
- console.log({
725
- messages: event.compactedMessageCount,
726
- beforeTokens: event.originalTokenCount,
727
- afterTokens: event.resultTokenCount,
728
- usage: event.usage,
729
- });
730
- }
731
- }
732
- ```
733
-
734
- ### Retention API design
735
-
736
- - **Public option:** `retention: { recentTurns: N }`.
737
- - **Counting unit:** a user-led turn, not an individual message. Each `user` message starts a turn;
738
- every following `assistant`, `tool`, or `system` message belongs to it until the next `user`
739
- message. Tool calls and results therefore cannot be split at the compaction boundary.
740
- - **Default:** omitting `retention` resolves to `{ recentTurns: 1 }`.
741
- - **Validation:** `N` must be a nonnegative safe integer. Fractions, negative values, infinity, and
742
- specifying both retention modes are rejected.
743
- - **Boundaries:** exactly the last `N` complete stored turns remain unsummarized. `N = 0` summarizes
744
- the whole stored transcript when it contains a user-led turn. If at most `N` turns exist,
745
- compaction is a no-op.
746
- - **Leading messages:** messages before the first `user` do not count as a turn. When older turns
747
- are summarized, this leading prefix is summarized with them.
748
- - **Trigger interaction:** the stored transcript plus incoming user message is compared with
749
- `afterTokens`, but the incoming message is not part of the stored-turn retention count.
750
- `afterTokens` remains only the automatic trigger threshold and does not cap retained turns.
751
- - **Compatibility:** deprecated `{ recentTokens: N }` remains supported with its previous
752
- whole-turn semantics. It is mutually exclusive with `recentTurns`.
753
-
754
- The built-in token counter is a lightweight provider-neutral estimate. Pass `tokenCounter` when you
755
- have a model-specific tokenizer or counting service. A custom counter may be async, can be called
756
- multiple times, and must return deterministic, monotonic, nonnegative safe integers:
757
-
758
- ```ts
759
- compaction: {
760
- trigger: { afterTokens: 32_000 },
761
- retention: { recentTurns: 4 },
762
- tokenCounter: (messages) => tokenizer.count(messages),
763
- compactor,
764
- }
765
- ```
766
-
767
- For migration, the deprecated `retention: { recentTokens: N }` form remains supported with its
768
- previous semantics: it keeps as many complete recent user-led turns as fit the token budget and
769
- always keeps the newest turn. `recentTokens` must be positive and less than `afterTokens`.
770
- `recentTurns` and `recentTokens` are mutually exclusive; use `recentTurns` for deterministic
771
- count-based retention. Omitting `retention` previously used
772
- `Math.floor(afterTokens / 4)` as a token budget and now retains one turn. To preserve the old
773
- implicit behavior during migration, set that value explicitly as `recentTokens`.
774
-
775
- You can force the configured policy without starting an Agent run. The result makes a no-op
776
- explicit rather than throwing:
777
-
778
- ```ts
779
- const result = await agent.compactMemory({ session });
780
-
781
- if (result.type === "compacted") {
782
- console.log(result.originalTokenCount, result.resultTokenCount);
783
- } else {
784
- console.log(result.reason); // "nothing_to_compact"
785
- }
786
- ```
787
-
788
- The automatic trigger is a threshold, not a hard storage limit. Omitting `compaction` keeps the
789
- full canonical history model-facing. Summary-provider retries belong to the compactor; full
790
- snapshot-to-checkpoint conflict retries are separately opt-in. A streamed `memory_compaction`
791
- event is emitted only after the store atomically commits the checkpoint.
792
-
793
- ## Structured Extraction
794
-
795
- ```ts
796
- import { extract } from "@anvia/core/extractor";
797
- import { z } from "zod";
798
-
799
- const ticketSchema = z.object({
800
- customer: z.string(),
801
- priority: z.enum(["low", "medium", "high"]),
802
- summary: z.string(),
803
- });
804
-
805
- const { output: ticket } = await extract({
806
- model,
807
- text: "Acme Co. reports checkout failures. Priority is high.",
808
- outputSchema: ticketSchema,
809
- retries: { maxAttempts: 2 },
810
- });
811
- ```
812
-
813
- ## Pipelines
814
-
815
- ```ts
816
- import { Pipeline } from "@anvia/core/pipeline";
817
- import { z } from "zod";
818
-
819
- const pipeline = new Pipeline({ id: "support-flow", inputSchema: z.string() })
820
- .agent({
821
- id: "draft",
822
- agent,
823
- suspension: "reject",
824
- request: ({ input }) => ({ prompt: `Draft a reply for this ticket:\n\n${input}` }),
825
- })
826
- .extract({
827
- id: "parse",
828
- model,
829
- outputSchema: ticketSchema,
830
- text: ({ input }) => input,
831
- });
832
-
833
- const { runId, output } = await pipeline.run({
834
- input: "Customer cannot complete checkout.",
835
- });
836
- ```
837
-
838
- Pipelines use the same named observability configuration as Agents. Pipeline observers receive the
839
- root run and every composed, parallel, Agent, extraction, and custom stage. A primary stage trace is
840
- propagated into an Agent stage so its generations and tools become children of that stage. This
841
- requires the Pipeline and Agent `primaryTrace` names to match. When they differ, Anvia leaves the
842
- Agent trace unchanged without raising an error:
843
-
844
- ```ts
845
- const pipeline = new Pipeline({
846
- id: "support-flow",
847
- inputSchema: z.string(),
848
- observability: {
849
- observers: { telemetry: pipelineObserver },
850
- primaryTrace: "telemetry",
851
- },
852
- }).agent({
853
- id: "draft",
854
- agent,
855
- suspension: "reject",
856
- request: ({ input }) => ({ prompt: input }),
857
- });
858
-
859
- const result = await pipeline.run({
860
- input: "Customer cannot complete checkout.",
861
- trace: { sessionId: "support-session" },
862
- });
863
-
864
- console.log(result.trace);
865
- ```
866
-
867
- The `observer` passed to `pipeline.run()` remains a separate stage-event sink intended for Studio,
868
- logging, and other operational consumers. Its errors are isolated unless `failOnObserverError` is
869
- enabled.
870
-
871
- `observability.errorPolicy` controls named observer failures instead. It accepts `"ignore"` or
872
- `"throw"` and defaults to `"ignore"`. With `"throw"`, the run rejects with a
873
- `PipelineObserverDispatchError` containing the failed phase and per-observer failures.
874
-
875
- Pipeline run and batch options also accept `trace.promptRef`. This identifies the pipeline root;
876
- it does not stamp every stage or Agent with that prompt. Select each Agent's relevant prompt with
877
- `request: ({ input }) => ({ prompt: input, trace: { promptRef } })`, especially when a pipeline uses
878
- multiple prompts. Trace parent propagation and prompt identity are independent.
879
-
880
- ## Documents
881
-
882
- Applications own file discovery, file reads, document parsing, source metadata, and per-file error
883
- policy. Core only provides deterministic in-memory text chunking:
884
-
885
- ```ts
886
- import { readFile } from "node:fs/promises";
887
- import { chunkText } from "@anvia/core/documents";
888
-
889
- const text = await readFile("guide.txt", "utf8");
890
- const chunks = chunkText({
891
- text,
892
- strategy: "recursive",
893
- maxSize: 1_000,
894
- overlap: 100,
895
- separators: ["\n\n", "\n", " "],
896
- });
897
- ```
898
-
899
- Use `strategy: "fixed"` for deterministic sliding windows. Recursive chunking requires an explicit
900
- separator order and falls back to fixed-size splitting when none of those separators can divide an
901
- oversized section. Chunk offsets use JavaScript string indices and always identify the exact source
902
- slice.
903
-
904
- For the common vector ingestion path, embed and upsert raw text with one call:
905
-
906
- ```ts
907
- import { ingestVectorText } from "@anvia/core/vector-store";
908
-
909
- await ingestVectorText({
910
- store,
911
- document: {
912
- id: "incident-42",
913
- text,
914
- metadata: { tenant: "acme" },
915
- },
916
- embeddingModel,
917
- chunking: {
918
- strategy: "recursive",
919
- maxSize: 1_000,
920
- overlap: 100,
921
- separators: ["\n\n", "\n", " "],
922
- },
923
- });
924
- ```
925
-
926
- `ingestVectorDocuments()` accepts the same document shape for batches. Without `chunking`, each
927
- source document is embedded as one chunk. Chunk embeddings remain grouped under the source document
928
- ID so re-ingestion replaces its complete vector representation instead of leaving stale chunks.
929
-
930
- `embedTexts()`, `embedSparseTexts()`, and `embedDocuments()` require exactly one embedding per
931
- requested text in every provider batch. Short and long batches reject before results are flattened
932
- or grouped, even when their total counts would match. Successful batches retain input batch order.
933
- Malformed successful responses are not retried, including with a custom retry policy. After a
934
- provider call completes, cancellation takes precedence over vector-shape errors, which take
935
- precedence over batch-count errors. Each channel's concurrency pool waits for its already-started
936
- batches to settle before rejecting. Hybrid `embedDocuments()` rejects when either channel's pool
937
- rejects. Opposite-channel provider calls may still be active, and that channel's queued batches may
938
- continue to run after the hybrid call rejects. Neither sibling cancellation nor global settlement
939
- is guaranteed.
940
-
941
- ## Media
942
-
943
- Media helpers follow the same one-object API and share `providerOptions`, `retries`, and
944
- `abortSignal`:
945
-
946
- ```ts
947
- import { generateImage, generateSpeech, transcribe } from "@anvia/core";
948
-
949
- const image = await generateImage({
950
- model: client.imageGenerationModel({ modelId: "gpt-image-1" }),
951
- prompt: "A compact robot drawing an architecture diagram.",
952
- width: 1024,
953
- height: 1024,
954
- });
955
- console.log(image.images[0].data);
956
-
957
- const speech = await generateSpeech({
958
- model: client.speechGenerationModel({ modelId: "gpt-4o-mini-tts" }),
959
- text: "Hello from Anvia.",
960
- voice: "alloy",
961
- });
962
-
963
- const transcript = await transcribe({
964
- model: client.transcriptionModel({ modelId: "gpt-4o-mini-transcribe" }),
965
- audio: {
966
- data: speech.audio.data,
967
- filename: "speech.mp3",
968
- mediaType: speech.audio.mediaType,
969
- },
970
- });
971
- console.log(transcript.text);
972
- ```
973
-
974
- ## MCP
975
-
976
- MCP clients live in the optional `@anvia/mcp` package. Core retains the lightweight registration
977
- contracts that let agents receive immutable MCP server snapshots without owning or closing their
978
- underlying transports:
979
-
980
- ```sh
981
- pnpm add @anvia/mcp
982
- ```
983
-
984
- Import `McpClient` and `McpClientGroup` from `@anvia/mcp`, connect them, and pass the resulting
985
- `servers` to `new Agent({ mcpServers })`. See the `@anvia/mcp` README for transport configuration,
986
- connection ownership, URL safety, and cleanup.
987
-
988
- ## Skill script cancellation
989
-
990
- The built-in `run_skill_script` tool uses the invocation's `ToolCallContext.abortSignal`.
991
- A pre-aborted signal prevents process creation. Cancellation rejects with `AbortError` and retains
992
- its reason as the cause. Normal execution preserves the existing stdout and stderr formatting,
993
- nonzero-exit errors, and timeout diagnostic.
994
-
995
- Cancellation and timeout send `SIGTERM` to the direct child, then `SIGKILL` after 250 ms if the child
996
- has not exited. The runner waits for confirmed direct-child exit before rejecting. If exit remains
997
- unconfirmed after 1,250 ms, it reports `Skill script termination could not be confirmed` instead.
998
- These intervals depend on event-loop scheduling and operating-system signal support.
999
-
1000
- Normal completion waits for output pipes to close. Cancellation and timeout destroy the runner's
1001
- pipes after direct-child exit, so inherited descendant pipes cannot delay settlement. The runner
1002
- does not terminate descendants or provide a sandbox. Each invocation removes its abort listener
1003
- and clears its owned timers on settlement. Agent runs keep their existing cancellation errors.
1004
- Closing a stream consumer aborts the run's existing cancellation signal before failure cleanup,
1005
- so active tools receive cancellation when the consumer returns from a yielded event.
1006
- Use `stream.cancel()` to cancel a pending pull blocked on a tool.
1007
-
1008
- ## Evaluations
1009
-
1010
- Import evaluation APIs from `@anvia/core/evals`. A suite contains cases, a target, and one or more
1011
- metrics. Required metric failures make a case fail; infrastructure or evaluation errors produce an
1012
- `invalid` outcome.
1013
-
1014
- ```ts
1015
- import { contains, exactMatch, runEvalSuite } from "@anvia/core/evals";
1016
-
1017
- const result = await runEvalSuite({
1018
- name: "support answers",
1019
- cases: [
1020
- {
1021
- id: "refund-window",
1022
- input: "When can I request a refund?",
1023
- expected: "Refunds are available for 30 days.",
1024
- },
1025
- ],
1026
- target: async (input) => answerSupportQuestion(input),
1027
- metrics: [exactMatch(), contains({ expected: "30 days" })],
1028
- });
1029
-
1030
- console.log(result.cases);
1031
- console.log(result.results[0]?.scores.exact_match);
1032
- ```
1033
-
1034
- `runEvalSuite` infers the case input, target output, expected value, metric names, and score types.
1035
- Metrics that implicitly read `case.expected`, `case.context`, or `case.retrievalContext` require
1036
- those fields at compile time. Supplying an explicit metric value or selector removes the matching
1037
- case requirement.
1038
-
1039
- Set `run: { promptRef: { name: "support", version: 7 } }` on `runEvalSuite()` to attribute the
1040
- evaluation run. The reference is preserved in `result.run` and the reporter's run-start, per-metric,
1041
- and run-end contexts. It does not automatically attribute the target Agent or judge generations;
1042
- configure those runs' `trace.promptRef` separately when relevant.
1043
-
1044
- ### Built-in metrics
1045
-
1046
- Deterministic metrics do not call a model:
1047
-
1048
- - `exactMatch`, `contains`, `notContains`, `containsAll`, and `containsAny`
1049
- - `matches`, `doesNotMatch`, `maxLength`, and `requiredFields`
1050
- - `semanticSimilarity`, which uses an embedding model
1051
-
1052
- Judge metrics call a completion model and record their evaluation token usage:
1053
-
1054
- - `llmJudge` and `llmScore`
1055
- - `answerRelevancy`, `promptAlignment`, `jsonCorrectness`, and `summarization`
1056
- - `hallucination`, `faithfulness`, and `abstention`
1057
- - `gEval`, `turnRelevancy`, and `knowledgeRetention`
1058
-
1059
- Numeric thresholds use values from `0` through `1`. `strictMode` requires a perfect higher-is-better
1060
- score or a zero lower-is-better score. Judge metrics default to including a final explanation;
1061
- setting `includeReason: false` saves that extra judge call when the explanation is unnecessary.
1062
-
1063
- ### Typed custom metrics
1064
-
1065
- Use `createEvalTypes` to bind input, output, and expected types once when defining multiple custom
1066
- metrics:
1067
-
1068
- ```ts
1069
- import { createEvalTypes, EvalOutcome } from "@anvia/core/evals";
1070
-
1071
- const supportEvals = createEvalTypes<string, { answer: string }, string>();
1072
-
1073
- const noHandoff = supportEvals.defineMetric({
1074
- name: "no_handoff",
1075
- dataType: "BOOLEAN",
1076
- evaluate: ({ output, signal }) =>
1077
- signal.aborted || output.answer.includes("contact support")
1078
- ? EvalOutcome.fail(false)
1079
- : EvalOutcome.pass(true),
1080
- });
1081
- ```
1082
-
1083
- Custom metrics return `EvalOutcome.pass`, `EvalOutcome.fail`, or `EvalOutcome.invalid`. Thrown metric
1084
- errors are converted to structured invalid outcomes containing the error kind and original error.
1085
-
1086
- ### Execution controls
1087
-
1088
- Evaluation targets receive an optional third argument containing the case `AbortSignal`.
1089
-
1090
- ```ts
1091
- const result = await runEvalSuite({
1092
- // cases, target, and metrics...
1093
- name: "release gate",
1094
- cases,
1095
- target: async (input, testCase, context) =>
1096
- generateAnswer(input, { signal: context?.signal, metadata: testCase.metadata }),
1097
- metrics,
1098
- targetConcurrency: 4,
1099
- metricConcurrency: 2,
1100
- caseTimeoutMs: 30_000,
1101
- signal: deploymentSignal,
1102
- failFast: true,
1103
- caseIds: ["refund-window", "billing-owner"],
1104
- shard: { index: 0, count: 4 },
1105
- onProgress(event) {
1106
- console.log(event.type);
1107
- },
1108
- });
1109
- ```
1110
-
1111
- - `concurrency` remains the shorthand for both target and metric concurrency.
1112
- - `targetConcurrency` and `metricConcurrency` can limit them independently.
1113
- - `caseTimeoutMs` covers the target and its metrics. Cooperative targets and metrics should observe
1114
- the supplied signal; the runner still stops awaiting work that ignores it.
1115
- - Aborting the suite-level `signal` stops scheduling cases and rejects the run with the abort reason,
1116
- preserving explicit `null` through case signals and final rejection.
1117
- - `caseIds`, `caseFilter`, and `shard` select cases before execution.
1118
- - `failFast` throws `EvalFailFastError` after the first completed required failure or invalid case.
1119
- - `onProgress` receives case, target, metric, and case-completion events.
1120
-
1121
- Use `selectEvalCaseIds(previousResult)` to select failed and invalid cases for a rerun.
1122
-
1123
- ### Cancellation ownership
1124
-
1125
- `agentEvalTarget` forwards the case signal to initial generation and approval resumes. If the
1126
- request also supplies `abortSignal`, either signal cancels that target invocation. The composed
1127
- signal preserves the first observed abort reason, including an explicit `null`. The target copies
1128
- request settings and removes its composition listeners when it settles. Direct two-argument target
1129
- calls remain supported.
1130
-
1131
- Cancellation rejects pending request, interaction responder, and output callbacks without starting
1132
- later phases. The callbacks keep their existing signatures. Their application-owned computation
1133
- cannot be forcibly stopped, and its eventual rejection is still observed.
1134
-
1135
- Built-in embedding and judge metrics forward the case signal to provider work and retry delays.
1136
- Providers must cooperate with cancellation. A settled suite does not by itself prove that a
1137
- noncooperative operation has stopped.
1138
-
1139
- `gEval` deduplicates concurrent preparation within each metric instance. Preparation owns a
1140
- separate signal, so cancelling one waiter does not cancel other live cases. When the last waiter
1141
- leaves, pending preparation is detached and cancelled. Rejected or empty preparation can be retried,
1142
- and stale results cannot replace a newer preparation. Successful steps are cached with detached
1143
- arrays. Setup usage is attached once to the first case that completes scoring with a valid outcome.
1144
- A case cancelled or failing during scoring leaves that usage available to another successful scorer,
1145
- including a concurrent waiter. Supplied `evaluationSteps` need no preparation request.
1146
-
1147
- If no case completes scoring, setup usage remains unclaimed. Aggregate evaluation usage is not a
1148
- complete provider billing ledger.
1149
-
1150
- ### Results, usage, and failures
1151
-
1152
- Every case result includes `targetStatus`, target and total durations, per-case usage and optional
1153
- cost. Every metric result includes its duration, optional cost, outcome, and reporter errors. A
1154
- successful target that returns `undefined` has `targetStatus: "succeeded"` and retains its `output`
1155
- property, so it is distinct from a failed target.
1156
-
1157
- The suite aggregates metric totals, case totals, target and evaluation usage, costs, duration, and
1158
- all reporter errors. Invalid outcomes may include these `kind` values: `target`, `metric`,
1159
- `configuration`, `provider`, or `timeout`.
1160
-
1161
- ### CI output and expectations
1162
-
1163
- `runEvalCli` prints a result and can set `process.exitCode`. Exit code `1` means a required metric
1164
- failed; `2` means a required metric was invalid. Use `defineEvalExpectations` with a defined suite to
1165
- type-check case and metric names.
1166
-
1167
- ```ts
1168
- import { defineEvalExpectations, defineEvalSuite, exactMatch, runEvalCli } from "@anvia/core/evals";
1169
-
1170
- const suite = defineEvalSuite({
1171
- name: "release gate",
1172
- cases: [{ id: "refund", input: "refund", expected: "30 days" }],
1173
- target: async () => "30 days",
1174
- metrics: [exactMatch({ name: "correct" })],
1175
- });
1176
-
1177
- await runEvalCli({
1178
- ...suite,
1179
- expectations: defineEvalExpectations(suite, {
1180
- outcomes: { refund: { correct: "pass" } },
1181
- }),
1182
- exitCode: true,
1183
- maxValueLength: 2_000,
1184
- redact: (value, context) => (context.kind === "input" ? "[redacted]" : value),
1185
- });
1186
- ```
1187
-
1188
- Use `formatEvalResult` for pure pretty or JSON formatting without writing to stdout. Both
1189
- `formatEvalResult` and `runEvalCli` support value truncation and redaction. JSON output otherwise
1190
- contains cases, outputs, metadata, and error details, so redact sensitive data before writing it to
1191
- shared CI logs.
1192
-
1193
- ## Public Areas
1194
-
1195
- - `agent`: typed Agent runtime, retries, and stream events
1196
- - `agent/interactions`: browser-safe interaction contracts, schemas, assertions, and parsers
1197
- - `tool`: typed tool creation and tool sets
1198
- - `completion`: direct completion helpers and provider-neutral model contracts
1199
- - `memory`: durable session memory interfaces and in-memory store
1200
- - `extractor`: schema-first structured extraction
1201
- - `pipeline`: typed sequential and parallel workflows
1202
- - `embeddings`: embedding helpers and document embedding utilities
1203
- - `vector-store`: in-memory vector search and vector search tools
1204
- - `streaming`: normalized stream helpers
1205
- - `mcp`: lightweight MCP tool and server registration contracts used by Agent
1206
- - `skills`: local skill loading
1207
- - `observability`: observer interfaces for runs, generations, and tool calls
1208
- - `evals`: evaluation helpers and reporters
1209
- - `redaction`: PII patterns and deep value redaction shared by observability adapters
1210
- - `documents`: deterministic in-memory text chunking
1211
- - `speech-generation`, `image-generation`, `transcription`: provider-neutral media interfaces
1212
-
1213
- ## Development
1214
-
1215
- ```sh
1216
- pnpm --filter @anvia/core typecheck
1217
- pnpm --filter @anvia/core test
1218
- pnpm --filter @anvia/core build
1219
- ```
68
+ - [Core guide and API examples](https://github.com/anvia-hq/anvia/blob/main/docs/packages/core.md)
69
+ - [Runnable cookbook](https://github.com/anvia-hq/anvia/tree/main/cookbook)
70
+ - [Package catalog](https://github.com/anvia-hq/anvia#readme)
71
+ - [Contributing](https://github.com/anvia-hq/anvia/blob/main/CONTRIBUTING.md)