@pyxmate/memory 1.17.15 → 1.17.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-34MTVIYK.mjs +109 -0
- package/dist/{chunk-LGNSLDGB.mjs → chunk-MDFUZ3V2.mjs} +328 -194
- package/dist/{chunk-WDF5LAZS.mjs → chunk-ZVI7DCB4.mjs} +1 -1
- package/dist/cli/pyx-mem.mjs +1 -1
- package/dist/dashboard.mjs +3 -3
- package/dist/data-plane-contract.mjs +1 -1
- package/dist/index.d.ts +124 -7
- package/dist/index.mjs +4 -2
- package/dist/react.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-A3L46P2G.mjs +0 -57
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ../shared/src/graph/extraction.ts
|
|
2
|
+
function normalizeGraphLabel(value, fallback) {
|
|
3
|
+
const normalized = value.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
5
|
+
}
|
|
6
|
+
function normalizeNameKey(name) {
|
|
7
|
+
return name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
8
|
+
}
|
|
9
|
+
function requireGraphRecord(value, field) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
throw new Error(`${field} must be an object`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function requireNonemptyGraphString(value, field) {
|
|
16
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
17
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function assertGraphExtractionPayload(value, field = "graph extraction payload") {
|
|
22
|
+
const record = requireGraphRecord(value, field);
|
|
23
|
+
if (!Array.isArray(record.entities)) throw new Error(`${field}.entities must be an array`);
|
|
24
|
+
if (!Array.isArray(record.relationships)) {
|
|
25
|
+
throw new Error(`${field}.relationships must be an array`);
|
|
26
|
+
}
|
|
27
|
+
const entities = record.entities.map((item, index) => {
|
|
28
|
+
const entity = requireGraphRecord(item, `${field}.entities[${index}]`);
|
|
29
|
+
requireNonemptyGraphString(entity.name, `${field}.entities[${index}].name`);
|
|
30
|
+
requireNonemptyGraphString(entity.type, `${field}.entities[${index}].type`);
|
|
31
|
+
if (entity.properties !== void 0 && (!entity.properties || typeof entity.properties !== "object" || Array.isArray(entity.properties))) {
|
|
32
|
+
throw new Error(`${field}.entities[${index}].properties must be an object`);
|
|
33
|
+
}
|
|
34
|
+
return item;
|
|
35
|
+
});
|
|
36
|
+
const entityNames = new Set(entities.map((entity) => normalizeNameKey(entity.name)));
|
|
37
|
+
const relationships = record.relationships.map((item, index) => {
|
|
38
|
+
const relationship = requireGraphRecord(item, `${field}.relationships[${index}]`);
|
|
39
|
+
const source = requireNonemptyGraphString(
|
|
40
|
+
relationship.source,
|
|
41
|
+
`${field}.relationships[${index}].source`
|
|
42
|
+
);
|
|
43
|
+
const target = requireNonemptyGraphString(
|
|
44
|
+
relationship.target,
|
|
45
|
+
`${field}.relationships[${index}].target`
|
|
46
|
+
);
|
|
47
|
+
requireNonemptyGraphString(relationship.type, `${field}.relationships[${index}].type`);
|
|
48
|
+
if (!entityNames.has(normalizeNameKey(source)) || !entityNames.has(normalizeNameKey(target))) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`${field}.relationships[${index}] endpoints must reference declared entity names`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (relationship.properties !== void 0 && (!relationship.properties || typeof relationship.properties !== "object" || Array.isArray(relationship.properties))) {
|
|
54
|
+
throw new Error(`${field}.relationships[${index}].properties must be an object`);
|
|
55
|
+
}
|
|
56
|
+
return item;
|
|
57
|
+
});
|
|
58
|
+
return { entities, relationships };
|
|
59
|
+
}
|
|
60
|
+
function relationshipKey(relationship) {
|
|
61
|
+
return [
|
|
62
|
+
relationship.source.trim().toLowerCase(),
|
|
63
|
+
relationship.target.trim().toLowerCase(),
|
|
64
|
+
normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
65
|
+
].join("\0");
|
|
66
|
+
}
|
|
67
|
+
function mergeExtractedEntities(callerEntities, callerRelationships, extracted) {
|
|
68
|
+
const entities = [...callerEntities ?? []];
|
|
69
|
+
const relationships = [...callerRelationships ?? []];
|
|
70
|
+
const nameByLowercase = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const entity of entities) {
|
|
72
|
+
const key = entity.name.toLowerCase();
|
|
73
|
+
if (!nameByLowercase.has(key)) nameByLowercase.set(key, entity.name);
|
|
74
|
+
}
|
|
75
|
+
for (const entity of extracted.entities) {
|
|
76
|
+
const key = entity.name.toLowerCase();
|
|
77
|
+
if (nameByLowercase.has(key)) continue;
|
|
78
|
+
entities.push({ ...entity, type: normalizeGraphLabel(entity.type, "CONCEPT") });
|
|
79
|
+
nameByLowercase.set(key, entity.name);
|
|
80
|
+
}
|
|
81
|
+
for (const relationship of extracted.relations) {
|
|
82
|
+
const source = nameByLowercase.get(relationship.source.toLowerCase());
|
|
83
|
+
const target = nameByLowercase.get(relationship.target.toLowerCase());
|
|
84
|
+
if (source && target) {
|
|
85
|
+
relationships.push({
|
|
86
|
+
...relationship,
|
|
87
|
+
source,
|
|
88
|
+
target,
|
|
89
|
+
type: normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const seenRelationships = /* @__PURE__ */ new Set();
|
|
94
|
+
const dedupedRelationships = [];
|
|
95
|
+
for (const relationship of relationships) {
|
|
96
|
+
const key = relationshipKey(relationship);
|
|
97
|
+
if (seenRelationships.has(key)) continue;
|
|
98
|
+
seenRelationships.add(key);
|
|
99
|
+
dedupedRelationships.push(relationship);
|
|
100
|
+
}
|
|
101
|
+
return { entities, relationships: dedupedRelationships };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
normalizeGraphLabel,
|
|
106
|
+
normalizeNameKey,
|
|
107
|
+
assertGraphExtractionPayload,
|
|
108
|
+
mergeExtractedEntities
|
|
109
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertGraphExtractionPayload,
|
|
2
3
|
mergeExtractedEntities
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-34MTVIYK.mjs";
|
|
4
5
|
|
|
5
6
|
// ../shared/src/constants/defaults.ts
|
|
6
7
|
var DEFAULTS = {
|
|
@@ -424,7 +425,6 @@ var MemoryClient = class {
|
|
|
424
425
|
* collect the terminal event; there is no separate `ingestFile()` Promise
|
|
425
426
|
* method by design (one wire format, one SDK method).
|
|
426
427
|
*/
|
|
427
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: NDJSON dispatch + SDK enrichment fan-out is two cooperating paths; documented inline and tested in memory-client-events.test.ts.
|
|
428
428
|
async *ingestFileEvents(file, options) {
|
|
429
429
|
const controller = new AbortController();
|
|
430
430
|
const relayAbort = () => controller.abort(options?.signal?.reason);
|
|
@@ -441,237 +441,371 @@ var MemoryClient = class {
|
|
|
441
441
|
...this._authHeaders
|
|
442
442
|
};
|
|
443
443
|
if (wantsTextWindows) headers["X-Pyx-Enrichment-Capabilities"] = "text_windows_v1";
|
|
444
|
-
|
|
444
|
+
const raw = yield* this.streamNdjsonUpload(
|
|
445
|
+
"/api/memory/ingest/file",
|
|
446
|
+
formData,
|
|
447
|
+
headers,
|
|
448
|
+
controller.signal
|
|
449
|
+
);
|
|
450
|
+
if (!raw) return;
|
|
451
|
+
const serverResult = this.fileIngestResultFromEvent({
|
|
452
|
+
...raw,
|
|
453
|
+
schemaVersion: 1,
|
|
454
|
+
type: "result",
|
|
455
|
+
stage: "complete"
|
|
456
|
+
});
|
|
457
|
+
yield* this.completeIngestFileEvents(file, serverResult, options, controller.signal);
|
|
458
|
+
} finally {
|
|
459
|
+
options?.signal?.removeEventListener("abort", relayAbort);
|
|
460
|
+
controller.abort();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Graph-only re-enrichment for a document whose chunks are already stored
|
|
465
|
+
* and searchable but whose graph build failed. Uploads the original to
|
|
466
|
+
* `/api/memory/graph/enrich/file` (prepare-only — the server performs zero
|
|
467
|
+
* store/delete before the final graph write), runs the SAME enrichment
|
|
468
|
+
* callback engine as {@link ingestFileEvents}, then finalizes into one
|
|
469
|
+
* stable graph anchor keyed by `documentKey`. Repeating the same key
|
|
470
|
+
* replaces the document's graph references idempotently.
|
|
471
|
+
*
|
|
472
|
+
* The terminal `result` is a {@link GraphEnrichResult} event carrying the
|
|
473
|
+
* ACTUAL persisted graph counts; abort and failures yield a terminal
|
|
474
|
+
* `error` event instead (the server retains the pending session for retry).
|
|
475
|
+
*/
|
|
476
|
+
async *graphEnrichFileEvents(file, options) {
|
|
477
|
+
if (typeof options?.documentKey !== "string" || options.documentKey.trim().length === 0) {
|
|
478
|
+
throw new Error("graphEnrichFileEvents requires a non-empty documentKey");
|
|
479
|
+
}
|
|
480
|
+
if (!options.enrichment?.extractEntitiesV2) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
"graphEnrichFileEvents requires enrichment.extractEntitiesV2 \u2014 graph-only re-enrichment is caller-extraction by definition"
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const controller = new AbortController();
|
|
486
|
+
const relayAbort = () => controller.abort(options.signal?.reason);
|
|
487
|
+
if (options.signal?.aborted) relayAbort();
|
|
488
|
+
options.signal?.addEventListener("abort", relayAbort, { once: true });
|
|
489
|
+
try {
|
|
490
|
+
const formData = new FormData();
|
|
491
|
+
formData.append("file", file);
|
|
492
|
+
formData.append("documentKey", options.documentKey);
|
|
493
|
+
if (options.namespaceId) formData.append("namespaceId", options.namespaceId);
|
|
494
|
+
const headers = {
|
|
495
|
+
Accept: "application/x-ndjson",
|
|
496
|
+
"X-Pyx-Enrichment-Capabilities": "text_windows_v1",
|
|
497
|
+
...this._authHeaders
|
|
498
|
+
};
|
|
499
|
+
const raw = yield* this.streamNdjsonUpload(
|
|
500
|
+
"/api/memory/graph/enrich/file",
|
|
501
|
+
formData,
|
|
502
|
+
headers,
|
|
503
|
+
controller.signal
|
|
504
|
+
);
|
|
505
|
+
if (!raw) return;
|
|
445
506
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
yield this.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
460
|
-
if (!contentType.includes("application/x-ndjson")) {
|
|
461
|
-
yield this.ingestErrorEvent(
|
|
462
|
-
new MemoryServerError(
|
|
463
|
-
`Memory server returned ${contentType || "unknown content-type"} instead of application/x-ndjson \u2014 server is older than v0.15.0`,
|
|
464
|
-
res.status
|
|
465
|
-
),
|
|
466
|
-
"parsing"
|
|
467
|
-
);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (!res.body) {
|
|
471
|
-
yield this.ingestErrorEvent(
|
|
472
|
-
new MemoryServerError("Memory server returned an empty stream", res.status),
|
|
473
|
-
"parsing"
|
|
507
|
+
const prepared = raw;
|
|
508
|
+
if (prepared.purpose !== "graph-only" || !prepared.enrichment) {
|
|
509
|
+
throw new MemoryServerError(
|
|
510
|
+
"Graph enrichment prepare returned an unexpected terminal result",
|
|
511
|
+
0
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const outcome = yield* this.runEnrichmentCallbacks(
|
|
515
|
+
file,
|
|
516
|
+
prepared.enrichment,
|
|
517
|
+
options.enrichment,
|
|
518
|
+
controller.signal,
|
|
519
|
+
"graph-only"
|
|
474
520
|
);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
let serverResult = null;
|
|
482
|
-
try {
|
|
483
|
-
while (true) {
|
|
484
|
-
const { done, value } = await reader.read();
|
|
485
|
-
if (done) break;
|
|
486
|
-
buffer += decoder.decode(value, { stream: true });
|
|
487
|
-
const lines = buffer.split("\n");
|
|
488
|
-
buffer = lines.pop() ?? "";
|
|
489
|
-
for (const line of lines) {
|
|
490
|
-
if (!line.trim()) continue;
|
|
491
|
-
const raw = JSON.parse(line);
|
|
492
|
-
const type = raw.type;
|
|
493
|
-
if (type === "progress" || type === "heartbeat") {
|
|
494
|
-
const stage = this.normalizeActiveIngestStage(raw.stage);
|
|
495
|
-
if (!stage) continue;
|
|
496
|
-
currentStage = stage;
|
|
497
|
-
yield { ...raw, schemaVersion: 1, type, stage };
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
if (type === "result") {
|
|
501
|
-
serverResult = this.fileIngestResultFromEvent({
|
|
502
|
-
...raw,
|
|
503
|
-
schemaVersion: 1,
|
|
504
|
-
type: "result",
|
|
505
|
-
stage: "complete"
|
|
506
|
-
});
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
if (type === "error") {
|
|
510
|
-
yield {
|
|
511
|
-
schemaVersion: 1,
|
|
512
|
-
type: "error",
|
|
513
|
-
stage: this.normalizeActiveIngestStage(raw.stage) ?? currentStage,
|
|
514
|
-
error: typeof raw.error === "string" ? raw.error : "File ingest failed",
|
|
515
|
-
message: typeof raw.message === "string" ? raw.message : void 0,
|
|
516
|
-
code: typeof raw.code === "string" || typeof raw.code === "number" ? raw.code : void 0,
|
|
517
|
-
status: typeof raw.status === "number" ? raw.status : void 0
|
|
518
|
-
};
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (serverResult) break;
|
|
521
|
+
const data = outcome.data;
|
|
522
|
+
if (data?.purpose !== "graph-only" || !Array.isArray(data.entryIds)) {
|
|
523
|
+
throw new MemoryServerError(
|
|
524
|
+
"Graph enrichment finalization returned an unexpected result",
|
|
525
|
+
0
|
|
526
|
+
);
|
|
523
527
|
}
|
|
528
|
+
yield { schemaVersion: 1, type: "result", stage: "complete", ...data };
|
|
524
529
|
} catch (err) {
|
|
525
|
-
yield this.ingestErrorEvent(err,
|
|
526
|
-
return;
|
|
527
|
-
} finally {
|
|
528
|
-
reader.releaseLock();
|
|
530
|
+
yield this.ingestErrorEvent(err, "enrichment");
|
|
529
531
|
}
|
|
530
|
-
if (!serverResult) {
|
|
531
|
-
yield this.ingestErrorEvent(
|
|
532
|
-
new MemoryServerError("File ingest stream ended without a server result", 0),
|
|
533
|
-
currentStage
|
|
534
|
-
);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
yield* this.completeIngestFileEvents(file, serverResult, options, controller.signal);
|
|
538
532
|
} finally {
|
|
539
|
-
options
|
|
533
|
+
options.signal?.removeEventListener("abort", relayAbort);
|
|
540
534
|
controller.abort();
|
|
541
535
|
}
|
|
542
536
|
}
|
|
543
537
|
/**
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
538
|
+
* POST a multipart body to an NDJSON streaming endpoint and relay its
|
|
539
|
+
* progress/heartbeat events. Returns the raw terminal `result` record, or
|
|
540
|
+
* null after yielding a terminal error (transport failure, server error
|
|
541
|
+
* event, non-NDJSON response, stream ending without a result). One
|
|
542
|
+
* implementation for both streaming surfaces so the wire protocol cannot
|
|
543
|
+
* fork.
|
|
544
|
+
*/
|
|
545
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one stateful NDJSON reader validates and relays both ingest and graph-only stream envelopes
|
|
546
|
+
async *streamNdjsonUpload(path, formData, headers, signal) {
|
|
547
|
+
let res;
|
|
548
|
+
try {
|
|
549
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
550
|
+
method: "POST",
|
|
551
|
+
body: formData,
|
|
552
|
+
headers,
|
|
553
|
+
signal
|
|
554
|
+
});
|
|
555
|
+
} catch (err) {
|
|
556
|
+
yield this.ingestErrorEvent(this.translateFetchError(err, path), "parsing");
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
560
|
+
if (!contentType.includes("application/x-ndjson")) {
|
|
561
|
+
yield this.ingestErrorEvent(
|
|
562
|
+
new MemoryServerError(
|
|
563
|
+
`Memory server returned ${contentType || "unknown content-type"} instead of application/x-ndjson \u2014 server is older than v0.15.0`,
|
|
564
|
+
res.status
|
|
565
|
+
),
|
|
566
|
+
"parsing"
|
|
567
|
+
);
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
if (!res.body) {
|
|
571
|
+
yield this.ingestErrorEvent(
|
|
572
|
+
new MemoryServerError("Memory server returned an empty stream", res.status),
|
|
573
|
+
"parsing"
|
|
574
|
+
);
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
const reader = res.body.getReader();
|
|
578
|
+
const decoder = new TextDecoder();
|
|
579
|
+
let buffer = "";
|
|
580
|
+
let currentStage = "parsing";
|
|
581
|
+
let serverResult = null;
|
|
582
|
+
try {
|
|
583
|
+
while (true) {
|
|
584
|
+
const { done, value } = await reader.read();
|
|
585
|
+
if (done) break;
|
|
586
|
+
buffer += decoder.decode(value, { stream: true });
|
|
587
|
+
const lines = buffer.split("\n");
|
|
588
|
+
buffer = lines.pop() ?? "";
|
|
589
|
+
for (const line of lines) {
|
|
590
|
+
if (!line.trim()) continue;
|
|
591
|
+
const raw = JSON.parse(line);
|
|
592
|
+
const type = raw.type;
|
|
593
|
+
if (type === "progress" || type === "heartbeat") {
|
|
594
|
+
const stage = this.normalizeActiveIngestStage(raw.stage);
|
|
595
|
+
if (!stage) continue;
|
|
596
|
+
currentStage = stage;
|
|
597
|
+
yield { ...raw, schemaVersion: 1, type, stage };
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (type === "result") {
|
|
601
|
+
serverResult = raw;
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
if (type === "error") {
|
|
605
|
+
yield {
|
|
606
|
+
schemaVersion: 1,
|
|
607
|
+
type: "error",
|
|
608
|
+
stage: this.normalizeActiveIngestStage(raw.stage) ?? currentStage,
|
|
609
|
+
error: typeof raw.error === "string" ? raw.error : "File ingest failed",
|
|
610
|
+
message: typeof raw.message === "string" ? raw.message : void 0,
|
|
611
|
+
code: typeof raw.code === "string" || typeof raw.code === "number" ? raw.code : void 0,
|
|
612
|
+
status: typeof raw.status === "number" ? raw.status : void 0
|
|
613
|
+
};
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (serverResult) break;
|
|
618
|
+
}
|
|
619
|
+
} catch (err) {
|
|
620
|
+
yield this.ingestErrorEvent(err, currentStage);
|
|
621
|
+
return null;
|
|
622
|
+
} finally {
|
|
623
|
+
reader.releaseLock();
|
|
624
|
+
}
|
|
625
|
+
if (!serverResult) {
|
|
626
|
+
yield this.ingestErrorEvent(
|
|
627
|
+
new MemoryServerError("File ingest stream ended without a server result", 0),
|
|
628
|
+
currentStage
|
|
629
|
+
);
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
return serverResult;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Run the SDK-side enrichment phase for an ingest result and yield the
|
|
636
|
+
* single terminal {@link IngestResultEvent} at the end. Skips work cleanly
|
|
637
|
+
* when the server emitted no enrichment block or the caller wired no
|
|
638
|
+
* callbacks. The callback work itself lives in
|
|
639
|
+
* {@link runEnrichmentCallbacks} — shared with the graph-only surface.
|
|
549
640
|
*/
|
|
550
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: image-describe / extract-entities-v2 / no-op are documented decision branches; the fan-out covers the v0.15.0 enrichment phase + heartbeat plumbing.
|
|
551
641
|
async *completeIngestFileEvents(file, result, options, signal) {
|
|
552
642
|
try {
|
|
553
643
|
if (!result.enrichment || !options?.enrichment) {
|
|
554
644
|
yield { schemaVersion: 1, type: "result", stage: "complete", ...result };
|
|
555
645
|
return;
|
|
556
646
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
};
|
|
573
|
-
const CONCURRENCY = 5;
|
|
574
|
-
for (let i = 0; i < images.length; i += CONCURRENCY) {
|
|
575
|
-
const batch = images.slice(i, i + CONCURRENCY);
|
|
576
|
-
const batchResults = yield* this.withSdkHeartbeats(
|
|
577
|
-
"enrichment",
|
|
578
|
-
Promise.all(
|
|
579
|
-
batch.map(async (imageMeta) => {
|
|
580
|
-
const imageRes = await fetch(
|
|
581
|
-
`${this.baseUrl}/api/memory/files/${fileId}/images/${imageMeta.imageId}?token=${encodeURIComponent(token)}`,
|
|
582
|
-
{ headers: this._authHeaders, signal }
|
|
583
|
-
);
|
|
584
|
-
if (!imageRes.ok) {
|
|
585
|
-
throw new MemoryServerError(
|
|
586
|
-
`Failed to fetch image ${imageMeta.imageId}: ${imageRes.status}`,
|
|
587
|
-
imageRes.status
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
const imageBuffer = await imageRes.arrayBuffer();
|
|
591
|
-
const description = await describeImage(imageBuffer, imageMeta);
|
|
592
|
-
return { imageId: imageMeta.imageId, description };
|
|
593
|
-
})
|
|
594
|
-
),
|
|
595
|
-
signal
|
|
596
|
-
);
|
|
597
|
-
descriptions.push(...batchResults.filter((d) => d.description.trim().length > 0));
|
|
598
|
-
yield {
|
|
599
|
-
schemaVersion: 1,
|
|
600
|
-
type: "progress",
|
|
601
|
-
stage: "enrichment",
|
|
602
|
-
filename: file.name,
|
|
603
|
-
imagesTotal: images.length,
|
|
604
|
-
imagesDescribed: descriptions.length,
|
|
605
|
-
message: `Described ${descriptions.length}/${images.length} images`
|
|
606
|
-
};
|
|
607
|
-
}
|
|
647
|
+
const outcome = yield* this.runEnrichmentCallbacks(
|
|
648
|
+
file,
|
|
649
|
+
result.enrichment,
|
|
650
|
+
options.enrichment,
|
|
651
|
+
signal,
|
|
652
|
+
"ingest"
|
|
653
|
+
);
|
|
654
|
+
if (outcome.posted) {
|
|
655
|
+
const enrichData = outcome.data;
|
|
656
|
+
result.entryIds.push(...enrichData.entryIds);
|
|
657
|
+
result.chunks += outcome.descriptions.length;
|
|
658
|
+
result.totalCharacters += outcome.descriptions.reduce(
|
|
659
|
+
(sum, d) => sum + d.description.length,
|
|
660
|
+
0
|
|
661
|
+
);
|
|
608
662
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
663
|
+
yield { schemaVersion: 1, type: "result", stage: "complete", ...result };
|
|
664
|
+
} catch (err) {
|
|
665
|
+
yield this.ingestErrorEvent(err, "enrichment", result);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* The ONE enrichment callback engine, shared by {@link ingestFileEvents}
|
|
670
|
+
* and {@link graphEnrichFileEvents}: fetches extracted images, invokes
|
|
671
|
+
* `describeImage` with bounded concurrency, invokes `extractEntitiesV2`
|
|
672
|
+
* exactly once, and POSTs `/files/{fileId}/enrich` — emitting progress and
|
|
673
|
+
* heartbeat events around each slow step. Throws on any failure; the
|
|
674
|
+
* purpose-specific wrappers translate that into their terminal error.
|
|
675
|
+
*
|
|
676
|
+
* The purpose controls empty-output semantics: ingest skips an optional
|
|
677
|
+
* empty add-on; graph-only finalizes an extractor that ran and found zero
|
|
678
|
+
* entities, but refuses to erase prior graph state when no extractable input
|
|
679
|
+
* reached the callback at all.
|
|
680
|
+
*/
|
|
681
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: image-describe / extract-entities-v2 / no-op are documented decision branches; the fan-out covers the v0.15.0 enrichment phase + heartbeat plumbing.
|
|
682
|
+
async *runEnrichmentCallbacks(file, enrichment, callbacks, signal, purpose) {
|
|
683
|
+
const { fileId, token, expiresAt, images } = enrichment;
|
|
684
|
+
const isV2 = "version" in enrichment;
|
|
685
|
+
const textWindows = isV2 ? enrichment.textWindows : [];
|
|
686
|
+
const descriptions = [];
|
|
687
|
+
const describeImage = callbacks.describeImage;
|
|
688
|
+
if (describeImage && images.length > 0) {
|
|
689
|
+
yield {
|
|
690
|
+
schemaVersion: 1,
|
|
691
|
+
type: "progress",
|
|
692
|
+
stage: "enrichment",
|
|
693
|
+
filename: file.name,
|
|
694
|
+
imagesTotal: images.length,
|
|
695
|
+
imagesDescribed: 0,
|
|
696
|
+
message: `Describing extracted images (0/${images.length})`
|
|
697
|
+
};
|
|
698
|
+
const CONCURRENCY = 5;
|
|
699
|
+
for (let i = 0; i < images.length; i += CONCURRENCY) {
|
|
700
|
+
const batch = images.slice(i, i + CONCURRENCY);
|
|
701
|
+
const batchResults = yield* this.withSdkHeartbeats(
|
|
702
|
+
"enrichment",
|
|
703
|
+
Promise.all(
|
|
704
|
+
batch.map(async (imageMeta) => {
|
|
705
|
+
const imageRes = await fetch(
|
|
706
|
+
`${this.baseUrl}/api/memory/files/${fileId}/images/${imageMeta.imageId}?token=${encodeURIComponent(token)}`,
|
|
707
|
+
{ headers: this._authHeaders, signal }
|
|
708
|
+
);
|
|
709
|
+
if (!imageRes.ok) {
|
|
710
|
+
throw new MemoryServerError(
|
|
711
|
+
`Failed to fetch image ${imageMeta.imageId}: ${imageRes.status}`,
|
|
712
|
+
imageRes.status
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
const imageBuffer = await imageRes.arrayBuffer();
|
|
716
|
+
const description = await describeImage(imageBuffer, imageMeta);
|
|
717
|
+
return { imageId: imageMeta.imageId, description };
|
|
718
|
+
})
|
|
719
|
+
),
|
|
720
|
+
signal
|
|
721
|
+
);
|
|
722
|
+
descriptions.push(...batchResults.filter((d) => d.description.trim().length > 0));
|
|
613
723
|
yield {
|
|
614
724
|
schemaVersion: 1,
|
|
615
725
|
type: "progress",
|
|
616
726
|
stage: "enrichment",
|
|
617
727
|
filename: file.name,
|
|
618
|
-
|
|
728
|
+
imagesTotal: images.length,
|
|
729
|
+
imagesDescribed: descriptions.length,
|
|
730
|
+
message: `Described ${descriptions.length}/${images.length} images`
|
|
619
731
|
};
|
|
620
|
-
const extracted = yield* this.withSdkHeartbeats(
|
|
621
|
-
"enrichment",
|
|
622
|
-
options.enrichment.extractEntitiesV2({
|
|
623
|
-
textWindows,
|
|
624
|
-
imageDescriptions: imageDescriptionTexts,
|
|
625
|
-
mimeType: file.type,
|
|
626
|
-
filename: file.name
|
|
627
|
-
}),
|
|
628
|
-
signal
|
|
629
|
-
);
|
|
630
|
-
entities = extracted.entities;
|
|
631
|
-
relationships = extracted.relationships;
|
|
632
|
-
}
|
|
633
|
-
const hasGraph = (entities?.length ?? 0) > 0;
|
|
634
|
-
const hasImages = descriptions.length > 0;
|
|
635
|
-
if (!hasGraph && !hasImages) {
|
|
636
|
-
yield { schemaVersion: 1, type: "result", stage: "complete", ...result };
|
|
637
|
-
return;
|
|
638
732
|
}
|
|
733
|
+
}
|
|
734
|
+
let entities;
|
|
735
|
+
let relationships;
|
|
736
|
+
const imageDescriptionTexts = descriptions.map((d) => d.description);
|
|
737
|
+
if (callbacks.extractEntitiesV2 && (textWindows.length > 0 || imageDescriptionTexts.length > 0)) {
|
|
639
738
|
yield {
|
|
640
739
|
schemaVersion: 1,
|
|
641
740
|
type: "progress",
|
|
642
741
|
stage: "enrichment",
|
|
643
742
|
filename: file.name,
|
|
644
|
-
message: "
|
|
743
|
+
message: "Extracting entities"
|
|
645
744
|
};
|
|
646
|
-
const
|
|
745
|
+
const rawExtracted = yield* this.withSdkHeartbeats(
|
|
647
746
|
"enrichment",
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
...this._authHeaders
|
|
654
|
-
},
|
|
655
|
-
signal,
|
|
656
|
-
body: JSON.stringify({ imageDescriptions: descriptions, entities, relationships })
|
|
747
|
+
callbacks.extractEntitiesV2({
|
|
748
|
+
textWindows,
|
|
749
|
+
imageDescriptions: imageDescriptionTexts,
|
|
750
|
+
mimeType: file.type,
|
|
751
|
+
filename: file.name
|
|
657
752
|
}),
|
|
658
753
|
signal
|
|
659
754
|
);
|
|
660
|
-
|
|
661
|
-
|
|
755
|
+
let extracted;
|
|
756
|
+
try {
|
|
757
|
+
extracted = assertGraphExtractionPayload(rawExtracted, "extractEntitiesV2 result");
|
|
758
|
+
} catch (error) {
|
|
662
759
|
throw new MemoryServerError(
|
|
663
|
-
|
|
664
|
-
|
|
760
|
+
error instanceof Error ? error.message : "extractEntitiesV2 returned an invalid result",
|
|
761
|
+
422
|
|
665
762
|
);
|
|
666
763
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
764
|
+
entities = extracted.entities;
|
|
765
|
+
relationships = extracted.relationships;
|
|
766
|
+
}
|
|
767
|
+
const hadExtractionInput = textWindows.length > 0 || imageDescriptionTexts.length > 0;
|
|
768
|
+
if (purpose === "graph-only" && !hadExtractionInput) {
|
|
769
|
+
throw new MemoryServerError(
|
|
770
|
+
"Graph enrichment produced no extractable text or image descriptions; prior graph state was preserved",
|
|
771
|
+
422
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const hasGraph = (entities?.length ?? 0) > 0;
|
|
775
|
+
const hasImages = descriptions.length > 0;
|
|
776
|
+
if (!hasGraph && !hasImages && purpose === "ingest") {
|
|
777
|
+
return { posted: false, data: null, descriptions };
|
|
778
|
+
}
|
|
779
|
+
yield {
|
|
780
|
+
schemaVersion: 1,
|
|
781
|
+
type: "progress",
|
|
782
|
+
stage: "enrichment",
|
|
783
|
+
filename: file.name,
|
|
784
|
+
message: "Persisting enrichment"
|
|
785
|
+
};
|
|
786
|
+
const enrichRes = yield* this.withSdkHeartbeats(
|
|
787
|
+
"enrichment",
|
|
788
|
+
fetch(`${this.baseUrl}/api/memory/files/${fileId}/enrich`, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: {
|
|
791
|
+
"Content-Type": "application/json",
|
|
792
|
+
"X-Enrichment-Token": `${token}:${expiresAt}`,
|
|
793
|
+
...this._authHeaders
|
|
794
|
+
},
|
|
795
|
+
signal,
|
|
796
|
+
body: JSON.stringify({ imageDescriptions: descriptions, entities, relationships })
|
|
797
|
+
}),
|
|
798
|
+
signal
|
|
799
|
+
);
|
|
800
|
+
if (!enrichRes.ok) {
|
|
801
|
+
const body = await enrichRes.json().catch(() => ({}));
|
|
802
|
+
throw new MemoryServerError(
|
|
803
|
+
body.error ?? `Enrichment failed: ${enrichRes.status}`,
|
|
804
|
+
enrichRes.status
|
|
805
|
+
);
|
|
674
806
|
}
|
|
807
|
+
const data = await this.parseApiResponse(enrichRes);
|
|
808
|
+
return { posted: true, data, descriptions };
|
|
675
809
|
}
|
|
676
810
|
/**
|
|
677
811
|
* Race a Promise against a periodic heartbeat tick. Yields a heartbeat
|
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -835,7 +835,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
835
835
|
return server;
|
|
836
836
|
}
|
|
837
837
|
async function runMcpProxyServer(opts) {
|
|
838
|
-
const version = opts.version ?? (true ? "1.17.
|
|
838
|
+
const version = opts.version ?? (true ? "1.17.16" : "0.0.0-dev");
|
|
839
839
|
const read = await opts.readCredentials();
|
|
840
840
|
if (!read.ok) {
|
|
841
841
|
const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
|
package/dist/dashboard.mjs
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-ZVI7DCB4.mjs";
|
|
15
|
+
import "./chunk-MDFUZ3V2.mjs";
|
|
16
|
+
import "./chunk-34MTVIYK.mjs";
|
|
17
17
|
export {
|
|
18
18
|
DashboardClient,
|
|
19
19
|
Poller,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, PrincipalContext as PrincipalContext$1, SensitivityLevel as SensitivityLevel$1, MemoryStats as MemoryStats$1, LineageParams as LineageParams$1, LineageResult as LineageResult$1, ReinforceParams as ReinforceParams$1, ReinforceResult as ReinforceResult$1, WikiLintReport as WikiLintReport$1, GraphRepairResult as GraphRepairResult$1, ExtractedImageMeta as ExtractedImageMeta$1, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1, EntityExtractionResult as EntityExtractionResult$1, Topology as Topology$1, IngestEvent as IngestEvent$1, GraphNode as GraphNode$1, GraphTraversalResult as GraphTraversalResult$1, CorrectionRecord as CorrectionRecord$1 } from '@pyx-memory/shared';
|
|
1
|
+
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, PrincipalContext as PrincipalContext$1, SensitivityLevel as SensitivityLevel$1, MemoryStats as MemoryStats$1, LineageParams as LineageParams$1, LineageResult as LineageResult$1, ReinforceParams as ReinforceParams$1, ReinforceResult as ReinforceResult$1, WikiLintReport as WikiLintReport$1, GraphRepairResult as GraphRepairResult$1, ExtractedImageMeta as ExtractedImageMeta$1, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1, EntityExtractionResult as EntityExtractionResult$1, Topology as Topology$1, IngestEvent as IngestEvent$1, GraphEnrichEvent as GraphEnrichEvent$1, GraphNode as GraphNode$1, GraphTraversalResult as GraphTraversalResult$1, CorrectionRecord as CorrectionRecord$1 } from '@pyx-memory/shared';
|
|
2
2
|
|
|
3
3
|
/** Parameters for paginated entry listing. */
|
|
4
4
|
interface MemoryListParams {
|
|
@@ -247,6 +247,22 @@ interface IngestFileOptions {
|
|
|
247
247
|
signal?: AbortSignal;
|
|
248
248
|
namespaceId?: string;
|
|
249
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Options for {@link MemoryClient.graphEnrichFileEvents}. `documentKey` is the
|
|
252
|
+
* caller's stable logical identity for the document whose graph is being
|
|
253
|
+
* rebuilt — the server derives the graph anchor id from
|
|
254
|
+
* (tenant, namespace, documentKey), so repeating the same key replaces the
|
|
255
|
+
* document's graph references instead of multiplying them.
|
|
256
|
+
*
|
|
257
|
+
* `enrichment.extractEntitiesV2` is required: graph-only re-enrichment is
|
|
258
|
+
* caller-extraction by definition (there is no server-side fallback).
|
|
259
|
+
*/
|
|
260
|
+
interface GraphEnrichFileOptions {
|
|
261
|
+
documentKey: string;
|
|
262
|
+
namespaceId?: string;
|
|
263
|
+
signal?: AbortSignal;
|
|
264
|
+
enrichment: EnrichmentCallbacks;
|
|
265
|
+
}
|
|
250
266
|
/**
|
|
251
267
|
* Caller-supplied enrichment for the per-call store path. Mirrors
|
|
252
268
|
* {@link EnrichmentCallbacks} for file ingest. When supplied, the SDK invokes
|
|
@@ -360,13 +376,50 @@ declare class MemoryClient implements ExtendedMemoryInterface {
|
|
|
360
376
|
*/
|
|
361
377
|
ingestFileEvents(file: File, options?: IngestFileOptions): AsyncIterable<IngestEvent$1>;
|
|
362
378
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
379
|
+
* Graph-only re-enrichment for a document whose chunks are already stored
|
|
380
|
+
* and searchable but whose graph build failed. Uploads the original to
|
|
381
|
+
* `/api/memory/graph/enrich/file` (prepare-only — the server performs zero
|
|
382
|
+
* store/delete before the final graph write), runs the SAME enrichment
|
|
383
|
+
* callback engine as {@link ingestFileEvents}, then finalizes into one
|
|
384
|
+
* stable graph anchor keyed by `documentKey`. Repeating the same key
|
|
385
|
+
* replaces the document's graph references idempotently.
|
|
386
|
+
*
|
|
387
|
+
* The terminal `result` is a {@link GraphEnrichResult} event carrying the
|
|
388
|
+
* ACTUAL persisted graph counts; abort and failures yield a terminal
|
|
389
|
+
* `error` event instead (the server retains the pending session for retry).
|
|
390
|
+
*/
|
|
391
|
+
graphEnrichFileEvents(file: File, options: GraphEnrichFileOptions): AsyncIterable<GraphEnrichEvent$1>;
|
|
392
|
+
/**
|
|
393
|
+
* POST a multipart body to an NDJSON streaming endpoint and relay its
|
|
394
|
+
* progress/heartbeat events. Returns the raw terminal `result` record, or
|
|
395
|
+
* null after yielding a terminal error (transport failure, server error
|
|
396
|
+
* event, non-NDJSON response, stream ending without a result). One
|
|
397
|
+
* implementation for both streaming surfaces so the wire protocol cannot
|
|
398
|
+
* fork.
|
|
399
|
+
*/
|
|
400
|
+
private streamNdjsonUpload;
|
|
401
|
+
/**
|
|
402
|
+
* Run the SDK-side enrichment phase for an ingest result and yield the
|
|
403
|
+
* single terminal {@link IngestResultEvent} at the end. Skips work cleanly
|
|
404
|
+
* when the server emitted no enrichment block or the caller wired no
|
|
405
|
+
* callbacks. The callback work itself lives in
|
|
406
|
+
* {@link runEnrichmentCallbacks} — shared with the graph-only surface.
|
|
368
407
|
*/
|
|
369
408
|
private completeIngestFileEvents;
|
|
409
|
+
/**
|
|
410
|
+
* The ONE enrichment callback engine, shared by {@link ingestFileEvents}
|
|
411
|
+
* and {@link graphEnrichFileEvents}: fetches extracted images, invokes
|
|
412
|
+
* `describeImage` with bounded concurrency, invokes `extractEntitiesV2`
|
|
413
|
+
* exactly once, and POSTs `/files/{fileId}/enrich` — emitting progress and
|
|
414
|
+
* heartbeat events around each slow step. Throws on any failure; the
|
|
415
|
+
* purpose-specific wrappers translate that into their terminal error.
|
|
416
|
+
*
|
|
417
|
+
* The purpose controls empty-output semantics: ingest skips an optional
|
|
418
|
+
* empty add-on; graph-only finalizes an extractor that ran and found zero
|
|
419
|
+
* entities, but refuses to erase prior graph state when no extractable input
|
|
420
|
+
* reached the callback at all.
|
|
421
|
+
*/
|
|
422
|
+
private runEnrichmentCallbacks;
|
|
370
423
|
/**
|
|
371
424
|
* Race a Promise against a periodic heartbeat tick. Yields a heartbeat
|
|
372
425
|
* IngestEvent every {@link INGEST_EVENT_HEARTBEAT_MS} until the promise
|
|
@@ -1274,6 +1327,10 @@ interface EntityExtractionResult {
|
|
|
1274
1327
|
type: string;
|
|
1275
1328
|
}>;
|
|
1276
1329
|
}
|
|
1330
|
+
interface GraphExtractionPayload {
|
|
1331
|
+
entities: IngestEntity[];
|
|
1332
|
+
relationships: IngestRelationship[];
|
|
1333
|
+
}
|
|
1277
1334
|
declare function normalizeGraphLabel(value: string, fallback: string): string;
|
|
1278
1335
|
/**
|
|
1279
1336
|
* Identity key for a graph node: the normalized name alone. `type` is a node
|
|
@@ -1287,6 +1344,16 @@ declare function normalizeGraphLabel(value: string, fallback: string): string;
|
|
|
1287
1344
|
* edge resolvability with it — all three MUST agree, so they import this one fn.
|
|
1288
1345
|
*/
|
|
1289
1346
|
declare function normalizeNameKey(name: string): string;
|
|
1347
|
+
/**
|
|
1348
|
+
* Runtime contract for destructive graph replacement payloads. TypeScript
|
|
1349
|
+
* callback types do not protect JavaScript callers or malformed LLM adapter
|
|
1350
|
+
* results; accepting a missing `entities` field as an empty extraction would
|
|
1351
|
+
* erase the prior graph. Both arrays are therefore required, every item is
|
|
1352
|
+
* structurally validated, and every relationship endpoint must resolve to a
|
|
1353
|
+
* declared entity under the graph store's exact name identity normalizer.
|
|
1354
|
+
* A genuinely empty `{ entities: [], relationships: [] }` remains valid.
|
|
1355
|
+
*/
|
|
1356
|
+
declare function assertGraphExtractionPayload(value: unknown, field?: string): GraphExtractionPayload;
|
|
1290
1357
|
/**
|
|
1291
1358
|
* Merge caller-provided entities/relationships with LLM-extracted ones.
|
|
1292
1359
|
*
|
|
@@ -1522,6 +1589,56 @@ interface IngestErrorEvent {
|
|
|
1522
1589
|
partialResult?: FileIngestResult;
|
|
1523
1590
|
}
|
|
1524
1591
|
type IngestEvent = IngestProgressEvent | IngestHeartbeatEvent | IngestResultEvent | IngestErrorEvent;
|
|
1592
|
+
/** What a pending enrichment session finalizes into. Sessions created before
|
|
1593
|
+
* graph-only support carry no purpose and behave as `'ingest'`. */
|
|
1594
|
+
type EnrichmentPurpose = 'ingest' | 'graph-only';
|
|
1595
|
+
/**
|
|
1596
|
+
* Terminal server event for POST /api/memory/graph/enrich/file. Deliberately
|
|
1597
|
+
* NOT an {@link IngestResultEvent}: the prepare phase stores nothing, so there
|
|
1598
|
+
* are no chunk counters to report — fabricating `chunks: 0` would make the
|
|
1599
|
+
* result indistinguishable from a real empty ingest.
|
|
1600
|
+
*/
|
|
1601
|
+
interface GraphEnrichPreparedEvent {
|
|
1602
|
+
schemaVersion: 1;
|
|
1603
|
+
type: 'result';
|
|
1604
|
+
stage: 'complete';
|
|
1605
|
+
purpose: 'graph-only';
|
|
1606
|
+
filename: string;
|
|
1607
|
+
fileType: string;
|
|
1608
|
+
enrichment: EnrichmentPendingV2;
|
|
1609
|
+
message?: string;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Terminal result of a graph-only re-enrichment run — also the response body
|
|
1613
|
+
* of POST /files/{fileId}/enrich when the pending session's purpose is
|
|
1614
|
+
* `'graph-only'`. Counts reflect ACTUAL graph persistence (mirrors
|
|
1615
|
+
* {@link EnrichResult}), never the submitted payload sizes.
|
|
1616
|
+
*/
|
|
1617
|
+
interface GraphEnrichResult {
|
|
1618
|
+
purpose: 'graph-only';
|
|
1619
|
+
filename: string;
|
|
1620
|
+
fileType: string;
|
|
1621
|
+
/** The single stable anchor entry id, including a successful zero-entity replacement. */
|
|
1622
|
+
entryIds: string[];
|
|
1623
|
+
entitiesStored: number;
|
|
1624
|
+
relationshipsStored: number;
|
|
1625
|
+
/** Present (and >0) only when edges were dropped — see {@link EnrichResult}. */
|
|
1626
|
+
relationshipsDropped?: number;
|
|
1627
|
+
droppedRelationships?: DroppedGraphRelationship[];
|
|
1628
|
+
}
|
|
1629
|
+
/** Terminal success of the SDK's graph-only stream — exactly one per run. */
|
|
1630
|
+
interface GraphEnrichResultEvent extends GraphEnrichResult {
|
|
1631
|
+
schemaVersion: 1;
|
|
1632
|
+
type: 'result';
|
|
1633
|
+
stage: 'complete';
|
|
1634
|
+
message?: string;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Typed view of the SDK's `graphEnrichFileEvents()` stream. Progress /
|
|
1638
|
+
* heartbeat / error envelopes are shared with the ingest stream (same wire
|
|
1639
|
+
* protocol); only the terminal result differs.
|
|
1640
|
+
*/
|
|
1641
|
+
type GraphEnrichEvent = IngestProgressEvent | IngestHeartbeatEvent | GraphEnrichResultEvent | IngestErrorEvent;
|
|
1525
1642
|
|
|
1526
1643
|
/**
|
|
1527
1644
|
* Namespace topology-isolation modes for v0.17.0.
|
|
@@ -1636,4 +1753,4 @@ interface CreatePyxMemoryOptions {
|
|
|
1636
1753
|
}
|
|
1637
1754
|
declare function createPyxMemory(opts?: CreatePyxMemoryOptions): MemoryClient;
|
|
1638
1755
|
|
|
1639
|
-
export { type AgentId, type ApiResponse, type ConsolidationRunResult, type CorrectionInput, type CorrectionRecord, type CreatePyxMemoryOptions, DEFAULTS, DEPRECATED_RAG_STRATEGIES, DisabledMemory, type DroppedGraphRelationship, type DueScanInput, EmbeddingProviderName, type EnrichmentCallbacks, type EntityExtractionResult, type ExtendedMemoryInterface, type FetchCorrectionsInput, type GraphEnrichment, type GraphEnrichmentStatus, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphRepairResult, type GraphTelemetrySnapshot, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, type LineageParams, type LineageResult, type LineageVersion, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemoryLogFilters, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, type MoveEntriesFilter, MoveFailureReason, type MoveResult, type MoveTarget, NamespaceIsolation, type PrincipalContext, RAGStrategy, type ReinforceParams, type ReinforceResult, type ReinforceSignal, SINGLE_TENANT_ID, type SecretElevationAggregate, type SecretElevationNotice, SensitivityLevel, type SourceEvidence, type StoreInput, StoreTarget, TAXONOMY_MAX_CATEGORIES, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, type Topology, type TopologyExtractionProvider, type TopologyServiceVariant, type UsageHygieneSnapshot, VectorProvider, type VectorStatus, type WikiLintReport, createPyxMemory, mergeExtractedEntities, normalizeGraphLabel, normalizeNameKey, projectSearchResponseForMcp, secretElevationAggregate, secretElevationNoticeFor, withSecretElevationNotice };
|
|
1756
|
+
export { type AgentId, type ApiResponse, type ConsolidationRunResult, type CorrectionInput, type CorrectionRecord, type CreatePyxMemoryOptions, DEFAULTS, DEPRECATED_RAG_STRATEGIES, DisabledMemory, type DroppedGraphRelationship, type DueScanInput, EmbeddingProviderName, type EnrichmentCallbacks, type EnrichmentPurpose, type EntityExtractionResult, type ExtendedMemoryInterface, type FetchCorrectionsInput, type GraphEnrichEvent, type GraphEnrichFileOptions, type GraphEnrichPreparedEvent, type GraphEnrichResult, type GraphEnrichResultEvent, type GraphEnrichment, type GraphEnrichmentStatus, type GraphExtractionPayload, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphRepairResult, type GraphTelemetrySnapshot, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, type LineageParams, type LineageResult, type LineageVersion, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemoryLogFilters, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, type MoveEntriesFilter, MoveFailureReason, type MoveResult, type MoveTarget, NamespaceIsolation, type PrincipalContext, RAGStrategy, type ReinforceParams, type ReinforceResult, type ReinforceSignal, SINGLE_TENANT_ID, type SecretElevationAggregate, type SecretElevationNotice, SensitivityLevel, type SourceEvidence, type StoreInput, StoreTarget, TAXONOMY_MAX_CATEGORIES, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, type Topology, type TopologyExtractionProvider, type TopologyServiceVariant, type UsageHygieneSnapshot, VectorProvider, type VectorStatus, type WikiLintReport, assertGraphExtractionPayload, createPyxMemory, mergeExtractedEntities, normalizeGraphLabel, normalizeNameKey, projectSearchResponseForMcp, secretElevationAggregate, secretElevationNoticeFor, withSecretElevationNotice };
|
package/dist/index.mjs
CHANGED
|
@@ -18,12 +18,13 @@ import {
|
|
|
18
18
|
secretElevationAggregate,
|
|
19
19
|
secretElevationNoticeFor,
|
|
20
20
|
withSecretElevationNotice
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-MDFUZ3V2.mjs";
|
|
22
22
|
import {
|
|
23
|
+
assertGraphExtractionPayload,
|
|
23
24
|
mergeExtractedEntities,
|
|
24
25
|
normalizeGraphLabel,
|
|
25
26
|
normalizeNameKey
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-34MTVIYK.mjs";
|
|
27
28
|
|
|
28
29
|
// src/preset.ts
|
|
29
30
|
var DEFAULT_MEMORY_URL = `http://localhost:${DEFAULTS.MEMORY_SERVER_PORT}`;
|
|
@@ -54,6 +55,7 @@ export {
|
|
|
54
55
|
StoreTarget,
|
|
55
56
|
TAXONOMY_MAX_CATEGORIES,
|
|
56
57
|
VectorProvider,
|
|
58
|
+
assertGraphExtractionPayload,
|
|
57
59
|
createPyxMemory,
|
|
58
60
|
mergeExtractedEntities,
|
|
59
61
|
normalizeGraphLabel,
|
package/dist/react.mjs
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-ZVI7DCB4.mjs";
|
|
15
|
+
import "./chunk-MDFUZ3V2.mjs";
|
|
16
|
+
import "./chunk-34MTVIYK.mjs";
|
|
17
17
|
|
|
18
18
|
// ../dashboard/src/hooks/use-consolidation-log.ts
|
|
19
19
|
import { useCallback as useCallback2, useMemo } from "react";
|
package/package.json
CHANGED
package/dist/chunk-A3L46P2G.mjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// ../shared/src/graph/extraction.ts
|
|
2
|
-
function normalizeGraphLabel(value, fallback) {
|
|
3
|
-
const normalized = value.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4
|
-
return normalized.length > 0 ? normalized : fallback;
|
|
5
|
-
}
|
|
6
|
-
function normalizeNameKey(name) {
|
|
7
|
-
return name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
8
|
-
}
|
|
9
|
-
function relationshipKey(relationship) {
|
|
10
|
-
return [
|
|
11
|
-
relationship.source.trim().toLowerCase(),
|
|
12
|
-
relationship.target.trim().toLowerCase(),
|
|
13
|
-
normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
14
|
-
].join("\0");
|
|
15
|
-
}
|
|
16
|
-
function mergeExtractedEntities(callerEntities, callerRelationships, extracted) {
|
|
17
|
-
const entities = [...callerEntities ?? []];
|
|
18
|
-
const relationships = [...callerRelationships ?? []];
|
|
19
|
-
const nameByLowercase = /* @__PURE__ */ new Map();
|
|
20
|
-
for (const entity of entities) {
|
|
21
|
-
const key = entity.name.toLowerCase();
|
|
22
|
-
if (!nameByLowercase.has(key)) nameByLowercase.set(key, entity.name);
|
|
23
|
-
}
|
|
24
|
-
for (const entity of extracted.entities) {
|
|
25
|
-
const key = entity.name.toLowerCase();
|
|
26
|
-
if (nameByLowercase.has(key)) continue;
|
|
27
|
-
entities.push({ ...entity, type: normalizeGraphLabel(entity.type, "CONCEPT") });
|
|
28
|
-
nameByLowercase.set(key, entity.name);
|
|
29
|
-
}
|
|
30
|
-
for (const relationship of extracted.relations) {
|
|
31
|
-
const source = nameByLowercase.get(relationship.source.toLowerCase());
|
|
32
|
-
const target = nameByLowercase.get(relationship.target.toLowerCase());
|
|
33
|
-
if (source && target) {
|
|
34
|
-
relationships.push({
|
|
35
|
-
...relationship,
|
|
36
|
-
source,
|
|
37
|
-
target,
|
|
38
|
-
type: normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const seenRelationships = /* @__PURE__ */ new Set();
|
|
43
|
-
const dedupedRelationships = [];
|
|
44
|
-
for (const relationship of relationships) {
|
|
45
|
-
const key = relationshipKey(relationship);
|
|
46
|
-
if (seenRelationships.has(key)) continue;
|
|
47
|
-
seenRelationships.add(key);
|
|
48
|
-
dedupedRelationships.push(relationship);
|
|
49
|
-
}
|
|
50
|
-
return { entities, relationships: dedupedRelationships };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
normalizeGraphLabel,
|
|
55
|
-
normalizeNameKey,
|
|
56
|
-
mergeExtractedEntities
|
|
57
|
-
};
|