UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,778 lines (1,765 loc) 122 kB
import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js"; import { o as resolveOAuthPath, s as resolveStateDir } from "./paths-CyR9Pa1R.js"; import { n as DEFAULT_AGENT_ID } from "./session-key-CgcjHuX_.js"; import { I as resolveUserPath } from "./registry-BmY4gNy6.js"; import { a as resolveAgentModelPrimary, n as resolveAgentConfig } from "./agent-scope-BLYwrEEA.js"; import { t as createSubsystemLogger } from "./subsystem-B5g771Td.js"; import { r as isTruthyEnvValue, t as formatCliCommand } from "./command-format-Ck2WauwF.js"; import { a as saveJsonFile, i as loadJsonFile, r as resolveCopilotApiToken, t as DEFAULT_COPILOT_API_BASE_URL } from "./github-copilot-token-BQ98eazZ.js"; import fs from "node:fs/promises"; import path from "node:path"; import fs$1 from "node:fs"; import { execFileSync } from "node:child_process"; import { createHash, randomBytes, randomUUID } from "node:crypto"; import { createAssistantMessageEventStream, getEnvApiKey, getOAuthApiKey, getOAuthProviders } from "@mariozechner/pi-ai"; import { BedrockClient, ListFoundationModelsCommand } from "@aws-sdk/client-bedrock"; //#region src/infra/shell-env.ts const DEFAULT_TIMEOUT_MS = 15e3; const DEFAULT_MAX_BUFFER_BYTES = 2 * 1024 * 1024; let lastAppliedKeys = []; let cachedShellPath; function resolveTimeoutMs(timeoutMs) { if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) return DEFAULT_TIMEOUT_MS; return Math.max(0, timeoutMs); } function resolveShell(env) { const shell = env.SHELL?.trim(); return shell && shell.length > 0 ? shell : "/bin/sh"; } function execLoginShellEnvZero(params) { return params.exec(params.shell, [ "-l", "-c", "env -0" ], { encoding: "buffer", timeout: params.timeoutMs, maxBuffer: DEFAULT_MAX_BUFFER_BYTES, env: params.env, stdio: [ "ignore", "pipe", "pipe" ] }); } function parseShellEnv(stdout) { const shellEnv = /* @__PURE__ */ new Map(); const parts = stdout.toString("utf8").split("\0"); for (const part of parts) { if (!part) continue; const eq = part.indexOf("="); if (eq <= 0) continue; const key = part.slice(0, eq); const value = part.slice(eq + 1); if (!key) continue; shellEnv.set(key, value); } return shellEnv; } function loadShellEnvFallback(opts) { const logger = opts.logger ?? console; const exec = opts.exec ?? execFileSync; if (!opts.enabled) { lastAppliedKeys = []; return { ok: true, applied: [], skippedReason: "disabled" }; } if (opts.expectedKeys.some((key) => Boolean(opts.env[key]?.trim()))) { lastAppliedKeys = []; return { ok: true, applied: [], skippedReason: "already-has-keys" }; } const timeoutMs = resolveTimeoutMs(opts.timeoutMs); const shell = resolveShell(opts.env); let stdout; try { stdout = execLoginShellEnvZero({ shell, env: opts.env, exec, timeoutMs }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); logger.warn(`[openclaw] shell env fallback failed: ${msg}`); lastAppliedKeys = []; return { ok: false, error: msg, applied: [] }; } const shellEnv = parseShellEnv(stdout); const applied = []; for (const key of opts.expectedKeys) { if (opts.env[key]?.trim()) continue; const value = shellEnv.get(key); if (!value?.trim()) continue; opts.env[key] = value; applied.push(key); } lastAppliedKeys = applied; return { ok: true, applied }; } function shouldEnableShellEnvFallback(env) { return isTruthyEnvValue(env.OPENCLAW_LOAD_SHELL_ENV); } function shouldDeferShellEnvFallback(env) { return isTruthyEnvValue(env.OPENCLAW_DEFER_SHELL_ENV_FALLBACK); } function resolveShellEnvFallbackTimeoutMs(env) { const raw = env.OPENCLAW_SHELL_ENV_TIMEOUT_MS?.trim(); if (!raw) return DEFAULT_TIMEOUT_MS; const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed)) return DEFAULT_TIMEOUT_MS; return Math.max(0, parsed); } function getShellPathFromLoginShell(opts) { if (cachedShellPath !== void 0) return cachedShellPath; if ((opts.platform ?? process.platform) === "win32") { cachedShellPath = null; return cachedShellPath; } const exec = opts.exec ?? execFileSync; const timeoutMs = resolveTimeoutMs(opts.timeoutMs); const shell = resolveShell(opts.env); let stdout; try { stdout = execLoginShellEnvZero({ shell, env: opts.env, exec, timeoutMs }); } catch { cachedShellPath = null; return cachedShellPath; } const shellPath = parseShellEnv(stdout).get("PATH")?.trim(); cachedShellPath = shellPath && shellPath.length > 0 ? shellPath : null; return cachedShellPath; } function getShellEnvAppliedKeys() { return [...lastAppliedKeys]; } //#endregion //#region src/utils/normalize-secret-input.ts /** * Secret normalization for copy/pasted credentials. * * Common footgun: line breaks (especially `\r`) embedded in API keys/tokens. * We strip line breaks anywhere, then trim whitespace at the ends. * * Intentionally does NOT remove ordinary spaces inside the string to avoid * silently altering "Bearer <token>" style values. */ function normalizeSecretInput(value) { if (typeof value !== "string") return ""; return value.replace(/[\r\n\u2028\u2029]+/g, "").trim(); } function normalizeOptionalSecretInput(value) { const normalized = normalizeSecretInput(value); return normalized ? normalized : void 0; } //#endregion //#region src/agents/auth-profiles/constants.ts const AUTH_STORE_VERSION = 1; const AUTH_PROFILE_FILENAME = "auth-profiles.json"; const LEGACY_AUTH_FILENAME = "auth.json"; const QWEN_CLI_PROFILE_ID = "qwen-portal:qwen-cli"; const MINIMAX_CLI_PROFILE_ID = "minimax-portal:minimax-cli"; const AUTH_STORE_LOCK_OPTIONS = { retries: { retries: 10, factor: 2, minTimeout: 100, maxTimeout: 1e4, randomize: true }, stale: 3e4 }; const EXTERNAL_CLI_SYNC_TTL_MS = 900 * 1e3; const EXTERNAL_CLI_NEAR_EXPIRY_MS = 600 * 1e3; const log$1 = createSubsystemLogger("agents/auth-profiles"); //#endregion //#region src/agents/auth-profiles/display.ts function resolveAuthProfileDisplayLabel(params) { const { cfg, store, profileId } = params; const profile = store.profiles[profileId]; const email = cfg?.auth?.profiles?.[profileId]?.email?.trim() || (profile && "email" in profile ? profile.email?.trim() : void 0); if (email) return `${profileId} (${email})`; return profileId; } //#endregion //#region src/agents/defaults.ts const DEFAULT_PROVIDER = "anthropic"; const DEFAULT_MODEL = "claude-opus-4-6"; const DEFAULT_CONTEXT_TOKENS = 2e5; //#endregion //#region src/agents/bedrock-discovery.ts const DEFAULT_REFRESH_INTERVAL_SECONDS = 3600; const DEFAULT_CONTEXT_WINDOW = 32e3; const DEFAULT_MAX_TOKENS = 4096; const DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const discoveryCache = /* @__PURE__ */ new Map(); let hasLoggedBedrockError = false; function normalizeProviderFilter(filter) { if (!filter || filter.length === 0) return []; const normalized = new Set(filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0)); return Array.from(normalized).toSorted(); } function buildCacheKey(params) { return JSON.stringify(params); } function includesTextModalities(modalities) { return (modalities ?? []).some((entry) => entry.toLowerCase() === "text"); } function isActive(summary) { const status = summary.modelLifecycle?.status; return typeof status === "string" ? status.toUpperCase() === "ACTIVE" : false; } function mapInputModalities(summary) { const inputs = summary.inputModalities ?? []; const mapped = /* @__PURE__ */ new Set(); for (const modality of inputs) { const lower = modality.toLowerCase(); if (lower === "text") mapped.add("text"); if (lower === "image") mapped.add("image"); } if (mapped.size === 0) mapped.add("text"); return Array.from(mapped); } function inferReasoningSupport(summary) { const haystack = `${summary.modelId ?? ""} ${summary.modelName ?? ""}`.toLowerCase(); return haystack.includes("reasoning") || haystack.includes("thinking"); } function resolveDefaultContextWindow(config) { const value = Math.floor(config?.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW); return value > 0 ? value : DEFAULT_CONTEXT_WINDOW; } function resolveDefaultMaxTokens(config) { const value = Math.floor(config?.defaultMaxTokens ?? DEFAULT_MAX_TOKENS); return value > 0 ? value : DEFAULT_MAX_TOKENS; } function matchesProviderFilter(summary, filter) { if (filter.length === 0) return true; const normalized = (summary.providerName ?? (typeof summary.modelId === "string" ? summary.modelId.split(".")[0] : void 0))?.trim().toLowerCase(); if (!normalized) return false; return filter.includes(normalized); } function shouldIncludeSummary(summary, filter) { if (!summary.modelId?.trim()) return false; if (!matchesProviderFilter(summary, filter)) return false; if (summary.responseStreamingSupported !== true) return false; if (!includesTextModalities(summary.outputModalities)) return false; if (!isActive(summary)) return false; return true; } function toModelDefinition(summary, defaults) { const id = summary.modelId?.trim() ?? ""; return { id, name: summary.modelName?.trim() || id, reasoning: inferReasoningSupport(summary), input: mapInputModalities(summary), cost: DEFAULT_COST, contextWindow: defaults.contextWindow, maxTokens: defaults.maxTokens }; } async function discoverBedrockModels(params) { const refreshIntervalSeconds = Math.max(0, Math.floor(params.config?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_SECONDS)); const providerFilter = normalizeProviderFilter(params.config?.providerFilter); const defaultContextWindow = resolveDefaultContextWindow(params.config); const defaultMaxTokens = resolveDefaultMaxTokens(params.config); const cacheKey = buildCacheKey({ region: params.region, providerFilter, refreshIntervalSeconds, defaultContextWindow, defaultMaxTokens }); const now = params.now?.() ?? Date.now(); if (refreshIntervalSeconds > 0) { const cached = discoveryCache.get(cacheKey); if (cached?.value && cached.expiresAt > now) return cached.value; if (cached?.inFlight) return cached.inFlight; } const client = (params.clientFactory ?? ((region) => new BedrockClient({ region })))(params.region); const discoveryPromise = (async () => { const response = await client.send(new ListFoundationModelsCommand({})); const discovered = []; for (const summary of response.modelSummaries ?? []) { if (!shouldIncludeSummary(summary, providerFilter)) continue; discovered.push(toModelDefinition(summary, { contextWindow: defaultContextWindow, maxTokens: defaultMaxTokens })); } return discovered.toSorted((a, b) => a.name.localeCompare(b.name)); })(); if (refreshIntervalSeconds > 0) discoveryCache.set(cacheKey, { expiresAt: now + refreshIntervalSeconds * 1e3, inFlight: discoveryPromise }); try { const value = await discoveryPromise; if (refreshIntervalSeconds > 0) discoveryCache.set(cacheKey, { expiresAt: now + refreshIntervalSeconds * 1e3, value }); return value; } catch (error) { if (refreshIntervalSeconds > 0) discoveryCache.delete(cacheKey); if (!hasLoggedBedrockError) { hasLoggedBedrockError = true; console.warn(`[bedrock-discovery] Failed to list models: ${String(error)}`); } return []; } } //#endregion //#region src/agents/cloudflare-ai-gateway.ts const CLOUDFLARE_AI_GATEWAY_PROVIDER_ID = "cloudflare-ai-gateway"; const CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_ID = "claude-sonnet-4-5"; const CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF = `${CLOUDFLARE_AI_GATEWAY_PROVIDER_ID}/${CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_ID}`; const CLOUDFLARE_AI_GATEWAY_DEFAULT_CONTEXT_WINDOW = 2e5; const CLOUDFLARE_AI_GATEWAY_DEFAULT_MAX_TOKENS = 64e3; const CLOUDFLARE_AI_GATEWAY_DEFAULT_COST = { input: 3, output: 15, cacheRead: .3, cacheWrite: 3.75 }; function buildCloudflareAiGatewayModelDefinition(params) { return { id: params?.id?.trim() || CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_ID, name: params?.name ?? "Claude Sonnet 4.5", reasoning: params?.reasoning ?? true, input: params?.input ?? ["text", "image"], cost: CLOUDFLARE_AI_GATEWAY_DEFAULT_COST, contextWindow: CLOUDFLARE_AI_GATEWAY_DEFAULT_CONTEXT_WINDOW, maxTokens: CLOUDFLARE_AI_GATEWAY_DEFAULT_MAX_TOKENS }; } function resolveCloudflareAiGatewayBaseUrl(params) { const accountId = params.accountId.trim(); const gatewayId = params.gatewayId.trim(); if (!accountId || !gatewayId) return ""; return `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/anthropic`; } //#endregion //#region src/agents/huggingface-models.ts /** Hugging Face Inference Providers (router) — OpenAI-compatible chat completions. */ const HUGGINGFACE_BASE_URL = "https://router.huggingface.co/v1"; /** Default cost when not in static catalog (HF pricing varies by provider). */ const HUGGINGFACE_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; /** Defaults for models discovered from GET /v1/models. */ const HUGGINGFACE_DEFAULT_CONTEXT_WINDOW = 131072; const HUGGINGFACE_DEFAULT_MAX_TOKENS = 8192; const HUGGINGFACE_MODEL_CATALOG = [ { id: "deepseek-ai/DeepSeek-R1", name: "DeepSeek R1", reasoning: true, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: 3, output: 7, cacheRead: 3, cacheWrite: 3 } }, { id: "deepseek-ai/DeepSeek-V3.1", name: "DeepSeek V3.1", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: .6, output: 1.25, cacheRead: .6, cacheWrite: .6 } }, { id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", name: "Llama 3.3 70B Instruct Turbo", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: .88, output: .88, cacheRead: .88, cacheWrite: .88 } }, { id: "openai/gpt-oss-120b", name: "GPT-OSS 120B", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } } ]; function buildHuggingfaceModelDefinition(model) { return { id: model.id, name: model.name, reasoning: model.reasoning, input: model.input, cost: model.cost, contextWindow: model.contextWindow, maxTokens: model.maxTokens }; } /** * Infer reasoning and display name from Hub-style model id (e.g. "deepseek-ai/DeepSeek-R1"). */ function inferredMetaFromModelId(id) { const base = id.split("/").pop() ?? id; const reasoning = /r1|reasoning|thinking|reason/i.test(id) || /-\d+[tb]?-thinking/i.test(base); return { name: base.replace(/-/g, " ").replace(/\b(\w)/g, (c) => c.toUpperCase()), reasoning }; } /** Prefer API-supplied display name, then owned_by/id, then inferred from id. */ function displayNameFromApiEntry(entry, inferredName) { const fromApi = typeof entry.name === "string" && entry.name.trim() || typeof entry.title === "string" && entry.title.trim() || typeof entry.display_name === "string" && entry.display_name.trim(); if (fromApi) return fromApi; if (typeof entry.owned_by === "string" && entry.owned_by.trim()) { const base = entry.id.split("/").pop() ?? entry.id; return `${entry.owned_by.trim()}/${base}`; } return inferredName; } /** * Discover chat-completion models from Hugging Face Inference Providers (GET /v1/models). * Requires a valid HF token. Falls back to static catalog on failure or in test env. */ async function discoverHuggingfaceModels(apiKey) { if (process.env.VITEST === "true" || false) return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); const trimmedKey = apiKey?.trim(); if (!trimmedKey) return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); try { const response = await fetch(`${HUGGINGFACE_BASE_URL}/models`, { signal: AbortSignal.timeout(1e4), headers: { Authorization: `Bearer ${trimmedKey}`, "Content-Type": "application/json" } }); if (!response.ok) { console.warn(`[huggingface-models] GET /v1/models failed: HTTP ${response.status}, using static catalog`); return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); } const data = (await response.json())?.data; if (!Array.isArray(data) || data.length === 0) { console.warn("[huggingface-models] No models in response, using static catalog"); return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); } const catalogById = new Map(HUGGINGFACE_MODEL_CATALOG.map((m) => [m.id, m])); const seen = /* @__PURE__ */ new Set(); const models = []; for (const entry of data) { const id = typeof entry?.id === "string" ? entry.id.trim() : ""; if (!id || seen.has(id)) continue; seen.add(id); const catalogEntry = catalogById.get(id); if (catalogEntry) models.push(buildHuggingfaceModelDefinition(catalogEntry)); else { const inferred = inferredMetaFromModelId(id); const name = displayNameFromApiEntry(entry, inferred.name); const modalities = entry.architecture?.input_modalities; const input = Array.isArray(modalities) && modalities.includes("image") ? ["text", "image"] : ["text"]; const contextLength = (Array.isArray(entry.providers) ? entry.providers : []).find((p) => typeof p?.context_length === "number" && p.context_length > 0)?.context_length ?? HUGGINGFACE_DEFAULT_CONTEXT_WINDOW; models.push({ id, name, reasoning: inferred.reasoning, input, cost: HUGGINGFACE_DEFAULT_COST, contextWindow: contextLength, maxTokens: HUGGINGFACE_DEFAULT_MAX_TOKENS }); } } return models.length > 0 ? models : HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); } catch (error) { console.warn(`[huggingface-models] Discovery failed: ${String(error)}, using static catalog`); return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition); } } //#endregion //#region src/agents/ollama-stream.ts const OLLAMA_NATIVE_BASE_URL = "http://127.0.0.1:11434"; function extractTextContent(content) { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; return content.filter((part) => part.type === "text").map((part) => part.text).join(""); } function extractOllamaImages(content) { if (!Array.isArray(content)) return []; return content.filter((part) => part.type === "image").map((part) => part.data); } function extractToolCalls(content) { if (!Array.isArray(content)) return []; const parts = content; const result = []; for (const part of parts) if (part.type === "toolCall") result.push({ function: { name: part.name, arguments: part.arguments } }); else if (part.type === "tool_use") result.push({ function: { name: part.name, arguments: part.input } }); return result; } function convertToOllamaMessages(messages, system) { const result = []; if (system) result.push({ role: "system", content: system }); for (const msg of messages) { const { role } = msg; if (role === "user") { const text = extractTextContent(msg.content); const images = extractOllamaImages(msg.content); result.push({ role: "user", content: text, ...images.length > 0 ? { images } : {} }); } else if (role === "assistant") { const text = extractTextContent(msg.content); const toolCalls = extractToolCalls(msg.content); result.push({ role: "assistant", content: text, ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {} }); } else if (role === "tool" || role === "toolResult") { const text = extractTextContent(msg.content); const toolName = typeof msg.toolName === "string" ? msg.toolName : void 0; result.push({ role: "tool", content: text, ...toolName ? { tool_name: toolName } : {} }); } } return result; } function extractOllamaTools(tools) { if (!tools || !Array.isArray(tools)) return []; const result = []; for (const tool of tools) { if (typeof tool.name !== "string" || !tool.name) continue; result.push({ type: "function", function: { name: tool.name, description: typeof tool.description === "string" ? tool.description : "", parameters: tool.parameters ?? {} } }); } return result; } function buildAssistantMessage(response, modelInfo) { const content = []; const text = response.message.content || response.message.reasoning || ""; if (text) content.push({ type: "text", text }); const toolCalls = response.message.tool_calls; if (toolCalls && toolCalls.length > 0) for (const tc of toolCalls) content.push({ type: "toolCall", id: `ollama_call_${randomUUID()}`, name: tc.function.name, arguments: tc.function.arguments }); const stopReason = toolCalls && toolCalls.length > 0 ? "toolUse" : "stop"; const usage = { input: response.prompt_eval_count ?? 0, output: response.eval_count ?? 0, cacheRead: 0, cacheWrite: 0, totalTokens: (response.prompt_eval_count ?? 0) + (response.eval_count ?? 0), cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }; return { role: "assistant", content, stopReason, api: modelInfo.api, provider: modelInfo.provider, model: modelInfo.id, usage, timestamp: Date.now() }; } async function* parseNdjsonStream(reader) { const decoder = new TextDecoder(); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; try { yield JSON.parse(trimmed); } catch { console.warn("[ollama-stream] Skipping malformed NDJSON line:", trimmed.slice(0, 120)); } } } if (buffer.trim()) try { yield JSON.parse(buffer.trim()); } catch { console.warn("[ollama-stream] Skipping malformed trailing data:", buffer.trim().slice(0, 120)); } } function resolveOllamaChatUrl(baseUrl) { return `${baseUrl.trim().replace(/\/+$/, "").replace(/\/v1$/i, "") || OLLAMA_NATIVE_BASE_URL}/api/chat`; } function createOllamaStreamFn(baseUrl) { const chatUrl = resolveOllamaChatUrl(baseUrl); return (model, context, options) => { const stream = createAssistantMessageEventStream(); const run = async () => { try { const ollamaMessages = convertToOllamaMessages(context.messages ?? [], context.systemPrompt); const ollamaTools = extractOllamaTools(context.tools); const ollamaOptions = { num_ctx: model.contextWindow ?? 65536 }; if (typeof options?.temperature === "number") ollamaOptions.temperature = options.temperature; if (typeof options?.maxTokens === "number") ollamaOptions.num_predict = options.maxTokens; const body = { model: model.id, messages: ollamaMessages, stream: true, ...ollamaTools.length > 0 ? { tools: ollamaTools } : {}, options: ollamaOptions }; const headers = { "Content-Type": "application/json", ...options?.headers }; if (options?.apiKey) headers.Authorization = `Bearer ${options.apiKey}`; const response = await fetch(chatUrl, { method: "POST", headers, body: JSON.stringify(body), signal: options?.signal }); if (!response.ok) { const errorText = await response.text().catch(() => "unknown error"); throw new Error(`Ollama API error ${response.status}: ${errorText}`); } if (!response.body) throw new Error("Ollama API returned empty response body"); const reader = response.body.getReader(); let accumulatedContent = ""; const accumulatedToolCalls = []; let finalResponse; for await (const chunk of parseNdjsonStream(reader)) { if (chunk.message?.content) accumulatedContent += chunk.message.content; else if (chunk.message?.reasoning) accumulatedContent += chunk.message.reasoning; if (chunk.message?.tool_calls) accumulatedToolCalls.push(...chunk.message.tool_calls); if (chunk.done) { finalResponse = chunk; break; } } if (!finalResponse) throw new Error("Ollama API stream ended without a final response"); finalResponse.message.content = accumulatedContent; if (accumulatedToolCalls.length > 0) finalResponse.message.tool_calls = accumulatedToolCalls; const assistantMessage = buildAssistantMessage(finalResponse, { api: model.api, provider: model.provider, id: model.id }); const reason = assistantMessage.stopReason === "toolUse" ? "toolUse" : "stop"; stream.push({ type: "done", reason, message: assistantMessage }); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); stream.push({ type: "error", reason: "error", error: { role: "assistant", content: [], stopReason: "error", errorMessage, api: model.api, provider: model.provider, model: model.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, timestamp: Date.now() } }); } finally { stream.end(); } }; queueMicrotask(() => void run()); return stream; }; } //#endregion //#region src/agents/synthetic-models.ts const SYNTHETIC_BASE_URL = "https://api.synthetic.new/anthropic"; const SYNTHETIC_DEFAULT_MODEL_ID = "hf:MiniMaxAI/MiniMax-M2.1"; const SYNTHETIC_DEFAULT_MODEL_REF = `synthetic/${SYNTHETIC_DEFAULT_MODEL_ID}`; const SYNTHETIC_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const SYNTHETIC_MODEL_CATALOG = [ { id: SYNTHETIC_DEFAULT_MODEL_ID, name: "MiniMax M2.1", reasoning: false, input: ["text"], contextWindow: 192e3, maxTokens: 65536 }, { id: "hf:moonshotai/Kimi-K2-Thinking", name: "Kimi K2 Thinking", reasoning: true, input: ["text"], contextWindow: 256e3, maxTokens: 8192 }, { id: "hf:zai-org/GLM-4.7", name: "GLM-4.7", reasoning: false, input: ["text"], contextWindow: 198e3, maxTokens: 128e3 }, { id: "hf:deepseek-ai/DeepSeek-R1-0528", name: "DeepSeek R1 0528", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:deepseek-ai/DeepSeek-V3-0324", name: "DeepSeek V3 0324", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:deepseek-ai/DeepSeek-V3.1", name: "DeepSeek V3.1", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:deepseek-ai/DeepSeek-V3.1-Terminus", name: "DeepSeek V3.1 Terminus", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:deepseek-ai/DeepSeek-V3.2", name: "DeepSeek V3.2", reasoning: false, input: ["text"], contextWindow: 159e3, maxTokens: 8192 }, { id: "hf:meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B Instruct", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", name: "Llama 4 Maverick 17B 128E Instruct FP8", reasoning: false, input: ["text"], contextWindow: 524e3, maxTokens: 8192 }, { id: "hf:moonshotai/Kimi-K2-Instruct-0905", name: "Kimi K2 Instruct 0905", reasoning: false, input: ["text"], contextWindow: 256e3, maxTokens: 8192 }, { id: "hf:moonshotai/Kimi-K2.5", name: "Kimi K2.5", reasoning: true, input: ["text"], contextWindow: 256e3, maxTokens: 8192 }, { id: "hf:openai/gpt-oss-120b", name: "GPT OSS 120B", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:Qwen/Qwen3-235B-A22B-Instruct-2507", name: "Qwen3 235B A22B Instruct 2507", reasoning: false, input: ["text"], contextWindow: 256e3, maxTokens: 8192 }, { id: "hf:Qwen/Qwen3-Coder-480B-A35B-Instruct", name: "Qwen3 Coder 480B A35B Instruct", reasoning: false, input: ["text"], contextWindow: 256e3, maxTokens: 8192 }, { id: "hf:Qwen/Qwen3-VL-235B-A22B-Instruct", name: "Qwen3 VL 235B A22B Instruct", reasoning: false, input: ["text", "image"], contextWindow: 25e4, maxTokens: 8192 }, { id: "hf:zai-org/GLM-4.5", name: "GLM-4.5", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 128e3 }, { id: "hf:zai-org/GLM-4.6", name: "GLM-4.6", reasoning: false, input: ["text"], contextWindow: 198e3, maxTokens: 128e3 }, { id: "hf:zai-org/GLM-5", name: "GLM-5", reasoning: true, input: ["text", "image"], contextWindow: 256e3, maxTokens: 128e3 }, { id: "hf:deepseek-ai/DeepSeek-V3", name: "DeepSeek V3", reasoning: false, input: ["text"], contextWindow: 128e3, maxTokens: 8192 }, { id: "hf:Qwen/Qwen3-235B-A22B-Thinking-2507", name: "Qwen3 235B A22B Thinking 2507", reasoning: true, input: ["text"], contextWindow: 256e3, maxTokens: 8192 } ]; function buildSyntheticModelDefinition(entry) { return { id: entry.id, name: entry.name, reasoning: entry.reasoning, input: [...entry.input], cost: SYNTHETIC_DEFAULT_COST, contextWindow: entry.contextWindow, maxTokens: entry.maxTokens }; } //#endregion //#region src/agents/together-models.ts const TOGETHER_BASE_URL = "https://api.together.xyz/v1"; const TOGETHER_MODEL_CATALOG = [ { id: "zai-org/GLM-4.7", name: "GLM 4.7 Fp8", reasoning: false, input: ["text"], contextWindow: 202752, maxTokens: 8192, cost: { input: .45, output: 2, cacheRead: .45, cacheWrite: 2 } }, { id: "moonshotai/Kimi-K2.5", name: "Kimi K2.5", reasoning: true, input: ["text", "image"], cost: { input: .5, output: 2.8, cacheRead: .5, cacheWrite: 2.8 }, contextWindow: 262144, maxTokens: 32768 }, { id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", name: "Llama 3.3 70B Instruct Turbo", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: .88, output: .88, cacheRead: .88, cacheWrite: .88 } }, { id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", name: "Llama 4 Scout 17B 16E Instruct", reasoning: false, input: ["text", "image"], contextWindow: 1e7, maxTokens: 32768, cost: { input: .18, output: .59, cacheRead: .18, cacheWrite: .18 } }, { id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", name: "Llama 4 Maverick 17B 128E Instruct FP8", reasoning: false, input: ["text", "image"], contextWindow: 2e7, maxTokens: 32768, cost: { input: .27, output: .85, cacheRead: .27, cacheWrite: .27 } }, { id: "deepseek-ai/DeepSeek-V3.1", name: "DeepSeek V3.1", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: .6, output: 1.25, cacheRead: .6, cacheWrite: .6 } }, { id: "deepseek-ai/DeepSeek-R1", name: "DeepSeek R1", reasoning: true, input: ["text"], contextWindow: 131072, maxTokens: 8192, cost: { input: 3, output: 7, cacheRead: 3, cacheWrite: 3 } }, { id: "moonshotai/Kimi-K2-Instruct-0905", name: "Kimi K2-Instruct 0905", reasoning: false, input: ["text"], contextWindow: 262144, maxTokens: 8192, cost: { input: 1, output: 3, cacheRead: 1, cacheWrite: 3 } } ]; function buildTogetherModelDefinition(model) { return { id: model.id, name: model.name, api: "openai-completions", reasoning: model.reasoning, input: model.input, cost: model.cost, contextWindow: model.contextWindow, maxTokens: model.maxTokens }; } //#endregion //#region src/agents/venice-models.ts const VENICE_BASE_URL = "https://api.venice.ai/api/v1"; const VENICE_DEFAULT_MODEL_ID = "llama-3.3-70b"; const VENICE_DEFAULT_MODEL_REF = `venice/${VENICE_DEFAULT_MODEL_ID}`; const VENICE_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; /** * Complete catalog of Venice AI models. * * Venice provides two privacy modes: * - "private": Fully private inference, no logging, ephemeral * - "anonymized": Proxied through Venice with metadata stripped (for proprietary models) * * Note: The `privacy` field is included for documentation purposes but is not * propagated to ModelDefinitionConfig as it's not part of the core model schema. * Privacy mode is determined by the model itself, not configurable at runtime. * * This catalog serves as a fallback when the Venice API is unreachable. */ const VENICE_MODEL_CATALOG = [ { id: "llama-3.3-70b", name: "Llama 3.3 70B", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "llama-3.2-3b", name: "Llama 3.2 3B", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "hermes-3-llama-3.1-405b", name: "Hermes 3 Llama 3.1 405B", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "qwen3-235b-a22b-thinking-2507", name: "Qwen3 235B Thinking", reasoning: true, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "qwen3-235b-a22b-instruct-2507", name: "Qwen3 235B Instruct", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "qwen3-coder-480b-a35b-instruct", name: "Qwen3 Coder 480B", reasoning: false, input: ["text"], contextWindow: 262144, maxTokens: 8192, privacy: "private" }, { id: "qwen3-next-80b", name: "Qwen3 Next 80B", reasoning: false, input: ["text"], contextWindow: 262144, maxTokens: 8192, privacy: "private" }, { id: "qwen3-vl-235b-a22b", name: "Qwen3 VL 235B (Vision)", reasoning: false, input: ["text", "image"], contextWindow: 262144, maxTokens: 8192, privacy: "private" }, { id: "qwen3-4b", name: "Venice Small (Qwen3 4B)", reasoning: true, input: ["text"], contextWindow: 32768, maxTokens: 8192, privacy: "private" }, { id: "deepseek-v3.2", name: "DeepSeek V3.2", reasoning: true, input: ["text"], contextWindow: 163840, maxTokens: 8192, privacy: "private" }, { id: "venice-uncensored", name: "Venice Uncensored (Dolphin-Mistral)", reasoning: false, input: ["text"], contextWindow: 32768, maxTokens: 8192, privacy: "private" }, { id: "mistral-31-24b", name: "Venice Medium (Mistral)", reasoning: false, input: ["text", "image"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "google-gemma-3-27b-it", name: "Google Gemma 3 27B Instruct", reasoning: false, input: ["text", "image"], contextWindow: 202752, maxTokens: 8192, privacy: "private" }, { id: "openai-gpt-oss-120b", name: "OpenAI GPT OSS 120B", reasoning: false, input: ["text"], contextWindow: 131072, maxTokens: 8192, privacy: "private" }, { id: "zai-org-glm-4.7", name: "GLM 4.7", reasoning: true, input: ["text"], contextWindow: 202752, maxTokens: 8192, privacy: "private" }, { id: "claude-opus-45", name: "Claude Opus 4.5 (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 202752, maxTokens: 8192, privacy: "anonymized" }, { id: "claude-sonnet-45", name: "Claude Sonnet 4.5 (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 202752, maxTokens: 8192, privacy: "anonymized" }, { id: "openai-gpt-52", name: "GPT-5.2 (via Venice)", reasoning: true, input: ["text"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "openai-gpt-52-codex", name: "GPT-5.2 Codex (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "gemini-3-pro-preview", name: "Gemini 3 Pro (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 202752, maxTokens: 8192, privacy: "anonymized" }, { id: "gemini-3-flash-preview", name: "Gemini 3 Flash (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "grok-41-fast", name: "Grok 4.1 Fast (via Venice)", reasoning: true, input: ["text", "image"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "grok-code-fast-1", name: "Grok Code Fast 1 (via Venice)", reasoning: true, input: ["text"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "kimi-k2-thinking", name: "Kimi K2 Thinking (via Venice)", reasoning: true, input: ["text"], contextWindow: 262144, maxTokens: 8192, privacy: "anonymized" }, { id: "minimax-m21", name: "MiniMax M2.1 (via Venice)", reasoning: true, input: ["text"], contextWindow: 202752, maxTokens: 8192, privacy: "anonymized" } ]; /** * Build a ModelDefinitionConfig from a Venice catalog entry. * * Note: The `privacy` field from the catalog is not included in the output * as ModelDefinitionConfig doesn't support custom metadata fields. Privacy * mode is inherent to each model and documented in the catalog/docs. */ function buildVeniceModelDefinition(entry) { return { id: entry.id, name: entry.name, reasoning: entry.reasoning, input: [...entry.input], cost: VENICE_DEFAULT_COST, contextWindow: entry.contextWindow, maxTokens: entry.maxTokens, compat: { supportsUsageInStreaming: false } }; } /** * Discover models from Venice API with fallback to static catalog. * The /models endpoint is public and doesn't require authentication. */ async function discoverVeniceModels() { if (process.env.VITEST) return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); try { const response = await fetch(`${VENICE_BASE_URL}/models`, { signal: AbortSignal.timeout(5e3) }); if (!response.ok) { console.warn(`[venice-models] Failed to discover models: HTTP ${response.status}, using static catalog`); return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); } const data = await response.json(); if (!Array.isArray(data.data) || data.data.length === 0) { console.warn("[venice-models] No models found from API, using static catalog"); return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); } const catalogById = new Map(VENICE_MODEL_CATALOG.map((m) => [m.id, m])); const models = []; for (const apiModel of data.data) { const catalogEntry = catalogById.get(apiModel.id); if (catalogEntry) models.push(buildVeniceModelDefinition(catalogEntry)); else { const isReasoning = apiModel.model_spec.capabilities.supportsReasoning || apiModel.id.toLowerCase().includes("thinking") || apiModel.id.toLowerCase().includes("reason") || apiModel.id.toLowerCase().includes("r1"); const hasVision = apiModel.model_spec.capabilities.supportsVision; models.push({ id: apiModel.id, name: apiModel.model_spec.name || apiModel.id, reasoning: isReasoning, input: hasVision ? ["text", "image"] : ["text"], cost: VENICE_DEFAULT_COST, contextWindow: apiModel.model_spec.availableContextTokens || 128e3, maxTokens: 8192, compat: { supportsUsageInStreaming: false } }); } } return models.length > 0 ? models : VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); } catch (error) { console.warn(`[venice-models] Discovery failed: ${String(error)}, using static catalog`); return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); } } //#endregion //#region src/agents/models-config.providers.ts const MINIMAX_PORTAL_BASE_URL = "https://api.minimax.io/anthropic"; const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.1"; const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01"; const MINIMAX_DEFAULT_CONTEXT_WINDOW = 2e5; const MINIMAX_DEFAULT_MAX_TOKENS = 8192; const MINIMAX_OAUTH_PLACEHOLDER = "minimax-oauth"; const MINIMAX_API_COST = { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 }; function buildMinimaxModel(params) { return { id: params.id, name: params.name, reasoning: params.reasoning, input: params.input, cost: MINIMAX_API_COST, contextWindow: MINIMAX_DEFAULT_CONTEXT_WINDOW, maxTokens: MINIMAX_DEFAULT_MAX_TOKENS }; } function buildMinimaxTextModel(params) { return buildMinimaxModel({ ...params, input: ["text"] }); } const XIAOMI_BASE_URL = "https://api.xiaomimimo.com/anthropic"; const XIAOMI_DEFAULT_MODEL_ID = "mimo-v2-flash"; const XIAOMI_DEFAULT_CONTEXT_WINDOW = 262144; const XIAOMI_DEFAULT_MAX_TOKENS = 8192; const XIAOMI_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256e3; const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; const MOONSHOT_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1"; const QWEN_PORTAL_OAUTH_PLACEHOLDER = "qwen-oauth"; const QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW = 128e3; const QWEN_PORTAL_DEFAULT_MAX_TOKENS = 8192; const QWEN_PORTAL_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const OLLAMA_API_BASE_URL = OLLAMA_NATIVE_BASE_URL; const OLLAMA_DEFAULT_CONTEXT_WINDOW = 128e3; const OLLAMA_DEFAULT_MAX_TOKENS = 8192; const OLLAMA_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const VLLM_BASE_URL = "http://127.0.0.1:8000/v1"; const VLLM_DEFAULT_CONTEXT_WINDOW = 128e3; const VLLM_DEFAULT_MAX_TOKENS = 8192; const VLLM_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const QIANFAN_BASE_URL = "https://qianfan.baidubce.com/v2"; const QIANFAN_DEFAULT_MODEL_ID = "deepseek-v3.2"; const QIANFAN_DEFAULT_CONTEXT_WINDOW = 98304; const QIANFAN_DEFAULT_MAX_TOKENS = 32768; const QIANFAN_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"; const NVIDIA_DEFAULT_MODEL_ID = "nvidia/llama-3.1-nemotron-70b-instruct"; const NVIDIA_DEFAULT_CONTEXT_WINDOW = 131072; const NVIDIA_DEFAULT_MAX_TOKENS = 4096; const NVIDIA_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; /** * Derive the Ollama native API base URL from a configured base URL. * * Users typically configure `baseUrl` with a `/v1` suffix (e.g. * `http://192.168.20.14:11434/v1`) for the OpenAI-compatible endpoint. * The native Ollama API lives at the root (e.g. `/api/tags`), so we * strip the `/v1` suffix when present. */ function resolveOllamaApiBase(configuredBaseUrl) { if (!configuredBaseUrl) return OLLAMA_API_BASE_URL; return configuredBaseUrl.replace(/\/+$/, "").replace(/\/v1$/i, ""); } async function discoverOllamaModels(baseUrl) { if (process.env.VITEST || false) return []; try { const apiBase = resolveOllamaApiBase(baseUrl); const response = await fetch(`${apiBase}/api/tags`, { signal: AbortSignal.timeout(5e3) }); if (!response.ok) { console.warn(`Failed to discover Ollama models: ${response.status}`); return []; } const data = await response.json(); if (!data.models || data.models.length === 0) { console.warn("No Ollama models found on local instance"); return []; } return data.models.map((model) => { const modelId = model.name; return { id: modelId, name: modelId, reasoning: modelId.toLowerCase().includes("r1") || modelId.toLowerCase().includes("reasoning"), input: ["text"], cost: OLLAMA_DEFAULT_COST, contextWindow: OLLAMA_DEFAULT_CONTEXT_WINDOW, maxTokens: OLLAMA_DEFAULT_MAX_TOKENS }; }); } catch (error) { console.warn(`Failed to discover Ollama models: ${String(error)}`); return []; } } async function discoverVllmModels(baseUrl, apiKey) { if (process.env.VITEST || false) return []; const url = `${baseUrl.trim().replace(/\/+$/, "")}/models`; try { const trimmedApiKey = apiKey?.trim(); const response = await fetch(url, { headers: trimmedApiKey ? { Authorization: `Bearer ${trimmedApiKey}` } : void 0, signal: AbortSignal.timeout(5e3) }); if (!response.ok) { console.warn(`Failed to discover vLLM models: ${response.status}`); return []; } const models = (await response.json()).data ?? []; if (models.length === 0) { console.warn("No vLLM models found on local instance"); return []; } return models.map((m) => ({ id: typeof m.id === "string" ? m.id.trim() : "" })).filter((m) => Boolean(m.id)).map((m) => { const modelId = m.id; const lower = modelId.toLowerCase(); return { id: modelId, name: modelId, reasoning: lower.includes("r1") || lower.includes("reasoning") || lower.includes("think"), input: ["text"], cost: VLLM_DEFAULT_COST, contextWindow: VLLM_DEFAULT_CONTEXT_WINDOW, maxTokens: VLLM_DEFAULT_MAX_TOKENS }; }); } catch (error) { console.warn(`Failed to discover vLLM models: ${String(error)}`); return []; } } function normalizeApiKeyConfig(value) { const trimmed = value.trim(); return /^\$\{([A-Z0-9_]+)\}$/.exec(trimmed)?.[1] ?? trimmed; } function resolveEnvApiKeyVarName(provider) { const resolved = resolveEnvApiKey(provider); if (!resolved) return; const match = /^(?:env: |shell env: )([A-Z0-9_]+)$/.exec(resolved.source); return match ? match[1] : void 0; } function resolveAwsSdkApiKeyVarName() { return resolveAwsSdkEnvVarName() ?? "AWS_PROFILE"; } function resolveApiKeyFromProfiles(params) { const ids = listProfilesForProvider(params.store, params.provider); for (const id of ids) { const cred = params.store.profiles[id]; if (!cred) continue; if (cred.type === "api_key") return cred.key; if (cred.type === "token") return cred.token; } } function normalizeGoogleModelId(id) { if (id === "gemini-3-pro") return "gemini-3-pro-preview"; if (id === "gemini-3-flash") return "gemini-3-flash-preview"; return id; } function normalizeGoogleProvider(provider) { let mutated = false; const models = provider.models.map((model) => { const nextId = normalizeGoogleModelId(model.id); if (nextId === model.id) return model; mutated = true; return { ...model, id: nextId }; }); return mutated ? { ...provider, models } : provider; } function normalizeProviders(params) { const { providers } = params; if (!providers) return providers; const authStore = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }); let mutated = false; const next = {}; for (const [key, provider] of Object.entries(providers)) { const normalizedKey = key.trim(); let normalizedProvider = provider; if (normalizedProvider.apiKey && normalizeApiKeyConfig(normalizedProvider.apiKey) !== normalizedProvider.apiKey) { mutated = true; normalizedProvider = { ...normalizedProvider, apiKey: normalizeApiKeyConfig(normalizedProvider.apiKey) }; } if (Array.isArray(normalizedProvider.models) && normalizedProvider.models.length > 0 && !normalizedProvider.apiKey?.trim()) if ((normalizedProvider.auth ?? (normalizedKey === "amazon-bedrock" ? "aws-sdk" : void 0)) === "aws-sdk") { const apiKey = resolveAwsSdkApiKeyVarName(); mutated = true; normalizedProvider = { ...normalizedProvider, apiKey }; } else { const fromEnv = resolveEnvApiKeyVarName(normalizedKey); const fromProfiles = resolveApiKeyFromProfiles({ provider: normalizedKey, store: authStore }); const apiKey = fromEnv ?? fromProfiles; if (apiKey?.trim()) { mutated = true; normalizedProvider = { ...normalizedProvider, apiKey }; } } if (normalizedKey === "google") { const googleNormalized = normalizeGoogleProvider(normalizedProvider); if (googleNormalized !== normalizedProvider) mutated = true; normalizedProvider = googleNormalized; } next[key] = normalizedProvider; } return mutated ? next : providers; } function buildMinimaxProvider() { return { baseUrl: MINIMAX_PORTAL_BASE_URL, api: "anthropic-messages", models: [ buildMinimaxTextModel({ id: MINIMAX_DEFAULT_MODEL_ID, name: "MiniMax M2.1", reasoning: false }), buildMinimaxTextModel({ id: "MiniMax-M2.1-lightning", name: "MiniMax M2.1 Lightning", reasoning: false }), buildMinimaxModel({ id: MINIMAX_DEFAULT_VISION_MODEL_ID, name: "MiniMax VL 01", reasoning: false, input: ["text", "image"] }), buildMinimaxTextModel({ id: "MiniMax-M2.5", name: "MiniMax M2.5", reasoning: true }), buildMinimaxTextModel({ id: "MiniMax-M2.5-Lightning", name: "MiniMax M2.5 Lightning", reasoning: true }) ] }; } function buildMinimaxPortalProvider() { return { baseUrl: MINIMAX_PORTAL_BASE_URL, api: "anthropic-messages", models: [buildMinimaxTextModel({ id: MINIMAX_DEFAULT_MODEL_ID, name: "MiniMax M2.1", reasoning: false }), buildMinimaxTextModel({ id: "MiniMax-M2.5", name: "MiniMax M2.5", reasoning: true })] }; } function buildMoonshotProvider() { return { baseUrl: MOONSHOT_BASE_URL, api: "openai-completions", models: [{ id: MOONSHOT_DEFAULT_MODEL_ID, name: "Kimi K2.5", reasoning: false, input: ["text"], cost: MOONSHOT_DEFAULT_COST, contextWindow: MOONSHOT_DEFAULT_CONTEXT_WINDOW, maxTokens: MOONSHOT_DEFAULT_MAX_TOKENS }] }; } function buildQwenPortalProvider() { return { baseUrl: QWEN_PORTAL_BASE_URL, api: "openai-completions", models: [{ id: "coder-model", name: "Qwen Coder", reasoning: false, input: ["text"], cost: QWEN_PORTAL_DEFAULT_COST, contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS }, { id: "vision-model", name: "Qwen Vision", reasoning: false, input: ["text", "image"], cost: QWEN_PORTAL_DEFAULT_COST, contextWindow: QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW, maxTokens: QWEN_PORTAL_DEFAULT_MAX_TOKENS }] }; } function buildSyntheticProvider() { return { baseUrl: SYNTHETIC_BASE_URL, api: "anthropic-messages", models: SYNTHETIC_MODEL_CAT