@tutti-os/workspace-file-reference 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,286 @@
1
+ // src/core/referenceSourceUtils.ts
2
+ var WORKSPACE_ROOT_GROUP_NODE_ID = "__workspace_root__";
3
+ function nodeRefKey(ref) {
4
+ return `${ref.sourceId}\0${ref.nodeId}`;
5
+ }
6
+ function nodeKey(node) {
7
+ return nodeRefKey(node.ref);
8
+ }
9
+ function normalizeReferenceNodeKind(kind) {
10
+ return kind === "folder" || kind === "directory" ? "folder" : "file";
11
+ }
12
+ function sortReferenceNodes(nodes) {
13
+ return [...nodes].sort((left, right) => {
14
+ if (left.kind !== right.kind) {
15
+ return left.kind === "folder" ? -1 : 1;
16
+ }
17
+ return left.displayName.localeCompare(right.displayName, void 0, {
18
+ sensitivity: "base"
19
+ });
20
+ });
21
+ }
22
+ function dedupeReferenceNodes(nodes) {
23
+ const seen = /* @__PURE__ */ new Set();
24
+ const result = [];
25
+ for (const node of nodes) {
26
+ const key = nodeKey(node);
27
+ if (seen.has(key)) {
28
+ continue;
29
+ }
30
+ seen.add(key);
31
+ result.push(node);
32
+ }
33
+ return result;
34
+ }
35
+ function appendReferencePage(current, next) {
36
+ return dedupeReferenceNodes([...current, ...next]);
37
+ }
38
+ function selectedReferenceToWorkspaceFileReference(selected) {
39
+ return {
40
+ path: selected.path,
41
+ kind: selected.kind,
42
+ ...selected.displayName ? { displayName: selected.displayName } : {}
43
+ };
44
+ }
45
+
46
+ // src/core/referenceSourceAggregator.ts
47
+ var SOURCE_ROOT_NODE_ID = "\0source-root";
48
+ function isSourceRootNode(ref) {
49
+ return ref.nodeId === SOURCE_ROOT_NODE_ID;
50
+ }
51
+ function createReferenceSourceAggregator(registry) {
52
+ const sourceMap = /* @__PURE__ */ new Map();
53
+ async function loadSources(scope) {
54
+ const sources = await registry.getSources(scope);
55
+ sourceMap.clear();
56
+ for (const source of sources) {
57
+ sourceMap.set(source.metadata.id, source);
58
+ }
59
+ return sources;
60
+ }
61
+ function requireSource(sourceId) {
62
+ const source = sourceMap.get(sourceId);
63
+ if (!source) {
64
+ throw new Error(`reference source not found: ${sourceId}`);
65
+ }
66
+ return source;
67
+ }
68
+ return {
69
+ async listSources(scope) {
70
+ const sources = await loadSources(scope);
71
+ return sources.map((source) => ({
72
+ sourceId: source.metadata.id,
73
+ label: source.metadata.label,
74
+ ...source.metadata.icon ? { icon: source.metadata.icon } : {},
75
+ capabilities: source.capabilities
76
+ }));
77
+ },
78
+ async listRoot(scope) {
79
+ const sources = await loadSources(scope);
80
+ return sources.map((source) => ({
81
+ ref: { sourceId: source.metadata.id, nodeId: SOURCE_ROOT_NODE_ID },
82
+ kind: "folder",
83
+ displayName: source.metadata.label,
84
+ hasChildren: true
85
+ }));
86
+ },
87
+ async listChildren(scope, node, input) {
88
+ const source = requireSource(node.sourceId);
89
+ return source.listChildren(scope, {
90
+ node: isSourceRootNode(node) ? null : node,
91
+ cursor: input?.cursor ?? null,
92
+ filter: input?.filter ?? null,
93
+ signal: input?.signal
94
+ });
95
+ },
96
+ async search(scope, sourceId, input) {
97
+ const source = requireSource(sourceId);
98
+ if (!source.search) {
99
+ return { entries: [], nextCursor: null };
100
+ }
101
+ return source.search(scope, input);
102
+ },
103
+ async open(scope, node) {
104
+ const source = requireSource(node.ref.sourceId);
105
+ await source.open?.(scope, node);
106
+ },
107
+ async readPreview(scope, node) {
108
+ const source = requireSource(node.ref.sourceId);
109
+ return await source.readPreview?.(scope, node) ?? null;
110
+ },
111
+ resolveSelection(node) {
112
+ return requireSource(node.ref.sourceId).resolveSelection(node);
113
+ },
114
+ getLoadedSource(sourceId) {
115
+ return sourceMap.get(sourceId);
116
+ }
117
+ };
118
+ }
119
+ function createStaticReferenceSourceRegistry(sources) {
120
+ return {
121
+ async getSources(scope) {
122
+ const availability = await Promise.all(
123
+ sources.map(async (source) => {
124
+ try {
125
+ return await source.isAvailable(scope);
126
+ } catch {
127
+ return false;
128
+ }
129
+ })
130
+ );
131
+ return sources.filter((_, index) => availability[index]).sort((left, right) => left.metadata.order - right.metadata.order);
132
+ }
133
+ };
134
+ }
135
+
136
+ // src/core/referenceListSource.ts
137
+ var GROUP_PREFIX = "g:";
138
+ var FILE_PREFIX = "f:";
139
+ function createReferenceListSource(input) {
140
+ const { sourceId, label, capabilities, isAvailable, backend, adapter } = input;
141
+ function fileReferenceOf(node) {
142
+ return { path: decodeSegment(FILE_PREFIX, node.ref.nodeId), kind: "file" };
143
+ }
144
+ const service = {
145
+ metadata: { id: sourceId, label, order: input.order ?? 0 },
146
+ capabilities,
147
+ isAvailable,
148
+ async listChildren(scope, { node, cursor, filter }) {
149
+ if (node && node.nodeId.startsWith(FILE_PREFIX)) {
150
+ return { entries: [], nextCursor: null };
151
+ }
152
+ const parentGroupId = node ? decodeSegment(GROUP_PREFIX, node.nodeId) : null;
153
+ const result = await backend.list(scope, {
154
+ parentGroupId,
155
+ cursor: cursor ?? null,
156
+ filter: filter ?? null
157
+ });
158
+ return {
159
+ entries: result.items.map((item) => itemToNode(sourceId, item)),
160
+ nextCursor: result.nextCursor ?? null
161
+ };
162
+ },
163
+ async open(_scope, node) {
164
+ await adapter.openReference?.(fileReferenceOf(node));
165
+ },
166
+ async readPreview(scope, node) {
167
+ if (!adapter.readReferencePreview) {
168
+ return null;
169
+ }
170
+ return adapter.readReferencePreview({
171
+ workspaceId: scope.workspaceId,
172
+ reference: fileReferenceOf(node)
173
+ });
174
+ },
175
+ resolveSelection(node) {
176
+ return {
177
+ path: decodeSegment(FILE_PREFIX, node.ref.nodeId),
178
+ kind: "file",
179
+ ...node.displayName ? { displayName: node.displayName } : {}
180
+ };
181
+ }
182
+ };
183
+ const backendSearch = backend.search?.bind(backend);
184
+ if (backendSearch) {
185
+ service.search = async (scope, input2) => {
186
+ const result = await backendSearch(scope, {
187
+ query: input2.query,
188
+ cursor: input2.cursor ?? null,
189
+ ...input2.limit == null ? {} : { limit: input2.limit },
190
+ ...input2.signal ? { signal: input2.signal } : {}
191
+ });
192
+ return {
193
+ entries: result.items.map((item) => itemToNode(sourceId, item)),
194
+ nextCursor: result.nextCursor ?? null
195
+ };
196
+ };
197
+ }
198
+ return service;
199
+ }
200
+ function itemToNode(sourceId, item) {
201
+ if (item.type === "group") {
202
+ return {
203
+ ref: { sourceId, nodeId: GROUP_PREFIX + base64UrlEncode(item.id) },
204
+ kind: "folder",
205
+ displayName: item.displayName,
206
+ hasChildren: true,
207
+ ...item.referenceCount == null ? {} : { childCount: item.referenceCount },
208
+ ...item.iconUrl ? { iconUrl: item.iconUrl } : {}
209
+ };
210
+ }
211
+ const reference = item.reference;
212
+ return {
213
+ ref: { sourceId, nodeId: FILE_PREFIX + base64UrlEncode(reference.path) },
214
+ kind: "file",
215
+ displayName: reference.displayName?.trim() || basename(reference.path),
216
+ ...reference.sizeBytes == null ? {} : { sizeBytes: reference.sizeBytes },
217
+ ...reference.mtimeMs == null ? {} : { mtimeMs: reference.mtimeMs },
218
+ ...reference.mimeType ? { mimeType: reference.mimeType } : {}
219
+ };
220
+ }
221
+ function decodeSegment(prefix, nodeId) {
222
+ if (!nodeId.startsWith(prefix)) {
223
+ throw new Error(
224
+ `reference-list nodeId missing ${prefix} prefix: ${nodeId}`
225
+ );
226
+ }
227
+ return base64UrlDecode(nodeId.slice(prefix.length));
228
+ }
229
+ function basename(path) {
230
+ const trimmed = path.replace(/\/+$/, "");
231
+ const index = trimmed.lastIndexOf("/");
232
+ return index >= 0 ? trimmed.slice(index + 1) : trimmed;
233
+ }
234
+ function base64UrlEncode(value) {
235
+ const bytes = new TextEncoder().encode(value);
236
+ let binary = "";
237
+ for (const byte of bytes) {
238
+ binary += String.fromCharCode(byte);
239
+ }
240
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
241
+ }
242
+ function base64UrlDecode(value) {
243
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
244
+ const binary = atob(normalized);
245
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
246
+ return new TextDecoder().decode(bytes);
247
+ }
248
+
249
+ // src/core/index.ts
250
+ function uniqueWorkspaceFileReferences(refs) {
251
+ const unique = /* @__PURE__ */ new Map();
252
+ for (const ref of refs) {
253
+ const normalizedPath = ref.path.trim();
254
+ if (!normalizedPath || unique.has(normalizedPath)) {
255
+ continue;
256
+ }
257
+ unique.set(normalizedPath, {
258
+ displayName: ref.displayName?.trim() || void 0,
259
+ kind: ref.kind === "folder" ? "folder" : "file",
260
+ path: normalizedPath,
261
+ ...ref.mtimeMs === void 0 ? {} : { mtimeMs: ref.mtimeMs },
262
+ ...ref.sizeBytes === void 0 ? {} : { sizeBytes: ref.sizeBytes }
263
+ });
264
+ }
265
+ return [...unique.values()];
266
+ }
267
+
268
+ export {
269
+ WORKSPACE_ROOT_GROUP_NODE_ID,
270
+ nodeRefKey,
271
+ nodeKey,
272
+ normalizeReferenceNodeKind,
273
+ sortReferenceNodes,
274
+ dedupeReferenceNodes,
275
+ appendReferencePage,
276
+ selectedReferenceToWorkspaceFileReference,
277
+ SOURCE_ROOT_NODE_ID,
278
+ isSourceRootNode,
279
+ createReferenceSourceAggregator,
280
+ createStaticReferenceSourceRegistry,
281
+ createReferenceListSource,
282
+ base64UrlEncode,
283
+ base64UrlDecode,
284
+ uniqueWorkspaceFileReferences
285
+ };
286
+ //# sourceMappingURL=chunk-7C3RZYQE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/referenceSourceUtils.ts","../src/core/referenceSourceAggregator.ts","../src/core/referenceListSource.ts","../src/core/index.ts"],"sourcesContent":["import type {\n NodeRef,\n ReferenceNode,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\nimport type { WorkspaceFileReference } from \"../contracts/index.ts\";\n\n/**\n * Reference Source Services 的共享 base 工具(形状处理,非取数)。\n * 设计见 docs/architecture/agent-reference-source-services.md §1 / §2。\n */\n\n/**\n * 「回到源根」哨兵 nodeId。源在 listSidebarGroups 里用它表示一个等价于源根的\n * 二级分组(如本地源的「个人」= 用户 home 根);picker 选中它走 navigateToRoot\n * 而非 ensureChildren,避免把哨兵当真实路径打后端。\n */\nexport const WORKSPACE_ROOT_GROUP_NODE_ID = \"__workspace_root__\";\n\n/** 节点的全局 key,用于 picker 树状态索引与去重。picker 不解析其内部结构。 */\nexport function nodeRefKey(ref: NodeRef): string {\n return `${ref.sourceId}\u0000${ref.nodeId}`;\n}\n\nexport function nodeKey(node: ReferenceNode): string {\n return nodeRefKey(node.ref);\n}\n\n/** 任意输入归一为 \"file\" | \"folder\"。 */\nexport function normalizeReferenceNodeKind(\n kind: string | null | undefined\n): \"file\" | \"folder\" {\n return kind === \"folder\" || kind === \"directory\" ? \"folder\" : \"file\";\n}\n\n/** folder 在前、同类按 displayName 本地化排序。 */\nexport function sortReferenceNodes(\n nodes: readonly ReferenceNode[]\n): ReferenceNode[] {\n return [...nodes].sort((left, right) => {\n if (left.kind !== right.kind) {\n return left.kind === \"folder\" ? -1 : 1;\n }\n return left.displayName.localeCompare(right.displayName, undefined, {\n sensitivity: \"base\"\n });\n });\n}\n\n/** 按 node key 去重,保留首次出现。支持 cursor append 的稳定去重。 */\nexport function dedupeReferenceNodes(\n nodes: readonly ReferenceNode[]\n): ReferenceNode[] {\n const seen = new Set<string>();\n const result: ReferenceNode[] = [];\n for (const node of nodes) {\n const key = nodeKey(node);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n result.push(node);\n }\n return result;\n}\n\n/** 续页累积:把新一页 append 到已有项后并去重(cursor 语义,不重排)。 */\nexport function appendReferencePage(\n current: readonly ReferenceNode[],\n next: readonly ReferenceNode[]\n): ReferenceNode[] {\n return dedupeReferenceNodes([...current, ...next]);\n}\n\n/** SelectedReference 与现有 WorkspaceFileReference 形状兼容,便于下游零改动复用。 */\nexport function selectedReferenceToWorkspaceFileReference(\n selected: SelectedReference\n): WorkspaceFileReference {\n return {\n path: selected.path,\n kind: selected.kind,\n ...(selected.displayName ? { displayName: selected.displayName } : {})\n };\n}\n","import type {\n ListChildrenResult,\n NodeRef,\n ReferenceNode,\n ReferencePreview,\n ReferenceScope,\n ReferenceSourceRegistry,\n ReferenceSourceService,\n SearchInput,\n SearchResult,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\n\n/**\n * 多源聚合器:根层级 = 每个可用源一个 folder 节点;其余按 node.ref.sourceId 委派。\n * 设计见 docs/architecture/agent-reference-source-services.md §3 / §4.4。\n */\n\n/**\n * 源根节点的 nodeId 哨兵。由聚合器创建源根节点时使用;\n * 任何真实源都不会产出 nodeId 等于此值的子节点(workspace 源 nodeId 是路径,\n * app 源 nodeId 以 \"app:\" 开头),因此路由无歧义。\n */\nexport const SOURCE_ROOT_NODE_ID = \"\u0000source-root\";\n\n/** 顶部 tab 的描述(source 身份 + 能力)。 */\nexport interface ReferenceSourceTab {\n sourceId: string;\n label: string;\n icon?: string;\n capabilities: ReferenceSourceService[\"capabilities\"];\n}\n\nexport interface ReferenceSourceAggregator {\n /** 顶部 tab 列表:每个可用源一个 tab(已过滤 isAvailable、按 order 排序)。 */\n listSources(scope: ReferenceScope): Promise<ReferenceSourceTab[]>;\n /** (备用,单树多根形态)根层级:每个可用源一个 folder 节点。 */\n listRoot(scope: ReferenceScope): Promise<ReferenceNode[]>;\n listChildren(\n scope: ReferenceScope,\n node: NodeRef,\n input?: {\n cursor?: string | null;\n filter?: string | null;\n signal?: AbortSignal;\n }\n ): Promise<ListChildrenResult>;\n search(\n scope: ReferenceScope,\n sourceId: string,\n input: SearchInput\n ): Promise<SearchResult>;\n open(scope: ReferenceScope, node: ReferenceNode): Promise<void>;\n readPreview(\n scope: ReferenceScope,\n node: ReferenceNode\n ): Promise<ReferencePreview | null>;\n resolveSelection(node: ReferenceNode): SelectedReference;\n /** 取已加载的源(用于读取 capabilities/metadata)。需先 listRoot 触发加载。 */\n getLoadedSource(sourceId: string): ReferenceSourceService | undefined;\n}\n\nexport function isSourceRootNode(ref: NodeRef): boolean {\n return ref.nodeId === SOURCE_ROOT_NODE_ID;\n}\n\nexport function createReferenceSourceAggregator(\n registry: ReferenceSourceRegistry\n): ReferenceSourceAggregator {\n // 同一 scope 内源稳定;缓存便于 resolveSelection 同步取源。\n const sourceMap = new Map<string, ReferenceSourceService>();\n\n async function loadSources(\n scope: ReferenceScope\n ): Promise<ReferenceSourceService[]> {\n const sources = await registry.getSources(scope);\n sourceMap.clear();\n for (const source of sources) {\n sourceMap.set(source.metadata.id, source);\n }\n return sources;\n }\n\n function requireSource(sourceId: string): ReferenceSourceService {\n const source = sourceMap.get(sourceId);\n if (!source) {\n throw new Error(`reference source not found: ${sourceId}`);\n }\n return source;\n }\n\n return {\n async listSources(scope) {\n const sources = await loadSources(scope);\n return sources.map((source) => ({\n sourceId: source.metadata.id,\n label: source.metadata.label,\n ...(source.metadata.icon ? { icon: source.metadata.icon } : {}),\n capabilities: source.capabilities\n }));\n },\n\n async listRoot(scope) {\n const sources = await loadSources(scope);\n return sources.map((source) => ({\n ref: { sourceId: source.metadata.id, nodeId: SOURCE_ROOT_NODE_ID },\n kind: \"folder\",\n displayName: source.metadata.label,\n hasChildren: true\n }));\n },\n\n async listChildren(scope, node, input) {\n const source = requireSource(node.sourceId);\n return source.listChildren(scope, {\n node: isSourceRootNode(node) ? null : node,\n cursor: input?.cursor ?? null,\n filter: input?.filter ?? null,\n signal: input?.signal\n });\n },\n\n async search(scope, sourceId, input) {\n const source = requireSource(sourceId);\n if (!source.search) {\n return { entries: [], nextCursor: null };\n }\n return source.search(scope, input);\n },\n\n async open(scope, node) {\n const source = requireSource(node.ref.sourceId);\n await source.open?.(scope, node);\n },\n\n async readPreview(scope, node) {\n const source = requireSource(node.ref.sourceId);\n return (await source.readPreview?.(scope, node)) ?? null;\n },\n\n resolveSelection(node) {\n return requireSource(node.ref.sourceId).resolveSelection(node);\n },\n\n getLoadedSource(sourceId) {\n return sourceMap.get(sourceId);\n }\n };\n}\n\n/** 静态 registry:持有一组源,按 isAvailable 过滤、按 order 排序。 */\nexport function createStaticReferenceSourceRegistry(\n sources: readonly ReferenceSourceService[]\n): ReferenceSourceRegistry {\n return {\n async getSources(scope) {\n const availability = await Promise.all(\n sources.map(async (source) => {\n try {\n return await source.isAvailable(scope);\n } catch {\n return false;\n }\n })\n );\n return sources\n .filter((_, index) => availability[index])\n .sort((left, right) => left.metadata.order - right.metadata.order);\n }\n };\n}\n","import type {\n ListChildrenResult,\n ReferenceNode,\n ReferencePreview,\n ReferenceScope,\n ReferenceSourceCapabilities,\n ReferenceSourceService,\n SearchInput,\n SearchResult,\n SelectedReference\n} from \"../contracts/referenceSource.ts\";\nimport type {\n WorkspaceFileReference,\n WorkspaceFileReferenceAdapter\n} from \"../contracts/index.ts\";\n\n/**\n * 统一的「引用列表协议」—— 与 app references 同一套 group/reference/parentGroupId/cursor 形状。\n * 各源(应用 / issue / 任务…)各自实现一个 ReferenceListBackend 把自家数据映射成此协议;\n * createReferenceListSource 把协议 item 映射成 ReferenceNode,各源前端逻辑完全复用。\n * 设计见 docs/architecture/agent-reference-source-services.md。\n */\n\nexport interface ReferenceListGroup {\n type: \"group\";\n /** 不透明分组 id;作为下钻的 parentGroupId 原样回传。 */\n id: string;\n displayName: string;\n referenceCount?: number | null;\n /** 可选分组图标(data URL / 远程 URL),如应用产物源的 app 图标。 */\n iconUrl?: string | null;\n}\n\nexport interface ReferenceListFile {\n /** daemon/host 可解析的文件路径。 */\n path: string;\n displayName?: string | null;\n sizeBytes?: number | null;\n mtimeMs?: number | null;\n mimeType?: string | null;\n}\n\nexport interface ReferenceListReference {\n type: \"reference\";\n reference: ReferenceListFile;\n}\n\nexport type ReferenceListItem = ReferenceListGroup | ReferenceListReference;\n\nexport interface ReferenceListRequest {\n /** null = 根层级。 */\n parentGroupId: string | null;\n cursor?: string | null;\n filter?: string | null;\n signal?: AbortSignal;\n}\n\nexport interface ReferenceListResult {\n items: ReferenceListItem[];\n nextCursor?: string | null;\n}\n\n/** 递归搜索请求(跨整源,非当前层 filter)。 */\nexport interface ReferenceListSearchRequest {\n query: string;\n cursor?: string | null;\n limit?: number;\n signal?: AbortSignal;\n}\n\n/** 各源自治的取数适配器:把自家数据映射成统一协议。 */\nexport interface ReferenceListBackend {\n list(\n scope: ReferenceScope,\n request: ReferenceListRequest\n ): Promise<ReferenceListResult>;\n /**\n * 可选:递归搜索。实现即代表该源支持全局搜索(对应 capabilities.searchable)。\n * 返回 flat reference items(协议层不返回 group)。\n */\n search?(\n scope: ReferenceScope,\n request: ReferenceListSearchRequest\n ): Promise<ReferenceListResult>;\n}\n\nexport interface CreateReferenceListSourceInput {\n sourceId: string;\n label: string;\n order?: number;\n capabilities: ReferenceSourceCapabilities;\n isAvailable: (scope: ReferenceScope) => boolean | Promise<boolean>;\n backend: ReferenceListBackend;\n /** open/preview 复用 host 链路(协议返回的 path 可被解析打开)。 */\n adapter: WorkspaceFileReferenceAdapter;\n}\n\nconst GROUP_PREFIX = \"g:\";\nconst FILE_PREFIX = \"f:\";\n\nexport function createReferenceListSource(\n input: CreateReferenceListSourceInput\n): ReferenceSourceService {\n const { sourceId, label, capabilities, isAvailable, backend, adapter } =\n input;\n\n function fileReferenceOf(node: ReferenceNode): WorkspaceFileReference {\n return { path: decodeSegment(FILE_PREFIX, node.ref.nodeId), kind: \"file\" };\n }\n\n const service: ReferenceSourceService = {\n metadata: { id: sourceId, label, order: input.order ?? 0 },\n capabilities,\n isAvailable,\n\n async listChildren(\n scope: ReferenceScope,\n { node, cursor, filter }\n ): Promise<ListChildrenResult> {\n // file 节点没有子节点。\n if (node && node.nodeId.startsWith(FILE_PREFIX)) {\n return { entries: [], nextCursor: null };\n }\n const parentGroupId = node\n ? decodeSegment(GROUP_PREFIX, node.nodeId)\n : null;\n const result = await backend.list(scope, {\n parentGroupId,\n cursor: cursor ?? null,\n filter: filter ?? null\n });\n return {\n entries: result.items.map((item) => itemToNode(sourceId, item)),\n nextCursor: result.nextCursor ?? null\n };\n },\n\n async open(_scope: ReferenceScope, node: ReferenceNode): Promise<void> {\n await adapter.openReference?.(fileReferenceOf(node));\n },\n\n async readPreview(\n scope: ReferenceScope,\n node: ReferenceNode\n ): Promise<ReferencePreview | null> {\n if (!adapter.readReferencePreview) {\n return null;\n }\n return adapter.readReferencePreview({\n workspaceId: scope.workspaceId,\n reference: fileReferenceOf(node)\n });\n },\n\n resolveSelection(node: ReferenceNode): SelectedReference {\n return {\n path: decodeSegment(FILE_PREFIX, node.ref.nodeId),\n kind: \"file\",\n ...(node.displayName ? { displayName: node.displayName } : {})\n };\n }\n };\n\n // 仅当 backend 实现了递归搜索时才暴露 search,与 capabilities.searchable 保持一致。\n const backendSearch = backend.search?.bind(backend);\n if (backendSearch) {\n service.search = async (\n scope: ReferenceScope,\n input: SearchInput\n ): Promise<SearchResult> => {\n const result = await backendSearch(scope, {\n query: input.query,\n cursor: input.cursor ?? null,\n ...(input.limit == null ? {} : { limit: input.limit }),\n ...(input.signal ? { signal: input.signal } : {})\n });\n return {\n entries: result.items.map((item) => itemToNode(sourceId, item)),\n nextCursor: result.nextCursor ?? null\n };\n };\n }\n\n return service;\n}\n\nfunction itemToNode(sourceId: string, item: ReferenceListItem): ReferenceNode {\n if (item.type === \"group\") {\n return {\n ref: { sourceId, nodeId: GROUP_PREFIX + base64UrlEncode(item.id) },\n kind: \"folder\",\n displayName: item.displayName,\n hasChildren: true,\n ...(item.referenceCount == null\n ? {}\n : { childCount: item.referenceCount }),\n ...(item.iconUrl ? { iconUrl: item.iconUrl } : {})\n };\n }\n const reference = item.reference;\n return {\n ref: { sourceId, nodeId: FILE_PREFIX + base64UrlEncode(reference.path) },\n kind: \"file\",\n displayName: reference.displayName?.trim() || basename(reference.path),\n ...(reference.sizeBytes == null ? {} : { sizeBytes: reference.sizeBytes }),\n ...(reference.mtimeMs == null ? {} : { mtimeMs: reference.mtimeMs }),\n ...(reference.mimeType ? { mimeType: reference.mimeType } : {})\n };\n}\n\nfunction decodeSegment(prefix: string, nodeId: string): string {\n if (!nodeId.startsWith(prefix)) {\n throw new Error(\n `reference-list nodeId missing ${prefix} prefix: ${nodeId}`\n );\n }\n return base64UrlDecode(nodeId.slice(prefix.length));\n}\n\nfunction basename(path: string): string {\n const trimmed = path.replace(/\\/+$/, \"\");\n const index = trimmed.lastIndexOf(\"/\");\n return index >= 0 ? trimmed.slice(index + 1) : trimmed;\n}\n\n/** UTF-8 安全的 base64url(浏览器/Node 通用)。 */\nexport function base64UrlEncode(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function base64UrlDecode(value: string): string {\n const normalized = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(normalized);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n return new TextDecoder().decode(bytes);\n}\n","import type { WorkspaceFileReference } from \"../contracts/index.ts\";\n\nexport * from \"./referenceSourceUtils.ts\";\nexport * from \"./referenceSourceAggregator.ts\";\nexport * from \"./referenceListSource.ts\";\n\nexport function uniqueWorkspaceFileReferences(\n refs: readonly WorkspaceFileReference[]\n): WorkspaceFileReference[] {\n const unique = new Map<string, WorkspaceFileReference>();\n for (const ref of refs) {\n const normalizedPath = ref.path.trim();\n if (!normalizedPath || unique.has(normalizedPath)) {\n continue;\n }\n unique.set(normalizedPath, {\n displayName: ref.displayName?.trim() || undefined,\n kind: ref.kind === \"folder\" ? \"folder\" : \"file\",\n path: normalizedPath,\n ...(ref.mtimeMs === undefined ? {} : { mtimeMs: ref.mtimeMs }),\n ...(ref.sizeBytes === undefined ? {} : { sizeBytes: ref.sizeBytes })\n });\n }\n return [...unique.values()];\n}\n"],"mappings":";AAiBO,IAAM,+BAA+B;AAGrC,SAAS,WAAW,KAAsB;AAC/C,SAAO,GAAG,IAAI,QAAQ,KAAI,IAAI,MAAM;AACtC;AAEO,SAAS,QAAQ,MAA6B;AACnD,SAAO,WAAW,KAAK,GAAG;AAC5B;AAGO,SAAS,2BACd,MACmB;AACnB,SAAO,SAAS,YAAY,SAAS,cAAc,WAAW;AAChE;AAGO,SAAS,mBACd,OACiB;AACjB,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,UAAU;AACtC,QAAI,KAAK,SAAS,MAAM,MAAM;AAC5B,aAAO,KAAK,SAAS,WAAW,KAAK;AAAA,IACvC;AACA,WAAO,KAAK,YAAY,cAAc,MAAM,aAAa,QAAW;AAAA,MAClE,aAAa;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;AAGO,SAAS,qBACd,OACiB;AACjB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAA0B,CAAC;AACjC,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,QAAQ,IAAI;AACxB,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB;AAAA,IACF;AACA,SAAK,IAAI,GAAG;AACZ,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAGO,SAAS,oBACd,SACA,MACiB;AACjB,SAAO,qBAAqB,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;AACnD;AAGO,SAAS,0CACd,UACwB;AACxB,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,MAAM,SAAS;AAAA,IACf,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,YAAY,IAAI,CAAC;AAAA,EACtE;AACF;;;AC5DO,IAAM,sBAAsB;AAuC5B,SAAS,iBAAiB,KAAuB;AACtD,SAAO,IAAI,WAAW;AACxB;AAEO,SAAS,gCACd,UAC2B;AAE3B,QAAM,YAAY,oBAAI,IAAoC;AAE1D,iBAAe,YACb,OACmC;AACnC,UAAM,UAAU,MAAM,SAAS,WAAW,KAAK;AAC/C,cAAU,MAAM;AAChB,eAAW,UAAU,SAAS;AAC5B,gBAAU,IAAI,OAAO,SAAS,IAAI,MAAM;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,UAA0C;AAC/D,UAAM,SAAS,UAAU,IAAI,QAAQ;AACrC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,+BAA+B,QAAQ,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,YAAY,OAAO;AACvB,YAAM,UAAU,MAAM,YAAY,KAAK;AACvC,aAAO,QAAQ,IAAI,CAAC,YAAY;AAAA,QAC9B,UAAU,OAAO,SAAS;AAAA,QAC1B,OAAO,OAAO,SAAS;AAAA,QACvB,GAAI,OAAO,SAAS,OAAO,EAAE,MAAM,OAAO,SAAS,KAAK,IAAI,CAAC;AAAA,QAC7D,cAAc,OAAO;AAAA,MACvB,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,SAAS,OAAO;AACpB,YAAM,UAAU,MAAM,YAAY,KAAK;AACvC,aAAO,QAAQ,IAAI,CAAC,YAAY;AAAA,QAC9B,KAAK,EAAE,UAAU,OAAO,SAAS,IAAI,QAAQ,oBAAoB;AAAA,QACjE,MAAM;AAAA,QACN,aAAa,OAAO,SAAS;AAAA,QAC7B,aAAa;AAAA,MACf,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,aAAa,OAAO,MAAM,OAAO;AACrC,YAAM,SAAS,cAAc,KAAK,QAAQ;AAC1C,aAAO,OAAO,aAAa,OAAO;AAAA,QAChC,MAAM,iBAAiB,IAAI,IAAI,OAAO;AAAA,QACtC,QAAQ,OAAO,UAAU;AAAA,QACzB,QAAQ,OAAO,UAAU;AAAA,QACzB,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,OAAO,UAAU,OAAO;AACnC,YAAM,SAAS,cAAc,QAAQ;AACrC,UAAI,CAAC,OAAO,QAAQ;AAClB,eAAO,EAAE,SAAS,CAAC,GAAG,YAAY,KAAK;AAAA,MACzC;AACA,aAAO,OAAO,OAAO,OAAO,KAAK;AAAA,IACnC;AAAA,IAEA,MAAM,KAAK,OAAO,MAAM;AACtB,YAAM,SAAS,cAAc,KAAK,IAAI,QAAQ;AAC9C,YAAM,OAAO,OAAO,OAAO,IAAI;AAAA,IACjC;AAAA,IAEA,MAAM,YAAY,OAAO,MAAM;AAC7B,YAAM,SAAS,cAAc,KAAK,IAAI,QAAQ;AAC9C,aAAQ,MAAM,OAAO,cAAc,OAAO,IAAI,KAAM;AAAA,IACtD;AAAA,IAEA,iBAAiB,MAAM;AACrB,aAAO,cAAc,KAAK,IAAI,QAAQ,EAAE,iBAAiB,IAAI;AAAA,IAC/D;AAAA,IAEA,gBAAgB,UAAU;AACxB,aAAO,UAAU,IAAI,QAAQ;AAAA,IAC/B;AAAA,EACF;AACF;AAGO,SAAS,oCACd,SACyB;AACzB,SAAO;AAAA,IACL,MAAM,WAAW,OAAO;AACtB,YAAM,eAAe,MAAM,QAAQ;AAAA,QACjC,QAAQ,IAAI,OAAO,WAAW;AAC5B,cAAI;AACF,mBAAO,MAAM,OAAO,YAAY,KAAK;AAAA,UACvC,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO,QACJ,OAAO,CAAC,GAAG,UAAU,aAAa,KAAK,CAAC,EACxC,KAAK,CAAC,MAAM,UAAU,KAAK,SAAS,QAAQ,MAAM,SAAS,KAAK;AAAA,IACrE;AAAA,EACF;AACF;;;ACzEA,IAAM,eAAe;AACrB,IAAM,cAAc;AAEb,SAAS,0BACd,OACwB;AACxB,QAAM,EAAE,UAAU,OAAO,cAAc,aAAa,SAAS,QAAQ,IACnE;AAEF,WAAS,gBAAgB,MAA6C;AACpE,WAAO,EAAE,MAAM,cAAc,aAAa,KAAK,IAAI,MAAM,GAAG,MAAM,OAAO;AAAA,EAC3E;AAEA,QAAM,UAAkC;AAAA,IACtC,UAAU,EAAE,IAAI,UAAU,OAAO,OAAO,MAAM,SAAS,EAAE;AAAA,IACzD;AAAA,IACA;AAAA,IAEA,MAAM,aACJ,OACA,EAAE,MAAM,QAAQ,OAAO,GACM;AAE7B,UAAI,QAAQ,KAAK,OAAO,WAAW,WAAW,GAAG;AAC/C,eAAO,EAAE,SAAS,CAAC,GAAG,YAAY,KAAK;AAAA,MACzC;AACA,YAAM,gBAAgB,OAClB,cAAc,cAAc,KAAK,MAAM,IACvC;AACJ,YAAM,SAAS,MAAM,QAAQ,KAAK,OAAO;AAAA,QACvC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,QAAQ,UAAU;AAAA,MACpB,CAAC;AACD,aAAO;AAAA,QACL,SAAS,OAAO,MAAM,IAAI,CAAC,SAAS,WAAW,UAAU,IAAI,CAAC;AAAA,QAC9D,YAAY,OAAO,cAAc;AAAA,MACnC;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,QAAwB,MAAoC;AACrE,YAAM,QAAQ,gBAAgB,gBAAgB,IAAI,CAAC;AAAA,IACrD;AAAA,IAEA,MAAM,YACJ,OACA,MACkC;AAClC,UAAI,CAAC,QAAQ,sBAAsB;AACjC,eAAO;AAAA,MACT;AACA,aAAO,QAAQ,qBAAqB;AAAA,QAClC,aAAa,MAAM;AAAA,QACnB,WAAW,gBAAgB,IAAI;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,IAEA,iBAAiB,MAAwC;AACvD,aAAO;AAAA,QACL,MAAM,cAAc,aAAa,KAAK,IAAI,MAAM;AAAA,QAChD,MAAM;AAAA,QACN,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,QAAQ,QAAQ,KAAK,OAAO;AAClD,MAAI,eAAe;AACjB,YAAQ,SAAS,OACf,OACAA,WAC0B;AAC1B,YAAM,SAAS,MAAM,cAAc,OAAO;AAAA,QACxC,OAAOA,OAAM;AAAA,QACb,QAAQA,OAAM,UAAU;AAAA,QACxB,GAAIA,OAAM,SAAS,OAAO,CAAC,IAAI,EAAE,OAAOA,OAAM,MAAM;AAAA,QACpD,GAAIA,OAAM,SAAS,EAAE,QAAQA,OAAM,OAAO,IAAI,CAAC;AAAA,MACjD,CAAC;AACD,aAAO;AAAA,QACL,SAAS,OAAO,MAAM,IAAI,CAAC,SAAS,WAAW,UAAU,IAAI,CAAC;AAAA,QAC9D,YAAY,OAAO,cAAc;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,UAAkB,MAAwC;AAC5E,MAAI,KAAK,SAAS,SAAS;AACzB,WAAO;AAAA,MACL,KAAK,EAAE,UAAU,QAAQ,eAAe,gBAAgB,KAAK,EAAE,EAAE;AAAA,MACjE,MAAM;AAAA,MACN,aAAa,KAAK;AAAA,MAClB,aAAa;AAAA,MACb,GAAI,KAAK,kBAAkB,OACvB,CAAC,IACD,EAAE,YAAY,KAAK,eAAe;AAAA,MACtC,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAClD;AAAA,EACF;AACA,QAAM,YAAY,KAAK;AACvB,SAAO;AAAA,IACL,KAAK,EAAE,UAAU,QAAQ,cAAc,gBAAgB,UAAU,IAAI,EAAE;AAAA,IACvE,MAAM;AAAA,IACN,aAAa,UAAU,aAAa,KAAK,KAAK,SAAS,UAAU,IAAI;AAAA,IACrE,GAAI,UAAU,aAAa,OAAO,CAAC,IAAI,EAAE,WAAW,UAAU,UAAU;AAAA,IACxE,GAAI,UAAU,WAAW,OAAO,CAAC,IAAI,EAAE,SAAS,UAAU,QAAQ;AAAA,IAClE,GAAI,UAAU,WAAW,EAAE,UAAU,UAAU,SAAS,IAAI,CAAC;AAAA,EAC/D;AACF;AAEA,SAAS,cAAc,QAAgB,QAAwB;AAC7D,MAAI,CAAC,OAAO,WAAW,MAAM,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,iCAAiC,MAAM,YAAY,MAAM;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,gBAAgB,OAAO,MAAM,OAAO,MAAM,CAAC;AACpD;AAEA,SAAS,SAAS,MAAsB;AACtC,QAAM,UAAU,KAAK,QAAQ,QAAQ,EAAE;AACvC,QAAM,QAAQ,QAAQ,YAAY,GAAG;AACrC,SAAO,SAAS,IAAI,QAAQ,MAAM,QAAQ,CAAC,IAAI;AACjD;AAGO,SAAS,gBAAgB,OAAuB;AACrD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEO,SAAS,gBAAgB,OAAuB;AACrD,QAAM,aAAa,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,QAAQ,WAAW,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC;AAClE,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;;;AC7OO,SAAS,8BACd,MAC0B;AAC1B,QAAM,SAAS,oBAAI,IAAoC;AACvD,aAAW,OAAO,MAAM;AACtB,UAAM,iBAAiB,IAAI,KAAK,KAAK;AACrC,QAAI,CAAC,kBAAkB,OAAO,IAAI,cAAc,GAAG;AACjD;AAAA,IACF;AACA,WAAO,IAAI,gBAAgB;AAAA,MACzB,aAAa,IAAI,aAAa,KAAK,KAAK;AAAA,MACxC,MAAM,IAAI,SAAS,WAAW,WAAW;AAAA,MACzC,MAAM;AAAA,MACN,GAAI,IAAI,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,IAAI,QAAQ;AAAA,MAC5D,GAAI,IAAI,cAAc,SAAY,CAAC,IAAI,EAAE,WAAW,IAAI,UAAU;AAAA,IACpE,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;","names":["input"]}