@cloudbase/agent-adapter-langgraph 0.0.15 → 0.0.18

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 CHANGED
@@ -6,8 +6,7 @@ import {
6
6
  import { Observable } from "rxjs";
7
7
  import {
8
8
  Annotation,
9
- MessagesAnnotation,
10
- Command
9
+ MessagesAnnotation
11
10
  } from "@langchain/langgraph";
12
11
 
13
12
  // src/util.ts
@@ -59,6 +58,7 @@ function convertJsonSchemaToZodSchema(jsonSchema, required) {
59
58
 
60
59
  // src/agent.ts
61
60
  import { noopLogger, isErrorWithCode } from "@cloudbase/agent-shared";
61
+ var LangChainCallbackHandler;
62
62
  var ClientPropertiesAnnotation = Annotation.Root({
63
63
  tools: Annotation
64
64
  });
@@ -70,6 +70,7 @@ var LanggraphAgent = class extends AbstractAgent {
70
70
  constructor(agentConfig) {
71
71
  super(agentConfig);
72
72
  this.compiledWorkflow = agentConfig.compiledWorkflow;
73
+ this.adapterName = agentConfig.adapterName || "LangGraph";
73
74
  const baseLogger = agentConfig.logger ?? noopLogger;
74
75
  this.logger = baseLogger.child?.({ component: "langgraph-agent" }) ?? baseLogger;
75
76
  }
@@ -82,6 +83,7 @@ var LanggraphAgent = class extends AbstractAgent {
82
83
  const { messages, runId, threadId } = input;
83
84
  const logger = this.logger.child?.({ runId, threadId }) ?? this.logger;
84
85
  logger.info?.("Run started");
86
+ await this.setupObservability(input, logger);
85
87
  const runStartedEvent = {
86
88
  type: EventType.RUN_STARTED,
87
89
  threadId,
@@ -89,11 +91,9 @@ var LanggraphAgent = class extends AbstractAgent {
89
91
  };
90
92
  logger.trace?.({ aguiEvent: runStartedEvent }, "Emitting AGUI event");
91
93
  subscriber.next(runStartedEvent);
92
- const isResume = !!input.forwardedProps?.resume;
93
94
  const lastUserMessage = messages.filter((m) => m.role === "user").pop();
94
95
  logger.debug?.(
95
96
  {
96
- isResume,
97
97
  messageCount: messages.length,
98
98
  toolCount: input.tools?.length ?? 0,
99
99
  tools: input.tools?.map((t) => t.name),
@@ -102,25 +102,21 @@ var LanggraphAgent = class extends AbstractAgent {
102
102
  "Preparing stream input"
103
103
  );
104
104
  logger.trace?.({ messages, tools: input.tools }, "Full input messages");
105
- const langChainMessages = isResume ? void 0 : aguiMessagesToLangChain(messages);
106
- if (langChainMessages) {
107
- logger.trace?.(
108
- {
109
- langChainMessages,
110
- messageCount: langChainMessages.length,
111
- messageTypes: langChainMessages.map((m) => ({
112
- type: m.type,
113
- id: m.id,
114
- hasToolCalls: "tool_calls" in m && Array.isArray(m.tool_calls) && m.tool_calls.length > 0,
115
- toolCallId: "tool_call_id" in m ? m.tool_call_id : void 0
116
- }))
117
- },
118
- "Converted LangGraph messages"
119
- );
120
- }
121
- const streamEventInput = isResume ? new Command({
122
- resume: JSON.stringify(input.forwardedProps?.resume?.payload)
123
- }) : {
105
+ const langChainMessages = aguiMessagesToLangChain(messages, logger);
106
+ logger.trace?.(
107
+ {
108
+ langChainMessages,
109
+ messageCount: langChainMessages.length,
110
+ messageTypes: langChainMessages.map((m) => ({
111
+ type: m.type,
112
+ id: m.id,
113
+ hasToolCalls: "tool_calls" in m && Array.isArray(m.tool_calls) && m.tool_calls.length > 0,
114
+ toolCallId: "tool_call_id" in m ? m.tool_call_id : void 0
115
+ }))
116
+ },
117
+ "Converted LangGraph messages"
118
+ );
119
+ const streamEventInput = {
124
120
  messages: langChainMessages,
125
121
  client: {
126
122
  tools: convertActionsToDynamicStructuredTools(
@@ -133,6 +129,7 @@ var LanggraphAgent = class extends AbstractAgent {
133
129
  };
134
130
  const stream = this.compiledWorkflow.streamEvents(streamEventInput, {
135
131
  version: "v2",
132
+ ...this.observabilityCallback ? { callbacks: [this.observabilityCallback] } : {},
136
133
  runId,
137
134
  configurable: {
138
135
  thread_id: threadId
@@ -152,7 +149,6 @@ var LanggraphAgent = class extends AbstractAgent {
152
149
  "Pre-populated handled tool call IDs from input messages"
153
150
  );
154
151
  }
155
- let interrupt;
156
152
  let currentToolCall = null;
157
153
  let eventCount = 0;
158
154
  let toolCallCount = 0;
@@ -477,46 +473,17 @@ var LanggraphAgent = class extends AbstractAgent {
477
473
  "State update with messages"
478
474
  );
479
475
  }
480
- if (chunk?.__interrupt__ && Array.isArray(chunk.__interrupt__) && chunk.__interrupt__.length > 0) {
481
- const rawInterrupt = chunk.__interrupt__[0];
482
- logger.debug?.(
483
- { interruptId: rawInterrupt.id },
484
- "Interrupt received"
485
- );
486
- interrupt = {
487
- id: rawInterrupt.id,
488
- // TODO: replace with actual reason
489
- reason: "agent requested interrupt",
490
- payload: rawInterrupt.value
491
- };
492
- }
493
476
  }
494
477
  }
495
478
  const stats = { eventCount, toolCallCount, textChunkCount };
496
- if (interrupt) {
497
- const runFinishedEvent = {
498
- type: EventType.RUN_FINISHED,
499
- threadId,
500
- runId,
501
- outcome: "interrupt",
502
- interrupt
503
- };
504
- logger.info?.(
505
- { outcome: "interrupt", interruptId: interrupt.id, ...stats },
506
- "Run finished with interrupt"
507
- );
508
- logger.trace?.({ aguiEvent: runFinishedEvent }, "Emitting AGUI event");
509
- subscriber.next(runFinishedEvent);
510
- } else {
511
- const runFinishedEvent = {
512
- type: EventType.RUN_FINISHED,
513
- threadId,
514
- runId
515
- };
516
- logger.info?.({ outcome: "complete", ...stats }, "Run finished");
517
- logger.trace?.({ aguiEvent: runFinishedEvent }, "Emitting AGUI event");
518
- subscriber.next(runFinishedEvent);
519
- }
479
+ const runFinishedEvent = {
480
+ type: EventType.RUN_FINISHED,
481
+ threadId,
482
+ runId
483
+ };
484
+ logger.info?.({ outcome: "complete", ...stats }, "Run finished");
485
+ logger.trace?.({ aguiEvent: runFinishedEvent }, "Emitting AGUI event");
486
+ subscriber.next(runFinishedEvent);
520
487
  } catch (error) {
521
488
  logger.error?.(
522
489
  { err: error, eventCount, toolCallCount, textChunkCount },
@@ -542,9 +509,56 @@ var LanggraphAgent = class extends AbstractAgent {
542
509
  cloned.logger = logger;
543
510
  return cloned;
544
511
  }
512
+ /**
513
+ * Setup observability for agent execution.
514
+ * Lazy loads observability callback, restores server context from forwardedProps,
515
+ * and configures the callback for graph execution.
516
+ */
517
+ async setupObservability(input, logger) {
518
+ if (!LangChainCallbackHandler) {
519
+ try {
520
+ logger.debug?.("Attempting to load observability...");
521
+ const obsModule = await import("@cloudbase/agent-observability/langchain");
522
+ LangChainCallbackHandler = obsModule.CallbackHandler;
523
+ logger.debug?.("\u2713 Observability handler loaded");
524
+ } catch (e) {
525
+ logger.debug?.(
526
+ "\u2717 Observability not available:",
527
+ e instanceof Error ? e.message : String(e)
528
+ );
529
+ }
530
+ }
531
+ this.observabilityCallback = LangChainCallbackHandler ? new LangChainCallbackHandler({
532
+ adapterName: this.adapterName,
533
+ logger
534
+ }) : void 0;
535
+ if (this.observabilityCallback && input.forwardedProps?.__agui_server_context) {
536
+ try {
537
+ const serverContextData = input.forwardedProps.__agui_server_context;
538
+ const serverSpanContext = {
539
+ traceId: serverContextData.traceId,
540
+ spanId: serverContextData.spanId,
541
+ traceFlags: serverContextData.traceFlags,
542
+ isRemote: false
543
+ };
544
+ this.observabilityCallback.setExternalParentContext(serverSpanContext, {
545
+ threadId: input.threadId,
546
+ runId: input.runId
547
+ });
548
+ logger.debug?.("\u2713 Server context restored:", {
549
+ traceId: serverSpanContext.traceId,
550
+ spanId: serverSpanContext.spanId,
551
+ threadId: input.threadId,
552
+ runId: input.runId
553
+ });
554
+ } catch (e) {
555
+ logger.debug?.("Failed to restore server context:", e);
556
+ }
557
+ }
558
+ }
545
559
  };
546
- function aguiMessagesToLangChain(messages) {
547
- return messages.map((message, index) => {
560
+ function aguiMessagesToLangChain(messages, logger) {
561
+ return messages.map((message) => {
548
562
  switch (message.role) {
549
563
  case "user":
550
564
  if (typeof message.content === "string") {
@@ -555,10 +569,34 @@ function aguiMessagesToLangChain(messages) {
555
569
  type: "human"
556
570
  };
557
571
  } else {
572
+ const langChainContent = [];
573
+ for (const part of message.content) {
574
+ if (part.type === "text") {
575
+ langChainContent.push({ type: "text", text: part.text });
576
+ } else if (part.type === "binary") {
577
+ const imageUrl = convertBinaryToImageUrl(
578
+ part,
579
+ message.id,
580
+ logger
581
+ );
582
+ if (imageUrl) {
583
+ langChainContent.push({
584
+ type: "image_url",
585
+ image_url: { url: imageUrl }
586
+ });
587
+ }
588
+ }
589
+ }
590
+ if (langChainContent.length === 0) {
591
+ logger?.warn?.(
592
+ { messageId: message.id },
593
+ "User message content array resulted in empty LangChain content"
594
+ );
595
+ }
558
596
  return {
559
597
  id: message.id,
560
598
  role: message.role,
561
- content: message.content.filter((m) => m.type === "text"),
599
+ content: langChainContent,
562
600
  type: "human"
563
601
  };
564
602
  }
@@ -603,6 +641,22 @@ function isValidJson(json) {
603
641
  return false;
604
642
  }
605
643
  }
644
+ function convertBinaryToImageUrl(part, messageId, logger) {
645
+ if (!part.mimeType.startsWith("image/")) {
646
+ logger?.debug?.(
647
+ { mimeType: part.mimeType, messageId },
648
+ "Skipping non-image binary content"
649
+ );
650
+ return void 0;
651
+ }
652
+ if (part.url) {
653
+ return part.url;
654
+ }
655
+ if (part.data) {
656
+ return `data:${part.mimeType};base64,${part.data}`;
657
+ }
658
+ return void 0;
659
+ }
606
660
 
607
661
  // src/checkpoint.ts
608
662
  import {
@@ -859,11 +913,368 @@ var TDAISaver = class extends BaseCheckpointSaver {
859
913
  }
860
914
  };
861
915
 
916
+ // src/cloudbase-saver.ts
917
+ import {
918
+ BaseCheckpointSaver as BaseCheckpointSaver3
919
+ } from "@langchain/langgraph";
920
+
921
+ // ../../../node_modules/.pnpm/@langchain+langgraph-checkpoint@1.0.0_@langchain+core@1.1.16_@opentelemetry+api@1.9.0_@_6d00458ac2f8320fcf28949c4b5d31a3/node_modules/@langchain/langgraph-checkpoint/dist/serde/types.js
922
+ var ERROR = "__error__";
923
+ var SCHEDULED = "__scheduled__";
924
+ var INTERRUPT = "__interrupt__";
925
+ var RESUME = "__resume__";
926
+
927
+ // ../../../node_modules/.pnpm/@langchain+langgraph-checkpoint@1.0.0_@langchain+core@1.1.16_@opentelemetry+api@1.9.0_@_6d00458ac2f8320fcf28949c4b5d31a3/node_modules/@langchain/langgraph-checkpoint/dist/serde/jsonplus.js
928
+ import { load } from "@langchain/core/load";
929
+
930
+ // ../../../node_modules/.pnpm/@langchain+langgraph-checkpoint@1.0.0_@langchain+core@1.1.16_@opentelemetry+api@1.9.0_@_6d00458ac2f8320fcf28949c4b5d31a3/node_modules/@langchain/langgraph-checkpoint/dist/base.js
931
+ function deepCopy(obj) {
932
+ if (typeof obj !== "object" || obj === null) return obj;
933
+ const newObj = Array.isArray(obj) ? [] : {};
934
+ for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = deepCopy(obj[key]);
935
+ return newObj;
936
+ }
937
+ function copyCheckpoint(checkpoint) {
938
+ return {
939
+ v: checkpoint.v,
940
+ id: checkpoint.id,
941
+ ts: checkpoint.ts,
942
+ channel_values: { ...checkpoint.channel_values },
943
+ channel_versions: { ...checkpoint.channel_versions },
944
+ versions_seen: deepCopy(checkpoint.versions_seen)
945
+ };
946
+ }
947
+ var WRITES_IDX_MAP = {
948
+ [ERROR]: -1,
949
+ [SCHEDULED]: -2,
950
+ [INTERRUPT]: -3,
951
+ [RESUME]: -4
952
+ };
953
+
954
+ // src/cloudbase-saver.ts
955
+ var CloudBaseSaver = class extends BaseCheckpointSaver3 {
956
+ constructor(config) {
957
+ super();
958
+ this.db = config.db;
959
+ this.userId = config.userId;
960
+ this.agentId = config.agentId ?? "default";
961
+ this.checkpointsCollection = config.checkpointsCollection ?? "checkpoints";
962
+ this.writesCollection = config.writesCollection ?? "checkpoint_writes";
963
+ }
964
+ /**
965
+ * Setup the required collections in CloudBase.
966
+ * Call this once before using the saver. Safe to call multiple times.
967
+ */
968
+ async setup() {
969
+ const createIfNotExists = async (name) => {
970
+ try {
971
+ await this.db.createCollection(name);
972
+ } catch (e) {
973
+ const error = e;
974
+ if (error.code !== "DATABASE_COLLECTION_ALREADY_EXIST") {
975
+ throw e;
976
+ }
977
+ }
978
+ };
979
+ await Promise.all([
980
+ createIfNotExists(this.checkpointsCollection),
981
+ createIfNotExists(this.writesCollection)
982
+ ]);
983
+ }
984
+ /**
985
+ * Serialize data to JSON object using serde.
986
+ * CloudBase is a document database, so we store JSON directly instead of Base64.
987
+ * Binary data (Uint8Array) is not supported - fail early if encountered.
988
+ */
989
+ async serialize(data) {
990
+ const [type, bytes] = await this.serde.dumpsTyped(data);
991
+ if (type === "bytes") {
992
+ throw new Error(
993
+ "Binary data (Uint8Array) is not supported in CloudBaseSaver. CloudBase is a document database that only supports JSON-serializable data."
994
+ );
995
+ }
996
+ const jsonString = new TextDecoder().decode(bytes);
997
+ return JSON.parse(jsonString);
998
+ }
999
+ /**
1000
+ * Deserialize JSON object back to data using serde.
1001
+ */
1002
+ async deserialize(data) {
1003
+ const jsonString = JSON.stringify(data);
1004
+ const bytes = new TextEncoder().encode(jsonString);
1005
+ return this.serde.loadsTyped("json", bytes);
1006
+ }
1007
+ /**
1008
+ * Retrieves a checkpoint from CloudBase based on the provided config.
1009
+ */
1010
+ async getTuple(config) {
1011
+ const {
1012
+ thread_id,
1013
+ checkpoint_ns = "",
1014
+ checkpoint_id
1015
+ } = config.configurable ?? {};
1016
+ if (!thread_id) {
1017
+ return void 0;
1018
+ }
1019
+ const query = {
1020
+ _openid: this.userId,
1021
+ agent_id: this.agentId,
1022
+ thread_id,
1023
+ checkpoint_ns
1024
+ };
1025
+ if (checkpoint_id) {
1026
+ query.checkpoint_id = checkpoint_id;
1027
+ }
1028
+ const { data: docs } = await this.db.collection(this.checkpointsCollection).where(query).orderBy("checkpoint_id", "desc").limit(1).get();
1029
+ if (docs.length === 0) {
1030
+ return void 0;
1031
+ }
1032
+ const doc = docs[0];
1033
+ const checkpointId = doc.checkpoint_id;
1034
+ const checkpoint = await this.deserialize(doc.checkpoint);
1035
+ const metadata = await this.deserialize(
1036
+ doc.metadata
1037
+ );
1038
+ const writeDocs = [];
1039
+ let writeSkip = 0;
1040
+ const writePageSize = 100;
1041
+ while (true) {
1042
+ const { data: docs2 } = await this.db.collection(this.writesCollection).where({
1043
+ _openid: this.userId,
1044
+ agent_id: this.agentId,
1045
+ thread_id,
1046
+ checkpoint_ns,
1047
+ checkpoint_id: checkpointId
1048
+ }).orderBy("idx", "asc").skip(writeSkip).limit(writePageSize).get();
1049
+ writeDocs.push(...docs2);
1050
+ if (docs2.length < writePageSize) break;
1051
+ writeSkip += docs2.length;
1052
+ }
1053
+ const pendingWrites = await Promise.all(
1054
+ writeDocs.map(async (writeDoc) => {
1055
+ const value = await this.deserialize(writeDoc.value);
1056
+ return [
1057
+ writeDoc.task_id,
1058
+ writeDoc.channel,
1059
+ value
1060
+ ];
1061
+ })
1062
+ );
1063
+ return {
1064
+ config: {
1065
+ configurable: {
1066
+ thread_id,
1067
+ checkpoint_ns,
1068
+ checkpoint_id: checkpointId
1069
+ }
1070
+ },
1071
+ checkpoint,
1072
+ metadata,
1073
+ pendingWrites,
1074
+ parentConfig: doc.parent_checkpoint_id ? {
1075
+ configurable: {
1076
+ thread_id,
1077
+ checkpoint_ns,
1078
+ checkpoint_id: doc.parent_checkpoint_id
1079
+ }
1080
+ } : void 0
1081
+ };
1082
+ }
1083
+ /**
1084
+ * List checkpoints from CloudBase, ordered by checkpoint_id descending.
1085
+ */
1086
+ async *list(config, options) {
1087
+ const { limit, before, filter } = options ?? {};
1088
+ const thread_id = config.configurable?.thread_id;
1089
+ const checkpoint_ns = config.configurable?.checkpoint_ns;
1090
+ if (!thread_id) {
1091
+ throw new Error("Thread ID is required");
1092
+ }
1093
+ const _ = this.db.command;
1094
+ const query = {
1095
+ _openid: this.userId,
1096
+ agent_id: this.agentId,
1097
+ thread_id
1098
+ };
1099
+ if (checkpoint_ns !== void 0 && checkpoint_ns !== null) {
1100
+ query.checkpoint_ns = checkpoint_ns;
1101
+ }
1102
+ if (before?.configurable?.checkpoint_id) {
1103
+ query.checkpoint_id = _.lt(before.configurable.checkpoint_id);
1104
+ }
1105
+ const validCheckpointMetadataKeys = ["source", "step", "writes", "parents"];
1106
+ if (filter) {
1107
+ for (const [key, value] of Object.entries(filter)) {
1108
+ if (value !== void 0 && validCheckpointMetadataKeys.includes(key)) {
1109
+ query[`metadata.${key}`] = value;
1110
+ }
1111
+ }
1112
+ }
1113
+ let skip = 0;
1114
+ const pageSize = Math.min(limit ?? 100, 100);
1115
+ let yielded = 0;
1116
+ while (true) {
1117
+ const { data: docs } = await this.db.collection(this.checkpointsCollection).where(query).orderBy("checkpoint_id", "desc").skip(skip).limit(pageSize).get();
1118
+ if (docs.length === 0) break;
1119
+ for (const doc of docs) {
1120
+ const checkpoint = await this.deserialize(
1121
+ doc.checkpoint
1122
+ );
1123
+ const metadata = await this.deserialize(
1124
+ doc.metadata
1125
+ );
1126
+ const writeDocs = [];
1127
+ let writeSkip = 0;
1128
+ const writePageSize = 100;
1129
+ while (true) {
1130
+ const { data: pageWriteDocs } = await this.db.collection(this.writesCollection).where({
1131
+ _openid: this.userId,
1132
+ agent_id: this.agentId,
1133
+ thread_id: doc.thread_id,
1134
+ checkpoint_ns: doc.checkpoint_ns,
1135
+ checkpoint_id: doc.checkpoint_id
1136
+ }).orderBy("idx", "asc").skip(writeSkip).limit(writePageSize).get();
1137
+ writeDocs.push(...pageWriteDocs);
1138
+ if (pageWriteDocs.length < writePageSize) break;
1139
+ writeSkip += pageWriteDocs.length;
1140
+ }
1141
+ const pendingWrites = await Promise.all(
1142
+ writeDocs.map(async (writeDoc) => {
1143
+ const value = await this.deserialize(writeDoc.value);
1144
+ return [
1145
+ writeDoc.task_id,
1146
+ writeDoc.channel,
1147
+ value
1148
+ ];
1149
+ })
1150
+ );
1151
+ yield {
1152
+ config: {
1153
+ configurable: {
1154
+ thread_id: doc.thread_id,
1155
+ checkpoint_ns: doc.checkpoint_ns,
1156
+ checkpoint_id: doc.checkpoint_id
1157
+ }
1158
+ },
1159
+ checkpoint,
1160
+ metadata,
1161
+ pendingWrites,
1162
+ parentConfig: doc.parent_checkpoint_id ? {
1163
+ configurable: {
1164
+ thread_id: doc.thread_id,
1165
+ checkpoint_ns: doc.checkpoint_ns,
1166
+ checkpoint_id: doc.parent_checkpoint_id
1167
+ }
1168
+ } : void 0
1169
+ };
1170
+ yielded++;
1171
+ if (limit && yielded >= limit) return;
1172
+ }
1173
+ skip += docs.length;
1174
+ if (docs.length < pageSize) break;
1175
+ }
1176
+ }
1177
+ /**
1178
+ * Save a checkpoint to CloudBase.
1179
+ */
1180
+ async put(config, checkpoint, metadata) {
1181
+ if (!config.configurable) {
1182
+ throw new Error("Empty configuration supplied.");
1183
+ }
1184
+ const thread_id = config.configurable.thread_id;
1185
+ const checkpoint_ns = config.configurable.checkpoint_ns ?? "";
1186
+ const checkpoint_id = checkpoint.id;
1187
+ if (!thread_id) {
1188
+ throw new Error(
1189
+ 'Missing "thread_id" field in passed "config.configurable".'
1190
+ );
1191
+ }
1192
+ const preparedCheckpoint = copyCheckpoint(checkpoint);
1193
+ const serializedCheckpoint = await this.serialize(preparedCheckpoint);
1194
+ const serializedMetadata = await this.serialize(metadata);
1195
+ const doc = {
1196
+ _openid: this.userId,
1197
+ agent_id: this.agentId,
1198
+ thread_id,
1199
+ checkpoint_ns,
1200
+ checkpoint_id,
1201
+ parent_checkpoint_id: config.configurable.checkpoint_id ?? null,
1202
+ checkpoint: serializedCheckpoint,
1203
+ metadata: serializedMetadata,
1204
+ created_at: /* @__PURE__ */ new Date()
1205
+ };
1206
+ const docId = `${this.agentId}:${thread_id}:${checkpoint_ns}:${checkpoint_id}`;
1207
+ await this.db.collection(this.checkpointsCollection).doc(docId).set(doc);
1208
+ return {
1209
+ configurable: {
1210
+ thread_id,
1211
+ checkpoint_ns,
1212
+ checkpoint_id
1213
+ }
1214
+ };
1215
+ }
1216
+ /**
1217
+ * Save intermediate writes to CloudBase.
1218
+ */
1219
+ async putWrites(config, writes, taskId) {
1220
+ if (!config.configurable) {
1221
+ throw new Error("Empty configuration supplied.");
1222
+ }
1223
+ if (!config.configurable.thread_id) {
1224
+ throw new Error("Missing thread_id field in config.configurable.");
1225
+ }
1226
+ if (!config.configurable.checkpoint_id) {
1227
+ throw new Error("Missing checkpoint_id field in config.configurable.");
1228
+ }
1229
+ const thread_id = config.configurable.thread_id;
1230
+ const checkpoint_ns = config.configurable.checkpoint_ns ?? "";
1231
+ const checkpoint_id = config.configurable.checkpoint_id;
1232
+ const serializedWrites = await Promise.all(
1233
+ writes.map(async ([channel, value], idx) => ({
1234
+ channel,
1235
+ value: await this.serialize(value),
1236
+ idx,
1237
+ docId: `${this.agentId}:${thread_id}:${checkpoint_ns}:${checkpoint_id}:${taskId}:${idx}`
1238
+ }))
1239
+ );
1240
+ await this.db.runTransaction(async (transaction) => {
1241
+ for (const { channel, value, idx, docId } of serializedWrites) {
1242
+ await transaction.collection(this.writesCollection).doc(docId).set({
1243
+ _openid: this.userId,
1244
+ agent_id: this.agentId,
1245
+ thread_id,
1246
+ checkpoint_ns,
1247
+ checkpoint_id,
1248
+ task_id: taskId,
1249
+ idx,
1250
+ channel,
1251
+ value
1252
+ });
1253
+ }
1254
+ });
1255
+ }
1256
+ /**
1257
+ * Delete all checkpoints and writes for a thread.
1258
+ */
1259
+ async deleteThread(threadId) {
1260
+ await this.db.collection(this.checkpointsCollection).where({
1261
+ _openid: this.userId,
1262
+ agent_id: this.agentId,
1263
+ thread_id: threadId
1264
+ }).remove();
1265
+ await this.db.collection(this.writesCollection).where({
1266
+ _openid: this.userId,
1267
+ agent_id: this.agentId,
1268
+ thread_id: threadId
1269
+ }).remove();
1270
+ }
1271
+ };
1272
+
862
1273
  // src/store/tdai-store.ts
863
1274
  import {
864
- BaseStore
1275
+ BaseStore as BaseStore2
865
1276
  } from "@langchain/langgraph";
866
- var TDAIStore = class extends BaseStore {
1277
+ var TDAIStore = class extends BaseStore2 {
867
1278
  constructor(config) {
868
1279
  super();
869
1280
  this.isSetup = false;
@@ -1292,6 +1703,7 @@ import { noopLogger as noopLogger2, createConsoleLogger } from "@cloudbase/agent
1292
1703
  export {
1293
1704
  ClientPropertiesAnnotation,
1294
1705
  ClientStateAnnotation,
1706
+ CloudBaseSaver,
1295
1707
  LanggraphAgent,
1296
1708
  TDAISaver,
1297
1709
  TDAIStore,