refactor(team): extract launch params helpers

This commit is contained in:
777genius 2026-05-22 11:28:07 +03:00
parent 7f828c2e63
commit 67a6d711eb
3 changed files with 227 additions and 85 deletions

View file

@ -11,7 +11,6 @@ import {
canDisplayTaskChangesForOptions,
type TaskChangeRequestOptions,
} from '@renderer/utils/taskChangeRequest';
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
import { getMemberColorByName } from '@shared/constants/memberColors';
@ -19,7 +18,6 @@ import { DEFAULT_TEAM_GRAPH_LAYOUT_MODE } from '@shared/constants/teamGraphLayou
import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
import { isLeadMember } from '@shared/utils/leadDetection';
import { createLogger } from '@shared/utils/logger';
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import { buildTeamGraphDefaultLayoutSeed } from '@shared/utils/teamGraphDefaultLayout';
import { getStableTeamOwnerId } from '@shared/utils/teamStableOwnerId';
@ -50,6 +48,11 @@ import {
mapSendMessageError,
shouldInvalidateCachedTeamDataForError,
} from '../team/teamErrorPolicies';
import {
areTeamLaunchParamsEqual,
buildLaunchParamsFromRuntimeRequest,
type TeamLaunchParams,
} from '../team/teamLaunchParams';
import {
captureTeamLocalStateEpoch,
clearAllTeamLocalStateEpochs,
@ -126,7 +129,6 @@ import type {
AddTaskCommentRequest,
CreateTaskRequest,
CrossTeamSendRequest,
EffortLevel,
GlobalTask,
InboxMessage,
KanbanColumnId,
@ -148,7 +150,6 @@ import type {
TeamLaunchRequest,
TeamMemberActivityMeta,
TeamMemberSnapshot,
TeamProviderId,
TeamProvisioningProgress,
TeamSummary,
TeamTask,
@ -167,6 +168,7 @@ export {
selectTeamMemberSnapshotsForName,
selectTeamTasksForName,
} from '../team/teamDataSelectors';
export type { TeamLaunchParams } from '../team/teamLaunchParams';
export type {
RefreshTeamMessagesHeadResult,
TeamMessagesCacheEntry,
@ -1226,16 +1228,6 @@ export interface PendingTeamSectionFocusState {
section: TeamSectionTarget;
}
/** Per-team launch parameters shown in the header badge. */
export interface TeamLaunchParams {
providerId?: TeamProviderId;
providerBackendId?: string;
model?: string; // 'opus' | 'sonnet' | 'haiku'
effort?: EffortLevel;
fastMode?: 'inherit' | 'on' | 'off';
limitContext?: boolean;
}
const resolvedMembersSelectorCache = new Map<
string,
{
@ -2278,77 +2270,6 @@ function saveLaunchParams(teamName: string, params: TeamLaunchParams): void {
}
}
/**
* Extract the base model name from the raw model string sent to CLI.
* E.g. 'opus[1m]' 'opus', 'sonnet' 'sonnet', undefined undefined.
*/
function extractBaseModel(raw?: string, providerId?: TeamProviderId): string | undefined {
return extractProviderScopedBaseModel(raw, providerId);
}
function buildLaunchParamsFromRuntimeRequest(
request: Pick<
TeamCreateRequest,
'providerId' | 'providerBackendId' | 'model' | 'effort' | 'fastMode' | 'limitContext'
>,
fallback?: TeamLaunchParams
): TeamLaunchParams {
const providerId = request.providerId ?? fallback?.providerId ?? 'anthropic';
const providerChanged =
request.providerId != null &&
fallback?.providerId != null &&
request.providerId !== fallback.providerId;
const hasModel = Object.hasOwn(request, 'model');
const baseModel =
hasModel && typeof request.model === 'string'
? extractBaseModel(request.model, providerId)
: undefined;
const rawProviderBackendId = Object.hasOwn(request, 'providerBackendId')
? request.providerBackendId
: providerChanged
? undefined
: fallback?.providerBackendId;
return {
providerId,
providerBackendId: migrateProviderBackendId(providerId, rawProviderBackendId),
model: hasModel
? baseModel || 'default'
: (providerChanged ? undefined : fallback?.model) || 'default',
effort: Object.hasOwn(request, 'effort')
? request.effort
: providerChanged
? undefined
: fallback?.effort,
fastMode: Object.hasOwn(request, 'fastMode')
? request.fastMode
: providerChanged
? undefined
: fallback?.fastMode,
limitContext:
typeof request.limitContext === 'boolean'
? request.limitContext
: providerChanged
? false
: (fallback?.limitContext ?? false),
};
}
function areTeamLaunchParamsEqual(
left: TeamLaunchParams | undefined,
right: TeamLaunchParams | undefined
): boolean {
if (left === right) return true;
if (!left || !right) return false;
return (
left.providerId === right.providerId &&
left.providerBackendId === right.providerBackendId &&
left.model === right.model &&
left.effort === right.effort &&
left.fastMode === right.fastMode &&
left.limitContext === right.limitContext
);
}
const TOOL_APPROVAL_PREFIX = 'team:toolApprovalSettings:';
function parseToolApprovalSettings(raw: string | null): ToolApprovalSettings {

View file

@ -0,0 +1,89 @@
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
import type {
EffortLevel,
TeamCreateRequest,
TeamFastMode,
TeamProviderId,
} from '@shared/types';
/** Per-team launch parameters shown in the header badge. */
export interface TeamLaunchParams {
providerId?: TeamProviderId;
providerBackendId?: string;
model?: string;
effort?: EffortLevel;
fastMode?: TeamFastMode;
limitContext?: boolean;
}
export function extractBaseModel(
raw?: string,
providerId?: TeamProviderId
): string | undefined {
return extractProviderScopedBaseModel(raw, providerId);
}
export function buildLaunchParamsFromRuntimeRequest(
request: Pick<
TeamCreateRequest,
'providerId' | 'providerBackendId' | 'model' | 'effort' | 'fastMode' | 'limitContext'
>,
fallback?: TeamLaunchParams
): TeamLaunchParams {
const providerId = request.providerId ?? fallback?.providerId ?? 'anthropic';
const providerChanged =
request.providerId != null &&
fallback?.providerId != null &&
request.providerId !== fallback.providerId;
const hasModel = Object.hasOwn(request, 'model');
const baseModel =
hasModel && typeof request.model === 'string'
? extractBaseModel(request.model, providerId)
: undefined;
const rawProviderBackendId = Object.hasOwn(request, 'providerBackendId')
? request.providerBackendId
: providerChanged
? undefined
: fallback?.providerBackendId;
return {
providerId,
providerBackendId: migrateProviderBackendId(providerId, rawProviderBackendId),
model: hasModel
? baseModel || 'default'
: (providerChanged ? undefined : fallback?.model) || 'default',
effort: Object.hasOwn(request, 'effort')
? request.effort
: providerChanged
? undefined
: fallback?.effort,
fastMode: Object.hasOwn(request, 'fastMode')
? request.fastMode
: providerChanged
? undefined
: fallback?.fastMode,
limitContext:
typeof request.limitContext === 'boolean'
? request.limitContext
: providerChanged
? false
: (fallback?.limitContext ?? false),
};
}
export function areTeamLaunchParamsEqual(
left: TeamLaunchParams | undefined,
right: TeamLaunchParams | undefined
): boolean {
if (left === right) return true;
if (!left || !right) return false;
return (
left.providerId === right.providerId &&
left.providerBackendId === right.providerBackendId &&
left.model === right.model &&
left.effort === right.effort &&
left.fastMode === right.fastMode &&
left.limitContext === right.limitContext
);
}

View file

@ -0,0 +1,132 @@
import { describe, expect, it } from 'vitest';
import {
areTeamLaunchParamsEqual,
buildLaunchParamsFromRuntimeRequest,
extractBaseModel,
} from '../../../src/renderer/store/team/teamLaunchParams';
import type { TeamLaunchParams } from '../../../src/renderer/store/team/teamLaunchParams';
const codexFallback: TeamLaunchParams = {
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
fastMode: 'on',
limitContext: true,
};
describe('teamLaunchParams', () => {
it('extracts provider-scoped base models', () => {
expect(extractBaseModel(' opus[1m] ', 'anthropic')).toBe('opus');
expect(extractBaseModel('sonnet', 'anthropic')).toBe('sonnet');
expect(extractBaseModel('gpt-5.5[1m]', 'codex')).toBe('gpt-5.5[1m]');
expect(extractBaseModel(' ', 'anthropic')).toBeUndefined();
expect(extractBaseModel(undefined, 'anthropic')).toBeUndefined();
});
it('builds default anthropic launch params without fallback', () => {
expect(buildLaunchParamsFromRuntimeRequest({})).toEqual({
providerId: 'anthropic',
providerBackendId: undefined,
model: 'default',
effort: undefined,
fastMode: undefined,
limitContext: false,
});
});
it('preserves fallback values for metadata-only requests on the same provider', () => {
expect(buildLaunchParamsFromRuntimeRequest({}, codexFallback)).toEqual(codexFallback);
});
it('resets provider-scoped values when the provider changes without explicit fields', () => {
expect(
buildLaunchParamsFromRuntimeRequest(
{
providerId: 'anthropic',
},
codexFallback
)
).toEqual({
providerId: 'anthropic',
providerBackendId: undefined,
model: 'default',
effort: undefined,
fastMode: undefined,
limitContext: false,
});
});
it('uses explicit model, effort, fast mode, and limitContext when present', () => {
expect(
buildLaunchParamsFromRuntimeRequest(
{
providerId: 'anthropic',
model: 'haiku[1m]',
effort: 'low',
fastMode: 'off',
limitContext: false,
},
codexFallback
)
).toEqual({
providerId: 'anthropic',
providerBackendId: undefined,
model: 'haiku',
effort: 'low',
fastMode: 'off',
limitContext: false,
});
});
it('treats an explicit undefined model as Default for the active provider', () => {
expect(
buildLaunchParamsFromRuntimeRequest(
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: undefined,
effort: 'low',
},
codexFallback
)
).toEqual({
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'default',
effort: 'low',
fastMode: 'on',
limitContext: true,
});
});
it('migrates legacy provider backend ids for codex requests', () => {
expect(
buildLaunchParamsFromRuntimeRequest({
providerId: 'codex',
providerBackendId: 'api',
})
).toEqual({
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'default',
effort: undefined,
fastMode: undefined,
limitContext: false,
});
});
it('compares launch params by all persisted fields', () => {
expect(areTeamLaunchParamsEqual(codexFallback, { ...codexFallback })).toBe(true);
expect(
areTeamLaunchParamsEqual(codexFallback, {
...codexFallback,
fastMode: 'off',
})
).toBe(false);
expect(areTeamLaunchParamsEqual(undefined, undefined)).toBe(true);
expect(areTeamLaunchParamsEqual(undefined, codexFallback)).toBe(false);
});
});