|
@@ -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,
|
|
|
+};
|