@anchrd/intel-api 0.2.0
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/README.md +96 -0
- package/bin/intel.mjs +134 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +21 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +39 -0
- package/dist/adapters/cloudflare/cloudflare.d.ts +8 -0
- package/dist/adapters/cloudflare/cloudflare.js +200 -0
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +29 -0
- package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
- package/dist/adapters/content/content.d.ts +3 -0
- package/dist/adapters/content/content.js +22 -0
- package/dist/adapters/content/content.types.d.ts +14 -0
- package/dist/adapters/content/content.types.js +1 -0
- package/dist/adapters/db/db-flows.d.ts +6 -0
- package/dist/adapters/db/db-flows.js +466 -0
- package/dist/adapters/db/db-indexing.d.ts +3 -0
- package/dist/adapters/db/db-indexing.js +70 -0
- package/dist/adapters/db/db-oauth.d.ts +3 -0
- package/dist/adapters/db/db-oauth.js +26 -0
- package/dist/adapters/db/db-tools.d.ts +6 -0
- package/dist/adapters/db/db-tools.js +200 -0
- package/dist/adapters/db/db.d.ts +6 -0
- package/dist/adapters/db/db.js +755 -0
- package/dist/adapters/db/db.types.d.ts +16 -0
- package/dist/adapters/db/db.types.js +1 -0
- package/dist/adapters/document-converter/document-converter.d.ts +2 -0
- package/dist/adapters/document-converter/document-converter.js +25 -0
- package/dist/adapters/document-converter/document-converter.types.d.ts +9 -0
- package/dist/adapters/document-converter/document-converter.types.js +1 -0
- package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
- package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
- package/dist/adapters/index-queue/index-queue.d.ts +10 -0
- package/dist/adapters/index-queue/index-queue.js +12 -0
- package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
- package/dist/adapters/index-queue/index-queue.types.js +1 -0
- package/dist/adapters/json-schema/json-schema.d.ts +2 -0
- package/dist/adapters/json-schema/json-schema.js +31 -0
- package/dist/adapters/openid/openid.d.ts +5 -0
- package/dist/adapters/openid/openid.js +159 -0
- package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
- package/dist/adapters/portal-tokens/portal-tokens.js +71 -0
- package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
- package/dist/adapters/remote-tools/remote-tools.js +109 -0
- package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
- package/dist/adapters/semantic-index/semantic-index.js +57 -0
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +32 -0
- package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
- package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
- package/dist/adapters/session-cookie/session-cookie.js +81 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
- package/dist/auth/auth.d.ts +2 -0
- package/dist/auth/auth.js +176 -0
- package/dist/auth/auth.types.d.ts +104 -0
- package/dist/auth/auth.types.js +1 -0
- package/dist/build/build.d.ts +2 -0
- package/dist/build/build.js +125 -0
- package/dist/build/build.types.d.ts +17 -0
- package/dist/build/build.types.js +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.js +141 -0
- package/dist/cli/cli.types.d.ts +14 -0
- package/dist/cli/cli.types.js +1 -0
- package/dist/flows/flows.d.ts +4 -0
- package/dist/flows/flows.js +409 -0
- package/dist/flows/flows.types.d.ts +108 -0
- package/dist/flows/flows.types.js +1 -0
- package/dist/http/http.d.ts +8 -0
- package/dist/http/http.js +286 -0
- package/dist/http/http.types.d.ts +14 -0
- package/dist/http/http.types.js +1 -0
- package/dist/indexing/indexing.d.ts +6 -0
- package/dist/indexing/indexing.js +48 -0
- package/dist/indexing/indexing.types.d.ts +10 -0
- package/dist/indexing/indexing.types.js +1 -0
- package/dist/intel/intel.d.ts +3 -0
- package/dist/intel/intel.js +77 -0
- package/dist/intel/intel.types.d.ts +14 -0
- package/dist/intel/intel.types.js +1 -0
- package/dist/knowledge/knowledge.d.ts +2 -0
- package/dist/knowledge/knowledge.js +479 -0
- package/dist/knowledge/knowledge.types.d.ts +162 -0
- package/dist/knowledge/knowledge.types.js +1 -0
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +484 -0
- package/dist/mcp/mcp.types.d.ts +10 -0
- package/dist/mcp/mcp.types.js +1 -0
- package/dist/prepare/prepare.d.ts +4 -0
- package/dist/prepare/prepare.js +16 -0
- package/dist/prepare/prepare.types.d.ts +9 -0
- package/dist/prepare/prepare.types.js +1 -0
- package/dist/shared/gate-authorization/gate-authorization.d.ts +4 -0
- package/dist/shared/gate-authorization/gate-authorization.js +19 -0
- package/dist/shared/intel-error/intel-error.d.ts +5 -0
- package/dist/shared/intel-error/intel-error.js +10 -0
- package/dist/shared/problem-details/problem-details.d.ts +8 -0
- package/dist/shared/problem-details/problem-details.js +4 -0
- package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
- package/dist/shared/safe-return-path/safe-return-path.js +11 -0
- package/dist/shared/sha256/sha256.d.ts +1 -0
- package/dist/shared/sha256/sha256.js +9 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +107 -0
- package/dist/tools/tools.types.d.ts +56 -0
- package/dist/tools/tools.types.js +1 -0
- package/examples/branding/favicon.svg +5 -0
- package/examples/branding/logo.svg +5 -0
- package/examples/branding/theme.css +16 -0
- package/examples/dev.vars.example +7 -0
- package/examples/intel.json +9 -0
- package/migrations/0000_intel_foundation.sql +203 -0
- package/migrations/0001_portal_native_tools.sql +21 -0
- package/package.json +62 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { IntelError } from "../shared/intel-error/intel-error.js";
|
|
2
|
+
function decodeBase64(value) {
|
|
3
|
+
if (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
|
|
4
|
+
throw new IntelError(400, "attachment_invalid", "Attachment content is not valid base64");
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
return Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new IntelError(400, "attachment_invalid", "Attachment content is not valid base64");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createKnowledge(deps) {
|
|
14
|
+
function mergeSearchResults(lexical, semantic, semanticScores, limit) {
|
|
15
|
+
const merged = new Map();
|
|
16
|
+
for (const citation of lexical) {
|
|
17
|
+
merged.set(citation.nodeId, {
|
|
18
|
+
citation,
|
|
19
|
+
lexicalScore: citation.score,
|
|
20
|
+
semanticScore: undefined,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
for (const citation of semantic) {
|
|
24
|
+
const current = merged.get(citation.nodeId);
|
|
25
|
+
merged.set(citation.nodeId, {
|
|
26
|
+
citation: current?.citation ?? citation,
|
|
27
|
+
lexicalScore: current?.lexicalScore,
|
|
28
|
+
semanticScore: semanticScores.get(citation.nodeId) ?? citation.score,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return [...merged.values()]
|
|
32
|
+
.map(({ citation, lexicalScore, semanticScore }) => {
|
|
33
|
+
const score = lexicalScore !== undefined && semanticScore !== undefined
|
|
34
|
+
? lexicalScore * 0.45 + semanticScore * 0.55
|
|
35
|
+
: lexicalScore !== undefined
|
|
36
|
+
? lexicalScore * 0.9
|
|
37
|
+
: (semanticScore ?? 0) * 0.85;
|
|
38
|
+
return {
|
|
39
|
+
...citation,
|
|
40
|
+
score: Math.max(0, Math.min(1, score)),
|
|
41
|
+
match: lexicalScore !== undefined && semanticScore !== undefined
|
|
42
|
+
? "hybrid"
|
|
43
|
+
: lexicalScore !== undefined
|
|
44
|
+
? "lexical"
|
|
45
|
+
: "semantic",
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
.sort((left, right) => right.score - left.score || right.freshness.localeCompare(left.freshness))
|
|
49
|
+
.slice(0, limit);
|
|
50
|
+
}
|
|
51
|
+
async function getDocument(node) {
|
|
52
|
+
if (node.currentVersionId === null)
|
|
53
|
+
return { node, version: null, content: null };
|
|
54
|
+
const version = await deps.repository.getVersion(node.currentVersionId);
|
|
55
|
+
if (!version)
|
|
56
|
+
throw new IntelError(500, "version_missing", "Current version is missing");
|
|
57
|
+
if (node.kind === "attachment")
|
|
58
|
+
return { node, version, content: null };
|
|
59
|
+
const content = await deps.content.get(version.contentKey);
|
|
60
|
+
if (content === null)
|
|
61
|
+
throw new IntelError(500, "content_missing", "Version content is missing");
|
|
62
|
+
return { node, version, content };
|
|
63
|
+
}
|
|
64
|
+
async function requireVisible(actor, nodeId) {
|
|
65
|
+
const node = await deps.repository.getVisible(actor, nodeId);
|
|
66
|
+
if (!node)
|
|
67
|
+
throw new IntelError(404, "knowledge_not_found", "Knowledge was not found");
|
|
68
|
+
return node;
|
|
69
|
+
}
|
|
70
|
+
async function nextSequence(node) {
|
|
71
|
+
if (!node.currentVersionId)
|
|
72
|
+
return 1;
|
|
73
|
+
const current = await deps.repository.getVersion(node.currentVersionId);
|
|
74
|
+
if (!current) {
|
|
75
|
+
throw new IntelError(500, "version_missing", "Current Knowledge version is missing");
|
|
76
|
+
}
|
|
77
|
+
return current.sequence + 1;
|
|
78
|
+
}
|
|
79
|
+
async function attachment(actor, nodeId) {
|
|
80
|
+
const node = await requireVisible(actor, nodeId);
|
|
81
|
+
if (node.kind !== "attachment" || !node.currentVersionId) {
|
|
82
|
+
throw new IntelError(404, "attachment_not_found", "Attachment was not found");
|
|
83
|
+
}
|
|
84
|
+
const version = await deps.repository.getVersion(node.currentVersionId);
|
|
85
|
+
if (!version)
|
|
86
|
+
throw new IntelError(500, "version_missing", "Current version is missing");
|
|
87
|
+
return {
|
|
88
|
+
node,
|
|
89
|
+
version,
|
|
90
|
+
resourceUri: `intel://knowledge/${encodeURIComponent(node.id)}/attachment`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
async list(actor, input) {
|
|
95
|
+
return { items: await deps.repository.listVisible(actor, input) };
|
|
96
|
+
},
|
|
97
|
+
async get(actor, nodeId) {
|
|
98
|
+
return await getDocument(await requireVisible(actor, nodeId));
|
|
99
|
+
},
|
|
100
|
+
async create(actor, input) {
|
|
101
|
+
const existingId = await deps.repository.findIdempotentNode(actor.id, "knowledge.create", input.idempotencyKey);
|
|
102
|
+
if (existingId)
|
|
103
|
+
return await requireVisible(actor, existingId);
|
|
104
|
+
if (input.parentId !== null && !(await deps.repository.canEdit(actor, input.parentId))) {
|
|
105
|
+
throw new IntelError(403, "knowledge_forbidden", "Parent folder cannot be edited");
|
|
106
|
+
}
|
|
107
|
+
const timestamp = deps.now().toISOString();
|
|
108
|
+
return await deps.repository.insertNode({
|
|
109
|
+
node: {
|
|
110
|
+
id: deps.id(),
|
|
111
|
+
parentId: input.parentId,
|
|
112
|
+
kind: input.kind,
|
|
113
|
+
title: input.title,
|
|
114
|
+
description: input.description,
|
|
115
|
+
contextPolicy: input.contextPolicy,
|
|
116
|
+
ownerId: actor.id,
|
|
117
|
+
currentVersionId: null,
|
|
118
|
+
createdAt: timestamp,
|
|
119
|
+
updatedAt: timestamp,
|
|
120
|
+
archivedAt: null,
|
|
121
|
+
},
|
|
122
|
+
actorId: actor.id,
|
|
123
|
+
idempotencyKey: input.idempotencyKey,
|
|
124
|
+
auditId: deps.id(),
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async save(actor, input) {
|
|
128
|
+
const existingId = await deps.repository.findIdempotentNode(actor.id, "knowledge.save", input.idempotencyKey);
|
|
129
|
+
if (existingId) {
|
|
130
|
+
const document = await getDocument(await requireVisible(actor, input.nodeId));
|
|
131
|
+
await deps.indexing.enqueue(existingId);
|
|
132
|
+
return document;
|
|
133
|
+
}
|
|
134
|
+
const node = await requireVisible(actor, input.nodeId);
|
|
135
|
+
if (node.kind !== "document") {
|
|
136
|
+
throw new IntelError(409, "document_content_required", "Only documents accept editor content versions");
|
|
137
|
+
}
|
|
138
|
+
if (!(await deps.repository.canEdit(actor, node.id))) {
|
|
139
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge cannot be edited");
|
|
140
|
+
}
|
|
141
|
+
if (node.currentVersionId !== input.baseVersionId) {
|
|
142
|
+
throw new IntelError(409, "version_conflict", "A newer version already exists");
|
|
143
|
+
}
|
|
144
|
+
const versionId = deps.id();
|
|
145
|
+
const contentKey = `knowledge/${node.id}/versions/${versionId}`;
|
|
146
|
+
const createdAt = deps.now().toISOString();
|
|
147
|
+
const version = {
|
|
148
|
+
id: versionId,
|
|
149
|
+
nodeId: node.id,
|
|
150
|
+
sequence: await nextSequence(node),
|
|
151
|
+
contentKey,
|
|
152
|
+
mediaType: input.mediaType,
|
|
153
|
+
contentHash: await deps.hash(input.content),
|
|
154
|
+
size: new TextEncoder().encode(input.content).byteLength,
|
|
155
|
+
createdBy: actor.id,
|
|
156
|
+
createdAt,
|
|
157
|
+
};
|
|
158
|
+
await deps.content.put(contentKey, input.content, input.mediaType);
|
|
159
|
+
let saved;
|
|
160
|
+
try {
|
|
161
|
+
saved = await deps.repository.appendVersion({
|
|
162
|
+
version,
|
|
163
|
+
actorId: actor.id,
|
|
164
|
+
baseVersionId: input.baseVersionId,
|
|
165
|
+
idempotencyKey: input.idempotencyKey,
|
|
166
|
+
auditId: deps.id(),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
await deps.content.delete(contentKey).catch(() => undefined);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
if (saved === "conflict") {
|
|
174
|
+
await deps.content.delete(contentKey);
|
|
175
|
+
const replayed = await deps.repository.findIdempotentNode(actor.id, "knowledge.save", input.idempotencyKey);
|
|
176
|
+
if (replayed)
|
|
177
|
+
return await getDocument(await requireVisible(actor, node.id));
|
|
178
|
+
throw new IntelError(409, "version_conflict", "A newer version already exists");
|
|
179
|
+
}
|
|
180
|
+
const updated = await requireVisible(actor, node.id);
|
|
181
|
+
await deps.indexing.enqueue(version.id);
|
|
182
|
+
return { node: updated, version, content: input.content };
|
|
183
|
+
},
|
|
184
|
+
async saveAttachment(actor, input) {
|
|
185
|
+
const existingId = await deps.repository.findIdempotentNode(actor.id, "knowledge.save", input.idempotencyKey);
|
|
186
|
+
if (existingId) {
|
|
187
|
+
const document = await getDocument(await requireVisible(actor, input.nodeId));
|
|
188
|
+
await deps.indexing.enqueue(existingId);
|
|
189
|
+
return document;
|
|
190
|
+
}
|
|
191
|
+
const node = await requireVisible(actor, input.nodeId);
|
|
192
|
+
if (node.kind !== "attachment") {
|
|
193
|
+
throw new IntelError(409, "not_an_attachment", "Only attachment nodes accept file uploads");
|
|
194
|
+
}
|
|
195
|
+
if (!(await deps.repository.canEdit(actor, node.id))) {
|
|
196
|
+
throw new IntelError(403, "knowledge_forbidden", "Attachment cannot be edited");
|
|
197
|
+
}
|
|
198
|
+
if (node.currentVersionId !== input.baseVersionId) {
|
|
199
|
+
throw new IntelError(409, "version_conflict", "A newer version already exists");
|
|
200
|
+
}
|
|
201
|
+
const bytes = decodeBase64(input.contentBase64);
|
|
202
|
+
const versionId = deps.id();
|
|
203
|
+
const contentKey = `knowledge/${node.id}/versions/${versionId}`;
|
|
204
|
+
const createdAt = deps.now().toISOString();
|
|
205
|
+
const version = {
|
|
206
|
+
id: versionId,
|
|
207
|
+
nodeId: node.id,
|
|
208
|
+
sequence: await nextSequence(node),
|
|
209
|
+
contentKey,
|
|
210
|
+
mediaType: input.mediaType,
|
|
211
|
+
contentHash: await deps.hash(bytes),
|
|
212
|
+
size: bytes.byteLength,
|
|
213
|
+
createdBy: actor.id,
|
|
214
|
+
createdAt,
|
|
215
|
+
};
|
|
216
|
+
await deps.content.putBytes(contentKey, bytes.buffer, input.mediaType);
|
|
217
|
+
let saved;
|
|
218
|
+
try {
|
|
219
|
+
saved = await deps.repository.appendVersion({
|
|
220
|
+
version,
|
|
221
|
+
actorId: actor.id,
|
|
222
|
+
baseVersionId: input.baseVersionId,
|
|
223
|
+
idempotencyKey: input.idempotencyKey,
|
|
224
|
+
auditId: deps.id(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
await deps.content.delete(contentKey).catch(() => undefined);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
if (saved === "conflict") {
|
|
232
|
+
await deps.content.delete(contentKey);
|
|
233
|
+
throw new IntelError(409, "version_conflict", "A newer version already exists");
|
|
234
|
+
}
|
|
235
|
+
await deps.indexing.enqueue(version.id);
|
|
236
|
+
return { node: await requireVisible(actor, node.id), version, content: null };
|
|
237
|
+
},
|
|
238
|
+
async getAttachment(actor, nodeId) {
|
|
239
|
+
return await attachment(actor, nodeId);
|
|
240
|
+
},
|
|
241
|
+
async readAttachment(actor, nodeId) {
|
|
242
|
+
const metadata = await attachment(actor, nodeId);
|
|
243
|
+
const body = await deps.content.getStream(metadata.version.contentKey);
|
|
244
|
+
if (body === null)
|
|
245
|
+
throw new IntelError(500, "content_missing", "Attachment is missing");
|
|
246
|
+
return { attachment: metadata, body };
|
|
247
|
+
},
|
|
248
|
+
async listVersions(actor, nodeId) {
|
|
249
|
+
await requireVisible(actor, nodeId);
|
|
250
|
+
return { items: await deps.repository.listVersions(nodeId) };
|
|
251
|
+
},
|
|
252
|
+
async update(actor, input) {
|
|
253
|
+
const current = await requireVisible(actor, input.nodeId);
|
|
254
|
+
if (!(await deps.repository.canEdit(actor, current.id))) {
|
|
255
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge cannot be edited");
|
|
256
|
+
}
|
|
257
|
+
const replayedId = await deps.repository.findIdempotentNode(actor.id, "knowledge.update", input.idempotencyKey);
|
|
258
|
+
if (replayedId)
|
|
259
|
+
return await requireVisible(actor, replayedId);
|
|
260
|
+
if (input.parentId !== undefined && input.parentId !== null) {
|
|
261
|
+
if (input.parentId === current.id) {
|
|
262
|
+
throw new IntelError(409, "move_cycle", "Knowledge cannot contain itself");
|
|
263
|
+
}
|
|
264
|
+
const parent = await requireVisible(actor, input.parentId);
|
|
265
|
+
if (parent.kind !== "folder") {
|
|
266
|
+
throw new IntelError(409, "parent_not_folder", "Knowledge parent must be a folder");
|
|
267
|
+
}
|
|
268
|
+
if (!(await deps.repository.canEdit(actor, parent.id))) {
|
|
269
|
+
throw new IntelError(403, "knowledge_forbidden", "Destination folder cannot be edited");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const updatedAt = deps.now().toISOString();
|
|
273
|
+
const updated = await deps.repository.updateNode({
|
|
274
|
+
node: {
|
|
275
|
+
...current,
|
|
276
|
+
parentId: input.parentId === undefined ? current.parentId : input.parentId,
|
|
277
|
+
title: input.title ?? current.title,
|
|
278
|
+
description: input.description === undefined ? current.description : input.description,
|
|
279
|
+
contextPolicy: input.contextPolicy ?? current.contextPolicy,
|
|
280
|
+
updatedAt,
|
|
281
|
+
},
|
|
282
|
+
baseUpdatedAt: input.baseUpdatedAt,
|
|
283
|
+
actorId: actor.id,
|
|
284
|
+
idempotencyKey: input.idempotencyKey,
|
|
285
|
+
auditId: deps.id(),
|
|
286
|
+
});
|
|
287
|
+
if (updated === "cycle") {
|
|
288
|
+
throw new IntelError(409, "move_cycle", "Knowledge cannot be moved into its descendant");
|
|
289
|
+
}
|
|
290
|
+
if (updated === "conflict") {
|
|
291
|
+
throw new IntelError(409, "update_conflict", "Knowledge was changed by another editor");
|
|
292
|
+
}
|
|
293
|
+
if (updated.currentVersionId)
|
|
294
|
+
await deps.indexing.enqueue(updated.currentVersionId);
|
|
295
|
+
return updated;
|
|
296
|
+
},
|
|
297
|
+
async archive(actor, input) {
|
|
298
|
+
const current = await requireVisible(actor, input.nodeId);
|
|
299
|
+
if (!(await deps.repository.canEdit(actor, current.id))) {
|
|
300
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge cannot be edited");
|
|
301
|
+
}
|
|
302
|
+
const replayedId = await deps.repository.findIdempotentNode(actor.id, "knowledge.archive", input.idempotencyKey);
|
|
303
|
+
if (replayedId)
|
|
304
|
+
return await requireVisible(actor, replayedId);
|
|
305
|
+
const updatedAt = deps.now().toISOString();
|
|
306
|
+
const updated = await deps.repository.archiveNode({
|
|
307
|
+
nodeId: current.id,
|
|
308
|
+
baseUpdatedAt: input.baseUpdatedAt,
|
|
309
|
+
archivedAt: input.archived ? updatedAt : null,
|
|
310
|
+
updatedAt,
|
|
311
|
+
actorId: actor.id,
|
|
312
|
+
idempotencyKey: input.idempotencyKey,
|
|
313
|
+
auditId: deps.id(),
|
|
314
|
+
});
|
|
315
|
+
if (updated === "conflict") {
|
|
316
|
+
throw new IntelError(409, "update_conflict", "Knowledge was changed by another editor");
|
|
317
|
+
}
|
|
318
|
+
return updated;
|
|
319
|
+
},
|
|
320
|
+
async listGrants(actor, resourceId) {
|
|
321
|
+
await requireVisible(actor, resourceId);
|
|
322
|
+
if (!(await deps.repository.canManage(actor, resourceId))) {
|
|
323
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge sharing cannot be managed");
|
|
324
|
+
}
|
|
325
|
+
return { items: await deps.repository.listGrants(resourceId) };
|
|
326
|
+
},
|
|
327
|
+
async listLinks(actor, nodeId) {
|
|
328
|
+
await requireVisible(actor, nodeId);
|
|
329
|
+
return { items: await deps.repository.listLinksVisible(actor, nodeId) };
|
|
330
|
+
},
|
|
331
|
+
async createLink(actor, input) {
|
|
332
|
+
await requireVisible(actor, input.sourceNodeId);
|
|
333
|
+
await requireVisible(actor, input.targetNodeId);
|
|
334
|
+
if (!(await deps.repository.canEdit(actor, input.sourceNodeId))) {
|
|
335
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge links cannot be edited");
|
|
336
|
+
}
|
|
337
|
+
const replayedId = await deps.repository.findIdempotentNode(actor.id, "knowledge.link", input.idempotencyKey);
|
|
338
|
+
if (replayedId) {
|
|
339
|
+
const replayed = await deps.repository.getLinkVisible(actor, replayedId);
|
|
340
|
+
if (replayed)
|
|
341
|
+
return replayed;
|
|
342
|
+
}
|
|
343
|
+
const existing = await deps.repository.findLinkVisible(actor, input);
|
|
344
|
+
if (existing)
|
|
345
|
+
return existing;
|
|
346
|
+
const createdAt = deps.now().toISOString();
|
|
347
|
+
return await deps.repository.insertLink({
|
|
348
|
+
link: {
|
|
349
|
+
id: deps.id(),
|
|
350
|
+
sourceNodeId: input.sourceNodeId,
|
|
351
|
+
targetNodeId: input.targetNodeId,
|
|
352
|
+
relation: input.relation,
|
|
353
|
+
label: input.label,
|
|
354
|
+
createdBy: actor.id,
|
|
355
|
+
createdAt,
|
|
356
|
+
},
|
|
357
|
+
actorId: actor.id,
|
|
358
|
+
idempotencyKey: input.idempotencyKey,
|
|
359
|
+
auditId: deps.id(),
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
async deleteLink(actor, input) {
|
|
363
|
+
await requireVisible(actor, input.sourceNodeId);
|
|
364
|
+
if (!(await deps.repository.canEdit(actor, input.sourceNodeId))) {
|
|
365
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge links cannot be edited");
|
|
366
|
+
}
|
|
367
|
+
const replayed = await deps.repository.findIdempotentNode(actor.id, "knowledge.unlink", input.idempotencyKey);
|
|
368
|
+
if (replayed)
|
|
369
|
+
return { deleted: replayed.startsWith("1:") };
|
|
370
|
+
const link = await deps.repository.getLinkVisible(actor, input.linkId);
|
|
371
|
+
if (!link || link.sourceNodeId !== input.sourceNodeId) {
|
|
372
|
+
throw new IntelError(404, "knowledge_link_not_found", "Knowledge link was not found");
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
deleted: await deps.repository.deleteLink({
|
|
376
|
+
sourceNodeId: input.sourceNodeId,
|
|
377
|
+
linkId: input.linkId,
|
|
378
|
+
actorId: actor.id,
|
|
379
|
+
idempotencyKey: input.idempotencyKey,
|
|
380
|
+
auditId: deps.id(),
|
|
381
|
+
occurredAt: deps.now().toISOString(),
|
|
382
|
+
}),
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
async graph(actor, input) {
|
|
386
|
+
return await deps.repository.graphVisible(actor, input);
|
|
387
|
+
},
|
|
388
|
+
async share(actor, input) {
|
|
389
|
+
await requireVisible(actor, input.resourceId);
|
|
390
|
+
if (!(await deps.repository.canManage(actor, input.resourceId))) {
|
|
391
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge sharing cannot be managed");
|
|
392
|
+
}
|
|
393
|
+
const replayedId = await deps.repository.findIdempotentNode(actor.id, "knowledge.share", input.idempotencyKey);
|
|
394
|
+
if (replayedId) {
|
|
395
|
+
const replayed = (await deps.repository.listGrants(input.resourceId)).find((grant) => grant.id === replayedId);
|
|
396
|
+
if (replayed)
|
|
397
|
+
return replayed;
|
|
398
|
+
}
|
|
399
|
+
const principal = input.principal.type === "email"
|
|
400
|
+
? { type: "email", email: input.principal.email.toLowerCase() }
|
|
401
|
+
: input.principal;
|
|
402
|
+
const timestamp = deps.now().toISOString();
|
|
403
|
+
return await deps.repository.setGrant({
|
|
404
|
+
grant: {
|
|
405
|
+
id: deps.id(),
|
|
406
|
+
resourceId: input.resourceId,
|
|
407
|
+
principal,
|
|
408
|
+
role: input.role,
|
|
409
|
+
expiresAt: input.expiresAt,
|
|
410
|
+
createdBy: actor.id,
|
|
411
|
+
createdAt: timestamp,
|
|
412
|
+
},
|
|
413
|
+
actorId: actor.id,
|
|
414
|
+
idempotencyKey: input.idempotencyKey,
|
|
415
|
+
auditId: deps.id(),
|
|
416
|
+
});
|
|
417
|
+
},
|
|
418
|
+
async revokeGrant(actor, input) {
|
|
419
|
+
await requireVisible(actor, input.resourceId);
|
|
420
|
+
if (!(await deps.repository.canManage(actor, input.resourceId))) {
|
|
421
|
+
throw new IntelError(403, "knowledge_forbidden", "Knowledge sharing cannot be managed");
|
|
422
|
+
}
|
|
423
|
+
const replayed = await deps.repository.findIdempotentRevocation(actor.id, input.idempotencyKey);
|
|
424
|
+
if (replayed !== null)
|
|
425
|
+
return { revoked: replayed };
|
|
426
|
+
return {
|
|
427
|
+
revoked: await deps.repository.revokeGrant({
|
|
428
|
+
resourceId: input.resourceId,
|
|
429
|
+
grantId: input.grantId,
|
|
430
|
+
actorId: actor.id,
|
|
431
|
+
idempotencyKey: input.idempotencyKey,
|
|
432
|
+
auditId: deps.id(),
|
|
433
|
+
occurredAt: deps.now().toISOString(),
|
|
434
|
+
}),
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
async search(actor, input) {
|
|
438
|
+
const lexical = await deps.repository.searchVisible(actor, {
|
|
439
|
+
...input,
|
|
440
|
+
limit: Math.min(50, input.limit * 2),
|
|
441
|
+
});
|
|
442
|
+
if (!deps.semantic)
|
|
443
|
+
return { items: lexical.slice(0, input.limit) };
|
|
444
|
+
try {
|
|
445
|
+
const hits = await deps.semantic.search(input.query, Math.min(100, input.limit * 4));
|
|
446
|
+
const semanticScores = new Map();
|
|
447
|
+
for (const hit of hits) {
|
|
448
|
+
semanticScores.set(hit.nodeId, Math.max(semanticScores.get(hit.nodeId) ?? 0, hit.score));
|
|
449
|
+
}
|
|
450
|
+
const semantic = await deps.repository.hydrateVisibleCitations(actor, [
|
|
451
|
+
...semanticScores.keys(),
|
|
452
|
+
]);
|
|
453
|
+
return {
|
|
454
|
+
items: mergeSearchResults(lexical, semantic, semanticScores, input.limit),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return { items: lexical.slice(0, input.limit) };
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
async reindex(actor) {
|
|
462
|
+
if (actor.canReindex !== true) {
|
|
463
|
+
throw new IntelError(403, "knowledge_reindex_forbidden", "Reindex permission is required");
|
|
464
|
+
}
|
|
465
|
+
let queued = 0;
|
|
466
|
+
let after = null;
|
|
467
|
+
for (;;) {
|
|
468
|
+
const versionIds = await deps.repository.listCurrentVersionIds({ after, limit: 100 });
|
|
469
|
+
for (const versionId of versionIds)
|
|
470
|
+
await deps.indexing.enqueue(versionId);
|
|
471
|
+
queued += versionIds.length;
|
|
472
|
+
if (versionIds.length < 100)
|
|
473
|
+
break;
|
|
474
|
+
after = versionIds.at(-1) ?? null;
|
|
475
|
+
}
|
|
476
|
+
return { queued };
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { ArchiveKnowledgeNodeInput, CreateKnowledgeLinkInput, CreateKnowledgeNodeInput, DeleteKnowledgeLinkInput, KnowledgeAttachment, KnowledgeCitation, KnowledgeDocument, KnowledgeGraph, KnowledgeGraphInput, KnowledgeLink, KnowledgeNode, KnowledgeVersion, ListKnowledgeNodesInput, ResourceGrant, RevokeKnowledgeGrantInput, SaveKnowledgeAttachmentInput, SaveKnowledgeVersionInput, SearchKnowledgeInput, ShareKnowledgeInput, UpdateKnowledgeNodeInput } from "@anchrd/intel-contract";
|
|
2
|
+
import type { SemanticIndex } from "../adapters/semantic-index/semantic-index.types.js";
|
|
3
|
+
export interface Actor {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
canReindex?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface NewKnowledgeNode {
|
|
9
|
+
node: KnowledgeNode;
|
|
10
|
+
actorId: string;
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
auditId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface NewKnowledgeVersion {
|
|
15
|
+
version: KnowledgeVersion;
|
|
16
|
+
actorId: string;
|
|
17
|
+
baseVersionId: string | null;
|
|
18
|
+
idempotencyKey: string;
|
|
19
|
+
auditId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface KnowledgeRepository {
|
|
22
|
+
listVisible(actor: Actor, input: ListKnowledgeNodesInput): Promise<KnowledgeNode[]>;
|
|
23
|
+
getVisible(actor: Actor, nodeId: string): Promise<KnowledgeNode | null>;
|
|
24
|
+
canEdit(actor: Actor, nodeId: string): Promise<boolean>;
|
|
25
|
+
canManage(actor: Actor, nodeId: string): Promise<boolean>;
|
|
26
|
+
findIdempotentNode(actorId: string, operation: "knowledge.create" | "knowledge.save" | "knowledge.update" | "knowledge.archive" | "knowledge.link" | "knowledge.unlink" | "knowledge.share" | "knowledge.revoke", idempotencyKey: string): Promise<string | null>;
|
|
27
|
+
findIdempotentRevocation(actorId: string, idempotencyKey: string): Promise<boolean | null>;
|
|
28
|
+
insertNode(input: NewKnowledgeNode): Promise<KnowledgeNode>;
|
|
29
|
+
getVersion(versionId: string): Promise<KnowledgeVersion | null>;
|
|
30
|
+
appendVersion(input: NewKnowledgeVersion): Promise<"saved" | "conflict">;
|
|
31
|
+
listVersions(nodeId: string): Promise<KnowledgeVersion[]>;
|
|
32
|
+
updateNode(input: {
|
|
33
|
+
node: KnowledgeNode;
|
|
34
|
+
baseUpdatedAt: string;
|
|
35
|
+
actorId: string;
|
|
36
|
+
idempotencyKey: string;
|
|
37
|
+
auditId: string;
|
|
38
|
+
}): Promise<"cycle" | "conflict" | KnowledgeNode>;
|
|
39
|
+
archiveNode(input: {
|
|
40
|
+
nodeId: string;
|
|
41
|
+
baseUpdatedAt: string;
|
|
42
|
+
archivedAt: string | null;
|
|
43
|
+
updatedAt: string;
|
|
44
|
+
actorId: string;
|
|
45
|
+
idempotencyKey: string;
|
|
46
|
+
auditId: string;
|
|
47
|
+
}): Promise<"conflict" | KnowledgeNode>;
|
|
48
|
+
listGrants(resourceId: string): Promise<ResourceGrant[]>;
|
|
49
|
+
listLinksVisible(actor: Actor, nodeId: string): Promise<KnowledgeLink[]>;
|
|
50
|
+
getLinkVisible(actor: Actor, linkId: string): Promise<KnowledgeLink | null>;
|
|
51
|
+
findLinkVisible(actor: Actor, input: Pick<CreateKnowledgeLinkInput, "sourceNodeId" | "targetNodeId" | "relation">): Promise<KnowledgeLink | null>;
|
|
52
|
+
insertLink(input: {
|
|
53
|
+
link: KnowledgeLink;
|
|
54
|
+
actorId: string;
|
|
55
|
+
idempotencyKey: string;
|
|
56
|
+
auditId: string;
|
|
57
|
+
}): Promise<KnowledgeLink>;
|
|
58
|
+
deleteLink(input: {
|
|
59
|
+
sourceNodeId: string;
|
|
60
|
+
linkId: string;
|
|
61
|
+
actorId: string;
|
|
62
|
+
idempotencyKey: string;
|
|
63
|
+
auditId: string;
|
|
64
|
+
occurredAt: string;
|
|
65
|
+
}): Promise<boolean>;
|
|
66
|
+
graphVisible(actor: Actor, input: KnowledgeGraphInput): Promise<KnowledgeGraph>;
|
|
67
|
+
setGrant(input: {
|
|
68
|
+
grant: ResourceGrant;
|
|
69
|
+
actorId: string;
|
|
70
|
+
idempotencyKey: string;
|
|
71
|
+
auditId: string;
|
|
72
|
+
}): Promise<ResourceGrant>;
|
|
73
|
+
revokeGrant(input: {
|
|
74
|
+
resourceId: string;
|
|
75
|
+
grantId: string;
|
|
76
|
+
actorId: string;
|
|
77
|
+
idempotencyKey: string;
|
|
78
|
+
auditId: string;
|
|
79
|
+
occurredAt: string;
|
|
80
|
+
}): Promise<boolean>;
|
|
81
|
+
searchVisible(actor: Actor, input: SearchKnowledgeInput): Promise<KnowledgeCitation[]>;
|
|
82
|
+
hydrateVisibleCitations(actor: Actor, nodeIds: string[]): Promise<KnowledgeCitation[]>;
|
|
83
|
+
listCurrentVersionIds(input: {
|
|
84
|
+
after: string | null;
|
|
85
|
+
limit: number;
|
|
86
|
+
}): Promise<string[]>;
|
|
87
|
+
}
|
|
88
|
+
export interface ContentStore {
|
|
89
|
+
get(key: string): Promise<string | null>;
|
|
90
|
+
getBytes(key: string): Promise<ArrayBuffer | null>;
|
|
91
|
+
getStream(key: string): Promise<ReadableStream<Uint8Array> | null>;
|
|
92
|
+
put(key: string, content: string, mediaType: string): Promise<void>;
|
|
93
|
+
putBytes(key: string, content: ArrayBuffer, mediaType: string): Promise<void>;
|
|
94
|
+
delete(key: string): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
export interface KnowledgeAttachmentBody {
|
|
97
|
+
attachment: KnowledgeAttachment;
|
|
98
|
+
body: ReadableStream<Uint8Array>;
|
|
99
|
+
}
|
|
100
|
+
export interface KnowledgeDeps {
|
|
101
|
+
repository: KnowledgeRepository;
|
|
102
|
+
content: ContentStore;
|
|
103
|
+
id(): string;
|
|
104
|
+
now(): Date;
|
|
105
|
+
hash(content: string | Uint8Array): Promise<string>;
|
|
106
|
+
indexing: {
|
|
107
|
+
enqueue(versionId: string): Promise<void>;
|
|
108
|
+
};
|
|
109
|
+
semantic?: SemanticIndex | undefined;
|
|
110
|
+
}
|
|
111
|
+
export interface KnowledgeService {
|
|
112
|
+
list(actor: Actor, input: ListKnowledgeNodesInput): Promise<{
|
|
113
|
+
items: KnowledgeNode[];
|
|
114
|
+
}>;
|
|
115
|
+
get(actor: Actor, nodeId: string): Promise<KnowledgeDocument>;
|
|
116
|
+
create(actor: Actor, input: CreateKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
117
|
+
save(actor: Actor, input: SaveKnowledgeVersionInput): Promise<KnowledgeDocument>;
|
|
118
|
+
saveAttachment(actor: Actor, input: SaveKnowledgeAttachmentInput): Promise<KnowledgeDocument>;
|
|
119
|
+
getAttachment(actor: Actor, nodeId: string): Promise<KnowledgeAttachment>;
|
|
120
|
+
readAttachment(actor: Actor, nodeId: string): Promise<KnowledgeAttachmentBody>;
|
|
121
|
+
listVersions(actor: Actor, nodeId: string): Promise<{
|
|
122
|
+
items: KnowledgeVersion[];
|
|
123
|
+
}>;
|
|
124
|
+
update(actor: Actor, input: UpdateKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
125
|
+
archive(actor: Actor, input: ArchiveKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
126
|
+
listGrants(actor: Actor, resourceId: string): Promise<{
|
|
127
|
+
items: ResourceGrant[];
|
|
128
|
+
}>;
|
|
129
|
+
listLinks(actor: Actor, nodeId: string): Promise<{
|
|
130
|
+
items: KnowledgeLink[];
|
|
131
|
+
}>;
|
|
132
|
+
createLink(actor: Actor, input: CreateKnowledgeLinkInput): Promise<KnowledgeLink>;
|
|
133
|
+
deleteLink(actor: Actor, input: DeleteKnowledgeLinkInput): Promise<{
|
|
134
|
+
deleted: boolean;
|
|
135
|
+
}>;
|
|
136
|
+
graph(actor: Actor, input: KnowledgeGraphInput): Promise<KnowledgeGraph>;
|
|
137
|
+
share(actor: Actor, input: ShareKnowledgeInput): Promise<ResourceGrant>;
|
|
138
|
+
revokeGrant(actor: Actor, input: RevokeKnowledgeGrantInput): Promise<{
|
|
139
|
+
revoked: boolean;
|
|
140
|
+
}>;
|
|
141
|
+
search(actor: Actor, input: SearchKnowledgeInput): Promise<{
|
|
142
|
+
items: KnowledgeCitation[];
|
|
143
|
+
}>;
|
|
144
|
+
reindex(actor: Actor): Promise<{
|
|
145
|
+
queued: number;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
export interface KnowledgeIndexTarget {
|
|
149
|
+
nodeId: string;
|
|
150
|
+
versionId: string;
|
|
151
|
+
title: string;
|
|
152
|
+
contentKey: string;
|
|
153
|
+
mediaType: string;
|
|
154
|
+
kind: "document" | "attachment";
|
|
155
|
+
updatedAt: string;
|
|
156
|
+
}
|
|
157
|
+
export interface KnowledgeIndexRepository {
|
|
158
|
+
getTarget(versionId: string): Promise<KnowledgeIndexTarget | null>;
|
|
159
|
+
replace(target: KnowledgeIndexTarget, content: string): Promise<void>;
|
|
160
|
+
markIndexed(versionId: string, occurredAt: string): Promise<void>;
|
|
161
|
+
markError(versionId: string, message: string, occurredAt: string): Promise<void>;
|
|
162
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|