@pyxmate/memory 1.13.0 → 1.15.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/dist/agent-contract.mjs +1 -1
- package/dist/{chunk-OUSGU272.mjs → chunk-3BTAAGMM.mjs} +12 -11
- package/dist/{chunk-HOBDPIHO.mjs → chunk-3FRA37TI.mjs} +1 -1
- package/dist/chunk-A3L46P2G.mjs +57 -0
- package/dist/{chunk-5BLT7RSY.mjs → chunk-IEBF7BQF.mjs} +48 -67
- package/dist/cli/pyx-mem.mjs +38 -6
- package/dist/dashboard.mjs +3 -2
- package/dist/data-plane-contract.d.ts +234 -0
- package/dist/data-plane-contract.mjs +1150 -0
- package/dist/index.d.ts +93 -54
- package/dist/index.mjs +4 -2
- package/dist/react.mjs +3 -2
- package/package.json +5 -1
package/dist/agent-contract.mjs
CHANGED
|
@@ -12,33 +12,34 @@ var PERSISTENT_MEMORY_SECTION = [
|
|
|
12
12
|
"- When the user references prior work, search for it.",
|
|
13
13
|
"- When investigating a bug, search for prior occurrences and fixes.",
|
|
14
14
|
'- When a question names a time \u2014 explicit or relative ("last year") \u2014 resolve it to an absolute ISO-8601 timestamp and pass it as search `anchorTime`: results rank by proximity to that time (soft \u2014 never excludes).',
|
|
15
|
-
"- Match search `effort` to risk: use `quick` for routine recall; use `deep` when quick returns nothing,
|
|
15
|
+
"- Match search `effort` to risk: use `quick` for routine recall; use `deep` when quick returns nothing, state may be superseded, or full history matters.",
|
|
16
16
|
"- For count/list questions pass `enumerationConcept` (the category phrase, any language); for Korean/cross-lingual recall add `enableRerank: true` (hybrid-only, slower).",
|
|
17
|
-
'- To trace how a fact changed ("what did X use before Y"
|
|
17
|
+
'- To trace how a fact changed ("what did X use before Y"), call `lineage` with `subject` + `relation` or an `entryId` instead of stitching together searches.',
|
|
18
|
+
"- When answering about current goals, priorities, or active work: treat explicitly completed or closed items as no longer active; when one goal explicitly replaces another, act on the replacement; goals stated to run concurrently stay active until each is individually completed or replaced; choose priorities only among still-active goals.",
|
|
18
19
|
"- Recalled memories reflect what was true when written \u2014 verify named files, functions, flags, and versions before acting.",
|
|
19
20
|
"",
|
|
20
21
|
"Reinforce and correct:",
|
|
21
22
|
"",
|
|
22
|
-
"- After a retrieved memory actually informs your work (you cited it or acted on it), call `reinforce` on that memory.
|
|
23
|
-
"- When
|
|
24
|
-
"- Before starting a task, call `fetch_applicable_corrections` with the task shape; pyx never auto-applies them, so decide which corrections fit.",
|
|
23
|
+
"- After a retrieved memory actually informs your work (you cited it or acted on it), call `reinforce` on that memory.",
|
|
24
|
+
"- When corrected, call `record_correction` with what was wrong, the replacement, when it applies, and service/file identifiers in `appliesWhen`; identifier overlap enables cross-phrase/language recall.",
|
|
25
|
+
"- Before starting a task, call `fetch_applicable_corrections` with the task shape (name identifiers); pyx never auto-applies them, so decide which corrections fit.",
|
|
25
26
|
"",
|
|
26
|
-
"Store when durable state settles
|
|
27
|
+
"Store when durable state settles, usually before a final response/handoff; if nothing durable changed, store nothing. Capture:",
|
|
27
28
|
"",
|
|
28
|
-
'- Facts (default) \u2014
|
|
29
|
+
'- Facts (default) \u2014 one correction/preference, root-cause fix, reasoned design decision, integration detail, gotcha, or explicit "remember this" request per memory.',
|
|
29
30
|
"- Process (`type: long-term`) \u2014 a reusable workflow, convention, or playbook a fresh session should follow. Store it once stabilized or adopted.",
|
|
30
31
|
"- Trajectory (`type: episodic`) \u2014 only at a real milestone, handoff, or user-requested checkpoint: one dated snapshot of what changed, where it stands, and the open frontier. Not a running log.",
|
|
31
|
-
"- Evolution \u2014 when a prior fact or decision changes, store the new state with its `eventTime` rather than overwriting the old one, so `lineage` can trace how and why it evolved.
|
|
32
|
+
"- Evolution \u2014 when a prior fact or decision changes, store the new state with its `eventTime` rather than overwriting the old one, so `lineage` can trace how and why it evolved.",
|
|
32
33
|
"",
|
|
33
34
|
"Memory rules:",
|
|
34
35
|
"",
|
|
35
36
|
"- Always include `topic` and `project`.",
|
|
36
37
|
"- Store concise facts and decisions, not deliberation.",
|
|
37
38
|
"- Keep one memory per concept; do not bundle unrelated facts.",
|
|
38
|
-
"- Do not store ephemeral
|
|
39
|
+
"- Do not store ephemeral files, raw logs, or progress. Aggregate high-frequency streams into a dated fact/summary with `eventTime`; never one memory or graph node per sample.",
|
|
39
40
|
'- Pass `eventTime` (ISO-8601, when the fact happened or took effect) for facts that can change or go stale. Recency ordering, dated ("as of") queries, and stale-vs-current resolution key off it.',
|
|
40
|
-
"- Graph is
|
|
41
|
-
'- Use specific durable entities
|
|
41
|
+
"- Graph is for retrieval. For named people, organizations, tools, places, events, or concepts, pass `entities` + `relationships` (caller-wins); connect multi-entity stores with a relationship or `triples`. Relationships matter as much as entities. Entity-free memories are valid.",
|
|
42
|
+
'- Use specific durable entities, never catch-all hubs ("me", "health"); bounded `IS_A` categories are the exception.',
|
|
42
43
|
'- For countable categories the user may later enumerate, add a canonical category `CONCEPT` node and an `IS_A` edge from each member to it (`"yoga"` IS_A `"fitness classes"`).',
|
|
43
44
|
"- Taxonomy: when a store response reports unnamed clusters, call get_taxonomy_state and name each via name_cluster (short, domain-true, user's language); rename only when composition clearly changed.",
|
|
44
45
|
"- Ingest documents/images only when they are worth persisting; images require a short description so they are searchable.",
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeExtractedEntities
|
|
3
|
+
} from "./chunk-A3L46P2G.mjs";
|
|
4
|
+
|
|
1
5
|
// ../shared/src/constants/defaults.ts
|
|
2
6
|
var DEFAULTS = {
|
|
3
7
|
DATA_DIR: "./data",
|
|
@@ -6,58 +10,6 @@ var DEFAULTS = {
|
|
|
6
10
|
};
|
|
7
11
|
var TAXONOMY_MAX_CATEGORIES = 10;
|
|
8
12
|
|
|
9
|
-
// ../shared/src/graph/extraction.ts
|
|
10
|
-
function normalizeGraphLabel(value, fallback) {
|
|
11
|
-
const normalized = value.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
12
|
-
return normalized.length > 0 ? normalized : fallback;
|
|
13
|
-
}
|
|
14
|
-
function normalizeNameKey(name) {
|
|
15
|
-
return name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
16
|
-
}
|
|
17
|
-
function relationshipKey(relationship) {
|
|
18
|
-
return [
|
|
19
|
-
relationship.source.trim().toLowerCase(),
|
|
20
|
-
relationship.target.trim().toLowerCase(),
|
|
21
|
-
normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
22
|
-
].join("\0");
|
|
23
|
-
}
|
|
24
|
-
function mergeExtractedEntities(callerEntities, callerRelationships, extracted) {
|
|
25
|
-
const entities = [...callerEntities ?? []];
|
|
26
|
-
const relationships = [...callerRelationships ?? []];
|
|
27
|
-
const nameByLowercase = /* @__PURE__ */ new Map();
|
|
28
|
-
for (const entity of entities) {
|
|
29
|
-
const key = entity.name.toLowerCase();
|
|
30
|
-
if (!nameByLowercase.has(key)) nameByLowercase.set(key, entity.name);
|
|
31
|
-
}
|
|
32
|
-
for (const entity of extracted.entities) {
|
|
33
|
-
const key = entity.name.toLowerCase();
|
|
34
|
-
if (nameByLowercase.has(key)) continue;
|
|
35
|
-
entities.push({ ...entity, type: normalizeGraphLabel(entity.type, "CONCEPT") });
|
|
36
|
-
nameByLowercase.set(key, entity.name);
|
|
37
|
-
}
|
|
38
|
-
for (const relationship of extracted.relations) {
|
|
39
|
-
const source = nameByLowercase.get(relationship.source.toLowerCase());
|
|
40
|
-
const target = nameByLowercase.get(relationship.target.toLowerCase());
|
|
41
|
-
if (source && target) {
|
|
42
|
-
relationships.push({
|
|
43
|
-
...relationship,
|
|
44
|
-
source,
|
|
45
|
-
target,
|
|
46
|
-
type: normalizeGraphLabel(relationship.type, "RELATED_TO")
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const seenRelationships = /* @__PURE__ */ new Set();
|
|
51
|
-
const dedupedRelationships = [];
|
|
52
|
-
for (const relationship of relationships) {
|
|
53
|
-
const key = relationshipKey(relationship);
|
|
54
|
-
if (seenRelationships.has(key)) continue;
|
|
55
|
-
seenRelationships.add(key);
|
|
56
|
-
dedupedRelationships.push(relationship);
|
|
57
|
-
}
|
|
58
|
-
return { entities, relationships: dedupedRelationships };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
13
|
// ../shared/src/types/isolation.ts
|
|
62
14
|
var NamespaceIsolation = {
|
|
63
15
|
SHARED: "shared",
|
|
@@ -230,6 +182,15 @@ var MemoryClient = class {
|
|
|
230
182
|
encodePathSegment(segment) {
|
|
231
183
|
return encodeURIComponent(segment);
|
|
232
184
|
}
|
|
185
|
+
authorityHeaders(options) {
|
|
186
|
+
if (!options) return {};
|
|
187
|
+
const key = options.idempotencyKey;
|
|
188
|
+
if (key === void 0) return {};
|
|
189
|
+
if (typeof key !== "string" || key.length === 0 || !key.isWellFormed() || new TextEncoder().encode(key).byteLength > 256) {
|
|
190
|
+
throw new Error("idempotencyKey must be 1-256 well-formed UTF-8 bytes");
|
|
191
|
+
}
|
|
192
|
+
return { "Idempotency-Key": key };
|
|
193
|
+
}
|
|
233
194
|
async initialize() {
|
|
234
195
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
235
196
|
headers: this._authHeaders
|
|
@@ -259,10 +220,11 @@ var MemoryClient = class {
|
|
|
259
220
|
return this.fetchApi("/api/memory/ingest", {
|
|
260
221
|
method: "POST",
|
|
261
222
|
body: JSON.stringify(payload),
|
|
223
|
+
headers: this.authorityHeaders(options),
|
|
262
224
|
...options?.signal ? { signal: options.signal } : {}
|
|
263
225
|
});
|
|
264
226
|
}
|
|
265
|
-
async search(params) {
|
|
227
|
+
async search(params, authority) {
|
|
266
228
|
if (params.enableMultiHop) {
|
|
267
229
|
throw new Error(
|
|
268
230
|
"enableMultiHop is embedded-only (the HTTP API does not forward it); use the embedded Memory class for iterative multi-hop retrieval"
|
|
@@ -285,20 +247,25 @@ var MemoryClient = class {
|
|
|
285
247
|
if (enumerationConcept) searchParams.set("enumerationConcept", enumerationConcept);
|
|
286
248
|
if (params.abstentionThreshold != null)
|
|
287
249
|
searchParams.set("abstentionThreshold", String(params.abstentionThreshold));
|
|
288
|
-
return this.fetchApi(`/api/memory/search?${searchParams}
|
|
250
|
+
return this.fetchApi(`/api/memory/search?${searchParams}`, {
|
|
251
|
+
headers: this.authorityHeaders(authority)
|
|
252
|
+
});
|
|
289
253
|
}
|
|
290
|
-
async get(id) {
|
|
254
|
+
async get(id, authority) {
|
|
291
255
|
try {
|
|
292
|
-
return await this.fetchApi(`/api/memory/entries/${this.encodePathSegment(id)}
|
|
256
|
+
return await this.fetchApi(`/api/memory/entries/${this.encodePathSegment(id)}`, {
|
|
257
|
+
headers: this.authorityHeaders(authority)
|
|
258
|
+
});
|
|
293
259
|
} catch (error) {
|
|
294
260
|
if (error instanceof MemoryServerError && error.isNotFound) return null;
|
|
295
261
|
throw error;
|
|
296
262
|
}
|
|
297
263
|
}
|
|
298
|
-
async delete(id) {
|
|
264
|
+
async delete(id, authority) {
|
|
299
265
|
try {
|
|
300
266
|
await this.fetchApi(`/api/memory/entries/${this.encodePathSegment(id)}`, {
|
|
301
|
-
method: "DELETE"
|
|
267
|
+
method: "DELETE",
|
|
268
|
+
headers: this.authorityHeaders(authority)
|
|
302
269
|
});
|
|
303
270
|
return true;
|
|
304
271
|
} catch (error) {
|
|
@@ -334,7 +301,7 @@ var MemoryClient = class {
|
|
|
334
301
|
}
|
|
335
302
|
async shutdown() {
|
|
336
303
|
}
|
|
337
|
-
async list(params = {}) {
|
|
304
|
+
async list(params = {}, authority) {
|
|
338
305
|
const searchParams = new URLSearchParams();
|
|
339
306
|
if (params.page != null) searchParams.set("page", String(params.page));
|
|
340
307
|
if (params.limit != null) searchParams.set("limit", String(params.limit));
|
|
@@ -344,7 +311,9 @@ var MemoryClient = class {
|
|
|
344
311
|
searchParams.set("includeKinds", params.includeKinds.join(","));
|
|
345
312
|
}
|
|
346
313
|
const qs = searchParams.toString();
|
|
347
|
-
return this.fetchApi(`/api/memory/entries${qs ? `?${qs}` : ""}
|
|
314
|
+
return this.fetchApi(`/api/memory/entries${qs ? `?${qs}` : ""}`, {
|
|
315
|
+
headers: this.authorityHeaders(authority)
|
|
316
|
+
});
|
|
348
317
|
}
|
|
349
318
|
// --- File ingest ---
|
|
350
319
|
/**
|
|
@@ -805,7 +774,7 @@ var MemoryClient = class {
|
|
|
805
774
|
);
|
|
806
775
|
return result.entries;
|
|
807
776
|
}
|
|
808
|
-
async lineage(params) {
|
|
777
|
+
async lineage(params, authority) {
|
|
809
778
|
const searchParams = new URLSearchParams();
|
|
810
779
|
if (params.subject) searchParams.set("subject", params.subject);
|
|
811
780
|
if (params.relation) searchParams.set("relation", params.relation);
|
|
@@ -818,12 +787,15 @@ var MemoryClient = class {
|
|
|
818
787
|
searchParams.set("eventTimeEnd", params.eventTimeRange[1]);
|
|
819
788
|
}
|
|
820
789
|
const qs = searchParams.toString();
|
|
821
|
-
return this.fetchApi(`/api/memory/lineage${qs ? `?${qs}` : ""}
|
|
790
|
+
return this.fetchApi(`/api/memory/lineage${qs ? `?${qs}` : ""}`, {
|
|
791
|
+
headers: this.authorityHeaders(authority)
|
|
792
|
+
});
|
|
822
793
|
}
|
|
823
|
-
async reinforce(params) {
|
|
794
|
+
async reinforce(params, authority) {
|
|
824
795
|
return this.fetchApi("/api/memory/reinforce", {
|
|
825
796
|
method: "POST",
|
|
826
|
-
body: JSON.stringify(params)
|
|
797
|
+
body: JSON.stringify(params),
|
|
798
|
+
headers: this.authorityHeaders(authority)
|
|
827
799
|
});
|
|
828
800
|
}
|
|
829
801
|
async log(filters = {}) {
|
|
@@ -915,6 +887,18 @@ var MemoryClient = class {
|
|
|
915
887
|
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
916
888
|
return this.fetchApi(`/api/memory/corrections?${params}`);
|
|
917
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* H26 B-d — deterministic prospective due-scan: entries with
|
|
892
|
+
* `eventTime ∈ [from, from+windowDays]` (inclusive), eventTime ascending,
|
|
893
|
+
* no relevance ranking. `GET /api/memory/due`.
|
|
894
|
+
*/
|
|
895
|
+
async dueScan(input) {
|
|
896
|
+
const params = new URLSearchParams({ windowDays: String(input.windowDays) });
|
|
897
|
+
if (input.from) params.set("from", input.from);
|
|
898
|
+
if (input.namespaceId) params.set("namespaceId", input.namespaceId);
|
|
899
|
+
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
900
|
+
return this.fetchApi(`/api/memory/due?${params}`);
|
|
901
|
+
}
|
|
918
902
|
async fetchApi(path, options) {
|
|
919
903
|
const signal = options?.signal ?? AbortSignal.timeout(this._requestTimeoutMs);
|
|
920
904
|
let res;
|
|
@@ -975,9 +959,6 @@ var MemoryClient = class {
|
|
|
975
959
|
export {
|
|
976
960
|
DEFAULTS,
|
|
977
961
|
TAXONOMY_MAX_CATEGORIES,
|
|
978
|
-
normalizeGraphLabel,
|
|
979
|
-
normalizeNameKey,
|
|
980
|
-
mergeExtractedEntities,
|
|
981
962
|
NamespaceIsolation,
|
|
982
963
|
MemoryType,
|
|
983
964
|
SensitivityLevel,
|
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PERSISTENT_MEMORY_SECTION,
|
|
4
4
|
buildDesignGuide
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-3BTAAGMM.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli/exit-codes.ts
|
|
8
8
|
var EXIT = {
|
|
@@ -551,6 +551,7 @@ function createReadCredentials(providerFactory) {
|
|
|
551
551
|
}
|
|
552
552
|
|
|
553
553
|
// src/mcp/proxy-server.ts
|
|
554
|
+
import { createHash, randomUUID } from "crypto";
|
|
554
555
|
import { readFile, stat } from "fs/promises";
|
|
555
556
|
import { basename, isAbsolute, resolve } from "path";
|
|
556
557
|
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -566,6 +567,12 @@ import {
|
|
|
566
567
|
|
|
567
568
|
// src/mcp/http-client.ts
|
|
568
569
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
570
|
+
function validateIdempotencyKey(key) {
|
|
571
|
+
if (key.length === 0 || !key.isWellFormed() || new TextEncoder().encode(key).byteLength > 256) {
|
|
572
|
+
throw new Error("idempotencyKey must be 1-256 well-formed UTF-8 bytes");
|
|
573
|
+
}
|
|
574
|
+
return key;
|
|
575
|
+
}
|
|
569
576
|
function createHttpClient(credentials, fetchImpl = fetch) {
|
|
570
577
|
const baseUrl = credentials.endpoint.replace(/\/+$/, "");
|
|
571
578
|
function buildUrl(path, query) {
|
|
@@ -578,7 +585,7 @@ function createHttpClient(credentials, fetchImpl = fetch) {
|
|
|
578
585
|
}
|
|
579
586
|
return url.toString();
|
|
580
587
|
}
|
|
581
|
-
function buildHeaders(scope, contentType) {
|
|
588
|
+
function buildHeaders(scope, contentType, idempotencyKey) {
|
|
582
589
|
const headers = new Headers();
|
|
583
590
|
headers.set("Authorization", `Bearer ${credentials.apiKey}`);
|
|
584
591
|
headers.set("Accept", "application/json");
|
|
@@ -587,6 +594,9 @@ function createHttpClient(credentials, fetchImpl = fetch) {
|
|
|
587
594
|
if (scope?.userId) headers.set("X-User-Id", scope.userId);
|
|
588
595
|
if (scope?.teamId) headers.set("X-Team-Id", scope.teamId);
|
|
589
596
|
if (scope?.callerAccessLevel) headers.set("X-Caller-Access-Level", scope.callerAccessLevel);
|
|
597
|
+
if (idempotencyKey !== void 0) {
|
|
598
|
+
headers.set("Idempotency-Key", validateIdempotencyKey(idempotencyKey));
|
|
599
|
+
}
|
|
590
600
|
return headers;
|
|
591
601
|
}
|
|
592
602
|
async function send(url, method, headers, body) {
|
|
@@ -613,12 +623,16 @@ function createHttpClient(credentials, fetchImpl = fetch) {
|
|
|
613
623
|
async requestJson(input) {
|
|
614
624
|
const url = buildUrl(input.path, input.query);
|
|
615
625
|
const hasBody = input.body !== void 0;
|
|
616
|
-
const headers = buildHeaders(
|
|
626
|
+
const headers = buildHeaders(
|
|
627
|
+
input.scope,
|
|
628
|
+
hasBody ? "application/json" : void 0,
|
|
629
|
+
input.idempotencyKey
|
|
630
|
+
);
|
|
617
631
|
return send(url, input.method, headers, hasBody ? JSON.stringify(input.body) : void 0);
|
|
618
632
|
},
|
|
619
633
|
async requestMultipart(input) {
|
|
620
634
|
const url = buildUrl(input.path);
|
|
621
|
-
const headers = buildHeaders(input.scope);
|
|
635
|
+
const headers = buildHeaders(input.scope, void 0, input.idempotencyKey);
|
|
622
636
|
const form = await input.formData();
|
|
623
637
|
return send(url, "POST", headers, form);
|
|
624
638
|
}
|
|
@@ -673,6 +687,23 @@ var LOCAL_UPLOAD_META_KEY = "pyx.dev/local-upload";
|
|
|
673
687
|
|
|
674
688
|
// src/mcp/proxy-server.ts
|
|
675
689
|
var REMOTE_MCP_PATH = "/mcp";
|
|
690
|
+
function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID()) {
|
|
691
|
+
const wrapped = async (input, init) => {
|
|
692
|
+
const upstream = input instanceof Request ? input : new Request(String(input), init);
|
|
693
|
+
const method = init?.method ?? upstream.method;
|
|
694
|
+
const body = init?.body ?? (input instanceof Request ? await input.clone().text() : void 0);
|
|
695
|
+
const headers = new Headers(init?.headers ?? upstream.headers);
|
|
696
|
+
if (method.toUpperCase() === "POST" && !headers.has("Idempotency-Key")) {
|
|
697
|
+
if (typeof body !== "string") {
|
|
698
|
+
throw new Error("remote MCP POST body must be text for stable request idempotency");
|
|
699
|
+
}
|
|
700
|
+
const key = createHash("sha256").update("pyx-mcp-idempotency-v1\0").update(sessionNonce).update("\0").update(body).digest("hex");
|
|
701
|
+
headers.set("Idempotency-Key", `mcp-${key}`);
|
|
702
|
+
}
|
|
703
|
+
return fetchImpl(input, { ...init, method, headers, body });
|
|
704
|
+
};
|
|
705
|
+
return Object.assign(wrapped, { preconnect: fetch.preconnect });
|
|
706
|
+
}
|
|
676
707
|
function extractLocalUpload(meta) {
|
|
677
708
|
if (meta === null || typeof meta !== "object") return void 0;
|
|
678
709
|
const descriptor = meta[LOCAL_UPLOAD_META_KEY];
|
|
@@ -798,7 +829,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
798
829
|
return server;
|
|
799
830
|
}
|
|
800
831
|
async function runMcpProxyServer(opts) {
|
|
801
|
-
const version = opts.version ?? (true ? "1.
|
|
832
|
+
const version = opts.version ?? (true ? "1.15.0" : "0.0.0-dev");
|
|
802
833
|
const read = await opts.readCredentials();
|
|
803
834
|
if (!read.ok) {
|
|
804
835
|
const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
|
|
@@ -808,7 +839,8 @@ async function runMcpProxyServer(opts) {
|
|
|
808
839
|
const url = new URL(`${endpoint.replace(/\/+$/, "")}${REMOTE_MCP_PATH}`);
|
|
809
840
|
const client = new McpClient({ name: "pyx-mem-proxy", version }, { capabilities: {} });
|
|
810
841
|
const clientTransport = new StreamableHTTPClientTransport(url, {
|
|
811
|
-
requestInit: { headers: { Authorization: `Bearer ${apiKey}` } }
|
|
842
|
+
requestInit: { headers: { Authorization: `Bearer ${apiKey}` } },
|
|
843
|
+
fetch: createMcpIdempotentFetch()
|
|
812
844
|
});
|
|
813
845
|
try {
|
|
814
846
|
await client.connect(clientTransport);
|
package/dist/dashboard.mjs
CHANGED
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-3FRA37TI.mjs";
|
|
15
|
+
import "./chunk-IEBF7BQF.mjs";
|
|
16
|
+
import "./chunk-A3L46P2G.mjs";
|
|
16
17
|
export {
|
|
17
18
|
DashboardClient,
|
|
18
19
|
Poller,
|