Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ When starting any new work (feature, fix, refactor, chore), always create a feat

## Design Principles

- **Model-agnostic** — single interface for 23 registered LLM providers
- **Plan-accurate provider metadata** — Concentrate AI is pay-as-you-go, uses
`concentrate-payg` as its deployment identifier, and uses its native Responses
API (`/v1/responses`) without legacy Chat Completions or Messages routing
- **Model-agnostic** — single interface for 75+ LLM providers
- **Host-neutral engine** — Eyrie owns provider routing, transport, caching,
retry/fallback, and normalized telemetry; hosts own product UX and semantics
- **Streaming-first** — all responses are streamed; blocking is opt-in
Expand Down
8 changes: 4 additions & 4 deletions catalog/live_enrich.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func FetchLiveProviderCatalog(env map[string]string) (Catalog, []LiveProviderEnr
ID: deploymentID,
Name: providerID,
ProviderID: providerID,
APIProtocolID: spec.ProtocolID,
AdapterConstructor: spec.AdapterID,
APIProtocolID: "openai-chat-completions",
AdapterConstructor: "openai",
NativeModelIDSource: NativeModelIDDiscovered,
ModelMappingsRequired: false,
}
Expand All @@ -83,7 +83,7 @@ func FetchLiveProviderCatalog(env map[string]string) (Catalog, []LiveProviderEnr
} else if hasInputPricing(entry.RawJSON) {
canonicalID = providerID + "/" + entryID
}
} else {
} else if hasInputPricing(entry.RawJSON) {
canonicalID = providerID + "/" + entryID
}

Expand Down Expand Up @@ -123,7 +123,7 @@ func FetchLiveModelEntriesForProvider(env map[string]string, providerID string)
return nil, fmt.Errorf("catalog: provider %q has no live model list API", providerID)
}
env = registry.ScopedProviderEnv(spec, env)
if !spec.PublicModelCatalog && !registry.CredentialPresent(spec, env) {
if !registry.CredentialPresent(spec, env) {
return nil, fmt.Errorf("catalog: set %s for %s", spec.CredentialEnv, providerID)
}
entries, err := live.Fetch(spec.LiveFetcherKey, env)
Expand Down
44 changes: 22 additions & 22 deletions catalog/registry/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func providerSpecs() []ProviderSpec {
return []ProviderSpec{
// ── Direct API providers ──────────────────────────────────────────
{
ProviderID: "anthropic", DisplayName: "Anthropic", DeploymentID: "anthropic-direct", SortOrder: 3, ChatPreference: 2,
ProviderID: "anthropic", DisplayName: "Anthropic", DeploymentID: "anthropic-direct", SortOrder: 1, ChatPreference: 2,
TransportKind: "anthropic",
RequiresKey: true, CredentialEnv: "ANTHROPIC_API_KEY",
CredentialAliases: []string{"CLAUDE_API_KEY"},
Expand All @@ -28,7 +28,7 @@ func providerSpecs() []ProviderSpec {
DirectFallbacks: []string{"openai"},
},
{
ProviderID: "openai", DisplayName: "OpenAI", DeploymentID: "openai-direct", SortOrder: 15, ChatPreference: 1,
ProviderID: "openai", DisplayName: "OpenAI", DeploymentID: "openai-direct", SortOrder: 2, ChatPreference: 1,
TransportKind: "openai",
RequiresKey: true, CredentialEnv: "OPENAI_API_KEY",
BaseURLEnv: []string{"OPENAI_BASE_URL", "OPENAI_API_BASE"},
Expand All @@ -38,7 +38,7 @@ func providerSpecs() []ProviderSpec {
DirectFallbacks: []string{"anthropic"},
},
{
ProviderID: "gemini", DisplayName: "Gemini API", DeploymentID: "gemini-direct", SortOrder: 9, ChatPreference: 5,
ProviderID: "gemini", DisplayName: "Gemini API", DeploymentID: "gemini-direct", SortOrder: 3, ChatPreference: 5,
RuntimeBaseURL: "https://generativelanguage.googleapis.com/v1beta/openai",
RequiresKey: true, CredentialEnv: "GEMINI_API_KEY",
CredentialAliases: []string{"GOOGLE_API_KEY"},
Expand All @@ -48,31 +48,31 @@ func providerSpecs() []ProviderSpec {
ProtocolID: "gemini-generate-content", AdapterID: "gemini", RuntimeProfileKey: "gemini",
},
{
ProviderID: "deepseek", DisplayName: "DeepSeek", DeploymentID: "deepseek-direct", SortOrder: 8, ChatPreference: 11,
ProviderID: "deepseek", DisplayName: "DeepSeek", DeploymentID: "deepseek-direct", SortOrder: 4, ChatPreference: 11,
RequiresKey: true, CredentialEnv: "DEEPSEEK_API_KEY",
BaseURLEnv: []string{"DEEPSEEK_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.deepseek.com/v1",
LiveFetcherKey: "deepseek", LiveCatalogKey: "deepseek",
ProtocolID: "openai-chat-completions", AdapterID: "deepseek", RuntimeProfileKey: "deepseek",
},
{
ProviderID: "grok", DisplayName: "xAI", DeploymentID: "grok-direct", SortOrder: 21, ChatPreference: 4,
ProviderID: "grok", DisplayName: "xAI", DeploymentID: "grok-direct", SortOrder: 5, ChatPreference: 4,
RequiresKey: true, CredentialEnv: "XAI_API_KEY",
BaseURLEnv: []string{"XAI_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.x.ai/v1",
LiveFetcherKey: "grok", LiveCatalogKey: "grok",
ProtocolID: "openai-chat-completions", AdapterID: "grok", RuntimeProfileKey: "grok",
},
{
ProviderID: "kimi", DisplayName: "Kimi", DeploymentID: "kimi-direct", SortOrder: 11, ChatPreference: 14,
ProviderID: "kimi", DisplayName: "Kimi", DeploymentID: "kimi-direct", SortOrder: 6, ChatPreference: 14,
RequiresKey: true, CredentialEnv: "MOONSHOT_API_KEY",
BaseURLEnv: []string{"MOONSHOT_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.moonshot.ai/v1",
LiveFetcherKey: "kimi", LiveCatalogKey: "kimi",
ProtocolID: "openai-chat-completions", AdapterID: "kimi", RuntimeProfileKey: "kimi",
},
{
ProviderID: "zai_coding", DisplayName: "Z.AI — Coding Plan", DeploymentID: "zai_coding-direct", SortOrder: 24, ChatPreference: 8,
ProviderID: "zai_coding", DisplayName: "Z.AI — Coding Plan", DeploymentID: "zai_coding-direct", SortOrder: 7, ChatPreference: 8,
RequiresKey: true, CredentialEnv: "ZAI_CODING_API_KEY",
BaseURLEnv: []string{"ZAI_CODING_BASE_URL", "ZAI_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.z.ai/api/coding/paas/v4",
Expand All @@ -81,7 +81,7 @@ func providerSpecs() []ProviderSpec {
PrepareCredentialEnv: true,
},
{
ProviderID: "zai_payg", DisplayName: "Z.AI — Pay-as-you-go", DeploymentID: "zai_payg-direct", SortOrder: 25, ChatPreference: 9,
ProviderID: "zai_payg", DisplayName: "Z.AI — Pay-as-you-go", DeploymentID: "zai_payg-direct", SortOrder: 8, ChatPreference: 9,
RequiresKey: true, CredentialEnv: "ZAI_API_KEY",
BaseURLEnv: []string{"ZAI_BASE_URL", "ZAI_API_BASE"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.z.ai/api/paas/v4",
Expand All @@ -90,7 +90,7 @@ func providerSpecs() []ProviderSpec {
PrepareCredentialEnv: true,
},
{
ProviderID: "xiaomi_mimo_token_plan", DisplayName: "Xiaomi MiMo — Token Plan", DeploymentID: "xiaomi_mimo_token_plan-direct", SortOrder: 23, ChatPreference: 16,
ProviderID: "xiaomi_mimo_token_plan", DisplayName: "Xiaomi MiMo — Token Plan", DeploymentID: "xiaomi_mimo_token_plan-direct", SortOrder: 9, ChatPreference: 16,
RequiresKey: true, CredentialEnv: "XIAOMI_MIMO_TOKEN_PLAN_API_KEY",
BaseURLEnv: []string{"XIAOMI_MIMO_TOKEN_PLAN_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "",
Expand All @@ -99,7 +99,7 @@ func providerSpecs() []ProviderSpec {
PrepareCredentialEnv: true,
},
{
ProviderID: "xiaomi_mimo_payg", DisplayName: "Xiaomi MiMo — Pay-as-you-go", DeploymentID: "xiaomi_mimo_payg-direct", SortOrder: 22, ChatPreference: 15,
ProviderID: "xiaomi_mimo_payg", DisplayName: "Xiaomi MiMo — Pay-as-you-go", DeploymentID: "xiaomi_mimo_payg-direct", SortOrder: 10, ChatPreference: 15,
RequiresKey: true, CredentialEnv: "XIAOMI_MIMO_PAYG_API_KEY",
CredentialAliases: []string{"XIAOMI_MIMO_API_KEY"},
BaseURLEnv: []string{"XIAOMI_MIMO_PAYG_BASE_URL", "XIAOMI_BASE_URL"},
Expand All @@ -108,15 +108,15 @@ func providerSpecs() []ProviderSpec {
ProtocolID: "openai-chat-completions", AdapterID: "xiaomi_mimo", RuntimeProfileKey: "xiaomi_mimo_payg",
},
{
ProviderID: "minimax_token_plan", DisplayName: "MiniMax — Token Plan", DeploymentID: "minimax_token_plan-direct", SortOrder: 14, ChatPreference: 17,
ProviderID: "minimax_token_plan", DisplayName: "MiniMax — Token Plan", DeploymentID: "minimax_token_plan-direct", SortOrder: 11, ChatPreference: 17,
RequiresKey: true, CredentialEnv: "MINIMAX_TOKEN_PLAN_API_KEY",
BaseURLEnv: []string{"MINIMAX_TOKEN_PLAN_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.minimax.io/v1",
LiveFetcherKey: "minimax_token_plan", LiveCatalogKey: "minimax_token_plan",
ProtocolID: "openai-chat-completions", AdapterID: "openai", RuntimeProfileKey: "minimax_token_plan",
},
{
ProviderID: "minimax_payg", DisplayName: "MiniMax — Pay-as-you-go", DeploymentID: "minimax_payg-direct", SortOrder: 13, ChatPreference: 18,
ProviderID: "minimax_payg", DisplayName: "MiniMax — Pay-as-you-go", DeploymentID: "minimax_payg-direct", SortOrder: 12, ChatPreference: 18,
RequiresKey: true, CredentialEnv: "MINIMAX_PAYG_API_KEY",
BaseURLEnv: []string{"MINIMAX_PAYG_BASE_URL", "MINIMAX_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.minimax.io/v1",
Expand All @@ -126,7 +126,7 @@ func providerSpecs() []ProviderSpec {

// ── Cloud platform providers ──────────────────────────────────────
{
ProviderID: "azure", DisplayName: "Azure OpenAI", DeploymentID: "openai-azure", SortOrder: 4, ChatPreference: 12,
ProviderID: "azure", DisplayName: "Azure OpenAI", DeploymentID: "openai-azure", SortOrder: 13, ChatPreference: 12,
TransportKind: "azure",
RequiresKey: true, CredentialEnv: "AZURE_OPENAI_API_KEY",
BaseURLEnv: []string{"AZURE_OPENAI_ENDPOINT"},
Expand All @@ -135,7 +135,7 @@ func providerSpecs() []ProviderSpec {
ProtocolID: "openai-chat-completions", AdapterID: "openai-azure", RuntimeProfileKey: "azure",
},
{
ProviderID: "bedrock", DisplayName: "Amazon Bedrock", DeploymentID: "anthropic-bedrock", SortOrder: 2, ChatPreference: 7,
ProviderID: "bedrock", DisplayName: "Amazon Bedrock", DeploymentID: "anthropic-bedrock", SortOrder: 14, ChatPreference: 7,
TransportKind: "bedrock",
RequiresKey: true, CredentialEnv: "AWS_SECRET_ACCESS_KEY",
CredentialEnvFallbacks: []string{"AWS_ACCESS_KEY_ID", "AWS_SESSION_TOKEN"},
Expand All @@ -145,7 +145,7 @@ func providerSpecs() []ProviderSpec {
ProtocolID: "anthropic-messages", AdapterID: "anthropic-bedrock", RuntimeProfileKey: "bedrock",
},
{
ProviderID: "vertex", DisplayName: "Vertex AI", DeploymentID: "gemini-vertex", SortOrder: 20, ChatPreference: 6,
ProviderID: "vertex", DisplayName: "Vertex AI", DeploymentID: "gemini-vertex", SortOrder: 15, ChatPreference: 6,
TransportKind: "vertex",
RequiresKey: true, CredentialEnv: "VERTEX_ACCESS_TOKEN",
CredentialEnvFallbacks: []string{"GOOGLE_OAUTH_ACCESS_TOKEN"},
Expand All @@ -157,7 +157,7 @@ func providerSpecs() []ProviderSpec {

// ── Aggregators ───────────────────────────────────────────────────
{
ProviderID: "openrouter", DisplayName: "OpenRouter", DeploymentID: "openrouter", SortOrder: 17, ChatPreference: 3,
ProviderID: "openrouter", DisplayName: "OpenRouter", DeploymentID: "openrouter", SortOrder: 16, ChatPreference: 3,
RequiresKey: true, CredentialEnv: "OPENROUTER_API_KEY",
BaseURLEnv: []string{"OPENROUTER_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://openrouter.ai/api/v1",
Expand Down Expand Up @@ -200,31 +200,31 @@ func providerSpecs() []ProviderSpec {

// ── Niche ─────────────────────────────────────────────────────────
{
ProviderID: "canopywave", DisplayName: "CanopyWave", DeploymentID: "canopywave", SortOrder: 5, ChatPreference: 10,
ProviderID: "canopywave", DisplayName: "CanopyWave", DeploymentID: "canopywave", SortOrder: 17, ChatPreference: 10,
RequiresKey: true, CredentialEnv: "CANOPYWAVE_API_KEY",
BaseURLEnv: []string{"CANOPYWAVE_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://inference.canopywave.io/v1",
LiveFetcherKey: "canopywave", LiveCatalogKey: "canopywave",
ProtocolID: "openai-chat-completions", AdapterID: "canopywave", RuntimeProfileKey: "canopywave",
},
{
ProviderID: "poolside", DisplayName: "Poolside", DeploymentID: "poolside", SortOrder: 19, ChatPreference: 20,
ProviderID: "poolside", DisplayName: "Poolside", DeploymentID: "poolside", SortOrder: 18, ChatPreference: 20,
RequiresKey: true, CredentialEnv: "POOLSIDE_API_KEY",
BaseURLEnv: []string{"POOLSIDE_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://inference.poolside.ai/v1",
LiveFetcherKey: "poolside", LiveCatalogKey: "poolside",
ProtocolID: "openai-chat-completions", AdapterID: "poolside", RuntimeProfileKey: "poolside",
},
{
ProviderID: "groq", DisplayName: "Groq", DeploymentID: "groq-direct", SortOrder: 10, ChatPreference: 21,
ProviderID: "groq", DisplayName: "Groq", DeploymentID: "groq-direct", SortOrder: 19, ChatPreference: 21,
RequiresKey: true, CredentialEnv: "GROQ_API_KEY",
BaseURLEnv: []string{"GROQ_BASE_URL"},
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.groq.com/openai/v1",
LiveFetcherKey: "groq", LiveCatalogKey: "groq",
ProtocolID: "openai-chat-completions", AdapterID: "groq", RuntimeProfileKey: "groq",
},
{
ProviderID: "clinepass", DisplayName: "ClinePass", DeploymentID: "clinepass", SortOrder: 6, ChatPreference: 22,
ProviderID: "clinepass", DisplayName: "ClinePass", DeploymentID: "clinepass", SortOrder: 20, ChatPreference: 22,
RuntimeBaseURL: "https://api.cline.bot/api/v1",
RequiresKey: true, CredentialEnv: "CLINE_API_KEY",
BaseURLEnv: []string{"CLINE_API_BASE"},
Expand All @@ -233,7 +233,7 @@ func providerSpecs() []ProviderSpec {
ProtocolID: "openai-chat-completions", AdapterID: "clinepass", RuntimeProfileKey: "clinepass",
},
{
ProviderID: "opencodego", DisplayName: "OpenCode Go", DeploymentID: "opencodego", SortOrder: 16, ChatPreference: 13,
ProviderID: "opencodego", DisplayName: "OpenCode Go", DeploymentID: "opencodego", SortOrder: 21, ChatPreference: 13,
RequiresKey: true, CredentialEnv: "OPENCODEGO_API_KEY",
BaseURLEnv: []string{"OPENCODEGO_BASE_URL"},
ProbeKind: ProbeOpenAIModels,
Expand All @@ -244,7 +244,7 @@ func providerSpecs() []ProviderSpec {

// ── Local ─────────────────────────────────────────────────────────
{
ProviderID: "ollama", DisplayName: "Ollama", DeploymentID: "ollama-local", SortOrder: 18, ChatPreference: 19,
ProviderID: "ollama", DisplayName: "Ollama", DeploymentID: "ollama-local", SortOrder: 22, ChatPreference: 19,
RuntimeBaseURL: "http://localhost:11434/v1", RuntimeCredentialEnv: "OLLAMA_API_KEY",
RequiresKey: false, CredentialEnv: "OLLAMA_BASE_URL",
BaseURLEnv: []string{"OLLAMA_BASE_URL"},
Expand Down
26 changes: 0 additions & 26 deletions catalog/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,32 +238,6 @@ func TestCapabilitySetFromEntry_ToolsAliasSupportsFunctionCalling(t *testing.T)
}
}

func TestCapabilitySetFromEntry_KeepsContextAndMaxOutput(t *testing.T) {
t.Parallel()
set := CapabilitySetFromEntry(live.Entry{
ContextWindow: 1_048_576,
MaxOutput: 131_072,
Features: []string{"tools", "thinking:enabled", "image_input"},
ThinkingEnabled: true,
ImageInput: true,
})
if set.MaxInputTokens != 1_048_576 {
t.Fatalf("MaxInputTokens = %d", set.MaxInputTokens)
}
if set.MaxOutputTokens != 131_072 {
t.Fatalf("MaxOutputTokens = %d", set.MaxOutputTokens)
}
if set.FunctionCalling != CapabilitySupported {
t.Fatalf("FunctionCalling = %q", set.FunctionCalling)
}
if set.ExplicitThinkingBudget != CapabilitySupported {
t.Fatalf("ExplicitThinkingBudget = %q", set.ExplicitThinkingBudget)
}
if set.ImageInput != CapabilitySupported {
t.Fatalf("ImageInput = %q", set.ImageInput)
}
}

func TestCapabilitySetFromLegacy_EmptyFeatures(t *testing.T) {
t.Parallel()
entry := ModelCatalogEntry{ID: "test-model"}
Expand Down
16 changes: 0 additions & 16 deletions client/adapters/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,6 @@ func thinkingDisabled() *anthropicThinking {
}

// resolveThinking builds the thinking config from core.ChatOptions.
// Explicit ThinkingMode / ThinkingBudgetTokens win. Otherwise ThinkingEnabled
// maps to Anthropic's documented toggle:
// - false → {type:"disabled"}
// - true → {type:"adaptive"} (recommended for current Claude models)
//
// See https://platform.claude.com/docs/en/build-with-claude/extended-thinking
func resolveThinking(opts core.ChatOptions) *anthropicThinking {
switch opts.ThinkingMode {
case "adaptive":
Expand All @@ -178,16 +172,6 @@ func resolveThinking(opts core.ChatOptions) *anthropicThinking {
}
return thinking
default:
thinkingEnabled := opts.ThinkingEnabled
if thinkingEnabled == nil {
thinkingEnabled = opts.GLMThinkingEnabled
}
if thinkingEnabled != nil {
if !*thinkingEnabled {
return thinkingDisabled()
}
return thinkingAdaptive()
}
// Legacy behavior: if budget > 0, enable with budget
return thinkingForBudget(opts.ThinkingBudgetTokens)
}
Expand Down
Loading
Loading