anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
500 lines • 17.3 kB
JavaScript
;
/**
* Anthropic MCP Provider Implementation
*
* Provider implementation for Anthropic Claude LLM services
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicProviderFactory = exports.AnthropicProvider = void 0;
const types_1 = require("../types");
const base_provider_1 = require("./base-provider");
/**
* Anthropic MCP Provider
*/
class AnthropicProvider extends base_provider_1.BaseLLMProvider {
constructor(config) {
super(config);
this.apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY || '';
this.baseURL = config.endpoint || 'https://api.anthropic.com/v1';
this.apiVersion = '2023-06-01';
// Set Anthropic-specific capabilities
this.capabilities = {
completion: true,
streaming: true,
functionCalling: false, // Claude doesn't have native function calling yet
embeddings: false,
moderation: false,
multimodal: true,
codeGeneration: true,
jsonMode: false
};
// Set Anthropic models
this.models = [
{
id: 'claude-3-opus-20240229',
name: 'Claude 3 Opus',
description: 'Most capable Claude 3 model',
capabilities: ['completion', 'code_generation', 'multimodal'],
contextLength: 200000,
inputCost: 0.015,
outputCost: 0.075,
deprecated: false
},
{
id: 'claude-3-sonnet-20240229',
name: 'Claude 3 Sonnet',
description: 'Balanced performance and speed',
capabilities: ['completion', 'code_generation', 'multimodal'],
contextLength: 200000,
inputCost: 0.003,
outputCost: 0.015,
deprecated: false
},
{
id: 'claude-3-haiku-20240307',
name: 'Claude 3 Haiku',
description: 'Fastest Claude 3 model',
capabilities: ['completion', 'code_generation'],
contextLength: 200000,
inputCost: 0.00025,
outputCost: 0.00125,
deprecated: false
},
{
id: 'claude-2.1',
name: 'Claude 2.1',
description: 'Previous generation Claude model',
capabilities: ['completion', 'code_generation'],
contextLength: 200000,
inputCost: 0.008,
outputCost: 0.024,
deprecated: false
}
];
}
/**
* Initialize the provider
*/
async initialize() {
if (!this.apiKey) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.INVALID_CONFIG,
message: 'Anthropic API key is required',
timestamp: new Date(),
provider: this.id,
retryable: false
});
}
try {
// Test the API key with a simple request
await this.testConnection();
this.status = types_1.ProviderStatus.AVAILABLE;
}
catch (error) {
this.status = types_1.ProviderStatus.ERROR;
throw new types_1.MCPError({
code: types_1.MCPErrorCode.PROVIDER_ERROR,
message: `Failed to initialize Anthropic provider: ${error.message}`,
timestamp: new Date(),
provider: this.id,
retryable: true
});
}
}
/**
* Test connection to Anthropic API
*/
async testConnection() {
// Anthropic doesn't have a models endpoint, so we'll try a minimal message
const response = await fetch(`${this.baseURL}/messages`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify({
model: 'claude-3-haiku-20240307',
max_tokens: 1,
messages: [
{
role: 'user',
content: 'test'
}
]
})
});
if (!response.ok && response.status !== 400) {
// 400 is expected for minimal request, but other errors indicate real issues
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
}
/**
* Check provider health
*/
async health() {
const start = Date.now();
try {
await this.testConnection();
const latency = Date.now() - start;
return {
status: 'healthy',
latency,
details: {
apiEndpoint: this.baseURL,
modelsAvailable: this.models.length,
version: this.apiVersion
}
};
}
catch (error) {
return {
status: 'unhealthy',
details: {
error: error.message,
apiEndpoint: this.baseURL
}
};
}
}
/**
* Send completion request
*/
async completion(request, context) {
const model = request.parameters?.model || this.getDefaultModel(request.type);
this.validateModel(model);
const requestBody = this.buildMessageRequest(request, model);
try {
const response = await fetch(`${this.baseURL}/messages`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw await this.handleAPIError(response);
}
const data = await response.json();
return this.convertMessageResponse(request, context, data);
}
catch (error) {
if (error instanceof types_1.MCPError) {
throw error;
}
throw new types_1.MCPError({
code: types_1.MCPErrorCode.PROVIDER_ERROR,
message: `Anthropic API error: ${error.message}`,
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: true
});
}
}
/**
* Send streaming completion request
*/
async *stream(request, context) {
const model = request.parameters?.model || this.getDefaultModel(request.type);
this.validateModel(model);
const requestBody = this.buildMessageRequest(request, model, true);
try {
const response = await fetch(`${this.baseURL}/messages`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw await this.handleAPIError(response);
}
if (!response.body) {
throw new Error('No response body for streaming');
}
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.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
return;
}
try {
const event = JSON.parse(data);
const responseChunk = this.convertStreamEvent(request, event);
if (responseChunk) {
yield responseChunk;
}
}
catch (parseError) {
// Skip invalid JSON chunks
continue;
}
}
}
}
}
finally {
reader.releaseLock();
}
}
catch (error) {
if (error instanceof types_1.MCPError) {
throw error;
}
throw new types_1.MCPError({
code: types_1.MCPErrorCode.PROVIDER_ERROR,
message: `Anthropic streaming error: ${error.message}`,
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: true
});
}
}
/**
* Generate embeddings (not supported by Anthropic)
*/
async embed(request, context) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND,
message: 'Embeddings not supported by Anthropic provider',
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: false
});
}
/**
* Moderate content (not supported by Anthropic)
*/
async moderate(request, context) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND,
message: 'Content moderation not supported by Anthropic provider',
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: false
});
}
/**
* Build message request body for Anthropic API
*/
buildMessageRequest(request, model, stream = false) {
const messages = this.buildMessages(request);
const requestBody = {
model,
max_tokens: request.parameters?.maxTokens || 4096,
messages,
stream
};
// Add optional parameters
if (request.parameters?.temperature !== undefined) {
requestBody.temperature = request.parameters.temperature;
}
if (request.parameters?.topP !== undefined) {
requestBody.top_p = request.parameters.topP;
}
if (request.parameters?.stop) {
requestBody.stop_sequences = Array.isArray(request.parameters.stop)
? request.parameters.stop
: [request.parameters.stop];
}
// Add system message if available from context
if (request.context?.summary) {
requestBody.system = request.context.summary;
}
return requestBody;
}
/**
* Build messages array for Anthropic API
*/
buildMessages(request) {
const messages = [];
// Add context messages if available (skip system messages as they go in system field)
if (request.context?.history) {
for (const msg of request.context.history) {
if (msg.role !== 'system') {
messages.push({
role: msg.role === 'assistant' ? 'assistant' : 'user',
content: msg.content
});
}
}
}
// Add current prompt
messages.push({
role: 'user',
content: request.prompt
});
return messages;
}
/**
* Convert Anthropic message response to standard format
*/
convertMessageResponse(request, context, data) {
const baseResponse = this.createBaseResponse(request, context);
const content = data.content.map(block => block.text).join('');
const usage = {
promptTokens: data.usage.input_tokens,
completionTokens: data.usage.output_tokens,
totalTokens: data.usage.input_tokens + data.usage.output_tokens,
model: data.model,
provider: this.id,
cost: this.calculateCost(data.usage.input_tokens, data.usage.output_tokens, data.model)
};
// Handle function calling through tool use (if we implement it later)
let functionCall;
if (request.type === types_1.LLMRequestType.FUNCTION_CALL) {
// For now, we'll try to extract function calls from the text response
functionCall = this.extractFunctionCallFromText(content);
}
return {
...baseResponse,
content,
functionCall,
usage,
model: data.model
};
}
/**
* Convert Anthropic stream event to standard format
*/
convertStreamEvent(request, event) {
if (event.type === 'content_block_delta' && event.delta?.text) {
return {
id: `chunk-${Date.now()}`,
requestId: request.id,
delta: event.delta.text,
finished: false
};
}
if (event.type === 'message_stop') {
return {
id: `chunk-${Date.now()}`,
requestId: request.id,
delta: '',
finished: true
};
}
return null;
}
/**
* Extract function calls from text response (basic implementation)
*/
extractFunctionCallFromText(content) {
// Look for function call patterns in the response
const functionCallPattern = /<function_call>\s*\{([^}]+)\}\s*<\/function_call>/;
const match = content.match(functionCallPattern);
if (match) {
try {
const call = JSON.parse(`{${match[1]}}`);
return {
name: call.name,
arguments: call.arguments || {},
id: `call-${Date.now()}`
};
}
catch {
// Invalid function call format
}
}
return undefined;
}
/**
* Get HTTP headers for Anthropic API
*/
getHeaders() {
const headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
'anthropic-version': this.apiVersion,
'User-Agent': 'anon-identity-mcp/1.0.0'
};
// Add custom headers from config
if (this.config.customHeaders) {
Object.assign(headers, this.config.customHeaders);
}
return headers;
}
/**
* Handle Anthropic API errors
*/
async handleAPIError(response) {
let errorData;
try {
errorData = await response.json();
}
catch {
errorData = { error: { message: response.statusText } };
}
const errorMessage = errorData.error?.message || errorData.message || 'Unknown Anthropic API error';
let errorCode = types_1.MCPErrorCode.PROVIDER_ERROR;
switch (response.status) {
case 400:
errorCode = types_1.MCPErrorCode.INVALID_REQUEST;
break;
case 401:
errorCode = types_1.MCPErrorCode.UNAUTHORIZED;
break;
case 403:
errorCode = types_1.MCPErrorCode.FORBIDDEN;
break;
case 404:
errorCode = types_1.MCPErrorCode.MODEL_NOT_FOUND;
break;
case 429:
errorCode = types_1.MCPErrorCode.RATE_LIMITED;
break;
case 500:
case 502:
case 503:
errorCode = types_1.MCPErrorCode.PROVIDER_UNAVAILABLE;
break;
}
return new types_1.MCPError({
code: errorCode,
message: errorMessage,
timestamp: new Date(),
provider: this.id,
retryable: [500, 502, 503, 429].includes(response.status),
details: {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: errorData
}
});
}
/**
* Override function conversion for Anthropic (currently not supported)
*/
convertFunctions(functions) {
// Anthropic doesn't have native function calling yet
// We could potentially convert to tool use format in the future
return [];
}
}
exports.AnthropicProvider = AnthropicProvider;
/**
* Anthropic Provider Factory
*/
class AnthropicProviderFactory {
static createProvider(config) {
return new AnthropicProvider(config);
}
static validateConfig(config) {
if (!config.apiKey && !process.env.ANTHROPIC_API_KEY) {
return false;
}
return true;
}
static getProviderType() {
return 'anthropic';
}
}
exports.AnthropicProviderFactory = AnthropicProviderFactory;
//# sourceMappingURL=anthropic-provider.js.map