refactor(team): extract launch params helpers
This commit is contained in:
parent
7f828c2e63
commit
67a6d711eb
3 changed files with 227 additions and 85 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
89
src/renderer/store/team/teamLaunchParams.ts
Normal file
89
src/renderer/store/team/teamLaunchParams.ts
Normal 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
|
||||
);
|
||||
}
|
||||
132
test/renderer/store/teamLaunchParams.test.ts
Normal file
132
test/renderer/store/teamLaunchParams.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue