@opengeni/documents 0.2.45 → 0.2.61

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.
package/dist/index.d.ts CHANGED
@@ -1,45 +1,44 @@
1
- import { Settings } from '@opengeni/config';
2
- import { FileAsset, KnowledgeSourceKind, DocumentSearchMode, AddDocumentRequest, DocumentCurationStatus, Document, CreateDocumentBaseRequest, DocumentBase, DocumentSearchResult } from '@opengeni/contracts';
3
- import { Database } from '@opengeni/db';
4
- import { ObjectStorage } from '@opengeni/storage';
5
-
6
- declare const DEFAULT_DOCUMENT_PARSER = "liteparse";
7
- declare const DEFAULT_DOCUMENT_EMBEDDING_MODEL = "text-embedding-3-large";
8
- declare const DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS = 3072;
9
- declare const DEFAULT_DOCUMENT_CHUNK_SIZE = 1200;
10
- declare const DEFAULT_DOCUMENT_CHUNK_OVERLAP = 160;
11
- declare const DEFAULT_DOCUMENT_CURATION_MODEL = "gpt-4o-mini";
12
- declare const DOCUMENT_CURATION_MAX_INPUT_CHARS = 24000;
13
- declare const DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;
14
- declare const DEFAULT_BASE_NAME = "Default";
15
- declare const DEFAULT_BASE_DESCRIPTION = "Default base for dropped files and notes.";
16
- type ParsedDocument = {
1
+ import type { Settings } from "@opengeni/config";
2
+ import type { AddDocumentRequest, CreateDocumentBaseRequest, Document, DocumentBase, DocumentCurationStatus, DocumentSearchMode, DocumentSearchResult, DocumentStatus, FileAsset, KnowledgeSourceKind } from "@opengeni/contracts";
3
+ import { type Database } from "@opengeni/db";
4
+ import type { ObjectStorage } from "@opengeni/storage";
5
+ export declare const DEFAULT_DOCUMENT_PARSER = "liteparse";
6
+ export declare const DEFAULT_DOCUMENT_EMBEDDING_MODEL = "text-embedding-3-large";
7
+ export declare const DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS = 3072;
8
+ export declare const DEFAULT_DOCUMENT_CHUNK_SIZE = 1200;
9
+ export declare const DEFAULT_DOCUMENT_CHUNK_OVERLAP = 160;
10
+ export declare const DEFAULT_DOCUMENT_CURATION_MODEL = "gpt-4o-mini";
11
+ export declare const DOCUMENT_CURATION_MAX_INPUT_CHARS = 24000;
12
+ export declare const DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;
13
+ export declare const DEFAULT_BASE_NAME = "Default";
14
+ export declare const DEFAULT_BASE_DESCRIPTION = "Default base for dropped files and notes.";
15
+ export type ParsedDocument = {
17
16
  text: string;
18
17
  metadata?: Record<string, unknown>;
19
18
  };
20
- type DocumentChunk = {
19
+ export type DocumentChunk = {
21
20
  text: string;
22
21
  metadata: Record<string, unknown>;
23
22
  };
24
- type DocumentParser = {
23
+ export type DocumentParser = {
25
24
  name: string;
26
25
  parse: (bytes: Uint8Array, file: FileAsset) => Promise<ParsedDocument>;
27
26
  };
28
- type DocumentChunker = {
27
+ export type DocumentChunker = {
29
28
  chunk: (parsed: ParsedDocument, file: FileAsset) => DocumentChunk[];
30
29
  };
31
- type DocumentEmbedder = {
30
+ export type DocumentEmbedder = {
32
31
  model: string;
33
32
  dimensions: number;
34
33
  embedMany: (texts: string[]) => Promise<number[][]>;
35
34
  embedQuery: (text: string) => Promise<number[]>;
36
35
  };
37
- type DocumentCurationCandidateBase = {
36
+ export type DocumentCurationCandidateBase = {
38
37
  id: string;
39
38
  name: string;
40
39
  description: string | null;
41
40
  };
42
- type DocumentCurationInput = {
41
+ export type DocumentCurationInput = {
43
42
  /** Parsed document text, clipped to DOCUMENT_CURATION_MAX_INPUT_CHARS. */
44
43
  text: string;
45
44
  filename: string;
@@ -48,7 +47,7 @@ type DocumentCurationInput = {
48
47
  /** Candidate bases the document could be filed into (never its current base). */
49
48
  bases: DocumentCurationCandidateBase[];
50
49
  };
51
- type DocumentCurationOutcome = {
50
+ export type DocumentCurationOutcome = {
52
51
  title: string | null;
53
52
  summary: string | null;
54
53
  sourceKind: KnowledgeSourceKind | null;
@@ -57,11 +56,11 @@ type DocumentCurationOutcome = {
57
56
  confidence: number;
58
57
  reason: string | null;
59
58
  };
60
- type DocumentCurator = {
59
+ export type DocumentCurator = {
61
60
  model: string;
62
61
  curate: (input: DocumentCurationInput) => Promise<DocumentCurationOutcome>;
63
62
  };
64
- type DocumentServices = {
63
+ export type DocumentServices = {
65
64
  parser: DocumentParser;
66
65
  chunker: DocumentChunker;
67
66
  embedder: DocumentEmbedder;
@@ -73,7 +72,7 @@ type DocumentServices = {
73
72
  * filter, private documents are invisible (only their creator may see them,
74
73
  * and only by passing their subject id).
75
74
  */
76
- type DocumentAccessFilter = {
75
+ export type DocumentAccessFilter = {
77
76
  /** Grant subject id of the human viewer; null/undefined hides private docs. */
78
77
  viewerSubjectId?: string | null | undefined;
79
78
  /**
@@ -83,7 +82,34 @@ type DocumentAccessFilter = {
83
82
  */
84
83
  agentOnly?: boolean | undefined;
85
84
  };
86
- type DocumentSearchInput = {
85
+ export type DocumentInventoryStatusCounts = Record<DocumentStatus, number>;
86
+ export type DocumentInventorySourceKindCounts = Record<KnowledgeSourceKind, number>;
87
+ export type DocumentInventory = {
88
+ baseCount: number;
89
+ bases: Array<{
90
+ id: string;
91
+ name: string;
92
+ visibleDocumentCount: number;
93
+ statusCounts: DocumentInventoryStatusCounts;
94
+ latestUpdatedAt: string | null;
95
+ }>;
96
+ visibleDocumentCount: number;
97
+ statusCounts: DocumentInventoryStatusCounts;
98
+ sourceKindCounts: DocumentInventorySourceKindCounts;
99
+ latestUpdatedAt: string | null;
100
+ topics: Array<{
101
+ name: string;
102
+ documentCount: number;
103
+ }>;
104
+ topicsTruncated: boolean;
105
+ };
106
+ export type DocumentInventoryInput = {
107
+ baseLimit: number;
108
+ topicLimit: number;
109
+ topicMaxChars: number;
110
+ access?: DocumentAccessFilter | undefined;
111
+ };
112
+ export type DocumentSearchInput = {
87
113
  workspaceId: string;
88
114
  query: string;
89
115
  baseIds?: string[] | undefined;
@@ -93,7 +119,7 @@ type DocumentSearchInput = {
93
119
  aclTags?: string[] | undefined;
94
120
  access?: DocumentAccessFilter | undefined;
95
121
  };
96
- type DocumentIndexHooks = {
122
+ export type DocumentIndexHooks = {
97
123
  beforeEmbed?: (input: {
98
124
  accountId: string;
99
125
  workspaceId: string;
@@ -101,20 +127,20 @@ type DocumentIndexHooks = {
101
127
  chunkCount: number;
102
128
  }) => Promise<void>;
103
129
  };
104
- declare class LiteParseDocumentParser implements DocumentParser {
130
+ export declare class LiteParseDocumentParser implements DocumentParser {
105
131
  readonly name = "liteparse";
106
132
  private parseQueue;
107
133
  parse(bytes: Uint8Array, file: FileAsset): Promise<ParsedDocument>;
108
134
  private parseWithLiteParse;
109
135
  private enqueueParse;
110
136
  }
111
- declare class RecursiveTextChunker implements DocumentChunker {
137
+ export declare class RecursiveTextChunker implements DocumentChunker {
112
138
  private readonly maxChars;
113
139
  private readonly overlapChars;
114
140
  constructor(maxChars?: number, overlapChars?: number);
115
141
  chunk(parsed: ParsedDocument, file: FileAsset): DocumentChunk[];
116
142
  }
117
- declare class OpenAIEmbeddingProvider implements DocumentEmbedder {
143
+ export declare class OpenAIEmbeddingProvider implements DocumentEmbedder {
118
144
  private client;
119
145
  private readonly apiKey;
120
146
  private readonly baseURL;
@@ -134,7 +160,7 @@ declare class OpenAIEmbeddingProvider implements DocumentEmbedder {
134
160
  embedQuery(text: string): Promise<number[]>;
135
161
  private openai;
136
162
  }
137
- declare class DeterministicEmbeddingProvider implements DocumentEmbedder {
163
+ export declare class DeterministicEmbeddingProvider implements DocumentEmbedder {
138
164
  readonly dimensions: number;
139
165
  readonly model: string;
140
166
  constructor(dimensions?: number, model?: string);
@@ -148,12 +174,12 @@ declare class DeterministicEmbeddingProvider implements DocumentEmbedder {
148
174
  * the `heuristic` provider and as the in-pipeline fallback when the LLM
149
175
  * curator fails — a drop must always end up named and summarized.
150
176
  */
151
- declare function heuristicCuration(input: DocumentCurationInput, contentType?: string): DocumentCurationOutcome;
152
- declare class HeuristicCurationProvider implements DocumentCurator {
177
+ export declare function heuristicCuration(input: DocumentCurationInput, contentType?: string): DocumentCurationOutcome;
178
+ export declare class HeuristicCurationProvider implements DocumentCurator {
153
179
  readonly model = "heuristic";
154
180
  curate(input: DocumentCurationInput): Promise<DocumentCurationOutcome>;
155
181
  }
156
- declare class OpenAICurationProvider implements DocumentCurator {
182
+ export declare class OpenAICurationProvider implements DocumentCurator {
157
183
  private client;
158
184
  private readonly apiKey;
159
185
  private readonly baseURL;
@@ -171,31 +197,37 @@ declare class OpenAICurationProvider implements DocumentCurator {
171
197
  private openai;
172
198
  }
173
199
  /** Parse + clamp model output; a targetBaseId outside the candidate list is dropped. */
174
- declare function parseCurationOutcome(raw: string, bases: DocumentCurationCandidateBase[]): DocumentCurationOutcome;
175
- declare function createDocumentServices(settings?: Settings, overrides?: Partial<DocumentServices>): DocumentServices;
176
- declare function documentOpenAIEmbeddingConfig(settings?: Settings): {
200
+ export declare function parseCurationOutcome(raw: string, bases: DocumentCurationCandidateBase[]): DocumentCurationOutcome;
201
+ export declare function createDocumentServices(settings?: Settings, overrides?: Partial<DocumentServices>): DocumentServices;
202
+ export declare function documentOpenAIEmbeddingConfig(settings?: Settings): {
177
203
  apiKey?: string | undefined;
178
204
  baseURL?: string | undefined;
179
205
  defaultHeaders?: Record<string, string> | undefined;
180
206
  defaultQuery?: Record<string, string> | undefined;
181
207
  };
182
- declare function createDocumentBase(db: Database, input: CreateDocumentBaseRequest & {
208
+ export declare function createDocumentBase(db: Database, input: CreateDocumentBaseRequest & {
183
209
  accountId: string;
184
210
  workspaceId: string;
185
211
  }): Promise<DocumentBase>;
186
- declare function listDocumentBases(db: Database, workspaceId: string): Promise<DocumentBase[]>;
187
- declare function getDocumentBase(db: Database, workspaceId: string, baseId: string): Promise<DocumentBase | null>;
212
+ export declare function listDocumentBases(db: Database, workspaceId: string): Promise<DocumentBase[]>;
213
+ /**
214
+ * Return a bounded structural inventory without loading document records.
215
+ * Document predicates are applied independently to every aggregate so private
216
+ * counts and topic metadata remain scoped to the initiating grant subject.
217
+ */
218
+ export declare function getDocumentInventory(db: Database, workspaceId: string, input: DocumentInventoryInput): Promise<DocumentInventory>;
219
+ export declare function getDocumentBase(db: Database, workspaceId: string, baseId: string): Promise<DocumentBase | null>;
188
220
  /**
189
221
  * Find-or-create the workspace's Default base — where knowledge drops land
190
222
  * before configured curation files them elsewhere. Matched by name
191
223
  * (case-insensitive) so a user-created "Default" is adopted rather than
192
224
  * duplicated.
193
225
  */
194
- declare function ensureDefaultBase(db: Database, input: {
226
+ export declare function ensureDefaultBase(db: Database, input: {
195
227
  accountId: string;
196
228
  workspaceId: string;
197
229
  }): Promise<DocumentBase>;
198
- declare function addDocumentToBase(db: Database, input: AddDocumentRequest & {
230
+ export declare function addDocumentToBase(db: Database, input: AddDocumentRequest & {
199
231
  accountId: string;
200
232
  workspaceId: string;
201
233
  baseId: string;
@@ -208,35 +240,34 @@ declare function addDocumentToBase(db: Database, input: AddDocumentRequest & {
208
240
  * target, applies the stored curation suggestion. A 'suggested' or 'pending'
209
241
  * document that gets moved counts as filed.
210
242
  */
211
- declare function moveDocumentToBase(db: Database, input: {
243
+ export declare function moveDocumentToBase(db: Database, input: {
212
244
  accountId: string;
213
245
  workspaceId: string;
214
246
  documentId: string;
215
247
  targetBaseId?: string | null | undefined;
216
248
  access?: DocumentAccessFilter | undefined;
217
249
  }): Promise<Document>;
218
- declare function deleteDocumentFromBase(db: Database, input: {
250
+ export declare function deleteDocumentFromBase(db: Database, input: {
219
251
  accountId: string;
220
252
  workspaceId: string;
221
253
  baseId: string;
222
254
  documentId: string;
223
255
  access?: DocumentAccessFilter | undefined;
224
256
  }): Promise<void>;
225
- declare function listDocuments(db: Database, workspaceId: string, baseId: string, access?: DocumentAccessFilter): Promise<Document[]>;
226
- declare function getDocument(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter): Promise<Document | null>;
227
- declare function queueDocumentForReindex(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter): Promise<Document>;
228
- declare function indexDocumentNow(db: Database, objectStorage: ObjectStorage, workspaceId: string, documentId: string, services?: DocumentServices, hooks?: DocumentIndexHooks): Promise<Document>;
229
- declare function searchDocuments(db: Database, input: DocumentSearchInput, services?: Pick<DocumentServices, "embedder">): Promise<DocumentSearchResult[]>;
230
- declare function getDocumentChunk(db: Database, workspaceId: string, chunkId: string, access?: DocumentAccessFilter): Promise<DocumentSearchResult | null>;
257
+ export declare function listDocuments(db: Database, workspaceId: string, baseId: string, access?: DocumentAccessFilter): Promise<Document[]>;
258
+ export declare function getDocument(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter): Promise<Document | null>;
259
+ export declare function queueDocumentForReindex(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter): Promise<Document>;
260
+ export declare function indexDocumentNow(db: Database, objectStorage: ObjectStorage, workspaceId: string, documentId: string, services?: DocumentServices, hooks?: DocumentIndexHooks): Promise<Document>;
261
+ export declare function searchDocuments(db: Database, input: DocumentSearchInput, services?: Pick<DocumentServices, "embedder">): Promise<DocumentSearchResult[]>;
262
+ export declare function getDocumentChunk(db: Database, workspaceId: string, chunkId: string, access?: DocumentAccessFilter): Promise<DocumentSearchResult | null>;
231
263
  /** Whether a single already-fetched document is readable by this human viewer. */
232
- declare function canViewDocument(document: Pick<DocumentAccessRecord, "visibility" | "createdBy">, viewerSubjectId: string | null | undefined): boolean;
264
+ export declare function canViewDocument(document: Pick<DocumentAccessRecord, "visibility" | "createdBy">, viewerSubjectId: string | null | undefined): boolean;
233
265
  type DocumentAccessRecord = {
234
266
  visibility: string;
235
267
  createdBy: string | null;
236
268
  agentAccess: boolean;
237
269
  };
238
- declare function parseDocumentBytes(bytes: Uint8Array, file: FileAsset, parser?: DocumentParser): Promise<ParsedDocument>;
239
- declare function chunkText(text: string, maxChars?: number, overlapChars?: number): string[];
240
- declare function deterministicEmbedding(text: string, dimensions?: number): number[];
241
-
242
- export { DEFAULT_BASE_DESCRIPTION, DEFAULT_BASE_NAME, DEFAULT_DOCUMENT_CHUNK_OVERLAP, DEFAULT_DOCUMENT_CHUNK_SIZE, DEFAULT_DOCUMENT_CURATION_MODEL, DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS, DEFAULT_DOCUMENT_EMBEDDING_MODEL, DEFAULT_DOCUMENT_PARSER, DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE, DOCUMENT_CURATION_MAX_INPUT_CHARS, DeterministicEmbeddingProvider, type DocumentAccessFilter, type DocumentChunk, type DocumentChunker, type DocumentCurationCandidateBase, type DocumentCurationInput, type DocumentCurationOutcome, type DocumentCurator, type DocumentEmbedder, type DocumentIndexHooks, type DocumentParser, type DocumentSearchInput, type DocumentServices, HeuristicCurationProvider, LiteParseDocumentParser, OpenAICurationProvider, OpenAIEmbeddingProvider, type ParsedDocument, RecursiveTextChunker, addDocumentToBase, canViewDocument, chunkText, createDocumentBase, createDocumentServices, deleteDocumentFromBase, deterministicEmbedding, documentOpenAIEmbeddingConfig, ensureDefaultBase, getDocument, getDocumentBase, getDocumentChunk, heuristicCuration, indexDocumentNow, listDocumentBases, listDocuments, moveDocumentToBase, parseCurationOutcome, parseDocumentBytes, queueDocumentForReindex, searchDocuments };
270
+ export declare function parseDocumentBytes(bytes: Uint8Array, file: FileAsset, parser?: DocumentParser): Promise<ParsedDocument>;
271
+ export declare function chunkText(text: string, maxChars?: number, overlapChars?: number): string[];
272
+ export declare function deterministicEmbedding(text: string, dimensions?: number): number[];
273
+ export {};
package/dist/index.js CHANGED
@@ -14,6 +14,9 @@ var DOCUMENT_CURATION_MAX_INPUT_CHARS = 24e3;
14
14
  var DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;
15
15
  var DEFAULT_BASE_NAME = "Default";
16
16
  var DEFAULT_BASE_DESCRIPTION = "Default base for dropped files and notes.";
17
+ var DOCUMENT_INVENTORY_MAX_BASE_LIMIT = 100;
18
+ var DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT = 100;
19
+ var DOCUMENT_INVENTORY_MAX_TOPIC_CHARS = 256;
17
20
  var LiteParseDocumentParser = class {
18
21
  name = DEFAULT_DOCUMENT_PARSER;
19
22
  parseQueue = Promise.resolve();
@@ -343,6 +346,123 @@ async function listDocumentBases(db, workspaceId) {
343
346
  return rows.map(mapDocumentBase);
344
347
  });
345
348
  }
349
+ async function getDocumentInventory(db, workspaceId, input) {
350
+ const baseLimit = requireDocumentInventoryLimit(
351
+ input.baseLimit,
352
+ DOCUMENT_INVENTORY_MAX_BASE_LIMIT,
353
+ "baseLimit"
354
+ );
355
+ const topicLimit = requireDocumentInventoryLimit(
356
+ input.topicLimit,
357
+ DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT,
358
+ "topicLimit"
359
+ );
360
+ const topicMaxChars = requireDocumentInventoryLimit(
361
+ input.topicMaxChars,
362
+ DOCUMENT_INVENTORY_MAX_TOPIC_CHARS,
363
+ "topicMaxChars"
364
+ );
365
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
366
+ const [baseTotal] = await scopedDb.select({ count: sql`count(*)::int` }).from(schema.documentBases).where(eq(schema.documentBases.workspaceId, workspaceId));
367
+ const baseJoin = and(
368
+ eq(schema.documents.workspaceId, workspaceId),
369
+ eq(schema.documents.baseId, schema.documentBases.id),
370
+ ...documentAccessConditions(input.access)
371
+ );
372
+ const baseRows = await scopedDb.select({
373
+ id: schema.documentBases.id,
374
+ name: schema.documentBases.name,
375
+ visibleDocumentCount: sql`count(${schema.documents.id})::int`,
376
+ queuedCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,
377
+ indexingCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,
378
+ readyCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,
379
+ failedCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,
380
+ latestUpdatedAt: sql`max(${schema.documents.updatedAt})`
381
+ }).from(schema.documentBases).leftJoin(schema.documents, baseJoin).where(eq(schema.documentBases.workspaceId, workspaceId)).groupBy(schema.documentBases.id, schema.documentBases.name, schema.documentBases.createdAt).orderBy(desc(schema.documentBases.createdAt), asc(schema.documentBases.id)).limit(baseLimit);
382
+ const documentWhere = and(
383
+ eq(schema.documents.workspaceId, workspaceId),
384
+ ...documentAccessConditions(input.access)
385
+ );
386
+ const [summary] = await scopedDb.select({
387
+ visibleDocumentCount: sql`count(${schema.documents.id})::int`,
388
+ queuedCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,
389
+ indexingCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,
390
+ readyCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,
391
+ failedCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,
392
+ manualUploadCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'manual_upload')::int`,
393
+ meetingTranscriptCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'meeting_transcript')::int`,
394
+ repositoryCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'repository')::int`,
395
+ emailCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'email')::int`,
396
+ chatCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'chat')::int`,
397
+ documentCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'document')::int`,
398
+ webCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'web')::int`,
399
+ otherCount: sql`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'other')::int`,
400
+ latestUpdatedAt: sql`max(${schema.documents.updatedAt})`
401
+ }).from(schema.documents).where(documentWhere);
402
+ const topicStringValue = sql`topic.value #>> '{}'`;
403
+ const topicNormalizedName = sql`btrim(regexp_replace(normalize(${topicStringValue}, NFKC), '[[:space:]]+', ' ', 'g'))`;
404
+ const topicName = sql`left(${topicNormalizedName}, ${topicMaxChars})`;
405
+ const topicDocumentCount = sql`count(distinct ${schema.documents.id})::int`;
406
+ const topicRows = await scopedDb.select({ name: topicName, documentCount: topicDocumentCount }).from(schema.documents).innerJoin(
407
+ sql`lateral jsonb_array_elements(${schema.documents.topics}) as topic(value)`,
408
+ sql`true`
409
+ ).where(
410
+ and(
411
+ documentWhere,
412
+ sql`jsonb_typeof(topic.value) = 'string'`,
413
+ sql`nullif(${topicNormalizedName}, '') is not null`
414
+ )
415
+ ).groupBy(sql`1`).orderBy(sql`2 desc`, sql`1 asc`).limit(topicLimit + 1);
416
+ const statusCounts = documentInventoryStatusCounts(summary ?? {});
417
+ const topics = topicRows.slice(0, topicLimit).map((topic) => ({
418
+ name: topic.name,
419
+ documentCount: Number(topic.documentCount)
420
+ }));
421
+ return {
422
+ baseCount: Number(baseTotal?.count ?? 0),
423
+ bases: baseRows.map((base) => ({
424
+ id: base.id,
425
+ name: base.name,
426
+ visibleDocumentCount: Number(base.visibleDocumentCount),
427
+ statusCounts: documentInventoryStatusCounts(base),
428
+ latestUpdatedAt: documentInventoryTimestamp(base.latestUpdatedAt)
429
+ })),
430
+ visibleDocumentCount: Number(summary?.visibleDocumentCount ?? 0),
431
+ statusCounts,
432
+ sourceKindCounts: {
433
+ manual_upload: Number(summary?.manualUploadCount ?? 0),
434
+ meeting_transcript: Number(summary?.meetingTranscriptCount ?? 0),
435
+ repository: Number(summary?.repositoryCount ?? 0),
436
+ email: Number(summary?.emailCount ?? 0),
437
+ chat: Number(summary?.chatCount ?? 0),
438
+ document: Number(summary?.documentCount ?? 0),
439
+ web: Number(summary?.webCount ?? 0),
440
+ other: Number(summary?.otherCount ?? 0)
441
+ },
442
+ latestUpdatedAt: documentInventoryTimestamp(summary?.latestUpdatedAt ?? null),
443
+ topics,
444
+ topicsTruncated: topicRows.length > topics.length
445
+ };
446
+ });
447
+ }
448
+ function requireDocumentInventoryLimit(value, max, name) {
449
+ if (!Number.isInteger(value) || value < 1 || value > max) {
450
+ throw new Error(`${name} must be an integer between 1 and ${max}`);
451
+ }
452
+ return value;
453
+ }
454
+ function documentInventoryStatusCounts(input) {
455
+ return {
456
+ queued: Number(input.queuedCount ?? 0),
457
+ indexing: Number(input.indexingCount ?? 0),
458
+ ready: Number(input.readyCount ?? 0),
459
+ failed: Number(input.failedCount ?? 0)
460
+ };
461
+ }
462
+ function documentInventoryTimestamp(value) {
463
+ if (!value) return null;
464
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
465
+ }
346
466
  async function getDocumentBase(db, workspaceId, baseId) {
347
467
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
348
468
  const [row] = await scopedDb.select().from(schema.documentBases).where(
@@ -1242,6 +1362,7 @@ export {
1242
1362
  getDocument,
1243
1363
  getDocumentBase,
1244
1364
  getDocumentChunk,
1365
+ getDocumentInventory,
1245
1366
  heuristicCuration,
1246
1367
  indexDocumentNow,
1247
1368
  listDocumentBases,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport type {\n AddDocumentRequest,\n CreateDocumentBaseRequest,\n Document,\n DocumentBase,\n DocumentCuration,\n DocumentCurationStatus,\n DocumentSearchMode,\n DocumentSearchResult,\n DocumentVisibility,\n FileAsset,\n KnowledgeSourceKind,\n} from \"@opengeni/contracts\";\nimport { requireFile, withRlsContext, withWorkspaceRls, type Database } from \"@opengeni/db\";\nimport * as schema from \"@opengeni/db/schema\";\nimport type { ObjectStorage } from \"@opengeni/storage\";\nimport { LiteParse } from \"@llamaindex/liteparse\";\nimport { and, asc, desc, eq, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport OpenAI from \"openai\";\n\nexport const DEFAULT_DOCUMENT_PARSER = \"liteparse\";\nexport const DEFAULT_DOCUMENT_EMBEDDING_MODEL = \"text-embedding-3-large\";\nexport const DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS = 3072;\nexport const DEFAULT_DOCUMENT_CHUNK_SIZE = 1200;\nexport const DEFAULT_DOCUMENT_CHUNK_OVERLAP = 160;\nexport const DEFAULT_DOCUMENT_CURATION_MODEL = \"gpt-4o-mini\";\n// Curator input is a preview, not the whole document — enough to name and\n// classify without paying for a full-document prompt on every drop.\nexport const DOCUMENT_CURATION_MAX_INPUT_CHARS = 24_000;\n// A base move is applied automatically only at or above this curator\n// confidence; below it the suggestion is surfaced for human review instead.\nexport const DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;\n// The per-workspace default base: where knowledge drops land (created on\n// first drop) and stay unless configured curation files them into a topical base.\nexport const DEFAULT_BASE_NAME = \"Default\";\nexport const DEFAULT_BASE_DESCRIPTION = \"Default base for dropped files and notes.\";\n\nexport type ParsedDocument = {\n text: string;\n metadata?: Record<string, unknown>;\n};\n\nexport type DocumentChunk = {\n text: string;\n metadata: Record<string, unknown>;\n};\n\nexport type DocumentParser = {\n name: string;\n parse: (bytes: Uint8Array, file: FileAsset) => Promise<ParsedDocument>;\n};\n\nexport type DocumentChunker = {\n chunk: (parsed: ParsedDocument, file: FileAsset) => DocumentChunk[];\n};\n\nexport type DocumentEmbedder = {\n model: string;\n dimensions: number;\n embedMany: (texts: string[]) => Promise<number[][]>;\n embedQuery: (text: string) => Promise<number[]>;\n};\n\nexport type DocumentCurationCandidateBase = {\n id: string;\n name: string;\n description: string | null;\n};\n\nexport type DocumentCurationInput = {\n /** Parsed document text, clipped to DOCUMENT_CURATION_MAX_INPUT_CHARS. */\n text: string;\n filename: string;\n /** Current (usually filename-derived) title. */\n title: string;\n /** Candidate bases the document could be filed into (never its current base). */\n bases: DocumentCurationCandidateBase[];\n};\n\nexport type DocumentCurationOutcome = {\n title: string | null;\n summary: string | null;\n sourceKind: KnowledgeSourceKind | null;\n topics: string[];\n targetBaseId: string | null;\n confidence: number;\n reason: string | null;\n};\n\nexport type DocumentCurator = {\n model: string;\n curate: (input: DocumentCurationInput) => Promise<DocumentCurationOutcome>;\n};\n\nexport type DocumentServices = {\n parser: DocumentParser;\n chunker: DocumentChunker;\n embedder: DocumentEmbedder;\n /** Optional: names/summarizes/categorizes dropped documents during indexing. */\n curator?: DocumentCurator | undefined;\n};\n\n/**\n * Read-scoping for document queries. Fail-closed: when a caller supplies no\n * filter, private documents are invisible (only their creator may see them,\n * and only by passing their subject id).\n */\nexport type DocumentAccessFilter = {\n /** Grant subject id of the human viewer; null/undefined hides private docs. */\n viewerSubjectId?: string | null | undefined;\n /**\n * Agent retrieval surface: only agent-enabled documents. Workspace-visible\n * documents are available to every agent; private documents are available\n * only when the agent carries the creating subject as its viewer subject.\n */\n agentOnly?: boolean | undefined;\n};\n\nexport type DocumentSearchInput = {\n workspaceId: string;\n query: string;\n baseIds?: string[] | undefined;\n limit?: number | undefined;\n mode?: DocumentSearchMode | undefined;\n sourceKinds?: KnowledgeSourceKind[] | undefined;\n aclTags?: string[] | undefined;\n access?: DocumentAccessFilter | undefined;\n};\n\nexport type DocumentIndexHooks = {\n beforeEmbed?: (input: {\n accountId: string;\n workspaceId: string;\n documentId: string;\n chunkCount: number;\n }) => Promise<void>;\n};\n\nexport class LiteParseDocumentParser implements DocumentParser {\n readonly name = DEFAULT_DOCUMENT_PARSER;\n private parseQueue: Promise<void> = Promise.resolve();\n\n async parse(bytes: Uint8Array, file: FileAsset): Promise<ParsedDocument> {\n const text = isTextLike(file)\n ? Buffer.from(bytes).toString(\"utf8\").replace(/\\0/g, \" \").trim()\n : await this.parseWithLiteParse(bytes);\n if (!text.trim()) {\n throw new Error(`Parsed document is empty: ${file.filename}`);\n }\n return {\n text: text.trim(),\n metadata: {\n parser: this.name,\n filename: file.filename,\n contentType: file.contentType,\n },\n };\n }\n\n private async parseWithLiteParse(bytes: Uint8Array): Promise<string> {\n return await this.enqueueParse(async () => {\n const parser = new LiteParse({ ocrEnabled: true, numWorkers: 1 });\n const result = await parser.parse(Buffer.from(bytes), true);\n const text = typeof result?.text === \"string\" ? result.text : \"\";\n return text.replace(/\\0/g, \" \").trim();\n });\n }\n\n private async enqueueParse<T>(task: () => Promise<T>): Promise<T> {\n const previous = this.parseQueue;\n let release: () => void = () => undefined;\n this.parseQueue = new Promise<void>((resolve) => {\n release = resolve;\n });\n await previous.catch(() => undefined);\n try {\n return await task();\n } finally {\n release();\n }\n }\n}\n\nexport class RecursiveTextChunker implements DocumentChunker {\n constructor(\n private readonly maxChars = DEFAULT_DOCUMENT_CHUNK_SIZE,\n private readonly overlapChars = DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n ) {\n if (overlapChars >= maxChars) {\n throw new Error(\"document chunk overlap must be smaller than chunk size\");\n }\n }\n\n chunk(parsed: ParsedDocument, file: FileAsset): DocumentChunk[] {\n return chunkText(parsed.text, this.maxChars, this.overlapChars).map((text, index) => ({\n text,\n metadata: {\n ...parsed.metadata,\n filename: file.filename,\n contentType: file.contentType,\n chunkIndex: index,\n },\n }));\n }\n}\n\nexport class OpenAIEmbeddingProvider implements DocumentEmbedder {\n private client: OpenAI | null = null;\n private readonly apiKey: string | undefined;\n private readonly baseURL: string | undefined;\n\n constructor(args: {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n model?: string | undefined;\n dimensions?: number | undefined;\n }) {\n this.apiKey = args.apiKey ?? process.env.OPENAI_API_KEY;\n this.baseURL = args.baseURL;\n this.defaultHeaders = args.defaultHeaders;\n this.defaultQuery = args.defaultQuery;\n this.model = args.model ?? DEFAULT_DOCUMENT_EMBEDDING_MODEL;\n this.dimensions = args.dimensions ?? DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS;\n }\n\n readonly model: string;\n readonly dimensions: number;\n private readonly defaultHeaders: Record<string, string> | undefined;\n private readonly defaultQuery: Record<string, string> | undefined;\n\n async embedMany(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const out: number[][] = [];\n for (let start = 0; start < texts.length; start += 64) {\n const batch = texts.slice(start, start + 64);\n const response = await this.openai().embeddings.create({\n model: this.model,\n input: batch,\n dimensions: this.dimensions,\n });\n for (const item of response.data) {\n out.push(validateEmbedding(item.embedding, this.dimensions, this.model));\n }\n }\n return out;\n }\n\n async embedQuery(text: string): Promise<number[]> {\n const [embedding] = await this.embedMany([text]);\n if (!embedding) {\n throw new Error(\"Embedding provider returned no query embedding\");\n }\n return embedding;\n }\n\n private openai(): OpenAI {\n if (!this.apiKey) {\n throw new Error(\"OpenAI document embeddings require an API key\");\n }\n this.client ??= new OpenAI({\n apiKey: this.apiKey,\n ...(this.baseURL ? { baseURL: this.baseURL } : {}),\n ...(this.defaultQuery ? { defaultQuery: this.defaultQuery } : {}),\n ...(this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}),\n });\n return this.client;\n }\n}\n\nexport class DeterministicEmbeddingProvider implements DocumentEmbedder {\n constructor(\n readonly dimensions = DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS,\n readonly model = `deterministic-local-${dimensions}`,\n ) {}\n\n async embedMany(texts: string[]): Promise<number[][]> {\n return texts.map((text) => deterministicEmbedding(text, this.dimensions));\n }\n\n async embedQuery(text: string): Promise<number[]> {\n return deterministicEmbedding(text, this.dimensions);\n }\n}\n\n/**\n * Deterministic no-network curation: first meaningful line becomes the title,\n * the opening text becomes the summary, and the kind is guessed from the\n * filename/content type. Never proposes a base move (confidence 0). Used as\n * the `heuristic` provider and as the in-pipeline fallback when the LLM\n * curator fails — a drop must always end up named and summarized.\n */\nexport function heuristicCuration(\n input: DocumentCurationInput,\n contentType = \"application/octet-stream\",\n): DocumentCurationOutcome {\n const firstLine = input.text\n .split(\"\\n\")\n .map((line) => line.replace(/^[#>\\s*-]+/, \"\").trim())\n .find((line) => line.length >= 3);\n const title = (firstLine ?? input.title).slice(0, 120).trim() || input.title;\n const summaryWindow = input.text.replace(/\\s+/g, \" \").trim().slice(0, 360);\n const summary =\n summaryWindow.length === 360 ? `${summaryWindow.slice(0, 357)}...` : summaryWindow;\n return {\n title,\n summary: summary || null,\n sourceKind: heuristicSourceKind(input.filename, contentType),\n topics: [],\n targetBaseId: null,\n confidence: 0,\n reason: null,\n };\n}\n\nfunction heuristicSourceKind(rawFilename: string, rawContentType: string): KnowledgeSourceKind {\n const filename = rawFilename.toLowerCase();\n const contentType = rawContentType.toLowerCase();\n if (filename.endsWith(\".eml\") || contentType === \"message/rfc822\") return \"email\";\n if (filename.endsWith(\".vtt\") || filename.endsWith(\".srt\") || filename.includes(\"transcript\")) {\n return \"meeting_transcript\";\n }\n if (contentType === \"text/html\") return \"web\";\n if (contentType === \"application/pdf\" || filename.endsWith(\".docx\") || filename.endsWith(\".md\")) {\n return \"document\";\n }\n return \"manual_upload\";\n}\n\nexport class HeuristicCurationProvider implements DocumentCurator {\n readonly model = \"heuristic\";\n\n async curate(input: DocumentCurationInput): Promise<DocumentCurationOutcome> {\n return heuristicCuration(input);\n }\n}\n\nconst CURATION_SYSTEM_PROMPT = [\n \"You organize a team knowledge base. Given the beginning of a dropped document\",\n \"and the list of existing collections (bases), return STRICT JSON with keys:\",\n '\"title\" (concise, specific, <= 120 chars, no filename extensions),',\n '\"summary\" (2-3 sentences, plain prose, what the document is and why it matters),',\n '\"sourceKind\" (one of: manual_upload, meeting_transcript, repository, email, chat, document, web, other),',\n '\"topics\" (3-6 short lowercase tags),',\n '\"targetBaseId\" (the id of the best-fitting existing base, or null if none fits),',\n '\"confidence\" (0..1 — how sure you are the document belongs in targetBaseId),',\n '\"reason\" (one sentence explaining the filing choice).',\n \"Only pick a targetBaseId from the provided list. If the document fits no base\",\n \"well, return targetBaseId null and confidence 0. Respond with JSON only.\",\n].join(\" \");\n\nexport class OpenAICurationProvider implements DocumentCurator {\n private client: OpenAI | null = null;\n private readonly apiKey: string | undefined;\n private readonly baseURL: string | undefined;\n private readonly defaultHeaders: Record<string, string> | undefined;\n private readonly defaultQuery: Record<string, string> | undefined;\n readonly model: string;\n\n constructor(args: {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n model?: string | undefined;\n }) {\n this.apiKey = args.apiKey ?? process.env.OPENAI_API_KEY;\n this.baseURL = args.baseURL;\n this.defaultHeaders = args.defaultHeaders;\n this.defaultQuery = args.defaultQuery;\n this.model = args.model ?? DEFAULT_DOCUMENT_CURATION_MODEL;\n }\n\n async curate(input: DocumentCurationInput): Promise<DocumentCurationOutcome> {\n const response = await this.openai().chat.completions.create({\n model: this.model,\n response_format: { type: \"json_object\" },\n messages: [\n { role: \"system\", content: CURATION_SYSTEM_PROMPT },\n {\n role: \"user\",\n content: JSON.stringify({\n filename: input.filename,\n currentTitle: input.title,\n bases: input.bases,\n text: input.text,\n }),\n },\n ],\n });\n const raw = response.choices[0]?.message?.content;\n if (!raw) {\n throw new Error(\"curation model returned no content\");\n }\n return parseCurationOutcome(raw, input.bases);\n }\n\n private openai(): OpenAI {\n if (!this.apiKey) {\n throw new Error(\"OpenAI document curation requires an API key\");\n }\n this.client ??= new OpenAI({\n apiKey: this.apiKey,\n ...(this.baseURL ? { baseURL: this.baseURL } : {}),\n ...(this.defaultQuery ? { defaultQuery: this.defaultQuery } : {}),\n ...(this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}),\n });\n return this.client;\n }\n}\n\n/** Parse + clamp model output; a targetBaseId outside the candidate list is dropped. */\nexport function parseCurationOutcome(\n raw: string,\n bases: DocumentCurationCandidateBase[],\n): DocumentCurationOutcome {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"curation model returned non-object JSON\");\n }\n const record = parsed as Record<string, unknown>;\n const knownBase = bases.find((base) => base.id === record.targetBaseId);\n const confidence =\n typeof record.confidence === \"number\" && Number.isFinite(record.confidence)\n ? Math.min(1, Math.max(0, record.confidence))\n : 0;\n return {\n title:\n cleanString(typeof record.title === \"string\" ? record.title.slice(0, 200) : null) ?? null,\n summary:\n cleanString(typeof record.summary === \"string\" ? record.summary.slice(0, 2000) : null) ??\n null,\n sourceKind:\n typeof record.sourceKind === \"string\"\n ? normalizeKnowledgeSourceKind(record.sourceKind)\n : null,\n topics: cleanStringArray(\n Array.isArray(record.topics)\n ? record.topics.filter((topic): topic is string => typeof topic === \"string\").slice(0, 8)\n : [],\n ).map((topic) => topic.toLowerCase().slice(0, 60)),\n targetBaseId: knownBase?.id ?? null,\n confidence: knownBase ? confidence : 0,\n reason:\n cleanString(typeof record.reason === \"string\" ? record.reason.slice(0, 500) : null) ?? null,\n };\n}\n\nexport function createDocumentServices(\n settings?: Settings,\n overrides: Partial<DocumentServices> = {},\n): DocumentServices {\n const dimensions = settings?.documentEmbeddingDimensions ?? DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS;\n const openAIEmbeddingConfig = documentOpenAIEmbeddingConfig(settings);\n return {\n parser: overrides.parser ?? new LiteParseDocumentParser(),\n chunker:\n overrides.chunker ??\n new RecursiveTextChunker(\n settings?.documentChunkSize ?? DEFAULT_DOCUMENT_CHUNK_SIZE,\n settings?.documentChunkOverlap ?? DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n ),\n embedder:\n overrides.embedder ??\n (settings?.documentEmbeddingProvider === \"deterministic\"\n ? new DeterministicEmbeddingProvider(dimensions, settings.documentEmbeddingModel)\n : new OpenAIEmbeddingProvider({\n ...openAIEmbeddingConfig,\n model: settings?.documentEmbeddingModel ?? DEFAULT_DOCUMENT_EMBEDDING_MODEL,\n dimensions,\n })),\n curator: overrides.curator ?? createDocumentCurator(settings),\n };\n}\n\nfunction createDocumentCurator(settings?: Settings): DocumentCurator | undefined {\n const provider = settings?.documentCurationProvider ?? \"openai\";\n if (provider === \"none\") return undefined;\n if (provider === \"heuristic\") return new HeuristicCurationProvider();\n const embeddingConfig = documentOpenAIEmbeddingConfig(settings);\n return new OpenAICurationProvider({\n apiKey: settings?.documentCurationApiKey ?? embeddingConfig.apiKey,\n baseURL: settings?.documentCurationBaseUrl ?? embeddingConfig.baseURL,\n defaultHeaders: embeddingConfig.defaultHeaders,\n defaultQuery: embeddingConfig.defaultQuery,\n model: settings?.documentCurationModel ?? DEFAULT_DOCUMENT_CURATION_MODEL,\n });\n}\n\nexport function documentOpenAIEmbeddingConfig(settings?: Settings): {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n} {\n if (!settings) return {};\n if (settings.documentEmbeddingApiKey || settings.documentEmbeddingBaseUrl) {\n return {\n apiKey:\n settings.documentEmbeddingApiKey ?? settings.openaiApiKey ?? settings.azureOpenaiApiKey,\n baseURL:\n settings.documentEmbeddingBaseUrl ?? settings.openaiBaseUrl ?? settings.azureOpenaiBaseUrl,\n };\n }\n if (settings.openaiProvider === \"azure\") {\n const baseURL = settings.azureOpenaiBaseUrl ?? azureDeploymentBaseUrl(settings);\n return {\n apiKey: settings.azureOpenaiApiKey ?? settings.azureOpenaiAdToken ?? \"azure-ad-token\",\n baseURL,\n defaultQuery: azureOpenAIDefaultQuery(settings, baseURL),\n defaultHeaders:\n settings.azureOpenaiAdToken && !settings.azureOpenaiApiKey\n ? { Authorization: `Bearer ${settings.azureOpenaiAdToken}` }\n : undefined,\n };\n }\n return {\n apiKey: settings.openaiApiKey,\n baseURL: settings.openaiBaseUrl,\n };\n}\n\nfunction azureDeploymentBaseUrl(settings: Settings): string {\n const endpoint = settings.azureOpenaiEndpoint?.replace(/\\/+$/, \"\");\n if (!endpoint || !settings.azureOpenaiDeployment) {\n throw new Error(\"Azure OpenAI endpoint/deployment settings are incomplete\");\n }\n return `${endpoint}/openai/deployments/${settings.azureOpenaiDeployment}`;\n}\n\nfunction azureOpenAIDefaultQuery(\n settings: Pick<Settings, \"azureOpenaiApiVersion\">,\n baseURL: string,\n): Record<string, string> | undefined {\n if (!settings.azureOpenaiApiVersion) return undefined;\n const normalized = baseURL.replace(/\\/+$/, \"\").toLowerCase();\n if (normalized.endsWith(\"/openai/v1\")) {\n return undefined;\n }\n return { \"api-version\": settings.azureOpenaiApiVersion };\n}\n\nexport async function createDocumentBase(\n db: Database,\n input: CreateDocumentBaseRequest & { accountId: string; workspaceId: string },\n): Promise<DocumentBase> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [row] = await scopedDb\n .insert(schema.documentBases)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n name: input.name.trim(),\n description: input.description?.trim() || null,\n })\n .returning();\n if (!row) throw new Error(\"Failed to create document base\");\n return mapDocumentBase(row);\n },\n );\n}\n\nexport async function listDocumentBases(\n db: Database,\n workspaceId: string,\n): Promise<DocumentBase[]> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const rows = await scopedDb\n .select()\n .from(schema.documentBases)\n .where(eq(schema.documentBases.workspaceId, workspaceId))\n .orderBy(desc(schema.documentBases.createdAt));\n return rows.map(mapDocumentBase);\n });\n}\n\nexport async function getDocumentBase(\n db: Database,\n workspaceId: string,\n baseId: string,\n): Promise<DocumentBase | null> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documentBases)\n .where(\n and(eq(schema.documentBases.workspaceId, workspaceId), eq(schema.documentBases.id, baseId)),\n )\n .limit(1);\n return row ? mapDocumentBase(row) : null;\n });\n}\n\n/**\n * Find-or-create the workspace's Default base — where knowledge drops land\n * before configured curation files them elsewhere. Matched by name\n * (case-insensitive) so a user-created \"Default\" is adopted rather than\n * duplicated.\n */\nexport async function ensureDefaultBase(\n db: Database,\n input: { accountId: string; workspaceId: string },\n): Promise<DocumentBase> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) =>\n await scopedDb.transaction(async (tx) => {\n const defaultName = sql`lower(btrim(${schema.documentBases.name})) = ${DEFAULT_BASE_NAME.toLowerCase()}`;\n const [existing] = await tx\n .select()\n .from(schema.documentBases)\n .where(and(eq(schema.documentBases.workspaceId, input.workspaceId), defaultName))\n .limit(1);\n if (existing) return mapDocumentBase(existing);\n\n // The partial unique index is the serialization point for concurrent\n // first drops. A losing insert re-reads the winner instead of\n // surfacing a duplicate-base error.\n const [inserted] = await tx\n .insert(schema.documentBases)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n name: DEFAULT_BASE_NAME,\n description: DEFAULT_BASE_DESCRIPTION,\n })\n .onConflictDoNothing()\n .returning();\n if (inserted) return mapDocumentBase(inserted);\n\n const [raced] = await tx\n .select()\n .from(schema.documentBases)\n .where(and(eq(schema.documentBases.workspaceId, input.workspaceId), defaultName))\n .limit(1);\n if (raced) return mapDocumentBase(raced);\n throw new Error(\"Failed to create Default document base\");\n }),\n );\n}\n\nexport async function addDocumentToBase(\n db: Database,\n input: AddDocumentRequest & {\n accountId: string;\n workspaceId: string;\n baseId: string;\n createdBy?: string | null | undefined;\n curationStatus?: DocumentCurationStatus | undefined;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<Document> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);\n if (!base) throw new Error(`Document base not found: ${input.baseId}`);\n const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);\n if (input.visibility === \"private\" && !cleanString(input.createdBy ?? null)) {\n throw new Error(\"private documents require a creating subject\");\n }\n const now = new Date();\n const [existing] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.baseId, input.baseId),\n eq(schema.documents.fileId, input.fileId),\n ),\n )\n .limit(1);\n if (existing) {\n if (!documentMatchesAccess(existing, input.access)) {\n throw new Error(`Document not found: ${existing.id}`);\n }\n // Idempotent re-add: refresh caller-supplied source metadata on the\n // existing row instead of silently discarding it (aclTags especially —\n // a re-add that tightens tags must not be a no-op). Access policy is\n // deliberately inherited: re-adding a known file is not an implicit\n // visibility/agent-policy update that another manager can exploit.\n const [updated] = await scopedDb\n .update(schema.documents)\n .set({\n title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? existing.title,\n ...(input.sourceKind !== undefined ? { sourceKind: input.sourceKind } : {}),\n sourceUri: cleanString(input.sourceUri) ?? existing.sourceUri,\n sourceExternalId: cleanString(input.sourceExternalId) ?? existing.sourceExternalId,\n sourceTitle: cleanString(input.sourceTitle) ?? existing.sourceTitle,\n sourceAuthor: cleanString(input.sourceAuthor) ?? existing.sourceAuthor,\n sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt) ?? existing.sourceCreatedAt,\n sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt) ?? existing.sourceUpdatedAt,\n sourceVersion: cleanString(input.sourceVersion) ?? existing.sourceVersion,\n ...(input.aclTags !== undefined ? { aclTags: cleanStringArray(input.aclTags) } : {}),\n updatedAt: now,\n })\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, existing.id),\n ...documentAccessConditions(input.access),\n ),\n )\n .returning();\n return mapDocument(updated ?? existing);\n }\n const [row] = await scopedDb\n .insert(schema.documents)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n baseId: input.baseId,\n fileId: input.fileId,\n status: \"queued\",\n title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? file.filename,\n parser: DEFAULT_DOCUMENT_PARSER,\n sourceKind: input.sourceKind ?? \"manual_upload\",\n sourceUri: cleanString(input.sourceUri) ?? null,\n sourceExternalId: cleanString(input.sourceExternalId) ?? null,\n sourceTitle: cleanString(input.sourceTitle) ?? null,\n sourceAuthor: cleanString(input.sourceAuthor) ?? null,\n sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),\n sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),\n sourceVersion: cleanString(input.sourceVersion) ?? null,\n aclTags: cleanStringArray(input.aclTags),\n visibility: input.visibility ?? \"workspace\",\n agentAccess: input.agentAccess ?? true,\n createdBy: input.createdBy ?? null,\n curationStatus: input.curationStatus ?? \"none\",\n updatedAt: now,\n })\n .returning();\n if (!row) throw new Error(\"Failed to create document\");\n return mapDocument(row);\n },\n );\n}\n\n/**\n * Move a document and its indexed chunks to another base. With no explicit\n * target, applies the stored curation suggestion. A 'suggested' or 'pending'\n * document that gets moved counts as filed.\n */\nexport async function moveDocumentToBase(\n db: Database,\n input: {\n accountId: string;\n workspaceId: string;\n documentId: string;\n targetBaseId?: string | null | undefined;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<Document> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .limit(1);\n if (!row) throw new Error(`Document not found: ${input.documentId}`);\n const suggestion = (row.curation as { suggestedBaseId?: string | null } | null)\n ?.suggestedBaseId;\n const targetBaseId = input.targetBaseId ?? suggestion;\n if (!targetBaseId) {\n throw new Error(\"document has no suggested base; pass targetBaseId\");\n }\n if (targetBaseId === row.baseId) return mapDocument(row);\n const base = await getDocumentBase(scopedDb, input.workspaceId, targetBaseId);\n if (!base) throw new Error(`Document base not found: ${targetBaseId}`);\n const [conflict] = await scopedDb\n .select({ id: schema.documents.id })\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.baseId, targetBaseId),\n eq(schema.documents.fileId, row.fileId),\n ),\n )\n .limit(1);\n if (conflict) {\n throw new Error(\"a document for this file already exists in the target base\");\n }\n const now = new Date();\n const moved = await scopedDb.transaction(async (tx) => {\n const [updated] = await tx\n .update(schema.documents)\n .set({\n baseId: targetBaseId,\n ...(row.curationStatus === \"suggested\" || row.curationStatus === \"pending\"\n ? { curationStatus: \"auto_filed\" }\n : {}),\n updatedAt: now,\n })\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .returning();\n if (updated) {\n await tx\n .update(schema.documentChunks)\n .set({ baseId: targetBaseId })\n .where(\n and(\n eq(schema.documentChunks.workspaceId, input.workspaceId),\n eq(schema.documentChunks.documentId, input.documentId),\n ),\n );\n }\n return updated;\n });\n if (!moved) throw new Error(`Document not found: ${input.documentId}`);\n return mapDocument(moved);\n },\n );\n}\n\nexport async function deleteDocumentFromBase(\n db: Database,\n input: {\n accountId: string;\n workspaceId: string;\n baseId: string;\n documentId: string;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<void> {\n await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [document] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .limit(1);\n if (!document) {\n throw new Error(`Document not found: ${input.documentId}`);\n }\n if (document.baseId !== input.baseId) {\n throw new Error(`Document not found: ${input.documentId}`);\n }\n await scopedDb\n .delete(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n );\n },\n );\n}\n\nexport async function listDocuments(\n db: Database,\n workspaceId: string,\n baseId: string,\n access?: DocumentAccessFilter,\n): Promise<Document[]> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const rows = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.baseId, baseId),\n ...documentAccessConditions(access),\n ),\n )\n .orderBy(asc(schema.documents.createdAt));\n return rows.map(mapDocument);\n });\n}\n\nexport async function getDocument(\n db: Database,\n workspaceId: string,\n documentId: string,\n access?: DocumentAccessFilter,\n): Promise<Document | null> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ...documentAccessConditions(access),\n ),\n )\n .limit(1);\n return row ? mapDocument(row) : null;\n });\n}\n\nexport async function queueDocumentForReindex(\n db: Database,\n workspaceId: string,\n documentId: string,\n access?: DocumentAccessFilter,\n): Promise<Document> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .update(schema.documents)\n .set({\n status: \"queued\",\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ...documentAccessConditions(access),\n ),\n )\n .returning();\n if (!row) throw new Error(`Document not found: ${documentId}`);\n return mapDocument(row);\n });\n}\n\nexport async function indexDocumentNow(\n db: Database,\n objectStorage: ObjectStorage,\n workspaceId: string,\n documentId: string,\n services: DocumentServices = createDocumentServices(),\n hooks: DocumentIndexHooks = {},\n): Promise<Document> {\n const [loadedDocument] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n )\n .limit(1),\n );\n if (!loadedDocument) throw new Error(`Document not found: ${documentId}`);\n let document: DocumentRow = loadedDocument;\n const file = await requireReadyFile(db, workspaceId, document.fileId);\n await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n await scopedDb\n .update(schema.documents)\n .set({\n status: \"indexing\",\n parser: services.parser.name,\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n );\n });\n try {\n const bytes = await objectStorage.getFileBytes(file);\n const parsed = await services.parser.parse(bytes, file);\n // Knowledge drops (curationStatus 'pending') are curated between parse and\n // chunking when a provider is enabled, so chunk metadata and base placement\n // reflect the curated truth. Disabled curation leaves caller metadata intact;\n // enabled-provider failures remain fail-soft through the heuristic fallback.\n if (document.curationStatus === \"pending\") {\n document = await curateDroppedDocument(db, services, document, parsed, file);\n }\n const chunks = services.chunker.chunk(parsed, file);\n await hooks.beforeEmbed?.({\n accountId: document.accountId,\n workspaceId: document.workspaceId,\n documentId,\n chunkCount: chunks.length,\n });\n const embeddings = await services.embedder.embedMany(chunks.map((chunk) => chunk.text));\n if (embeddings.length !== chunks.length) {\n throw new Error(\n `Embedding provider returned ${embeddings.length} embeddings for ${chunks.length} chunks`,\n );\n }\n await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb.transaction(async (tx) => {\n await tx\n .delete(schema.documentChunks)\n .where(\n and(\n eq(schema.documentChunks.workspaceId, workspaceId),\n eq(schema.documentChunks.documentId, documentId),\n ),\n );\n if (chunks.length > 0) {\n await tx.insert(schema.documentChunks).values(\n chunks.map((chunk, index) => ({\n accountId: document.accountId,\n workspaceId: document.workspaceId,\n documentId,\n baseId: document.baseId,\n fileId: file.id,\n chunkIndex: index,\n text: chunk.text,\n metadata: {\n ...chunk.metadata,\n documentTitle: document.title,\n sourceKind: document.sourceKind,\n sourceUri: document.sourceUri,\n sourceExternalId: document.sourceExternalId,\n sourceTitle: document.sourceTitle,\n sourceAuthor: document.sourceAuthor,\n sourceCreatedAt: document.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: document.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: document.sourceVersion,\n aclTags: document.aclTags,\n },\n embedding: validateEmbedding(\n embeddings[index] ?? [],\n services.embedder.dimensions,\n services.embedder.model,\n ),\n embeddingModel: services.embedder.model,\n })),\n );\n }\n await tx\n .update(schema.documents)\n .set({\n status: \"ready\",\n parser: services.parser.name,\n chunkCount: chunks.length,\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ),\n );\n }),\n );\n } catch (error) {\n const [failed] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n status: \"failed\",\n error: error instanceof Error ? error.message : String(error),\n updatedAt: new Date(),\n })\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n )\n .returning(),\n );\n if (!failed) throw error;\n return mapDocument(failed);\n }\n // Internal indexing must be able to return a private document to the caller\n // that created/queued it. Public reads remain fail-closed when no subject is\n // supplied; the creator subject is the document's frozen access principal.\n const updated = await getDocument(db, workspaceId, documentId, {\n viewerSubjectId: document.createdBy,\n });\n if (!updated) throw new Error(`Document disappeared after indexing: ${documentId}`);\n return updated;\n}\n\ntype DocumentRow = typeof schema.documents.$inferSelect;\n\nasync function curateDroppedDocument(\n db: Database,\n services: DocumentServices,\n document: DocumentRow,\n parsed: ParsedDocument,\n file: FileAsset,\n): Promise<DocumentRow> {\n // `none` is an explicit disabled-curation policy. Do not silently replace\n // it with heuristics: indexing still proceeds, but the drop remains an\n // ordinary uncured document with its caller-supplied title and metadata.\n if (!services.curator) {\n const [updated] = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n curationStatus: \"none\",\n summary: null,\n topics: [],\n curation: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.id, document.id),\n ),\n )\n .returning(),\n );\n return updated ?? document;\n }\n const bases = await listDocumentBases(db, document.workspaceId);\n const candidates: DocumentCurationCandidateBase[] = bases\n .filter((base) => base.id !== document.baseId)\n .map((base) => ({ id: base.id, name: base.name, description: base.description }));\n const input: DocumentCurationInput = {\n text: parsed.text.slice(0, DOCUMENT_CURATION_MAX_INPUT_CHARS),\n filename: file.filename,\n title: document.title,\n bases: candidates,\n };\n let outcome: DocumentCurationOutcome;\n let model: string;\n let failure: string | null = null;\n try {\n outcome = await services.curator.curate(input);\n model = services.curator.model;\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n console.warn(\"document curation failed; applying heuristic fallback\", {\n workspaceId: document.workspaceId,\n documentId: document.id,\n error: failure,\n });\n outcome = heuristicCuration(input, file.contentType);\n model = \"heuristic\";\n }\n const suggestedBase = candidates.find((base) => base.id === outcome.targetBaseId) ?? null;\n let moveToBaseId: string | null = null;\n if (\n suggestedBase &&\n failure === null &&\n outcome.confidence >= DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE\n ) {\n // The (workspace, base, file) unique index means a same-file twin already\n // in the target base blocks the move; keep it as a suggestion instead.\n const conflict = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({ id: schema.documents.id })\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.baseId, suggestedBase.id),\n eq(schema.documents.fileId, document.fileId),\n ),\n )\n .limit(1),\n );\n if (conflict.length === 0) {\n moveToBaseId = suggestedBase.id;\n }\n }\n const curation: DocumentCuration = {\n suggestedBaseId: suggestedBase?.id ?? null,\n suggestedBaseName: suggestedBase?.name ?? null,\n confidence: outcome.confidence,\n reason: failure ? `curation failed (${failure}); heuristic fallback applied` : outcome.reason,\n originalTitle: document.title,\n model,\n };\n const curationStatus: DocumentCurationStatus = failure\n ? \"failed\"\n : moveToBaseId\n ? \"auto_filed\"\n : \"suggested\";\n const [updated] = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n title: outcome.title ?? document.title,\n summary: outcome.summary,\n topics: outcome.topics,\n ...(outcome.sourceKind ? { sourceKind: outcome.sourceKind } : {}),\n ...(moveToBaseId ? { baseId: moveToBaseId } : {}),\n curationStatus,\n curation,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.id, document.id),\n ),\n )\n .returning(),\n );\n return updated ?? document;\n}\n\nexport async function searchDocuments(\n db: Database,\n input: DocumentSearchInput,\n services: Pick<DocumentServices, \"embedder\"> = createDocumentServices(),\n): Promise<DocumentSearchResult[]> {\n const mode = input.mode ?? \"hybrid\";\n const limit = Math.min(Math.max(input.limit ?? 5, 1), 50);\n const candidateLimit = mode === \"hybrid\" ? Math.min(limit * 4, 100) : limit;\n const rows: CombinedSearchRow[] = [];\n if (mode === \"vector\" || mode === \"hybrid\") {\n try {\n rows.push(...(await vectorSearchDocuments(db, input, candidateLimit, services)));\n } catch (error) {\n if (mode === \"vector\") {\n throw error;\n }\n console.warn(\n \"document hybrid search vector component failed; falling back to keyword search\",\n {\n workspaceId: input.workspaceId,\n error: error instanceof Error ? error.message : String(error),\n },\n );\n }\n }\n if (mode === \"keyword\" || mode === \"hybrid\") {\n rows.push(...(await keywordSearchDocuments(db, input, candidateLimit)));\n }\n return mergeDocumentSearchRows(rows, mode).slice(0, limit);\n}\n\nasync function vectorSearchDocuments(\n db: Database,\n input: DocumentSearchInput,\n limit: number,\n services: Pick<DocumentServices, \"embedder\">,\n): Promise<CombinedSearchRow[]> {\n const queryEmbedding = await services.embedder.embedQuery(input.query);\n validateEmbedding(queryEmbedding, services.embedder.dimensions, services.embedder.model);\n const distance = sql<number>`${schema.documentChunks.embedding} <=> ${vectorLiteral(queryEmbedding)}::vector`;\n const rows = await withWorkspaceRls(\n db,\n input.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n distance,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(and(...documentSearchConditions(input, services.embedder.model)))\n .orderBy(distance)\n .limit(limit),\n );\n return rows.map((row) => ({\n ...mapSearchRowBase(row, input.workspaceId),\n vectorScore: 1 / (1 + Number(row.distance)),\n keywordScore: null,\n }));\n}\n\nasync function keywordSearchDocuments(\n db: Database,\n input: DocumentSearchInput,\n limit: number,\n): Promise<CombinedSearchRow[]> {\n const rank = sql<number>`ts_rank_cd(to_tsvector('simple', ${schema.documentChunks.text}), plainto_tsquery('simple', ${input.query}))`;\n const rows = await withWorkspaceRls(\n db,\n input.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n rank,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(\n and(\n ...documentSearchConditions(input),\n sql`to_tsvector('simple', ${schema.documentChunks.text}) @@ plainto_tsquery('simple', ${input.query})`,\n ),\n )\n .orderBy(desc(rank))\n .limit(limit),\n );\n return rows.map((row) => ({\n ...mapSearchRowBase(row, input.workspaceId),\n vectorScore: null,\n keywordScore: normalizeKeywordScore(Number(row.rank)),\n }));\n}\n\nexport async function getDocumentChunk(\n db: Database,\n workspaceId: string,\n chunkId: string,\n access?: DocumentAccessFilter,\n): Promise<DocumentSearchResult | null> {\n const [row] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(\n and(\n eq(schema.documentChunks.workspaceId, workspaceId),\n eq(schema.documentChunks.id, chunkId),\n eq(schema.documents.status, \"ready\"),\n ...documentAccessConditions(access),\n ),\n )\n .limit(1),\n );\n if (!row) return null;\n return {\n ...mapSearchRowBase(row, workspaceId),\n score: 1,\n matchType: \"hybrid\",\n vectorScore: null,\n keywordScore: null,\n };\n}\n\ntype SearchRowBase = Omit<\n DocumentSearchResult,\n \"score\" | \"matchType\" | \"vectorScore\" | \"keywordScore\"\n>;\ntype CombinedSearchRow = SearchRowBase & {\n vectorScore: number | null;\n keywordScore: number | null;\n};\n\n/**\n * Visibility/agent scoping shared by every document read path. Fail-closed:\n * with no filter supplied, private documents are invisible.\n */\nfunction documentAccessConditions(access: DocumentAccessFilter | undefined): SQL[] {\n if (access?.agentOnly) {\n const viewer = access.viewerSubjectId;\n const visibility = viewer\n ? or(eq(schema.documents.visibility, \"workspace\"), eq(schema.documents.createdBy, viewer))\n : eq(schema.documents.visibility, \"workspace\");\n return [eq(schema.documents.agentAccess, true), ...(visibility ? [visibility] : [])];\n }\n const viewer = access?.viewerSubjectId;\n if (viewer) {\n const condition = or(\n eq(schema.documents.visibility, \"workspace\"),\n eq(schema.documents.createdBy, viewer),\n );\n return condition ? [condition] : [];\n }\n return [eq(schema.documents.visibility, \"workspace\")];\n}\n\nfunction documentMatchesAccess(\n document: Pick<DocumentAccessRecord, \"visibility\" | \"createdBy\" | \"agentAccess\">,\n access: DocumentAccessFilter | undefined,\n): boolean {\n if (access?.agentOnly) {\n return (\n document.agentAccess &&\n (document.visibility !== \"private\" ||\n (!!access.viewerSubjectId && document.createdBy === access.viewerSubjectId))\n );\n }\n return canViewDocument(document, access?.viewerSubjectId);\n}\n\n/** Whether a single already-fetched document is readable by this human viewer. */\nexport function canViewDocument(\n document: Pick<DocumentAccessRecord, \"visibility\" | \"createdBy\">,\n viewerSubjectId: string | null | undefined,\n): boolean {\n return (\n document.visibility !== \"private\" ||\n (!!viewerSubjectId && document.createdBy === viewerSubjectId)\n );\n}\n\ntype DocumentAccessRecord = {\n visibility: string;\n createdBy: string | null;\n agentAccess: boolean;\n};\n\nfunction documentSearchConditions(input: DocumentSearchInput, embeddingModel?: string): SQL[] {\n const conditions: SQL[] = [\n eq(schema.documents.status, \"ready\"),\n eq(schema.documentChunks.workspaceId, input.workspaceId),\n ...documentAccessConditions(input.access),\n ];\n if (embeddingModel) {\n conditions.push(eq(schema.documentChunks.embeddingModel, embeddingModel));\n }\n if (input.baseIds?.length) {\n conditions.push(inArray(schema.documentChunks.baseId, input.baseIds));\n }\n if (input.sourceKinds?.length) {\n conditions.push(inArray(schema.documents.sourceKind, input.sourceKinds));\n }\n const aclTags = cleanStringArray(input.aclTags);\n if (aclTags.length > 0) {\n conditions.push(sql`${schema.documents.aclTags} @> ${JSON.stringify(aclTags)}::jsonb`);\n }\n return conditions;\n}\n\nfunction mapSearchRowBase(\n row: {\n chunkId: string;\n documentId: string;\n baseId: string;\n fileId: string;\n title: string;\n text: string;\n chunkIndex: number;\n metadata: Record<string, unknown>;\n sourceKind: string;\n sourceUri: string | null;\n sourceExternalId: string | null;\n sourceTitle: string | null;\n sourceAuthor: string | null;\n sourceCreatedAt: Date | null;\n sourceUpdatedAt: Date | null;\n sourceVersion: string | null;\n aclTags: string[];\n },\n workspaceId: string,\n): SearchRowBase {\n return {\n chunkId: row.chunkId,\n workspaceId,\n documentId: row.documentId,\n baseId: row.baseId,\n fileId: row.fileId,\n title: row.title,\n text: row.text,\n chunkIndex: row.chunkIndex,\n metadata: row.metadata,\n sourceKind: normalizeKnowledgeSourceKind(row.sourceKind),\n sourceUri: row.sourceUri,\n sourceExternalId: row.sourceExternalId,\n sourceTitle: row.sourceTitle,\n sourceAuthor: row.sourceAuthor,\n sourceCreatedAt: row.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: row.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: row.sourceVersion,\n aclTags: cleanStringArray(row.aclTags),\n };\n}\n\nfunction mergeDocumentSearchRows(\n rows: CombinedSearchRow[],\n mode: DocumentSearchMode,\n): DocumentSearchResult[] {\n const byChunk = new Map<string, CombinedSearchRow>();\n for (const row of rows) {\n const existing = byChunk.get(row.chunkId);\n if (!existing) {\n byChunk.set(row.chunkId, row);\n continue;\n }\n byChunk.set(row.chunkId, {\n ...existing,\n vectorScore: Math.max(existing.vectorScore ?? 0, row.vectorScore ?? 0) || null,\n keywordScore: Math.max(existing.keywordScore ?? 0, row.keywordScore ?? 0) || null,\n });\n }\n return [...byChunk.values()]\n .map((row) => {\n const vectorScore = row.vectorScore;\n const keywordScore = row.keywordScore;\n const matchType: DocumentSearchMode =\n vectorScore !== null && keywordScore !== null\n ? \"hybrid\"\n : vectorScore !== null\n ? \"vector\"\n : \"keyword\";\n return {\n ...row,\n score: combinedSearchScore(mode, vectorScore, keywordScore, matchType),\n matchType,\n };\n })\n .sort(\n (left, right) =>\n right.score - left.score ||\n (right.vectorScore ?? 0) - (left.vectorScore ?? 0) ||\n (right.keywordScore ?? 0) - (left.keywordScore ?? 0) ||\n left.chunkIndex - right.chunkIndex,\n );\n}\n\nfunction combinedSearchScore(\n mode: DocumentSearchMode,\n vectorScore: number | null,\n keywordScore: number | null,\n matchType: DocumentSearchMode,\n): number {\n const vector = vectorScore ?? 0;\n const keyword = keywordScore ?? 0;\n if (mode === \"vector\") return roundScore(vector);\n if (mode === \"keyword\") return roundScore(keyword);\n return roundScore(\n Math.min(1, 0.65 * vector + 0.35 * keyword + (matchType === \"hybrid\" ? 0.1 : 0)),\n );\n}\n\nfunction normalizeKeywordScore(rank: number): number {\n if (!Number.isFinite(rank) || rank <= 0) {\n return 0;\n }\n return rank / (rank + 1);\n}\n\nfunction roundScore(value: number): number {\n return Number(value.toFixed(6));\n}\n\nexport async function parseDocumentBytes(\n bytes: Uint8Array,\n file: FileAsset,\n parser: DocumentParser = new LiteParseDocumentParser(),\n): Promise<ParsedDocument> {\n return await parser.parse(bytes, file);\n}\n\nexport function chunkText(\n text: string,\n maxChars = DEFAULT_DOCUMENT_CHUNK_SIZE,\n overlapChars = DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n): string[] {\n if (overlapChars >= maxChars) {\n throw new Error(\"chunk overlap must be smaller than chunk size\");\n }\n const normalized = text\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+/g, \" \")\n .trim();\n if (!normalized) return [];\n const paragraphs = normalized\n .split(/\\n{2,}/)\n .map((part) => part.replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n const chunks: string[] = [];\n let current = \"\";\n for (const paragraph of paragraphs.length > 0 ? paragraphs : [normalized.replace(/\\s+/g, \" \")]) {\n for (const part of splitOversizedText(paragraph, maxChars)) {\n if (!current) {\n current = part;\n } else if (current.length + 1 + part.length <= maxChars) {\n current = `${current} ${part}`;\n } else {\n chunks.push(current);\n current = withOverlap(current, overlapChars, part, maxChars);\n }\n }\n }\n if (current) chunks.push(current);\n return chunks.map((chunk) => chunk.trim()).filter(Boolean);\n}\n\nexport function deterministicEmbedding(\n text: string,\n dimensions = DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS,\n): number[] {\n const values = new Array(dimensions).fill(0);\n const tokens = text.toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [];\n for (const token of tokens) {\n let hash = 2166136261;\n for (const char of token) {\n hash ^= char.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n values[Math.abs(hash) % dimensions] += 1;\n }\n const norm = Math.hypot(...values) || 1;\n return values.map((value) => Number((value / norm).toFixed(6)));\n}\n\nasync function requireReadyFile(\n db: Database,\n workspaceId: string,\n fileId: string,\n): Promise<FileAsset> {\n const file = await requireFile(db, workspaceId, fileId);\n if (file.status !== \"ready\") {\n throw new Error(`File ${fileId} is ${file.status}`);\n }\n return file;\n}\n\nfunction validateEmbedding(values: number[], dimensions: number, model: string): number[] {\n if (values.length !== dimensions) {\n throw new Error(\n `Embedding model ${model} returned ${values.length} dimensions; expected ${dimensions}`,\n );\n }\n if (values.some((value) => !Number.isFinite(value))) {\n throw new Error(`Embedding model ${model} returned non-finite values`);\n }\n return values;\n}\n\nfunction isTextLike(file: FileAsset): boolean {\n const contentType = file.contentType.toLowerCase();\n const filename = file.filename.toLowerCase();\n return (\n contentType.startsWith(\"text/\") ||\n contentType === \"application/json\" ||\n contentType === \"application/xml\" ||\n contentType === \"application/x-yaml\" ||\n filename.endsWith(\".md\") ||\n filename.endsWith(\".markdown\") ||\n filename.endsWith(\".json\") ||\n filename.endsWith(\".yaml\") ||\n filename.endsWith(\".yml\") ||\n filename.endsWith(\".csv\") ||\n filename.endsWith(\".tsv\") ||\n filename.endsWith(\".xml\")\n );\n}\n\nfunction splitOversizedText(text: string, maxChars: number): string[] {\n if (text.length <= maxChars) return [text];\n const out: string[] = [];\n let remaining = text;\n while (remaining.length > maxChars) {\n const window = remaining.slice(0, maxChars + 1);\n const breakAt = Math.max(\n window.lastIndexOf(\". \"),\n window.lastIndexOf(\"? \"),\n window.lastIndexOf(\"! \"),\n window.lastIndexOf(\"; \"),\n window.lastIndexOf(\", \"),\n window.lastIndexOf(\" \"),\n );\n const end = breakAt > Math.floor(maxChars * 0.5) ? breakAt + 1 : maxChars;\n out.push(remaining.slice(0, end).trim());\n remaining = remaining.slice(end).trim();\n }\n if (remaining) out.push(remaining);\n return out;\n}\n\nfunction withOverlap(\n previous: string,\n overlapChars: number,\n next: string,\n maxChars: number,\n): string {\n if (overlapChars <= 0) return next;\n const overlap = previous\n .slice(Math.max(0, previous.length - overlapChars))\n .replace(/^\\S+\\s+/, \"\")\n .trim();\n const candidate = overlap ? `${overlap} ${next}` : next;\n return candidate.length <= maxChars ? candidate : next;\n}\n\nfunction cleanString(value: string | undefined | null): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction cleanStringArray(values: string[] | undefined | null): string[] {\n return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];\n}\n\nfunction parseOptionalDate(value: string | undefined): Date | null {\n const trimmed = cleanString(value);\n if (!trimmed) {\n return null;\n }\n const date = new Date(trimmed);\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Invalid source timestamp: ${trimmed}`);\n }\n return date;\n}\n\nfunction normalizeKnowledgeSourceKind(value: string): KnowledgeSourceKind {\n switch (value) {\n case \"manual_upload\":\n case \"meeting_transcript\":\n case \"repository\":\n case \"email\":\n case \"chat\":\n case \"document\":\n case \"web\":\n case \"other\":\n return value;\n default:\n return \"other\";\n }\n}\n\nfunction vectorLiteral(values: number[]): string {\n return `[${values.join(\",\")}]`;\n}\n\nfunction mapDocumentBase(row: typeof schema.documentBases.$inferSelect): DocumentBase {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n name: row.name,\n description: row.description,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction mapDocument(row: typeof schema.documents.$inferSelect): Document {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n baseId: row.baseId,\n fileId: row.fileId,\n status: row.status as Document[\"status\"],\n title: row.title,\n parser: row.parser,\n chunkCount: row.chunkCount,\n error: row.error,\n sourceKind: normalizeKnowledgeSourceKind(row.sourceKind),\n sourceUri: row.sourceUri,\n sourceExternalId: row.sourceExternalId,\n sourceTitle: row.sourceTitle,\n sourceAuthor: row.sourceAuthor,\n sourceCreatedAt: row.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: row.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: row.sourceVersion,\n aclTags: cleanStringArray(row.aclTags),\n visibility: normalizeDocumentVisibility(row.visibility),\n createdBy: row.createdBy,\n agentAccess: row.agentAccess,\n summary: row.summary,\n topics: cleanStringArray(row.topics),\n curationStatus: normalizeDocumentCurationStatus(row.curationStatus),\n curation: (row.curation as DocumentCuration | null) ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction normalizeDocumentVisibility(value: string): DocumentVisibility {\n return value === \"private\" ? \"private\" : \"workspace\";\n}\n\nfunction normalizeDocumentCurationStatus(value: string): DocumentCurationStatus {\n switch (value) {\n case \"pending\":\n case \"suggested\":\n case \"auto_filed\":\n case \"failed\":\n return value;\n default:\n return \"none\";\n }\n}\n"],"mappings":"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","names":["updated","viewer"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport type {\n AddDocumentRequest,\n CreateDocumentBaseRequest,\n Document,\n DocumentBase,\n DocumentCuration,\n DocumentCurationStatus,\n DocumentSearchMode,\n DocumentSearchResult,\n DocumentStatus,\n DocumentVisibility,\n FileAsset,\n KnowledgeSourceKind,\n} from \"@opengeni/contracts\";\nimport { requireFile, withRlsContext, withWorkspaceRls, type Database } from \"@opengeni/db\";\nimport * as schema from \"@opengeni/db/schema\";\nimport type { ObjectStorage } from \"@opengeni/storage\";\nimport { LiteParse } from \"@llamaindex/liteparse\";\nimport { and, asc, desc, eq, inArray, or, sql, type SQL } from \"drizzle-orm\";\nimport OpenAI from \"openai\";\n\nexport const DEFAULT_DOCUMENT_PARSER = \"liteparse\";\nexport const DEFAULT_DOCUMENT_EMBEDDING_MODEL = \"text-embedding-3-large\";\nexport const DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS = 3072;\nexport const DEFAULT_DOCUMENT_CHUNK_SIZE = 1200;\nexport const DEFAULT_DOCUMENT_CHUNK_OVERLAP = 160;\nexport const DEFAULT_DOCUMENT_CURATION_MODEL = \"gpt-4o-mini\";\n// Curator input is a preview, not the whole document — enough to name and\n// classify without paying for a full-document prompt on every drop.\nexport const DOCUMENT_CURATION_MAX_INPUT_CHARS = 24_000;\n// A base move is applied automatically only at or above this curator\n// confidence; below it the suggestion is surfaced for human review instead.\nexport const DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;\n// The per-workspace default base: where knowledge drops land (created on\n// first drop) and stay unless configured curation files them into a topical base.\nexport const DEFAULT_BASE_NAME = \"Default\";\nexport const DEFAULT_BASE_DESCRIPTION = \"Default base for dropped files and notes.\";\n\nexport type ParsedDocument = {\n text: string;\n metadata?: Record<string, unknown>;\n};\n\nexport type DocumentChunk = {\n text: string;\n metadata: Record<string, unknown>;\n};\n\nexport type DocumentParser = {\n name: string;\n parse: (bytes: Uint8Array, file: FileAsset) => Promise<ParsedDocument>;\n};\n\nexport type DocumentChunker = {\n chunk: (parsed: ParsedDocument, file: FileAsset) => DocumentChunk[];\n};\n\nexport type DocumentEmbedder = {\n model: string;\n dimensions: number;\n embedMany: (texts: string[]) => Promise<number[][]>;\n embedQuery: (text: string) => Promise<number[]>;\n};\n\nexport type DocumentCurationCandidateBase = {\n id: string;\n name: string;\n description: string | null;\n};\n\nexport type DocumentCurationInput = {\n /** Parsed document text, clipped to DOCUMENT_CURATION_MAX_INPUT_CHARS. */\n text: string;\n filename: string;\n /** Current (usually filename-derived) title. */\n title: string;\n /** Candidate bases the document could be filed into (never its current base). */\n bases: DocumentCurationCandidateBase[];\n};\n\nexport type DocumentCurationOutcome = {\n title: string | null;\n summary: string | null;\n sourceKind: KnowledgeSourceKind | null;\n topics: string[];\n targetBaseId: string | null;\n confidence: number;\n reason: string | null;\n};\n\nexport type DocumentCurator = {\n model: string;\n curate: (input: DocumentCurationInput) => Promise<DocumentCurationOutcome>;\n};\n\nexport type DocumentServices = {\n parser: DocumentParser;\n chunker: DocumentChunker;\n embedder: DocumentEmbedder;\n /** Optional: names/summarizes/categorizes dropped documents during indexing. */\n curator?: DocumentCurator | undefined;\n};\n\n/**\n * Read-scoping for document queries. Fail-closed: when a caller supplies no\n * filter, private documents are invisible (only their creator may see them,\n * and only by passing their subject id).\n */\nexport type DocumentAccessFilter = {\n /** Grant subject id of the human viewer; null/undefined hides private docs. */\n viewerSubjectId?: string | null | undefined;\n /**\n * Agent retrieval surface: only agent-enabled documents. Workspace-visible\n * documents are available to every agent; private documents are available\n * only when the agent carries the creating subject as its viewer subject.\n */\n agentOnly?: boolean | undefined;\n};\n\nexport type DocumentInventoryStatusCounts = Record<DocumentStatus, number>;\nexport type DocumentInventorySourceKindCounts = Record<KnowledgeSourceKind, number>;\n\nexport type DocumentInventory = {\n baseCount: number;\n bases: Array<{\n id: string;\n name: string;\n visibleDocumentCount: number;\n statusCounts: DocumentInventoryStatusCounts;\n latestUpdatedAt: string | null;\n }>;\n visibleDocumentCount: number;\n statusCounts: DocumentInventoryStatusCounts;\n sourceKindCounts: DocumentInventorySourceKindCounts;\n latestUpdatedAt: string | null;\n topics: Array<{ name: string; documentCount: number }>;\n topicsTruncated: boolean;\n};\n\nexport type DocumentInventoryInput = {\n baseLimit: number;\n topicLimit: number;\n topicMaxChars: number;\n access?: DocumentAccessFilter | undefined;\n};\n\nconst DOCUMENT_INVENTORY_MAX_BASE_LIMIT = 100;\nconst DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT = 100;\nconst DOCUMENT_INVENTORY_MAX_TOPIC_CHARS = 256;\n\nexport type DocumentSearchInput = {\n workspaceId: string;\n query: string;\n baseIds?: string[] | undefined;\n limit?: number | undefined;\n mode?: DocumentSearchMode | undefined;\n sourceKinds?: KnowledgeSourceKind[] | undefined;\n aclTags?: string[] | undefined;\n access?: DocumentAccessFilter | undefined;\n};\n\nexport type DocumentIndexHooks = {\n beforeEmbed?: (input: {\n accountId: string;\n workspaceId: string;\n documentId: string;\n chunkCount: number;\n }) => Promise<void>;\n};\n\nexport class LiteParseDocumentParser implements DocumentParser {\n readonly name = DEFAULT_DOCUMENT_PARSER;\n private parseQueue: Promise<void> = Promise.resolve();\n\n async parse(bytes: Uint8Array, file: FileAsset): Promise<ParsedDocument> {\n const text = isTextLike(file)\n ? Buffer.from(bytes).toString(\"utf8\").replace(/\\0/g, \" \").trim()\n : await this.parseWithLiteParse(bytes);\n if (!text.trim()) {\n throw new Error(`Parsed document is empty: ${file.filename}`);\n }\n return {\n text: text.trim(),\n metadata: {\n parser: this.name,\n filename: file.filename,\n contentType: file.contentType,\n },\n };\n }\n\n private async parseWithLiteParse(bytes: Uint8Array): Promise<string> {\n return await this.enqueueParse(async () => {\n const parser = new LiteParse({ ocrEnabled: true, numWorkers: 1 });\n const result = await parser.parse(Buffer.from(bytes), true);\n const text = typeof result?.text === \"string\" ? result.text : \"\";\n return text.replace(/\\0/g, \" \").trim();\n });\n }\n\n private async enqueueParse<T>(task: () => Promise<T>): Promise<T> {\n const previous = this.parseQueue;\n let release: () => void = () => undefined;\n this.parseQueue = new Promise<void>((resolve) => {\n release = resolve;\n });\n await previous.catch(() => undefined);\n try {\n return await task();\n } finally {\n release();\n }\n }\n}\n\nexport class RecursiveTextChunker implements DocumentChunker {\n constructor(\n private readonly maxChars = DEFAULT_DOCUMENT_CHUNK_SIZE,\n private readonly overlapChars = DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n ) {\n if (overlapChars >= maxChars) {\n throw new Error(\"document chunk overlap must be smaller than chunk size\");\n }\n }\n\n chunk(parsed: ParsedDocument, file: FileAsset): DocumentChunk[] {\n return chunkText(parsed.text, this.maxChars, this.overlapChars).map((text, index) => ({\n text,\n metadata: {\n ...parsed.metadata,\n filename: file.filename,\n contentType: file.contentType,\n chunkIndex: index,\n },\n }));\n }\n}\n\nexport class OpenAIEmbeddingProvider implements DocumentEmbedder {\n private client: OpenAI | null = null;\n private readonly apiKey: string | undefined;\n private readonly baseURL: string | undefined;\n\n constructor(args: {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n model?: string | undefined;\n dimensions?: number | undefined;\n }) {\n this.apiKey = args.apiKey ?? process.env.OPENAI_API_KEY;\n this.baseURL = args.baseURL;\n this.defaultHeaders = args.defaultHeaders;\n this.defaultQuery = args.defaultQuery;\n this.model = args.model ?? DEFAULT_DOCUMENT_EMBEDDING_MODEL;\n this.dimensions = args.dimensions ?? DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS;\n }\n\n readonly model: string;\n readonly dimensions: number;\n private readonly defaultHeaders: Record<string, string> | undefined;\n private readonly defaultQuery: Record<string, string> | undefined;\n\n async embedMany(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const out: number[][] = [];\n for (let start = 0; start < texts.length; start += 64) {\n const batch = texts.slice(start, start + 64);\n const response = await this.openai().embeddings.create({\n model: this.model,\n input: batch,\n dimensions: this.dimensions,\n });\n for (const item of response.data) {\n out.push(validateEmbedding(item.embedding, this.dimensions, this.model));\n }\n }\n return out;\n }\n\n async embedQuery(text: string): Promise<number[]> {\n const [embedding] = await this.embedMany([text]);\n if (!embedding) {\n throw new Error(\"Embedding provider returned no query embedding\");\n }\n return embedding;\n }\n\n private openai(): OpenAI {\n if (!this.apiKey) {\n throw new Error(\"OpenAI document embeddings require an API key\");\n }\n this.client ??= new OpenAI({\n apiKey: this.apiKey,\n ...(this.baseURL ? { baseURL: this.baseURL } : {}),\n ...(this.defaultQuery ? { defaultQuery: this.defaultQuery } : {}),\n ...(this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}),\n });\n return this.client;\n }\n}\n\nexport class DeterministicEmbeddingProvider implements DocumentEmbedder {\n constructor(\n readonly dimensions = DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS,\n readonly model = `deterministic-local-${dimensions}`,\n ) {}\n\n async embedMany(texts: string[]): Promise<number[][]> {\n return texts.map((text) => deterministicEmbedding(text, this.dimensions));\n }\n\n async embedQuery(text: string): Promise<number[]> {\n return deterministicEmbedding(text, this.dimensions);\n }\n}\n\n/**\n * Deterministic no-network curation: first meaningful line becomes the title,\n * the opening text becomes the summary, and the kind is guessed from the\n * filename/content type. Never proposes a base move (confidence 0). Used as\n * the `heuristic` provider and as the in-pipeline fallback when the LLM\n * curator fails — a drop must always end up named and summarized.\n */\nexport function heuristicCuration(\n input: DocumentCurationInput,\n contentType = \"application/octet-stream\",\n): DocumentCurationOutcome {\n const firstLine = input.text\n .split(\"\\n\")\n .map((line) => line.replace(/^[#>\\s*-]+/, \"\").trim())\n .find((line) => line.length >= 3);\n const title = (firstLine ?? input.title).slice(0, 120).trim() || input.title;\n const summaryWindow = input.text.replace(/\\s+/g, \" \").trim().slice(0, 360);\n const summary =\n summaryWindow.length === 360 ? `${summaryWindow.slice(0, 357)}...` : summaryWindow;\n return {\n title,\n summary: summary || null,\n sourceKind: heuristicSourceKind(input.filename, contentType),\n topics: [],\n targetBaseId: null,\n confidence: 0,\n reason: null,\n };\n}\n\nfunction heuristicSourceKind(rawFilename: string, rawContentType: string): KnowledgeSourceKind {\n const filename = rawFilename.toLowerCase();\n const contentType = rawContentType.toLowerCase();\n if (filename.endsWith(\".eml\") || contentType === \"message/rfc822\") return \"email\";\n if (filename.endsWith(\".vtt\") || filename.endsWith(\".srt\") || filename.includes(\"transcript\")) {\n return \"meeting_transcript\";\n }\n if (contentType === \"text/html\") return \"web\";\n if (contentType === \"application/pdf\" || filename.endsWith(\".docx\") || filename.endsWith(\".md\")) {\n return \"document\";\n }\n return \"manual_upload\";\n}\n\nexport class HeuristicCurationProvider implements DocumentCurator {\n readonly model = \"heuristic\";\n\n async curate(input: DocumentCurationInput): Promise<DocumentCurationOutcome> {\n return heuristicCuration(input);\n }\n}\n\nconst CURATION_SYSTEM_PROMPT = [\n \"You organize a team knowledge base. Given the beginning of a dropped document\",\n \"and the list of existing collections (bases), return STRICT JSON with keys:\",\n '\"title\" (concise, specific, <= 120 chars, no filename extensions),',\n '\"summary\" (2-3 sentences, plain prose, what the document is and why it matters),',\n '\"sourceKind\" (one of: manual_upload, meeting_transcript, repository, email, chat, document, web, other),',\n '\"topics\" (3-6 short lowercase tags),',\n '\"targetBaseId\" (the id of the best-fitting existing base, or null if none fits),',\n '\"confidence\" (0..1 — how sure you are the document belongs in targetBaseId),',\n '\"reason\" (one sentence explaining the filing choice).',\n \"Only pick a targetBaseId from the provided list. If the document fits no base\",\n \"well, return targetBaseId null and confidence 0. Respond with JSON only.\",\n].join(\" \");\n\nexport class OpenAICurationProvider implements DocumentCurator {\n private client: OpenAI | null = null;\n private readonly apiKey: string | undefined;\n private readonly baseURL: string | undefined;\n private readonly defaultHeaders: Record<string, string> | undefined;\n private readonly defaultQuery: Record<string, string> | undefined;\n readonly model: string;\n\n constructor(args: {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n model?: string | undefined;\n }) {\n this.apiKey = args.apiKey ?? process.env.OPENAI_API_KEY;\n this.baseURL = args.baseURL;\n this.defaultHeaders = args.defaultHeaders;\n this.defaultQuery = args.defaultQuery;\n this.model = args.model ?? DEFAULT_DOCUMENT_CURATION_MODEL;\n }\n\n async curate(input: DocumentCurationInput): Promise<DocumentCurationOutcome> {\n const response = await this.openai().chat.completions.create({\n model: this.model,\n response_format: { type: \"json_object\" },\n messages: [\n { role: \"system\", content: CURATION_SYSTEM_PROMPT },\n {\n role: \"user\",\n content: JSON.stringify({\n filename: input.filename,\n currentTitle: input.title,\n bases: input.bases,\n text: input.text,\n }),\n },\n ],\n });\n const raw = response.choices[0]?.message?.content;\n if (!raw) {\n throw new Error(\"curation model returned no content\");\n }\n return parseCurationOutcome(raw, input.bases);\n }\n\n private openai(): OpenAI {\n if (!this.apiKey) {\n throw new Error(\"OpenAI document curation requires an API key\");\n }\n this.client ??= new OpenAI({\n apiKey: this.apiKey,\n ...(this.baseURL ? { baseURL: this.baseURL } : {}),\n ...(this.defaultQuery ? { defaultQuery: this.defaultQuery } : {}),\n ...(this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}),\n });\n return this.client;\n }\n}\n\n/** Parse + clamp model output; a targetBaseId outside the candidate list is dropped. */\nexport function parseCurationOutcome(\n raw: string,\n bases: DocumentCurationCandidateBase[],\n): DocumentCurationOutcome {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"curation model returned non-object JSON\");\n }\n const record = parsed as Record<string, unknown>;\n const knownBase = bases.find((base) => base.id === record.targetBaseId);\n const confidence =\n typeof record.confidence === \"number\" && Number.isFinite(record.confidence)\n ? Math.min(1, Math.max(0, record.confidence))\n : 0;\n return {\n title:\n cleanString(typeof record.title === \"string\" ? record.title.slice(0, 200) : null) ?? null,\n summary:\n cleanString(typeof record.summary === \"string\" ? record.summary.slice(0, 2000) : null) ??\n null,\n sourceKind:\n typeof record.sourceKind === \"string\"\n ? normalizeKnowledgeSourceKind(record.sourceKind)\n : null,\n topics: cleanStringArray(\n Array.isArray(record.topics)\n ? record.topics.filter((topic): topic is string => typeof topic === \"string\").slice(0, 8)\n : [],\n ).map((topic) => topic.toLowerCase().slice(0, 60)),\n targetBaseId: knownBase?.id ?? null,\n confidence: knownBase ? confidence : 0,\n reason:\n cleanString(typeof record.reason === \"string\" ? record.reason.slice(0, 500) : null) ?? null,\n };\n}\n\nexport function createDocumentServices(\n settings?: Settings,\n overrides: Partial<DocumentServices> = {},\n): DocumentServices {\n const dimensions = settings?.documentEmbeddingDimensions ?? DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS;\n const openAIEmbeddingConfig = documentOpenAIEmbeddingConfig(settings);\n return {\n parser: overrides.parser ?? new LiteParseDocumentParser(),\n chunker:\n overrides.chunker ??\n new RecursiveTextChunker(\n settings?.documentChunkSize ?? DEFAULT_DOCUMENT_CHUNK_SIZE,\n settings?.documentChunkOverlap ?? DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n ),\n embedder:\n overrides.embedder ??\n (settings?.documentEmbeddingProvider === \"deterministic\"\n ? new DeterministicEmbeddingProvider(dimensions, settings.documentEmbeddingModel)\n : new OpenAIEmbeddingProvider({\n ...openAIEmbeddingConfig,\n model: settings?.documentEmbeddingModel ?? DEFAULT_DOCUMENT_EMBEDDING_MODEL,\n dimensions,\n })),\n curator: overrides.curator ?? createDocumentCurator(settings),\n };\n}\n\nfunction createDocumentCurator(settings?: Settings): DocumentCurator | undefined {\n const provider = settings?.documentCurationProvider ?? \"openai\";\n if (provider === \"none\") return undefined;\n if (provider === \"heuristic\") return new HeuristicCurationProvider();\n const embeddingConfig = documentOpenAIEmbeddingConfig(settings);\n return new OpenAICurationProvider({\n apiKey: settings?.documentCurationApiKey ?? embeddingConfig.apiKey,\n baseURL: settings?.documentCurationBaseUrl ?? embeddingConfig.baseURL,\n defaultHeaders: embeddingConfig.defaultHeaders,\n defaultQuery: embeddingConfig.defaultQuery,\n model: settings?.documentCurationModel ?? DEFAULT_DOCUMENT_CURATION_MODEL,\n });\n}\n\nexport function documentOpenAIEmbeddingConfig(settings?: Settings): {\n apiKey?: string | undefined;\n baseURL?: string | undefined;\n defaultHeaders?: Record<string, string> | undefined;\n defaultQuery?: Record<string, string> | undefined;\n} {\n if (!settings) return {};\n if (settings.documentEmbeddingApiKey || settings.documentEmbeddingBaseUrl) {\n return {\n apiKey:\n settings.documentEmbeddingApiKey ?? settings.openaiApiKey ?? settings.azureOpenaiApiKey,\n baseURL:\n settings.documentEmbeddingBaseUrl ?? settings.openaiBaseUrl ?? settings.azureOpenaiBaseUrl,\n };\n }\n if (settings.openaiProvider === \"azure\") {\n const baseURL = settings.azureOpenaiBaseUrl ?? azureDeploymentBaseUrl(settings);\n return {\n apiKey: settings.azureOpenaiApiKey ?? settings.azureOpenaiAdToken ?? \"azure-ad-token\",\n baseURL,\n defaultQuery: azureOpenAIDefaultQuery(settings, baseURL),\n defaultHeaders:\n settings.azureOpenaiAdToken && !settings.azureOpenaiApiKey\n ? { Authorization: `Bearer ${settings.azureOpenaiAdToken}` }\n : undefined,\n };\n }\n return {\n apiKey: settings.openaiApiKey,\n baseURL: settings.openaiBaseUrl,\n };\n}\n\nfunction azureDeploymentBaseUrl(settings: Settings): string {\n const endpoint = settings.azureOpenaiEndpoint?.replace(/\\/+$/, \"\");\n if (!endpoint || !settings.azureOpenaiDeployment) {\n throw new Error(\"Azure OpenAI endpoint/deployment settings are incomplete\");\n }\n return `${endpoint}/openai/deployments/${settings.azureOpenaiDeployment}`;\n}\n\nfunction azureOpenAIDefaultQuery(\n settings: Pick<Settings, \"azureOpenaiApiVersion\">,\n baseURL: string,\n): Record<string, string> | undefined {\n if (!settings.azureOpenaiApiVersion) return undefined;\n const normalized = baseURL.replace(/\\/+$/, \"\").toLowerCase();\n if (normalized.endsWith(\"/openai/v1\")) {\n return undefined;\n }\n return { \"api-version\": settings.azureOpenaiApiVersion };\n}\n\nexport async function createDocumentBase(\n db: Database,\n input: CreateDocumentBaseRequest & { accountId: string; workspaceId: string },\n): Promise<DocumentBase> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [row] = await scopedDb\n .insert(schema.documentBases)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n name: input.name.trim(),\n description: input.description?.trim() || null,\n })\n .returning();\n if (!row) throw new Error(\"Failed to create document base\");\n return mapDocumentBase(row);\n },\n );\n}\n\nexport async function listDocumentBases(\n db: Database,\n workspaceId: string,\n): Promise<DocumentBase[]> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const rows = await scopedDb\n .select()\n .from(schema.documentBases)\n .where(eq(schema.documentBases.workspaceId, workspaceId))\n .orderBy(desc(schema.documentBases.createdAt));\n return rows.map(mapDocumentBase);\n });\n}\n\n/**\n * Return a bounded structural inventory without loading document records.\n * Document predicates are applied independently to every aggregate so private\n * counts and topic metadata remain scoped to the initiating grant subject.\n */\nexport async function getDocumentInventory(\n db: Database,\n workspaceId: string,\n input: DocumentInventoryInput,\n): Promise<DocumentInventory> {\n const baseLimit = requireDocumentInventoryLimit(\n input.baseLimit,\n DOCUMENT_INVENTORY_MAX_BASE_LIMIT,\n \"baseLimit\",\n );\n const topicLimit = requireDocumentInventoryLimit(\n input.topicLimit,\n DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT,\n \"topicLimit\",\n );\n const topicMaxChars = requireDocumentInventoryLimit(\n input.topicMaxChars,\n DOCUMENT_INVENTORY_MAX_TOPIC_CHARS,\n \"topicMaxChars\",\n );\n\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [baseTotal] = await scopedDb\n .select({ count: sql<number>`count(*)::int` })\n .from(schema.documentBases)\n .where(eq(schema.documentBases.workspaceId, workspaceId));\n\n const baseJoin = and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.baseId, schema.documentBases.id),\n ...documentAccessConditions(input.access),\n );\n const baseRows = await scopedDb\n .select({\n id: schema.documentBases.id,\n name: schema.documentBases.name,\n visibleDocumentCount: sql<number>`count(${schema.documents.id})::int`,\n queuedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,\n indexingCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,\n readyCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,\n failedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,\n latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,\n })\n .from(schema.documentBases)\n .leftJoin(schema.documents, baseJoin)\n .where(eq(schema.documentBases.workspaceId, workspaceId))\n .groupBy(schema.documentBases.id, schema.documentBases.name, schema.documentBases.createdAt)\n .orderBy(desc(schema.documentBases.createdAt), asc(schema.documentBases.id))\n .limit(baseLimit);\n\n const documentWhere = and(\n eq(schema.documents.workspaceId, workspaceId),\n ...documentAccessConditions(input.access),\n );\n const [summary] = await scopedDb\n .select({\n visibleDocumentCount: sql<number>`count(${schema.documents.id})::int`,\n queuedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,\n indexingCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,\n readyCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,\n failedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,\n manualUploadCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'manual_upload')::int`,\n meetingTranscriptCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'meeting_transcript')::int`,\n repositoryCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'repository')::int`,\n emailCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'email')::int`,\n chatCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'chat')::int`,\n documentCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'document')::int`,\n webCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'web')::int`,\n otherCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'other')::int`,\n latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,\n })\n .from(schema.documents)\n .where(documentWhere);\n\n const topicStringValue = sql<string>`topic.value #>> '{}'`;\n const topicNormalizedName = sql<string>`btrim(regexp_replace(normalize(${topicStringValue}, NFKC), '[[:space:]]+', ' ', 'g'))`;\n const topicName = sql<string>`left(${topicNormalizedName}, ${topicMaxChars})`;\n const topicDocumentCount = sql<number>`count(distinct ${schema.documents.id})::int`;\n const topicRows = await scopedDb\n .select({ name: topicName, documentCount: topicDocumentCount })\n .from(schema.documents)\n .innerJoin(\n sql`lateral jsonb_array_elements(${schema.documents.topics}) as topic(value)`,\n sql`true`,\n )\n .where(\n and(\n documentWhere,\n sql`jsonb_typeof(topic.value) = 'string'`,\n sql`nullif(${topicNormalizedName}, '') is not null`,\n ),\n )\n .groupBy(sql`1`)\n .orderBy(sql`2 desc`, sql`1 asc`)\n .limit(topicLimit + 1);\n\n const statusCounts = documentInventoryStatusCounts(summary ?? {});\n const topics = topicRows.slice(0, topicLimit).map((topic) => ({\n name: topic.name,\n documentCount: Number(topic.documentCount),\n }));\n return {\n baseCount: Number(baseTotal?.count ?? 0),\n bases: baseRows.map((base) => ({\n id: base.id,\n name: base.name,\n visibleDocumentCount: Number(base.visibleDocumentCount),\n statusCounts: documentInventoryStatusCounts(base),\n latestUpdatedAt: documentInventoryTimestamp(base.latestUpdatedAt),\n })),\n visibleDocumentCount: Number(summary?.visibleDocumentCount ?? 0),\n statusCounts,\n sourceKindCounts: {\n manual_upload: Number(summary?.manualUploadCount ?? 0),\n meeting_transcript: Number(summary?.meetingTranscriptCount ?? 0),\n repository: Number(summary?.repositoryCount ?? 0),\n email: Number(summary?.emailCount ?? 0),\n chat: Number(summary?.chatCount ?? 0),\n document: Number(summary?.documentCount ?? 0),\n web: Number(summary?.webCount ?? 0),\n other: Number(summary?.otherCount ?? 0),\n },\n latestUpdatedAt: documentInventoryTimestamp(summary?.latestUpdatedAt ?? null),\n topics,\n topicsTruncated: topicRows.length > topics.length,\n };\n });\n}\n\nfunction requireDocumentInventoryLimit(value: number, max: number, name: string): number {\n if (!Number.isInteger(value) || value < 1 || value > max) {\n throw new Error(`${name} must be an integer between 1 and ${max}`);\n }\n return value;\n}\n\nfunction documentInventoryStatusCounts(input: {\n queuedCount?: number | null | undefined;\n indexingCount?: number | null | undefined;\n readyCount?: number | null | undefined;\n failedCount?: number | null | undefined;\n}): DocumentInventoryStatusCounts {\n return {\n queued: Number(input.queuedCount ?? 0),\n indexing: Number(input.indexingCount ?? 0),\n ready: Number(input.readyCount ?? 0),\n failed: Number(input.failedCount ?? 0),\n };\n}\n\nfunction documentInventoryTimestamp(value: Date | string | null): string | null {\n if (!value) return null;\n return value instanceof Date ? value.toISOString() : new Date(value).toISOString();\n}\n\nexport async function getDocumentBase(\n db: Database,\n workspaceId: string,\n baseId: string,\n): Promise<DocumentBase | null> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documentBases)\n .where(\n and(eq(schema.documentBases.workspaceId, workspaceId), eq(schema.documentBases.id, baseId)),\n )\n .limit(1);\n return row ? mapDocumentBase(row) : null;\n });\n}\n\n/**\n * Find-or-create the workspace's Default base — where knowledge drops land\n * before configured curation files them elsewhere. Matched by name\n * (case-insensitive) so a user-created \"Default\" is adopted rather than\n * duplicated.\n */\nexport async function ensureDefaultBase(\n db: Database,\n input: { accountId: string; workspaceId: string },\n): Promise<DocumentBase> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) =>\n await scopedDb.transaction(async (tx) => {\n const defaultName = sql`lower(btrim(${schema.documentBases.name})) = ${DEFAULT_BASE_NAME.toLowerCase()}`;\n const [existing] = await tx\n .select()\n .from(schema.documentBases)\n .where(and(eq(schema.documentBases.workspaceId, input.workspaceId), defaultName))\n .limit(1);\n if (existing) return mapDocumentBase(existing);\n\n // The partial unique index is the serialization point for concurrent\n // first drops. A losing insert re-reads the winner instead of\n // surfacing a duplicate-base error.\n const [inserted] = await tx\n .insert(schema.documentBases)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n name: DEFAULT_BASE_NAME,\n description: DEFAULT_BASE_DESCRIPTION,\n })\n .onConflictDoNothing()\n .returning();\n if (inserted) return mapDocumentBase(inserted);\n\n const [raced] = await tx\n .select()\n .from(schema.documentBases)\n .where(and(eq(schema.documentBases.workspaceId, input.workspaceId), defaultName))\n .limit(1);\n if (raced) return mapDocumentBase(raced);\n throw new Error(\"Failed to create Default document base\");\n }),\n );\n}\n\nexport async function addDocumentToBase(\n db: Database,\n input: AddDocumentRequest & {\n accountId: string;\n workspaceId: string;\n baseId: string;\n createdBy?: string | null | undefined;\n curationStatus?: DocumentCurationStatus | undefined;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<Document> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);\n if (!base) throw new Error(`Document base not found: ${input.baseId}`);\n const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);\n if (input.visibility === \"private\" && !cleanString(input.createdBy ?? null)) {\n throw new Error(\"private documents require a creating subject\");\n }\n const now = new Date();\n const [existing] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.baseId, input.baseId),\n eq(schema.documents.fileId, input.fileId),\n ),\n )\n .limit(1);\n if (existing) {\n if (!documentMatchesAccess(existing, input.access)) {\n throw new Error(`Document not found: ${existing.id}`);\n }\n // Idempotent re-add: refresh caller-supplied source metadata on the\n // existing row instead of silently discarding it (aclTags especially —\n // a re-add that tightens tags must not be a no-op). Access policy is\n // deliberately inherited: re-adding a known file is not an implicit\n // visibility/agent-policy update that another manager can exploit.\n const [updated] = await scopedDb\n .update(schema.documents)\n .set({\n title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? existing.title,\n ...(input.sourceKind !== undefined ? { sourceKind: input.sourceKind } : {}),\n sourceUri: cleanString(input.sourceUri) ?? existing.sourceUri,\n sourceExternalId: cleanString(input.sourceExternalId) ?? existing.sourceExternalId,\n sourceTitle: cleanString(input.sourceTitle) ?? existing.sourceTitle,\n sourceAuthor: cleanString(input.sourceAuthor) ?? existing.sourceAuthor,\n sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt) ?? existing.sourceCreatedAt,\n sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt) ?? existing.sourceUpdatedAt,\n sourceVersion: cleanString(input.sourceVersion) ?? existing.sourceVersion,\n ...(input.aclTags !== undefined ? { aclTags: cleanStringArray(input.aclTags) } : {}),\n updatedAt: now,\n })\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, existing.id),\n ...documentAccessConditions(input.access),\n ),\n )\n .returning();\n return mapDocument(updated ?? existing);\n }\n const [row] = await scopedDb\n .insert(schema.documents)\n .values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n baseId: input.baseId,\n fileId: input.fileId,\n status: \"queued\",\n title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? file.filename,\n parser: DEFAULT_DOCUMENT_PARSER,\n sourceKind: input.sourceKind ?? \"manual_upload\",\n sourceUri: cleanString(input.sourceUri) ?? null,\n sourceExternalId: cleanString(input.sourceExternalId) ?? null,\n sourceTitle: cleanString(input.sourceTitle) ?? null,\n sourceAuthor: cleanString(input.sourceAuthor) ?? null,\n sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),\n sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),\n sourceVersion: cleanString(input.sourceVersion) ?? null,\n aclTags: cleanStringArray(input.aclTags),\n visibility: input.visibility ?? \"workspace\",\n agentAccess: input.agentAccess ?? true,\n createdBy: input.createdBy ?? null,\n curationStatus: input.curationStatus ?? \"none\",\n updatedAt: now,\n })\n .returning();\n if (!row) throw new Error(\"Failed to create document\");\n return mapDocument(row);\n },\n );\n}\n\n/**\n * Move a document and its indexed chunks to another base. With no explicit\n * target, applies the stored curation suggestion. A 'suggested' or 'pending'\n * document that gets moved counts as filed.\n */\nexport async function moveDocumentToBase(\n db: Database,\n input: {\n accountId: string;\n workspaceId: string;\n documentId: string;\n targetBaseId?: string | null | undefined;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<Document> {\n return await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .limit(1);\n if (!row) throw new Error(`Document not found: ${input.documentId}`);\n const suggestion = (row.curation as { suggestedBaseId?: string | null } | null)\n ?.suggestedBaseId;\n const targetBaseId = input.targetBaseId ?? suggestion;\n if (!targetBaseId) {\n throw new Error(\"document has no suggested base; pass targetBaseId\");\n }\n if (targetBaseId === row.baseId) return mapDocument(row);\n const base = await getDocumentBase(scopedDb, input.workspaceId, targetBaseId);\n if (!base) throw new Error(`Document base not found: ${targetBaseId}`);\n const [conflict] = await scopedDb\n .select({ id: schema.documents.id })\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.baseId, targetBaseId),\n eq(schema.documents.fileId, row.fileId),\n ),\n )\n .limit(1);\n if (conflict) {\n throw new Error(\"a document for this file already exists in the target base\");\n }\n const now = new Date();\n const moved = await scopedDb.transaction(async (tx) => {\n const [updated] = await tx\n .update(schema.documents)\n .set({\n baseId: targetBaseId,\n ...(row.curationStatus === \"suggested\" || row.curationStatus === \"pending\"\n ? { curationStatus: \"auto_filed\" }\n : {}),\n updatedAt: now,\n })\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .returning();\n if (updated) {\n await tx\n .update(schema.documentChunks)\n .set({ baseId: targetBaseId })\n .where(\n and(\n eq(schema.documentChunks.workspaceId, input.workspaceId),\n eq(schema.documentChunks.documentId, input.documentId),\n ),\n );\n }\n return updated;\n });\n if (!moved) throw new Error(`Document not found: ${input.documentId}`);\n return mapDocument(moved);\n },\n );\n}\n\nexport async function deleteDocumentFromBase(\n db: Database,\n input: {\n accountId: string;\n workspaceId: string;\n baseId: string;\n documentId: string;\n access?: DocumentAccessFilter | undefined;\n },\n): Promise<void> {\n await withRlsContext(\n db,\n { accountId: input.accountId, workspaceId: input.workspaceId },\n async (scopedDb) => {\n const [document] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n )\n .limit(1);\n if (!document) {\n throw new Error(`Document not found: ${input.documentId}`);\n }\n if (document.baseId !== input.baseId) {\n throw new Error(`Document not found: ${input.documentId}`);\n }\n await scopedDb\n .delete(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, input.workspaceId),\n eq(schema.documents.id, input.documentId),\n ...documentAccessConditions(input.access),\n ),\n );\n },\n );\n}\n\nexport async function listDocuments(\n db: Database,\n workspaceId: string,\n baseId: string,\n access?: DocumentAccessFilter,\n): Promise<Document[]> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const rows = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.baseId, baseId),\n ...documentAccessConditions(access),\n ),\n )\n .orderBy(asc(schema.documents.createdAt));\n return rows.map(mapDocument);\n });\n}\n\nexport async function getDocument(\n db: Database,\n workspaceId: string,\n documentId: string,\n access?: DocumentAccessFilter,\n): Promise<Document | null> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ...documentAccessConditions(access),\n ),\n )\n .limit(1);\n return row ? mapDocument(row) : null;\n });\n}\n\nexport async function queueDocumentForReindex(\n db: Database,\n workspaceId: string,\n documentId: string,\n access?: DocumentAccessFilter,\n): Promise<Document> {\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const [row] = await scopedDb\n .update(schema.documents)\n .set({\n status: \"queued\",\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ...documentAccessConditions(access),\n ),\n )\n .returning();\n if (!row) throw new Error(`Document not found: ${documentId}`);\n return mapDocument(row);\n });\n}\n\nexport async function indexDocumentNow(\n db: Database,\n objectStorage: ObjectStorage,\n workspaceId: string,\n documentId: string,\n services: DocumentServices = createDocumentServices(),\n hooks: DocumentIndexHooks = {},\n): Promise<Document> {\n const [loadedDocument] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select()\n .from(schema.documents)\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n )\n .limit(1),\n );\n if (!loadedDocument) throw new Error(`Document not found: ${documentId}`);\n let document: DocumentRow = loadedDocument;\n const file = await requireReadyFile(db, workspaceId, document.fileId);\n await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n await scopedDb\n .update(schema.documents)\n .set({\n status: \"indexing\",\n parser: services.parser.name,\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n );\n });\n try {\n const bytes = await objectStorage.getFileBytes(file);\n const parsed = await services.parser.parse(bytes, file);\n // Knowledge drops (curationStatus 'pending') are curated between parse and\n // chunking when a provider is enabled, so chunk metadata and base placement\n // reflect the curated truth. Disabled curation leaves caller metadata intact;\n // enabled-provider failures remain fail-soft through the heuristic fallback.\n if (document.curationStatus === \"pending\") {\n document = await curateDroppedDocument(db, services, document, parsed, file);\n }\n const chunks = services.chunker.chunk(parsed, file);\n await hooks.beforeEmbed?.({\n accountId: document.accountId,\n workspaceId: document.workspaceId,\n documentId,\n chunkCount: chunks.length,\n });\n const embeddings = await services.embedder.embedMany(chunks.map((chunk) => chunk.text));\n if (embeddings.length !== chunks.length) {\n throw new Error(\n `Embedding provider returned ${embeddings.length} embeddings for ${chunks.length} chunks`,\n );\n }\n await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb.transaction(async (tx) => {\n await tx\n .delete(schema.documentChunks)\n .where(\n and(\n eq(schema.documentChunks.workspaceId, workspaceId),\n eq(schema.documentChunks.documentId, documentId),\n ),\n );\n if (chunks.length > 0) {\n await tx.insert(schema.documentChunks).values(\n chunks.map((chunk, index) => ({\n accountId: document.accountId,\n workspaceId: document.workspaceId,\n documentId,\n baseId: document.baseId,\n fileId: file.id,\n chunkIndex: index,\n text: chunk.text,\n metadata: {\n ...chunk.metadata,\n documentTitle: document.title,\n sourceKind: document.sourceKind,\n sourceUri: document.sourceUri,\n sourceExternalId: document.sourceExternalId,\n sourceTitle: document.sourceTitle,\n sourceAuthor: document.sourceAuthor,\n sourceCreatedAt: document.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: document.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: document.sourceVersion,\n aclTags: document.aclTags,\n },\n embedding: validateEmbedding(\n embeddings[index] ?? [],\n services.embedder.dimensions,\n services.embedder.model,\n ),\n embeddingModel: services.embedder.model,\n })),\n );\n }\n await tx\n .update(schema.documents)\n .set({\n status: \"ready\",\n parser: services.parser.name,\n chunkCount: chunks.length,\n error: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, workspaceId),\n eq(schema.documents.id, documentId),\n ),\n );\n }),\n );\n } catch (error) {\n const [failed] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n status: \"failed\",\n error: error instanceof Error ? error.message : String(error),\n updatedAt: new Date(),\n })\n .where(\n and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),\n )\n .returning(),\n );\n if (!failed) throw error;\n return mapDocument(failed);\n }\n // Internal indexing must be able to return a private document to the caller\n // that created/queued it. Public reads remain fail-closed when no subject is\n // supplied; the creator subject is the document's frozen access principal.\n const updated = await getDocument(db, workspaceId, documentId, {\n viewerSubjectId: document.createdBy,\n });\n if (!updated) throw new Error(`Document disappeared after indexing: ${documentId}`);\n return updated;\n}\n\ntype DocumentRow = typeof schema.documents.$inferSelect;\n\nasync function curateDroppedDocument(\n db: Database,\n services: DocumentServices,\n document: DocumentRow,\n parsed: ParsedDocument,\n file: FileAsset,\n): Promise<DocumentRow> {\n // `none` is an explicit disabled-curation policy. Do not silently replace\n // it with heuristics: indexing still proceeds, but the drop remains an\n // ordinary uncured document with its caller-supplied title and metadata.\n if (!services.curator) {\n const [updated] = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n curationStatus: \"none\",\n summary: null,\n topics: [],\n curation: null,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.id, document.id),\n ),\n )\n .returning(),\n );\n return updated ?? document;\n }\n const bases = await listDocumentBases(db, document.workspaceId);\n const candidates: DocumentCurationCandidateBase[] = bases\n .filter((base) => base.id !== document.baseId)\n .map((base) => ({ id: base.id, name: base.name, description: base.description }));\n const input: DocumentCurationInput = {\n text: parsed.text.slice(0, DOCUMENT_CURATION_MAX_INPUT_CHARS),\n filename: file.filename,\n title: document.title,\n bases: candidates,\n };\n let outcome: DocumentCurationOutcome;\n let model: string;\n let failure: string | null = null;\n try {\n outcome = await services.curator.curate(input);\n model = services.curator.model;\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n console.warn(\"document curation failed; applying heuristic fallback\", {\n workspaceId: document.workspaceId,\n documentId: document.id,\n error: failure,\n });\n outcome = heuristicCuration(input, file.contentType);\n model = \"heuristic\";\n }\n const suggestedBase = candidates.find((base) => base.id === outcome.targetBaseId) ?? null;\n let moveToBaseId: string | null = null;\n if (\n suggestedBase &&\n failure === null &&\n outcome.confidence >= DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE\n ) {\n // The (workspace, base, file) unique index means a same-file twin already\n // in the target base blocks the move; keep it as a suggestion instead.\n const conflict = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({ id: schema.documents.id })\n .from(schema.documents)\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.baseId, suggestedBase.id),\n eq(schema.documents.fileId, document.fileId),\n ),\n )\n .limit(1),\n );\n if (conflict.length === 0) {\n moveToBaseId = suggestedBase.id;\n }\n }\n const curation: DocumentCuration = {\n suggestedBaseId: suggestedBase?.id ?? null,\n suggestedBaseName: suggestedBase?.name ?? null,\n confidence: outcome.confidence,\n reason: failure ? `curation failed (${failure}); heuristic fallback applied` : outcome.reason,\n originalTitle: document.title,\n model,\n };\n const curationStatus: DocumentCurationStatus = failure\n ? \"failed\"\n : moveToBaseId\n ? \"auto_filed\"\n : \"suggested\";\n const [updated] = await withWorkspaceRls(\n db,\n document.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .update(schema.documents)\n .set({\n title: outcome.title ?? document.title,\n summary: outcome.summary,\n topics: outcome.topics,\n ...(outcome.sourceKind ? { sourceKind: outcome.sourceKind } : {}),\n ...(moveToBaseId ? { baseId: moveToBaseId } : {}),\n curationStatus,\n curation,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(schema.documents.workspaceId, document.workspaceId),\n eq(schema.documents.id, document.id),\n ),\n )\n .returning(),\n );\n return updated ?? document;\n}\n\nexport async function searchDocuments(\n db: Database,\n input: DocumentSearchInput,\n services: Pick<DocumentServices, \"embedder\"> = createDocumentServices(),\n): Promise<DocumentSearchResult[]> {\n const mode = input.mode ?? \"hybrid\";\n const limit = Math.min(Math.max(input.limit ?? 5, 1), 50);\n const candidateLimit = mode === \"hybrid\" ? Math.min(limit * 4, 100) : limit;\n const rows: CombinedSearchRow[] = [];\n if (mode === \"vector\" || mode === \"hybrid\") {\n try {\n rows.push(...(await vectorSearchDocuments(db, input, candidateLimit, services)));\n } catch (error) {\n if (mode === \"vector\") {\n throw error;\n }\n console.warn(\n \"document hybrid search vector component failed; falling back to keyword search\",\n {\n workspaceId: input.workspaceId,\n error: error instanceof Error ? error.message : String(error),\n },\n );\n }\n }\n if (mode === \"keyword\" || mode === \"hybrid\") {\n rows.push(...(await keywordSearchDocuments(db, input, candidateLimit)));\n }\n return mergeDocumentSearchRows(rows, mode).slice(0, limit);\n}\n\nasync function vectorSearchDocuments(\n db: Database,\n input: DocumentSearchInput,\n limit: number,\n services: Pick<DocumentServices, \"embedder\">,\n): Promise<CombinedSearchRow[]> {\n const queryEmbedding = await services.embedder.embedQuery(input.query);\n validateEmbedding(queryEmbedding, services.embedder.dimensions, services.embedder.model);\n const distance = sql<number>`${schema.documentChunks.embedding} <=> ${vectorLiteral(queryEmbedding)}::vector`;\n const rows = await withWorkspaceRls(\n db,\n input.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n distance,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(and(...documentSearchConditions(input, services.embedder.model)))\n .orderBy(distance)\n .limit(limit),\n );\n return rows.map((row) => ({\n ...mapSearchRowBase(row, input.workspaceId),\n vectorScore: 1 / (1 + Number(row.distance)),\n keywordScore: null,\n }));\n}\n\nasync function keywordSearchDocuments(\n db: Database,\n input: DocumentSearchInput,\n limit: number,\n): Promise<CombinedSearchRow[]> {\n const rank = sql<number>`ts_rank_cd(to_tsvector('simple', ${schema.documentChunks.text}), plainto_tsquery('simple', ${input.query}))`;\n const rows = await withWorkspaceRls(\n db,\n input.workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n rank,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(\n and(\n ...documentSearchConditions(input),\n sql`to_tsvector('simple', ${schema.documentChunks.text}) @@ plainto_tsquery('simple', ${input.query})`,\n ),\n )\n .orderBy(desc(rank))\n .limit(limit),\n );\n return rows.map((row) => ({\n ...mapSearchRowBase(row, input.workspaceId),\n vectorScore: null,\n keywordScore: normalizeKeywordScore(Number(row.rank)),\n }));\n}\n\nexport async function getDocumentChunk(\n db: Database,\n workspaceId: string,\n chunkId: string,\n access?: DocumentAccessFilter,\n): Promise<DocumentSearchResult | null> {\n const [row] = await withWorkspaceRls(\n db,\n workspaceId,\n async (scopedDb) =>\n await scopedDb\n .select({\n chunkId: schema.documentChunks.id,\n documentId: schema.documentChunks.documentId,\n baseId: schema.documentChunks.baseId,\n fileId: schema.documentChunks.fileId,\n title: schema.documents.title,\n text: schema.documentChunks.text,\n chunkIndex: schema.documentChunks.chunkIndex,\n metadata: schema.documentChunks.metadata,\n sourceKind: schema.documents.sourceKind,\n sourceUri: schema.documents.sourceUri,\n sourceExternalId: schema.documents.sourceExternalId,\n sourceTitle: schema.documents.sourceTitle,\n sourceAuthor: schema.documents.sourceAuthor,\n sourceCreatedAt: schema.documents.sourceCreatedAt,\n sourceUpdatedAt: schema.documents.sourceUpdatedAt,\n sourceVersion: schema.documents.sourceVersion,\n aclTags: schema.documents.aclTags,\n })\n .from(schema.documentChunks)\n .innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))\n .where(\n and(\n eq(schema.documentChunks.workspaceId, workspaceId),\n eq(schema.documentChunks.id, chunkId),\n eq(schema.documents.status, \"ready\"),\n ...documentAccessConditions(access),\n ),\n )\n .limit(1),\n );\n if (!row) return null;\n return {\n ...mapSearchRowBase(row, workspaceId),\n score: 1,\n matchType: \"hybrid\",\n vectorScore: null,\n keywordScore: null,\n };\n}\n\ntype SearchRowBase = Omit<\n DocumentSearchResult,\n \"score\" | \"matchType\" | \"vectorScore\" | \"keywordScore\"\n>;\ntype CombinedSearchRow = SearchRowBase & {\n vectorScore: number | null;\n keywordScore: number | null;\n};\n\n/**\n * Visibility/agent scoping shared by every document read path. Fail-closed:\n * with no filter supplied, private documents are invisible.\n */\nfunction documentAccessConditions(access: DocumentAccessFilter | undefined): SQL[] {\n if (access?.agentOnly) {\n const viewer = access.viewerSubjectId;\n const visibility = viewer\n ? or(eq(schema.documents.visibility, \"workspace\"), eq(schema.documents.createdBy, viewer))\n : eq(schema.documents.visibility, \"workspace\");\n return [eq(schema.documents.agentAccess, true), ...(visibility ? [visibility] : [])];\n }\n const viewer = access?.viewerSubjectId;\n if (viewer) {\n const condition = or(\n eq(schema.documents.visibility, \"workspace\"),\n eq(schema.documents.createdBy, viewer),\n );\n return condition ? [condition] : [];\n }\n return [eq(schema.documents.visibility, \"workspace\")];\n}\n\nfunction documentMatchesAccess(\n document: Pick<DocumentAccessRecord, \"visibility\" | \"createdBy\" | \"agentAccess\">,\n access: DocumentAccessFilter | undefined,\n): boolean {\n if (access?.agentOnly) {\n return (\n document.agentAccess &&\n (document.visibility !== \"private\" ||\n (!!access.viewerSubjectId && document.createdBy === access.viewerSubjectId))\n );\n }\n return canViewDocument(document, access?.viewerSubjectId);\n}\n\n/** Whether a single already-fetched document is readable by this human viewer. */\nexport function canViewDocument(\n document: Pick<DocumentAccessRecord, \"visibility\" | \"createdBy\">,\n viewerSubjectId: string | null | undefined,\n): boolean {\n return (\n document.visibility !== \"private\" ||\n (!!viewerSubjectId && document.createdBy === viewerSubjectId)\n );\n}\n\ntype DocumentAccessRecord = {\n visibility: string;\n createdBy: string | null;\n agentAccess: boolean;\n};\n\nfunction documentSearchConditions(input: DocumentSearchInput, embeddingModel?: string): SQL[] {\n const conditions: SQL[] = [\n eq(schema.documents.status, \"ready\"),\n eq(schema.documentChunks.workspaceId, input.workspaceId),\n ...documentAccessConditions(input.access),\n ];\n if (embeddingModel) {\n conditions.push(eq(schema.documentChunks.embeddingModel, embeddingModel));\n }\n if (input.baseIds?.length) {\n conditions.push(inArray(schema.documentChunks.baseId, input.baseIds));\n }\n if (input.sourceKinds?.length) {\n conditions.push(inArray(schema.documents.sourceKind, input.sourceKinds));\n }\n const aclTags = cleanStringArray(input.aclTags);\n if (aclTags.length > 0) {\n conditions.push(sql`${schema.documents.aclTags} @> ${JSON.stringify(aclTags)}::jsonb`);\n }\n return conditions;\n}\n\nfunction mapSearchRowBase(\n row: {\n chunkId: string;\n documentId: string;\n baseId: string;\n fileId: string;\n title: string;\n text: string;\n chunkIndex: number;\n metadata: Record<string, unknown>;\n sourceKind: string;\n sourceUri: string | null;\n sourceExternalId: string | null;\n sourceTitle: string | null;\n sourceAuthor: string | null;\n sourceCreatedAt: Date | null;\n sourceUpdatedAt: Date | null;\n sourceVersion: string | null;\n aclTags: string[];\n },\n workspaceId: string,\n): SearchRowBase {\n return {\n chunkId: row.chunkId,\n workspaceId,\n documentId: row.documentId,\n baseId: row.baseId,\n fileId: row.fileId,\n title: row.title,\n text: row.text,\n chunkIndex: row.chunkIndex,\n metadata: row.metadata,\n sourceKind: normalizeKnowledgeSourceKind(row.sourceKind),\n sourceUri: row.sourceUri,\n sourceExternalId: row.sourceExternalId,\n sourceTitle: row.sourceTitle,\n sourceAuthor: row.sourceAuthor,\n sourceCreatedAt: row.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: row.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: row.sourceVersion,\n aclTags: cleanStringArray(row.aclTags),\n };\n}\n\nfunction mergeDocumentSearchRows(\n rows: CombinedSearchRow[],\n mode: DocumentSearchMode,\n): DocumentSearchResult[] {\n const byChunk = new Map<string, CombinedSearchRow>();\n for (const row of rows) {\n const existing = byChunk.get(row.chunkId);\n if (!existing) {\n byChunk.set(row.chunkId, row);\n continue;\n }\n byChunk.set(row.chunkId, {\n ...existing,\n vectorScore: Math.max(existing.vectorScore ?? 0, row.vectorScore ?? 0) || null,\n keywordScore: Math.max(existing.keywordScore ?? 0, row.keywordScore ?? 0) || null,\n });\n }\n return [...byChunk.values()]\n .map((row) => {\n const vectorScore = row.vectorScore;\n const keywordScore = row.keywordScore;\n const matchType: DocumentSearchMode =\n vectorScore !== null && keywordScore !== null\n ? \"hybrid\"\n : vectorScore !== null\n ? \"vector\"\n : \"keyword\";\n return {\n ...row,\n score: combinedSearchScore(mode, vectorScore, keywordScore, matchType),\n matchType,\n };\n })\n .sort(\n (left, right) =>\n right.score - left.score ||\n (right.vectorScore ?? 0) - (left.vectorScore ?? 0) ||\n (right.keywordScore ?? 0) - (left.keywordScore ?? 0) ||\n left.chunkIndex - right.chunkIndex,\n );\n}\n\nfunction combinedSearchScore(\n mode: DocumentSearchMode,\n vectorScore: number | null,\n keywordScore: number | null,\n matchType: DocumentSearchMode,\n): number {\n const vector = vectorScore ?? 0;\n const keyword = keywordScore ?? 0;\n if (mode === \"vector\") return roundScore(vector);\n if (mode === \"keyword\") return roundScore(keyword);\n return roundScore(\n Math.min(1, 0.65 * vector + 0.35 * keyword + (matchType === \"hybrid\" ? 0.1 : 0)),\n );\n}\n\nfunction normalizeKeywordScore(rank: number): number {\n if (!Number.isFinite(rank) || rank <= 0) {\n return 0;\n }\n return rank / (rank + 1);\n}\n\nfunction roundScore(value: number): number {\n return Number(value.toFixed(6));\n}\n\nexport async function parseDocumentBytes(\n bytes: Uint8Array,\n file: FileAsset,\n parser: DocumentParser = new LiteParseDocumentParser(),\n): Promise<ParsedDocument> {\n return await parser.parse(bytes, file);\n}\n\nexport function chunkText(\n text: string,\n maxChars = DEFAULT_DOCUMENT_CHUNK_SIZE,\n overlapChars = DEFAULT_DOCUMENT_CHUNK_OVERLAP,\n): string[] {\n if (overlapChars >= maxChars) {\n throw new Error(\"chunk overlap must be smaller than chunk size\");\n }\n const normalized = text\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+/g, \" \")\n .trim();\n if (!normalized) return [];\n const paragraphs = normalized\n .split(/\\n{2,}/)\n .map((part) => part.replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n const chunks: string[] = [];\n let current = \"\";\n for (const paragraph of paragraphs.length > 0 ? paragraphs : [normalized.replace(/\\s+/g, \" \")]) {\n for (const part of splitOversizedText(paragraph, maxChars)) {\n if (!current) {\n current = part;\n } else if (current.length + 1 + part.length <= maxChars) {\n current = `${current} ${part}`;\n } else {\n chunks.push(current);\n current = withOverlap(current, overlapChars, part, maxChars);\n }\n }\n }\n if (current) chunks.push(current);\n return chunks.map((chunk) => chunk.trim()).filter(Boolean);\n}\n\nexport function deterministicEmbedding(\n text: string,\n dimensions = DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS,\n): number[] {\n const values = new Array(dimensions).fill(0);\n const tokens = text.toLowerCase().match(/[\\p{L}\\p{N}_-]+/gu) ?? [];\n for (const token of tokens) {\n let hash = 2166136261;\n for (const char of token) {\n hash ^= char.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n values[Math.abs(hash) % dimensions] += 1;\n }\n const norm = Math.hypot(...values) || 1;\n return values.map((value) => Number((value / norm).toFixed(6)));\n}\n\nasync function requireReadyFile(\n db: Database,\n workspaceId: string,\n fileId: string,\n): Promise<FileAsset> {\n const file = await requireFile(db, workspaceId, fileId);\n if (file.status !== \"ready\") {\n throw new Error(`File ${fileId} is ${file.status}`);\n }\n return file;\n}\n\nfunction validateEmbedding(values: number[], dimensions: number, model: string): number[] {\n if (values.length !== dimensions) {\n throw new Error(\n `Embedding model ${model} returned ${values.length} dimensions; expected ${dimensions}`,\n );\n }\n if (values.some((value) => !Number.isFinite(value))) {\n throw new Error(`Embedding model ${model} returned non-finite values`);\n }\n return values;\n}\n\nfunction isTextLike(file: FileAsset): boolean {\n const contentType = file.contentType.toLowerCase();\n const filename = file.filename.toLowerCase();\n return (\n contentType.startsWith(\"text/\") ||\n contentType === \"application/json\" ||\n contentType === \"application/xml\" ||\n contentType === \"application/x-yaml\" ||\n filename.endsWith(\".md\") ||\n filename.endsWith(\".markdown\") ||\n filename.endsWith(\".json\") ||\n filename.endsWith(\".yaml\") ||\n filename.endsWith(\".yml\") ||\n filename.endsWith(\".csv\") ||\n filename.endsWith(\".tsv\") ||\n filename.endsWith(\".xml\")\n );\n}\n\nfunction splitOversizedText(text: string, maxChars: number): string[] {\n if (text.length <= maxChars) return [text];\n const out: string[] = [];\n let remaining = text;\n while (remaining.length > maxChars) {\n const window = remaining.slice(0, maxChars + 1);\n const breakAt = Math.max(\n window.lastIndexOf(\". \"),\n window.lastIndexOf(\"? \"),\n window.lastIndexOf(\"! \"),\n window.lastIndexOf(\"; \"),\n window.lastIndexOf(\", \"),\n window.lastIndexOf(\" \"),\n );\n const end = breakAt > Math.floor(maxChars * 0.5) ? breakAt + 1 : maxChars;\n out.push(remaining.slice(0, end).trim());\n remaining = remaining.slice(end).trim();\n }\n if (remaining) out.push(remaining);\n return out;\n}\n\nfunction withOverlap(\n previous: string,\n overlapChars: number,\n next: string,\n maxChars: number,\n): string {\n if (overlapChars <= 0) return next;\n const overlap = previous\n .slice(Math.max(0, previous.length - overlapChars))\n .replace(/^\\S+\\s+/, \"\")\n .trim();\n const candidate = overlap ? `${overlap} ${next}` : next;\n return candidate.length <= maxChars ? candidate : next;\n}\n\nfunction cleanString(value: string | undefined | null): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction cleanStringArray(values: string[] | undefined | null): string[] {\n return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];\n}\n\nfunction parseOptionalDate(value: string | undefined): Date | null {\n const trimmed = cleanString(value);\n if (!trimmed) {\n return null;\n }\n const date = new Date(trimmed);\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Invalid source timestamp: ${trimmed}`);\n }\n return date;\n}\n\nfunction normalizeKnowledgeSourceKind(value: string): KnowledgeSourceKind {\n switch (value) {\n case \"manual_upload\":\n case \"meeting_transcript\":\n case \"repository\":\n case \"email\":\n case \"chat\":\n case \"document\":\n case \"web\":\n case \"other\":\n return value;\n default:\n return \"other\";\n }\n}\n\nfunction vectorLiteral(values: number[]): string {\n return `[${values.join(\",\")}]`;\n}\n\nfunction mapDocumentBase(row: typeof schema.documentBases.$inferSelect): DocumentBase {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n name: row.name,\n description: row.description,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction mapDocument(row: typeof schema.documents.$inferSelect): Document {\n return {\n id: row.id,\n workspaceId: row.workspaceId,\n baseId: row.baseId,\n fileId: row.fileId,\n status: row.status as Document[\"status\"],\n title: row.title,\n parser: row.parser,\n chunkCount: row.chunkCount,\n error: row.error,\n sourceKind: normalizeKnowledgeSourceKind(row.sourceKind),\n sourceUri: row.sourceUri,\n sourceExternalId: row.sourceExternalId,\n sourceTitle: row.sourceTitle,\n sourceAuthor: row.sourceAuthor,\n sourceCreatedAt: row.sourceCreatedAt?.toISOString() ?? null,\n sourceUpdatedAt: row.sourceUpdatedAt?.toISOString() ?? null,\n sourceVersion: row.sourceVersion,\n aclTags: cleanStringArray(row.aclTags),\n visibility: normalizeDocumentVisibility(row.visibility),\n createdBy: row.createdBy,\n agentAccess: row.agentAccess,\n summary: row.summary,\n topics: cleanStringArray(row.topics),\n curationStatus: normalizeDocumentCurationStatus(row.curationStatus),\n curation: (row.curation as DocumentCuration | null) ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction normalizeDocumentVisibility(value: string): DocumentVisibility {\n return value === \"private\" ? \"private\" : \"workspace\";\n}\n\nfunction normalizeDocumentCurationStatus(value: string): DocumentCurationStatus {\n switch (value) {\n case \"pending\":\n case \"suggested\":\n case \"auto_filed\":\n case \"failed\":\n return value;\n default:\n return \"none\";\n }\n}\n"],"mappings":";AAeA,SAAS,aAAa,gBAAgB,wBAAuC;AAC7E,YAAY,YAAY;AAExB,SAAS,iBAAiB;AAC1B,SAAS,KAAK,KAAK,MAAM,IAAI,SAAS,IAAI,WAAqB;AAC/D,OAAO,YAAY;AAEZ,IAAM,0BAA0B;AAChC,IAAM,mCAAmC;AACzC,IAAM,wCAAwC;AAC9C,IAAM,8BAA8B;AACpC,IAAM,iCAAiC;AACvC,IAAM,kCAAkC;AAGxC,IAAM,oCAAoC;AAG1C,IAAM,yCAAyC;AAG/C,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;AA8GxC,IAAM,oCAAoC;AAC1C,IAAM,qCAAqC;AAC3C,IAAM,qCAAqC;AAsBpC,IAAM,0BAAN,MAAwD;AAAA,EACpD,OAAO;AAAA,EACR,aAA4B,QAAQ,QAAQ;AAAA,EAEpD,MAAM,MAAM,OAAmB,MAA0C;AACvE,UAAM,OAAO,WAAW,IAAI,IACxB,OAAO,KAAK,KAAK,EAAE,SAAS,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,KAAK,IAC7D,MAAM,KAAK,mBAAmB,KAAK;AACvC,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB,YAAM,IAAI,MAAM,6BAA6B,KAAK,QAAQ,EAAE;AAAA,IAC9D;AACA,WAAO;AAAA,MACL,MAAM,KAAK,KAAK;AAAA,MAChB,UAAU;AAAA,QACR,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf,aAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,mBAAmB,OAAoC;AACnE,WAAO,MAAM,KAAK,aAAa,YAAY;AACzC,YAAM,SAAS,IAAI,UAAU,EAAE,YAAY,MAAM,YAAY,EAAE,CAAC;AAChE,YAAM,SAAS,MAAM,OAAO,MAAM,OAAO,KAAK,KAAK,GAAG,IAAI;AAC1D,YAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,OAAO,OAAO;AAC9D,aAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,KAAK;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,aAAgB,MAAoC;AAChE,UAAM,WAAW,KAAK;AACtB,QAAI,UAAsB,MAAM;AAChC,SAAK,aAAa,IAAI,QAAc,CAAC,YAAY;AAC/C,gBAAU;AAAA,IACZ,CAAC;AACD,UAAM,SAAS,MAAM,MAAM,MAAS;AACpC,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,UAAE;AACA,cAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEO,IAAM,uBAAN,MAAsD;AAAA,EAC3D,YACmB,WAAW,6BACX,eAAe,gCAChC;AAFiB;AACA;AAEjB,QAAI,gBAAgB,UAAU;AAC5B,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAAA,EACF;AAAA,EAEA,MAAM,QAAwB,MAAkC;AAC9D,WAAO,UAAU,OAAO,MAAM,KAAK,UAAU,KAAK,YAAY,EAAE,IAAI,CAAC,MAAM,WAAW;AAAA,MACpF;AAAA,MACA,UAAU;AAAA,QACR,GAAG,OAAO;AAAA,QACV,UAAU,KAAK;AAAA,QACf,aAAa,KAAK;AAAA,QAClB,YAAY;AAAA,MACd;AAAA,IACF,EAAE;AAAA,EACJ;AACF;AAEO,IAAM,0BAAN,MAA0D;AAAA,EACvD,SAAwB;AAAA,EACf;AAAA,EACA;AAAA,EAEjB,YAAY,MAOT;AACD,SAAK,SAAS,KAAK,UAAU,QAAQ,IAAI;AACzC,SAAK,UAAU,KAAK;AACpB,SAAK,iBAAiB,KAAK;AAC3B,SAAK,eAAe,KAAK;AACzB,SAAK,QAAQ,KAAK,SAAS;AAC3B,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA,EAES;AAAA,EACA;AAAA,EACQ;AAAA,EACA;AAAA,EAEjB,MAAM,UAAU,OAAsC;AACpD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,MAAkB,CAAC;AACzB,aAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,IAAI;AACrD,YAAM,QAAQ,MAAM,MAAM,OAAO,QAAQ,EAAE;AAC3C,YAAM,WAAW,MAAM,KAAK,OAAO,EAAE,WAAW,OAAO;AAAA,QACrD,OAAO,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,YAAY,KAAK;AAAA,MACnB,CAAC;AACD,iBAAW,QAAQ,SAAS,MAAM;AAChC,YAAI,KAAK,kBAAkB,KAAK,WAAW,KAAK,YAAY,KAAK,KAAK,CAAC;AAAA,MACzE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,MAAiC;AAChD,UAAM,CAAC,SAAS,IAAI,MAAM,KAAK,UAAU,CAAC,IAAI,CAAC;AAC/C,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAiB;AACvB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AACA,SAAK,WAAW,IAAI,OAAO;AAAA,MACzB,QAAQ,KAAK;AAAA,MACb,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChD,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,MAC/D,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACvE,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AACF;AAEO,IAAM,iCAAN,MAAiE;AAAA,EACtE,YACW,aAAa,uCACb,QAAQ,uBAAuB,UAAU,IAClD;AAFS;AACA;AAAA,EACR;AAAA,EAEH,MAAM,UAAU,OAAsC;AACpD,WAAO,MAAM,IAAI,CAAC,SAAS,uBAAuB,MAAM,KAAK,UAAU,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAM,WAAW,MAAiC;AAChD,WAAO,uBAAuB,MAAM,KAAK,UAAU;AAAA,EACrD;AACF;AASO,SAAS,kBACd,OACA,cAAc,4BACW;AACzB,QAAM,YAAY,MAAM,KACrB,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,cAAc,EAAE,EAAE,KAAK,CAAC,EACnD,KAAK,CAAC,SAAS,KAAK,UAAU,CAAC;AAClC,QAAM,SAAS,aAAa,MAAM,OAAO,MAAM,GAAG,GAAG,EAAE,KAAK,KAAK,MAAM;AACvE,QAAM,gBAAgB,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;AACzE,QAAM,UACJ,cAAc,WAAW,MAAM,GAAG,cAAc,MAAM,GAAG,GAAG,CAAC,QAAQ;AACvE,SAAO;AAAA,IACL;AAAA,IACA,SAAS,WAAW;AAAA,IACpB,YAAY,oBAAoB,MAAM,UAAU,WAAW;AAAA,IAC3D,QAAQ,CAAC;AAAA,IACT,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AACF;AAEA,SAAS,oBAAoB,aAAqB,gBAA6C;AAC7F,QAAM,WAAW,YAAY,YAAY;AACzC,QAAM,cAAc,eAAe,YAAY;AAC/C,MAAI,SAAS,SAAS,MAAM,KAAK,gBAAgB,iBAAkB,QAAO;AAC1E,MAAI,SAAS,SAAS,MAAM,KAAK,SAAS,SAAS,MAAM,KAAK,SAAS,SAAS,YAAY,GAAG;AAC7F,WAAO;AAAA,EACT;AACA,MAAI,gBAAgB,YAAa,QAAO;AACxC,MAAI,gBAAgB,qBAAqB,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,KAAK,GAAG;AAC/F,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,IAAM,4BAAN,MAA2D;AAAA,EACvD,QAAQ;AAAA,EAEjB,MAAM,OAAO,OAAgE;AAC3E,WAAO,kBAAkB,KAAK;AAAA,EAChC;AACF;AAEA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEH,IAAM,yBAAN,MAAwD;AAAA,EACrD,SAAwB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACR;AAAA,EAET,YAAY,MAMT;AACD,SAAK,SAAS,KAAK,UAAU,QAAQ,IAAI;AACzC,SAAK,UAAU,KAAK;AACpB,SAAK,iBAAiB,KAAK;AAC3B,SAAK,eAAe,KAAK;AACzB,SAAK,QAAQ,KAAK,SAAS;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,OAAgE;AAC3E,UAAM,WAAW,MAAM,KAAK,OAAO,EAAE,KAAK,YAAY,OAAO;AAAA,MAC3D,OAAO,KAAK;AAAA,MACZ,iBAAiB,EAAE,MAAM,cAAc;AAAA,MACvC,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,uBAAuB;AAAA,QAClD;AAAA,UACE,MAAM;AAAA,UACN,SAAS,KAAK,UAAU;AAAA,YACtB,UAAU,MAAM;AAAA,YAChB,cAAc,MAAM;AAAA,YACpB,OAAO,MAAM;AAAA,YACb,MAAM,MAAM;AAAA,UACd,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,MAAM,SAAS,QAAQ,CAAC,GAAG,SAAS;AAC1C,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,WAAO,qBAAqB,KAAK,MAAM,KAAK;AAAA,EAC9C;AAAA,EAEQ,SAAiB;AACvB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AACA,SAAK,WAAW,IAAI,OAAO;AAAA,MACzB,QAAQ,KAAK;AAAA,MACb,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChD,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,MAC/D,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACvE,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AACF;AAGO,SAAS,qBACd,KACA,OACyB;AACzB,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,QAAM,SAAS;AACf,QAAM,YAAY,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO,YAAY;AACtE,QAAM,aACJ,OAAO,OAAO,eAAe,YAAY,OAAO,SAAS,OAAO,UAAU,IACtE,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC,IAC1C;AACN,SAAO;AAAA,IACL,OACE,YAAY,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,KAAK;AAAA,IACvF,SACE,YAAY,OAAO,OAAO,YAAY,WAAW,OAAO,QAAQ,MAAM,GAAG,GAAI,IAAI,IAAI,KACrF;AAAA,IACF,YACE,OAAO,OAAO,eAAe,WACzB,6BAA6B,OAAO,UAAU,IAC9C;AAAA,IACN,QAAQ;AAAA,MACN,MAAM,QAAQ,OAAO,MAAM,IACvB,OAAO,OAAO,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,EAAE,MAAM,GAAG,CAAC,IACtF,CAAC;AAAA,IACP,EAAE,IAAI,CAAC,UAAU,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IACjD,cAAc,WAAW,MAAM;AAAA,IAC/B,YAAY,YAAY,aAAa;AAAA,IACrC,QACE,YAAY,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,MAAM,GAAG,GAAG,IAAI,IAAI,KAAK;AAAA,EAC3F;AACF;AAEO,SAAS,uBACd,UACA,YAAuC,CAAC,GACtB;AAClB,QAAM,aAAa,UAAU,+BAA+B;AAC5D,QAAM,wBAAwB,8BAA8B,QAAQ;AACpE,SAAO;AAAA,IACL,QAAQ,UAAU,UAAU,IAAI,wBAAwB;AAAA,IACxD,SACE,UAAU,WACV,IAAI;AAAA,MACF,UAAU,qBAAqB;AAAA,MAC/B,UAAU,wBAAwB;AAAA,IACpC;AAAA,IACF,UACE,UAAU,aACT,UAAU,8BAA8B,kBACrC,IAAI,+BAA+B,YAAY,SAAS,sBAAsB,IAC9E,IAAI,wBAAwB;AAAA,MAC1B,GAAG;AAAA,MACH,OAAO,UAAU,0BAA0B;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,IACP,SAAS,UAAU,WAAW,sBAAsB,QAAQ;AAAA,EAC9D;AACF;AAEA,SAAS,sBAAsB,UAAkD;AAC/E,QAAM,WAAW,UAAU,4BAA4B;AACvD,MAAI,aAAa,OAAQ,QAAO;AAChC,MAAI,aAAa,YAAa,QAAO,IAAI,0BAA0B;AACnE,QAAM,kBAAkB,8BAA8B,QAAQ;AAC9D,SAAO,IAAI,uBAAuB;AAAA,IAChC,QAAQ,UAAU,0BAA0B,gBAAgB;AAAA,IAC5D,SAAS,UAAU,2BAA2B,gBAAgB;AAAA,IAC9D,gBAAgB,gBAAgB;AAAA,IAChC,cAAc,gBAAgB;AAAA,IAC9B,OAAO,UAAU,yBAAyB;AAAA,EAC5C,CAAC;AACH;AAEO,SAAS,8BAA8B,UAK5C;AACA,MAAI,CAAC,SAAU,QAAO,CAAC;AACvB,MAAI,SAAS,2BAA2B,SAAS,0BAA0B;AACzE,WAAO;AAAA,MACL,QACE,SAAS,2BAA2B,SAAS,gBAAgB,SAAS;AAAA,MACxE,SACE,SAAS,4BAA4B,SAAS,iBAAiB,SAAS;AAAA,IAC5E;AAAA,EACF;AACA,MAAI,SAAS,mBAAmB,SAAS;AACvC,UAAM,UAAU,SAAS,sBAAsB,uBAAuB,QAAQ;AAC9E,WAAO;AAAA,MACL,QAAQ,SAAS,qBAAqB,SAAS,sBAAsB;AAAA,MACrE;AAAA,MACA,cAAc,wBAAwB,UAAU,OAAO;AAAA,MACvD,gBACE,SAAS,sBAAsB,CAAC,SAAS,oBACrC,EAAE,eAAe,UAAU,SAAS,kBAAkB,GAAG,IACzD;AAAA,IACR;AAAA,EACF;AACA,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB,SAAS,SAAS;AAAA,EACpB;AACF;AAEA,SAAS,uBAAuB,UAA4B;AAC1D,QAAM,WAAW,SAAS,qBAAqB,QAAQ,QAAQ,EAAE;AACjE,MAAI,CAAC,YAAY,CAAC,SAAS,uBAAuB;AAChD,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO,GAAG,QAAQ,uBAAuB,SAAS,qBAAqB;AACzE;AAEA,SAAS,wBACP,UACA,SACoC;AACpC,MAAI,CAAC,SAAS,sBAAuB,QAAO;AAC5C,QAAM,aAAa,QAAQ,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAC3D,MAAI,WAAW,SAAS,YAAY,GAAG;AACrC,WAAO;AAAA,EACT;AACA,SAAO,EAAE,eAAe,SAAS,sBAAsB;AACzD;AAEA,eAAsB,mBACpB,IACA,OACuB;AACvB,SAAO,MAAM;AAAA,IACX;AAAA,IACA,EAAE,WAAW,MAAM,WAAW,aAAa,MAAM,YAAY;AAAA,IAC7D,OAAO,aAAa;AAClB,YAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAc,oBAAa,EAC3B,OAAO;AAAA,QACN,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB,MAAM,MAAM,KAAK,KAAK;AAAA,QACtB,aAAa,MAAM,aAAa,KAAK,KAAK;AAAA,MAC5C,CAAC,EACA,UAAU;AACb,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,gCAAgC;AAC1D,aAAO,gBAAgB,GAAG;AAAA,IAC5B;AAAA,EACF;AACF;AAEA,eAAsB,kBACpB,IACA,aACyB;AACzB,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,OAAO,MAAM,SAChB,OAAO,EACP,KAAY,oBAAa,EACzB,MAAM,GAAU,qBAAc,aAAa,WAAW,CAAC,EACvD,QAAQ,KAAY,qBAAc,SAAS,CAAC;AAC/C,WAAO,KAAK,IAAI,eAAe;AAAA,EACjC,CAAC;AACH;AAOA,eAAsB,qBACpB,IACA,aACA,OAC4B;AAC5B,QAAM,YAAY;AAAA,IAChB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACA,QAAM,aAAa;AAAA,IACjB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACA,QAAM,gBAAgB;AAAA,IACpB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AAEA,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,CAAC,SAAS,IAAI,MAAM,SACvB,OAAO,EAAE,OAAO,mBAA2B,CAAC,EAC5C,KAAY,oBAAa,EACzB,MAAM,GAAU,qBAAc,aAAa,WAAW,CAAC;AAE1D,UAAM,WAAW;AAAA,MACf,GAAU,iBAAU,aAAa,WAAW;AAAA,MAC5C,GAAU,iBAAU,QAAe,qBAAc,EAAE;AAAA,MACnD,GAAG,yBAAyB,MAAM,MAAM;AAAA,IAC1C;AACA,UAAM,WAAW,MAAM,SACpB,OAAO;AAAA,MACN,IAAW,qBAAc;AAAA,MACzB,MAAa,qBAAc;AAAA,MAC3B,sBAAsB,YAA2B,iBAAU,EAAE;AAAA,MAC7D,aAAa,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC9F,eAAe,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAChG,YAAY,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC7F,aAAa,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC9F,iBAAiB,UAA8B,iBAAU,SAAS;AAAA,IACpE,CAAC,EACA,KAAY,oBAAa,EACzB,SAAgB,kBAAW,QAAQ,EACnC,MAAM,GAAU,qBAAc,aAAa,WAAW,CAAC,EACvD,QAAe,qBAAc,IAAW,qBAAc,MAAa,qBAAc,SAAS,EAC1F,QAAQ,KAAY,qBAAc,SAAS,GAAG,IAAW,qBAAc,EAAE,CAAC,EAC1E,MAAM,SAAS;AAElB,UAAM,gBAAgB;AAAA,MACpB,GAAU,iBAAU,aAAa,WAAW;AAAA,MAC5C,GAAG,yBAAyB,MAAM,MAAM;AAAA,IAC1C;AACA,UAAM,CAAC,OAAO,IAAI,MAAM,SACrB,OAAO;AAAA,MACN,sBAAsB,YAA2B,iBAAU,EAAE;AAAA,MAC7D,aAAa,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC9F,eAAe,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAChG,YAAY,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC7F,aAAa,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,MAAM;AAAA,MAC9F,mBAAmB,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MACxG,wBAAwB,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MAC7G,iBAAiB,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MACtG,YAAY,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MACjG,WAAW,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MAChG,eAAe,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MACpG,UAAU,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MAC/F,YAAY,YAA2B,iBAAU,EAAE,mBAA0B,iBAAU,UAAU;AAAA,MACjG,iBAAiB,UAA8B,iBAAU,SAAS;AAAA,IACpE,CAAC,EACA,KAAY,gBAAS,EACrB,MAAM,aAAa;AAEtB,UAAM,mBAAmB;AACzB,UAAM,sBAAsB,qCAA6C,gBAAgB;AACzF,UAAM,YAAY,WAAmB,mBAAmB,KAAK,aAAa;AAC1E,UAAM,qBAAqB,qBAAoC,iBAAU,EAAE;AAC3E,UAAM,YAAY,MAAM,SACrB,OAAO,EAAE,MAAM,WAAW,eAAe,mBAAmB,CAAC,EAC7D,KAAY,gBAAS,EACrB;AAAA,MACC,mCAA0C,iBAAU,MAAM;AAAA,MAC1D;AAAA,IACF,EACC;AAAA,MACC;AAAA,QACE;AAAA,QACA;AAAA,QACA,aAAa,mBAAmB;AAAA,MAClC;AAAA,IACF,EACC,QAAQ,MAAM,EACd,QAAQ,aAAa,UAAU,EAC/B,MAAM,aAAa,CAAC;AAEvB,UAAM,eAAe,8BAA8B,WAAW,CAAC,CAAC;AAChE,UAAM,SAAS,UAAU,MAAM,GAAG,UAAU,EAAE,IAAI,CAAC,WAAW;AAAA,MAC5D,MAAM,MAAM;AAAA,MACZ,eAAe,OAAO,MAAM,aAAa;AAAA,IAC3C,EAAE;AACF,WAAO;AAAA,MACL,WAAW,OAAO,WAAW,SAAS,CAAC;AAAA,MACvC,OAAO,SAAS,IAAI,CAAC,UAAU;AAAA,QAC7B,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX,sBAAsB,OAAO,KAAK,oBAAoB;AAAA,QACtD,cAAc,8BAA8B,IAAI;AAAA,QAChD,iBAAiB,2BAA2B,KAAK,eAAe;AAAA,MAClE,EAAE;AAAA,MACF,sBAAsB,OAAO,SAAS,wBAAwB,CAAC;AAAA,MAC/D;AAAA,MACA,kBAAkB;AAAA,QAChB,eAAe,OAAO,SAAS,qBAAqB,CAAC;AAAA,QACrD,oBAAoB,OAAO,SAAS,0BAA0B,CAAC;AAAA,QAC/D,YAAY,OAAO,SAAS,mBAAmB,CAAC;AAAA,QAChD,OAAO,OAAO,SAAS,cAAc,CAAC;AAAA,QACtC,MAAM,OAAO,SAAS,aAAa,CAAC;AAAA,QACpC,UAAU,OAAO,SAAS,iBAAiB,CAAC;AAAA,QAC5C,KAAK,OAAO,SAAS,YAAY,CAAC;AAAA,QAClC,OAAO,OAAO,SAAS,cAAc,CAAC;AAAA,MACxC;AAAA,MACA,iBAAiB,2BAA2B,SAAS,mBAAmB,IAAI;AAAA,MAC5E;AAAA,MACA,iBAAiB,UAAU,SAAS,OAAO;AAAA,IAC7C;AAAA,EACF,CAAC;AACH;AAEA,SAAS,8BAA8B,OAAe,KAAa,MAAsB;AACvF,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAK;AACxD,UAAM,IAAI,MAAM,GAAG,IAAI,qCAAqC,GAAG,EAAE;AAAA,EACnE;AACA,SAAO;AACT;AAEA,SAAS,8BAA8B,OAKL;AAChC,SAAO;AAAA,IACL,QAAQ,OAAO,MAAM,eAAe,CAAC;AAAA,IACrC,UAAU,OAAO,MAAM,iBAAiB,CAAC;AAAA,IACzC,OAAO,OAAO,MAAM,cAAc,CAAC;AAAA,IACnC,QAAQ,OAAO,MAAM,eAAe,CAAC;AAAA,EACvC;AACF;AAEA,SAAS,2BAA2B,OAA4C;AAC9E,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,iBAAiB,OAAO,MAAM,YAAY,IAAI,IAAI,KAAK,KAAK,EAAE,YAAY;AACnF;AAEA,eAAsB,gBACpB,IACA,aACA,QAC8B;AAC9B,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAO,EACP,KAAY,oBAAa,EACzB;AAAA,MACC,IAAI,GAAU,qBAAc,aAAa,WAAW,GAAG,GAAU,qBAAc,IAAI,MAAM,CAAC;AAAA,IAC5F,EACC,MAAM,CAAC;AACV,WAAO,MAAM,gBAAgB,GAAG,IAAI;AAAA,EACtC,CAAC;AACH;AAQA,eAAsB,kBACpB,IACA,OACuB;AACvB,SAAO,MAAM;AAAA,IACX;AAAA,IACA,EAAE,WAAW,MAAM,WAAW,aAAa,MAAM,YAAY;AAAA,IAC7D,OAAO,aACL,MAAM,SAAS,YAAY,OAAO,OAAO;AACvC,YAAM,cAAc,kBAAyB,qBAAc,IAAI,QAAQ,kBAAkB,YAAY,CAAC;AACtG,YAAM,CAAC,QAAQ,IAAI,MAAM,GACtB,OAAO,EACP,KAAY,oBAAa,EACzB,MAAM,IAAI,GAAU,qBAAc,aAAa,MAAM,WAAW,GAAG,WAAW,CAAC,EAC/E,MAAM,CAAC;AACV,UAAI,SAAU,QAAO,gBAAgB,QAAQ;AAK7C,YAAM,CAAC,QAAQ,IAAI,MAAM,GACtB,OAAc,oBAAa,EAC3B,OAAO;AAAA,QACN,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB,MAAM;AAAA,QACN,aAAa;AAAA,MACf,CAAC,EACA,oBAAoB,EACpB,UAAU;AACb,UAAI,SAAU,QAAO,gBAAgB,QAAQ;AAE7C,YAAM,CAAC,KAAK,IAAI,MAAM,GACnB,OAAO,EACP,KAAY,oBAAa,EACzB,MAAM,IAAI,GAAU,qBAAc,aAAa,MAAM,WAAW,GAAG,WAAW,CAAC,EAC/E,MAAM,CAAC;AACV,UAAI,MAAO,QAAO,gBAAgB,KAAK;AACvC,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D,CAAC;AAAA,EACL;AACF;AAEA,eAAsB,kBACpB,IACA,OAQmB;AACnB,SAAO,MAAM;AAAA,IACX;AAAA,IACA,EAAE,WAAW,MAAM,WAAW,aAAa,MAAM,YAAY;AAAA,IAC7D,OAAO,aAAa;AAClB,YAAM,OAAO,MAAM,gBAAgB,UAAU,MAAM,aAAa,MAAM,MAAM;AAC5E,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,4BAA4B,MAAM,MAAM,EAAE;AACrE,YAAM,OAAO,MAAM,iBAAiB,UAAU,MAAM,aAAa,MAAM,MAAM;AAC7E,UAAI,MAAM,eAAe,aAAa,CAAC,YAAY,MAAM,aAAa,IAAI,GAAG;AAC3E,cAAM,IAAI,MAAM,8CAA8C;AAAA,MAChE;AACA,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,CAAC,QAAQ,IAAI,MAAM,SACtB,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,UAClD,GAAU,iBAAU,QAAQ,MAAM,MAAM;AAAA,UACxC,GAAU,iBAAU,QAAQ,MAAM,MAAM;AAAA,QAC1C;AAAA,MACF,EACC,MAAM,CAAC;AACV,UAAI,UAAU;AACZ,YAAI,CAAC,sBAAsB,UAAU,MAAM,MAAM,GAAG;AAClD,gBAAM,IAAI,MAAM,uBAAuB,SAAS,EAAE,EAAE;AAAA,QACtD;AAMA,cAAM,CAAC,OAAO,IAAI,MAAM,SACrB,OAAc,gBAAS,EACvB,IAAI;AAAA,UACH,OAAO,YAAY,MAAM,KAAK,KAAK,YAAY,MAAM,WAAW,KAAK,SAAS;AAAA,UAC9E,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,UACzE,WAAW,YAAY,MAAM,SAAS,KAAK,SAAS;AAAA,UACpD,kBAAkB,YAAY,MAAM,gBAAgB,KAAK,SAAS;AAAA,UAClE,aAAa,YAAY,MAAM,WAAW,KAAK,SAAS;AAAA,UACxD,cAAc,YAAY,MAAM,YAAY,KAAK,SAAS;AAAA,UAC1D,iBAAiB,kBAAkB,MAAM,eAAe,KAAK,SAAS;AAAA,UACtE,iBAAiB,kBAAkB,MAAM,eAAe,KAAK,SAAS;AAAA,UACtE,eAAe,YAAY,MAAM,aAAa,KAAK,SAAS;AAAA,UAC5D,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,iBAAiB,MAAM,OAAO,EAAE,IAAI,CAAC;AAAA,UAClF,WAAW;AAAA,QACb,CAAC,EACA;AAAA,UACC;AAAA,YACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,YAClD,GAAU,iBAAU,IAAI,SAAS,EAAE;AAAA,YACnC,GAAG,yBAAyB,MAAM,MAAM;AAAA,UAC1C;AAAA,QACF,EACC,UAAU;AACb,eAAO,YAAY,WAAW,QAAQ;AAAA,MACxC;AACA,YAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAc,gBAAS,EACvB,OAAO;AAAA,QACN,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB,QAAQ,MAAM;AAAA,QACd,QAAQ,MAAM;AAAA,QACd,QAAQ;AAAA,QACR,OAAO,YAAY,MAAM,KAAK,KAAK,YAAY,MAAM,WAAW,KAAK,KAAK;AAAA,QAC1E,QAAQ;AAAA,QACR,YAAY,MAAM,cAAc;AAAA,QAChC,WAAW,YAAY,MAAM,SAAS,KAAK;AAAA,QAC3C,kBAAkB,YAAY,MAAM,gBAAgB,KAAK;AAAA,QACzD,aAAa,YAAY,MAAM,WAAW,KAAK;AAAA,QAC/C,cAAc,YAAY,MAAM,YAAY,KAAK;AAAA,QACjD,iBAAiB,kBAAkB,MAAM,eAAe;AAAA,QACxD,iBAAiB,kBAAkB,MAAM,eAAe;AAAA,QACxD,eAAe,YAAY,MAAM,aAAa,KAAK;AAAA,QACnD,SAAS,iBAAiB,MAAM,OAAO;AAAA,QACvC,YAAY,MAAM,cAAc;AAAA,QAChC,aAAa,MAAM,eAAe;AAAA,QAClC,WAAW,MAAM,aAAa;AAAA,QAC9B,gBAAgB,MAAM,kBAAkB;AAAA,QACxC,WAAW;AAAA,MACb,CAAC,EACA,UAAU;AACb,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,2BAA2B;AACrD,aAAO,YAAY,GAAG;AAAA,IACxB;AAAA,EACF;AACF;AAOA,eAAsB,mBACpB,IACA,OAOmB;AACnB,SAAO,MAAM;AAAA,IACX;AAAA,IACA,EAAE,WAAW,MAAM,WAAW,aAAa,MAAM,YAAY;AAAA,IAC7D,OAAO,aAAa;AAClB,YAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,UAClD,GAAU,iBAAU,IAAI,MAAM,UAAU;AAAA,UACxC,GAAG,yBAAyB,MAAM,MAAM;AAAA,QAC1C;AAAA,MACF,EACC,MAAM,CAAC;AACV,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uBAAuB,MAAM,UAAU,EAAE;AACnE,YAAM,aAAc,IAAI,UACpB;AACJ,YAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAI,CAAC,cAAc;AACjB,cAAM,IAAI,MAAM,mDAAmD;AAAA,MACrE;AACA,UAAI,iBAAiB,IAAI,OAAQ,QAAO,YAAY,GAAG;AACvD,YAAM,OAAO,MAAM,gBAAgB,UAAU,MAAM,aAAa,YAAY;AAC5E,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,4BAA4B,YAAY,EAAE;AACrE,YAAM,CAAC,QAAQ,IAAI,MAAM,SACtB,OAAO,EAAE,IAAW,iBAAU,GAAG,CAAC,EAClC,KAAY,gBAAS,EACrB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,UAClD,GAAU,iBAAU,QAAQ,YAAY;AAAA,UACxC,GAAU,iBAAU,QAAQ,IAAI,MAAM;AAAA,QACxC;AAAA,MACF,EACC,MAAM,CAAC;AACV,UAAI,UAAU;AACZ,cAAM,IAAI,MAAM,4DAA4D;AAAA,MAC9E;AACA,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,QAAQ,MAAM,SAAS,YAAY,OAAO,OAAO;AACrD,cAAM,CAAC,OAAO,IAAI,MAAM,GACrB,OAAc,gBAAS,EACvB,IAAI;AAAA,UACH,QAAQ;AAAA,UACR,GAAI,IAAI,mBAAmB,eAAe,IAAI,mBAAmB,YAC7D,EAAE,gBAAgB,aAAa,IAC/B,CAAC;AAAA,UACL,WAAW;AAAA,QACb,CAAC,EACA;AAAA,UACC;AAAA,YACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,YAClD,GAAU,iBAAU,IAAI,MAAM,UAAU;AAAA,YACxC,GAAG,yBAAyB,MAAM,MAAM;AAAA,UAC1C;AAAA,QACF,EACC,UAAU;AACb,YAAI,SAAS;AACX,gBAAM,GACH,OAAc,qBAAc,EAC5B,IAAI,EAAE,QAAQ,aAAa,CAAC,EAC5B;AAAA,YACC;AAAA,cACE,GAAU,sBAAe,aAAa,MAAM,WAAW;AAAA,cACvD,GAAU,sBAAe,YAAY,MAAM,UAAU;AAAA,YACvD;AAAA,UACF;AAAA,QACJ;AACA,eAAO;AAAA,MACT,CAAC;AACD,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,uBAAuB,MAAM,UAAU,EAAE;AACrE,aAAO,YAAY,KAAK;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,eAAsB,uBACpB,IACA,OAOe;AACf,QAAM;AAAA,IACJ;AAAA,IACA,EAAE,WAAW,MAAM,WAAW,aAAa,MAAM,YAAY;AAAA,IAC7D,OAAO,aAAa;AAClB,YAAM,CAAC,QAAQ,IAAI,MAAM,SACtB,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,UAClD,GAAU,iBAAU,IAAI,MAAM,UAAU;AAAA,UACxC,GAAG,yBAAyB,MAAM,MAAM;AAAA,QAC1C;AAAA,MACF,EACC,MAAM,CAAC;AACV,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,uBAAuB,MAAM,UAAU,EAAE;AAAA,MAC3D;AACA,UAAI,SAAS,WAAW,MAAM,QAAQ;AACpC,cAAM,IAAI,MAAM,uBAAuB,MAAM,UAAU,EAAE;AAAA,MAC3D;AACA,YAAM,SACH,OAAc,gBAAS,EACvB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,MAAM,WAAW;AAAA,UAClD,GAAU,iBAAU,IAAI,MAAM,UAAU;AAAA,UACxC,GAAG,yBAAyB,MAAM,MAAM;AAAA,QAC1C;AAAA,MACF;AAAA,IACJ;AAAA,EACF;AACF;AAEA,eAAsB,cACpB,IACA,aACA,QACA,QACqB;AACrB,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,OAAO,MAAM,SAChB,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,MACC;AAAA,QACE,GAAU,iBAAU,aAAa,WAAW;AAAA,QAC5C,GAAU,iBAAU,QAAQ,MAAM;AAAA,QAClC,GAAG,yBAAyB,MAAM;AAAA,MACpC;AAAA,IACF,EACC,QAAQ,IAAW,iBAAU,SAAS,CAAC;AAC1C,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B,CAAC;AACH;AAEA,eAAsB,YACpB,IACA,aACA,YACA,QAC0B;AAC1B,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,MACC;AAAA,QACE,GAAU,iBAAU,aAAa,WAAW;AAAA,QAC5C,GAAU,iBAAU,IAAI,UAAU;AAAA,QAClC,GAAG,yBAAyB,MAAM;AAAA,MACpC;AAAA,IACF,EACC,MAAM,CAAC;AACV,WAAO,MAAM,YAAY,GAAG,IAAI;AAAA,EAClC,CAAC;AACH;AAEA,eAAsB,wBACpB,IACA,aACA,YACA,QACmB;AACnB,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,CAAC,GAAG,IAAI,MAAM,SACjB,OAAc,gBAAS,EACvB,IAAI;AAAA,MACH,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC,EACA;AAAA,MACC;AAAA,QACE,GAAU,iBAAU,aAAa,WAAW;AAAA,QAC5C,GAAU,iBAAU,IAAI,UAAU;AAAA,QAClC,GAAG,yBAAyB,MAAM;AAAA,MACpC;AAAA,IACF,EACC,UAAU;AACb,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,uBAAuB,UAAU,EAAE;AAC7D,WAAO,YAAY,GAAG;AAAA,EACxB,CAAC;AACH;AAEA,eAAsB,iBACpB,IACA,eACA,aACA,YACA,WAA6B,uBAAuB,GACpD,QAA4B,CAAC,GACV;AACnB,QAAM,CAAC,cAAc,IAAI,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,OAAO,aACL,MAAM,SACH,OAAO,EACP,KAAY,gBAAS,EACrB;AAAA,MACC,IAAI,GAAU,iBAAU,aAAa,WAAW,GAAG,GAAU,iBAAU,IAAI,UAAU,CAAC;AAAA,IACxF,EACC,MAAM,CAAC;AAAA,EACd;AACA,MAAI,CAAC,eAAgB,OAAM,IAAI,MAAM,uBAAuB,UAAU,EAAE;AACxE,MAAI,WAAwB;AAC5B,QAAM,OAAO,MAAM,iBAAiB,IAAI,aAAa,SAAS,MAAM;AACpE,QAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AAC1D,UAAM,SACH,OAAc,gBAAS,EACvB,IAAI;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,SAAS,OAAO;AAAA,MACxB,OAAO;AAAA,MACP,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC,EACA;AAAA,MACC,IAAI,GAAU,iBAAU,aAAa,WAAW,GAAG,GAAU,iBAAU,IAAI,UAAU,CAAC;AAAA,IACxF;AAAA,EACJ,CAAC;AACD,MAAI;AACF,UAAM,QAAQ,MAAM,cAAc,aAAa,IAAI;AACnD,UAAM,SAAS,MAAM,SAAS,OAAO,MAAM,OAAO,IAAI;AAKtD,QAAI,SAAS,mBAAmB,WAAW;AACzC,iBAAW,MAAM,sBAAsB,IAAI,UAAU,UAAU,QAAQ,IAAI;AAAA,IAC7E;AACA,UAAM,SAAS,SAAS,QAAQ,MAAM,QAAQ,IAAI;AAClD,UAAM,MAAM,cAAc;AAAA,MACxB,WAAW,SAAS;AAAA,MACpB,aAAa,SAAS;AAAA,MACtB;AAAA,MACA,YAAY,OAAO;AAAA,IACrB,CAAC;AACD,UAAM,aAAa,MAAM,SAAS,SAAS,UAAU,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AACtF,QAAI,WAAW,WAAW,OAAO,QAAQ;AACvC,YAAM,IAAI;AAAA,QACR,+BAA+B,WAAW,MAAM,mBAAmB,OAAO,MAAM;AAAA,MAClF;AAAA,IACF;AACA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,OAAO,aACL,MAAM,SAAS,YAAY,OAAO,OAAO;AACvC,cAAM,GACH,OAAc,qBAAc,EAC5B;AAAA,UACC;AAAA,YACE,GAAU,sBAAe,aAAa,WAAW;AAAA,YACjD,GAAU,sBAAe,YAAY,UAAU;AAAA,UACjD;AAAA,QACF;AACF,YAAI,OAAO,SAAS,GAAG;AACrB,gBAAM,GAAG,OAAc,qBAAc,EAAE;AAAA,YACrC,OAAO,IAAI,CAAC,OAAO,WAAW;AAAA,cAC5B,WAAW,SAAS;AAAA,cACpB,aAAa,SAAS;AAAA,cACtB;AAAA,cACA,QAAQ,SAAS;AAAA,cACjB,QAAQ,KAAK;AAAA,cACb,YAAY;AAAA,cACZ,MAAM,MAAM;AAAA,cACZ,UAAU;AAAA,gBACR,GAAG,MAAM;AAAA,gBACT,eAAe,SAAS;AAAA,gBACxB,YAAY,SAAS;AAAA,gBACrB,WAAW,SAAS;AAAA,gBACpB,kBAAkB,SAAS;AAAA,gBAC3B,aAAa,SAAS;AAAA,gBACtB,cAAc,SAAS;AAAA,gBACvB,iBAAiB,SAAS,iBAAiB,YAAY,KAAK;AAAA,gBAC5D,iBAAiB,SAAS,iBAAiB,YAAY,KAAK;AAAA,gBAC5D,eAAe,SAAS;AAAA,gBACxB,SAAS,SAAS;AAAA,cACpB;AAAA,cACA,WAAW;AAAA,gBACT,WAAW,KAAK,KAAK,CAAC;AAAA,gBACtB,SAAS,SAAS;AAAA,gBAClB,SAAS,SAAS;AAAA,cACpB;AAAA,cACA,gBAAgB,SAAS,SAAS;AAAA,YACpC,EAAE;AAAA,UACJ;AAAA,QACF;AACA,cAAM,GACH,OAAc,gBAAS,EACvB,IAAI;AAAA,UACH,QAAQ;AAAA,UACR,QAAQ,SAAS,OAAO;AAAA,UACxB,YAAY,OAAO;AAAA,UACnB,OAAO;AAAA,UACP,WAAW,oBAAI,KAAK;AAAA,QACtB,CAAC,EACA;AAAA,UACC;AAAA,YACE,GAAU,iBAAU,aAAa,WAAW;AAAA,YAC5C,GAAU,iBAAU,IAAI,UAAU;AAAA,UACpC;AAAA,QACF;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,EACF,SAAS,OAAO;AACd,UAAM,CAAC,MAAM,IAAI,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA,OAAO,aACL,MAAM,SACH,OAAc,gBAAS,EACvB,IAAI;AAAA,QACH,QAAQ;AAAA,QACR,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC5D,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC,EACA;AAAA,QACC,IAAI,GAAU,iBAAU,aAAa,WAAW,GAAG,GAAU,iBAAU,IAAI,UAAU,CAAC;AAAA,MACxF,EACC,UAAU;AAAA,IACjB;AACA,QAAI,CAAC,OAAQ,OAAM;AACnB,WAAO,YAAY,MAAM;AAAA,EAC3B;AAIA,QAAM,UAAU,MAAM,YAAY,IAAI,aAAa,YAAY;AAAA,IAC7D,iBAAiB,SAAS;AAAA,EAC5B,CAAC;AACD,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,wCAAwC,UAAU,EAAE;AAClF,SAAO;AACT;AAIA,eAAe,sBACb,IACA,UACA,UACA,QACA,MACsB;AAItB,MAAI,CAAC,SAAS,SAAS;AACrB,UAAM,CAACA,QAAO,IAAI,MAAM;AAAA,MACtB;AAAA,MACA,SAAS;AAAA,MACT,OAAO,aACL,MAAM,SACH,OAAc,gBAAS,EACvB,IAAI;AAAA,QACH,gBAAgB;AAAA,QAChB,SAAS;AAAA,QACT,QAAQ,CAAC;AAAA,QACT,UAAU;AAAA,QACV,WAAW,oBAAI,KAAK;AAAA,MACtB,CAAC,EACA;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,SAAS,WAAW;AAAA,UACrD,GAAU,iBAAU,IAAI,SAAS,EAAE;AAAA,QACrC;AAAA,MACF,EACC,UAAU;AAAA,IACjB;AACA,WAAOA,YAAW;AAAA,EACpB;AACA,QAAM,QAAQ,MAAM,kBAAkB,IAAI,SAAS,WAAW;AAC9D,QAAM,aAA8C,MACjD,OAAO,CAAC,SAAS,KAAK,OAAO,SAAS,MAAM,EAC5C,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,MAAM,aAAa,KAAK,YAAY,EAAE;AAClF,QAAM,QAA+B;AAAA,IACnC,MAAM,OAAO,KAAK,MAAM,GAAG,iCAAiC;AAAA,IAC5D,UAAU,KAAK;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,OAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACJ,MAAI,UAAyB;AAC7B,MAAI;AACF,cAAU,MAAM,SAAS,QAAQ,OAAO,KAAK;AAC7C,YAAQ,SAAS,QAAQ;AAAA,EAC3B,SAAS,OAAO;AACd,cAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC/D,YAAQ,KAAK,yDAAyD;AAAA,MACpE,aAAa,SAAS;AAAA,MACtB,YAAY,SAAS;AAAA,MACrB,OAAO;AAAA,IACT,CAAC;AACD,cAAU,kBAAkB,OAAO,KAAK,WAAW;AACnD,YAAQ;AAAA,EACV;AACA,QAAM,gBAAgB,WAAW,KAAK,CAAC,SAAS,KAAK,OAAO,QAAQ,YAAY,KAAK;AACrF,MAAI,eAA8B;AAClC,MACE,iBACA,YAAY,QACZ,QAAQ,cAAc,wCACtB;AAGA,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,SAAS;AAAA,MACT,OAAO,aACL,MAAM,SACH,OAAO,EAAE,IAAW,iBAAU,GAAG,CAAC,EAClC,KAAY,gBAAS,EACrB;AAAA,QACC;AAAA,UACE,GAAU,iBAAU,aAAa,SAAS,WAAW;AAAA,UACrD,GAAU,iBAAU,QAAQ,cAAc,EAAE;AAAA,UAC5C,GAAU,iBAAU,QAAQ,SAAS,MAAM;AAAA,QAC7C;AAAA,MACF,EACC,MAAM,CAAC;AAAA,IACd;AACA,QAAI,SAAS,WAAW,GAAG;AACzB,qBAAe,cAAc;AAAA,IAC/B;AAAA,EACF;AACA,QAAM,WAA6B;AAAA,IACjC,iBAAiB,eAAe,MAAM;AAAA,IACtC,mBAAmB,eAAe,QAAQ;AAAA,IAC1C,YAAY,QAAQ;AAAA,IACpB,QAAQ,UAAU,oBAAoB,OAAO,kCAAkC,QAAQ;AAAA,IACvF,eAAe,SAAS;AAAA,IACxB;AAAA,EACF;AACA,QAAM,iBAAyC,UAC3C,WACA,eACE,eACA;AACN,QAAM,CAAC,OAAO,IAAI,MAAM;AAAA,IACtB;AAAA,IACA,SAAS;AAAA,IACT,OAAO,aACL,MAAM,SACH,OAAc,gBAAS,EACvB,IAAI;AAAA,MACH,OAAO,QAAQ,SAAS,SAAS;AAAA,MACjC,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,MAC/D,GAAI,eAAe,EAAE,QAAQ,aAAa,IAAI,CAAC;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC,EACA;AAAA,MACC;AAAA,QACE,GAAU,iBAAU,aAAa,SAAS,WAAW;AAAA,QACrD,GAAU,iBAAU,IAAI,SAAS,EAAE;AAAA,MACrC;AAAA,IACF,EACC,UAAU;AAAA,EACjB;AACA,SAAO,WAAW;AACpB;AAEA,eAAsB,gBACpB,IACA,OACA,WAA+C,uBAAuB,GACrC;AACjC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE;AACxD,QAAM,iBAAiB,SAAS,WAAW,KAAK,IAAI,QAAQ,GAAG,GAAG,IAAI;AACtE,QAAM,OAA4B,CAAC;AACnC,MAAI,SAAS,YAAY,SAAS,UAAU;AAC1C,QAAI;AACF,WAAK,KAAK,GAAI,MAAM,sBAAsB,IAAI,OAAO,gBAAgB,QAAQ,CAAE;AAAA,IACjF,SAAS,OAAO;AACd,UAAI,SAAS,UAAU;AACrB,cAAM;AAAA,MACR;AACA,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,UACE,aAAa,MAAM;AAAA,UACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,aAAa,SAAS,UAAU;AAC3C,SAAK,KAAK,GAAI,MAAM,uBAAuB,IAAI,OAAO,cAAc,CAAE;AAAA,EACxE;AACA,SAAO,wBAAwB,MAAM,IAAI,EAAE,MAAM,GAAG,KAAK;AAC3D;AAEA,eAAe,sBACb,IACA,OACA,OACA,UAC8B;AAC9B,QAAM,iBAAiB,MAAM,SAAS,SAAS,WAAW,MAAM,KAAK;AACrE,oBAAkB,gBAAgB,SAAS,SAAS,YAAY,SAAS,SAAS,KAAK;AACvF,QAAM,WAAW,MAAqB,sBAAe,SAAS,QAAQ,cAAc,cAAc,CAAC;AACnG,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA,MAAM;AAAA,IACN,OAAO,aACL,MAAM,SACH,OAAO;AAAA,MACN,SAAgB,sBAAe;AAAA,MAC/B,YAAmB,sBAAe;AAAA,MAClC,QAAe,sBAAe;AAAA,MAC9B,QAAe,sBAAe;AAAA,MAC9B,OAAc,iBAAU;AAAA,MACxB,MAAa,sBAAe;AAAA,MAC5B,YAAmB,sBAAe;AAAA,MAClC,UAAiB,sBAAe;AAAA,MAChC,YAAmB,iBAAU;AAAA,MAC7B,WAAkB,iBAAU;AAAA,MAC5B,kBAAyB,iBAAU;AAAA,MACnC,aAAoB,iBAAU;AAAA,MAC9B,cAAqB,iBAAU;AAAA,MAC/B,iBAAwB,iBAAU;AAAA,MAClC,iBAAwB,iBAAU;AAAA,MAClC,eAAsB,iBAAU;AAAA,MAChC,SAAgB,iBAAU;AAAA,MAC1B;AAAA,IACF,CAAC,EACA,KAAY,qBAAc,EAC1B,UAAiB,kBAAW,GAAU,sBAAe,YAAmB,iBAAU,EAAE,CAAC,EACrF,MAAM,IAAI,GAAG,yBAAyB,OAAO,SAAS,SAAS,KAAK,CAAC,CAAC,EACtE,QAAQ,QAAQ,EAChB,MAAM,KAAK;AAAA,EAClB;AACA,SAAO,KAAK,IAAI,CAAC,SAAS;AAAA,IACxB,GAAG,iBAAiB,KAAK,MAAM,WAAW;AAAA,IAC1C,aAAa,KAAK,IAAI,OAAO,IAAI,QAAQ;AAAA,IACzC,cAAc;AAAA,EAChB,EAAE;AACJ;AAEA,eAAe,uBACb,IACA,OACA,OAC8B;AAC9B,QAAM,OAAO,uCAAsD,sBAAe,IAAI,gCAAgC,MAAM,KAAK;AACjI,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA,MAAM;AAAA,IACN,OAAO,aACL,MAAM,SACH,OAAO;AAAA,MACN,SAAgB,sBAAe;AAAA,MAC/B,YAAmB,sBAAe;AAAA,MAClC,QAAe,sBAAe;AAAA,MAC9B,QAAe,sBAAe;AAAA,MAC9B,OAAc,iBAAU;AAAA,MACxB,MAAa,sBAAe;AAAA,MAC5B,YAAmB,sBAAe;AAAA,MAClC,UAAiB,sBAAe;AAAA,MAChC,YAAmB,iBAAU;AAAA,MAC7B,WAAkB,iBAAU;AAAA,MAC5B,kBAAyB,iBAAU;AAAA,MACnC,aAAoB,iBAAU;AAAA,MAC9B,cAAqB,iBAAU;AAAA,MAC/B,iBAAwB,iBAAU;AAAA,MAClC,iBAAwB,iBAAU;AAAA,MAClC,eAAsB,iBAAU;AAAA,MAChC,SAAgB,iBAAU;AAAA,MAC1B;AAAA,IACF,CAAC,EACA,KAAY,qBAAc,EAC1B,UAAiB,kBAAW,GAAU,sBAAe,YAAmB,iBAAU,EAAE,CAAC,EACrF;AAAA,MACC;AAAA,QACE,GAAG,yBAAyB,KAAK;AAAA,QACjC,4BAAmC,sBAAe,IAAI,kCAAkC,MAAM,KAAK;AAAA,MACrG;AAAA,IACF,EACC,QAAQ,KAAK,IAAI,CAAC,EAClB,MAAM,KAAK;AAAA,EAClB;AACA,SAAO,KAAK,IAAI,CAAC,SAAS;AAAA,IACxB,GAAG,iBAAiB,KAAK,MAAM,WAAW;AAAA,IAC1C,aAAa;AAAA,IACb,cAAc,sBAAsB,OAAO,IAAI,IAAI,CAAC;AAAA,EACtD,EAAE;AACJ;AAEA,eAAsB,iBACpB,IACA,aACA,SACA,QACsC;AACtC,QAAM,CAAC,GAAG,IAAI,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA,OAAO,aACL,MAAM,SACH,OAAO;AAAA,MACN,SAAgB,sBAAe;AAAA,MAC/B,YAAmB,sBAAe;AAAA,MAClC,QAAe,sBAAe;AAAA,MAC9B,QAAe,sBAAe;AAAA,MAC9B,OAAc,iBAAU;AAAA,MACxB,MAAa,sBAAe;AAAA,MAC5B,YAAmB,sBAAe;AAAA,MAClC,UAAiB,sBAAe;AAAA,MAChC,YAAmB,iBAAU;AAAA,MAC7B,WAAkB,iBAAU;AAAA,MAC5B,kBAAyB,iBAAU;AAAA,MACnC,aAAoB,iBAAU;AAAA,MAC9B,cAAqB,iBAAU;AAAA,MAC/B,iBAAwB,iBAAU;AAAA,MAClC,iBAAwB,iBAAU;AAAA,MAClC,eAAsB,iBAAU;AAAA,MAChC,SAAgB,iBAAU;AAAA,IAC5B,CAAC,EACA,KAAY,qBAAc,EAC1B,UAAiB,kBAAW,GAAU,sBAAe,YAAmB,iBAAU,EAAE,CAAC,EACrF;AAAA,MACC;AAAA,QACE,GAAU,sBAAe,aAAa,WAAW;AAAA,QACjD,GAAU,sBAAe,IAAI,OAAO;AAAA,QACpC,GAAU,iBAAU,QAAQ,OAAO;AAAA,QACnC,GAAG,yBAAyB,MAAM;AAAA,MACpC;AAAA,IACF,EACC,MAAM,CAAC;AAAA,EACd;AACA,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO;AAAA,IACL,GAAG,iBAAiB,KAAK,WAAW;AAAA,IACpC,OAAO;AAAA,IACP,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,EAChB;AACF;AAeA,SAAS,yBAAyB,QAAiD;AACjF,MAAI,QAAQ,WAAW;AACrB,UAAMC,UAAS,OAAO;AACtB,UAAM,aAAaA,UACf,GAAG,GAAU,iBAAU,YAAY,WAAW,GAAG,GAAU,iBAAU,WAAWA,OAAM,CAAC,IACvF,GAAU,iBAAU,YAAY,WAAW;AAC/C,WAAO,CAAC,GAAU,iBAAU,aAAa,IAAI,GAAG,GAAI,aAAa,CAAC,UAAU,IAAI,CAAC,CAAE;AAAA,EACrF;AACA,QAAM,SAAS,QAAQ;AACvB,MAAI,QAAQ;AACV,UAAM,YAAY;AAAA,MAChB,GAAU,iBAAU,YAAY,WAAW;AAAA,MAC3C,GAAU,iBAAU,WAAW,MAAM;AAAA,IACvC;AACA,WAAO,YAAY,CAAC,SAAS,IAAI,CAAC;AAAA,EACpC;AACA,SAAO,CAAC,GAAU,iBAAU,YAAY,WAAW,CAAC;AACtD;AAEA,SAAS,sBACP,UACA,QACS;AACT,MAAI,QAAQ,WAAW;AACrB,WACE,SAAS,gBACR,SAAS,eAAe,aACtB,CAAC,CAAC,OAAO,mBAAmB,SAAS,cAAc,OAAO;AAAA,EAEjE;AACA,SAAO,gBAAgB,UAAU,QAAQ,eAAe;AAC1D;AAGO,SAAS,gBACd,UACA,iBACS;AACT,SACE,SAAS,eAAe,aACvB,CAAC,CAAC,mBAAmB,SAAS,cAAc;AAEjD;AAQA,SAAS,yBAAyB,OAA4B,gBAAgC;AAC5F,QAAM,aAAoB;AAAA,IACxB,GAAU,iBAAU,QAAQ,OAAO;AAAA,IACnC,GAAU,sBAAe,aAAa,MAAM,WAAW;AAAA,IACvD,GAAG,yBAAyB,MAAM,MAAM;AAAA,EAC1C;AACA,MAAI,gBAAgB;AAClB,eAAW,KAAK,GAAU,sBAAe,gBAAgB,cAAc,CAAC;AAAA,EAC1E;AACA,MAAI,MAAM,SAAS,QAAQ;AACzB,eAAW,KAAK,QAAe,sBAAe,QAAQ,MAAM,OAAO,CAAC;AAAA,EACtE;AACA,MAAI,MAAM,aAAa,QAAQ;AAC7B,eAAW,KAAK,QAAe,iBAAU,YAAY,MAAM,WAAW,CAAC;AAAA,EACzE;AACA,QAAM,UAAU,iBAAiB,MAAM,OAAO;AAC9C,MAAI,QAAQ,SAAS,GAAG;AACtB,eAAW,KAAK,MAAa,iBAAU,OAAO,OAAO,KAAK,UAAU,OAAO,CAAC,SAAS;AAAA,EACvF;AACA,SAAO;AACT;AAEA,SAAS,iBACP,KAmBA,aACe;AACf,SAAO;AAAA,IACL,SAAS,IAAI;AAAA,IACb;AAAA,IACA,YAAY,IAAI;AAAA,IAChB,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV,YAAY,IAAI;AAAA,IAChB,UAAU,IAAI;AAAA,IACd,YAAY,6BAA6B,IAAI,UAAU;AAAA,IACvD,WAAW,IAAI;AAAA,IACf,kBAAkB,IAAI;AAAA,IACtB,aAAa,IAAI;AAAA,IACjB,cAAc,IAAI;AAAA,IAClB,iBAAiB,IAAI,iBAAiB,YAAY,KAAK;AAAA,IACvD,iBAAiB,IAAI,iBAAiB,YAAY,KAAK;AAAA,IACvD,eAAe,IAAI;AAAA,IACnB,SAAS,iBAAiB,IAAI,OAAO;AAAA,EACvC;AACF;AAEA,SAAS,wBACP,MACA,MACwB;AACxB,QAAM,UAAU,oBAAI,IAA+B;AACnD,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,QAAQ,IAAI,IAAI,OAAO;AACxC,QAAI,CAAC,UAAU;AACb,cAAQ,IAAI,IAAI,SAAS,GAAG;AAC5B;AAAA,IACF;AACA,YAAQ,IAAI,IAAI,SAAS;AAAA,MACvB,GAAG;AAAA,MACH,aAAa,KAAK,IAAI,SAAS,eAAe,GAAG,IAAI,eAAe,CAAC,KAAK;AAAA,MAC1E,cAAc,KAAK,IAAI,SAAS,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,KAAK;AAAA,IAC/E,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,EACxB,IAAI,CAAC,QAAQ;AACZ,UAAM,cAAc,IAAI;AACxB,UAAM,eAAe,IAAI;AACzB,UAAM,YACJ,gBAAgB,QAAQ,iBAAiB,OACrC,WACA,gBAAgB,OACd,WACA;AACR,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO,oBAAoB,MAAM,aAAa,cAAc,SAAS;AAAA,MACrE;AAAA,IACF;AAAA,EACF,CAAC,EACA;AAAA,IACC,CAAC,MAAM,UACL,MAAM,QAAQ,KAAK,UAClB,MAAM,eAAe,MAAM,KAAK,eAAe,OAC/C,MAAM,gBAAgB,MAAM,KAAK,gBAAgB,MAClD,KAAK,aAAa,MAAM;AAAA,EAC5B;AACJ;AAEA,SAAS,oBACP,MACA,aACA,cACA,WACQ;AACR,QAAM,SAAS,eAAe;AAC9B,QAAM,UAAU,gBAAgB;AAChC,MAAI,SAAS,SAAU,QAAO,WAAW,MAAM;AAC/C,MAAI,SAAS,UAAW,QAAO,WAAW,OAAO;AACjD,SAAO;AAAA,IACL,KAAK,IAAI,GAAG,OAAO,SAAS,OAAO,WAAW,cAAc,WAAW,MAAM,EAAE;AAAA,EACjF;AACF;AAEA,SAAS,sBAAsB,MAAsB;AACnD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,GAAG;AACvC,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,OAAO;AACxB;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,OAAO,MAAM,QAAQ,CAAC,CAAC;AAChC;AAEA,eAAsB,mBACpB,OACA,MACA,SAAyB,IAAI,wBAAwB,GAC5B;AACzB,SAAO,MAAM,OAAO,MAAM,OAAO,IAAI;AACvC;AAEO,SAAS,UACd,MACA,WAAW,6BACX,eAAe,gCACL;AACV,MAAI,gBAAgB,UAAU;AAC5B,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,QAAM,aAAa,KAChB,QAAQ,SAAS,IAAI,EACrB,QAAQ,WAAW,GAAG,EACtB,KAAK;AACR,MAAI,CAAC,WAAY,QAAO,CAAC;AACzB,QAAM,aAAa,WAChB,MAAM,QAAQ,EACd,IAAI,CAAC,SAAS,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAK,CAAC,EAC9C,OAAO,OAAO;AACjB,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAU;AACd,aAAW,aAAa,WAAW,SAAS,IAAI,aAAa,CAAC,WAAW,QAAQ,QAAQ,GAAG,CAAC,GAAG;AAC9F,eAAW,QAAQ,mBAAmB,WAAW,QAAQ,GAAG;AAC1D,UAAI,CAAC,SAAS;AACZ,kBAAU;AAAA,MACZ,WAAW,QAAQ,SAAS,IAAI,KAAK,UAAU,UAAU;AACvD,kBAAU,GAAG,OAAO,IAAI,IAAI;AAAA,MAC9B,OAAO;AACL,eAAO,KAAK,OAAO;AACnB,kBAAU,YAAY,SAAS,cAAc,MAAM,QAAQ;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACA,MAAI,QAAS,QAAO,KAAK,OAAO;AAChC,SAAO,OAAO,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO;AAC3D;AAEO,SAAS,uBACd,MACA,aAAa,uCACH;AACV,QAAM,SAAS,IAAI,MAAM,UAAU,EAAE,KAAK,CAAC;AAC3C,QAAM,SAAS,KAAK,YAAY,EAAE,MAAM,mBAAmB,KAAK,CAAC;AACjE,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO;AACX,eAAW,QAAQ,OAAO;AACxB,cAAQ,KAAK,YAAY,CAAC,KAAK;AAC/B,aAAO,KAAK,KAAK,MAAM,QAAQ;AAAA,IACjC;AACA,WAAO,KAAK,IAAI,IAAI,IAAI,UAAU,KAAK;AAAA,EACzC;AACA,QAAM,OAAO,KAAK,MAAM,GAAG,MAAM,KAAK;AACtC,SAAO,OAAO,IAAI,CAAC,UAAU,QAAQ,QAAQ,MAAM,QAAQ,CAAC,CAAC,CAAC;AAChE;AAEA,eAAe,iBACb,IACA,aACA,QACoB;AACpB,QAAM,OAAO,MAAM,YAAY,IAAI,aAAa,MAAM;AACtD,MAAI,KAAK,WAAW,SAAS;AAC3B,UAAM,IAAI,MAAM,QAAQ,MAAM,OAAO,KAAK,MAAM,EAAE;AAAA,EACpD;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAkB,YAAoB,OAAyB;AACxF,MAAI,OAAO,WAAW,YAAY;AAChC,UAAM,IAAI;AAAA,MACR,mBAAmB,KAAK,aAAa,OAAO,MAAM,yBAAyB,UAAU;AAAA,IACvF;AAAA,EACF;AACA,MAAI,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,SAAS,KAAK,CAAC,GAAG;AACnD,UAAM,IAAI,MAAM,mBAAmB,KAAK,6BAA6B;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAA0B;AAC5C,QAAM,cAAc,KAAK,YAAY,YAAY;AACjD,QAAM,WAAW,KAAK,SAAS,YAAY;AAC3C,SACE,YAAY,WAAW,OAAO,KAC9B,gBAAgB,sBAChB,gBAAgB,qBAChB,gBAAgB,wBAChB,SAAS,SAAS,KAAK,KACvB,SAAS,SAAS,WAAW,KAC7B,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,MAAM,KACxB,SAAS,SAAS,MAAM,KACxB,SAAS,SAAS,MAAM,KACxB,SAAS,SAAS,MAAM;AAE5B;AAEA,SAAS,mBAAmB,MAAc,UAA4B;AACpE,MAAI,KAAK,UAAU,SAAU,QAAO,CAAC,IAAI;AACzC,QAAM,MAAgB,CAAC;AACvB,MAAI,YAAY;AAChB,SAAO,UAAU,SAAS,UAAU;AAClC,UAAM,SAAS,UAAU,MAAM,GAAG,WAAW,CAAC;AAC9C,UAAM,UAAU,KAAK;AAAA,MACnB,OAAO,YAAY,IAAI;AAAA,MACvB,OAAO,YAAY,IAAI;AAAA,MACvB,OAAO,YAAY,IAAI;AAAA,MACvB,OAAO,YAAY,IAAI;AAAA,MACvB,OAAO,YAAY,IAAI;AAAA,MACvB,OAAO,YAAY,GAAG;AAAA,IACxB;AACA,UAAM,MAAM,UAAU,KAAK,MAAM,WAAW,GAAG,IAAI,UAAU,IAAI;AACjE,QAAI,KAAK,UAAU,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC;AACvC,gBAAY,UAAU,MAAM,GAAG,EAAE,KAAK;AAAA,EACxC;AACA,MAAI,UAAW,KAAI,KAAK,SAAS;AACjC,SAAO;AACT;AAEA,SAAS,YACP,UACA,cACA,MACA,UACQ;AACR,MAAI,gBAAgB,EAAG,QAAO;AAC9B,QAAM,UAAU,SACb,MAAM,KAAK,IAAI,GAAG,SAAS,SAAS,YAAY,CAAC,EACjD,QAAQ,WAAW,EAAE,EACrB,KAAK;AACR,QAAM,YAAY,UAAU,GAAG,OAAO,IAAI,IAAI,KAAK;AACnD,SAAO,UAAU,UAAU,WAAW,YAAY;AACpD;AAEA,SAAS,YAAY,OAAsD;AACzE,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,UAAU,UAAU;AAC7B;AAEA,SAAS,iBAAiB,QAA+C;AACvE,SAAO,CAAC,GAAG,IAAI,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AACjF;AAEA,SAAS,kBAAkB,OAAwC;AACjE,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI,KAAK,OAAO;AAC7B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChC,UAAM,IAAI,MAAM,6BAA6B,OAAO,EAAE;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,6BAA6B,OAAoC;AACxE,UAAQ,OAAO;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,cAAc,QAA0B;AAC/C,SAAO,IAAI,OAAO,KAAK,GAAG,CAAC;AAC7B;AAEA,SAAS,gBAAgB,KAA6D;AACpF,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,aAAa,IAAI;AAAA,IACjB,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI,UAAU,YAAY;AAAA,IACrC,WAAW,IAAI,UAAU,YAAY;AAAA,EACvC;AACF;AAEA,SAAS,YAAY,KAAqD;AACxE,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB,OAAO,IAAI;AAAA,IACX,YAAY,6BAA6B,IAAI,UAAU;AAAA,IACvD,WAAW,IAAI;AAAA,IACf,kBAAkB,IAAI;AAAA,IACtB,aAAa,IAAI;AAAA,IACjB,cAAc,IAAI;AAAA,IAClB,iBAAiB,IAAI,iBAAiB,YAAY,KAAK;AAAA,IACvD,iBAAiB,IAAI,iBAAiB,YAAY,KAAK;AAAA,IACvD,eAAe,IAAI;AAAA,IACnB,SAAS,iBAAiB,IAAI,OAAO;AAAA,IACrC,YAAY,4BAA4B,IAAI,UAAU;AAAA,IACtD,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,SAAS,IAAI;AAAA,IACb,QAAQ,iBAAiB,IAAI,MAAM;AAAA,IACnC,gBAAgB,gCAAgC,IAAI,cAAc;AAAA,IAClE,UAAW,IAAI,YAAwC;AAAA,IACvD,WAAW,IAAI,UAAU,YAAY;AAAA,IACrC,WAAW,IAAI,UAAU,YAAY;AAAA,EACvC;AACF;AAEA,SAAS,4BAA4B,OAAmC;AACtE,SAAO,UAAU,YAAY,YAAY;AAC3C;AAEA,SAAS,gCAAgC,OAAuC;AAC9E,UAAQ,OAAO;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;","names":["updated","viewer"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/documents",
3
- "version": "0.2.45",
3
+ "version": "0.2.61",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,16 +26,16 @@
26
26
  "provenance": true
27
27
  },
28
28
  "scripts": {
29
- "build": "tsup",
30
- "typecheck": "tsgo --noEmit",
29
+ "build": "bun ../../scripts/build-typescript-package.ts",
30
+ "typecheck": "tsc --noEmit",
31
31
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
32
32
  },
33
33
  "dependencies": {
34
34
  "@llamaindex/liteparse": "^1.5.3",
35
- "@opengeni/config": "^0.7.13",
36
- "@opengeni/contracts": "^0.23.0",
37
- "@opengeni/db": "^0.14.3",
38
- "@opengeni/storage": "^0.2.37",
35
+ "@opengeni/config": "^0.8.1",
36
+ "@opengeni/contracts": "^0.27.0",
37
+ "@opengeni/db": "^0.17.1",
38
+ "@opengeni/storage": "^0.2.48",
39
39
  "drizzle-orm": "^0.45.2",
40
40
  "openai": "6.36.0"
41
41
  }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  DocumentCurationStatus,
9
9
  DocumentSearchMode,
10
10
  DocumentSearchResult,
11
+ DocumentStatus,
11
12
  DocumentVisibility,
12
13
  FileAsset,
13
14
  KnowledgeSourceKind,
@@ -117,6 +118,37 @@ export type DocumentAccessFilter = {
117
118
  agentOnly?: boolean | undefined;
118
119
  };
119
120
 
121
+ export type DocumentInventoryStatusCounts = Record<DocumentStatus, number>;
122
+ export type DocumentInventorySourceKindCounts = Record<KnowledgeSourceKind, number>;
123
+
124
+ export type DocumentInventory = {
125
+ baseCount: number;
126
+ bases: Array<{
127
+ id: string;
128
+ name: string;
129
+ visibleDocumentCount: number;
130
+ statusCounts: DocumentInventoryStatusCounts;
131
+ latestUpdatedAt: string | null;
132
+ }>;
133
+ visibleDocumentCount: number;
134
+ statusCounts: DocumentInventoryStatusCounts;
135
+ sourceKindCounts: DocumentInventorySourceKindCounts;
136
+ latestUpdatedAt: string | null;
137
+ topics: Array<{ name: string; documentCount: number }>;
138
+ topicsTruncated: boolean;
139
+ };
140
+
141
+ export type DocumentInventoryInput = {
142
+ baseLimit: number;
143
+ topicLimit: number;
144
+ topicMaxChars: number;
145
+ access?: DocumentAccessFilter | undefined;
146
+ };
147
+
148
+ const DOCUMENT_INVENTORY_MAX_BASE_LIMIT = 100;
149
+ const DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT = 100;
150
+ const DOCUMENT_INVENTORY_MAX_TOPIC_CHARS = 256;
151
+
120
152
  export type DocumentSearchInput = {
121
153
  workspaceId: string;
122
154
  query: string;
@@ -579,6 +611,166 @@ export async function listDocumentBases(
579
611
  });
580
612
  }
581
613
 
614
+ /**
615
+ * Return a bounded structural inventory without loading document records.
616
+ * Document predicates are applied independently to every aggregate so private
617
+ * counts and topic metadata remain scoped to the initiating grant subject.
618
+ */
619
+ export async function getDocumentInventory(
620
+ db: Database,
621
+ workspaceId: string,
622
+ input: DocumentInventoryInput,
623
+ ): Promise<DocumentInventory> {
624
+ const baseLimit = requireDocumentInventoryLimit(
625
+ input.baseLimit,
626
+ DOCUMENT_INVENTORY_MAX_BASE_LIMIT,
627
+ "baseLimit",
628
+ );
629
+ const topicLimit = requireDocumentInventoryLimit(
630
+ input.topicLimit,
631
+ DOCUMENT_INVENTORY_MAX_TOPIC_LIMIT,
632
+ "topicLimit",
633
+ );
634
+ const topicMaxChars = requireDocumentInventoryLimit(
635
+ input.topicMaxChars,
636
+ DOCUMENT_INVENTORY_MAX_TOPIC_CHARS,
637
+ "topicMaxChars",
638
+ );
639
+
640
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
641
+ const [baseTotal] = await scopedDb
642
+ .select({ count: sql<number>`count(*)::int` })
643
+ .from(schema.documentBases)
644
+ .where(eq(schema.documentBases.workspaceId, workspaceId));
645
+
646
+ const baseJoin = and(
647
+ eq(schema.documents.workspaceId, workspaceId),
648
+ eq(schema.documents.baseId, schema.documentBases.id),
649
+ ...documentAccessConditions(input.access),
650
+ );
651
+ const baseRows = await scopedDb
652
+ .select({
653
+ id: schema.documentBases.id,
654
+ name: schema.documentBases.name,
655
+ visibleDocumentCount: sql<number>`count(${schema.documents.id})::int`,
656
+ queuedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,
657
+ indexingCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,
658
+ readyCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,
659
+ failedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,
660
+ latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,
661
+ })
662
+ .from(schema.documentBases)
663
+ .leftJoin(schema.documents, baseJoin)
664
+ .where(eq(schema.documentBases.workspaceId, workspaceId))
665
+ .groupBy(schema.documentBases.id, schema.documentBases.name, schema.documentBases.createdAt)
666
+ .orderBy(desc(schema.documentBases.createdAt), asc(schema.documentBases.id))
667
+ .limit(baseLimit);
668
+
669
+ const documentWhere = and(
670
+ eq(schema.documents.workspaceId, workspaceId),
671
+ ...documentAccessConditions(input.access),
672
+ );
673
+ const [summary] = await scopedDb
674
+ .select({
675
+ visibleDocumentCount: sql<number>`count(${schema.documents.id})::int`,
676
+ queuedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'queued')::int`,
677
+ indexingCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'indexing')::int`,
678
+ readyCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'ready')::int`,
679
+ failedCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.status} = 'failed')::int`,
680
+ manualUploadCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'manual_upload')::int`,
681
+ meetingTranscriptCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'meeting_transcript')::int`,
682
+ repositoryCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'repository')::int`,
683
+ emailCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'email')::int`,
684
+ chatCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'chat')::int`,
685
+ documentCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'document')::int`,
686
+ webCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'web')::int`,
687
+ otherCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'other')::int`,
688
+ latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,
689
+ })
690
+ .from(schema.documents)
691
+ .where(documentWhere);
692
+
693
+ const topicStringValue = sql<string>`topic.value #>> '{}'`;
694
+ const topicNormalizedName = sql<string>`btrim(regexp_replace(normalize(${topicStringValue}, NFKC), '[[:space:]]+', ' ', 'g'))`;
695
+ const topicName = sql<string>`left(${topicNormalizedName}, ${topicMaxChars})`;
696
+ const topicDocumentCount = sql<number>`count(distinct ${schema.documents.id})::int`;
697
+ const topicRows = await scopedDb
698
+ .select({ name: topicName, documentCount: topicDocumentCount })
699
+ .from(schema.documents)
700
+ .innerJoin(
701
+ sql`lateral jsonb_array_elements(${schema.documents.topics}) as topic(value)`,
702
+ sql`true`,
703
+ )
704
+ .where(
705
+ and(
706
+ documentWhere,
707
+ sql`jsonb_typeof(topic.value) = 'string'`,
708
+ sql`nullif(${topicNormalizedName}, '') is not null`,
709
+ ),
710
+ )
711
+ .groupBy(sql`1`)
712
+ .orderBy(sql`2 desc`, sql`1 asc`)
713
+ .limit(topicLimit + 1);
714
+
715
+ const statusCounts = documentInventoryStatusCounts(summary ?? {});
716
+ const topics = topicRows.slice(0, topicLimit).map((topic) => ({
717
+ name: topic.name,
718
+ documentCount: Number(topic.documentCount),
719
+ }));
720
+ return {
721
+ baseCount: Number(baseTotal?.count ?? 0),
722
+ bases: baseRows.map((base) => ({
723
+ id: base.id,
724
+ name: base.name,
725
+ visibleDocumentCount: Number(base.visibleDocumentCount),
726
+ statusCounts: documentInventoryStatusCounts(base),
727
+ latestUpdatedAt: documentInventoryTimestamp(base.latestUpdatedAt),
728
+ })),
729
+ visibleDocumentCount: Number(summary?.visibleDocumentCount ?? 0),
730
+ statusCounts,
731
+ sourceKindCounts: {
732
+ manual_upload: Number(summary?.manualUploadCount ?? 0),
733
+ meeting_transcript: Number(summary?.meetingTranscriptCount ?? 0),
734
+ repository: Number(summary?.repositoryCount ?? 0),
735
+ email: Number(summary?.emailCount ?? 0),
736
+ chat: Number(summary?.chatCount ?? 0),
737
+ document: Number(summary?.documentCount ?? 0),
738
+ web: Number(summary?.webCount ?? 0),
739
+ other: Number(summary?.otherCount ?? 0),
740
+ },
741
+ latestUpdatedAt: documentInventoryTimestamp(summary?.latestUpdatedAt ?? null),
742
+ topics,
743
+ topicsTruncated: topicRows.length > topics.length,
744
+ };
745
+ });
746
+ }
747
+
748
+ function requireDocumentInventoryLimit(value: number, max: number, name: string): number {
749
+ if (!Number.isInteger(value) || value < 1 || value > max) {
750
+ throw new Error(`${name} must be an integer between 1 and ${max}`);
751
+ }
752
+ return value;
753
+ }
754
+
755
+ function documentInventoryStatusCounts(input: {
756
+ queuedCount?: number | null | undefined;
757
+ indexingCount?: number | null | undefined;
758
+ readyCount?: number | null | undefined;
759
+ failedCount?: number | null | undefined;
760
+ }): DocumentInventoryStatusCounts {
761
+ return {
762
+ queued: Number(input.queuedCount ?? 0),
763
+ indexing: Number(input.indexingCount ?? 0),
764
+ ready: Number(input.readyCount ?? 0),
765
+ failed: Number(input.failedCount ?? 0),
766
+ };
767
+ }
768
+
769
+ function documentInventoryTimestamp(value: Date | string | null): string | null {
770
+ if (!value) return null;
771
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
772
+ }
773
+
582
774
  export async function getDocumentBase(
583
775
  db: Database,
584
776
  workspaceId: string,