detached 76a207d

feat(provider): add GPT-6.1 Sol to catalog (#234)

Add gpt-6.1-sol for openai (Responses) and openai-codex with published pricing and long-context tier. Share one GPT-5.6/GPT-6.x family check so dotted GPT-6.x ids keep native max reasoning and explicit prompt caching.
commit
76a207d8d98f72c26588a488c59bbf5b924f163b
author
bnema · · 2026-09-29 19:17 UTC
committer
GitHub · · 2026-09-29 19:17 UTC
parent
30477c4
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
6 files · +58 -4
M docs/providers.md +13 -0
@@ -206,6 +206,19 @@ rates, not subscription billing or quota measurements.
206206 Sources: [Astra model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
207207 and [prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
208208
209+## GPT-6.1 Sol
210+
211+`gpt-6.1-sol` is available through `openai` (Responses API) and `openai-codex`
212+with the same 500,000-token working budget, 128,000-token maximum output, and
213+native reasoning efforts `low` through `max` as Astra.
214+
215+Standard API rates per million tokens are $2 input, $10 output, $0.10 cache
216+reads, and $2.50 cache writes. Above 272,000 total input tokens, the whole
217+request is billed at $4 input, $15 output, $0.20 cache reads, and $5 cache
218+writes.
219+
220+Source: [GPT-6.1 Sol model specifications](https://developers.openai.com/api/docs/models/gpt-6.1-sol).
221+
209222 ## Session identity and caching
210223
211224 zut supplies every provider request with a root cache identity, a
@@ -206,6 +206,19 @@ rates, not subscription billing or quota measurements.
206 Sources: [Astra model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)206 Sources: [Astra model specifications](https://developers.openai.com/api/docs/models/gpt-6-astra)
207 and [prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).207 and [prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching).
208 208
209+## GPT-6.1 Sol
210+
211+`gpt-6.1-sol` is available through `openai` (Responses API) and `openai-codex`
212+with the same 500,000-token working budget, 128,000-token maximum output, and
213+native reasoning efforts `low` through `max` as Astra.
214+
215+Standard API rates per million tokens are $2 input, $10 output, $0.10 cache
216+reads, and $2.50 cache writes. Above 272,000 total input tokens, the whole
217+request is billed at $4 input, $15 output, $0.20 cache reads, and $5 cache
218+writes.
219+
220+Source: [GPT-6.1 Sol model specifications](https://developers.openai.com/api/docs/models/gpt-6.1-sol).
221+
209 ## Session identity and caching222 ## Session identity and caching
210 223
211 zut supplies every provider request with a root cache identity, a224 zut supplies every provider request with a root cache identity, a
M packages/provider/astra_test.go +18 -0
@@ -43,6 +43,24 @@ func TestAstraReleasedMetadata(t *testing.T) {
4343 }
4444 }
4545
46+func TestGPT61SolFamilyCapabilities(t *testing.T) {
47+ for _, name := range []string{"openai", "openai-codex"} {
48+ m, err := FindModel(name, "gpt-6.1-sol")
49+ if err != nil {
50+ t.Fatal(err)
51+ }
52+ if m.Speculative || !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
53+ t.Fatalf("%s: native max unavailable: %+v", name, m)
54+ }
55+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 4 || m.PriceOutputAbove != 15 || m.PriceCacheReadAbove != 0.2 || m.PriceCacheWriteAbove != 5 {
56+ t.Fatalf("%s: long-context rates: %+v", name, m)
57+ }
58+ }
59+ if OpenAICodexReasoningEffort("max", "gpt-6.1-sol") != "max" || !supportsOpenAIExplicitPromptCache("gpt-6.1-sol") {
60+ t.Fatal("gpt-6.1-sol must share GPT-6 max reasoning and explicit prompt caching")
61+ }
62+}
63+
4664 func TestAstraResponsesCapabilities(t *testing.T) {
4765 public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http
4866 custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)
@@ -43,6 +43,24 @@ func TestAstraReleasedMetadata(t *testing.T) {
43 }43 }
44 }44 }
45 45
46+func TestGPT61SolFamilyCapabilities(t *testing.T) {
47+ for _, name := range []string{"openai", "openai-codex"} {
48+ m, err := FindModel(name, "gpt-6.1-sol")
49+ if err != nil {
50+ t.Fatal(err)
51+ }
52+ if m.Speculative || !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
53+ t.Fatalf("%s: native max unavailable: %+v", name, m)
54+ }
55+ if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 4 || m.PriceOutputAbove != 15 || m.PriceCacheReadAbove != 0.2 || m.PriceCacheWriteAbove != 5 {
56+ t.Fatalf("%s: long-context rates: %+v", name, m)
57+ }
58+ }
59+ if OpenAICodexReasoningEffort("max", "gpt-6.1-sol") != "max" || !supportsOpenAIExplicitPromptCache("gpt-6.1-sol") {
60+ t.Fatal("gpt-6.1-sol must share GPT-6 max reasoning and explicit prompt caching")
61+ }
62+}
63+
46 func TestAstraResponsesCapabilities(t *testing.T) {64 func TestAstraResponsesCapabilities(t *testing.T) {
47 public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http65 public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http
48 custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)66 custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)
M packages/provider/models.go +14 -0
@@ -398,6 +398,13 @@ var Catalog = []Model{
398398 PriceTierInputTokens: 272000,
399399 PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
400400 },
401+ {
402+ Provider: "openai", ID: "gpt-6.1-sol", DisplayName: "GPT-6.1 Sol", API: APIResponses,
403+ ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
404+ PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.1, PriceCacheWrite: 2.5,
405+ PriceTierInputTokens: 272000,
406+ PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.2, PriceCacheWriteAbove: 5,
407+ },
401408 // ---- OpenAI Codex / ChatGPT subscription backend ----
402409 // Same model ids as the OpenAI family, but routed through the
403410 // ChatGPT Codex OAuth backend rather than api.openai.com.
@@ -454,6 +461,13 @@ var Catalog = []Model{
454461 PriceTierInputTokens: 272000,
455462 PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.4, PriceCacheWriteAbove: 5,
456463 },
464+ {
465+ Provider: "openai-codex", ID: "gpt-6.1-sol", DisplayName: "GPT-6.1 Sol",
466+ ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
467+ PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.1, PriceCacheWrite: 2.5,
468+ PriceTierInputTokens: 272000,
469+ PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.2, PriceCacheWriteAbove: 5,
470+ },
457471 {
458472 Provider: "openai-codex", ID: "gpt-6-luna", DisplayName: "GPT-6 Luna",
459473 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
@@ -398,6 +398,13 @@ var Catalog = []Model{
398 PriceTierInputTokens: 272000,398 PriceTierInputTokens: 272000,
399 PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,399 PriceInputAbove: 20, PriceOutputAbove: 75, PriceCacheReadAbove: 2, PriceCacheWriteAbove: 25,
400 },400 },
401+ {
402+ Provider: "openai", ID: "gpt-6.1-sol", DisplayName: "GPT-6.1 Sol", API: APIResponses,
403+ ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
404+ PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.1, PriceCacheWrite: 2.5,
405+ PriceTierInputTokens: 272000,
406+ PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.2, PriceCacheWriteAbove: 5,
407+ },
401 // ---- OpenAI Codex / ChatGPT subscription backend ----408 // ---- OpenAI Codex / ChatGPT subscription backend ----
402 // Same model ids as the OpenAI family, but routed through the409 // Same model ids as the OpenAI family, but routed through the
403 // ChatGPT Codex OAuth backend rather than api.openai.com.410 // ChatGPT Codex OAuth backend rather than api.openai.com.
@@ -454,6 +461,13 @@ var Catalog = []Model{
454 PriceTierInputTokens: 272000,461 PriceTierInputTokens: 272000,
455 PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.4, PriceCacheWriteAbove: 5,462 PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.4, PriceCacheWriteAbove: 5,
456 },463 },
464+ {
465+ Provider: "openai-codex", ID: "gpt-6.1-sol", DisplayName: "GPT-6.1 Sol",
466+ ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
467+ PriceInput: 2, PriceOutput: 10, PriceCacheRead: 0.1, PriceCacheWrite: 2.5,
468+ PriceTierInputTokens: 272000,
469+ PriceInputAbove: 4, PriceOutputAbove: 15, PriceCacheReadAbove: 0.2, PriceCacheWriteAbove: 5,
470+ },
457 {471 {
458 Provider: "openai-codex", ID: "gpt-6-luna", DisplayName: "GPT-6 Luna",472 Provider: "openai-codex", ID: "gpt-6-luna", DisplayName: "GPT-6 Luna",
459 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,473 ContextWindow: openAIContextWindowTarget, MaxOutput: 128000, Reasoning: true,
M packages/provider/openai_codex.go +1 -2
@@ -295,8 +295,7 @@ func (c *codexClient) findModel(id string) (Model, error) {
295295 }
296296
297297 func supportsOpenAIExplicitPromptCache(model string) bool {
298- id := strings.ToLower(strings.TrimSpace(model))
299- return strings.HasPrefix(id, "gpt-5.6-") || strings.HasPrefix(id, "gpt-6-")
298+ return isGPT56OrGPT6Family(model)
300299 }
301300
302301 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
@@ -295,8 +295,7 @@ func (c *codexClient) findModel(id string) (Model, error) {
295 }295 }
296 296
297 func supportsOpenAIExplicitPromptCache(model string) bool {297 func supportsOpenAIExplicitPromptCache(model string) bool {
298- id := strings.ToLower(strings.TrimSpace(model))298+ return isGPT56OrGPT6Family(model)
299- return strings.HasPrefix(id, "gpt-5.6-") || strings.HasPrefix(id, "gpt-6-")
300 }299 }
301 300
302 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {301 func (c *codexClient) buildRequest(req Request) (*codexRequest, error) {
M packages/provider/provider_test.go +2 -0
@@ -766,11 +766,13 @@ func TestGPT56AndGPT6CatalogEntries(t *testing.T) {
766766 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
767767 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
768768 {"openai", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
769+ {"openai", "gpt-6.1-sol", 500000, 2, 10, 0.1, 2.5},
769770 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
770771 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
771772 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
772773 {"openai-codex", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
773774 {"openai-codex", "gpt-6-sol", 500000, 2, 10, 0.2, 2.5},
775+ {"openai-codex", "gpt-6.1-sol", 500000, 2, 10, 0.1, 2.5},
774776 {"openai-codex", "gpt-6-luna", 500000, 0.1, 0.5, 0.01, 0.125},
775777 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},
776778 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},
@@ -766,11 +766,13 @@ func TestGPT56AndGPT6CatalogEntries(t *testing.T) {
766 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},766 {"openai", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
767 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},767 {"openai", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
768 {"openai", "gpt-6-astra", 500000, 10, 50, 1, 12.5},768 {"openai", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
769+ {"openai", "gpt-6.1-sol", 500000, 2, 10, 0.1, 2.5},
769 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},770 {"openai-codex", "gpt-5.6-luna", 500000, 1, 6, 0.1, 1.25},
770 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},771 {"openai-codex", "gpt-5.6-sol", 500000, 5, 30, 0.5, 6.25},
771 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},772 {"openai-codex", "gpt-5.6-terra", 500000, 2.5, 15, 0.25, 3.125},
772 {"openai-codex", "gpt-6-astra", 500000, 10, 50, 1, 12.5},773 {"openai-codex", "gpt-6-astra", 500000, 10, 50, 1, 12.5},
773 {"openai-codex", "gpt-6-sol", 500000, 2, 10, 0.2, 2.5},774 {"openai-codex", "gpt-6-sol", 500000, 2, 10, 0.2, 2.5},
775+ {"openai-codex", "gpt-6.1-sol", 500000, 2, 10, 0.1, 2.5},
774 {"openai-codex", "gpt-6-luna", 500000, 0.1, 0.5, 0.01, 0.125},776 {"openai-codex", "gpt-6-luna", 500000, 0.1, 0.5, 0.01, 0.125},
775 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},777 {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25},
776 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},778 {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25},
M packages/provider/reasoning.go +10 -2
@@ -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-") || strings.HasPrefix(id, "gpt-6-") {
61+ if isGPT56OrGPT6Family(id) {
6262 levels = append(levels, "max")
6363 }
6464 return levels
@@ -261,6 +261,14 @@ func OpenAICompatAnthropicEffort(level string) string {
261261 }
262262 }
263263
264+// isGPT56OrGPT6Family reports whether id belongs to the GPT-5.6 or GPT-6.x
265+// families (for example gpt-6-astra or gpt-6.1-sol), which share native max
266+// reasoning and explicit prompt-cache support.
267+func isGPT56OrGPT6Family(id string) bool {
268+ id = strings.ToLower(strings.TrimSpace(id))
269+ return strings.HasPrefix(id, "gpt-5.6-") || strings.HasPrefix(id, "gpt-6-") || strings.HasPrefix(id, "gpt-6.")
270+}
271+
264272 // OpenAICodexReasoningEffort maps zut levels onto the Responses API effort
265273 // enum. GPT-5.6 and GPT-6 support native max; other models clamp to xhigh.
266274 func OpenAICodexReasoningEffort(level, model string) string {
@@ -274,7 +282,7 @@ func OpenAICodexReasoningEffort(level, model string) string {
274282 case "xhigh":
275283 return "xhigh"
276284 case "max":
277- if strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") || strings.HasPrefix(strings.ToLower(model), "gpt-6-") {
285+ if isGPT56OrGPT6Family(model) {
278286 return "max"
279287 }
280288 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-") || strings.HasPrefix(id, "gpt-6-") {61+ if isGPT56OrGPT6Family(id) {
62 levels = append(levels, "max")62 levels = append(levels, "max")
63 }63 }
64 return levels64 return levels
@@ -261,6 +261,14 @@ func OpenAICompatAnthropicEffort(level string) string {
261 }261 }
262 }262 }
263 263
264+// isGPT56OrGPT6Family reports whether id belongs to the GPT-5.6 or GPT-6.x
265+// families (for example gpt-6-astra or gpt-6.1-sol), which share native max
266+// reasoning and explicit prompt-cache support.
267+func isGPT56OrGPT6Family(id string) bool {
268+ id = strings.ToLower(strings.TrimSpace(id))
269+ return strings.HasPrefix(id, "gpt-5.6-") || strings.HasPrefix(id, "gpt-6-") || strings.HasPrefix(id, "gpt-6.")
270+}
271+
264 // OpenAICodexReasoningEffort maps zut levels onto the Responses API effort272 // OpenAICodexReasoningEffort maps zut levels onto the Responses API effort
265 // enum. GPT-5.6 and GPT-6 support native max; other models clamp to xhigh.273 // enum. GPT-5.6 and GPT-6 support native max; other models clamp to xhigh.
266 func OpenAICodexReasoningEffort(level, model string) string {274 func OpenAICodexReasoningEffort(level, model string) string {
@@ -274,7 +282,7 @@ func OpenAICodexReasoningEffort(level, model string) string {
274 case "xhigh":282 case "xhigh":
275 return "xhigh"283 return "xhigh"
276 case "max":284 case "max":
277- if strings.HasPrefix(strings.ToLower(model), "gpt-5.6-") || strings.HasPrefix(strings.ToLower(model), "gpt-6-") {285+ if isGPT56OrGPT6Family(model) {
278 return "max"286 return "max"
279 }287 }
280 return "xhigh"288 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 476

↑↓ moveenter openesc close