mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 10:12:11 +03:00
Compare commits
2 Commits
release/v3
...
fix/releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8dfccf1c8 | ||
|
|
1dc2e4fffd |
@@ -1,7 +1,3 @@
|
||||
feat(images): execute full combo strategy + fallback in /v1/images/generations (#9239)
|
||||
- feat(images): execute full combo strategy + fallback in /v1/images/generations (#9239)
|
||||
|
||||
Add open-sse/services/imageCombo.ts that expands combo targets, filters
|
||||
to images-capable, executes priority strategy with handleImageGeneration
|
||||
per target, and returns first success or last failure. Route patches
|
||||
detect combo names before model resolution and divert to the new
|
||||
execution path.
|
||||
Adds open-sse/services/imageCombo.ts that expands combo targets, filters to images-capable, executes the priority strategy with handleImageGeneration per target, and returns the first success or last failure. Route patches detect combo names before model resolution and divert to the new execution path.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
---
|
||||
feature: 9490
|
||||
---
|
||||
- feat(opencode-plugin): warm catalog startup from disk snapshot + parallel refresh (#9490)
|
||||
|
||||
**Warm catalog startup from disk snapshot + parallel refresh** (opencode-plugin): The config-shim hook now reads the last disk snapshot *before* fetching, so the provider registers immediately with the last-known-good catalog (~1-2s vs ~30s on a warm gateway). All six fetchers run concurrently via `Promise.allSettled` instead of sequentially. A failed refresh keeps the snapshot (no overwrite). An in-flight guard prevents concurrent refreshes for the same cache key. The `features.diskCache: false` opt-out disables the warm read entirely.
|
||||
The config-shim hook now reads the last disk snapshot before fetching, so the provider registers immediately with the last-known-good catalog (~1-2s vs ~30s on a warm gateway). All six fetchers run concurrently via Promise.allSettled instead of sequentially. A failed refresh keeps the snapshot (no overwrite). An in-flight guard prevents concurrent refreshes for the same cache key. The features.diskCache: false opt-out disables the warm read entirely.
|
||||
|
||||
@@ -233,32 +233,6 @@ import { hyperagentProvider } from "./registry/hyperagent/index.ts";
|
||||
import { muse_codeProvider } from "./registry/muse-code/index.ts";
|
||||
import { naga_acProvider } from "./registry/naga-ac/index.ts";
|
||||
import { chatanywhereProvider } from "./registry/chatanywhere/index.ts";
|
||||
import { zyloApiProvider } from "./registry/zylo-api/index.ts";
|
||||
import { poolsideProvider } from "./registry/poolside/index.ts";
|
||||
import { fastrouterProvider } from "./registry/fastrouter/index.ts";
|
||||
import { anyapiProvider } from "./registry/anyapi/index.ts";
|
||||
import { electronhubProvider } from "./registry/electronhub/index.ts";
|
||||
import { llmgatewayProvider } from "./registry/llmgateway/index.ts";
|
||||
import { llmKiwiProvider } from "./registry/llm-kiwi/index.ts";
|
||||
import { literouterProvider } from "./registry/literouter/index.ts";
|
||||
import { mnnAiProvider } from "./registry/mnn-ai/index.ts";
|
||||
import { meganovaAiProvider } from "./registry/meganova-ai/index.ts";
|
||||
import { mixlayerProvider } from "./registry/mixlayer/index.ts";
|
||||
import { spekaProvider } from "./registry/speka/index.ts";
|
||||
import { tokenreplyProvider } from "./registry/tokenreply/index.ts";
|
||||
import { yoloAutoProvider } from "./registry/yolo-auto/index.ts";
|
||||
import { dxntProvider } from "./registry/dxnt/index.ts";
|
||||
import { cloudcodeOneProvider } from "./registry/cloudcode-one/index.ts";
|
||||
import { ofoxaiProvider } from "./registry/ofoxai/index.ts";
|
||||
import { zerolimitaiProvider } from "./registry/zerolimitai/index.ts";
|
||||
import { helyxaiProvider } from "./registry/helyxai/index.ts";
|
||||
import { aurikoProvider } from "./registry/auriko/index.ts";
|
||||
import { poixeAiProvider } from "./registry/poixe-ai/index.ts";
|
||||
import { nagaAiProvider } from "./registry/naga-ai/index.ts";
|
||||
import { chatOripeProvider } from "./registry/chat-oripe/index.ts";
|
||||
import { freeinferenceProvider } from "./registry/freeinference/index.ts";
|
||||
import { freeAiProvider } from "./registry/free-ai/index.ts";
|
||||
|
||||
|
||||
export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
aimlapi: aimlapiProvider,
|
||||
@@ -491,33 +465,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
promptql: promptqlProvider,
|
||||
hyperagent: hyperagentProvider,
|
||||
"muse-code": muse_codeProvider,
|
||||
"zylo-api": zyloApiProvider,
|
||||
unorouter: unorouterProvider,
|
||||
"naga-ac": naga_acProvider,
|
||||
chatanywhere: chatanywhereProvider,
|
||||
poolside: poolsideProvider,
|
||||
fastrouter: fastrouterProvider,
|
||||
anyapi: anyapiProvider,
|
||||
electronhub: electronhubProvider,
|
||||
llmgateway: llmgatewayProvider,
|
||||
"llm-kiwi": llmKiwiProvider,
|
||||
literouter: literouterProvider,
|
||||
"mnn-ai": mnnAiProvider,
|
||||
"meganova-ai": meganovaAiProvider,
|
||||
mixlayer: mixlayerProvider,
|
||||
speka: spekaProvider,
|
||||
tokenreply: tokenreplyProvider,
|
||||
"yolo-auto": yoloAutoProvider,
|
||||
dxnt: dxntProvider,
|
||||
"cloudcode-one": cloudcodeOneProvider,
|
||||
ofoxai: ofoxaiProvider,
|
||||
zerolimitai: zerolimitaiProvider,
|
||||
helyxai: helyxaiProvider,
|
||||
auriko: aurikoProvider,
|
||||
"poixe-ai": poixeAiProvider,
|
||||
"naga-ai": nagaAiProvider,
|
||||
"chat-oripe": chatOripeProvider,
|
||||
freeinference: freeinferenceProvider,
|
||||
"free-ai": freeAiProvider,
|
||||
|
||||
};
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const aurikoProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "auriko",
|
||||
alias: "auriko",
|
||||
baseUrl: "https://api.auriko.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.auriko.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
// The upstream brand and hostname are ambiguous, so avoid unverified quota claims.
|
||||
export const chatOripeProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "chat-oripe",
|
||||
alias: "chat-oripe",
|
||||
baseUrl: "https://api.oriper.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.oriper.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,12 +1,17 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
// International endpoint; audited free access is limited to non-commercial use.
|
||||
export const chatanywhereProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
// ChatAnywhere (api.chatanywhere.tech) — OpenAI-compatible gateway from the
|
||||
// chatanywhere/GPT_API_free project (~38.7k GitHub stars). Requires GitHub-account-
|
||||
// gated API key. Free tier is for personal non-commercial use only.
|
||||
export const chatanywhereProvider: RegistryEntry = {
|
||||
id: "chatanywhere",
|
||||
alias: "chatanywhere",
|
||||
baseUrl: "https://api.chatanywhere.org/v1/chat/completions",
|
||||
modelsUrl: "https://api.chatanywhere.org/v1/models",
|
||||
models: [],
|
||||
alias: "chtany",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.chatanywhere.tech/v1/chat/completions",
|
||||
modelsUrl: "https://api.chatanywhere.tech/v1/models",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
passthroughModels: true,
|
||||
});
|
||||
models: [],
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* CloudCode.ONE - OpenAI-compatible API with published free model aliases.
|
||||
*
|
||||
* The Anthropic-compatible endpoint is documented separately; this registry
|
||||
* covers the OpenAI-compatible API surface audited for this migration.
|
||||
*/
|
||||
export const cloudcodeOneProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "cloudcode-one",
|
||||
alias: "cloudcode-one",
|
||||
baseUrl: "https://api.cloudcode.one/v1/chat/completions",
|
||||
modelsUrl: "https://api.cloudcode.one/v1/models",
|
||||
models: [
|
||||
{ id: "glm-4.7-flash", name: "GLM 4.7 Flash" },
|
||||
{ id: "glm-4.6v-flash", name: "GLM 4.6V Flash" },
|
||||
],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RegistryEntry } from "../shared";
|
||||
import type { RegistryEntry } from "../../shared";
|
||||
|
||||
export const deepaiProvider: RegistryEntry = {
|
||||
id: "deepai",
|
||||
@@ -7,6 +7,7 @@ export const deepaiProvider: RegistryEntry = {
|
||||
baseUrl: "https://api.deepai.org",
|
||||
authType: "apikey",
|
||||
authHeader: "api-key",
|
||||
executor: "default",
|
||||
models: [
|
||||
{ id: "text2img", name: "Text to Image" },
|
||||
],
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* DXNT - OpenAI-compatible API with a free account quota.
|
||||
*
|
||||
* Models are discovered from the provider's authenticated catalog rather than
|
||||
* copied into a static list, so account-specific availability remains intact.
|
||||
*/
|
||||
export const dxntProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "dxnt",
|
||||
alias: "dxnt",
|
||||
baseUrl: "https://www.dxnt.com/v1/chat/completions",
|
||||
modelsUrl: "https://www.dxnt.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const freeAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "free-ai",
|
||||
alias: "free-ai",
|
||||
baseUrl: "https://api.free.ai/v1/chat/",
|
||||
modelsUrl: "https://api.free.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const freeinferenceProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "freeinference",
|
||||
alias: "freeinference",
|
||||
baseUrl: "https://freeinference.org/v1/chat/completions",
|
||||
modelsUrl: "https://freeinference.org/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const helyxaiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "helyxai",
|
||||
alias: "helyxai",
|
||||
baseUrl: "https://helyxai.space/v1/chat/completions",
|
||||
modelsUrl: "https://helyxai.space/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const literouterProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "literouter",
|
||||
alias: "literouter",
|
||||
baseUrl: "https://api.literouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.literouter.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const meganovaAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "meganova-ai",
|
||||
alias: "meganova-ai",
|
||||
baseUrl: "https://api.meganova.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.meganova.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const mixlayerProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "mixlayer",
|
||||
alias: "mixlayer",
|
||||
baseUrl: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
modelsUrl: "https://models.mixlayer.ai/v1/models",
|
||||
models: [{ id: "qwen/qwen3.5-4b-free", name: "Qwen 3.5 4B (free)" }],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const mnnAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "mnn-ai",
|
||||
alias: "mnn-ai",
|
||||
baseUrl: "https://api.mnnai.ru/v1/chat/completions",
|
||||
modelsUrl: "https://api.mnnai.ru/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
// Free access terms may permit data collection or training use; discover models dynamically.
|
||||
export const nagaAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "naga-ai",
|
||||
alias: "naga-ai",
|
||||
baseUrl: "https://api.naga.ac/v1/chat/completions",
|
||||
modelsUrl: "https://api.naga.ac/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const ofoxaiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "ofoxai",
|
||||
alias: "ofoxai",
|
||||
baseUrl: "https://api.ofox.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.ofox.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const poixeAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "poixe-ai",
|
||||
alias: "poixe-ai",
|
||||
baseUrl: "https://api.poixe.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.poixe.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const spekaProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "speka",
|
||||
alias: "speka",
|
||||
baseUrl: "https://speka.me/v1/chat/completions",
|
||||
modelsUrl: "https://speka.me/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const tokenreplyProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "tokenreply",
|
||||
alias: "tokenreply",
|
||||
baseUrl: "https://api.tokenreply.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.tokenreply.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const unorouterProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
export const unorouterProvider: RegistryEntry = {
|
||||
id: "unorouter",
|
||||
alias: "unorouter",
|
||||
baseUrl: "https://api.unorouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.unorouter.com/v1/models",
|
||||
models: [],
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.unorouter.ai/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
defaultContextLength: 128000,
|
||||
models: [{ id: "auto", name: "Auto (Best Available)" }],
|
||||
passthroughModels: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* Yolo-Auto - OpenAI-compatible API with a request-limited free tier.
|
||||
*
|
||||
* The catalog is kept intentionally small: the documented free-tier model is
|
||||
* seeded while passthrough discovery allows the service to publish updates.
|
||||
*/
|
||||
export const yoloAutoProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "yolo-auto",
|
||||
alias: "yolo-auto",
|
||||
baseUrl: "https://yolo-auto.com/v1/chat/completions",
|
||||
modelsUrl: "https://yolo-auto.com/v1/models",
|
||||
models: [{ id: "qwen3.6-35b-a3b", name: "Qwen 3.6 35B A3B" }],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const zerolimitaiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "zerolimitai",
|
||||
alias: "zerolimitai",
|
||||
baseUrl: "https://www.zerolimitai.com/api/v1/chat/completions",
|
||||
modelsUrl: "https://www.zerolimitai.com/api/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -537,7 +537,7 @@ function sanitizeResponsesUsage(usage: unknown): unknown {
|
||||
// DeepSeek native API: map flat prompt_cache_hit_tokens into input_tokens_details
|
||||
if (
|
||||
normalized.prompt_cache_hit_tokens !== undefined &&
|
||||
!normalized.input_tokens_details?.cached_tokens
|
||||
!(toRecord(normalized.input_tokens_details) ?? {}).cached_tokens
|
||||
) {
|
||||
normalized.input_tokens_details = {
|
||||
...(normalized.input_tokens_details as Record<string, unknown> || {}),
|
||||
@@ -549,7 +549,7 @@ function sanitizeResponsesUsage(usage: unknown): unknown {
|
||||
if (
|
||||
normalized.cache_read_input_tokens !== undefined &&
|
||||
normalized.cache_read_input_tokens !== 0 &&
|
||||
!normalized.input_tokens_details?.cached_tokens
|
||||
!(toRecord(normalized.input_tokens_details) ?? {}).cached_tokens
|
||||
) {
|
||||
normalized.input_tokens_details = {
|
||||
...(normalized.input_tokens_details as Record<string, unknown> || {}),
|
||||
|
||||
@@ -304,7 +304,7 @@ function buildSerperRequest(
|
||||
url: `${config.baseUrl}${endpoint}`,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-API-Key": params.token },
|
||||
headers: { "Content-Type": "application/json", ...(params.token ? { "X-API-Key": params.token } : {}) },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
@@ -322,7 +322,7 @@ function buildBraveRequest(
|
||||
url: `${config.baseUrl}${endpoint}?${qp}`,
|
||||
init: {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json", "X-Subscription-Token": params.token },
|
||||
headers: { Accept: "application/json", ...(params.token ? { "X-Subscription-Token": params.token } : {}) },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -348,7 +348,7 @@ function buildExaRequest(
|
||||
url: config.baseUrl,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "x-api-key": params.token },
|
||||
headers: { "Content-Type": "application/json", ...(params.token ? { "x-api-key": params.token } : {}) },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -665,8 +665,6 @@ export function extractUsage(chunk) {
|
||||
chunk.usage.reasoning_tokens,
|
||||
// xAI's exact provider-reported cost (port of decolua/9router#2453, capability A).
|
||||
cost_in_usd_ticks: chunk.usage.cost_in_usd_ticks,
|
||||
cache_read_input_tokens: chunk.usage.cache_read_input_tokens,
|
||||
cache_creation_input_tokens: chunk.usage.cache_creation_input_tokens,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export const KNOWN_DUPLICATE_VERSIONS = new Set([
|
||||
// O stale-enforcement exige que cada reserva seja removida quando os arquivos
|
||||
// correspondentes aterrissarem na release.
|
||||
// ---------------------------------------------------------------------------
|
||||
export const KNOWN_GAPS = new Set(["026", "055", "121", "144", "145", "147", "148", "149"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
|
||||
export const KNOWN_GAPS = new Set(["026", "055", "121", "144", "145", "148", "149"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
|
||||
|
||||
function pad3(n) {
|
||||
return String(n).padStart(3, "0");
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
-- Migration 139: generic job registry (jobs + job_runs tables)
|
||||
-- Job registry (#8848): centralized periodic-job scheduling + run history.
|
||||
--
|
||||
-- jobs: one row per registered job (interval or cron), with env-flag gating
|
||||
-- job_runs: one row per execution (running/success/failure), pruned by count + age
|
||||
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL DEFAULT 'interval' CHECK(type IN ('interval', 'cron')),
|
||||
cron TEXT, -- cron expression (type='cron'); NULL for interval jobs
|
||||
interval_ms INTEGER, -- interval in ms (type='interval'); NULL for cron jobs
|
||||
enabled INTEGER NOT NULL DEFAULT 1, -- 0=disabled, 1=enabled
|
||||
env_flag TEXT, -- env var name (boolean gate), e.g. 'OMNIROUTE_WARMUP_ENABLED'; NULL = no gate
|
||||
config TEXT NOT NULL DEFAULT '{}', -- JSON config (concurrency, timezone, envDefault, ...)
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS job_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
started_at TEXT NOT NULL, -- ISO-8601, written explicitly by recordRun (no DB default)
|
||||
finished_at TEXT, -- ISO-8601; NULL while running
|
||||
status TEXT NOT NULL DEFAULT 'running', -- 'running' | 'success' | 'failure'
|
||||
error_message TEXT, -- sanitized error message (no stack trace)
|
||||
records_affected INTEGER DEFAULT 0, -- job-specific meaning (see below)
|
||||
duration_ms INTEGER, -- execution duration in ms
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_jr_job_id ON job_runs(job_id, started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_jr_started_at ON job_runs(started_at);
|
||||
|
||||
-- Built-in job registration (idempotent - INSERT OR IGNORE).
|
||||
-- env_flag = NULL means "no registry-level boolean gate"; per-job disable semantics
|
||||
-- live inside the handler itself (see token_health_check wrapper).
|
||||
-- 'warmup' is seeded disabled because its handler is not part of this change.
|
||||
-- startAll() filters on enabled before it looks for a handler, so a disabled row
|
||||
-- stays quiet instead of warning on every boot; the change that brings the warmup
|
||||
-- handler flips it on.
|
||||
INSERT OR IGNORE INTO jobs (id, type, cron, interval_ms, enabled, env_flag, config) VALUES
|
||||
('budget_reset', 'interval', NULL, 600000, 1, NULL, '{}'),
|
||||
('warmup', 'cron', '0 7 * * *', NULL, 0, 'OMNIROUTE_WARMUP_ENABLED', '{"timezone":"America/Los_Angeles","envDefault":false}'),
|
||||
('token_health_check', 'interval', NULL, 60000, 1, NULL, '{}');
|
||||
|
||||
-- records_affected semantics:
|
||||
-- budget_reset = number of budget records reset (UPDATE ... SET budget_used=0 row count)
|
||||
-- warmup = number of connections attempted for warmup
|
||||
-- token_health_check = number of connections swept by the health check
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getDbInstance } from "./core";
|
||||
import { invalidateDbCache } from "./readCache";
|
||||
|
||||
export type ModelCapabilityOverrideKey = "max_input_tokens" | "max_output_tokens";
|
||||
export type ModelCapabilityOverrideKey = "max_input_tokens" | "max_output_tokens" | "max_token";
|
||||
|
||||
export interface ModelCapabilityOverride {
|
||||
provider: string;
|
||||
@@ -21,7 +21,7 @@ interface OverrideRow {
|
||||
}
|
||||
|
||||
function isSupportedKey(value: unknown): value is ModelCapabilityOverrideKey {
|
||||
return value === "max_input_tokens" || value === "max_output_tokens";
|
||||
return value === "max_input_tokens" || value === "max_output_tokens" || value === "max_token";
|
||||
}
|
||||
|
||||
function isPositiveInteger(value: unknown): value is number {
|
||||
|
||||
@@ -222,7 +222,7 @@ export async function executeWebSearch(
|
||||
.filter((provider) => supportsSearchType(provider, searchType))
|
||||
.sort((a, b) => a.costPerQuery - b.costPerQuery)
|
||||
.map((provider) => provider.id)
|
||||
.filter((providerId) => providerId !== providerConfig.id);
|
||||
.filter((providerId) => providerId !== providerConfig!.id);
|
||||
|
||||
for (const providerId of otherIds) {
|
||||
const creds = await resolveSearchCredentials(providerId);
|
||||
|
||||
@@ -7,33 +7,6 @@ export const PROVIDER_ENDPOINTS = {
|
||||
dgrid: "https://api.dgrid.ai/v1/chat/completions",
|
||||
bai: "https://api.b.ai/v1/chat/completions",
|
||||
qiniu: "https://api.qnaigc.com/v1/chat/completions",
|
||||
"zylo-api": "https://api.zyloai.net/v1/chat/completions",
|
||||
unorouter: "https://api.unorouter.com/v1/chat/completions",
|
||||
poolside: "https://inference.poolside.ai/v1/chat/completions",
|
||||
fastrouter: "https://api.fastrouter.ai/api/v1/chat/completions",
|
||||
anyapi: "https://api.anyapi.ai/v1/chat/completions",
|
||||
electronhub: "https://api.electronhub.ai/v1/chat/completions",
|
||||
llmgateway: "https://api.llmgateway.io/v1/chat/completions",
|
||||
"llm-kiwi": "https://api.llm.kiwi/v1/chat/completions",
|
||||
literouter: "https://api.literouter.com/v1/chat/completions",
|
||||
"mnn-ai": "https://api.mnnai.ru/v1/chat/completions",
|
||||
"meganova-ai": "https://api.meganova.ai/v1/chat/completions",
|
||||
mixlayer: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
speka: "https://speka.me/v1/chat/completions",
|
||||
tokenreply: "https://api.tokenreply.com/v1/chat/completions",
|
||||
"yolo-auto": "https://yolo-auto.com/v1/chat/completions",
|
||||
dxnt: "https://www.dxnt.com/v1/chat/completions",
|
||||
"cloudcode-one": "https://api.cloudcode.one/v1/chat/completions",
|
||||
ofoxai: "https://api.ofox.ai/v1/chat/completions",
|
||||
zerolimitai: "https://www.zerolimitai.com/api/v1/chat/completions",
|
||||
chatanywhere: "https://api.chatanywhere.org/v1/chat/completions",
|
||||
helyxai: "https://helyxai.space/v1/chat/completions",
|
||||
auriko: "https://api.auriko.ai/v1/chat/completions",
|
||||
"poixe-ai": "https://api.poixe.com/v1/chat/completions",
|
||||
"naga-ai": "https://api.naga.ac/v1/chat/completions",
|
||||
"chat-oripe": "https://api.oriper.com/v1/chat/completions",
|
||||
freeinference: "https://freeinference.org/v1/chat/completions",
|
||||
"free-ai": "https://api.free.ai/v1/chat/",
|
||||
glm: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
glmt: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
"bailian-coding-plan": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
|
||||
@@ -21,10 +21,7 @@ import { PROVIDER_MODELS as MODELS } from "@omniroute/open-sse/config/providerMo
|
||||
const PASSTHROUGH_PROVIDERS = new Set(
|
||||
Object.entries(AI_PROVIDERS)
|
||||
.filter(([, p]) => (p as any).passthroughModels)
|
||||
.flatMap(([key, provider]) => {
|
||||
const alias = (provider as { alias?: unknown }).alias;
|
||||
return typeof alias === "string" && alias.length > 0 ? [key, alias] : [key];
|
||||
})
|
||||
.map(([key]) => key)
|
||||
);
|
||||
|
||||
// Wrap isValidModel with passthrough providers
|
||||
|
||||
@@ -96,32 +96,7 @@ export const AGGREGATOR_PROVIDER_IDS = new Set([
|
||||
"g4f-nvidia",
|
||||
"naga-ac",
|
||||
"chatanywhere",
|
||||
"zylo-api",
|
||||
"fastrouter",
|
||||
"anyapi",
|
||||
"electronhub",
|
||||
"llmgateway",
|
||||
"llm-kiwi",
|
||||
"literouter",
|
||||
"mnn-ai",
|
||||
"meganova-ai",
|
||||
"mixlayer",
|
||||
"speka",
|
||||
"tokenreply",
|
||||
"yolo-auto",
|
||||
"dxnt",
|
||||
"cloudcode-one",
|
||||
"ofoxai",
|
||||
"zerolimitai",
|
||||
"helyxai",
|
||||
"auriko",
|
||||
"poixe-ai",
|
||||
"naga-ai",
|
||||
"chat-oripe",
|
||||
"freeinference",
|
||||
"free-ai",
|
||||
|
||||
]);;
|
||||
]);
|
||||
|
||||
export const ENTERPRISE_CLOUD_PROVIDER_IDS = new Set([
|
||||
"azure-openai",
|
||||
|
||||
@@ -53,9 +53,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
color: "#8B5CF6",
|
||||
textIcon: "UR",
|
||||
passthroughModels: true,
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Models with the :free suffix do not debit balance; limit is 1 request/minute per free model per user.",
|
||||
website: "https://unorouter.ai",
|
||||
apiHint: "Create an API key at https://unorouter.ai, then paste it here as a Bearer token.",
|
||||
},
|
||||
@@ -442,37 +439,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
apiHint:
|
||||
"Use https://api.oriper.com/v1 only after confirming the provider's current documentation, terms and key issuance. No quota is guaranteed by this catalog.",
|
||||
},
|
||||
freeinference: {
|
||||
id: "freeinference",
|
||||
alias: "freeinference",
|
||||
name: "FreeInference",
|
||||
icon: "science",
|
||||
color: "#8B5CF6",
|
||||
textIcon: "FI",
|
||||
passthroughModels: true,
|
||||
website: "https://freeinference.org",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free research access without a card; non-Harvard applicants require manual approval and no numeric quota is publicly guaranteed.",
|
||||
apiHint:
|
||||
"Apply for a FreeInference key, then use https://freeinference.org/v1 as the OpenAI-compatible base URL. Terms allow prompt/response logging and possible publication of anonymized research data; never send sensitive or production data.",
|
||||
},
|
||||
"free-ai": {
|
||||
id: "free-ai",
|
||||
alias: "free-ai",
|
||||
name: "Free.ai",
|
||||
icon: "hub",
|
||||
color: "#16A34A",
|
||||
textIcon: "FA",
|
||||
passthroughModels: true,
|
||||
website: "https://free.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"30,000 tokens/day cover self-hosted models after email verification. Usage beyond the pool can bill at raw cost, and premium external models are paid.",
|
||||
apiHint:
|
||||
"Create an sk-free- key, then use the nonstandard but OpenAI-shaped https://api.free.ai/v1/chat/ endpoint. Select a self-hosted zero-price model to stay within the free pool.",
|
||||
},
|
||||
|
||||
dgrid: {
|
||||
id: "dgrid",
|
||||
alias: "dgrid",
|
||||
|
||||
@@ -45,21 +45,6 @@ export const APIKEY_PROVIDERS_INFERENCE = {
|
||||
hasFree: true,
|
||||
freeNote: "Free plan: 3-day trial with open-source models — no credit card required",
|
||||
},
|
||||
poolside: {
|
||||
id: "poolside",
|
||||
alias: "poolside",
|
||||
name: "Poolside",
|
||||
icon: "memory",
|
||||
color: "#111827",
|
||||
textIcon: "PS",
|
||||
passthroughModels: true,
|
||||
website: "https://poolside.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Laguna S 2.1 and XS 2.1 are free during Preview; no public numeric quota is published.",
|
||||
apiHint:
|
||||
"Create a free developer API key, then use https://inference.poolside.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
fireworks: {
|
||||
id: "fireworks",
|
||||
alias: "fireworks",
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["zylo-api", "zylo", "https://api.zyloai.net/v1/chat/completions"],
|
||||
["unorouter", "unorouter", "https://api.unorouter.com/v1/chat/completions"],
|
||||
["poolside", "poolside", "https://inference.poolside.ai/v1/chat/completions"],
|
||||
["fastrouter", "fastrouter", "https://api.fastrouter.ai/api/v1/chat/completions"],
|
||||
["anyapi", "anyapi", "https://api.anyapi.ai/v1/chat/completions"],
|
||||
["electronhub", "electronhub", "https://api.electronhub.ai/v1/chat/completions"],
|
||||
["llmgateway", "llmgateway", "https://api.llmgateway.io/v1/chat/completions"],
|
||||
["llm-kiwi", "llmkiwi", "https://api.llm.kiwi/v1/chat/completions"],
|
||||
] as const;
|
||||
|
||||
for (const [id, alias, endpoint] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, alias);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, alias);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.equal(isValidModel(alias, "future/live-catalog-model"), true);
|
||||
});
|
||||
}
|
||||
|
||||
test("gateway providers are classified as aggregators while direct Poolside is not", () => {
|
||||
const gatewayIds = providers.map(([id]) => id).filter((id) => id !== "poolside");
|
||||
for (const id of gatewayIds) assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has("poolside"), false);
|
||||
});
|
||||
|
||||
test("LLM.Kiwi statically exposes only the confirmed Free plan models", () => {
|
||||
assert.deepEqual(REGISTRY["llm-kiwi"].models, [
|
||||
{ id: "auto", name: "Auto" },
|
||||
{ id: "hrLLM", name: "hrLLM" },
|
||||
]);
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { literouterProvider } from "../../open-sse/config/providers/registry/literouter/index.ts";
|
||||
import { meganovaAiProvider } from "../../open-sse/config/providers/registry/meganova-ai/index.ts";
|
||||
import { mnnAiProvider } from "../../open-sse/config/providers/registry/mnn-ai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: literouterProvider,
|
||||
id: "literouter",
|
||||
chatUrl: "https://api.literouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.literouter.com/v1/models",
|
||||
},
|
||||
{
|
||||
entry: mnnAiProvider,
|
||||
id: "mnn-ai",
|
||||
chatUrl: "https://api.mnnai.ru/v1/chat/completions",
|
||||
modelsUrl: "https://api.mnnai.ru/v1/models",
|
||||
},
|
||||
{
|
||||
entry: meganovaAiProvider,
|
||||
id: "meganova-ai",
|
||||
chatUrl: "https://api.meganova.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.meganova.ai/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
test(`${id} uses an OpenAI-compatible Bearer registry entry`, () => {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`${id} leaves model discovery to the live upstream catalog`, () => {
|
||||
assert.deepEqual(entry.models, []);
|
||||
});
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { mixlayerProvider } from "../../open-sse/config/providers/registry/mixlayer/index.ts";
|
||||
import { spekaProvider } from "../../open-sse/config/providers/registry/speka/index.ts";
|
||||
import { tokenreplyProvider } from "../../open-sse/config/providers/registry/tokenreply/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: mixlayerProvider,
|
||||
id: "mixlayer",
|
||||
chatUrl: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
modelsUrl: "https://models.mixlayer.ai/v1/models",
|
||||
},
|
||||
{
|
||||
entry: spekaProvider,
|
||||
id: "speka",
|
||||
chatUrl: "https://speka.me/v1/chat/completions",
|
||||
modelsUrl: "https://speka.me/v1/models",
|
||||
},
|
||||
{
|
||||
entry: tokenreplyProvider,
|
||||
id: "tokenreply",
|
||||
chatUrl: "https://api.tokenreply.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.tokenreply.com/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 2-B providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
assert.ok(Array.isArray(entry.models));
|
||||
}
|
||||
});
|
||||
|
||||
test("Mixlayer seeds only its documented free model", () => {
|
||||
assert.deepEqual(
|
||||
mixlayerProvider.models.map((model) => model.id),
|
||||
["qwen/qwen3.5-4b-free"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Speka and TokenReply rely on live model catalogs without invented models", () => {
|
||||
assert.deepEqual(spekaProvider.models, []);
|
||||
assert.deepEqual(tokenreplyProvider.models, []);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { cloudcodeOneProvider } from "../../open-sse/config/providers/registry/cloudcode-one/index.ts";
|
||||
import { dxntProvider } from "../../open-sse/config/providers/registry/dxnt/index.ts";
|
||||
import { yoloAutoProvider } from "../../open-sse/config/providers/registry/yolo-auto/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
modelIds: string[];
|
||||
}> = [
|
||||
{
|
||||
entry: yoloAutoProvider,
|
||||
id: "yolo-auto",
|
||||
chatUrl: "https://yolo-auto.com/v1/chat/completions",
|
||||
modelsUrl: "https://yolo-auto.com/v1/models",
|
||||
modelIds: ["qwen3.6-35b-a3b"],
|
||||
},
|
||||
{
|
||||
entry: dxntProvider,
|
||||
id: "dxnt",
|
||||
chatUrl: "https://www.dxnt.com/v1/chat/completions",
|
||||
modelsUrl: "https://www.dxnt.com/v1/models",
|
||||
modelIds: [],
|
||||
},
|
||||
{
|
||||
entry: cloudcodeOneProvider,
|
||||
id: "cloudcode-one",
|
||||
chatUrl: "https://api.cloudcode.one/v1/chat/completions",
|
||||
modelsUrl: "https://api.cloudcode.one/v1/models",
|
||||
modelIds: ["glm-4.7-flash", "glm-4.6v-flash"],
|
||||
},
|
||||
];
|
||||
|
||||
for (const { entry, id, chatUrl, modelsUrl, modelIds } of providers) {
|
||||
test(`${id} uses the standard OpenAI-compatible API-key registry shape`, () => {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`${id} seeds only the audited model identifiers`, () => {
|
||||
assert.deepEqual(
|
||||
(entry.models ?? []).map((model) => model.id),
|
||||
modelIds
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["literouter", "https://api.literouter.com/v1/chat/completions", []],
|
||||
["mnn-ai", "https://api.mnnai.ru/v1/chat/completions", []],
|
||||
["meganova-ai", "https://api.meganova.ai/v1/chat/completions", []],
|
||||
["mixlayer", "https://models.mixlayer.ai/v1/chat/completions", ["qwen/qwen3.5-4b-free"]],
|
||||
["speka", "https://speka.me/v1/chat/completions", []],
|
||||
["tokenreply", "https://api.tokenreply.com/v1/chat/completions", []],
|
||||
["yolo-auto", "https://yolo-auto.com/v1/chat/completions", ["qwen3.6-35b-a3b"]],
|
||||
["dxnt", "https://www.dxnt.com/v1/chat/completions", []],
|
||||
[
|
||||
"cloudcode-one",
|
||||
"https://api.cloudcode.one/v1/chat/completions",
|
||||
["glm-4.7-flash", "glm-4.6v-flash"],
|
||||
],
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint, modelIds] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, id);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, id);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(
|
||||
registry.models.map((model) => model.id),
|
||||
modelIds
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test("risk-sensitive metadata preserves the audited quota qualifications", () => {
|
||||
assert.match(APIKEY_PROVIDERS["mnn-ai"].apiHint ?? "", /jurisdiction.*privacy/i);
|
||||
assert.match(APIKEY_PROVIDERS["meganova-ai"].freeNote ?? "", /per-model quotas/i);
|
||||
assert.match(APIKEY_PROVIDERS["meganova-ai"].freeNote ?? "", /paid overage/i);
|
||||
assert.match(APIKEY_PROVIDERS.tokenreply.freeNote ?? "", /no fixed global free quota/i);
|
||||
assert.match(APIKEY_PROVIDERS["yolo-auto"].freeNote ?? "", /no numeric daily quota/i);
|
||||
assert.match(APIKEY_PROVIDERS["cloudcode-one"].freeNote ?? "", /credit or a coupon/i);
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { chatanywhereProvider } from "../../open-sse/config/providers/registry/chatanywhere/index.ts";
|
||||
import { ofoxaiProvider } from "../../open-sse/config/providers/registry/ofoxai/index.ts";
|
||||
import { zerolimitaiProvider } from "../../open-sse/config/providers/registry/zerolimitai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: ofoxaiProvider,
|
||||
id: "ofoxai",
|
||||
chatUrl: "https://api.ofox.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.ofox.ai/v1/models",
|
||||
},
|
||||
{
|
||||
entry: zerolimitaiProvider,
|
||||
id: "zerolimitai",
|
||||
chatUrl: "https://www.zerolimitai.com/api/v1/chat/completions",
|
||||
modelsUrl: "https://www.zerolimitai.com/api/v1/models",
|
||||
},
|
||||
{
|
||||
entry: chatanywhereProvider,
|
||||
id: "chatanywhere",
|
||||
chatUrl: "https://api.chatanywhere.org/v1/chat/completions",
|
||||
modelsUrl: "https://api.chatanywhere.org/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 3-A providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
assert.deepEqual(entry.models, []);
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { aurikoProvider } from "../../open-sse/config/providers/registry/auriko/index.ts";
|
||||
import { helyxaiProvider } from "../../open-sse/config/providers/registry/helyxai/index.ts";
|
||||
import { poixeAiProvider } from "../../open-sse/config/providers/registry/poixe-ai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: helyxaiProvider,
|
||||
id: "helyxai",
|
||||
chatUrl: "https://helyxai.space/v1/chat/completions",
|
||||
modelsUrl: "https://helyxai.space/v1/models",
|
||||
},
|
||||
{
|
||||
entry: aurikoProvider,
|
||||
id: "auriko",
|
||||
chatUrl: "https://api.auriko.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.auriko.ai/v1/models",
|
||||
},
|
||||
{
|
||||
entry: poixeAiProvider,
|
||||
id: "poixe-ai",
|
||||
chatUrl: "https://api.poixe.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.poixe.com/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 3-B providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
}
|
||||
});
|
||||
|
||||
test("Wave 3-B providers rely on live catalogs without invented models", () => {
|
||||
for (const { entry } of providers) {
|
||||
assert.deepEqual(entry.models, []);
|
||||
}
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { chatOripeProvider } from "../../open-sse/config/providers/registry/chat-oripe/index.ts";
|
||||
import { nagaAiProvider } from "../../open-sse/config/providers/registry/naga-ai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: nagaAiProvider,
|
||||
id: "naga-ai",
|
||||
chatUrl: "https://api.naga.ac/v1/chat/completions",
|
||||
modelsUrl: "https://api.naga.ac/v1/models",
|
||||
},
|
||||
{
|
||||
entry: chatOripeProvider,
|
||||
id: "chat-oripe",
|
||||
chatUrl: "https://api.oriper.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.oriper.com/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 3-C providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
assert.deepEqual(entry.models, []);
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["ofoxai", "https://api.ofox.ai/v1/chat/completions"],
|
||||
["zerolimitai", "https://www.zerolimitai.com/api/v1/chat/completions"],
|
||||
["chatanywhere", "https://api.chatanywhere.org/v1/chat/completions"],
|
||||
["helyxai", "https://helyxai.space/v1/chat/completions"],
|
||||
["auriko", "https://api.auriko.ai/v1/chat/completions"],
|
||||
["poixe-ai", "https://api.poixe.com/v1/chat/completions"],
|
||||
["naga-ai", "https://api.naga.ac/v1/chat/completions"],
|
||||
["chat-oripe", "https://api.oriper.com/v1/chat/completions"],
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, id);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, id);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(registry.models, []);
|
||||
});
|
||||
}
|
||||
|
||||
test("Wave 3 metadata preserves the audited legal, quota and privacy warnings", () => {
|
||||
assert.match(APIKEY_PROVIDERS.chatanywhere.freeNote ?? "", /commercial traffic/i);
|
||||
assert.match(APIKEY_PROVIDERS.zerolimitai.freeNote ?? "", /3 and 7 days/i);
|
||||
assert.match(APIKEY_PROVIDERS.helyxai.freeNote ?? "", /100,000 tokens\/day/i);
|
||||
assert.match(APIKEY_PROVIDERS.auriko.freeNote ?? "", /not a free-token pool/i);
|
||||
assert.match(APIKEY_PROVIDERS["poixe-ai"].freeNote ?? "", /2 RPM\/5 RPD/i);
|
||||
assert.match(APIKEY_PROVIDERS["naga-ai"].freeNote ?? "", /training/i);
|
||||
assert.match(APIKEY_PROVIDERS["chat-oripe"].freeNote ?? "", /unconfirmed/i);
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { freeinferenceProvider } from "../../open-sse/config/providers/registry/freeinference/index.ts";
|
||||
import {
|
||||
DefaultExecutor,
|
||||
getExecutor,
|
||||
hasSpecializedExecutor,
|
||||
} from "../../open-sse/executors/index.ts";
|
||||
|
||||
test("FreeInference exposes an OpenAI-compatible Bearer registry", () => {
|
||||
assert.equal(freeinferenceProvider.id, "freeinference");
|
||||
assert.equal(freeinferenceProvider.alias, "freeinference");
|
||||
assert.equal(freeinferenceProvider.format, "openai");
|
||||
assert.equal(freeinferenceProvider.executor, "default");
|
||||
assert.equal(freeinferenceProvider.authType, "apikey");
|
||||
assert.equal(freeinferenceProvider.authHeader, "bearer");
|
||||
assert.equal(freeinferenceProvider.baseUrl, "https://freeinference.org/v1/chat/completions");
|
||||
assert.equal(freeinferenceProvider.modelsUrl, "https://freeinference.org/v1/models");
|
||||
assert.deepEqual(freeinferenceProvider.models, []);
|
||||
assert.equal(freeinferenceProvider.passthroughModels, true);
|
||||
});
|
||||
|
||||
test("FreeInference uses DefaultExecutor without specialized behavior", () => {
|
||||
assert.equal(hasSpecializedExecutor("freeinference"), false);
|
||||
assert.ok(getExecutor("freeinference") instanceof DefaultExecutor);
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { freeAiProvider } from "../../open-sse/config/providers/registry/free-ai/index.ts";
|
||||
import {
|
||||
DefaultExecutor,
|
||||
getExecutor,
|
||||
hasSpecializedExecutor,
|
||||
} from "../../open-sse/executors/index.ts";
|
||||
|
||||
test("Free.ai exposes its exact OpenAI-compatible endpoint and live catalog", () => {
|
||||
assert.equal(freeAiProvider.id, "free-ai");
|
||||
assert.equal(freeAiProvider.alias, "free-ai");
|
||||
assert.equal(freeAiProvider.format, "openai");
|
||||
assert.equal(freeAiProvider.executor, "default");
|
||||
assert.equal(freeAiProvider.authType, "apikey");
|
||||
assert.equal(freeAiProvider.authHeader, "bearer");
|
||||
assert.equal(freeAiProvider.baseUrl, "https://api.free.ai/v1/chat/");
|
||||
assert.equal(freeAiProvider.modelsUrl, "https://api.free.ai/v1/models");
|
||||
assert.deepEqual(freeAiProvider.models, []);
|
||||
assert.equal(freeAiProvider.passthroughModels, true);
|
||||
});
|
||||
|
||||
test("Free.ai uses DefaultExecutor without a specialized executor", () => {
|
||||
assert.ok(getExecutor("free-ai") instanceof DefaultExecutor);
|
||||
assert.equal(hasSpecializedExecutor("free-ai"), false);
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { deriveConfigFromRegistryModelsUrl } from "../../src/app/api/providers/[id]/models/discoveryConfig.ts";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor, hasSpecializedExecutor } =
|
||||
await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["freeinference", "https://freeinference.org/v1/chat/completions"],
|
||||
["free-ai", "https://api.free.ai/v1/chat/"],
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint] of providers) {
|
||||
test(`${id} is fully wired without a specialized executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, id);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, id);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.equal(hasSpecializedExecutor(id), false);
|
||||
const executor = getExecutor(id);
|
||||
assert.ok(executor instanceof DefaultExecutor);
|
||||
assert.equal(executor.buildUrl("live-model", false), endpoint);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(registry.models, []);
|
||||
});
|
||||
}
|
||||
|
||||
test("Wave 4 model discovery accepts both public catalog response envelopes", () => {
|
||||
const freeInferenceDiscovery = deriveConfigFromRegistryModelsUrl("freeinference");
|
||||
const freeAiDiscovery = deriveConfigFromRegistryModelsUrl("free-ai");
|
||||
|
||||
assert.ok(freeInferenceDiscovery);
|
||||
assert.ok(freeAiDiscovery);
|
||||
assert.deepEqual(freeInferenceDiscovery.parseResponse({ data: [{ id: "glm-5.1" }] }), [
|
||||
{ id: "glm-5.1" },
|
||||
]);
|
||||
assert.deepEqual(freeAiDiscovery.parseResponse({ models: [{ id: "qwen7b" }] }), [
|
||||
{ id: "qwen7b" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("Wave 4 metadata preserves approval, logging and overage warnings", () => {
|
||||
assert.match(APIKEY_PROVIDERS.freeinference.freeNote ?? "", /manual approval/i);
|
||||
assert.match(APIKEY_PROVIDERS.freeinference.apiHint ?? "", /logging/i);
|
||||
assert.match(APIKEY_PROVIDERS["free-ai"].freeNote ?? "", /30,000 tokens\/day/i);
|
||||
assert.match(APIKEY_PROVIDERS["free-ai"].freeNote ?? "", /premium external models are paid/i);
|
||||
assert.match(APIKEY_PROVIDERS["free-ai"].apiHint ?? "", /\/v1\/chat\//i);
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
|
||||
test("passthrough providers accept live model ids through both provider id and alias", () => {
|
||||
for (const [id, alias] of [
|
||||
["zylo-api", "zylo"],
|
||||
["llm-kiwi", "llmkiwi"],
|
||||
["freetheai", "fta"],
|
||||
] as const) {
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.equal(isValidModel(alias, "future/live-catalog-model"), true);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user