diff --git a/docs/providers/index.md b/docs/providers/index.md index c18ad70fb..f5508fa08 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) +- [Nillion (nilAI, privacy-preserving)](/providers/nillion) - [Ollama (local models)](/providers/ollama) ## Transcription providers diff --git a/docs/providers/nillion.md b/docs/providers/nillion.md new file mode 100644 index 000000000..d3d65aa26 --- /dev/null +++ b/docs/providers/nillion.md @@ -0,0 +1,191 @@ +--- +summary: "Use Nillion nilAI privacy-preserving models in Moltbot" +read_when: + - You want privacy-preserving AI inference in Moltbot + - You want Nillion nilAI setup guidance +--- +# Nillion nilAI + +Nillion provides privacy-preserving AI inference using blind computation. Your data remains private throughout the entire inference process - nilAI serves models running in a TEE and other techniques to ensure your prompts and responses are never exposed. + +## Why Nillion in Moltbot + +- **Privacy-preserving inference** using blind computation. +- **Reasoning support** for complex tasks. +- **Tool calling** support for function execution. +- **OpenAI-compatible** `/v1` endpoints for easy integration. + +## Features + +| Feature | Support | +|---------|---------| +| **Streaming** | Supported | +| **Reasoning** | Supported | +| **Tool/Function calling** | Supported | +| **Text generation** | Supported | +| **Vision/Images** | Not supported | + +## Setup + +### 1. Get API Key + +1. Sign up at [nilai.nillion.com](https://nilai.nillion.com) +2. Navigate to API settings +3. Create a new API key +4. Copy your API key + +### 2. Configure Moltbot + +**Option A: Environment Variable** + +```bash +export NILLION_API_KEY="your-api-key" +``` + +**Option B: Interactive Setup (Recommended)** + +```bash +moltbot onboard --auth-choice nillion-api-key +``` + +This will: +1. Prompt for your API key (or use existing `NILLION_API_KEY`) +2. Show available Nillion models +3. Let you pick your default model +4. Configure the provider automatically + +**Option C: Non-interactive** + +```bash +moltbot onboard --non-interactive \ + --auth-choice nillion-api-key \ + --nillion-api-key "your-api-key" +``` + +### 3. Verify Setup + +```bash +# List available models +moltbot models list | grep nillion + +# Run an agent turn +moltbot agent --session-id test --message "Hello, are you working?" +``` + +## Model Selection + +After setup, Moltbot shows available Nillion models. + +- **Default**: `nillion/openai/gpt-oss-20b` (nilAI Private 20B) for privacy-preserving inference with reasoning. + +Change your default model anytime: + +```bash +moltbot models set "nillion/openai/gpt-oss-20b" +``` + +List all available models: + +```bash +moltbot models list | grep nillion +``` + +## Configure via `moltbot configure` + +1. Run `moltbot configure` +2. Select **Model/auth** +3. Choose **Nillion nilAI** + +## Available Models + +| Model ID | Display Name | Context (tokens) | Features | +|----------|--------------|------------------|----------| +| `openai/gpt-oss-20b` | nilAI Private 20B | 128k | Reasoning, Tool calling | + +The full model reference is `nillion/openai/gpt-oss-20b`. + +## Reasoning Support + +nilAI Private 20B supports reasoning, which enables the model to think through complex problems step by step before providing an answer. This is particularly useful for: + +- Complex problem solving +- Multi-step reasoning tasks +- Tool/function calling decisions + +## Tool Calling + +nilAI supports function/tool calling, allowing the model to: + +- Invoke external tools when needed +- Return structured function call responses +- Handle parallel tool calls + +## Usage Examples + +```bash +# Start an agent session +moltbot agent --session-id myproject --message "Help me with my code" + +# Use with the TUI (terminal user interface) +moltbot tui + +# Send a message via a channel +moltbot message send --target "+1234567890" --text "Hello from nilAI" + +# Open the dashboard +moltbot dashboard +``` + +## Troubleshooting + +### API key not recognized + +```bash +echo $NILLION_API_KEY +moltbot models list | grep nillion +``` + +Ensure the key is correctly set. + +### Connection issues + +Nillion API is at `https://nilai-f910.nillion.network/v1`. Ensure your network allows HTTPS connections. + +### Model not found + +The model reference includes the full path: `nillion/openai/gpt-oss-20b`. Make sure to use the complete reference. + +## Config file example + +```json5 +{ + env: { NILLION_API_KEY: "your-key" }, + agents: { defaults: { model: { primary: "nillion/openai/gpt-oss-20b" } } }, + models: { + mode: "merge", + providers: { + nillion: { + baseUrl: "https://nilai-f910.nillion.network/v1", + apiKey: "${NILLION_API_KEY}", + api: "openai-responses", + models: [ + { + id: "openai/gpt-oss-20b", + name: "nilAI Private 20B", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 8192 + } + ] + } + } + } +} +``` + +## Links + +- [Nillion](https://nillion.com) +- [nilAI Documentation](https://nilai.nillion.com) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 96e4e4ae6..f06d1da21 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -283,6 +283,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { minimax: "MINIMAX_API_KEY", synthetic: "SYNTHETIC_API_KEY", venice: "VENICE_API_KEY", + nillion: "NILLION_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", }; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a176dac8a..2fe651061 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,6 +13,11 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; +import { + buildNillionModelDefinition, + NILLION_BASE_URL, + NILLION_MODEL_CATALOG, +} from "./nillion-models.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -350,6 +355,14 @@ async function buildVeniceProvider(): Promise { }; } +function buildNillionProvider(): ProviderConfig { + return { + baseUrl: NILLION_BASE_URL, + api: "openai-responses", + models: NILLION_MODEL_CATALOG.map(buildNillionModelDefinition), + }; +} + async function buildOllamaProvider(): Promise { const models = await discoverOllamaModels(); return { @@ -402,6 +415,13 @@ export async function resolveImplicitProviders(params: { providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey }; } + const nillionKey = + resolveEnvApiKeyVarName("nillion") ?? + resolveApiKeyFromProfiles({ provider: "nillion", store: authStore }); + if (nillionKey) { + providers.nillion = { ...buildNillionProvider(), apiKey: nillionKey }; + } + const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal"); if (qwenProfiles.length > 0) { providers["qwen-portal"] = { diff --git a/src/agents/nillion-models.ts b/src/agents/nillion-models.ts new file mode 100644 index 000000000..5d7028bc8 --- /dev/null +++ b/src/agents/nillion-models.ts @@ -0,0 +1,61 @@ +import type { ModelDefinitionConfig } from "../config/types.js"; + +export const NILLION_BASE_URL = "https://nilai-f910.nillion.network/v1"; +export const NILLION_DEFAULT_MODEL_ID = "openai/gpt-oss-20b"; +export const NILLION_DEFAULT_MODEL_REF = `nillion/${NILLION_DEFAULT_MODEL_ID}`; + +// Nillion uses credit-based pricing. Set to 0 as costs vary. +export const NILLION_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +const NILLION_COMPAT = { + supportsStore: false, + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", +} as const; + +/** + * Catalog of Nillion nilAI models. + * + * Nillion provides privacy-preserving AI inference using secure multi-party + * computation (MPC) and other cryptographic techniques. The nilAI platform + * uses blind computation to keep your data private during inference. + * + * Model ID mapping: + * - User-facing: nillion/openai/gpt-oss-20b + * - Display name: nilAI Private 20B + * - API model ID: openai/gpt-oss-20b + */ +export const NILLION_MODEL_CATALOG = [ + { + id: "openai/gpt-oss-20b", + name: "nilAI Private 20B", + reasoning: true, + input: ["text"], + contextWindow: 128000, + maxTokens: 8192, + }, +] as const; + +export type NillionCatalogEntry = (typeof NILLION_MODEL_CATALOG)[number]; + +/** + * Build a ModelDefinitionConfig from a Nillion catalog entry. + */ +export function buildNillionModelDefinition(entry: NillionCatalogEntry): ModelDefinitionConfig { + return { + id: entry.id, + name: entry.name, + reasoning: entry.reasoning, + input: [...entry.input], + cost: NILLION_DEFAULT_COST, + contextWindow: entry.contextWindow, + maxTokens: entry.maxTokens, + compat: NILLION_COMPAT, + }; +} diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..ba038100f 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -17,6 +17,8 @@ import { applyKimiCodeProviderConfig, applyMoonshotConfig, applyMoonshotProviderConfig, + applyNillionConfig, + applyNillionProviderConfig, applyOpencodeZenConfig, applyOpencodeZenProviderConfig, applyOpenrouterConfig, @@ -30,6 +32,7 @@ import { applyZaiConfig, KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, + NILLION_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, @@ -37,6 +40,7 @@ import { setGeminiApiKey, setKimiCodeApiKey, setMoonshotApiKey, + setNillionApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, @@ -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 === "nillion") { + authChoice = "nillion-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; } @@ -522,6 +528,64 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "nillion-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "nillion") { + await setNillionApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Nillion nilAI provides privacy-preserving AI inference.", + "Get your API key at: https://nilai.nillion.com", + ].join("\n"), + "Nillion nilAI", + ); + } + + const envKey = resolveEnvApiKey("nillion"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing NILLION_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setNillionApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Nillion API key", + validate: validateApiKeyInput, + }); + await setNillionApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "nillion:default", + provider: "nillion", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: NILLION_DEFAULT_MODEL_REF, + applyDefaultConfig: applyNillionConfig, + applyProviderConfig: applyNillionProviderConfig, + noteDefault: NILLION_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..6b9ee5efc 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", + "nillion-api-key": "nillion", "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 921ee01d1..c50fa8982 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -10,6 +10,12 @@ import { VENICE_DEFAULT_MODEL_REF, VENICE_MODEL_CATALOG, } from "../agents/venice-models.js"; +import { + buildNillionModelDefinition, + NILLION_BASE_URL, + NILLION_MODEL_CATALOG, +} from "../agents/nillion-models.js"; +import { NILLION_DEFAULT_MODEL_REF } from "./onboard-auth.credentials.js"; import type { MoltbotConfig } from "../config/config.js"; import { OPENROUTER_DEFAULT_MODEL_REF, @@ -411,6 +417,83 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig { }; } +/** + * Apply Nillion provider configuration without changing the default model. + * Registers Nillion models and sets up the provider, but preserves existing model selection. + */ +export function applyNillionProviderConfig(cfg: MoltbotConfig): MoltbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[NILLION_DEFAULT_MODEL_REF] = { + ...models[NILLION_DEFAULT_MODEL_REF], + alias: models[NILLION_DEFAULT_MODEL_REF]?.alias ?? "nilAI Private 20B", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.nillion; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const nillionModels = NILLION_MODEL_CATALOG.map(buildNillionModelDefinition); + const mergedModels = [ + ...existingModels, + ...nillionModels.filter( + (model) => !existingModels.some((existing) => existing.id === model.id), + ), + ]; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + providers.nillion = { + ...existingProviderRest, + baseUrl: NILLION_BASE_URL, + api: "openai-responses", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : nillionModels, + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +/** + * Apply Nillion provider configuration AND set Nillion as the default model. + * Use this when Nillion is the primary provider choice during onboarding. + */ +export function applyNillionConfig(cfg: MoltbotConfig): MoltbotConfig { + const next = applyNillionProviderConfig(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: NILLION_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyAuthProfileConfig( cfg: MoltbotConfig, params: { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index b2fb58542..1e98b0f7f 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -112,6 +112,20 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { }); } +export async function setNillionApiKey(key: string, agentDir?: string) { + // Write to resolved agent dir so gateway finds credentials on startup. + upsertAuthProfile({ + profileId: "nillion:default", + credential: { + type: "api_key", + provider: "nillion", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + +export const NILLION_DEFAULT_MODEL_REF = "nillion/openai/gpt-oss-20b"; 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.ts b/src/commands/onboard-auth.ts index b122d89cf..5cb9ab37e 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -3,12 +3,15 @@ export { SYNTHETIC_DEFAULT_MODEL_REF, } from "../agents/synthetic-models.js"; export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js"; +export { NILLION_DEFAULT_MODEL_ID, NILLION_DEFAULT_MODEL_REF } from "../agents/nillion-models.js"; export { applyAuthProfileConfig, applyKimiCodeConfig, applyKimiCodeProviderConfig, applyMoonshotConfig, applyMoonshotProviderConfig, + applyNillionConfig, + applyNillionProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, applySyntheticConfig, @@ -33,12 +36,14 @@ export { applyOpencodeZenProviderConfig, } from "./onboard-auth.config-opencode.js"; export { + NILLION_DEFAULT_MODEL_REF as NILLION_CREDENTIALS_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, setAnthropicApiKey, setGeminiApiKey, setKimiCodeApiKey, setMinimaxApiKey, setMoonshotApiKey, + setNillionApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index aa1d9afe0..203255f98 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" + | "nillion-api-key" | "codex-cli" | "apiKey" | "gemini-api-key" @@ -70,6 +71,7 @@ export type OnboardOptions = { minimaxApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; + nillionApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind;