@odla-ai/ai 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,28 @@
1
1
  # @odla-ai/ai
2
2
 
3
- > ⚠️ **Experimentalan agentic-coding experiment.** This package is built and
4
- > operated by autonomous coding agents as an experiment in agentic loops. APIs
5
- > change without notice and nothing here is production-hardened. **Use at your
6
- > own risk.**
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
7
 
8
8
  One general-purpose interface for AI inference across **Anthropic (Claude)**,
9
9
  **OpenAI (GPT)**, and **Google (Gemini)** — text, image, and audio — plus a
10
10
  tool-use **agent engine** and an **eval harness**. It's the place every odla app
11
11
  reaches for when it needs AI.
12
12
 
13
- - **Library-only.** Import it in-process and bring your own keys. No hosted
14
- gateway, no network hop.
13
+ - **Library surface.** Import it in-process and bring your own keys; this
14
+ package exports no generic hosted gateway. Separately, odla's System AI
15
+ broker uses the same facade server-side for bounded o11y/security purposes
16
+ and returns purpose grants rather than provider credentials.
15
17
  - **One canonical shape.** Anthropic-flavored content blocks, forced tool calls
16
18
  with object inputs, a normalized streaming event vocabulary, cache-aware usage,
17
19
  and a closed error taxonomy — the same regardless of which provider answers.
18
20
  - **Capability-checked.** Every request is validated against the model before
19
21
  dispatch, so "audio to Claude" fails fast with a clear error instead of an
20
22
  opaque provider 400.
21
- - **Isomorphic.** Node 20+, Cloudflare Workers, the browser. Provider SDKs are
22
- lazy-loaded a Claude-only consumer never pulls in `openai` or `@google/genai`.
23
+ - **Server runtimes.** Node 20+ and Cloudflare Workers. Provider adapters are
24
+ runtime-lazy, but all three provider SDKs are package dependencies. Do not put
25
+ long-lived provider keys in browser code; use your own authenticated proxy.
23
26
 
24
27
  ```bash
25
28
  npm install @odla-ai/ai
@@ -77,9 +80,17 @@ const { value } = await ai.extract<{ sentiment: "positive" | "negative" | "neutr
77
80
  },
78
81
  },
79
82
  });
80
- // value.sentiment === "positive" (already a parsed object never a JSON string)
83
+ // value.sentiment === "positive" (parsed and validated before it is returned)
81
84
  ```
82
85
 
86
+ `extract` and local agent tools validate provider arguments before returning or
87
+ executing them. The dependency-free validator supports the common tool-schema
88
+ subset: local `$ref`/`$defs`, `type`, `enum`, `const`, composition, object
89
+ properties/required/additional properties, arrays, and string/number constraints.
90
+ Unknown or unsupported schema keywords outside that documented subset fail
91
+ closed. `extract()` throws `ToolInputError`; local agent tools return an error
92
+ `tool_result` and their handlers do not execute.
93
+
83
94
  ### Web search
84
95
 
85
96
  ```ts
@@ -146,6 +157,28 @@ console.log(run.toolCalls); // [{ toolUse, output }]
146
157
  console.log(run.usage); // accumulated across turns
147
158
  ```
148
159
 
160
+ Provider calls and tool handlers receive a combined cancellation signal. Runs
161
+ can also stop before additional effects when a run-wide budget is exhausted:
162
+
163
+ ```ts
164
+ const run = await runAgent(ai, calculator, {
165
+ input: "Reconcile these records",
166
+ signal: request.signal,
167
+ deadline: Date.now() + 30_000,
168
+ budget: { maxInputTokens: 20_000, maxOutputTokens: 4_000, maxToolCalls: 8 },
169
+ });
170
+ ```
171
+
172
+ Token limits are post-turn ceilings because provider input usage is known only
173
+ after a response. `maxInputTokens` and `maxTotalTokens` therefore cannot
174
+ tokenizer-bound the first prompt; they stop later turns and tool effects once
175
+ reported usage reaches the ceiling. `maxOutputTokens` and the remaining total
176
+ budget cap each request's `maxTokens` before dispatch. Limits must be integers
177
+ (`maxToolCalls` may be zero; token limits must be positive). When a limit blocks
178
+ tools, the loop records a matching error `tool_result` for every requested tool
179
+ before persisting memory, so retries never inherit an unmatched effect request.
180
+ `stoppedReason` is `"budget_exhausted"` when a limit prevents further work.
181
+
149
182
  Tools can declare taint constraints (`outputTaint` / `acceptsTaint`) for a
150
183
  lightweight CaMeL-style prompt-injection gate, and personas can carry a pluggable
151
184
  `MemoryScope`.
@@ -229,20 +262,23 @@ The agent handshake (an agent never takes a platform secret):
229
262
  import { requestToken, init as odlaInit } from "@odla-ai/db";
230
263
  import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, runAgent, init } from "@odla-ai/ai";
231
264
 
232
- // 1. Device-authorization handshake — the human supplies the endpoint and
233
- // approves a short code in odla-db Studio; you get a scoped, revocable token.
234
- const { token } = await requestToken({ endpoint: ODLA_URL, onCode: ({ userCode }) => console.log(userCode) });
265
+ const ODLA_PLATFORM = "https://odla.ai";
266
+ const ODLA_DB_URL = "https://db.odla.ai";
267
+
268
+ // 1. Device authorization — the human approves a short code at odla.ai;
269
+ // the agent receives a scoped, revocable platform token.
270
+ const { token } = await requestToken({ endpoint: ODLA_PLATFORM, onCode: ({ userCode }) => console.log(userCode) });
235
271
 
236
272
  // 2. Provision: create app, mint an app key, push the agent schema, and store the
237
273
  // user's BYO provider keys as odla-db secrets (encrypted at rest).
238
274
  const { appId, appKey } = await provisionAgentApp({
239
- endpoint: ODLA_URL,
275
+ endpoint: ODLA_PLATFORM,
240
276
  token, // operator/dev token — needed to WRITE secrets
241
277
  providerKeys: { openai: OPENAI_KEY, anthropic: ANTHROPIC_KEY },
242
278
  });
243
279
 
244
280
  // 3. Runtime: read keys from secrets with the app key, persist memory + run traces.
245
- const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_URL });
281
+ const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
246
282
  const ai = init({ resolveKey: odlaDbKeyResolver(db) }); // keys come from odla-db secrets
247
283
 
248
284
  const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
@@ -279,11 +315,28 @@ const { ai, provider, model } = await initFromPlatform({
279
315
 
280
316
  The config comes from the anonymous `public-config` endpoint and is cached
281
317
  (~60s by default), so switching provider or model in Studio takes effect
282
- without a redeploy; the key resolves from the vault at call time, so rotation
283
- is just writing the secret again. **Never put provider keys in an app
318
+ without a redeploy. The configured provider is enforced: its facade catalog is
319
+ filtered to that provider, and a mismatched configured/default/call model fails
320
+ with `ConfigError`. If Studio has no default model, each call must name a model
321
+ for that provider. Tenant-bound facades are never shared globally. Provider
322
+ keys and SDK clients have finite 60-second in-isolate caches; rotation replaces
323
+ the previous credential generation rather than retaining it. Set
324
+ `keyResolverOptions: { ttlMs, cacheVersion }` to shorten or immediately
325
+ invalidate it after rotation. **Never put provider keys in an app
284
326
  Worker's wrangler vars/secrets** — the worker's only secret is its odla-db
285
327
  app key.
286
328
 
329
+ ### 0.3 migration notes
330
+
331
+ - Tool/extraction JSON Schemas now fail closed. `extract()` throws
332
+ `ToolInputError`; `runAgent()` records an error `tool_result` and does not run
333
+ a local handler when its arguments do not validate.
334
+ - Platform calls cannot cross the provider selected in Studio.
335
+ - `CancelledError` (`cancelled`) and `DeadlineExceededError`
336
+ (`deadline_exceeded`) distinguish caller limits from provider failures.
337
+ - Invalid run budgets now throw `ConfigError`; budget stops persist paired tool
338
+ results and may therefore add one final user/tool-result turn to memory.
339
+
287
340
  ## Models & capabilities
288
341
 
289
342
  The catalog maps a canonical model id → provider, native id, and capabilities.
@@ -307,7 +360,8 @@ models; extend or override it via `buildCatalog` / `init({ catalog })`.
307
360
 
308
361
  Every error is an `OdlaAIError` subclass carrying a stable `code` — branch on
309
362
  `err.code`, not message text: `ConfigError`, `AuthError`, `RateLimitError`,
310
- `CapabilityError`, `ContextWindowError`, `InvalidRequestError`, `ProviderError`.
363
+ `CapabilityError`, `ContextWindowError`, `InvalidRequestError`, `ToolInputError`,
364
+ `CancelledError`, `DeadlineExceededError`, `ProviderError`.
311
365
 
312
366
  ## Develop
313
367
 
@@ -1,9 +1,13 @@
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-QFT2DU2X.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";
@@ -103,7 +107,7 @@ function mapContent(blocks) {
103
107
  for (const b of blocks) {
104
108
  if (b.type === "text") out.push({ type: "text", text: b.text });
105
109
  else if (b.type === "tool_use") {
106
- 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) });
107
111
  } else if (b.type === "thinking") {
108
112
  out.push({ type: "thinking", thinking: b.thinking, signature: b.signature });
109
113
  }
@@ -182,6 +186,7 @@ var AnthropicProvider = class {
182
186
  this.client = client ?? new Anthropic({ apiKey });
183
187
  }
184
188
  async create(spec, req) {
189
+ const signal = signalWithDeadline(req.signal, req.deadline);
185
190
  try {
186
191
  let messages = toAnthropicMessages(req);
187
192
  const content = [];
@@ -190,7 +195,8 @@ var AnthropicProvider = class {
190
195
  let id = "";
191
196
  for (let i = 0; i < 8; i++) {
192
197
  const res = await this.client.messages.create(
193
- buildParams(spec, req, messages)
198
+ buildParams(spec, req, messages),
199
+ { signal }
194
200
  );
195
201
  id = res.id;
196
202
  addUsage(usage, mapUsage(res.usage));
@@ -204,24 +210,26 @@ var AnthropicProvider = class {
204
210
  }
205
211
  return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
206
212
  } catch (err) {
207
- mapProviderError(err, "anthropic");
213
+ mapProviderError(err, "anthropic", signal);
208
214
  }
209
215
  }
210
216
  async *stream(spec, req) {
217
+ const signal = signalWithDeadline(req.signal, req.deadline);
211
218
  try {
212
219
  const events = this.client.messages.stream(
213
- buildParams(spec, req, toAnthropicMessages(req))
220
+ buildParams(spec, req, toAnthropicMessages(req)),
221
+ { signal }
214
222
  );
215
223
  for await (const raw of events) {
216
224
  const ev = mapStreamEvent(raw, req);
217
225
  if (ev) yield ev;
218
226
  }
219
227
  } catch (err) {
220
- yield { type: "error", error: normalizeError(err, "anthropic").toShape() };
228
+ yield { type: "error", error: normalizeError(err, "anthropic", signal).toShape() };
221
229
  }
222
230
  }
223
231
  };
224
232
  export {
225
233
  AnthropicProvider
226
234
  };
227
- //# sourceMappingURL=anthropic-BIGCVQE4.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":"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","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":[]}
@@ -64,6 +64,23 @@ var InvalidRequestError = class extends OdlaAIError {
64
64
  super("invalid_request", message, opts);
65
65
  }
66
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
+ };
67
84
  var ProviderError = class extends OdlaAIError {
68
85
  constructor(message, opts) {
69
86
  super("provider_error", message, opts);
@@ -97,8 +114,10 @@ function messageOf(err) {
97
114
  if (typeof err === "string") return err;
98
115
  return "unknown provider error";
99
116
  }
100
- function normalizeError(err, provider) {
117
+ function normalizeError(err, provider, signal) {
101
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 });
102
121
  const status = statusOf(err);
103
122
  const message = messageOf(err);
104
123
  const tagged = `[${provider}] ${message}`;
@@ -114,8 +133,41 @@ function normalizeError(err, provider) {
114
133
  }
115
134
  return new ProviderError(tagged, { providerStatus: status, cause: err });
116
135
  }
117
- function mapProviderError(err, provider) {
118
- 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 });
119
171
  }
120
172
 
121
173
  export {
@@ -128,8 +180,13 @@ export {
128
180
  CapabilityError,
129
181
  ContextWindowError,
130
182
  InvalidRequestError,
183
+ ToolInputError,
184
+ CancelledError,
185
+ DeadlineExceededError,
131
186
  ProviderError,
132
187
  normalizeError,
133
- mapProviderError
188
+ mapProviderError,
189
+ signalWithDeadline,
190
+ throwIfAborted
134
191
  };
135
- //# sourceMappingURL=chunk-QFT2DU2X.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":[]}
@@ -1,12 +1,16 @@
1
1
  import {
2
2
  stringifyBlocks
3
3
  } from "./chunk-U7F6VJCV.js";
4
+ import {
5
+ toolInput
6
+ } from "./chunk-OX4TA4ZH.js";
4
7
  import {
5
8
  CapabilityError,
6
9
  emptyUsage,
7
10
  mapProviderError,
8
- normalizeError
9
- } from "./chunk-QFT2DU2X.js";
11
+ normalizeError,
12
+ signalWithDeadline
13
+ } from "./chunk-PXXCN2EU.js";
10
14
 
11
15
  // src/providers/google.ts
12
16
  import { GoogleGenAI } from "@google/genai";
@@ -99,7 +103,7 @@ function mapResponse(res, req) {
99
103
  else if (p.functionCall) {
100
104
  sawToolUse = true;
101
105
  const name = p.functionCall.name ?? "";
102
- content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: p.functionCall.args ?? {} });
106
+ content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: toolInput(p.functionCall.args ?? {}, "google", name) });
103
107
  }
104
108
  }
105
109
  return {
@@ -191,19 +195,25 @@ var GoogleProvider = class {
191
195
  this.client = client ?? new GoogleGenAI({ apiKey });
192
196
  }
193
197
  async create(spec, req) {
198
+ const signal = signalWithDeadline(req.signal, req.deadline);
194
199
  try {
195
- const res = await this.client.models.generateContent(buildParams(spec, req));
200
+ const params = buildParams(spec, req);
201
+ params.config = { ...params.config, abortSignal: signal };
202
+ const res = await this.client.models.generateContent(params);
196
203
  return mapResponse(res, req);
197
204
  } catch (err) {
198
- mapProviderError(err, "google");
205
+ mapProviderError(err, "google", signal);
199
206
  }
200
207
  }
201
208
  async *stream(spec, req) {
209
+ const signal = signalWithDeadline(req.signal, req.deadline);
202
210
  try {
203
- const iter = await this.client.models.generateContentStream(buildParams(spec, req));
211
+ const params = buildParams(spec, req);
212
+ params.config = { ...params.config, abortSignal: signal };
213
+ const iter = await this.client.models.generateContentStream(params);
204
214
  yield* mapStream(iter, req);
205
215
  } catch (err) {
206
- yield { type: "error", error: normalizeError(err, "google").toShape() };
216
+ yield { type: "error", error: normalizeError(err, "google", signal).toShape() };
207
217
  }
208
218
  }
209
219
  };
@@ -212,4 +222,4 @@ export {
212
222
  mapResponse,
213
223
  toContents
214
224
  };
215
- //# sourceMappingURL=google-QY5B4T5O.js.map
225
+ //# sourceMappingURL=google-3XV2VWVB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/google.ts","../src/providers/google/request.ts","../src/providers/google/response.ts","../src/providers/google/stream.ts"],"sourcesContent":["// Google (Gemini) adapter, on @google/genai's models.generateContent /\n// generateContentStream. Gemini is natively multimodal (text + image + audio +\n// PDF), so most translation is structural: canonical messages → Gemini\n// `contents` (role \"assistant\" → \"model\"), tools → functionDeclarations,\n// system → config.systemInstruction, tool_use/tool_result → functionCall/\n// functionResponse (name-keyed). The translations live in ./google/request.\n\nimport { GoogleGenAI } from \"@google/genai\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { mapProviderError, normalizeError } from \"./errors\";\nimport type { OracleEvent, OracleRequest, OracleResponse } from \"../shape/index\";\nimport { buildParams, toContents } from \"./google/request\";\nimport { mapResponse } from \"./google/response\";\nimport { mapStream } from \"./google/stream\";\nimport { signalWithDeadline } from \"../client/abort\";\n\nexport class GoogleProvider implements Provider {\n readonly id = \"google\" as const;\n private client: GoogleGenAI;\n\n constructor(apiKey: string, client?: GoogleGenAI) {\n this.client = client ?? new GoogleGenAI({ apiKey });\n }\n\n async create(spec: ModelSpec, req: OracleRequest): Promise<OracleResponse> {\n const signal = signalWithDeadline(req.signal, req.deadline);\n try {\n const params = buildParams(spec, req);\n params.config = { ...params.config, abortSignal: signal };\n const res = await this.client.models.generateContent(params);\n return mapResponse(res, req);\n } catch (err) {\n mapProviderError(err, \"google\", 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 params = buildParams(spec, req);\n params.config = { ...params.config, abortSignal: signal };\n const iter = await this.client.models.generateContentStream(params);\n yield* mapStream(iter, req);\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"google\", signal).toShape() };\n }\n }\n}\n\n// Re-exported for the test suite (test/adapters.test.ts) and callers that pinned\n// these helpers when they lived in this file.\nexport { toContents, mapResponse };\n","// Google (Gemini) request mapping: canonical shape → generateContent params.\nimport { FunctionCallingConfigMode } from \"@google/genai\";\nimport type { Content, GenerateContentParameters, Part } from \"@google/genai\";\nimport type { ModelSpec } from \"../../shape/capabilities\";\nimport { CapabilityError, type OracleContentBlock, type OracleRequest, type ToolChoice } from \"../../shape/index\";\nimport { stringifyBlocks } from \"../blocks\";\n\nexport function buildParams(spec: ModelSpec, req: OracleRequest): GenerateContentParameters {\n const config: Record<string, unknown> = { maxOutputTokens: req.maxTokens };\n\n if (req.system !== undefined) {\n config.systemInstruction = typeof req.system === \"string\" ? req.system : req.system.map((b) => b.text).join(\"\");\n }\n if (req.temperature !== undefined) config.temperature = req.temperature;\n if (req.stopSequences && req.stopSequences.length > 0) config.stopSequences = req.stopSequences;\n\n const tools = buildTools(req);\n if (tools) config.tools = tools;\n\n const toolConfig = mapToolChoice(req.toolChoice);\n if (toolConfig) config.toolConfig = toolConfig;\n\n if (req.thinking === \"adaptive\" && spec.capabilities.thinking) {\n config.thinkingConfig = { thinkingBudget: -1 }; // -1 = dynamic (\"let the model decide\")\n }\n\n if (req.responseFormat && spec.capabilities.structuredOutput) {\n config.responseMimeType = \"application/json\";\n config.responseSchema = req.responseFormat.schema;\n }\n\n if (req.providerExtras) Object.assign(config, req.providerExtras);\n\n return { model: spec.nativeId, contents: toContents(req), config } as unknown as GenerateContentParameters;\n}\n\nexport function toContents(req: OracleRequest): Content[] {\n return req.messages.map((m) => ({\n role: m.role === \"assistant\" ? \"model\" : \"user\",\n parts: (typeof m.content === \"string\" ? [{ type: \"text\" as const, text: m.content }] : m.content).map(toPart),\n }));\n}\n\nfunction toPart(b: OracleContentBlock): Part {\n switch (b.type) {\n case \"text\":\n return { text: b.text };\n case \"image\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google image input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };\n case \"audio\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google audio input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };\n case \"document\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google document input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: \"application/pdf\", data: b.source.data } };\n case \"tool_use\":\n return { functionCall: { name: b.name, args: b.input } };\n case \"tool_result\":\n return {\n functionResponse: {\n // Gemini correlates by name; the canonical tool_use id is keyed to it.\n name: b.toolUseId,\n response: { result: typeof b.content === \"string\" ? b.content : stringifyBlocks(b.content) },\n },\n };\n case \"thinking\":\n return { text: \"\" }; // no thinking input channel; drop content\n }\n}\n\nfunction buildTools(req: OracleRequest): unknown[] | undefined {\n const tools: unknown[] = [];\n if (req.tools && req.tools.length > 0) {\n tools.push({\n functionDeclarations: req.tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n })),\n });\n }\n if (req.webSearch) tools.push({ googleSearch: {} });\n return tools.length > 0 ? tools : undefined;\n}\n\nfunction mapToolChoice(tc: ToolChoice | undefined): unknown | undefined {\n if (tc === undefined || tc === \"auto\") return undefined;\n if (tc === \"none\") return { functionCallingConfig: { mode: FunctionCallingConfigMode.NONE } };\n if (tc === \"any\") return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY } };\n return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY, allowedFunctionNames: [tc.name] } };\n}\n","// Google (Gemini) response mapping. mapFinish / mapUsage are also used by the\n// stream mapper (kept here so the entry ↔ stream edge stays one-directional).\nimport type { GenerateContentResponse } from \"@google/genai\";\nimport type { OracleContentBlock, OracleRequest, OracleResponse, OracleStopReason, OracleUsage } from \"../../shape/index\";\nimport { toolInput } from \"../tool-input\";\n\nexport function mapResponse(res: GenerateContentResponse, req: OracleRequest): OracleResponse {\n const candidate = res.candidates?.[0];\n const parts = candidate?.content?.parts ?? [];\n const content: OracleContentBlock[] = [];\n let sawToolUse = false;\n\n for (const p of parts) {\n if (p.text) content.push({ type: \"text\", text: p.text });\n else if (p.functionCall) {\n sawToolUse = true;\n const name = p.functionCall.name ?? \"\";\n content.push({ type: \"tool_use\", id: p.functionCall.id ?? name, name, input: toolInput(p.functionCall.args ?? {}, \"google\", name) });\n }\n }\n\n return {\n id: res.responseId ?? \"\",\n model: req.model,\n provider: \"google\",\n role: \"assistant\",\n content,\n stopReason: sawToolUse ? \"tool_use\" : mapFinish(candidate?.finishReason),\n usage: mapUsage(res),\n };\n}\n\nexport function mapFinish(reason: string | null | undefined): OracleStopReason {\n switch (reason) {\n case \"STOP\":\n return \"end_turn\";\n case \"MAX_TOKENS\":\n return \"max_tokens\";\n case \"SAFETY\":\n case \"RECITATION\":\n case \"BLOCKLIST\":\n case \"PROHIBITED_CONTENT\":\n case \"SPII\":\n return \"refusal\";\n default:\n return \"end_turn\";\n }\n}\n\nexport function mapUsage(res: GenerateContentResponse): OracleUsage {\n const u = res.usageMetadata;\n const usage: OracleUsage = {\n inputTokens: u?.promptTokenCount ?? 0,\n outputTokens: u?.candidatesTokenCount ?? 0,\n };\n if (u?.cachedContentTokenCount != null) usage.cacheReadTokens = u.cachedContentTokenCount;\n return usage;\n}\n","// Google (Gemini) stream mapping onto the canonical event vocabulary.\nimport type { GenerateContentResponse } from \"@google/genai\";\nimport { emptyUsage, type OracleEvent, type OracleRequest, type OracleStopReason } from \"../../shape/index\";\nimport { mapFinish, mapUsage } from \"./response\";\n\nexport async function* mapStream(iter: AsyncIterable<GenerateContentResponse>, req: OracleRequest): AsyncIterable<OracleEvent> {\n let started = false;\n let textOpen = false;\n let nextBlockIndex = 1;\n let stopReason: OracleStopReason = \"end_turn\";\n const usage = emptyUsage();\n\n for await (const chunk of iter) {\n if (!started) {\n started = true;\n yield {\n type: \"message_start\",\n message: { id: chunk.responseId ?? \"\", model: req.model, provider: \"google\", role: \"assistant\", content: [], usage: emptyUsage() },\n };\n }\n\n const candidate = chunk.candidates?.[0];\n for (const p of candidate?.content?.parts ?? []) {\n if (p.text) {\n if (!textOpen) {\n textOpen = true;\n yield { type: \"content_block_start\", index: 0, contentBlock: { type: \"text\", text: \"\" } };\n }\n yield { type: \"content_block_delta\", index: 0, delta: { type: \"text_delta\", text: p.text } };\n } else if (p.functionCall) {\n const idx = nextBlockIndex++;\n const name = p.functionCall.name ?? \"\";\n yield { type: \"content_block_start\", index: idx, contentBlock: { type: \"tool_use\", id: p.functionCall.id ?? name, name, input: {} } };\n yield { type: \"content_block_delta\", index: idx, delta: { type: \"input_json_delta\", partialJson: JSON.stringify(p.functionCall.args ?? {}) } };\n yield { type: \"content_block_stop\", index: idx };\n stopReason = \"tool_use\";\n }\n }\n\n if (chunk.usageMetadata) {\n const u = mapUsage(chunk);\n usage.inputTokens = u.inputTokens; // Gemini reports cumulative counts\n usage.outputTokens = u.outputTokens;\n if (u.cacheReadTokens != null) usage.cacheReadTokens = u.cacheReadTokens;\n }\n if (candidate?.finishReason && stopReason !== \"tool_use\") stopReason = mapFinish(candidate.finishReason);\n }\n\n if (textOpen) yield { type: \"content_block_stop\", index: 0 };\n yield { type: \"message_delta\", delta: { stopReason }, usage };\n yield { type: \"message_stop\" };\n}\n"],"mappings":"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","names":[]}