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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user