@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.js
CHANGED
|
@@ -1,29 +1,410 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthError,
|
|
3
|
+
CancelledError,
|
|
3
4
|
CapabilityError,
|
|
4
5
|
ConfigError,
|
|
5
6
|
ContextWindowError,
|
|
7
|
+
DeadlineExceededError,
|
|
6
8
|
InvalidRequestError,
|
|
7
9
|
OdlaAIError,
|
|
8
10
|
ProviderError,
|
|
9
11
|
RateLimitError,
|
|
12
|
+
ToolInputError,
|
|
10
13
|
addUsage,
|
|
11
|
-
blocksOf,
|
|
12
14
|
emptyUsage,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
normalizeError,
|
|
16
|
+
signalWithDeadline,
|
|
17
|
+
throwIfAborted
|
|
18
|
+
} from "./chunk-PXXCN2EU.js";
|
|
19
|
+
|
|
20
|
+
// src/shape/helpers.ts
|
|
21
|
+
function isTextBlock(b) {
|
|
22
|
+
return b.type === "text";
|
|
23
|
+
}
|
|
24
|
+
function isImageBlock(b) {
|
|
25
|
+
return b.type === "image";
|
|
26
|
+
}
|
|
27
|
+
function isAudioBlock(b) {
|
|
28
|
+
return b.type === "audio";
|
|
29
|
+
}
|
|
30
|
+
function isDocumentBlock(b) {
|
|
31
|
+
return b.type === "document";
|
|
32
|
+
}
|
|
33
|
+
function isToolUseBlock(b) {
|
|
34
|
+
return b.type === "tool_use";
|
|
35
|
+
}
|
|
36
|
+
function isToolResultBlock(b) {
|
|
37
|
+
return b.type === "tool_result";
|
|
38
|
+
}
|
|
39
|
+
function isThinkingBlock(b) {
|
|
40
|
+
return b.type === "thinking";
|
|
41
|
+
}
|
|
42
|
+
function blocksOf(content) {
|
|
43
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
44
|
+
}
|
|
45
|
+
function extractText(content) {
|
|
46
|
+
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
47
|
+
}
|
|
48
|
+
function extractToolUses(content) {
|
|
49
|
+
return content.filter(isToolUseBlock);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/shape/json-schema-shape.ts
|
|
53
|
+
var ANNOTATION_KEYS = /* @__PURE__ */ new Set([
|
|
54
|
+
"$schema",
|
|
55
|
+
"$id",
|
|
56
|
+
"$anchor",
|
|
57
|
+
"title",
|
|
58
|
+
"description",
|
|
59
|
+
"default",
|
|
60
|
+
"examples",
|
|
61
|
+
"deprecated",
|
|
62
|
+
"readOnly",
|
|
63
|
+
"writeOnly",
|
|
64
|
+
"format"
|
|
65
|
+
]);
|
|
66
|
+
var SUPPORTED_KEYS = /* @__PURE__ */ new Set([
|
|
67
|
+
...ANNOTATION_KEYS,
|
|
68
|
+
"$ref",
|
|
69
|
+
"$defs",
|
|
70
|
+
"definitions",
|
|
71
|
+
"type",
|
|
72
|
+
"enum",
|
|
73
|
+
"const",
|
|
74
|
+
"allOf",
|
|
75
|
+
"anyOf",
|
|
76
|
+
"oneOf",
|
|
77
|
+
"not",
|
|
78
|
+
"properties",
|
|
79
|
+
"required",
|
|
80
|
+
"additionalProperties",
|
|
81
|
+
"minProperties",
|
|
82
|
+
"maxProperties",
|
|
83
|
+
"items",
|
|
84
|
+
"minItems",
|
|
85
|
+
"maxItems",
|
|
86
|
+
"uniqueItems",
|
|
87
|
+
"minLength",
|
|
88
|
+
"maxLength",
|
|
89
|
+
"pattern",
|
|
90
|
+
"minimum",
|
|
91
|
+
"maximum",
|
|
92
|
+
"exclusiveMinimum",
|
|
93
|
+
"exclusiveMaximum",
|
|
94
|
+
"multipleOf"
|
|
95
|
+
]);
|
|
96
|
+
var ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
97
|
+
"null",
|
|
98
|
+
"boolean",
|
|
99
|
+
"object",
|
|
100
|
+
"array",
|
|
101
|
+
"number",
|
|
102
|
+
"integer",
|
|
103
|
+
"string"
|
|
104
|
+
]);
|
|
105
|
+
function validateSchemaShape(schema, path, root, issues) {
|
|
106
|
+
if (typeof schema === "boolean") return;
|
|
107
|
+
if (!isRecord(schema)) {
|
|
108
|
+
issue(issues, path, "schema must be an object or boolean");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const key of Object.keys(schema)) {
|
|
112
|
+
if (!SUPPORTED_KEYS.has(key)) issue(issues, path, `schema uses unsupported keyword "${key}"`);
|
|
113
|
+
}
|
|
114
|
+
if (typeof schema.$ref === "string" && resolveLocalRef(root, schema.$ref) === void 0) {
|
|
115
|
+
issue(issues, path, `schema reference "${schema.$ref}" could not be resolved`);
|
|
116
|
+
} else if (schema.$ref !== void 0 && typeof schema.$ref !== "string") {
|
|
117
|
+
issue(issues, path, "schema $ref must be a string");
|
|
118
|
+
}
|
|
119
|
+
const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : [];
|
|
120
|
+
if (schema.type !== void 0 && (types.length === 0 || !types.every((type) => typeof type === "string" && ALLOWED_TYPES.has(type)))) {
|
|
121
|
+
issue(issues, path, "schema type contains an unsupported JSON type");
|
|
122
|
+
}
|
|
123
|
+
if (schema.enum !== void 0 && !Array.isArray(schema.enum)) {
|
|
124
|
+
issue(issues, path, "schema enum must be an array");
|
|
125
|
+
}
|
|
126
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string"))) {
|
|
127
|
+
issue(issues, path, "schema required must be a string array");
|
|
128
|
+
}
|
|
129
|
+
validateConstraintShapes(schema, path, issues);
|
|
130
|
+
validateChildSchemas(schema, path, root, issues);
|
|
131
|
+
}
|
|
132
|
+
function validateChildSchemas(schema, path, root, issues) {
|
|
133
|
+
for (const key of ["properties", "$defs", "definitions"]) {
|
|
134
|
+
const group = schema[key];
|
|
135
|
+
if (group === void 0) continue;
|
|
136
|
+
if (!isRecord(group)) {
|
|
137
|
+
issue(issues, path, `schema ${key} must be an object`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
for (const [name, child] of Object.entries(group)) {
|
|
141
|
+
validateSchemaShape(child, `${path}.${key}.${name}`, root, issues);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
145
|
+
const group = schema[key];
|
|
146
|
+
if (group === void 0) continue;
|
|
147
|
+
if (!Array.isArray(group) || group.length === 0) {
|
|
148
|
+
issue(issues, path, `schema ${key} must be a non-empty array`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
group.forEach((child, index2) => validateSchemaShape(child, `${path}.${key}[${index2}]`, root, issues));
|
|
152
|
+
}
|
|
153
|
+
for (const key of ["items", "not", "additionalProperties"]) {
|
|
154
|
+
const child = schema[key];
|
|
155
|
+
if (child !== void 0) validateSchemaShape(child, `${path}.${key}`, root, issues);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function validateConstraintShapes(schema, path, issues) {
|
|
159
|
+
for (const key of ["minLength", "maxLength", "minItems", "maxItems", "minProperties", "maxProperties"]) {
|
|
160
|
+
const value = schema[key];
|
|
161
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 0)) {
|
|
162
|
+
issue(issues, path, `schema ${key} must be a non-negative integer`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const key of ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"]) {
|
|
166
|
+
const value = schema[key];
|
|
167
|
+
if (value !== void 0 && !isFiniteNumber(value)) {
|
|
168
|
+
issue(issues, path, `schema ${key} must be a finite number`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (schema.multipleOf !== void 0 && (!isFiniteNumber(schema.multipleOf) || schema.multipleOf <= 0)) {
|
|
172
|
+
issue(issues, path, "schema multipleOf must be a finite number greater than zero");
|
|
173
|
+
}
|
|
174
|
+
if (schema.uniqueItems !== void 0 && typeof schema.uniqueItems !== "boolean") {
|
|
175
|
+
issue(issues, path, "schema uniqueItems must be a boolean");
|
|
176
|
+
}
|
|
177
|
+
validatePatternShape(schema.pattern, path, issues);
|
|
178
|
+
}
|
|
179
|
+
function validatePatternShape(value, path, issues) {
|
|
180
|
+
if (value === void 0) return;
|
|
181
|
+
if (typeof value !== "string") {
|
|
182
|
+
issue(issues, path, "schema pattern must be a string");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
new RegExp(value, "u");
|
|
187
|
+
} catch {
|
|
188
|
+
issue(issues, path, `schema pattern "${value}" is invalid`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function resolveLocalRef(root, ref) {
|
|
192
|
+
if (ref === "#") return root;
|
|
193
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
194
|
+
let current = root;
|
|
195
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
196
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
197
|
+
if (!isRecord(current) || !Object.hasOwn(current, key)) return void 0;
|
|
198
|
+
current = current[key];
|
|
199
|
+
}
|
|
200
|
+
return current;
|
|
201
|
+
}
|
|
202
|
+
function isRecord(value) {
|
|
203
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
204
|
+
}
|
|
205
|
+
function isFiniteNumber(value) {
|
|
206
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
207
|
+
}
|
|
208
|
+
function issue(issues, path, message) {
|
|
209
|
+
issues.push({ path, message });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/shape/json-schema.ts
|
|
213
|
+
function validateJsonSchema(schema, value) {
|
|
214
|
+
const issues = [];
|
|
215
|
+
validateSchemaShape(schema, "$", schema, issues);
|
|
216
|
+
if (issues.length > 0) return { valid: false, issues };
|
|
217
|
+
visit(schema, value, "$", schema, issues, /* @__PURE__ */ new Set());
|
|
218
|
+
return { valid: issues.length === 0, issues };
|
|
219
|
+
}
|
|
220
|
+
function visit(schema, value, path, root, issues, refs) {
|
|
221
|
+
if (schema === true) return;
|
|
222
|
+
if (schema === false) {
|
|
223
|
+
issue2(issues, path, "is rejected by the schema");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (!isRecord2(schema)) {
|
|
227
|
+
issue2(issues, path, "schema must be an object or boolean");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
for (const key of Object.keys(schema)) {
|
|
231
|
+
if (!SUPPORTED_KEYS.has(key)) {
|
|
232
|
+
issue2(issues, path, `schema uses unsupported keyword "${key}"`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (typeof schema.$ref === "string") {
|
|
237
|
+
const target = resolveLocalRef2(root, schema.$ref);
|
|
238
|
+
if (target === void 0) {
|
|
239
|
+
issue2(issues, path, `schema reference "${schema.$ref}" could not be resolved`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (refs.has(schema.$ref)) {
|
|
243
|
+
issue2(issues, path, `cyclic schema reference "${schema.$ref}" is unsupported`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const nextRefs = new Set(refs);
|
|
247
|
+
nextRefs.add(schema.$ref);
|
|
248
|
+
visit(target, value, path, root, issues, nextRefs);
|
|
249
|
+
}
|
|
250
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => jsonEqual(candidate, value))) {
|
|
251
|
+
issue2(issues, path, "must equal one of the allowed enum values");
|
|
252
|
+
}
|
|
253
|
+
if (Object.hasOwn(schema, "const") && !jsonEqual(schema.const, value)) {
|
|
254
|
+
issue2(issues, path, "must equal the constant value");
|
|
255
|
+
}
|
|
256
|
+
if (Array.isArray(schema.allOf)) {
|
|
257
|
+
for (const child of schema.allOf) visit(child, value, path, root, issues, refs);
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((child) => matches(child, value, root, refs))) {
|
|
260
|
+
issue2(issues, path, "must match at least one anyOf schema");
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(schema.oneOf)) {
|
|
263
|
+
const matchesCount = schema.oneOf.filter((child) => matches(child, value, root, refs)).length;
|
|
264
|
+
if (matchesCount !== 1) issue2(issues, path, "must match exactly one oneOf schema");
|
|
265
|
+
}
|
|
266
|
+
if (schema.not !== void 0 && matches(schema.not, value, root, refs)) {
|
|
267
|
+
issue2(issues, path, "must not match the forbidden schema");
|
|
268
|
+
}
|
|
269
|
+
const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) && schema.type.every((t) => typeof t === "string") ? schema.type : void 0;
|
|
270
|
+
if (schema.type !== void 0 && !types) {
|
|
271
|
+
issue2(issues, path, "schema type must be a string or string array");
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (types && !types.some((type) => isType(value, type))) {
|
|
275
|
+
issue2(issues, path, `must be ${types.join(" or ")}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (typeof value === "string") validateString(schema, value, path, issues);
|
|
279
|
+
if (typeof value === "number") validateNumber(schema, value, path, issues);
|
|
280
|
+
if (Array.isArray(value)) validateArray(schema, value, path, root, issues, refs);
|
|
281
|
+
if (isRecord2(value)) validateObject(schema, value, path, root, issues, refs);
|
|
282
|
+
}
|
|
283
|
+
function validateString(schema, value, path, issues) {
|
|
284
|
+
if (isFiniteNumber2(schema.minLength) && [...value].length < schema.minLength) issue2(issues, path, `must have at least ${schema.minLength} characters`);
|
|
285
|
+
if (isFiniteNumber2(schema.maxLength) && [...value].length > schema.maxLength) issue2(issues, path, `must have at most ${schema.maxLength} characters`);
|
|
286
|
+
if (typeof schema.pattern === "string") {
|
|
287
|
+
try {
|
|
288
|
+
if (!new RegExp(schema.pattern, "u").test(value)) issue2(issues, path, `must match pattern ${schema.pattern}`);
|
|
289
|
+
} catch {
|
|
290
|
+
issue2(issues, path, `schema pattern "${schema.pattern}" is invalid`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function validateNumber(schema, value, path, issues) {
|
|
295
|
+
if (!Number.isFinite(value)) {
|
|
296
|
+
issue2(issues, path, "must be a finite number");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (isFiniteNumber2(schema.minimum) && value < schema.minimum) issue2(issues, path, `must be >= ${schema.minimum}`);
|
|
300
|
+
if (isFiniteNumber2(schema.maximum) && value > schema.maximum) issue2(issues, path, `must be <= ${schema.maximum}`);
|
|
301
|
+
if (isFiniteNumber2(schema.exclusiveMinimum) && value <= schema.exclusiveMinimum) issue2(issues, path, `must be > ${schema.exclusiveMinimum}`);
|
|
302
|
+
if (isFiniteNumber2(schema.exclusiveMaximum) && value >= schema.exclusiveMaximum) issue2(issues, path, `must be < ${schema.exclusiveMaximum}`);
|
|
303
|
+
if (isFiniteNumber2(schema.multipleOf) && schema.multipleOf > 0) {
|
|
304
|
+
const quotient = value / schema.multipleOf;
|
|
305
|
+
if (Math.abs(quotient - Math.round(quotient)) > Number.EPSILON * Math.max(1, Math.abs(quotient))) {
|
|
306
|
+
issue2(issues, path, `must be a multiple of ${schema.multipleOf}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function validateArray(schema, value, path, root, issues, refs) {
|
|
311
|
+
if (isFiniteNumber2(schema.minItems) && value.length < schema.minItems) issue2(issues, path, `must contain at least ${schema.minItems} items`);
|
|
312
|
+
if (isFiniteNumber2(schema.maxItems) && value.length > schema.maxItems) issue2(issues, path, `must contain at most ${schema.maxItems} items`);
|
|
313
|
+
if (schema.uniqueItems === true) {
|
|
314
|
+
for (let i = 0; i < value.length; i++) {
|
|
315
|
+
if (value.slice(0, i).some((other) => jsonEqual(other, value[i]))) {
|
|
316
|
+
issue2(issues, `${path}[${i}]`, "must be unique");
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (schema.items !== void 0) {
|
|
322
|
+
for (let i = 0; i < value.length; i++) visit(schema.items, value[i], `${path}[${i}]`, root, issues, refs);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function validateObject(schema, value, path, root, issues, refs) {
|
|
326
|
+
const keys = Object.keys(value);
|
|
327
|
+
if (isFiniteNumber2(schema.minProperties) && keys.length < schema.minProperties) issue2(issues, path, `must contain at least ${schema.minProperties} properties`);
|
|
328
|
+
if (isFiniteNumber2(schema.maxProperties) && keys.length > schema.maxProperties) issue2(issues, path, `must contain at most ${schema.maxProperties} properties`);
|
|
329
|
+
if (schema.required !== void 0 && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string"))) {
|
|
330
|
+
issue2(issues, path, "schema required must be a string array");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
for (const key of schema.required ?? []) {
|
|
334
|
+
if (!Object.hasOwn(value, key)) issue2(issues, childPath(path, key), "is required");
|
|
335
|
+
}
|
|
336
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
337
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
338
|
+
if (Object.hasOwn(value, key)) visit(child, value[key], childPath(path, key), root, issues, refs);
|
|
339
|
+
}
|
|
340
|
+
const extras = keys.filter((key) => !Object.hasOwn(properties, key));
|
|
341
|
+
if (schema.additionalProperties === false) {
|
|
342
|
+
for (const key of extras) issue2(issues, childPath(path, key), "is not an allowed property");
|
|
343
|
+
} else if (schema.additionalProperties !== void 0 && schema.additionalProperties !== true) {
|
|
344
|
+
for (const key of extras) visit(schema.additionalProperties, value[key], childPath(path, key), root, issues, refs);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function matches(schema, value, root, refs) {
|
|
348
|
+
const nested = [];
|
|
349
|
+
visit(schema, value, "$", root, nested, refs);
|
|
350
|
+
return nested.length === 0;
|
|
351
|
+
}
|
|
352
|
+
function resolveLocalRef2(root, ref) {
|
|
353
|
+
if (ref === "#") return root;
|
|
354
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
355
|
+
let current = root;
|
|
356
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
357
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
358
|
+
if (!isRecord2(current) || !Object.hasOwn(current, key)) return void 0;
|
|
359
|
+
current = current[key];
|
|
360
|
+
}
|
|
361
|
+
return current;
|
|
362
|
+
}
|
|
363
|
+
function isRecord2(value) {
|
|
364
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
365
|
+
}
|
|
366
|
+
function isType(value, type) {
|
|
367
|
+
switch (type) {
|
|
368
|
+
case "null":
|
|
369
|
+
return value === null;
|
|
370
|
+
case "boolean":
|
|
371
|
+
return typeof value === "boolean";
|
|
372
|
+
case "object":
|
|
373
|
+
return isRecord2(value);
|
|
374
|
+
case "array":
|
|
375
|
+
return Array.isArray(value);
|
|
376
|
+
case "number":
|
|
377
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
378
|
+
case "integer":
|
|
379
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
380
|
+
case "string":
|
|
381
|
+
return typeof value === "string";
|
|
382
|
+
default:
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function isFiniteNumber2(value) {
|
|
387
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
388
|
+
}
|
|
389
|
+
function jsonEqual(a, b) {
|
|
390
|
+
if (Object.is(a, b)) return true;
|
|
391
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
|
|
392
|
+
if (isRecord2(a) && isRecord2(b)) {
|
|
393
|
+
const aKeys = Object.keys(a);
|
|
394
|
+
const bKeys = Object.keys(b);
|
|
395
|
+
return aKeys.length === bKeys.length && aKeys.every((key) => Object.hasOwn(b, key) && jsonEqual(a[key], b[key]));
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
function childPath(path, key) {
|
|
400
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
|
|
401
|
+
}
|
|
402
|
+
function issue2(issues, path, message) {
|
|
403
|
+
issues.push({ path, message });
|
|
404
|
+
}
|
|
24
405
|
|
|
25
406
|
// src/shape/capabilities.ts
|
|
26
|
-
function chatCapabilities(
|
|
407
|
+
function chatCapabilities(overrides2 = {}) {
|
|
27
408
|
return {
|
|
28
409
|
kind: "chat",
|
|
29
410
|
textIn: true,
|
|
@@ -36,7 +417,7 @@ function chatCapabilities(overrides = {}) {
|
|
|
36
417
|
streaming: true,
|
|
37
418
|
structuredOutput: true,
|
|
38
419
|
webSearch: false,
|
|
39
|
-
...
|
|
420
|
+
...overrides2
|
|
40
421
|
};
|
|
41
422
|
}
|
|
42
423
|
function validateRequest(spec, req) {
|
|
@@ -125,6 +506,15 @@ var OPENAI_MODELS = [
|
|
|
125
506
|
capabilities: chatCapabilities({ effort: true }),
|
|
126
507
|
contextWindow: 4e5
|
|
127
508
|
},
|
|
509
|
+
{
|
|
510
|
+
// Compact GPT-5.4 reasoning model (effort knob), same 400K context as the
|
|
511
|
+
// rest of the GPT-5 line. The default for odla-o11y AI triage.
|
|
512
|
+
id: "gpt-5.4-mini",
|
|
513
|
+
provider: "openai",
|
|
514
|
+
nativeId: "gpt-5.4-mini",
|
|
515
|
+
capabilities: chatCapabilities({ effort: true }),
|
|
516
|
+
contextWindow: 4e5
|
|
517
|
+
},
|
|
128
518
|
{
|
|
129
519
|
id: "gpt-5",
|
|
130
520
|
provider: "openai",
|
|
@@ -242,37 +632,60 @@ function providersInCatalog(catalog) {
|
|
|
242
632
|
}
|
|
243
633
|
|
|
244
634
|
// src/providers/registry.ts
|
|
635
|
+
var CLIENT_TTL_MS = 6e4;
|
|
245
636
|
var cache = /* @__PURE__ */ new Map();
|
|
637
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
638
|
+
var overrides = /* @__PURE__ */ new Map();
|
|
246
639
|
async function getProvider(id, apiKey) {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
640
|
+
const override = overrides.get(id);
|
|
641
|
+
if (override?.apiKey === apiKey) return override.provider;
|
|
642
|
+
const fingerprint = await fingerprintKey(apiKey);
|
|
643
|
+
const existing = cache.get(id);
|
|
644
|
+
if (existing?.fingerprint === fingerprint && existing.expiresAt > Date.now()) return existing.provider;
|
|
645
|
+
if (existing) cache.delete(id);
|
|
646
|
+
const pending = inflight.get(id);
|
|
647
|
+
if (pending?.fingerprint === fingerprint) return pending.promise;
|
|
648
|
+
const promise = createProvider(id, apiKey).then((provider) => {
|
|
649
|
+
cache.set(id, { fingerprint, provider, expiresAt: Date.now() + CLIENT_TTL_MS });
|
|
650
|
+
return provider;
|
|
651
|
+
}).finally(() => {
|
|
652
|
+
if (inflight.get(id)?.promise === promise) inflight.delete(id);
|
|
653
|
+
});
|
|
654
|
+
inflight.set(id, { fingerprint, promise });
|
|
655
|
+
return promise;
|
|
656
|
+
}
|
|
657
|
+
async function createProvider(id, apiKey) {
|
|
250
658
|
let provider;
|
|
251
659
|
switch (id) {
|
|
252
660
|
case "anthropic": {
|
|
253
|
-
const { AnthropicProvider } = await import("./anthropic-
|
|
661
|
+
const { AnthropicProvider } = await import("./anthropic-2NPMHJZT.js");
|
|
254
662
|
provider = new AnthropicProvider(apiKey);
|
|
255
663
|
break;
|
|
256
664
|
}
|
|
257
665
|
case "openai": {
|
|
258
|
-
const { OpenAIProvider } = await import("./openai-
|
|
666
|
+
const { OpenAIProvider } = await import("./openai-NTQCF577.js");
|
|
259
667
|
provider = new OpenAIProvider(apiKey);
|
|
260
668
|
break;
|
|
261
669
|
}
|
|
262
670
|
case "google": {
|
|
263
|
-
const { GoogleProvider } = await import("./google-
|
|
671
|
+
const { GoogleProvider } = await import("./google-3XV2VWVB.js");
|
|
264
672
|
provider = new GoogleProvider(apiKey);
|
|
265
673
|
break;
|
|
266
674
|
}
|
|
267
675
|
}
|
|
268
|
-
cache.set(cacheKey, provider);
|
|
269
676
|
return provider;
|
|
270
677
|
}
|
|
678
|
+
async function fingerprintKey(apiKey) {
|
|
679
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(apiKey));
|
|
680
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
681
|
+
}
|
|
271
682
|
function registerProvider(id, apiKey, provider) {
|
|
272
|
-
|
|
683
|
+
overrides.set(id, { apiKey, provider });
|
|
273
684
|
}
|
|
274
685
|
function clearProviderCache() {
|
|
275
686
|
cache.clear();
|
|
687
|
+
inflight.clear();
|
|
688
|
+
overrides.clear();
|
|
276
689
|
}
|
|
277
690
|
|
|
278
691
|
// src/client/keys.ts
|
|
@@ -342,7 +755,9 @@ function init(opts = {}) {
|
|
|
342
755
|
messages: [{ role: "user", content: c.user }],
|
|
343
756
|
tools: [{ name: c.tool.name, description: c.tool.description ?? "", inputSchema: c.tool.parameters }],
|
|
344
757
|
toolChoice: { type: "tool", name: c.tool.name },
|
|
345
|
-
maxTokens: c.maxTokens ?? 4096
|
|
758
|
+
maxTokens: c.maxTokens ?? 4096,
|
|
759
|
+
signal: c.signal,
|
|
760
|
+
deadline: c.deadline
|
|
346
761
|
};
|
|
347
762
|
const { spec, provider } = await resolveProviderFor(model);
|
|
348
763
|
validateRequest(spec, req);
|
|
@@ -351,6 +766,11 @@ function init(opts = {}) {
|
|
|
351
766
|
if (!block || block.type !== "tool_use") {
|
|
352
767
|
throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`);
|
|
353
768
|
}
|
|
769
|
+
const validation = validateJsonSchema(c.tool.parameters, block.input);
|
|
770
|
+
if (!validation.valid) {
|
|
771
|
+
const detail = validation.issues.slice(0, 5).map((i) => `${i.path} ${i.message}`).join("; ");
|
|
772
|
+
throw new ToolInputError(`Tool "${c.tool.name}" returned invalid input: ${detail}`, { tool: c.tool.name });
|
|
773
|
+
}
|
|
354
774
|
return { value: block.input, usage: res.usage };
|
|
355
775
|
};
|
|
356
776
|
const search = async (c) => {
|
|
@@ -360,7 +780,9 @@ function init(opts = {}) {
|
|
|
360
780
|
system: c.system,
|
|
361
781
|
messages: [{ role: "user", content: c.user }],
|
|
362
782
|
webSearch: true,
|
|
363
|
-
maxTokens: c.maxTokens ?? 8192
|
|
783
|
+
maxTokens: c.maxTokens ?? 8192,
|
|
784
|
+
signal: c.signal,
|
|
785
|
+
deadline: c.deadline
|
|
364
786
|
};
|
|
365
787
|
const { spec, provider } = await resolveProviderFor(model);
|
|
366
788
|
validateRequest(spec, req);
|
|
@@ -386,16 +808,20 @@ function generateLlmsTxt(catalog = DEFAULT_CATALOG) {
|
|
|
386
808
|
const models = Object.values(catalog).map((s) => `- \`${s.id}\` (${s.provider}) \u2014 ${capsSummary(s.capabilities)}`).join("\n");
|
|
387
809
|
return `# @odla-ai/ai \u2014 LLM context
|
|
388
810
|
|
|
811
|
+
> EARLY ACCESS \u2014 pre-1.0. Agents work from bounded runbooks; humans approve
|
|
812
|
+
> credentials, production changes, releases, and merges. APIs and exact package
|
|
813
|
+
> availability can change. Review documented guarantees and limitations; this
|
|
814
|
+
> software is MIT-licensed and provided without warranty.
|
|
815
|
+
|
|
389
816
|
One general-purpose interface for AI inference across Anthropic (Claude), OpenAI
|
|
390
817
|
(GPT), and Google (Gemini) \u2014 text, image, and audio \u2014 plus a tool-use agent
|
|
391
818
|
engine and an eval harness. Library-only: import in-process, bring your own keys.
|
|
392
|
-
|
|
819
|
+
Targets Node 20+ and Cloudflare Workers. Provider adapters are runtime-lazy, but
|
|
820
|
+
all provider SDKs are package dependencies. Never expose long-lived keys in a browser.
|
|
393
821
|
|
|
394
822
|
## Install
|
|
395
823
|
|
|
396
824
|
npm i @odla-ai/ai
|
|
397
|
-
# provider SDKs are peer-lazy-loaded; install those you use:
|
|
398
|
-
# @anthropic-ai/sdk openai @google/genai
|
|
399
825
|
# optional, for odla-db-backed storage + secrets:
|
|
400
826
|
# @odla-ai/db
|
|
401
827
|
|
|
@@ -409,7 +835,8 @@ Isomorphic (Node 20+, Cloudflare Workers, browser). Provider SDKs are lazy-loade
|
|
|
409
835
|
- \`ai.stream(input)\` \u2192 async iterable of normalized events: message_start \u2192
|
|
410
836
|
content_block_start \u2192 content_block_delta {text_delta|thinking_delta|input_json_delta}
|
|
411
837
|
\u2192 content_block_stop \u2192 message_delta \u2192 message_stop (plus \`error\`).
|
|
412
|
-
- \`ai.extract<T>({ model, user, tool })\` \u2192 forced tool call; returns
|
|
838
|
+
- \`ai.extract<T>({ model, user, tool })\` \u2192 forced tool call; returns an object
|
|
839
|
+
validated against the declared JSON Schema. Unsupported assertions fail closed.
|
|
413
840
|
- \`ai.search({ model, user })\` \u2192 web-search-grounded prose.
|
|
414
841
|
|
|
415
842
|
## Content blocks (multimodal)
|
|
@@ -428,7 +855,15 @@ audio block to a Claude model throws CapabilityError before any network call.
|
|
|
428
855
|
// run.finalText, run.toolCalls, run.usage, run.steps, run.stoppedReason
|
|
429
856
|
|
|
430
857
|
Tools may carry taint labels (outputTaint / acceptsTaint) for a CaMeL-style
|
|
431
|
-
prompt-injection gate. Personas may carry a MemoryScope.
|
|
858
|
+
prompt-injection gate. Personas may carry a MemoryScope. Tool inputs are schema-
|
|
859
|
+
validated before handlers run. Pass signal/deadline and a run budget
|
|
860
|
+
({maxInputTokens,maxOutputTokens,maxTotalTokens,maxToolCalls}) to bound follow-up
|
|
861
|
+
effects and requested output. Input and total limits are post-turn ceilings:
|
|
862
|
+
provider usage is unavailable before the first response, so they cannot
|
|
863
|
+
tokenizer-bound the first prompt. Output and remaining-total limits cap the next
|
|
864
|
+
request's maxTokens. Token limits must be positive integers; maxToolCalls may be
|
|
865
|
+
zero. Budget exits pair every requested tool_use with an error tool_result before
|
|
866
|
+
memory is persisted.
|
|
432
867
|
|
|
433
868
|
## Skills
|
|
434
869
|
|
|
@@ -455,14 +890,17 @@ device-authorization handshake for a scoped, revocable token, then provisions.
|
|
|
455
890
|
import { requestToken, init as odlaInit } from "@odla-ai/db";
|
|
456
891
|
import { provisionAgentApp, odlaDbKeyResolver, OdlaDbMemory, persistRun, init } from "@odla-ai/ai";
|
|
457
892
|
|
|
458
|
-
|
|
459
|
-
const
|
|
893
|
+
const ODLA_PLATFORM = "https://odla.ai";
|
|
894
|
+
const ODLA_DB_URL = "https://db.odla.ai";
|
|
895
|
+
|
|
896
|
+
// 1. Handshake: the human approves a short code at odla.ai.
|
|
897
|
+
const { token } = await requestToken({ endpoint: ODLA_PLATFORM, onCode: ({ userCode }) => show(userCode) });
|
|
460
898
|
|
|
461
899
|
// 2. Provision: create app, mint app key, push agent schema, store BYO provider keys as secrets.
|
|
462
|
-
const { appId, appKey } = await provisionAgentApp({ endpoint:
|
|
900
|
+
const { appId, appKey } = await provisionAgentApp({ endpoint: ODLA_PLATFORM, token, providerKeys: { openai: OPENAI_KEY } });
|
|
463
901
|
|
|
464
902
|
// 3. Runtime: read keys from odla-db secrets; persist memory + run traces.
|
|
465
|
-
const db = odlaInit({ appId, adminToken: appKey, endpoint:
|
|
903
|
+
const db = odlaInit({ appId, adminToken: appKey, endpoint: ODLA_DB_URL });
|
|
466
904
|
const ai = init({ resolveKey: odlaDbKeyResolver(db) });
|
|
467
905
|
const persona = { name: "assistant", model: "gpt-5", memory: new OdlaDbMemory({ db, sessionId: "user-42" }) };
|
|
468
906
|
const run = await runAgent(ai, persona, { input: "hello" });
|
|
@@ -488,16 +926,22 @@ env's odla-db tenant secrets, write-only for operators). One call reads both:
|
|
|
488
926
|
platform: "https://odla.ai", appId: APP_ID, env: "prod", db });
|
|
489
927
|
// ai.chat / ai.stream / ai.extract \u2014 provider/model from public-config
|
|
490
928
|
// (cached ~60s, so Studio changes apply without redeploys); the key is
|
|
491
|
-
// fetched from the vault
|
|
929
|
+
// fetched from the vault via a finite-TTL odlaDbKeyResolver.
|
|
492
930
|
|
|
493
931
|
The only secret the Worker carries is its odla-db app key. Rotating the LLM
|
|
494
|
-
key = writing the secret again;
|
|
932
|
+
key = writing the secret again; the default warm-isolate key TTL is 60 seconds
|
|
933
|
+
and cacheVersion provides immediate invalidation. Provider SDK clients are
|
|
934
|
+
fingerprinted, bounded to one generation per provider, and expire after 60
|
|
935
|
+
seconds. The platform provider is enforced: models from other providers are
|
|
936
|
+
absent from the facade catalog. If no default model is configured, each call
|
|
937
|
+
must name a model for that provider. Only public config is shared; tenant-bound
|
|
938
|
+
Ai facades are not cached globally.
|
|
495
939
|
|
|
496
940
|
## Errors
|
|
497
941
|
|
|
498
942
|
Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on err.code:
|
|
499
943
|
config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
|
|
500
|
-
tool_input_invalid, provider_error.
|
|
944
|
+
tool_input_invalid, cancelled, deadline_exceeded, provider_error.
|
|
501
945
|
|
|
502
946
|
## Models
|
|
503
947
|
|
|
@@ -547,6 +991,7 @@ function assertSinkAcceptsTaint(sink, allowed, actual) {
|
|
|
547
991
|
|
|
548
992
|
// src/agent/loop.ts
|
|
549
993
|
async function runAgent(inference, persona, input) {
|
|
994
|
+
validateRunLimits(persona.maxSteps, input.budget);
|
|
550
995
|
const { system, tools } = composeSkills(persona.system, persona.tools, persona.skills);
|
|
551
996
|
const handlerByName = /* @__PURE__ */ new Map();
|
|
552
997
|
for (const t of tools) if (t.handler) handlerByName.set(t.name, t);
|
|
@@ -558,21 +1003,26 @@ async function runAgent(inference, persona, input) {
|
|
|
558
1003
|
const toolCalls = [];
|
|
559
1004
|
const accumulatedTaint = /* @__PURE__ */ new Set(["llm_inherited"]);
|
|
560
1005
|
const maxSteps = persona.maxSteps ?? 8;
|
|
1006
|
+
const runSignal = signalWithDeadline(input.signal, input.deadline);
|
|
561
1007
|
let response;
|
|
562
1008
|
let stoppedReason = "max_steps";
|
|
563
1009
|
let steps = 0;
|
|
1010
|
+
let attemptedToolCalls = 0;
|
|
564
1011
|
while (steps < maxSteps) {
|
|
565
1012
|
steps++;
|
|
1013
|
+
const remainingOutput = remainingOutputTokens(usage, input.budget);
|
|
566
1014
|
response = await inference.chat({
|
|
567
1015
|
model: persona.model,
|
|
568
1016
|
system,
|
|
569
1017
|
messages,
|
|
570
1018
|
tools: tools.length > 0 ? tools.map(toOracleTool) : void 0,
|
|
571
|
-
maxTokens: persona.maxTokens ?? 4096,
|
|
1019
|
+
maxTokens: Math.min(persona.maxTokens ?? 4096, remainingOutput ?? Number.POSITIVE_INFINITY),
|
|
572
1020
|
effort: persona.effort,
|
|
573
1021
|
thinking: persona.thinking,
|
|
574
1022
|
temperature: persona.temperature,
|
|
575
|
-
webSearch: persona.webSearch
|
|
1023
|
+
webSearch: persona.webSearch,
|
|
1024
|
+
signal: runSignal,
|
|
1025
|
+
deadline: input.deadline
|
|
576
1026
|
});
|
|
577
1027
|
addUsage(usage, response.usage);
|
|
578
1028
|
messages.push({ role: "assistant", content: response.content });
|
|
@@ -585,8 +1035,22 @@ async function runAgent(inference, persona, input) {
|
|
|
585
1035
|
stoppedReason = "end_turn";
|
|
586
1036
|
break;
|
|
587
1037
|
}
|
|
1038
|
+
if (budgetExceeded(usage, input.budget)) {
|
|
1039
|
+
stoppedReason = "budget_exhausted";
|
|
1040
|
+
messages.push({
|
|
1041
|
+
role: "user",
|
|
1042
|
+
content: toolUses.map((toolUse) => errorResult(toolUse.id, "Run budget exhausted before tool execution."))
|
|
1043
|
+
});
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
588
1046
|
const resultBlocks = [];
|
|
589
1047
|
for (const toolUse of toolUses) {
|
|
1048
|
+
if (input.budget?.maxToolCalls !== void 0 && attemptedToolCalls >= input.budget.maxToolCalls) {
|
|
1049
|
+
stoppedReason = "budget_exhausted";
|
|
1050
|
+
resultBlocks.push(errorResult(toolUse.id, "Run tool-call budget exhausted before execution."));
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
attemptedToolCalls++;
|
|
590
1054
|
const def = handlerByName.get(toolUse.name);
|
|
591
1055
|
if (!def || !def.handler) {
|
|
592
1056
|
resultBlocks.push(errorResult(toolUse.id, `No local handler for tool "${toolUse.name}".`));
|
|
@@ -594,16 +1058,26 @@ async function runAgent(inference, persona, input) {
|
|
|
594
1058
|
}
|
|
595
1059
|
if (def.acceptsTaint) assertSinkAcceptsTaint(def.name, def.acceptsTaint, accumulatedTaint);
|
|
596
1060
|
let output;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
output = { content: `Tool "${toolUse.name}"
|
|
1061
|
+
const validation = validateJsonSchema(def.inputSchema, toolUse.input);
|
|
1062
|
+
if (!validation.valid) {
|
|
1063
|
+
const detail = validation.issues.slice(0, 5).map((i) => `${i.path} ${i.message}`).join("; ");
|
|
1064
|
+
output = { content: `Tool "${toolUse.name}" input is invalid: ${detail}`, isError: true };
|
|
1065
|
+
} else {
|
|
1066
|
+
throwIfAborted(runSignal);
|
|
1067
|
+
try {
|
|
1068
|
+
output = await def.handler(toolUse.input, { taint: new Set(accumulatedTaint), signal: runSignal });
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
if (error instanceof CancelledError || error instanceof DeadlineExceededError) throw error;
|
|
1071
|
+
throwIfAborted(runSignal);
|
|
1072
|
+
output = { content: `Tool "${toolUse.name}" failed.`, isError: true };
|
|
1073
|
+
}
|
|
601
1074
|
}
|
|
602
1075
|
toolCalls.push({ toolUse, output });
|
|
603
1076
|
resultBlocks.push({ type: "tool_result", toolUseId: toolUse.id, content: output.content, isError: output.isError });
|
|
604
1077
|
for (const label of def.outputTaint ?? []) accumulatedTaint.add(label);
|
|
605
1078
|
}
|
|
606
1079
|
messages.push({ role: "user", content: resultBlocks });
|
|
1080
|
+
if (stoppedReason === "budget_exhausted") break;
|
|
607
1081
|
}
|
|
608
1082
|
if (!response) throw new Error("runAgent: maxSteps must be at least 1");
|
|
609
1083
|
if (persona.memory) await persona.memory.append(messages.slice(runStartIndex));
|
|
@@ -617,6 +1091,36 @@ async function runAgent(inference, persona, input) {
|
|
|
617
1091
|
stoppedReason
|
|
618
1092
|
};
|
|
619
1093
|
}
|
|
1094
|
+
function budgetExceeded(usage, budget) {
|
|
1095
|
+
if (!budget) return false;
|
|
1096
|
+
if (budget.maxInputTokens !== void 0 && usage.inputTokens >= budget.maxInputTokens) return true;
|
|
1097
|
+
if (budget.maxOutputTokens !== void 0 && usage.outputTokens >= budget.maxOutputTokens) return true;
|
|
1098
|
+
if (budget.maxTotalTokens !== void 0 && usage.inputTokens + usage.outputTokens >= budget.maxTotalTokens) return true;
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
function remainingOutputTokens(usage, budget) {
|
|
1102
|
+
if (!budget) return void 0;
|
|
1103
|
+
const remaining = [
|
|
1104
|
+
budget.maxOutputTokens === void 0 ? void 0 : budget.maxOutputTokens - usage.outputTokens,
|
|
1105
|
+
budget.maxTotalTokens === void 0 ? void 0 : budget.maxTotalTokens - usage.inputTokens - usage.outputTokens
|
|
1106
|
+
].filter((value) => value !== void 0);
|
|
1107
|
+
return remaining.length === 0 ? void 0 : Math.max(1, Math.min(...remaining));
|
|
1108
|
+
}
|
|
1109
|
+
function validateRunLimits(maxSteps, budget) {
|
|
1110
|
+
if (maxSteps !== void 0 && (!Number.isInteger(maxSteps) || maxSteps < 1)) {
|
|
1111
|
+
throw new ConfigError("persona.maxSteps must be a positive integer.");
|
|
1112
|
+
}
|
|
1113
|
+
if (!budget) return;
|
|
1114
|
+
for (const key of ["maxInputTokens", "maxOutputTokens", "maxTotalTokens"]) {
|
|
1115
|
+
const value = budget[key];
|
|
1116
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
1117
|
+
throw new ConfigError(`budget.${key} must be a positive integer.`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (budget.maxToolCalls !== void 0 && (!Number.isInteger(budget.maxToolCalls) || budget.maxToolCalls < 0)) {
|
|
1121
|
+
throw new ConfigError("budget.maxToolCalls must be a non-negative integer.");
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
620
1124
|
function errorResult(toolUseId, message) {
|
|
621
1125
|
return { type: "tool_result", toolUseId, content: message, isError: true };
|
|
622
1126
|
}
|
|
@@ -919,22 +1423,32 @@ var DEFAULT_SECRET_NAMES = {
|
|
|
919
1423
|
};
|
|
920
1424
|
function odlaDbKeyResolver(db, opts = {}) {
|
|
921
1425
|
const nameFor = opts.secretName ?? ((p) => DEFAULT_SECRET_NAMES[p]);
|
|
922
|
-
const
|
|
1426
|
+
const ttlMs = opts.cache === false ? 0 : Math.max(0, opts.ttlMs ?? 6e4);
|
|
923
1427
|
const cache3 = /* @__PURE__ */ new Map();
|
|
924
1428
|
return async (provider) => {
|
|
925
|
-
|
|
1429
|
+
const version = typeof opts.cacheVersion === "function" ? opts.cacheVersion(provider) : opts.cacheVersion ?? "";
|
|
1430
|
+
if (ttlMs > 0) {
|
|
926
1431
|
const hit = cache3.get(provider);
|
|
927
|
-
if (hit
|
|
1432
|
+
if (hit?.version === version && hit.expiresAt > Date.now()) return hit.value;
|
|
1433
|
+
cache3.delete(provider);
|
|
928
1434
|
}
|
|
929
1435
|
try {
|
|
930
1436
|
const key = await db.secrets.get(nameFor(provider));
|
|
931
|
-
if (
|
|
1437
|
+
if (ttlMs > 0) cache3.set(provider, { version, value: key, expiresAt: Date.now() + ttlMs });
|
|
932
1438
|
return key;
|
|
933
|
-
} catch {
|
|
934
|
-
return void 0;
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
if (isMissingSecret(error)) return void 0;
|
|
1441
|
+
throw error;
|
|
935
1442
|
}
|
|
936
1443
|
};
|
|
937
1444
|
}
|
|
1445
|
+
function isMissingSecret(error) {
|
|
1446
|
+
if (!error || typeof error !== "object") return false;
|
|
1447
|
+
const rec = error;
|
|
1448
|
+
if (rec.status === 404 || rec.statusCode === 404 || rec.code === "not_found") return true;
|
|
1449
|
+
const message = error instanceof Error ? error.message : "";
|
|
1450
|
+
return /\b404\b/.test(message);
|
|
1451
|
+
}
|
|
938
1452
|
|
|
939
1453
|
// src/store/platform.ts
|
|
940
1454
|
var cache2 = /* @__PURE__ */ new Map();
|
|
@@ -965,19 +1479,22 @@ async function initFromPlatform(opts) {
|
|
|
965
1479
|
const key = `${opts.platform}|${opts.appId}|${opts.env}`;
|
|
966
1480
|
const now = Date.now();
|
|
967
1481
|
const hit = ttl > 0 ? cache2.get(key) : void 0;
|
|
968
|
-
|
|
969
|
-
const { provider, model } =
|
|
970
|
-
if (
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1482
|
+
const config = hit && hit.expiresAt > now ? hit.value : await fetchAiConfig(opts);
|
|
1483
|
+
const { provider, model } = config;
|
|
1484
|
+
if (ttl > 0 && (!hit || hit.expiresAt <= now)) cache2.set(key, { value: config, expiresAt: now + ttl });
|
|
1485
|
+
const sourceCatalog = opts.initOptions?.catalog ?? DEFAULT_CATALOG;
|
|
1486
|
+
const catalog = Object.fromEntries(
|
|
1487
|
+
Object.entries(sourceCatalog).filter(([, spec]) => spec.provider === provider)
|
|
1488
|
+
);
|
|
1489
|
+
const defaultModel = model ?? opts.initOptions?.defaultModel;
|
|
1490
|
+
if (defaultModel) resolveModel(catalog, defaultModel);
|
|
974
1491
|
const ai = init({
|
|
975
1492
|
...opts.initOptions,
|
|
976
|
-
|
|
977
|
-
|
|
1493
|
+
catalog,
|
|
1494
|
+
resolveKey: odlaDbKeyResolver(opts.db, opts.keyResolverOptions),
|
|
1495
|
+
defaultModel
|
|
978
1496
|
});
|
|
979
1497
|
const value = { ai, provider, ...model ? { model } : {} };
|
|
980
|
-
if (ttl > 0) cache2.set(key, { value, expiresAt: now + ttl });
|
|
981
1498
|
return value;
|
|
982
1499
|
}
|
|
983
1500
|
|
|
@@ -1170,11 +1687,13 @@ export {
|
|
|
1170
1687
|
AGENT_SCHEMA,
|
|
1171
1688
|
ANTHROPIC_MODELS,
|
|
1172
1689
|
AuthError,
|
|
1690
|
+
CancelledError,
|
|
1173
1691
|
CapabilityError,
|
|
1174
1692
|
ConfigError,
|
|
1175
1693
|
ContextWindowError,
|
|
1176
1694
|
DEFAULT_CATALOG,
|
|
1177
1695
|
DEFAULT_SECRET_NAMES,
|
|
1696
|
+
DeadlineExceededError,
|
|
1178
1697
|
GOOGLE_MODELS,
|
|
1179
1698
|
InMemoryScope,
|
|
1180
1699
|
InvalidRequestError,
|
|
@@ -1185,6 +1704,7 @@ export {
|
|
|
1185
1704
|
ProviderError,
|
|
1186
1705
|
RateLimitError,
|
|
1187
1706
|
TaintError,
|
|
1707
|
+
ToolInputError,
|
|
1188
1708
|
addUsage,
|
|
1189
1709
|
assertSinkAcceptsTaint,
|
|
1190
1710
|
blocksOf,
|
|
@@ -1230,6 +1750,7 @@ export {
|
|
|
1230
1750
|
structuredMatch,
|
|
1231
1751
|
taint,
|
|
1232
1752
|
toOracleTool,
|
|
1753
|
+
validateJsonSchema,
|
|
1233
1754
|
validateRequest
|
|
1234
1755
|
};
|
|
1235
1756
|
//# sourceMappingURL=index.js.map
|