KernelDeimos 9 месяцев назад
Родитель
Сommit
d009cd0aaf

+ 26 - 1
package-lock.json

@@ -263,6 +263,30 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@anthropic-ai/sdk": {
+      "version": "0.26.1",
+      "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.26.1.tgz",
+      "integrity": "sha512-HeMJP1bDFfQPQS3XTJAmfXkFBdZ88wvfkE05+vsoA9zGn5dHqEaHOPsqkazf/i0gXYg2XlLxxZrf6rUAarSqzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "^18.11.18",
+        "@types/node-fetch": "^2.6.4",
+        "abort-controller": "^3.0.0",
+        "agentkeepalive": "^4.2.1",
+        "form-data-encoder": "1.7.2",
+        "formdata-node": "^4.3.2",
+        "node-fetch": "^2.6.7"
+      }
+    },
+    "node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
+      "version": "18.19.45",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.45.tgz",
+      "integrity": "sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
     "node_modules/@aws-crypto/sha256-browser": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
@@ -16561,9 +16585,10 @@
     },
     "src/backend": {
       "name": "@heyputer/backend",
-      "version": "2.1.0",
+      "version": "2.4.1",
       "license": "AGPL-3.0-only",
       "dependencies": {
+        "@anthropic-ai/sdk": "^0.26.1",
         "@aws-sdk/client-polly": "^3.622.0",
         "@aws-sdk/client-textract": "^3.621.0",
         "@heyputer/kv.js": "^0.1.3",

+ 1 - 0
src/backend/package.json

@@ -7,6 +7,7 @@
     "test": "npx mocha"
   },
   "dependencies": {
+    "@anthropic-ai/sdk": "^0.26.1",
     "@aws-sdk/client-polly": "^3.622.0",
     "@aws-sdk/client-textract": "^3.621.0",
     "@heyputer/kv.js": "^0.1.3",

+ 109 - 0
src/backend/src/modules/puterai/ClaudeService.js

@@ -0,0 +1,109 @@
+const { default: Anthropic } = require("@anthropic-ai/sdk");
+const BaseService = require("../../services/BaseService");
+const { whatis } = require("../../util/langutil");
+const { PassThrough } = require("stream");
+const { TypedValue } = require("../../services/drivers/meta/Runtime");
+
+const PUTER_PROMPT = `
+    You are running on an open-source platform called Puter,
+    as the Claude implementation for a driver interface
+    called puter-chat-completion.
+    
+    The following JSON contains system messages from the
+    user of the driver interface (typically an app on Puter):
+`.replace('\n', ' ').trim();
+
+class ClaudeService extends BaseService {
+    static MODULES = {
+        Anthropic: require('@anthropic-ai/sdk'),
+    }
+    
+    async _init () {
+        this.anthropic = new Anthropic({
+            apiKey: this.config.apiKey
+        });
+    }
+    
+    static IMPLEMENTS = {
+        ['puter-chat-completion']: {
+            async complete ({ messages, stream }) {
+                const adapted_messages = [];
+                
+                const system_prompts = [];
+                let previous_was_user = false;
+                for ( const message of messages ) {
+                    if ( typeof message.content === 'string' ) {
+                        message.content = {
+                            type: 'text',
+                            text: message.content,
+                        };
+                    }
+                    if ( whatis(message.content) !== 'array' ) {
+                        message.content = [message.content];
+                    }
+                    if ( message.role === 'user' && previous_was_user ) {
+                        const last_msg = adapted_messages[adapted_messages.length-1];
+                        last_msg.content.push(
+                            ...(Array.isArray ? message.content : [message.content])
+                        );
+                        continue;
+                    }
+                    if ( message.role === 'system' ) {
+                        system_prompts.push(...message.content);
+                        continue;
+                    }
+                    adapted_messages.push(message);
+                    if ( message.role === 'user' ) {
+                        previous_was_user = true;
+                    }
+                }
+                
+                if ( stream ) {
+                    const stream = new PassThrough();
+                    const retval = new TypedValue({
+                        $: 'stream',
+                        content_type: 'application/x-ndjson',
+                        chunked: true,
+                    }, stream);
+                    (async () => {
+                        const completion = await this.anthropic.messages.stream({
+                            model: 'claude-3-5-sonnet-20240620',
+                            max_tokens: 1000,
+                            temperature: 0,
+                            system: PUTER_PROMPT + JSON.stringify(system_prompts),
+                            messages: adapted_messages,
+                        });
+                        for await ( const event of completion ) {
+                            if (
+                                event.type !== 'content_block_delta' ||
+                                event.delta.type !== 'text_delta'
+                            ) continue;
+                            const str = JSON.stringify({
+                                text: event.delta.text,
+                            });
+                            stream.write(str + '\n');
+                        }
+                    })();
+
+                    return retval;
+                }
+
+                const msg = await this.anthropic.messages.create({
+                    model: 'claude-3-5-sonnet-20240620',
+                    max_tokens: 1000,
+                    temperature: 0,
+                    system: PUTER_PROMPT + JSON.stringify(system_prompts),
+                    messages: adapted_messages,
+                });
+                return {
+                    message: msg,
+                    finish_reason: 'stop'
+                };
+            }
+        }
+    }
+}
+
+module.exports = {
+    ClaudeService,
+};

+ 5 - 0
src/backend/src/modules/puterai/PuterAIModule.js

@@ -28,6 +28,11 @@ class PuterAIModule extends AdvancedBase {
             const { OpenAIImageGenerationService } = require('./OpenAIImageGenerationService');
             services.registerService('openai-image-generation', OpenAIImageGenerationService);
         }
+        
+        if ( !! config?.services?.claude ) {
+            const { ClaudeService } = require('./ClaudeService');
+            services.registerService('claude', ClaudeService);
+        }
     }
 }