@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
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
4
+
3
5
  // src/core/tool-output.ts
4
6
  var imageTypes = /* @__PURE__ */ new Set([
5
7
  "image/png",
@@ -103,6 +105,72 @@ function textToolOutput(value, fallback) {
103
105
  return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
104
106
  }
105
107
 
108
+ // src/core/provider-error.ts
109
+ function invalidProviderResponse(message) {
110
+ return Object.assign(new Error(message), {
111
+ name: "InvalidProviderResponseError",
112
+ status: 502,
113
+ code: "INVALID_PROVIDER_RESPONSE"
114
+ });
115
+ }
116
+ function providerErrorEvent(error, code) {
117
+ const details = error ?? {};
118
+ const event = {
119
+ type: "error",
120
+ message: error instanceof Error ? error.message : String(error),
121
+ code,
122
+ ...typeof details.status === "number" ? { status: details.status } : {},
123
+ ...typeof details.code === "string" ? { providerCode: details.code } : {},
124
+ ...typeof details.param === "string" ? { param: details.param } : {},
125
+ ...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
126
+ };
127
+ Object.defineProperty(event, "cause", { value: error, enumerable: false });
128
+ return event;
129
+ }
130
+ function errorFromEvent(event) {
131
+ if (event.cause instanceof Error) return event.cause;
132
+ return Object.assign(new Error(event.message), {
133
+ status: event.status,
134
+ code: event.providerCode ?? event.code,
135
+ param: event.param,
136
+ request_id: event.requestId
137
+ });
138
+ }
139
+ function throwIfAborted(signal) {
140
+ if (!signal?.aborted) return;
141
+ const error = new Error("The operation was aborted");
142
+ error.name = "AbortError";
143
+ throw error;
144
+ }
145
+
146
+ // src/core/tool-validation.ts
147
+ function parseToolArguments(value) {
148
+ let args = value;
149
+ if (typeof value === "string") {
150
+ try {
151
+ args = JSON.parse(value || "{}");
152
+ } catch {
153
+ throw invalidProviderResponse("Tool arguments are not valid JSON");
154
+ }
155
+ }
156
+ if (args === null || typeof args !== "object" || Array.isArray(args))
157
+ throw invalidProviderResponse("Tool arguments must be a JSON object");
158
+ return args;
159
+ }
160
+ function validateToolCalls(calls) {
161
+ const ids = /* @__PURE__ */ new Set();
162
+ for (const call of calls) {
163
+ if (!call || typeof call.id !== "string" || !call.id || typeof call.name !== "string" || !call.name || ids.has(call.id))
164
+ throw invalidProviderResponse(
165
+ "Tool calls have missing or duplicate identities"
166
+ );
167
+ ids.add(call.id);
168
+ if (typeof call.args === "string")
169
+ throw invalidProviderResponse("Parsed tool arguments must be an object");
170
+ parseToolArguments(call.args);
171
+ }
172
+ }
173
+
106
174
  // src/core/openai-parameters.ts
107
175
  function capabilities(model) {
108
176
  const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
@@ -113,6 +181,19 @@ function capabilities(model) {
113
181
  const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
114
182
  return { major, minor, reasoning, supportsNone };
115
183
  }
184
+ function filterOpenAIResponseInclude(model, include, options = {}) {
185
+ const c = capabilities(model);
186
+ let result = include;
187
+ if (options.parameterPolicy !== "passthrough" && c.reasoning && !(options.reasoningEffort === "none" && c.supportsNone) && include.includes("message.output_text.logprobs")) {
188
+ result = include.filter(
189
+ (value) => value !== "message.output_text.logprobs"
190
+ );
191
+ (options.onWarning ?? console.warn)(
192
+ `[llm-sdk] ${model}: omitted message.output_text.logprobs because reasoning is enabled.`
193
+ );
194
+ }
195
+ return result.length ? result : void 0;
196
+ }
116
197
  function requiresOpenAIResponses(model, baseUrl, options = {}) {
117
198
  if (options.parameterPolicy === "passthrough") return false;
118
199
  const c = capabilities(model);
@@ -260,44 +341,6 @@ function getResponseReplay(metadata) {
260
341
  return responseReplayMetadata(metadata)?.openai?.response?.output;
261
342
  }
262
343
 
263
- // src/core/provider-error.ts
264
- function invalidProviderResponse(message) {
265
- return Object.assign(new Error(message), {
266
- name: "InvalidProviderResponseError",
267
- status: 502,
268
- code: "INVALID_PROVIDER_RESPONSE"
269
- });
270
- }
271
- function providerErrorEvent(error, code) {
272
- const details = error ?? {};
273
- const event = {
274
- type: "error",
275
- message: error instanceof Error ? error.message : String(error),
276
- code,
277
- ...typeof details.status === "number" ? { status: details.status } : {},
278
- ...typeof details.code === "string" ? { providerCode: details.code } : {},
279
- ...typeof details.param === "string" ? { param: details.param } : {},
280
- ...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
281
- };
282
- Object.defineProperty(event, "cause", { value: error, enumerable: false });
283
- return event;
284
- }
285
- function errorFromEvent(event) {
286
- if (event.cause instanceof Error) return event.cause;
287
- return Object.assign(new Error(event.message), {
288
- status: event.status,
289
- code: event.providerCode ?? event.code,
290
- param: event.param,
291
- request_id: event.requestId
292
- });
293
- }
294
- function throwIfAborted(signal) {
295
- if (!signal?.aborted) return;
296
- const error = new Error("The operation was aborted");
297
- error.name = "AbortError";
298
- throw error;
299
- }
300
-
301
344
  // src/core/utils.ts
302
345
  function generateId(prefix = "id") {
303
346
  return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
@@ -305,9 +348,6 @@ function generateId(prefix = "id") {
305
348
  function generateMessageId() {
306
349
  return generateId("msg");
307
350
  }
308
- function generateToolCallId() {
309
- return generateId("call");
310
- }
311
351
 
312
352
  // src/adapters/base.ts
313
353
  function stringifyForDebug(value) {
@@ -429,6 +469,7 @@ function toOpenAIResponseFormat(rf) {
429
469
  };
430
470
  }
431
471
  function toOpenAIResponsesTextFormat(rf) {
472
+ if (rf?.type === "json_object") return { type: "json_object" };
432
473
  if (!rf || rf.type !== "json_schema") return void 0;
433
474
  return {
434
475
  type: "json_schema",
@@ -558,6 +599,8 @@ var OpenAIAdapter = class _OpenAIAdapter {
558
599
  apiKey: this.config.apiKey,
559
600
  baseURL: this.config.baseUrl,
560
601
  organization: this.config.organization,
602
+ project: this.config.project,
603
+ timeout: this.config.timeout,
561
604
  defaultHeaders: this.config.headers
562
605
  });
563
606
  }
@@ -622,11 +665,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
622
665
  }
623
666
  buildResponsesInput(request) {
624
667
  const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
625
- const input = [];
668
+ const input2 = [];
626
669
  for (const message of sourceMessages) {
627
670
  if (message.role === "system") {
628
671
  if (message.content && message.content !== request.systemPrompt) {
629
- input.push({
672
+ input2.push({
630
673
  type: "message",
631
674
  role: "developer",
632
675
  content: this.toResponsesContent(message.content, "developer")
@@ -637,12 +680,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
637
680
  if (message.role === "assistant") {
638
681
  const replay = getResponseReplay(message.providerMetadata);
639
682
  if (replay) {
640
- input.push(...replay);
683
+ input2.push(...replay);
641
684
  continue;
642
685
  }
643
686
  const content = this.toResponsesContent(message.content, "assistant");
644
687
  if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
645
- input.push({
688
+ input2.push({
646
689
  type: "message",
647
690
  role: "assistant",
648
691
  content
@@ -650,7 +693,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
650
693
  }
651
694
  const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
652
695
  for (const toolCall of toolCalls) {
653
- input.push({
696
+ input2.push({
654
697
  type: "function_call",
655
698
  call_id: toolCall.id,
656
699
  name: toolCall.function?.name,
@@ -660,7 +703,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
660
703
  continue;
661
704
  }
662
705
  if (message.role === "tool") {
663
- input.push({
706
+ input2.push({
664
707
  type: "function_call_output",
665
708
  call_id: message.tool_call_id,
666
709
  output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
@@ -668,13 +711,13 @@ var OpenAIAdapter = class _OpenAIAdapter {
668
711
  continue;
669
712
  }
670
713
  const role = message.role === "developer" ? "developer" : "user";
671
- input.push({
714
+ input2.push({
672
715
  type: "message",
673
716
  role,
674
717
  content: this.toResponsesContent(message.content, role)
675
718
  });
676
719
  }
677
- return input;
720
+ return input2;
678
721
  }
679
722
  /** Preserve Copilot screenshot attachments on every OpenAI transport. */
680
723
  formatRawMessages(messages, preserveProviderMetadata = false) {
@@ -839,6 +882,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
839
882
  request.config?.responseFormat
840
883
  );
841
884
  const modelIdForPayload = request.config?.model || this.model;
885
+ const include = filterOpenAIResponseInclude(
886
+ modelIdForPayload,
887
+ [
888
+ .../* @__PURE__ */ new Set([
889
+ ...options?.include ?? [],
890
+ ...options?.store === false ? ["reasoning.encrypted_content"] : []
891
+ ])
892
+ ],
893
+ this.parameterOptions(request)
894
+ );
842
895
  const payload = {
843
896
  model: modelIdForPayload,
844
897
  instructions: request.systemPrompt,
@@ -862,14 +915,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
862
915
  ...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
863
916
  ...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
864
917
  ...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
865
- ...options?.store === false || options?.include ? {
866
- include: [
867
- .../* @__PURE__ */ new Set([
868
- ...options?.include ?? [],
869
- ...options?.store === false ? ["reasoning.encrypted_content"] : []
870
- ])
871
- ]
872
- } : {},
918
+ ...include ? { include } : {},
873
919
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
874
920
  stream
875
921
  };
@@ -1240,15 +1286,20 @@ var OpenAIAdapter = class _OpenAIAdapter {
1240
1286
  }
1241
1287
  throwIfAborted(request.signal);
1242
1288
  if (!finishReason)
1243
- throw new Error("OpenAI stream ended without a finish reason");
1289
+ throw invalidProviderResponse(
1290
+ "OpenAI stream ended without a finish reason"
1291
+ );
1244
1292
  if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
1245
- throw new Error("OpenAI stream ended with incomplete tool calls");
1246
- }
1247
- for (const call of toolCalls.values()) {
1248
- if (!call.id || !call.name)
1249
- throw new Error("OpenAI stream contains an incomplete tool call");
1250
- JSON.parse(call.arguments || "{}");
1293
+ throw invalidProviderResponse(
1294
+ "OpenAI stream ended with incomplete tool calls"
1295
+ );
1251
1296
  }
1297
+ validateToolCalls(
1298
+ [...toolCalls.values()].map((call) => ({
1299
+ ...call,
1300
+ args: parseToolArguments(call.arguments)
1301
+ }))
1302
+ );
1252
1303
  for (const call of toolCalls.values()) {
1253
1304
  yield {
1254
1305
  type: "action:start",
@@ -1332,20 +1383,20 @@ var OpenAIAdapter = class _OpenAIAdapter {
1332
1383
  logProviderPayload("openai", "response payload", response, request.debug);
1333
1384
  const choice = response.choices?.[0];
1334
1385
  const message = choice?.message;
1386
+ const toolCalls = (message?.tool_calls ?? []).map((toolCall) => ({
1387
+ id: toolCall.id,
1388
+ name: toolCall.function?.name,
1389
+ args: parseToolArguments(toolCall.function?.arguments ?? "{}"),
1390
+ ...toolCall.extra_content ? { extra_content: toolCall.extra_content } : {}
1391
+ }));
1392
+ validateToolCalls(toolCalls);
1393
+ if (toolCalls.length && choice?.finish_reason && !["tool_calls", "function_call"].includes(choice.finish_reason))
1394
+ throw invalidProviderResponse(
1395
+ "OpenAI response ended with incomplete tool calls"
1396
+ );
1335
1397
  return {
1336
1398
  content: message?.content ?? "",
1337
- toolCalls: message?.tool_calls?.map((toolCall) => ({
1338
- id: toolCall.id ?? generateToolCallId(),
1339
- name: toolCall.function?.name ?? "",
1340
- args: (() => {
1341
- try {
1342
- return JSON.parse(toolCall.function?.arguments ?? "{}");
1343
- } catch {
1344
- return {};
1345
- }
1346
- })(),
1347
- ...toolCall.extra_content ? { extra_content: toolCall.extra_content } : {}
1348
- })) ?? [],
1399
+ toolCalls,
1349
1400
  usage: response.usage ? {
1350
1401
  promptTokens: response.usage.prompt_tokens,
1351
1402
  completionTokens: response.usage.completion_tokens,
@@ -1566,9 +1617,14 @@ function openai(modelId, options = {}) {
1566
1617
  (tc) => ({
1567
1618
  id: tc.id,
1568
1619
  name: tc.function.name,
1569
- args: JSON.parse(tc.function.arguments || "{}")
1620
+ args: parseToolArguments(tc.function.arguments)
1570
1621
  })
1571
1622
  );
1623
+ validateToolCalls(toolCalls);
1624
+ if (toolCalls.length && choice.finish_reason && !["tool_calls", "function_call"].includes(choice.finish_reason))
1625
+ throw invalidProviderResponse(
1626
+ "OpenAI response ended with incomplete tool calls"
1627
+ );
1572
1628
  return {
1573
1629
  text: message.content ?? "",
1574
1630
  toolCalls,
@@ -1687,18 +1743,25 @@ function openai(modelId, options = {}) {
1687
1743
  }
1688
1744
  throwIfAborted(params.signal);
1689
1745
  if (!finishReason)
1690
- throw new Error("OpenAI stream ended without a finish reason");
1746
+ throw invalidProviderResponse(
1747
+ "OpenAI stream ended without a finish reason"
1748
+ );
1691
1749
  const calls = [...toolCalls.values()].map((call) => {
1692
1750
  if (!call.id || !call.name)
1693
- throw new Error("OpenAI stream contains an incomplete tool call");
1751
+ throw invalidProviderResponse(
1752
+ "OpenAI stream contains an incomplete tool call"
1753
+ );
1694
1754
  return {
1695
1755
  id: call.id,
1696
1756
  name: call.name,
1697
- args: JSON.parse(call.arguments || "{}")
1757
+ args: parseToolArguments(call.arguments)
1698
1758
  };
1699
1759
  });
1700
1760
  if (calls.length && finishReason !== "tool-calls")
1701
- throw new Error("OpenAI stream ended with incomplete tool calls");
1761
+ throw invalidProviderResponse(
1762
+ "OpenAI stream ended with incomplete tool calls"
1763
+ );
1764
+ validateToolCalls(calls);
1702
1765
  for (const toolCall of calls) yield { type: "tool-call", toolCall };
1703
1766
  yield { type: "finish", finishReason, usage };
1704
1767
  }
@@ -1783,6 +1846,739 @@ function formatMessagesForOpenAI2(messages, responses = false) {
1783
1846
  });
1784
1847
  }
1785
1848
 
1849
+ // src/core/decisions.ts
1850
+ var decisionErrorMarker = /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError");
1851
+ var _a, _b;
1852
+ var DecisionAPIError = class extends (_b = Error, _a = decisionErrorMarker, _b) {
1853
+ constructor(message, code, status, requestId) {
1854
+ super(message);
1855
+ this.code = code;
1856
+ this.status = status;
1857
+ this.requestId = requestId;
1858
+ this.name = "DecisionAPIError";
1859
+ this[_a] = true;
1860
+ }
1861
+ /** Works across CJS/ESM and separately bundled provider entry points. */
1862
+ static isInstance(error) {
1863
+ return typeof error === "object" && error !== null && /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError") in error;
1864
+ }
1865
+ /** Advisory only: this SDK does not retry Decisions automatically. */
1866
+ get retryable() {
1867
+ return this.code === "network_error" || this.code === "http_error" && (this.status === 408 || this.status === 429 || this.status != null && this.status >= 500);
1868
+ }
1869
+ };
1870
+
1871
+ // src/core/decision-validation.ts
1872
+ function decisionInputError(message) {
1873
+ throw new DecisionAPIError(message, "invalid_input");
1874
+ }
1875
+ function decisionResponseError(message) {
1876
+ throw new DecisionAPIError(message, "invalid_response");
1877
+ }
1878
+ function isDecisionRecord(value) {
1879
+ if (value === null || typeof value !== "object" || Array.isArray(value))
1880
+ return false;
1881
+ const proto = Object.getPrototypeOf(value);
1882
+ return proto === Object.prototype || proto === null;
1883
+ }
1884
+ function json(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
1885
+ if (depth > 64) return false;
1886
+ if (value === null || typeof value === "string" || typeof value === "boolean")
1887
+ return true;
1888
+ if (typeof value === "number") return Number.isFinite(value);
1889
+ if (typeof value !== "object" || !Array.isArray(value) && !isDecisionRecord(value) || seen.has(value))
1890
+ return false;
1891
+ seen.add(value);
1892
+ const valid = Object.getOwnPropertySymbols(value).length === 0 && (Array.isArray(value) ? Array.from(value) : Object.values(value)).every(
1893
+ (item) => json(item, seen, depth + 1)
1894
+ );
1895
+ seen.delete(value);
1896
+ return valid;
1897
+ }
1898
+ function input(value) {
1899
+ return (typeof value === "string" || Array.isArray(value) || isDecisionRecord(value)) && json(value);
1900
+ }
1901
+ function assertDecisionKeys(value, allowed, label) {
1902
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
1903
+ decisionInputError(`${label} contains an unsupported field`);
1904
+ }
1905
+ function validateDecisionInput(options) {
1906
+ if (!isDecisionRecord(options))
1907
+ decisionInputError("Decisions options must be an object");
1908
+ assertDecisionKeys(
1909
+ options,
1910
+ ["state", "questions", "images", "signal", "timeoutMs", "providerOptions"],
1911
+ "Decisions options"
1912
+ );
1913
+ if (!input(options.state))
1914
+ decisionInputError(
1915
+ "state must be finite, acyclic JSON text, object, or array (maximum depth 64)"
1916
+ );
1917
+ if (!isDecisionRecord(options.questions) || Object.keys(options.questions).length === 0)
1918
+ decisionInputError("questions must be a nonempty map");
1919
+ for (const [id, question] of Object.entries(options.questions)) {
1920
+ if (!id.length || !isDecisionRecord(question) || !input(question.instructions))
1921
+ decisionInputError(
1922
+ "Every question needs a nonempty ID and JSON-compatible instructions"
1923
+ );
1924
+ assertDecisionKeys(
1925
+ question,
1926
+ ["type", "instructions", "criteria"],
1927
+ "Question"
1928
+ );
1929
+ if (question.type === "boolean") {
1930
+ if (question.criteria !== void 0 && (!isDecisionRecord(question.criteria) || Object.entries(question.criteria).some(
1931
+ ([key, value]) => !["true", "false"].includes(key) || !input(value)
1932
+ )))
1933
+ decisionInputError("Boolean criteria may describe only true and false");
1934
+ } else if (question.type === "choice") {
1935
+ if (!isDecisionRecord(question.criteria) || Object.keys(question.criteria).length < 2 || Object.keys(question.criteria).length > 255 || Object.entries(question.criteria).some(
1936
+ ([key, value]) => !key.length || value !== null && !input(value)
1937
+ ))
1938
+ decisionInputError(
1939
+ "Choice criteria require 2\u2013255 named options with JSON descriptions or null"
1940
+ );
1941
+ } else if (question.type === "score") {
1942
+ if (!Array.isArray(question.criteria) || question.criteria.length < 2 || !Array.from(question.criteria).every(input))
1943
+ decisionInputError(
1944
+ "Score criteria require at least two ordered JSON descriptions"
1945
+ );
1946
+ } else
1947
+ decisionInputError("Question type must be boolean, choice, or score");
1948
+ }
1949
+ if (options.timeoutMs !== void 0 && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 2147483647))
1950
+ decisionInputError("timeoutMs must be a positive 32-bit integer");
1951
+ if (options.providerOptions !== void 0 && (!isDecisionRecord(options.providerOptions) || !json(options.providerOptions)))
1952
+ decisionInputError("providerOptions must be a JSON object");
1953
+ if (options.images !== void 0 && (!Array.isArray(options.images) || !Array.from(options.images).every(
1954
+ (image) => isDecisionRecord(image) && image.data instanceof Uint8Array
1955
+ )))
1956
+ decisionInputError(
1957
+ "Decision images must contain encoded bytes; hosted image URLs are unsupported"
1958
+ );
1959
+ }
1960
+ function probability(value) {
1961
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
1962
+ }
1963
+ function exactKeys(value, keys) {
1964
+ return Object.keys(value).length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
1965
+ }
1966
+ function validateDecisionResult(questions, result) {
1967
+ if (!isDecisionRecord(result) || !isDecisionRecord(result.answers) || !exactKeys(result.answers, Object.keys(questions)))
1968
+ decisionResponseError(
1969
+ "Decisions must return exactly one answer per question"
1970
+ );
1971
+ const rounding = result.rounding;
1972
+ let pError = 0, sError = 0;
1973
+ if (rounding !== void 0) {
1974
+ if (!isDecisionRecord(rounding) || ![rounding.probabilityDecimals, rounding.scoreDecimals].every(
1975
+ (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 15
1976
+ ))
1977
+ decisionResponseError("Invalid decision rounding precision");
1978
+ pError = 0.5 * 10 ** -rounding.probabilityDecimals;
1979
+ sError = 0.5 * 10 ** -rounding.scoreDecimals;
1980
+ }
1981
+ const epsilon = 1e-6;
1982
+ for (const [id, question] of Object.entries(questions)) {
1983
+ const answer = result.answers[id];
1984
+ if (!isDecisionRecord(answer))
1985
+ decisionResponseError("Invalid decision answer");
1986
+ if (answer.type === "refusal") continue;
1987
+ if (answer.type !== question.type)
1988
+ decisionResponseError("Decision answer type does not match its question");
1989
+ if (answer.confidence !== void 0 && !probability(answer.confidence))
1990
+ decisionResponseError(
1991
+ "Confidence must be finite and between zero and one"
1992
+ );
1993
+ if (question.type === "boolean") {
1994
+ if (!probability(answer.probability))
1995
+ decisionResponseError(
1996
+ "Boolean probability must be between zero and one"
1997
+ );
1998
+ continue;
1999
+ }
2000
+ const keys = question.type === "choice" ? Object.keys(question.criteria) : question.criteria.map((_, index) => String(index));
2001
+ if (question.type === "choice" && (typeof answer.choice !== "string" || !keys.includes(answer.choice)))
2002
+ decisionResponseError("Decision selected an unknown choice");
2003
+ if (question.type === "score" && (typeof answer.score !== "number" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > keys.length - 1))
2004
+ decisionResponseError("Decision score is outside its ordered levels");
2005
+ if (answer.probabilities === void 0) continue;
2006
+ const distribution2 = answer.probabilities;
2007
+ if (!isDecisionRecord(distribution2) || !exactKeys(distribution2, keys) || !Object.values(distribution2).every(probability))
2008
+ decisionResponseError(
2009
+ "Decision distribution must contain every option exactly once with finite probabilities"
2010
+ );
2011
+ const values = Object.values(distribution2);
2012
+ if (Math.abs(values.reduce((a, b) => a + b, 0) - 1) > epsilon + keys.length * pError)
2013
+ decisionResponseError("Decision probabilities do not sum to one");
2014
+ if (question.type === "choice") {
2015
+ if (values.some(
2016
+ (v) => v > distribution2[answer.choice] + epsilon + 2 * pError
2017
+ ))
2018
+ decisionResponseError(
2019
+ "Decision choice is not a highest-probability option"
2020
+ );
2021
+ } else {
2022
+ const mean = keys.reduce(
2023
+ (sum, key) => sum + Number(key) * distribution2[key],
2024
+ 0
2025
+ );
2026
+ const tolerance = epsilon + sError + pError * keys.reduce((sum, key) => sum + Number(key), 0);
2027
+ if (Math.abs(answer.score - mean) > tolerance)
2028
+ decisionResponseError(
2029
+ "Decision score does not match its probability-weighted levels"
2030
+ );
2031
+ }
2032
+ }
2033
+ }
2034
+
2035
+ // src/providers/decisions/http.ts
2036
+ async function postDecision(url, headers, body, options, fetchFn, maxBytes) {
2037
+ const encoded = JSON.stringify(body);
2038
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes)
2039
+ decisionInputError("Decision request exceeds the provider's byte limit");
2040
+ options.signal?.throwIfAborted();
2041
+ const controller = new AbortController();
2042
+ const relay = () => controller.abort(options.signal?.reason);
2043
+ options.signal?.addEventListener("abort", relay, { once: true });
2044
+ const timer = setTimeout(
2045
+ () => controller.abort(
2046
+ new DOMException("Decision request timed out", "TimeoutError")
2047
+ ),
2048
+ options.timeoutMs ?? 6e4
2049
+ );
2050
+ let rejectAbort = () => {
2051
+ };
2052
+ const aborted = new Promise((_, reject) => {
2053
+ rejectAbort = () => reject(controller.signal.reason);
2054
+ controller.signal.addEventListener("abort", rejectAbort, { once: true });
2055
+ });
2056
+ const perform = async () => {
2057
+ let response;
2058
+ try {
2059
+ response = await fetchFn(url, {
2060
+ method: "POST",
2061
+ headers,
2062
+ body: encoded,
2063
+ signal: controller.signal,
2064
+ redirect: "error"
2065
+ });
2066
+ } catch {
2067
+ controller.signal.throwIfAborted();
2068
+ throw new DecisionAPIError(
2069
+ "Decision request failed before receiving a response",
2070
+ "network_error"
2071
+ );
2072
+ }
2073
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("cf-ray") ?? void 0;
2074
+ if (controller.signal.aborted) {
2075
+ void response.body?.cancel().catch(() => {
2076
+ });
2077
+ controller.signal.throwIfAborted();
2078
+ }
2079
+ if (!response.ok) {
2080
+ void response.body?.cancel().catch(() => {
2081
+ });
2082
+ throw new DecisionAPIError(
2083
+ `Decision API returned HTTP ${response.status}`,
2084
+ "http_error",
2085
+ response.status,
2086
+ requestId
2087
+ );
2088
+ }
2089
+ if (!response.body)
2090
+ decisionResponseError("Decision API returned an empty body");
2091
+ const reader = response.body.getReader();
2092
+ const cancel = () => {
2093
+ void reader.cancel().catch(() => {
2094
+ });
2095
+ };
2096
+ controller.signal.addEventListener("abort", cancel, { once: true });
2097
+ const chunks = [];
2098
+ let size = 0;
2099
+ try {
2100
+ while (true) {
2101
+ controller.signal.throwIfAborted();
2102
+ const { value: value2, done } = await reader.read();
2103
+ if (done) break;
2104
+ size += value2.byteLength;
2105
+ if (size > 8 * 1024 * 1024) {
2106
+ cancel();
2107
+ decisionResponseError("Decision API response exceeds 8 MiB");
2108
+ }
2109
+ chunks.push(value2);
2110
+ }
2111
+ } catch (error) {
2112
+ controller.signal.throwIfAborted();
2113
+ if (error instanceof DecisionAPIError) throw error;
2114
+ throw new DecisionAPIError(
2115
+ "Decision response could not be read",
2116
+ "network_error",
2117
+ response.status,
2118
+ requestId
2119
+ );
2120
+ } finally {
2121
+ controller.signal.removeEventListener("abort", cancel);
2122
+ reader.releaseLock();
2123
+ }
2124
+ controller.signal.throwIfAborted();
2125
+ let value;
2126
+ try {
2127
+ value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
2128
+ } catch {
2129
+ decisionResponseError("Decision API did not return valid JSON");
2130
+ }
2131
+ return { value, status: response.status, requestId };
2132
+ };
2133
+ try {
2134
+ return await Promise.race([perform(), aborted]);
2135
+ } finally {
2136
+ clearTimeout(timer);
2137
+ options.signal?.removeEventListener("abort", relay);
2138
+ controller.signal.removeEventListener("abort", rejectAbort);
2139
+ }
2140
+ }
2141
+
2142
+ // src/providers/decisions/images.ts
2143
+ function unsupportedDecision(message) {
2144
+ throw new DecisionAPIError(message, "unsupported_option");
2145
+ }
2146
+ function prepareDecisionImages(images, provider) {
2147
+ if (provider === "openrouter" && images.length)
2148
+ unsupportedDecision(
2149
+ "OpenRouter's native Decisions schema does not support image inputs"
2150
+ );
2151
+ if (provider === "cloudflare" && images.length > 4)
2152
+ decisionInputError("Cloudflare Decisions supports at most four images");
2153
+ if (provider === "openai" && images.length > 128)
2154
+ decisionInputError("OpenAI Decisions supports at most 128 images");
2155
+ let total = 0;
2156
+ return images.map((image) => {
2157
+ assertDecisionKeys(
2158
+ image,
2159
+ ["data", "mediaType", "detail"],
2160
+ "Decision image"
2161
+ );
2162
+ if (image.detail !== void 0) {
2163
+ if (provider !== "openai")
2164
+ unsupportedDecision(
2165
+ "Image detail is only supported by OpenAI Decisions"
2166
+ );
2167
+ if (!["auto", "low", "high", "original"].includes(image.detail))
2168
+ decisionInputError("Invalid image detail");
2169
+ }
2170
+ const data = Buffer.from(image.data);
2171
+ const signature = data.subarray(0, 12).toString("latin1");
2172
+ 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;
2173
+ if (!mediaType || image.mediaType !== void 0 && image.mediaType !== mediaType)
2174
+ decisionInputError(
2175
+ "Decision image bytes must match a supported encoded image format"
2176
+ );
2177
+ if (provider === "typesafe" && !["image/png", "image/jpeg"].includes(mediaType))
2178
+ unsupportedDecision("Jev images must be PNG or JPEG");
2179
+ if (provider === "cloudflare" && mediaType === "image/gif")
2180
+ unsupportedDecision("Cloudflare Decisions does not accept GIF images");
2181
+ const limit = provider === "cloudflare" ? 4 : provider === "typesafe" ? 16 : 20;
2182
+ if (data.length > limit * 1024 * 1024)
2183
+ decisionInputError("Decision image exceeds this provider's file limit");
2184
+ total += data.length;
2185
+ if (provider === "cloudflare" && total > 8 * 1024 * 1024)
2186
+ decisionInputError("Cloudflare Decisions images exceed 8 MiB total");
2187
+ const base64 = data.toString("base64");
2188
+ return {
2189
+ url: `data:${mediaType};base64,${base64}`,
2190
+ base64,
2191
+ mediaType,
2192
+ detail: image.detail
2193
+ };
2194
+ });
2195
+ }
2196
+
2197
+ // src/providers/decisions/response.ts
2198
+ function distribution(value, score) {
2199
+ if (!Array.isArray(value))
2200
+ decisionResponseError(
2201
+ "OpenAI Decisions omitted its probability distribution"
2202
+ );
2203
+ const entries = [];
2204
+ const seen = /* @__PURE__ */ new Set();
2205
+ for (const item of value) {
2206
+ if (!isDecisionRecord(item) || (score ? typeof item.value !== "number" || !Number.isInteger(item.value) || item.value < 0 : typeof item.value !== "string"))
2207
+ decisionResponseError("Invalid decision probability option");
2208
+ const key = String(item.value);
2209
+ if (seen.has(key))
2210
+ decisionResponseError("Duplicate option in decision probabilities");
2211
+ seen.add(key);
2212
+ entries.push([key, item.probability]);
2213
+ }
2214
+ return Object.fromEntries(entries);
2215
+ }
2216
+ function parseDecisionResponse(raw, provider, questions, status, requestId) {
2217
+ if (!isDecisionRecord(raw))
2218
+ decisionResponseError("Decision response must be an object");
2219
+ let payload = raw;
2220
+ if (provider === "cloudflare") {
2221
+ if (raw.success === false)
2222
+ throw new DecisionAPIError(
2223
+ "Cloudflare reported an unsuccessful decision request",
2224
+ "http_error",
2225
+ status,
2226
+ requestId
2227
+ );
2228
+ if (raw.success !== true || !isDecisionRecord(raw.result))
2229
+ decisionResponseError(
2230
+ "Cloudflare Decisions returned an invalid result envelope"
2231
+ );
2232
+ payload = raw.result;
2233
+ }
2234
+ if (typeof payload.model !== "string" || !payload.model)
2235
+ decisionResponseError("Decision response omitted its model identity");
2236
+ const entries = [];
2237
+ if (provider === "openai") {
2238
+ if (!Array.isArray(payload.answers))
2239
+ decisionResponseError("OpenAI Decisions answers must be an array");
2240
+ const seen = /* @__PURE__ */ new Set();
2241
+ for (const answer of payload.answers) {
2242
+ if (!isDecisionRecord(answer))
2243
+ decisionResponseError("Invalid OpenAI decision answer");
2244
+ if (answer.type === "refusal" && answer.name == null)
2245
+ throw new DecisionAPIError(
2246
+ "OpenAI refused an unnamed decision question",
2247
+ "refusal",
2248
+ status,
2249
+ requestId
2250
+ );
2251
+ if (typeof answer.name !== "string" || seen.has(answer.name))
2252
+ decisionResponseError(
2253
+ "OpenAI decision answers have missing or duplicate question identities"
2254
+ );
2255
+ seen.add(answer.name);
2256
+ entries.push([answer.name, answer]);
2257
+ }
2258
+ } else {
2259
+ if (!isDecisionRecord(payload.answers))
2260
+ decisionResponseError("Decisions answers must be a named map");
2261
+ entries.push(...Object.entries(payload.answers));
2262
+ }
2263
+ const answers = Object.fromEntries(
2264
+ entries.map(([id, value]) => {
2265
+ if (!isDecisionRecord(value))
2266
+ decisionResponseError("Invalid decision answer");
2267
+ if (value.type === "refusal" && provider === "openai")
2268
+ return [id, { type: "refusal" }];
2269
+ const booleanType = provider === "openai" ? "predicate" : "noul";
2270
+ if (value.type === booleanType)
2271
+ return [
2272
+ id,
2273
+ {
2274
+ type: "boolean",
2275
+ probability: provider === "openai" ? value.probability : value.noul
2276
+ }
2277
+ ];
2278
+ if (value.type !== "choice" && value.type !== "score")
2279
+ decisionResponseError(
2280
+ "Provider returned an unsupported decision answer type"
2281
+ );
2282
+ if (provider !== "openrouter" && value.probabilities == null)
2283
+ decisionResponseError(
2284
+ "Provider omitted a required decision distribution"
2285
+ );
2286
+ const probabilities = provider === "openai" ? distribution(value.probabilities, value.type === "score") : value.probabilities;
2287
+ const fields = {
2288
+ ...probabilities !== void 0 && {
2289
+ probabilities
2290
+ },
2291
+ ...value.confidence != null && {
2292
+ confidence: value.confidence
2293
+ }
2294
+ };
2295
+ return [
2296
+ id,
2297
+ value.type === "choice" ? { type: "choice", choice: value.choice, ...fields } : { type: "score", score: value.score, ...fields }
2298
+ ];
2299
+ })
2300
+ );
2301
+ const result = {
2302
+ answers,
2303
+ response: {
2304
+ provider,
2305
+ modelId: payload.model,
2306
+ status,
2307
+ requestId: requestId ?? (typeof payload.id === "string" ? payload.id : void 0),
2308
+ body: raw
2309
+ }
2310
+ };
2311
+ const typesafe = provider === "typesafe" || provider === "openrouter" && (payload.model.startsWith("typesafe/") || payload.provider === "TypeSafe");
2312
+ if (typesafe) result.rounding = { probabilityDecimals: 2, scoreDecimals: 2 };
2313
+ if (payload.usage != null) {
2314
+ if (!isDecisionRecord(payload.usage))
2315
+ decisionResponseError("Invalid decision usage");
2316
+ const usage = payload.usage;
2317
+ const details = isDecisionRecord(usage.input_tokens_details) ? usage.input_tokens_details : {};
2318
+ result.usage = {};
2319
+ const mapped = {
2320
+ inputTokens: usage.input_tokens,
2321
+ outputTokens: usage.output_tokens,
2322
+ totalTokens: usage.total_tokens,
2323
+ cachedInputTokens: details.cached_tokens,
2324
+ cacheWriteTokens: details.cache_write_tokens,
2325
+ costUsd: usage.cost
2326
+ };
2327
+ for (const [key, value] of Object.entries(mapped)) {
2328
+ if (value == null) continue;
2329
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || key !== "costUsd" && !Number.isInteger(value))
2330
+ decisionResponseError("Invalid decision usage count");
2331
+ result.usage[key] = value;
2332
+ }
2333
+ }
2334
+ validateDecisionResult(questions, result);
2335
+ return result;
2336
+ }
2337
+
2338
+ // src/providers/decisions/model.ts
2339
+ function asText(value) {
2340
+ return typeof value === "string" ? value : JSON.stringify(value);
2341
+ }
2342
+ function requestOptions(options, provider) {
2343
+ for (const key of Object.keys(options.providerOptions ?? {})) {
2344
+ if (key !== provider)
2345
+ unsupportedDecision(
2346
+ "Decision providerOptions must match the selected provider"
2347
+ );
2348
+ }
2349
+ const opts = options.providerOptions?.[provider] ?? {};
2350
+ if (!isDecisionRecord(opts))
2351
+ decisionInputError("Decision provider options must be an object");
2352
+ const allowed = provider === "openai" ? ["safetyIdentifier", "promptCacheKey"] : provider === "typesafe" ? ["longContext"] : provider === "openrouter" ? ["sessionId", "provider"] : [];
2353
+ assertDecisionKeys(opts, allowed, "Decision provider options");
2354
+ for (const [key, limit] of [
2355
+ ["safetyIdentifier", 128],
2356
+ ["promptCacheKey", 64],
2357
+ ["sessionId", 256]
2358
+ ]) {
2359
+ if (opts[key] !== void 0 && (typeof opts[key] !== "string" || opts[key].length > limit))
2360
+ decisionInputError("Invalid decision identifier or cache key");
2361
+ }
2362
+ if (opts.longContext !== void 0 && typeof opts.longContext !== "boolean")
2363
+ decisionInputError("longContext must be boolean");
2364
+ if (opts.provider !== void 0) {
2365
+ if (!isDecisionRecord(opts.provider))
2366
+ decisionInputError("OpenRouter routing preferences must be an object");
2367
+ assertDecisionKeys(
2368
+ opts.provider,
2369
+ ["order", "allowFallbacks"],
2370
+ "OpenRouter routing preferences"
2371
+ );
2372
+ if (opts.provider.allowFallbacks !== void 0 && typeof opts.provider.allowFallbacks !== "boolean")
2373
+ decisionInputError("allowFallbacks must be boolean");
2374
+ if (opts.provider.order !== void 0 && (!Array.isArray(opts.provider.order) || !opts.provider.order.every(
2375
+ (value) => typeof value === "string" && value.length
2376
+ )))
2377
+ decisionInputError("Provider order must be an array of provider names");
2378
+ }
2379
+ return opts;
2380
+ }
2381
+ function nativeDecisionModel(modelId, config) {
2382
+ if (typeof modelId !== "string" || !modelId.trim())
2383
+ decisionInputError("Decision model ID is required");
2384
+ const provider = config.provider;
2385
+ return {
2386
+ provider,
2387
+ modelId,
2388
+ async doDecide(options) {
2389
+ validateDecisionInput(options);
2390
+ options.signal?.throwIfAborted();
2391
+ const opts = requestOptions(options, provider);
2392
+ const questions = Object.entries(options.questions);
2393
+ if (provider === "openai" && questions.length > 200)
2394
+ decisionInputError("OpenAI Decisions supports at most 200 questions");
2395
+ if (provider === "cloudflare" && (questions.length > 64 || questions.some(([id]) => !/^[A-Za-z0-9_.-]{1,100}$/.test(id))))
2396
+ decisionInputError(
2397
+ "Cloudflare supports 1\u201364 questions with IDs of 1\u2013100 letters, digits, underscores, dots, or hyphens"
2398
+ );
2399
+ for (const [, question] of questions) {
2400
+ if (provider !== "openai" && question.type === "score" && question.criteria.length > 10)
2401
+ decisionInputError(
2402
+ "System One adapters support at most ten score levels"
2403
+ );
2404
+ if (provider === "cloudflare" && typeof question.instructions === "string" && !question.instructions.trim())
2405
+ decisionInputError("Cloudflare requires nonempty instructions");
2406
+ if (provider === "openrouter" && question.type === "boolean" && question.criteria !== void 0 && (question.criteria.true === void 0 || question.criteria.false === void 0))
2407
+ decisionInputError(
2408
+ "OpenRouter boolean criteria must describe both true and false when supplied"
2409
+ );
2410
+ }
2411
+ const images = prepareDecisionImages(options.images ?? [], provider);
2412
+ const headers = Object.fromEntries(
2413
+ Object.entries(config.headers ?? {}).filter(
2414
+ ([name]) => !["authorization", "content-type"].includes(name.toLowerCase())
2415
+ )
2416
+ );
2417
+ headers["Content-Type"] = "application/json";
2418
+ const key = config.apiKey ?? process.env[config.keyEnv];
2419
+ if (!key?.trim())
2420
+ decisionInputError(`Missing ${config.keyEnv} for Decisions`);
2421
+ headers.Authorization = `Bearer ${key}`;
2422
+ let body;
2423
+ if (provider === "openai") {
2424
+ body = {
2425
+ model: modelId,
2426
+ input: [
2427
+ {
2428
+ role: "user",
2429
+ content: [
2430
+ { type: "input_text", text: asText(options.state) },
2431
+ ...images.map((image) => ({
2432
+ type: "input_image",
2433
+ image_url: image.url,
2434
+ ...image.detail !== void 0 && { detail: image.detail }
2435
+ }))
2436
+ ]
2437
+ }
2438
+ ],
2439
+ questions: questions.map(([name, question]) => {
2440
+ const instructions = asText(question.instructions);
2441
+ if (question.type === "boolean")
2442
+ return {
2443
+ type: "predicate",
2444
+ name,
2445
+ instructions: [
2446
+ instructions,
2447
+ ...Object.entries(question.criteria ?? {}).map(
2448
+ ([key2, value]) => `Criteria for ${key2}:
2449
+ ${asText(value)}`
2450
+ )
2451
+ ].join("\n\n")
2452
+ };
2453
+ if (question.type === "choice")
2454
+ return {
2455
+ type: "choice",
2456
+ name,
2457
+ instructions,
2458
+ choices: Object.entries(question.criteria).map(
2459
+ ([value, description]) => ({
2460
+ value,
2461
+ ...description !== null && {
2462
+ description: asText(description)
2463
+ }
2464
+ })
2465
+ )
2466
+ };
2467
+ return {
2468
+ type: "score",
2469
+ name,
2470
+ instructions,
2471
+ levels: question.criteria.map((description, index) => ({
2472
+ label: String(index),
2473
+ description: asText(description)
2474
+ }))
2475
+ };
2476
+ }),
2477
+ ...opts.safetyIdentifier !== void 0 && {
2478
+ safety_identifier: opts.safetyIdentifier
2479
+ },
2480
+ ...opts.promptCacheKey !== void 0 && {
2481
+ prompt_cache_key: opts.promptCacheKey
2482
+ }
2483
+ };
2484
+ } else {
2485
+ body = {
2486
+ model: config.bodyModel ?? modelId,
2487
+ state: options.state,
2488
+ questions: Object.fromEntries(
2489
+ questions.map(([id, question]) => [
2490
+ id,
2491
+ {
2492
+ ...question,
2493
+ type: question.type === "boolean" ? "noul" : question.type
2494
+ }
2495
+ ])
2496
+ )
2497
+ };
2498
+ if (provider === "typesafe") {
2499
+ if (opts.longContext)
2500
+ headers["TypeSafe-Danger-Zone-Long-Context"] = "true";
2501
+ if (images.length) {
2502
+ const refs = images.map(() => crypto.randomUUID());
2503
+ body.state = { state: options.state, images: refs };
2504
+ body.assets = Object.fromEntries(
2505
+ images.map((image, index) => [
2506
+ refs[index],
2507
+ {
2508
+ type: "image",
2509
+ data_base64: image.base64,
2510
+ format: image.mediaType === "image/png" ? "png" : "jpeg"
2511
+ }
2512
+ ])
2513
+ );
2514
+ headers["TypeSafe-Danger-Zone-Images"] = "true";
2515
+ }
2516
+ } else if (provider === "cloudflare" && images.length) {
2517
+ body.images = images.map((image) => ({
2518
+ content_type: image.mediaType,
2519
+ base64: image.base64
2520
+ }));
2521
+ } else if (provider === "openrouter") {
2522
+ if (opts.sessionId !== void 0) body.session_id = opts.sessionId;
2523
+ if (isDecisionRecord(opts.provider))
2524
+ body.provider = {
2525
+ ...opts.provider.order !== void 0 && {
2526
+ order: opts.provider.order
2527
+ },
2528
+ ...opts.provider.allowFallbacks !== void 0 && {
2529
+ allow_fallbacks: opts.provider.allowFallbacks
2530
+ }
2531
+ };
2532
+ }
2533
+ }
2534
+ const base = config.baseUrl ?? config.defaultBaseUrl;
2535
+ let url;
2536
+ try {
2537
+ url = new URL(`${base.replace(/\/+$/, "")}${config.path}`);
2538
+ } catch {
2539
+ decisionInputError("Invalid Decisions base URL");
2540
+ }
2541
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
2542
+ decisionInputError(
2543
+ "Decisions base URL must be an HTTP(S) API root without credentials, query, or fragment"
2544
+ );
2545
+ const response = await postDecision(
2546
+ url.href,
2547
+ headers,
2548
+ body,
2549
+ options,
2550
+ config.fetch ?? globalThis.fetch,
2551
+ (provider === "cloudflare" ? 13 : 32) * 1024 * 1024
2552
+ );
2553
+ return parseDecisionResponse(
2554
+ response.value,
2555
+ provider,
2556
+ options.questions,
2557
+ response.status,
2558
+ response.requestId
2559
+ );
2560
+ }
2561
+ };
2562
+ }
2563
+
2564
+ // src/providers/openai/decisions.ts
2565
+ function openaiDecision(modelId = "gpt-6-luna", options = {}) {
2566
+ return nativeDecisionModel(modelId, {
2567
+ ...options,
2568
+ headers: {
2569
+ ...options.headers,
2570
+ ...options.organization && {
2571
+ "OpenAI-Organization": options.organization
2572
+ },
2573
+ ...options.project && { "OpenAI-Project": options.project }
2574
+ },
2575
+ provider: "openai",
2576
+ keyEnv: "OPENAI_API_KEY",
2577
+ defaultBaseUrl: "https://api.openai.com/v1",
2578
+ path: "/decisions"
2579
+ });
2580
+ }
2581
+
1786
2582
  // src/providers/types.ts
1787
2583
  function createCallableProvider(providerFn, properties) {
1788
2584
  Object.defineProperty(providerFn, "name", {
@@ -1801,17 +2597,17 @@ function createCallableProvider(providerFn, properties) {
1801
2597
 
1802
2598
  // src/core/audio.ts
1803
2599
  var audioErrorMarker = /* @__PURE__ */ Symbol.for("@yourgpt/llm-sdk/AudioAPIError");
1804
- var _a;
2600
+ var _a2;
1805
2601
  var AudioAPIError = class extends Error {
1806
2602
  constructor(message, code, status, requestId) {
1807
2603
  super(message);
1808
2604
  this.code = code;
1809
2605
  this.status = status;
1810
2606
  this.requestId = requestId;
1811
- this[_a] = true;
2607
+ this[_a2] = true;
1812
2608
  this.name = "AudioAPIError";
1813
2609
  }
1814
- static [(_a = audioErrorMarker, Symbol.hasInstance)](value) {
2610
+ static [(_a2 = audioErrorMarker, Symbol.hasInstance)](value) {
1815
2611
  return !!value && typeof value === "object" && value[audioErrorMarker] === true;
1816
2612
  }
1817
2613
  };
@@ -2187,6 +2983,497 @@ function openaiTranscription(modelId = "gpt-4o-mini-transcribe", config = {}) {
2187
2983
  };
2188
2984
  }
2189
2985
 
2986
+ // src/core/image.ts
2987
+ var marker = /* @__PURE__ */ Symbol.for("@yourgpt/llm-sdk/MediaAPIError");
2988
+ var _a3;
2989
+ var MediaAPIError = class extends Error {
2990
+ constructor(message, code, status, requestId) {
2991
+ super(message);
2992
+ this.code = code;
2993
+ this.status = status;
2994
+ this.requestId = requestId;
2995
+ this[_a3] = true;
2996
+ this.name = "MediaAPIError";
2997
+ }
2998
+ static [(_a3 = marker, Symbol.hasInstance)](value) {
2999
+ return !!value && typeof value === "object" && value[marker] === true;
3000
+ }
3001
+ };
3002
+
3003
+ // src/providers/openai/media-http.ts
3004
+ function invalidMedia(message) {
3005
+ throw new MediaAPIError(message, "invalid_input");
3006
+ }
3007
+ async function postMedia(config, path, body, options = {}) {
3008
+ options.signal?.throwIfAborted();
3009
+ const timeout = options.timeoutMs ?? 12e4;
3010
+ if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 6e5)
3011
+ invalidMedia("timeoutMs must be between 1 and 600000");
3012
+ const key = config.apiKey ?? process.env.OPENAI_API_KEY;
3013
+ if (!key) invalidMedia("An OpenAI API key is required on the server");
3014
+ const headers = { Authorization: `Bearer ${key}` };
3015
+ if (typeof body === "string") headers["Content-Type"] = "application/json";
3016
+ if (config.organization) headers["OpenAI-Organization"] = config.organization;
3017
+ if (config.project) headers["OpenAI-Project"] = config.project;
3018
+ if (options.safetyIdentifier)
3019
+ headers["OpenAI-Safety-Identifier"] = options.safetyIdentifier;
3020
+ const controller = new AbortController();
3021
+ const relay = () => controller.abort(options.signal?.reason);
3022
+ options.signal?.addEventListener("abort", relay, { once: true });
3023
+ const timer = setTimeout(
3024
+ () => controller.abort(
3025
+ new DOMException("Media request timed out", "TimeoutError")
3026
+ ),
3027
+ timeout
3028
+ );
3029
+ let rejectAbort = () => {
3030
+ };
3031
+ const aborted = new Promise((_, reject) => {
3032
+ rejectAbort = () => reject(controller.signal.reason);
3033
+ controller.signal.addEventListener("abort", rejectAbort, { once: true });
3034
+ });
3035
+ const perform = async () => {
3036
+ let response;
3037
+ try {
3038
+ response = await (config.fetch ?? globalThis.fetch)(
3039
+ `${(config.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")}${path}`,
3040
+ {
3041
+ method: "POST",
3042
+ headers,
3043
+ body,
3044
+ signal: controller.signal,
3045
+ redirect: "error"
3046
+ }
3047
+ );
3048
+ } catch {
3049
+ controller.signal.throwIfAborted();
3050
+ throw new MediaAPIError(
3051
+ "OpenAI media connection failed",
3052
+ "network_error"
3053
+ );
3054
+ }
3055
+ const requestId = response.headers.get("x-request-id") ?? void 0;
3056
+ if (controller.signal.aborted || !response.ok) {
3057
+ void response.body?.cancel().catch(() => {
3058
+ });
3059
+ controller.signal.throwIfAborted();
3060
+ throw new MediaAPIError(
3061
+ `OpenAI media returned HTTP ${response.status}`,
3062
+ "http_error",
3063
+ response.status,
3064
+ requestId
3065
+ );
3066
+ }
3067
+ const limit = options.maxResponseBytes ?? 1024 * 1024;
3068
+ if (Number(response.headers.get("content-length")) > limit) {
3069
+ void response.body?.cancel().catch(() => {
3070
+ });
3071
+ throw new MediaAPIError(
3072
+ "Media response exceeds size limit",
3073
+ "invalid_response"
3074
+ );
3075
+ }
3076
+ const reader = response.body?.getReader();
3077
+ if (!reader)
3078
+ throw new MediaAPIError("Media response is empty", "invalid_response");
3079
+ const cancel = () => {
3080
+ void reader.cancel().catch(() => {
3081
+ });
3082
+ };
3083
+ controller.signal.addEventListener("abort", cancel, { once: true });
3084
+ const chunks = [];
3085
+ let size = 0;
3086
+ try {
3087
+ while (true) {
3088
+ controller.signal.throwIfAborted();
3089
+ const { done, value } = await reader.read();
3090
+ if (done) break;
3091
+ size += value.byteLength;
3092
+ if (size > limit) {
3093
+ cancel();
3094
+ throw new MediaAPIError(
3095
+ "Media response exceeds size limit",
3096
+ "invalid_response"
3097
+ );
3098
+ }
3099
+ chunks.push(value);
3100
+ }
3101
+ } catch (error) {
3102
+ controller.signal.throwIfAborted();
3103
+ if (error instanceof MediaAPIError) throw error;
3104
+ throw new MediaAPIError(
3105
+ "Media response could not be read",
3106
+ "network_error"
3107
+ );
3108
+ } finally {
3109
+ controller.signal.removeEventListener("abort", cancel);
3110
+ reader.releaseLock();
3111
+ }
3112
+ controller.signal.throwIfAborted();
3113
+ return {
3114
+ text: Buffer.concat(chunks).toString("utf8"),
3115
+ status: response.status,
3116
+ requestId
3117
+ };
3118
+ };
3119
+ try {
3120
+ return await Promise.race([perform(), aborted]);
3121
+ } finally {
3122
+ clearTimeout(timer);
3123
+ options.signal?.removeEventListener("abort", relay);
3124
+ controller.signal.removeEventListener("abort", rejectAbort);
3125
+ }
3126
+ }
3127
+ function mediaJSON(text) {
3128
+ try {
3129
+ const data = JSON.parse(text);
3130
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
3131
+ } catch {
3132
+ }
3133
+ throw new MediaAPIError(
3134
+ "Media API returned invalid JSON",
3135
+ "invalid_response"
3136
+ );
3137
+ }
3138
+
3139
+ // src/providers/openai/image.ts
3140
+ var mimes = {
3141
+ png: "image/png",
3142
+ jpeg: "image/jpeg",
3143
+ webp: "image/webp"
3144
+ };
3145
+ function imageFormat(data) {
3146
+ if (Buffer.from(data.subarray(0, 8)).equals(
3147
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
3148
+ ))
3149
+ return "image/png";
3150
+ if (data[0] === 255 && data[1] === 216 && data[2] === 255)
3151
+ return "image/jpeg";
3152
+ if (Buffer.from(data.subarray(0, 4)).toString() === "RIFF" && Buffer.from(data.subarray(8, 12)).toString() === "WEBP")
3153
+ return "image/webp";
3154
+ }
3155
+ async function fileBlob(file, mask = false) {
3156
+ if (!file || !Object.values(mimes).includes(file.mediaType) || mask && file.mediaType !== "image/png")
3157
+ invalidMedia("Images must be PNG, JPEG or WebP; masks must be PNG");
3158
+ const size = file.data instanceof Blob ? file.data.size : file.data?.byteLength;
3159
+ if (!size || size >= 5e7)
3160
+ invalidMedia("Each input image must be smaller than 50 MB");
3161
+ const bytes = file.data instanceof Blob ? new Uint8Array(await file.data.arrayBuffer()) : file.data instanceof Uint8Array ? file.data : file.data instanceof ArrayBuffer ? new Uint8Array(file.data) : invalidMedia("Image data must be bytes or a Blob");
3162
+ if (imageFormat(bytes) !== file.mediaType)
3163
+ invalidMedia("Image bytes do not match the media type");
3164
+ return new Blob([new Uint8Array(bytes)], { type: file.mediaType });
3165
+ }
3166
+ function optionsFor(model, options) {
3167
+ const allowed = [
3168
+ "quality",
3169
+ "background",
3170
+ "outputFormat",
3171
+ "outputCompression",
3172
+ "moderation",
3173
+ "inputFidelity",
3174
+ "safetyIdentifier"
3175
+ ];
3176
+ for (const [provider, value] of Object.entries(
3177
+ options.providerOptions ?? {}
3178
+ )) {
3179
+ if (provider !== "openai" || !value || typeof value !== "object" || Object.keys(value).some((k) => !allowed.includes(k)))
3180
+ throw new MediaAPIError(
3181
+ "Unsupported image provider option",
3182
+ "unsupported_option"
3183
+ );
3184
+ }
3185
+ const o = options.providerOptions?.openai ?? {};
3186
+ for (const [key, values] of Object.entries({
3187
+ quality: ["auto", "low", "medium", "high", "xhigh", "max"],
3188
+ background: ["auto", "opaque", "transparent"],
3189
+ outputFormat: ["png", "jpeg", "webp"],
3190
+ moderation: ["auto", "low"],
3191
+ inputFidelity: ["high", "low"]
3192
+ })) {
3193
+ const value = o[key];
3194
+ if (value !== void 0 && !values.includes(value))
3195
+ invalidMedia(`Invalid image ${key}`);
3196
+ }
3197
+ if ((o.quality === "xhigh" || o.quality === "max") && !model.startsWith("gpt-image-2.5"))
3198
+ invalidMedia("xhigh/max quality requires a GPT Image 2.5 model");
3199
+ if (o.background === "transparent" && o.outputFormat === "jpeg")
3200
+ invalidMedia("JPEG does not support transparency");
3201
+ if (o.outputCompression !== void 0 && (!Number.isInteger(o.outputCompression) || o.outputCompression < 0 || o.outputCompression > 100 || !["jpeg", "webp"].includes(o.outputFormat ?? "png")))
3202
+ invalidMedia("Compression must be 0\u2013100 and requires JPEG or WebP");
3203
+ if (o.inputFidelity !== void 0 && !options.images)
3204
+ invalidMedia("inputFidelity requires image inputs");
3205
+ if (o.moderation !== void 0 && options.images)
3206
+ throw new MediaAPIError(
3207
+ "moderation is supported for generation only",
3208
+ "unsupported_option"
3209
+ );
3210
+ if (o.safetyIdentifier !== void 0 && (typeof o.safetyIdentifier !== "string" || !o.safetyIdentifier.length || o.safetyIdentifier.length > 128))
3211
+ invalidMedia("safetyIdentifier must contain 1\u2013128 characters");
3212
+ return o;
3213
+ }
3214
+ function openaiImage(modelId = "gpt-image-2.5-flare", config = {}) {
3215
+ if (!/^(gpt-image-|chatgpt-image-)/.test(modelId))
3216
+ invalidMedia(
3217
+ "Use a GPT Image model; retired DALL-E models are not supported"
3218
+ );
3219
+ return {
3220
+ provider: "openai",
3221
+ modelId,
3222
+ async doGenerate(options) {
3223
+ options.signal?.throwIfAborted();
3224
+ if (typeof options.prompt !== "string" || !options.prompt.trim() || options.prompt.length > 32e3)
3225
+ invalidMedia("Image prompt must contain 1\u201332000 characters");
3226
+ const n = options.n ?? 1;
3227
+ if (!Number.isInteger(n) || n < 1 || n > 10)
3228
+ invalidMedia("n must be an integer from 1 to 10");
3229
+ if (options.size !== void 0 && options.size !== "auto") {
3230
+ const match = /^(\d+)x(\d+)$/.exec(options.size);
3231
+ if (!match) invalidMedia("Image size must be auto or WIDTHxHEIGHT");
3232
+ const w = Number(match[1]), h = Number(match[2]);
3233
+ if (modelId.startsWith("gpt-image-2")) {
3234
+ if (w % 16 || h % 16 || w > 3840 || h > 3840 || w / h > 3 || h / w > 3 || w * h < 655360 || w * h > 8294400)
3235
+ invalidMedia(
3236
+ "Image size exceeds GPT Image dimensions or pixel limits"
3237
+ );
3238
+ } else if (!["1024x1024", "1536x1024", "1024x1536"].includes(options.size))
3239
+ invalidMedia("This model requires a standard GPT Image size");
3240
+ }
3241
+ if (options.images && (!Array.isArray(options.images) || !options.images.length || options.images.length > 16))
3242
+ invalidMedia("Editing requires 1\u201316 input images");
3243
+ if (options.mask && !options.images)
3244
+ invalidMedia("A mask requires image inputs");
3245
+ const o = optionsFor(modelId, options);
3246
+ const format = o.outputFormat ?? "png";
3247
+ const fields = {
3248
+ model: modelId,
3249
+ prompt: options.prompt,
3250
+ n,
3251
+ size: options.size,
3252
+ quality: o.quality,
3253
+ background: o.background,
3254
+ output_format: format,
3255
+ output_compression: o.outputCompression,
3256
+ moderation: o.moderation,
3257
+ input_fidelity: o.inputFidelity,
3258
+ safety_identifier: o.safetyIdentifier
3259
+ };
3260
+ let body = JSON.stringify(fields);
3261
+ if (options.images) {
3262
+ const form = new FormData();
3263
+ for (const [key, value] of Object.entries(fields))
3264
+ if (value !== void 0) form.set(key, String(value));
3265
+ let total = 0;
3266
+ for (const [i, input2] of options.images.entries()) {
3267
+ const blob = await fileBlob(input2);
3268
+ total += blob.size;
3269
+ if (total > 1e8)
3270
+ invalidMedia("Total image input exceeds the SDK's 100 MB limit");
3271
+ form.append(
3272
+ "image[]",
3273
+ blob,
3274
+ `image-${i}.${input2.mediaType.split("/")[1]}`
3275
+ );
3276
+ }
3277
+ if (options.mask) {
3278
+ const mask = await fileBlob(options.mask, true);
3279
+ total += mask.size;
3280
+ if (total > 1e8)
3281
+ invalidMedia("Total image input exceeds the SDK's 100 MB limit");
3282
+ form.set("mask", mask, "mask.png");
3283
+ }
3284
+ body = form;
3285
+ }
3286
+ const response = await postMedia(
3287
+ config,
3288
+ options.images ? "/images/edits" : "/images/generations",
3289
+ body,
3290
+ { ...options, maxResponseBytes: 128 * 1024 * 1024 }
3291
+ );
3292
+ const data = mediaJSON(response.text);
3293
+ if (!Array.isArray(data.data) || data.data.length !== n)
3294
+ throw new MediaAPIError(
3295
+ "Images API returned an unexpected image count",
3296
+ "invalid_response"
3297
+ );
3298
+ const images = data.data.map((item) => {
3299
+ const encoded = item?.b64_json;
3300
+ if (typeof encoded !== "string" || !encoded.length || encoded.length % 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded))
3301
+ throw new MediaAPIError(
3302
+ "Images API returned invalid base64",
3303
+ "invalid_response"
3304
+ );
3305
+ const bytes = new Uint8Array(Buffer.from(encoded, "base64"));
3306
+ if (imageFormat(bytes) !== mimes[format])
3307
+ throw new MediaAPIError(
3308
+ "Images API returned an unexpected image format",
3309
+ "invalid_response"
3310
+ );
3311
+ return {
3312
+ uint8Array: bytes,
3313
+ get base64() {
3314
+ return Buffer.from(bytes).toString("base64");
3315
+ },
3316
+ mediaType: mimes[format],
3317
+ ...typeof item.revised_prompt === "string" ? { revisedPrompt: item.revised_prompt } : {}
3318
+ };
3319
+ });
3320
+ return {
3321
+ image: images[0],
3322
+ images,
3323
+ usage: data.usage,
3324
+ response: {
3325
+ provider: "openai",
3326
+ modelId,
3327
+ status: response.status,
3328
+ requestId: response.requestId
3329
+ },
3330
+ providerMetadata: {
3331
+ created: data.created,
3332
+ size: data.size,
3333
+ quality: data.quality,
3334
+ background: data.background
3335
+ }
3336
+ };
3337
+ }
3338
+ };
3339
+ }
3340
+
3341
+ // src/providers/openai/realtime.ts
3342
+ function buildSession(model, o) {
3343
+ if (o.instructions !== void 0 && (typeof o.instructions !== "string" || o.instructions.length > 1e5))
3344
+ invalidMedia(
3345
+ "Realtime instructions must be a string up to 100000 characters"
3346
+ );
3347
+ for (const value of [o.voice, o.transcriptionModel])
3348
+ if (value !== void 0 && (typeof value !== "string" || !value.trim() || value.length > 100))
3349
+ invalidMedia("Invalid realtime voice or transcription model");
3350
+ if (o.language !== void 0 && !/^[a-z]{2}$/.test(o.language))
3351
+ invalidMedia("Realtime language must be a two-letter code");
3352
+ if (o.language && !o.transcriptionModel)
3353
+ invalidMedia("language requires a transcription model");
3354
+ if (o.outputModality !== void 0 && !["audio", "text"].includes(o.outputModality))
3355
+ invalidMedia("Invalid realtime output modality");
3356
+ if (o.maxOutputTokens !== void 0 && o.maxOutputTokens !== "inf" && (!Number.isInteger(o.maxOutputTokens) || o.maxOutputTokens < 1 || o.maxOutputTokens > 4096))
3357
+ invalidMedia("maxOutputTokens must be 1\u20134096 or inf");
3358
+ if (o.safetyIdentifier !== void 0 && (typeof o.safetyIdentifier !== "string" || !/^[\x21-\x7e]{1,128}$/.test(o.safetyIdentifier)))
3359
+ invalidMedia(
3360
+ "safetyIdentifier must contain 1\u2013128 printable ASCII characters"
3361
+ );
3362
+ const names = /* @__PURE__ */ new Set();
3363
+ if (o.tools && (!Array.isArray(o.tools) || o.tools.length > 128))
3364
+ invalidMedia("Realtime supports up to 128 tools");
3365
+ const tools = o.tools?.map((tool) => {
3366
+ if (!tool || !/^[a-zA-Z0-9_-]{1,64}$/.test(tool.name) || names.has(tool.name) || !tool.parameters || tool.parameters.type !== "object")
3367
+ invalidMedia(
3368
+ "Realtime tools require unique names and object parameter schemas"
3369
+ );
3370
+ names.add(tool.name);
3371
+ return {
3372
+ type: "function",
3373
+ name: tool.name,
3374
+ description: tool.description,
3375
+ parameters: tool.parameters
3376
+ };
3377
+ });
3378
+ let turn;
3379
+ const td = o.turnDetection;
3380
+ if (td === null) turn = null;
3381
+ else if (td?.type === "semantic_vad") {
3382
+ if (td.eagerness !== void 0 && !["auto", "low", "medium", "high"].includes(td.eagerness))
3383
+ invalidMedia("Invalid semantic VAD eagerness");
3384
+ turn = {
3385
+ type: td.type,
3386
+ eagerness: td.eagerness,
3387
+ create_response: true,
3388
+ interrupt_response: true
3389
+ };
3390
+ } else {
3391
+ if (td && td.type !== "server_vad")
3392
+ invalidMedia("Invalid realtime turn detection");
3393
+ if (td?.threshold !== void 0 && (!Number.isFinite(td.threshold) || td.threshold < 0 || td.threshold > 1))
3394
+ invalidMedia("VAD threshold must be 0\u20131");
3395
+ for (const value of [td?.silenceDurationMs, td?.prefixPaddingMs])
3396
+ if (value !== void 0 && (!Number.isInteger(value) || value < 0 || value > 1e4))
3397
+ invalidMedia("VAD durations must be 0\u201310000 milliseconds");
3398
+ turn = {
3399
+ type: "server_vad",
3400
+ threshold: td?.threshold,
3401
+ silence_duration_ms: td?.silenceDurationMs,
3402
+ prefix_padding_ms: td?.prefixPaddingMs,
3403
+ create_response: true,
3404
+ interrupt_response: true
3405
+ };
3406
+ }
3407
+ return {
3408
+ type: "realtime",
3409
+ model,
3410
+ instructions: o.instructions,
3411
+ output_modalities: [o.outputModality ?? "audio"],
3412
+ max_output_tokens: o.maxOutputTokens,
3413
+ audio: {
3414
+ input: {
3415
+ turn_detection: turn,
3416
+ ...o.transcriptionModel ? {
3417
+ transcription: {
3418
+ model: o.transcriptionModel,
3419
+ language: o.language
3420
+ }
3421
+ } : {}
3422
+ },
3423
+ output: { voice: o.voice ?? "marin" }
3424
+ },
3425
+ tools
3426
+ };
3427
+ }
3428
+ function openaiRealtime(modelId = "gpt-realtime-2.1", config = {}) {
3429
+ if (!modelId.startsWith("gpt-realtime"))
3430
+ invalidMedia("openaiRealtime requires a gpt-realtime model");
3431
+ return {
3432
+ provider: "openai",
3433
+ modelId,
3434
+ async createClientSecret(options = {}) {
3435
+ const ttl = options.expiresAfterSeconds ?? 60;
3436
+ if (!Number.isInteger(ttl) || ttl < 10 || ttl > 7200)
3437
+ invalidMedia("Client secret lifetime must be 10\u20137200 seconds");
3438
+ const session = buildSession(modelId, options);
3439
+ const response = await postMedia(
3440
+ config,
3441
+ "/realtime/client_secrets",
3442
+ JSON.stringify({
3443
+ session,
3444
+ expires_after: { anchor: "created_at", seconds: ttl }
3445
+ }),
3446
+ { ...options, timeoutMs: options.timeoutMs ?? 3e4 }
3447
+ );
3448
+ const data = mediaJSON(response.text);
3449
+ if (typeof data.value !== "string" || !data.value.startsWith("ek_") || !Number.isFinite(data.expires_at) || data.expires_at <= Date.now() / 1e3)
3450
+ throw new MediaAPIError(
3451
+ "Realtime returned an invalid or expired client secret",
3452
+ "invalid_response"
3453
+ );
3454
+ return { value: data.value, expiresAt: data.expires_at, modelId };
3455
+ },
3456
+ async createCall(options) {
3457
+ if (typeof options.sdp !== "string" || !options.sdp.startsWith("v=0") || options.sdp.length > 65536)
3458
+ invalidMedia("A valid SDP offer up to 64 KiB is required");
3459
+ const form = new FormData();
3460
+ form.set("sdp", options.sdp);
3461
+ form.set("session", JSON.stringify(buildSession(modelId, options)));
3462
+ const response = await postMedia(config, "/realtime/calls", form, {
3463
+ ...options,
3464
+ timeoutMs: options.timeoutMs ?? 3e4,
3465
+ maxResponseBytes: 65536
3466
+ });
3467
+ if (!response.text.startsWith("v=0"))
3468
+ throw new MediaAPIError(
3469
+ "Realtime returned an invalid SDP answer",
3470
+ "invalid_response"
3471
+ );
3472
+ return { sdp: response.text };
3473
+ }
3474
+ };
3475
+ }
3476
+
2190
3477
  // src/providers/openai/index.ts
2191
3478
  var OPENAI_MODELS2 = {
2192
3479
  // GPT-4o series
@@ -2302,6 +3589,10 @@ function createOpenAI(config = {}) {
2302
3589
  apiKey,
2303
3590
  model: modelId,
2304
3591
  baseUrl: config.baseUrl,
3592
+ organization: config.organization,
3593
+ project: config.project,
3594
+ timeout: config.timeout,
3595
+ headers: config.headers,
2305
3596
  reasoningEffort: config.reasoningEffort,
2306
3597
  api: config.api,
2307
3598
  responses: config.responses,
@@ -2338,6 +3629,9 @@ exports.createOpenAI = createOpenAI;
2338
3629
  exports.createOpenAIModel = openai;
2339
3630
  exports.createOpenAIProvider = createOpenAIProvider;
2340
3631
  exports.openai = openai;
3632
+ exports.openaiDecision = openaiDecision;
3633
+ exports.openaiImage = openaiImage;
3634
+ exports.openaiRealtime = openaiRealtime;
2341
3635
  exports.openaiSpeech = openaiSpeech;
2342
3636
  exports.openaiTranscription = openaiTranscription;
2343
3637
  //# sourceMappingURL=index.js.map