anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
543 lines • 18.3 kB
JavaScript
"use strict";
/**
* OpenAI MCP Provider Implementation
*
* Provider implementation for OpenAI LLM services
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIProviderFactory = exports.OpenAIProvider = void 0;
const types_1 = require("../types");
const base_provider_1 = require("./base-provider");
/**
* OpenAI MCP Provider
*/
class OpenAIProvider extends base_provider_1.BaseLLMProvider {
constructor(config) {
super(config);
this.apiKey = config.apiKey || process.env.OPENAI_API_KEY || '';
this.baseURL = config.endpoint || 'https://api.openai.com/v1';
this.organization = config.customHeaders?.['OpenAI-Organization'];
// Set OpenAI-specific capabilities
this.capabilities = {
completion: true,
streaming: true,
functionCalling: true,
embeddings: true,
moderation: true,
multimodal: true,
codeGeneration: true,
jsonMode: true
};
// Set OpenAI models
this.models = [
{
id: 'gpt-4',
name: 'GPT-4',
description: 'Most capable GPT-4 model',
capabilities: ['completion', 'function_calling', 'code_generation'],
contextLength: 8192,
inputCost: 0.03,
outputCost: 0.06,
deprecated: false
},
{
id: 'gpt-4-turbo-preview',
name: 'GPT-4 Turbo',
description: 'Latest GPT-4 Turbo model',
capabilities: ['completion', 'function_calling', 'json_mode'],
contextLength: 128000,
inputCost: 0.01,
outputCost: 0.03,
deprecated: false
},
{
id: 'gpt-3.5-turbo',
name: 'GPT-3.5 Turbo',
description: 'Fast and efficient model',
capabilities: ['completion', 'function_calling'],
contextLength: 4096,
inputCost: 0.0005,
outputCost: 0.0015,
deprecated: false
},
{
id: 'text-embedding-ada-002',
name: 'Text Embedding Ada 002',
description: 'Text embedding model',
capabilities: ['embeddings'],
contextLength: 8191,
inputCost: 0.0001,
outputCost: 0,
deprecated: false
}
];
}
/**
* Initialize the provider
*/
async initialize() {
if (!this.apiKey) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.INVALID_CONFIG,
message: 'OpenAI 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 OpenAI provider: ${error.message}`,
timestamp: new Date(),
provider: this.id,
retryable: true
});
}
}
/**
* Test connection to OpenAI API
*/
async testConnection() {
const response = await fetch(`${this.baseURL}/models`, {
headers: this.getHeaders()
});
if (!response.ok) {
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
}
};
}
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.buildCompletionRequest(request, model);
try {
const response = await fetch(`${this.baseURL}/chat/completions`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw await this.handleAPIError(response);
}
const data = await response.json();
return this.convertCompletionResponse(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: `OpenAI 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.buildCompletionRequest(request, model, true);
try {
const response = await fetch(`${this.baseURL}/chat/completions`, {
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 chunk = JSON.parse(data);
const responseChunk = this.convertStreamChunk(request, chunk);
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: `OpenAI streaming error: ${error.message}`,
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: true
});
}
}
/**
* Generate embeddings
*/
async embed(request, context) {
const model = request.parameters?.model || 'text-embedding-ada-002';
const requestBody = {
input: request.prompt,
model: model
};
try {
const response = await fetch(`${this.baseURL}/embeddings`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw await this.handleAPIError(response);
}
const data = await response.json();
return this.convertEmbeddingResponse(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: `OpenAI embedding error: ${error.message}`,
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: true
});
}
}
/**
* Moderate content
*/
async moderate(request, context) {
const requestBody = {
input: request.prompt
};
try {
const response = await fetch(`${this.baseURL}/moderations`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw await this.handleAPIError(response);
}
const data = await response.json();
return this.convertModerationResponse(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: `OpenAI moderation error: ${error.message}`,
timestamp: new Date(),
provider: this.id,
requestId: request.id,
retryable: true
});
}
}
/**
* Build completion request body
*/
buildCompletionRequest(request, model, stream = false) {
const messages = this.buildMessages(request);
const functions = request.functions ? this.convertFunctions(request.functions) : undefined;
const requestBody = {
model,
messages,
stream,
max_tokens: request.parameters?.maxTokens,
temperature: request.parameters?.temperature,
top_p: request.parameters?.topP,
frequency_penalty: request.parameters?.frequencyPenalty,
presence_penalty: request.parameters?.presencePenalty,
stop: request.parameters?.stop
};
// Add function calling support
if (functions && functions.length > 0) {
requestBody.functions = functions;
if (request.type === types_1.LLMRequestType.FUNCTION_CALL) {
requestBody.function_call = 'auto';
}
}
// Add JSON mode support
if (request.parameters?.responseFormat?.type === 'json_object') {
requestBody.response_format = { type: 'json_object' };
}
// Remove undefined values
Object.keys(requestBody).forEach(key => {
if (requestBody[key] === undefined) {
delete requestBody[key];
}
});
return requestBody;
}
/**
* Build messages array for OpenAI API
*/
buildMessages(request) {
const messages = [];
// Add context messages if available
if (request.context?.history) {
for (const msg of request.context.history) {
messages.push({
role: msg.role === 'function' ? 'assistant' : msg.role,
content: msg.content
});
}
}
// Add current prompt
messages.push({
role: 'user',
content: request.prompt
});
return messages;
}
/**
* Convert function definitions to OpenAI format
*/
convertFunctions(functions) {
return functions.map(fn => ({
name: fn.name,
description: fn.description,
parameters: fn.parameters
}));
}
/**
* Convert OpenAI completion response to standard format
*/
convertCompletionResponse(request, context, data) {
const choice = data.choices[0];
const baseResponse = this.createBaseResponse(request, context);
let functionCall;
if (choice.message.function_call) {
functionCall = {
name: choice.message.function_call.name,
arguments: JSON.parse(choice.message.function_call.arguments || '{}'),
id: `call-${Date.now()}`
};
}
const usage = {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens,
model: data.model,
provider: this.id,
cost: this.calculateCost(data.usage.prompt_tokens, data.usage.completion_tokens, data.model)
};
return {
...baseResponse,
content: choice.message.content,
functionCall,
usage,
model: data.model
};
}
/**
* Convert OpenAI stream chunk to standard format
*/
convertStreamChunk(request, chunk) {
const choice = chunk.choices[0];
if (!choice?.delta?.content) {
return null;
}
return {
id: chunk.id,
requestId: request.id,
delta: choice.delta.content,
finished: choice.finish_reason === 'stop'
};
}
/**
* Convert OpenAI embedding response to standard format
*/
convertEmbeddingResponse(request, context, data) {
const baseResponse = this.createBaseResponse(request, context);
const usage = {
promptTokens: data.usage.prompt_tokens,
completionTokens: 0,
totalTokens: data.usage.total_tokens,
model: data.model,
provider: this.id,
cost: this.calculateCost(data.usage.prompt_tokens, 0, data.model)
};
return {
...baseResponse,
embedding: data.data[0]?.embedding,
usage,
model: data.model
};
}
/**
* Convert OpenAI moderation response to standard format
*/
convertModerationResponse(request, context, data) {
const baseResponse = this.createBaseResponse(request, context);
const result = data.results[0];
return {
...baseResponse,
moderationResult: {
flagged: result.flagged,
categories: result.categories,
categoryScores: result.category_scores
},
model: data.model
};
}
/**
* Get HTTP headers for OpenAI API
*/
getHeaders() {
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'User-Agent': 'anon-identity-mcp/1.0.0'
};
if (this.organization) {
headers['OpenAI-Organization'] = this.organization;
}
// Add custom headers from config
if (this.config.customHeaders) {
Object.assign(headers, this.config.customHeaders);
}
return headers;
}
/**
* Handle OpenAI API errors
*/
async handleAPIError(response) {
let errorData;
try {
errorData = await response.json();
}
catch {
errorData = { error: { message: response.statusText } };
}
const errorMessage = errorData.error?.message || 'Unknown OpenAI API error';
let errorCode = types_1.MCPErrorCode.PROVIDER_ERROR;
switch (response.status) {
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
}
});
}
}
exports.OpenAIProvider = OpenAIProvider;
/**
* OpenAI Provider Factory
*/
class OpenAIProviderFactory {
static createProvider(config) {
return new OpenAIProvider(config);
}
static validateConfig(config) {
if (!config.apiKey && !process.env.OPENAI_API_KEY) {
return false;
}
return true;
}
static getProviderType() {
return 'openai';
}
}
exports.OpenAIProviderFactory = OpenAIProviderFactory;
//# sourceMappingURL=openai-provider.js.map