@neo4j-labs/agent-memory 0.3.0 → 0.4.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/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { extractRequestId, supportsUserAgentHeader, defaultUserAgent, BridgeTransport } from './chunk-TGBKROHO.js';
2
- export { VERSION } from './chunk-TGBKROHO.js';
3
- import { ConnectionError, AuthenticationError, NotSupportedError, TransportError, ValidationError } from './chunk-ASQMU7YC.js';
1
+ import { extractRequestId, supportsUserAgentHeader, defaultUserAgent, BridgeTransport } from './chunk-E752ALCR.js';
2
+ export { VERSION } from './chunk-E752ALCR.js';
3
+ import { ValidationError, NotSupportedError, ConnectionError, AuthenticationError, TransportError } from './chunk-ASQMU7YC.js';
4
4
  export { AuthenticationError, ConnectionError, MemoryError, NotFoundError, NotSupportedError, TransportError, ValidationError } from './chunk-ASQMU7YC.js';
5
5
 
6
6
  // src/auth/index.ts
@@ -165,6 +165,59 @@ var LongTermMemory = class {
165
165
  });
166
166
  return wire.map(toEntity);
167
167
  }
168
+ /**
169
+ * Poll entity search until async extraction has caught up, or time out.
170
+ *
171
+ * NAMS extracts entities in a background pipeline, so writes return before
172
+ * the entities are searchable. This helper lets application and test code
173
+ * await consistency explicitly instead of racing a fixed delay.
174
+ *
175
+ * Provide one of:
176
+ * - `predicate` — called with the current results; return true when satisfied.
177
+ * - `expectedNames` — succeed once every name appears (case-insensitive).
178
+ * **Recommended**: NAMS entity search is vector/nearest-neighbor, so a
179
+ * `minResults` threshold is satisfied almost immediately on a non-empty
180
+ * workspace; prefer `expectedNames`/`predicate` to confirm a *specific*
181
+ * extraction landed.
182
+ * - otherwise — succeed once at least `minResults` entities match.
183
+ *
184
+ * Returns `true` if satisfied within `timeoutMs`, `false` otherwise (it does
185
+ * not throw, so callers can branch or skip gracefully).
186
+ */
187
+ async waitForExtraction(options) {
188
+ const {
189
+ query,
190
+ expectedNames,
191
+ minResults = 1,
192
+ predicate,
193
+ timeoutMs = 3e4,
194
+ intervalMs = 1e3
195
+ } = options;
196
+ const q = query ?? (expectedNames && expectedNames.length > 0 ? expectedNames[0] : void 0);
197
+ if (q === void 0 && predicate === void 0) {
198
+ throw new ValidationError(
199
+ "waitForExtraction requires one of: query, expectedNames, or predicate."
200
+ );
201
+ }
202
+ const want = (expectedNames ?? []).map((n) => n.toLowerCase());
203
+ const fetch2 = Math.max(minResults, want.length, options.limit ?? 10);
204
+ const deadline = Date.now() + timeoutMs;
205
+ for (; ; ) {
206
+ const results = await this.searchEntities(q ?? "", { limit: fetch2 });
207
+ let ok;
208
+ if (predicate !== void 0) {
209
+ ok = predicate(results);
210
+ } else if (want.length > 0) {
211
+ const found = new Set(results.map((e) => e.name.toLowerCase()));
212
+ ok = want.every((n) => found.has(n));
213
+ } else {
214
+ ok = results.length >= minResults;
215
+ }
216
+ if (ok) return true;
217
+ if (Date.now() >= deadline) return false;
218
+ await new Promise((r) => setTimeout(r, intervalMs));
219
+ }
220
+ }
168
221
  async searchPreferences(query, options) {
169
222
  const wire = await this.transport.request("search_preferences", {
170
223
  query,
@@ -286,6 +339,172 @@ var LongTermMemory = class {
286
339
  }
287
340
  };
288
341
 
342
+ // src/ontology/index.ts
343
+ function toDocument(raw) {
344
+ if (!raw || typeof raw !== "object" || !raw.domain) return void 0;
345
+ return {
346
+ domain: raw.domain,
347
+ entityTypes: (raw.entity_types ?? []).map((e) => ({
348
+ label: e.label,
349
+ poleType: e.pole_type,
350
+ subtype: e.subtype,
351
+ color: e.color,
352
+ icon: e.icon,
353
+ properties: (e.properties ?? []).map((p) => ({
354
+ name: p.name,
355
+ type: p.type,
356
+ required: p.required,
357
+ unique: p.unique,
358
+ enum: p.enum ?? void 0
359
+ }))
360
+ })),
361
+ relationships: raw.relationships ?? []
362
+ };
363
+ }
364
+ function toDocumentFromJson(schemaJson) {
365
+ if (schemaJson == null) return void 0;
366
+ try {
367
+ return toDocument(JSON.parse(schemaJson));
368
+ } catch {
369
+ return void 0;
370
+ }
371
+ }
372
+ function toVersion(raw) {
373
+ return {
374
+ id: raw.id,
375
+ ontologyId: raw.ontology_id,
376
+ revision: raw.revision,
377
+ validationMode: raw.validation_mode,
378
+ document: toDocumentFromJson(raw.schema_json),
379
+ schemaHash: raw.schema_hash,
380
+ createdAt: raw.created_at,
381
+ message: raw.message
382
+ };
383
+ }
384
+ function toSummary(raw) {
385
+ return {
386
+ id: raw.id,
387
+ name: raw.name,
388
+ displayName: raw.display_name,
389
+ description: raw.description,
390
+ emoji: raw.emoji,
391
+ tagline: raw.tagline,
392
+ isSystem: raw.is_system ?? false,
393
+ currentRevision: raw.current_revision,
394
+ isActive: raw.is_active ?? false
395
+ };
396
+ }
397
+ function docToWire(doc) {
398
+ return {
399
+ domain: doc.domain,
400
+ entity_types: doc.entityTypes.map((e) => ({
401
+ label: e.label,
402
+ pole_type: e.poleType,
403
+ subtype: e.subtype,
404
+ color: e.color,
405
+ icon: e.icon,
406
+ properties: (e.properties ?? []).map((p) => ({
407
+ name: p.name,
408
+ type: p.type,
409
+ required: p.required,
410
+ unique: p.unique,
411
+ enum: p.enum
412
+ }))
413
+ })),
414
+ relationships: doc.relationships
415
+ };
416
+ }
417
+ var OntologyClient = class {
418
+ constructor(transport) {
419
+ this.transport = transport;
420
+ }
421
+ async list() {
422
+ const raw = await this.transport.request(
423
+ "list_ontologies",
424
+ {}
425
+ );
426
+ return (raw.ontologies ?? []).map(toSummary);
427
+ }
428
+ async get(id) {
429
+ const raw = await this.transport.request(
430
+ "get_ontology",
431
+ { id }
432
+ );
433
+ const r = raw.record ?? {};
434
+ return {
435
+ record: {
436
+ id: r.id,
437
+ name: r.name,
438
+ description: r.description,
439
+ workspaceId: r.workspace_id,
440
+ isSystem: r.is_system ?? false,
441
+ createdAt: r.created_at
442
+ },
443
+ versions: (raw.versions ?? []).map(toVersion)
444
+ };
445
+ }
446
+ async getActive() {
447
+ const raw = await this.transport.request(
448
+ "get_active_ontology",
449
+ {}
450
+ );
451
+ const document = toDocument(raw.ontology);
452
+ if (!document) {
453
+ throw new NotSupportedError("No active ontology bound for this workspace.");
454
+ }
455
+ const active = { document };
456
+ const summaries = await this.list();
457
+ const match = summaries.find((s) => s.isActive) ?? summaries.find((s) => s.name === document.domain.id);
458
+ if (match) {
459
+ active.ontologyId = match.id;
460
+ const detail = await this.get(match.id);
461
+ const current = currentVersion(detail, match.currentRevision);
462
+ if (current) {
463
+ active.validationMode = current.validationMode;
464
+ active.revision = current.revision;
465
+ active.versionId = current.id;
466
+ }
467
+ }
468
+ return active;
469
+ }
470
+ async clone(templateName) {
471
+ const raw = await this.transport.request("clone_ontology", { name: templateName });
472
+ return toVersion(raw);
473
+ }
474
+ async create(options) {
475
+ const body = { ontology: docToWire(options.schema) };
476
+ if (options.validationMode !== void 0) body.validation_mode = options.validationMode;
477
+ const raw = await this.transport.request("create_ontology", { body });
478
+ return toVersion(raw);
479
+ }
480
+ async update(options) {
481
+ const body = { ontology: docToWire(options.schema) };
482
+ if (options.validationMode !== void 0) body.validation_mode = options.validationMode;
483
+ const raw = await this.transport.request("update_ontology", {
484
+ id: options.id,
485
+ body
486
+ });
487
+ return toVersion(raw);
488
+ }
489
+ async activate(versionId) {
490
+ const raw = await this.transport.request("activate_ontology", {
491
+ body: { version_id: versionId }
492
+ });
493
+ return toVersion(raw);
494
+ }
495
+ async delete(id) {
496
+ await this.transport.request("delete_ontology", { id });
497
+ }
498
+ };
499
+ function currentVersion(ontology, revision) {
500
+ if (ontology.versions.length === 0) return void 0;
501
+ if (revision !== void 0) {
502
+ const exact = ontology.versions.find((v) => v.revision === revision);
503
+ if (exact) return exact;
504
+ }
505
+ return ontology.versions.reduce((a, b) => b.revision > a.revision ? b : a);
506
+ }
507
+
289
508
  // src/query/index.ts
290
509
  var QueryConsole = class {
291
510
  constructor(transport) {
@@ -509,6 +728,15 @@ var ReasoningMemory = class {
509
728
  };
510
729
 
511
730
  // src/short-term/index.ts
731
+ function resolveConversationId(sessionId, conversationId, method) {
732
+ const resolved = sessionId ?? conversationId;
733
+ if (resolved === void 0) {
734
+ throw new ValidationError(
735
+ `${method} requires sessionId (alias: conversationId) \u2014 the NAMS API is conversation-scoped.`
736
+ );
737
+ }
738
+ return resolved;
739
+ }
512
740
  function toMessage(w) {
513
741
  return {
514
742
  id: w.id,
@@ -566,8 +794,9 @@ var ShortTermMemory = class {
566
794
  }
567
795
  // ---- Bronze tier (bridge) ----------------------------------------------
568
796
  async addMessage(sessionId, role, content, options) {
797
+ const conv = resolveConversationId(sessionId, options?.conversationId, "addMessage");
569
798
  const wire = await this.transport.request("add_message", {
570
- session_id: sessionId,
799
+ session_id: conv,
571
800
  role,
572
801
  content,
573
802
  metadata: options?.metadata
@@ -575,16 +804,18 @@ var ShortTermMemory = class {
575
804
  return toMessage(wire);
576
805
  }
577
806
  async getConversation(sessionId, options) {
807
+ const conv = resolveConversationId(sessionId, options?.conversationId, "getConversation");
578
808
  const wire = await this.transport.request("get_conversation", {
579
- session_id: sessionId,
809
+ session_id: conv,
580
810
  limit: options?.limit
581
811
  });
582
812
  return toConversation(wire);
583
813
  }
584
814
  async searchMessages(query, options) {
815
+ const conv = options?.sessionId ?? options?.conversationId;
585
816
  const wire = await this.transport.request("search_messages", {
586
817
  query,
587
- session_id: options?.sessionId,
818
+ session_id: conv,
588
819
  limit: options?.limit ?? 10,
589
820
  threshold: options?.threshold ?? 0.7
590
821
  });
@@ -602,8 +833,9 @@ var ShortTermMemory = class {
602
833
  });
603
834
  return result.deleted;
604
835
  }
605
- async clearSession(sessionId) {
606
- await this.transport.request("clear_session", { session_id: sessionId });
836
+ async clearSession(sessionId, options) {
837
+ const conv = resolveConversationId(sessionId, options?.conversationId, "clearSession");
838
+ await this.transport.request("clear_session", { session_id: conv });
607
839
  }
608
840
  // ---- Volume 5 / hosted-native methods -----------------------------------
609
841
  /** Create a new conversation (hosted service). */
@@ -969,7 +1201,32 @@ var ROUTES = {
969
1201
  method: "POST",
970
1202
  path: "/auth/refresh",
971
1203
  hasBody: true
972
- }
1204
+ },
1205
+ // Ontologies — snake_case sub-API (verified against staging; absent from the
1206
+ // OpenAPI spec). Bodies are sent verbatim via `snakeBody`.
1207
+ list_ontologies: { method: "GET", path: "/ontologies" },
1208
+ get_ontology: { method: "GET", path: "/ontologies/{id}", pathParams: ["id"] },
1209
+ get_active_ontology: { method: "GET", path: "/ontologies/active" },
1210
+ clone_ontology: {
1211
+ method: "POST",
1212
+ path: "/ontologies/{name}/clone",
1213
+ pathParams: ["name"]
1214
+ },
1215
+ create_ontology: { method: "POST", path: "/ontologies", hasBody: true, snakeBody: true },
1216
+ update_ontology: {
1217
+ method: "PUT",
1218
+ path: "/ontologies/{id}",
1219
+ pathParams: ["id"],
1220
+ hasBody: true,
1221
+ snakeBody: true
1222
+ },
1223
+ activate_ontology: {
1224
+ method: "POST",
1225
+ path: "/ontologies/active",
1226
+ hasBody: true,
1227
+ snakeBody: true
1228
+ },
1229
+ delete_ontology: { method: "DELETE", path: "/ontologies/{id}", pathParams: ["id"] }
973
1230
  };
974
1231
  var RestTransport = class {
975
1232
  endpoint;
@@ -1107,13 +1364,17 @@ var RestTransport = class {
1107
1364
  const query = queryEntries.length ? "?" + queryEntries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&") : "";
1108
1365
  let body;
1109
1366
  if (route.hasBody) {
1110
- const bodyObj = {};
1111
- for (const [k, v] of Object.entries(camelParams)) {
1112
- if (!consumed.has(k) && v !== void 0 && v !== null) {
1113
- bodyObj[k] = v;
1367
+ if (route.snakeBody) {
1368
+ body = JSON.stringify(original.body ?? {});
1369
+ } else {
1370
+ const bodyObj = {};
1371
+ for (const [k, v] of Object.entries(camelParams)) {
1372
+ if (!consumed.has(k) && v !== void 0 && v !== null) {
1373
+ bodyObj[k] = v;
1374
+ }
1114
1375
  }
1376
+ body = JSON.stringify(bodyObj);
1115
1377
  }
1116
- body = JSON.stringify(bodyObj);
1117
1378
  }
1118
1379
  const url = `${this.endpoint}${path}${query}`;
1119
1380
  const start = nowMs();
@@ -1229,6 +1490,8 @@ var MemoryClient = class {
1229
1490
  query;
1230
1491
  /** API-key & OAuth management (hosted service only). */
1231
1492
  auth;
1493
+ /** Ontology lifecycle — typed domain schemas (hosted service only). */
1494
+ ontology;
1232
1495
  transport;
1233
1496
  constructor(optionsOrTransport = {}) {
1234
1497
  if (isTransport(optionsOrTransport)) {
@@ -1241,6 +1504,7 @@ var MemoryClient = class {
1241
1504
  this.reasoning = new ReasoningMemory(this.transport);
1242
1505
  this.query = new QueryConsole(this.transport);
1243
1506
  this.auth = new AuthClient(this.transport);
1507
+ this.ontology = new OntologyClient(this.transport);
1244
1508
  }
1245
1509
  async connect() {
1246
1510
  await this.transport.connect();
@@ -1261,9 +1525,22 @@ function resolveApiKey(option) {
1261
1525
  if (typeof process === "undefined" || !process.env) return void 0;
1262
1526
  return process.env.MEMORY_API_KEY;
1263
1527
  }
1528
+ function resolveWorkspaceId(option) {
1529
+ if (option !== void 0) return option;
1530
+ if (typeof process === "undefined" || !process.env) return void 0;
1531
+ return process.env.MEMORY_WORKSPACE_ID;
1532
+ }
1533
+ function withWorkspaceHeader(headers, workspaceId) {
1534
+ if (!workspaceId) return headers;
1535
+ const hasExplicit = headers !== void 0 && Object.keys(headers).some((k) => k.toLowerCase() === "x-workspace-id");
1536
+ if (hasExplicit) return headers;
1537
+ return { ...headers ?? {}, "X-Workspace-Id": workspaceId };
1538
+ }
1264
1539
  function createTransport(options) {
1265
1540
  const endpoint = options.endpoint;
1266
1541
  const apiKey = resolveApiKey(options.apiKey);
1542
+ const workspaceId = resolveWorkspaceId(options.workspaceId);
1543
+ const headers = withWorkspaceHeader(options.headers, workspaceId);
1267
1544
  const choice = pickTransport(endpoint ?? DEFAULT_ENDPOINT, options.transport);
1268
1545
  if (choice === "rest") {
1269
1546
  return new RestTransport({
@@ -1271,7 +1548,7 @@ function createTransport(options) {
1271
1548
  apiKey,
1272
1549
  tokenProvider: options.tokenProvider,
1273
1550
  timeout: options.timeout,
1274
- headers: options.headers,
1551
+ headers,
1275
1552
  logger: options.logger
1276
1553
  });
1277
1554
  }
@@ -1282,7 +1559,7 @@ function createTransport(options) {
1282
1559
  endpoint,
1283
1560
  apiKey,
1284
1561
  timeout: options.timeout,
1285
- headers: options.headers,
1562
+ headers,
1286
1563
  logger: options.logger
1287
1564
  });
1288
1565
  }
@@ -1308,6 +1585,6 @@ var LazyConnectTransport = class {
1308
1585
  }
1309
1586
  };
1310
1587
 
1311
- export { AuthClient, LongTermMemory, MemoryClient, QueryConsole, ReasoningMemory, RestTransport, ShortTermMemory };
1588
+ export { AuthClient, LongTermMemory, MemoryClient, OntologyClient, QueryConsole, ReasoningMemory, RestTransport, ShortTermMemory };
1312
1589
  //# sourceMappingURL=index.js.map
1313
1590
  //# sourceMappingURL=index.js.map