browser-use 0.0.1 → 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +761 -0
- package/dist/agent/cloud-events.d.ts +264 -0
- package/dist/agent/cloud-events.js +318 -0
- package/dist/agent/gif.d.ts +15 -0
- package/dist/agent/gif.js +215 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/message-manager/service.d.ts +30 -0
- package/dist/agent/message-manager/service.js +208 -0
- package/dist/agent/message-manager/utils.d.ts +2 -0
- package/dist/agent/message-manager/utils.js +41 -0
- package/dist/agent/message-manager/views.d.ts +26 -0
- package/dist/agent/message-manager/views.js +73 -0
- package/dist/agent/prompts.d.ts +52 -0
- package/dist/agent/prompts.js +259 -0
- package/dist/agent/service.d.ts +290 -0
- package/dist/agent/service.js +2200 -0
- package/dist/agent/views.d.ts +741 -0
- package/dist/agent/views.js +537 -0
- package/dist/browser/browser.d.ts +7 -0
- package/dist/browser/browser.js +5 -0
- package/dist/browser/context.d.ts +8 -0
- package/dist/browser/context.js +4 -0
- package/dist/browser/dvd-screensaver.d.ts +101 -0
- package/dist/browser/dvd-screensaver.js +270 -0
- package/dist/browser/extensions.d.ts +63 -0
- package/dist/browser/extensions.js +359 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/playwright-manager.d.ts +47 -0
- package/dist/browser/playwright-manager.js +146 -0
- package/dist/browser/profile.d.ts +196 -0
- package/dist/browser/profile.js +815 -0
- package/dist/browser/session.d.ts +505 -0
- package/dist/browser/session.js +3409 -0
- package/dist/browser/types.d.ts +1184 -0
- package/dist/browser/types.js +1 -0
- package/dist/browser/utils.d.ts +1 -0
- package/dist/browser/utils.js +19 -0
- package/dist/browser/views.d.ts +78 -0
- package/dist/browser/views.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +430 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/registry/index.d.ts +2 -0
- package/dist/controller/registry/index.js +2 -0
- package/dist/controller/registry/service.d.ts +45 -0
- package/dist/controller/registry/service.js +184 -0
- package/dist/controller/registry/views.d.ts +55 -0
- package/dist/controller/registry/views.js +174 -0
- package/dist/controller/service.d.ts +49 -0
- package/dist/controller/service.js +1176 -0
- package/dist/controller/views.d.ts +241 -0
- package/dist/controller/views.js +88 -0
- package/dist/dom/clickable-element-processor/service.d.ts +11 -0
- package/dist/dom/clickable-element-processor/service.js +60 -0
- package/dist/dom/dom_tree/index.js +1400 -0
- package/dist/dom/history-tree-processor/service.d.ts +14 -0
- package/dist/dom/history-tree-processor/service.js +75 -0
- package/dist/dom/history-tree-processor/view.d.ts +54 -0
- package/dist/dom/history-tree-processor/view.js +56 -0
- package/dist/dom/playground/extraction.d.ts +19 -0
- package/dist/dom/playground/extraction.js +187 -0
- package/dist/dom/playground/process-dom.d.ts +1 -0
- package/dist/dom/playground/process-dom.js +5 -0
- package/dist/dom/playground/test-accessibility.d.ts +44 -0
- package/dist/dom/playground/test-accessibility.js +111 -0
- package/dist/dom/service.d.ts +19 -0
- package/dist/dom/service.js +227 -0
- package/dist/dom/utils.d.ts +1 -0
- package/dist/dom/utils.js +6 -0
- package/dist/dom/views.d.ts +61 -0
- package/dist/dom/views.js +247 -0
- package/dist/event-bus.d.ts +11 -0
- package/dist/event-bus.js +19 -0
- package/dist/exceptions.d.ts +10 -0
- package/dist/exceptions.js +22 -0
- package/dist/filesystem/file-system.d.ts +68 -0
- package/dist/filesystem/file-system.js +412 -0
- package/dist/filesystem/index.d.ts +1 -0
- package/dist/filesystem/index.js +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/integrations/gmail/actions.d.ts +12 -0
- package/dist/integrations/gmail/actions.js +113 -0
- package/dist/integrations/gmail/index.d.ts +2 -0
- package/dist/integrations/gmail/index.js +2 -0
- package/dist/integrations/gmail/service.d.ts +61 -0
- package/dist/integrations/gmail/service.js +260 -0
- package/dist/llm/anthropic/chat.d.ts +28 -0
- package/dist/llm/anthropic/chat.js +126 -0
- package/dist/llm/anthropic/index.d.ts +2 -0
- package/dist/llm/anthropic/index.js +2 -0
- package/dist/llm/anthropic/serializer.d.ts +68 -0
- package/dist/llm/anthropic/serializer.js +285 -0
- package/dist/llm/aws/chat-anthropic.d.ts +61 -0
- package/dist/llm/aws/chat-anthropic.js +176 -0
- package/dist/llm/aws/chat-bedrock.d.ts +15 -0
- package/dist/llm/aws/chat-bedrock.js +80 -0
- package/dist/llm/aws/index.d.ts +3 -0
- package/dist/llm/aws/index.js +3 -0
- package/dist/llm/aws/serializer.d.ts +5 -0
- package/dist/llm/aws/serializer.js +68 -0
- package/dist/llm/azure/chat.d.ts +15 -0
- package/dist/llm/azure/chat.js +83 -0
- package/dist/llm/azure/index.d.ts +1 -0
- package/dist/llm/azure/index.js +1 -0
- package/dist/llm/base.d.ts +16 -0
- package/dist/llm/base.js +1 -0
- package/dist/llm/deepseek/chat.d.ts +15 -0
- package/dist/llm/deepseek/chat.js +51 -0
- package/dist/llm/deepseek/index.d.ts +2 -0
- package/dist/llm/deepseek/index.js +2 -0
- package/dist/llm/deepseek/serializer.d.ts +6 -0
- package/dist/llm/deepseek/serializer.js +57 -0
- package/dist/llm/exceptions.d.ts +10 -0
- package/dist/llm/exceptions.js +18 -0
- package/dist/llm/google/chat.d.ts +20 -0
- package/dist/llm/google/chat.js +144 -0
- package/dist/llm/google/index.d.ts +2 -0
- package/dist/llm/google/index.js +2 -0
- package/dist/llm/google/serializer.d.ts +6 -0
- package/dist/llm/google/serializer.js +64 -0
- package/dist/llm/groq/chat.d.ts +15 -0
- package/dist/llm/groq/chat.js +52 -0
- package/dist/llm/groq/index.d.ts +3 -0
- package/dist/llm/groq/index.js +3 -0
- package/dist/llm/groq/parser.d.ts +32 -0
- package/dist/llm/groq/parser.js +189 -0
- package/dist/llm/groq/serializer.d.ts +6 -0
- package/dist/llm/groq/serializer.js +56 -0
- package/dist/llm/messages.d.ts +77 -0
- package/dist/llm/messages.js +157 -0
- package/dist/llm/ollama/chat.d.ts +15 -0
- package/dist/llm/ollama/chat.js +77 -0
- package/dist/llm/ollama/index.d.ts +2 -0
- package/dist/llm/ollama/index.js +2 -0
- package/dist/llm/ollama/serializer.d.ts +6 -0
- package/dist/llm/ollama/serializer.js +53 -0
- package/dist/llm/openai/chat.d.ts +38 -0
- package/dist/llm/openai/chat.js +174 -0
- package/dist/llm/openai/index.d.ts +3 -0
- package/dist/llm/openai/index.js +3 -0
- package/dist/llm/openai/like.d.ts +17 -0
- package/dist/llm/openai/like.js +19 -0
- package/dist/llm/openai/serializer.d.ts +6 -0
- package/dist/llm/openai/serializer.js +57 -0
- package/dist/llm/openrouter/chat.d.ts +15 -0
- package/dist/llm/openrouter/chat.js +74 -0
- package/dist/llm/openrouter/index.d.ts +2 -0
- package/dist/llm/openrouter/index.js +2 -0
- package/dist/llm/openrouter/serializer.d.ts +3 -0
- package/dist/llm/openrouter/serializer.js +3 -0
- package/dist/llm/schema.d.ts +6 -0
- package/dist/llm/schema.js +77 -0
- package/dist/llm/views.d.ts +15 -0
- package/dist/llm/views.js +12 -0
- package/dist/logging-config.d.ts +25 -0
- package/dist/logging-config.js +89 -0
- package/dist/mcp/client.d.ts +142 -0
- package/dist/mcp/client.js +638 -0
- package/dist/mcp/controller.d.ts +6 -0
- package/dist/mcp/controller.js +38 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/server.d.ts +134 -0
- package/dist/mcp/server.js +759 -0
- package/dist/observability-decorators.d.ts +158 -0
- package/dist/observability-decorators.js +286 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.js +58 -0
- package/dist/screenshots/index.d.ts +1 -0
- package/dist/screenshots/index.js +1 -0
- package/dist/screenshots/service.d.ts +6 -0
- package/dist/screenshots/service.js +28 -0
- package/dist/sync/auth.d.ts +27 -0
- package/dist/sync/auth.js +205 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/service.d.ts +21 -0
- package/dist/sync/service.js +146 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/service.d.ts +12 -0
- package/dist/telemetry/service.js +85 -0
- package/dist/telemetry/views.d.ts +112 -0
- package/dist/telemetry/views.js +112 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/service.d.ts +35 -0
- package/dist/tokens/service.js +423 -0
- package/dist/tokens/views.d.ts +58 -0
- package/dist/tokens/views.js +1 -0
- package/dist/utils.d.ts +128 -0
- package/dist/utils.js +529 -0
- package/package.json +94 -5
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail API Service for Browser Use
|
|
3
|
+
* Handles Gmail API authentication, email reading, and 2FA code extraction.
|
|
4
|
+
* This service provides a clean interface for agents to interact with Gmail.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { google } from 'googleapis';
|
|
9
|
+
import { createLogger } from '../../logging-config.js';
|
|
10
|
+
import { CONFIG } from '../../config.js';
|
|
11
|
+
const logger = createLogger('browser_use.gmail');
|
|
12
|
+
export class GmailService {
|
|
13
|
+
static SCOPES = [
|
|
14
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
15
|
+
];
|
|
16
|
+
configDir;
|
|
17
|
+
credentialsFile;
|
|
18
|
+
tokenFile;
|
|
19
|
+
accessToken;
|
|
20
|
+
service = null;
|
|
21
|
+
creds = null;
|
|
22
|
+
_authenticated = false;
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
// Set up configuration directory
|
|
25
|
+
this.configDir = options.config_dir || CONFIG.BROWSER_USE_CONFIG_DIR;
|
|
26
|
+
// Direct access token support
|
|
27
|
+
this.accessToken = options.access_token || null;
|
|
28
|
+
// Ensure config directory exists (only if not using direct token)
|
|
29
|
+
if (!this.accessToken) {
|
|
30
|
+
if (!fs.existsSync(this.configDir)) {
|
|
31
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Set up credential paths
|
|
35
|
+
this.credentialsFile =
|
|
36
|
+
options.credentials_file ||
|
|
37
|
+
path.join(this.configDir, 'gmail_credentials.json');
|
|
38
|
+
this.tokenFile =
|
|
39
|
+
options.token_file || path.join(this.configDir, 'gmail_token.json');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if Gmail service is authenticated
|
|
43
|
+
*/
|
|
44
|
+
isAuthenticated() {
|
|
45
|
+
return this._authenticated && this.service !== null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Handle OAuth authentication and token management
|
|
49
|
+
*/
|
|
50
|
+
async authenticate() {
|
|
51
|
+
try {
|
|
52
|
+
logger.info('🔐 Authenticating with Gmail API...');
|
|
53
|
+
// Check if using direct access token
|
|
54
|
+
if (this.accessToken) {
|
|
55
|
+
logger.info('🔑 Using provided access token');
|
|
56
|
+
const auth = new google.auth.OAuth2();
|
|
57
|
+
auth.setCredentials({ access_token: this.accessToken });
|
|
58
|
+
this.service = google.gmail({ version: 'v1', auth });
|
|
59
|
+
this._authenticated = true;
|
|
60
|
+
logger.info('✅ Gmail API ready with access token!');
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Original file-based authentication flow
|
|
64
|
+
// Try to load existing tokens
|
|
65
|
+
if (fs.existsSync(this.tokenFile)) {
|
|
66
|
+
const tokenData = JSON.parse(fs.readFileSync(this.tokenFile, 'utf-8'));
|
|
67
|
+
const auth = new google.auth.OAuth2();
|
|
68
|
+
auth.setCredentials(tokenData);
|
|
69
|
+
this.creds = auth;
|
|
70
|
+
logger.debug('📁 Loaded existing tokens');
|
|
71
|
+
}
|
|
72
|
+
// If no valid credentials, run OAuth flow
|
|
73
|
+
if (!this.creds ||
|
|
74
|
+
!this.creds.credentials ||
|
|
75
|
+
!this.creds.credentials.access_token) {
|
|
76
|
+
if (this.creds &&
|
|
77
|
+
this.creds.credentials &&
|
|
78
|
+
this.creds.credentials.refresh_token) {
|
|
79
|
+
logger.info('🔄 Refreshing expired tokens...');
|
|
80
|
+
await this.creds.refreshAccessToken();
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
logger.info('🌐 Starting OAuth flow...');
|
|
84
|
+
if (!fs.existsSync(this.credentialsFile)) {
|
|
85
|
+
logger.error(`❌ Gmail credentials file not found: ${this.credentialsFile}\n` +
|
|
86
|
+
'Please download it from Google Cloud Console:\n' +
|
|
87
|
+
'1. Go to https://console.cloud.google.com/\n' +
|
|
88
|
+
'2. APIs & Services > Credentials\n' +
|
|
89
|
+
'3. Download OAuth 2.0 Client JSON\n' +
|
|
90
|
+
`4. Save as 'gmail_credentials.json' in ${this.configDir}/`);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const credentials = JSON.parse(fs.readFileSync(this.credentialsFile, 'utf-8'));
|
|
94
|
+
const { client_secret, client_id, redirect_uris } = credentials.installed || credentials.web;
|
|
95
|
+
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
|
96
|
+
const authUrl = oAuth2Client.generateAuthUrl({
|
|
97
|
+
access_type: 'offline',
|
|
98
|
+
scope: GmailService.SCOPES,
|
|
99
|
+
});
|
|
100
|
+
logger.info(`🔗 Please visit this URL to authorize:\n${authUrl}`);
|
|
101
|
+
logger.info('⏳ Waiting for authorization code...');
|
|
102
|
+
// Note: In a real implementation, you would use a web server to handle the OAuth callback
|
|
103
|
+
// For now, we'll throw an error with instructions
|
|
104
|
+
throw new Error('OAuth flow requires manual intervention. Please:\n' +
|
|
105
|
+
`1. Visit: ${authUrl}\n` +
|
|
106
|
+
'2. Authorize the application\n' +
|
|
107
|
+
'3. Copy the authorization code\n' +
|
|
108
|
+
'4. Implement token exchange logic');
|
|
109
|
+
}
|
|
110
|
+
// Save tokens for next time
|
|
111
|
+
fs.writeFileSync(this.tokenFile, JSON.stringify(this.creds.credentials));
|
|
112
|
+
logger.info(`💾 Tokens saved to ${this.tokenFile}`);
|
|
113
|
+
}
|
|
114
|
+
// Build Gmail service
|
|
115
|
+
this.service = google.gmail({ version: 'v1', auth: this.creds });
|
|
116
|
+
this._authenticated = true;
|
|
117
|
+
logger.info('✅ Gmail API ready!');
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logger.error(`❌ Gmail authentication failed: ${error}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get recent emails with optional query filter
|
|
127
|
+
*/
|
|
128
|
+
async getRecentEmails(options = {}) {
|
|
129
|
+
const { max_results = 10, query = '', time_filter = '1h' } = options;
|
|
130
|
+
if (!this.isAuthenticated()) {
|
|
131
|
+
logger.error('❌ Gmail service not authenticated. Call authenticate() first.');
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
// Add time filter to query if provided
|
|
136
|
+
let fullQuery = query;
|
|
137
|
+
if (time_filter && !query.includes('newer_than:')) {
|
|
138
|
+
fullQuery = `newer_than:${time_filter} ${query}`.trim();
|
|
139
|
+
}
|
|
140
|
+
logger.info(`📧 Fetching ${max_results} recent emails...`);
|
|
141
|
+
if (fullQuery) {
|
|
142
|
+
logger.debug(`🔍 Query: ${fullQuery}`);
|
|
143
|
+
}
|
|
144
|
+
// Get message list
|
|
145
|
+
const results = (await this.service.users.messages.list({
|
|
146
|
+
userId: 'me',
|
|
147
|
+
maxResults: max_results,
|
|
148
|
+
q: fullQuery,
|
|
149
|
+
}));
|
|
150
|
+
const messages = results.data.messages || [];
|
|
151
|
+
if (!messages.length) {
|
|
152
|
+
logger.info('📭 No messages found');
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
logger.info(`📨 Found ${messages.length} messages, fetching details...`);
|
|
156
|
+
// Get full message details
|
|
157
|
+
const emails = [];
|
|
158
|
+
for (let i = 0; i < messages.length; i++) {
|
|
159
|
+
const message = messages[i];
|
|
160
|
+
logger.debug(`📖 Reading email ${i + 1}/${messages.length}...`);
|
|
161
|
+
const fullMessage = (await this.service.users.messages.get({
|
|
162
|
+
userId: 'me',
|
|
163
|
+
id: message.id,
|
|
164
|
+
format: 'full',
|
|
165
|
+
}));
|
|
166
|
+
const emailData = this._parseEmail(fullMessage.data);
|
|
167
|
+
emails.push(emailData);
|
|
168
|
+
}
|
|
169
|
+
return emails;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
logger.error(`❌ Gmail API error: ${error.message || error}`);
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Parse Gmail message into readable format
|
|
178
|
+
*/
|
|
179
|
+
_parseEmail(message) {
|
|
180
|
+
const headers = message.payload?.headers || [];
|
|
181
|
+
const headerMap = {};
|
|
182
|
+
for (const header of headers) {
|
|
183
|
+
if (header.name && header.value) {
|
|
184
|
+
headerMap[header.name] = header.value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
id: message.id || '',
|
|
189
|
+
thread_id: message.threadId || '',
|
|
190
|
+
subject: headerMap['Subject'] || '',
|
|
191
|
+
from: headerMap['From'] || '',
|
|
192
|
+
to: headerMap['To'] || '',
|
|
193
|
+
date: headerMap['Date'] || '',
|
|
194
|
+
timestamp: parseInt(message.internalDate || '0'),
|
|
195
|
+
body: this._extractBody(message.payload || {}),
|
|
196
|
+
raw_message: message,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Extract email body from payload
|
|
201
|
+
*/
|
|
202
|
+
_extractBody(payload) {
|
|
203
|
+
let body = '';
|
|
204
|
+
if (payload.body?.data) {
|
|
205
|
+
// Simple email body
|
|
206
|
+
body = Buffer.from(payload.body.data, 'base64').toString('utf-8');
|
|
207
|
+
}
|
|
208
|
+
else if (payload.parts) {
|
|
209
|
+
// Multi-part email
|
|
210
|
+
for (const part of payload.parts) {
|
|
211
|
+
if (part.mimeType === 'text/plain' && part.body?.data) {
|
|
212
|
+
const partBody = Buffer.from(part.body.data, 'base64').toString('utf-8');
|
|
213
|
+
body += partBody;
|
|
214
|
+
}
|
|
215
|
+
else if (part.mimeType === 'text/html' && !body && part.body?.data) {
|
|
216
|
+
// Fallback to HTML if no plain text
|
|
217
|
+
body = Buffer.from(part.body.data, 'base64').toString('utf-8');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return body;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Send an email
|
|
225
|
+
*/
|
|
226
|
+
async sendMessage(to, subject, body) {
|
|
227
|
+
if (!this.isAuthenticated()) {
|
|
228
|
+
logger.error('❌ Gmail service not authenticated.');
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const utf8Subject = `=?utf-8?B?${Buffer.from(subject).toString('base64')}?=`;
|
|
233
|
+
const messageParts = [
|
|
234
|
+
`To: ${to}`,
|
|
235
|
+
'Content-Type: text/html; charset=utf-8',
|
|
236
|
+
'MIME-Version: 1.0',
|
|
237
|
+
`Subject: ${utf8Subject}`,
|
|
238
|
+
'',
|
|
239
|
+
body,
|
|
240
|
+
];
|
|
241
|
+
const message = messageParts.join('\n');
|
|
242
|
+
const encodedMessage = Buffer.from(message)
|
|
243
|
+
.toString('base64')
|
|
244
|
+
.replace(/\+/g, '-')
|
|
245
|
+
.replace(/\//g, '_')
|
|
246
|
+
.replace(/=+$/, '');
|
|
247
|
+
const res = await this.service.users.messages.send({
|
|
248
|
+
userId: 'me',
|
|
249
|
+
requestBody: {
|
|
250
|
+
raw: encodedMessage,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return res.data;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
logger.error(`❌ Failed to send email: ${error.message || error}`);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { BaseChatModel, ChatInvokeOptions } from '../base.js';
|
|
2
|
+
import { ChatInvokeCompletion } from '../views.js';
|
|
3
|
+
import { type Message } from '../messages.js';
|
|
4
|
+
export interface ChatAnthropicOptions {
|
|
5
|
+
model?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
maxTokens?: number;
|
|
9
|
+
temperature?: number | null;
|
|
10
|
+
topP?: number | null;
|
|
11
|
+
maxRetries?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class ChatAnthropic implements BaseChatModel {
|
|
14
|
+
model: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
private client;
|
|
17
|
+
private maxTokens;
|
|
18
|
+
private temperature;
|
|
19
|
+
private topP;
|
|
20
|
+
constructor(options?: ChatAnthropicOptions);
|
|
21
|
+
get name(): string;
|
|
22
|
+
get model_name(): string;
|
|
23
|
+
private getUsage;
|
|
24
|
+
ainvoke(messages: Message[], output_format?: undefined, options?: ChatInvokeOptions): Promise<ChatInvokeCompletion<string>>;
|
|
25
|
+
ainvoke<T>(messages: Message[], output_format: {
|
|
26
|
+
parse: (input: string) => T;
|
|
27
|
+
} | undefined, options?: ChatInvokeOptions): Promise<ChatInvokeCompletion<T>>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
3
|
+
import { ChatInvokeCompletion } from '../views.js';
|
|
4
|
+
import { SystemMessage } from '../messages.js';
|
|
5
|
+
import { AnthropicMessageSerializer } from './serializer.js';
|
|
6
|
+
import { ModelProviderError, ModelRateLimitError } from '../exceptions.js';
|
|
7
|
+
export class ChatAnthropic {
|
|
8
|
+
model;
|
|
9
|
+
provider = 'anthropic';
|
|
10
|
+
client;
|
|
11
|
+
maxTokens;
|
|
12
|
+
temperature;
|
|
13
|
+
topP;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
const { model = 'claude-sonnet-4-20250514', apiKey, baseURL, maxTokens = 8192, temperature = null, topP = null, maxRetries = 10, } = options;
|
|
16
|
+
this.model = model;
|
|
17
|
+
this.maxTokens = maxTokens;
|
|
18
|
+
this.temperature = temperature;
|
|
19
|
+
this.topP = topP;
|
|
20
|
+
this.client = new Anthropic({
|
|
21
|
+
apiKey,
|
|
22
|
+
baseURL,
|
|
23
|
+
maxRetries,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
get name() {
|
|
27
|
+
return this.model;
|
|
28
|
+
}
|
|
29
|
+
get model_name() {
|
|
30
|
+
return this.model;
|
|
31
|
+
}
|
|
32
|
+
getUsage(response) {
|
|
33
|
+
const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
|
|
34
|
+
const cacheCreationTokens = response.usage.cache_creation_input_tokens ?? 0;
|
|
35
|
+
return {
|
|
36
|
+
prompt_tokens: response.usage.input_tokens + cacheReadTokens,
|
|
37
|
+
completion_tokens: response.usage.output_tokens,
|
|
38
|
+
total_tokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
39
|
+
prompt_cached_tokens: cacheReadTokens || null,
|
|
40
|
+
prompt_cache_creation_tokens: cacheCreationTokens || null,
|
|
41
|
+
prompt_image_tokens: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async ainvoke(messages, output_format, options = {}) {
|
|
45
|
+
const serializer = new AnthropicMessageSerializer();
|
|
46
|
+
const [anthropicMessages] = serializer.serializeMessages(messages);
|
|
47
|
+
const systemMessage = messages.find((msg) => msg instanceof SystemMessage);
|
|
48
|
+
const system = systemMessage ? systemMessage.text : undefined;
|
|
49
|
+
let tools = undefined;
|
|
50
|
+
let toolChoice = undefined;
|
|
51
|
+
if (output_format && 'schema' in output_format && output_format.schema) {
|
|
52
|
+
// Assuming output_format is a Zod schema wrapper
|
|
53
|
+
try {
|
|
54
|
+
const jsonSchema = zodToJsonSchema(output_format, {
|
|
55
|
+
name: 'Response',
|
|
56
|
+
target: 'jsonSchema7',
|
|
57
|
+
});
|
|
58
|
+
tools = [
|
|
59
|
+
{
|
|
60
|
+
name: 'response',
|
|
61
|
+
description: 'The response to the user request',
|
|
62
|
+
input_schema: jsonSchema,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
toolChoice = { type: 'tool', name: 'response' };
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
console.warn('Failed to convert output_format to JSON schema for Anthropic', e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Build model parameters
|
|
72
|
+
const modelParams = {};
|
|
73
|
+
if (this.temperature !== null) {
|
|
74
|
+
modelParams.temperature = this.temperature;
|
|
75
|
+
}
|
|
76
|
+
if (this.topP !== null) {
|
|
77
|
+
modelParams.top_p = this.topP;
|
|
78
|
+
}
|
|
79
|
+
// Add cache_control to tools if present
|
|
80
|
+
if (tools && tools.length > 0) {
|
|
81
|
+
tools = tools.map((tool, index) => {
|
|
82
|
+
if (index === tools.length - 1) {
|
|
83
|
+
return {
|
|
84
|
+
...tool,
|
|
85
|
+
cache_control: { type: 'ephemeral' },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return tool;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const response = await this.client.messages.create({
|
|
93
|
+
model: this.model,
|
|
94
|
+
max_tokens: this.maxTokens,
|
|
95
|
+
system: system,
|
|
96
|
+
messages: anthropicMessages,
|
|
97
|
+
tools: tools,
|
|
98
|
+
tool_choice: toolChoice,
|
|
99
|
+
...modelParams,
|
|
100
|
+
}, options.signal ? { signal: options.signal } : undefined);
|
|
101
|
+
let completion = '';
|
|
102
|
+
// Handle tool use response
|
|
103
|
+
const toolUseBlock = response.content.find((block) => block.type === 'tool_use');
|
|
104
|
+
if (toolUseBlock && output_format) {
|
|
105
|
+
completion = output_format.parse(toolUseBlock.input);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Fallback to text content
|
|
109
|
+
const textBlock = response.content.find((block) => block.type === 'text');
|
|
110
|
+
completion = textBlock ? textBlock.text : '';
|
|
111
|
+
}
|
|
112
|
+
const usage = this.getUsage(response);
|
|
113
|
+
return new ChatInvokeCompletion(completion, usage);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Handle Anthropic-specific errors
|
|
117
|
+
if (error?.status === 429) {
|
|
118
|
+
throw new ModelRateLimitError(error?.message ?? 'Rate limit exceeded', 429, this.model);
|
|
119
|
+
}
|
|
120
|
+
if (error?.status >= 500) {
|
|
121
|
+
throw new ModelProviderError(error?.message ?? 'Server error', error.status, this.model);
|
|
122
|
+
}
|
|
123
|
+
throw new ModelProviderError(error?.message ?? String(error), error?.status ?? 500, this.model);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Message Serializer with Prompt Caching Support
|
|
3
|
+
*
|
|
4
|
+
* This serializer converts custom message types to Anthropic's MessageParam format
|
|
5
|
+
* and implements Anthropic's Prompt Caching feature to reduce costs by up to 90%.
|
|
6
|
+
*
|
|
7
|
+
* Caching Strategy:
|
|
8
|
+
* - Only the last message with cache=true will have cache_control enabled
|
|
9
|
+
* - Caching is most effective for system prompts and large conversation histories
|
|
10
|
+
* - Cache writes cost 25% more, but cache reads cost 90% less
|
|
11
|
+
*
|
|
12
|
+
* Example cost savings:
|
|
13
|
+
* - Without caching: 10,000 tokens @ $3/M = $0.030 per request
|
|
14
|
+
* - With caching (90% hit rate):
|
|
15
|
+
* - First request: 10,000 tokens @ $3.75/M (write) = $0.0375
|
|
16
|
+
* - Subsequent: 1,000 tokens @ $3/M + 9,000 tokens @ $0.30/M = $0.0057
|
|
17
|
+
* - Savings: 81% cost reduction
|
|
18
|
+
*/
|
|
19
|
+
import type { MessageParam, TextBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs';
|
|
20
|
+
import { type Message } from '../messages.js';
|
|
21
|
+
export declare class AnthropicMessageSerializer {
|
|
22
|
+
/**
|
|
23
|
+
* Serialize a list of messages, extracting any system message
|
|
24
|
+
*
|
|
25
|
+
* @param messages - List of messages to serialize
|
|
26
|
+
* @returns Tuple of [messages, system_message]
|
|
27
|
+
*/
|
|
28
|
+
serializeMessages(messages: Message[]): [MessageParam[], (string | TextBlockParam[])?];
|
|
29
|
+
/**
|
|
30
|
+
* Serialize a single message
|
|
31
|
+
*/
|
|
32
|
+
serializeMessage(message: Message): MessageParam;
|
|
33
|
+
/**
|
|
34
|
+
* Serialize cache control parameter
|
|
35
|
+
*/
|
|
36
|
+
private _serializeCacheControl;
|
|
37
|
+
/**
|
|
38
|
+
* Serialize text content part with optional caching
|
|
39
|
+
*/
|
|
40
|
+
private _serializeContentPartText;
|
|
41
|
+
/**
|
|
42
|
+
* Serialize image content part
|
|
43
|
+
*/
|
|
44
|
+
private _serializeContentPartImage;
|
|
45
|
+
/**
|
|
46
|
+
* Serialize content (string or array) with optional caching
|
|
47
|
+
*/
|
|
48
|
+
private _serializeContent;
|
|
49
|
+
/**
|
|
50
|
+
* Serialize content to string format (for system messages)
|
|
51
|
+
*/
|
|
52
|
+
private _serializeContentToStr;
|
|
53
|
+
/**
|
|
54
|
+
* Check if URL is a base64 encoded image
|
|
55
|
+
*/
|
|
56
|
+
private _isBase64Image;
|
|
57
|
+
/**
|
|
58
|
+
* Parse base64 data URL to extract media type and data
|
|
59
|
+
*/
|
|
60
|
+
private _parseBase64Url;
|
|
61
|
+
/**
|
|
62
|
+
* Clean cache settings so only the last cache=true message remains cached
|
|
63
|
+
*
|
|
64
|
+
* Because of how Claude caching works, only the last cache message matters.
|
|
65
|
+
* This method automatically removes cache=True from all messages except the last one.
|
|
66
|
+
*/
|
|
67
|
+
private _cleanCacheMessages;
|
|
68
|
+
}
|