feat(onboard): add Ollama provider support
This commit is contained in:
parent
cb4b3f74b5
commit
e8216d78c7
@ -20,7 +20,8 @@ export type AuthChoiceGroupId =
|
|||||||
| "minimax"
|
| "minimax"
|
||||||
| "synthetic"
|
| "synthetic"
|
||||||
| "venice"
|
| "venice"
|
||||||
| "qwen";
|
| "qwen"
|
||||||
|
| "ollama";
|
||||||
|
|
||||||
export type AuthChoiceGroup = {
|
export type AuthChoiceGroup = {
|
||||||
value: AuthChoiceGroupId;
|
value: AuthChoiceGroupId;
|
||||||
@ -113,6 +114,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
|||||||
hint: "API key",
|
hint: "API key",
|
||||||
choices: ["opencode-zen"],
|
choices: ["opencode-zen"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "ollama",
|
||||||
|
label: "Ollama",
|
||||||
|
hint: "Local + cloud models",
|
||||||
|
choices: ["ollama"],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function buildAuthChoiceOptions(params: {
|
export function buildAuthChoiceOptions(params: {
|
||||||
@ -183,6 +190,11 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "MiniMax M2.1 Lightning",
|
label: "MiniMax M2.1 Lightning",
|
||||||
hint: "Faster, higher output cost",
|
hint: "Faster, higher output cost",
|
||||||
});
|
});
|
||||||
|
options.push({
|
||||||
|
value: "ollama",
|
||||||
|
label: "Ollama",
|
||||||
|
hint: "Sign-in is handled by Ollama when required",
|
||||||
|
});
|
||||||
if (params.includeSkip) {
|
if (params.includeSkip) {
|
||||||
options.push({ value: "skip", label: "Skip for now" });
|
options.push({ value: "skip", label: "Skip for now" });
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/commands/auth-choice.apply.ollama.ts
Normal file
73
src/commands/auth-choice.apply.ollama.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||||
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
applyOllamaProviderConfig,
|
||||||
|
OLLAMA_BASE_URL,
|
||||||
|
OLLAMA_DEFAULT_API_KEY,
|
||||||
|
setOllamaApiKey,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
|
|
||||||
|
export async function applyAuthChoiceOllama(
|
||||||
|
params: ApplyAuthChoiceParams,
|
||||||
|
): Promise<ApplyAuthChoiceResult | null> {
|
||||||
|
if (params.authChoice !== "ollama") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextConfig = params.config;
|
||||||
|
|
||||||
|
await params.prompter.note(
|
||||||
|
[
|
||||||
|
"Ollama runs models locally or in the cloud.",
|
||||||
|
"Make sure Ollama is installed and running: https://ollama.com",
|
||||||
|
"Default server: http://127.0.0.1:11434",
|
||||||
|
].join("\n"),
|
||||||
|
"Ollama",
|
||||||
|
);
|
||||||
|
|
||||||
|
const useDefault = await params.prompter.confirm({
|
||||||
|
message: `Use default Ollama server (${OLLAMA_BASE_URL.replace("/v1", "")})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let baseUrl = OLLAMA_BASE_URL;
|
||||||
|
if (!useDefault) {
|
||||||
|
const customUrl = await params.prompter.text({
|
||||||
|
message: "Enter Ollama server URL (e.g., http://192.168.1.100:11434)",
|
||||||
|
validate: (value) => {
|
||||||
|
if (!value?.trim()) return "URL is required";
|
||||||
|
try {
|
||||||
|
new URL(value.trim());
|
||||||
|
return undefined;
|
||||||
|
} catch {
|
||||||
|
return "Invalid URL format";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Append /v1 if not present for OpenAI-compatible endpoint
|
||||||
|
const trimmedUrl = String(customUrl).trim().replace(/\/+$/, "");
|
||||||
|
baseUrl = trimmedUrl.endsWith("/v1") ? trimmedUrl : `${trimmedUrl}/v1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the placeholder API key to enable provider discovery
|
||||||
|
await setOllamaApiKey(OLLAMA_DEFAULT_API_KEY, params.agentDir);
|
||||||
|
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "ollama:default",
|
||||||
|
provider: "ollama",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
|
||||||
|
nextConfig = applyOllamaProviderConfig(nextConfig, { baseUrl });
|
||||||
|
|
||||||
|
await params.prompter.note(
|
||||||
|
[
|
||||||
|
"Ollama configured successfully.",
|
||||||
|
"Models will be discovered automatically from your Ollama server.",
|
||||||
|
"Use `ollama pull <model>` to download models, then `moltbot models list` to see them.",
|
||||||
|
].join("\n"),
|
||||||
|
"Setup complete",
|
||||||
|
);
|
||||||
|
|
||||||
|
return { config: nextConfig };
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { applyAuthChoiceGoogleAntigravity } from "./auth-choice.apply.google-ant
|
|||||||
import { applyAuthChoiceGoogleGeminiCli } from "./auth-choice.apply.google-gemini-cli.js";
|
import { applyAuthChoiceGoogleGeminiCli } from "./auth-choice.apply.google-gemini-cli.js";
|
||||||
import { applyAuthChoiceMiniMax } from "./auth-choice.apply.minimax.js";
|
import { applyAuthChoiceMiniMax } from "./auth-choice.apply.minimax.js";
|
||||||
import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
|
import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
|
||||||
|
import { applyAuthChoiceOllama } from "./auth-choice.apply.ollama.js";
|
||||||
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
|
||||||
import { applyAuthChoiceQwenPortal } from "./auth-choice.apply.qwen-portal.js";
|
import { applyAuthChoiceQwenPortal } from "./auth-choice.apply.qwen-portal.js";
|
||||||
import type { AuthChoice } from "./onboard-types.js";
|
import type { AuthChoice } from "./onboard-types.js";
|
||||||
@ -46,6 +47,7 @@ export async function applyAuthChoice(
|
|||||||
applyAuthChoiceGoogleGeminiCli,
|
applyAuthChoiceGoogleGeminiCli,
|
||||||
applyAuthChoiceCopilotProxy,
|
applyAuthChoiceCopilotProxy,
|
||||||
applyAuthChoiceQwenPortal,
|
applyAuthChoiceQwenPortal,
|
||||||
|
applyAuthChoiceOllama,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const handler of handlers) {
|
for (const handler of handlers) {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
|||||||
minimax: "lmstudio",
|
minimax: "lmstudio",
|
||||||
"opencode-zen": "opencode",
|
"opencode-zen": "opencode",
|
||||||
"qwen-portal": "qwen-portal",
|
"qwen-portal": "qwen-portal",
|
||||||
|
ollama: "ollama",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolvePreferredProviderForAuthChoice(choice: AuthChoice): string | undefined {
|
export function resolvePreferredProviderForAuthChoice(choice: AuthChoice): string | undefined {
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import {
|
|||||||
MOONSHOT_BASE_URL,
|
MOONSHOT_BASE_URL,
|
||||||
MOONSHOT_DEFAULT_MODEL_ID,
|
MOONSHOT_DEFAULT_MODEL_ID,
|
||||||
MOONSHOT_DEFAULT_MODEL_REF,
|
MOONSHOT_DEFAULT_MODEL_REF,
|
||||||
|
OLLAMA_BASE_URL,
|
||||||
|
OLLAMA_DEFAULT_API_KEY,
|
||||||
} from "./onboard-auth.models.js";
|
} from "./onboard-auth.models.js";
|
||||||
|
|
||||||
export function applyZaiConfig(cfg: MoltbotConfig): MoltbotConfig {
|
export function applyZaiConfig(cfg: MoltbotConfig): MoltbotConfig {
|
||||||
@ -411,6 +413,53 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Ollama provider configuration without changing the default model.
|
||||||
|
* Sets up the provider with dynamic model discovery; models are populated at runtime.
|
||||||
|
*/
|
||||||
|
export function applyOllamaProviderConfig(
|
||||||
|
cfg: MoltbotConfig,
|
||||||
|
params?: { baseUrl?: string },
|
||||||
|
): MoltbotConfig {
|
||||||
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const existingProvider = providers.ollama;
|
||||||
|
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> as { apiKey?: string };
|
||||||
|
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||||
|
const normalizedApiKey = resolvedApiKey?.trim() || OLLAMA_DEFAULT_API_KEY;
|
||||||
|
const baseUrl = params?.baseUrl ?? OLLAMA_BASE_URL;
|
||||||
|
|
||||||
|
providers.ollama = {
|
||||||
|
...existingProviderRest,
|
||||||
|
baseUrl,
|
||||||
|
api: "openai-completions",
|
||||||
|
apiKey: normalizedApiKey,
|
||||||
|
// Models are discovered dynamically via Ollama's /api/tags endpoint
|
||||||
|
models: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Ollama provider configuration. Since Ollama discovers models dynamically,
|
||||||
|
* this just configures the provider; model selection happens separately.
|
||||||
|
*/
|
||||||
|
export function applyOllamaConfig(
|
||||||
|
cfg: MoltbotConfig,
|
||||||
|
params?: { baseUrl?: string },
|
||||||
|
): MoltbotConfig {
|
||||||
|
return applyOllamaProviderConfig(cfg, params);
|
||||||
|
}
|
||||||
|
|
||||||
export function applyAuthProfileConfig(
|
export function applyAuthProfileConfig(
|
||||||
cfg: MoltbotConfig,
|
cfg: MoltbotConfig,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@ -164,3 +164,15 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) {
|
|||||||
agentDir: resolveAuthAgentDir(agentDir),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setOllamaApiKey(key: string, agentDir?: string) {
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "ollama:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "ollama",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,9 @@ export const KIMI_CODE_MAX_TOKENS = 32768;
|
|||||||
export const KIMI_CODE_HEADERS = { "User-Agent": "KimiCLI/0.77" } as const;
|
export const KIMI_CODE_HEADERS = { "User-Agent": "KimiCLI/0.77" } as const;
|
||||||
export const KIMI_CODE_COMPAT = { supportsDeveloperRole: false } as const;
|
export const KIMI_CODE_COMPAT = { supportsDeveloperRole: false } as const;
|
||||||
|
|
||||||
|
export const OLLAMA_BASE_URL = "http://127.0.0.1:11434/v1";
|
||||||
|
export const OLLAMA_DEFAULT_API_KEY = "ollama";
|
||||||
|
|
||||||
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
||||||
export const MINIMAX_API_COST = {
|
export const MINIMAX_API_COST = {
|
||||||
input: 15,
|
input: 15,
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export {
|
|||||||
applyKimiCodeProviderConfig,
|
applyKimiCodeProviderConfig,
|
||||||
applyMoonshotConfig,
|
applyMoonshotConfig,
|
||||||
applyMoonshotProviderConfig,
|
applyMoonshotProviderConfig,
|
||||||
|
applyOllamaConfig,
|
||||||
|
applyOllamaProviderConfig,
|
||||||
applyOpenrouterConfig,
|
applyOpenrouterConfig,
|
||||||
applyOpenrouterProviderConfig,
|
applyOpenrouterProviderConfig,
|
||||||
applySyntheticConfig,
|
applySyntheticConfig,
|
||||||
@ -39,6 +41,7 @@ export {
|
|||||||
setKimiCodeApiKey,
|
setKimiCodeApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
setMoonshotApiKey,
|
setMoonshotApiKey,
|
||||||
|
setOllamaApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
setOpenrouterApiKey,
|
setOpenrouterApiKey,
|
||||||
setSyntheticApiKey,
|
setSyntheticApiKey,
|
||||||
@ -64,4 +67,6 @@ export {
|
|||||||
MOONSHOT_BASE_URL,
|
MOONSHOT_BASE_URL,
|
||||||
MOONSHOT_DEFAULT_MODEL_ID,
|
MOONSHOT_DEFAULT_MODEL_ID,
|
||||||
MOONSHOT_DEFAULT_MODEL_REF,
|
MOONSHOT_DEFAULT_MODEL_REF,
|
||||||
|
OLLAMA_BASE_URL,
|
||||||
|
OLLAMA_DEFAULT_API_KEY,
|
||||||
} from "./onboard-auth.models.js";
|
} from "./onboard-auth.models.js";
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export type AuthChoice =
|
|||||||
| "github-copilot"
|
| "github-copilot"
|
||||||
| "copilot-proxy"
|
| "copilot-proxy"
|
||||||
| "qwen-portal"
|
| "qwen-portal"
|
||||||
|
| "ollama"
|
||||||
| "skip";
|
| "skip";
|
||||||
export type GatewayAuthChoice = "token" | "password";
|
export type GatewayAuthChoice = "token" | "password";
|
||||||
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
export type ResetScope = "config" | "config+creds+sessions" | "full";
|
||||||
@ -71,6 +72,7 @@ export type OnboardOptions = {
|
|||||||
syntheticApiKey?: string;
|
syntheticApiKey?: string;
|
||||||
veniceApiKey?: string;
|
veniceApiKey?: string;
|
||||||
opencodeZenApiKey?: string;
|
opencodeZenApiKey?: string;
|
||||||
|
ollamaBaseUrl?: string;
|
||||||
gatewayPort?: number;
|
gatewayPort?: number;
|
||||||
gatewayBind?: GatewayBind;
|
gatewayBind?: GatewayBind;
|
||||||
gatewayAuth?: GatewayAuthChoice;
|
gatewayAuth?: GatewayAuthChoice;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user