@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/README.md +121 -0
- package/dist/index.d.mts +79 -2
- package/dist/index.d.ts +79 -2
- package/dist/index.js +476 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +478 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -9
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 =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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:
|
|
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
|
|
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,
|