Add Codex Luna and preview routing
- commit
- 9e3f5d24390f4a882ffeafd2844627ecb3d41949
- author
- patriceckhart · · 2026-07-09 18:42 UTC
- parent
- c6b35a2
- signature
- unsigned
- refs
- tag: v0.2.70
4 files · +139 -16
M packages/provider/models.go +6 -12
| @@ -316,18 +316,6 @@ var Catalog = []Model{ | |||
| 316 | 316 | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | |
| 317 | 317 | Speculative: true, | |
| 318 | 318 | }, | |
| 319 | - | { | |
| 320 | - | Provider: "openai", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", | |
| 321 | - | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |
| 322 | - | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | |
| 323 | - | Speculative: true, | |
| 324 | - | }, | |
| 325 | - | { | |
| 326 | - | Provider: "openai", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", | |
| 327 | - | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |
| 328 | - | PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, | |
| 329 | - | Speculative: true, | |
| 330 | - | }, | |
| 331 | 319 | ||
| 332 | 320 | // ---- OpenAI Codex / ChatGPT subscription backend ---- | |
| 333 | 321 | // Same model ids as the OpenAI family, but routed through the | |
| @@ -356,6 +344,12 @@ var Catalog = []Model{ | |||
| 356 | 344 | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | |
| 357 | 345 | Speculative: true, | |
| 358 | 346 | }, | |
| 347 | + | { | |
| 348 | + | Provider: "openai-codex", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", | |
| 349 | + | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |
| 350 | + | PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, | |
| 351 | + | Speculative: true, | |
| 352 | + | }, | |
| 359 | 353 | { | |
| 360 | 354 | Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", | |
| 361 | 355 | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |
| @@ -316,18 +316,6 @@ 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-sol", DisplayName: "GPT-5.6 Sol", | |||
| 321 | - | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |||
| 322 | - | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | |||
| 323 | - | Speculative: true, | |||
| 324 | - | }, | |||
| 325 | - | { | |||
| 326 | - | Provider: "openai", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", | |||
| 327 | - | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |||
| 328 | - | PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, | |||
| 329 | - | Speculative: true, | |||
| 330 | - | }, | |||
| 331 | 319 | ||||
| 332 | // ---- OpenAI Codex / ChatGPT subscription backend ---- | 320 | // ---- OpenAI Codex / ChatGPT subscription backend ---- | ||
| 333 | // Same model ids as the OpenAI family, but routed through the | 321 | // Same model ids as the OpenAI family, but routed through the | ||
| @@ -356,6 +344,12 @@ var Catalog = []Model{ | |||||
| 356 | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | 344 | PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, | ||
| 357 | Speculative: true, | 345 | Speculative: true, | ||
| 358 | }, | 346 | }, | ||
| 347 | + | { | |||
| 348 | + | Provider: "openai-codex", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", | |||
| 349 | + | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | |||
| 350 | + | PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, | |||
| 351 | + | Speculative: true, | |||
| 352 | + | }, | |||
| 359 | { | 353 | { | ||
| 360 | Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", | 354 | Provider: "openai-codex", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", | ||
| 361 | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | 355 | ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, | ||
M packages/provider/openai_codex.go +42 -2
| @@ -3,7 +3,9 @@ package provider | |||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | 5 | "context" | |
| 6 | + | "crypto/rand" | |
| 6 | 7 | "encoding/base64" | |
| 8 | + | "encoding/hex" | |
| 7 | 9 | "encoding/json" | |
| 8 | 10 | "fmt" | |
| 9 | 11 | "io" | |
| @@ -146,6 +148,7 @@ type codexRequest struct { | |||
| 146 | 148 | ParallelToolCalls bool `json:"parallel_tool_calls"` | |
| 147 | 149 | Include []string `json:"include,omitempty"` | |
| 148 | 150 | Reasoning *codexReasoningConfig `json:"reasoning,omitempty"` | |
| 151 | + | PromptCacheKey string `json:"prompt_cache_key,omitempty"` | |
| 149 | 152 | } | |
| 150 | 153 | ||
| 151 | 154 | // ---- Request building ---- | |
| @@ -310,6 +313,29 @@ func splitCallID(id string) (string, string) { | |||
| 310 | 313 | return id, "" | |
| 311 | 314 | } | |
| 312 | 315 | ||
| 316 | + | func usesCodexCLIRouting(model string) bool { | |
| 317 | + | switch model { | |
| 318 | + | case "gpt-5.6-luna", "gpt-5.6-luna-pro", "gpt-5.6-terra", "gpt-5.6-terra-pro": | |
| 319 | + | return true | |
| 320 | + | default: | |
| 321 | + | return false | |
| 322 | + | } | |
| 323 | + | } | |
| 324 | + | ||
| 325 | + | func newCodexSessionID() string { | |
| 326 | + | var b [16]byte | |
| 327 | + | if _, err := rand.Read(b[:]); err != nil { | |
| 328 | + | return fmt.Sprintf("zot-%d", time.Now().UnixNano()) | |
| 329 | + | } | |
| 330 | + | return strings.Join([]string{ | |
| 331 | + | hex.EncodeToString(b[0:4]), | |
| 332 | + | hex.EncodeToString(b[4:6]), | |
| 333 | + | hex.EncodeToString(b[6:8]), | |
| 334 | + | hex.EncodeToString(b[8:10]), | |
| 335 | + | hex.EncodeToString(b[10:16]), | |
| 336 | + | }, "-") | |
| 337 | + | } | |
| 338 | + | ||
| 313 | 339 | // ---- Streaming ---- | |
| 314 | 340 | ||
| 315 | 341 | func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, error) { | |
| @@ -317,6 +343,11 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||
| 317 | 343 | if err != nil { | |
| 318 | 344 | return nil, err | |
| 319 | 345 | } | |
| 346 | + | var codexCLISessionID string | |
| 347 | + | if usesCodexCLIRouting(wire.Model) { | |
| 348 | + | codexCLISessionID = newCodexSessionID() | |
| 349 | + | wire.PromptCacheKey = codexCLISessionID | |
| 350 | + | } | |
| 320 | 351 | body, err := json.Marshal(wire) | |
| 321 | 352 | if err != nil { | |
| 322 | 353 | return nil, err | |
| @@ -332,8 +363,17 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||
| 332 | 363 | httpReq.Header.Set("authorization", "Bearer "+c.token) | |
| 333 | 364 | httpReq.Header.Set("chatgpt-account-id", c.accountID) | |
| 334 | 365 | httpReq.Header.Set("openai-beta", "responses=experimental") | |
| 335 | - | httpReq.Header.Set("originator", "zot") | |
| 336 | - | httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH)) | |
| 366 | + | if codexCLISessionID != "" { | |
| 367 | + | // Some preview models are only admitted or reliably served by the | |
| 368 | + | // ChatGPT Codex backend when the request follows Codex CLI routing | |
| 369 | + | // metadata. Keep this narrow so Sol retains zot's proven shape. | |
| 370 | + | httpReq.Header.Set("originator", "codex_cli_rs") | |
| 371 | + | httpReq.Header.Set("session-id", codexCLISessionID) | |
| 372 | + | httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0") | |
| 373 | + | } else { | |
| 374 | + | httpReq.Header.Set("originator", "zot") | |
| 375 | + | httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH)) | |
| 376 | + | } | |
| 337 | 377 | return httpReq, nil | |
| 338 | 378 | } | |
| 339 | 379 | ||
| @@ -3,7 +3,9 @@ package provider | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | "context" | 5 | "context" | ||
| 6 | + | "crypto/rand" | |||
| 6 | "encoding/base64" | 7 | "encoding/base64" | ||
| 8 | + | "encoding/hex" | |||
| 7 | "encoding/json" | 9 | "encoding/json" | ||
| 8 | "fmt" | 10 | "fmt" | ||
| 9 | "io" | 11 | "io" | ||
| @@ -146,6 +148,7 @@ type codexRequest struct { | |||||
| 146 | ParallelToolCalls bool `json:"parallel_tool_calls"` | 148 | ParallelToolCalls bool `json:"parallel_tool_calls"` | ||
| 147 | Include []string `json:"include,omitempty"` | 149 | Include []string `json:"include,omitempty"` | ||
| 148 | Reasoning *codexReasoningConfig `json:"reasoning,omitempty"` | 150 | Reasoning *codexReasoningConfig `json:"reasoning,omitempty"` | ||
| 151 | + | PromptCacheKey string `json:"prompt_cache_key,omitempty"` | |||
| 149 | } | 152 | } | ||
| 150 | 153 | ||||
| 151 | // ---- Request building ---- | 154 | // ---- Request building ---- | ||
| @@ -310,6 +313,29 @@ func splitCallID(id string) (string, string) { | |||||
| 310 | return id, "" | 313 | return id, "" | ||
| 311 | } | 314 | } | ||
| 312 | 315 | ||||
| 316 | + | func usesCodexCLIRouting(model string) bool { | |||
| 317 | + | switch model { | |||
| 318 | + | case "gpt-5.6-luna", "gpt-5.6-luna-pro", "gpt-5.6-terra", "gpt-5.6-terra-pro": | |||
| 319 | + | return true | |||
| 320 | + | default: | |||
| 321 | + | return false | |||
| 322 | + | } | |||
| 323 | + | } | |||
| 324 | + | ||||
| 325 | + | func newCodexSessionID() string { | |||
| 326 | + | var b [16]byte | |||
| 327 | + | if _, err := rand.Read(b[:]); err != nil { | |||
| 328 | + | return fmt.Sprintf("zot-%d", time.Now().UnixNano()) | |||
| 329 | + | } | |||
| 330 | + | return strings.Join([]string{ | |||
| 331 | + | hex.EncodeToString(b[0:4]), | |||
| 332 | + | hex.EncodeToString(b[4:6]), | |||
| 333 | + | hex.EncodeToString(b[6:8]), | |||
| 334 | + | hex.EncodeToString(b[8:10]), | |||
| 335 | + | hex.EncodeToString(b[10:16]), | |||
| 336 | + | }, "-") | |||
| 337 | + | } | |||
| 338 | + | ||||
| 313 | // ---- Streaming ---- | 339 | // ---- Streaming ---- | ||
| 314 | 340 | ||||
| 315 | func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, error) { | 341 | func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, error) { | ||
| @@ -317,6 +343,11 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||||
| 317 | if err != nil { | 343 | if err != nil { | ||
| 318 | return nil, err | 344 | return nil, err | ||
| 319 | } | 345 | } | ||
| 346 | + | var codexCLISessionID string | |||
| 347 | + | if usesCodexCLIRouting(wire.Model) { | |||
| 348 | + | codexCLISessionID = newCodexSessionID() | |||
| 349 | + | wire.PromptCacheKey = codexCLISessionID | |||
| 350 | + | } | |||
| 320 | body, err := json.Marshal(wire) | 351 | body, err := json.Marshal(wire) | ||
| 321 | if err != nil { | 352 | if err != nil { | ||
| 322 | return nil, err | 353 | return nil, err | ||
| @@ -332,8 +363,17 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||||
| 332 | httpReq.Header.Set("authorization", "Bearer "+c.token) | 363 | httpReq.Header.Set("authorization", "Bearer "+c.token) | ||
| 333 | httpReq.Header.Set("chatgpt-account-id", c.accountID) | 364 | httpReq.Header.Set("chatgpt-account-id", c.accountID) | ||
| 334 | httpReq.Header.Set("openai-beta", "responses=experimental") | 365 | httpReq.Header.Set("openai-beta", "responses=experimental") | ||
| 335 | - | httpReq.Header.Set("originator", "zot") | 366 | + | if codexCLISessionID != "" { |
| 336 | - | httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH)) | 367 | + | // Some preview models are only admitted or reliably served by the |
| 368 | + | // ChatGPT Codex backend when the request follows Codex CLI routing | |||
| 369 | + | // metadata. Keep this narrow so Sol retains zot's proven shape. | |||
| 370 | + | httpReq.Header.Set("originator", "codex_cli_rs") | |||
| 371 | + | httpReq.Header.Set("session-id", codexCLISessionID) | |||
| 372 | + | httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0") | |||
| 373 | + | } else { | |||
| 374 | + | httpReq.Header.Set("originator", "zot") | |||
| 375 | + | httpReq.Header.Set("user-agent", fmt.Sprintf("zot (%s %s)", runtime.GOOS, runtime.GOARCH)) | |||
| 376 | + | } | |||
| 337 | return httpReq, nil | 377 | return httpReq, nil | ||
| 338 | } | 378 | } | ||
| 339 | 379 | ||||
M packages/provider/openai_codex_test.go +90 -0
| @@ -1,6 +1,11 @@ | |||
| 1 | 1 | package provider | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "context" | |
| 6 | + | "encoding/json" | |
| 7 | + | "io" | |
| 8 | + | "net/http" | |
| 4 | 9 | "strings" | |
| 5 | 10 | "testing" | |
| 6 | 11 | ) | |
| @@ -61,3 +66,88 @@ func TestCodexImageToolResultMirror(t *testing.T) { | |||
| 61 | 66 | t.Fatalf("mirrored user image was not serialized as input_image") | |
| 62 | 67 | } | |
| 63 | 68 | } | |
| 69 | + | ||
| 70 | + | type roundTripFunc func(*http.Request) (*http.Response, error) | |
| 71 | + | ||
| 72 | + | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } | |
| 73 | + | ||
| 74 | + | func TestCodexPreviewModelUsesCodexCLIShape(t *testing.T) { | |
| 75 | + | c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient) | |
| 76 | + | var gotReq *http.Request | |
| 77 | + | var gotBody codexRequest | |
| 78 | + | c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |
| 79 | + | gotReq = r | |
| 80 | + | if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |
| 81 | + | t.Fatal(err) | |
| 82 | + | } | |
| 83 | + | return &http.Response{ | |
| 84 | + | StatusCode: http.StatusOK, | |
| 85 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |
| 86 | + | Body: io.NopCloser(strings.NewReader("")), | |
| 87 | + | }, nil | |
| 88 | + | }) | |
| 89 | + | ||
| 90 | + | events, err := c.Stream(context.Background(), Request{ | |
| 91 | + | Model: "gpt-5.6-terra", | |
| 92 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |
| 93 | + | }) | |
| 94 | + | if err != nil { | |
| 95 | + | t.Fatal(err) | |
| 96 | + | } | |
| 97 | + | for range events { | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | if gotReq == nil { | |
| 101 | + | t.Fatal("request was not sent") | |
| 102 | + | } | |
| 103 | + | if gotReq.Header.Get("originator") != "codex_cli_rs" { | |
| 104 | + | t.Fatalf("originator = %q", gotReq.Header.Get("originator")) | |
| 105 | + | } | |
| 106 | + | if gotReq.Header.Get("user-agent") != "codex_cli_rs/0.0.0" { | |
| 107 | + | t.Fatalf("user-agent = %q", gotReq.Header.Get("user-agent")) | |
| 108 | + | } | |
| 109 | + | if gotBody.PromptCacheKey == "" { | |
| 110 | + | t.Fatal("prompt_cache_key was not set") | |
| 111 | + | } | |
| 112 | + | if gotReq.Header.Get("session-id") != gotBody.PromptCacheKey { | |
| 113 | + | t.Fatalf("session-id = %q, prompt_cache_key = %q", gotReq.Header.Get("session-id"), gotBody.PromptCacheKey) | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | func TestCodexSolKeepsZotShape(t *testing.T) { | |
| 118 | + | c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient) | |
| 119 | + | var gotReq *http.Request | |
| 120 | + | var body bytes.Buffer | |
| 121 | + | c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |
| 122 | + | gotReq = r | |
| 123 | + | _, _ = body.ReadFrom(r.Body) | |
| 124 | + | return &http.Response{ | |
| 125 | + | StatusCode: http.StatusOK, | |
| 126 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |
| 127 | + | Body: io.NopCloser(strings.NewReader("")), | |
| 128 | + | }, nil | |
| 129 | + | }) | |
| 130 | + | ||
| 131 | + | events, err := c.Stream(context.Background(), Request{ | |
| 132 | + | Model: "gpt-5.6-sol", | |
| 133 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |
| 134 | + | }) | |
| 135 | + | if err != nil { | |
| 136 | + | t.Fatal(err) | |
| 137 | + | } | |
| 138 | + | for range events { | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | if gotReq == nil { | |
| 142 | + | t.Fatal("request was not sent") | |
| 143 | + | } | |
| 144 | + | if gotReq.Header.Get("originator") != "zot" { | |
| 145 | + | t.Fatalf("originator = %q", gotReq.Header.Get("originator")) | |
| 146 | + | } | |
| 147 | + | if gotReq.Header.Get("session-id") != "" { | |
| 148 | + | t.Fatalf("session-id = %q", gotReq.Header.Get("session-id")) | |
| 149 | + | } | |
| 150 | + | if strings.Contains(body.String(), "prompt_cache_key") { | |
| 151 | + | t.Fatalf("Sol request unexpectedly included prompt_cache_key: %s", body.String()) | |
| 152 | + | } | |
| 153 | + | } | |
| @@ -1,6 +1,11 @@ | |||||
| 1 | package provider | 1 | package provider | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "bytes" | |||
| 5 | + | "context" | |||
| 6 | + | "encoding/json" | |||
| 7 | + | "io" | |||
| 8 | + | "net/http" | |||
| 4 | "strings" | 9 | "strings" | ||
| 5 | "testing" | 10 | "testing" | ||
| 6 | ) | 11 | ) | ||
| @@ -61,3 +66,88 @@ func TestCodexImageToolResultMirror(t *testing.T) { | |||||
| 61 | t.Fatalf("mirrored user image was not serialized as input_image") | 66 | t.Fatalf("mirrored user image was not serialized as input_image") | ||
| 62 | } | 67 | } | ||
| 63 | } | 68 | } | ||
| 69 | + | ||||
| 70 | + | type roundTripFunc func(*http.Request) (*http.Response, error) | |||
| 71 | + | ||||
| 72 | + | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } | |||
| 73 | + | ||||
| 74 | + | func TestCodexPreviewModelUsesCodexCLIShape(t *testing.T) { | |||
| 75 | + | c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient) | |||
| 76 | + | var gotReq *http.Request | |||
| 77 | + | var gotBody codexRequest | |||
| 78 | + | c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |||
| 79 | + | gotReq = r | |||
| 80 | + | if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |||
| 81 | + | t.Fatal(err) | |||
| 82 | + | } | |||
| 83 | + | return &http.Response{ | |||
| 84 | + | StatusCode: http.StatusOK, | |||
| 85 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |||
| 86 | + | Body: io.NopCloser(strings.NewReader("")), | |||
| 87 | + | }, nil | |||
| 88 | + | }) | |||
| 89 | + | ||||
| 90 | + | events, err := c.Stream(context.Background(), Request{ | |||
| 91 | + | Model: "gpt-5.6-terra", | |||
| 92 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |||
| 93 | + | }) | |||
| 94 | + | if err != nil { | |||
| 95 | + | t.Fatal(err) | |||
| 96 | + | } | |||
| 97 | + | for range events { | |||
| 98 | + | } | |||
| 99 | + | ||||
| 100 | + | if gotReq == nil { | |||
| 101 | + | t.Fatal("request was not sent") | |||
| 102 | + | } | |||
| 103 | + | if gotReq.Header.Get("originator") != "codex_cli_rs" { | |||
| 104 | + | t.Fatalf("originator = %q", gotReq.Header.Get("originator")) | |||
| 105 | + | } | |||
| 106 | + | if gotReq.Header.Get("user-agent") != "codex_cli_rs/0.0.0" { | |||
| 107 | + | t.Fatalf("user-agent = %q", gotReq.Header.Get("user-agent")) | |||
| 108 | + | } | |||
| 109 | + | if gotBody.PromptCacheKey == "" { | |||
| 110 | + | t.Fatal("prompt_cache_key was not set") | |||
| 111 | + | } | |||
| 112 | + | if gotReq.Header.Get("session-id") != gotBody.PromptCacheKey { | |||
| 113 | + | t.Fatalf("session-id = %q, prompt_cache_key = %q", gotReq.Header.Get("session-id"), gotBody.PromptCacheKey) | |||
| 114 | + | } | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | func TestCodexSolKeepsZotShape(t *testing.T) { | |||
| 118 | + | c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient) | |||
| 119 | + | var gotReq *http.Request | |||
| 120 | + | var body bytes.Buffer | |||
| 121 | + | c.http.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |||
| 122 | + | gotReq = r | |||
| 123 | + | _, _ = body.ReadFrom(r.Body) | |||
| 124 | + | return &http.Response{ | |||
| 125 | + | StatusCode: http.StatusOK, | |||
| 126 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |||
| 127 | + | Body: io.NopCloser(strings.NewReader("")), | |||
| 128 | + | }, nil | |||
| 129 | + | }) | |||
| 130 | + | ||||
| 131 | + | events, err := c.Stream(context.Background(), Request{ | |||
| 132 | + | Model: "gpt-5.6-sol", | |||
| 133 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |||
| 134 | + | }) | |||
| 135 | + | if err != nil { | |||
| 136 | + | t.Fatal(err) | |||
| 137 | + | } | |||
| 138 | + | for range events { | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | if gotReq == nil { | |||
| 142 | + | t.Fatal("request was not sent") | |||
| 143 | + | } | |||
| 144 | + | if gotReq.Header.Get("originator") != "zot" { | |||
| 145 | + | t.Fatalf("originator = %q", gotReq.Header.Get("originator")) | |||
| 146 | + | } | |||
| 147 | + | if gotReq.Header.Get("session-id") != "" { | |||
| 148 | + | t.Fatalf("session-id = %q", gotReq.Header.Get("session-id")) | |||
| 149 | + | } | |||
| 150 | + | if strings.Contains(body.String(), "prompt_cache_key") { | |||
| 151 | + | t.Fatalf("Sol request unexpectedly included prompt_cache_key: %s", body.String()) | |||
| 152 | + | } | |||
| 153 | + | } | |||
M packages/provider/provider_test.go +1 -2
| @@ -393,8 +393,7 @@ func TestGPT56CatalogEntries(t *testing.T) { | |||
| 393 | 393 | priceOut float64 | |
| 394 | 394 | cacheRead float64 | |
| 395 | 395 | }{ | |
| 396 | - | {"openai", "gpt-5.6-sol", 5, 30, 0.5}, | |
| 397 | - | {"openai", "gpt-5.6-terra", 2.5, 15, 0.25}, | |
| 396 | + | {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1}, | |
| 398 | 397 | {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5}, | |
| 399 | 398 | {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25}, | |
| 400 | 399 | } | |
| @@ -393,8 +393,7 @@ func TestGPT56CatalogEntries(t *testing.T) { | |||||
| 393 | priceOut float64 | 393 | priceOut float64 | ||
| 394 | cacheRead float64 | 394 | cacheRead float64 | ||
| 395 | }{ | 395 | }{ | ||
| 396 | - | {"openai", "gpt-5.6-sol", 5, 30, 0.5}, | 396 | + | {"openai-codex", "gpt-5.6-luna", 1, 6, 0.1}, |
| 397 | - | {"openai", "gpt-5.6-terra", 2.5, 15, 0.25}, | |||
| 398 | {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5}, | 397 | {"openai-codex", "gpt-5.6-sol", 5, 30, 0.5}, | ||
| 399 | {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25}, | 398 | {"openai-codex", "gpt-5.6-terra", 2.5, 15, 0.25}, | ||
| 400 | } | 399 | } | ||