@tanstack/ai-lovable 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/esm/adapters/embedding.d.ts +16 -0
  4. package/dist/esm/adapters/embedding.js +66 -0
  5. package/dist/esm/adapters/embedding.js.map +1 -0
  6. package/dist/esm/adapters/factory.d.ts +16 -0
  7. package/dist/esm/adapters/factory.js +28 -0
  8. package/dist/esm/adapters/factory.js.map +1 -0
  9. package/dist/esm/adapters/image.d.ts +26 -0
  10. package/dist/esm/adapters/image.js +145 -0
  11. package/dist/esm/adapters/image.js.map +1 -0
  12. package/dist/esm/adapters/responses-text.d.ts +24 -0
  13. package/dist/esm/adapters/responses-text.js +30 -0
  14. package/dist/esm/adapters/responses-text.js.map +1 -0
  15. package/dist/esm/adapters/summarize.d.ts +9 -0
  16. package/dist/esm/adapters/summarize.js +17 -0
  17. package/dist/esm/adapters/summarize.js.map +1 -0
  18. package/dist/esm/adapters/text.d.ts +20 -0
  19. package/dist/esm/adapters/text.js +21 -0
  20. package/dist/esm/adapters/text.js.map +1 -0
  21. package/dist/esm/adapters/transcription.d.ts +19 -0
  22. package/dist/esm/adapters/transcription.js +123 -0
  23. package/dist/esm/adapters/transcription.js.map +1 -0
  24. package/dist/esm/adapters/tts.d.ts +16 -0
  25. package/dist/esm/adapters/tts.js +69 -0
  26. package/dist/esm/adapters/tts.js.map +1 -0
  27. package/dist/esm/adapters/video.d.ts +41 -0
  28. package/dist/esm/adapters/video.js +164 -0
  29. package/dist/esm/adapters/video.js.map +1 -0
  30. package/dist/esm/audio/transcription-provider-options.d.ts +6 -0
  31. package/dist/esm/audio/tts-provider-options.d.ts +8 -0
  32. package/dist/esm/byok.d.ts +1 -0
  33. package/dist/esm/byok.js +11 -0
  34. package/dist/esm/byok.js.map +1 -0
  35. package/dist/esm/embedding/embedding-provider-options.d.ts +9 -0
  36. package/dist/esm/image/image-input-to-file.d.ts +8 -0
  37. package/dist/esm/image/image-input-to-file.js +47 -0
  38. package/dist/esm/image/image-input-to-file.js.map +1 -0
  39. package/dist/esm/image/image-provider-options.d.ts +12 -0
  40. package/dist/esm/index.d.ts +26 -0
  41. package/dist/esm/index.js +12 -0
  42. package/dist/esm/message-types.d.ts +44 -0
  43. package/dist/esm/model-meta.d.ts +71 -0
  44. package/dist/esm/model-meta.js +63 -0
  45. package/dist/esm/model-meta.js.map +1 -0
  46. package/dist/esm/text/responses-provider-options.d.ts +3 -0
  47. package/dist/esm/text/text-provider-options.d.ts +17 -0
  48. package/dist/esm/utils/client.d.ts +14 -0
  49. package/dist/esm/utils/client.js +35 -0
  50. package/dist/esm/utils/client.js.map +1 -0
  51. package/dist/esm/video/video-provider-options.d.ts +19 -0
  52. package/dist/esm/video/video-provider-options.js +47 -0
  53. package/dist/esm/video/video-provider-options.js.map +1 -0
  54. package/package.json +79 -0
  55. package/src/adapters/embedding.ts +102 -0
  56. package/src/adapters/factory.ts +78 -0
  57. package/src/adapters/image.ts +304 -0
  58. package/src/adapters/responses-text.ts +66 -0
  59. package/src/adapters/summarize.ts +35 -0
  60. package/src/adapters/text.ts +49 -0
  61. package/src/adapters/transcription.ts +226 -0
  62. package/src/adapters/tts.ts +97 -0
  63. package/src/adapters/video.ts +303 -0
  64. package/src/audio/transcription-provider-options.ts +7 -0
  65. package/src/audio/tts-provider-options.ts +21 -0
  66. package/src/byok.ts +7 -0
  67. package/src/embedding/embedding-provider-options.ts +9 -0
  68. package/src/image/image-input-to-file.ts +81 -0
  69. package/src/image/image-provider-options.ts +17 -0
  70. package/src/index.ts +125 -0
  71. package/src/message-types.ts +45 -0
  72. package/src/model-meta.ts +161 -0
  73. package/src/text/responses-provider-options.ts +8 -0
  74. package/src/text/text-provider-options.ts +18 -0
  75. package/src/utils/client.ts +50 -0
  76. package/src/video/video-provider-options.ts +128 -0
@@ -0,0 +1,123 @@
1
+ import { getLovableApiKeyFromEnv, openaiRequestOptions, withLovableDefaults } from "../utils/client.js";
2
+ import OpenAI from "openai";
3
+ import { base64ToArrayBuffer, generateId } from "@tanstack/ai-utils";
4
+ import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters";
5
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
+ //#region src/adapters/transcription.ts
7
+ function isPlainFormat(format) {
8
+ return format === "json" || format === "text";
9
+ }
10
+ function buildTranscriptionUsage(response) {
11
+ const usage = response?.usage;
12
+ if (!usage || usage.type === "duration") return;
13
+ const result = {
14
+ promptTokens: usage.input_tokens || 0,
15
+ completionTokens: usage.output_tokens || 0,
16
+ totalTokens: usage.total_tokens || 0
17
+ };
18
+ const inputDetails = usage.input_token_details;
19
+ const promptTokensDetails = {
20
+ ...inputDetails?.audio_tokens ? { audioTokens: inputDetails.audio_tokens } : {},
21
+ ...inputDetails?.text_tokens ? { textTokens: inputDetails.text_tokens } : {}
22
+ };
23
+ if (Object.keys(promptTokensDetails).length > 0) result.promptTokensDetails = promptTokensDetails;
24
+ if (usage.output_tokens) result.completionTokensDetails = { textTokens: usage.output_tokens };
25
+ return result;
26
+ }
27
+ var LovableTranscriptionAdapter = class extends BaseTranscriptionAdapter {
28
+ name = "lovable";
29
+ client;
30
+ constructor(config, model) {
31
+ super(model, {});
32
+ this.client = new OpenAI(withLovableDefaults(config));
33
+ }
34
+ async transcribe(options) {
35
+ const { model, language } = options;
36
+ try {
37
+ const request = this.buildTranscriptionRequest(options);
38
+ options.logger.request(`activity=transcription provider=${this.name} model=${model}`, {
39
+ provider: this.name,
40
+ model
41
+ });
42
+ const response = await this.client.audio.transcriptions.create(request, openaiRequestOptions(options.abortSignal));
43
+ const usage = typeof response === "string" ? void 0 : buildTranscriptionUsage(response);
44
+ return {
45
+ id: generateId(this.name),
46
+ model,
47
+ text: typeof response === "string" ? response : response.text,
48
+ ...language !== void 0 && { language },
49
+ ...usage !== void 0 && { usage }
50
+ };
51
+ } catch (error) {
52
+ options.logger.errors(`${this.name}.transcribe fatal`, {
53
+ error: toRunErrorPayload(error, `${this.name}.transcribe failed`),
54
+ source: `${this.name}.transcribe`
55
+ });
56
+ throw error;
57
+ }
58
+ }
59
+ buildTranscriptionRequest(options) {
60
+ const { model, audio, language, prompt, responseFormat, modelOptions } = options;
61
+ const file = this.prepareAudioFile(audio);
62
+ const topLevelResponseFormat = responseFormat;
63
+ const effectiveResponseFormat = topLevelResponseFormat ?? modelOptions?.response_format;
64
+ if (topLevelResponseFormat !== void 0 && modelOptions?.response_format !== void 0 && topLevelResponseFormat !== modelOptions.response_format) throw new Error(`Conflicting response formats: responseFormat="${topLevelResponseFormat}" and modelOptions.response_format="${modelOptions.response_format}". Provide only one.`);
65
+ if (effectiveResponseFormat !== void 0 && !isPlainFormat(effectiveResponseFormat)) throw new Error(`lovable: model "${model}" only supports json and text response formats; received "${effectiveResponseFormat}".`);
66
+ const request = {
67
+ ...modelOptions,
68
+ model,
69
+ file
70
+ };
71
+ delete request.stream;
72
+ if (language !== void 0) request.language = language;
73
+ if (prompt !== void 0) request.prompt = prompt;
74
+ request.response_format = mapResponseFormat(effectiveResponseFormat);
75
+ return request;
76
+ }
77
+ prepareAudioFile(audio) {
78
+ if (typeof File !== "undefined" && audio instanceof File) return audio;
79
+ if (typeof Blob !== "undefined" && audio instanceof Blob) {
80
+ this.ensureFileSupport();
81
+ return new File([audio], "audio.mp3", { type: audio.type || "audio/mpeg" });
82
+ }
83
+ if (typeof ArrayBuffer !== "undefined" && audio instanceof ArrayBuffer) {
84
+ this.ensureFileSupport();
85
+ return new File([audio], "audio.mp3", { type: "audio/mpeg" });
86
+ }
87
+ if (typeof audio === "string") {
88
+ this.ensureFileSupport();
89
+ if (audio.startsWith("data:")) {
90
+ const parts = audio.split(",");
91
+ const header = parts[0];
92
+ const base64Data = parts[1] || "";
93
+ const mimeType = (header?.match(/data:([^;]+)/))?.[1] || "audio/mpeg";
94
+ const bytes = base64ToArrayBuffer(base64Data);
95
+ const extension = mimeType.split("/")[1] || "mp3";
96
+ return new File([bytes], `audio.${extension}`, { type: mimeType });
97
+ }
98
+ const bytes = base64ToArrayBuffer(audio);
99
+ return new File([bytes], "audio.mp3", { type: "audio/mpeg" });
100
+ }
101
+ throw new Error("Invalid audio input type");
102
+ }
103
+ ensureFileSupport() {
104
+ if (typeof File === "undefined") throw new Error("`File` is not available in this environment. Use Node.js 20 or newer, or pass a File object directly.");
105
+ }
106
+ };
107
+ function mapResponseFormat(format) {
108
+ if (format === "text") return "text";
109
+ return "json";
110
+ }
111
+ function createLovableTranscription(model, apiKey, config) {
112
+ return new LovableTranscriptionAdapter({
113
+ apiKey,
114
+ ...config
115
+ }, model);
116
+ }
117
+ function lovableTranscription(model, config) {
118
+ return createLovableTranscription(model, getLovableApiKeyFromEnv(), config);
119
+ }
120
+ //#endregion
121
+ export { LovableTranscriptionAdapter, createLovableTranscription, lovableTranscription };
122
+
123
+ //# sourceMappingURL=transcription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { base64ToArrayBuffer, generateId } from '@tanstack/ai-utils'\nimport {\n getLovableApiKeyFromEnv,\n openaiRequestOptions,\n withLovableDefaults,\n} from '../utils/client'\nimport type {\n TokenUsage,\n TranscriptionOptions,\n TranscriptionResponseFormat,\n TranscriptionResult,\n} from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { LovableTranscriptionModel } from '../model-meta'\nimport type { LovableTranscriptionProviderOptions } from '../audio/transcription-provider-options'\nimport type { LovableClientConfig } from '../utils/client'\n\nfunction isPlainFormat(format: string): format is 'json' | 'text' {\n return format === 'json' || format === 'text'\n}\n\nexport interface LovableTranscriptionConfig extends LovableClientConfig {}\n\nfunction buildTranscriptionUsage(\n response?: OpenAI_SDK.Audio.TranscriptionCreateResponse,\n): TokenUsage | undefined {\n const usage = response?.usage\n if (!usage || usage.type === 'duration') {\n return undefined\n }\n\n const result: TokenUsage = {\n promptTokens: usage.input_tokens || 0,\n completionTokens: usage.output_tokens || 0,\n totalTokens: usage.total_tokens || 0,\n }\n\n const inputDetails = usage.input_token_details\n const promptTokensDetails = {\n ...(inputDetails?.audio_tokens\n ? { audioTokens: inputDetails.audio_tokens }\n : {}),\n ...(inputDetails?.text_tokens\n ? { textTokens: inputDetails.text_tokens }\n : {}),\n }\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n if (usage.output_tokens) {\n result.completionTokensDetails = { textTokens: usage.output_tokens }\n }\n\n return result\n}\n\nexport class LovableTranscriptionAdapter<\n TModel extends LovableTranscriptionModel,\n> extends BaseTranscriptionAdapter<\n TModel,\n LovableTranscriptionProviderOptions\n> {\n readonly name = 'lovable' as const\n\n protected client: OpenAI\n\n constructor(config: LovableTranscriptionConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withLovableDefaults(config))\n }\n\n async transcribe(\n options: TranscriptionOptions<LovableTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, language } = options\n\n try {\n const request = this.buildTranscriptionRequest(options)\n\n options.logger.request(\n `activity=transcription provider=${this.name} model=${model}`,\n { provider: this.name, model },\n )\n\n const response = await this.client.audio.transcriptions.create(\n request,\n openaiRequestOptions(options.abortSignal),\n )\n const usage =\n typeof response === 'string'\n ? undefined\n : buildTranscriptionUsage(response)\n\n return {\n id: generateId(this.name),\n model,\n text: typeof response === 'string' ? response : response.text,\n ...(language !== undefined && { language }),\n ...(usage !== undefined && { usage }),\n }\n } catch (error: unknown) {\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 private buildTranscriptionRequest(\n options: TranscriptionOptions<LovableTranscriptionProviderOptions>,\n ): OpenAI_SDK.Audio.TranscriptionCreateParamsNonStreaming {\n const { model, audio, language, prompt, responseFormat, modelOptions } =\n options\n const file = this.prepareAudioFile(audio)\n const topLevelResponseFormat = responseFormat\n const effectiveResponseFormat =\n topLevelResponseFormat ?? modelOptions?.response_format\n\n if (\n topLevelResponseFormat !== undefined &&\n modelOptions?.response_format !== undefined &&\n topLevelResponseFormat !== modelOptions.response_format\n ) {\n throw new Error(\n `Conflicting response formats: responseFormat=\"${topLevelResponseFormat}\" and modelOptions.response_format=\"${modelOptions.response_format}\". Provide only one.`,\n )\n }\n\n if (\n effectiveResponseFormat !== undefined &&\n !isPlainFormat(effectiveResponseFormat)\n ) {\n throw new Error(\n `lovable: model \"${model}\" only supports json and text response formats; received \"${effectiveResponseFormat}\".`,\n )\n }\n\n const request: OpenAI_SDK.Audio.TranscriptionCreateParamsNonStreaming = {\n ...modelOptions,\n model,\n file,\n }\n delete request.stream\n if (language !== undefined) {\n request.language = language\n }\n if (prompt !== undefined) {\n request.prompt = prompt\n }\n request.response_format = mapResponseFormat(effectiveResponseFormat)\n\n return request\n }\n\n protected prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\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 if (typeof ArrayBuffer !== 'undefined' && audio instanceof ArrayBuffer) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n if (typeof audio === 'string') {\n this.ensureFileSupport()\n\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 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 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\nfunction mapResponseFormat(\n format?: TranscriptionResponseFormat,\n): 'json' | 'text' {\n if (format === 'text') return 'text'\n return 'json'\n}\n\nexport function createLovableTranscription<\n TModel extends LovableTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<LovableTranscriptionConfig, 'apiKey'>,\n): LovableTranscriptionAdapter<TModel> {\n return new LovableTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\nexport function lovableTranscription<TModel extends LovableTranscriptionModel>(\n model: TModel,\n config?: Omit<LovableTranscriptionConfig, 'apiKey'>,\n): LovableTranscriptionAdapter<TModel> {\n return createLovableTranscription(model, getLovableApiKeyFromEnv(), config)\n}\n"],"mappings":";;;;;;AAoBA,SAAS,cAAc,QAA2C;CAChE,OAAO,WAAW,UAAU,WAAW;AACzC;AAIA,SAAS,wBACP,UACwB;CACxB,MAAM,QAAQ,UAAU;CACxB,IAAI,CAAC,SAAS,MAAM,SAAS,YAC3B;CAGF,MAAM,SAAqB;EACzB,cAAc,MAAM,gBAAgB;EACpC,kBAAkB,MAAM,iBAAiB;EACzC,aAAa,MAAM,gBAAgB;CACrC;CAEA,MAAM,eAAe,MAAM;CAC3B,MAAM,sBAAsB;EAC1B,GAAI,cAAc,eACd,EAAE,aAAa,aAAa,aAAa,IACzC,CAAC;EACL,GAAI,cAAc,cACd,EAAE,YAAY,aAAa,YAAY,IACvC,CAAC;CACP;CACA,IAAI,OAAO,KAAK,mBAAmB,CAAC,CAAC,SAAS,GAC5C,OAAO,sBAAsB;CAE/B,IAAI,MAAM,eACR,OAAO,0BAA0B,EAAE,YAAY,MAAM,cAAc;CAGrE,OAAO;AACT;AAEA,IAAa,8BAAb,cAEU,yBAGR;CACA,OAAgB;CAEhB;CAEA,YAAY,QAAoC,OAAe;EAC7D,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,IAAI,OAAO,oBAAoB,MAAM,CAAC;CACtD;CAEA,MAAM,WACJ,SAC8B;EAC9B,MAAM,EAAE,OAAO,aAAa;EAE5B,IAAI;GACF,MAAM,UAAU,KAAK,0BAA0B,OAAO;GAEtD,QAAQ,OAAO,QACb,mCAAmC,KAAK,KAAK,SAAS,SACtD;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GAEA,MAAM,WAAW,MAAM,KAAK,OAAO,MAAM,eAAe,OACtD,SACA,qBAAqB,QAAQ,WAAW,CAC1C;GACA,MAAM,QACJ,OAAO,aAAa,WAChB,KAAA,IACA,wBAAwB,QAAQ;GAEtC,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,MAAM,OAAO,aAAa,WAAW,WAAW,SAAS;IACzD,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IACzC,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACrC;EACF,SAAS,OAAgB;GACvB,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,oBAAoB;IACrD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,mBAAmB;IAChE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;CAEA,0BACE,SACwD;EACxD,MAAM,EAAE,OAAO,OAAO,UAAU,QAAQ,gBAAgB,iBACtD;EACF,MAAM,OAAO,KAAK,iBAAiB,KAAK;EACxC,MAAM,yBAAyB;EAC/B,MAAM,0BACJ,0BAA0B,cAAc;EAE1C,IACE,2BAA2B,KAAA,KAC3B,cAAc,oBAAoB,KAAA,KAClC,2BAA2B,aAAa,iBAExC,MAAM,IAAI,MACR,iDAAiD,uBAAuB,sCAAsC,aAAa,gBAAgB,qBAC7I;EAGF,IACE,4BAA4B,KAAA,KAC5B,CAAC,cAAc,uBAAuB,GAEtC,MAAM,IAAI,MACR,mBAAmB,MAAM,4DAA4D,wBAAwB,GAC/G;EAGF,MAAM,UAAkE;GACtE,GAAG;GACH;GACA;EACF;EACA,OAAO,QAAQ;EACf,IAAI,aAAa,KAAA,GACf,QAAQ,WAAW;EAErB,IAAI,WAAW,KAAA,GACb,QAAQ,SAAS;EAEnB,QAAQ,kBAAkB,kBAAkB,uBAAuB;EAEnE,OAAO;CACT;CAEA,iBAA2B,OAAiD;EAC1E,IAAI,OAAO,SAAS,eAAe,iBAAiB,MAClD,OAAO;EAET,IAAI,OAAO,SAAS,eAAe,iBAAiB,MAAM;GACxD,KAAK,kBAAkB;GACvB,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,aAAa,EACpC,MAAM,MAAM,QAAQ,aACtB,CAAC;EACH;EACA,IAAI,OAAO,gBAAgB,eAAe,iBAAiB,aAAa;GACtE,KAAK,kBAAkB;GACvB,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,aAAa,CAAC;EAC9D;EACA,IAAI,OAAO,UAAU,UAAU;GAC7B,KAAK,kBAAkB;GAEvB,IAAI,MAAM,WAAW,OAAO,GAAG;IAC7B,MAAM,QAAQ,MAAM,MAAM,GAAG;IAC7B,MAAM,SAAS,MAAM;IACrB,MAAM,aAAa,MAAM,MAAM;IAE/B,MAAM,YADY,QAAQ,MAAM,cAAc,EAAA,GACjB,MAAM;IACnC,MAAM,QAAQ,oBAAoB,UAAU;IAC5C,MAAM,YAAY,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM;IAC5C,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,SAAS,aAAa,EAAE,MAAM,SAAS,CAAC;GACnE;GAEA,MAAM,QAAQ,oBAAoB,KAAK;GACvC,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,aAAa,CAAC;EAC9D;EAEA,MAAM,IAAI,MAAM,0BAA0B;CAC5C;CAEA,oBAAkC;EAChC,IAAI,OAAO,SAAS,aAClB,MAAM,IAAI,MACR,uGAEF;CAEJ;AACF;AAEA,SAAS,kBACP,QACiB;CACjB,IAAI,WAAW,QAAQ,OAAO;CAC9B,OAAO;AACT;AAEA,SAAgB,2BAGd,OACA,QACA,QACqC;CACrC,OAAO,IAAI,4BAA4B;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AACrE;AAEA,SAAgB,qBACd,OACA,QACqC;CACrC,OAAO,2BAA2B,OAAO,wBAAwB,GAAG,MAAM;AAC5E"}
@@ -0,0 +1,16 @@
1
+ import { default as OpenAI } from 'openai';
2
+ import { BaseTTSAdapter } from '@tanstack/ai/adapters';
3
+ import { TTSOptions, TTSResult } from '@tanstack/ai';
4
+ import { LovableTTSModel } from '../model-meta.js';
5
+ import { LovableTTSProviderOptions } from '../audio/tts-provider-options.js';
6
+ import { LovableClientConfig } from '../utils/client.js';
7
+ export interface LovableTTSConfig extends LovableClientConfig {
8
+ }
9
+ export declare class LovableTTSAdapter<TModel extends LovableTTSModel> extends BaseTTSAdapter<TModel, LovableTTSProviderOptions> {
10
+ readonly name: "lovable";
11
+ protected client: OpenAI;
12
+ constructor(config: LovableTTSConfig, model: TModel);
13
+ generateSpeech(options: TTSOptions<LovableTTSProviderOptions>): Promise<TTSResult>;
14
+ }
15
+ export declare function createLovableSpeech<TModel extends LovableTTSModel>(model: TModel, apiKey: string, config?: Omit<LovableTTSConfig, 'apiKey'>): LovableTTSAdapter<TModel>;
16
+ export declare function lovableSpeech<TModel extends LovableTTSModel>(model: TModel, config?: Omit<LovableTTSConfig, 'apiKey'>): LovableTTSAdapter<TModel>;
@@ -0,0 +1,69 @@
1
+ import { getLovableApiKeyFromEnv, openaiRequestOptions, withLovableDefaults } from "../utils/client.js";
2
+ import OpenAI from "openai";
3
+ import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
4
+ import { BaseTTSAdapter } from "@tanstack/ai/adapters";
5
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
6
+ //#region src/adapters/tts.ts
7
+ var CONTENT_TYPES = {
8
+ mp3: "audio/mpeg",
9
+ opus: "audio/opus",
10
+ aac: "audio/aac",
11
+ flac: "audio/flac",
12
+ wav: "audio/wav",
13
+ pcm: "audio/pcm"
14
+ };
15
+ var LovableTTSAdapter = class extends BaseTTSAdapter {
16
+ name = "lovable";
17
+ client;
18
+ constructor(config, model) {
19
+ super(model, {});
20
+ this.client = new OpenAI(withLovableDefaults(config));
21
+ }
22
+ async generateSpeech(options) {
23
+ const { model, text, voice, format, speed, modelOptions } = options;
24
+ const request = {
25
+ model,
26
+ input: text,
27
+ voice: voice || "alloy",
28
+ response_format: format,
29
+ ...speed !== void 0 && { speed },
30
+ ...modelOptions ?? {}
31
+ };
32
+ try {
33
+ options.logger.request(`activity=tts provider=${this.name} model=${model} format=${request.response_format ?? "default"} voice=${request.voice}`, {
34
+ provider: this.name,
35
+ model
36
+ });
37
+ const arrayBuffer = await (await this.client.audio.speech.create(request, openaiRequestOptions(options.abortSignal))).arrayBuffer();
38
+ const base64 = arrayBufferToBase64(arrayBuffer);
39
+ const outputFormat = request.response_format || "mp3";
40
+ const contentType = CONTENT_TYPES[outputFormat] || "audio/mpeg";
41
+ return {
42
+ id: generateId(this.name),
43
+ model,
44
+ audio: base64,
45
+ format: outputFormat,
46
+ contentType
47
+ };
48
+ } catch (error) {
49
+ options.logger.errors(`${this.name}.generateSpeech fatal`, {
50
+ error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),
51
+ source: `${this.name}.generateSpeech`
52
+ });
53
+ throw error;
54
+ }
55
+ }
56
+ };
57
+ function createLovableSpeech(model, apiKey, config) {
58
+ return new LovableTTSAdapter({
59
+ apiKey,
60
+ ...config
61
+ }, model);
62
+ }
63
+ function lovableSpeech(model, config) {
64
+ return createLovableSpeech(model, getLovableApiKeyFromEnv(), config);
65
+ }
66
+ //#endregion
67
+ export { LovableTTSAdapter, createLovableSpeech, lovableSpeech };
68
+
69
+ //# sourceMappingURL=tts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts.js","names":[],"sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport {\n getLovableApiKeyFromEnv,\n openaiRequestOptions,\n withLovableDefaults,\n} from '../utils/client'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { LovableTTSModel } from '../model-meta'\nimport type { LovableTTSProviderOptions } from '../audio/tts-provider-options'\nimport type { LovableClientConfig } from '../utils/client'\n\nexport interface LovableTTSConfig extends LovableClientConfig {}\n\nconst CONTENT_TYPES: 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\nexport class LovableTTSAdapter<\n TModel extends LovableTTSModel,\n> extends BaseTTSAdapter<TModel, LovableTTSProviderOptions> {\n readonly name = 'lovable' as const\n\n protected client: OpenAI\n\n constructor(config: LovableTTSConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withLovableDefaults(config))\n }\n\n async generateSpeech(\n options: TTSOptions<LovableTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: voice || 'alloy',\n response_format: format,\n ...(speed !== undefined && { 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(\n request,\n openaiRequestOptions(options.abortSignal),\n )\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n const outputFormat = (request.response_format as string) || 'mp3'\n const contentType = CONTENT_TYPES[outputFormat] || 'audio/mpeg'\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\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\nexport function createLovableSpeech<TModel extends LovableTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<LovableTTSConfig, 'apiKey'>,\n): LovableTTSAdapter<TModel> {\n return new LovableTTSAdapter({ apiKey, ...config }, model)\n}\n\nexport function lovableSpeech<TModel extends LovableTTSModel>(\n model: TModel,\n config?: Omit<LovableTTSConfig, 'apiKey'>,\n): LovableTTSAdapter<TModel> {\n return createLovableSpeech(model, getLovableApiKeyFromEnv(), config)\n}\n"],"mappings":";;;;;;AAiBA,IAAM,gBAAwC;CAC5C,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;AACP;AAEA,IAAa,oBAAb,cAEU,eAAkD;CAC1D,OAAgB;CAEhB;CAEA,YAAY,QAA0B,OAAe;EACnD,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,IAAI,OAAO,oBAAoB,MAAM,CAAC;CACtD;CAEA,MAAM,eACJ,SACoB;EACpB,MAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;EAE5D,MAAM,UAA+C;GACnD;GACA,OAAO;GACP,OAAO,SAAS;GAChB,iBAAiB;GACjB,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,gBAAgB,CAAC;EACvB;EAEA,IAAI;GACF,QAAQ,OAAO,QACb,yBAAyB,KAAK,KAAK,SAAS,MAAM,UAAU,QAAQ,mBAAmB,UAAU,SAAS,QAAQ,SAClH;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GAKA,MAAM,cAAc,OAAM,MAJH,KAAK,OAAO,MAAM,OAAO,OAC9C,SACA,qBAAqB,QAAQ,WAAW,CAC1C,EAAA,CACmC,YAAY;GAC/C,MAAM,SAAS,oBAAoB,WAAW;GAC9C,MAAM,eAAgB,QAAQ,mBAA8B;GAC5D,MAAM,cAAc,cAAc,iBAAiB;GAEnD,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,OAAO;IACP,QAAQ;IACR;GACF;EACF,SAAS,OAAgB;GACvB,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACzD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,uBAAuB;IACpE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;AACF;AAEA,SAAgB,oBACd,OACA,QACA,QAC2B;CAC3B,OAAO,IAAI,kBAAkB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AAC3D;AAEA,SAAgB,cACd,OACA,QAC2B;CAC3B,OAAO,oBAAoB,OAAO,wBAAwB,GAAG,MAAM;AACrE"}
@@ -0,0 +1,41 @@
1
+ import { default as OpenAI } from 'openai';
2
+ import { BaseVideoAdapter, DurationOptions } from '@tanstack/ai/adapters';
3
+ import { VideoGenerationOptions, VideoJobResult, VideoStatusResult, VideoUrlResult } from '@tanstack/ai';
4
+ import { LovableVideoModel, LovableVideoModelDurationByName, LovableVideoModelInputModalitiesByName, LovableVideoModelProviderOptionsByName, LovableVideoModelSizeByName } from '../model-meta.js';
5
+ import { LovableVideoDuration, LovableVideoProviderOptions } from '../video/video-provider-options.js';
6
+ import { LovableClientConfig } from '../utils/client.js';
7
+ /**
8
+ * @experimental Video generation is an experimental feature and may change.
9
+ */
10
+ export interface LovableVideoConfig extends LovableClientConfig {
11
+ /**
12
+ * Opt into fetching HTTP(S) image URL inputs for `input_reference`.
13
+ * The endpoint requires uploaded file bytes, so an HTTP(S) URL has to be
14
+ * downloaded and buffered in memory. When `false` (the default), HTTP(S)
15
+ * URL image inputs throw.
16
+ */
17
+ allowUrlFetch?: boolean;
18
+ }
19
+ /**
20
+ * @experimental Video generation is an experimental feature and may change.
21
+ */
22
+ export declare class LovableVideoAdapter<TModel extends LovableVideoModel> extends BaseVideoAdapter<TModel, LovableVideoProviderOptions, LovableVideoModelProviderOptionsByName, LovableVideoModelSizeByName, LovableVideoModelInputModalitiesByName, LovableVideoModelDurationByName> {
23
+ readonly name: "lovable";
24
+ protected client: OpenAI;
25
+ protected clientConfig: LovableVideoConfig;
26
+ constructor(config: LovableVideoConfig, model: TModel);
27
+ createVideoJob(options: VideoGenerationOptions<LovableVideoProviderOptions, LovableVideoModelSizeByName[TModel], LovableVideoModelDurationByName[TModel]>): Promise<VideoJobResult>;
28
+ getVideoStatus(jobId: string): Promise<VideoStatusResult>;
29
+ getVideoUrl(jobId: string): Promise<VideoUrlResult>;
30
+ availableDurations(): DurationOptions<LovableVideoDuration>;
31
+ snapDuration(seconds: number): LovableVideoDuration | undefined;
32
+ protected mapStatus(apiStatus: string): 'pending' | 'processing' | 'completed' | 'failed';
33
+ }
34
+ /**
35
+ * @experimental Video generation is an experimental feature and may change.
36
+ */
37
+ export declare function createLovableVideo<TModel extends LovableVideoModel>(model: TModel, apiKey: string, config?: Omit<LovableVideoConfig, 'apiKey'>): LovableVideoAdapter<TModel>;
38
+ /**
39
+ * @experimental Video generation is an experimental feature and may change.
40
+ */
41
+ export declare function lovableVideo<TModel extends LovableVideoModel>(model: TModel, config?: Omit<LovableVideoConfig, 'apiKey'>): LovableVideoAdapter<TModel>;
@@ -0,0 +1,164 @@
1
+ import { getLovableApiKeyFromEnv, openaiRequestOptions, withLovableDefaults } from "../utils/client.js";
2
+ import { imagePartToFile } from "../image/image-input-to-file.js";
3
+ import { toApiSeconds, validateHighResDuration, validateVideoSeconds, validateVideoSize } from "../video/video-provider-options.js";
4
+ import OpenAI from "openai";
5
+ import { arrayBufferToBase64 } from "@tanstack/ai-utils";
6
+ import { BaseVideoAdapter, snapToDurationOption } from "@tanstack/ai/adapters";
7
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
8
+ import { resolveMediaPrompt } from "@tanstack/ai";
9
+ //#region src/adapters/video.ts
10
+ var LARGE_MEDIA_BUFFER_BYTES = 10485760;
11
+ var VIDEO_DURATIONS = [
12
+ 4,
13
+ 6,
14
+ 8
15
+ ];
16
+ function warnIfLargeMediaBuffer(byteLength, source) {
17
+ if (byteLength <= LARGE_MEDIA_BUFFER_BYTES) return;
18
+ console.warn(`[lovable.${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.`);
19
+ }
20
+ /**
21
+ * @experimental Video generation is an experimental feature and may change.
22
+ */
23
+ var LovableVideoAdapter = class extends BaseVideoAdapter {
24
+ name = "lovable";
25
+ client;
26
+ clientConfig;
27
+ constructor(config, model) {
28
+ super({}, model);
29
+ this.clientConfig = config;
30
+ const { allowUrlFetch: _allowUrlFetch, ...clientOptions } = config;
31
+ this.client = new OpenAI(withLovableDefaults(clientOptions));
32
+ }
33
+ async createVideoJob(options) {
34
+ const { model, size, duration, modelOptions } = options;
35
+ const resolvedSize = size ?? modelOptions?.size;
36
+ validateVideoSize(model, resolvedSize);
37
+ const seconds = duration ?? modelOptions?.seconds;
38
+ validateVideoSeconds(model, seconds);
39
+ validateHighResDuration(model, resolvedSize, seconds);
40
+ const resolved = resolveMediaPrompt(options.prompt);
41
+ if (resolved.videos.length > 0) throw new Error(`${this.name}.createVideoJob does not support video prompt parts (model: ${model}).`);
42
+ if (resolved.audios.length > 0) throw new Error(`${this.name}.createVideoJob does not support audio prompt parts (model: ${model}).`);
43
+ if (resolved.images.length > 1) throw new Error(`${this.name}: video models accept at most one input_reference image; received ${resolved.images.length}.`);
44
+ const request = {
45
+ model,
46
+ prompt: resolved.text
47
+ };
48
+ const [inputReference] = resolved.images;
49
+ if (inputReference) request.input_reference = await imagePartToFile(inputReference, "input-reference", this.clientConfig.allowUrlFetch ?? false, options.abortSignal);
50
+ if (resolvedSize) request.size = resolvedSize;
51
+ if (seconds !== void 0) {
52
+ const apiSeconds = toApiSeconds(seconds);
53
+ if (apiSeconds !== void 0) request.seconds = apiSeconds;
54
+ }
55
+ try {
56
+ options.logger.request(`activity=video.create provider=${this.name} model=${model} size=${request.size ?? "default"} seconds=${request.seconds ?? "default"}`, {
57
+ provider: this.name,
58
+ model
59
+ });
60
+ return {
61
+ jobId: (await this.client.videos.create(request, openaiRequestOptions(options.abortSignal))).id,
62
+ model
63
+ };
64
+ } catch (error) {
65
+ options.logger.errors(`${this.name}.createVideoJob fatal`, {
66
+ error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),
67
+ source: `${this.name}.createVideoJob`
68
+ });
69
+ throw error;
70
+ }
71
+ }
72
+ async getVideoStatus(jobId) {
73
+ try {
74
+ const response = await this.client.videos.retrieve(jobId);
75
+ return {
76
+ jobId,
77
+ status: this.mapStatus(response.status),
78
+ progress: response.progress,
79
+ ...response.error?.message !== void 0 && { error: response.error.message }
80
+ };
81
+ } catch (error) {
82
+ if (isHttpStatus(error, 404)) return {
83
+ jobId,
84
+ status: "failed",
85
+ error: "Job not found"
86
+ };
87
+ throw error;
88
+ }
89
+ }
90
+ async getVideoUrl(jobId) {
91
+ try {
92
+ const videoInfo = await this.client.videos.retrieve(jobId);
93
+ const directUrl = videoResourceUrl(videoInfo);
94
+ if (directUrl) return {
95
+ jobId,
96
+ url: directUrl,
97
+ ...videoInfo.expires_at != null && { expiresAt: /* @__PURE__ */ new Date(videoInfo.expires_at * 1e3) }
98
+ };
99
+ return dataUrlFromResponse(jobId, await this.client.videos.downloadContent(jobId), "video.downloadContent");
100
+ } catch (error) {
101
+ if (isHttpStatus(error, 404)) throw new Error(`Video job not found: ${jobId}`);
102
+ if (isHttpStatus(error, 400)) throw new Error(`Video is not ready for download. Check status first. Job ID: ${jobId}`);
103
+ throw error;
104
+ }
105
+ }
106
+ availableDurations() {
107
+ return {
108
+ kind: "discrete",
109
+ values: VIDEO_DURATIONS
110
+ };
111
+ }
112
+ snapDuration(seconds) {
113
+ return snapToDurationOption(seconds, this.availableDurations());
114
+ }
115
+ mapStatus(apiStatus) {
116
+ switch (apiStatus) {
117
+ case "queued":
118
+ case "pending": return "pending";
119
+ case "processing":
120
+ case "in_progress": return "processing";
121
+ case "completed":
122
+ case "succeeded": return "completed";
123
+ case "failed":
124
+ case "error":
125
+ case "cancelled": return "failed";
126
+ default: return "processing";
127
+ }
128
+ }
129
+ };
130
+ async function dataUrlFromResponse(jobId, contentResponse, source) {
131
+ const buffer = await (await contentResponse.blob()).arrayBuffer();
132
+ warnIfLargeMediaBuffer(buffer.byteLength, source);
133
+ const base64 = arrayBufferToBase64(buffer);
134
+ return {
135
+ jobId,
136
+ url: `data:${contentResponse.headers.get("content-type") || "video/mp4"};base64,${base64}`
137
+ };
138
+ }
139
+ function videoResourceUrl(video) {
140
+ const extra = video;
141
+ if (extra.url && extra.url.length > 0) return extra.url;
142
+ }
143
+ function isHttpStatus(error, status) {
144
+ return typeof error === "object" && error !== null && "status" in error && error.status === status;
145
+ }
146
+ /**
147
+ * @experimental Video generation is an experimental feature and may change.
148
+ */
149
+ function createLovableVideo(model, apiKey, config) {
150
+ return new LovableVideoAdapter({
151
+ apiKey,
152
+ ...config
153
+ }, model);
154
+ }
155
+ /**
156
+ * @experimental Video generation is an experimental feature and may change.
157
+ */
158
+ function lovableVideo(model, config) {
159
+ return createLovableVideo(model, getLovableApiKeyFromEnv(), config);
160
+ }
161
+ //#endregion
162
+ export { LovableVideoAdapter, createLovableVideo, lovableVideo };
163
+
164
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport {\n getLovableApiKeyFromEnv,\n openaiRequestOptions,\n withLovableDefaults,\n} from '../utils/client'\nimport { imagePartToFile } from '../image/image-input-to-file'\nimport {\n toApiSeconds,\n validateHighResDuration,\n validateVideoSeconds,\n validateVideoSize,\n} from '../video/video-provider-options'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type {\n VideoGenerationOptions,\n VideoJobResult,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type {\n LovableVideoModel,\n LovableVideoModelDurationByName,\n LovableVideoModelInputModalitiesByName,\n LovableVideoModelProviderOptionsByName,\n LovableVideoModelSizeByName,\n} from '../model-meta'\nimport type {\n LovableVideoDuration,\n LovableVideoProviderOptions,\n} from '../video/video-provider-options'\nimport type { LovableClientConfig } from '../utils/client'\n\nconst LARGE_MEDIA_BUFFER_BYTES = 10 * 1024 * 1024\nconst VIDEO_DURATIONS = [\n 4, 6, 8,\n] as const satisfies ReadonlyArray<LovableVideoDuration>\n\nfunction warnIfLargeMediaBuffer(byteLength: number, source: string): void {\n if (byteLength <= LARGE_MEDIA_BUFFER_BYTES) return\n console.warn(\n `[lovable.${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 * @experimental Video generation is an experimental feature and may change.\n */\nexport interface LovableVideoConfig extends LovableClientConfig {\n /**\n * Opt into fetching HTTP(S) image URL inputs for `input_reference`.\n * The endpoint requires uploaded file bytes, so an HTTP(S) URL has to be\n * downloaded and buffered in memory. When `false` (the default), HTTP(S)\n * URL image inputs throw.\n */\n allowUrlFetch?: boolean\n}\n\n/**\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class LovableVideoAdapter<\n TModel extends LovableVideoModel,\n> extends BaseVideoAdapter<\n TModel,\n LovableVideoProviderOptions,\n LovableVideoModelProviderOptionsByName,\n LovableVideoModelSizeByName,\n LovableVideoModelInputModalitiesByName,\n LovableVideoModelDurationByName\n> {\n readonly name = 'lovable' as const\n\n protected client: OpenAI\n protected clientConfig: LovableVideoConfig\n\n constructor(config: LovableVideoConfig, model: TModel) {\n super({}, model)\n this.clientConfig = config\n const { allowUrlFetch: _allowUrlFetch, ...clientOptions } = config\n this.client = new OpenAI(withLovableDefaults(clientOptions))\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n LovableVideoProviderOptions,\n LovableVideoModelSizeByName[TModel],\n LovableVideoModelDurationByName[TModel]\n >,\n ): Promise<VideoJobResult> {\n const { model, size, duration, modelOptions } = options\n\n const resolvedSize = size ?? modelOptions?.size\n validateVideoSize(model, resolvedSize)\n const seconds = duration ?? modelOptions?.seconds\n validateVideoSeconds(model, seconds)\n validateHighResDuration(model, resolvedSize, seconds)\n\n const resolved = resolveMediaPrompt(options.prompt)\n\n if (resolved.videos.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support video prompt parts (model: ${model}).`,\n )\n }\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support audio prompt parts (model: ${model}).`,\n )\n }\n if (resolved.images.length > 1) {\n throw new Error(\n `${this.name}: video models accept at most one input_reference image; received ${resolved.images.length}.`,\n )\n }\n\n const request: OpenAI_SDK.Videos.VideoCreateParams = {\n model,\n prompt: resolved.text,\n }\n const [inputReference] = resolved.images\n if (inputReference) {\n request.input_reference = await imagePartToFile(\n inputReference,\n 'input-reference',\n this.clientConfig.allowUrlFetch ?? false,\n options.abortSignal,\n )\n }\n if (resolvedSize) {\n // Gateway Veo sizes include 1080p and 4K, which are not in the OpenAI SDK union.\n request.size = resolvedSize as OpenAI_SDK.Videos.VideoSize\n }\n if (seconds !== undefined) {\n const apiSeconds = toApiSeconds(seconds)\n if (apiSeconds !== undefined) {\n // Gateway Veo clips can be 6 seconds. The OpenAI SDK union is 4/8/12.\n request.seconds = apiSeconds as OpenAI_SDK.Videos.VideoSeconds\n }\n }\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 const response = await this.client.videos.create(\n request,\n openaiRequestOptions(options.abortSignal),\n )\n return { jobId: response.id, model }\n } catch (error: unknown) {\n options.logger.errors(`${this.name}.createVideoJob fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n try {\n const response = await this.client.videos.retrieve(jobId)\n return {\n jobId,\n status: this.mapStatus(response.status),\n progress: response.progress,\n ...(response.error?.message !== undefined && {\n error: response.error.message,\n }),\n }\n } catch (error: unknown) {\n if (isHttpStatus(error, 404)) {\n return { jobId, status: 'failed', error: 'Job not found' }\n }\n throw error\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n try {\n const videoInfo = await this.client.videos.retrieve(jobId)\n const directUrl = videoResourceUrl(videoInfo)\n if (directUrl) {\n return {\n jobId,\n url: directUrl,\n ...(videoInfo.expires_at != null && {\n expiresAt: new Date(videoInfo.expires_at * 1000),\n }),\n }\n }\n\n const contentResponse = await this.client.videos.downloadContent(jobId)\n return dataUrlFromResponse(\n jobId,\n contentResponse,\n 'video.downloadContent',\n )\n } catch (error: unknown) {\n if (isHttpStatus(error, 404)) {\n throw new Error(`Video job not found: ${jobId}`)\n }\n if (isHttpStatus(error, 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 override availableDurations(): DurationOptions<LovableVideoDuration> {\n return { kind: 'discrete', values: VIDEO_DURATIONS }\n }\n\n override snapDuration(seconds: number): LovableVideoDuration | undefined {\n return snapToDurationOption(seconds, this.availableDurations())\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\nasync function dataUrlFromResponse(\n jobId: string,\n contentResponse: Response,\n source: string,\n): Promise<VideoUrlResult> {\n const videoBlob = await contentResponse.blob()\n const buffer = await videoBlob.arrayBuffer()\n warnIfLargeMediaBuffer(buffer.byteLength, source)\n const base64 = arrayBufferToBase64(buffer)\n const mimeType = contentResponse.headers.get('content-type') || 'video/mp4'\n return {\n jobId,\n url: `data:${mimeType};base64,${base64}`,\n }\n}\n\nfunction videoResourceUrl(video: OpenAI_SDK.Videos.Video): string | undefined {\n const extra = video as OpenAI_SDK.Videos.Video & { url?: string }\n if (extra.url && extra.url.length > 0) {\n return extra.url\n }\n return undefined\n}\n\nfunction isHttpStatus(error: unknown, status: number): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status: unknown }).status === status\n )\n}\n\n/**\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function createLovableVideo<TModel extends LovableVideoModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<LovableVideoConfig, 'apiKey'>,\n): LovableVideoAdapter<TModel> {\n return new LovableVideoAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function lovableVideo<TModel extends LovableVideoModel>(\n model: TModel,\n config?: Omit<LovableVideoConfig, 'apiKey'>,\n): LovableVideoAdapter<TModel> {\n return createLovableVideo(model, getLovableApiKeyFromEnv(), config)\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type LovableTranscriptionResponseFormat = 'json' | 'text';
2
+ export interface LovableTranscriptionProviderOptions {
3
+ temperature?: number;
4
+ response_format?: LovableTranscriptionResponseFormat;
5
+ prompt?: string;
6
+ }
@@ -0,0 +1,8 @@
1
+ export type LovableTTSVoice = 'alloy' | 'ash' | 'ballad' | 'coral' | 'echo' | 'fable' | 'onyx' | 'nova' | 'sage' | 'shimmer' | 'verse';
2
+ export type LovableTTSFormat = 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm';
3
+ export interface LovableTTSProviderOptions {
4
+ /**
5
+ * Extra voice direction in plain language, for example "speak slowly and warmly".
6
+ */
7
+ instructions?: string;
8
+ }
@@ -0,0 +1 @@
1
+ export declare const lovableByok: import('@tanstack/ai/byok').ByokProvider<"lovable">;
@@ -0,0 +1,11 @@
1
+ import { defineByokProvider } from "@tanstack/ai/byok";
2
+ //#region src/byok.ts
3
+ var lovableByok = defineByokProvider({
4
+ id: "lovable",
5
+ label: "Lovable AI Gateway",
6
+ env: "LOVABLE_API_KEY"
7
+ });
8
+ //#endregion
9
+ export { lovableByok };
10
+
11
+ //# sourceMappingURL=byok.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byok.js","names":[],"sources":["../../src/byok.ts"],"sourcesContent":["import { defineByokProvider } from '@tanstack/ai/byok'\n\nexport const lovableByok = defineByokProvider({\n id: 'lovable',\n label: 'Lovable AI Gateway',\n env: 'LOVABLE_API_KEY',\n})\n"],"mappings":";;AAEA,IAAa,cAAc,mBAAmB;CAC5C,IAAI;CACJ,OAAO;CACP,KAAK;AACP,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Provider options for Lovable embedding models.
3
+ *
4
+ * `dimensions` is a top-level option on `embed()`. `encoding_format` is
5
+ * pinned to `float` so vectors are always `number[]`.
6
+ */
7
+ export interface LovableEmbeddingProviderOptions {
8
+ user?: string;
9
+ }
@@ -0,0 +1,8 @@
1
+ import { ImagePart, MediaInputMetadata } from '@tanstack/ai';
2
+ /**
3
+ * Convert a TanStack `ImagePart` into an OpenAI-compatible `File`.
4
+ *
5
+ * HTTP(S) URLs are fetched only when `allowUrlFetch` is set. The gateway
6
+ * edit and `input_reference` endpoints need uploaded bytes, not a URL.
7
+ */
8
+ export declare function imagePartToFile(part: ImagePart<MediaInputMetadata>, fallbackName: string, allowUrlFetch: boolean, abortSignal?: AbortSignal): Promise<File>;
@@ -0,0 +1,47 @@
1
+ import { base64ToArrayBuffer } from "@tanstack/ai-utils";
2
+ //#region src/image/image-input-to-file.ts
3
+ var DEFAULT_MIME = "image/png";
4
+ var MIME_TO_EXT = {
5
+ "image/png": "png",
6
+ "image/jpeg": "jpg",
7
+ "image/jpg": "jpg",
8
+ "image/webp": "webp",
9
+ "image/gif": "gif"
10
+ };
11
+ function extForMime(mimeType) {
12
+ return MIME_TO_EXT[mimeType] ?? mimeType.split("/")[1] ?? "png";
13
+ }
14
+ function ensureFileSupport() {
15
+ if (typeof File === "undefined") throw new Error("`File` is not available in this environment. Image-conditioned generation requires Node 20+ or a browser context.");
16
+ }
17
+ /**
18
+ * Convert a TanStack `ImagePart` into an OpenAI-compatible `File`.
19
+ *
20
+ * HTTP(S) URLs are fetched only when `allowUrlFetch` is set. The gateway
21
+ * edit and `input_reference` endpoints need uploaded bytes, not a URL.
22
+ */
23
+ async function imagePartToFile(part, fallbackName, allowUrlFetch, abortSignal) {
24
+ ensureFileSupport();
25
+ if (part.source.type === "data") {
26
+ const mimeType = part.source.mimeType || DEFAULT_MIME;
27
+ const bytes = base64ToArrayBuffer(part.source.value);
28
+ return new File([bytes], `${fallbackName}.${extForMime(mimeType)}`, { type: mimeType });
29
+ }
30
+ if (/^https?:\/\//i.test(part.source.value) && !allowUrlFetch) throw new Error(`lovable: HTTP(S) URL image inputs are not fetched by default because the edit / input_reference endpoints require uploaded bytes, so the image would be downloaded and buffered in memory (risking OOM on constrained runtimes). Pass a data: URI, or set \`allowUrlFetch: true\` on the adapter config to opt into fetching. URL: ${part.source.value}`);
31
+ const response = await fetch(part.source.value, abortSignal ? { signal: abortSignal } : void 0);
32
+ if (!response.ok) throw new Error(`Failed to fetch image input (${response.status} ${response.statusText}): ${part.source.value}`);
33
+ const blob = await response.blob();
34
+ const mimeType = part.source.mimeType || blob.type || inferMimeFromUrl(part.source.value);
35
+ return new File([blob], `${fallbackName}.${extForMime(mimeType)}`, { type: mimeType });
36
+ }
37
+ function inferMimeFromUrl(url) {
38
+ const match = url.match(/\.(png|jpe?g|webp|gif)(?:\?|#|$)/i);
39
+ if (!match || !match[1]) return DEFAULT_MIME;
40
+ const ext = match[1].toLowerCase();
41
+ if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
42
+ return `image/${ext}`;
43
+ }
44
+ //#endregion
45
+ export { imagePartToFile };
46
+
47
+ //# sourceMappingURL=image-input-to-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-input-to-file.js","names":[],"sources":["../../../src/image/image-input-to-file.ts"],"sourcesContent":["import { base64ToArrayBuffer } from '@tanstack/ai-utils'\nimport type { ImagePart, MediaInputMetadata } from '@tanstack/ai'\n\nconst DEFAULT_MIME = 'image/png'\nconst MIME_TO_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n}\n\nfunction extForMime(mimeType: string): string {\n return MIME_TO_EXT[mimeType] ?? mimeType.split('/')[1] ?? 'png'\n}\n\nfunction ensureFileSupport(): void {\n if (typeof File === 'undefined') {\n throw new Error(\n '`File` is not available in this environment. ' +\n 'Image-conditioned generation requires Node 20+ or a browser context.',\n )\n }\n}\n\n/**\n * Convert a TanStack `ImagePart` into an OpenAI-compatible `File`.\n *\n * HTTP(S) URLs are fetched only when `allowUrlFetch` is set. The gateway\n * edit and `input_reference` endpoints need uploaded bytes, not a URL.\n */\nexport async function imagePartToFile(\n part: ImagePart<MediaInputMetadata>,\n fallbackName: string,\n allowUrlFetch: boolean,\n abortSignal?: AbortSignal,\n): Promise<File> {\n ensureFileSupport()\n\n if (part.source.type === 'data') {\n const mimeType = part.source.mimeType || DEFAULT_MIME\n const bytes = base64ToArrayBuffer(part.source.value)\n return new File([bytes], `${fallbackName}.${extForMime(mimeType)}`, {\n type: mimeType,\n })\n }\n\n if (/^https?:\\/\\//i.test(part.source.value) && !allowUrlFetch) {\n throw new Error(\n `lovable: HTTP(S) URL image inputs are not fetched by default because ` +\n `the edit / input_reference endpoints require uploaded bytes, so ` +\n `the image would be downloaded and buffered in memory (risking OOM on ` +\n `constrained runtimes). Pass a data: URI, or set \\`allowUrlFetch: true\\` ` +\n `on the adapter config to opt into fetching. URL: ${part.source.value}`,\n )\n }\n\n const response = await fetch(\n part.source.value,\n abortSignal ? { signal: abortSignal } : undefined,\n )\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image input (${response.status} ${response.statusText}): ${part.source.value}`,\n )\n }\n const blob = await response.blob()\n const mimeType =\n part.source.mimeType || blob.type || inferMimeFromUrl(part.source.value)\n return new File([blob], `${fallbackName}.${extForMime(mimeType)}`, {\n type: mimeType,\n })\n}\n\nfunction inferMimeFromUrl(url: string): string {\n const match = url.match(/\\.(png|jpe?g|webp|gif)(?:\\?|#|$)/i)\n if (!match || !match[1]) return DEFAULT_MIME\n const ext = match[1].toLowerCase()\n if (ext === 'jpg' || ext === 'jpeg') return 'image/jpeg'\n return `image/${ext}`\n}\n"],"mappings":";;AAGA,IAAM,eAAe;AACrB,IAAM,cAAsC;CAC1C,aAAa;CACb,cAAc;CACd,aAAa;CACb,cAAc;CACd,aAAa;AACf;AAEA,SAAS,WAAW,UAA0B;CAC5C,OAAO,YAAY,aAAa,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM;AAC5D;AAEA,SAAS,oBAA0B;CACjC,IAAI,OAAO,SAAS,aAClB,MAAM,IAAI,MACR,mHAEF;AAEJ;;;;;;;AAQA,eAAsB,gBACpB,MACA,cACA,eACA,aACe;CACf,kBAAkB;CAElB,IAAI,KAAK,OAAO,SAAS,QAAQ;EAC/B,MAAM,WAAW,KAAK,OAAO,YAAY;EACzC,MAAM,QAAQ,oBAAoB,KAAK,OAAO,KAAK;EACnD,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,GAAG,aAAa,GAAG,WAAW,QAAQ,KAAK,EAClE,MAAM,SACR,CAAC;CACH;CAEA,IAAI,gBAAgB,KAAK,KAAK,OAAO,KAAK,KAAK,CAAC,eAC9C,MAAM,IAAI,MACR,sUAIsD,KAAK,OAAO,OACpE;CAGF,MAAM,WAAW,MAAM,MACrB,KAAK,OAAO,OACZ,cAAc,EAAE,QAAQ,YAAY,IAAI,KAAA,CAC1C;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MACR,gCAAgC,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,KAAK,OAAO,OAC1F;CAEF,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,MAAM,WACJ,KAAK,OAAO,YAAY,KAAK,QAAQ,iBAAiB,KAAK,OAAO,KAAK;CACzE,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,GAAG,aAAa,GAAG,WAAW,QAAQ,KAAK,EACjE,MAAM,SACR,CAAC;AACH;AAEA,SAAS,iBAAiB,KAAqB;CAC7C,MAAM,QAAQ,IAAI,MAAM,mCAAmC;CAC3D,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,OAAO;CAChC,MAAM,MAAM,MAAM,EAAE,CAAC,YAAY;CACjC,IAAI,QAAQ,SAAS,QAAQ,QAAQ,OAAO;CAC5C,OAAO,SAAS;AAClB"}
@@ -0,0 +1,12 @@
1
+ export type LovableImageSize = '1024x1024' | '1536x1024' | '1024x1536' | 'auto';
2
+ export type LovableImageQuality = 'high' | 'medium' | 'low' | 'auto';
3
+ export type LovableImageOutputFormat = 'png' | 'jpeg' | 'webp';
4
+ export type LovableImageBackground = 'transparent' | 'opaque' | 'auto';
5
+ export type LovableImageModeration = 'low' | 'auto';
6
+ export interface LovableImageProviderOptions {
7
+ quality?: LovableImageQuality;
8
+ output_format?: LovableImageOutputFormat;
9
+ background?: LovableImageBackground;
10
+ moderation?: LovableImageModeration;
11
+ user?: string;
12
+ }