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
@@ -16,6 +16,9 @@ function getThreadMetaKey(threadKey, threadId) {
16
16
  function getThreadStateKey(threadKey, threadId) {
17
17
  return `${threadKey}:state:thread:${threadId}`;
18
18
  }
19
+ function getThreadDedupKey(threadId, dedupId) {
20
+ return `dedup:${dedupId}:thread:${threadId}`;
21
+ }
19
22
 
20
23
  // src/lib/thread/manager.ts
21
24
  var APPEND_IDEMPOTENT_SCRIPT = `
@@ -29,9 +32,6 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
29
32
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
30
33
  return 1
31
34
  `;
32
- function getDedupKey(threadId, id) {
33
- return `dedup:${id}:thread:${threadId}`;
34
- }
35
35
  function createThreadManager(config) {
36
36
  const {
37
37
  redis,
@@ -39,11 +39,13 @@ function createThreadManager(config) {
39
39
  key = "messages",
40
40
  serialize = (m) => JSON.stringify(m),
41
41
  deserialize = (raw) => JSON.parse(raw),
42
- idOf
42
+ idOf,
43
+ ttlSeconds = THREAD_TTL_SECONDS
43
44
  } = config;
44
45
  const redisKey = getThreadListKey(key, threadId);
45
46
  const metaKey = getThreadMetaKey(key, threadId);
46
47
  const stateKey = getThreadStateKey(key, threadId);
48
+ const dedupKey = (id) => getThreadDedupKey(threadId, id);
47
49
  async function assertThreadExists() {
48
50
  const exists = await redis.exists(metaKey);
49
51
  if (!exists) {
@@ -53,7 +55,7 @@ function createThreadManager(config) {
53
55
  return {
54
56
  async initialize() {
55
57
  await redis.del(redisKey);
56
- await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
58
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
57
59
  },
58
60
  async load() {
59
61
  await assertThreadExists();
@@ -65,18 +67,17 @@ function createThreadManager(config) {
65
67
  await assertThreadExists();
66
68
  if (idOf) {
67
69
  const dedupId = messages.map(idOf).join(":");
68
- const dedupKey = getDedupKey(threadId, dedupId);
69
70
  await redis.eval(
70
71
  APPEND_IDEMPOTENT_SCRIPT,
71
72
  2,
72
- dedupKey,
73
+ dedupKey(dedupId),
73
74
  redisKey,
74
- String(THREAD_TTL_SECONDS),
75
+ String(ttlSeconds),
75
76
  ...messages.map(serialize)
76
77
  );
77
78
  } else {
78
79
  await redis.rpush(redisKey, ...messages.map(serialize));
79
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
80
+ await redis.expire(redisKey, ttlSeconds);
80
81
  }
81
82
  },
82
83
  async fork(newThreadId) {
@@ -91,11 +92,11 @@ function createThreadManager(config) {
91
92
  if (data.length > 0) {
92
93
  const newKey = getThreadListKey(key, newThreadId);
93
94
  await redis.rpush(newKey, ...data);
94
- await redis.expire(newKey, THREAD_TTL_SECONDS);
95
+ await redis.expire(newKey, ttlSeconds);
95
96
  }
96
97
  if (stateRaw != null) {
97
98
  const newStateKey = getThreadStateKey(key, newThreadId);
98
- await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
99
+ await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
99
100
  }
100
101
  return forked;
101
102
  },
@@ -110,15 +111,13 @@ function createThreadManager(config) {
110
111
  const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
111
112
  await redis.del(redisKey);
112
113
  if (existingIds.length > 0) {
113
- await redis.del(
114
- ...existingIds.map((id) => getDedupKey(threadId, id))
115
- );
114
+ await redis.del(...existingIds.map(dedupKey));
116
115
  }
117
116
  if (messages.length > 0) {
118
117
  await redis.rpush(redisKey, ...messages.map(serialize));
119
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
118
+ await redis.expire(redisKey, ttlSeconds);
120
119
  }
121
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
120
+ await redis.expire(metaKey, ttlSeconds);
122
121
  },
123
122
  async delete() {
124
123
  await redis.del(redisKey, metaKey, stateKey);
@@ -130,12 +129,7 @@ function createThreadManager(config) {
130
129
  },
131
130
  async saveState(state) {
132
131
  await assertThreadExists();
133
- await redis.set(
134
- stateKey,
135
- JSON.stringify(state),
136
- "EX",
137
- THREAD_TTL_SECONDS
138
- );
132
+ await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
139
133
  },
140
134
  async deleteState() {
141
135
  await redis.del(stateKey);
@@ -164,21 +158,132 @@ function createThreadManager(config) {
164
158
  if (idx === -1) return;
165
159
  if (idx === 0) {
166
160
  await redis.del(redisKey);
167
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
161
+ await redis.expire(metaKey, ttlSeconds);
168
162
  } else {
169
163
  await redis.ltrim(redisKey, 0, idx - 1);
170
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
164
+ await redis.expire(redisKey, ttlSeconds);
171
165
  }
172
166
  if (removedIds.length > 0) {
173
- await redis.del(
174
- ...removedIds.map((id) => getDedupKey(threadId, id))
175
- );
167
+ await redis.del(...removedIds.map(dedupKey));
176
168
  }
177
169
  }
178
170
  };
179
171
  }
180
172
 
181
- // src/adapters/thread/langchain/thread-manager.ts
173
+ // src/lib/thread/snapshot.ts
174
+ async function encodeSnapshot(config) {
175
+ const { redis, threadKey, threadId, idOf } = config;
176
+ const metaKey = getThreadMetaKey(threadKey, threadId);
177
+ if (await redis.exists(metaKey) === 0) {
178
+ return null;
179
+ }
180
+ const listKey = getThreadListKey(threadKey, threadId);
181
+ const stateKey = getThreadStateKey(threadKey, threadId);
182
+ const messages = await redis.lrange(listKey, 0, -1);
183
+ const stateRaw = await redis.get(stateKey);
184
+ const state = stateRaw == null ? null : JSON.parse(stateRaw);
185
+ const dedupIds = idOf ? messages.map(idOf) : [];
186
+ return { v: 1, messages, state, dedupIds };
187
+ }
188
+ async function applySnapshot(config) {
189
+ const {
190
+ redis,
191
+ threadKey,
192
+ threadId,
193
+ snapshot,
194
+ ttlSeconds = THREAD_TTL_SECONDS
195
+ } = config;
196
+ const metaKey = getThreadMetaKey(threadKey, threadId);
197
+ if (await redis.exists(metaKey) === 1) {
198
+ return;
199
+ }
200
+ const listKey = getThreadListKey(threadKey, threadId);
201
+ const stateKey = getThreadStateKey(threadKey, threadId);
202
+ await redis.del(listKey, stateKey);
203
+ const pipeline = redis.pipeline();
204
+ if (snapshot.messages.length > 0) {
205
+ pipeline.rpush(listKey, ...snapshot.messages);
206
+ pipeline.expire(listKey, ttlSeconds);
207
+ }
208
+ if (snapshot.state != null) {
209
+ pipeline.set(stateKey, JSON.stringify(snapshot.state), "EX", ttlSeconds);
210
+ }
211
+ for (const id of snapshot.dedupIds) {
212
+ pipeline.set(getThreadDedupKey(threadId, id), "1", "EX", ttlSeconds);
213
+ }
214
+ const results = await pipeline.exec();
215
+ if (results) {
216
+ const firstErr = results.find(([err]) => err)?.[0] ?? null;
217
+ if (firstErr) {
218
+ await redis.del(
219
+ listKey,
220
+ stateKey,
221
+ ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
222
+ ).catch(() => void 0);
223
+ throw firstErr;
224
+ }
225
+ }
226
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
227
+ }
228
+ async function clearHotTier(config) {
229
+ const { redis, threadKey, threadId, dedupIds = [] } = config;
230
+ const keys = [
231
+ getThreadListKey(threadKey, threadId),
232
+ getThreadMetaKey(threadKey, threadId),
233
+ getThreadStateKey(threadKey, threadId),
234
+ ...dedupIds.map((id) => getThreadDedupKey(threadId, id))
235
+ ];
236
+ await redis.del(...keys);
237
+ }
238
+
239
+ // src/lib/thread/tiered.ts
240
+ function createTieredThreadManager(config) {
241
+ const {
242
+ redis,
243
+ threadId,
244
+ key = "messages",
245
+ coldStore,
246
+ idOf,
247
+ deserialize = (raw) => JSON.parse(raw),
248
+ ttlSeconds = THREAD_TTL_SECONDS
249
+ } = config;
250
+ const base = createThreadManager(config);
251
+ const rawIdOf = idOf ? (raw) => idOf(deserialize(raw)) : void 0;
252
+ return Object.assign(base, {
253
+ async hydrate() {
254
+ if (!coldStore) return;
255
+ const snapshot = await coldStore.read(key, threadId);
256
+ if (!snapshot) return;
257
+ await applySnapshot({
258
+ redis,
259
+ threadKey: key,
260
+ threadId,
261
+ snapshot,
262
+ ttlSeconds
263
+ });
264
+ },
265
+ async flush(opts) {
266
+ if (!coldStore) return;
267
+ const snapshot = await encodeSnapshot({
268
+ redis,
269
+ threadKey: key,
270
+ threadId,
271
+ ...rawIdOf ? { idOf: rawIdOf } : {}
272
+ });
273
+ if (!snapshot) return;
274
+ await coldStore.write(key, threadId, snapshot);
275
+ const deleteHot = opts?.deleteHot ?? true;
276
+ if (deleteHot) {
277
+ await clearHotTier({
278
+ redis,
279
+ threadKey: key,
280
+ threadId,
281
+ dedupIds: snapshot.dedupIds
282
+ });
283
+ }
284
+ }
285
+ });
286
+ }
182
287
  function storedMessageId(msg) {
183
288
  if (msg.type === "tool" && msg.data.tool_call_id) {
184
289
  return msg.data.tool_call_id;
@@ -193,7 +298,8 @@ function createLangChainThreadManager(config) {
193
298
  redis: config.redis,
194
299
  threadId: config.threadId,
195
300
  key: config.key,
196
- idOf: storedMessageId
301
+ idOf: storedMessageId,
302
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
197
303
  };
198
304
  const base = createThreadManager(baseConfig);
199
305
  const helpers = {
@@ -371,46 +477,43 @@ async function invokeLangChainModel({
371
477
  // src/adapters/thread/langchain/activities.ts
372
478
  function createLangChainAdapter(config) {
373
479
  const { redis } = config;
480
+ const baseExtras = {
481
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
482
+ };
483
+ const makeProviderThread = (threadId, threadKey) => createLangChainThreadManager({
484
+ redis,
485
+ threadId,
486
+ key: threadKey,
487
+ ...baseExtras
488
+ });
489
+ const makeTieredBase = (threadId, threadKey) => createTieredThreadManager({
490
+ redis,
491
+ threadId,
492
+ key: threadKey,
493
+ idOf: storedMessageId,
494
+ ...baseExtras,
495
+ ...config.coldStore && { coldStore: config.coldStore }
496
+ });
374
497
  const threadOps = {
375
498
  async initializeThread(threadId, threadKey) {
376
- const thread = createLangChainThreadManager({
377
- redis,
378
- threadId,
379
- key: threadKey
380
- });
499
+ const thread = makeProviderThread(threadId, threadKey);
381
500
  await thread.initialize();
382
501
  },
383
502
  async appendHumanMessage(threadId, id, content, threadKey) {
384
- const thread = createLangChainThreadManager({
385
- redis,
386
- threadId,
387
- key: threadKey
388
- });
503
+ const thread = makeProviderThread(threadId, threadKey);
389
504
  await thread.appendUserMessage(id, content);
390
505
  },
391
506
  async appendSystemMessage(threadId, id, content, threadKey) {
392
- const thread = createLangChainThreadManager({
393
- redis,
394
- threadId,
395
- key: threadKey
396
- });
507
+ const thread = makeProviderThread(threadId, threadKey);
397
508
  await thread.appendSystemMessage(id, content);
398
509
  },
399
510
  async appendToolResult(id, cfg) {
400
511
  const { threadId, threadKey, toolCallId, content } = cfg;
401
- const thread = createLangChainThreadManager({
402
- redis,
403
- threadId,
404
- key: threadKey
405
- });
512
+ const thread = makeProviderThread(threadId, threadKey);
406
513
  await thread.appendToolResult(id, toolCallId, "", content);
407
514
  },
408
515
  async appendAgentMessage(threadId, id, message, threadKey) {
409
- const thread = createLangChainThreadManager({
410
- redis,
411
- threadId,
412
- key: threadKey
413
- });
516
+ const thread = makeProviderThread(threadId, threadKey);
414
517
  const patched = { ...message, data: { ...message.data, id } };
415
518
  await thread.append([patched]);
416
519
  },
@@ -419,29 +522,30 @@ function createLangChainAdapter(config) {
419
522
  redis,
420
523
  threadId: sourceThreadId,
421
524
  key: threadKey,
422
- hooks: config.hooks
525
+ hooks: config.hooks,
526
+ ...baseExtras
423
527
  });
424
528
  await thread.fork(targetThreadId);
425
529
  },
426
530
  async truncateThread(threadId, messageId, threadKey) {
427
- const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
531
+ const thread = makeProviderThread(threadId, threadKey);
428
532
  await thread.truncateFromId(messageId);
429
533
  },
430
534
  async loadThreadState(threadId, threadKey) {
431
- const thread = createLangChainThreadManager({
432
- redis,
433
- threadId,
434
- key: threadKey
435
- });
535
+ const thread = makeProviderThread(threadId, threadKey);
436
536
  return thread.loadState();
437
537
  },
438
538
  async saveThreadState(threadId, state, threadKey) {
439
- const thread = createLangChainThreadManager({
440
- redis,
441
- threadId,
442
- key: threadKey
443
- });
539
+ const thread = makeProviderThread(threadId, threadKey);
444
540
  await thread.saveState(state);
541
+ },
542
+ async hydrateThread(threadId, threadKey) {
543
+ if (!config.coldStore) return;
544
+ await makeTieredBase(threadId, threadKey).hydrate();
545
+ },
546
+ async flushThread(threadId, threadKey) {
547
+ if (!config.coldStore) return;
548
+ await makeTieredBase(threadId, threadKey).flush();
445
549
  }
446
550
  };
447
551
  function createActivities(scope) {