@opengeni/documents 0.5.39 → 0.5.41
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 +9 -1
- package/dist/index.js +106 -29
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +133 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/documents",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.41",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@llamaindex/liteparse": "^1.5.3",
|
|
43
|
-
"@opengeni/config": "^0.16.
|
|
44
|
-
"@opengeni/contracts": "^0.
|
|
45
|
-
"@opengeni/db": "^0.
|
|
46
|
-
"@opengeni/storage": "^0.2.
|
|
43
|
+
"@opengeni/config": "^0.16.4",
|
|
44
|
+
"@opengeni/contracts": "^1.0.1",
|
|
45
|
+
"@opengeni/db": "^1.0.1",
|
|
46
|
+
"@opengeni/storage": "^0.2.96",
|
|
47
47
|
"drizzle-orm": "^0.45.2",
|
|
48
48
|
"openai": "6.47.0"
|
|
49
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
KnowledgeProviderCitation,
|
|
4
|
+
type KnowledgeProviderCitation as KnowledgeProviderCitationValue,
|
|
5
|
+
type AddDocumentRequest,
|
|
4
6
|
CreateDocumentBaseRequest,
|
|
5
7
|
Document,
|
|
6
8
|
DocumentAuthorityKind,
|
|
@@ -20,7 +22,7 @@ import type {
|
|
|
20
22
|
ListIndexedDocumentsResponse,
|
|
21
23
|
} from "@opengeni/contracts";
|
|
22
24
|
import {
|
|
23
|
-
|
|
25
|
+
getFilesForSubject,
|
|
24
26
|
rlsContextForWorkspace,
|
|
25
27
|
setSubjectRlsContext,
|
|
26
28
|
withRlsContext,
|
|
@@ -967,7 +969,18 @@ export async function addDocumentToBase(
|
|
|
967
969
|
if (viewerSubjectId) await setSubjectRlsContext(scopedDb, viewerSubjectId);
|
|
968
970
|
const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);
|
|
969
971
|
if (!base) throw new Error(`Document base not found: ${input.baseId}`);
|
|
970
|
-
const
|
|
972
|
+
const initiatingSubjectId = cleanString(input.initiatingSubjectId ?? null);
|
|
973
|
+
const createdBy = cleanString(input.createdBy ?? null);
|
|
974
|
+
if (initiatingSubjectId && createdBy && initiatingSubjectId !== createdBy) {
|
|
975
|
+
throw new Error("document file authority must match the exact initiating subject");
|
|
976
|
+
}
|
|
977
|
+
const fileAuthoritySubjectId = initiatingSubjectId ?? createdBy ?? null;
|
|
978
|
+
const file = await requireReadyFile(scopedDb, {
|
|
979
|
+
accountId: input.accountId,
|
|
980
|
+
workspaceId: input.workspaceId,
|
|
981
|
+
subjectId: fileAuthoritySubjectId,
|
|
982
|
+
fileId: input.fileId,
|
|
983
|
+
});
|
|
971
984
|
const knowledgeSourceIdentity = cleanString(input.knowledgeSourceIdentity ?? null);
|
|
972
985
|
if (knowledgeSourceIdentity && knowledgeSourceIdentity.length > 512) {
|
|
973
986
|
throw new Error("knowledge source document identity exceeds 512 characters");
|
|
@@ -1054,7 +1067,7 @@ export async function addDocumentToBase(
|
|
|
1054
1067
|
authoritySubjectId: authority.subjectId,
|
|
1055
1068
|
visibility: authority.kind === "personal" ? "private" : "workspace",
|
|
1056
1069
|
agentAccess: input.agentAccess ?? true,
|
|
1057
|
-
createdBy:
|
|
1070
|
+
createdBy: fileAuthoritySubjectId,
|
|
1058
1071
|
curationStatus: input.curationStatus ?? "none",
|
|
1059
1072
|
updatedAt: now,
|
|
1060
1073
|
})
|
|
@@ -1403,6 +1416,30 @@ export async function getDocument(
|
|
|
1403
1416
|
});
|
|
1404
1417
|
}
|
|
1405
1418
|
|
|
1419
|
+
/**
|
|
1420
|
+
* Internal ingestion-only read used after the worker has independently resolved
|
|
1421
|
+
* and fenced the immutable document authority tuple. It deliberately does not
|
|
1422
|
+
* apply provider retrieval authorization because a new Drive document must be
|
|
1423
|
+
* indexed before its first ACL evidence can be attached. User, API, MCP, and
|
|
1424
|
+
* agent reads must use getDocument/effective retrieval instead.
|
|
1425
|
+
*/
|
|
1426
|
+
export async function getDocumentForIndexing(
|
|
1427
|
+
db: Database,
|
|
1428
|
+
workspaceId: string,
|
|
1429
|
+
documentId: string,
|
|
1430
|
+
): Promise<Document | null> {
|
|
1431
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
1432
|
+
const [row] = await scopedDb
|
|
1433
|
+
.select()
|
|
1434
|
+
.from(schema.documents)
|
|
1435
|
+
.where(
|
|
1436
|
+
and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)),
|
|
1437
|
+
)
|
|
1438
|
+
.limit(1);
|
|
1439
|
+
return row ? mapDocument(row) : null;
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1406
1443
|
export async function queueDocumentForReindex(
|
|
1407
1444
|
db: Database,
|
|
1408
1445
|
workspaceId: string,
|
|
@@ -1477,7 +1514,12 @@ export async function indexDocumentNow(
|
|
|
1477
1514
|
);
|
|
1478
1515
|
if (!loadedDocument) throw new Error(`Document not found: ${documentId}`);
|
|
1479
1516
|
let document: DocumentRow = loadedDocument;
|
|
1480
|
-
const file = await requireReadyFile(db,
|
|
1517
|
+
const file = await requireReadyFile(db, {
|
|
1518
|
+
accountId: document.accountId,
|
|
1519
|
+
workspaceId,
|
|
1520
|
+
subjectId: cleanString(document.createdBy) ?? null,
|
|
1521
|
+
fileId: document.fileId,
|
|
1522
|
+
});
|
|
1481
1523
|
await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1482
1524
|
await scopedDb
|
|
1483
1525
|
.update(schema.documents)
|
|
@@ -1604,9 +1646,7 @@ export async function indexDocumentNow(
|
|
|
1604
1646
|
// Internal indexing must be able to return a private document to the caller
|
|
1605
1647
|
// that created/queued it. Public reads remain fail-closed when no subject is
|
|
1606
1648
|
// supplied; the creator subject is the document's frozen access principal.
|
|
1607
|
-
const updated = await
|
|
1608
|
-
viewerSubjectId: document.authoritySubjectId,
|
|
1609
|
-
});
|
|
1649
|
+
const updated = await getDocumentForIndexing(db, workspaceId, documentId);
|
|
1610
1650
|
if (!updated) throw new Error(`Document disappeared after indexing: ${documentId}`);
|
|
1611
1651
|
return updated;
|
|
1612
1652
|
}
|
|
@@ -1839,7 +1879,11 @@ export async function searchEffectiveKnowledge(
|
|
|
1839
1879
|
access,
|
|
1840
1880
|
async (scopedDb) =>
|
|
1841
1881
|
await scopedDb
|
|
1842
|
-
.select({
|
|
1882
|
+
.select({
|
|
1883
|
+
chunk: schema.documentChunks,
|
|
1884
|
+
document: schema.documents,
|
|
1885
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
1886
|
+
})
|
|
1843
1887
|
.from(schema.documentChunks)
|
|
1844
1888
|
.innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))
|
|
1845
1889
|
.where(
|
|
@@ -1862,7 +1906,7 @@ export async function searchEffectiveKnowledge(
|
|
|
1862
1906
|
if (!row) return [];
|
|
1863
1907
|
return [
|
|
1864
1908
|
{
|
|
1865
|
-
record: knowledgeChunkRecord(row.document, row.chunk),
|
|
1909
|
+
record: knowledgeChunkRecord(row.document, row.chunk, row.citation),
|
|
1866
1910
|
retrieval: {
|
|
1867
1911
|
score: rankedResult.score,
|
|
1868
1912
|
matchType: rankedResult.matchType,
|
|
@@ -1895,8 +1939,11 @@ export async function getEffectiveKnowledgeRecord(
|
|
|
1895
1939
|
access,
|
|
1896
1940
|
async (scopedDb) => {
|
|
1897
1941
|
if (target.kind === "document") {
|
|
1898
|
-
const [
|
|
1899
|
-
.select(
|
|
1942
|
+
const [row] = await scopedDb
|
|
1943
|
+
.select({
|
|
1944
|
+
document: schema.documents,
|
|
1945
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
1946
|
+
})
|
|
1900
1947
|
.from(schema.documents)
|
|
1901
1948
|
.where(
|
|
1902
1949
|
and(
|
|
@@ -1907,10 +1954,14 @@ export async function getEffectiveKnowledgeRecord(
|
|
|
1907
1954
|
),
|
|
1908
1955
|
)
|
|
1909
1956
|
.limit(1);
|
|
1910
|
-
return
|
|
1957
|
+
return row ? knowledgeDocumentRecord(row.document, row.citation) : null;
|
|
1911
1958
|
}
|
|
1912
1959
|
const [row] = await scopedDb
|
|
1913
|
-
.select({
|
|
1960
|
+
.select({
|
|
1961
|
+
chunk: schema.documentChunks,
|
|
1962
|
+
document: schema.documents,
|
|
1963
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
1964
|
+
})
|
|
1914
1965
|
.from(schema.documentChunks)
|
|
1915
1966
|
.innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))
|
|
1916
1967
|
.where(
|
|
@@ -1923,7 +1974,7 @@ export async function getEffectiveKnowledgeRecord(
|
|
|
1923
1974
|
),
|
|
1924
1975
|
)
|
|
1925
1976
|
.limit(1);
|
|
1926
|
-
return row ? knowledgeChunkRecord(row.document, row.chunk) : null;
|
|
1977
|
+
return row ? knowledgeChunkRecord(row.document, row.chunk, row.citation) : null;
|
|
1927
1978
|
},
|
|
1928
1979
|
);
|
|
1929
1980
|
}
|
|
@@ -1988,7 +2039,11 @@ export async function browseEffectiveKnowledge(
|
|
|
1988
2039
|
.limit(1);
|
|
1989
2040
|
if (!authorizedParent) return { records: [], nextCursor: null, hasMore: false };
|
|
1990
2041
|
const rows = await scopedDb
|
|
1991
|
-
.select({
|
|
2042
|
+
.select({
|
|
2043
|
+
chunk: schema.documentChunks,
|
|
2044
|
+
document: schema.documents,
|
|
2045
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2046
|
+
})
|
|
1992
2047
|
.from(schema.documentChunks)
|
|
1993
2048
|
.innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))
|
|
1994
2049
|
.where(
|
|
@@ -2006,7 +2061,7 @@ export async function browseEffectiveKnowledge(
|
|
|
2006
2061
|
const page = rows.slice(0, limit);
|
|
2007
2062
|
const last = page.at(-1)?.chunk.chunkIndex;
|
|
2008
2063
|
return {
|
|
2009
|
-
records: page.map((row) => knowledgeChunkRecord(row.document, row.chunk)),
|
|
2064
|
+
records: page.map((row) => knowledgeChunkRecord(row.document, row.chunk, row.citation)),
|
|
2010
2065
|
nextCursor:
|
|
2011
2066
|
hasMore && last !== undefined
|
|
2012
2067
|
? encodeKnowledgeBrowseCursor(cursorScope, BigInt(last + 1))
|
|
@@ -2026,16 +2081,19 @@ export async function browseEffectiveKnowledge(
|
|
|
2026
2081
|
if (sourceKinds.length > 0)
|
|
2027
2082
|
conditions.push(inArray(schema.documents.sourceKind, sourceKinds));
|
|
2028
2083
|
const rows = await scopedDb
|
|
2029
|
-
.select(
|
|
2084
|
+
.select({
|
|
2085
|
+
document: schema.documents,
|
|
2086
|
+
citation: googleDriveCitationProjection(input.workspaceId, access),
|
|
2087
|
+
})
|
|
2030
2088
|
.from(schema.documents)
|
|
2031
2089
|
.where(and(...conditions))
|
|
2032
2090
|
.orderBy(asc(schema.documents.indexSequence))
|
|
2033
2091
|
.limit(limit + 1);
|
|
2034
2092
|
const hasMore = rows.length > limit;
|
|
2035
2093
|
const page = rows.slice(0, limit);
|
|
2036
|
-
const last = page.at(-1)?.indexSequence;
|
|
2094
|
+
const last = page.at(-1)?.document.indexSequence;
|
|
2037
2095
|
return {
|
|
2038
|
-
records: page.map(knowledgeDocumentRecord),
|
|
2096
|
+
records: page.map((row) => knowledgeDocumentRecord(row.document, row.citation)),
|
|
2039
2097
|
nextCursor:
|
|
2040
2098
|
hasMore && last !== undefined && last !== null
|
|
2041
2099
|
? encodeKnowledgeBrowseCursor(cursorScope, last)
|
|
@@ -2132,7 +2190,10 @@ function parseKnowledgeRecordId(value: string): {
|
|
|
2132
2190
|
return { kind: match[1] as "document" | "document_chunk", id: match[2]!.toLowerCase() };
|
|
2133
2191
|
}
|
|
2134
2192
|
|
|
2135
|
-
function knowledgeDocumentRecord(
|
|
2193
|
+
function knowledgeDocumentRecord(
|
|
2194
|
+
document: typeof schema.documents.$inferSelect,
|
|
2195
|
+
citation: unknown = null,
|
|
2196
|
+
): KnowledgeRecord {
|
|
2136
2197
|
if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
|
|
2137
2198
|
const projected = projectKnowledgeRecord({
|
|
2138
2199
|
title: document.title,
|
|
@@ -2151,6 +2212,7 @@ function knowledgeDocumentRecord(document: typeof schema.documents.$inferSelect)
|
|
|
2151
2212
|
provenance: {
|
|
2152
2213
|
source: projected.source,
|
|
2153
2214
|
indexedAt: document.indexedAt.toISOString(),
|
|
2215
|
+
citation: parseKnowledgeProviderCitation(citation),
|
|
2154
2216
|
},
|
|
2155
2217
|
lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
|
|
2156
2218
|
quality: knowledgeQuality(document),
|
|
@@ -2162,6 +2224,7 @@ function knowledgeDocumentRecord(document: typeof schema.documents.$inferSelect)
|
|
|
2162
2224
|
function knowledgeChunkRecord(
|
|
2163
2225
|
document: typeof schema.documents.$inferSelect,
|
|
2164
2226
|
chunk: typeof schema.documentChunks.$inferSelect,
|
|
2227
|
+
citation: unknown = null,
|
|
2165
2228
|
): KnowledgeRecord {
|
|
2166
2229
|
if (!document.indexedAt) throw new Error(`Ready document is missing indexed_at: ${document.id}`);
|
|
2167
2230
|
const projected = projectKnowledgeRecord({
|
|
@@ -2181,6 +2244,7 @@ function knowledgeChunkRecord(
|
|
|
2181
2244
|
provenance: {
|
|
2182
2245
|
source: projected.source,
|
|
2183
2246
|
indexedAt: document.indexedAt.toISOString(),
|
|
2247
|
+
citation: parseKnowledgeProviderCitation(citation),
|
|
2184
2248
|
},
|
|
2185
2249
|
lifecycle: { state: "active", updatedAt: document.updatedAt.toISOString() },
|
|
2186
2250
|
quality: knowledgeQuality(document),
|
|
@@ -2259,6 +2323,7 @@ async function vectorSearchDocuments(
|
|
|
2259
2323
|
authorityKind: schema.documents.authorityKind,
|
|
2260
2324
|
authorityWorkspaceId: schema.documents.authorityWorkspaceId,
|
|
2261
2325
|
authoritySubjectId: schema.documents.authoritySubjectId,
|
|
2326
|
+
citation: googleDriveCitationProjection(input.workspaceId, input.access),
|
|
2262
2327
|
distance,
|
|
2263
2328
|
})
|
|
2264
2329
|
.from(schema.documentChunks)
|
|
@@ -2309,6 +2374,7 @@ async function keywordSearchDocuments(
|
|
|
2309
2374
|
authorityKind: schema.documents.authorityKind,
|
|
2310
2375
|
authorityWorkspaceId: schema.documents.authorityWorkspaceId,
|
|
2311
2376
|
authoritySubjectId: schema.documents.authoritySubjectId,
|
|
2377
|
+
citation: googleDriveCitationProjection(input.workspaceId, input.access),
|
|
2312
2378
|
rank,
|
|
2313
2379
|
})
|
|
2314
2380
|
.from(schema.documentChunks)
|
|
@@ -2365,6 +2431,7 @@ export async function getDocumentChunk(
|
|
|
2365
2431
|
authorityKind: schema.documents.authorityKind,
|
|
2366
2432
|
authorityWorkspaceId: schema.documents.authorityWorkspaceId,
|
|
2367
2433
|
authoritySubjectId: schema.documents.authoritySubjectId,
|
|
2434
|
+
citation: googleDriveCitationProjection(workspaceId, access),
|
|
2368
2435
|
})
|
|
2369
2436
|
.from(schema.documentChunks)
|
|
2370
2437
|
.innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id))
|
|
@@ -2492,10 +2559,17 @@ function documentAccessConditions(
|
|
|
2492
2559
|
const authority = viewer
|
|
2493
2560
|
? (or(organization, workspace, personal) ?? organization)
|
|
2494
2561
|
: (or(organization, workspace) ?? organization);
|
|
2562
|
+
const viewerSql = viewer ? sql`${viewer}` : sql`NULL::text`;
|
|
2563
|
+
const providerAuthorization = sql`google_drive_file_authorized(
|
|
2564
|
+
${schema.documents.accountId},
|
|
2565
|
+
${workspaceId}::uuid,
|
|
2566
|
+
${viewerSql},
|
|
2567
|
+
${schema.documents.fileId}
|
|
2568
|
+
)`;
|
|
2495
2569
|
if (access?.agentOnly) {
|
|
2496
|
-
return [eq(schema.documents.agentAccess, true), authority];
|
|
2570
|
+
return [eq(schema.documents.agentAccess, true), authority, providerAuthorization];
|
|
2497
2571
|
}
|
|
2498
|
-
return [authority];
|
|
2572
|
+
return [authority, providerAuthorization];
|
|
2499
2573
|
}
|
|
2500
2574
|
|
|
2501
2575
|
function documentMatchesAccess(
|
|
@@ -2606,6 +2680,7 @@ function mapSearchRowBase(row: {
|
|
|
2606
2680
|
authorityKind: string;
|
|
2607
2681
|
authorityWorkspaceId: string | null;
|
|
2608
2682
|
authoritySubjectId: string | null;
|
|
2683
|
+
citation?: unknown;
|
|
2609
2684
|
}): SearchRowBase {
|
|
2610
2685
|
return {
|
|
2611
2686
|
chunkId: row.chunkId,
|
|
@@ -2629,9 +2704,29 @@ function mapSearchRowBase(row: {
|
|
|
2629
2704
|
authorityKind: normalizeDocumentAuthorityKind(row.authorityKind),
|
|
2630
2705
|
authorityWorkspaceId: row.authorityWorkspaceId,
|
|
2631
2706
|
authoritySubjectId: row.authoritySubjectId,
|
|
2707
|
+
citation: parseKnowledgeProviderCitation(row.citation),
|
|
2632
2708
|
};
|
|
2633
2709
|
}
|
|
2634
2710
|
|
|
2711
|
+
function googleDriveCitationProjection(
|
|
2712
|
+
workspaceId: string,
|
|
2713
|
+
access: DocumentAccessFilter | undefined,
|
|
2714
|
+
): SQL<unknown> {
|
|
2715
|
+
const viewer = cleanString(access?.viewerSubjectId ?? null);
|
|
2716
|
+
const viewerSql = viewer ? sql`${viewer}` : sql`NULL::text`;
|
|
2717
|
+
return sql`google_drive_document_citation(
|
|
2718
|
+
${schema.documents.accountId},
|
|
2719
|
+
${workspaceId}::uuid,
|
|
2720
|
+
${viewerSql},
|
|
2721
|
+
${schema.documents.id},
|
|
2722
|
+
${schema.documents.fileId}
|
|
2723
|
+
)`;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function parseKnowledgeProviderCitation(value: unknown): KnowledgeProviderCitationValue | null {
|
|
2727
|
+
return value === null || value === undefined ? null : KnowledgeProviderCitation.parse(value);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2635
2730
|
function mergeDocumentSearchRows(
|
|
2636
2731
|
rows: CombinedSearchRow[],
|
|
2637
2732
|
mode: DocumentSearchMode,
|
|
@@ -2763,12 +2858,22 @@ export function deterministicEmbedding(
|
|
|
2763
2858
|
|
|
2764
2859
|
async function requireReadyFile(
|
|
2765
2860
|
db: Database,
|
|
2766
|
-
|
|
2767
|
-
|
|
2861
|
+
input: {
|
|
2862
|
+
accountId: string;
|
|
2863
|
+
workspaceId: string;
|
|
2864
|
+
subjectId: string | null;
|
|
2865
|
+
fileId: string;
|
|
2866
|
+
},
|
|
2768
2867
|
): Promise<FileAsset> {
|
|
2769
|
-
const file = await
|
|
2868
|
+
const [file] = await getFilesForSubject(db, {
|
|
2869
|
+
accountId: input.accountId,
|
|
2870
|
+
workspaceId: input.workspaceId,
|
|
2871
|
+
subjectId: input.subjectId,
|
|
2872
|
+
fileIds: [input.fileId],
|
|
2873
|
+
});
|
|
2874
|
+
if (!file) throw new Error(`File not found: ${input.fileId}`);
|
|
2770
2875
|
if (file.status !== "ready") {
|
|
2771
|
-
throw new Error(`File ${fileId} is ${file.status}`);
|
|
2876
|
+
throw new Error(`File ${input.fileId} is ${file.status}`);
|
|
2772
2877
|
}
|
|
2773
2878
|
return file;
|
|
2774
2879
|
}
|