feat(providers): default ChatGPT OAuth to GPT-5.5

Default ChatGPT Subscription (OAuth) provider model selection to GPT-5.5 and update model metadata, tests, and docs.
This commit is contained in:
Duy /zuey/ authored and GitHub committed 2026-05-18 20:33:40 +07:00
1 parent 134cc7119f
commit 52aa110e1f
21 files changed
+96 -48

No files matched your search

+6
View File
@@ -23,6 +23,12 @@ All notable changes to GoClaw are documented here. For full documentation, see [
with downgrade protection. WebSocket events `package.update.*` for owner
clients. See `docs/packages-github.md` § "Updating Installed Packages".
### Changed
- **ChatGPT Subscription (OAuth)** — default model and backend-owned model catalog
now prefer `gpt-5.5`, with reasoning metadata and context-window defaults updated
for provider-first model selection.
### Breaking Changes
- **Context pruning now opt-in.** Previously tool-result trimming ran by default
+1 -1
View File
@@ -45,7 +45,7 @@ func authStatusCmd() *cobra.Command {
name = provider
}
fmt.Printf("ChatGPT OAuth account: active (alias: %s)\n", name)
fmt.Printf("Use model prefix '%s/' in agent config (e.g. %s/gpt-5.4).\n", name, name)
fmt.Printf("Use model prefix '%s/' in agent config (e.g. %s/gpt-5.5).\n", name, name)
} else {
fmt.Printf("No ChatGPT OAuth tokens found for alias '%s'.\n", provider)
fmt.Println("Use the web UI to authenticate this ChatGPT OAuth account.")
+4 -4
View File
@@ -62,7 +62,7 @@ Streaming fallback is conservative: backup models are tried only if the stream f
|----------|------|----------|---------------|
| **anthropic** | Native HTTP + SSE | API key required | `claude-sonnet-4-5-20250929` |
| **claude_cli** | stdio subprocess + MCP | Binary path (default: `claude`) | `sonnet` |
| **codex** | OAuth Responses API | OAuth token source | `gpt-5.3-codex` |
| **codex** | OAuth Responses API | OAuth token source | `gpt-5.5` |
| **acp** | JSON-RPC 2.0 subagents | Binary + workspace dir | `claude` |
| **dashscope** | OpenAI-compat wrapper | API key + custom models | `qwen3-max` |
| **openai** (+ 10+ variants) | OpenAI-compatible | API key + endpoint URL | Model-specific |
@@ -571,7 +571,7 @@ Claude CLI inherits thinking support from the underlying Claude model. Thinking
## 12. Codex Provider
The Codex provider integrates with OpenAI's ChatGPT Responses API (OAuth-based), enabling access to gpt-5.3-codex model through the chatgpt.com backend. Unlike standard OpenAI endpoints, Codex uses OAuth token refresh and a custom response format with "phase" markers.
The Codex provider integrates with OpenAI's ChatGPT Responses API (OAuth-based), defaulting to `gpt-5.5` through the chatgpt.com backend. Unlike standard OpenAI endpoints, Codex uses OAuth token refresh and a custom response format with "phase" markers.
### Configuration
@@ -582,7 +582,7 @@ tokenSource := &MyTokenSource{} // implements TokenSource interface
provider := NewCodexProvider("codex", tokenSource, "", "")
// or specify custom API base and model:
provider := NewCodexProvider("codex", tokenSource,
"https://chatgpt.com/backend-api", "gpt-5.3-codex")
"https://chatgpt.com/backend-api", "gpt-5.5")
```
### API Endpoint
@@ -601,7 +601,7 @@ Codex returns structured responses with phase markers:
```json
{
"id": "...",
"model": "gpt-5.3-codex",
"model": "gpt-5.5",
"choices": [{
"message": {
"role": "assistant",
+5 -5
View File
@@ -148,7 +148,7 @@ can override or inherit routing behavior while keeping the main `provider` field
```json
{
"provider": "openai-codex",
"model": "gpt-5.4",
"model": "gpt-5.5",
"chatgpt_oauth_routing": {
"override_mode": "custom",
"strategy": "round_robin"
@@ -214,7 +214,7 @@ Agents can now store capability-aware GPT-5/Codex reasoning intent under top-lev
```json
{
"provider": "openai-codex",
"model": "gpt-5.4",
"model": "gpt-5.5",
"reasoning_config": {
"override_mode": "inherit"
}
@@ -364,11 +364,11 @@ Example response:
{
"models": [
{
"id": "gpt-5.4",
"name": "GPT-5.4",
"id": "gpt-5.5",
"name": "GPT-5.5",
"reasoning": {
"levels": ["none", "low", "medium", "high", "xhigh"],
"default_effort": "none"
"default_effort": "medium"
}
},
{
+12
View File
@@ -6,6 +6,18 @@ Significant changes, features, and fixes in reverse chronological order.
## 2026-05-18
### Providers: ChatGPT OAuth GPT-5.5 default
**Changed**
- Updated ChatGPT Subscription (OAuth) default model, provider model catalog, reasoning metadata, and docs examples to prefer `gpt-5.5`.
**Tests**
- Updated focused provider/model catalog, reasoning, registry, and token-window coverage for `gpt-5.5`.
---
### Deployment: Codex CLI service-user auth
**Fixes**
+3
View File
@@ -1,5 +1,7 @@
package http
import "github.com/nextlevelbuilder/goclaw/internal/providers"
// bailianModels returns a hardcoded list of models available on the
// Bailian Coding platform (coding-intl.dashscope.aliyuncs.com).
// The platform does not expose a /v1/models endpoint.
@@ -84,6 +86,7 @@ func acpModels() []ModelInfo {
// chatGPTOAuthModels returns models available via ChatGPT OAuth integration.
func chatGPTOAuthModels() []ModelInfo {
return withReasoningCapabilities([]ModelInfo{
{ID: providers.DefaultCodexModel, Name: "GPT-5.5"},
{ID: "gpt-5.4", Name: "GPT-5.4"},
{ID: "gpt-5.4-mini", Name: "GPT-5.4 Mini"},
{ID: "gpt-5.3-codex", Name: "GPT-5.3 Codex"},
+6 -6
View File
@@ -97,22 +97,22 @@ func TestProvidersHandlerListProviderModelsChatGPTOAuthIncludesReasoningMetadata
var found bool
for _, model := range result.Models {
if model.ID != "gpt-5.4" {
if model.ID != "gpt-5.5" {
continue
}
found = true
if model.Reasoning == nil {
t.Fatal("gpt-5.4 reasoning = nil, want capability metadata")
t.Fatal("gpt-5.5 reasoning = nil, want capability metadata")
}
if model.Reasoning.DefaultEffort != "none" {
t.Fatalf("gpt-5.4 default_effort = %q, want none", model.Reasoning.DefaultEffort)
if model.Reasoning.DefaultEffort != "medium" {
t.Fatalf("gpt-5.5 default_effort = %q, want medium", model.Reasoning.DefaultEffort)
}
if got := model.Reasoning.Levels; len(got) != 5 || got[4] != "xhigh" {
t.Fatalf("gpt-5.4 levels = %#v, want none..xhigh", got)
t.Fatalf("gpt-5.5 levels = %#v, want none..xhigh", got)
}
}
if !found {
t.Fatal("gpt-5.4 not found in ChatGPT OAuth model list")
t.Fatal("gpt-5.5 not found in ChatGPT OAuth model list")
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ func NewCodexAdapter(cfg ProviderConfig) (ProviderAdapter, error) {
model := cfg.Model
if model == "" {
model = "gpt-5.4"
model = DefaultCodexModel
}
var ts TokenSource
@@ -53,7 +53,7 @@ func (a *CodexAdapter) Capabilities() ProviderCapabilities {
Vision: true,
CacheControl: false,
ImageGeneration: true, // Codex (OpenAI Responses API) supports native image_generation tool
MaxContextWindow: 1_000_000,
MaxContextWindow: 1_050_000,
TokenizerID: "o200k_base",
}
}
+13 -2
View File
@@ -35,12 +35,23 @@ func TestCodexAdapter_Defaults(t *testing.T) {
if !caps.Thinking || !caps.Vision {
t.Errorf("expected Thinking+Vision, got %+v", caps)
}
if caps.MaxContextWindow != 1_000_000 {
t.Errorf("MaxContextWindow = %d, want 1_000_000", caps.MaxContextWindow)
if caps.MaxContextWindow != 1_050_000 {
t.Errorf("MaxContextWindow = %d, want 1_050_000", caps.MaxContextWindow)
}
if caps.TokenizerID != "o200k_base" {
t.Errorf("TokenizerID = %q, want o200k_base", caps.TokenizerID)
}
body, _, err := a.ToRequest(ChatRequest{Messages: []Message{{Role: "user", Content: "hi"}}})
if err != nil {
t.Fatalf("ToRequest error: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("decode body: %v", err)
}
if payload["model"] != DefaultCodexModel {
t.Errorf("default model = %v, want %s", payload["model"], DefaultCodexModel)
}
}
// TestCodexAdapter_BaseURLTrimmed verifies trailing slashes are stripped from
+5 -2
View File
@@ -15,6 +15,9 @@ type CodexRoutingDefaults struct {
ExtraProviderNames []string
}
// DefaultCodexModel is the default model for ChatGPT Subscription (OAuth).
const DefaultCodexModel = "gpt-5.5"
// CodexProvider implements Provider for the OpenAI Responses API,
// used with ChatGPT subscription via OAuth (Codex flow).
// Wire format: POST /codex/responses on chatgpt.com backend.
@@ -37,7 +40,7 @@ func NewCodexProvider(name string, tokenSource TokenSource, apiBase, defaultMode
apiBase = strings.TrimRight(apiBase, "/")
if defaultModel == "" {
defaultModel = "gpt-5.4"
defaultModel = DefaultCodexModel
}
return &CodexProvider{
@@ -78,7 +81,7 @@ func (p *CodexProvider) Capabilities() ProviderCapabilities {
Vision: true,
CacheControl: false,
ImageGeneration: true, // Codex (OpenAI Responses API) supports native image_generation tool
MaxContextWindow: 1_000_000,
MaxContextWindow: 1_050_000,
TokenizerID: "o200k_base",
}
}
+2 -2
View File
@@ -47,8 +47,8 @@ func TestCodexProviderName(t *testing.T) {
func TestCodexProviderDefaultModel(t *testing.T) {
p := NewCodexProvider("test", &staticTokenSource{token: "test"}, "", "")
if p.DefaultModel() != "gpt-5.4" {
t.Errorf("DefaultModel() = %q, want %q", p.DefaultModel(), "gpt-5.4")
if p.DefaultModel() != DefaultCodexModel {
t.Errorf("DefaultModel() = %q, want %q", p.DefaultModel(), DefaultCodexModel)
}
p2 := NewCodexProvider("test", &staticTokenSource{token: "test"}, "", "o3")
+3 -4
View File
@@ -11,11 +11,10 @@ var openAIForwardCompatMap = map[string]struct {
Patch *ModelSpec
}{
"gpt-5.5": {
Template: "gpt-5.4",
Patch: &ModelSpec{ContextWindow: 1_000_000, MaxTokens: 200_000},
Template: "gpt-5.5",
},
"gpt-5.6": {
Template: "gpt-5.4",
Template: "gpt-5.5",
Patch: &ModelSpec{ContextWindow: 2_000_000, MaxTokens: 200_000},
},
"o5-mini": {
@@ -24,7 +23,7 @@ var openAIForwardCompatMap = map[string]struct {
},
}
// ResolveForwardCompat handles models like "gpt-5.5" by cloning from "gpt-5.4".
// ResolveForwardCompat handles future model aliases by cloning from the latest known template.
func (r *OpenAIForwardCompat) ResolveForwardCompat(modelID string, registry ModelRegistry) *ModelSpec {
// Direct map lookup (exact match)
if entry, ok := openAIForwardCompatMap[modelID]; ok {
+1
View File
@@ -154,6 +154,7 @@ func SeedDefaultModels(r *InMemoryRegistry) {
// OpenAI models
for _, s := range []ModelSpec{
{ID: "gpt-5.5", Provider: "openai", ContextWindow: 1_050_000, MaxTokens: 128_000, Reasoning: true, Vision: true, TokenizerID: "o200k_base"},
{ID: "gpt-5.4", Provider: "openai", ContextWindow: 1_000_000, MaxTokens: 100_000, Reasoning: true, Vision: true, TokenizerID: "o200k_base"},
{ID: "gpt-5.2", Provider: "openai", ContextWindow: 256_000, MaxTokens: 64_000, Reasoning: true, Vision: true, TokenizerID: "o200k_base"},
{ID: "gpt-4o", Provider: "openai", ContextWindow: 128_000, MaxTokens: 16_384, Reasoning: false, Vision: true, TokenizerID: "o200k_base"},
+16 -14
View File
@@ -263,10 +263,10 @@ func TestCloneFromTemplatePatchBooleanFields(t *testing.T) {
registry := &InMemoryRegistry{}
template := ModelSpec{
ID: "base",
Provider: "openai",
ID: "base",
Provider: "openai",
Reasoning: false,
Vision: false,
Vision: false,
}
registry.Register(template)
@@ -371,7 +371,7 @@ func TestOpenAIForwardCompatResolveExactMatch(t *testing.T) {
resolver := &OpenAIForwardCompat{}
registry.RegisterResolver("openai", resolver)
// Try to resolve gpt-5.5 which should use gpt-5.4 as template with patch
// gpt-5.5 is a seeded model, so direct resolution should win.
resolved := registry.Resolve("openai", "gpt-5.5")
if resolved == nil {
@@ -380,12 +380,11 @@ func TestOpenAIForwardCompatResolveExactMatch(t *testing.T) {
if resolved.ID != "gpt-5.5" {
t.Errorf("expected ID=gpt-5.5, got %s", resolved.ID)
}
// Should have patched values from the map
if resolved.ContextWindow != 1_000_000 {
t.Errorf("expected ContextWindow=1000000 from patch, got %d", resolved.ContextWindow)
if resolved.ContextWindow != 1_050_000 {
t.Errorf("expected ContextWindow=1050000, got %d", resolved.ContextWindow)
}
if resolved.MaxTokens != 200_000 {
t.Errorf("expected MaxTokens=200000 from patch, got %d", resolved.MaxTokens)
if resolved.MaxTokens != 128_000 {
t.Errorf("expected MaxTokens=128000, got %d", resolved.MaxTokens)
}
}
@@ -403,9 +402,12 @@ func TestOpenAIForwardCompatResolvePrefixMatch(t *testing.T) {
if resolved.ID != "gpt-5.5-turbo" {
t.Errorf("expected ID=gpt-5.5-turbo, got %s", resolved.ID)
}
// Should use gpt-5.4 as template with gpt-5.5 patch
if resolved.MaxTokens != 200_000 {
t.Errorf("expected MaxTokens=200000 from patch, got %d", resolved.MaxTokens)
// Should use gpt-5.5 as the latest known template.
if resolved.ContextWindow != 1_050_000 {
t.Errorf("expected ContextWindow=1050000 from template, got %d", resolved.ContextWindow)
}
if resolved.MaxTokens != 128_000 {
t.Errorf("expected MaxTokens=128000 from template, got %d", resolved.MaxTokens)
}
}
@@ -462,8 +464,8 @@ func TestCloneFromTemplatePatchZeroValuesIgnored(t *testing.T) {
// Patch with zero values should be ignored
patch := &ModelSpec{
ContextWindow: 0, // Should be ignored
MaxTokens: 0, // Should be ignored
ContextWindow: 0, // Should be ignored
MaxTokens: 0, // Should be ignored
TokenizerID: "", // Should be ignored
Cost: ModelCost{
InputPer1M: 0, // Should be ignored
+1 -1
View File
@@ -41,7 +41,7 @@ func ValidateImageModel(model string) (string, error) {
// NativeImageRequest describes a single image generation request.
type NativeImageRequest struct {
// Model is the parent LLM model for the Responses API call (e.g. "gpt-5.4").
// Model is the parent LLM model for the Responses API call (e.g. "gpt-5.5").
// NOT the image model — see ImageModel below.
// If empty, the provider uses its own default LLM model.
Model string
+1 -1
View File
@@ -181,7 +181,7 @@ func (p *OpenAIProvider) buildRequestBody(model string, req ChatRequest, stream
if v, ok := req.Options[OptTemperature]; ok {
// Certain model families don't support custom temperature (locked to default).
// This is a model-level constraint, not provider-specific — applies to both OpenAI and Azure.
// Note: gpt-5.X flagship models (gpt-5.1, gpt-5.4) DO support temperature;
// Note: gpt-5.X flagship models (gpt-5.1, gpt-5.4, gpt-5.5) DO support temperature;
// only the mini/nano reasoning variants reject it.
skipTemp := strings.HasPrefix(capabilityModel, "gpt-5-mini") || strings.HasPrefix(capabilityModel, "gpt-5-nano") || strings.HasPrefix(capabilityModel, "o1") || strings.HasPrefix(capabilityModel, "o3") || strings.HasPrefix(capabilityModel, "o4")
if !skipTemp {
@@ -25,6 +25,7 @@ type reasoningCapabilityEntry struct {
}
var reasoningCapabilityEntries = []reasoningCapabilityEntry{
{id: "gpt-5.5", capability: ReasoningCapability{Levels: []string{"none", "low", "medium", "high", "xhigh"}, DefaultEffort: "medium"}},
{id: "gpt-5.4-mini", capability: ReasoningCapability{Levels: []string{"none", "low", "medium", "high", "xhigh"}, DefaultEffort: "none"}},
{id: "gpt-5-mini", capability: ReasoningCapability{Levels: []string{"none", "low", "medium", "high", "xhigh"}, DefaultEffort: "none"}},
{id: "gpt-5.4", capability: ReasoningCapability{Levels: []string{"none", "low", "medium", "high", "xhigh"}, DefaultEffort: "none"}},
@@ -30,6 +30,14 @@ func TestLookupReasoningCapability(t *testing.T) {
if capability.Supports("low") {
t.Fatal("expected gpt-5.1-codex-max to reject low")
}
capability = LookupReasoningCapability("gpt-5.5")
if capability == nil {
t.Fatal("LookupReasoningCapability(gpt-5.5) = nil, want capability")
}
if capability.DefaultEffort != "medium" {
t.Fatalf("gpt-5.5 default_effort = %q, want medium", capability.DefaultEffort)
}
}
func TestResolveReasoningDecisionDowngradesUnsupportedEffort(t *testing.T) {
@@ -82,6 +82,7 @@ func TestModelContextWindow(t *testing.T) {
}{
{"claude-sonnet-4-5-20250929", 200_000},
{"gpt-4o-mini", 128_000},
{"gpt-5.5", 1_050_000},
{"gpt-5.4", 1_000_000},
{"unknown-model", 200_000}, // conservative default
}
+3 -2
View File
@@ -31,8 +31,8 @@ type TokenizerID string
const (
TokenizerCL100K TokenizerID = "cl100k_base" // Claude, GPT-3.5/4
TokenizerO200K TokenizerID = "o200k_base" // GPT-4o, GPT-5
TokenizerFallback TokenizerID = "fallback" // rune-count / 3
TokenizerO200K TokenizerID = "o200k_base" // GPT-4o, GPT-5
TokenizerFallback TokenizerID = "fallback" // rune-count / 3
)
// ModelInfo maps a model name prefix to its tokenizer + context window.
@@ -47,6 +47,7 @@ var DefaultRegistry = map[string]ModelInfo{
"claude-": {TokenizerCL100K, 200_000},
"gpt-4o": {TokenizerO200K, 128_000},
"gpt-4": {TokenizerCL100K, 128_000},
"gpt-5.5": {TokenizerO200K, 1_050_000},
"gpt-5": {TokenizerO200K, 1_000_000},
"qwen-": {TokenizerCL100K, 128_000},
"deepseek-": {TokenizerCL100K, 128_000},
@@ -61,7 +61,7 @@ export function OAuthSection({
<p className="mt-1">
{t("oauth.modelPrefixHint")} <code className="rounded bg-muted px-1 font-mono">{provider}/</code>{" "}
{t("oauth.modelPrefixExample", {
example: `${provider}/gpt-5.4`,
example: `${provider}/gpt-5.5`,
})}
</p>
</div>
@@ -244,7 +244,7 @@ export function OAuthSection({
<p className="mt-1">
{t("oauth.modelPrefixHint")} <code className="rounded bg-muted px-1 font-mono">{resolvedProviderName}/</code>{" "}
{t("oauth.modelPrefixExample", {
example: `${resolvedProviderName}/gpt-5.4`,
example: `${resolvedProviderName}/gpt-5.5`,
})}
</p>
</div>