123 lines
4.6 KiB
TypeScript
123 lines
4.6 KiB
TypeScript
import { fromProvisioningMembers } from '@features/team-runtime-lanes';
|
|
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
|
|
|
|
import type { TeamCreateRequest } from '@shared/types';
|
|
|
|
export type TeamLaunchCompatibilityLevel = 'ready' | 'repairable' | 'unsafe';
|
|
export type TeamLaunchCompatibilityRosterSource = 'members-meta' | 'config' | 'inboxes' | 'missing';
|
|
export type TeamLaunchCompatibilityRepairAction = 'materialize-members-meta';
|
|
|
|
export interface TeamLaunchCompatibilityReport {
|
|
level: TeamLaunchCompatibilityLevel;
|
|
rosterSource: TeamLaunchCompatibilityRosterSource;
|
|
members: TeamCreateRequest['members'];
|
|
warnings: string[];
|
|
blockers: string[];
|
|
repairAction?: TeamLaunchCompatibilityRepairAction;
|
|
}
|
|
|
|
export function isOpenCodeLegacyProvisioningRequest(request: {
|
|
providerId?: unknown;
|
|
members?: readonly { providerId?: unknown; provider?: unknown }[];
|
|
}): boolean {
|
|
return (
|
|
normalizeOptionalTeamProviderId(request.providerId) === 'opencode' ||
|
|
(request.members ?? []).some(
|
|
(member) =>
|
|
normalizeOptionalTeamProviderId(member.providerId) === 'opencode' ||
|
|
normalizeOptionalTeamProviderId(member.provider) === 'opencode'
|
|
)
|
|
);
|
|
}
|
|
|
|
export function isPureOpenCodeProvisioningRequest(request: {
|
|
providerId?: unknown;
|
|
members?: readonly { providerId?: unknown; provider?: unknown }[];
|
|
}): boolean {
|
|
if (!isOpenCodeLegacyProvisioningRequest(request)) {
|
|
return false;
|
|
}
|
|
|
|
const rootProviderId = normalizeOptionalTeamProviderId(request.providerId);
|
|
if (rootProviderId && rootProviderId !== 'opencode') {
|
|
return false;
|
|
}
|
|
|
|
return (request.members ?? []).every((member) => {
|
|
const memberProviderId =
|
|
normalizeOptionalTeamProviderId(member.providerId) ??
|
|
normalizeOptionalTeamProviderId(member.provider);
|
|
return !memberProviderId || memberProviderId === 'opencode';
|
|
});
|
|
}
|
|
|
|
export function getOpenCodeMixedProviderProvisioningError(): string {
|
|
return (
|
|
'This OpenCode mixed-team request is outside the current support scope. ' +
|
|
'Supported mixed teams keep the lead on Anthropic or Codex. OpenCode-led mixed teams still remain blocked in this phase.'
|
|
);
|
|
}
|
|
|
|
export function getMixedLaunchFallbackRecoveryError(): string {
|
|
return 'This old mixed team is missing stable member metadata. Open Edit Team and save the roster once before launching.';
|
|
}
|
|
|
|
export function assertOpenCodeNotLaunchedThroughLegacyProvisioning(request: {
|
|
providerId?: unknown;
|
|
members?: readonly { providerId?: unknown; provider?: unknown }[];
|
|
}): void {
|
|
if (!isOpenCodeLegacyProvisioningRequest(request)) {
|
|
return;
|
|
}
|
|
const lanePlan = fromProvisioningMembers(
|
|
normalizeOptionalTeamProviderId(request.providerId),
|
|
(request.members ?? []).map((member, index) => ({
|
|
name: `member-${index + 1}`,
|
|
providerId:
|
|
normalizeOptionalTeamProviderId(member.providerId) ??
|
|
normalizeOptionalTeamProviderId(member.provider),
|
|
}))
|
|
);
|
|
if (!lanePlan.ok) {
|
|
throw new Error(lanePlan.message || getOpenCodeMixedProviderProvisioningError());
|
|
}
|
|
if (!isPureOpenCodeProvisioningRequest(request)) {
|
|
return;
|
|
}
|
|
throw new Error(
|
|
'OpenCode team launch is not enabled in the legacy Claude stream-json provisioning path. ' +
|
|
'Use the gated OpenCode runtime adapter once production launch is enabled.'
|
|
);
|
|
}
|
|
|
|
export function mergeProvisioningWarnings(
|
|
existing: string[] | undefined,
|
|
nextWarning: string | null
|
|
): string[] | undefined {
|
|
if (!nextWarning) return existing;
|
|
const merged = (existing ?? []).filter((warning) => warning !== nextWarning);
|
|
merged.push(nextWarning);
|
|
return merged.length > 0 ? merged : undefined;
|
|
}
|
|
|
|
const DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD = 8;
|
|
const DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS = 20;
|
|
|
|
export function buildLargeDeterministicBootstrapWarning(memberCount: number): string | null {
|
|
if (memberCount <= DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD) {
|
|
return null;
|
|
}
|
|
return (
|
|
`Large Codex team launch: ${memberCount} primary teammates will bootstrap in one runtime. ` +
|
|
`Launches above ${DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD} teammates can be slower and more likely to hit provider rate limits or bootstrap timeouts.`
|
|
);
|
|
}
|
|
|
|
export function assertDeterministicBootstrapPrimaryMemberLimit(memberCount: number): void {
|
|
if (memberCount <= DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS) {
|
|
return;
|
|
}
|
|
throw new Error(
|
|
`Codex deterministic bootstrap currently supports up to ${DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS} primary teammates; this team has ${memberCount}. Reduce primary teammates or move extra OpenCode members to secondary lanes.`
|
|
);
|
|
}
|