diff --git a/internal/agent/loop_history.go b/internal/agent/loop_history.go index 2cace9ff..dc411b78 100644 --- a/internal/agent/loop_history.go +++ b/internal/agent/loop_history.go @@ -252,7 +252,7 @@ func (l *Loop) buildMessages(ctx context.Context, history []providers.Message, s SandboxWorkspaceAccess: l.sandboxWorkspaceAccess, ShellDenyGroups: l.shellDenyGroups, SelfEvolve: l.selfEvolve, - ProviderType: l.provider.Name(), + ProviderType: providerTypeOf(l.provider), CredentialCLIContext: l.buildCredentialCLIContext(ctx), IsBootstrap: hadBootstrap && l.agentType != store.AgentTypePredefined, }) diff --git a/internal/agent/memoryflush.go b/internal/agent/memoryflush.go index ed84c666..28197087 100644 --- a/internal/agent/memoryflush.go +++ b/internal/agent/memoryflush.go @@ -108,7 +108,7 @@ func (l *Loop) runMemoryFlush(ctx context.Context, sessionKey string, settings * Mode: PromptMinimal, ToolNames: l.filteredToolNames(), HasMemory: l.hasMemory, - ProviderType: l.provider.Name(), + ProviderType: providerTypeOf(l.provider), }) systemPrompt += "\n\n" + settings.SystemPrompt diff --git a/internal/agent/systemprompt.go b/internal/agent/systemprompt.go index ec130dc9..7da0676e 100644 --- a/internal/agent/systemprompt.go +++ b/internal/agent/systemprompt.go @@ -6,10 +6,25 @@ import ( "strings" "github.com/nextlevelbuilder/goclaw/internal/bootstrap" + "github.com/nextlevelbuilder/goclaw/internal/providers" "github.com/nextlevelbuilder/goclaw/internal/store" "github.com/nextlevelbuilder/goclaw/internal/tools" ) +// providerTypeOf extracts the DB provider_type (e.g. "chatgpt_oauth", "codex") +// from a Provider. Falls back to Name() if the provider doesn't expose ProviderType(). +func providerTypeOf(p providers.Provider) string { + type providerTyper interface { + ProviderType() string + } + if pt, ok := p.(providerTyper); ok { + if t := pt.ProviderType(); t != "" { + return t + } + } + return p.Name() +} + // PromptMode controls which system prompt sections are included. // Matches TS PromptMode type in system-prompt.ts. type PromptMode string diff --git a/internal/agent/systemprompt_sections.go b/internal/agent/systemprompt_sections.go index 1576fc97..77f837d7 100644 --- a/internal/agent/systemprompt_sections.go +++ b/internal/agent/systemprompt_sections.go @@ -374,10 +374,17 @@ func buildPersonaReminder(files []bootstrap.ContextFile, agentType, providerType // needsSOULEcho returns true for providers that benefit from recency-zone personality echo. // GPT models have strong recency bias and tend to lose persona in long prompts. +// Matches first-party OpenAI (chatgpt_oauth) and Codex only — not compat proxies. func needsSOULEcho(providerType string) bool { - switch strings.ToLower(providerType) { - case "openai", "codex": + lower := strings.ToLower(providerType) + if strings.Contains(lower, "compat") { + return false // openai_compat routes to non-OpenAI models + } + switch { + case lower == "openai" || lower == "codex": return true + case strings.Contains(lower, "chatgpt"): + return true // chatgpt_oauth, chatgpt_plus, etc. } return false } diff --git a/internal/providers/schema_profile.go b/internal/providers/schema_profile.go index 7944638e..1c156dc7 100644 --- a/internal/providers/schema_profile.go +++ b/internal/providers/schema_profile.go @@ -79,11 +79,21 @@ func profileForProvider(name string) SchemaProfile { } // isOpenAIStrict returns true for providers known to support strict tool mode. -// Only first-party OpenAI and Codex are safe — third-party proxies (OpenRouter, -// DeepSeek, Groq, DashScope, etc.) may reject nullable unions or the strict flag. +// Matches first-party OpenAI (including chatgpt_oauth) and Codex. +// Explicitly excludes openai_compat (proxy for OpenRouter, DeepSeek, Groq, etc.). func isOpenAIStrict(name string) bool { lower := strings.ToLower(name) - return lower == "openai" || lower == "codex" + // Exclude compat/proxy providers first — they route to non-OpenAI models. + if strings.Contains(lower, "compat") { + return false + } + switch { + case lower == "openai" || lower == "codex": + return true + case strings.Contains(lower, "chatgpt"): + return true // chatgpt_oauth, chatgpt_plus, etc. + } + return false } // isGeminiName matches config names ("gemini", "gemini-flash") and