feat(runtime): switch codex default to native with hidden fallback
This commit is contained in:
parent
b5dfa14868
commit
e90bdc5b7f
15 changed files with 292 additions and 27 deletions
|
|
@ -342,7 +342,7 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||
runtime: {
|
||||
providerBackends: {
|
||||
gemini: 'auto',
|
||||
codex: 'auto',
|
||||
codex: 'codex-native',
|
||||
},
|
||||
},
|
||||
display: {
|
||||
|
|
|
|||
|
|
@ -844,7 +844,7 @@ export const CliStatusBanner = (): React.JSX.Element | null => {
|
|||
|
||||
const currentBackends = appConfig?.runtime?.providerBackends ?? {
|
||||
gemini: 'auto' as const,
|
||||
codex: 'auto' as const,
|
||||
codex: 'codex-native' as const,
|
||||
};
|
||||
|
||||
await updateConfig('runtime', {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ import {
|
|||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@renderer/components/ui/tooltip';
|
||||
import {
|
||||
formatProviderBackendLabel,
|
||||
isLegacyCodexProviderBackendId,
|
||||
} from '@renderer/utils/providerBackendIdentity';
|
||||
|
||||
import type { CliProviderStatus } from '@shared/types';
|
||||
|
||||
|
|
@ -53,10 +57,43 @@ export function getProviderRuntimeBackendAudienceLabel(
|
|||
return option.audience === 'internal' ? 'Internal' : null;
|
||||
}
|
||||
|
||||
export function getVisibleProviderRuntimeBackendOptions(
|
||||
provider: CliProviderStatus
|
||||
): NonNullable<CliProviderStatus['availableBackends']> {
|
||||
const options = provider.availableBackends ?? [];
|
||||
if (provider.providerId !== 'codex') {
|
||||
return options;
|
||||
}
|
||||
|
||||
const selectedBackendId = provider.selectedBackendId ?? null;
|
||||
return options.filter(
|
||||
(option) => !isLegacyCodexProviderBackendId(option.id) || option.id === selectedBackendId
|
||||
);
|
||||
}
|
||||
|
||||
export function getOptionDisplayLabel(
|
||||
provider: CliProviderStatus,
|
||||
option: NonNullable<CliProviderStatus['availableBackends']>[number],
|
||||
resolvedOption: NonNullable<CliProviderStatus['availableBackends']>[number] | null
|
||||
): string {
|
||||
if (provider.providerId === 'codex') {
|
||||
if (option.id === 'auto') {
|
||||
const currentLabel =
|
||||
resolvedOption && resolvedOption.id !== 'auto'
|
||||
? (formatProviderBackendLabel(provider.providerId, resolvedOption.id) ??
|
||||
resolvedOption.label)
|
||||
: null;
|
||||
return currentLabel
|
||||
? `Legacy auto fallback (currently: ${currentLabel})`
|
||||
: 'Legacy auto fallback';
|
||||
}
|
||||
|
||||
const legacyLabel = formatProviderBackendLabel(provider.providerId, option.id);
|
||||
if (legacyLabel) {
|
||||
return legacyLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (option.id !== 'auto') {
|
||||
return option.label;
|
||||
}
|
||||
|
|
@ -77,7 +114,7 @@ export function getProviderRuntimeBackendSummary(provider: CliProviderStatus): s
|
|||
const selectedBackendId = provider.selectedBackendId ?? options[0]?.id ?? '';
|
||||
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
||||
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
||||
const parts = [getOptionDisplayLabel(selectedOption, resolvedOption)];
|
||||
const parts = [getOptionDisplayLabel(provider, selectedOption, resolvedOption)];
|
||||
const audienceLabel = getProviderRuntimeBackendAudienceLabel(selectedOption);
|
||||
const stateLabel = getProviderRuntimeBackendStateLabel(selectedOption);
|
||||
|
||||
|
|
@ -96,7 +133,7 @@ export const ProviderRuntimeBackendSelector = ({
|
|||
disabled = false,
|
||||
onSelect,
|
||||
}: Props): React.JSX.Element | null => {
|
||||
const options = provider.availableBackends ?? [];
|
||||
const options = getVisibleProviderRuntimeBackendOptions(provider);
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -104,7 +141,7 @@ export const ProviderRuntimeBackendSelector = ({
|
|||
const selectedBackendId = provider.selectedBackendId ?? options[0]?.id ?? '';
|
||||
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
||||
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
||||
const selectedLabel = getOptionDisplayLabel(selectedOption, resolvedOption);
|
||||
const selectedLabel = getOptionDisplayLabel(provider, selectedOption, resolvedOption);
|
||||
const selectedStateLabel = getProviderRuntimeBackendStateLabel(selectedOption);
|
||||
const selectedAudienceLabel = getProviderRuntimeBackendAudienceLabel(selectedOption);
|
||||
|
||||
|
|
@ -153,7 +190,9 @@ export const ProviderRuntimeBackendSelector = ({
|
|||
>
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate">{getOptionDisplayLabel(option, resolvedOption)}</span>
|
||||
<span className="truncate">
|
||||
{getOptionDisplayLabel(provider, option, resolvedOption)}
|
||||
</span>
|
||||
{option.recommended ? (
|
||||
<span
|
||||
className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ const API_KEY_PROVIDER_CONFIG: Record<
|
|||
name: 'OpenAI API Key',
|
||||
title: 'API key',
|
||||
description:
|
||||
'Use `OPENAI_API_KEY` with the public OpenAI Responses API. Your Codex subscription session stays available when you switch back.',
|
||||
'Use `OPENAI_API_KEY` for Codex runs that need API-key billing. Codex native stays the primary runtime path while your subscription session remains available when you switch back.',
|
||||
placeholder: 'sk-proj-...',
|
||||
},
|
||||
gemini: {
|
||||
|
|
@ -125,10 +125,10 @@ function getConnectionDescription(provider: CliProviderStatus): string {
|
|||
return 'Choose how app-launched Anthropic sessions authenticate.';
|
||||
case 'codex':
|
||||
return hasExplicitRuntimeBackends(provider)
|
||||
? 'Choose which credentials app-launched Codex sessions should use. Runtime backend is configured separately below.'
|
||||
? 'Choose which credentials app-launched Codex sessions should use. Codex native remains the primary runtime path unless you intentionally keep a legacy fallback selected.'
|
||||
: provider.connection?.apiKeyBetaEnabled
|
||||
? 'Choose whether app-launched Codex sessions use your Codex subscription or an OpenAI API key.'
|
||||
: 'Codex uses your subscription session by default. Enable API key mode if you want to switch Codex credential routing to API-key billing.';
|
||||
? 'Choose whether app-launched Codex sessions use your Codex subscription or API-key billing.'
|
||||
: 'Codex native uses your subscription session by default. Enable API key mode only if you want native Codex launches to consume API-key credentials.';
|
||||
case 'gemini':
|
||||
return 'Configure optional API access. CLI SDK and ADC are still discovered automatically.';
|
||||
}
|
||||
|
|
@ -140,8 +140,8 @@ function getRuntimeDescription(provider: CliProviderStatus): string {
|
|||
return 'Anthropic currently has no separate runtime backend selector.';
|
||||
case 'codex':
|
||||
return hasExplicitRuntimeBackends(provider)
|
||||
? 'Choose which Codex runtime backend multimodel should use. Connection method only controls credentials.'
|
||||
: 'Codex runtime selection follows the active connection method automatically.';
|
||||
? 'Choose which Codex runtime backend multimodel should use. Codex native is the default. Legacy fallbacks stay hidden unless they are already selected.'
|
||||
: 'Codex native is the default runtime path. Connection method only controls which credentials the runtime can consume.';
|
||||
case 'gemini':
|
||||
return 'Choose which Gemini runtime backend multimodel should use.';
|
||||
}
|
||||
|
|
@ -161,8 +161,8 @@ function getAuthModeDescription(providerId: CliProviderId, authMode: CliProvider
|
|||
|
||||
if (providerId === 'codex') {
|
||||
return authMode === 'api_key'
|
||||
? 'Use API-key credentials for app-launched Codex sessions. The selected runtime backend decides how those credentials are consumed.'
|
||||
: 'Use your Codex subscription session. API-key-only backends remain unavailable until you switch this credential mode.';
|
||||
? 'Use API-key credentials for app-launched Codex sessions. Codex native remains the primary runtime path and will consume those credentials when needed.'
|
||||
: 'Use your Codex subscription session. API-key-only fallback paths remain unavailable until you switch this credential mode.';
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ export function useSettingsHandlers({
|
|||
runtime: {
|
||||
providerBackends: {
|
||||
gemini: 'auto',
|
||||
codex: 'auto',
|
||||
codex: 'codex-native',
|
||||
},
|
||||
},
|
||||
display: {
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ export const CliStatusSection = (): React.JSX.Element | null => {
|
|||
async (providerId: CliProviderId, backendId: string) => {
|
||||
const currentBackends = appConfig?.runtime?.providerBackends ?? {
|
||||
gemini: 'auto' as const,
|
||||
codex: 'auto' as const,
|
||||
codex: 'codex-native' as const,
|
||||
};
|
||||
|
||||
if (providerId !== 'gemini' && providerId !== 'codex') {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import {
|
|||
normalizeCreateLaunchProviderForUi,
|
||||
} from '@renderer/utils/geminiUiFreeze';
|
||||
import { normalizePath } from '@renderer/utils/pathNormalize';
|
||||
import { resolveEffectiveProviderBackendId } from '@renderer/utils/providerBackendIdentity';
|
||||
import { resolveUiOwnedProviderBackendId } from '@renderer/utils/providerBackendIdentity';
|
||||
import {
|
||||
getTeamModelSelectionError,
|
||||
normalizeExplicitTeamModelForUi,
|
||||
|
|
@ -974,8 +974,10 @@ export const CreateTeamDialog = ({
|
|||
prompt: prompt.trim() || undefined,
|
||||
providerId: selectedProviderId,
|
||||
providerBackendId:
|
||||
resolveEffectiveProviderBackendId(runtimeProviderStatusById.get(selectedProviderId)) ??
|
||||
undefined,
|
||||
resolveUiOwnedProviderBackendId(
|
||||
selectedProviderId,
|
||||
runtimeProviderStatusById.get(selectedProviderId)
|
||||
) ?? undefined,
|
||||
model: effectiveModel,
|
||||
effort: (selectedEffort as EffortLevel) || undefined,
|
||||
limitContext,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import {
|
|||
normalizeCreateLaunchProviderForUi,
|
||||
} from '@renderer/utils/geminiUiFreeze';
|
||||
import { normalizePath } from '@renderer/utils/pathNormalize';
|
||||
import { resolveEffectiveProviderBackendId } from '@renderer/utils/providerBackendIdentity';
|
||||
import { resolveUiOwnedProviderBackendId } from '@renderer/utils/providerBackendIdentity';
|
||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||
import {
|
||||
getTeamModelSelectionError,
|
||||
|
|
@ -1403,7 +1403,8 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|||
prompt: promptDraft.value.trim() || undefined,
|
||||
providerId: selectedProviderId,
|
||||
providerBackendId:
|
||||
resolveEffectiveProviderBackendId(
|
||||
resolveUiOwnedProviderBackendId(
|
||||
selectedProviderId,
|
||||
runtimeProviderStatusById.get(selectedProviderId)
|
||||
) ??
|
||||
previousLaunchParams?.providerBackendId ??
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog';
|
||||
import { formatProviderBackendLabel } from '@renderer/utils/providerBackendIdentity';
|
||||
import { AlertTriangle, CheckCircle2, Loader2 } from 'lucide-react';
|
||||
|
||||
import type { TeamProviderId } from '@shared/types';
|
||||
|
|
@ -34,7 +35,7 @@ export function getProvisioningProviderBackendSummary(
|
|||
provider:
|
||||
| Pick<
|
||||
CliProviderStatus,
|
||||
'selectedBackendId' | 'resolvedBackendId' | 'availableBackends' | 'backend'
|
||||
'providerId' | 'selectedBackendId' | 'resolvedBackendId' | 'availableBackends' | 'backend'
|
||||
>
|
||||
| null
|
||||
| undefined
|
||||
|
|
@ -47,9 +48,21 @@ export function getProvisioningProviderBackendSummary(
|
|||
const optionById = new Map(options.map((option) => [option.id, option.label]));
|
||||
const effectiveBackendId = provider.resolvedBackendId ?? provider.selectedBackendId;
|
||||
const effectiveOption = options.find((option) => option.id === effectiveBackendId) ?? null;
|
||||
const inferredProviderId =
|
||||
provider.providerId ??
|
||||
(effectiveBackendId === 'codex-native' ||
|
||||
effectiveBackendId === 'adapter' ||
|
||||
options.some((option) => option.id === 'codex-native' || option.id === 'adapter')
|
||||
? 'codex'
|
||||
: undefined);
|
||||
const normalizedLabel =
|
||||
formatProviderBackendLabel(inferredProviderId, effectiveBackendId ?? undefined) ?? null;
|
||||
|
||||
const baseSummary = effectiveBackendId
|
||||
? (optionById.get(effectiveBackendId) ?? provider.backend?.label ?? effectiveBackendId)
|
||||
? (normalizedLabel ??
|
||||
optionById.get(effectiveBackendId) ??
|
||||
provider.backend?.label ??
|
||||
effectiveBackendId)
|
||||
: (provider.backend?.label ?? null);
|
||||
|
||||
if (!baseSummary) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
|
||||
import { getDefaultProviderBackendId } from '@renderer/utils/providerBackendIdentity';
|
||||
import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability';
|
||||
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
|
|
@ -106,6 +107,7 @@ export function resolveLaunchDialogPrefill({
|
|||
providerBackendId:
|
||||
previousLaunchParams?.providerBackendId?.trim() ||
|
||||
savedRequest?.providerBackendId?.trim() ||
|
||||
getDefaultProviderBackendId(providerId) ||
|
||||
undefined,
|
||||
model: matchingModel
|
||||
? normalizeExplicitTeamModelForUi(providerId, matchingModel)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,46 @@ function normalizeOptionalBackendId(value: string | null | undefined): string |
|
|||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function getDefaultProviderBackendId(
|
||||
providerId: TeamProviderId | CliProviderStatus['providerId'] | undefined
|
||||
): string | undefined {
|
||||
return providerId === 'codex' ? 'codex-native' : undefined;
|
||||
}
|
||||
|
||||
export function isLegacyCodexProviderBackendId(
|
||||
providerBackendId: string | null | undefined
|
||||
): boolean {
|
||||
const normalizedBackendId = normalizeOptionalBackendId(providerBackendId);
|
||||
return (
|
||||
normalizedBackendId === 'auto' ||
|
||||
normalizedBackendId === 'adapter' ||
|
||||
normalizedBackendId === 'api'
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveEffectiveProviderBackendId(
|
||||
provider: Pick<CliProviderStatus, 'selectedBackendId' | 'resolvedBackendId'> | null | undefined
|
||||
): string | undefined {
|
||||
return normalizeOptionalBackendId(provider?.resolvedBackendId ?? provider?.selectedBackendId);
|
||||
}
|
||||
|
||||
export function formatTeamProviderBackendLabel(
|
||||
export function resolveUiOwnedProviderBackendId(
|
||||
providerId: TeamProviderId | CliProviderStatus['providerId'] | undefined,
|
||||
provider: Pick<CliProviderStatus, 'selectedBackendId' | 'resolvedBackendId'> | null | undefined
|
||||
): string | undefined {
|
||||
const normalizedProviderId = providerId ?? undefined;
|
||||
if (normalizedProviderId === 'codex') {
|
||||
const selectedBackendId = normalizeOptionalBackendId(provider?.selectedBackendId);
|
||||
if (!selectedBackendId || selectedBackendId === 'auto') {
|
||||
return 'codex-native';
|
||||
}
|
||||
return selectedBackendId;
|
||||
}
|
||||
|
||||
return resolveEffectiveProviderBackendId(provider);
|
||||
}
|
||||
|
||||
export function formatProviderBackendLabel(
|
||||
providerId: TeamProviderId | undefined,
|
||||
providerBackendId: string | undefined
|
||||
): string | undefined {
|
||||
|
|
@ -26,11 +59,11 @@ export function formatTeamProviderBackendLabel(
|
|||
case 'codex-native':
|
||||
return 'Codex native';
|
||||
case 'adapter':
|
||||
return 'Default adapter';
|
||||
return 'Legacy adapter fallback';
|
||||
case 'api':
|
||||
return 'OpenAI API';
|
||||
return 'Legacy OpenAI fallback';
|
||||
case 'auto':
|
||||
return undefined;
|
||||
return 'Legacy auto fallback';
|
||||
default:
|
||||
return normalizedBackendId;
|
||||
}
|
||||
|
|
@ -51,3 +84,10 @@ export function formatTeamProviderBackendLabel(
|
|||
|
||||
return normalizedBackendId;
|
||||
}
|
||||
|
||||
export function formatTeamProviderBackendLabel(
|
||||
providerId: TeamProviderId | undefined,
|
||||
providerBackendId: string | undefined
|
||||
): string | undefined {
|
||||
return formatProviderBackendLabel(providerId, providerBackendId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getOptionDisplayLabel,
|
||||
getProviderRuntimeBackendAudienceLabel,
|
||||
getProviderRuntimeBackendStateLabel,
|
||||
getProviderRuntimeBackendSummary,
|
||||
getVisibleProviderRuntimeBackendOptions,
|
||||
} from '@renderer/components/runtime/ProviderRuntimeBackendSelector';
|
||||
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
||||
|
||||
|
|
@ -106,4 +108,81 @@ describe('ProviderRuntimeBackendSelector helpers', () => {
|
|||
'Codex native - internal - auth required'
|
||||
);
|
||||
});
|
||||
|
||||
it('hides codex legacy fallbacks from normal selectors when native is selected', () => {
|
||||
const provider = createCodexProvider({
|
||||
availableBackends: [
|
||||
{
|
||||
id: 'auto',
|
||||
label: 'Auto',
|
||||
description: 'Automatically choose the best backend.',
|
||||
selectable: true,
|
||||
recommended: false,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'general',
|
||||
},
|
||||
{
|
||||
id: 'api',
|
||||
label: 'OpenAI API',
|
||||
description: 'Legacy public Responses API fallback.',
|
||||
selectable: true,
|
||||
recommended: false,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'internal',
|
||||
},
|
||||
{
|
||||
id: 'codex-native',
|
||||
label: 'Codex native',
|
||||
description: 'Use the local codex exec JSON seam.',
|
||||
selectable: true,
|
||||
recommended: true,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'general',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(getVisibleProviderRuntimeBackendOptions(provider).map((option) => option.id)).toEqual([
|
||||
'codex-native',
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps an explicitly selected legacy codex fallback readable during the soak', () => {
|
||||
const provider = createCodexProvider({
|
||||
selectedBackendId: 'api',
|
||||
resolvedBackendId: 'api',
|
||||
availableBackends: [
|
||||
{
|
||||
id: 'api',
|
||||
label: 'OpenAI API',
|
||||
description: 'Legacy public Responses API fallback.',
|
||||
selectable: true,
|
||||
recommended: false,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'internal',
|
||||
},
|
||||
{
|
||||
id: 'codex-native',
|
||||
label: 'Codex native',
|
||||
description: 'Use the local codex exec JSON seam.',
|
||||
selectable: true,
|
||||
recommended: true,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'general',
|
||||
},
|
||||
],
|
||||
});
|
||||
const visibleOptions = getVisibleProviderRuntimeBackendOptions(provider);
|
||||
|
||||
expect(visibleOptions.map((option) => option.id)).toEqual(['api', 'codex-native']);
|
||||
expect(getOptionDisplayLabel(provider, visibleOptions[0], null)).toBe(
|
||||
'Legacy OpenAI fallback'
|
||||
);
|
||||
expect(getProviderRuntimeBackendSummary(provider)).toBe('Legacy OpenAI fallback - internal');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -187,4 +187,40 @@ describe('ProvisioningProviderStatusList', () => {
|
|||
})
|
||||
).toBe('Codex native - internal, locked');
|
||||
});
|
||||
|
||||
it('marks explicit legacy codex fallback summaries as legacy during the soak', () => {
|
||||
expect(
|
||||
getProvisioningProviderBackendSummary({
|
||||
providerId: 'codex',
|
||||
selectedBackendId: 'api',
|
||||
resolvedBackendId: 'api',
|
||||
backend: {
|
||||
kind: 'api',
|
||||
label: 'OpenAI API',
|
||||
},
|
||||
availableBackends: [
|
||||
{
|
||||
id: 'api',
|
||||
label: 'OpenAI API',
|
||||
description: 'Legacy public Responses API fallback.',
|
||||
selectable: true,
|
||||
recommended: false,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'internal',
|
||||
},
|
||||
{
|
||||
id: 'codex-native',
|
||||
label: 'Codex native',
|
||||
description: 'Use codex exec JSON mode.',
|
||||
selectable: true,
|
||||
recommended: true,
|
||||
available: true,
|
||||
state: 'ready',
|
||||
audience: 'general',
|
||||
},
|
||||
],
|
||||
})
|
||||
).toBe('Legacy OpenAI fallback - internal');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
|
||||
expect(result).toEqual({
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'codex-native',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
|
|
@ -89,6 +90,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
|
||||
expect(result).toEqual({
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'codex-native',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
|
|
@ -156,6 +158,37 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('defaults new Codex launch flows to codex-native when no backend was persisted', () => {
|
||||
const result = resolveLaunchDialogPrefill({
|
||||
members: [
|
||||
{
|
||||
name: 'team-lead',
|
||||
agentType: 'team-lead',
|
||||
providerId: 'codex',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
},
|
||||
] as ResolvedTeamMember[],
|
||||
savedRequest: null,
|
||||
previousLaunchParams: undefined,
|
||||
multimodelEnabled: true,
|
||||
storedProviderId: 'codex',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
codex: 'gpt-5.4',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'codex-native',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not carry a frozen Gemini model into an Anthropic fallback', () => {
|
||||
const members = [
|
||||
{
|
||||
|
|
@ -237,6 +270,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
|
||||
expect(result).toEqual({
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'codex-native',
|
||||
model: 'custom-model[1m]',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
|
|
@ -264,6 +298,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
|
||||
expect(result).toEqual({
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'codex-native',
|
||||
model: 'custom-model[1m]',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
|
|
|
|||
|
|
@ -98,4 +98,22 @@ describe('resolveMemberRuntimeSummary', () => {
|
|||
)
|
||||
).toBe('5.4 Mini · Medium · Codex native');
|
||||
});
|
||||
|
||||
it('marks persisted legacy Codex lanes as legacy fallbacks in the runtime summary', () => {
|
||||
const member = createMember({ model: 'gpt-5.4-mini' });
|
||||
|
||||
expect(
|
||||
resolveMemberRuntimeSummary(
|
||||
member,
|
||||
{
|
||||
providerId: 'codex',
|
||||
providerBackendId: 'api',
|
||||
model: 'gpt-5.4-mini',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
},
|
||||
undefined
|
||||
)
|
||||
).toBe('5.4 Mini · Medium · Legacy OpenAI fallback');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue