@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 +21 -0
- package/README.md +68 -11
- package/dist/{anthropic-JXDXR7O7.js → anthropic-2NPMHJZT.js} +67 -51
- package/dist/anthropic-2NPMHJZT.js.map +1 -0
- package/dist/chunk-OX4TA4ZH.js +16 -0
- package/dist/chunk-OX4TA4ZH.js.map +1 -0
- package/dist/{chunk-LANGYP65.js → chunk-PXXCN2EU.js} +65 -46
- package/dist/chunk-PXXCN2EU.js.map +1 -0
- package/dist/chunk-U7F6VJCV.js +9 -0
- package/dist/chunk-U7F6VJCV.js.map +1 -0
- package/dist/{google-3TFOFIQO.js → google-3XV2VWVB.js} +50 -31
- package/dist/google-3XV2VWVB.js.map +1 -0
- package/dist/index.cjs +867 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +194 -74
- package/dist/index.d.ts +194 -74
- package/dist/index.js +578 -57
- package/dist/index.js.map +1 -1
- package/dist/{openai-XZRXMKCC.js → openai-NTQCF577.js} +54 -36
- package/dist/openai-NTQCF577.js.map +1 -0
- package/llms.txt +35 -12
- package/package.json +12 -5
- package/dist/anthropic-JXDXR7O7.js.map +0 -1
- package/dist/chunk-LANGYP65.js.map +0 -1
- package/dist/google-3TFOFIQO.js.map +0 -1
- package/dist/openai-XZRXMKCC.js.map +0 -1
|
@@ -1,40 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stringifyBlocks
|
|
3
|
+
} from "./chunk-U7F6VJCV.js";
|
|
4
|
+
import {
|
|
5
|
+
toolInput
|
|
6
|
+
} from "./chunk-OX4TA4ZH.js";
|
|
1
7
|
import {
|
|
2
8
|
CapabilityError,
|
|
9
|
+
ToolInputError,
|
|
3
10
|
addUsage,
|
|
4
11
|
emptyUsage,
|
|
5
12
|
mapProviderError,
|
|
6
|
-
normalizeError
|
|
7
|
-
|
|
13
|
+
normalizeError,
|
|
14
|
+
signalWithDeadline
|
|
15
|
+
} from "./chunk-PXXCN2EU.js";
|
|
8
16
|
|
|
9
17
|
// src/providers/openai.ts
|
|
10
18
|
import OpenAI from "openai";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
client;
|
|
14
|
-
constructor(apiKey, client) {
|
|
15
|
-
this.client = client ?? new OpenAI({ apiKey });
|
|
16
|
-
}
|
|
17
|
-
async create(spec, req) {
|
|
18
|
-
try {
|
|
19
|
-
const res = await this.client.chat.completions.create(
|
|
20
|
-
buildParams(spec, req, false)
|
|
21
|
-
);
|
|
22
|
-
return mapResponse(res, req);
|
|
23
|
-
} catch (err) {
|
|
24
|
-
mapProviderError(err, "openai");
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async *stream(spec, req) {
|
|
28
|
-
try {
|
|
29
|
-
const stream = await this.client.chat.completions.create(
|
|
30
|
-
buildParams(spec, req, true)
|
|
31
|
-
);
|
|
32
|
-
yield* mapStream(stream, req);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
yield { type: "error", error: normalizeError(err, "openai").toShape() };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
19
|
+
|
|
20
|
+
// src/providers/openai/request.ts
|
|
38
21
|
function buildParams(spec, req, stream) {
|
|
39
22
|
const params = {
|
|
40
23
|
model: spec.nativeId,
|
|
@@ -131,9 +114,6 @@ function audioFormat(m) {
|
|
|
131
114
|
if (m === "audio/mp3" || m === "audio/mpeg") return "mp3";
|
|
132
115
|
throw new CapabilityError(`OpenAI audio input supports wav and mp3, not "${m}".`);
|
|
133
116
|
}
|
|
134
|
-
function stringifyBlocks(blocks) {
|
|
135
|
-
return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
|
|
136
|
-
}
|
|
137
117
|
function buildTools(req) {
|
|
138
118
|
if (!req.tools || req.tools.length === 0) return void 0;
|
|
139
119
|
return req.tools.map((t) => ({
|
|
@@ -153,6 +133,8 @@ function reasoningEffort(e) {
|
|
|
153
133
|
if (e === "medium") return "medium";
|
|
154
134
|
return "high";
|
|
155
135
|
}
|
|
136
|
+
|
|
137
|
+
// src/providers/openai/response.ts
|
|
156
138
|
function mapResponse(res, req) {
|
|
157
139
|
const choice = res.choices[0];
|
|
158
140
|
const content = [];
|
|
@@ -176,9 +158,10 @@ function parseArgs(raw) {
|
|
|
176
158
|
if (!raw) return {};
|
|
177
159
|
try {
|
|
178
160
|
const parsed = JSON.parse(raw);
|
|
179
|
-
return parsed
|
|
180
|
-
} catch {
|
|
181
|
-
|
|
161
|
+
return toolInput(parsed, "openai", "function");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof ToolInputError) throw error;
|
|
164
|
+
throw new ToolInputError("[openai] tool returned malformed JSON arguments.", { cause: error });
|
|
182
165
|
}
|
|
183
166
|
}
|
|
184
167
|
function mapFinish(reason) {
|
|
@@ -202,6 +185,8 @@ function mapUsage(u) {
|
|
|
202
185
|
if (cached != null) usage.cacheReadTokens = cached;
|
|
203
186
|
return usage;
|
|
204
187
|
}
|
|
188
|
+
|
|
189
|
+
// src/providers/openai/stream.ts
|
|
205
190
|
async function* mapStream(stream, req) {
|
|
206
191
|
let started = false;
|
|
207
192
|
let textOpen = false;
|
|
@@ -249,10 +234,43 @@ async function* mapStream(stream, req) {
|
|
|
249
234
|
yield { type: "message_delta", delta: { stopReason }, usage };
|
|
250
235
|
yield { type: "message_stop" };
|
|
251
236
|
}
|
|
237
|
+
|
|
238
|
+
// src/providers/openai.ts
|
|
239
|
+
var OpenAIProvider = class {
|
|
240
|
+
id = "openai";
|
|
241
|
+
client;
|
|
242
|
+
constructor(apiKey, client) {
|
|
243
|
+
this.client = client ?? new OpenAI({ apiKey });
|
|
244
|
+
}
|
|
245
|
+
async create(spec, req) {
|
|
246
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
247
|
+
try {
|
|
248
|
+
const res = await this.client.chat.completions.create(
|
|
249
|
+
buildParams(spec, req, false),
|
|
250
|
+
{ signal }
|
|
251
|
+
);
|
|
252
|
+
return mapResponse(res, req);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
mapProviderError(err, "openai", signal);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async *stream(spec, req) {
|
|
258
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
259
|
+
try {
|
|
260
|
+
const stream = await this.client.chat.completions.create(
|
|
261
|
+
buildParams(spec, req, true),
|
|
262
|
+
{ signal }
|
|
263
|
+
);
|
|
264
|
+
yield* mapStream(stream, req);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
yield { type: "error", error: normalizeError(err, "openai", signal).toShape() };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
252
270
|
export {
|
|
253
271
|
OpenAIProvider,
|
|
254
272
|
mapResponse,
|
|
255
273
|
parseArgs,
|
|
256
274
|
toOpenAIMessages
|
|
257
275
|
};
|
|
258
|
-
//# sourceMappingURL=openai-
|
|
276
|
+
//# sourceMappingURL=openai-NTQCF577.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/openai.ts","../src/providers/openai/request.ts","../src/providers/openai/response.ts","../src/providers/openai/stream.ts"],"sourcesContent":["// OpenAI adapter, on the Chat Completions API (stable across the current model\n// lineup, multimodal, tool calls, streaming). The interesting translations —\n// system folded into a leading message, JSON-string tool `arguments` parsed to an\n// object, and tool_result blocks re-emitted as role:\"tool\" messages — happen in\n// ./openai/request so the rest of odla-ai only ever sees the canonical shape.\n\nimport OpenAI from \"openai\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { normalizeError, mapProviderError } from \"./errors\";\nimport type { OracleEvent, OracleRequest, OracleResponse } from \"../shape/index\";\nimport { buildParams, toOpenAIMessages } from \"./openai/request\";\nimport { mapResponse, parseArgs } from \"./openai/response\";\nimport { mapStream } from \"./openai/stream\";\nimport { signalWithDeadline } from \"../client/abort\";\n\ntype ChatParams = OpenAI.Chat.Completions.ChatCompletionCreateParams;\n\nexport class OpenAIProvider implements Provider {\n readonly id = \"openai\" as const;\n private client: OpenAI;\n\n constructor(apiKey: string, client?: OpenAI) {\n this.client = client ?? new OpenAI({ apiKey });\n }\n\n async create(spec: ModelSpec, req: OracleRequest): Promise<OracleResponse> {\n const signal = signalWithDeadline(req.signal, req.deadline);\n try {\n const res = (await this.client.chat.completions.create(\n buildParams(spec, req, false) as unknown as ChatParams,\n { signal },\n )) as OpenAI.Chat.Completions.ChatCompletion;\n return mapResponse(res, req);\n } catch (err) {\n mapProviderError(err, \"openai\", 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 stream = (await this.client.chat.completions.create(\n buildParams(spec, req, true) as unknown as ChatParams,\n { signal },\n )) as unknown as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>;\n yield* mapStream(stream, req);\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"openai\", 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 { toOpenAIMessages, mapResponse, parseArgs };\n","// OpenAI request mapping: canonical shape → Chat Completions params.\nimport type OpenAI from \"openai\";\nimport type { ModelSpec } from \"../../shape/capabilities\";\nimport {\n CapabilityError,\n type AudioMediaType,\n type Effort,\n type OracleContentBlock,\n type OracleRequest,\n type ToolChoice,\n} from \"../../shape/index\";\nimport { stringifyBlocks } from \"../blocks\";\n\ntype ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam;\n\nexport function buildParams(spec: ModelSpec, req: OracleRequest, stream: boolean): Record<string, unknown> {\n const params: Record<string, unknown> = {\n model: spec.nativeId,\n messages: toOpenAIMessages(req),\n max_completion_tokens: req.maxTokens,\n };\n\n const tools = buildTools(req);\n if (tools) params.tools = tools;\n\n const toolChoice = mapToolChoice(req.toolChoice);\n if (toolChoice !== undefined) params.tool_choice = toolChoice;\n\n if (req.stopSequences && req.stopSequences.length > 0) params.stop = req.stopSequences;\n\n // Reasoning (effort) models reject a custom temperature; only send it elsewhere.\n if (req.temperature !== undefined && !spec.capabilities.effort) params.temperature = req.temperature;\n\n if (req.effort && spec.capabilities.effort) params.reasoning_effort = reasoningEffort(req.effort);\n\n if (req.responseFormat && spec.capabilities.structuredOutput) {\n params.response_format = {\n type: \"json_schema\",\n json_schema: { name: req.responseFormat.name ?? \"response\", schema: req.responseFormat.schema },\n };\n }\n\n // Search-capable chat models (e.g. gpt-5-search-api) take web_search_options;\n // the capability gate keeps this off models without webSearch.\n if (req.webSearch) params.web_search_options = { search_context_size: \"medium\" };\n\n if (stream) {\n params.stream = true;\n params.stream_options = { include_usage: true };\n }\n\n if (req.providerExtras) Object.assign(params, req.providerExtras);\n return params;\n}\n\n/** Expand canonical messages into OpenAI's flat message list: system folded to a\n * leading message, tool_result blocks lifted to role:\"tool\" messages, tool_use\n * blocks emitted as assistant tool_calls. */\nexport function toOpenAIMessages(req: OracleRequest): ChatMessage[] {\n const out: ChatMessage[] = [];\n\n if (req.system !== undefined) {\n const text = typeof req.system === \"string\" ? req.system : req.system.map((b) => b.text).join(\"\");\n out.push({ role: \"system\", content: text });\n }\n\n for (const msg of req.messages) {\n const blocks = typeof msg.content === \"string\" ? [{ type: \"text\" as const, text: msg.content }] : msg.content;\n\n if (msg.role === \"assistant\") {\n const textParts: string[] = [];\n const toolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] = [];\n for (const b of blocks) {\n if (b.type === \"text\") textParts.push(b.text);\n else if (b.type === \"tool_use\") {\n toolCalls.push({\n id: b.id,\n type: \"function\",\n function: { name: b.name, arguments: JSON.stringify(b.input) },\n } as OpenAI.Chat.Completions.ChatCompletionMessageToolCall);\n }\n // thinking blocks are dropped — OpenAI has no thinking input channel.\n }\n const assistant: Record<string, unknown> = { role: \"assistant\", content: textParts.join(\"\") || null };\n if (toolCalls.length > 0) assistant.tool_calls = toolCalls;\n out.push(assistant as unknown as ChatMessage);\n continue;\n }\n\n // user turn: tool_result blocks become tool messages; the rest becomes one\n // user message with multimodal content parts.\n const toolResults = blocks.filter((b) => b.type === \"tool_result\");\n for (const b of toolResults) {\n if (b.type !== \"tool_result\") continue;\n out.push({\n role: \"tool\",\n tool_call_id: b.toolUseId,\n content: typeof b.content === \"string\" ? b.content : stringifyBlocks(b.content),\n });\n }\n const rest = blocks.filter((b) => b.type !== \"tool_result\");\n if (rest.length > 0) out.push({ role: \"user\", content: rest.map(userPart) });\n }\n\n return out;\n}\n\ntype UserPart = OpenAI.Chat.Completions.ChatCompletionContentPart;\n\nfunction userPart(b: OracleContentBlock): UserPart {\n switch (b.type) {\n case \"text\":\n return { type: \"text\", text: b.text };\n case \"image\":\n return {\n type: \"image_url\",\n image_url: {\n url: b.source.type === \"url\" ? b.source.url : `data:${b.source.mediaType};base64,${b.source.data}`,\n },\n };\n case \"audio\": {\n if (b.source.type !== \"base64\") {\n throw new CapabilityError(\"OpenAI audio input requires base64 data, not a URL.\");\n }\n return {\n type: \"input_audio\",\n input_audio: { data: b.source.data, format: audioFormat(b.source.mediaType) },\n } as UserPart;\n }\n default:\n // document/tool_* are excluded from OpenAI user content (documents are\n // gated off by capabilities; tool_result handled separately).\n return { type: \"text\", text: stringifyBlocks([b]) };\n }\n}\n\nfunction audioFormat(m: AudioMediaType): \"wav\" | \"mp3\" {\n if (m === \"audio/wav\") return \"wav\";\n if (m === \"audio/mp3\" || m === \"audio/mpeg\") return \"mp3\";\n throw new CapabilityError(`OpenAI audio input supports wav and mp3, not \"${m}\".`);\n}\n\nfunction buildTools(req: OracleRequest): unknown[] | undefined {\n if (!req.tools || req.tools.length === 0) return undefined;\n return req.tools.map((t) => ({\n type: \"function\",\n function: { name: t.name, description: t.description, parameters: t.inputSchema },\n }));\n}\n\nfunction mapToolChoice(tc: ToolChoice | undefined): unknown | undefined {\n if (tc === undefined) return undefined;\n if (tc === \"auto\") return \"auto\";\n if (tc === \"none\") return \"none\";\n if (tc === \"any\") return \"required\";\n return { type: \"function\", function: { name: tc.name } };\n}\n\nfunction reasoningEffort(e: Effort): \"low\" | \"medium\" | \"high\" {\n if (e === \"low\") return \"low\";\n if (e === \"medium\") return \"medium\";\n return \"high\"; // high / xhigh / max → high\n}\n","// OpenAI response mapping: Chat Completion → canonical response. mapFinish /\n// mapUsage are also consumed by the stream mapper (kept here, not in the entry,\n// so the entry ↔ stream dependency stays one-directional — no import cycle).\nimport type OpenAI from \"openai\";\nimport {\n type OracleContentBlock,\n type OracleRequest,\n type OracleResponse,\n type OracleStopReason,\n type OracleUsage,\n} from \"../../shape/index\";\nimport { ToolInputError } from \"../../shape/index\";\nimport { toolInput } from \"../tool-input\";\n\nexport function mapResponse(res: OpenAI.Chat.Completions.ChatCompletion, req: OracleRequest): OracleResponse {\n const choice = res.choices[0];\n const content: OracleContentBlock[] = [];\n const message = choice?.message;\n\n if (message?.content) content.push({ type: \"text\", text: message.content });\n for (const call of message?.tool_calls ?? []) {\n if (call.type !== \"function\") continue;\n content.push({ type: \"tool_use\", id: call.id, name: call.function.name, input: parseArgs(call.function.arguments) });\n }\n\n return {\n id: res.id,\n model: req.model,\n provider: \"openai\",\n role: \"assistant\",\n content,\n stopReason: mapFinish(choice?.finish_reason),\n usage: mapUsage(res.usage),\n };\n}\n\n/** OpenAI hands tool arguments back as a JSON *string*; malformed JSON fails\n * closed so a tool handler never receives invented empty arguments. */\nexport function parseArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return toolInput(parsed, \"openai\", \"function\");\n } catch (error) {\n if (error instanceof ToolInputError) throw error;\n throw new ToolInputError(\"[openai] tool returned malformed JSON arguments.\", { cause: error });\n }\n}\n\nexport function mapFinish(reason: string | null | undefined): OracleStopReason {\n switch (reason) {\n case \"stop\":\n return \"end_turn\";\n case \"length\":\n return \"max_tokens\";\n case \"tool_calls\":\n case \"function_call\":\n return \"tool_use\";\n case \"content_filter\":\n return \"refusal\";\n default:\n return \"end_turn\";\n }\n}\n\nexport function mapUsage(u: OpenAI.Completions.CompletionUsage | undefined): OracleUsage {\n const usage: OracleUsage = { inputTokens: u?.prompt_tokens ?? 0, outputTokens: u?.completion_tokens ?? 0 };\n const cached = u?.prompt_tokens_details?.cached_tokens;\n if (cached != null) usage.cacheReadTokens = cached;\n return usage;\n}\n","// OpenAI stream mapping onto the canonical event vocabulary.\nimport type OpenAI from \"openai\";\nimport { addUsage, emptyUsage, type OracleEvent, type OracleRequest, type OracleStopReason } from \"../../shape/index\";\nimport { mapFinish, mapUsage } from \"./response\";\n\n/** Map an OpenAI chat stream onto the canonical event vocabulary. OpenAI has no\n * explicit block start/stop, so we synthesize them: a text channel (index 0) and\n * one channel per tool_call index, opened lazily and closed at finish. */\nexport async function* mapStream(\n stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>,\n req: OracleRequest,\n): AsyncIterable<OracleEvent> {\n let started = false;\n let textOpen = false;\n const toolIndex = new Map<number, number>(); // openai tool_call index → our block index\n let nextBlockIndex = 1; // 0 reserved for text\n let stopReason: OracleStopReason = \"end_turn\";\n const usage = emptyUsage();\n\n for await (const chunk of stream) {\n if (!started) {\n started = true;\n yield {\n type: \"message_start\",\n message: { id: chunk.id, model: req.model, provider: \"openai\", role: \"assistant\", content: [], usage: emptyUsage() },\n };\n }\n\n if (chunk.usage) addUsage(usage, mapUsage(chunk.usage));\n\n const choice = chunk.choices[0];\n const delta = choice?.delta;\n\n if (delta?.content) {\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: delta.content } };\n }\n\n for (const tc of delta?.tool_calls ?? []) {\n let blockIndex = toolIndex.get(tc.index);\n if (blockIndex === undefined) {\n blockIndex = nextBlockIndex++;\n toolIndex.set(tc.index, blockIndex);\n yield {\n type: \"content_block_start\",\n index: blockIndex,\n contentBlock: { type: \"tool_use\", id: tc.id ?? \"\", name: tc.function?.name ?? \"\", input: {} },\n };\n }\n if (tc.function?.arguments) {\n yield { type: \"content_block_delta\", index: blockIndex, delta: { type: \"input_json_delta\", partialJson: tc.function.arguments } };\n }\n }\n\n if (choice?.finish_reason) stopReason = mapFinish(choice.finish_reason);\n }\n\n if (textOpen) yield { type: \"content_block_stop\", index: 0 };\n for (const blockIndex of toolIndex.values()) yield { type: \"content_block_stop\", index: blockIndex };\n yield { type: \"message_delta\", delta: { stopReason }, usage };\n yield { type: \"message_stop\" };\n}\n"],"mappings":"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","names":[]}
|
package/llms.txt
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# @odla-ai/ai — LLM context
|
|
2
2
|
|
|
3
|
+
> EARLY ACCESS — pre-1.0. Agents work from bounded runbooks; humans approve
|
|
4
|
+
> credentials, production changes, releases, and merges. APIs and exact package
|
|
5
|
+
> availability can change. Review documented guarantees and limitations; this
|
|
6
|
+
> software is MIT-licensed and provided without warranty.
|
|
7
|
+
|
|
3
8
|
One general-purpose interface for AI inference across Anthropic (Claude), OpenAI
|
|
4
9
|
(GPT), and Google (Gemini) — text, image, and audio — plus a tool-use agent
|
|
5
10
|
engine and an eval harness. Library-only: import in-process, bring your own keys.
|
|
6
|
-
|
|
11
|
+
Targets Node 20+ and Cloudflare Workers. Provider adapters are runtime-lazy, but
|
|
12
|
+
all provider SDKs are package dependencies. Never expose long-lived keys in a browser.
|
|
7
13
|
|
|
8
14
|
## Install
|
|
9
15
|
|
|
10
16
|
npm i @odla-ai/ai
|
|
11
|
-
# provider SDKs are peer-lazy-loaded; install those you use:
|
|
12
|
-
# @anthropic-ai/sdk openai @google/genai
|
|
13
17
|
# optional, for odla-db-backed storage + secrets:
|
|
14
18
|
# @odla-ai/db
|
|
15
19
|
|
|
@@ -23,7 +27,8 @@ Isomorphic (Node 20+, Cloudflare Workers, browser). Provider SDKs are lazy-loade
|
|
|
23
27
|
- `ai.stream(input)` → async iterable of normalized events: message_start →
|
|
24
28
|
content_block_start → content_block_delta {text_delta|thinking_delta|input_json_delta}
|
|
25
29
|
→ content_block_stop → message_delta → message_stop (plus `error`).
|
|
26
|
-
- `ai.extract<T>({ model, user, tool })` → forced tool call; returns
|
|
30
|
+
- `ai.extract<T>({ model, user, tool })` → forced tool call; returns an object
|
|
31
|
+
validated against the declared JSON Schema. Unsupported assertions fail closed.
|
|
27
32
|
- `ai.search({ model, user })` → web-search-grounded prose.
|
|
28
33
|
|
|
29
34
|
## Content blocks (multimodal)
|
|
@@ -42,7 +47,15 @@ audio block to a Claude model throws CapabilityError before any network call.
|
|
|
42
47
|
// run.finalText, run.toolCalls, run.usage, run.steps, run.stoppedReason
|
|
43
48
|
|
|
44
49
|
Tools may carry taint labels (outputTaint / acceptsTaint) for a CaMeL-style
|
|
45
|
-
prompt-injection gate. Personas may carry a MemoryScope.
|
|
50
|
+
prompt-injection gate. Personas may carry a MemoryScope. Tool inputs are schema-
|
|
51
|
+
validated before handlers run. Pass signal/deadline and a run budget
|
|
52
|
+
({maxInputTokens,maxOutputTokens,maxTotalTokens,maxToolCalls}) to bound follow-up
|
|
53
|
+
effects and requested output. Input and total limits are post-turn ceilings:
|
|
54
|
+
provider usage is unavailable before the first response, so they cannot
|
|
55
|
+
tokenizer-bound the first prompt. Output and remaining-total limits cap the next
|
|
56
|
+
request's maxTokens. Token limits must be positive integers; maxToolCalls may be
|
|
57
|
+
zero. Budget exits pair every requested tool_use with an error tool_result before
|
|
58
|
+
memory is persisted.
|
|
46
59
|
|
|
47
60
|
## Skills
|
|
48
61
|
|
|
@@ -69,14 +82,17 @@ device-authorization handshake for a scoped, revocable token, then provisions.
|
|
|
69
82
|
import { requestToken, init as odlaInit } from "@odla-ai/db";
|
|
70
83
|
import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, init } from "@odla-ai/ai";
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
const
|
|
85
|
+
const ODLA_PLATFORM = "https://odla.ai";
|
|
86
|
+
const ODLA_DB_URL = "https://db.odla.ai";
|
|
87
|
+
|
|
88
|
+
// 1. Handshake: the human approves a short code at odla.ai.
|
|
89
|
+
const { token } = await requestToken({ endpoint: ODLA_PLATFORM, onCode: ({ userCode }) => show(userCode) });
|
|
74
90
|
|
|
75
91
|
// 2. Provision: create app, mint app key, push agent schema, store BYO provider keys as secrets.
|
|
76
|
-
const { appId, appKey } = await provisionAgentApp({ endpoint:
|
|
92
|
+
const { appId, appKey } = await provisionAgentApp({ endpoint: ODLA_PLATFORM, token, providerKeys: { openai: OPENAI_KEY } });
|
|
77
93
|
|
|
78
94
|
// 3. Runtime: read keys from odla-db secrets; persist memory + run traces.
|
|
79
|
-
const db = odlaInit({ appId, adminToken: appKey, endpoint:
|
|
95
|
+
const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
|
|
80
96
|
const ai = init({ resolveKey: odlaDbKeyResolver(db) });
|
|
81
97
|
const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
|
|
82
98
|
const run = await runAgent(ai, persona, { input: "hello" });
|
|
@@ -102,16 +118,22 @@ env's odla-db tenant secrets, write-only for operators). One call reads both:
|
|
|
102
118
|
platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
|
|
103
119
|
// ai.chat / ai.stream / ai.extract — provider/model from public-config
|
|
104
120
|
// (cached ~60s, so Studio changes apply without redeploys); the key is
|
|
105
|
-
// fetched from the vault
|
|
121
|
+
// fetched from the vault via a finite-TTL odlaDbKeyResolver.
|
|
106
122
|
|
|
107
123
|
The only secret the Worker carries is its odla-db app key. Rotating the LLM
|
|
108
|
-
key = writing the secret again;
|
|
124
|
+
key = writing the secret again; the default warm-isolate key TTL is 60 seconds
|
|
125
|
+
and cacheVersion provides immediate invalidation. Provider SDK clients are
|
|
126
|
+
fingerprinted, bounded to one generation per provider, and expire after 60
|
|
127
|
+
seconds. The platform provider is enforced: models from other providers are
|
|
128
|
+
absent from the facade catalog. If no default model is configured, each call
|
|
129
|
+
must name a model for that provider. Only public config is shared; tenant-bound
|
|
130
|
+
Ai facades are not cached globally.
|
|
109
131
|
|
|
110
132
|
## Errors
|
|
111
133
|
|
|
112
134
|
Every error is an OdlaAIError subclass with a stable `code` — branch on err.code:
|
|
113
135
|
config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
|
|
114
|
-
tool_input_invalid, provider_error.
|
|
136
|
+
tool_input_invalid, cancelled, deadline_exceeded, provider_error.
|
|
115
137
|
|
|
116
138
|
## Models
|
|
117
139
|
|
|
@@ -120,6 +142,7 @@ tool_input_invalid, provider_error.
|
|
|
120
142
|
- `claude-fable-5` (anthropic) — text, image, tools, thinking, effort, web-search, structured
|
|
121
143
|
- `claude-haiku-4-5` (anthropic) — text, image, tools, structured
|
|
122
144
|
- `gpt-5.5` (openai) — text, image, tools, effort, structured
|
|
145
|
+
- `gpt-5.4-mini` (openai) — text, image, tools, effort, structured
|
|
123
146
|
- `gpt-5` (openai) — text, image, tools, effort, structured
|
|
124
147
|
- `gpt-5-mini` (openai) — text, image, tools, effort, structured
|
|
125
148
|
- `gpt-5-nano` (openai) — text, image, tools, effort, structured
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odla-ai/ai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One general-purpose interface for AI inference across Anthropic (Claude), OpenAI, and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent engine and eval harness
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "One general-purpose interface for AI inference across Anthropic (Claude), OpenAI, and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent engine and eval harness for Node 20+ and Cloudflare Workers.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"homepage": "https://odla.ai/docs/packages/ai",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cory/odla-ai.git",
|
|
10
|
+
"directory": "packages/ai"
|
|
11
|
+
},
|
|
6
12
|
"type": "module",
|
|
7
13
|
"main": "./dist/index.cjs",
|
|
8
14
|
"module": "./dist/index.js",
|
|
@@ -24,7 +30,8 @@
|
|
|
24
30
|
"node": ">=20"
|
|
25
31
|
},
|
|
26
32
|
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
33
|
+
"access": "public",
|
|
34
|
+
"provenance": false
|
|
28
35
|
},
|
|
29
36
|
"keywords": [
|
|
30
37
|
"odla",
|
|
@@ -50,7 +57,7 @@
|
|
|
50
57
|
"smoke": "tsx scripts/smoke.ts",
|
|
51
58
|
"gen:llms": "tsx -e \"import('./src/index.ts').then(m => process.stdout.write(m.generateLlmsTxt()))\" > llms.txt",
|
|
52
59
|
"check:publish": "tsx scripts/check-publish-safety.ts",
|
|
53
|
-
"prepublishOnly": "npm run build && npm run check:publish"
|
|
60
|
+
"prepublishOnly": "npm run build && npm run gen:llms && npm run check:publish"
|
|
54
61
|
},
|
|
55
62
|
"dependencies": {
|
|
56
63
|
"@anthropic-ai/sdk": "^0.70.0",
|
|
@@ -58,7 +65,7 @@
|
|
|
58
65
|
"openai": "^6.0.0"
|
|
59
66
|
},
|
|
60
67
|
"peerDependencies": {
|
|
61
|
-
"@odla-ai/db": ">=0.2.0 <0.
|
|
68
|
+
"@odla-ai/db": ">=0.2.0 <0.7.0"
|
|
62
69
|
},
|
|
63
70
|
"peerDependenciesMeta": {
|
|
64
71
|
"@odla-ai/db": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/anthropic.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 and cast at the call site (the wire accepts them),\n// 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 {\n addUsage,\n emptyUsage,\n type OracleContentBlock,\n type OracleEvent,\n type OracleRequest,\n type OracleResponse,\n type OracleStopReason,\n type OracleUsage,\n type TextBlock,\n type ToolChoice,\n} from \"../shape/index\";\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 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 );\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\");\n }\n }\n\n async *stream(spec: ModelSpec, req: OracleRequest): AsyncIterable<OracleEvent> {\n try {\n const events = this.client.messages.stream(\n buildParams(spec, req, toAnthropicMessages(req)) as unknown as Anthropic.MessageStreamParams,\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\").toShape() };\n }\n }\n}\n\n// ----- request mapping -----\n\nfunction 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\nfunction 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\n// ----- response mapping -----\n\nfunction 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: (b.input ?? {}) as Record<string, unknown> });\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\nfunction 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\nfunction 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\n// ----- stream mapping -----\n\nfunction 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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shape/index.ts","../src/providers/errors.ts"],"sourcesContent":["// The canonical request / response / event types for talking to any LLM\n// provider through odla-ai. One shape regardless of which vendor answers.\n//\n// Modeled after Anthropic's content-block shape because: (a) odla is\n// Claude-native in spirit, (b) Anthropic's tool-use is cleaner (object `input`\n// instead of a JSON-string `arguments`, structured content blocks, tool_result\n// as a content block), and (c) translating OpenAI/Google → Anthropic loses less\n// fidelity than the reverse. Forked and generalized from odla-ai-old's\n// `@odla-ai/oracle-shape`: this version adds Google as a first-class provider,\n// adds audio (and PDF document) input, replaces two-provider hardcoding with an\n// open provider union, and generalizes reasoning controls.\n//\n// This module has ZERO runtime dependencies. It is the load-bearing contract the\n// adapters, the client facade, the agent loop, and the eval harness all speak;\n// provider SDK types never leak past the adapters.\n\n// ----- Providers -----\n\n/** Every provider odla-ai can route to. Apps should not branch on this for\n * portability, but it is carried on responses/events for observability. */\nexport type ProviderId = \"anthropic\" | \"openai\" | \"google\";\n\n// ----- Content blocks -----\n\nexport interface TextBlock {\n type: \"text\";\n text: string;\n /** Anthropic prompt-caching marker. Other providers ignore it. */\n cacheControl?: { type: \"ephemeral\" };\n}\n\nexport type ImageMediaType = \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\nexport type ImageSource =\n | { type: \"base64\"; mediaType: ImageMediaType; data: string }\n | { type: \"url\"; url: string };\n\nexport interface ImageBlock {\n type: \"image\";\n source: ImageSource;\n}\n\nexport type AudioMediaType =\n | \"audio/wav\"\n | \"audio/mp3\"\n | \"audio/mpeg\"\n | \"audio/ogg\"\n | \"audio/flac\"\n | \"audio/aac\"\n | \"audio/webm\";\n\nexport type AudioSource =\n | { type: \"base64\"; mediaType: AudioMediaType; data: string }\n | { type: \"url\"; url: string };\n\n/** Audio input. Supported by OpenAI (audio models) and Google (Gemini); NOT by\n * Anthropic — a request carrying an AudioBlock to a Claude model is rejected up\n * front by capability validation, never sent. */\nexport interface AudioBlock {\n type: \"audio\";\n source: AudioSource;\n}\n\nexport type DocumentSource =\n | { type: \"base64\"; mediaType: \"application/pdf\"; data: string }\n | { type: \"url\"; url: string };\n\n/** A document (PDF) input. Supported by Anthropic and Google. */\nexport interface DocumentBlock {\n type: \"document\";\n source: DocumentSource;\n}\n\nexport interface ToolUseBlock {\n type: \"tool_use\";\n /** Correlates a `tool_result` with this call. Translated from OpenAI's\n * `tool_calls[].id` by the openai adapter. */\n id: string;\n name: string;\n /** Already-parsed object. OpenAI's JSON-string `arguments` is parsed before it\n * reaches this shape — callers never JSON.parse a tool's arguments. */\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n type: \"tool_result\";\n toolUseId: string;\n /** String for plain-text results (most tools); a content-block array for tools\n * that return structured / multi-modal content. */\n content: string | OracleContentBlock[];\n isError?: boolean;\n}\n\nexport interface ThinkingBlock {\n type: \"thinking\";\n /** Extended-thinking text (Anthropic). Empty when `display` is omitted. */\n thinking: string;\n signature?: string;\n}\n\nexport type OracleContentBlock =\n | TextBlock\n | ImageBlock\n | AudioBlock\n | DocumentBlock\n | ToolUseBlock\n | ToolResultBlock\n | ThinkingBlock;\n\n// ----- Messages -----\n\nexport type OracleRole = \"user\" | \"assistant\";\n\nexport interface OracleMessage {\n role: OracleRole;\n /** A plain string (shorthand for a single TextBlock) or an explicit array for\n * multi-modal / tool turns. */\n content: string | OracleContentBlock[];\n}\n\n// ----- Tools -----\n\nexport interface OracleTool {\n name: string;\n description: string;\n /** JSON Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** How the model may use tools this turn. `{ type: \"tool\", name }` forces one\n * specific tool — the mechanism behind `extract<T>()`. */\nexport type ToolChoice = \"auto\" | \"any\" | \"none\" | { type: \"tool\"; name: string };\n\n// ----- Reasoning / output controls -----\n\n/** Whether the model reasons before answering. Maps to Anthropic adaptive\n * thinking; emulated or ignored on providers without a native equivalent. */\nexport type ThinkingMode = \"off\" | \"adaptive\";\n\n/** Reasoning/spend depth. Maps to Anthropic `output_config.effort`; the openai\n * and google adapters map it to their nearest reasoning-effort knob. */\nexport type Effort = \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\n/** Constrain the response to valid JSON matching a schema (structured output). */\nexport interface ResponseFormat {\n type: \"json_schema\";\n name?: string;\n schema: Record<string, unknown>;\n}\n\n// ----- Request -----\n\nexport interface OracleRequest {\n /** Canonical model id from the catalog; resolved to a provider + native id. */\n model: string;\n /** System prompt. Top-level for Anthropic/Google; the openai adapter folds it\n * into a leading system message. */\n system?: string | TextBlock[];\n messages: OracleMessage[];\n tools?: OracleTool[];\n toolChoice?: ToolChoice;\n maxTokens: number;\n temperature?: number;\n stopSequences?: string[];\n /** Reasoning mode. Only applied on models whose capabilities allow it. */\n thinking?: ThinkingMode;\n /** Reasoning/spend depth. Only applied on models whose capabilities allow it. */\n effort?: Effort;\n responseFormat?: ResponseFormat;\n /** Enable the provider's server-side web-search tool for this request. */\n webSearch?: boolean;\n /** Provider-specific escape hatch, passed through opaquely. Using it gives up\n * portability across providers. */\n providerExtras?: Record<string, unknown>;\n}\n\n// ----- Usage -----\n\nexport interface OracleUsage {\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n}\n\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\n// ----- Response (non-streaming) -----\n\nexport type OracleStopReason =\n | \"end_turn\"\n | \"max_tokens\"\n | \"stop_sequence\"\n | \"tool_use\"\n | \"pause_turn\"\n | \"refusal\"\n | \"error\";\n\nexport interface OracleResponse {\n id: string;\n model: string;\n /** The provider that actually answered — for observability, not branching. */\n provider: ProviderId;\n role: \"assistant\";\n content: OracleContentBlock[];\n stopReason: OracleStopReason;\n usage: OracleUsage;\n}\n\n// ----- Streaming events -----\n\n/** A discriminated union of SSE events with the same vocabulary regardless of\n * provider. The anthropic adapter passes these through nearly 1:1; the openai\n * and google adapters map their native streams onto this shape. */\nexport type OracleEvent =\n | MessageStartEvent\n | ContentBlockStartEvent\n | ContentBlockDeltaEvent\n | ContentBlockStopEvent\n | MessageDeltaEvent\n | MessageStopEvent\n | OracleErrorEvent;\n\nexport interface MessageStartEvent {\n type: \"message_start\";\n message: {\n id: string;\n model: string;\n provider: ProviderId;\n role: \"assistant\";\n content: [];\n usage: OracleUsage;\n };\n}\n\nexport interface ContentBlockStartEvent {\n type: \"content_block_start\";\n index: number;\n contentBlock: OracleContentBlock;\n}\n\nexport type ContentBlockDelta =\n | { type: \"text_delta\"; text: string }\n | { type: \"thinking_delta\"; thinking: string }\n | { type: \"input_json_delta\"; partialJson: string };\n\nexport interface ContentBlockDeltaEvent {\n type: \"content_block_delta\";\n index: number;\n delta: ContentBlockDelta;\n}\n\nexport interface ContentBlockStopEvent {\n type: \"content_block_stop\";\n index: number;\n}\n\nexport interface MessageDeltaEvent {\n type: \"message_delta\";\n delta: { stopReason?: OracleStopReason };\n usage: OracleUsage;\n}\n\nexport interface MessageStopEvent {\n type: \"message_stop\";\n}\n\nexport interface OracleErrorEvent {\n type: \"error\";\n error: OracleErrorShape;\n}\n\n// ----- Errors -----\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 | \"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/** 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\n// ----- Helpers (cheap, dependency-free) -----\n\nexport function isTextBlock(b: OracleContentBlock): b is TextBlock {\n return b.type === \"text\";\n}\nexport function isImageBlock(b: OracleContentBlock): b is ImageBlock {\n return b.type === \"image\";\n}\nexport function isAudioBlock(b: OracleContentBlock): b is AudioBlock {\n return b.type === \"audio\";\n}\nexport function isDocumentBlock(b: OracleContentBlock): b is DocumentBlock {\n return b.type === \"document\";\n}\nexport function isToolUseBlock(b: OracleContentBlock): b is ToolUseBlock {\n return b.type === \"tool_use\";\n}\nexport function isToolResultBlock(b: OracleContentBlock): b is ToolResultBlock {\n return b.type === \"tool_result\";\n}\nexport function isThinkingBlock(b: OracleContentBlock): b is ThinkingBlock {\n return b.type === \"thinking\";\n}\n\n/** Normalize a message's `content` (string shorthand or block array) to blocks. */\nexport function blocksOf(content: string | OracleContentBlock[]): OracleContentBlock[] {\n return typeof content === \"string\" ? [{ type: \"text\", text: content }] : content;\n}\n\n/** Concatenate the text of every TextBlock. Image/audio/tool/thinking ignored. */\nexport function extractText(content: OracleContentBlock[]): string {\n return content.filter(isTextBlock).map((b) => b.text).join(\"\");\n}\n\n/** Collect every tool_use block from response or message content. */\nexport function extractToolUses(content: OracleContentBlock[]): ToolUseBlock[] {\n return content.filter(isToolUseBlock);\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 ContextWindowError,\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): OdlaAIError {\n if (err instanceof OdlaAIError) return 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): never {\n throw normalizeError(err, provider);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/google.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 — Gemini correlates by function name, so we key\n// the canonical tool_use id to the name).\n\nimport { GoogleGenAI, FunctionCallingConfigMode } from \"@google/genai\";\nimport type { Content, GenerateContentResponse, GenerateContentParameters, Part } from \"@google/genai\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { mapProviderError, normalizeError } from \"./errors\";\nimport {\n CapabilityError,\n emptyUsage,\n type OracleContentBlock,\n type OracleEvent,\n type OracleRequest,\n type OracleResponse,\n type OracleStopReason,\n type OracleUsage,\n type ToolChoice,\n} from \"../shape/index\";\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 try {\n const res = await this.client.models.generateContent(buildParams(spec, req));\n return mapResponse(res, req);\n } catch (err) {\n mapProviderError(err, \"google\");\n }\n }\n\n async *stream(spec: ModelSpec, req: OracleRequest): AsyncIterable<OracleEvent> {\n try {\n const iter = await this.client.models.generateContentStream(buildParams(spec, req));\n yield* mapStream(iter, req);\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"google\").toShape() };\n }\n }\n}\n\n// ----- request mapping -----\n\nfunction 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 stringifyBlocks(blocks: OracleContentBlock[]): string {\n return blocks.map((b) => (b.type === \"text\" ? b.text : `[${b.type}]`)).join(\"\");\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\n// ----- response mapping -----\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: (p.functionCall.args ?? {}) as Record<string, unknown> });\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\nfunction 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\nfunction 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\n// ----- stream mapping -----\n\nasync 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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/openai.ts"],"sourcesContent":["// OpenAI adapter, on the Chat Completions API (stable across the current model\n// lineup, multimodal, tool calls, streaming). The interesting translations —\n// system folded into a leading message, JSON-string tool `arguments` parsed to an\n// object, and tool_result blocks re-emitted as role:\"tool\" messages — happen here\n// so the rest of odla-ai only ever sees the canonical (Anthropic-flavored) shape.\n\nimport OpenAI from \"openai\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { normalizeError, mapProviderError } from \"./errors\";\nimport {\n CapabilityError,\n addUsage,\n emptyUsage,\n type AudioMediaType,\n type Effort,\n type OracleContentBlock,\n type OracleEvent,\n type OracleRequest,\n type OracleResponse,\n type OracleStopReason,\n type OracleUsage,\n type ToolChoice,\n} from \"../shape/index\";\n\ntype ChatParams = OpenAI.Chat.Completions.ChatCompletionCreateParams;\ntype ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam;\n\nexport class OpenAIProvider implements Provider {\n readonly id = \"openai\" as const;\n private client: OpenAI;\n\n constructor(apiKey: string, client?: OpenAI) {\n this.client = client ?? new OpenAI({ apiKey });\n }\n\n async create(spec: ModelSpec, req: OracleRequest): Promise<OracleResponse> {\n try {\n const res = (await this.client.chat.completions.create(\n buildParams(spec, req, false) as unknown as ChatParams,\n )) as OpenAI.Chat.Completions.ChatCompletion;\n return mapResponse(res, req);\n } catch (err) {\n mapProviderError(err, \"openai\");\n }\n }\n\n async *stream(spec: ModelSpec, req: OracleRequest): AsyncIterable<OracleEvent> {\n try {\n const stream = (await this.client.chat.completions.create(\n buildParams(spec, req, true) as unknown as ChatParams,\n )) as unknown as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>;\n yield* mapStream(stream, req);\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"openai\").toShape() };\n }\n }\n}\n\n// ----- request mapping -----\n\nfunction buildParams(spec: ModelSpec, req: OracleRequest, stream: boolean): Record<string, unknown> {\n const params: Record<string, unknown> = {\n model: spec.nativeId,\n messages: toOpenAIMessages(req),\n max_completion_tokens: req.maxTokens,\n };\n\n const tools = buildTools(req);\n if (tools) params.tools = tools;\n\n const toolChoice = mapToolChoice(req.toolChoice);\n if (toolChoice !== undefined) params.tool_choice = toolChoice;\n\n if (req.stopSequences && req.stopSequences.length > 0) params.stop = req.stopSequences;\n\n // Reasoning (effort) models reject a custom temperature; only send it elsewhere.\n if (req.temperature !== undefined && !spec.capabilities.effort) params.temperature = req.temperature;\n\n if (req.effort && spec.capabilities.effort) params.reasoning_effort = reasoningEffort(req.effort);\n\n if (req.responseFormat && spec.capabilities.structuredOutput) {\n params.response_format = {\n type: \"json_schema\",\n json_schema: { name: req.responseFormat.name ?? \"response\", schema: req.responseFormat.schema },\n };\n }\n\n // Search-capable chat models (e.g. gpt-5-search-api) take web_search_options;\n // the capability gate keeps this off models without webSearch.\n if (req.webSearch) params.web_search_options = { search_context_size: \"medium\" };\n\n if (stream) {\n params.stream = true;\n params.stream_options = { include_usage: true };\n }\n\n if (req.providerExtras) Object.assign(params, req.providerExtras);\n return params;\n}\n\n/** Expand canonical messages into OpenAI's flat message list: system folded to a\n * leading message, tool_result blocks lifted to role:\"tool\" messages, tool_use\n * blocks emitted as assistant tool_calls. */\nexport function toOpenAIMessages(req: OracleRequest): ChatMessage[] {\n const out: ChatMessage[] = [];\n\n if (req.system !== undefined) {\n const text = typeof req.system === \"string\" ? req.system : req.system.map((b) => b.text).join(\"\");\n out.push({ role: \"system\", content: text });\n }\n\n for (const msg of req.messages) {\n const blocks = typeof msg.content === \"string\" ? [{ type: \"text\" as const, text: msg.content }] : msg.content;\n\n if (msg.role === \"assistant\") {\n const textParts: string[] = [];\n const toolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] = [];\n for (const b of blocks) {\n if (b.type === \"text\") textParts.push(b.text);\n else if (b.type === \"tool_use\") {\n toolCalls.push({\n id: b.id,\n type: \"function\",\n function: { name: b.name, arguments: JSON.stringify(b.input) },\n } as OpenAI.Chat.Completions.ChatCompletionMessageToolCall);\n }\n // thinking blocks are dropped — OpenAI has no thinking input channel.\n }\n const assistant: Record<string, unknown> = { role: \"assistant\", content: textParts.join(\"\") || null };\n if (toolCalls.length > 0) assistant.tool_calls = toolCalls;\n out.push(assistant as unknown as ChatMessage);\n continue;\n }\n\n // user turn: tool_result blocks become tool messages; the rest becomes one\n // user message with multimodal content parts.\n const toolResults = blocks.filter((b) => b.type === \"tool_result\");\n for (const b of toolResults) {\n if (b.type !== \"tool_result\") continue;\n out.push({\n role: \"tool\",\n tool_call_id: b.toolUseId,\n content: typeof b.content === \"string\" ? b.content : stringifyBlocks(b.content),\n });\n }\n const rest = blocks.filter((b) => b.type !== \"tool_result\");\n if (rest.length > 0) out.push({ role: \"user\", content: rest.map(userPart) });\n }\n\n return out;\n}\n\ntype UserPart = OpenAI.Chat.Completions.ChatCompletionContentPart;\n\nfunction userPart(b: OracleContentBlock): UserPart {\n switch (b.type) {\n case \"text\":\n return { type: \"text\", text: b.text };\n case \"image\":\n return {\n type: \"image_url\",\n image_url: {\n url: b.source.type === \"url\" ? b.source.url : `data:${b.source.mediaType};base64,${b.source.data}`,\n },\n };\n case \"audio\": {\n if (b.source.type !== \"base64\") {\n throw new CapabilityError(\"OpenAI audio input requires base64 data, not a URL.\");\n }\n return {\n type: \"input_audio\",\n input_audio: { data: b.source.data, format: audioFormat(b.source.mediaType) },\n } as UserPart;\n }\n default:\n // document/tool_* are excluded from OpenAI user content (documents are\n // gated off by capabilities; tool_result handled separately).\n return { type: \"text\", text: stringifyBlocks([b]) };\n }\n}\n\nfunction audioFormat(m: AudioMediaType): \"wav\" | \"mp3\" {\n if (m === \"audio/wav\") return \"wav\";\n if (m === \"audio/mp3\" || m === \"audio/mpeg\") return \"mp3\";\n throw new CapabilityError(`OpenAI audio input supports wav and mp3, not \"${m}\".`);\n}\n\nfunction stringifyBlocks(blocks: OracleContentBlock[]): string {\n return blocks\n .map((b) => (b.type === \"text\" ? b.text : `[${b.type}]`))\n .join(\"\");\n}\n\nfunction buildTools(req: OracleRequest): unknown[] | undefined {\n if (!req.tools || req.tools.length === 0) return undefined;\n return req.tools.map((t) => ({\n type: \"function\",\n function: { name: t.name, description: t.description, parameters: t.inputSchema },\n }));\n}\n\nfunction mapToolChoice(tc: ToolChoice | undefined): unknown | undefined {\n if (tc === undefined) return undefined;\n if (tc === \"auto\") return \"auto\";\n if (tc === \"none\") return \"none\";\n if (tc === \"any\") return \"required\";\n return { type: \"function\", function: { name: tc.name } };\n}\n\nfunction reasoningEffort(e: Effort): \"low\" | \"medium\" | \"high\" {\n if (e === \"low\") return \"low\";\n if (e === \"medium\") return \"medium\";\n return \"high\"; // high / xhigh / max → high\n}\n\n// ----- response mapping -----\n\nexport function mapResponse(res: OpenAI.Chat.Completions.ChatCompletion, req: OracleRequest): OracleResponse {\n const choice = res.choices[0];\n const content: OracleContentBlock[] = [];\n const message = choice?.message;\n\n if (message?.content) content.push({ type: \"text\", text: message.content });\n for (const call of message?.tool_calls ?? []) {\n if (call.type !== \"function\") continue;\n content.push({ type: \"tool_use\", id: call.id, name: call.function.name, input: parseArgs(call.function.arguments) });\n }\n\n return {\n id: res.id,\n model: req.model,\n provider: \"openai\",\n role: \"assistant\",\n content,\n stopReason: mapFinish(choice?.finish_reason),\n usage: mapUsage(res.usage),\n };\n}\n\n/** OpenAI hands tool arguments back as a JSON *string*; the canonical shape wants\n * a parsed object. Tolerant: malformed JSON becomes an empty object. */\nexport function parseArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction mapFinish(reason: string | null | undefined): OracleStopReason {\n switch (reason) {\n case \"stop\":\n return \"end_turn\";\n case \"length\":\n return \"max_tokens\";\n case \"tool_calls\":\n case \"function_call\":\n return \"tool_use\";\n case \"content_filter\":\n return \"refusal\";\n default:\n return \"end_turn\";\n }\n}\n\nfunction mapUsage(u: OpenAI.Completions.CompletionUsage | undefined): OracleUsage {\n const usage: OracleUsage = { inputTokens: u?.prompt_tokens ?? 0, outputTokens: u?.completion_tokens ?? 0 };\n const cached = u?.prompt_tokens_details?.cached_tokens;\n if (cached != null) usage.cacheReadTokens = cached;\n return usage;\n}\n\n// ----- stream mapping -----\n\n/** Map an OpenAI chat stream onto the canonical event vocabulary. OpenAI has no\n * explicit block start/stop, so we synthesize them: a text channel (index 0) and\n * one channel per tool_call index, opened lazily and closed at finish. */\nasync function* mapStream(\n stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>,\n req: OracleRequest,\n): AsyncIterable<OracleEvent> {\n let started = false;\n let textOpen = false;\n const toolIndex = new Map<number, number>(); // openai tool_call index → our block index\n let nextBlockIndex = 1; // 0 reserved for text\n let stopReason: OracleStopReason = \"end_turn\";\n const usage = emptyUsage();\n\n for await (const chunk of stream) {\n if (!started) {\n started = true;\n yield {\n type: \"message_start\",\n message: { id: chunk.id, model: req.model, provider: \"openai\", role: \"assistant\", content: [], usage: emptyUsage() },\n };\n }\n\n if (chunk.usage) addUsage(usage, mapUsage(chunk.usage));\n\n const choice = chunk.choices[0];\n const delta = choice?.delta;\n\n if (delta?.content) {\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: delta.content } };\n }\n\n for (const tc of delta?.tool_calls ?? []) {\n let blockIndex = toolIndex.get(tc.index);\n if (blockIndex === undefined) {\n blockIndex = nextBlockIndex++;\n toolIndex.set(tc.index, blockIndex);\n yield {\n type: \"content_block_start\",\n index: blockIndex,\n contentBlock: { type: \"tool_use\", id: tc.id ?? \"\", name: tc.function?.name ?? \"\", input: {} },\n };\n }\n if (tc.function?.arguments) {\n yield { type: \"content_block_delta\", index: blockIndex, delta: { type: \"input_json_delta\", partialJson: tc.function.arguments } };\n }\n }\n\n if (choice?.finish_reason) stopReason = mapFinish(choice.finish_reason);\n }\n\n if (textOpen) yield { type: \"content_block_stop\", index: 0 };\n for (const blockIndex of toolIndex.values()) yield { type: \"content_block_stop\", index: blockIndex };\n yield { type: \"message_delta\", delta: { stopReason }, usage };\n yield { type: \"message_stop\" };\n}\n"],"mappings":";;;;;;;;;AAMA,OAAO,YAAY;AAsBZ,IAAM,iBAAN,MAAyC;AAAA,EACrC,KAAK;AAAA,EACN;AAAA,EAER,YAAY,QAAgB,QAAiB;AAC3C,SAAK,SAAS,UAAU,IAAI,OAAO,EAAE,OAAO,CAAC;AAAA,EAC/C;AAAA,EAEA,MAAM,OAAO,MAAiB,KAA6C;AACzE,QAAI;AACF,YAAM,MAAO,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QAC9C,YAAY,MAAM,KAAK,KAAK;AAAA,MAC9B;AACA,aAAO,YAAY,KAAK,GAAG;AAAA,IAC7B,SAAS,KAAK;AACZ,uBAAiB,KAAK,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,OAAO,OAAO,MAAiB,KAAgD;AAC7E,QAAI;AACF,YAAM,SAAU,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QACjD,YAAY,MAAM,KAAK,IAAI;AAAA,MAC7B;AACA,aAAO,UAAU,QAAQ,GAAG;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,EAAE,MAAM,SAAS,OAAO,eAAe,KAAK,QAAQ,EAAE,QAAQ,EAAE;AAAA,IACxE;AAAA,EACF;AACF;AAIA,SAAS,YAAY,MAAiB,KAAoB,QAA0C;AAClG,QAAM,SAAkC;AAAA,IACtC,OAAO,KAAK;AAAA,IACZ,UAAU,iBAAiB,GAAG;AAAA,IAC9B,uBAAuB,IAAI;AAAA,EAC7B;AAEA,QAAM,QAAQ,WAAW,GAAG;AAC5B,MAAI,MAAO,QAAO,QAAQ;AAE1B,QAAM,aAAa,cAAc,IAAI,UAAU;AAC/C,MAAI,eAAe,OAAW,QAAO,cAAc;AAEnD,MAAI,IAAI,iBAAiB,IAAI,cAAc,SAAS,EAAG,QAAO,OAAO,IAAI;AAGzE,MAAI,IAAI,gBAAgB,UAAa,CAAC,KAAK,aAAa,OAAQ,QAAO,cAAc,IAAI;AAEzF,MAAI,IAAI,UAAU,KAAK,aAAa,OAAQ,QAAO,mBAAmB,gBAAgB,IAAI,MAAM;AAEhG,MAAI,IAAI,kBAAkB,KAAK,aAAa,kBAAkB;AAC5D,WAAO,kBAAkB;AAAA,MACvB,MAAM;AAAA,MACN,aAAa,EAAE,MAAM,IAAI,eAAe,QAAQ,YAAY,QAAQ,IAAI,eAAe,OAAO;AAAA,IAChG;AAAA,EACF;AAIA,MAAI,IAAI,UAAW,QAAO,qBAAqB,EAAE,qBAAqB,SAAS;AAE/E,MAAI,QAAQ;AACV,WAAO,SAAS;AAChB,WAAO,iBAAiB,EAAE,eAAe,KAAK;AAAA,EAChD;AAEA,MAAI,IAAI,eAAgB,QAAO,OAAO,QAAQ,IAAI,cAAc;AAChE,SAAO;AACT;AAKO,SAAS,iBAAiB,KAAmC;AAClE,QAAM,MAAqB,CAAC;AAE5B,MAAI,IAAI,WAAW,QAAW;AAC5B,UAAM,OAAO,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE;AAChG,QAAI,KAAK,EAAE,MAAM,UAAU,SAAS,KAAK,CAAC;AAAA,EAC5C;AAEA,aAAW,OAAO,IAAI,UAAU;AAC9B,UAAM,SAAS,OAAO,IAAI,YAAY,WAAW,CAAC,EAAE,MAAM,QAAiB,MAAM,IAAI,QAAQ,CAAC,IAAI,IAAI;AAEtG,QAAI,IAAI,SAAS,aAAa;AAC5B,YAAM,YAAsB,CAAC;AAC7B,YAAM,YAAqE,CAAC;AAC5E,iBAAW,KAAK,QAAQ;AACtB,YAAI,EAAE,SAAS,OAAQ,WAAU,KAAK,EAAE,IAAI;AAAA,iBACnC,EAAE,SAAS,YAAY;AAC9B,oBAAU,KAAK;AAAA,YACb,IAAI,EAAE;AAAA,YACN,MAAM;AAAA,YACN,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,KAAK,UAAU,EAAE,KAAK,EAAE;AAAA,UAC/D,CAA0D;AAAA,QAC5D;AAAA,MAEF;AACA,YAAM,YAAqC,EAAE,MAAM,aAAa,SAAS,UAAU,KAAK,EAAE,KAAK,KAAK;AACpG,UAAI,UAAU,SAAS,EAAG,WAAU,aAAa;AACjD,UAAI,KAAK,SAAmC;AAC5C;AAAA,IACF;AAIA,UAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,aAAa;AACjE,eAAW,KAAK,aAAa;AAC3B,UAAI,EAAE,SAAS,cAAe;AAC9B,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,cAAc,EAAE;AAAA,QAChB,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,gBAAgB,EAAE,OAAO;AAAA,MAChF,CAAC;AAAA,IACH;AACA,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,aAAa;AAC1D,QAAI,KAAK,SAAS,EAAG,KAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ,EAAE,CAAC;AAAA,EAC7E;AAEA,SAAO;AACT;AAIA,SAAS,SAAS,GAAiC;AACjD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,MAAM,EAAE,KAAK;AAAA,IACtC,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,WAAW;AAAA,UACT,KAAK,EAAE,OAAO,SAAS,QAAQ,EAAE,OAAO,MAAM,QAAQ,EAAE,OAAO,SAAS,WAAW,EAAE,OAAO,IAAI;AAAA,QAClG;AAAA,MACF;AAAA,IACF,KAAK,SAAS;AACZ,UAAI,EAAE,OAAO,SAAS,UAAU;AAC9B,cAAM,IAAI,gBAAgB,qDAAqD;AAAA,MACjF;AACA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,aAAa,EAAE,MAAM,EAAE,OAAO,MAAM,QAAQ,YAAY,EAAE,OAAO,SAAS,EAAE;AAAA,MAC9E;AAAA,IACF;AAAA,IACA;AAGE,aAAO,EAAE,MAAM,QAAQ,MAAM,gBAAgB,CAAC,CAAC,CAAC,EAAE;AAAA,EACtD;AACF;AAEA,SAAS,YAAY,GAAkC;AACrD,MAAI,MAAM,YAAa,QAAO;AAC9B,MAAI,MAAM,eAAe,MAAM,aAAc,QAAO;AACpD,QAAM,IAAI,gBAAgB,iDAAiD,CAAC,IAAI;AAClF;AAEA,SAAS,gBAAgB,QAAsC;AAC7D,SAAO,OACJ,IAAI,CAAC,MAAO,EAAE,SAAS,SAAS,EAAE,OAAO,IAAI,EAAE,IAAI,GAAI,EACvD,KAAK,EAAE;AACZ;AAEA,SAAS,WAAW,KAA2C;AAC7D,MAAI,CAAC,IAAI,SAAS,IAAI,MAAM,WAAW,EAAG,QAAO;AACjD,SAAO,IAAI,MAAM,IAAI,CAAC,OAAO;AAAA,IAC3B,MAAM;AAAA,IACN,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,EAAE,aAAa,YAAY,EAAE,YAAY;AAAA,EAClF,EAAE;AACJ;AAEA,SAAS,cAAc,IAAiD;AACtE,MAAI,OAAO,OAAW,QAAO;AAC7B,MAAI,OAAO,OAAQ,QAAO;AAC1B,MAAI,OAAO,OAAQ,QAAO;AAC1B,MAAI,OAAO,MAAO,QAAO;AACzB,SAAO,EAAE,MAAM,YAAY,UAAU,EAAE,MAAM,GAAG,KAAK,EAAE;AACzD;AAEA,SAAS,gBAAgB,GAAsC;AAC7D,MAAI,MAAM,MAAO,QAAO;AACxB,MAAI,MAAM,SAAU,QAAO;AAC3B,SAAO;AACT;AAIO,SAAS,YAAY,KAA6C,KAAoC;AAC3G,QAAM,SAAS,IAAI,QAAQ,CAAC;AAC5B,QAAM,UAAgC,CAAC;AACvC,QAAM,UAAU,QAAQ;AAExB,MAAI,SAAS,QAAS,SAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAC1E,aAAW,QAAQ,SAAS,cAAc,CAAC,GAAG;AAC5C,QAAI,KAAK,SAAS,WAAY;AAC9B,YAAQ,KAAK,EAAE,MAAM,YAAY,IAAI,KAAK,IAAI,MAAM,KAAK,SAAS,MAAM,OAAO,UAAU,KAAK,SAAS,SAAS,EAAE,CAAC;AAAA,EACrH;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,OAAO,IAAI;AAAA,IACX,UAAU;AAAA,IACV,MAAM;AAAA,IACN;AAAA,IACA,YAAY,UAAU,QAAQ,aAAa;AAAA,IAC3C,OAAO,SAAS,IAAI,KAAK;AAAA,EAC3B;AACF;AAIO,SAAS,UAAU,KAAkD;AAC1E,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,UAAU,OAAO,WAAW,WAAY,SAAqC,CAAC;AAAA,EACvF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,UAAU,QAAqD;AACtE,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,SAAS,GAAgE;AAChF,QAAM,QAAqB,EAAE,aAAa,GAAG,iBAAiB,GAAG,cAAc,GAAG,qBAAqB,EAAE;AACzG,QAAM,SAAS,GAAG,uBAAuB;AACzC,MAAI,UAAU,KAAM,OAAM,kBAAkB;AAC5C,SAAO;AACT;AAOA,gBAAgB,UACd,QACA,KAC4B;AAC5B,MAAI,UAAU;AACd,MAAI,WAAW;AACf,QAAM,YAAY,oBAAI,IAAoB;AAC1C,MAAI,iBAAiB;AACrB,MAAI,aAA+B;AACnC,QAAM,QAAQ,WAAW;AAEzB,mBAAiB,SAAS,QAAQ;AAChC,QAAI,CAAC,SAAS;AACZ,gBAAU;AACV,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,SAAS,EAAE,IAAI,MAAM,IAAI,OAAO,IAAI,OAAO,UAAU,UAAU,MAAM,aAAa,SAAS,CAAC,GAAG,OAAO,WAAW,EAAE;AAAA,MACrH;AAAA,IACF;AAEA,QAAI,MAAM,MAAO,UAAS,OAAO,SAAS,MAAM,KAAK,CAAC;AAEtD,UAAM,SAAS,MAAM,QAAQ,CAAC;AAC9B,UAAM,QAAQ,QAAQ;AAEtB,QAAI,OAAO,SAAS;AAClB,UAAI,CAAC,UAAU;AACb,mBAAW;AACX,cAAM,EAAE,MAAM,uBAAuB,OAAO,GAAG,cAAc,EAAE,MAAM,QAAQ,MAAM,GAAG,EAAE;AAAA,MAC1F;AACA,YAAM,EAAE,MAAM,uBAAuB,OAAO,GAAG,OAAO,EAAE,MAAM,cAAc,MAAM,MAAM,QAAQ,EAAE;AAAA,IACpG;AAEA,eAAW,MAAM,OAAO,cAAc,CAAC,GAAG;AACxC,UAAI,aAAa,UAAU,IAAI,GAAG,KAAK;AACvC,UAAI,eAAe,QAAW;AAC5B,qBAAa;AACb,kBAAU,IAAI,GAAG,OAAO,UAAU;AAClC,cAAM;AAAA,UACJ,MAAM;AAAA,UACN,OAAO;AAAA,UACP,cAAc,EAAE,MAAM,YAAY,IAAI,GAAG,MAAM,IAAI,MAAM,GAAG,UAAU,QAAQ,IAAI,OAAO,CAAC,EAAE;AAAA,QAC9F;AAAA,MACF;AACA,UAAI,GAAG,UAAU,WAAW;AAC1B,cAAM,EAAE,MAAM,uBAAuB,OAAO,YAAY,OAAO,EAAE,MAAM,oBAAoB,aAAa,GAAG,SAAS,UAAU,EAAE;AAAA,MAClI;AAAA,IACF;AAEA,QAAI,QAAQ,cAAe,cAAa,UAAU,OAAO,aAAa;AAAA,EACxE;AAEA,MAAI,SAAU,OAAM,EAAE,MAAM,sBAAsB,OAAO,EAAE;AAC3D,aAAW,cAAc,UAAU,OAAO,EAAG,OAAM,EAAE,MAAM,sBAAsB,OAAO,WAAW;AACnG,QAAM,EAAE,MAAM,iBAAiB,OAAO,EAAE,WAAW,GAAG,MAAM;AAC5D,QAAM,EAAE,MAAM,eAAe;AAC/B;","names":[]}
|