Skip to content

Commit 2c8da38

Browse files
committed
dev: add puter_path support for OpenAI
1 parent ba3ff03 commit 2c8da38

File tree

1 file changed

+77
-1
lines changed

1 file changed

+77
-1
lines changed

src/backend/src/modules/puterai/OpenAICompletionService.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@
1818
*/
1919

2020
// METADATA // {"ai-commented":{"service":"claude"}}
21+
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
22+
const { LLRead } = require('../../filesystem/ll_operations/ll_read');
2123
const BaseService = require('../../services/BaseService');
2224
const { Context } = require('../../util/context');
25+
const { stream_to_buffer } = require('../../util/streamutil');
2326
const OpenAIUtil = require('./lib/OpenAIUtil');
2427

28+
// We're capping at 5MB, which sucks, but Chat Completions doesn't suuport
29+
// file inputs.
30+
const MAX_FILE_SIZE = 5 * 1_000_000;
31+
2532
/**
2633
* OpenAICompletionService class provides an interface to OpenAI's chat completion API.
2734
* Extends BaseService to handle chat completions, message moderation, token counting,
@@ -34,6 +41,11 @@ class OpenAICompletionService extends BaseService {
3441
openai: require('openai'),
3542
tiktoken: require('tiktoken'),
3643
}
44+
45+
/**
46+
* @type {import('openai').OpenAI}
47+
*/
48+
openai;
3749

3850
/**
3951
* Initializes the OpenAI service by setting up the API client with credentials
@@ -325,7 +337,71 @@ class OpenAICompletionService extends BaseService {
325337
}
326338

327339
this.log.info('PRIVATE UID FOR USER ' + user_private_uid)
328-
340+
341+
// Perform file uploads
342+
{
343+
const actor = Context.get('actor');
344+
const { user } = actor.type;
345+
346+
const file_input_tasks = [];
347+
for ( const message of messages ) {
348+
// We can assume `message.content` is not undefined because
349+
// Messages.normalize_single_message ensures this.
350+
for ( const contentPart of message.content ) {
351+
if ( ! contentPart.puter_path ) continue;
352+
file_input_tasks.push({
353+
node: await (new FSNodeParam(contentPart.puter_path)).consolidate({
354+
req: { user },
355+
getParam: () => contentPart.puter_path,
356+
}),
357+
contentPart,
358+
});
359+
}
360+
}
361+
362+
const promises = [];
363+
for ( const task of file_input_tasks ) promises.push((async () => {
364+
if ( await task.node.get('size') > MAX_FILE_SIZE ) {
365+
delete task.contentPart.puter_path;
366+
task.contentPart.type = 'text';
367+
task.contentPart.text = `{error: input file exceeded maximum of ${MAX_FILE_SIZE} bytes; ` +
368+
`the user did not write this message}`; // "poor man's system prompt"
369+
return; // "continue"
370+
}
371+
372+
const ll_read = new LLRead();
373+
const stream = await ll_read.run({
374+
actor: Context.get('actor'),
375+
fsNode: task.node,
376+
});
377+
const require = this.require;
378+
const mime = require('mime-types');
379+
const mimeType = mime.contentType(await task.node.get('name'));
380+
381+
const buffer = await stream_to_buffer(stream);
382+
const base64 = buffer.toString('base64');
383+
384+
delete task.contentPart.puter_path;
385+
if ( mimeType.startsWith('image/') ) {
386+
task.contentPart.type = 'image_url',
387+
task.contentPart.image_url = {
388+
url: `data:${mimeType};base64,${base64}`,
389+
};
390+
} else if ( mimeType.startsWith('audio/') ) {
391+
task.contentPart.type = 'input_audio',
392+
task.contentPart.input_audio = {
393+
data: `data:${mimeType};base64,${base64}`,
394+
format: mimeType.split('/')[1],
395+
}
396+
} else {
397+
task.contentPart.type = 'text';
398+
task.contentPart.text = `{error: input file has unsupported MIME type; ` +
399+
`the user did not write this message}`; // "poor man's system prompt"
400+
}
401+
})());
402+
await Promise.all(promises);
403+
}
404+
329405
// Here's something fun; the documentation shows `type: 'image_url'` in
330406
// objects that contain an image url, but everything still works if
331407
// that's missing. We normalise it here so the token count code works.

0 commit comments

Comments
 (0)