@odla-ai/ai 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +68 -11
- package/dist/{anthropic-JXDXR7O7.js → anthropic-2NPMHJZT.js} +67 -51
- package/dist/anthropic-2NPMHJZT.js.map +1 -0
- package/dist/chunk-OX4TA4ZH.js +16 -0
- package/dist/chunk-OX4TA4ZH.js.map +1 -0
- package/dist/{chunk-LANGYP65.js → chunk-PXXCN2EU.js} +65 -46
- package/dist/chunk-PXXCN2EU.js.map +1 -0
- package/dist/chunk-U7F6VJCV.js +9 -0
- package/dist/chunk-U7F6VJCV.js.map +1 -0
- package/dist/{google-3TFOFIQO.js → google-3XV2VWVB.js} +50 -31
- package/dist/google-3XV2VWVB.js.map +1 -0
- package/dist/index.cjs +867 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +194 -74
- package/dist/index.d.ts +194 -74
- package/dist/index.js +578 -57
- package/dist/index.js.map +1 -1
- package/dist/{openai-XZRXMKCC.js → openai-NTQCF577.js} +54 -36
- package/dist/openai-NTQCF577.js.map +1 -0
- package/llms.txt +35 -12
- package/package.json +12 -5
- package/dist/anthropic-JXDXR7O7.js.map +0 -1
- package/dist/chunk-LANGYP65.js.map +0 -1
- package/dist/google-3TFOFIQO.js.map +0 -1
- package/dist/openai-XZRXMKCC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
35
|
));
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
|
|
38
|
-
// src/shape/
|
|
38
|
+
// src/shape/blocks.ts
|
|
39
|
+
var init_blocks = __esm({
|
|
40
|
+
"src/shape/blocks.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/shape/usage.ts
|
|
39
46
|
function emptyUsage() {
|
|
40
47
|
return { inputTokens: 0, outputTokens: 0 };
|
|
41
48
|
}
|
|
@@ -49,39 +56,30 @@ function addUsage(into, more) {
|
|
|
49
56
|
into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;
|
|
50
57
|
}
|
|
51
58
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function extractText(content) {
|
|
77
|
-
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
78
|
-
}
|
|
79
|
-
function extractToolUses(content) {
|
|
80
|
-
return content.filter(isToolUseBlock);
|
|
81
|
-
}
|
|
82
|
-
var OdlaAIError, ConfigError, AuthError, RateLimitError, CapabilityError, ContextWindowError, InvalidRequestError, ProviderError;
|
|
83
|
-
var init_shape = __esm({
|
|
84
|
-
"src/shape/index.ts"() {
|
|
59
|
+
var init_usage = __esm({
|
|
60
|
+
"src/shape/usage.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// src/shape/request.ts
|
|
66
|
+
var init_request = __esm({
|
|
67
|
+
"src/shape/request.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/shape/events.ts
|
|
73
|
+
var init_events = __esm({
|
|
74
|
+
"src/shape/events.ts"() {
|
|
75
|
+
"use strict";
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// src/shape/errors.ts
|
|
80
|
+
var OdlaAIError, ConfigError, AuthError, RateLimitError, CapabilityError, ContextWindowError, InvalidRequestError, ToolInputError, CancelledError, DeadlineExceededError, ProviderError;
|
|
81
|
+
var init_errors = __esm({
|
|
82
|
+
"src/shape/errors.ts"() {
|
|
85
83
|
"use strict";
|
|
86
84
|
OdlaAIError = class extends Error {
|
|
87
85
|
code;
|
|
@@ -133,6 +131,23 @@ var init_shape = __esm({
|
|
|
133
131
|
super("invalid_request", message, opts);
|
|
134
132
|
}
|
|
135
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
|
+
};
|
|
136
151
|
ProviderError = class extends OdlaAIError {
|
|
137
152
|
constructor(message, opts) {
|
|
138
153
|
super("provider_error", message, opts);
|
|
@@ -141,6 +156,423 @@ var init_shape = __esm({
|
|
|
141
156
|
}
|
|
142
157
|
});
|
|
143
158
|
|
|
159
|
+
// src/shape/helpers.ts
|
|
160
|
+
function isTextBlock(b) {
|
|
161
|
+
return b.type === "text";
|
|
162
|
+
}
|
|
163
|
+
function isImageBlock(b) {
|
|
164
|
+
return b.type === "image";
|
|
165
|
+
}
|
|
166
|
+
function isAudioBlock(b) {
|
|
167
|
+
return b.type === "audio";
|
|
168
|
+
}
|
|
169
|
+
function isDocumentBlock(b) {
|
|
170
|
+
return b.type === "document";
|
|
171
|
+
}
|
|
172
|
+
function isToolUseBlock(b) {
|
|
173
|
+
return b.type === "tool_use";
|
|
174
|
+
}
|
|
175
|
+
function isToolResultBlock(b) {
|
|
176
|
+
return b.type === "tool_result";
|
|
177
|
+
}
|
|
178
|
+
function isThinkingBlock(b) {
|
|
179
|
+
return b.type === "thinking";
|
|
180
|
+
}
|
|
181
|
+
function blocksOf(content) {
|
|
182
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
183
|
+
}
|
|
184
|
+
function extractText(content) {
|
|
185
|
+
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
186
|
+
}
|
|
187
|
+
function extractToolUses(content) {
|
|
188
|
+
return content.filter(isToolUseBlock);
|
|
189
|
+
}
|
|
190
|
+
var init_helpers = __esm({
|
|
191
|
+
"src/shape/helpers.ts"() {
|
|
192
|
+
"use strict";
|
|
193
|
+
}
|
|
194
|
+
});
|
|
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
|
+
|
|
562
|
+
// src/shape/index.ts
|
|
563
|
+
var init_shape = __esm({
|
|
564
|
+
"src/shape/index.ts"() {
|
|
565
|
+
"use strict";
|
|
566
|
+
init_blocks();
|
|
567
|
+
init_usage();
|
|
568
|
+
init_request();
|
|
569
|
+
init_events();
|
|
570
|
+
init_errors();
|
|
571
|
+
init_helpers();
|
|
572
|
+
init_json_schema();
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
144
576
|
// src/providers/errors.ts
|
|
145
577
|
function statusOf(err) {
|
|
146
578
|
if (err && typeof err === "object") {
|
|
@@ -168,8 +600,10 @@ function messageOf(err) {
|
|
|
168
600
|
if (typeof err === "string") return err;
|
|
169
601
|
return "unknown provider error";
|
|
170
602
|
}
|
|
171
|
-
function normalizeError(err, provider) {
|
|
603
|
+
function normalizeError(err, provider, signal) {
|
|
172
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 });
|
|
173
607
|
const status = statusOf(err);
|
|
174
608
|
const message = messageOf(err);
|
|
175
609
|
const tagged = `[${provider}] ${message}`;
|
|
@@ -185,21 +619,27 @@ function normalizeError(err, provider) {
|
|
|
185
619
|
}
|
|
186
620
|
return new ProviderError(tagged, { providerStatus: status, cause: err });
|
|
187
621
|
}
|
|
188
|
-
function mapProviderError(err, provider) {
|
|
189
|
-
throw normalizeError(err, provider);
|
|
622
|
+
function mapProviderError(err, provider, signal) {
|
|
623
|
+
throw normalizeError(err, provider, signal);
|
|
190
624
|
}
|
|
191
|
-
|
|
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;
|
|
634
|
+
}
|
|
635
|
+
var init_errors2 = __esm({
|
|
192
636
|
"src/providers/errors.ts"() {
|
|
193
637
|
"use strict";
|
|
194
638
|
init_shape();
|
|
195
639
|
}
|
|
196
640
|
});
|
|
197
641
|
|
|
198
|
-
// src/providers/anthropic.ts
|
|
199
|
-
var anthropic_exports = {};
|
|
200
|
-
__export(anthropic_exports, {
|
|
201
|
-
AnthropicProvider: () => AnthropicProvider
|
|
202
|
-
});
|
|
642
|
+
// src/providers/anthropic/request.ts
|
|
203
643
|
function buildParams(spec, req, messages) {
|
|
204
644
|
const params = {
|
|
205
645
|
model: spec.nativeId,
|
|
@@ -287,12 +727,33 @@ function mapToolChoice(tc) {
|
|
|
287
727
|
if (tc === "none") return { type: "none" };
|
|
288
728
|
return { type: "tool", name: tc.name };
|
|
289
729
|
}
|
|
730
|
+
var init_request2 = __esm({
|
|
731
|
+
"src/providers/anthropic/request.ts"() {
|
|
732
|
+
"use strict";
|
|
733
|
+
}
|
|
734
|
+
});
|
|
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
|
+
|
|
750
|
+
// src/providers/anthropic/response.ts
|
|
290
751
|
function mapContent(blocks) {
|
|
291
752
|
const out = [];
|
|
292
753
|
for (const b of blocks) {
|
|
293
754
|
if (b.type === "text") out.push({ type: "text", text: b.text });
|
|
294
755
|
else if (b.type === "tool_use") {
|
|
295
|
-
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) });
|
|
296
757
|
} else if (b.type === "thinking") {
|
|
297
758
|
out.push({ type: "thinking", thinking: b.thinking, signature: b.signature });
|
|
298
759
|
}
|
|
@@ -318,6 +779,14 @@ function mapStop(reason) {
|
|
|
318
779
|
return "end_turn";
|
|
319
780
|
}
|
|
320
781
|
}
|
|
782
|
+
var init_response = __esm({
|
|
783
|
+
"src/providers/anthropic/response.ts"() {
|
|
784
|
+
"use strict";
|
|
785
|
+
init_tool_input();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// src/providers/anthropic/stream.ts
|
|
321
790
|
function mapStreamEvent(raw, req) {
|
|
322
791
|
switch (raw.type) {
|
|
323
792
|
case "message_start":
|
|
@@ -359,13 +828,58 @@ function mapStartBlock(cb) {
|
|
|
359
828
|
if (cb.type === "thinking") return { type: "thinking", thinking: cb.thinking, signature: cb.signature };
|
|
360
829
|
return void 0;
|
|
361
830
|
}
|
|
831
|
+
var init_stream = __esm({
|
|
832
|
+
"src/providers/anthropic/stream.ts"() {
|
|
833
|
+
"use strict";
|
|
834
|
+
init_response();
|
|
835
|
+
}
|
|
836
|
+
});
|
|
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
|
+
|
|
867
|
+
// src/providers/anthropic.ts
|
|
868
|
+
var anthropic_exports = {};
|
|
869
|
+
__export(anthropic_exports, {
|
|
870
|
+
AnthropicProvider: () => AnthropicProvider
|
|
871
|
+
});
|
|
362
872
|
var import_sdk, AnthropicProvider;
|
|
363
873
|
var init_anthropic = __esm({
|
|
364
874
|
"src/providers/anthropic.ts"() {
|
|
365
875
|
"use strict";
|
|
366
876
|
import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
367
|
-
|
|
877
|
+
init_errors2();
|
|
368
878
|
init_shape();
|
|
879
|
+
init_request2();
|
|
880
|
+
init_response();
|
|
881
|
+
init_stream();
|
|
882
|
+
init_abort();
|
|
369
883
|
AnthropicProvider = class {
|
|
370
884
|
id = "anthropic";
|
|
371
885
|
client;
|
|
@@ -374,6 +888,7 @@ var init_anthropic = __esm({
|
|
|
374
888
|
this.client = client ?? new import_sdk.default({ apiKey });
|
|
375
889
|
}
|
|
376
890
|
async create(spec, req) {
|
|
891
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
377
892
|
try {
|
|
378
893
|
let messages = toAnthropicMessages(req);
|
|
379
894
|
const content = [];
|
|
@@ -382,7 +897,8 @@ var init_anthropic = __esm({
|
|
|
382
897
|
let id = "";
|
|
383
898
|
for (let i = 0; i < 8; i++) {
|
|
384
899
|
const res = await this.client.messages.create(
|
|
385
|
-
buildParams(spec, req, messages)
|
|
900
|
+
buildParams(spec, req, messages),
|
|
901
|
+
{ signal }
|
|
386
902
|
);
|
|
387
903
|
id = res.id;
|
|
388
904
|
addUsage(usage, mapUsage(res.usage));
|
|
@@ -396,34 +912,39 @@ var init_anthropic = __esm({
|
|
|
396
912
|
}
|
|
397
913
|
return { id, model: req.model, provider: "anthropic", role: "assistant", content, stopReason, usage };
|
|
398
914
|
} catch (err) {
|
|
399
|
-
mapProviderError(err, "anthropic");
|
|
915
|
+
mapProviderError(err, "anthropic", signal);
|
|
400
916
|
}
|
|
401
917
|
}
|
|
402
918
|
async *stream(spec, req) {
|
|
919
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
403
920
|
try {
|
|
404
921
|
const events = this.client.messages.stream(
|
|
405
|
-
buildParams(spec, req, toAnthropicMessages(req))
|
|
922
|
+
buildParams(spec, req, toAnthropicMessages(req)),
|
|
923
|
+
{ signal }
|
|
406
924
|
);
|
|
407
925
|
for await (const raw of events) {
|
|
408
926
|
const ev = mapStreamEvent(raw, req);
|
|
409
927
|
if (ev) yield ev;
|
|
410
928
|
}
|
|
411
929
|
} catch (err) {
|
|
412
|
-
yield { type: "error", error: normalizeError(err, "anthropic").toShape() };
|
|
930
|
+
yield { type: "error", error: normalizeError(err, "anthropic", signal).toShape() };
|
|
413
931
|
}
|
|
414
932
|
}
|
|
415
933
|
};
|
|
416
934
|
}
|
|
417
935
|
});
|
|
418
936
|
|
|
419
|
-
// src/providers/
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
937
|
+
// src/providers/blocks.ts
|
|
938
|
+
function stringifyBlocks(blocks) {
|
|
939
|
+
return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
|
|
940
|
+
}
|
|
941
|
+
var init_blocks2 = __esm({
|
|
942
|
+
"src/providers/blocks.ts"() {
|
|
943
|
+
"use strict";
|
|
944
|
+
}
|
|
426
945
|
});
|
|
946
|
+
|
|
947
|
+
// src/providers/openai/request.ts
|
|
427
948
|
function buildParams2(spec, req, stream) {
|
|
428
949
|
const params = {
|
|
429
950
|
model: spec.nativeId,
|
|
@@ -520,9 +1041,6 @@ function audioFormat(m) {
|
|
|
520
1041
|
if (m === "audio/mp3" || m === "audio/mpeg") return "mp3";
|
|
521
1042
|
throw new CapabilityError(`OpenAI audio input supports wav and mp3, not "${m}".`);
|
|
522
1043
|
}
|
|
523
|
-
function stringifyBlocks(blocks) {
|
|
524
|
-
return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
|
|
525
|
-
}
|
|
526
1044
|
function buildTools2(req) {
|
|
527
1045
|
if (!req.tools || req.tools.length === 0) return void 0;
|
|
528
1046
|
return req.tools.map((t) => ({
|
|
@@ -542,6 +1060,15 @@ function reasoningEffort(e) {
|
|
|
542
1060
|
if (e === "medium") return "medium";
|
|
543
1061
|
return "high";
|
|
544
1062
|
}
|
|
1063
|
+
var init_request3 = __esm({
|
|
1064
|
+
"src/providers/openai/request.ts"() {
|
|
1065
|
+
"use strict";
|
|
1066
|
+
init_shape();
|
|
1067
|
+
init_blocks2();
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
// src/providers/openai/response.ts
|
|
545
1072
|
function mapResponse(res, req) {
|
|
546
1073
|
const choice = res.choices[0];
|
|
547
1074
|
const content = [];
|
|
@@ -565,9 +1092,10 @@ function parseArgs(raw) {
|
|
|
565
1092
|
if (!raw) return {};
|
|
566
1093
|
try {
|
|
567
1094
|
const parsed = JSON.parse(raw);
|
|
568
|
-
return parsed
|
|
569
|
-
} catch {
|
|
570
|
-
|
|
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 });
|
|
571
1099
|
}
|
|
572
1100
|
}
|
|
573
1101
|
function mapFinish(reason) {
|
|
@@ -591,6 +1119,15 @@ function mapUsage2(u) {
|
|
|
591
1119
|
if (cached != null) usage.cacheReadTokens = cached;
|
|
592
1120
|
return usage;
|
|
593
1121
|
}
|
|
1122
|
+
var init_response2 = __esm({
|
|
1123
|
+
"src/providers/openai/response.ts"() {
|
|
1124
|
+
"use strict";
|
|
1125
|
+
init_shape();
|
|
1126
|
+
init_tool_input();
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// src/providers/openai/stream.ts
|
|
594
1131
|
async function* mapStream(stream, req) {
|
|
595
1132
|
let started = false;
|
|
596
1133
|
let textOpen = false;
|
|
@@ -638,13 +1175,32 @@ async function* mapStream(stream, req) {
|
|
|
638
1175
|
yield { type: "message_delta", delta: { stopReason }, usage };
|
|
639
1176
|
yield { type: "message_stop" };
|
|
640
1177
|
}
|
|
1178
|
+
var init_stream2 = __esm({
|
|
1179
|
+
"src/providers/openai/stream.ts"() {
|
|
1180
|
+
"use strict";
|
|
1181
|
+
init_shape();
|
|
1182
|
+
init_response2();
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
// src/providers/openai.ts
|
|
1187
|
+
var openai_exports = {};
|
|
1188
|
+
__export(openai_exports, {
|
|
1189
|
+
OpenAIProvider: () => OpenAIProvider,
|
|
1190
|
+
mapResponse: () => mapResponse,
|
|
1191
|
+
parseArgs: () => parseArgs,
|
|
1192
|
+
toOpenAIMessages: () => toOpenAIMessages
|
|
1193
|
+
});
|
|
641
1194
|
var import_openai2, OpenAIProvider;
|
|
642
1195
|
var init_openai = __esm({
|
|
643
1196
|
"src/providers/openai.ts"() {
|
|
644
1197
|
"use strict";
|
|
645
1198
|
import_openai2 = __toESM(require("openai"), 1);
|
|
646
|
-
|
|
647
|
-
|
|
1199
|
+
init_errors2();
|
|
1200
|
+
init_request3();
|
|
1201
|
+
init_response2();
|
|
1202
|
+
init_stream2();
|
|
1203
|
+
init_abort();
|
|
648
1204
|
OpenAIProvider = class {
|
|
649
1205
|
id = "openai";
|
|
650
1206
|
client;
|
|
@@ -652,36 +1208,34 @@ var init_openai = __esm({
|
|
|
652
1208
|
this.client = client ?? new import_openai2.default({ apiKey });
|
|
653
1209
|
}
|
|
654
1210
|
async create(spec, req) {
|
|
1211
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
655
1212
|
try {
|
|
656
1213
|
const res = await this.client.chat.completions.create(
|
|
657
|
-
buildParams2(spec, req, false)
|
|
1214
|
+
buildParams2(spec, req, false),
|
|
1215
|
+
{ signal }
|
|
658
1216
|
);
|
|
659
1217
|
return mapResponse(res, req);
|
|
660
1218
|
} catch (err) {
|
|
661
|
-
mapProviderError(err, "openai");
|
|
1219
|
+
mapProviderError(err, "openai", signal);
|
|
662
1220
|
}
|
|
663
1221
|
}
|
|
664
1222
|
async *stream(spec, req) {
|
|
1223
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
665
1224
|
try {
|
|
666
1225
|
const stream = await this.client.chat.completions.create(
|
|
667
|
-
buildParams2(spec, req, true)
|
|
1226
|
+
buildParams2(spec, req, true),
|
|
1227
|
+
{ signal }
|
|
668
1228
|
);
|
|
669
1229
|
yield* mapStream(stream, req);
|
|
670
1230
|
} catch (err) {
|
|
671
|
-
yield { type: "error", error: normalizeError(err, "openai").toShape() };
|
|
1231
|
+
yield { type: "error", error: normalizeError(err, "openai", signal).toShape() };
|
|
672
1232
|
}
|
|
673
1233
|
}
|
|
674
1234
|
};
|
|
675
1235
|
}
|
|
676
1236
|
});
|
|
677
1237
|
|
|
678
|
-
// src/providers/google.ts
|
|
679
|
-
var google_exports = {};
|
|
680
|
-
__export(google_exports, {
|
|
681
|
-
GoogleProvider: () => GoogleProvider,
|
|
682
|
-
mapResponse: () => mapResponse2,
|
|
683
|
-
toContents: () => toContents
|
|
684
|
-
});
|
|
1238
|
+
// src/providers/google/request.ts
|
|
685
1239
|
function buildParams3(spec, req) {
|
|
686
1240
|
const config = { maxOutputTokens: req.maxTokens };
|
|
687
1241
|
if (req.system !== void 0) {
|
|
@@ -729,16 +1283,13 @@ function toPart(b) {
|
|
|
729
1283
|
functionResponse: {
|
|
730
1284
|
// Gemini correlates by name; the canonical tool_use id is keyed to it.
|
|
731
1285
|
name: b.toolUseId,
|
|
732
|
-
response: { result: typeof b.content === "string" ? b.content :
|
|
1286
|
+
response: { result: typeof b.content === "string" ? b.content : stringifyBlocks(b.content) }
|
|
733
1287
|
}
|
|
734
1288
|
};
|
|
735
1289
|
case "thinking":
|
|
736
1290
|
return { text: "" };
|
|
737
1291
|
}
|
|
738
1292
|
}
|
|
739
|
-
function stringifyBlocks2(blocks) {
|
|
740
|
-
return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
|
|
741
|
-
}
|
|
742
1293
|
function buildTools3(req) {
|
|
743
1294
|
const tools = [];
|
|
744
1295
|
if (req.tools && req.tools.length > 0) {
|
|
@@ -759,6 +1310,17 @@ function mapToolChoice3(tc) {
|
|
|
759
1310
|
if (tc === "any") return { functionCallingConfig: { mode: import_genai.FunctionCallingConfigMode.ANY } };
|
|
760
1311
|
return { functionCallingConfig: { mode: import_genai.FunctionCallingConfigMode.ANY, allowedFunctionNames: [tc.name] } };
|
|
761
1312
|
}
|
|
1313
|
+
var import_genai;
|
|
1314
|
+
var init_request4 = __esm({
|
|
1315
|
+
"src/providers/google/request.ts"() {
|
|
1316
|
+
"use strict";
|
|
1317
|
+
import_genai = require("@google/genai");
|
|
1318
|
+
init_shape();
|
|
1319
|
+
init_blocks2();
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// src/providers/google/response.ts
|
|
762
1324
|
function mapResponse2(res, req) {
|
|
763
1325
|
const candidate = res.candidates?.[0];
|
|
764
1326
|
const parts = candidate?.content?.parts ?? [];
|
|
@@ -769,7 +1331,7 @@ function mapResponse2(res, req) {
|
|
|
769
1331
|
else if (p.functionCall) {
|
|
770
1332
|
sawToolUse = true;
|
|
771
1333
|
const name = p.functionCall.name ?? "";
|
|
772
|
-
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) });
|
|
773
1335
|
}
|
|
774
1336
|
}
|
|
775
1337
|
return {
|
|
@@ -807,6 +1369,14 @@ function mapUsage3(res) {
|
|
|
807
1369
|
if (u?.cachedContentTokenCount != null) usage.cacheReadTokens = u.cachedContentTokenCount;
|
|
808
1370
|
return usage;
|
|
809
1371
|
}
|
|
1372
|
+
var init_response3 = __esm({
|
|
1373
|
+
"src/providers/google/response.ts"() {
|
|
1374
|
+
"use strict";
|
|
1375
|
+
init_tool_input();
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
// src/providers/google/stream.ts
|
|
810
1380
|
async function* mapStream2(iter, req) {
|
|
811
1381
|
let started = false;
|
|
812
1382
|
let textOpen = false;
|
|
@@ -850,33 +1420,57 @@ async function* mapStream2(iter, req) {
|
|
|
850
1420
|
yield { type: "message_delta", delta: { stopReason }, usage };
|
|
851
1421
|
yield { type: "message_stop" };
|
|
852
1422
|
}
|
|
853
|
-
var
|
|
1423
|
+
var init_stream3 = __esm({
|
|
1424
|
+
"src/providers/google/stream.ts"() {
|
|
1425
|
+
"use strict";
|
|
1426
|
+
init_shape();
|
|
1427
|
+
init_response3();
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
// src/providers/google.ts
|
|
1432
|
+
var google_exports = {};
|
|
1433
|
+
__export(google_exports, {
|
|
1434
|
+
GoogleProvider: () => GoogleProvider,
|
|
1435
|
+
mapResponse: () => mapResponse2,
|
|
1436
|
+
toContents: () => toContents
|
|
1437
|
+
});
|
|
1438
|
+
var import_genai2, GoogleProvider;
|
|
854
1439
|
var init_google = __esm({
|
|
855
1440
|
"src/providers/google.ts"() {
|
|
856
1441
|
"use strict";
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1442
|
+
import_genai2 = require("@google/genai");
|
|
1443
|
+
init_errors2();
|
|
1444
|
+
init_request4();
|
|
1445
|
+
init_response3();
|
|
1446
|
+
init_stream3();
|
|
1447
|
+
init_abort();
|
|
860
1448
|
GoogleProvider = class {
|
|
861
1449
|
id = "google";
|
|
862
1450
|
client;
|
|
863
1451
|
constructor(apiKey, client) {
|
|
864
|
-
this.client = client ?? new
|
|
1452
|
+
this.client = client ?? new import_genai2.GoogleGenAI({ apiKey });
|
|
865
1453
|
}
|
|
866
1454
|
async create(spec, req) {
|
|
1455
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
867
1456
|
try {
|
|
868
|
-
const
|
|
1457
|
+
const params = buildParams3(spec, req);
|
|
1458
|
+
params.config = { ...params.config, abortSignal: signal };
|
|
1459
|
+
const res = await this.client.models.generateContent(params);
|
|
869
1460
|
return mapResponse2(res, req);
|
|
870
1461
|
} catch (err) {
|
|
871
|
-
mapProviderError(err, "google");
|
|
1462
|
+
mapProviderError(err, "google", signal);
|
|
872
1463
|
}
|
|
873
1464
|
}
|
|
874
1465
|
async *stream(spec, req) {
|
|
1466
|
+
const signal = signalWithDeadline(req.signal, req.deadline);
|
|
875
1467
|
try {
|
|
876
|
-
const
|
|
1468
|
+
const params = buildParams3(spec, req);
|
|
1469
|
+
params.config = { ...params.config, abortSignal: signal };
|
|
1470
|
+
const iter = await this.client.models.generateContentStream(params);
|
|
877
1471
|
yield* mapStream2(iter, req);
|
|
878
1472
|
} catch (err) {
|
|
879
|
-
yield { type: "error", error: normalizeError(err, "google").toShape() };
|
|
1473
|
+
yield { type: "error", error: normalizeError(err, "google", signal).toShape() };
|
|
880
1474
|
}
|
|
881
1475
|
}
|
|
882
1476
|
};
|
|
@@ -889,11 +1483,13 @@ __export(index_exports, {
|
|
|
889
1483
|
AGENT_SCHEMA: () => AGENT_SCHEMA,
|
|
890
1484
|
ANTHROPIC_MODELS: () => ANTHROPIC_MODELS,
|
|
891
1485
|
AuthError: () => AuthError,
|
|
1486
|
+
CancelledError: () => CancelledError,
|
|
892
1487
|
CapabilityError: () => CapabilityError,
|
|
893
1488
|
ConfigError: () => ConfigError,
|
|
894
1489
|
ContextWindowError: () => ContextWindowError,
|
|
895
1490
|
DEFAULT_CATALOG: () => DEFAULT_CATALOG,
|
|
896
1491
|
DEFAULT_SECRET_NAMES: () => DEFAULT_SECRET_NAMES,
|
|
1492
|
+
DeadlineExceededError: () => DeadlineExceededError,
|
|
897
1493
|
GOOGLE_MODELS: () => GOOGLE_MODELS,
|
|
898
1494
|
InMemoryScope: () => InMemoryScope,
|
|
899
1495
|
InvalidRequestError: () => InvalidRequestError,
|
|
@@ -904,6 +1500,7 @@ __export(index_exports, {
|
|
|
904
1500
|
ProviderError: () => ProviderError,
|
|
905
1501
|
RateLimitError: () => RateLimitError,
|
|
906
1502
|
TaintError: () => TaintError,
|
|
1503
|
+
ToolInputError: () => ToolInputError,
|
|
907
1504
|
addUsage: () => addUsage,
|
|
908
1505
|
assertSinkAcceptsTaint: () => assertSinkAcceptsTaint,
|
|
909
1506
|
blocksOf: () => blocksOf,
|
|
@@ -949,6 +1546,7 @@ __export(index_exports, {
|
|
|
949
1546
|
structuredMatch: () => structuredMatch,
|
|
950
1547
|
taint: () => taint,
|
|
951
1548
|
toOracleTool: () => toOracleTool,
|
|
1549
|
+
validateJsonSchema: () => validateJsonSchema,
|
|
952
1550
|
validateRequest: () => validateRequest
|
|
953
1551
|
});
|
|
954
1552
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -958,7 +1556,7 @@ init_shape();
|
|
|
958
1556
|
|
|
959
1557
|
// src/shape/capabilities.ts
|
|
960
1558
|
init_shape();
|
|
961
|
-
function chatCapabilities(
|
|
1559
|
+
function chatCapabilities(overrides2 = {}) {
|
|
962
1560
|
return {
|
|
963
1561
|
kind: "chat",
|
|
964
1562
|
textIn: true,
|
|
@@ -971,7 +1569,7 @@ function chatCapabilities(overrides = {}) {
|
|
|
971
1569
|
streaming: true,
|
|
972
1570
|
structuredOutput: true,
|
|
973
1571
|
webSearch: false,
|
|
974
|
-
...
|
|
1572
|
+
...overrides2
|
|
975
1573
|
};
|
|
976
1574
|
}
|
|
977
1575
|
function validateRequest(spec, req) {
|
|
@@ -1063,6 +1661,15 @@ var OPENAI_MODELS = [
|
|
|
1063
1661
|
capabilities: chatCapabilities({ effort: true }),
|
|
1064
1662
|
contextWindow: 4e5
|
|
1065
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
|
+
},
|
|
1066
1673
|
{
|
|
1067
1674
|
id: "gpt-5",
|
|
1068
1675
|
provider: "openai",
|
|
@@ -1180,11 +1787,29 @@ function providersInCatalog(catalog) {
|
|
|
1180
1787
|
}
|
|
1181
1788
|
|
|
1182
1789
|
// src/providers/registry.ts
|
|
1790
|
+
var CLIENT_TTL_MS = 6e4;
|
|
1183
1791
|
var cache = /* @__PURE__ */ new Map();
|
|
1792
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
1793
|
+
var overrides = /* @__PURE__ */ new Map();
|
|
1184
1794
|
async function getProvider(id, apiKey) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
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) {
|
|
1188
1813
|
let provider;
|
|
1189
1814
|
switch (id) {
|
|
1190
1815
|
case "anthropic": {
|
|
@@ -1203,14 +1828,19 @@ async function getProvider(id, apiKey) {
|
|
|
1203
1828
|
break;
|
|
1204
1829
|
}
|
|
1205
1830
|
}
|
|
1206
|
-
cache.set(cacheKey, provider);
|
|
1207
1831
|
return provider;
|
|
1208
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
|
+
}
|
|
1209
1837
|
function registerProvider(id, apiKey, provider) {
|
|
1210
|
-
|
|
1838
|
+
overrides.set(id, { apiKey, provider });
|
|
1211
1839
|
}
|
|
1212
1840
|
function clearProviderCache() {
|
|
1213
1841
|
cache.clear();
|
|
1842
|
+
inflight.clear();
|
|
1843
|
+
overrides.clear();
|
|
1214
1844
|
}
|
|
1215
1845
|
|
|
1216
1846
|
// src/client/keys.ts
|
|
@@ -1281,7 +1911,9 @@ function init(opts = {}) {
|
|
|
1281
1911
|
messages: [{ role: "user", content: c.user }],
|
|
1282
1912
|
tools: [{ name: c.tool.name, description: c.tool.description ?? "", inputSchema: c.tool.parameters }],
|
|
1283
1913
|
toolChoice: { type: "tool", name: c.tool.name },
|
|
1284
|
-
maxTokens: c.maxTokens ?? 4096
|
|
1914
|
+
maxTokens: c.maxTokens ?? 4096,
|
|
1915
|
+
signal: c.signal,
|
|
1916
|
+
deadline: c.deadline
|
|
1285
1917
|
};
|
|
1286
1918
|
const { spec, provider } = await resolveProviderFor(model);
|
|
1287
1919
|
validateRequest(spec, req);
|
|
@@ -1290,6 +1922,11 @@ function init(opts = {}) {
|
|
|
1290
1922
|
if (!block || block.type !== "tool_use") {
|
|
1291
1923
|
throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`);
|
|
1292
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
|
+
}
|
|
1293
1930
|
return { value: block.input, usage: res.usage };
|
|
1294
1931
|
};
|
|
1295
1932
|
const search = async (c) => {
|
|
@@ -1299,7 +1936,9 @@ function init(opts = {}) {
|
|
|
1299
1936
|
system: c.system,
|
|
1300
1937
|
messages: [{ role: "user", content: c.user }],
|
|
1301
1938
|
webSearch: true,
|
|
1302
|
-
maxTokens: c.maxTokens ?? 8192
|
|
1939
|
+
maxTokens: c.maxTokens ?? 8192,
|
|
1940
|
+
signal: c.signal,
|
|
1941
|
+
deadline: c.deadline
|
|
1303
1942
|
};
|
|
1304
1943
|
const { spec, provider } = await resolveProviderFor(model);
|
|
1305
1944
|
validateRequest(spec, req);
|
|
@@ -1328,16 +1967,20 @@ function generateLlmsTxt(catalog = DEFAULT_CATALOG) {
|
|
|
1328
1967
|
const models = Object.values(catalog).map((s) => `- \`${s.id}\` (${s.provider}) \u2014 ${capsSummary(s.capabilities)}`).join("\n");
|
|
1329
1968
|
return `# @odla-ai/ai \u2014 LLM context
|
|
1330
1969
|
|
|
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.
|
|
1974
|
+
|
|
1331
1975
|
One general-purpose interface for AI inference across Anthropic (Claude), OpenAI
|
|
1332
1976
|
(GPT), and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent
|
|
1333
1977
|
engine and an eval harness. Library-only: import in-process, bring your own keys.
|
|
1334
|
-
|
|
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.
|
|
1335
1980
|
|
|
1336
1981
|
## Install
|
|
1337
1982
|
|
|
1338
1983
|
npm i @odla-ai/ai
|
|
1339
|
-
# provider SDKs are peer-lazy-loaded; install those you use:
|
|
1340
|
-
# @anthropic-ai/sdk openai @google/genai
|
|
1341
1984
|
# optional, for odla-db-backed storage + secrets:
|
|
1342
1985
|
# @odla-ai/db
|
|
1343
1986
|
|
|
@@ -1351,7 +1994,8 @@ Isomorphic (Node 20+, Cloudflare Workers, browser). Provider SDKs are lazy-loade
|
|
|
1351
1994
|
- \`ai.stream(input)\` \u2192 async iterable of normalized events: message_start \u2192
|
|
1352
1995
|
content_block_start \u2192 content_block_delta {text_delta|thinking_delta|input_json_delta}
|
|
1353
1996
|
\u2192 content_block_stop \u2192 message_delta \u2192 message_stop (plus \`error\`).
|
|
1354
|
-
- \`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.
|
|
1355
1999
|
- \`ai.search({ model, user })\` \u2192 web-search-grounded prose.
|
|
1356
2000
|
|
|
1357
2001
|
## Content blocks (multimodal)
|
|
@@ -1370,7 +2014,15 @@ audio block to a Claude model throws CapabilityError before any network call.
|
|
|
1370
2014
|
// run.finalText, run.toolCalls, run.usage, run.steps, run.stoppedReason
|
|
1371
2015
|
|
|
1372
2016
|
Tools may carry taint labels (outputTaint / acceptsTaint) for a CaMeL-style
|
|
1373
|
-
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.
|
|
1374
2026
|
|
|
1375
2027
|
## Skills
|
|
1376
2028
|
|
|
@@ -1397,14 +2049,17 @@ device-authorization handshake for a scoped, revocable token, then provisions.
|
|
|
1397
2049
|
import { requestToken, init as odlaInit } from "@odla-ai/db";
|
|
1398
2050
|
import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, init } from "@odla-ai/ai";
|
|
1399
2051
|
|
|
1400
|
-
|
|
1401
|
-
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) });
|
|
1402
2057
|
|
|
1403
2058
|
// 2. Provision: create app, mint app key, push agent schema, store BYO provider keys as secrets.
|
|
1404
|
-
const { appId, appKey } = await provisionAgentApp({ endpoint:
|
|
2059
|
+
const { appId, appKey } = await provisionAgentApp({ endpoint: ODLA_PLATFORM, token, providerKeys: { openai: OPENAI_KEY } });
|
|
1405
2060
|
|
|
1406
2061
|
// 3. Runtime: read keys from odla-db secrets; persist memory + run traces.
|
|
1407
|
-
const db = odlaInit({ appId, adminToken: appKey, endpoint:
|
|
2062
|
+
const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
|
|
1408
2063
|
const ai = init({ resolveKey: odlaDbKeyResolver(db) });
|
|
1409
2064
|
const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
|
|
1410
2065
|
const run = await runAgent(ai, persona, { input: "hello" });
|
|
@@ -1430,16 +2085,22 @@ env's odla-db tenant secrets, write-only for operators). One call reads both:
|
|
|
1430
2085
|
platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
|
|
1431
2086
|
// ai.chat / ai.stream / ai.extract \u2014 provider/model from public-config
|
|
1432
2087
|
// (cached ~60s, so Studio changes apply without redeploys); the key is
|
|
1433
|
-
// fetched from the vault
|
|
2088
|
+
// fetched from the vault via a finite-TTL odlaDbKeyResolver.
|
|
1434
2089
|
|
|
1435
2090
|
The only secret the Worker carries is its odla-db app key. Rotating the LLM
|
|
1436
|
-
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.
|
|
1437
2098
|
|
|
1438
2099
|
## Errors
|
|
1439
2100
|
|
|
1440
2101
|
Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
|
|
1441
2102
|
config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
|
|
1442
|
-
tool_input_invalid, provider_error.
|
|
2103
|
+
tool_input_invalid, cancelled, deadline_exceeded, provider_error.
|
|
1443
2104
|
|
|
1444
2105
|
## Models
|
|
1445
2106
|
|
|
@@ -1451,10 +2112,11 @@ via buildCatalog / init({ catalog }).
|
|
|
1451
2112
|
}
|
|
1452
2113
|
|
|
1453
2114
|
// src/index.ts
|
|
1454
|
-
|
|
2115
|
+
init_errors2();
|
|
1455
2116
|
|
|
1456
2117
|
// src/agent/loop.ts
|
|
1457
2118
|
init_shape();
|
|
2119
|
+
init_abort();
|
|
1458
2120
|
|
|
1459
2121
|
// src/agent/tools.ts
|
|
1460
2122
|
function toOracleTool(t) {
|
|
@@ -1496,6 +2158,7 @@ function assertSinkAcceptsTaint(sink, allowed, actual) {
|
|
|
1496
2158
|
|
|
1497
2159
|
// src/agent/loop.ts
|
|
1498
2160
|
async function runAgent(inference, persona, input) {
|
|
2161
|
+
validateRunLimits(persona.maxSteps, input.budget);
|
|
1499
2162
|
const { system, tools } = composeSkills(persona.system, persona.tools, persona.skills);
|
|
1500
2163
|
const handlerByName = /* @__PURE__ */ new Map();
|
|
1501
2164
|
for (const t of tools) if (t.handler) handlerByName.set(t.name, t);
|
|
@@ -1507,21 +2170,26 @@ async function runAgent(inference, persona, input) {
|
|
|
1507
2170
|
const toolCalls = [];
|
|
1508
2171
|
const accumulatedTaint = /* @__PURE__ */ new Set(["llm_inherited"]);
|
|
1509
2172
|
const maxSteps = persona.maxSteps ?? 8;
|
|
2173
|
+
const runSignal = signalWithDeadline(input.signal, input.deadline);
|
|
1510
2174
|
let response;
|
|
1511
2175
|
let stoppedReason = "max_steps";
|
|
1512
2176
|
let steps = 0;
|
|
2177
|
+
let attemptedToolCalls = 0;
|
|
1513
2178
|
while (steps < maxSteps) {
|
|
1514
2179
|
steps++;
|
|
2180
|
+
const remainingOutput = remainingOutputTokens(usage, input.budget);
|
|
1515
2181
|
response = await inference.chat({
|
|
1516
2182
|
model: persona.model,
|
|
1517
2183
|
system,
|
|
1518
2184
|
messages,
|
|
1519
2185
|
tools: tools.length > 0 ? tools.map(toOracleTool) : void 0,
|
|
1520
|
-
maxTokens: persona.maxTokens ?? 4096,
|
|
2186
|
+
maxTokens: Math.min(persona.maxTokens ?? 4096, remainingOutput ?? Number.POSITIVE_INFINITY),
|
|
1521
2187
|
effort: persona.effort,
|
|
1522
2188
|
thinking: persona.thinking,
|
|
1523
2189
|
temperature: persona.temperature,
|
|
1524
|
-
webSearch: persona.webSearch
|
|
2190
|
+
webSearch: persona.webSearch,
|
|
2191
|
+
signal: runSignal,
|
|
2192
|
+
deadline: input.deadline
|
|
1525
2193
|
});
|
|
1526
2194
|
addUsage(usage, response.usage);
|
|
1527
2195
|
messages.push({ role: "assistant", content: response.content });
|
|
@@ -1534,8 +2202,22 @@ async function runAgent(inference, persona, input) {
|
|
|
1534
2202
|
stoppedReason = "end_turn";
|
|
1535
2203
|
break;
|
|
1536
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
|
+
}
|
|
1537
2213
|
const resultBlocks = [];
|
|
1538
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++;
|
|
1539
2221
|
const def = handlerByName.get(toolUse.name);
|
|
1540
2222
|
if (!def || !def.handler) {
|
|
1541
2223
|
resultBlocks.push(errorResult(toolUse.id, `No local handler for tool "${toolUse.name}".`));
|
|
@@ -1543,16 +2225,26 @@ async function runAgent(inference, persona, input) {
|
|
|
1543
2225
|
}
|
|
1544
2226
|
if (def.acceptsTaint) assertSinkAcceptsTaint(def.name, def.acceptsTaint, accumulatedTaint);
|
|
1545
2227
|
let output;
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
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
|
+
}
|
|
1550
2241
|
}
|
|
1551
2242
|
toolCalls.push({ toolUse, output });
|
|
1552
2243
|
resultBlocks.push({ type: "tool_result", toolUseId: toolUse.id, content: output.content, isError: output.isError });
|
|
1553
2244
|
for (const label of def.outputTaint ?? []) accumulatedTaint.add(label);
|
|
1554
2245
|
}
|
|
1555
2246
|
messages.push({ role: "user", content: resultBlocks });
|
|
2247
|
+
if (stoppedReason === "budget_exhausted") break;
|
|
1556
2248
|
}
|
|
1557
2249
|
if (!response) throw new Error("runAgent: maxSteps must be at least 1");
|
|
1558
2250
|
if (persona.memory) await persona.memory.append(messages.slice(runStartIndex));
|
|
@@ -1566,6 +2258,36 @@ async function runAgent(inference, persona, input) {
|
|
|
1566
2258
|
stoppedReason
|
|
1567
2259
|
};
|
|
1568
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
|
+
}
|
|
1569
2291
|
function errorResult(toolUseId, message) {
|
|
1570
2292
|
return { type: "tool_result", toolUseId, content: message, isError: true };
|
|
1571
2293
|
}
|
|
@@ -1870,22 +2592,32 @@ var DEFAULT_SECRET_NAMES = {
|
|
|
1870
2592
|
};
|
|
1871
2593
|
function odlaDbKeyResolver(db, opts = {}) {
|
|
1872
2594
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
1873
|
-
const
|
|
2595
|
+
const ttlMs = opts.cache === false ? 0 : Math.max(0, opts.ttlMs ?? 6e4);
|
|
1874
2596
|
const cache3 = /* @__PURE__ */ new Map();
|
|
1875
2597
|
return async (provider) => {
|
|
1876
|
-
|
|
2598
|
+
const version = typeof opts.cacheVersion === "function" ? opts.cacheVersion(provider) : opts.cacheVersion ?? "";
|
|
2599
|
+
if (ttlMs > 0) {
|
|
1877
2600
|
const hit = cache3.get(provider);
|
|
1878
|
-
if (hit
|
|
2601
|
+
if (hit?.version === version && hit.expiresAt > Date.now()) return hit.value;
|
|
2602
|
+
cache3.delete(provider);
|
|
1879
2603
|
}
|
|
1880
2604
|
try {
|
|
1881
2605
|
const key = await db.secrets.get(nameFor(provider));
|
|
1882
|
-
if (
|
|
2606
|
+
if (ttlMs > 0) cache3.set(provider, { version, value: key, expiresAt: Date.now() + ttlMs });
|
|
1883
2607
|
return key;
|
|
1884
|
-
} catch {
|
|
1885
|
-
return void 0;
|
|
2608
|
+
} catch (error) {
|
|
2609
|
+
if (isMissingSecret(error)) return void 0;
|
|
2610
|
+
throw error;
|
|
1886
2611
|
}
|
|
1887
2612
|
};
|
|
1888
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
|
+
}
|
|
1889
2621
|
|
|
1890
2622
|
// src/store/platform.ts
|
|
1891
2623
|
init_shape();
|
|
@@ -1917,19 +2649,22 @@ async function initFromPlatform(opts) {
|
|
|
1917
2649
|
const key = `${opts.platform}|${opts.appId}|${opts.env}`;
|
|
1918
2650
|
const now = Date.now();
|
|
1919
2651
|
const hit = ttl > 0 ? cache2.get(key) : void 0;
|
|
1920
|
-
|
|
1921
|
-
const { provider, model } =
|
|
1922
|
-
if (
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
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);
|
|
1926
2661
|
const ai = init({
|
|
1927
2662
|
...opts.initOptions,
|
|
1928
|
-
|
|
1929
|
-
|
|
2663
|
+
catalog,
|
|
2664
|
+
resolveKey: odlaDbKeyResolver(opts.db, opts.keyResolverOptions),
|
|
2665
|
+
defaultModel
|
|
1930
2666
|
});
|
|
1931
2667
|
const value = { ai, provider, ...model ? { model } : {} };
|
|
1932
|
-
if (ttl > 0) cache2.set(key, { value, expiresAt: now + ttl });
|
|
1933
2668
|
return value;
|
|
1934
2669
|
}
|
|
1935
2670
|
|