zeitlich 0.2.38 → 0.2.40

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 (125) hide show
  1. package/README.md +18 -0
  2. package/dist/{activities-BKhMtKDd.d.ts → activities-CULxRzJ1.d.ts} +4 -6
  3. package/dist/{activities-CDcwkRZs.d.cts → activities-CvUrG3YG.d.cts} +4 -6
  4. package/dist/adapter-id-BB-mmrts.d.cts +17 -0
  5. package/dist/adapter-id-BB-mmrts.d.ts +17 -0
  6. package/dist/adapter-id-CMwVrVqv.d.cts +17 -0
  7. package/dist/adapter-id-CMwVrVqv.d.ts +17 -0
  8. package/dist/adapter-id-CbY2zeSt.d.cts +17 -0
  9. package/dist/adapter-id-CbY2zeSt.d.ts +17 -0
  10. package/dist/adapters/thread/anthropic/index.cjs +140 -23
  11. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  12. package/dist/adapters/thread/anthropic/index.d.cts +8 -7
  13. package/dist/adapters/thread/anthropic/index.d.ts +8 -7
  14. package/dist/adapters/thread/anthropic/index.js +140 -24
  15. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  16. package/dist/adapters/thread/anthropic/workflow.cjs +8 -3
  17. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  18. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -4
  19. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -4
  20. package/dist/adapters/thread/anthropic/workflow.js +8 -4
  21. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  22. package/dist/adapters/thread/google-genai/index.cjs +140 -23
  23. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/index.d.cts +5 -4
  25. package/dist/adapters/thread/google-genai/index.d.ts +5 -4
  26. package/dist/adapters/thread/google-genai/index.js +140 -24
  27. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  28. package/dist/adapters/thread/google-genai/workflow.cjs +8 -3
  29. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  30. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -4
  31. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -4
  32. package/dist/adapters/thread/google-genai/workflow.js +8 -4
  33. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  34. package/dist/adapters/thread/index.cjs +16 -0
  35. package/dist/adapters/thread/index.cjs.map +1 -0
  36. package/dist/adapters/thread/index.d.cts +34 -0
  37. package/dist/adapters/thread/index.d.ts +34 -0
  38. package/dist/adapters/thread/index.js +12 -0
  39. package/dist/adapters/thread/index.js.map +1 -0
  40. package/dist/adapters/thread/langchain/index.cjs +139 -24
  41. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  42. package/dist/adapters/thread/langchain/index.d.cts +8 -7
  43. package/dist/adapters/thread/langchain/index.d.ts +8 -7
  44. package/dist/adapters/thread/langchain/index.js +139 -25
  45. package/dist/adapters/thread/langchain/index.js.map +1 -1
  46. package/dist/adapters/thread/langchain/workflow.cjs +8 -3
  47. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  48. package/dist/adapters/thread/langchain/workflow.d.cts +5 -4
  49. package/dist/adapters/thread/langchain/workflow.d.ts +5 -4
  50. package/dist/adapters/thread/langchain/workflow.js +8 -4
  51. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  52. package/dist/index.cjs +267 -48
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +6 -6
  55. package/dist/index.d.ts +6 -6
  56. package/dist/index.js +264 -49
  57. package/dist/index.js.map +1 -1
  58. package/dist/{proxy-D_3x7RN4.d.cts → proxy-5EbwzaY4.d.cts} +1 -1
  59. package/dist/{proxy-CUlKSvZS.d.ts → proxy-wZufFfBh.d.ts} +1 -1
  60. package/dist/{thread-manager-CVu7o2cs.d.ts → thread-manager-BNiIt5r8.d.ts} +2 -4
  61. package/dist/{thread-manager-c1gPopAG.d.ts → thread-manager-BoN5DOvG.d.cts} +2 -4
  62. package/dist/{thread-manager-wGi-LqIP.d.cts → thread-manager-BqBAIsED.d.ts} +2 -4
  63. package/dist/{thread-manager-HSwyh28L.d.cts → thread-manager-DF8WuCRs.d.cts} +2 -4
  64. package/dist/{types-BH_IRryz.d.ts → types-C7OoY7h8.d.ts} +54 -6
  65. package/dist/{types-C06FwR96.d.cts → types-Cn2r3ol3.d.cts} +163 -44
  66. package/dist/{types-BaOw4hKI.d.cts → types-CuISs0Ub.d.cts} +54 -6
  67. package/dist/{types-DNr31FzL.d.ts → types-DeQH84C_.d.ts} +163 -44
  68. package/dist/{workflow-CSCkpwAL.d.ts → workflow-C2MZZj5K.d.ts} +82 -2
  69. package/dist/{workflow-DuvMZ8Vm.d.cts → workflow-DhplIN65.d.cts} +82 -2
  70. package/dist/workflow.cjs +189 -37
  71. package/dist/workflow.cjs.map +1 -1
  72. package/dist/workflow.d.cts +2 -2
  73. package/dist/workflow.d.ts +2 -2
  74. package/dist/workflow.js +186 -38
  75. package/dist/workflow.js.map +1 -1
  76. package/package.json +11 -1
  77. package/src/adapters/thread/adapter-id.test.ts +42 -0
  78. package/src/adapters/thread/anthropic/activities.ts +33 -7
  79. package/src/adapters/thread/anthropic/adapter-id.ts +16 -0
  80. package/src/adapters/thread/anthropic/fork-transform.test.ts +291 -0
  81. package/src/adapters/thread/anthropic/index.ts +3 -0
  82. package/src/adapters/thread/anthropic/model-invoker.ts +8 -4
  83. package/src/adapters/thread/anthropic/proxy.ts +3 -2
  84. package/src/adapters/thread/anthropic/thread-manager.ts +27 -4
  85. package/src/adapters/thread/google-genai/activities.ts +33 -7
  86. package/src/adapters/thread/google-genai/adapter-id.ts +16 -0
  87. package/src/adapters/thread/google-genai/fork-transform.test.ts +149 -0
  88. package/src/adapters/thread/google-genai/index.ts +3 -0
  89. package/src/adapters/thread/google-genai/model-invoker.ts +7 -3
  90. package/src/adapters/thread/google-genai/proxy.ts +3 -2
  91. package/src/adapters/thread/google-genai/thread-manager.ts +27 -4
  92. package/src/adapters/thread/index.ts +39 -0
  93. package/src/adapters/thread/langchain/activities.ts +33 -7
  94. package/src/adapters/thread/langchain/adapter-id.ts +16 -0
  95. package/src/adapters/thread/langchain/fork-transform.test.ts +142 -0
  96. package/src/adapters/thread/langchain/index.ts +3 -0
  97. package/src/adapters/thread/langchain/model-invoker.ts +8 -3
  98. package/src/adapters/thread/langchain/proxy.ts +3 -2
  99. package/src/adapters/thread/langchain/thread-manager.ts +27 -4
  100. package/src/lib/lifecycle.ts +3 -1
  101. package/src/lib/model/types.ts +7 -10
  102. package/src/lib/session/session-edge-cases.integration.test.ts +131 -63
  103. package/src/lib/session/session.integration.test.ts +174 -5
  104. package/src/lib/session/session.ts +69 -28
  105. package/src/lib/session/types.ts +61 -9
  106. package/src/lib/state/index.ts +1 -0
  107. package/src/lib/state/manager.integration.test.ts +109 -0
  108. package/src/lib/state/manager.ts +38 -8
  109. package/src/lib/state/types.ts +25 -0
  110. package/src/lib/subagent/handler.ts +124 -11
  111. package/src/lib/subagent/index.ts +5 -1
  112. package/src/lib/subagent/subagent.integration.test.ts +528 -0
  113. package/src/lib/subagent/types.ts +63 -14
  114. package/src/lib/subagent/workflow.ts +29 -2
  115. package/src/lib/thread/index.ts +5 -0
  116. package/src/lib/thread/keys.test.ts +101 -0
  117. package/src/lib/thread/keys.ts +94 -0
  118. package/src/lib/thread/manager.test.ts +139 -0
  119. package/src/lib/thread/manager.ts +92 -14
  120. package/src/lib/thread/proxy.ts +2 -0
  121. package/src/lib/thread/types.ts +60 -6
  122. package/src/lib/tool-router/types.ts +16 -8
  123. package/src/lib/types.ts +12 -0
  124. package/src/workflow.ts +12 -1
  125. package/tsup.config.ts +1 -0
package/README.md CHANGED
@@ -482,6 +482,24 @@ const session = await createSession({
482
482
 
483
483
  The `Subagent` tool is automatically added when subagents are configured, allowing the LLM to spawn child workflows.
484
484
 
485
+ #### Child workflow timeouts
486
+
487
+ Every subagent child workflow runs with a default `workflowRunTimeout` of `1h` (exported as `DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT`). This is a safety bound: without it, a child that fails to initialize or repeatedly fails workflow tasks is retried forever by Temporal and the parent's `Subagent` tool call would hang indefinitely. With it, Temporal eventually terminates the child and the parent receives a structured `ChildWorkflowFailure` which the tool router surfaces to the LLM through the normal failure-hook pipeline (`onPostToolUseFailure`, per-subagent `onExecutionFailure`).
488
+
489
+ You can override the default — or set any other `ChildWorkflowOptions` — via the `workflowOptions` field:
490
+
491
+ ```typescript
492
+ export const researcherSubagent = defineSubagent(researcherWorkflow, {
493
+ workflowOptions: {
494
+ workflowRunTimeout: "10m",
495
+ workflowTaskTimeout: "1m",
496
+ retry: { maximumAttempts: 2 },
497
+ },
498
+ });
499
+ ```
500
+
501
+ `workflowId`, `taskQueue`, and `args` are managed by the subagent handler itself and cannot be overridden via `workflowOptions` — use the top-level `taskQueue` field on `SubagentConfig` to route a subagent to a different task queue.
502
+
485
503
  ### Skills
486
504
 
487
505
  Zeitlich has first-class support for the [agentskills.io](https://agentskills.io) specification. Skills are reusable instruction sets that an agent can load on-demand via the built-in `ReadSkill` tool — progressive disclosure keeps token usage low while giving agents access to rich, domain-specific guidance.
@@ -1,7 +1,8 @@
1
1
  import Redis from 'ioredis';
2
2
  import { Part, Content, GoogleGenAI } from '@google/genai';
3
- import { a as ModelInvoker, P as PrefixedThreadOps, S as ScopedPrefix, R as RouterContext, b as ToolHandlerResponse, c as ActivityToolHandler } from './types-DNr31FzL.js';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BH_IRryz.js';
3
+ import { a as ModelInvoker, b as PrefixedThreadOps, S as ScopedPrefix, R as RouterContext, c as ToolHandlerResponse, d as ActivityToolHandler } from './types-DeQH84C_.js';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-C7OoY7h8.js';
5
+ import { A as ADAPTER_ID } from './adapter-id-BB-mmrts.js';
5
6
 
6
7
  /** SDK-native content type for Google GenAI human messages */
7
8
  type GoogleGenAIContent = string | Part[];
@@ -24,8 +25,6 @@ interface GoogleGenAIThreadManagerConfig {
24
25
  interface GoogleGenAIInvocationPayload {
25
26
  contents: Content[];
26
27
  systemInstruction?: Part[];
27
- /** Number of stored messages loaded from Redis before preparation. */
28
- storedLength: number;
29
28
  }
30
29
  /** Thread manager with Google GenAI Content convenience helpers */
31
30
  interface GoogleGenAIThreadManager extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse, GoogleGenAISystemContent> {
@@ -39,8 +38,7 @@ interface GoogleGenAIThreadManager extends ProviderThreadManager<StoredContent,
39
38
  */
40
39
  declare function createGoogleGenAIThreadManager(config: GoogleGenAIThreadManagerConfig): GoogleGenAIThreadManager;
41
40
 
42
- declare const ADAPTER_PREFIX: "googleGenAI";
43
- type GoogleGenAIThreadOps<TScope extends string = ""> = PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>, GoogleGenAIContent>;
41
+ type GoogleGenAIThreadOps<TScope extends string = ""> = PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_ID>, GoogleGenAIContent>;
44
42
  interface GoogleGenAIAdapterConfig {
45
43
  redis: Redis;
46
44
  client?: GoogleGenAI;
@@ -1,7 +1,8 @@
1
1
  import Redis from 'ioredis';
2
2
  import { Part, Content, GoogleGenAI } from '@google/genai';
3
- import { a as ModelInvoker, P as PrefixedThreadOps, S as ScopedPrefix, R as RouterContext, b as ToolHandlerResponse, c as ActivityToolHandler } from './types-C06FwR96.cjs';
4
- import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-BaOw4hKI.cjs';
3
+ import { a as ModelInvoker, b as PrefixedThreadOps, S as ScopedPrefix, R as RouterContext, c as ToolHandlerResponse, d as ActivityToolHandler } from './types-Cn2r3ol3.cjs';
4
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from './types-CuISs0Ub.cjs';
5
+ import { A as ADAPTER_ID } from './adapter-id-BB-mmrts.cjs';
5
6
 
6
7
  /** SDK-native content type for Google GenAI human messages */
7
8
  type GoogleGenAIContent = string | Part[];
@@ -24,8 +25,6 @@ interface GoogleGenAIThreadManagerConfig {
24
25
  interface GoogleGenAIInvocationPayload {
25
26
  contents: Content[];
26
27
  systemInstruction?: Part[];
27
- /** Number of stored messages loaded from Redis before preparation. */
28
- storedLength: number;
29
28
  }
30
29
  /** Thread manager with Google GenAI Content convenience helpers */
31
30
  interface GoogleGenAIThreadManager extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse, GoogleGenAISystemContent> {
@@ -39,8 +38,7 @@ interface GoogleGenAIThreadManager extends ProviderThreadManager<StoredContent,
39
38
  */
40
39
  declare function createGoogleGenAIThreadManager(config: GoogleGenAIThreadManagerConfig): GoogleGenAIThreadManager;
41
40
 
42
- declare const ADAPTER_PREFIX: "googleGenAI";
43
- type GoogleGenAIThreadOps<TScope extends string = ""> = PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>, GoogleGenAIContent>;
41
+ type GoogleGenAIThreadOps<TScope extends string = ""> = PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_ID>, GoogleGenAIContent>;
44
42
  interface GoogleGenAIAdapterConfig {
45
43
  redis: Redis;
46
44
  client?: GoogleGenAI;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the Google GenAI thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `googleGenAICodingAgentInitializeThread`) and
6
+ * must never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/google-genai` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"googleGenAI"`.
12
+ */
13
+ declare const ADAPTER_ID: "googleGenAI";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the Google GenAI thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `googleGenAICodingAgentInitializeThread`) and
6
+ * must never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/google-genai` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"googleGenAI"`.
12
+ */
13
+ declare const ADAPTER_ID: "googleGenAI";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the Anthropic thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `anthropicCodingAgentInitializeThread`) and must
6
+ * never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/anthropic` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"anthropic"`.
12
+ */
13
+ declare const ADAPTER_ID: "anthropic";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the Anthropic thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `anthropicCodingAgentInitializeThread`) and must
6
+ * never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/anthropic` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"anthropic"`.
12
+ */
13
+ declare const ADAPTER_ID: "anthropic";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the LangChain thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `langChainCodingAgentInitializeThread`) and must
6
+ * never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/langchain` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"langChain"`.
12
+ */
13
+ declare const ADAPTER_ID: "langChain";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public adapter identity for the LangChain thread adapter.
3
+ *
4
+ * This value is wire format — it appears as the prefix for Temporal
5
+ * activity names (e.g. `langChainCodingAgentInitializeThread`) and must
6
+ * never change, since renaming it would orphan existing persisted
7
+ * threads and break in-flight workflows.
8
+ *
9
+ * Re-exported from `zeitlich/adapters/thread/langchain` so downstream
10
+ * consumers can use the exact same literal the adapter uses internally,
11
+ * typed as the narrow string literal `"langChain"`.
12
+ */
13
+ declare const ADAPTER_ID: "langChain";
14
+ /** Narrow string-literal type for {@link ADAPTER_ID}. */
15
+ type AdapterId = typeof ADAPTER_ID;
16
+
17
+ export { ADAPTER_ID as A, type AdapterId as a };
@@ -2,8 +2,22 @@
2
2
 
3
3
  var activity = require('@temporalio/activity');
4
4
 
5
- // src/lib/thread/manager.ts
5
+ // src/adapters/thread/anthropic/adapter-id.ts
6
+ var ADAPTER_ID = "anthropic";
7
+
8
+ // src/lib/thread/keys.ts
6
9
  var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
10
+ function getThreadListKey(threadKey, threadId) {
11
+ return `${threadKey}:thread:${threadId}`;
12
+ }
13
+ function getThreadMetaKey(threadKey, threadId) {
14
+ return `${threadKey}:meta:thread:${threadId}`;
15
+ }
16
+ function getThreadStateKey(threadKey, threadId) {
17
+ return `${threadKey}:state:thread:${threadId}`;
18
+ }
19
+
20
+ // src/lib/thread/manager.ts
7
21
  var APPEND_IDEMPOTENT_SCRIPT = `
8
22
  if redis.call('EXISTS', KEYS[1]) == 1 then
9
23
  return 0
@@ -15,8 +29,8 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
15
29
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
16
30
  return 1
17
31
  `;
18
- function getThreadKey(threadId, key) {
19
- return `${key}:thread:${threadId}`;
32
+ function getDedupKey(threadId, id) {
33
+ return `dedup:${id}:thread:${threadId}`;
20
34
  }
21
35
  function createThreadManager(config) {
22
36
  const {
@@ -27,8 +41,9 @@ function createThreadManager(config) {
27
41
  deserialize = (raw) => JSON.parse(raw),
28
42
  idOf
29
43
  } = config;
30
- const redisKey = getThreadKey(threadId, key);
31
- const metaKey = getThreadKey(threadId, `${key}:meta`);
44
+ const redisKey = getThreadListKey(key, threadId);
45
+ const metaKey = getThreadMetaKey(key, threadId);
46
+ const stateKey = getThreadStateKey(key, threadId);
32
47
  async function assertThreadExists() {
33
48
  const exists = await redis.exists(metaKey);
34
49
  if (!exists) {
@@ -50,7 +65,7 @@ function createThreadManager(config) {
50
65
  await assertThreadExists();
51
66
  if (idOf) {
52
67
  const dedupId = messages.map(idOf).join(":");
53
- const dedupKey = getThreadKey(threadId, `dedup:${dedupId}`);
68
+ const dedupKey = getDedupKey(threadId, dedupId);
54
69
  await redis.eval(
55
70
  APPEND_IDEMPOTENT_SCRIPT,
56
71
  2,
@@ -67,34 +82,98 @@ function createThreadManager(config) {
67
82
  async fork(newThreadId) {
68
83
  await assertThreadExists();
69
84
  const data = await redis.lrange(redisKey, 0, -1);
85
+ const stateRaw = await redis.get(stateKey);
70
86
  const forked = createThreadManager({
71
87
  ...config,
72
88
  threadId: newThreadId
73
89
  });
74
90
  await forked.initialize();
75
91
  if (data.length > 0) {
76
- const newKey = getThreadKey(newThreadId, key);
92
+ const newKey = getThreadListKey(key, newThreadId);
77
93
  await redis.rpush(newKey, ...data);
78
94
  await redis.expire(newKey, THREAD_TTL_SECONDS);
79
95
  }
96
+ if (stateRaw != null) {
97
+ const newStateKey = getThreadStateKey(key, newThreadId);
98
+ await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
99
+ }
80
100
  return forked;
81
101
  },
102
+ async replaceAll(messages) {
103
+ await assertThreadExists();
104
+ if (!idOf) {
105
+ throw new Error(
106
+ "replaceAll requires the thread manager to be configured with `idOf`"
107
+ );
108
+ }
109
+ const existing = await redis.lrange(redisKey, 0, -1);
110
+ const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
111
+ await redis.del(redisKey);
112
+ if (existingIds.length > 0) {
113
+ await redis.del(
114
+ ...existingIds.map((id) => getDedupKey(threadId, id))
115
+ );
116
+ }
117
+ if (messages.length > 0) {
118
+ await redis.rpush(redisKey, ...messages.map(serialize));
119
+ await redis.expire(redisKey, THREAD_TTL_SECONDS);
120
+ }
121
+ await redis.expire(metaKey, THREAD_TTL_SECONDS);
122
+ },
82
123
  async delete() {
83
- await redis.del(redisKey, metaKey);
124
+ await redis.del(redisKey, metaKey, stateKey);
125
+ },
126
+ async loadState() {
127
+ const raw = await redis.get(stateKey);
128
+ if (raw == null) return null;
129
+ return JSON.parse(raw);
130
+ },
131
+ async saveState(state) {
132
+ await assertThreadExists();
133
+ await redis.set(
134
+ stateKey,
135
+ JSON.stringify(state),
136
+ "EX",
137
+ THREAD_TTL_SECONDS
138
+ );
139
+ },
140
+ async deleteState() {
141
+ await redis.del(stateKey);
84
142
  },
85
143
  async length() {
86
144
  await assertThreadExists();
87
145
  return redis.llen(redisKey);
88
146
  },
89
- async truncate(length) {
147
+ async truncateFromId(messageId) {
90
148
  await assertThreadExists();
91
- if (length <= 0) {
149
+ if (!idOf) {
150
+ throw new Error(
151
+ "truncateFromId requires the thread manager to be configured with `idOf`"
152
+ );
153
+ }
154
+ const data = await redis.lrange(redisKey, 0, -1);
155
+ let idx = -1;
156
+ const removedIds = [];
157
+ for (let i = 0; i < data.length; i++) {
158
+ const raw = data[i];
159
+ if (raw === void 0) continue;
160
+ const id = idOf(deserialize(raw));
161
+ if (idx === -1 && id === messageId) idx = i;
162
+ if (idx !== -1) removedIds.push(id);
163
+ }
164
+ if (idx === -1) return;
165
+ if (idx === 0) {
92
166
  await redis.del(redisKey);
93
167
  await redis.expire(metaKey, THREAD_TTL_SECONDS);
94
168
  } else {
95
- await redis.ltrim(redisKey, 0, length - 1);
169
+ await redis.ltrim(redisKey, 0, idx - 1);
96
170
  await redis.expire(redisKey, THREAD_TTL_SECONDS);
97
171
  }
172
+ if (removedIds.length > 0) {
173
+ await redis.del(
174
+ ...removedIds.map((id) => getDedupKey(threadId, id))
175
+ );
176
+ }
98
177
  }
99
178
  };
100
179
  }
@@ -203,12 +282,33 @@ function createAnthropicThreadManager(config) {
203
282
  const messages = mergeConsecutiveMessages(conversationMessages);
204
283
  return {
205
284
  messages: onPreparedMessage ? messages.map((msg, i) => onPreparedMessage(msg, i, messages)) : messages,
206
- ...system ? { system } : {},
207
- storedLength: stored.length
285
+ ...system ? { system } : {}
208
286
  };
209
287
  }
210
288
  };
211
- return Object.assign(base, helpers);
289
+ const manager = Object.assign(base, helpers);
290
+ const originalFork = manager.fork.bind(manager);
291
+ manager.fork = async (newThreadId) => {
292
+ await originalFork(newThreadId);
293
+ const forked = createAnthropicThreadManager({
294
+ ...config,
295
+ threadId: newThreadId
296
+ });
297
+ const { onForkPrepareThread, onForkTransform } = config.hooks ?? {};
298
+ if (!onForkPrepareThread && !onForkTransform) {
299
+ return forked;
300
+ }
301
+ let next = await forked.load();
302
+ if (onForkPrepareThread) {
303
+ next = await onForkPrepareThread(next);
304
+ }
305
+ if (onForkTransform) {
306
+ next = next.map((msg, i) => onForkTransform(msg, i, next));
307
+ }
308
+ await forked.replaceAll(next);
309
+ return forked;
310
+ };
311
+ return manager;
212
312
  }
213
313
  function getActivityContext() {
214
314
  try {
@@ -235,7 +335,7 @@ function createAnthropicModelInvoker({
235
335
  hooks
236
336
  }) {
237
337
  return async function invokeAnthropicModel2(config) {
238
- const { threadId, threadKey, state } = config;
338
+ const { threadId, threadKey, state, assistantMessageId } = config;
239
339
  const { heartbeat, signal } = getActivityContext();
240
340
  const thread = createAnthropicThreadManager({
241
341
  redis,
@@ -243,7 +343,8 @@ function createAnthropicModelInvoker({
243
343
  key: threadKey,
244
344
  hooks
245
345
  });
246
- const { messages, system, storedLength } = await thread.prepareForInvocation();
346
+ await thread.truncateFromId(assistantMessageId);
347
+ const { messages, system } = await thread.prepareForInvocation();
247
348
  const anthropicTools = toAnthropicTools(state.tools);
248
349
  const tools = anthropicTools.length > 0 ? anthropicTools : void 0;
249
350
  const params = {
@@ -273,8 +374,7 @@ function createAnthropicModelInvoker({
273
374
  outputTokens: response.usage.output_tokens,
274
375
  cachedWriteTokens: response.usage.cache_creation_input_tokens ?? void 0,
275
376
  cachedReadTokens: response.usage.cache_read_input_tokens ?? void 0
276
- },
277
- threadLengthAtCall: storedLength
377
+ }
278
378
  };
279
379
  };
280
380
  }
@@ -297,7 +397,6 @@ async function invokeAnthropicModel({
297
397
  }
298
398
 
299
399
  // src/adapters/thread/anthropic/activities.ts
300
- var ADAPTER_PREFIX = "anthropic";
301
400
  function createAnthropicAdapter(config) {
302
401
  const { redis, client } = config;
303
402
  const threadOps = {
@@ -346,17 +445,34 @@ function createAnthropicAdapter(config) {
346
445
  const thread = createAnthropicThreadManager({
347
446
  redis,
348
447
  threadId: sourceThreadId,
349
- key: threadKey
448
+ key: threadKey,
449
+ hooks: config.hooks
350
450
  });
351
451
  await thread.fork(targetThreadId);
352
452
  },
353
- async truncateThread(threadId, length, threadKey) {
453
+ async truncateThread(threadId, messageId, threadKey) {
354
454
  const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
355
- await thread.truncate(length);
455
+ await thread.truncateFromId(messageId);
456
+ },
457
+ async loadThreadState(threadId, threadKey) {
458
+ const thread = createAnthropicThreadManager({
459
+ redis,
460
+ threadId,
461
+ key: threadKey
462
+ });
463
+ return thread.loadState();
464
+ },
465
+ async saveThreadState(threadId, state, threadKey) {
466
+ const thread = createAnthropicThreadManager({
467
+ redis,
468
+ threadId,
469
+ key: threadKey
470
+ });
471
+ await thread.saveState(state);
356
472
  }
357
473
  };
358
474
  function createActivities(scope) {
359
- const prefix = scope ? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}` : ADAPTER_PREFIX;
475
+ const prefix = scope ? `${ADAPTER_ID}${scope.charAt(0).toUpperCase()}${scope.slice(1)}` : ADAPTER_ID;
360
476
  const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
361
477
  return Object.fromEntries(
362
478
  Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v])
@@ -386,6 +502,7 @@ function createAnthropicAdapter(config) {
386
502
  };
387
503
  }
388
504
 
505
+ exports.ADAPTER_ID = ADAPTER_ID;
389
506
  exports.createAnthropicAdapter = createAnthropicAdapter;
390
507
  exports.createAnthropicModelInvoker = createAnthropicModelInvoker;
391
508
  exports.createAnthropicThreadManager = createAnthropicThreadManager;