aiwrapper
Version:
A Universal AI Wrapper for JavaScript & TypeScript
170 lines • 7.2 kB
JavaScript
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