@semiont/make-meaning 0.1.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 +742 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +715 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
// src/resource-context.ts
|
|
2
|
+
import { FilesystemViewStorage } from "@semiont/event-sourcing";
|
|
3
|
+
import { FilesystemRepresentationStore } from "@semiont/content";
|
|
4
|
+
import { getPrimaryRepresentation, decodeRepresentation } from "@semiont/api-client";
|
|
5
|
+
var ResourceContext = class {
|
|
6
|
+
/**
|
|
7
|
+
* Get resource metadata from view storage
|
|
8
|
+
*/
|
|
9
|
+
static async getResourceMetadata(resourceId, config) {
|
|
10
|
+
const basePath = config.services.filesystem.path;
|
|
11
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
12
|
+
const viewStorage = new FilesystemViewStorage(basePath, projectRoot);
|
|
13
|
+
const view = await viewStorage.get(resourceId);
|
|
14
|
+
if (!view) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return view.resource;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* List all resources by scanning view storage
|
|
21
|
+
*/
|
|
22
|
+
static async listResources(filters, config) {
|
|
23
|
+
const basePath = config.services.filesystem.path;
|
|
24
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
25
|
+
const viewStorage = new FilesystemViewStorage(basePath, projectRoot);
|
|
26
|
+
const allViews = await viewStorage.getAll();
|
|
27
|
+
const resources = [];
|
|
28
|
+
for (const view of allViews) {
|
|
29
|
+
const doc = view.resource;
|
|
30
|
+
if (filters?.archived !== void 0 && doc.archived !== filters.archived) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (filters?.search) {
|
|
34
|
+
const searchLower = filters.search.toLowerCase();
|
|
35
|
+
if (!doc.name.toLowerCase().includes(searchLower)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
resources.push(doc);
|
|
40
|
+
}
|
|
41
|
+
resources.sort((a, b) => {
|
|
42
|
+
const aTime = a.dateCreated ? new Date(a.dateCreated).getTime() : 0;
|
|
43
|
+
const bTime = b.dateCreated ? new Date(b.dateCreated).getTime() : 0;
|
|
44
|
+
return bTime - aTime;
|
|
45
|
+
});
|
|
46
|
+
return resources;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Add content previews to resources (for search results)
|
|
50
|
+
* Retrieves and decodes the first 200 characters of each resource's primary representation
|
|
51
|
+
*/
|
|
52
|
+
static async addContentPreviews(resources, config) {
|
|
53
|
+
const basePath = config.services.filesystem.path;
|
|
54
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
55
|
+
const repStore = new FilesystemRepresentationStore({ basePath }, projectRoot);
|
|
56
|
+
return await Promise.all(
|
|
57
|
+
resources.map(async (doc) => {
|
|
58
|
+
try {
|
|
59
|
+
const primaryRep = getPrimaryRepresentation(doc);
|
|
60
|
+
if (primaryRep?.checksum && primaryRep?.mediaType) {
|
|
61
|
+
const contentBuffer = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
|
|
62
|
+
const contentPreview = decodeRepresentation(contentBuffer, primaryRep.mediaType).slice(0, 200);
|
|
63
|
+
return { ...doc, content: contentPreview };
|
|
64
|
+
}
|
|
65
|
+
return { ...doc, content: "" };
|
|
66
|
+
} catch {
|
|
67
|
+
return { ...doc, content: "" };
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/annotation-context.ts
|
|
75
|
+
import { generateResourceSummary, generateText } from "@semiont/inference";
|
|
76
|
+
import {
|
|
77
|
+
getBodySource,
|
|
78
|
+
getTargetSource,
|
|
79
|
+
getTargetSelector,
|
|
80
|
+
getResourceEntityTypes,
|
|
81
|
+
getTextPositionSelector,
|
|
82
|
+
getPrimaryRepresentation as getPrimaryRepresentation2,
|
|
83
|
+
decodeRepresentation as decodeRepresentation2
|
|
84
|
+
} from "@semiont/api-client";
|
|
85
|
+
import { FilesystemRepresentationStore as FilesystemRepresentationStore2 } from "@semiont/content";
|
|
86
|
+
import { FilesystemViewStorage as FilesystemViewStorage2 } from "@semiont/event-sourcing";
|
|
87
|
+
import { resourceId as createResourceId, uriToResourceId } from "@semiont/core";
|
|
88
|
+
import { getEntityTypes } from "@semiont/ontology";
|
|
89
|
+
var AnnotationContext = class {
|
|
90
|
+
/**
|
|
91
|
+
* Build LLM context for an annotation
|
|
92
|
+
*
|
|
93
|
+
* @param annotationUri - Full annotation URI (e.g., http://localhost:4000/annotations/abc123)
|
|
94
|
+
* @param resourceId - Source resource ID
|
|
95
|
+
* @param config - Application configuration
|
|
96
|
+
* @param options - Context building options
|
|
97
|
+
* @returns Rich context for LLM processing
|
|
98
|
+
* @throws Error if annotation or resource not found
|
|
99
|
+
*/
|
|
100
|
+
static async buildLLMContext(annotationUri, resourceId, config, options = {}) {
|
|
101
|
+
const {
|
|
102
|
+
includeSourceContext = true,
|
|
103
|
+
includeTargetContext = true,
|
|
104
|
+
contextWindow = 1e3
|
|
105
|
+
} = options;
|
|
106
|
+
if (contextWindow < 100 || contextWindow > 5e3) {
|
|
107
|
+
throw new Error("contextWindow must be between 100 and 5000");
|
|
108
|
+
}
|
|
109
|
+
console.log(`[AnnotationContext] buildLLMContext called with annotationUri=${annotationUri}, resourceId=${resourceId}`);
|
|
110
|
+
const basePath = config.services.filesystem.path;
|
|
111
|
+
console.log(`[AnnotationContext] basePath=${basePath}`);
|
|
112
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
113
|
+
const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
|
|
114
|
+
const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
|
|
115
|
+
console.log(`[AnnotationContext] Getting view for resourceId=${resourceId}`);
|
|
116
|
+
let sourceView;
|
|
117
|
+
try {
|
|
118
|
+
sourceView = await viewStorage.get(resourceId);
|
|
119
|
+
console.log(`[AnnotationContext] Got view:`, !!sourceView);
|
|
120
|
+
if (!sourceView) {
|
|
121
|
+
throw new Error("Source resource not found");
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`[AnnotationContext] Error getting view:`, error);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
console.log(`[AnnotationContext] Looking for annotation ${annotationUri} in resource ${resourceId}`);
|
|
128
|
+
console.log(`[AnnotationContext] View has ${sourceView.annotations.annotations.length} annotations`);
|
|
129
|
+
console.log(`[AnnotationContext] First 5 annotation IDs:`, sourceView.annotations.annotations.slice(0, 5).map((a) => a.id));
|
|
130
|
+
const annotation = sourceView.annotations.annotations.find((a) => a.id === annotationUri);
|
|
131
|
+
console.log(`[AnnotationContext] Found annotation:`, !!annotation);
|
|
132
|
+
if (!annotation) {
|
|
133
|
+
throw new Error("Annotation not found in view");
|
|
134
|
+
}
|
|
135
|
+
const targetSource = getTargetSource(annotation.target);
|
|
136
|
+
const targetResourceId = targetSource.split("/").pop();
|
|
137
|
+
console.log(`[AnnotationContext] Target source: ${targetSource}, Expected resource ID: ${resourceId}, Extracted ID: ${targetResourceId}`);
|
|
138
|
+
if (targetResourceId !== resourceId) {
|
|
139
|
+
throw new Error(`Annotation target resource ID (${targetResourceId}) does not match expected resource ID (${resourceId})`);
|
|
140
|
+
}
|
|
141
|
+
const sourceDoc = sourceView.resource;
|
|
142
|
+
const bodySource = getBodySource(annotation.body);
|
|
143
|
+
let targetDoc = null;
|
|
144
|
+
if (bodySource) {
|
|
145
|
+
const parts = bodySource.split("/");
|
|
146
|
+
const lastPart = parts[parts.length - 1];
|
|
147
|
+
if (!lastPart) {
|
|
148
|
+
throw new Error(`Invalid body source URI: ${bodySource}`);
|
|
149
|
+
}
|
|
150
|
+
const targetResourceId2 = createResourceId(lastPart);
|
|
151
|
+
const targetView = await viewStorage.get(targetResourceId2);
|
|
152
|
+
targetDoc = targetView?.resource || null;
|
|
153
|
+
}
|
|
154
|
+
let sourceContext;
|
|
155
|
+
if (includeSourceContext) {
|
|
156
|
+
const primaryRep = getPrimaryRepresentation2(sourceDoc);
|
|
157
|
+
if (!primaryRep?.checksum || !primaryRep?.mediaType) {
|
|
158
|
+
throw new Error("Source content not found");
|
|
159
|
+
}
|
|
160
|
+
const sourceContent = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
|
|
161
|
+
const contentStr = decodeRepresentation2(sourceContent, primaryRep.mediaType);
|
|
162
|
+
const targetSelectorRaw = getTargetSelector(annotation.target);
|
|
163
|
+
const targetSelector = Array.isArray(targetSelectorRaw) ? targetSelectorRaw[0] : targetSelectorRaw;
|
|
164
|
+
console.log(`[AnnotationContext] Target selector type:`, targetSelector?.type);
|
|
165
|
+
if (!targetSelector) {
|
|
166
|
+
console.warn(`[AnnotationContext] No target selector found`);
|
|
167
|
+
} else if (targetSelector.type === "TextPositionSelector") {
|
|
168
|
+
const selector = targetSelector;
|
|
169
|
+
const start = selector.start;
|
|
170
|
+
const end = selector.end;
|
|
171
|
+
const before = contentStr.slice(Math.max(0, start - contextWindow), start);
|
|
172
|
+
const selected = contentStr.slice(start, end);
|
|
173
|
+
const after = contentStr.slice(end, Math.min(contentStr.length, end + contextWindow));
|
|
174
|
+
sourceContext = { before, selected, after };
|
|
175
|
+
console.log(`[AnnotationContext] Built source context using TextPositionSelector (${start}-${end})`);
|
|
176
|
+
} else if (targetSelector.type === "TextQuoteSelector") {
|
|
177
|
+
const selector = targetSelector;
|
|
178
|
+
const exact = selector.exact;
|
|
179
|
+
const index = contentStr.indexOf(exact);
|
|
180
|
+
if (index !== -1) {
|
|
181
|
+
const start = index;
|
|
182
|
+
const end = index + exact.length;
|
|
183
|
+
const before = contentStr.slice(Math.max(0, start - contextWindow), start);
|
|
184
|
+
const selected = exact;
|
|
185
|
+
const after = contentStr.slice(end, Math.min(contentStr.length, end + contextWindow));
|
|
186
|
+
sourceContext = { before, selected, after };
|
|
187
|
+
console.log(`[AnnotationContext] Built source context using TextQuoteSelector (found at ${index})`);
|
|
188
|
+
} else {
|
|
189
|
+
console.warn(`[AnnotationContext] TextQuoteSelector exact text not found in content: "${exact.substring(0, 50)}..."`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
console.warn(`[AnnotationContext] Unknown selector type: ${targetSelector.type}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let targetContext;
|
|
196
|
+
if (includeTargetContext && targetDoc) {
|
|
197
|
+
const targetRep = getPrimaryRepresentation2(targetDoc);
|
|
198
|
+
if (targetRep?.checksum && targetRep?.mediaType) {
|
|
199
|
+
const targetContent = await repStore.retrieve(targetRep.checksum, targetRep.mediaType);
|
|
200
|
+
const contentStr = decodeRepresentation2(targetContent, targetRep.mediaType);
|
|
201
|
+
targetContext = {
|
|
202
|
+
content: contentStr.slice(0, contextWindow * 2),
|
|
203
|
+
summary: await generateResourceSummary(targetDoc.name, contentStr, getResourceEntityTypes(targetDoc), config)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const suggestedResolution = void 0;
|
|
208
|
+
const generationContext = sourceContext ? {
|
|
209
|
+
sourceContext: {
|
|
210
|
+
before: sourceContext.before || "",
|
|
211
|
+
selected: sourceContext.selected,
|
|
212
|
+
after: sourceContext.after || ""
|
|
213
|
+
},
|
|
214
|
+
metadata: {
|
|
215
|
+
resourceType: "document",
|
|
216
|
+
language: sourceDoc.language,
|
|
217
|
+
entityTypes: getEntityTypes(annotation)
|
|
218
|
+
}
|
|
219
|
+
} : void 0;
|
|
220
|
+
const response = {
|
|
221
|
+
annotation,
|
|
222
|
+
sourceResource: sourceDoc,
|
|
223
|
+
targetResource: targetDoc,
|
|
224
|
+
...generationContext ? { context: generationContext } : {},
|
|
225
|
+
...sourceContext ? { sourceContext } : {},
|
|
226
|
+
// Keep for backward compatibility
|
|
227
|
+
...targetContext ? { targetContext } : {},
|
|
228
|
+
...suggestedResolution ? { suggestedResolution } : {}
|
|
229
|
+
};
|
|
230
|
+
return response;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get resource annotations from view storage (fast path)
|
|
234
|
+
* Throws if view missing
|
|
235
|
+
*/
|
|
236
|
+
static async getResourceAnnotations(resourceId, config) {
|
|
237
|
+
if (!config.services?.filesystem?.path) {
|
|
238
|
+
throw new Error("Filesystem path not found in configuration");
|
|
239
|
+
}
|
|
240
|
+
const basePath = config.services.filesystem.path;
|
|
241
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
242
|
+
const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
|
|
243
|
+
const view = await viewStorage.get(resourceId);
|
|
244
|
+
if (!view) {
|
|
245
|
+
throw new Error(`Resource ${resourceId} not found in view storage`);
|
|
246
|
+
}
|
|
247
|
+
return view.annotations;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get all annotations
|
|
251
|
+
* @returns Array of all annotation objects
|
|
252
|
+
*/
|
|
253
|
+
static async getAllAnnotations(resourceId, config) {
|
|
254
|
+
const annotations = await this.getResourceAnnotations(resourceId, config);
|
|
255
|
+
return await this.enrichResolvedReferences(annotations.annotations, config);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Enrich reference annotations with resolved document names
|
|
259
|
+
* Adds _resolvedDocumentName property to annotations that link to documents
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
static async enrichResolvedReferences(annotations, config) {
|
|
263
|
+
if (!config.services?.filesystem?.path) {
|
|
264
|
+
return annotations;
|
|
265
|
+
}
|
|
266
|
+
const resolvedUris = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const ann of annotations) {
|
|
268
|
+
if (ann.motivation === "linking" && ann.body) {
|
|
269
|
+
const body = Array.isArray(ann.body) ? ann.body : [ann.body];
|
|
270
|
+
for (const item of body) {
|
|
271
|
+
if (item.purpose === "linking" && item.source) {
|
|
272
|
+
resolvedUris.add(item.source);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (resolvedUris.size === 0) {
|
|
278
|
+
return annotations;
|
|
279
|
+
}
|
|
280
|
+
const basePath = config.services.filesystem.path;
|
|
281
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
282
|
+
const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
|
|
283
|
+
const metadataPromises = Array.from(resolvedUris).map(async (uri) => {
|
|
284
|
+
const docId = uri.split("/resources/")[1];
|
|
285
|
+
if (!docId) return null;
|
|
286
|
+
try {
|
|
287
|
+
const view = await viewStorage.get(docId);
|
|
288
|
+
if (view?.resource?.name) {
|
|
289
|
+
return {
|
|
290
|
+
uri,
|
|
291
|
+
metadata: {
|
|
292
|
+
name: view.resource.name,
|
|
293
|
+
mediaType: view.resource.mediaType
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
} catch (e) {
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
});
|
|
301
|
+
const results = await Promise.all(metadataPromises);
|
|
302
|
+
const uriToMetadata = /* @__PURE__ */ new Map();
|
|
303
|
+
for (const result of results) {
|
|
304
|
+
if (result) {
|
|
305
|
+
uriToMetadata.set(result.uri, result.metadata);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return annotations.map((ann) => {
|
|
309
|
+
if (ann.motivation === "linking" && ann.body) {
|
|
310
|
+
const body = Array.isArray(ann.body) ? ann.body : [ann.body];
|
|
311
|
+
for (const item of body) {
|
|
312
|
+
if (item.purpose === "linking" && item.source) {
|
|
313
|
+
const metadata = uriToMetadata.get(item.source);
|
|
314
|
+
if (metadata) {
|
|
315
|
+
return {
|
|
316
|
+
...ann,
|
|
317
|
+
_resolvedDocumentName: metadata.name,
|
|
318
|
+
_resolvedDocumentMediaType: metadata.mediaType
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return ann;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get resource stats (version info)
|
|
329
|
+
* @returns Version and timestamp info for the annotations
|
|
330
|
+
*/
|
|
331
|
+
static async getResourceStats(resourceId, config) {
|
|
332
|
+
const annotations = await this.getResourceAnnotations(resourceId, config);
|
|
333
|
+
return {
|
|
334
|
+
resourceId: annotations.resourceId,
|
|
335
|
+
version: annotations.version,
|
|
336
|
+
updatedAt: annotations.updatedAt
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Check if resource exists in view storage
|
|
341
|
+
*/
|
|
342
|
+
static async resourceExists(resourceId, config) {
|
|
343
|
+
if (!config.services?.filesystem?.path) {
|
|
344
|
+
throw new Error("Filesystem path not found in configuration");
|
|
345
|
+
}
|
|
346
|
+
const basePath = config.services.filesystem.path;
|
|
347
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
348
|
+
const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
|
|
349
|
+
return await viewStorage.exists(resourceId);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get a single annotation by ID
|
|
353
|
+
* O(1) lookup using resource ID to access view storage
|
|
354
|
+
*/
|
|
355
|
+
static async getAnnotation(annotationId, resourceId, config) {
|
|
356
|
+
const annotations = await this.getResourceAnnotations(resourceId, config);
|
|
357
|
+
return annotations.annotations.find((a) => {
|
|
358
|
+
const shortId = a.id.split("/").pop();
|
|
359
|
+
return shortId === annotationId;
|
|
360
|
+
}) || null;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* List annotations with optional filtering
|
|
364
|
+
* @param filters - Optional filters like resourceId and type
|
|
365
|
+
* @throws Error if resourceId not provided (cross-resource queries not supported in view storage)
|
|
366
|
+
*/
|
|
367
|
+
static async listAnnotations(filters, config) {
|
|
368
|
+
if (!filters?.resourceId) {
|
|
369
|
+
throw new Error("resourceId is required for annotation listing - cross-resource queries not supported in view storage");
|
|
370
|
+
}
|
|
371
|
+
return await this.getAllAnnotations(filters.resourceId, config);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get annotation context (selected text with surrounding context)
|
|
375
|
+
*/
|
|
376
|
+
static async getAnnotationContext(annotationId, resourceId, contextBefore, contextAfter, config) {
|
|
377
|
+
const basePath = config.services.filesystem.path;
|
|
378
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
379
|
+
const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
|
|
380
|
+
const annotation = await this.getAnnotation(annotationId, resourceId, config);
|
|
381
|
+
if (!annotation) {
|
|
382
|
+
throw new Error("Annotation not found");
|
|
383
|
+
}
|
|
384
|
+
const resource = await ResourceContext.getResourceMetadata(
|
|
385
|
+
uriToResourceId(getTargetSource(annotation.target)),
|
|
386
|
+
config
|
|
387
|
+
);
|
|
388
|
+
if (!resource) {
|
|
389
|
+
throw new Error("Resource not found");
|
|
390
|
+
}
|
|
391
|
+
const contentStr = await this.getResourceContent(resource, repStore);
|
|
392
|
+
const context = this.extractAnnotationContext(annotation, contentStr, contextBefore, contextAfter);
|
|
393
|
+
return {
|
|
394
|
+
annotation,
|
|
395
|
+
context,
|
|
396
|
+
resource: {
|
|
397
|
+
"@context": resource["@context"],
|
|
398
|
+
"@id": resource["@id"],
|
|
399
|
+
name: resource.name,
|
|
400
|
+
entityTypes: resource.entityTypes,
|
|
401
|
+
representations: resource.representations,
|
|
402
|
+
archived: resource.archived,
|
|
403
|
+
creationMethod: resource.creationMethod,
|
|
404
|
+
wasAttributedTo: resource.wasAttributedTo,
|
|
405
|
+
dateCreated: resource.dateCreated
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Generate AI summary of annotation in context
|
|
411
|
+
*/
|
|
412
|
+
static async generateAnnotationSummary(annotationId, resourceId, config) {
|
|
413
|
+
const basePath = config.services.filesystem.path;
|
|
414
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
415
|
+
const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
|
|
416
|
+
const annotation = await this.getAnnotation(annotationId, resourceId, config);
|
|
417
|
+
if (!annotation) {
|
|
418
|
+
throw new Error("Annotation not found");
|
|
419
|
+
}
|
|
420
|
+
const resource = await ResourceContext.getResourceMetadata(
|
|
421
|
+
uriToResourceId(getTargetSource(annotation.target)),
|
|
422
|
+
config
|
|
423
|
+
);
|
|
424
|
+
if (!resource) {
|
|
425
|
+
throw new Error("Resource not found");
|
|
426
|
+
}
|
|
427
|
+
const contentStr = await this.getResourceContent(resource, repStore);
|
|
428
|
+
const contextSize = 500;
|
|
429
|
+
const context = this.extractAnnotationContext(annotation, contentStr, contextSize, contextSize);
|
|
430
|
+
const annotationEntityTypes = getEntityTypes(annotation);
|
|
431
|
+
const summary = await this.generateSummary(resource, context, annotationEntityTypes, config);
|
|
432
|
+
return {
|
|
433
|
+
summary,
|
|
434
|
+
relevantFields: {
|
|
435
|
+
resourceId: resource.id,
|
|
436
|
+
resourceName: resource.name,
|
|
437
|
+
entityTypes: annotationEntityTypes
|
|
438
|
+
},
|
|
439
|
+
context: {
|
|
440
|
+
before: context.before.substring(Math.max(0, context.before.length - 200)),
|
|
441
|
+
// Last 200 chars
|
|
442
|
+
selected: context.selected,
|
|
443
|
+
after: context.after.substring(0, 200)
|
|
444
|
+
// First 200 chars
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get resource content as string
|
|
450
|
+
*/
|
|
451
|
+
static async getResourceContent(resource, repStore) {
|
|
452
|
+
const primaryRep = getPrimaryRepresentation2(resource);
|
|
453
|
+
if (!primaryRep?.checksum || !primaryRep?.mediaType) {
|
|
454
|
+
throw new Error("Resource content not found");
|
|
455
|
+
}
|
|
456
|
+
const content = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
|
|
457
|
+
return decodeRepresentation2(content, primaryRep.mediaType);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Extract annotation context from resource content
|
|
461
|
+
*/
|
|
462
|
+
static extractAnnotationContext(annotation, contentStr, contextBefore, contextAfter) {
|
|
463
|
+
const targetSelector = getTargetSelector(annotation.target);
|
|
464
|
+
const posSelector = targetSelector ? getTextPositionSelector(targetSelector) : null;
|
|
465
|
+
if (!posSelector) {
|
|
466
|
+
throw new Error("TextPositionSelector required for context");
|
|
467
|
+
}
|
|
468
|
+
const selStart = posSelector.start;
|
|
469
|
+
const selEnd = posSelector.end;
|
|
470
|
+
const start = Math.max(0, selStart - contextBefore);
|
|
471
|
+
const end = Math.min(contentStr.length, selEnd + contextAfter);
|
|
472
|
+
return {
|
|
473
|
+
before: contentStr.substring(start, selStart),
|
|
474
|
+
selected: contentStr.substring(selStart, selEnd),
|
|
475
|
+
after: contentStr.substring(selEnd, end)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Generate LLM summary of annotation in context
|
|
480
|
+
*/
|
|
481
|
+
static async generateSummary(resource, context, entityTypes, config) {
|
|
482
|
+
const summaryPrompt = `Summarize this text in context:
|
|
483
|
+
|
|
484
|
+
Context before: "${context.before.substring(Math.max(0, context.before.length - 200))}"
|
|
485
|
+
Selected exact: "${context.selected}"
|
|
486
|
+
Context after: "${context.after.substring(0, 200)}"
|
|
487
|
+
|
|
488
|
+
Resource: ${resource.name}
|
|
489
|
+
Entity types: ${entityTypes.join(", ")}`;
|
|
490
|
+
return await generateText(summaryPrompt, config, 500, 0.5);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/graph-context.ts
|
|
495
|
+
import { getGraphDatabase } from "@semiont/graph";
|
|
496
|
+
import { resourceIdToURI } from "@semiont/core";
|
|
497
|
+
var GraphContext = class {
|
|
498
|
+
/**
|
|
499
|
+
* Get all resources referencing this resource (backlinks)
|
|
500
|
+
* Requires graph traversal - must use graph database
|
|
501
|
+
*/
|
|
502
|
+
static async getBacklinks(resourceId, config) {
|
|
503
|
+
const graphDb = await getGraphDatabase(config);
|
|
504
|
+
const resourceUri = resourceIdToURI(resourceId, config.services.backend.publicURL);
|
|
505
|
+
return await graphDb.getResourceReferencedBy(resourceUri);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Find shortest path between two resources
|
|
509
|
+
* Requires graph traversal - must use graph database
|
|
510
|
+
*/
|
|
511
|
+
static async findPath(fromResourceId, toResourceId, config, maxDepth) {
|
|
512
|
+
const graphDb = await getGraphDatabase(config);
|
|
513
|
+
return await graphDb.findPath(fromResourceId, toResourceId, maxDepth);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Get resource connections (graph edges)
|
|
517
|
+
* Requires graph traversal - must use graph database
|
|
518
|
+
*/
|
|
519
|
+
static async getResourceConnections(resourceId, config) {
|
|
520
|
+
const graphDb = await getGraphDatabase(config);
|
|
521
|
+
return await graphDb.getResourceConnections(resourceId);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Search resources by name (cross-resource query)
|
|
525
|
+
* Requires full-text search - must use graph database
|
|
526
|
+
*/
|
|
527
|
+
static async searchResources(query, config, limit) {
|
|
528
|
+
const graphDb = await getGraphDatabase(config);
|
|
529
|
+
return await graphDb.searchResources(query, limit);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/annotation-detection.ts
|
|
534
|
+
import { FilesystemRepresentationStore as FilesystemRepresentationStore3 } from "@semiont/content";
|
|
535
|
+
import { getPrimaryRepresentation as getPrimaryRepresentation3, decodeRepresentation as decodeRepresentation3 } from "@semiont/api-client";
|
|
536
|
+
import {
|
|
537
|
+
MotivationPrompts,
|
|
538
|
+
MotivationParsers,
|
|
539
|
+
generateText as generateText2
|
|
540
|
+
} from "@semiont/inference";
|
|
541
|
+
import { getTagSchema, getSchemaCategory } from "@semiont/ontology";
|
|
542
|
+
var AnnotationDetection = class {
|
|
543
|
+
/**
|
|
544
|
+
* Detect comments in a resource
|
|
545
|
+
*
|
|
546
|
+
* @param resourceId - The resource to analyze
|
|
547
|
+
* @param config - Environment configuration
|
|
548
|
+
* @param instructions - Optional user instructions for comment generation
|
|
549
|
+
* @param tone - Optional tone guidance (e.g., "academic", "conversational")
|
|
550
|
+
* @param density - Optional target number of comments per 2000 words
|
|
551
|
+
* @returns Array of validated comment matches
|
|
552
|
+
*/
|
|
553
|
+
static async detectComments(resourceId, config, instructions, tone, density) {
|
|
554
|
+
const resource = await ResourceContext.getResourceMetadata(resourceId, config);
|
|
555
|
+
if (!resource) {
|
|
556
|
+
throw new Error(`Resource ${resourceId} not found`);
|
|
557
|
+
}
|
|
558
|
+
const content = await this.loadResourceContent(resourceId, config);
|
|
559
|
+
if (!content) {
|
|
560
|
+
throw new Error(`Could not load content for resource ${resourceId}`);
|
|
561
|
+
}
|
|
562
|
+
const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density);
|
|
563
|
+
const response = await generateText2(
|
|
564
|
+
prompt,
|
|
565
|
+
config,
|
|
566
|
+
3e3,
|
|
567
|
+
// maxTokens: Higher than highlights/assessments due to comment text
|
|
568
|
+
0.4
|
|
569
|
+
// temperature: Slightly higher to allow creative context
|
|
570
|
+
);
|
|
571
|
+
return MotivationParsers.parseComments(response, content);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Detect highlights in a resource
|
|
575
|
+
*
|
|
576
|
+
* @param resourceId - The resource to analyze
|
|
577
|
+
* @param config - Environment configuration
|
|
578
|
+
* @param instructions - Optional user instructions for highlight selection
|
|
579
|
+
* @param density - Optional target number of highlights per 2000 words
|
|
580
|
+
* @returns Array of validated highlight matches
|
|
581
|
+
*/
|
|
582
|
+
static async detectHighlights(resourceId, config, instructions, density) {
|
|
583
|
+
const resource = await ResourceContext.getResourceMetadata(resourceId, config);
|
|
584
|
+
if (!resource) {
|
|
585
|
+
throw new Error(`Resource ${resourceId} not found`);
|
|
586
|
+
}
|
|
587
|
+
const content = await this.loadResourceContent(resourceId, config);
|
|
588
|
+
if (!content) {
|
|
589
|
+
throw new Error(`Could not load content for resource ${resourceId}`);
|
|
590
|
+
}
|
|
591
|
+
const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density);
|
|
592
|
+
const response = await generateText2(
|
|
593
|
+
prompt,
|
|
594
|
+
config,
|
|
595
|
+
2e3,
|
|
596
|
+
// maxTokens: Lower than comments/assessments (no body text)
|
|
597
|
+
0.3
|
|
598
|
+
// temperature: Low for consistent importance judgments
|
|
599
|
+
);
|
|
600
|
+
return MotivationParsers.parseHighlights(response, content);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Detect assessments in a resource
|
|
604
|
+
*
|
|
605
|
+
* @param resourceId - The resource to analyze
|
|
606
|
+
* @param config - Environment configuration
|
|
607
|
+
* @param instructions - Optional user instructions for assessment generation
|
|
608
|
+
* @param tone - Optional tone guidance (e.g., "critical", "supportive")
|
|
609
|
+
* @param density - Optional target number of assessments per 2000 words
|
|
610
|
+
* @returns Array of validated assessment matches
|
|
611
|
+
*/
|
|
612
|
+
static async detectAssessments(resourceId, config, instructions, tone, density) {
|
|
613
|
+
const resource = await ResourceContext.getResourceMetadata(resourceId, config);
|
|
614
|
+
if (!resource) {
|
|
615
|
+
throw new Error(`Resource ${resourceId} not found`);
|
|
616
|
+
}
|
|
617
|
+
const content = await this.loadResourceContent(resourceId, config);
|
|
618
|
+
if (!content) {
|
|
619
|
+
throw new Error(`Could not load content for resource ${resourceId}`);
|
|
620
|
+
}
|
|
621
|
+
const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density);
|
|
622
|
+
const response = await generateText2(
|
|
623
|
+
prompt,
|
|
624
|
+
config,
|
|
625
|
+
3e3,
|
|
626
|
+
// maxTokens: Higher for assessment text
|
|
627
|
+
0.3
|
|
628
|
+
// temperature: Lower for analytical consistency
|
|
629
|
+
);
|
|
630
|
+
return MotivationParsers.parseAssessments(response, content);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Detect tags in a resource for a specific category
|
|
634
|
+
*
|
|
635
|
+
* @param resourceId - The resource to analyze
|
|
636
|
+
* @param config - Environment configuration
|
|
637
|
+
* @param schemaId - The tag schema identifier (e.g., "irac", "imrad")
|
|
638
|
+
* @param category - The specific category to detect
|
|
639
|
+
* @returns Array of validated tag matches
|
|
640
|
+
*/
|
|
641
|
+
static async detectTags(resourceId, config, schemaId, category) {
|
|
642
|
+
const schema = getTagSchema(schemaId);
|
|
643
|
+
if (!schema) {
|
|
644
|
+
throw new Error(`Invalid tag schema: ${schemaId}`);
|
|
645
|
+
}
|
|
646
|
+
const categoryInfo = getSchemaCategory(schemaId, category);
|
|
647
|
+
if (!categoryInfo) {
|
|
648
|
+
throw new Error(`Invalid category "${category}" for schema ${schemaId}`);
|
|
649
|
+
}
|
|
650
|
+
const resource = await ResourceContext.getResourceMetadata(resourceId, config);
|
|
651
|
+
if (!resource) {
|
|
652
|
+
throw new Error(`Resource ${resourceId} not found`);
|
|
653
|
+
}
|
|
654
|
+
const content = await this.loadResourceContent(resourceId, config);
|
|
655
|
+
if (!content) {
|
|
656
|
+
throw new Error(`Could not load content for resource ${resourceId}`);
|
|
657
|
+
}
|
|
658
|
+
const prompt = MotivationPrompts.buildTagPrompt(
|
|
659
|
+
content,
|
|
660
|
+
category,
|
|
661
|
+
schema.name,
|
|
662
|
+
schema.description,
|
|
663
|
+
schema.domain,
|
|
664
|
+
categoryInfo.description,
|
|
665
|
+
categoryInfo.examples
|
|
666
|
+
);
|
|
667
|
+
const response = await generateText2(
|
|
668
|
+
prompt,
|
|
669
|
+
config,
|
|
670
|
+
4e3,
|
|
671
|
+
// maxTokens: Higher for full document analysis
|
|
672
|
+
0.2
|
|
673
|
+
// temperature: Lower for structural consistency
|
|
674
|
+
);
|
|
675
|
+
const parsedTags = MotivationParsers.parseTags(response);
|
|
676
|
+
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Load resource content from representation store
|
|
680
|
+
* Helper method used by all detection methods
|
|
681
|
+
*
|
|
682
|
+
* @param resourceId - The resource ID to load
|
|
683
|
+
* @param config - Environment configuration
|
|
684
|
+
* @returns Resource content as string, or null if not available
|
|
685
|
+
*/
|
|
686
|
+
static async loadResourceContent(resourceId, config) {
|
|
687
|
+
const resource = await ResourceContext.getResourceMetadata(resourceId, config);
|
|
688
|
+
if (!resource) return null;
|
|
689
|
+
const primaryRep = getPrimaryRepresentation3(resource);
|
|
690
|
+
if (!primaryRep) return null;
|
|
691
|
+
const baseMediaType = primaryRep.mediaType?.split(";")[0]?.trim() || "";
|
|
692
|
+
if (baseMediaType !== "text/plain" && baseMediaType !== "text/markdown") {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
if (!primaryRep.checksum || !primaryRep.mediaType) return null;
|
|
696
|
+
const basePath = config.services.filesystem.path;
|
|
697
|
+
const projectRoot = config._metadata?.projectRoot;
|
|
698
|
+
const repStore = new FilesystemRepresentationStore3({ basePath }, projectRoot);
|
|
699
|
+
const contentBuffer = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
|
|
700
|
+
return decodeRepresentation3(contentBuffer, primaryRep.mediaType);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/index.ts
|
|
705
|
+
var PACKAGE_NAME = "@semiont/make-meaning";
|
|
706
|
+
var VERSION = "0.1.0";
|
|
707
|
+
export {
|
|
708
|
+
AnnotationContext,
|
|
709
|
+
AnnotationDetection,
|
|
710
|
+
GraphContext,
|
|
711
|
+
PACKAGE_NAME,
|
|
712
|
+
ResourceContext,
|
|
713
|
+
VERSION
|
|
714
|
+
};
|
|
715
|
+
//# sourceMappingURL=index.js.map
|