@pyxmate/memory 1.16.0 → 1.16.2

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.
@@ -168,6 +168,7 @@ function getDefaultKeychain() {
168
168
  }
169
169
 
170
170
  // src/cli/probe.ts
171
+ import { randomUUID } from "crypto";
171
172
  var PROBE_PATH = "/api/memory/entries?limit=1";
172
173
  var PROBE_TIMEOUT_MS = 8e3;
173
174
  async function probeCredentials(args) {
@@ -175,10 +176,15 @@ async function probeCredentials(args) {
175
176
  const url = `${args.endpoint}${PROBE_PATH}`;
176
177
  const ctl = new AbortController();
177
178
  const timer = setTimeout(() => ctl.abort(), PROBE_TIMEOUT_MS);
179
+ const idempotencyKey = `cli-probe-${randomUUID()}`;
178
180
  try {
179
181
  const res = await fetcher(url, {
180
182
  method: "GET",
181
- headers: { Authorization: `Bearer ${args.apiKey}`, Accept: "application/json" },
183
+ headers: {
184
+ Authorization: `Bearer ${args.apiKey}`,
185
+ Accept: "application/json",
186
+ "Idempotency-Key": idempotencyKey
187
+ },
182
188
  signal: ctl.signal
183
189
  });
184
190
  if (res.status === 401 || res.status === 403) return { kind: "invalid", status: res.status };
@@ -551,7 +557,7 @@ function createReadCredentials(providerFactory) {
551
557
  }
552
558
 
553
559
  // src/mcp/proxy-server.ts
554
- import { createHash, randomUUID } from "crypto";
560
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
555
561
  import { readFile, stat } from "fs/promises";
556
562
  import { basename, isAbsolute, resolve } from "path";
557
563
  import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
@@ -687,7 +693,7 @@ var LOCAL_UPLOAD_META_KEY = "pyx.dev/local-upload";
687
693
 
688
694
  // src/mcp/proxy-server.ts
689
695
  var REMOTE_MCP_PATH = "/mcp";
690
- function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID()) {
696
+ function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID2()) {
691
697
  const wrapped = async (input, init) => {
692
698
  const upstream = input instanceof Request ? input : new Request(String(input), init);
693
699
  const method = init?.method ?? upstream.method;
@@ -829,7 +835,7 @@ function createProxyServer(client, version, uploadLocalFile) {
829
835
  return server;
830
836
  }
831
837
  async function runMcpProxyServer(opts) {
832
- const version = opts.version ?? (true ? "1.16.0" : "0.0.0-dev");
838
+ const version = opts.version ?? (true ? "1.16.2" : "0.0.0-dev");
833
839
  const read = await opts.readCredentials();
834
840
  if (!read.ok) {
835
841
  const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
@@ -19,6 +19,7 @@ var DATA_PLANE_COMPILER_LIMITS = {
19
19
  profileContentBytes: 8192
20
20
  };
21
21
  var MEMORY_TYPES = ["short-term", "long-term", "working", "episodic", "summary"];
22
+ var ENTRY_STATUSES = ["active", "superseded", "archived"];
22
23
  var SENSITIVITY_LEVELS = ["public", "internal", "secret"];
23
24
  var PRINCIPAL_KINDS = ["api_key", "user"];
24
25
  var TENANT_MODES = ["single", "multi"];
@@ -300,6 +301,34 @@ function normalizeEntitySynthesisGet(input, mcp) {
300
301
  requireEnum(value.entityType, ENTITY_TYPES, "input.entityType");
301
302
  return { name };
302
303
  }
304
+ var LIST_CURSOR_MAX_TOKEN_CHARS = 4096;
305
+ var BASE64URL_TOKEN_RE = /^[A-Za-z0-9_-]+$/;
306
+ var ISO_DATE_TIME_PREFIX_RE = /^\d{4}-\d{2}-\d{2}T/;
307
+ function parseListCursorToken(token) {
308
+ if (token.length === 0 || token.length > LIST_CURSOR_MAX_TOKEN_CHARS) {
309
+ throw new Error("cursor token is empty or exceeds the size bound");
310
+ }
311
+ if (!BASE64URL_TOKEN_RE.test(token)) {
312
+ throw new Error("cursor token contains non-base64url characters");
313
+ }
314
+ let decoded;
315
+ try {
316
+ decoded = JSON.parse(Buffer.from(token, "base64url").toString("utf8"));
317
+ } catch {
318
+ throw new Error("cursor token is not base64url-encoded JSON");
319
+ }
320
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
321
+ throw new Error("cursor token payload must be an object");
322
+ }
323
+ const { c, i } = decoded;
324
+ if (typeof c !== "string" || !ISO_DATE_TIME_PREFIX_RE.test(c) || Number.isNaN(Date.parse(c))) {
325
+ throw new Error("cursor token field `c` must be an ISO-8601 timestamp");
326
+ }
327
+ if (typeof i !== "string" || i.length === 0) {
328
+ throw new Error("cursor token field `i` must be a non-empty entry id");
329
+ }
330
+ return { createdAt: c, id: i };
331
+ }
303
332
  function normalizeRestList(input) {
304
333
  const value = requireObject(input, "input");
305
334
  requireExactKeys(value, ["limit", "offset", "type"], "input");
@@ -335,21 +364,42 @@ function normalizeRestGet(input) {
335
364
  }
336
365
  return normalized;
337
366
  }
367
+ function normalizeMcpListEntries(value, normalized) {
368
+ if (value.since !== void 0) requireString(value.since, "input.since", { nonempty: false });
369
+ withOptional(normalized, "status", optionalEnum(value.status, ENTRY_STATUSES, "input.status"));
370
+ if (value.cursor !== void 0) {
371
+ if (value.page !== void 0) {
372
+ throw new Error("input.cursor and input.page are mutually exclusive");
373
+ }
374
+ if (typeof value.cursor !== "string") throw new Error("input.cursor must be a string");
375
+ parseListCursorToken(value.cursor);
376
+ normalized.cursor = value.cursor;
377
+ } else {
378
+ normalized.page = optionalInteger(value.page, "input.page", 1, Number.MAX_SAFE_INTEGER) ?? 1;
379
+ }
380
+ normalized.limit = optionalClampedPositiveInteger(value.limit, "input.limit", 100) ?? 20;
381
+ withOptional(
382
+ normalized,
383
+ "agentId",
384
+ normalizeAgentId(value.agentId, "input.agentId", null, false)
385
+ );
386
+ }
338
387
  function normalizeMcpList(input, defaultAgentId) {
339
388
  const value = requireObject(input, "input");
340
- requireExactKeys(value, ["mode", "page", "limit", "type", "agentId", "since"], "input");
389
+ requireExactKeys(
390
+ value,
391
+ ["mode", "page", "limit", "cursor", "status", "type", "agentId", "since"],
392
+ "input"
393
+ );
341
394
  const mode = value.mode === void 0 ? "entries" : requireEnum(value.mode, ["entries", "log"], "input.mode");
395
+ if (mode === "log") {
396
+ if (value.cursor !== void 0) throw new Error("input.cursor is only valid in entries mode");
397
+ if (value.status !== void 0) throw new Error("input.status is only valid in entries mode");
398
+ }
342
399
  const normalized = { mode };
343
400
  withOptional(normalized, "type", optionalEnum(value.type, MEMORY_TYPES, "input.type"));
344
401
  if (mode === "entries") {
345
- if (value.since !== void 0) requireString(value.since, "input.since", { nonempty: false });
346
- normalized.page = optionalInteger(value.page, "input.page", 1, Number.MAX_SAFE_INTEGER) ?? 1;
347
- normalized.limit = optionalClampedPositiveInteger(value.limit, "input.limit", 100) ?? 20;
348
- withOptional(
349
- normalized,
350
- "agentId",
351
- normalizeAgentId(value.agentId, "input.agentId", null, false)
352
- );
402
+ normalizeMcpListEntries(value, normalized);
353
403
  } else {
354
404
  if (value.page !== void 0)
355
405
  requireInteger(value.page, "input.page", 1, Number.MAX_SAFE_INTEGER);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyxmate/memory",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
4
4
  "type": "module",
5
5
  "description": "SDK for pyx-memory — Memory as a Service for AI agents",
6
6
  "license": "MIT",