@tanstack/ai-grok 0.14.9 → 0.14.10

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 (36) hide show
  1. package/dist/esm/adapters/image.js +228 -193
  2. package/dist/esm/adapters/image.js.map +1 -1
  3. package/dist/esm/adapters/summarize.js +49 -12
  4. package/dist/esm/adapters/summarize.js.map +1 -1
  5. package/dist/esm/adapters/text.js +82 -38
  6. package/dist/esm/adapters/text.js.map +1 -1
  7. package/dist/esm/adapters/transcription.js +133 -114
  8. package/dist/esm/adapters/transcription.js.map +1 -1
  9. package/dist/esm/adapters/tts.js +147 -130
  10. package/dist/esm/adapters/tts.js.map +1 -1
  11. package/dist/esm/adapters/video.js +275 -226
  12. package/dist/esm/adapters/video.js.map +1 -1
  13. package/dist/esm/image/image-provider-options.js +64 -66
  14. package/dist/esm/image/image-provider-options.js.map +1 -1
  15. package/dist/esm/index.js +3 -31
  16. package/dist/esm/model-meta.js +149 -47
  17. package/dist/esm/model-meta.js.map +1 -1
  18. package/dist/esm/realtime/adapter.js +729 -812
  19. package/dist/esm/realtime/adapter.js.map +1 -1
  20. package/dist/esm/realtime/index.js +3 -0
  21. package/dist/esm/realtime/token.js +78 -72
  22. package/dist/esm/realtime/token.js.map +1 -1
  23. package/dist/esm/tools/index.js +57 -96
  24. package/dist/esm/tools/index.js.map +1 -1
  25. package/dist/esm/utils/audio.js +116 -162
  26. package/dist/esm/utils/audio.js.map +1 -1
  27. package/dist/esm/utils/client.js +22 -16
  28. package/dist/esm/utils/client.js.map +1 -1
  29. package/dist/esm/utils/index.js +5 -0
  30. package/dist/esm/utils/schema-converter.js +2 -0
  31. package/dist/esm/video/video-provider-options.js +83 -57
  32. package/dist/esm/video/video-provider-options.js.map +1 -1
  33. package/package.json +8 -8
  34. package/src/realtime/adapter.ts +1 -1
  35. package/src/utils/audio.ts +1 -1
  36. package/dist/esm/index.js.map +0 -1
@@ -1,44 +1,88 @@
1
+ import { getGrokApiKeyFromEnv, withGrokDefaults } from "../utils/client.js";
2
+ import { convertToolsToProviderFormat } from "../tools/index.js";
1
3
  import OpenAI from "openai";
2
4
  import { OpenAIBaseResponsesTextAdapter } from "@tanstack/openai-base";
3
- import { withGrokDefaults, getGrokApiKeyFromEnv } from "../utils/client.js";
4
- import { convertToolsToProviderFormat } from "../tools/index.js";
5
- class GrokTextAdapter extends OpenAIBaseResponsesTextAdapter {
6
- kind = "text";
7
- name = "grok";
8
- constructor(config, model) {
9
- super(model, "grok", new OpenAI(withGrokDefaults(config)));
10
- }
11
- mapOptionsToRequest(options) {
12
- const { tools: _baseTools, ...request } = super.mapOptionsToRequest({
13
- ...options,
14
- tools: void 0
15
- });
16
- if (this.model === "grok-build-0.1" && request.reasoning !== void 0) {
17
- throw new Error(
18
- "grok-build-0.1 does not support reasoning modelOptions; omit reasoning for this model."
19
- );
20
- }
21
- const tools = options.tools ? convertToolsToProviderFormat(options.tools) : void 0;
22
- return {
23
- ...request,
24
- // xAI recommends encrypted reasoning for reasoning-capable Responses
25
- // requests; callers can still override either field in modelOptions.
26
- store: request.store ?? false,
27
- include: request.include ?? ["reasoning.encrypted_content"],
28
- ...tools && tools.length > 0 && { tools }
29
- };
30
- }
31
- }
5
+ //#region src/adapters/text.ts
6
+ /**
7
+ * Grok Text (Chat) Adapter
8
+ *
9
+ * Tree-shakeable adapter for Grok chat/text completion functionality.
10
+ * Uses xAI's OpenAI-compatible Responses API.
11
+ *
12
+ * Delegates implementation to {@link OpenAIBaseResponsesTextAdapter}
13
+ * from `@tanstack/openai-base` and threads Grok-specific tool-capability
14
+ * typing through the 5th generic of the base class.
15
+ */
16
+ var GrokTextAdapter = class extends OpenAIBaseResponsesTextAdapter {
17
+ kind = "text";
18
+ name = "grok";
19
+ constructor(config, model) {
20
+ super(model, "grok", new OpenAI(withGrokDefaults(config)));
21
+ }
22
+ mapOptionsToRequest(options) {
23
+ const { tools: _baseTools, ...request } = super.mapOptionsToRequest({
24
+ ...options,
25
+ tools: void 0
26
+ });
27
+ if (this.model === "grok-build-0.1" && request.reasoning !== void 0) throw new Error("grok-build-0.1 does not support reasoning modelOptions; omit reasoning for this model.");
28
+ const tools = options.tools ? convertToolsToProviderFormat(options.tools) : void 0;
29
+ return {
30
+ ...request,
31
+ store: request.store ?? false,
32
+ include: request.include ?? ["reasoning.encrypted_content"],
33
+ ...tools && tools.length > 0 && { tools }
34
+ };
35
+ }
36
+ };
37
+ /**
38
+ * Creates a Grok text adapter with explicit API key.
39
+ * Type resolution happens here at the call site.
40
+ *
41
+ * @param model - The model name (e.g., 'grok-build-0.1')
42
+ * @param apiKey - Your xAI API key
43
+ * @param config - Optional additional configuration
44
+ * @returns Configured Grok text adapter instance with resolved types
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const adapter = createGrokText('grok-build-0.1', "xai-...");
49
+ * // adapter has type-safe providerOptions for grok-build-0.1
50
+ * ```
51
+ */
32
52
  function createGrokText(model, apiKey, config) {
33
- return new GrokTextAdapter({ apiKey, ...config }, model);
53
+ return new GrokTextAdapter({
54
+ apiKey,
55
+ ...config
56
+ }, model);
34
57
  }
58
+ /**
59
+ * Creates a Grok text adapter with automatic API key detection from environment variables.
60
+ * Type resolution happens here at the call site.
61
+ *
62
+ * Looks for `XAI_API_KEY` in:
63
+ * - `process.env` (Node.js)
64
+ * - `window.env` (Browser with injected env)
65
+ *
66
+ * @param model - The model name (e.g., 'grok-build-0.1')
67
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
68
+ * @returns Configured Grok text adapter instance with resolved types
69
+ * @throws Error if XAI_API_KEY is not found in environment
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Automatically uses XAI_API_KEY from environment
74
+ * const adapter = grokText('grok-build-0.1');
75
+ *
76
+ * const stream = chat({
77
+ * adapter,
78
+ * messages: [{ role: "user", content: "Hello!" }]
79
+ * });
80
+ * ```
81
+ */
35
82
  function grokText(model, config) {
36
- const apiKey = getGrokApiKeyFromEnv();
37
- return createGrokText(model, apiKey, config);
83
+ return createGrokText(model, getGrokApiKeyFromEnv(), config);
38
84
  }
39
- export {
40
- GrokTextAdapter,
41
- createGrokText,
42
- grokText
43
- };
44
- //# sourceMappingURL=text.js.map
85
+ //#endregion
86
+ export { GrokTextAdapter, createGrokText, grokText };
87
+
88
+ //# sourceMappingURL=text.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { OpenAIBaseResponsesTextAdapter } from '@tanstack/openai-base'\nimport { getGrokApiKeyFromEnv, withGrokDefaults } from '../utils/client'\nimport { convertToolsToProviderFormat } from '../tools'\nimport type {\n GROK_CHAT_MODELS,\n GrokChatModelToolCapabilitiesByName,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type { Modality, TextOptions } from '@tanstack/ai'\nimport type { GrokMessageMetadataByModality } from '../message-types'\nimport type { GrokClientConfig } from '../utils/client'\nimport type { ResponseCreateParams } from 'openai/resources/responses/responses'\n\n/**\n * Resolve tool capabilities for a specific Grok model.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GrokChatModelToolCapabilitiesByName\n ? NonNullable<GrokChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/**\n * Configuration for Grok text adapter\n */\nexport interface GrokTextConfig extends GrokClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Grok Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Grok chat/text completion functionality.\n * Uses xAI's OpenAI-compatible Responses API.\n *\n * Delegates implementation to {@link OpenAIBaseResponsesTextAdapter}\n * from `@tanstack/openai-base` and threads Grok-specific tool-capability\n * typing through the 5th generic of the base class.\n */\nexport class GrokTextAdapter<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n // Use `Record<string, any>` (not `unknown`) to match the OpenAI text\n // adapter: the resolved Grok provider options are a type-alias intersection\n // with no explicit index signature, which is assignable to\n // `Record<string, any>` but not `Record<string, unknown>`. See issue #821.\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends OpenAIBaseResponsesTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GrokMessageMetadataByModality,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n override readonly name = 'grok' as const\n\n constructor(config: GrokTextConfig, model: TModel) {\n super(model, 'grok', new OpenAI(withGrokDefaults(config)))\n }\n\n protected override mapOptionsToRequest(\n options: TextOptions<TProviderOptions>,\n ): Omit<ResponseCreateParams, 'stream'> {\n const { tools: _baseTools, ...request } = super.mapOptionsToRequest({\n ...options,\n tools: undefined,\n })\n void _baseTools\n\n if (this.model === 'grok-build-0.1' && request.reasoning !== undefined) {\n throw new Error(\n 'grok-build-0.1 does not support reasoning modelOptions; omit reasoning for this model.',\n )\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n return {\n ...request,\n // xAI recommends encrypted reasoning for reasoning-capable Responses\n // requests; callers can still override either field in modelOptions.\n store: request.store ?? false,\n include: request.include ?? ['reasoning.encrypted_content'],\n ...(tools &&\n tools.length > 0 && { tools: tools as ResponseCreateParams['tools'] }),\n }\n }\n}\n\n/**\n * Creates a Grok text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'grok-build-0.1')\n * @param apiKey - Your xAI API key\n * @param config - Optional additional configuration\n * @returns Configured Grok text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGrokText('grok-build-0.1', \"xai-...\");\n * // adapter has type-safe providerOptions for grok-build-0.1\n * ```\n */\nexport function createGrokText<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n return new GrokTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `XAI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'grok-build-0.1')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Grok text adapter instance with resolved types\n * @throws Error if XAI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses XAI_API_KEY from environment\n * const adapter = grokText('grok-build-0.1');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function grokText<TModel extends (typeof GROK_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokText(model, apiKey, config)\n}\n"],"names":[],"mappings":";;;;AA2CO,MAAM,wBAWH,+BAMR;AAAA,EACkB,OAAO;AAAA,EACP,OAAO;AAAA,EAEzB,YAAY,QAAwB,OAAe;AACjD,UAAM,OAAO,QAAQ,IAAI,OAAO,iBAAiB,MAAM,CAAC,CAAC;AAAA,EAC3D;AAAA,EAEmB,oBACjB,SACsC;AACtC,UAAM,EAAE,OAAO,YAAY,GAAG,QAAA,IAAY,MAAM,oBAAoB;AAAA,MAClE,GAAG;AAAA,MACH,OAAO;AAAA,IAAA,CACR;AAGD,QAAI,KAAK,UAAU,oBAAoB,QAAQ,cAAc,QAAW;AACtE,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,UAAM,QAAQ,QAAQ,QAClB,6BAA6B,QAAQ,KAAK,IAC1C;AAEJ,WAAO;AAAA,MACL,GAAG;AAAA;AAAA;AAAA,MAGH,OAAO,QAAQ,SAAS;AAAA,MACxB,SAAS,QAAQ,WAAW,CAAC,6BAA6B;AAAA,MAC1D,GAAI,SACF,MAAM,SAAS,KAAK,EAAE,MAAA;AAAA,IAA8C;AAAA,EAE1E;AACF;AAiBO,SAAS,eAGd,OACA,QACA,QACyB;AACzB,SAAO,IAAI,gBAAgB,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AACzD;AA0BO,SAAS,SACd,OACA,QACyB;AACzB,QAAM,SAAS,qBAAA;AACf,SAAO,eAAe,OAAO,QAAQ,MAAM;AAC7C;"}
1
+ {"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { OpenAIBaseResponsesTextAdapter } from '@tanstack/openai-base'\nimport { getGrokApiKeyFromEnv, withGrokDefaults } from '../utils/client'\nimport { convertToolsToProviderFormat } from '../tools'\nimport type {\n GROK_CHAT_MODELS,\n GrokChatModelToolCapabilitiesByName,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type { Modality, TextOptions } from '@tanstack/ai'\nimport type { GrokMessageMetadataByModality } from '../message-types'\nimport type { GrokClientConfig } from '../utils/client'\nimport type { ResponseCreateParams } from 'openai/resources/responses/responses'\n\n/**\n * Resolve tool capabilities for a specific Grok model.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GrokChatModelToolCapabilitiesByName\n ? NonNullable<GrokChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/**\n * Configuration for Grok text adapter\n */\nexport interface GrokTextConfig extends GrokClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Grok Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Grok chat/text completion functionality.\n * Uses xAI's OpenAI-compatible Responses API.\n *\n * Delegates implementation to {@link OpenAIBaseResponsesTextAdapter}\n * from `@tanstack/openai-base` and threads Grok-specific tool-capability\n * typing through the 5th generic of the base class.\n */\nexport class GrokTextAdapter<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n // Use `Record<string, any>` (not `unknown`) to match the OpenAI text\n // adapter: the resolved Grok provider options are a type-alias intersection\n // with no explicit index signature, which is assignable to\n // `Record<string, any>` but not `Record<string, unknown>`. See issue #821.\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends OpenAIBaseResponsesTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GrokMessageMetadataByModality,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n override readonly name = 'grok' as const\n\n constructor(config: GrokTextConfig, model: TModel) {\n super(model, 'grok', new OpenAI(withGrokDefaults(config)))\n }\n\n protected override mapOptionsToRequest(\n options: TextOptions<TProviderOptions>,\n ): Omit<ResponseCreateParams, 'stream'> {\n const { tools: _baseTools, ...request } = super.mapOptionsToRequest({\n ...options,\n tools: undefined,\n })\n void _baseTools\n\n if (this.model === 'grok-build-0.1' && request.reasoning !== undefined) {\n throw new Error(\n 'grok-build-0.1 does not support reasoning modelOptions; omit reasoning for this model.',\n )\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n return {\n ...request,\n // xAI recommends encrypted reasoning for reasoning-capable Responses\n // requests; callers can still override either field in modelOptions.\n store: request.store ?? false,\n include: request.include ?? ['reasoning.encrypted_content'],\n ...(tools &&\n tools.length > 0 && { tools: tools as ResponseCreateParams['tools'] }),\n }\n }\n}\n\n/**\n * Creates a Grok text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'grok-build-0.1')\n * @param apiKey - Your xAI API key\n * @param config - Optional additional configuration\n * @returns Configured Grok text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGrokText('grok-build-0.1', \"xai-...\");\n * // adapter has type-safe providerOptions for grok-build-0.1\n * ```\n */\nexport function createGrokText<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n return new GrokTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `XAI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'grok-build-0.1')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Grok text adapter instance with resolved types\n * @throws Error if XAI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses XAI_API_KEY from environment\n * const adapter = grokText('grok-build-0.1');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function grokText<TModel extends (typeof GROK_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokText(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2CA,IAAa,kBAAb,cAWU,+BAMR;CACA,OAAyB;CACzB,OAAyB;CAEzB,YAAY,QAAwB,OAAe;EACjD,MAAM,OAAO,QAAQ,IAAI,OAAO,iBAAiB,MAAM,CAAC,CAAC;CAC3D;CAEA,oBACE,SACsC;EACtC,MAAM,EAAE,OAAO,YAAY,GAAG,YAAY,MAAM,oBAAoB;GAClE,GAAG;GACH,OAAO,KAAA;EACT,CAAC;EAGD,IAAI,KAAK,UAAU,oBAAoB,QAAQ,cAAc,KAAA,GAC3D,MAAM,IAAI,MACR,wFACF;EAGF,MAAM,QAAQ,QAAQ,QAClB,6BAA6B,QAAQ,KAAK,IAC1C,KAAA;EAEJ,OAAO;GACL,GAAG;GAGH,OAAO,QAAQ,SAAS;GACxB,SAAS,QAAQ,WAAW,CAAC,6BAA6B;GAC1D,GAAI,SACF,MAAM,SAAS,KAAK,EAAS,MAAuC;EACxE;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,eAGd,OACA,QACA,QACyB;CACzB,OAAO,IAAI,gBAAgB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AACzD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,SACd,OACA,QACyB;CAEzB,OAAO,eAAe,OADP,qBACc,GAAQ,MAAM;AAC7C"}
@@ -1,122 +1,141 @@
1
- import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters";
2
- import { generateId } from "@tanstack/ai-utils";
3
1
  import { getGrokApiKeyFromEnv } from "../utils/client.js";
4
- import "@tanstack/openai-base";
5
2
  import { toAudioFile } from "../utils/audio.js";
6
- const DEFAULT_GROK_BASE_URL = "https://api.x.ai/v1";
7
- class GrokTranscriptionAdapter extends BaseTranscriptionAdapter {
8
- name = "grok";
9
- apiKey;
10
- baseURL;
11
- defaultHeaders;
12
- constructor(config, model) {
13
- super(model, config);
14
- this.apiKey = config.apiKey;
15
- this.baseURL = (config.baseURL ?? DEFAULT_GROK_BASE_URL).replace(/\/+$/, "");
16
- this.defaultHeaders = config.defaultHeaders ?? {};
17
- }
18
- async transcribe(options) {
19
- const { logger } = options;
20
- const { model, audio, language, modelOptions } = options;
21
- logger.request(
22
- `activity=generateTranscription provider=grok model=${model}`,
23
- { provider: "grok", model }
24
- );
25
- const file = toAudioFile(audio, modelOptions?.audio_format);
26
- const form = buildTranscriptionFormData({ file, language, modelOptions });
27
- try {
28
- const response = await fetch(`${this.baseURL}/stt`, {
29
- method: "POST",
30
- headers: {
31
- // `defaultHeaders` first so Authorization always wins.
32
- ...this.defaultHeaders,
33
- Authorization: `Bearer ${this.apiKey}`
34
- },
35
- body: form
36
- });
37
- if (!response.ok) {
38
- const errorText = await response.text();
39
- throw new Error(
40
- `Grok transcription request failed: ${response.status} ${errorText}`
41
- );
42
- }
43
- const data = await response.json();
44
- const words = data.words?.map(
45
- (w) => {
46
- const tw = {
47
- word: w.text,
48
- start: w.start,
49
- end: w.end
50
- };
51
- if (w.confidence !== void 0) tw.confidence = w.confidence;
52
- if (w.speaker !== void 0) tw.speaker = w.speaker;
53
- return tw;
54
- }
55
- );
56
- const resolvedLanguage = data.language ?? language;
57
- const usage = data.duration !== void 0 && data.duration > 0 ? {
58
- promptTokens: 0,
59
- completionTokens: 0,
60
- totalTokens: 0,
61
- durationSeconds: data.duration
62
- } : void 0;
63
- return {
64
- id: generateId(this.name),
65
- model,
66
- text: data.text,
67
- ...resolvedLanguage !== void 0 && { language: resolvedLanguage },
68
- duration: data.duration,
69
- ...words !== void 0 && { words },
70
- ...usage !== void 0 && { usage }
71
- };
72
- } catch (error) {
73
- logger.errors("grok.transcribe fatal", {
74
- error,
75
- source: "grok.transcribe"
76
- });
77
- throw error;
78
- }
79
- }
80
- }
3
+ import { generateId } from "../utils/index.js";
4
+ import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters";
5
+ //#region src/adapters/transcription.ts
6
+ var DEFAULT_GROK_BASE_URL = "https://api.x.ai/v1";
7
+ /**
8
+ * Grok Speech-to-Text Adapter.
9
+ *
10
+ * Talks to `POST {baseURL}/stt` per
11
+ * https://docs.x.ai/developers/rest-api-reference/inference/voice
12
+ */
13
+ var GrokTranscriptionAdapter = class extends BaseTranscriptionAdapter {
14
+ name = "grok";
15
+ apiKey;
16
+ baseURL;
17
+ defaultHeaders;
18
+ constructor(config, model) {
19
+ super(model, config);
20
+ this.apiKey = config.apiKey;
21
+ this.baseURL = (config.baseURL ?? DEFAULT_GROK_BASE_URL).replace(/\/+$/, "");
22
+ this.defaultHeaders = config.defaultHeaders ?? {};
23
+ }
24
+ async transcribe(options) {
25
+ const { logger } = options;
26
+ const { model, audio, language, modelOptions } = options;
27
+ logger.request(`activity=generateTranscription provider=grok model=${model}`, {
28
+ provider: "grok",
29
+ model
30
+ });
31
+ const form = buildTranscriptionFormData({
32
+ file: toAudioFile(audio, modelOptions?.audio_format),
33
+ language,
34
+ modelOptions
35
+ });
36
+ try {
37
+ const response = await fetch(`${this.baseURL}/stt`, {
38
+ method: "POST",
39
+ headers: {
40
+ ...this.defaultHeaders,
41
+ Authorization: `Bearer ${this.apiKey}`
42
+ },
43
+ body: form
44
+ });
45
+ if (!response.ok) {
46
+ const errorText = await response.text();
47
+ throw new Error(`Grok transcription request failed: ${response.status} ${errorText}`);
48
+ }
49
+ const data = await response.json();
50
+ const words = data.words?.map((w) => {
51
+ const tw = {
52
+ word: w.text,
53
+ start: w.start,
54
+ end: w.end
55
+ };
56
+ if (w.confidence !== void 0) tw.confidence = w.confidence;
57
+ if (w.speaker !== void 0) tw.speaker = w.speaker;
58
+ return tw;
59
+ });
60
+ const resolvedLanguage = data.language ?? language;
61
+ const usage = data.duration !== void 0 && data.duration > 0 ? {
62
+ promptTokens: 0,
63
+ completionTokens: 0,
64
+ totalTokens: 0,
65
+ durationSeconds: data.duration
66
+ } : void 0;
67
+ return {
68
+ id: generateId(this.name),
69
+ model,
70
+ text: data.text,
71
+ ...resolvedLanguage !== void 0 && { language: resolvedLanguage },
72
+ duration: data.duration,
73
+ ...words !== void 0 && { words },
74
+ ...usage !== void 0 && { usage }
75
+ };
76
+ } catch (error) {
77
+ logger.errors("grok.transcribe fatal", {
78
+ error,
79
+ source: "grok.transcribe"
80
+ });
81
+ throw error;
82
+ }
83
+ }
84
+ };
85
+ /**
86
+ * Build the multipart/form-data body for `POST /v1/stt`, coercing SDK-level
87
+ * model options into xAI's wire format (booleans as `'true'`/`'false'`
88
+ * strings, numeric fields stringified, etc.).
89
+ *
90
+ * Wire-field mapping:
91
+ * - `modelOptions.inverse_text_normalization` → `format` (xAI's chosen
92
+ * wire-field name for the ITN boolean; the SDK surfaces it under the
93
+ * clearer `inverse_text_normalization` key).
94
+ * - `modelOptions.audio_format`, `sample_rate`, `multichannel`, `channels`,
95
+ * `diarize` map to same-named form fields.
96
+ */
81
97
  function buildTranscriptionFormData(options) {
82
- const { file, language, modelOptions } = options;
83
- const form = new FormData();
84
- form.set("file", file);
85
- if (language) form.set("language", language);
86
- if (modelOptions?.audio_format !== void 0) {
87
- form.set("audio_format", modelOptions.audio_format);
88
- }
89
- if (modelOptions?.sample_rate !== void 0) {
90
- form.set("sample_rate", String(modelOptions.sample_rate));
91
- }
92
- if (modelOptions?.inverse_text_normalization !== void 0) {
93
- form.set(
94
- "format",
95
- modelOptions.inverse_text_normalization ? "true" : "false"
96
- );
97
- }
98
- if (modelOptions?.multichannel !== void 0) {
99
- form.set("multichannel", modelOptions.multichannel ? "true" : "false");
100
- }
101
- if (modelOptions?.channels !== void 0) {
102
- form.set("channels", String(modelOptions.channels));
103
- }
104
- if (modelOptions?.diarize !== void 0) {
105
- form.set("diarize", modelOptions.diarize ? "true" : "false");
106
- }
107
- return form;
98
+ const { file, language, modelOptions } = options;
99
+ const form = new FormData();
100
+ form.set("file", file);
101
+ if (language) form.set("language", language);
102
+ if (modelOptions?.audio_format !== void 0) form.set("audio_format", modelOptions.audio_format);
103
+ if (modelOptions?.sample_rate !== void 0) form.set("sample_rate", String(modelOptions.sample_rate));
104
+ if (modelOptions?.inverse_text_normalization !== void 0) form.set("format", modelOptions.inverse_text_normalization ? "true" : "false");
105
+ if (modelOptions?.multichannel !== void 0) form.set("multichannel", modelOptions.multichannel ? "true" : "false");
106
+ if (modelOptions?.channels !== void 0) form.set("channels", String(modelOptions.channels));
107
+ if (modelOptions?.diarize !== void 0) form.set("diarize", modelOptions.diarize ? "true" : "false");
108
+ return form;
108
109
  }
110
+ /**
111
+ * Creates a Grok transcription adapter with an explicit API key.
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const adapter = createGrokTranscription('grok-stt', 'xai-...')
116
+ * const result = await generateTranscription({
117
+ * adapter,
118
+ * audio: audioFile,
119
+ * language: 'en',
120
+ * })
121
+ * ```
122
+ */
109
123
  function createGrokTranscription(model, apiKey, config) {
110
- return new GrokTranscriptionAdapter({ apiKey, ...config }, model);
124
+ return new GrokTranscriptionAdapter({
125
+ apiKey,
126
+ ...config
127
+ }, model);
111
128
  }
129
+ /**
130
+ * Creates a Grok transcription adapter, reading the API key from
131
+ * `XAI_API_KEY` in the environment.
132
+ *
133
+ * @throws Error if `XAI_API_KEY` is not set.
134
+ */
112
135
  function grokTranscription(model, config) {
113
- const apiKey = getGrokApiKeyFromEnv();
114
- return createGrokTranscription(model, apiKey, config);
136
+ return createGrokTranscription(model, getGrokApiKeyFromEnv(), config);
115
137
  }
116
- export {
117
- GrokTranscriptionAdapter,
118
- buildTranscriptionFormData,
119
- createGrokTranscription,
120
- grokTranscription
121
- };
122
- //# sourceMappingURL=transcription.js.map
138
+ //#endregion
139
+ export { GrokTranscriptionAdapter, buildTranscriptionFormData, createGrokTranscription, grokTranscription };
140
+
141
+ //# sourceMappingURL=transcription.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { generateId, getGrokApiKeyFromEnv, toAudioFile } from '../utils'\nimport type {\n TokenUsage,\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type { GrokTranscriptionModel } from '../model-meta'\nimport type { GrokTranscriptionProviderOptions } from '../audio/transcription-provider-options'\n\n/**\n * Grok-specific extension of `TranscriptionWord` that surfaces the extra\n * fields xAI returns when diarization / confidence are enabled. The base\n * cross-provider `TranscriptionWord` contract doesn't include these, so\n * callers who know they're using Grok can narrow with:\n *\n * ```ts\n * const words = result.words as Array<GrokTranscriptionWord> | undefined\n * ```\n */\nexport interface GrokTranscriptionWord extends TranscriptionWord {\n /** Model confidence for the word, when xAI returns one. */\n confidence?: number\n /** Speaker index, populated when `modelOptions.diarize === true`. */\n speaker?: number\n}\n\nconst DEFAULT_GROK_BASE_URL = 'https://api.x.ai/v1'\n\n/**\n * Configuration for the Grok transcription adapter.\n *\n * Uses direct `fetch` rather than the OpenAI SDK because xAI's `/v1/stt`\n * endpoint is not OpenAI-compatible.\n */\nexport interface GrokTranscriptionConfig {\n apiKey: string\n baseURL?: string\n /** Additional headers to merge into every request (e.g., test IDs). */\n defaultHeaders?: Record<string, string>\n}\n\n/**\n * xAI STT response shape from `POST /v1/stt`.\n * Grok returns word-level timestamps only; no segment array.\n */\ninterface GrokSTTWord {\n text: string\n start: number\n end: number\n confidence?: number\n speaker?: number\n}\n\ninterface GrokSTTResponse {\n text: string\n language?: string\n duration?: number\n words?: Array<GrokSTTWord>\n channels?: Array<unknown>\n}\n\n/**\n * Grok Speech-to-Text Adapter.\n *\n * Talks to `POST {baseURL}/stt` per\n * https://docs.x.ai/developers/rest-api-reference/inference/voice\n */\nexport class GrokTranscriptionAdapter<\n TModel extends GrokTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, GrokTranscriptionProviderOptions> {\n readonly name = 'grok' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n\n constructor(config: GrokTranscriptionConfig, model: TModel) {\n super(model, config)\n this.apiKey = config.apiKey\n this.baseURL = (config.baseURL ?? DEFAULT_GROK_BASE_URL).replace(/\\/+$/, '')\n this.defaultHeaders = config.defaultHeaders ?? {}\n }\n\n async transcribe(\n options: TranscriptionOptions<GrokTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { logger } = options\n const { model, audio, language, modelOptions } = options\n\n logger.request(\n `activity=generateTranscription provider=grok model=${model}`,\n { provider: 'grok', model },\n )\n\n const file = toAudioFile(audio, modelOptions?.audio_format)\n const form = buildTranscriptionFormData({ file, language, modelOptions })\n\n try {\n const response = await fetch(`${this.baseURL}/stt`, {\n method: 'POST',\n headers: {\n // `defaultHeaders` first so Authorization always wins.\n ...this.defaultHeaders,\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: form,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(\n `Grok transcription request failed: ${response.status} ${errorText}`,\n )\n }\n\n const data = (await response.json()) as GrokSTTResponse\n\n const words: Array<TranscriptionWord> | undefined = data.words?.map(\n (w) => {\n // Construct a GrokTranscriptionWord so that `confidence` and\n // `speaker` (when xAI returns them under `diarize` / confidence\n // mode) are preserved on the result. The returned array is typed\n // as `Array<TranscriptionWord>` per the cross-provider contract;\n // callers who want the extras narrow via `as Array<GrokTranscriptionWord>`.\n const tw: GrokTranscriptionWord = {\n word: w.text,\n start: w.start,\n end: w.end,\n }\n if (w.confidence !== undefined) tw.confidence = w.confidence\n if (w.speaker !== undefined) tw.speaker = w.speaker\n return tw\n },\n )\n\n const resolvedLanguage = data.language ?? language\n // xAI's /v1/stt response carries no token counts — STT is duration-billed —\n // so surface the audio duration as `durationSeconds`, mirroring the\n // whisper-1 path in the OpenAI transcription adapter.\n const usage: TokenUsage | undefined =\n data.duration !== undefined && data.duration > 0\n ? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n durationSeconds: data.duration,\n }\n : undefined\n return {\n id: generateId(this.name),\n model,\n text: data.text,\n ...(resolvedLanguage !== undefined && { language: resolvedLanguage }),\n duration: data.duration,\n ...(words !== undefined && { words }),\n ...(usage !== undefined && { usage }),\n }\n } catch (error) {\n logger.errors('grok.transcribe fatal', {\n error,\n source: 'grok.transcribe',\n })\n throw error\n }\n }\n}\n\n/**\n * Build the multipart/form-data body for `POST /v1/stt`, coercing SDK-level\n * model options into xAI's wire format (booleans as `'true'`/`'false'`\n * strings, numeric fields stringified, etc.).\n *\n * Wire-field mapping:\n * - `modelOptions.inverse_text_normalization` → `format` (xAI's chosen\n * wire-field name for the ITN boolean; the SDK surfaces it under the\n * clearer `inverse_text_normalization` key).\n * - `modelOptions.audio_format`, `sample_rate`, `multichannel`, `channels`,\n * `diarize` map to same-named form fields.\n */\nexport function buildTranscriptionFormData(options: {\n file: File\n language: string | undefined\n modelOptions: GrokTranscriptionProviderOptions | undefined\n}): FormData {\n const { file, language, modelOptions } = options\n const form = new FormData()\n form.set('file', file)\n if (language) form.set('language', language)\n if (modelOptions?.audio_format !== undefined) {\n form.set('audio_format', modelOptions.audio_format)\n }\n if (modelOptions?.sample_rate !== undefined) {\n form.set('sample_rate', String(modelOptions.sample_rate))\n }\n if (modelOptions?.inverse_text_normalization !== undefined) {\n form.set(\n 'format',\n modelOptions.inverse_text_normalization ? 'true' : 'false',\n )\n }\n if (modelOptions?.multichannel !== undefined) {\n form.set('multichannel', modelOptions.multichannel ? 'true' : 'false')\n }\n if (modelOptions?.channels !== undefined) {\n form.set('channels', String(modelOptions.channels))\n }\n if (modelOptions?.diarize !== undefined) {\n form.set('diarize', modelOptions.diarize ? 'true' : 'false')\n }\n return form\n}\n\n/**\n * Creates a Grok transcription adapter with an explicit API key.\n *\n * @example\n * ```typescript\n * const adapter = createGrokTranscription('grok-stt', 'xai-...')\n * const result = await generateTranscription({\n * adapter,\n * audio: audioFile,\n * language: 'en',\n * })\n * ```\n */\nexport function createGrokTranscription<TModel extends GrokTranscriptionModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTranscriptionConfig, 'apiKey'>,\n): GrokTranscriptionAdapter<TModel> {\n return new GrokTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok transcription adapter, reading the API key from\n * `XAI_API_KEY` in the environment.\n *\n * @throws Error if `XAI_API_KEY` is not set.\n */\nexport function grokTranscription<TModel extends GrokTranscriptionModel>(\n model: TModel,\n config?: Omit<GrokTranscriptionConfig, 'apiKey'>,\n): GrokTranscriptionAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokTranscription(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { generateId, getGrokApiKeyFromEnv, toAudioFile } from '../utils'\nimport type {\n TokenUsage,\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type { GrokTranscriptionModel } from '../model-meta'\nimport type { GrokTranscriptionProviderOptions } from '../audio/transcription-provider-options'\n\n/**\n * Grok-specific extension of `TranscriptionWord` that surfaces the extra\n * fields xAI returns when diarization / confidence are enabled. The base\n * cross-provider `TranscriptionWord` contract doesn't include these, so\n * callers who know they're using Grok can narrow with:\n *\n * ```ts\n * const words = result.words as Array<GrokTranscriptionWord> | undefined\n * ```\n */\nexport interface GrokTranscriptionWord extends TranscriptionWord {\n /** Model confidence for the word, when xAI returns one. */\n confidence?: number\n /** Speaker index, populated when `modelOptions.diarize === true`. */\n speaker?: number\n}\n\nconst DEFAULT_GROK_BASE_URL = 'https://api.x.ai/v1'\n\n/**\n * Configuration for the Grok transcription adapter.\n *\n * Uses direct `fetch` rather than the OpenAI SDK because xAI's `/v1/stt`\n * endpoint is not OpenAI-compatible.\n */\nexport interface GrokTranscriptionConfig {\n apiKey: string\n baseURL?: string\n /** Additional headers to merge into every request (e.g., test IDs). */\n defaultHeaders?: Record<string, string>\n}\n\n/**\n * xAI STT response shape from `POST /v1/stt`.\n * Grok returns word-level timestamps only; no segment array.\n */\ninterface GrokSTTWord {\n text: string\n start: number\n end: number\n confidence?: number\n speaker?: number\n}\n\ninterface GrokSTTResponse {\n text: string\n language?: string\n duration?: number\n words?: Array<GrokSTTWord>\n channels?: Array<unknown>\n}\n\n/**\n * Grok Speech-to-Text Adapter.\n *\n * Talks to `POST {baseURL}/stt` per\n * https://docs.x.ai/developers/rest-api-reference/inference/voice\n */\nexport class GrokTranscriptionAdapter<\n TModel extends GrokTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, GrokTranscriptionProviderOptions> {\n readonly name = 'grok' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n\n constructor(config: GrokTranscriptionConfig, model: TModel) {\n super(model, config)\n this.apiKey = config.apiKey\n this.baseURL = (config.baseURL ?? DEFAULT_GROK_BASE_URL).replace(/\\/+$/, '')\n this.defaultHeaders = config.defaultHeaders ?? {}\n }\n\n async transcribe(\n options: TranscriptionOptions<GrokTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { logger } = options\n const { model, audio, language, modelOptions } = options\n\n logger.request(\n `activity=generateTranscription provider=grok model=${model}`,\n { provider: 'grok', model },\n )\n\n const file = toAudioFile(audio, modelOptions?.audio_format)\n const form = buildTranscriptionFormData({ file, language, modelOptions })\n\n try {\n const response = await fetch(`${this.baseURL}/stt`, {\n method: 'POST',\n headers: {\n // `defaultHeaders` first so Authorization always wins.\n ...this.defaultHeaders,\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: form,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(\n `Grok transcription request failed: ${response.status} ${errorText}`,\n )\n }\n\n const data = (await response.json()) as GrokSTTResponse\n\n const words: Array<TranscriptionWord> | undefined = data.words?.map(\n (w) => {\n // Construct a GrokTranscriptionWord so that `confidence` and\n // `speaker` (when xAI returns them under `diarize` / confidence\n // mode) are preserved on the result. The returned array is typed\n // as `Array<TranscriptionWord>` per the cross-provider contract;\n // callers who want the extras narrow via `as Array<GrokTranscriptionWord>`.\n const tw: GrokTranscriptionWord = {\n word: w.text,\n start: w.start,\n end: w.end,\n }\n if (w.confidence !== undefined) tw.confidence = w.confidence\n if (w.speaker !== undefined) tw.speaker = w.speaker\n return tw\n },\n )\n\n const resolvedLanguage = data.language ?? language\n // xAI's /v1/stt response carries no token counts — STT is duration-billed —\n // so surface the audio duration as `durationSeconds`, mirroring the\n // whisper-1 path in the OpenAI transcription adapter.\n const usage: TokenUsage | undefined =\n data.duration !== undefined && data.duration > 0\n ? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n durationSeconds: data.duration,\n }\n : undefined\n return {\n id: generateId(this.name),\n model,\n text: data.text,\n ...(resolvedLanguage !== undefined && { language: resolvedLanguage }),\n duration: data.duration,\n ...(words !== undefined && { words }),\n ...(usage !== undefined && { usage }),\n }\n } catch (error) {\n logger.errors('grok.transcribe fatal', {\n error,\n source: 'grok.transcribe',\n })\n throw error\n }\n }\n}\n\n/**\n * Build the multipart/form-data body for `POST /v1/stt`, coercing SDK-level\n * model options into xAI's wire format (booleans as `'true'`/`'false'`\n * strings, numeric fields stringified, etc.).\n *\n * Wire-field mapping:\n * - `modelOptions.inverse_text_normalization` → `format` (xAI's chosen\n * wire-field name for the ITN boolean; the SDK surfaces it under the\n * clearer `inverse_text_normalization` key).\n * - `modelOptions.audio_format`, `sample_rate`, `multichannel`, `channels`,\n * `diarize` map to same-named form fields.\n */\nexport function buildTranscriptionFormData(options: {\n file: File\n language: string | undefined\n modelOptions: GrokTranscriptionProviderOptions | undefined\n}): FormData {\n const { file, language, modelOptions } = options\n const form = new FormData()\n form.set('file', file)\n if (language) form.set('language', language)\n if (modelOptions?.audio_format !== undefined) {\n form.set('audio_format', modelOptions.audio_format)\n }\n if (modelOptions?.sample_rate !== undefined) {\n form.set('sample_rate', String(modelOptions.sample_rate))\n }\n if (modelOptions?.inverse_text_normalization !== undefined) {\n form.set(\n 'format',\n modelOptions.inverse_text_normalization ? 'true' : 'false',\n )\n }\n if (modelOptions?.multichannel !== undefined) {\n form.set('multichannel', modelOptions.multichannel ? 'true' : 'false')\n }\n if (modelOptions?.channels !== undefined) {\n form.set('channels', String(modelOptions.channels))\n }\n if (modelOptions?.diarize !== undefined) {\n form.set('diarize', modelOptions.diarize ? 'true' : 'false')\n }\n return form\n}\n\n/**\n * Creates a Grok transcription adapter with an explicit API key.\n *\n * @example\n * ```typescript\n * const adapter = createGrokTranscription('grok-stt', 'xai-...')\n * const result = await generateTranscription({\n * adapter,\n * audio: audioFile,\n * language: 'en',\n * })\n * ```\n */\nexport function createGrokTranscription<TModel extends GrokTranscriptionModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTranscriptionConfig, 'apiKey'>,\n): GrokTranscriptionAdapter<TModel> {\n return new GrokTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok transcription adapter, reading the API key from\n * `XAI_API_KEY` in the environment.\n *\n * @throws Error if `XAI_API_KEY` is not set.\n */\nexport function grokTranscription<TModel extends GrokTranscriptionModel>(\n model: TModel,\n config?: Omit<GrokTranscriptionConfig, 'apiKey'>,\n): GrokTranscriptionAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokTranscription(model, apiKey, config)\n}\n"],"mappings":"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"}