@opengeni/documents 0.5.18 → 0.5.32
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/atlassian.d.ts +78 -0
- package/dist/atlassian.js +140 -0
- package/dist/atlassian.js.map +1 -0
- package/dist/google-drive.d.ts +2 -0
- package/dist/google-drive.js +12 -1
- package/dist/google-drive.js.map +1 -1
- package/dist/index.d.ts +31 -3
- package/dist/index.js +174 -26
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
- package/src/atlassian.ts +250 -0
- package/src/google-drive.ts +21 -1
- package/src/index.ts +246 -26
package/dist/index.js
CHANGED
|
@@ -8,9 +8,8 @@ import {
|
|
|
8
8
|
withWorkspaceSubjectRls
|
|
9
9
|
} from "@opengeni/db";
|
|
10
10
|
import * as schema from "@opengeni/db/schema";
|
|
11
|
-
import {
|
|
12
|
-
import { and, asc, desc, eq, inArray, or, sql } from "drizzle-orm";
|
|
13
|
-
import OpenAI from "openai";
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
import { and, asc, desc, eq, gt, inArray, isNotNull, or, sql } from "drizzle-orm";
|
|
14
13
|
var DEFAULT_DOCUMENT_PARSER = "liteparse";
|
|
15
14
|
var DEFAULT_DOCUMENT_EMBEDDING_MODEL = "text-embedding-3-large";
|
|
16
15
|
var DEFAULT_DOCUMENT_EMBEDDING_DIMENSIONS = 3072;
|
|
@@ -19,6 +18,7 @@ var DEFAULT_DOCUMENT_CHUNK_OVERLAP = 160;
|
|
|
19
18
|
var DEFAULT_DOCUMENT_CURATION_MODEL = "gpt-4o-mini";
|
|
20
19
|
var DOCUMENT_CURATION_MAX_INPUT_CHARS = 24e3;
|
|
21
20
|
var DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES = 1024;
|
|
21
|
+
var DOCUMENT_INDEX_CHECKPOINT_MAX_CHARS = 1024;
|
|
22
22
|
var DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE = 0.75;
|
|
23
23
|
var DEFAULT_BASE_NAME = "Default";
|
|
24
24
|
var DEFAULT_BASE_DESCRIPTION = "Default base for dropped files and notes.";
|
|
@@ -44,6 +44,7 @@ var LiteParseDocumentParser = class {
|
|
|
44
44
|
}
|
|
45
45
|
async parseWithLiteParse(bytes) {
|
|
46
46
|
return await this.enqueueParse(async () => {
|
|
47
|
+
const { LiteParse } = await import("@llamaindex/liteparse");
|
|
47
48
|
const parser = new LiteParse({ ocrEnabled: true, numWorkers: 1 });
|
|
48
49
|
const result = await parser.parse(Buffer.from(bytes), true);
|
|
49
50
|
const text = typeof result?.text === "string" ? result.text : "";
|
|
@@ -85,7 +86,7 @@ var RecursiveTextChunker = class {
|
|
|
85
86
|
}
|
|
86
87
|
};
|
|
87
88
|
var OpenAIEmbeddingProvider = class {
|
|
88
|
-
|
|
89
|
+
clientPromise = null;
|
|
89
90
|
apiKey;
|
|
90
91
|
baseURL;
|
|
91
92
|
constructor(args) {
|
|
@@ -105,7 +106,7 @@ var OpenAIEmbeddingProvider = class {
|
|
|
105
106
|
const out = [];
|
|
106
107
|
for (let start = 0; start < texts.length; start += 64) {
|
|
107
108
|
const batch = texts.slice(start, start + 64);
|
|
108
|
-
const response = await this.openai().embeddings.create({
|
|
109
|
+
const response = await (await this.openai()).embeddings.create({
|
|
109
110
|
model: this.model,
|
|
110
111
|
input: batch,
|
|
111
112
|
dimensions: this.dimensions
|
|
@@ -123,17 +124,19 @@ var OpenAIEmbeddingProvider = class {
|
|
|
123
124
|
}
|
|
124
125
|
return embedding;
|
|
125
126
|
}
|
|
126
|
-
openai() {
|
|
127
|
+
async openai() {
|
|
127
128
|
if (!this.apiKey) {
|
|
128
129
|
throw new Error("OpenAI document embeddings require an API key");
|
|
129
130
|
}
|
|
130
|
-
this.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
this.clientPromise ??= import("openai").then(
|
|
132
|
+
({ default: OpenAIClient }) => new OpenAIClient({
|
|
133
|
+
apiKey: this.apiKey,
|
|
134
|
+
...this.baseURL ? { baseURL: this.baseURL } : {},
|
|
135
|
+
...this.defaultQuery ? { defaultQuery: this.defaultQuery } : {},
|
|
136
|
+
...this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
return await this.clientPromise;
|
|
137
140
|
}
|
|
138
141
|
};
|
|
139
142
|
var DeterministicEmbeddingProvider = class {
|
|
@@ -196,7 +199,7 @@ var CURATION_SYSTEM_PROMPT = [
|
|
|
196
199
|
"well, return targetBaseId null and confidence 0. Respond with JSON only."
|
|
197
200
|
].join(" ");
|
|
198
201
|
var OpenAICurationProvider = class {
|
|
199
|
-
|
|
202
|
+
clientPromise = null;
|
|
200
203
|
apiKey;
|
|
201
204
|
baseURL;
|
|
202
205
|
defaultHeaders;
|
|
@@ -210,7 +213,7 @@ var OpenAICurationProvider = class {
|
|
|
210
213
|
this.model = args.model ?? DEFAULT_DOCUMENT_CURATION_MODEL;
|
|
211
214
|
}
|
|
212
215
|
async curate(input) {
|
|
213
|
-
const response = await this.openai().chat.completions.create({
|
|
216
|
+
const response = await (await this.openai()).chat.completions.create({
|
|
214
217
|
model: this.model,
|
|
215
218
|
response_format: { type: "json_object" },
|
|
216
219
|
messages: [
|
|
@@ -232,17 +235,19 @@ var OpenAICurationProvider = class {
|
|
|
232
235
|
}
|
|
233
236
|
return parseCurationOutcome(raw, input.bases);
|
|
234
237
|
}
|
|
235
|
-
openai() {
|
|
238
|
+
async openai() {
|
|
236
239
|
if (!this.apiKey) {
|
|
237
240
|
throw new Error("OpenAI document curation requires an API key");
|
|
238
241
|
}
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
this.clientPromise ??= import("openai").then(
|
|
243
|
+
({ default: OpenAIClient }) => new OpenAIClient({
|
|
244
|
+
apiKey: this.apiKey,
|
|
245
|
+
...this.baseURL ? { baseURL: this.baseURL } : {},
|
|
246
|
+
...this.defaultQuery ? { defaultQuery: this.defaultQuery } : {},
|
|
247
|
+
...this.defaultHeaders ? { defaultHeaders: this.defaultHeaders } : {}
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
return await this.clientPromise;
|
|
246
251
|
}
|
|
247
252
|
};
|
|
248
253
|
function parseCurationOutcome(raw, bases) {
|
|
@@ -534,15 +539,24 @@ async function addDocumentToBase(db, input) {
|
|
|
534
539
|
const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);
|
|
535
540
|
if (!base) throw new Error(`Document base not found: ${input.baseId}`);
|
|
536
541
|
const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);
|
|
542
|
+
const knowledgeSourceIdentity = cleanString(input.knowledgeSourceIdentity ?? null);
|
|
543
|
+
if (knowledgeSourceIdentity && knowledgeSourceIdentity.length > 512) {
|
|
544
|
+
throw new Error("knowledge source document identity exceeds 512 characters");
|
|
545
|
+
}
|
|
537
546
|
const now = /* @__PURE__ */ new Date();
|
|
538
547
|
const [existing] = await scopedDb.select().from(schema.documents).where(
|
|
539
548
|
and(
|
|
540
549
|
eq(schema.documents.workspaceId, input.workspaceId),
|
|
541
|
-
eq(schema.documents.
|
|
542
|
-
|
|
550
|
+
...knowledgeSourceIdentity ? [eq(schema.documents.knowledgeSourceIdentity, knowledgeSourceIdentity)] : [
|
|
551
|
+
eq(schema.documents.baseId, input.baseId),
|
|
552
|
+
eq(schema.documents.fileId, input.fileId)
|
|
553
|
+
]
|
|
543
554
|
)
|
|
544
555
|
).limit(1);
|
|
545
556
|
if (existing) {
|
|
557
|
+
if (knowledgeSourceIdentity && existing.fileId !== input.fileId) {
|
|
558
|
+
throw new Error("knowledge source document identity is bound to different content");
|
|
559
|
+
}
|
|
546
560
|
if (!documentMatchesAccess(existing, input.workspaceId, input.access)) {
|
|
547
561
|
throw new Error(`Document not found: ${existing.id}`);
|
|
548
562
|
}
|
|
@@ -587,6 +601,7 @@ async function addDocumentToBase(db, input) {
|
|
|
587
601
|
sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),
|
|
588
602
|
sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),
|
|
589
603
|
sourceVersion: cleanString(input.sourceVersion) ?? null,
|
|
604
|
+
knowledgeSourceIdentity,
|
|
590
605
|
aclTags: cleanStringArray(input.aclTags),
|
|
591
606
|
authorityKind: authority.kind,
|
|
592
607
|
authorityWorkspaceId: authority.workspaceId,
|
|
@@ -630,7 +645,7 @@ async function moveDocumentToBase(db, input) {
|
|
|
630
645
|
and(
|
|
631
646
|
eq(schema.documents.workspaceId, input.workspaceId),
|
|
632
647
|
eq(schema.documents.baseId, targetBaseId),
|
|
633
|
-
eq(schema.documents.fileId, row.fileId)
|
|
648
|
+
...row.knowledgeSourceIdentity ? [eq(schema.documents.knowledgeSourceIdentity, row.knowledgeSourceIdentity)] : [eq(schema.documents.fileId, row.fileId)]
|
|
634
649
|
)
|
|
635
650
|
).limit(1);
|
|
636
651
|
if (conflict) {
|
|
@@ -710,6 +725,101 @@ async function listDocuments(db, workspaceId, baseId, access) {
|
|
|
710
725
|
return rows.map(mapDocument);
|
|
711
726
|
});
|
|
712
727
|
}
|
|
728
|
+
async function listEffectiveIndexedDocuments(db, input) {
|
|
729
|
+
const initiatingSubjectId = canonicalEffectiveDocumentSubject(input.initiatingSubjectId);
|
|
730
|
+
const limit = input.limit ?? 50;
|
|
731
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
732
|
+
throw new Error("indexed document list limit must be between 1 and 100");
|
|
733
|
+
}
|
|
734
|
+
const afterSequence = input.checkpoint ? decodeDocumentIndexCheckpoint(input.checkpoint, {
|
|
735
|
+
accountId: input.accountId,
|
|
736
|
+
workspaceId: input.workspaceId,
|
|
737
|
+
initiatingSubjectId
|
|
738
|
+
}) : 0n;
|
|
739
|
+
const access = {
|
|
740
|
+
agentOnly: true,
|
|
741
|
+
viewerSubjectId: initiatingSubjectId
|
|
742
|
+
};
|
|
743
|
+
const rows = await withDocumentAccountRls(
|
|
744
|
+
db,
|
|
745
|
+
input.accountId,
|
|
746
|
+
input.workspaceId,
|
|
747
|
+
access,
|
|
748
|
+
async (scopedDb) => await scopedDb.select().from(schema.documents).where(
|
|
749
|
+
and(
|
|
750
|
+
eq(schema.documents.accountId, input.accountId),
|
|
751
|
+
eq(schema.documents.status, "ready"),
|
|
752
|
+
isNotNull(schema.documents.indexSequence),
|
|
753
|
+
gt(schema.documents.indexSequence, afterSequence),
|
|
754
|
+
...documentAccessConditions(input.workspaceId, access)
|
|
755
|
+
)
|
|
756
|
+
).orderBy(asc(schema.documents.indexSequence)).limit(limit + 1)
|
|
757
|
+
);
|
|
758
|
+
const hasMore = rows.length > limit;
|
|
759
|
+
const pageRows = rows.slice(0, limit);
|
|
760
|
+
const nextSequence = pageRows.at(-1)?.indexSequence ?? afterSequence;
|
|
761
|
+
if (nextSequence === null) {
|
|
762
|
+
throw new Error("ready document is missing its index sequence");
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
documents: pageRows.map(mapIndexedDocumentSummary),
|
|
766
|
+
nextCheckpoint: encodeDocumentIndexCheckpoint({
|
|
767
|
+
accountId: input.accountId,
|
|
768
|
+
workspaceId: input.workspaceId,
|
|
769
|
+
initiatingSubjectId,
|
|
770
|
+
sequence: nextSequence
|
|
771
|
+
}),
|
|
772
|
+
hasMore
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function encodeDocumentIndexCheckpoint(input) {
|
|
776
|
+
if (input.sequence < 0n) throw new Error("document index checkpoint sequence is invalid");
|
|
777
|
+
return Buffer.from(
|
|
778
|
+
JSON.stringify({
|
|
779
|
+
v: 1,
|
|
780
|
+
s: documentIndexCheckpointScope(input),
|
|
781
|
+
q: input.sequence.toString()
|
|
782
|
+
}),
|
|
783
|
+
"utf8"
|
|
784
|
+
).toString("base64url");
|
|
785
|
+
}
|
|
786
|
+
function decodeDocumentIndexCheckpoint(value, scope) {
|
|
787
|
+
try {
|
|
788
|
+
if (!value || value.length > DOCUMENT_INDEX_CHECKPOINT_MAX_CHARS) {
|
|
789
|
+
throw new Error("checkpoint length");
|
|
790
|
+
}
|
|
791
|
+
const bytes = Buffer.from(value, "base64url");
|
|
792
|
+
if (bytes.toString("base64url") !== value) throw new Error("checkpoint encoding");
|
|
793
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
794
|
+
if (Object.keys(parsed).sort().join(",") !== "q,s,v" || parsed.v !== 1 || typeof parsed.s !== "string" || typeof parsed.q !== "string" || !/^(0|[1-9][0-9]*)$/.test(parsed.q)) {
|
|
795
|
+
throw new Error("checkpoint payload");
|
|
796
|
+
}
|
|
797
|
+
if (parsed.s !== documentIndexCheckpointScope(scope)) {
|
|
798
|
+
throw new Error("document index checkpoint belongs to a different workspace or subject");
|
|
799
|
+
}
|
|
800
|
+
return BigInt(parsed.q);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
if (error instanceof Error && error.message === "document index checkpoint belongs to a different workspace or subject") {
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
throw new Error("invalid document index checkpoint", { cause: error });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function documentIndexCheckpointScope(input) {
|
|
809
|
+
return createHash("sha256").update("opengeni:document-index-checkpoint:v1\0").update(input.accountId).update("\0").update(input.workspaceId).update("\0").update(canonicalEffectiveDocumentSubject(input.initiatingSubjectId)).digest("hex");
|
|
810
|
+
}
|
|
811
|
+
function canonicalEffectiveDocumentSubject(value) {
|
|
812
|
+
const subjectId = cleanString(value);
|
|
813
|
+
if (!subjectId || subjectId !== value) {
|
|
814
|
+
throw new Error("effective document retrieval requires an initiating subject");
|
|
815
|
+
}
|
|
816
|
+
if (new TextEncoder().encode(subjectId).byteLength > DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`effective document initiating subject exceeds ${DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES} UTF-8 bytes`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
return subjectId;
|
|
822
|
+
}
|
|
713
823
|
async function getDocument(db, workspaceId, documentId, access) {
|
|
714
824
|
return await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
715
825
|
const [row] = await scopedDb.select().from(schema.documents).where(
|
|
@@ -1508,6 +1618,40 @@ function mapDocument(row) {
|
|
|
1508
1618
|
updatedAt: row.updatedAt.toISOString()
|
|
1509
1619
|
};
|
|
1510
1620
|
}
|
|
1621
|
+
function mapIndexedDocumentSummary(row) {
|
|
1622
|
+
if (row.indexSequence === null || row.indexedAt === null) {
|
|
1623
|
+
throw new Error(`Ready document is missing index completion metadata: ${row.id}`);
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
id: row.id,
|
|
1627
|
+
title: row.title,
|
|
1628
|
+
parser: row.parser,
|
|
1629
|
+
chunkCount: row.chunkCount,
|
|
1630
|
+
indexedAt: row.indexedAt.toISOString(),
|
|
1631
|
+
summary: row.summary,
|
|
1632
|
+
topics: cleanStringArray(row.topics),
|
|
1633
|
+
source: {
|
|
1634
|
+
kind: normalizeKnowledgeSourceKind(row.sourceKind),
|
|
1635
|
+
uri: row.sourceUri,
|
|
1636
|
+
externalId: row.sourceExternalId,
|
|
1637
|
+
title: row.sourceTitle,
|
|
1638
|
+
author: row.sourceAuthor,
|
|
1639
|
+
createdAt: row.sourceCreatedAt?.toISOString() ?? null,
|
|
1640
|
+
updatedAt: row.sourceUpdatedAt?.toISOString() ?? null,
|
|
1641
|
+
version: row.sourceVersion
|
|
1642
|
+
},
|
|
1643
|
+
provenance: {
|
|
1644
|
+
ingestionWorkspaceId: row.workspaceId,
|
|
1645
|
+
baseId: row.baseId,
|
|
1646
|
+
fileId: row.fileId,
|
|
1647
|
+
authorityKind: normalizeDocumentAuthorityKind(row.authorityKind),
|
|
1648
|
+
authorityWorkspaceId: row.authorityWorkspaceId,
|
|
1649
|
+
authoritySubjectId: row.authoritySubjectId,
|
|
1650
|
+
createdBy: row.createdBy,
|
|
1651
|
+
createdAt: row.createdAt.toISOString()
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1511
1655
|
function normalizeDocumentAuthorityKind(value) {
|
|
1512
1656
|
switch (value) {
|
|
1513
1657
|
case "organization":
|
|
@@ -1543,6 +1687,7 @@ export {
|
|
|
1543
1687
|
DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES,
|
|
1544
1688
|
DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE,
|
|
1545
1689
|
DOCUMENT_CURATION_MAX_INPUT_CHARS,
|
|
1690
|
+
DOCUMENT_INDEX_CHECKPOINT_MAX_CHARS,
|
|
1546
1691
|
DeterministicEmbeddingProvider,
|
|
1547
1692
|
HeuristicCurationProvider,
|
|
1548
1693
|
LiteParseDocumentParser,
|
|
@@ -1554,9 +1699,11 @@ export {
|
|
|
1554
1699
|
chunkText,
|
|
1555
1700
|
createDocumentBase,
|
|
1556
1701
|
createDocumentServices,
|
|
1702
|
+
decodeDocumentIndexCheckpoint,
|
|
1557
1703
|
deleteDocumentFromBase,
|
|
1558
1704
|
deterministicEmbedding,
|
|
1559
1705
|
documentOpenAIEmbeddingConfig,
|
|
1706
|
+
encodeDocumentIndexCheckpoint,
|
|
1560
1707
|
ensureDefaultBase,
|
|
1561
1708
|
getDocument,
|
|
1562
1709
|
getDocumentBase,
|
|
@@ -1567,6 +1714,7 @@ export {
|
|
|
1567
1714
|
listDocumentBases,
|
|
1568
1715
|
listDocumentBasesEnsuringDefault,
|
|
1569
1716
|
listDocuments,
|
|
1717
|
+
listEffectiveIndexedDocuments,
|
|
1570
1718
|
moveDocumentToBase,
|
|
1571
1719
|
parseCurationOutcome,
|
|
1572
1720
|
parseDocumentBytes,
|