@warlock.js/ai-openai 5.30.0 → 5.32.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 (58) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +3 -3
  3. package/cjs/index.cjs +715 -51
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/config.type.d.mts +13 -13
  6. package/esm/config.type.d.mts.map +1 -1
  7. package/esm/embedder.d.mts +1 -3
  8. package/esm/embedder.d.mts.map +1 -1
  9. package/esm/embedder.mjs.map +1 -1
  10. package/esm/image.d.mts +1 -3
  11. package/esm/image.d.mts.map +1 -1
  12. package/esm/index.d.mts +4 -1
  13. package/esm/index.mjs +3 -1
  14. package/esm/kimi-tools.d.mts +69 -0
  15. package/esm/kimi-tools.d.mts.map +1 -0
  16. package/esm/kimi-tools.mjs +256 -0
  17. package/esm/kimi-tools.mjs.map +1 -0
  18. package/esm/known-pricing.d.mts +39 -0
  19. package/esm/known-pricing.d.mts.map +1 -0
  20. package/esm/known-pricing.mjs +121 -0
  21. package/esm/known-pricing.mjs.map +1 -0
  22. package/esm/model.mjs +82 -23
  23. package/esm/model.mjs.map +1 -1
  24. package/esm/provider-options.d.mts +13 -0
  25. package/esm/provider-options.d.mts.map +1 -0
  26. package/esm/provider-options.mjs +56 -0
  27. package/esm/provider-options.mjs.map +1 -0
  28. package/esm/responses-model.mjs +125 -19
  29. package/esm/responses-model.mjs.map +1 -1
  30. package/esm/sdk.d.mts +4 -5
  31. package/esm/sdk.d.mts.map +1 -1
  32. package/esm/sdk.mjs +5 -3
  33. package/esm/sdk.mjs.map +1 -1
  34. package/esm/speech.d.mts +1 -3
  35. package/esm/speech.d.mts.map +1 -1
  36. package/esm/transcription.d.mts +1 -3
  37. package/esm/transcription.d.mts.map +1 -1
  38. package/esm/transcription.mjs.map +1 -1
  39. package/esm/utils/build-usage.mjs +14 -2
  40. package/esm/utils/build-usage.mjs.map +1 -1
  41. package/esm/utils/hosted-output.mjs +22 -0
  42. package/esm/utils/hosted-output.mjs.map +1 -0
  43. package/esm/utils/index.mjs +2 -2
  44. package/esm/utils/map-responses-status.mjs.map +1 -1
  45. package/esm/utils/prompt-cache-params.mjs +25 -6
  46. package/esm/utils/prompt-cache-params.mjs.map +1 -1
  47. package/esm/utils/reasoning-replay.mjs +23 -1
  48. package/esm/utils/reasoning-replay.mjs.map +1 -1
  49. package/esm/utils/structured-output.mjs +1 -1
  50. package/esm/utils/structured-output.mjs.map +1 -1
  51. package/esm/utils/to-openai-messages.mjs.map +1 -1
  52. package/esm/utils/to-openai-tools.mjs.map +1 -1
  53. package/esm/utils/wrap-openai-error.mjs +6 -1
  54. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  55. package/llms-full.txt +235 -37
  56. package/llms.txt +1 -1
  57. package/package.json +3 -3
  58. package/skills/setup-openai/SKILL.md +235 -37
@@ -4,18 +4,33 @@ import { mapResponsesFinishReason, toResponseFailure } from "./utils/map-respons
4
4
  import { buildPromptCacheParams } from "./utils/prompt-cache-params.mjs";
5
5
  import { toOpenAIReasoningEffort } from "./utils/reasoning-effort.mjs";
6
6
  import { inferStructuredOutput, planStructuredOutput } from "./utils/structured-output.mjs";
7
- import { attachReasoningReplay } from "./utils/reasoning-replay.mjs";
7
+ import { attachReasoningReplay, pickTurnReasoning } from "./utils/reasoning-replay.mjs";
8
8
  import { toResponsesInput } from "./utils/to-responses-input.mjs";
9
9
  import { toResponsesToolChoice, toResponsesTools } from "./utils/to-responses-tools.mjs";
10
10
  import "./utils/index.mjs";
11
+ import { mergeProviderBody } from "./provider-options.mjs";
11
12
  import { inferReasoningCapability } from "./known-reasoning-models.mjs";
12
13
  import { inferVisionCapability } from "./known-vision-models.mjs";
13
- import { InvalidRequestError, ProviderError, safeJsonParse } from "@warlock.js/ai";
14
+ import { hostedOutput } from "./utils/hosted-output.mjs";
15
+ import { InvalidRequestError, ProviderError, resolveStreamChunks, safeJsonParse, selectProviderTools } from "@warlock.js/ai";
14
16
  import { log } from "@warlock.js/logger";
15
17
 
16
18
  //#region ../../packages/ai-openai/src/responses-model.ts
17
19
  const LOG_MODULE = "ai.openai";
18
20
  /**
21
+ * Responses output item types of built-in tools the CALLER must execute
22
+ * (installed SDK `ResponseOutputItem` union). The adapter maps only
23
+ * `function_call` to a neutral tool call, so these fail closed.
24
+ */
25
+ const CLIENT_RUN_ITEM_TYPES = /* @__PURE__ */ new Set([
26
+ "computer_call",
27
+ "custom_tool_call",
28
+ "local_shell_call",
29
+ "shell_call",
30
+ "apply_patch_call",
31
+ "mcp_approval_request"
32
+ ]);
33
+ /**
19
34
  * OpenAI Responses API implementation of `ModelContract` (`api: "responses"`).
20
35
  *
21
36
  * **Role.** A sibling of `OpenAIModel` that speaks `POST /v1/responses`
@@ -42,7 +57,17 @@ const LOG_MODULE = "ai.openai";
42
57
  *
43
58
  * **Streaming.** `stream()` uses `responses.create({ stream: true })` and yields
44
59
  * the same chunk sequence as the Chat path: text `delta`s, then the
45
- * `tool-call`s, then one `done`.
60
+ * `tool-call`s, then one `done`. With `streamChunks` it also yields
61
+ * `reasoning` (reasoning summary / reasoning text deltas) and `tool-args`
62
+ * (`response.function_call_arguments.delta`).
63
+ *
64
+ * **Provider-defined tools.** `ai.providerTool({ provider: "openai" })`
65
+ * definitions are appended to `tools` verbatim. Hosted tools run inside the
66
+ * response; their output items (`web_search_call`, …) are retained in
67
+ * `providerMetadata.openai.output` for inspection and are not replayed
68
+ * (with `store: false` the next turn only needs the answer text), and a
69
+ * reasoning item that led into one is not replayed either (see
70
+ * `pickTurnReasoning`).
46
71
  */
47
72
  var OpenAIResponsesModel = class {
48
73
  constructor(client, config, provider = "openai") {
@@ -94,6 +119,7 @@ var OpenAIResponsesModel = class {
94
119
  hasToolCalls: toolCalls !== void 0
95
120
  });
96
121
  const usage = this.extractUsage(response.usage);
122
+ const providerMetadata = hostedOutput(usage, output, this.pricing);
97
123
  this.logger.debug(LOG_MODULE, "response", "call to responses.create succeeded", {
98
124
  finishReason,
99
125
  usage
@@ -102,9 +128,30 @@ var OpenAIResponsesModel = class {
102
128
  content,
103
129
  finishReason,
104
130
  usage,
131
+ ...providerMetadata ? { providerMetadata } : {},
105
132
  ...toolCalls ? { toolCalls } : {}
106
133
  };
107
134
  }
135
+ /** Count native Responses input tokens using the same converted input and tools. */
136
+ async countTokens(messages, options) {
137
+ const request = this.buildRequest(messages, options);
138
+ const body = {
139
+ model: request.model,
140
+ input: request.input,
141
+ instructions: request.instructions,
142
+ tools: request.tools,
143
+ tool_choice: request.tool_choice,
144
+ reasoning: request.reasoning,
145
+ text: request.text,
146
+ parallel_tool_calls: request.parallel_tool_calls,
147
+ truncation: request.truncation ?? void 0
148
+ };
149
+ try {
150
+ return (await this.client.responses.inputTokens.count(body, options?.signal ? { signal: options.signal } : void 0)).input_tokens;
151
+ } catch (thrown) {
152
+ throw this.logAndWrap(thrown);
153
+ }
154
+ }
108
155
  /**
109
156
  * Incremental streaming completion via `responses.create({ stream: true })`
110
157
  * (the SDK's `Stream<ResponseStreamEvent>`, `responses.d.ts:55`). Yields the
@@ -139,7 +186,9 @@ var OpenAIResponsesModel = class {
139
186
  throw this.logAndWrap(thrown);
140
187
  }
141
188
  const toolCalls = [];
142
- const reasoningItems = [];
189
+ const outputItems = [];
190
+ const want = resolveStreamChunks(options);
191
+ const streamingCalls = /* @__PURE__ */ new Map();
143
192
  let terminal;
144
193
  try {
145
194
  for await (const event of events) {
@@ -150,10 +199,36 @@ var OpenAIResponsesModel = class {
150
199
  };
151
200
  continue;
152
201
  }
202
+ if (event.type === "response.reasoning_summary_text.delta" || event.type === "response.reasoning_text.delta") {
203
+ if (want.reasoning && event.delta) yield {
204
+ type: "reasoning",
205
+ delta: event.delta
206
+ };
207
+ continue;
208
+ }
209
+ if (event.type === "response.output_item.added") {
210
+ const { item } = event;
211
+ if (item.type === "function_call" && item.id) streamingCalls.set(item.id, {
212
+ callId: item.call_id,
213
+ name: item.name
214
+ });
215
+ continue;
216
+ }
217
+ if (event.type === "response.function_call_arguments.delta") {
218
+ const call = streamingCalls.get(event.item_id);
219
+ if (want.toolArgs && call && event.delta) yield {
220
+ type: "tool-args",
221
+ callId: call.callId,
222
+ name: call.name,
223
+ delta: event.delta
224
+ };
225
+ continue;
226
+ }
153
227
  if (event.type === "response.output_item.done") {
154
228
  const { item } = event;
155
- if (item.type === "reasoning") reasoningItems.push(item);
156
- else if (item.type === "function_call" && item.status !== "incomplete") toolCalls.push(this.toToolCall(item));
229
+ this.assertServerRunItem(item);
230
+ outputItems.push(item);
231
+ if (item.type === "function_call" && item.status !== "incomplete") toolCalls.push(this.toToolCall(item));
157
232
  continue;
158
233
  }
159
234
  if (event.type === "response.completed" || event.type === "response.incomplete") {
@@ -174,7 +249,7 @@ var OpenAIResponsesModel = class {
174
249
  } catch (thrown) {
175
250
  throw this.logAndWrap(thrown);
176
251
  }
177
- for (const call of attachReasoningReplay(toolCalls, this.name, reasoningItems)) yield {
252
+ for (const call of attachReasoningReplay(toolCalls, this.name, pickTurnReasoning(outputItems))) yield {
178
253
  type: "tool-call",
179
254
  id: call.id,
180
255
  name: call.name,
@@ -187,6 +262,8 @@ var OpenAIResponsesModel = class {
187
262
  hasToolCalls: toolCalls.length > 0
188
263
  });
189
264
  const usage = this.extractUsage(terminal.usage);
265
+ const output = terminal.output?.length ? terminal.output : outputItems;
266
+ const providerMetadata = hostedOutput(usage, output, this.pricing);
190
267
  this.logger.debug(LOG_MODULE, "response", "Streaming call to responses.create succeeded", {
191
268
  finishReason,
192
269
  usage
@@ -194,7 +271,8 @@ var OpenAIResponsesModel = class {
194
271
  yield {
195
272
  type: "done",
196
273
  finishReason,
197
- usage
274
+ usage,
275
+ ...providerMetadata ? { providerMetadata } : {}
198
276
  };
199
277
  }
200
278
  /**
@@ -212,16 +290,16 @@ var OpenAIResponsesModel = class {
212
290
  */
213
291
  buildRequest(messages, options) {
214
292
  const { instructions, input } = toResponsesInput(messages, this.name);
215
- const tools = toResponsesTools(options?.tools);
293
+ const tools = this.buildTools(options);
216
294
  const toolChoice = tools ? toResponsesToolChoice(options?.toolChoice) : void 0;
217
295
  const maxTokens = options?.maxTokens ?? this.config.maxTokens;
218
296
  const temperature = options?.temperature ?? this.config.temperature;
219
- return {
297
+ return mergeProviderBody({
220
298
  model: this.name,
221
299
  ...instructions !== void 0 ? { instructions } : {},
222
300
  input,
223
301
  store: false,
224
- include: ["reasoning.encrypted_content"],
302
+ include: ["reasoning.encrypted_content", ...tools?.some((tool) => tool.type === "web_search" || tool.type === "web_search_2025_08_26" || tool.type === "web_search_preview" || tool.type === "web_search_preview_2025_03_11") ? ["web_search_call.action.sources"] : []],
225
303
  ...maxTokens !== void 0 ? { max_output_tokens: maxTokens } : {},
226
304
  ...!this.capabilities.reasoning && temperature !== void 0 ? { temperature } : {},
227
305
  ...tools ? { tools } : {},
@@ -229,7 +307,24 @@ var OpenAIResponsesModel = class {
229
307
  ...this.buildTextFormat(options?.responseSchema),
230
308
  ...this.buildReasoningParams(options?.reasoning),
231
309
  ...buildPromptCacheParams(this.provider, this.config)
232
- };
310
+ }, options);
311
+ }
312
+ /**
313
+ * The request `tools`: function tools (flat, `strict: false`), then the
314
+ * `ai.providerTool({ provider: "openai" })` definitions verbatim — the
315
+ * Responses built-in tools (`web_search`, `file_search`,
316
+ * `code_interpreter`, `image_generation`, `mcp`, …). Other providers'
317
+ * definitions are skipped with a warning. `undefined` when there are none.
318
+ */
319
+ buildTools(options) {
320
+ const { tools, definitions } = selectProviderTools(options, {
321
+ accepts: [this.provider],
322
+ module: LOG_MODULE,
323
+ model: this.name,
324
+ logger: this.logger
325
+ });
326
+ const mapped = [...toResponsesTools(tools) ?? [], ...definitions];
327
+ return mapped.length > 0 ? mapped : void 0;
233
328
  }
234
329
  /**
235
330
  * Structured output on Responses lives in `text.format`, and `name` /
@@ -266,12 +361,8 @@ var OpenAIResponsesModel = class {
266
361
  readOutput(output) {
267
362
  let content = "";
268
363
  const toolCalls = [];
269
- const reasoningItems = [];
270
364
  for (const item of output) {
271
- if (item.type === "reasoning") {
272
- reasoningItems.push(item);
273
- continue;
274
- }
365
+ this.assertServerRunItem(item);
275
366
  if (item.type === "message") {
276
367
  for (const part of item.content) content += part.type === "refusal" ? part.refusal : part.text;
277
368
  continue;
@@ -280,9 +371,24 @@ var OpenAIResponsesModel = class {
280
371
  }
281
372
  return {
282
373
  content,
283
- toolCalls: toolCalls.length > 0 ? attachReasoningReplay(toolCalls, this.name, reasoningItems) : void 0
374
+ toolCalls: toolCalls.length > 0 ? attachReasoningReplay(toolCalls, this.name, pickTurnReasoning(output)) : void 0
284
375
  };
285
376
  }
377
+ /**
378
+ * Fail closed on a built-in tool call that YOUR code would have to run
379
+ * (`shell_call`, `apply_patch_call`, `computer_call`, …, from a
380
+ * provider-defined client tool). These items have their own output item
381
+ * types that this adapter does not map, so the turn would otherwise end
382
+ * silently with the call dropped. Hosted (server-run) tool items pass.
383
+ */
384
+ assertServerRunItem(item) {
385
+ if (!CLIENT_RUN_ITEM_TYPES.has(item.type)) return;
386
+ throw new InvalidRequestError(`OpenAI returned a "${item.type}" item: that built-in tool is executed by the caller, which the Responses adapter does not support. Use OpenAI's server-run built-in tools (web_search, file_search, code_interpreter, image_generation, mcp without approval) with ai.providerTool(), or a function tool from ai.tool().`, { context: {
387
+ provider: this.provider,
388
+ model: this.name,
389
+ itemType: item.type
390
+ } });
391
+ }
286
392
  /** One completed `function_call` item as a neutral tool call (`call_id` -> `id`). */
287
393
  toToolCall(item) {
288
394
  return {
@@ -311,7 +417,7 @@ var OpenAIResponsesModel = class {
311
417
  cachedTokens: raw.input_tokens_details?.cached_tokens,
312
418
  cacheWriteTokens: raw.input_tokens_details?.cache_write_tokens,
313
419
  reasoningTokens: raw.output_tokens_details?.reasoning_tokens
314
- });
420
+ }, this.pricing);
315
421
  }
316
422
  /** Wrap (idempotently), log and return a failure for the caller to throw. */
317
423
  logAndWrap(thrown) {
@@ -1 +1 @@
1
- {"version":3,"file":"responses-model.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/responses-model.ts"],"sourcesContent":["import {\n InvalidRequestError,\n ProviderError,\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIModelConfig } from \"./config.type\";\nimport { inferReasoningCapability } from \"./known-reasoning-models\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport {\n attachReasoningReplay,\n buildPromptCacheParams,\n buildUsage,\n inferStructuredOutput,\n mapResponsesFinishReason,\n planStructuredOutput,\n toOpenAIReasoningEffort,\n toResponseFailure,\n toResponsesInput,\n toResponsesToolChoice,\n toResponsesTools,\n wrapOpenAIError,\n} from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/**\n * OpenAI Responses API implementation of `ModelContract` (`api: \"responses\"`).\n *\n * **Role.** A sibling of `OpenAIModel` that speaks `POST /v1/responses`\n * instead of Chat Completions. Its point is letting a tool-using agent run a\n * reasoning model with reasoning ON: the Chat path has to force\n * `reasoning_effort: \"none\"` whenever tools are attached, Responses does not.\n *\n * **Why a separate class.** Only `OpenAISDK.model()` constructs it, and only\n * for the direct `\"openai\"` provider. The wrapper packages (deepseek, groq,\n * xai, mistral) build `OpenAIModel` and cannot reach this class, so an\n * endpoint that does not implement `/v1/responses` is never sent there. The\n * constructor re-checks the provider label as a second lock.\n *\n * **Stateless.** Every request sends `store: false`; nothing is kept on the\n * server and no `previous_response_id` is used. Full history is re-sent each\n * call, exactly like the Chat path.\n *\n * **Reasoning replay.** Every request asks for\n * `include: [\"reasoning.encrypted_content\"]`. The `reasoning` items of a\n * tool-calling turn are stored on the FIRST tool call as\n * `providerMetadata.openaiResponses = { model, reasoningItems }` and sent back\n * before that turn's `function_call` items on the next request (only when the\n * stored model equals the current one). Text-only turns are not replayed.\n *\n * **Streaming.** `stream()` uses `responses.create({ stream: true })` and yields\n * the same chunk sequence as the Chat path: text `delta`s, then the\n * `tool-call`s, then one `done`.\n */\nexport class OpenAIResponsesModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: OpenAI;\n private readonly config: OpenAIModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIModelConfig, provider: string = \"openai\") {\n if (provider !== \"openai\") {\n throw new InvalidRequestError(\n `api: \"responses\" is supported only on the direct \"openai\" provider; got provider \"${provider}\".`,\n { context: { provider, model: config.name, api: \"responses\" } },\n );\n }\n\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? inferStructuredOutput(config.responseFormat),\n vision: config.vision ?? inferVisionCapability(config.name),\n reasoning: config.reasoning ?? inferReasoningCapability(config.name),\n // Read-side cache accounting (`cachedTokens`, `cacheWriteTokens`) works\n // and `prompt_cache_key` is forwarded. Explicit write breakpoints are not\n // mapped onto Responses yet, so a `cacheControl.breakpoints` hint is\n // ignored rather than rejected.\n promptCaching: true,\n // PDF and audio input are off under Responses in this adapter: the\n // `input_file` / `input_audio` field-level mapping is unverified. The\n // `pdf` / `audio` config flags are deliberately NOT honoured here.\n pdf: false,\n audio: false,\n };\n }\n\n /**\n * Single-shot completion against `client.responses.create`. Sends the full\n * history as Responses `input` items, walks `response.output`, and reshapes\n * it into a vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to responses.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: OpenAI.Responses.Response;\n\n try {\n response = await this.client.responses.create(\n this.buildRequest(messages, options),\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n if (response.status === \"failed\") {\n throw this.logAndWrap(toResponseFailure(response, this.name));\n }\n\n const output = Array.isArray(response.output) ? response.output : [];\n const { content, toolCalls } = this.readOutput(output);\n const finishReason = mapResponsesFinishReason({\n status: response.status,\n incomplete_details: response.incomplete_details,\n hasToolCalls: toolCalls !== undefined,\n });\n const usage = this.extractUsage(response.usage);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to responses.create succeeded\", {\n finishReason,\n usage,\n });\n\n return { content, finishReason, usage, ...(toolCalls ? { toolCalls } : {}) };\n }\n\n /**\n * Incremental streaming completion via `responses.create({ stream: true })`\n * (the SDK's `Stream<ResponseStreamEvent>`, `responses.d.ts:55`). Yields the\n * same neutral `ModelStreamChunk` sequence as the Chat path:\n *\n * - `response.output_text.delta` / `response.refusal.delta` -> `delta`.\n * - `response.output_item.done` with a completed `function_call` item ->\n * buffered; `reasoning` items are captured for replay. The `done` copy is\n * used because the `added` copy may be incomplete (`responses.d.ts:5711`).\n * - After the loop: the buffered `tool-call`s (the first carries the\n * replay metadata), then a single `done` with the finish reason and usage\n * from `response.completed` / `response.incomplete`.\n * - `response.failed` and `error` events throw a typed `AIError`; a stream\n * that ends with no terminal event throws `ProviderError`.\n * - Abort goes through the request `signal`; the SDK rejects the read and\n * the error is wrapped like any other.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to responses.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let events: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>;\n\n try {\n events = await this.client.responses.create(\n { ...this.buildRequest(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const toolCalls: ModelToolCallRequest[] = [];\n const reasoningItems: OpenAI.Responses.ResponseReasoningItem[] = [];\n let terminal: OpenAI.Responses.Response | undefined;\n\n try {\n for await (const event of events) {\n if (event.type === \"response.output_text.delta\" || event.type === \"response.refusal.delta\") {\n if (event.delta) {\n yield { type: \"delta\", content: event.delta };\n }\n\n continue;\n }\n\n if (event.type === \"response.output_item.done\") {\n const { item } = event;\n\n if (item.type === \"reasoning\") {\n reasoningItems.push(item);\n } else if (item.type === \"function_call\" && item.status !== \"incomplete\") {\n toolCalls.push(this.toToolCall(item));\n }\n\n continue;\n }\n\n if (event.type === \"response.completed\" || event.type === \"response.incomplete\") {\n terminal = event.response;\n continue;\n }\n\n if (event.type === \"response.failed\") {\n throw toResponseFailure(event.response, this.name);\n }\n\n if (event.type === \"error\") {\n throw wrapOpenAIError({ code: event.code, message: event.message, param: event.param });\n }\n }\n\n if (terminal === undefined) {\n throw new ProviderError(\"OpenAI Responses stream ended before response.completed.\", {\n context: { provider: this.provider, model: this.name },\n });\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n for (const call of attachReasoningReplay(toolCalls, this.name, reasoningItems)) {\n yield {\n type: \"tool-call\",\n id: call.id,\n name: call.name,\n input: call.input,\n ...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),\n };\n }\n\n const finishReason = mapResponsesFinishReason({\n status: terminal.status,\n incomplete_details: terminal.incomplete_details,\n hasToolCalls: toolCalls.length > 0,\n });\n const usage = this.extractUsage(terminal.usage);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to responses.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the `responses.create` body.\n *\n * - System prompt -> `instructions`; history -> `input` items.\n * - `store: false` always (the API default is `true` when omitted), plus\n * `include: [\"reasoning.encrypted_content\"]` for stateless reasoning replay.\n * - `max_output_tokens` caps reasoning and visible output together.\n * - `temperature` only for non-reasoning models (same rule as Chat).\n * - `tools` flat with `strict: false`; `tool_choice` only with tools.\n * - `reasoning.effort` only when the caller gave one, through the same\n * clamp table as Chat. With no explicit effort the provider default\n * applies EVEN with tools attached - that is the point of this API.\n */\n private buildRequest(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): OpenAI.Responses.ResponseCreateParamsNonStreaming {\n const { instructions, input } = toResponsesInput(messages, this.name);\n const tools = toResponsesTools(options?.tools);\n const toolChoice = tools ? toResponsesToolChoice(options?.toolChoice) : undefined;\n const maxTokens = options?.maxTokens ?? this.config.maxTokens;\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n model: this.name,\n ...(instructions !== undefined ? { instructions } : {}),\n input,\n store: false,\n // Stateless replay: ask for the encrypted reasoning blobs so they can be\n // sent back on the next tool turn (decision 3).\n include: [\"reasoning.encrypted_content\"],\n ...(maxTokens !== undefined ? { max_output_tokens: maxTokens } : {}),\n ...(!this.capabilities.reasoning && temperature !== undefined ? { temperature } : {}),\n ...(tools ? { tools } : {}),\n ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),\n ...this.buildTextFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning),\n ...buildPromptCacheParams(this.provider, this.config),\n };\n }\n\n /**\n * Structured output on Responses lives in `text.format`, and `name` /\n * `schema` / `strict` sit directly on the format object (Chat nests them\n * under `json_schema`). The strict-or-downgrade decision is the shared\n * `planStructuredOutput`.\n */\n private buildTextFormat(responseSchema: Record<string, unknown> | undefined): {\n text?: OpenAI.Responses.ResponseTextConfig;\n } {\n const plan = planStructuredOutput(responseSchema, this.config.responseFormat);\n\n if (plan.mode === \"json_schema\") {\n return {\n text: { format: { type: \"json_schema\", name: \"response\", schema: plan.schema, strict: true } },\n };\n }\n\n if (plan.mode === \"json_object\") {\n return { text: { format: { type: \"json_object\" } } };\n }\n\n return {};\n }\n\n /** `reasoning.effort` for reasoning-capable models, only when the caller set one. */\n private buildReasoningParams(reasoning: ModelCallOptions[\"reasoning\"]): {\n reasoning?: { effort: ReturnType<typeof toOpenAIReasoningEffort> };\n } {\n if (!this.capabilities.reasoning || !reasoning?.effort) {\n return {};\n }\n\n return { reasoning: { effort: toOpenAIReasoningEffort(reasoning.effort) } };\n }\n\n /**\n * Walk `response.output`: `message` items contribute their `output_text`\n * text and any `refusal` text (surfaced as ordinary content, decision 5);\n * each completed `function_call` item becomes one tool call with\n * `id = call_id`. `reasoning` items carry no visible content and are\n * not content. A `function_call` the API marked `incomplete` (truncated\n * arguments) is skipped rather than dispatched with empty input.\n * `reasoning` items are captured and attached to the first tool call for\n * replay (see `attachReasoningReplay`).\n */\n private readOutput(output: OpenAI.Responses.ResponseOutputItem[]): {\n content: string;\n toolCalls: ModelToolCallRequest[] | undefined;\n } {\n let content = \"\";\n const toolCalls: ModelToolCallRequest[] = [];\n const reasoningItems: OpenAI.Responses.ResponseReasoningItem[] = [];\n\n for (const item of output) {\n if (item.type === \"reasoning\") {\n reasoningItems.push(item);\n continue;\n }\n\n if (item.type === \"message\") {\n for (const part of item.content) {\n content += part.type === \"refusal\" ? part.refusal : part.text;\n }\n\n continue;\n }\n\n if (item.type === \"function_call\" && item.status !== \"incomplete\") {\n toolCalls.push(this.toToolCall(item));\n }\n }\n\n return {\n content,\n toolCalls:\n toolCalls.length > 0\n ? attachReasoningReplay(toolCalls, this.name, reasoningItems)\n : undefined,\n };\n }\n\n /** One completed `function_call` item as a neutral tool call (`call_id` -> `id`). */\n private toToolCall(item: OpenAI.Responses.ResponseFunctionToolCall): ModelToolCallRequest {\n return {\n id: item.call_id,\n name: item.name,\n input: safeJsonParse<Record<string, unknown>>(item.arguments, {}),\n };\n }\n\n /**\n * Normalize `usage` (absent on some partials) into the neutral `Usage`.\n * `input_tokens_details.cached_tokens` -> `cachedTokens`,\n * `cache_write_tokens` -> `cacheWriteTokens`,\n * `output_tokens_details.reasoning_tokens` -> `reasoningTokens`. The\n * detail blocks are typed non-optional but read defensively.\n */\n private extractUsage(raw: OpenAI.Responses.ResponseUsage | undefined): Usage {\n if (!raw) {\n return { input: 0, output: 0, total: 0 };\n }\n\n return buildUsage({\n input: raw.input_tokens,\n output: raw.output_tokens,\n total: raw.total_tokens,\n cachedTokens: raw.input_tokens_details?.cached_tokens,\n cacheWriteTokens: raw.input_tokens_details?.cache_write_tokens,\n reasoningTokens: raw.output_tokens_details?.reasoning_tokens,\n });\n }\n\n /** Wrap (idempotently), log and return a failure for the caller to throw. */\n private logAndWrap(thrown: unknown): ReturnType<typeof wrapOpenAIError> {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnB,IAAa,uBAAb,MAA2D;CAUzD,AAAO,YAAY,QAAgB,QAA2B,WAAmB,UAAU;gBAFzD;EAGhC,IAAI,aAAa,UACf,MAAM,IAAI,oBACR,qFAAqF,SAAS,KAC9F,EAAE,SAAS;GAAE;GAAU,OAAO,OAAO;GAAM,KAAK;EAAY,EAAE,CAChE;EAGF,KAAK,SAAS;EACd,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;EACtB,KAAK,eAAe;GAClB,kBAAkB,OAAO,oBAAoB,sBAAsB,OAAO,cAAc;GACxF,QAAQ,OAAO,UAAU,sBAAsB,OAAO,IAAI;GAC1D,WAAW,OAAO,aAAa,yBAAyB,OAAO,IAAI;GAKnE,eAAe;GAIf,KAAK;GACL,OAAO;EACT;CACF;;;;;;;CAQA,MAAa,SAAS,UAAqB,SAAoD;EAC7F,KAAK,OAAO,MAAM,YAAY,WAAW,qCAAqC;GAC5E,OAAO,KAAK;GACZ,cAAc,SAAS;GACvB,WAAW;GACX,WAAW,SAAS,OAAO,UAAU;EACvC,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,UAAU,OACrC,KAAK,aAAa,UAAU,OAAO,GACnC,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,IAAI,SAAS,WAAW,UACtB,MAAM,KAAK,WAAW,kBAAkB,UAAU,KAAK,IAAI,CAAC;EAG9D,MAAM,SAAS,MAAM,QAAQ,SAAS,MAAM,IAAI,SAAS,SAAS,CAAC;EACnE,MAAM,EAAE,SAAS,cAAc,KAAK,WAAW,MAAM;EACrD,MAAM,eAAe,yBAAyB;GAC5C,QAAQ,SAAS;GACjB,oBAAoB,SAAS;GAC7B,cAAc,cAAc;EAC9B,CAAC;EACD,MAAM,QAAQ,KAAK,aAAa,SAAS,KAAK;EAE9C,KAAK,OAAO,MAAM,YAAY,YAAY,sCAAsC;GAC9E;GACA;EACF,CAAC;EAED,OAAO;GAAE;GAAS;GAAc;GAAO,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EAAG;CAC7E;;;;;;;;;;;;;;;;;;CAmBA,OAAc,OACZ,UACA,SACiC;EACjC,KAAK,OAAO,MAAM,YAAY,WAAW,+CAA+C;GACtF,OAAO,KAAK;GACZ,cAAc,SAAS;GACvB,WAAW;GACX,WAAW,SAAS,OAAO,UAAU;EACvC,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,SAAS,MAAM,KAAK,OAAO,UAAU,OACnC;IAAE,GAAG,KAAK,aAAa,UAAU,OAAO;IAAG,QAAQ;GAAK,GACxD,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,MAAM,YAAoC,CAAC;EAC3C,MAAM,iBAA2D,CAAC;EAClE,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,SAAS,QAAQ;IAChC,IAAI,MAAM,SAAS,gCAAgC,MAAM,SAAS,0BAA0B;KAC1F,IAAI,MAAM,OACR,MAAM;MAAE,MAAM;MAAS,SAAS,MAAM;KAAM;KAG9C;IACF;IAEA,IAAI,MAAM,SAAS,6BAA6B;KAC9C,MAAM,EAAE,SAAS;KAEjB,IAAI,KAAK,SAAS,aAChB,eAAe,KAAK,IAAI;UACnB,IAAI,KAAK,SAAS,mBAAmB,KAAK,WAAW,cAC1D,UAAU,KAAK,KAAK,WAAW,IAAI,CAAC;KAGtC;IACF;IAEA,IAAI,MAAM,SAAS,wBAAwB,MAAM,SAAS,uBAAuB;KAC/E,WAAW,MAAM;KACjB;IACF;IAEA,IAAI,MAAM,SAAS,mBACjB,MAAM,kBAAkB,MAAM,UAAU,KAAK,IAAI;IAGnD,IAAI,MAAM,SAAS,SACjB,MAAM,gBAAgB;KAAE,MAAM,MAAM;KAAM,SAAS,MAAM;KAAS,OAAO,MAAM;IAAM,CAAC;GAE1F;GAEA,IAAI,aAAa,QACf,MAAM,IAAI,cAAc,4DAA4D,EAClF,SAAS;IAAE,UAAU,KAAK;IAAU,OAAO,KAAK;GAAK,EACvD,CAAC;EAEL,SAAS,QAAQ;GACf,MAAM,KAAK,WAAW,MAAM;EAC9B;EAEA,KAAK,MAAM,QAAQ,sBAAsB,WAAW,KAAK,MAAM,cAAc,GAC3E,MAAM;GACJ,MAAM;GACN,IAAI,KAAK;GACT,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,GAAI,KAAK,mBAAmB,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;EAC7E;EAGF,MAAM,eAAe,yBAAyB;GAC5C,QAAQ,SAAS;GACjB,oBAAoB,SAAS;GAC7B,cAAc,UAAU,SAAS;EACnC,CAAC;EACD,MAAM,QAAQ,KAAK,aAAa,SAAS,KAAK;EAE9C,KAAK,OAAO,MAAM,YAAY,YAAY,gDAAgD;GACxF;GACA;EACF,CAAC;EAED,MAAM;GAAE,MAAM;GAAQ;GAAc;EAAM;CAC5C;;;;;;;;;;;;;;CAeA,AAAQ,aACN,UACA,SACmD;EACnD,MAAM,EAAE,cAAc,UAAU,iBAAiB,UAAU,KAAK,IAAI;EACpE,MAAM,QAAQ,iBAAiB,SAAS,KAAK;EAC7C,MAAM,aAAa,QAAQ,sBAAsB,SAAS,UAAU,IAAI;EACxE,MAAM,YAAY,SAAS,aAAa,KAAK,OAAO;EACpD,MAAM,cAAc,SAAS,eAAe,KAAK,OAAO;EAExD,OAAO;GACL,OAAO,KAAK;GACZ,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;GACrD;GACA,OAAO;GAGP,SAAS,CAAC,6BAA6B;GACvC,GAAI,cAAc,SAAY,EAAE,mBAAmB,UAAU,IAAI,CAAC;GAClE,GAAI,CAAC,KAAK,aAAa,aAAa,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;GACnF,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,GAAI,eAAe,SAAY,EAAE,aAAa,WAAW,IAAI,CAAC;GAC9D,GAAG,KAAK,gBAAgB,SAAS,cAAc;GAC/C,GAAG,KAAK,qBAAqB,SAAS,SAAS;GAC/C,GAAG,uBAAuB,KAAK,UAAU,KAAK,MAAM;EACtD;CACF;;;;;;;CAQA,AAAQ,gBAAgB,gBAEtB;EACA,MAAM,OAAO,qBAAqB,gBAAgB,KAAK,OAAO,cAAc;EAE5E,IAAI,KAAK,SAAS,eAChB,OAAO,EACL,MAAM,EAAE,QAAQ;GAAE,MAAM;GAAe,MAAM;GAAY,QAAQ,KAAK;GAAQ,QAAQ;EAAK,EAAE,EAC/F;EAGF,IAAI,KAAK,SAAS,eAChB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,EAAE,EAAE;EAGrD,OAAO,CAAC;CACV;;CAGA,AAAQ,qBAAqB,WAE3B;EACA,IAAI,CAAC,KAAK,aAAa,aAAa,CAAC,WAAW,QAC9C,OAAO,CAAC;EAGV,OAAO,EAAE,WAAW,EAAE,QAAQ,wBAAwB,UAAU,MAAM,EAAE,EAAE;CAC5E;;;;;;;;;;;CAYA,AAAQ,WAAW,QAGjB;EACA,IAAI,UAAU;EACd,MAAM,YAAoC,CAAC;EAC3C,MAAM,iBAA2D,CAAC;EAElE,KAAK,MAAM,QAAQ,QAAQ;GACzB,IAAI,KAAK,SAAS,aAAa;IAC7B,eAAe,KAAK,IAAI;IACxB;GACF;GAEA,IAAI,KAAK,SAAS,WAAW;IAC3B,KAAK,MAAM,QAAQ,KAAK,SACtB,WAAW,KAAK,SAAS,YAAY,KAAK,UAAU,KAAK;IAG3D;GACF;GAEA,IAAI,KAAK,SAAS,mBAAmB,KAAK,WAAW,cACnD,UAAU,KAAK,KAAK,WAAW,IAAI,CAAC;EAExC;EAEA,OAAO;GACL;GACA,WACE,UAAU,SAAS,IACf,sBAAsB,WAAW,KAAK,MAAM,cAAc,IAC1D;EACR;CACF;;CAGA,AAAQ,WAAW,MAAuE;EACxF,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,OAAO,cAAuC,KAAK,WAAW,CAAC,CAAC;EAClE;CACF;;;;;;;;CASA,AAAQ,aAAa,KAAwD;EAC3E,IAAI,CAAC,KACH,OAAO;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAGzC,OAAO,WAAW;GAChB,OAAO,IAAI;GACX,QAAQ,IAAI;GACZ,OAAO,IAAI;GACX,cAAc,IAAI,sBAAsB;GACxC,kBAAkB,IAAI,sBAAsB;GAC5C,iBAAiB,IAAI,uBAAuB;EAC9C,CAAC;CACH;;CAGA,AAAQ,WAAW,QAAqD;EACtE,MAAM,UAAU,gBAAgB,MAAM;EAEtC,KAAK,OAAO,MAAM,YAAY,SAAS,QAAQ,SAAS;GACtD,MAAM,QAAQ;GACd,SAAS,QAAQ;EACnB,CAAC;EAED,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"responses-model.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/responses-model.ts"],"sourcesContent":["import {\n InvalidRequestError,\n ProviderError,\n resolveStreamChunks,\n safeJsonParse,\n selectProviderTools,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport { hostedOutput } from \"./utils/hosted-output\";\nimport { mergeProviderBody } from \"./provider-options\";\nimport type { OpenAIModelConfig } from \"./config.type\";\nimport { inferReasoningCapability } from \"./known-reasoning-models\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport {\n attachReasoningReplay,\n buildPromptCacheParams,\n buildUsage,\n inferStructuredOutput,\n mapResponsesFinishReason,\n pickTurnReasoning,\n planStructuredOutput,\n toOpenAIReasoningEffort,\n toResponseFailure,\n toResponsesInput,\n toResponsesToolChoice,\n toResponsesTools,\n wrapOpenAIError,\n} from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/**\n * Responses output item types of built-in tools the CALLER must execute\n * (installed SDK `ResponseOutputItem` union). The adapter maps only\n * `function_call` to a neutral tool call, so these fail closed.\n */\nconst CLIENT_RUN_ITEM_TYPES: ReadonlySet<string> = new Set([\n \"computer_call\",\n \"custom_tool_call\",\n \"local_shell_call\",\n \"shell_call\",\n \"apply_patch_call\",\n \"mcp_approval_request\",\n]);\n\n/**\n * OpenAI Responses API implementation of `ModelContract` (`api: \"responses\"`).\n *\n * **Role.** A sibling of `OpenAIModel` that speaks `POST /v1/responses`\n * instead of Chat Completions. Its point is letting a tool-using agent run a\n * reasoning model with reasoning ON: the Chat path has to force\n * `reasoning_effort: \"none\"` whenever tools are attached, Responses does not.\n *\n * **Why a separate class.** Only `OpenAISDK.model()` constructs it, and only\n * for the direct `\"openai\"` provider. The wrapper packages (deepseek, groq,\n * xai, mistral) build `OpenAIModel` and cannot reach this class, so an\n * endpoint that does not implement `/v1/responses` is never sent there. The\n * constructor re-checks the provider label as a second lock.\n *\n * **Stateless.** Every request sends `store: false`; nothing is kept on the\n * server and no `previous_response_id` is used. Full history is re-sent each\n * call, exactly like the Chat path.\n *\n * **Reasoning replay.** Every request asks for\n * `include: [\"reasoning.encrypted_content\"]`. The `reasoning` items of a\n * tool-calling turn are stored on the FIRST tool call as\n * `providerMetadata.openaiResponses = { model, reasoningItems }` and sent back\n * before that turn's `function_call` items on the next request (only when the\n * stored model equals the current one). Text-only turns are not replayed.\n *\n * **Streaming.** `stream()` uses `responses.create({ stream: true })` and yields\n * the same chunk sequence as the Chat path: text `delta`s, then the\n * `tool-call`s, then one `done`. With `streamChunks` it also yields\n * `reasoning` (reasoning summary / reasoning text deltas) and `tool-args`\n * (`response.function_call_arguments.delta`).\n *\n * **Provider-defined tools.** `ai.providerTool({ provider: \"openai\" })`\n * definitions are appended to `tools` verbatim. Hosted tools run inside the\n * response; their output items (`web_search_call`, …) are retained in\n * `providerMetadata.openai.output` for inspection and are not replayed\n * (with `store: false` the next turn only needs the answer text), and a\n * reasoning item that led into one is not replayed either (see\n * `pickTurnReasoning`).\n */\nexport class OpenAIResponsesModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: OpenAI;\n private readonly config: OpenAIModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIModelConfig, provider: string = \"openai\") {\n if (provider !== \"openai\") {\n throw new InvalidRequestError(\n `api: \"responses\" is supported only on the direct \"openai\" provider; got provider \"${provider}\".`,\n { context: { provider, model: config.name, api: \"responses\" } },\n );\n }\n\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? inferStructuredOutput(config.responseFormat),\n vision: config.vision ?? inferVisionCapability(config.name),\n reasoning: config.reasoning ?? inferReasoningCapability(config.name),\n // Read-side cache accounting (`cachedTokens`, `cacheWriteTokens`) works\n // and `prompt_cache_key` is forwarded. Explicit write breakpoints are not\n // mapped onto Responses yet, so a `cacheControl.breakpoints` hint is\n // ignored rather than rejected.\n promptCaching: true,\n // PDF and audio input are off under Responses in this adapter: the\n // `input_file` / `input_audio` field-level mapping is unverified. The\n // `pdf` / `audio` config flags are deliberately NOT honoured here.\n pdf: false,\n audio: false,\n };\n }\n\n /**\n * Single-shot completion against `client.responses.create`. Sends the full\n * history as Responses `input` items, walks `response.output`, and reshapes\n * it into a vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to responses.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: OpenAI.Responses.Response;\n\n try {\n response = await this.client.responses.create(\n this.buildRequest(messages, options),\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n if (response.status === \"failed\") {\n throw this.logAndWrap(toResponseFailure(response, this.name));\n }\n\n const output = Array.isArray(response.output) ? response.output : [];\n const { content, toolCalls } = this.readOutput(output);\n const finishReason = mapResponsesFinishReason({\n status: response.status,\n incomplete_details: response.incomplete_details,\n hasToolCalls: toolCalls !== undefined,\n });\n const usage = this.extractUsage(response.usage);\n const providerMetadata = hostedOutput(usage, output, this.pricing);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to responses.create succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content,\n finishReason,\n usage,\n ...(providerMetadata ? { providerMetadata } : {}),\n ...(toolCalls ? { toolCalls } : {}),\n };\n }\n\n /** Count native Responses input tokens using the same converted input and tools. */\n public async countTokens(messages: Message[], options?: ModelCallOptions): Promise<number> {\n const request = this.buildRequest(messages, options);\n const body: OpenAI.Responses.InputTokenCountParams = {\n model: request.model,\n input: request.input,\n instructions: request.instructions,\n tools: request.tools,\n tool_choice: request.tool_choice,\n reasoning: request.reasoning,\n text: request.text,\n parallel_tool_calls: request.parallel_tool_calls,\n truncation: request.truncation ?? undefined,\n };\n try {\n const response = await this.client.responses.inputTokens.count(\n body,\n options?.signal ? { signal: options.signal } : undefined,\n );\n return response.input_tokens;\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n }\n\n /**\n * Incremental streaming completion via `responses.create({ stream: true })`\n * (the SDK's `Stream<ResponseStreamEvent>`, `responses.d.ts:55`). Yields the\n * same neutral `ModelStreamChunk` sequence as the Chat path:\n *\n * - `response.output_text.delta` / `response.refusal.delta` -> `delta`.\n * - `response.output_item.done` with a completed `function_call` item ->\n * buffered; `reasoning` items are captured for replay. The `done` copy is\n * used because the `added` copy may be incomplete (`responses.d.ts:5711`).\n * - After the loop: the buffered `tool-call`s (the first carries the\n * replay metadata), then a single `done` with the finish reason and usage\n * from `response.completed` / `response.incomplete`.\n * - `response.failed` and `error` events throw a typed `AIError`; a stream\n * that ends with no terminal event throws `ProviderError`.\n * - Abort goes through the request `signal`; the SDK rejects the read and\n * the error is wrapped like any other.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to responses.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let events: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>;\n\n try {\n events = await this.client.responses.create(\n { ...this.buildRequest(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const toolCalls: ModelToolCallRequest[] = [];\n const outputItems: OpenAI.Responses.ResponseOutputItem[] = [];\n const want = resolveStreamChunks(options);\n // `function_call_arguments.delta` names its item by `item_id` only; the\n // `call_id` and `name` come from that item's `output_item.added`.\n const streamingCalls = new Map<string, { callId: string; name: string }>();\n let terminal: OpenAI.Responses.Response | undefined;\n\n try {\n for await (const event of events) {\n if (\n event.type === \"response.output_text.delta\" ||\n event.type === \"response.refusal.delta\"\n ) {\n if (event.delta) {\n yield { type: \"delta\", content: event.delta };\n }\n\n continue;\n }\n\n if (\n event.type === \"response.reasoning_summary_text.delta\" ||\n event.type === \"response.reasoning_text.delta\"\n ) {\n if (want.reasoning && event.delta) {\n yield { type: \"reasoning\", delta: event.delta };\n }\n\n continue;\n }\n\n if (event.type === \"response.output_item.added\") {\n const { item } = event;\n\n if (item.type === \"function_call\" && item.id) {\n streamingCalls.set(item.id, { callId: item.call_id, name: item.name });\n }\n\n continue;\n }\n\n if (event.type === \"response.function_call_arguments.delta\") {\n const call = streamingCalls.get(event.item_id);\n\n if (want.toolArgs && call && event.delta) {\n yield { type: \"tool-args\", callId: call.callId, name: call.name, delta: event.delta };\n }\n\n continue;\n }\n\n if (event.type === \"response.output_item.done\") {\n const { item } = event;\n\n this.assertServerRunItem(item);\n outputItems.push(item);\n\n if (item.type === \"function_call\" && item.status !== \"incomplete\") {\n toolCalls.push(this.toToolCall(item));\n }\n\n continue;\n }\n\n if (event.type === \"response.completed\" || event.type === \"response.incomplete\") {\n terminal = event.response;\n continue;\n }\n\n if (event.type === \"response.failed\") {\n throw toResponseFailure(event.response, this.name);\n }\n\n if (event.type === \"error\") {\n throw wrapOpenAIError({ code: event.code, message: event.message, param: event.param });\n }\n }\n\n if (terminal === undefined) {\n throw new ProviderError(\"OpenAI Responses stream ended before response.completed.\", {\n context: { provider: this.provider, model: this.name },\n });\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n for (const call of attachReasoningReplay(\n toolCalls,\n this.name,\n pickTurnReasoning(outputItems),\n )) {\n yield {\n type: \"tool-call\",\n id: call.id,\n name: call.name,\n input: call.input,\n ...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),\n };\n }\n\n const finishReason = mapResponsesFinishReason({\n status: terminal.status,\n incomplete_details: terminal.incomplete_details,\n hasToolCalls: toolCalls.length > 0,\n });\n const usage = this.extractUsage(terminal.usage);\n // Terminal output is authoritative; fall back to item.done events when absent.\n // Never add both snapshots, which would double-count hosted calls.\n const output = terminal.output?.length ? terminal.output : outputItems;\n const providerMetadata = hostedOutput(usage, output, this.pricing);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to responses.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage, ...(providerMetadata ? { providerMetadata } : {}) };\n }\n\n /**\n * Assemble the `responses.create` body.\n *\n * - System prompt -> `instructions`; history -> `input` items.\n * - `store: false` always (the API default is `true` when omitted), plus\n * `include: [\"reasoning.encrypted_content\"]` for stateless reasoning replay.\n * - `max_output_tokens` caps reasoning and visible output together.\n * - `temperature` only for non-reasoning models (same rule as Chat).\n * - `tools` flat with `strict: false`; `tool_choice` only with tools.\n * - `reasoning.effort` only when the caller gave one, through the same\n * clamp table as Chat. With no explicit effort the provider default\n * applies EVEN with tools attached - that is the point of this API.\n */\n private buildRequest(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): OpenAI.Responses.ResponseCreateParamsNonStreaming {\n const { instructions, input } = toResponsesInput(messages, this.name);\n const tools = this.buildTools(options);\n const toolChoice = tools ? toResponsesToolChoice(options?.toolChoice) : undefined;\n const maxTokens = options?.maxTokens ?? this.config.maxTokens;\n const temperature = options?.temperature ?? this.config.temperature;\n\n return mergeProviderBody<OpenAI.Responses.ResponseCreateParamsNonStreaming>(\n {\n model: this.name,\n ...(instructions !== undefined ? { instructions } : {}),\n input,\n store: false,\n // Stateless replay: ask for the encrypted reasoning blobs so they can be\n // sent back on the next tool turn (decision 3).\n include: [\n \"reasoning.encrypted_content\",\n ...(tools?.some(\n (tool) =>\n tool.type === \"web_search\" ||\n tool.type === \"web_search_2025_08_26\" ||\n tool.type === \"web_search_preview\" ||\n tool.type === \"web_search_preview_2025_03_11\",\n )\n ? [\"web_search_call.action.sources\" as const]\n : []),\n ],\n ...(maxTokens !== undefined ? { max_output_tokens: maxTokens } : {}),\n ...(!this.capabilities.reasoning && temperature !== undefined ? { temperature } : {}),\n ...(tools ? { tools } : {}),\n ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),\n ...this.buildTextFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning),\n ...buildPromptCacheParams(this.provider, this.config),\n },\n options,\n );\n }\n\n /**\n * The request `tools`: function tools (flat, `strict: false`), then the\n * `ai.providerTool({ provider: \"openai\" })` definitions verbatim — the\n * Responses built-in tools (`web_search`, `file_search`,\n * `code_interpreter`, `image_generation`, `mcp`, …). Other providers'\n * definitions are skipped with a warning. `undefined` when there are none.\n */\n private buildTools(options: ModelCallOptions | undefined): OpenAI.Responses.Tool[] | undefined {\n const { tools, definitions } = selectProviderTools(options, {\n accepts: [this.provider],\n module: LOG_MODULE,\n model: this.name,\n logger: this.logger,\n });\n const mapped: OpenAI.Responses.Tool[] = [\n ...(toResponsesTools(tools) ?? []),\n ...(definitions as unknown as OpenAI.Responses.Tool[]),\n ];\n\n return mapped.length > 0 ? mapped : undefined;\n }\n\n /**\n * Structured output on Responses lives in `text.format`, and `name` /\n * `schema` / `strict` sit directly on the format object (Chat nests them\n * under `json_schema`). The strict-or-downgrade decision is the shared\n * `planStructuredOutput`.\n */\n private buildTextFormat(responseSchema: Record<string, unknown> | undefined): {\n text?: OpenAI.Responses.ResponseTextConfig;\n } {\n const plan = planStructuredOutput(responseSchema, this.config.responseFormat);\n\n if (plan.mode === \"json_schema\") {\n return {\n text: {\n format: { type: \"json_schema\", name: \"response\", schema: plan.schema, strict: true },\n },\n };\n }\n\n if (plan.mode === \"json_object\") {\n return { text: { format: { type: \"json_object\" } } };\n }\n\n return {};\n }\n\n /** `reasoning.effort` for reasoning-capable models, only when the caller set one. */\n private buildReasoningParams(reasoning: ModelCallOptions[\"reasoning\"]): {\n reasoning?: { effort: ReturnType<typeof toOpenAIReasoningEffort> };\n } {\n if (!this.capabilities.reasoning || !reasoning?.effort) {\n return {};\n }\n\n return { reasoning: { effort: toOpenAIReasoningEffort(reasoning.effort) } };\n }\n\n /**\n * Walk `response.output`: `message` items contribute their `output_text`\n * text and any `refusal` text (surfaced as ordinary content, decision 5);\n * each completed `function_call` item becomes one tool call with\n * `id = call_id`. `reasoning` items carry no visible content and are\n * not content. A `function_call` the API marked `incomplete` (truncated\n * arguments) is skipped rather than dispatched with empty input.\n * `reasoning` items are captured and attached to the first tool call for\n * replay (see `attachReasoningReplay`).\n */\n private readOutput(output: OpenAI.Responses.ResponseOutputItem[]): {\n content: string;\n toolCalls: ModelToolCallRequest[] | undefined;\n } {\n let content = \"\";\n const toolCalls: ModelToolCallRequest[] = [];\n\n for (const item of output) {\n this.assertServerRunItem(item);\n\n if (item.type === \"message\") {\n for (const part of item.content) {\n content += part.type === \"refusal\" ? part.refusal : part.text;\n }\n\n continue;\n }\n\n if (item.type === \"function_call\" && item.status !== \"incomplete\") {\n toolCalls.push(this.toToolCall(item));\n }\n }\n\n return {\n content,\n toolCalls:\n toolCalls.length > 0\n ? attachReasoningReplay(toolCalls, this.name, pickTurnReasoning(output))\n : undefined,\n };\n }\n\n /**\n * Fail closed on a built-in tool call that YOUR code would have to run\n * (`shell_call`, `apply_patch_call`, `computer_call`, …, from a\n * provider-defined client tool). These items have their own output item\n * types that this adapter does not map, so the turn would otherwise end\n * silently with the call dropped. Hosted (server-run) tool items pass.\n */\n private assertServerRunItem(item: { type: string }): void {\n if (!CLIENT_RUN_ITEM_TYPES.has(item.type)) {\n return;\n }\n\n throw new InvalidRequestError(\n `OpenAI returned a \"${item.type}\" item: that built-in tool is executed by the caller, ` +\n \"which the Responses adapter does not support. Use OpenAI's server-run built-in tools \" +\n \"(web_search, file_search, code_interpreter, image_generation, mcp without approval) \" +\n \"with ai.providerTool(), or a function tool from ai.tool().\",\n { context: { provider: this.provider, model: this.name, itemType: item.type } },\n );\n }\n\n /** One completed `function_call` item as a neutral tool call (`call_id` -> `id`). */\n private toToolCall(item: OpenAI.Responses.ResponseFunctionToolCall): ModelToolCallRequest {\n return {\n id: item.call_id,\n name: item.name,\n input: safeJsonParse<Record<string, unknown>>(item.arguments, {}),\n };\n }\n\n /**\n * Normalize `usage` (absent on some partials) into the neutral `Usage`.\n * `input_tokens_details.cached_tokens` -> `cachedTokens`,\n * `cache_write_tokens` -> `cacheWriteTokens`,\n * `output_tokens_details.reasoning_tokens` -> `reasoningTokens`. The\n * detail blocks are typed non-optional but read defensively.\n */\n private extractUsage(raw: OpenAI.Responses.ResponseUsage | undefined): Usage {\n if (!raw) {\n return { input: 0, output: 0, total: 0 };\n }\n\n return buildUsage(\n {\n input: raw.input_tokens,\n output: raw.output_tokens,\n total: raw.total_tokens,\n cachedTokens: raw.input_tokens_details?.cached_tokens,\n cacheWriteTokens: raw.input_tokens_details?.cache_write_tokens,\n reasoningTokens: raw.output_tokens_details?.reasoning_tokens,\n },\n this.pricing,\n );\n }\n\n /** Wrap (idempotently), log and return a failure for the caller to throw. */\n private logAndWrap(thrown: unknown): ReturnType<typeof wrapOpenAIError> {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n"],"mappings":"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"}
package/esm/sdk.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { OpenAIEmbedderConfig, OpenAIImageConfig, OpenAIModelConfig, OpenAISDKConfig, OpenAISpeechConfig, OpenAITranscriptionConfig } from "./config.type.mjs";
2
2
  import { EmbedderContract, ImageModelContract, ModelContract, SDKAdapterContract, SpeechModelContract, TranscriptionModelContract } from "@warlock.js/ai";
3
-
4
3
  //#region ../../packages/ai-openai/src/sdk.d.ts
5
4
  /**
6
5
  * OpenAI-backed implementation of `SDKAdapterContract`.
@@ -36,7 +35,7 @@ import { EmbedderContract, ImageModelContract, ModelContract, SDKAdapterContract
36
35
  * const ai = { agent, tool, systemPrompt, persona, instruction, openai: new OpenAISDK({ apiKey }) };
37
36
  * const myAgent = ai.agent({ model: ai.openai.model({ name: "gpt-4o-mini" }) });
38
37
  */
39
- declare class OpenAISDK implements SDKAdapterContract {
38
+ export declare class OpenAISDK implements SDKAdapterContract {
40
39
  private readonly client;
41
40
  private readonly provider;
42
41
  private readonly pricing?;
@@ -50,8 +49,9 @@ declare class OpenAISDK implements SDKAdapterContract {
50
49
  * coming from the same upstream.
51
50
  *
52
51
  * Pricing resolution: per-model `config.pricing` wins; otherwise the
53
- * SDK-level registry entry keyed by `config.name`; otherwise
54
- * `undefined` (no cost computed).
52
+ * SDK-level registry entry keyed by `config.name`; otherwise, when the
53
+ * provider label is `"openai"`, the built-in list price
54
+ * (`OPENAI_MODEL_PRICING`); otherwise `undefined` (no cost computed).
55
55
  *
56
56
  * `config.api` selects the wire API. Omitted or `"chat"` builds the Chat
57
57
  * Completions `OpenAIModel`. `"responses"` builds `OpenAIResponsesModel`
@@ -124,5 +124,4 @@ declare class OpenAISDK implements SDKAdapterContract {
124
124
  transcribe(config: OpenAITranscriptionConfig): TranscriptionModelContract;
125
125
  }
126
126
  //#endregion
127
- export { OpenAISDK };
128
127
  //# sourceMappingURL=sdk.d.mts.map
package/esm/sdk.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/sdk.ts"],"mappings":";;;;;;AA4DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,eAAA;EAqGpB;;;;;;;;;;;;;AAkCyE;;;;;;EAxGzE,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;;;;;;;;EAmC5B,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;;;;EAc5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;;;;;;;;;;;;;;;;;;EAqBxC,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,kBAAA;;;;;;;;;;;;EAmBlC,MAAA,CAAO,MAAA,EAAQ,kBAAA,GAAqB,mBAAA;;;;;;;;;;;;EAepC,UAAA,CAAW,MAAA,EAAQ,yBAAA,GAA4B,0BAAA;AAAA"}
1
+ {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/sdk.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA6Da,qBAAqB;mBACf;mBACA;mBACA;EAEjB,YAAmB,QAAQ;;;;;;;;;;;;;;;;;;;;;EAgCpB,MAAM,QAAQ,oBAAoB;;;;;;;;EAsC5B,MAAM,cAAc,kBAAkB;;;;;;;;;;;EAc5C,SAAS,QAAQ,uBAAuB;;;;;;;;;;;;;;;;;;EAqBxC,MAAM,QAAQ,oBAAoB;;;;;;;;;;;;EAmBlC,OAAO,QAAQ,qBAAqB;;;;;;;;;;;;EAepC,WAAW,QAAQ,4BAA4B"}
package/esm/sdk.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { openAIPricingFor } from "./known-pricing.mjs";
1
2
  import { OpenAIEmbedder } from "./embedder.mjs";
2
3
  import { OpenAIImageModel } from "./image.mjs";
3
4
  import { OpenAIModel } from "./model.mjs";
@@ -58,8 +59,9 @@ var OpenAISDK = class {
58
59
  * coming from the same upstream.
59
60
  *
60
61
  * Pricing resolution: per-model `config.pricing` wins; otherwise the
61
- * SDK-level registry entry keyed by `config.name`; otherwise
62
- * `undefined` (no cost computed).
62
+ * SDK-level registry entry keyed by `config.name`; otherwise, when the
63
+ * provider label is `"openai"`, the built-in list price
64
+ * (`OPENAI_MODEL_PRICING`); otherwise `undefined` (no cost computed).
63
65
  *
64
66
  * `config.api` selects the wire API. Omitted or `"chat"` builds the Chat
65
67
  * Completions `OpenAIModel`. `"responses"` builds `OpenAIResponsesModel`
@@ -80,7 +82,7 @@ var OpenAISDK = class {
80
82
  model: config.name,
81
83
  api
82
84
  } });
83
- const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
85
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name] ?? (this.provider === "openai" ? openAIPricingFor(config.name) : void 0);
84
86
  const resolvedConfig = resolvedPricing === config.pricing ? config : {
85
87
  ...config,
86
88
  pricing: resolvedPricing
package/esm/sdk.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/sdk.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n SpeechModelContract,\n TranscriptionModelContract,\n} from \"@warlock.js/ai\";\nimport { InvalidRequestError, approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n OpenAIEmbedderConfig,\n OpenAIImageConfig,\n OpenAIModelConfig,\n OpenAISDKConfig,\n OpenAISpeechConfig,\n OpenAITranscriptionConfig,\n} from \"./config.type\";\nimport { OpenAIEmbedder } from \"./embedder\";\nimport { OpenAIImageModel } from \"./image\";\nimport { OpenAIModel } from \"./model\";\nimport { OpenAIResponsesModel } from \"./responses-model\";\nimport { OpenAISpeechModel } from \"./speech\";\nimport { OpenAITranscriptionModel } from \"./transcription\";\n\n/**\n * OpenAI-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for any OpenAI-compatible provider\n * (OpenAI, Azure OpenAI, OpenRouter, local gateways speaking the Chat\n * Completions protocol). A single `OpenAISDK` instance holds one live\n * `OpenAI` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per provider/account and reuse it\n * across all agents, workflows, and supervisors that target that\n * provider.\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client (authentication, base URL) and\n * its lifetime scope. Factory for `OpenAIModel` instances — each\n * model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `OpenAIModel` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `OpenAI` client is heavy to construct and\n * designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the PascalCase naming convention readers\n * expect from a constructor.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });\n * const model = openai.model({ name: \"gpt-4o\", temperature: 0.7 });\n * const tokens = await openai.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.openai` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, persona, instruction, openai: new OpenAISDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.openai.model({ name: \"gpt-4o-mini\" }) });\n */\nexport class OpenAISDK implements SDKAdapterContract {\n private readonly client: OpenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: OpenAISDKConfig) {\n // Peel off the framework-only keys and forward every other upstream\n // `ClientOptions` (timeout, maxRetries, defaultHeaders, fetch,\n // organization, project, …) verbatim — they type-check, so dropping them\n // is a silent footgun. Mirrors the Bedrock/Google/Ollama adapters.\n const { provider, pricing, ...clientOptions } = config;\n\n this.client = new OpenAI(clientOptions);\n this.provider = provider ?? \"openai\";\n this.pricing = pricing;\n }\n\n /**\n * Build an `OpenAIModel` bound to this SDK's client. Each call returns\n * a fresh model instance, but all instances share the underlying\n * `OpenAI` client — connection pools, rate limits, and authentication\n * state stay unified across every model produced here. The SDK's\n * `provider` label is forwarded so every model self-identifies as\n * coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n *\n * `config.api` selects the wire API. Omitted or `\"chat\"` builds the Chat\n * Completions `OpenAIModel`. `\"responses\"` builds `OpenAIResponsesModel`\n * and is accepted ONLY when this SDK's provider label is `\"openai\"`: any\n * other label (the wrapper packages deepseek / groq / xai / mistral, or a\n * custom gateway label) throws, so an endpoint not known to implement\n * `/v1/responses` is never sent there.\n */\n public model(config: OpenAIModelConfig): ModelContract {\n const api: unknown = config.api;\n\n if (api !== undefined && api !== \"chat\" && api !== \"responses\") {\n throw new InvalidRequestError(\n `Unknown OpenAI api \"${String(api)}\"; expected \"chat\" or \"responses\".`,\n { context: { provider: this.provider, model: config.name, api } },\n );\n }\n\n if (api === \"responses\" && this.provider !== \"openai\") {\n throw new InvalidRequestError(\n `api: \"responses\" is supported only on the direct \"openai\" provider; this SDK is labeled \"${this.provider}\". Use the default api: \"chat\".`,\n { context: { provider: this.provider, model: config.name, api } },\n );\n }\n\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n if (api === \"responses\") {\n return new OpenAIResponsesModel(this.client, resolvedConfig, this.provider);\n }\n\n return new OpenAIModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses a\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Accepts an optional model id for future per-model tokenizer\n * dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build an `OpenAIEmbedder` bound to this SDK's client. Each call\n * returns a fresh embedder instance sharing the same underlying\n * `OpenAI` client — connection pools and authentication stay unified\n * across every embedder produced here.\n *\n * @example\n * const embedder = openai.embedder({ name: \"text-embedding-3-small\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: OpenAIEmbedderConfig): EmbedderContract {\n return new OpenAIEmbedder(this.client, config);\n }\n\n /**\n * Build an `OpenAIImageModel` bound to this SDK's client for use with\n * `ai.image({ model, prompt })`. `config.name` is passed through to\n * `client.images.generate` as given — no id is rejected locally, so\n * an unsupported model fails at OpenAI, not here. Metering follows\n * the family: `gpt-image-*` is token-metered, `dall-e-*` per-image.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined` (no cost computed). A token-priced\n * `gpt-image-1` entry can live in the same SDK registry as the chat\n * models.\n *\n * @example\n * const model = openai.image({ name: \"gpt-image-1\" });\n * const { data } = await ai.image({ model, prompt: \"a red bicycle\" });\n */\n public image(config: OpenAIImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIImageModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Build an `OpenAISpeechModel` (text-to-speech) bound to this SDK's\n * client, for use with `ai.speech({ model, text })`. `config.name` is\n * passed through to `audio.speech.create` as given — no id is\n * rejected locally, so an unsupported model fails at OpenAI, not\n * here.\n *\n * @example\n * const tts = openai.speech({ name: \"tts-1\", voice: \"alloy\" });\n * const { data } = await ai.speech({ model: tts, text: \"Hello\" });\n */\n public speech(config: OpenAISpeechConfig): SpeechModelContract {\n return new OpenAISpeechModel(this.client, config, this.provider);\n }\n\n /**\n * Build an `OpenAITranscriptionModel` (speech-to-text) bound to this\n * SDK's client, for use with `ai.transcribe({ model, audio })`.\n * `config.name` is passed through to `audio.transcriptions.create`\n * as given — no id is rejected locally, so an unsupported model\n * fails at OpenAI, not here.\n *\n * @example\n * const stt = openai.transcribe({ name: \"whisper-1\" });\n * const { data } = await ai.transcribe({ model: stt, audio });\n */\n public transcribe(config: OpenAITranscriptionConfig): TranscriptionModelContract {\n return new OpenAITranscriptionModel(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAK1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,SAAS,IAAI,OAAO,aAAa;EACtC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;;;;;;;;;;;CAqBA,AAAO,MAAM,QAA0C;EACrD,MAAM,MAAe,OAAO;EAE5B,IAAI,QAAQ,UAAa,QAAQ,UAAU,QAAQ,aACjD,MAAM,IAAI,oBACR,uBAAuB,OAAO,GAAG,EAAE,qCACnC,EAAE,SAAS;GAAE,UAAU,KAAK;GAAU,OAAO,OAAO;GAAM;EAAI,EAAE,CAClE;EAGF,IAAI,QAAQ,eAAe,KAAK,aAAa,UAC3C,MAAM,IAAI,oBACR,4FAA4F,KAAK,SAAS,kCAC1G,EAAE,SAAS;GAAE,UAAU,KAAK;GAAU,OAAO,OAAO;GAAM;EAAI,EAAE,CAClE;EAGF,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,IAAI,QAAQ,aACV,OAAO,IAAI,qBAAqB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;EAG5E,OAAO,IAAI,YAAY,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACnE;;;;;;;;CASA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;;;;CAYA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,QAAQ,MAAM;CAC/C;;;;;;;;;;;;;;;;;;CAmBA,AAAO,MAAM,QAA+C;EAC1D,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,iBAAiB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACxE;;;;;;;;;;;;CAaA,AAAO,OAAO,QAAiD;EAC7D,OAAO,IAAI,kBAAkB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACjE;;;;;;;;;;;;CAaA,AAAO,WAAW,QAA+D;EAC/E,OAAO,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACxE;AACF"}
1
+ {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/sdk.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n SpeechModelContract,\n TranscriptionModelContract,\n} from \"@warlock.js/ai\";\nimport { InvalidRequestError, approximateTokenCount } from \"@warlock.js/ai\";\nimport { openAIPricingFor } from \"./known-pricing\";\nimport type {\n OpenAIEmbedderConfig,\n OpenAIImageConfig,\n OpenAIModelConfig,\n OpenAISDKConfig,\n OpenAISpeechConfig,\n OpenAITranscriptionConfig,\n} from \"./config.type\";\nimport { OpenAIEmbedder } from \"./embedder\";\nimport { OpenAIImageModel } from \"./image\";\nimport { OpenAIModel } from \"./model\";\nimport { OpenAIResponsesModel } from \"./responses-model\";\nimport { OpenAISpeechModel } from \"./speech\";\nimport { OpenAITranscriptionModel } from \"./transcription\";\n\n/**\n * OpenAI-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for any OpenAI-compatible provider\n * (OpenAI, Azure OpenAI, OpenRouter, local gateways speaking the Chat\n * Completions protocol). A single `OpenAISDK` instance holds one live\n * `OpenAI` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per provider/account and reuse it\n * across all agents, workflows, and supervisors that target that\n * provider.\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client (authentication, base URL) and\n * its lifetime scope. Factory for `OpenAIModel` instances — each\n * model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `OpenAIModel` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `OpenAI` client is heavy to construct and\n * designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the PascalCase naming convention readers\n * expect from a constructor.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });\n * const model = openai.model({ name: \"gpt-4o\", temperature: 0.7 });\n * const tokens = await openai.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.openai` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, persona, instruction, openai: new OpenAISDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.openai.model({ name: \"gpt-4o-mini\" }) });\n */\nexport class OpenAISDK implements SDKAdapterContract {\n private readonly client: OpenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: OpenAISDKConfig) {\n // Peel off the framework-only keys and forward every other upstream\n // `ClientOptions` (timeout, maxRetries, defaultHeaders, fetch,\n // organization, project, …) verbatim — they type-check, so dropping them\n // is a silent footgun. Mirrors the Bedrock/Google/Ollama adapters.\n const { provider, pricing, ...clientOptions } = config;\n\n this.client = new OpenAI(clientOptions);\n this.provider = provider ?? \"openai\";\n this.pricing = pricing;\n }\n\n /**\n * Build an `OpenAIModel` bound to this SDK's client. Each call returns\n * a fresh model instance, but all instances share the underlying\n * `OpenAI` client — connection pools, rate limits, and authentication\n * state stay unified across every model produced here. The SDK's\n * `provider` label is forwarded so every model self-identifies as\n * coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise, when the\n * provider label is `\"openai\"`, the built-in list price\n * (`OPENAI_MODEL_PRICING`); otherwise `undefined` (no cost computed).\n *\n * `config.api` selects the wire API. Omitted or `\"chat\"` builds the Chat\n * Completions `OpenAIModel`. `\"responses\"` builds `OpenAIResponsesModel`\n * and is accepted ONLY when this SDK's provider label is `\"openai\"`: any\n * other label (the wrapper packages deepseek / groq / xai / mistral, or a\n * custom gateway label) throws, so an endpoint not known to implement\n * `/v1/responses` is never sent there.\n */\n public model(config: OpenAIModelConfig): ModelContract {\n const api: unknown = config.api;\n\n if (api !== undefined && api !== \"chat\" && api !== \"responses\") {\n throw new InvalidRequestError(\n `Unknown OpenAI api \"${String(api)}\"; expected \"chat\" or \"responses\".`,\n { context: { provider: this.provider, model: config.name, api } },\n );\n }\n\n if (api === \"responses\" && this.provider !== \"openai\") {\n throw new InvalidRequestError(\n `api: \"responses\" is supported only on the direct \"openai\" provider; this SDK is labeled \"${this.provider}\". Use the default api: \"chat\".`,\n { context: { provider: this.provider, model: config.name, api } },\n );\n }\n\n const resolvedPricing =\n config.pricing ??\n this.pricing?.[config.name] ??\n (this.provider === \"openai\" ? openAIPricingFor(config.name) : undefined);\n const resolvedConfig: OpenAIModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n if (api === \"responses\") {\n return new OpenAIResponsesModel(this.client, resolvedConfig, this.provider);\n }\n\n return new OpenAIModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses a\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Accepts an optional model id for future per-model tokenizer\n * dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build an `OpenAIEmbedder` bound to this SDK's client. Each call\n * returns a fresh embedder instance sharing the same underlying\n * `OpenAI` client — connection pools and authentication stay unified\n * across every embedder produced here.\n *\n * @example\n * const embedder = openai.embedder({ name: \"text-embedding-3-small\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: OpenAIEmbedderConfig): EmbedderContract {\n return new OpenAIEmbedder(this.client, config);\n }\n\n /**\n * Build an `OpenAIImageModel` bound to this SDK's client for use with\n * `ai.image({ model, prompt })`. `config.name` is passed through to\n * `client.images.generate` as given — no id is rejected locally, so\n * an unsupported model fails at OpenAI, not here. Metering follows\n * the family: `gpt-image-*` is token-metered, `dall-e-*` per-image.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined` (no cost computed). A token-priced\n * `gpt-image-1` entry can live in the same SDK registry as the chat\n * models.\n *\n * @example\n * const model = openai.image({ name: \"gpt-image-1\" });\n * const { data } = await ai.image({ model, prompt: \"a red bicycle\" });\n */\n public image(config: OpenAIImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIImageModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Build an `OpenAISpeechModel` (text-to-speech) bound to this SDK's\n * client, for use with `ai.speech({ model, text })`. `config.name` is\n * passed through to `audio.speech.create` as given — no id is\n * rejected locally, so an unsupported model fails at OpenAI, not\n * here.\n *\n * @example\n * const tts = openai.speech({ name: \"tts-1\", voice: \"alloy\" });\n * const { data } = await ai.speech({ model: tts, text: \"Hello\" });\n */\n public speech(config: OpenAISpeechConfig): SpeechModelContract {\n return new OpenAISpeechModel(this.client, config, this.provider);\n }\n\n /**\n * Build an `OpenAITranscriptionModel` (speech-to-text) bound to this\n * SDK's client, for use with `ai.transcribe({ model, audio })`.\n * `config.name` is passed through to `audio.transcriptions.create`\n * as given — no id is rejected locally, so an unsupported model\n * fails at OpenAI, not here.\n *\n * @example\n * const stt = openai.transcribe({ name: \"whisper-1\" });\n * const { data } = await ai.transcribe({ model: stt, audio });\n */\n public transcribe(config: OpenAITranscriptionConfig): TranscriptionModelContract {\n return new OpenAITranscriptionModel(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAK1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,SAAS,IAAI,OAAO,aAAa;EACtC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;;;;;;;;;;;;CAsBA,AAAO,MAAM,QAA0C;EACrD,MAAM,MAAe,OAAO;EAE5B,IAAI,QAAQ,UAAa,QAAQ,UAAU,QAAQ,aACjD,MAAM,IAAI,oBACR,uBAAuB,OAAO,GAAG,EAAE,qCACnC,EAAE,SAAS;GAAE,UAAU,KAAK;GAAU,OAAO,OAAO;GAAM;EAAI,EAAE,CAClE;EAGF,IAAI,QAAQ,eAAe,KAAK,aAAa,UAC3C,MAAM,IAAI,oBACR,4FAA4F,KAAK,SAAS,kCAC1G,EAAE,SAAS;GAAE,UAAU,KAAK;GAAU,OAAO,OAAO;GAAM;EAAI,EAAE,CAClE;EAGF,MAAM,kBACJ,OAAO,WACP,KAAK,UAAU,OAAO,UACrB,KAAK,aAAa,WAAW,iBAAiB,OAAO,IAAI,IAAI;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,IAAI,QAAQ,aACV,OAAO,IAAI,qBAAqB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;EAG5E,OAAO,IAAI,YAAY,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACnE;;;;;;;;CASA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;;;;CAYA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,QAAQ,MAAM;CAC/C;;;;;;;;;;;;;;;;;;CAmBA,AAAO,MAAM,QAA+C;EAC1D,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,iBAAiB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACxE;;;;;;;;;;;;CAaA,AAAO,OAAO,QAAiD;EAC7D,OAAO,IAAI,kBAAkB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACjE;;;;;;;;;;;;CAaA,AAAO,WAAW,QAA+D;EAC/E,OAAO,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACxE;AACF"}
package/esm/speech.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { OpenAISpeechConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { SpeechGenerationResponse, SpeechModelContract, SpeechModelPricing, SpeechOptions } from "@warlock.js/ai";
4
-
5
4
  //#region ../../packages/ai-openai/src/speech.d.ts
6
5
  /**
7
6
  * OpenAI-backed implementation of `SpeechModelContract` (text-to-speech)
@@ -18,7 +17,7 @@ import { SpeechGenerationResponse, SpeechModelContract, SpeechModelPricing, Spee
18
17
  * const tts = new OpenAISpeechModel(client, { name: "tts-1", voice: "alloy" }, "openai");
19
18
  * const { audio } = await tts.generate("Welcome aboard.");
20
19
  */
21
- declare class OpenAISpeechModel implements SpeechModelContract {
20
+ export declare class OpenAISpeechModel implements SpeechModelContract {
22
21
  readonly name: string;
23
22
  readonly provider: string;
24
23
  readonly pricing?: SpeechModelPricing;
@@ -29,5 +28,4 @@ declare class OpenAISpeechModel implements SpeechModelContract {
29
28
  generate(text: string, options?: SpeechOptions): Promise<SpeechGenerationResponse>;
30
29
  }
31
30
  //#endregion
32
- export { OpenAISpeechModel };
33
31
  //# sourceMappingURL=speech.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"speech.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/speech.ts"],"mappings":";;;;;;;AA8CA;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,mBAAA;EAAA,SACxB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,kBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,YAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,kBAAA,EAAoB,QAAA;EAQlD,QAAA,CAAS,IAAA,UAAc,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,wBAAA;AAAA"}
1
+ {"version":3,"file":"speech.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/speech.ts"],"mappings":";;;;;;;;;;;;;;;;;;;qBA8Ca,6BAA6B;WACxB;WACA;WACA,UAAU;mBAET;mBACA;mBACA;EAEjB,YAAmB,QAAQ,QAAQ,QAAQ,oBAAoB;EAQlD,SAAS,cAAc,UAAU,gBAAgB,QAAQ"}
@@ -1,7 +1,6 @@
1
1
  import { OpenAITranscriptionConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { AudioInput, TranscribeOptions, TranscriptionModelContract, TranscriptionModelPricing, TranscriptionResponse } from "@warlock.js/ai";
4
-
5
4
  //#region ../../packages/ai-openai/src/transcription.d.ts
6
5
  /**
7
6
  * OpenAI-backed implementation of `TranscriptionModelContract`
@@ -22,7 +21,7 @@ import { AudioInput, TranscribeOptions, TranscriptionModelContract, Transcriptio
22
21
  * const stt = new OpenAITranscriptionModel(client, { name: "whisper-1" }, "openai");
23
22
  * const { text } = await stt.transcribe({ base64, mediaType: "audio/mpeg" });
24
23
  */
25
- declare class OpenAITranscriptionModel implements TranscriptionModelContract {
24
+ export declare class OpenAITranscriptionModel implements TranscriptionModelContract {
26
25
  readonly name: string;
27
26
  readonly provider: string;
28
27
  readonly pricing?: TranscriptionModelPricing;
@@ -32,5 +31,4 @@ declare class OpenAITranscriptionModel implements TranscriptionModelContract {
32
31
  transcribe(audio: AudioInput, options?: TranscribeOptions): Promise<TranscriptionResponse>;
33
32
  }
34
33
  //#endregion
35
- export { OpenAITranscriptionModel };
36
34
  //# sourceMappingURL=transcription.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/transcription.ts"],"mappings":";;;;;;;AAiDA;;;;;;;;;;;;;;;;;cAAa,wBAAA,YAAoC,0BAAA;EAAA,SAC/B,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,yBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,MAAA;cAGf,MAAA,EAAQ,MAAA,EACR,MAAA,EAAQ,yBAAA,EACR,QAAA;EAQW,UAAA,CACX,KAAA,EAAO,UAAA,EACP,OAAA,GAAU,iBAAA,GACT,OAAA,CAAQ,qBAAA;AAAA"}
1
+ {"version":3,"file":"transcription.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/transcription.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;qBAiDa,oCAAoC;WAC/B;WACA;WACA,UAAU;mBAET;mBACA;EAEjB,YACE,QAAQ,QACR,QAAQ,2BACR;EAQW,WACX,OAAO,YACP,UAAU,oBACT,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/transcription.ts"],"sourcesContent":["import {\n type AudioInput,\n type TranscribeOptions,\n type TranscriptionModelContract,\n type TranscriptionModelPricing,\n type TranscriptionResponse,\n type TranscriptionSegment,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport OpenAI, { toFile } from \"openai\";\nimport type { OpenAITranscriptionConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Defensive view over the response, whose shape varies by `response_format`. */\ntype RawTranscription = {\n text: string;\n duration?: number;\n language?: string;\n segments?: Array<{ text: string; start?: number; end?: number }>;\n usage?: {\n type?: string;\n seconds?: number;\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n };\n};\n\n/**\n * OpenAI-backed implementation of `TranscriptionModelContract`\n * (speech-to-text) via `audio.transcriptions.create`. Consumed by the\n * `ai.transcribe()` verb.\n *\n * **No model-id validation.** `config.name` is forwarded to\n * `audio.transcriptions.create` exactly as given — the constructor\n * never inspects it. An unrecognized id fails at OpenAI (wrapped into\n * the typed `AIError` hierarchy by `transcribe()`), not here.\n *\n * **Response format.** Defaults to `verbose_json` for `whisper-1` (so\n * the run gets a `duration` + timestamped `segments`) and `json` for\n * the token-metered `gpt-4o-transcribe` family. Base64 audio is wrapped\n * in an uploadable via the SDK's `toFile`.\n *\n * @example\n * const stt = new OpenAITranscriptionModel(client, { name: \"whisper-1\" }, \"openai\");\n * const { text } = await stt.transcribe({ base64, mediaType: \"audio/mpeg\" });\n */\nexport class OpenAITranscriptionModel implements TranscriptionModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: TranscriptionModelPricing;\n\n private readonly client: OpenAI;\n private readonly logger: Logger = log;\n\n public constructor(\n client: OpenAI,\n config: OpenAITranscriptionConfig,\n provider: string = \"openai\",\n ) {\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async transcribe(\n audio: AudioInput,\n options?: TranscribeOptions,\n ): Promise<TranscriptionResponse> {\n const isWhisper = this.name.startsWith(\"whisper\");\n const format = options?.format ?? (isWhisper ? \"verbose_json\" : \"json\");\n\n const file = await toFile(Buffer.from(audio.base64, \"base64\"), audio.filename ?? \"audio\", {\n type: audio.mediaType,\n });\n\n this.logger.debug(LOG_MODULE, \"transcription.request\", \"audio.transcriptions.create\", {\n model: this.name,\n format,\n });\n\n let raw: unknown;\n\n try {\n raw = await this.client.audio.transcriptions.create(\n {\n model: this.name,\n file,\n response_format: format as OpenAI.Audio.TranscriptionCreateParams[\"response_format\"],\n ...(options?.language ? { language: options.language } : {}),\n ...(options?.prompt ? { prompt: options.prompt } : {}),\n } as OpenAI.Audio.TranscriptionCreateParamsNonStreaming,\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n this.logger.error(LOG_MODULE, \"transcription.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n throw wrapped;\n }\n\n const response = raw as RawTranscription;\n\n const segments: TranscriptionSegment[] | undefined = response.segments?.map((segment) => ({\n text: segment.text,\n ...(segment.start !== undefined ? { start: segment.start } : {}),\n ...(segment.end !== undefined ? { end: segment.end } : {}),\n }));\n\n const durationSeconds =\n response.duration ?? (response.usage?.type === \"duration\" ? response.usage.seconds : undefined);\n\n const usage =\n response.usage?.type === \"tokens\"\n ? {\n input: response.usage.input_tokens ?? 0,\n output: response.usage.output_tokens ?? 0,\n total: response.usage.total_tokens ?? 0,\n }\n : { input: 0, output: 0, total: 0 };\n\n return {\n text: response.text,\n ...(segments && segments.length > 0 ? { segments } : {}),\n ...(durationSeconds !== undefined ? { durationSeconds } : {}),\n usage,\n };\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;AAoCnB,IAAa,2BAAb,MAA4E;CAQ1E,AAAO,YACL,QACA,QACA,WAAmB,UACnB;gBANgC;EAOhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;CACxB;CAEA,MAAa,WACX,OACA,SACgC;EAChC,MAAM,YAAY,KAAK,KAAK,WAAW,SAAS;EAChD,MAAM,SAAS,SAAS,WAAW,YAAY,iBAAiB;EAEhE,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,MAAM,QAAQ,QAAQ,GAAG,MAAM,YAAY,SAAS,EACxF,MAAM,MAAM,UACd,CAAC;EAED,KAAK,OAAO,MAAM,YAAY,yBAAyB,+BAA+B;GACpF,OAAO,KAAK;GACZ;EACF,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,MAAM,MAAM,KAAK,OAAO,MAAM,eAAe,OAC3C;IACE,OAAO,KAAK;IACZ;IACA,iBAAiB;IACjB,GAAI,SAAS,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;IAC1D,GAAI,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;GACtD,GACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GACtC,KAAK,OAAO,MAAM,YAAY,uBAAuB,QAAQ,SAAS;IACpE,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GACD,MAAM;EACR;EAEA,MAAM,WAAW;EAEjB,MAAM,WAA+C,SAAS,UAAU,KAAK,aAAa;GACxF,MAAM,QAAQ;GACd,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;GAC9D,GAAI,QAAQ,QAAQ,SAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1D,EAAE;EAEF,MAAM,kBACJ,SAAS,aAAa,SAAS,OAAO,SAAS,aAAa,SAAS,MAAM,UAAU;EAEvF,MAAM,QACJ,SAAS,OAAO,SAAS,WACrB;GACE,OAAO,SAAS,MAAM,gBAAgB;GACtC,QAAQ,SAAS,MAAM,iBAAiB;GACxC,OAAO,SAAS,MAAM,gBAAgB;EACxC,IACA;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAEtC,OAAO;GACL,MAAM,SAAS;GACf,GAAI,YAAY,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;GACtD,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;GAC3D;EACF;CACF;AACF"}
1
+ {"version":3,"file":"transcription.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/transcription.ts"],"sourcesContent":["import {\n type AudioInput,\n type TranscribeOptions,\n type TranscriptionModelContract,\n type TranscriptionModelPricing,\n type TranscriptionResponse,\n type TranscriptionSegment,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport OpenAI, { toFile } from \"openai\";\nimport type { OpenAITranscriptionConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Defensive view over the response, whose shape varies by `response_format`. */\ntype RawTranscription = {\n text: string;\n duration?: number;\n language?: string;\n segments?: Array<{ text: string; start?: number; end?: number }>;\n usage?: {\n type?: string;\n seconds?: number;\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n };\n};\n\n/**\n * OpenAI-backed implementation of `TranscriptionModelContract`\n * (speech-to-text) via `audio.transcriptions.create`. Consumed by the\n * `ai.transcribe()` verb.\n *\n * **No model-id validation.** `config.name` is forwarded to\n * `audio.transcriptions.create` exactly as given — the constructor\n * never inspects it. An unrecognized id fails at OpenAI (wrapped into\n * the typed `AIError` hierarchy by `transcribe()`), not here.\n *\n * **Response format.** Defaults to `verbose_json` for `whisper-1` (so\n * the run gets a `duration` + timestamped `segments`) and `json` for\n * the token-metered `gpt-4o-transcribe` family. Base64 audio is wrapped\n * in an uploadable via the SDK's `toFile`.\n *\n * @example\n * const stt = new OpenAITranscriptionModel(client, { name: \"whisper-1\" }, \"openai\");\n * const { text } = await stt.transcribe({ base64, mediaType: \"audio/mpeg\" });\n */\nexport class OpenAITranscriptionModel implements TranscriptionModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: TranscriptionModelPricing;\n\n private readonly client: OpenAI;\n private readonly logger: Logger = log;\n\n public constructor(\n client: OpenAI,\n config: OpenAITranscriptionConfig,\n provider: string = \"openai\",\n ) {\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async transcribe(\n audio: AudioInput,\n options?: TranscribeOptions,\n ): Promise<TranscriptionResponse> {\n const isWhisper = this.name.startsWith(\"whisper\");\n const format = options?.format ?? (isWhisper ? \"verbose_json\" : \"json\");\n\n const file = await toFile(Buffer.from(audio.base64, \"base64\"), audio.filename ?? \"audio\", {\n type: audio.mediaType,\n });\n\n this.logger.debug(LOG_MODULE, \"transcription.request\", \"audio.transcriptions.create\", {\n model: this.name,\n format,\n });\n\n let raw: unknown;\n\n try {\n raw = await this.client.audio.transcriptions.create(\n {\n model: this.name,\n file,\n response_format: format as OpenAI.Audio.TranscriptionCreateParams[\"response_format\"],\n ...(options?.language ? { language: options.language } : {}),\n ...(options?.prompt ? { prompt: options.prompt } : {}),\n } as OpenAI.Audio.TranscriptionCreateParamsNonStreaming,\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n this.logger.error(LOG_MODULE, \"transcription.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n throw wrapped;\n }\n\n const response = raw as RawTranscription;\n\n const segments: TranscriptionSegment[] | undefined = response.segments?.map((segment) => ({\n text: segment.text,\n ...(segment.start !== undefined ? { start: segment.start } : {}),\n ...(segment.end !== undefined ? { end: segment.end } : {}),\n }));\n\n const durationSeconds =\n response.duration ??\n (response.usage?.type === \"duration\" ? response.usage.seconds : undefined);\n\n const usage =\n response.usage?.type === \"tokens\"\n ? {\n input: response.usage.input_tokens ?? 0,\n output: response.usage.output_tokens ?? 0,\n total: response.usage.total_tokens ?? 0,\n }\n : { input: 0, output: 0, total: 0 };\n\n return {\n text: response.text,\n ...(segments && segments.length > 0 ? { segments } : {}),\n ...(durationSeconds !== undefined ? { durationSeconds } : {}),\n usage,\n };\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;AAoCnB,IAAa,2BAAb,MAA4E;CAQ1E,AAAO,YACL,QACA,QACA,WAAmB,UACnB;gBANgC;EAOhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;CACxB;CAEA,MAAa,WACX,OACA,SACgC;EAChC,MAAM,YAAY,KAAK,KAAK,WAAW,SAAS;EAChD,MAAM,SAAS,SAAS,WAAW,YAAY,iBAAiB;EAEhE,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,MAAM,QAAQ,QAAQ,GAAG,MAAM,YAAY,SAAS,EACxF,MAAM,MAAM,UACd,CAAC;EAED,KAAK,OAAO,MAAM,YAAY,yBAAyB,+BAA+B;GACpF,OAAO,KAAK;GACZ;EACF,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,MAAM,MAAM,KAAK,OAAO,MAAM,eAAe,OAC3C;IACE,OAAO,KAAK;IACZ;IACA,iBAAiB;IACjB,GAAI,SAAS,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;IAC1D,GAAI,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;GACtD,GACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GACtC,KAAK,OAAO,MAAM,YAAY,uBAAuB,QAAQ,SAAS;IACpE,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GACD,MAAM;EACR;EAEA,MAAM,WAAW;EAEjB,MAAM,WAA+C,SAAS,UAAU,KAAK,aAAa;GACxF,MAAM,QAAQ;GACd,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;GAC9D,GAAI,QAAQ,QAAQ,SAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1D,EAAE;EAEF,MAAM,kBACJ,SAAS,aACR,SAAS,OAAO,SAAS,aAAa,SAAS,MAAM,UAAU;EAElE,MAAM,QACJ,SAAS,OAAO,SAAS,WACrB;GACE,OAAO,SAAS,MAAM,gBAAgB;GACtC,QAAQ,SAAS,MAAM,iBAAiB;GACxC,OAAO,SAAS,MAAM,gBAAgB;EACxC,IACA;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAEtC,OAAO;GACL,MAAM,SAAS;GACf,GAAI,YAAY,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;GACtD,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;GAC3D;EACF;CACF;AACF"}