@yourgpt/llm-sdk 2.6.0-beta.3 → 2.6.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -10
  3. package/dist/adapters/index.d.mts +11 -13
  4. package/dist/adapters/index.d.ts +11 -13
  5. package/dist/adapters/index.js +327 -100
  6. package/dist/adapters/index.mjs +327 -100
  7. package/dist/audio-K-Z3-Bce.d.mts +191 -0
  8. package/dist/audio-K-Z3-Bce.d.ts +191 -0
  9. package/dist/{base-BICaQaRS.d.mts → base-6LzczuCg.d.ts} +8 -1
  10. package/dist/{base-CpRPB8u8.d.ts → base-DySbTP8C.d.mts} +8 -1
  11. package/dist/decisions-CJzOt87t.d.mts +137 -0
  12. package/dist/decisions-CJzOt87t.d.ts +137 -0
  13. package/dist/fallback/index.d.mts +4 -4
  14. package/dist/fallback/index.d.ts +4 -4
  15. package/dist/http-BQxUT5A9.d.mts +9 -0
  16. package/dist/http-BQxUT5A9.d.ts +9 -0
  17. package/dist/index.d.mts +14 -8
  18. package/dist/index.d.ts +14 -8
  19. package/dist/index.js +599 -195
  20. package/dist/index.mjs +596 -196
  21. package/dist/providers/anthropic/index.d.mts +7 -4
  22. package/dist/providers/anthropic/index.d.ts +7 -4
  23. package/dist/providers/anthropic/index.js +383 -102
  24. package/dist/providers/anthropic/index.mjs +383 -102
  25. package/dist/providers/azure/index.d.mts +3 -3
  26. package/dist/providers/azure/index.d.ts +3 -3
  27. package/dist/providers/cloudflare/index.d.mts +11 -0
  28. package/dist/providers/cloudflare/index.d.ts +11 -0
  29. package/dist/providers/cloudflare/index.js +743 -0
  30. package/dist/providers/cloudflare/index.mjs +741 -0
  31. package/dist/providers/fireworks/index.d.mts +3 -3
  32. package/dist/providers/fireworks/index.d.ts +3 -3
  33. package/dist/providers/fireworks/index.js +108 -59
  34. package/dist/providers/fireworks/index.mjs +108 -59
  35. package/dist/providers/google/index.d.mts +3 -3
  36. package/dist/providers/google/index.d.ts +3 -3
  37. package/dist/providers/google/index.js +108 -59
  38. package/dist/providers/google/index.mjs +108 -59
  39. package/dist/providers/ollama/index.d.mts +4 -4
  40. package/dist/providers/ollama/index.d.ts +4 -4
  41. package/dist/providers/openai/index.d.mts +38 -6
  42. package/dist/providers/openai/index.d.ts +38 -6
  43. package/dist/providers/openai/index.js +1378 -84
  44. package/dist/providers/openai/index.mjs +1376 -85
  45. package/dist/providers/openrouter/index.d.mts +21 -4
  46. package/dist/providers/openrouter/index.d.ts +21 -4
  47. package/dist/providers/openrouter/index.js +967 -130
  48. package/dist/providers/openrouter/index.mjs +967 -131
  49. package/dist/providers/togetherai/index.d.mts +3 -3
  50. package/dist/providers/togetherai/index.d.ts +3 -3
  51. package/dist/providers/togetherai/index.js +108 -59
  52. package/dist/providers/togetherai/index.mjs +108 -59
  53. package/dist/providers/typesafe/index.d.mts +8 -0
  54. package/dist/providers/typesafe/index.d.ts +8 -0
  55. package/dist/providers/typesafe/index.js +735 -0
  56. package/dist/providers/typesafe/index.mjs +733 -0
  57. package/dist/providers/xai/index.d.mts +3 -3
  58. package/dist/providers/xai/index.d.ts +3 -3
  59. package/dist/providers/xai/index.js +108 -59
  60. package/dist/providers/xai/index.mjs +108 -59
  61. package/dist/{types-DCFqkJAy.d.ts → types-B0y6VZSQ.d.ts} +1 -1
  62. package/dist/{types-DHQEMFHA.d.mts → types-BXQMtaCX.d.ts} +17 -3
  63. package/dist/{types-C3RgRbEC.d.mts → types-BY6BVhFg.d.mts} +1 -1
  64. package/dist/{types-Dikj3En4.d.ts → types-C7_qVSSd.d.mts} +17 -3
  65. package/dist/{types-Dg1Bcg1k.d.mts → types-D7lzMMLu.d.mts} +13 -1
  66. package/dist/{types-Dg1Bcg1k.d.ts → types-D7lzMMLu.d.ts} +13 -1
  67. package/dist/yourgpt/index.d.mts +1 -1
  68. package/dist/yourgpt/index.d.ts +1 -1
  69. package/docs/decisions.md +174 -0
  70. package/docs/media.md +201 -0
  71. package/docs/multimodal-and-audio.md +9 -2
  72. package/package.json +34 -12
  73. package/dist/audio-BBSwnjo8.d.mts +0 -92
  74. package/dist/audio-BBSwnjo8.d.ts +0 -92
@@ -0,0 +1,735 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/providers/decisions/model.ts
6
+
7
+ // src/core/decisions.ts
8
+ var decisionErrorMarker = /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError");
9
+ var _a, _b;
10
+ var DecisionAPIError = class extends (_b = Error, _a = decisionErrorMarker, _b) {
11
+ constructor(message, code, status, requestId) {
12
+ super(message);
13
+ this.code = code;
14
+ this.status = status;
15
+ this.requestId = requestId;
16
+ this.name = "DecisionAPIError";
17
+ this[_a] = true;
18
+ }
19
+ /** Works across CJS/ESM and separately bundled provider entry points. */
20
+ static isInstance(error) {
21
+ return typeof error === "object" && error !== null && /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError") in error;
22
+ }
23
+ /** Advisory only: this SDK does not retry Decisions automatically. */
24
+ get retryable() {
25
+ return this.code === "network_error" || this.code === "http_error" && (this.status === 408 || this.status === 429 || this.status != null && this.status >= 500);
26
+ }
27
+ };
28
+
29
+ // src/core/decision-validation.ts
30
+ function decisionInputError(message) {
31
+ throw new DecisionAPIError(message, "invalid_input");
32
+ }
33
+ function decisionResponseError(message) {
34
+ throw new DecisionAPIError(message, "invalid_response");
35
+ }
36
+ function isDecisionRecord(value) {
37
+ if (value === null || typeof value !== "object" || Array.isArray(value))
38
+ return false;
39
+ const proto = Object.getPrototypeOf(value);
40
+ return proto === Object.prototype || proto === null;
41
+ }
42
+ function json(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
43
+ if (depth > 64) return false;
44
+ if (value === null || typeof value === "string" || typeof value === "boolean")
45
+ return true;
46
+ if (typeof value === "number") return Number.isFinite(value);
47
+ if (typeof value !== "object" || !Array.isArray(value) && !isDecisionRecord(value) || seen.has(value))
48
+ return false;
49
+ seen.add(value);
50
+ const valid = Object.getOwnPropertySymbols(value).length === 0 && (Array.isArray(value) ? Array.from(value) : Object.values(value)).every(
51
+ (item) => json(item, seen, depth + 1)
52
+ );
53
+ seen.delete(value);
54
+ return valid;
55
+ }
56
+ function input(value) {
57
+ return (typeof value === "string" || Array.isArray(value) || isDecisionRecord(value)) && json(value);
58
+ }
59
+ function assertDecisionKeys(value, allowed, label) {
60
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
61
+ decisionInputError(`${label} contains an unsupported field`);
62
+ }
63
+ function validateDecisionInput(options) {
64
+ if (!isDecisionRecord(options))
65
+ decisionInputError("Decisions options must be an object");
66
+ assertDecisionKeys(
67
+ options,
68
+ ["state", "questions", "images", "signal", "timeoutMs", "providerOptions"],
69
+ "Decisions options"
70
+ );
71
+ if (!input(options.state))
72
+ decisionInputError(
73
+ "state must be finite, acyclic JSON text, object, or array (maximum depth 64)"
74
+ );
75
+ if (!isDecisionRecord(options.questions) || Object.keys(options.questions).length === 0)
76
+ decisionInputError("questions must be a nonempty map");
77
+ for (const [id, question] of Object.entries(options.questions)) {
78
+ if (!id.length || !isDecisionRecord(question) || !input(question.instructions))
79
+ decisionInputError(
80
+ "Every question needs a nonempty ID and JSON-compatible instructions"
81
+ );
82
+ assertDecisionKeys(
83
+ question,
84
+ ["type", "instructions", "criteria"],
85
+ "Question"
86
+ );
87
+ if (question.type === "boolean") {
88
+ if (question.criteria !== void 0 && (!isDecisionRecord(question.criteria) || Object.entries(question.criteria).some(
89
+ ([key, value]) => !["true", "false"].includes(key) || !input(value)
90
+ )))
91
+ decisionInputError("Boolean criteria may describe only true and false");
92
+ } else if (question.type === "choice") {
93
+ if (!isDecisionRecord(question.criteria) || Object.keys(question.criteria).length < 2 || Object.keys(question.criteria).length > 255 || Object.entries(question.criteria).some(
94
+ ([key, value]) => !key.length || value !== null && !input(value)
95
+ ))
96
+ decisionInputError(
97
+ "Choice criteria require 2\u2013255 named options with JSON descriptions or null"
98
+ );
99
+ } else if (question.type === "score") {
100
+ if (!Array.isArray(question.criteria) || question.criteria.length < 2 || !Array.from(question.criteria).every(input))
101
+ decisionInputError(
102
+ "Score criteria require at least two ordered JSON descriptions"
103
+ );
104
+ } else
105
+ decisionInputError("Question type must be boolean, choice, or score");
106
+ }
107
+ if (options.timeoutMs !== void 0 && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 2147483647))
108
+ decisionInputError("timeoutMs must be a positive 32-bit integer");
109
+ if (options.providerOptions !== void 0 && (!isDecisionRecord(options.providerOptions) || !json(options.providerOptions)))
110
+ decisionInputError("providerOptions must be a JSON object");
111
+ if (options.images !== void 0 && (!Array.isArray(options.images) || !Array.from(options.images).every(
112
+ (image) => isDecisionRecord(image) && image.data instanceof Uint8Array
113
+ )))
114
+ decisionInputError(
115
+ "Decision images must contain encoded bytes; hosted image URLs are unsupported"
116
+ );
117
+ }
118
+ function probability(value) {
119
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
120
+ }
121
+ function exactKeys(value, keys) {
122
+ return Object.keys(value).length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
123
+ }
124
+ function validateDecisionResult(questions, result) {
125
+ if (!isDecisionRecord(result) || !isDecisionRecord(result.answers) || !exactKeys(result.answers, Object.keys(questions)))
126
+ decisionResponseError(
127
+ "Decisions must return exactly one answer per question"
128
+ );
129
+ const rounding = result.rounding;
130
+ let pError = 0, sError = 0;
131
+ if (rounding !== void 0) {
132
+ if (!isDecisionRecord(rounding) || ![rounding.probabilityDecimals, rounding.scoreDecimals].every(
133
+ (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 15
134
+ ))
135
+ decisionResponseError("Invalid decision rounding precision");
136
+ pError = 0.5 * 10 ** -rounding.probabilityDecimals;
137
+ sError = 0.5 * 10 ** -rounding.scoreDecimals;
138
+ }
139
+ const epsilon = 1e-6;
140
+ for (const [id, question] of Object.entries(questions)) {
141
+ const answer = result.answers[id];
142
+ if (!isDecisionRecord(answer))
143
+ decisionResponseError("Invalid decision answer");
144
+ if (answer.type === "refusal") continue;
145
+ if (answer.type !== question.type)
146
+ decisionResponseError("Decision answer type does not match its question");
147
+ if (answer.confidence !== void 0 && !probability(answer.confidence))
148
+ decisionResponseError(
149
+ "Confidence must be finite and between zero and one"
150
+ );
151
+ if (question.type === "boolean") {
152
+ if (!probability(answer.probability))
153
+ decisionResponseError(
154
+ "Boolean probability must be between zero and one"
155
+ );
156
+ continue;
157
+ }
158
+ const keys = question.type === "choice" ? Object.keys(question.criteria) : question.criteria.map((_, index) => String(index));
159
+ if (question.type === "choice" && (typeof answer.choice !== "string" || !keys.includes(answer.choice)))
160
+ decisionResponseError("Decision selected an unknown choice");
161
+ if (question.type === "score" && (typeof answer.score !== "number" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > keys.length - 1))
162
+ decisionResponseError("Decision score is outside its ordered levels");
163
+ if (answer.probabilities === void 0) continue;
164
+ const distribution2 = answer.probabilities;
165
+ if (!isDecisionRecord(distribution2) || !exactKeys(distribution2, keys) || !Object.values(distribution2).every(probability))
166
+ decisionResponseError(
167
+ "Decision distribution must contain every option exactly once with finite probabilities"
168
+ );
169
+ const values = Object.values(distribution2);
170
+ if (Math.abs(values.reduce((a, b) => a + b, 0) - 1) > epsilon + keys.length * pError)
171
+ decisionResponseError("Decision probabilities do not sum to one");
172
+ if (question.type === "choice") {
173
+ if (values.some(
174
+ (v) => v > distribution2[answer.choice] + epsilon + 2 * pError
175
+ ))
176
+ decisionResponseError(
177
+ "Decision choice is not a highest-probability option"
178
+ );
179
+ } else {
180
+ const mean = keys.reduce(
181
+ (sum, key) => sum + Number(key) * distribution2[key],
182
+ 0
183
+ );
184
+ const tolerance = epsilon + sError + pError * keys.reduce((sum, key) => sum + Number(key), 0);
185
+ if (Math.abs(answer.score - mean) > tolerance)
186
+ decisionResponseError(
187
+ "Decision score does not match its probability-weighted levels"
188
+ );
189
+ }
190
+ }
191
+ }
192
+
193
+ // src/providers/decisions/http.ts
194
+ async function postDecision(url, headers, body, options, fetchFn, maxBytes) {
195
+ const encoded = JSON.stringify(body);
196
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes)
197
+ decisionInputError("Decision request exceeds the provider's byte limit");
198
+ options.signal?.throwIfAborted();
199
+ const controller = new AbortController();
200
+ const relay = () => controller.abort(options.signal?.reason);
201
+ options.signal?.addEventListener("abort", relay, { once: true });
202
+ const timer = setTimeout(
203
+ () => controller.abort(
204
+ new DOMException("Decision request timed out", "TimeoutError")
205
+ ),
206
+ options.timeoutMs ?? 6e4
207
+ );
208
+ let rejectAbort = () => {
209
+ };
210
+ const aborted = new Promise((_, reject) => {
211
+ rejectAbort = () => reject(controller.signal.reason);
212
+ controller.signal.addEventListener("abort", rejectAbort, { once: true });
213
+ });
214
+ const perform = async () => {
215
+ let response;
216
+ try {
217
+ response = await fetchFn(url, {
218
+ method: "POST",
219
+ headers,
220
+ body: encoded,
221
+ signal: controller.signal,
222
+ redirect: "error"
223
+ });
224
+ } catch {
225
+ controller.signal.throwIfAborted();
226
+ throw new DecisionAPIError(
227
+ "Decision request failed before receiving a response",
228
+ "network_error"
229
+ );
230
+ }
231
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("cf-ray") ?? void 0;
232
+ if (controller.signal.aborted) {
233
+ void response.body?.cancel().catch(() => {
234
+ });
235
+ controller.signal.throwIfAborted();
236
+ }
237
+ if (!response.ok) {
238
+ void response.body?.cancel().catch(() => {
239
+ });
240
+ throw new DecisionAPIError(
241
+ `Decision API returned HTTP ${response.status}`,
242
+ "http_error",
243
+ response.status,
244
+ requestId
245
+ );
246
+ }
247
+ if (!response.body)
248
+ decisionResponseError("Decision API returned an empty body");
249
+ const reader = response.body.getReader();
250
+ const cancel = () => {
251
+ void reader.cancel().catch(() => {
252
+ });
253
+ };
254
+ controller.signal.addEventListener("abort", cancel, { once: true });
255
+ const chunks = [];
256
+ let size = 0;
257
+ try {
258
+ while (true) {
259
+ controller.signal.throwIfAborted();
260
+ const { value: value2, done } = await reader.read();
261
+ if (done) break;
262
+ size += value2.byteLength;
263
+ if (size > 8 * 1024 * 1024) {
264
+ cancel();
265
+ decisionResponseError("Decision API response exceeds 8 MiB");
266
+ }
267
+ chunks.push(value2);
268
+ }
269
+ } catch (error) {
270
+ controller.signal.throwIfAborted();
271
+ if (error instanceof DecisionAPIError) throw error;
272
+ throw new DecisionAPIError(
273
+ "Decision response could not be read",
274
+ "network_error",
275
+ response.status,
276
+ requestId
277
+ );
278
+ } finally {
279
+ controller.signal.removeEventListener("abort", cancel);
280
+ reader.releaseLock();
281
+ }
282
+ controller.signal.throwIfAborted();
283
+ let value;
284
+ try {
285
+ value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
286
+ } catch {
287
+ decisionResponseError("Decision API did not return valid JSON");
288
+ }
289
+ return { value, status: response.status, requestId };
290
+ };
291
+ try {
292
+ return await Promise.race([perform(), aborted]);
293
+ } finally {
294
+ clearTimeout(timer);
295
+ options.signal?.removeEventListener("abort", relay);
296
+ controller.signal.removeEventListener("abort", rejectAbort);
297
+ }
298
+ }
299
+
300
+ // src/providers/decisions/images.ts
301
+ function unsupportedDecision(message) {
302
+ throw new DecisionAPIError(message, "unsupported_option");
303
+ }
304
+ function prepareDecisionImages(images, provider) {
305
+ if (provider === "openrouter" && images.length)
306
+ unsupportedDecision(
307
+ "OpenRouter's native Decisions schema does not support image inputs"
308
+ );
309
+ if (provider === "cloudflare" && images.length > 4)
310
+ decisionInputError("Cloudflare Decisions supports at most four images");
311
+ if (provider === "openai" && images.length > 128)
312
+ decisionInputError("OpenAI Decisions supports at most 128 images");
313
+ let total = 0;
314
+ return images.map((image) => {
315
+ assertDecisionKeys(
316
+ image,
317
+ ["data", "mediaType", "detail"],
318
+ "Decision image"
319
+ );
320
+ if (image.detail !== void 0) {
321
+ if (provider !== "openai")
322
+ unsupportedDecision(
323
+ "Image detail is only supported by OpenAI Decisions"
324
+ );
325
+ if (!["auto", "low", "high", "original"].includes(image.detail))
326
+ decisionInputError("Invalid image detail");
327
+ }
328
+ const data = Buffer.from(image.data);
329
+ const signature = data.subarray(0, 12).toString("latin1");
330
+ const mediaType = signature.startsWith("\x89PNG\r\n\n") ? "image/png" : data[0] === 255 && data[1] === 216 && data[2] === 255 ? "image/jpeg" : signature.startsWith("RIFF") && signature.slice(8) === "WEBP" ? "image/webp" : /^GIF8[79]a/.test(signature) ? "image/gif" : void 0;
331
+ if (!mediaType || image.mediaType !== void 0 && image.mediaType !== mediaType)
332
+ decisionInputError(
333
+ "Decision image bytes must match a supported encoded image format"
334
+ );
335
+ if (provider === "typesafe" && !["image/png", "image/jpeg"].includes(mediaType))
336
+ unsupportedDecision("Jev images must be PNG or JPEG");
337
+ if (provider === "cloudflare" && mediaType === "image/gif")
338
+ unsupportedDecision("Cloudflare Decisions does not accept GIF images");
339
+ const limit = provider === "cloudflare" ? 4 : provider === "typesafe" ? 16 : 20;
340
+ if (data.length > limit * 1024 * 1024)
341
+ decisionInputError("Decision image exceeds this provider's file limit");
342
+ total += data.length;
343
+ if (provider === "cloudflare" && total > 8 * 1024 * 1024)
344
+ decisionInputError("Cloudflare Decisions images exceed 8 MiB total");
345
+ const base64 = data.toString("base64");
346
+ return {
347
+ url: `data:${mediaType};base64,${base64}`,
348
+ base64,
349
+ mediaType,
350
+ detail: image.detail
351
+ };
352
+ });
353
+ }
354
+
355
+ // src/providers/decisions/response.ts
356
+ function distribution(value, score) {
357
+ if (!Array.isArray(value))
358
+ decisionResponseError(
359
+ "OpenAI Decisions omitted its probability distribution"
360
+ );
361
+ const entries = [];
362
+ const seen = /* @__PURE__ */ new Set();
363
+ for (const item of value) {
364
+ if (!isDecisionRecord(item) || (score ? typeof item.value !== "number" || !Number.isInteger(item.value) || item.value < 0 : typeof item.value !== "string"))
365
+ decisionResponseError("Invalid decision probability option");
366
+ const key = String(item.value);
367
+ if (seen.has(key))
368
+ decisionResponseError("Duplicate option in decision probabilities");
369
+ seen.add(key);
370
+ entries.push([key, item.probability]);
371
+ }
372
+ return Object.fromEntries(entries);
373
+ }
374
+ function parseDecisionResponse(raw, provider, questions, status, requestId) {
375
+ if (!isDecisionRecord(raw))
376
+ decisionResponseError("Decision response must be an object");
377
+ let payload = raw;
378
+ if (provider === "cloudflare") {
379
+ if (raw.success === false)
380
+ throw new DecisionAPIError(
381
+ "Cloudflare reported an unsuccessful decision request",
382
+ "http_error",
383
+ status,
384
+ requestId
385
+ );
386
+ if (raw.success !== true || !isDecisionRecord(raw.result))
387
+ decisionResponseError(
388
+ "Cloudflare Decisions returned an invalid result envelope"
389
+ );
390
+ payload = raw.result;
391
+ }
392
+ if (typeof payload.model !== "string" || !payload.model)
393
+ decisionResponseError("Decision response omitted its model identity");
394
+ const entries = [];
395
+ if (provider === "openai") {
396
+ if (!Array.isArray(payload.answers))
397
+ decisionResponseError("OpenAI Decisions answers must be an array");
398
+ const seen = /* @__PURE__ */ new Set();
399
+ for (const answer of payload.answers) {
400
+ if (!isDecisionRecord(answer))
401
+ decisionResponseError("Invalid OpenAI decision answer");
402
+ if (answer.type === "refusal" && answer.name == null)
403
+ throw new DecisionAPIError(
404
+ "OpenAI refused an unnamed decision question",
405
+ "refusal",
406
+ status,
407
+ requestId
408
+ );
409
+ if (typeof answer.name !== "string" || seen.has(answer.name))
410
+ decisionResponseError(
411
+ "OpenAI decision answers have missing or duplicate question identities"
412
+ );
413
+ seen.add(answer.name);
414
+ entries.push([answer.name, answer]);
415
+ }
416
+ } else {
417
+ if (!isDecisionRecord(payload.answers))
418
+ decisionResponseError("Decisions answers must be a named map");
419
+ entries.push(...Object.entries(payload.answers));
420
+ }
421
+ const answers = Object.fromEntries(
422
+ entries.map(([id, value]) => {
423
+ if (!isDecisionRecord(value))
424
+ decisionResponseError("Invalid decision answer");
425
+ if (value.type === "refusal" && provider === "openai")
426
+ return [id, { type: "refusal" }];
427
+ const booleanType = provider === "openai" ? "predicate" : "noul";
428
+ if (value.type === booleanType)
429
+ return [
430
+ id,
431
+ {
432
+ type: "boolean",
433
+ probability: provider === "openai" ? value.probability : value.noul
434
+ }
435
+ ];
436
+ if (value.type !== "choice" && value.type !== "score")
437
+ decisionResponseError(
438
+ "Provider returned an unsupported decision answer type"
439
+ );
440
+ if (provider !== "openrouter" && value.probabilities == null)
441
+ decisionResponseError(
442
+ "Provider omitted a required decision distribution"
443
+ );
444
+ const probabilities = provider === "openai" ? distribution(value.probabilities, value.type === "score") : value.probabilities;
445
+ const fields = {
446
+ ...probabilities !== void 0 && {
447
+ probabilities
448
+ },
449
+ ...value.confidence != null && {
450
+ confidence: value.confidence
451
+ }
452
+ };
453
+ return [
454
+ id,
455
+ value.type === "choice" ? { type: "choice", choice: value.choice, ...fields } : { type: "score", score: value.score, ...fields }
456
+ ];
457
+ })
458
+ );
459
+ const result = {
460
+ answers,
461
+ response: {
462
+ provider,
463
+ modelId: payload.model,
464
+ status,
465
+ requestId: requestId ?? (typeof payload.id === "string" ? payload.id : void 0),
466
+ body: raw
467
+ }
468
+ };
469
+ const typesafe = provider === "typesafe" || provider === "openrouter" && (payload.model.startsWith("typesafe/") || payload.provider === "TypeSafe");
470
+ if (typesafe) result.rounding = { probabilityDecimals: 2, scoreDecimals: 2 };
471
+ if (payload.usage != null) {
472
+ if (!isDecisionRecord(payload.usage))
473
+ decisionResponseError("Invalid decision usage");
474
+ const usage = payload.usage;
475
+ const details = isDecisionRecord(usage.input_tokens_details) ? usage.input_tokens_details : {};
476
+ result.usage = {};
477
+ const mapped = {
478
+ inputTokens: usage.input_tokens,
479
+ outputTokens: usage.output_tokens,
480
+ totalTokens: usage.total_tokens,
481
+ cachedInputTokens: details.cached_tokens,
482
+ cacheWriteTokens: details.cache_write_tokens,
483
+ costUsd: usage.cost
484
+ };
485
+ for (const [key, value] of Object.entries(mapped)) {
486
+ if (value == null) continue;
487
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || key !== "costUsd" && !Number.isInteger(value))
488
+ decisionResponseError("Invalid decision usage count");
489
+ result.usage[key] = value;
490
+ }
491
+ }
492
+ validateDecisionResult(questions, result);
493
+ return result;
494
+ }
495
+
496
+ // src/providers/decisions/model.ts
497
+ function asText(value) {
498
+ return typeof value === "string" ? value : JSON.stringify(value);
499
+ }
500
+ function requestOptions(options, provider) {
501
+ for (const key of Object.keys(options.providerOptions ?? {})) {
502
+ if (key !== provider)
503
+ unsupportedDecision(
504
+ "Decision providerOptions must match the selected provider"
505
+ );
506
+ }
507
+ const opts = options.providerOptions?.[provider] ?? {};
508
+ if (!isDecisionRecord(opts))
509
+ decisionInputError("Decision provider options must be an object");
510
+ const allowed = provider === "openai" ? ["safetyIdentifier", "promptCacheKey"] : provider === "typesafe" ? ["longContext"] : provider === "openrouter" ? ["sessionId", "provider"] : [];
511
+ assertDecisionKeys(opts, allowed, "Decision provider options");
512
+ for (const [key, limit] of [
513
+ ["safetyIdentifier", 128],
514
+ ["promptCacheKey", 64],
515
+ ["sessionId", 256]
516
+ ]) {
517
+ if (opts[key] !== void 0 && (typeof opts[key] !== "string" || opts[key].length > limit))
518
+ decisionInputError("Invalid decision identifier or cache key");
519
+ }
520
+ if (opts.longContext !== void 0 && typeof opts.longContext !== "boolean")
521
+ decisionInputError("longContext must be boolean");
522
+ if (opts.provider !== void 0) {
523
+ if (!isDecisionRecord(opts.provider))
524
+ decisionInputError("OpenRouter routing preferences must be an object");
525
+ assertDecisionKeys(
526
+ opts.provider,
527
+ ["order", "allowFallbacks"],
528
+ "OpenRouter routing preferences"
529
+ );
530
+ if (opts.provider.allowFallbacks !== void 0 && typeof opts.provider.allowFallbacks !== "boolean")
531
+ decisionInputError("allowFallbacks must be boolean");
532
+ if (opts.provider.order !== void 0 && (!Array.isArray(opts.provider.order) || !opts.provider.order.every(
533
+ (value) => typeof value === "string" && value.length
534
+ )))
535
+ decisionInputError("Provider order must be an array of provider names");
536
+ }
537
+ return opts;
538
+ }
539
+ function nativeDecisionModel(modelId, config) {
540
+ if (typeof modelId !== "string" || !modelId.trim())
541
+ decisionInputError("Decision model ID is required");
542
+ const provider = config.provider;
543
+ return {
544
+ provider,
545
+ modelId,
546
+ async doDecide(options) {
547
+ validateDecisionInput(options);
548
+ options.signal?.throwIfAborted();
549
+ const opts = requestOptions(options, provider);
550
+ const questions = Object.entries(options.questions);
551
+ if (provider === "openai" && questions.length > 200)
552
+ decisionInputError("OpenAI Decisions supports at most 200 questions");
553
+ if (provider === "cloudflare" && (questions.length > 64 || questions.some(([id]) => !/^[A-Za-z0-9_.-]{1,100}$/.test(id))))
554
+ decisionInputError(
555
+ "Cloudflare supports 1\u201364 questions with IDs of 1\u2013100 letters, digits, underscores, dots, or hyphens"
556
+ );
557
+ for (const [, question] of questions) {
558
+ if (provider !== "openai" && question.type === "score" && question.criteria.length > 10)
559
+ decisionInputError(
560
+ "System One adapters support at most ten score levels"
561
+ );
562
+ if (provider === "cloudflare" && typeof question.instructions === "string" && !question.instructions.trim())
563
+ decisionInputError("Cloudflare requires nonempty instructions");
564
+ if (provider === "openrouter" && question.type === "boolean" && question.criteria !== void 0 && (question.criteria.true === void 0 || question.criteria.false === void 0))
565
+ decisionInputError(
566
+ "OpenRouter boolean criteria must describe both true and false when supplied"
567
+ );
568
+ }
569
+ const images = prepareDecisionImages(options.images ?? [], provider);
570
+ const headers = Object.fromEntries(
571
+ Object.entries(config.headers ?? {}).filter(
572
+ ([name]) => !["authorization", "content-type"].includes(name.toLowerCase())
573
+ )
574
+ );
575
+ headers["Content-Type"] = "application/json";
576
+ const key = config.apiKey ?? process.env[config.keyEnv];
577
+ if (!key?.trim())
578
+ decisionInputError(`Missing ${config.keyEnv} for Decisions`);
579
+ headers.Authorization = `Bearer ${key}`;
580
+ let body;
581
+ if (provider === "openai") {
582
+ body = {
583
+ model: modelId,
584
+ input: [
585
+ {
586
+ role: "user",
587
+ content: [
588
+ { type: "input_text", text: asText(options.state) },
589
+ ...images.map((image) => ({
590
+ type: "input_image",
591
+ image_url: image.url,
592
+ ...image.detail !== void 0 && { detail: image.detail }
593
+ }))
594
+ ]
595
+ }
596
+ ],
597
+ questions: questions.map(([name, question]) => {
598
+ const instructions = asText(question.instructions);
599
+ if (question.type === "boolean")
600
+ return {
601
+ type: "predicate",
602
+ name,
603
+ instructions: [
604
+ instructions,
605
+ ...Object.entries(question.criteria ?? {}).map(
606
+ ([key2, value]) => `Criteria for ${key2}:
607
+ ${asText(value)}`
608
+ )
609
+ ].join("\n\n")
610
+ };
611
+ if (question.type === "choice")
612
+ return {
613
+ type: "choice",
614
+ name,
615
+ instructions,
616
+ choices: Object.entries(question.criteria).map(
617
+ ([value, description]) => ({
618
+ value,
619
+ ...description !== null && {
620
+ description: asText(description)
621
+ }
622
+ })
623
+ )
624
+ };
625
+ return {
626
+ type: "score",
627
+ name,
628
+ instructions,
629
+ levels: question.criteria.map((description, index) => ({
630
+ label: String(index),
631
+ description: asText(description)
632
+ }))
633
+ };
634
+ }),
635
+ ...opts.safetyIdentifier !== void 0 && {
636
+ safety_identifier: opts.safetyIdentifier
637
+ },
638
+ ...opts.promptCacheKey !== void 0 && {
639
+ prompt_cache_key: opts.promptCacheKey
640
+ }
641
+ };
642
+ } else {
643
+ body = {
644
+ model: config.bodyModel ?? modelId,
645
+ state: options.state,
646
+ questions: Object.fromEntries(
647
+ questions.map(([id, question]) => [
648
+ id,
649
+ {
650
+ ...question,
651
+ type: question.type === "boolean" ? "noul" : question.type
652
+ }
653
+ ])
654
+ )
655
+ };
656
+ if (provider === "typesafe") {
657
+ if (opts.longContext)
658
+ headers["TypeSafe-Danger-Zone-Long-Context"] = "true";
659
+ if (images.length) {
660
+ const refs = images.map(() => crypto.randomUUID());
661
+ body.state = { state: options.state, images: refs };
662
+ body.assets = Object.fromEntries(
663
+ images.map((image, index) => [
664
+ refs[index],
665
+ {
666
+ type: "image",
667
+ data_base64: image.base64,
668
+ format: image.mediaType === "image/png" ? "png" : "jpeg"
669
+ }
670
+ ])
671
+ );
672
+ headers["TypeSafe-Danger-Zone-Images"] = "true";
673
+ }
674
+ } else if (provider === "cloudflare" && images.length) {
675
+ body.images = images.map((image) => ({
676
+ content_type: image.mediaType,
677
+ base64: image.base64
678
+ }));
679
+ } else if (provider === "openrouter") {
680
+ if (opts.sessionId !== void 0) body.session_id = opts.sessionId;
681
+ if (isDecisionRecord(opts.provider))
682
+ body.provider = {
683
+ ...opts.provider.order !== void 0 && {
684
+ order: opts.provider.order
685
+ },
686
+ ...opts.provider.allowFallbacks !== void 0 && {
687
+ allow_fallbacks: opts.provider.allowFallbacks
688
+ }
689
+ };
690
+ }
691
+ }
692
+ const base = config.baseUrl ?? config.defaultBaseUrl;
693
+ let url;
694
+ try {
695
+ url = new URL(`${base.replace(/\/+$/, "")}${config.path}`);
696
+ } catch {
697
+ decisionInputError("Invalid Decisions base URL");
698
+ }
699
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
700
+ decisionInputError(
701
+ "Decisions base URL must be an HTTP(S) API root without credentials, query, or fragment"
702
+ );
703
+ const response = await postDecision(
704
+ url.href,
705
+ headers,
706
+ body,
707
+ options,
708
+ config.fetch ?? globalThis.fetch,
709
+ (provider === "cloudflare" ? 13 : 32) * 1024 * 1024
710
+ );
711
+ return parseDecisionResponse(
712
+ response.value,
713
+ provider,
714
+ options.questions,
715
+ response.status,
716
+ response.requestId
717
+ );
718
+ }
719
+ };
720
+ }
721
+
722
+ // src/providers/typesafe/index.ts
723
+ function typesafeDecision(modelId = "jev-latest", options = {}) {
724
+ return nativeDecisionModel(modelId, {
725
+ ...options,
726
+ provider: "typesafe",
727
+ keyEnv: "TYPESAFE_AI_API_KEY",
728
+ defaultBaseUrl: "https://api.typesafe.ai/v1",
729
+ path: "/systemone"
730
+ });
731
+ }
732
+
733
+ exports.typesafeDecision = typesafeDecision;
734
+ //# sourceMappingURL=index.js.map
735
+ //# sourceMappingURL=index.js.map