fokemal / shared /types.ts
automindy's picture
Sync to upstream v0.13.7, keep HF Space adaptations
dcedb25
Raw History Blame Contribute Delete
24.6 kB
// ---- Platform & Model Types ----
/** How the global outbound proxy URL is interpreted. Per-key proxies always
* use the traditional forward-proxy transport. */
export type ProxyMode = 'forward' | 'fetch-relay';
/** A model declared beside a custom endpoint in an import file (#382). A
* capability flag is present only when the paste declared it via a trailing
* -TOOLS / -VISION suffix. */
export interface ImportModelEntry {
id: string;
supportsTools?: boolean;
supportsVision?: boolean;
}
export interface PreviewKey {
keyName: string;
keyValue: string;
detectedPlatform: string | null;
prefix: string;
/** Custom endpoints only: the upstream URL the export file carried (#687). */
baseUrl?: string;
/** Custom endpoints only: models to register alongside the key (#382). */
models?: ImportModelEntry[];
isDuplicate?: boolean;
}
export interface ImportKey {
keyName: string;
keyValue: string;
platform: string;
baseUrl?: string;
models?: ImportModelEntry[];
}
export interface PreviewResponse {
keys: PreviewKey[];
total: number;
skipped: string[];
duplicates: number;
}
export interface ImportSelectedRequest {
keys: ImportKey[];
}
export interface ImportSelectedResponse {
imported: number;
skipped: string[];
errors: Array<{ key: string; error: string }>;
total: number;
/** Models registered for imported custom endpoints (#382). */
modelsRegistered: number;
}
// Active platforms — must match server/src/providers/index.ts and
// server/src/routes/keys.ts PLATFORMS allowlist.
// Moonshot and MiniMax direct integrations were dropped in migrateModelsV4
// (see server/src/db/index.ts). HuggingFace was dropped in V4 and re-added
// in V13 via the router.huggingface.co Inference Providers meta-router.
// SambaNova was dropped in V23 (free tier permanently retired — 402
// "payment method required" once the one-time $5 trial credit lapses).
export type Platform =
| 'google'
| 'groq'
| 'cerebras'
// Sail Research — native Responses API provider. $5 in free credits refreshes
// monthly when a payment method is attached; usage beyond the grant is
// pay-as-you-go. Background polling is required for its flex-only models.
| 'sail'
// Responses-only gateway; a shared monthly free allowance, not per model.
| 'aclide'
// OpenAI-compatible chat and embeddings; one shared $1 monthly allowance.
| 'speka'
// Ongoing free research API for chat and speech recognition; catalog only.
| 'typhoon'
// Permanent free chat aliases, shared fair-use quota; signed catalog only.
| 'plugsky'
// Catalog-only free chat gateways; shared monthly/daily account allowances.
| 'inferbase'
| 'simplellm'
// Selected zero-priced routes have daily/rolling quotas without a top-up;
// signed catalog only. The public roster also includes paid/promotional IDs.
| 'llmtr'
// Catalog-managed free routes: monthly request quota / ongoing $0 models.
| 'gizmo'
| 'blockrun'
// Hosted vision API: $5/workspace in recurring monthly credits, shared
// across models. Signed catalog only; no bundled model seeds.
| 'moondream'
// Hosted gateways; model rows are delivered by the signed catalog only.
// ElectronHub renews weekly credits; Experiential renews monthly credits.
| 'electronhub'
| 'experiential'
// Catalog-managed gateways: monthly shared credits vs daily free-model quota.
| 'router9'
| 'septor'
| 'clod'
| 'speechify'
| 'blaze'
// Lucidity Composite, Api.Airforce and DreamPrompting expose daily free
// allowances; Waterfall and Logfare publish community / fair-use tiers.
| 'lucidity'
| 'airforce'
| 'dreamprompting'
| 'waterfall'
| 'logfare'
// B.AI — OpenAI-compatible gateway. Its catalog row is a live-tested,
// limited-time 0-credit promotion, not a recurring free allowance.
| 'bai'
// AnyAPI — OpenAI-compatible gateway. Free tier is $0/no card/recurring but
// capped at 100K tokens/day over "free and basic" models only; no RPM/RPD is
// published. Catalog rows live in the hosted catalog (premium now, free after
// 30 days).
| 'anyapi'
// AMD Radeon Cloud TokenFactory — OpenAI-compatible shared inference. Its
// rotating public-model roster is free without consuming instance credits,
// with recurring account-level allowance and request/concurrency controls.
| 'radeon'
| 'nvidia'
| 'mistral'
| 'sambanova'
| 'openrouter'
| 'github'
| 'cohere'
| 'cloudflare'
| 'zhipu'
| 'ollama'
| 'kilo'
| 'pollinations'
| 'llm7'
| 'huggingface'
// OpenCode Zen — OpenAI-compatible gateway. Free promotional models require a
// free (no-card) account key from opencode.ai/auth; see migrateModelsV18.
| 'opencode'
// OVHcloud AI Endpoints — OpenAI-compatible, keyless anonymous tier
// (2 req/min per IP per model); see migrateModelsV26.
| 'ovh'
// Agnes AI (Sapiens AI) — OpenAI-compatible (LiteLLM + vLLM backend). Serves
// its own proprietary Agnes models; the free key comes from
// platform.agnes-ai.com (no card).
| 'agnes'
// Reka — OpenAI-compatible. Native multimodal models (reka-edge takes
// image/video). New accounts need prepaid credits (#1202); key from
// platform.reka.ai.
| 'reka'
// SiliconFlow — OpenAI-compatible. Registered for its FREE generative-media
// models (FLUX.1-schnell image, CosyVoice2 TTS) routed via services/media.ts;
// chat is supported too. Key from siliconflow.com (no card).
| 'siliconflow'
// Routeway — OpenAI-compatible aggregator. Free ':free' models ($0) on a
// rate-limited pool (~5 rpm observed); requires a browser User-Agent (CF
// blocks others). Key from routeway.ai (no card).
| 'routeway'
// BazaarLink — OpenAI-compatible aggregator. Free 'auto:free' route picks an
// available zero-cost model. Key from bazaarlink.ai (no card).
| 'bazaarlink'
// AINative Studio — OpenAI-compatible aggregator. Advertises a recurring
// ~10M tokens/month free allocation (no card); quota unverified. Key from
// ainative.studio.
| 'ainative'
// Aion Labs — OpenAI-compatible aggregator with a no-card free API key.
// Catalog rows live in the Oracle catalog (premium now, free after 30 days).
| 'aion'
// Requesty — OpenAI-compatible router with no-card free models/credits.
// Catalog rows live in the Oracle catalog (premium now, free after 30 days).
| 'requesty'
// NavyAI — OpenAI-compatible unified API. Free plan is 150K tokens/day and
// 20 RPM; catalog rows live in the Oracle catalog (premium now, free after 30 days).
| 'navy'
// NaraRouter — OpenAI-compatible aggregator. Free account key from
// router.bynara.id after Telegram channel/link verification; free-plan routes
// reset daily and are catalog-managed (premium now, free after 30 days).
| 'nara'
// SEA-LION (AI Singapore) — OpenAI-compatible first-party API. Free key
// (Google sign-in, no card, no region wall) at 10 RPM; catalog rows live in
// the Oracle catalog (premium now, free after 30 days).
| 'sealion'
// OrcaRouter — OpenAI-compatible aggregator (api.orcarouter.ai/v1). Free key
// from orcarouter.ai (no card); recurring rate-limited free aliases at $0
// (never fall back to paid). Catalog rows live in the Oracle catalog
// (premium now, free after the 30-day model-age gate).
| 'orcarouter'
// UnoRouter (unorouter.com) — OpenAI-compatible aggregator. The web app is a
// Next.js site at unorouter.com; the API lives at api.unorouter.com/v1. Free
// key from unorouter.com (no card); free models carry a `:free` suffix and a
// per-minute rate limit (429 on cap, e.g. "1 request(s) every 1 min").
// Live-probed 2026-08-23: /v1/models is public without a key but 401s on a
// wrong key, and /v1/chat/completions 401s without a key, so default key
// validation works. Catalog rows live in the hosted catalog (premium now,
// free after the 30-day model-age gate).
| 'unorouter'
// xKiro (xkiro.com) — OpenAI-compatible gateway at api.xkiro.com/v1. Free key
// from xkiro.com (no card); free plan is 5M tokens/day on its free models,
// paid models 403 on a free key.
// /v1/models is public (200 with no key), so key validation must probe
// /v1/usage, which 401s on a missing/invalid ClientApiKey. Catalog rows live
// in the hosted catalog (premium now, free after the 30-day model-age gate).
| 'xkiro'
// ModelScope (魔搭社区, Alibaba) — OpenAI-compatible inference API
// (api-inference.modelscope.cn/v1). Free tier is 2000 requests/day
// account-wide, but calls only work after the ModelScope account is bound to
// an Alibaba Cloud CHINA-site (cn) account with Chinese real-name
// verification — tokens mint without binding, then every call 401s. Catalog
// rows land after community testing confirms per-model behavior (#581).
| 'modelscope'
// ── Chinese domestic providers (#922/#923/#924) ────────────────────────────
// All four need Chinese real-name verification (实名认证) on the cloud account
// before a key will serve traffic, the same wall ModelScope hits above.
// LongCat is the exception worth knowing: its platform accepts an email
// signup from outside mainland China.
//
// Baidu Qianfan (百度千帆) — OpenAI-compatible (https://qianfan.baidubce.com/v2).
// The ERNIE-Speed / ERNIE-Lite / ERNIE-Tiny series are free indefinitely via
// pay-as-you-go billing rather than a token pool, so the ceiling is rate
// limits, not a balance. Baidu calls the arrangement "long-term". Real-name
// auth (individual or enterprise) required.
| 'qianfan'
// Volcengine Ark (火山方舟, ByteDance) — OpenAI-compatible
// (https://ark.cn-beijing.volces.com/api/v3). Individual developers get a
// RECURRING daily per-model free reward quota (raised from 500K to 2M
// tokens/day/model), on top of a one-time 500K new-user grant. The strongest
// recurring free tier of the four.
| 'volcengine'
// LongCat (Meituan / 美团) — OpenAI-compatible
// (https://api.longcat.chat/openai/v1); also exposes an Anthropic-compatible
// surface at /anthropic. Free tier is daily; the figure quoted at platform
// launch was 100K tokens/day. Meituan has ANNOUNCED a 50M tokens/day
// Flash-Lite free tier but it was described as a future plan, so it is not
// treated as live here.
| 'longcat'
// iFlytek Spark (讯飞星火) — OpenAI-compatible
// (https://spark-api-open.xf-yun.com/v1), Bearer auth using the console's
// APIPassword (not the APIKey/APISecret pair the older WebSocket API used).
// The Lite model (model id `lite`) is documented as free to call; iFlytek
// does not publish a token ceiling or a QPS figure for it, so neither is
// claimed here.
| 'xfyun'
// AI Horde — free, community-powered inference (volunteer workers) via an
// OpenAI-compatible proxy (https://oai.aihorde.net/v1). Queue-based, so calls
// can take tens of seconds; no tool support; usage is reported as kudos, not
// tokens. Anonymous key `0000000000` works (lowest priority); a registered
// aihorde.net key raises queue priority. Has a dedicated AIHordeProvider that
// normalizes the proxy's OpenAI divergences. See issue #345.
| 'aihorde'
// User-configured OpenAI-compatible endpoint (llama.cpp, LM Studio, vLLM,
// Ollama, any base_url). The endpoint URL lives on the api_keys row; see #117.
| 'custom';
export interface Model {
id: number;
platform: Platform;
modelId: string;
displayName: string;
intelligenceRank: number;
speedRank: number;
sizeLabel: string;
rpmLimit: number | null;
rpdLimit: number | null;
tpmLimit: number | null;
tpdLimit: number | null;
monthlyTokenBudget: string;
contextWindow: number | null;
enabled: boolean;
supportsVision: boolean;
supportsTools: boolean;
/** 'discovered': fetched from a built-in provider's own /models because the
* catalog carries no models for it (#1348). */
source?: 'catalog' | 'custom' | 'discovered';
keyId?: number | null;
endpointScope?: string | null;
}
// ---- Quirks ----
// Structured, reusable notes about catalog models. One quirk is applied to many
// models via selector parameters (see quirk_targets / services/quirks.ts).
export type QuirkSeverity = 'info' | 'warning' | 'blocker';
export interface Quirk {
slug: string;
title: string;
body: string;
severity: QuirkSeverity;
}
export interface QuirkTarget {
platform: Platform | null;
modelGlob: string | null;
}
export interface ModelListRow {
platform: string;
model_id: string;
display_name: string;
context_window: number | null;
// 1 when the catalog row is enabled. 1 when an enabled key can serve it
// (enabled AND a matching enabled api_key exists). SQLite returns 0/1.
enabled: number;
available: number;
}
export type KeyStatus = 'healthy' | 'rate_limited' | 'invalid' | 'error' | 'unknown';
export interface ApiKeyModel {
id: number;
kind: 'chat' | 'embedding' | 'image' | 'audio' | 'transcription';
modelId: string;
displayName: string;
family?: string | null;
}
/** An active rate-limit cooldown on one model for a key. A key can be healthy
* and enabled yet still skipped by the router because of these. */
export interface ApiKeyCooldown {
modelId: string;
expiresAtMs: number;
remainingMs: number;
}
export interface ApiKey {
id: number;
platform: Platform;
label: string;
maskedKey: string;
baseUrl: string | null;
/** Optional custom-endpoint group label (#1176); null = legacy single
* "Custom" group. Only set on platform='custom' rows. */
groupLabel?: string | null;
status: KeyStatus;
enabled: boolean;
/** This row is the anonymous sentinel of a key-optional platform: there is
* no credential to copy, scope or reveal. */
keyless: boolean;
/** The platform works with or without a key (Kilo, OVH, AI Horde), so a key
* can be added to or left off this row (#1331). */
keyOptional?: boolean;
/** Whether an export file would actually contain this row. The server decides
* it so the dialog's "will export N keys" cannot drift from the export. */
exportable: boolean;
createdAt: string;
lastCheckedAt: string | null;
lastHealthError: string | null;
/** Model ids this key is limited to; null = serves every model of its
* platform (#657). */
modelScope?: string[] | null;
/** Per-key monthly request cap (0 = unlimited, #1158). */
monthlyRequestCap?: number;
/** Per-key monthly token cap (0 = unlimited, #1158). */
monthlyTokenCap?: number;
/** Current UTC month's successful usage against the caps above, with the
* ISO time of the next monthly reset. */
monthlyUsage?: { requests: number; tokens: number; resetsAt: string };
/** The per-key proxy override with its password masked (#590); '' = none. */
maskedProxyUrl?: string;
models?: ApiKeyModel[];
cooldowns?: ApiKeyCooldown[];
/** True for a built-in provider key whose platform the catalog carries no
* models for, so the dashboard offers Fetch models on it (#1348). */
modelDiscovery?: boolean;
}
export interface ApiKeyCreate {
platform: Platform;
key: string;
label?: string;
}
// ---- Fallback Config ----
export interface FallbackEntry {
modelId: number;
platform: Platform;
displayName: string;
intelligenceRank: number;
speedRank: number;
priority: number;
enabled: boolean;
// Present when model unification is enabled — identifies the logical model
// this provider row belongs to so the dashboard can render grouped rows.
groupKey?: string;
canonicalId?: string;
groupLabel?: string;
}
// ---- Model Grouping (unify the same model across providers) ----
// One logical model can be served by several providers (rows in the `models`
// table). When unification is enabled, those rows collapse into a single group
// keyed by a normalized display name; see server/src/services/model-groups.ts.
export interface ModelGroupInfo {
groupKey: string; // normalized display name — the grouping identity
canonicalId: string; // stable slug advertised on /v1/models
groupLabel: string; // human label (suffix-stripped display name)
}
export interface UnifyOverrides {
// Coalesce several normalized display-names (or exact "platform:model_id"
// members) into one group keyed by `into`.
merges: { into: string; keys: string[] }[];
// Force a specific "platform:model_id" row out of its computed group.
splits: { member: string; groupKey?: string }[];
}
export interface UnifySettings {
enabled: boolean;
overrides: UnifyOverrides;
}
// ---- OpenAI-Compatible Types ----
export interface ChatToolCallFunction {
name: string;
arguments: string;
}
export interface ChatToolCall {
id: string;
type: 'function';
function: ChatToolCallFunction;
thought_signature?: string;
}
export interface ChatToolFunctionDefinition {
name: string;
description?: string;
parameters?: Record<string, unknown>;
strict?: boolean;
}
export interface ChatToolDefinition {
type: 'function';
function: ChatToolFunctionDefinition;
}
export type ChatToolChoice =
| 'none'
| 'auto'
| 'required'
| {
type: 'function';
function: {
name: string;
};
};
// OpenAI's multimodal envelope: clients like opencode / continue.dev send
// content as an array of typed blocks even for text-only messages, and
// Gemini-lineage agents (Qwen Code, AionUI) send part-style `{ text }` blocks
// with no `type` — plus bare strings inside arrays. We accept all of it on
// the wire and flatten to string for providers that don't support arrays
// (Cohere, Cloudflare). See server/src/lib/content.ts. (#200)
export type ChatContentBlock = string | { type?: string; text?: string; [key: string]: unknown };
export type ChatContent = string | null | ChatContentBlock[];
export interface ChatMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: ChatContent;
name?: string;
tool_call_id?: string;
tool_calls?: ChatToolCall[];
// The model's thinking trace on an assistant turn. Some thinking models
// (DeepSeek on OpenCode Zen) require it to be replayed verbatim on the next
// turn or they 400; the proxy preserves and forwards it. See issue #255.
reasoning_content?: string;
// Moonshot's "partial" prefill flag on an assistant turn: when true, the
// model continues the given text instead of starting a fresh turn. Only
// forwarded to models that understand it (Moonshot/Kimi); stripped for all
// other providers. See issue #1038.
partial?: boolean;
}
export interface ChatCompletionRequest {
model?: string;
messages: ChatMessage[];
temperature?: number;
max_tokens?: number;
stream?: boolean;
stream_options?: {
include_usage?: boolean;
};
top_p?: number;
stop?: string | string[];
tools?: ChatToolDefinition[];
tool_choice?: ChatToolChoice;
parallel_tool_calls?: boolean;
}
export interface ChatCompletionChoice {
index: number;
message: ChatMessage;
finish_reason: string | null;
// Present when the client requested logprobs and the provider returned
// them; passed through verbatim (provider shapes vary slightly).
logprobs?: unknown;
}
export interface TokenUsage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
// OpenAI-standard breakdown some providers advertise alongside the totals.
// Optional because many free-tier endpoints omit it; the proxy falls back
// to its chars/4 estimate when absent. (#764)
completion_tokens_details?: { reasoning_tokens?: number };
prompt_tokens_details?: { cached_tokens?: number };
// Gateway-synthesized block (upstream never sent usage): flagged so a
// cost-accounting client can tell it apart from the upstream's real
// counts. (#1084)
estimated?: boolean;
}
export interface ChatCompletionResponse {
id: string;
object: 'chat.completion';
created: number;
model: string;
choices: ChatCompletionChoice[];
usage: TokenUsage;
_routed_via?: {
platform: Platform;
model: string;
};
}
export interface ChatCompletionChunk {
id: string;
object: 'chat.completion.chunk';
created: number;
model: string;
choices: {
index: number;
delta: {
role?: 'assistant';
content?: string;
reasoning_content?: string;
tool_calls?: ChatToolCall[];
};
finish_reason: string | null;
}[];
usage?: TokenUsage;
}
// ---- Analytics Types ----
export interface AnalyticsSummary {
totalRequests: number;
successRate: number;
totalInputTokens: number;
totalOutputTokens: number;
avgLatencyMs: number;
estimatedCostSavings: number;
}
export interface PlatformStats {
platform: Platform;
requests: number;
successRate: number;
avgLatencyMs: number;
totalInputTokens: number;
totalOutputTokens: number;
}
export interface TimelinePoint {
timestamp: string;
requests: number;
successCount: number;
failureCount: number;
}
export interface RequestLog {
id: number;
platform: Platform;
modelId: string;
status: 'success' | 'error';
inputTokens: number;
outputTokens: number;
latencyMs: number;
error: string | null;
createdAt: string;
}
// ---- Rate Limit Types ----
export interface RateLimitStatus {
platform: Platform;
modelId: string;
rpm: { used: number; limit: number | null };
rpd: { used: number; limit: number | null };
tpm: { used: number; limit: number | null };
available: boolean;
nextResetAt: string | null;
}
// ---- Provider Quota Observability ----
export type QuotaMetric = 'requests' | 'tokens' | 'credits' | 'neurons';
export type QuotaResetStrategy = 'fixed_calendar' | 'rolling_window' | 'token_bucket' | 'provider_reported' | 'unknown';
export type QuotaObservationSource = 'header' | 'quota_api' | 'error_body' | 'local_usage' | 'documentation' | 'probe';
export interface ProviderQuotaState {
platform: Platform;
keyId: number;
/** The key's operator-facing label, when the row still names a live key. */
keyLabel?: string | null;
quotaPoolKey: string;
metric: QuotaMetric;
limit: number | null;
remaining: number | null;
resetAt: string | null;
resetStrategy: QuotaResetStrategy;
source: QuotaObservationSource;
confidence: number;
notes: string | null;
observedAt: string;
updatedAt: string;
}
export interface ProviderQuotaObservation extends ProviderQuotaState {
id: string;
statusCode: number | null;
retryAfterMs: number | null;
providerAccountId: string | null;
modelId: string | null;
endpoint: string | null;
rawJson: string | null;
createdAt: string;
}
export interface QuotaOutlookPool {
platform: Platform;
pool: string;
limit: number | null;
remaining: number | null;
remainingPct: number | null;
observedAt: string | null;
resetAt: string | null;
/** Successful requests through this instance during the observation window. */
recentRequestCount: number;
ratePerMin: number;
unavailableReason: 'quota_not_reported' | 'stale_observation' | 'reset_not_reported' | 'low_confidence' | null;
estimatedExhaustionAt: string | null;
status: 'unknown' | 'stale' | 'unavailable' | 'insufficient_data' | 'resets_first' | 'forecast' | 'exhausted';
warning: 'low_balance' | 'exhausting_soon' | null;
}
export interface QuotaOutlookResponse {
generatedAt: string;
observationWindowMinutes: number;
minimumRequests: number;
pools: QuotaOutlookPool[];
}
// ---- Provider Dashboard Types ----
export type ProviderHealthStatus = 'healthy' | 'issues' | 'rate_limited' | 'unknown' | 'unconfigured';
export interface ProviderSummary {
platform: Platform;
name: string;
totalKeys: number;
enabledKeys: number;
healthyKeys: number;
totalModels: number;
activeModels: number;
status: ProviderHealthStatus;
isConfigured: boolean;
}
export interface GroupedProvider {
id: string;
platform: Platform;
name: string;
url?: string;
baseUrl?: string | null;
endpointScope?: string | null;
keyless?: boolean;
keys: ApiKey[];
models: Model[];
summary: ProviderSummary;
}
export interface CustomModelCreate {
platform: Platform;
modelId: string;
displayName?: string;
contextWindow?: number | null;
rpmLimit?: number | null;
rpdLimit?: number | null;
tpmLimit?: number | null;
tpdLimit?: number | null;
supportsVision?: boolean;
supportsTools?: boolean;
}
export interface ModelTestResult {
success: boolean;
modelId: string;
latencyMs: number;
error?: string;
}