mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
fix(codex): never advertise null service_tiers in models manifest
intersectUpstreamModelMetadata falls back to null for every Codex tool
capability field whose value differs across peer accounts. Since
e6d191a83 added service_tiers to codexToolCapabilityFields, a model served
by accounts with different (or partially declared) tier lists ends up with
"service_tiers": null in the generated Codex manifest. Codex decodes
service_tiers as a required array and rejects the whole manifest, so the
client loses model metadata for every model.
Fall back to [] for conflicting service_tiers (do not advertise any tier
that is not shared by all peers), and normalize an explicit null to []
when applying capabilities.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
458b92abd4
commit
c0e5a4670e
@@ -21,6 +21,11 @@ func applyCodexToolCapabilities(dst, src map[string]json.RawMessage, overwrite b
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
// Codex decodes service_tiers as a required array and rejects the whole
|
||||
// models manifest on null, so an undeclared tier list is advertised as [].
|
||||
if field == "service_tiers" && bytes.Equal(value, []byte("null")) {
|
||||
value = []byte("[]")
|
||||
}
|
||||
// These Codex fields are nullable booleans or strings, never arbitrary objects.
|
||||
if !bytes.Equal(value, []byte("null")) {
|
||||
if field == "service_tiers" {
|
||||
@@ -280,6 +285,9 @@ func intersectUpstreamModelMetadata(modelID string, candidates []UpstreamModelMe
|
||||
fallback := json.RawMessage("null")
|
||||
if field == "supports_search_tool" || field == "use_responses_lite" {
|
||||
fallback = json.RawMessage("false")
|
||||
} else if field == "service_tiers" {
|
||||
// Conflicting peers must not advertise any tier, and Codex requires an array.
|
||||
fallback = json.RawMessage("[]")
|
||||
}
|
||||
result.CodexToolCapabilities[field] = fallback
|
||||
}
|
||||
|
||||
@@ -108,6 +108,43 @@ func TestAstraCodexToolCapabilitiesUseAccountScopeAndSharedDeclarations(t *testi
|
||||
require.Equal(t, "3000", model["comp_hash"])
|
||||
}
|
||||
|
||||
// Scenario: Codex rejects the whole models manifest when service_tiers is null,
|
||||
// so conflicting or null tier declarations must be advertised as an empty array.
|
||||
func TestCodexToolCapabilitiesNeverAdvertiseNullServiceTiers(t *testing.T) {
|
||||
newAccount := func(id int64, tiers string) Account {
|
||||
account := Account{ID: id, Platform: PlatformOpenAI, Type: AccountTypeAPIKey, Credentials: map[string]any{
|
||||
"base_url": "https://relay.example/v1", "model_mapping": map[string]any{"gpt-6-sol": "gpt-6-sol"},
|
||||
}}
|
||||
if tiers != "" {
|
||||
account.SetUpstreamModelMetadataSnapshot(UpstreamModelMetadataSnapshot{Models: map[string]UpstreamModelMetadata{
|
||||
"gpt-6-sol": {CodexToolCapabilities: map[string]json.RawMessage{"service_tiers": json.RawMessage(tiers)}},
|
||||
}})
|
||||
}
|
||||
return account
|
||||
}
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
accounts []Account
|
||||
}{
|
||||
{"conflicting peer tiers", []Account{newAccount(1, `[{"id":"priority"}]`), newAccount(2, `[]`)}},
|
||||
{"undeclared peer tiers", []Account{newAccount(1, `[{"id":"priority"}]`), newAccount(2, "")}},
|
||||
{"explicit null tiers", []Account{newAccount(1, "null")}},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
body, err := buildCodexModelsManifestForAccounts(PlatformOpenAI, []string{"gpt-6-sol"}, tt.accounts, nil, nil, true)
|
||||
require.NoError(t, err)
|
||||
model := decodeCodexManifestModels(t, body)[0]
|
||||
require.Equal(t, []any{}, model["service_tiers"])
|
||||
})
|
||||
}
|
||||
|
||||
merged := intersectUpstreamModelMetadata("gpt-6-sol", []UpstreamModelMetadata{
|
||||
{CodexToolCapabilities: map[string]json.RawMessage{"service_tiers": json.RawMessage(`[{"id":"priority"}]`)}},
|
||||
{CodexToolCapabilities: map[string]json.RawMessage{"service_tiers": json.RawMessage(`[]`)}},
|
||||
})
|
||||
require.JSONEq(t, `[]`, string(merged.CodexToolCapabilities["service_tiers"]))
|
||||
}
|
||||
|
||||
func TestAstraCodexToolCapabilitiesPreserveLiveNullAndFalse(t *testing.T) {
|
||||
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey,
|
||||
Credentials: map[string]any{"base_url": "https://api.openai.com/v1"}}
|
||||
|
||||
Reference in New Issue
Block a user