detached c6cf9e7

Fix GPT-5.6 Responses errors and metadata

commit
c6cf9e7e7830d844f531f602a6cabc0f33b1efe1
author
patriceckhart · · 2026-07-10 05:20 UTC
parent
c18646e
signature
unsigned
5 files · +113 -40
M packages/provider/models.go +12 -12
@@ -318,20 +318,20 @@ var Catalog = []Model{
318318 },
319319 {
320320 Provider: "openai", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",
321- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
322- PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1,
321+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
322+ PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25,
323323 Speculative: true,
324324 },
325325 {
326326 Provider: "openai", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",
327- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
328- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,
327+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
328+ PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
329329 Speculative: true,
330330 },
331331 {
332332 Provider: "openai", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",
333- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
334- PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25,
333+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
334+ PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
335335 Speculative: true,
336336 },
337337
@@ -364,20 +364,20 @@ var Catalog = []Model{
364364 },
365365 {
366366 Provider: "openai-codex", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",
367- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
368- PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1,
367+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
368+ PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25,
369369 Speculative: true,
370370 },
371371 {
372372 Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",
373- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
374- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,
373+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
374+ PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
375375 Speculative: true,
376376 },
377377 {
378378 Provider: "openai-codex", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",
379- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,
380- PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25,
379+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
380+ PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
381381 Speculative: true,
382382 },
383383 }
@@ -318,20 +318,20 @@ var Catalog = []Model{
318 },318 },
319 {319 {
320 Provider: "openai", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",320 Provider: "openai", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",
321- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,321+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
322- PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1,322+ PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25,
323 Speculative: true,323 Speculative: true,
324 },324 },
325 {325 {
326 Provider: "openai", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",326 Provider: "openai", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",
327- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,327+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
328- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,328+ PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
329 Speculative: true,329 Speculative: true,
330 },330 },
331 {331 {
332 Provider: "openai", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",332 Provider: "openai", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",
333- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,333+ ContextWindow: 272000, MaxOutput: 128000, Reasoning: true,
334- PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25,334+ PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
335 Speculative: true,335 Speculative: true,
336 },336 },
337 337
@@ -364,20 +364,20 @@ var Catalog = []Model{
364 },364 },
365 {365 {
366 Provider: "openai-codex", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",366 Provider: "openai-codex", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna",
367- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,367+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
368- PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1,368+ PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25,
369 Speculative: true,369 Speculative: true,
370 },370 },
371 {371 {
372 Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",372 Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol",
373- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,373+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
374- PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,374+ PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25,
375 Speculative: true,375 Speculative: true,
376 },376 },
377 {377 {
378 Provider: "openai-codex", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",378 Provider: "openai-codex", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra",
379- ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true,379+ ContextWindow: 372000, MaxOutput: 128000, Reasoning: true,
380- PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25,380+ PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125,
381 Speculative: true,381 Speculative: true,
382 },382 },
383 }383 }
M packages/provider/openai_codex.go +29 -15
@@ -38,11 +38,11 @@ import (
3838 const codexDefaultBaseURL = "https://chatgpt.com/backend-api/codex/responses"
3939
4040 type codexClient struct {
41- token string
42- accountID string
43- baseURL string
44- codexRoutingAllGPT56 bool
45- http *http.Client
41+ token string
42+ accountID string
43+ baseURL string
44+ errorLabel string
45+ http *http.Client
4646 }
4747
4848 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint
@@ -53,10 +53,11 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
5353 baseURL = codexDefaultBaseURL
5454 }
5555 return &codexClient{
56- token: token,
57- accountID: accountID,
58- baseURL: strings.TrimRight(baseURL, "/"),
59- http: &http.Client{Timeout: 0},
56+ token: token,
57+ accountID: accountID,
58+ baseURL: strings.TrimRight(baseURL, "/"),
59+ errorLabel: "codex",
60+ http: &http.Client{Timeout: 0},
6061 }
6162 }
6263
@@ -323,10 +324,6 @@ func usesCodexCLIRouting(model string) bool {
323324 }
324325 }
325326
326-func isGPT56PreviewModel(model string) bool {
327- return strings.HasPrefix(model, "gpt-5.6-")
328-}
329-
330327 func newCodexSessionID() string {
331328 var b [16]byte
332329 if _, err := rand.Read(b[:]); err != nil {
@@ -349,7 +346,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
349346 return nil, err
350347 }
351348 var codexCLISessionID string
352- if usesCodexCLIRouting(wire.Model) || (c.codexRoutingAllGPT56 && isGPT56PreviewModel(wire.Model)) {
349+ if usesCodexCLIRouting(wire.Model) {
353350 codexCLISessionID = newCodexSessionID()
354351 wire.PromptCacheKey = codexCLISessionID
355352 }
@@ -641,14 +638,31 @@ func (c *codexClient) runStream(ctx context.Context, resp *http.Response, req Re
641638 var p struct {
642639 Message string `json:"message"`
643640 Code string `json:"code"`
641+ Error struct {
642+ Message string `json:"message"`
643+ Code string `json:"code"`
644+ } `json:"error"`
644645 }
645646 _ = json.Unmarshal([]byte(ev.Data), &p)
646647 msg := p.Message
648+ if msg == "" {
649+ msg = p.Error.Message
650+ }
647651 if msg == "" {
648652 msg = p.Code
649653 }
654+ if msg == "" {
655+ msg = p.Error.Code
656+ }
657+ if msg == "" {
658+ msg = strings.TrimSpace(ev.Data)
659+ }
650660 stop = StopError
651- finalErr = fmt.Errorf("codex error: %s", msg)
661+ label := c.errorLabel
662+ if label == "" {
663+ label = "codex"
664+ }
665+ finalErr = fmt.Errorf("%s error: %s", label, msg)
652666 sendDone()
653667 return
654668 }
@@ -38,11 +38,11 @@ import (
38 const codexDefaultBaseURL = "https://chatgpt.com/backend-api/codex/responses"38 const codexDefaultBaseURL = "https://chatgpt.com/backend-api/codex/responses"
39 39
40 type codexClient struct {40 type codexClient struct {
41- token string41+ token string
42- accountID string42+ accountID string
43- baseURL string43+ baseURL string
44- codexRoutingAllGPT56 bool44+ errorLabel string
45- http *http.Client45+ http *http.Client
46 }46 }
47 47
48 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint48 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint
@@ -53,10 +53,11 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
53 baseURL = codexDefaultBaseURL53 baseURL = codexDefaultBaseURL
54 }54 }
55 return &codexClient{55 return &codexClient{
56- token: token,56+ token: token,
57- accountID: accountID,57+ accountID: accountID,
58- baseURL: strings.TrimRight(baseURL, "/"),58+ baseURL: strings.TrimRight(baseURL, "/"),
59- http: &http.Client{Timeout: 0},59+ errorLabel: "codex",
60+ http: &http.Client{Timeout: 0},
60 }61 }
61 }62 }
62 63
@@ -323,10 +324,6 @@ func usesCodexCLIRouting(model string) bool {
323 }324 }
324 }325 }
325 326
326-func isGPT56PreviewModel(model string) bool {
327- return strings.HasPrefix(model, "gpt-5.6-")
328-}
329-
330 func newCodexSessionID() string {327 func newCodexSessionID() string {
331 var b [16]byte328 var b [16]byte
332 if _, err := rand.Read(b[:]); err != nil {329 if _, err := rand.Read(b[:]); err != nil {
@@ -349,7 +346,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
349 return nil, err346 return nil, err
350 }347 }
351 var codexCLISessionID string348 var codexCLISessionID string
352- if usesCodexCLIRouting(wire.Model) || (c.codexRoutingAllGPT56 && isGPT56PreviewModel(wire.Model)) {349+ if usesCodexCLIRouting(wire.Model) {
353 codexCLISessionID = newCodexSessionID()350 codexCLISessionID = newCodexSessionID()
354 wire.PromptCacheKey = codexCLISessionID351 wire.PromptCacheKey = codexCLISessionID
355 }352 }
@@ -641,14 +638,31 @@ func (c *codexClient) runStream(ctx context.Context, resp *http.Response, req Re
641 var p struct {638 var p struct {
642 Message string `json:"message"`639 Message string `json:"message"`
643 Code string `json:"code"`640 Code string `json:"code"`
641+ Error struct {
642+ Message string `json:"message"`
643+ Code string `json:"code"`
644+ } `json:"error"`
644 }645 }
645 _ = json.Unmarshal([]byte(ev.Data), &p)646 _ = json.Unmarshal([]byte(ev.Data), &p)
646 msg := p.Message647 msg := p.Message
648+ if msg == "" {
649+ msg = p.Error.Message
650+ }
647 if msg == "" {651 if msg == "" {
648 msg = p.Code652 msg = p.Code
649 }653 }
654+ if msg == "" {
655+ msg = p.Error.Code
656+ }
657+ if msg == "" {
658+ msg = strings.TrimSpace(ev.Data)
659+ }
650 stop = StopError660 stop = StopError
651- finalErr = fmt.Errorf("codex error: %s", msg)661+ label := c.errorLabel
662+ if label == "" {
663+ label = "codex"
664+ }
665+ finalErr = fmt.Errorf("%s error: %s", label, msg)
652 sendDone()666 sendDone()
653 return667 return
654 }668 }
M packages/provider/openai_codex_test.go +57 -0
@@ -114,6 +114,63 @@ func TestCodexPreviewModelUsesCodexCLIShape(t *testing.T) {
114114 }
115115 }
116116
117+func TestOpenAIGPT56DoesNotUseCodexCLIRouting(t *testing.T) {
118+ named := NewOpenAIResponsesNamed("token", "https://example.test/v1/responses", "openai").(*renamedClient)
119+ c := named.inner.(*codexClient)
120+ var gotReq *http.Request
121+ var gotBody codexRequest
122+ c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) {
123+ gotReq = r
124+ if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
125+ t.Fatal(err)
126+ }
127+ return &http.Response{
128+ StatusCode: http.StatusOK,
129+ Header: http.Header{"Content-Type": []string{"text/event-stream"}},
130+ Body: io.NopCloser(strings.NewReader("")),
131+ }, nil
132+ })
133+
134+ events, err := c.Stream(context.Background(), Request{
135+ Model: "gpt-5.6-sol",
136+ Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}},
137+ })
138+ if err != nil {
139+ t.Fatal(err)
140+ }
141+ for range events {
142+ }
143+
144+ if gotReq == nil {
145+ t.Fatal("request was not sent")
146+ }
147+ if gotReq.Header.Get("session-id") != "" {
148+ t.Fatalf("session-id = %q", gotReq.Header.Get("session-id"))
149+ }
150+ if gotBody.PromptCacheKey != "" {
151+ t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey)
152+ }
153+}
154+
155+func TestCodexNestedStreamError(t *testing.T) {
156+ c := NewOpenAICodex("token", "acct", "").(*codexClient)
157+ resp := &http.Response{
158+ Body: io.NopCloser(strings.NewReader("data: {\"type\":\"error\",\"error\":{\"code\":\"model_not_available\",\"message\":\"limited preview\"}}\n\n")),
159+ }
160+ out := make(chan Event, 16)
161+ go c.runStream(context.Background(), resp, Request{Model: "gpt-5.6-sol"}, out)
162+
163+ var got error
164+ for ev := range out {
165+ if done, ok := ev.(EventDone); ok {
166+ got = done.Err
167+ }
168+ }
169+ if got == nil || got.Error() != "codex error: limited preview" {
170+ t.Fatalf("error = %v", got)
171+ }
172+}
173+
117174 func TestCodexSolKeepsZotShape(t *testing.T) {
118175 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)
119176 var gotReq *http.Request
@@ -114,6 +114,63 @@ func TestCodexPreviewModelUsesCodexCLIShape(t *testing.T) {
114 }114 }
115 }115 }
116 116
117+func TestOpenAIGPT56DoesNotUseCodexCLIRouting(t *testing.T) {
118+ named := NewOpenAIResponsesNamed("token", "https://example.test/v1/responses", "openai").(*renamedClient)
119+ c := named.inner.(*codexClient)
120+ var gotReq *http.Request
121+ var gotBody codexRequest
122+ c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) {
123+ gotReq = r
124+ if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
125+ t.Fatal(err)
126+ }
127+ return &http.Response{
128+ StatusCode: http.StatusOK,
129+ Header: http.Header{"Content-Type": []string{"text/event-stream"}},
130+ Body: io.NopCloser(strings.NewReader("")),
131+ }, nil
132+ })
133+
134+ events, err := c.Stream(context.Background(), Request{
135+ Model: "gpt-5.6-sol",
136+ Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}},
137+ })
138+ if err != nil {
139+ t.Fatal(err)
140+ }
141+ for range events {
142+ }
143+
144+ if gotReq == nil {
145+ t.Fatal("request was not sent")
146+ }
147+ if gotReq.Header.Get("session-id") != "" {
148+ t.Fatalf("session-id = %q", gotReq.Header.Get("session-id"))
149+ }
150+ if gotBody.PromptCacheKey != "" {
151+ t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey)
152+ }
153+}
154+
155+func TestCodexNestedStreamError(t *testing.T) {
156+ c := NewOpenAICodex("token", "acct", "").(*codexClient)
157+ resp := &http.Response{
158+ Body: io.NopCloser(strings.NewReader("data: {\"type\":\"error\",\"error\":{\"code\":\"model_not_available\",\"message\":\"limited preview\"}}\n\n")),
159+ }
160+ out := make(chan Event, 16)
161+ go c.runStream(context.Background(), resp, Request{Model: "gpt-5.6-sol"}, out)
162+
163+ var got error
164+ for ev := range out {
165+ if done, ok := ev.(EventDone); ok {
166+ got = done.Err
167+ }
168+ }
169+ if got == nil || got.Error() != "codex error: limited preview" {
170+ t.Fatalf("error = %v", got)
171+ }
172+}
173+
117 func TestCodexSolKeepsZotShape(t *testing.T) {174 func TestCodexSolKeepsZotShape(t *testing.T) {
118 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)175 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)
119 var gotReq *http.Request176 var gotReq *http.Request
M packages/provider/openai_responses.go +5 -5
@@ -63,11 +63,11 @@ func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client {
6363 Timeout: 0,
6464 }
6565 inner := &codexClient{
66- token: apiKey,
67- accountID: "", // unused; transport strips the header
68- baseURL: strings.TrimRight(baseURL, "/"),
69- codexRoutingAllGPT56: true,
70- http: httpClient,
66+ token: apiKey,
67+ accountID: "", // unused; transport strips the header
68+ baseURL: strings.TrimRight(baseURL, "/"),
69+ errorLabel: "openai",
70+ http: httpClient,
7171 }
7272 return &renamedClient{inner: inner, name: name}
7373 }
@@ -63,11 +63,11 @@ func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client {
63 Timeout: 0,63 Timeout: 0,
64 }64 }
65 inner := &codexClient{65 inner := &codexClient{
66- token: apiKey,66+ token: apiKey,
67- accountID: "", // unused; transport strips the header67+ accountID: "", // unused; transport strips the header
68- baseURL: strings.TrimRight(baseURL, "/"),68+ baseURL: strings.TrimRight(baseURL, "/"),
69- codexRoutingAllGPT56: true,69+ errorLabel: "openai",
70- http: httpClient,70+ http: httpClient,
71 }71 }
72 return &renamedClient{inner: inner, name: name}72 return &renamedClient{inner: inner, name: name}
73 }73 }
M packages/provider/provider_test.go +10 -8
@@ -389,26 +389,28 @@ func TestGPT56CatalogEntries(t *testing.T) {
389389 cases := []struct {
390390 provider string
391391 id string
392+ context int
392393 priceInput float64
393394 priceOut float64
394395 cacheRead float64
396+ cacheWrite float64
395397 }{
396- {"openai", "gpt-5.6-luna", 1, 6, 0.1},
397- {"openai", "gpt-5.6-sol", 5, 30, 0.5},
398- {"openai", "gpt-5.6-terra", 2.5, 15, 0.25},
399- {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1},
400- {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5},
401- {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25},
398+ {"openai", "gpt-5.6-luna", 272000, 1, 6, 0.1, 1.25},
399+ {"openai", "gpt-5.6-sol", 272000, 5, 30, 0.5, 6.25},
400+ {"openai", "gpt-5.6-terra", 272000, 2.5, 15, 0.25, 3.125},
401+ {"openai-codex", "gpt-5.6-luna", 372000, 1, 6, 0.1, 1.25},
402+ {"openai-codex", "gpt-5.6-sol", 372000, 5, 30, 0.5, 6.25},
403+ {"openai-codex", "gpt-5.6-terra", 372000, 2.5, 15, 0.25, 3.125},
402404 }
403405 for _, tc := range cases {
404406 m, err := FindModel(tc.provider, tc.id)
405407 if err != nil {
406408 t.Fatal(err)
407409 }
408- if m.ContextWindow != 1050000 || m.MaxOutput != 128000 || !m.Reasoning {
410+ if m.ContextWindow != tc.context || m.MaxOutput != 128000 || !m.Reasoning {
409411 t.Fatalf("%s/%s limits: %+v", tc.provider, tc.id, m)
410412 }
411- if m.PriceInput != tc.priceInput || m.PriceOutput != tc.priceOut || m.PriceCacheRead != tc.cacheRead {
413+ if m.PriceInput != tc.priceInput || m.PriceOutput != tc.priceOut || m.PriceCacheRead != tc.cacheRead || m.PriceCacheWrite != tc.cacheWrite {
412414 t.Fatalf("%s/%s prices: %+v", tc.provider, tc.id, m)
413415 }
414416 }
@@ -389,26 +389,28 @@ func TestGPT56CatalogEntries(t *testing.T) {
389 cases := []struct {389 cases := []struct {
390 provider string390 provider string
391 id string391 id string
392+ context int
392 priceInput float64393 priceInput float64
393 priceOut float64394 priceOut float64
394 cacheRead float64395 cacheRead float64
396+ cacheWrite float64
395 }{397 }{
396- {"openai", "gpt-5.6-luna", 1, 6, 0.1},398+ {"openai", "gpt-5.6-luna", 272000, 1, 6, 0.1, 1.25},
397- {"openai", "gpt-5.6-sol", 5, 30, 0.5},399+ {"openai", "gpt-5.6-sol", 272000, 5, 30, 0.5, 6.25},
398- {"openai", "gpt-5.6-terra", 2.5, 15, 0.25},400+ {"openai", "gpt-5.6-terra", 272000, 2.5, 15, 0.25, 3.125},
399- {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1},401+ {"openai-codex", "gpt-5.6-luna", 372000, 1, 6, 0.1, 1.25},
400- {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5},402+ {"openai-codex", "gpt-5.6-sol", 372000, 5, 30, 0.5, 6.25},
401- {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25},403+ {"openai-codex", "gpt-5.6-terra", 372000, 2.5, 15, 0.25, 3.125},
402 }404 }
403 for _, tc := range cases {405 for _, tc := range cases {
404 m, err := FindModel(tc.provider, tc.id)406 m, err := FindModel(tc.provider, tc.id)
405 if err != nil {407 if err != nil {
406 t.Fatal(err)408 t.Fatal(err)
407 }409 }
408- if m.ContextWindow != 1050000 || m.MaxOutput != 128000 || !m.Reasoning {410+ if m.ContextWindow != tc.context || m.MaxOutput != 128000 || !m.Reasoning {
409 t.Fatalf("%s/%s limits: %+v", tc.provider, tc.id, m)411 t.Fatalf("%s/%s limits: %+v", tc.provider, tc.id, m)
410 }412 }
411- if m.PriceInput != tc.priceInput || m.PriceOutput != tc.priceOut || m.PriceCacheRead != tc.cacheRead {413+ if m.PriceInput != tc.priceInput || m.PriceOutput != tc.priceOut || m.PriceCacheRead != tc.cacheRead || m.PriceCacheWrite != tc.cacheWrite {
412 t.Fatalf("%s/%s prices: %+v", tc.provider, tc.id, m)414 t.Fatalf("%s/%s prices: %+v", tc.provider, tc.id, m)
413 }415 }
414 }416 }

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 476

↑↓ moveenter openesc close