From 98a9c25cfe9d705a3035058bbd10fa95cda92853 Mon Sep 17 00:00:00 2001 From: 777genius Date: Mon, 20 Apr 2026 22:06:35 +0300 Subject: [PATCH] fix(validate): normalize provider backend ids --- src/main/http/teams.ts | 9 ++- src/main/ipc/teams.ts | 75 ++++++++++++------- .../services/team/TeamProvisioningService.ts | 9 ++- .../team/dialogs/LaunchTeamDialog.tsx | 9 ++- src/renderer/utils/providerBackendIdentity.ts | 12 ++- src/shared/types/team.ts | 2 +- src/shared/utils/providerBackend.ts | 32 +++++--- 7 files changed, 100 insertions(+), 48 deletions(-) diff --git a/src/main/http/teams.ts b/src/main/http/teams.ts index 28439548..5495aef5 100644 --- a/src/main/http/teams.ts +++ b/src/main/http/teams.ts @@ -1,6 +1,7 @@ import { validateTeamName } from '@main/ipc/guards'; import { getErrorMessage } from '@shared/utils/errorHandling'; import { createLogger } from '@shared/utils/logger'; +import { migrateProviderBackendId } from '@shared/utils/providerBackend'; import { isAbsolute } from 'path'; import type { HttpServices } from './index'; @@ -100,7 +101,13 @@ function parseLaunchRequest(teamName: string, body: unknown): TeamLaunchRequest throw new HttpBadRequestError('providerId must be anthropic, codex, or gemini'); })(); const prompt = assertOptionalString(payload.prompt, 'prompt'); - const providerBackendId = assertOptionalString(payload.providerBackendId, 'providerBackendId'); + const rawProviderBackendId = assertOptionalString(payload.providerBackendId, 'providerBackendId'); + const providerBackendId = migrateProviderBackendId(providerId, rawProviderBackendId); + if (rawProviderBackendId && !providerBackendId) { + throw new HttpBadRequestError( + 'providerBackendId must be one of auto, adapter, api, cli-sdk, or codex-native' + ); + } const model = assertOptionalString(payload.model, 'model'); const effort = assertOptionalEffort(payload.effort); const clearContext = assertOptionalBoolean(payload.clearContext, 'clearContext'); diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 39f2135a..a01e1d5c 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -91,7 +91,7 @@ import { PROTECTED_CLI_FLAGS, } from '@shared/utils/cliArgsParser'; import { createLogger } from '@shared/utils/logger'; -import { migrateProviderBackendId } from '@shared/utils/providerBackend'; +import { isTeamProviderBackendId, migrateProviderBackendId } from '@shared/utils/providerBackend'; import { isRateLimitMessage } from '@shared/utils/rateLimitDetector'; import { buildStandaloneSlashCommandMeta, @@ -186,6 +186,8 @@ import type { TeamLaunchResponse, TeamMemberActivityMeta, TeamMessageNotificationData, + TeamProviderBackendId, + TeamProviderId, TeamProvisioningPrepareResult, TeamProvisioningProgress, TeamSummary, @@ -1131,8 +1133,9 @@ function parseOptionalMemberProviderId( } function parseOptionalProviderBackendId( - value: unknown -): { valid: true; value: string | undefined } | { valid: false; error: string } { + value: unknown, + providerId?: TeamProviderId +): { valid: true; value: TeamProviderBackendId | undefined } | { valid: false; error: string } { if (value === undefined || value === null || value === '') { return { valid: true, value: undefined }; } @@ -1146,7 +1149,19 @@ function parseOptionalProviderBackendId( if (trimmed.length > 64) { return { valid: false, error: 'providerBackendId too long (max 64)' }; } - return { valid: true, value: trimmed }; + if (providerId) { + const migratedBackendId = migrateProviderBackendId(providerId, trimmed); + if (migratedBackendId) { + return { valid: true, value: migratedBackendId }; + } + } else if (isTeamProviderBackendId(trimmed)) { + return { valid: true, value: trimmed }; + } + + return { + valid: false, + error: 'providerBackendId must be one of auto, adapter, api, cli-sdk, or codex-native', + }; } function parseOptionalMemberEffort( @@ -1242,7 +1257,16 @@ async function validateProvisioningRequest( if (payload.prompt !== undefined && typeof payload.prompt !== 'string') { return { valid: false, error: 'prompt must be a string' }; } - const providerBackendValidation = parseOptionalProviderBackendId(payload.providerBackendId); + const providerId = + payload.providerId === 'codex' + ? 'codex' + : payload.providerId === 'gemini' + ? 'gemini' + : 'anthropic'; + const providerBackendValidation = parseOptionalProviderBackendId( + payload.providerBackendId, + providerId + ); if (!providerBackendValidation.valid) { return { valid: false, error: providerBackendValidation.error }; } @@ -1297,12 +1321,7 @@ async function validateProvisioningRequest( members, cwd, prompt: typeof payload.prompt === 'string' ? payload.prompt.trim() || undefined : undefined, - providerId: - payload.providerId === 'codex' - ? 'codex' - : payload.providerId === 'gemini' - ? 'gemini' - : 'anthropic', + providerId, providerBackendId: providerBackendValidation.value, model: typeof payload.model === 'string' ? payload.model.trim() || undefined : undefined, effort: isValidEffort(payload.effort) ? payload.effort : undefined, @@ -1413,7 +1432,16 @@ async function handleLaunchTeam( if (payload.model !== undefined && typeof payload.model !== 'string') { return { success: false, error: 'model must be a string' }; } - const providerBackendValidation = parseOptionalProviderBackendId(payload.providerBackendId); + const providerId = + payload.providerId === 'codex' + ? 'codex' + : payload.providerId === 'gemini' + ? 'gemini' + : 'anthropic'; + const providerBackendValidation = parseOptionalProviderBackendId( + payload.providerBackendId, + providerId + ); if (!providerBackendValidation.valid) { return { success: false, error: providerBackendValidation.error }; } @@ -1439,15 +1467,13 @@ async function handleLaunchTeam( const members = membersMeta?.members ?? []; const resolvedProviderId = - payload.providerId === 'codex' - ? 'codex' - : payload.providerId === 'gemini' - ? 'gemini' - : meta?.providerId === 'codex' - ? 'codex' - : meta?.providerId === 'gemini' - ? 'gemini' - : 'anthropic'; + providerId === 'codex' || providerId === 'gemini' + ? providerId + : meta?.providerId === 'codex' + ? 'codex' + : meta?.providerId === 'gemini' + ? 'gemini' + : 'anthropic'; const createRequest: TeamCreateRequest = { teamName: tn, @@ -1501,12 +1527,7 @@ async function handleLaunchTeam( teamName: validatedTeamName.value!, cwd, prompt: typeof payload.prompt === 'string' ? payload.prompt.trim() || undefined : undefined, - providerId: - payload.providerId === 'codex' - ? 'codex' - : payload.providerId === 'gemini' - ? 'gemini' - : 'anthropic', + providerId, providerBackendId: providerBackendValidation.value, model: typeof payload.model === 'string' ? payload.model.trim() || undefined : undefined, effort: isValidEffort(payload.effort) ? payload.effort : undefined, diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 176f2d12..06ba0121 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -38,10 +38,9 @@ import { getMemberColorByName } from '@shared/constants/memberColors'; import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team'; import { resolveLanguageName } from '@shared/utils/agentLanguage'; import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; -import { getErrorMessage } from '@shared/utils/errorHandling'; -import { buildTeamMemberColorMap } from '@shared/utils/teamMemberColors'; import { parseCliArgs } from '@shared/utils/cliArgsParser'; import { deriveContextMetrics, inferContextWindowTokens } from '@shared/utils/contextMetrics'; +import { getErrorMessage } from '@shared/utils/errorHandling'; import { isInboxNoiseMessage, isMeaningfulBootstrapCheckInMessage, @@ -57,6 +56,7 @@ import { parseAllTeammateMessages, type ParsedTeammateContent, } from '@shared/utils/teammateMessageParser'; +import { buildTeamMemberColorMap } from '@shared/utils/teamMemberColors'; import { createCliAutoSuffixNameGuard, parseNumericSuffixName } from '@shared/utils/teamMemberName'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { @@ -4540,7 +4540,10 @@ export class TeamProvisioningService { teamName, updatedAt, runId: run?.runId ?? null, - providerBackendId: run?.request.providerBackendId ?? persistedTeamMeta?.providerBackendId, + providerBackendId: migrateProviderBackendId( + run?.request.providerId ?? persistedTeamMeta?.providerId, + run?.request.providerBackendId ?? persistedTeamMeta?.providerBackendId + ), members: snapshotMembers, }; diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx index 6189bfe1..49203ee3 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -50,14 +50,15 @@ import { normalizeCreateLaunchProviderForUi, } from '@renderer/utils/geminiUiFreeze'; import { normalizePath } from '@renderer/utils/pathNormalize'; +import { nameColorSet } from '@renderer/utils/projectColor'; import { resolveUiOwnedProviderBackendId } from '@renderer/utils/providerBackendIdentity'; import { refreshCliStatusForCurrentMode } from '@renderer/utils/refreshCliStatus'; -import { nameColorSet } from '@renderer/utils/projectColor'; import { getTeamModelSelectionError, normalizeExplicitTeamModelForUi, } from '@renderer/utils/teamModelAvailability'; import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog'; +import { migrateProviderBackendId } from '@shared/utils/providerBackend'; import { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { @@ -1454,8 +1455,10 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen selectedProviderId, runtimeProviderStatusById.get(selectedProviderId) ) ?? - previousLaunchParams?.providerBackendId ?? - savedLaunchProviderBackendId ?? + migrateProviderBackendId( + selectedProviderId, + previousLaunchParams?.providerBackendId ?? savedLaunchProviderBackendId + ) ?? undefined, model: computeEffectiveTeamModel(selectedModel, limitContext, selectedProviderId), effort: (selectedEffort as EffortLevel) || undefined, diff --git a/src/renderer/utils/providerBackendIdentity.ts b/src/renderer/utils/providerBackendIdentity.ts index 4bbdcd0d..b45dd8eb 100644 --- a/src/renderer/utils/providerBackendIdentity.ts +++ b/src/renderer/utils/providerBackendIdentity.ts @@ -1,10 +1,11 @@ import { formatProviderBackendLabel, getDefaultProviderBackendId, + isTeamProviderBackendId, migrateProviderBackendId, } from '@shared/utils/providerBackend'; -import type { CliProviderStatus, TeamProviderId } from '@shared/types'; +import type { CliProviderStatus, TeamProviderBackendId, TeamProviderId } from '@shared/types'; function normalizeOptionalBackendId(value: string | null | undefined): string | undefined { const trimmed = value?.trim(); @@ -15,14 +16,17 @@ export { formatProviderBackendLabel, getDefaultProviderBackendId }; export function resolveEffectiveProviderBackendId( provider: Pick | null | undefined -): string | undefined { - return normalizeOptionalBackendId(provider?.resolvedBackendId ?? provider?.selectedBackendId); +): TeamProviderBackendId | undefined { + const backendId = normalizeOptionalBackendId( + provider?.resolvedBackendId ?? provider?.selectedBackendId + ); + return isTeamProviderBackendId(backendId) ? backendId : undefined; } export function resolveUiOwnedProviderBackendId( providerId: TeamProviderId | CliProviderStatus['providerId'] | undefined, provider: Pick | null | undefined -): string | undefined { +): TeamProviderBackendId | undefined { return migrateProviderBackendId( providerId, provider?.selectedBackendId ?? provider?.resolvedBackendId diff --git a/src/shared/types/team.ts b/src/shared/types/team.ts index 9748313d..c5f9499b 100644 --- a/src/shared/types/team.ts +++ b/src/shared/types/team.ts @@ -784,7 +784,7 @@ export interface TeamViewSnapshot { export type EffortLevel = 'low' | 'medium' | 'high'; export type TeamProviderId = 'anthropic' | 'codex' | 'gemini'; -export type TeamProviderBackendId = string; +export type TeamProviderBackendId = 'auto' | 'adapter' | 'api' | 'cli-sdk' | 'codex-native'; export interface TeamLaunchRequest { teamName: string; diff --git a/src/shared/utils/providerBackend.ts b/src/shared/utils/providerBackend.ts index 7d5c3ad0..1f5a8359 100644 --- a/src/shared/utils/providerBackend.ts +++ b/src/shared/utils/providerBackend.ts @@ -1,6 +1,12 @@ -import type { TeamProviderId } from '@shared/types'; +import type { TeamProviderBackendId, TeamProviderId } from '@shared/types'; -type RuntimeProviderId = TeamProviderId; +const TEAM_PROVIDER_BACKEND_IDS = new Set([ + 'auto', + 'adapter', + 'api', + 'cli-sdk', + 'codex-native', +]); function normalizeOptionalBackendId(value: unknown): string | undefined { if (typeof value !== 'string') { @@ -11,8 +17,8 @@ function normalizeOptionalBackendId(value: unknown): string | undefined { } export function getDefaultProviderBackendId( - providerId: TeamProviderId | RuntimeProviderId | undefined -): string | undefined { + providerId: TeamProviderId | undefined +): TeamProviderBackendId | undefined { return providerId === 'codex' ? 'codex-native' : undefined; } @@ -27,20 +33,28 @@ export function isLegacyCodexProviderBackendId( ); } -export function migrateProviderBackendId( - providerId: TeamProviderId | RuntimeProviderId | undefined, +export function isTeamProviderBackendId( providerBackendId: string | null | undefined -): string | undefined { +): providerBackendId is TeamProviderBackendId { + return ( + !!providerBackendId && TEAM_PROVIDER_BACKEND_IDS.has(providerBackendId as TeamProviderBackendId) + ); +} + +export function migrateProviderBackendId( + providerId: TeamProviderId | undefined, + providerBackendId: string | null | undefined +): TeamProviderBackendId | undefined { const normalizedBackendId = normalizeOptionalBackendId(providerBackendId); if (providerId !== 'codex') { - return normalizedBackendId; + return isTeamProviderBackendId(normalizedBackendId) ? normalizedBackendId : undefined; } if (!normalizedBackendId || isLegacyCodexProviderBackendId(normalizedBackendId)) { return 'codex-native'; } - return normalizedBackendId; + return isTeamProviderBackendId(normalizedBackendId) ? normalizedBackendId : undefined; } export function formatProviderBackendLabel(