llmforge
Version:
One API, every AI model, instant switching. Change from GPT-4 to Gemini to local models with a single config update. LLMForge is the lightweight, TypeScript-first solution for multi-provider AI applications with zero vendor lock-in.
411 lines • 15.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAIClient = void 0;
const http_client_1 = require("../core/http-client");
const expo_1 = require("../strategies/retry/expo");
const ai_builder_1 = require("../builder/ai.builder");
const openai_stream_1 = require("../strategies/stream/openai.stream");
class OpenAIClient {
constructor(config) {
var _a;
this.retryHandler = new expo_1.RetryHandler({
maxRetries: config.maxRetries,
retryDelay: config.retryDelay,
});
this.httpClient = new http_client_1.HttpClient(config, (_a = config.baseUrl) !== null && _a !== void 0 ? _a : 'https://api.openai.com', this.retryHandler);
this.streamProcessor = new openai_stream_1.OpenAIStreamProcessor();
}
convertReadableToResponse(readable) {
return new Response(readable, {
headers: new Headers(),
status: 200,
statusText: 'OK'
});
}
convertToOpenAIRequest(request) {
var _a;
const openAIRequest = {
model: this.httpClient.getConfig().model,
messages: this.convertContentsToMessages(request.contents),
};
// Handle generation config
if (request.generationConfig) {
const config = request.generationConfig;
if (config.temperature !== undefined)
openAIRequest.temperature = config.temperature;
if (config.maxOutputTokens !== undefined)
openAIRequest.max_tokens = config.maxOutputTokens;
if (config.topP !== undefined)
openAIRequest.top_p = config.topP;
if (config.stopSequences !== undefined)
openAIRequest.stop = config.stopSequences;
// Handle thinking/reasoning config
if (((_a = config.thinkingConfig) === null || _a === void 0 ? void 0 : _a.thinkingBudget) !== undefined) {
const budget = config.thinkingConfig.thinkingBudget;
openAIRequest.reasoning = {
effort: budget === -1 ? 'medium' : budget > 1000 ? 'high' : 'low'
};
}
// Handle JSON schema format
if (config.responseMimeType === 'application/json') {
openAIRequest.text = {
format: {
type: 'json_schema',
name: 'response_format',
strict: true,
schema: {
type: 'object',
properties: {
content: { type: 'string' }
},
required: ['content'],
additionalProperties: false
}
}
};
}
}
// Handle tools
if (request.tools && request.tools.length > 0) {
openAIRequest.tools = this.convertToolsToOpenAI(request.tools);
}
// Handle system instruction
if (request.systemInstruction) {
const systemMessage = {
role: 'system',
content: request.systemInstruction.parts.map((part) => part.text).join('\n')
};
if (openAIRequest.messages) {
openAIRequest.messages.unshift(systemMessage);
}
else {
openAIRequest.messages = [systemMessage];
}
}
return openAIRequest;
}
convertContentsToMessages(contents) {
return contents.map(content => {
const role = content.role === 'model' ? 'assistant' : (content.role || 'user');
const message = {
role: role,
content: content.parts.map((part) => {
if ('text' in part) {
return part.text;
}
// Handle other part types if needed
return '';
}).join('\n')
};
return message;
});
}
convertToolsToOpenAI(tools) {
return tools.flatMap(tool => {
if (tool.functionDeclarations) {
return tool.functionDeclarations.map((func) => ({
type: 'function',
function: {
name: func.name,
description: func.description,
parameters: func.parameters
}
}));
}
return [];
});
}
// Convert OpenAI response to Gemini format
convertFromOpenAIResponse(response) {
const candidates = response.choices.map((choice) => ({
content: {
role: 'model',
parts: [{ text: choice.message.content }]
},
finishReason: choice.finish_reason,
index: choice.index
}));
return {
candidates,
usageMetadata: response.usage ? {
promptTokenCount: response.usage.prompt_tokens,
candidatesTokenCount: response.usage.completion_tokens,
totalTokenCount: response.usage.total_tokens
} : undefined
};
}
async generateContent(request) {
ai_builder_1.MessageValidator.validateContents(request.contents);
const openAIRequest = this.convertToOpenAIRequest(request);
// Determine endpoint based on model and features
let endpoint = '/v1/chat/completions';
if (openAIRequest.reasoning) {
endpoint = '/v1/responses'; // For reasoning models
openAIRequest.input = openAIRequest.messages;
delete openAIRequest.messages;
}
const response = await this.httpClient.request(endpoint, {
method: 'POST',
body: JSON.stringify(openAIRequest),
headers: {
'Authorization': `Bearer ${this.httpClient.getConfig().apiKey}`,
'Content-Type': 'application/json'
}
});
return this.convertFromOpenAIResponse(response);
}
async generateContentStream(request, options) {
ai_builder_1.MessageValidator.validateContents(request.contents);
const openAIRequest = { ...this.convertToOpenAIRequest(request), stream: true };
let endpoint = '/v1/chat/completions';
if (openAIRequest.reasoning) {
endpoint = '/v1/responses';
openAIRequest.input = openAIRequest.messages;
delete openAIRequest.messages;
}
const response = await this.httpClient.streamRequest(endpoint, {
method: 'POST',
body: JSON.stringify(openAIRequest),
headers: {
'Authorization': `Bearer ${this.httpClient.getConfig().apiKey}`,
'Content-Type': 'application/json'
}
});
return this.streamProcessor.processStream(this.convertReadableToResponse(response), options);
}
async *generateContentStreamAsync(request) {
ai_builder_1.MessageValidator.validateContents(request.contents);
const openAIRequest = { ...this.convertToOpenAIRequest(request), stream: true };
let endpoint = '/v1/chat/completions';
if (openAIRequest.reasoning) {
endpoint = '/v1/responses';
openAIRequest.input = openAIRequest.messages;
delete openAIRequest.messages;
}
const response = await this.httpClient.streamRequest(endpoint, {
method: 'POST',
body: JSON.stringify(openAIRequest),
headers: {
'Authorization': `Bearer ${this.httpClient.getConfig().apiKey}`,
'Content-Type': 'application/json'
}
});
yield* this.streamProcessor.createAsyncGenerator(this.convertReadableToResponse(response));
}
async chat(message, options = {}) {
const contents = ai_builder_1.ContentBuilder.textOnly(message);
const request = {
contents,
generationConfig: options.generationConfig,
tools: options.tools,
};
if (options.systemInstruction) {
request.systemInstruction = {
parts: [{ text: options.systemInstruction }],
};
}
return this.generateContent(request);
}
async chatStream(message, options = {}) {
const contents = ai_builder_1.ContentBuilder.textOnly(message);
const request = {
contents,
generationConfig: options.generationConfig,
tools: options.tools,
};
if (options.systemInstruction) {
request.systemInstruction = {
parts: [{ text: options.systemInstruction }],
};
}
return this.generateContentStream(request, options.streamOptions);
}
async continueConversation(contents, options = {}) {
const request = {
contents,
generationConfig: options.generationConfig,
tools: options.tools,
};
return this.generateContent(request);
}
async analyzeImage(base64Image, mimeType, prompt, options = {}) {
const contents = ai_builder_1.ContentBuilder.create()
.addImageFromBase64(base64Image, mimeType, prompt)
.build();
return this.generateContent({
contents,
generationConfig: options.generationConfig,
});
}
async analyzeDocument(base64Document, mimeType, prompt, options = {}) {
const request = {
contents: ai_builder_1.ContentBuilder.create()
.addDocumentFromBase64(base64Document, mimeType, prompt)
.build(),
generationConfig: options.generationConfig,
};
return this.generateContent(request);
}
async callFunction(message, tools, options = {}) {
const request = {
contents: ai_builder_1.ContentBuilder.textOnly(message),
tools,
generationConfig: options.generationConfig,
};
if (options.systemInstruction) {
request.systemInstruction = {
parts: [{ text: options.systemInstruction }],
};
}
return this.generateContent(request);
}
// OpenAI-specific thinking/reasoning mode
async generateWithThinking(message, thinkingBudget = -1, options = {}) {
const request = {
contents: ai_builder_1.ContentBuilder.textOnly(message),
generationConfig: {
thinkingConfig: {
thinkingBudget,
},
},
};
// Override effort if provided
if (options.effort) {
request.generationConfig.thinkingConfig = {
thinkingBudget: options.effort === 'high' ? 2000 : options.effort === 'low' ? 100 : 1000
};
}
// Add JSON schema if provided
if (options.jsonSchema) {
request.generationConfig.responseMimeType = 'application/json';
}
if (options.systemInstruction) {
request.systemInstruction = {
parts: [{ text: options.systemInstruction }],
};
}
return this.generateContentStream(request, {
onThinking: options.onThinking,
});
}
// OpenAI-specific method for handling the exact curl request format
async handleOpenAIRequest(openAIRequest) {
let endpoint = '/v1/chat/completions';
// Handle reasoning endpoint
if (openAIRequest.reasoning || openAIRequest.input) {
endpoint = '/v1/responses';
}
return this.httpClient.request(endpoint, {
method: 'POST',
body: JSON.stringify(openAIRequest),
headers: {
'Authorization': `Bearer ${this.httpClient.getConfig().apiKey}`,
'Content-Type': 'application/json'
}
});
}
get stream() {
return this.streamProcessor;
}
updateConfig(newConfig) {
this.httpClient.updateConfig(newConfig);
}
getConfig() {
return this.httpClient.getConfig();
}
}
exports.OpenAIClient = OpenAIClient;
// // Usage example for the exact curl request:
// export class OpenAIRequestHandler {
// private client: OpenAIClient;
// constructor(config: Config) {
// this.client = new OpenAIClient(config);
// }
// async handleCurlRequest(): Promise<OpenAIResponse> {
// const request: OpenAIRequest = {
// model: "o4-mini",
// input: [
// {
// role: "user",
// content: [
// {
// type: "input_text",
// text: "hii there"
// }
// ]
// },
// {
// role: "assistant",
// content: [
// {
// type: "output_text",
// text: "Hello! How can I help you today?"
// }
// ]
// },
// {
// role: "user",
// content: [
// {
// type: "input_text",
// text: "what can you help with"
// }
// ]
// },
// {
// role: "assistant",
// content: [
// {
// type: "output_text",
// text: "I can help with a wide range of tasks..."
// }
// ]
// },
// {
// role: "user",
// content: [
// {
// type: "input_text",
// text: "hii there"
// }
// ]
// }
// ],
// text: {
// format: {
// type: "json_schema",
// name: "thinking_output",
// strict: true,
// schema: {
// type: "object",
// properties: {
// thinking: {
// type: "string",
// description: "Description of the thought process or reasoning."
// },
// final_output: {
// type: "string",
// description: "Final result or conclusion derived from the thinking process."
// }
// },
// required: ["thinking", "final_output"],
// additionalProperties: false
// }
// }
// },
// reasoning: {
// effort: "medium"
// },
// tools: [],
// store: true
// };
// return this.client.handleOpenAIRequest(request);
// }
// }
// export * from '../types';
// export * from '../strategies/retry/expo';
// export * from '../core/http-client';
// export * from '../builder/ai.builder';
// export * from '../strategies/stream/openai.stream';
// export { OpenAIRequest, OpenAIResponse, OpenAIMessage } from './openai-types';
//# sourceMappingURL=openai.adapter.js.map