@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,778 lines (1,765 loc) • 122 kB
JavaScript
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