fix(team): prevent double [1m] suffix on model string during re-launch
This commit is contained in:
parent
644b45942f
commit
bda2e160f7
7 changed files with 93 additions and 9 deletions
|
|
@ -575,6 +575,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|||
multimodelEnabled,
|
||||
storedProviderId,
|
||||
storedEffort: storedEffort === null ? 'medium' : storedEffort,
|
||||
storedLimitContext: localStorage.getItem('team:lastLimitContext') === 'true',
|
||||
getStoredModel: getStoredTeamModel,
|
||||
});
|
||||
setSavedLaunchProviderId(savedProviderId);
|
||||
|
|
@ -590,10 +591,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|||
setSelectedProviderIdRaw(launchPrefill.providerId);
|
||||
setSelectedModelRaw(launchPrefill.model);
|
||||
setSelectedEffortRaw(launchPrefill.effort);
|
||||
setLimitContextRaw(
|
||||
savedRequest?.limitContext === true ||
|
||||
localStorage.getItem('team:lastLimitContext') === 'true'
|
||||
);
|
||||
setLimitContextRaw(launchPrefill.limitContext);
|
||||
setSkipPermissionsRaw(
|
||||
savedRequest?.skipPermissions ??
|
||||
localStorage.getItem('team:lastSkipPermissions') !== 'false'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
GEMINI_UI_DISABLED_REASON,
|
||||
isGeminiUiFrozen,
|
||||
} from '@renderer/utils/geminiUiFreeze';
|
||||
import { stripTrailingOneMillionSuffixes } from '@renderer/utils/teamModelContext';
|
||||
import {
|
||||
doesTeamModelCarryProviderBrand,
|
||||
getProviderScopedTeamModelLabel,
|
||||
|
|
@ -99,7 +100,7 @@ export function computeEffectiveTeamModel(
|
|||
limitContext: boolean,
|
||||
providerId: 'anthropic' | 'codex' | 'gemini' = 'anthropic'
|
||||
): string | undefined {
|
||||
const base = selectedModel || undefined;
|
||||
const base = stripTrailingOneMillionSuffixes(selectedModel);
|
||||
if (providerId !== 'anthropic') return base;
|
||||
if (limitContext) return base;
|
||||
if (base === 'haiku') return base;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
|
||||
import { stripTrailingOneMillionSuffixes } from '@renderer/utils/teamModelContext';
|
||||
import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
|
||||
|
|
@ -9,6 +10,7 @@ interface PreviousLaunchParamsLike {
|
|||
providerId?: TeamProviderId;
|
||||
model?: string;
|
||||
effort?: string;
|
||||
limitContext?: boolean;
|
||||
}
|
||||
|
||||
interface LaunchDialogPrefillInput {
|
||||
|
|
@ -18,6 +20,7 @@ interface LaunchDialogPrefillInput {
|
|||
multimodelEnabled: boolean;
|
||||
storedProviderId: TeamProviderId;
|
||||
storedEffort: string;
|
||||
storedLimitContext: boolean;
|
||||
getStoredModel: (providerId: TeamProviderId) => string;
|
||||
}
|
||||
|
||||
|
|
@ -25,6 +28,7 @@ interface LaunchDialogPrefillResult {
|
|||
providerId: TeamProviderId;
|
||||
model: string;
|
||||
effort: string;
|
||||
limitContext: boolean;
|
||||
}
|
||||
|
||||
function normalizeModelCandidate(model: string | undefined): string {
|
||||
|
|
@ -32,7 +36,7 @@ function normalizeModelCandidate(model: string | undefined): string {
|
|||
if (!trimmed || trimmed === 'default' || trimmed === '__default__') {
|
||||
return '';
|
||||
}
|
||||
return trimmed;
|
||||
return stripTrailingOneMillionSuffixes(trimmed) ?? '';
|
||||
}
|
||||
|
||||
function canReuseModelForSelectedProvider(
|
||||
|
|
@ -52,6 +56,7 @@ export function resolveLaunchDialogPrefill({
|
|||
multimodelEnabled,
|
||||
storedProviderId,
|
||||
storedEffort,
|
||||
storedLimitContext,
|
||||
getStoredModel,
|
||||
}: LaunchDialogPrefillInput): LaunchDialogPrefillResult {
|
||||
const currentLead = members.find((member) => isLeadMember(member));
|
||||
|
|
@ -88,6 +93,8 @@ export function resolveLaunchDialogPrefill({
|
|||
|
||||
const effort =
|
||||
currentLead?.effort ?? savedRequest?.effort ?? previousLaunchParams?.effort ?? storedEffort;
|
||||
const limitContext =
|
||||
previousLaunchParams?.limitContext ?? savedRequest?.limitContext ?? storedLimitContext;
|
||||
|
||||
return {
|
||||
providerId,
|
||||
|
|
@ -95,5 +102,6 @@ export function resolveLaunchDialogPrefill({
|
|||
? normalizeTeamModelForUi(providerId, matchingModel)
|
||||
: getStoredModel(providerId),
|
||||
effort,
|
||||
limitContext,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
canDisplayTaskChangesForOptions,
|
||||
type TaskChangeRequestOptions,
|
||||
} from '@renderer/utils/taskChangeRequest';
|
||||
import { stripTrailingOneMillionSuffixes } from '@renderer/utils/teamModelContext';
|
||||
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
|
||||
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
|
|
@ -1220,8 +1221,7 @@ function saveLaunchParams(teamName: string, params: TeamLaunchParams): void {
|
|||
* E.g. 'opus[1m]' → 'opus', 'sonnet' → 'sonnet', undefined → undefined.
|
||||
*/
|
||||
function extractBaseModel(raw?: string): string | undefined {
|
||||
if (!raw) return undefined;
|
||||
return raw.replace(/\[1m\]$/, '') || undefined;
|
||||
return stripTrailingOneMillionSuffixes(raw);
|
||||
}
|
||||
|
||||
const TOOL_APPROVAL_PREFIX = 'team:toolApprovalSettings:';
|
||||
|
|
|
|||
8
src/renderer/utils/teamModelContext.ts
Normal file
8
src/renderer/utils/teamModelContext.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export function stripTrailingOneMillionSuffixes(model: string | undefined): string | undefined {
|
||||
const trimmed = model?.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return trimmed.replace(/(?:\[1m\])+$/, '') || undefined;
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { formatTeamModelSummary } from '@renderer/components/team/dialogs/TeamModelSelector';
|
||||
import {
|
||||
computeEffectiveTeamModel,
|
||||
formatTeamModelSummary,
|
||||
} from '@renderer/components/team/dialogs/TeamModelSelector';
|
||||
import {
|
||||
GPT_5_1_CODEX_MINI_UI_DISABLED_REASON,
|
||||
GPT_5_3_CODEX_SPARK_UI_DISABLED_REASON,
|
||||
|
|
@ -36,3 +39,34 @@ describe('formatTeamModelSummary', () => {
|
|||
expect(normalizeTeamModelForUi('codex', 'gpt-5.4-mini')).toBe('gpt-5.4-mini');
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeEffectiveTeamModel', () => {
|
||||
it('appends [1m] for anthropic models', () => {
|
||||
expect(computeEffectiveTeamModel('opus', false, 'anthropic')).toBe('opus[1m]');
|
||||
expect(computeEffectiveTeamModel('sonnet', false, 'anthropic')).toBe('sonnet[1m]');
|
||||
});
|
||||
|
||||
it('does not double-append [1m] when input already has it', () => {
|
||||
expect(computeEffectiveTeamModel('opus[1m]', false, 'anthropic')).toBe('opus[1m]');
|
||||
expect(computeEffectiveTeamModel('sonnet[1m]', false, 'anthropic')).toBe('sonnet[1m]');
|
||||
expect(computeEffectiveTeamModel('opus[1m][1m]', false, 'anthropic')).toBe('opus[1m]');
|
||||
});
|
||||
|
||||
it('defaults to opus[1m] when no model selected', () => {
|
||||
expect(computeEffectiveTeamModel('', false, 'anthropic')).toBe('opus[1m]');
|
||||
});
|
||||
|
||||
it('returns base model without [1m] when limitContext is true', () => {
|
||||
expect(computeEffectiveTeamModel('opus', true, 'anthropic')).toBe('opus');
|
||||
expect(computeEffectiveTeamModel('opus[1m]', true, 'anthropic')).toBe('opus');
|
||||
expect(computeEffectiveTeamModel('opus[1m][1m]', true, 'anthropic')).toBe('opus');
|
||||
});
|
||||
|
||||
it('returns haiku as-is', () => {
|
||||
expect(computeEffectiveTeamModel('haiku', false, 'anthropic')).toBe('haiku');
|
||||
});
|
||||
|
||||
it('returns non-anthropic models as-is', () => {
|
||||
expect(computeEffectiveTeamModel('gpt-5.4', false, 'codex')).toBe('gpt-5.4');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
multimodelEnabled: true,
|
||||
storedProviderId: 'anthropic',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
anthropic: 'haiku',
|
||||
codex: 'gpt-5.4',
|
||||
|
|
@ -48,6 +49,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
providerId: 'codex',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -78,6 +80,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
multimodelEnabled: true,
|
||||
storedProviderId: 'anthropic',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
anthropic: 'haiku',
|
||||
codex: 'gpt-5.4',
|
||||
|
|
@ -88,6 +91,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
providerId: 'codex',
|
||||
model: 'gpt-5.4',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -103,6 +107,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
multimodelEnabled: true,
|
||||
storedProviderId: 'anthropic',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
anthropic: 'haiku',
|
||||
codex: 'gpt-5.4',
|
||||
|
|
@ -113,6 +118,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
providerId: 'codex',
|
||||
model: 'gpt-5.3-codex',
|
||||
effort: 'high',
|
||||
limitContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -134,6 +140,7 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
multimodelEnabled: true,
|
||||
storedProviderId: 'anthropic',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
anthropic: 'haiku',
|
||||
codex: 'gpt-5.4',
|
||||
|
|
@ -144,6 +151,34 @@ describe('resolveLaunchDialogPrefill', () => {
|
|||
providerId: 'anthropic',
|
||||
model: 'haiku',
|
||||
effort: 'medium',
|
||||
limitContext: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('prefers per-team launch params for limitContext over stale global storage', () => {
|
||||
const result = resolveLaunchDialogPrefill({
|
||||
members: [],
|
||||
savedRequest: null,
|
||||
previousLaunchParams: {
|
||||
providerId: 'anthropic',
|
||||
model: 'opus[1m][1m]',
|
||||
effort: 'high',
|
||||
limitContext: true,
|
||||
},
|
||||
multimodelEnabled: true,
|
||||
storedProviderId: 'anthropic',
|
||||
storedEffort: 'medium',
|
||||
storedLimitContext: false,
|
||||
getStoredModel: createStoredModelGetter({
|
||||
anthropic: 'haiku',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
providerId: 'anthropic',
|
||||
model: 'opus',
|
||||
effort: 'high',
|
||||
limitContext: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue