@tanstack/openai-base 0.2.1 → 0.3.1

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 (79) hide show
  1. package/README.md +121 -0
  2. package/dist/esm/adapters/chat-completions-text.d.ts +49 -21
  3. package/dist/esm/adapters/chat-completions-text.js +480 -68
  4. package/dist/esm/adapters/chat-completions-text.js.map +1 -1
  5. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +8 -4
  6. package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -1
  7. package/dist/esm/adapters/responses-text.d.ts +46 -33
  8. package/dist/esm/adapters/responses-text.js +661 -142
  9. package/dist/esm/adapters/responses-text.js.map +1 -1
  10. package/dist/esm/index.d.ts +2 -9
  11. package/dist/esm/index.js +4 -16
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/tools/apply-patch-tool.d.ts +2 -2
  14. package/dist/esm/tools/apply-patch-tool.js.map +1 -1
  15. package/dist/esm/tools/code-interpreter-tool.d.ts +3 -2
  16. package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
  17. package/dist/esm/tools/computer-use-tool.d.ts +2 -2
  18. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  19. package/dist/esm/tools/custom-tool.d.ts +2 -2
  20. package/dist/esm/tools/custom-tool.js.map +1 -1
  21. package/dist/esm/tools/file-search-tool.d.ts +2 -2
  22. package/dist/esm/tools/file-search-tool.js.map +1 -1
  23. package/dist/esm/tools/function-tool.d.ts +2 -2
  24. package/dist/esm/tools/function-tool.js.map +1 -1
  25. package/dist/esm/tools/image-generation-tool.d.ts +3 -2
  26. package/dist/esm/tools/image-generation-tool.js.map +1 -1
  27. package/dist/esm/tools/local-shell-tool.d.ts +3 -2
  28. package/dist/esm/tools/local-shell-tool.js.map +1 -1
  29. package/dist/esm/tools/mcp-tool.d.ts +3 -2
  30. package/dist/esm/tools/mcp-tool.js.map +1 -1
  31. package/dist/esm/tools/shell-tool.d.ts +2 -2
  32. package/dist/esm/tools/shell-tool.js.map +1 -1
  33. package/dist/esm/tools/web-search-preview-tool.d.ts +2 -2
  34. package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
  35. package/dist/esm/tools/web-search-tool.d.ts +2 -2
  36. package/dist/esm/tools/web-search-tool.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/adapters/chat-completions-text.ts +605 -117
  39. package/src/adapters/chat-completions-tool-converter.ts +9 -5
  40. package/src/adapters/responses-text.ts +869 -210
  41. package/src/index.ts +2 -12
  42. package/src/tools/apply-patch-tool.ts +2 -2
  43. package/src/tools/code-interpreter-tool.ts +4 -2
  44. package/src/tools/computer-use-tool.ts +2 -2
  45. package/src/tools/custom-tool.ts +2 -2
  46. package/src/tools/file-search-tool.ts +3 -3
  47. package/src/tools/function-tool.ts +2 -2
  48. package/src/tools/image-generation-tool.ts +4 -2
  49. package/src/tools/local-shell-tool.ts +4 -2
  50. package/src/tools/mcp-tool.ts +4 -2
  51. package/src/tools/shell-tool.ts +2 -2
  52. package/src/tools/web-search-preview-tool.ts +2 -2
  53. package/src/tools/web-search-tool.ts +2 -2
  54. package/dist/esm/adapters/image.d.ts +0 -32
  55. package/dist/esm/adapters/image.js +0 -89
  56. package/dist/esm/adapters/image.js.map +0 -1
  57. package/dist/esm/adapters/summarize.d.ts +0 -28
  58. package/dist/esm/adapters/summarize.js +0 -112
  59. package/dist/esm/adapters/summarize.js.map +0 -1
  60. package/dist/esm/adapters/transcription.d.ts +0 -34
  61. package/dist/esm/adapters/transcription.js +0 -131
  62. package/dist/esm/adapters/transcription.js.map +0 -1
  63. package/dist/esm/adapters/tts.d.ts +0 -26
  64. package/dist/esm/adapters/tts.js +0 -78
  65. package/dist/esm/adapters/tts.js.map +0 -1
  66. package/dist/esm/adapters/video.d.ts +0 -72
  67. package/dist/esm/adapters/video.js +0 -238
  68. package/dist/esm/adapters/video.js.map +0 -1
  69. package/dist/esm/types/config.d.ts +0 -4
  70. package/dist/esm/utils/client.d.ts +0 -3
  71. package/dist/esm/utils/client.js +0 -8
  72. package/dist/esm/utils/client.js.map +0 -1
  73. package/src/adapters/image.ts +0 -158
  74. package/src/adapters/summarize.ts +0 -174
  75. package/src/adapters/transcription.ts +0 -194
  76. package/src/adapters/tts.ts +0 -124
  77. package/src/adapters/video.ts +0 -385
  78. package/src/types/config.ts +0 -5
  79. package/src/utils/client.ts +0 -8
@@ -1 +0,0 @@
1
- {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { base64ToArrayBuffer, generateId } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-Compatible Transcription (Speech-to-Text) Adapter\n *\n * A generalized base class for providers that implement OpenAI-compatible audio\n * transcription APIs. Providers can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override audio handling or response mapping methods as needed\n *\n * All methods that handle audio input or map response formats are `protected`\n * so subclasses can override them.\n */\nexport class OpenAICompatibleTranscriptionAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n> extends BaseTranscriptionAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super(model, {})\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async transcribe(\n options: TranscriptionOptions<TProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, prompt, responseFormat, modelOptions } =\n options\n\n // Convert audio input to File object\n const file = this.prepareAudioFile(audio)\n\n // Build request\n const request: OpenAI_SDK.Audio.TranscriptionCreateParams = {\n model,\n file,\n language,\n prompt,\n response_format: this.mapResponseFormat(responseFormat),\n ...modelOptions,\n }\n\n // Call API - use verbose_json to get timestamps when available\n const useVerbose =\n responseFormat === 'verbose_json' ||\n (!responseFormat && this.shouldDefaultToVerbose(model))\n\n try {\n options.logger.request(\n `activity=transcription provider=${this.name} model=${model} verbose=${useVerbose}`,\n { provider: this.name, model },\n )\n if (useVerbose) {\n const response = await this.client.audio.transcriptions.create({\n ...request,\n response_format: 'verbose_json',\n })\n\n return {\n id: generateId(this.name),\n model,\n text: response.text,\n language: response.language,\n duration: response.duration,\n segments: response.segments?.map(\n (seg): TranscriptionSegment => ({\n id: seg.id,\n start: seg.start,\n end: seg.end,\n text: seg.text,\n // The OpenAI SDK types `avg_logprob` as `number`, so call Math.exp\n // directly. Previously this was guarded with `seg.avg_logprob ?`\n // which treated `0` (perfect-confidence) as missing.\n confidence: Math.exp(seg.avg_logprob),\n }),\n ),\n words: response.words?.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n })),\n }\n } else {\n const response = await this.client.audio.transcriptions.create(request)\n\n return {\n id: generateId(this.name),\n model,\n text: typeof response === 'string' ? response : response.text,\n language,\n }\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.transcribe fatal`, {\n error: toRunErrorPayload(error, `${this.name}.transcribe failed`),\n source: `${this.name}.transcribe`,\n })\n throw error\n }\n }\n\n protected prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File {\n // If already a File, return it\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n\n // If Blob, convert to File\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', {\n type: audio.type || 'audio/mpeg',\n })\n }\n\n // If ArrayBuffer, convert to File\n if (typeof ArrayBuffer !== 'undefined' && audio instanceof ArrayBuffer) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n // If base64 string, decode and convert to File\n if (typeof audio === 'string') {\n this.ensureFileSupport()\n\n // Check if it's a data URL\n if (audio.startsWith('data:')) {\n const parts = audio.split(',')\n const header = parts[0]\n const base64Data = parts[1] || ''\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const bytes = base64ToArrayBuffer(base64Data)\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n // Assume raw base64\n const bytes = base64ToArrayBuffer(audio)\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n /**\n * Checks that the global `File` constructor is available.\n * Throws a descriptive error in environments that lack it (e.g. Node < 20).\n */\n private ensureFileSupport(): void {\n if (typeof File === 'undefined') {\n throw new Error(\n '`File` is not available in this environment. ' +\n 'Use Node.js 20 or newer, or pass a File object directly.',\n )\n }\n }\n\n /**\n * Whether the adapter should default to verbose_json when no response format is specified.\n * Override in provider-specific subclasses for model-specific behavior.\n */\n protected shouldDefaultToVerbose(_model: string): boolean {\n return false\n }\n\n protected mapResponseFormat(\n format?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt',\n ): OpenAI_SDK.Audio.TranscriptionCreateParams['response_format'] {\n if (!format) return 'json'\n return format as OpenAI_SDK.Audio.TranscriptionCreateParams['response_format']\n }\n}\n"],"names":["bytes"],"mappings":"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"}
@@ -1,26 +0,0 @@
1
- import { BaseTTSAdapter } from '@tanstack/ai/adapters';
2
- import { TTSOptions, TTSResult } from '@tanstack/ai';
3
- import { default as OpenAI_SDK } from 'openai';
4
- import { OpenAICompatibleClientConfig } from '../types/config.js';
5
- /**
6
- * OpenAI-Compatible Text-to-Speech Adapter
7
- *
8
- * A generalized base class for providers that implement OpenAI-compatible TTS APIs.
9
- * Providers can extend this class and only need to:
10
- * - Set `baseURL` in the config
11
- * - Lock the generic type parameters to provider-specific types
12
- * - Override validation methods or request building for provider-specific constraints
13
- *
14
- * All methods that validate inputs or build requests are `protected` so subclasses
15
- * can override them.
16
- */
17
- export declare class OpenAICompatibleTTSAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>> extends BaseTTSAdapter<TModel, TProviderOptions> {
18
- readonly name: string;
19
- protected client: OpenAI_SDK;
20
- constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
21
- generateSpeech(options: TTSOptions<TProviderOptions>): Promise<TTSResult>;
22
- protected validateAudioInput(text: string): void;
23
- protected validateSpeed(speed?: number): void;
24
- protected validateInstructions(_model: string, _modelOptions?: TProviderOptions): void;
25
- protected getContentType(format: string): string;
26
- }
@@ -1,78 +0,0 @@
1
- import { BaseTTSAdapter } from "@tanstack/ai/adapters";
2
- import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
3
- import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
4
- import { createOpenAICompatibleClient } from "../utils/client.js";
5
- class OpenAICompatibleTTSAdapter extends BaseTTSAdapter {
6
- constructor(config, model, name = "openai-compatible") {
7
- super(model, {});
8
- this.name = name;
9
- this.client = createOpenAICompatibleClient(config);
10
- }
11
- async generateSpeech(options) {
12
- const { model, text, voice, format, speed, modelOptions } = options;
13
- this.validateAudioInput(text);
14
- this.validateSpeed(speed);
15
- this.validateInstructions(model, modelOptions);
16
- const request = {
17
- model,
18
- input: text,
19
- voice: voice || "alloy",
20
- response_format: format,
21
- speed,
22
- ...modelOptions
23
- };
24
- try {
25
- options.logger.request(
26
- `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? "default"} voice=${request.voice}`,
27
- { provider: this.name, model }
28
- );
29
- const response = await this.client.audio.speech.create(request);
30
- const arrayBuffer = await response.arrayBuffer();
31
- const base64 = arrayBufferToBase64(arrayBuffer);
32
- const outputFormat = request.response_format || "mp3";
33
- const contentType = this.getContentType(outputFormat);
34
- return {
35
- id: generateId(this.name),
36
- model,
37
- audio: base64,
38
- format: outputFormat,
39
- contentType
40
- };
41
- } catch (error) {
42
- options.logger.errors(`${this.name}.generateSpeech fatal`, {
43
- error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),
44
- source: `${this.name}.generateSpeech`
45
- });
46
- throw error;
47
- }
48
- }
49
- validateAudioInput(text) {
50
- if (text.length > 4096) {
51
- throw new Error("Input text exceeds maximum length of 4096 characters.");
52
- }
53
- }
54
- validateSpeed(speed) {
55
- if (speed !== void 0) {
56
- if (speed < 0.25 || speed > 4) {
57
- throw new Error("Speed must be between 0.25 and 4.0.");
58
- }
59
- }
60
- }
61
- validateInstructions(_model, _modelOptions) {
62
- }
63
- getContentType(format) {
64
- const contentTypes = {
65
- mp3: "audio/mpeg",
66
- opus: "audio/opus",
67
- aac: "audio/aac",
68
- flac: "audio/flac",
69
- wav: "audio/wav",
70
- pcm: "audio/pcm"
71
- };
72
- return contentTypes[format] || "audio/mpeg";
73
- }
74
- }
75
- export {
76
- OpenAICompatibleTTSAdapter
77
- };
78
- //# sourceMappingURL=tts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tts.js","sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-Compatible Text-to-Speech Adapter\n *\n * A generalized base class for providers that implement OpenAI-compatible TTS APIs.\n * Providers can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override validation methods or request building for provider-specific constraints\n *\n * All methods that validate inputs or build requests are `protected` so subclasses\n * can override them.\n */\nexport class OpenAICompatibleTTSAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n> extends BaseTTSAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super(model, {})\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async generateSpeech(\n options: TTSOptions<TProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n // Validate inputs\n this.validateAudioInput(text)\n this.validateSpeed(speed)\n this.validateInstructions(model, modelOptions)\n\n // Build request\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: (voice || 'alloy') as OpenAI_SDK.Audio.SpeechCreateParams['voice'],\n response_format: format,\n speed,\n ...modelOptions,\n }\n\n try {\n options.logger.request(\n `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? 'default'} voice=${request.voice}`,\n { provider: this.name, model },\n )\n const response = await this.client.audio.speech.create(request)\n\n // Convert response to base64. Buffer is Node-only; use atob fallback in\n // browser/edge runtimes where the SDK can run.\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n\n const outputFormat = (request.response_format as string) || 'mp3'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n\n protected validateAudioInput(text: string): void {\n if (text.length > 4096) {\n throw new Error('Input text exceeds maximum length of 4096 characters.')\n }\n }\n\n protected validateSpeed(speed?: number): void {\n if (speed !== undefined) {\n if (speed < 0.25 || speed > 4.0) {\n throw new Error('Speed must be between 0.25 and 4.0.')\n }\n }\n }\n\n protected validateInstructions(\n _model: string,\n _modelOptions?: TProviderOptions,\n ): void {\n // Default: no instructions validation — subclasses can override\n }\n\n protected getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n mp3: 'audio/mpeg',\n opus: 'audio/opus',\n aac: 'audio/aac',\n flac: 'audio/flac',\n wav: 'audio/wav',\n pcm: 'audio/pcm',\n }\n return contentTypes[format] || 'audio/mpeg'\n }\n}\n"],"names":[],"mappings":";;;;AAoBO,MAAM,mCAGH,eAAyC;AAAA,EAKjD,YACE,QACA,OACA,OAAe,qBACf;AACA,UAAM,OAAO,EAAE;AACf,SAAK,OAAO;AACZ,SAAK,SAAS,6BAA6B,MAAM;AAAA,EACnD;AAAA,EAEA,MAAM,eACJ,SACoB;AACpB,UAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;AAG5D,SAAK,mBAAmB,IAAI;AAC5B,SAAK,cAAc,KAAK;AACxB,SAAK,qBAAqB,OAAO,YAAY;AAG7C,UAAM,UAA+C;AAAA,MACnD;AAAA,MACA,OAAO;AAAA,MACP,OAAQ,SAAS;AAAA,MACjB,iBAAiB;AAAA,MACjB;AAAA,MACA,GAAG;AAAA,IAAA;AAGL,QAAI;AACF,cAAQ,OAAO;AAAA,QACb,yBAAyB,KAAK,IAAI,UAAU,KAAK,WAAW,QAAQ,mBAAmB,SAAS,UAAU,QAAQ,KAAK;AAAA,QACvH,EAAE,UAAU,KAAK,MAAM,MAAA;AAAA,MAAM;AAE/B,YAAM,WAAW,MAAM,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO;AAI9D,YAAM,cAAc,MAAM,SAAS,YAAA;AACnC,YAAM,SAAS,oBAAoB,WAAW;AAE9C,YAAM,eAAgB,QAAQ,mBAA8B;AAC5D,YAAM,cAAc,KAAK,eAAe,YAAY;AAEpD,aAAO;AAAA,QACL,IAAI,WAAW,KAAK,IAAI;AAAA,QACxB;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ,SAAS,OAAgB;AAGvB,cAAQ,OAAO,OAAO,GAAG,KAAK,IAAI,yBAAyB;AAAA,QACzD,OAAO,kBAAkB,OAAO,GAAG,KAAK,IAAI,wBAAwB;AAAA,QACpE,QAAQ,GAAG,KAAK,IAAI;AAAA,MAAA,CACrB;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEU,mBAAmB,MAAoB;AAC/C,QAAI,KAAK,SAAS,MAAM;AACtB,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAAA,EACF;AAAA,EAEU,cAAc,OAAsB;AAC5C,QAAI,UAAU,QAAW;AACvB,UAAI,QAAQ,QAAQ,QAAQ,GAAK;AAC/B,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA,EAEU,qBACR,QACA,eACM;AAAA,EAER;AAAA,EAEU,eAAe,QAAwB;AAC/C,UAAM,eAAuC;AAAA,MAC3C,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,IAAA;AAEP,WAAO,aAAa,MAAM,KAAK;AAAA,EACjC;AACF;"}
@@ -1,72 +0,0 @@
1
- import { BaseVideoAdapter } from '@tanstack/ai/adapters';
2
- import { VideoGenerationOptions, VideoJobResult, VideoStatusResult, VideoUrlResult } from '@tanstack/ai';
3
- import { default as OpenAI_SDK } from 'openai';
4
- import { OpenAICompatibleClientConfig } from '../types/config.js';
5
- /**
6
- * OpenAI-Compatible Video Generation Adapter
7
- *
8
- * A generalized base class for providers that implement OpenAI-compatible video
9
- * generation APIs. Uses a job/polling architecture for async video generation.
10
- *
11
- * Providers can extend this class and only need to:
12
- * - Set `baseURL` in the config
13
- * - Lock the generic type parameters to provider-specific types
14
- * - Override validation or request building methods as needed
15
- *
16
- * All methods that validate inputs, build requests, or map responses are `protected`
17
- * so subclasses can override them.
18
- *
19
- * @experimental Video generation is an experimental feature and may change.
20
- */
21
- export declare class OpenAICompatibleVideoAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>, TModelProviderOptionsByName extends Record<string, any> = Record<string, any>, TModelSizeByName extends Record<string, string> = Record<string, string>> extends BaseVideoAdapter<TModel, TProviderOptions, TModelProviderOptionsByName, TModelSizeByName> {
22
- readonly name: string;
23
- protected client: OpenAI_SDK;
24
- protected clientConfig: OpenAICompatibleClientConfig;
25
- constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
26
- /**
27
- * Create a new video generation job.
28
- *
29
- * @experimental Video generation is an experimental feature and may change.
30
- */
31
- createVideoJob(options: VideoGenerationOptions<TProviderOptions>): Promise<VideoJobResult>;
32
- /**
33
- * Returns the underlying OpenAI Videos resource. Pulled out as a protected
34
- * accessor so subclasses targeting forks of the SDK can swap the access
35
- * path without forcing each call site to cast through `any`.
36
- */
37
- protected getVideosClient(): {
38
- create: (req: Record<string, any>) => Promise<{
39
- id: string;
40
- }>;
41
- retrieve: (id: string) => Promise<{
42
- id: string;
43
- status: string;
44
- progress?: number;
45
- url?: string;
46
- expires_at?: number;
47
- error?: {
48
- message?: string;
49
- };
50
- }>;
51
- downloadContent?: (id: string) => Promise<Response>;
52
- content?: (id: string) => Promise<unknown>;
53
- getContent?: (id: string) => Promise<unknown>;
54
- download?: (id: string) => Promise<unknown>;
55
- };
56
- /**
57
- * Get the current status of a video generation job.
58
- *
59
- * @experimental Video generation is an experimental feature and may change.
60
- */
61
- getVideoStatus(jobId: string): Promise<VideoStatusResult>;
62
- /**
63
- * Get the URL to download/view the generated video.
64
- *
65
- * @experimental Video generation is an experimental feature and may change.
66
- */
67
- getVideoUrl(jobId: string): Promise<VideoUrlResult>;
68
- protected buildRequest(options: VideoGenerationOptions<TProviderOptions>): Record<string, any>;
69
- protected validateVideoSize(_model: string, _size?: string): void;
70
- protected validateVideoSeconds(_model: string, _seconds?: number | string): void;
71
- protected mapStatus(apiStatus: string): 'pending' | 'processing' | 'completed' | 'failed';
72
- }
@@ -1,238 +0,0 @@
1
- import { BaseVideoAdapter } from "@tanstack/ai/adapters";
2
- import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
3
- import { arrayBufferToBase64 } from "@tanstack/ai-utils";
4
- import { createOpenAICompatibleClient } from "../utils/client.js";
5
- const LARGE_MEDIA_BUFFER_BYTES = 10 * 1024 * 1024;
6
- function warnIfLargeMediaBuffer(byteLength, source, providerName) {
7
- if (byteLength <= LARGE_MEDIA_BUFFER_BYTES) return;
8
- console.warn(
9
- `[${providerName}.${source}] downloaded ${(byteLength / 1024 / 1024).toFixed(1)} MiB into memory before base64 encoding. Workers/serverless runtimes commonly run out of memory above ~10 MiB. Consider streaming the video through a CDN or your own storage layer instead.`
10
- );
11
- }
12
- class OpenAICompatibleVideoAdapter extends BaseVideoAdapter {
13
- constructor(config, model, name = "openai-compatible") {
14
- super(config, model);
15
- this.name = name;
16
- this.clientConfig = config;
17
- this.client = createOpenAICompatibleClient(config);
18
- }
19
- /**
20
- * Create a new video generation job.
21
- *
22
- * @experimental Video generation is an experimental feature and may change.
23
- */
24
- async createVideoJob(options) {
25
- const { model, size, duration, modelOptions } = options;
26
- this.validateVideoSize(model, size);
27
- const seconds = duration ?? modelOptions?.seconds;
28
- this.validateVideoSeconds(model, seconds);
29
- const request = this.buildRequest(options);
30
- try {
31
- options.logger.request(
32
- `activity=video.create provider=${this.name} model=${model} size=${request.size ?? "default"} seconds=${request.seconds ?? "default"}`,
33
- { provider: this.name, model }
34
- );
35
- const videosClient = this.getVideosClient();
36
- const response = await videosClient.create(request);
37
- return {
38
- jobId: response.id,
39
- model
40
- };
41
- } catch (error) {
42
- options.logger.errors(`${this.name}.createVideoJob fatal`, {
43
- error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),
44
- source: `${this.name}.createVideoJob`
45
- });
46
- if (error?.message?.includes("videos") || error?.code === "invalid_api") {
47
- throw new Error(
48
- `Video generation API is not available. The API may require special access. Original error: ${error.message}`
49
- );
50
- }
51
- throw error;
52
- }
53
- }
54
- /**
55
- * Returns the underlying OpenAI Videos resource. Pulled out as a protected
56
- * accessor so subclasses targeting forks of the SDK can swap the access
57
- * path without forcing each call site to cast through `any`.
58
- */
59
- getVideosClient() {
60
- return this.client.videos;
61
- }
62
- /**
63
- * Get the current status of a video generation job.
64
- *
65
- * @experimental Video generation is an experimental feature and may change.
66
- */
67
- async getVideoStatus(jobId) {
68
- try {
69
- const videosClient = this.getVideosClient();
70
- const response = await videosClient.retrieve(jobId);
71
- return {
72
- jobId,
73
- status: this.mapStatus(response.status),
74
- progress: response.progress,
75
- error: response.error?.message
76
- };
77
- } catch (error) {
78
- if (error.status === 404) {
79
- return {
80
- jobId,
81
- status: "failed",
82
- error: "Job not found"
83
- };
84
- }
85
- throw error;
86
- }
87
- }
88
- /**
89
- * Get the URL to download/view the generated video.
90
- *
91
- * @experimental Video generation is an experimental feature and may change.
92
- */
93
- async getVideoUrl(jobId) {
94
- try {
95
- const videosClient = this.getVideosClient();
96
- const videoInfo = await videosClient.retrieve(jobId);
97
- if (videoInfo.url) {
98
- return {
99
- jobId,
100
- url: videoInfo.url,
101
- expiresAt: videoInfo.expires_at ? new Date(videoInfo.expires_at) : void 0
102
- };
103
- }
104
- if (typeof videosClient.downloadContent === "function") {
105
- const contentResponse = await videosClient.downloadContent(jobId);
106
- const videoBlob = await contentResponse.blob();
107
- const buffer = await videoBlob.arrayBuffer();
108
- warnIfLargeMediaBuffer(
109
- buffer.byteLength,
110
- "video.downloadContent",
111
- this.name
112
- );
113
- const base64 = arrayBufferToBase64(buffer);
114
- const mimeType = contentResponse.headers.get("content-type") || "video/mp4";
115
- return {
116
- jobId,
117
- url: `data:${mimeType};base64,${base64}`,
118
- expiresAt: void 0
119
- };
120
- }
121
- let response;
122
- if (typeof videosClient.content === "function") {
123
- response = await videosClient.content(jobId);
124
- } else if (typeof videosClient.getContent === "function") {
125
- response = await videosClient.getContent(jobId);
126
- } else if (typeof videosClient.download === "function") {
127
- response = await videosClient.download(jobId);
128
- } else {
129
- const baseUrl = this.clientConfig.baseURL || "https://api.openai.com/v1";
130
- const apiKey = this.clientConfig.apiKey;
131
- const contentResponse = await fetch(
132
- `${baseUrl}/videos/${jobId}/content`,
133
- {
134
- method: "GET",
135
- headers: {
136
- Authorization: `Bearer ${apiKey}`
137
- }
138
- }
139
- );
140
- if (!contentResponse.ok) {
141
- const contentType = contentResponse.headers.get("content-type");
142
- if (contentType?.includes("application/json")) {
143
- const errorData = await contentResponse.json().catch(() => ({}));
144
- throw new Error(
145
- errorData.error?.message || `Failed to get video content: ${contentResponse.status}`
146
- );
147
- }
148
- throw new Error(
149
- `Failed to get video content: ${contentResponse.status}`
150
- );
151
- }
152
- const videoBlob = await contentResponse.blob();
153
- const buffer = await videoBlob.arrayBuffer();
154
- warnIfLargeMediaBuffer(buffer.byteLength, "video.fetch", this.name);
155
- const base64 = arrayBufferToBase64(buffer);
156
- const mimeType = contentResponse.headers.get("content-type") || "video/mp4";
157
- return {
158
- jobId,
159
- url: `data:${mimeType};base64,${base64}`,
160
- expiresAt: void 0
161
- };
162
- }
163
- const fallthroughBlob = typeof response?.blob === "function" ? await response.blob() : response instanceof Blob ? response : null;
164
- if (!fallthroughBlob) {
165
- throw new Error(
166
- `Video content download via SDK fall-through returned an unexpected shape (no blob()). Override getVideoUrl() in your subclass to handle this provider.`
167
- );
168
- }
169
- const fallthroughBuffer = await fallthroughBlob.arrayBuffer();
170
- warnIfLargeMediaBuffer(
171
- fallthroughBuffer.byteLength,
172
- "video.sdkFallthrough",
173
- this.name
174
- );
175
- const fallthroughBase64 = arrayBufferToBase64(fallthroughBuffer);
176
- const fallthroughMime = (typeof response?.headers?.get === "function" ? response.headers.get("content-type") : void 0) || fallthroughBlob.type || "video/mp4";
177
- return {
178
- jobId,
179
- url: `data:${fallthroughMime};base64,${fallthroughBase64}`,
180
- expiresAt: void 0
181
- };
182
- } catch (error) {
183
- if (error.status === 404) {
184
- throw new Error(`Video job not found: ${jobId}`);
185
- }
186
- if (error.status === 400) {
187
- throw new Error(
188
- `Video is not ready for download. Check status first. Job ID: ${jobId}`
189
- );
190
- }
191
- throw error;
192
- }
193
- }
194
- buildRequest(options) {
195
- const { model, prompt, size, duration, modelOptions } = options;
196
- const request = {
197
- model,
198
- prompt
199
- };
200
- if (size) {
201
- request["size"] = size;
202
- } else if (modelOptions?.size) {
203
- request["size"] = modelOptions.size;
204
- }
205
- const seconds = duration ?? modelOptions?.seconds;
206
- if (seconds !== void 0) {
207
- request["seconds"] = String(seconds);
208
- }
209
- return request;
210
- }
211
- validateVideoSize(_model, _size) {
212
- }
213
- validateVideoSeconds(_model, _seconds) {
214
- }
215
- mapStatus(apiStatus) {
216
- switch (apiStatus) {
217
- case "queued":
218
- case "pending":
219
- return "pending";
220
- case "processing":
221
- case "in_progress":
222
- return "processing";
223
- case "completed":
224
- case "succeeded":
225
- return "completed";
226
- case "failed":
227
- case "error":
228
- case "cancelled":
229
- return "failed";
230
- default:
231
- return "processing";
232
- }
233
- }
234
- }
235
- export {
236
- OpenAICompatibleVideoAdapter
237
- };
238
- //# sourceMappingURL=video.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"video.js","sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { BaseVideoAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport type {\n VideoGenerationOptions,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * Threshold for emitting a \"this download will probably OOM serverless\n * runtimes\" warning. Anything larger than this (in bytes) gets surfaced via\n * console.warn — workers and small isolates routinely run out of memory once\n * a downloaded video is base64-encoded (the encoded form is ~33% larger and\n * resides in V8 heap rather than streaming through the runtime's network\n * layer).\n */\nconst LARGE_MEDIA_BUFFER_BYTES = 10 * 1024 * 1024\n\nfunction warnIfLargeMediaBuffer(\n byteLength: number,\n source: string,\n providerName: string,\n): void {\n if (byteLength <= LARGE_MEDIA_BUFFER_BYTES) return\n // No InternalLogger plumbed through to these download paths yet; surface\n // via console.warn so Workers / Lambda dashboards still capture it.\n console.warn(\n `[${providerName}.${source}] downloaded ${(byteLength / 1024 / 1024).toFixed(1)} MiB into memory before base64 encoding. ` +\n `Workers/serverless runtimes commonly run out of memory above ~10 MiB. ` +\n `Consider streaming the video through a CDN or your own storage layer instead.`,\n )\n}\n\n/**\n * OpenAI-Compatible Video Generation Adapter\n *\n * A generalized base class for providers that implement OpenAI-compatible video\n * generation APIs. Uses a job/polling architecture for async video generation.\n *\n * Providers can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override validation or request building methods as needed\n *\n * All methods that validate inputs, build requests, or map responses are `protected`\n * so subclasses can override them.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class OpenAICompatibleVideoAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n TModelProviderOptionsByName extends Record<string, any> = Record<string, any>,\n TModelSizeByName extends Record<string, string> = Record<string, string>,\n> extends BaseVideoAdapter<\n TModel,\n TProviderOptions,\n TModelProviderOptionsByName,\n TModelSizeByName\n> {\n readonly name: string\n\n protected client: OpenAI_SDK\n protected clientConfig: OpenAICompatibleClientConfig\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super(config, model)\n this.name = name\n this.clientConfig = config\n this.client = createOpenAICompatibleClient(config)\n }\n\n /**\n * Create a new video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n async createVideoJob(\n options: VideoGenerationOptions<TProviderOptions>,\n ): Promise<VideoJobResult> {\n const { model, size, duration, modelOptions } = options\n\n // Validate inputs\n this.validateVideoSize(model, size)\n const seconds = duration ?? (modelOptions as any)?.seconds\n this.validateVideoSeconds(model, seconds)\n\n // Build request\n const request = this.buildRequest(options)\n\n try {\n options.logger.request(\n `activity=video.create provider=${this.name} model=${model} size=${request.size ?? 'default'} seconds=${request.seconds ?? 'default'}`,\n { provider: this.name, model },\n )\n // The video API on the OpenAI SDK is still experimental and shipped on\n // some SDK versions but not others; access through `videosClient` lets\n // subclasses override the entry point or supply a polyfill without\n // forcing every call site through `as any`.\n const videosClient = this.getVideosClient()\n const response = await videosClient.create(request)\n\n return {\n jobId: response.id,\n model,\n }\n } catch (error: any) {\n options.logger.errors(`${this.name}.createVideoJob fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),\n source: `${this.name}.createVideoJob`,\n })\n if (error?.message?.includes('videos') || error?.code === 'invalid_api') {\n throw new Error(\n `Video generation API is not available. The API may require special access. ` +\n `Original error: ${error.message}`,\n )\n }\n throw error\n }\n }\n\n /**\n * Returns the underlying OpenAI Videos resource. Pulled out as a protected\n * accessor so subclasses targeting forks of the SDK can swap the access\n * path without forcing each call site to cast through `any`.\n */\n protected getVideosClient(): {\n create: (req: Record<string, any>) => Promise<{ id: string }>\n retrieve: (id: string) => Promise<{\n id: string\n status: string\n progress?: number\n url?: string\n expires_at?: number\n error?: { message?: string }\n }>\n downloadContent?: (id: string) => Promise<Response>\n content?: (id: string) => Promise<unknown>\n getContent?: (id: string) => Promise<unknown>\n download?: (id: string) => Promise<unknown>\n } {\n return (this.client as unknown as { videos: any }).videos\n }\n\n /**\n * Get the current status of a video generation job.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n try {\n const videosClient = this.getVideosClient()\n const response = await videosClient.retrieve(jobId)\n\n return {\n jobId,\n status: this.mapStatus(response.status),\n progress: response.progress,\n error: response.error?.message,\n }\n } catch (error: any) {\n if (error.status === 404) {\n return {\n jobId,\n status: 'failed',\n error: 'Job not found',\n }\n }\n throw error\n }\n }\n\n /**\n * Get the URL to download/view the generated video.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n try {\n const videosClient = this.getVideosClient()\n\n // Prefer retrieve() because many openai-compatible backends (and the\n // aimock test harness) return the URL directly on the video resource\n // and do not implement a separate /content endpoint. Subclasses can\n // override this method if they need to download raw bytes via\n // downloadContent()/content().\n const videoInfo = await videosClient.retrieve(jobId)\n if (videoInfo.url) {\n return {\n jobId,\n url: videoInfo.url,\n expiresAt: videoInfo.expires_at\n ? new Date(videoInfo.expires_at)\n : undefined,\n }\n }\n\n // SDK download fall-through: try the various possible method names in\n // decreasing order of modernity.\n if (typeof videosClient.downloadContent === 'function') {\n const contentResponse = await videosClient.downloadContent(jobId)\n const videoBlob = await contentResponse.blob()\n const buffer = await videoBlob.arrayBuffer()\n warnIfLargeMediaBuffer(\n buffer.byteLength,\n 'video.downloadContent',\n this.name,\n )\n const base64 = arrayBufferToBase64(buffer)\n const mimeType =\n contentResponse.headers.get('content-type') || 'video/mp4'\n return {\n jobId,\n url: `data:${mimeType};base64,${base64}`,\n expiresAt: undefined,\n }\n }\n\n // The remaining SDK fall-throughs all return a binary payload\n // (Blob/Response/ArrayBuffer-shaped), NOT an `{ url, expires_at }`\n // object the way the bottom return assumed. Convert to a data URL\n // here so the caller actually receives a usable URL.\n let response: any\n if (typeof videosClient.content === 'function') {\n response = await videosClient.content(jobId)\n } else if (typeof videosClient.getContent === 'function') {\n response = await videosClient.getContent(jobId)\n } else if (typeof videosClient.download === 'function') {\n response = await videosClient.download(jobId)\n } else {\n // Last resort: raw fetch with auth header.\n const baseUrl = this.clientConfig.baseURL || 'https://api.openai.com/v1'\n const apiKey = this.clientConfig.apiKey\n\n const contentResponse = await fetch(\n `${baseUrl}/videos/${jobId}/content`,\n {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n },\n )\n\n if (!contentResponse.ok) {\n const contentType = contentResponse.headers.get('content-type')\n if (contentType?.includes('application/json')) {\n const errorData = await contentResponse.json().catch(() => ({}))\n throw new Error(\n errorData.error?.message ||\n `Failed to get video content: ${contentResponse.status}`,\n )\n }\n throw new Error(\n `Failed to get video content: ${contentResponse.status}`,\n )\n }\n\n const videoBlob = await contentResponse.blob()\n const buffer = await videoBlob.arrayBuffer()\n warnIfLargeMediaBuffer(buffer.byteLength, 'video.fetch', this.name)\n const base64 = arrayBufferToBase64(buffer)\n const mimeType =\n contentResponse.headers.get('content-type') || 'video/mp4'\n\n return {\n jobId,\n url: `data:${mimeType};base64,${base64}`,\n expiresAt: undefined,\n }\n }\n\n // The fall-through SDK methods produce a Blob-ish or fetch-`Response`-ish\n // object. Read it as bytes and wrap in a data URL so callers see an\n // actual playable URL instead of the API endpoint URL (which is what\n // `response.url` would be on a fetch Response).\n const fallthroughBlob =\n typeof response?.blob === 'function'\n ? await response.blob()\n : response instanceof Blob\n ? response\n : null\n if (!fallthroughBlob) {\n throw new Error(\n `Video content download via SDK fall-through returned an unexpected shape (no blob()). ` +\n `Override getVideoUrl() in your subclass to handle this provider.`,\n )\n }\n const fallthroughBuffer = await fallthroughBlob.arrayBuffer()\n warnIfLargeMediaBuffer(\n fallthroughBuffer.byteLength,\n 'video.sdkFallthrough',\n this.name,\n )\n const fallthroughBase64 = arrayBufferToBase64(fallthroughBuffer)\n const fallthroughMime =\n (typeof response?.headers?.get === 'function'\n ? response.headers.get('content-type')\n : undefined) ||\n fallthroughBlob.type ||\n 'video/mp4'\n return {\n jobId,\n url: `data:${fallthroughMime};base64,${fallthroughBase64}`,\n expiresAt: undefined,\n }\n } catch (error: any) {\n if (error.status === 404) {\n throw new Error(`Video job not found: ${jobId}`)\n }\n if (error.status === 400) {\n throw new Error(\n `Video is not ready for download. Check status first. Job ID: ${jobId}`,\n )\n }\n throw error\n }\n }\n\n protected buildRequest(\n options: VideoGenerationOptions<TProviderOptions>,\n ): Record<string, any> {\n const { model, prompt, size, duration, modelOptions } = options\n\n const request: Record<string, any> = {\n model,\n prompt,\n }\n\n if (size) {\n request['size'] = size\n } else if ((modelOptions as any)?.size) {\n request['size'] = (modelOptions as any).size\n }\n\n const seconds = duration ?? (modelOptions as any)?.seconds\n if (seconds !== undefined) {\n request['seconds'] = String(seconds)\n }\n\n return request\n }\n\n protected validateVideoSize(_model: string, _size?: string): void {\n // Default: no size validation — subclasses can override\n }\n\n protected validateVideoSeconds(\n _model: string,\n _seconds?: number | string,\n ): void {\n // Default: no duration validation — subclasses can override\n }\n\n protected mapStatus(\n apiStatus: string,\n ): 'pending' | 'processing' | 'completed' | 'failed' {\n switch (apiStatus) {\n case 'queued':\n case 'pending':\n return 'pending'\n case 'processing':\n case 'in_progress':\n return 'processing'\n case 'completed':\n case 'succeeded':\n return 'completed'\n case 'failed':\n case 'error':\n case 'cancelled':\n return 'failed'\n default:\n return 'processing'\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- import { ClientOptions } from 'openai';
2
- export interface OpenAICompatibleClientConfig extends ClientOptions {
3
- apiKey: string;
4
- }
@@ -1,3 +0,0 @@
1
- import { default as OpenAI } from 'openai';
2
- import { OpenAICompatibleClientConfig } from '../types/config.js';
3
- export declare function createOpenAICompatibleClient(config: OpenAICompatibleClientConfig): OpenAI;
@@ -1,8 +0,0 @@
1
- import OpenAI from "openai";
2
- function createOpenAICompatibleClient(config) {
3
- return new OpenAI(config);
4
- }
5
- export {
6
- createOpenAICompatibleClient
7
- };
8
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\nexport function createOpenAICompatibleClient(\n config: OpenAICompatibleClientConfig,\n): OpenAI {\n return new OpenAI(config)\n}\n"],"names":[],"mappings":";AAGO,SAAS,6BACd,QACQ;AACR,SAAO,IAAI,OAAO,MAAM;AAC1B;"}