@ubiquity-os/plugin-sdk
Version:
SDK for plugin support.
170 lines (168 loc) • 5.96 kB
JavaScript
// src/helpers/openrouter.ts
async function getOpenRouterModels() {
const response = await fetch("https://openrouter.ai/api/v1/models");
if (!response.ok) {
throw new Error("Failed to fetch models");
}
const result = await response.json();
return result.data;
}
async function getOpenRouterModelTokenLimits(modelId) {
const models = await getOpenRouterModels();
const model = models.find((m) => m.id === modelId);
if (!model) {
return null;
}
return {
contextLength: model.context_length,
maxCompletionTokens: model.top_provider.max_completion_tokens || model.context_length
};
}
// src/helpers/retry.ts
import { TypeBoxError } from "@sinclair/typebox";
import { LogReturn } from "@ubiquity-os/ubiquity-os-logger";
import ms from "ms";
function sleep(ms2) {
return new Promise((resolve) => setTimeout(resolve, ms2));
}
async function handleRetryError(err, delay, options, isLastLoop) {
const lastError = err;
if (options.onError) {
await options.onError(err);
}
let shouldRetry;
if (options.isErrorRetryable) {
shouldRetry = await options.isErrorRetryable(err);
}
if (shouldRetry === false || isLastLoop) {
throw lastError;
}
if (typeof shouldRetry === "number" && Number.isFinite(shouldRetry)) {
await sleep(shouldRetry);
} else {
await sleep(delay);
delay *= 2;
}
return { delay, lastError };
}
async function retry(fn, options) {
let delay = 1e3;
let lastError = null;
for (let i = 0; i < options.maxRetries + 1; i++) {
try {
return await fn();
} catch (err) {
const result = await handleRetryError(err, delay, options, i >= options.maxRetries);
delay = result.delay;
lastError = result.lastError;
}
}
throw lastError;
}
function checkLlmRetryableState(error) {
if (error instanceof SyntaxError || error instanceof TypeBoxError || error instanceof LogReturn) {
return true;
}
const status = extractStatus(error) ?? extractStatusFromMessage(error);
if (!status) return false;
if (status === 429) {
const headers = extractHeaders(error);
const tokenDelay = parseRetryAfter(headers, "x-ratelimit-reset-tokens");
const requestDelay = parseRetryAfter(headers, "x-ratelimit-reset-requests");
const retryAfter = parseRetryAfter(headers, "retry-after");
const delays = [tokenDelay, requestDelay, retryAfter].filter((value) => Number.isFinite(value));
if (!delays.length) return true;
return Math.max(...delays);
}
return status >= 500;
}
function extractStatus(error) {
const direct = getStatusFromSource(error);
if (direct !== null) return direct;
if (typeof error !== "object" || error === null || !("cause" in error)) return null;
return getStatusFromSource(error.cause);
}
function getStatusFromSource(source) {
if (typeof source !== "object" || source === null) return null;
const maybeError = source;
const candidates = [maybeError.status, maybeError.statusCode, maybeError.response?.status];
for (const candidate of candidates) {
if (typeof candidate === "number") return candidate;
if (typeof candidate === "string" && candidate.trim()) {
const parsed = Number.parseInt(candidate, 10);
if (Number.isFinite(parsed)) return parsed;
}
}
return null;
}
function extractStatusFromMessage(error) {
const message = extractMessage(error);
if (!message) return null;
const match = STATUS_FROM_MESSAGE_REGEX.exec(message);
if (!match) return null;
const status = Number(match[1]);
return Number.isFinite(status) ? status : null;
}
function extractMessage(error) {
if (typeof error === "string") return error;
if (typeof error !== "object" || error === null || !("message" in error)) return void 0;
const maybeMessage = error.message;
return typeof maybeMessage === "string" ? maybeMessage : void 0;
}
function extractHeaders(error) {
if (typeof error !== "object" || error === null) return void 0;
const maybeError = error;
if (maybeError.headers) return maybeError.headers;
if (maybeError.response?.headers) return maybeError.response.headers;
const maybeCause = maybeError.cause;
if (typeof maybeCause === "object" && maybeCause !== null) {
const cause = maybeCause;
if (cause.headers) return cause.headers;
if (cause.response?.headers) return cause.response.headers;
}
return void 0;
}
function parseRetryAfter(headers, headerName) {
const value = getHeaderValue(headers, headerName);
if (!value) return null;
const delay = parseDelayMs(value, headerName.toLowerCase() === "retry-after");
return Number.isFinite(delay) ? delay : null;
}
function getHeaderValue(headers, headerName) {
if (!headers || typeof headers !== "object") return void 0;
const maybeHeaders = headers;
if (typeof maybeHeaders.get === "function") {
const value = maybeHeaders.get(headerName);
return typeof value === "string" ? value : void 0;
}
const direct = maybeHeaders[headerName] ?? maybeHeaders[headerName.toLowerCase()] ?? maybeHeaders[headerName.toUpperCase()];
return typeof direct === "string" ? direct : void 0;
}
var STATUS_FROM_MESSAGE_REGEX = /\b(?:status(?: code)?|http|error)\b\D*(\d{3})(?!\d)/i;
function parseDelayMs(value, numericIsSeconds) {
const trimmed = value.trim();
if (!trimmed) return null;
if (isNumericString(trimmed)) {
const numeric = Number(trimmed);
if (!Number.isFinite(numeric)) return null;
return numericIsSeconds ? numeric * 1e3 : numeric;
}
const duration = ms(trimmed);
if (Number.isFinite(duration)) return duration;
return parseDateDelay(trimmed);
}
function parseDateDelay(value) {
const parsedDate = Date.parse(value);
if (Number.isNaN(parsedDate)) return null;
const delay = parsedDate - Date.now();
return delay > 0 ? delay : 0;
}
function isNumericString(value) {
return /^\d+(?:\.\d+)?$/.test(value);
}
export {
checkLlmRetryableState,
getOpenRouterModelTokenLimits,
getOpenRouterModels,
retry
};