Add GPT-5.6 to Azure and Vercel providers
- commit
- dbd0dfa9d4f1f2ad863881da93884a7b6be49dcb
- author
- patriceckhart · · 2026-07-10 05:56 UTC
- parent
- 1fa7669
- signature
- unsigned
- refs
- tag: v0.2.73
8 files · +207 -31
M docs/providers.md +7 -3
| @@ -162,12 +162,16 @@ zot --provider cloudflare-workers-ai | |||
| 162 | 162 | ```bash | |
| 163 | 163 | export AZURE_OPENAI_API_KEY=... | |
| 164 | 164 | export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com | |
| 165 | - | export AZURE_OPENAI_API_VERSION=2024-02-01 # optional | |
| 165 | + | export AZURE_OPENAI_API_VERSION=v1 # optional, v1 is the default | |
| 166 | 166 | zot --provider azure-openai-responses | |
| 167 | 167 | ``` | |
| 168 | 168 | ||
| 169 | - | If your Azure deployment names differ from zot model IDs, add model overrides | |
| 170 | - | in `$ZOT_HOME/models.json`. | |
| 169 | + | The provider uses Azure's Responses API. If deployment names differ from zot | |
| 170 | + | model IDs, map them without changing the catalog: | |
| 171 | + | ||
| 172 | + | ```bash | |
| 173 | + | export AZURE_OPENAI_DEPLOYMENT_NAME_MAP='gpt-5.6-luna=luna-preview,gpt-5.6-sol=sol-preview' | |
| 174 | + | ``` | |
| 171 | 175 | ||
| 172 | 176 | ## Auth file | |
| 173 | 177 | ||
| @@ -162,12 +162,16 @@ zot --provider cloudflare-workers-ai | |||||
| 162 | ```bash | 162 | ```bash | ||
| 163 | export AZURE_OPENAI_API_KEY=... | 163 | export AZURE_OPENAI_API_KEY=... | ||
| 164 | export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com | 164 | export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com | ||
| 165 | - | export AZURE_OPENAI_API_VERSION=2024-02-01 # optional | 165 | + | export AZURE_OPENAI_API_VERSION=v1 # optional, v1 is the default |
| 166 | zot --provider azure-openai-responses | 166 | zot --provider azure-openai-responses | ||
| 167 | ``` | 167 | ``` | ||
| 168 | 168 | ||||
| 169 | - | If your Azure deployment names differ from zot model IDs, add model overrides | 169 | + | The provider uses Azure's Responses API. If deployment names differ from zot |
| 170 | - | in `$ZOT_HOME/models.json`. | 170 | + | model IDs, map them without changing the catalog: |
| 171 | + | ||||
| 172 | + | ```bash | |||
| 173 | + | export AZURE_OPENAI_DEPLOYMENT_NAME_MAP='gpt-5.6-luna=luna-preview,gpt-5.6-sol=sol-preview' | |||
| 174 | + | ``` | |||
| 171 | 175 | ||||
| 172 | ## Auth file | 176 | ## Auth file | ||
| 173 | 177 | ||||
M packages/provider/azure_openai.go +90 -1
| @@ -30,7 +30,10 @@ import ( | |||
| 30 | 30 | "strings" | |
| 31 | 31 | ) | |
| 32 | 32 | ||
| 33 | - | const defaultAzureAPIVersion = "2024-10-21" | |
| 33 | + | const ( | |
| 34 | + | defaultAzureAPIVersion = "2024-10-21" | |
| 35 | + | defaultAzureResponsesAPIVersion = "v1" | |
| 36 | + | ) | |
| 34 | 37 | ||
| 35 | 38 | // azureRewriteTransport rewrites every outgoing OpenAI Chat Completions | |
| 36 | 39 | // URL into the Azure deployment-scoped shape: | |
| @@ -134,3 +137,89 @@ func NewAzureOpenAI(apiKey, baseURL string) Client { | |||
| 134 | 137 | http: httpClient, | |
| 135 | 138 | } | |
| 136 | 139 | } | |
| 140 | + | ||
| 141 | + | // azureResponsesTransport applies Azure API-key authentication and API | |
| 142 | + | // versioning to requests using the OpenAI Responses wire format. | |
| 143 | + | type azureResponsesTransport struct { | |
| 144 | + | inner http.RoundTripper | |
| 145 | + | apiKey string | |
| 146 | + | apiVersion string | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | func (t *azureResponsesTransport) RoundTrip(req *http.Request) (*http.Response, error) { | |
| 150 | + | clone := req.Clone(req.Context()) | |
| 151 | + | clone.Header.Del("authorization") | |
| 152 | + | clone.Header.Del("chatgpt-account-id") | |
| 153 | + | clone.Header.Del("openai-beta") | |
| 154 | + | clone.Header.Del("originator") | |
| 155 | + | clone.Header.Set("api-key", t.apiKey) | |
| 156 | + | q := clone.URL.Query() | |
| 157 | + | q.Set("api-version", t.apiVersion) | |
| 158 | + | clone.URL.RawQuery = q.Encode() | |
| 159 | + | return t.inner.RoundTrip(clone) | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | // NewAzureOpenAIResponses creates an Azure OpenAI Responses API client. | |
| 163 | + | // The configured model id is used as the deployment name unless overridden | |
| 164 | + | // through AZURE_OPENAI_DEPLOYMENT_NAME_MAP (model=deployment pairs). | |
| 165 | + | func NewAzureOpenAIResponses(apiKey, baseURL string) Client { | |
| 166 | + | if baseURL == "" { | |
| 167 | + | baseURL = os.Getenv("AZURE_OPENAI_BASE_URL") | |
| 168 | + | if baseURL == "" { | |
| 169 | + | if resource := os.Getenv("AZURE_OPENAI_RESOURCE_NAME"); resource != "" { | |
| 170 | + | baseURL = "https://" + resource + ".openai.azure.com" | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | } | |
| 174 | + | if baseURL == "" { | |
| 175 | + | return &unimplementedClient{ | |
| 176 | + | name: "azure-openai-responses", | |
| 177 | + | hint: "set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME (or pass --base-url)", | |
| 178 | + | } | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | endpoint := strings.TrimRight(baseURL, "/") | |
| 182 | + | switch { | |
| 183 | + | case strings.HasSuffix(endpoint, "/openai/v1/responses"): | |
| 184 | + | case strings.HasSuffix(endpoint, "/openai/v1"): | |
| 185 | + | endpoint += "/responses" | |
| 186 | + | case strings.HasSuffix(endpoint, "/openai"): | |
| 187 | + | endpoint += "/v1/responses" | |
| 188 | + | default: | |
| 189 | + | endpoint += "/openai/v1/responses" | |
| 190 | + | } | |
| 191 | + | apiVersion := os.Getenv("AZURE_OPENAI_API_VERSION") | |
| 192 | + | if apiVersion == "" { | |
| 193 | + | apiVersion = defaultAzureResponsesAPIVersion | |
| 194 | + | } | |
| 195 | + | httpClient := &http.Client{ | |
| 196 | + | Transport: &azureResponsesTransport{ | |
| 197 | + | inner: http.DefaultTransport, | |
| 198 | + | apiKey: apiKey, | |
| 199 | + | apiVersion: apiVersion, | |
| 200 | + | }, | |
| 201 | + | Timeout: 0, | |
| 202 | + | } | |
| 203 | + | inner := &codexClient{ | |
| 204 | + | token: apiKey, | |
| 205 | + | baseURL: endpoint, | |
| 206 | + | errorLabel: "azure openai", | |
| 207 | + | providerName: "azure-openai-responses", | |
| 208 | + | modelName: azureDeploymentName, | |
| 209 | + | disableCLIRouting: true, | |
| 210 | + | http: httpClient, | |
| 211 | + | } | |
| 212 | + | return &renamedClient{inner: inner, name: "azure-openai-responses"} | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | func azureDeploymentName(model string) string { | |
| 216 | + | for _, pair := range strings.Split(os.Getenv("AZURE_OPENAI_DEPLOYMENT_NAME_MAP"), ",") { | |
| 217 | + | parts := strings.SplitN(strings.TrimSpace(pair), "=", 2) | |
| 218 | + | if len(parts) == 2 && strings.TrimSpace(parts[0]) == model { | |
| 219 | + | if deployment := strings.TrimSpace(parts[1]); deployment != "" { | |
| 220 | + | return deployment | |
| 221 | + | } | |
| 222 | + | } | |
| 223 | + | } | |
| 224 | + | return model | |
| 225 | + | } | |
| @@ -30,7 +30,10 @@ import ( | |||||
| 30 | "strings" | 30 | "strings" | ||
| 31 | ) | 31 | ) | ||
| 32 | 32 | ||||
| 33 | - | const defaultAzureAPIVersion = "2024-10-21" | 33 | + | const ( |
| 34 | + | defaultAzureAPIVersion = "2024-10-21" | |||
| 35 | + | defaultAzureResponsesAPIVersion = "v1" | |||
| 36 | + | ) | |||
| 34 | 37 | ||||
| 35 | // azureRewriteTransport rewrites every outgoing OpenAI Chat Completions | 38 | // azureRewriteTransport rewrites every outgoing OpenAI Chat Completions | ||
| 36 | // URL into the Azure deployment-scoped shape: | 39 | // URL into the Azure deployment-scoped shape: | ||
| @@ -134,3 +137,89 @@ func NewAzureOpenAI(apiKey, baseURL string) Client { | |||||
| 134 | http: httpClient, | 137 | http: httpClient, | ||
| 135 | } | 138 | } | ||
| 136 | } | 139 | } | ||
| 140 | + | ||||
| 141 | + | // azureResponsesTransport applies Azure API-key authentication and API | |||
| 142 | + | // versioning to requests using the OpenAI Responses wire format. | |||
| 143 | + | type azureResponsesTransport struct { | |||
| 144 | + | inner http.RoundTripper | |||
| 145 | + | apiKey string | |||
| 146 | + | apiVersion string | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | func (t *azureResponsesTransport) RoundTrip(req *http.Request) (*http.Response, error) { | |||
| 150 | + | clone := req.Clone(req.Context()) | |||
| 151 | + | clone.Header.Del("authorization") | |||
| 152 | + | clone.Header.Del("chatgpt-account-id") | |||
| 153 | + | clone.Header.Del("openai-beta") | |||
| 154 | + | clone.Header.Del("originator") | |||
| 155 | + | clone.Header.Set("api-key", t.apiKey) | |||
| 156 | + | q := clone.URL.Query() | |||
| 157 | + | q.Set("api-version", t.apiVersion) | |||
| 158 | + | clone.URL.RawQuery = q.Encode() | |||
| 159 | + | return t.inner.RoundTrip(clone) | |||
| 160 | + | } | |||
| 161 | + | ||||
| 162 | + | // NewAzureOpenAIResponses creates an Azure OpenAI Responses API client. | |||
| 163 | + | // The configured model id is used as the deployment name unless overridden | |||
| 164 | + | // through AZURE_OPENAI_DEPLOYMENT_NAME_MAP (model=deployment pairs). | |||
| 165 | + | func NewAzureOpenAIResponses(apiKey, baseURL string) Client { | |||
| 166 | + | if baseURL == "" { | |||
| 167 | + | baseURL = os.Getenv("AZURE_OPENAI_BASE_URL") | |||
| 168 | + | if baseURL == "" { | |||
| 169 | + | if resource := os.Getenv("AZURE_OPENAI_RESOURCE_NAME"); resource != "" { | |||
| 170 | + | baseURL = "https://" + resource + ".openai.azure.com" | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | } | |||
| 174 | + | if baseURL == "" { | |||
| 175 | + | return &unimplementedClient{ | |||
| 176 | + | name: "azure-openai-responses", | |||
| 177 | + | hint: "set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME (or pass --base-url)", | |||
| 178 | + | } | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | endpoint := strings.TrimRight(baseURL, "/") | |||
| 182 | + | switch { | |||
| 183 | + | case strings.HasSuffix(endpoint, "/openai/v1/responses"): | |||
| 184 | + | case strings.HasSuffix(endpoint, "/openai/v1"): | |||
| 185 | + | endpoint += "/responses" | |||
| 186 | + | case strings.HasSuffix(endpoint, "/openai"): | |||
| 187 | + | endpoint += "/v1/responses" | |||
| 188 | + | default: | |||
| 189 | + | endpoint += "/openai/v1/responses" | |||
| 190 | + | } | |||
| 191 | + | apiVersion := os.Getenv("AZURE_OPENAI_API_VERSION") | |||
| 192 | + | if apiVersion == "" { | |||
| 193 | + | apiVersion = defaultAzureResponsesAPIVersion | |||
| 194 | + | } | |||
| 195 | + | httpClient := &http.Client{ | |||
| 196 | + | Transport: &azureResponsesTransport{ | |||
| 197 | + | inner: http.DefaultTransport, | |||
| 198 | + | apiKey: apiKey, | |||
| 199 | + | apiVersion: apiVersion, | |||
| 200 | + | }, | |||
| 201 | + | Timeout: 0, | |||
| 202 | + | } | |||
| 203 | + | inner := &codexClient{ | |||
| 204 | + | token: apiKey, | |||
| 205 | + | baseURL: endpoint, | |||
| 206 | + | errorLabel: "azure openai", | |||
| 207 | + | providerName: "azure-openai-responses", | |||
| 208 | + | modelName: azureDeploymentName, | |||
| 209 | + | disableCLIRouting: true, | |||
| 210 | + | http: httpClient, | |||
| 211 | + | } | |||
| 212 | + | return &renamedClient{inner: inner, name: "azure-openai-responses"} | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | func azureDeploymentName(model string) string { | |||
| 216 | + | for _, pair := range strings.Split(os.Getenv("AZURE_OPENAI_DEPLOYMENT_NAME_MAP"), ",") { | |||
| 217 | + | parts := strings.SplitN(strings.TrimSpace(pair), "=", 2) | |||
| 218 | + | if len(parts) == 2 && strings.TrimSpace(parts[0]) == model { | |||
| 219 | + | if deployment := strings.TrimSpace(parts[1]); deployment != "" { | |||
| 220 | + | return deployment | |||
| 221 | + | } | |||
| 222 | + | } | |||
| 223 | + | } | |||
| 224 | + | return model | |||
| 225 | + | } | |||
A packages/provider/azure_openai_test.go +69 -0
| @@ -0,0 +1,69 @@ | |||
| 1 | + | package provider | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | "io" | |
| 7 | + | "net/http" | |
| 8 | + | "strings" | |
| 9 | + | "testing" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func TestAzureOpenAIResponsesRequestShape(t *testing.T) { | |
| 13 | + | t.Setenv("AZURE_OPENAI_API_VERSION", "v1") | |
| 14 | + | t.Setenv("AZURE_OPENAI_DEPLOYMENT_NAME_MAP", "gpt-5.6-sol=sol-preview") | |
| 15 | + | ||
| 16 | + | named := NewAzureOpenAIResponses("azure-key", "https://example.openai.azure.com").(*renamedClient) | |
| 17 | + | c := named.inner.(*codexClient) | |
| 18 | + | transport := c.http.Transport.(*azureResponsesTransport) | |
| 19 | + | var gotReq *http.Request | |
| 20 | + | var gotBody codexRequest | |
| 21 | + | transport.inner = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |
| 22 | + | gotReq = r | |
| 23 | + | if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |
| 24 | + | t.Fatal(err) | |
| 25 | + | } | |
| 26 | + | return &http.Response{ | |
| 27 | + | StatusCode: http.StatusOK, | |
| 28 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |
| 29 | + | Body: io.NopCloser(strings.NewReader("")), | |
| 30 | + | }, nil | |
| 31 | + | }) | |
| 32 | + | ||
| 33 | + | events, err := c.Stream(context.Background(), Request{ | |
| 34 | + | Model: "gpt-5.6-sol", | |
| 35 | + | Reasoning: "max", | |
| 36 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |
| 37 | + | }) | |
| 38 | + | if err != nil { | |
| 39 | + | t.Fatal(err) | |
| 40 | + | } | |
| 41 | + | var startProvider string | |
| 42 | + | for event := range events { | |
| 43 | + | if start, ok := event.(EventStart); ok { | |
| 44 | + | startProvider = start.Provider | |
| 45 | + | } | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | if startProvider != "azure-openai-responses" { | |
| 49 | + | t.Fatalf("start provider = %q", startProvider) | |
| 50 | + | } | |
| 51 | + | if gotReq == nil { | |
| 52 | + | t.Fatal("request was not sent") | |
| 53 | + | } | |
| 54 | + | if gotReq.URL.String() != "https://example.openai.azure.com/openai/v1/responses?api-version=v1" { | |
| 55 | + | t.Fatalf("url = %q", gotReq.URL.String()) | |
| 56 | + | } | |
| 57 | + | if gotReq.Header.Get("api-key") != "azure-key" || gotReq.Header.Get("authorization") != "" { | |
| 58 | + | t.Fatalf("headers = %#v", gotReq.Header) | |
| 59 | + | } | |
| 60 | + | if gotBody.Model != "sol-preview" { | |
| 61 | + | t.Fatalf("model = %q", gotBody.Model) | |
| 62 | + | } | |
| 63 | + | if gotBody.Reasoning == nil || gotBody.Reasoning.Effort != "max" { | |
| 64 | + | t.Fatalf("reasoning = %+v", gotBody.Reasoning) | |
| 65 | + | } | |
| 66 | + | if gotBody.PromptCacheKey != "" { | |
| 67 | + | t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey) | |
| 68 | + | } | |
| 69 | + | } | |
| @@ -0,0 +1,69 @@ | |||||
| 1 | + | package provider | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | "io" | |||
| 7 | + | "net/http" | |||
| 8 | + | "strings" | |||
| 9 | + | "testing" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func TestAzureOpenAIResponsesRequestShape(t *testing.T) { | |||
| 13 | + | t.Setenv("AZURE_OPENAI_API_VERSION", "v1") | |||
| 14 | + | t.Setenv("AZURE_OPENAI_DEPLOYMENT_NAME_MAP", "gpt-5.6-sol=sol-preview") | |||
| 15 | + | ||||
| 16 | + | named := NewAzureOpenAIResponses("azure-key", "https://example.openai.azure.com").(*renamedClient) | |||
| 17 | + | c := named.inner.(*codexClient) | |||
| 18 | + | transport := c.http.Transport.(*azureResponsesTransport) | |||
| 19 | + | var gotReq *http.Request | |||
| 20 | + | var gotBody codexRequest | |||
| 21 | + | transport.inner = roundTripFunc(func(r *http.Request) (*http.Response, error) { | |||
| 22 | + | gotReq = r | |||
| 23 | + | if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |||
| 24 | + | t.Fatal(err) | |||
| 25 | + | } | |||
| 26 | + | return &http.Response{ | |||
| 27 | + | StatusCode: http.StatusOK, | |||
| 28 | + | Header: http.Header{"Content-Type": []string{"text/event-stream"}}, | |||
| 29 | + | Body: io.NopCloser(strings.NewReader("")), | |||
| 30 | + | }, nil | |||
| 31 | + | }) | |||
| 32 | + | ||||
| 33 | + | events, err := c.Stream(context.Background(), Request{ | |||
| 34 | + | Model: "gpt-5.6-sol", | |||
| 35 | + | Reasoning: "max", | |||
| 36 | + | Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hi"}}}}, | |||
| 37 | + | }) | |||
| 38 | + | if err != nil { | |||
| 39 | + | t.Fatal(err) | |||
| 40 | + | } | |||
| 41 | + | var startProvider string | |||
| 42 | + | for event := range events { | |||
| 43 | + | if start, ok := event.(EventStart); ok { | |||
| 44 | + | startProvider = start.Provider | |||
| 45 | + | } | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | if startProvider != "azure-openai-responses" { | |||
| 49 | + | t.Fatalf("start provider = %q", startProvider) | |||
| 50 | + | } | |||
| 51 | + | if gotReq == nil { | |||
| 52 | + | t.Fatal("request was not sent") | |||
| 53 | + | } | |||
| 54 | + | if gotReq.URL.String() != "https://example.openai.azure.com/openai/v1/responses?api-version=v1" { | |||
| 55 | + | t.Fatalf("url = %q", gotReq.URL.String()) | |||
| 56 | + | } | |||
| 57 | + | if gotReq.Header.Get("api-key") != "azure-key" || gotReq.Header.Get("authorization") != "" { | |||
| 58 | + | t.Fatalf("headers = %#v", gotReq.Header) | |||
| 59 | + | } | |||
| 60 | + | if gotBody.Model != "sol-preview" { | |||
| 61 | + | t.Fatalf("model = %q", gotBody.Model) | |||
| 62 | + | } | |||
| 63 | + | if gotBody.Reasoning == nil || gotBody.Reasoning.Effort != "max" { | |||
| 64 | + | t.Fatalf("reasoning = %+v", gotBody.Reasoning) | |||
| 65 | + | } | |||
| 66 | + | if gotBody.PromptCacheKey != "" { | |||
| 67 | + | t.Fatalf("prompt_cache_key = %q", gotBody.PromptCacheKey) | |||
| 68 | + | } | |||
| 69 | + | } | |||
M packages/provider/catalog_builtin.go +6 -0
| @@ -466,6 +466,9 @@ var builtinCatalog = []Model{ | |||
| 466 | 466 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.4-pro", DisplayName: "GPT 5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 467 | 467 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5", DisplayName: "GPT 5.5", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 468 | 468 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5-pro", DisplayName: "GPT 5.5 Pro", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 469 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-luna", DisplayName: "GPT 5.6 Luna", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 470 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-sol", DisplayName: "GPT 5.6 Sol", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 471 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-terra", DisplayName: "GPT 5.6 Terra", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 469 | 472 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-20b", DisplayName: "GPT OSS 20B", ContextWindow: 131072, MaxOutput: 8192, Reasoning: true, PriceInput: 0.05, PriceOutput: 0.2, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 470 | 473 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-safeguard-20b", DisplayName: "GPT OSS Safeguard 20B", ContextWindow: 131072, MaxOutput: 65536, Reasoning: true, PriceInput: 0.075, PriceOutput: 0.3, PriceCacheRead: 0.037, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| 471 | 474 | {Provider: "vercel-ai-gateway", ID: "openai/o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: "https://ai-gateway.vercel.sh"}, | |
| @@ -622,6 +625,9 @@ var builtinCatalog = []Model{ | |||
| 622 | 625 | {Provider: "azure-openai-responses", ID: "gpt-5.4-pro", DisplayName: "GPT-5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | |
| 623 | 626 | {Provider: "azure-openai-responses", ID: "gpt-5.5", DisplayName: "GPT-5.5", ContextWindow: 272000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: ""}, | |
| 624 | 627 | {Provider: "azure-openai-responses", ID: "gpt-5.5-pro", DisplayName: "GPT-5.5 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | |
| 628 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25, BaseURL: ""}, | |
| 629 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, BaseURL: ""}, | |
| 630 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125, BaseURL: ""}, | |
| 625 | 631 | {Provider: "azure-openai-responses", ID: "o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: ""}, | |
| 626 | 632 | {Provider: "azure-openai-responses", ID: "o1-pro", DisplayName: "o1-pro", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 150, PriceOutput: 600, PriceCacheRead: 0, BaseURL: ""}, | |
| 627 | 633 | {Provider: "azure-openai-responses", ID: "o3", DisplayName: "o3", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 2, PriceOutput: 8, PriceCacheRead: 0.5, BaseURL: ""}, | |
| @@ -466,6 +466,9 @@ var builtinCatalog = []Model{ | |||||
| 466 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.4-pro", DisplayName: "GPT 5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | 466 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.4-pro", DisplayName: "GPT 5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| 467 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5", DisplayName: "GPT 5.5", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: "https://ai-gateway.vercel.sh"}, | 467 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5", DisplayName: "GPT 5.5", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| 468 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5-pro", DisplayName: "GPT 5.5 Pro", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | 468 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.5-pro", DisplayName: "GPT 5.5 Pro", ContextWindow: 1000000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| 469 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-luna", DisplayName: "GPT 5.6 Luna", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25, BaseURL: "https://ai-gateway.vercel.sh"}, | |||
| 470 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-sol", DisplayName: "GPT 5.6 Sol", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, BaseURL: "https://ai-gateway.vercel.sh"}, | |||
| 471 | + | {Provider: "vercel-ai-gateway", ID: "openai/gpt-5.6-terra", DisplayName: "GPT 5.6 Terra", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125, BaseURL: "https://ai-gateway.vercel.sh"}, | |||
| 469 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-20b", DisplayName: "GPT OSS 20B", ContextWindow: 131072, MaxOutput: 8192, Reasoning: true, PriceInput: 0.05, PriceOutput: 0.2, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | 472 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-20b", DisplayName: "GPT OSS 20B", ContextWindow: 131072, MaxOutput: 8192, Reasoning: true, PriceInput: 0.05, PriceOutput: 0.2, PriceCacheRead: 0, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| 470 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-safeguard-20b", DisplayName: "GPT OSS Safeguard 20B", ContextWindow: 131072, MaxOutput: 65536, Reasoning: true, PriceInput: 0.075, PriceOutput: 0.3, PriceCacheRead: 0.037, BaseURL: "https://ai-gateway.vercel.sh"}, | 473 | {Provider: "vercel-ai-gateway", ID: "openai/gpt-oss-safeguard-20b", DisplayName: "GPT OSS Safeguard 20B", ContextWindow: 131072, MaxOutput: 65536, Reasoning: true, PriceInput: 0.075, PriceOutput: 0.3, PriceCacheRead: 0.037, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| 471 | {Provider: "vercel-ai-gateway", ID: "openai/o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: "https://ai-gateway.vercel.sh"}, | 474 | {Provider: "vercel-ai-gateway", ID: "openai/o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: "https://ai-gateway.vercel.sh"}, | ||
| @@ -622,6 +625,9 @@ var builtinCatalog = []Model{ | |||||
| 622 | {Provider: "azure-openai-responses", ID: "gpt-5.4-pro", DisplayName: "GPT-5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | 625 | {Provider: "azure-openai-responses", ID: "gpt-5.4-pro", DisplayName: "GPT-5.4 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | ||
| 623 | {Provider: "azure-openai-responses", ID: "gpt-5.5", DisplayName: "GPT-5.5", ContextWindow: 272000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: ""}, | 626 | {Provider: "azure-openai-responses", ID: "gpt-5.5", DisplayName: "GPT-5.5", ContextWindow: 272000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, BaseURL: ""}, | ||
| 624 | {Provider: "azure-openai-responses", ID: "gpt-5.5-pro", DisplayName: "GPT-5.5 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | 627 | {Provider: "azure-openai-responses", ID: "gpt-5.5-pro", DisplayName: "GPT-5.5 Pro", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 30, PriceOutput: 180, PriceCacheRead: 0, BaseURL: ""}, | ||
| 628 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-luna", DisplayName: "GPT-5.6 Luna", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 1, PriceOutput: 6, PriceCacheRead: 0.1, PriceCacheWrite: 1.25, BaseURL: ""}, | |||
| 629 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-sol", DisplayName: "GPT-5.6 Sol", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 5, PriceOutput: 30, PriceCacheRead: 0.5, PriceCacheWrite: 6.25, BaseURL: ""}, | |||
| 630 | + | {Provider: "azure-openai-responses", ID: "gpt-5.6-terra", DisplayName: "GPT-5.6 Terra", ContextWindow: 1050000, MaxOutput: 128000, Reasoning: true, PriceInput: 2.5, PriceOutput: 15, PriceCacheRead: 0.25, PriceCacheWrite: 3.125, BaseURL: ""}, | |||
| 625 | {Provider: "azure-openai-responses", ID: "o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: ""}, | 631 | {Provider: "azure-openai-responses", ID: "o1", DisplayName: "o1", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 15, PriceOutput: 60, PriceCacheRead: 7.5, BaseURL: ""}, | ||
| 626 | {Provider: "azure-openai-responses", ID: "o1-pro", DisplayName: "o1-pro", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 150, PriceOutput: 600, PriceCacheRead: 0, BaseURL: ""}, | 632 | {Provider: "azure-openai-responses", ID: "o1-pro", DisplayName: "o1-pro", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 150, PriceOutput: 600, PriceCacheRead: 0, BaseURL: ""}, | ||
| 627 | {Provider: "azure-openai-responses", ID: "o3", DisplayName: "o3", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 2, PriceOutput: 8, PriceCacheRead: 0.5, BaseURL: ""}, | 633 | {Provider: "azure-openai-responses", ID: "o3", DisplayName: "o3", ContextWindow: 200000, MaxOutput: 100000, Reasoning: true, PriceInput: 2, PriceOutput: 8, PriceCacheRead: 0.5, BaseURL: ""}, | ||
M packages/provider/extra_providers.go +0 -10
| @@ -253,16 +253,6 @@ func NewGoogleVertex(apiKey, baseURL string) Client { | |||
| 253 | 253 | return NewVertex(apiKey, baseURL) | |
| 254 | 254 | } | |
| 255 | 255 | ||
| 256 | - | // NewAzureOpenAIResponses delegates to the real Azure OpenAI client. | |
| 257 | - | // Despite the provider id mentioning "responses", zot uses Azure's | |
| 258 | - | // Chat Completions endpoint (older but functionally complete for our | |
| 259 | - | // agent loop) to avoid duplicating the full openai-responses wire | |
| 260 | - | // client. Models register under provider id `azure-openai-responses` | |
| 261 | - | // so user catalogs keep working unchanged. | |
| 262 | - | func NewAzureOpenAIResponses(apiKey, baseURL string) Client { | |
| 263 | - | return NewAzureOpenAI(apiKey, baseURL) | |
| 264 | - | } | |
| 265 | - | ||
| 266 | 256 | // NewMistral returns a Mistral client using their OpenAI-compatible Chat | |
| 267 | 257 | // Completions endpoint at https://api.mistral.ai/v1. Mistral also offers a | |
| 268 | 258 | // bespoke "Conversations" API, but the OpenAI-compat endpoint supports | |
| @@ -253,16 +253,6 @@ func NewGoogleVertex(apiKey, baseURL string) Client { | |||||
| 253 | return NewVertex(apiKey, baseURL) | 253 | return NewVertex(apiKey, baseURL) | ||
| 254 | } | 254 | } | ||
| 255 | 255 | ||||
| 256 | - | // NewAzureOpenAIResponses delegates to the real Azure OpenAI client. | |||
| 257 | - | // Despite the provider id mentioning "responses", zot uses Azure's | |||
| 258 | - | // Chat Completions endpoint (older but functionally complete for our | |||
| 259 | - | // agent loop) to avoid duplicating the full openai-responses wire | |||
| 260 | - | // client. Models register under provider id `azure-openai-responses` | |||
| 261 | - | // so user catalogs keep working unchanged. | |||
| 262 | - | func NewAzureOpenAIResponses(apiKey, baseURL string) Client { | |||
| 263 | - | return NewAzureOpenAI(apiKey, baseURL) | |||
| 264 | - | } | |||
| 265 | - | ||||
| 266 | // NewMistral returns a Mistral client using their OpenAI-compatible Chat | 256 | // NewMistral returns a Mistral client using their OpenAI-compatible Chat | ||
| 267 | // Completions endpoint at https://api.mistral.ai/v1. Mistral also offers a | 257 | // Completions endpoint at https://api.mistral.ai/v1. Mistral also offers a | ||
| 268 | // bespoke "Conversations" API, but the OpenAI-compat endpoint supports | 258 | // bespoke "Conversations" API, but the OpenAI-compat endpoint supports | ||
M packages/provider/openai_codex.go +23 -12
| @@ -38,11 +38,14 @@ import ( | |||
| 38 | 38 | const codexDefaultBaseURL = "https://chatgpt.com/backend-api/codex/responses" | |
| 39 | 39 | ||
| 40 | 40 | type codexClient struct { | |
| 41 | - | token string | |
| 42 | - | accountID string | |
| 43 | - | baseURL string | |
| 44 | - | errorLabel string | |
| 45 | - | http *http.Client | |
| 41 | + | token string | |
| 42 | + | accountID string | |
| 43 | + | baseURL string | |
| 44 | + | errorLabel string | |
| 45 | + | providerName string | |
| 46 | + | modelName func(string) string | |
| 47 | + | disableCLIRouting bool | |
| 48 | + | http *http.Client | |
| 46 | 49 | } | |
| 47 | 50 | ||
| 48 | 51 | // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint | |
| @@ -53,11 +56,12 @@ func NewOpenAICodex(token, accountID, baseURL string) Client { | |||
| 53 | 56 | baseURL = codexDefaultBaseURL | |
| 54 | 57 | } | |
| 55 | 58 | return &codexClient{ | |
| 56 | - | token: token, | |
| 57 | - | accountID: accountID, | |
| 58 | - | baseURL: strings.TrimRight(baseURL, "/"), | |
| 59 | - | errorLabel: "codex", | |
| 60 | - | http: &http.Client{Timeout: 0}, | |
| 59 | + | token: token, | |
| 60 | + | accountID: accountID, | |
| 61 | + | baseURL: strings.TrimRight(baseURL, "/"), | |
| 62 | + | errorLabel: "codex", | |
| 63 | + | providerName: "openai-codex", | |
| 64 | + | http: &http.Client{Timeout: 0}, | |
| 61 | 65 | } | |
| 62 | 66 | } | |
| 63 | 67 | ||
| @@ -345,8 +349,11 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||
| 345 | 349 | if err != nil { | |
| 346 | 350 | return nil, err | |
| 347 | 351 | } | |
| 352 | + | if c.modelName != nil { | |
| 353 | + | wire.Model = c.modelName(wire.Model) | |
| 354 | + | } | |
| 348 | 355 | var codexCLISessionID string | |
| 349 | - | if usesCodexCLIRouting(wire.Model) { | |
| 356 | + | if !c.disableCLIRouting && usesCodexCLIRouting(wire.Model) { | |
| 350 | 357 | codexCLISessionID = newCodexSessionID() | |
| 351 | 358 | wire.PromptCacheKey = codexCLISessionID | |
| 352 | 359 | } | |
| @@ -402,7 +409,11 @@ func (c *codexClient) runStream(ctx context.Context, resp *http.Response, req Re | |||
| 402 | 409 | if model.ID == "" { | |
| 403 | 410 | model, _ = FindModel("openai", req.Model) | |
| 404 | 411 | } | |
| 405 | - | out <- EventStart{Model: req.Model, Provider: "openai-codex"} | |
| 412 | + | providerName := c.providerName | |
| 413 | + | if providerName == "" { | |
| 414 | + | providerName = "openai-codex" | |
| 415 | + | } | |
| 416 | + | out <- EventStart{Model: req.Model, Provider: providerName} | |
| 406 | 417 | ||
| 407 | 418 | raw := make(chan sseEvent, 16) | |
| 408 | 419 | go readSSE(resp.Body, raw) | |
| @@ -38,11 +38,14 @@ 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 string | 41 | + | token string |
| 42 | - | accountID string | 42 | + | accountID string |
| 43 | - | baseURL string | 43 | + | baseURL string |
| 44 | - | errorLabel string | 44 | + | errorLabel string |
| 45 | - | http *http.Client | 45 | + | providerName string |
| 46 | + | modelName func(string) string | |||
| 47 | + | disableCLIRouting bool | |||
| 48 | + | http *http.Client | |||
| 46 | } | 49 | } | ||
| 47 | 50 | ||||
| 48 | // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint | 51 | // NewOpenAICodex creates a client that talks to ChatGPT's Codex endpoint | ||
| @@ -53,11 +56,12 @@ func NewOpenAICodex(token, accountID, baseURL string) Client { | |||||
| 53 | baseURL = codexDefaultBaseURL | 56 | baseURL = codexDefaultBaseURL | ||
| 54 | } | 57 | } | ||
| 55 | return &codexClient{ | 58 | return &codexClient{ | ||
| 56 | - | token: token, | 59 | + | token: token, |
| 57 | - | accountID: accountID, | 60 | + | accountID: accountID, |
| 58 | - | baseURL: strings.TrimRight(baseURL, "/"), | 61 | + | baseURL: strings.TrimRight(baseURL, "/"), |
| 59 | - | errorLabel: "codex", | 62 | + | errorLabel: "codex", |
| 60 | - | http: &http.Client{Timeout: 0}, | 63 | + | providerName: "openai-codex", |
| 64 | + | http: &http.Client{Timeout: 0}, | |||
| 61 | } | 65 | } | ||
| 62 | } | 66 | } | ||
| 63 | 67 | ||||
| @@ -345,8 +349,11 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er | |||||
| 345 | if err != nil { | 349 | if err != nil { | ||
| 346 | return nil, err | 350 | return nil, err | ||
| 347 | } | 351 | } | ||
| 352 | + | if c.modelName != nil { | |||
| 353 | + | wire.Model = c.modelName(wire.Model) | |||
| 354 | + | } | |||
| 348 | var codexCLISessionID string | 355 | var codexCLISessionID string | ||
| 349 | - | if usesCodexCLIRouting(wire.Model) { | 356 | + | if !c.disableCLIRouting && usesCodexCLIRouting(wire.Model) { |
| 350 | codexCLISessionID = newCodexSessionID() | 357 | codexCLISessionID = newCodexSessionID() | ||
| 351 | wire.PromptCacheKey = codexCLISessionID | 358 | wire.PromptCacheKey = codexCLISessionID | ||
| 352 | } | 359 | } | ||
| @@ -402,7 +409,11 @@ func (c *codexClient) runStream(ctx context.Context, resp *http.Response, req Re | |||||
| 402 | if model.ID == "" { | 409 | if model.ID == "" { | ||
| 403 | model, _ = FindModel("openai", req.Model) | 410 | model, _ = FindModel("openai", req.Model) | ||
| 404 | } | 411 | } | ||
| 405 | - | out <- EventStart{Model: req.Model, Provider: "openai-codex"} | 412 | + | providerName := c.providerName |
| 413 | + | if providerName == "" { | |||
| 414 | + | providerName = "openai-codex" | |||
| 415 | + | } | |||
| 416 | + | out <- EventStart{Model: req.Model, Provider: providerName} | |||
| 406 | 417 | ||||
| 407 | raw := make(chan sseEvent, 16) | 418 | raw := make(chan sseEvent, 16) | ||
| 408 | go readSSE(resp.Body, raw) | 419 | go readSSE(resp.Body, raw) | ||
M packages/provider/openai_responses.go +6 -5
| @@ -63,11 +63,12 @@ func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client { | |||
| 63 | 63 | Timeout: 0, | |
| 64 | 64 | } | |
| 65 | 65 | inner := &codexClient{ | |
| 66 | - | token: apiKey, | |
| 67 | - | accountID: "", // unused; transport strips the header | |
| 68 | - | baseURL: strings.TrimRight(baseURL, "/"), | |
| 69 | - | errorLabel: "openai", | |
| 70 | - | http: httpClient, | |
| 66 | + | token: apiKey, | |
| 67 | + | accountID: "", // unused; transport strips the header | |
| 68 | + | baseURL: strings.TrimRight(baseURL, "/"), | |
| 69 | + | errorLabel: "openai", | |
| 70 | + | providerName: name, | |
| 71 | + | http: httpClient, | |
| 71 | 72 | } | |
| 72 | 73 | return &renamedClient{inner: inner, name: name} | |
| 73 | 74 | } | |
| @@ -63,11 +63,12 @@ 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 header | 67 | + | accountID: "", // unused; transport strips the header |
| 68 | - | baseURL: strings.TrimRight(baseURL, "/"), | 68 | + | baseURL: strings.TrimRight(baseURL, "/"), |
| 69 | - | errorLabel: "openai", | 69 | + | errorLabel: "openai", |
| 70 | - | http: httpClient, | 70 | + | providerName: name, |
| 71 | + | http: httpClient, | |||
| 71 | } | 72 | } | ||
| 72 | return &renamedClient{inner: inner, name: name} | 73 | return &renamedClient{inner: inner, name: name} | ||
| 73 | } | 74 | } | ||
M packages/provider/provider_test.go +6 -0
| @@ -414,6 +414,12 @@ func TestGPT56CatalogEntries(t *testing.T) { | |||
| 414 | 414 | {"openai-codex", "gpt-5.6-luna", 372000, 1, 6, 0.1, 1.25}, | |
| 415 | 415 | {"openai-codex", "gpt-5.6-sol", 372000, 5, 30, 0.5, 6.25}, | |
| 416 | 416 | {"openai-codex", "gpt-5.6-terra", 372000, 2.5, 15, 0.25, 3.125}, | |
| 417 | + | {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25}, | |
| 418 | + | {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25}, | |
| 419 | + | {"azure-openai-responses", "gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125}, | |
| 420 | + | {"vercel-ai-gateway", "openai/gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25}, | |
| 421 | + | {"vercel-ai-gateway", "openai/gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25}, | |
| 422 | + | {"vercel-ai-gateway", "openai/gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125}, | |
| 417 | 423 | } | |
| 418 | 424 | for _, tc := range cases { | |
| 419 | 425 | m, err := FindModel(tc.provider, tc.id) | |
| @@ -414,6 +414,12 @@ func TestGPT56CatalogEntries(t *testing.T) { | |||||
| 414 | {"openai-codex", "gpt-5.6-luna", 372000, 1, 6, 0.1, 1.25}, | 414 | {"openai-codex", "gpt-5.6-luna", 372000, 1, 6, 0.1, 1.25}, | ||
| 415 | {"openai-codex", "gpt-5.6-sol", 372000, 5, 30, 0.5, 6.25}, | 415 | {"openai-codex", "gpt-5.6-sol", 372000, 5, 30, 0.5, 6.25}, | ||
| 416 | {"openai-codex", "gpt-5.6-terra", 372000, 2.5, 15, 0.25, 3.125}, | 416 | {"openai-codex", "gpt-5.6-terra", 372000, 2.5, 15, 0.25, 3.125}, | ||
| 417 | + | {"azure-openai-responses", "gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25}, | |||
| 418 | + | {"azure-openai-responses", "gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25}, | |||
| 419 | + | {"azure-openai-responses", "gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125}, | |||
| 420 | + | {"vercel-ai-gateway", "openai/gpt-5.6-luna", 1050000, 1, 6, 0.1, 1.25}, | |||
| 421 | + | {"vercel-ai-gateway", "openai/gpt-5.6-sol", 1050000, 5, 30, 0.5, 6.25}, | |||
| 422 | + | {"vercel-ai-gateway", "openai/gpt-5.6-terra", 1050000, 2.5, 15, 0.25, 3.125}, | |||
| 417 | } | 423 | } | ||
| 418 | for _, tc := range cases { | 424 | for _, tc := range cases { | ||
| 419 | m, err := FindModel(tc.provider, tc.id) | 425 | m, err := FindModel(tc.provider, tc.id) | ||