@warlock.js/ai-openai 5.29.0 → 5.31.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 (73) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +3 -3
  3. package/cjs/index.cjs +437 -69
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/config.type.d.mts +6 -4
  6. package/esm/config.type.d.mts.map +1 -1
  7. package/esm/embedder.d.mts +1 -1
  8. package/esm/embedder.d.mts.map +1 -1
  9. package/esm/embedder.mjs +1 -1
  10. package/esm/embedder.mjs.map +1 -1
  11. package/esm/image.d.mts +1 -1
  12. package/esm/image.d.mts.map +1 -1
  13. package/esm/image.mjs +1 -1
  14. package/esm/image.mjs.map +1 -1
  15. package/esm/index.d.mts +3 -1
  16. package/esm/index.mjs +2 -1
  17. package/esm/known-pricing.d.mts +35 -0
  18. package/esm/known-pricing.d.mts.map +1 -0
  19. package/esm/known-pricing.mjs +117 -0
  20. package/esm/known-pricing.mjs.map +1 -0
  21. package/esm/known-reasoning-models.mjs +1 -1
  22. package/esm/known-reasoning-models.mjs.map +1 -1
  23. package/esm/known-vision-models.mjs +1 -1
  24. package/esm/known-vision-models.mjs.map +1 -1
  25. package/esm/model.mjs +76 -24
  26. package/esm/model.mjs.map +1 -1
  27. package/esm/provider-options.d.mts +8 -0
  28. package/esm/provider-options.d.mts.map +1 -0
  29. package/esm/provider-options.mjs +49 -0
  30. package/esm/provider-options.mjs.map +1 -0
  31. package/esm/responses-model.mjs +117 -18
  32. package/esm/responses-model.mjs.map +1 -1
  33. package/esm/sdk.d.mts +4 -3
  34. package/esm/sdk.d.mts.map +1 -1
  35. package/esm/sdk.mjs +6 -4
  36. package/esm/sdk.mjs.map +1 -1
  37. package/esm/speech.d.mts +1 -1
  38. package/esm/speech.d.mts.map +1 -1
  39. package/esm/speech.mjs +1 -1
  40. package/esm/speech.mjs.map +1 -1
  41. package/esm/transcription.d.mts +1 -1
  42. package/esm/transcription.d.mts.map +1 -1
  43. package/esm/transcription.mjs +1 -1
  44. package/esm/transcription.mjs.map +1 -1
  45. package/esm/utils/build-usage.mjs +15 -3
  46. package/esm/utils/build-usage.mjs.map +1 -1
  47. package/esm/utils/index.mjs +1 -1
  48. package/esm/utils/map-finish-reason.mjs +1 -1
  49. package/esm/utils/map-finish-reason.mjs.map +1 -1
  50. package/esm/utils/map-responses-status.mjs +1 -1
  51. package/esm/utils/map-responses-status.mjs.map +1 -1
  52. package/esm/utils/prompt-cache-params.mjs +26 -7
  53. package/esm/utils/prompt-cache-params.mjs.map +1 -1
  54. package/esm/utils/reasoning-effort.mjs +1 -1
  55. package/esm/utils/reasoning-effort.mjs.map +1 -1
  56. package/esm/utils/reasoning-replay.mjs +24 -2
  57. package/esm/utils/reasoning-replay.mjs.map +1 -1
  58. package/esm/utils/structured-output.mjs +1 -1
  59. package/esm/utils/structured-output.mjs.map +1 -1
  60. package/esm/utils/to-openai-messages.mjs +1 -1
  61. package/esm/utils/to-openai-messages.mjs.map +1 -1
  62. package/esm/utils/to-openai-tools.mjs +1 -1
  63. package/esm/utils/to-openai-tools.mjs.map +1 -1
  64. package/esm/utils/to-responses-input.mjs +1 -1
  65. package/esm/utils/to-responses-input.mjs.map +1 -1
  66. package/esm/utils/to-responses-tools.mjs +1 -1
  67. package/esm/utils/to-responses-tools.mjs.map +1 -1
  68. package/esm/utils/wrap-openai-error.mjs +7 -2
  69. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  70. package/llms-full.txt +128 -37
  71. package/llms.txt +1 -1
  72. package/package.json +5 -4
  73. package/skills/setup-openai/SKILL.md +128 -37
@@ -1,7 +1,7 @@
1
1
  import OpenAI, { ClientOptions } from "openai";
2
2
  import { EmbedderConfig, ImageModelConfig, ModelConfig, ModelPricing, SpeechModelConfig, TranscriptionModelConfig } from "@warlock.js/ai";
3
3
 
4
- //#region ../ai-openai/src/config.type.d.ts
4
+ //#region ../../packages/ai-openai/src/config.type.d.ts
5
5
  /**
6
6
  * Configuration for the OpenAI SDK adapter.
7
7
  *
@@ -138,9 +138,11 @@ type OpenAIModelConfig = ModelConfig & {
138
138
  */
139
139
  reasoning?: boolean;
140
140
  /**
141
- * Stable request bucket used by OpenAI to improve prompt-cache matching.
142
- * Sent only by models whose SDK provider label is `"openai"`; compatible
143
- * wrapper endpoints deliberately never receive this OpenAI-only field.
141
+ * Stable request bucket used to improve prompt-cache matching, sent as
142
+ * `prompt_cache_key`. Sent when the SDK provider label is `"openai"`,
143
+ * `"xai"` or `"mistral"` — the endpoints that document the field. Other
144
+ * labels (Groq and DeepSeek cache automatically; custom gateways) never
145
+ * receive it.
144
146
  */
145
147
  promptCacheKey?: string;
146
148
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../ai-openai/src/config.type.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;;;AAeuC;AAiBvC;;;;AAAgC;AAahC;;;;AAAqB;AAcrB;;KA3DY,eAAA,GAAkB,IAAA,CAAK,aAAA;EA2DH;;;;;;;;EAlD9B,QAAA;EAwDM;;;;;EAlDN,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;;AA4HpB;AAaP;;;;KAxHY,oBAAA;AAsIZ;;;;AAAgD;AAahD;;;;AAAkD;AAalD;AA1BA,KAzHY,SAAA;;;AAmJoD;;;;;;;;;;KArIpD,iBAAA,GAAoB,WAAA;;;;;;EAM9B,GAAA,GAAM,SAAA;;;;;;;;EAQN,MAAA;;;;;;;;;;;;;;EAcA,cAAA,GAAiB,oBAAA;;;;;;;;;EASjB,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;EAMA,cAAA;;;;;;EAMA,oBAAA,GAAuB,OAAA,CACrB,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,0BAAA;;;;;;;;;EAW1B,GAAA;;;;;;;;EAQA,KAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;;;;KAcrC,iBAAA,GAAoB,gBAAgB;;;;;;;;;;;;KAapC,kBAAA,GAAqB,iBAAiB;;;;;;;;;;;;KAatC,yBAAA,GAA4B,wBAAwB"}
1
+ {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/config.type.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;;;AAeuC;AAiBvC;;;;AAAgC;AAahC;;;;AAAqB;AAcrB;;KA3DY,eAAA,GAAkB,IAAA,CAAK,aAAA;EA2DH;;;;;;;;EAlD9B,QAAA;EAwDM;;;;;EAlDN,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;;AA8HpB;AAaP;;;;KA1HY,oBAAA;AAwIZ;;;;AAAgD;AAahD;;;;AAAkD;AAalD;AA1BA,KA3HY,SAAA;;;AAqJoD;;;;;;;;;;KAvIpD,iBAAA,GAAoB,WAAA;;;;;;EAM9B,GAAA,GAAM,SAAA;;;;;;;;EAQN,MAAA;;;;;;;;;;;;;;EAcA,cAAA,GAAiB,oBAAA;;;;;;;;;EASjB,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;;;EAQA,cAAA;;;;;;EAMA,oBAAA,GAAuB,OAAA,CACrB,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,0BAAA;;;;;;;;;EAW1B,GAAA;;;;;;;;EAQA,KAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;;;;KAcrC,iBAAA,GAAoB,gBAAgB;;;;;;;;;;;;KAapC,kBAAA,GAAqB,iBAAiB;;;;;;;;;;;;KAatC,yBAAA,GAA4B,wBAAwB"}
@@ -2,7 +2,7 @@ import { OpenAIEmbedderConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { EmbedderContract, EmbeddingBatchResult, EmbeddingResult } from "@warlock.js/ai";
4
4
 
5
- //#region ../ai-openai/src/embedder.d.ts
5
+ //#region ../../packages/ai-openai/src/embedder.d.ts
6
6
  /**
7
7
  * OpenAI-backed implementation of `EmbedderContract`.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.d.mts","names":[],"sources":["../../../../../../ai-openai/src/embedder.ts"],"mappings":";;;;;;;AA2CA;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,gBAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EACT,UAAA;EAAA,iBAEU,MAAA;EA8CJ;;;;;EAAA,iBAvCI,oBAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,oBAAA;EAO9B,KAAA,CAAM,KAAA,WAAgB,OAAA,CAAQ,eAAA;EA6B9B,SAAA,CAAU,MAAA,aAAmB,OAAA,CAAQ,oBAAA;;;;;;;UAgBpC,OAAA;AAAA"}
1
+ {"version":3,"file":"embedder.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"mappings":";;;;;;;AAyCA;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,gBAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EACT,UAAA;EAAA,iBAEU,MAAA;EA8CJ;;;;;EAAA,iBAvCI,oBAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,oBAAA;EAO9B,KAAA,CAAM,KAAA,WAAgB,OAAA,CAAQ,eAAA;EA6B9B,SAAA,CAAU,MAAA,aAAmB,OAAA,CAAQ,oBAAA;;;;;;;UAgBpC,OAAA;AAAA"}
package/esm/embedder.mjs CHANGED
@@ -3,7 +3,7 @@ import "./utils/index.mjs";
3
3
  import { EmbeddingVectorCountMismatchError } from "@warlock.js/ai";
4
4
  import { log } from "@warlock.js/logger";
5
5
 
6
- //#region ../ai-openai/src/embedder.ts
6
+ //#region ../../packages/ai-openai/src/embedder.ts
7
7
  const LOG_MODULE = "ai.openai";
8
8
  /**
9
9
  * OpenAI-backed implementation of `EmbedderContract`.
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.mjs","names":[],"sources":["../../../../../../ai-openai/src/embedder.ts"],"sourcesContent":["import {\n EmbeddingVectorCountMismatchError,\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIEmbedderConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\ntype EmbeddingsResponse = Awaited<\n ReturnType<OpenAI[\"embeddings\"][\"create\"]>\n>;\n\n/**\n * OpenAI-backed implementation of `EmbedderContract`.\n *\n * **Role.** Converts text (or a batch of texts) into floating-point\n * vectors via OpenAI's Embeddings API. Standalone primitive — no\n * relationship to chat completions, tools, or the agent loop.\n *\n * **Dimensions.** When no `dimensions` override is supplied in config,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached for all subsequent calls —\n * even if a later response were to return a different length, the\n * first value wins so batches stay dimensionally consistent. Passing\n * `dimensions` in config both forwards the truncation hint to the API\n * (for models like `text-embedding-3-*`) and sets the initial value.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the\n * typed `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError` —\n * callers catch `AIError` subclasses (`ProviderRateLimitError`,\n * `ProviderAuthError`, etc.) instead of OpenAI's own classes.\n *\n * @example\n * const embedder = new OpenAIEmbedder(client, { name: \"text-embedding-3-small\" });\n * const { vector, dimensions, usage } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class OpenAIEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider = \"openai\";\n public dimensions: number;\n\n private readonly client: OpenAI;\n\n /**\n * User-specified truncation hint, or `undefined` if omitted.\n * Forwarded to the API on every call so OpenAI can truncate the\n * embedding server-side for models that support it.\n */\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIEmbedderConfig) {\n this.client = client;\n this.name = config.name;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const { response, usage } = await this.request(input);\n\n const [first] = response.data;\n\n if (first === undefined) {\n // A 200 whose `data` array is empty. Before this guard the next line\n // read `.embedding` off `undefined` and the caller got a bare\n // `TypeError: Cannot read properties of undefined`, naming neither the\n // provider nor what it actually returned.\n //\n // Same typed error the RAG and skill-catalog paths raise for this\n // contract violation, so one shortfall has one name wherever it\n // surfaces. Expected 1, received 0.\n throw new EmbeddingVectorCountMismatchError({\n provider: this.provider,\n expectedCount: 1,\n receivedCount: response.data.length,\n record: input,\n });\n }\n\n return {\n vector: first.embedding,\n dimensions: this.dimensions,\n usage,\n };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const { response, usage } = await this.request(inputs);\n\n return {\n vectors: response.data.map((d) => d.embedding),\n dimensions: this.dimensions,\n usage,\n };\n }\n\n /**\n * Shared transport for both `embed()` and `embedMany()` — issues the\n * `embeddings.create` call, wraps provider errors, caches dimensions\n * on the first successful response, and returns the raw response\n * plus a camelCase usage object for the caller to shape.\n */\n private async request(input: string | string[]): Promise<{\n response: EmbeddingsResponse;\n usage: EmbeddingUsage;\n }> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embeddings.create\", {\n model: this.name,\n batch: Array.isArray(input),\n count: Array.isArray(input) ? input.length : 1,\n });\n\n let response: EmbeddingsResponse;\n\n try {\n response = await this.client.embeddings.create({\n model: this.name,\n input,\n ...(this.configuredDimensions !== undefined\n ? { dimensions: this.configuredDimensions }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embeddings.create returned\", {\n dimensions: response.data[0]?.embedding.length,\n usage: {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n },\n });\n\n // Cache dimensions on the first response. Once set, stays set —\n // we trust the first call to define the shape for this embedder.\n //\n // Reads the first entry optionally rather than asserting it: this runs for\n // BOTH embed() and embedMany(), and an empty `data` array is a real\n // provider response, not an impossible one. Leaving `dimensions` at 0 for\n // an empty response is correct — the callers each raise their own typed\n // error about the shortfall, and cacheing a dimension off a response that\n // carried no vectors would be inventing a shape from nothing.\n if (this.dimensions === 0) {\n const firstEmbedding = response.data[0]?.embedding;\n\n if (firstEmbedding !== undefined) {\n this.dimensions = firstEmbedding.length;\n }\n }\n\n const usage: EmbeddingUsage = {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n };\n\n return { response, usage };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnB,IAAa,iBAAb,MAAwD;CAetD,AAAO,YAAY,QAAgB,QAA8B;kBAbtC;gBAWO;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,uBAAuB,OAAO;EACnC,KAAK,aAAa,OAAO,cAAc;CACzC;CAEA,MAAa,MAAM,OAAyC;EAC1D,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,KAAK;EAEpD,MAAM,CAAC,SAAS,SAAS;EAEzB,IAAI,UAAU,QASZ,MAAM,IAAI,kCAAkC;GAC1C,UAAU,KAAK;GACf,eAAe;GACf,eAAe,SAAS,KAAK;GAC7B,QAAQ;EACV,CAAC;EAGH,OAAO;GACL,QAAQ,MAAM;GACd,YAAY,KAAK;GACjB;EACF;CACF;CAEA,MAAa,UAAU,QAAiD;EACtE,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,MAAM;EAErD,OAAO;GACL,SAAS,SAAS,KAAK,KAAK,MAAM,EAAE,SAAS;GAC7C,YAAY,KAAK;GACjB;EACF;CACF;;;;;;;CAQA,MAAc,QAAQ,OAGnB;EACD,KAAK,OAAO,MAAM,YAAY,oBAAoB,qBAAqB;GACrE,OAAO,KAAK;GACZ,OAAO,MAAM,QAAQ,KAAK;GAC1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;EAC/C,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,WAAW,OAAO;IAC7C,OAAO,KAAK;IACZ;IACA,GAAI,KAAK,yBAAyB,SAC9B,EAAE,YAAY,KAAK,qBAAqB,IACxC,CAAC;GACP,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,kBAAkB,QAAQ,SAAS;IAC/D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,KAAK,OAAO,MAAM,YAAY,qBAAqB,8BAA8B;GAC/E,YAAY,SAAS,KAAK,EAAE,EAAE,UAAU;GACxC,OAAO;IACL,cAAc,SAAS,MAAM;IAC7B,aAAa,SAAS,MAAM;GAC9B;EACF,CAAC;EAWD,IAAI,KAAK,eAAe,GAAG;GACzB,MAAM,iBAAiB,SAAS,KAAK,EAAE,EAAE;GAEzC,IAAI,mBAAmB,QACrB,KAAK,aAAa,eAAe;EAErC;EAEA,MAAM,QAAwB;GAC5B,cAAc,SAAS,MAAM;GAC7B,aAAa,SAAS,MAAM;EAC9B;EAEA,OAAO;GAAE;GAAU;EAAM;CAC3B;AACF"}
1
+ {"version":3,"file":"embedder.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"sourcesContent":["import {\n EmbeddingVectorCountMismatchError,\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIEmbedderConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\ntype EmbeddingsResponse = Awaited<ReturnType<OpenAI[\"embeddings\"][\"create\"]>>;\n\n/**\n * OpenAI-backed implementation of `EmbedderContract`.\n *\n * **Role.** Converts text (or a batch of texts) into floating-point\n * vectors via OpenAI's Embeddings API. Standalone primitive — no\n * relationship to chat completions, tools, or the agent loop.\n *\n * **Dimensions.** When no `dimensions` override is supplied in config,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached for all subsequent calls —\n * even if a later response were to return a different length, the\n * first value wins so batches stay dimensionally consistent. Passing\n * `dimensions` in config both forwards the truncation hint to the API\n * (for models like `text-embedding-3-*`) and sets the initial value.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the\n * typed `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError` —\n * callers catch `AIError` subclasses (`ProviderRateLimitError`,\n * `ProviderAuthError`, etc.) instead of OpenAI's own classes.\n *\n * @example\n * const embedder = new OpenAIEmbedder(client, { name: \"text-embedding-3-small\" });\n * const { vector, dimensions, usage } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class OpenAIEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider = \"openai\";\n public dimensions: number;\n\n private readonly client: OpenAI;\n\n /**\n * User-specified truncation hint, or `undefined` if omitted.\n * Forwarded to the API on every call so OpenAI can truncate the\n * embedding server-side for models that support it.\n */\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIEmbedderConfig) {\n this.client = client;\n this.name = config.name;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const { response, usage } = await this.request(input);\n\n const [first] = response.data;\n\n if (first === undefined) {\n // A 200 whose `data` array is empty. Before this guard the next line\n // read `.embedding` off `undefined` and the caller got a bare\n // `TypeError: Cannot read properties of undefined`, naming neither the\n // provider nor what it actually returned.\n //\n // Same typed error the RAG and skill-catalog paths raise for this\n // contract violation, so one shortfall has one name wherever it\n // surfaces. Expected 1, received 0.\n throw new EmbeddingVectorCountMismatchError({\n provider: this.provider,\n expectedCount: 1,\n receivedCount: response.data.length,\n record: input,\n });\n }\n\n return {\n vector: first.embedding,\n dimensions: this.dimensions,\n usage,\n };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const { response, usage } = await this.request(inputs);\n\n return {\n vectors: response.data.map((d) => d.embedding),\n dimensions: this.dimensions,\n usage,\n };\n }\n\n /**\n * Shared transport for both `embed()` and `embedMany()` — issues the\n * `embeddings.create` call, wraps provider errors, caches dimensions\n * on the first successful response, and returns the raw response\n * plus a camelCase usage object for the caller to shape.\n */\n private async request(input: string | string[]): Promise<{\n response: EmbeddingsResponse;\n usage: EmbeddingUsage;\n }> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embeddings.create\", {\n model: this.name,\n batch: Array.isArray(input),\n count: Array.isArray(input) ? input.length : 1,\n });\n\n let response: EmbeddingsResponse;\n\n try {\n response = await this.client.embeddings.create({\n model: this.name,\n input,\n ...(this.configuredDimensions !== undefined\n ? { dimensions: this.configuredDimensions }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embeddings.create returned\", {\n dimensions: response.data[0]?.embedding.length,\n usage: {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n },\n });\n\n // Cache dimensions on the first response. Once set, stays set —\n // we trust the first call to define the shape for this embedder.\n //\n // Reads the first entry optionally rather than asserting it: this runs for\n // BOTH embed() and embedMany(), and an empty `data` array is a real\n // provider response, not an impossible one. Leaving `dimensions` at 0 for\n // an empty response is correct — the callers each raise their own typed\n // error about the shortfall, and cacheing a dimension off a response that\n // carried no vectors would be inventing a shape from nothing.\n if (this.dimensions === 0) {\n const firstEmbedding = response.data[0]?.embedding;\n\n if (firstEmbedding !== undefined) {\n this.dimensions = firstEmbedding.length;\n }\n }\n\n const usage: EmbeddingUsage = {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n };\n\n return { response, usage };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BnB,IAAa,iBAAb,MAAwD;CAetD,AAAO,YAAY,QAAgB,QAA8B;kBAbtC;gBAWO;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,uBAAuB,OAAO;EACnC,KAAK,aAAa,OAAO,cAAc;CACzC;CAEA,MAAa,MAAM,OAAyC;EAC1D,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,KAAK;EAEpD,MAAM,CAAC,SAAS,SAAS;EAEzB,IAAI,UAAU,QASZ,MAAM,IAAI,kCAAkC;GAC1C,UAAU,KAAK;GACf,eAAe;GACf,eAAe,SAAS,KAAK;GAC7B,QAAQ;EACV,CAAC;EAGH,OAAO;GACL,QAAQ,MAAM;GACd,YAAY,KAAK;GACjB;EACF;CACF;CAEA,MAAa,UAAU,QAAiD;EACtE,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,MAAM;EAErD,OAAO;GACL,SAAS,SAAS,KAAK,KAAK,MAAM,EAAE,SAAS;GAC7C,YAAY,KAAK;GACjB;EACF;CACF;;;;;;;CAQA,MAAc,QAAQ,OAGnB;EACD,KAAK,OAAO,MAAM,YAAY,oBAAoB,qBAAqB;GACrE,OAAO,KAAK;GACZ,OAAO,MAAM,QAAQ,KAAK;GAC1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;EAC/C,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,WAAW,OAAO;IAC7C,OAAO,KAAK;IACZ;IACA,GAAI,KAAK,yBAAyB,SAC9B,EAAE,YAAY,KAAK,qBAAqB,IACxC,CAAC;GACP,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,kBAAkB,QAAQ,SAAS;IAC/D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,KAAK,OAAO,MAAM,YAAY,qBAAqB,8BAA8B;GAC/E,YAAY,SAAS,KAAK,EAAE,EAAE,UAAU;GACxC,OAAO;IACL,cAAc,SAAS,MAAM;IAC7B,aAAa,SAAS,MAAM;GAC9B;EACF,CAAC;EAWD,IAAI,KAAK,eAAe,GAAG;GACzB,MAAM,iBAAiB,SAAS,KAAK,EAAE,EAAE;GAEzC,IAAI,mBAAmB,QACrB,KAAK,aAAa,eAAe;EAErC;EAEA,MAAM,QAAwB;GAC5B,cAAc,SAAS,MAAM;GAC7B,aAAa,SAAS,MAAM;EAC9B;EAEA,OAAO;GAAE;GAAU;EAAM;CAC3B;AACF"}
package/esm/image.d.mts CHANGED
@@ -2,7 +2,7 @@ import { OpenAIImageConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { ImageGenerationOptions, ImageGenerationResponse, ImageModelContract, ImageModelPricing } from "@warlock.js/ai";
4
4
 
5
- //#region ../ai-openai/src/image.d.ts
5
+ //#region ../../packages/ai-openai/src/image.d.ts
6
6
  /**
7
7
  * OpenAI-backed implementation of `ImageModelContract`.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.mts","names":[],"sources":["../../../../../../ai-openai/src/image.ts"],"mappings":";;;;;;;AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,kBAAA;EAAA,SACvB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,iBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,iBAAA,EAAmB,QAAA;EAOjD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;;;;;;;;UA+EH,gBAAA;AAAA"}
1
+ {"version":3,"file":"image.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/image.ts"],"mappings":";;;;;;;AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,kBAAA;EAAA,SACvB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,iBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,iBAAA,EAAmB,QAAA;EAOjD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;;;;;;;;UA+EH,gBAAA;AAAA"}
package/esm/image.mjs CHANGED
@@ -3,7 +3,7 @@ import "./utils/index.mjs";
3
3
  import { ProviderError } from "@warlock.js/ai";
4
4
  import { log } from "@warlock.js/logger";
5
5
 
6
- //#region ../ai-openai/src/image.ts
6
+ //#region ../../packages/ai-openai/src/image.ts
7
7
  const LOG_MODULE = "ai.openai";
8
8
  /** Map a neutral output container to its IANA media type. */
9
9
  function mediaTypeFor(format) {
package/esm/image.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"image.mjs","names":[],"sources":["../../../../../../ai-openai/src/image.ts"],"sourcesContent":["import {\n ProviderError,\n type GeneratedImage,\n type ImageGenerationOptions,\n type ImageGenerationResponse,\n type ImageModelContract,\n type ImageModelPricing,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIImageConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Map a neutral output container to its IANA media type. */\nfunction mediaTypeFor(format: string | undefined): string {\n switch (format) {\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n default:\n return \"image/png\";\n }\n}\n\n/**\n * OpenAI-backed implementation of `ImageModelContract`.\n *\n * **Role.** Bridges the vendor-neutral `ai.image()` verb to OpenAI's\n * **Images** API. Metering follows the family the id belongs to: the\n * token-metered `gpt-image-*` models (always return base64 bytes) and\n * the per-image-metered `dall-e-*` models (URL or base64). Like\n * `OpenAIEmbedder`, it's a standalone primitive — no relationship to\n * chat completions, tools, or the agent loop.\n *\n * **No model-id validation.** `config.name` is forwarded to\n * `client.images.generate` exactly as given — the constructor never\n * inspects it. OpenAI adds and retires image model ids on its own\n * schedule, so an id this adapter does not recognize is not the\n * adapter's call to refuse; an unsupported id surfaces as a provider\n * error from OpenAI (wrapped into the typed `AIError` hierarchy by\n * `generate()`), not as a local one.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the typed\n * `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError`, so a\n * caller catches `ProviderRateLimitError` / `ContentFilterError` /\n * `ProviderAuthError` rather than OpenAI's own classes. `ai.image()`\n * turns those throws into `result.error`.\n *\n * @example\n * const model = new OpenAIImageModel(client, { name: \"gpt-image-1\" }, \"openai\");\n * const { images, usage } = await model.generate(\"a teal ceramic mug, studio light\");\n */\nexport class OpenAIImageModel implements ImageModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: ImageModelPricing;\n\n private readonly client: OpenAI;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIImageConfig, provider: string = \"openai\") {\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async generate(\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const isGptImage = this.name.startsWith(\"gpt-image\");\n // gpt-image always returns base64 bytes (no `response_format` knob);\n // DALL·E defaults to self-contained base64 here (URLs expire in ~60\n // min), but the caller can ask for a URL via `options.responseFormat`.\n const responseFormat =\n (options?.responseFormat as \"url\" | \"b64_json\" | undefined) ??\n (isGptImage ? undefined : \"b64_json\");\n\n const body: OpenAI.Images.ImageGenerateParamsNonStreaming = {\n model: this.name,\n prompt,\n };\n\n if (options?.count !== undefined) body.n = options.count;\n if (options?.size !== undefined) body.size = options.size;\n if (options?.quality !== undefined) {\n body.quality = options.quality as OpenAI.Images.ImageGenerateParamsNonStreaming[\"quality\"];\n }\n if (!isGptImage && responseFormat) body.response_format = responseFormat;\n if (isGptImage && options?.format !== undefined) {\n body.output_format =\n options.format as OpenAI.Images.ImageGenerateParamsNonStreaming[\"output_format\"];\n }\n if (options?.background !== undefined) {\n body.background =\n options.background as OpenAI.Images.ImageGenerateParamsNonStreaming[\"background\"];\n }\n\n this.logger.debug(LOG_MODULE, \"image.request\", \"images.generate\", {\n model: this.name,\n count: options?.count ?? 1,\n });\n\n let response: OpenAI.Images.ImagesResponse;\n\n try {\n response = await this.client.images.generate(\n body,\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"image.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const images = (response.data ?? []).map((image) =>\n this.toGeneratedImage(image, options?.format),\n );\n\n const usage = response.usage\n ? {\n input: response.usage.input_tokens,\n output: response.usage.output_tokens,\n total: response.usage.total_tokens,\n }\n : { input: 0, output: 0, total: 0 };\n\n this.logger.debug(LOG_MODULE, \"image.response\", \"images.generate succeeded\", {\n images: images.length,\n usage,\n });\n\n return { images, usage };\n }\n\n /**\n * Normalize one OpenAI `Image` into the neutral discriminated shape.\n * Base64 wins when present (gpt-image, and DALL·E in b64 mode);\n * otherwise a hosted URL. A response carrying neither is a provider\n * contract violation — surface it as a typed `ProviderError` rather\n * than emitting a half-formed part.\n */\n private toGeneratedImage(image: OpenAI.Images.Image, format: string | undefined): GeneratedImage {\n if (image.b64_json) {\n return {\n type: \"base64\",\n base64: image.b64_json,\n mediaType: mediaTypeFor(format),\n ...(image.revised_prompt ? { revisedPrompt: image.revised_prompt } : {}),\n };\n }\n\n if (image.url) {\n return {\n type: \"url\",\n url: image.url,\n ...(image.revised_prompt ? { revisedPrompt: image.revised_prompt } : {}),\n };\n }\n\n throw new ProviderError(\"OpenAI image response contained neither base64 bytes nor a URL.\");\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa;;AAGnB,SAAS,aAAa,QAAoC;CACxD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,mBAAb,MAA4D;CAQ1D,AAAO,YAAY,QAAgB,QAA2B,WAAmB,UAAU;gBAFzD;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;CACxB;CAEA,MAAa,SACX,QACA,SACkC;EAClC,MAAM,aAAa,KAAK,KAAK,WAAW,WAAW;EAInD,MAAM,iBACH,SAAS,mBACT,aAAa,SAAY;EAE5B,MAAM,OAAsD;GAC1D,OAAO,KAAK;GACZ;EACF;EAEA,IAAI,SAAS,UAAU,QAAW,KAAK,IAAI,QAAQ;EACnD,IAAI,SAAS,SAAS,QAAW,KAAK,OAAO,QAAQ;EACrD,IAAI,SAAS,YAAY,QACvB,KAAK,UAAU,QAAQ;EAEzB,IAAI,CAAC,cAAc,gBAAgB,KAAK,kBAAkB;EAC1D,IAAI,cAAc,SAAS,WAAW,QACpC,KAAK,gBACH,QAAQ;EAEZ,IAAI,SAAS,eAAe,QAC1B,KAAK,aACH,QAAQ;EAGZ,KAAK,OAAO,MAAM,YAAY,iBAAiB,mBAAmB;GAChE,OAAO,KAAK;GACZ,OAAO,SAAS,SAAS;EAC3B,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,OAAO,SAClC,MACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,eAAe,QAAQ,SAAS;IAC5D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,MAAM,UAAU,SAAS,QAAQ,CAAC,EAAC,CAAE,KAAK,UACxC,KAAK,iBAAiB,OAAO,SAAS,MAAM,CAC9C;EAEA,MAAM,QAAQ,SAAS,QACnB;GACE,OAAO,SAAS,MAAM;GACtB,QAAQ,SAAS,MAAM;GACvB,OAAO,SAAS,MAAM;EACxB,IACA;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAEpC,KAAK,OAAO,MAAM,YAAY,kBAAkB,6BAA6B;GAC3E,QAAQ,OAAO;GACf;EACF,CAAC;EAED,OAAO;GAAE;GAAQ;EAAM;CACzB;;;;;;;;CASA,AAAQ,iBAAiB,OAA4B,QAA4C;EAC/F,IAAI,MAAM,UACR,OAAO;GACL,MAAM;GACN,QAAQ,MAAM;GACd,WAAW,aAAa,MAAM;GAC9B,GAAI,MAAM,iBAAiB,EAAE,eAAe,MAAM,eAAe,IAAI,CAAC;EACxE;EAGF,IAAI,MAAM,KACR,OAAO;GACL,MAAM;GACN,KAAK,MAAM;GACX,GAAI,MAAM,iBAAiB,EAAE,eAAe,MAAM,eAAe,IAAI,CAAC;EACxE;EAGF,MAAM,IAAI,cAAc,iEAAiE;CAC3F;AACF"}
1
+ {"version":3,"file":"image.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/image.ts"],"sourcesContent":["import {\n ProviderError,\n type GeneratedImage,\n type ImageGenerationOptions,\n type ImageGenerationResponse,\n type ImageModelContract,\n type ImageModelPricing,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIImageConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Map a neutral output container to its IANA media type. */\nfunction mediaTypeFor(format: string | undefined): string {\n switch (format) {\n case \"jpeg\":\n case \"jpg\":\n return \"image/jpeg\";\n case \"webp\":\n return \"image/webp\";\n default:\n return \"image/png\";\n }\n}\n\n/**\n * OpenAI-backed implementation of `ImageModelContract`.\n *\n * **Role.** Bridges the vendor-neutral `ai.image()` verb to OpenAI's\n * **Images** API. Metering follows the family the id belongs to: the\n * token-metered `gpt-image-*` models (always return base64 bytes) and\n * the per-image-metered `dall-e-*` models (URL or base64). Like\n * `OpenAIEmbedder`, it's a standalone primitive — no relationship to\n * chat completions, tools, or the agent loop.\n *\n * **No model-id validation.** `config.name` is forwarded to\n * `client.images.generate` exactly as given — the constructor never\n * inspects it. OpenAI adds and retires image model ids on its own\n * schedule, so an id this adapter does not recognize is not the\n * adapter's call to refuse; an unsupported id surfaces as a provider\n * error from OpenAI (wrapped into the typed `AIError` hierarchy by\n * `generate()`), not as a local one.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the typed\n * `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError`, so a\n * caller catches `ProviderRateLimitError` / `ContentFilterError` /\n * `ProviderAuthError` rather than OpenAI's own classes. `ai.image()`\n * turns those throws into `result.error`.\n *\n * @example\n * const model = new OpenAIImageModel(client, { name: \"gpt-image-1\" }, \"openai\");\n * const { images, usage } = await model.generate(\"a teal ceramic mug, studio light\");\n */\nexport class OpenAIImageModel implements ImageModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: ImageModelPricing;\n\n private readonly client: OpenAI;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIImageConfig, provider: string = \"openai\") {\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n }\n\n public async generate(\n prompt: string,\n options?: ImageGenerationOptions,\n ): Promise<ImageGenerationResponse> {\n const isGptImage = this.name.startsWith(\"gpt-image\");\n // gpt-image always returns base64 bytes (no `response_format` knob);\n // DALL·E defaults to self-contained base64 here (URLs expire in ~60\n // min), but the caller can ask for a URL via `options.responseFormat`.\n const responseFormat =\n (options?.responseFormat as \"url\" | \"b64_json\" | undefined) ??\n (isGptImage ? undefined : \"b64_json\");\n\n const body: OpenAI.Images.ImageGenerateParamsNonStreaming = {\n model: this.name,\n prompt,\n };\n\n if (options?.count !== undefined) body.n = options.count;\n if (options?.size !== undefined) body.size = options.size;\n if (options?.quality !== undefined) {\n body.quality = options.quality as OpenAI.Images.ImageGenerateParamsNonStreaming[\"quality\"];\n }\n if (!isGptImage && responseFormat) body.response_format = responseFormat;\n if (isGptImage && options?.format !== undefined) {\n body.output_format =\n options.format as OpenAI.Images.ImageGenerateParamsNonStreaming[\"output_format\"];\n }\n if (options?.background !== undefined) {\n body.background =\n options.background as OpenAI.Images.ImageGenerateParamsNonStreaming[\"background\"];\n }\n\n this.logger.debug(LOG_MODULE, \"image.request\", \"images.generate\", {\n model: this.name,\n count: options?.count ?? 1,\n });\n\n let response: OpenAI.Images.ImagesResponse;\n\n try {\n response = await this.client.images.generate(\n body,\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"image.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const images = (response.data ?? []).map((image) =>\n this.toGeneratedImage(image, options?.format),\n );\n\n const usage = response.usage\n ? {\n input: response.usage.input_tokens,\n output: response.usage.output_tokens,\n total: response.usage.total_tokens,\n }\n : { input: 0, output: 0, total: 0 };\n\n this.logger.debug(LOG_MODULE, \"image.response\", \"images.generate succeeded\", {\n images: images.length,\n usage,\n });\n\n return { images, usage };\n }\n\n /**\n * Normalize one OpenAI `Image` into the neutral discriminated shape.\n * Base64 wins when present (gpt-image, and DALL·E in b64 mode);\n * otherwise a hosted URL. A response carrying neither is a provider\n * contract violation — surface it as a typed `ProviderError` rather\n * than emitting a half-formed part.\n */\n private toGeneratedImage(image: OpenAI.Images.Image, format: string | undefined): GeneratedImage {\n if (image.b64_json) {\n return {\n type: \"base64\",\n base64: image.b64_json,\n mediaType: mediaTypeFor(format),\n ...(image.revised_prompt ? { revisedPrompt: image.revised_prompt } : {}),\n };\n }\n\n if (image.url) {\n return {\n type: \"url\",\n url: image.url,\n ...(image.revised_prompt ? { revisedPrompt: image.revised_prompt } : {}),\n };\n }\n\n throw new ProviderError(\"OpenAI image response contained neither base64 bytes nor a URL.\");\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,aAAa;;AAGnB,SAAS,aAAa,QAAoC;CACxD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,mBAAb,MAA4D;CAQ1D,AAAO,YAAY,QAAgB,QAA2B,WAAmB,UAAU;gBAFzD;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;CACxB;CAEA,MAAa,SACX,QACA,SACkC;EAClC,MAAM,aAAa,KAAK,KAAK,WAAW,WAAW;EAInD,MAAM,iBACH,SAAS,mBACT,aAAa,SAAY;EAE5B,MAAM,OAAsD;GAC1D,OAAO,KAAK;GACZ;EACF;EAEA,IAAI,SAAS,UAAU,QAAW,KAAK,IAAI,QAAQ;EACnD,IAAI,SAAS,SAAS,QAAW,KAAK,OAAO,QAAQ;EACrD,IAAI,SAAS,YAAY,QACvB,KAAK,UAAU,QAAQ;EAEzB,IAAI,CAAC,cAAc,gBAAgB,KAAK,kBAAkB;EAC1D,IAAI,cAAc,SAAS,WAAW,QACpC,KAAK,gBACH,QAAQ;EAEZ,IAAI,SAAS,eAAe,QAC1B,KAAK,aACH,QAAQ;EAGZ,KAAK,OAAO,MAAM,YAAY,iBAAiB,mBAAmB;GAChE,OAAO,KAAK;GACZ,OAAO,SAAS,SAAS;EAC3B,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,OAAO,SAClC,MACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,eAAe,QAAQ,SAAS;IAC5D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,MAAM,UAAU,SAAS,QAAQ,CAAC,EAAC,CAAE,KAAK,UACxC,KAAK,iBAAiB,OAAO,SAAS,MAAM,CAC9C;EAEA,MAAM,QAAQ,SAAS,QACnB;GACE,OAAO,SAAS,MAAM;GACtB,QAAQ,SAAS,MAAM;GACvB,OAAO,SAAS,MAAM;EACxB,IACA;GAAE,OAAO;GAAG,QAAQ;GAAG,OAAO;EAAE;EAEpC,KAAK,OAAO,MAAM,YAAY,kBAAkB,6BAA6B;GAC3E,QAAQ,OAAO;GACf;EACF,CAAC;EAED,OAAO;GAAE;GAAQ;EAAM;CACzB;;;;;;;;CASA,AAAQ,iBAAiB,OAA4B,QAA4C;EAC/F,IAAI,MAAM,UACR,OAAO;GACL,MAAM;GACN,QAAQ,MAAM;GACd,WAAW,aAAa,MAAM;GAC9B,GAAI,MAAM,iBAAiB,EAAE,eAAe,MAAM,eAAe,IAAI,CAAC;EACxE;EAGF,IAAI,MAAM,KACR,OAAO;GACL,MAAM;GACN,KAAK,MAAM;GACX,GAAI,MAAM,iBAAiB,EAAE,eAAe,MAAM,eAAe,IAAI,CAAC;EACxE;EAGF,MAAM,IAAI,cAAc,iEAAiE;CAC3F;AACF"}
package/esm/index.d.mts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { OpenAIEmbedderConfig, OpenAIImageConfig, OpenAISDKConfig, OpenAISpeechConfig, OpenAITranscriptionConfig } from "./config.type.mjs";
2
2
  import { OpenAISDK } from "./sdk.mjs";
3
+ import { OPENAI_MODEL_PRICING, openAIPricingFor } from "./known-pricing.mjs";
3
4
  import { OpenAIEmbedder } from "./embedder.mjs";
4
5
  import { OpenAIImageModel } from "./image.mjs";
5
6
  import { OpenAISpeechModel } from "./speech.mjs";
6
7
  import { OpenAITranscriptionModel } from "./transcription.mjs";
7
- export { OpenAIEmbedder, type OpenAIEmbedderConfig, type OpenAIImageConfig, OpenAIImageModel, OpenAISDK, type OpenAISDKConfig, type OpenAISpeechConfig, OpenAISpeechModel, type OpenAITranscriptionConfig, OpenAITranscriptionModel };
8
+ import { OpenAICallOptions } from "./provider-options.mjs";
9
+ export { OPENAI_MODEL_PRICING, type OpenAICallOptions, OpenAIEmbedder, type OpenAIEmbedderConfig, type OpenAIImageConfig, OpenAIImageModel, OpenAISDK, type OpenAISDKConfig, type OpenAISpeechConfig, OpenAISpeechModel, type OpenAITranscriptionConfig, OpenAITranscriptionModel, openAIPricingFor };
package/esm/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
+ import { OPENAI_MODEL_PRICING, openAIPricingFor } from "./known-pricing.mjs";
1
2
  import { OpenAIEmbedder } from "./embedder.mjs";
2
3
  import { OpenAIImageModel } from "./image.mjs";
3
4
  import { OpenAISpeechModel } from "./speech.mjs";
4
5
  import { OpenAITranscriptionModel } from "./transcription.mjs";
5
6
  import { OpenAISDK } from "./sdk.mjs";
6
7
 
7
- export { OpenAIEmbedder, OpenAIImageModel, OpenAISDK, OpenAISpeechModel, OpenAITranscriptionModel };
8
+ export { OPENAI_MODEL_PRICING, OpenAIEmbedder, OpenAIImageModel, OpenAISDK, OpenAISpeechModel, OpenAITranscriptionModel, openAIPricingFor };
@@ -0,0 +1,35 @@
1
+ import { ModelPricing } from "@warlock.js/ai";
2
+
3
+ //#region ../../packages/ai-openai/src/known-pricing.d.ts
4
+ /**
5
+ * Built-in OpenAI API list prices, USD per 1,000,000 tokens, keyed by
6
+ * exact model id. `cachedInput` is the cached-input rate. None of these
7
+ * models charges for cache writes.
8
+ *
9
+ * Source: https://developers.openai.com/api/docs/pricing (the target of
10
+ * platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17.
11
+ *
12
+ * Applied only when the SDK's provider label is `"openai"`, and only on
13
+ * an exact id match — dated snapshots can carry other prices. Left out on
14
+ * purpose, so pass `pricing` yourself for them:
15
+ *
16
+ * - Models priced by prompt length (a higher rate above 272K input
17
+ * tokens): the gpt-6 / gpt-5.6 / gpt-5.5 / gpt-5.4 families and the
18
+ * `-pro` models. A flat `ModelPricing` cannot express the tiers.
19
+ * - Promotional, Batch, Flex and Priority pricing, and the regional
20
+ * (data-residency) uplift.
21
+ */
22
+ declare const OPENAI_MODEL_PRICING: Readonly<Record<string, ModelPricing>>;
23
+ /**
24
+ * Look up the built-in list price for an OpenAI model id (exact match).
25
+ * Returns `undefined` for unknown ids, so cost stays unknown rather than
26
+ * wrong.
27
+ *
28
+ * @example
29
+ * openAIPricingFor("gpt-4o-mini"); // { input: 0.15, output: 0.6, cachedInput: 0.075 }
30
+ * openAIPricingFor("gpt-5.5"); // undefined — priced by prompt length
31
+ */
32
+ declare function openAIPricingFor(modelName: string): ModelPricing | undefined;
33
+ //#endregion
34
+ export { OPENAI_MODEL_PRICING, openAIPricingFor };
35
+ //# sourceMappingURL=known-pricing.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-pricing.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/known-pricing.ts"],"mappings":";;;;;AAoBA;;;;;;;;;;;;AAAuE;AA4BvE;;;cA5Ba,oBAAA,EAAsB,QAAA,CAAS,MAAA,SAAe,YAAA;AA4BM;;;;;;;;;AAAA,iBAAjD,gBAAA,CAAiB,SAAA,WAAoB,YAAY"}
@@ -0,0 +1,117 @@
1
+ //#region ../../packages/ai-openai/src/known-pricing.ts
2
+ /**
3
+ * Built-in OpenAI API list prices, USD per 1,000,000 tokens, keyed by
4
+ * exact model id. `cachedInput` is the cached-input rate. None of these
5
+ * models charges for cache writes.
6
+ *
7
+ * Source: https://developers.openai.com/api/docs/pricing (the target of
8
+ * platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17.
9
+ *
10
+ * Applied only when the SDK's provider label is `"openai"`, and only on
11
+ * an exact id match — dated snapshots can carry other prices. Left out on
12
+ * purpose, so pass `pricing` yourself for them:
13
+ *
14
+ * - Models priced by prompt length (a higher rate above 272K input
15
+ * tokens): the gpt-6 / gpt-5.6 / gpt-5.5 / gpt-5.4 families and the
16
+ * `-pro` models. A flat `ModelPricing` cannot express the tiers.
17
+ * - Promotional, Batch, Flex and Priority pricing, and the regional
18
+ * (data-residency) uplift.
19
+ */
20
+ const OPENAI_MODEL_PRICING = {
21
+ "gpt-5.4-mini": {
22
+ input: .75,
23
+ output: 4.5,
24
+ cachedInput: .075
25
+ },
26
+ "gpt-5.4-nano": {
27
+ input: .2,
28
+ output: 1.25,
29
+ cachedInput: .02
30
+ },
31
+ "gpt-5.2": {
32
+ input: 1.75,
33
+ output: 14,
34
+ cachedInput: .175
35
+ },
36
+ "gpt-5.1": {
37
+ input: 1.25,
38
+ output: 10,
39
+ cachedInput: .125
40
+ },
41
+ "gpt-5": {
42
+ input: 1.25,
43
+ output: 10,
44
+ cachedInput: .125
45
+ },
46
+ "gpt-5-mini": {
47
+ input: .25,
48
+ output: 2,
49
+ cachedInput: .025
50
+ },
51
+ "gpt-5-nano": {
52
+ input: .05,
53
+ output: .4,
54
+ cachedInput: .005
55
+ },
56
+ "gpt-4.1": {
57
+ input: 2,
58
+ output: 8,
59
+ cachedInput: .5
60
+ },
61
+ "gpt-4.1-mini": {
62
+ input: .4,
63
+ output: 1.6,
64
+ cachedInput: .1
65
+ },
66
+ "gpt-4.1-nano": {
67
+ input: .1,
68
+ output: .4,
69
+ cachedInput: .025
70
+ },
71
+ "gpt-4o": {
72
+ input: 2.5,
73
+ output: 10,
74
+ cachedInput: 1.25
75
+ },
76
+ "gpt-4o-mini": {
77
+ input: .15,
78
+ output: .6,
79
+ cachedInput: .075
80
+ },
81
+ o3: {
82
+ input: 2,
83
+ output: 8,
84
+ cachedInput: .5
85
+ },
86
+ "o4-mini": {
87
+ input: 1.1,
88
+ output: 4.4,
89
+ cachedInput: .275
90
+ },
91
+ "o3-mini": {
92
+ input: 1.1,
93
+ output: 4.4,
94
+ cachedInput: .55
95
+ },
96
+ o1: {
97
+ input: 15,
98
+ output: 60,
99
+ cachedInput: 7.5
100
+ }
101
+ };
102
+ /**
103
+ * Look up the built-in list price for an OpenAI model id (exact match).
104
+ * Returns `undefined` for unknown ids, so cost stays unknown rather than
105
+ * wrong.
106
+ *
107
+ * @example
108
+ * openAIPricingFor("gpt-4o-mini"); // { input: 0.15, output: 0.6, cachedInput: 0.075 }
109
+ * openAIPricingFor("gpt-5.5"); // undefined — priced by prompt length
110
+ */
111
+ function openAIPricingFor(modelName) {
112
+ return OPENAI_MODEL_PRICING[modelName];
113
+ }
114
+
115
+ //#endregion
116
+ export { OPENAI_MODEL_PRICING, openAIPricingFor };
117
+ //# sourceMappingURL=known-pricing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-pricing.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/known-pricing.ts"],"sourcesContent":["import type { ModelPricing } from \"@warlock.js/ai\";\n\n/**\n * Built-in OpenAI API list prices, USD per 1,000,000 tokens, keyed by\n * exact model id. `cachedInput` is the cached-input rate. None of these\n * models charges for cache writes.\n *\n * Source: https://developers.openai.com/api/docs/pricing (the target of\n * platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17.\n *\n * Applied only when the SDK's provider label is `\"openai\"`, and only on\n * an exact id match — dated snapshots can carry other prices. Left out on\n * purpose, so pass `pricing` yourself for them:\n *\n * - Models priced by prompt length (a higher rate above 272K input\n * tokens): the gpt-6 / gpt-5.6 / gpt-5.5 / gpt-5.4 families and the\n * `-pro` models. A flat `ModelPricing` cannot express the tiers.\n * - Promotional, Batch, Flex and Priority pricing, and the regional\n * (data-residency) uplift.\n */\nexport const OPENAI_MODEL_PRICING: Readonly<Record<string, ModelPricing>> = {\n \"gpt-5.4-mini\": { input: 0.75, output: 4.5, cachedInput: 0.075 },\n \"gpt-5.4-nano\": { input: 0.2, output: 1.25, cachedInput: 0.02 },\n \"gpt-5.2\": { input: 1.75, output: 14, cachedInput: 0.175 },\n \"gpt-5.1\": { input: 1.25, output: 10, cachedInput: 0.125 },\n \"gpt-5\": { input: 1.25, output: 10, cachedInput: 0.125 },\n \"gpt-5-mini\": { input: 0.25, output: 2, cachedInput: 0.025 },\n \"gpt-5-nano\": { input: 0.05, output: 0.4, cachedInput: 0.005 },\n \"gpt-4.1\": { input: 2, output: 8, cachedInput: 0.5 },\n \"gpt-4.1-mini\": { input: 0.4, output: 1.6, cachedInput: 0.1 },\n \"gpt-4.1-nano\": { input: 0.1, output: 0.4, cachedInput: 0.025 },\n \"gpt-4o\": { input: 2.5, output: 10, cachedInput: 1.25 },\n \"gpt-4o-mini\": { input: 0.15, output: 0.6, cachedInput: 0.075 },\n o3: { input: 2, output: 8, cachedInput: 0.5 },\n \"o4-mini\": { input: 1.1, output: 4.4, cachedInput: 0.275 },\n \"o3-mini\": { input: 1.1, output: 4.4, cachedInput: 0.55 },\n o1: { input: 15, output: 60, cachedInput: 7.5 },\n};\n\n/**\n * Look up the built-in list price for an OpenAI model id (exact match).\n * Returns `undefined` for unknown ids, so cost stays unknown rather than\n * wrong.\n *\n * @example\n * openAIPricingFor(\"gpt-4o-mini\"); // { input: 0.15, output: 0.6, cachedInput: 0.075 }\n * openAIPricingFor(\"gpt-5.5\"); // undefined — priced by prompt length\n */\nexport function openAIPricingFor(modelName: string): ModelPricing | undefined {\n return OPENAI_MODEL_PRICING[modelName];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,MAAa,uBAA+D;CAC1E,gBAAgB;EAAE,OAAO;EAAM,QAAQ;EAAK,aAAa;CAAM;CAC/D,gBAAgB;EAAE,OAAO;EAAK,QAAQ;EAAM,aAAa;CAAK;CAC9D,WAAW;EAAE,OAAO;EAAM,QAAQ;EAAI,aAAa;CAAM;CACzD,WAAW;EAAE,OAAO;EAAM,QAAQ;EAAI,aAAa;CAAM;CACzD,SAAS;EAAE,OAAO;EAAM,QAAQ;EAAI,aAAa;CAAM;CACvD,cAAc;EAAE,OAAO;EAAM,QAAQ;EAAG,aAAa;CAAM;CAC3D,cAAc;EAAE,OAAO;EAAM,QAAQ;EAAK,aAAa;CAAM;CAC7D,WAAW;EAAE,OAAO;EAAG,QAAQ;EAAG,aAAa;CAAI;CACnD,gBAAgB;EAAE,OAAO;EAAK,QAAQ;EAAK,aAAa;CAAI;CAC5D,gBAAgB;EAAE,OAAO;EAAK,QAAQ;EAAK,aAAa;CAAM;CAC9D,UAAU;EAAE,OAAO;EAAK,QAAQ;EAAI,aAAa;CAAK;CACtD,eAAe;EAAE,OAAO;EAAM,QAAQ;EAAK,aAAa;CAAM;CAC9D,IAAI;EAAE,OAAO;EAAG,QAAQ;EAAG,aAAa;CAAI;CAC5C,WAAW;EAAE,OAAO;EAAK,QAAQ;EAAK,aAAa;CAAM;CACzD,WAAW;EAAE,OAAO;EAAK,QAAQ;EAAK,aAAa;CAAK;CACxD,IAAI;EAAE,OAAO;EAAI,QAAQ;EAAI,aAAa;CAAI;AAChD;;;;;;;;;;AAWA,SAAgB,iBAAiB,WAA6C;CAC5E,OAAO,qBAAqB;AAC9B"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/known-reasoning-models.ts
1
+ //#region ../../packages/ai-openai/src/known-reasoning-models.ts
2
2
  /**
3
3
  * Model-name prefixes for OpenAI families that expose internal
4
4
  * reasoning / thinking tokens and accept the `reasoning_effort`
@@ -1 +1 @@
1
- {"version":3,"file":"known-reasoning-models.mjs","names":[],"sources":["../../../../../../ai-openai/src/known-reasoning-models.ts"],"sourcesContent":["/**\n * Model-name prefixes for OpenAI families that expose internal\n * reasoning / thinking tokens and accept the `reasoning_effort`\n * request parameter on the Chat Completions API.\n *\n * Matched as a prefix so dated variants (`o3-2025-04-16`) and\n * `-mini` / `-pro` suffixes (`o4-mini`, `gpt-5-pro`) are covered\n * without listing every release tag explicitly.\n *\n * Maintenance: append a new prefix when OpenAI ships a reasoning\n * model family that doesn't already match. Devs can always override\n * per-model via `openai.model({ name, reasoning: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst REASONING_CAPABLE_PREFIXES = [\"o1\", \"o3\", \"o4\", \"gpt-5\", \"gpt-6\"];\n\n/**\n * Infer whether a given OpenAI model name is a reasoning model (o-series\n * and the gpt-5 / gpt-6 families) based on the known-prefix list. Unknown models\n * default to `false` so the adapter never forwards an unsupported\n * `reasoning_effort` param to a non-reasoning model (which would 400).\n *\n * @example\n * inferReasoningCapability(\"o3-mini\"); // → true\n * inferReasoningCapability(\"o4-mini\"); // → true\n * inferReasoningCapability(\"gpt-5-pro\"); // → true\n * inferReasoningCapability(\"gpt-6-astra\"); // → true\n * inferReasoningCapability(\"gpt-4o\"); // → false\n * inferReasoningCapability(\"custom-llm\"); // → false\n */\nexport function inferReasoningCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return REASONING_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAM,6BAA6B;CAAC;CAAM;CAAM;CAAM;CAAS;AAAO;;;;;;;;;;;;;;;AAgBtE,SAAgB,yBAAyB,WAA4B;CACnE,MAAM,aAAa,UAAU,YAAY;CAEzC,OAAO,2BAA2B,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC;AAClF"}
1
+ {"version":3,"file":"known-reasoning-models.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/known-reasoning-models.ts"],"sourcesContent":["/**\n * Model-name prefixes for OpenAI families that expose internal\n * reasoning / thinking tokens and accept the `reasoning_effort`\n * request parameter on the Chat Completions API.\n *\n * Matched as a prefix so dated variants (`o3-2025-04-16`) and\n * `-mini` / `-pro` suffixes (`o4-mini`, `gpt-5-pro`) are covered\n * without listing every release tag explicitly.\n *\n * Maintenance: append a new prefix when OpenAI ships a reasoning\n * model family that doesn't already match. Devs can always override\n * per-model via `openai.model({ name, reasoning: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst REASONING_CAPABLE_PREFIXES = [\"o1\", \"o3\", \"o4\", \"gpt-5\", \"gpt-6\"];\n\n/**\n * Infer whether a given OpenAI model name is a reasoning model (o-series\n * and the gpt-5 / gpt-6 families) based on the known-prefix list. Unknown models\n * default to `false` so the adapter never forwards an unsupported\n * `reasoning_effort` param to a non-reasoning model (which would 400).\n *\n * @example\n * inferReasoningCapability(\"o3-mini\"); // → true\n * inferReasoningCapability(\"o4-mini\"); // → true\n * inferReasoningCapability(\"gpt-5-pro\"); // → true\n * inferReasoningCapability(\"gpt-6-astra\"); // → true\n * inferReasoningCapability(\"gpt-4o\"); // → false\n * inferReasoningCapability(\"custom-llm\"); // → false\n */\nexport function inferReasoningCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return REASONING_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAM,6BAA6B;CAAC;CAAM;CAAM;CAAM;CAAS;AAAO;;;;;;;;;;;;;;;AAgBtE,SAAgB,yBAAyB,WAA4B;CACnE,MAAM,aAAa,UAAU,YAAY;CAEzC,OAAO,2BAA2B,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC;AAClF"}
@@ -1,4 +1,4 @@
1
- //#region ../ai-openai/src/known-vision-models.ts
1
+ //#region ../../packages/ai-openai/src/known-vision-models.ts
2
2
  /**
3
3
  * Model-name prefixes for OpenAI families that support vision input
4
4
  * (image attachments) on the Chat Completions API.
@@ -1 +1 @@
1
- {"version":3,"file":"known-vision-models.mjs","names":[],"sources":["../../../../../../ai-openai/src/known-vision-models.ts"],"sourcesContent":["/**\n * Model-name prefixes for OpenAI families that support vision input\n * (image attachments) on the Chat Completions API.\n *\n * Matched as a prefix so dated variants (`gpt-4o-2024-08-06`) and\n * `-mini` / `-preview` suffixes (`gpt-4o-mini`, `gpt-4-turbo-preview`)\n * are covered without listing every release tag explicitly.\n *\n * Maintenance: append a new prefix when OpenAI ships a vision-capable\n * model family that doesn't already match. Devs can always override\n * per-model via `openai.model({ name, vision: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst VISION_CAPABLE_PREFIXES = [\n \"gpt-4o\",\n \"gpt-4-turbo\",\n \"gpt-4.1\",\n \"gpt-5\",\n \"gpt-6\",\n \"o1\",\n \"o3\",\n \"o4\",\n \"chatgpt-4o\",\n];\n\n/**\n * Infer whether a given OpenAI model name supports vision based on the\n * known-prefix list. Unknown models default to `false` so that passing\n * an image attachment to an unsupported model surfaces a clear,\n * agent-side capability error instead of an opaque OpenAI 400.\n *\n * @example\n * inferVisionCapability(\"gpt-4o-mini\"); // → true\n * inferVisionCapability(\"gpt-4o-2024-08-06\"); // → true\n * inferVisionCapability(\"gpt-3.5-turbo\"); // → false\n * inferVisionCapability(\"custom-llm\"); // → false\n */\nexport function inferVisionCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,WAA4B;CAChE,MAAM,aAAa,UAAU,YAAY;CAEzC,OAAO,wBAAwB,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC;AAC/E"}
1
+ {"version":3,"file":"known-vision-models.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/known-vision-models.ts"],"sourcesContent":["/**\n * Model-name prefixes for OpenAI families that support vision input\n * (image attachments) on the Chat Completions API.\n *\n * Matched as a prefix so dated variants (`gpt-4o-2024-08-06`) and\n * `-mini` / `-preview` suffixes (`gpt-4o-mini`, `gpt-4-turbo-preview`)\n * are covered without listing every release tag explicitly.\n *\n * Maintenance: append a new prefix when OpenAI ships a vision-capable\n * model family that doesn't already match. Devs can always override\n * per-model via `openai.model({ name, vision: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst VISION_CAPABLE_PREFIXES = [\n \"gpt-4o\",\n \"gpt-4-turbo\",\n \"gpt-4.1\",\n \"gpt-5\",\n \"gpt-6\",\n \"o1\",\n \"o3\",\n \"o4\",\n \"chatgpt-4o\",\n];\n\n/**\n * Infer whether a given OpenAI model name supports vision based on the\n * known-prefix list. Unknown models default to `false` so that passing\n * an image attachment to an unsupported model surfaces a clear,\n * agent-side capability error instead of an opaque OpenAI 400.\n *\n * @example\n * inferVisionCapability(\"gpt-4o-mini\"); // → true\n * inferVisionCapability(\"gpt-4o-2024-08-06\"); // → true\n * inferVisionCapability(\"gpt-3.5-turbo\"); // → false\n * inferVisionCapability(\"custom-llm\"); // → false\n */\nexport function inferVisionCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;AAcA,SAAgB,sBAAsB,WAA4B;CAChE,MAAM,aAAa,UAAU,YAAY;CAEzC,OAAO,wBAAwB,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC;AAC/E"}
package/esm/model.mjs CHANGED
@@ -7,12 +7,13 @@ import { inferStructuredOutput, planStructuredOutput } from "./utils/structured-
7
7
  import { toOpenAIMessages } from "./utils/to-openai-messages.mjs";
8
8
  import { toOpenAITools } from "./utils/to-openai-tools.mjs";
9
9
  import "./utils/index.mjs";
10
+ import { mergeProviderBody } from "./provider-options.mjs";
10
11
  import { inferReasoningCapability } from "./known-reasoning-models.mjs";
11
12
  import { inferVisionCapability } from "./known-vision-models.mjs";
12
- import { InvalidRequestError, ProviderError, safeJsonParse } from "@warlock.js/ai";
13
+ import { InvalidRequestError, ProviderError, resolveStreamChunks, safeJsonParse, selectProviderTools } from "@warlock.js/ai";
13
14
  import { log } from "@warlock.js/logger";
14
15
 
15
- //#region ../ai-openai/src/model.ts
16
+ //#region ../../packages/ai-openai/src/model.ts
16
17
  const LOG_MODULE = "ai.openai";
17
18
  /**
18
19
  * OpenAI-backed implementation of `ModelContract`.
@@ -88,15 +89,15 @@ var OpenAIModel = class {
88
89
  });
89
90
  let response;
90
91
  try {
91
- response = await this.client.chat.completions.create({
92
+ response = await this.client.chat.completions.create(mergeProviderBody({
92
93
  model: this.name,
93
94
  messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
94
95
  ...this.buildSamplingParams(options),
95
96
  ...buildPromptCacheParams(this.provider, this.config),
96
- tools: toOpenAITools(options?.tools),
97
+ ...this.buildTools(options),
97
98
  ...this.buildResponseFormat(options?.responseSchema),
98
99
  ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
99
- }, options?.signal ? { signal: options.signal } : void 0);
100
+ }, options), options?.signal ? { signal: options.signal } : void 0);
100
101
  } catch (thrown) {
101
102
  const wrapped = wrapOpenAIError(thrown);
102
103
  this.logger.error(LOG_MODULE, "error", wrapped.message, {
@@ -129,6 +130,11 @@ var OpenAIModel = class {
129
130
  * `delta` for text tokens, `tool-call` when the model requests a tool,
130
131
  * and a terminal `done` carrying the final finish reason + usage totals.
131
132
  * Callers consume it with `for await`.
133
+ *
134
+ * With `options.streamChunks` it also yields `tool-args` for each
135
+ * `tool_calls[].function.arguments` fragment and `reasoning` for a
136
+ * vendor's `reasoning_content` / `reasoning` delta field. Usage arrives
137
+ * only on the final chunk here, so no `usage` chunk is emitted.
132
138
  */
133
139
  async *stream(messages, options) {
134
140
  this.logger.debug(LOG_MODULE, "request", "Starting streaming call to chat.completions", {
@@ -139,17 +145,17 @@ var OpenAIModel = class {
139
145
  });
140
146
  let stream;
141
147
  try {
142
- stream = await this.client.chat.completions.create({
148
+ stream = await this.client.chat.completions.create(mergeProviderBody({
143
149
  model: this.name,
144
150
  messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
145
151
  ...this.buildSamplingParams(options),
146
152
  ...buildPromptCacheParams(this.provider, this.config),
147
- tools: toOpenAITools(options?.tools),
153
+ ...this.buildTools(options),
148
154
  stream: true,
149
155
  stream_options: { include_usage: true },
150
156
  ...this.buildResponseFormat(options?.responseSchema),
151
157
  ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
152
- }, options?.signal ? { signal: options.signal } : void 0);
158
+ }, options), options?.signal ? { signal: options.signal } : void 0);
153
159
  } catch (thrown) {
154
160
  const wrapped = wrapOpenAIError(thrown);
155
161
  this.logger.error(LOG_MODULE, "error", wrapped.message, {
@@ -159,16 +165,24 @@ var OpenAIModel = class {
159
165
  throw wrapped;
160
166
  }
161
167
  let rawFinishReason = "stop";
162
- const usage = {
168
+ let usage = {
163
169
  input: 0,
164
170
  output: 0,
165
171
  total: 0
166
172
  };
173
+ const want = resolveStreamChunks(options);
167
174
  const toolCallAccum = /* @__PURE__ */ new Map();
168
175
  try {
169
176
  for await (const chunk of stream) {
170
177
  const delta = chunk.choices[0]?.delta;
171
178
  const finish = chunk.choices[0]?.finish_reason;
179
+ if (want.reasoning && delta) {
180
+ const reasoning = readReasoningDelta(delta);
181
+ if (reasoning) yield {
182
+ type: "reasoning",
183
+ delta: reasoning
184
+ };
185
+ }
172
186
  if (delta?.content) yield {
173
187
  type: "delta",
174
188
  content: delta.content
@@ -178,23 +192,25 @@ var OpenAIModel = class {
178
192
  if (!toolCallAccum.has(idx)) toolCallAccum.set(idx, {
179
193
  id: "",
180
194
  name: "",
181
- arguments: ""
195
+ arguments: "",
196
+ streamed: 0
182
197
  });
183
198
  const acc = toolCallAccum.get(idx);
184
199
  if (toolCall.id) acc.id = toolCall.id;
185
200
  if (toolCall.function?.name) acc.name = toolCall.function.name;
186
201
  if (toolCall.function?.arguments) acc.arguments += toolCall.function.arguments;
202
+ if (want.toolArgs && acc.name && acc.arguments.length > acc.streamed) {
203
+ yield {
204
+ type: "tool-args",
205
+ callId: acc.id,
206
+ name: acc.name,
207
+ delta: acc.arguments.slice(acc.streamed)
208
+ };
209
+ acc.streamed = acc.arguments.length;
210
+ }
187
211
  }
188
212
  if (finish) rawFinishReason = finish;
189
- if (chunk.usage) {
190
- usage.input = chunk.usage.prompt_tokens ?? 0;
191
- usage.output = chunk.usage.completion_tokens ?? 0;
192
- usage.total = chunk.usage.total_tokens ?? 0;
193
- const cached = chunk.usage.prompt_tokens_details?.cached_tokens;
194
- if (cached !== void 0 && cached > 0) usage.cachedTokens = cached;
195
- const reasoning = chunk.usage.completion_tokens_details?.reasoning_tokens;
196
- if (reasoning !== void 0 && reasoning > 0) usage.reasoningTokens = reasoning;
197
- }
213
+ if (chunk.usage) usage = this.extractUsage(chunk.usage);
198
214
  }
199
215
  for (const acc of toolCallAccum.values()) {
200
216
  if (!acc.name) continue;
@@ -224,6 +240,25 @@ var OpenAIModel = class {
224
240
  usage
225
241
  };
226
242
  }
243
+ /**
244
+ * Spread-friendly `tools` fragment: the function tools, then the
245
+ * provider-defined entries whose `provider` equals this model's
246
+ * provider label (`"openai"`, or the vendor label an OpenAI-compatible
247
+ * wrapper sets, e.g. `"groq"`), sent verbatim. Chat Completions only
248
+ * accepts `function` and `custom` tool types from OpenAI itself; its
249
+ * hosted tools (`web_search`, `file_search`, …) need `api: "responses"`
250
+ * and OpenAI rejects them here with a 400.
251
+ */
252
+ buildTools(options) {
253
+ const { tools, definitions } = selectProviderTools(options, {
254
+ accepts: [this.provider],
255
+ module: LOG_MODULE,
256
+ model: this.name,
257
+ logger: this.logger
258
+ });
259
+ const mapped = [...toOpenAITools(tools) ?? [], ...definitions];
260
+ return mapped.length > 0 ? { tools: mapped } : {};
261
+ }
227
262
  /** Map the neutral cache hint only for the direct OpenAI provider. */
228
263
  promptCacheBreakpoints(options) {
229
264
  return this.provider === "openai" ? options?.cacheControl?.breakpoints ?? 0 : 0;
@@ -262,7 +297,9 @@ var OpenAIModel = class {
262
297
  * (the hidden reasoning channel on o-series / gpt-5 models, already
263
298
  * counted within `output`). Both are emitted only when the provider
264
299
  * reports a positive value, so non-reasoning / uncached calls keep the
265
- * lean `{ input, output, total }` shape.
300
+ * lean `{ input, output, total }` shape. `cacheWriteTokens` mirrors
301
+ * `prompt_tokens_details.cache_write_tokens` (billed writes on the
302
+ * models that charge for them). `cost` is attached when priced.
266
303
  */
267
304
  extractUsage(raw) {
268
305
  if (!raw) return {
@@ -271,12 +308,13 @@ var OpenAIModel = class {
271
308
  total: 0
272
309
  };
273
310
  return buildUsage({
274
- input: raw.prompt_tokens,
275
- output: raw.completion_tokens,
276
- total: raw.total_tokens,
311
+ input: raw.prompt_tokens ?? 0,
312
+ output: raw.completion_tokens ?? 0,
313
+ total: raw.total_tokens ?? 0,
277
314
  cachedTokens: raw.prompt_tokens_details?.cached_tokens,
315
+ cacheWriteTokens: raw.prompt_tokens_details?.cache_write_tokens,
278
316
  reasoningTokens: raw.completion_tokens_details?.reasoning_tokens
279
- });
317
+ }, this.pricing);
280
318
  }
281
319
  /**
282
320
  * Translate the neutral `ModelCallOptions.reasoning` hint into OpenAI's
@@ -352,6 +390,20 @@ var OpenAIModel = class {
352
390
  }));
353
391
  }
354
392
  };
393
+ /**
394
+ * Reasoning text an OpenAI-compatible vendor puts on a Chat Completions
395
+ * stream delta. OpenAI itself streams none on this API. DeepSeek documents
396
+ * `delta.reasoning_content` (API reference, chat completion chunk); Groq
397
+ * documents the parsed reasoning under a `reasoning` field. Neither is in
398
+ * the OpenAI SDK's delta type, so the delta is read as a plain record.
399
+ */
400
+ function readReasoningDelta(delta) {
401
+ const record = delta;
402
+ for (const key of ["reasoning_content", "reasoning"]) {
403
+ const value = record[key];
404
+ if (typeof value === "string" && value !== "") return value;
405
+ }
406
+ }
355
407
 
356
408
  //#endregion
357
409
  export { OpenAIModel };