@heyputer/puter.js 2.1.6 → 2.1.8

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 (57) hide show
  1. package/dist/puter.cjs +2 -2
  2. package/index.d.ts +103 -626
  3. package/package.json +1 -1
  4. package/src/index.js +91 -91
  5. package/src/lib/APICallLogger.js +20 -21
  6. package/src/lib/EventListener.js +10 -10
  7. package/src/lib/filesystem/APIFS.js +11 -19
  8. package/src/lib/filesystem/CacheFS.js +25 -25
  9. package/src/lib/filesystem/PostMessageFS.js +11 -11
  10. package/src/lib/filesystem/definitions.js +11 -10
  11. package/src/lib/path.js +505 -446
  12. package/src/lib/polyfills/fileReaderPoly.js +40 -0
  13. package/src/lib/polyfills/localStorage.js +30 -33
  14. package/src/lib/polyfills/xhrshim.js +206 -207
  15. package/src/lib/utils.js +160 -151
  16. package/src/lib/xdrpc.js +9 -9
  17. package/src/modules/AI.js +416 -290
  18. package/src/modules/Apps.js +56 -56
  19. package/src/modules/Auth.js +17 -17
  20. package/src/modules/Debug.js +1 -1
  21. package/src/modules/Drivers.js +41 -41
  22. package/src/modules/FSItem.js +64 -62
  23. package/src/modules/FileSystem/index.js +22 -23
  24. package/src/modules/FileSystem/operations/copy.js +7 -7
  25. package/src/modules/FileSystem/operations/deleteFSEntry.js +14 -12
  26. package/src/modules/FileSystem/operations/getReadUrl.js +16 -14
  27. package/src/modules/FileSystem/operations/mkdir.js +11 -11
  28. package/src/modules/FileSystem/operations/move.js +12 -12
  29. package/src/modules/FileSystem/operations/read.js +10 -10
  30. package/src/modules/FileSystem/operations/readdir.js +28 -28
  31. package/src/modules/FileSystem/operations/rename.js +11 -11
  32. package/src/modules/FileSystem/operations/sign.js +33 -30
  33. package/src/modules/FileSystem/operations/space.js +7 -7
  34. package/src/modules/FileSystem/operations/stat.js +25 -25
  35. package/src/modules/FileSystem/operations/symlink.js +15 -17
  36. package/src/modules/FileSystem/operations/upload.js +151 -122
  37. package/src/modules/FileSystem/operations/write.js +16 -12
  38. package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +10 -6
  39. package/src/modules/Hosting.js +29 -29
  40. package/src/modules/KV.js +23 -23
  41. package/src/modules/OS.js +15 -15
  42. package/src/modules/Perms.js +19 -21
  43. package/src/modules/PuterDialog.js +46 -48
  44. package/src/modules/Threads.js +17 -20
  45. package/src/modules/UI.js +156 -156
  46. package/src/modules/Util.js +3 -3
  47. package/src/modules/Workers.js +52 -49
  48. package/src/modules/networking/PSocket.js +38 -38
  49. package/src/modules/networking/PTLS.js +54 -47
  50. package/src/modules/networking/PWispHandler.js +49 -47
  51. package/src/modules/networking/parsers.js +110 -108
  52. package/src/modules/networking/requests.js +67 -78
  53. package/src/services/APIAccess.js +9 -9
  54. package/src/services/FSRelay.js +6 -6
  55. package/src/services/Filesystem.js +8 -8
  56. package/src/services/NoPuterYet.js +2 -2
  57. package/src/services/XDIncoming.js +1 -1
package/src/modules/AI.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import * as utils from '../lib/utils.js';
2
2
 
3
3
  const normalizeTTSProvider = (value) => {
4
- if (typeof value !== 'string') {
4
+ if ( typeof value !== 'string' ) {
5
5
  return 'aws-polly';
6
6
  }
7
7
  const lower = value.toLowerCase();
8
- if (lower === 'openai') return 'openai';
9
- if (lower === 'aws' || lower === 'polly' || lower === 'aws-polly') return 'aws-polly';
8
+ if ( lower === 'openai' ) return 'openai';
9
+ if ( ['elevenlabs', 'eleven', '11labs', '11-labs', 'eleven-labs', 'elevenlabs-tts'].includes(lower) ) return 'elevenlabs';
10
+ if ( lower === 'aws' || lower === 'polly' || lower === 'aws-polly' ) return 'aws-polly';
10
11
  return value;
11
12
  };
12
13
 
@@ -20,6 +21,13 @@ const TOGETHER_IMAGE_MODEL_PREFIXES = [
20
21
  'sg161222/',
21
22
  'wavymulder/',
22
23
  'prompthero/',
24
+ 'bytedance-seed/',
25
+ 'hidream-ai/',
26
+ 'lykon/',
27
+ 'qwen/',
28
+ 'rundiffusion/',
29
+ 'google/',
30
+ 'ideogram/',
23
31
  ];
24
32
 
25
33
  const TOGETHER_IMAGE_MODEL_KEYWORDS = [
@@ -40,7 +48,7 @@ const TOGETHER_VIDEO_MODEL_PREFIXES = [
40
48
  'wan-ai/',
41
49
  ];
42
50
 
43
- class AI{
51
+ class AI {
44
52
  /**
45
53
  * Creates a new instance with the given authentication token, API origin, and app ID,
46
54
  *
@@ -68,7 +76,7 @@ class AI{
68
76
 
69
77
  /**
70
78
  * Sets the API origin.
71
- *
79
+ *
72
80
  * @param {string} APIOrigin - The new API origin.
73
81
  * @memberof [AI]
74
82
  * @returns {void}
@@ -77,83 +85,96 @@ class AI{
77
85
  this.APIOrigin = APIOrigin;
78
86
  }
79
87
 
80
- /**
88
+ /**
81
89
  * Returns a list of available AI models.
82
90
  * @param {string} provider - The provider to filter the models returned.
83
- * @returns {Object} Object containing lists of available models by provider
91
+ * @returns {Array} Array containing available model objects
84
92
  */
85
- async listModels(provider) {
86
- const modelsByProvider = {};
93
+ async listModels (provider) {
94
+ // Prefer the public API endpoint and fall back to the legacy driver call if needed.
95
+ const headers = this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {};
96
+
97
+ const tryFetchModels = async () => {
98
+ const resp = await fetch(`${this.APIOrigin }/puterai/chat/models/details`, { headers });
99
+ if ( !resp.ok ) return null;
100
+ const data = await resp.json();
101
+ const models = Array.isArray(data?.models) ? data.models : [];
102
+ return provider ? models.filter(model => model.provider === provider) : models;
103
+ };
104
+
105
+ const tryDriverModels = async () => {
106
+ const models = await puter.drivers.call('puter-chat-completion', 'ai-chat', 'models');
107
+ const result = Array.isArray(models?.result) ? models.result : [];
108
+ return provider ? result.filter(model => model.provider === provider) : result;
109
+ };
87
110
 
88
- const models = await puter.drivers.call('puter-chat-completion','ai-chat','models');
111
+ const models = await (async () => {
112
+ try {
113
+ const apiModels = await tryFetchModels();
114
+ if ( apiModels !== null ) return apiModels;
115
+ } catch (e) {
116
+ // Ignore and fall back to the driver call below.
117
+ }
118
+ try {
119
+ return await tryDriverModels();
120
+ } catch (e) {
121
+ return [];
122
+ }
123
+ })();
89
124
 
90
- if (!models || !models.result || !Array.isArray(models.result)) {
91
- return modelsByProvider;
92
- }
93
- models.result.forEach(item => {
94
- if (!item.provider || !item.id) return;
95
- if (provider && item.provider !== provider) return;
96
- if (!modelsByProvider[item.provider]) modelsByProvider[item.provider] = [];
97
- modelsByProvider[item.provider].push(item.id);
98
- });
99
-
100
- return modelsByProvider;
125
+ return models;
101
126
  }
102
127
 
103
128
  /**
104
129
  * Returns a list of all available AI providers
105
130
  * @returns {Array} Array containing providers
106
131
  */
107
- async listModelProviders() {
108
- let providers = [];
109
- const models = await puter.drivers.call('puter-chat-completion','ai-chat','models');
110
-
111
- if (!models || !models.result || !Array.isArray(models.result)) return providers; // if models is invalid then return empty array
112
- providers = new Set(); // Use a Set to store unique providers
113
- models.result.forEach(item => {
114
- if (item.provider) providers.add(item.provider);
132
+ async listModelProviders () {
133
+ const models = await this.listModels();
134
+ const providers = new Set();
135
+ (models ?? []).forEach(item => {
136
+ if ( item?.provider ) providers.add(item.provider);
115
137
  });
116
- providers = Array.from(providers); // Convert Set to an array
117
- return providers;
138
+ return Array.from(providers);
118
139
  }
119
-
140
+
120
141
  img2txt = async (...args) => {
121
142
  const MAX_INPUT_SIZE = 10 * 1024 * 1024;
122
- if (!args || args.length === 0) {
143
+ if ( !args || args.length === 0 ) {
123
144
  throw { message: 'Arguments are required', code: 'arguments_required' };
124
145
  }
125
146
 
126
147
  const isBlobLike = (value) => {
127
- if (typeof Blob === 'undefined') return false;
148
+ if ( typeof Blob === 'undefined' ) return false;
128
149
  return value instanceof Blob || (typeof File !== 'undefined' && value instanceof File);
129
150
  };
130
151
  const isPlainObject = (value) => value && typeof value === 'object' && !Array.isArray(value) && !isBlobLike(value);
131
152
  const normalizeProvider = (value) => {
132
- if (!value) return 'aws-textract';
153
+ if ( ! value ) return 'aws-textract';
133
154
  const normalized = String(value).toLowerCase();
134
- if (['aws', 'textract', 'aws-textract'].includes(normalized)) return 'aws-textract';
135
- if (['mistral', 'mistral-ocr'].includes(normalized)) return 'mistral';
155
+ if ( ['aws', 'textract', 'aws-textract'].includes(normalized) ) return 'aws-textract';
156
+ if ( ['mistral', 'mistral-ocr'].includes(normalized) ) return 'mistral';
136
157
  return 'aws-textract';
137
158
  };
138
159
 
139
160
  let options = {};
140
- if (isPlainObject(args[0])) {
161
+ if ( isPlainObject(args[0]) ) {
141
162
  options = { ...args[0] };
142
163
  } else {
143
164
  options.source = args[0];
144
165
  }
145
166
 
146
167
  let testMode = false;
147
- for (let i = 1; i < args.length; i++) {
168
+ for ( let i = 1; i < args.length; i++ ) {
148
169
  const value = args[i];
149
- if (typeof value === 'boolean') {
170
+ if ( typeof value === 'boolean' ) {
150
171
  testMode = testMode || value;
151
- } else if (isPlainObject(value)) {
172
+ } else if ( isPlainObject(value) ) {
152
173
  options = { ...options, ...value };
153
174
  }
154
175
  }
155
176
 
156
- if (typeof options.testMode === 'boolean') {
177
+ if ( typeof options.testMode === 'boolean' ) {
157
178
  testMode = options.testMode;
158
179
  }
159
180
 
@@ -161,46 +182,46 @@ class AI{
161
182
  delete options.provider;
162
183
  delete options.testMode;
163
184
 
164
- if (!options.source) {
185
+ if ( ! options.source ) {
165
186
  throw { message: 'Source is required', code: 'source_required' };
166
187
  }
167
188
 
168
- if (isBlobLike(options.source)) {
189
+ if ( isBlobLike(options.source) ) {
169
190
  options.source = await utils.blobToDataUri(options.source);
170
- } else if (options.source?.source && isBlobLike(options.source.source)) {
191
+ } else if ( options.source?.source && isBlobLike(options.source.source) ) {
171
192
  // Support shape { source: Blob }
172
193
  options.source = await utils.blobToDataUri(options.source.source);
173
194
  }
174
195
 
175
- if (typeof options.source === 'string' &&
196
+ if ( typeof options.source === 'string' &&
176
197
  options.source.startsWith('data:') &&
177
- options.source.length > MAX_INPUT_SIZE) {
178
- throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
198
+ options.source.length > MAX_INPUT_SIZE ) {
199
+ throw { message: `Input size cannot be larger than ${ MAX_INPUT_SIZE}`, code: 'input_too_large' };
179
200
  }
180
201
 
181
202
  const toText = (result) => {
182
- if (!result) return '';
183
- if (Array.isArray(result.blocks) && result.blocks.length) {
203
+ if ( ! result ) return '';
204
+ if ( Array.isArray(result.blocks) && result.blocks.length ) {
184
205
  let str = '';
185
- for (const block of result.blocks) {
186
- if (typeof block?.text !== 'string') continue;
187
- if (!block.type || block.type === 'text/textract:LINE' || block.type.startsWith('text/')) {
188
- str += block.text + '\n';
206
+ for ( const block of result.blocks ) {
207
+ if ( typeof block?.text !== 'string' ) continue;
208
+ if ( !block.type || block.type === 'text/textract:LINE' || block.type.startsWith('text/') ) {
209
+ str += `${block.text }\n`;
189
210
  }
190
211
  }
191
- if (str.trim()) return str;
212
+ if ( str.trim() ) return str;
192
213
  }
193
- if (Array.isArray(result.pages) && result.pages.length) {
214
+ if ( Array.isArray(result.pages) && result.pages.length ) {
194
215
  const markdown = result.pages
195
216
  .map(page => (page?.markdown || '').trim())
196
217
  .filter(Boolean)
197
218
  .join('\n\n');
198
- if (markdown.trim()) return markdown;
219
+ if ( markdown.trim() ) return markdown;
199
220
  }
200
- if (typeof result.document_annotation === 'string') {
221
+ if ( typeof result.document_annotation === 'string' ) {
201
222
  return result.document_annotation;
202
223
  }
203
- if (typeof result.text === 'string') {
224
+ if ( typeof result.text === 'string' ) {
204
225
  return result.text;
205
226
  }
206
227
  return '';
@@ -212,17 +233,17 @@ class AI{
212
233
  });
213
234
 
214
235
  return await driverCall.call(this, options);
215
- }
236
+ };
216
237
 
217
238
  txt2speech = async (...args) => {
218
239
  let MAX_INPUT_SIZE = 3000;
219
240
  let options = {};
220
241
  let testMode = false;
221
242
 
222
- if(!args){
223
- throw({message: 'Arguments are required', code: 'arguments_required'});
243
+ if ( ! args ) {
244
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
224
245
  }
225
-
246
+
226
247
  // Accept arguments in the following formats:
227
248
  // 1. Shorthand API
228
249
  // puter.ai.txt2speech("Hello world")
@@ -238,123 +259,258 @@ class AI{
238
259
  // puter.ai.txt2speech("Hello world", "en-US")
239
260
  // puter.ai.txt2speech("Hello world", "en-US", "Joanna")
240
261
  // puter.ai.txt2speech("Hello world", "en-US", "Joanna", "neural")
241
- //
262
+ //
242
263
  // Undefined parameters will be set to default values:
243
264
  // - voice: "Joanna"
244
265
  // - engine: "standard"
245
266
  // - language: "en-US"
246
267
 
247
-
248
- if (typeof args[0] === 'string') {
268
+ if ( typeof args[0] === 'string' ) {
249
269
  options = { text: args[0] };
250
270
  }
251
271
 
252
- if (args[1] && typeof args[1] === 'object' && !Array.isArray(args[1])) {
272
+ if ( args[1] && typeof args[1] === 'object' && !Array.isArray(args[1]) ) {
253
273
  // for verbose object API
254
274
  Object.assign(options, args[1]);
255
- } else if (args[1] && typeof args[1] === 'string') {
275
+ } else if ( args[1] && typeof args[1] === 'string' ) {
256
276
  // for legacy positional-arguments API
257
- //
277
+ //
258
278
  // puter.ai.txt2speech(<text>, <language>, <voice>, <engine>)
259
279
  options.language = args[1];
260
-
261
- if (args[2] && typeof args[2] === 'string') {
280
+
281
+ if ( args[2] && typeof args[2] === 'string' ) {
262
282
  options.voice = args[2];
263
283
  }
264
-
265
- if (args[3] && typeof args[3] === 'string') {
284
+
285
+ if ( args[3] && typeof args[3] === 'string' ) {
266
286
  options.engine = args[3];
267
287
  }
268
- } else if (args[1] && typeof args[1] !== 'boolean') {
288
+ } else if ( args[1] && typeof args[1] !== 'boolean' ) {
269
289
  // If second argument is not an object, string, or boolean, throw an error
270
290
  throw { message: 'Second argument must be an options object or language string. Use: txt2speech("text", { voice: "name", engine: "type", language: "code" }) or txt2speech("text", "language", "voice", "engine")', code: 'invalid_arguments' };
271
291
  }
272
292
 
273
293
  // Validate required text parameter
274
- if (!options.text) {
294
+ if ( ! options.text ) {
275
295
  throw { message: 'Text parameter is required', code: 'text_required' };
276
296
  }
277
297
 
278
298
  const validEngines = ['standard', 'neural', 'long-form', 'generative'];
279
299
  let provider = normalizeTTSProvider(options.provider);
280
300
 
281
- if (options.engine && normalizeTTSProvider(options.engine) === 'openai' && !options.provider) {
301
+ if ( options.engine && normalizeTTSProvider(options.engine) === 'openai' && !options.provider ) {
282
302
  provider = 'openai';
283
303
  }
284
304
 
285
- if (provider === 'openai') {
286
- if (!options.model && typeof options.engine === 'string') {
305
+ if ( options.engine && normalizeTTSProvider(options.engine) === 'elevenlabs' && !options.provider ) {
306
+ provider = 'elevenlabs';
307
+ }
308
+
309
+ if ( provider === 'openai' ) {
310
+ if ( !options.model && typeof options.engine === 'string' ) {
287
311
  options.model = options.engine;
288
312
  }
289
- if (!options.voice) {
313
+ if ( ! options.voice ) {
290
314
  options.voice = 'alloy';
291
315
  }
292
- if (!options.model) {
316
+ if ( ! options.model ) {
293
317
  options.model = 'gpt-4o-mini-tts';
294
318
  }
295
- if (!options.response_format) {
319
+ if ( ! options.response_format ) {
296
320
  options.response_format = 'mp3';
297
321
  }
298
322
  delete options.engine;
323
+ } else if ( provider === 'elevenlabs' ) {
324
+ if ( ! options.voice ) {
325
+ options.voice = '21m00Tcm4TlvDq8ikWAM';
326
+ }
327
+ if ( ! options.model && typeof options.engine === 'string' ) {
328
+ options.model = options.engine;
329
+ }
330
+ if ( ! options.model ) {
331
+ options.model = 'eleven_multilingual_v2';
332
+ }
333
+ if ( ! options.output_format && !options.response_format ) {
334
+ options.output_format = 'mp3_44100_128';
335
+ }
336
+ if ( options.response_format && !options.output_format ) {
337
+ options.output_format = options.response_format;
338
+ }
339
+ delete options.engine;
299
340
  } else {
300
341
  provider = 'aws-polly';
301
342
 
302
- if (options.engine && !validEngines.includes(options.engine)) {
303
- throw { message: 'Invalid engine. Must be one of: ' + validEngines.join(', '), code: 'invalid_engine' };
343
+ if ( options.engine && !validEngines.includes(options.engine) ) {
344
+ throw { message: `Invalid engine. Must be one of: ${ validEngines.join(', ')}`, code: 'invalid_engine' };
304
345
  }
305
346
 
306
- if (!options.voice) {
347
+ if ( ! options.voice ) {
307
348
  options.voice = 'Joanna';
308
349
  }
309
- if (!options.engine) {
350
+ if ( ! options.engine ) {
310
351
  options.engine = 'standard';
311
352
  }
312
- if (!options.language) {
353
+ if ( ! options.language ) {
313
354
  options.language = 'en-US';
314
355
  }
315
356
  }
316
357
 
317
358
  // check input size
318
- if (options.text.length > MAX_INPUT_SIZE) {
319
- throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
359
+ if ( options.text.length > MAX_INPUT_SIZE ) {
360
+ throw { message: `Input size cannot be larger than ${ MAX_INPUT_SIZE}`, code: 'input_too_large' };
320
361
  }
321
362
 
322
363
  // determine if test mode is enabled (check all arguments for boolean true)
323
- for (let i = 0; i < args.length; i++) {
324
- if (typeof args[i] === 'boolean' && args[i] === true) {
364
+ for ( let i = 0; i < args.length; i++ ) {
365
+ if ( typeof args[i] === 'boolean' && args[i] === true ) {
325
366
  testMode = true;
326
367
  break;
327
368
  }
328
369
  }
329
370
 
330
- const driverName = provider === 'openai' ? 'openai-tts' : 'aws-polly';
371
+ const driverName = provider === 'openai'
372
+ ? 'openai-tts'
373
+ : (provider === 'elevenlabs' ? 'elevenlabs-tts' : 'aws-polly');
331
374
 
332
375
  return await utils.make_driver_method(['source'], 'puter-tts', driverName, 'synthesize', {
333
376
  responseType: 'blob',
334
377
  test_mode: testMode ?? false,
335
378
  transform: async (result) => {
336
379
  let url;
337
- if (typeof result === 'string') {
380
+ if ( typeof result === 'string' ) {
338
381
  url = result;
339
- } else if (result instanceof Blob) {
382
+ } else if ( result instanceof Blob ) {
340
383
  url = await utils.blob_to_url(result);
341
- } else if (result instanceof ArrayBuffer) {
384
+ } else if ( result instanceof ArrayBuffer ) {
342
385
  const blob = new Blob([result]);
343
386
  url = await utils.blob_to_url(blob);
344
- } else if (result && typeof result === 'object' && typeof result.arrayBuffer === 'function') {
387
+ } else if ( result && typeof result === 'object' && typeof result.arrayBuffer === 'function' ) {
345
388
  const arrayBuffer = await result.arrayBuffer();
346
389
  const blob = new Blob([arrayBuffer], { type: result.type || undefined });
347
390
  url = await utils.blob_to_url(blob);
348
391
  } else {
349
392
  throw { message: 'Unexpected audio response format', code: 'invalid_audio_response' };
350
393
  }
351
- const audio = new Audio(url);
394
+ const audio = new (globalThis.Audio || Object)();
395
+ audio.src = url;
352
396
  audio.toString = () => url;
353
397
  audio.valueOf = () => url;
354
398
  return audio;
355
- }
399
+ },
356
400
  }).call(this, options);
357
- }
401
+ };
402
+
403
+ speech2speech = async (...args) => {
404
+ const MAX_INPUT_SIZE = 25 * 1024 * 1024;
405
+ if ( !args || !args.length ) {
406
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
407
+ }
408
+
409
+ const normalizeSource = async (value) => {
410
+ if ( value instanceof Blob ) {
411
+ return await utils.blobToDataUri(value);
412
+ }
413
+ return value;
414
+ };
415
+
416
+ const normalizeOptions = (opts = {}) => {
417
+ const normalized = { ...opts };
418
+ if ( normalized.voiceId && !normalized.voice && !normalized.voice_id ) normalized.voice = normalized.voiceId;
419
+ if ( normalized.modelId && !normalized.model && !normalized.model_id ) normalized.model = normalized.modelId;
420
+ if ( normalized.outputFormat && !normalized.output_format ) normalized.output_format = normalized.outputFormat;
421
+ if ( normalized.voiceSettings && !normalized.voice_settings ) normalized.voice_settings = normalized.voiceSettings;
422
+ if ( normalized.fileFormat && !normalized.file_format ) normalized.file_format = normalized.fileFormat;
423
+ if ( normalized.removeBackgroundNoise !== undefined && normalized.remove_background_noise === undefined ) {
424
+ normalized.remove_background_noise = normalized.removeBackgroundNoise;
425
+ }
426
+ if ( normalized.optimizeStreamingLatency !== undefined && normalized.optimize_streaming_latency === undefined ) {
427
+ normalized.optimize_streaming_latency = normalized.optimizeStreamingLatency;
428
+ }
429
+ if ( normalized.enableLogging !== undefined && normalized.enable_logging === undefined ) {
430
+ normalized.enable_logging = normalized.enableLogging;
431
+ }
432
+ delete normalized.voiceId;
433
+ delete normalized.modelId;
434
+ delete normalized.outputFormat;
435
+ delete normalized.voiceSettings;
436
+ delete normalized.fileFormat;
437
+ delete normalized.removeBackgroundNoise;
438
+ delete normalized.optimizeStreamingLatency;
439
+ delete normalized.enableLogging;
440
+ return normalized;
441
+ };
442
+
443
+ let options = {};
444
+ let testMode = false;
445
+
446
+ const primary = args[0];
447
+ if ( primary && typeof primary === 'object' && !Array.isArray(primary) && !(primary instanceof Blob) ) {
448
+ options = { ...primary };
449
+ } else {
450
+ options.audio = await normalizeSource(primary);
451
+ }
452
+
453
+ if ( args[1] && typeof args[1] === 'object' && !Array.isArray(args[1]) && !(args[1] instanceof Blob) ) {
454
+ options = { ...options, ...args[1] };
455
+ } else if ( typeof args[1] === 'boolean' ) {
456
+ testMode = args[1];
457
+ }
458
+
459
+ if ( typeof args[2] === 'boolean' ) {
460
+ testMode = args[2];
461
+ }
462
+
463
+ if ( options.file ) {
464
+ options.audio = await normalizeSource(options.file);
465
+ delete options.file;
466
+ }
467
+
468
+ if ( options.audio instanceof Blob ) {
469
+ options.audio = await normalizeSource(options.audio);
470
+ }
471
+
472
+ if ( ! options.audio ) {
473
+ throw { message: 'Audio input is required', code: 'audio_required' };
474
+ }
475
+
476
+ if ( typeof options.audio === 'string' && options.audio.startsWith('data:') ) {
477
+ const base64 = options.audio.split(',')[1] || '';
478
+ const padding = base64.endsWith('==') ? 2 : (base64.endsWith('=') ? 1 : 0);
479
+ const byteLength = Math.floor((base64.length * 3) / 4) - padding;
480
+ if ( byteLength > MAX_INPUT_SIZE ) {
481
+ throw { message: 'Input size cannot be larger than 25 MB', code: 'input_too_large' };
482
+ }
483
+ }
484
+
485
+ const driverArgs = normalizeOptions({ ...options });
486
+ delete driverArgs.provider;
487
+
488
+ return await utils.make_driver_method(['audio'], 'puter-speech2speech', 'elevenlabs-voice-changer', 'convert', {
489
+ responseType: 'blob',
490
+ test_mode: testMode,
491
+ transform: async (result) => {
492
+ let url;
493
+ if ( typeof result === 'string' ) {
494
+ url = result;
495
+ } else if ( result instanceof Blob ) {
496
+ url = await utils.blob_to_url(result);
497
+ } else if ( result instanceof ArrayBuffer ) {
498
+ const blob = new Blob([result]);
499
+ url = await utils.blob_to_url(blob);
500
+ } else if ( result && typeof result === 'object' && typeof result.arrayBuffer === 'function' ) {
501
+ const arrayBuffer = await result.arrayBuffer();
502
+ const blob = new Blob([arrayBuffer], { type: result.type || undefined });
503
+ url = await utils.blob_to_url(blob);
504
+ } else {
505
+ throw { message: 'Unexpected audio response format', code: 'invalid_audio_response' };
506
+ }
507
+ const audio = new Audio(url);
508
+ audio.toString = () => url;
509
+ audio.valueOf = () => url;
510
+ return audio;
511
+ },
512
+ }).call(this, driverArgs);
513
+ };
358
514
 
359
515
  speech2txt = async (...args) => {
360
516
  const MAX_INPUT_SIZE = 25 * 1024 * 1024;
@@ -398,7 +554,7 @@ class AI{
398
554
  options.file = await normalizeSource(options.file);
399
555
  }
400
556
 
401
- if ( !options.file ) {
557
+ if ( ! options.file ) {
402
558
  throw { message: 'Audio input is required', code: 'audio_required' };
403
559
  }
404
560
 
@@ -426,7 +582,7 @@ class AI{
426
582
  return result;
427
583
  },
428
584
  }).call(this, driverArgs);
429
- }
585
+ };
430
586
 
431
587
  // Add new methods for TTS engine management
432
588
  txt2speech = Object.assign(this.txt2speech, {
@@ -438,19 +594,25 @@ class AI{
438
594
  let provider = 'aws-polly';
439
595
  let params = {};
440
596
 
441
- if (typeof options === 'string') {
597
+ if ( typeof options === 'string' ) {
442
598
  provider = normalizeTTSProvider(options);
443
- } else if (options && typeof options === 'object') {
599
+ } else if ( options && typeof options === 'object' ) {
444
600
  provider = normalizeTTSProvider(options.provider) || provider;
445
601
  params = { ...options };
446
602
  delete params.provider;
447
603
  }
448
604
 
449
- if (provider === 'openai') {
605
+ if ( provider === 'openai' ) {
450
606
  params.provider = 'openai';
451
607
  }
452
608
 
453
- const driverName = provider === 'openai' ? 'openai-tts' : 'aws-polly';
609
+ if ( provider === 'elevenlabs' ) {
610
+ params.provider = 'elevenlabs';
611
+ }
612
+
613
+ const driverName = provider === 'openai'
614
+ ? 'openai-tts'
615
+ : (provider === 'elevenlabs' ? 'elevenlabs-tts' : 'aws-polly');
454
616
 
455
617
  return await utils.make_driver_method(['source'], 'puter-tts', driverName, 'list_engines', {
456
618
  responseType: 'text',
@@ -466,28 +628,33 @@ class AI{
466
628
  let provider = 'aws-polly';
467
629
  let params = {};
468
630
 
469
- if (typeof options === 'string') {
631
+ if ( typeof options === 'string' ) {
470
632
  params.engine = options;
471
- } else if (options && typeof options === 'object') {
633
+ } else if ( options && typeof options === 'object' ) {
472
634
  provider = normalizeTTSProvider(options.provider) || provider;
473
635
  params = { ...options };
474
636
  delete params.provider;
475
637
  }
476
638
 
477
- if (provider === 'openai') {
639
+ if ( provider === 'openai' ) {
478
640
  params.provider = 'openai';
479
641
  delete params.engine;
480
642
  }
481
643
 
482
- const driverName = provider === 'openai' ? 'openai-tts' : 'aws-polly';
644
+ if ( provider === 'elevenlabs' ) {
645
+ params.provider = 'elevenlabs';
646
+ }
647
+
648
+ const driverName = provider === 'openai'
649
+ ? 'openai-tts'
650
+ : (provider === 'elevenlabs' ? 'elevenlabs-tts' : 'aws-polly');
483
651
 
484
652
  return utils.make_driver_method(['source'], 'puter-tts', driverName, 'list_voices', {
485
653
  responseType: 'text',
486
654
  }).call(this, params);
487
- }
655
+ },
488
656
  });
489
657
 
490
-
491
658
  // accepts either a string or an array of message objects
492
659
  // if string, it's treated as the prompt which is a shorthand for { messages: [{ content: prompt }] }
493
660
  // if object, it's treated as the full argument object that the API expects
@@ -502,100 +669,99 @@ class AI{
502
669
  let driver = 'openai-completion';
503
670
 
504
671
  // Check that the argument is not undefined or null
505
- if(!args){
506
- throw({message: 'Arguments are required', code: 'arguments_required'});
672
+ if ( ! args ) {
673
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
507
674
  }
508
675
 
509
676
  // ai.chat(prompt)
510
- if(typeof args[0] === 'string'){
677
+ if ( typeof args[0] === 'string' ) {
511
678
  requestParams = { messages: [{ content: args[0] }] };
512
679
  }
513
680
 
514
681
  // ai.chat(prompt, testMode)
515
- if (typeof args[0] === 'string' && (!args[1] || typeof args[1] === 'boolean')) {
682
+ if ( typeof args[0] === 'string' && (!args[1] || typeof args[1] === 'boolean') ) {
516
683
  requestParams = { messages: [{ content: args[0] }] };
517
684
  }
518
685
 
519
686
  // ai.chat(prompt, imageURL/File)
520
687
  // ai.chat(prompt, imageURL/File, testMode)
521
- else if (typeof args[0] === 'string' && (typeof args[1] === 'string' || args[1] instanceof File)) {
688
+ else if ( typeof args[0] === 'string' && (typeof args[1] === 'string' || args[1] instanceof File) ) {
522
689
  // if imageURL is a File, transform it to a data URI
523
- if(args[1] instanceof File){
690
+ if ( args[1] instanceof File ) {
524
691
  args[1] = await utils.blobToDataUri(args[1]);
525
692
  }
526
693
 
527
694
  // parse args[1] as an image_url object
528
- requestParams = {
695
+ requestParams = {
529
696
  vision: true,
530
697
  messages: [
531
- {
698
+ {
532
699
  content: [
533
700
  args[0],
534
701
  {
535
702
  image_url: {
536
- url: args[1]
537
- }
538
- }
539
- ],
540
- }
541
- ]
703
+ url: args[1],
704
+ },
705
+ },
706
+ ],
707
+ },
708
+ ],
542
709
  };
543
710
  }
544
711
  // chat(prompt, [imageURLs])
545
- else if (typeof args[0] === 'string' && Array.isArray(args[1])) {
712
+ else if ( typeof args[0] === 'string' && Array.isArray(args[1]) ) {
546
713
  // parse args[1] as an array of image_url objects
547
- for (let i = 0; i < args[1].length; i++) {
714
+ for ( let i = 0; i < args[1].length; i++ ) {
548
715
  args[1][i] = { image_url: { url: args[1][i] } };
549
716
  }
550
- requestParams = {
717
+ requestParams = {
551
718
  vision: true,
552
719
  messages: [
553
- {
720
+ {
554
721
  content: [
555
722
  args[0],
556
- ...args[1]
557
- ],
558
- }
559
- ]
723
+ ...args[1],
724
+ ],
725
+ },
726
+ ],
560
727
  };
561
728
  }
562
729
  // chat([messages])
563
- else if (Array.isArray(args[0])) {
730
+ else if ( Array.isArray(args[0]) ) {
564
731
  requestParams = { messages: args[0] };
565
732
  }
566
733
 
567
734
  // determine if testMode is enabled
568
- if (typeof args[1] === 'boolean' && args[1] === true ||
735
+ if ( typeof args[1] === 'boolean' && args[1] === true ||
569
736
  typeof args[2] === 'boolean' && args[2] === true ||
570
- typeof args[3] === 'boolean' && args[3] === true) {
737
+ typeof args[3] === 'boolean' && args[3] === true ) {
571
738
  testMode = true;
572
739
  }
573
-
740
+
574
741
  // if any of the args is an object, assume it's the user parameters object
575
742
  const is_object = v => {
576
743
  return typeof v === 'object' &&
577
744
  !Array.isArray(v) &&
578
745
  v !== null;
579
746
  };
580
- for (let i = 0; i < args.length; i++) {
581
- if (is_object(args[i])) {
747
+ for ( let i = 0; i < args.length; i++ ) {
748
+ if ( is_object(args[i]) ) {
582
749
  userParams = args[i];
583
750
  break;
584
751
  }
585
752
  }
586
753
 
587
-
588
754
  // Copy relevant parameters from userParams to requestParams
589
- if (userParams.model) {
755
+ if ( userParams.model ) {
590
756
  requestParams.model = userParams.model;
591
757
  }
592
- if (userParams.temperature) {
758
+ if ( userParams.temperature ) {
593
759
  requestParams.temperature = userParams.temperature;
594
760
  }
595
- if (userParams.max_tokens) {
761
+ if ( userParams.max_tokens ) {
596
762
  requestParams.max_tokens = userParams.max_tokens;
597
763
  }
598
-
764
+
599
765
  // convert undefined to empty string so that .startsWith works
600
766
  requestParams.model = requestParams.model ?? '';
601
767
 
@@ -604,21 +770,21 @@ class AI{
604
770
  // for example: "claude-3-5-sonnet" should become "anthropic/claude-3-5-sonnet"
605
771
  // but for now, we want to keep the old behavior
606
772
  // so we remove the "anthropic/" prefix if it exists
607
- if (requestParams.model && requestParams.model.startsWith('anthropic/')) {
773
+ if ( requestParams.model && requestParams.model.startsWith('anthropic/') ) {
608
774
  requestParams.model = requestParams.model.replace('anthropic/', '');
609
775
  }
610
776
 
611
777
  // convert to the correct model name if necessary
612
- if( requestParams.model === 'claude-3-5-sonnet'){
778
+ if ( requestParams.model === 'claude-3-5-sonnet' ) {
613
779
  requestParams.model = 'claude-3-5-sonnet-latest';
614
780
  }
615
- if( requestParams.model === 'claude-3-7-sonnet' || requestParams.model === 'claude'){
781
+ if ( requestParams.model === 'claude-3-7-sonnet' || requestParams.model === 'claude' ) {
616
782
  requestParams.model = 'claude-3-7-sonnet-latest';
617
783
  }
618
- if( requestParams.model === 'claude-sonnet-4' || requestParams.model === 'claude-sonnet-4-latest'){
784
+ if ( requestParams.model === 'claude-sonnet-4' || requestParams.model === 'claude-sonnet-4-latest' ) {
619
785
  requestParams.model = 'claude-sonnet-4-20250514';
620
786
  }
621
- if( requestParams.model === 'claude-opus-4' || requestParams.model === 'claude-opus-4-latest') {
787
+ if ( requestParams.model === 'claude-opus-4' || requestParams.model === 'claude-opus-4-latest' ) {
622
788
  requestParams.model = 'claude-opus-4-20250514';
623
789
  }
624
790
  if ( requestParams.model === 'mistral' ) {
@@ -632,73 +798,22 @@ class AI{
632
798
  }
633
799
 
634
800
  // o1-mini to openrouter:openai/o1-mini
635
- if ( requestParams.model === 'o1-mini') {
801
+ if ( requestParams.model === 'o1-mini' ) {
636
802
  requestParams.model = 'openrouter:openai/o1-mini';
637
803
  }
638
804
 
639
805
  // if a model is prepended with "openai/", remove it
640
- if (requestParams.model && requestParams.model.startsWith('openai/')) {
806
+ if ( requestParams.model && requestParams.model.startsWith('openai/') ) {
641
807
  requestParams.model = requestParams.model.replace('openai/', '');
642
808
  driver = 'openai-completion';
643
809
  }
644
-
645
- // if model starts with:
646
- // agentica-org/
647
- // ai21/
648
- // aion-labs/
649
- // alfredpros/
650
- // alpindale/
651
- // amazon/
652
- // anthracite-org/
653
- // arcee-ai/
654
- // arliai/
655
- // baidu/
656
- // bytedance/
657
- // cognitivecomputations/
658
- // cohere/
659
- // deepseek/
660
- // eleutherai/
661
- // google/
662
- // gryphe/
663
- // inception/
664
- // infermatic/
665
- // liquid/
666
- // mancer/
667
- // meta-llama/
668
- // microsoft/
669
- // minimax/
670
- // mistralai/
671
- // moonshotai/
672
- // morph/
673
- // neversleep/
674
- // nousresearch/
675
- // nvidia/
676
- // openrouter/
677
- // perplexity/
678
- // pygmalionai/
679
- // qwen/
680
- // raifle/
681
- // rekaai/
682
- // sao10k/
683
- // sarvamai/
684
- // scb10x/
685
- // shisa-ai/
686
- // sophosympatheia/
687
- // switchpoint/
688
- // tencent/
689
- // thedrummer/
690
- // thudm/
691
- // tngtech/
692
- // undi95/
693
- // x-ai/
694
- // z-ai/
695
-
696
- // prepend it with openrouter:
697
- if (
810
+ // For the following providers, we need to prepend "openrouter:" to the model name so that the backend driver can handle it
811
+ if (
698
812
  requestParams.model.startsWith('agentica-org/') ||
699
813
  requestParams.model.startsWith('ai21/') ||
700
814
  requestParams.model.startsWith('aion-labs/') ||
701
815
  requestParams.model.startsWith('alfredpros/') ||
816
+ requestParams.model.startsWith('allenai/') ||
702
817
  requestParams.model.startsWith('alpindale/') ||
703
818
  requestParams.model.startsWith('amazon/') ||
704
819
  requestParams.model.startsWith('anthracite-org/') ||
@@ -708,9 +823,9 @@ class AI{
708
823
  requestParams.model.startsWith('bytedance/') ||
709
824
  requestParams.model.startsWith('cognitivecomputations/') ||
710
825
  requestParams.model.startsWith('cohere/') ||
711
- requestParams.model.startsWith('deepseek/') ||
826
+ requestParams.model.startsWith('deepseek/') ||
712
827
  requestParams.model.startsWith('eleutherai/') ||
713
- requestParams.model.startsWith('google/') ||
828
+ requestParams.model.startsWith('google/') ||
714
829
  requestParams.model.startsWith('gryphe/') ||
715
830
  requestParams.model.startsWith('inception/') ||
716
831
  requestParams.model.startsWith('infermatic/') ||
@@ -728,7 +843,7 @@ class AI{
728
843
  requestParams.model.startsWith('openrouter/') ||
729
844
  requestParams.model.startsWith('perplexity/') ||
730
845
  requestParams.model.startsWith('pygmalionai/') ||
731
- requestParams.model.startsWith('qwen/') ||
846
+ requestParams.model.startsWith('qwen/') ||
732
847
  requestParams.model.startsWith('raifle/') ||
733
848
  requestParams.model.startsWith('rekaai/') ||
734
849
  requestParams.model.startsWith('sao10k/') ||
@@ -742,54 +857,59 @@ class AI{
742
857
  requestParams.model.startsWith('thudm/') ||
743
858
  requestParams.model.startsWith('tngtech/') ||
744
859
  requestParams.model.startsWith('undi95/') ||
745
- requestParams.model.startsWith('x-ai/') ||
860
+ requestParams.model.startsWith('x-ai/') ||
746
861
  requestParams.model.startsWith('z-ai/')
747
862
  ) {
748
- requestParams.model = 'openrouter:' + requestParams.model;
863
+ requestParams.model = `openrouter:${ requestParams.model}`;
749
864
  }
750
865
 
751
866
  // map model to the appropriate driver
752
- if (!requestParams.model || requestParams.model.startsWith('gpt-')) {
867
+ if ( !requestParams.model || requestParams.model.startsWith('gpt-') ) {
753
868
  driver = 'openai-completion';
754
- }else if(
869
+ } else if (
755
870
  requestParams.model.startsWith('claude-')
756
- ){
871
+ ) {
757
872
  driver = 'claude';
758
- }else if(requestParams.model === 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo' || requestParams.model === `google/gemma-2-27b-it`){
873
+ } else if ( requestParams.model === 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo' || requestParams.model === 'google/gemma-2-27b-it' ) {
759
874
  driver = 'together-ai';
760
- }else if(requestParams.model.startsWith('mistral-') || requestParams.model.startsWith('codestral-') || requestParams.model.startsWith('pixtral-') || requestParams.model.startsWith('magistral-') || requestParams.model.startsWith('devstral-') || requestParams.model.startsWith('mistral-ocr-') || requestParams.model.startsWith('open-mistral-')){
875
+ } else if ( requestParams.model.startsWith('mistral-') || requestParams.model.startsWith('codestral-') || requestParams.model.startsWith('pixtral-') || requestParams.model.startsWith('magistral-') || requestParams.model.startsWith('devstral-') || requestParams.model.startsWith('mistral-ocr-') || requestParams.model.startsWith('open-mistral-') ) {
761
876
  driver = 'mistral';
762
- }else if([
763
- "distil-whisper-large-v3-en",
764
- "gemma2-9b-it",
765
- "gemma-7b-it",
766
- "llama-3.1-70b-versatile",
767
- "llama-3.1-8b-instant",
768
- "llama3-70b-8192",
769
- "llama3-8b-8192",
770
- "llama3-groq-70b-8192-tool-use-preview",
771
- "llama3-groq-8b-8192-tool-use-preview",
772
- "llama-guard-3-8b",
773
- "mixtral-8x7b-32768",
774
- "whisper-large-v3"
775
- ].includes(requestParams.model)) {
877
+ } else if ( [
878
+ 'distil-whisper-large-v3-en',
879
+ 'gemma2-9b-it',
880
+ 'gemma-7b-it',
881
+ 'llama-3.1-70b-versatile',
882
+ 'llama-3.1-8b-instant',
883
+ 'llama3-70b-8192',
884
+ 'llama3-8b-8192',
885
+ 'llama3-groq-70b-8192-tool-use-preview',
886
+ 'llama3-groq-8b-8192-tool-use-preview',
887
+ 'llama-guard-3-8b',
888
+ 'mixtral-8x7b-32768',
889
+ 'whisper-large-v3',
890
+ ].includes(requestParams.model) ) {
776
891
  driver = 'groq';
777
- }else if(requestParams.model === 'grok-beta') {
892
+ } else if ( requestParams.model === 'grok-beta' ) {
778
893
  driver = 'xai';
779
894
  }
780
- else if(requestParams.model.startsWith('grok-')){
895
+ else if ( requestParams.model.startsWith('grok-') ) {
781
896
  driver = 'openrouter';
782
897
  }
783
- else if(
898
+ else if (
784
899
  requestParams.model === 'deepseek-chat' ||
785
900
  requestParams.model === 'deepseek-reasoner'
786
- ){
901
+ ) {
787
902
  driver = 'deepseek';
788
903
  }
789
- else if(
904
+ else if (
790
905
  requestParams.model === 'gemini-1.5-flash' ||
791
- requestParams.model === 'gemini-2.0-flash'
792
- ){
906
+ requestParams.model === 'gemini-2.0-flash' ||
907
+ requestParams.model === 'gemini-2.5-flash' ||
908
+ requestParams.model === 'gemini-2.5-flash-lite' ||
909
+ requestParams.model === 'gemini-2.0-flash-lite' ||
910
+ requestParams.model === 'gemini-3-pro-preview' ||
911
+ requestParams.model === 'gemini-2.5-pro'
912
+ ) {
793
913
  driver = 'gemini';
794
914
  }
795
915
  else if ( requestParams.model.startsWith('openrouter:') ) {
@@ -800,10 +920,10 @@ class AI{
800
920
  }
801
921
 
802
922
  // stream flag from userParams
803
- if(userParams.stream !== undefined && typeof userParams.stream === 'boolean'){
923
+ if ( userParams.stream !== undefined && typeof userParams.stream === 'boolean' ) {
804
924
  requestParams.stream = userParams.stream;
805
925
  }
806
-
926
+
807
927
  if ( userParams.driver ) {
808
928
  driver = userParams.driver;
809
929
  }
@@ -815,7 +935,7 @@ class AI{
815
935
  requestParams[name] = userParams[name];
816
936
  }
817
937
  }
818
-
938
+
819
939
  if ( requestParams.model === '' ) {
820
940
  delete requestParams.model;
821
941
  }
@@ -830,16 +950,16 @@ class AI{
830
950
 
831
951
  result.valueOf = () => {
832
952
  return result.message?.content;
833
- }
953
+ };
834
954
 
835
955
  return result;
836
- }
956
+ },
837
957
  }).call(this, requestParams);
838
- }
958
+ };
839
959
 
840
960
  /**
841
961
  * Generate images from text prompts or perform image-to-image generation
842
- *
962
+ *
843
963
  * @param {string|object} prompt - Text prompt or options object
844
964
  * @param {object|boolean} [options] - Generation options or test mode flag
845
965
  * @param {string} [options.prompt] - Text description of the image to generate
@@ -848,11 +968,11 @@ class AI{
848
968
  * @param {string} [options.input_image] - Base64 encoded input image for image-to-image generation
849
969
  * @param {string} [options.input_image_mime_type] - MIME type of input image (e.g., "image/png")
850
970
  * @returns {Promise<Image>} Generated image object with src property
851
- *
971
+ *
852
972
  * @example
853
973
  * // Text-to-image
854
974
  * const img = await puter.ai.txt2img("A beautiful sunset");
855
- *
975
+ *
856
976
  * @example
857
977
  * // Image-to-image
858
978
  * const img = await puter.ai.txt2img({
@@ -866,32 +986,38 @@ class AI{
866
986
  let options = {};
867
987
  let testMode = false;
868
988
 
869
- if(!args){
870
- throw({message: 'Arguments are required', code: 'arguments_required'});
989
+ if ( ! args ) {
990
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
871
991
  }
872
992
 
873
993
  // if argument is string transform it to the object that the API expects
874
- if (typeof args[0] === 'string') {
994
+ if ( typeof args[0] === 'string' ) {
875
995
  options = { prompt: args[0] };
876
996
  }
877
997
 
878
998
  // if second argument is string, it's the `testMode`
879
- if (typeof args[1] === 'boolean' && args[1] === true) {
999
+ if ( typeof args[1] === 'boolean' && args[1] === true ) {
880
1000
  testMode = true;
881
1001
  }
882
1002
 
883
- if (typeof args[0] === 'string' && typeof args[1] === "object") {
1003
+ if ( typeof args[0] === 'string' && typeof args[1] === 'object' ) {
884
1004
  options = args[1];
885
1005
  options.prompt = args[0];
886
1006
  }
887
1007
 
888
- if (typeof args[0] === 'object') {
889
- options = args[0]
1008
+ if ( typeof args[0] === 'object' ) {
1009
+ options = args[0];
1010
+ }
1011
+
1012
+ let AIService = 'openai-image-generation';
1013
+ if ( options.model === 'nano-banana' )
1014
+ {
1015
+ options.model = 'gemini-2.5-flash-image-preview';
890
1016
  }
891
1017
 
892
- let AIService = "openai-image-generation"
893
- if (options.model === "nano-banana")
894
- options.model = "gemini-2.5-flash-image-preview";
1018
+ if (options.model === "nano-banana-pro") {
1019
+ options.model = "gemini-3-pro-image-preview";
1020
+ }
895
1021
 
896
1022
  const driverHint = typeof options.driver === 'string' ? options.driver : undefined;
897
1023
  const providerRaw = typeof options.provider === 'string'
@@ -905,16 +1031,16 @@ class AI{
905
1031
  (TOGETHER_IMAGE_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix)) ||
906
1032
  TOGETHER_IMAGE_MODEL_KEYWORDS.some(keyword => modelLower.includes(keyword)));
907
1033
 
908
- if (driverHint) {
1034
+ if ( driverHint ) {
909
1035
  AIService = driverHint;
910
- } else if (providerHint === 'gemini') {
911
- AIService = "gemini-image-generation";
912
- } else if (providerHint === 'together' || providerHint === 'together-ai') {
913
- AIService = "together-image-generation";
914
- } else if (options.model === "gemini-2.5-flash-image-preview") {
915
- AIService = "gemini-image-generation";
916
- } else if (looksLikeTogetherModel) {
917
- AIService = "together-image-generation";
1036
+ } else if ( providerHint === 'gemini' ) {
1037
+ AIService = 'gemini-image-generation';
1038
+ } else if ( providerHint === 'together' || providerHint === 'together-ai' ) {
1039
+ AIService = 'together-image-generation';
1040
+ } else if (options.model === 'gemini-2.5-flash-image-preview' || options.model === "gemini-3-pro-image-preview" ) {
1041
+ AIService = 'gemini-image-generation';
1042
+ } else if ( looksLikeTogetherModel ) {
1043
+ AIService = 'together-image-generation';
918
1044
  }
919
1045
  // Call the original chat.complete method
920
1046
  return await utils.make_driver_method(['prompt'], 'puter-image-generation', AIService, 'generate', {
@@ -936,49 +1062,49 @@ class AI{
936
1062
  } else {
937
1063
  throw { message: 'Unexpected image response format', code: 'invalid_image_response' };
938
1064
  }
939
- let img = new Image();
1065
+ let img = new (globalThis.Image || Object)();
940
1066
  img.src = url;
941
1067
  img.toString = () => img.src;
942
1068
  img.valueOf = () => img.src;
943
1069
  return img;
944
- }
1070
+ },
945
1071
  }).call(this, options);
946
- }
1072
+ };
947
1073
 
948
1074
  txt2vid = async (...args) => {
949
1075
  let options = {};
950
1076
  let testMode = false;
951
1077
 
952
- if(!args){
953
- throw({message: 'Arguments are required', code: 'arguments_required'});
1078
+ if ( ! args ) {
1079
+ throw ({ message: 'Arguments are required', code: 'arguments_required' });
954
1080
  }
955
1081
 
956
- if (typeof args[0] === 'string') {
1082
+ if ( typeof args[0] === 'string' ) {
957
1083
  options = { prompt: args[0] };
958
1084
  }
959
1085
 
960
- if (typeof args[1] === 'boolean' && args[1] === true) {
1086
+ if ( typeof args[1] === 'boolean' && args[1] === true ) {
961
1087
  testMode = true;
962
1088
  }
963
1089
 
964
- if (typeof args[0] === 'string' && typeof args[1] === "object") {
1090
+ if ( typeof args[0] === 'string' && typeof args[1] === 'object' ) {
965
1091
  options = args[1];
966
1092
  options.prompt = args[0];
967
1093
  }
968
1094
 
969
- if (typeof args[0] === 'object') {
1095
+ if ( typeof args[0] === 'object' ) {
970
1096
  options = args[0];
971
1097
  }
972
1098
 
973
- if (!options.prompt) {
974
- throw({message: 'Prompt parameter is required', code: 'prompt_required'});
1099
+ if ( ! options.prompt ) {
1100
+ throw ({ message: 'Prompt parameter is required', code: 'prompt_required' });
975
1101
  }
976
1102
 
977
- if (!options.model) {
1103
+ if ( ! options.model ) {
978
1104
  options.model = 'sora-2';
979
1105
  }
980
1106
 
981
- if (options.duration !== undefined && options.seconds === undefined) {
1107
+ if ( options.duration !== undefined && options.seconds === undefined ) {
982
1108
  options.seconds = options.duration;
983
1109
  }
984
1110
 
@@ -994,17 +1120,17 @@ class AI{
994
1120
  const looksLikeTogetherVideoModel = typeof options.model === 'string' &&
995
1121
  TOGETHER_VIDEO_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix));
996
1122
 
997
- if (driverHintLower === 'together' || driverHintLower === 'together-ai') {
1123
+ if ( driverHintLower === 'together' || driverHintLower === 'together-ai' ) {
998
1124
  videoService = 'together-video-generation';
999
- } else if (driverHintLower === 'together-video-generation') {
1125
+ } else if ( driverHintLower === 'together-video-generation' ) {
1000
1126
  videoService = 'together-video-generation';
1001
- } else if (driverHintLower === 'openai') {
1127
+ } else if ( driverHintLower === 'openai' ) {
1002
1128
  videoService = 'openai-video-generation';
1003
- } else if (driverHint) {
1129
+ } else if ( driverHint ) {
1004
1130
  videoService = driverHint;
1005
- } else if (providerHint === 'together' || providerHint === 'together-ai') {
1131
+ } else if ( providerHint === 'together' || providerHint === 'together-ai' ) {
1006
1132
  videoService = 'together-video-generation';
1007
- } else if (looksLikeTogetherVideoModel) {
1133
+ } else if ( looksLikeTogetherVideoModel ) {
1008
1134
  videoService = 'together-video-generation';
1009
1135
  }
1010
1136
 
@@ -1014,34 +1140,34 @@ class AI{
1014
1140
  transform: async result => {
1015
1141
  let sourceUrl = null;
1016
1142
  let mimeType = null;
1017
- if (result instanceof Blob) {
1143
+ if ( result instanceof Blob ) {
1018
1144
  sourceUrl = await utils.blob_to_url(result);
1019
1145
  mimeType = result.type || 'video/mp4';
1020
- } else if (typeof result === 'string') {
1146
+ } else if ( typeof result === 'string' ) {
1021
1147
  sourceUrl = result;
1022
- } else if (result && typeof result === 'object') {
1148
+ } else if ( result && typeof result === 'object' ) {
1023
1149
  sourceUrl = result.asset_url || result.url || result.href || null;
1024
1150
  mimeType = result.mime_type || result.content_type || null;
1025
1151
  }
1026
1152
 
1027
- if (!sourceUrl) {
1153
+ if ( ! sourceUrl ) {
1028
1154
  return result;
1029
1155
  }
1030
1156
 
1031
- const video = document.createElement('video');
1157
+ const video = (globalThis.document?.createElement('video') || {setAttribute: ()=>{}});
1032
1158
  video.src = sourceUrl;
1033
1159
  video.controls = true;
1034
1160
  video.preload = 'metadata';
1035
- if (mimeType) {
1161
+ if ( mimeType ) {
1036
1162
  video.setAttribute('data-mime-type', mimeType);
1037
1163
  }
1038
1164
  video.setAttribute('data-source', sourceUrl);
1039
1165
  video.toString = () => video.src;
1040
1166
  video.valueOf = () => video.src;
1041
1167
  return video;
1042
- }
1168
+ },
1043
1169
  }).call(this, options);
1044
- }
1170
+ };
1045
1171
  }
1046
1172
 
1047
1173
  export default AI;