From 8a469478659b0a87405193afa89df0e0a9f3302f Mon Sep 17 00:00:00 2001 From: DAlaoli <265052293+gebdalaoli-arch@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:50:01 +0800 Subject: [PATCH] Fix stale official Astra input modalities --- .../service/openai_codex_astra_image_test.go | 72 +++++++++++++++++++ .../service/openai_codex_models_service.go | 25 ++++++- 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 backend/internal/service/openai_codex_astra_image_test.go diff --git a/backend/internal/service/openai_codex_astra_image_test.go b/backend/internal/service/openai_codex_astra_image_test.go new file mode 100644 index 0000000000..ec4cd3eeea --- /dev/null +++ b/backend/internal/service/openai_codex_astra_image_test.go @@ -0,0 +1,72 @@ +package service + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuildCodexModelsManifestForGroupCorrectsOfficialAstraStaleModalities(t *testing.T) { + t.Parallel() + + const groupID int64 = 780 + account := Account{ + ID: 1, + Platform: PlatformOpenAI, + Type: AccountTypeAPIKey, + Credentials: map[string]any{"api_key": "sk-test"}, + } + account.SetUpstreamModelMetadataSnapshot(UpstreamModelMetadataSnapshot{ + Models: map[string]UpstreamModelMetadata{ + "gpt-6-astra": {ID: "gpt-6-astra", InputModalities: []string{"text"}}, + }, + }) + + svc := &GatewayService{accountRepo: codexModelsVisibilityAccountRepo{ + byGroup: map[int64][]Account{groupID: {account}}, + }} + + body, err := svc.BuildCodexModelsManifestForGroup( + context.Background(), + &Group{ID: groupID, Platform: PlatformOpenAI}, + "", + []string{"gpt-6-astra"}, + ) + require.NoError(t, err) + models := decodeCodexManifestModels(t, body) + require.Len(t, models, 1) + require.Equal(t, []any{"text", "image"}, models[0]["input_modalities"]) +} + +func TestBuildCodexModelsManifestForGroupPreservesCompatibleAstraTextOnlyMetadata(t *testing.T) { + t.Parallel() + + const groupID int64 = 781 + account := Account{ + ID: 2, + Platform: PlatformOpenAI, + Type: AccountTypeAPIKey, + Credentials: map[string]any{"api_key": "sk-test", "base_url": "https://relay.example.test/v1"}, + } + account.SetUpstreamModelMetadataSnapshot(UpstreamModelMetadataSnapshot{ + Models: map[string]UpstreamModelMetadata{ + "gpt-6-astra": {ID: "gpt-6-astra", InputModalities: []string{"text"}}, + }, + }) + + svc := &GatewayService{accountRepo: codexModelsVisibilityAccountRepo{ + byGroup: map[int64][]Account{groupID: {account}}, + }} + + body, err := svc.BuildCodexModelsManifestForGroup( + context.Background(), + &Group{ID: groupID, Platform: PlatformOpenAI}, + "", + []string{"gpt-6-astra"}, + ) + require.NoError(t, err) + models := decodeCodexManifestModels(t, body) + require.Len(t, models, 1) + require.Equal(t, []any{"text"}, models[0]["input_modalities"]) +} diff --git a/backend/internal/service/openai_codex_models_service.go b/backend/internal/service/openai_codex_models_service.go index 9e537a9c78..442d2853ce 100644 --- a/backend/internal/service/openai_codex_models_service.go +++ b/backend/internal/service/openai_codex_models_service.go @@ -888,13 +888,15 @@ func buildCodexModelsManifest( seen[modelID] = struct{}{} descriptor := newConfiguredCodexModelDescriptor(metadataModelID) descriptor.Slug = modelID - if imageInputModels[modelID] { - descriptor.InputModalities = []string{"text", "image"} - } descriptor.SupportsSearchTool = searchToolModels[modelID] if metadata, ok := modelMetadata[modelID]; ok { applyUpstreamModelMetadataToCodexDescriptor(&descriptor, metadata) } + if imageInputModels[modelID] { + // Apply the capability-derived modality after upstream metadata so + // a stale official Astra snapshot cannot downgrade the catalog. + descriptor.InputModalities = []string{"text", "image"} + } if metadataModelID != modelID { descriptor.DisplayName = modelID descriptor.Description = configuredCodexCustomDescription @@ -1186,6 +1188,13 @@ func accountCodexModelSupportsImageInput(account *Account, upstreamModel string) case PlatformOpenAI: if metadata, ok := account.GetUpstreamModelMetadata(upstreamModel); ok { if modalities := normalizeCodexInputModalities(metadata.InputModalities); len(modalities) > 0 { + // Official GPT-6 Astra metadata briefly shipped with a stale + // text-only modality list. Keep explicit provider metadata + // authoritative for compatible hosts, but repair that stale + // official snapshot at the capability boundary. + if isOpenAIGPT6AstraModel(upstreamModel) && isOfficialOpenAICodexAccount(account) { + return true + } return stringSliceContains(modalities, "image") } } @@ -1212,6 +1221,16 @@ func accountCodexModelSupportsImageInput(account *Account, upstreamModel string) } } +func isOfficialOpenAICodexAccount(account *Account) bool { + if account == nil || account.Platform != PlatformOpenAI { + return false + } + if account.IsOpenAIOAuth() { + return true + } + return account.IsOpenAIApiKey() && isOfficialOpenAIModelsBaseURL(account.GetOpenAIBaseURL()) +} + func isGrokCodexImageInputModel(model string) bool { switch strings.ToLower(strings.TrimSpace(model)) { case "grok-4.3",