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
13 changes: 12 additions & 1 deletion internal/driver/openclaw/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func GenerateConfig(rc *driver.ResolvedClaw) ([]byte, error) {
return nil, fmt.Errorf("config generation: cllama provider %q apiKey: %w", provider, err)
}
}
if err := setPath(config, basePath+".api", defaultModelAPIForProvider(provider)); err != nil {
if err := setPath(config, basePath+".api", cllamaModelAPIForProvider(provider)); err != nil {
return nil, fmt.Errorf("config generation: cllama provider %q api: %w", provider, err)
}
modelDefs := make([]interface{}, 0, len(modelIDs))
Expand Down Expand Up @@ -459,6 +459,17 @@ func defaultModelAPIForProvider(provider string) string {
}
}

func cllamaModelAPIForProvider(provider string) string {
switch normalizeProviderID(provider) {
case "anthropic", "synthetic", "minimax-portal", "kimi-coding", "cloudflare-ai-gateway", "xiaomi":
return "anthropic-messages"
default:
// cllama exposes OpenAI-compatible routing for non-Anthropic providers,
// even when the upstream vendor has a native API surface.
return "openai-completions"
}
}

// setPath sets a nested value in a map using a dotted path.
func setPath(obj map[string]interface{}, path string, value interface{}) error {
return shared.SetPath(obj, path, value)
Expand Down
67 changes: 67 additions & 0 deletions internal/driver/openclaw/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func TestGenerateConfigCllamaRewritesProviderBaseURL(t *testing.T) {
if anthropic["baseUrl"] != "http://cllama:8080/v1" {
t.Errorf("expected proxy baseUrl, got %v", anthropic["baseUrl"])
}
if anthropic["api"] != "anthropic-messages" {
t.Fatalf("expected anthropic provider behind cllama to use anthropic-messages, got %v", anthropic["api"])
}
modelEntries, ok := anthropic["models"].([]interface{})
if !ok || len(modelEntries) == 0 {
t.Fatalf("expected models.providers.anthropic.models entries, got %T %v", anthropic["models"], anthropic["models"])
Expand All @@ -96,6 +99,70 @@ func TestGenerateConfigCllamaRewritesProviderBaseURL(t *testing.T) {
}
}

func TestGenerateConfigCllamaGoogleUsesOpenAICompletions(t *testing.T) {
rc := &driver.ResolvedClaw{
Models: map[string]string{"primary": "google/gemini-3-flash-preview"},
Cllama: []string{"passthrough"},
CllamaToken: "weston:abc123hex",
}
data, err := GenerateConfig(rc)
if err != nil {
t.Fatal(err)
}
var config map[string]interface{}
if err := json.Unmarshal(data, &config); err != nil {
t.Fatal(err)
}
modelsCfg, ok := config["models"].(map[string]interface{})
if !ok {
t.Fatal("expected models config")
}
providers, ok := modelsCfg["providers"].(map[string]interface{})
if !ok {
t.Fatal("expected models.providers config")
}
google, ok := providers["google"].(map[string]interface{})
if !ok {
t.Fatal("expected models.providers.google config")
}
if google["baseUrl"] != "http://cllama:8080/v1" {
t.Fatalf("expected proxy baseUrl, got %v", google["baseUrl"])
}
if google["apiKey"] != "weston:abc123hex" {
t.Fatalf("expected cllama bearer token, got %v", google["apiKey"])
}
if google["api"] != "openai-completions" {
t.Fatalf("expected google provider behind cllama to use openai-completions, got %v", google["api"])
}
modelEntries, ok := google["models"].([]interface{})
if !ok || len(modelEntries) != 1 {
t.Fatalf("expected one google model entry, got %T %v", google["models"], google["models"])
}
entry, ok := modelEntries[0].(map[string]interface{})
if !ok {
t.Fatalf("expected google model entry object, got %T", modelEntries[0])
}
if entry["id"] != "google/gemini-3-flash-preview" {
t.Fatalf("expected google model id to stay provider-prefixed for cllama, got %v", entry["id"])
}
}

func TestGenerateConfigDirectGoogleKeepsNativeAPI(t *testing.T) {
rc := &driver.ResolvedClaw{
Models: map[string]string{"primary": "google/gemini-3-flash-preview"},
}
data, err := GenerateConfig(rc)
if err != nil {
t.Fatal(err)
}
if got, ok := getPath(data, "models.providers.google.api"); ok {
t.Fatalf("expected no models.providers.google config without cllama, got %v", got)
}
if got, ok := getPath(data, "agents.defaults.model.primary"); !ok || got != "google/gemini-3-flash-preview" {
t.Fatalf("expected direct google model to remain on agents.defaults.model.primary, got %v (present=%v)", got, ok)
}
}

func TestGenerateConfigNoCllamaNoProviderRewrite(t *testing.T) {
rc := &driver.ResolvedClaw{
Models: map[string]string{"primary": "anthropic/claude-sonnet-4"},
Expand Down