mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 10:12:11 +03:00
Compare commits
16 Commits
fix/securi
...
feat/condu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d37def719f | ||
|
|
f87a82b092 | ||
|
|
4b2bd2e37f | ||
|
|
9813f35ff0 | ||
|
|
54c69cf48d | ||
|
|
12b957a2df | ||
|
|
5d3c988715 | ||
|
|
af97f7fc31 | ||
|
|
4d6c855258 | ||
|
|
7b951761fc | ||
|
|
b97318d73b | ||
|
|
a75295f359 | ||
|
|
00e15af622 | ||
|
|
5eb10e896d | ||
|
|
33baf62b58 | ||
|
|
d42a58141b |
@@ -1,11 +0,0 @@
|
||||
- **feat(plugins):** add onStreamComplete built-in event exposing streaming usage and timing (#9571)
|
||||
|
||||
Adds a new `onStreamComplete` plugin event that fires after an SSE stream is fully
|
||||
consumed, carrying usage token counts and timing metrics (latency, TTFT). Built-in
|
||||
events now include `onStreamComplete` as a fire-and-forget lifecycle hook.
|
||||
|
||||
Payload: `status`, `usage` (prompt_tokens, completion_tokens, reasoning_tokens,
|
||||
cache_read_input_tokens, cache_creation_input_tokens), `timing` (latencyMs, ttft),
|
||||
`model`, `provider`, `errorCode`.
|
||||
|
||||
Non-breaking — existing `onResponse` hooks with `{ streamed: true }` remain unchanged.
|
||||
@@ -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,5 +1,5 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { CONOL_FALLBACK_MODELS } from "../../../../services/conolModels.ts";
|
||||
import { CONOL_FALLBACK_MODELS } from "../../../services/conolModels.ts";
|
||||
|
||||
export const conol_webProvider: RegistryEntry = {
|
||||
id: "conol-web",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import { BaseExecutor, type ExecuteInput } from "./base.ts";
|
||||
import { makeExecutorErrorResult as makeErrorResult } from "../utils/error.ts";
|
||||
import { initTinyCmsWasm, generateSecurePayload } from "./tinycmsSigner.ts";
|
||||
@@ -30,9 +28,9 @@ async function fetchChallenge(uuid: string): Promise<any> {
|
||||
const res = await fetch(CHALLENGE_URL, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
uuid: uuid,
|
||||
"uuid": uuid,
|
||||
"x-origin": "https://gov.freegpt.win",
|
||||
Accept: "application/json",
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
|
||||
},
|
||||
});
|
||||
@@ -69,10 +67,10 @@ export class TinyCmsExecutor extends BaseExecutor {
|
||||
const challengeObj = await fetchChallenge(uuid);
|
||||
|
||||
const timestamp = Date.now().toString();
|
||||
// The nonce is signed into the anti-replay payload below, so it must come from a CSPRNG.
|
||||
// `randomUUID` from node:crypto is always available on the supported runtimes — the old
|
||||
// non-cryptographic fallback produced a predictable nonce with no way to notice.
|
||||
const nonceJs = randomUUID();
|
||||
const nonceJs =
|
||||
typeof crypto !== "undefined" && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
|
||||
const securePayload = generateSecurePayload(
|
||||
uuid,
|
||||
@@ -123,7 +121,12 @@ export class TinyCmsExecutor extends BaseExecutor {
|
||||
body: response.body,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return makeErrorResult(500, `TinyCMS Error: ${err.message}`, body, CHAT_URL);
|
||||
return makeErrorResult(
|
||||
500,
|
||||
`TinyCMS Error: ${err.message}`,
|
||||
body,
|
||||
CHAT_URL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +251,7 @@ import { recordCompressionCacheStats } from "./chatCore/compressionCacheStats.ts
|
||||
import { writeCavemanOutputAnalytics } from "./chatCore/cavemanOutputAnalytics.ts";
|
||||
import { scheduleQuotaShareConsumption } from "./chatCore/quotaShareConsumption.ts";
|
||||
import { emitRequestGamificationEvent } from "./chatCore/gamificationEvent.ts";
|
||||
import {
|
||||
runPluginOnResponseHook,
|
||||
runPluginOnStreamCompleteHook,
|
||||
} from "./chatCore/pluginOnResponse.ts";
|
||||
import { runPluginOnResponseHook } from "./chatCore/pluginOnResponse.ts";
|
||||
import { scheduleStreamingQuotaShareConsumption } from "./chatCore/streamingQuotaShare.ts";
|
||||
import { recordStreamingUsageStats } from "./chatCore/streamingUsageStats.ts";
|
||||
import { recordStreamingCost } from "./chatCore/streamingCost.ts";
|
||||
@@ -4937,17 +4934,6 @@ export async function handleChatCore({
|
||||
streamUsage,
|
||||
log,
|
||||
});
|
||||
|
||||
// Plugin onStreamComplete hook — fire-and-forget, fail-open (#9571)
|
||||
runPluginOnStreamCompleteHook({
|
||||
status: normalizedStreamStatus,
|
||||
usage: streamUsage as Record<string, unknown> | undefined,
|
||||
ttft,
|
||||
model,
|
||||
provider,
|
||||
errorCode: streamErrorCode,
|
||||
startTime,
|
||||
});
|
||||
};
|
||||
|
||||
const streamFailureFinalizers = streamFailure.createStreamFailureFinalizers({
|
||||
|
||||
@@ -45,57 +45,3 @@ export async function runPluginOnResponseHook(args: {
|
||||
/* plugin onResponse optional */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload passed to plugin onStreamComplete hooks after a streaming response is consumed.
|
||||
* Carries usage token counts, timing metrics (latency, TTFT), model, provider, and error code.
|
||||
*/
|
||||
export type PluginOnStreamCompletePayload = {
|
||||
status: number;
|
||||
usage?: {
|
||||
prompt_tokens?: number;
|
||||
completion_tokens?: number;
|
||||
reasoning_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
};
|
||||
timing?: {
|
||||
latencyMs: number;
|
||||
ttft?: number;
|
||||
};
|
||||
model?: string;
|
||||
provider?: string;
|
||||
errorCode?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run plugin onStreamComplete hooks — fire-and-forget and fail-open.
|
||||
* Called inside the onStreamComplete callback (chatCore.ts) where usage and timing data
|
||||
* converge after an SSE stream is fully consumed.
|
||||
*/
|
||||
export async function runPluginOnStreamCompleteHook(args: {
|
||||
status: number;
|
||||
usage?: Record<string, unknown>;
|
||||
ttft?: number;
|
||||
model: string | null | undefined;
|
||||
provider: string | null | undefined;
|
||||
errorCode?: string | null | undefined;
|
||||
startTime: number;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { runOnStreamComplete } = await import("@/lib/plugins/hooks");
|
||||
runOnStreamComplete({
|
||||
status: args.status,
|
||||
usage: args.usage as PluginOnStreamCompletePayload["usage"],
|
||||
timing: {
|
||||
latencyMs: Date.now() - args.startTime,
|
||||
ttft: args.ttft,
|
||||
},
|
||||
model: args.model ?? undefined,
|
||||
provider: args.provider ?? undefined,
|
||||
errorCode: args.errorCode ?? undefined,
|
||||
}).catch(() => {});
|
||||
} catch (_) {
|
||||
/* plugin onStreamComplete optional */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,16 +237,12 @@ function trustedEnvironmentText(parsed: CodexParsedRequest): string {
|
||||
}
|
||||
|
||||
function decodeXmlText(value: string): string {
|
||||
// `&` MUST be decoded last: decoding it first turns `&quot;` (the encoding of the
|
||||
// literal text `"`) into `"`, which the following pass then decodes again into `"`.
|
||||
// These values feed the workspace-root trust comparison below, so a double-unescape lets an
|
||||
// encoded path decode into a different path than the one the client actually declared.
|
||||
return value
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll(""", '"')
|
||||
.replaceAll("'", "'")
|
||||
.replaceAll("&", "&");
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function uniqueAbsolutePaths(values: string[], field: string): string[] {
|
||||
|
||||
@@ -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",
|
||||
@@ -1213,7 +1179,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
authHint: "Get your Regolo API key from regolo.ai, then paste it here as a Bearer token.",
|
||||
apiHint:
|
||||
"OpenAI-compatible endpoint at https://api.regolo.ai/v1 with dynamic model discovery (19 models).",
|
||||
},
|
||||
"naga-ac": {
|
||||
id: "naga-ac",
|
||||
alias: "naga",
|
||||
@@ -1230,4 +1195,19 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
authHint:
|
||||
"Get API key at naga.ac — Google/GitHub/Discord signup available.",
|
||||
},
|
||||
chatanywhere: {
|
||||
id: "chatanywhere",
|
||||
alias: "chtany",
|
||||
name: "ChatAnywhere",
|
||||
icon: "chat",
|
||||
color: "#10B981",
|
||||
textIcon: "CA",
|
||||
website: "https://api.chatanywhere.tech",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free tier: 5 req/day for GPT-5/4o/4.1, 30/day DeepSeek, 200/day gpt-4o-mini. Personal non-commercial use only — see chatanywhere/GPT_API_free. Requires GitHub-account-gated API key.",
|
||||
passthroughModels: true,
|
||||
authHint:
|
||||
"Get free API key at api.chatanywhere.tech — requires GitHub account signup.",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
import { test, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { registerHook, unregisterHook } = await import("../../src/lib/plugins/hooks.ts");const { runPluginOnResponseHook, runPluginOnStreamCompleteHook } = await import("../../open-sse/handlers/chatCore/pluginOnResponse.ts");
|
||||
const { registerHook, unregisterHook } = await import("../../src/lib/plugins/hooks.ts");
|
||||
const { runPluginOnResponseHook } =
|
||||
await import("../../open-sse/handlers/chatCore/pluginOnResponse.ts");
|
||||
|
||||
async function waitFor(pred: () => boolean, timeoutMs = 2000): Promise<void> {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
@@ -17,7 +19,6 @@ async function waitFor(pred: () => boolean, timeoutMs = 2000): Promise<void> {
|
||||
|
||||
after(() => {
|
||||
unregisterHook("onResponse", "test-onresponse-plugin");
|
||||
unregisterHook("onStreamComplete", "test-onstreamcomplete-plugin");
|
||||
});
|
||||
|
||||
test("no registered hooks → resolves without throwing (no-op)", async () => {
|
||||
@@ -142,140 +143,3 @@ test("a throwing hook never rejects the caller (fail-open)", async () => {
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 30));
|
||||
});
|
||||
|
||||
// ── onStreamComplete hook tests (#9571) ──
|
||||
|
||||
test("onStreamComplete: no registered hooks resolves without throwing (no-op)", async () => {
|
||||
const start = Date.now();
|
||||
await assert.doesNotReject(
|
||||
runPluginOnStreamCompleteHook({
|
||||
status: 200,
|
||||
usage: { prompt_tokens: 10, completion_tokens: 20 },
|
||||
ttft: 150,
|
||||
model: "gpt-4",
|
||||
provider: "openai",
|
||||
errorCode: undefined,
|
||||
startTime: start - 500,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("onStreamComplete: registered hook receives usage + timing payload", async () => {
|
||||
let captured: Record<string, unknown> | undefined;
|
||||
registerHook(
|
||||
"onStreamComplete",
|
||||
"test-onstreamcomplete-plugin",
|
||||
async (payload: Record<string, unknown>) => {
|
||||
captured = payload;
|
||||
}
|
||||
);
|
||||
|
||||
const startTime = Date.now() - 500;
|
||||
await runPluginOnStreamCompleteHook({
|
||||
status: 200,
|
||||
usage: { prompt_tokens: 42, completion_tokens: 100, reasoning_tokens: 5 },
|
||||
ttft: 200,
|
||||
model: "claude-3-opus",
|
||||
provider: "anthropic",
|
||||
errorCode: undefined,
|
||||
startTime,
|
||||
});
|
||||
|
||||
await waitFor(() => captured !== undefined);
|
||||
assert.ok(captured, "expected onStreamComplete hook to be invoked");
|
||||
|
||||
// payload shape: status, usage, timing, model, provider
|
||||
assert.equal(captured!.status, 200);
|
||||
assert.ok(captured!.usage, "usage should be present");
|
||||
assert.equal((captured!.usage as Record<string, number>).prompt_tokens, 42);
|
||||
assert.equal((captured!.usage as Record<string, number>).completion_tokens, 100);
|
||||
assert.equal((captured!.usage as Record<string, number>).reasoning_tokens, 5);
|
||||
|
||||
assert.ok(captured!.timing, "timing should be present");
|
||||
const timing = captured!.timing as Record<string, number>;
|
||||
assert.equal(timing.ttft, 200);
|
||||
assert.ok(timing.latencyMs > 450, "latencyMs should be near 500");
|
||||
|
||||
assert.equal(captured!.model, "claude-3-opus");
|
||||
assert.equal(captured!.provider, "anthropic");
|
||||
assert.equal(captured!.errorCode, undefined);
|
||||
});
|
||||
|
||||
test("onStreamComplete: payload includes cache token fields when present", async () => {
|
||||
let captured: Record<string, unknown> | undefined;
|
||||
registerHook(
|
||||
"onStreamComplete",
|
||||
"test-onstreamcomplete-plugin",
|
||||
async (payload: Record<string, unknown>) => {
|
||||
captured = payload;
|
||||
}
|
||||
);
|
||||
|
||||
await runPluginOnStreamCompleteHook({
|
||||
status: 200,
|
||||
usage: {
|
||||
prompt_tokens: 50,
|
||||
completion_tokens: 30,
|
||||
cache_read_input_tokens: 20,
|
||||
cache_creation_input_tokens: 10,
|
||||
},
|
||||
ttft: 100,
|
||||
model: "gpt-4",
|
||||
provider: "openai",
|
||||
errorCode: undefined,
|
||||
startTime: Date.now(),
|
||||
});
|
||||
|
||||
await waitFor(() => captured !== undefined);
|
||||
assert.ok(captured);
|
||||
const usage = captured!.usage as Record<string, number>;
|
||||
assert.equal(usage.cache_read_input_tokens, 20);
|
||||
assert.equal(usage.cache_creation_input_tokens, 10);
|
||||
});
|
||||
|
||||
test("onStreamComplete: throwing hook never rejects the caller (fail-open)", async () => {
|
||||
registerHook("onStreamComplete", "test-onstreamcomplete-plugin", async () => {
|
||||
throw new Error("stream-complete-boom");
|
||||
});
|
||||
|
||||
await assert.doesNotReject(
|
||||
runPluginOnStreamCompleteHook({
|
||||
status: 500,
|
||||
usage: undefined,
|
||||
ttft: undefined,
|
||||
model: "gpt-4",
|
||||
provider: "openai",
|
||||
errorCode: "upstream_error",
|
||||
startTime: Date.now(),
|
||||
})
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 30));
|
||||
});
|
||||
|
||||
test("onStreamComplete: errorCode is passed through when provided", async () => {
|
||||
let captured: Record<string, unknown> | undefined;
|
||||
registerHook(
|
||||
"onStreamComplete",
|
||||
"test-onstreamcomplete-plugin",
|
||||
async (payload: Record<string, unknown>) => {
|
||||
captured = payload;
|
||||
}
|
||||
);
|
||||
|
||||
await runPluginOnStreamCompleteHook({
|
||||
status: 502,
|
||||
usage: undefined,
|
||||
ttft: undefined,
|
||||
model: "grok-3",
|
||||
provider: "xai",
|
||||
errorCode: "upstream_timeout",
|
||||
startTime: Date.now(),
|
||||
});
|
||||
|
||||
await waitFor(() => captured !== undefined);
|
||||
assert.ok(captured);
|
||||
assert.equal(captured!.status, 502);
|
||||
assert.equal(captured!.errorCode, "upstream_timeout");
|
||||
assert.equal(captured!.model, "grok-3");
|
||||
assert.equal(captured!.provider, "xai");
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
const REPO_ROOT = join(import.meta.dirname, "..", "..");
|
||||
|
||||
/**
|
||||
* Regression guards for the CodeQL alerts triaged on 2026-08-12.
|
||||
*
|
||||
* Both are source-level invariants rather than behavioral round-trips: the functions they
|
||||
* protect are module-private (`decodeXmlText`) or only reachable through a live upstream
|
||||
* handshake (`tinycms` nonce), so the guard asserts the property on the source itself.
|
||||
*/
|
||||
|
||||
test("decodeXmlText decodes & last so encoded entities do not double-unescape", () => {
|
||||
const source = readFileSync(
|
||||
join(REPO_ROOT, "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/environment.ts"),
|
||||
"utf8"
|
||||
);
|
||||
const body = /function decodeXmlText\(value: string\): string \{([\s\S]*?)\n\}/.exec(source)?.[1];
|
||||
assert.ok(body, "decodeXmlText not found — update this guard if the helper was renamed");
|
||||
|
||||
const order = [...body.matchAll(/replaceAll\("(&[^"]+;)"/g)].map((match) => match[1]);
|
||||
assert.ok(order.length >= 2, `expected several entity replacements, got ${order.length}`);
|
||||
assert.equal(
|
||||
order.at(-1),
|
||||
"&",
|
||||
`"&" must be the LAST entity decoded, otherwise "&quot;" decodes to '"' instead ` +
|
||||
`of the literal """. Current order: ${order.join(" -> ")}`
|
||||
);
|
||||
|
||||
// Mirror the implementation to document the property the ordering buys us.
|
||||
const decode = (value: string) =>
|
||||
order.reduce((acc, entity) => {
|
||||
const plain = { "<": "<", ">": ">", """: '"', "'": "'", "&": "&" }[entity];
|
||||
return plain === undefined ? acc : acc.replaceAll(entity, plain);
|
||||
}, value);
|
||||
assert.equal(decode("&quot;"), """);
|
||||
assert.equal(decode("&#39;"), "'");
|
||||
assert.equal(decode("&lt;"), "<");
|
||||
});
|
||||
|
||||
test("tinycms signs its anti-replay nonce with a CSPRNG, never Math.random", () => {
|
||||
const source = readFileSync(join(REPO_ROOT, "open-sse/executors/tinycms.ts"), "utf8");
|
||||
|
||||
assert.match(
|
||||
source,
|
||||
/const nonceJs = randomUUID\(\)/,
|
||||
"the tinycms nonce must come from node:crypto randomUUID"
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/Math\.random\(\)/,
|
||||
"Math.random() is not a CSPRNG — the nonce is signed into the anti-replay payload"
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user