@warlock.js/ai-openai 4.8.2 → 4.9.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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/cjs/index.cjs +31 -20
  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-image-models.d.mts +1 -1
  15. package/esm/known-image-models.d.mts.map +1 -1
  16. package/esm/known-image-models.mjs +1 -1
  17. package/esm/known-image-models.mjs.map +1 -1
  18. package/esm/known-reasoning-models.mjs +1 -1
  19. package/esm/known-reasoning-models.mjs.map +1 -1
  20. package/esm/known-vision-models.mjs +1 -1
  21. package/esm/known-vision-models.mjs.map +1 -1
  22. package/esm/model.mjs +19 -8
  23. package/esm/model.mjs.map +1 -1
  24. package/esm/sdk.d.mts +1 -1
  25. package/esm/sdk.d.mts.map +1 -1
  26. package/esm/sdk.mjs +1 -1
  27. package/esm/sdk.mjs.map +1 -1
  28. package/esm/speech.d.mts +1 -1
  29. package/esm/speech.d.mts.map +1 -1
  30. package/esm/speech.mjs +1 -1
  31. package/esm/speech.mjs.map +1 -1
  32. package/esm/transcription.d.mts +1 -1
  33. package/esm/transcription.d.mts.map +1 -1
  34. package/esm/transcription.mjs +1 -1
  35. package/esm/transcription.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/to-openai-messages.mjs +1 -1
  39. package/esm/utils/to-openai-messages.mjs.map +1 -1
  40. package/esm/utils/to-openai-tools.mjs +1 -1
  41. package/esm/utils/to-openai-tools.mjs.map +1 -1
  42. package/esm/utils/wrap-openai-error.mjs +1 -1
  43. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  44. package/llms-full.txt +13 -6
  45. package/package.json +3 -3
  46. package/skills/setup-openai/SKILL.md +13 -6
@@ -1 +1 @@
1
- {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/config.type.ts"],"mappings":";;;;;;AAwCA;;;;;;;;;;;;;;AAOuC;AAiBvC;;;;AAAgC;AAUhC;;;;;;;;KAlCY,eAAA,GAAkB,aAAA;EAC5B,QAAA;EAoFA;;;AAQK;AAaP;EAnGE,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;AAmGsB;AAYjD;;;;AAAgD;AAWhD;;;;AAAkD;AAWlD;;KApHY,oBAAA;;AAoHoD;;;;;;;KA1GpD,iBAAA,GAAoB,WAAA;;;;;;;;EAQ9B,MAAA;;;;;;;;;;;;;;EAcA,cAAA,GAAiB,oBAAoB;;;;;;;;;EASrC,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;;;;EASA,GAAA;;;;;;;;EAQA,KAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;;KAYrC,iBAAA,GAAoB,gBAAgB;;;;;;;;;;KAWpC,kBAAA,GAAqB,iBAAiB;;;;;;;;;;KAWtC,yBAAA,GAA4B,wBAAwB"}
1
+ {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../ai-openai/src/config.type.ts"],"mappings":";;;;;;AAwCA;;;;;;;;;;;;;;AAOuC;AAiBvC;;;;AAAgC;AAUhC;;;;;;;;KAlCY,eAAA,GAAkB,aAAA;EAC5B,QAAA;EAoFA;;;AAQK;AAaP;EAnGE,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;AAmGsB;AAYjD;;;;AAAgD;AAWhD;;;;AAAkD;AAWlD;;KApHY,oBAAA;;AAoHoD;;;;;;;KA1GpD,iBAAA,GAAoB,WAAA;;;;;;;;EAQ9B,MAAA;;;;;;;;;;;;;;EAcA,cAAA,GAAiB,oBAAoB;;;;;;;;;EASrC,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;;;;EASA,GAAA;;;;;;;;EAQA,KAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;;KAYrC,iBAAA,GAAoB,gBAAgB;;;;;;;;;;KAWpC,kBAAA,GAAqB,iBAAiB;;;;;;;;;;KAWtC,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 ../@warlock.js/ai-openai/src/embedder.d.ts
5
+ //#region ../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":["../../../../../../@warlock.js/ai-openai/src/embedder.ts"],"mappings":";;;;;;;AA0CA;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,gBAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EACT,UAAA;EAAA,iBAEU,MAAA;EA2BJ;;;;;EAAA,iBApBI,oBAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,oBAAA;EAO9B,KAAA,CAAM,KAAA,WAAgB,OAAA,CAAQ,eAAA;EAU9B,SAAA,CAAU,MAAA,aAAmB,OAAA,CAAQ,oBAAA;;;;;;;UAgBpC,OAAA;AAAA"}
1
+ {"version":3,"file":"embedder.d.mts","names":[],"sources":["../../../../../../ai-openai/src/embedder.ts"],"mappings":";;;;;;;AA0CA;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,gBAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EACT,UAAA;EAAA,iBAEU,MAAA;EA2BJ;;;;;EAAA,iBApBI,oBAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,oBAAA;EAO9B,KAAA,CAAM,KAAA,WAAgB,OAAA,CAAQ,eAAA;EAU9B,SAAA,CAAU,MAAA,aAAmB,OAAA,CAAQ,oBAAA;;;;;;;UAgBpC,OAAA;AAAA"}
package/esm/embedder.mjs CHANGED
@@ -2,7 +2,7 @@ import { wrapOpenAIError } from "./utils/wrap-openai-error.mjs";
2
2
  import "./utils/index.mjs";
3
3
  import { log } from "@warlock.js/logger";
4
4
 
5
- //#region ../@warlock.js/ai-openai/src/embedder.ts
5
+ //#region ../ai-openai/src/embedder.ts
6
6
  const LOG_MODULE = "ai.openai";
7
7
  /**
8
8
  * OpenAI-backed implementation of `EmbedderContract`.
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/embedder.ts"],"sourcesContent":["import {\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 return {\n vector: response.data[0].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 if (this.dimensions === 0) {\n this.dimensions = response.data[0].embedding.length;\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":";;;;;AAWA,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,OAAO;GACL,QAAQ,SAAS,KAAK,EAAE,CAAC;GACzB,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;EAID,IAAI,KAAK,eAAe,GACtB,KAAK,aAAa,SAAS,KAAK,EAAE,CAAC,UAAU;EAG/C,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":["../../../../../../ai-openai/src/embedder.ts"],"sourcesContent":["import {\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 return {\n vector: response.data[0].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 if (this.dimensions === 0) {\n this.dimensions = response.data[0].embedding.length;\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":";;;;;AAWA,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,OAAO;GACL,QAAQ,SAAS,KAAK,EAAE,CAAC;GACzB,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;EAID,IAAI,KAAK,eAAe,GACtB,KAAK,aAAa,SAAS,KAAK,EAAE,CAAC,UAAU;EAG/C,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 ../@warlock.js/ai-openai/src/image.d.ts
5
+ //#region ../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":["../../../../../../@warlock.js/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;EAcjD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;EA6Ea;AAAA;;;;;;EAAA,QAAhB,gBAAA;AAAA"}
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;EAcjD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;EA6Ea;AAAA;;;;;;EAAA,QAAhB,gBAAA;AAAA"}
package/esm/image.mjs CHANGED
@@ -4,7 +4,7 @@ import { isOpenAIImageModel } from "./known-image-models.mjs";
4
4
  import { InvalidRequestError, ProviderError } from "@warlock.js/ai";
5
5
  import { log } from "@warlock.js/logger";
6
6
 
7
- //#region ../@warlock.js/ai-openai/src/image.ts
7
+ //#region ../ai-openai/src/image.ts
8
8
  const LOG_MODULE = "ai.openai";
9
9
  /** Map a neutral output container to its IANA media type. */
10
10
  function mediaTypeFor(format) {
package/esm/image.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"image.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/image.ts"],"sourcesContent":["import {\n InvalidRequestError,\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 { isOpenAIImageModel } from \"./known-image-models\";\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 for the two image families OpenAI ships: 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 * **Capability guard.** The constructor rejects a non-image model id\n * up front (`gpt-4o` → typed `InvalidRequestError`) so the mistake\n * surfaces at wiring time, not as a downstream provider 400 — the\n * \"fail fast at construction\" rule shared with the embedder/vision\n * guards.\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 if (!isOpenAIImageModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known OpenAI image-generation model. ` +\n \"Use a `gpt-image-*` or `dall-e-*` model with openai.image({ name }).\",\n );\n }\n\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.ImageGenerateParamsBase[\"quality\"];\n }\n if (!isGptImage && responseFormat) body.response_format = responseFormat;\n if (isGptImage && options?.format !== undefined) {\n body.output_format = options.format as OpenAI.Images.ImageGenerateParamsBase[\"output_format\"];\n }\n if (options?.background !== undefined) {\n body.background = options.background as OpenAI.Images.ImageGenerateParamsBase[\"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":";;;;;;;AAeA,MAAM,aAAa;;AAGnB,SAAS,aAAa,QAAoC;CACxD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,mBAAb,MAA4D;CAQ1D,AAAO,YAAY,QAAgB,QAA2B,WAAmB,UAAU;gBAFzD;EAGhC,IAAI,CAAC,mBAAmB,OAAO,IAAI,GACjC,MAAM,IAAI,oBACR,IAAI,OAAO,KAAK,yHAElB;EAGF,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,gBAAgB,QAAQ;EAE/B,IAAI,SAAS,eAAe,QAC1B,KAAK,aAAa,QAAQ;EAG5B,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":["../../../../../../ai-openai/src/image.ts"],"sourcesContent":["import {\n InvalidRequestError,\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 { isOpenAIImageModel } from \"./known-image-models\";\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 for the two image families OpenAI ships: 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 * **Capability guard.** The constructor rejects a non-image model id\n * up front (`gpt-4o` → typed `InvalidRequestError`) so the mistake\n * surfaces at wiring time, not as a downstream provider 400 — the\n * \"fail fast at construction\" rule shared with the embedder/vision\n * guards.\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 if (!isOpenAIImageModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known OpenAI image-generation model. ` +\n \"Use a `gpt-image-*` or `dall-e-*` model with openai.image({ name }).\",\n );\n }\n\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.ImageGenerateParamsBase[\"quality\"];\n }\n if (!isGptImage && responseFormat) body.response_format = responseFormat;\n if (isGptImage && options?.format !== undefined) {\n body.output_format = options.format as OpenAI.Images.ImageGenerateParamsBase[\"output_format\"];\n }\n if (options?.background !== undefined) {\n body.background = options.background as OpenAI.Images.ImageGenerateParamsBase[\"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":";;;;;;;AAeA,MAAM,aAAa;;AAGnB,SAAS,aAAa,QAAoC;CACxD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,mBAAb,MAA4D;CAQ1D,AAAO,YAAY,QAAgB,QAA2B,WAAmB,UAAU;gBAFzD;EAGhC,IAAI,CAAC,mBAAmB,OAAO,IAAI,GACjC,MAAM,IAAI,oBACR,IAAI,OAAO,KAAK,yHAElB;EAGF,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,gBAAgB,QAAQ;EAE/B,IAAI,SAAS,eAAe,QAC1B,KAAK,aAAa,QAAQ;EAG5B,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,4 +1,4 @@
1
- //#region ../@warlock.js/ai-openai/src/known-image-models.d.ts
1
+ //#region ../ai-openai/src/known-image-models.d.ts
2
2
  /**
3
3
  * Model-id prefixes OpenAI exposes through the **Images** API
4
4
  * (`client.images.generate`). The two live families:
@@ -1 +1 @@
1
- {"version":3,"file":"known-image-models.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/known-image-models.ts"],"mappings":";;AAaA;;;;AAA2E;AAa3E;;;;AAA+C;;;cAblC,2BAAA;;;;;;;;;;;;iBAaG,kBAAA,CAAmB,IAAY"}
1
+ {"version":3,"file":"known-image-models.d.mts","names":[],"sources":["../../../../../../ai-openai/src/known-image-models.ts"],"mappings":";;AAaA;;;;AAA2E;AAa3E;;;;AAA+C;;;cAblC,2BAAA;;;;;;;;;;;;iBAaG,kBAAA,CAAmB,IAAY"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-openai/src/known-image-models.ts
1
+ //#region ../ai-openai/src/known-image-models.ts
2
2
  /**
3
3
  * Model-id prefixes OpenAI exposes through the **Images** API
4
4
  * (`client.images.generate`). The two live families:
@@ -1 +1 @@
1
- {"version":3,"file":"known-image-models.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/known-image-models.ts"],"sourcesContent":["/**\n * Model-id prefixes OpenAI exposes through the **Images** API\n * (`client.images.generate`). The two live families:\n *\n * - `gpt-image-*` — token-metered, always returns base64 bytes (no\n * `response_format` knob), supports `output_format` + `background`.\n * - `dall-e-*` — per-image-metered, returns a URL or base64 via\n * `response_format`.\n *\n * Used by {@link isOpenAIImageModel} for the construction-time guard so\n * `openai.image({ name: \"gpt-4o\" })` fails fast with a curated error\n * instead of a downstream 400 — mirroring the embedder/vision guards.\n */\nexport const OPENAI_IMAGE_MODEL_PREFIXES = [\"gpt-image\", \"dall-e\"] as const;\n\n/**\n * True when `name` is a recognized OpenAI image-generation model. A\n * prefix match (not an exact list) so dated snapshots\n * (`gpt-image-1-mini`, `dall-e-3`) are covered without a maintenance\n * burden every time OpenAI ships a point release.\n *\n * @example\n * isOpenAIImageModel(\"gpt-image-1\"); // true\n * isOpenAIImageModel(\"dall-e-3\"); // true\n * isOpenAIImageModel(\"gpt-4o\"); // false\n */\nexport function isOpenAIImageModel(name: string): boolean {\n return OPENAI_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,8BAA8B,CAAC,aAAa,QAAQ;;;;;;;;;;;;AAajE,SAAgB,mBAAmB,MAAuB;CACxD,OAAO,4BAA4B,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC;AAC7E"}
1
+ {"version":3,"file":"known-image-models.mjs","names":[],"sources":["../../../../../../ai-openai/src/known-image-models.ts"],"sourcesContent":["/**\n * Model-id prefixes OpenAI exposes through the **Images** API\n * (`client.images.generate`). The two live families:\n *\n * - `gpt-image-*` — token-metered, always returns base64 bytes (no\n * `response_format` knob), supports `output_format` + `background`.\n * - `dall-e-*` — per-image-metered, returns a URL or base64 via\n * `response_format`.\n *\n * Used by {@link isOpenAIImageModel} for the construction-time guard so\n * `openai.image({ name: \"gpt-4o\" })` fails fast with a curated error\n * instead of a downstream 400 — mirroring the embedder/vision guards.\n */\nexport const OPENAI_IMAGE_MODEL_PREFIXES = [\"gpt-image\", \"dall-e\"] as const;\n\n/**\n * True when `name` is a recognized OpenAI image-generation model. A\n * prefix match (not an exact list) so dated snapshots\n * (`gpt-image-1-mini`, `dall-e-3`) are covered without a maintenance\n * burden every time OpenAI ships a point release.\n *\n * @example\n * isOpenAIImageModel(\"gpt-image-1\"); // true\n * isOpenAIImageModel(\"dall-e-3\"); // true\n * isOpenAIImageModel(\"gpt-4o\"); // false\n */\nexport function isOpenAIImageModel(name: string): boolean {\n return OPENAI_IMAGE_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,8BAA8B,CAAC,aAAa,QAAQ;;;;;;;;;;;;AAajE,SAAgB,mBAAmB,MAAuB;CACxD,OAAO,4BAA4B,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC;AAC7E"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-openai/src/known-reasoning-models.ts
1
+ //#region ../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":["../../../../../../@warlock.js/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\"];\n\n/**\n * Infer whether a given OpenAI model name is a reasoning model (o-series\n * and the gpt-5 family) 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-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;AAAO;;;;;;;;;;;;;;AAe7D,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":["../../../../../../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\"];\n\n/**\n * Infer whether a given OpenAI model name is a reasoning model (o-series\n * and the gpt-5 family) 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-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;AAAO;;;;;;;;;;;;;;AAe7D,SAAgB,yBAAyB,WAA4B;CACnE,MAAM,aAAa,UAAU,YAAY;CAEzC,OAAO,2BAA2B,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC;AAClF"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-openai/src/known-vision-models.ts
1
+ //#region ../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":["../../../../../../@warlock.js/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 \"o1\",\n \"o3\",\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;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":["../../../../../../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 \"o1\",\n \"o3\",\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;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
@@ -8,7 +8,7 @@ import { inferVisionCapability } from "./known-vision-models.mjs";
8
8
  import { safeJsonParse } from "@warlock.js/ai";
9
9
  import { log } from "@warlock.js/logger";
10
10
 
11
- //#region ../@warlock.js/ai-openai/src/model.ts
11
+ //#region ../ai-openai/src/model.ts
12
12
  const LOG_MODULE = "ai.openai";
13
13
  /**
14
14
  * Map an explicit `responseFormat` override to the default
@@ -99,7 +99,7 @@ var OpenAIModel = class {
99
99
  max_tokens: options?.maxTokens ?? this.config.maxTokens,
100
100
  tools: toOpenAITools(options?.tools),
101
101
  ...this.buildResponseFormat(options?.responseSchema),
102
- ...this.buildReasoningParams(options?.reasoning)
102
+ ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
103
103
  }, options?.signal ? { signal: options.signal } : void 0);
104
104
  } catch (thrown) {
105
105
  const wrapped = wrapOpenAIError(thrown);
@@ -147,7 +147,7 @@ var OpenAIModel = class {
147
147
  stream: true,
148
148
  stream_options: { include_usage: true },
149
149
  ...this.buildResponseFormat(options?.responseSchema),
150
- ...this.buildReasoningParams(options?.reasoning)
150
+ ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
151
151
  }, options?.signal ? { signal: options.signal } : void 0);
152
152
  } catch (thrown) {
153
153
  const wrapped = wrapOpenAIError(thrown);
@@ -354,16 +354,27 @@ var OpenAIModel = class {
354
354
  * leaves the model reasoning server-side by default, so tools would
355
355
  * still be rejected.
356
356
  *
357
- * No-ops in two cases so the adapter never forwards an unsupported
358
- * param: (1) the model is not reasoning-capable
357
+ * No-ops when the model is not reasoning-capable
359
358
  * (`capabilities.reasoning` is false — e.g. `gpt-4o`, which 400s on any
360
- * `reasoning_effort`), or (2) the caller supplied no `effort`.
359
+ * `reasoning_effort`).
360
+ *
361
+ * When the caller gave no explicit `effort`: defaults to `"none"` IF
362
+ * `hasTools` is true, else omits the param (provider default — the
363
+ * pre-existing behavior for a tool-less call). The default exists
364
+ * because, on a reasoning-capable model, there is no working alternative
365
+ * to `"none"` when tools are attached — omitting `reasoning_effort`
366
+ * leaves reasoning on server-side, which Chat Completions rejects tools
367
+ * for (empty replies on some model generations, a hard 400 — "Function
368
+ * tools with reasoning_effort are not supported ... in
369
+ * /v1/chat/completions" — on newer ones). An explicit `effort` from the
370
+ * caller always wins over this default, in either direction.
361
371
  *
362
372
  * Returns an empty spread when nothing applies, so the caller can
363
373
  * unconditionally `...buildReasoningParams(...)` into the request.
364
374
  */
365
- buildReasoningParams(reasoning) {
366
- if (!this.capabilities.reasoning || !reasoning?.effort) return {};
375
+ buildReasoningParams(reasoning, hasTools) {
376
+ if (!this.capabilities.reasoning) return {};
377
+ if (!reasoning?.effort) return hasTools ? { reasoning_effort: "none" } : {};
367
378
  return { reasoning_effort: reasoning.effort };
368
379
  }
369
380
  /**
package/esm/model.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"model.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/model.ts"],"sourcesContent":["import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIModelConfig, OpenAIResponseFormat } from \"./config.type\";\nimport { inferReasoningCapability } from \"./known-reasoning-models\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapFinishReason, toOpenAIMessages, toOpenAITools, wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\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 */\nfunction inferStructuredOutput(responseFormat: OpenAIResponseFormat | undefined): boolean {\n if (responseFormat === \"json_object\" || responseFormat === \"text\") {\n return false;\n }\n\n return true;\n}\n\n/**\n * OpenAI-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `openai` SDK. Agents,\n * workflows, and supervisors never talk to OpenAI directly — they hold a\n * `ModelContract`, and this class is what makes that contract concrete for\n * any OpenAI-compatible endpoint (OpenAI, Azure OpenAI, OpenRouter, local\n * gateways that speak the Chat Completions protocol).\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client + frozen `ModelConfig` (name,\n * temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` and\n * `ToolContract[]` into OpenAI wire shapes on the way out, and\n * translating OpenAI's response (content, finish reason, tool calls,\n * usage) back into the neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as a\n * class (see §4.2 of code-style.md — \"long-lived state across calls\").\n *\n * @example\n * import OpenAI from \"openai\";\n * const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n * const model = new OpenAIModel(client, { name: \"gpt-4o\", temperature: 0.3 });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class OpenAIModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: OpenAI;\n private readonly config: OpenAIModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIModelConfig, provider: string = \"openai\") {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? inferStructuredOutput(config.responseFormat),\n vision: config.vision ?? inferVisionCapability(config.name),\n // o-series + gpt-5 models surface a reasoning channel and accept\n // the `reasoning_effort` param. Explicit config wins over the\n // name-prefix inference.\n reasoning: config.reasoning ?? inferReasoningCapability(config.name),\n // OpenAI prompt caching is automatic on the Chat Completions API\n // (no caller-supplied breakpoints — the platform caches long\n // prompt prefixes server-side and reports the hit count via\n // `prompt_tokens_details.cached_tokens`). We therefore advertise\n // the read-side accounting capability as always available while\n // treating `ModelCallOptions.cacheControl` write breakpoints as a\n // no-op (see `buildReasoningParams` siblings — there is no cache\n // param to emit).\n promptCaching: true,\n // PDF + audio INPUT are off by default — OpenAI accepts `file`\n // (PDF) and `input_audio` parts only on specific models, so the\n // flags are conservative/honest and opt-in via config rather than\n // name-inferred. When set, the agent admits the attachments and\n // `toOpenAIMessages` maps them to the real wire parts.\n pdf: config.pdf ?? false,\n audio: config.audio ?? false,\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Chat\n * Completions endpoint, waits for the terminal response, and reshapes it\n * into a vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n // Per-call request/response logs are hot-path in production agents\n // — keep them at `debug` so `info` stays reserved for lifecycle\n // events (agent starting/completed, etc.). Operators who need to\n // audit every LLM call can raise log-level at runtime.\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to chat.completions\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: OpenAI.Chat.Completions.ChatCompletion;\n\n try {\n response = await this.client.chat.completions.create(\n {\n model: this.name,\n messages: toOpenAIMessages(messages),\n temperature: options?.temperature ?? this.config.temperature,\n max_tokens: options?.maxTokens ?? this.config.maxTokens,\n tools: toOpenAITools(options?.tools),\n ...this.buildResponseFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const choice = response.choices[0];\n const finishReason = mapFinishReason(choice.finish_reason);\n const usage = this.extractUsage(response.usage);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to chat.completions succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: choice.message.content ?? \"\",\n finishReason,\n usage,\n toolCalls: this.extractToolCalls(choice.message.tool_calls),\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s —\n * `delta` for text tokens, `tool-call` when the model requests a tool,\n * and a terminal `done` carrying the final finish reason + usage totals.\n * Callers consume it with `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to chat.completions\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<typeof this.client.chat.completions.create>>;\n\n try {\n stream = await this.client.chat.completions.create(\n {\n model: this.name,\n messages: toOpenAIMessages(messages),\n temperature: options?.temperature ?? this.config.temperature,\n max_tokens: options?.maxTokens ?? this.config.maxTokens,\n tools: toOpenAITools(options?.tools),\n stream: true,\n stream_options: { include_usage: true },\n ...this.buildResponseFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n let rawFinishReason: string = \"stop\";\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolCallAccum = new Map<number, { id: string; name: string; arguments: string }>();\n\n try {\n for await (const chunk of stream as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>) {\n const delta = chunk.choices[0]?.delta;\n const finish = chunk.choices[0]?.finish_reason;\n\n if (delta?.content) {\n yield { type: \"delta\", content: delta.content };\n }\n\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const idx = toolCall.index ?? 0;\n if (!toolCallAccum.has(idx)) {\n toolCallAccum.set(idx, { id: \"\", name: \"\", arguments: \"\" });\n }\n const acc = toolCallAccum.get(idx)!;\n if (toolCall.id) acc.id = toolCall.id;\n if (toolCall.function?.name) acc.name = toolCall.function.name;\n if (toolCall.function?.arguments) acc.arguments += toolCall.function.arguments;\n }\n }\n\n if (finish) {\n rawFinishReason = finish;\n }\n\n if (chunk.usage) {\n usage.input = chunk.usage.prompt_tokens ?? 0;\n usage.output = chunk.usage.completion_tokens ?? 0;\n usage.total = chunk.usage.total_tokens ?? 0;\n const cached = chunk.usage.prompt_tokens_details?.cached_tokens;\n if (cached !== undefined && cached > 0) {\n usage.cachedTokens = cached;\n }\n const reasoning = chunk.usage.completion_tokens_details?.reasoning_tokens;\n if (reasoning !== undefined && reasoning > 0) {\n usage.reasoningTokens = reasoning;\n }\n }\n }\n\n for (const acc of toolCallAccum.values()) {\n // Skip accumulators that never received a function name — those\n // are partial fragments the model started but never identified\n // (e.g. arguments-only deltas with no originating `id`/`name`).\n // Yielding them produces nameless tool-calls the agent runtime\n // can't dispatch and would mis-attribute as a registered tool.\n if (!acc.name) continue;\n\n yield {\n type: \"tool-call\",\n id: acc.id,\n name: acc.name,\n input: safeJsonParse<Record<string, unknown>>(acc.arguments, {}),\n };\n }\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const finishReason = mapFinishReason(rawFinishReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to chat.completions succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Translate the neutral `responseSchema` option into OpenAI's\n * `response_format` parameter.\n *\n * When `config.responseFormat` is set, it wins: `\"text\"` emits no\n * `response_format` at all, `\"json_object\"` always picks the loose\n * mode, and `\"json_schema\"` picks strict mode (with the same\n * `isStrictCompatible` safety check — a malformed schema still\n * degrades to `json_object` rather than 400). The override exists\n * because some targets (older OpenAI models, OpenRouter routes,\n * Ollama OpenAI-compat) reject strict `json_schema` outright.\n *\n * When the override is omitted, uses strict `json_schema` mode\n * (token-level enforcement) only when the schema is a proper\n * root-object JSON Schema (`{ type: \"object\", properties: ... }`).\n * For anything else — malformed extractor output, non-object\n * schemas, or future shapes we haven't tested — falls back to loose\n * `json_object` mode, which guarantees *some* valid JSON without\n * enforcing shape. The agent's soft instruction already embeds the\n * schema text in the system prompt when the model declares no\n * native structured-output capability, so shape validation still\n * runs client-side via the Standard Schema `validate()` call.\n *\n * Returns an empty spread when no schema was supplied, so the caller\n * can unconditionally `...buildResponseFormat(...)` into the request.\n */\n private buildResponseFormat(responseSchema: Record<string, unknown> | undefined): {\n response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams[\"response_format\"];\n } {\n if (!responseSchema) {\n return {};\n }\n\n const override = this.config.responseFormat;\n\n if (override === \"text\") {\n return {};\n }\n\n if (override === \"json_object\") {\n return { response_format: { type: \"json_object\" } };\n }\n\n // Either auto-select (no override) or explicit `\"json_schema\"`.\n // The strict-compat check still applies in the explicit case —\n // a malformed / non-object schema would 400 before sampling, so\n // we degrade to `json_object` rather than crash.\n if (this.isStrictCompatible(responseSchema)) {\n return {\n response_format: {\n type: \"json_schema\",\n json_schema: {\n name: \"response\",\n schema: responseSchema,\n strict: true,\n },\n },\n };\n }\n\n return { response_format: { type: \"json_object\" } };\n }\n\n /**\n * OpenAI strict `json_schema` mode requires the root to be a JSON\n * Schema object type (`{ type: \"object\", properties: ... }`). Anything\n * else (top-level arrays, primitives, unknown shapes) is rejected with\n * a 400 before a token is sampled. We check structurally here so the\n * first call doesn't crash on a malformed extraction — loose\n * `json_object` mode is a safe degradation.\n */\n private isStrictCompatible(schema: Record<string, unknown>): boolean {\n return (\n schema.type === \"object\" &&\n typeof schema.properties === \"object\" &&\n schema.properties !== null &&\n this.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\n * as nullable, e.g. `type: [\"string\", \"null\"]`, and still appear in\n * `required`). A schema that violates this anywhere in the tree is NOT\n * sent in strict `json_schema` mode — it degrades to loose\n * `json_object` so a hand-built or optional-bearing schema can't 400\n * the call (\"'required' ... must include every key in properties\").\n * Client-side `validate()` still enforces the full shape.\n */\n private 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 (!this.isStrictSafeNode(properties[key])) {\n return false;\n }\n }\n }\n\n if (record.items !== undefined && !this.isStrictSafeNode(record.items)) {\n return false;\n }\n\n for (const branch of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n const value = record[branch];\n if (Array.isArray(value) && value.some((sub) => !this.isStrictSafeNode(sub))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Normalize OpenAI's `usage` block (which may be absent on some responses\n * or partials) into the neutral `Usage` shape. Missing usage collapses to\n * zeros rather than propagating `undefined`, so downstream aggregation\n * math stays safe.\n *\n * `cachedTokens` mirrors `prompt_tokens_details.cached_tokens` (the\n * subset of the prompt served from OpenAI's automatic prompt cache);\n * `reasoningTokens` mirrors `completion_tokens_details.reasoning_tokens`\n * (the hidden reasoning channel on o-series / gpt-5 models, already\n * counted within `output`). Both are emitted only when the provider\n * reports a positive value, so non-reasoning / uncached calls keep the\n * lean `{ input, output, total }` shape.\n */\n private extractUsage(raw: OpenAI.Completions.CompletionUsage | undefined): Usage {\n if (!raw) {\n return { input: 0, output: 0, total: 0 };\n }\n\n const cachedTokens = raw.prompt_tokens_details?.cached_tokens;\n const reasoningTokens = raw.completion_tokens_details?.reasoning_tokens;\n\n return {\n input: raw.prompt_tokens,\n output: raw.completion_tokens,\n total: raw.total_tokens,\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n ...(reasoningTokens !== undefined && reasoningTokens > 0 ? { reasoningTokens } : {}),\n };\n }\n\n /**\n * Translate the neutral `ModelCallOptions.reasoning` hint into OpenAI's\n * `reasoning_effort` request param. Only `effort` maps — OpenAI's Chat\n * Completions API exposes a discrete effort knob, not a token budget,\n * so `reasoning.maxTokens` (the Anthropic extended-thinking cap) has no\n * wire equivalent here and is silently ignored.\n *\n * The neutral `ReasoningEffort` (`\"low\" | \"medium\" | \"high\" | \"none\"`)\n * is a subset of OpenAI's accepted values, so it forwards verbatim —\n * `\"none\"` included. `\"none\"` is load-bearing: gpt-5 / o-series models\n * **reject function tools** on Chat Completions while reasoning is\n * active, and the endpoint accepts tools only when `reasoning_effort`\n * is `\"none\"` (the alternative is the Responses API). This is why the\n * param is EMITTED for `\"none\"` rather than omitted — omitting it\n * leaves the model reasoning server-side by default, so tools would\n * still be rejected.\n *\n * No-ops in two cases so the adapter never forwards an unsupported\n * param: (1) the model is not reasoning-capable\n * (`capabilities.reasoning` is false — e.g. `gpt-4o`, which 400s on any\n * `reasoning_effort`), or (2) the caller supplied no `effort`.\n *\n * Returns an empty spread when nothing applies, so the caller can\n * unconditionally `...buildReasoningParams(...)` into the request.\n */\n private buildReasoningParams(reasoning: ModelCallOptions[\"reasoning\"]): {\n reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams[\"reasoning_effort\"];\n } {\n if (!this.capabilities.reasoning || !reasoning?.effort) {\n return {};\n }\n\n return { reasoning_effort: reasoning.effort };\n }\n\n /**\n * Reshape OpenAI's `tool_calls` array into the neutral\n * `ModelToolCallRequest[]`. The raw `arguments` field is a JSON string\n * per OpenAI's protocol — we parse it defensively via `safeJsonParse` so\n * malformed or empty arguments yield an empty object instead of crashing\n * the trip. Returns `undefined` when no tools were requested so callers\n * can branch on presence.\n */\n private extractToolCalls(\n rawToolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] | undefined,\n ): ModelToolCallRequest[] | undefined {\n if (!rawToolCalls || rawToolCalls.length === 0) {\n return undefined;\n }\n\n return rawToolCalls.map((toolCall) => ({\n id: toolCall.id,\n name: (toolCall as any).function.name,\n input: safeJsonParse<Record<string, unknown>>((toolCall as any).function.arguments, {}),\n }));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.mjs","names":[],"sources":["../../../../../../ai-openai/src/model.ts"],"sourcesContent":["import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIModelConfig, OpenAIResponseFormat } from \"./config.type\";\nimport { inferReasoningCapability } from \"./known-reasoning-models\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapFinishReason, toOpenAIMessages, toOpenAITools, wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\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 */\nfunction inferStructuredOutput(responseFormat: OpenAIResponseFormat | undefined): boolean {\n if (responseFormat === \"json_object\" || responseFormat === \"text\") {\n return false;\n }\n\n return true;\n}\n\n/**\n * OpenAI-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `openai` SDK. Agents,\n * workflows, and supervisors never talk to OpenAI directly — they hold a\n * `ModelContract`, and this class is what makes that contract concrete for\n * any OpenAI-compatible endpoint (OpenAI, Azure OpenAI, OpenRouter, local\n * gateways that speak the Chat Completions protocol).\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client + frozen `ModelConfig` (name,\n * temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` and\n * `ToolContract[]` into OpenAI wire shapes on the way out, and\n * translating OpenAI's response (content, finish reason, tool calls,\n * usage) back into the neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as a\n * class (see §4.2 of code-style.md — \"long-lived state across calls\").\n *\n * @example\n * import OpenAI from \"openai\";\n * const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n * const model = new OpenAIModel(client, { name: \"gpt-4o\", temperature: 0.3 });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class OpenAIModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: OpenAI;\n private readonly config: OpenAIModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIModelConfig, provider: string = \"openai\") {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? inferStructuredOutput(config.responseFormat),\n vision: config.vision ?? inferVisionCapability(config.name),\n // o-series + gpt-5 models surface a reasoning channel and accept\n // the `reasoning_effort` param. Explicit config wins over the\n // name-prefix inference.\n reasoning: config.reasoning ?? inferReasoningCapability(config.name),\n // OpenAI prompt caching is automatic on the Chat Completions API\n // (no caller-supplied breakpoints — the platform caches long\n // prompt prefixes server-side and reports the hit count via\n // `prompt_tokens_details.cached_tokens`). We therefore advertise\n // the read-side accounting capability as always available while\n // treating `ModelCallOptions.cacheControl` write breakpoints as a\n // no-op (see `buildReasoningParams` siblings — there is no cache\n // param to emit).\n promptCaching: true,\n // PDF + audio INPUT are off by default — OpenAI accepts `file`\n // (PDF) and `input_audio` parts only on specific models, so the\n // flags are conservative/honest and opt-in via config rather than\n // name-inferred. When set, the agent admits the attachments and\n // `toOpenAIMessages` maps them to the real wire parts.\n pdf: config.pdf ?? false,\n audio: config.audio ?? false,\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Chat\n * Completions endpoint, waits for the terminal response, and reshapes it\n * into a vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n // Per-call request/response logs are hot-path in production agents\n // — keep them at `debug` so `info` stays reserved for lifecycle\n // events (agent starting/completed, etc.). Operators who need to\n // audit every LLM call can raise log-level at runtime.\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to chat.completions\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: OpenAI.Chat.Completions.ChatCompletion;\n\n try {\n response = await this.client.chat.completions.create(\n {\n model: this.name,\n messages: toOpenAIMessages(messages),\n temperature: options?.temperature ?? this.config.temperature,\n max_tokens: options?.maxTokens ?? this.config.maxTokens,\n tools: toOpenAITools(options?.tools),\n ...this.buildResponseFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length)),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const choice = response.choices[0];\n const finishReason = mapFinishReason(choice.finish_reason);\n const usage = this.extractUsage(response.usage);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to chat.completions succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: choice.message.content ?? \"\",\n finishReason,\n usage,\n toolCalls: this.extractToolCalls(choice.message.tool_calls),\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s —\n * `delta` for text tokens, `tool-call` when the model requests a tool,\n * and a terminal `done` carrying the final finish reason + usage totals.\n * Callers consume it with `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to chat.completions\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<typeof this.client.chat.completions.create>>;\n\n try {\n stream = await this.client.chat.completions.create(\n {\n model: this.name,\n messages: toOpenAIMessages(messages),\n temperature: options?.temperature ?? this.config.temperature,\n max_tokens: options?.maxTokens ?? this.config.maxTokens,\n tools: toOpenAITools(options?.tools),\n stream: true,\n stream_options: { include_usage: true },\n ...this.buildResponseFormat(options?.responseSchema),\n ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length)),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n let rawFinishReason: string = \"stop\";\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolCallAccum = new Map<number, { id: string; name: string; arguments: string }>();\n\n try {\n for await (const chunk of stream as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>) {\n const delta = chunk.choices[0]?.delta;\n const finish = chunk.choices[0]?.finish_reason;\n\n if (delta?.content) {\n yield { type: \"delta\", content: delta.content };\n }\n\n if (delta?.tool_calls) {\n for (const toolCall of delta.tool_calls) {\n const idx = toolCall.index ?? 0;\n if (!toolCallAccum.has(idx)) {\n toolCallAccum.set(idx, { id: \"\", name: \"\", arguments: \"\" });\n }\n const acc = toolCallAccum.get(idx)!;\n if (toolCall.id) acc.id = toolCall.id;\n if (toolCall.function?.name) acc.name = toolCall.function.name;\n if (toolCall.function?.arguments) acc.arguments += toolCall.function.arguments;\n }\n }\n\n if (finish) {\n rawFinishReason = finish;\n }\n\n if (chunk.usage) {\n usage.input = chunk.usage.prompt_tokens ?? 0;\n usage.output = chunk.usage.completion_tokens ?? 0;\n usage.total = chunk.usage.total_tokens ?? 0;\n const cached = chunk.usage.prompt_tokens_details?.cached_tokens;\n if (cached !== undefined && cached > 0) {\n usage.cachedTokens = cached;\n }\n const reasoning = chunk.usage.completion_tokens_details?.reasoning_tokens;\n if (reasoning !== undefined && reasoning > 0) {\n usage.reasoningTokens = reasoning;\n }\n }\n }\n\n for (const acc of toolCallAccum.values()) {\n // Skip accumulators that never received a function name — those\n // are partial fragments the model started but never identified\n // (e.g. arguments-only deltas with no originating `id`/`name`).\n // Yielding them produces nameless tool-calls the agent runtime\n // can't dispatch and would mis-attribute as a registered tool.\n if (!acc.name) continue;\n\n yield {\n type: \"tool-call\",\n id: acc.id,\n name: acc.name,\n input: safeJsonParse<Record<string, unknown>>(acc.arguments, {}),\n };\n }\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n const finishReason = mapFinishReason(rawFinishReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to chat.completions succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Translate the neutral `responseSchema` option into OpenAI's\n * `response_format` parameter.\n *\n * When `config.responseFormat` is set, it wins: `\"text\"` emits no\n * `response_format` at all, `\"json_object\"` always picks the loose\n * mode, and `\"json_schema\"` picks strict mode (with the same\n * `isStrictCompatible` safety check — a malformed schema still\n * degrades to `json_object` rather than 400). The override exists\n * because some targets (older OpenAI models, OpenRouter routes,\n * Ollama OpenAI-compat) reject strict `json_schema` outright.\n *\n * When the override is omitted, uses strict `json_schema` mode\n * (token-level enforcement) only when the schema is a proper\n * root-object JSON Schema (`{ type: \"object\", properties: ... }`).\n * For anything else — malformed extractor output, non-object\n * schemas, or future shapes we haven't tested — falls back to loose\n * `json_object` mode, which guarantees *some* valid JSON without\n * enforcing shape. The agent's soft instruction already embeds the\n * schema text in the system prompt when the model declares no\n * native structured-output capability, so shape validation still\n * runs client-side via the Standard Schema `validate()` call.\n *\n * Returns an empty spread when no schema was supplied, so the caller\n * can unconditionally `...buildResponseFormat(...)` into the request.\n */\n private buildResponseFormat(responseSchema: Record<string, unknown> | undefined): {\n response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams[\"response_format\"];\n } {\n if (!responseSchema) {\n return {};\n }\n\n const override = this.config.responseFormat;\n\n if (override === \"text\") {\n return {};\n }\n\n if (override === \"json_object\") {\n return { response_format: { type: \"json_object\" } };\n }\n\n // Either auto-select (no override) or explicit `\"json_schema\"`.\n // The strict-compat check still applies in the explicit case —\n // a malformed / non-object schema would 400 before sampling, so\n // we degrade to `json_object` rather than crash.\n if (this.isStrictCompatible(responseSchema)) {\n return {\n response_format: {\n type: \"json_schema\",\n json_schema: {\n name: \"response\",\n schema: responseSchema,\n strict: true,\n },\n },\n };\n }\n\n return { response_format: { type: \"json_object\" } };\n }\n\n /**\n * OpenAI strict `json_schema` mode requires the root to be a JSON\n * Schema object type (`{ type: \"object\", properties: ... }`). Anything\n * else (top-level arrays, primitives, unknown shapes) is rejected with\n * a 400 before a token is sampled. We check structurally here so the\n * first call doesn't crash on a malformed extraction — loose\n * `json_object` mode is a safe degradation.\n */\n private isStrictCompatible(schema: Record<string, unknown>): boolean {\n return (\n schema.type === \"object\" &&\n typeof schema.properties === \"object\" &&\n schema.properties !== null &&\n this.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\n * as nullable, e.g. `type: [\"string\", \"null\"]`, and still appear in\n * `required`). A schema that violates this anywhere in the tree is NOT\n * sent in strict `json_schema` mode — it degrades to loose\n * `json_object` so a hand-built or optional-bearing schema can't 400\n * the call (\"'required' ... must include every key in properties\").\n * Client-side `validate()` still enforces the full shape.\n */\n private 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 (!this.isStrictSafeNode(properties[key])) {\n return false;\n }\n }\n }\n\n if (record.items !== undefined && !this.isStrictSafeNode(record.items)) {\n return false;\n }\n\n for (const branch of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n const value = record[branch];\n if (Array.isArray(value) && value.some((sub) => !this.isStrictSafeNode(sub))) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Normalize OpenAI's `usage` block (which may be absent on some responses\n * or partials) into the neutral `Usage` shape. Missing usage collapses to\n * zeros rather than propagating `undefined`, so downstream aggregation\n * math stays safe.\n *\n * `cachedTokens` mirrors `prompt_tokens_details.cached_tokens` (the\n * subset of the prompt served from OpenAI's automatic prompt cache);\n * `reasoningTokens` mirrors `completion_tokens_details.reasoning_tokens`\n * (the hidden reasoning channel on o-series / gpt-5 models, already\n * counted within `output`). Both are emitted only when the provider\n * reports a positive value, so non-reasoning / uncached calls keep the\n * lean `{ input, output, total }` shape.\n */\n private extractUsage(raw: OpenAI.Completions.CompletionUsage | undefined): Usage {\n if (!raw) {\n return { input: 0, output: 0, total: 0 };\n }\n\n const cachedTokens = raw.prompt_tokens_details?.cached_tokens;\n const reasoningTokens = raw.completion_tokens_details?.reasoning_tokens;\n\n return {\n input: raw.prompt_tokens,\n output: raw.completion_tokens,\n total: raw.total_tokens,\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n ...(reasoningTokens !== undefined && reasoningTokens > 0 ? { reasoningTokens } : {}),\n };\n }\n\n /**\n * Translate the neutral `ModelCallOptions.reasoning` hint into OpenAI's\n * `reasoning_effort` request param. Only `effort` maps — OpenAI's Chat\n * Completions API exposes a discrete effort knob, not a token budget,\n * so `reasoning.maxTokens` (the Anthropic extended-thinking cap) has no\n * wire equivalent here and is silently ignored.\n *\n * The neutral `ReasoningEffort` (`\"low\" | \"medium\" | \"high\" | \"none\"`)\n * is a subset of OpenAI's accepted values, so it forwards verbatim —\n * `\"none\"` included. `\"none\"` is load-bearing: gpt-5 / o-series models\n * **reject function tools** on Chat Completions while reasoning is\n * active, and the endpoint accepts tools only when `reasoning_effort`\n * is `\"none\"` (the alternative is the Responses API). This is why the\n * param is EMITTED for `\"none\"` rather than omitted — omitting it\n * leaves the model reasoning server-side by default, so tools would\n * still be rejected.\n *\n * No-ops when the model is not reasoning-capable\n * (`capabilities.reasoning` is false — e.g. `gpt-4o`, which 400s on any\n * `reasoning_effort`).\n *\n * When the caller gave no explicit `effort`: defaults to `\"none\"` IF\n * `hasTools` is true, else omits the param (provider default — the\n * pre-existing behavior for a tool-less call). The default exists\n * because, on a reasoning-capable model, there is no working alternative\n * to `\"none\"` when tools are attached — omitting `reasoning_effort`\n * leaves reasoning on server-side, which Chat Completions rejects tools\n * for (empty replies on some model generations, a hard 400 — \"Function\n * tools with reasoning_effort are not supported ... in\n * /v1/chat/completions\" — on newer ones). An explicit `effort` from the\n * caller always wins over this default, in either direction.\n *\n * Returns an empty spread when nothing applies, so the caller can\n * unconditionally `...buildReasoningParams(...)` into the request.\n */\n private buildReasoningParams(\n reasoning: ModelCallOptions[\"reasoning\"],\n hasTools: boolean,\n ): {\n reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams[\"reasoning_effort\"];\n } {\n if (!this.capabilities.reasoning) {\n return {};\n }\n\n if (!reasoning?.effort) {\n return hasTools ? { reasoning_effort: \"none\" } : {};\n }\n\n return { reasoning_effort: reasoning.effort };\n }\n\n /**\n * Reshape OpenAI's `tool_calls` array into the neutral\n * `ModelToolCallRequest[]`. The raw `arguments` field is a JSON string\n * per OpenAI's protocol — we parse it defensively via `safeJsonParse` so\n * malformed or empty arguments yield an empty object instead of crashing\n * the trip. Returns `undefined` when no tools were requested so callers\n * can branch on presence.\n */\n private extractToolCalls(\n rawToolCalls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[] | undefined,\n ): ModelToolCallRequest[] | undefined {\n if (!rawToolCalls || rawToolCalls.length === 0) {\n return undefined;\n }\n\n return rawToolCalls.map((toolCall) => ({\n id: toolCall.id,\n name: (toolCall as any).function.name,\n input: safeJsonParse<Record<string, unknown>>((toolCall as any).function.arguments, {}),\n }));\n }\n}\n"],"mappings":"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"}
package/esm/sdk.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { OpenAIEmbedderConfig, OpenAIImageConfig, OpenAIModelConfig, OpenAISDKConfig, OpenAISpeechConfig, OpenAITranscriptionConfig } from "./config.type.mjs";
2
2
  import { EmbedderContract, ImageModelContract, ModelContract, SDKAdapterContract, SpeechModelContract, TranscriptionModelContract } from "@warlock.js/ai";
3
3
 
4
- //#region ../@warlock.js/ai-openai/src/sdk.d.ts
4
+ //#region ../ai-openai/src/sdk.d.ts
5
5
  /**
6
6
  * OpenAI-backed implementation of `SDKAdapterContract`.
7
7
  *
package/esm/sdk.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/sdk.ts"],"mappings":";;;;;;AA2DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,eAAA;EAyEpB;;;;;;;;;;;;EAjDA,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;EAiFuC;;;;;;;EAlEnE,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;;;;EAc5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;;;;;;;;;;;;;;;;;EAoBxC,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,kBAAA;;;;;;;;;;;EAkBlC,MAAA,CAAO,MAAA,EAAQ,kBAAA,GAAqB,mBAAA;;;;;;;;;;;EAcpC,UAAA,CAAW,MAAA,EAAQ,yBAAA,GAA4B,0BAAA;AAAA"}
1
+ {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../../ai-openai/src/sdk.ts"],"mappings":";;;;;;AA2DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,kBAAA;EAAA,iBACf,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,eAAA;EAyEpB;;;;;;;;;;;;EAjDA,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,aAAA;EAiFuC;;;;;;;EAlEnE,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;;;;EAc5C,QAAA,CAAS,MAAA,EAAQ,oBAAA,GAAuB,gBAAA;;;;;;;;;;;;;;;;;EAoBxC,KAAA,CAAM,MAAA,EAAQ,iBAAA,GAAoB,kBAAA;;;;;;;;;;;EAkBlC,MAAA,CAAO,MAAA,EAAQ,kBAAA,GAAqB,mBAAA;;;;;;;;;;;EAcpC,UAAA,CAAW,MAAA,EAAQ,yBAAA,GAA4B,0BAAA;AAAA"}
package/esm/sdk.mjs CHANGED
@@ -6,7 +6,7 @@ import { OpenAITranscriptionModel } from "./transcription.mjs";
6
6
  import OpenAI from "openai";
7
7
  import { approximateTokenCount } from "@warlock.js/ai";
8
8
 
9
- //#region ../@warlock.js/ai-openai/src/sdk.ts
9
+ //#region ../ai-openai/src/sdk.ts
10
10
  /**
11
11
  * OpenAI-backed implementation of `SDKAdapterContract`.
12
12
  *
package/esm/sdk.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/sdk.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n SpeechModelContract,\n TranscriptionModelContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n OpenAIEmbedderConfig,\n OpenAIImageConfig,\n OpenAIModelConfig,\n OpenAISDKConfig,\n OpenAISpeechConfig,\n OpenAITranscriptionConfig,\n} from \"./config.type\";\nimport { OpenAIEmbedder } from \"./embedder\";\nimport { OpenAIImageModel } from \"./image\";\nimport { OpenAIModel } from \"./model\";\nimport { OpenAISpeechModel } from \"./speech\";\nimport { OpenAITranscriptionModel } from \"./transcription\";\n\n/**\n * OpenAI-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for any OpenAI-compatible provider\n * (OpenAI, Azure OpenAI, OpenRouter, local gateways speaking the Chat\n * Completions protocol). A single `OpenAISDK` instance holds one live\n * `OpenAI` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per provider/account and reuse it\n * across all agents, workflows, and supervisors that target that\n * provider.\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client (authentication, base URL) and\n * its lifetime scope. Factory for `OpenAIModel` instances — each\n * model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `OpenAIModel` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `OpenAI` client is heavy to construct and\n * designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the PascalCase naming convention readers\n * expect from a constructor.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });\n * const model = openai.model({ name: \"gpt-4o\", temperature: 0.7 });\n * const tokens = await openai.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.openai` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, persona, instruction, openai: new OpenAISDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.openai.model({ name: \"gpt-4o-mini\" }) });\n */\nexport class OpenAISDK implements SDKAdapterContract {\n private readonly client: OpenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: OpenAISDKConfig) {\n // Peel off the framework-only keys and forward every other upstream\n // `ClientOptions` (timeout, maxRetries, defaultHeaders, fetch,\n // organization, project, …) verbatim — they type-check, so dropping them\n // is a silent footgun. Mirrors the Bedrock/Google/Ollama adapters.\n const { provider, pricing, ...clientOptions } = config;\n\n this.client = new OpenAI(clientOptions);\n this.provider = provider ?? \"openai\";\n this.pricing = pricing;\n }\n\n /**\n * Build an `OpenAIModel` bound to this SDK's client. Each call returns\n * a fresh model instance, but all instances share the underlying\n * `OpenAI` client — connection pools, rate limits, and authentication\n * state stay unified across every model produced here. The SDK's\n * `provider` label is forwarded so every model self-identifies as\n * coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: OpenAIModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses a\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Accepts an optional model id for future per-model tokenizer\n * dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build an `OpenAIEmbedder` bound to this SDK's client. Each call\n * returns a fresh embedder instance sharing the same underlying\n * `OpenAI` client — connection pools and authentication stay unified\n * across every embedder produced here.\n *\n * @example\n * const embedder = openai.embedder({ name: \"text-embedding-3-small\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: OpenAIEmbedderConfig): EmbedderContract {\n return new OpenAIEmbedder(this.client, config);\n }\n\n /**\n * Build an `OpenAIImageModel` bound to this SDK's client for use with\n * `ai.image({ model, prompt })`. Accepts the `gpt-image-*` (token-metered)\n * and `dall-e-*` (per-image-metered) families; a non-image model id\n * is rejected at construction.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined` (no cost computed). A token-priced\n * `gpt-image-1` entry can live in the same SDK registry as the chat\n * models.\n *\n * @example\n * const model = openai.image({ name: \"gpt-image-1\" });\n * const { data } = await ai.image({ model, prompt: \"a red bicycle\" });\n */\n public image(config: OpenAIImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIImageModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Build an `OpenAISpeechModel` (text-to-speech) bound to this SDK's\n * client, for use with `ai.speech({ model, text })`. Accepts the\n * `tts-1` / `gpt-4o-mini-tts` families; a non-TTS model id is rejected\n * at construction.\n *\n * @example\n * const tts = openai.speech({ name: \"tts-1\", voice: \"alloy\" });\n * const { data } = await ai.speech({ model: tts, text: \"Hello\" });\n */\n public speech(config: OpenAISpeechConfig): SpeechModelContract {\n return new OpenAISpeechModel(this.client, config, this.provider);\n }\n\n /**\n * Build an `OpenAITranscriptionModel` (speech-to-text) bound to this\n * SDK's client, for use with `ai.transcribe({ model, audio })`.\n * Accepts the `whisper-1` / `gpt-4o-transcribe` families; a non-STT\n * model id is rejected at construction.\n *\n * @example\n * const stt = openai.transcribe({ name: \"whisper-1\" });\n * const { data } = await ai.transcribe({ model: stt, audio });\n */\n public transcribe(config: OpenAITranscriptionConfig): TranscriptionModelContract {\n return new OpenAITranscriptionModel(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAK1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,SAAS,IAAI,OAAO,aAAa;EACtC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;;;;CAcA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACnE;;;;;;;;CASA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;;;;CAYA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,QAAQ,MAAM;CAC/C;;;;;;;;;;;;;;;;;CAkBA,AAAO,MAAM,QAA+C;EAC1D,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,iBAAiB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACxE;;;;;;;;;;;CAYA,AAAO,OAAO,QAAiD;EAC7D,OAAO,IAAI,kBAAkB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACjE;;;;;;;;;;;CAYA,AAAO,WAAW,QAA+D;EAC/E,OAAO,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACxE;AACF"}
1
+ {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../../ai-openai/src/sdk.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport type {\n EmbedderContract,\n ImageModelContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n SpeechModelContract,\n TranscriptionModelContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n OpenAIEmbedderConfig,\n OpenAIImageConfig,\n OpenAIModelConfig,\n OpenAISDKConfig,\n OpenAISpeechConfig,\n OpenAITranscriptionConfig,\n} from \"./config.type\";\nimport { OpenAIEmbedder } from \"./embedder\";\nimport { OpenAIImageModel } from \"./image\";\nimport { OpenAIModel } from \"./model\";\nimport { OpenAISpeechModel } from \"./speech\";\nimport { OpenAITranscriptionModel } from \"./transcription\";\n\n/**\n * OpenAI-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for any OpenAI-compatible provider\n * (OpenAI, Azure OpenAI, OpenRouter, local gateways speaking the Chat\n * Completions protocol). A single `OpenAISDK` instance holds one live\n * `OpenAI` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per provider/account and reuse it\n * across all agents, workflows, and supervisors that target that\n * provider.\n *\n * **Responsibility.**\n * - Owns: a long-lived `OpenAI` client (authentication, base URL) and\n * its lifetime scope. Factory for `OpenAIModel` instances — each\n * model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `OpenAIModel` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `OpenAI` client is heavy to construct and\n * designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the PascalCase naming convention readers\n * expect from a constructor.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });\n * const model = openai.model({ name: \"gpt-4o\", temperature: 0.7 });\n * const tokens = await openai.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.openai` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, persona, instruction, openai: new OpenAISDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.openai.model({ name: \"gpt-4o-mini\" }) });\n */\nexport class OpenAISDK implements SDKAdapterContract {\n private readonly client: OpenAI;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: OpenAISDKConfig) {\n // Peel off the framework-only keys and forward every other upstream\n // `ClientOptions` (timeout, maxRetries, defaultHeaders, fetch,\n // organization, project, …) verbatim — they type-check, so dropping them\n // is a silent footgun. Mirrors the Bedrock/Google/Ollama adapters.\n const { provider, pricing, ...clientOptions } = config;\n\n this.client = new OpenAI(clientOptions);\n this.provider = provider ?? \"openai\";\n this.pricing = pricing;\n }\n\n /**\n * Build an `OpenAIModel` bound to this SDK's client. Each call returns\n * a fresh model instance, but all instances share the underlying\n * `OpenAI` client — connection pools, rate limits, and authentication\n * state stay unified across every model produced here. The SDK's\n * `provider` label is forwarded so every model self-identifies as\n * coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: OpenAIModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses a\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Accepts an optional model id for future per-model tokenizer\n * dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build an `OpenAIEmbedder` bound to this SDK's client. Each call\n * returns a fresh embedder instance sharing the same underlying\n * `OpenAI` client — connection pools and authentication stay unified\n * across every embedder produced here.\n *\n * @example\n * const embedder = openai.embedder({ name: \"text-embedding-3-small\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: OpenAIEmbedderConfig): EmbedderContract {\n return new OpenAIEmbedder(this.client, config);\n }\n\n /**\n * Build an `OpenAIImageModel` bound to this SDK's client for use with\n * `ai.image({ model, prompt })`. Accepts the `gpt-image-*` (token-metered)\n * and `dall-e-*` (per-image-metered) families; a non-image model id\n * is rejected at construction.\n *\n * Pricing resolution mirrors `model()`: per-model `config.pricing`\n * wins, otherwise the SDK-level registry entry keyed by `config.name`,\n * otherwise `undefined` (no cost computed). A token-priced\n * `gpt-image-1` entry can live in the same SDK registry as the chat\n * models.\n *\n * @example\n * const model = openai.image({ name: \"gpt-image-1\" });\n * const { data } = await ai.image({ model, prompt: \"a red bicycle\" });\n */\n public image(config: OpenAIImageConfig): ImageModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: OpenAIImageConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new OpenAIImageModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Build an `OpenAISpeechModel` (text-to-speech) bound to this SDK's\n * client, for use with `ai.speech({ model, text })`. Accepts the\n * `tts-1` / `gpt-4o-mini-tts` families; a non-TTS model id is rejected\n * at construction.\n *\n * @example\n * const tts = openai.speech({ name: \"tts-1\", voice: \"alloy\" });\n * const { data } = await ai.speech({ model: tts, text: \"Hello\" });\n */\n public speech(config: OpenAISpeechConfig): SpeechModelContract {\n return new OpenAISpeechModel(this.client, config, this.provider);\n }\n\n /**\n * Build an `OpenAITranscriptionModel` (speech-to-text) bound to this\n * SDK's client, for use with `ai.transcribe({ model, audio })`.\n * Accepts the `whisper-1` / `gpt-4o-transcribe` families; a non-STT\n * model id is rejected at construction.\n *\n * @example\n * const stt = openai.transcribe({ name: \"whisper-1\" });\n * const { data } = await ai.transcribe({ model: stt, audio });\n */\n public transcribe(config: OpenAITranscriptionConfig): TranscriptionModelContract {\n return new OpenAITranscriptionModel(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,YAAb,MAAqD;CAKnD,AAAO,YAAY,QAAyB;EAK1C,MAAM,EAAE,UAAU,SAAS,GAAG,kBAAkB;EAEhD,KAAK,SAAS,IAAI,OAAO,aAAa;EACtC,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;;;;CAcA,AAAO,MAAM,QAA0C;EACrD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,YAAY,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACnE;;;;;;;;CASA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;;;;CAYA,AAAO,SAAS,QAAgD;EAC9D,OAAO,IAAI,eAAe,KAAK,QAAQ,MAAM;CAC/C;;;;;;;;;;;;;;;;;CAkBA,AAAO,MAAM,QAA+C;EAC1D,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,iBAAiB,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACxE;;;;;;;;;;;CAYA,AAAO,OAAO,QAAiD;EAC7D,OAAO,IAAI,kBAAkB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACjE;;;;;;;;;;;CAYA,AAAO,WAAW,QAA+D;EAC/E,OAAO,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CACxE;AACF"}
package/esm/speech.d.mts CHANGED
@@ -2,7 +2,7 @@ import { OpenAISpeechConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { SpeechGenerationResponse, SpeechModelContract, SpeechModelPricing, SpeechOptions } from "@warlock.js/ai";
4
4
 
5
- //#region ../@warlock.js/ai-openai/src/speech.d.ts
5
+ //#region ../ai-openai/src/speech.d.ts
6
6
  /** True when `name` is a recognized OpenAI text-to-speech model. */
7
7
  declare function isOpenAISpeechModel(name: string): boolean;
8
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"speech.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/speech.ts"],"mappings":";;;;;;iBAkBgB,mBAAA,CAAoB,IAAY;AAAhD;;;;AAAgD;AAmChD;;;;;;;;AAnCA,cAmCa,iBAAA,YAA6B,mBAAA;EAAA,SACxB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,kBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,YAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,kBAAA,EAAoB,QAAA;EAelD,QAAA,CAAS,IAAA,UAAc,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,wBAAA;AAAA"}
1
+ {"version":3,"file":"speech.d.mts","names":[],"sources":["../../../../../../ai-openai/src/speech.ts"],"mappings":";;;;;;iBAkBgB,mBAAA,CAAoB,IAAY;AAAhD;;;;AAAgD;AAmChD;;;;;;;;AAnCA,cAmCa,iBAAA,YAA6B,mBAAA;EAAA,SACxB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,kBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,YAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,kBAAA,EAAoB,QAAA;EAelD,QAAA,CAAS,IAAA,UAAc,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,wBAAA;AAAA"}
package/esm/speech.mjs CHANGED
@@ -3,7 +3,7 @@ import "./utils/index.mjs";
3
3
  import { InvalidRequestError } from "@warlock.js/ai";
4
4
  import { log } from "@warlock.js/logger";
5
5
 
6
- //#region ../@warlock.js/ai-openai/src/speech.ts
6
+ //#region ../ai-openai/src/speech.ts
7
7
  const LOG_MODULE = "ai.openai";
8
8
  /** Model-id prefixes OpenAI exposes through the **Speech** (TTS) API. */
9
9
  const SPEECH_MODEL_PREFIXES = [
@@ -1 +1 @@
1
- {"version":3,"file":"speech.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/speech.ts"],"sourcesContent":["import {\n InvalidRequestError,\n type SpeechGenerationResponse,\n type SpeechModelContract,\n type SpeechModelPricing,\n type SpeechOptions,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAISpeechConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Model-id prefixes OpenAI exposes through the **Speech** (TTS) API. */\nconst SPEECH_MODEL_PREFIXES = [\"tts-1\", \"gpt-4o-mini-tts\", \"gpt-audio\"] as const;\n\n/** True when `name` is a recognized OpenAI text-to-speech model. */\nexport function isOpenAISpeechModel(name: string): boolean {\n return SPEECH_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n\n/** Map a neutral output container hint to its IANA audio media type. */\nfunction audioMediaType(format: string | undefined): string {\n switch (format) {\n case \"opus\":\n return \"audio/opus\";\n case \"aac\":\n return \"audio/aac\";\n case \"flac\":\n return \"audio/flac\";\n case \"wav\":\n return \"audio/wav\";\n case \"pcm\":\n return \"audio/pcm\";\n default:\n return \"audio/mpeg\";\n }\n}\n\n/**\n * OpenAI-backed implementation of `SpeechModelContract` (text-to-speech)\n * via `audio.speech.create`. Standalone primitive — no relation to chat\n * completions or the agent loop. Consumed by the `ai.speech()` verb.\n *\n * **Capability guard.** The constructor rejects a non-TTS model id up\n * front (`tts-1` / `gpt-4o-mini-tts` only) so the mistake surfaces at\n * wiring time, mirroring the embedder / image guards.\n *\n * @example\n * const tts = new OpenAISpeechModel(client, { name: \"tts-1\", voice: \"alloy\" }, \"openai\");\n * const { audio } = await tts.generate(\"Welcome aboard.\");\n */\nexport class OpenAISpeechModel implements SpeechModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: SpeechModelPricing;\n\n private readonly client: OpenAI;\n private readonly defaultVoice?: string;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAISpeechConfig, provider: string = \"openai\") {\n if (!isOpenAISpeechModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known OpenAI text-to-speech model. ` +\n \"Use a `tts-1` / `tts-1-hd` / `gpt-4o-mini-tts` model with openai.speech({ name }).\",\n );\n }\n\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.defaultVoice = config.voice;\n }\n\n public async generate(text: string, options?: SpeechOptions): Promise<SpeechGenerationResponse> {\n const format = options?.format ?? \"mp3\";\n\n this.logger.debug(LOG_MODULE, \"speech.request\", \"audio.speech.create\", {\n model: this.name,\n characters: text.length,\n });\n\n let response: Response;\n\n try {\n response = await this.client.audio.speech.create(\n {\n model: this.name,\n input: text,\n voice: options?.voice ?? this.defaultVoice ?? \"alloy\",\n response_format: format as OpenAI.Audio.SpeechCreateParams[\"response_format\"],\n ...(options?.speed !== undefined ? { speed: options.speed } : {}),\n ...(options?.instructions !== undefined ? { instructions: options.instructions } : {}),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n this.logger.error(LOG_MODULE, \"speech.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n throw wrapped;\n }\n\n const base64 = Buffer.from(await response.arrayBuffer()).toString(\"base64\");\n\n return {\n audio: { type: \"base64\", base64, mediaType: audioMediaType(format) },\n // The Speech API reports no token usage; spend is priced per\n // character (or per token for gpt-4o-mini-tts) by `ai.speech()`.\n usage: { input: 0, output: 0, total: 0 },\n characters: text.length,\n };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;AAGnB,MAAM,wBAAwB;CAAC;CAAS;CAAmB;AAAW;;AAGtE,SAAgB,oBAAoB,MAAuB;CACzD,OAAO,sBAAsB,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC;AACvE;;AAGA,SAAS,eAAe,QAAoC;CAC1D,QAAQ,QAAR;EACE,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;AAeA,IAAa,oBAAb,MAA8D;CAS5D,AAAO,YAAY,QAAgB,QAA4B,WAAmB,UAAU;gBAF1D;EAGhC,IAAI,CAAC,oBAAoB,OAAO,IAAI,GAClC,MAAM,IAAI,oBACR,IAAI,OAAO,KAAK,uIAElB;EAGF,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;EACtB,KAAK,eAAe,OAAO;CAC7B;CAEA,MAAa,SAAS,MAAc,SAA4D;EAC9F,MAAM,SAAS,SAAS,UAAU;EAElC,KAAK,OAAO,MAAM,YAAY,kBAAkB,uBAAuB;GACrE,OAAO,KAAK;GACZ,YAAY,KAAK;EACnB,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,MAAM,OAAO,OACxC;IACE,OAAO,KAAK;IACZ,OAAO;IACP,OAAO,SAAS,SAAS,KAAK,gBAAgB;IAC9C,iBAAiB;IACjB,GAAI,SAAS,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;IAC/D,GAAI,SAAS,iBAAiB,SAAY,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;GACtF,GACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GACtC,KAAK,OAAO,MAAM,YAAY,gBAAgB,QAAQ,SAAS;IAC7D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GACD,MAAM;EACR;EAIA,OAAO;GACL,OAAO;IAAE,MAAM;IAAU,QAHZ,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC,CAAC,SAAS,QAGlC;IAAG,WAAW,eAAe,MAAM;GAAE;GAGnE,OAAO;IAAE,OAAO;IAAG,QAAQ;IAAG,OAAO;GAAE;GACvC,YAAY,KAAK;EACnB;CACF;AACF"}
1
+ {"version":3,"file":"speech.mjs","names":[],"sources":["../../../../../../ai-openai/src/speech.ts"],"sourcesContent":["import {\n InvalidRequestError,\n type SpeechGenerationResponse,\n type SpeechModelContract,\n type SpeechModelPricing,\n type SpeechOptions,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAISpeechConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\n/** Model-id prefixes OpenAI exposes through the **Speech** (TTS) API. */\nconst SPEECH_MODEL_PREFIXES = [\"tts-1\", \"gpt-4o-mini-tts\", \"gpt-audio\"] as const;\n\n/** True when `name` is a recognized OpenAI text-to-speech model. */\nexport function isOpenAISpeechModel(name: string): boolean {\n return SPEECH_MODEL_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n\n/** Map a neutral output container hint to its IANA audio media type. */\nfunction audioMediaType(format: string | undefined): string {\n switch (format) {\n case \"opus\":\n return \"audio/opus\";\n case \"aac\":\n return \"audio/aac\";\n case \"flac\":\n return \"audio/flac\";\n case \"wav\":\n return \"audio/wav\";\n case \"pcm\":\n return \"audio/pcm\";\n default:\n return \"audio/mpeg\";\n }\n}\n\n/**\n * OpenAI-backed implementation of `SpeechModelContract` (text-to-speech)\n * via `audio.speech.create`. Standalone primitive — no relation to chat\n * completions or the agent loop. Consumed by the `ai.speech()` verb.\n *\n * **Capability guard.** The constructor rejects a non-TTS model id up\n * front (`tts-1` / `gpt-4o-mini-tts` only) so the mistake surfaces at\n * wiring time, mirroring the embedder / image guards.\n *\n * @example\n * const tts = new OpenAISpeechModel(client, { name: \"tts-1\", voice: \"alloy\" }, \"openai\");\n * const { audio } = await tts.generate(\"Welcome aboard.\");\n */\nexport class OpenAISpeechModel implements SpeechModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly pricing?: SpeechModelPricing;\n\n private readonly client: OpenAI;\n private readonly defaultVoice?: string;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAISpeechConfig, provider: string = \"openai\") {\n if (!isOpenAISpeechModel(config.name)) {\n throw new InvalidRequestError(\n `\"${config.name}\" is not a known OpenAI text-to-speech model. ` +\n \"Use a `tts-1` / `tts-1-hd` / `gpt-4o-mini-tts` model with openai.speech({ name }).\",\n );\n }\n\n this.client = client;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.defaultVoice = config.voice;\n }\n\n public async generate(text: string, options?: SpeechOptions): Promise<SpeechGenerationResponse> {\n const format = options?.format ?? \"mp3\";\n\n this.logger.debug(LOG_MODULE, \"speech.request\", \"audio.speech.create\", {\n model: this.name,\n characters: text.length,\n });\n\n let response: Response;\n\n try {\n response = await this.client.audio.speech.create(\n {\n model: this.name,\n input: text,\n voice: options?.voice ?? this.defaultVoice ?? \"alloy\",\n response_format: format as OpenAI.Audio.SpeechCreateParams[\"response_format\"],\n ...(options?.speed !== undefined ? { speed: options.speed } : {}),\n ...(options?.instructions !== undefined ? { instructions: options.instructions } : {}),\n },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n this.logger.error(LOG_MODULE, \"speech.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n throw wrapped;\n }\n\n const base64 = Buffer.from(await response.arrayBuffer()).toString(\"base64\");\n\n return {\n audio: { type: \"base64\", base64, mediaType: audioMediaType(format) },\n // The Speech API reports no token usage; spend is priced per\n // character (or per token for gpt-4o-mini-tts) by `ai.speech()`.\n usage: { input: 0, output: 0, total: 0 },\n characters: text.length,\n };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;AAGnB,MAAM,wBAAwB;CAAC;CAAS;CAAmB;AAAW;;AAGtE,SAAgB,oBAAoB,MAAuB;CACzD,OAAO,sBAAsB,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC;AACvE;;AAGA,SAAS,eAAe,QAAoC;CAC1D,QAAQ,QAAR;EACE,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;;AAeA,IAAa,oBAAb,MAA8D;CAS5D,AAAO,YAAY,QAAgB,QAA4B,WAAmB,UAAU;gBAF1D;EAGhC,IAAI,CAAC,oBAAoB,OAAO,IAAI,GAClC,MAAM,IAAI,oBACR,IAAI,OAAO,KAAK,uIAElB;EAGF,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,WAAW;EAChB,KAAK,UAAU,OAAO;EACtB,KAAK,eAAe,OAAO;CAC7B;CAEA,MAAa,SAAS,MAAc,SAA4D;EAC9F,MAAM,SAAS,SAAS,UAAU;EAElC,KAAK,OAAO,MAAM,YAAY,kBAAkB,uBAAuB;GACrE,OAAO,KAAK;GACZ,YAAY,KAAK;EACnB,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,MAAM,OAAO,OACxC;IACE,OAAO,KAAK;IACZ,OAAO;IACP,OAAO,SAAS,SAAS,KAAK,gBAAgB;IAC9C,iBAAiB;IACjB,GAAI,SAAS,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;IAC/D,GAAI,SAAS,iBAAiB,SAAY,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;GACtF,GACA,SAAS,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,MACjD;EACF,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GACtC,KAAK,OAAO,MAAM,YAAY,gBAAgB,QAAQ,SAAS;IAC7D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GACD,MAAM;EACR;EAIA,OAAO;GACL,OAAO;IAAE,MAAM;IAAU,QAHZ,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC,CAAC,SAAS,QAGlC;IAAG,WAAW,eAAe,MAAM;GAAE;GAGnE,OAAO;IAAE,OAAO;IAAG,QAAQ;IAAG,OAAO;GAAE;GACvC,YAAY,KAAK;EACnB;CACF;AACF"}
@@ -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 ../@warlock.js/ai-openai/src/transcription.d.ts
5
+ //#region ../ai-openai/src/transcription.d.ts
6
6
  /** True when `name` is a recognized OpenAI speech-to-text model. */
7
7
  declare function isOpenAITranscriptionModel(name: string): boolean;
8
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-openai/src/transcription.ts"],"mappings":";;;;;;iBAoBgB,0BAAA,CAA2B,IAAY;AAAvD;;;;AAAuD;AAiCvD;;;;;;;;;AAjCA,cAiCa,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;EAeW,UAAA,CACX,KAAA,EAAO,UAAA,EACP,OAAA,GAAU,iBAAA,GACT,OAAA,CAAQ,qBAAA;AAAA"}
1
+ {"version":3,"file":"transcription.d.mts","names":[],"sources":["../../../../../../ai-openai/src/transcription.ts"],"mappings":";;;;;;iBAoBgB,0BAAA,CAA2B,IAAY;AAAvD;;;;AAAuD;AAiCvD;;;;;;;;;AAjCA,cAiCa,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;EAeW,UAAA,CACX,KAAA,EAAO,UAAA,EACP,OAAA,GAAU,iBAAA,GACT,OAAA,CAAQ,qBAAA;AAAA"}
@@ -4,7 +4,7 @@ import { toFile } from "openai";
4
4
  import { InvalidRequestError } from "@warlock.js/ai";
5
5
  import { log } from "@warlock.js/logger";
6
6
 
7
- //#region ../@warlock.js/ai-openai/src/transcription.ts
7
+ //#region ../ai-openai/src/transcription.ts
8
8
  const LOG_MODULE = "ai.openai";
9
9
  /** Model-id prefixes OpenAI exposes through the **Transcription** (STT) API. */
10
10
  const TRANSCRIPTION_MODEL_PREFIXES = [