@odla-ai/ai 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -17
- package/dist/{anthropic-BIGCVQE4.js → anthropic-2NPMHJZT.js} +16 -8
- 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-QFT2DU2X.js → chunk-PXXCN2EU.js} +62 -5
- package/dist/chunk-PXXCN2EU.js.map +1 -0
- package/dist/{google-QY5B4T5O.js → google-3XV2VWVB.js} +18 -8
- package/dist/google-3XV2VWVB.js.map +1 -0
- package/dist/index.cjs +664 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -10
- package/dist/index.d.ts +120 -10
- package/dist/index.js +545 -50
- package/dist/index.js.map +1 -1
- package/dist/{openai-PTZPJYTT.js → openai-NTQCF577.js} +20 -10
- package/dist/openai-NTQCF577.js.map +1 -0
- package/llms.txt +34 -15
- package/package.json +12 -5
- package/dist/anthropic-BIGCVQE4.js.map +0 -1
- package/dist/chunk-QFT2DU2X.js.map +0 -1
- package/dist/google-QY5B4T5O.js.map +0 -1
- package/dist/openai-PTZPJYTT.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -77,7 +77,7 @@ var init_events = __esm({
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
// src/shape/errors.ts
|
|
80
|
-
var OdlaAIError, ConfigError, AuthError, RateLimitError, CapabilityError, ContextWindowError, InvalidRequestError, ProviderError;
|
|
80
|
+
var OdlaAIError, ConfigError, AuthError, RateLimitError, CapabilityError, ContextWindowError, InvalidRequestError, ToolInputError, CancelledError, DeadlineExceededError, ProviderError;
|
|
81
81
|
var init_errors = __esm({
|
|
82
82
|
"src/shape/errors.ts"() {
|
|
83
83
|
"use strict";
|
|
@@ -131,6 +131,23 @@ var init_errors = __esm({
|
|
|
131
131
|
super("invalid_request", message, opts);
|
|
132
132
|
}
|
|
133
133
|
};
|
|
134
|
+
ToolInputError = class extends OdlaAIError {
|
|
135
|
+
tool;
|
|
136
|
+
constructor(message, opts) {
|
|
137
|
+
super("tool_input_invalid", message, { cause: opts?.cause });
|
|
138
|
+
this.tool = opts?.tool;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
CancelledError = class extends OdlaAIError {
|
|
142
|
+
constructor(message = "AI request was cancelled.", opts) {
|
|
143
|
+
super("cancelled", message, opts);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
DeadlineExceededError = class extends OdlaAIError {
|
|
147
|
+
constructor(message = "AI request deadline was exceeded.", opts) {
|
|
148
|
+
super("deadline_exceeded", message, opts);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
134
151
|
ProviderError = class extends OdlaAIError {
|
|
135
152
|
constructor(message, opts) {
|
|
136
153
|
super("provider_error", message, opts);
|
|
@@ -176,6 +193,372 @@ var init_helpers = __esm({
|
|
|
176
193
|
}
|
|
177
194
|
});
|
|
178
195
|
|
|
196
|
+
// src/shape/json-schema-shape.ts
|
|
197
|
+
function validateSchemaShape(schema, path, root, issues) {
|
|
198
|
+
if (typeof schema === "boolean") return;
|
|
199
|
+
if (!isRecord(schema)) {
|
|
200
|
+
issue(issues, path, "schema must be an object or boolean");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const key of Object.keys(schema)) {
|
|
204
|
+
if (!SUPPORTED_KEYS.has(key)) issue(issues, path, `schema uses unsupported keyword "${key}"`);
|
|
205
|
+
}
|
|
206
|
+
if (typeof schema.$ref === "string" && resolveLocalRef(root, schema.$ref) === void 0) {
|
|
207
|
+
issue(issues, path, `schema reference "${schema.$ref}" could not be resolved`);
|
|
208
|
+
} else if (schema.$ref !== void 0 && typeof schema.$ref !== "string") {
|
|
209
|
+
issue(issues, path, "schema $ref must be a string");
|
|
210
|
+
}
|
|
211
|
+
const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : [];
|
|
212
|
+
if (schema.type !== void 0 && (types.length === 0 || !types.every((type) => typeof type === "string" && ALLOWED_TYPES.has(type)))) {
|
|
213
|
+
issue(issues, path, "schema type contains an unsupported JSON type");
|
|
214
|
+
}
|
|
215
|
+
if (schema.enum !== void 0 && !Array.isArray(schema.enum)) {
|
|
216
|
+
issue(issues, path, "schema enum must be an array");
|
|
217
|
+
}
|
|
218
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string"))) {
|
|
219
|
+
issue(issues, path, "schema required must be a string array");
|
|
220
|
+
}
|
|
221
|
+
validateConstraintShapes(schema, path, issues);
|
|
222
|
+
validateChildSchemas(schema, path, root, issues);
|
|
223
|
+
}
|
|
224
|
+
function validateChildSchemas(schema, path, root, issues) {
|
|
225
|
+
for (const key of ["properties", "$defs", "definitions"]) {
|
|
226
|
+
const group = schema[key];
|
|
227
|
+
if (group === void 0) continue;
|
|
228
|
+
if (!isRecord(group)) {
|
|
229
|
+
issue(issues, path, `schema ${key} must be an object`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
for (const [name, child] of Object.entries(group)) {
|
|
233
|
+
validateSchemaShape(child, `${path}.${key}.${name}`, root, issues);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
237
|
+
const group = schema[key];
|
|
238
|
+
if (group === void 0) continue;
|
|
239
|
+
if (!Array.isArray(group) || group.length === 0) {
|
|
240
|
+
issue(issues, path, `schema ${key} must be a non-empty array`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
group.forEach((child, index2) => validateSchemaShape(child, `${path}.${key}[${index2}]`, root, issues));
|
|
244
|
+
}
|
|
245
|
+
for (const key of ["items", "not", "additionalProperties"]) {
|
|
246
|
+
const child = schema[key];
|
|
247
|
+
if (child !== void 0) validateSchemaShape(child, `${path}.${key}`, root, issues);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function validateConstraintShapes(schema, path, issues) {
|
|
251
|
+
for (const key of ["minLength", "maxLength", "minItems", "maxItems", "minProperties", "maxProperties"]) {
|
|
252
|
+
const value = schema[key];
|
|
253
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 0)) {
|
|
254
|
+
issue(issues, path, `schema ${key} must be a non-negative integer`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const key of ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"]) {
|
|
258
|
+
const value = schema[key];
|
|
259
|
+
if (value !== void 0 && !isFiniteNumber(value)) {
|
|
260
|
+
issue(issues, path, `schema ${key} must be a finite number`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (schema.multipleOf !== void 0 && (!isFiniteNumber(schema.multipleOf) || schema.multipleOf <= 0)) {
|
|
264
|
+
issue(issues, path, "schema multipleOf must be a finite number greater than zero");
|
|
265
|
+
}
|
|
266
|
+
if (schema.uniqueItems !== void 0 && typeof schema.uniqueItems !== "boolean") {
|
|
267
|
+
issue(issues, path, "schema uniqueItems must be a boolean");
|
|
268
|
+
}
|
|
269
|
+
validatePatternShape(schema.pattern, path, issues);
|
|
270
|
+
}
|
|
271
|
+
function validatePatternShape(value, path, issues) {
|
|
272
|
+
if (value === void 0) return;
|
|
273
|
+
if (typeof value !== "string") {
|
|
274
|
+
issue(issues, path, "schema pattern must be a string");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
new RegExp(value, "u");
|
|
279
|
+
} catch {
|
|
280
|
+
issue(issues, path, `schema pattern "${value}" is invalid`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function resolveLocalRef(root, ref) {
|
|
284
|
+
if (ref === "#") return root;
|
|
285
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
286
|
+
let current = root;
|
|
287
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
288
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
289
|
+
if (!isRecord(current) || !Object.hasOwn(current, key)) return void 0;
|
|
290
|
+
current = current[key];
|
|
291
|
+
}
|
|
292
|
+
return current;
|
|
293
|
+
}
|
|
294
|
+
function isRecord(value) {
|
|
295
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
296
|
+
}
|
|
297
|
+
function isFiniteNumber(value) {
|
|
298
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
299
|
+
}
|
|
300
|
+
function issue(issues, path, message) {
|
|
301
|
+
issues.push({ path, message });
|
|
302
|
+
}
|
|
303
|
+
var ANNOTATION_KEYS, SUPPORTED_KEYS, ALLOWED_TYPES;
|
|
304
|
+
var init_json_schema_shape = __esm({
|
|
305
|
+
"src/shape/json-schema-shape.ts"() {
|
|
306
|
+
"use strict";
|
|
307
|
+
ANNOTATION_KEYS = /* @__PURE__ */ new Set([
|
|
308
|
+
"$schema",
|
|
309
|
+
"$id",
|
|
310
|
+
"$anchor",
|
|
311
|
+
"title",
|
|
312
|
+
"description",
|
|
313
|
+
"default",
|
|
314
|
+
"examples",
|
|
315
|
+
"deprecated",
|
|
316
|
+
"readOnly",
|
|
317
|
+
"writeOnly",
|
|
318
|
+
"format"
|
|
319
|
+
]);
|
|
320
|
+
SUPPORTED_KEYS = /* @__PURE__ */ new Set([
|
|
321
|
+
...ANNOTATION_KEYS,
|
|
322
|
+
"$ref",
|
|
323
|
+
"$defs",
|
|
324
|
+
"definitions",
|
|
325
|
+
"type",
|
|
326
|
+
"enum",
|
|
327
|
+
"const",
|
|
328
|
+
"allOf",
|
|
329
|
+
"anyOf",
|
|
330
|
+
"oneOf",
|
|
331
|
+
"not",
|
|
332
|
+
"properties",
|
|
333
|
+
"required",
|
|
334
|
+
"additionalProperties",
|
|
335
|
+
"minProperties",
|
|
336
|
+
"maxProperties",
|
|
337
|
+
"items",
|
|
338
|
+
"minItems",
|
|
339
|
+
"maxItems",
|
|
340
|
+
"uniqueItems",
|
|
341
|
+
"minLength",
|
|
342
|
+
"maxLength",
|
|
343
|
+
"pattern",
|
|
344
|
+
"minimum",
|
|
345
|
+
"maximum",
|
|
346
|
+
"exclusiveMinimum",
|
|
347
|
+
"exclusiveMaximum",
|
|
348
|
+
"multipleOf"
|
|
349
|
+
]);
|
|
350
|
+
ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
351
|
+
"null",
|
|
352
|
+
"boolean",
|
|
353
|
+
"object",
|
|
354
|
+
"array",
|
|
355
|
+
"number",
|
|
356
|
+
"integer",
|
|
357
|
+
"string"
|
|
358
|
+
]);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// src/shape/json-schema.ts
|
|
363
|
+
function validateJsonSchema(schema, value) {
|
|
364
|
+
const issues = [];
|
|
365
|
+
validateSchemaShape(schema, "$", schema, issues);
|
|
366
|
+
if (issues.length > 0) return { valid: false, issues };
|
|
367
|
+
visit(schema, value, "$", schema, issues, /* @__PURE__ */ new Set());
|
|
368
|
+
return { valid: issues.length === 0, issues };
|
|
369
|
+
}
|
|
370
|
+
function visit(schema, value, path, root, issues, refs) {
|
|
371
|
+
if (schema === true) return;
|
|
372
|
+
if (schema === false) {
|
|
373
|
+
issue2(issues, path, "is rejected by the schema");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (!isRecord2(schema)) {
|
|
377
|
+
issue2(issues, path, "schema must be an object or boolean");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
for (const key of Object.keys(schema)) {
|
|
381
|
+
if (!SUPPORTED_KEYS.has(key)) {
|
|
382
|
+
issue2(issues, path, `schema uses unsupported keyword "${key}"`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (typeof schema.$ref === "string") {
|
|
387
|
+
const target = resolveLocalRef2(root, schema.$ref);
|
|
388
|
+
if (target === void 0) {
|
|
389
|
+
issue2(issues, path, `schema reference "${schema.$ref}" could not be resolved`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (refs.has(schema.$ref)) {
|
|
393
|
+
issue2(issues, path, `cyclic schema reference "${schema.$ref}" is unsupported`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const nextRefs = new Set(refs);
|
|
397
|
+
nextRefs.add(schema.$ref);
|
|
398
|
+
visit(target, value, path, root, issues, nextRefs);
|
|
399
|
+
}
|
|
400
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => jsonEqual(candidate, value))) {
|
|
401
|
+
issue2(issues, path, "must equal one of the allowed enum values");
|
|
402
|
+
}
|
|
403
|
+
if (Object.hasOwn(schema, "const") && !jsonEqual(schema.const, value)) {
|
|
404
|
+
issue2(issues, path, "must equal the constant value");
|
|
405
|
+
}
|
|
406
|
+
if (Array.isArray(schema.allOf)) {
|
|
407
|
+
for (const child of schema.allOf) visit(child, value, path, root, issues, refs);
|
|
408
|
+
}
|
|
409
|
+
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((child) => matches(child, value, root, refs))) {
|
|
410
|
+
issue2(issues, path, "must match at least one anyOf schema");
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(schema.oneOf)) {
|
|
413
|
+
const matchesCount = schema.oneOf.filter((child) => matches(child, value, root, refs)).length;
|
|
414
|
+
if (matchesCount !== 1) issue2(issues, path, "must match exactly one oneOf schema");
|
|
415
|
+
}
|
|
416
|
+
if (schema.not !== void 0 && matches(schema.not, value, root, refs)) {
|
|
417
|
+
issue2(issues, path, "must not match the forbidden schema");
|
|
418
|
+
}
|
|
419
|
+
const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) && schema.type.every((t) => typeof t === "string") ? schema.type : void 0;
|
|
420
|
+
if (schema.type !== void 0 && !types) {
|
|
421
|
+
issue2(issues, path, "schema type must be a string or string array");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (types && !types.some((type) => isType(value, type))) {
|
|
425
|
+
issue2(issues, path, `must be ${types.join(" or ")}`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (typeof value === "string") validateString(schema, value, path, issues);
|
|
429
|
+
if (typeof value === "number") validateNumber(schema, value, path, issues);
|
|
430
|
+
if (Array.isArray(value)) validateArray(schema, value, path, root, issues, refs);
|
|
431
|
+
if (isRecord2(value)) validateObject(schema, value, path, root, issues, refs);
|
|
432
|
+
}
|
|
433
|
+
function validateString(schema, value, path, issues) {
|
|
434
|
+
if (isFiniteNumber2(schema.minLength) && [...value].length < schema.minLength) issue2(issues, path, `must have at least ${schema.minLength} characters`);
|
|
435
|
+
if (isFiniteNumber2(schema.maxLength) && [...value].length > schema.maxLength) issue2(issues, path, `must have at most ${schema.maxLength} characters`);
|
|
436
|
+
if (typeof schema.pattern === "string") {
|
|
437
|
+
try {
|
|
438
|
+
if (!new RegExp(schema.pattern, "u").test(value)) issue2(issues, path, `must match pattern ${schema.pattern}`);
|
|
439
|
+
} catch {
|
|
440
|
+
issue2(issues, path, `schema pattern "${schema.pattern}" is invalid`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function validateNumber(schema, value, path, issues) {
|
|
445
|
+
if (!Number.isFinite(value)) {
|
|
446
|
+
issue2(issues, path, "must be a finite number");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (isFiniteNumber2(schema.minimum) && value < schema.minimum) issue2(issues, path, `must be >= ${schema.minimum}`);
|
|
450
|
+
if (isFiniteNumber2(schema.maximum) && value > schema.maximum) issue2(issues, path, `must be <= ${schema.maximum}`);
|
|
451
|
+
if (isFiniteNumber2(schema.exclusiveMinimum) && value <= schema.exclusiveMinimum) issue2(issues, path, `must be > ${schema.exclusiveMinimum}`);
|
|
452
|
+
if (isFiniteNumber2(schema.exclusiveMaximum) && value >= schema.exclusiveMaximum) issue2(issues, path, `must be < ${schema.exclusiveMaximum}`);
|
|
453
|
+
if (isFiniteNumber2(schema.multipleOf) && schema.multipleOf > 0) {
|
|
454
|
+
const quotient = value / schema.multipleOf;
|
|
455
|
+
if (Math.abs(quotient - Math.round(quotient)) > Number.EPSILON * Math.max(1, Math.abs(quotient))) {
|
|
456
|
+
issue2(issues, path, `must be a multiple of ${schema.multipleOf}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function validateArray(schema, value, path, root, issues, refs) {
|
|
461
|
+
if (isFiniteNumber2(schema.minItems) && value.length < schema.minItems) issue2(issues, path, `must contain at least ${schema.minItems} items`);
|
|
462
|
+
if (isFiniteNumber2(schema.maxItems) && value.length > schema.maxItems) issue2(issues, path, `must contain at most ${schema.maxItems} items`);
|
|
463
|
+
if (schema.uniqueItems === true) {
|
|
464
|
+
for (let i = 0; i < value.length; i++) {
|
|
465
|
+
if (value.slice(0, i).some((other) => jsonEqual(other, value[i]))) {
|
|
466
|
+
issue2(issues, `${path}[${i}]`, "must be unique");
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (schema.items !== void 0) {
|
|
472
|
+
for (let i = 0; i < value.length; i++) visit(schema.items, value[i], `${path}[${i}]`, root, issues, refs);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function validateObject(schema, value, path, root, issues, refs) {
|
|
476
|
+
const keys = Object.keys(value);
|
|
477
|
+
if (isFiniteNumber2(schema.minProperties) && keys.length < schema.minProperties) issue2(issues, path, `must contain at least ${schema.minProperties} properties`);
|
|
478
|
+
if (isFiniteNumber2(schema.maxProperties) && keys.length > schema.maxProperties) issue2(issues, path, `must contain at most ${schema.maxProperties} properties`);
|
|
479
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string"))) {
|
|
480
|
+
issue2(issues, path, "schema required must be a string array");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
for (const key of schema.required ?? []) {
|
|
484
|
+
if (!Object.hasOwn(value, key)) issue2(issues, childPath(path, key), "is required");
|
|
485
|
+
}
|
|
486
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
487
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
488
|
+
if (Object.hasOwn(value, key)) visit(child, value[key], childPath(path, key), root, issues, refs);
|
|
489
|
+
}
|
|
490
|
+
const extras = keys.filter((key) => !Object.hasOwn(properties, key));
|
|
491
|
+
if (schema.additionalProperties === false) {
|
|
492
|
+
for (const key of extras) issue2(issues, childPath(path, key), "is not an allowed property");
|
|
493
|
+
} else if (schema.additionalProperties !== void 0 && schema.additionalProperties !== true) {
|
|
494
|
+
for (const key of extras) visit(schema.additionalProperties, value[key], childPath(path, key), root, issues, refs);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function matches(schema, value, root, refs) {
|
|
498
|
+
const nested = [];
|
|
499
|
+
visit(schema, value, "$", root, nested, refs);
|
|
500
|
+
return nested.length === 0;
|
|
501
|
+
}
|
|
502
|
+
function resolveLocalRef2(root, ref) {
|
|
503
|
+
if (ref === "#") return root;
|
|
504
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
505
|
+
let current = root;
|
|
506
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
507
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
508
|
+
if (!isRecord2(current) || !Object.hasOwn(current, key)) return void 0;
|
|
509
|
+
current = current[key];
|
|
510
|
+
}
|
|
511
|
+
return current;
|
|
512
|
+
}
|
|
513
|
+
function isRecord2(value) {
|
|
514
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
515
|
+
}
|
|
516
|
+
function isType(value, type) {
|
|
517
|
+
switch (type) {
|
|
518
|
+
case "null":
|
|
519
|
+
return value === null;
|
|
520
|
+
case "boolean":
|
|
521
|
+
return typeof value === "boolean";
|
|
522
|
+
case "object":
|
|
523
|
+
return isRecord2(value);
|
|
524
|
+
case "array":
|
|
525
|
+
return Array.isArray(value);
|
|
526
|
+
case "number":
|
|
527
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
528
|
+
case "integer":
|
|
529
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
530
|
+
case "string":
|
|
531
|
+
return typeof value === "string";
|
|
532
|
+
default:
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function isFiniteNumber2(value) {
|
|
537
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
538
|
+
}
|
|
539
|
+
function jsonEqual(a, b) {
|
|
540
|
+
if (Object.is(a, b)) return true;
|
|
541
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
|
|
542
|
+
if (isRecord2(a) && isRecord2(b)) {
|
|
543
|
+
const aKeys = Object.keys(a);
|
|
544
|
+
const bKeys = Object.keys(b);
|
|
545
|
+
return aKeys.length === bKeys.length && aKeys.every((key) => Object.hasOwn(b, key) && jsonEqual(a[key], b[key]));
|
|
546
|
+
}
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
function childPath(path, key) {
|
|
550
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
|
|
551
|
+
}
|
|
552
|
+
function issue2(issues, path, message) {
|
|
553
|
+
issues.push({ path, message });
|
|
554
|
+
}
|
|
555
|
+
var init_json_schema = __esm({
|
|
556
|
+
"src/shape/json-schema.ts"() {
|
|
557
|
+
"use strict";
|
|
558
|
+
init_json_schema_shape();
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
179
562
|
// src/shape/index.ts
|
|
180
563
|
var init_shape = __esm({
|
|
181
564
|
"src/shape/index.ts"() {
|
|
@@ -186,6 +569,7 @@ var init_shape = __esm({
|
|
|
186
569
|
init_events();
|
|
187
570
|
init_errors();
|
|
188
571
|
init_helpers();
|
|
572
|
+
init_json_schema();
|
|
189
573
|
}
|
|
190
574
|
});
|
|
191
575
|
|
|
@@ -216,8 +600,10 @@ function messageOf(err) {
|
|
|
216
600
|
if (typeof err === "string") return err;
|
|
217
601
|
return "unknown provider error";
|
|
218
602
|
}
|
|
219
|
-
function normalizeError(err, provider) {
|
|
603
|
+
function normalizeError(err, provider, signal) {
|
|
220
604
|
if (err instanceof OdlaAIError) return err;
|
|
605
|
+
const cancellation = cancellationError(signal?.aborted ? signal.reason : err);
|
|
606
|
+
if (signal?.aborted || cancellation) return cancellation ?? new CancelledError(void 0, { cause: err });
|
|
221
607
|
const status = statusOf(err);
|
|
222
608
|
const message = messageOf(err);
|
|
223
609
|
const tagged = `[${provider}] ${message}`;
|
|
@@ -233,8 +619,18 @@ function normalizeError(err, provider) {
|
|
|
233
619
|
}
|
|
234
620
|
return new ProviderError(tagged, { providerStatus: status, cause: err });
|
|
235
621
|
}
|
|
236
|
-
function mapProviderError(err, provider) {
|
|
237
|
-
throw normalizeError(err, provider);
|
|
622
|
+
function mapProviderError(err, provider, signal) {
|
|
623
|
+
throw normalizeError(err, provider, signal);
|
|
624
|
+
}
|
|
625
|
+
function cancellationError(reason) {
|
|
626
|
+
if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) return reason;
|
|
627
|
+
if (!reason || typeof reason !== "object") return void 0;
|
|
628
|
+
const rec = reason;
|
|
629
|
+
if (rec.name === "TimeoutError") return new DeadlineExceededError(void 0, { cause: reason });
|
|
630
|
+
if (rec.name === "AbortError" || rec.name === "APIUserAbortError" || rec.code === "ABORT_ERR") {
|
|
631
|
+
return new CancelledError(void 0, { cause: reason });
|
|
632
|
+
}
|
|
633
|
+
return void 0;
|
|
238
634
|
}
|
|
239
635
|
var init_errors2 = __esm({
|
|
240
636
|
"src/providers/errors.ts"() {
|
|
@@ -337,13 +733,27 @@ var init_request2 = __esm({
|
|
|
337
733
|
}
|
|
338
734
|
});
|
|
339
735
|
|
|
736
|
+
// src/providers/tool-input.ts
|
|
737
|
+
function toolInput(value, provider, name) {
|
|
738
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
739
|
+
return value;
|
|
740
|
+
}
|
|
741
|
+
throw new ToolInputError(`[${provider}] tool "${name}" returned arguments that are not a JSON object.`, { tool: name });
|
|
742
|
+
}
|
|
743
|
+
var init_tool_input = __esm({
|
|
744
|
+
"src/providers/tool-input.ts"() {
|
|
745
|
+
"use strict";
|
|
746
|
+
init_shape();
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
340
750
|
// src/providers/anthropic/response.ts
|
|
341
751
|
function mapContent(blocks) {
|
|
342
752
|
const out = [];
|
|
343
753
|
for (const b of blocks) {
|
|
344
754
|
if (b.type === "text") out.push({ type: "text", text: b.text });
|
|
345
755
|
else if (b.type === "tool_use") {
|
|
346
|
-
out.push({ type: "tool_use", id: b.id, name: b.name, input: b.input ?? {} });
|
|
756
|
+
out.push({ type: "tool_use", id: b.id, name: b.name, input: toolInput(b.input ?? {}, "anthropic", b.name) });
|
|
347
757
|
} else if (b.type === "thinking") {
|
|
348
758
|
out.push({ type: "thinking", thinking: b.thinking, signature: b.signature });
|
|
349
759
|
}
|
|
@@ -372,6 +782,7 @@ function mapStop(reason) {
|
|
|
372
782
|
var init_response = __esm({
|
|
373
783
|
"src/providers/anthropic/response.ts"() {
|
|
374
784
|
"use strict";
|
|
785
|
+
init_tool_input();
|
|
375
786
|
}
|
|
376
787
|
});
|
|
377
788
|
|
|
@@ -424,6 +835,35 @@ var init_stream = __esm({
|
|
|
424
835
|
}
|
|
425
836
|
});
|
|
426
837
|
|
|
838
|
+
// src/client/abort.ts
|
|
839
|
+
function signalWithDeadline(signal, deadline) {
|
|
840
|
+
if (deadline === void 0) return signal;
|
|
841
|
+
if (!Number.isSafeInteger(deadline)) throw new ConfigError("deadline must be a finite integer Unix timestamp in milliseconds.");
|
|
842
|
+
const remaining = deadline - Date.now();
|
|
843
|
+
if (remaining <= 0) {
|
|
844
|
+
const expired = new AbortController();
|
|
845
|
+
expired.abort(new DeadlineExceededError());
|
|
846
|
+
return signal ? AbortSignal.any([signal, expired.signal]) : expired.signal;
|
|
847
|
+
}
|
|
848
|
+
const timeout = AbortSignal.timeout(remaining);
|
|
849
|
+
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
850
|
+
}
|
|
851
|
+
function throwIfAborted(signal) {
|
|
852
|
+
if (!signal?.aborted) return;
|
|
853
|
+
const reason = signal.reason;
|
|
854
|
+
if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) throw reason;
|
|
855
|
+
if (reason && typeof reason === "object" && reason.name === "TimeoutError") {
|
|
856
|
+
throw new DeadlineExceededError(void 0, { cause: reason });
|
|
857
|
+
}
|
|
858
|
+
throw new CancelledError(void 0, { cause: reason });
|
|
859
|
+
}
|
|
860
|
+
var init_abort = __esm({
|
|
861
|
+
"src/client/abort.ts"() {
|
|
862
|
+
"use strict";
|
|
863
|
+
init_errors();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
427
867
|
// src/providers/anthropic.ts
|
|
428
868
|
var anthropic_exports = {};
|
|
429
869
|
__export(anthropic_exports, {
|
|
@@ -439,6 +879,7 @@ var init_anthropic = __esm({
|
|
|
439
879
|
init_request2();
|
|
440
880
|
init_response();
|
|
441
881
|
init_stream();
|
|
882
|
+
init_abort();
|
|
442
883
|
AnthropicProvider = class {
|
|
443
884
|
id = "anthropic";
|
|
444
885
|
client;
|
|
@@ -447,6 +888,7 @@ var init_anthropic = __esm({
|
|
|
447
888
|
this.client = client ?? new import_sdk.default({ apiKey });
|
|
448
889
|
}
|
|
449
890
|
async create(spec, req) {
|
|
891
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
450
892
|
try {
|
|
451
893
|
let messages = toAnthropicMessages(req);
|
|
452
894
|
const content = [];
|
|
@@ -455,7 +897,8 @@ var init_anthropic = __esm({
|
|
|
455
897
|
let id = "";
|
|
456
898
|
for (let i = 0; i < 8; i++) {
|
|
457
899
|
const res = await this.client.messages.create(
|
|
458
|
-
buildParams(spec, req, messages)
|
|
900
|
+
buildParams(spec, req, messages),
|
|
901
|
+
{ signal }
|
|
459
902
|
);
|
|
460
903
|
id = res.id;
|
|
461
904
|
addUsage(usage, mapUsage(res.usage));
|
|
@@ -469,20 +912,22 @@ var init_anthropic = __esm({
|
|
|
469
912
|
}
|
|
470
913
|
return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
|
|
471
914
|
} catch (err) {
|
|
472
|
-
mapProviderError(err, "anthropic");
|
|
915
|
+
mapProviderError(err, "anthropic", signal);
|
|
473
916
|
}
|
|
474
917
|
}
|
|
475
918
|
async *stream(spec, req) {
|
|
919
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
476
920
|
try {
|
|
477
921
|
const events = this.client.messages.stream(
|
|
478
|
-
buildParams(spec, req, toAnthropicMessages(req))
|
|
922
|
+
buildParams(spec, req, toAnthropicMessages(req)),
|
|
923
|
+
{ signal }
|
|
479
924
|
);
|
|
480
925
|
for await (const raw of events) {
|
|
481
926
|
const ev = mapStreamEvent(raw, req);
|
|
482
927
|
if (ev) yield ev;
|
|
483
928
|
}
|
|
484
929
|
} catch (err) {
|
|
485
|
-
yield { type: "error", error: normalizeError(err, "anthropic").toShape() };
|
|
930
|
+
yield { type: "error", error: normalizeError(err, "anthropic", signal).toShape() };
|
|
486
931
|
}
|
|
487
932
|
}
|
|
488
933
|
};
|
|
@@ -647,9 +1092,10 @@ function parseArgs(raw) {
|
|
|
647
1092
|
if (!raw) return {};
|
|
648
1093
|
try {
|
|
649
1094
|
const parsed = JSON.parse(raw);
|
|
650
|
-
return parsed
|
|
651
|
-
} catch {
|
|
652
|
-
|
|
1095
|
+
return toolInput(parsed, "openai", "function");
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
if (error instanceof ToolInputError) throw error;
|
|
1098
|
+
throw new ToolInputError("[openai] tool returned malformed JSON arguments.", { cause: error });
|
|
653
1099
|
}
|
|
654
1100
|
}
|
|
655
1101
|
function mapFinish(reason) {
|
|
@@ -676,6 +1122,8 @@ function mapUsage2(u) {
|
|
|
676
1122
|
var init_response2 = __esm({
|
|
677
1123
|
"src/providers/openai/response.ts"() {
|
|
678
1124
|
"use strict";
|
|
1125
|
+
init_shape();
|
|
1126
|
+
init_tool_input();
|
|
679
1127
|
}
|
|
680
1128
|
});
|
|
681
1129
|
|
|
@@ -752,6 +1200,7 @@ var init_openai = __esm({
|
|
|
752
1200
|
init_request3();
|
|
753
1201
|
init_response2();
|
|
754
1202
|
init_stream2();
|
|
1203
|
+
init_abort();
|
|
755
1204
|
OpenAIProvider = class {
|
|
756
1205
|
id = "openai";
|
|
757
1206
|
client;
|
|
@@ -759,23 +1208,27 @@ var init_openai = __esm({
|
|
|
759
1208
|
this.client = client ?? new import_openai2.default({ apiKey });
|
|
760
1209
|
}
|
|
761
1210
|
async create(spec, req) {
|
|
1211
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
762
1212
|
try {
|
|
763
1213
|
const res = await this.client.chat.completions.create(
|
|
764
|
-
buildParams2(spec, req, false)
|
|
1214
|
+
buildParams2(spec, req, false),
|
|
1215
|
+
{ signal }
|
|
765
1216
|
);
|
|
766
1217
|
return mapResponse(res, req);
|
|
767
1218
|
} catch (err) {
|
|
768
|
-
mapProviderError(err, "openai");
|
|
1219
|
+
mapProviderError(err, "openai", signal);
|
|
769
1220
|
}
|
|
770
1221
|
}
|
|
771
1222
|
async *stream(spec, req) {
|
|
1223
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
772
1224
|
try {
|
|
773
1225
|
const stream = await this.client.chat.completions.create(
|
|
774
|
-
buildParams2(spec, req, true)
|
|
1226
|
+
buildParams2(spec, req, true),
|
|
1227
|
+
{ signal }
|
|
775
1228
|
);
|
|
776
1229
|
yield* mapStream(stream, req);
|
|
777
1230
|
} catch (err) {
|
|
778
|
-
yield { type: "error", error: normalizeError(err, "openai").toShape() };
|
|
1231
|
+
yield { type: "error", error: normalizeError(err, "openai", signal).toShape() };
|
|
779
1232
|
}
|
|
780
1233
|
}
|
|
781
1234
|
};
|
|
@@ -878,7 +1331,7 @@ function mapResponse2(res, req) {
|
|
|
878
1331
|
else if (p.functionCall) {
|
|
879
1332
|
sawToolUse = true;
|
|
880
1333
|
const name = p.functionCall.name ?? "";
|
|
881
|
-
content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: p.functionCall.args ?? {} });
|
|
1334
|
+
content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: toolInput(p.functionCall.args ?? {}, "google", name) });
|
|
882
1335
|
}
|
|
883
1336
|
}
|
|
884
1337
|
return {
|
|
@@ -919,6 +1372,7 @@ function mapUsage3(res) {
|
|
|
919
1372
|
var init_response3 = __esm({
|
|
920
1373
|
"src/providers/google/response.ts"() {
|
|
921
1374
|
"use strict";
|
|
1375
|
+
init_tool_input();
|
|
922
1376
|
}
|
|
923
1377
|
});
|
|
924
1378
|
|
|
@@ -990,6 +1444,7 @@ var init_google = __esm({
|
|
|
990
1444
|
init_request4();
|
|
991
1445
|
init_response3();
|
|
992
1446
|
init_stream3();
|
|
1447
|
+
init_abort();
|
|
993
1448
|
GoogleProvider = class {
|
|
994
1449
|
id = "google";
|
|
995
1450
|
client;
|
|
@@ -997,19 +1452,25 @@ var init_google = __esm({
|
|
|
997
1452
|
this.client = client ?? new import_genai2.GoogleGenAI({ apiKey });
|
|
998
1453
|
}
|
|
999
1454
|
async create(spec, req) {
|
|
1455
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
1000
1456
|
try {
|
|
1001
|
-
const
|
|
1457
|
+
const params = buildParams3(spec, req);
|
|
1458
|
+
params.config = { ...params.config, abortSignal: signal };
|
|
1459
|
+
const res = await this.client.models.generateContent(params);
|
|
1002
1460
|
return mapResponse2(res, req);
|
|
1003
1461
|
} catch (err) {
|
|
1004
|
-
mapProviderError(err, "google");
|
|
1462
|
+
mapProviderError(err, "google", signal);
|
|
1005
1463
|
}
|
|
1006
1464
|
}
|
|
1007
1465
|
async *stream(spec, req) {
|
|
1466
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
1008
1467
|
try {
|
|
1009
|
-
const
|
|
1468
|
+
const params = buildParams3(spec, req);
|
|
1469
|
+
params.config = { ...params.config, abortSignal: signal };
|
|
1470
|
+
const iter = await this.client.models.generateContentStream(params);
|
|
1010
1471
|
yield* mapStream2(iter, req);
|
|
1011
1472
|
} catch (err) {
|
|
1012
|
-
yield { type: "error", error: normalizeError(err, "google").toShape() };
|
|
1473
|
+
yield { type: "error", error: normalizeError(err, "google", signal).toShape() };
|
|
1013
1474
|
}
|
|
1014
1475
|
}
|
|
1015
1476
|
};
|
|
@@ -1022,11 +1483,13 @@ __export(index_exports, {
|
|
|
1022
1483
|
AGENT_SCHEMA: () => AGENT_SCHEMA,
|
|
1023
1484
|
ANTHROPIC_MODELS: () => ANTHROPIC_MODELS,
|
|
1024
1485
|
AuthError: () => AuthError,
|
|
1486
|
+
CancelledError: () => CancelledError,
|
|
1025
1487
|
CapabilityError: () => CapabilityError,
|
|
1026
1488
|
ConfigError: () => ConfigError,
|
|
1027
1489
|
ContextWindowError: () => ContextWindowError,
|
|
1028
1490
|
DEFAULT_CATALOG: () => DEFAULT_CATALOG,
|
|
1029
1491
|
DEFAULT_SECRET_NAMES: () => DEFAULT_SECRET_NAMES,
|
|
1492
|
+
DeadlineExceededError: () => DeadlineExceededError,
|
|
1030
1493
|
GOOGLE_MODELS: () => GOOGLE_MODELS,
|
|
1031
1494
|
InMemoryScope: () => InMemoryScope,
|
|
1032
1495
|
InvalidRequestError: () => InvalidRequestError,
|
|
@@ -1037,6 +1500,7 @@ __export(index_exports, {
|
|
|
1037
1500
|
ProviderError: () => ProviderError,
|
|
1038
1501
|
RateLimitError: () => RateLimitError,
|
|
1039
1502
|
TaintError: () => TaintError,
|
|
1503
|
+
ToolInputError: () => ToolInputError,
|
|
1040
1504
|
addUsage: () => addUsage,
|
|
1041
1505
|
assertSinkAcceptsTaint: () => assertSinkAcceptsTaint,
|
|
1042
1506
|
blocksOf: () => blocksOf,
|
|
@@ -1082,6 +1546,7 @@ __export(index_exports, {
|
|
|
1082
1546
|
structuredMatch: () => structuredMatch,
|
|
1083
1547
|
taint: () => taint,
|
|
1084
1548
|
toOracleTool: () => toOracleTool,
|
|
1549
|
+
validateJsonSchema: () => validateJsonSchema,
|
|
1085
1550
|
validateRequest: () => validateRequest
|
|
1086
1551
|
});
|
|
1087
1552
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1091,7 +1556,7 @@ init_shape();
|
|
|
1091
1556
|
|
|
1092
1557
|
// src/shape/capabilities.ts
|
|
1093
1558
|
init_shape();
|
|
1094
|
-
function chatCapabilities(
|
|
1559
|
+
function chatCapabilities(overrides2 = {}) {
|
|
1095
1560
|
return {
|
|
1096
1561
|
kind: "chat",
|
|
1097
1562
|
textIn: true,
|
|
@@ -1104,7 +1569,7 @@ function chatCapabilities(overrides = {}) {
|
|
|
1104
1569
|
streaming: true,
|
|
1105
1570
|
structuredOutput: true,
|
|
1106
1571
|
webSearch: false,
|
|
1107
|
-
...
|
|
1572
|
+
...overrides2
|
|
1108
1573
|
};
|
|
1109
1574
|
}
|
|
1110
1575
|
function validateRequest(spec, req) {
|
|
@@ -1196,6 +1661,15 @@ var OPENAI_MODELS = [
|
|
|
1196
1661
|
capabilities: chatCapabilities({ effort: true }),
|
|
1197
1662
|
contextWindow: 4e5
|
|
1198
1663
|
},
|
|
1664
|
+
{
|
|
1665
|
+
// Compact GPT-5.4 reasoning model (effort knob), same 400K context as the
|
|
1666
|
+
// rest of the GPT-5 line. The default for odla-o11y AI triage.
|
|
1667
|
+
id: "gpt-5.4-mini",
|
|
1668
|
+
provider: "openai",
|
|
1669
|
+
nativeId: "gpt-5.4-mini",
|
|
1670
|
+
capabilities: chatCapabilities({ effort: true }),
|
|
1671
|
+
contextWindow: 4e5
|
|
1672
|
+
},
|
|
1199
1673
|
{
|
|
1200
1674
|
id: "gpt-5",
|
|
1201
1675
|
provider: "openai",
|
|
@@ -1313,11 +1787,29 @@ function providersInCatalog(catalog) {
|
|
|
1313
1787
|
}
|
|
1314
1788
|
|
|
1315
1789
|
// src/providers/registry.ts
|
|
1790
|
+
var CLIENT_TTL_MS = 6e4;
|
|
1316
1791
|
var cache = /* @__PURE__ */ new Map();
|
|
1792
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
1793
|
+
var overrides = /* @__PURE__ */ new Map();
|
|
1317
1794
|
async function getProvider(id, apiKey) {
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1795
|
+
const override = overrides.get(id);
|
|
1796
|
+
if (override?.apiKey === apiKey) return override.provider;
|
|
1797
|
+
const fingerprint = await fingerprintKey(apiKey);
|
|
1798
|
+
const existing = cache.get(id);
|
|
1799
|
+
if (existing?.fingerprint === fingerprint && existing.expiresAt > Date.now()) return existing.provider;
|
|
1800
|
+
if (existing) cache.delete(id);
|
|
1801
|
+
const pending = inflight.get(id);
|
|
1802
|
+
if (pending?.fingerprint === fingerprint) return pending.promise;
|
|
1803
|
+
const promise = createProvider(id, apiKey).then((provider) => {
|
|
1804
|
+
cache.set(id, { fingerprint, provider, expiresAt: Date.now() + CLIENT_TTL_MS });
|
|
1805
|
+
return provider;
|
|
1806
|
+
}).finally(() => {
|
|
1807
|
+
if (inflight.get(id)?.promise === promise) inflight.delete(id);
|
|
1808
|
+
});
|
|
1809
|
+
inflight.set(id, { fingerprint, promise });
|
|
1810
|
+
return promise;
|
|
1811
|
+
}
|
|
1812
|
+
async function createProvider(id, apiKey) {
|
|
1321
1813
|
let provider;
|
|
1322
1814
|
switch (id) {
|
|
1323
1815
|
case "anthropic": {
|
|
@@ -1336,14 +1828,19 @@ async function getProvider(id, apiKey) {
|
|
|
1336
1828
|
break;
|
|
1337
1829
|
}
|
|
1338
1830
|
}
|
|
1339
|
-
cache.set(cacheKey, provider);
|
|
1340
1831
|
return provider;
|
|
1341
1832
|
}
|
|
1833
|
+
async function fingerprintKey(apiKey) {
|
|
1834
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(apiKey));
|
|
1835
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1836
|
+
}
|
|
1342
1837
|
function registerProvider(id, apiKey, provider) {
|
|
1343
|
-
|
|
1838
|
+
overrides.set(id, { apiKey, provider });
|
|
1344
1839
|
}
|
|
1345
1840
|
function clearProviderCache() {
|
|
1346
1841
|
cache.clear();
|
|
1842
|
+
inflight.clear();
|
|
1843
|
+
overrides.clear();
|
|
1347
1844
|
}
|
|
1348
1845
|
|
|
1349
1846
|
// src/client/keys.ts
|
|
@@ -1414,7 +1911,9 @@ function init(opts = {}) {
|
|
|
1414
1911
|
messages: [{ role: "user", content: c.user }],
|
|
1415
1912
|
tools: [{ name: c.tool.name, description: c.tool.description ?? "", inputSchema: c.tool.parameters }],
|
|
1416
1913
|
toolChoice: { type: "tool", name: c.tool.name },
|
|
1417
|
-
maxTokens: c.maxTokens ?? 4096
|
|
1914
|
+
maxTokens: c.maxTokens ?? 4096,
|
|
1915
|
+
signal: c.signal,
|
|
1916
|
+
deadline: c.deadline
|
|
1418
1917
|
};
|
|
1419
1918
|
const { spec, provider } = await resolveProviderFor(model);
|
|
1420
1919
|
validateRequest(spec, req);
|
|
@@ -1423,6 +1922,11 @@ function init(opts = {}) {
|
|
|
1423
1922
|
if (!block || block.type !== "tool_use") {
|
|
1424
1923
|
throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`);
|
|
1425
1924
|
}
|
|
1925
|
+
const validation = validateJsonSchema(c.tool.parameters, block.input);
|
|
1926
|
+
if (!validation.valid) {
|
|
1927
|
+
const detail = validation.issues.slice(0, 5).map((i) => `${i.path} ${i.message}`).join("; ");
|
|
1928
|
+
throw new ToolInputError(`Tool "${c.tool.name}" returned invalid input: ${detail}`, { tool: c.tool.name });
|
|
1929
|
+
}
|
|
1426
1930
|
return { value: block.input, usage: res.usage };
|
|
1427
1931
|
};
|
|
1428
1932
|
const search = async (c) => {
|
|
@@ -1432,7 +1936,9 @@ function init(opts = {}) {
|
|
|
1432
1936
|
system: c.system,
|
|
1433
1937
|
messages: [{ role: "user", content: c.user }],
|
|
1434
1938
|
webSearch: true,
|
|
1435
|
-
maxTokens: c.maxTokens ?? 8192
|
|
1939
|
+
maxTokens: c.maxTokens ?? 8192,
|
|
1940
|
+
signal: c.signal,
|
|
1941
|
+
deadline: c.deadline
|
|
1436
1942
|
};
|
|
1437
1943
|
const { spec, provider } = await resolveProviderFor(model);
|
|
1438
1944
|
validateRequest(spec, req);
|
|
@@ -1461,20 +1967,20 @@ function generateLlmsTxt(catalog = DEFAULT_CATALOG) {
|
|
|
1461
1967
|
const models = Object.values(catalog).map((s) => `- \`${s.id}\` (${s.provider}) \u2014 ${capsSummary(s.capabilities)}`).join("\n");
|
|
1462
1968
|
return `# @odla-ai/ai \u2014 LLM context
|
|
1463
1969
|
|
|
1464
|
-
>
|
|
1465
|
-
>
|
|
1466
|
-
>
|
|
1970
|
+
> EARLY ACCESS \u2014 pre-1.0. Agents work from bounded runbooks; humans approve
|
|
1971
|
+
> credentials, production changes, releases, and merges. APIs and exact package
|
|
1972
|
+
> availability can change. Review documented guarantees and limitations; this
|
|
1973
|
+
> software is MIT-licensed and provided without warranty.
|
|
1467
1974
|
|
|
1468
1975
|
One general-purpose interface for AI inference across Anthropic (Claude), OpenAI
|
|
1469
1976
|
(GPT), and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent
|
|
1470
1977
|
engine and an eval harness. Library-only: import in-process, bring your own keys.
|
|
1471
|
-
|
|
1978
|
+
Targets Node 20+ and Cloudflare Workers. Provider adapters are runtime-lazy, but
|
|
1979
|
+
all provider SDKs are package dependencies. Never expose long-lived keys in a browser.
|
|
1472
1980
|
|
|
1473
1981
|
## Install
|
|
1474
1982
|
|
|
1475
1983
|
npm i @odla-ai/ai
|
|
1476
|
-
# provider SDKs are peer-lazy-loaded; install those you use:
|
|
1477
|
-
# @anthropic-ai/sdk openai @google/genai
|
|
1478
1984
|
# optional, for odla-db-backed storage + secrets:
|
|
1479
1985
|
# @odla-ai/db
|
|
1480
1986
|
|
|
@@ -1488,7 +1994,8 @@ Isomorphic (Node 20+, Cloudflare Workers, browser). Provider SDKs are lazy-loade
|
|
|
1488
1994
|
- \`ai.stream(input)\` \u2192 async iterable of normalized events: message_start \u2192
|
|
1489
1995
|
content_block_start \u2192 content_block_delta {text_delta|thinking_delta|input_json_delta}
|
|
1490
1996
|
\u2192 content_block_stop \u2192 message_delta \u2192 message_stop (plus \`error\`).
|
|
1491
|
-
- \`ai.extract<T>({ model, user, tool })\` \u2192 forced tool call; returns
|
|
1997
|
+
- \`ai.extract<T>({ model, user, tool })\` \u2192 forced tool call; returns an object
|
|
1998
|
+
validated against the declared JSON Schema. Unsupported assertions fail closed.
|
|
1492
1999
|
- \`ai.search({ model, user })\` \u2192 web-search-grounded prose.
|
|
1493
2000
|
|
|
1494
2001
|
## Content blocks (multimodal)
|
|
@@ -1507,7 +2014,15 @@ audio block to a Claude model throws CapabilityError before any network call.
|
|
|
1507
2014
|
// run.finalText, run.toolCalls, run.usage, run.steps, run.stoppedReason
|
|
1508
2015
|
|
|
1509
2016
|
Tools may carry taint labels (outputTaint / acceptsTaint) for a CaMeL-style
|
|
1510
|
-
prompt-injection gate. Personas may carry a MemoryScope.
|
|
2017
|
+
prompt-injection gate. Personas may carry a MemoryScope. Tool inputs are schema-
|
|
2018
|
+
validated before handlers run. Pass signal/deadline and a run budget
|
|
2019
|
+
({maxInputTokens,maxOutputTokens,maxTotalTokens,maxToolCalls}) to bound follow-up
|
|
2020
|
+
effects and requested output. Input and total limits are post-turn ceilings:
|
|
2021
|
+
provider usage is unavailable before the first response, so they cannot
|
|
2022
|
+
tokenizer-bound the first prompt. Output and remaining-total limits cap the next
|
|
2023
|
+
request's maxTokens. Token limits must be positive integers; maxToolCalls may be
|
|
2024
|
+
zero. Budget exits pair every requested tool_use with an error tool_result before
|
|
2025
|
+
memory is persisted.
|
|
1511
2026
|
|
|
1512
2027
|
## Skills
|
|
1513
2028
|
|
|
@@ -1534,14 +2049,17 @@ device-authorization handshake for a scoped, revocable token, then provisions.
|
|
|
1534
2049
|
import { requestToken, init as odlaInit } from "@odla-ai/db";
|
|
1535
2050
|
import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, init } from "@odla-ai/ai";
|
|
1536
2051
|
|
|
1537
|
-
|
|
1538
|
-
const
|
|
2052
|
+
const ODLA_PLATFORM = "https://odla.ai";
|
|
2053
|
+
const ODLA_DB_URL = "https://db.odla.ai";
|
|
2054
|
+
|
|
2055
|
+
// 1. Handshake: the human approves a short code at odla.ai.
|
|
2056
|
+
const { token } = await requestToken({ endpoint: ODLA_PLATFORM, onCode: ({ userCode }) => show(userCode) });
|
|
1539
2057
|
|
|
1540
2058
|
// 2. Provision: create app, mint app key, push agent schema, store BYO provider keys as secrets.
|
|
1541
|
-
const { appId, appKey } = await provisionAgentApp({ endpoint:
|
|
2059
|
+
const { appId, appKey } = await provisionAgentApp({ endpoint: ODLA_PLATFORM, token, providerKeys: { openai: OPENAI_KEY } });
|
|
1542
2060
|
|
|
1543
2061
|
// 3. Runtime: read keys from odla-db secrets; persist memory + run traces.
|
|
1544
|
-
const db = odlaInit({ appId, adminToken: appKey, endpoint:
|
|
2062
|
+
const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
|
|
1545
2063
|
const ai = init({ resolveKey: odlaDbKeyResolver(db) });
|
|
1546
2064
|
const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
|
|
1547
2065
|
const run = await runAgent(ai, persona, { input: "hello" });
|
|
@@ -1567,16 +2085,22 @@ env's odla-db tenant secrets, write-only for operators). One call reads both:
|
|
|
1567
2085
|
platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
|
|
1568
2086
|
// ai.chat / ai.stream / ai.extract \u2014 provider/model from public-config
|
|
1569
2087
|
// (cached ~60s, so Studio changes apply without redeploys); the key is
|
|
1570
|
-
// fetched from the vault
|
|
2088
|
+
// fetched from the vault via a finite-TTL odlaDbKeyResolver.
|
|
1571
2089
|
|
|
1572
2090
|
The only secret the Worker carries is its odla-db app key. Rotating the LLM
|
|
1573
|
-
key = writing the secret again;
|
|
2091
|
+
key = writing the secret again; the default warm-isolate key TTL is 60 seconds
|
|
2092
|
+
and cacheVersion provides immediate invalidation. Provider SDK clients are
|
|
2093
|
+
fingerprinted, bounded to one generation per provider, and expire after 60
|
|
2094
|
+
seconds. The platform provider is enforced: models from other providers are
|
|
2095
|
+
absent from the facade catalog. If no default model is configured, each call
|
|
2096
|
+
must name a model for that provider. Only public config is shared; tenant-bound
|
|
2097
|
+
Ai facades are not cached globally.
|
|
1574
2098
|
|
|
1575
2099
|
## Errors
|
|
1576
2100
|
|
|
1577
2101
|
Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
|
|
1578
2102
|
config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
|
|
1579
|
-
tool_input_invalid, provider_error.
|
|
2103
|
+
tool_input_invalid, cancelled, deadline_exceeded, provider_error.
|
|
1580
2104
|
|
|
1581
2105
|
## Models
|
|
1582
2106
|
|
|
@@ -1592,6 +2116,7 @@ init_errors2();
|
|
|
1592
2116
|
|
|
1593
2117
|
// src/agent/loop.ts
|
|
1594
2118
|
init_shape();
|
|
2119
|
+
init_abort();
|
|
1595
2120
|
|
|
1596
2121
|
// src/agent/tools.ts
|
|
1597
2122
|
function toOracleTool(t) {
|
|
@@ -1633,6 +2158,7 @@ function assertSinkAcceptsTaint(sink, allowed, actual) {
|
|
|
1633
2158
|
|
|
1634
2159
|
// src/agent/loop.ts
|
|
1635
2160
|
async function runAgent(inference, persona, input) {
|
|
2161
|
+
validateRunLimits(persona.maxSteps, input.budget);
|
|
1636
2162
|
const { system, tools } = composeSkills(persona.system, persona.tools, persona.skills);
|
|
1637
2163
|
const handlerByName = /* @__PURE__ */ new Map();
|
|
1638
2164
|
for (const t of tools) if (t.handler) handlerByName.set(t.name, t);
|
|
@@ -1644,21 +2170,26 @@ async function runAgent(inference, persona, input) {
|
|
|
1644
2170
|
const toolCalls = [];
|
|
1645
2171
|
const accumulatedTaint = /* @__PURE__ */ new Set(["llm_inherited"]);
|
|
1646
2172
|
const maxSteps = persona.maxSteps ?? 8;
|
|
2173
|
+
const runSignal = signalWithDeadline(input.signal, input.deadline);
|
|
1647
2174
|
let response;
|
|
1648
2175
|
let stoppedReason = "max_steps";
|
|
1649
2176
|
let steps = 0;
|
|
2177
|
+
let attemptedToolCalls = 0;
|
|
1650
2178
|
while (steps < maxSteps) {
|
|
1651
2179
|
steps++;
|
|
2180
|
+
const remainingOutput = remainingOutputTokens(usage, input.budget);
|
|
1652
2181
|
response = await inference.chat({
|
|
1653
2182
|
model: persona.model,
|
|
1654
2183
|
system,
|
|
1655
2184
|
messages,
|
|
1656
2185
|
tools: tools.length > 0 ? tools.map(toOracleTool) : void 0,
|
|
1657
|
-
maxTokens: persona.maxTokens ?? 4096,
|
|
2186
|
+
maxTokens: Math.min(persona.maxTokens ?? 4096, remainingOutput ?? Number.POSITIVE_INFINITY),
|
|
1658
2187
|
effort: persona.effort,
|
|
1659
2188
|
thinking: persona.thinking,
|
|
1660
2189
|
temperature: persona.temperature,
|
|
1661
|
-
webSearch: persona.webSearch
|
|
2190
|
+
webSearch: persona.webSearch,
|
|
2191
|
+
signal: runSignal,
|
|
2192
|
+
deadline: input.deadline
|
|
1662
2193
|
});
|
|
1663
2194
|
addUsage(usage, response.usage);
|
|
1664
2195
|
messages.push({ role: "assistant", content: response.content });
|
|
@@ -1671,8 +2202,22 @@ async function runAgent(inference, persona, input) {
|
|
|
1671
2202
|
stoppedReason = "end_turn";
|
|
1672
2203
|
break;
|
|
1673
2204
|
}
|
|
2205
|
+
if (budgetExceeded(usage, input.budget)) {
|
|
2206
|
+
stoppedReason = "budget_exhausted";
|
|
2207
|
+
messages.push({
|
|
2208
|
+
role: "user",
|
|
2209
|
+
content: toolUses.map((toolUse) => errorResult(toolUse.id, "Run budget exhausted before tool execution."))
|
|
2210
|
+
});
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
1674
2213
|
const resultBlocks = [];
|
|
1675
2214
|
for (const toolUse of toolUses) {
|
|
2215
|
+
if (input.budget?.maxToolCalls !== void 0 && attemptedToolCalls >= input.budget.maxToolCalls) {
|
|
2216
|
+
stoppedReason = "budget_exhausted";
|
|
2217
|
+
resultBlocks.push(errorResult(toolUse.id, "Run tool-call budget exhausted before execution."));
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
attemptedToolCalls++;
|
|
1676
2221
|
const def = handlerByName.get(toolUse.name);
|
|
1677
2222
|
if (!def || !def.handler) {
|
|
1678
2223
|
resultBlocks.push(errorResult(toolUse.id, `No local handler for tool "${toolUse.name}".`));
|
|
@@ -1680,16 +2225,26 @@ async function runAgent(inference, persona, input) {
|
|
|
1680
2225
|
}
|
|
1681
2226
|
if (def.acceptsTaint) assertSinkAcceptsTaint(def.name, def.acceptsTaint, accumulatedTaint);
|
|
1682
2227
|
let output;
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
output = { content: `Tool "${toolUse.name}"
|
|
2228
|
+
const validation = validateJsonSchema(def.inputSchema, toolUse.input);
|
|
2229
|
+
if (!validation.valid) {
|
|
2230
|
+
const detail = validation.issues.slice(0, 5).map((i) => `${i.path} ${i.message}`).join("; ");
|
|
2231
|
+
output = { content: `Tool "${toolUse.name}" input is invalid: ${detail}`, isError: true };
|
|
2232
|
+
} else {
|
|
2233
|
+
throwIfAborted(runSignal);
|
|
2234
|
+
try {
|
|
2235
|
+
output = await def.handler(toolUse.input, { taint: new Set(accumulatedTaint), signal: runSignal });
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
if (error instanceof CancelledError || error instanceof DeadlineExceededError) throw error;
|
|
2238
|
+
throwIfAborted(runSignal);
|
|
2239
|
+
output = { content: `Tool "${toolUse.name}" failed.`, isError: true };
|
|
2240
|
+
}
|
|
1687
2241
|
}
|
|
1688
2242
|
toolCalls.push({ toolUse, output });
|
|
1689
2243
|
resultBlocks.push({ type: "tool_result", toolUseId: toolUse.id, content: output.content, isError: output.isError });
|
|
1690
2244
|
for (const label of def.outputTaint ?? []) accumulatedTaint.add(label);
|
|
1691
2245
|
}
|
|
1692
2246
|
messages.push({ role: "user", content: resultBlocks });
|
|
2247
|
+
if (stoppedReason === "budget_exhausted") break;
|
|
1693
2248
|
}
|
|
1694
2249
|
if (!response) throw new Error("runAgent: maxSteps must be at least 1");
|
|
1695
2250
|
if (persona.memory) await persona.memory.append(messages.slice(runStartIndex));
|
|
@@ -1703,6 +2258,36 @@ async function runAgent(inference, persona, input) {
|
|
|
1703
2258
|
stoppedReason
|
|
1704
2259
|
};
|
|
1705
2260
|
}
|
|
2261
|
+
function budgetExceeded(usage, budget) {
|
|
2262
|
+
if (!budget) return false;
|
|
2263
|
+
if (budget.maxInputTokens !== void 0 && usage.inputTokens >= budget.maxInputTokens) return true;
|
|
2264
|
+
if (budget.maxOutputTokens !== void 0 && usage.outputTokens >= budget.maxOutputTokens) return true;
|
|
2265
|
+
if (budget.maxTotalTokens !== void 0 && usage.inputTokens + usage.outputTokens >= budget.maxTotalTokens) return true;
|
|
2266
|
+
return false;
|
|
2267
|
+
}
|
|
2268
|
+
function remainingOutputTokens(usage, budget) {
|
|
2269
|
+
if (!budget) return void 0;
|
|
2270
|
+
const remaining = [
|
|
2271
|
+
budget.maxOutputTokens === void 0 ? void 0 : budget.maxOutputTokens - usage.outputTokens,
|
|
2272
|
+
budget.maxTotalTokens === void 0 ? void 0 : budget.maxTotalTokens - usage.inputTokens - usage.outputTokens
|
|
2273
|
+
].filter((value) => value !== void 0);
|
|
2274
|
+
return remaining.length === 0 ? void 0 : Math.max(1, Math.min(...remaining));
|
|
2275
|
+
}
|
|
2276
|
+
function validateRunLimits(maxSteps, budget) {
|
|
2277
|
+
if (maxSteps !== void 0 && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
|
|
2278
|
+
throw new ConfigError("persona.maxSteps must be a positive integer.");
|
|
2279
|
+
}
|
|
2280
|
+
if (!budget) return;
|
|
2281
|
+
for (const key of ["maxInputTokens", "maxOutputTokens", "maxTotalTokens"]) {
|
|
2282
|
+
const value = budget[key];
|
|
2283
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
2284
|
+
throw new ConfigError(`budget.${key} must be a positive integer.`);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
if (budget.maxToolCalls !== void 0 && (!Number.isInteger(budget.maxToolCalls) || budget.maxToolCalls < 0)) {
|
|
2288
|
+
throw new ConfigError("budget.maxToolCalls must be a non-negative integer.");
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
1706
2291
|
function errorResult(toolUseId, message) {
|
|
1707
2292
|
return { type: "tool_result", toolUseId, content: message, isError: true };
|
|
1708
2293
|
}
|
|
@@ -2007,22 +2592,32 @@ var DEFAULT_SECRET_NAMES = {
|
|
|
2007
2592
|
};
|
|
2008
2593
|
function odlaDbKeyResolver(db, opts = {}) {
|
|
2009
2594
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
2010
|
-
const
|
|
2595
|
+
const ttlMs = opts.cache === false ? 0 : Math.max(0, opts.ttlMs ?? 6e4);
|
|
2011
2596
|
const cache3 = /* @__PURE__ */ new Map();
|
|
2012
2597
|
return async (provider) => {
|
|
2013
|
-
|
|
2598
|
+
const version = typeof opts.cacheVersion === "function" ? opts.cacheVersion(provider) : opts.cacheVersion ?? "";
|
|
2599
|
+
if (ttlMs > 0) {
|
|
2014
2600
|
const hit = cache3.get(provider);
|
|
2015
|
-
if (hit
|
|
2601
|
+
if (hit?.version === version && hit.expiresAt > Date.now()) return hit.value;
|
|
2602
|
+
cache3.delete(provider);
|
|
2016
2603
|
}
|
|
2017
2604
|
try {
|
|
2018
2605
|
const key = await db.secrets.get(nameFor(provider));
|
|
2019
|
-
if (
|
|
2606
|
+
if (ttlMs > 0) cache3.set(provider, { version, value: key, expiresAt: Date.now() + ttlMs });
|
|
2020
2607
|
return key;
|
|
2021
|
-
} catch {
|
|
2022
|
-
return void 0;
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
if (isMissingSecret(error)) return void 0;
|
|
2610
|
+
throw error;
|
|
2023
2611
|
}
|
|
2024
2612
|
};
|
|
2025
2613
|
}
|
|
2614
|
+
function isMissingSecret(error) {
|
|
2615
|
+
if (!error || typeof error !== "object") return false;
|
|
2616
|
+
const rec = error;
|
|
2617
|
+
if (rec.status === 404 || rec.statusCode === 404 || rec.code === "not_found") return true;
|
|
2618
|
+
const message = error instanceof Error ? error.message : "";
|
|
2619
|
+
return /\b404\b/.test(message);
|
|
2620
|
+
}
|
|
2026
2621
|
|
|
2027
2622
|
// src/store/platform.ts
|
|
2028
2623
|
init_shape();
|
|
@@ -2054,19 +2649,22 @@ async function initFromPlatform(opts) {
|
|
|
2054
2649
|
const key = `${opts.platform}|${opts.appId}|${opts.env}`;
|
|
2055
2650
|
const now = Date.now();
|
|
2056
2651
|
const hit = ttl > 0 ? cache2.get(key) : void 0;
|
|
2057
|
-
|
|
2058
|
-
const { provider, model } =
|
|
2059
|
-
if (
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2652
|
+
const config = hit && hit.expiresAt > now ? hit.value : await fetchAiConfig(opts);
|
|
2653
|
+
const { provider, model } = config;
|
|
2654
|
+
if (ttl > 0 && (!hit || hit.expiresAt <= now)) cache2.set(key, { value: config, expiresAt: now + ttl });
|
|
2655
|
+
const sourceCatalog = opts.initOptions?.catalog ?? DEFAULT_CATALOG;
|
|
2656
|
+
const catalog = Object.fromEntries(
|
|
2657
|
+
Object.entries(sourceCatalog).filter(([, spec]) => spec.provider === provider)
|
|
2658
|
+
);
|
|
2659
|
+
const defaultModel = model ?? opts.initOptions?.defaultModel;
|
|
2660
|
+
if (defaultModel) resolveModel(catalog, defaultModel);
|
|
2063
2661
|
const ai = init({
|
|
2064
2662
|
...opts.initOptions,
|
|
2065
|
-
|
|
2066
|
-
|
|
2663
|
+
catalog,
|
|
2664
|
+
resolveKey: odlaDbKeyResolver(opts.db, opts.keyResolverOptions),
|
|
2665
|
+
defaultModel
|
|
2067
2666
|
});
|
|
2068
2667
|
const value = { ai, provider, ...model ? { model } : {} };
|
|
2069
|
-
if (ttl > 0) cache2.set(key, { value, expiresAt: now + ttl });
|
|
2070
2668
|
return value;
|
|
2071
2669
|
}
|
|
2072
2670
|
|