@semiont/make-meaning 0.5.25 → 0.5.27
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 +1 -1
- package/dist/index.d.ts +132 -29
- package/dist/index.js +301 -152
- package/dist/index.js.map +1 -1
- package/dist/smelter-main.js +2 -3
- package/dist/smelter-main.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { STALL_THRESHOLD_MS, FsJobQueue } from '@semiont/jobs';
|
|
2
2
|
import { FilesystemViewStorage, resolveStorageUri, EventQuery, createEventStore } from '@semiont/event-sourcing';
|
|
3
|
-
import { getResourceEntityTypes, getResourceId, getTargetSource, resourceId, getPrimaryRepresentation, decodeRepresentation, getBodySource, getTargetSelector, deriveViews, getTextPositionSelector, annotationId, errField, userId, generateUuid, getExactText, busRequest, cloneToken, baseMediaType, isSupportedMediaType, capabilitiesOf, didToAgent, assembleAnnotation, jobId, entityType, baseUrl, busLog, BRIDGED_CHANNELS, burstBuffer, textExtractionOf, chunkText, getPrimaryMediaType, extensionForMediaType, applyBodyOperations, softwareToAgent } from '@semiont/core';
|
|
3
|
+
import { getResourceEntityTypes, getResourceId, getTargetSource, resourceId, getPrimaryRepresentation, decodeRepresentation, getBodySource, getTargetSelector, deriveViews, getTextPositionSelector, annotationId, errField, userId, generateUuid, getExactText, busRequest, cloneToken, baseMediaType, isSupportedMediaType, capabilitiesOf, didToAgent, assembleAnnotation, isGenerationJobParams, jobId, entityType, baseUrl, busLog, BRIDGED_CHANNELS, burstBuffer, textExtractionOf, chunkText, getPrimaryMediaType, extensionForMediaType, applyBodyOperations, softwareToAgent } from '@semiont/core';
|
|
4
4
|
import { recordGatherDegrade, withActorSpan, recordBusEmit, withSpan, SpanKind, registerJobQueueProvider, registerVectorIndexSizeProvider } from '@semiont/observability';
|
|
5
5
|
import { createInferenceClient } from '@semiont/inference';
|
|
6
|
-
import { getGraphDatabase } from '@semiont/graph';
|
|
6
|
+
import { compareByRecencyThenId, getGraphDatabase } from '@semiont/graph';
|
|
7
7
|
import { WorkingTreeStore, createAnchoredTextStore, deriveStorageUri, anchoredTextStoreOverTransport, EXTRACTORS, calculateChecksum } from '@semiont/content';
|
|
8
8
|
import { getEntityTypes, DEFAULT_ENTITY_TYPES } from '@semiont/ontology';
|
|
9
|
+
import { mergeByResource } from '@semiont/vectors';
|
|
9
10
|
import { promises } from 'fs';
|
|
10
11
|
import * as path3 from 'path';
|
|
11
12
|
import { createGzip, createGunzip } from 'zlib';
|
|
@@ -9905,11 +9906,9 @@ async function createKnowledgeBase(eventStore, project, graphDb, eventBus, logge
|
|
|
9905
9906
|
graph: graphDb,
|
|
9906
9907
|
weaveProgress,
|
|
9907
9908
|
smeltProgress,
|
|
9909
|
+
vectors: options.vectorStore,
|
|
9908
9910
|
projectionsDir: project.projectionsDir
|
|
9909
9911
|
};
|
|
9910
|
-
if (options?.vectorStore) {
|
|
9911
|
-
kb.vectors = options.vectorStore;
|
|
9912
|
-
}
|
|
9913
9912
|
return kb;
|
|
9914
9913
|
}
|
|
9915
9914
|
var PROJECTION_LAG_BACKOFF_MS = [25, 50, 100, 200];
|
|
@@ -9937,13 +9936,6 @@ var GraphContext = class {
|
|
|
9937
9936
|
static async getResourceConnections(resourceId10, kb) {
|
|
9938
9937
|
return kb.graph.getResourceConnections(resourceId10);
|
|
9939
9938
|
}
|
|
9940
|
-
/**
|
|
9941
|
-
* Search resources by name (cross-resource query)
|
|
9942
|
-
* Requires full-text search - must use graph database
|
|
9943
|
-
*/
|
|
9944
|
-
static async searchResources(query, kb, limit) {
|
|
9945
|
-
return kb.graph.searchResources(query, limit);
|
|
9946
|
-
}
|
|
9947
9939
|
/**
|
|
9948
9940
|
* Build the unified knowledge graph for a resource's neighborhood:
|
|
9949
9941
|
* resources AND annotations as typed nodes, typed/directional edges.
|
|
@@ -10064,6 +10056,18 @@ Format as a simple list, one suggestion per line.`;
|
|
|
10064
10056
|
}
|
|
10065
10057
|
return response.split("\n").map((line) => line.replace(/^[-*•]\s*/, "").trim()).filter((line) => line.length > 0).slice(0, 3);
|
|
10066
10058
|
}
|
|
10059
|
+
|
|
10060
|
+
// src/graph-read-grace.ts
|
|
10061
|
+
async function resourceWithViewGrace(kb, rid) {
|
|
10062
|
+
const fromGraph = await kb.graph.getResource(rid).catch(() => null);
|
|
10063
|
+
if (fromGraph) return { resource: fromGraph, laggedBehindView: false };
|
|
10064
|
+
const view = await kb.views.get(rid);
|
|
10065
|
+
if (view) return { resource: view.resource, laggedBehindView: true };
|
|
10066
|
+
return { resource: null, laggedBehindView: false };
|
|
10067
|
+
}
|
|
10068
|
+
|
|
10069
|
+
// src/resource-context.ts
|
|
10070
|
+
var SEMANTIC_OVER_FETCH = 4;
|
|
10067
10071
|
var ResourceContext = class _ResourceContext {
|
|
10068
10072
|
/**
|
|
10069
10073
|
* Get resource metadata from view storage
|
|
@@ -10076,38 +10080,80 @@ var ResourceContext = class _ResourceContext {
|
|
|
10076
10080
|
return view.resource;
|
|
10077
10081
|
}
|
|
10078
10082
|
/**
|
|
10079
|
-
* List resources, optionally filtered
|
|
10083
|
+
* List resources, optionally filtered, as one page plus the size of the whole
|
|
10084
|
+
* match set. Every filter is applied before pagination on both paths — a
|
|
10085
|
+
* filter applied afterwards narrows the page rather than the match set, which
|
|
10086
|
+
* is how a search scoped to an entity type can come back empty while hundreds
|
|
10087
|
+
* of resources match.
|
|
10080
10088
|
*
|
|
10081
|
-
* When `search` is set,
|
|
10082
|
-
*
|
|
10083
|
-
* The graph result is then narrowed by `archived` if requested.
|
|
10089
|
+
* When `search` is set, the entire query — filtering, ordering and
|
|
10090
|
+
* pagination — runs inside the graph engine.
|
|
10084
10091
|
*
|
|
10085
|
-
* When `search` is unset,
|
|
10086
|
-
*
|
|
10092
|
+
* When `search` is unset, the materialized views answer instead. They are the
|
|
10093
|
+
* barrier-stamped projection, so an unsearched listing is read-your-writes
|
|
10094
|
+
* where the graph is only eventually consistent.
|
|
10087
10095
|
*/
|
|
10088
|
-
static async listResources(filters, kb) {
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10096
|
+
static async listResources(filters, kb, semantic) {
|
|
10097
|
+
const { search: rawSearch, archived, entityType: entityType2, offset = 0, limit = 50 } = filters ?? {};
|
|
10098
|
+
const search = rawSearch?.trim() || void 0;
|
|
10099
|
+
if (search) {
|
|
10100
|
+
const lexical = await kb.graph.listResources({
|
|
10101
|
+
search,
|
|
10102
|
+
archived,
|
|
10103
|
+
entityTypes: entityType2 ? [entityType2] : void 0,
|
|
10104
|
+
offset,
|
|
10105
|
+
limit
|
|
10106
|
+
});
|
|
10107
|
+
if (lexical.total > 0 || offset > 0) return { ...lexical, matchKind: "lexical" };
|
|
10108
|
+
return _ResourceContext.semanticFallback(search, limit, kb, semantic);
|
|
10093
10109
|
}
|
|
10094
10110
|
const allViews = await kb.views.getAll();
|
|
10095
|
-
const
|
|
10096
|
-
|
|
10097
|
-
const doc = view.resource;
|
|
10098
|
-
if (filters?.archived !== void 0 && doc.archived !== filters.archived) {
|
|
10099
|
-
continue;
|
|
10100
|
-
}
|
|
10101
|
-
resources.push(doc);
|
|
10102
|
-
}
|
|
10103
|
-
return _ResourceContext.sortByDateDesc(resources);
|
|
10111
|
+
const matches = allViews.map((view) => view.resource).filter((doc) => archived === void 0 || doc.archived === archived).filter((doc) => !entityType2 || getResourceEntityTypes(doc).includes(entityType2)).sort(compareByRecencyThenId);
|
|
10112
|
+
return { resources: matches.slice(offset, offset + limit), total: matches.length, matchKind: "lexical" };
|
|
10104
10113
|
}
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10114
|
+
/**
|
|
10115
|
+
* Answer an empty lexical search from the vector index (SEMANTIC-FALLBACK):
|
|
10116
|
+
* embed the query once, fold chunk hits per resource, floor them, and label
|
|
10117
|
+
* the answer 'semantic' so the UI can say "no title matches, but these
|
|
10118
|
+
* documents discuss it".
|
|
10119
|
+
*
|
|
10120
|
+
* Degradation is the contract (axioms S3–S5): unconfigured vectors, an
|
|
10121
|
+
* absent provider, or ANY failure inside the fallback yields the same
|
|
10122
|
+
* empty page the caller already had, labelled 'lexical' — a broken
|
|
10123
|
+
* fallback must never turn a working empty search into an error.
|
|
10124
|
+
*
|
|
10125
|
+
* The floor is applied HERE rather than passed as `scoreThreshold`, so the
|
|
10126
|
+
* below-floor hits exist to be counted — the debug line is the evidence
|
|
10127
|
+
* decision #1's guessed 0.6 gets tuned from.
|
|
10128
|
+
*/
|
|
10129
|
+
static async semanticFallback(search, limit, kb, semantic) {
|
|
10130
|
+
const empty = { resources: [], total: 0, matchKind: "lexical" };
|
|
10131
|
+
try {
|
|
10132
|
+
const embedding = await semantic.embeddingProvider.embed(search);
|
|
10133
|
+
const hits = await kb.vectors.searchResources(embedding, { limit: limit * SEMANTIC_OVER_FETCH });
|
|
10134
|
+
const merged = mergeByResource(hits);
|
|
10135
|
+
const aboveFloor = merged.filter((h) => h.score >= semantic.semanticFloor);
|
|
10136
|
+
semantic.logger.debug("[search FALLBACK] semantic score distribution", {
|
|
10137
|
+
chunkHits: hits.length,
|
|
10138
|
+
resources: merged.length,
|
|
10139
|
+
aboveFloor: aboveFloor.length,
|
|
10140
|
+
belowFloor: merged.length - aboveFloor.length,
|
|
10141
|
+
topScore: merged[0]?.score,
|
|
10142
|
+
bottomScore: merged[merged.length - 1]?.score,
|
|
10143
|
+
floor: semantic.semanticFloor
|
|
10144
|
+
});
|
|
10145
|
+
const resources = [];
|
|
10146
|
+
for (const hit of aboveFloor.slice(0, limit)) {
|
|
10147
|
+
const { resource } = await resourceWithViewGrace(kb, hit.resourceId);
|
|
10148
|
+
if (resource) resources.push({ ...resource, content: hit.text });
|
|
10149
|
+
}
|
|
10150
|
+
return { resources, total: aboveFloor.length, matchKind: "semantic" };
|
|
10151
|
+
} catch (error) {
|
|
10152
|
+
semantic.logger.warn("[search FALLBACK] degraded to the empty lexical page", {
|
|
10153
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
10154
|
+
});
|
|
10155
|
+
return empty;
|
|
10156
|
+
}
|
|
10111
10157
|
}
|
|
10112
10158
|
/**
|
|
10113
10159
|
* Add content previews to resources (for search results)
|
|
@@ -10157,7 +10203,7 @@ var AnnotationContext = class {
|
|
|
10157
10203
|
* @returns Rich context for LLM processing
|
|
10158
10204
|
* @throws Error if annotation or resource not found
|
|
10159
10205
|
*/
|
|
10160
|
-
static async buildLLMContext(annotationId5, resourceId10, kb, options = {}, inferenceClient, logger
|
|
10206
|
+
static async buildLLMContext(annotationId5, resourceId10, kb, embeddingProvider, options = {}, inferenceClient, logger) {
|
|
10161
10207
|
const {
|
|
10162
10208
|
includeSourceContext = true,
|
|
10163
10209
|
includeTargetContext = true,
|
|
@@ -10289,7 +10335,7 @@ Summary:`;
|
|
|
10289
10335
|
}
|
|
10290
10336
|
}
|
|
10291
10337
|
let semanticContext;
|
|
10292
|
-
if (
|
|
10338
|
+
if (sourceContext?.selected) {
|
|
10293
10339
|
try {
|
|
10294
10340
|
const focalEmbedding = await embeddingProvider.embed(sourceContext.selected);
|
|
10295
10341
|
const results = await kb.vectors.searchAnnotations(focalEmbedding, {
|
|
@@ -10613,46 +10659,44 @@ var LLMContext = class {
|
|
|
10613
10659
|
if (options.includeContent) content.related = relatedContent;
|
|
10614
10660
|
let semanticContext;
|
|
10615
10661
|
const vectors = kb.vectors;
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
});
|
|
10640
|
-
}
|
|
10662
|
+
const excludeEntityTypes = options.excludeEntityTypes ?? [];
|
|
10663
|
+
const search = () => vectors.searchByResource(resourceId10, {
|
|
10664
|
+
limit: options.maxResources,
|
|
10665
|
+
scoreThreshold: 0.5,
|
|
10666
|
+
...excludeEntityTypes.length ? { filter: { excludeEntityTypes } } : {}
|
|
10667
|
+
});
|
|
10668
|
+
let matches = await search();
|
|
10669
|
+
if (matches.length === 0) {
|
|
10670
|
+
const contentChecksum = getPrimaryRepresentation(mainDoc)?.checksum;
|
|
10671
|
+
if (contentChecksum) {
|
|
10672
|
+
try {
|
|
10673
|
+
const outcome = await kb.smeltProgress.whenSettled(resourceIdStr, contentChecksum, settleTimeoutMs);
|
|
10674
|
+
if (outcome === "indexed") {
|
|
10675
|
+
matches = await search();
|
|
10676
|
+
}
|
|
10677
|
+
} catch (error) {
|
|
10678
|
+
if (!(error instanceof SmeltProgressTimeout)) throw error;
|
|
10679
|
+
recordGatherDegrade("vectors");
|
|
10680
|
+
logger.warn("[gather DEGRADED] semanticContext absent \u2014 the vector projection did not settle within the barrier", {
|
|
10681
|
+
resourceId: resourceIdStr,
|
|
10682
|
+
contentChecksum,
|
|
10683
|
+
timeoutMs: settleTimeoutMs
|
|
10684
|
+
});
|
|
10641
10685
|
}
|
|
10642
10686
|
}
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
}
|
|
10653
|
-
|
|
10654
|
-
}
|
|
10655
|
-
}
|
|
10687
|
+
}
|
|
10688
|
+
if (matches.length > 0) {
|
|
10689
|
+
semanticContext = {
|
|
10690
|
+
similar: matches.map((m) => ({
|
|
10691
|
+
text: m.text,
|
|
10692
|
+
resourceId: m.resourceId,
|
|
10693
|
+
...m.annotationId ? { annotationId: m.annotationId } : {},
|
|
10694
|
+
score: m.score,
|
|
10695
|
+
...m.entityTypes ? { entityTypes: m.entityTypes } : {},
|
|
10696
|
+
...m.machineRead ? { machineRead: true } : {}
|
|
10697
|
+
})),
|
|
10698
|
+
...excludeEntityTypes.length ? { excludedEntityTypes: excludeEntityTypes } : {}
|
|
10699
|
+
};
|
|
10656
10700
|
}
|
|
10657
10701
|
return {
|
|
10658
10702
|
focus: {
|
|
@@ -10730,10 +10774,10 @@ var Gatherer = class {
|
|
|
10730
10774
|
annotationId(event.annotationId),
|
|
10731
10775
|
resourceId(event.resourceId),
|
|
10732
10776
|
this.kb,
|
|
10777
|
+
this.embeddingProvider,
|
|
10733
10778
|
event.options ?? {},
|
|
10734
10779
|
this.inferenceClient,
|
|
10735
|
-
this.logger
|
|
10736
|
-
this.embeddingProvider
|
|
10780
|
+
this.logger
|
|
10737
10781
|
);
|
|
10738
10782
|
this.eventBus.get("gather:complete").next({
|
|
10739
10783
|
correlationId: event.correlationId,
|
|
@@ -10802,17 +10846,6 @@ var Gatherer = class {
|
|
|
10802
10846
|
// src/matcher.ts
|
|
10803
10847
|
var import_rxjs2 = __toESM(require_cjs());
|
|
10804
10848
|
var import_operators2 = __toESM(require_operators());
|
|
10805
|
-
|
|
10806
|
-
// src/graph-read-grace.ts
|
|
10807
|
-
async function resourceWithViewGrace(kb, rid) {
|
|
10808
|
-
const fromGraph = await kb.graph.getResource(rid).catch(() => null);
|
|
10809
|
-
if (fromGraph) return { resource: fromGraph, laggedBehindView: false };
|
|
10810
|
-
const view = await kb.views.get(rid);
|
|
10811
|
-
if (view) return { resource: view.resource, laggedBehindView: true };
|
|
10812
|
-
return { resource: null, laggedBehindView: false };
|
|
10813
|
-
}
|
|
10814
|
-
|
|
10815
|
-
// src/matcher.ts
|
|
10816
10849
|
var Matcher = class {
|
|
10817
10850
|
constructor(kb, eventBus, logger, inferenceClient, embeddingProvider) {
|
|
10818
10851
|
this.kb = kb;
|
|
@@ -10885,7 +10918,7 @@ var Matcher = class {
|
|
|
10885
10918
|
* Context-driven search: multi-source retrieval + composite scoring
|
|
10886
10919
|
*
|
|
10887
10920
|
* Retrieval sources:
|
|
10888
|
-
* 1. Name match — graph.
|
|
10921
|
+
* 1. Name match — graph.listResources({ search: searchTerm })
|
|
10889
10922
|
* 2. Entity type match — graph.listResources({ entityTypes })
|
|
10890
10923
|
* 3. Graph neighborhood — connections from GatheredContext
|
|
10891
10924
|
*
|
|
@@ -10901,7 +10934,7 @@ var Matcher = class {
|
|
|
10901
10934
|
const views = deriveViews(context.graph, mainResourceId);
|
|
10902
10935
|
const connections = views.connections;
|
|
10903
10936
|
const [nameMatches, entityTypeMatches, semanticMatches] = await Promise.all([
|
|
10904
|
-
this.kb.graph.
|
|
10937
|
+
this.kb.graph.listResources({ search: searchTerm, limit: 20 }).then((r) => r.resources),
|
|
10905
10938
|
annotationEntityTypes.length > 0 ? this.kb.graph.listResources({ entityTypes: annotationEntityTypes, limit: 50 }).then((r) => r.resources) : Promise.resolve([]),
|
|
10906
10939
|
// 4. Semantic match — vector similarity search (if vectors configured)
|
|
10907
10940
|
this.searchVectors(searchTerm)
|
|
@@ -11127,7 +11160,7 @@ For each candidate, output a line with the number and score, like:
|
|
|
11127
11160
|
* from the scorer.
|
|
11128
11161
|
*/
|
|
11129
11162
|
async searchVectors(searchTerm) {
|
|
11130
|
-
if (!
|
|
11163
|
+
if (!searchTerm.trim()) return [];
|
|
11131
11164
|
try {
|
|
11132
11165
|
const embedding = await this.embeddingProvider.embed(searchTerm);
|
|
11133
11166
|
const results = await this.kb.vectors.searchResources(embedding, {
|
|
@@ -11691,6 +11724,29 @@ var JOB_TYPES = [
|
|
|
11691
11724
|
"tag-annotation",
|
|
11692
11725
|
"generation"
|
|
11693
11726
|
];
|
|
11727
|
+
var inferencePairKey = (provider, model) => `${provider} ${model}`;
|
|
11728
|
+
function eachAdmittedInference(config, visit) {
|
|
11729
|
+
for (const jobType of JOB_TYPES) {
|
|
11730
|
+
try {
|
|
11731
|
+
visit(resolveWorkerInference(config, jobType), jobType);
|
|
11732
|
+
} catch {
|
|
11733
|
+
}
|
|
11734
|
+
}
|
|
11735
|
+
for (const actor of ["gatherer", "matcher"]) {
|
|
11736
|
+
try {
|
|
11737
|
+
visit(resolveActorInference(config, actor));
|
|
11738
|
+
} catch {
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11741
|
+
}
|
|
11742
|
+
function deriveInferencePairs(config) {
|
|
11743
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
11744
|
+
eachAdmittedInference(config, (inference) => {
|
|
11745
|
+
const key = inferencePairKey(inference.type, inference.model);
|
|
11746
|
+
if (!pairs.has(key)) pairs.set(key, inference);
|
|
11747
|
+
});
|
|
11748
|
+
return pairs;
|
|
11749
|
+
}
|
|
11694
11750
|
function deriveAgentRoster(config) {
|
|
11695
11751
|
const domain = config.site?.domain;
|
|
11696
11752
|
if (!domain) {
|
|
@@ -11699,8 +11755,8 @@ function deriveAgentRoster(config) {
|
|
|
11699
11755
|
);
|
|
11700
11756
|
}
|
|
11701
11757
|
const roster = /* @__PURE__ */ new Map();
|
|
11702
|
-
|
|
11703
|
-
const key =
|
|
11758
|
+
eachAdmittedInference(config, (inference, jobType) => {
|
|
11759
|
+
const key = inferencePairKey(inference.type, inference.model);
|
|
11704
11760
|
let entry = roster.get(key);
|
|
11705
11761
|
if (!entry) {
|
|
11706
11762
|
entry = {
|
|
@@ -11710,19 +11766,7 @@ function deriveAgentRoster(config) {
|
|
|
11710
11766
|
roster.set(key, entry);
|
|
11711
11767
|
}
|
|
11712
11768
|
if (jobType) entry.servesJobTypes.push(jobType);
|
|
11713
|
-
};
|
|
11714
|
-
for (const jobType of JOB_TYPES) {
|
|
11715
|
-
try {
|
|
11716
|
-
admit(resolveWorkerInference(config, jobType), jobType);
|
|
11717
|
-
} catch {
|
|
11718
|
-
}
|
|
11719
|
-
}
|
|
11720
|
-
for (const actor of ["gatherer", "matcher"]) {
|
|
11721
|
-
try {
|
|
11722
|
-
admit(resolveActorInference(config, actor));
|
|
11723
|
-
} catch {
|
|
11724
|
-
}
|
|
11725
|
-
}
|
|
11769
|
+
});
|
|
11726
11770
|
return [...roster.values()].map(
|
|
11727
11771
|
({ agent, servesJobTypes }) => servesJobTypes.length ? { agent, servesJobTypes } : { agent }
|
|
11728
11772
|
);
|
|
@@ -11730,12 +11774,14 @@ function deriveAgentRoster(config) {
|
|
|
11730
11774
|
|
|
11731
11775
|
// src/browser.ts
|
|
11732
11776
|
var Browser = class {
|
|
11733
|
-
constructor(views, kb, eventBus, project, config, logger) {
|
|
11777
|
+
constructor(views, kb, eventBus, project, config, limitsDiscovery, embeddingProvider, logger) {
|
|
11734
11778
|
this.views = views;
|
|
11735
11779
|
this.kb = kb;
|
|
11736
11780
|
this.eventBus = eventBus;
|
|
11737
11781
|
this.project = project;
|
|
11738
11782
|
this.config = config;
|
|
11783
|
+
this.limitsDiscovery = limitsDiscovery;
|
|
11784
|
+
this.embeddingProvider = embeddingProvider;
|
|
11739
11785
|
this.logger = logger;
|
|
11740
11786
|
}
|
|
11741
11787
|
views;
|
|
@@ -11743,6 +11789,8 @@ var Browser = class {
|
|
|
11743
11789
|
eventBus;
|
|
11744
11790
|
project;
|
|
11745
11791
|
config;
|
|
11792
|
+
limitsDiscovery;
|
|
11793
|
+
embeddingProvider;
|
|
11746
11794
|
subscriptions = [];
|
|
11747
11795
|
logger;
|
|
11748
11796
|
async initialize() {
|
|
@@ -11835,24 +11883,30 @@ var Browser = class {
|
|
|
11835
11883
|
}
|
|
11836
11884
|
async handleBrowseResources(event) {
|
|
11837
11885
|
try {
|
|
11838
|
-
let filteredDocs = await ResourceContext.listResources({
|
|
11839
|
-
search: event.search,
|
|
11840
|
-
archived: event.archived
|
|
11841
|
-
}, this.kb);
|
|
11842
|
-
if (event.entityType) {
|
|
11843
|
-
filteredDocs = filteredDocs.filter((doc) => getResourceEntityTypes(doc).includes(event.entityType));
|
|
11844
|
-
}
|
|
11845
11886
|
const offset = event.offset ?? 0;
|
|
11846
11887
|
const limit = event.limit ?? 50;
|
|
11847
|
-
const
|
|
11848
|
-
|
|
11888
|
+
const result = await ResourceContext.listResources({
|
|
11889
|
+
search: event.search,
|
|
11890
|
+
archived: event.archived,
|
|
11891
|
+
entityType: event.entityType,
|
|
11892
|
+
offset,
|
|
11893
|
+
limit
|
|
11894
|
+
}, this.kb, {
|
|
11895
|
+
embeddingProvider: this.embeddingProvider,
|
|
11896
|
+
semanticFloor: this.config.search.semanticFloor,
|
|
11897
|
+
logger: this.logger
|
|
11898
|
+
});
|
|
11899
|
+
const formattedDocs = event.search && result.matchKind === "lexical" ? await ResourceContext.addContentPreviews(result.resources, this.kb) : result.resources;
|
|
11849
11900
|
this.eventBus.get("browse:resources-result").next({
|
|
11850
11901
|
correlationId: event.correlationId,
|
|
11851
11902
|
response: {
|
|
11852
11903
|
resources: formattedDocs,
|
|
11853
|
-
total:
|
|
11904
|
+
total: result.total,
|
|
11854
11905
|
offset,
|
|
11855
|
-
limit
|
|
11906
|
+
limit,
|
|
11907
|
+
// The producer of the answer labels it (P1b moved the label here
|
|
11908
|
+
// from a hardcoded 'lexical' when the fallback landed).
|
|
11909
|
+
matchKind: result.matchKind
|
|
11856
11910
|
}
|
|
11857
11911
|
});
|
|
11858
11912
|
} catch (error) {
|
|
@@ -12061,7 +12115,7 @@ var Browser = class {
|
|
|
12061
12115
|
}
|
|
12062
12116
|
async handleBrowseAgents(event) {
|
|
12063
12117
|
try {
|
|
12064
|
-
const agents = deriveAgentRoster(this.config);
|
|
12118
|
+
const agents = await this.limitsDiscovery.enrich(deriveAgentRoster(this.config));
|
|
12065
12119
|
this.eventBus.get("browse:agents-result").next({
|
|
12066
12120
|
correlationId: event.correlationId,
|
|
12067
12121
|
response: { agents }
|
|
@@ -12178,6 +12232,63 @@ var Browser = class {
|
|
|
12178
12232
|
this.logger.info("Browser actor stopped");
|
|
12179
12233
|
}
|
|
12180
12234
|
};
|
|
12235
|
+
var LIMITS_ENRICH_BUDGET_MS = 1500;
|
|
12236
|
+
function createLimitsDiscovery(config, logger, options) {
|
|
12237
|
+
const clientFactory = createInferenceClient;
|
|
12238
|
+
const budgetMs = LIMITS_ENRICH_BUDGET_MS;
|
|
12239
|
+
const clients = /* @__PURE__ */ new Map();
|
|
12240
|
+
for (const [pair, inference] of deriveInferencePairs(config)) {
|
|
12241
|
+
try {
|
|
12242
|
+
clients.set(pair, clientFactory(inference, logger));
|
|
12243
|
+
} catch (error) {
|
|
12244
|
+
logger.debug("Limits discovery: client construction failed \u2014 pair enriches as absent", {
|
|
12245
|
+
pair,
|
|
12246
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
12247
|
+
});
|
|
12248
|
+
}
|
|
12249
|
+
}
|
|
12250
|
+
const consult = async (pair, client) => {
|
|
12251
|
+
let timer2;
|
|
12252
|
+
try {
|
|
12253
|
+
const discovery = client.limits();
|
|
12254
|
+
const raced = await Promise.race([
|
|
12255
|
+
discovery,
|
|
12256
|
+
new Promise((resolve2) => {
|
|
12257
|
+
timer2 = setTimeout(() => resolve2(void 0), budgetMs);
|
|
12258
|
+
})
|
|
12259
|
+
]);
|
|
12260
|
+
if (raced === void 0) {
|
|
12261
|
+
void discovery.catch(() => {
|
|
12262
|
+
});
|
|
12263
|
+
logger.debug("Limits discovery: consult exceeded budget \u2014 entry enriches as absent", { pair, budgetMs });
|
|
12264
|
+
return void 0;
|
|
12265
|
+
}
|
|
12266
|
+
return { contextTokens: raced.contextTokens, maxOutputTokens: raced.maxOutputTokens };
|
|
12267
|
+
} catch (error) {
|
|
12268
|
+
logger.debug("Limits discovery: consult failed \u2014 entry enriches as absent", {
|
|
12269
|
+
pair,
|
|
12270
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
12271
|
+
});
|
|
12272
|
+
return void 0;
|
|
12273
|
+
} finally {
|
|
12274
|
+
clearTimeout(timer2);
|
|
12275
|
+
}
|
|
12276
|
+
};
|
|
12277
|
+
return {
|
|
12278
|
+
async enrich(entries) {
|
|
12279
|
+
const settled = await Promise.allSettled(entries.map(async (entry) => {
|
|
12280
|
+
const agent = entry.agent;
|
|
12281
|
+
if (agent["@type"] !== "Software" || !agent.provider || !agent.model) return entry;
|
|
12282
|
+
const pair = inferencePairKey(agent.provider, agent.model);
|
|
12283
|
+
const client = clients.get(pair);
|
|
12284
|
+
if (!client) return entry;
|
|
12285
|
+
const limits = await consult(pair, client);
|
|
12286
|
+
return limits ? { ...entry, limits } : entry;
|
|
12287
|
+
}));
|
|
12288
|
+
return settled.map((s, i) => s.status === "fulfilled" ? s.value : entries[i]);
|
|
12289
|
+
}
|
|
12290
|
+
};
|
|
12291
|
+
}
|
|
12181
12292
|
|
|
12182
12293
|
// src/event-enrichment.ts
|
|
12183
12294
|
function eventAnnotationId(event) {
|
|
@@ -12207,7 +12318,7 @@ function asBusRequestPrimitive(eventBus) {
|
|
|
12207
12318
|
return {
|
|
12208
12319
|
emit(channel, payload) {
|
|
12209
12320
|
eventBus.get(channel).next(payload);
|
|
12210
|
-
return Promise.resolve();
|
|
12321
|
+
return Promise.resolve(-1);
|
|
12211
12322
|
},
|
|
12212
12323
|
stream(channel) {
|
|
12213
12324
|
return eventBus.get(channel).asObservable();
|
|
@@ -12656,6 +12767,38 @@ function registerJobCommandHandlers(eventBus, jobQueue, project, parentLogger) {
|
|
|
12656
12767
|
throw new Error("_userId is required (injected by bus gateway)");
|
|
12657
12768
|
}
|
|
12658
12769
|
const user = parseDidUser(_userId);
|
|
12770
|
+
let effectiveResourceId;
|
|
12771
|
+
if (jobType === "generation") {
|
|
12772
|
+
if (resId !== void 0) {
|
|
12773
|
+
throw new Error(
|
|
12774
|
+
"generation job:create must omit resourceId \u2014 the context's focus is authoritative"
|
|
12775
|
+
);
|
|
12776
|
+
}
|
|
12777
|
+
const bag = params;
|
|
12778
|
+
if (bag && bag.referenceId !== void 0) {
|
|
12779
|
+
throw new Error(
|
|
12780
|
+
"generation job:create must omit params.referenceId \u2014 the context's focus is authoritative"
|
|
12781
|
+
);
|
|
12782
|
+
}
|
|
12783
|
+
if (!isGenerationJobParams(params)) {
|
|
12784
|
+
throw new Error(
|
|
12785
|
+
"generation params do not satisfy GenerationJobParams (title, storageUri, and context are required)"
|
|
12786
|
+
);
|
|
12787
|
+
}
|
|
12788
|
+
const focus = params.context.focus;
|
|
12789
|
+
const rid = focus?.kind === "resource" ? focus.resource?.["@id"] : focus?.kind === "annotation" ? focus.sourceResource?.["@id"] : void 0;
|
|
12790
|
+
if (typeof rid !== "string" || rid.length === 0) {
|
|
12791
|
+
throw new Error(
|
|
12792
|
+
"generation context has no usable focus \u2014 pass a GatheredContext produced by gather.resource(...) or gather.annotation(...)"
|
|
12793
|
+
);
|
|
12794
|
+
}
|
|
12795
|
+
effectiveResourceId = rid;
|
|
12796
|
+
} else {
|
|
12797
|
+
if (typeof resId !== "string" || resId.length === 0) {
|
|
12798
|
+
throw new Error(`${jobType} job:create requires resourceId`);
|
|
12799
|
+
}
|
|
12800
|
+
effectiveResourceId = resId;
|
|
12801
|
+
}
|
|
12659
12802
|
const job = {
|
|
12660
12803
|
status: "pending",
|
|
12661
12804
|
metadata: {
|
|
@@ -12674,7 +12817,7 @@ function registerJobCommandHandlers(eventBus, jobQueue, project, parentLogger) {
|
|
|
12674
12817
|
maxRetries: jobType === "generation" ? 0 : 1
|
|
12675
12818
|
},
|
|
12676
12819
|
params: {
|
|
12677
|
-
resourceId: resourceId(
|
|
12820
|
+
resourceId: resourceId(effectiveResourceId),
|
|
12678
12821
|
...params
|
|
12679
12822
|
}
|
|
12680
12823
|
};
|
|
@@ -12880,35 +13023,40 @@ async function createKnowledgeSystemFromConfig(project, config, eventBus, logger
|
|
|
12880
13023
|
logger.info("Connecting to graph database", { type: graphConfig.type });
|
|
12881
13024
|
const graphDb = await withStartupTimeout("Graph database", getGraphDatabase(graphConfig));
|
|
12882
13025
|
const eventStore = createEventStore(project, eventBus, logger.child({ component: "event-store" }));
|
|
12883
|
-
let vectorStore;
|
|
12884
|
-
let embeddingProvider;
|
|
12885
13026
|
const vectorsConfig = config.services.vectors;
|
|
12886
13027
|
const embeddingConfig = config.services.embedding;
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
})
|
|
12909
|
-
|
|
12910
|
-
|
|
13028
|
+
const { createVectorStore, createEmbeddingProvider } = await import('@semiont/vectors');
|
|
13029
|
+
logger.info("Connecting to embedding provider", { type: embeddingConfig.type, model: embeddingConfig.model });
|
|
13030
|
+
const embeddingProvider = await withStartupTimeout(
|
|
13031
|
+
"Embedding provider",
|
|
13032
|
+
createEmbeddingProvider(embeddingConfig)
|
|
13033
|
+
);
|
|
13034
|
+
logger.info("Connecting to vector store", { type: vectorsConfig.type });
|
|
13035
|
+
const vectorStore = await withStartupTimeout(
|
|
13036
|
+
"Vector store",
|
|
13037
|
+
createVectorStore({
|
|
13038
|
+
type: vectorsConfig.type,
|
|
13039
|
+
host: vectorsConfig.host,
|
|
13040
|
+
port: vectorsConfig.port,
|
|
13041
|
+
// Dimensionality is discovered from the provider, so it is passed as a
|
|
13042
|
+
// thunk rather than probed here: the store calls it only if it needs it
|
|
13043
|
+
// (Qdrant, and only to CREATE a collection). This matches how inference
|
|
13044
|
+
// treats provider-derived facts — the client is built with no I/O and
|
|
13045
|
+
// `limits()` are discovered at the point of use — instead of making a
|
|
13046
|
+
// network round-trip a precondition of booting. A `memory` store, or a
|
|
13047
|
+
// Qdrant whose collections already exist, never consults the provider.
|
|
13048
|
+
dimensions: () => embeddingProvider.dimensions()
|
|
13049
|
+
})
|
|
13050
|
+
);
|
|
13051
|
+
if (vectorsConfig.type === "memory") {
|
|
13052
|
+
logger.info("memory vector store: the index rebuilds from the event log on every restart (reconcile re-embeds)");
|
|
12911
13053
|
}
|
|
13054
|
+
logger.info("Vector search initialized", {
|
|
13055
|
+
store: vectorsConfig.type,
|
|
13056
|
+
embedding: embeddingConfig.type,
|
|
13057
|
+
model: embeddingConfig.model
|
|
13058
|
+
});
|
|
13059
|
+
registerVectorIndexSizeProvider(() => vectorStore.count());
|
|
12912
13060
|
const kb = await createKnowledgeBase(eventStore, project, graphDb, eventBus, logger, {
|
|
12913
13061
|
vectorStore,
|
|
12914
13062
|
skipRebuild
|
|
@@ -12940,7 +13088,8 @@ async function createKnowledgeSystemFromConfig(project, config, eventBus, logger
|
|
|
12940
13088
|
embeddingProvider
|
|
12941
13089
|
);
|
|
12942
13090
|
await matcher.initialize();
|
|
12943
|
-
const
|
|
13091
|
+
const limitsDiscovery = createLimitsDiscovery(config, logger.child({ component: "limits-discovery" }));
|
|
13092
|
+
const browser = new Browser(kb.views, kb, eventBus, project, config, limitsDiscovery, embeddingProvider, logger.child({ component: "browser" }));
|
|
12944
13093
|
await browser.initialize();
|
|
12945
13094
|
const cloneTokenManager = new CloneTokenManager(kb, eventBus, logger.child({ component: "clone-token-manager" }));
|
|
12946
13095
|
await cloneTokenManager.initialize();
|
|
@@ -13023,6 +13172,7 @@ var LocalTransport = class {
|
|
|
13023
13172
|
}
|
|
13024
13173
|
}
|
|
13025
13174
|
);
|
|
13175
|
+
return -1;
|
|
13026
13176
|
}
|
|
13027
13177
|
on(channel, handler) {
|
|
13028
13178
|
const sub = this.bus.get(channel).subscribe(handler);
|
|
@@ -14250,7 +14400,6 @@ async function replayEvent(event, eventBus, eventStore, resolveBlob, contentStor
|
|
|
14250
14400
|
break;
|
|
14251
14401
|
// Job events are transient — skip during replay
|
|
14252
14402
|
case "job:started":
|
|
14253
|
-
case "job:progress":
|
|
14254
14403
|
case "job:completed":
|
|
14255
14404
|
case "job:failed":
|
|
14256
14405
|
logger?.debug("Skipping job event during replay", { type: event.type });
|