feat: add Kilo Code (kilocode) as first-class inference provider (#1666)

Add Kilo Gateway (kilo.ai) as an API-key provider with OpenAI-compatible
endpoint at https://api.kilo.ai/api/gateway. Supports 500+ models from
Anthropic, OpenAI, Google, xAI, Mistral, MiniMax via a single API key.

- Register kilocode in PROVIDER_REGISTRY with aliases (kilo, kilo-code,
  kilo-gateway) and KILOCODE_API_KEY / KILOCODE_BASE_URL env vars
- Add to model catalog, CLI provider menu, setup wizard, doctor checks
- Add google/gemini-3-flash-preview as default aux model
- 12 new tests covering registration, aliases, credential resolution,
  runtime config
- Documentation updates (env vars, config, fallback providers)
- Fix setup test index shift from provider insertion

Inspired by PR #1473 by @amanning3390.

Co-authored-by: amanning3390 <amanning3390@users.noreply.github.com>
This commit is contained in:
Teknium
2026-03-17 02:40:34 -07:00
committed by GitHub
parent 6c6d12033f
commit 35d948b6e1
11 changed files with 131 additions and 14 deletions

View File

@@ -174,11 +174,19 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = {
"opencode-go": ProviderConfig(
id="opencode-go",
name="OpenCode Go",
auth_type="api_key",
auth_type="***",
inference_base_url="https://opencode.ai/zen/go/v1",
api_key_env_vars=("OPENCODE_GO_API_KEY",),
api_key_env_vars=("OPEN...",),
base_url_env_var="OPENCODE_GO_BASE_URL",
),
"kilocode": ProviderConfig(
id="kilocode",
name="Kilo Code",
auth_type="api_key",
inference_base_url="https://api.kilo.ai/api/gateway",
api_key_env_vars=("KILOCODE_API_KEY",),
base_url_env_var="KILOCODE_BASE_URL",
),
}
@@ -559,6 +567,7 @@ def resolve_provider(
"aigateway": "ai-gateway", "vercel": "ai-gateway", "vercel-ai-gateway": "ai-gateway",
"opencode": "opencode-zen", "zen": "opencode-zen",
"go": "opencode-go", "opencode-go-sub": "opencode-go",
"kilo": "kilocode", "kilo-code": "kilocode", "kilo-gateway": "kilocode",
}
normalized = _PROVIDER_ALIASES.get(normalized, normalized)

View File

@@ -46,6 +46,7 @@ _PROVIDER_ENV_HINTS = (
"KIMI_API_KEY",
"MINIMAX_API_KEY",
"MINIMAX_CN_API_KEY",
"KILOCODE_API_KEY",
)
@@ -571,6 +572,7 @@ def run_doctor(args):
("MiniMax", ("MINIMAX_API_KEY",), None, "MINIMAX_BASE_URL", False),
("MiniMax (China)", ("MINIMAX_CN_API_KEY",), None, "MINIMAX_CN_BASE_URL", False),
("AI Gateway", ("AI_GATEWAY_API_KEY",), "https://ai-gateway.vercel.sh/v1/models", "AI_GATEWAY_BASE_URL", True),
("Kilo Code", ("KILOCODE_API_KEY",), "https://api.kilo.ai/api/gateway/models", "KILOCODE_BASE_URL", True),
]
for _pname, _env_vars, _default_url, _base_env, _supports_health_check in _apikey_providers:
_key = ""

View File

@@ -783,6 +783,7 @@ def cmd_model(args):
"opencode-zen": "OpenCode Zen",
"opencode-go": "OpenCode Go",
"ai-gateway": "AI Gateway",
"kilocode": "Kilo Code",
"custom": "Custom endpoint",
}
active_label = provider_labels.get(active, active)
@@ -802,6 +803,7 @@ def cmd_model(args):
("kimi-coding", "Kimi / Moonshot (Moonshot AI direct API)"),
("minimax", "MiniMax (global direct API)"),
("minimax-cn", "MiniMax China (domestic direct API)"),
("kilocode", "Kilo Code (Kilo Gateway API)"),
("opencode-zen", "OpenCode Zen (35+ curated models, pay-as-you-go)"),
("opencode-go", "OpenCode Go (open models, $10/month subscription)"),
("ai-gateway", "AI Gateway (Vercel — 200+ models, pay-per-use)"),
@@ -873,7 +875,7 @@ def cmd_model(args):
_model_flow_anthropic(config, current_model)
elif selected_provider == "kimi-coding":
_model_flow_kimi(config, current_model)
elif selected_provider in ("zai", "minimax", "minimax-cn", "opencode-zen", "opencode-go", "ai-gateway"):
elif selected_provider in ("zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway"):
_model_flow_api_key_provider(config, selected_provider, current_model)
@@ -1433,6 +1435,13 @@ _PROVIDER_MODELS = {
"MiniMax-M2.5-highspeed",
"MiniMax-M2.1",
],
"kilocode": [
"anthropic/claude-opus-4.6",
"anthropic/claude-sonnet-4.6",
"openai/gpt-5.4",
"google/gemini-3-pro-preview",
"google/gemini-3-flash-preview",
],
}
@@ -2609,7 +2618,7 @@ For more help on a command:
)
chat_parser.add_argument(
"--provider",
choices=["auto", "openrouter", "nous", "openai-codex", "anthropic", "zai", "kimi-coding", "minimax", "minimax-cn"],
choices=["auto", "openrouter", "nous", "openai-codex", "anthropic", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode"],
default=None,
help="Inference provider (default: auto)"
)

View File

@@ -139,6 +139,13 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
"google/gemini-2.5-flash",
"deepseek/deepseek-v3.2",
],
"kilocode": [
"anthropic/claude-opus-4.6",
"anthropic/claude-sonnet-4.6",
"openai/gpt-5.4",
"google/gemini-3-pro-preview",
"google/gemini-3-flash-preview",
],
}
_PROVIDER_LABELS = {
@@ -154,6 +161,7 @@ _PROVIDER_LABELS = {
"opencode-zen": "OpenCode Zen",
"opencode-go": "OpenCode Go",
"ai-gateway": "AI Gateway",
"kilocode": "Kilo Code",
"custom": "Custom endpoint",
}
@@ -176,6 +184,9 @@ _PROVIDER_ALIASES = {
"aigateway": "ai-gateway",
"vercel": "ai-gateway",
"vercel-ai-gateway": "ai-gateway",
"kilo": "kilocode",
"kilo-code": "kilocode",
"kilo-gateway": "kilocode",
}
@@ -209,7 +220,7 @@ def list_available_providers() -> list[dict[str, str]]:
# Canonical providers in display order
_PROVIDER_ORDER = [
"openrouter", "nous", "openai-codex",
"zai", "kimi-coding", "minimax", "minimax-cn", "anthropic",
"zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic",
"opencode-zen", "opencode-go",
"ai-gateway", "deepseek", "custom",
]

View File

@@ -60,6 +60,7 @@ _DEFAULT_PROVIDER_MODELS = {
"minimax": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"],
"minimax-cn": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"],
"ai-gateway": ["anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", "openai/gpt-5", "google/gemini-3-flash"],
"kilocode": ["anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4", "google/gemini-3-pro-preview", "google/gemini-3-flash-preview"],
}
@@ -734,6 +735,7 @@ def setup_model_provider(config: dict):
"Kimi / Moonshot (Kimi coding models)",
"MiniMax (global endpoint)",
"MiniMax China (mainland China endpoint)",
"Kilo Code (Kilo Gateway API)",
"Anthropic (Claude models — API key or Claude Code subscription)",
"AI Gateway (Vercel — 200+ models, pay-per-use)",
"OpenCode Zen (35+ curated models, pay-as-you-go)",
@@ -1142,7 +1144,40 @@ def setup_model_provider(config: dict):
_set_model_provider(config, "minimax-cn", pconfig.inference_base_url)
selected_base_url = pconfig.inference_base_url
elif provider_idx == 8: # Anthropic
elif provider_idx == 8: # Kilo Code
selected_provider = "kilocode"
print()
print_header("Kilo Code API Key")
pconfig = PROVIDER_REGISTRY["kilocode"]
print_info(f"Provider: {pconfig.name}")
print_info(f"Base URL: {pconfig.inference_base_url}")
print_info("Get your API key at: https://kilo.ai")
print()
existing_key = get_env_value("KILOCODE_API_KEY")
if existing_key:
print_info(f"Current: {existing_key[:8]}... (configured)")
if prompt_yes_no("Update API key?", False):
api_key = prompt(" Kilo Code API key", password=True)
if api_key:
save_env_value("KILOCODE_API_KEY", api_key)
print_success("Kilo Code API key updated")
else:
api_key = prompt(" Kilo Code API key", password=True)
if api_key:
save_env_value("KILOCODE_API_KEY", api_key)
print_success("Kilo Code API key saved")
else:
print_warning("Skipped - agent won't work without an API key")
# Clear custom endpoint vars if switching
if existing_custom:
save_env_value("OPENAI_BASE_URL", "")
save_env_value("OPENAI_API_KEY", "")
_set_model_provider(config, "kilocode", pconfig.inference_base_url)
selected_base_url = pconfig.inference_base_url
elif provider_idx == 9: # Anthropic
selected_provider = "anthropic"
print()
print_header("Anthropic Authentication")
@@ -1246,7 +1281,7 @@ def setup_model_provider(config: dict):
_set_model_provider(config, "anthropic")
selected_base_url = ""
elif provider_idx == 9: # AI Gateway
elif provider_idx == 10: # AI Gateway
selected_provider = "ai-gateway"
print()
print_header("AI Gateway API Key")
@@ -1278,7 +1313,7 @@ def setup_model_provider(config: dict):
_update_config_for_provider("ai-gateway", pconfig.inference_base_url, default_model="anthropic/claude-opus-4.6")
_set_model_provider(config, "ai-gateway", pconfig.inference_base_url)
elif provider_idx == 10: # OpenCode Zen
elif provider_idx == 11: # OpenCode Zen
selected_provider = "opencode-zen"
print()
print_header("OpenCode Zen API Key")
@@ -1311,7 +1346,7 @@ def setup_model_provider(config: dict):
_set_model_provider(config, "opencode-zen", pconfig.inference_base_url)
selected_base_url = pconfig.inference_base_url
elif provider_idx == 11: # OpenCode Go
elif provider_idx == 12: # OpenCode Go
selected_provider = "opencode-go"
print()
print_header("OpenCode Go API Key")
@@ -1344,7 +1379,7 @@ def setup_model_provider(config: dict):
_set_model_provider(config, "opencode-go", pconfig.inference_base_url)
selected_base_url = pconfig.inference_base_url
# else: provider_idx == 12 (Keep current) — only shown when a provider already exists
# else: provider_idx == 13 (Keep current) — only shown when a provider already exists
# Normalize "keep current" to an explicit provider so downstream logic
# doesn't fall back to the generic OpenRouter/static-model path.
if selected_provider is None:
@@ -1515,7 +1550,7 @@ def setup_model_provider(config: dict):
_set_default_model(config, custom)
_update_config_for_provider("openai-codex", DEFAULT_CODEX_BASE_URL)
_set_model_provider(config, "openai-codex", DEFAULT_CODEX_BASE_URL)
elif selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn", "ai-gateway"):
elif selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "ai-gateway"):
_setup_provider_model_selection(
config, selected_provider, current_model,
prompt_choice, prompt,
@@ -1576,7 +1611,7 @@ def setup_model_provider(config: dict):
# Write provider+base_url to config.yaml only after model selection is complete.
# This prevents a race condition where the gateway picks up a new provider
# before the model name has been updated to match.
if selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn", "anthropic") and selected_base_url is not None:
if selected_provider in ("zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic") and selected_base_url is not None:
_update_config_for_provider(selected_provider, selected_base_url)
save_config(config)