import type { WorkspaceTabTarget } from "@/stores/workspace-tabs-store"; import { normalizeWorkspaceFileLocation, workspaceFileLocationsEqual } from "@/workspace/file-open"; type WorkspaceDraftTabSetup = NonNullable["setup"]>; export function normalizeWorkspaceTabTarget( value: WorkspaceTabTarget | null | undefined, ): WorkspaceTabTarget | null { if (!value || typeof value !== "object" || typeof value.kind !== "string") { return null; } if (value.kind === "draft") { const draftId = trimNonEmpty(value.draftId); if (!draftId) { return null; } const setup = normalizeWorkspaceDraftTabSetup(value.setup); return setup ? { kind: "draft", draftId, setup } : { kind: "draft", draftId }; } if (value.kind === "agent") { const agentId = trimNonEmpty(value.agentId); return agentId ? { kind: "agent", agentId } : null; } if (value.kind === "terminal") { const terminalId = trimNonEmpty(value.terminalId); return terminalId ? { kind: "terminal", terminalId } : null; } if (value.kind === "browser") { const browserId = trimNonEmpty(value.browserId); return browserId ? { kind: "browser", browserId } : null; } if (value.kind === "file") { return normalizeFileTabTarget(value); } if (value.kind === "setup") { const workspaceId = trimNonEmpty(value.workspaceId); return workspaceId ? { kind: "setup", workspaceId } : null; } return null; } export function normalizeWorkspaceDraftTabSetup( value: unknown, ): WorkspaceDraftTabSetup | undefined { const record = isPlainRecord(value) ? value : null; if (!record) { return undefined; } const provider = trimNonEmpty(typeof record.provider === "string" ? record.provider : null); const cwd = trimNonEmpty(typeof record.cwd === "string" ? record.cwd : null); if (!provider || !cwd) { return undefined; } return { provider, cwd, modeId: trimOptionalString(typeof record.modeId === "string" ? record.modeId : null), model: trimOptionalString(typeof record.model === "string" ? record.model : null), thinkingOptionId: trimOptionalString( typeof record.thinkingOptionId === "string" ? record.thinkingOptionId : null, ), featureValues: isPlainRecord(record.featureValues) ? { ...record.featureValues } : {}, }; } export function workspaceTabTargetsEqual( left: WorkspaceTabTarget, right: WorkspaceTabTarget, ): boolean { if (left.kind !== right.kind) { return false; } if (left.kind === "draft" && right.kind === "draft") { return left.draftId === right.draftId && workspaceDraftTabSetupsEqual(left.setup, right.setup); } if (left.kind === "agent" && right.kind === "agent") { return left.agentId === right.agentId; } if (left.kind === "terminal" && right.kind === "terminal") { return left.terminalId === right.terminalId; } if (left.kind === "browser" && right.kind === "browser") { return left.browserId === right.browserId; } if (left.kind === "file" && right.kind === "file") { return workspaceFileLocationsEqual(left, right); } if (left.kind === "setup" && right.kind === "setup") { return left.workspaceId === right.workspaceId; } return false; } function workspaceDraftTabSetupsEqual( left: WorkspaceDraftTabSetup | undefined, right: WorkspaceDraftTabSetup | undefined, ): boolean { if (!left || !right) { return left === right; } return ( left.provider === right.provider && left.cwd === right.cwd && left.modeId === right.modeId && left.model === right.model && left.thinkingOptionId === right.thinkingOptionId && recordsShallowEqual(left.featureValues, right.featureValues) ); } function recordsShallowEqual( left: Record, right: Record, ): boolean { const leftKeys = Object.keys(left); if (leftKeys.length !== Object.keys(right).length) { return false; } for (const key of leftKeys) { if (!Object.hasOwn(right, key) || !Object.is(left[key], right[key])) { return false; } } return true; } export function buildDeterministicWorkspaceTabId(target: WorkspaceTabTarget): string { if (target.kind === "draft") { return target.draftId; } if (target.kind === "agent") { return `agent_${target.agentId}`; } if (target.kind === "terminal") { return `terminal_${target.terminalId}`; } if (target.kind === "browser") { return `browser_${target.browserId}`; } if (target.kind === "setup") { return `setup_${target.workspaceId}`; } return `file_${target.path}`; } function trimNonEmpty(value: string | null | undefined): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function normalizeFileTabTarget( value: Extract, ): WorkspaceTabTarget | null { const location = normalizeWorkspaceFileLocation(value); return location ? { kind: "file", ...location } : null; } function trimOptionalString(value: string | null | undefined): string | null { return value == null ? null : trimNonEmpty(value); } function isPlainRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }