From 13f32e2f7dad1e1415c6c5b99a1356f0368ee605 Mon Sep 17 00:00:00 2001 From: John Fawcett Date: Mon, 23 Feb 2026 17:29:27 -0600 Subject: [PATCH] feat: Add Kilo Gateway provider (#20212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add Kilo Gateway provider Add support for Kilo Gateway as a model provider, similar to OpenRouter. Kilo Gateway provides a unified API that routes requests to many models behind a single endpoint and API key. Changes: - Add kilocode provider option to auth-choice and onboarding flows - Add KILOCODE_API_KEY environment variable support - Add kilocode/ model prefix handling in model-auth and extra-params - Add provider documentation in docs/providers/kilocode.md - Update model-providers.md with Kilo Gateway section - Add design doc for the integration * kilocode: add provider tests and normalize onboard auth-choice registration * kilocode: register in resolveImplicitProviders so models appear in provider filter * kilocode: update base URL from /api/openrouter/ to /api/gateway/ * docs: fix formatting in kilocode docs * fix: address PR review — remove kilocode from cacheRetention, fix stale model refs and CLI name in docs, fix TS2742 * docs: fix stale refs in design doc — Moltbot to OpenClaw, MoltbotConfig to OpenClawConfig, remove extra-params section, fix doc path * fix: use resolveAgentModelPrimaryValue for AgentModelConfig union type --------- Co-authored-by: Mark IJbema --- docs/concepts/model-providers.md | 12 + docs/design/kilo-gateway-integration.md | 534 ++++++++++++++++++ docs/providers/kilocode.md | 50 ++ src/agents/model-auth.ts | 1 + .../models-config.providers.kilocode.test.ts | 49 ++ src/agents/models-config.providers.ts | 37 ++ src/agents/pi-embedded-runner/cache-ttl.ts | 3 + .../pi-embedded-runner/kilocode.test.ts | 21 + src/agents/transcript-policy.ts | 2 +- src/cli/program/register.onboard.ts | 1 + src/commands/auth-choice-options.ts | 7 + .../auth-choice.apply.api-providers.ts | 17 + .../auth-choice.preferred-provider.ts | 1 + .../onboard-auth.config-core.kilocode.test.ts | 168 ++++++ src/commands/onboard-auth.config-core.ts | 37 ++ src/commands/onboard-auth.credentials.ts | 13 + src/commands/onboard-auth.models.ts | 23 + src/commands/onboard-auth.ts | 7 + .../local/auth-choice-inference.ts | 1 + .../local/auth-choice.ts | 25 + src/commands/onboard-provider-auth-flags.ts | 8 + src/commands/onboard-types.ts | 3 + src/config/io.ts | 1 + 23 files changed, 1020 insertions(+), 1 deletion(-) create mode 100644 docs/design/kilo-gateway-integration.md create mode 100644 docs/providers/kilocode.md create mode 100644 src/agents/models-config.providers.kilocode.test.ts create mode 100644 src/agents/pi-embedded-runner/kilocode.test.ts create mode 100644 src/commands/onboard-auth.config-core.kilocode.test.ts diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 1d6e6a0eb96..192b2fa66fc 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -126,10 +126,22 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** - Example model: `vercel-ai-gateway/anthropic/claude-opus-4.6` - CLI: `openclaw onboard --auth-choice ai-gateway-api-key` +### Kilo Gateway + +- Provider: `kilocode` +- Auth: `KILOCODE_API_KEY` +- Example model: `kilocode/anthropic/claude-opus-4.6` +- CLI: `openclaw onboard --kilocode-api-key ` +- Base URL: `https://api.kilo.ai/api/gateway/` + +See [/providers/kilocode](/providers/kilocode) for setup details. + ### Other built-in providers - OpenRouter: `openrouter` (`OPENROUTER_API_KEY`) - Example model: `openrouter/anthropic/claude-sonnet-4-5` +- Kilo Gateway: `kilocode` (`KILOCODE_API_KEY`) +- Example model: `kilocode/anthropic/claude-opus-4.6` - xAI: `xai` (`XAI_API_KEY`) - Mistral: `mistral` (`MISTRAL_API_KEY`) - Example model: `mistral/mistral-large-latest` diff --git a/docs/design/kilo-gateway-integration.md b/docs/design/kilo-gateway-integration.md new file mode 100644 index 00000000000..596a77f1385 --- /dev/null +++ b/docs/design/kilo-gateway-integration.md @@ -0,0 +1,534 @@ +# Kilo Gateway Provider Integration Design + +## Overview + +This document outlines the design for integrating "Kilo Gateway" as a first-class provider in OpenClaw, modeled after the existing OpenRouter implementation. Kilo Gateway uses an OpenAI-compatible completions API with a different base URL. + +## Design Decisions + +### 1. Provider Naming + +**Recommendation: `kilocode`** + +Rationale: + +- Matches the user config example provided (`kilocode` provider key) +- Consistent with existing provider naming patterns (e.g., `openrouter`, `opencode`, `moonshot`) +- Short and memorable +- Avoids confusion with generic "kilo" or "gateway" terms + +Alternative considered: `kilo-gateway` - rejected because hyphenated names are less common in the codebase and `kilocode` is more concise. + +### 2. Default Model Reference + +**Recommendation: `kilocode/anthropic/claude-opus-4.6`** + +Rationale: + +- Based on user config example +- Claude Opus 4.5 is a capable default model +- Explicit model selection avoids reliance on auto-routing + +### 3. Base URL Configuration + +**Recommendation: Hardcoded default with config override** + +- **Default Base URL:** `https://api.kilo.ai/api/gateway/` +- **Configurable:** Yes, via `models.providers.kilocode.baseUrl` + +This matches the pattern used by other providers like Moonshot, Venice, and Synthetic. + +### 4. Model Scanning + +**Recommendation: No dedicated model scanning endpoint initially** + +Rationale: + +- Kilo Gateway proxies to OpenRouter, so models are dynamic +- Users can manually configure models in their config +- If Kilo Gateway exposes a `/models` endpoint in the future, scanning can be added + +### 5. Special Handling + +**Recommendation: Inherit OpenRouter behavior for Anthropic models** + +Since Kilo Gateway proxies to OpenRouter, the same special handling should apply: + +- Cache TTL eligibility for `anthropic/*` models +- Extra params (cacheControlTtl) for `anthropic/*` models +- Transcript policy follows OpenRouter patterns + +## Files to Modify + +### Core Credential Management + +#### 1. `src/commands/onboard-auth.credentials.ts` + +Add: + +```typescript +export const KILOCODE_DEFAULT_MODEL_REF = "kilocode/anthropic/claude-opus-4.6"; + +export async function setKilocodeApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "kilocode:default", + credential: { + type: "api_key", + provider: "kilocode", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} +``` + +#### 2. `src/agents/model-auth.ts` + +Add to `envMap` in `resolveEnvApiKey()`: + +```typescript +const envMap: Record = { + // ... existing entries + kilocode: "KILOCODE_API_KEY", +}; +``` + +#### 3. `src/config/io.ts` + +Add to `SHELL_ENV_EXPECTED_KEYS`: + +```typescript +const SHELL_ENV_EXPECTED_KEYS = [ + // ... existing entries + "KILOCODE_API_KEY", +]; +``` + +### Config Application + +#### 4. `src/commands/onboard-auth.config-core.ts` + +Add new functions: + +```typescript +export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/"; + +export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[KILOCODE_DEFAULT_MODEL_REF] = { + ...models[KILOCODE_DEFAULT_MODEL_REF], + alias: models[KILOCODE_DEFAULT_MODEL_REF]?.alias ?? "Kilo Gateway", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.kilocode; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + + providers.kilocode = { + ...existingProviderRest, + baseUrl: KILOCODE_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig { + const next = applyKilocodeProviderConfig(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: KILOCODE_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} +``` + +### Auth Choice System + +#### 5. `src/commands/onboard-types.ts` + +Add to `AuthChoice` type: + +```typescript +export type AuthChoice = + // ... existing choices + "kilocode-api-key"; +// ... +``` + +Add to `OnboardOptions`: + +```typescript +export type OnboardOptions = { + // ... existing options + kilocodeApiKey?: string; + // ... +}; +``` + +#### 6. `src/commands/auth-choice-options.ts` + +Add to `AuthChoiceGroupId`: + +```typescript +export type AuthChoiceGroupId = + // ... existing groups + "kilocode"; +// ... +``` + +Add to `AUTH_CHOICE_GROUP_DEFS`: + +```typescript +{ + value: "kilocode", + label: "Kilo Gateway", + hint: "API key (OpenRouter-compatible)", + choices: ["kilocode-api-key"], +}, +``` + +Add to `buildAuthChoiceOptions()`: + +```typescript +options.push({ + value: "kilocode-api-key", + label: "Kilo Gateway API key", + hint: "OpenRouter-compatible gateway", +}); +``` + +#### 7. `src/commands/auth-choice.preferred-provider.ts` + +Add mapping: + +```typescript +const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { + // ... existing mappings + "kilocode-api-key": "kilocode", +}; +``` + +### Auth Choice Application + +#### 8. `src/commands/auth-choice.apply.api-providers.ts` + +Add import: + +```typescript +import { + // ... existing imports + applyKilocodeConfig, + applyKilocodeProviderConfig, + KILOCODE_DEFAULT_MODEL_REF, + setKilocodeApiKey, +} from "./onboard-auth.js"; +``` + +Add handling for `kilocode-api-key`: + +```typescript +if (authChoice === "kilocode-api-key") { + const store = ensureAuthProfileStore(params.agentDir, { + allowKeychainPrompt: false, + }); + const profileOrder = resolveAuthProfileOrder({ + cfg: nextConfig, + store, + provider: "kilocode", + }); + const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId])); + const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined; + let profileId = "kilocode:default"; + let mode: "api_key" | "oauth" | "token" = "api_key"; + let hasCredential = false; + + if (existingProfileId && existingCred?.type) { + profileId = existingProfileId; + mode = + existingCred.type === "oauth" ? "oauth" : existingCred.type === "token" ? "token" : "api_key"; + hasCredential = true; + } + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kilocode") { + await setKilocodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + const envKey = resolveEnvApiKey("kilocode"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing KILOCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setKilocodeApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + } + + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Kilo Gateway API key", + validate: validateApiKeyInput, + }); + await setKilocodeApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + hasCredential = true; + } + + if (hasCredential) { + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId, + provider: "kilocode", + mode, + }); + } + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: KILOCODE_DEFAULT_MODEL_REF, + applyDefaultConfig: applyKilocodeConfig, + applyProviderConfig: applyKilocodeProviderConfig, + noteDefault: KILOCODE_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; +} +``` + +Also add tokenProvider mapping at the top of the function: + +```typescript +if (params.opts.tokenProvider === "kilocode") { + authChoice = "kilocode-api-key"; +} +``` + +### CLI Registration + +#### 9. `src/cli/program/register.onboard.ts` + +Add CLI option: + +```typescript +.option("--kilocode-api-key ", "Kilo Gateway API key") +``` + +Add to action handler: + +```typescript +kilocodeApiKey: opts.kilocodeApiKey as string | undefined, +``` + +Update auth-choice help text: + +```typescript +.option( + "--auth-choice ", + "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|kilocode-api-key|ai-gateway-api-key|...", +) +``` + +### Non-Interactive Onboarding + +#### 10. `src/commands/onboard-non-interactive/local/auth-choice.ts` + +Add handling for `kilocode-api-key`: + +```typescript +if (authChoice === "kilocode-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "kilocode", + cfg: baseConfig, + flagValue: opts.kilocodeApiKey, + flagName: "--kilocode-api-key", + envVar: "KILOCODE_API_KEY", + }); + await setKilocodeApiKey(resolved.apiKey, agentDir); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "kilocode:default", + provider: "kilocode", + mode: "api_key", + }); + // ... apply default model +} +``` + +### Export Updates + +#### 11. `src/commands/onboard-auth.ts` + +Add exports: + +```typescript +export { + // ... existing exports + applyKilocodeConfig, + applyKilocodeProviderConfig, + KILOCODE_BASE_URL, +} from "./onboard-auth.config-core.js"; + +export { + // ... existing exports + KILOCODE_DEFAULT_MODEL_REF, + setKilocodeApiKey, +} from "./onboard-auth.credentials.js"; +``` + +### Special Handling (Optional) + +#### 12. `src/agents/pi-embedded-runner/cache-ttl.ts` + +Add Kilo Gateway support for Anthropic models: + +```typescript +export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean { + const normalizedProvider = provider.toLowerCase(); + const normalizedModelId = modelId.toLowerCase(); + if (normalizedProvider === "anthropic") return true; + if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/")) + return true; + if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) return true; + return false; +} +``` + +#### 13. `src/agents/transcript-policy.ts` + +Add Kilo Gateway handling (similar to OpenRouter): + +```typescript +const isKilocodeGemini = provider === "kilocode" && modelId.toLowerCase().includes("gemini"); + +// Include in needsNonImageSanitize check +const needsNonImageSanitize = + isGoogle || isAnthropic || isMistral || isOpenRouterGemini || isKilocodeGemini; +``` + +## Configuration Structure + +### User Config Example + +```json +{ + "models": { + "mode": "merge", + "providers": { + "kilocode": { + "baseUrl": "https://api.kilo.ai/api/gateway/", + "apiKey": "xxxxx", + "api": "openai-completions", + "models": [ + { + "id": "anthropic/claude-opus-4.6", + "name": "Anthropic: Claude Opus 4.6" + }, + { "id": "minimax/minimax-m2.1:free", "name": "Minimax: Minimax M2.1" } + ] + } + } + } +} +``` + +### Auth Profile Structure + +```json +{ + "profiles": { + "kilocode:default": { + "type": "api_key", + "provider": "kilocode", + "key": "xxxxx" + } + } +} +``` + +## Testing Considerations + +1. **Unit Tests:** + - Test `setKilocodeApiKey()` writes correct profile + - Test `applyKilocodeConfig()` sets correct defaults + - Test `resolveEnvApiKey("kilocode")` returns correct env var + +2. **Integration Tests:** + - Test onboarding flow with `--auth-choice kilocode-api-key` + - Test non-interactive onboarding with `--kilocode-api-key` + - Test model selection with `kilocode/` prefix + +3. **E2E Tests:** + - Test actual API calls through Kilo Gateway (live tests) + +## Migration Notes + +- No migration needed for existing users +- New users can immediately use `kilocode-api-key` auth choice +- Existing manual config with `kilocode` provider will continue to work + +## Future Considerations + +1. **Model Catalog:** If Kilo Gateway exposes a `/models` endpoint, add scanning support similar to `scanOpenRouterModels()` + +2. **OAuth Support:** If Kilo Gateway adds OAuth, extend the auth system accordingly + +3. **Rate Limiting:** Consider adding rate limit handling specific to Kilo Gateway if needed + +4. **Documentation:** Add docs at `docs/providers/kilocode.md` explaining setup and usage + +## Summary of Changes + +| File | Change Type | Description | +| ----------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- | +| `src/commands/onboard-auth.credentials.ts` | Add | `KILOCODE_DEFAULT_MODEL_REF`, `setKilocodeApiKey()` | +| `src/agents/model-auth.ts` | Modify | Add `kilocode` to `envMap` | +| `src/config/io.ts` | Modify | Add `KILOCODE_API_KEY` to shell env keys | +| `src/commands/onboard-auth.config-core.ts` | Add | `applyKilocodeProviderConfig()`, `applyKilocodeConfig()` | +| `src/commands/onboard-types.ts` | Modify | Add `kilocode-api-key` to `AuthChoice`, add `kilocodeApiKey` to options | +| `src/commands/auth-choice-options.ts` | Modify | Add `kilocode` group and option | +| `src/commands/auth-choice.preferred-provider.ts` | Modify | Add `kilocode-api-key` mapping | +| `src/commands/auth-choice.apply.api-providers.ts` | Modify | Add `kilocode-api-key` handling | +| `src/cli/program/register.onboard.ts` | Modify | Add `--kilocode-api-key` option | +| `src/commands/onboard-non-interactive/local/auth-choice.ts` | Modify | Add non-interactive handling | +| `src/commands/onboard-auth.ts` | Modify | Export new functions | +| `src/agents/pi-embedded-runner/cache-ttl.ts` | Modify | Add kilocode support | +| `src/agents/transcript-policy.ts` | Modify | Add kilocode Gemini handling | diff --git a/docs/providers/kilocode.md b/docs/providers/kilocode.md new file mode 100644 index 00000000000..08dbce7c2ce --- /dev/null +++ b/docs/providers/kilocode.md @@ -0,0 +1,50 @@ +--- +summary: "Use Kilo Gateway's unified API to access many models in OpenClaw" +read_when: + - You want a single API key for many LLMs + - You want to run models via Kilo Gateway in OpenClaw +--- + +# Kilo Gateway + +Kilo Gateway provides a **unified API** that routes requests to many models behind a single +endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL. + +## Getting an API key + +1. Go to [app.kilo.ai](https://app.kilo.ai) +2. Sign in or create an account +3. Navigate to API Keys and generate a new key + +## CLI setup + +```bash +openclaw onboard --kilocode-api-key +``` + +Or set the environment variable: + +```bash +export KILOCODE_API_KEY="your-api-key" +``` + +## Config snippet + +```json5 +{ + env: { KILOCODE_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { primary: "kilocode/anthropic/claude-opus-4.6" }, + }, + }, +} +``` + +## Notes + +- Model refs are `kilocode//` (e.g., `kilocode/anthropic/claude-opus-4.6`). +- Default model: `kilocode/anthropic/claude-opus-4.6` +- Base URL: `https://api.kilo.ai/api/gateway/` +- For more model/provider options, see [/concepts/model-providers](/concepts/model-providers). +- Kilo Gateway uses a Bearer token with your API key under the hood. diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index e3a2b8142de..56cf33cdc44 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -322,6 +322,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { qianfan: "QIANFAN_API_KEY", ollama: "OLLAMA_API_KEY", vllm: "VLLM_API_KEY", + kilocode: "KILOCODE_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) { diff --git a/src/agents/models-config.providers.kilocode.test.ts b/src/agents/models-config.providers.kilocode.test.ts new file mode 100644 index 00000000000..bb709d7d075 --- /dev/null +++ b/src/agents/models-config.providers.kilocode.test.ts @@ -0,0 +1,49 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { captureEnv } from "../test-utils/env.js"; +import { buildKilocodeProvider, resolveImplicitProviders } from "./models-config.providers.js"; + +describe("Kilo Gateway implicit provider", () => { + it("should include kilocode when KILOCODE_API_KEY is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["KILOCODE_API_KEY"]); + process.env.KILOCODE_API_KEY = "test-key"; + + try { + const providers = await resolveImplicitProviders({ agentDir }); + expect(providers?.kilocode).toBeDefined(); + expect(providers?.kilocode?.models?.length).toBeGreaterThan(0); + } finally { + envSnapshot.restore(); + } + }); + + it("should not include kilocode when no API key is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["KILOCODE_API_KEY"]); + delete process.env.KILOCODE_API_KEY; + + try { + const providers = await resolveImplicitProviders({ agentDir }); + expect(providers?.kilocode).toBeUndefined(); + } finally { + envSnapshot.restore(); + } + }); + + it("should build kilocode provider with correct configuration", () => { + const provider = buildKilocodeProvider(); + expect(provider.baseUrl).toBe("https://api.kilo.ai/api/gateway/"); + expect(provider.api).toBe("openai-completions"); + expect(provider.models).toBeDefined(); + expect(provider.models.length).toBeGreaterThan(0); + }); + + it("should include the default kilocode model", () => { + const provider = buildKilocodeProvider(); + const modelIds = provider.models.map((m) => m.id); + expect(modelIds).toContain("anthropic/claude-opus-4.6"); + }); +}); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 30e0326e609..5dc34c52fa4 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -764,6 +764,36 @@ export function buildNvidiaProvider(): ProviderConfig { }; } +// Kilo Gateway provider +const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/"; +const KILOCODE_DEFAULT_MODEL_ID = "anthropic/claude-opus-4.6"; +const KILOCODE_DEFAULT_CONTEXT_WINDOW = 200000; +const KILOCODE_DEFAULT_MAX_TOKENS = 8192; +const KILOCODE_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +export function buildKilocodeProvider(): ProviderConfig { + return { + baseUrl: KILOCODE_BASE_URL, + api: "openai-completions", + models: [ + { + id: KILOCODE_DEFAULT_MODEL_ID, + name: "Claude Opus 4.6", + reasoning: true, + input: ["text", "image"], + cost: KILOCODE_DEFAULT_COST, + contextWindow: KILOCODE_DEFAULT_CONTEXT_WINDOW, + maxTokens: KILOCODE_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + export async function resolveImplicitProviders(params: { agentDir: string; explicitProviders?: Record | null; @@ -951,6 +981,13 @@ export async function resolveImplicitProviders(params: { providers.nvidia = { ...buildNvidiaProvider(), apiKey: nvidiaKey }; } + const kilocodeKey = + resolveEnvApiKeyVarName("kilocode") ?? + resolveApiKeyFromProfiles({ provider: "kilocode", store: authStore }); + if (kilocodeKey) { + providers.kilocode = { ...buildKilocodeProvider(), apiKey: kilocodeKey }; + } + return providers; } diff --git a/src/agents/pi-embedded-runner/cache-ttl.ts b/src/agents/pi-embedded-runner/cache-ttl.ts index d3969e4fb62..53231bdc605 100644 --- a/src/agents/pi-embedded-runner/cache-ttl.ts +++ b/src/agents/pi-embedded-runner/cache-ttl.ts @@ -29,6 +29,9 @@ export function isCacheTtlEligibleProvider(provider: string, modelId: string): b if (normalizedProvider === "openrouter" && isOpenRouterCacheTtlModel(normalizedModelId)) { return true; } + if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) { + return true; + } return false; } diff --git a/src/agents/pi-embedded-runner/kilocode.test.ts b/src/agents/pi-embedded-runner/kilocode.test.ts new file mode 100644 index 00000000000..cbb626d8ba7 --- /dev/null +++ b/src/agents/pi-embedded-runner/kilocode.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { isCacheTtlEligibleProvider } from "./cache-ttl.js"; + +describe("kilocode cache-ttl eligibility", () => { + it("is eligible when model starts with anthropic/", () => { + expect(isCacheTtlEligibleProvider("kilocode", "anthropic/claude-opus-4.6")).toBe(true); + }); + + it("is eligible with other anthropic models", () => { + expect(isCacheTtlEligibleProvider("kilocode", "anthropic/claude-sonnet-4")).toBe(true); + }); + + it("is not eligible for non-anthropic models on kilocode", () => { + expect(isCacheTtlEligibleProvider("kilocode", "openai/gpt-5")).toBe(false); + }); + + it("is case-insensitive for provider name", () => { + expect(isCacheTtlEligibleProvider("Kilocode", "anthropic/claude-opus-4.6")).toBe(true); + expect(isCacheTtlEligibleProvider("KILOCODE", "Anthropic/claude-opus-4.6")).toBe(true); + }); +}); diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 7f7e08d6386..0672bf1e840 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -91,7 +91,7 @@ export function resolveTranscriptPolicy(params: { !OPENAI_COMPAT_TURN_MERGE_EXCLUDED_PROVIDERS.has(provider); const isMistral = isMistralModel({ provider, modelId }); const isOpenRouterGemini = - (provider === "openrouter" || provider === "opencode") && + (provider === "openrouter" || provider === "opencode" || provider === "kilocode") && modelId.toLowerCase().includes("gemini"); const isCopilotClaude = provider === "github-copilot" && modelId.toLowerCase().includes("claude"); diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index a530413ad39..4cd14ec04ff 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -133,6 +133,7 @@ export function registerOnboardCommand(program: Command) { openaiApiKey: opts.openaiApiKey as string | undefined, mistralApiKey: opts.mistralApiKey as string | undefined, openrouterApiKey: opts.openrouterApiKey as string | undefined, + kilocodeApiKey: opts.kilocodeApiKey as string | undefined, aiGatewayApiKey: opts.aiGatewayApiKey as string | undefined, cloudflareAiGatewayAccountId: opts.cloudflareAiGatewayAccountId as string | undefined, cloudflareAiGatewayGatewayId: opts.cloudflareAiGatewayGatewayId as string | undefined, diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index ea2f7218cb7..f611be1ce0d 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -94,6 +94,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "API key", choices: ["openrouter-api-key"], }, + { + value: "kilocode", + label: "Kilo Gateway", + hint: "API key (OpenRouter-compatible)", + choices: ["kilocode-api-key"], + }, { value: "qwen", label: "Qwen", @@ -206,6 +212,7 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray = [ label: "Qianfan API key", }, { value: "openrouter-api-key", label: "OpenRouter API key" }, + { value: "kilocode-api-key", label: "Kilo Gateway API key" }, { value: "litellm-api-key", label: "LiteLLM API key", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index c67559356b2..2b1e80387da 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -23,6 +23,8 @@ import { applyAuthProfileConfig, applyCloudflareAiGatewayConfig, applyCloudflareAiGatewayProviderConfig, + applyKilocodeConfig, + applyKilocodeProviderConfig, applyQianfanConfig, applyQianfanProviderConfig, applyKimiCodeConfig, @@ -50,6 +52,7 @@ import { applyZaiConfig, applyZaiProviderConfig, CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, + KILOCODE_DEFAULT_MODEL_REF, LITELLM_DEFAULT_MODEL_REF, QIANFAN_DEFAULT_MODEL_REF, KIMI_CODING_MODEL_REF, @@ -63,6 +66,7 @@ import { setCloudflareAiGatewayConfig, setQianfanApiKey, setGeminiApiKey, + setKilocodeApiKey, setLitellmApiKey, setKimiCodingApiKey, setMistralApiKey, @@ -97,6 +101,7 @@ const API_KEY_TOKEN_PROVIDER_AUTH_CHOICE: Record = { huggingface: "huggingface-api-key", mistral: "mistral-api-key", opencode: "opencode-zen", + kilocode: "kilocode-api-key", qianfan: "qianfan-api-key", }; @@ -277,6 +282,18 @@ const SIMPLE_API_KEY_PROVIDER_FLOWS: Partial> = { chutes: "chutes", "openai-api-key": "openai", "openrouter-api-key": "openrouter", + "kilocode-api-key": "kilocode", "ai-gateway-api-key": "vercel-ai-gateway", "cloudflare-ai-gateway-api-key": "cloudflare-ai-gateway", "moonshot-api-key": "moonshot", diff --git a/src/commands/onboard-auth.config-core.kilocode.test.ts b/src/commands/onboard-auth.config-core.kilocode.test.ts new file mode 100644 index 00000000000..33e16c6c88a --- /dev/null +++ b/src/commands/onboard-auth.config-core.kilocode.test.ts @@ -0,0 +1,168 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { resolveApiKeyForProvider, resolveEnvApiKey } from "../agents/model-auth.js"; +import type { OpenClawConfig } from "../config/config.js"; +import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; +import { captureEnv } from "../test-utils/env.js"; +import { + applyKilocodeProviderConfig, + applyKilocodeConfig, + KILOCODE_BASE_URL, +} from "./onboard-auth.config-core.js"; +import { KILOCODE_DEFAULT_MODEL_REF } from "./onboard-auth.credentials.js"; +import { + buildKilocodeModelDefinition, + KILOCODE_DEFAULT_MODEL_ID, + KILOCODE_DEFAULT_CONTEXT_WINDOW, + KILOCODE_DEFAULT_MAX_TOKENS, + KILOCODE_DEFAULT_COST, +} from "./onboard-auth.models.js"; + +const emptyCfg: OpenClawConfig = {}; + +describe("Kilo Gateway provider config", () => { + describe("constants", () => { + it("KILOCODE_BASE_URL points to kilo openrouter endpoint", () => { + expect(KILOCODE_BASE_URL).toBe("https://api.kilo.ai/api/gateway/"); + }); + + it("KILOCODE_DEFAULT_MODEL_REF includes provider prefix", () => { + expect(KILOCODE_DEFAULT_MODEL_REF).toBe("kilocode/anthropic/claude-opus-4.6"); + }); + + it("KILOCODE_DEFAULT_MODEL_ID is anthropic/claude-opus-4.6", () => { + expect(KILOCODE_DEFAULT_MODEL_ID).toBe("anthropic/claude-opus-4.6"); + }); + }); + + describe("buildKilocodeModelDefinition", () => { + it("returns correct model shape", () => { + const model = buildKilocodeModelDefinition(); + expect(model.id).toBe(KILOCODE_DEFAULT_MODEL_ID); + expect(model.name).toBe("Claude Opus 4.6"); + expect(model.reasoning).toBe(true); + expect(model.input).toEqual(["text", "image"]); + expect(model.contextWindow).toBe(KILOCODE_DEFAULT_CONTEXT_WINDOW); + expect(model.maxTokens).toBe(KILOCODE_DEFAULT_MAX_TOKENS); + expect(model.cost).toEqual(KILOCODE_DEFAULT_COST); + }); + }); + + describe("applyKilocodeProviderConfig", () => { + it("registers kilocode provider with correct baseUrl and api", () => { + const result = applyKilocodeProviderConfig(emptyCfg); + const provider = result.models?.providers?.kilocode; + expect(provider).toBeDefined(); + expect(provider?.baseUrl).toBe(KILOCODE_BASE_URL); + expect(provider?.api).toBe("openai-completions"); + }); + + it("includes the default model in the provider model list", () => { + const result = applyKilocodeProviderConfig(emptyCfg); + const provider = result.models?.providers?.kilocode; + const models = provider?.models; + expect(Array.isArray(models)).toBe(true); + const modelIds = models?.map((m) => m.id) ?? []; + expect(modelIds).toContain(KILOCODE_DEFAULT_MODEL_ID); + }); + + it("sets Kilo Gateway alias in agent default models", () => { + const result = applyKilocodeProviderConfig(emptyCfg); + const agentModel = result.agents?.defaults?.models?.[KILOCODE_DEFAULT_MODEL_REF]; + expect(agentModel).toBeDefined(); + expect(agentModel?.alias).toBe("Kilo Gateway"); + }); + + it("preserves existing alias if already set", () => { + const cfg: OpenClawConfig = { + agents: { + defaults: { + models: { + [KILOCODE_DEFAULT_MODEL_REF]: { alias: "My Custom Alias" }, + }, + }, + }, + }; + const result = applyKilocodeProviderConfig(cfg); + const agentModel = result.agents?.defaults?.models?.[KILOCODE_DEFAULT_MODEL_REF]; + expect(agentModel?.alias).toBe("My Custom Alias"); + }); + + it("does not change the default model selection", () => { + const cfg: OpenClawConfig = { + agents: { + defaults: { + model: { primary: "openai/gpt-5" }, + }, + }, + }; + const result = applyKilocodeProviderConfig(cfg); + expect(resolveAgentModelPrimaryValue(result.agents?.defaults?.model)).toBe("openai/gpt-5"); + }); + }); + + describe("applyKilocodeConfig", () => { + it("sets kilocode as the default model", () => { + const result = applyKilocodeConfig(emptyCfg); + expect(resolveAgentModelPrimaryValue(result.agents?.defaults?.model)).toBe( + KILOCODE_DEFAULT_MODEL_REF, + ); + }); + + it("also registers the provider", () => { + const result = applyKilocodeConfig(emptyCfg); + const provider = result.models?.providers?.kilocode; + expect(provider).toBeDefined(); + expect(provider?.baseUrl).toBe(KILOCODE_BASE_URL); + }); + }); + + describe("env var resolution", () => { + it("resolves KILOCODE_API_KEY from env", () => { + const envSnapshot = captureEnv(["KILOCODE_API_KEY"]); + process.env.KILOCODE_API_KEY = "test-kilo-key"; + + try { + const result = resolveEnvApiKey("kilocode"); + expect(result).not.toBeNull(); + expect(result?.apiKey).toBe("test-kilo-key"); + expect(result?.source).toContain("KILOCODE_API_KEY"); + } finally { + envSnapshot.restore(); + } + }); + + it("returns null when KILOCODE_API_KEY is not set", () => { + const envSnapshot = captureEnv(["KILOCODE_API_KEY"]); + delete process.env.KILOCODE_API_KEY; + + try { + const result = resolveEnvApiKey("kilocode"); + expect(result).toBeNull(); + } finally { + envSnapshot.restore(); + } + }); + + it("resolves the kilocode api key via resolveApiKeyForProvider", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["KILOCODE_API_KEY"]); + process.env.KILOCODE_API_KEY = "kilo-provider-test-key"; + + try { + const auth = await resolveApiKeyForProvider({ + provider: "kilocode", + agentDir, + }); + + expect(auth.apiKey).toBe("kilo-provider-test-key"); + expect(auth.mode).toBe("api-key"); + expect(auth.source).toContain("KILOCODE_API_KEY"); + } finally { + envSnapshot.restore(); + } + }); + }); +}); diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index e39d0a26fe6..328b12a1dd9 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -31,6 +31,7 @@ import type { OpenClawConfig } from "../config/config.js"; import type { ModelApi } from "../config/types.models.js"; import { HUGGINGFACE_DEFAULT_MODEL_REF, + KILOCODE_DEFAULT_MODEL_REF, MISTRAL_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, @@ -58,10 +59,12 @@ import { applyProviderConfigWithModelCatalog, } from "./onboard-auth.config-shared.js"; import { + buildKilocodeModelDefinition, buildMistralModelDefinition, buildZaiModelDefinition, buildMoonshotModelDefinition, buildXaiModelDefinition, + KILOCODE_DEFAULT_MODEL_ID, MISTRAL_BASE_URL, MISTRAL_DEFAULT_MODEL_ID, QIANFAN_BASE_URL, @@ -430,6 +433,40 @@ export function applyMistralConfig(cfg: OpenClawConfig): OpenClawConfig { return applyAgentDefaultModelPrimary(next, MISTRAL_DEFAULT_MODEL_REF); } +export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/"; + +/** + * Apply Kilo Gateway provider configuration without changing the default model. + * Registers Kilo Gateway and sets up the provider, but preserves existing model selection. + */ +export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[KILOCODE_DEFAULT_MODEL_REF] = { + ...models[KILOCODE_DEFAULT_MODEL_REF], + alias: models[KILOCODE_DEFAULT_MODEL_REF]?.alias ?? "Kilo Gateway", + }; + + const defaultModel = buildKilocodeModelDefinition(); + + return applyProviderConfigWithDefaultModel(cfg, { + agentModels: models, + providerId: "kilocode", + api: "openai-completions", + baseUrl: KILOCODE_BASE_URL, + defaultModel, + defaultModelId: KILOCODE_DEFAULT_MODEL_ID, + }); +} + +/** + * Apply Kilo Gateway provider configuration AND set Kilo Gateway as the default model. + * Use this when Kilo Gateway is the primary provider choice during onboarding. + */ +export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig { + const next = applyKilocodeProviderConfig(cfg); + return applyAgentDefaultModelPrimary(next, KILOCODE_DEFAULT_MODEL_REF); +} + export function applyAuthProfileConfig( cfg: OpenClawConfig, params: { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 958fa1739e9..fcd0fe29cb0 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -213,6 +213,7 @@ export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5"; export const LITELLM_DEFAULT_MODEL_REF = "litellm/claude-opus-4-6"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.6"; +export const KILOCODE_DEFAULT_MODEL_REF = "kilocode/anthropic/claude-opus-4.6"; export async function setZaiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. @@ -372,3 +373,15 @@ export async function setMistralApiKey(key: string, agentDir?: string) { agentDir: resolveAuthAgentDir(agentDir), }); } + +export async function setKilocodeApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "kilocode:default", + credential: { + type: "api_key", + provider: "kilocode", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index 167cde6809d..dc4ac9043d3 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -204,3 +204,26 @@ export function buildXaiModelDefinition(): ModelDefinitionConfig { maxTokens: XAI_DEFAULT_MAX_TOKENS, }; } + +// Kilo Gateway model definitions +export const KILOCODE_DEFAULT_MODEL_ID = "anthropic/claude-opus-4.6"; +export const KILOCODE_DEFAULT_CONTEXT_WINDOW = 200000; +export const KILOCODE_DEFAULT_MAX_TOKENS = 8192; +export const KILOCODE_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + +export function buildKilocodeModelDefinition(): ModelDefinitionConfig { + return { + id: KILOCODE_DEFAULT_MODEL_ID, + name: "Claude Opus 4.6", + reasoning: true, + input: ["text", "image"], + cost: KILOCODE_DEFAULT_COST, + contextWindow: KILOCODE_DEFAULT_CONTEXT_WINDOW, + maxTokens: KILOCODE_DEFAULT_MAX_TOKENS, + }; +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 16ec9477852..de506df0bb5 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -9,6 +9,8 @@ export { applyCloudflareAiGatewayProviderConfig, applyHuggingfaceConfig, applyHuggingfaceProviderConfig, + applyKilocodeConfig, + applyKilocodeProviderConfig, applyQianfanConfig, applyQianfanProviderConfig, applyKimiCodeConfig, @@ -37,6 +39,7 @@ export { applyXiaomiProviderConfig, applyZaiConfig, applyZaiProviderConfig, + KILOCODE_BASE_URL, } from "./onboard-auth.config-core.js"; export { applyMinimaxApiConfig, @@ -55,12 +58,14 @@ export { } from "./onboard-auth.config-opencode.js"; export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, + KILOCODE_DEFAULT_MODEL_REF, LITELLM_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, setAnthropicApiKey, setCloudflareAiGatewayConfig, setQianfanApiKey, setGeminiApiKey, + setKilocodeApiKey, setLitellmApiKey, setKimiCodingApiKey, setMinimaxApiKey, @@ -86,12 +91,14 @@ export { XAI_DEFAULT_MODEL_REF, } from "./onboard-auth.credentials.js"; export { + buildKilocodeModelDefinition, buildMinimaxApiModelDefinition, buildMinimaxModelDefinition, buildMistralModelDefinition, buildMoonshotModelDefinition, buildZaiModelDefinition, DEFAULT_MINIMAX_BASE_URL, + KILOCODE_DEFAULT_MODEL_ID, MOONSHOT_CN_BASE_URL, QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID, diff --git a/src/commands/onboard-non-interactive/local/auth-choice-inference.ts b/src/commands/onboard-non-interactive/local/auth-choice-inference.ts index 1043d227d3b..aecab3ba489 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice-inference.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice-inference.ts @@ -14,6 +14,7 @@ type AuthChoiceFlagOptions = Pick< | "openaiApiKey" | "mistralApiKey" | "openrouterApiKey" + | "kilocodeApiKey" | "aiGatewayApiKey" | "cloudflareAiGatewayApiKey" | "moonshotApiKey" diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 09b4870185c..9f9ce49a581 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -12,6 +12,7 @@ import { applyPrimaryModel } from "../../model-picker.js"; import { applyAuthProfileConfig, applyCloudflareAiGatewayConfig, + applyKilocodeConfig, applyQianfanConfig, applyKimiCodeConfig, applyMinimaxApiConfig, @@ -35,6 +36,7 @@ import { setCloudflareAiGatewayConfig, setQianfanApiKey, setGeminiApiKey, + setKilocodeApiKey, setKimiCodingApiKey, setLitellmApiKey, setMistralApiKey, @@ -441,6 +443,29 @@ export async function applyNonInteractiveAuthChoice(params: { return applyOpenrouterConfig(nextConfig); } + if (authChoice === "kilocode-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "kilocode", + cfg: baseConfig, + flagValue: opts.kilocodeApiKey, + flagName: "--kilocode-api-key", + envVar: "KILOCODE_API_KEY", + runtime, + }); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setKilocodeApiKey(resolved.key); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "kilocode:default", + provider: "kilocode", + mode: "api_key", + }); + return applyKilocodeConfig(nextConfig); + } + if (authChoice === "litellm-api-key") { const resolved = await resolveNonInteractiveApiKey({ provider: "litellm", diff --git a/src/commands/onboard-provider-auth-flags.ts b/src/commands/onboard-provider-auth-flags.ts index a9560e7f1ff..a1038625a78 100644 --- a/src/commands/onboard-provider-auth-flags.ts +++ b/src/commands/onboard-provider-auth-flags.ts @@ -6,6 +6,7 @@ type OnboardProviderAuthOptionKey = keyof Pick< | "openaiApiKey" | "mistralApiKey" | "openrouterApiKey" + | "kilocodeApiKey" | "aiGatewayApiKey" | "cloudflareAiGatewayApiKey" | "moonshotApiKey" @@ -64,6 +65,13 @@ export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray cliOption: "--openrouter-api-key ", description: "OpenRouter API key", }, + { + optionKey: "kilocodeApiKey", + authChoice: "kilocode-api-key", + cliFlag: "--kilocode-api-key", + cliOption: "--kilocode-api-key ", + description: "Kilo Gateway API key", + }, { optionKey: "aiGatewayApiKey", authChoice: "ai-gateway-api-key", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index bb3bdb471d8..fa655752b1f 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -13,6 +13,7 @@ export type AuthChoice = | "openai-codex" | "openai-api-key" | "openrouter-api-key" + | "kilocode-api-key" | "litellm-api-key" | "ai-gateway-api-key" | "cloudflare-ai-gateway-api-key" @@ -58,6 +59,7 @@ export type AuthChoiceGroupId = | "google" | "copilot" | "openrouter" + | "kilocode" | "litellm" | "ai-gateway" | "cloudflare-ai-gateway" @@ -108,6 +110,7 @@ export type OnboardOptions = { openaiApiKey?: string; mistralApiKey?: string; openrouterApiKey?: string; + kilocodeApiKey?: string; litellmApiKey?: string; aiGatewayApiKey?: string; cloudflareAiGatewayAccountId?: string; diff --git a/src/config/io.ts b/src/config/io.ts index 574b52ee293..bff292048fb 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -62,6 +62,7 @@ const SHELL_ENV_EXPECTED_KEYS = [ "AI_GATEWAY_API_KEY", "MINIMAX_API_KEY", "SYNTHETIC_API_KEY", + "KILOCODE_API_KEY", "ELEVENLABS_API_KEY", "TELEGRAM_BOT_TOKEN", "DISCORD_BOT_TOKEN",