@odla-ai/ai 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cory Ondrejka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @odla-ai/ai
2
2
 
3
+ > ⚠️ **Early access — pre-1.0.** Agents work from bounded runbooks; humans
4
+ > approve credentials, production changes, releases, and merges. APIs and exact
5
+ > package availability can change. Review the documented guarantees and
6
+ > limitations; this software is MIT-licensed and provided without warranty.
7
+
3
8
  One general-purpose interface for AI inference across **Anthropic (Claude)**,
4
9
  **OpenAI (GPT)**, and **Google (Gemini)** — text, image, and audio — plus a
5
10
  tool-use **agent engine** and an **eval harness**. It's the place every odla app
@@ -13,8 +18,9 @@ reaches for when it needs AI.
13
18
  - **Capability-checked.** Every request is validated against the model before
14
19
  dispatch, so "audio to Claude" fails fast with a clear error instead of an
15
20
  opaque provider 400.
16
- - **Isomorphic.** Node 20+, Cloudflare Workers, the browser. Provider SDKs are
17
- lazy-loaded a Claude-only consumer never pulls in `openai` or `@google/genai`.
21
+ - **Server runtimes.** Node 20+ and Cloudflare Workers. Provider adapters are
22
+ runtime-lazy, but all three provider SDKs are package dependencies. Do not put
23
+ long-lived provider keys in browser code; use your own authenticated proxy.
18
24
 
19
25
  ```bash
20
26
  npm install @odla-ai/ai
@@ -72,9 +78,17 @@ const { value } = await ai.extract<{ sentiment: "positive" | "negative" | "neutr
72
78
  },
73
79
  },
74
80
  });
75
- // value.sentiment === "positive" (already a parsed object never a JSON string)
81
+ // value.sentiment === "positive" (parsed and validated before it is returned)
76
82
  ```
77
83
 
84
+ `extract` and local agent tools validate provider arguments before returning or
85
+ executing them. The dependency-free validator supports the common tool-schema
86
+ subset: local `$ref`/`$defs`, `type`, `enum`, `const`, composition, object
87
+ properties/required/additional properties, arrays, and string/number constraints.
88
+ Unknown or unsupported schema keywords outside that documented subset fail
89
+ closed. `extract()` throws `ToolInputError`; local agent tools return an error
90
+ `tool_result` and their handlers do not execute.
91
+
78
92
  ### Web search
79
93
 
80
94
  ```ts
@@ -141,6 +155,28 @@ console.log(run.toolCalls); // [{ toolUse, output }]
141
155
  console.log(run.usage); // accumulated across turns
142
156
  ```
143
157
 
158
+ Provider calls and tool handlers receive a combined cancellation signal. Runs
159
+ can also stop before additional effects when a run-wide budget is exhausted:
160
+
161
+ ```ts
162
+ const run = await runAgent(ai, calculator, {
163
+ input: "Reconcile these records",
164
+ signal: request.signal,
165
+ deadline: Date.now() + 30_000,
166
+ budget: { maxInputTokens: 20_000, maxOutputTokens: 4_000, maxToolCalls: 8 },
167
+ });
168
+ ```
169
+
170
+ Token limits are post-turn ceilings because provider input usage is known only
171
+ after a response. `maxInputTokens` and `maxTotalTokens` therefore cannot
172
+ tokenizer-bound the first prompt; they stop later turns and tool effects once
173
+ reported usage reaches the ceiling. `maxOutputTokens` and the remaining total
174
+ budget cap each request's `maxTokens` before dispatch. Limits must be integers
175
+ (`maxToolCalls` may be zero; token limits must be positive). When a limit blocks
176
+ tools, the loop records a matching error `tool_result` for every requested tool
177
+ before persisting memory, so retries never inherit an unmatched effect request.
178
+ `stoppedReason` is `"budget_exhausted"` when a limit prevents further work.
179
+
144
180
  Tools can declare taint constraints (`outputTaint` / `acceptsTaint`) for a
145
181
  lightweight CaMeL-style prompt-injection gate, and personas can carry a pluggable
146
182
  `MemoryScope`.
@@ -224,20 +260,23 @@ The agent handshake (an agent never takes a platform secret):
224
260
  import { requestToken, init as odlaInit } from "@odla-ai/db";
225
261
  import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, runAgent, init } from "@odla-ai/ai";
226
262
 
227
- // 1. Device-authorization handshake — the human supplies the endpoint and
228
- // approves a short code in odla-db Studio; you get a scoped, revocable token.
229
- const { token } = await requestToken({ endpoint: ODLA_URL, onCode: ({ userCode }) => console.log(userCode) });
263
+ const ODLA_PLATFORM = "https://odla.ai";
264
+ const ODLA_DB_URL = "https://db.odla.ai";
265
+
266
+ // 1. Device authorization — the human approves a short code at odla.ai;
267
+ // the agent receives a scoped, revocable platform token.
268
+ const { token } = await requestToken({ endpoint: ODLA_PLATFORM, onCode: ({ userCode }) => console.log(userCode) });
230
269
 
231
270
  // 2. Provision: create app, mint an app key, push the agent schema, and store the
232
271
  // user's BYO provider keys as odla-db secrets (encrypted at rest).
233
272
  const { appId, appKey } = await provisionAgentApp({
234
- endpoint: ODLA_URL,
273
+ endpoint: ODLA_PLATFORM,
235
274
  token, // operator/dev token — needed to WRITE secrets
236
275
  providerKeys: { openai: OPENAI_KEY, anthropic: ANTHROPIC_KEY },
237
276
  });
238
277
 
239
278
  // 3. Runtime: read keys from secrets with the app key, persist memory + run traces.
240
- const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_URL });
279
+ const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
241
280
  const ai = init({ resolveKey: odlaDbKeyResolver(db) }); // keys come from odla-db secrets
242
281
 
243
282
  const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
@@ -274,11 +313,28 @@ const { ai, provider, model } = await initFromPlatform({
274
313
 
275
314
  The config comes from the anonymous `public-config` endpoint and is cached
276
315
  (~60s by default), so switching provider or model in Studio takes effect
277
- without a redeploy; the key resolves from the vault at call time, so rotation
278
- is just writing the secret again. **Never put provider keys in an app
316
+ without a redeploy. The configured provider is enforced: its facade catalog is
317
+ filtered to that provider, and a mismatched configured/default/call model fails
318
+ with `ConfigError`. If Studio has no default model, each call must name a model
319
+ for that provider. Tenant-bound facades are never shared globally. Provider
320
+ keys and SDK clients have finite 60-second in-isolate caches; rotation replaces
321
+ the previous credential generation rather than retaining it. Set
322
+ `keyResolverOptions: { ttlMs, cacheVersion }` to shorten or immediately
323
+ invalidate it after rotation. **Never put provider keys in an app
279
324
  Worker's wrangler vars/secrets** — the worker's only secret is its odla-db
280
325
  app key.
281
326
 
327
+ ### 0.3 migration notes
328
+
329
+ - Tool/extraction JSON Schemas now fail closed. `extract()` throws
330
+ `ToolInputError`; `runAgent()` records an error `tool_result` and does not run
331
+ a local handler when its arguments do not validate.
332
+ - Platform calls cannot cross the provider selected in Studio.
333
+ - `CancelledError` (`cancelled`) and `DeadlineExceededError`
334
+ (`deadline_exceeded`) distinguish caller limits from provider failures.
335
+ - Invalid run budgets now throw `ConfigError`; budget stops persist paired tool
336
+ results and may therefore add one final user/tool-result turn to memory.
337
+
282
338
  ## Models & capabilities
283
339
 
284
340
  The catalog maps a canonical model id → provider, native id, and capabilities.
@@ -302,7 +358,8 @@ models; extend or override it via `buildCatalog` / `init({ catalog })`.
302
358
 
303
359
  Every error is an `OdlaAIError` subclass carrying a stable `code` — branch on
304
360
  `err.code`, not message text: `ConfigError`, `AuthError`, `RateLimitError`,
305
- `CapabilityError`, `ContextWindowError`, `InvalidRequestError`, `ProviderError`.
361
+ `CapabilityError`, `ContextWindowError`, `InvalidRequestError`, `ToolInputError`,
362
+ `CancelledError`, `DeadlineExceededError`, `ProviderError`.
306
363
 
307
364
  ## Develop
308
365
 
@@ -1,59 +1,18 @@
1
+ import {
2
+ toolInput
3
+ } from "./chunk-OX4TA4ZH.js";
1
4
  import {
2
5
  addUsage,
3
6
  emptyUsage,
4
7
  mapProviderError,
5
- normalizeError
6
- } from "./chunk-LANGYP65.js";
8
+ normalizeError,
9
+ signalWithDeadline
10
+ } from "./chunk-PXXCN2EU.js";
7
11
 
8
12
  // src/providers/anthropic.ts
9
13
  import Anthropic from "@anthropic-ai/sdk";
10
- var AnthropicProvider = class {
11
- id = "anthropic";
12
- client;
13
- /** `client` may be injected (tests, custom transport); otherwise built from key. */
14
- constructor(apiKey, client) {
15
- this.client = client ?? new Anthropic({ apiKey });
16
- }
17
- async create(spec, req) {
18
- try {
19
- let messages = toAnthropicMessages(req);
20
- const content = [];
21
- const usage = emptyUsage();
22
- let stopReason = "end_turn";
23
- let id = "";
24
- for (let i = 0; i < 8; i++) {
25
- const res = await this.client.messages.create(
26
- buildParams(spec, req, messages)
27
- );
28
- id = res.id;
29
- addUsage(usage, mapUsage(res.usage));
30
- content.push(...mapContent(res.content));
31
- stopReason = mapStop(res.stop_reason);
32
- if (res.stop_reason === "pause_turn") {
33
- messages = [...messages, { role: "assistant", content: res.content }];
34
- continue;
35
- }
36
- break;
37
- }
38
- return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
39
- } catch (err) {
40
- mapProviderError(err, "anthropic");
41
- }
42
- }
43
- async *stream(spec, req) {
44
- try {
45
- const events = this.client.messages.stream(
46
- buildParams(spec, req, toAnthropicMessages(req))
47
- );
48
- for await (const raw of events) {
49
- const ev = mapStreamEvent(raw, req);
50
- if (ev) yield ev;
51
- }
52
- } catch (err) {
53
- yield { type: "error", error: normalizeError(err, "anthropic").toShape() };
54
- }
55
- }
56
- };
14
+
15
+ // src/providers/anthropic/request.ts
57
16
  function buildParams(spec, req, messages) {
58
17
  const params = {
59
18
  model: spec.nativeId,
@@ -141,12 +100,14 @@ function mapToolChoice(tc) {
141
100
  if (tc === "none") return { type: "none" };
142
101
  return { type: "tool", name: tc.name };
143
102
  }
103
+
104
+ // src/providers/anthropic/response.ts
144
105
  function mapContent(blocks) {
145
106
  const out = [];
146
107
  for (const b of blocks) {
147
108
  if (b.type === "text") out.push({ type: "text", text: b.text });
148
109
  else if (b.type === "tool_use") {
149
- out.push({ type: "tool_use", id: b.id, name: b.name, input: b.input ?? {} });
110
+ out.push({ type: "tool_use", id: b.id, name: b.name, input: toolInput(b.input ?? {}, "anthropic", b.name) });
150
111
  } else if (b.type === "thinking") {
151
112
  out.push({ type: "thinking", thinking: b.thinking, signature: b.signature });
152
113
  }
@@ -172,6 +133,8 @@ function mapStop(reason) {
172
133
  return "end_turn";
173
134
  }
174
135
  }
136
+
137
+ // src/providers/anthropic/stream.ts
175
138
  function mapStreamEvent(raw, req) {
176
139
  switch (raw.type) {
177
140
  case "message_start":
@@ -213,7 +176,60 @@ function mapStartBlock(cb) {
213
176
  if (cb.type === "thinking") return { type: "thinking", thinking: cb.thinking, signature: cb.signature };
214
177
  return void 0;
215
178
  }
179
+
180
+ // src/providers/anthropic.ts
181
+ var AnthropicProvider = class {
182
+ id = "anthropic";
183
+ client;
184
+ /** `client` may be injected (tests, custom transport); otherwise built from key. */
185
+ constructor(apiKey, client) {
186
+ this.client = client ?? new Anthropic({ apiKey });
187
+ }
188
+ async create(spec, req) {
189
+ const signal = signalWithDeadline(req.signal, req.deadline);
190
+ try {
191
+ let messages = toAnthropicMessages(req);
192
+ const content = [];
193
+ const usage = emptyUsage();
194
+ let stopReason = "end_turn";
195
+ let id = "";
196
+ for (let i = 0; i < 8; i++) {
197
+ const res = await this.client.messages.create(
198
+ buildParams(spec, req, messages),
199
+ { signal }
200
+ );
201
+ id = res.id;
202
+ addUsage(usage, mapUsage(res.usage));
203
+ content.push(...mapContent(res.content));
204
+ stopReason = mapStop(res.stop_reason);
205
+ if (res.stop_reason === "pause_turn") {
206
+ messages = [...messages, { role: "assistant", content: res.content }];
207
+ continue;
208
+ }
209
+ break;
210
+ }
211
+ return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
212
+ } catch (err) {
213
+ mapProviderError(err, "anthropic", signal);
214
+ }
215
+ }
216
+ async *stream(spec, req) {
217
+ const signal = signalWithDeadline(req.signal, req.deadline);
218
+ try {
219
+ const events = this.client.messages.stream(
220
+ buildParams(spec, req, toAnthropicMessages(req)),
221
+ { signal }
222
+ );
223
+ for await (const raw of events) {
224
+ const ev = mapStreamEvent(raw, req);
225
+ if (ev) yield ev;
226
+ }
227
+ } catch (err) {
228
+ yield { type: "error", error: normalizeError(err, "anthropic", signal).toShape() };
229
+ }
230
+ }
231
+ };
216
232
  export {
217
233
  AnthropicProvider
218
234
  };
219
- //# sourceMappingURL=anthropic-JXDXR7O7.js.map
235
+ //# sourceMappingURL=anthropic-2NPMHJZT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/anthropic.ts","../src/providers/anthropic/request.ts","../src/providers/anthropic/response.ts","../src/providers/anthropic/stream.ts"],"sourcesContent":["// Anthropic (Claude) adapter. The canonical shape is Anthropic-flavored, so this\n// is the lowest-translation adapter. Ported from odla-kg's claude.ts patterns:\n// forced tool-call via tool_choice, web_search_20260209 with pause_turn\n// continuation, adaptive thinking + output_config.effort (never budget_tokens).\n//\n// The installed @anthropic-ai/sdk (0.70.x) doesn't yet type adaptive thinking,\n// output_config, or web_search_20260209 — those newer request fields are built\n// into a loose params object in ./anthropic/request and cast at the call site\n// (the wire accepts them), exactly as odla-kg does for the web-search tool literal.\n\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { mapProviderError, normalizeError } from \"./errors\";\nimport { addUsage, emptyUsage, type OracleContentBlock, type OracleEvent, type OracleRequest, type OracleResponse, type OracleStopReason } from \"../shape/index\";\nimport { buildParams, toAnthropicMessages } from \"./anthropic/request\";\nimport { mapContent, mapStop, mapUsage } from \"./anthropic/response\";\nimport { mapStreamEvent } from \"./anthropic/stream\";\nimport { signalWithDeadline } from \"../client/abort\";\n\nexport class AnthropicProvider implements Provider {\n readonly id = \"anthropic\" as const;\n private client: Anthropic;\n\n /** `client` may be injected (tests, custom transport); otherwise built from key. */\n constructor(apiKey: string, client?: Anthropic) {\n this.client = client ?? new Anthropic({ apiKey });\n }\n\n async create(spec: ModelSpec, req: OracleRequest): Promise<OracleResponse> {\n const signal = signalWithDeadline(req.signal, req.deadline);\n try {\n let messages = toAnthropicMessages(req);\n const content: OracleContentBlock[] = [];\n const usage = emptyUsage();\n let stopReason: OracleStopReason = \"end_turn\";\n let id = \"\";\n\n // Loop only to resolve server-side tools (pause_turn), max 8 hops. Client\n // tool_use / end_turn / etc. return immediately.\n for (let i = 0; i < 8; i++) {\n const res = await this.client.messages.create(\n buildParams(spec, req, messages) as unknown as Anthropic.MessageCreateParamsNonStreaming,\n { signal },\n );\n id = res.id;\n addUsage(usage, mapUsage(res.usage));\n content.push(...mapContent(res.content));\n stopReason = mapStop(res.stop_reason);\n if (res.stop_reason === \"pause_turn\") {\n messages = [...messages, { role: \"assistant\", content: res.content }];\n continue;\n }\n break;\n }\n\n return { id, model: req.model, provider: \"anthropic\", role: \"assistant\", content, stopReason, usage };\n } catch (err) {\n mapProviderError(err, \"anthropic\", signal);\n }\n }\n\n async *stream(spec: ModelSpec, req: OracleRequest): AsyncIterable<OracleEvent> {\n const signal = signalWithDeadline(req.signal, req.deadline);\n try {\n const events = this.client.messages.stream(\n buildParams(spec, req, toAnthropicMessages(req)) as unknown as Anthropic.MessageStreamParams,\n { signal },\n );\n for await (const raw of events) {\n const ev = mapStreamEvent(raw, req);\n if (ev) yield ev;\n }\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"anthropic\", signal).toShape() };\n }\n }\n}\n","// Anthropic request mapping. The canonical shape is Anthropic-flavored, so this\n// is the lowest-translation adapter.\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { ModelSpec } from \"../../shape/capabilities\";\nimport type { OracleContentBlock, OracleRequest, TextBlock, ToolChoice } from \"../../shape/index\";\n\nexport function buildParams(\n spec: ModelSpec,\n req: OracleRequest,\n messages: Anthropic.MessageParam[],\n): Record<string, unknown> {\n const params: Record<string, unknown> = {\n model: spec.nativeId,\n max_tokens: req.maxTokens,\n messages,\n };\n\n const system = toAnthropicSystem(req.system);\n if (system !== undefined) params.system = system;\n\n const tools = buildTools(req);\n if (tools) params.tools = tools;\n\n const toolChoice = mapToolChoice(req.toolChoice);\n if (toolChoice) params.tool_choice = toolChoice;\n\n if (req.stopSequences && req.stopSequences.length > 0) params.stop_sequences = req.stopSequences;\n\n // Sampling params are rejected on the frontier models (they carry effort);\n // only the non-effort tier (Haiku) accepts temperature.\n if (req.temperature !== undefined && !spec.capabilities.effort) params.temperature = req.temperature;\n\n if (req.thinking === \"adaptive\" && spec.capabilities.thinking) params.thinking = { type: \"adaptive\" };\n\n const outputConfig: Record<string, unknown> = {};\n if (req.effort && spec.capabilities.effort) outputConfig.effort = req.effort;\n if (req.responseFormat && spec.capabilities.structuredOutput) {\n outputConfig.format = { type: \"json_schema\", schema: req.responseFormat.schema };\n }\n if (Object.keys(outputConfig).length > 0) params.output_config = outputConfig;\n\n if (req.providerExtras) Object.assign(params, req.providerExtras);\n return params;\n}\n\nfunction toAnthropicSystem(system: OracleRequest[\"system\"]): string | Anthropic.TextBlockParam[] | undefined {\n if (system === undefined) return undefined;\n if (typeof system === \"string\") return system;\n return system.map((b: TextBlock) => ({\n type: \"text\" as const,\n text: b.text,\n ...(b.cacheControl ? { cache_control: { type: \"ephemeral\" as const } } : {}),\n }));\n}\n\nexport function toAnthropicMessages(req: OracleRequest): Anthropic.MessageParam[] {\n return req.messages.map((m) => ({\n role: m.role,\n content: typeof m.content === \"string\" ? m.content : m.content.map(anthropicBlock),\n }));\n}\n\nfunction anthropicBlock(b: OracleContentBlock): Anthropic.ContentBlockParam {\n switch (b.type) {\n case \"text\":\n return {\n type: \"text\",\n text: b.text,\n ...(b.cacheControl ? { cache_control: { type: \"ephemeral\" } } : {}),\n };\n case \"image\":\n return {\n type: \"image\",\n source:\n b.source.type === \"base64\"\n ? { type: \"base64\", media_type: b.source.mediaType, data: b.source.data }\n : { type: \"url\", url: b.source.url },\n } as Anthropic.ContentBlockParam;\n case \"document\":\n return {\n type: \"document\",\n source:\n b.source.type === \"base64\"\n ? { type: \"base64\", media_type: \"application/pdf\", data: b.source.data }\n : { type: \"url\", url: b.source.url },\n } as Anthropic.ContentBlockParam;\n case \"tool_use\":\n return { type: \"tool_use\", id: b.id, name: b.name, input: b.input };\n case \"tool_result\":\n return {\n type: \"tool_result\",\n tool_use_id: b.toolUseId,\n content:\n typeof b.content === \"string\"\n ? b.content\n : (b.content.map(anthropicBlock) as Anthropic.ToolResultBlockParam[\"content\"]),\n ...(b.isError ? { is_error: true } : {}),\n };\n case \"thinking\":\n return { type: \"thinking\", thinking: b.thinking, signature: b.signature ?? \"\" };\n case \"audio\":\n // Guarded by validateRequest; defensive in case validation is bypassed.\n throw new Error(\"Anthropic does not support audio input\");\n }\n}\n\nfunction buildTools(req: OracleRequest): unknown[] | undefined {\n const tools: unknown[] = [];\n for (const t of req.tools ?? []) {\n tools.push({ name: t.name, description: t.description, input_schema: t.inputSchema });\n }\n if (req.webSearch) tools.push({ type: \"web_search_20260209\", name: \"web_search\" });\n return tools.length > 0 ? tools : undefined;\n}\n\nfunction mapToolChoice(tc: ToolChoice | undefined): unknown | undefined {\n if (tc === undefined) return undefined;\n if (tc === \"auto\") return { type: \"auto\" };\n if (tc === \"any\") return { type: \"any\" };\n if (tc === \"none\") return { type: \"none\" };\n return { type: \"tool\", name: tc.name };\n}\n","// Anthropic response mapping. mapUsage / mapStop are also used by the stream\n// mapper (kept here so the entry ↔ stream edge stays one-directional).\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { OracleContentBlock, OracleStopReason, OracleUsage } from \"../../shape/index\";\nimport { toolInput } from \"../tool-input\";\n\nexport function mapContent(blocks: Anthropic.ContentBlock[]): OracleContentBlock[] {\n const out: OracleContentBlock[] = [];\n for (const b of blocks) {\n if (b.type === \"text\") out.push({ type: \"text\", text: b.text });\n else if (b.type === \"tool_use\") {\n out.push({ type: \"tool_use\", id: b.id, name: b.name, input: toolInput(b.input ?? {}, \"anthropic\", b.name) });\n } else if (b.type === \"thinking\") {\n out.push({ type: \"thinking\", thinking: b.thinking, signature: b.signature });\n }\n // server_tool_use / web_search_tool_result blocks are dropped — the answer\n // text lives in text blocks.\n }\n return out;\n}\n\nexport function mapUsage(u: Anthropic.Usage): OracleUsage {\n const usage: OracleUsage = { inputTokens: u.input_tokens ?? 0, outputTokens: u.output_tokens ?? 0 };\n if (u.cache_creation_input_tokens != null) usage.cacheCreationTokens = u.cache_creation_input_tokens;\n if (u.cache_read_input_tokens != null) usage.cacheReadTokens = u.cache_read_input_tokens;\n return usage;\n}\n\nexport function mapStop(reason: string | null | undefined): OracleStopReason {\n switch (reason) {\n case \"end_turn\":\n case \"max_tokens\":\n case \"stop_sequence\":\n case \"tool_use\":\n case \"pause_turn\":\n case \"refusal\":\n return reason;\n default:\n return \"end_turn\";\n }\n}\n","// Anthropic stream mapping. The SDK's raw events pass through nearly 1:1.\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { OracleContentBlock, OracleEvent, OracleRequest } from \"../../shape/index\";\nimport { mapStop, mapUsage } from \"./response\";\n\nexport function mapStreamEvent(raw: Anthropic.RawMessageStreamEvent, req: OracleRequest): OracleEvent | undefined {\n switch (raw.type) {\n case \"message_start\":\n return {\n type: \"message_start\",\n message: {\n id: raw.message.id,\n model: req.model,\n provider: \"anthropic\",\n role: \"assistant\",\n content: [],\n usage: mapUsage(raw.message.usage),\n },\n };\n case \"content_block_start\": {\n const block = mapStartBlock(raw.content_block);\n return block ? { type: \"content_block_start\", index: raw.index, contentBlock: block } : undefined;\n }\n case \"content_block_delta\": {\n const d = raw.delta;\n if (d.type === \"text_delta\") return { type: \"content_block_delta\", index: raw.index, delta: { type: \"text_delta\", text: d.text } };\n if (d.type === \"thinking_delta\") return { type: \"content_block_delta\", index: raw.index, delta: { type: \"thinking_delta\", thinking: d.thinking } };\n if (d.type === \"input_json_delta\") return { type: \"content_block_delta\", index: raw.index, delta: { type: \"input_json_delta\", partialJson: d.partial_json } };\n return undefined;\n }\n case \"content_block_stop\":\n return { type: \"content_block_stop\", index: raw.index };\n case \"message_delta\":\n return { type: \"message_delta\", delta: { stopReason: mapStop(raw.delta.stop_reason) }, usage: mapUsage(raw.usage as Anthropic.Usage) };\n case \"message_stop\":\n return { type: \"message_stop\" };\n default:\n return undefined;\n }\n}\n\nfunction mapStartBlock(cb: Anthropic.ContentBlock): OracleContentBlock | undefined {\n if (cb.type === \"text\") return { type: \"text\", text: cb.text };\n if (cb.type === \"tool_use\") return { type: \"tool_use\", id: cb.id, name: cb.name, input: {} };\n if (cb.type === \"thinking\") return { type: \"thinking\", thinking: cb.thinking, signature: cb.signature };\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;AAUA,OAAO,eAAe;;;ACJf,SAAS,YACd,MACA,KACA,UACyB;AACzB,QAAM,SAAkC;AAAA,IACtC,OAAO,KAAK;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,kBAAkB,IAAI,MAAM;AAC3C,MAAI,WAAW,OAAW,QAAO,SAAS;AAE1C,QAAM,QAAQ,WAAW,GAAG;AAC5B,MAAI,MAAO,QAAO,QAAQ;AAE1B,QAAM,aAAa,cAAc,IAAI,UAAU;AAC/C,MAAI,WAAY,QAAO,cAAc;AAErC,MAAI,IAAI,iBAAiB,IAAI,cAAc,SAAS,EAAG,QAAO,iBAAiB,IAAI;AAInF,MAAI,IAAI,gBAAgB,UAAa,CAAC,KAAK,aAAa,OAAQ,QAAO,cAAc,IAAI;AAEzF,MAAI,IAAI,aAAa,cAAc,KAAK,aAAa,SAAU,QAAO,WAAW,EAAE,MAAM,WAAW;AAEpG,QAAM,eAAwC,CAAC;AAC/C,MAAI,IAAI,UAAU,KAAK,aAAa,OAAQ,cAAa,SAAS,IAAI;AACtE,MAAI,IAAI,kBAAkB,KAAK,aAAa,kBAAkB;AAC5D,iBAAa,SAAS,EAAE,MAAM,eAAe,QAAQ,IAAI,eAAe,OAAO;AAAA,EACjF;AACA,MAAI,OAAO,KAAK,YAAY,EAAE,SAAS,EAAG,QAAO,gBAAgB;AAEjE,MAAI,IAAI,eAAgB,QAAO,OAAO,QAAQ,IAAI,cAAc;AAChE,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAkF;AAC3G,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,SAAO,OAAO,IAAI,CAAC,OAAkB;AAAA,IACnC,MAAM;AAAA,IACN,MAAM,EAAE;AAAA,IACR,GAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAqB,EAAE,IAAI,CAAC;AAAA,EAC5E,EAAE;AACJ;AAEO,SAAS,oBAAoB,KAA8C;AAChF,SAAO,IAAI,SAAS,IAAI,CAAC,OAAO;AAAA,IAC9B,MAAM,EAAE;AAAA,IACR,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,EAAE,QAAQ,IAAI,cAAc;AAAA,EACnF,EAAE;AACJ;AAEA,SAAS,eAAe,GAAoD;AAC1E,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,EAAE;AAAA,QACR,GAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,EAAE,IAAI,CAAC;AAAA,MACnE;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QACE,EAAE,OAAO,SAAS,WACd,EAAE,MAAM,UAAU,YAAY,EAAE,OAAO,WAAW,MAAM,EAAE,OAAO,KAAK,IACtE,EAAE,MAAM,OAAO,KAAK,EAAE,OAAO,IAAI;AAAA,MACzC;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QACE,EAAE,OAAO,SAAS,WACd,EAAE,MAAM,UAAU,YAAY,mBAAmB,MAAM,EAAE,OAAO,KAAK,IACrE,EAAE,MAAM,OAAO,KAAK,EAAE,OAAO,IAAI;AAAA,MACzC;AAAA,IACF,KAAK;AACH,aAAO,EAAE,MAAM,YAAY,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,OAAO,EAAE,MAAM;AAAA,IACpE,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,aAAa,EAAE;AAAA,QACf,SACE,OAAO,EAAE,YAAY,WACjB,EAAE,UACD,EAAE,QAAQ,IAAI,cAAc;AAAA,QACnC,GAAI,EAAE,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAAA,MACxC;AAAA,IACF,KAAK;AACH,aAAO,EAAE,MAAM,YAAY,UAAU,EAAE,UAAU,WAAW,EAAE,aAAa,GAAG;AAAA,IAChF,KAAK;AAEH,YAAM,IAAI,MAAM,wCAAwC;AAAA,EAC5D;AACF;AAEA,SAAS,WAAW,KAA2C;AAC7D,QAAM,QAAmB,CAAC;AAC1B,aAAW,KAAK,IAAI,SAAS,CAAC,GAAG;AAC/B,UAAM,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,EAAE,aAAa,cAAc,EAAE,YAAY,CAAC;AAAA,EACtF;AACA,MAAI,IAAI,UAAW,OAAM,KAAK,EAAE,MAAM,uBAAuB,MAAM,aAAa,CAAC;AACjF,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,cAAc,IAAiD;AACtE,MAAI,OAAO,OAAW,QAAO;AAC7B,MAAI,OAAO,OAAQ,QAAO,EAAE,MAAM,OAAO;AACzC,MAAI,OAAO,MAAO,QAAO,EAAE,MAAM,MAAM;AACvC,MAAI,OAAO,OAAQ,QAAO,EAAE,MAAM,OAAO;AACzC,SAAO,EAAE,MAAM,QAAQ,MAAM,GAAG,KAAK;AACvC;;;ACnHO,SAAS,WAAW,QAAwD;AACjF,QAAM,MAA4B,CAAC;AACnC,aAAW,KAAK,QAAQ;AACtB,QAAI,EAAE,SAAS,OAAQ,KAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,EAAE,KAAK,CAAC;AAAA,aACrD,EAAE,SAAS,YAAY;AAC9B,UAAI,KAAK,EAAE,MAAM,YAAY,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,OAAO,UAAU,EAAE,SAAS,CAAC,GAAG,aAAa,EAAE,IAAI,EAAE,CAAC;AAAA,IAC7G,WAAW,EAAE,SAAS,YAAY;AAChC,UAAI,KAAK,EAAE,MAAM,YAAY,UAAU,EAAE,UAAU,WAAW,EAAE,UAAU,CAAC;AAAA,IAC7E;AAAA,EAGF;AACA,SAAO;AACT;AAEO,SAAS,SAAS,GAAiC;AACxD,QAAM,QAAqB,EAAE,aAAa,EAAE,gBAAgB,GAAG,cAAc,EAAE,iBAAiB,EAAE;AAClG,MAAI,EAAE,+BAA+B,KAAM,OAAM,sBAAsB,EAAE;AACzE,MAAI,EAAE,2BAA2B,KAAM,OAAM,kBAAkB,EAAE;AACjE,SAAO;AACT;AAEO,SAAS,QAAQ,QAAqD;AAC3E,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;ACnCO,SAAS,eAAe,KAAsC,KAA6C;AAChH,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,UACP,IAAI,IAAI,QAAQ;AAAA,UAChB,OAAO,IAAI;AAAA,UACX,UAAU;AAAA,UACV,MAAM;AAAA,UACN,SAAS,CAAC;AAAA,UACV,OAAO,SAAS,IAAI,QAAQ,KAAK;AAAA,QACnC;AAAA,MACF;AAAA,IACF,KAAK,uBAAuB;AAC1B,YAAM,QAAQ,cAAc,IAAI,aAAa;AAC7C,aAAO,QAAQ,EAAE,MAAM,uBAAuB,OAAO,IAAI,OAAO,cAAc,MAAM,IAAI;AAAA,IAC1F;AAAA,IACA,KAAK,uBAAuB;AAC1B,YAAM,IAAI,IAAI;AACd,UAAI,EAAE,SAAS,aAAc,QAAO,EAAE,MAAM,uBAAuB,OAAO,IAAI,OAAO,OAAO,EAAE,MAAM,cAAc,MAAM,EAAE,KAAK,EAAE;AACjI,UAAI,EAAE,SAAS,iBAAkB,QAAO,EAAE,MAAM,uBAAuB,OAAO,IAAI,OAAO,OAAO,EAAE,MAAM,kBAAkB,UAAU,EAAE,SAAS,EAAE;AACjJ,UAAI,EAAE,SAAS,mBAAoB,QAAO,EAAE,MAAM,uBAAuB,OAAO,IAAI,OAAO,OAAO,EAAE,MAAM,oBAAoB,aAAa,EAAE,aAAa,EAAE;AAC5J,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,EAAE,MAAM,sBAAsB,OAAO,IAAI,MAAM;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,MAAM,iBAAiB,OAAO,EAAE,YAAY,QAAQ,IAAI,MAAM,WAAW,EAAE,GAAG,OAAO,SAAS,IAAI,KAAwB,EAAE;AAAA,IACvI,KAAK;AACH,aAAO,EAAE,MAAM,eAAe;AAAA,IAChC;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,cAAc,IAA4D;AACjF,MAAI,GAAG,SAAS,OAAQ,QAAO,EAAE,MAAM,QAAQ,MAAM,GAAG,KAAK;AAC7D,MAAI,GAAG,SAAS,WAAY,QAAO,EAAE,MAAM,YAAY,IAAI,GAAG,IAAI,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE;AAC3F,MAAI,GAAG,SAAS,WAAY,QAAO,EAAE,MAAM,YAAY,UAAU,GAAG,UAAU,WAAW,GAAG,UAAU;AACtG,SAAO;AACT;;;AH1BO,IAAM,oBAAN,MAA4C;AAAA,EACxC,KAAK;AAAA,EACN;AAAA;AAAA,EAGR,YAAY,QAAgB,QAAoB;AAC9C,SAAK,SAAS,UAAU,IAAI,UAAU,EAAE,OAAO,CAAC;AAAA,EAClD;AAAA,EAEA,MAAM,OAAO,MAAiB,KAA6C;AACzE,UAAM,SAAS,mBAAmB,IAAI,QAAQ,IAAI,QAAQ;AAC1D,QAAI;AACF,UAAI,WAAW,oBAAoB,GAAG;AACtC,YAAM,UAAgC,CAAC;AACvC,YAAM,QAAQ,WAAW;AACzB,UAAI,aAA+B;AACnC,UAAI,KAAK;AAIT,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,MAAM,MAAM,KAAK,OAAO,SAAS;AAAA,UACrC,YAAY,MAAM,KAAK,QAAQ;AAAA,UAC/B,EAAE,OAAO;AAAA,QACX;AACA,aAAK,IAAI;AACT,iBAAS,OAAO,SAAS,IAAI,KAAK,CAAC;AACnC,gBAAQ,KAAK,GAAG,WAAW,IAAI,OAAO,CAAC;AACvC,qBAAa,QAAQ,IAAI,WAAW;AACpC,YAAI,IAAI,gBAAgB,cAAc;AACpC,qBAAW,CAAC,GAAG,UAAU,EAAE,MAAM,aAAa,SAAS,IAAI,QAAQ,CAAC;AACpE;AAAA,QACF;AACA;AAAA,MACF;AAEA,aAAO,EAAE,IAAI,OAAO,IAAI,OAAO,UAAU,aAAa,MAAM,aAAa,SAAS,YAAY,MAAM;AAAA,IACtG,SAAS,KAAK;AACZ,uBAAiB,KAAK,aAAa,MAAM;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,OAAO,OAAO,MAAiB,KAAgD;AAC7E,UAAM,SAAS,mBAAmB,IAAI,QAAQ,IAAI,QAAQ;AAC1D,QAAI;AACF,YAAM,SAAS,KAAK,OAAO,SAAS;AAAA,QAClC,YAAY,MAAM,KAAK,oBAAoB,GAAG,CAAC;AAAA,QAC/C,EAAE,OAAO;AAAA,MACX;AACA,uBAAiB,OAAO,QAAQ;AAC9B,cAAM,KAAK,eAAe,KAAK,GAAG;AAClC,YAAI,GAAI,OAAM;AAAA,MAChB;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,EAAE,MAAM,SAAS,OAAO,eAAe,KAAK,aAAa,MAAM,EAAE,QAAQ,EAAE;AAAA,IACnF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,16 @@
1
+ import {
2
+ ToolInputError
3
+ } from "./chunk-PXXCN2EU.js";
4
+
5
+ // src/providers/tool-input.ts
6
+ function toolInput(value, provider, name) {
7
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
8
+ return value;
9
+ }
10
+ throw new ToolInputError(`[${provider}] tool "${name}" returned arguments that are not a JSON object.`, { tool: name });
11
+ }
12
+
13
+ export {
14
+ toolInput
15
+ };
16
+ //# sourceMappingURL=chunk-OX4TA4ZH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/tool-input.ts"],"sourcesContent":["import { ToolInputError, type ProviderId } from \"../shape/index\";\n\n/** Normalize a provider's decoded function arguments without manufacturing an\n * empty object from malformed data. Schema validation happens at the facade or\n * agent boundary, but structural corruption fails here. */\nexport function toolInput(value: unknown, provider: ProviderId, name: string): Record<string, unknown> {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n throw new ToolInputError(`[${provider}] tool \"${name}\" returned arguments that are not a JSON object.`, { tool: name });\n}\n"],"mappings":";;;;;AAKO,SAAS,UAAU,OAAgB,UAAsB,MAAuC;AACrG,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACxE,WAAO;AAAA,EACT;AACA,QAAM,IAAI,eAAe,IAAI,QAAQ,WAAW,IAAI,oDAAoD,EAAE,MAAM,KAAK,CAAC;AACxH;","names":[]}
@@ -1,4 +1,4 @@
1
- // src/shape/index.ts
1
+ // src/shape/usage.ts
2
2
  function emptyUsage() {
3
3
  return { inputTokens: 0, outputTokens: 0 };
4
4
  }
@@ -12,6 +12,8 @@ function addUsage(into, more) {
12
12
  into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;
13
13
  }
14
14
  }
15
+
16
+ // src/shape/errors.ts
15
17
  var OdlaAIError = class extends Error {
16
18
  code;
17
19
  providerStatus;
@@ -62,41 +64,28 @@ var InvalidRequestError = class extends OdlaAIError {
62
64
  super("invalid_request", message, opts);
63
65
  }
64
66
  };
67
+ var ToolInputError = class extends OdlaAIError {
68
+ tool;
69
+ constructor(message, opts) {
70
+ super("tool_input_invalid", message, { cause: opts?.cause });
71
+ this.tool = opts?.tool;
72
+ }
73
+ };
74
+ var CancelledError = class extends OdlaAIError {
75
+ constructor(message = "AI request was cancelled.", opts) {
76
+ super("cancelled", message, opts);
77
+ }
78
+ };
79
+ var DeadlineExceededError = class extends OdlaAIError {
80
+ constructor(message = "AI request deadline was exceeded.", opts) {
81
+ super("deadline_exceeded", message, opts);
82
+ }
83
+ };
65
84
  var ProviderError = class extends OdlaAIError {
66
85
  constructor(message, opts) {
67
86
  super("provider_error", message, opts);
68
87
  }
69
88
  };
70
- function isTextBlock(b) {
71
- return b.type === "text";
72
- }
73
- function isImageBlock(b) {
74
- return b.type === "image";
75
- }
76
- function isAudioBlock(b) {
77
- return b.type === "audio";
78
- }
79
- function isDocumentBlock(b) {
80
- return b.type === "document";
81
- }
82
- function isToolUseBlock(b) {
83
- return b.type === "tool_use";
84
- }
85
- function isToolResultBlock(b) {
86
- return b.type === "tool_result";
87
- }
88
- function isThinkingBlock(b) {
89
- return b.type === "thinking";
90
- }
91
- function blocksOf(content) {
92
- return typeof content === "string" ? [{ type: "text", text: content }] : content;
93
- }
94
- function extractText(content) {
95
- return content.filter(isTextBlock).map((b) => b.text).join("");
96
- }
97
- function extractToolUses(content) {
98
- return content.filter(isToolUseBlock);
99
- }
100
89
 
101
90
  // src/providers/errors.ts
102
91
  function statusOf(err) {
@@ -125,8 +114,10 @@ function messageOf(err) {
125
114
  if (typeof err === "string") return err;
126
115
  return "unknown provider error";
127
116
  }
128
- function normalizeError(err, provider) {
117
+ function normalizeError(err, provider, signal) {
129
118
  if (err instanceof OdlaAIError) return err;
119
+ const cancellation = cancellationError(signal?.aborted ? signal.reason : err);
120
+ if (signal?.aborted || cancellation) return cancellation ?? new CancelledError(void 0, { cause: err });
130
121
  const status = statusOf(err);
131
122
  const message = messageOf(err);
132
123
  const tagged = `[${provider}] ${message}`;
@@ -142,8 +133,41 @@ function normalizeError(err, provider) {
142
133
  }
143
134
  return new ProviderError(tagged, { providerStatus: status, cause: err });
144
135
  }
145
- function mapProviderError(err, provider) {
146
- throw normalizeError(err, provider);
136
+ function mapProviderError(err, provider, signal) {
137
+ throw normalizeError(err, provider, signal);
138
+ }
139
+ function cancellationError(reason) {
140
+ if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) return reason;
141
+ if (!reason || typeof reason !== "object") return void 0;
142
+ const rec = reason;
143
+ if (rec.name === "TimeoutError") return new DeadlineExceededError(void 0, { cause: reason });
144
+ if (rec.name === "AbortError" || rec.name === "APIUserAbortError" || rec.code === "ABORT_ERR") {
145
+ return new CancelledError(void 0, { cause: reason });
146
+ }
147
+ return void 0;
148
+ }
149
+
150
+ // src/client/abort.ts
151
+ function signalWithDeadline(signal, deadline) {
152
+ if (deadline === void 0) return signal;
153
+ if (!Number.isSafeInteger(deadline)) throw new ConfigError("deadline must be a finite integer Unix timestamp in milliseconds.");
154
+ const remaining = deadline - Date.now();
155
+ if (remaining <= 0) {
156
+ const expired = new AbortController();
157
+ expired.abort(new DeadlineExceededError());
158
+ return signal ? AbortSignal.any([signal, expired.signal]) : expired.signal;
159
+ }
160
+ const timeout = AbortSignal.timeout(remaining);
161
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
162
+ }
163
+ function throwIfAborted(signal) {
164
+ if (!signal?.aborted) return;
165
+ const reason = signal.reason;
166
+ if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) throw reason;
167
+ if (reason && typeof reason === "object" && reason.name === "TimeoutError") {
168
+ throw new DeadlineExceededError(void 0, { cause: reason });
169
+ }
170
+ throw new CancelledError(void 0, { cause: reason });
147
171
  }
148
172
 
149
173
  export {
@@ -156,18 +180,13 @@ export {
156
180
  CapabilityError,
157
181
  ContextWindowError,
158
182
  InvalidRequestError,
183
+ ToolInputError,
184
+ CancelledError,
185
+ DeadlineExceededError,
159
186
  ProviderError,
160
- isTextBlock,
161
- isImageBlock,
162
- isAudioBlock,
163
- isDocumentBlock,
164
- isToolUseBlock,
165
- isToolResultBlock,
166
- isThinkingBlock,
167
- blocksOf,
168
- extractText,
169
- extractToolUses,
170
187
  normalizeError,
171
- mapProviderError
188
+ mapProviderError,
189
+ signalWithDeadline,
190
+ throwIfAborted
172
191
  };
173
- //# sourceMappingURL=chunk-LANGYP65.js.map
192
+ //# sourceMappingURL=chunk-PXXCN2EU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shape/usage.ts","../src/shape/errors.ts","../src/providers/errors.ts","../src/client/abort.ts"],"sourcesContent":["// Token usage accounting. The one runtime island with no dependencies.\n\nexport interface OracleUsage {\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n}\n\n/** A fresh zeroed usage tally (0 input/output tokens, no cache fields) — the seed for accumulation. */\nexport function emptyUsage(): OracleUsage {\n return { inputTokens: 0, outputTokens: 0 };\n}\n\n/** Accumulate `more` into `into`, in place. Cache fields sum when present. */\nexport function addUsage(into: OracleUsage, more: OracleUsage): void {\n into.inputTokens += more.inputTokens;\n into.outputTokens += more.outputTokens;\n if (more.cacheCreationTokens !== undefined) {\n into.cacheCreationTokens = (into.cacheCreationTokens ?? 0) + more.cacheCreationTokens;\n }\n if (more.cacheReadTokens !== undefined) {\n into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;\n }\n}\n","// The normalized error taxonomy and the class hierarchy every adapter throws.\n\n/** The closed taxonomy of normalized error codes carried by every OdlaAIError\n * and by the streaming `error` event. */\nexport type OracleErrorType =\n | \"invalid_request\"\n | \"auth\"\n | \"rate_limit\"\n | \"context_window\"\n | \"cancelled\"\n | \"deadline_exceeded\"\n | \"tool_input_invalid\"\n | \"capability_unsupported\"\n | \"config\"\n | \"provider_error\";\n\n/** The plain-data error shape used inside the streaming `error` event. */\nexport interface OracleErrorShape {\n code: OracleErrorType;\n message: string;\n /** HTTP status from the upstream provider, when applicable. */\n providerStatus?: number;\n /** Seconds until a retry might succeed (rate limit / overload). */\n retryAfter?: number;\n}\n\n/** Base class for every error odla-ai throws. Carries a stable string `code`\n * (the odla ecosystem convention — see odla-db's AuthError/RuleError) so\n * callers branch on `err.code`, never on message text. */\nexport class OdlaAIError extends Error {\n readonly code: OracleErrorType;\n readonly providerStatus?: number;\n readonly retryAfter?: number;\n\n constructor(\n code: OracleErrorType,\n message: string,\n opts?: { providerStatus?: number; retryAfter?: number; cause?: unknown },\n ) {\n super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n this.code = code;\n this.providerStatus = opts?.providerStatus;\n this.retryAfter = opts?.retryAfter;\n }\n\n toShape(): OracleErrorShape {\n return {\n code: this.code,\n message: this.message,\n providerStatus: this.providerStatus,\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/** A provider/model is not configured — e.g. no API key for its provider, or an\n * unknown canonical model id. The library analog of odla-db's 501 gating. */\nexport class ConfigError extends OdlaAIError {\n constructor(message: string) {\n super(\"config\", message);\n }\n}\n\n/** Authentication with the provider failed (bad/missing key). */\nexport class AuthError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"auth\", message, opts);\n }\n}\n\n/** The provider rate-limited or is overloaded; check `retryAfter`. */\nexport class RateLimitError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; retryAfter?: number; cause?: unknown }) {\n super(\"rate_limit\", message, opts);\n }\n}\n\n/** The request asked a model to do something it cannot — audio to Claude, tools\n * to a text-only model, web search where unsupported, etc. Raised before any\n * network call by `validateRequest`. */\nexport class CapabilityError extends OdlaAIError {\n constructor(message: string) {\n super(\"capability_unsupported\", message);\n }\n}\n\n/** The request exceeded the model's context window. */\nexport class ContextWindowError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"context_window\", message, opts);\n }\n}\n\n/** The request was malformed / rejected as invalid by the provider. */\nexport class InvalidRequestError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"invalid_request\", message, opts);\n }\n}\n\n/** A provider returned tool arguments that are malformed or do not satisfy the\n * declared JSON Schema. Tool handlers are never invoked for this error. */\nexport class ToolInputError extends OdlaAIError {\n readonly tool?: string;\n\n constructor(message: string, opts?: { tool?: string; cause?: unknown }) {\n super(\"tool_input_invalid\", message, { cause: opts?.cause });\n this.tool = opts?.tool;\n }\n}\n\n/** The caller cancelled a request. This is not an upstream failure and should\n * not be retried unless the caller explicitly starts a new operation. */\nexport class CancelledError extends OdlaAIError {\n constructor(message = \"AI request was cancelled.\", opts?: { cause?: unknown }) {\n super(\"cancelled\", message, opts);\n }\n}\n\n/** The request's absolute deadline elapsed. Callers may choose a new deadline,\n * but should not classify this as a provider outage. */\nexport class DeadlineExceededError extends OdlaAIError {\n constructor(message = \"AI request deadline was exceeded.\", opts?: { cause?: unknown }) {\n super(\"deadline_exceeded\", message, opts);\n }\n}\n\n/** Any other upstream provider failure (5xx, transport, unexpected shape). */\nexport class ProviderError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"provider_error\", message, opts);\n }\n}\n","// Normalize a thrown provider-SDK error into the odla-ai error taxonomy. Kept\n// dependency-light: it duck-types the HTTP status / retry-after off the error\n// object rather than importing each SDK's error classes, so it works uniformly\n// across @anthropic-ai/sdk, openai, and @google/genai.\n\nimport {\n AuthError,\n CancelledError,\n ContextWindowError,\n DeadlineExceededError,\n InvalidRequestError,\n OdlaAIError,\n ProviderError,\n RateLimitError,\n type ProviderId,\n} from \"../shape/index\";\n\nfunction statusOf(err: unknown): number | undefined {\n if (err && typeof err === \"object\") {\n const rec = err as Record<string, unknown>;\n for (const k of [\"status\", \"statusCode\", \"code\"]) {\n const v = rec[k];\n if (typeof v === \"number\") return v;\n }\n }\n return undefined;\n}\n\nfunction retryAfterOf(err: unknown): number | undefined {\n if (err && typeof err === \"object\") {\n const headers = (err as { headers?: unknown }).headers;\n if (headers && typeof headers === \"object\") {\n const raw = (headers as Record<string, unknown>)[\"retry-after\"];\n const n = typeof raw === \"string\" ? Number(raw) : typeof raw === \"number\" ? raw : NaN;\n if (Number.isFinite(n)) return n;\n }\n }\n return undefined;\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === \"string\") return err;\n return \"unknown provider error\";\n}\n\n/** Map any provider error to an OdlaAIError, returning it (does not throw).\n * Re-returns an OdlaAIError unchanged (already normalized). */\nexport function normalizeError(err: unknown, provider: ProviderId, signal?: AbortSignal): OdlaAIError {\n if (err instanceof OdlaAIError) return err;\n const cancellation = cancellationError(signal?.aborted ? signal.reason : err);\n if (signal?.aborted || cancellation) return cancellation ?? new CancelledError(undefined, { cause: err });\n\n const status = statusOf(err);\n const message = messageOf(err);\n const tagged = `[${provider}] ${message}`;\n\n if (status === 401 || status === 403) return new AuthError(tagged, { providerStatus: status, cause: err });\n if (status === 429) {\n return new RateLimitError(tagged, { providerStatus: status, retryAfter: retryAfterOf(err), cause: err });\n }\n if (status === 400 || status === 422) {\n if (/context|token limit|too long|maximum context/i.test(message)) {\n return new ContextWindowError(tagged, { providerStatus: status, cause: err });\n }\n return new InvalidRequestError(tagged, { providerStatus: status, cause: err });\n }\n return new ProviderError(tagged, { providerStatus: status, cause: err });\n}\n\n/**\n * Normalize and throw. The `never` return lets call sites write\n * `catch (e) { mapProviderError(e, id); }` with no fallthrough.\n */\nexport function mapProviderError(err: unknown, provider: ProviderId, signal?: AbortSignal): never {\n throw normalizeError(err, provider, signal);\n}\n\nfunction cancellationError(reason: unknown): CancelledError | DeadlineExceededError | undefined {\n if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) return reason;\n if (!reason || typeof reason !== \"object\") return undefined;\n const rec = reason as { name?: unknown; code?: unknown; message?: unknown };\n if (rec.name === \"TimeoutError\") return new DeadlineExceededError(undefined, { cause: reason });\n if (rec.name === \"AbortError\" || rec.name === \"APIUserAbortError\" || rec.code === \"ABORT_ERR\") {\n return new CancelledError(undefined, { cause: reason });\n }\n return undefined;\n}\n","import { CancelledError, ConfigError, DeadlineExceededError } from \"../shape/errors\";\n\n/** Combine a caller signal with an absolute deadline. Kept provider-neutral so\n * every adapter and the agent loop observes the same cancellation contract. */\nexport function signalWithDeadline(signal?: AbortSignal, deadline?: number): AbortSignal | undefined {\n if (deadline === undefined) return signal;\n if (!Number.isSafeInteger(deadline)) throw new ConfigError(\"deadline must be a finite integer Unix timestamp in milliseconds.\");\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n const expired = new AbortController();\n expired.abort(new DeadlineExceededError());\n return signal ? AbortSignal.any([signal, expired.signal]) : expired.signal;\n }\n const timeout = AbortSignal.timeout(remaining);\n return signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\n/** Fail synchronously before beginning a local side effect when a run was\n * cancelled or its deadline elapsed. Handlers must still observe the signal\n * while in flight; this closes the already-aborted race at invocation time. */\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return;\n const reason = signal.reason;\n if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) throw reason;\n if (reason && typeof reason === \"object\" && (reason as { name?: unknown }).name === \"TimeoutError\") {\n throw new DeadlineExceededError(undefined, { cause: reason });\n }\n throw new CancelledError(undefined, { cause: reason });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ // src/providers/blocks.ts
2
+ function stringifyBlocks(blocks) {
3
+ return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
4
+ }
5
+
6
+ export {
7
+ stringifyBlocks
8
+ };
9
+ //# sourceMappingURL=chunk-U7F6VJCV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/blocks.ts"],"sourcesContent":["import type { OracleContentBlock } from \"../shape/index\";\n\n/** Render content blocks to a plain string for providers/fields that only accept\n * text: text blocks pass through, everything else becomes a `[type]` placeholder.\n * Shared by the openai and google request mappers (tool_result fallback). */\nexport function stringifyBlocks(blocks: OracleContentBlock[]): string {\n return blocks.map((b) => (b.type === \"text\" ? b.text : `[${b.type}]`)).join(\"\");\n}\n"],"mappings":";AAKO,SAAS,gBAAgB,QAAsC;AACpE,SAAO,OAAO,IAAI,CAAC,MAAO,EAAE,SAAS,SAAS,EAAE,OAAO,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAAE;AAChF;","names":[]}