| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- package service
- import "testing"
- func TestResolveOpenAIForwardModel(t *testing.T) {
- tests := []struct {
- name string
- account *Account
- requestedModel string
- messagesDispatchMappedModel string
- expectedModel string
- }{
- {
- name: "uses messages dispatch model for known claude family",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "claude-opus-4-6",
- messagesDispatchMappedModel: "gpt-4o-mini",
- expectedModel: "gpt-4o-mini",
- },
- {
- name: "uses exact messages dispatch model for unknown claude family",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "claude-fable-5",
- messagesDispatchMappedModel: " gpt-5.6-sol ",
- expectedModel: "gpt-5.6-sol",
- },
- {
- name: "nil account uses messages dispatch model",
- requestedModel: "claude-fable-5",
- messagesDispatchMappedModel: "gpt-5.6-sol",
- expectedModel: "gpt-5.6-sol",
- },
- {
- name: "nil account without messages dispatch keeps requested model",
- requestedModel: "claude-fable-5",
- expectedModel: "claude-fable-5",
- },
- {
- name: "ordinary unknown gpt model has no messages dispatch fallback",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt6",
- expectedModel: "gpt6",
- },
- {
- name: "account exact mapping overrides messages dispatch model",
- account: &Account{
- Credentials: map[string]any{
- "model_mapping": map[string]any{
- "claude-fable-5": "gpt-5.5",
- },
- },
- },
- requestedModel: "claude-fable-5",
- messagesDispatchMappedModel: "gpt-5.6-sol",
- expectedModel: "gpt-5.5",
- },
- {
- name: "account wildcard mapping overrides messages dispatch model",
- account: &Account{
- Credentials: map[string]any{
- "model_mapping": map[string]any{
- "claude-*": "gpt-5.4",
- },
- },
- },
- requestedModel: "claude-fable-5",
- messagesDispatchMappedModel: "gpt-5.6-sol",
- expectedModel: "gpt-5.4",
- },
- {
- name: "account passthrough mapping overrides messages dispatch model",
- account: &Account{
- Credentials: map[string]any{
- "model_mapping": map[string]any{
- "claude-fable-5": "claude-fable-5",
- },
- },
- },
- requestedModel: "claude-fable-5",
- messagesDispatchMappedModel: "gpt-5.6-sol",
- expectedModel: "claude-fable-5",
- },
- {
- name: "ordinary codex spark request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt-5.3-codex-spark",
- expectedModel: "gpt-5.3-codex-spark",
- },
- {
- name: "ordinary gpt-5.5 request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt-5.5",
- expectedModel: "gpt-5.5",
- },
- {
- name: "ordinary gpt-5.5-pro request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt-5.5-pro",
- expectedModel: "gpt-5.5-pro",
- },
- {
- name: "ordinary compact-spelled gpt5.5 request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt5.5",
- expectedModel: "gpt5.5",
- },
- {
- name: "ordinary namespaced gpt-5.5 request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "openai/gpt-5.5",
- expectedModel: "openai/gpt-5.5",
- },
- {
- name: "ordinary compact gpt-5.5 request keeps requested model",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt-5.5-openai-compact",
- expectedModel: "gpt-5.5-openai-compact",
- },
- {
- name: "whitespace-only messages dispatch model is ignored",
- account: &Account{
- Credentials: map[string]any{},
- },
- requestedModel: "gpt-5.5",
- messagesDispatchMappedModel: " ",
- expectedModel: "gpt-5.5",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := resolveOpenAIForwardModel(tt.account, tt.requestedModel, tt.messagesDispatchMappedModel); got != tt.expectedModel {
- t.Fatalf("resolveOpenAIForwardModel(...) = %q, want %q", got, tt.expectedModel)
- }
- })
- }
- }
- func TestResolveOpenAICompactForwardModel(t *testing.T) {
- tests := []struct {
- name string
- account *Account
- model string
- expectedModel string
- }{
- {
- name: "nil account keeps original model",
- account: nil,
- model: "gpt-5.4",
- expectedModel: "gpt-5.4",
- },
- {
- name: "missing compact mapping keeps original model",
- account: &Account{
- Credentials: map[string]any{},
- },
- model: "gpt-5.4",
- expectedModel: "gpt-5.4",
- },
- {
- name: "exact compact mapping overrides model",
- account: &Account{
- Credentials: map[string]any{
- "compact_model_mapping": map[string]any{
- "gpt-5.4": "gpt-5.4-openai-compact",
- },
- },
- },
- model: "gpt-5.4",
- expectedModel: "gpt-5.4-openai-compact",
- },
- {
- name: "wildcard compact mapping overrides model",
- account: &Account{
- Credentials: map[string]any{
- "compact_model_mapping": map[string]any{
- "gpt-5.*": "gpt-5-openai-compact",
- },
- },
- },
- model: "gpt-5.4",
- expectedModel: "gpt-5-openai-compact",
- },
- {
- name: "passthrough compact mapping remains unchanged",
- account: &Account{
- Credentials: map[string]any{
- "compact_model_mapping": map[string]any{
- "gpt-5.4": "gpt-5.4",
- },
- },
- },
- model: "gpt-5.4",
- expectedModel: "gpt-5.4",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := resolveOpenAICompactForwardModel(tt.account, tt.model); got != tt.expectedModel {
- t.Fatalf("resolveOpenAICompactForwardModel(...) = %q, want %q", got, tt.expectedModel)
- }
- })
- }
- }
- func TestResolveOpenAIForwardMappedModels_CompactMappingPrecedence(t *testing.T) {
- conflictingMappings := map[string]any{
- "model_mapping": map[string]any{"gpt-5.5": "gpt-5.4"},
- "compact_model_mapping": map[string]any{"gpt-5.5": "gpt-5.5-openai-compact"},
- }
- mappedOnlyCompact := map[string]any{
- "model_mapping": map[string]any{"gpt-5.5": "gpt-5.4"},
- "compact_model_mapping": map[string]any{"gpt-5.4": "gpt-5.4-openai-compact"},
- }
- tests := []struct {
- name string
- account *Account
- requireCompact bool
- wantBilling string
- wantUpstream string
- }{
- {
- name: "compact uses client-visible model before ordinary mapping",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth,
- Credentials: conflictingMappings},
- requireCompact: true,
- wantBilling: "gpt-5.4",
- wantUpstream: "gpt-5.5-openai-compact",
- },
- {
- name: "non-compact uses ordinary mapping",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth,
- Credentials: conflictingMappings},
- wantBilling: "gpt-5.4",
- wantUpstream: "gpt-5.4",
- },
- {
- name: "compact falls back to ordinary mapped model",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth,
- Credentials: mappedOnlyCompact},
- requireCompact: true,
- wantBilling: "gpt-5.4",
- wantUpstream: "gpt-5.4-openai-compact",
- },
- {
- name: "passthrough ignores ordinary mapping",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth,
- Credentials: conflictingMappings, Extra: map[string]any{"openai_passthrough": true}},
- requireCompact: true,
- wantBilling: "gpt-5.5",
- wantUpstream: "gpt-5.5-openai-compact",
- },
- {
- name: "raw chat fallback never applies compact mapping",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey,
- Credentials: conflictingMappings, Extra: map[string]any{"openai_responses_supported": false}},
- requireCompact: true,
- wantBilling: "gpt-5.4",
- wantUpstream: "gpt-5.4",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- billing, upstream := resolveOpenAIForwardMappedModels(tt.account, "gpt-5.5", tt.requireCompact)
- if billing != tt.wantBilling {
- t.Fatalf("billing model = %q, want %q", billing, tt.wantBilling)
- }
- if upstream != tt.wantUpstream {
- t.Fatalf("upstream model = %q, want %q", upstream, tt.wantUpstream)
- }
- if scheduler := resolveOpenAIAccountUpstreamModelForRequest(tt.account, "gpt-5.5", tt.requireCompact); scheduler != upstream {
- t.Fatalf("scheduler model %q disagrees with Forward model %q", scheduler, upstream)
- }
- })
- }
- }
- func TestCanonicalOpenAIAccountSchedulingModelMatchesForwardSemantics(t *testing.T) {
- tests := []struct {
- name string
- account *Account
- model string
- want string
- }{
- {
- name: "OpenAI OAuth applies Codex alias normalization",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth},
- model: "gpt-5.6",
- want: "gpt-5.6-sol",
- },
- {
- name: "OpenAI passthrough ignores ordinary account mapping",
- account: &Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth,
- Credentials: map[string]any{"model_mapping": map[string]any{"public": "private"}},
- Extra: map[string]any{"openai_passthrough": true}},
- model: "public",
- want: "public",
- },
- {
- name: "Grok OAuth does not inherit OpenAI Codex aliases",
- account: &Account{Platform: PlatformGrok, Type: AccountTypeOAuth},
- model: "gpt-5.6",
- want: "gpt-5.6",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := canonicalOpenAIAccountSchedulingModel(tt.account, tt.model); got != tt.want {
- t.Fatalf("canonical scheduling model = %q, want %q", got, tt.want)
- }
- })
- }
- }
- func TestResolveOpenAIErrorSchedulingModelPrefersActualUpstreamModel(t *testing.T) {
- if got := resolveOpenAIErrorSchedulingModel("gpt-5.4", "gpt-5.5-openai-compact"); got != "gpt-5.5-openai-compact" {
- t.Fatalf("error scheduling model = %q, want compact upstream model", got)
- }
- if got := resolveOpenAIErrorSchedulingModel("gpt-5.4", ""); got != "gpt-5.4" {
- t.Fatalf("empty upstream fallback = %q, want billing model", got)
- }
- }
- func TestNormalizeCodexModel(t *testing.T) {
- cases := map[string]string{
- "gpt-5.3-codex-spark": "gpt-5.3-codex-spark",
- "gpt-5.3-codex-spark-high": "gpt-5.3-codex-spark",
- "gpt-5.3-codex-spark-xhigh": "gpt-5.3-codex-spark",
- "gpt-5.3": "gpt-5.3-codex",
- "gpt-image-2": "gpt-image-2",
- "gpt-5.4-nano": "gpt-5.4-nano",
- "gpt-5.4-nano-high": "gpt-5.4-nano",
- "gpt6": "gpt6",
- "claude-opus-4-6": "claude-opus-4-6",
- }
- for input, expected := range cases {
- if got := normalizeCodexModel(input); got != expected {
- t.Fatalf("normalizeCodexModel(%q) = %q, want %q", input, got, expected)
- }
- }
- }
- func TestNormalizeOpenAIModelForUpstream(t *testing.T) {
- tests := []struct {
- name string
- account *Account
- model string
- want string
- }{
- {
- name: "oauth routes bare GPT-5.6 alias to Sol",
- account: &Account{Type: AccountTypeOAuth},
- model: "gpt-5.6",
- want: "gpt-5.6-sol",
- },
- {
- name: "oauth routes provider-prefixed GPT-5.6 alias to Sol",
- account: &Account{Type: AccountTypeOAuth},
- model: "openai/gpt-5.6",
- want: "gpt-5.6-sol",
- },
- {
- name: "oauth preserves unknown non codex model",
- account: &Account{Type: AccountTypeOAuth},
- model: "gemini-3-flash-preview",
- want: "gemini-3-flash-preview",
- },
- {
- name: "oauth preserves invalid gpt model",
- account: &Account{Type: AccountTypeOAuth},
- model: "gpt6",
- want: "gpt6",
- },
- {
- name: "oauth normalizes known codex alias",
- account: &Account{Type: AccountTypeOAuth},
- model: "gpt-5.4-high",
- want: "gpt-5.4",
- },
- {
- name: "oauth preserves GPT-5.5 Pro model",
- account: &Account{Type: AccountTypeOAuth},
- model: "openai/gpt-5.5-pro",
- want: "gpt-5.5-pro",
- },
- {
- name: "oauth preserves codex auto review model",
- account: &Account{Type: AccountTypeOAuth},
- model: "codex-auto-review",
- want: "codex-auto-review",
- },
- {
- name: "apikey preserves official bare GPT-5.6 alias",
- account: &Account{Type: AccountTypeAPIKey},
- model: "gpt-5.6",
- want: "gpt-5.6",
- },
- {
- name: "apikey preserves custom compatible model",
- account: &Account{Type: AccountTypeAPIKey},
- model: "gemini-3-flash-preview",
- want: "gemini-3-flash-preview",
- },
- {
- name: "apikey preserves official non codex model",
- account: &Account{Type: AccountTypeAPIKey},
- model: "gpt-4.1",
- want: "gpt-4.1",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := normalizeOpenAIModelForUpstream(tt.account, tt.model); got != tt.want {
- t.Fatalf("normalizeOpenAIModelForUpstream(...) = %q, want %q", got, tt.want)
- }
- })
- }
- }
- func TestUsageBillingModelCandidatesPreserveCodexAutoReviewModel(t *testing.T) {
- candidates := usageBillingModelCandidates("codex-auto-review")
- expected := []string{"codex-auto-review"}
- if len(candidates) != len(expected) {
- t.Fatalf("usageBillingModelCandidates(codex-auto-review) = %#v, want %#v", candidates, expected)
- }
- for i := range expected {
- if candidates[i] != expected[i] {
- t.Fatalf("usageBillingModelCandidates(codex-auto-review) = %#v, want %#v", candidates, expected)
- }
- }
- }
- func TestUsageBillingModelCandidatesPreserveGPT55ProModel(t *testing.T) {
- candidates := usageBillingModelCandidates("openai/gpt-5.5-pro")
- expected := []string{"openai/gpt-5.5-pro", "gpt-5.5-pro"}
- if len(candidates) != len(expected) {
- t.Fatalf("usageBillingModelCandidates(openai/gpt-5.5-pro) = %#v, want %#v", candidates, expected)
- }
- for i := range expected {
- if candidates[i] != expected[i] {
- t.Fatalf("usageBillingModelCandidates(openai/gpt-5.5-pro) = %#v, want %#v", candidates, expected)
- }
- }
- }
|