@warlock.js/ai-openai 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +3 -3
  3. package/cjs/index.cjs +715 -51
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/config.type.d.mts +13 -13
  6. package/esm/config.type.d.mts.map +1 -1
  7. package/esm/embedder.d.mts +1 -3
  8. package/esm/embedder.d.mts.map +1 -1
  9. package/esm/embedder.mjs.map +1 -1
  10. package/esm/image.d.mts +1 -3
  11. package/esm/image.d.mts.map +1 -1
  12. package/esm/index.d.mts +4 -1
  13. package/esm/index.mjs +3 -1
  14. package/esm/kimi-tools.d.mts +69 -0
  15. package/esm/kimi-tools.d.mts.map +1 -0
  16. package/esm/kimi-tools.mjs +256 -0
  17. package/esm/kimi-tools.mjs.map +1 -0
  18. package/esm/known-pricing.d.mts +39 -0
  19. package/esm/known-pricing.d.mts.map +1 -0
  20. package/esm/known-pricing.mjs +121 -0
  21. package/esm/known-pricing.mjs.map +1 -0
  22. package/esm/model.mjs +82 -23
  23. package/esm/model.mjs.map +1 -1
  24. package/esm/provider-options.d.mts +13 -0
  25. package/esm/provider-options.d.mts.map +1 -0
  26. package/esm/provider-options.mjs +56 -0
  27. package/esm/provider-options.mjs.map +1 -0
  28. package/esm/responses-model.mjs +125 -19
  29. package/esm/responses-model.mjs.map +1 -1
  30. package/esm/sdk.d.mts +4 -5
  31. package/esm/sdk.d.mts.map +1 -1
  32. package/esm/sdk.mjs +5 -3
  33. package/esm/sdk.mjs.map +1 -1
  34. package/esm/speech.d.mts +1 -3
  35. package/esm/speech.d.mts.map +1 -1
  36. package/esm/transcription.d.mts +1 -3
  37. package/esm/transcription.d.mts.map +1 -1
  38. package/esm/transcription.mjs.map +1 -1
  39. package/esm/utils/build-usage.mjs +14 -2
  40. package/esm/utils/build-usage.mjs.map +1 -1
  41. package/esm/utils/hosted-output.mjs +22 -0
  42. package/esm/utils/hosted-output.mjs.map +1 -0
  43. package/esm/utils/index.mjs +2 -2
  44. package/esm/utils/map-responses-status.mjs.map +1 -1
  45. package/esm/utils/prompt-cache-params.mjs +25 -6
  46. package/esm/utils/prompt-cache-params.mjs.map +1 -1
  47. package/esm/utils/reasoning-replay.mjs +23 -1
  48. package/esm/utils/reasoning-replay.mjs.map +1 -1
  49. package/esm/utils/structured-output.mjs +1 -1
  50. package/esm/utils/structured-output.mjs.map +1 -1
  51. package/esm/utils/to-openai-messages.mjs.map +1 -1
  52. package/esm/utils/to-openai-tools.mjs.map +1 -1
  53. package/esm/utils/wrap-openai-error.mjs +6 -1
  54. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  55. package/llms-full.txt +235 -37
  56. package/llms.txt +1 -1
  57. package/package.json +3 -3
  58. package/skills/setup-openai/SKILL.md +235 -37
@@ -1,6 +1,5 @@
1
1
  import OpenAI, { ClientOptions } from "openai";
2
2
  import { EmbedderConfig, ImageModelConfig, ModelConfig, ModelPricing, SpeechModelConfig, TranscriptionModelConfig } from "@warlock.js/ai";
3
-
4
3
  //#region ../../packages/ai-openai/src/config.type.d.ts
5
4
  /**
6
5
  * Configuration for the OpenAI SDK adapter.
@@ -32,7 +31,7 @@ import { EmbedderConfig, ImageModelConfig, ModelConfig, ModelPricing, SpeechMode
32
31
  * },
33
32
  * });
34
33
  */
35
- type OpenAISDKConfig = Omit<ClientOptions, "provider"> & {
34
+ export type OpenAISDKConfig = Omit<ClientOptions, "provider"> & {
36
35
  /**
37
36
  * Free-form upstream label. `ClientOptions.provider` is omitted above
38
37
  * on purpose: openai v7 added its own `provider?: Provider` key (an
@@ -63,7 +62,7 @@ type OpenAISDKConfig = Omit<ClientOptions, "provider"> & {
63
62
  * // Route through OpenRouter to a model that rejects strict json_schema:
64
63
  * openai.model({ name: "some-legacy-model", responseFormat: "json_object" });
65
64
  */
66
- type OpenAIResponseFormat = "json_schema" | "json_object" | "text";
65
+ export type OpenAIResponseFormat = "json_schema" | "json_object" | "text";
67
66
  /**
68
67
  * Which OpenAI endpoint a model speaks.
69
68
  *
@@ -75,7 +74,7 @@ type OpenAIResponseFormat = "json_schema" | "json_object" | "text";
75
74
  * only: `OpenAISDK.model()` throws for any other provider label, so the
76
75
  * wrapper packages (deepseek, groq, xai, mistral) can never reach it.
77
76
  */
78
- type OpenAIApi = "chat" | "responses";
77
+ export type OpenAIApi = "chat" | "responses";
79
78
  /**
80
79
  * Per-model configuration for `OpenAISDK.model()`. Extends the neutral
81
80
  * `ModelConfig` with OpenAI-specific capability overrides.
@@ -88,7 +87,7 @@ type OpenAIApi = "chat" | "responses";
88
87
  * // Reasoning model + tools on the Responses API (direct OpenAI only):
89
88
  * openai.model({ name: "gpt-5.6", api: "responses" });
90
89
  */
91
- type OpenAIModelConfig = ModelConfig & {
90
+ export type OpenAIModelConfig = ModelConfig & {
92
91
  /**
93
92
  * Wire API to use. Default `"chat"` (Chat Completions). `"responses"`
94
93
  * selects the stateless Responses adapter and is accepted only on the
@@ -138,9 +137,11 @@ type OpenAIModelConfig = ModelConfig & {
138
137
  */
139
138
  reasoning?: boolean;
140
139
  /**
141
- * Stable request bucket used by OpenAI to improve prompt-cache matching.
142
- * Sent only by models whose SDK provider label is `"openai"`; compatible
143
- * wrapper endpoints deliberately never receive this OpenAI-only field.
140
+ * Stable request bucket used to improve prompt-cache matching, sent as
141
+ * `prompt_cache_key`. Sent when the SDK provider label is `"openai"`,
142
+ * `"xai"` or `"mistral"` — the endpoints that document the field. Other
143
+ * labels (Groq and DeepSeek cache automatically; custom gateways) never
144
+ * receive it.
144
145
  */
145
146
  promptCacheKey?: string;
146
147
  /**
@@ -177,7 +178,7 @@ type OpenAIModelConfig = ModelConfig & {
177
178
  * openai.embedder({ name: "text-embedding-3-small" });
178
179
  * openai.embedder({ name: "text-embedding-3-large", dimensions: 256 });
179
180
  */
180
- type OpenAIEmbedderConfig = EmbedderConfig;
181
+ export type OpenAIEmbedderConfig = EmbedderConfig;
181
182
  /**
182
183
  * Per-model configuration for `OpenAISDK.image()`. Mirrors the neutral
183
184
  * {@link ImageModelConfig} — `name` is the image model id, passed
@@ -190,7 +191,7 @@ type OpenAIEmbedderConfig = EmbedderConfig;
190
191
  * openai.image({ name: "gpt-image-1" });
191
192
  * openai.image({ name: "dall-e-3", pricing: { perImage: 0.04 } });
192
193
  */
193
- type OpenAIImageConfig = ImageModelConfig;
194
+ export type OpenAIImageConfig = ImageModelConfig;
194
195
  /**
195
196
  * Per-model configuration for `OpenAISDK.speech()`. Mirrors the neutral
196
197
  * {@link SpeechModelConfig} — `name` is the TTS model id, passed
@@ -202,7 +203,7 @@ type OpenAIImageConfig = ImageModelConfig;
202
203
  * @example
203
204
  * openai.speech({ name: "tts-1", voice: "alloy", pricing: { perMillionCharacters: 15 } });
204
205
  */
205
- type OpenAISpeechConfig = SpeechModelConfig;
206
+ export type OpenAISpeechConfig = SpeechModelConfig;
206
207
  /**
207
208
  * Per-model configuration for `OpenAISDK.transcribe()`. Mirrors the
208
209
  * neutral {@link TranscriptionModelConfig} — `name` is the STT model
@@ -214,7 +215,6 @@ type OpenAISpeechConfig = SpeechModelConfig;
214
215
  * @example
215
216
  * openai.transcribe({ name: "whisper-1", pricing: { perMinute: 0.006 } });
216
217
  */
217
- type OpenAITranscriptionConfig = TranscriptionModelConfig;
218
+ export type OpenAITranscriptionConfig = TranscriptionModelConfig;
218
219
  //#endregion
219
- export { OpenAIEmbedderConfig, OpenAIImageConfig, OpenAIModelConfig, OpenAISDKConfig, OpenAISpeechConfig, OpenAITranscriptionConfig };
220
220
  //# sourceMappingURL=config.type.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/config.type.ts"],"mappings":";;;;;AAyCA;;;;;;;;;;;;;;;;AAeuC;AAiBvC;;;;AAAgC;AAahC;;;;AAAqB;AAcrB;;KA3DY,eAAA,GAAkB,IAAA,CAAK,aAAA;EA2DH;;;;;;;;EAlD9B,QAAA;EAwDM;;;;;EAlDN,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;;AA4HpB;AAaP;;;;KAxHY,oBAAA;AAsIZ;;;;AAAgD;AAahD;;;;AAAkD;AAalD;AA1BA,KAzHY,SAAA;;;AAmJoD;;;;;;;;;;KArIpD,iBAAA,GAAoB,WAAA;;;;;;EAM9B,GAAA,GAAM,SAAA;;;;;;;;EAQN,MAAA;;;;;;;;;;;;;;EAcA,cAAA,GAAiB,oBAAA;;;;;;;;;EASjB,gBAAA;;;;;;;;;;;EAWA,SAAA;;;;;;EAMA,cAAA;;;;;;EAMA,oBAAA,GAAuB,OAAA,CACrB,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,0BAAA;;;;;;;;;EAW1B,GAAA;;;;;;;;EAQA,KAAA;AAAA;;;;;;;;;;;KAaU,oBAAA,GAAuB,cAAc;;;;;;;;;;;;;KAcrC,iBAAA,GAAoB,gBAAgB;;;;;;;;;;;;KAapC,kBAAA,GAAqB,iBAAiB;;;;;;;;;;;;KAatC,yBAAA,GAA4B,wBAAwB"}
1
+ {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/config.type.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAyCY,kBAAkB,KAAK;;;;;;;;;EASjC;;;;;;EAMA,UAAU,eAAe;;;;;;;;;;;;;;;;YAiBf;;;;;;;;;;;;YAaA;;;;;;;;;;;;;YAcA,oBAAoB;;;;;;EAM9B,MAAM;;;;;;;;EAQN;;;;;;;;;;;;;;EAcA,iBAAiB;;;;;;;;;EASjB;;;;;;;;;;;EAWA;;;;;;;;EAQA;;;;;;EAMA,uBAAuB,QACrB,OAAO,KAAK,YAAY;;;;;;;;;EAW1B;;;;;;;;EAQA;;;;;;;;;;;;YAaU,uBAAuB;;;;;;;;;;;;;YAcvB,oBAAoB;;;;;;;;;;;;YAapB,qBAAqB;;;;;;;;;;;;YAarB,4BAA4B"}
@@ -1,7 +1,6 @@
1
1
  import { OpenAIEmbedderConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { EmbedderContract, EmbeddingBatchResult, EmbeddingResult } from "@warlock.js/ai";
4
-
5
4
  //#region ../../packages/ai-openai/src/embedder.d.ts
6
5
  /**
7
6
  * OpenAI-backed implementation of `EmbedderContract`.
@@ -28,7 +27,7 @@ import { EmbedderContract, EmbeddingBatchResult, EmbeddingResult } from "@warloc
28
27
  * const { vector, dimensions, usage } = await embedder.embed("Hello world");
29
28
  * const { vectors } = await embedder.embedMany(["doc 1", "doc 2"]);
30
29
  */
31
- declare class OpenAIEmbedder implements EmbedderContract {
30
+ export declare class OpenAIEmbedder implements EmbedderContract {
32
31
  readonly name: string;
33
32
  readonly provider = "openai";
34
33
  dimensions: number;
@@ -52,5 +51,4 @@ declare class OpenAIEmbedder implements EmbedderContract {
52
51
  private request;
53
52
  }
54
53
  //#endregion
55
- export { OpenAIEmbedder };
56
54
  //# sourceMappingURL=embedder.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"mappings":";;;;;;;AA2CA;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,gBAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EACT,UAAA;EAAA,iBAEU,MAAA;EA8CJ;;;;;EAAA,iBAvCI,oBAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,oBAAA;EAO9B,KAAA,CAAM,KAAA,WAAgB,OAAA,CAAQ,eAAA;EA6B9B,SAAA,CAAU,MAAA,aAAmB,OAAA,CAAQ,oBAAA;;;;;;;UAgBpC,OAAA;AAAA"}
1
+ {"version":3,"file":"embedder.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAyCa,0BAA0B;WACrB;WACA;EACT;mBAEU;;;;;;mBAOA;mBACA;EAEjB,YAAmB,QAAQ,QAAQ,QAAQ;EAO9B,MAAM,gBAAgB,QAAQ;EA6B9B,UAAU,mBAAmB,QAAQ;;;;;;;UAgBpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"sourcesContent":["import {\n EmbeddingVectorCountMismatchError,\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIEmbedderConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\ntype EmbeddingsResponse = Awaited<\n ReturnType<OpenAI[\"embeddings\"][\"create\"]>\n>;\n\n/**\n * OpenAI-backed implementation of `EmbedderContract`.\n *\n * **Role.** Converts text (or a batch of texts) into floating-point\n * vectors via OpenAI's Embeddings API. Standalone primitive — no\n * relationship to chat completions, tools, or the agent loop.\n *\n * **Dimensions.** When no `dimensions` override is supplied in config,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached for all subsequent calls —\n * even if a later response were to return a different length, the\n * first value wins so batches stay dimensionally consistent. Passing\n * `dimensions` in config both forwards the truncation hint to the API\n * (for models like `text-embedding-3-*`) and sets the initial value.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the\n * typed `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError` —\n * callers catch `AIError` subclasses (`ProviderRateLimitError`,\n * `ProviderAuthError`, etc.) instead of OpenAI's own classes.\n *\n * @example\n * const embedder = new OpenAIEmbedder(client, { name: \"text-embedding-3-small\" });\n * const { vector, dimensions, usage } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class OpenAIEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider = \"openai\";\n public dimensions: number;\n\n private readonly client: OpenAI;\n\n /**\n * User-specified truncation hint, or `undefined` if omitted.\n * Forwarded to the API on every call so OpenAI can truncate the\n * embedding server-side for models that support it.\n */\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIEmbedderConfig) {\n this.client = client;\n this.name = config.name;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const { response, usage } = await this.request(input);\n\n const [first] = response.data;\n\n if (first === undefined) {\n // A 200 whose `data` array is empty. Before this guard the next line\n // read `.embedding` off `undefined` and the caller got a bare\n // `TypeError: Cannot read properties of undefined`, naming neither the\n // provider nor what it actually returned.\n //\n // Same typed error the RAG and skill-catalog paths raise for this\n // contract violation, so one shortfall has one name wherever it\n // surfaces. Expected 1, received 0.\n throw new EmbeddingVectorCountMismatchError({\n provider: this.provider,\n expectedCount: 1,\n receivedCount: response.data.length,\n record: input,\n });\n }\n\n return {\n vector: first.embedding,\n dimensions: this.dimensions,\n usage,\n };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const { response, usage } = await this.request(inputs);\n\n return {\n vectors: response.data.map((d) => d.embedding),\n dimensions: this.dimensions,\n usage,\n };\n }\n\n /**\n * Shared transport for both `embed()` and `embedMany()` — issues the\n * `embeddings.create` call, wraps provider errors, caches dimensions\n * on the first successful response, and returns the raw response\n * plus a camelCase usage object for the caller to shape.\n */\n private async request(input: string | string[]): Promise<{\n response: EmbeddingsResponse;\n usage: EmbeddingUsage;\n }> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embeddings.create\", {\n model: this.name,\n batch: Array.isArray(input),\n count: Array.isArray(input) ? input.length : 1,\n });\n\n let response: EmbeddingsResponse;\n\n try {\n response = await this.client.embeddings.create({\n model: this.name,\n input,\n ...(this.configuredDimensions !== undefined\n ? { dimensions: this.configuredDimensions }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embeddings.create returned\", {\n dimensions: response.data[0]?.embedding.length,\n usage: {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n },\n });\n\n // Cache dimensions on the first response. Once set, stays set —\n // we trust the first call to define the shape for this embedder.\n //\n // Reads the first entry optionally rather than asserting it: this runs for\n // BOTH embed() and embedMany(), and an empty `data` array is a real\n // provider response, not an impossible one. Leaving `dimensions` at 0 for\n // an empty response is correct — the callers each raise their own typed\n // error about the shortfall, and cacheing a dimension off a response that\n // carried no vectors would be inventing a shape from nothing.\n if (this.dimensions === 0) {\n const firstEmbedding = response.data[0]?.embedding;\n\n if (firstEmbedding !== undefined) {\n this.dimensions = firstEmbedding.length;\n }\n }\n\n const usage: EmbeddingUsage = {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n };\n\n return { response, usage };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnB,IAAa,iBAAb,MAAwD;CAetD,AAAO,YAAY,QAAgB,QAA8B;kBAbtC;gBAWO;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,uBAAuB,OAAO;EACnC,KAAK,aAAa,OAAO,cAAc;CACzC;CAEA,MAAa,MAAM,OAAyC;EAC1D,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,KAAK;EAEpD,MAAM,CAAC,SAAS,SAAS;EAEzB,IAAI,UAAU,QASZ,MAAM,IAAI,kCAAkC;GAC1C,UAAU,KAAK;GACf,eAAe;GACf,eAAe,SAAS,KAAK;GAC7B,QAAQ;EACV,CAAC;EAGH,OAAO;GACL,QAAQ,MAAM;GACd,YAAY,KAAK;GACjB;EACF;CACF;CAEA,MAAa,UAAU,QAAiD;EACtE,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,MAAM;EAErD,OAAO;GACL,SAAS,SAAS,KAAK,KAAK,MAAM,EAAE,SAAS;GAC7C,YAAY,KAAK;GACjB;EACF;CACF;;;;;;;CAQA,MAAc,QAAQ,OAGnB;EACD,KAAK,OAAO,MAAM,YAAY,oBAAoB,qBAAqB;GACrE,OAAO,KAAK;GACZ,OAAO,MAAM,QAAQ,KAAK;GAC1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;EAC/C,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,WAAW,OAAO;IAC7C,OAAO,KAAK;IACZ;IACA,GAAI,KAAK,yBAAyB,SAC9B,EAAE,YAAY,KAAK,qBAAqB,IACxC,CAAC;GACP,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,kBAAkB,QAAQ,SAAS;IAC/D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,KAAK,OAAO,MAAM,YAAY,qBAAqB,8BAA8B;GAC/E,YAAY,SAAS,KAAK,EAAE,EAAE,UAAU;GACxC,OAAO;IACL,cAAc,SAAS,MAAM;IAC7B,aAAa,SAAS,MAAM;GAC9B;EACF,CAAC;EAWD,IAAI,KAAK,eAAe,GAAG;GACzB,MAAM,iBAAiB,SAAS,KAAK,EAAE,EAAE;GAEzC,IAAI,mBAAmB,QACrB,KAAK,aAAa,eAAe;EAErC;EAEA,MAAM,QAAwB;GAC5B,cAAc,SAAS,MAAM;GAC7B,aAAa,SAAS,MAAM;EAC9B;EAEA,OAAO;GAAE;GAAU;EAAM;CAC3B;AACF"}
1
+ {"version":3,"file":"embedder.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/embedder.ts"],"sourcesContent":["import {\n EmbeddingVectorCountMismatchError,\n type EmbeddingBatchResult,\n type EmbeddingResult,\n type EmbeddingUsage,\n type EmbedderContract,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type OpenAI from \"openai\";\nimport type { OpenAIEmbedderConfig } from \"./config.type\";\nimport { wrapOpenAIError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.openai\";\n\ntype EmbeddingsResponse = Awaited<ReturnType<OpenAI[\"embeddings\"][\"create\"]>>;\n\n/**\n * OpenAI-backed implementation of `EmbedderContract`.\n *\n * **Role.** Converts text (or a batch of texts) into floating-point\n * vectors via OpenAI's Embeddings API. Standalone primitive — no\n * relationship to chat completions, tools, or the agent loop.\n *\n * **Dimensions.** When no `dimensions` override is supplied in config,\n * `this.dimensions` starts at `0` and is populated from the first\n * response's vector length, then cached for all subsequent calls —\n * even if a later response were to return a different length, the\n * first value wins so batches stay dimensionally consistent. Passing\n * `dimensions` in config both forwards the truncation hint to the API\n * (for models like `text-embedding-3-*`) and sets the initial value.\n *\n * **Error handling.** Raw OpenAI SDK errors are wrapped into the\n * typed `@warlock.js/ai` `AIError` hierarchy via `wrapOpenAIError` —\n * callers catch `AIError` subclasses (`ProviderRateLimitError`,\n * `ProviderAuthError`, etc.) instead of OpenAI's own classes.\n *\n * @example\n * const embedder = new OpenAIEmbedder(client, { name: \"text-embedding-3-small\" });\n * const { vector, dimensions, usage } = await embedder.embed(\"Hello world\");\n * const { vectors } = await embedder.embedMany([\"doc 1\", \"doc 2\"]);\n */\nexport class OpenAIEmbedder implements EmbedderContract {\n public readonly name: string;\n public readonly provider = \"openai\";\n public dimensions: number;\n\n private readonly client: OpenAI;\n\n /**\n * User-specified truncation hint, or `undefined` if omitted.\n * Forwarded to the API on every call so OpenAI can truncate the\n * embedding server-side for models that support it.\n */\n private readonly configuredDimensions: number | undefined;\n private readonly logger: Logger = log;\n\n public constructor(client: OpenAI, config: OpenAIEmbedderConfig) {\n this.client = client;\n this.name = config.name;\n this.configuredDimensions = config.dimensions;\n this.dimensions = config.dimensions ?? 0;\n }\n\n public async embed(input: string): Promise<EmbeddingResult> {\n const { response, usage } = await this.request(input);\n\n const [first] = response.data;\n\n if (first === undefined) {\n // A 200 whose `data` array is empty. Before this guard the next line\n // read `.embedding` off `undefined` and the caller got a bare\n // `TypeError: Cannot read properties of undefined`, naming neither the\n // provider nor what it actually returned.\n //\n // Same typed error the RAG and skill-catalog paths raise for this\n // contract violation, so one shortfall has one name wherever it\n // surfaces. Expected 1, received 0.\n throw new EmbeddingVectorCountMismatchError({\n provider: this.provider,\n expectedCount: 1,\n receivedCount: response.data.length,\n record: input,\n });\n }\n\n return {\n vector: first.embedding,\n dimensions: this.dimensions,\n usage,\n };\n }\n\n public async embedMany(inputs: string[]): Promise<EmbeddingBatchResult> {\n const { response, usage } = await this.request(inputs);\n\n return {\n vectors: response.data.map((d) => d.embedding),\n dimensions: this.dimensions,\n usage,\n };\n }\n\n /**\n * Shared transport for both `embed()` and `embedMany()` — issues the\n * `embeddings.create` call, wraps provider errors, caches dimensions\n * on the first successful response, and returns the raw response\n * plus a camelCase usage object for the caller to shape.\n */\n private async request(input: string | string[]): Promise<{\n response: EmbeddingsResponse;\n usage: EmbeddingUsage;\n }> {\n this.logger.debug(LOG_MODULE, \"embedder.request\", \"embeddings.create\", {\n model: this.name,\n batch: Array.isArray(input),\n count: Array.isArray(input) ? input.length : 1,\n });\n\n let response: EmbeddingsResponse;\n\n try {\n response = await this.client.embeddings.create({\n model: this.name,\n input,\n ...(this.configuredDimensions !== undefined\n ? { dimensions: this.configuredDimensions }\n : {}),\n });\n } catch (thrown) {\n const wrapped = wrapOpenAIError(thrown);\n\n this.logger.error(LOG_MODULE, \"embedder.error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n throw wrapped;\n }\n\n this.logger.debug(LOG_MODULE, \"embedder.response\", \"embeddings.create returned\", {\n dimensions: response.data[0]?.embedding.length,\n usage: {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n },\n });\n\n // Cache dimensions on the first response. Once set, stays set —\n // we trust the first call to define the shape for this embedder.\n //\n // Reads the first entry optionally rather than asserting it: this runs for\n // BOTH embed() and embedMany(), and an empty `data` array is a real\n // provider response, not an impossible one. Leaving `dimensions` at 0 for\n // an empty response is correct — the callers each raise their own typed\n // error about the shortfall, and cacheing a dimension off a response that\n // carried no vectors would be inventing a shape from nothing.\n if (this.dimensions === 0) {\n const firstEmbedding = response.data[0]?.embedding;\n\n if (firstEmbedding !== undefined) {\n this.dimensions = firstEmbedding.length;\n }\n }\n\n const usage: EmbeddingUsage = {\n promptTokens: response.usage.prompt_tokens,\n totalTokens: response.usage.total_tokens,\n };\n\n return { response, usage };\n }\n}\n"],"mappings":";;;;;;AAYA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BnB,IAAa,iBAAb,MAAwD;CAetD,AAAO,YAAY,QAAgB,QAA8B;kBAbtC;gBAWO;EAGhC,KAAK,SAAS;EACd,KAAK,OAAO,OAAO;EACnB,KAAK,uBAAuB,OAAO;EACnC,KAAK,aAAa,OAAO,cAAc;CACzC;CAEA,MAAa,MAAM,OAAyC;EAC1D,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,KAAK;EAEpD,MAAM,CAAC,SAAS,SAAS;EAEzB,IAAI,UAAU,QASZ,MAAM,IAAI,kCAAkC;GAC1C,UAAU,KAAK;GACf,eAAe;GACf,eAAe,SAAS,KAAK;GAC7B,QAAQ;EACV,CAAC;EAGH,OAAO;GACL,QAAQ,MAAM;GACd,YAAY,KAAK;GACjB;EACF;CACF;CAEA,MAAa,UAAU,QAAiD;EACtE,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK,QAAQ,MAAM;EAErD,OAAO;GACL,SAAS,SAAS,KAAK,KAAK,MAAM,EAAE,SAAS;GAC7C,YAAY,KAAK;GACjB;EACF;CACF;;;;;;;CAQA,MAAc,QAAQ,OAGnB;EACD,KAAK,OAAO,MAAM,YAAY,oBAAoB,qBAAqB;GACrE,OAAO,KAAK;GACZ,OAAO,MAAM,QAAQ,KAAK;GAC1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;EAC/C,CAAC;EAED,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,KAAK,OAAO,WAAW,OAAO;IAC7C,OAAO,KAAK;IACZ;IACA,GAAI,KAAK,yBAAyB,SAC9B,EAAE,YAAY,KAAK,qBAAqB,IACxC,CAAC;GACP,CAAC;EACH,SAAS,QAAQ;GACf,MAAM,UAAU,gBAAgB,MAAM;GAEtC,KAAK,OAAO,MAAM,YAAY,kBAAkB,QAAQ,SAAS;IAC/D,MAAM,QAAQ;IACd,SAAS,QAAQ;GACnB,CAAC;GAED,MAAM;EACR;EAEA,KAAK,OAAO,MAAM,YAAY,qBAAqB,8BAA8B;GAC/E,YAAY,SAAS,KAAK,EAAE,EAAE,UAAU;GACxC,OAAO;IACL,cAAc,SAAS,MAAM;IAC7B,aAAa,SAAS,MAAM;GAC9B;EACF,CAAC;EAWD,IAAI,KAAK,eAAe,GAAG;GACzB,MAAM,iBAAiB,SAAS,KAAK,EAAE,EAAE;GAEzC,IAAI,mBAAmB,QACrB,KAAK,aAAa,eAAe;EAErC;EAEA,MAAM,QAAwB;GAC5B,cAAc,SAAS,MAAM;GAC7B,aAAa,SAAS,MAAM;EAC9B;EAEA,OAAO;GAAE;GAAU;EAAM;CAC3B;AACF"}
package/esm/image.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { OpenAIImageConfig } from "./config.type.mjs";
2
2
  import OpenAI from "openai";
3
3
  import { ImageGenerationOptions, ImageGenerationResponse, ImageModelContract, ImageModelPricing } from "@warlock.js/ai";
4
-
5
4
  //#region ../../packages/ai-openai/src/image.d.ts
6
5
  /**
7
6
  * OpenAI-backed implementation of `ImageModelContract`.
@@ -31,7 +30,7 @@ import { ImageGenerationOptions, ImageGenerationResponse, ImageModelContract, Im
31
30
  * const model = new OpenAIImageModel(client, { name: "gpt-image-1" }, "openai");
32
31
  * const { images, usage } = await model.generate("a teal ceramic mug, studio light");
33
32
  */
34
- declare class OpenAIImageModel implements ImageModelContract {
33
+ export declare class OpenAIImageModel implements ImageModelContract {
35
34
  readonly name: string;
36
35
  readonly provider: string;
37
36
  readonly pricing?: ImageModelPricing;
@@ -49,5 +48,4 @@ declare class OpenAIImageModel implements ImageModelContract {
49
48
  private toGeneratedImage;
50
49
  }
51
50
  //#endregion
52
- export { OpenAIImageModel };
53
51
  //# sourceMappingURL=image.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/image.ts"],"mappings":";;;;;;;AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,kBAAA;EAAA,SACvB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA,GAAU,iBAAA;EAAA,iBAET,MAAA;EAAA,iBACA,MAAA;cAEE,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,iBAAA,EAAmB,QAAA;EAOjD,QAAA,CACX,MAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA,CAAQ,uBAAA;;;;;;;;UA+EH,gBAAA;AAAA"}
1
+ {"version":3,"file":"image.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/image.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwDa,4BAA4B;WACvB;WACA;WACA,UAAU;mBAET;mBACA;EAEjB,YAAmB,QAAQ,QAAQ,QAAQ,mBAAmB;EAOjD,SACX,gBACA,UAAU,yBACT,QAAQ;;;;;;;;UA+EH"}
package/esm/index.d.mts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { OpenAIEmbedderConfig, OpenAIImageConfig, OpenAISDKConfig, OpenAISpeechConfig, OpenAITranscriptionConfig } from "./config.type.mjs";
2
2
  import { OpenAISDK } from "./sdk.mjs";
3
+ import { OPENAI_MODEL_PRICING, OPENAI_SERVER_TOOL_PRICING, openAIPricingFor } from "./known-pricing.mjs";
3
4
  import { OpenAIEmbedder } from "./embedder.mjs";
4
5
  import { OpenAIImageModel } from "./image.mjs";
5
6
  import { OpenAISpeechModel } from "./speech.mjs";
6
7
  import { OpenAITranscriptionModel } from "./transcription.mjs";
7
- export { OpenAIEmbedder, type OpenAIEmbedderConfig, type OpenAIImageConfig, OpenAIImageModel, OpenAISDK, type OpenAISDKConfig, type OpenAISpeechConfig, OpenAISpeechModel, type OpenAITranscriptionConfig, OpenAITranscriptionModel };
8
+ import { OpenAICallOptions, QwenCallOptions } from "./provider-options.mjs";
9
+ import { KIMI_SERVER_TOOL_PRICING, KimiFetchOutput, KimiSearchOutput, KimiSearchResult, KimiSearchToolOptions, KimiToolOptions, kimiFetchTool, kimiSearchTool } from "./kimi-tools.mjs";
10
+ export { KIMI_SERVER_TOOL_PRICING, type KimiFetchOutput, type KimiSearchOutput, type KimiSearchResult, type KimiSearchToolOptions, type KimiToolOptions, OPENAI_MODEL_PRICING, OPENAI_SERVER_TOOL_PRICING, type OpenAICallOptions, OpenAIEmbedder, type OpenAIEmbedderConfig, type OpenAIImageConfig, OpenAIImageModel, OpenAISDK, type OpenAISDKConfig, type OpenAISpeechConfig, OpenAISpeechModel, type OpenAITranscriptionConfig, OpenAITranscriptionModel, type QwenCallOptions, kimiFetchTool, kimiSearchTool, openAIPricingFor };
package/esm/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
+ import { OPENAI_MODEL_PRICING, OPENAI_SERVER_TOOL_PRICING, openAIPricingFor } from "./known-pricing.mjs";
1
2
  import { OpenAIEmbedder } from "./embedder.mjs";
2
3
  import { OpenAIImageModel } from "./image.mjs";
3
4
  import { OpenAISpeechModel } from "./speech.mjs";
4
5
  import { OpenAITranscriptionModel } from "./transcription.mjs";
5
6
  import { OpenAISDK } from "./sdk.mjs";
7
+ import { KIMI_SERVER_TOOL_PRICING, kimiFetchTool, kimiSearchTool } from "./kimi-tools.mjs";
6
8
 
7
- export { OpenAIEmbedder, OpenAIImageModel, OpenAISDK, OpenAISpeechModel, OpenAITranscriptionModel };
9
+ export { KIMI_SERVER_TOOL_PRICING, OPENAI_MODEL_PRICING, OPENAI_SERVER_TOOL_PRICING, OpenAIEmbedder, OpenAIImageModel, OpenAISDK, OpenAISpeechModel, OpenAITranscriptionModel, kimiFetchTool, kimiSearchTool, openAIPricingFor };
@@ -0,0 +1,69 @@
1
+ import { ClientOptions } from "openai";
2
+ import { ServerToolPricing, ToolContract } from "@warlock.js/ai";
3
+ //#region ../../packages/ai-openai/src/kimi-tools.d.ts
4
+ /** USD per billable REST request, verified 2026-10-18:
5
+ * https://platform.kimi.ai/docs/pricing/websearch
6
+ * HTTP 200 + nonempty search_results / nonblank markdown only. Taxes excluded.
7
+ * Namespaced rates avoid charging the generic counters a second time. */
8
+ export declare const KIMI_SERVER_TOOL_PRICING: Readonly<ServerToolPricing>;
9
+ export type KimiToolOptions = Pick<ClientOptions, "baseURL" | "fetch" | "timeout" | "defaultHeaders"> & {
10
+ apiKey: NonNullable<ClientOptions["apiKey"]>;
11
+ /** Total snippet / page / chunk characters returned to the model. Default 12,000. */
12
+ maxContentChars?: number;
13
+ /** Per-request USD rates keyed like KIMI_SERVER_TOOL_PRICING. {} leaves calls unpriced. */
14
+ pricing?: ServerToolPricing;
15
+ };
16
+ export type KimiSearchToolOptions = KimiToolOptions & {
17
+ /** Default "basic". Pro returns ranked passages and supports site/date constraints. */
18
+ mode?: "basic" | "pro";
19
+ /** 1–20, default 5. */
20
+ limit?: number;
21
+ /** 1–60 seconds, default 30. */
22
+ timeoutSeconds?: number;
23
+ /** Basic only; default false. */
24
+ includeContent?: boolean;
25
+ /** Pro only; at most five sites, combined with OR. */
26
+ sites?: string[];
27
+ /** Pro only; dates accept YYYY, YYYY-MM or YYYY-MM-DD (validated by Kimi). */
28
+ timeWindow?: {
29
+ start?: string;
30
+ end?: string;
31
+ };
32
+ };
33
+ export type KimiSearchResult = {
34
+ title: string;
35
+ url: string;
36
+ snippet: string;
37
+ siteName: string;
38
+ date: string;
39
+ authority: string;
40
+ text?: string;
41
+ chunks?: {
42
+ text: string;
43
+ score: number;
44
+ }[];
45
+ };
46
+ export type KimiSearchOutput = {
47
+ results: KimiSearchResult[];
48
+ truncated: boolean;
49
+ };
50
+ export type KimiFetchOutput = {
51
+ url: string;
52
+ title: string;
53
+ markdown: string;
54
+ truncated: boolean;
55
+ };
56
+ /** Standalone first-party REST search, registered as an ordinary client function tool.
57
+ * Wire contract: https://platform.kimi.ai/docs/api/tools-search and tools-search-pro.
58
+ * Reuse the API key / baseURL configuration passed to OpenAISDK; the default URL
59
+ * here is Moonshot, independent of the model used by the agent. */
60
+ export declare function kimiSearchTool(options: KimiSearchToolOptions): ToolContract<{
61
+ query: string;
62
+ }, KimiSearchOutput>;
63
+ /** First-party URL-to-Markdown REST tool.
64
+ * https://platform.kimi.ai/docs/api/tools-fetch — only http/https inputs. */
65
+ export declare function kimiFetchTool(options: KimiToolOptions): ToolContract<{
66
+ url: string;
67
+ }, KimiFetchOutput>;
68
+ //#endregion
69
+ //# sourceMappingURL=kimi-tools.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kimi-tools.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/kimi-tools.ts"],"mappings":";;;;;;;qBAuBa,0BAA0B,SAAS;YAMpC,kBAAkB,KAC5B;EAGA,QAAQ,YAAY;;EAEpB;;EAEA,UAAU;;YAGA,wBAAwB;;EAElC;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;IAAe;IAAgB;;;YAGrB;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IAAW;IAAc;;;YAEf;EAAqB,SAAS;EAAoB;;YAClD;EAAoB;EAAa;EAAe;EAAkB;;;;;;wBAQ9D,eACd,SAAS,wBACR;EAAe;GAAiB;;;wBA2FnB,cACd,SAAS,kBACR;EAAe;GAAe"}
@@ -0,0 +1,256 @@
1
+ import { wrapOpenAIError } from "./utils/wrap-openai-error.mjs";
2
+ import OpenAI, { APIError } from "openai";
3
+ import { AgentCancelledError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError, computeCost, tool } from "@warlock.js/ai";
4
+
5
+ //#region ../../packages/ai-openai/src/kimi-tools.ts
6
+ /** USD per billable REST request, verified 2026-10-18:
7
+ * https://platform.kimi.ai/docs/pricing/websearch
8
+ * HTTP 200 + nonempty search_results / nonblank markdown only. Taxes excluded.
9
+ * Namespaced rates avoid charging the generic counters a second time. */
10
+ const KIMI_SERVER_TOOL_PRICING = Object.freeze({
11
+ kimiSearchRequests: .002,
12
+ kimiSearchProRequests: .003,
13
+ kimiFetchRequests: .002
14
+ });
15
+ /** Standalone first-party REST search, registered as an ordinary client function tool.
16
+ * Wire contract: https://platform.kimi.ai/docs/api/tools-search and tools-search-pro.
17
+ * Reuse the API key / baseURL configuration passed to OpenAISDK; the default URL
18
+ * here is Moonshot, independent of the model used by the agent. */
19
+ function kimiSearchTool(options) {
20
+ const mode = options.mode ?? "basic";
21
+ if (mode !== "basic" && mode !== "pro") throw new InvalidRequestError("Invalid Kimi search mode.");
22
+ const limit = integer(options.limit ?? 5, 1, 20, "limit");
23
+ const timeout = integer(options.timeoutSeconds ?? 30, 1, 60, "timeoutSeconds");
24
+ if (mode === "basic" && (options.sites !== void 0 || options.timeWindow !== void 0)) throw new InvalidRequestError("Kimi sites and timeWindow require mode: pro.");
25
+ if (mode === "pro" && options.includeContent !== void 0) throw new InvalidRequestError("Kimi includeContent is available only in basic mode.");
26
+ if (options.sites && (options.sites.length > 5 || options.sites.some((site) => !site || /[\s()]/u.test(site)))) throw new InvalidRequestError("Kimi sites accepts at most five nonempty sites without whitespace or parentheses.");
27
+ const maxChars = contentLimit(options);
28
+ const client = createClient(options);
29
+ const counter = mode === "pro" ? "kimiSearchProRequests" : "kimiSearchRequests";
30
+ return meteredTool({
31
+ name: mode === "pro" ? "kimi_search_pro" : "kimi_search",
32
+ description: "Search the web with Kimi and return cited sources and relevant text.",
33
+ input: stringInput("query"),
34
+ async execute({ query }, ctx, record) {
35
+ const body = {
36
+ text_query: query,
37
+ limit,
38
+ timeout_seconds: timeout,
39
+ ...mode === "basic" ? { include_content: options.includeContent ?? false } : {
40
+ ...options.sites !== void 0 ? { sites: options.sites } : {},
41
+ ...options.timeWindow !== void 0 ? { time_window: options.timeWindow } : {}
42
+ }
43
+ };
44
+ const raw = await request(client, mode === "pro" ? "/tools/search_pro" : "/tools/search", body, ctx);
45
+ if (!isObject(raw.data) || !Array.isArray(raw.data.search_results)) throw new ProviderError("Kimi returned invalid search_results.");
46
+ record(raw.status === 200 && raw.data.search_results.length > 0);
47
+ const budget = textBudget(maxChars);
48
+ return {
49
+ results: raw.data.search_results.slice(0, limit).map((value) => {
50
+ if (!isObject(value)) throw new ProviderError("Kimi returned an invalid search result.");
51
+ const result = {
52
+ title: stringValue(value.title),
53
+ url: stringValue(value.url),
54
+ snippet: budget.take(stringValue(value.snippet)),
55
+ siteName: stringValue(value.site_name),
56
+ date: stringValue(value.date),
57
+ authority: stringValue(value.authority)
58
+ };
59
+ if (mode === "basic" && options.includeContent) result.text = budget.take(stringValue(value.text));
60
+ if (mode === "pro") {
61
+ if (!Array.isArray(value.chunks)) throw new ProviderError("Kimi returned invalid search chunks.");
62
+ result.chunks = value.chunks.map((chunk) => {
63
+ if (!isObject(chunk) || typeof chunk.score !== "number" || !Number.isFinite(chunk.score)) throw new ProviderError("Kimi returned an invalid search chunk.");
64
+ return {
65
+ text: budget.take(stringValue(chunk.text)),
66
+ score: chunk.score
67
+ };
68
+ }).filter((chunk) => chunk.text.length > 0);
69
+ }
70
+ return result;
71
+ }),
72
+ truncated: budget.truncated || raw.data.search_results.length > limit
73
+ };
74
+ }
75
+ }, counter, options.pricing);
76
+ }
77
+ /** First-party URL-to-Markdown REST tool.
78
+ * https://platform.kimi.ai/docs/api/tools-fetch — only http/https inputs. */
79
+ function kimiFetchTool(options) {
80
+ const client = createClient(options);
81
+ const maxChars = contentLimit(options);
82
+ return meteredTool({
83
+ name: "kimi_fetch",
84
+ description: "Read a web page with Kimi and return its title, URL and Markdown body.",
85
+ input: stringInput("url"),
86
+ async execute({ url }, ctx, record) {
87
+ let parsed;
88
+ try {
89
+ parsed = new URL(url);
90
+ } catch {
91
+ throw new InvalidRequestError("Kimi fetch requires an http/https URL.");
92
+ }
93
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new InvalidRequestError("Kimi fetch requires an http/https URL.");
94
+ const raw = await request(client, "/tools/fetch", { url }, ctx);
95
+ if (!isObject(raw.data) || typeof raw.data.markdown !== "string") throw new ProviderError("Kimi returned invalid fetch content.");
96
+ record(raw.status === 200 && raw.data.markdown.trim().length > 0);
97
+ return {
98
+ url: stringValue(raw.data.url),
99
+ title: stringValue(raw.data.title),
100
+ markdown: raw.data.markdown.slice(0, maxChars),
101
+ truncated: raw.data.markdown.length > maxChars
102
+ };
103
+ }
104
+ }, "kimiFetchRequests", options.pricing);
105
+ }
106
+ function createClient(options) {
107
+ const { apiKey, baseURL, fetch, timeout, defaultHeaders } = options;
108
+ if (!apiKey) throw new ProviderAuthError("Kimi tools require an explicitly configured API key.");
109
+ return new OpenAI({
110
+ apiKey,
111
+ baseURL: baseURL ?? "https://api.moonshot.ai/v1",
112
+ fetch,
113
+ timeout,
114
+ defaultHeaders,
115
+ maxRetries: 0
116
+ });
117
+ }
118
+ async function request(client, path, body, ctx) {
119
+ try {
120
+ ctx?.signal?.throwIfAborted();
121
+ const response = await client.post(path, {
122
+ body,
123
+ signal: ctx?.signal
124
+ }).withResponse();
125
+ return {
126
+ data: response.data,
127
+ status: response.response.status
128
+ };
129
+ } catch (thrown) {
130
+ if (ctx?.signal?.aborted) throw new AgentCancelledError("Kimi tool request cancelled.", { cause: thrown });
131
+ if (thrown instanceof APIError) {
132
+ const context = {
133
+ status: thrown.status,
134
+ type: thrown.type,
135
+ requestId: thrown.headers?.get("x-msh-track-id")
136
+ };
137
+ if (thrown.status === 408 || thrown.status === 504) throw new ProviderTimeoutError(thrown.message, {
138
+ cause: thrown,
139
+ context
140
+ });
141
+ if (thrown.status === 429) {
142
+ const retry = Number(thrown.headers?.get("retry-after") ?? NaN);
143
+ throw new ProviderRateLimitError(thrown.message, {
144
+ cause: thrown,
145
+ context,
146
+ retryAfter: Number.isFinite(retry) && retry >= 0 ? retry * 1e3 : void 0
147
+ });
148
+ }
149
+ if (thrown.status === 403 && !thrown.type) throw new ProviderAuthError(thrown.message, {
150
+ cause: thrown,
151
+ context
152
+ });
153
+ }
154
+ throw wrapOpenAIError(thrown);
155
+ }
156
+ }
157
+ /** Each invoke owns its ledger, including concurrent calls and post-response failures.
158
+ * ToolContract.invoke is the existing accounting boundary used by agents/reports. */
159
+ function meteredTool(config, counter, prices = KIMI_SERVER_TOOL_PRICING) {
160
+ const rate = prices[counter];
161
+ if (rate !== void 0 && (!Number.isFinite(rate) || rate < 0)) throw new InvalidRequestError("Kimi per-call pricing must be finite and nonnegative.");
162
+ return {
163
+ ...tool({
164
+ ...config,
165
+ execute: (input, ctx) => config.execute(input, ctx, () => {})
166
+ }),
167
+ async invoke(input, ctx) {
168
+ let usage = {
169
+ input: 0,
170
+ output: 0,
171
+ total: 0
172
+ };
173
+ const result = await tool({
174
+ ...config,
175
+ execute: (validated, context) => config.execute(validated, context, (billable) => {
176
+ const count = billable ? 1 : 0;
177
+ usage = {
178
+ input: 0,
179
+ output: 0,
180
+ total: 0,
181
+ serverToolUse: {
182
+ [counter]: count,
183
+ [counter === "kimiFetchRequests" ? "webFetchRequests" : "webSearchRequests"]: count
184
+ }
185
+ };
186
+ if (rate !== void 0) usage.cost = computeCost(usage, {
187
+ input: 0,
188
+ output: 0,
189
+ serverTools: { [counter]: rate }
190
+ });
191
+ })
192
+ }).invoke(input, ctx);
193
+ return {
194
+ ...result,
195
+ usage,
196
+ report: {
197
+ ...result.report,
198
+ usage
199
+ }
200
+ };
201
+ }
202
+ };
203
+ }
204
+ function integer(value, min, max, name) {
205
+ if (!Number.isInteger(value) || value < min || value > max) throw new InvalidRequestError(`Kimi ${name} must be an integer from ${min} to ${max}.`);
206
+ return value;
207
+ }
208
+ function contentLimit(options) {
209
+ return integer(options.maxContentChars ?? 12e3, 1, Number.MAX_SAFE_INTEGER, "maxContentChars");
210
+ }
211
+ function isObject(value) {
212
+ return typeof value === "object" && value !== null && !Array.isArray(value);
213
+ }
214
+ function stringValue(value) {
215
+ if (typeof value !== "string") throw new ProviderError("Kimi returned an invalid text field.");
216
+ return value;
217
+ }
218
+ function textBudget(max) {
219
+ return {
220
+ truncated: false,
221
+ take(text) {
222
+ const result = text.slice(0, max);
223
+ if (text.length > max) this.truncated = true;
224
+ max -= result.length;
225
+ return result;
226
+ }
227
+ };
228
+ }
229
+ function stringInput(key) {
230
+ return {
231
+ jsonSchema: {
232
+ type: "object",
233
+ properties: { [key]: {
234
+ type: "string",
235
+ minLength: 1
236
+ } },
237
+ required: [key],
238
+ additionalProperties: false
239
+ },
240
+ "~standard": {
241
+ version: 1,
242
+ vendor: "kimi",
243
+ validate(value) {
244
+ if (!isObject(value) || typeof value[key] !== "string" || !value[key].trim()) return { issues: [{
245
+ message: `${key} must be a nonempty string`,
246
+ path: [key]
247
+ }] };
248
+ return { value: { [key]: value[key] } };
249
+ }
250
+ }
251
+ };
252
+ }
253
+
254
+ //#endregion
255
+ export { KIMI_SERVER_TOOL_PRICING, kimiFetchTool, kimiSearchTool };
256
+ //# sourceMappingURL=kimi-tools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kimi-tools.mjs","names":[],"sources":["../../../../../../../packages/ai-openai/src/kimi-tools.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport {\n AgentCancelledError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n computeCost,\n tool,\n type ServerToolPricing,\n type ToolConfig,\n type ToolContext,\n type ToolContract,\n type Usage,\n} from \"@warlock.js/ai\";\nimport OpenAI, { APIError, type ClientOptions } from \"openai\";\nimport { wrapOpenAIError } from \"./utils/wrap-openai-error\";\n\n/** USD per billable REST request, verified 2026-10-18:\n * https://platform.kimi.ai/docs/pricing/websearch\n * HTTP 200 + nonempty search_results / nonblank markdown only. Taxes excluded.\n * Namespaced rates avoid charging the generic counters a second time. */\nexport const KIMI_SERVER_TOOL_PRICING: Readonly<ServerToolPricing> = Object.freeze({\n kimiSearchRequests: 0.002,\n kimiSearchProRequests: 0.003,\n kimiFetchRequests: 0.002,\n});\n\nexport type KimiToolOptions = Pick<\n ClientOptions,\n \"baseURL\" | \"fetch\" | \"timeout\" | \"defaultHeaders\"\n> & {\n apiKey: NonNullable<ClientOptions[\"apiKey\"]>;\n /** Total snippet / page / chunk characters returned to the model. Default 12,000. */\n maxContentChars?: number;\n /** Per-request USD rates keyed like KIMI_SERVER_TOOL_PRICING. {} leaves calls unpriced. */\n pricing?: ServerToolPricing;\n};\n\nexport type KimiSearchToolOptions = KimiToolOptions & {\n /** Default \"basic\". Pro returns ranked passages and supports site/date constraints. */\n mode?: \"basic\" | \"pro\";\n /** 1–20, default 5. */\n limit?: number;\n /** 1–60 seconds, default 30. */\n timeoutSeconds?: number;\n /** Basic only; default false. */\n includeContent?: boolean;\n /** Pro only; at most five sites, combined with OR. */\n sites?: string[];\n /** Pro only; dates accept YYYY, YYYY-MM or YYYY-MM-DD (validated by Kimi). */\n timeWindow?: { start?: string; end?: string };\n};\n\nexport type KimiSearchResult = {\n title: string;\n url: string;\n snippet: string;\n siteName: string;\n date: string;\n authority: string;\n text?: string;\n chunks?: { text: string; score: number }[];\n};\nexport type KimiSearchOutput = { results: KimiSearchResult[]; truncated: boolean };\nexport type KimiFetchOutput = { url: string; title: string; markdown: string; truncated: boolean };\n\ntype Counter = \"kimiSearchRequests\" | \"kimiSearchProRequests\" | \"kimiFetchRequests\";\n\n/** Standalone first-party REST search, registered as an ordinary client function tool.\n * Wire contract: https://platform.kimi.ai/docs/api/tools-search and tools-search-pro.\n * Reuse the API key / baseURL configuration passed to OpenAISDK; the default URL\n * here is Moonshot, independent of the model used by the agent. */\nexport function kimiSearchTool(\n options: KimiSearchToolOptions,\n): ToolContract<{ query: string }, KimiSearchOutput> {\n const mode = options.mode ?? \"basic\";\n if (mode !== \"basic\" && mode !== \"pro\")\n throw new InvalidRequestError(\"Invalid Kimi search mode.\");\n const limit = integer(options.limit ?? 5, 1, 20, \"limit\");\n const timeout = integer(options.timeoutSeconds ?? 30, 1, 60, \"timeoutSeconds\");\n if (mode === \"basic\" && (options.sites !== undefined || options.timeWindow !== undefined)) {\n throw new InvalidRequestError(\"Kimi sites and timeWindow require mode: pro.\");\n }\n if (mode === \"pro\" && options.includeContent !== undefined) {\n throw new InvalidRequestError(\"Kimi includeContent is available only in basic mode.\");\n }\n if (\n options.sites &&\n (options.sites.length > 5 || options.sites.some((site) => !site || /[\\s()]/u.test(site)))\n ) {\n throw new InvalidRequestError(\n \"Kimi sites accepts at most five nonempty sites without whitespace or parentheses.\",\n );\n }\n const maxChars = contentLimit(options);\n const client = createClient(options);\n const counter = mode === \"pro\" ? \"kimiSearchProRequests\" : \"kimiSearchRequests\";\n return meteredTool(\n {\n name: mode === \"pro\" ? \"kimi_search_pro\" : \"kimi_search\",\n description: \"Search the web with Kimi and return cited sources and relevant text.\",\n input: stringInput(\"query\"),\n async execute({ query }, ctx, record) {\n const body = {\n text_query: query,\n limit,\n timeout_seconds: timeout,\n ...(mode === \"basic\"\n ? { include_content: options.includeContent ?? false }\n : {\n ...(options.sites !== undefined ? { sites: options.sites } : {}),\n ...(options.timeWindow !== undefined ? { time_window: options.timeWindow } : {}),\n }),\n };\n const raw = await request(\n client,\n mode === \"pro\" ? \"/tools/search_pro\" : \"/tools/search\",\n body,\n ctx,\n );\n if (!isObject(raw.data) || !Array.isArray(raw.data.search_results)) {\n throw new ProviderError(\"Kimi returned invalid search_results.\");\n }\n record(raw.status === 200 && raw.data.search_results.length > 0);\n const budget = textBudget(maxChars);\n const results = raw.data.search_results.slice(0, limit).map((value): KimiSearchResult => {\n if (!isObject(value)) throw new ProviderError(\"Kimi returned an invalid search result.\");\n const result: KimiSearchResult = {\n title: stringValue(value.title),\n url: stringValue(value.url),\n snippet: budget.take(stringValue(value.snippet)),\n siteName: stringValue(value.site_name),\n date: stringValue(value.date),\n authority: stringValue(value.authority),\n };\n if (mode === \"basic\" && options.includeContent)\n result.text = budget.take(stringValue(value.text));\n if (mode === \"pro\") {\n if (!Array.isArray(value.chunks))\n throw new ProviderError(\"Kimi returned invalid search chunks.\");\n result.chunks = value.chunks\n .map((chunk) => {\n if (\n !isObject(chunk) ||\n typeof chunk.score !== \"number\" ||\n !Number.isFinite(chunk.score)\n ) {\n throw new ProviderError(\"Kimi returned an invalid search chunk.\");\n }\n return { text: budget.take(stringValue(chunk.text)), score: chunk.score };\n })\n .filter((chunk) => chunk.text.length > 0);\n }\n return result;\n });\n return { results, truncated: budget.truncated || raw.data.search_results.length > limit };\n },\n },\n counter,\n options.pricing,\n );\n}\n\n/** First-party URL-to-Markdown REST tool.\n * https://platform.kimi.ai/docs/api/tools-fetch — only http/https inputs. */\nexport function kimiFetchTool(\n options: KimiToolOptions,\n): ToolContract<{ url: string }, KimiFetchOutput> {\n const client = createClient(options);\n const maxChars = contentLimit(options);\n return meteredTool(\n {\n name: \"kimi_fetch\",\n description: \"Read a web page with Kimi and return its title, URL and Markdown body.\",\n input: stringInput(\"url\"),\n async execute({ url }, ctx, record) {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new InvalidRequestError(\"Kimi fetch requires an http/https URL.\");\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new InvalidRequestError(\"Kimi fetch requires an http/https URL.\");\n }\n const raw = await request(client, \"/tools/fetch\", { url }, ctx);\n if (!isObject(raw.data) || typeof raw.data.markdown !== \"string\") {\n throw new ProviderError(\"Kimi returned invalid fetch content.\");\n }\n record(raw.status === 200 && raw.data.markdown.trim().length > 0);\n return {\n url: stringValue(raw.data.url),\n title: stringValue(raw.data.title),\n markdown: raw.data.markdown.slice(0, maxChars),\n truncated: raw.data.markdown.length > maxChars,\n };\n },\n },\n \"kimiFetchRequests\",\n options.pricing,\n );\n}\n\nfunction createClient(options: KimiToolOptions): OpenAI {\n const { apiKey, baseURL, fetch, timeout, defaultHeaders } = options;\n if (!apiKey) throw new ProviderAuthError(\"Kimi tools require an explicitly configured API key.\");\n return new OpenAI({\n apiKey,\n baseURL: baseURL ?? \"https://api.moonshot.ai/v1\",\n fetch,\n timeout,\n defaultHeaders,\n maxRetries: 0,\n });\n}\n\nasync function request(client: OpenAI, path: string, body: object, ctx?: ToolContext) {\n try {\n ctx?.signal?.throwIfAborted();\n // No automatic retries: a lost response does not prove the vendor did not bill it.\n const response = await client.post<unknown>(path, { body, signal: ctx?.signal }).withResponse();\n return { data: response.data, status: response.response.status };\n } catch (thrown) {\n if (ctx?.signal?.aborted) {\n throw new AgentCancelledError(\"Kimi tool request cancelled.\", { cause: thrown });\n }\n if (thrown instanceof APIError) {\n const context = {\n status: thrown.status,\n type: thrown.type,\n requestId: thrown.headers?.get(\"x-msh-track-id\"),\n };\n if (thrown.status === 408 || thrown.status === 504)\n throw new ProviderTimeoutError(thrown.message, { cause: thrown, context });\n if (thrown.status === 429) {\n const retry = Number(thrown.headers?.get(\"retry-after\") ?? Number.NaN);\n throw new ProviderRateLimitError(thrown.message, {\n cause: thrown,\n context,\n retryAfter: Number.isFinite(retry) && retry >= 0 ? retry * 1000 : undefined,\n });\n }\n if (thrown.status === 403 && !thrown.type)\n throw new ProviderAuthError(thrown.message, { cause: thrown, context });\n }\n throw wrapOpenAIError(thrown);\n }\n}\n\n/** Each invoke owns its ledger, including concurrent calls and post-response failures.\n * ToolContract.invoke is the existing accounting boundary used by agents/reports. */\nfunction meteredTool<TInput, TOutput>(\n config: Omit<ToolConfig<TInput, TOutput>, \"execute\"> & {\n execute(\n input: TInput,\n ctx: ToolContext | undefined,\n record: (billable: boolean) => void,\n ): Promise<TOutput>;\n },\n counter: Counter,\n prices: ServerToolPricing = KIMI_SERVER_TOOL_PRICING,\n): ToolContract<TInput, TOutput> {\n const rate = prices[counter];\n if (rate !== undefined && (!Number.isFinite(rate) || rate < 0))\n throw new InvalidRequestError(\"Kimi per-call pricing must be finite and nonnegative.\");\n const base = tool({\n ...config,\n execute: (input: TInput, ctx?: ToolContext) => config.execute(input, ctx, () => {}),\n });\n return {\n ...base,\n async invoke(input, ctx) {\n let usage: Usage = { input: 0, output: 0, total: 0 };\n const invocation = tool({\n ...config,\n execute: (validated: TInput, context?: ToolContext) =>\n config.execute(validated, context, (billable) => {\n const count = billable ? 1 : 0;\n usage = {\n input: 0,\n output: 0,\n total: 0,\n serverToolUse: {\n [counter]: count,\n [counter === \"kimiFetchRequests\" ? \"webFetchRequests\" : \"webSearchRequests\"]: count,\n },\n };\n if (rate !== undefined)\n usage.cost = computeCost(usage, {\n input: 0,\n output: 0,\n serverTools: { [counter]: rate },\n });\n }),\n });\n const result = await invocation.invoke(input, ctx);\n return { ...result, usage, report: { ...result.report, usage } };\n },\n };\n}\n\nfunction integer(value: number, min: number, max: number, name: string): number {\n if (!Number.isInteger(value) || value < min || value > max)\n throw new InvalidRequestError(`Kimi ${name} must be an integer from ${min} to ${max}.`);\n return value;\n}\nfunction contentLimit(options: KimiToolOptions): number {\n return integer(options.maxContentChars ?? 12_000, 1, Number.MAX_SAFE_INTEGER, \"maxContentChars\");\n}\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nfunction stringValue(value: unknown): string {\n if (typeof value !== \"string\") throw new ProviderError(\"Kimi returned an invalid text field.\");\n return value;\n}\nfunction textBudget(max: number) {\n return {\n truncated: false,\n take(text: string) {\n const result = text.slice(0, max);\n if (text.length > max) this.truncated = true;\n max -= result.length;\n return result;\n },\n };\n}\nfunction stringInput<K extends string>(\n key: K,\n): StandardSchemaV1<Record<K, string>> & { jsonSchema: Record<string, unknown> } {\n return {\n jsonSchema: {\n type: \"object\",\n properties: { [key]: { type: \"string\", minLength: 1 } },\n required: [key],\n additionalProperties: false,\n },\n \"~standard\": {\n version: 1,\n vendor: \"kimi\",\n validate(value) {\n if (!isObject(value) || typeof value[key] !== \"string\" || !value[key].trim()) {\n return { issues: [{ message: `${key} must be a nonempty string`, path: [key] }] };\n }\n return { value: { [key]: value[key] } as Record<K, string> };\n },\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { ModelPricing } from "@warlock.js/ai";
2
+ //#region ../../packages/ai-openai/src/known-pricing.d.ts
3
+ /**
4
+ * Built-in OpenAI API list prices, USD per 1,000,000 tokens, keyed by
5
+ * exact model id. `cachedInput` is the cached-input rate. None of these
6
+ * models charges for cache writes.
7
+ *
8
+ * Source: https://developers.openai.com/api/docs/pricing (the target of
9
+ * platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17.
10
+ *
11
+ * Applied only when the SDK's provider label is `"openai"`, and only on
12
+ * an exact id match — dated snapshots can carry other prices. Left out on
13
+ * purpose, so pass `pricing` yourself for them:
14
+ *
15
+ * - Models priced by prompt length (a higher rate above 272K input
16
+ * tokens): the gpt-6 / gpt-5.6 / gpt-5.5 / gpt-5.4 families and the
17
+ * `-pro` models. A flat `ModelPricing` cannot express the tiers.
18
+ * - Promotional, Batch, Flex and Priority pricing, and the regional
19
+ * (data-residency) uplift.
20
+ */
21
+ export declare const OPENAI_MODEL_PRICING: Readonly<Record<string, ModelPricing>>;
22
+ /**
23
+ * Look up the built-in list price for an OpenAI model id (exact match).
24
+ * Returns `undefined` for unknown ids, so cost stays unknown rather than
25
+ * wrong.
26
+ *
27
+ * @example
28
+ * openAIPricingFor("gpt-4o-mini"); // { input: 0.15, output: 0.6, cachedInput: 0.075 }
29
+ * openAIPricingFor("gpt-5.5"); // undefined — priced by prompt length
30
+ */
31
+ export declare function openAIPricingFor(modelName: string): ModelPricing | undefined;
32
+ /** USD per request for web_search (NOT non-reasoning web_search_preview).
33
+ * Search content tokens are additional; preview variants can have other rates.
34
+ * https://developers.openai.com/api/docs/pricing#built-in-tools — verified 2026-10-18. */
35
+ export declare const OPENAI_SERVER_TOOL_PRICING: {
36
+ webSearch: number;
37
+ };
38
+ //#endregion
39
+ //# sourceMappingURL=known-pricing.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-pricing.d.mts","names":[],"sources":["../../../../../../../packages/ai-openai/src/known-pricing.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;qBAoBa,sBAAsB,SAAS,eAAe;;;;;;;;;;wBA4B3C,iBAAiB,oBAAoB;;;;qBAOxC;EAA+B"}