detached af6d635

fix(provider): align Astra specs and caching with release (#176)

commit
af6d63500f01e0789411f6cc2679c0daf4125963
author
bnema · · 2026-09-05 10:55 UTC
committer
GitHub · · 2026-09-05 10:55 UTC
parent
d92a60f
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
6 files · +127 -11
M docs/providers.md +26 -0
@@ -148,6 +148,25 @@ provider currently returns an error instead of silently changing that provider's
148148 request. Subagent children inherit the setting from their parent unless their
149149 profile sets `fastMode` explicitly.
150150
151+## GPT-6 Astra
152+
153+`gpt-6-astra` is available through `openai` (Responses API with an API key)
154+and `openai-codex` (ChatGPT subscription). Both catalog entries deliberately
155+use a **500,000-token working context budget**, rather than the larger advertised
156+maximum, and retain a 128,000-token maximum output. Native reasoning efforts
157+`low`, `medium`, `high`, `xhigh`, and `max` are supported. Selecting reasoning off
158+omits the effort parameter; it does not disable Astra's reasoning.
159+
160+Standard API rates per million tokens are $10 input, $50 output, $1 cache reads,
161+and $12.50 cache writes. Above **272,000 total input tokens**, including cache
162+reads and writes, rates for the entire request become $20 input, $75 output,
163+$2 cache reads, and $25 cache writes. The 500k working budget does not avoid
164+this pricing tier. Codex catalog dollar estimates use these API-equivalent
165+rates, not subscription billing or quota measurements.
166+
167+Sources: [Astra model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
168+and [prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
169+
151170 ## Session identity and caching
152171
153172 zut supplies every provider request with a root cache identity, a
@@ -171,6 +190,13 @@ report cache detail. Cache writes remain separate from that hit rate. Provider
171190 retention and eviction, request routing, and OpenAI's 1,024-token eligibility
172191 minimum can still produce a miss even when the stable prefix is unchanged.
173192
193+On the official public OpenAI Responses endpoint, GPT-5.6 and GPT-6 Astra
194+requests include an explicit cache breakpoint after the stable instruction/tool
195+prefix while retaining implicit caching for conversation history. The Codex
196+subscription route and custom Responses endpoints do not receive these explicit
197+cache controls. Changing request-level reasoning effort can still disrupt prefix
198+reuse: zut does not yet send cache-preserving `configuration_update` items.
199+
174200 ### OpenAI Responses WebSocket mode
175201
176202 For the exact public endpoint `https://api.openai.com/v1/responses`, zut uses
@@ -148,6 +148,25 @@ provider currently returns an error instead of silently changing that provider's
148 request. Subagent children inherit the setting from their parent unless their148 request. Subagent children inherit the setting from their parent unless their
149 profile sets `fastMode` explicitly.149 profile sets `fastMode` explicitly.
150 150
151+## GPT-6 Astra
152+
153+`gpt-6-astra` is available through `openai` (Responses API with an API key)
154+and `openai-codex` (ChatGPT subscription). Both catalog entries deliberately
155+use a **500,000-token working context budget**, rather than the larger advertised
156+maximum, and retain a 128,000-token maximum output. Native reasoning efforts
157+`low`, `medium`, `high`, `xhigh`, and `max` are supported. Selecting reasoning off
158+omits the effort parameter; it does not disable Astra's reasoning.
159+
160+Standard API rates per million tokens are $10 input, $50 output, $1 cache reads,
161+and $12.50 cache writes. Above **272,000 total input tokens**, including cache
162+reads and writes, rates for the entire request become $20 input, $75 output,
163+$2 cache reads, and $25 cache writes. The 500k working budget does not avoid
164+this pricing tier. Codex catalog dollar estimates use these API-equivalent
165+rates, not subscription billing or quota measurements.
166+
167+Sources: [Astra model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
168+and [prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
169+
151 ## Session identity and caching170 ## Session identity and caching
152 171
153 zut supplies every provider request with a root cache identity, a172 zut supplies every provider request with a root cache identity, a
@@ -171,6 +190,13 @@ report cache detail. Cache writes remain separate from that hit rate. Provider
171 retention and eviction, request routing, and OpenAI's 1,024-token eligibility190 retention and eviction, request routing, and OpenAI's 1,024-token eligibility
172 minimum can still produce a miss even when the stable prefix is unchanged.191 minimum can still produce a miss even when the stable prefix is unchanged.
173 192
193+On the official public OpenAI Responses endpoint, GPT-5.6 and GPT-6 Astra
194+requests include an explicit cache breakpoint after the stable instruction/tool
195+prefix while retaining implicit caching for conversation history. The Codex
196+subscription route and custom Responses endpoints do not receive these explicit
197+cache controls. Changing request-level reasoning effort can still disrupt prefix
198+reuse: zut does not yet send cache-preserving `configuration_update` items.
199+
174 ### OpenAI Responses WebSocket mode200 ### OpenAI Responses WebSocket mode
175 201
176 For the exact public endpoint `https://api.openai.com/v1/responses`, zut uses202 For the exact public endpoint `https://api.openai.com/v1/responses`, zut uses
A packages/provider/astra_test.go +85 -0
@@ -0,0 +1,85 @@
1+package provider
2+
3+import (
4+ "math"
5+ "slices"
6+ "testing"
7+)
8+
9+func TestAstraReleasedMetadata(t *testing.T) {
10+ for _, name := range []string{"openai", "openai-codex"} {
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.ContextWindow != 500000 || m.MaxOutput != 128000 || m.Speculative {
17+ t.Fatalf("released model must retain the intentional 500k budget: %+v", m)
18+ }
19+ if name == "openai" && m.API != APIResponses {
20+ t.Fatalf("API = %q, want Responses", m.API)
21+ }
22+ if !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
23+ t.Fatalf("native max unavailable: %v", AvailableReasoningLevels(m))
24+ }
25+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 20 || m.PriceOutputAbove != 75 || m.PriceCacheReadAbove != 2 || m.PriceCacheWriteAbove != 25 {
26+ t.Fatalf("long-context rates: %+v", m)
27+ }
28+ for _, tc := range []struct {
29+ name string
30+ usage Usage
31+ want float64
32+ }{
33+ {"at_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100000, CacheWriteTokens: 72000, OutputTokens: 1000}, 2.05},
34+ {"above_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100001, CacheWriteTokens: 72000, OutputTokens: 1000}, 4.075002},
35+ } {
36+ t.Run(tc.name, func(t *testing.T) {
37+ if got := ComputeCost(m, tc.usage); math.Abs(got-tc.want) > 1e-9 {
38+ t.Fatalf("cost = %.9f, want %.9f", got, tc.want)
39+ }
40+ })
41+ }
42+ })
43+ }
44+}
45+
46+func TestAstraResponsesCapabilities(t *testing.T) {
47+ public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http
48+ custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)
49+ for _, tc := range []struct {
50+ name string
51+ client *codexClient
52+ breakpoint bool
53+ }{
54+ {"public", public, true},
55+ {"subscription", newOpenAICodexClient("synthetic-token", "synthetic-account", ""), false},
56+ {"custom", custom, false},
57+ } {
58+ t.Run(tc.name, func(t *testing.T) {
59+ wire, err := tc.client.buildRequest(Request{Model: "gpt-6-astra", Reasoning: "max", Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hello"}}}}})
60+ if err != nil {
61+ t.Fatal(err)
62+ }
63+ if wire.Reasoning == nil || wire.Reasoning.Effort != "max" {
64+ t.Fatalf("reasoning = %+v, want max", wire.Reasoning)
65+ }
66+ if !tc.breakpoint {
67+ if wire.PromptCacheOptions != nil || len(wire.Input) != 1 {
68+ t.Fatalf("undeclared endpoint received explicit caching: %+v", wire)
69+ }
70+ return
71+ }
72+ if wire.PromptCacheOptions == nil || wire.PromptCacheOptions.Mode != "implicit" || len(wire.Input) != 2 {
73+ t.Fatalf("missing stable boundary with implicit history caching: %+v", wire)
74+ }
75+ boundary, ok := wire.Input[0].(codexInputMessage)
76+ if !ok || boundary.Role != "developer" || len(boundary.Content) != 1 {
77+ t.Fatalf("boundary = %#v", wire.Input[0])
78+ }
79+ text, ok := boundary.Content[0].(codexInputText)
80+ if !ok || text.PromptCacheBreakpoint == nil || text.PromptCacheBreakpoint.Mode != "explicit" {
81+ t.Fatalf("breakpoint = %#v", boundary.Content[0])
82+ }
83+ })
84+ }
85+}
@@ -0,0 +1,85 @@
1+package provider
2+
3+import (
4+ "math"
5+ "slices"
6+ "testing"
7+)
8+
9+func TestAstraReleasedMetadata(t *testing.T) {
10+ for _, name := range []string{"openai", "openai-codex"} {
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.ContextWindow != 500000 || m.MaxOutput != 128000 || m.Speculative {
17+ t.Fatalf("released model must retain the intentional 500k budget: %+v", m)
18+ }
19+ if name == "openai" && m.API != APIResponses {
20+ t.Fatalf("API = %q, want Responses", m.API)
21+ }
22+ if !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
23+ t.Fatalf("native max unavailable: %v", AvailableReasoningLevels(m))
24+ }
25+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 20 || m.PriceOutputAbove != 75 || m.PriceCacheReadAbove != 2 || m.PriceCacheWriteAbove != 25 {
26+ t.Fatalf("long-context rates: %+v", m)
27+ }
28+ for _, tc := range []struct {
29+ name string
30+ usage Usage
31+ want float64
32+ }{
33+ {"at_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100000, CacheWriteTokens: 72000, OutputTokens: 1000}, 2.05},
34+ {"above_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100001, CacheWriteTokens: 72000, OutputTokens: 1000}, 4.075002},
35+ } {
36+ t.Run(tc.name, func(t *testing.T) {
37+ if got := ComputeCost(m, tc.usage); math.Abs(got-tc.want) > 1e-9 {
38+ t.Fatalf("cost = %.9f, want %.9f", got, tc.want)
39+ }
40+ })
41+ }
42+ })
43+ }
44+}
45+
46+func TestAstraResponsesCapabilities(t *testing.T) {
47+ public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http
48+ custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)
49+ for _, tc := range []struct {
50+ name string
51+ client *codexClient
52+ breakpoint bool
53+ }{
54+ {"public", public, true},
55+ {"subscription", newOpenAICodexClient("synthetic-token", "synthetic-account", ""), false},
56+ {"custom", custom, false},
57+ } {
58+ t.Run(tc.name, func(t *testing.T) {
59+ wire, err := tc.client.buildRequest(Request{Model: "gpt-6-astra", Reasoning: "max", Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hello"}}}}})
60+ if err != nil {
61+ t.Fatal(err)
62+ }
63+ if wire.Reasoning == nil || wire.Reasoning.Effort != "max" {
64+ t.Fatalf("reasoning = %+v, want max", wire.Reasoning)
65+ }
66+ if !tc.breakpoint {
67+ if wire.PromptCacheOptions != nil || len(wire.Input) != 1 {
68+ t.Fatalf("undeclared endpoint received explicit caching: %+v", wire)
69+ }
70+ return
71+ }
72+ if wire.PromptCacheOptions == nil || wire.PromptCacheOptions.Mode != "implicit" || len(wire.Input) != 2 {
73+ t.Fatalf("missing stable boundary with implicit history caching: %+v", wire)
74+ }
75+ boundary, ok := wire.Input[0].(codexInputMessage)
76+ if !ok || boundary.Role != "developer" || len(boundary.Content) != 1 {
77+ t.Fatalf("boundary = %#v", wire.Input[0])
78+ }
79+ text, ok := boundary.Content[0].(codexInputText)
80+ if !ok || text.PromptCacheBreakpoint == nil || text.PromptCacheBreakpoint.Mode != "explicit" {
81+ t.Fatalf("breakpoint = %#v", boundary.Content[0])
82+ }
83+ })
84+ }
85+}
M packages/provider/models.go +7 -3
@@ -6,6 +6,7 @@ import (
66 "sync"
77 )
88
9+// Deliberate working budget, not the models' advertised maximum context.
910 const openAIContextWindowTarget = 500_000
1011
1112 // Model describes a single LLM we know about.
@@ -358,8 +359,9 @@ var Catalog = []Model{
358359 {
359360 Provider: "openai", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
360361 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
361- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
362- Speculative: true,
362+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
363+ PriceTierInputTokens: 272000,
364+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
363365 },
364366 // ---- OpenAI Codex / ChatGPT subscription backend ----
365367 // Same model ids as the OpenAI family, but routed through the
@@ -406,7 +408,9 @@ var Catalog = []Model{
406408 {
407409 Provider: "openai-codex", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra",
408410 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
409- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
411+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
412+ PriceTierInputTokens: 272000,
413+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
410414 },
411415 }
412416
@@ -6,6 +6,7 @@ import (
6 "sync"6 "sync"
7 )7 )
8 8
9+// Deliberate working budget, not the models' advertised maximum context.
9 const openAIContextWindowTarget = 500_00010 const openAIContextWindowTarget = 500_000
10 11
11 // Model describes a single LLM we know about.12 // Model describes a single LLM we know about.
@@ -358,8 +359,9 @@ var Catalog = []Model{
358 {359 {
359 Provider: "openai", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,360 Provider: "openai", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra", API: APIResponses,
360 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,361 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
361- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,362+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
362- Speculative: true,363+ PriceTierInputTokens: 272000,
364+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
363 },365 },
364 // ---- OpenAI Codex / ChatGPT subscription backend ----366 // ---- OpenAI Codex / ChatGPT subscription backend ----
365 // Same model ids as the OpenAI family, but routed through the367 // Same model ids as the OpenAI family, but routed through the
@@ -406,7 +408,9 @@ var Catalog = []Model{
406 {408 {
407 Provider: "openai-codex", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra",409 Provider: "openai-codex", ID: "gpt-6-astra", DisplayName: "GPT-6 Astra",
408 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,410 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
409- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,411+ PriceInput: 10, PriceOutput: 50, PriceCacheRead: 1, PriceCacheWrite: 12.5,
412+ PriceTierInputTokens: 272000,
413+ PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
410 },414 },
411 }415 }
412 416
M packages/provider/openai_codex.go +4 -3
@@ -214,8 +214,9 @@ func (c *codexClient) findModel(id string) (Model, error) {
214214 return FindModel("openai", id)
215215 }
216216
217-func isGPT56Model(model string) bool {
218- return strings.HasPrefix(strings.ToLower(strings.TrimSpace(model)), "gpt-5.6-")
217+func supportsOpenAIExplicitPromptCache(model string) bool {
218+ id := strings.ToLower(strings.TrimSpace(model))
219+ return strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra"
219220 }
220221
221222 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
@@ -243,7 +244,7 @@ func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
243244 if req.FastMode {
244245 body.ServiceTier = fastModeServiceTier
245246 }
246- if c.capabilities.ExplicitPromptCacheBreakpoint && isGPT56Model(req.Model) {
247+ if c.capabilities.ExplicitPromptCacheBreakpoint && supportsOpenAIExplicitPromptCache(req.Model) {
247248 // Keep implicit mode so OpenAI can retain later conversation
248249 // checkpoints as well as this stable instruction/tool boundary.
249250 body.PromptCacheOptions = &codexPromptCacheOptions{Mode: "implicit"}
@@ -214,8 +214,9 @@ func (c *codexClient) findModel(id string) (Model, error) {
214 return FindModel("openai", id)214 return FindModel("openai", id)
215 }215 }
216 216
217-func isGPT56Model(model string) bool {217+func supportsOpenAIExplicitPromptCache(model string) bool {
218- return strings.HasPrefix(strings.ToLower(strings.TrimSpace(model)), "gpt-5.6-")218+ id := strings.ToLower(strings.TrimSpace(model))
219+ return strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra"
219 }220 }
220 221
221 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {222 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
@@ -243,7 +244,7 @@ func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
243 if req.FastMode {244 if req.FastMode {
244 body.ServiceTier = fastModeServiceTier245 body.ServiceTier = fastModeServiceTier
245 }246 }
246- if c.capabilities.ExplicitPromptCacheBreakpoint && isGPT56Model(req.Model) {247+ if c.capabilities.ExplicitPromptCacheBreakpoint && supportsOpenAIExplicitPromptCache(req.Model) {
247 // Keep implicit mode so OpenAI can retain later conversation248 // Keep implicit mode so OpenAI can retain later conversation
248 // checkpoints as well as this stable instruction/tool boundary.249 // checkpoints as well as this stable instruction/tool boundary.
249 body.PromptCacheOptions = &codexPromptCacheOptions{Mode: "implicit"}250 body.PromptCacheOptions = &codexPromptCacheOptions{Mode: "implicit"}
M packages/provider/provider_test.go +2 -2
@@ -658,11 +658,11 @@ func TestGPT56AndGPT6CatalogEntries(t *testing.T) {
658658 {"openai", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
659659 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
660660 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
661- {"openai", "gpt-6-astra", 500000, 5, 30, 0.5, 6.25},
661+ {"openai", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
662662 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
663663 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
664664 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
665- {"openai-codex", "gpt-6-astra", 500000, 5, 30, 0.5, 6.25},
665+ {"openai-codex", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
666666 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},
667667 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},
668668 {"azure-openai-responses", "gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125},
@@ -658,11 +658,11 @@ func TestGPT56AndGPT6CatalogEntries(t *testing.T) {
658 {"openai", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},658 {"openai", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
659 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},659 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
660 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},660 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
661- {"openai", "gpt-6-astra", 500000, 5, 30, 0.5, 6.25},661+ {"openai", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
662 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},662 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
663 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},663 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
664 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},664 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
665- {"openai-codex", "gpt-6-astra", 500000, 5, 30, 0.5, 6.25},665+ {"openai-codex", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
666 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},666 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},
667 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},667 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},
668 {"azure-openai-responses", "gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125},668 {"azure-openai-responses", "gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125},
M packages/provider/reasoning.go +3 -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 strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra" {
6262 levels = append(levels, "max")
6363 }
6464 return levels
@@ -249,7 +249,7 @@ func OpenAICompatAnthropicEffort(level string) string {
249249 }
250250
251251 // OpenAICodexReasoningEffort maps zut 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 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 strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") || strings.EqualFold(model, "gpt-6-astra") {
265265 return "max"
266266 }
267267 return "xhigh"
@@ -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 strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra" {
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 zut levels onto the Responses API effort251 // OpenAICodexReasoningEffort maps zut 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 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 strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") || strings.EqualFold(model, "gpt-6-astra") {
265 return "max"265 return "max"
266 }266 }
267 return "xhigh"267 return "xhigh"

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