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
@@ -14,6 +14,9 @@ function getThreadMetaKey(threadKey, threadId) {
14
14
  function getThreadStateKey(threadKey, threadId) {
15
15
  return `${threadKey}:state:thread:${threadId}`;
16
16
  }
17
+ function getThreadDedupKey(threadId, dedupId) {
18
+ return `dedup:${dedupId}:thread:${threadId}`;
19
+ }
17
20
 
18
21
  // src/lib/thread/manager.ts
19
22
  var APPEND_IDEMPOTENT_SCRIPT = `
@@ -27,9 +30,6 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
27
30
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
28
31
  return 1
29
32
  `;
30
- function getDedupKey(threadId, id) {
31
- return `dedup:${id}:thread:${threadId}`;
32
- }
33
33
  function createThreadManager(config) {
34
34
  const {
35
35
  redis,
@@ -37,11 +37,13 @@ function createThreadManager(config) {
37
37
  key = "messages",
38
38
  serialize = (m) => JSON.stringify(m),
39
39
  deserialize = (raw) => JSON.parse(raw),
40
- idOf
40
+ idOf,
41
+ ttlSeconds = THREAD_TTL_SECONDS
41
42
  } = config;
42
43
  const redisKey = getThreadListKey(key, threadId);
43
44
  const metaKey = getThreadMetaKey(key, threadId);
44
45
  const stateKey = getThreadStateKey(key, threadId);
46
+ const dedupKey = (id) => getThreadDedupKey(threadId, id);
45
47
  async function assertThreadExists() {
46
48
  const exists = await redis.exists(metaKey);
47
49
  if (!exists) {
@@ -51,7 +53,7 @@ function createThreadManager(config) {
51
53
  return {
52
54
  async initialize() {
53
55
  await redis.del(redisKey);
54
- await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
56
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
55
57
  },
56
58
  async load() {
57
59
  await assertThreadExists();
@@ -63,18 +65,17 @@ function createThreadManager(config) {
63
65
  await assertThreadExists();
64
66
  if (idOf) {
65
67
  const dedupId = messages.map(idOf).join(":");
66
- const dedupKey = getDedupKey(threadId, dedupId);
67
68
  await redis.eval(
68
69
  APPEND_IDEMPOTENT_SCRIPT,
69
70
  2,
70
- dedupKey,
71
+ dedupKey(dedupId),
71
72
  redisKey,
72
- String(THREAD_TTL_SECONDS),
73
+ String(ttlSeconds),
73
74
  ...messages.map(serialize)
74
75
  );
75
76
  } else {
76
77
  await redis.rpush(redisKey, ...messages.map(serialize));
77
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
78
+ await redis.expire(redisKey, ttlSeconds);
78
79
  }
79
80
  },
80
81
  async fork(newThreadId) {
@@ -89,11 +90,11 @@ function createThreadManager(config) {
89
90
  if (data.length > 0) {
90
91
  const newKey = getThreadListKey(key, newThreadId);
91
92
  await redis.rpush(newKey, ...data);
92
- await redis.expire(newKey, THREAD_TTL_SECONDS);
93
+ await redis.expire(newKey, ttlSeconds);
93
94
  }
94
95
  if (stateRaw != null) {
95
96
  const newStateKey = getThreadStateKey(key, newThreadId);
96
- await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
97
+ await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
97
98
  }
98
99
  return forked;
99
100
  },
@@ -108,15 +109,13 @@ function createThreadManager(config) {
108
109
  const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
109
110
  await redis.del(redisKey);
110
111
  if (existingIds.length > 0) {
111
- await redis.del(
112
- ...existingIds.map((id) => getDedupKey(threadId, id))
113
- );
112
+ await redis.del(...existingIds.map(dedupKey));
114
113
  }
115
114
  if (messages.length > 0) {
116
115
  await redis.rpush(redisKey, ...messages.map(serialize));
117
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
116
+ await redis.expire(redisKey, ttlSeconds);
118
117
  }
119
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
118
+ await redis.expire(metaKey, ttlSeconds);
120
119
  },
121
120
  async delete() {
122
121
  await redis.del(redisKey, metaKey, stateKey);
@@ -128,12 +127,7 @@ function createThreadManager(config) {
128
127
  },
129
128
  async saveState(state) {
130
129
  await assertThreadExists();
131
- await redis.set(
132
- stateKey,
133
- JSON.stringify(state),
134
- "EX",
135
- THREAD_TTL_SECONDS
136
- );
130
+ await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
137
131
  },
138
132
  async deleteState() {
139
133
  await redis.del(stateKey);
@@ -162,20 +156,133 @@ function createThreadManager(config) {
162
156
  if (idx === -1) return;
163
157
  if (idx === 0) {
164
158
  await redis.del(redisKey);
165
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
159
+ await redis.expire(metaKey, ttlSeconds);
166
160
  } else {
167
161
  await redis.ltrim(redisKey, 0, idx - 1);
168
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
162
+ await redis.expire(redisKey, ttlSeconds);
169
163
  }
170
164
  if (removedIds.length > 0) {
171
- await redis.del(
172
- ...removedIds.map((id) => getDedupKey(threadId, id))
173
- );
165
+ await redis.del(...removedIds.map(dedupKey));
174
166
  }
175
167
  }
176
168
  };
177
169
  }
178
170
 
171
+ // src/lib/thread/snapshot.ts
172
+ async function encodeSnapshot(config) {
173
+ const { redis, threadKey, threadId, idOf } = config;
174
+ const metaKey = getThreadMetaKey(threadKey, threadId);
175
+ if (await redis.exists(metaKey) === 0) {
176
+ return null;
177
+ }
178
+ const listKey = getThreadListKey(threadKey, threadId);
179
+ const stateKey = getThreadStateKey(threadKey, threadId);
180
+ const messages = await redis.lrange(listKey, 0, -1);
181
+ const stateRaw = await redis.get(stateKey);
182
+ const state = stateRaw == null ? null : JSON.parse(stateRaw);
183
+ const dedupIds = idOf ? messages.map(idOf) : [];
184
+ return { v: 1, messages, state, dedupIds };
185
+ }
186
+ async function applySnapshot(config) {
187
+ const {
188
+ redis,
189
+ threadKey,
190
+ threadId,
191
+ snapshot,
192
+ ttlSeconds = THREAD_TTL_SECONDS
193
+ } = config;
194
+ const metaKey = getThreadMetaKey(threadKey, threadId);
195
+ if (await redis.exists(metaKey) === 1) {
196
+ return;
197
+ }
198
+ const listKey = getThreadListKey(threadKey, threadId);
199
+ const stateKey = getThreadStateKey(threadKey, threadId);
200
+ await redis.del(listKey, stateKey);
201
+ const pipeline = redis.pipeline();
202
+ if (snapshot.messages.length > 0) {
203
+ pipeline.rpush(listKey, ...snapshot.messages);
204
+ pipeline.expire(listKey, ttlSeconds);
205
+ }
206
+ if (snapshot.state != null) {
207
+ pipeline.set(stateKey, JSON.stringify(snapshot.state), "EX", ttlSeconds);
208
+ }
209
+ for (const id of snapshot.dedupIds) {
210
+ pipeline.set(getThreadDedupKey(threadId, id), "1", "EX", ttlSeconds);
211
+ }
212
+ const results = await pipeline.exec();
213
+ if (results) {
214
+ const firstErr = results.find(([err]) => err)?.[0] ?? null;
215
+ if (firstErr) {
216
+ await redis.del(
217
+ listKey,
218
+ stateKey,
219
+ ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
220
+ ).catch(() => void 0);
221
+ throw firstErr;
222
+ }
223
+ }
224
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
225
+ }
226
+ async function clearHotTier(config) {
227
+ const { redis, threadKey, threadId, dedupIds = [] } = config;
228
+ const keys = [
229
+ getThreadListKey(threadKey, threadId),
230
+ getThreadMetaKey(threadKey, threadId),
231
+ getThreadStateKey(threadKey, threadId),
232
+ ...dedupIds.map((id) => getThreadDedupKey(threadId, id))
233
+ ];
234
+ await redis.del(...keys);
235
+ }
236
+
237
+ // src/lib/thread/tiered.ts
238
+ function createTieredThreadManager(config) {
239
+ const {
240
+ redis,
241
+ threadId,
242
+ key = "messages",
243
+ coldStore,
244
+ idOf,
245
+ deserialize = (raw) => JSON.parse(raw),
246
+ ttlSeconds = THREAD_TTL_SECONDS
247
+ } = config;
248
+ const base = createThreadManager(config);
249
+ const rawIdOf = idOf ? (raw) => idOf(deserialize(raw)) : void 0;
250
+ return Object.assign(base, {
251
+ async hydrate() {
252
+ if (!coldStore) return;
253
+ const snapshot = await coldStore.read(key, threadId);
254
+ if (!snapshot) return;
255
+ await applySnapshot({
256
+ redis,
257
+ threadKey: key,
258
+ threadId,
259
+ snapshot,
260
+ ttlSeconds
261
+ });
262
+ },
263
+ async flush(opts) {
264
+ if (!coldStore) return;
265
+ const snapshot = await encodeSnapshot({
266
+ redis,
267
+ threadKey: key,
268
+ threadId,
269
+ ...rawIdOf ? { idOf: rawIdOf } : {}
270
+ });
271
+ if (!snapshot) return;
272
+ await coldStore.write(key, threadId, snapshot);
273
+ const deleteHot = opts?.deleteHot ?? true;
274
+ if (deleteHot) {
275
+ await clearHotTier({
276
+ redis,
277
+ threadKey: key,
278
+ threadId,
279
+ dedupIds: snapshot.dedupIds
280
+ });
281
+ }
282
+ }
283
+ });
284
+ }
285
+
179
286
  // src/adapters/thread/google-genai/thread-manager.ts
180
287
  function storedContentId(msg) {
181
288
  return msg.id;
@@ -209,7 +316,8 @@ function createGoogleGenAIThreadManager(config) {
209
316
  redis: config.redis,
210
317
  threadId: config.threadId,
211
318
  key: config.key,
212
- idOf: storedContentId
319
+ idOf: storedContentId,
320
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
213
321
  };
214
322
  const base = createThreadManager(baseConfig);
215
323
  const helpers = {
@@ -391,38 +499,39 @@ async function invokeGoogleGenAIModel({
391
499
  // src/adapters/thread/google-genai/activities.ts
392
500
  function createGoogleGenAIAdapter(config) {
393
501
  const { redis } = config;
502
+ const baseExtras = {
503
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
504
+ };
505
+ const makeProviderThread = (threadId, threadKey) => createGoogleGenAIThreadManager({
506
+ redis,
507
+ threadId,
508
+ key: threadKey,
509
+ ...baseExtras
510
+ });
511
+ const makeTieredBase = (threadId, threadKey) => createTieredThreadManager({
512
+ redis,
513
+ threadId,
514
+ key: threadKey,
515
+ idOf: storedContentId,
516
+ ...baseExtras,
517
+ ...config.coldStore && { coldStore: config.coldStore }
518
+ });
394
519
  const threadOps = {
395
520
  async initializeThread(threadId, threadKey) {
396
- const thread = createGoogleGenAIThreadManager({
397
- redis,
398
- threadId,
399
- key: threadKey
400
- });
521
+ const thread = makeProviderThread(threadId, threadKey);
401
522
  await thread.initialize();
402
523
  },
403
524
  async appendHumanMessage(threadId, id, content, threadKey) {
404
- const thread = createGoogleGenAIThreadManager({
405
- redis,
406
- threadId,
407
- key: threadKey
408
- });
525
+ const thread = makeProviderThread(threadId, threadKey);
409
526
  await thread.appendUserMessage(id, content);
410
527
  },
411
528
  async appendSystemMessage(threadId, id, content, threadKey) {
412
- const thread = createGoogleGenAIThreadManager({
413
- redis,
414
- threadId,
415
- key: threadKey
416
- });
529
+ const thread = makeProviderThread(threadId, threadKey);
417
530
  await thread.appendSystemMessage(id, content);
418
531
  },
419
532
  async appendToolResult(id, cfg) {
420
533
  const { threadId, threadKey, toolCallId, toolName, content } = cfg;
421
- const thread = createGoogleGenAIThreadManager({
422
- redis,
423
- threadId,
424
- key: threadKey
425
- });
534
+ const thread = makeProviderThread(threadId, threadKey);
426
535
  await thread.appendToolResult(
427
536
  id,
428
537
  toolCallId,
@@ -431,11 +540,7 @@ function createGoogleGenAIAdapter(config) {
431
540
  );
432
541
  },
433
542
  async appendAgentMessage(threadId, id, message, threadKey) {
434
- const thread = createGoogleGenAIThreadManager({
435
- redis,
436
- threadId,
437
- key: threadKey
438
- });
543
+ const thread = makeProviderThread(threadId, threadKey);
439
544
  await thread.appendModelContent(id, message.parts ?? []);
440
545
  },
441
546
  async forkThread(sourceThreadId, targetThreadId, threadKey) {
@@ -443,33 +548,30 @@ function createGoogleGenAIAdapter(config) {
443
548
  redis,
444
549
  threadId: sourceThreadId,
445
550
  key: threadKey,
446
- hooks: config.hooks
551
+ hooks: config.hooks,
552
+ ...baseExtras
447
553
  });
448
554
  await thread.fork(targetThreadId);
449
555
  },
450
556
  async truncateThread(threadId, messageId, threadKey) {
451
- const thread = createGoogleGenAIThreadManager({
452
- redis,
453
- threadId,
454
- key: threadKey
455
- });
557
+ const thread = makeProviderThread(threadId, threadKey);
456
558
  await thread.truncateFromId(messageId);
457
559
  },
458
560
  async loadThreadState(threadId, threadKey) {
459
- const thread = createGoogleGenAIThreadManager({
460
- redis,
461
- threadId,
462
- key: threadKey
463
- });
561
+ const thread = makeProviderThread(threadId, threadKey);
464
562
  return thread.loadState();
465
563
  },
466
564
  async saveThreadState(threadId, state, threadKey) {
467
- const thread = createGoogleGenAIThreadManager({
468
- redis,
469
- threadId,
470
- key: threadKey
471
- });
565
+ const thread = makeProviderThread(threadId, threadKey);
472
566
  await thread.saveState(state);
567
+ },
568
+ async hydrateThread(threadId, threadKey) {
569
+ if (!config.coldStore) return;
570
+ await makeTieredBase(threadId, threadKey).hydrate();
571
+ },
572
+ async flushThread(threadId, threadKey) {
573
+ if (!config.coldStore) return;
574
+ await makeTieredBase(threadId, threadKey).flush();
473
575
  }
474
576
  };
475
577
  function createActivities(scope) {