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,
|
canDisplayTaskChangesForOptions,
|
||||||
type TaskChangeRequestOptions,
|
type TaskChangeRequestOptions,
|
||||||
} from '@renderer/utils/taskChangeRequest';
|
} from '@renderer/utils/taskChangeRequest';
|
||||||
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
|
|
||||||
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
|
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
|
||||||
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
||||||
import { getMemberColorByName } from '@shared/constants/memberColors';
|
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 { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
|
||||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||||
import { createLogger } from '@shared/utils/logger';
|
import { createLogger } from '@shared/utils/logger';
|
||||||
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
|
|
||||||
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
|
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
|
||||||
import { buildTeamGraphDefaultLayoutSeed } from '@shared/utils/teamGraphDefaultLayout';
|
import { buildTeamGraphDefaultLayoutSeed } from '@shared/utils/teamGraphDefaultLayout';
|
||||||
import { getStableTeamOwnerId } from '@shared/utils/teamStableOwnerId';
|
import { getStableTeamOwnerId } from '@shared/utils/teamStableOwnerId';
|
||||||
|
|
@ -50,6 +48,11 @@ import {
|
||||||
mapSendMessageError,
|
mapSendMessageError,
|
||||||
shouldInvalidateCachedTeamDataForError,
|
shouldInvalidateCachedTeamDataForError,
|
||||||
} from '../team/teamErrorPolicies';
|
} from '../team/teamErrorPolicies';
|
||||||
|
import {
|
||||||
|
areTeamLaunchParamsEqual,
|
||||||
|
buildLaunchParamsFromRuntimeRequest,
|
||||||
|
type TeamLaunchParams,
|
||||||
|
} from '../team/teamLaunchParams';
|
||||||
import {
|
import {
|
||||||
captureTeamLocalStateEpoch,
|
captureTeamLocalStateEpoch,
|
||||||
clearAllTeamLocalStateEpochs,
|
clearAllTeamLocalStateEpochs,
|
||||||
|
|
@ -126,7 +129,6 @@ import type {
|
||||||
AddTaskCommentRequest,
|
AddTaskCommentRequest,
|
||||||
CreateTaskRequest,
|
CreateTaskRequest,
|
||||||
CrossTeamSendRequest,
|
CrossTeamSendRequest,
|
||||||
EffortLevel,
|
|
||||||
GlobalTask,
|
GlobalTask,
|
||||||
InboxMessage,
|
InboxMessage,
|
||||||
KanbanColumnId,
|
KanbanColumnId,
|
||||||
|
|
@ -148,7 +150,6 @@ import type {
|
||||||
TeamLaunchRequest,
|
TeamLaunchRequest,
|
||||||
TeamMemberActivityMeta,
|
TeamMemberActivityMeta,
|
||||||
TeamMemberSnapshot,
|
TeamMemberSnapshot,
|
||||||
TeamProviderId,
|
|
||||||
TeamProvisioningProgress,
|
TeamProvisioningProgress,
|
||||||
TeamSummary,
|
TeamSummary,
|
||||||
TeamTask,
|
TeamTask,
|
||||||
|
|
@ -167,6 +168,7 @@ export {
|
||||||
selectTeamMemberSnapshotsForName,
|
selectTeamMemberSnapshotsForName,
|
||||||
selectTeamTasksForName,
|
selectTeamTasksForName,
|
||||||
} from '../team/teamDataSelectors';
|
} from '../team/teamDataSelectors';
|
||||||
|
export type { TeamLaunchParams } from '../team/teamLaunchParams';
|
||||||
export type {
|
export type {
|
||||||
RefreshTeamMessagesHeadResult,
|
RefreshTeamMessagesHeadResult,
|
||||||
TeamMessagesCacheEntry,
|
TeamMessagesCacheEntry,
|
||||||
|
|
@ -1226,16 +1228,6 @@ export interface PendingTeamSectionFocusState {
|
||||||
section: TeamSectionTarget;
|
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<
|
const resolvedMembersSelectorCache = new Map<
|
||||||
string,
|
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:';
|
const TOOL_APPROVAL_PREFIX = 'team:toolApprovalSettings:';
|
||||||
|
|
||||||
function parseToolApprovalSettings(raw: string | null): 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