UNPKG

aiwrapper

Version:

A Universal AI Wrapper for JavaScript & TypeScript

170 lines 7.2 kB
import { LangResultWithMessages, LanguageProvider, } from "../language-provider.js"; import { httpRequestWithRetry as fetch, } from "../../http-request.js"; import { processResponseStream } from "../../process-response-stream.js"; import { models } from 'aimodels'; import { calculateModelResponseTokens } from "../utils/token-calculator.js"; export class OpenAILikeLang extends LanguageProvider { constructor(config) { super(config.model); // Get model info from aimodels - it's optional now const modelInfo = models.id(config.model); this.modelInfo = modelInfo; // can be undefined this._config = config; } /** * Creates an instance of OpenAILikeLang for custom OpenAI-compatible APIs * @param options Configuration options for the custom API * @returns A new OpenAILikeLang instance */ static custom(options) { return new OpenAILikeLang({ apiKey: options.apiKey, model: options.model, systemPrompt: options.systemPrompt || "", maxTokens: options.maxTokens, maxCompletionTokens: options.maxCompletionTokens, baseURL: options.baseURL, headers: options.headers, bodyProperties: options.bodyProperties, reasoningEffort: options.reasoningEffort, }); } async ask(prompt, onResult) { const messages = []; if (this._config.systemPrompt) { messages.push({ role: "system", content: this._config.systemPrompt, }); } messages.push({ role: "user", content: prompt, }); return await this.chat(messages, onResult); } transformMessages(messages) { // By default, no transformation return messages; } transformBody(body) { const transformedBody = Object.assign({}, body); // Add reasoning_effort if specified and we're using a reasoning model if (this._config.reasoningEffort && this.supportsReasoning()) { transformedBody.reasoning_effort = this._config.reasoningEffort; } // Add max_completion_tokens if specified (for reasoning models) if (this._config.maxCompletionTokens !== undefined && this.supportsReasoning()) { transformedBody.max_completion_tokens = this._config.maxCompletionTokens; } return transformedBody; } /** * Checks if the current model has reasoning capabilities * @returns True if the model supports reasoning, false otherwise */ supportsReasoning() { if (this.modelInfo) { return this.modelInfo.canReason(); } return false; } async chat(messages, onResult) { const result = new LangResultWithMessages(messages); const transformedMessages = this.transformMessages(messages); // Calculate max tokens for the request, using model info if available const requestMaxTokens = this.modelInfo ? calculateModelResponseTokens(this.modelInfo, transformedMessages, this._config.maxTokens) : this._config.maxTokens || 4000; // Default if no model info or maxTokens // For reasoning models, ensure there's enough space for reasoning // if maxCompletionTokens is not explicitly set if (this.supportsReasoning() && this._config.maxCompletionTokens === undefined) { this._config.maxCompletionTokens = Math.max(requestMaxTokens, 25000); } const onData = (data) => { this.handleStreamData(data, result, messages, onResult); }; const body = this.transformBody(Object.assign({ model: this._config.model, messages: transformedMessages, stream: true, max_tokens: requestMaxTokens }, this._config.bodyProperties)); const response = await fetch(`${this._config.baseURL}/chat/completions`, { method: "POST", headers: Object.assign(Object.assign({ "Content-Type": "application/json" }, (this._config.apiKey ? { "Authorization": `Bearer ${this._config.apiKey}` } : {})), this._config.headers), body: JSON.stringify(body), onNotOkResponse: async (res, decision) => { if (res.status === 401) { decision.retry = false; throw new Error("Authentication failed. Please check your credentials and try again."); } if (res.status === 400) { const data = await res.text(); decision.retry = false; throw new Error(data); } return decision; }, }).catch((err) => { throw new Error(err); }); await processResponseStream(response, onData); return result; } /** * Handles streaming data from the API response * This method can be overridden by subclasses to add custom handling for different response formats * @param data The current data chunk from the stream * @param result The result object being built * @param messages The original messages array * @param onResult Optional callback for streaming results */ handleStreamData(data, result, messages, onResult) { if (data.finished) { result.finished = true; onResult === null || onResult === void 0 ? void 0 : onResult(result); return; } if (data.choices !== undefined) { const deltaContent = data.choices[0].delta.content ? data.choices[0].delta.content : ""; result.answer += deltaContent; result.messages = [...messages, { role: "assistant", content: result.answer, }]; onResult === null || onResult === void 0 ? void 0 : onResult(result); } } /** * Sets the reasoning effort level for the model * @param effort The reasoning effort level: "low", "medium", or "high" * @returns this instance for method chaining */ setReasoningEffort(effort) { this._config.reasoningEffort = effort; return this; } /** * Gets the current reasoning effort level * @returns The current reasoning effort level or undefined if not set */ getReasoningEffort() { return this._config.reasoningEffort; } /** * Sets the maximum number of tokens (including reasoning tokens) that can be generated * This is specific to reasoning models and controls the total token output * @param maxTokens The maximum number of tokens to generate * @returns this instance for method chaining */ setMaxCompletionTokens(maxTokens) { this._config.maxCompletionTokens = maxTokens; return this; } /** * Gets the current maximum completion tokens setting * @returns The current maximum completion tokens or undefined if not set */ getMaxCompletionTokens() { return this._config.maxCompletionTokens; } } //# sourceMappingURL=openai-like-lang.js.map