@tanstack/ai-grok 0.14.8 → 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.
- package/dist/esm/adapters/image.js +228 -193
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/summarize.js +49 -12
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.js +82 -38
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/transcription.js +133 -114
- package/dist/esm/adapters/transcription.js.map +1 -1
- package/dist/esm/adapters/tts.js +147 -130
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/adapters/video.js +275 -226
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/image/image-provider-options.js +64 -66
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/index.js +3 -31
- package/dist/esm/model-meta.js +149 -47
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +729 -812
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/index.js +3 -0
- package/dist/esm/realtime/token.js +78 -72
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/tools/index.js +57 -96
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/utils/audio.js +116 -162
- package/dist/esm/utils/audio.js.map +1 -1
- package/dist/esm/utils/client.js +22 -16
- package/dist/esm/utils/client.js.map +1 -1
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/schema-converter.js +2 -0
- package/dist/esm/video/video-provider-options.js +83 -57
- package/dist/esm/video/video-provider-options.js.map +1 -1
- package/package.json +8 -8
- package/src/realtime/adapter.ts +1 -1
- package/src/utils/audio.ts +1 -1
- package/dist/esm/index.js.map +0 -1
package/dist/esm/tools/index.js
CHANGED
|
@@ -1,113 +1,74 @@
|
|
|
1
|
-
import { brandProviderTool } from "@tanstack/ai";
|
|
2
1
|
import { convertFunctionToolToResponsesFormat } from "@tanstack/openai-base";
|
|
3
|
-
import {
|
|
2
|
+
import { brandProviderTool } from "@tanstack/ai";
|
|
3
|
+
//#region src/tools/index.ts
|
|
4
4
|
function providerTool(kind, description, metadata) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
return brandProviderTool({
|
|
6
|
+
name: kind,
|
|
7
|
+
description,
|
|
8
|
+
metadata: {
|
|
9
|
+
__kind: `grok.${kind}`,
|
|
10
|
+
...metadata
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
13
|
}
|
|
14
14
|
function grokWebSearchTool(config = {}) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
if (config.filters?.excluded_domains !== void 0 && config.filters.excluded_domains.length > 5) {
|
|
24
|
-
throw new Error("excluded_domains supports at most 5 domains.");
|
|
25
|
-
}
|
|
26
|
-
return providerTool("web_search", "Search the web", {
|
|
27
|
-
type: "web_search",
|
|
28
|
-
...config
|
|
29
|
-
});
|
|
15
|
+
if (config.filters?.allowed_domains !== void 0 && config.filters.excluded_domains !== void 0) throw new Error("allowed_domains and excluded_domains cannot both be provided.");
|
|
16
|
+
if (config.filters?.allowed_domains !== void 0 && config.filters.allowed_domains.length > 5) throw new Error("allowed_domains supports at most 5 domains.");
|
|
17
|
+
if (config.filters?.excluded_domains !== void 0 && config.filters.excluded_domains.length > 5) throw new Error("excluded_domains supports at most 5 domains.");
|
|
18
|
+
return providerTool("web_search", "Search the web", {
|
|
19
|
+
type: "web_search",
|
|
20
|
+
...config
|
|
21
|
+
});
|
|
30
22
|
}
|
|
31
23
|
function grokXSearchTool(config = {}) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
if (config.excluded_x_handles !== void 0 && config.excluded_x_handles.length > 20) {
|
|
41
|
-
throw new Error("excluded_x_handles supports at most 20 handles.");
|
|
42
|
-
}
|
|
43
|
-
return providerTool("x_search", "Search X posts", {
|
|
44
|
-
type: "x_search",
|
|
45
|
-
...config
|
|
46
|
-
});
|
|
24
|
+
if (config.allowed_x_handles !== void 0 && config.excluded_x_handles !== void 0) throw new Error("allowed_x_handles and excluded_x_handles cannot both be provided.");
|
|
25
|
+
if (config.allowed_x_handles !== void 0 && config.allowed_x_handles.length > 20) throw new Error("allowed_x_handles supports at most 20 handles.");
|
|
26
|
+
if (config.excluded_x_handles !== void 0 && config.excluded_x_handles.length > 20) throw new Error("excluded_x_handles supports at most 20 handles.");
|
|
27
|
+
return providerTool("x_search", "Search X posts", {
|
|
28
|
+
type: "x_search",
|
|
29
|
+
...config
|
|
30
|
+
});
|
|
47
31
|
}
|
|
48
32
|
function grokFileSearchTool(config) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return providerTool("file_search", "Search xAI file collections", {
|
|
58
|
-
type: "file_search",
|
|
59
|
-
...config
|
|
60
|
-
});
|
|
33
|
+
if (config.vector_store_ids.length === 0) throw new Error("vector_store_ids must contain at least one collection id.");
|
|
34
|
+
if (config.max_num_results !== void 0) {
|
|
35
|
+
if (config.max_num_results < 1 || config.max_num_results > 50) throw new Error("max_num_results must be between 1 and 50.");
|
|
36
|
+
}
|
|
37
|
+
return providerTool("file_search", "Search xAI file collections", {
|
|
38
|
+
type: "file_search",
|
|
39
|
+
...config
|
|
40
|
+
});
|
|
61
41
|
}
|
|
62
42
|
function grokMCPTool(config) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
...config
|
|
69
|
-
});
|
|
43
|
+
if (!config.server_url) throw new Error("server_url must be provided.");
|
|
44
|
+
return providerTool("mcp", config.server_description || "Remote MCP server", {
|
|
45
|
+
type: "mcp",
|
|
46
|
+
...config
|
|
47
|
+
});
|
|
70
48
|
}
|
|
71
49
|
function getGrokProviderToolKind(tool) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return "file_search";
|
|
80
|
-
case "grok.mcp":
|
|
81
|
-
return "mcp";
|
|
82
|
-
default:
|
|
83
|
-
return void 0;
|
|
84
|
-
}
|
|
50
|
+
switch (tool.metadata?.__kind) {
|
|
51
|
+
case "grok.web_search": return "web_search";
|
|
52
|
+
case "grok.x_search": return "x_search";
|
|
53
|
+
case "grok.file_search": return "file_search";
|
|
54
|
+
case "grok.mcp": return "mcp";
|
|
55
|
+
default: return;
|
|
56
|
+
}
|
|
85
57
|
}
|
|
86
58
|
function convertGrokProviderToolToAdapterFormat(tool, kind) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
const { __kind: _kind, ...toolConfig } = metadata;
|
|
94
|
-
return toolConfig;
|
|
59
|
+
const metadata = tool.metadata;
|
|
60
|
+
if (metadata.type !== kind) throw new Error(`convertGrokProviderToolToAdapterFormat: tool "${tool.name}" has mismatched Grok tool metadata.`);
|
|
61
|
+
const { __kind: _kind, ...toolConfig } = metadata;
|
|
62
|
+
return toolConfig;
|
|
95
63
|
}
|
|
96
64
|
function convertToolsToProviderFormat(tools) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return convertFunctionToolToResponsesFormat(tool);
|
|
103
|
-
});
|
|
65
|
+
return tools.map((tool) => {
|
|
66
|
+
const grokProviderToolKind = getGrokProviderToolKind(tool);
|
|
67
|
+
if (grokProviderToolKind) return convertGrokProviderToolToAdapterFormat(tool, grokProviderToolKind);
|
|
68
|
+
return convertFunctionToolToResponsesFormat(tool);
|
|
69
|
+
});
|
|
104
70
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
grokMCPTool,
|
|
110
|
-
grokWebSearchTool,
|
|
111
|
-
grokXSearchTool
|
|
112
|
-
};
|
|
113
|
-
//# sourceMappingURL=index.js.map
|
|
71
|
+
//#endregion
|
|
72
|
+
export { convertFunctionToolToResponsesFormat as convertFunctionToolToAdapterFormat, convertToolsToProviderFormat, grokFileSearchTool, grokMCPTool, grokWebSearchTool, grokXSearchTool };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/tools/index.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport { convertFunctionToolToResponsesFormat } from '@tanstack/openai-base'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\nimport type { ResponsesFunctionTool } from '@tanstack/openai-base'\nimport type { GrokProviderToolKind } from '../model-meta'\n\nexport type FunctionTool = ResponsesFunctionTool\n\nexport { convertFunctionToolToResponsesFormat as convertFunctionToolToAdapterFormat }\n\nexport type GrokProviderTool<TKind extends GrokProviderToolKind> = ProviderTool<\n 'grok',\n TKind\n>\n\ntype GrokToolKindMarker<TKind extends GrokProviderToolKind> = `grok.${TKind}`\n\nexport interface GrokWebSearchToolConfig {\n type: 'web_search'\n filters?: {\n allowed_domains?: Array<string>\n excluded_domains?: Array<string>\n }\n enable_image_understanding?: boolean\n enable_image_search?: boolean\n}\n\nexport interface GrokXSearchToolConfig {\n type: 'x_search'\n allowed_x_handles?: Array<string>\n excluded_x_handles?: Array<string>\n from_date?: string\n to_date?: string\n enable_image_understanding?: boolean\n enable_video_understanding?: boolean\n}\n\nexport interface GrokFileSearchToolConfig {\n type: 'file_search'\n vector_store_ids: Array<string>\n max_num_results?: number\n}\n\nexport interface GrokMCPToolConfig {\n type: 'mcp'\n server_label: string\n server_url: string\n allowed_tools?: Array<string>\n server_description?: string\n authorization?: string\n headers?: Record<string, string>\n}\n\nexport type GrokServerTool =\n | GrokWebSearchToolConfig\n | GrokXSearchToolConfig\n | GrokFileSearchToolConfig\n | GrokMCPToolConfig\n\ntype GrokProviderToolMetadata<TKind extends GrokProviderToolKind> = Extract<\n GrokServerTool,\n { type: TKind }\n> & {\n __kind: GrokToolKindMarker<TKind>\n}\n\nexport type GrokResponsesTool = GrokServerTool | ResponsesFunctionTool\n\nfunction providerTool<TKind extends GrokProviderToolKind>(\n kind: TKind,\n description: string,\n metadata: Extract<GrokServerTool, { type: TKind }>,\n): GrokProviderTool<TKind> {\n return brandProviderTool<GrokProviderTool<TKind>>({\n name: kind,\n description,\n metadata: {\n __kind: `grok.${kind}`,\n ...metadata,\n },\n })\n}\n\nexport function grokWebSearchTool(\n config: Omit<GrokWebSearchToolConfig, 'type'> = {},\n): GrokProviderTool<'web_search'> {\n if (\n config.filters?.allowed_domains !== undefined &&\n config.filters.excluded_domains !== undefined\n ) {\n throw new Error(\n 'allowed_domains and excluded_domains cannot both be provided.',\n )\n }\n if (\n config.filters?.allowed_domains !== undefined &&\n config.filters.allowed_domains.length > 5\n ) {\n throw new Error('allowed_domains supports at most 5 domains.')\n }\n if (\n config.filters?.excluded_domains !== undefined &&\n config.filters.excluded_domains.length > 5\n ) {\n throw new Error('excluded_domains supports at most 5 domains.')\n }\n return providerTool('web_search', 'Search the web', {\n type: 'web_search',\n ...config,\n })\n}\n\nexport function grokXSearchTool(\n config: Omit<GrokXSearchToolConfig, 'type'> = {},\n): GrokProviderTool<'x_search'> {\n if (\n config.allowed_x_handles !== undefined &&\n config.excluded_x_handles !== undefined\n ) {\n throw new Error(\n 'allowed_x_handles and excluded_x_handles cannot both be provided.',\n )\n }\n if (\n config.allowed_x_handles !== undefined &&\n config.allowed_x_handles.length > 20\n ) {\n throw new Error('allowed_x_handles supports at most 20 handles.')\n }\n if (\n config.excluded_x_handles !== undefined &&\n config.excluded_x_handles.length > 20\n ) {\n throw new Error('excluded_x_handles supports at most 20 handles.')\n }\n return providerTool('x_search', 'Search X posts', {\n type: 'x_search',\n ...config,\n })\n}\n\nexport function grokFileSearchTool(\n config: Omit<GrokFileSearchToolConfig, 'type'>,\n): GrokProviderTool<'file_search'> {\n if (config.vector_store_ids.length === 0) {\n throw new Error('vector_store_ids must contain at least one collection id.')\n }\n if (config.max_num_results !== undefined) {\n if (config.max_num_results < 1 || config.max_num_results > 50) {\n throw new Error('max_num_results must be between 1 and 50.')\n }\n }\n return providerTool('file_search', 'Search xAI file collections', {\n type: 'file_search',\n ...config,\n })\n}\n\nexport function grokMCPTool(\n config: Omit<GrokMCPToolConfig, 'type'>,\n): GrokProviderTool<'mcp'> {\n if (!config.server_url) {\n throw new Error('server_url must be provided.')\n }\n return providerTool('mcp', config.server_description || 'Remote MCP server', {\n type: 'mcp',\n ...config,\n })\n}\n\nfunction getGrokProviderToolKind(tool: Tool): GrokProviderToolKind | undefined {\n const kind = (tool.metadata as { __kind?: unknown } | undefined)?.__kind\n switch (kind) {\n case 'grok.web_search':\n return 'web_search'\n case 'grok.x_search':\n return 'x_search'\n case 'grok.file_search':\n return 'file_search'\n case 'grok.mcp':\n return 'mcp'\n default:\n return undefined\n }\n}\n\nfunction convertGrokProviderToolToAdapterFormat(\n tool: Tool,\n kind: GrokProviderToolKind,\n): GrokServerTool {\n const metadata = tool.metadata as GrokProviderToolMetadata<typeof kind>\n if (metadata.type !== kind) {\n throw new Error(\n `convertGrokProviderToolToAdapterFormat: tool \"${tool.name}\" has mismatched Grok tool metadata.`,\n )\n }\n const { __kind: _kind, ...toolConfig } = metadata\n void _kind\n return toolConfig\n}\n\nexport function convertToolsToProviderFormat(\n tools: Array<Tool>,\n): Array<GrokResponsesTool> {\n return tools.map((tool) => {\n const grokProviderToolKind = getGrokProviderToolKind(tool)\n if (grokProviderToolKind) {\n return convertGrokProviderToolToAdapterFormat(tool, grokProviderToolKind)\n }\n return convertFunctionToolToResponsesFormat(tool)\n })\n}\n"],"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/tools/index.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport { convertFunctionToolToResponsesFormat } from '@tanstack/openai-base'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\nimport type { ResponsesFunctionTool } from '@tanstack/openai-base'\nimport type { GrokProviderToolKind } from '../model-meta'\n\nexport type FunctionTool = ResponsesFunctionTool\n\nexport { convertFunctionToolToResponsesFormat as convertFunctionToolToAdapterFormat }\n\nexport type GrokProviderTool<TKind extends GrokProviderToolKind> = ProviderTool<\n 'grok',\n TKind\n>\n\ntype GrokToolKindMarker<TKind extends GrokProviderToolKind> = `grok.${TKind}`\n\nexport interface GrokWebSearchToolConfig {\n type: 'web_search'\n filters?: {\n allowed_domains?: Array<string>\n excluded_domains?: Array<string>\n }\n enable_image_understanding?: boolean\n enable_image_search?: boolean\n}\n\nexport interface GrokXSearchToolConfig {\n type: 'x_search'\n allowed_x_handles?: Array<string>\n excluded_x_handles?: Array<string>\n from_date?: string\n to_date?: string\n enable_image_understanding?: boolean\n enable_video_understanding?: boolean\n}\n\nexport interface GrokFileSearchToolConfig {\n type: 'file_search'\n vector_store_ids: Array<string>\n max_num_results?: number\n}\n\nexport interface GrokMCPToolConfig {\n type: 'mcp'\n server_label: string\n server_url: string\n allowed_tools?: Array<string>\n server_description?: string\n authorization?: string\n headers?: Record<string, string>\n}\n\nexport type GrokServerTool =\n | GrokWebSearchToolConfig\n | GrokXSearchToolConfig\n | GrokFileSearchToolConfig\n | GrokMCPToolConfig\n\ntype GrokProviderToolMetadata<TKind extends GrokProviderToolKind> = Extract<\n GrokServerTool,\n { type: TKind }\n> & {\n __kind: GrokToolKindMarker<TKind>\n}\n\nexport type GrokResponsesTool = GrokServerTool | ResponsesFunctionTool\n\nfunction providerTool<TKind extends GrokProviderToolKind>(\n kind: TKind,\n description: string,\n metadata: Extract<GrokServerTool, { type: TKind }>,\n): GrokProviderTool<TKind> {\n return brandProviderTool<GrokProviderTool<TKind>>({\n name: kind,\n description,\n metadata: {\n __kind: `grok.${kind}`,\n ...metadata,\n },\n })\n}\n\nexport function grokWebSearchTool(\n config: Omit<GrokWebSearchToolConfig, 'type'> = {},\n): GrokProviderTool<'web_search'> {\n if (\n config.filters?.allowed_domains !== undefined &&\n config.filters.excluded_domains !== undefined\n ) {\n throw new Error(\n 'allowed_domains and excluded_domains cannot both be provided.',\n )\n }\n if (\n config.filters?.allowed_domains !== undefined &&\n config.filters.allowed_domains.length > 5\n ) {\n throw new Error('allowed_domains supports at most 5 domains.')\n }\n if (\n config.filters?.excluded_domains !== undefined &&\n config.filters.excluded_domains.length > 5\n ) {\n throw new Error('excluded_domains supports at most 5 domains.')\n }\n return providerTool('web_search', 'Search the web', {\n type: 'web_search',\n ...config,\n })\n}\n\nexport function grokXSearchTool(\n config: Omit<GrokXSearchToolConfig, 'type'> = {},\n): GrokProviderTool<'x_search'> {\n if (\n config.allowed_x_handles !== undefined &&\n config.excluded_x_handles !== undefined\n ) {\n throw new Error(\n 'allowed_x_handles and excluded_x_handles cannot both be provided.',\n )\n }\n if (\n config.allowed_x_handles !== undefined &&\n config.allowed_x_handles.length > 20\n ) {\n throw new Error('allowed_x_handles supports at most 20 handles.')\n }\n if (\n config.excluded_x_handles !== undefined &&\n config.excluded_x_handles.length > 20\n ) {\n throw new Error('excluded_x_handles supports at most 20 handles.')\n }\n return providerTool('x_search', 'Search X posts', {\n type: 'x_search',\n ...config,\n })\n}\n\nexport function grokFileSearchTool(\n config: Omit<GrokFileSearchToolConfig, 'type'>,\n): GrokProviderTool<'file_search'> {\n if (config.vector_store_ids.length === 0) {\n throw new Error('vector_store_ids must contain at least one collection id.')\n }\n if (config.max_num_results !== undefined) {\n if (config.max_num_results < 1 || config.max_num_results > 50) {\n throw new Error('max_num_results must be between 1 and 50.')\n }\n }\n return providerTool('file_search', 'Search xAI file collections', {\n type: 'file_search',\n ...config,\n })\n}\n\nexport function grokMCPTool(\n config: Omit<GrokMCPToolConfig, 'type'>,\n): GrokProviderTool<'mcp'> {\n if (!config.server_url) {\n throw new Error('server_url must be provided.')\n }\n return providerTool('mcp', config.server_description || 'Remote MCP server', {\n type: 'mcp',\n ...config,\n })\n}\n\nfunction getGrokProviderToolKind(tool: Tool): GrokProviderToolKind | undefined {\n const kind = (tool.metadata as { __kind?: unknown } | undefined)?.__kind\n switch (kind) {\n case 'grok.web_search':\n return 'web_search'\n case 'grok.x_search':\n return 'x_search'\n case 'grok.file_search':\n return 'file_search'\n case 'grok.mcp':\n return 'mcp'\n default:\n return undefined\n }\n}\n\nfunction convertGrokProviderToolToAdapterFormat(\n tool: Tool,\n kind: GrokProviderToolKind,\n): GrokServerTool {\n const metadata = tool.metadata as GrokProviderToolMetadata<typeof kind>\n if (metadata.type !== kind) {\n throw new Error(\n `convertGrokProviderToolToAdapterFormat: tool \"${tool.name}\" has mismatched Grok tool metadata.`,\n )\n }\n const { __kind: _kind, ...toolConfig } = metadata\n void _kind\n return toolConfig\n}\n\nexport function convertToolsToProviderFormat(\n tools: Array<Tool>,\n): Array<GrokResponsesTool> {\n return tools.map((tool) => {\n const grokProviderToolKind = getGrokProviderToolKind(tool)\n if (grokProviderToolKind) {\n return convertGrokProviderToolToAdapterFormat(tool, grokProviderToolKind)\n }\n return convertFunctionToolToResponsesFormat(tool)\n })\n}\n"],"mappings":";;;AAoEA,SAAS,aACP,MACA,aACA,UACyB;CACzB,OAAO,kBAA2C;EAChD,MAAM;EACN;EACA,UAAU;GACR,QAAQ,QAAQ;GAChB,GAAG;EACL;CACF,CAAC;AACH;AAEA,SAAgB,kBACd,SAAgD,CAAC,GACjB;CAChC,IACE,OAAO,SAAS,oBAAoB,KAAA,KACpC,OAAO,QAAQ,qBAAqB,KAAA,GAEpC,MAAM,IAAI,MACR,+DACF;CAEF,IACE,OAAO,SAAS,oBAAoB,KAAA,KACpC,OAAO,QAAQ,gBAAgB,SAAS,GAExC,MAAM,IAAI,MAAM,6CAA6C;CAE/D,IACE,OAAO,SAAS,qBAAqB,KAAA,KACrC,OAAO,QAAQ,iBAAiB,SAAS,GAEzC,MAAM,IAAI,MAAM,8CAA8C;CAEhE,OAAO,aAAa,cAAc,kBAAkB;EAClD,MAAM;EACN,GAAG;CACL,CAAC;AACH;AAEA,SAAgB,gBACd,SAA8C,CAAC,GACjB;CAC9B,IACE,OAAO,sBAAsB,KAAA,KAC7B,OAAO,uBAAuB,KAAA,GAE9B,MAAM,IAAI,MACR,mEACF;CAEF,IACE,OAAO,sBAAsB,KAAA,KAC7B,OAAO,kBAAkB,SAAS,IAElC,MAAM,IAAI,MAAM,gDAAgD;CAElE,IACE,OAAO,uBAAuB,KAAA,KAC9B,OAAO,mBAAmB,SAAS,IAEnC,MAAM,IAAI,MAAM,iDAAiD;CAEnE,OAAO,aAAa,YAAY,kBAAkB;EAChD,MAAM;EACN,GAAG;CACL,CAAC;AACH;AAEA,SAAgB,mBACd,QACiC;CACjC,IAAI,OAAO,iBAAiB,WAAW,GACrC,MAAM,IAAI,MAAM,2DAA2D;CAE7E,IAAI,OAAO,oBAAoB,KAAA;MACzB,OAAO,kBAAkB,KAAK,OAAO,kBAAkB,IACzD,MAAM,IAAI,MAAM,2CAA2C;CAAA;CAG/D,OAAO,aAAa,eAAe,+BAA+B;EAChE,MAAM;EACN,GAAG;CACL,CAAC;AACH;AAEA,SAAgB,YACd,QACyB;CACzB,IAAI,CAAC,OAAO,YACV,MAAM,IAAI,MAAM,8BAA8B;CAEhD,OAAO,aAAa,OAAO,OAAO,sBAAsB,qBAAqB;EAC3E,MAAM;EACN,GAAG;CACL,CAAC;AACH;AAEA,SAAS,wBAAwB,MAA8C;CAE7E,QADc,KAAK,UAA+C,QAClE;EACE,KAAK,mBACH,OAAO;EACT,KAAK,iBACH,OAAO;EACT,KAAK,oBACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,SACE;CACJ;AACF;AAEA,SAAS,uCACP,MACA,MACgB;CAChB,MAAM,WAAW,KAAK;CACtB,IAAI,SAAS,SAAS,MACpB,MAAM,IAAI,MACR,iDAAiD,KAAK,KAAK,qCAC7D;CAEF,MAAM,EAAE,QAAQ,OAAO,GAAG,eAAe;CAEzC,OAAO;AACT;AAEA,SAAgB,6BACd,OAC0B;CAC1B,OAAO,MAAM,KAAK,SAAS;EACzB,MAAM,uBAAuB,wBAAwB,IAAI;EACzD,IAAI,sBACF,OAAO,uCAAuC,MAAM,oBAAoB;EAE1E,OAAO,qCAAqC,IAAI;CAClD,CAAC;AACH"}
|
package/dist/esm/utils/audio.js
CHANGED
|
@@ -1,172 +1,126 @@
|
|
|
1
|
+
//#region src/utils/audio.ts
|
|
2
|
+
/**
|
|
3
|
+
* Coerce the various audio input shapes accepted by `TranscriptionOptions.audio`
|
|
4
|
+
* into a `File` suitable for `multipart/form-data` uploads.
|
|
5
|
+
*
|
|
6
|
+
* For base64 string inputs we require an explicit MIME type — either via a
|
|
7
|
+
* `data:<mime>;base64,<payload>` URI prefix, or via the caller-provided
|
|
8
|
+
* `audioFormat` parameter. Bare base64 without either is rejected, because
|
|
9
|
+
* silently defaulting to `audio/mpeg` misreports non-mp3 audio to the server.
|
|
10
|
+
*
|
|
11
|
+
* The same rule applies to raw `ArrayBuffer` inputs: the caller must supply
|
|
12
|
+
* an `audioFormat` so we know what MIME type and extension to use.
|
|
13
|
+
*/
|
|
1
14
|
function toAudioFile(audio, audioFormat) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
if (typeof audio === "string") {
|
|
39
|
-
if (audio.startsWith("data:")) {
|
|
40
|
-
const [header, base64Data] = audio.split(",");
|
|
41
|
-
const headerMatch = header?.match(/data:([^;]+)/);
|
|
42
|
-
const uriMimeType = headerMatch?.[1];
|
|
43
|
-
if (!uriMimeType) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
"Malformed data: URI in toAudioFile: cannot parse MIME type — expected data:<mime>[;charset=…][;base64],<payload>"
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
if (base64Data === void 0 || base64Data.trim() === "") {
|
|
49
|
-
throw new Error(
|
|
50
|
-
"Malformed data: URI in toAudioFile: missing base64 payload after comma"
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
const mimeType2 = audioFormat ? toMimeType(audioFormat) : uriMimeType;
|
|
54
|
-
const buffer2 = base64ToArrayBuffer(base64Data);
|
|
55
|
-
return new File([buffer2], `audio.${extensionFor(mimeType2)}`, {
|
|
56
|
-
type: mimeType2
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (!audioFormat) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
"toAudioFile requires a data: URI (e.g. data:audio/wav;base64,...) or an explicit audioFormat argument — bare base64 strings have no MIME type to infer"
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
const buffer = base64ToArrayBuffer(audio);
|
|
65
|
-
const mimeType = toMimeType(audioFormat);
|
|
66
|
-
return new File([buffer], `audio.${extensionFor(mimeType)}`, {
|
|
67
|
-
type: mimeType
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
throw new Error("Invalid audio input type");
|
|
15
|
+
if (typeof File !== "undefined" && audio instanceof File) {
|
|
16
|
+
if (audioFormat) {
|
|
17
|
+
const mimeType = toMimeType(audioFormat);
|
|
18
|
+
return new File([audio], `audio.${extensionFor(mimeType)}`, { type: mimeType });
|
|
19
|
+
}
|
|
20
|
+
if (audio.type) return audio;
|
|
21
|
+
throw new Error("toAudioFile cannot infer type for File input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")");
|
|
22
|
+
}
|
|
23
|
+
if (typeof Blob !== "undefined" && audio instanceof Blob) {
|
|
24
|
+
const mimeType = audioFormat ? toMimeType(audioFormat) : audio.type || void 0;
|
|
25
|
+
if (!mimeType) throw new Error("toAudioFile cannot infer type for Blob input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")");
|
|
26
|
+
return new File([audio], `audio.${extensionFor(mimeType)}`, { type: mimeType });
|
|
27
|
+
}
|
|
28
|
+
if (audio instanceof ArrayBuffer) {
|
|
29
|
+
if (!audioFormat) throw new Error("toAudioFile cannot infer type for ArrayBuffer input — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")");
|
|
30
|
+
const mimeType = toMimeType(audioFormat);
|
|
31
|
+
return new File([audio], `audio.${extensionFor(mimeType)}`, { type: mimeType });
|
|
32
|
+
}
|
|
33
|
+
if (typeof audio === "string") {
|
|
34
|
+
if (audio.startsWith("data:")) {
|
|
35
|
+
const [header, base64Data] = audio.split(",");
|
|
36
|
+
const uriMimeType = (header?.match(/data:([^;]+)/))?.[1];
|
|
37
|
+
if (!uriMimeType) throw new Error("Malformed data: URI in toAudioFile: cannot parse MIME type — expected data:<mime>[;charset=…][;base64],<payload>");
|
|
38
|
+
if (base64Data === void 0 || base64Data.trim() === "") throw new Error("Malformed data: URI in toAudioFile: missing base64 payload after comma");
|
|
39
|
+
const mimeType = audioFormat ? toMimeType(audioFormat) : uriMimeType;
|
|
40
|
+
const buffer = base64ToArrayBuffer(base64Data);
|
|
41
|
+
return new File([buffer], `audio.${extensionFor(mimeType)}`, { type: mimeType });
|
|
42
|
+
}
|
|
43
|
+
if (!audioFormat) throw new Error("toAudioFile requires a data: URI (e.g. data:audio/wav;base64,...) or an explicit audioFormat argument — bare base64 strings have no MIME type to infer");
|
|
44
|
+
const buffer = base64ToArrayBuffer(audio);
|
|
45
|
+
const mimeType = toMimeType(audioFormat);
|
|
46
|
+
return new File([buffer], `audio.${extensionFor(mimeType)}`, { type: mimeType });
|
|
47
|
+
}
|
|
48
|
+
throw new Error("Invalid audio input type");
|
|
71
49
|
}
|
|
72
50
|
function toMimeType(audioFormat) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
case "m4a":
|
|
91
|
-
return "audio/mp4";
|
|
92
|
-
case "webm":
|
|
93
|
-
return "audio/webm";
|
|
94
|
-
case "pcm":
|
|
95
|
-
return "audio/L16";
|
|
96
|
-
case "mulaw":
|
|
97
|
-
return "audio/basic";
|
|
98
|
-
case "alaw":
|
|
99
|
-
return "audio/x-alaw-basic";
|
|
100
|
-
default:
|
|
101
|
-
return `audio/${ext}`;
|
|
102
|
-
}
|
|
51
|
+
if (audioFormat.includes("/")) return audioFormat;
|
|
52
|
+
const ext = audioFormat.toLowerCase();
|
|
53
|
+
switch (ext) {
|
|
54
|
+
case "mp3": return "audio/mpeg";
|
|
55
|
+
case "wav": return "audio/wav";
|
|
56
|
+
case "ogg": return "audio/ogg";
|
|
57
|
+
case "opus": return "audio/opus";
|
|
58
|
+
case "flac": return "audio/flac";
|
|
59
|
+
case "aac": return "audio/aac";
|
|
60
|
+
case "mp4": return "audio/mp4";
|
|
61
|
+
case "m4a": return "audio/mp4";
|
|
62
|
+
case "webm": return "audio/webm";
|
|
63
|
+
case "pcm": return "audio/L16";
|
|
64
|
+
case "mulaw": return "audio/basic";
|
|
65
|
+
case "alaw": return "audio/x-alaw-basic";
|
|
66
|
+
default: return `audio/${ext}`;
|
|
67
|
+
}
|
|
103
68
|
}
|
|
104
69
|
function extensionFor(mimeType) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return "pcm";
|
|
125
|
-
case "audio/basic":
|
|
126
|
-
return "mulaw";
|
|
127
|
-
case "audio/x-alaw-basic":
|
|
128
|
-
return "alaw";
|
|
129
|
-
default: {
|
|
130
|
-
const slash = mimeType.indexOf("/");
|
|
131
|
-
if (slash === -1) return "bin";
|
|
132
|
-
return mimeType.slice(slash + 1) || "bin";
|
|
133
|
-
}
|
|
134
|
-
}
|
|
70
|
+
switch (mimeType) {
|
|
71
|
+
case "audio/mpeg": return "mp3";
|
|
72
|
+
case "audio/wav":
|
|
73
|
+
case "audio/x-wav": return "wav";
|
|
74
|
+
case "audio/ogg": return "ogg";
|
|
75
|
+
case "audio/opus": return "opus";
|
|
76
|
+
case "audio/flac": return "flac";
|
|
77
|
+
case "audio/aac": return "aac";
|
|
78
|
+
case "audio/mp4": return "m4a";
|
|
79
|
+
case "audio/webm": return "webm";
|
|
80
|
+
case "audio/L16": return "pcm";
|
|
81
|
+
case "audio/basic": return "mulaw";
|
|
82
|
+
case "audio/x-alaw-basic": return "alaw";
|
|
83
|
+
default: {
|
|
84
|
+
const slash = mimeType.indexOf("/");
|
|
85
|
+
if (slash === -1) return "bin";
|
|
86
|
+
return mimeType.slice(slash + 1) || "bin";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
135
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Cross-runtime ArrayBuffer → base64 conversion.
|
|
92
|
+
*
|
|
93
|
+
* Uses Node's `Buffer` when available (fastest path on server) and falls
|
|
94
|
+
* back to `btoa` + chunked `String.fromCharCode` everywhere else (browser,
|
|
95
|
+
* Cloudflare Workers, Bun, Deno). Chunking is required because a very
|
|
96
|
+
* large audio buffer spread into `String.fromCharCode(...bytes)` in one
|
|
97
|
+
* call can hit `Maximum call stack size exceeded`.
|
|
98
|
+
*/
|
|
136
99
|
function arrayBufferToBase64(buffer) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
null,
|
|
147
|
-
// eslint-disable-next-line no-restricted-syntax -- TS lib types String.fromCharCode.apply as Array<number> but runtime accepts any ArrayLike
|
|
148
|
-
bytes.subarray(i, end)
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
return btoa(binary);
|
|
100
|
+
if (typeof Buffer !== "undefined") return Buffer.from(buffer).toString("base64");
|
|
101
|
+
const bytes = new Uint8Array(buffer);
|
|
102
|
+
const chunkSize = 32768;
|
|
103
|
+
let binary = "";
|
|
104
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
105
|
+
const end = Math.min(i + chunkSize, bytes.length);
|
|
106
|
+
binary += String.fromCharCode.apply(null, bytes.subarray(i, end));
|
|
107
|
+
}
|
|
108
|
+
return btoa(binary);
|
|
152
109
|
}
|
|
153
110
|
function base64ToArrayBuffer(base64) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
return buffer;
|
|
111
|
+
let binary;
|
|
112
|
+
try {
|
|
113
|
+
binary = atob(base64);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
116
|
+
throw new Error(`Invalid base64 input to toAudioFile: ${msg}`);
|
|
117
|
+
}
|
|
118
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
119
|
+
const bytes = new Uint8Array(buffer);
|
|
120
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
121
|
+
return buffer;
|
|
167
122
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//# sourceMappingURL=audio.js.map
|
|
123
|
+
//#endregion
|
|
124
|
+
export { arrayBufferToBase64, toAudioFile };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio.js","sources":["../../../src/utils/audio.ts"],"sourcesContent":["/**\n * Coerce the various audio input shapes accepted by `TranscriptionOptions.audio`\n * into a `File` suitable for `multipart/form-data` uploads.\n *\n * For base64 string inputs we require an explicit MIME type — either via a\n * `data:<mime>;base64,<payload>` URI prefix, or via the caller-provided\n * `audioFormat` parameter. Bare base64 without either is rejected, because\n * silently defaulting to `audio/mpeg` misreports non-mp3 audio to the server.\n *\n * The same rule applies to raw `ArrayBuffer` inputs: the caller must supply\n * an `audioFormat` so we know what MIME type and extension to use.\n */\nexport function toAudioFile(\n audio: string | File | Blob | ArrayBuffer,\n audioFormat?: string,\n): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n // Prefer the caller-supplied `audioFormat` over a potentially empty or\n // incorrect `File.type` — callers pass `audioFormat` precisely because\n // they have more context than the browser does about the payload. If\n // neither is set, fall through to the Blob-style error path below.\n if (audioFormat) {\n const mimeType = toMimeType(audioFormat)\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n if (audio.type) {\n return audio\n }\n throw new Error(\n 'toAudioFile cannot infer type for File input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n // Mirror the ArrayBuffer / bare-base64 paths: prefer the explicit\n // audioFormat argument over the Blob's (often empty) .type. We refuse to\n // fall back to `application/octet-stream` because that mislabels audio\n // for the server.\n const mimeType = audioFormat\n ? toMimeType(audioFormat)\n : audio.type || undefined\n if (!mimeType) {\n throw new Error(\n 'toAudioFile cannot infer type for Blob input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (audio instanceof ArrayBuffer) {\n if (!audioFormat) {\n throw new Error(\n 'toAudioFile cannot infer type for ArrayBuffer input — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n const mimeType = toMimeType(audioFormat)\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (typeof audio === 'string') {\n if (audio.startsWith('data:')) {\n const [header, base64Data] = audio.split(',')\n // Fail loudly on malformed data: URIs instead of silently defaulting\n // to `audio/mpeg` — the file's contract is that we never mislabel\n // audio for the server.\n const headerMatch = header?.match(/data:([^;]+)/)\n const uriMimeType = headerMatch?.[1]\n if (!uriMimeType) {\n throw new Error(\n 'Malformed data: URI in toAudioFile: cannot parse MIME type — expected data:<mime>[;charset=…][;base64],<payload>',\n )\n }\n if (base64Data === undefined || base64Data.trim() === '') {\n throw new Error(\n 'Malformed data: URI in toAudioFile: missing base64 payload after comma',\n )\n }\n // Caller-supplied `audioFormat` wins over the URI-embedded MIME: the\n // caller has more context (the URI MIME may be wrong, or a generic\n // `application/octet-stream`).\n const mimeType = audioFormat ? toMimeType(audioFormat) : uriMimeType\n const buffer = base64ToArrayBuffer(base64Data)\n return new File([buffer], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (!audioFormat) {\n throw new Error(\n 'toAudioFile requires a data: URI (e.g. data:audio/wav;base64,...) or an explicit audioFormat argument — bare base64 strings have no MIME type to infer',\n )\n }\n\n const buffer = base64ToArrayBuffer(audio)\n const mimeType = toMimeType(audioFormat)\n return new File([buffer], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n throw new Error('Invalid audio input type')\n}\n\nfunction toMimeType(audioFormat: string): string {\n // Accept either \"audio/…\" strings or bare extensions like \"mp3\".\n if (audioFormat.includes('/')) return audioFormat\n const ext = audioFormat.toLowerCase()\n switch (ext) {\n case 'mp3':\n return 'audio/mpeg'\n case 'wav':\n return 'audio/wav'\n case 'ogg':\n return 'audio/ogg'\n case 'opus':\n return 'audio/opus'\n case 'flac':\n return 'audio/flac'\n case 'aac':\n return 'audio/aac'\n case 'mp4':\n return 'audio/mp4'\n case 'm4a':\n return 'audio/mp4'\n case 'webm':\n return 'audio/webm'\n case 'pcm':\n return 'audio/L16'\n case 'mulaw':\n return 'audio/basic'\n case 'alaw':\n return 'audio/x-alaw-basic'\n default:\n return `audio/${ext}`\n }\n}\n\nfunction extensionFor(mimeType: string): string {\n switch (mimeType) {\n case 'audio/mpeg':\n return 'mp3'\n case 'audio/wav':\n case 'audio/x-wav':\n return 'wav'\n case 'audio/ogg':\n return 'ogg'\n case 'audio/opus':\n return 'opus'\n case 'audio/flac':\n return 'flac'\n case 'audio/aac':\n return 'aac'\n case 'audio/mp4':\n return 'm4a'\n case 'audio/webm':\n return 'webm'\n case 'audio/L16':\n return 'pcm'\n case 'audio/basic':\n return 'mulaw'\n case 'audio/x-alaw-basic':\n return 'alaw'\n default: {\n const slash = mimeType.indexOf('/')\n if (slash === -1) return 'bin'\n return mimeType.slice(slash + 1) || 'bin'\n }\n }\n}\n\n/**\n * Cross-runtime ArrayBuffer → base64 conversion.\n *\n * Uses Node's `Buffer` when available (fastest path on server) and falls\n * back to `btoa` + chunked `String.fromCharCode` everywhere else (browser,\n * Cloudflare Workers, Bun, Deno). Chunking is required because a very\n * large audio buffer spread into `String.fromCharCode(...bytes)` in one\n * call can hit `Maximum call stack size exceeded`.\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(buffer).toString('base64')\n }\n const bytes = new Uint8Array(buffer)\n const chunkSize = 0x8000\n let binary = ''\n for (let i = 0; i < bytes.length; i += chunkSize) {\n const end = Math.min(i + chunkSize, bytes.length)\n binary += String.fromCharCode.apply(\n null,\n // eslint-disable-next-line no-restricted-syntax -- TS lib types String.fromCharCode.apply as Array<number> but runtime accepts any ArrayLike\n bytes.subarray(i, end) as unknown as Array<number>,\n )\n }\n return btoa(binary)\n}\n\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n let binary: string\n try {\n binary = atob(base64)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`Invalid base64 input to toAudioFile: ${msg}`)\n }\n const buffer = new ArrayBuffer(binary.length)\n const bytes = new Uint8Array(buffer)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return buffer\n}\n"],"names":["mimeType","buffer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audio.js","names":[],"sources":["../../../src/utils/audio.ts"],"sourcesContent":["/**\n * Coerce the various audio input shapes accepted by `TranscriptionOptions.audio`\n * into a `File` suitable for `multipart/form-data` uploads.\n *\n * For base64 string inputs we require an explicit MIME type — either via a\n * `data:<mime>;base64,<payload>` URI prefix, or via the caller-provided\n * `audioFormat` parameter. Bare base64 without either is rejected, because\n * silently defaulting to `audio/mpeg` misreports non-mp3 audio to the server.\n *\n * The same rule applies to raw `ArrayBuffer` inputs: the caller must supply\n * an `audioFormat` so we know what MIME type and extension to use.\n */\nexport function toAudioFile(\n audio: string | File | Blob | ArrayBuffer,\n audioFormat?: string,\n): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n // Prefer the caller-supplied `audioFormat` over a potentially empty or\n // incorrect `File.type` — callers pass `audioFormat` precisely because\n // they have more context than the browser does about the payload. If\n // neither is set, fall through to the Blob-style error path below.\n if (audioFormat) {\n const mimeType = toMimeType(audioFormat)\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n if (audio.type) {\n return audio\n }\n throw new Error(\n 'toAudioFile cannot infer type for File input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n // Mirror the ArrayBuffer / bare-base64 paths: prefer the explicit\n // audioFormat argument over the Blob's (often empty) .type. We refuse to\n // fall back to `application/octet-stream` because that mislabels audio\n // for the server.\n const mimeType = audioFormat\n ? toMimeType(audioFormat)\n : audio.type || undefined\n if (!mimeType) {\n throw new Error(\n 'toAudioFile cannot infer type for Blob input with empty .type — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (audio instanceof ArrayBuffer) {\n if (!audioFormat) {\n throw new Error(\n 'toAudioFile cannot infer type for ArrayBuffer input — pass an explicit audioFormat (e.g. \"mp3\", \"wav\", \"audio/mpeg\")',\n )\n }\n const mimeType = toMimeType(audioFormat)\n return new File([audio], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (typeof audio === 'string') {\n if (audio.startsWith('data:')) {\n const [header, base64Data] = audio.split(',')\n // Fail loudly on malformed data: URIs instead of silently defaulting\n // to `audio/mpeg` — the file's contract is that we never mislabel\n // audio for the server.\n const headerMatch = header?.match(/data:([^;]+)/)\n const uriMimeType = headerMatch?.[1]\n if (!uriMimeType) {\n throw new Error(\n 'Malformed data: URI in toAudioFile: cannot parse MIME type — expected data:<mime>[;charset=…][;base64],<payload>',\n )\n }\n if (base64Data === undefined || base64Data.trim() === '') {\n throw new Error(\n 'Malformed data: URI in toAudioFile: missing base64 payload after comma',\n )\n }\n // Caller-supplied `audioFormat` wins over the URI-embedded MIME: the\n // caller has more context (the URI MIME may be wrong, or a generic\n // `application/octet-stream`).\n const mimeType = audioFormat ? toMimeType(audioFormat) : uriMimeType\n const buffer = base64ToArrayBuffer(base64Data)\n return new File([buffer], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (!audioFormat) {\n throw new Error(\n 'toAudioFile requires a data: URI (e.g. data:audio/wav;base64,...) or an explicit audioFormat argument — bare base64 strings have no MIME type to infer',\n )\n }\n\n const buffer = base64ToArrayBuffer(audio)\n const mimeType = toMimeType(audioFormat)\n return new File([buffer], `audio.${extensionFor(mimeType)}`, {\n type: mimeType,\n })\n }\n\n throw new Error('Invalid audio input type')\n}\n\nfunction toMimeType(audioFormat: string): string {\n // Accept either \"audio/…\" strings or bare extensions like \"mp3\".\n if (audioFormat.includes('/')) return audioFormat\n const ext = audioFormat.toLowerCase()\n switch (ext) {\n case 'mp3':\n return 'audio/mpeg'\n case 'wav':\n return 'audio/wav'\n case 'ogg':\n return 'audio/ogg'\n case 'opus':\n return 'audio/opus'\n case 'flac':\n return 'audio/flac'\n case 'aac':\n return 'audio/aac'\n case 'mp4':\n return 'audio/mp4'\n case 'm4a':\n return 'audio/mp4'\n case 'webm':\n return 'audio/webm'\n case 'pcm':\n return 'audio/L16'\n case 'mulaw':\n return 'audio/basic'\n case 'alaw':\n return 'audio/x-alaw-basic'\n default:\n return `audio/${ext}`\n }\n}\n\nfunction extensionFor(mimeType: string): string {\n switch (mimeType) {\n case 'audio/mpeg':\n return 'mp3'\n case 'audio/wav':\n case 'audio/x-wav':\n return 'wav'\n case 'audio/ogg':\n return 'ogg'\n case 'audio/opus':\n return 'opus'\n case 'audio/flac':\n return 'flac'\n case 'audio/aac':\n return 'aac'\n case 'audio/mp4':\n return 'm4a'\n case 'audio/webm':\n return 'webm'\n case 'audio/L16':\n return 'pcm'\n case 'audio/basic':\n return 'mulaw'\n case 'audio/x-alaw-basic':\n return 'alaw'\n default: {\n const slash = mimeType.indexOf('/')\n if (slash === -1) return 'bin'\n return mimeType.slice(slash + 1) || 'bin'\n }\n }\n}\n\n/**\n * Cross-runtime ArrayBuffer → base64 conversion.\n *\n * Uses Node's `Buffer` when available (fastest path on server) and falls\n * back to `btoa` + chunked `String.fromCharCode` everywhere else (browser,\n * Cloudflare Workers, Bun, Deno). Chunking is required because a very\n * large audio buffer spread into `String.fromCharCode(...bytes)` in one\n * call can hit `Maximum call stack size exceeded`.\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(buffer).toString('base64')\n }\n const bytes = new Uint8Array(buffer)\n const chunkSize = 0x8000\n let binary = ''\n for (let i = 0; i < bytes.length; i += chunkSize) {\n const end = Math.min(i + chunkSize, bytes.length)\n binary += String.fromCharCode.apply(\n null,\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- TS lib types String.fromCharCode.apply as Array<number> but runtime accepts any ArrayLike\n bytes.subarray(i, end) as unknown as Array<number>,\n )\n }\n return btoa(binary)\n}\n\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n let binary: string\n try {\n binary = atob(base64)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`Invalid base64 input to toAudioFile: ${msg}`)\n }\n const buffer = new ArrayBuffer(binary.length)\n const bytes = new Uint8Array(buffer)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return buffer\n}\n"],"mappings":"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"}
|
package/dist/esm/utils/client.js
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { getApiKeyFromEnv } from "@tanstack/ai-utils";
|
|
2
|
+
//#region src/utils/client.ts
|
|
3
|
+
/**
|
|
4
|
+
* Gets Grok API key from environment variables
|
|
5
|
+
* @throws Error if XAI_API_KEY is not found
|
|
6
|
+
*/
|
|
2
7
|
function getGrokApiKeyFromEnv() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
);
|
|
9
|
-
}
|
|
8
|
+
try {
|
|
9
|
+
return getApiKeyFromEnv("XAI_API_KEY");
|
|
10
|
+
} catch {
|
|
11
|
+
throw new Error("XAI_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.");
|
|
12
|
+
}
|
|
10
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a Grok client config with the default xAI base URL applied
|
|
16
|
+
* when not already set.
|
|
17
|
+
*/
|
|
11
18
|
function withGrokDefaults(config) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
return {
|
|
20
|
+
...config,
|
|
21
|
+
baseURL: config.baseURL || "https://api.x.ai/v1"
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
//# sourceMappingURL=client.js.map
|
|
24
|
+
//#endregion
|
|
25
|
+
export { getGrokApiKeyFromEnv, withGrokDefaults };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { ClientOptions } from 'openai'\n\nexport interface GrokClientConfig extends Omit<ClientOptions, 'apiKey'> {\n apiKey: string\n}\n\n/**\n * Gets Grok API key from environment variables\n * @throws Error if XAI_API_KEY is not found\n */\nexport function getGrokApiKeyFromEnv(): string {\n try {\n return getApiKeyFromEnv('XAI_API_KEY')\n } catch {\n throw new Error(\n 'XAI_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',\n )\n }\n}\n\n/**\n * Returns a Grok client config with the default xAI base URL applied\n * when not already set.\n */\nexport function withGrokDefaults(config: GrokClientConfig): GrokClientConfig {\n return {\n ...config,\n baseURL: config.baseURL || 'https://api.x.ai/v1',\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { ClientOptions } from 'openai'\n\nexport interface GrokClientConfig extends Omit<ClientOptions, 'apiKey'> {\n apiKey: string\n}\n\n/**\n * Gets Grok API key from environment variables\n * @throws Error if XAI_API_KEY is not found\n */\nexport function getGrokApiKeyFromEnv(): string {\n try {\n return getApiKeyFromEnv('XAI_API_KEY')\n } catch {\n throw new Error(\n 'XAI_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',\n )\n }\n}\n\n/**\n * Returns a Grok client config with the default xAI base URL applied\n * when not already set.\n */\nexport function withGrokDefaults(config: GrokClientConfig): GrokClientConfig {\n return {\n ...config,\n baseURL: config.baseURL || 'https://api.x.ai/v1',\n }\n}\n"],"mappings":";;;;;;AAWA,SAAgB,uBAA+B;CAC7C,IAAI;EACF,OAAO,iBAAiB,aAAa;CACvC,QAAQ;EACN,MAAM,IAAI,MACR,4HACF;CACF;AACF;;;;;AAMA,SAAgB,iBAAiB,QAA4C;CAC3E,OAAO;EACL,GAAG;EACH,SAAS,OAAO,WAAW;CAC7B;AACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { getGrokApiKeyFromEnv } from "./client.js";
|
|
2
|
+
import "./schema-converter.js";
|
|
3
|
+
import { arrayBufferToBase64, toAudioFile } from "./audio.js";
|
|
4
|
+
import { generateId as generateId$1 } from "@tanstack/ai-utils";
|
|
5
|
+
export { arrayBufferToBase64, generateId$1 as generateId, getGrokApiKeyFromEnv, toAudioFile };
|