AI Providers¶
Keen Code supports multiple AI providers through a plugin-like architecture. The provider system handles model discovery, authentication, and communication with different LLM backends.
Supported Providers¶
| Provider | ID | Authentication | Models |
|---|---|---|---|
| Anthropic | anthropic |
API Key | Claude Opus 5, Fable 5, Sonnet 5, Opus 4.8, Haiku 4.5 |
| OpenAI | openai |
API Key | GPT-5.6 Sol, GPT-5.6 Luna, GPT-5.6 Terra, GPT-5.5, GPT-5.4 |
| Codex | openai-codex |
OAuth | GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna, GPT-5.5, GPT-5.4 |
| Google AI | googleai |
API Key | Gemini 3.1 Pro, 3.6 Flash, 3.5 Flash, 3.5 Flash-Lite |
| Moonshot AI | moonshotai |
API Key | Kimi K3, K2.7 Code, K2.7 Code High-Speed, K2.6, K2.5 |
| Z.ai | zai |
API Key | GLM-5.2, GLM-5.1 |
| DeepSeek | deepseek |
API Key | DeepSeek V4 Flash, V4 Pro |
| MiniMax | minimax |
API Key | MiniMax M3, M2.7, M2.7 High-Speed |
| Amazon Bedrock | amazon-bedrock |
AWS credentials | Claude Fable 5, Sonnet 5, Opus 4.8, Opus 4.6, Sonnet 4.6, Haiku 4.5 |
| OpenCode Go | opencode-go |
API Key | Grok 4.5, GPT-5.6 Luna, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo V2.5 Pro, MiMo V2.5, Qwen3.8 Max, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M3, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash, Hy3 |
| OpenAI Compatible | openai-compatible |
API Key | Any OpenAI-compatible model |
OpenAI-Compatible Providers¶
Keen supports arbitrary OpenAI-compatible endpoints through the openai-compatible provider. This is config-file only: set active_provider, active_model, and the provider entry in ~/.keen/configs.json, then restart Keen.
OpenRouter Example¶
{
"active_provider": "openai-compatible",
"active_model": "tencent/hy3:free",
"providers": {
"openai-compatible": {
"models": ["tencent/hy3:free"],
"api_key": "sk-or-v1-...",
"base_url": "https://openrouter.ai/api/v1",
"headers": {
"HTTP-Referer": "https://your-site.com",
"X-Title": "Keen Code"
}
}
}
}
Notes:
active_providermust be"openai-compatible".active_modelmust match one of the entries inproviders.openai-compatible.models.base_urlis required and must be the OpenAI-compatible chat completions endpoint base (usually ending in/v1).api_keyis sent as theAuthorization: Bearer ...token.- Custom headers in
headersare sent with every request.
Provider Registry¶
Provider and model metadata is stored in internal/providers/registry.yaml. This includes:
- Context window sizes
- Supported thinking efforts
- Model display names
// internal/providers/loader.go
type Registry struct {
Providers []Provider `yaml:"providers"`
}
type Provider struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Models []Model `yaml:"models"`
}
type Model struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
ContextWindow int `yaml:"context_window"`
ThinkingEfforts []string `yaml:"thinking_efforts"`
}
Configuration¶
Global Config (~/.keen/configs.json)¶
{
"active_provider": "opencode-go",
"active_model": "kimi-k2.6",
"thinking_effort": "enabled",
"show_thinking": true,
"adversary_provider": "anthropic",
"adversary_model": "claude-sonnet-5",
"providers": {
"opencode-go": {
"models": ["kimi-k2.6"],
"api_key": "oc_..."
}
}
}
adversary_provider and adversary_model are set via /adversary model and control which model is used for adversarial reviews. They are independent of active_provider/active_model and can point to any configured provider.
Custom Headers¶
You can attach custom HTTP headers to a provider by adding a headers object to that provider's config in ~/.keen/configs.json. These headers are sent with every request to that provider.
{
"active_provider": "deepseek",
"active_model": "deepseek-v4-pro",
"providers": {
"deepseek": {
"models": ["deepseek-v4-pro"],
"api_key": "sk-...",
"headers": {
"x_header_1": "val1",
"x_header_2": "val2"
}
}
}
}
Notes:
- Header names and values are plain strings.
- They are set per-provider; different providers can have different headers.
- Custom headers must be added by editing the config file directly. The
/modelUI does not provide a field for them. - Applied to all clients: Anthropic, OpenAI, Codex, DeepSeek, Moonshot AI, Z.ai, MiniMax, OpenCode Go, Google AI (Genkit), and Amazon Bedrock.
Config Resolution¶
Keen loads ~/.keen/configs.json through internal/config.Loader, then builds the runtime ResolvedConfig in internal/cli/cmd/root.go.
Resolution order for the default interactive and headless paths:
1. Provider: global.active_provider
2. Model: global.active_model
3. API key: providers.{provider}.api_key_helper → providers.{provider}.api_key
For keen run --provider, the selected provider's config replaces the active provider for that invocation. If --model is omitted, Keen uses the selected provider's first configured model. The API key is still resolved through api_key_helper first, then api_key.
Authentication¶
API Key Authentication¶
Most providers use API key authentication. Keys are stored in the global config under providers.{provider}.api_key.
Instead of storing a key, a provider can define api_key_helper. Keen executes the helper locally when resolving the provider config, trims stdout, and uses that value as the in-memory API key for the current run/session. When api_key_helper is set, it always wins over api_key; api_key can be empty and Keen does not write the helper output back to ~/.keen/configs.json.
{
"active_provider": "anthropic",
"active_model": "claude-sonnet-5",
"providers": {
"anthropic": {
"models": ["claude-sonnet-5"],
"api_key": "",
"api_key_helper": "example-auth token || (example-auth login > /dev/null 2>&1 && example-auth token)"
}
}
}
Security note:
api_key_helperis executed as a shell command with the privileges of the running process. Treat it as executable code: never paste untrusted strings into this field, audit any helper script before use, and keep~/.keenpermissions strict (e.g.chmod 700 ~/.keenandchmod 600 ~/.keen/configs.json) so other local users cannot inject or read its contents.
MiniMax uses its Anthropic-compatible API. Users normally leave base_url unset. Keen uses https://api.minimax.io/anthropic, which the Anthropic SDK extends to /v1/messages.
OpenCode Go also uses API key authentication. Users normally leave base_url unset. Keen follows OpenCode's model-specific endpoints: GPT-5.6 Luna uses /v1/responses, MiniMax and Qwen use /v1/messages, and the other curated models use /v1/chat/completions.
OAuth Authentication (OpenAI Codex)¶
OpenAI Codex uses OAuth with PKCE flow:
// internal/auth/oauth.go
type OAuthManager struct {
Store *Store
HTTPClient *http.Client
OpenBrowser BrowserOpener
}
Flow: 1. Generate PKCE verifier/challenge and state 2. Start local HTTP server on port 1455 3. Open browser to authorization URL 4. Receive callback, exchange code for tokens 5. Store refresh/access tokens
Token refresh is automatic when the access token expires.
AWS Authentication (Amazon Bedrock)¶
Amazon Bedrock uses AWS credential authentication via the AWS SDK:
// internal/config/config.go
const AuthModeAWS = "aws"
Credentials are loaded from the standard AWS credential chain (~/.aws/credentials, environment variables, IAM roles, etc.). No API key is stored in Keen's global config. An optional custom base_url can be configured to override the Bedrock endpoint.
The default AWS region is us-east-1 if none is configured in the environment.
LLM Client Architecture¶
// internal/llm/client.go
type LLMClient interface {
StreamChat(ctx context.Context, messages []Message, toolRegistry *tools.Registry, opts ...StreamOptions) (<-chan StreamEvent, error)
Reset()
}
Three client implementations:
AnthropicClient (internal/llm/anthropic.go)¶
Direct integration with Anthropic SDK:
- Streaming via ssestream.Stream
- Tool conversion to Anthropic tool format
- Adaptive thinking and model-specific effort support
- Cached token tracking
- MiniMax models through MiniMax's Anthropic-compatible /messages endpoint
- OpenCode Go MiniMax and Qwen models through the Anthropic-compatible /messages endpoint
BedrockClient (internal/llm/bedrock.go)¶
AWS SDK integration for Amazon Bedrock:
- Streaming via bedrockruntime.ConverseStream
- Tool conversion to Bedrock tool format
- Reasoning content support (thinking text, signatures, redacted content)
- Prompt caching with cache points on system prompts, tools, and messages
- Cached token tracking
- Adaptive thinking through additionalModelRequestFields
OpenAIResponsesClient (internal/llm/openai_responses.go)¶
OpenAI Responses API for: - OpenAI (GPT models) - OpenCode Go GPT-5.6 Luna
OpenAICompatibleClient (internal/llm/openai.go)¶
OpenAI-compatible API for: - DeepSeek - Moonshot AI (Kimi) - Z.ai (GLM) - OpenCode Go Grok, GLM, Kimi, DeepSeek, MiMo, and Hy3 models
OpenCode Go Qwen and MiniMax models use the Anthropic-compatible client.
Handles provider-specific features like the reasoning_content extension and thinking controls for compatible providers.
GenkitClient (internal/llm/genkit.go)¶
Firebase Genkit integration for Google AI (Gemini). Currently the only provider using Genkit.
Stream Events¶
All clients emit a unified stream of events:
type StreamEvent struct {
Type StreamEventType
Content string // for Chunk, ReasoningChunk
ToolCall *ToolCall // for ToolStart, ToolEnd
Usage *TokenUsage // for Usage
Error error // for Error, Retry
Attempt int // for Retry
}
Event types:
- StreamEventTypeChunk - Text content delta
- StreamEventTypeReasoningChunk - Thinking/reasoning content
- StreamEventTypeToolStart - Tool execution begins
- StreamEventTypeToolEnd - Tool execution completes
- StreamEventTypeUsage - Token usage stats
- StreamEventTypeDone - Response complete
- StreamEventTypeError - Unrecoverable error
- StreamEventTypeRetry - Retrying after error
- StreamEventTypeIncomplete - Turn limit reached with pending state
Thinking Efforts¶
Models expose their provider-specific thinking values without normalizing them to a common scale:
| Provider | Efforts |
|---|---|
| Anthropic | low, medium, high, xhigh, max |
| OpenAI | none, low, medium, high, xhigh, max |
| Google AI | low, medium, high, minimal |
| Moonshot AI | K3: low, high, max; K2.6: enabled, disabled |
| DeepSeek | disabled, high, max |
| Amazon Bedrock | low, medium, high, xhigh, max |
| Z.ai | GLM-5.2: disabled, high, max; GLM-5.1: enabled, disabled |
| MiniMax | M3: enabled, adaptive, disabled |
| OpenCode Go | Model-specific; see internal/providers/registry.yaml |
The selected effort is stored in thinking_effort and passed to the provider without changing its meaning.
OpenCode Go thinking controls are model-family specific:
- GPT-5.6 Luna uses the Responses API. Kimi K3 and Hy3 send their documented reasoning_effort values.
- DeepSeek sends thinking.type plus reasoning_effort.
- Qwen uses the Anthropic-compatible endpoint with an enabled/disabled thinking toggle.
- MiniMax M3 sends thinking.type as enabled, adaptive, or disabled.
- Models without a registry thinking_efforts entry do not receive a Keen-sent thinking control; returned reasoning is still streamed when the provider exposes it.
GPT-5.4 in Codex OAuth and Kimi K2.5 are retained for existing users and are scheduled for removal after their August 31, 2026 retirements.