@neo4j-labs/agent-memory 0.3.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 ADDED
@@ -0,0 +1,1313 @@
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';
4
+ export { AuthenticationError, ConnectionError, MemoryError, NotFoundError, NotSupportedError, TransportError, ValidationError } from './chunk-ASQMU7YC.js';
5
+
6
+ // src/auth/index.ts
7
+ function toApiKey(w) {
8
+ return {
9
+ id: w.id,
10
+ label: w.label,
11
+ scopes: w.scopes ?? [],
12
+ workspaceId: w.workspace_id,
13
+ createdAt: w.created_at,
14
+ expiresAt: w.expires_at,
15
+ key: w.key
16
+ };
17
+ }
18
+ var AuthClient = class {
19
+ constructor(transport) {
20
+ this.transport = transport;
21
+ }
22
+ /** List API keys for a workspace (no plaintext). */
23
+ async listApiKeys(workspaceId) {
24
+ const wire = await this.transport.request("list_api_keys", {
25
+ workspace_id: workspaceId
26
+ });
27
+ return (wire ?? []).map(toApiKey);
28
+ }
29
+ /** Create a new API key. The plaintext value is returned only once. */
30
+ async createApiKey(input) {
31
+ const wire = await this.transport.request("create_api_key", {
32
+ label: input.label,
33
+ scopes: input.scopes,
34
+ workspace_id: input.workspaceId
35
+ });
36
+ return toApiKey(wire);
37
+ }
38
+ /** Revoke (delete) an API key by id. */
39
+ async revokeApiKey(keyId) {
40
+ await this.transport.request("revoke_api_key", { key_id: keyId });
41
+ }
42
+ /** Reveal the plaintext value of a stored API key. */
43
+ async revealApiKey(keyId, workspaceId) {
44
+ const wire = await this.transport.request("reveal_api_key", {
45
+ key_id: keyId,
46
+ workspace_id: workspaceId
47
+ });
48
+ return toApiKey(wire);
49
+ }
50
+ /** Exchange a refresh token for a fresh access/refresh pair. */
51
+ async refreshAccessToken(refreshToken) {
52
+ const wire = await this.transport.request("refresh_access_token", {
53
+ refresh_token: refreshToken
54
+ });
55
+ return {
56
+ accessToken: wire.access_token,
57
+ refreshToken: wire.refresh_token,
58
+ expiresIn: wire.expires_in
59
+ };
60
+ }
61
+ };
62
+
63
+ // src/long-term/index.ts
64
+ function toEntity(w) {
65
+ return {
66
+ id: w.id,
67
+ name: w.name,
68
+ type: w.type,
69
+ subtype: w.subtype,
70
+ description: w.description,
71
+ embedding: w.embedding,
72
+ canonicalName: w.canonical_name,
73
+ createdAt: w.created_at ?? "",
74
+ updatedAt: w.updated_at,
75
+ confidence: w.confidence,
76
+ sourceStage: w.source_stage,
77
+ relationships: w.relationships?.map(toRelRef)
78
+ };
79
+ }
80
+ function toRelRef(w) {
81
+ return {
82
+ id: w.id,
83
+ type: w.type,
84
+ targetId: w.target_id,
85
+ targetName: w.target_name,
86
+ properties: w.properties
87
+ };
88
+ }
89
+ function toPreference(w) {
90
+ return {
91
+ id: w.id,
92
+ category: w.category,
93
+ preference: w.preference,
94
+ context: w.context,
95
+ embedding: w.embedding
96
+ };
97
+ }
98
+ function toFact(w) {
99
+ return {
100
+ id: w.id,
101
+ subject: w.subject,
102
+ predicate: w.predicate,
103
+ object: w.object,
104
+ embedding: w.embedding
105
+ };
106
+ }
107
+ function toRelationship(w) {
108
+ return {
109
+ id: w.id,
110
+ sourceId: w.source_id,
111
+ targetId: w.target_id,
112
+ relationshipType: w.relationship_type,
113
+ properties: w.properties ?? {}
114
+ };
115
+ }
116
+ function toMention(w) {
117
+ return {
118
+ conversationId: w.conversation_id,
119
+ messageId: w.message_id,
120
+ content: w.content,
121
+ timestamp: w.timestamp
122
+ };
123
+ }
124
+ function toGraphNode(w) {
125
+ return { id: w.id, name: w.name, type: w.type };
126
+ }
127
+ function toGraphEdge(w) {
128
+ return { id: w.id, source: w.source, target: w.target, type: w.type };
129
+ }
130
+ var LongTermMemory = class {
131
+ constructor(transport) {
132
+ this.transport = transport;
133
+ }
134
+ // ---- Silver tier (bridge) ----------------------------------------------
135
+ async addEntity(name, entityType, options) {
136
+ const wire = await this.transport.request("add_entity", {
137
+ name,
138
+ entity_type: entityType,
139
+ type: entityType,
140
+ description: options?.description
141
+ });
142
+ return toEntity(wire);
143
+ }
144
+ async addPreference(category, preference, options) {
145
+ const wire = await this.transport.request("add_preference", {
146
+ category,
147
+ preference,
148
+ context: options?.context
149
+ });
150
+ return toPreference(wire);
151
+ }
152
+ async addFact(subject, predicate, obj) {
153
+ const wire = await this.transport.request("add_fact", {
154
+ subject,
155
+ predicate,
156
+ obj
157
+ });
158
+ return toFact(wire);
159
+ }
160
+ async searchEntities(query, options) {
161
+ const wire = await this.transport.request("search_entities", {
162
+ query,
163
+ type: options?.type,
164
+ limit: options?.limit ?? 10
165
+ });
166
+ return wire.map(toEntity);
167
+ }
168
+ async searchPreferences(query, options) {
169
+ const wire = await this.transport.request("search_preferences", {
170
+ query,
171
+ category: options?.category,
172
+ limit: options?.limit ?? 10
173
+ });
174
+ return wire.map(toPreference);
175
+ }
176
+ async getEntityByName(name) {
177
+ const wire = await this.transport.request("get_entity_by_name", {
178
+ name
179
+ });
180
+ return wire ? toEntity(wire) : null;
181
+ }
182
+ async getRelatedEntities(entityId, options) {
183
+ const wire = await this.transport.request("get_related_entities", {
184
+ entity_id: entityId,
185
+ relationship_type: options?.relationshipType,
186
+ depth: options?.depth ?? 1
187
+ });
188
+ return wire.map(toEntity);
189
+ }
190
+ async addRelationship(sourceId, targetId, relationshipType, options) {
191
+ const wire = await this.transport.request("add_relationship", {
192
+ source_id: sourceId,
193
+ target_id: targetId,
194
+ relationship_type: relationshipType,
195
+ properties: options?.properties
196
+ });
197
+ return toRelationship(wire);
198
+ }
199
+ async mergeDuplicateEntities(sourceId, targetId, options) {
200
+ const wire = await this.transport.request("merge_duplicate_entities", {
201
+ source_id: sourceId,
202
+ target_id: targetId,
203
+ canonical_name: options?.canonicalName
204
+ });
205
+ return toEntity(wire);
206
+ }
207
+ // ---- Volume 5 / hosted-native methods -----------------------------------
208
+ /** List all entities, optionally filtered by entity type. */
209
+ async listEntities(options) {
210
+ const wire = await this.transport.request("list_entities", {
211
+ type: options?.type,
212
+ limit: options?.limit
213
+ });
214
+ return wire.map(toEntity);
215
+ }
216
+ /** Fetch one entity (with relationships) by id. */
217
+ async getEntity(entityId) {
218
+ const wire = await this.transport.request("get_entity", {
219
+ entity_id: entityId
220
+ });
221
+ return toEntity(wire);
222
+ }
223
+ /** Update an existing entity's name and/or description.
224
+ *
225
+ * The hosted PUT /v1/entities/{id} returns `{status: "updated"}` rather
226
+ * than the full entity, so when the response lacks an `id` we follow up
227
+ * with a GET to keep the SDK contract — "update returns the updated
228
+ * Entity". Bridge transports return the entity directly, so we tolerate
229
+ * both shapes.
230
+ */
231
+ async updateEntity(entityId, options) {
232
+ const wire = await this.transport.request(
233
+ "update_entity",
234
+ {
235
+ entity_id: entityId,
236
+ name: options.name,
237
+ description: options.description
238
+ }
239
+ );
240
+ if (wire && typeof wire === "object" && "id" in wire && wire.id) {
241
+ return toEntity(wire);
242
+ }
243
+ return this.getEntity(entityId);
244
+ }
245
+ /** Delete an entity and its relationships. */
246
+ async deleteEntity(entityId) {
247
+ await this.transport.request("delete_entity", { entity_id: entityId });
248
+ }
249
+ /** Score an entity 0-1 and optionally mark it human-confirmed. */
250
+ async setEntityFeedback(entityId, options) {
251
+ const result = await this.transport.request(
252
+ "set_entity_feedback",
253
+ {
254
+ entity_id: entityId,
255
+ user_score: options.userScore,
256
+ confirmed: options.confirmed
257
+ }
258
+ );
259
+ return { id: result.id, updated: result.updated };
260
+ }
261
+ /** All cross-conversation mentions of this entity. */
262
+ async getEntityHistory(entityId) {
263
+ const wire = await this.transport.request("get_entity_history", {
264
+ entity_id: entityId
265
+ });
266
+ return {
267
+ entityId: wire.entity_id,
268
+ mentions: (wire.mentions ?? []).map(toMention)
269
+ };
270
+ }
271
+ /** Merge `sourceId` into `targetId`, leaving a SAME_AS provenance link. */
272
+ async mergeEntities(sourceId, targetId) {
273
+ const wire = await this.transport.request("merge_entities", {
274
+ source_id: sourceId,
275
+ target_id: targetId
276
+ });
277
+ return { sourceId: wire.source_id, targetId: wire.target_id, status: wire.status };
278
+ }
279
+ /** Full-graph view of all entities + edges. Pair with NVL for visualization. */
280
+ async getEntityGraph() {
281
+ const wire = await this.transport.request("get_entity_graph", {});
282
+ return {
283
+ nodes: (wire.nodes ?? []).map(toGraphNode),
284
+ edges: (wire.edges ?? []).map(toGraphEdge)
285
+ };
286
+ }
287
+ };
288
+
289
+ // src/query/index.ts
290
+ var QueryConsole = class {
291
+ constructor(transport) {
292
+ this.transport = transport;
293
+ }
294
+ /**
295
+ * Execute a read-only Cypher query.
296
+ *
297
+ * @example
298
+ * const r = await client.query.cypher({
299
+ * cypher: "MATCH (e:Entity) RETURN e.name AS name LIMIT $n",
300
+ * params: { n: 10 },
301
+ * });
302
+ */
303
+ async cypher(input) {
304
+ const wire = await this.transport.request("cypher_query", {
305
+ cypher: input.cypher,
306
+ params: input.params ?? {}
307
+ });
308
+ return {
309
+ columns: wire.columns ?? [],
310
+ rows: wire.rows ?? [],
311
+ stats: wire.stats
312
+ };
313
+ }
314
+ };
315
+
316
+ // src/reasoning/index.ts
317
+ function toToolCall(w) {
318
+ return {
319
+ id: w.id,
320
+ toolName: w.tool_name ?? w.toolName ?? "",
321
+ arguments: w.arguments ?? (w.input ? safeParseObject(w.input) : {}) ?? {},
322
+ result: w.result ?? w.output,
323
+ status: w.status ?? "success",
324
+ durationMs: w.duration_ms ?? w.durationMs,
325
+ error: w.error
326
+ };
327
+ }
328
+ function safeParseObject(input) {
329
+ try {
330
+ const parsed = JSON.parse(input);
331
+ return typeof parsed === "object" && parsed !== null ? parsed : { value: parsed };
332
+ } catch {
333
+ return { raw: input };
334
+ }
335
+ }
336
+ function toStep(w) {
337
+ return {
338
+ id: w.id,
339
+ traceId: w.trace_id ?? "",
340
+ stepNumber: w.step_number ?? 0,
341
+ thought: w.thought,
342
+ action: w.action,
343
+ observation: w.observation,
344
+ toolCalls: (w.tool_calls ?? []).map(toToolCall)
345
+ };
346
+ }
347
+ function toTrace(w) {
348
+ return {
349
+ id: w.id,
350
+ sessionId: w.session_id ?? "",
351
+ task: w.task ?? "",
352
+ steps: (w.steps ?? []).map(toStep),
353
+ outcome: w.outcome,
354
+ success: w.success,
355
+ startedAt: w.started_at ?? "",
356
+ completedAt: w.completed_at
357
+ };
358
+ }
359
+ function toToolStats(w) {
360
+ return {
361
+ name: w.name,
362
+ totalCalls: w.total_calls,
363
+ successfulCalls: w.successful_calls,
364
+ failedCalls: w.failed_calls,
365
+ successRate: w.success_rate,
366
+ avgDurationMs: w.avg_duration_ms
367
+ };
368
+ }
369
+ function toAgentStep(w) {
370
+ return {
371
+ id: w.id,
372
+ conversationId: w.conversation_id,
373
+ reasoning: w.reasoning,
374
+ actionTaken: w.action_taken,
375
+ result: w.result,
376
+ createdAt: w.created_at ?? ""
377
+ };
378
+ }
379
+ var ReasoningMemory = class {
380
+ constructor(transport) {
381
+ this.transport = transport;
382
+ }
383
+ // ---- Silver tier (bridge) ----------------------------------------------
384
+ async startTrace(sessionId, task) {
385
+ const wire = await this.transport.request("start_trace", {
386
+ session_id: sessionId,
387
+ task
388
+ });
389
+ return toTrace(wire);
390
+ }
391
+ async addStep(traceId, options) {
392
+ const wire = await this.transport.request("add_step", {
393
+ trace_id: traceId,
394
+ thought: options?.thought,
395
+ action: options?.action,
396
+ observation: options?.observation
397
+ });
398
+ return toStep(wire);
399
+ }
400
+ async recordToolCall(stepId, toolName, args, options) {
401
+ const wire = await this.transport.request("record_tool_call", {
402
+ step_id: stepId,
403
+ tool_name: toolName,
404
+ arguments: args,
405
+ input: typeof args === "string" ? args : JSON.stringify(args),
406
+ result: options?.result,
407
+ output: typeof options?.result === "string" ? options.result : void 0,
408
+ status: options?.status ?? "success",
409
+ duration_ms: options?.durationMs,
410
+ error: options?.error
411
+ });
412
+ return toToolCall(wire);
413
+ }
414
+ async completeTrace(traceId, options) {
415
+ const wire = await this.transport.request("complete_trace", {
416
+ trace_id: traceId,
417
+ outcome: options?.outcome,
418
+ success: options?.success
419
+ });
420
+ return toTrace(wire);
421
+ }
422
+ async getTraceWithSteps(traceId) {
423
+ const wire = await this.transport.request("get_trace_with_steps", {
424
+ trace_id: traceId
425
+ });
426
+ return wire ? toTrace(wire) : null;
427
+ }
428
+ async listTraces(options) {
429
+ const wire = await this.transport.request("list_traces", {
430
+ session_id: options?.sessionId,
431
+ limit: options?.limit ?? 100
432
+ });
433
+ return wire.map(toTrace);
434
+ }
435
+ async getToolStats(toolName) {
436
+ const wire = await this.transport.request("get_tool_stats", {
437
+ tool_name: toolName
438
+ });
439
+ return wire.map(toToolStats);
440
+ }
441
+ async getSimilarTraces(task, options) {
442
+ const wire = await this.transport.request("get_similar_traces", {
443
+ task,
444
+ limit: options?.limit ?? 5,
445
+ success_only: options?.successOnly ?? true
446
+ });
447
+ return wire.map(toTrace);
448
+ }
449
+ // ---- Volume 5 / hosted-native methods -----------------------------------
450
+ /** Record one reasoning step under a conversation (hosted REACT model). */
451
+ async recordStep(input) {
452
+ const wire = await this.transport.request("record_step", {
453
+ conversation_id: input.conversationId,
454
+ reasoning: input.reasoning,
455
+ action_taken: input.actionTaken,
456
+ result: input.result
457
+ });
458
+ return toAgentStep(wire);
459
+ }
460
+ /** List all reasoning steps for one conversation. */
461
+ async listSteps(conversationId) {
462
+ const wire = await this.transport.request("list_steps", {
463
+ conversation_id: conversationId
464
+ });
465
+ return wire.map(toAgentStep);
466
+ }
467
+ /** Detailed step explanation with tool calls and influenced entities. */
468
+ async explainStep(stepId) {
469
+ const wire = await this.transport.request("explain_step", {
470
+ step_id: stepId
471
+ });
472
+ return {
473
+ ...toAgentStep(wire),
474
+ toolCalls: (wire.tool_calls ?? []).map(toToolCall),
475
+ influencedEntities: (wire.influenced_entities ?? []).map((e) => ({
476
+ id: String(e["id"] ?? ""),
477
+ name: String(e["name"] ?? ""),
478
+ type: String(e["type"] ?? ""),
479
+ description: e["description"],
480
+ createdAt: String(e["created_at"] ?? "")
481
+ }))
482
+ };
483
+ }
484
+ /** Full reasoning trace for a conversation (steps + tool calls). */
485
+ async getTraceByConversation(conversationId) {
486
+ const wire = await this.transport.request(
487
+ "get_trace_by_conversation",
488
+ { conversation_id: conversationId }
489
+ );
490
+ return {
491
+ conversationId: wire.conversation_id,
492
+ steps: (wire.steps ?? []).map(toAgentStep),
493
+ toolCalls: (wire.tool_calls ?? []).map(toToolCall)
494
+ };
495
+ }
496
+ /** All reasoning steps that influenced an entity's creation.
497
+ *
498
+ * Hosted REST returns the chain under `provenance`; bridge / older
499
+ * responses use `steps`. Accept either.
500
+ */
501
+ async getEntityProvenance(entityId) {
502
+ const wire = await this.transport.request("get_entity_provenance", { entity_id: entityId });
503
+ const rawSteps = wire.steps ?? wire.provenance ?? [];
504
+ return {
505
+ entityId: wire.entity_id,
506
+ steps: rawSteps.map(toAgentStep)
507
+ };
508
+ }
509
+ };
510
+
511
+ // src/short-term/index.ts
512
+ function toMessage(w) {
513
+ return {
514
+ id: w.id,
515
+ role: w.role ?? "user",
516
+ content: w.content,
517
+ timestamp: w.timestamp ?? w.created_at ?? "",
518
+ embedding: w.embedding,
519
+ metadata: w.metadata ?? {},
520
+ conversationId: w.conversation_id
521
+ };
522
+ }
523
+ function toConversation(w) {
524
+ return {
525
+ id: w.id,
526
+ sessionId: w.session_id ?? w.id,
527
+ messages: (w.messages ?? []).map(toMessage),
528
+ messageCount: w.message_count,
529
+ title: w.title,
530
+ createdAt: w.created_at ?? "",
531
+ updatedAt: w.updated_at,
532
+ workspaceId: w.workspace_id,
533
+ userId: w.user_id,
534
+ metadata: w.metadata
535
+ };
536
+ }
537
+ function toSessionInfo(w) {
538
+ return {
539
+ sessionId: w.session_id ?? w.id ?? "",
540
+ messageCount: w.message_count ?? 0,
541
+ createdAt: w.created_at,
542
+ updatedAt: w.updated_at
543
+ };
544
+ }
545
+ function toObservation(w) {
546
+ return {
547
+ id: w.id,
548
+ conversationId: w.conversation_id,
549
+ content: w.content,
550
+ windowStart: w.window_start,
551
+ windowEnd: w.window_end,
552
+ createdAt: w.created_at
553
+ };
554
+ }
555
+ function toReflection(w) {
556
+ return {
557
+ id: w.id,
558
+ conversationId: w.conversation_id,
559
+ content: w.content,
560
+ createdAt: w.created_at
561
+ };
562
+ }
563
+ var ShortTermMemory = class {
564
+ constructor(transport) {
565
+ this.transport = transport;
566
+ }
567
+ // ---- Bronze tier (bridge) ----------------------------------------------
568
+ async addMessage(sessionId, role, content, options) {
569
+ const wire = await this.transport.request("add_message", {
570
+ session_id: sessionId,
571
+ role,
572
+ content,
573
+ metadata: options?.metadata
574
+ });
575
+ return toMessage(wire);
576
+ }
577
+ async getConversation(sessionId, options) {
578
+ const wire = await this.transport.request("get_conversation", {
579
+ session_id: sessionId,
580
+ limit: options?.limit
581
+ });
582
+ return toConversation(wire);
583
+ }
584
+ async searchMessages(query, options) {
585
+ const wire = await this.transport.request("search_messages", {
586
+ query,
587
+ session_id: options?.sessionId,
588
+ limit: options?.limit ?? 10,
589
+ threshold: options?.threshold ?? 0.7
590
+ });
591
+ return wire.map(toMessage);
592
+ }
593
+ async listSessions(options) {
594
+ const wire = await this.transport.request("list_sessions", {
595
+ limit: options?.limit ?? 100
596
+ });
597
+ return wire.map(toSessionInfo);
598
+ }
599
+ async deleteMessage(messageId) {
600
+ const result = await this.transport.request("delete_message", {
601
+ message_id: messageId
602
+ });
603
+ return result.deleted;
604
+ }
605
+ async clearSession(sessionId) {
606
+ await this.transport.request("clear_session", { session_id: sessionId });
607
+ }
608
+ // ---- Volume 5 / hosted-native methods -----------------------------------
609
+ /** Create a new conversation (hosted service). */
610
+ async createConversation(options) {
611
+ const wire = await this.transport.request("create_conversation", {
612
+ user_id: options.userId,
613
+ metadata: options.metadata
614
+ });
615
+ return toConversation(wire);
616
+ }
617
+ /** List conversations the API key has access to. */
618
+ async listConversations(options) {
619
+ const wire = await this.transport.request("list_conversations", {
620
+ limit: options?.limit,
621
+ userId: options?.userId
622
+ });
623
+ return wire.map(toConversation);
624
+ }
625
+ /** Fetch conversation metadata (no messages). */
626
+ async getConversationMetadata(conversationId) {
627
+ const wire = await this.transport.request("get_conversation_metadata", {
628
+ conversation_id: conversationId
629
+ });
630
+ return toConversation(wire);
631
+ }
632
+ /** Delete a conversation and all its messages. */
633
+ async deleteConversation(conversationId) {
634
+ await this.transport.request("delete_conversation", { conversation_id: conversationId });
635
+ }
636
+ /**
637
+ * Three-tier conversational context (reflections + observations + recent
638
+ * messages). The richest input you can hand an LLM about a conversation.
639
+ */
640
+ async getContext(conversationId) {
641
+ const wire = await this.transport.request("get_context", {
642
+ conversation_id: conversationId
643
+ });
644
+ return {
645
+ reflections: (wire.reflections ?? []).map(toReflection),
646
+ observations: (wire.observations ?? []).map(toObservation),
647
+ recentMessages: (wire.recent_messages ?? []).map(toMessage)
648
+ };
649
+ }
650
+ /** Bulk-add up to 100 messages in one request. */
651
+ async bulkAddMessages(conversationId, messages) {
652
+ if (messages.length > 100) {
653
+ throw new Error("bulkAddMessages accepts a maximum of 100 messages per call.");
654
+ }
655
+ const wire = await this.transport.request("bulk_add_messages", {
656
+ conversation_id: conversationId,
657
+ messages
658
+ });
659
+ return wire.map(toMessage);
660
+ }
661
+ /** Auto-generated message-window summaries. */
662
+ async getObservations(conversationId, options) {
663
+ const wire = await this.transport.request("get_observations", {
664
+ conversation_id: conversationId,
665
+ limit: options?.limit
666
+ });
667
+ return wire.map(toObservation);
668
+ }
669
+ /** Higher-level reflections derived from observations. */
670
+ async getReflections(conversationId) {
671
+ const wire = await this.transport.request("get_reflections", {
672
+ conversation_id: conversationId
673
+ });
674
+ return wire.map(toReflection);
675
+ }
676
+ };
677
+
678
+ // src/transport/casing.ts
679
+ var SNAKE_RE = /_([a-z0-9])/g;
680
+ var CAMEL_RE = /([A-Z])/g;
681
+ function snakeKey(key) {
682
+ return key.replace(CAMEL_RE, (_, c) => `_${c.toLowerCase()}`);
683
+ }
684
+ function camelKey(key) {
685
+ return key.replace(SNAKE_RE, (_, c) => c.toUpperCase());
686
+ }
687
+ function snakeToCamel(value) {
688
+ if (Array.isArray(value)) {
689
+ return value.map((v) => snakeToCamel(v));
690
+ }
691
+ if (value !== null && typeof value === "object") {
692
+ const out = {};
693
+ for (const [k, v] of Object.entries(value)) {
694
+ out[camelKey(k)] = snakeToCamel(v);
695
+ }
696
+ return out;
697
+ }
698
+ return value;
699
+ }
700
+ function camelToSnake(value) {
701
+ if (Array.isArray(value)) {
702
+ return value.map((v) => camelToSnake(v));
703
+ }
704
+ if (value !== null && typeof value === "object") {
705
+ const out = {};
706
+ for (const [k, v] of Object.entries(value)) {
707
+ out[snakeKey(k)] = camelToSnake(v);
708
+ }
709
+ return out;
710
+ }
711
+ return value;
712
+ }
713
+
714
+ // src/transport/rest.ts
715
+ function trimTrailingSlashes(s) {
716
+ let end = s.length;
717
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
718
+ return s.slice(0, end);
719
+ }
720
+ function nowMs() {
721
+ if (typeof performance !== "undefined" && typeof performance.now === "function") {
722
+ return performance.now();
723
+ }
724
+ return Date.now();
725
+ }
726
+ function snakeToCamelKey(s) {
727
+ return s.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
728
+ }
729
+ var ROUTES = {
730
+ // Lifecycle ----------------------------------------------------------------
731
+ setup: "noop",
732
+ teardown: "noop",
733
+ // Hosted has no global clear; we delete every conversation owned by the API
734
+ // key. This is best-effort — see clearAllData() for the implementation.
735
+ clear_all_data: "noop",
736
+ // Short-Term — legacy bridge methods (mapped where a clean REST equivalent
737
+ // exists; bridge sessionId is treated as the conversationId UUID).
738
+ add_message: {
739
+ method: "POST",
740
+ path: "/conversations/{sessionId}/messages",
741
+ pathParams: ["sessionId"],
742
+ hasBody: true
743
+ },
744
+ get_conversation: {
745
+ method: "GET",
746
+ path: "/conversations/{sessionId}/messages",
747
+ pathParams: ["sessionId"],
748
+ queryParams: ["limit"],
749
+ shape: (raw, p) => {
750
+ const messages = raw?.messages ?? raw ?? [];
751
+ return {
752
+ id: p["sessionId"],
753
+ session_id: p["sessionId"],
754
+ messages,
755
+ created_at: null
756
+ };
757
+ }
758
+ },
759
+ list_sessions: {
760
+ method: "GET",
761
+ path: "/conversations",
762
+ queryParams: ["limit"],
763
+ shape: (raw) => {
764
+ const conversations = raw?.conversations ?? [];
765
+ return conversations.map((c) => {
766
+ const conv = c;
767
+ return {
768
+ session_id: conv["id"],
769
+ message_count: conv["messageCount"] ?? 0,
770
+ created_at: conv["createdAt"],
771
+ updated_at: conv["updatedAt"]
772
+ };
773
+ });
774
+ }
775
+ },
776
+ search_messages: {
777
+ method: "POST",
778
+ path: "/conversations/{sessionId}/search",
779
+ pathParams: ["sessionId"],
780
+ hasBody: true,
781
+ shape: (raw) => raw?.messages ?? []
782
+ },
783
+ clear_session: {
784
+ method: "DELETE",
785
+ path: "/conversations/{sessionId}",
786
+ pathParams: ["sessionId"]
787
+ },
788
+ delete_message: "unsupported",
789
+ // Long-Term — legacy mapped methods
790
+ add_entity: {
791
+ method: "POST",
792
+ path: "/entities",
793
+ hasBody: true
794
+ },
795
+ search_entities: {
796
+ method: "POST",
797
+ path: "/entities/search",
798
+ hasBody: true,
799
+ shape: (raw) => raw?.entities ?? []
800
+ },
801
+ add_preference: "unsupported",
802
+ add_fact: "unsupported",
803
+ search_preferences: "unsupported",
804
+ get_entity_by_name: "unsupported",
805
+ get_related_entities: "unsupported",
806
+ add_relationship: "unsupported",
807
+ merge_duplicate_entities: "unsupported",
808
+ // Reasoning — legacy not directly representable in REST
809
+ start_trace: "unsupported",
810
+ add_step: "unsupported",
811
+ record_tool_call: {
812
+ method: "POST",
813
+ path: "/reasoning/tool-calls",
814
+ hasBody: true
815
+ },
816
+ complete_trace: "unsupported",
817
+ get_trace_with_steps: "unsupported",
818
+ list_traces: "unsupported",
819
+ get_tool_stats: "unsupported",
820
+ get_similar_traces: "unsupported",
821
+ // ---- Hosted-native methods (Volume 5 / Platinum tier) --------------------
822
+ create_conversation: {
823
+ method: "POST",
824
+ path: "/conversations",
825
+ hasBody: true
826
+ },
827
+ list_conversations: {
828
+ method: "GET",
829
+ path: "/conversations",
830
+ queryParams: ["limit", "user_id"],
831
+ shape: (raw) => raw?.conversations ?? raw
832
+ },
833
+ get_conversation_metadata: {
834
+ method: "GET",
835
+ path: "/conversations/{conversationId}",
836
+ pathParams: ["conversationId"]
837
+ },
838
+ delete_conversation: {
839
+ method: "DELETE",
840
+ path: "/conversations/{conversationId}",
841
+ pathParams: ["conversationId"]
842
+ },
843
+ get_context: {
844
+ method: "GET",
845
+ path: "/conversations/{conversationId}/context",
846
+ pathParams: ["conversationId"]
847
+ },
848
+ bulk_add_messages: {
849
+ method: "POST",
850
+ path: "/conversations/{conversationId}/messages/bulk",
851
+ pathParams: ["conversationId"],
852
+ hasBody: true,
853
+ shape: (raw) => raw?.messages ?? raw
854
+ },
855
+ get_observations: {
856
+ method: "GET",
857
+ path: "/conversations/{conversationId}/observations",
858
+ pathParams: ["conversationId"],
859
+ queryParams: ["limit"],
860
+ shape: (raw) => raw?.observations ?? raw
861
+ },
862
+ get_reflections: {
863
+ method: "GET",
864
+ path: "/conversations/{conversationId}/reflections",
865
+ pathParams: ["conversationId"],
866
+ shape: (raw) => raw?.reflections ?? raw
867
+ },
868
+ list_entities: {
869
+ method: "GET",
870
+ path: "/entities",
871
+ queryParams: ["type", "limit"],
872
+ shape: (raw) => raw?.entities ?? raw
873
+ },
874
+ get_entity: {
875
+ method: "GET",
876
+ path: "/entities/{entityId}",
877
+ pathParams: ["entityId"]
878
+ },
879
+ update_entity: {
880
+ method: "PUT",
881
+ path: "/entities/{entityId}",
882
+ pathParams: ["entityId"],
883
+ hasBody: true
884
+ },
885
+ delete_entity: {
886
+ method: "DELETE",
887
+ path: "/entities/{entityId}",
888
+ pathParams: ["entityId"]
889
+ },
890
+ set_entity_feedback: {
891
+ method: "PUT",
892
+ path: "/entities/{entityId}/feedback",
893
+ pathParams: ["entityId"],
894
+ hasBody: true
895
+ },
896
+ get_entity_history: {
897
+ method: "GET",
898
+ path: "/entities/{entityId}/history",
899
+ pathParams: ["entityId"]
900
+ },
901
+ merge_entities: {
902
+ method: "POST",
903
+ path: "/entities/{sourceId}/merge",
904
+ pathParams: ["sourceId"],
905
+ hasBody: true
906
+ },
907
+ get_entity_graph: {
908
+ method: "GET",
909
+ path: "/entities/graph"
910
+ },
911
+ explain_step: {
912
+ method: "GET",
913
+ path: "/reasoning/explain/{stepId}",
914
+ pathParams: ["stepId"]
915
+ },
916
+ get_trace_by_conversation: {
917
+ method: "GET",
918
+ path: "/reasoning/trace/{conversationId}",
919
+ pathParams: ["conversationId"]
920
+ },
921
+ get_entity_provenance: {
922
+ method: "GET",
923
+ path: "/reasoning/provenance/{entityId}",
924
+ pathParams: ["entityId"]
925
+ },
926
+ record_step: {
927
+ method: "POST",
928
+ path: "/reasoning/steps",
929
+ hasBody: true
930
+ },
931
+ list_steps: {
932
+ method: "GET",
933
+ path: "/reasoning/steps",
934
+ queryParams: ["conversation_id"],
935
+ shape: (raw) => raw?.steps ?? raw
936
+ },
937
+ cypher_query: {
938
+ method: "POST",
939
+ path: "/query",
940
+ hasBody: true
941
+ },
942
+ // Auth
943
+ list_api_keys: {
944
+ method: "GET",
945
+ path: "/auth/api-keys",
946
+ queryParams: ["workspace_id"],
947
+ shape: (raw) => {
948
+ const r = raw;
949
+ return r?.keys ?? r?.api_keys ?? raw;
950
+ }
951
+ },
952
+ create_api_key: {
953
+ method: "POST",
954
+ path: "/auth/api-keys",
955
+ hasBody: true
956
+ },
957
+ revoke_api_key: {
958
+ method: "DELETE",
959
+ path: "/auth/api-keys/{keyId}",
960
+ pathParams: ["keyId"]
961
+ },
962
+ reveal_api_key: {
963
+ method: "GET",
964
+ path: "/auth/api-keys/{keyId}/reveal",
965
+ pathParams: ["keyId"],
966
+ queryParams: ["workspace_id"]
967
+ },
968
+ refresh_access_token: {
969
+ method: "POST",
970
+ path: "/auth/refresh",
971
+ hasBody: true
972
+ }
973
+ };
974
+ var RestTransport = class {
975
+ endpoint;
976
+ apiKey;
977
+ tokenProvider;
978
+ timeout;
979
+ headers;
980
+ logger;
981
+ constructor(options) {
982
+ this.endpoint = trimTrailingSlashes(options.endpoint);
983
+ this.apiKey = options.apiKey;
984
+ this.tokenProvider = options.tokenProvider;
985
+ this.timeout = options.timeout ?? 3e4;
986
+ this.headers = options.headers ?? {};
987
+ this.logger = options.logger;
988
+ }
989
+ async connect() {
990
+ const url = `${this.endpoint}/conversations?limit=1`;
991
+ const start = nowMs();
992
+ this.emit({ kind: "request", method: "connect", url, httpMethod: "GET" });
993
+ let response;
994
+ try {
995
+ response = await fetch(url, {
996
+ method: "GET",
997
+ headers: await this.buildHeaders(),
998
+ signal: AbortSignal.timeout(this.timeout)
999
+ });
1000
+ } catch (error) {
1001
+ const durationMs2 = nowMs() - start;
1002
+ if (error instanceof TypeError) {
1003
+ const err = new ConnectionError(
1004
+ `Failed to connect to ${this.endpoint}: ${error.message}`,
1005
+ { cause: error }
1006
+ );
1007
+ this.emit({ kind: "error", method: "connect", url, durationMs: durationMs2, message: err.message });
1008
+ throw err;
1009
+ }
1010
+ if (error instanceof DOMException && error.name === "TimeoutError") {
1011
+ const err = new ConnectionError(
1012
+ `Connection to ${this.endpoint} timed out after ${this.timeout}ms`,
1013
+ { cause: error }
1014
+ );
1015
+ this.emit({ kind: "error", method: "connect", url, durationMs: durationMs2, message: err.message });
1016
+ throw err;
1017
+ }
1018
+ throw error;
1019
+ }
1020
+ const durationMs = nowMs() - start;
1021
+ const requestId = extractRequestId(response.headers);
1022
+ if (response.status === 401 || response.status === 403) {
1023
+ const err = new AuthenticationError(
1024
+ `Authentication failed against ${this.endpoint}: ${response.status} ${response.statusText}`,
1025
+ { requestId }
1026
+ );
1027
+ this.emit({
1028
+ kind: "error",
1029
+ method: "connect",
1030
+ url,
1031
+ status: response.status,
1032
+ requestId,
1033
+ durationMs,
1034
+ message: err.message
1035
+ });
1036
+ throw err;
1037
+ }
1038
+ if (!response.ok && response.status >= 500) {
1039
+ const err = new ConnectionError(
1040
+ `Server error from ${this.endpoint}: ${response.status} ${response.statusText}`,
1041
+ { requestId }
1042
+ );
1043
+ this.emit({
1044
+ kind: "error",
1045
+ method: "connect",
1046
+ url,
1047
+ status: response.status,
1048
+ requestId,
1049
+ durationMs,
1050
+ message: err.message
1051
+ });
1052
+ throw err;
1053
+ }
1054
+ this.emit({
1055
+ kind: "response",
1056
+ method: "connect",
1057
+ url,
1058
+ status: response.status,
1059
+ requestId,
1060
+ durationMs
1061
+ });
1062
+ }
1063
+ async close() {
1064
+ }
1065
+ async request(method, params) {
1066
+ const route = ROUTES[method];
1067
+ if (!route) {
1068
+ throw new NotSupportedError(
1069
+ `Method '${method}' is not implemented by RestTransport. Use BridgeTransport for full TCK conformance, or call a hosted-native method.`
1070
+ );
1071
+ }
1072
+ if (route === "noop") return void 0;
1073
+ if (route === "unsupported") {
1074
+ throw new NotSupportedError(
1075
+ `Method '${method}' has no equivalent in the hosted Neo4j Agent Memory REST API. It is supported by BridgeTransport only.`
1076
+ );
1077
+ }
1078
+ const original = params ?? {};
1079
+ const camelParams = snakeToCamel(original);
1080
+ let path = route.path;
1081
+ const consumed = /* @__PURE__ */ new Set();
1082
+ for (const name of route.pathParams ?? []) {
1083
+ const v = camelParams[name];
1084
+ if (v === void 0 || v === null || v === "") {
1085
+ throw new TransportError(
1086
+ `Missing required path parameter '${name}' for method '${method}'`,
1087
+ 400,
1088
+ camelParams
1089
+ );
1090
+ }
1091
+ path = path.replace(`{${name}}`, encodeURIComponent(String(v)));
1092
+ consumed.add(name);
1093
+ }
1094
+ const queryEntries = [];
1095
+ for (const name of route.queryParams ?? []) {
1096
+ let v = original[name];
1097
+ if (v === void 0 || v === null) {
1098
+ const camel = snakeToCamelKey(name);
1099
+ v = camelParams[camel];
1100
+ }
1101
+ if (v !== void 0 && v !== null) {
1102
+ queryEntries.push([name, String(v)]);
1103
+ consumed.add(name);
1104
+ consumed.add(snakeToCamelKey(name));
1105
+ }
1106
+ }
1107
+ const query = queryEntries.length ? "?" + queryEntries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&") : "";
1108
+ let body;
1109
+ 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;
1114
+ }
1115
+ }
1116
+ body = JSON.stringify(bodyObj);
1117
+ }
1118
+ const url = `${this.endpoint}${path}${query}`;
1119
+ const start = nowMs();
1120
+ this.emit({ kind: "request", method, url, httpMethod: route.method });
1121
+ let response;
1122
+ try {
1123
+ response = await fetch(url, {
1124
+ method: route.method,
1125
+ headers: await this.buildHeaders(route.hasBody),
1126
+ body,
1127
+ signal: AbortSignal.timeout(this.timeout)
1128
+ });
1129
+ } catch (error) {
1130
+ const durationMs2 = nowMs() - start;
1131
+ if (error instanceof TypeError) {
1132
+ const err = new ConnectionError(
1133
+ `Request to ${url} failed: ${error.message}`,
1134
+ { cause: error }
1135
+ );
1136
+ this.emit({ kind: "error", method, url, durationMs: durationMs2, message: err.message });
1137
+ throw err;
1138
+ }
1139
+ throw error;
1140
+ }
1141
+ const requestId = extractRequestId(response.headers);
1142
+ const durationMs = nowMs() - start;
1143
+ if (response.status === 401 || response.status === 403) {
1144
+ const err = new AuthenticationError(
1145
+ `Authentication failed: ${response.status} ${response.statusText}`,
1146
+ { requestId }
1147
+ );
1148
+ this.emit({
1149
+ kind: "error",
1150
+ method,
1151
+ url,
1152
+ status: response.status,
1153
+ requestId,
1154
+ durationMs,
1155
+ message: err.message
1156
+ });
1157
+ throw err;
1158
+ }
1159
+ if (response.status === 204) {
1160
+ this.emit({ kind: "response", method, url, status: 204, requestId, durationMs });
1161
+ return void 0;
1162
+ }
1163
+ const text = await response.text();
1164
+ if (!response.ok) {
1165
+ let errorBody;
1166
+ try {
1167
+ errorBody = JSON.parse(text);
1168
+ } catch {
1169
+ errorBody = text;
1170
+ }
1171
+ const errMsg = typeof errorBody === "object" && errorBody !== null && "error" in errorBody ? String(errorBody["error"]) : `HTTP ${response.status}`;
1172
+ const err = new TransportError(
1173
+ `${method} failed: ${errMsg}`,
1174
+ response.status,
1175
+ errorBody,
1176
+ { requestId }
1177
+ );
1178
+ this.emit({
1179
+ kind: "error",
1180
+ method,
1181
+ url,
1182
+ status: response.status,
1183
+ requestId,
1184
+ durationMs,
1185
+ message: err.message
1186
+ });
1187
+ throw err;
1188
+ }
1189
+ this.emit({ kind: "response", method, url, status: response.status, requestId, durationMs });
1190
+ if (!text) return void 0;
1191
+ let parsed = JSON.parse(text);
1192
+ if (route.shape) parsed = route.shape(parsed, camelParams);
1193
+ return camelToSnake(parsed);
1194
+ }
1195
+ emit(event) {
1196
+ if (!this.logger) return;
1197
+ try {
1198
+ this.logger(event);
1199
+ } catch {
1200
+ }
1201
+ }
1202
+ async buildHeaders(includeContentType = false) {
1203
+ const headers = {};
1204
+ const canSendUserAgent = supportsUserAgentHeader();
1205
+ for (const [key, value] of Object.entries(this.headers)) {
1206
+ if (key.toLowerCase() === "user-agent" && !canSendUserAgent) continue;
1207
+ headers[key] = value;
1208
+ }
1209
+ if (canSendUserAgent && !Object.keys(headers).some((key) => key.toLowerCase() === "user-agent")) {
1210
+ headers["User-Agent"] = defaultUserAgent();
1211
+ }
1212
+ if (includeContentType) headers["Content-Type"] = "application/json";
1213
+ const token = this.tokenProvider ? await this.tokenProvider() : this.apiKey;
1214
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1215
+ return headers;
1216
+ }
1217
+ };
1218
+
1219
+ // src/client.ts
1220
+ var DEFAULT_ENDPOINT = "https://memory.neo4jlabs.com/v1";
1221
+ var MemoryClient = class {
1222
+ /** Short-term (conversational) memory operations. */
1223
+ shortTerm;
1224
+ /** Long-term (entity / preference / fact / graph) memory operations. */
1225
+ longTerm;
1226
+ /** Reasoning (trace / step / tool call / provenance) memory operations. */
1227
+ reasoning;
1228
+ /** Read-only Cypher query console (hosted service only). */
1229
+ query;
1230
+ /** API-key & OAuth management (hosted service only). */
1231
+ auth;
1232
+ transport;
1233
+ constructor(optionsOrTransport = {}) {
1234
+ if (isTransport(optionsOrTransport)) {
1235
+ this.transport = optionsOrTransport;
1236
+ } else {
1237
+ this.transport = new LazyConnectTransport(createTransport(optionsOrTransport));
1238
+ }
1239
+ this.shortTerm = new ShortTermMemory(this.transport);
1240
+ this.longTerm = new LongTermMemory(this.transport);
1241
+ this.reasoning = new ReasoningMemory(this.transport);
1242
+ this.query = new QueryConsole(this.transport);
1243
+ this.auth = new AuthClient(this.transport);
1244
+ }
1245
+ async connect() {
1246
+ await this.transport.connect();
1247
+ }
1248
+ async close() {
1249
+ await this.transport.close();
1250
+ }
1251
+ };
1252
+ function isTransport(obj) {
1253
+ return typeof obj === "object" && obj !== null && "request" in obj && typeof obj.request === "function";
1254
+ }
1255
+ function pickTransport(endpoint, mode) {
1256
+ if (mode === "bridge" || mode === "rest") return mode;
1257
+ return /\/v\d+\b/.test(endpoint) ? "rest" : "bridge";
1258
+ }
1259
+ function resolveApiKey(option) {
1260
+ if (option !== void 0) return option;
1261
+ if (typeof process === "undefined" || !process.env) return void 0;
1262
+ return process.env.MEMORY_API_KEY;
1263
+ }
1264
+ function createTransport(options) {
1265
+ const endpoint = options.endpoint;
1266
+ const apiKey = resolveApiKey(options.apiKey);
1267
+ const choice = pickTransport(endpoint ?? DEFAULT_ENDPOINT, options.transport);
1268
+ if (choice === "rest") {
1269
+ return new RestTransport({
1270
+ endpoint: endpoint ?? DEFAULT_ENDPOINT,
1271
+ apiKey,
1272
+ tokenProvider: options.tokenProvider,
1273
+ timeout: options.timeout,
1274
+ headers: options.headers,
1275
+ logger: options.logger
1276
+ });
1277
+ }
1278
+ if (!endpoint) {
1279
+ throw new ValidationError("endpoint must be provided for bridge transport.");
1280
+ }
1281
+ return new BridgeTransport({
1282
+ endpoint,
1283
+ apiKey,
1284
+ timeout: options.timeout,
1285
+ headers: options.headers,
1286
+ logger: options.logger
1287
+ });
1288
+ }
1289
+ var LazyConnectTransport = class {
1290
+ constructor(inner) {
1291
+ this.inner = inner;
1292
+ }
1293
+ connectPromise = null;
1294
+ async request(method, params) {
1295
+ return this.inner.request(method, params);
1296
+ }
1297
+ async connect() {
1298
+ if (!this.connectPromise) {
1299
+ this.connectPromise = this.inner.connect().catch((err) => {
1300
+ this.connectPromise = null;
1301
+ throw err;
1302
+ });
1303
+ }
1304
+ return this.connectPromise;
1305
+ }
1306
+ async close() {
1307
+ return this.inner.close();
1308
+ }
1309
+ };
1310
+
1311
+ export { AuthClient, LongTermMemory, MemoryClient, QueryConsole, ReasoningMemory, RestTransport, ShortTermMemory };
1312
+ //# sourceMappingURL=index.js.map
1313
+ //# sourceMappingURL=index.js.map