@yourgpt/llm-sdk 2.6.0-beta.1 → 2.6.0-beta.3

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 (61) hide show
  1. package/README.md +146 -0
  2. package/dist/adapters/index.d.mts +5 -5
  3. package/dist/adapters/index.d.ts +5 -5
  4. package/dist/adapters/index.js +437 -39
  5. package/dist/adapters/index.mjs +437 -39
  6. package/dist/audio-BBSwnjo8.d.mts +92 -0
  7. package/dist/audio-BBSwnjo8.d.ts +92 -0
  8. package/dist/{base-CkeT7u-O.d.mts → base-BICaQaRS.d.mts} +7 -3
  9. package/dist/{base-BdTpUAWI.d.ts → base-CpRPB8u8.d.ts} +7 -3
  10. package/dist/fallback/index.d.mts +4 -4
  11. package/dist/fallback/index.d.ts +4 -4
  12. package/dist/index.d.mts +20 -36
  13. package/dist/index.d.ts +20 -36
  14. package/dist/index.js +327 -69
  15. package/dist/index.mjs +325 -70
  16. package/dist/providers/anthropic/index.d.mts +3 -3
  17. package/dist/providers/anthropic/index.d.ts +3 -3
  18. package/dist/providers/anthropic/index.js +116 -7
  19. package/dist/providers/anthropic/index.mjs +116 -7
  20. package/dist/providers/azure/index.d.mts +3 -3
  21. package/dist/providers/azure/index.d.ts +3 -3
  22. package/dist/providers/azure/index.js +101 -4
  23. package/dist/providers/azure/index.mjs +101 -4
  24. package/dist/providers/fireworks/index.d.mts +3 -3
  25. package/dist/providers/fireworks/index.d.ts +3 -3
  26. package/dist/providers/fireworks/index.js +393 -31
  27. package/dist/providers/fireworks/index.mjs +393 -31
  28. package/dist/providers/google/index.d.mts +3 -3
  29. package/dist/providers/google/index.d.ts +3 -3
  30. package/dist/providers/google/index.js +393 -31
  31. package/dist/providers/google/index.mjs +393 -31
  32. package/dist/providers/ollama/index.d.mts +4 -4
  33. package/dist/providers/ollama/index.d.ts +4 -4
  34. package/dist/providers/ollama/index.js +87 -2
  35. package/dist/providers/ollama/index.mjs +87 -2
  36. package/dist/providers/openai/index.d.mts +25 -6
  37. package/dist/providers/openai/index.d.ts +25 -6
  38. package/dist/providers/openai/index.js +822 -40
  39. package/dist/providers/openai/index.mjs +821 -41
  40. package/dist/providers/openrouter/index.d.mts +3 -3
  41. package/dist/providers/openrouter/index.d.ts +3 -3
  42. package/dist/providers/openrouter/index.js +393 -31
  43. package/dist/providers/openrouter/index.mjs +393 -31
  44. package/dist/providers/togetherai/index.d.mts +3 -3
  45. package/dist/providers/togetherai/index.d.ts +3 -3
  46. package/dist/providers/togetherai/index.js +393 -31
  47. package/dist/providers/togetherai/index.mjs +393 -31
  48. package/dist/providers/xai/index.d.mts +3 -3
  49. package/dist/providers/xai/index.d.ts +3 -3
  50. package/dist/providers/xai/index.js +393 -31
  51. package/dist/providers/xai/index.mjs +393 -31
  52. package/dist/{types-BWRYIvl_.d.mts → types-C3RgRbEC.d.mts} +1 -1
  53. package/dist/{types-CpXcT-tY.d.ts → types-DCFqkJAy.d.ts} +1 -1
  54. package/dist/{types-NOswlhm9.d.mts → types-DHQEMFHA.d.mts} +3 -3
  55. package/dist/{types-BW3csYo5.d.mts → types-Dg1Bcg1k.d.mts} +106 -3
  56. package/dist/{types-BW3csYo5.d.ts → types-Dg1Bcg1k.d.ts} +106 -3
  57. package/dist/{types-DA54bSmX.d.ts → types-Dikj3En4.d.ts} +3 -3
  58. package/dist/yourgpt/index.d.mts +1 -1
  59. package/dist/yourgpt/index.d.ts +1 -1
  60. package/docs/multimodal-and-audio.md +170 -0
  61. package/package.json +3 -2
@@ -1,5 +1,108 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/tool-output.ts
4
+ var imageTypes = /* @__PURE__ */ new Set([
5
+ "image/png",
6
+ "image/jpeg",
7
+ "image/webp",
8
+ "image/gif"
9
+ ]);
10
+ function normalizeToolOutput(value) {
11
+ const output = value;
12
+ if (output?.type !== "content" || !Array.isArray(output.content) || !output.content.length) {
13
+ throw new Error("Tool model output must contain a nonempty content array");
14
+ }
15
+ return {
16
+ type: "content",
17
+ content: output.content.map((part) => {
18
+ if (part?.type === "text" && typeof part.text === "string")
19
+ return { type: "text", text: part.text };
20
+ if (part?.type !== "image")
21
+ throw new Error("Unsupported tool output content type");
22
+ const sources = ["data", "url", "fileId"].filter((key) => key in part);
23
+ if (sources.length !== 1)
24
+ throw new Error(
25
+ "Tool image requires exactly one of data, url, or fileId"
26
+ );
27
+ if ("fileId" in part) {
28
+ if (part.provider !== "openai" || typeof part.fileId !== "string" || !part.fileId.trim())
29
+ throw new Error(
30
+ "Tool image file references must name the openai provider and fileId"
31
+ );
32
+ return { type: "image", fileId: part.fileId, provider: "openai" };
33
+ }
34
+ if ("url" in part) {
35
+ let url;
36
+ try {
37
+ url = new URL(part.url);
38
+ } catch {
39
+ throw new Error("Invalid tool image URL");
40
+ }
41
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
42
+ throw new Error(
43
+ "Tool image URL must use HTTP(S) without credentials"
44
+ );
45
+ return { type: "image", url: url.href };
46
+ }
47
+ if (!("data" in part) || !imageTypes.has(part.mediaType))
48
+ throw new Error("Unsupported tool image media type");
49
+ let data = part.data instanceof Uint8Array ? Buffer.from(part.data).toString("base64") : part.data;
50
+ if (typeof data !== "string")
51
+ throw new Error("Tool image data must be base64 or bytes");
52
+ if (data.startsWith("data:")) {
53
+ const match = /^data:([^;,]+);base64,(.+)$/.exec(data);
54
+ if (!match || match[1] !== part.mediaType)
55
+ throw new Error(
56
+ "Tool image data URL media type does not match mediaType"
57
+ );
58
+ data = match[2];
59
+ }
60
+ if (!data.length || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
61
+ throw new Error("Invalid tool image base64 data");
62
+ if (Buffer.byteLength(data, "base64") > 20 * 1024 * 1024)
63
+ throw new Error("Tool image exceeds 20 MiB");
64
+ if (Buffer.from(data, "base64").toString("base64") !== data)
65
+ throw new Error("Invalid tool image base64 data");
66
+ return { type: "image", data, mediaType: part.mediaType };
67
+ })
68
+ };
69
+ }
70
+ function toolOutputForProvider(value, provider) {
71
+ const output = normalizeToolOutput(value);
72
+ return output.content.map((part) => {
73
+ if (part.type === "text")
74
+ return {
75
+ type: provider === "responses" ? "input_text" : "text",
76
+ text: part.text
77
+ };
78
+ if (provider === "unsupported")
79
+ throw new Error(
80
+ "Image tool results require OpenAI Responses or Anthropic; this endpoint is not supported"
81
+ );
82
+ if ("fileId" in part) {
83
+ if (provider !== "responses")
84
+ throw new Error(
85
+ "OpenAI image file references cannot be sent to another provider"
86
+ );
87
+ return { type: "input_image", file_id: part.fileId, detail: "auto" };
88
+ }
89
+ if (provider === "responses")
90
+ return {
91
+ type: "input_image",
92
+ image_url: "url" in part ? part.url : `data:${part.mediaType};base64,${part.data}`,
93
+ detail: "auto"
94
+ };
95
+ return {
96
+ type: "image",
97
+ source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
98
+ };
99
+ });
100
+ }
101
+ function textToolOutput(value, fallback) {
102
+ if (value === void 0) return fallback;
103
+ return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
104
+ }
105
+
3
106
  // src/core/openai-parameters.ts
4
107
  function capabilities(model) {
5
108
  const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
@@ -51,7 +154,120 @@ function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api
51
154
  return result;
52
155
  }
53
156
 
157
+ // src/core/openai-responses.ts
158
+ function resolveOpenAITransport(defaults, overrides) {
159
+ return {
160
+ api: overrides?.api ?? defaults.api ?? "auto",
161
+ responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
162
+ };
163
+ }
164
+ function useOpenAIResponses(model, baseURL, parameters, transport2, hasTools, hasReplay, nativeToolSearch = false) {
165
+ const api = transport2.api ?? "auto";
166
+ if (!["auto", "chat", "responses"].includes(api)) {
167
+ throw new Error(`Unknown OpenAI API selection: ${api}`);
168
+ }
169
+ if (api === "chat") {
170
+ if (transport2.responses !== void 0 || nativeToolSearch) {
171
+ throw new Error(
172
+ "Responses options and native tool search require the Responses API"
173
+ );
174
+ }
175
+ return false;
176
+ }
177
+ let officialEndpoint = !baseURL;
178
+ try {
179
+ officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
180
+ } catch {
181
+ }
182
+ return api === "responses" || transport2.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
183
+ }
184
+ function responseMetadata(response) {
185
+ const output = Array.isArray(response?.output) ? response.output : [];
186
+ return {
187
+ openai: {
188
+ response: {
189
+ id: response?.id,
190
+ model: response?.model,
191
+ status: response?.status,
192
+ output,
193
+ usage: response?.usage,
194
+ store: response?.store,
195
+ refusals: output.flatMap(
196
+ (item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
197
+ (part) => part?.type === "refusal" && typeof part.refusal === "string"
198
+ ).map((part) => part.refusal) : []
199
+ )
200
+ }
201
+ }
202
+ };
203
+ }
204
+ function responseReplayMetadata(metadata) {
205
+ const response = metadata?.openai?.response;
206
+ if (!response) return metadata;
207
+ const output = response.output.map((item) => {
208
+ const fields = ["type", "id", "status"];
209
+ switch (item.type) {
210
+ case "reasoning":
211
+ fields.push("encrypted_content");
212
+ break;
213
+ case "message":
214
+ fields.push("role", "phase");
215
+ break;
216
+ case "function_call":
217
+ fields.push("call_id", "name", "namespace", "arguments");
218
+ break;
219
+ case "tool_search_call":
220
+ fields.push("call_id", "execution", "arguments");
221
+ break;
222
+ case "tool_search_output":
223
+ fields.push("call_id", "tools");
224
+ break;
225
+ case "web_search_call":
226
+ fields.push("action");
227
+ break;
228
+ default:
229
+ return stripItemDiagnostics(item);
230
+ }
231
+ const replay = Object.fromEntries(
232
+ fields.filter((key) => item[key] !== void 0).map((key) => [key, item[key]])
233
+ );
234
+ if (item.type === "reasoning") replay.summary = [];
235
+ if (item.type === "message")
236
+ replay.content = Array.isArray(item.content) ? item.content.flatMap(
237
+ (part) => part?.type === "output_text" && typeof part.text === "string" ? [{ type: "output_text", text: part.text, annotations: [] }] : part?.type === "refusal" && typeof part.refusal === "string" ? [{ type: "refusal", refusal: part.refusal }] : []
238
+ ) : [];
239
+ return stripItemDiagnostics(replay);
240
+ });
241
+ return { openai: { response: { output, refusals: [] } } };
242
+ }
243
+ function stripItemDiagnostics(item) {
244
+ const {
245
+ usage: _usage,
246
+ logprobs: _logprobs,
247
+ metadata: _metadata,
248
+ ...state
249
+ } = item;
250
+ return state;
251
+ }
252
+ function getResponseReplay(metadata) {
253
+ const response = metadata?.openai?.response;
254
+ if (response === void 0) return void 0;
255
+ if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
256
+ throw new Error(
257
+ "Invalid OpenAI Responses replay metadata: output must contain typed items"
258
+ );
259
+ }
260
+ return responseReplayMetadata(metadata)?.openai?.response?.output;
261
+ }
262
+
54
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
+ }
55
271
  function providerErrorEvent(error, code) {
56
272
  const details = error ?? {};
57
273
  const event = {
@@ -283,7 +499,7 @@ function messageToOpenAIContent(message) {
283
499
  }
284
500
  return blocks;
285
501
  }
286
- function formatMessagesForOpenAI(messages, systemPrompt) {
502
+ function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
287
503
  const formatted = [];
288
504
  if (systemPrompt) {
289
505
  formatted.push({ role: "system", content: systemPrompt });
@@ -301,7 +517,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
301
517
  const assistantMsg = {
302
518
  role: "assistant",
303
519
  // Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
304
- content: hasToolCalls ? msg.content || null : msg.content
520
+ content: hasToolCalls ? msg.content || null : msg.content,
521
+ ...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
305
522
  };
306
523
  if (hasToolCalls) {
307
524
  assistantMsg.tool_calls = msg.tool_calls;
@@ -310,7 +527,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
310
527
  } else if (msg.role === "tool" && msg.tool_call_id) {
311
528
  formatted.push({
312
529
  role: "tool",
313
- content: msg.content ?? "",
530
+ content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
531
+ ...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
314
532
  tool_call_id: msg.tool_call_id
315
533
  });
316
534
  }
@@ -361,13 +579,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
361
579
  }));
362
580
  }
363
581
  shouldUseResponsesApi(request) {
364
- if (this.toolDefinitions(request).length === 0) return false;
365
- if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
366
- return true;
367
- return requiresOpenAIResponses(
582
+ return useOpenAIResponses(
368
583
  request.config?.model || this.model,
369
584
  this.config.baseUrl,
370
- this.parameterOptions(request)
585
+ this.parameterOptions(request),
586
+ resolveOpenAITransport(this.config, request.config?.openai),
587
+ this.toolDefinitions(request).length > 0,
588
+ !!(request.rawMessages ?? request.messages)?.some(
589
+ (message) => message.providerMetadata?.openai?.response
590
+ ),
591
+ request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
371
592
  );
372
593
  }
373
594
  /**
@@ -400,11 +621,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
400
621
  });
401
622
  }
402
623
  buildResponsesInput(request) {
403
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
624
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
404
625
  const input = [];
405
626
  for (const message of sourceMessages) {
406
627
  if (message.role === "system") {
407
- if (!request.systemPrompt && message.content) {
628
+ if (message.content && message.content !== request.systemPrompt) {
408
629
  input.push({
409
630
  type: "message",
410
631
  role: "developer",
@@ -414,6 +635,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
414
635
  continue;
415
636
  }
416
637
  if (message.role === "assistant") {
638
+ const replay = getResponseReplay(message.providerMetadata);
639
+ if (replay) {
640
+ input.push(...replay);
641
+ continue;
642
+ }
417
643
  const content = this.toResponsesContent(message.content, "assistant");
418
644
  if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
419
645
  input.push({
@@ -437,7 +663,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
437
663
  input.push({
438
664
  type: "function_call_output",
439
665
  call_id: message.tool_call_id,
440
- output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
666
+ output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
441
667
  });
442
668
  continue;
443
669
  }
@@ -451,10 +677,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
451
677
  return input;
452
678
  }
453
679
  /** Preserve Copilot screenshot attachments on every OpenAI transport. */
454
- formatRawMessages(messages) {
680
+ formatRawMessages(messages, preserveProviderMetadata = false) {
455
681
  return messages.map((message) => {
456
- const { attachments, ...rest } = message;
457
- let content = message.content;
682
+ const { attachments, providerMetadata, modelOutput, ...rest } = message;
683
+ let content = message.role === "tool" && !preserveProviderMetadata && modelOutput !== void 0 ? textToolOutput(modelOutput, "") : message.content;
684
+ if (preserveProviderMetadata && modelOutput)
685
+ rest.modelOutput = modelOutput;
458
686
  if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
459
687
  content = null;
460
688
  }
@@ -467,7 +695,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
467
695
  content = [...parts, ...images];
468
696
  }
469
697
  }
470
- return { ...rest, content };
698
+ return {
699
+ ...rest,
700
+ content,
701
+ ...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
702
+ };
471
703
  });
472
704
  }
473
705
  /**
@@ -548,14 +780,40 @@ var OpenAIAdapter = class _OpenAIAdapter {
548
780
  ).map((part) => part.text) : []
549
781
  ).join("") : "";
550
782
  const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
551
- const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
552
- id: item.call_id ?? item.id ?? generateToolCallId(),
553
- name: item.name,
554
- args: JSON.parse(item.arguments ?? "{}")
555
- })) : [];
783
+ const seenCallIds = /* @__PURE__ */ new Set();
784
+ const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
785
+ if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
786
+ throw invalidProviderResponse(
787
+ "Responses contains an incomplete function call"
788
+ );
789
+ }
790
+ if (seenCallIds.has(item.call_id))
791
+ throw invalidProviderResponse(
792
+ "Responses contains duplicate function call IDs"
793
+ );
794
+ seenCallIds.add(item.call_id);
795
+ let args;
796
+ try {
797
+ args = JSON.parse(item.arguments);
798
+ } catch {
799
+ throw invalidProviderResponse(
800
+ "Responses function arguments must be valid JSON"
801
+ );
802
+ }
803
+ if (!args || typeof args !== "object" || Array.isArray(args))
804
+ throw invalidProviderResponse(
805
+ "Responses function arguments must be a JSON object"
806
+ );
807
+ return {
808
+ id: item.call_id,
809
+ name: item.name,
810
+ args
811
+ };
812
+ }) : [];
556
813
  return {
557
814
  content,
558
815
  toolCalls,
816
+ providerMetadata: responseMetadata(response),
559
817
  usage: response?.usage ? {
560
818
  promptTokens: response.usage.input_tokens ?? 0,
561
819
  completionTokens: response.usage.output_tokens ?? 0,
@@ -566,6 +824,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
566
824
  }
567
825
  buildResponsesPayload(request, stream) {
568
826
  const openaiToolOptions = request.providerToolOptions?.openai;
827
+ const options = resolveOpenAITransport(
828
+ this.config,
829
+ request.config?.openai
830
+ ).responses;
831
+ const parameters = buildOpenAIParameters(
832
+ request.config?.model || this.model,
833
+ request.config?.maxTokens ?? this.config.maxTokens,
834
+ request.config?.temperature ?? this.config.temperature,
835
+ this.parameterOptions(request),
836
+ "responses"
837
+ );
569
838
  const responsesTextFormat = toOpenAIResponsesTextFormat(
570
839
  request.config?.responseFormat
571
840
  );
@@ -581,13 +850,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
581
850
  ),
582
851
  tool_choice: openaiToolOptions?.toolChoice,
583
852
  parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
584
- ...buildOpenAIParameters(
585
- modelIdForPayload,
586
- request.config?.maxTokens ?? this.config.maxTokens,
587
- request.config?.temperature ?? this.config.temperature,
588
- this.parameterOptions(request),
589
- "responses"
590
- ),
853
+ ...parameters,
854
+ ...options?.reasoningSummary ? {
855
+ reasoning: {
856
+ ...parameters.reasoning,
857
+ summary: options.reasoningSummary
858
+ }
859
+ } : {},
860
+ ...options?.store !== void 0 ? { store: options.store } : {},
861
+ ...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
862
+ ...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
863
+ ...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
864
+ ...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
+ } : {},
591
873
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
592
874
  stream
593
875
  };
@@ -646,6 +928,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
646
928
  let usage;
647
929
  const citations = [];
648
930
  let completed = false;
931
+ let terminalResponse;
932
+ let responseIdentity = {};
933
+ const outputItems = /* @__PURE__ */ new Map();
934
+ let streamedText = "";
649
935
  function* flushToolCalls() {
650
936
  for (const call of toolCalls.values()) {
651
937
  if (!call.name)
@@ -662,17 +948,36 @@ var OpenAIAdapter = class _OpenAIAdapter {
662
948
  for await (const event of stream) {
663
949
  throwIfAborted(request.signal);
664
950
  logProviderPayload("openai", "stream chunk", event, request.debug);
951
+ if (completed)
952
+ throw new Error("Responses stream emitted an event after completion");
665
953
  const index = event?.output_index ?? 0;
666
954
  switch (event?.type) {
955
+ case "response.created":
956
+ responseIdentity = {
957
+ id: event.response?.id,
958
+ model: event.response?.model
959
+ };
960
+ break;
961
+ case "response.output_item.done":
962
+ if (event.item) outputItems.set(index, event.item);
963
+ if (event.item?.type === "function_call") {
964
+ toolCalls.set(index, {
965
+ id: event.item.call_id,
966
+ name: event.item.name,
967
+ arguments: event.item.arguments
968
+ });
969
+ }
970
+ break;
667
971
  case "response.output_text.delta":
668
972
  if (event.delta) {
973
+ streamedText += event.delta;
669
974
  yield { type: "message:delta", content: event.delta };
670
975
  }
671
976
  break;
672
977
  case "response.output_item.added":
673
978
  if (event.item?.type === "function_call") {
674
979
  toolCalls.set(index, {
675
- id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
980
+ id: event.item.call_id ?? "",
676
981
  name: event.item.name ?? "",
677
982
  arguments: event.item.arguments ?? ""
678
983
  });
@@ -691,7 +996,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
691
996
  break;
692
997
  }
693
998
  case "response.completed": {
999
+ if (event.response?.status && event.response.status !== "completed")
1000
+ throw new Error(`Responses stream ${event.response.status}`);
694
1001
  completed = true;
1002
+ terminalResponse = { ...responseIdentity, ...event.response };
695
1003
  const found = this.collectResponsesCitations(
696
1004
  event.response?.output,
697
1005
  citations.length
@@ -728,12 +1036,66 @@ var OpenAIAdapter = class _OpenAIAdapter {
728
1036
  throwIfAborted(request.signal);
729
1037
  if (!completed)
730
1038
  throw new Error("Responses stream ended without a completed response");
1039
+ const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
1040
+ if (output.some((item) => item.type === "function_call"))
1041
+ toolCalls.clear();
1042
+ for (const [index, item] of output.entries()) {
1043
+ if (item.type === "function_call") {
1044
+ toolCalls.set(index, {
1045
+ id: item.call_id,
1046
+ name: item.name,
1047
+ arguments: item.arguments
1048
+ });
1049
+ }
1050
+ }
1051
+ const uniqueCalls = /* @__PURE__ */ new Map();
1052
+ for (const call of toolCalls.values()) {
1053
+ if (!call.id || !call.name)
1054
+ throw invalidProviderResponse(
1055
+ "Responses stream contains an incomplete tool call"
1056
+ );
1057
+ uniqueCalls.set(call.id, call);
1058
+ }
1059
+ toolCalls.clear();
1060
+ [...uniqueCalls.values()].forEach(
1061
+ (call, index) => toolCalls.set(index, call)
1062
+ );
1063
+ if (!output.length && toolCalls.size) {
1064
+ for (const call of toolCalls.values())
1065
+ output.push({
1066
+ type: "function_call",
1067
+ call_id: call.id,
1068
+ name: call.name,
1069
+ arguments: call.arguments
1070
+ });
1071
+ }
1072
+ if (!output.length && streamedText)
1073
+ output.push({
1074
+ type: "message",
1075
+ role: "assistant",
1076
+ content: [{ type: "output_text", text: streamedText, annotations: [] }]
1077
+ });
1078
+ terminalResponse.output = output;
1079
+ const finalText = this.parseResponsesResult(terminalResponse).content;
1080
+ if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
1081
+ yield {
1082
+ type: "message:delta",
1083
+ content: finalText.slice(streamedText.length)
1084
+ };
1085
+ }
731
1086
  yield* flushToolCalls();
732
1087
  if (citations.length > 0) {
733
1088
  yield { type: "citation", citations: deduplicateCitations(citations) };
734
1089
  }
735
- yield { type: "message:end" };
736
- yield { type: "done", usage };
1090
+ yield {
1091
+ type: "message:end",
1092
+ providerMetadata: responseMetadata(terminalResponse)
1093
+ };
1094
+ yield {
1095
+ type: "done",
1096
+ usage,
1097
+ providerMetadata: responseMetadata(terminalResponse)
1098
+ };
737
1099
  }
738
1100
  async *stream(request) {
739
1101
  throwIfAborted(request.signal);
@@ -1101,7 +1463,16 @@ function openai(modelId, options = {}) {
1101
1463
  };
1102
1464
  }
1103
1465
  function useResponses(params) {
1104
- return !!params.tools?.length && requiresOpenAIResponses(modelId, baseURL, parameterOptions(params));
1466
+ return useOpenAIResponses(
1467
+ modelId,
1468
+ baseURL,
1469
+ parameterOptions(params),
1470
+ resolveOpenAITransport(options, params.openai),
1471
+ !!params.tools?.length,
1472
+ params.messages.some(
1473
+ (message) => message.role === "assistant" && !!message.providerMetadata?.openai?.response
1474
+ )
1475
+ );
1105
1476
  }
1106
1477
  const responsesAdapter = new OpenAIAdapter({
1107
1478
  ...options,
@@ -1112,7 +1483,7 @@ function openai(modelId, options = {}) {
1112
1483
  function responsesRequest(params) {
1113
1484
  return {
1114
1485
  messages: [],
1115
- rawMessages: formatMessagesForOpenAI2(params.messages),
1486
+ rawMessages: formatMessagesForOpenAI2(params.messages, true),
1116
1487
  toolDefinitions: params.tools?.map((raw) => {
1117
1488
  const tool = raw;
1118
1489
  if (tool.type !== "function")
@@ -1127,6 +1498,10 @@ function openai(modelId, options = {}) {
1127
1498
  };
1128
1499
  }),
1129
1500
  config: {
1501
+ openai: {
1502
+ ...resolveOpenAITransport(options, params.openai),
1503
+ api: "responses"
1504
+ },
1130
1505
  temperature: params.temperature,
1131
1506
  maxTokens: params.maxTokens,
1132
1507
  responseFormat: params.responseFormat,
@@ -1158,13 +1533,14 @@ function openai(modelId, options = {}) {
1158
1533
  return {
1159
1534
  text: result.content,
1160
1535
  toolCalls: result.toolCalls,
1161
- finishReason: result.toolCalls.length ? "tool-calls" : "stop",
1536
+ finishReason: result.toolCalls.length ? "tool-calls" : result.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
1162
1537
  usage: result.usage ?? {
1163
1538
  promptTokens: 0,
1164
1539
  completionTokens: 0,
1165
1540
  totalTokens: 0
1166
1541
  },
1167
- rawResponse: result.rawResponse
1542
+ rawResponse: result.rawResponse,
1543
+ providerMetadata: result.providerMetadata
1168
1544
  };
1169
1545
  }
1170
1546
  const client2 = await getClient();
@@ -1246,7 +1622,8 @@ function openai(modelId, options = {}) {
1246
1622
  case "done":
1247
1623
  yield {
1248
1624
  type: "finish",
1249
- finishReason: hasTools ? "tool-calls" : "stop",
1625
+ finishReason: hasTools ? "tool-calls" : event.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
1626
+ providerMetadata: event.providerMetadata,
1250
1627
  usage: {
1251
1628
  promptTokens: event.usage?.prompt_tokens ?? 0,
1252
1629
  completionTokens: event.usage?.completion_tokens ?? 0,
@@ -1342,7 +1719,7 @@ function mapFinishReason(reason) {
1342
1719
  return "unknown";
1343
1720
  }
1344
1721
  }
1345
- function formatMessagesForOpenAI2(messages) {
1722
+ function formatMessagesForOpenAI2(messages, responses = false) {
1346
1723
  return messages.map((msg) => {
1347
1724
  switch (msg.role) {
1348
1725
  case "system":
@@ -1359,16 +1736,28 @@ function formatMessagesForOpenAI2(messages) {
1359
1736
  }
1360
1737
  if (part.type === "image") {
1361
1738
  const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
1362
- const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
1739
+ const url = /^(data:|https?:\/\/)/.test(imageData) ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
1363
1740
  return { type: "image_url", image_url: { url, detail: "auto" } };
1364
1741
  }
1365
- return { type: "text", text: "" };
1742
+ if (part.type === "file") {
1743
+ if (!responses)
1744
+ throw new Error(
1745
+ "File inputs require the Responses API on this OpenAI path"
1746
+ );
1747
+ return /^https?:\/\//.test(part.data) ? { type: "input_file", file_url: part.data } : {
1748
+ type: "input_file",
1749
+ filename: part.filename ?? "document.pdf",
1750
+ file_data: part.data.startsWith("data:") ? part.data : `data:${part.mimeType};base64,${part.data}`
1751
+ };
1752
+ }
1753
+ throw new Error("Unsupported OpenAI message content");
1366
1754
  })
1367
1755
  };
1368
1756
  case "assistant":
1369
1757
  const assistantMsg = {
1370
1758
  role: "assistant",
1371
- content: msg.content
1759
+ content: msg.content,
1760
+ ...responses && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
1372
1761
  };
1373
1762
  if (msg.toolCalls && msg.toolCalls.length > 0) {
1374
1763
  assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
@@ -1385,7 +1774,8 @@ function formatMessagesForOpenAI2(messages) {
1385
1774
  return {
1386
1775
  role: "tool",
1387
1776
  tool_call_id: msg.toolCallId,
1388
- content: msg.content
1777
+ content: responses ? msg.content : textToolOutput(msg.modelOutput, msg.content),
1778
+ ...responses && msg.modelOutput ? { modelOutput: msg.modelOutput } : {}
1389
1779
  };
1390
1780
  default:
1391
1781
  return msg;
@@ -1409,6 +1799,394 @@ function createCallableProvider(providerFn, properties) {
1409
1799
  return providerFn;
1410
1800
  }
1411
1801
 
1802
+ // src/core/audio.ts
1803
+ var audioErrorMarker = /* @__PURE__ */ Symbol.for("@yourgpt/llm-sdk/AudioAPIError");
1804
+ var _a;
1805
+ var AudioAPIError = class extends Error {
1806
+ constructor(message, code, status, requestId) {
1807
+ super(message);
1808
+ this.code = code;
1809
+ this.status = status;
1810
+ this.requestId = requestId;
1811
+ this[_a] = true;
1812
+ this.name = "AudioAPIError";
1813
+ }
1814
+ static [(_a = audioErrorMarker, Symbol.hasInstance)](value) {
1815
+ return !!value && typeof value === "object" && value[audioErrorMarker] === true;
1816
+ }
1817
+ };
1818
+
1819
+ // src/providers/openai/audio.ts
1820
+ var formats = {
1821
+ mp3: "audio/mpeg",
1822
+ opus: "audio/ogg",
1823
+ aac: "audio/aac",
1824
+ flac: "audio/flac",
1825
+ wav: "audio/wav",
1826
+ pcm: "audio/pcm"
1827
+ };
1828
+ var extensions = {
1829
+ "audio/mpeg": "mp3",
1830
+ "audio/mp3": "mp3",
1831
+ "audio/mp4": "m4a",
1832
+ "video/mp4": "mp4",
1833
+ "audio/wav": "wav",
1834
+ "audio/x-wav": "wav",
1835
+ "audio/webm": "webm",
1836
+ "video/webm": "webm",
1837
+ "audio/ogg": "ogg",
1838
+ "audio/flac": "flac",
1839
+ "audio/x-flac": "flac"
1840
+ };
1841
+ var MAX_AUDIO = 25e6;
1842
+ function hasAudioHeader(data, format) {
1843
+ const head = Buffer.from(data.subarray(0, 12)).toString("latin1");
1844
+ if (format === "wav")
1845
+ return head.startsWith("RIFF") && head.slice(8, 12) === "WAVE";
1846
+ if (format === "mp3" || format === "mpeg" || format === "mpga")
1847
+ return head.startsWith("ID3") || data[0] === 255 && (data[1] & 224) === 224;
1848
+ if (format === "opus" || format === "ogg") return head.startsWith("OggS");
1849
+ if (format === "flac") return head.startsWith("fLaC");
1850
+ if (format === "aac") return data[0] === 255 && (data[1] & 246) === 240;
1851
+ if (format === "webm")
1852
+ return data[0] === 26 && data[1] === 69 && data[2] === 223 && data[3] === 163;
1853
+ if (format === "m4a" || format === "mp4") return head.slice(4, 8) === "ftyp";
1854
+ return format === "pcm" && data.length > 0 && data.length % 2 === 0;
1855
+ }
1856
+ function invalid(message) {
1857
+ throw new AudioAPIError(message, "invalid_input");
1858
+ }
1859
+ function unsupported(message) {
1860
+ throw new AudioAPIError(message, "unsupported_option");
1861
+ }
1862
+ function providerOptions(value, allowed) {
1863
+ for (const [provider, options] of Object.entries(value ?? {})) {
1864
+ if (provider !== "openai")
1865
+ unsupported(
1866
+ `Provider options for ${provider} cannot be used with OpenAI audio`
1867
+ );
1868
+ for (const key of Object.keys(options))
1869
+ if (!allowed.includes(key))
1870
+ unsupported(`Unsupported OpenAI audio option: ${key}`);
1871
+ }
1872
+ return value?.openai ?? {};
1873
+ }
1874
+ async function readBody(response, limit) {
1875
+ if (Number(response.headers.get("content-length")) > limit) {
1876
+ await response.body?.cancel();
1877
+ throw new AudioAPIError(
1878
+ "Audio API response exceeds the size limit",
1879
+ "invalid_response"
1880
+ );
1881
+ }
1882
+ const reader = response.body?.getReader();
1883
+ if (!reader) return new Uint8Array();
1884
+ const chunks = [];
1885
+ let length = 0;
1886
+ try {
1887
+ while (true) {
1888
+ const { done, value } = await reader.read();
1889
+ if (done) break;
1890
+ length += value.length;
1891
+ if (length > limit) {
1892
+ await reader.cancel();
1893
+ throw new AudioAPIError(
1894
+ "Audio API response exceeds the size limit",
1895
+ "invalid_response"
1896
+ );
1897
+ }
1898
+ chunks.push(value);
1899
+ }
1900
+ } finally {
1901
+ reader.releaseLock();
1902
+ }
1903
+ const data = new Uint8Array(length);
1904
+ let offset = 0;
1905
+ for (const chunk of chunks) {
1906
+ data.set(chunk, offset);
1907
+ offset += chunk.length;
1908
+ }
1909
+ return data;
1910
+ }
1911
+ function transport(config) {
1912
+ return async (path, body, signal) => {
1913
+ signal?.throwIfAborted();
1914
+ const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
1915
+ if (!apiKey) invalid("OpenAI audio requires an API key on the server");
1916
+ const headers = {
1917
+ Authorization: `Bearer ${apiKey}`
1918
+ };
1919
+ if (typeof body === "string") headers["Content-Type"] = "application/json";
1920
+ if (config.organization)
1921
+ headers["OpenAI-Organization"] = config.organization;
1922
+ if (config.project) headers["OpenAI-Project"] = config.project;
1923
+ let response;
1924
+ try {
1925
+ response = await (config.fetch ?? globalThis.fetch)(
1926
+ `${(config.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")}${path}`,
1927
+ { method: "POST", headers, body, signal, redirect: "error" }
1928
+ );
1929
+ } catch {
1930
+ signal?.throwIfAborted();
1931
+ throw new AudioAPIError("OpenAI audio request failed", "network_error");
1932
+ }
1933
+ if (!response.ok) {
1934
+ await response.body?.cancel();
1935
+ throw new AudioAPIError(
1936
+ `OpenAI audio request failed (HTTP ${response.status})`,
1937
+ "provider_error",
1938
+ response.status,
1939
+ response.headers.get("x-request-id") ?? void 0
1940
+ );
1941
+ }
1942
+ return response;
1943
+ };
1944
+ }
1945
+ function openaiSpeech(modelId = "gpt-4o-mini-tts", config = {}) {
1946
+ const request = transport(config);
1947
+ return {
1948
+ provider: "openai",
1949
+ modelId,
1950
+ async doGenerate(options) {
1951
+ const {
1952
+ text,
1953
+ voice = "alloy",
1954
+ outputFormat = "mp3",
1955
+ speed,
1956
+ instructions,
1957
+ signal
1958
+ } = options;
1959
+ signal?.throwIfAborted();
1960
+ providerOptions(options.providerOptions, []);
1961
+ if (typeof text !== "string" || !text.trim() || text.length > 4096)
1962
+ invalid("Speech text must contain 1\u20134096 characters");
1963
+ if (!Object.prototype.hasOwnProperty.call(formats, outputFormat))
1964
+ unsupported("Unsupported OpenAI speech output format");
1965
+ if (typeof voice !== "string" || !voice.trim())
1966
+ invalid("Speech voice must not be empty");
1967
+ if (speed !== void 0 && (!Number.isFinite(speed) || speed < 0.25 || speed > 4))
1968
+ invalid("Speech speed must be between 0.25 and 4");
1969
+ if (instructions !== void 0) {
1970
+ if (typeof instructions !== "string" || instructions.length > 4096)
1971
+ invalid(
1972
+ "Speech instructions must be a string of at most 4096 characters"
1973
+ );
1974
+ if (modelId === "tts-1" || modelId === "tts-1-hd")
1975
+ unsupported("Instructions are not supported by tts-1 or tts-1-hd");
1976
+ }
1977
+ const response = await request(
1978
+ "/audio/speech",
1979
+ JSON.stringify({
1980
+ model: modelId,
1981
+ input: text,
1982
+ voice,
1983
+ response_format: outputFormat,
1984
+ speed,
1985
+ instructions
1986
+ }),
1987
+ signal
1988
+ );
1989
+ const mediaType = response.headers.get("content-type")?.split(";")[0].trim();
1990
+ if (mediaType && !mediaType.startsWith("audio/") && mediaType !== "application/octet-stream") {
1991
+ await response.body?.cancel();
1992
+ throw new AudioAPIError(
1993
+ "Speech endpoint returned a non-audio response",
1994
+ "invalid_response"
1995
+ );
1996
+ }
1997
+ const data = await readBody(response, MAX_AUDIO);
1998
+ signal?.throwIfAborted();
1999
+ if (!data.length || !hasAudioHeader(data, outputFormat))
2000
+ throw new AudioAPIError(
2001
+ "Speech endpoint returned empty or invalid audio",
2002
+ "invalid_response"
2003
+ );
2004
+ return {
2005
+ audio: {
2006
+ uint8Array: data,
2007
+ get base64() {
2008
+ return Buffer.from(data).toString("base64");
2009
+ },
2010
+ mediaType: formats[outputFormat],
2011
+ format: outputFormat
2012
+ },
2013
+ warnings: [],
2014
+ response: {
2015
+ provider: "openai",
2016
+ modelId,
2017
+ requestId: response.headers.get("x-request-id") ?? void 0,
2018
+ status: response.status
2019
+ }
2020
+ };
2021
+ }
2022
+ };
2023
+ }
2024
+ async function audioBytes(options) {
2025
+ const blob = typeof Blob !== "undefined" && options.audio instanceof Blob ? options.audio : void 0;
2026
+ if (blob && blob.size > MAX_AUDIO)
2027
+ invalid("Transcription file exceeds 25 MB");
2028
+ const declared = options.mediaType?.split(";")[0].trim();
2029
+ const blobType = blob?.type.split(";")[0].trim();
2030
+ if (declared && blobType && declared !== blobType)
2031
+ invalid("Transcription mediaType does not match the Blob type");
2032
+ const mediaType = declared || blobType || "";
2033
+ const extension = extensions[mediaType];
2034
+ if (!extension)
2035
+ unsupported(
2036
+ "Unsupported transcription media type; provide an encoded audio file"
2037
+ );
2038
+ let bytes;
2039
+ if (blob) bytes = new Uint8Array(await blob.arrayBuffer());
2040
+ else if (options.audio instanceof Uint8Array) bytes = options.audio;
2041
+ else if (options.audio instanceof ArrayBuffer)
2042
+ bytes = new Uint8Array(options.audio);
2043
+ else if (typeof options.audio === "string") {
2044
+ const base64 = options.audio;
2045
+ if (base64.length > MAX_AUDIO * 4 / 3 + 4)
2046
+ invalid("Transcription file exceeds 25 MB");
2047
+ if (!base64 || base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64))
2048
+ invalid(
2049
+ "Audio must be bytes, a Blob, or valid base64; URLs are not fetched"
2050
+ );
2051
+ bytes = Buffer.from(base64, "base64");
2052
+ if (Buffer.from(bytes).toString("base64") !== base64)
2053
+ invalid("Invalid audio base64");
2054
+ } else invalid("Audio must be bytes, a Blob, or base64");
2055
+ if (!bytes.length || bytes.length > MAX_AUDIO)
2056
+ invalid("Transcription file must contain 1 byte to 25 MB");
2057
+ if (!hasAudioHeader(bytes, extension))
2058
+ invalid("Audio header does not match the declared media type");
2059
+ return { bytes, mediaType, extension };
2060
+ }
2061
+ function openaiTranscription(modelId = "gpt-4o-mini-transcribe", config = {}) {
2062
+ const request = transport(config);
2063
+ return {
2064
+ provider: "openai",
2065
+ modelId,
2066
+ async doTranscribe(options) {
2067
+ options.signal?.throwIfAborted();
2068
+ if (modelId.includes("realtime"))
2069
+ unsupported(
2070
+ "Realtime models require a realtime session; use a file transcription model"
2071
+ );
2072
+ const opts = providerOptions(options.providerOptions, [
2073
+ "timestampGranularities",
2074
+ "includeLogprobs",
2075
+ "chunkingStrategy"
2076
+ ]);
2077
+ const timestamps = opts.timestampGranularities;
2078
+ if (timestamps !== void 0 && (!Array.isArray(timestamps) || !timestamps.length || timestamps.some((x) => x !== "word" && x !== "segment")))
2079
+ invalid("timestampGranularities must contain word or segment");
2080
+ if (timestamps !== void 0 && modelId !== "whisper-1")
2081
+ unsupported("Timestamp granularities require whisper-1");
2082
+ if (opts.includeLogprobs !== void 0 && typeof opts.includeLogprobs !== "boolean")
2083
+ invalid("includeLogprobs must be boolean");
2084
+ if (opts.includeLogprobs && !/^gpt-4o-(mini-)?transcribe(?:-\d{4}-\d{2}-\d{2})?$/.test(modelId))
2085
+ unsupported(
2086
+ "Log probabilities require a supported gpt-4o transcription model"
2087
+ );
2088
+ const diarize = modelId === "gpt-4o-transcribe-diarize";
2089
+ if (opts.chunkingStrategy !== void 0 && opts.chunkingStrategy !== "auto")
2090
+ unsupported("Only automatic audio chunking is supported");
2091
+ if (diarize && options.prompt !== void 0)
2092
+ unsupported("Prompts are not supported by the diarization model");
2093
+ if (options.language !== void 0 && !/^[a-z]{2}$/.test(options.language))
2094
+ invalid("language must be a two-letter ISO-639-1 code");
2095
+ if (options.prompt !== void 0 && typeof options.prompt !== "string")
2096
+ invalid("Transcription prompt must be a string");
2097
+ const { bytes, mediaType, extension } = await audioBytes(options);
2098
+ options.signal?.throwIfAborted();
2099
+ const fileName = options.filename ?? `audio.${extension}`;
2100
+ if (!/^[^/\\\r\n\x00]+\.[a-z0-9]+$/i.test(fileName))
2101
+ invalid("Transcription filename must be a filename with an extension");
2102
+ const allowedExt = mediaType === "audio/mpeg" ? ["mp3", "mpeg", "mpga"] : mediaType === "audio/mp4" ? ["m4a", "mp4"] : [extension];
2103
+ if (!allowedExt.includes(fileName.split(".").at(-1).toLowerCase()))
2104
+ invalid("Transcription filename extension does not match mediaType");
2105
+ const form = new FormData();
2106
+ form.append(
2107
+ "file",
2108
+ new Blob([new Uint8Array(bytes)], { type: mediaType }),
2109
+ fileName
2110
+ );
2111
+ form.append("model", modelId);
2112
+ form.append(
2113
+ "response_format",
2114
+ diarize ? "diarized_json" : timestamps ? "verbose_json" : "json"
2115
+ );
2116
+ if (options.language) form.append("language", options.language);
2117
+ if (options.prompt) form.append("prompt", options.prompt);
2118
+ if (timestamps)
2119
+ for (const value2 of timestamps)
2120
+ form.append("timestamp_granularities[]", value2);
2121
+ if (opts.includeLogprobs) form.append("include[]", "logprobs");
2122
+ if (diarize || opts.chunkingStrategy)
2123
+ form.append("chunking_strategy", "auto");
2124
+ const response = await request(
2125
+ "/audio/transcriptions",
2126
+ form,
2127
+ options.signal
2128
+ );
2129
+ let value;
2130
+ try {
2131
+ value = JSON.parse(
2132
+ Buffer.from(await readBody(response, 5 * 1024 * 1024)).toString(
2133
+ "utf8"
2134
+ )
2135
+ );
2136
+ } catch (error) {
2137
+ options.signal?.throwIfAborted();
2138
+ if (error instanceof AudioAPIError) throw error;
2139
+ throw new AudioAPIError(
2140
+ "Transcription endpoint returned invalid JSON",
2141
+ "invalid_response"
2142
+ );
2143
+ }
2144
+ options.signal?.throwIfAborted();
2145
+ if (!value || typeof value.text !== "string")
2146
+ throw new AudioAPIError(
2147
+ "Transcription response is missing text",
2148
+ "invalid_response"
2149
+ );
2150
+ function timed(items, word = false) {
2151
+ if (items === void 0) return void 0;
2152
+ if (!Array.isArray(items) || items.some(
2153
+ (x) => !x || typeof x[word ? "word" : "text"] !== "string" || !Number.isFinite(x.start) || !Number.isFinite(x.end) || x.start < 0 || x.end < x.start
2154
+ ))
2155
+ throw new AudioAPIError(
2156
+ "Invalid transcription timestamps",
2157
+ "invalid_response"
2158
+ );
2159
+ return items.map(
2160
+ (x) => word ? { word: x.word, start: x.start, end: x.end } : {
2161
+ text: x.text,
2162
+ start: x.start,
2163
+ end: x.end,
2164
+ ...typeof x.speaker === "string" ? { speaker: x.speaker } : {}
2165
+ }
2166
+ );
2167
+ }
2168
+ return {
2169
+ text: value.text,
2170
+ language: typeof value.language === "string" ? value.language : void 0,
2171
+ durationSeconds: typeof value.duration === "number" ? value.duration : void 0,
2172
+ segments: timed(value.segments),
2173
+ words: timed(value.words, true),
2174
+ usage: value.usage,
2175
+ providerMetadata: {
2176
+ openai: { ...value.logprobs ? { logprobs: value.logprobs } : {} }
2177
+ },
2178
+ warnings: [],
2179
+ response: {
2180
+ provider: "openai",
2181
+ modelId,
2182
+ status: response.status,
2183
+ requestId: response.headers.get("x-request-id") ?? void 0
2184
+ }
2185
+ };
2186
+ }
2187
+ };
2188
+ }
2189
+
1412
2190
  // src/providers/openai/index.ts
1413
2191
  var OPENAI_MODELS2 = {
1414
2192
  // GPT-4o series
@@ -1525,6 +2303,8 @@ function createOpenAI(config = {}) {
1525
2303
  model: modelId,
1526
2304
  baseUrl: config.baseUrl,
1527
2305
  reasoningEffort: config.reasoningEffort,
2306
+ api: config.api,
2307
+ responses: config.responses,
1528
2308
  parameterPolicy: config.parameterPolicy,
1529
2309
  onWarning: config.onWarning
1530
2310
  });
@@ -1558,5 +2338,7 @@ exports.createOpenAI = createOpenAI;
1558
2338
  exports.createOpenAIModel = openai;
1559
2339
  exports.createOpenAIProvider = createOpenAIProvider;
1560
2340
  exports.openai = openai;
2341
+ exports.openaiSpeech = openaiSpeech;
2342
+ exports.openaiTranscription = openaiTranscription;
1561
2343
  //# sourceMappingURL=index.js.map
1562
2344
  //# sourceMappingURL=index.js.map