mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix: remove unnecessary lock mechanism and upgrade Claude 3 to Claude 4
- Remove sync.RWMutex and rrCounters from Config struct - Simplify GetModelConfig to use global atomic counter for load balancing - Remove unnecessary locks from HasProvidersConfig, SaveConfig, etc. - Add buildModelWithProtocol helper to handle models with existing prefix - Fix TestCreateProviderReturnsHTTPProviderForOpenRouter to use model_list - Upgrade all Claude 3 references to Claude 4 across documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -838,8 +838,8 @@ Cette conception permet également le **support multi-agent** avec une sélectio
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -774,8 +774,8 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -222,8 +222,8 @@ picoclaw onboard
|
||||
"api_key": "your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude3",
|
||||
"model": "anthropic/claude-3-sonnet",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"api_key": "your-anthropic-key"
|
||||
}
|
||||
],
|
||||
@@ -733,8 +733,8 @@ This design also enables **multi-agent support** with flexible provider selectio
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -839,8 +839,8 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -816,8 +816,8 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
+4
-4
@@ -231,8 +231,8 @@ picoclaw onboard
|
||||
"api_key": "your-api-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude3",
|
||||
"model": "anthropic/claude-3-sonnet",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"api_key": "your-anthropic-key"
|
||||
}
|
||||
],
|
||||
@@ -610,8 +610,8 @@ Agent 读取 HEARTBEAT.md
|
||||
"api_key": "sk-your-openai-key"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "claude3",
|
||||
"model": "anthropic/claude-3-sonnet",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_base": "https://api.anthropic.com/v1"
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ Problem: Agent needs to know both `provider` and `model`, adding complexity.
|
||||
Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design:
|
||||
|
||||
1. **Model-centric**: Users care about models, not providers
|
||||
2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.2`, `anthropic/claude-3-sonnet`
|
||||
2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.2`, `anthropic/claude-sonnet-4`
|
||||
3. **Configuration-driven**: Adding new Providers only requires config changes, no code changes
|
||||
|
||||
### 2.2 New Configuration Structure
|
||||
@@ -86,8 +86,8 @@ Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design:
|
||||
"api_key": "sk-xxx"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-3-sonnet",
|
||||
"model": "anthropic/claude-3-5-sonnet-20241022",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"api_key": "sk-xxx"
|
||||
},
|
||||
{
|
||||
@@ -184,7 +184,7 @@ Identify protocol via prefix in `model` field:
|
||||
"system_prompt": "You are a coding assistant..."
|
||||
},
|
||||
"translator": {
|
||||
"model": "claude-3-sonnet"
|
||||
"model": "claude-sonnet-4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ The new `model_list` configuration offers several advantages:
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "claude3",
|
||||
"model": "anthropic/claude-3-sonnet",
|
||||
"model_name": "claude-sonnet-4",
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"api_key": "sk-ant-your-key"
|
||||
},
|
||||
{
|
||||
@@ -83,12 +83,12 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
||||
| Prefix | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `openai/` | OpenAI API (default) | `openai/gpt-5.2` |
|
||||
| `anthropic/` | Anthropic API | `anthropic/claude-3-opus` |
|
||||
| `anthropic/` | Anthropic API | `anthropic/claude-opus-4` |
|
||||
| `antigravity/` | Google via Antigravity OAuth | `antigravity/gemini-2.0-flash` |
|
||||
| `claude-cli/` | Claude CLI (local) | `claude-cli/claude-3-sonnet` |
|
||||
| `claude-cli/` | Claude CLI (local) | `claude-cli/claude-sonnet-4` |
|
||||
| `codex-cli/` | Codex CLI (local) | `codex-cli/codex-4` |
|
||||
| `github-copilot/` | GitHub Copilot | `github-copilot/gpt-4o` |
|
||||
| `openrouter/` | OpenRouter | `openrouter/anthropic/claude-3` |
|
||||
| `openrouter/` | OpenRouter | `openrouter/anthropic/claude-sonnet-4` |
|
||||
| `groq/` | Groq API | `groq/llama-3.1-70b` |
|
||||
| `deepseek/` | DeepSeek API | `deepseek/deepseek-chat` |
|
||||
| `cerebras/` | Cerebras API | `cerebras/llama-3.3-70b` |
|
||||
|
||||
+19
-63
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/caarlos0/env/v11"
|
||||
)
|
||||
|
||||
// rrCounter is a global counter for round-robin load balancing across models.
|
||||
var rrCounter atomic.Uint64
|
||||
|
||||
// FlexibleStringSlice is a []string that also accepts JSON numbers,
|
||||
// so allow_from can contain both "123" and 123.
|
||||
type FlexibleStringSlice []string
|
||||
@@ -45,18 +47,16 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Agents AgentsConfig `json:"agents"`
|
||||
Bindings []AgentBinding `json:"bindings,omitempty"`
|
||||
Session SessionConfig `json:"session,omitempty"`
|
||||
Channels ChannelsConfig `json:"channels"`
|
||||
Providers ProvidersConfig `json:"providers,omitempty"`
|
||||
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
||||
Gateway GatewayConfig `json:"gateway"`
|
||||
Tools ToolsConfig `json:"tools"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
Devices DevicesConfig `json:"devices"`
|
||||
mu sync.RWMutex
|
||||
rrCounters map[string]*atomic.Uint64 // Round-robin counters for load balancing
|
||||
Agents AgentsConfig `json:"agents"`
|
||||
Bindings []AgentBinding `json:"bindings,omitempty"`
|
||||
Session SessionConfig `json:"session,omitempty"`
|
||||
Channels ChannelsConfig `json:"channels"`
|
||||
Providers ProvidersConfig `json:"providers,omitempty"`
|
||||
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
||||
Gateway GatewayConfig `json:"gateway"`
|
||||
Tools ToolsConfig `json:"tools"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
Devices DevicesConfig `json:"devices"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements custom JSON marshaling for Config
|
||||
@@ -350,7 +350,7 @@ type OpenAIProviderConfig struct {
|
||||
type ModelConfig struct {
|
||||
// Required fields
|
||||
ModelName string `json:"model_name"` // User-facing alias for the model
|
||||
Model string `json:"model"` // Protocol/model-identifier (e.g., "openai/gpt-4o", "anthropic/claude-3")
|
||||
Model string `json:"model"` // Protocol/model-identifier (e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4")
|
||||
|
||||
// HTTP-based providers
|
||||
APIBase string `json:"api_base,omitempty"` // API endpoint URL
|
||||
@@ -454,9 +454,6 @@ func LoadConfig(path string) (*Config, error) {
|
||||
}
|
||||
|
||||
func SaveConfig(path string, cfg *Config) error {
|
||||
cfg.mu.RLock()
|
||||
defer cfg.mu.RUnlock()
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -471,14 +468,10 @@ func SaveConfig(path string, cfg *Config) error {
|
||||
}
|
||||
|
||||
func (c *Config) WorkspacePath() string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return expandHome(c.Agents.Defaults.Workspace)
|
||||
}
|
||||
|
||||
func (c *Config) GetAPIKey() string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.Providers.OpenRouter.APIKey != "" {
|
||||
return c.Providers.OpenRouter.APIKey
|
||||
}
|
||||
@@ -510,8 +503,6 @@ func (c *Config) GetAPIKey() string {
|
||||
}
|
||||
|
||||
func (c *Config) GetAPIBase() string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if c.Providers.OpenRouter.APIKey != "" {
|
||||
if c.Providers.OpenRouter.APIBase != "" {
|
||||
return c.Providers.OpenRouter.APIBase
|
||||
@@ -544,54 +535,22 @@ func expandHome(path string) string {
|
||||
// GetModelConfig returns the ModelConfig for the given model name.
|
||||
// If multiple configs exist with the same model_name, it uses round-robin
|
||||
// selection for load balancing. Returns an error if the model is not found.
|
||||
// Uses double-check locking for optimal read performance.
|
||||
func (c *Config) GetModelConfig(modelName string) (*ModelConfig, error) {
|
||||
// First pass: use read lock to find matches
|
||||
c.mu.RLock()
|
||||
matches := c.findMatchesLocked(modelName)
|
||||
matches := c.findMatches(modelName)
|
||||
if len(matches) == 0 {
|
||||
c.mu.RUnlock()
|
||||
return nil, fmt.Errorf("model %q not found in model_list or providers", modelName)
|
||||
}
|
||||
if len(matches) == 1 {
|
||||
c.mu.RUnlock()
|
||||
return &matches[0], nil
|
||||
}
|
||||
|
||||
// Multiple configs - check if counter exists
|
||||
counter, ok := c.rrCounters[modelName]
|
||||
c.mu.RUnlock()
|
||||
|
||||
// Double-check locking: only acquire write lock if counter needs initialization
|
||||
if !ok {
|
||||
c.mu.Lock()
|
||||
// Re-check after acquiring write lock
|
||||
if c.rrCounters == nil {
|
||||
c.rrCounters = make(map[string]*atomic.Uint64)
|
||||
}
|
||||
if c.rrCounters[modelName] == nil {
|
||||
c.rrCounters[modelName] = &atomic.Uint64{}
|
||||
}
|
||||
counter = c.rrCounters[modelName]
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Re-fetch matches to ensure consistency (ModelList could have changed)
|
||||
c.mu.RLock()
|
||||
matches = c.findMatchesLocked(modelName)
|
||||
c.mu.RUnlock()
|
||||
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("model %q not found in model_list or providers", modelName)
|
||||
}
|
||||
|
||||
idx := counter.Add(1) % uint64(len(matches))
|
||||
// Multiple configs - use round-robin for load balancing
|
||||
idx := rrCounter.Add(1) % uint64(len(matches))
|
||||
return &matches[idx], nil
|
||||
}
|
||||
|
||||
// findMatchesLocked finds all ModelConfig entries with the given model_name.
|
||||
// Must be called with c.mu locked (read or write).
|
||||
func (c *Config) findMatchesLocked(modelName string) []ModelConfig {
|
||||
// findMatches finds all ModelConfig entries with the given model_name.
|
||||
func (c *Config) findMatches(modelName string) []ModelConfig {
|
||||
var matches []ModelConfig
|
||||
for i := range c.ModelList {
|
||||
if c.ModelList[i].ModelName == modelName {
|
||||
@@ -603,9 +562,6 @@ func (c *Config) findMatchesLocked(modelName string) []ModelConfig {
|
||||
|
||||
// HasProvidersConfig checks if any provider in the old providers config has configuration.
|
||||
func (c *Config) HasProvidersConfig() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
v := c.Providers
|
||||
return v.Anthropic.APIKey != "" || v.Anthropic.APIBase != "" ||
|
||||
v.OpenAI.APIKey != "" || v.OpenAI.APIBase != "" ||
|
||||
|
||||
@@ -160,6 +160,12 @@ func DefaultConfig() *Config {
|
||||
},
|
||||
|
||||
// OpenRouter (100+ models) - https://openrouter.ai/keys
|
||||
{
|
||||
ModelName: "openrouter-auto",
|
||||
Model: "openrouter/auto",
|
||||
APIBase: "https://openrouter.ai/api/v1",
|
||||
APIKey: "",
|
||||
},
|
||||
{
|
||||
ModelName: "openrouter-gpt-5.2",
|
||||
Model: "openrouter/openai/gpt-5.2",
|
||||
|
||||
+14
-3
@@ -10,6 +10,17 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildModelWithProtocol constructs a model string with protocol prefix.
|
||||
// If the model already contains a "/" (indicating it has a protocol prefix), it is returned as-is.
|
||||
// Otherwise, the protocol prefix is added.
|
||||
func buildModelWithProtocol(protocol, model string) string {
|
||||
if strings.Contains(model, "/") {
|
||||
// Model already has a protocol prefix, return as-is
|
||||
return model
|
||||
}
|
||||
return protocol + "/" + model
|
||||
}
|
||||
|
||||
// providerMigrationConfig defines how to migrate a provider from old config to new format.
|
||||
type providerMigrationConfig struct {
|
||||
// providerNames are the possible names used in agents.defaults.provider
|
||||
@@ -67,7 +78,7 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||
}
|
||||
return ModelConfig{
|
||||
ModelName: "anthropic",
|
||||
Model: "anthropic/claude-3-sonnet",
|
||||
Model: "anthropic/claude-sonnet-4",
|
||||
APIKey: p.Anthropic.APIKey,
|
||||
APIBase: p.Anthropic.APIBase,
|
||||
Proxy: p.Anthropic.Proxy,
|
||||
@@ -325,13 +336,13 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
|
||||
// Check if this is the user's configured provider
|
||||
if slices.Contains(m.providerNames, userProvider) && userModel != "" {
|
||||
// Use the user's configured model instead of default
|
||||
mc.Model = m.protocol + "/" + userModel
|
||||
mc.Model = buildModelWithProtocol(m.protocol, userModel)
|
||||
} else if userProvider == "" && userModel != "" && !legacyModelNameApplied {
|
||||
// Legacy config: no explicit provider field but model is specified
|
||||
// Use userModel as ModelName for the FIRST provider so GetModelConfig(model) can find it
|
||||
// This maintains backward compatibility with old configs that relied on implicit provider selection
|
||||
mc.ModelName = userModel
|
||||
mc.Model = m.protocol + "/" + userModel
|
||||
mc.Model = buildModelWithProtocol(m.protocol, userModel)
|
||||
legacyModelNameApplied = true
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ func TestConvertProvidersToModelList_Anthropic(t *testing.T) {
|
||||
if result[0].ModelName != "anthropic" {
|
||||
t.Errorf("ModelName = %q, want %q", result[0].ModelName, "anthropic")
|
||||
}
|
||||
if result[0].Model != "anthropic/claude-3-sonnet" {
|
||||
t.Errorf("Model = %q, want %q", result[0].Model, "anthropic/claude-3-sonnet")
|
||||
if result[0].Model != "anthropic/claude-sonnet-4" {
|
||||
t.Errorf("Model = %q, want %q", result[0].Model, "anthropic/claude-sonnet-4")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestConvertProvidersToModelList_PreservesUserModel_Anthropic(t *testing.T)
|
||||
Agents: AgentsConfig{
|
||||
Defaults: AgentDefaults{
|
||||
Provider: "claude", // alternative name
|
||||
Model: "claude-3-opus-20240229",
|
||||
Model: "claude-opus-4-20250514",
|
||||
},
|
||||
},
|
||||
Providers: ProvidersConfig{
|
||||
@@ -253,8 +253,8 @@ func TestConvertProvidersToModelList_PreservesUserModel_Anthropic(t *testing.T)
|
||||
t.Fatalf("len(result) = %d, want 1", len(result))
|
||||
}
|
||||
|
||||
if result[0].Model != "anthropic/claude-3-opus-20240229" {
|
||||
t.Errorf("Model = %q, want %q", result[0].Model, "anthropic/claude-3-opus-20240229")
|
||||
if result[0].Model != "anthropic/claude-opus-4-20250514" {
|
||||
t.Errorf("Model = %q, want %q", result[0].Model, "anthropic/claude-opus-4-20250514")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,3 +495,57 @@ func TestConvertProvidersToModelList_NoProviderField_NoModel(t *testing.T) {
|
||||
t.Errorf("ModelName = %q, want %q", result[0].ModelName, "zhipu")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for buildModelWithProtocol helper function
|
||||
|
||||
func TestBuildModelWithProtocol_NoPrefix(t *testing.T) {
|
||||
result := buildModelWithProtocol("openai", "gpt-5.2")
|
||||
if result != "openai/gpt-5.2" {
|
||||
t.Errorf("buildModelWithProtocol(openai, gpt-5.2) = %q, want %q", result, "openai/gpt-5.2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildModelWithProtocol_AlreadyHasPrefix(t *testing.T) {
|
||||
result := buildModelWithProtocol("openrouter", "openrouter/auto")
|
||||
if result != "openrouter/auto" {
|
||||
t.Errorf("buildModelWithProtocol(openrouter, openrouter/auto) = %q, want %q", result, "openrouter/auto")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildModelWithProtocol_DifferentPrefix(t *testing.T) {
|
||||
result := buildModelWithProtocol("anthropic", "openrouter/claude-sonnet-4")
|
||||
if result != "openrouter/claude-sonnet-4" {
|
||||
t.Errorf("buildModelWithProtocol(anthropic, openrouter/claude-sonnet-4) = %q, want %q", result, "openrouter/claude-sonnet-4")
|
||||
}
|
||||
}
|
||||
|
||||
// Test for legacy config with protocol prefix in model name
|
||||
func TestConvertProvidersToModelList_LegacyModelWithProtocolPrefix(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Agents: AgentsConfig{
|
||||
Defaults: AgentDefaults{
|
||||
Provider: "", // No explicit provider
|
||||
Model: "openrouter/auto", // Model already has protocol prefix
|
||||
},
|
||||
},
|
||||
Providers: ProvidersConfig{
|
||||
OpenRouter: ProviderConfig{APIKey: "sk-or-test"},
|
||||
},
|
||||
}
|
||||
|
||||
result := ConvertProvidersToModelList(cfg)
|
||||
|
||||
if len(result) < 1 {
|
||||
t.Fatalf("len(result) = %d, want at least 1", len(result))
|
||||
}
|
||||
|
||||
// First provider should use userModel as ModelName for backward compatibility
|
||||
if result[0].ModelName != "openrouter/auto" {
|
||||
t.Errorf("ModelName = %q, want %q", result[0].ModelName, "openrouter/auto")
|
||||
}
|
||||
|
||||
// Model should NOT have duplicated prefix
|
||||
if result[0].Model != "openrouter/auto" {
|
||||
t.Errorf("Model = %q, want %q (should not duplicate prefix)", result[0].Model, "openrouter/auto")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,15 @@ func TestResolveProviderSelection(t *testing.T) {
|
||||
|
||||
func TestCreateProviderReturnsHTTPProviderForOpenRouter(t *testing.T) {
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Agents.Defaults.Model = "openrouter/auto"
|
||||
cfg.Providers.OpenRouter.APIKey = "sk-or-test"
|
||||
cfg.Agents.Defaults.Model = "test-openrouter"
|
||||
cfg.ModelList = []config.ModelConfig{
|
||||
{
|
||||
ModelName: "test-openrouter",
|
||||
Model: "openrouter/auto",
|
||||
APIKey: "sk-or-test",
|
||||
APIBase: "https://openrouter.ai/api/v1",
|
||||
},
|
||||
}
|
||||
|
||||
provider, _, err := CreateProvider(cfg)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user