detached 7430103

Add GPT-6 Astra support across OpenAI and Copilot providers

commit
7430103d80299f9a802f42b11a89dc3652dcca8f
author
Dario · · 2026-09-05 02:41 UTC
parent
b412b2a
signature
unsigned
9 files · +135 -8
M docs/providers.md +17 -0
@@ -90,6 +90,23 @@ These providers support subscription login:
9090 OAuth tokens are stored in `$ZOT_HOME/auth.json` and refreshed when refresh is
9191 available.
9292
93+## GPT-6 Astra
94+
95+Select `gpt-6-astra` with provider `openai`, `openai-responses`,
96+`openai-codex`, or `github-copilot`, for example:
97+
98+```bash
99+zot --provider github-copilot --model gpt-6-astra
100+```
101+
102+Astra uses the Responses API and supports `low`, `medium`, `high`, `xhigh`,
103+and `max` reasoning. The catalog includes its 1,050,000-token context window,
104+128,000-token output limit, and standard token prices, including the higher
105+rates for prompts above 272,000 tokens. Subscription access depends on your
106+plan, rollout, and organization policy; catalog availability does not grant access.
107+See the [OpenAI model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
108+and [GitHub Copilot availability](https://github.blog/changelog/2026-09-04-gpt-6-astra-is-generally-available-in-github-copilot/).
109+
93110 ## API-key providers
94111
95112 These providers can use environment variables. Simple API-key providers can
@@ -90,6 +90,23 @@ These providers support subscription login:
90 OAuth tokens are stored in `$ZOT_HOME/auth.json` and refreshed when refresh is90 OAuth tokens are stored in `$ZOT_HOME/auth.json` and refreshed when refresh is
91 available.91 available.
92 92
93+## GPT-6 Astra
94+
95+Select `gpt-6-astra` with provider `openai`, `openai-responses`,
96+`openai-codex`, or `github-copilot`, for example:
97+
98+```bash
99+zot --provider github-copilot --model gpt-6-astra
100+```
101+
102+Astra uses the Responses API and supports `low`, `medium`, `high`, `xhigh`,
103+and `max` reasoning. The catalog includes its 1,050,000-token context window,
104+128,000-token output limit, and standard token prices, including the higher
105+rates for prompts above 272,000 tokens. Subscription access depends on your
106+plan, rollout, and organization policy; catalog availability does not grant access.
107+See the [OpenAI model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
108+and [GitHub Copilot availability](https://github.blog/changelog/2026-09-04-gpt-6-astra-is-generally-available-in-github-copilot/).
109+
93 ## API-key providers110 ## API-key providers
94 111
95 These providers can use environment variables. Simple API-key providers can112 These providers can use environment variables. Simple API-key providers can
M packages/provider/catalog_builtin.go +1 -0
@@ -163,6 +163,7 @@ var builtinCatalog = []Model{
163163 {Provider: "github-copilot", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
164164 {Provider: "github-copilot", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
165165 {Provider: "github-copilot", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
166+ {Provider: "github-copilot", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses, ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5, PriceTierInputTokens: 272000, PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25, BaseURL: "https://api.individual.githubcopilot.com"},
166167 {Provider: "github-copilot", ID: "grok-code-fast-1", DisplayName: "Grok Code Fast 1", ContextWindow: 128000, MaxOutput: 64000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
167168 // ----- google -----
168169 {Provider: "google", ID: "gemini-3-flash-preview", DisplayName: "Gemini 3 Flash Preview", ContextWindow: 1048576, MaxOutput: 65536, Reasoning: true, PriceInput: 0.5, PriceOutput: 3, PriceCacheRead: 0.05, BaseURL: "https://generativelanguage.googleapis.com/v1beta"},
@@ -163,6 +163,7 @@ var builtinCatalog = []Model{
163 {Provider: "github-copilot", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},163 {Provider: "github-copilot", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
164 {Provider: "github-copilot", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},164 {Provider: "github-copilot", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
165 {Provider: "github-copilot", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},165 {Provider: "github-copilot", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", API: APIResponses, ContextWindow: 400000, MaxOutput: 128000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
166+ {Provider: "github-copilot", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses, ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5, PriceTierInputTokens: 272000, PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25, BaseURL: "https://api.individual.githubcopilot.com"},
166 {Provider: "github-copilot", ID: "grok-code-fast-1", DisplayName: "Grok Code Fast 1", ContextWindow: 128000, MaxOutput: 64000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},167 {Provider: "github-copilot", ID: "grok-code-fast-1", DisplayName: "Grok Code Fast 1", ContextWindow: 128000, MaxOutput: 64000, Reasoning: true, PriceInput: 0, PriceOutput: 0, PriceCacheRead: 0, BaseURL: "https://api.individual.githubcopilot.com"},
167 // ----- google -----168 // ----- google -----
168 {Provider: "google", ID: "gemini-3-flash-preview", DisplayName: "Gemini 3 Flash Preview", ContextWindow: 1048576, MaxOutput: 65536, Reasoning: true, PriceInput: 0.5, PriceOutput: 3, PriceCacheRead: 0.05, BaseURL: "https://generativelanguage.googleapis.com/v1beta"},169 {Provider: "google", ID: "gemini-3-flash-preview", DisplayName: "Gemini 3 Flash Preview", ContextWindow: 1048576, MaxOutput: 65536, Reasoning: true, PriceInput: 0.5, PriceOutput: 3, PriceCacheRead: 0.05, BaseURL: "https://generativelanguage.googleapis.com/v1beta"},
M packages/provider/extra_models.go +5 -0
@@ -48,6 +48,11 @@ var supplementCatalog = []Model{
4848 BaseURL: "https://api.x.ai/v1"},
4949
5050 // ----- openai-responses (public OpenAI Responses API) -----
51+ {Provider: "openai-responses", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra (Responses)", API: APIResponses,
52+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
53+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
54+ PriceTierInputTokens: 272000,
55+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25},
5156 {Provider: "openai-responses", ID: "gpt-5", DisplayName: "GPT-5 (Responses)",
5257 ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
5358 PriceInput: 1.25, PriceOutput: 10.00, PriceCacheRead: 0.125},
@@ -48,6 +48,11 @@ var supplementCatalog = []Model{
48 BaseURL: "https://api.x.ai/v1"},48 BaseURL: "https://api.x.ai/v1"},
49 49
50 // ----- openai-responses (public OpenAI Responses API) -----50 // ----- openai-responses (public OpenAI Responses API) -----
51+ {Provider: "openai-responses", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra (Responses)", API: APIResponses,
52+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
53+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
54+ PriceTierInputTokens: 272000,
55+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25},
51 {Provider: "openai-responses", ID: "gpt-5", DisplayName: "GPT-5 (Responses)",56 {Provider: "openai-responses", ID: "gpt-5", DisplayName: "GPT-5 (Responses)",
52 ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,57 ContextWindow: 400000, MaxOutput: 128000, Reasoning: true,
53 PriceInput: 1.25, PriceOutput: 10.00, PriceCacheRead: 0.125},58 PriceInput: 1.25, PriceOutput: 10.00, PriceCacheRead: 0.125},
M packages/provider/github_copilot.go +2 -3
@@ -203,9 +203,8 @@ const copilotDefaultBaseURL = "https://api.individual.githubcopilot.com"
203203 // The pat must be a GitHub Personal Access Token with Copilot access.
204204 //
205205 // Copilot exposes two wire protocols: most models use Chat Completions
206-// (/chat/completions), but the newer GPT-5.6 family (sol/terra/luna) is
207-// only served through the Responses API (/responses) and returns an
208-// "chat/completions is not available for gpt models" error otherwise.
206+// (/chat/completions), while newer GPT models use the Responses API
207+// (/responses), including GPT-5.6 (sol/terra/luna) and GPT-6 Astra.
209208 // A model router dispatches each request to the matching wire client
210209 // based on the model's catalog API tag.
211210 func NewGithubCopilotClient(pat string) Client {
@@ -203,9 +203,8 @@ const copilotDefaultBaseURL = "https://api.individual.githubcopilot.com"
203 // The pat must be a GitHub Personal Access Token with Copilot access.203 // The pat must be a GitHub Personal Access Token with Copilot access.
204 //204 //
205 // Copilot exposes two wire protocols: most models use Chat Completions205 // Copilot exposes two wire protocols: most models use Chat Completions
206-// (/chat/completions), but the newer GPT-5.6 family (sol/terra/luna) is206+// (/chat/completions), while newer GPT models use the Responses API
207-// only served through the Responses API (/responses) and returns an207+// (/responses), including GPT-5.6 (sol/terra/luna) and GPT-6 Astra.
208-// "chat/completions is not available for gpt models" error otherwise.
209 // A model router dispatches each request to the matching wire client208 // A model router dispatches each request to the matching wire client
210 // based on the model's catalog API tag.209 // based on the model's catalog API tag.
211 func NewGithubCopilotClient(pat string) Client {210 func NewGithubCopilotClient(pat string) Client {
M packages/provider/github_copilot_test.go +2 -2
@@ -69,7 +69,7 @@ func TestCopilotHostRewrite(t *testing.T) {
6969 }
7070 }
7171
72-func TestCopilotRoutesGPT56ToResponses(t *testing.T) {
72+func TestCopilotRoutesModernGPTToResponses(t *testing.T) {
7373 router, ok := NewGithubCopilotClient("pat").(*modelRouter)
7474 if !ok {
7575 t.Fatal("NewGithubCopilotClient did not return a modelRouter")
@@ -86,7 +86,7 @@ func TestCopilotRoutesGPT56ToResponses(t *testing.T) {
8686 if codex.baseURL != want {
8787 t.Errorf("github-copilot Responses baseURL = %q, want %q", codex.baseURL, want)
8888 }
89- for _, id := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"} {
89+ for _, id := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"} {
9090 m, err := FindModel("github-copilot", id)
9191 if err != nil {
9292 t.Fatalf("FindModel(%q): %v", id, err)
@@ -69,7 +69,7 @@ func TestCopilotHostRewrite(t *testing.T) {
69 }69 }
70 }70 }
71 71
72-func TestCopilotRoutesGPT56ToResponses(t *testing.T) {72+func TestCopilotRoutesModernGPTToResponses(t *testing.T) {
73 router, ok := NewGithubCopilotClient("pat").(*modelRouter)73 router, ok := NewGithubCopilotClient("pat").(*modelRouter)
74 if !ok {74 if !ok {
75 t.Fatal("NewGithubCopilotClient did not return a modelRouter")75 t.Fatal("NewGithubCopilotClient did not return a modelRouter")
@@ -86,7 +86,7 @@ func TestCopilotRoutesGPT56ToResponses(t *testing.T) {
86 if codex.baseURL != want {86 if codex.baseURL != want {
87 t.Errorf("github-copilot Responses baseURL = %q, want %q", codex.baseURL, want)87 t.Errorf("github-copilot Responses baseURL = %q, want %q", codex.baseURL, want)
88 }88 }
89- for _, id := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"} {89+ for _, id := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"} {
90 m, err := FindModel("github-copilot", id)90 m, err := FindModel("github-copilot", id)
91 if err != nil {91 if err != nil {
92 t.Fatalf("FindModel(%q): %v", id, err)92 t.Fatalf("FindModel(%q): %v", id, err)
A packages/provider/gpt6_astra_test.go +79 -0
@@ -0,0 +1,79 @@
1+package provider
2+
3+import (
4+ "context"
5+ "slices"
6+ "testing"
7+)
8+
9+func TestGPT6AstraCatalog(t *testing.T) {
10+ for _, name := range []string{"openai", "openai-responses", "openai-codex", "github-copilot"} {
11+ t.Run(name, func(t *testing.T) {
12+ m, err := FindModel(name, "gpt-6-astra")
13+ if err != nil {
14+ t.Fatal(err)
15+ }
16+ if m.API != APIResponses || m.ContextWindow != 1050000 || m.MaxOutput != 128000 || !m.Reasoning || m.Speculative {
17+ t.Fatalf("unexpected model capabilities: %+v", m)
18+ }
19+ if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 1 || m.PriceCacheWrite != 12.5 {
20+ t.Fatalf("unexpected standard prices: %+v", m)
21+ }
22+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 20 || m.PriceOutputAbove != 75 || m.PriceCacheReadAbove != 2 || m.PriceCacheWriteAbove != 25 {
23+ t.Fatalf("unexpected long-context prices: %+v", m)
24+ }
25+ wantLevels := []string{"", "low", "medium", "high", "xhigh", "max"}
26+ if got := AvailableReasoningLevels(m); !slices.Equal(got, wantLevels) {
27+ t.Fatalf("reasoning levels = %q, want %q", got, wantLevels)
28+ }
29+ })
30+ }
31+}
32+
33+func TestGPT6AstraResponsesReasoning(t *testing.T) {
34+ copilot := NewGithubCopilotClient("test-token").(*modelRouter)
35+ clients := map[string]*codexClient{
36+ "openai": NewOpenAIResponsesNamed("test-token", "", "openai").(*renamedClient).inner.(*codexClient),
37+ "openai-responses": NewOpenAIResponsesNamed("test-token", "", "openai-responses").(*renamedClient).inner.(*codexClient),
38+ "openai-codex": NewOpenAICodex("test-token", "test-account", "").(*codexClient),
39+ "github-copilot": copilot.byAPI[APIResponses].(*renamedClient).inner.(*codexClient),
40+ }
41+ for name, client := range clients {
42+ t.Run(name, func(t *testing.T) {
43+ for _, effort := range []string{"", "low", "medium", "high", "xhigh", "max"} {
44+ wire, err := client.buildRequest(Request{Model: "gpt-6-astra", Reasoning: effort})
45+ if err != nil {
46+ t.Fatal(err)
47+ }
48+ if wire.Model != "gpt-6-astra" {
49+ t.Fatalf("wire model = %q", wire.Model)
50+ }
51+ if effort == "" {
52+ if wire.Reasoning != nil {
53+ t.Fatalf("unexpected reasoning config: %+v", wire.Reasoning)
54+ }
55+ } else if wire.Reasoning == nil || wire.Reasoning.Effort != effort {
56+ t.Fatalf("reasoning = %+v, want %q", wire.Reasoning, effort)
57+ }
58+ }
59+ })
60+ }
61+}
62+
63+func TestGPT6AstraCopilotDispatch(t *testing.T) {
64+ router := NewGithubCopilotClient("test-token").(*modelRouter)
65+ completions := &routeCaptureClient{name: "github-copilot"}
66+ responses := &routeCaptureClient{name: "github-copilot"}
67+ router.fallback = completions
68+ router.byAPI[APIResponses] = responses
69+
70+ stream, err := router.Stream(context.Background(), Request{Model: "gpt-6-astra"})
71+ if err != nil {
72+ t.Fatal(err)
73+ }
74+ for range stream {
75+ }
76+ if !slices.Equal(responses.models, []string{"gpt-6-astra"}) || len(completions.models) != 0 {
77+ t.Fatalf("Responses models = %v, Completions models = %v", responses.models, completions.models)
78+ }
79+}
@@ -0,0 +1,79 @@
1+package provider
2+
3+import (
4+ "context"
5+ "slices"
6+ "testing"
7+)
8+
9+func TestGPT6AstraCatalog(t *testing.T) {
10+ for _, name := range []string{"openai", "openai-responses", "openai-codex", "github-copilot"} {
11+ t.Run(name, func(t *testing.T) {
12+ m, err := FindModel(name, "gpt-6-astra")
13+ if err != nil {
14+ t.Fatal(err)
15+ }
16+ if m.API != APIResponses || m.ContextWindow != 1050000 || m.MaxOutput != 128000 || !m.Reasoning || m.Speculative {
17+ t.Fatalf("unexpected model capabilities: %+v", m)
18+ }
19+ if m.PriceInput != 10 || m.PriceOutput != 50 || m.PriceCacheRead != 1 || m.PriceCacheWrite != 12.5 {
20+ t.Fatalf("unexpected standard prices: %+v", m)
21+ }
22+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 20 || m.PriceOutputAbove != 75 || m.PriceCacheReadAbove != 2 || m.PriceCacheWriteAbove != 25 {
23+ t.Fatalf("unexpected long-context prices: %+v", m)
24+ }
25+ wantLevels := []string{"", "low", "medium", "high", "xhigh", "max"}
26+ if got := AvailableReasoningLevels(m); !slices.Equal(got, wantLevels) {
27+ t.Fatalf("reasoning levels = %q, want %q", got, wantLevels)
28+ }
29+ })
30+ }
31+}
32+
33+func TestGPT6AstraResponsesReasoning(t *testing.T) {
34+ copilot := NewGithubCopilotClient("test-token").(*modelRouter)
35+ clients := map[string]*codexClient{
36+ "openai": NewOpenAIResponsesNamed("test-token", "", "openai").(*renamedClient).inner.(*codexClient),
37+ "openai-responses": NewOpenAIResponsesNamed("test-token", "", "openai-responses").(*renamedClient).inner.(*codexClient),
38+ "openai-codex": NewOpenAICodex("test-token", "test-account", "").(*codexClient),
39+ "github-copilot": copilot.byAPI[APIResponses].(*renamedClient).inner.(*codexClient),
40+ }
41+ for name, client := range clients {
42+ t.Run(name, func(t *testing.T) {
43+ for _, effort := range []string{"", "low", "medium", "high", "xhigh", "max"} {
44+ wire, err := client.buildRequest(Request{Model: "gpt-6-astra", Reasoning: effort})
45+ if err != nil {
46+ t.Fatal(err)
47+ }
48+ if wire.Model != "gpt-6-astra" {
49+ t.Fatalf("wire model = %q", wire.Model)
50+ }
51+ if effort == "" {
52+ if wire.Reasoning != nil {
53+ t.Fatalf("unexpected reasoning config: %+v", wire.Reasoning)
54+ }
55+ } else if wire.Reasoning == nil || wire.Reasoning.Effort != effort {
56+ t.Fatalf("reasoning = %+v, want %q", wire.Reasoning, effort)
57+ }
58+ }
59+ })
60+ }
61+}
62+
63+func TestGPT6AstraCopilotDispatch(t *testing.T) {
64+ router := NewGithubCopilotClient("test-token").(*modelRouter)
65+ completions := &routeCaptureClient{name: "github-copilot"}
66+ responses := &routeCaptureClient{name: "github-copilot"}
67+ router.fallback = completions
68+ router.byAPI[APIResponses] = responses
69+
70+ stream, err := router.Stream(context.Background(), Request{Model: "gpt-6-astra"})
71+ if err != nil {
72+ t.Fatal(err)
73+ }
74+ for range stream {
75+ }
76+ if !slices.Equal(responses.models, []string{"gpt-6-astra"}) || len(completions.models) != 0 {
77+ t.Fatalf("Responses models = %v, Completions models = %v", responses.models, completions.models)
78+ }
79+}
M packages/provider/models.go +16 -0
@@ -376,6 +376,15 @@ var Catalog = []Model{
376376 Speculative: true,
377377 },
378378
379+ // ---- OpenAI / GPT-6 ----
380+ {
381+ Provider: "openai", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
382+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
383+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
384+ PriceTierInputTokens: 272000,
385+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
386+ },
387+
379388 // ---- OpenAI Codex / ChatGPT subscription backend ----
380389 // Same model ids as the OpenAI family, but routed through the
381390 // ChatGPT Codex OAuth backend rather than api.openai.com.
@@ -421,6 +430,13 @@ var Catalog = []Model{
421430 PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
422431 Speculative: true,
423432 },
433+ {
434+ Provider: "openai-codex", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
435+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
436+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
437+ PriceTierInputTokens: 272000,
438+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
439+ },
424440 }
425441
426442 // DefaultModel is used when the user does not specify one.
@@ -376,6 +376,15 @@ var Catalog = []Model{
376 Speculative: true,376 Speculative: true,
377 },377 },
378 378
379+ // ---- OpenAI / GPT-6 ----
380+ {
381+ Provider: "openai", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
382+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
383+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
384+ PriceTierInputTokens: 272000,
385+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
386+ },
387+
379 // ---- OpenAI Codex / ChatGPT subscription backend ----388 // ---- OpenAI Codex / ChatGPT subscription backend ----
380 // Same model ids as the OpenAI family, but routed through the389 // Same model ids as the OpenAI family, but routed through the
381 // ChatGPT Codex OAuth backend rather than api.openai.com.390 // ChatGPT Codex OAuth backend rather than api.openai.com.
@@ -421,6 +430,13 @@ var Catalog = []Model{
421 PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,430 PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
422 Speculative: true,431 Speculative: true,
423 },432 },
433+ {
434+ Provider: "openai-codex", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
435+ ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
436+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
437+ PriceTierInputTokens: 272000,
438+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
439+ },
424 }440 }
425 441
426 // DefaultModel is used when the user does not specify one.442 // DefaultModel is used when the user does not specify one.
M packages/provider/reasoning.go +8 -3
@@ -58,7 +58,7 @@ func defaultReasoningLevels(model Model) []string {
5858 }
5959 if model.API == APIResponses || model.Provider == "openai-codex" || model.Provider == "openai-responses" || model.Provider == "azure-openai-responses" {
6060 levels := []string{"", "low", "medium", "high", "xhigh"}
61- if strings.HasPrefix(id, "gpt-5.6-") {
61+ if supportsResponsesMaxEffort(id) {
6262 levels = append(levels, "max")
6363 }
6464 return levels
@@ -249,7 +249,7 @@ func OpenAICompatAnthropicEffort(level string) string {
249249 }
250250
251251 // OpenAICodexReasoningEffort maps zot levels onto the Responses API effort
252-// enum. GPT-5.6 supports native max; other models clamp max to xhigh.
252+// enum. GPT-5.6 and GPT-6 Astra support native max; other models clamp max to xhigh.
253253 func OpenAICodexReasoningEffort(level, model string) string {
254254 switch NormalizeReasoning(level) {
255255 case "minimum", "low":
@@ -261,7 +261,7 @@ func OpenAICodexReasoningEffort(level, model string) string {
261261 case "xhigh":
262262 return "xhigh"
263263 case "max":
264- if strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") {
264+ if supportsResponsesMaxEffort(model) {
265265 return "max"
266266 }
267267 return "xhigh"
@@ -269,3 +269,8 @@ func OpenAICodexReasoningEffort(level, model string) string {
269269 return ""
270270 }
271271 }
272+
273+func supportsResponsesMaxEffort(model string) bool {
274+ id := strings.ToLower(model)
275+ return strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra"
276+}
@@ -58,7 +58,7 @@ func defaultReasoningLevels(model Model) []string {
58 }58 }
59 if model.API == APIResponses || model.Provider == "openai-codex" || model.Provider == "openai-responses" || model.Provider == "azure-openai-responses" {59 if model.API == APIResponses || model.Provider == "openai-codex" || model.Provider == "openai-responses" || model.Provider == "azure-openai-responses" {
60 levels := []string{"", "low", "medium", "high", "xhigh"}60 levels := []string{"", "low", "medium", "high", "xhigh"}
61- if strings.HasPrefix(id, "gpt-5.6-") {61+ if supportsResponsesMaxEffort(id) {
62 levels = append(levels, "max")62 levels = append(levels, "max")
63 }63 }
64 return levels64 return levels
@@ -249,7 +249,7 @@ func OpenAICompatAnthropicEffort(level string) string {
249 }249 }
250 250
251 // OpenAICodexReasoningEffort maps zot levels onto the Responses API effort251 // OpenAICodexReasoningEffort maps zot levels onto the Responses API effort
252-// enum. GPT-5.6 supports native max; other models clamp max to xhigh.252+// enum. GPT-5.6 and GPT-6 Astra support native max; other models clamp max to xhigh.
253 func OpenAICodexReasoningEffort(level, model string) string {253 func OpenAICodexReasoningEffort(level, model string) string {
254 switch NormalizeReasoning(level) {254 switch NormalizeReasoning(level) {
255 case "minimum", "low":255 case "minimum", "low":
@@ -261,7 +261,7 @@ func OpenAICodexReasoningEffort(level, model string) string {
261 case "xhigh":261 case "xhigh":
262 return "xhigh"262 return "xhigh"
263 case "max":263 case "max":
264- if strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") {264+ if supportsResponsesMaxEffort(model) {
265 return "max"265 return "max"
266 }266 }
267 return "xhigh"267 return "xhigh"
@@ -269,3 +269,8 @@ func OpenAICodexReasoningEffort(level, model string) string {
269 return ""269 return ""
270 }270 }
271 }271 }
272+
273+func supportsResponsesMaxEffort(model string) bool {
274+ id := strings.ToLower(model)
275+ return strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra"
276+}
M packages/provider/reasoning_test.go +5 -0
@@ -15,6 +15,7 @@ func TestAvailableReasoningLevels(t *testing.T) {
1515 {name: "generic openai", model: Model{Reasoning: true}, want: []string{"", "low", "medium", "high"}},
1616 {name: "responses", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.5"}, want: []string{"", "low", "medium", "high", "xhigh"}},
1717 {name: "responses native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
18+ {name: "astra native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-6-astra"}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
1819 {name: "adaptive", model: Model{Reasoning: true, AdaptiveThinking: true}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
1920 {name: "adaptive compatible", model: Model{Reasoning: true, AdaptiveThinkingCompat: true}, want: []string{"", "high"}},
2021 {name: "gemini pro", model: Model{Provider: "google", ID: "gemini-3-pro-preview", Reasoning: true}, want: []string{"", "low", "high"}},
@@ -63,6 +64,7 @@ func TestClampReasoningForModel(t *testing.T) {
6364 {name: "adaptive minimum", model: Model{Reasoning: true, AdaptiveThinking: true}, level: "minimum", want: "low"},
6465 {name: "gemini pro medium", model: Model{Provider: "google", ID: "gemini-3-pro", Reasoning: true}, level: "medium", want: "high"},
6566 {name: "native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, level: "max", want: "max"},
67+ {name: "astra native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-6-astra"}, level: "max", want: "max"},
6668 {
6769 name: "explicit equivalent",
6870 model: Model{Reasoning: true, ReasoningLevelMap: map[string]string{"minimum": "high"}},
@@ -132,6 +134,9 @@ func TestReasoningEffortMappings(t *testing.T) {
132134 {"xhigh", "gpt-5.6-sol", "high", "xhigh", "xhigh", 32768, "xhigh"},
133135 {"max", "gpt-5.6-sol", "high", "max", "max", 32768, "max"},
134136 {"max", "gpt-5.5", "high", "max", "xhigh", 32768, "max"},
137+ {"max", "gpt-6-astra", "high", "max", "max", 32768, "max"},
138+ {"max", "GPT-6-ASTRA", "high", "max", "max", 32768, "max"},
139+ {"max", "gpt-6-unknown", "high", "max", "xhigh", 32768, "max"},
135140 }
136141 for _, tc := range cases {
137142 if got := NormalizeReasoning(tc.level); got != tc.normalized {
@@ -15,6 +15,7 @@ func TestAvailableReasoningLevels(t *testing.T) {
15 {name: "generic openai", model: Model{Reasoning: true}, want: []string{"", "low", "medium", "high"}},15 {name: "generic openai", model: Model{Reasoning: true}, want: []string{"", "low", "medium", "high"}},
16 {name: "responses", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.5"}, want: []string{"", "low", "medium", "high", "xhigh"}},16 {name: "responses", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.5"}, want: []string{"", "low", "medium", "high", "xhigh"}},
17 {name: "responses native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},17 {name: "responses native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
18+ {name: "astra native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-6-astra"}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
18 {name: "adaptive", model: Model{Reasoning: true, AdaptiveThinking: true}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},19 {name: "adaptive", model: Model{Reasoning: true, AdaptiveThinking: true}, want: []string{"", "low", "medium", "high", "xhigh", "max"}},
19 {name: "adaptive compatible", model: Model{Reasoning: true, AdaptiveThinkingCompat: true}, want: []string{"", "high"}},20 {name: "adaptive compatible", model: Model{Reasoning: true, AdaptiveThinkingCompat: true}, want: []string{"", "high"}},
20 {name: "gemini pro", model: Model{Provider: "google", ID: "gemini-3-pro-preview", Reasoning: true}, want: []string{"", "low", "high"}},21 {name: "gemini pro", model: Model{Provider: "google", ID: "gemini-3-pro-preview", Reasoning: true}, want: []string{"", "low", "high"}},
@@ -63,6 +64,7 @@ func TestClampReasoningForModel(t *testing.T) {
63 {name: "adaptive minimum", model: Model{Reasoning: true, AdaptiveThinking: true}, level: "minimum", want: "low"},64 {name: "adaptive minimum", model: Model{Reasoning: true, AdaptiveThinking: true}, level: "minimum", want: "low"},
64 {name: "gemini pro medium", model: Model{Provider: "google", ID: "gemini-3-pro", Reasoning: true}, level: "medium", want: "high"},65 {name: "gemini pro medium", model: Model{Provider: "google", ID: "gemini-3-pro", Reasoning: true}, level: "medium", want: "high"},
65 {name: "native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, level: "max", want: "max"},66 {name: "native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-5.6-sol"}, level: "max", want: "max"},
67+ {name: "astra native max", model: Model{Reasoning: true, API: APIResponses, ID: "gpt-6-astra"}, level: "max", want: "max"},
66 {68 {
67 name: "explicit equivalent",69 name: "explicit equivalent",
68 model: Model{Reasoning: true, ReasoningLevelMap: map[string]string{"minimum": "high"}},70 model: Model{Reasoning: true, ReasoningLevelMap: map[string]string{"minimum": "high"}},
@@ -132,6 +134,9 @@ func TestReasoningEffortMappings(t *testing.T) {
132 {"xhigh", "gpt-5.6-sol", "high", "xhigh", "xhigh", 32768, "xhigh"},134 {"xhigh", "gpt-5.6-sol", "high", "xhigh", "xhigh", 32768, "xhigh"},
133 {"max", "gpt-5.6-sol", "high", "max", "max", 32768, "max"},135 {"max", "gpt-5.6-sol", "high", "max", "max", 32768, "max"},
134 {"max", "gpt-5.5", "high", "max", "xhigh", 32768, "max"},136 {"max", "gpt-5.5", "high", "max", "xhigh", 32768, "max"},
137+ {"max", "gpt-6-astra", "high", "max", "max", 32768, "max"},
138+ {"max", "GPT-6-ASTRA", "high", "max", "max", 32768, "max"},
139+ {"max", "gpt-6-unknown", "high", "max", "xhigh", 32768, "max"},
135 }140 }
136 for _, tc := range cases {141 for _, tc := range cases {
137 if got := NormalizeReasoning(tc.level); got != tc.normalized {142 if got := NormalizeReasoning(tc.level); got != tc.normalized {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close