fix(validate): normalize provider backend ids

This commit is contained in:
777genius 2026-04-20 22:06:35 +03:00
parent 66409ca56b
commit 98a9c25cfe
7 changed files with 100 additions and 48 deletions

View file

@ -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');

View file

@ -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,

View file

@ -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,
};

View file

@ -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,

View file

@ -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<CliProviderStatus, 'selectedBackendId' | 'resolvedBackendId'> | 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<CliProviderStatus, 'selectedBackendId' | 'resolvedBackendId'> | null | undefined
): string | undefined {
): TeamProviderBackendId | undefined {
return migrateProviderBackendId(
providerId,
provider?.selectedBackendId ?? provider?.resolvedBackendId

View file

@ -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;

View file

@ -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<TeamProviderBackendId>([
'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(