UNPKG

@ubiquity-os/plugin-sdk

Version:

SDK for plugin support.

170 lines (168 loc) 5.96 kB
// 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 };