detached c18646e

Route GPT-5.6 OpenAI models via Responses

commit
c18646e1d2a391c26d1e1a9f27462fab64bf5968
author
patriceckhart · · 2026-07-09 18:58 UTC
parent
9e3f5d2
signature
unsigned
refs
tag: v0.2.71
5 files · +47 -10
M packages/agent/build.go +3 -0
@@ -748,6 +748,9 @@ func (r Resolved) NewClient() provider.Client {
748748 case "deepseek":
749749 return wrap(provider.NewDeepSeek(r.Credential, r.BaseURL))
750750 case "openai":
751+ if strings.HasPrefix(r.Model, "gpt-5.6-") {
752+ return wrap(provider.NewOpenAIResponsesNamed(r.Credential, r.BaseURL, "openai"))
753+ }
751754 return wrap(provider.NewOpenAI(r.Credential, r.BaseURL))
752755 case "openai-codex":
753756 inner := wrap(provider.NewOpenAICodex(r.Credential, r.AccountID, r.BaseURL))
@@ -748,6 +748,9 @@ func (r Resolved) NewClient() provider.Client {
748 case "deepseek":748 case "deepseek":
749 return wrap(provider.NewDeepSeek(r.Credential, r.BaseURL))749 return wrap(provider.NewDeepSeek(r.Credential, r.BaseURL))
750 case "openai":750 case "openai":
751+ if strings.HasPrefix(r.Model, "gpt-5.6-") {
752+ return wrap(provider.NewOpenAIResponsesNamed(r.Credential, r.BaseURL, "openai"))
753+ }
751 return wrap(provider.NewOpenAI(r.Credential, r.BaseURL))754 return wrap(provider.NewOpenAI(r.Credential, r.BaseURL))
752 case "openai-codex":755 case "openai-codex":
753 inner := wrap(provider.NewOpenAICodex(r.Credential, r.AccountID, r.BaseURL))756 inner := wrap(provider.NewOpenAICodex(r.Credential, r.AccountID, r.BaseURL))
M packages/provider/models.go +18 -0
@@ -316,6 +316,24 @@ var Catalog = []Model{
316316 PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,
317317 Speculative: true,
318318 },
319+ {
320+ 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,
323+ Speculative: true,
324+ },
325+ {
326+ 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,
329+ Speculative: true,
330+ },
331+ {
332+ 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,
335+ Speculative: true,
336+ },
319337
320338 // ---- OpenAI Codex / ChatGPT subscription backend ----
321339 // Same model ids as the OpenAI family, but routed through the
@@ -316,6 +316,24 @@ var Catalog = []Model{
316 PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,316 PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5,
317 Speculative: true,317 Speculative: true,
318 },318 },
319+ {
320+ 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,
323+ Speculative: true,
324+ },
325+ {
326+ 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,
329+ Speculative: true,
330+ },
331+ {
332+ 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,
335+ Speculative: true,
336+ },
319 337
320 // ---- OpenAI Codex / ChatGPT subscription backend ----338 // ---- OpenAI Codex / ChatGPT subscription backend ----
321 // Same model ids as the OpenAI family, but routed through the339 // Same model ids as the OpenAI family, but routed through the
M packages/provider/openai_codex.go +10 -5
@@ -38,10 +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- http *http.Client
41+ token string
42+ accountID string
43+ baseURL string
44+ codexRoutingAllGPT56 bool
45+ http *http.Client
4546 }
4647
4748 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint
@@ -322,6 +323,10 @@ func usesCodexCLIRouting(model string) bool {
322323 }
323324 }
324325
326+func isGPT56PreviewModel(model string) bool {
327+ return strings.HasPrefix(model, "gpt-5.6-")
328+}
329+
325330 func newCodexSessionID() string {
326331 var b [16]byte
327332 if _, err := rand.Read(b[:]); err != nil {
@@ -344,7 +349,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
344349 return nil, err
345350 }
346351 var codexCLISessionID string
347- if usesCodexCLIRouting(wire.Model) {
352+ if usesCodexCLIRouting(wire.Model) || (c.codexRoutingAllGPT56 && isGPT56PreviewModel(wire.Model)) {
348353 codexCLISessionID = newCodexSessionID()
349354 wire.PromptCacheKey = codexCLISessionID
350355 }
@@ -38,10 +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- http *http.Client44+ codexRoutingAllGPT56 bool
45+ http *http.Client
45 }46 }
46 47
47 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint48 // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint
@@ -322,6 +323,10 @@ func usesCodexCLIRouting(model string) bool {
322 }323 }
323 }324 }
324 325
326+func isGPT56PreviewModel(model string) bool {
327+ return strings.HasPrefix(model, "gpt-5.6-")
328+}
329+
325 func newCodexSessionID() string {330 func newCodexSessionID() string {
326 var b [16]byte331 var b [16]byte
327 if _, err := rand.Read(b[:]); err != nil {332 if _, err := rand.Read(b[:]); err != nil {
@@ -344,7 +349,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
344 return nil, err349 return nil, err
345 }350 }
346 var codexCLISessionID string351 var codexCLISessionID string
347- if usesCodexCLIRouting(wire.Model) {352+ if usesCodexCLIRouting(wire.Model) || (c.codexRoutingAllGPT56 && isGPT56PreviewModel(wire.Model)) {
348 codexCLISessionID = newCodexSessionID()353 codexCLISessionID = newCodexSessionID()
349 wire.PromptCacheKey = codexCLISessionID354 wire.PromptCacheKey = codexCLISessionID
350 }355 }
M packages/provider/openai_responses.go +13 -5
@@ -48,6 +48,13 @@ func (t *openaiResponsesTransport) RoundTrip(req *http.Request) (*http.Response,
4848 //
4949 // baseURL may be empty; defaults to https://api.openai.com/v1/responses.
5050 func NewOpenAIResponses(apiKey, baseURL string) Client {
51+ return NewOpenAIResponsesNamed(apiKey, baseURL, "openai-responses")
52+}
53+
54+// NewOpenAIResponsesNamed returns a public OpenAI Responses-API client
55+// reporting the supplied provider name. This lets the `openai` provider
56+// route Responses-only preview models without changing the visible provider.
57+func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client {
5158 if baseURL == "" {
5259 baseURL = openaiResponsesDefaultBaseURL
5360 }
@@ -56,10 +63,11 @@ func NewOpenAIResponses(apiKey, baseURL string) Client {
5663 Timeout: 0,
5764 }
5865 inner := &codexClient{
59- token: apiKey,
60- accountID: "", // unused; transport strips the header
61- baseURL: strings.TrimRight(baseURL, "/"),
62- http: httpClient,
66+ token: apiKey,
67+ accountID: "", // unused; transport strips the header
68+ baseURL: strings.TrimRight(baseURL, "/"),
69+ codexRoutingAllGPT56: true,
70+ http: httpClient,
6371 }
64- return &renamedClient{inner: inner, name: "openai-responses"}
72+ return &renamedClient{inner: inner, name: name}
6573 }
@@ -48,6 +48,13 @@ func (t *openaiResponsesTransport) RoundTrip(req *http.Request) (*http.Response,
48 //48 //
49 // baseURL may be empty; defaults to https://api.openai.com/v1/responses.49 // baseURL may be empty; defaults to https://api.openai.com/v1/responses.
50 func NewOpenAIResponses(apiKey, baseURL string) Client {50 func NewOpenAIResponses(apiKey, baseURL string) Client {
51+ return NewOpenAIResponsesNamed(apiKey, baseURL, "openai-responses")
52+}
53+
54+// NewOpenAIResponsesNamed returns a public OpenAI Responses-API client
55+// reporting the supplied provider name. This lets the `openai` provider
56+// route Responses-only preview models without changing the visible provider.
57+func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client {
51 if baseURL == "" {58 if baseURL == "" {
52 baseURL = openaiResponsesDefaultBaseURL59 baseURL = openaiResponsesDefaultBaseURL
53 }60 }
@@ -56,10 +63,11 @@ func NewOpenAIResponses(apiKey, baseURL string) Client {
56 Timeout: 0,63 Timeout: 0,
57 }64 }
58 inner := &codexClient{65 inner := &codexClient{
59- token: apiKey,66+ token: apiKey,
60- accountID: "", // unused; transport strips the header67+ accountID: "", // unused; transport strips the header
61- baseURL: strings.TrimRight(baseURL, "/"),68+ baseURL: strings.TrimRight(baseURL, "/"),
62- http: httpClient,69+ codexRoutingAllGPT56: true,
70+ http: httpClient,
63 }71 }
64- return &renamedClient{inner: inner, name: "openai-responses"}72+ return &renamedClient{inner: inner, name: name}
65 }73 }
M packages/provider/provider_test.go +3 -0
@@ -393,6 +393,9 @@ func TestGPT56CatalogEntries(t *testing.T) {
393393 priceOut float64
394394 cacheRead float64
395395 }{
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},
396399 {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1},
397400 {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5},
398401 {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25},
@@ -393,6 +393,9 @@ func TestGPT56CatalogEntries(t *testing.T) {
393 priceOut float64393 priceOut float64
394 cacheRead float64394 cacheRead float64
395 }{395 }{
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},
396 {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1},399 {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1},
397 {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5},400 {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5},
398 {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25},401 {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25},

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