@adminforth/agent 1.16.4 → 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,131 bytes received 436 bytes 371,134.00 bytes/sec
35
- total size is 183,342 speedup is 0.99
34
+ sent 185,196 bytes received 432 bytes 371,256.00 bytes/sec
35
+ total size is 183,434 speedup is 0.99
@@ -193,6 +193,7 @@ const props = defineProps<{
193
193
  name: string;
194
194
  }>;
195
195
  defaultModeName: string | null;
196
+ stickByDefault: boolean;
196
197
  }
197
198
  }>();
198
199
 
@@ -245,6 +246,7 @@ onMounted(async () => {
245
246
  agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
246
247
  agentStore.regisrerTextInput(textInput.value);
247
248
  textInput.value?.focus();
249
+ agentStore.setIsTeleportedToBody(props.meta.stickByDefault);
248
250
  await agentStore.fetchSessionsList();
249
251
  });
250
252
 
@@ -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();
@@ -193,6 +193,7 @@ const props = defineProps<{
193
193
  name: string;
194
194
  }>;
195
195
  defaultModeName: string | null;
196
+ stickByDefault: boolean;
196
197
  }
197
198
  }>();
198
199
 
@@ -245,6 +246,7 @@ onMounted(async () => {
245
246
  agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
246
247
  agentStore.regisrerTextInput(textInput.value);
247
248
  textInput.value?.focus();
249
+ agentStore.setIsTeleportedToBody(props.meta.stickByDefault);
248
250
  await agentStore.fetchSessionsList();
249
251
  });
250
252
 
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;
@@ -100,7 +131,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
100
131
  modifyResourceConfig: { get: () => super.modifyResourceConfig }
101
132
  });
102
133
  return __awaiter(this, void 0, void 0, function* () {
103
- var _a;
134
+ var _a, _b;
104
135
  _super.modifyResourceConfig.call(this, adminforth, resourceConfig);
105
136
  if (!((_a = this.options.modes) === null || _a === void 0 ? void 0 : _a.length)) {
106
137
  throw new Error("modes is required for AdminForthAgentPlugin");
@@ -114,6 +145,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
114
145
  pluginInstanceId: this.pluginInstanceId,
115
146
  modes: this.options.modes.map((mode) => ({ name: mode.name })),
116
147
  defaultModeName: this.options.modes[0].name,
148
+ stickByDefault: (_b = this.options.stickByDefault) !== null && _b !== void 0 ? _b : false,
117
149
  }
118
150
  });
119
151
  if (!this.pluginOptions.sessionResource) {
@@ -240,19 +272,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
240
272
  });
241
273
  const maxTokens = (_f = this.options.maxTokens) !== null && _f !== void 0 ? _f : 10000;
242
274
  const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
243
- const model = createAgentChatModel({
244
- adapter: selectedMode.completionAdapter,
245
- maxTokens,
246
- });
247
- const summaryModel = createAgentChatModel({
248
- adapter: selectedMode.completionAdapter,
249
- maxTokens,
250
- });
275
+ const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
251
276
  const systemPrompt = yield this.agentSystemPromptPromise;
252
277
  const stream = yield callAgent({
253
278
  name: `adminforth-agent-${this.pluginInstanceId}`,
254
279
  model,
255
280
  summaryModel,
281
+ checkpointer: this.getCheckpointer(),
256
282
  messages: [
257
283
  new SystemMessage(systemPrompt),
258
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;
@@ -130,6 +178,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
130
178
  pluginInstanceId: this.pluginInstanceId,
131
179
  modes: this.options.modes.map((mode) => ({ name: mode.name })),
132
180
  defaultModeName: this.options.modes[0].name,
181
+ stickByDefault: this.options.stickByDefault ?? false,
133
182
  }
134
183
  });
135
184
  if (!this.pluginOptions.sessionResource) {
@@ -277,20 +326,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
277
326
 
278
327
  const maxTokens = this.options.maxTokens ?? 10000;
279
328
  const selectedMode = this.options.modes.find((mode) => mode.name === body.mode) ?? this.options.modes[0];
280
- const model = createAgentChatModel({
281
- adapter: selectedMode.completionAdapter,
282
- maxTokens,
283
- });
284
-
285
- const summaryModel = createAgentChatModel({
286
- adapter: selectedMode.completionAdapter,
287
- maxTokens,
288
- });
329
+ const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
289
330
  const systemPrompt = await this.agentSystemPromptPromise;
290
331
  const stream = await callAgent({
291
332
  name: `adminforth-agent-${this.pluginInstanceId}`,
292
333
  model,
293
334
  summaryModel,
335
+ checkpointer: this.getCheckpointer(),
294
336
  messages: [
295
337
  new SystemMessage(systemPrompt),
296
338
  new HumanMessage(prompt),
@@ -322,7 +364,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
322
364
  : Array.isArray(token?.content)
323
365
  ? token.content
324
366
  : [];
325
-
326
367
  const reasoningDelta = blocks
327
368
  .filter((b: any) => b?.type === "reasoning")
328
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.16.4",
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.
@@ -70,4 +87,15 @@ export interface PluginOptions extends PluginsCommonOptions {
70
87
  * Resource configuration for turns.
71
88
  */
72
89
  turnResource: ITurnResource;
90
+
91
+ /**
92
+ * Makes chat sticky by default. By default this prop is false
93
+ */
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;
73
101
  }