zeitlich 0.2.45 → 0.2.46

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.
Files changed (89) hide show
  1. package/README.md +78 -10
  2. package/dist/{activities-CrN-ghLo.d.ts → activities-Bm4TLTid.d.ts} +22 -2
  3. package/dist/{activities-Coafq5zr.d.cts → activities-CyeiqK_f.d.cts} +22 -2
  4. package/dist/adapters/thread/anthropic/index.cjs +171 -65
  5. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  6. package/dist/adapters/thread/anthropic/index.d.cts +19 -4
  7. package/dist/adapters/thread/anthropic/index.d.ts +19 -4
  8. package/dist/adapters/thread/anthropic/index.js +171 -65
  9. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  10. package/dist/adapters/thread/anthropic/workflow.cjs +3 -1
  11. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  12. package/dist/adapters/thread/anthropic/workflow.d.cts +4 -4
  13. package/dist/adapters/thread/anthropic/workflow.d.ts +4 -4
  14. package/dist/adapters/thread/anthropic/workflow.js +3 -1
  15. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  16. package/dist/adapters/thread/google-genai/index.cjs +171 -69
  17. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  18. package/dist/adapters/thread/google-genai/index.d.cts +5 -4
  19. package/dist/adapters/thread/google-genai/index.d.ts +5 -4
  20. package/dist/adapters/thread/google-genai/index.js +171 -69
  21. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  22. package/dist/adapters/thread/google-genai/workflow.cjs +3 -1
  23. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -4
  25. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -4
  26. package/dist/adapters/thread/google-genai/workflow.js +3 -1
  27. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  28. package/dist/adapters/thread/langchain/index.cjs +170 -66
  29. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  30. package/dist/adapters/thread/langchain/index.d.cts +18 -4
  31. package/dist/adapters/thread/langchain/index.d.ts +18 -4
  32. package/dist/adapters/thread/langchain/index.js +170 -66
  33. package/dist/adapters/thread/langchain/index.js.map +1 -1
  34. package/dist/adapters/thread/langchain/workflow.cjs +3 -1
  35. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  36. package/dist/adapters/thread/langchain/workflow.d.cts +4 -4
  37. package/dist/adapters/thread/langchain/workflow.d.ts +4 -4
  38. package/dist/adapters/thread/langchain/workflow.js +3 -1
  39. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  40. package/dist/cold-store-BC5L5Z8A.d.cts +117 -0
  41. package/dist/cold-store-CFHwemBJ.d.ts +117 -0
  42. package/dist/index.cjs +226 -27
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +138 -8
  45. package/dist/index.d.ts +138 -8
  46. package/dist/index.js +220 -28
  47. package/dist/index.js.map +1 -1
  48. package/dist/{proxy-Bf7uI-Hw.d.cts → proxy-BxFyd6cg.d.cts} +1 -1
  49. package/dist/{proxy-COqA95FW.d.ts → proxy-Cskmj4Yx.d.ts} +1 -1
  50. package/dist/{thread-manager-BsLO3Fgc.d.cts → thread-manager-9tezUcLW.d.cts} +8 -2
  51. package/dist/{thread-manager-Bi1XlbpJ.d.ts → thread-manager-B-zy3xrs.d.ts} +8 -2
  52. package/dist/{thread-manager-wRVVBFgj.d.cts → thread-manager-D33SUmZa.d.cts} +8 -2
  53. package/dist/{thread-manager-BhkOyQ1I.d.ts → thread-manager-DduoSkvJ.d.ts} +8 -2
  54. package/dist/{types-CdALEF3z.d.cts → types-CnuN9T6t.d.cts} +22 -0
  55. package/dist/{types-ChAy_jSP.d.ts → types-CwN6_tAL.d.ts} +22 -0
  56. package/dist/{types-BkX4HLzi.d.ts → types-L5bvbF-n.d.ts} +17 -1
  57. package/dist/{types-C66-BVBr.d.cts → types-oxt8GN97.d.cts} +17 -1
  58. package/dist/{workflow-BwT5EybR.d.ts → workflow-B1TOcHbt.d.ts} +33 -2
  59. package/dist/{workflow-DMmiaw6w.d.cts → workflow-DIaIV7L2.d.cts} +33 -2
  60. package/dist/workflow.cjs +14 -1
  61. package/dist/workflow.cjs.map +1 -1
  62. package/dist/workflow.d.cts +2 -2
  63. package/dist/workflow.d.ts +2 -2
  64. package/dist/workflow.js +14 -1
  65. package/dist/workflow.js.map +1 -1
  66. package/package.json +6 -1
  67. package/src/adapters/thread/anthropic/activities.ts +72 -36
  68. package/src/adapters/thread/anthropic/thread-manager.ts +9 -1
  69. package/src/adapters/thread/google-genai/activities.ts +64 -40
  70. package/src/adapters/thread/google-genai/thread-manager.ts +9 -1
  71. package/src/adapters/thread/langchain/activities.ts +63 -36
  72. package/src/adapters/thread/langchain/thread-manager.ts +9 -1
  73. package/src/index.ts +20 -1
  74. package/src/lib/session/session-edge-cases.integration.test.ts +12 -0
  75. package/src/lib/session/session.integration.test.ts +138 -0
  76. package/src/lib/session/session.ts +29 -0
  77. package/src/lib/session/types.ts +22 -0
  78. package/src/lib/thread/cold-store.test.ts +193 -0
  79. package/src/lib/thread/cold-store.ts +250 -0
  80. package/src/lib/thread/index.ts +32 -0
  81. package/src/lib/thread/keys.ts +20 -0
  82. package/src/lib/thread/manager.ts +16 -27
  83. package/src/lib/thread/proxy.ts +2 -0
  84. package/src/lib/thread/snapshot.test.ts +443 -0
  85. package/src/lib/thread/snapshot.ts +163 -0
  86. package/src/lib/thread/test-utils.ts +228 -0
  87. package/src/lib/thread/tiered.test.ts +281 -0
  88. package/src/lib/thread/tiered.ts +135 -0
  89. package/src/lib/thread/types.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -201,6 +201,7 @@
201
201
  },
202
202
  "devDependencies": {
203
203
  "@anthropic-ai/sdk": "^0.93.0",
204
+ "@aws-sdk/client-s3": "^3.1000.0",
204
205
  "@daytonaio/sdk": "^0.171.0",
205
206
  "@e2b/code-interpreter": "^2.3.3",
206
207
  "@eslint/js": "^10.0.1",
@@ -223,6 +224,7 @@
223
224
  },
224
225
  "peerDependencies": {
225
226
  "@anthropic-ai/sdk": ">=0.50.0",
227
+ "@aws-sdk/client-s3": ">=3.700.0",
226
228
  "@daytonaio/sdk": ">=0.153.0",
227
229
  "@e2b/code-interpreter": "^2.3.3",
228
230
  "@google/genai": "^1.43.0",
@@ -241,6 +243,9 @@
241
243
  "@anthropic-ai/sdk": {
242
244
  "optional": true
243
245
  },
246
+ "@aws-sdk/client-s3": {
247
+ "optional": true
248
+ },
244
249
  "@google/genai": {
245
250
  "optional": true
246
251
  },
@@ -13,11 +13,15 @@ import type {
13
13
  ScopedPrefix,
14
14
  } from "../../../lib/session/types";
15
15
  import type { ModelInvoker } from "../../../lib/model";
16
+ import { createTieredThreadManager } from "../../../lib/thread/tiered";
17
+ import type { ColdThreadStore } from "../../../lib/thread/cold-store";
16
18
  import {
17
19
  createAnthropicThreadManager,
20
+ storedMessageId,
18
21
  type AnthropicContent,
19
22
  type AnthropicSystemContent,
20
23
  type AnthropicThreadManagerHooks,
24
+ type StoredMessage,
21
25
  } from "./thread-manager";
22
26
  import {
23
27
  createAnthropicModelInvoker,
@@ -38,6 +42,20 @@ export interface AnthropicAdapterConfig {
38
42
  /** Maximum tokens to generate. Defaults to 16384. */
39
43
  maxTokens?: number;
40
44
  hooks?: AnthropicThreadManagerHooks;
45
+ /**
46
+ * Optional durable cold tier (e.g. S3, R2, GCS). When provided,
47
+ * the session will hydrate the thread from cold storage on entry
48
+ * (`continue`/`fork` modes) and flush it back on every exit path.
49
+ * When omitted, the adapter is Redis-only and `hydrateThread`/
50
+ * `flushThread` activities are no-ops.
51
+ */
52
+ coldStore?: ColdThreadStore;
53
+ /**
54
+ * Override the default Redis TTL (90 days) for thread keys. When
55
+ * pairing the adapter with a `coldStore`, a shorter TTL (hours)
56
+ * is typically more appropriate.
57
+ */
58
+ ttlSeconds?: number;
41
59
  }
42
60
 
43
61
  /**
@@ -135,16 +153,41 @@ export function createAnthropicAdapter(
135
153
  ): AnthropicAdapter {
136
154
  const { redis, client } = config;
137
155
 
156
+ /**
157
+ * Common per-call config plumbed into both the provider thread
158
+ * manager (for message I/O) and the tiered base manager (for
159
+ * hot↔cold lifecycle ops). Keeping them in lockstep means a single
160
+ * `coldStore` / `ttlSeconds` configuration controls every Redis
161
+ * write the adapter does.
162
+ */
163
+ const baseExtras = {
164
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
165
+ };
166
+
167
+ const makeProviderThread = (threadId: string, threadKey?: string) =>
168
+ createAnthropicThreadManager({
169
+ redis,
170
+ threadId,
171
+ key: threadKey,
172
+ ...baseExtras,
173
+ });
174
+
175
+ const makeTieredBase = (threadId: string, threadKey?: string) =>
176
+ createTieredThreadManager<StoredMessage>({
177
+ redis,
178
+ threadId,
179
+ key: threadKey,
180
+ idOf: storedMessageId,
181
+ ...baseExtras,
182
+ ...(config.coldStore && { coldStore: config.coldStore }),
183
+ });
184
+
138
185
  const threadOps: ThreadOps<AnthropicContent> = {
139
186
  async initializeThread(
140
187
  threadId: string,
141
188
  threadKey?: string
142
189
  ): Promise<void> {
143
- const thread = createAnthropicThreadManager({
144
- redis,
145
- threadId,
146
- key: threadKey,
147
- });
190
+ const thread = makeProviderThread(threadId, threadKey);
148
191
  await thread.initialize();
149
192
  },
150
193
 
@@ -154,11 +197,7 @@ export function createAnthropicAdapter(
154
197
  content: AnthropicContent,
155
198
  threadKey?: string
156
199
  ): Promise<void> {
157
- const thread = createAnthropicThreadManager({
158
- redis,
159
- threadId,
160
- key: threadKey,
161
- });
200
+ const thread = makeProviderThread(threadId, threadKey);
162
201
  await thread.appendUserMessage(id, content);
163
202
  },
164
203
 
@@ -168,21 +207,13 @@ export function createAnthropicAdapter(
168
207
  content: AnthropicSystemContent,
169
208
  threadKey?: string
170
209
  ): Promise<void> {
171
- const thread = createAnthropicThreadManager({
172
- redis,
173
- threadId,
174
- key: threadKey,
175
- });
210
+ const thread = makeProviderThread(threadId, threadKey);
176
211
  await thread.appendSystemMessage(id, content);
177
212
  },
178
213
 
179
214
  async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
180
215
  const { threadId, threadKey, toolCallId, toolName, content } = cfg;
181
- const thread = createAnthropicThreadManager({
182
- redis,
183
- threadId,
184
- key: threadKey,
185
- });
216
+ const thread = makeProviderThread(threadId, threadKey);
186
217
  await thread.appendToolResult(id, toolCallId, toolName, content);
187
218
  },
188
219
 
@@ -192,11 +223,7 @@ export function createAnthropicAdapter(
192
223
  message: Anthropic.Messages.Message,
193
224
  threadKey?: string
194
225
  ): Promise<void> {
195
- const thread = createAnthropicThreadManager({
196
- redis,
197
- threadId,
198
- key: threadKey,
199
- });
226
+ const thread = makeProviderThread(threadId, threadKey);
200
227
  await thread.appendAssistantMessage(id, message.content);
201
228
  },
202
229
 
@@ -210,6 +237,7 @@ export function createAnthropicAdapter(
210
237
  threadId: sourceThreadId,
211
238
  key: threadKey,
212
239
  hooks: config.hooks,
240
+ ...baseExtras,
213
241
  });
214
242
  await thread.fork(targetThreadId);
215
243
  },
@@ -219,7 +247,7 @@ export function createAnthropicAdapter(
219
247
  messageId: string,
220
248
  threadKey?: string,
221
249
  ): Promise<void> {
222
- const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
250
+ const thread = makeProviderThread(threadId, threadKey);
223
251
  await thread.truncateFromId(messageId);
224
252
  },
225
253
 
@@ -227,11 +255,7 @@ export function createAnthropicAdapter(
227
255
  threadId: string,
228
256
  threadKey?: string
229
257
  ): Promise<PersistedThreadState | null> {
230
- const thread = createAnthropicThreadManager({
231
- redis,
232
- threadId,
233
- key: threadKey,
234
- });
258
+ const thread = makeProviderThread(threadId, threadKey);
235
259
  return thread.loadState();
236
260
  },
237
261
 
@@ -240,13 +264,25 @@ export function createAnthropicAdapter(
240
264
  state: PersistedThreadState,
241
265
  threadKey?: string
242
266
  ): Promise<void> {
243
- const thread = createAnthropicThreadManager({
244
- redis,
245
- threadId,
246
- key: threadKey,
247
- });
267
+ const thread = makeProviderThread(threadId, threadKey);
248
268
  await thread.saveState(state);
249
269
  },
270
+
271
+ async hydrateThread(
272
+ threadId: string,
273
+ threadKey?: string
274
+ ): Promise<void> {
275
+ if (!config.coldStore) return;
276
+ await makeTieredBase(threadId, threadKey).hydrate();
277
+ },
278
+
279
+ async flushThread(
280
+ threadId: string,
281
+ threadKey?: string
282
+ ): Promise<void> {
283
+ if (!config.coldStore) return;
284
+ await makeTieredBase(threadId, threadKey).flush();
285
+ },
250
286
  };
251
287
 
252
288
  function createActivities<S extends string = "">(
@@ -35,6 +35,12 @@ export interface AnthropicThreadManagerConfig {
35
35
  /** Thread key, defaults to 'messages' */
36
36
  key?: string;
37
37
  hooks?: AnthropicThreadManagerHooks;
38
+ /**
39
+ * Override the default thread TTL (90 days). When pairing the
40
+ * adapter with a durable cold tier, a shorter TTL (hours) is
41
+ * typically more appropriate.
42
+ */
43
+ ttlSeconds?: number;
38
44
  }
39
45
 
40
46
  /** Prepared payload ready to send to the Anthropic API */
@@ -57,7 +63,8 @@ export interface AnthropicThreadManager extends ProviderThreadManager<
57
63
  prepareForInvocation(): Promise<AnthropicInvocationPayload>;
58
64
  }
59
65
 
60
- function storedMessageId(msg: StoredMessage): string {
66
+ /** Extract the unique id from a {@link StoredMessage}. */
67
+ export function storedMessageId(msg: StoredMessage): string {
61
68
  return msg.id;
62
69
  }
63
70
 
@@ -113,6 +120,7 @@ export function createAnthropicThreadManager(
113
120
  threadId: config.threadId,
114
121
  key: config.key,
115
122
  idOf: storedMessageId,
123
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
116
124
  };
117
125
 
118
126
  const base = createThreadManager(baseConfig);
@@ -13,11 +13,15 @@ import type {
13
13
  ScopedPrefix,
14
14
  } from "../../../lib/session/types";
15
15
  import type { ModelInvoker } from "../../../lib/model";
16
+ import { createTieredThreadManager } from "../../../lib/thread/tiered";
17
+ import type { ColdThreadStore } from "../../../lib/thread/cold-store";
16
18
  import {
17
19
  createGoogleGenAIThreadManager,
20
+ storedContentId,
18
21
  type GoogleGenAIContent,
19
22
  type GoogleGenAISystemContent,
20
23
  type GoogleGenAIThreadManagerHooks,
24
+ type StoredContent,
21
25
  } from "./thread-manager";
22
26
  import { createGoogleGenAIModelInvoker } from "./model-invoker";
23
27
  import { ADAPTER_ID } from "./adapter-id";
@@ -34,6 +38,19 @@ export interface GoogleGenAIAdapterConfig {
34
38
  /** Default model name (e.g. 'gemini-2.5-flash'). If omitted, use `createModelInvoker()` */
35
39
  model?: string;
36
40
  hooks?: GoogleGenAIThreadManagerHooks;
41
+ /**
42
+ * Optional durable cold tier (e.g. S3, R2, GCS). When provided,
43
+ * the session hydrates the thread on entry (`continue`/`fork`) and
44
+ * flushes it on every exit path. When omitted, the adapter is
45
+ * Redis-only and `hydrateThread`/`flushThread` activities are no-ops.
46
+ */
47
+ coldStore?: ColdThreadStore;
48
+ /**
49
+ * Override the default Redis TTL (90 days). When pairing the
50
+ * adapter with a `coldStore`, a shorter TTL (hours) is typically
51
+ * more appropriate.
52
+ */
53
+ ttlSeconds?: number;
37
54
  }
38
55
 
39
56
  /**
@@ -140,16 +157,34 @@ export function createGoogleGenAIAdapter(
140
157
  ): GoogleGenAIAdapter {
141
158
  const { redis } = config;
142
159
 
160
+ const baseExtras = {
161
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
162
+ };
163
+
164
+ const makeProviderThread = (threadId: string, threadKey?: string) =>
165
+ createGoogleGenAIThreadManager({
166
+ redis,
167
+ threadId,
168
+ key: threadKey,
169
+ ...baseExtras,
170
+ });
171
+
172
+ const makeTieredBase = (threadId: string, threadKey?: string) =>
173
+ createTieredThreadManager<StoredContent>({
174
+ redis,
175
+ threadId,
176
+ key: threadKey,
177
+ idOf: storedContentId,
178
+ ...baseExtras,
179
+ ...(config.coldStore && { coldStore: config.coldStore }),
180
+ });
181
+
143
182
  const threadOps: ThreadOps<GoogleGenAIContent> = {
144
183
  async initializeThread(
145
184
  threadId: string,
146
185
  threadKey?: string
147
186
  ): Promise<void> {
148
- const thread = createGoogleGenAIThreadManager({
149
- redis,
150
- threadId,
151
- key: threadKey,
152
- });
187
+ const thread = makeProviderThread(threadId, threadKey);
153
188
  await thread.initialize();
154
189
  },
155
190
 
@@ -159,11 +194,7 @@ export function createGoogleGenAIAdapter(
159
194
  content: GoogleGenAIContent,
160
195
  threadKey?: string
161
196
  ): Promise<void> {
162
- const thread = createGoogleGenAIThreadManager({
163
- redis,
164
- threadId,
165
- key: threadKey,
166
- });
197
+ const thread = makeProviderThread(threadId, threadKey);
167
198
  await thread.appendUserMessage(id, content);
168
199
  },
169
200
 
@@ -173,21 +204,13 @@ export function createGoogleGenAIAdapter(
173
204
  content: GoogleGenAISystemContent,
174
205
  threadKey?: string
175
206
  ): Promise<void> {
176
- const thread = createGoogleGenAIThreadManager({
177
- redis,
178
- threadId,
179
- key: threadKey,
180
- });
207
+ const thread = makeProviderThread(threadId, threadKey);
181
208
  await thread.appendSystemMessage(id, content);
182
209
  },
183
210
 
184
211
  async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
185
212
  const { threadId, threadKey, toolCallId, toolName, content } = cfg;
186
- const thread = createGoogleGenAIThreadManager({
187
- redis,
188
- threadId,
189
- key: threadKey,
190
- });
213
+ const thread = makeProviderThread(threadId, threadKey);
191
214
  await thread.appendToolResult(
192
215
  id,
193
216
  toolCallId,
@@ -202,11 +225,7 @@ export function createGoogleGenAIAdapter(
202
225
  message: Content,
203
226
  threadKey?: string
204
227
  ): Promise<void> {
205
- const thread = createGoogleGenAIThreadManager({
206
- redis,
207
- threadId,
208
- key: threadKey,
209
- });
228
+ const thread = makeProviderThread(threadId, threadKey);
210
229
  await thread.appendModelContent(id, message.parts ?? []);
211
230
  },
212
231
 
@@ -220,6 +239,7 @@ export function createGoogleGenAIAdapter(
220
239
  threadId: sourceThreadId,
221
240
  key: threadKey,
222
241
  hooks: config.hooks,
242
+ ...baseExtras,
223
243
  });
224
244
  await thread.fork(targetThreadId);
225
245
  },
@@ -229,11 +249,7 @@ export function createGoogleGenAIAdapter(
229
249
  messageId: string,
230
250
  threadKey?: string,
231
251
  ): Promise<void> {
232
- const thread = createGoogleGenAIThreadManager({
233
- redis,
234
- threadId,
235
- key: threadKey,
236
- });
252
+ const thread = makeProviderThread(threadId, threadKey);
237
253
  await thread.truncateFromId(messageId);
238
254
  },
239
255
 
@@ -241,11 +257,7 @@ export function createGoogleGenAIAdapter(
241
257
  threadId: string,
242
258
  threadKey?: string
243
259
  ): Promise<PersistedThreadState | null> {
244
- const thread = createGoogleGenAIThreadManager({
245
- redis,
246
- threadId,
247
- key: threadKey,
248
- });
260
+ const thread = makeProviderThread(threadId, threadKey);
249
261
  return thread.loadState();
250
262
  },
251
263
 
@@ -254,13 +266,25 @@ export function createGoogleGenAIAdapter(
254
266
  state: PersistedThreadState,
255
267
  threadKey?: string
256
268
  ): Promise<void> {
257
- const thread = createGoogleGenAIThreadManager({
258
- redis,
259
- threadId,
260
- key: threadKey,
261
- });
269
+ const thread = makeProviderThread(threadId, threadKey);
262
270
  await thread.saveState(state);
263
271
  },
272
+
273
+ async hydrateThread(
274
+ threadId: string,
275
+ threadKey?: string
276
+ ): Promise<void> {
277
+ if (!config.coldStore) return;
278
+ await makeTieredBase(threadId, threadKey).hydrate();
279
+ },
280
+
281
+ async flushThread(
282
+ threadId: string,
283
+ threadKey?: string
284
+ ): Promise<void> {
285
+ if (!config.coldStore) return;
286
+ await makeTieredBase(threadId, threadKey).flush();
287
+ },
264
288
  };
265
289
 
266
290
  function createActivities<S extends string = "">(
@@ -31,6 +31,12 @@ export interface GoogleGenAIThreadManagerConfig {
31
31
  /** Thread key, defaults to 'messages' */
32
32
  key?: string;
33
33
  hooks?: GoogleGenAIThreadManagerHooks;
34
+ /**
35
+ * Override the default thread TTL (90 days). When pairing the
36
+ * adapter with a durable cold tier, a shorter TTL (hours) is
37
+ * typically more appropriate.
38
+ */
39
+ ttlSeconds?: number;
34
40
  }
35
41
 
36
42
  /** Prepared payload ready to send to the Google GenAI API */
@@ -50,7 +56,8 @@ export interface GoogleGenAIThreadManager extends ProviderThreadManager<
50
56
  prepareForInvocation(): Promise<GoogleGenAIInvocationPayload>;
51
57
  }
52
58
 
53
- function storedContentId(msg: StoredContent): string {
59
+ /** Extract the unique id from a {@link StoredContent}. */
60
+ export function storedContentId(msg: StoredContent): string {
54
61
  return msg.id;
55
62
  }
56
63
 
@@ -103,6 +110,7 @@ export function createGoogleGenAIThreadManager(
103
110
  threadId: config.threadId,
104
111
  key: config.key,
105
112
  idOf: storedContentId,
113
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
106
114
  };
107
115
 
108
116
  const base = createThreadManager(baseConfig);
@@ -13,10 +13,13 @@ import type {
13
13
  ScopedPrefix,
14
14
  } from "../../../lib/session/types";
15
15
  import type { ModelInvoker } from "../../../lib/model";
16
+ import { createTieredThreadManager } from "../../../lib/thread/tiered";
17
+ import type { ColdThreadStore } from "../../../lib/thread/cold-store";
16
18
  import type { StoredMessage } from "@langchain/core/messages";
17
19
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
18
20
  import {
19
21
  createLangChainThreadManager,
22
+ storedMessageId,
20
23
  type LangChainContent,
21
24
  type LangChainSystemContent,
22
25
  type LangChainThreadManagerHooks,
@@ -35,6 +38,19 @@ export interface LangChainAdapterConfig {
35
38
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
39
  model?: BaseChatModel<any>;
37
40
  hooks?: LangChainThreadManagerHooks;
41
+ /**
42
+ * Optional durable cold tier (e.g. S3, R2, GCS). When provided,
43
+ * the session hydrates the thread on entry (`continue`/`fork`) and
44
+ * flushes it on every exit path. When omitted, the adapter is
45
+ * Redis-only and `hydrateThread`/`flushThread` activities are no-ops.
46
+ */
47
+ coldStore?: ColdThreadStore;
48
+ /**
49
+ * Override the default Redis TTL (90 days). When pairing the
50
+ * adapter with a `coldStore`, a shorter TTL (hours) is typically
51
+ * more appropriate.
52
+ */
53
+ ttlSeconds?: number;
38
54
  }
39
55
 
40
56
  /**
@@ -117,16 +133,34 @@ export function createLangChainAdapter(
117
133
  ): LangChainAdapter {
118
134
  const { redis } = config;
119
135
 
136
+ const baseExtras = {
137
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
138
+ };
139
+
140
+ const makeProviderThread = (threadId: string, threadKey?: string) =>
141
+ createLangChainThreadManager({
142
+ redis,
143
+ threadId,
144
+ key: threadKey,
145
+ ...baseExtras,
146
+ });
147
+
148
+ const makeTieredBase = (threadId: string, threadKey?: string) =>
149
+ createTieredThreadManager<StoredMessage>({
150
+ redis,
151
+ threadId,
152
+ key: threadKey,
153
+ idOf: storedMessageId,
154
+ ...baseExtras,
155
+ ...(config.coldStore && { coldStore: config.coldStore }),
156
+ });
157
+
120
158
  const threadOps: ThreadOps<LangChainContent> = {
121
159
  async initializeThread(
122
160
  threadId: string,
123
161
  threadKey?: string
124
162
  ): Promise<void> {
125
- const thread = createLangChainThreadManager({
126
- redis,
127
- threadId,
128
- key: threadKey,
129
- });
163
+ const thread = makeProviderThread(threadId, threadKey);
130
164
  await thread.initialize();
131
165
  },
132
166
 
@@ -136,11 +170,7 @@ export function createLangChainAdapter(
136
170
  content: LangChainContent,
137
171
  threadKey?: string
138
172
  ): Promise<void> {
139
- const thread = createLangChainThreadManager({
140
- redis,
141
- threadId,
142
- key: threadKey,
143
- });
173
+ const thread = makeProviderThread(threadId, threadKey);
144
174
  await thread.appendUserMessage(id, content);
145
175
  },
146
176
 
@@ -150,21 +180,13 @@ export function createLangChainAdapter(
150
180
  content: LangChainSystemContent,
151
181
  threadKey?: string
152
182
  ): Promise<void> {
153
- const thread = createLangChainThreadManager({
154
- redis,
155
- threadId,
156
- key: threadKey,
157
- });
183
+ const thread = makeProviderThread(threadId, threadKey);
158
184
  await thread.appendSystemMessage(id, content);
159
185
  },
160
186
 
161
187
  async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
162
188
  const { threadId, threadKey, toolCallId, content } = cfg;
163
- const thread = createLangChainThreadManager({
164
- redis,
165
- threadId,
166
- key: threadKey,
167
- });
189
+ const thread = makeProviderThread(threadId, threadKey);
168
190
  await thread.appendToolResult(id, toolCallId, "", content);
169
191
  },
170
192
 
@@ -174,11 +196,7 @@ export function createLangChainAdapter(
174
196
  message: StoredMessage,
175
197
  threadKey?: string
176
198
  ): Promise<void> {
177
- const thread = createLangChainThreadManager({
178
- redis,
179
- threadId,
180
- key: threadKey,
181
- });
199
+ const thread = makeProviderThread(threadId, threadKey);
182
200
  const patched = { ...message, data: { ...message.data, id } };
183
201
  await thread.append([patched]);
184
202
  },
@@ -193,6 +211,7 @@ export function createLangChainAdapter(
193
211
  threadId: sourceThreadId,
194
212
  key: threadKey,
195
213
  hooks: config.hooks,
214
+ ...baseExtras,
196
215
  });
197
216
  await thread.fork(targetThreadId);
198
217
  },
@@ -202,7 +221,7 @@ export function createLangChainAdapter(
202
221
  messageId: string,
203
222
  threadKey?: string,
204
223
  ): Promise<void> {
205
- const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
224
+ const thread = makeProviderThread(threadId, threadKey);
206
225
  await thread.truncateFromId(messageId);
207
226
  },
208
227
 
@@ -210,11 +229,7 @@ export function createLangChainAdapter(
210
229
  threadId: string,
211
230
  threadKey?: string
212
231
  ): Promise<PersistedThreadState | null> {
213
- const thread = createLangChainThreadManager({
214
- redis,
215
- threadId,
216
- key: threadKey,
217
- });
232
+ const thread = makeProviderThread(threadId, threadKey);
218
233
  return thread.loadState();
219
234
  },
220
235
 
@@ -223,13 +238,25 @@ export function createLangChainAdapter(
223
238
  state: PersistedThreadState,
224
239
  threadKey?: string
225
240
  ): Promise<void> {
226
- const thread = createLangChainThreadManager({
227
- redis,
228
- threadId,
229
- key: threadKey,
230
- });
241
+ const thread = makeProviderThread(threadId, threadKey);
231
242
  await thread.saveState(state);
232
243
  },
244
+
245
+ async hydrateThread(
246
+ threadId: string,
247
+ threadKey?: string
248
+ ): Promise<void> {
249
+ if (!config.coldStore) return;
250
+ await makeTieredBase(threadId, threadKey).hydrate();
251
+ },
252
+
253
+ async flushThread(
254
+ threadId: string,
255
+ threadKey?: string
256
+ ): Promise<void> {
257
+ if (!config.coldStore) return;
258
+ await makeTieredBase(threadId, threadKey).flush();
259
+ },
233
260
  };
234
261
 
235
262
  function createActivities<S extends string = "">(
@@ -34,6 +34,12 @@ export interface LangChainThreadManagerConfig {
34
34
  /** Thread key, defaults to 'messages' */
35
35
  key?: string;
36
36
  hooks?: LangChainThreadManagerHooks;
37
+ /**
38
+ * Override the default thread TTL (90 days). When pairing the
39
+ * adapter with a durable cold tier, a shorter TTL (hours) is
40
+ * typically more appropriate.
41
+ */
42
+ ttlSeconds?: number;
37
43
  }
38
44
 
39
45
  /** Prepared payload ready to send to a LangChain chat model */
@@ -52,7 +58,8 @@ export interface LangChainThreadManager extends ProviderThreadManager<
52
58
  prepareForInvocation(): Promise<LangChainInvocationPayload>;
53
59
  }
54
60
 
55
- function storedMessageId(msg: StoredMessage): string {
61
+ /** Extract the unique id from a LangChain {@link StoredMessage}. */
62
+ export function storedMessageId(msg: StoredMessage): string {
56
63
  if (msg.type === "tool" && msg.data.tool_call_id) {
57
64
  return msg.data.tool_call_id;
58
65
  }
@@ -77,6 +84,7 @@ export function createLangChainThreadManager(
77
84
  threadId: config.threadId,
78
85
  key: config.key,
79
86
  idOf: storedMessageId,
87
+ ...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
80
88
  };
81
89
 
82
90
  const base = createThreadManager(baseConfig);