@adminforth/agent 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ import type { RunnableConfig } from "@langchain/core/runnables";
2
+ import {
3
+ BaseCheckpointSaver,
4
+ type Checkpoint,
5
+ type CheckpointPendingWrite,
6
+ type CheckpointMetadata,
7
+ type CheckpointTuple,
8
+ type PendingWrite,
9
+ WRITES_IDX_MAP,
10
+ } from "@langchain/langgraph-checkpoint";
11
+ import type { PluginOptions } from "../types.js";
12
+ import { Filters } from "adminforth";
13
+
14
+ const ROOT_CHECKPOINT_NAMESPACE = "__root__";
15
+
16
+ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
17
+ constructor(
18
+ private readonly adminforth: any,
19
+ private readonly pluginOptions: PluginOptions,
20
+ ) {
21
+ super();
22
+ }
23
+
24
+ private get resourceConfig() {
25
+ const resource = this.pluginOptions.checkpointResource;
26
+ if (!resource) {
27
+ throw new Error("checkpointResource is not configured");
28
+ }
29
+ return resource;
30
+ }
31
+
32
+ private resource() {
33
+ return this.adminforth.resource(this.resourceConfig.resourceId);
34
+ }
35
+
36
+ private serialize(value: unknown): string | null {
37
+ if (value === undefined || value === null) return null;
38
+ return JSON.stringify(value);
39
+ }
40
+
41
+ private deserialize<T>(value: unknown): T | null {
42
+ if (value === undefined || value === null) return null;
43
+ if (typeof value === "string") {
44
+ return JSON.parse(value) as T;
45
+ }
46
+ return value as T;
47
+ }
48
+
49
+ private now(): string {
50
+ return new Date().toISOString();
51
+ }
52
+
53
+ private encodeCheckpointNamespace(checkpointNs: string): string {
54
+ return checkpointNs === "" ? ROOT_CHECKPOINT_NAMESPACE : checkpointNs;
55
+ }
56
+
57
+ private decodeCheckpointNamespace(checkpointNs: unknown): string {
58
+ return checkpointNs === ROOT_CHECKPOINT_NAMESPACE ? "" : String(checkpointNs ?? "");
59
+ }
60
+
61
+ private getConfigValues(config: RunnableConfig) {
62
+ const configurable = (config.configurable ?? {}) as Record<string, unknown>;
63
+
64
+ return {
65
+ threadId: String(configurable.thread_id ?? ""),
66
+ checkpointNs: String(configurable.checkpoint_ns ?? ""),
67
+ checkpointId: configurable.checkpoint_id
68
+ ? String(configurable.checkpoint_id)
69
+ : null,
70
+ };
71
+ }
72
+
73
+ private buildConfig(threadId: string, checkpointNs: string, checkpointId: string): RunnableConfig {
74
+ return {
75
+ configurable: {
76
+ thread_id: threadId,
77
+ checkpoint_ns: checkpointNs,
78
+ checkpoint_id: checkpointId,
79
+ },
80
+ };
81
+ }
82
+
83
+ private buildCheckpointRowId(threadId: string, checkpointNs: string, checkpointId: string) {
84
+ return `cp:${threadId}:${checkpointNs}:${checkpointId}`;
85
+ }
86
+
87
+ private buildWritesRowId(
88
+ threadId: string,
89
+ checkpointNs: string,
90
+ checkpointId: string,
91
+ taskId: string,
92
+ seq: number,
93
+ ) {
94
+ return `wr:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:${seq}`;
95
+ }
96
+
97
+ private getWriteIndex(channel: string, index: number): number {
98
+ return WRITES_IDX_MAP[channel] ?? index;
99
+ }
100
+
101
+ private isDuplicateCheckpointWriteError(error: unknown): boolean {
102
+ return error instanceof Error && error.message.includes("UNIQUE constraint failed");
103
+ }
104
+
105
+ async put(
106
+ config: RunnableConfig,
107
+ checkpoint: Checkpoint,
108
+ metadata: CheckpointMetadata,
109
+ _newVersions: Record<string, unknown>,
110
+ ): Promise<RunnableConfig> {
111
+ const r = this.resourceConfig;
112
+ const { threadId, checkpointNs } = this.getConfigValues(config);
113
+ const checkpointId = String((checkpoint as any).id);
114
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
115
+
116
+ const parentCheckpointId = this.getConfigValues(config).checkpointId;
117
+ const createdAt = this.now();
118
+
119
+ await this.resource().create({
120
+ [r.idField]: this.buildCheckpointRowId(threadId, storedCheckpointNs, checkpointId),
121
+ [r.threadIdField]: threadId,
122
+ [r.checkpointNamespaceField]: storedCheckpointNs,
123
+ [r.checkpointIdField]: checkpointId,
124
+ [r.parentCheckpointIdField]: parentCheckpointId,
125
+ [r.rowKindField]: "checkpoint",
126
+ [r.taskIdField]: null,
127
+ [r.sequenceField]: 0,
128
+ [r.createdAtField]: createdAt,
129
+ [r.checkpointPayloadField]: this.serialize(checkpoint),
130
+ [r.metadataPayloadField]: this.serialize(metadata),
131
+ [r.writesPayloadField]: null,
132
+ [r.schemaVersionField]: 1,
133
+ });
134
+
135
+ return this.buildConfig(threadId, checkpointNs, checkpointId);
136
+ }
137
+
138
+ async putWrites(
139
+ config: RunnableConfig,
140
+ writes: PendingWrite[],
141
+ taskId: string,
142
+ ): Promise<void> {
143
+ const r = this.resourceConfig;
144
+ const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
145
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
146
+
147
+ if (!checkpointId) {
148
+ throw new Error("putWrites requires checkpoint_id in config");
149
+ }
150
+
151
+ const createdAt = this.now();
152
+
153
+ await Promise.all(
154
+ writes.map(async ([channel, value], index) => {
155
+ const writeIndex = this.getWriteIndex(channel, index);
156
+
157
+ try {
158
+ await this.resource().create({
159
+ [r.idField]: this.buildWritesRowId(
160
+ threadId,
161
+ storedCheckpointNs,
162
+ checkpointId,
163
+ taskId,
164
+ writeIndex,
165
+ ),
166
+ [r.threadIdField]: threadId,
167
+ [r.checkpointNamespaceField]: storedCheckpointNs,
168
+ [r.checkpointIdField]: checkpointId,
169
+ [r.parentCheckpointIdField]: null,
170
+ [r.rowKindField]: "writes",
171
+ [r.taskIdField]: taskId,
172
+ [r.sequenceField]: writeIndex,
173
+ [r.createdAtField]: createdAt,
174
+ [r.checkpointPayloadField]: null,
175
+ [r.metadataPayloadField]: null,
176
+ [r.writesPayloadField]: this.serialize([channel, value] satisfies PendingWrite),
177
+ [r.schemaVersionField]: 1,
178
+ });
179
+ } catch (error) {
180
+ if (!this.isDuplicateCheckpointWriteError(error)) {
181
+ throw error;
182
+ }
183
+ }
184
+ }),
185
+ );
186
+ }
187
+
188
+ async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
189
+ const r = this.resourceConfig;
190
+ const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
191
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
192
+
193
+ const checkpointRows = await this.resource().list(
194
+ checkpointId
195
+ ? Filters.AND(
196
+ Filters.EQ(r.threadIdField, threadId),
197
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
198
+ Filters.EQ(r.checkpointIdField, checkpointId),
199
+ Filters.EQ(r.rowKindField, "checkpoint"),
200
+ )
201
+ : Filters.AND(
202
+ Filters.EQ(r.threadIdField, threadId),
203
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
204
+ Filters.EQ(r.rowKindField, "checkpoint"),
205
+ ),
206
+ 1,
207
+ undefined,
208
+ [{ field: r.checkpointIdField, direction: "desc" }],
209
+ );
210
+
211
+ const checkpointRow = checkpointRows[0];
212
+ if (!checkpointRow) {
213
+ return undefined;
214
+ }
215
+
216
+ const resolvedCheckpointId = String(checkpointRow[r.checkpointIdField]);
217
+
218
+ const writesRows = await this.resource().list(
219
+ Filters.AND(
220
+ Filters.EQ(r.threadIdField, threadId),
221
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
222
+ Filters.EQ(r.checkpointIdField, resolvedCheckpointId),
223
+ Filters.EQ(r.rowKindField, "writes"),
224
+ ),
225
+ undefined,
226
+ undefined,
227
+ [{ field: r.sequenceField, direction: "asc" }],
228
+ );
229
+
230
+ const pendingWrites: CheckpointPendingWrite[] = writesRows.flatMap((row) => {
231
+ const taskId = String(row[r.taskIdField] ?? "");
232
+ const write = this.deserialize<PendingWrite>(row[r.writesPayloadField]);
233
+ if (!write) {
234
+ return [];
235
+ }
236
+
237
+ const [channel, value] = write;
238
+ return [[taskId, channel, value]];
239
+ });
240
+
241
+ const parentCheckpointId = checkpointRow[r.parentCheckpointIdField]
242
+ ? String(checkpointRow[r.parentCheckpointIdField])
243
+ : null;
244
+
245
+ const tuple: CheckpointTuple = {
246
+ config: this.buildConfig(threadId, checkpointNs, resolvedCheckpointId),
247
+ checkpoint: this.deserialize<Checkpoint>(
248
+ checkpointRow[r.checkpointPayloadField],
249
+ ) as Checkpoint,
250
+ metadata: (this.deserialize<CheckpointMetadata>(
251
+ checkpointRow[r.metadataPayloadField],
252
+ ) ?? {}) as CheckpointMetadata,
253
+ parentConfig: parentCheckpointId
254
+ ? this.buildConfig(threadId, checkpointNs, parentCheckpointId)
255
+ : undefined,
256
+ pendingWrites,
257
+ };
258
+
259
+ return tuple;
260
+ }
261
+
262
+ async *list(
263
+ config: RunnableConfig,
264
+ options?: {
265
+ before?: RunnableConfig;
266
+ limit?: number;
267
+ },
268
+ ): AsyncGenerator<CheckpointTuple> {
269
+ const r = this.resourceConfig;
270
+ const { threadId, checkpointNs } = this.getConfigValues(config);
271
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
272
+ const beforeCheckpointId = options?.before
273
+ ? this.getConfigValues(options.before).checkpointId
274
+ : null;
275
+
276
+ const filters: Filters[] = [
277
+ Filters.EQ(r.rowKindField, "checkpoint"),
278
+ Filters.EQ(r.threadIdField, threadId),
279
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
280
+ ];
281
+
282
+ if (beforeCheckpointId) {
283
+ filters.push(Filters.LT(r.checkpointIdField, beforeCheckpointId));
284
+ }
285
+
286
+ const rows = await this.resource().list(
287
+ Filters.AND(...filters),
288
+ options?.limit,
289
+ undefined,
290
+ [{ field: r.checkpointIdField, direction: "desc" }],
291
+ );
292
+
293
+ for (const row of rows) {
294
+ const tuple = await this.getTuple(
295
+ this.buildConfig(
296
+ String(row[r.threadIdField]),
297
+ this.decodeCheckpointNamespace(row[r.checkpointNamespaceField]),
298
+ String(row[r.checkpointIdField]),
299
+ ),
300
+ );
301
+
302
+ if (tuple) {
303
+ yield tuple;
304
+ }
305
+ }
306
+ }
307
+
308
+ async deleteThread(threadId: string, checkpointNs = ""): Promise<void> {
309
+ const r = this.resourceConfig;
310
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
311
+
312
+ const rows = await this.resource().list(
313
+ Filters.AND(
314
+ Filters.EQ(r.threadIdField, threadId),
315
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
316
+ ),
317
+ undefined,
318
+ undefined,
319
+ [{ field: r.createdAtField, direction: "desc" }],
320
+ );
321
+
322
+ for (const row of rows) {
323
+ await this.adminforth
324
+ .resource(this.pluginOptions.checkpointResource!.resourceId)
325
+ .delete(row[r.idField]);
326
+ }
327
+ }
328
+ }
@@ -1,7 +1,7 @@
1
1
  import { createAgent, summarizationMiddleware } from "langchain";
2
2
  import { logger, type AdminUser, type CompletionAdapter } from "adminforth";
3
3
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
4
- import { MemorySaver, type Messages } from "@langchain/langgraph";
4
+ import {type BaseCheckpointSaver, type Messages } from "@langchain/langgraph";
5
5
  import type { LLMResult } from "@langchain/core/outputs";
6
6
  import { z } from "zod";
7
7
  import { ChatOpenAI } from "@langchain/openai";
@@ -15,8 +15,6 @@ import { createOpenAiResponsesContinuationMiddleware } from "./middleware/openAi
15
15
  import type { ApiBasedTool } from "../apiBasedTools.js";
16
16
  import type { ToolCallEventSink } from "./toolCallEvents.js";
17
17
 
18
- const checkpointer = new MemorySaver();
19
-
20
18
  export const contextSchema = z.object({
21
19
  adminUser: z.custom<AdminUser>(),
22
20
  userTimeZone: z.string(),
@@ -36,6 +34,8 @@ type OpenAIBackedCompletionAdapter = CompletionAdapter & {
36
34
  };
37
35
  };
38
36
 
37
+ type OpenAiReasoningConfig = Record<string, unknown>;
38
+
39
39
  type LlmOutputTokenUsage = {
40
40
  promptTokens?: unknown;
41
41
  completionTokens?: unknown;
@@ -178,7 +178,14 @@ export function createAgentChatModel(params: {
178
178
 
179
179
  const model = params.modelName ?? options.model ?? "gpt-5-nano";
180
180
  const baseURL = options.baseURL ?? options.baseUrl;
181
- const reasoning = options.extraRequestBodyParameters?.reasoning;
181
+ const reasoning = options.extraRequestBodyParameters
182
+ ?.reasoning as OpenAiReasoningConfig | undefined;
183
+ const reasoningConfig = reasoning
184
+ ? {
185
+ ...reasoning,
186
+ summary: "auto",
187
+ }
188
+ : undefined;
182
189
 
183
190
  // @ts-ignore
184
191
  return new ChatOpenAI({
@@ -192,7 +199,7 @@ export function createAgentChatModel(params: {
192
199
 
193
200
  promptCacheRetention: "in_memory",
194
201
 
195
- ...(reasoning ? { reasoning } : {}),
202
+ ...(reasoningConfig ? { reasoning: reasoningConfig } : {}),
196
203
  ...(typeof options.timeoutMs === "number"
197
204
  ? { timeout: options.timeoutMs }
198
205
  : {}),
@@ -210,6 +217,7 @@ export async function callAgent(params: {
210
217
  name: string;
211
218
  model: ChatOpenAI;
212
219
  summaryModel: ChatOpenAI;
220
+ checkpointer?: BaseCheckpointSaver;
213
221
  messages: Messages;
214
222
  adminUser: AdminUser;
215
223
  apiBasedTools: Record<string, ApiBasedTool>;
@@ -224,6 +232,7 @@ export async function callAgent(params: {
224
232
  name,
225
233
  model,
226
234
  summaryModel,
235
+ checkpointer,
227
236
  messages,
228
237
  adminUser,
229
238
  apiBasedTools,
package/build.log CHANGED
@@ -31,5 +31,5 @@ custom/skills/fetch_data/SKILL.md
31
31
  custom/skills/mutate_data/
32
32
  custom/skills/mutate_data/SKILL.md
33
33
 
34
- sent 185,223 bytes received 432 bytes 371,310.00 bytes/sec
34
+ sent 185,196 bytes received 432 bytes 371,256.00 bytes/sec
35
35
  total size is 183,434 speedup is 0.99
@@ -0,0 +1,236 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
11
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
14
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
15
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
16
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
17
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
18
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
19
+ function fulfill(value) { resume("next", value); }
20
+ function reject(value) { resume("throw", value); }
21
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
22
+ };
23
+ import { BaseCheckpointSaver, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
24
+ import { Filters } from "adminforth";
25
+ const ROOT_CHECKPOINT_NAMESPACE = "__root__";
26
+ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
27
+ constructor(adminforth, pluginOptions) {
28
+ super();
29
+ this.adminforth = adminforth;
30
+ this.pluginOptions = pluginOptions;
31
+ }
32
+ get resourceConfig() {
33
+ const resource = this.pluginOptions.checkpointResource;
34
+ if (!resource) {
35
+ throw new Error("checkpointResource is not configured");
36
+ }
37
+ return resource;
38
+ }
39
+ resource() {
40
+ return this.adminforth.resource(this.resourceConfig.resourceId);
41
+ }
42
+ serialize(value) {
43
+ if (value === undefined || value === null)
44
+ return null;
45
+ return JSON.stringify(value);
46
+ }
47
+ deserialize(value) {
48
+ if (value === undefined || value === null)
49
+ return null;
50
+ if (typeof value === "string") {
51
+ return JSON.parse(value);
52
+ }
53
+ return value;
54
+ }
55
+ now() {
56
+ return new Date().toISOString();
57
+ }
58
+ encodeCheckpointNamespace(checkpointNs) {
59
+ return checkpointNs === "" ? ROOT_CHECKPOINT_NAMESPACE : checkpointNs;
60
+ }
61
+ decodeCheckpointNamespace(checkpointNs) {
62
+ return checkpointNs === ROOT_CHECKPOINT_NAMESPACE ? "" : String(checkpointNs !== null && checkpointNs !== void 0 ? checkpointNs : "");
63
+ }
64
+ getConfigValues(config) {
65
+ var _a, _b, _c;
66
+ const configurable = ((_a = config.configurable) !== null && _a !== void 0 ? _a : {});
67
+ return {
68
+ threadId: String((_b = configurable.thread_id) !== null && _b !== void 0 ? _b : ""),
69
+ checkpointNs: String((_c = configurable.checkpoint_ns) !== null && _c !== void 0 ? _c : ""),
70
+ checkpointId: configurable.checkpoint_id
71
+ ? String(configurable.checkpoint_id)
72
+ : null,
73
+ };
74
+ }
75
+ buildConfig(threadId, checkpointNs, checkpointId) {
76
+ return {
77
+ configurable: {
78
+ thread_id: threadId,
79
+ checkpoint_ns: checkpointNs,
80
+ checkpoint_id: checkpointId,
81
+ },
82
+ };
83
+ }
84
+ buildCheckpointRowId(threadId, checkpointNs, checkpointId) {
85
+ return `cp:${threadId}:${checkpointNs}:${checkpointId}`;
86
+ }
87
+ buildWritesRowId(threadId, checkpointNs, checkpointId, taskId, seq) {
88
+ return `wr:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:${seq}`;
89
+ }
90
+ getWriteIndex(channel, index) {
91
+ var _a;
92
+ return (_a = WRITES_IDX_MAP[channel]) !== null && _a !== void 0 ? _a : index;
93
+ }
94
+ isDuplicateCheckpointWriteError(error) {
95
+ return error instanceof Error && error.message.includes("UNIQUE constraint failed");
96
+ }
97
+ put(config, checkpoint, metadata, _newVersions) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ const r = this.resourceConfig;
100
+ const { threadId, checkpointNs } = this.getConfigValues(config);
101
+ const checkpointId = String(checkpoint.id);
102
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
103
+ const parentCheckpointId = this.getConfigValues(config).checkpointId;
104
+ const createdAt = this.now();
105
+ yield this.resource().create({
106
+ [r.idField]: this.buildCheckpointRowId(threadId, storedCheckpointNs, checkpointId),
107
+ [r.threadIdField]: threadId,
108
+ [r.checkpointNamespaceField]: storedCheckpointNs,
109
+ [r.checkpointIdField]: checkpointId,
110
+ [r.parentCheckpointIdField]: parentCheckpointId,
111
+ [r.rowKindField]: "checkpoint",
112
+ [r.taskIdField]: null,
113
+ [r.sequenceField]: 0,
114
+ [r.createdAtField]: createdAt,
115
+ [r.checkpointPayloadField]: this.serialize(checkpoint),
116
+ [r.metadataPayloadField]: this.serialize(metadata),
117
+ [r.writesPayloadField]: null,
118
+ [r.schemaVersionField]: 1,
119
+ });
120
+ return this.buildConfig(threadId, checkpointNs, checkpointId);
121
+ });
122
+ }
123
+ putWrites(config, writes, taskId) {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ const r = this.resourceConfig;
126
+ const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
127
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
128
+ if (!checkpointId) {
129
+ throw new Error("putWrites requires checkpoint_id in config");
130
+ }
131
+ const createdAt = this.now();
132
+ yield Promise.all(writes.map((_a, index_1) => __awaiter(this, [_a, index_1], void 0, function* ([channel, value], index) {
133
+ const writeIndex = this.getWriteIndex(channel, index);
134
+ try {
135
+ yield this.resource().create({
136
+ [r.idField]: this.buildWritesRowId(threadId, storedCheckpointNs, checkpointId, taskId, writeIndex),
137
+ [r.threadIdField]: threadId,
138
+ [r.checkpointNamespaceField]: storedCheckpointNs,
139
+ [r.checkpointIdField]: checkpointId,
140
+ [r.parentCheckpointIdField]: null,
141
+ [r.rowKindField]: "writes",
142
+ [r.taskIdField]: taskId,
143
+ [r.sequenceField]: writeIndex,
144
+ [r.createdAtField]: createdAt,
145
+ [r.checkpointPayloadField]: null,
146
+ [r.metadataPayloadField]: null,
147
+ [r.writesPayloadField]: this.serialize([channel, value]),
148
+ [r.schemaVersionField]: 1,
149
+ });
150
+ }
151
+ catch (error) {
152
+ if (!this.isDuplicateCheckpointWriteError(error)) {
153
+ throw error;
154
+ }
155
+ }
156
+ })));
157
+ });
158
+ }
159
+ getTuple(config) {
160
+ return __awaiter(this, void 0, void 0, function* () {
161
+ var _a;
162
+ const r = this.resourceConfig;
163
+ const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
164
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
165
+ const checkpointRows = yield this.resource().list(checkpointId
166
+ ? Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.checkpointIdField, checkpointId), Filters.EQ(r.rowKindField, "checkpoint"))
167
+ : Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.rowKindField, "checkpoint")), 1, undefined, [{ field: r.checkpointIdField, direction: "desc" }]);
168
+ const checkpointRow = checkpointRows[0];
169
+ if (!checkpointRow) {
170
+ return undefined;
171
+ }
172
+ const resolvedCheckpointId = String(checkpointRow[r.checkpointIdField]);
173
+ const writesRows = yield this.resource().list(Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.checkpointIdField, resolvedCheckpointId), Filters.EQ(r.rowKindField, "writes")), undefined, undefined, [{ field: r.sequenceField, direction: "asc" }]);
174
+ const pendingWrites = writesRows.flatMap((row) => {
175
+ var _a;
176
+ const taskId = String((_a = row[r.taskIdField]) !== null && _a !== void 0 ? _a : "");
177
+ const write = this.deserialize(row[r.writesPayloadField]);
178
+ if (!write) {
179
+ return [];
180
+ }
181
+ const [channel, value] = write;
182
+ return [[taskId, channel, value]];
183
+ });
184
+ const parentCheckpointId = checkpointRow[r.parentCheckpointIdField]
185
+ ? String(checkpointRow[r.parentCheckpointIdField])
186
+ : null;
187
+ const tuple = {
188
+ config: this.buildConfig(threadId, checkpointNs, resolvedCheckpointId),
189
+ checkpoint: this.deserialize(checkpointRow[r.checkpointPayloadField]),
190
+ metadata: ((_a = this.deserialize(checkpointRow[r.metadataPayloadField])) !== null && _a !== void 0 ? _a : {}),
191
+ parentConfig: parentCheckpointId
192
+ ? this.buildConfig(threadId, checkpointNs, parentCheckpointId)
193
+ : undefined,
194
+ pendingWrites,
195
+ };
196
+ return tuple;
197
+ });
198
+ }
199
+ list(config, options) {
200
+ return __asyncGenerator(this, arguments, function* list_1() {
201
+ const r = this.resourceConfig;
202
+ const { threadId, checkpointNs } = this.getConfigValues(config);
203
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
204
+ const beforeCheckpointId = (options === null || options === void 0 ? void 0 : options.before)
205
+ ? this.getConfigValues(options.before).checkpointId
206
+ : null;
207
+ const filters = [
208
+ Filters.EQ(r.rowKindField, "checkpoint"),
209
+ Filters.EQ(r.threadIdField, threadId),
210
+ Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
211
+ ];
212
+ if (beforeCheckpointId) {
213
+ filters.push(Filters.LT(r.checkpointIdField, beforeCheckpointId));
214
+ }
215
+ const rows = yield __await(this.resource().list(Filters.AND(...filters), options === null || options === void 0 ? void 0 : options.limit, undefined, [{ field: r.checkpointIdField, direction: "desc" }]));
216
+ for (const row of rows) {
217
+ const tuple = yield __await(this.getTuple(this.buildConfig(String(row[r.threadIdField]), this.decodeCheckpointNamespace(row[r.checkpointNamespaceField]), String(row[r.checkpointIdField]))));
218
+ if (tuple) {
219
+ yield yield __await(tuple);
220
+ }
221
+ }
222
+ });
223
+ }
224
+ deleteThread(threadId_1) {
225
+ return __awaiter(this, arguments, void 0, function* (threadId, checkpointNs = "") {
226
+ const r = this.resourceConfig;
227
+ const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
228
+ const rows = yield this.resource().list(Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs)), undefined, undefined, [{ field: r.createdAtField, direction: "desc" }]);
229
+ for (const row of rows) {
230
+ yield this.adminforth
231
+ .resource(this.pluginOptions.checkpointResource.resourceId)
232
+ .delete(row[r.idField]);
233
+ }
234
+ });
235
+ }
236
+ }
@@ -10,14 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { createAgent, summarizationMiddleware } from "langchain";
11
11
  import { logger } from "adminforth";
12
12
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
13
- import { MemorySaver } from "@langchain/langgraph";
14
13
  import { z } from "zod";
15
14
  import { ChatOpenAI } from "@langchain/openai";
16
15
  import { createAgentTools } from "./tools/index.js";
17
16
  import { createApiBasedToolsMiddleware } from "./middleware/apiBasedTools.js";
18
17
  import { createSequenceDebugMiddleware, } from "./middleware/sequenceDebug.js";
19
18
  import { createOpenAiResponsesContinuationMiddleware } from "./middleware/openAiResponsesContinuation.js";
20
- const checkpointer = new MemorySaver();
21
19
  export const contextSchema = z.object({
22
20
  adminUser: z.custom(),
23
21
  userTimeZone: z.string(),
@@ -113,8 +111,10 @@ export function createAgentChatModel(params) {
113
111
  const model = (_c = (_b = params.modelName) !== null && _b !== void 0 ? _b : options.model) !== null && _c !== void 0 ? _c : "gpt-5-nano";
114
112
  const baseURL = (_d = options.baseURL) !== null && _d !== void 0 ? _d : options.baseUrl;
115
113
  const reasoning = (_e = options.extraRequestBodyParameters) === null || _e === void 0 ? void 0 : _e.reasoning;
114
+ const reasoningConfig = reasoning
115
+ ? Object.assign(Object.assign({}, reasoning), { summary: "auto" }) : undefined;
116
116
  // @ts-ignore
117
- return new ChatOpenAI(Object.assign(Object.assign(Object.assign({ apiKey: options.openAiApiKey, model, maxTokens: params.maxTokens, useResponsesApi: true, outputVersion: "v1", streaming: true, promptCacheKey: `adminforth-agent:${model}:system-v1:tools-v1`, promptCacheRetention: "in_memory" }, (reasoning ? { reasoning } : {})), (typeof options.timeoutMs === "number"
117
+ return new ChatOpenAI(Object.assign(Object.assign(Object.assign({ apiKey: options.openAiApiKey, model, maxTokens: params.maxTokens, useResponsesApi: true, outputVersion: "v1", streaming: true, promptCacheKey: `adminforth-agent:${model}:system-v1:tools-v1`, promptCacheRetention: "in_memory" }, (reasoningConfig ? { reasoning: reasoningConfig } : {})), (typeof options.timeoutMs === "number"
118
118
  ? { timeout: options.timeoutMs }
119
119
  : {})), (baseURL
120
120
  ? {
@@ -126,7 +126,7 @@ export function createAgentChatModel(params) {
126
126
  }
127
127
  export function callAgent(params) {
128
128
  return __awaiter(this, void 0, void 0, function* () {
129
- const { name, model, summaryModel, messages, adminUser, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
129
+ const { name, model, summaryModel, checkpointer, messages, adminUser, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
130
130
  const tools = yield createAgentTools(customComponentsDir, apiBasedTools);
131
131
  const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools);
132
132
  const openAiResponsesContinuationMiddleware = createOpenAiResponsesContinuationMiddleware();
package/dist/index.js CHANGED
@@ -17,7 +17,9 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
17
17
  import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
18
18
  import { randomUUID } from 'crypto';
19
19
  import { HumanMessage, SystemMessage } from "langchain";
20
+ import { MemorySaver } from "@langchain/langgraph";
20
21
  import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
22
+ import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
21
23
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
22
24
  import { prepareApiBasedTools as buildApiBasedTools, } from './apiBasedTools.js';
23
25
  import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT, } from "./agent/systemPrompt.js";
@@ -88,9 +90,38 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
88
90
  }));
89
91
  });
90
92
  }
93
+ getModeModels(mode, maxTokens) {
94
+ const cachedModels = this.modelsByModeName.get(mode.name);
95
+ if (cachedModels) {
96
+ return cachedModels;
97
+ }
98
+ const models = {
99
+ model: createAgentChatModel({
100
+ adapter: mode.completionAdapter,
101
+ maxTokens,
102
+ }),
103
+ summaryModel: createAgentChatModel({
104
+ adapter: mode.completionAdapter,
105
+ maxTokens,
106
+ }),
107
+ };
108
+ this.modelsByModeName.set(mode.name, models);
109
+ return models;
110
+ }
111
+ getCheckpointer() {
112
+ if (this.checkpointer) {
113
+ return this.checkpointer;
114
+ }
115
+ this.checkpointer = this.options.checkpointResource
116
+ ? new AdminForthCheckpointSaver(this.adminforth, this.options)
117
+ : new MemorySaver();
118
+ return this.checkpointer;
119
+ }
91
120
  constructor(options) {
92
121
  super(options, import.meta.url);
93
122
  this.apiBasedTools = {};
123
+ this.checkpointer = null;
124
+ this.modelsByModeName = new Map();
94
125
  this.options = options;
95
126
  this.agentSystemPromptPromise = Promise.resolve(appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt));
96
127
  this.shouldHaveSingleInstancePerWholeApp = () => false;
@@ -241,19 +272,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
241
272
  });
242
273
  const maxTokens = (_f = this.options.maxTokens) !== null && _f !== void 0 ? _f : 10000;
243
274
  const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
244
- const model = createAgentChatModel({
245
- adapter: selectedMode.completionAdapter,
246
- maxTokens,
247
- });
248
- const summaryModel = createAgentChatModel({
249
- adapter: selectedMode.completionAdapter,
250
- maxTokens,
251
- });
275
+ const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
252
276
  const systemPrompt = yield this.agentSystemPromptPromise;
253
277
  const stream = yield callAgent({
254
278
  name: `adminforth-agent-${this.pluginInstanceId}`,
255
279
  model,
256
280
  summaryModel,
281
+ checkpointer: this.getCheckpointer(),
257
282
  messages: [
258
283
  new SystemMessage(systemPrompt),
259
284
  new HumanMessage(prompt),
package/index.ts CHANGED
@@ -9,7 +9,9 @@ import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
9
9
  import type { PluginOptions } from './types.js';
10
10
  import { randomUUID } from 'crypto';
11
11
  import { HumanMessage, SystemMessage } from "langchain";
12
+ import { MemorySaver, type BaseCheckpointSaver } from "@langchain/langgraph";
12
13
  import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
14
+ import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
13
15
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
14
16
  import {
15
17
  prepareApiBasedTools as buildApiBasedTools,
@@ -22,6 +24,7 @@ import {
22
24
  } from "./agent/systemPrompt.js";
23
25
  import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "./agent/tools/index.js";
24
26
  import type { ToolCallEvent } from "./agent/toolCallEvents.js";
27
+ import type { ChatOpenAI } from "@langchain/openai";
25
28
 
26
29
  function isAggregateErrorLike(
27
30
  error: unknown,
@@ -69,6 +72,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
69
72
  options: PluginOptions;
70
73
  apiBasedTools: Record<string, ApiBasedTool> = {};
71
74
  agentSystemPromptPromise: Promise<string>;
75
+ private checkpointer: BaseCheckpointSaver | null = null;
76
+ private readonly modelsByModeName = new Map<
77
+ string,
78
+ {
79
+ model: ChatOpenAI;
80
+ summaryModel: ChatOpenAI;
81
+ }
82
+ >();
72
83
 
73
84
  private async createNewTurn(sessionId: string, prompt: string, response?: string) {
74
85
  const turnId = randomUUID();
@@ -107,6 +118,43 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
107
118
  }));
108
119
  }
109
120
 
121
+ private getModeModels(
122
+ mode: PluginOptions["modes"][number],
123
+ maxTokens: number,
124
+ ) {
125
+ const cachedModels = this.modelsByModeName.get(mode.name);
126
+
127
+ if (cachedModels) {
128
+ return cachedModels;
129
+ }
130
+
131
+ const models = {
132
+ model: createAgentChatModel({
133
+ adapter: mode.completionAdapter,
134
+ maxTokens,
135
+ }),
136
+ summaryModel: createAgentChatModel({
137
+ adapter: mode.completionAdapter,
138
+ maxTokens,
139
+ }),
140
+ };
141
+
142
+ this.modelsByModeName.set(mode.name, models);
143
+ return models;
144
+ }
145
+
146
+ private getCheckpointer() {
147
+ if (this.checkpointer) {
148
+ return this.checkpointer;
149
+ }
150
+
151
+ this.checkpointer = this.options.checkpointResource
152
+ ? new AdminForthCheckpointSaver(this.adminforth, this.options)
153
+ : new MemorySaver();
154
+
155
+ return this.checkpointer;
156
+ }
157
+
110
158
  constructor(options: PluginOptions) {
111
159
  super(options, import.meta.url);
112
160
  this.options = options;
@@ -278,20 +326,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
278
326
 
279
327
  const maxTokens = this.options.maxTokens ?? 10000;
280
328
  const selectedMode = this.options.modes.find((mode) => mode.name === body.mode) ?? this.options.modes[0];
281
- const model = createAgentChatModel({
282
- adapter: selectedMode.completionAdapter,
283
- maxTokens,
284
- });
285
-
286
- const summaryModel = createAgentChatModel({
287
- adapter: selectedMode.completionAdapter,
288
- maxTokens,
289
- });
329
+ const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
290
330
  const systemPrompt = await this.agentSystemPromptPromise;
291
331
  const stream = await callAgent({
292
332
  name: `adminforth-agent-${this.pluginInstanceId}`,
293
333
  model,
294
334
  summaryModel,
335
+ checkpointer: this.getCheckpointer(),
295
336
  messages: [
296
337
  new SystemMessage(systemPrompt),
297
338
  new HumanMessage(prompt),
@@ -323,7 +364,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
323
364
  : Array.isArray(token?.content)
324
365
  ? token.content
325
366
  : [];
326
-
327
367
  const reasoningDelta = blocks
328
368
  .filter((b: any) => b?.type === "reasoning")
329
369
  .map((b: any) => String(b.reasoning ?? ""))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "dependencies": {
24
24
  "@langchain/core": "^1.1.40",
25
25
  "@langchain/langgraph": "^1.2.8",
26
+ "@langchain/langgraph-checkpoint": "^1.0.1",
26
27
  "@langchain/openai": "^1.4.4",
27
28
  "adminforth": "2.27.0-next.47",
28
29
  "dayjs": "^1.11.20",
package/types.ts CHANGED
@@ -24,6 +24,23 @@ interface ITurnResource {
24
24
  debugField?: string;
25
25
  }
26
26
 
27
+ interface ICheckpointResource {
28
+ resourceId: string;
29
+ idField: string;
30
+ threadIdField: string;
31
+ checkpointNamespaceField: string;
32
+ checkpointIdField: string;
33
+ parentCheckpointIdField: string;
34
+ rowKindField: string;
35
+ taskIdField: string;
36
+ sequenceField: string;
37
+ createdAtField: string;
38
+ checkpointPayloadField: string;
39
+ metadataPayloadField: string;
40
+ writesPayloadField: string;
41
+ schemaVersionField: string;
42
+ }
43
+
27
44
  export interface PluginOptions extends PluginsCommonOptions {
28
45
  /**
29
46
  * Optional placeholder examples to preload for the chat textarea.
@@ -75,4 +92,10 @@ export interface PluginOptions extends PluginsCommonOptions {
75
92
  * Makes chat sticky by default. By default this prop is false
76
93
  */
77
94
  stickByDefault?: boolean;
95
+
96
+ /**
97
+ * Optional resource configuration for a persistent LangGraph checkpointer.
98
+ * Falls back to an in-memory MemorySaver when omitted.
99
+ */
100
+ checkpointResource?: ICheckpointResource;
78
101
  }