From be5502d0f9bec8a0a8318f932d04d7ed06b28851 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Mon, 26 Jan 2026 19:51:04 -0500 Subject: [PATCH] feat: Add OVHcloud AI Endpoints provider --- docs/providers/index.md | 1 + docs/providers/ovhcloud.md | 71 ++++++++ src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 17 ++ src/agents/ovhcloud-models.ts | 170 ++++++++++++++++++ src/cli/program/register.onboard.ts | 3 +- src/commands/auth-choice-options.ts | 12 ++ .../auth-choice.apply.api-providers.ts | 65 +++++++ .../auth-choice.preferred-provider.ts | 1 + src/commands/onboard-auth.config-core.ts | 77 ++++++++ src/commands/onboard-auth.credentials.ts | 13 ++ src/commands/onboard-auth.models.ts | 30 ++++ src/commands/onboard-auth.ts | 4 + .../local/auth-choice.ts | 21 +++ src/commands/onboard-types.ts | 2 + 15 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 docs/providers/ovhcloud.md create mode 100644 src/agents/ovhcloud-models.ts diff --git a/docs/providers/index.md b/docs/providers/index.md index b4779d201..4ad5f7d16 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -45,6 +45,7 @@ See [Venice AI](/providers/venice). - [GLM models](/providers/glm) - [MiniMax](/providers/minimax) - [Venius (Venice AI, privacy-focused)](/providers/venice) +- [OVHcloud AI Endpoints](/providers/ovhcloud) - [Ollama (local models)](/providers/ollama) ## Transcription providers diff --git a/docs/providers/ovhcloud.md b/docs/providers/ovhcloud.md new file mode 100644 index 000000000..a0a194011 --- /dev/null +++ b/docs/providers/ovhcloud.md @@ -0,0 +1,71 @@ +--- +summary: "Use OVHcloud AI Endpoints in Clawdbot" +read_when: + - You want OVHcloud AI Endpoints models in Clawdbot + - You need OVHcloud setup guidance +--- +# OVHcloud AI Endpoints + +OVHcloud is the leading cloud provider in Europe and provides AI Endpoints: inference APIs for a selection of open-source models, such as Llama, Qwen, GPT OSS, and more. All inferences run in Europe, offering GDPR compliance, sovereignty, and data privacy. Your prompt and the LLM response are neither used nor saved. + +Source: [OVHcloud AI Endpoints](https://www.ovhcloud.com/en/public-cloud/ai-endpoints/) + +## Model overview + +OVHcloud AI Endpoints offers access to multiple open-weight models through a +serverless inference API. The recommended model is **gpt-oss-120b**, a powerful 120 billion parameter model made by OpenAI. + +You can also explore our [catalog](https://www.ovhcloud.com/en/public-cloud/ai-endpoints/catalog) to browse all our available models. + +## Setup + +### Quick start + +Configure via CLI: + +```bash +clawdbot onboard --auth-choice ovhcloud-api-key +``` + +Or set the API key manually: + +```bash +export OVHCLOUD_API_KEY="your-api-key-here" +``` + +### Config snippet + +```json5 +{ + agents: { + defaults: { + model: { primary: "ovhcloud/gpt-oss-120b" } + } + } +} +``` + +## Notes + +- Model refs use `ovhcloud/` format. +- Your data is not reused or kept by OVHcloud; data privacy is guaranteed. +- Update pricing values in `models.json` if you need exact cost tracking. +- See [Model providers](/concepts/model-providers) for provider rules. +- Use `clawdbot models list` and `clawdbot models set ovhcloud/gpt-oss-120b` to switch models. + +## Troubleshooting + +### "Unknown model: ovhcloud/model-name" + +This usually means the **OVHcloud provider isn't configured** (no provider entry and no OVHcloud auth profile/env key found). Fix by: + +- Running `clawdbot configure` and selecting **OVHcloud**, or +- Adding the `models.providers.ovhcloud` block manually, or +- Setting `OVHCLOUD_API_KEY` (or an OVHcloud auth profile) so the provider can be injected. + +Make sure the model id is **case-sensitive**: `ovhcloud/gpt-oss-120b`. + +Then recheck with: +```bash +clawdbot models list +``` \ No newline at end of file diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 680d0f53c..70d9debea 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -285,6 +285,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", + ovhcloud: "OVHCLOUD_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) return null; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 996f09dd0..0bb9c1702 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,6 +13,7 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; +import { discoverOvhcloudModels, OVHCLOUD_BASE_URL } from "./ovhcloud-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -359,6 +360,15 @@ async function buildOllamaProvider(): Promise { }; } +async function buildOvhcloudProvider(): Promise { + const models = await discoverOvhcloudModels(); + return { + baseUrl: OVHCLOUD_BASE_URL, + api: "openai-completions", + models, + }; +} + export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { @@ -418,6 +428,13 @@ export async function resolveImplicitProviders(params: { providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey }; } + const ovhcloudKey = + resolveEnvApiKeyVarName("ovhcloud") ?? + resolveApiKeyFromProfiles({ provider: "ovhcloud", store: authStore }); + if (ovhcloudKey) { + providers.ovhcloud = { ...(await buildOvhcloudProvider()), apiKey: ovhcloudKey }; + } + return providers; } diff --git a/src/agents/ovhcloud-models.ts b/src/agents/ovhcloud-models.ts new file mode 100644 index 000000000..4152eb060 --- /dev/null +++ b/src/agents/ovhcloud-models.ts @@ -0,0 +1,170 @@ +import type { ModelDefinitionConfig } from "../config/types.js"; + +export const OVHCLOUD_BASE_URL = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1"; +export const OVHCLOUD_DEFAULT_MODEL_ID = "gpt-oss-120b"; +export const OVHCLOUD_DEFAULT_MODEL_REF = `ovhcloud/${OVHCLOUD_DEFAULT_MODEL_ID}`; +export const OVHCLOUD_DEFAULT_CONTEXT_WINDOW = 128000; +export const OVHCLOUD_DEFAULT_MAX_TOKENS = 8192; +export const OVHCLOUD_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +// OVHcloud Catalog API response types +interface OvhcloudModelPricing { + prompt: string; + completion: string; + input_cache_reads: string; + input_cache_writes: string; + image: string; + request: string; +} + +interface OvhcloudModel { + id: string; + name: string; + description?: string; + context_length: number; + max_output_length: number; + input_modalities: string[]; + output_modalities: string[]; + supported_features: string[]; + pricing: OvhcloudModelPricing; + hugging_face_id?: string; + openrouter?: { + slug: string; + }; + quantization?: string; + created?: number; + datacenters?: Array<{ country_code: string }>; +} + +interface OvhcloudCatalogResponse { + data: OvhcloudModel[]; +} + +const OVHCLOUD_CATALOG_URL = "https://catalog.endpoints.ai.ovh.net/rest/v2/openrouter"; + +/** + * Discover models from OVHcloud AI Endpoints catalog with fallback to default model. + * The catalog endpoint is public and does not require authentication. + */ +export async function discoverOvhcloudModels(): Promise { + // Skip API discovery in test environment + if (process.env.VITEST || process.env.NODE_ENV === "test") { + return [ + { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: OVHCLOUD_DEFAULT_MODEL_ID, + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }, + ]; + } + + try { + const response = await fetch(OVHCLOUD_CATALOG_URL, { + signal: AbortSignal.timeout(5000), + }); + + if (!response.ok) { + console.warn( + `[ovhcloud-models] Failed to discover models: HTTP ${response.status}, using default model`, + ); + return [ + { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: OVHCLOUD_DEFAULT_MODEL_ID, + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }, + ]; + } + + const data = (await response.json()) as OvhcloudCatalogResponse; + if (!Array.isArray(data.data) || data.data.length === 0) { + console.warn("[ovhcloud-models] No models found from catalog, using default model"); + return [ + { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: OVHCLOUD_DEFAULT_MODEL_ID, + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }, + ]; + } + + // Convert discovered models to ModelDefinitionConfig + const models: ModelDefinitionConfig[] = data.data.map((apiModel) => { + const isReasoning = apiModel.supported_features?.includes("reasoning") ?? false; + const hasVision = apiModel.input_modalities?.includes("image") ?? false; + + // Parse pricing (values are strings representing cost per token, convert to cost per million tokens) + // Example: "0.00000009" per token = 0.09 per million tokens + const parsePricing = (value: string): number => { + if (!value || value === "") return 0; + const num = parseFloat(value); + if (isNaN(num) || num === 0) return 0; + // Convert per-token to per-million-tokens, round to nearest integer + // For very small values (< 0.5 per million), use Math.ceil to avoid rounding to 0 + const perMillion = num * 1000000; + return perMillion < 0.5 ? Math.ceil(perMillion) : Math.round(perMillion); + }; + + const cost = { + input: parsePricing(apiModel.pricing.prompt), + output: parsePricing(apiModel.pricing.completion), + cacheRead: parsePricing(apiModel.pricing.input_cache_reads), + cacheWrite: parsePricing(apiModel.pricing.input_cache_writes), + }; + + return { + id: apiModel.id, + name: apiModel.name || apiModel.id, + reasoning: isReasoning, + input: hasVision ? ["text", "image"] : ["text"], + cost, + contextWindow: apiModel.context_length || OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: apiModel.max_output_length || OVHCLOUD_DEFAULT_MAX_TOKENS, + }; + }); + + return models.length > 0 + ? models + : [ + { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: OVHCLOUD_DEFAULT_MODEL_ID, + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }, + ]; + } catch (error) { + console.warn(`[ovhcloud-models] Discovery failed: ${String(error)}, using default model`); + return [ + { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: OVHCLOUD_DEFAULT_MODEL_ID, + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }, + ]; + } +} diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index eac6a60df..52c4d6ffe 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|ovhcloud-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", ) .option( "--token-provider ", @@ -75,6 +75,7 @@ export function registerOnboardCommand(program: Command) { .option("--minimax-api-key ", "MiniMax API key") .option("--synthetic-api-key ", "Synthetic API key") .option("--venice-api-key ", "Venice API key") + .option("--ovhcloud-api-key ", "OVHcloud AI Endpoints API key") .option("--opencode-zen-api-key ", "OpenCode Zen API key") .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|tailnet|lan|auto|custom") diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 6b49ff17b..281fbb669 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -20,6 +20,7 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" + | "ovhcloud" | "qwen"; export type AuthChoiceGroup = { @@ -71,6 +72,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "Privacy-focused (uncensored models)", choices: ["venice-api-key"], }, + { + value: "ovhcloud", + label: "OVHcloud AI Endpoints", + hint: "European-based with sovereignty and data privacy", + choices: ["ovhcloud-api-key"], + }, { value: "google", label: "Google", @@ -147,6 +154,11 @@ export function buildAuthChoiceOptions(params: { label: "Venice AI API key", hint: "Privacy-focused inference (uncensored models)", }); + options.push({ + value: "ovhcloud-api-key", + label: "OVHcloud AI Endpoints API key", + hint: "European-based with sovereignty and data privacy", + }); options.push({ value: "github-copilot", label: "GitHub Copilot (GitHub device login)", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..d4a7f5fca 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -21,6 +21,8 @@ import { applyOpencodeZenProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, + applyOvhcloudConfig, + applyOvhcloudProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyVeniceConfig, @@ -31,6 +33,7 @@ import { KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, + OVHCLOUD_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, @@ -39,6 +42,7 @@ import { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setOvhcloudApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -83,6 +87,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "synthetic-api-key"; } else if (params.opts.tokenProvider === "venice") { authChoice = "venice-api-key"; + } else if (params.opts.tokenProvider === "ovhcloud") { + authChoice = "ovhcloud-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; } @@ -522,6 +528,65 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "ovhcloud-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "ovhcloud") { + await setOvhcloudApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Access the control panel at https://www.ovhcloud.com/ and navigate to", + "Public Cloud > AI & Machine Learning > AI Endpoints > API Keys", + "Recommended model: gpt-oss-120b", + ].join("\n"), + "OVHcloud AI Endpoints", + ); + } + + const envKey = resolveEnvApiKey("ovhcloud"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing OVHCLOUD_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setOvhcloudApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter OVHcloud AI Endpoints API key", + validate: validateApiKeyInput, + }); + await setOvhcloudApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "ovhcloud:default", + provider: "ovhcloud", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: OVHCLOUD_DEFAULT_MODEL_REF, + applyDefaultConfig: applyOvhcloudConfig, + applyProviderConfig: applyOvhcloudProviderConfig, + noteDefault: OVHCLOUD_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + if (authChoice === "opencode-zen") { let hasCredential = false; if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") { diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index 6fe26b59a..5c3b4d763 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -20,6 +20,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "zai-api-key": "zai", "synthetic-api-key": "synthetic", "venice-api-key": "venice", + "ovhcloud-api-key": "ovhcloud", "github-copilot": "github-copilot", "copilot-proxy": "copilot-proxy", "minimax-cloud": "minimax", diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 0d3a8523a..d8ffd5266 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -19,12 +19,16 @@ import { import { buildKimiCodeModelDefinition, buildMoonshotModelDefinition, + buildOvhcloudModelDefinition, KIMI_CODE_BASE_URL, KIMI_CODE_MODEL_ID, KIMI_CODE_MODEL_REF, MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + OVHCLOUD_BASE_URL, + OVHCLOUD_DEFAULT_MODEL_ID, + OVHCLOUD_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig { @@ -411,6 +415,79 @@ export function applyVeniceConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +/** + * Apply OVHcloud provider configuration without changing the default model. + * Registers OVHcloud models and sets up the provider, but preserves existing model selection. + */ +export function applyOvhcloudProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[OVHCLOUD_DEFAULT_MODEL_REF] = { + ...models[OVHCLOUD_DEFAULT_MODEL_REF], + alias: models[OVHCLOUD_DEFAULT_MODEL_REF]?.alias ?? "gpt-oss-120b", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.ovhcloud; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const defaultModel = buildOvhcloudModelDefinition(); + const hasDefaultModel = existingModels.some((model) => model.id === OVHCLOUD_DEFAULT_MODEL_ID); + const mergedModels = hasDefaultModel ? existingModels : [...existingModels, defaultModel]; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + providers.ovhcloud = { + ...existingProviderRest, + baseUrl: OVHCLOUD_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : [defaultModel], + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +/** + * Apply OVHcloud provider configuration AND set OVHcloud as the default model. + * Use this when OVHcloud is the primary provider choice during onboarding. + */ +export function applyOvhcloudConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyOvhcloudProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: OVHCLOUD_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyAuthProfileConfig( cfg: ClawdbotConfig, params: { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0c7dff409..bdfa58f12 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -112,6 +112,19 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { }); } +export async function setOvhcloudApiKey(key: string, agentDir?: string) { + // Write to resolved agent dir so gateway finds credentials on startup. + upsertAuthProfile({ + profileId: "ovhcloud:default", + credential: { + type: "api_key", + provider: "ovhcloud", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index de5a4edaa..037814e1f 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -1,4 +1,12 @@ import type { ModelDefinitionConfig } from "../config/types.js"; +import { + OVHCLOUD_BASE_URL, + OVHCLOUD_DEFAULT_COST, + OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + OVHCLOUD_DEFAULT_MAX_TOKENS, + OVHCLOUD_DEFAULT_MODEL_ID, + OVHCLOUD_DEFAULT_MODEL_REF, +} from "../agents/ovhcloud-models.js"; export const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1"; export const MINIMAX_API_BASE_URL = "https://api.minimax.io/anthropic"; @@ -52,6 +60,16 @@ export const KIMI_CODE_DEFAULT_COST = { cacheWrite: 0, }; +// Re-export OVHcloud constants from ovhcloud-models.ts +export { + OVHCLOUD_BASE_URL, + OVHCLOUD_DEFAULT_COST, + OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + OVHCLOUD_DEFAULT_MAX_TOKENS, + OVHCLOUD_DEFAULT_MODEL_ID, + OVHCLOUD_DEFAULT_MODEL_REF, +}; + const MINIMAX_MODEL_CATALOG = { "MiniMax-M2.1": { name: "MiniMax M2.1", reasoning: false }, "MiniMax-M2.1-lightning": { @@ -116,3 +134,15 @@ export function buildKimiCodeModelDefinition(): ModelDefinitionConfig { compat: KIMI_CODE_COMPAT, }; } + +export function buildOvhcloudModelDefinition(): ModelDefinitionConfig { + return { + id: OVHCLOUD_DEFAULT_MODEL_ID, + name: "gpt-oss-120b", + reasoning: false, + input: ["text"], + cost: OVHCLOUD_DEFAULT_COST, + contextWindow: OVHCLOUD_DEFAULT_CONTEXT_WINDOW, + maxTokens: OVHCLOUD_DEFAULT_MAX_TOKENS, + }; +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b122d89cf..41401b43f 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -11,6 +11,8 @@ export { applyMoonshotProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, + applyOvhcloudConfig, + applyOvhcloudProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyVeniceConfig, @@ -41,6 +43,7 @@ export { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setOvhcloudApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -64,4 +67,5 @@ export { MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + OVHCLOUD_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index c5558596a..1ea73efeb 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -14,6 +14,7 @@ import { applyMoonshotConfig, applyOpencodeZenConfig, applyOpenrouterConfig, + applyOvhcloudConfig, applySyntheticConfig, applyVeniceConfig, applyVercelAiGatewayConfig, @@ -25,6 +26,7 @@ import { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setOvhcloudApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -309,6 +311,25 @@ export async function applyNonInteractiveAuthChoice(params: { return applyVeniceConfig(nextConfig); } + if (authChoice === "ovhcloud-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "ovhcloud", + cfg: baseConfig, + flagValue: opts.ovhcloudApiKey, + flagName: "--ovhcloud-api-key", + envVar: "OVHCLOUD_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setOvhcloudApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "ovhcloud:default", + provider: "ovhcloud", + mode: "api_key", + }); + return applyOvhcloudConfig(nextConfig); + } + if ( authChoice === "minimax-cloud" || authChoice === "minimax-api" || diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index aa1d9afe0..17057b9bc 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -17,6 +17,7 @@ export type AuthChoice = | "kimi-code-api-key" | "synthetic-api-key" | "venice-api-key" + | "ovhcloud-api-key" | "codex-cli" | "apiKey" | "gemini-api-key" @@ -70,6 +71,7 @@ export type OnboardOptions = { minimaxApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; + ovhcloudApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind;