@heyputer/puter.js 1.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 (74) hide show
  1. package/APACHE_LICENSE.txt +201 -0
  2. package/README.md +88 -0
  3. package/doc/devlog.md +49 -0
  4. package/package.json +31 -0
  5. package/src/bg.png +0 -0
  6. package/src/bg.webp +0 -0
  7. package/src/index.js +745 -0
  8. package/src/lib/APICallLogger.js +110 -0
  9. package/src/lib/EventListener.js +51 -0
  10. package/src/lib/RequestError.js +6 -0
  11. package/src/lib/filesystem/APIFS.js +73 -0
  12. package/src/lib/filesystem/CacheFS.js +243 -0
  13. package/src/lib/filesystem/PostMessageFS.js +40 -0
  14. package/src/lib/filesystem/definitions.js +39 -0
  15. package/src/lib/path.js +509 -0
  16. package/src/lib/polyfills/localStorage.js +92 -0
  17. package/src/lib/polyfills/xhrshim.js +233 -0
  18. package/src/lib/socket.io/socket.io.esm.min.js +7 -0
  19. package/src/lib/socket.io/socket.io.esm.min.js.map +1 -0
  20. package/src/lib/socket.io/socket.io.js +4385 -0
  21. package/src/lib/socket.io/socket.io.js.map +1 -0
  22. package/src/lib/socket.io/socket.io.min.js +7 -0
  23. package/src/lib/socket.io/socket.io.min.js.map +1 -0
  24. package/src/lib/socket.io/socket.io.msgpack.min.js +7 -0
  25. package/src/lib/socket.io/socket.io.msgpack.min.js.map +1 -0
  26. package/src/lib/utils.js +620 -0
  27. package/src/lib/xdrpc.js +104 -0
  28. package/src/modules/AI.js +680 -0
  29. package/src/modules/Apps.js +215 -0
  30. package/src/modules/Auth.js +171 -0
  31. package/src/modules/Debug.js +39 -0
  32. package/src/modules/Drivers.js +278 -0
  33. package/src/modules/FSItem.js +139 -0
  34. package/src/modules/FileSystem/index.js +187 -0
  35. package/src/modules/FileSystem/operations/copy.js +64 -0
  36. package/src/modules/FileSystem/operations/deleteFSEntry.js +59 -0
  37. package/src/modules/FileSystem/operations/getReadUrl.js +42 -0
  38. package/src/modules/FileSystem/operations/mkdir.js +62 -0
  39. package/src/modules/FileSystem/operations/move.js +75 -0
  40. package/src/modules/FileSystem/operations/read.js +46 -0
  41. package/src/modules/FileSystem/operations/readdir.js +102 -0
  42. package/src/modules/FileSystem/operations/rename.js +58 -0
  43. package/src/modules/FileSystem/operations/sign.js +103 -0
  44. package/src/modules/FileSystem/operations/space.js +40 -0
  45. package/src/modules/FileSystem/operations/stat.js +95 -0
  46. package/src/modules/FileSystem/operations/symlink.js +55 -0
  47. package/src/modules/FileSystem/operations/upload.js +440 -0
  48. package/src/modules/FileSystem/operations/write.js +65 -0
  49. package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +21 -0
  50. package/src/modules/Hosting.js +138 -0
  51. package/src/modules/KV.js +301 -0
  52. package/src/modules/OS.js +95 -0
  53. package/src/modules/Perms.js +109 -0
  54. package/src/modules/PuterDialog.js +481 -0
  55. package/src/modules/Threads.js +75 -0
  56. package/src/modules/UI.js +1555 -0
  57. package/src/modules/Util.js +38 -0
  58. package/src/modules/Workers.js +120 -0
  59. package/src/modules/networking/PSocket.js +87 -0
  60. package/src/modules/networking/PTLS.js +100 -0
  61. package/src/modules/networking/PWispHandler.js +89 -0
  62. package/src/modules/networking/parsers.js +157 -0
  63. package/src/modules/networking/requests.js +282 -0
  64. package/src/services/APIAccess.js +46 -0
  65. package/src/services/FSRelay.js +20 -0
  66. package/src/services/Filesystem.js +122 -0
  67. package/src/services/NoPuterYet.js +20 -0
  68. package/src/services/XDIncoming.js +44 -0
  69. package/test/ai.test.js +214 -0
  70. package/test/fs.test.js +798 -0
  71. package/test/index.html +1183 -0
  72. package/test/kv.test.js +548 -0
  73. package/test/txt2speech.test.js +178 -0
  74. package/webpack.config.js +25 -0
@@ -0,0 +1,680 @@
1
+ import * as utils from '../lib/utils.js';
2
+
3
+ class AI{
4
+ /**
5
+ * Creates a new instance with the given authentication token, API origin, and app ID,
6
+ *
7
+ * @class
8
+ * @param {string} authToken - Token used to authenticate the user.
9
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
10
+ * @param {string} appID - ID of the app to use.
11
+ */
12
+ constructor (context) {
13
+ this.authToken = context.authToken;
14
+ this.APIOrigin = context.APIOrigin;
15
+ this.appID = context.appID;
16
+ }
17
+
18
+ /**
19
+ * Sets a new authentication token and resets the socket connection with the updated token, if applicable.
20
+ *
21
+ * @param {string} authToken - The new authentication token.
22
+ * @memberof [AI]
23
+ * @returns {void}
24
+ */
25
+ setAuthToken (authToken) {
26
+ this.authToken = authToken;
27
+ }
28
+
29
+ /**
30
+ * Sets the API origin.
31
+ *
32
+ * @param {string} APIOrigin - The new API origin.
33
+ * @memberof [AI]
34
+ * @returns {void}
35
+ */
36
+ setAPIOrigin (APIOrigin) {
37
+ this.APIOrigin = APIOrigin;
38
+ }
39
+
40
+ /**
41
+ * Returns a list of available AI models.
42
+ * @param {string} provider - The provider to filter the models returned.
43
+ * @returns {Object} Object containing lists of available models by provider
44
+ */
45
+ async listModels(provider) {
46
+ const modelsByProvider = {};
47
+
48
+ const models = await puter.drivers.call('puter-chat-completion','ai-chat','models');
49
+
50
+ if (!models || !models.result || !Array.isArray(models.result)) {
51
+ return modelsByProvider;
52
+ }
53
+ models.result.forEach(item => {
54
+ if (!item.provider || !item.id) return;
55
+ if (provider && item.provider !== provider) return;
56
+ if (!modelsByProvider[item.provider]) modelsByProvider[item.provider] = [];
57
+ modelsByProvider[item.provider].push(item.id);
58
+ });
59
+
60
+ return modelsByProvider;
61
+ }
62
+
63
+ /**
64
+ * Returns a list of all available AI providers
65
+ * @returns {Array} Array containing providers
66
+ */
67
+ async listModelProviders() {
68
+ let providers = [];
69
+ const models = await puter.drivers.call('puter-chat-completion','ai-chat','models');
70
+
71
+ if (!models || !models.result || !Array.isArray(models.result)) return providers; // if models is invalid then return empty array
72
+ providers = new Set(); // Use a Set to store unique providers
73
+ models.result.forEach(item => {
74
+ if (item.provider) providers.add(item.provider);
75
+ });
76
+ providers = Array.from(providers); // Convert Set to an array
77
+ return providers;
78
+ }
79
+
80
+ img2txt = async (...args) => {
81
+ let MAX_INPUT_SIZE = 10 * 1024 * 1024;
82
+ let options = {};
83
+ let testMode = false;
84
+
85
+ // Check that the argument is not undefined or null
86
+ if(!args){
87
+ throw({message: 'Arguments are required', code: 'arguments_required'});
88
+ }
89
+
90
+ // if argument is string transform it to the object that the API expects
91
+ if (typeof args[0] === 'string' || args[0] instanceof Blob) {
92
+ options.source = args[0];
93
+ }
94
+
95
+ // if input is a blob, transform it to a data URI
96
+ if (args[0].source instanceof Blob) {
97
+ options.source = await utils.blobToDataUri(args[0].source);
98
+ }
99
+
100
+ // check input size
101
+ if (options.source.length > this.MAX_INPUT_SIZE) {
102
+ throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
103
+ }
104
+
105
+ // determine if test mode is enabled
106
+ if (typeof args[1] === 'boolean' && args[1] === true ||
107
+ typeof args[2] === 'boolean' && args[2] === true ||
108
+ typeof args[3] === 'boolean' && args[3] === true) {
109
+ testMode = true;
110
+ }
111
+
112
+ return await utils.make_driver_method(['source'], 'puter-ocr', 'aws-textract', 'recognize', {
113
+ test_mode: testMode ?? false,
114
+ transform: async (result) => {
115
+ let str = '';
116
+ for (let i = 0; i < result?.blocks?.length; i++) {
117
+ if("text/textract:LINE" === result.blocks[i].type)
118
+ str += result.blocks[i].text + "\n";
119
+ }
120
+ return str;
121
+ }
122
+ }).call(this, options);
123
+ }
124
+
125
+ txt2speech = async (...args) => {
126
+ let MAX_INPUT_SIZE = 3000;
127
+ let options = {};
128
+ let testMode = false;
129
+
130
+ if(!args){
131
+ throw({message: 'Arguments are required', code: 'arguments_required'});
132
+ }
133
+
134
+ // Accept arguments in the following formats:
135
+ // 1. Shorthand API
136
+ // puter.ai.txt2speech("Hello world")
137
+ // 2. Verbose API
138
+ // puter.ai.txt2speech("Hello world", {
139
+ // voice: "Joanna",
140
+ // engine: "neural",
141
+ // language: "en-US"
142
+ // })
143
+ // 3. Positional arguments (Legacy)
144
+ // puter.ai.txt2speech(<text>, <language>, <voice>, <engine>)
145
+ // e.g:
146
+ // puter.ai.txt2speech("Hello world", "en-US")
147
+ // puter.ai.txt2speech("Hello world", "en-US", "Joanna")
148
+ // puter.ai.txt2speech("Hello world", "en-US", "Joanna", "neural")
149
+ //
150
+ // Undefined parameters will be set to default values:
151
+ // - voice: "Joanna"
152
+ // - engine: "standard"
153
+ // - language: "en-US"
154
+
155
+
156
+ if (typeof args[0] === 'string') {
157
+ options = { text: args[0] };
158
+ }
159
+
160
+ if (args[1] && typeof args[1] === 'object' && !Array.isArray(args[1])) {
161
+ // for verbose object API
162
+ Object.assign(options, args[1]);
163
+ } else if (args[1] && typeof args[1] === 'string') {
164
+ // for legacy positional-arguments API
165
+ //
166
+ // puter.ai.txt2speech(<text>, <language>, <voice>, <engine>)
167
+ options.language = args[1];
168
+
169
+ if (args[2] && typeof args[2] === 'string') {
170
+ options.voice = args[2];
171
+ }
172
+
173
+ if (args[3] && typeof args[3] === 'string') {
174
+ options.engine = args[3];
175
+ }
176
+ } else if (args[1] && typeof args[1] !== 'boolean') {
177
+ // If second argument is not an object, string, or boolean, throw an error
178
+ 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' };
179
+ }
180
+
181
+ // Validate required text parameter
182
+ if (!options.text) {
183
+ throw { message: 'Text parameter is required', code: 'text_required' };
184
+ }
185
+
186
+ // Validate engine if provided
187
+ if (options.engine) {
188
+ const validEngines = ['standard', 'neural', 'long-form', 'generative'];
189
+ if (!validEngines.includes(options.engine)) {
190
+ throw { message: 'Invalid engine. Must be one of: ' + validEngines.join(', '), code: 'invalid_engine' };
191
+ }
192
+ }
193
+
194
+ // Set default values if not provided
195
+ if (!options.voice) {
196
+ options.voice = 'Joanna';
197
+ }
198
+ if (!options.engine) {
199
+ options.engine = 'standard';
200
+ }
201
+ if (!options.language) {
202
+ options.language = 'en-US';
203
+ }
204
+
205
+ // check input size
206
+ if (options.text.length > MAX_INPUT_SIZE) {
207
+ throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
208
+ }
209
+
210
+ // determine if test mode is enabled (check all arguments for boolean true)
211
+ for (let i = 0; i < args.length; i++) {
212
+ if (typeof args[i] === 'boolean' && args[i] === true) {
213
+ testMode = true;
214
+ break;
215
+ }
216
+ }
217
+
218
+ return await utils.make_driver_method(['source'], 'puter-tts', 'aws-polly', 'synthesize', {
219
+ responseType: 'blob',
220
+ test_mode: testMode ?? false,
221
+ transform: async (result) => {
222
+ const url = await utils.blob_to_url(result);
223
+ const audio = new Audio(url);
224
+ audio.toString = () => url;
225
+ audio.valueOf = () => url;
226
+ return audio;
227
+ }
228
+ }).call(this, options);
229
+ }
230
+
231
+ // Add new methods for TTS engine management
232
+ txt2speech = Object.assign(this.txt2speech, {
233
+ /**
234
+ * List available TTS engines with pricing information
235
+ * @returns {Promise<Array>} Array of available engines
236
+ */
237
+ listEngines: async () => {
238
+ return await utils.make_driver_method(['source'], 'puter-tts', 'aws-polly', 'list_engines', {
239
+ responseType: 'text',
240
+ }).call(this, {});
241
+ },
242
+
243
+ /**
244
+ * List all available voices, optionally filtered by engine
245
+ * @param {string} [engine] - Optional engine filter
246
+ * @returns {Promise<Array>} Array of available voices
247
+ */
248
+ listVoices: async (engine) => {
249
+ const params = {};
250
+ if (engine) {
251
+ params.engine = engine;
252
+ }
253
+
254
+ return utils.make_driver_method(['source'], 'puter-tts', 'aws-polly', 'list_voices', {
255
+ responseType: 'text',
256
+ }).call(this, params);
257
+ }
258
+ });
259
+
260
+
261
+ // accepts either a string or an array of message objects
262
+ // if string, it's treated as the prompt which is a shorthand for { messages: [{ content: prompt }] }
263
+ // if object, it's treated as the full argument object that the API expects
264
+ chat = async (...args) => {
265
+ // requestParams: parameters that will be sent to the backend driver
266
+ let requestParams = {};
267
+ // userParams: parameters provided by the user in the function call
268
+ let userParams = {};
269
+ let testMode = false;
270
+
271
+ // default driver is openai-completion
272
+ let driver = 'openai-completion';
273
+
274
+ // Check that the argument is not undefined or null
275
+ if(!args){
276
+ throw({message: 'Arguments are required', code: 'arguments_required'});
277
+ }
278
+
279
+ // ai.chat(prompt)
280
+ if(typeof args[0] === 'string'){
281
+ requestParams = { messages: [{ content: args[0] }] };
282
+ }
283
+
284
+ // ai.chat(prompt, testMode)
285
+ if (typeof args[0] === 'string' && (!args[1] || typeof args[1] === 'boolean')) {
286
+ requestParams = { messages: [{ content: args[0] }] };
287
+ }
288
+
289
+ // ai.chat(prompt, imageURL/File)
290
+ // ai.chat(prompt, imageURL/File, testMode)
291
+ else if (typeof args[0] === 'string' && (typeof args[1] === 'string' || args[1] instanceof File)) {
292
+ // if imageURL is a File, transform it to a data URI
293
+ if(args[1] instanceof File){
294
+ args[1] = await utils.blobToDataUri(args[1]);
295
+ }
296
+
297
+ // parse args[1] as an image_url object
298
+ requestParams = {
299
+ vision: true,
300
+ messages: [
301
+ {
302
+ content: [
303
+ args[0],
304
+ {
305
+ image_url: {
306
+ url: args[1]
307
+ }
308
+ }
309
+ ],
310
+ }
311
+ ]
312
+ };
313
+ }
314
+ // chat(prompt, [imageURLs])
315
+ else if (typeof args[0] === 'string' && Array.isArray(args[1])) {
316
+ // parse args[1] as an array of image_url objects
317
+ for (let i = 0; i < args[1].length; i++) {
318
+ args[1][i] = { image_url: { url: args[1][i] } };
319
+ }
320
+ requestParams = {
321
+ vision: true,
322
+ messages: [
323
+ {
324
+ content: [
325
+ args[0],
326
+ ...args[1]
327
+ ],
328
+ }
329
+ ]
330
+ };
331
+ }
332
+ // chat([messages])
333
+ else if (Array.isArray(args[0])) {
334
+ requestParams = { messages: args[0] };
335
+ }
336
+
337
+ // determine if testMode is enabled
338
+ if (typeof args[1] === 'boolean' && args[1] === true ||
339
+ typeof args[2] === 'boolean' && args[2] === true ||
340
+ typeof args[3] === 'boolean' && args[3] === true) {
341
+ testMode = true;
342
+ }
343
+
344
+ // if any of the args is an object, assume it's the user parameters object
345
+ const is_object = v => {
346
+ return typeof v === 'object' &&
347
+ !Array.isArray(v) &&
348
+ v !== null;
349
+ };
350
+ for (let i = 0; i < args.length; i++) {
351
+ if (is_object(args[i])) {
352
+ userParams = args[i];
353
+ break;
354
+ }
355
+ }
356
+
357
+
358
+ // Copy relevant parameters from userParams to requestParams
359
+ if (userParams.model) {
360
+ requestParams.model = userParams.model;
361
+ }
362
+ if (userParams.temperature) {
363
+ requestParams.temperature = userParams.temperature;
364
+ }
365
+ if (userParams.max_tokens) {
366
+ requestParams.max_tokens = userParams.max_tokens;
367
+ }
368
+
369
+ // convert undefined to empty string so that .startsWith works
370
+ requestParams.model = requestParams.model ?? '';
371
+
372
+ // If model starts with "anthropic/", remove it
373
+ // later on we should standardize the model names to [vendor]/[model]
374
+ // for example: "claude-3-5-sonnet" should become "anthropic/claude-3-5-sonnet"
375
+ // but for now, we want to keep the old behavior
376
+ // so we remove the "anthropic/" prefix if it exists
377
+ if (requestParams.model && requestParams.model.startsWith('anthropic/')) {
378
+ requestParams.model = requestParams.model.replace('anthropic/', '');
379
+ }
380
+
381
+ // convert to the correct model name if necessary
382
+ if( requestParams.model === 'claude-3-5-sonnet'){
383
+ requestParams.model = 'claude-3-5-sonnet-latest';
384
+ }
385
+ if( requestParams.model === 'claude-3-7-sonnet' || requestParams.model === 'claude'){
386
+ requestParams.model = 'claude-3-7-sonnet-latest';
387
+ }
388
+ if( requestParams.model === 'claude-sonnet-4' || requestParams.model === 'claude-sonnet-4-latest'){
389
+ requestParams.model = 'claude-sonnet-4-20250514';
390
+ }
391
+ if( requestParams.model === 'claude-opus-4' || requestParams.model === 'claude-opus-4-latest') {
392
+ requestParams.model = 'claude-opus-4-20250514';
393
+ }
394
+ if ( requestParams.model === 'mistral' ) {
395
+ requestParams.model = 'mistral-large-latest';
396
+ }
397
+ if ( requestParams.model === 'groq' ) {
398
+ requestParams.model = 'llama3-8b-8192';
399
+ }
400
+ if ( requestParams.model === 'deepseek' ) {
401
+ requestParams.model = 'deepseek-chat';
402
+ }
403
+
404
+ // o1-mini to openrouter:openai/o1-mini
405
+ if ( requestParams.model === 'o1-mini') {
406
+ requestParams.model = 'openrouter:openai/o1-mini';
407
+ }
408
+
409
+ // if a model is prepended with "openai/", remove it
410
+ if (requestParams.model && requestParams.model.startsWith('openai/')) {
411
+ requestParams.model = requestParams.model.replace('openai/', '');
412
+ driver = 'openai-completion';
413
+ }
414
+
415
+ // if model starts with:
416
+ // agentica-org/
417
+ // ai21/
418
+ // aion-labs/
419
+ // alfredpros/
420
+ // alpindale/
421
+ // amazon/
422
+ // anthracite-org/
423
+ // arcee-ai/
424
+ // arliai/
425
+ // baidu/
426
+ // bytedance/
427
+ // cognitivecomputations/
428
+ // cohere/
429
+ // deepseek/
430
+ // eleutherai/
431
+ // google/
432
+ // gryphe/
433
+ // inception/
434
+ // infermatic/
435
+ // liquid/
436
+ // mancer/
437
+ // meta-llama/
438
+ // microsoft/
439
+ // minimax/
440
+ // mistralai/
441
+ // moonshotai/
442
+ // morph/
443
+ // neversleep/
444
+ // nousresearch/
445
+ // nvidia/
446
+ // openrouter/
447
+ // perplexity/
448
+ // pygmalionai/
449
+ // qwen/
450
+ // raifle/
451
+ // rekaai/
452
+ // sao10k/
453
+ // sarvamai/
454
+ // scb10x/
455
+ // shisa-ai/
456
+ // sophosympatheia/
457
+ // switchpoint/
458
+ // tencent/
459
+ // thedrummer/
460
+ // thudm/
461
+ // tngtech/
462
+ // undi95/
463
+ // x-ai/
464
+ // z-ai/
465
+
466
+ // prepend it with openrouter:
467
+ if (
468
+ requestParams.model.startsWith('agentica-org/') ||
469
+ requestParams.model.startsWith('ai21/') ||
470
+ requestParams.model.startsWith('aion-labs/') ||
471
+ requestParams.model.startsWith('alfredpros/') ||
472
+ requestParams.model.startsWith('alpindale/') ||
473
+ requestParams.model.startsWith('amazon/') ||
474
+ requestParams.model.startsWith('anthracite-org/') ||
475
+ requestParams.model.startsWith('arcee-ai/') ||
476
+ requestParams.model.startsWith('arliai/') ||
477
+ requestParams.model.startsWith('baidu/') ||
478
+ requestParams.model.startsWith('bytedance/') ||
479
+ requestParams.model.startsWith('cognitivecomputations/') ||
480
+ requestParams.model.startsWith('cohere/') ||
481
+ requestParams.model.startsWith('deepseek/') ||
482
+ requestParams.model.startsWith('eleutherai/') ||
483
+ requestParams.model.startsWith('google/') ||
484
+ requestParams.model.startsWith('gryphe/') ||
485
+ requestParams.model.startsWith('inception/') ||
486
+ requestParams.model.startsWith('infermatic/') ||
487
+ requestParams.model.startsWith('liquid/') ||
488
+ requestParams.model.startsWith('mancer/') ||
489
+ requestParams.model.startsWith('meta-llama/') ||
490
+ requestParams.model.startsWith('microsoft/') ||
491
+ requestParams.model.startsWith('minimax/') ||
492
+ requestParams.model.startsWith('mistralai/') ||
493
+ requestParams.model.startsWith('moonshotai/') ||
494
+ requestParams.model.startsWith('morph/') ||
495
+ requestParams.model.startsWith('neversleep/') ||
496
+ requestParams.model.startsWith('nousresearch/') ||
497
+ requestParams.model.startsWith('nvidia/') ||
498
+ requestParams.model.startsWith('openrouter/') ||
499
+ requestParams.model.startsWith('perplexity/') ||
500
+ requestParams.model.startsWith('pygmalionai/') ||
501
+ requestParams.model.startsWith('qwen/') ||
502
+ requestParams.model.startsWith('raifle/') ||
503
+ requestParams.model.startsWith('rekaai/') ||
504
+ requestParams.model.startsWith('sao10k/') ||
505
+ requestParams.model.startsWith('sarvamai/') ||
506
+ requestParams.model.startsWith('scb10x/') ||
507
+ requestParams.model.startsWith('shisa-ai/') ||
508
+ requestParams.model.startsWith('sophosympatheia/') ||
509
+ requestParams.model.startsWith('switchpoint/') ||
510
+ requestParams.model.startsWith('tencent/') ||
511
+ requestParams.model.startsWith('thedrummer/') ||
512
+ requestParams.model.startsWith('thudm/') ||
513
+ requestParams.model.startsWith('tngtech/') ||
514
+ requestParams.model.startsWith('undi95/') ||
515
+ requestParams.model.startsWith('x-ai/') ||
516
+ requestParams.model.startsWith('z-ai/')
517
+ ) {
518
+ requestParams.model = 'openrouter:' + requestParams.model;
519
+ }
520
+
521
+ // map model to the appropriate driver
522
+ if (!requestParams.model || requestParams.model.startsWith('gpt-')) {
523
+ driver = 'openai-completion';
524
+ }else if(
525
+ requestParams.model.startsWith('claude-')
526
+ ){
527
+ driver = 'claude';
528
+ }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`){
529
+ driver = 'together-ai';
530
+ }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-')){
531
+ driver = 'mistral';
532
+ }else if([
533
+ "distil-whisper-large-v3-en",
534
+ "gemma2-9b-it",
535
+ "gemma-7b-it",
536
+ "llama-3.1-70b-versatile",
537
+ "llama-3.1-8b-instant",
538
+ "llama3-70b-8192",
539
+ "llama3-8b-8192",
540
+ "llama3-groq-70b-8192-tool-use-preview",
541
+ "llama3-groq-8b-8192-tool-use-preview",
542
+ "llama-guard-3-8b",
543
+ "mixtral-8x7b-32768",
544
+ "whisper-large-v3"
545
+ ].includes(requestParams.model)) {
546
+ driver = 'groq';
547
+ }else if(requestParams.model === 'grok-beta') {
548
+ driver = 'xai';
549
+ }
550
+ else if(requestParams.model.startsWith('grok-')){
551
+ driver = 'openrouter';
552
+ }
553
+ else if(
554
+ requestParams.model === 'deepseek-chat' ||
555
+ requestParams.model === 'deepseek-reasoner'
556
+ ){
557
+ driver = 'deepseek';
558
+ }
559
+ else if(
560
+ requestParams.model === 'gemini-1.5-flash' ||
561
+ requestParams.model === 'gemini-2.0-flash'
562
+ ){
563
+ driver = 'gemini';
564
+ }
565
+ else if ( requestParams.model.startsWith('openrouter:') ) {
566
+ driver = 'openrouter';
567
+ }
568
+
569
+ // stream flag from userParams
570
+ if(userParams.stream !== undefined && typeof userParams.stream === 'boolean'){
571
+ requestParams.stream = userParams.stream;
572
+ }
573
+
574
+ if ( userParams.driver ) {
575
+ driver = userParams.driver;
576
+ }
577
+
578
+ // Additional parameters to pass from userParams to requestParams
579
+ const PARAMS_TO_PASS = ['tools', 'response'];
580
+ for ( const name of PARAMS_TO_PASS ) {
581
+ if ( userParams[name] ) {
582
+ requestParams[name] = userParams[name];
583
+ }
584
+ }
585
+
586
+ if ( requestParams.model === '' ) {
587
+ delete requestParams.model;
588
+ }
589
+
590
+ // Call the original chat.complete method
591
+ return await utils.make_driver_method(['messages'], 'puter-chat-completion', driver, 'complete', {
592
+ test_mode: testMode ?? false,
593
+ transform: async (result) => {
594
+ result.toString = () => {
595
+ return result.message?.content;
596
+ };
597
+
598
+ result.valueOf = () => {
599
+ return result.message?.content;
600
+ }
601
+
602
+ return result;
603
+ }
604
+ }).call(this, requestParams);
605
+ }
606
+
607
+ /**
608
+ * Generate images from text prompts or perform image-to-image generation
609
+ *
610
+ * @param {string|object} prompt - Text prompt or options object
611
+ * @param {object|boolean} [options] - Generation options or test mode flag
612
+ * @param {string} [options.prompt] - Text description of the image to generate
613
+ * @param {string} [options.model] - Model to use (e.g., "gemini-2.5-flash-image-preview")
614
+ * @param {object} [options.ratio] - Image dimensions (e.g., {w: 1024, h: 1024})
615
+ * @param {string} [options.input_image] - Base64 encoded input image for image-to-image generation
616
+ * @param {string} [options.input_image_mime_type] - MIME type of input image (e.g., "image/png")
617
+ * @returns {Promise<Image>} Generated image object with src property
618
+ *
619
+ * @example
620
+ * // Text-to-image
621
+ * const img = await puter.ai.txt2img("A beautiful sunset");
622
+ *
623
+ * @example
624
+ * // Image-to-image
625
+ * const img = await puter.ai.txt2img({
626
+ * prompt: "Transform this into a watercolor painting",
627
+ * input_image: base64ImageData,
628
+ * input_image_mime_type: "image/png",
629
+ * model: "gemini-2.5-flash-image-preview"
630
+ * });
631
+ */
632
+ txt2img = async (...args) => {
633
+ let options = {};
634
+ let testMode = false;
635
+
636
+ if(!args){
637
+ throw({message: 'Arguments are required', code: 'arguments_required'});
638
+ }
639
+
640
+ // if argument is string transform it to the object that the API expects
641
+ if (typeof args[0] === 'string') {
642
+ options = { prompt: args[0] };
643
+ }
644
+
645
+ // if second argument is string, it's the `testMode`
646
+ if (typeof args[1] === 'boolean' && args[1] === true) {
647
+ testMode = true;
648
+ }
649
+
650
+ if (typeof args[0] === 'string' && typeof args[1] === "object") {
651
+ options = args[1];
652
+ options.prompt = args[0];
653
+ }
654
+
655
+ if (typeof args[0] === 'object') {
656
+ options = args[0]
657
+ }
658
+
659
+ let AIService = "openai-image-generation"
660
+ if (options.model === "nano-banana")
661
+ options.model = "gemini-2.5-flash-image-preview";
662
+
663
+ if (options.model === "gemini-2.5-flash-image-preview")
664
+ AIService = "gemini-image-generation";
665
+ // Call the original chat.complete method
666
+ return await utils.make_driver_method(['prompt'], 'puter-image-generation', AIService, 'generate', {
667
+ responseType: 'blob',
668
+ test_mode: testMode ?? false,
669
+ transform: async blob => {
670
+ let img = new Image();
671
+ img.src = await utils.blob_to_url(blob);
672
+ img.toString = () => img.src;
673
+ img.valueOf = () => img.src;
674
+ return img;
675
+ }
676
+ }).call(this, options);
677
+ }
678
+ }
679
+
680
+ export default AI;