@warlock.js/ai-openai 5.28.0 → 5.30.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 (62) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/cjs/index.cjs +21 -21
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +1 -1
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/embedder.d.mts +1 -1
  7. package/esm/embedder.d.mts.map +1 -1
  8. package/esm/embedder.mjs +1 -1
  9. package/esm/embedder.mjs.map +1 -1
  10. package/esm/image.d.mts +1 -1
  11. package/esm/image.d.mts.map +1 -1
  12. package/esm/image.mjs +1 -1
  13. package/esm/image.mjs.map +1 -1
  14. package/esm/known-reasoning-models.mjs +1 -1
  15. package/esm/known-reasoning-models.mjs.map +1 -1
  16. package/esm/known-vision-models.mjs +1 -1
  17. package/esm/known-vision-models.mjs.map +1 -1
  18. package/esm/model.mjs +1 -1
  19. package/esm/model.mjs.map +1 -1
  20. package/esm/responses-model.mjs +1 -1
  21. package/esm/responses-model.mjs.map +1 -1
  22. package/esm/sdk.d.mts +1 -1
  23. package/esm/sdk.d.mts.map +1 -1
  24. package/esm/sdk.mjs +1 -1
  25. package/esm/sdk.mjs.map +1 -1
  26. package/esm/speech.d.mts +1 -1
  27. package/esm/speech.d.mts.map +1 -1
  28. package/esm/speech.mjs +1 -1
  29. package/esm/speech.mjs.map +1 -1
  30. package/esm/transcription.d.mts +1 -1
  31. package/esm/transcription.d.mts.map +1 -1
  32. package/esm/transcription.mjs +1 -1
  33. package/esm/transcription.mjs.map +1 -1
  34. package/esm/utils/build-usage.mjs +1 -1
  35. package/esm/utils/build-usage.mjs.map +1 -1
  36. package/esm/utils/map-finish-reason.mjs +1 -1
  37. package/esm/utils/map-finish-reason.mjs.map +1 -1
  38. package/esm/utils/map-responses-status.mjs +1 -1
  39. package/esm/utils/map-responses-status.mjs.map +1 -1
  40. package/esm/utils/prompt-cache-params.mjs +1 -1
  41. package/esm/utils/prompt-cache-params.mjs.map +1 -1
  42. package/esm/utils/reasoning-effort.mjs +1 -1
  43. package/esm/utils/reasoning-effort.mjs.map +1 -1
  44. package/esm/utils/reasoning-replay.mjs +1 -1
  45. package/esm/utils/reasoning-replay.mjs.map +1 -1
  46. package/esm/utils/structured-output.mjs +1 -1
  47. package/esm/utils/structured-output.mjs.map +1 -1
  48. package/esm/utils/to-openai-messages.mjs +1 -1
  49. package/esm/utils/to-openai-messages.mjs.map +1 -1
  50. package/esm/utils/to-openai-tools.mjs +1 -1
  51. package/esm/utils/to-openai-tools.mjs.map +1 -1
  52. package/esm/utils/to-responses-input.mjs +1 -1
  53. package/esm/utils/to-responses-input.mjs.map +1 -1
  54. package/esm/utils/to-responses-tools.mjs +1 -1
  55. package/esm/utils/to-responses-tools.mjs.map +1 -1
  56. package/esm/utils/wrap-openai-error.mjs +1 -1
  57. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  58. package/llms-full.txt +7 -7
  59. package/llms.txt +1 -1
  60. package/package.json +5 -4
  61. package/skills/README.md +2 -2
  62. package/skills/setup-openai/SKILL.md +7 -7
@@ -2,7 +2,7 @@ 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
4
 
5
- //#region ../ai-openai/src/transcription.d.ts
5
+ //#region ../../packages/ai-openai/src/transcription.d.ts
6
6
  /**
7
7
  * OpenAI-backed implementation of `TranscriptionModelContract`
8
8
  * (speech-to-text) via `audio.transcriptions.create`. Consumed by the
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.d.mts","names":[],"sources":["../../../../../../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":";;;;;;;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"}
@@ -3,7 +3,7 @@ import "./utils/index.mjs";
3
3
  import { toFile } from "openai";
4
4
  import { log } from "@warlock.js/logger";
5
5
 
6
- //#region ../ai-openai/src/transcription.ts
6
+ //#region ../../packages/ai-openai/src/transcription.ts
7
7
  const LOG_MODULE = "ai.openai";
8
8
  /**
9
9
  * OpenAI-backed implementation of `TranscriptionModelContract`
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.mjs","names":[],"sources":["../../../../../../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 ?? (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,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/build-usage.ts
1
+ //#region ../../packages/ai-openai/src/utils/build-usage.ts
2
2
  /**
3
3
  * Build the neutral `Usage` from extracted counts. The optional detail
4
4
  * counters are emitted only when the provider reports a positive value, so
@@ -1 +1 @@
1
- {"version":3,"file":"build-usage.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/build-usage.ts"],"sourcesContent":["import type { Usage } from \"@warlock.js/ai\";\n\n/** The already-extracted token counts of one provider usage block. */\nexport type UsageCounts = {\n input: number;\n output: number;\n total: number;\n /** Subset of `input` served from the prompt cache. */\n cachedTokens?: number;\n /** Hidden reasoning channel, already counted within `output`. */\n reasoningTokens?: number;\n /** Subset of `input` written to the prompt cache. */\n cacheWriteTokens?: number;\n};\n\n/**\n * Build the neutral `Usage` from extracted counts. The optional detail\n * counters are emitted only when the provider reports a positive value, so\n * non-reasoning / uncached calls keep the lean `{ input, output, total }`\n * shape. Shared by the Chat Completions and Responses adapters so both\n * apply the same rule.\n */\nexport function buildUsage(counts: UsageCounts): Usage {\n const { input, output, total, cachedTokens, reasoningTokens, cacheWriteTokens } = counts;\n\n return {\n input,\n output,\n total,\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n ...(reasoningTokens !== undefined && reasoningTokens > 0 ? { reasoningTokens } : {}),\n ...(cacheWriteTokens !== undefined && cacheWriteTokens > 0 ? { cacheWriteTokens } : {}),\n };\n}\n"],"mappings":";;;;;;;;AAsBA,SAAgB,WAAW,QAA4B;CACrD,MAAM,EAAE,OAAO,QAAQ,OAAO,cAAc,iBAAiB,qBAAqB;CAElF,OAAO;EACL;EACA;EACA;EACA,GAAI,iBAAiB,UAAa,eAAe,IAAI,EAAE,aAAa,IAAI,CAAC;EACzE,GAAI,oBAAoB,UAAa,kBAAkB,IAAI,EAAE,gBAAgB,IAAI,CAAC;EAClF,GAAI,qBAAqB,UAAa,mBAAmB,IAAI,EAAE,iBAAiB,IAAI,CAAC;CACvF;AACF"}
1
+ {"version":3,"file":"build-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/build-usage.ts"],"sourcesContent":["import type { Usage } from \"@warlock.js/ai\";\n\n/** The already-extracted token counts of one provider usage block. */\nexport type UsageCounts = {\n input: number;\n output: number;\n total: number;\n /** Subset of `input` served from the prompt cache. */\n cachedTokens?: number;\n /** Hidden reasoning channel, already counted within `output`. */\n reasoningTokens?: number;\n /** Subset of `input` written to the prompt cache. */\n cacheWriteTokens?: number;\n};\n\n/**\n * Build the neutral `Usage` from extracted counts. The optional detail\n * counters are emitted only when the provider reports a positive value, so\n * non-reasoning / uncached calls keep the lean `{ input, output, total }`\n * shape. Shared by the Chat Completions and Responses adapters so both\n * apply the same rule.\n */\nexport function buildUsage(counts: UsageCounts): Usage {\n const { input, output, total, cachedTokens, reasoningTokens, cacheWriteTokens } = counts;\n\n return {\n input,\n output,\n total,\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n ...(reasoningTokens !== undefined && reasoningTokens > 0 ? { reasoningTokens } : {}),\n ...(cacheWriteTokens !== undefined && cacheWriteTokens > 0 ? { cacheWriteTokens } : {}),\n };\n}\n"],"mappings":";;;;;;;;AAsBA,SAAgB,WAAW,QAA4B;CACrD,MAAM,EAAE,OAAO,QAAQ,OAAO,cAAc,iBAAiB,qBAAqB;CAElF,OAAO;EACL;EACA;EACA;EACA,GAAI,iBAAiB,UAAa,eAAe,IAAI,EAAE,aAAa,IAAI,CAAC;EACzE,GAAI,oBAAoB,UAAa,kBAAkB,IAAI,EAAE,gBAAgB,IAAI,CAAC;EAClF,GAAI,qBAAqB,UAAa,mBAAmB,IAAI,EAAE,iBAAiB,IAAI,CAAC;CACvF;AACF"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/map-finish-reason.ts
1
+ //#region ../../packages/ai-openai/src/utils/map-finish-reason.ts
2
2
  const finishReasonMap = {
3
3
  stop: "stop",
4
4
  tool_calls: "tool_calls",
@@ -1 +1 @@
1
- {"version":3,"file":"map-finish-reason.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/map-finish-reason.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst finishReasonMap: Record<string, FinishReason> = {\n stop: \"stop\",\n tool_calls: \"tool_calls\",\n length: \"length\",\n};\n\n/**\n * Map the raw OpenAI `finish_reason` string to the normalized FinishReason union.\n * Unknown/unexpected values fall through to \"error\".\n *\n * @example\n * mapFinishReason(\"stop\"); // \"stop\"\n * mapFinishReason(\"tool_calls\"); // \"tool_calls\"\n * mapFinishReason(null); // \"error\"\n */\nexport function mapFinishReason(raw: string | null | undefined): FinishReason {\n return finishReasonMap[raw ?? \"\"] ?? \"error\";\n}\n"],"mappings":";AAEA,MAAM,kBAAgD;CACpD,MAAM;CACN,YAAY;CACZ,QAAQ;AACV;;;;;;;;;;AAWA,SAAgB,gBAAgB,KAA8C;CAC5E,OAAO,gBAAgB,OAAO,OAAO;AACvC"}
1
+ {"version":3,"file":"map-finish-reason.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/map-finish-reason.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst finishReasonMap: Record<string, FinishReason> = {\n stop: \"stop\",\n tool_calls: \"tool_calls\",\n length: \"length\",\n};\n\n/**\n * Map the raw OpenAI `finish_reason` string to the normalized FinishReason union.\n * Unknown/unexpected values fall through to \"error\".\n *\n * @example\n * mapFinishReason(\"stop\"); // \"stop\"\n * mapFinishReason(\"tool_calls\"); // \"tool_calls\"\n * mapFinishReason(null); // \"error\"\n */\nexport function mapFinishReason(raw: string | null | undefined): FinishReason {\n return finishReasonMap[raw ?? \"\"] ?? \"error\";\n}\n"],"mappings":";AAEA,MAAM,kBAAgD;CACpD,MAAM;CACN,YAAY;CACZ,QAAQ;AACV;;;;;;;;;;AAWA,SAAgB,gBAAgB,KAA8C;CAC5E,OAAO,gBAAgB,OAAO,OAAO;AACvC"}
@@ -1,6 +1,6 @@
1
1
  import { wrapOpenAIError } from "./wrap-openai-error.mjs";
2
2
 
3
- //#region ../ai-openai/src/utils/map-responses-status.ts
3
+ //#region ../../packages/ai-openai/src/utils/map-responses-status.ts
4
4
  /**
5
5
  * Derive our `FinishReason` from a Responses terminal state. Responses has
6
6
  * no `finish_reason`, so it comes from `status`, `incomplete_details` and the
@@ -1 +1 @@
1
- {"version":3,"file":"map-responses-status.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/map-responses-status.ts"],"sourcesContent":["import type { AIError, FinishReason } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { wrapOpenAIError } from \"./wrap-openai-error\";\n\n/** The slice of a Responses `Response` the status mapper reads. */\nexport type ResponsesTerminalState = Pick<\n OpenAI.Responses.Response,\n \"status\" | \"incomplete_details\"\n> & {\n /** True when `output` holds at least one `function_call` item the model completed. */\n hasToolCalls: boolean;\n};\n\n/**\n * Derive our `FinishReason` from a Responses terminal state. Responses has\n * no `finish_reason`, so it comes from `status`, `incomplete_details` and the\n * output items (design section 2, decision 5):\n *\n * - `completed` with a function call -> `tool_calls`; without -> `stop`\n * (a refusal is ordinary text and also `stop`).\n * - `incomplete` / `max_output_tokens` and `max_messages` -> `length`.\n * - `incomplete` / `content_filter` and `steered` (or an unknown / missing\n * reason) -> `error`. There is no neutral `content_filter` value.\n * - `cancelled`, `queued`, `in_progress` at a terminal read -> `error`.\n * - `failed` is not mapped here: callers throw it via {@link toResponseFailure}.\n *\n * A missing `status` is treated as `completed` (the SDK types it optional).\n */\nexport function mapResponsesFinishReason(state: ResponsesTerminalState): FinishReason {\n const status = state.status ?? \"completed\";\n\n if (status === \"completed\") {\n return state.hasToolCalls ? \"tool_calls\" : \"stop\";\n }\n\n if (status === \"incomplete\") {\n const reason = state.incomplete_details?.reason;\n\n return reason === \"max_output_tokens\" || reason === \"max_messages\" ? \"length\" : \"error\";\n }\n\n return \"error\";\n}\n\n/**\n * Turn a `failed` Responses payload into the same typed `AIError` the Chat\n * path throws for a provider failure, by running it through\n * `wrapOpenAIError` (so `rate_limit_exceeded` becomes a\n * `ProviderRateLimitError`, everything else a `ProviderError`, with `code`\n * and request id kept on `context`). The response id and model are added so\n * the failure is traceable.\n */\nexport function toResponseFailure(\n response: OpenAI.Responses.Response,\n model: string,\n): AIError {\n const error = response.error;\n const requestId = (response as { _request_id?: string | null })._request_id;\n\n const wrapped = wrapOpenAIError({\n code: error?.code,\n message: error?.message ?? \"OpenAI response failed without an error object.\",\n ...(requestId ? { request_id: requestId } : {}),\n });\n\n if (wrapped.context) {\n Object.assign(wrapped.context, { provider: \"openai\", model, responseId: response.id });\n }\n\n return wrapped;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4BA,SAAgB,yBAAyB,OAA6C;CACpF,MAAM,SAAS,MAAM,UAAU;CAE/B,IAAI,WAAW,aACb,OAAO,MAAM,eAAe,eAAe;CAG7C,IAAI,WAAW,cAAc;EAC3B,MAAM,SAAS,MAAM,oBAAoB;EAEzC,OAAO,WAAW,uBAAuB,WAAW,iBAAiB,WAAW;CAClF;CAEA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,kBACd,UACA,OACS;CACT,MAAM,QAAQ,SAAS;CACvB,MAAM,YAAa,SAA6C;CAEhE,MAAM,UAAU,gBAAgB;EAC9B,MAAM,OAAO;EACb,SAAS,OAAO,WAAW;EAC3B,GAAI,YAAY,EAAE,YAAY,UAAU,IAAI,CAAC;CAC/C,CAAC;CAED,IAAI,QAAQ,SACV,OAAO,OAAO,QAAQ,SAAS;EAAE,UAAU;EAAU;EAAO,YAAY,SAAS;CAAG,CAAC;CAGvF,OAAO;AACT"}
1
+ {"version":3,"file":"map-responses-status.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/map-responses-status.ts"],"sourcesContent":["import type { AIError, FinishReason } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { wrapOpenAIError } from \"./wrap-openai-error\";\n\n/** The slice of a Responses `Response` the status mapper reads. */\nexport type ResponsesTerminalState = Pick<\n OpenAI.Responses.Response,\n \"status\" | \"incomplete_details\"\n> & {\n /** True when `output` holds at least one `function_call` item the model completed. */\n hasToolCalls: boolean;\n};\n\n/**\n * Derive our `FinishReason` from a Responses terminal state. Responses has\n * no `finish_reason`, so it comes from `status`, `incomplete_details` and the\n * output items (design section 2, decision 5):\n *\n * - `completed` with a function call -> `tool_calls`; without -> `stop`\n * (a refusal is ordinary text and also `stop`).\n * - `incomplete` / `max_output_tokens` and `max_messages` -> `length`.\n * - `incomplete` / `content_filter` and `steered` (or an unknown / missing\n * reason) -> `error`. There is no neutral `content_filter` value.\n * - `cancelled`, `queued`, `in_progress` at a terminal read -> `error`.\n * - `failed` is not mapped here: callers throw it via {@link toResponseFailure}.\n *\n * A missing `status` is treated as `completed` (the SDK types it optional).\n */\nexport function mapResponsesFinishReason(state: ResponsesTerminalState): FinishReason {\n const status = state.status ?? \"completed\";\n\n if (status === \"completed\") {\n return state.hasToolCalls ? \"tool_calls\" : \"stop\";\n }\n\n if (status === \"incomplete\") {\n const reason = state.incomplete_details?.reason;\n\n return reason === \"max_output_tokens\" || reason === \"max_messages\" ? \"length\" : \"error\";\n }\n\n return \"error\";\n}\n\n/**\n * Turn a `failed` Responses payload into the same typed `AIError` the Chat\n * path throws for a provider failure, by running it through\n * `wrapOpenAIError` (so `rate_limit_exceeded` becomes a\n * `ProviderRateLimitError`, everything else a `ProviderError`, with `code`\n * and request id kept on `context`). The response id and model are added so\n * the failure is traceable.\n */\nexport function toResponseFailure(\n response: OpenAI.Responses.Response,\n model: string,\n): AIError {\n const error = response.error;\n const requestId = (response as { _request_id?: string | null })._request_id;\n\n const wrapped = wrapOpenAIError({\n code: error?.code,\n message: error?.message ?? \"OpenAI response failed without an error object.\",\n ...(requestId ? { request_id: requestId } : {}),\n });\n\n if (wrapped.context) {\n Object.assign(wrapped.context, { provider: \"openai\", model, responseId: response.id });\n }\n\n return wrapped;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4BA,SAAgB,yBAAyB,OAA6C;CACpF,MAAM,SAAS,MAAM,UAAU;CAE/B,IAAI,WAAW,aACb,OAAO,MAAM,eAAe,eAAe;CAG7C,IAAI,WAAW,cAAc;EAC3B,MAAM,SAAS,MAAM,oBAAoB;EAEzC,OAAO,WAAW,uBAAuB,WAAW,iBAAiB,WAAW;CAClF;CAEA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,kBACd,UACA,OACS;CACT,MAAM,QAAQ,SAAS;CACvB,MAAM,YAAa,SAA6C;CAEhE,MAAM,UAAU,gBAAgB;EAC9B,MAAM,OAAO;EACb,SAAS,OAAO,WAAW;EAC3B,GAAI,YAAY,EAAE,YAAY,UAAU,IAAI,CAAC;CAC/C,CAAC;CAED,IAAI,QAAQ,SACV,OAAO,OAAO,QAAQ,SAAS;EAAE,UAAU;EAAU;EAAO,YAAY,SAAS;CAAG,CAAC;CAGvF,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/prompt-cache-params.ts
1
+ //#region ../../packages/ai-openai/src/utils/prompt-cache-params.ts
2
2
  /**
3
3
  * Build the OpenAI-only prompt-cache request fields from the model config.
4
4
  * Returns nothing for any provider label other than `"openai"`: wrapper
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-cache-params.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/prompt-cache-params.ts"],"sourcesContent":["import type { OpenAIModelConfig } from \"../config.type\";\n\n/** The OpenAI-only prompt-cache request fields common to Chat Completions and Responses. */\nexport type PromptCacheParams = {\n prompt_cache_key?: string;\n prompt_cache_retention?: NonNullable<OpenAIModelConfig[\"promptCacheRetention\"]>;\n};\n\n/**\n * Build the OpenAI-only prompt-cache request fields from the model config.\n * Returns nothing for any provider label other than `\"openai\"`: wrapper\n * endpoints use the same transport but may reject these OpenAI-only fields.\n */\nexport function buildPromptCacheParams(\n provider: string,\n config: Pick<OpenAIModelConfig, \"promptCacheKey\" | \"promptCacheRetention\">,\n): PromptCacheParams {\n if (provider !== \"openai\") {\n return {};\n }\n\n return {\n ...(config.promptCacheKey !== undefined ? { prompt_cache_key: config.promptCacheKey } : {}),\n ...(config.promptCacheRetention !== undefined\n ? { prompt_cache_retention: config.promptCacheRetention }\n : {}),\n };\n}\n"],"mappings":";;;;;;AAaA,SAAgB,uBACd,UACA,QACmB;CACnB,IAAI,aAAa,UACf,OAAO,CAAC;CAGV,OAAO;EACL,GAAI,OAAO,mBAAmB,SAAY,EAAE,kBAAkB,OAAO,eAAe,IAAI,CAAC;EACzF,GAAI,OAAO,yBAAyB,SAChC,EAAE,wBAAwB,OAAO,qBAAqB,IACtD,CAAC;CACP;AACF"}
1
+ {"version":3,"file":"prompt-cache-params.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/prompt-cache-params.ts"],"sourcesContent":["import type { OpenAIModelConfig } from \"../config.type\";\n\n/** The OpenAI-only prompt-cache request fields common to Chat Completions and Responses. */\nexport type PromptCacheParams = {\n prompt_cache_key?: string;\n prompt_cache_retention?: NonNullable<OpenAIModelConfig[\"promptCacheRetention\"]>;\n};\n\n/**\n * Build the OpenAI-only prompt-cache request fields from the model config.\n * Returns nothing for any provider label other than `\"openai\"`: wrapper\n * endpoints use the same transport but may reject these OpenAI-only fields.\n */\nexport function buildPromptCacheParams(\n provider: string,\n config: Pick<OpenAIModelConfig, \"promptCacheKey\" | \"promptCacheRetention\">,\n): PromptCacheParams {\n if (provider !== \"openai\") {\n return {};\n }\n\n return {\n ...(config.promptCacheKey !== undefined ? { prompt_cache_key: config.promptCacheKey } : {}),\n ...(config.promptCacheRetention !== undefined\n ? { prompt_cache_retention: config.promptCacheRetention }\n : {}),\n };\n}\n"],"mappings":";;;;;;AAaA,SAAgB,uBACd,UACA,QACmB;CACnB,IAAI,aAAa,UACf,OAAO,CAAC;CAGV,OAAO;EACL,GAAI,OAAO,mBAAmB,SAAY,EAAE,kBAAkB,OAAO,eAAe,IAAI,CAAC;EACzF,GAAI,OAAO,yBAAyB,SAChC,EAAE,wBAAwB,OAAO,qBAAqB,IACtD,CAAC;CACP;AACF"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/reasoning-effort.ts
1
+ //#region ../../packages/ai-openai/src/utils/reasoning-effort.ts
2
2
  /**
3
3
  * The installed OpenAI SDK 7.23.0 declares `reasoning_effort` as
4
4
  * `none | minimal | low | medium | high | xhigh | max` in
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-effort.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/reasoning-effort.ts"],"sourcesContent":["import type { ReasoningEffort } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/** The effort strings the OpenAI wire accepts (shared by Chat Completions and Responses). */\nexport type OpenAIReasoningEffort = NonNullable<\n OpenAI.Chat.Completions.ChatCompletionCreateParams[\"reasoning_effort\"]\n>;\n\n/**\n * The installed OpenAI SDK 7.23.0 declares `reasoning_effort` as\n * `none | minimal | low | medium | high | xhigh | max` in\n * `openai/src/resources/shared.ts:367`. Its type notes that model support\n * varies but does not provide a model-family-specific accepted set, so map by\n * that union alone. This exhaustive record makes a future core level a\n * typecheck failure until its OpenAI clamp is consciously selected.\n *\n * Both the Chat Completions adapter (`reasoning_effort`) and the Responses\n * adapter (`reasoning.effort`) read this one table, so the two can never\n * disagree on how a neutral level reaches the wire.\n */\nconst EFFORT_TO_OPENAI_EFFORT: Record<ReasoningEffort, OpenAIReasoningEffort> = {\n none: \"none\",\n minimal: \"minimal\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"xhigh\",\n max: \"max\",\n};\n\n/**\n * Map a neutral `ReasoningEffort` to the value OpenAI accepts.\n *\n * @example\n * toOpenAIReasoningEffort(\"high\"); // \"high\"\n */\nexport function toOpenAIReasoningEffort(effort: ReasoningEffort): OpenAIReasoningEffort {\n return EFFORT_TO_OPENAI_EFFORT[effort];\n}\n"],"mappings":";;;;;;;;;;;;;AAoBA,MAAM,0BAA0E;CAC9E,MAAM;CACN,SAAS;CACT,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACP,KAAK;AACP;;;;;;;AAQA,SAAgB,wBAAwB,QAAgD;CACtF,OAAO,wBAAwB;AACjC"}
1
+ {"version":3,"file":"reasoning-effort.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/reasoning-effort.ts"],"sourcesContent":["import type { ReasoningEffort } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/** The effort strings the OpenAI wire accepts (shared by Chat Completions and Responses). */\nexport type OpenAIReasoningEffort = NonNullable<\n OpenAI.Chat.Completions.ChatCompletionCreateParams[\"reasoning_effort\"]\n>;\n\n/**\n * The installed OpenAI SDK 7.23.0 declares `reasoning_effort` as\n * `none | minimal | low | medium | high | xhigh | max` in\n * `openai/src/resources/shared.ts:367`. Its type notes that model support\n * varies but does not provide a model-family-specific accepted set, so map by\n * that union alone. This exhaustive record makes a future core level a\n * typecheck failure until its OpenAI clamp is consciously selected.\n *\n * Both the Chat Completions adapter (`reasoning_effort`) and the Responses\n * adapter (`reasoning.effort`) read this one table, so the two can never\n * disagree on how a neutral level reaches the wire.\n */\nconst EFFORT_TO_OPENAI_EFFORT: Record<ReasoningEffort, OpenAIReasoningEffort> = {\n none: \"none\",\n minimal: \"minimal\",\n low: \"low\",\n medium: \"medium\",\n high: \"high\",\n xhigh: \"xhigh\",\n max: \"max\",\n};\n\n/**\n * Map a neutral `ReasoningEffort` to the value OpenAI accepts.\n *\n * @example\n * toOpenAIReasoningEffort(\"high\"); // \"high\"\n */\nexport function toOpenAIReasoningEffort(effort: ReasoningEffort): OpenAIReasoningEffort {\n return EFFORT_TO_OPENAI_EFFORT[effort];\n}\n"],"mappings":";;;;;;;;;;;;;AAoBA,MAAM,0BAA0E;CAC9E,MAAM;CACN,SAAS;CACT,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACP,KAAK;AACP;;;;;;;AAQA,SAAgB,wBAAwB,QAAgD;CACtF,OAAO,wBAAwB;AACjC"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/reasoning-replay.ts
1
+ //#region ../../packages/ai-openai/src/utils/reasoning-replay.ts
2
2
  /**
3
3
  * Key under `ModelToolCallRequest.providerMetadata` owned by the Responses
4
4
  * adapter. This name and the shape below are PERSISTED in users' conversation
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-replay.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/reasoning-replay.ts"],"sourcesContent":["import type { ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/** A `reasoning` output item as the Responses API returns it. */\nexport type ResponsesReasoningItem = OpenAI.Responses.ResponseReasoningItem;\n\n/**\n * Key under `ModelToolCallRequest.providerMetadata` owned by the Responses\n * adapter. This name and the shape below are PERSISTED in users' conversation\n * memory, so they are part of the public contract: do not rename.\n */\nexport const RESPONSES_METADATA_KEY = \"openaiResponses\";\n\n/**\n * What the Responses adapter stores on the FIRST tool call of an assistant\n * turn so the next request can replay the model's reasoning (design\n * section 4, decisions 2 and 3).\n */\nexport type ResponsesReplayMetadata = {\n /** Model the items were produced by; replay is skipped when it differs. */\n model: string;\n /** Reasoning items of the turn, in the order the API emitted them. */\n reasoningItems: ResponsesReasoningItem[];\n};\n\n/**\n * Keep only the reasoning items that can actually be replayed. With\n * `store: false` the server keeps nothing, so an item without\n * `encrypted_content` has nothing to restore and replaying its bare `id`\n * would be rejected as an unknown item.\n */\nexport function pickReplayableReasoning(\n items: readonly ResponsesReasoningItem[],\n): ResponsesReasoningItem[] {\n return items.filter(\n (item) => typeof item.encrypted_content === \"string\" && item.encrypted_content !== \"\",\n );\n}\n\n/**\n * Attach the turn's reasoning items to the FIRST tool call only (parallel\n * calls do not each repeat the blobs). Returns the calls unchanged when\n * there is nothing to replay or no tool call to ride on (a text-only turn is\n * deliberately not replayed, decision 2). Does not mutate its input.\n */\nexport function attachReasoningReplay(\n toolCalls: readonly ModelToolCallRequest[],\n model: string,\n reasoningItems: readonly ResponsesReasoningItem[],\n): ModelToolCallRequest[] {\n const replayable = pickReplayableReasoning(reasoningItems);\n const [first, ...rest] = toolCalls;\n\n if (first === undefined || replayable.length === 0) {\n return [...toolCalls];\n }\n\n const metadata: ResponsesReplayMetadata = { model, reasoningItems: replayable };\n\n return [\n { ...first, providerMetadata: { ...first.providerMetadata, [RESPONSES_METADATA_KEY]: metadata } },\n ...rest,\n ];\n}\n\n/**\n * Read the stored reasoning items off an assistant turn's tool calls, for\n * replay on `model`. Returns `[]` when the key is absent or malformed, or\n * when the stored `model` differs from `model` (encrypted blobs are not\n * portable across models, design section 4). De-duplicates by item `id`.\n */\nexport function readReasoningReplay(\n toolCalls: readonly ModelToolCallRequest[],\n model: string,\n): ResponsesReasoningItem[] {\n const seen = new Set<string>();\n const items: ResponsesReasoningItem[] = [];\n\n for (const call of toolCalls) {\n const stored = call.providerMetadata?.[RESPONSES_METADATA_KEY];\n\n if (!isReplayMetadata(stored) || stored.model !== model) {\n continue;\n }\n\n for (const item of stored.reasoningItems) {\n if (item?.type !== \"reasoning\" || seen.has(item.id)) {\n continue;\n }\n\n seen.add(item.id);\n items.push(item);\n }\n }\n\n return items;\n}\n\n/**\n * Shape a stored reasoning item for the next request. Sends the four fields\n * a stateless replay needs (`type`, `id`, `summary`, `encrypted_content`) and\n * drops the response-only `status` and the optional raw `content`.\n *\n * UNVERIFIED: the SDK types accept the full item as input (`ResponseInputItem`\n * includes `ResponseReasoningItem`) and do not say which fields the API\n * refuses on replay with `store: false`. This is the conservative guess; a\n * live probe settles it.\n */\nexport function toReplayReasoningItem(item: ResponsesReasoningItem): ResponsesReasoningItem {\n return {\n type: \"reasoning\",\n id: item.id,\n summary: Array.isArray(item.summary) ? item.summary : [],\n encrypted_content: item.encrypted_content,\n };\n}\n\nfunction isReplayMetadata(value: unknown): value is ResponsesReplayMetadata {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const candidate = value as Partial<ResponsesReplayMetadata>;\n\n return typeof candidate.model === \"string\" && Array.isArray(candidate.reasoningItems);\n}\n"],"mappings":";;;;;;AAWA,MAAa,yBAAyB;;;;;;;AAoBtC,SAAgB,wBACd,OAC0B;CAC1B,OAAO,MAAM,QACV,SAAS,OAAO,KAAK,sBAAsB,YAAY,KAAK,sBAAsB,EACrF;AACF;;;;;;;AAQA,SAAgB,sBACd,WACA,OACA,gBACwB;CACxB,MAAM,aAAa,wBAAwB,cAAc;CACzD,MAAM,CAAC,OAAO,GAAG,QAAQ;CAEzB,IAAI,UAAU,UAAa,WAAW,WAAW,GAC/C,OAAO,CAAC,GAAG,SAAS;CAGtB,MAAM,WAAoC;EAAE;EAAO,gBAAgB;CAAW;CAE9E,OAAO,CACL;EAAE,GAAG;EAAO,kBAAkB;GAAE,GAAG,MAAM;IAAmB,yBAAyB;EAAS;CAAE,GAChG,GAAG,IACL;AACF;;;;;;;AAQA,SAAgB,oBACd,WACA,OAC0B;CAC1B,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,QAAkC,CAAC;CAEzC,KAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,SAAS,KAAK,mBAAmB;EAEvC,IAAI,CAAC,iBAAiB,MAAM,KAAK,OAAO,UAAU,OAChD;EAGF,KAAK,MAAM,QAAQ,OAAO,gBAAgB;GACxC,IAAI,MAAM,SAAS,eAAe,KAAK,IAAI,KAAK,EAAE,GAChD;GAGF,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,KAAK,IAAI;EACjB;CACF;CAEA,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,sBAAsB,MAAsD;CAC1F,OAAO;EACL,MAAM;EACN,IAAI,KAAK;EACT,SAAS,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;EACvD,mBAAmB,KAAK;CAC1B;AACF;AAEA,SAAS,iBAAiB,OAAkD;CAC1E,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,YAAY;CAElB,OAAO,OAAO,UAAU,UAAU,YAAY,MAAM,QAAQ,UAAU,cAAc;AACtF"}
1
+ {"version":3,"file":"reasoning-replay.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/reasoning-replay.ts"],"sourcesContent":["import type { ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/** A `reasoning` output item as the Responses API returns it. */\nexport type ResponsesReasoningItem = OpenAI.Responses.ResponseReasoningItem;\n\n/**\n * Key under `ModelToolCallRequest.providerMetadata` owned by the Responses\n * adapter. This name and the shape below are PERSISTED in users' conversation\n * memory, so they are part of the public contract: do not rename.\n */\nexport const RESPONSES_METADATA_KEY = \"openaiResponses\";\n\n/**\n * What the Responses adapter stores on the FIRST tool call of an assistant\n * turn so the next request can replay the model's reasoning (design\n * section 4, decisions 2 and 3).\n */\nexport type ResponsesReplayMetadata = {\n /** Model the items were produced by; replay is skipped when it differs. */\n model: string;\n /** Reasoning items of the turn, in the order the API emitted them. */\n reasoningItems: ResponsesReasoningItem[];\n};\n\n/**\n * Keep only the reasoning items that can actually be replayed. With\n * `store: false` the server keeps nothing, so an item without\n * `encrypted_content` has nothing to restore and replaying its bare `id`\n * would be rejected as an unknown item.\n */\nexport function pickReplayableReasoning(\n items: readonly ResponsesReasoningItem[],\n): ResponsesReasoningItem[] {\n return items.filter(\n (item) => typeof item.encrypted_content === \"string\" && item.encrypted_content !== \"\",\n );\n}\n\n/**\n * Attach the turn's reasoning items to the FIRST tool call only (parallel\n * calls do not each repeat the blobs). Returns the calls unchanged when\n * there is nothing to replay or no tool call to ride on (a text-only turn is\n * deliberately not replayed, decision 2). Does not mutate its input.\n */\nexport function attachReasoningReplay(\n toolCalls: readonly ModelToolCallRequest[],\n model: string,\n reasoningItems: readonly ResponsesReasoningItem[],\n): ModelToolCallRequest[] {\n const replayable = pickReplayableReasoning(reasoningItems);\n const [first, ...rest] = toolCalls;\n\n if (first === undefined || replayable.length === 0) {\n return [...toolCalls];\n }\n\n const metadata: ResponsesReplayMetadata = { model, reasoningItems: replayable };\n\n return [\n { ...first, providerMetadata: { ...first.providerMetadata, [RESPONSES_METADATA_KEY]: metadata } },\n ...rest,\n ];\n}\n\n/**\n * Read the stored reasoning items off an assistant turn's tool calls, for\n * replay on `model`. Returns `[]` when the key is absent or malformed, or\n * when the stored `model` differs from `model` (encrypted blobs are not\n * portable across models, design section 4). De-duplicates by item `id`.\n */\nexport function readReasoningReplay(\n toolCalls: readonly ModelToolCallRequest[],\n model: string,\n): ResponsesReasoningItem[] {\n const seen = new Set<string>();\n const items: ResponsesReasoningItem[] = [];\n\n for (const call of toolCalls) {\n const stored = call.providerMetadata?.[RESPONSES_METADATA_KEY];\n\n if (!isReplayMetadata(stored) || stored.model !== model) {\n continue;\n }\n\n for (const item of stored.reasoningItems) {\n if (item?.type !== \"reasoning\" || seen.has(item.id)) {\n continue;\n }\n\n seen.add(item.id);\n items.push(item);\n }\n }\n\n return items;\n}\n\n/**\n * Shape a stored reasoning item for the next request. Sends the four fields\n * a stateless replay needs (`type`, `id`, `summary`, `encrypted_content`) and\n * drops the response-only `status` and the optional raw `content`.\n *\n * UNVERIFIED: the SDK types accept the full item as input (`ResponseInputItem`\n * includes `ResponseReasoningItem`) and do not say which fields the API\n * refuses on replay with `store: false`. This is the conservative guess; a\n * live probe settles it.\n */\nexport function toReplayReasoningItem(item: ResponsesReasoningItem): ResponsesReasoningItem {\n return {\n type: \"reasoning\",\n id: item.id,\n summary: Array.isArray(item.summary) ? item.summary : [],\n encrypted_content: item.encrypted_content,\n };\n}\n\nfunction isReplayMetadata(value: unknown): value is ResponsesReplayMetadata {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const candidate = value as Partial<ResponsesReplayMetadata>;\n\n return typeof candidate.model === \"string\" && Array.isArray(candidate.reasoningItems);\n}\n"],"mappings":";;;;;;AAWA,MAAa,yBAAyB;;;;;;;AAoBtC,SAAgB,wBACd,OAC0B;CAC1B,OAAO,MAAM,QACV,SAAS,OAAO,KAAK,sBAAsB,YAAY,KAAK,sBAAsB,EACrF;AACF;;;;;;;AAQA,SAAgB,sBACd,WACA,OACA,gBACwB;CACxB,MAAM,aAAa,wBAAwB,cAAc;CACzD,MAAM,CAAC,OAAO,GAAG,QAAQ;CAEzB,IAAI,UAAU,UAAa,WAAW,WAAW,GAC/C,OAAO,CAAC,GAAG,SAAS;CAGtB,MAAM,WAAoC;EAAE;EAAO,gBAAgB;CAAW;CAE9E,OAAO,CACL;EAAE,GAAG;EAAO,kBAAkB;GAAE,GAAG,MAAM;IAAmB,yBAAyB;EAAS;CAAE,GAChG,GAAG,IACL;AACF;;;;;;;AAQA,SAAgB,oBACd,WACA,OAC0B;CAC1B,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,QAAkC,CAAC;CAEzC,KAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,SAAS,KAAK,mBAAmB;EAEvC,IAAI,CAAC,iBAAiB,MAAM,KAAK,OAAO,UAAU,OAChD;EAGF,KAAK,MAAM,QAAQ,OAAO,gBAAgB;GACxC,IAAI,MAAM,SAAS,eAAe,KAAK,IAAI,KAAK,EAAE,GAChD;GAGF,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,KAAK,IAAI;EACjB;CACF;CAEA,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,sBAAsB,MAAsD;CAC1F,OAAO;EACL,MAAM;EACN,IAAI,KAAK;EACT,SAAS,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;EACvD,mBAAmB,KAAK;CAC1B;AACF;AAEA,SAAS,iBAAiB,OAAkD;CAC1E,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,YAAY;CAElB,OAAO,OAAO,UAAU,UAAU,YAAY,MAAM,QAAQ,UAAU,cAAc;AACtF"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/utils/structured-output.ts
1
+ //#region ../../packages/ai-openai/src/utils/structured-output.ts
2
2
  /**
3
3
  * Map an explicit `responseFormat` override to the default
4
4
  * `structuredOutput` capability. Loose wire modes (`"json_object"`,
@@ -1 +1 @@
1
- {"version":3,"file":"structured-output.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/structured-output.ts"],"sourcesContent":["import type { OpenAIResponseFormat } from \"../config.type\";\n\n/**\n * How a `responseSchema` should reach the wire, decided once for both the\n * Chat Completions adapter (`response_format`) and the Responses adapter\n * (`text.format`). Only the wire location differs between the two.\n */\nexport type StructuredOutputPlan =\n | { mode: \"none\" }\n | { mode: \"json_object\" }\n | { mode: \"json_schema\"; schema: Record<string, unknown> };\n\n/**\n * Map an explicit `responseFormat` override to the default\n * `structuredOutput` capability. Loose wire modes (`\"json_object\"`,\n * `\"text\"`) don't enforce shape, so the agent needs to see the soft\n * schema hint in the system prompt — that only happens when the\n * capability is `false`. Default (no override) stays `true` to\n * preserve the prior assumption that OpenAI models support strict\n * structured output.\n */\nexport function inferStructuredOutput(\n responseFormat: OpenAIResponseFormat | undefined,\n): boolean {\n if (responseFormat === \"json_object\" || responseFormat === \"text\") {\n return false;\n }\n\n return true;\n}\n\n/**\n * Decide the structured-output mode for a call.\n *\n * When `override` (the `responseFormat` config) is set, it wins: `\"text\"`\n * emits nothing, `\"json_object\"` always picks the loose mode, and\n * `\"json_schema\"` picks strict mode (with the same strict-compatibility\n * safety check - a malformed schema still degrades to `json_object` rather\n * than 400). The override exists because some targets (older OpenAI models,\n * OpenRouter routes, Ollama OpenAI-compat) reject strict `json_schema`\n * outright.\n *\n * When the override is omitted, strict `json_schema` mode (token-level\n * enforcement) is used only when the schema is a proper root-object JSON\n * Schema whose every object lists all of its properties in `required`. For\n * anything else it falls back to loose `json_object` mode. Client-side\n * Standard Schema `validate()` still enforces the full shape.\n */\nexport function planStructuredOutput(\n responseSchema: Record<string, unknown> | undefined,\n override: OpenAIResponseFormat | undefined,\n): StructuredOutputPlan {\n if (!responseSchema || override === \"text\") {\n return { mode: \"none\" };\n }\n\n if (override === \"json_object\") {\n return { mode: \"json_object\" };\n }\n\n // Either auto-select (no override) or explicit `\"json_schema\"`. The\n // strict-compat check still applies in the explicit case - a malformed or\n // non-object schema would 400 before sampling, so degrade to `json_object`.\n if (isStrictCompatible(responseSchema)) {\n return { mode: \"json_schema\", schema: responseSchema };\n }\n\n return { mode: \"json_object\" };\n}\n\n/**\n * OpenAI strict `json_schema` mode requires the root to be a JSON Schema\n * object type (`{ type: \"object\", properties: ... }`). Anything else\n * (top-level arrays, primitives, unknown shapes) is rejected with a 400\n * before a token is sampled. Checked structurally here so the first call\n * doesn't crash on a malformed extraction.\n */\nexport function isStrictCompatible(schema: Record<string, unknown>): boolean {\n return (\n schema.type === \"object\" &&\n typeof schema.properties === \"object\" &&\n schema.properties !== null &&\n isStrictSafeNode(schema)\n );\n}\n\n/**\n * Recursively check the one strict-mode rule schemas most often trip on:\n * every object must list ALL of its `properties` in `required` (OpenAI\n * strict has no notion of optional - optional fields must be expressed as\n * nullable, e.g. `type: [\"string\", \"null\"]`, and still appear in\n * `required`). A schema that violates this anywhere in the tree is NOT sent\n * in strict mode, so a hand-built or optional-bearing schema can't 400 the\n * call (\"'required' ... must include every key in properties\").\n */\nfunction isStrictSafeNode(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return true;\n }\n\n const record = node as Record<string, unknown>;\n\n if (record.type === \"object\" && record.properties && typeof record.properties === \"object\") {\n const properties = record.properties as Record<string, unknown>;\n const keys = Object.keys(properties);\n const required = Array.isArray(record.required) ? (record.required as unknown[]) : [];\n\n if (keys.some((key) => !required.includes(key))) {\n return false;\n }\n\n for (const key of keys) {\n if (!isStrictSafeNode(properties[key])) {\n return false;\n }\n }\n }\n\n if (record.items !== undefined && !isStrictSafeNode(record.items)) {\n return false;\n }\n\n for (const branch of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n const value = record[branch];\n\n if (Array.isArray(value) && value.some((sub) => !isStrictSafeNode(sub))) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,sBACd,gBACS;CACT,IAAI,mBAAmB,iBAAiB,mBAAmB,QACzD,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBACd,gBACA,UACsB;CACtB,IAAI,CAAC,kBAAkB,aAAa,QAClC,OAAO,EAAE,MAAM,OAAO;CAGxB,IAAI,aAAa,eACf,OAAO,EAAE,MAAM,cAAc;CAM/B,IAAI,mBAAmB,cAAc,GACnC,OAAO;EAAE,MAAM;EAAe,QAAQ;CAAe;CAGvD,OAAO,EAAE,MAAM,cAAc;AAC/B;;;;;;;;AASA,SAAgB,mBAAmB,QAA0C;CAC3E,OACE,OAAO,SAAS,YAChB,OAAO,OAAO,eAAe,YAC7B,OAAO,eAAe,QACtB,iBAAiB,MAAM;AAE3B;;;;;;;;;;AAWA,SAAS,iBAAiB,MAAwB;CAChD,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,OAAO;CAGT,MAAM,SAAS;CAEf,IAAI,OAAO,SAAS,YAAY,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;EAC1F,MAAM,aAAa,OAAO;EAC1B,MAAM,OAAO,OAAO,KAAK,UAAU;EACnC,MAAM,WAAW,MAAM,QAAQ,OAAO,QAAQ,IAAK,OAAO,WAAyB,CAAC;EAEpF,IAAI,KAAK,MAAM,QAAQ,CAAC,SAAS,SAAS,GAAG,CAAC,GAC5C,OAAO;EAGT,KAAK,MAAM,OAAO,MAChB,IAAI,CAAC,iBAAiB,WAAW,IAAI,GACnC,OAAO;CAGb;CAEA,IAAI,OAAO,UAAU,UAAa,CAAC,iBAAiB,OAAO,KAAK,GAC9D,OAAO;CAGT,KAAK,MAAM,UAAU;EAAC;EAAS;EAAS;CAAO,GAAY;EACzD,MAAM,QAAQ,OAAO;EAErB,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC,iBAAiB,GAAG,CAAC,GACpE,OAAO;CAEX;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"structured-output.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/structured-output.ts"],"sourcesContent":["import type { OpenAIResponseFormat } from \"../config.type\";\n\n/**\n * How a `responseSchema` should reach the wire, decided once for both the\n * Chat Completions adapter (`response_format`) and the Responses adapter\n * (`text.format`). Only the wire location differs between the two.\n */\nexport type StructuredOutputPlan =\n | { mode: \"none\" }\n | { mode: \"json_object\" }\n | { mode: \"json_schema\"; schema: Record<string, unknown> };\n\n/**\n * Map an explicit `responseFormat` override to the default\n * `structuredOutput` capability. Loose wire modes (`\"json_object\"`,\n * `\"text\"`) don't enforce shape, so the agent needs to see the soft\n * schema hint in the system prompt — that only happens when the\n * capability is `false`. Default (no override) stays `true` to\n * preserve the prior assumption that OpenAI models support strict\n * structured output.\n */\nexport function inferStructuredOutput(\n responseFormat: OpenAIResponseFormat | undefined,\n): boolean {\n if (responseFormat === \"json_object\" || responseFormat === \"text\") {\n return false;\n }\n\n return true;\n}\n\n/**\n * Decide the structured-output mode for a call.\n *\n * When `override` (the `responseFormat` config) is set, it wins: `\"text\"`\n * emits nothing, `\"json_object\"` always picks the loose mode, and\n * `\"json_schema\"` picks strict mode (with the same strict-compatibility\n * safety check - a malformed schema still degrades to `json_object` rather\n * than 400). The override exists because some targets (older OpenAI models,\n * OpenRouter routes, Ollama OpenAI-compat) reject strict `json_schema`\n * outright.\n *\n * When the override is omitted, strict `json_schema` mode (token-level\n * enforcement) is used only when the schema is a proper root-object JSON\n * Schema whose every object lists all of its properties in `required`. For\n * anything else it falls back to loose `json_object` mode. Client-side\n * Standard Schema `validate()` still enforces the full shape.\n */\nexport function planStructuredOutput(\n responseSchema: Record<string, unknown> | undefined,\n override: OpenAIResponseFormat | undefined,\n): StructuredOutputPlan {\n if (!responseSchema || override === \"text\") {\n return { mode: \"none\" };\n }\n\n if (override === \"json_object\") {\n return { mode: \"json_object\" };\n }\n\n // Either auto-select (no override) or explicit `\"json_schema\"`. The\n // strict-compat check still applies in the explicit case - a malformed or\n // non-object schema would 400 before sampling, so degrade to `json_object`.\n if (isStrictCompatible(responseSchema)) {\n return { mode: \"json_schema\", schema: responseSchema };\n }\n\n return { mode: \"json_object\" };\n}\n\n/**\n * OpenAI strict `json_schema` mode requires the root to be a JSON Schema\n * object type (`{ type: \"object\", properties: ... }`). Anything else\n * (top-level arrays, primitives, unknown shapes) is rejected with a 400\n * before a token is sampled. Checked structurally here so the first call\n * doesn't crash on a malformed extraction.\n */\nexport function isStrictCompatible(schema: Record<string, unknown>): boolean {\n return (\n schema.type === \"object\" &&\n typeof schema.properties === \"object\" &&\n schema.properties !== null &&\n isStrictSafeNode(schema)\n );\n}\n\n/**\n * Recursively check the one strict-mode rule schemas most often trip on:\n * every object must list ALL of its `properties` in `required` (OpenAI\n * strict has no notion of optional - optional fields must be expressed as\n * nullable, e.g. `type: [\"string\", \"null\"]`, and still appear in\n * `required`). A schema that violates this anywhere in the tree is NOT sent\n * in strict mode, so a hand-built or optional-bearing schema can't 400 the\n * call (\"'required' ... must include every key in properties\").\n */\nfunction isStrictSafeNode(node: unknown): boolean {\n if (!node || typeof node !== \"object\") {\n return true;\n }\n\n const record = node as Record<string, unknown>;\n\n if (record.type === \"object\" && record.properties && typeof record.properties === \"object\") {\n const properties = record.properties as Record<string, unknown>;\n const keys = Object.keys(properties);\n const required = Array.isArray(record.required) ? (record.required as unknown[]) : [];\n\n if (keys.some((key) => !required.includes(key))) {\n return false;\n }\n\n for (const key of keys) {\n if (!isStrictSafeNode(properties[key])) {\n return false;\n }\n }\n }\n\n if (record.items !== undefined && !isStrictSafeNode(record.items)) {\n return false;\n }\n\n for (const branch of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n const value = record[branch];\n\n if (Array.isArray(value) && value.some((sub) => !isStrictSafeNode(sub))) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,sBACd,gBACS;CACT,IAAI,mBAAmB,iBAAiB,mBAAmB,QACzD,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBACd,gBACA,UACsB;CACtB,IAAI,CAAC,kBAAkB,aAAa,QAClC,OAAO,EAAE,MAAM,OAAO;CAGxB,IAAI,aAAa,eACf,OAAO,EAAE,MAAM,cAAc;CAM/B,IAAI,mBAAmB,cAAc,GACnC,OAAO;EAAE,MAAM;EAAe,QAAQ;CAAe;CAGvD,OAAO,EAAE,MAAM,cAAc;AAC/B;;;;;;;;AASA,SAAgB,mBAAmB,QAA0C;CAC3E,OACE,OAAO,SAAS,YAChB,OAAO,OAAO,eAAe,YAC7B,OAAO,eAAe,QACtB,iBAAiB,MAAM;AAE3B;;;;;;;;;;AAWA,SAAS,iBAAiB,MAAwB;CAChD,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,OAAO;CAGT,MAAM,SAAS;CAEf,IAAI,OAAO,SAAS,YAAY,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;EAC1F,MAAM,aAAa,OAAO;EAC1B,MAAM,OAAO,OAAO,KAAK,UAAU;EACnC,MAAM,WAAW,MAAM,QAAQ,OAAO,QAAQ,IAAK,OAAO,WAAyB,CAAC;EAEpF,IAAI,KAAK,MAAM,QAAQ,CAAC,SAAS,SAAS,GAAG,CAAC,GAC5C,OAAO;EAGT,KAAK,MAAM,OAAO,MAChB,IAAI,CAAC,iBAAiB,WAAW,IAAI,GACnC,OAAO;CAGb;CAEA,IAAI,OAAO,UAAU,UAAa,CAAC,iBAAiB,OAAO,KAAK,GAC9D,OAAO;CAGT,KAAK,MAAM,UAAU;EAAC;EAAS;EAAS;CAAO,GAAY;EACzD,MAAM,QAAQ,OAAO;EAErB,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC,iBAAiB,GAAG,CAAC,GACpE,OAAO;CAEX;CAEA,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  import { InvalidRequestError } from "@warlock.js/ai";
2
2
 
3
- //#region ../ai-openai/src/utils/to-openai-messages.ts
3
+ //#region ../../packages/ai-openai/src/utils/to-openai-messages.ts
4
4
  /**
5
5
  * Convert vendor-neutral Message[] to OpenAI's chat message shape.
6
6
  * Handles the `tool` role (requires `tool_call_id`) and assistant messages
@@ -1 +1 @@
1
- {"version":3,"file":"to-openai-messages.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/to-openai-messages.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/**\n * Convert vendor-neutral Message[] to OpenAI's chat message shape.\n * Handles the `tool` role (requires `tool_call_id`) and assistant messages\n * that carry `toolCalls` from a prior model response.\n *\n * Multipart `content` (a `ContentPart[]`) is mapped into OpenAI's user-message\n * content-parts shape: text becomes `{ type: \"text\", text }`, images become\n * `{ type: \"image_url\", image_url: { url } }` — with base64 sources rendered\n * as `data:` URLs inline.\n *\n * @example\n * const openaiMessages = toOpenAIMessages([\n * { role: \"user\", content: \"Hi\" },\n * { role: \"tool\", toolCallId: \"call_1\", content: '{\"ok\":true}' },\n * ]);\n *\n * @example\n * toOpenAIMessages([\n * { role: \"user\", content: [\n * { type: \"text\", text: \"What is this?\" },\n * { type: \"image\", source: { url: \"https://example.com/cat.jpg\" } },\n * ]},\n * ]);\n */\nexport function toOpenAIMessages(\n messages: Message[],\n cacheBreakpoints: number = 0,\n): OpenAI.Chat.Completions.ChatCompletionMessageParam[] {\n const mapped = messages.map((m) => {\n if (m.role === \"tool\") {\n return {\n role: \"tool\",\n content: stringifyContent(m.content),\n tool_call_id: m.toolCallId ?? \"\",\n } satisfies OpenAI.Chat.Completions.ChatCompletionToolMessageParam;\n }\n if (m.role === \"assistant\" && m.toolCalls && m.toolCalls.length > 0) {\n return {\n role: \"assistant\",\n content: stringifyContent(m.content),\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: JSON.stringify(tc.input ?? {}) },\n })),\n } satisfies OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam;\n }\n\n if (m.role === \"user\" && Array.isArray(m.content)) {\n return {\n role: \"user\",\n content: m.content.map(toOpenAIContentPart),\n } satisfies OpenAI.Chat.Completions.ChatCompletionUserMessageParam;\n }\n\n return { role: m.role, content: stringifyContent(m.content) } as\n | OpenAI.Chat.Completions.ChatCompletionUserMessageParam\n | OpenAI.Chat.Completions.ChatCompletionSystemMessageParam\n | OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam;\n });\n\n return addPromptCacheBreakpoints(mapped, cacheBreakpoints);\n}\n\n/** Place explicit markers on the last requested user-content parts (maximum four). */\nfunction addPromptCacheBreakpoints(\n messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n requested: number,\n): OpenAI.Chat.Completions.ChatCompletionMessageParam[] {\n let remaining = Math.min(4, Math.max(0, Math.floor(requested)));\n if (remaining === 0) return messages;\n\n const marked = [...messages];\n for (let index = marked.length - 1; index >= 0 && remaining > 0; index--) {\n const message = marked[index];\n if (message === undefined || message.role !== \"user\") continue;\n\n if (typeof message.content === \"string\") {\n marked[index] = {\n ...message,\n content: [\n { type: \"text\", text: message.content, prompt_cache_breakpoint: { mode: \"explicit\" } },\n ],\n };\n remaining--;\n continue;\n }\n\n const content = [...message.content];\n for (let partIndex = content.length - 1; partIndex >= 0 && remaining > 0; partIndex--) {\n const part = content[partIndex];\n if (part === undefined) continue;\n content[partIndex] = { ...part, prompt_cache_breakpoint: { mode: \"explicit\" } };\n remaining--;\n }\n marked[index] = { ...message, content };\n }\n\n return marked;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role (system / assistant text / tool), collapse a `ContentPart[]` to\n * its concatenated text so OpenAI's wire format stays valid. Plain\n * strings pass through unchanged.\n */\nexport function stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a resolved `ContentPart` to an OpenAI chat content part — one\n * branch per modality, each to its real wire shape:\n *\n * - `text` → `{ type: \"text\" }`.\n * - `image` → `{ type: \"image_url\" }` (remote URL, or a `data:` URL for\n * inlined base64 bytes).\n * - `pdf` → `{ type: \"file\", file: { file_data } }` (OpenAI document\n * input; base64 only — there is no remote-URL file source).\n * - `audio` → `{ type: \"input_audio\", input_audio: { data, format } }`\n * (base64 only; `wav` / `mp3` are the only formats OpenAI accepts).\n *\n * PDF and audio reach this point ONLY when the model declared the\n * matching capability (`openai.model({ name, pdf: true })` /\n * `{ audio: true }`) — the agent's modality gate throws upfront\n * otherwise, so capability and behavior stay in lockstep. A remote-URL\n * pdf/audio source raises a typed `InvalidRequestError` here rather\n * than a downstream provider fault.\n */\nfunction toOpenAIContentPart(part: ContentPart): OpenAI.Chat.Completions.ChatCompletionContentPart {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n if (part.type === \"image\") {\n return { type: \"image_url\", image_url: { url: toImageUrl(part.source) } };\n }\n\n if (part.type === \"pdf\") {\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"OpenAI chat completions cannot fetch a remote-URL PDF; supply base64 document bytes instead.\",\n );\n }\n\n return {\n type: \"file\",\n file: {\n filename: \"document.pdf\",\n file_data: `data:${part.source.mediaType};base64,${part.source.base64}`,\n },\n };\n }\n\n // Audio — the remaining `ContentPart` variant.\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"OpenAI chat completions cannot fetch remote-URL audio; supply base64 audio bytes instead.\",\n );\n }\n\n return {\n type: \"input_audio\",\n input_audio: {\n data: part.source.base64,\n format: toOpenAIAudioFormat(part.source.mediaType),\n },\n };\n}\n\n/**\n * Render an image source as a URL: a remote URL passes through, inlined\n * base64 bytes become a `data:` URL. Shared with the Responses adapter.\n */\nexport function toImageUrl(\n source: Extract<ContentPart, { type: \"image\" }>[\"source\"],\n): string {\n return \"url\" in source ? source.url : `data:${source.mediaType};base64,${source.base64}`;\n}\n\n/**\n * Narrow a neutral audio media type to the two formats OpenAI's\n * `input_audio` accepts (`wav` / `mp3`). An unsupported type raises a\n * typed `InvalidRequestError` up front rather than a provider 400.\n */\nfunction toOpenAIAudioFormat(mediaType: string): \"wav\" | \"mp3\" {\n if (mediaType === \"audio/wav\" || mediaType === \"audio/x-wav\" || mediaType === \"audio/wave\") {\n return \"wav\";\n }\n\n if (mediaType === \"audio/mp3\" || mediaType === \"audio/mpeg\" || mediaType === \"audio/mpga\") {\n return \"mp3\";\n }\n\n throw new InvalidRequestError(\n `OpenAI input_audio supports only \"wav\" and \"mp3\"; got \"${mediaType}\".`,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"to-openai-messages.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/to-openai-messages.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/**\n * Convert vendor-neutral Message[] to OpenAI's chat message shape.\n * Handles the `tool` role (requires `tool_call_id`) and assistant messages\n * that carry `toolCalls` from a prior model response.\n *\n * Multipart `content` (a `ContentPart[]`) is mapped into OpenAI's user-message\n * content-parts shape: text becomes `{ type: \"text\", text }`, images become\n * `{ type: \"image_url\", image_url: { url } }` — with base64 sources rendered\n * as `data:` URLs inline.\n *\n * @example\n * const openaiMessages = toOpenAIMessages([\n * { role: \"user\", content: \"Hi\" },\n * { role: \"tool\", toolCallId: \"call_1\", content: '{\"ok\":true}' },\n * ]);\n *\n * @example\n * toOpenAIMessages([\n * { role: \"user\", content: [\n * { type: \"text\", text: \"What is this?\" },\n * { type: \"image\", source: { url: \"https://example.com/cat.jpg\" } },\n * ]},\n * ]);\n */\nexport function toOpenAIMessages(\n messages: Message[],\n cacheBreakpoints: number = 0,\n): OpenAI.Chat.Completions.ChatCompletionMessageParam[] {\n const mapped = messages.map((m) => {\n if (m.role === \"tool\") {\n return {\n role: \"tool\",\n content: stringifyContent(m.content),\n tool_call_id: m.toolCallId ?? \"\",\n } satisfies OpenAI.Chat.Completions.ChatCompletionToolMessageParam;\n }\n if (m.role === \"assistant\" && m.toolCalls && m.toolCalls.length > 0) {\n return {\n role: \"assistant\",\n content: stringifyContent(m.content),\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: { name: tc.name, arguments: JSON.stringify(tc.input ?? {}) },\n })),\n } satisfies OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam;\n }\n\n if (m.role === \"user\" && Array.isArray(m.content)) {\n return {\n role: \"user\",\n content: m.content.map(toOpenAIContentPart),\n } satisfies OpenAI.Chat.Completions.ChatCompletionUserMessageParam;\n }\n\n return { role: m.role, content: stringifyContent(m.content) } as\n | OpenAI.Chat.Completions.ChatCompletionUserMessageParam\n | OpenAI.Chat.Completions.ChatCompletionSystemMessageParam\n | OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam;\n });\n\n return addPromptCacheBreakpoints(mapped, cacheBreakpoints);\n}\n\n/** Place explicit markers on the last requested user-content parts (maximum four). */\nfunction addPromptCacheBreakpoints(\n messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],\n requested: number,\n): OpenAI.Chat.Completions.ChatCompletionMessageParam[] {\n let remaining = Math.min(4, Math.max(0, Math.floor(requested)));\n if (remaining === 0) return messages;\n\n const marked = [...messages];\n for (let index = marked.length - 1; index >= 0 && remaining > 0; index--) {\n const message = marked[index];\n if (message === undefined || message.role !== \"user\") continue;\n\n if (typeof message.content === \"string\") {\n marked[index] = {\n ...message,\n content: [\n { type: \"text\", text: message.content, prompt_cache_breakpoint: { mode: \"explicit\" } },\n ],\n };\n remaining--;\n continue;\n }\n\n const content = [...message.content];\n for (let partIndex = content.length - 1; partIndex >= 0 && remaining > 0; partIndex--) {\n const part = content[partIndex];\n if (part === undefined) continue;\n content[partIndex] = { ...part, prompt_cache_breakpoint: { mode: \"explicit\" } };\n remaining--;\n }\n marked[index] = { ...message, content };\n }\n\n return marked;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role (system / assistant text / tool), collapse a `ContentPart[]` to\n * its concatenated text so OpenAI's wire format stays valid. Plain\n * strings pass through unchanged.\n */\nexport function stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a resolved `ContentPart` to an OpenAI chat content part — one\n * branch per modality, each to its real wire shape:\n *\n * - `text` → `{ type: \"text\" }`.\n * - `image` → `{ type: \"image_url\" }` (remote URL, or a `data:` URL for\n * inlined base64 bytes).\n * - `pdf` → `{ type: \"file\", file: { file_data } }` (OpenAI document\n * input; base64 only — there is no remote-URL file source).\n * - `audio` → `{ type: \"input_audio\", input_audio: { data, format } }`\n * (base64 only; `wav` / `mp3` are the only formats OpenAI accepts).\n *\n * PDF and audio reach this point ONLY when the model declared the\n * matching capability (`openai.model({ name, pdf: true })` /\n * `{ audio: true }`) — the agent's modality gate throws upfront\n * otherwise, so capability and behavior stay in lockstep. A remote-URL\n * pdf/audio source raises a typed `InvalidRequestError` here rather\n * than a downstream provider fault.\n */\nfunction toOpenAIContentPart(part: ContentPart): OpenAI.Chat.Completions.ChatCompletionContentPart {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n if (part.type === \"image\") {\n return { type: \"image_url\", image_url: { url: toImageUrl(part.source) } };\n }\n\n if (part.type === \"pdf\") {\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"OpenAI chat completions cannot fetch a remote-URL PDF; supply base64 document bytes instead.\",\n );\n }\n\n return {\n type: \"file\",\n file: {\n filename: \"document.pdf\",\n file_data: `data:${part.source.mediaType};base64,${part.source.base64}`,\n },\n };\n }\n\n // Audio — the remaining `ContentPart` variant.\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"OpenAI chat completions cannot fetch remote-URL audio; supply base64 audio bytes instead.\",\n );\n }\n\n return {\n type: \"input_audio\",\n input_audio: {\n data: part.source.base64,\n format: toOpenAIAudioFormat(part.source.mediaType),\n },\n };\n}\n\n/**\n * Render an image source as a URL: a remote URL passes through, inlined\n * base64 bytes become a `data:` URL. Shared with the Responses adapter.\n */\nexport function toImageUrl(\n source: Extract<ContentPart, { type: \"image\" }>[\"source\"],\n): string {\n return \"url\" in source ? source.url : `data:${source.mediaType};base64,${source.base64}`;\n}\n\n/**\n * Narrow a neutral audio media type to the two formats OpenAI's\n * `input_audio` accepts (`wav` / `mp3`). An unsupported type raises a\n * typed `InvalidRequestError` up front rather than a provider 400.\n */\nfunction toOpenAIAudioFormat(mediaType: string): \"wav\" | \"mp3\" {\n if (mediaType === \"audio/wav\" || mediaType === \"audio/x-wav\" || mediaType === \"audio/wave\") {\n return \"wav\";\n }\n\n if (mediaType === \"audio/mp3\" || mediaType === \"audio/mpeg\" || mediaType === \"audio/mpga\") {\n return \"mp3\";\n }\n\n throw new InvalidRequestError(\n `OpenAI input_audio supports only \"wav\" and \"mp3\"; got \"${mediaType}\".`,\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { extractJsonSchema } from "@warlock.js/ai";
2
2
 
3
- //#region ../ai-openai/src/utils/to-openai-tools.ts
3
+ //#region ../../packages/ai-openai/src/utils/to-openai-tools.ts
4
4
  /**
5
5
  * Convert vendor-neutral ToolConfig[] to OpenAI's tools array.
6
6
  * Uses the shared `extractJsonSchema` helper; falls back to an empty-object
@@ -1 +1 @@
1
- {"version":3,"file":"to-openai-tools.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/to-openai-tools.ts"],"sourcesContent":["import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/**\n * Convert vendor-neutral ToolConfig[] to OpenAI's tools array.\n * Uses the shared `extractJsonSchema` helper; falls back to an empty-object\n * schema when extraction fails so the tool still registers with the provider.\n *\n * @example\n * const tools = toOpenAITools([weatherTool, calculatorTool]);\n * await client.chat.completions.create({ model, messages, tools });\n */\nexport function toOpenAITools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): OpenAI.Chat.Completions.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: toToolParameters(tool.input),\n },\n }));\n}\n\n/**\n * Resolve a tool's input schema to a JSON-Schema object. OpenAI's\n * function `parameters` expects an object root; anything else (or a\n * failed extraction) degrades to an empty-object schema so the tool\n * still registers and the model simply sees no parameters.\n */\nexport function toToolParameters(input: ToolConfig<unknown, unknown>[\"input\"]): Record<string, unknown> {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema;\n }\n\n return { type: \"object\", properties: {} };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,cACd,OAC0D;CAC1D,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM;EACN,UAAU;GACR,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,YAAY,iBAAiB,KAAK,KAAK;EACzC;CACF,EAAE;AACJ;;;;;;;AAQA,SAAgB,iBAAiB,OAAuE;CACtG,MAAM,SAAS,kBAAkB,KAAK;CAEtC,IAAI,UAAU,OAAO,SAAS,UAC5B,OAAO;CAGT,OAAO;EAAE,MAAM;EAAU,YAAY,CAAC;CAAE;AAC1C"}
1
+ {"version":3,"file":"to-openai-tools.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/to-openai-tools.ts"],"sourcesContent":["import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\n\n/**\n * Convert vendor-neutral ToolConfig[] to OpenAI's tools array.\n * Uses the shared `extractJsonSchema` helper; falls back to an empty-object\n * schema when extraction fails so the tool still registers with the provider.\n *\n * @example\n * const tools = toOpenAITools([weatherTool, calculatorTool]);\n * await client.chat.completions.create({ model, messages, tools });\n */\nexport function toOpenAITools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): OpenAI.Chat.Completions.ChatCompletionTool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: toToolParameters(tool.input),\n },\n }));\n}\n\n/**\n * Resolve a tool's input schema to a JSON-Schema object. OpenAI's\n * function `parameters` expects an object root; anything else (or a\n * failed extraction) degrades to an empty-object schema so the tool\n * still registers and the model simply sees no parameters.\n */\nexport function toToolParameters(input: ToolConfig<unknown, unknown>[\"input\"]): Record<string, unknown> {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema;\n }\n\n return { type: \"object\", properties: {} };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,cACd,OAC0D;CAC1D,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM;EACN,UAAU;GACR,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,YAAY,iBAAiB,KAAK,KAAK;EACzC;CACF,EAAE;AACJ;;;;;;;AAQA,SAAgB,iBAAiB,OAAuE;CACtG,MAAM,SAAS,kBAAkB,KAAK;CAEtC,IAAI,UAAU,OAAO,SAAS,UAC5B,OAAO;CAGT,OAAO;EAAE,MAAM;EAAU,YAAY,CAAC;CAAE;AAC1C"}
@@ -2,7 +2,7 @@ import { readReasoningReplay, toReplayReasoningItem } from "./reasoning-replay.m
2
2
  import { stringifyContent, toImageUrl } from "./to-openai-messages.mjs";
3
3
  import { InvalidRequestError } from "@warlock.js/ai";
4
4
 
5
- //#region ../ai-openai/src/utils/to-responses-input.ts
5
+ //#region ../../packages/ai-openai/src/utils/to-responses-input.ts
6
6
  /**
7
7
  * Convert vendor-neutral `Message[]` into the Responses request pieces.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"to-responses-input.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/to-responses-input.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { stringifyContent, toImageUrl } from \"./to-openai-messages\";\nimport { readReasoningReplay, toReplayReasoningItem } from \"./reasoning-replay\";\n\n/** One item of the Responses `input` array. */\nexport type ResponsesInputItem = OpenAI.Responses.ResponseInputItem;\n\n/** What `toResponsesInput` produces: the `instructions` string plus the `input` items. */\nexport type ResponsesInputPlan = {\n /** The system prompt, sent as the top-level `instructions` field. */\n instructions?: string;\n input: ResponsesInputItem[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into the Responses request pieces.\n *\n * - The FIRST system message becomes `instructions` (decision 9: it avoids\n * the `system` vs `developer` role question on reasoning models). Any\n * later system message stays inline as a `system` role item.\n * - `user` / `assistant` text becomes an `EasyInputMessage` (`{ role,\n * content }`). Multipart user content becomes `input_text` / `input_image`\n * parts; images always carry `detail: \"auto\"` because `ResponseInputImage.detail`\n * is required by the SDK types.\n * - An assistant message that requested tools becomes its text (when\n * non-empty) followed by one top-level `function_call` item per call; our\n * `ModelToolCallRequest.id` is the wire `call_id`. No item `id` is sent:\n * with `store: false` nothing was persisted for it to refer to.\n * - When `model` is given, reasoning items stored on the turn's tool calls\n * (`providerMetadata.openaiResponses`, see `reasoning-replay.ts`) are\n * emitted BEFORE the turn's text and `function_call` items (the order the\n * API produced them), only if they were produced by that same model.\n * Without `model` nothing is replayed.\n * - A `tool` message becomes a top-level `function_call_output` item.\n *\n * PDF and audio parts are rejected with a typed `InvalidRequestError`: the\n * Responses adapter declares those capabilities off, so they only arrive\n * here if a caller bypasses the agent's capability gate.\n *\n * @example\n * toResponsesInput([\n * { role: \"system\", content: \"Be brief.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // { instructions: \"Be brief.\", input: [{ role: \"user\", content: \"Hi\" }] }\n */\nexport function toResponsesInput(messages: Message[], model?: string): ResponsesInputPlan {\n const input: ResponsesInputItem[] = [];\n let instructions: string | undefined;\n let instructionsTaken = false;\n\n for (const message of messages) {\n if (message.role === \"system\") {\n const text = stringifyContent(message.content);\n\n if (!instructionsTaken) {\n instructionsTaken = true;\n instructions = text === \"\" ? undefined : text;\n continue;\n }\n\n input.push({ role: \"system\", content: text });\n continue;\n }\n\n if (message.role === \"tool\") {\n input.push({\n type: \"function_call_output\",\n call_id: message.toolCallId ?? \"\",\n output: stringifyContent(message.content),\n });\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const text = stringifyContent(message.content);\n\n if (model !== undefined) {\n for (const item of readReasoningReplay(message.toolCalls, model)) {\n input.push(toReplayReasoningItem(item));\n }\n }\n\n if (text !== \"\") {\n input.push({ role: \"assistant\", content: text });\n }\n\n for (const call of message.toolCalls) {\n input.push({\n type: \"function_call\",\n call_id: call.id,\n name: call.name,\n arguments: JSON.stringify(call.input ?? {}),\n });\n }\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n input.push({ role: \"user\", content: message.content.map(toResponsesContentPart) });\n continue;\n }\n\n input.push({ role: message.role, content: stringifyContent(message.content) });\n }\n\n return { ...(instructions !== undefined ? { instructions } : {}), input };\n}\n\n/** Map a resolved `ContentPart` to a Responses input content part (text and images only). */\nfunction toResponsesContentPart(\n part: ContentPart,\n): OpenAI.Responses.ResponseInputText | OpenAI.Responses.ResponseInputImage {\n if (part.type === \"text\") {\n return { type: \"input_text\", text: part.text };\n }\n\n if (part.type === \"image\") {\n return { type: \"input_image\", image_url: toImageUrl(part.source), detail: \"auto\" };\n }\n\n throw new InvalidRequestError(\n `OpenAI api: \"responses\" does not accept ${part.type} input in this adapter; use api: \"chat\" for ${part.type} attachments.`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,iBAAiB,UAAqB,OAAoC;CACxF,MAAM,QAA8B,CAAC;CACrC,IAAI;CACJ,IAAI,oBAAoB;CAExB,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,CAAC,mBAAmB;IACtB,oBAAoB;IACpB,eAAe,SAAS,KAAK,SAAY;IACzC;GACF;GAEA,MAAM,KAAK;IAAE,MAAM;IAAU,SAAS;GAAK,CAAC;GAC5C;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,MAAM,KAAK;IACT,MAAM;IACN,SAAS,QAAQ,cAAc;IAC/B,QAAQ,iBAAiB,QAAQ,OAAO;GAC1C,CAAC;GACD;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,UAAU,QACZ,KAAK,MAAM,QAAQ,oBAAoB,QAAQ,WAAW,KAAK,GAC7D,MAAM,KAAK,sBAAsB,IAAI,CAAC;GAI1C,IAAI,SAAS,IACX,MAAM,KAAK;IAAE,MAAM;IAAa,SAAS;GAAK,CAAC;GAGjD,KAAK,MAAM,QAAQ,QAAQ,WACzB,MAAM,KAAK;IACT,MAAM;IACN,SAAS,KAAK;IACd,MAAM,KAAK;IACX,WAAW,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC;GAC5C,CAAC;GAGH;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,MAAM,KAAK;IAAE,MAAM;IAAQ,SAAS,QAAQ,QAAQ,IAAI,sBAAsB;GAAE,CAAC;GACjF;EACF;EAEA,MAAM,KAAK;GAAE,MAAM,QAAQ;GAAM,SAAS,iBAAiB,QAAQ,OAAO;EAAE,CAAC;CAC/E;CAEA,OAAO;EAAE,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;EAAI;CAAM;AAC1E;;AAGA,SAAS,uBACP,MAC0E;CAC1E,IAAI,KAAK,SAAS,QAChB,OAAO;EAAE,MAAM;EAAc,MAAM,KAAK;CAAK;CAG/C,IAAI,KAAK,SAAS,SAChB,OAAO;EAAE,MAAM;EAAe,WAAW,WAAW,KAAK,MAAM;EAAG,QAAQ;CAAO;CAGnF,MAAM,IAAI,oBACR,2CAA2C,KAAK,KAAK,8CAA8C,KAAK,KAAK,cAC/G;AACF"}
1
+ {"version":3,"file":"to-responses-input.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/to-responses-input.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { stringifyContent, toImageUrl } from \"./to-openai-messages\";\nimport { readReasoningReplay, toReplayReasoningItem } from \"./reasoning-replay\";\n\n/** One item of the Responses `input` array. */\nexport type ResponsesInputItem = OpenAI.Responses.ResponseInputItem;\n\n/** What `toResponsesInput` produces: the `instructions` string plus the `input` items. */\nexport type ResponsesInputPlan = {\n /** The system prompt, sent as the top-level `instructions` field. */\n instructions?: string;\n input: ResponsesInputItem[];\n};\n\n/**\n * Convert vendor-neutral `Message[]` into the Responses request pieces.\n *\n * - The FIRST system message becomes `instructions` (decision 9: it avoids\n * the `system` vs `developer` role question on reasoning models). Any\n * later system message stays inline as a `system` role item.\n * - `user` / `assistant` text becomes an `EasyInputMessage` (`{ role,\n * content }`). Multipart user content becomes `input_text` / `input_image`\n * parts; images always carry `detail: \"auto\"` because `ResponseInputImage.detail`\n * is required by the SDK types.\n * - An assistant message that requested tools becomes its text (when\n * non-empty) followed by one top-level `function_call` item per call; our\n * `ModelToolCallRequest.id` is the wire `call_id`. No item `id` is sent:\n * with `store: false` nothing was persisted for it to refer to.\n * - When `model` is given, reasoning items stored on the turn's tool calls\n * (`providerMetadata.openaiResponses`, see `reasoning-replay.ts`) are\n * emitted BEFORE the turn's text and `function_call` items (the order the\n * API produced them), only if they were produced by that same model.\n * Without `model` nothing is replayed.\n * - A `tool` message becomes a top-level `function_call_output` item.\n *\n * PDF and audio parts are rejected with a typed `InvalidRequestError`: the\n * Responses adapter declares those capabilities off, so they only arrive\n * here if a caller bypasses the agent's capability gate.\n *\n * @example\n * toResponsesInput([\n * { role: \"system\", content: \"Be brief.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // { instructions: \"Be brief.\", input: [{ role: \"user\", content: \"Hi\" }] }\n */\nexport function toResponsesInput(messages: Message[], model?: string): ResponsesInputPlan {\n const input: ResponsesInputItem[] = [];\n let instructions: string | undefined;\n let instructionsTaken = false;\n\n for (const message of messages) {\n if (message.role === \"system\") {\n const text = stringifyContent(message.content);\n\n if (!instructionsTaken) {\n instructionsTaken = true;\n instructions = text === \"\" ? undefined : text;\n continue;\n }\n\n input.push({ role: \"system\", content: text });\n continue;\n }\n\n if (message.role === \"tool\") {\n input.push({\n type: \"function_call_output\",\n call_id: message.toolCallId ?? \"\",\n output: stringifyContent(message.content),\n });\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const text = stringifyContent(message.content);\n\n if (model !== undefined) {\n for (const item of readReasoningReplay(message.toolCalls, model)) {\n input.push(toReplayReasoningItem(item));\n }\n }\n\n if (text !== \"\") {\n input.push({ role: \"assistant\", content: text });\n }\n\n for (const call of message.toolCalls) {\n input.push({\n type: \"function_call\",\n call_id: call.id,\n name: call.name,\n arguments: JSON.stringify(call.input ?? {}),\n });\n }\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n input.push({ role: \"user\", content: message.content.map(toResponsesContentPart) });\n continue;\n }\n\n input.push({ role: message.role, content: stringifyContent(message.content) });\n }\n\n return { ...(instructions !== undefined ? { instructions } : {}), input };\n}\n\n/** Map a resolved `ContentPart` to a Responses input content part (text and images only). */\nfunction toResponsesContentPart(\n part: ContentPart,\n): OpenAI.Responses.ResponseInputText | OpenAI.Responses.ResponseInputImage {\n if (part.type === \"text\") {\n return { type: \"input_text\", text: part.text };\n }\n\n if (part.type === \"image\") {\n return { type: \"input_image\", image_url: toImageUrl(part.source), detail: \"auto\" };\n }\n\n throw new InvalidRequestError(\n `OpenAI api: \"responses\" does not accept ${part.type} input in this adapter; use api: \"chat\" for ${part.type} attachments.`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,iBAAiB,UAAqB,OAAoC;CACxF,MAAM,QAA8B,CAAC;CACrC,IAAI;CACJ,IAAI,oBAAoB;CAExB,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,CAAC,mBAAmB;IACtB,oBAAoB;IACpB,eAAe,SAAS,KAAK,SAAY;IACzC;GACF;GAEA,MAAM,KAAK;IAAE,MAAM;IAAU,SAAS;GAAK,CAAC;GAC5C;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,MAAM,KAAK;IACT,MAAM;IACN,SAAS,QAAQ,cAAc;IAC/B,QAAQ,iBAAiB,QAAQ,OAAO;GAC1C,CAAC;GACD;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,UAAU,QACZ,KAAK,MAAM,QAAQ,oBAAoB,QAAQ,WAAW,KAAK,GAC7D,MAAM,KAAK,sBAAsB,IAAI,CAAC;GAI1C,IAAI,SAAS,IACX,MAAM,KAAK;IAAE,MAAM;IAAa,SAAS;GAAK,CAAC;GAGjD,KAAK,MAAM,QAAQ,QAAQ,WACzB,MAAM,KAAK;IACT,MAAM;IACN,SAAS,KAAK;IACd,MAAM,KAAK;IACX,WAAW,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC;GAC5C,CAAC;GAGH;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,MAAM,KAAK;IAAE,MAAM;IAAQ,SAAS,QAAQ,QAAQ,IAAI,sBAAsB;GAAE,CAAC;GACjF;EACF;EAEA,MAAM,KAAK;GAAE,MAAM,QAAQ;GAAM,SAAS,iBAAiB,QAAQ,OAAO;EAAE,CAAC;CAC/E;CAEA,OAAO;EAAE,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;EAAI;CAAM;AAC1E;;AAGA,SAAS,uBACP,MAC0E;CAC1E,IAAI,KAAK,SAAS,QAChB,OAAO;EAAE,MAAM;EAAc,MAAM,KAAK;CAAK;CAG/C,IAAI,KAAK,SAAS,SAChB,OAAO;EAAE,MAAM;EAAe,WAAW,WAAW,KAAK,MAAM;EAAG,QAAQ;CAAO;CAGnF,MAAM,IAAI,oBACR,2CAA2C,KAAK,KAAK,8CAA8C,KAAK,KAAK,cAC/G;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { toToolParameters } from "./to-openai-tools.mjs";
2
2
 
3
- //#region ../ai-openai/src/utils/to-responses-tools.ts
3
+ //#region ../../packages/ai-openai/src/utils/to-responses-tools.ts
4
4
  /**
5
5
  * Convert vendor-neutral `ToolConfig[]` to Responses function tools.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"to-responses-tools.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/to-responses-tools.ts"],"sourcesContent":["import type { ToolConfig } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { toToolParameters } from \"./to-openai-tools\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` to Responses function tools.\n *\n * Responses tools are FLAT (`name` / `description` / `parameters` sit beside\n * `type`, not under a nested `function` object) and `strict` is required by\n * the SDK type (`FunctionTool.strict: boolean | null`). The Chat path never\n * sets `strict` (non-strict is the OpenAI default there), so every tool is\n * sent with `strict: false` to keep the same behavior.\n *\n * @example\n * toResponsesTools([weatherTool]);\n * // [{ type: \"function\", name: \"getWeather\", description: \"...\", parameters: {...}, strict: false }]\n */\nexport function toResponsesTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): OpenAI.Responses.FunctionTool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n name: tool.name,\n description: tool.description,\n parameters: toToolParameters(tool.input),\n strict: false,\n }));\n}\n\n/**\n * Read an optional `toolChoice` call option into the Responses `tool_choice`.\n *\n * `ModelCallOptions` does not declare `toolChoice` today (it only arrives\n * through the open index signature), so the value is validated here rather\n * than trusted. Accepted: `\"none\" | \"auto\" | \"required\"`, or a forced\n * function as `{ name }` / `{ type: \"function\", name }` (the Chat-style\n * nested `{ type: \"function\", function: { name } }` is also accepted). The\n * Responses form is flat: `{ type: \"function\", name }`. Anything else yields\n * `undefined`, leaving the provider default.\n */\nexport function toResponsesToolChoice(\n toolChoice: unknown,\n): OpenAI.Responses.ToolChoiceOptions | OpenAI.Responses.ToolChoiceFunction | undefined {\n if (toolChoice === \"none\" || toolChoice === \"auto\" || toolChoice === \"required\") {\n return toolChoice;\n }\n\n if (typeof toolChoice !== \"object\" || toolChoice === null) {\n return undefined;\n }\n\n const record = toolChoice as { name?: unknown; function?: { name?: unknown } };\n const name = typeof record.name === \"string\" ? record.name : record.function?.name;\n\n if (typeof name === \"string\" && name !== \"\") {\n return { type: \"function\", name };\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAgB,iBACd,OAC6C;CAC7C,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM;EACN,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,YAAY,iBAAiB,KAAK,KAAK;EACvC,QAAQ;CACV,EAAE;AACJ;;;;;;;;;;;;AAaA,SAAgB,sBACd,YACsF;CACtF,IAAI,eAAe,UAAU,eAAe,UAAU,eAAe,YACnE,OAAO;CAGT,IAAI,OAAO,eAAe,YAAY,eAAe,MACnD;CAGF,MAAM,SAAS;CACf,MAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO,OAAO,UAAU;CAE9E,IAAI,OAAO,SAAS,YAAY,SAAS,IACvC,OAAO;EAAE,MAAM;EAAY;CAAK;AAIpC"}
1
+ {"version":3,"file":"to-responses-tools.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/to-responses-tools.ts"],"sourcesContent":["import type { ToolConfig } from \"@warlock.js/ai\";\nimport type OpenAI from \"openai\";\nimport { toToolParameters } from \"./to-openai-tools\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` to Responses function tools.\n *\n * Responses tools are FLAT (`name` / `description` / `parameters` sit beside\n * `type`, not under a nested `function` object) and `strict` is required by\n * the SDK type (`FunctionTool.strict: boolean | null`). The Chat path never\n * sets `strict` (non-strict is the OpenAI default there), so every tool is\n * sent with `strict: false` to keep the same behavior.\n *\n * @example\n * toResponsesTools([weatherTool]);\n * // [{ type: \"function\", name: \"getWeather\", description: \"...\", parameters: {...}, strict: false }]\n */\nexport function toResponsesTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): OpenAI.Responses.FunctionTool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n type: \"function\",\n name: tool.name,\n description: tool.description,\n parameters: toToolParameters(tool.input),\n strict: false,\n }));\n}\n\n/**\n * Read an optional `toolChoice` call option into the Responses `tool_choice`.\n *\n * `ModelCallOptions` does not declare `toolChoice` today (it only arrives\n * through the open index signature), so the value is validated here rather\n * than trusted. Accepted: `\"none\" | \"auto\" | \"required\"`, or a forced\n * function as `{ name }` / `{ type: \"function\", name }` (the Chat-style\n * nested `{ type: \"function\", function: { name } }` is also accepted). The\n * Responses form is flat: `{ type: \"function\", name }`. Anything else yields\n * `undefined`, leaving the provider default.\n */\nexport function toResponsesToolChoice(\n toolChoice: unknown,\n): OpenAI.Responses.ToolChoiceOptions | OpenAI.Responses.ToolChoiceFunction | undefined {\n if (toolChoice === \"none\" || toolChoice === \"auto\" || toolChoice === \"required\") {\n return toolChoice;\n }\n\n if (typeof toolChoice !== \"object\" || toolChoice === null) {\n return undefined;\n }\n\n const record = toolChoice as { name?: unknown; function?: { name?: unknown } };\n const name = typeof record.name === \"string\" ? record.name : record.function?.name;\n\n if (typeof name === \"string\" && name !== \"\") {\n return { type: \"function\", name };\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAgB,iBACd,OAC6C;CAC7C,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM;EACN,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,YAAY,iBAAiB,KAAK,KAAK;EACvC,QAAQ;CACV,EAAE;AACJ;;;;;;;;;;;;AAaA,SAAgB,sBACd,YACsF;CACtF,IAAI,eAAe,UAAU,eAAe,UAAU,eAAe,YACnE,OAAO;CAGT,IAAI,OAAO,eAAe,YAAY,eAAe,MACnD;CAGF,MAAM,SAAS;CACf,MAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO,OAAO,UAAU;CAE9E,IAAI,OAAO,SAAS,YAAY,SAAS,IACvC,OAAO;EAAE,MAAM;EAAY;CAAK;AAIpC"}
@@ -1,7 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { AIError, ContentFilterError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError, QuotaExceededError } from "@warlock.js/ai";
3
3
 
4
- //#region ../ai-openai/src/utils/wrap-openai-error.ts
4
+ //#region ../../packages/ai-openai/src/utils/wrap-openai-error.ts
5
5
  /**
6
6
  * Wrap any thrown value caught inside the OpenAI adapter into the
7
7
  * appropriate `@warlock.js/ai` `AIError` subclass.
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-openai-error.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/wrap-openai-error.ts"],"sourcesContent":["import {\n AIError,\n ContentFilterError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport OpenAI from \"openai\";\n\n/**\n * Raw-error fields the wrapper reads off an OpenAI SDK error.\n *\n * `APIError` exposes `status`, `code`, `message`, `type`, `headers` —\n * we duck-type because wrapped retries, proxied errors, and custom\n * error subclasses sometimes lose the `instanceof` relationship.\n */\ntype OpenAIErrorShape = {\n status?: number;\n code?: string | null;\n message?: string;\n type?: string | null;\n /** The request parameter the provider rejected, e.g. `max_tokens`. */\n param?: string | null;\n headers?: Record<string, string> | undefined;\n name?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the OpenAI adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Prefers `APIError.code` when present (stable\n * machine identifier across SDK versions), falls back to `status` when\n * `code` is missing (common with proxied deployments that strip the\n * field). Name-based detection (`APIConnectionTimeoutError`) catches\n * transport-layer errors that never produced an HTTP response.\n *\n * `AIError` instances are returned unchanged — callers can pass the\n * error through `try/catch/throw wrap(e)` pipelines without accidental\n * double-wrapping.\n *\n * @example\n * try {\n * return await this.client.chat.completions.create(...);\n * } catch (thrown) {\n * throw wrapOpenAIError(thrown);\n * }\n */\nexport function wrapOpenAIError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(thrown, shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.code === \"invalid_api_key\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"insufficient_quota\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || shape.code === \"rate_limit_exceeded\") {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.code === \"context_length_exceeded\") {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"content_filter\") {\n return new ContentFilterError(message, {\n cause: thrown,\n context,\n reason: message,\n });\n }\n\n if (typeof shape.status === \"number\" && shape.status >= 400 && shape.status < 500) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — some consumers wrap the SDK, and proxies sometimes strip the\n * prototype chain. Duck-typing on the visible fields is resilient to\n * both.\n */\nfunction toShape(thrown: unknown): OpenAIErrorShape {\n if (thrown instanceof OpenAI.APIError) {\n return {\n status: thrown.status,\n code: thrown.code,\n message: thrown.message,\n type: thrown.type,\n param: thrown.param,\n headers: thrown.headers as Record<string, string> | undefined,\n name: thrown.name,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n param: typeof raw.param === \"string\" ? raw.param : undefined,\n headers:\n typeof raw.headers === \"object\" && raw.headers !== null\n ? (raw.headers as Record<string, string>)\n : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. OpenAI's SDK\n * throws `APIConnectionTimeoutError` for transport-level timeouts, and\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the lower socket\n * layer. Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: OpenAIErrorShape): boolean {\n if (thrown instanceof OpenAI.APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(\n thrown: unknown,\n shape: OpenAIErrorShape,\n): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.code) {\n context.code = shape.code;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n if (shape.param) {\n context.param = shape.param;\n }\n\n const requestId = readRequestId(thrown);\n\n if (requestId) {\n context.requestId = requestId;\n }\n\n return context;\n}\n\n/**\n * The request id OpenAI returned. Current SDKs expose it as\n * `APIError.requestID`; older ones and hand-rolled errors use\n * `request_id` / `requestId`. Extract defensively.\n */\nfunction readRequestId(thrown: unknown): string | undefined {\n if (typeof thrown !== \"object\" || thrown === null) {\n return undefined;\n }\n\n const raw = thrown as Record<string, unknown>;\n\n if (typeof raw.request_id === \"string\") {\n return raw.request_id;\n }\n\n if (typeof raw.requestId === \"string\") {\n return raw.requestId;\n }\n\n if (typeof raw.requestID === \"string\") {\n return raw.requestID;\n }\n\n return undefined;\n}\n\n/**\n * Parse the `Retry-After` response header (seconds per HTTP spec)\n * into milliseconds so consumers can feed it straight to `setTimeout`.\n * Returns `undefined` when missing or unparseable.\n */\nfunction parseRetryAfter(headers: Record<string, string> | undefined): number | undefined {\n if (!headers) {\n return undefined;\n }\n\n const raw = headers[\"retry-after\"] ?? headers[\"Retry-After\"];\n\n if (!raw) {\n return undefined;\n }\n\n const seconds = Number(raw);\n\n if (!Number.isFinite(seconds) || seconds < 0) {\n return undefined;\n }\n\n return Math.round(seconds * 1000);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wrap-openai-error.mjs","names":[],"sources":["../../../../../../../../packages/ai-openai/src/utils/wrap-openai-error.ts"],"sourcesContent":["import {\n AIError,\n ContentFilterError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport OpenAI from \"openai\";\n\n/**\n * Raw-error fields the wrapper reads off an OpenAI SDK error.\n *\n * `APIError` exposes `status`, `code`, `message`, `type`, `headers` —\n * we duck-type because wrapped retries, proxied errors, and custom\n * error subclasses sometimes lose the `instanceof` relationship.\n */\ntype OpenAIErrorShape = {\n status?: number;\n code?: string | null;\n message?: string;\n type?: string | null;\n /** The request parameter the provider rejected, e.g. `max_tokens`. */\n param?: string | null;\n headers?: Record<string, string> | undefined;\n name?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the OpenAI adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Prefers `APIError.code` when present (stable\n * machine identifier across SDK versions), falls back to `status` when\n * `code` is missing (common with proxied deployments that strip the\n * field). Name-based detection (`APIConnectionTimeoutError`) catches\n * transport-layer errors that never produced an HTTP response.\n *\n * `AIError` instances are returned unchanged — callers can pass the\n * error through `try/catch/throw wrap(e)` pipelines without accidental\n * double-wrapping.\n *\n * @example\n * try {\n * return await this.client.chat.completions.create(...);\n * } catch (thrown) {\n * throw wrapOpenAIError(thrown);\n * }\n */\nexport function wrapOpenAIError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(thrown, shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.code === \"invalid_api_key\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"insufficient_quota\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || shape.code === \"rate_limit_exceeded\") {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.code === \"context_length_exceeded\") {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"content_filter\") {\n return new ContentFilterError(message, {\n cause: thrown,\n context,\n reason: message,\n });\n }\n\n if (typeof shape.status === \"number\" && shape.status >= 400 && shape.status < 500) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — some consumers wrap the SDK, and proxies sometimes strip the\n * prototype chain. Duck-typing on the visible fields is resilient to\n * both.\n */\nfunction toShape(thrown: unknown): OpenAIErrorShape {\n if (thrown instanceof OpenAI.APIError) {\n return {\n status: thrown.status,\n code: thrown.code,\n message: thrown.message,\n type: thrown.type,\n param: thrown.param,\n headers: thrown.headers as Record<string, string> | undefined,\n name: thrown.name,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n param: typeof raw.param === \"string\" ? raw.param : undefined,\n headers:\n typeof raw.headers === \"object\" && raw.headers !== null\n ? (raw.headers as Record<string, string>)\n : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. OpenAI's SDK\n * throws `APIConnectionTimeoutError` for transport-level timeouts, and\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the lower socket\n * layer. Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: OpenAIErrorShape): boolean {\n if (thrown instanceof OpenAI.APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(\n thrown: unknown,\n shape: OpenAIErrorShape,\n): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.code) {\n context.code = shape.code;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n if (shape.param) {\n context.param = shape.param;\n }\n\n const requestId = readRequestId(thrown);\n\n if (requestId) {\n context.requestId = requestId;\n }\n\n return context;\n}\n\n/**\n * The request id OpenAI returned. Current SDKs expose it as\n * `APIError.requestID`; older ones and hand-rolled errors use\n * `request_id` / `requestId`. Extract defensively.\n */\nfunction readRequestId(thrown: unknown): string | undefined {\n if (typeof thrown !== \"object\" || thrown === null) {\n return undefined;\n }\n\n const raw = thrown as Record<string, unknown>;\n\n if (typeof raw.request_id === \"string\") {\n return raw.request_id;\n }\n\n if (typeof raw.requestId === \"string\") {\n return raw.requestId;\n }\n\n if (typeof raw.requestID === \"string\") {\n return raw.requestID;\n }\n\n return undefined;\n}\n\n/**\n * Parse the `Retry-After` response header (seconds per HTTP spec)\n * into milliseconds so consumers can feed it straight to `setTimeout`.\n * Returns `undefined` when missing or unparseable.\n */\nfunction parseRetryAfter(headers: Record<string, string> | undefined): number | undefined {\n if (!headers) {\n return undefined;\n }\n\n const raw = headers[\"retry-after\"] ?? headers[\"Retry-After\"];\n\n if (!raw) {\n return undefined;\n }\n\n const seconds = Number(raw);\n\n if (!Number.isFinite(seconds) || seconds < 0) {\n return undefined;\n }\n\n return Math.round(seconds * 1000);\n}\n"],"mappings":"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"}
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: setup-openai
11
- description: 'Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; agent wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; adapter comparison — `@warlock.js/ai/pick-ai-provider/SKILL.md`; competing adapters `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `openai` SDK, Vercel `@ai-sdk/openai`.'
11
+ description: 'Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; adapter comparison — the `pick-ai-provider` topic of the `warlock-js-ai` skill; competing adapters `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `openai` SDK, Vercel `@ai-sdk/openai`.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/ai-openai`
@@ -97,7 +97,7 @@ const { data } = await ai.image({ model: gpt, prompt: "a red bicycle", size: "10
97
97
 
98
98
  - `gpt-image-*` is token-metered (price with `{ input, output }`) and always returns base64 bytes — the adapter never sends `response_format` (the API rejects it).
99
99
  - `dall-e-*` is per-image (price with `{ perImage }` / `perImageBySize`); defaults to base64, opt into a hosted URL with `options: { responseFormat: "url" }`.
100
- - The model id is **not validated locally** — `openai.image({ name })` passes it straight to `client.images.generate`, so an id OpenAI does not serve fails at the provider (wrapped into the typed `AIError` hierarchy), not at construction. Same for `openai.speech({ name })` and `openai.transcribe({ name })`. There are no model-id helpers to consult: `isOpenAIImageModel()`, `OPENAI_IMAGE_MODEL_PREFIXES`, `isOpenAISpeechModel()` and `isOpenAITranscriptionModel()` were all removed in 4.13.0 — branch on your own id list if you need one. The verb surface (envelope, options, cost-truth) lives in [`@warlock.js/ai/generate-images/SKILL.md`](@warlock.js/ai/generate-images/SKILL.md).
100
+ - The model id is **not validated locally** — `openai.image({ name })` passes it straight to `client.images.generate`, so an id OpenAI does not serve fails at the provider (wrapped into the typed `AIError` hierarchy), not at construction. Same for `openai.speech({ name })` and `openai.transcribe({ name })`. There are no model-id helpers to consult: `isOpenAIImageModel()`, `OPENAI_IMAGE_MODEL_PREFIXES`, `isOpenAISpeechModel()` and `isOpenAITranscriptionModel()` were all removed in 4.13.0 — branch on your own id list if you need one. The verb surface (envelope, options, cost-truth) lives in the `generate-images` topic of the `warlock-js-ai` skill.
101
101
 
102
102
  ## Streaming
103
103
 
@@ -238,11 +238,11 @@ const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" })
238
238
  usage.cost; // { input, output, cachedInput?, cachedOutput? } — per-channel USD breakdown of THIS run
239
239
  ```
240
240
 
241
- The registry is per-model; `usage.cost` is the per-channel breakdown the framework computes from `tokens × pricing[model]`. Resolution at `model()` time: per-model `pricing` (`openai.model({ name, pricing })`) > SDK registry > `undefined` (no cost computed). See [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md).
241
+ The registry is per-model; `usage.cost` is the per-channel breakdown the framework computes from `tokens × pricing[model]`. Resolution at `model()` time: per-model `pricing` (`openai.model({ name, pricing })`) > SDK registry > `undefined` (no cost computed). See the `pick-ai-provider` topic of the `warlock-js-ai` skill.
242
242
 
243
243
  ## Errors
244
244
 
245
- Raw OpenAI SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy via the adapter's error wrapper. Dispatch keys on `APIError.status + code` combined — see [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md).
245
+ Raw OpenAI SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy via the adapter's error wrapper. Dispatch keys on `APIError.status + code` combined — see the `handle-ai-errors` topic of the `warlock-js-ai` skill.
246
246
 
247
247
  ## When NOT to use this skill
248
248
 
@@ -251,8 +251,8 @@ Raw OpenAI SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hier
251
251
 
252
252
  ## See also
253
253
 
254
- - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — passing the model into `ai.agent({...})`
255
- - [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md) — embedder usage
256
- - [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) — adapter comparison
254
+ - The `run-ai-agent` topic of the `warlock-js-ai` skill — passing the model into `ai.agent({...})`
255
+ - The `embed-text` topic of the `warlock-js-ai` skill — embedder usage
256
+ - The `pick-ai-provider` topic of the `warlock-js-ai` skill — adapter comparison
257
257
 
258
258
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [setup-openai](@warlock.js/ai-openai/setup-openai/SKILL.md): Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; agent wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; adapter comparison — `@warlock.js/ai/pick-ai-provider/SKILL.md`; competing adapters `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `openai` SDK, Vercel `@ai-sdk/openai`.
9
+ - [setup-openai](@warlock.js/ai-openai/setup-openai/SKILL.md): Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; adapter comparison — the `pick-ai-provider` topic of the `warlock-js-ai` skill; competing adapters `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `openai` SDK, Vercel `@ai-sdk/openai`.
package/package.json CHANGED
@@ -10,16 +10,17 @@
10
10
  "license": "MIT",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/warlockjs/ai-openai"
13
+ "url": "git+https://github.com/warlockjs/framework.git",
14
+ "directory": "packages/ai-openai"
14
15
  },
15
16
  "dependencies": {
16
17
  "openai": "^7.23.0",
17
- "@warlock.js/logger": "5.28.0"
18
+ "@warlock.js/logger": "5.30.0"
18
19
  },
19
20
  "peerDependencies": {
20
- "@warlock.js/ai": "5.28.0"
21
+ "@warlock.js/ai": "5.30.0"
21
22
  },
22
- "version": "5.28.0",
23
+ "version": "5.30.0",
23
24
  "main": "./cjs/index.cjs",
24
25
  "module": "./esm/index.mjs",
25
26
  "types": "./esm/index.d.mts",
package/skills/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # `@warlock.js/ai-openai` — skills index
2
2
 
3
- Per-task skills. All cross-references use the form `@warlock.js/<pkg>/<skill>/SKILL.md`.
3
+ Per-task skills. Cross-references name the topic ("the `<topic>` topic"), and for another package add its skill ("the `<topic>` topic of the `warlock-js-<pkg>` skill").
4
4
 
5
5
  ## Skills
6
6
 
7
- ### [`setup-openai/`](./setup-openai/SKILL.md)
7
+ ### `setup-openai`
8
8
 
9
9
  Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?}) for ModelContract, .embedder({name, dimensions?}) for embeddings. Covers o-series / gpt-5 reasoning (reasoning_effort), automatic prompt caching with cost-truth usage (cachedTokens/reasoningTokens), and the per-model pricing registry. Load when wiring an OpenAI-backed model into a @warlock.js agent or routing via an OpenAI-compatible gateway.