import type { TControllerModelChoice, IControllerRuntimeId, TControllerSessionId, } from '../ts_interfaces/index.js'; import type { IControllerSettingsDocument, } from '../ts/interfaces.projects.js'; import { assertControllerSettingsDocument } from '../ts/classes.authmodels.js'; import type { IV30LegacySessionGroup, IV30LegacySessionGroupsDocument, } from './v30_sessionlayoutitemrefs.js'; type TObject = Record; export interface IProtocolV6DocumentMigrationResult { document: T; migrated: boolean; } const isPlainObject = (valueArg: unknown): valueArg is TObject => { if (typeof valueArg !== 'object' || valueArg === null || Array.isArray(valueArg)) return false; const prototype = Object.getPrototypeOf(valueArg); return prototype === Object.prototype || prototype === null; }; const hasExactKeys = ( valueArg: TObject, requiredArg: readonly string[], optionalArg: readonly string[] = [], ): boolean => { const allowed = new Set([...requiredArg, ...optionalArg]); return requiredArg.every((key) => Object.hasOwn(valueArg, key)) && Object.keys(valueArg).every((key) => allowed.has(key)); }; const isBoundedString = (valueArg: unknown, maximumBytesArg: number): valueArg is string => typeof valueArg === 'string' && valueArg.length > 0 && Buffer.byteLength(valueArg, 'utf8') <= maximumBytesArg; const isRuntimeId = (valueArg: unknown): valueArg is IControllerRuntimeId => isPlainObject(valueArg) && hasExactKeys(valueArg, ['harnessId', 'nativeId']) && ( valueArg.harnessId === 'opencode' || valueArg.harnessId === 'flex' || valueArg.harnessId === 'controller' ) && isBoundedString(valueArg.nativeId, 512); const qualifyLegacySessionId = (nativeIdArg: string): TControllerSessionId => ({ harnessId: 'opencode', nativeId: nativeIdArg, }); const toSessionRuntimeId = (valueArg: IControllerRuntimeId): TControllerSessionId | undefined => ( valueArg.harnessId === 'opencode' || valueArg.harnessId === 'flex' ? valueArg as TControllerSessionId : undefined ); const isLegacySessionId = (valueArg: unknown): valueArg is string => isBoundedString(valueArg, 512) && /^[A-Za-z0-9_-]+$/.test(valueArg); const isLegacyModelChoice = (valueArg: unknown): valueArg is { providerID: string; modelID: string; variant?: string; } => { if (!isPlainObject(valueArg)) return false; if (!hasExactKeys(valueArg, ['providerID', 'modelID'], ['variant'])) return false; return isBoundedString(valueArg.providerID, 256) && isBoundedString(valueArg.modelID, 256) && ( !Object.hasOwn(valueArg, 'variant') || ( typeof valueArg.variant === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(valueArg.variant) ) ); }; const qualifyLegacyModel = ( modelArg: { providerID: string; modelID: string; variant?: string }, ): TControllerModelChoice => ({ harnessId: 'opencode', providerID: modelArg.providerID, modelID: modelArg.modelID, ...(modelArg.variant === undefined ? {} : { variant: modelArg.variant }), }); export const migrateProtocolV6SettingsDocument = ( valueArg: unknown, ): IProtocolV6DocumentMigrationResult => { if (!isPlainObject(valueArg)) throw new Error('Invalid controller settings migration document.'); if (Object.hasOwn(valueArg, 'defaultModels')) { assertControllerSettingsDocument(valueArg); return { document: valueArg, migrated: false, }; } if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'defaultModel'], ['autoAcceptPermissions', 'selectedOpenCodeProviderConnectionId'], ) || (valueArg.defaultModel !== null && !isLegacyModelChoice(valueArg.defaultModel)) ) { throw new Error('Invalid legacy controller settings migration document.'); } return { document: { id: valueArg.id as string, controllerId: valueArg.controllerId as string, defaultModels: valueArg.defaultModel === null ? [] : [qualifyLegacyModel(valueArg.defaultModel)], ...(valueArg.autoAcceptPermissions === undefined ? {} : { autoAcceptPermissions: valueArg.autoAcceptPermissions as true }), ...(valueArg.selectedOpenCodeProviderConnectionId === undefined ? {} : { selectedOpenCodeProviderConnectionId: valueArg.selectedOpenCodeProviderConnectionId as string, }), }, migrated: true, }; }; export const migrateProtocolV6SessionGroupsDocument = ( valueArg: unknown, ): IProtocolV6DocumentMigrationResult => { if (!isPlainObject(valueArg)) throw new Error('Invalid controller layout migration document.'); if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'projectId', 'groups'], ['ungroupedSessionIds', 'revision'], ) || !Array.isArray(valueArg.groups) ) { throw new Error('Invalid controller layout migration document.'); } const hasLegacyShape = valueArg.ungroupedSessionIds === undefined || valueArg.revision === undefined || valueArg.groups.some((groupArg) => ( isPlainObject(groupArg) && Array.isArray(groupArg.sessionIds) && groupArg.sessionIds.some(isLegacySessionId) )) || ( Array.isArray(valueArg.ungroupedSessionIds) && valueArg.ungroupedSessionIds.some(isLegacySessionId) ); if (!hasLegacyShape) { return { document: valueArg as unknown as IV30LegacySessionGroupsDocument, migrated: false, }; } const groups: IV30LegacySessionGroup[] = valueArg.groups.map((groupArg) => { if ( !isPlainObject(groupArg) || !hasExactKeys(groupArg, ['id', 'name', 'sessionIds']) || !Array.isArray(groupArg.sessionIds) ) { throw new Error('Invalid legacy controller layout group.'); } return { id: groupArg.id as string, name: groupArg.name as string, sessionIds: groupArg.sessionIds.flatMap((sessionIdArg): TControllerSessionId[] => { if (isLegacySessionId(sessionIdArg)) return [qualifyLegacySessionId(sessionIdArg)]; if (isRuntimeId(sessionIdArg)) { const sessionId = toSessionRuntimeId(sessionIdArg); return sessionId ? [sessionId] : []; } throw new Error('Invalid legacy controller layout session ID.'); }), }; }); const legacyUngrouped = valueArg.ungroupedSessionIds ?? []; if (!Array.isArray(legacyUngrouped)) { throw new Error('Invalid legacy controller ungrouped session list.'); } const ungroupedSessionIds = legacyUngrouped.flatMap((sessionIdArg): TControllerSessionId[] => { if (isLegacySessionId(sessionIdArg)) return [qualifyLegacySessionId(sessionIdArg)]; if (isRuntimeId(sessionIdArg)) { const sessionId = toSessionRuntimeId(sessionIdArg); return sessionId ? [sessionId] : []; } throw new Error('Invalid legacy controller ungrouped session ID.'); }); return { document: { id: valueArg.id as string, controllerId: valueArg.controllerId as string, projectId: valueArg.projectId as string, groups, ungroupedSessionIds, revision: valueArg.revision === undefined ? 0 : valueArg.revision as number, }, migrated: true, }; };