@juspay/neurolink
Version:
Universal AI Development Platform with external MCP server integration, multi-provider support, and professional CLI. Connect to 65+ MCP servers for filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major pr
309 lines (308 loc) • 12.3 kB
JavaScript
/**
* Anthropic AI Provider (Direct API)
*
* Direct integration with Anthropic's Claude models via their native API.
* Supports Claude 3.5 Sonnet, Claude 3.5 Haiku, and Claude 3 Opus.
*/
import { AIProviderName } from '../core/types.js';
import { logger } from '../utils/logger.js';
export class AnthropicProvider {
name = AIProviderName.ANTHROPIC;
apiKey;
baseURL;
defaultModel;
constructor() {
this.apiKey = this.getApiKey();
this.baseURL = process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com';
this.defaultModel = process.env.ANTHROPIC_MODEL || 'claude-3-5-sonnet-20241022';
logger.debug(`[AnthropicProvider] Initialized with model: ${this.defaultModel}`);
}
getApiKey() {
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY environment variable is required');
}
return apiKey;
}
getModel() {
return this.defaultModel;
}
async makeRequest(endpoint, body, stream = false) {
const url = `${this.baseURL}/v1/${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
'anthropic-version': '2023-06-01',
'anthropic-dangerous-direct-browser-access': 'true' // Required for browser usage
};
logger.debug(`[AnthropicProvider.makeRequest] ${stream ? 'Streaming' : 'Non-streaming'} request to ${url}`);
logger.debug(`[AnthropicProvider.makeRequest] Model: ${body.model}, Max tokens: ${body.max_tokens}`);
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(body)
});
if (!response.ok) {
const errorText = await response.text();
logger.error(`[AnthropicProvider.makeRequest] API error ${response.status}: ${errorText}`);
throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
}
return response;
}
async generateText(optionsOrPrompt, schema) {
logger.debug('[AnthropicProvider.generateText] Starting text generation');
// Parse parameters with backward compatibility
const options = typeof optionsOrPrompt === 'string'
? { prompt: optionsOrPrompt }
: optionsOrPrompt;
const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options;
logger.debug(`[AnthropicProvider.generateText] Prompt: "${prompt.substring(0, 100)}...", Temperature: ${temperature}, Max tokens: ${maxTokens}`);
const requestBody = {
model: this.getModel(),
max_tokens: maxTokens,
messages: [
{
role: 'user',
content: prompt
}
],
temperature,
system: systemPrompt
};
try {
const response = await this.makeRequest('messages', requestBody);
const data = await response.json();
logger.debug(`[AnthropicProvider.generateText] Success. Generated ${data.usage.output_tokens} tokens`);
const content = data.content.map(block => block.text).join('');
return {
content,
provider: this.name,
model: data.model,
usage: {
promptTokens: data.usage.input_tokens,
completionTokens: data.usage.output_tokens,
totalTokens: data.usage.input_tokens + data.usage.output_tokens
},
finishReason: data.stop_reason
};
}
catch (error) {
logger.error('[AnthropicProvider.generateText] Error:', error);
throw error;
}
}
async streamText(optionsOrPrompt, schema) {
logger.debug('[AnthropicProvider.streamText] Starting text streaming');
// Parse parameters with backward compatibility
const options = typeof optionsOrPrompt === 'string'
? { prompt: optionsOrPrompt }
: optionsOrPrompt;
const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options;
logger.debug(`[AnthropicProvider.streamText] Streaming prompt: "${prompt.substring(0, 100)}..."`);
const requestBody = {
model: this.getModel(),
max_tokens: maxTokens,
messages: [
{
role: 'user',
content: prompt
}
],
temperature,
system: systemPrompt,
stream: true
};
try {
const response = await this.makeRequest('messages', requestBody, true);
if (!response.body) {
throw new Error('No response body received');
}
// Return a StreamTextResult-like object
return {
textStream: this.createAsyncIterable(response.body),
text: '',
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
finishReason: 'end_turn'
};
}
catch (error) {
logger.error('[AnthropicProvider.streamText] Error:', error);
throw error;
}
}
async *createAsyncIterable(body) {
const reader = body.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim() === '')
continue;
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data.trim() === '[DONE]')
continue;
try {
const chunk = JSON.parse(data);
// Extract text content from different chunk types
if (chunk.type === 'content_block_delta' && chunk.delta?.text) {
yield chunk.delta.text;
}
}
catch (parseError) {
logger.warn('[AnthropicProvider.createAsyncIterable] Failed to parse chunk:', parseError);
continue;
}
}
}
}
}
finally {
reader.releaseLock();
}
}
async *generateTextStream(optionsOrPrompt) {
logger.debug('[AnthropicProvider.generateTextStream] Starting text streaming');
// Parse parameters with backward compatibility
const options = typeof optionsOrPrompt === 'string'
? { prompt: optionsOrPrompt }
: optionsOrPrompt;
const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = 'You are Claude, an AI assistant created by Anthropic. You are helpful, harmless, and honest.' } = options;
logger.debug(`[AnthropicProvider.generateTextStream] Streaming prompt: "${prompt.substring(0, 100)}..."`);
const requestBody = {
model: this.getModel(),
max_tokens: maxTokens,
messages: [
{
role: 'user',
content: prompt
}
],
temperature,
system: systemPrompt,
stream: true
};
try {
const response = await this.makeRequest('messages', requestBody, true);
if (!response.body) {
throw new Error('No response body received');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim() === '')
continue;
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data.trim() === '[DONE]')
continue;
try {
const chunk = JSON.parse(data);
// Extract text content from different chunk types
if (chunk.type === 'content_block_delta' && chunk.delta?.text) {
yield {
content: chunk.delta.text,
provider: this.name,
model: this.getModel()
};
}
}
catch (parseError) {
logger.warn('[AnthropicProvider.generateTextStream] Failed to parse chunk:', parseError);
continue;
}
}
}
}
}
finally {
reader.releaseLock();
}
logger.debug('[AnthropicProvider.generateTextStream] Streaming completed');
}
catch (error) {
logger.error('[AnthropicProvider.generateTextStream] Error:', error);
throw error;
}
}
async testConnection() {
logger.debug('[AnthropicProvider.testConnection] Testing connection to Anthropic API');
const startTime = Date.now();
try {
await this.generateText({
prompt: 'Hello',
maxTokens: 5
});
const responseTime = Date.now() - startTime;
logger.debug(`[AnthropicProvider.testConnection] Connection test successful (${responseTime}ms)`);
return {
success: true,
responseTime
};
}
catch (error) {
const responseTime = Date.now() - startTime;
logger.error(`[AnthropicProvider.testConnection] Connection test failed (${responseTime}ms):`, error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
responseTime
};
}
}
isConfigured() {
try {
this.getApiKey();
return true;
}
catch {
return false;
}
}
getRequiredConfig() {
return ['ANTHROPIC_API_KEY'];
}
getOptionalConfig() {
return ['ANTHROPIC_MODEL', 'ANTHROPIC_BASE_URL'];
}
getModels() {
return [
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-3-haiku-20240307'
];
}
supportsStreaming() {
return true;
}
supportsSchema() {
return false; // Anthropic doesn't have native JSON schema support like OpenAI
}
getCapabilities() {
return [
'text-generation',
'streaming',
'conversation',
'system-prompts',
'long-context' // Claude models support up to 200k tokens
];
}
}