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