import { config } from "./config"; export type AdapterId = | "anthropic" | "openai" | "gemini" | "deepseek" | "kimi" | "zhipu" | "minimax" | "qwen" | "codex-cli"; /** * Provider source bucket — every model declares which call paths can * serve it. Availability is now a per-source check rather than a global * profile gate, so e.g. MiniMax/Kimi work whether or not the hosted * Replit integrations are configured. `DOATLAS_PROFILE` is downgraded * to a *preference filter* in `listModels` (it controls ordering / * default model picking, not whether a configured key works). */ export type ModelSource = | "replit-integration" | "byok-direct" | "china-direct"; export interface ModelInfo { id: string; name: string; provider: string; description: string; context_length: number; capabilities: Array<"text" | "vision" | "tools" | "streaming">; pricing: { input_per_1k: number; output_per_1k: number }; default: boolean; available: boolean; // Internal routing hints (not exposed to client). tier: "S" | "A"; adapter: AdapterId; upstreamModel: string; /** * Source buckets this model can be served from. The first matching * bucket with credentials wins; we never try to fall through to a * different source mid-call. */ sources: ModelSource[]; } const ALL_MODELS: ModelInfo[] = [ // -- S tier (旗舰) -- { id: "mdl_claude-sonnet-4-6", name: "DoAtlas Pro", provider: "DoAtlas", description: "旗舰研究模型,深度推理与代码能力最强,适合复杂文献综述、机制推理与多步分析。", context_length: 200_000, capabilities: ["text", "vision", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "S", adapter: "anthropic", upstreamModel: "claude-sonnet-4-6", sources: ["replit-integration", "byok-direct"], }, { id: "mdl_gpt-5_2", name: "DoAtlas Pro X", provider: "DoAtlas", description: "多模态旗舰模型,视觉与文本兼顾,适合图表解读、影像分析与通用研究问答。", context_length: 128_000, capabilities: ["text", "vision", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "S", adapter: "openai", upstreamModel: "gpt-5.2", sources: ["replit-integration", "byok-direct"], }, { id: "mdl_gemini-2_5-pro", name: "DoAtlas Vision", provider: "DoAtlas", description: "Gemini 2.5 Pro 多模态旗舰,超长上下文与图表理解,适合长文档解析与多模态研究问答。", context_length: 1_000_000, capabilities: ["text", "vision", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "S", adapter: "gemini", upstreamModel: "gemini-2.5-pro", sources: ["replit-integration", "byok-direct"], }, { id: "mdl_deepseek-r1", name: "DoAtlas Reason Pro", provider: "DoAtlas", description: "DeepSeek R1 推理模型,专注链式推理与数学/科学论证,适合机制假设与复杂证据综合。", context_length: 128_000, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "S", adapter: "deepseek", upstreamModel: "deepseek-reasoner", sources: ["china-direct"], }, { id: "mdl_codex-cli", name: "DoAtlas Code", provider: "DoAtlas Local", description: "本地代码助手,运行在你自己的机器上,可读写本地文件与执行命令;需在本地部署中配置。", context_length: 200_000, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "S", adapter: "codex-cli", upstreamModel: "codex-cli", sources: ["byok-direct"], }, // -- A tier (通用) -- { id: "mdl_glm-4_6", name: "DoAtlas Air", provider: "DoAtlas", description: "智谱 GLM-4 系列,长上下文 128k,响应快速,适合日常问答与长文本处理。", context_length: 131_072, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "zhipu", upstreamModel: "glm-4-plus", sources: ["china-direct"], }, { id: "mdl_minimax-m2", name: "DoAtlas Search", provider: "DoAtlas", description: "MiniMax M2 智能体模型,配合统一 web_search 工具实现实时联网检索,适合需要最新资料的研究问答。", context_length: 200_000, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "minimax", upstreamModel: "MiniMax-M2.7-highspeed", sources: ["china-direct"], }, { id: "mdl_deepseek-v3", name: "DoAtlas Chat", provider: "DoAtlas", description: "DeepSeek V3 通用对话模型,性价比高,适合日常问答与代码补全。", context_length: 128_000, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "deepseek", upstreamModel: "deepseek-chat", sources: ["china-direct"], }, { id: "mdl_kimi-k2", name: "DoAtlas Long", provider: "DoAtlas", description: "Kimi 长文本模型,超长上下文与文档检索能力强,适合大文档/多 PDF 阅读。", context_length: 256_000, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "kimi", upstreamModel: "kimi-k2-0905-preview", sources: ["china-direct"], }, { id: "mdl_qwen-max", name: "DoAtlas Bilingual", provider: "DoAtlas", description: "通义千问 Max 双语通用模型,中英推理与工具调用稳定,适合中文研究场景。", context_length: 131_072, capabilities: ["text", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "qwen", upstreamModel: "qwen-max", sources: ["china-direct"], }, { id: "mdl_gemini-2_5-flash", name: "DoAtlas Vision Lite", provider: "DoAtlas", description: "Gemini 2.5 Flash 轻量多模态模型,速度快、成本低,适合实时问答与轻量视觉任务。", context_length: 1_000_000, capabilities: ["text", "vision", "tools", "streaming"], pricing: { input_per_1k: 0, output_per_1k: 0 }, default: false, available: false, tier: "A", adapter: "gemini", upstreamModel: "gemini-2.5-flash", sources: ["replit-integration", "byok-direct"], }, ]; /** * Adapter-by-source availability table. A model is available when at * least one of its declared sources has credentials configured. */ function sourceAvailable(adapter: AdapterId, source: ModelSource): boolean { switch (adapter) { case "anthropic": if (source === "replit-integration") return config.hasAnthropicCloud; if (source === "byok-direct") return config.hasAnthropicDirect; return false; case "openai": if (source === "replit-integration") return config.hasOpenAICloud; if (source === "byok-direct") return config.hasOpenAIDirect; return false; case "gemini": if (source === "replit-integration") return config.hasGeminiCloud; if (source === "byok-direct") return config.hasGeminiDirect; return false; case "deepseek": return source === "china-direct" && config.hasDeepseek; case "kimi": return source === "china-direct" && config.hasKimi; case "zhipu": return source === "china-direct" && config.hasZhipu; case "minimax": return source === "china-direct" && config.hasMinimax; case "qwen": return source === "china-direct" && config.hasQwen; case "codex-cli": return source === "byok-direct" && config.hasCodexCli; default: return false; } } function isAvailable(m: ModelInfo): boolean { return m.sources.some((s) => sourceAvailable(m.adapter, s)); } /** * Return the source actually used to call this model. The first * configured source from the model's `sources` list wins. */ export function resolveSource(m: ModelInfo): ModelSource | null { for (const s of m.sources) { if (sourceAvailable(m.adapter, s)) return s; } return null; } export function listModels(): ModelInfo[] { const profile = config.profile; const out = ALL_MODELS.map((m) => ({ ...m, available: isAvailable(m), })); // Profile is now a preference filter for default selection only. // "cloud" prefers replit-integration/byok-direct models; "local" // accepts everything that's available. const preferCloud = profile === "cloud"; const candidates = preferCloud ? out.filter( (m) => m.available && m.sources.some((s) => s === "replit-integration" || s === "byok-direct"), ) : out.filter((m) => m.available); const first = candidates[0] ?? out.find((m) => m.available); if (first) { const target = out.find((m) => m.id === first.id); if (target) target.default = true; } return out; } export function findModel(id: string | null | undefined): ModelInfo | null { if (!id) return null; return listModels().find((m) => m.id === id) ?? null; } export function pickDefaultModel(): ModelInfo | null { return listModels().find((m) => m.default) ?? null; } export function publicModel(m: ModelInfo) { // Strip internal routing fields. const { tier: _t, adapter: _a, upstreamModel: _u, sources: _s, ...pub } = m; return pub; }