openrouter-client
Version:
An API wrapper for OpenRouter
230 lines (227 loc) • 7.43 kB
JavaScript
// src/index.ts
import { EventEmitter } from "events";
// src/types.ts
var types_exports = {};
// src/index.ts
var errorCodesAndMesssages = {
400: "Bad Request (invalid or missing params, CORS)",
401: "Invalid credentials (OAuth session expired, disabled/invalid API key)",
402: "Your account or API key has insufficient credits. Add more credits and retry the request.",
408: "Your request timed out",
429: "You are being rate limited",
502: "Your chosen model is down or we received an invalid response from it",
503: "There is no available model provider that meets your routing requirements"
};
var OpenRouter = class {
apiKey;
globalConfig;
constructor(apiKey, globalConfig) {
this.apiKey = apiKey;
this.globalConfig = globalConfig || {};
}
async chat(messages, config, signal) {
config = config || this.globalConfig;
const extraHeaders = {};
if (config.httpReferer) {
extraHeaders["HTTP-Referer"] = config.httpReferer;
}
if (config.xTitle) {
extraHeaders["X-Title"] = config.xTitle;
}
let request;
try {
request = await fetch(
"https://openrouter.ai/api/v1/chat/completions",
{
method: "POST",
signal,
headers: {
...extraHeaders,
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ messages, ...config })
}
);
} catch (e) {
if (typeof e == "object" && e !== null) {
if ("name" in e) {
if (e.name == "AbortError") {
return { success: false, error: "AbortError" };
}
}
}
return { success: false, error: e };
}
const response = await request.json();
if ("error" in response) {
return {
success: false,
errorCode: response.error.status,
errorMessage: response.error.message,
metadata: response.error.metadata
};
}
return { success: true, data: response };
}
async getGenerationStats(id) {
const request = await fetch(
`https://openrouter.ai/api/v1/generation?id=${id}`
);
return await request.json();
}
};
var OpenRouterStream = class extends EventEmitter {
apiKey;
globalConfig;
constructor(apiKey, globalConfig) {
super();
this.apiKey = apiKey;
this.globalConfig = globalConfig || {};
}
/** Sends back chunks. First message sent back might be "hello" and the second chunk might be " world" */
async chatStreamChunk(messages, config) {
config = config || this.globalConfig;
const extraHeaders = {};
if (config.httpReferer) {
extraHeaders["HTTP-Referer"] = config.httpReferer;
}
if (config.xTitle) {
extraHeaders["X-Title"] = config.xTitle;
}
const request = await fetch(
"https://openrouter.ai/api/v1/chat/completions",
{
method: "POST",
headers: {
...extraHeaders,
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ messages, ...config, stream: true })
}
);
if (!request.ok || request.body === null) {
const errorText = await request.text();
this.emit("error", new Error(`HTTP error ${request.status}: ${errorText}`));
return;
}
const reader = request.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
buffer += decoder.decode(value, { stream: true });
const splitData = buffer.split("\n").filter((item) => item !== "");
for (let i = 0; i < splitData.length; i++) {
const data = splitData[i];
if (data === ": OPENROUTER PROCESSING") {
buffer = buffer.slice(data.length + 1);
continue;
}
if (data.includes("[DONE]")) {
this.emit("end");
return;
}
let parsedData;
try {
parsedData = JSON.parse(data.split("data: ")[1]);
buffer = buffer.slice(data.length + 1);
} catch (e) {
continue;
}
this.emit("data", parsedData);
}
}
}
//** This function passes back the entire object. So the first message might be { content: "hello" } and the second message might be { content: "hello world" }. This differs from `chatStreamChunk`, which only sends the new token that was generated instead of the whole object. */
async chatStreamWhole(messages, config) {
config = config || this.globalConfig;
const extraHeaders = {};
if (config.httpReferer) {
extraHeaders["HTTP-Referer"] = config.httpReferer;
}
if (config.xTitle) {
extraHeaders["X-Title"] = config.xTitle;
}
const request = await fetch(
"https://openrouter.ai/api/v1/chat/completions",
{
method: "POST",
headers: {
...extraHeaders,
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ messages, ...config, stream: true })
}
);
if (!request.ok || request.body === null) {
const errorText = await request.text();
this.emit("error", new Error(`HTTP error ${request.status}: ${errorText}`));
return;
}
const reader = request.body.getReader();
const decoder = new TextDecoder("utf-8");
let whole = {
id: "",
provider: "",
model: "",
object: "",
created: 0,
finish_reason: "",
//@ts-ignore
choices: messages
};
whole.choices.push({ role: "user", content: "", reasoning: null });
let buffer = "";
while (true) {
const { done, value } = await reader.read();
buffer += decoder.decode(value, { stream: true });
const splitData = buffer.split("\n").filter((item) => item !== "");
for (let i = 0; i < splitData.length; i++) {
const data = splitData[i];
if (data === ": OPENROUTER PROCESSING") {
buffer = buffer.slice(data.length + 1);
continue;
}
if (data.includes("[DONE]")) {
this.emit("end");
return;
}
let parsedData;
try {
parsedData = JSON.parse(data.split("data: ")[1]);
buffer = buffer.slice(data.length + 1);
} catch (e) {
continue;
}
if ("usage" in parsedData) {
whole.usage = parsedData.usage;
} else {
whole.id = parsedData.id;
whole.provider = parsedData.provider;
whole.model = parsedData.model;
whole.object = parsedData.object;
whole.created = parsedData.created;
whole.finish_reason = parsedData.choices[0].finish_reason;
}
whole.choices.at(-1).role = parsedData.choices[0].delta.role;
whole.choices.at(-1).content += parsedData.choices[0].delta.content;
if (whole.choices.at(-1).reasoning == null && parsedData.choices[0].delta.reasoning) {
whole.choices.at(-1).reasoning = "";
}
if (parsedData.choices[0].delta.reasoning) {
whole.choices.at(-1).reasoning += parsedData.choices[0].delta.reasoning;
}
this.emit("data", whole);
}
}
}
};
export {
OpenRouter,
OpenRouterStream,
errorCodesAndMesssages,
types_exports as types
};