@odla-ai/ai 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,2002 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/shape/index.ts
34
+ function emptyUsage() {
35
+ return { inputTokens: 0, outputTokens: 0 };
36
+ }
37
+ function addUsage(into, more) {
38
+ into.inputTokens += more.inputTokens;
39
+ into.outputTokens += more.outputTokens;
40
+ if (more.cacheCreationTokens !== void 0) {
41
+ into.cacheCreationTokens = (into.cacheCreationTokens ?? 0) + more.cacheCreationTokens;
42
+ }
43
+ if (more.cacheReadTokens !== void 0) {
44
+ into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;
45
+ }
46
+ }
47
+ function isTextBlock(b) {
48
+ return b.type === "text";
49
+ }
50
+ function isImageBlock(b) {
51
+ return b.type === "image";
52
+ }
53
+ function isAudioBlock(b) {
54
+ return b.type === "audio";
55
+ }
56
+ function isDocumentBlock(b) {
57
+ return b.type === "document";
58
+ }
59
+ function isToolUseBlock(b) {
60
+ return b.type === "tool_use";
61
+ }
62
+ function isToolResultBlock(b) {
63
+ return b.type === "tool_result";
64
+ }
65
+ function isThinkingBlock(b) {
66
+ return b.type === "thinking";
67
+ }
68
+ function blocksOf(content) {
69
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
70
+ }
71
+ function extractText(content) {
72
+ return content.filter(isTextBlock).map((b) => b.text).join("");
73
+ }
74
+ function extractToolUses(content) {
75
+ return content.filter(isToolUseBlock);
76
+ }
77
+ var OdlaAIError, ConfigError, AuthError, RateLimitError, CapabilityError, ContextWindowError, InvalidRequestError, ProviderError;
78
+ var init_shape = __esm({
79
+ "src/shape/index.ts"() {
80
+ "use strict";
81
+ OdlaAIError = class extends Error {
82
+ code;
83
+ providerStatus;
84
+ retryAfter;
85
+ constructor(code, message, opts) {
86
+ super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
87
+ this.name = new.target.name;
88
+ this.code = code;
89
+ this.providerStatus = opts?.providerStatus;
90
+ this.retryAfter = opts?.retryAfter;
91
+ }
92
+ toShape() {
93
+ return {
94
+ code: this.code,
95
+ message: this.message,
96
+ providerStatus: this.providerStatus,
97
+ retryAfter: this.retryAfter
98
+ };
99
+ }
100
+ };
101
+ ConfigError = class extends OdlaAIError {
102
+ constructor(message) {
103
+ super("config", message);
104
+ }
105
+ };
106
+ AuthError = class extends OdlaAIError {
107
+ constructor(message, opts) {
108
+ super("auth", message, opts);
109
+ }
110
+ };
111
+ RateLimitError = class extends OdlaAIError {
112
+ constructor(message, opts) {
113
+ super("rate_limit", message, opts);
114
+ }
115
+ };
116
+ CapabilityError = class extends OdlaAIError {
117
+ constructor(message) {
118
+ super("capability_unsupported", message);
119
+ }
120
+ };
121
+ ContextWindowError = class extends OdlaAIError {
122
+ constructor(message, opts) {
123
+ super("context_window", message, opts);
124
+ }
125
+ };
126
+ InvalidRequestError = class extends OdlaAIError {
127
+ constructor(message, opts) {
128
+ super("invalid_request", message, opts);
129
+ }
130
+ };
131
+ ProviderError = class extends OdlaAIError {
132
+ constructor(message, opts) {
133
+ super("provider_error", message, opts);
134
+ }
135
+ };
136
+ }
137
+ });
138
+
139
+ // src/providers/errors.ts
140
+ function statusOf(err) {
141
+ if (err && typeof err === "object") {
142
+ const rec = err;
143
+ for (const k of ["status", "statusCode", "code"]) {
144
+ const v = rec[k];
145
+ if (typeof v === "number") return v;
146
+ }
147
+ }
148
+ return void 0;
149
+ }
150
+ function retryAfterOf(err) {
151
+ if (err && typeof err === "object") {
152
+ const headers = err.headers;
153
+ if (headers && typeof headers === "object") {
154
+ const raw = headers["retry-after"];
155
+ const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : NaN;
156
+ if (Number.isFinite(n)) return n;
157
+ }
158
+ }
159
+ return void 0;
160
+ }
161
+ function messageOf(err) {
162
+ if (err instanceof Error) return err.message;
163
+ if (typeof err === "string") return err;
164
+ return "unknown provider error";
165
+ }
166
+ function normalizeError(err, provider) {
167
+ if (err instanceof OdlaAIError) return err;
168
+ const status = statusOf(err);
169
+ const message = messageOf(err);
170
+ const tagged = `[${provider}] ${message}`;
171
+ if (status === 401 || status === 403) return new AuthError(tagged, { providerStatus: status, cause: err });
172
+ if (status === 429) {
173
+ return new RateLimitError(tagged, { providerStatus: status, retryAfter: retryAfterOf(err), cause: err });
174
+ }
175
+ if (status === 400 || status === 422) {
176
+ if (/context|token limit|too long|maximum context/i.test(message)) {
177
+ return new ContextWindowError(tagged, { providerStatus: status, cause: err });
178
+ }
179
+ return new InvalidRequestError(tagged, { providerStatus: status, cause: err });
180
+ }
181
+ return new ProviderError(tagged, { providerStatus: status, cause: err });
182
+ }
183
+ function mapProviderError(err, provider) {
184
+ throw normalizeError(err, provider);
185
+ }
186
+ var init_errors = __esm({
187
+ "src/providers/errors.ts"() {
188
+ "use strict";
189
+ init_shape();
190
+ }
191
+ });
192
+
193
+ // src/providers/anthropic.ts
194
+ var anthropic_exports = {};
195
+ __export(anthropic_exports, {
196
+ AnthropicProvider: () => AnthropicProvider
197
+ });
198
+ function buildParams(spec, req, messages) {
199
+ const params = {
200
+ model: spec.nativeId,
201
+ max_tokens: req.maxTokens,
202
+ messages
203
+ };
204
+ const system = toAnthropicSystem(req.system);
205
+ if (system !== void 0) params.system = system;
206
+ const tools = buildTools(req);
207
+ if (tools) params.tools = tools;
208
+ const toolChoice = mapToolChoice(req.toolChoice);
209
+ if (toolChoice) params.tool_choice = toolChoice;
210
+ if (req.stopSequences && req.stopSequences.length > 0) params.stop_sequences = req.stopSequences;
211
+ if (req.temperature !== void 0 && !spec.capabilities.effort) params.temperature = req.temperature;
212
+ if (req.thinking === "adaptive" && spec.capabilities.thinking) params.thinking = { type: "adaptive" };
213
+ const outputConfig = {};
214
+ if (req.effort && spec.capabilities.effort) outputConfig.effort = req.effort;
215
+ if (req.responseFormat && spec.capabilities.structuredOutput) {
216
+ outputConfig.format = { type: "json_schema", schema: req.responseFormat.schema };
217
+ }
218
+ if (Object.keys(outputConfig).length > 0) params.output_config = outputConfig;
219
+ if (req.providerExtras) Object.assign(params, req.providerExtras);
220
+ return params;
221
+ }
222
+ function toAnthropicSystem(system) {
223
+ if (system === void 0) return void 0;
224
+ if (typeof system === "string") return system;
225
+ return system.map((b) => ({
226
+ type: "text",
227
+ text: b.text,
228
+ ...b.cacheControl ? { cache_control: { type: "ephemeral" } } : {}
229
+ }));
230
+ }
231
+ function toAnthropicMessages(req) {
232
+ return req.messages.map((m) => ({
233
+ role: m.role,
234
+ content: typeof m.content === "string" ? m.content : m.content.map(anthropicBlock)
235
+ }));
236
+ }
237
+ function anthropicBlock(b) {
238
+ switch (b.type) {
239
+ case "text":
240
+ return {
241
+ type: "text",
242
+ text: b.text,
243
+ ...b.cacheControl ? { cache_control: { type: "ephemeral" } } : {}
244
+ };
245
+ case "image":
246
+ return {
247
+ type: "image",
248
+ source: b.source.type === "base64" ? { type: "base64", media_type: b.source.mediaType, data: b.source.data } : { type: "url", url: b.source.url }
249
+ };
250
+ case "document":
251
+ return {
252
+ type: "document",
253
+ source: b.source.type === "base64" ? { type: "base64", media_type: "application/pdf", data: b.source.data } : { type: "url", url: b.source.url }
254
+ };
255
+ case "tool_use":
256
+ return { type: "tool_use", id: b.id, name: b.name, input: b.input };
257
+ case "tool_result":
258
+ return {
259
+ type: "tool_result",
260
+ tool_use_id: b.toolUseId,
261
+ content: typeof b.content === "string" ? b.content : b.content.map(anthropicBlock),
262
+ ...b.isError ? { is_error: true } : {}
263
+ };
264
+ case "thinking":
265
+ return { type: "thinking", thinking: b.thinking, signature: b.signature ?? "" };
266
+ case "audio":
267
+ throw new Error("Anthropic does not support audio input");
268
+ }
269
+ }
270
+ function buildTools(req) {
271
+ const tools = [];
272
+ for (const t of req.tools ?? []) {
273
+ tools.push({ name: t.name, description: t.description, input_schema: t.inputSchema });
274
+ }
275
+ if (req.webSearch) tools.push({ type: "web_search_20260209", name: "web_search" });
276
+ return tools.length > 0 ? tools : void 0;
277
+ }
278
+ function mapToolChoice(tc) {
279
+ if (tc === void 0) return void 0;
280
+ if (tc === "auto") return { type: "auto" };
281
+ if (tc === "any") return { type: "any" };
282
+ if (tc === "none") return { type: "none" };
283
+ return { type: "tool", name: tc.name };
284
+ }
285
+ function mapContent(blocks) {
286
+ const out = [];
287
+ for (const b of blocks) {
288
+ if (b.type === "text") out.push({ type: "text", text: b.text });
289
+ else if (b.type === "tool_use") {
290
+ out.push({ type: "tool_use", id: b.id, name: b.name, input: b.input ?? {} });
291
+ } else if (b.type === "thinking") {
292
+ out.push({ type: "thinking", thinking: b.thinking, signature: b.signature });
293
+ }
294
+ }
295
+ return out;
296
+ }
297
+ function mapUsage(u) {
298
+ const usage = { inputTokens: u.input_tokens ?? 0, outputTokens: u.output_tokens ?? 0 };
299
+ if (u.cache_creation_input_tokens != null) usage.cacheCreationTokens = u.cache_creation_input_tokens;
300
+ if (u.cache_read_input_tokens != null) usage.cacheReadTokens = u.cache_read_input_tokens;
301
+ return usage;
302
+ }
303
+ function mapStop(reason) {
304
+ switch (reason) {
305
+ case "end_turn":
306
+ case "max_tokens":
307
+ case "stop_sequence":
308
+ case "tool_use":
309
+ case "pause_turn":
310
+ case "refusal":
311
+ return reason;
312
+ default:
313
+ return "end_turn";
314
+ }
315
+ }
316
+ function mapStreamEvent(raw, req) {
317
+ switch (raw.type) {
318
+ case "message_start":
319
+ return {
320
+ type: "message_start",
321
+ message: {
322
+ id: raw.message.id,
323
+ model: req.model,
324
+ provider: "anthropic",
325
+ role: "assistant",
326
+ content: [],
327
+ usage: mapUsage(raw.message.usage)
328
+ }
329
+ };
330
+ case "content_block_start": {
331
+ const block = mapStartBlock(raw.content_block);
332
+ return block ? { type: "content_block_start", index: raw.index, contentBlock: block } : void 0;
333
+ }
334
+ case "content_block_delta": {
335
+ const d = raw.delta;
336
+ if (d.type === "text_delta") return { type: "content_block_delta", index: raw.index, delta: { type: "text_delta", text: d.text } };
337
+ if (d.type === "thinking_delta") return { type: "content_block_delta", index: raw.index, delta: { type: "thinking_delta", thinking: d.thinking } };
338
+ if (d.type === "input_json_delta") return { type: "content_block_delta", index: raw.index, delta: { type: "input_json_delta", partialJson: d.partial_json } };
339
+ return void 0;
340
+ }
341
+ case "content_block_stop":
342
+ return { type: "content_block_stop", index: raw.index };
343
+ case "message_delta":
344
+ return { type: "message_delta", delta: { stopReason: mapStop(raw.delta.stop_reason) }, usage: mapUsage(raw.usage) };
345
+ case "message_stop":
346
+ return { type: "message_stop" };
347
+ default:
348
+ return void 0;
349
+ }
350
+ }
351
+ function mapStartBlock(cb) {
352
+ if (cb.type === "text") return { type: "text", text: cb.text };
353
+ if (cb.type === "tool_use") return { type: "tool_use", id: cb.id, name: cb.name, input: {} };
354
+ if (cb.type === "thinking") return { type: "thinking", thinking: cb.thinking, signature: cb.signature };
355
+ return void 0;
356
+ }
357
+ var import_sdk, AnthropicProvider;
358
+ var init_anthropic = __esm({
359
+ "src/providers/anthropic.ts"() {
360
+ "use strict";
361
+ import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
362
+ init_errors();
363
+ init_shape();
364
+ AnthropicProvider = class {
365
+ id = "anthropic";
366
+ client;
367
+ /** `client` may be injected (tests, custom transport); otherwise built from key. */
368
+ constructor(apiKey, client) {
369
+ this.client = client ?? new import_sdk.default({ apiKey });
370
+ }
371
+ async create(spec, req) {
372
+ try {
373
+ let messages = toAnthropicMessages(req);
374
+ const content = [];
375
+ const usage = emptyUsage();
376
+ let stopReason = "end_turn";
377
+ let id = "";
378
+ for (let i = 0; i < 8; i++) {
379
+ const res = await this.client.messages.create(
380
+ buildParams(spec, req, messages)
381
+ );
382
+ id = res.id;
383
+ addUsage(usage, mapUsage(res.usage));
384
+ content.push(...mapContent(res.content));
385
+ stopReason = mapStop(res.stop_reason);
386
+ if (res.stop_reason === "pause_turn") {
387
+ messages = [...messages, { role: "assistant", content: res.content }];
388
+ continue;
389
+ }
390
+ break;
391
+ }
392
+ return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
393
+ } catch (err) {
394
+ mapProviderError(err, "anthropic");
395
+ }
396
+ }
397
+ async *stream(spec, req) {
398
+ try {
399
+ const events = this.client.messages.stream(
400
+ buildParams(spec, req, toAnthropicMessages(req))
401
+ );
402
+ for await (const raw of events) {
403
+ const ev = mapStreamEvent(raw, req);
404
+ if (ev) yield ev;
405
+ }
406
+ } catch (err) {
407
+ yield { type: "error", error: normalizeError(err, "anthropic").toShape() };
408
+ }
409
+ }
410
+ };
411
+ }
412
+ });
413
+
414
+ // src/providers/openai.ts
415
+ var openai_exports = {};
416
+ __export(openai_exports, {
417
+ OpenAIProvider: () => OpenAIProvider,
418
+ mapResponse: () => mapResponse,
419
+ parseArgs: () => parseArgs,
420
+ toOpenAIMessages: () => toOpenAIMessages
421
+ });
422
+ function buildParams2(spec, req, stream) {
423
+ const params = {
424
+ model: spec.nativeId,
425
+ messages: toOpenAIMessages(req),
426
+ max_completion_tokens: req.maxTokens
427
+ };
428
+ const tools = buildTools2(req);
429
+ if (tools) params.tools = tools;
430
+ const toolChoice = mapToolChoice2(req.toolChoice);
431
+ if (toolChoice !== void 0) params.tool_choice = toolChoice;
432
+ if (req.stopSequences && req.stopSequences.length > 0) params.stop = req.stopSequences;
433
+ if (req.temperature !== void 0 && !spec.capabilities.effort) params.temperature = req.temperature;
434
+ if (req.effort && spec.capabilities.effort) params.reasoning_effort = reasoningEffort(req.effort);
435
+ if (req.responseFormat && spec.capabilities.structuredOutput) {
436
+ params.response_format = {
437
+ type: "json_schema",
438
+ json_schema: { name: req.responseFormat.name ?? "response", schema: req.responseFormat.schema }
439
+ };
440
+ }
441
+ if (stream) {
442
+ params.stream = true;
443
+ params.stream_options = { include_usage: true };
444
+ }
445
+ if (req.providerExtras) Object.assign(params, req.providerExtras);
446
+ return params;
447
+ }
448
+ function toOpenAIMessages(req) {
449
+ const out = [];
450
+ if (req.system !== void 0) {
451
+ const text = typeof req.system === "string" ? req.system : req.system.map((b) => b.text).join("");
452
+ out.push({ role: "system", content: text });
453
+ }
454
+ for (const msg of req.messages) {
455
+ const blocks = typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content;
456
+ if (msg.role === "assistant") {
457
+ const textParts = [];
458
+ const toolCalls = [];
459
+ for (const b of blocks) {
460
+ if (b.type === "text") textParts.push(b.text);
461
+ else if (b.type === "tool_use") {
462
+ toolCalls.push({
463
+ id: b.id,
464
+ type: "function",
465
+ function: { name: b.name, arguments: JSON.stringify(b.input) }
466
+ });
467
+ }
468
+ }
469
+ const assistant = { role: "assistant", content: textParts.join("") || null };
470
+ if (toolCalls.length > 0) assistant.tool_calls = toolCalls;
471
+ out.push(assistant);
472
+ continue;
473
+ }
474
+ const toolResults = blocks.filter((b) => b.type === "tool_result");
475
+ for (const b of toolResults) {
476
+ if (b.type !== "tool_result") continue;
477
+ out.push({
478
+ role: "tool",
479
+ tool_call_id: b.toolUseId,
480
+ content: typeof b.content === "string" ? b.content : stringifyBlocks(b.content)
481
+ });
482
+ }
483
+ const rest = blocks.filter((b) => b.type !== "tool_result");
484
+ if (rest.length > 0) out.push({ role: "user", content: rest.map(userPart) });
485
+ }
486
+ return out;
487
+ }
488
+ function userPart(b) {
489
+ switch (b.type) {
490
+ case "text":
491
+ return { type: "text", text: b.text };
492
+ case "image":
493
+ return {
494
+ type: "image_url",
495
+ image_url: {
496
+ url: b.source.type === "url" ? b.source.url : `data:${b.source.mediaType};base64,${b.source.data}`
497
+ }
498
+ };
499
+ case "audio": {
500
+ if (b.source.type !== "base64") {
501
+ throw new CapabilityError("OpenAI audio input requires base64 data, not a URL.");
502
+ }
503
+ return {
504
+ type: "input_audio",
505
+ input_audio: { data: b.source.data, format: audioFormat(b.source.mediaType) }
506
+ };
507
+ }
508
+ default:
509
+ return { type: "text", text: stringifyBlocks([b]) };
510
+ }
511
+ }
512
+ function audioFormat(m) {
513
+ if (m === "audio/wav") return "wav";
514
+ if (m === "audio/mp3" || m === "audio/mpeg") return "mp3";
515
+ throw new CapabilityError(`OpenAI audio input supports wav and mp3, not "${m}".`);
516
+ }
517
+ function stringifyBlocks(blocks) {
518
+ return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
519
+ }
520
+ function buildTools2(req) {
521
+ if (!req.tools || req.tools.length === 0) return void 0;
522
+ return req.tools.map((t) => ({
523
+ type: "function",
524
+ function: { name: t.name, description: t.description, parameters: t.inputSchema }
525
+ }));
526
+ }
527
+ function mapToolChoice2(tc) {
528
+ if (tc === void 0) return void 0;
529
+ if (tc === "auto") return "auto";
530
+ if (tc === "none") return "none";
531
+ if (tc === "any") return "required";
532
+ return { type: "function", function: { name: tc.name } };
533
+ }
534
+ function reasoningEffort(e) {
535
+ if (e === "low") return "low";
536
+ if (e === "medium") return "medium";
537
+ return "high";
538
+ }
539
+ function mapResponse(res, req) {
540
+ const choice = res.choices[0];
541
+ const content = [];
542
+ const message = choice?.message;
543
+ if (message?.content) content.push({ type: "text", text: message.content });
544
+ for (const call of message?.tool_calls ?? []) {
545
+ if (call.type !== "function") continue;
546
+ content.push({ type: "tool_use", id: call.id, name: call.function.name, input: parseArgs(call.function.arguments) });
547
+ }
548
+ return {
549
+ id: res.id,
550
+ model: req.model,
551
+ provider: "openai",
552
+ role: "assistant",
553
+ content,
554
+ stopReason: mapFinish(choice?.finish_reason),
555
+ usage: mapUsage2(res.usage)
556
+ };
557
+ }
558
+ function parseArgs(raw) {
559
+ if (!raw) return {};
560
+ try {
561
+ const parsed = JSON.parse(raw);
562
+ return parsed && typeof parsed === "object" ? parsed : {};
563
+ } catch {
564
+ return {};
565
+ }
566
+ }
567
+ function mapFinish(reason) {
568
+ switch (reason) {
569
+ case "stop":
570
+ return "end_turn";
571
+ case "length":
572
+ return "max_tokens";
573
+ case "tool_calls":
574
+ case "function_call":
575
+ return "tool_use";
576
+ case "content_filter":
577
+ return "refusal";
578
+ default:
579
+ return "end_turn";
580
+ }
581
+ }
582
+ function mapUsage2(u) {
583
+ const usage = { inputTokens: u?.prompt_tokens ?? 0, outputTokens: u?.completion_tokens ?? 0 };
584
+ const cached = u?.prompt_tokens_details?.cached_tokens;
585
+ if (cached != null) usage.cacheReadTokens = cached;
586
+ return usage;
587
+ }
588
+ async function* mapStream(stream, req) {
589
+ let started = false;
590
+ let textOpen = false;
591
+ const toolIndex = /* @__PURE__ */ new Map();
592
+ let nextBlockIndex = 1;
593
+ let stopReason = "end_turn";
594
+ const usage = emptyUsage();
595
+ for await (const chunk of stream) {
596
+ if (!started) {
597
+ started = true;
598
+ yield {
599
+ type: "message_start",
600
+ message: { id: chunk.id, model: req.model, provider: "openai", role: "assistant", content: [], usage: emptyUsage() }
601
+ };
602
+ }
603
+ if (chunk.usage) addUsage(usage, mapUsage2(chunk.usage));
604
+ const choice = chunk.choices[0];
605
+ const delta = choice?.delta;
606
+ if (delta?.content) {
607
+ if (!textOpen) {
608
+ textOpen = true;
609
+ yield { type: "content_block_start", index: 0, contentBlock: { type: "text", text: "" } };
610
+ }
611
+ yield { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: delta.content } };
612
+ }
613
+ for (const tc of delta?.tool_calls ?? []) {
614
+ let blockIndex = toolIndex.get(tc.index);
615
+ if (blockIndex === void 0) {
616
+ blockIndex = nextBlockIndex++;
617
+ toolIndex.set(tc.index, blockIndex);
618
+ yield {
619
+ type: "content_block_start",
620
+ index: blockIndex,
621
+ contentBlock: { type: "tool_use", id: tc.id ?? "", name: tc.function?.name ?? "", input: {} }
622
+ };
623
+ }
624
+ if (tc.function?.arguments) {
625
+ yield { type: "content_block_delta", index: blockIndex, delta: { type: "input_json_delta", partialJson: tc.function.arguments } };
626
+ }
627
+ }
628
+ if (choice?.finish_reason) stopReason = mapFinish(choice.finish_reason);
629
+ }
630
+ if (textOpen) yield { type: "content_block_stop", index: 0 };
631
+ for (const blockIndex of toolIndex.values()) yield { type: "content_block_stop", index: blockIndex };
632
+ yield { type: "message_delta", delta: { stopReason }, usage };
633
+ yield { type: "message_stop" };
634
+ }
635
+ var import_openai2, OpenAIProvider;
636
+ var init_openai = __esm({
637
+ "src/providers/openai.ts"() {
638
+ "use strict";
639
+ import_openai2 = __toESM(require("openai"), 1);
640
+ init_errors();
641
+ init_shape();
642
+ OpenAIProvider = class {
643
+ id = "openai";
644
+ client;
645
+ constructor(apiKey, client) {
646
+ this.client = client ?? new import_openai2.default({ apiKey });
647
+ }
648
+ async create(spec, req) {
649
+ try {
650
+ const res = await this.client.chat.completions.create(
651
+ buildParams2(spec, req, false)
652
+ );
653
+ return mapResponse(res, req);
654
+ } catch (err) {
655
+ mapProviderError(err, "openai");
656
+ }
657
+ }
658
+ async *stream(spec, req) {
659
+ try {
660
+ const stream = await this.client.chat.completions.create(
661
+ buildParams2(spec, req, true)
662
+ );
663
+ yield* mapStream(stream, req);
664
+ } catch (err) {
665
+ yield { type: "error", error: normalizeError(err, "openai").toShape() };
666
+ }
667
+ }
668
+ };
669
+ }
670
+ });
671
+
672
+ // src/providers/google.ts
673
+ var google_exports = {};
674
+ __export(google_exports, {
675
+ GoogleProvider: () => GoogleProvider,
676
+ mapResponse: () => mapResponse2,
677
+ toContents: () => toContents
678
+ });
679
+ function buildParams3(spec, req) {
680
+ const config = { maxOutputTokens: req.maxTokens };
681
+ if (req.system !== void 0) {
682
+ config.systemInstruction = typeof req.system === "string" ? req.system : req.system.map((b) => b.text).join("");
683
+ }
684
+ if (req.temperature !== void 0) config.temperature = req.temperature;
685
+ if (req.stopSequences && req.stopSequences.length > 0) config.stopSequences = req.stopSequences;
686
+ const tools = buildTools3(req);
687
+ if (tools) config.tools = tools;
688
+ const toolConfig = mapToolChoice3(req.toolChoice);
689
+ if (toolConfig) config.toolConfig = toolConfig;
690
+ if (req.thinking === "adaptive" && spec.capabilities.thinking) {
691
+ config.thinkingConfig = { thinkingBudget: -1 };
692
+ }
693
+ if (req.responseFormat && spec.capabilities.structuredOutput) {
694
+ config.responseMimeType = "application/json";
695
+ config.responseSchema = req.responseFormat.schema;
696
+ }
697
+ if (req.providerExtras) Object.assign(config, req.providerExtras);
698
+ return { model: spec.nativeId, contents: toContents(req), config };
699
+ }
700
+ function toContents(req) {
701
+ return req.messages.map((m) => ({
702
+ role: m.role === "assistant" ? "model" : "user",
703
+ parts: (typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content).map(toPart)
704
+ }));
705
+ }
706
+ function toPart(b) {
707
+ switch (b.type) {
708
+ case "text":
709
+ return { text: b.text };
710
+ case "image":
711
+ if (b.source.type !== "base64") throw new CapabilityError("Google image input requires base64 data, not a URL.");
712
+ return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };
713
+ case "audio":
714
+ if (b.source.type !== "base64") throw new CapabilityError("Google audio input requires base64 data, not a URL.");
715
+ return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };
716
+ case "document":
717
+ if (b.source.type !== "base64") throw new CapabilityError("Google document input requires base64 data, not a URL.");
718
+ return { inlineData: { mimeType: "application/pdf", data: b.source.data } };
719
+ case "tool_use":
720
+ return { functionCall: { name: b.name, args: b.input } };
721
+ case "tool_result":
722
+ return {
723
+ functionResponse: {
724
+ // Gemini correlates by name; the canonical tool_use id is keyed to it.
725
+ name: b.toolUseId,
726
+ response: { result: typeof b.content === "string" ? b.content : stringifyBlocks2(b.content) }
727
+ }
728
+ };
729
+ case "thinking":
730
+ return { text: "" };
731
+ }
732
+ }
733
+ function stringifyBlocks2(blocks) {
734
+ return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
735
+ }
736
+ function buildTools3(req) {
737
+ const tools = [];
738
+ if (req.tools && req.tools.length > 0) {
739
+ tools.push({
740
+ functionDeclarations: req.tools.map((t) => ({
741
+ name: t.name,
742
+ description: t.description,
743
+ parameters: t.inputSchema
744
+ }))
745
+ });
746
+ }
747
+ if (req.webSearch) tools.push({ googleSearch: {} });
748
+ return tools.length > 0 ? tools : void 0;
749
+ }
750
+ function mapToolChoice3(tc) {
751
+ if (tc === void 0 || tc === "auto") return void 0;
752
+ if (tc === "none") return { functionCallingConfig: { mode: import_genai.FunctionCallingConfigMode.NONE } };
753
+ if (tc === "any") return { functionCallingConfig: { mode: import_genai.FunctionCallingConfigMode.ANY } };
754
+ return { functionCallingConfig: { mode: import_genai.FunctionCallingConfigMode.ANY, allowedFunctionNames: [tc.name] } };
755
+ }
756
+ function mapResponse2(res, req) {
757
+ const candidate = res.candidates?.[0];
758
+ const parts = candidate?.content?.parts ?? [];
759
+ const content = [];
760
+ let sawToolUse = false;
761
+ for (const p of parts) {
762
+ if (p.text) content.push({ type: "text", text: p.text });
763
+ else if (p.functionCall) {
764
+ sawToolUse = true;
765
+ const name = p.functionCall.name ?? "";
766
+ content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: p.functionCall.args ?? {} });
767
+ }
768
+ }
769
+ return {
770
+ id: res.responseId ?? "",
771
+ model: req.model,
772
+ provider: "google",
773
+ role: "assistant",
774
+ content,
775
+ stopReason: sawToolUse ? "tool_use" : mapFinish2(candidate?.finishReason),
776
+ usage: mapUsage3(res)
777
+ };
778
+ }
779
+ function mapFinish2(reason) {
780
+ switch (reason) {
781
+ case "STOP":
782
+ return "end_turn";
783
+ case "MAX_TOKENS":
784
+ return "max_tokens";
785
+ case "SAFETY":
786
+ case "RECITATION":
787
+ case "BLOCKLIST":
788
+ case "PROHIBITED_CONTENT":
789
+ case "SPII":
790
+ return "refusal";
791
+ default:
792
+ return "end_turn";
793
+ }
794
+ }
795
+ function mapUsage3(res) {
796
+ const u = res.usageMetadata;
797
+ const usage = {
798
+ inputTokens: u?.promptTokenCount ?? 0,
799
+ outputTokens: u?.candidatesTokenCount ?? 0
800
+ };
801
+ if (u?.cachedContentTokenCount != null) usage.cacheReadTokens = u.cachedContentTokenCount;
802
+ return usage;
803
+ }
804
+ async function* mapStream2(iter, req) {
805
+ let started = false;
806
+ let textOpen = false;
807
+ let nextBlockIndex = 1;
808
+ let stopReason = "end_turn";
809
+ const usage = emptyUsage();
810
+ for await (const chunk of iter) {
811
+ if (!started) {
812
+ started = true;
813
+ yield {
814
+ type: "message_start",
815
+ message: { id: chunk.responseId ?? "", model: req.model, provider: "google", role: "assistant", content: [], usage: emptyUsage() }
816
+ };
817
+ }
818
+ const candidate = chunk.candidates?.[0];
819
+ for (const p of candidate?.content?.parts ?? []) {
820
+ if (p.text) {
821
+ if (!textOpen) {
822
+ textOpen = true;
823
+ yield { type: "content_block_start", index: 0, contentBlock: { type: "text", text: "" } };
824
+ }
825
+ yield { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: p.text } };
826
+ } else if (p.functionCall) {
827
+ const idx = nextBlockIndex++;
828
+ const name = p.functionCall.name ?? "";
829
+ yield { type: "content_block_start", index: idx, contentBlock: { type: "tool_use", id: p.functionCall.id ?? name, name, input: {} } };
830
+ yield { type: "content_block_delta", index: idx, delta: { type: "input_json_delta", partialJson: JSON.stringify(p.functionCall.args ?? {}) } };
831
+ yield { type: "content_block_stop", index: idx };
832
+ stopReason = "tool_use";
833
+ }
834
+ }
835
+ if (chunk.usageMetadata) {
836
+ const u = mapUsage3(chunk);
837
+ usage.inputTokens = u.inputTokens;
838
+ usage.outputTokens = u.outputTokens;
839
+ if (u.cacheReadTokens != null) usage.cacheReadTokens = u.cacheReadTokens;
840
+ }
841
+ if (candidate?.finishReason && stopReason !== "tool_use") stopReason = mapFinish2(candidate.finishReason);
842
+ }
843
+ if (textOpen) yield { type: "content_block_stop", index: 0 };
844
+ yield { type: "message_delta", delta: { stopReason }, usage };
845
+ yield { type: "message_stop" };
846
+ }
847
+ var import_genai, GoogleProvider;
848
+ var init_google = __esm({
849
+ "src/providers/google.ts"() {
850
+ "use strict";
851
+ import_genai = require("@google/genai");
852
+ init_errors();
853
+ init_shape();
854
+ GoogleProvider = class {
855
+ id = "google";
856
+ client;
857
+ constructor(apiKey, client) {
858
+ this.client = client ?? new import_genai.GoogleGenAI({ apiKey });
859
+ }
860
+ async create(spec, req) {
861
+ try {
862
+ const res = await this.client.models.generateContent(buildParams3(spec, req));
863
+ return mapResponse2(res, req);
864
+ } catch (err) {
865
+ mapProviderError(err, "google");
866
+ }
867
+ }
868
+ async *stream(spec, req) {
869
+ try {
870
+ const iter = await this.client.models.generateContentStream(buildParams3(spec, req));
871
+ yield* mapStream2(iter, req);
872
+ } catch (err) {
873
+ yield { type: "error", error: normalizeError(err, "google").toShape() };
874
+ }
875
+ }
876
+ };
877
+ }
878
+ });
879
+
880
+ // src/index.ts
881
+ var index_exports = {};
882
+ __export(index_exports, {
883
+ AGENT_SCHEMA: () => AGENT_SCHEMA,
884
+ ANTHROPIC_MODELS: () => ANTHROPIC_MODELS,
885
+ AuthError: () => AuthError,
886
+ CapabilityError: () => CapabilityError,
887
+ ConfigError: () => ConfigError,
888
+ ContextWindowError: () => ContextWindowError,
889
+ DEFAULT_CATALOG: () => DEFAULT_CATALOG,
890
+ DEFAULT_SECRET_NAMES: () => DEFAULT_SECRET_NAMES,
891
+ GOOGLE_MODELS: () => GOOGLE_MODELS,
892
+ InMemoryScope: () => InMemoryScope,
893
+ InvalidRequestError: () => InvalidRequestError,
894
+ NS: () => NS,
895
+ OPENAI_MODELS: () => OPENAI_MODELS,
896
+ OdlaAIError: () => OdlaAIError,
897
+ OdlaDbMemory: () => OdlaDbMemory,
898
+ ProviderError: () => ProviderError,
899
+ RateLimitError: () => RateLimitError,
900
+ TaintError: () => TaintError,
901
+ addUsage: () => addUsage,
902
+ assertSinkAcceptsTaint: () => assertSinkAcceptsTaint,
903
+ blocksOf: () => blocksOf,
904
+ buildCatalog: () => buildCatalog,
905
+ chatCapabilities: () => chatCapabilities,
906
+ clearProviderCache: () => clearProviderCache,
907
+ composeSkills: () => composeSkills,
908
+ createApp: () => createApp,
909
+ emptyUsage: () => emptyUsage,
910
+ entityCrudSkill: () => entityCrudSkill,
911
+ evaluate: () => evaluate,
912
+ exactMatch: () => exactMatch,
913
+ extractText: () => extractText,
914
+ extractToolUses: () => extractToolUses,
915
+ generateLlmsTxt: () => generateLlmsTxt,
916
+ getProvider: () => getProvider,
917
+ includes: () => includes,
918
+ init: () => init,
919
+ isAudioBlock: () => isAudioBlock,
920
+ isDocumentBlock: () => isDocumentBlock,
921
+ isImageBlock: () => isImageBlock,
922
+ isTextBlock: () => isTextBlock,
923
+ isThinkingBlock: () => isThinkingBlock,
924
+ isToolResultBlock: () => isToolResultBlock,
925
+ isToolUseBlock: () => isToolUseBlock,
926
+ llmJudge: () => llmJudge,
927
+ looksLikeKey: () => looksLikeKey,
928
+ mintAppKey: () => mintAppKey,
929
+ normalizeError: () => normalizeError,
930
+ odlaDbKeyResolver: () => odlaDbKeyResolver,
931
+ persistRun: () => persistRun,
932
+ providersInCatalog: () => providersInCatalog,
933
+ provisionAgentApp: () => provisionAgentApp,
934
+ pushAgentSchema: () => pushAgentSchema,
935
+ putSecret: () => putSecret,
936
+ queryRuns: () => queryRuns,
937
+ redact: () => redact,
938
+ registerProvider: () => registerProvider,
939
+ resolveModel: () => resolveModel,
940
+ runAgent: () => runAgent,
941
+ structuredMatch: () => structuredMatch,
942
+ taint: () => taint,
943
+ toOracleTool: () => toOracleTool,
944
+ validateRequest: () => validateRequest
945
+ });
946
+ module.exports = __toCommonJS(index_exports);
947
+
948
+ // src/client/init.ts
949
+ init_shape();
950
+
951
+ // src/shape/capabilities.ts
952
+ init_shape();
953
+ function chatCapabilities(overrides = {}) {
954
+ return {
955
+ kind: "chat",
956
+ textIn: true,
957
+ imageIn: true,
958
+ audioIn: false,
959
+ documentIn: false,
960
+ toolUse: true,
961
+ thinking: false,
962
+ effort: false,
963
+ streaming: true,
964
+ structuredOutput: true,
965
+ webSearch: false,
966
+ ...overrides
967
+ };
968
+ }
969
+ function validateRequest(spec, req) {
970
+ const caps = spec.capabilities;
971
+ const fail = (what) => {
972
+ throw new CapabilityError(
973
+ `Model "${spec.id}" (${spec.provider}) does not support ${what}.`
974
+ );
975
+ };
976
+ if (caps.kind !== "chat") fail(`chat requests (it is a "${caps.kind}" model)`);
977
+ for (const msg of req.messages) {
978
+ for (const block of blocksOf(msg.content)) {
979
+ if (block.type === "image" && !caps.imageIn) fail("image input");
980
+ if (block.type === "audio" && !caps.audioIn) fail("audio input");
981
+ if (block.type === "document" && !caps.documentIn) fail("document (PDF) input");
982
+ }
983
+ }
984
+ if (req.tools && req.tools.length > 0 && !caps.toolUse) fail("tool use");
985
+ if (req.webSearch && !caps.webSearch) fail("web search");
986
+ if (req.responseFormat && !caps.structuredOutput) fail("structured output");
987
+ if (req.effort && !caps.effort) fail("the effort parameter");
988
+ if (req.thinking === "adaptive" && !caps.thinking) fail("thinking");
989
+ }
990
+
991
+ // src/catalog/index.ts
992
+ init_shape();
993
+
994
+ // src/catalog/anthropic.ts
995
+ var FRONTIER = () => chatCapabilities({
996
+ documentIn: true,
997
+ // PDF
998
+ thinking: true,
999
+ // adaptive
1000
+ effort: true,
1001
+ // output_config.effort
1002
+ webSearch: true,
1003
+ // web_search_20260209
1004
+ audioIn: false
1005
+ // Claude has no audio input
1006
+ });
1007
+ var ANTHROPIC_MODELS = [
1008
+ {
1009
+ id: "claude-opus-4-8",
1010
+ provider: "anthropic",
1011
+ nativeId: "claude-opus-4-8",
1012
+ capabilities: FRONTIER(),
1013
+ contextWindow: 1e6,
1014
+ maxOutput: 128e3
1015
+ },
1016
+ {
1017
+ id: "claude-sonnet-5",
1018
+ provider: "anthropic",
1019
+ nativeId: "claude-sonnet-5",
1020
+ capabilities: FRONTIER(),
1021
+ contextWindow: 1e6,
1022
+ maxOutput: 128e3
1023
+ },
1024
+ {
1025
+ id: "claude-fable-5",
1026
+ provider: "anthropic",
1027
+ nativeId: "claude-fable-5",
1028
+ capabilities: FRONTIER(),
1029
+ contextWindow: 1e6,
1030
+ maxOutput: 128e3
1031
+ },
1032
+ {
1033
+ // Haiku 4.5: no effort param (errors), web search needs Sonnet-4.6+, so both off.
1034
+ id: "claude-haiku-4-5",
1035
+ provider: "anthropic",
1036
+ nativeId: "claude-haiku-4-5",
1037
+ capabilities: chatCapabilities({
1038
+ documentIn: true,
1039
+ thinking: false,
1040
+ effort: false,
1041
+ webSearch: false,
1042
+ audioIn: false
1043
+ }),
1044
+ contextWindow: 2e5,
1045
+ maxOutput: 64e3
1046
+ }
1047
+ ];
1048
+
1049
+ // src/catalog/openai.ts
1050
+ var OPENAI_MODELS = [
1051
+ {
1052
+ id: "gpt-5.5",
1053
+ provider: "openai",
1054
+ nativeId: "gpt-5.5",
1055
+ capabilities: chatCapabilities({ effort: true }),
1056
+ contextWindow: 4e5
1057
+ },
1058
+ {
1059
+ id: "gpt-5",
1060
+ provider: "openai",
1061
+ nativeId: "gpt-5",
1062
+ capabilities: chatCapabilities({ effort: true }),
1063
+ contextWindow: 4e5
1064
+ },
1065
+ {
1066
+ id: "gpt-5-mini",
1067
+ provider: "openai",
1068
+ nativeId: "gpt-5-mini",
1069
+ capabilities: chatCapabilities({ effort: true }),
1070
+ contextWindow: 4e5
1071
+ },
1072
+ {
1073
+ // Non-reasoning multimodal chat (image input, no effort knob).
1074
+ id: "gpt-4o",
1075
+ provider: "openai",
1076
+ nativeId: "gpt-4o",
1077
+ capabilities: chatCapabilities(),
1078
+ contextWindow: 128e3
1079
+ },
1080
+ {
1081
+ // Audio-input model line — accepts audio, not images.
1082
+ id: "gpt-audio",
1083
+ provider: "openai",
1084
+ nativeId: "gpt-audio",
1085
+ capabilities: chatCapabilities({ imageIn: false, audioIn: true }),
1086
+ contextWindow: 128e3
1087
+ }
1088
+ ];
1089
+
1090
+ // src/catalog/google.ts
1091
+ var GEMINI = () => chatCapabilities({
1092
+ audioIn: true,
1093
+ documentIn: true,
1094
+ thinking: true,
1095
+ effort: true,
1096
+ // mapped onto thinkingConfig
1097
+ webSearch: true
1098
+ // googleSearch grounding
1099
+ });
1100
+ var GOOGLE_MODELS = [
1101
+ {
1102
+ id: "gemini-2.5-pro",
1103
+ provider: "google",
1104
+ nativeId: "gemini-2.5-pro",
1105
+ capabilities: GEMINI(),
1106
+ contextWindow: 1e6
1107
+ },
1108
+ {
1109
+ id: "gemini-2.5-flash",
1110
+ provider: "google",
1111
+ nativeId: "gemini-2.5-flash",
1112
+ capabilities: GEMINI(),
1113
+ contextWindow: 1e6
1114
+ },
1115
+ {
1116
+ id: "gemini-2.0-flash",
1117
+ provider: "google",
1118
+ nativeId: "gemini-2.0-flash",
1119
+ capabilities: chatCapabilities({
1120
+ audioIn: true,
1121
+ documentIn: true,
1122
+ thinking: false,
1123
+ effort: false,
1124
+ webSearch: true
1125
+ }),
1126
+ contextWindow: 1e6
1127
+ }
1128
+ ];
1129
+
1130
+ // src/catalog/index.ts
1131
+ function index(specs) {
1132
+ const out = {};
1133
+ for (const spec of specs) out[spec.id] = spec;
1134
+ return out;
1135
+ }
1136
+ var DEFAULT_CATALOG = index([
1137
+ ...ANTHROPIC_MODELS,
1138
+ ...OPENAI_MODELS,
1139
+ ...GOOGLE_MODELS
1140
+ ]);
1141
+ function buildCatalog(base, extra = []) {
1142
+ return { ...base, ...index(extra) };
1143
+ }
1144
+ function resolveModel(catalog, id) {
1145
+ const spec = catalog[id];
1146
+ if (!spec) {
1147
+ const known = Object.keys(catalog).sort().join(", ");
1148
+ throw new ConfigError(`Unknown model "${id}". Known models: ${known || "(none)"}.`);
1149
+ }
1150
+ return spec;
1151
+ }
1152
+ function providersInCatalog(catalog) {
1153
+ const set = /* @__PURE__ */ new Set();
1154
+ for (const spec of Object.values(catalog)) set.add(spec.provider);
1155
+ return [...set];
1156
+ }
1157
+
1158
+ // src/providers/registry.ts
1159
+ var cache = /* @__PURE__ */ new Map();
1160
+ async function getProvider(id, apiKey) {
1161
+ const cacheKey = `${id}:${apiKey}`;
1162
+ const existing = cache.get(cacheKey);
1163
+ if (existing) return existing;
1164
+ let provider;
1165
+ switch (id) {
1166
+ case "anthropic": {
1167
+ const { AnthropicProvider: AnthropicProvider2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
1168
+ provider = new AnthropicProvider2(apiKey);
1169
+ break;
1170
+ }
1171
+ case "openai": {
1172
+ const { OpenAIProvider: OpenAIProvider2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
1173
+ provider = new OpenAIProvider2(apiKey);
1174
+ break;
1175
+ }
1176
+ case "google": {
1177
+ const { GoogleProvider: GoogleProvider2 } = await Promise.resolve().then(() => (init_google(), google_exports));
1178
+ provider = new GoogleProvider2(apiKey);
1179
+ break;
1180
+ }
1181
+ }
1182
+ cache.set(cacheKey, provider);
1183
+ return provider;
1184
+ }
1185
+ function registerProvider(id, apiKey, provider) {
1186
+ cache.set(`${id}:${apiKey}`, provider);
1187
+ }
1188
+ function clearProviderCache() {
1189
+ cache.clear();
1190
+ }
1191
+
1192
+ // src/client/keys.ts
1193
+ init_shape();
1194
+ async function resolveApiKey(provider, opts, forModel) {
1195
+ let key;
1196
+ if (opts.resolveKey) key = await opts.resolveKey(provider);
1197
+ if (!key) key = opts.keys?.[provider];
1198
+ if (!key) {
1199
+ throw new ConfigError(
1200
+ `No API key configured for provider "${provider}" (required by model "${forModel}"). Pass keys: { ${provider}: "..." } or a resolveKey() to init().`
1201
+ );
1202
+ }
1203
+ return key;
1204
+ }
1205
+ function looksLikeKey(provider, key) {
1206
+ switch (provider) {
1207
+ case "anthropic":
1208
+ return /^sk-ant-[a-zA-Z0-9_-]{20,}/.test(key);
1209
+ case "openai":
1210
+ return /^sk-(?!ant-)(?:proj-)?[a-zA-Z0-9_-]{20,}/.test(key);
1211
+ case "google":
1212
+ return /^AIza[0-9A-Za-z_-]{35}$/.test(key) || key.length >= 20;
1213
+ }
1214
+ }
1215
+ function redact(text) {
1216
+ return text.replace(/sk-ant-[a-zA-Z0-9_-]{6,}/g, "sk-ant-\u2026").replace(/sk-(?!ant-)(?:proj-)?[a-zA-Z0-9_-]{10,}/g, "sk-\u2026").replace(/\bAIza[0-9A-Za-z_-]{6,}/g, "AIza\u2026");
1217
+ }
1218
+
1219
+ // src/client/init.ts
1220
+ function init(opts = {}) {
1221
+ const catalog = opts.catalog ?? DEFAULT_CATALOG;
1222
+ const pickModel = (model) => {
1223
+ const chosen = model ?? opts.defaultModel;
1224
+ if (!chosen) throw new ConfigError("No model specified and no defaultModel set in init().");
1225
+ return chosen;
1226
+ };
1227
+ const resolveProviderFor = async (model) => {
1228
+ const spec = resolveModel(catalog, model);
1229
+ const injected = opts.providers?.[spec.provider];
1230
+ if (injected) return { spec, provider: injected };
1231
+ const key = await resolveApiKey(spec.provider, opts, model);
1232
+ const provider = await getProvider(spec.provider, key);
1233
+ return { spec, provider };
1234
+ };
1235
+ const chat = async (input) => {
1236
+ const model = pickModel(input.model);
1237
+ const req = { ...input, model };
1238
+ const { spec, provider } = await resolveProviderFor(model);
1239
+ validateRequest(spec, req);
1240
+ return provider.create(spec, req);
1241
+ };
1242
+ async function* stream(input) {
1243
+ const model = pickModel(input.model);
1244
+ const req = { ...input, model };
1245
+ const { spec, provider } = await resolveProviderFor(model);
1246
+ if (!spec.capabilities.streaming) {
1247
+ throw new CapabilityError(`Model "${model}" (${spec.provider}) does not support streaming.`);
1248
+ }
1249
+ validateRequest(spec, req);
1250
+ yield* provider.stream(spec, req);
1251
+ }
1252
+ const extract = async (c) => {
1253
+ const model = pickModel(c.model);
1254
+ const req = {
1255
+ model,
1256
+ system: c.system,
1257
+ messages: [{ role: "user", content: c.user }],
1258
+ tools: [{ name: c.tool.name, description: c.tool.description ?? "", inputSchema: c.tool.parameters }],
1259
+ toolChoice: { type: "tool", name: c.tool.name },
1260
+ maxTokens: c.maxTokens ?? 4096
1261
+ };
1262
+ const { spec, provider } = await resolveProviderFor(model);
1263
+ validateRequest(spec, req);
1264
+ const res = await provider.create(spec, req);
1265
+ const block = res.content.find((b) => b.type === "tool_use" && b.name === c.tool.name);
1266
+ if (!block || block.type !== "tool_use") {
1267
+ throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`);
1268
+ }
1269
+ return { value: block.input, usage: res.usage };
1270
+ };
1271
+ const search = async (c) => {
1272
+ const model = pickModel(c.model);
1273
+ const req = {
1274
+ model,
1275
+ system: c.system,
1276
+ messages: [{ role: "user", content: c.user }],
1277
+ webSearch: true,
1278
+ maxTokens: c.maxTokens ?? 8192
1279
+ };
1280
+ const { spec, provider } = await resolveProviderFor(model);
1281
+ validateRequest(spec, req);
1282
+ const res = await provider.create(spec, req);
1283
+ return { text: extractText(res.content), usage: res.usage };
1284
+ };
1285
+ return { catalog, chat, stream, extract, search };
1286
+ }
1287
+
1288
+ // src/index.ts
1289
+ init_shape();
1290
+
1291
+ // src/doc/llms.ts
1292
+ function capsSummary(c) {
1293
+ const flags = ["text"];
1294
+ if (c.imageIn) flags.push("image");
1295
+ if (c.audioIn) flags.push("audio");
1296
+ if (c.toolUse) flags.push("tools");
1297
+ if (c.thinking) flags.push("thinking");
1298
+ if (c.effort) flags.push("effort");
1299
+ if (c.webSearch) flags.push("web-search");
1300
+ if (c.structuredOutput) flags.push("structured");
1301
+ return flags.join(", ");
1302
+ }
1303
+ function generateLlmsTxt(catalog = DEFAULT_CATALOG) {
1304
+ const models = Object.values(catalog).map((s) => `- \`${s.id}\` (${s.provider}) \u2014 ${capsSummary(s.capabilities)}`).join("\n");
1305
+ return `# @odla-ai/ai \u2014 LLM context
1306
+
1307
+ One general-purpose interface for AI inference across Anthropic (Claude), OpenAI
1308
+ (GPT), and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent
1309
+ engine and an eval harness. Library-only: import in-process, bring your own keys.
1310
+ Isomorphic (Node 20+, Cloudflare Workers, browser). Provider SDKs are lazy-loaded.
1311
+
1312
+ ## Install
1313
+
1314
+ npm i @odla-ai/ai
1315
+ # provider SDKs are peer-lazy-loaded; install those you use:
1316
+ # @anthropic-ai/sdk openai @google/genai
1317
+ # optional, for odla-db-backed storage + secrets:
1318
+ # @odla-ai/db
1319
+
1320
+ ## Core call
1321
+
1322
+ import { init } from "@odla-ai/ai";
1323
+ const ai = init({ keys: { anthropic: KEY, openai: KEY, google: KEY }, defaultModel: "claude-opus-4-8" });
1324
+ const res = await ai.chat({ model: "gpt-5", messages: [{ role: "user", content: "hi" }], maxTokens: 256 });
1325
+ // res.content: OracleContentBlock[] (text/tool_use/thinking)
1326
+
1327
+ - \`ai.stream(input)\` \u2192 async iterable of normalized events: message_start \u2192
1328
+ content_block_start \u2192 content_block_delta {text_delta|thinking_delta|input_json_delta}
1329
+ \u2192 content_block_stop \u2192 message_delta \u2192 message_stop (plus \`error\`).
1330
+ - \`ai.extract<T>({ model, user, tool })\` \u2192 forced tool call; returns a parsed object.
1331
+ - \`ai.search({ model, user })\` \u2192 web-search-grounded prose.
1332
+
1333
+ ## Content blocks (multimodal)
1334
+
1335
+ text; image { source: base64|url }; audio { source: base64|url }; document (PDF);
1336
+ tool_use { id, name, input }; tool_result { toolUseId, content }; thinking.
1337
+ Audio input is accepted by OpenAI (\`gpt-audio\`) and Google, NOT by Claude \u2014 an
1338
+ audio block to a Claude model throws CapabilityError before any network call.
1339
+
1340
+ ## Agents
1341
+
1342
+ import { runAgent, type Persona, type ToolDef } from "@odla-ai/ai";
1343
+ const add: ToolDef = { name: "add", description: "add", inputSchema: {...}, handler: (i) => ({ content: String(i.a + i.b) }) };
1344
+ const persona: Persona = { name: "calc", model: "claude-opus-4-8", system: "...", tools: [add], maxSteps: 4 };
1345
+ const run = await runAgent(ai, persona, { input: "what is 21+21?" });
1346
+ // run.finalText, run.toolCalls, run.usage, run.steps, run.stoppedReason
1347
+
1348
+ Tools may carry taint labels (outputTaint / acceptsTaint) for a CaMeL-style
1349
+ prompt-injection gate. Personas may carry a MemoryScope.
1350
+
1351
+ ## Skills
1352
+
1353
+ A Skill = { name, instructions?, tools: ToolDef[] } is a bundle you attach to a
1354
+ persona (persona.skills); its tools merge into the model's tool list and its
1355
+ instructions into the system prompt. Turn an odla-db entity into CRUD tools:
1356
+
1357
+ import { entityCrudSkill } from "@odla-ai/ai";
1358
+ const todos = entityCrudSkill({ db, entity: "todos", fields: { text: { type: "string" }, done: { type: "boolean" } }, required: ["text"] });
1359
+ // \u2192 tools: list_todos, create_todo, update_todo (partial), delete_todo \u2014 handlers write to odla-db
1360
+ const run = await runAgent(ai, { name: "todo-bot", model: "gpt-5", skills: [todos] }, { input: "add buy milk" });
1361
+
1362
+ ## Evals
1363
+
1364
+ import { evaluate, exactMatch, llmJudge } from "@odla-ai/ai";
1365
+ const report = await evaluate({ inference: ai, model: "claude-opus-4-8", grader: exactMatch(), cases: [{ input, expected }] });
1366
+ // graders: exactMatch, includes, structuredMatch, llmJudge; pass a persona to eval an agent.
1367
+
1368
+ ## Storage + BYO keys via odla-db (@odla-ai/db) \u2014 the handshake
1369
+
1370
+ Follows odla-db's model. An agent never takes a platform secret; it does a
1371
+ device-authorization handshake for a scoped, revocable token, then provisions.
1372
+
1373
+ import { requestToken, init as odlaInit } from "@odla-ai/db";
1374
+ import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, init } from "@odla-ai/ai";
1375
+
1376
+ // 1. Handshake: the human supplies the endpoint and approves a short code in odla-db Studio.
1377
+ const { token } = await requestToken({ endpoint: ODLA_URL, onCode: ({ userCode }) => show(userCode) });
1378
+
1379
+ // 2. Provision: create app, mint app key, push agent schema, store BYO provider keys as secrets.
1380
+ const { appId, appKey } = await provisionAgentApp({ endpoint: ODLA_URL, token, providerKeys: { openai: OPENAI_KEY } });
1381
+
1382
+ // 3. Runtime: read keys from odla-db secrets; persist memory + run traces.
1383
+ const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_URL });
1384
+ const ai = init({ resolveKey: odlaDbKeyResolver(db) });
1385
+ const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
1386
+ const run = await runAgent(ai, persona, { input: "hello" });
1387
+ await persistRun(run, { db, sessionId: "user-42" });
1388
+
1389
+ Secrets are read-only from the app key (odlaDbKeyResolver \u2192 db.secrets.get); they
1390
+ are WRITTEN only with the operator/dev token via provisionAgentApp/putSecret. Keys
1391
+ are AES-GCM-encrypted at rest in odla-db and never returned to browser clients.
1392
+
1393
+ ## Errors
1394
+
1395
+ Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
1396
+ config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
1397
+ tool_input_invalid, provider_error.
1398
+
1399
+ ## Models
1400
+
1401
+ ${models}
1402
+
1403
+ Model ids live in a data catalog and drift \u2014 verify against provider docs; extend
1404
+ via buildCatalog / init({ catalog }).
1405
+ `;
1406
+ }
1407
+
1408
+ // src/index.ts
1409
+ init_errors();
1410
+
1411
+ // src/agent/loop.ts
1412
+ init_shape();
1413
+
1414
+ // src/agent/tools.ts
1415
+ function toOracleTool(t) {
1416
+ return { name: t.name, description: t.description, inputSchema: t.inputSchema };
1417
+ }
1418
+
1419
+ // src/agent/skill.ts
1420
+ function composeSkills(system, tools, skills) {
1421
+ const composedTools = [...tools ?? []];
1422
+ const sections = system ? [system] : [];
1423
+ for (const skill of skills ?? []) {
1424
+ composedTools.push(...skill.tools);
1425
+ if (skill.instructions) sections.push(`## ${skill.name}
1426
+ ${skill.instructions}`);
1427
+ }
1428
+ return { system: sections.length > 0 ? sections.join("\n\n") : void 0, tools: composedTools };
1429
+ }
1430
+
1431
+ // src/agent/taint.ts
1432
+ init_shape();
1433
+ function taint(...labels) {
1434
+ return new Set(labels);
1435
+ }
1436
+ var TaintError = class extends OdlaAIError {
1437
+ constructor(message) {
1438
+ super("invalid_request", message);
1439
+ }
1440
+ };
1441
+ function assertSinkAcceptsTaint(sink, allowed, actual) {
1442
+ const allow = new Set(allowed);
1443
+ for (const label of actual) {
1444
+ if (!allow.has(label)) {
1445
+ throw new TaintError(
1446
+ `Tool "${sink}" refused: conversation carries taint "${label}" which is not in its allowlist [${allowed.join(", ")}].`
1447
+ );
1448
+ }
1449
+ }
1450
+ }
1451
+
1452
+ // src/agent/loop.ts
1453
+ async function runAgent(inference, persona, input) {
1454
+ const { system, tools } = composeSkills(persona.system, persona.tools, persona.skills);
1455
+ const handlerByName = /* @__PURE__ */ new Map();
1456
+ for (const t of tools) if (t.handler) handlerByName.set(t.name, t);
1457
+ const priorFromMemory = persona.memory ? await persona.memory.load() : [];
1458
+ const startingInput = input.messages ? input.messages : input.input !== void 0 ? [{ role: "user", content: input.input }] : [];
1459
+ const messages = [...priorFromMemory, ...startingInput];
1460
+ const runStartIndex = messages.length - startingInput.length;
1461
+ const usage = emptyUsage();
1462
+ const toolCalls = [];
1463
+ const accumulatedTaint = /* @__PURE__ */ new Set(["llm_inherited"]);
1464
+ const maxSteps = persona.maxSteps ?? 8;
1465
+ let response;
1466
+ let stoppedReason = "max_steps";
1467
+ let steps = 0;
1468
+ while (steps < maxSteps) {
1469
+ steps++;
1470
+ response = await inference.chat({
1471
+ model: persona.model,
1472
+ system,
1473
+ messages,
1474
+ tools: tools.length > 0 ? tools.map(toOracleTool) : void 0,
1475
+ maxTokens: persona.maxTokens ?? 4096,
1476
+ effort: persona.effort,
1477
+ thinking: persona.thinking,
1478
+ temperature: persona.temperature,
1479
+ webSearch: persona.webSearch
1480
+ });
1481
+ addUsage(usage, response.usage);
1482
+ messages.push({ role: "assistant", content: response.content });
1483
+ if (response.stopReason === "refusal") {
1484
+ stoppedReason = "refusal";
1485
+ break;
1486
+ }
1487
+ const toolUses = extractToolUses(response.content);
1488
+ if (toolUses.length === 0) {
1489
+ stoppedReason = "end_turn";
1490
+ break;
1491
+ }
1492
+ const resultBlocks = [];
1493
+ for (const toolUse of toolUses) {
1494
+ const def = handlerByName.get(toolUse.name);
1495
+ if (!def || !def.handler) {
1496
+ resultBlocks.push(errorResult(toolUse.id, `No local handler for tool "${toolUse.name}".`));
1497
+ continue;
1498
+ }
1499
+ if (def.acceptsTaint) assertSinkAcceptsTaint(def.name, def.acceptsTaint, accumulatedTaint);
1500
+ let output;
1501
+ try {
1502
+ output = await def.handler(toolUse.input, { taint: new Set(accumulatedTaint), signal: input.signal });
1503
+ } catch (err) {
1504
+ output = { content: `Tool "${toolUse.name}" threw: ${err.message}`, isError: true };
1505
+ }
1506
+ toolCalls.push({ toolUse, output });
1507
+ resultBlocks.push({ type: "tool_result", toolUseId: toolUse.id, content: output.content, isError: output.isError });
1508
+ for (const label of def.outputTaint ?? []) accumulatedTaint.add(label);
1509
+ }
1510
+ messages.push({ role: "user", content: resultBlocks });
1511
+ }
1512
+ if (!response) throw new Error("runAgent: maxSteps must be at least 1");
1513
+ if (persona.memory) await persona.memory.append(messages.slice(runStartIndex));
1514
+ return {
1515
+ finalText: extractText(response.content),
1516
+ response,
1517
+ messages,
1518
+ usage,
1519
+ steps,
1520
+ toolCalls,
1521
+ stoppedReason
1522
+ };
1523
+ }
1524
+ function errorResult(toolUseId, message) {
1525
+ return { type: "tool_result", toolUseId, content: message, isError: true };
1526
+ }
1527
+
1528
+ // src/agent/memory.ts
1529
+ var InMemoryScope = class {
1530
+ messages = [];
1531
+ load() {
1532
+ return [...this.messages];
1533
+ }
1534
+ append(messages) {
1535
+ this.messages.push(...messages);
1536
+ }
1537
+ clear() {
1538
+ this.messages = [];
1539
+ }
1540
+ };
1541
+
1542
+ // src/eval/harness.ts
1543
+ init_shape();
1544
+ async function evaluate(opts) {
1545
+ if (!opts.persona && !opts.model) {
1546
+ throw new Error("evaluate() requires either a persona or a model.");
1547
+ }
1548
+ const results = await runPool(opts.cases, opts.concurrency ?? 5, (c) => runCase(opts, c));
1549
+ const usage = emptyUsage();
1550
+ let passed = 0;
1551
+ for (const r of results) {
1552
+ addUsage(usage, r.usage);
1553
+ if (r.grade.pass) passed++;
1554
+ }
1555
+ const total = results.length;
1556
+ return { total, passed, failed: total - passed, passRate: total ? passed / total : 0, results, usage };
1557
+ }
1558
+ async function runCase(opts, c) {
1559
+ const messages = toMessages(c.input);
1560
+ let output;
1561
+ let response;
1562
+ let usage;
1563
+ if (opts.persona) {
1564
+ const run = await runAgent(opts.inference, opts.persona, { messages });
1565
+ output = run.finalText;
1566
+ response = run.response;
1567
+ usage = run.usage;
1568
+ } else {
1569
+ response = await opts.inference.chat({
1570
+ model: opts.model,
1571
+ system: opts.system,
1572
+ messages,
1573
+ maxTokens: opts.maxTokens ?? 1024
1574
+ });
1575
+ output = extractText(response.content);
1576
+ usage = response.usage;
1577
+ }
1578
+ const grade = await opts.grader({ case: c, output, response });
1579
+ return { case: c, output, grade, response, usage };
1580
+ }
1581
+ function toMessages(input) {
1582
+ if (Array.isArray(input) && input.length > 0 && "role" in input[0]) {
1583
+ return input;
1584
+ }
1585
+ return [{ role: "user", content: input }];
1586
+ }
1587
+ async function runPool(items, limit, fn) {
1588
+ const results = new Array(items.length);
1589
+ let next = 0;
1590
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
1591
+ for (; ; ) {
1592
+ const i = next++;
1593
+ if (i >= items.length) return;
1594
+ results[i] = await fn(items[i]);
1595
+ }
1596
+ });
1597
+ await Promise.all(workers);
1598
+ return results;
1599
+ }
1600
+
1601
+ // src/eval/graders.ts
1602
+ init_shape();
1603
+ function exactMatch() {
1604
+ return ({ case: c, output }) => {
1605
+ const expected = String(c.expected ?? "").trim();
1606
+ const pass = output.trim() === expected;
1607
+ return { pass, reason: pass ? "exact match" : `expected "${expected}", got "${output.trim()}"` };
1608
+ };
1609
+ }
1610
+ function includes(opts = {}) {
1611
+ const ci = opts.caseInsensitive ?? true;
1612
+ return ({ case: c, output }) => {
1613
+ const needle = String(c.expected ?? "");
1614
+ const hay = ci ? output.toLowerCase() : output;
1615
+ const pass = hay.includes(ci ? needle.toLowerCase() : needle);
1616
+ return { pass, reason: pass ? `contains "${needle}"` : `missing "${needle}"` };
1617
+ };
1618
+ }
1619
+ function structuredMatch() {
1620
+ return ({ case: c, output }) => {
1621
+ let actual;
1622
+ try {
1623
+ actual = JSON.parse(stripFences(output));
1624
+ } catch {
1625
+ return { pass: false, reason: "output is not valid JSON" };
1626
+ }
1627
+ const ok = subset(c.expected, actual);
1628
+ return { pass: ok, reason: ok ? "structural subset match" : "output is missing expected fields/values" };
1629
+ };
1630
+ }
1631
+ function llmJudge(opts) {
1632
+ return async ({ case: c, output }) => {
1633
+ const rubric = typeof c.expected === "string" ? c.expected : JSON.stringify(c.expected);
1634
+ const system = 'You are a strict evaluation judge. Decide whether the CANDIDATE output satisfies the RUBRIC. Respond with ONLY a JSON object: {"pass": boolean, "reason": string}. No prose, no code fences.' + (opts.extraGuidance ? ` ${opts.extraGuidance}` : "");
1635
+ const user = `RUBRIC:
1636
+ ${rubric}
1637
+
1638
+ CANDIDATE:
1639
+ ${output}`;
1640
+ const res = await opts.inference.chat({
1641
+ model: opts.model,
1642
+ system,
1643
+ messages: [{ role: "user", content: user }],
1644
+ maxTokens: 512
1645
+ });
1646
+ const text = extractText(res.content);
1647
+ try {
1648
+ const verdict = JSON.parse(stripFences(text));
1649
+ return { pass: Boolean(verdict.pass), reason: typeof verdict.reason === "string" ? verdict.reason : void 0 };
1650
+ } catch {
1651
+ const pass = /\bpass(ed)?\b|\btrue\b|\byes\b/i.test(text) && !/\bfail(ed)?\b|\bfalse\b|\bno\b/i.test(text);
1652
+ return { pass, reason: `unparseable judge output: ${text.slice(0, 200)}` };
1653
+ }
1654
+ };
1655
+ }
1656
+ function stripFences(s) {
1657
+ return s.replace(/^\s*```(?:json)?\s*/i, "").replace(/\s*```\s*$/i, "").trim();
1658
+ }
1659
+ function subset(expected, actual) {
1660
+ if (expected === null || typeof expected !== "object") return expected === actual;
1661
+ if (Array.isArray(expected)) {
1662
+ if (!Array.isArray(actual) || actual.length < expected.length) return false;
1663
+ return expected.every((e, i) => subset(e, actual[i]));
1664
+ }
1665
+ if (actual === null || typeof actual !== "object" || Array.isArray(actual)) return false;
1666
+ const a = actual;
1667
+ return Object.entries(expected).every(([k, v]) => subset(v, a[k]));
1668
+ }
1669
+
1670
+ // src/store/schema.ts
1671
+ var NS = {
1672
+ conversation: "agent_conversation",
1673
+ message: "agent_message",
1674
+ run: "agent_run"
1675
+ };
1676
+ var AGENT_SCHEMA = {
1677
+ entities: {
1678
+ agent_conversation: {
1679
+ attrs: {
1680
+ key: { type: "string", unique: true, indexed: true, optional: false },
1681
+ sessionId: { type: "string", unique: false, indexed: true, optional: false },
1682
+ title: { type: "string", unique: false, indexed: false, optional: true },
1683
+ createdAt: { type: "date", unique: false, indexed: true, optional: false },
1684
+ updatedAt: { type: "date", unique: false, indexed: true, optional: false }
1685
+ }
1686
+ },
1687
+ agent_message: {
1688
+ attrs: {
1689
+ key: { type: "string", unique: true, indexed: true, optional: false },
1690
+ sessionId: { type: "string", unique: false, indexed: true, optional: false },
1691
+ seq: { type: "number", unique: false, indexed: true, optional: false },
1692
+ role: { type: "string", unique: false, indexed: false, optional: false },
1693
+ content: { type: "string", unique: false, indexed: false, optional: false },
1694
+ createdAt: { type: "date", unique: false, indexed: true, optional: false }
1695
+ }
1696
+ },
1697
+ agent_run: {
1698
+ attrs: {
1699
+ key: { type: "string", unique: true, indexed: true, optional: false },
1700
+ sessionId: { type: "string", unique: false, indexed: true, optional: false },
1701
+ persona: { type: "string", unique: false, indexed: false, optional: true },
1702
+ status: { type: "string", unique: false, indexed: true, optional: false },
1703
+ steps: { type: "number", unique: false, indexed: false, optional: false },
1704
+ inputTokens: { type: "number", unique: false, indexed: false, optional: false },
1705
+ outputTokens: { type: "number", unique: false, indexed: false, optional: false },
1706
+ finalText: { type: "string", unique: false, indexed: false, optional: true },
1707
+ trace: { type: "json", unique: false, indexed: false, optional: false },
1708
+ createdAt: { type: "date", unique: false, indexed: true, optional: false }
1709
+ }
1710
+ }
1711
+ },
1712
+ links: {}
1713
+ };
1714
+
1715
+ // src/store/memory.ts
1716
+ var OdlaDbMemory = class {
1717
+ db;
1718
+ sessionId;
1719
+ title;
1720
+ msgNs;
1721
+ convNs;
1722
+ loadedCount = 0;
1723
+ constructor(opts) {
1724
+ this.db = opts.db;
1725
+ this.sessionId = opts.sessionId;
1726
+ this.title = opts.title;
1727
+ this.msgNs = opts.namespaces?.message ?? NS.message;
1728
+ this.convNs = opts.namespaces?.conversation ?? NS.conversation;
1729
+ }
1730
+ async load() {
1731
+ const res = await this.db.query({
1732
+ [this.msgNs]: { $: { where: { sessionId: this.sessionId }, order: { seq: "asc" }, limit: 1e5 } }
1733
+ });
1734
+ const rows = res[this.msgNs] ?? [];
1735
+ this.loadedCount = rows.length;
1736
+ return rows.map((r) => ({ role: r.role ?? "user", content: parseContent(r.content) }));
1737
+ }
1738
+ async append(messages) {
1739
+ if (messages.length === 0) return;
1740
+ const base = this.loadedCount;
1741
+ const now = Date.now();
1742
+ const ops = [
1743
+ {
1744
+ t: "update",
1745
+ ns: this.convNs,
1746
+ id: { ns: this.convNs, attr: "key", value: this.sessionId },
1747
+ attrs: {
1748
+ key: this.sessionId,
1749
+ sessionId: this.sessionId,
1750
+ ...this.title ? { title: this.title } : {},
1751
+ ...base === 0 ? { createdAt: now } : {},
1752
+ updatedAt: now
1753
+ }
1754
+ }
1755
+ ];
1756
+ messages.forEach((m, i) => {
1757
+ const seq = base + i;
1758
+ const key = `${this.sessionId}:${seq}`;
1759
+ ops.push({
1760
+ t: "update",
1761
+ ns: this.msgNs,
1762
+ id: { ns: this.msgNs, attr: "key", value: key },
1763
+ attrs: { key, sessionId: this.sessionId, seq, role: m.role, content: JSON.stringify(m.content), createdAt: now }
1764
+ });
1765
+ });
1766
+ await this.db.transact(ops, {
1767
+ mutationId: `${this.sessionId}:append:${base}:${base + messages.length}`
1768
+ });
1769
+ this.loadedCount += messages.length;
1770
+ }
1771
+ };
1772
+ function parseContent(raw) {
1773
+ if (raw === void 0) return "";
1774
+ try {
1775
+ return JSON.parse(raw);
1776
+ } catch {
1777
+ return raw;
1778
+ }
1779
+ }
1780
+
1781
+ // src/store/runs.ts
1782
+ async function persistRun(run, opts) {
1783
+ const ns = opts.namespace ?? NS.run;
1784
+ const key = opts.runId ?? `${opts.sessionId}:${Date.now()}`;
1785
+ await opts.db.transact(
1786
+ [
1787
+ {
1788
+ t: "update",
1789
+ ns,
1790
+ id: { ns, attr: "key", value: key },
1791
+ attrs: {
1792
+ key,
1793
+ sessionId: opts.sessionId,
1794
+ ...opts.personaName ? { persona: opts.personaName } : {},
1795
+ status: run.stoppedReason,
1796
+ steps: run.steps,
1797
+ inputTokens: run.usage.inputTokens,
1798
+ outputTokens: run.usage.outputTokens,
1799
+ finalText: run.finalText,
1800
+ trace: {
1801
+ toolCalls: run.toolCalls.map((t) => ({ name: t.toolUse.name, input: t.toolUse.input, output: t.output })),
1802
+ messages: run.messages
1803
+ },
1804
+ createdAt: Date.now()
1805
+ }
1806
+ }
1807
+ ],
1808
+ { mutationId: `run:${key}` }
1809
+ );
1810
+ return key;
1811
+ }
1812
+ async function queryRuns(opts) {
1813
+ const ns = opts.namespace ?? NS.run;
1814
+ const res = await opts.db.query({
1815
+ [ns]: { $: { where: { sessionId: opts.sessionId }, order: { createdAt: "desc" }, limit: opts.limit ?? 50 } }
1816
+ });
1817
+ return res[ns] ?? [];
1818
+ }
1819
+
1820
+ // src/store/keys.ts
1821
+ var DEFAULT_SECRET_NAMES = {
1822
+ anthropic: "anthropic_api_key",
1823
+ openai: "openai_api_key",
1824
+ google: "google_api_key"
1825
+ };
1826
+ function odlaDbKeyResolver(db, opts = {}) {
1827
+ const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
1828
+ const useCache = opts.cache ?? true;
1829
+ const cache2 = /* @__PURE__ */ new Map();
1830
+ return async (provider) => {
1831
+ if (useCache) {
1832
+ const hit = cache2.get(provider);
1833
+ if (hit !== void 0) return hit;
1834
+ }
1835
+ try {
1836
+ const key = await db.secrets.get(nameFor(provider));
1837
+ if (useCache) cache2.set(provider, key);
1838
+ return key;
1839
+ } catch {
1840
+ return void 0;
1841
+ }
1842
+ };
1843
+ }
1844
+
1845
+ // src/store/provision.ts
1846
+ init_shape();
1847
+ async function post(ctx, path, body) {
1848
+ const f = ctx.fetch ?? fetch;
1849
+ const res = await f(`${ctx.endpoint.replace(/\/$/, "")}${path}`, {
1850
+ method: "POST",
1851
+ headers: { authorization: `Bearer ${ctx.token}`, "content-type": "application/json" },
1852
+ body: JSON.stringify(body)
1853
+ });
1854
+ if (!res.ok) throw errorFor(res.status, await safeText(res), path);
1855
+ return await res.json().catch(() => ({}));
1856
+ }
1857
+ async function postWithKey(ctx, path, appKey, body) {
1858
+ const f = ctx.fetch ?? fetch;
1859
+ const res = await f(`${ctx.endpoint.replace(/\/$/, "")}${path}`, {
1860
+ method: "POST",
1861
+ headers: { authorization: `Bearer ${appKey}`, "content-type": "application/json" },
1862
+ body: JSON.stringify(body)
1863
+ });
1864
+ if (!res.ok) throw errorFor(res.status, await safeText(res), path);
1865
+ return await res.json().catch(() => ({}));
1866
+ }
1867
+ async function createApp(ctx, opts = {}) {
1868
+ const body = {};
1869
+ if (opts.appId) body.appId = opts.appId;
1870
+ if (opts.name) body.name = opts.name;
1871
+ const json = await post(ctx, "/admin/apps", body);
1872
+ const appId = json.appId ?? json.id ?? json.app?.appId ?? opts.appId;
1873
+ if (!appId) throw new ProviderError("[odla-db] createApp: response did not include an appId");
1874
+ return appId;
1875
+ }
1876
+ async function mintAppKey(ctx, appId) {
1877
+ const json = await post(ctx, `/admin/apps/${encodeURIComponent(appId)}/keys`, {});
1878
+ const key = json.key;
1879
+ if (!key) throw new ProviderError("[odla-db] mintAppKey: response did not include a key");
1880
+ return key;
1881
+ }
1882
+ async function pushAgentSchema(ctx, appId, appKey) {
1883
+ await postWithKey(ctx, `/app/${encodeURIComponent(appId)}/schema`, appKey, { schema: AGENT_SCHEMA });
1884
+ }
1885
+ async function putSecret(ctx, appId, name, value) {
1886
+ await post(ctx, `/admin/apps/${encodeURIComponent(appId)}/secrets`, { name, value });
1887
+ }
1888
+ async function provisionAgentApp(opts) {
1889
+ const ctx = { endpoint: opts.endpoint, token: opts.token, fetch: opts.fetch };
1890
+ const appId = await createApp(ctx, { appId: opts.appId, name: opts.appName });
1891
+ const appKey = await mintAppKey(ctx, appId);
1892
+ if (opts.pushSchema !== false) {
1893
+ await pushAgentSchema({ endpoint: opts.endpoint, fetch: opts.fetch }, appId, appKey);
1894
+ }
1895
+ const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
1896
+ for (const [provider, value] of Object.entries(opts.providerKeys ?? {})) {
1897
+ if (!value) continue;
1898
+ await putSecret(ctx, appId, nameFor(provider), value);
1899
+ }
1900
+ return { appId, appKey };
1901
+ }
1902
+ function errorFor(status, text, path) {
1903
+ const msg = redact(`[odla-db] ${path} \u2192 ${status} ${text}`.trim());
1904
+ if (status === 401 || status === 403) return new AuthError(msg, { providerStatus: status });
1905
+ if (status === 501) return new ConfigError(`${msg} (secrets/feature not enabled on the odla-db worker)`);
1906
+ if (status === 400) return new ConfigError(msg);
1907
+ return new ProviderError(msg, { providerStatus: status });
1908
+ }
1909
+ async function safeText(res) {
1910
+ try {
1911
+ return (await res.text()).slice(0, 300);
1912
+ } catch {
1913
+ return "";
1914
+ }
1915
+ }
1916
+
1917
+ // src/store/crud.ts
1918
+ function entityCrudSkill(opts) {
1919
+ const { db, entity, fields } = opts;
1920
+ const singular = opts.singular ?? entity.replace(/s$/, "");
1921
+ const plural = opts.plural ?? entity;
1922
+ const idField = opts.idField ?? "id";
1923
+ const genId = opts.genId ?? (() => globalThis.crypto.randomUUID());
1924
+ const stampAttr = opts.stampCreatedAt === void 0 ? "createdAt" : opts.stampCreatedAt;
1925
+ const order = opts.order ?? { createdAt: "asc" };
1926
+ const fieldNames = Object.keys(fields);
1927
+ const pickFields = (input) => {
1928
+ const out = {};
1929
+ for (const k of fieldNames) if (k in input && input[k] !== void 0) out[k] = input[k];
1930
+ return out;
1931
+ };
1932
+ const ok = (obj) => ({ content: JSON.stringify(obj) });
1933
+ const listTool = {
1934
+ name: `list_${plural}`,
1935
+ description: `List ${plural}. Optionally filter by exact field values and/or limit the count.`,
1936
+ inputSchema: {
1937
+ type: "object",
1938
+ properties: {
1939
+ filter: { type: "object", description: `Exact-match filters, e.g. { "done": false }`, additionalProperties: true },
1940
+ limit: { type: "number", description: "Max rows to return." }
1941
+ }
1942
+ },
1943
+ handler: async (input) => {
1944
+ const filter = input.filter ?? void 0;
1945
+ const limit = typeof input.limit === "number" ? input.limit : void 0;
1946
+ const $ = { order };
1947
+ if (filter && Object.keys(filter).length > 0) $.where = filter;
1948
+ if (limit !== void 0) $.limit = limit;
1949
+ const res = await db.query({ [entity]: { $ } });
1950
+ return ok({ [plural]: res[entity] ?? [] });
1951
+ }
1952
+ };
1953
+ const createTool = {
1954
+ name: `create_${singular}`,
1955
+ description: `Create a new ${singular}.`,
1956
+ inputSchema: { type: "object", properties: fields, ...opts.required ? { required: opts.required } : {} },
1957
+ handler: async (input) => {
1958
+ const id = genId();
1959
+ const attrs = pickFields(input);
1960
+ if (stampAttr) attrs[stampAttr] = Date.now();
1961
+ await db.transact([{ t: "update", ns: entity, id, attrs }]);
1962
+ return ok({ id, created: true });
1963
+ }
1964
+ };
1965
+ const updateTool = {
1966
+ name: `update_${singular}`,
1967
+ description: `Update fields of an existing ${singular} by ${idField}. Only the fields you pass change.`,
1968
+ inputSchema: {
1969
+ type: "object",
1970
+ properties: { [idField]: { type: "string", description: `The ${singular}'s id.` }, ...fields },
1971
+ required: [idField]
1972
+ },
1973
+ handler: async (input) => {
1974
+ const id = String(input[idField] ?? "");
1975
+ if (!id) return { content: `Missing "${idField}".`, isError: true };
1976
+ const attrs = pickFields(input);
1977
+ if (Object.keys(attrs).length === 0) return { content: "No fields to update.", isError: true };
1978
+ await db.transact([{ t: "update", ns: entity, id, attrs }]);
1979
+ return ok({ id, updated: true });
1980
+ }
1981
+ };
1982
+ const deleteTool = {
1983
+ name: `delete_${singular}`,
1984
+ description: `Delete a ${singular} by ${idField}.`,
1985
+ inputSchema: { type: "object", properties: { [idField]: { type: "string" } }, required: [idField] },
1986
+ handler: async (input) => {
1987
+ const id = String(input[idField] ?? "");
1988
+ if (!id) return { content: `Missing "${idField}".`, isError: true };
1989
+ const op = { t: "delete", ns: entity, id };
1990
+ await db.transact([op]);
1991
+ return ok({ id, deleted: true });
1992
+ }
1993
+ };
1994
+ const guidance = `Manage ${plural} in the database. Use list_${plural} to read (optionally filtered), create_${singular} to add, update_${singular} to change fields (e.g. mark done), and delete_${singular} to remove. Always confirm the current list with list_${plural} before updating or deleting so you use the right id.` + (opts.instructions ? `
1995
+ ${opts.instructions}` : "");
1996
+ return {
1997
+ name: opts.name ?? `${entity} management`,
1998
+ instructions: guidance,
1999
+ tools: [listTool, createTool, updateTool, deleteTool]
2000
+ };
2001
+ }
2002
+ //# sourceMappingURL=index.cjs.map