import { STATE_VERSION } from "../constants.ts"; import { canonicalName, compactSourceRef, displayName, makeItemKey } from "../identity.ts"; import type { HelpState, IndexedItem, LegacyHelpState, ResourceKind, ResourceScope } from "../types.ts"; export function emptyState(): HelpState { return { version: STATE_VERSION, items: {} }; } export function legacySourceFromKey(key: string | undefined): string | undefined { if (!key) return undefined; const first = key.indexOf(":"); const last = key.lastIndexOf(":"); if (first < 0 || last <= first) return undefined; return key.slice(first + 1, last); } function fallbackSourceLabel(kind: ResourceKind, sourceRef: string, scope?: ResourceScope): string { if (sourceRef === "builtin") return kind === "tool" ? "内置工具" : "内置"; if (sourceRef === "sdk") return kind === "tool" ? "SDK 工具" : "SDK"; if (sourceRef.startsWith("pkg:")) return `npm:${sourceRef.slice("pkg:".length)}`; if (sourceRef.startsWith("npm:")) return sourceRef; if (sourceRef.startsWith("mcp:")) return sourceRef === "mcp:gateway" ? "内置网关" : "MCP 服务器"; if (sourceRef.startsWith("local:")) return scope === "project" ? "项目本地" : "本地包"; return sourceRef; } export function mergeItem(existing: IndexedItem | undefined, next: IndexedItem): IndexedItem { if (!existing) return next; return { ...existing, ...next, description: next.description ?? existing.description, sourceLabel: next.sourceLabel || existing.sourceLabel, sourceRef: next.sourceRef || existing.sourceRef, useCount: Math.max(existing.useCount ?? 0, next.useCount ?? 0), lastUsedAt: Math.max(existing.lastUsedAt ?? 0, next.lastUsedAt ?? 0) || undefined, lastSeenAt: Math.max(existing.lastSeenAt ?? 0, next.lastSeenAt ?? 0) || undefined, pinned: Boolean(existing.pinned || next.pinned), }; } export function normalizeRawItem(raw: Partial & { key?: string; kind?: ResourceKind; name?: string; source?: string }): IndexedItem | undefined { if (!raw.kind || !raw.name) return undefined; const sourceRef = compactSourceRef(raw.sourceRef ?? raw.source ?? legacySourceFromKey(raw.key) ?? raw.packageName ?? raw.sourceLabel); const name = canonicalName(raw.kind, raw.name); const key = makeItemKey(raw.kind, name, sourceRef); return { key, kind: raw.kind, name, displayName: raw.displayName ?? displayName(raw.kind, name), description: raw.description, sourceLabel: raw.sourceLabel || fallbackSourceLabel(raw.kind, sourceRef, raw.scope), sourceRef, scope: raw.scope, packageName: raw.packageName, useCount: raw.useCount ?? 0, lastUsedAt: raw.lastUsedAt, lastSeenAt: raw.lastSeenAt, pinned: raw.pinned, }; } export function migrateState(state: LegacyHelpState | HelpState): HelpState { const items: Record = {}; for (const raw of Object.values(state.items ?? {})) { const next = normalizeRawItem(raw); if (!next) continue; items[next.key] = mergeItem(items[next.key], next); } return { version: STATE_VERSION, items, lastRefresh: state.lastRefresh, }; }