@cloudbase/agent-adapter-langgraph 0.0.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1062 @@
1
+ // src/agent.ts
2
+ import {
3
+ EventType
4
+ } from "@ag-ui/client";
5
+ import { AbstractAgent } from "@cloudbase/agent-agents/abstract";
6
+ import { Observable } from "rxjs";
7
+ import {
8
+ Annotation,
9
+ MessagesAnnotation,
10
+ Command
11
+ } from "@langchain/langgraph";
12
+
13
+ // src/util.ts
14
+ import { DynamicStructuredTool } from "@langchain/core/tools";
15
+ import z from "zod/v4";
16
+ function convertActionsToDynamicStructuredTools(actions) {
17
+ return actions.map(convertActionToDynamicStructuredTool);
18
+ }
19
+ function convertActionToDynamicStructuredTool(actionInput) {
20
+ return new DynamicStructuredTool({
21
+ name: actionInput.name,
22
+ description: actionInput.description,
23
+ schema: convertJsonSchemaToZodSchema(actionInput.parameters, true),
24
+ func: async () => {
25
+ return "";
26
+ }
27
+ });
28
+ }
29
+ function convertJsonSchemaToZodSchema(jsonSchema, required) {
30
+ if (jsonSchema.type === "object") {
31
+ const spec = {};
32
+ if (!jsonSchema.properties || !Object.keys(jsonSchema.properties).length) {
33
+ return !required ? z.object(spec).optional() : z.object(spec);
34
+ }
35
+ for (const [key, value] of Object.entries(jsonSchema.properties)) {
36
+ spec[key] = convertJsonSchemaToZodSchema(
37
+ value,
38
+ jsonSchema.required ? jsonSchema.required.includes(key) : false
39
+ );
40
+ }
41
+ let schema = z.object(spec).describe(jsonSchema.description);
42
+ return required ? schema : schema.optional();
43
+ } else if (jsonSchema.type === "string") {
44
+ let schema = z.string().describe(jsonSchema.description);
45
+ return required ? schema : schema.optional();
46
+ } else if (jsonSchema.type === "number") {
47
+ let schema = z.number().describe(jsonSchema.description);
48
+ return required ? schema : schema.optional();
49
+ } else if (jsonSchema.type === "boolean") {
50
+ let schema = z.boolean().describe(jsonSchema.description);
51
+ return required ? schema : schema.optional();
52
+ } else if (jsonSchema.type === "array") {
53
+ let itemSchema = convertJsonSchemaToZodSchema(jsonSchema.items, true);
54
+ let schema = z.array(itemSchema).describe(jsonSchema.description);
55
+ return required ? schema : schema.optional();
56
+ }
57
+ throw new Error("Invalid JSON schema");
58
+ }
59
+
60
+ // src/agent.ts
61
+ var AGKitPropertiesAnnotation = Annotation.Root({
62
+ actions: Annotation
63
+ });
64
+ var AGKitStateAnnotation = Annotation.Root({
65
+ agKit: Annotation,
66
+ ...MessagesAnnotation.spec
67
+ });
68
+ var LanggraphAgent = class extends AbstractAgent {
69
+ constructor(agentConfig) {
70
+ super(agentConfig);
71
+ this.compiledWorkflow = agentConfig.compiledWorkflow;
72
+ }
73
+ run(input) {
74
+ return new Observable((subscriber) => {
75
+ this._run(subscriber, input);
76
+ });
77
+ }
78
+ async _run(subscriber, input) {
79
+ const { messages, runId, threadId } = input;
80
+ const oldMessageIds = /* @__PURE__ */ new Set();
81
+ if (this.compiledWorkflow.checkpointer && typeof this.compiledWorkflow.checkpointer !== "boolean") {
82
+ const res = await this.compiledWorkflow.getState({
83
+ configurable: { thread_id: threadId }
84
+ });
85
+ const oldMessages = res.values?.messages || [];
86
+ oldMessages.filter((x) => x.id).forEach((x) => {
87
+ oldMessageIds.add(x.id);
88
+ });
89
+ }
90
+ subscriber.next({
91
+ type: EventType.RUN_STARTED,
92
+ threadId,
93
+ runId
94
+ });
95
+ const streamEventInput = input.forwardedProps?.resume ? new Command({
96
+ resume: JSON.stringify(input.forwardedProps?.resume?.payload)
97
+ }) : {
98
+ messages: aguiMessagesToLangChain(messages),
99
+ agKit: {
100
+ actions: convertActionsToDynamicStructuredTools(
101
+ input.tools.map((x) => ({
102
+ ...x,
103
+ parameters: typeof x.parameters === "string" ? JSON.parse(x.parameters) : x.parameters
104
+ }))
105
+ )
106
+ }
107
+ };
108
+ const stream = this.compiledWorkflow.streamEvents(streamEventInput, {
109
+ version: "v2",
110
+ runId,
111
+ configurable: {
112
+ thread_id: threadId
113
+ }
114
+ });
115
+ const chatModelRuns = [];
116
+ const handledToolCallIds = /* @__PURE__ */ new Set();
117
+ let interrupt;
118
+ let currentToolCall = null;
119
+ for await (const event of stream) {
120
+ if (event.event.startsWith("ChannelWrite<")) {
121
+ continue;
122
+ }
123
+ if (event.event === "on_chat_model_start") {
124
+ chatModelRuns.push({ runId: event.run_id });
125
+ continue;
126
+ }
127
+ if (event.event === "on_chat_model_stream") {
128
+ if (Array.isArray(event.data.chunk?.tool_call_chunks) && event.data.chunk?.tool_call_chunks?.length > 0) {
129
+ event.data.chunk.tool_call_chunks.map((x) => ({
130
+ ...x,
131
+ args: typeof x.args === "string" ? x.args : x.args ? JSON.stringify(x.args) : ""
132
+ })).forEach((toolCall) => {
133
+ if (currentToolCall) {
134
+ if (toolCall.id && currentToolCall.id !== toolCall.id) {
135
+ subscriber.next({
136
+ toolCallId: currentToolCall.id,
137
+ type: EventType.TOOL_CALL_END
138
+ });
139
+ if (toolCall.name && toolCall.id) {
140
+ currentToolCall = toolCall;
141
+ subscriber.next({
142
+ toolCallId: currentToolCall.id,
143
+ toolCallName: currentToolCall.name,
144
+ type: EventType.TOOL_CALL_START
145
+ });
146
+ if (currentToolCall.args) {
147
+ subscriber.next({
148
+ toolCallId: currentToolCall.id,
149
+ delta: currentToolCall.args,
150
+ type: EventType.TOOL_CALL_ARGS
151
+ });
152
+ if (isValidJson(currentToolCall.args)) {
153
+ subscriber.next({
154
+ toolCallId: currentToolCall.id,
155
+ type: EventType.TOOL_CALL_END
156
+ });
157
+ currentToolCall = null;
158
+ }
159
+ }
160
+ }
161
+ } else {
162
+ if (toolCall.args) {
163
+ currentToolCall.args += toolCall.args;
164
+ subscriber.next({
165
+ toolCallId: currentToolCall.id,
166
+ delta: toolCall.args,
167
+ type: EventType.TOOL_CALL_ARGS
168
+ });
169
+ if (isValidJson(currentToolCall.args)) {
170
+ subscriber.next({
171
+ toolCallId: currentToolCall.id,
172
+ type: EventType.TOOL_CALL_END
173
+ });
174
+ currentToolCall = null;
175
+ }
176
+ }
177
+ }
178
+ } else {
179
+ if (toolCall.name && toolCall.id) {
180
+ currentToolCall = toolCall;
181
+ subscriber.next({
182
+ toolCallId: toolCall.id,
183
+ toolCallName: toolCall.name,
184
+ type: EventType.TOOL_CALL_START
185
+ });
186
+ if (toolCall.args) {
187
+ subscriber.next({
188
+ toolCallId: toolCall.id,
189
+ delta: toolCall.args,
190
+ type: EventType.TOOL_CALL_ARGS
191
+ });
192
+ if (isValidJson(toolCall.args)) {
193
+ subscriber.next({
194
+ toolCallId: toolCall.id,
195
+ type: EventType.TOOL_CALL_END
196
+ });
197
+ currentToolCall = null;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ });
203
+ }
204
+ const chatModelRun = chatModelRuns.find(
205
+ (run) => run.runId === event.run_id
206
+ );
207
+ if (!chatModelRun) {
208
+ subscriber.next({
209
+ type: EventType.RUN_ERROR,
210
+ message: `Received a message from an unknown chat model run. Run Id: ${event.run_id}`
211
+ });
212
+ continue;
213
+ }
214
+ if (!chatModelRun.messageId) {
215
+ const messageId = event.data.chunk.id;
216
+ chatModelRun.messageId = messageId;
217
+ subscriber.next({
218
+ messageId,
219
+ type: EventType.TEXT_MESSAGE_START,
220
+ role: "assistant"
221
+ });
222
+ const delta = event.data.chunk.content;
223
+ typeof delta === "string" && delta && subscriber.next({
224
+ messageId: chatModelRun.messageId,
225
+ type: EventType.TEXT_MESSAGE_CONTENT,
226
+ delta
227
+ });
228
+ continue;
229
+ } else {
230
+ if (chatModelRun.messageId !== event.data.chunk.id) {
231
+ subscriber.next({
232
+ type: EventType.RUN_ERROR,
233
+ message: `Received a message of unknown message id from current run. Run Id: ${event.run_id} Message Id from current run: ${chatModelRun.messageId} Message Id from received message: ${event.data.chunk.id}`
234
+ });
235
+ continue;
236
+ }
237
+ const delta = event.data.chunk.content;
238
+ typeof delta === "string" && delta && subscriber.next({
239
+ messageId: chatModelRun.messageId,
240
+ type: EventType.TEXT_MESSAGE_CONTENT,
241
+ delta
242
+ });
243
+ continue;
244
+ }
245
+ }
246
+ if (event.event === "on_chat_model_end") {
247
+ const chatModelRun = chatModelRuns.find(
248
+ (run) => run.runId === event.run_id
249
+ );
250
+ if (!chatModelRun) {
251
+ subscriber.next({
252
+ type: EventType.RUN_ERROR,
253
+ message: `Received a on_chat_model_end event from an unknown chat model run. Run Id: ${event.run_id}`
254
+ });
255
+ continue;
256
+ }
257
+ subscriber.next({
258
+ type: EventType.TEXT_MESSAGE_END,
259
+ messageId: chatModelRun.messageId
260
+ });
261
+ continue;
262
+ }
263
+ if (event.event === "on_chain_end") {
264
+ const messages2 = event.data.output?.messages;
265
+ if (Array.isArray(messages2)) {
266
+ const inputMessages = event.data.input?.messages;
267
+ const lastInputMessage = inputMessages?.[inputMessages?.length - 1];
268
+ const messageId = lastInputMessage?.id;
269
+ const toolCallMessages = messages2.filter((x) => x.id && !oldMessageIds.has(x.id)).filter((x) => x?.tool_call_id);
270
+ toolCallMessages.forEach((x) => {
271
+ if (handledToolCallIds.has(x.tool_call_id)) {
272
+ return;
273
+ }
274
+ subscriber.next({
275
+ toolCallId: x.tool_call_id,
276
+ type: EventType.TOOL_CALL_RESULT,
277
+ content: x.content,
278
+ messageId
279
+ });
280
+ handledToolCallIds.add(x.tool_call_id);
281
+ });
282
+ continue;
283
+ }
284
+ }
285
+ if (event.event === "on_chain_stream" && event.data.chunk?.__interrupt__ && Array.isArray(event.data.chunk.__interrupt__) && event.data.chunk.__interrupt__.length > 0) {
286
+ const rawInterrupt = event.data.chunk.__interrupt__[0];
287
+ interrupt = {
288
+ id: rawInterrupt.id,
289
+ // TODO: replace with actual reason
290
+ reason: "agent requested interrupt",
291
+ payload: rawInterrupt.value
292
+ };
293
+ }
294
+ }
295
+ if (interrupt) {
296
+ subscriber.next({
297
+ type: EventType.RUN_FINISHED,
298
+ threadId,
299
+ runId,
300
+ outcome: "interrupt",
301
+ interrupt
302
+ });
303
+ } else {
304
+ subscriber.next({
305
+ type: EventType.RUN_FINISHED,
306
+ threadId,
307
+ runId
308
+ });
309
+ }
310
+ }
311
+ clone() {
312
+ const workflow = this.compiledWorkflow;
313
+ this.compiledWorkflow = void 0;
314
+ const cloned = super.clone();
315
+ this.compiledWorkflow = workflow;
316
+ cloned.compiledWorkflow = workflow;
317
+ return cloned;
318
+ }
319
+ };
320
+ function aguiMessagesToLangChain(messages) {
321
+ return messages.map((message, index) => {
322
+ switch (message.role) {
323
+ case "user":
324
+ return {
325
+ id: message.id,
326
+ role: message.role,
327
+ content: message.content,
328
+ type: "human"
329
+ };
330
+ case "assistant":
331
+ return {
332
+ id: message.id,
333
+ type: "ai",
334
+ role: message.role,
335
+ content: message.content ?? "",
336
+ tool_calls: (message.toolCalls ?? []).map((tc) => ({
337
+ id: tc.id,
338
+ name: tc.function.name,
339
+ args: JSON.parse(tc.function.arguments),
340
+ type: "tool_call"
341
+ }))
342
+ };
343
+ case "system":
344
+ return {
345
+ id: message.id,
346
+ role: message.role,
347
+ content: message.content,
348
+ type: "system"
349
+ };
350
+ case "tool":
351
+ return {
352
+ content: message.content,
353
+ role: message.role,
354
+ type: message.role,
355
+ tool_call_id: message.toolCallId,
356
+ id: message.id
357
+ };
358
+ default:
359
+ console.error(`Message role ${message.role} is not implemented`);
360
+ throw new Error("message role is not supported.");
361
+ }
362
+ });
363
+ }
364
+ function isValidJson(json) {
365
+ try {
366
+ JSON.parse(json);
367
+ return true;
368
+ } catch (e) {
369
+ return false;
370
+ }
371
+ }
372
+
373
+ // src/checkpoint.ts
374
+ import {
375
+ BaseCheckpointSaver
376
+ } from "@langchain/langgraph";
377
+ import {
378
+ MemoryClient,
379
+ Order
380
+ } from "@cloudbase/agent-agents";
381
+ var TDAISaver = class extends BaseCheckpointSaver {
382
+ constructor(config) {
383
+ super();
384
+ const {
385
+ checkpointType = "checkpoints",
386
+ checkpointWritesType = "checkpoint_writes",
387
+ ...clientConfig
388
+ } = config;
389
+ this.memoryClient = new MemoryClient(clientConfig);
390
+ this.checkpointType = checkpointType;
391
+ this.checkpointWritesType = checkpointWritesType;
392
+ }
393
+ /**
394
+ * Retrieves a checkpoint from TDAI Memory based on the provided config.
395
+ * If the config contains a "checkpoint_id" key, the checkpoint with the matching
396
+ * thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint
397
+ * for the given thread ID is retrieved.
398
+ */
399
+ async getTuple(config) {
400
+ try {
401
+ const {
402
+ thread_id,
403
+ checkpoint_ns = "",
404
+ checkpoint_id
405
+ } = config.configurable ?? {};
406
+ if (!thread_id) {
407
+ return void 0;
408
+ }
409
+ const query = {
410
+ collection: this.checkpointType,
411
+ checkpoint_ns
412
+ };
413
+ if (checkpoint_id) {
414
+ query.checkpoint_id = checkpoint_id;
415
+ }
416
+ const { events = [] } = await this.memoryClient.queryEvents({
417
+ sessionId: thread_id,
418
+ where: query,
419
+ orderBy: { checkpoint_id: Order.DESCENDING },
420
+ limit: 1
421
+ });
422
+ if (events.length === 0) {
423
+ return void 0;
424
+ }
425
+ const doc = events[0];
426
+ const configurableValues = {
427
+ checkpoint_ns,
428
+ checkpoint_id: doc.checkpoint_id
429
+ };
430
+ const checkpoint = doc.checkpoint;
431
+ const { events: serializedWrites = [] } = await this.memoryClient.queryEvents({
432
+ sessionId: thread_id,
433
+ where: {
434
+ collection: this.checkpointWritesType,
435
+ ...configurableValues
436
+ }
437
+ });
438
+ const pendingWrites = serializedWrites.map(
439
+ (serializedWrite) => {
440
+ return [
441
+ serializedWrite.task_id,
442
+ serializedWrite.channel,
443
+ serializedWrite.value
444
+ ];
445
+ }
446
+ );
447
+ const metadata = doc.metadata || {};
448
+ return {
449
+ config: { configurable: configurableValues },
450
+ checkpoint,
451
+ pendingWrites,
452
+ metadata,
453
+ parentConfig: doc.parent_checkpoint_id != null ? {
454
+ configurable: {
455
+ thread_id,
456
+ checkpoint_ns,
457
+ checkpoint_id: doc.parent_checkpoint_id
458
+ }
459
+ } : void 0
460
+ };
461
+ } catch (error) {
462
+ console.error("Error getting checkpoint:", error);
463
+ return void 0;
464
+ }
465
+ }
466
+ /**
467
+ * Retrieve a list of checkpoint tuples from TDAI Memory based on the provided config.
468
+ * The checkpoints are ordered by checkpoint ID in descending order (newest first).
469
+ */
470
+ async *list(config, options) {
471
+ const { limit, before, filter } = options ?? {};
472
+ if (!config?.configurable?.thread_id) {
473
+ throw new Error("Thread ID is required");
474
+ }
475
+ const query = {
476
+ collection: this.checkpointType
477
+ };
478
+ if (config?.configurable?.checkpoint_ns !== void 0 && config?.configurable?.checkpoint_ns !== null) {
479
+ query.checkpoint_ns = config.configurable.checkpoint_ns;
480
+ }
481
+ if (filter) {
482
+ Object.entries(filter).forEach(([key, value]) => {
483
+ query[`metadata.${key}`] = value;
484
+ });
485
+ }
486
+ if (before) {
487
+ query.checkpoint_id = { $lt: before.configurable?.checkpoint_id };
488
+ }
489
+ const { events = [] } = await this.memoryClient.queryEvents({
490
+ sessionId: config?.configurable?.thread_id || "default",
491
+ where: query,
492
+ orderBy: { checkpoint_id: Order.DESCENDING },
493
+ limit
494
+ });
495
+ for (const doc of events) {
496
+ const checkpoint = doc.checkpoint;
497
+ const metadata = doc.metadata || {};
498
+ yield {
499
+ config: {
500
+ configurable: {
501
+ thread_id: doc.thread_id,
502
+ checkpoint_ns: doc.checkpoint_ns,
503
+ checkpoint_id: doc.checkpoint_id
504
+ }
505
+ },
506
+ checkpoint,
507
+ metadata,
508
+ parentConfig: doc.parent_checkpoint_id ? {
509
+ configurable: {
510
+ thread_id: doc.thread_id,
511
+ checkpoint_ns: doc.checkpoint_ns,
512
+ checkpoint_id: doc.parent_checkpoint_id
513
+ }
514
+ } : void 0
515
+ };
516
+ }
517
+ }
518
+ /**
519
+ * Saves a checkpoint to TDAI Memory. The checkpoint is associated with the
520
+ * provided config and its parent config (if any).
521
+ */
522
+ async put(config, checkpoint, metadata) {
523
+ try {
524
+ const thread_id = config.configurable?.thread_id;
525
+ const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
526
+ const checkpoint_id = checkpoint.id;
527
+ if (thread_id === void 0) {
528
+ throw new Error(
529
+ `The provided config must contain a configurable field with a "thread_id" field.`
530
+ );
531
+ }
532
+ const doc = {
533
+ collection: this.checkpointType,
534
+ checkpoint_ns,
535
+ checkpoint_id,
536
+ parent_checkpoint_id: config.configurable?.checkpoint_id,
537
+ checkpoint,
538
+ metadata
539
+ };
540
+ const { events = [] } = await this.memoryClient.queryEvents({
541
+ sessionId: thread_id,
542
+ where: {
543
+ collection: this.checkpointType,
544
+ checkpoint_ns,
545
+ checkpoint_id
546
+ }
547
+ });
548
+ if (events[0]) {
549
+ this.memoryClient.deleteEvent({
550
+ sessionId: thread_id,
551
+ eventId: events[0].id
552
+ // messages: doc,
553
+ });
554
+ } else {
555
+ await this.memoryClient.appendEvent({
556
+ sessionId: thread_id,
557
+ messages: doc
558
+ });
559
+ }
560
+ return {
561
+ configurable: {
562
+ thread_id,
563
+ checkpoint_ns,
564
+ checkpoint_id
565
+ }
566
+ };
567
+ } catch (error) {
568
+ console.error("Error saving checkpoint:", error);
569
+ throw error;
570
+ }
571
+ }
572
+ /**
573
+ * Saves intermediate writes associated with a checkpoint to TDAI Memory.
574
+ */
575
+ async putWrites(config, writes, taskId) {
576
+ try {
577
+ const thread_id = config.configurable?.thread_id;
578
+ const checkpoint_ns = config.configurable?.checkpoint_ns;
579
+ const checkpoint_id = config.configurable?.checkpoint_id;
580
+ if (thread_id === void 0 || checkpoint_ns === void 0 || checkpoint_id === void 0) {
581
+ throw new Error(
582
+ `The provided config must contain a configurable field with "thread_id", "checkpoint_ns" and "checkpoint_id" fields.`
583
+ );
584
+ }
585
+ const writePromises = writes.map(async ([channel, value], idx) => {
586
+ const writeDoc = {
587
+ collection: this.checkpointWritesType,
588
+ checkpoint_ns,
589
+ checkpoint_id,
590
+ task_id: taskId,
591
+ idx,
592
+ channel,
593
+ value
594
+ // Store directly as JSON
595
+ };
596
+ return this.memoryClient.appendEvent({
597
+ sessionId: thread_id,
598
+ messages: writeDoc
599
+ });
600
+ });
601
+ await Promise.all(writePromises);
602
+ } catch (error) {
603
+ console.error("Error storing writes:", error);
604
+ throw error;
605
+ }
606
+ }
607
+ /**
608
+ * Delete all checkpoints and writes for a thread from TDAI Memory.
609
+ */
610
+ async deleteThread(threadId) {
611
+ try {
612
+ await this.memoryClient.deleteSession({
613
+ sessionId: threadId
614
+ });
615
+ } catch (error) {
616
+ console.error("Error deleting thread:", error);
617
+ throw error;
618
+ }
619
+ }
620
+ /**
621
+ * Close the memory client connection
622
+ */
623
+ close() {
624
+ this.memoryClient.close();
625
+ }
626
+ };
627
+
628
+ // src/store/tdai-store.ts
629
+ import {
630
+ BaseStore
631
+ } from "@langchain/langgraph";
632
+ var TDAIStore = class extends BaseStore {
633
+ constructor(config) {
634
+ super();
635
+ this.isSetup = false;
636
+ this.isClosed = false;
637
+ this.client = config.memoryClient;
638
+ this.namespacePrefix = config.namespacePrefix || [];
639
+ this.ttlConfig = config.ttl;
640
+ this.ensureTables = config.ensureTables ?? true;
641
+ this.sessionId = config.sessionId || "default_session";
642
+ this.defaultStrategy = config.defaultStrategy || "store";
643
+ }
644
+ /**
645
+ * Create a storage key from namespace and key
646
+ */
647
+ createStorageKey(namespace, key) {
648
+ const fullNamespace = [...this.namespacePrefix, ...namespace];
649
+ return `${fullNamespace.join(":")}:${key}`;
650
+ }
651
+ /**
652
+ * Parse a storage key back to namespace and key
653
+ */
654
+ parseStorageKey(storageKey) {
655
+ const parts = storageKey.split(":");
656
+ const prefixLength = this.namespacePrefix.length;
657
+ const namespace = parts.slice(prefixLength, -1);
658
+ const key = parts[parts.length - 1];
659
+ return { namespace, key };
660
+ }
661
+ /**
662
+ * Put an item with optional TTL.
663
+ */
664
+ async put(namespace, key, value, index, options) {
665
+ if (!this.isSetup && this.ensureTables) {
666
+ await this.setup();
667
+ }
668
+ const storageKey = this.createStorageKey(namespace, key);
669
+ const content = JSON.stringify({
670
+ storageKey,
671
+ namespace,
672
+ key,
673
+ value,
674
+ index,
675
+ ttl: options?.ttl || this.ttlConfig?.defaultTtlSeconds
676
+ });
677
+ await this.client.appendRecord({
678
+ sessionId: this.sessionId,
679
+ content,
680
+ strategy: this.defaultStrategy
681
+ });
682
+ }
683
+ /**
684
+ * Get an item by namespace and key.
685
+ */
686
+ async get(namespace, key) {
687
+ if (!this.isSetup && this.ensureTables) {
688
+ await this.setup();
689
+ }
690
+ const storageKey = this.createStorageKey(namespace, key);
691
+ try {
692
+ const { records = [] } = await this.client.searchRecords({
693
+ content: storageKey,
694
+ sessionId: this.sessionId,
695
+ limit: 1
696
+ });
697
+ if (!records.length) {
698
+ return null;
699
+ }
700
+ const record = records[0];
701
+ const data = JSON.parse(record.record_content);
702
+ if (data.ttl && record.created_at) {
703
+ const createdTime = new Date(record.created_at).getTime();
704
+ const now = Date.now();
705
+ if (now > createdTime + data.ttl * 1e3) {
706
+ await this.delete(namespace, key);
707
+ return null;
708
+ }
709
+ }
710
+ return {
711
+ namespace,
712
+ key,
713
+ value: data.value,
714
+ createdAt: new Date(record.created_at || Date.now()),
715
+ updatedAt: new Date(record.updated_at || Date.now())
716
+ };
717
+ } catch (error) {
718
+ return null;
719
+ }
720
+ }
721
+ /**
722
+ * Delete an item by namespace and key.
723
+ */
724
+ async delete(namespace, key) {
725
+ if (!this.isSetup && this.ensureTables) {
726
+ await this.setup();
727
+ }
728
+ const storageKey = this.createStorageKey(namespace, key);
729
+ try {
730
+ const { records = [] } = await this.client.searchRecords({
731
+ content: storageKey,
732
+ sessionId: this.sessionId,
733
+ limit: 1
734
+ });
735
+ const record = records[0];
736
+ if (record) {
737
+ await this.client.deleteRecord({
738
+ sessionId: this.sessionId,
739
+ recordId: record.record_id
740
+ });
741
+ }
742
+ } catch (error) {
743
+ }
744
+ }
745
+ /**
746
+ * List namespaces with optional filtering.
747
+ */
748
+ async listNamespaces(options = {}) {
749
+ if (!this.isSetup && this.ensureTables) {
750
+ await this.setup();
751
+ }
752
+ const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;
753
+ try {
754
+ const { records = [] } = await this.client.queryRecords({
755
+ limit: 1e3
756
+ // Large limit to get all records
757
+ });
758
+ const namespaceSet = /* @__PURE__ */ new Set();
759
+ for (const record of records) {
760
+ try {
761
+ const data = JSON.parse(record.record_content);
762
+ if (data.namespace) {
763
+ const namespace = data.namespace;
764
+ if (prefix && prefix.length > 0) {
765
+ const hasPrefix = prefix.every((p, i) => namespace[i] === p);
766
+ if (!hasPrefix) continue;
767
+ }
768
+ if (suffix && suffix.length > 0) {
769
+ const namespaceSuffix = namespace.slice(-suffix.length);
770
+ if (JSON.stringify(namespaceSuffix) !== JSON.stringify(suffix)) {
771
+ continue;
772
+ }
773
+ }
774
+ if (maxDepth !== void 0 && namespace.length > maxDepth) {
775
+ continue;
776
+ }
777
+ namespaceSet.add(JSON.stringify(namespace));
778
+ }
779
+ } catch (error) {
780
+ }
781
+ }
782
+ const namespaces = Array.from(namespaceSet).map((ns) => JSON.parse(ns)).sort().slice(offset, offset + limit);
783
+ return namespaces;
784
+ } catch (error) {
785
+ return [];
786
+ }
787
+ }
788
+ /**
789
+ * Execute multiple operations in a single batch.
790
+ */
791
+ async batch(operations) {
792
+ if (!this.isSetup && this.ensureTables) {
793
+ await this.setup();
794
+ }
795
+ const results = [];
796
+ for (const operation of operations) {
797
+ if ("namespacePrefix" in operation) {
798
+ results.push(await this.executeSearch(operation));
799
+ } else if ("key" in operation && !("value" in operation)) {
800
+ const getOp = operation;
801
+ results.push(await this.get(getOp.namespace, getOp.key));
802
+ } else if ("value" in operation) {
803
+ const putOp = operation;
804
+ if (putOp.value !== null) {
805
+ await this.put(
806
+ putOp.namespace,
807
+ putOp.key,
808
+ putOp.value,
809
+ putOp.index,
810
+ putOp.options
811
+ );
812
+ }
813
+ results.push(void 0);
814
+ } else if ("matchConditions" in operation) {
815
+ const listOp = operation;
816
+ results.push(await this.executeListNamespaces(listOp));
817
+ } else {
818
+ throw new Error(
819
+ `Unsupported operation type: ${JSON.stringify(operation)}`
820
+ );
821
+ }
822
+ }
823
+ return results;
824
+ }
825
+ /**
826
+ * Execute search operation
827
+ */
828
+ async executeSearch(operation) {
829
+ const { namespacePrefix, ...searchOptions } = operation;
830
+ return this.search(namespacePrefix, searchOptions);
831
+ }
832
+ /**
833
+ * Execute list namespaces operation
834
+ */
835
+ async executeListNamespaces(operation) {
836
+ const { matchConditions, maxDepth, limit = 100, offset = 0 } = operation;
837
+ let prefix;
838
+ let suffix;
839
+ if (matchConditions && matchConditions.length > 0) {
840
+ for (const condition of matchConditions) {
841
+ if (condition.matchType === "prefix") {
842
+ prefix = condition.path;
843
+ } else if (condition.matchType === "suffix") {
844
+ suffix = condition.path;
845
+ }
846
+ }
847
+ }
848
+ return this.listNamespaces({
849
+ prefix,
850
+ suffix,
851
+ maxDepth,
852
+ limit,
853
+ offset
854
+ });
855
+ }
856
+ /**
857
+ * Initialize the store.
858
+ */
859
+ async setup() {
860
+ if (this.isSetup) return;
861
+ if (this.ttlConfig?.sweepIntervalMinutes) {
862
+ const intervalMs = this.ttlConfig.sweepIntervalMinutes * 60 * 1e3;
863
+ this.sweepInterval = setInterval(async () => {
864
+ try {
865
+ await this.sweepExpiredItems();
866
+ } catch (error) {
867
+ console.error("Error during TTL sweep:", error);
868
+ }
869
+ }, intervalMs);
870
+ }
871
+ this.isSetup = true;
872
+ }
873
+ /**
874
+ * Start the store.
875
+ */
876
+ async start() {
877
+ if (this.ensureTables && !this.isSetup) {
878
+ await this.setup();
879
+ }
880
+ }
881
+ /**
882
+ * Stop the store and close all connections.
883
+ */
884
+ async stop() {
885
+ if (this.isClosed) return;
886
+ if (this.sweepInterval) {
887
+ clearInterval(this.sweepInterval);
888
+ this.sweepInterval = void 0;
889
+ }
890
+ this.client.close();
891
+ this.isClosed = true;
892
+ }
893
+ /**
894
+ * Manually sweep expired items from the store.
895
+ */
896
+ async sweepExpiredItems() {
897
+ if (!this.isSetup && this.ensureTables) {
898
+ await this.setup();
899
+ }
900
+ try {
901
+ const { records = [] } = await this.client.queryRecords({
902
+ limit: 1e3
903
+ });
904
+ let cleanedCount = 0;
905
+ const now = Date.now();
906
+ for (const record of records) {
907
+ try {
908
+ const data = JSON.parse(record.record_content);
909
+ if (data.ttl && data.createdAt) {
910
+ const createdTime = new Date(data.createdAt).getTime();
911
+ if (now > createdTime + data.ttl * 1e3) {
912
+ await this.client.deleteRecord({
913
+ sessionId: this.sessionId,
914
+ recordId: record.record_id
915
+ });
916
+ cleanedCount++;
917
+ }
918
+ }
919
+ } catch (error) {
920
+ }
921
+ }
922
+ return cleanedCount;
923
+ } catch (error) {
924
+ return 0;
925
+ }
926
+ }
927
+ /**
928
+ * Get statistics about the store.
929
+ */
930
+ async getStats() {
931
+ if (!this.isSetup && this.ensureTables) {
932
+ await this.setup();
933
+ }
934
+ try {
935
+ const { records = [] } = await this.client.queryRecords({
936
+ limit: 1e3
937
+ });
938
+ let totalItems = 0;
939
+ let expiredItems = 0;
940
+ const namespaces = /* @__PURE__ */ new Set();
941
+ const dates = [];
942
+ const now = Date.now();
943
+ for (const record of records) {
944
+ try {
945
+ const data = JSON.parse(record.record_content);
946
+ totalItems++;
947
+ if (data.namespace) {
948
+ namespaces.add(data.namespace.join(":"));
949
+ }
950
+ if (data.createdAt) {
951
+ dates.push(new Date(data.createdAt));
952
+ }
953
+ if (data.ttl && data.createdAt) {
954
+ const createdTime = new Date(data.createdAt).getTime();
955
+ if (now > createdTime + data.ttl * 1e3) {
956
+ expiredItems++;
957
+ }
958
+ }
959
+ } catch (error) {
960
+ }
961
+ }
962
+ const oldestItem = dates.length > 0 ? new Date(Math.min(...dates.map((d) => d.getTime()))) : null;
963
+ const newestItem = dates.length > 0 ? new Date(Math.max(...dates.map((d) => d.getTime()))) : null;
964
+ return {
965
+ totalItems,
966
+ expiredItems,
967
+ namespaceCount: namespaces.size,
968
+ oldestItem,
969
+ newestItem
970
+ };
971
+ } catch (error) {
972
+ return {
973
+ totalItems: 0,
974
+ expiredItems: 0,
975
+ namespaceCount: 0,
976
+ oldestItem: null,
977
+ newestItem: null
978
+ };
979
+ }
980
+ }
981
+ /**
982
+ * Search for items in the store with support for text search and filtering.
983
+ */
984
+ async search(namespacePrefix, options = {}) {
985
+ if (!this.isSetup && this.ensureTables) {
986
+ await this.setup();
987
+ }
988
+ const { filter, query, limit = 10, offset = 0, refreshTtl } = options;
989
+ try {
990
+ const namespaceKey = [...this.namespacePrefix, ...namespacePrefix].join(
991
+ ":"
992
+ );
993
+ let searchResult;
994
+ if (query) {
995
+ searchResult = await this.client.searchRecords({
996
+ content: query,
997
+ limit: limit + offset
998
+ });
999
+ } else {
1000
+ searchResult = await this.client.queryRecords({
1001
+ limit: limit + offset
1002
+ });
1003
+ }
1004
+ const items = [];
1005
+ const now = Date.now();
1006
+ if (searchResult.records) {
1007
+ for (const record of searchResult.records.slice(
1008
+ offset,
1009
+ offset + limit
1010
+ )) {
1011
+ try {
1012
+ const data = JSON.parse(record.record_content);
1013
+ if (namespaceKey && !data.storageKey?.startsWith(namespaceKey)) {
1014
+ continue;
1015
+ }
1016
+ if (data.ttl && data.createdAt) {
1017
+ const createdTime = new Date(data.createdAt).getTime();
1018
+ if (now > createdTime + data.ttl * 1e3) {
1019
+ continue;
1020
+ }
1021
+ }
1022
+ if (filter) {
1023
+ let matches = true;
1024
+ for (const [key, value] of Object.entries(filter)) {
1025
+ if (data.value[key] !== value) {
1026
+ matches = false;
1027
+ break;
1028
+ }
1029
+ }
1030
+ if (!matches) continue;
1031
+ }
1032
+ const item = {
1033
+ namespace: data.namespace,
1034
+ key: data.key,
1035
+ value: data.value,
1036
+ createdAt: new Date(record.created_at || Date.now()),
1037
+ updatedAt: new Date(record.updated_at || Date.now())
1038
+ };
1039
+ items.push(item);
1040
+ if (refreshTtl && this.ttlConfig?.defaultTtlSeconds) {
1041
+ await this.put(data.namespace, data.key, data.value, void 0, {
1042
+ ttl: this.ttlConfig.defaultTtlSeconds
1043
+ });
1044
+ }
1045
+ } catch (error) {
1046
+ }
1047
+ }
1048
+ }
1049
+ return items;
1050
+ } catch (error) {
1051
+ return [];
1052
+ }
1053
+ }
1054
+ };
1055
+ export {
1056
+ AGKitPropertiesAnnotation,
1057
+ AGKitStateAnnotation,
1058
+ LanggraphAgent,
1059
+ TDAISaver,
1060
+ TDAIStore
1061
+ };
1062
+ //# sourceMappingURL=index.mjs.map