zeitlich 0.2.36 → 0.2.38

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 (204) hide show
  1. package/README.md +146 -92
  2. package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
  3. package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
  4. package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
  8. package/dist/adapters/sandbox/bedrock/index.js +17 -14
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
  12. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  13. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  14. package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +11 -3
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
  19. package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
  20. package/dist/adapters/sandbox/daytona/index.js +11 -3
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
  23. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  25. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  26. package/dist/adapters/sandbox/daytona/workflow.js +2 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +73 -12
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
  31. package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
  32. package/dist/adapters/sandbox/e2b/index.js +73 -12
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
  35. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  36. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  37. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  38. package/dist/adapters/sandbox/e2b/workflow.js +2 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +8 -3
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
  44. package/dist/adapters/sandbox/inmemory/index.js +8 -3
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
  47. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  48. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  49. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  50. package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +94 -39
  53. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  54. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  55. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  56. package/dist/adapters/thread/anthropic/index.js +94 -39
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
  59. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  60. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  61. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  62. package/dist/adapters/thread/anthropic/workflow.js +7 -2
  63. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  64. package/dist/adapters/thread/google-genai/index.cjs +77 -28
  65. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  66. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  68. package/dist/adapters/thread/google-genai/index.js +77 -28
  69. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  70. package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
  71. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  72. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  73. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  74. package/dist/adapters/thread/google-genai/workflow.js +7 -2
  75. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  76. package/dist/adapters/thread/langchain/index.cjs +57 -10
  77. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  78. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  79. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  80. package/dist/adapters/thread/langchain/index.js +57 -10
  81. package/dist/adapters/thread/langchain/index.js.map +1 -1
  82. package/dist/adapters/thread/langchain/workflow.cjs +7 -2
  83. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  84. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  85. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  86. package/dist/adapters/thread/langchain/workflow.js +7 -2
  87. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  88. package/dist/index.cjs +322 -146
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +20 -14
  91. package/dist/index.d.ts +20 -14
  92. package/dist/index.js +323 -147
  93. package/dist/index.js.map +1 -1
  94. package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
  95. package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
  96. package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
  97. package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
  98. package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
  99. package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
  100. package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
  101. package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
  102. package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
  103. package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
  104. package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
  105. package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
  106. package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
  107. package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
  108. package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
  109. package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
  110. package/dist/workflow.cjs +274 -130
  111. package/dist/workflow.cjs.map +1 -1
  112. package/dist/workflow.d.cts +3 -3
  113. package/dist/workflow.d.ts +3 -3
  114. package/dist/workflow.js +275 -131
  115. package/dist/workflow.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
  118. package/src/adapters/sandbox/bedrock/index.ts +22 -11
  119. package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
  120. package/src/adapters/sandbox/daytona/index.ts +18 -3
  121. package/src/adapters/sandbox/daytona/proxy.ts +2 -0
  122. package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
  123. package/src/adapters/sandbox/e2b/index.ts +87 -14
  124. package/src/adapters/sandbox/e2b/proxy.ts +2 -0
  125. package/src/adapters/sandbox/e2b/types.ts +16 -0
  126. package/src/adapters/sandbox/inmemory/index.ts +17 -3
  127. package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
  128. package/src/adapters/thread/anthropic/activities.ts +58 -26
  129. package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
  130. package/src/adapters/thread/anthropic/proxy.ts +6 -2
  131. package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
  132. package/src/adapters/thread/anthropic/thread-manager.ts +63 -46
  133. package/src/adapters/thread/google-genai/activities.ts +20 -2
  134. package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
  135. package/src/adapters/thread/google-genai/proxy.ts +6 -2
  136. package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
  137. package/src/adapters/thread/google-genai/thread-manager.ts +57 -33
  138. package/src/adapters/thread/langchain/activities.ts +55 -24
  139. package/src/adapters/thread/langchain/hooks.test.ts +36 -49
  140. package/src/adapters/thread/langchain/hooks.ts +18 -5
  141. package/src/adapters/thread/langchain/model-invoker.ts +5 -4
  142. package/src/adapters/thread/langchain/proxy.ts +6 -2
  143. package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
  144. package/src/adapters/thread/langchain/thread-manager.ts +23 -9
  145. package/src/index.ts +4 -1
  146. package/src/lib/activity.ts +16 -6
  147. package/src/lib/hooks/types.ts +6 -6
  148. package/src/lib/lifecycle.ts +18 -3
  149. package/src/lib/model/proxy.ts +2 -2
  150. package/src/lib/model/types.ts +10 -0
  151. package/src/lib/observability/hooks.ts +4 -5
  152. package/src/lib/observability/index.ts +1 -4
  153. package/src/lib/sandbox/manager.ts +45 -20
  154. package/src/lib/sandbox/node-fs.ts +3 -6
  155. package/src/lib/sandbox/sandbox.test.ts +36 -3
  156. package/src/lib/sandbox/tree.integration.test.ts +10 -3
  157. package/src/lib/sandbox/types.ts +60 -6
  158. package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
  159. package/src/lib/session/session.integration.test.ts +161 -1
  160. package/src/lib/session/session.ts +106 -21
  161. package/src/lib/session/types.ts +25 -5
  162. package/src/lib/skills/fs-provider.ts +12 -8
  163. package/src/lib/skills/handler.ts +1 -1
  164. package/src/lib/skills/parse.ts +3 -1
  165. package/src/lib/skills/register.ts +1 -3
  166. package/src/lib/skills/skills.integration.test.ts +25 -15
  167. package/src/lib/state/manager.integration.test.ts +12 -2
  168. package/src/lib/subagent/define.ts +1 -1
  169. package/src/lib/subagent/handler.ts +186 -71
  170. package/src/lib/subagent/index.ts +1 -5
  171. package/src/lib/subagent/register.ts +3 -2
  172. package/src/lib/subagent/signals.ts +1 -10
  173. package/src/lib/subagent/subagent.integration.test.ts +526 -248
  174. package/src/lib/subagent/tool.ts +4 -3
  175. package/src/lib/subagent/types.ts +50 -20
  176. package/src/lib/subagent/workflow.ts +9 -49
  177. package/src/lib/thread/id.test.ts +1 -1
  178. package/src/lib/thread/id.ts +1 -2
  179. package/src/lib/thread/manager.ts +18 -0
  180. package/src/lib/thread/proxy.ts +4 -4
  181. package/src/lib/thread/types.ts +20 -3
  182. package/src/lib/tool-router/index.ts +3 -5
  183. package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
  184. package/src/lib/tool-router/router.integration.test.ts +12 -0
  185. package/src/lib/tool-router/router.ts +90 -16
  186. package/src/lib/tool-router/types.ts +45 -4
  187. package/src/lib/tool-router/with-sandbox.ts +19 -5
  188. package/src/lib/virtual-fs/filesystem.ts +1 -1
  189. package/src/lib/virtual-fs/index.ts +5 -1
  190. package/src/lib/virtual-fs/mutations.ts +2 -4
  191. package/src/lib/virtual-fs/queries.ts +9 -5
  192. package/src/lib/virtual-fs/types.ts +4 -1
  193. package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
  194. package/src/lib/workflow.test.ts +7 -4
  195. package/src/lib/workflow.ts +1 -5
  196. package/src/tools/ask-user-question/tool.ts +1 -3
  197. package/src/tools/glob/handler.ts +1 -4
  198. package/src/tools/task-get/handler.ts +4 -5
  199. package/src/tools/task-list/handler.ts +1 -4
  200. package/src/tools/task-update/handler.ts +4 -5
  201. package/src/workflow.ts +22 -7
  202. package/tsup.config.ts +9 -6
  203. package/src/lib/.env +0 -1
  204. package/src/tools/bash/.env +0 -1
@@ -2,7 +2,10 @@ import type Redis from "ioredis";
2
2
  import type Anthropic from "@anthropic-ai/sdk";
3
3
  import type { SerializableToolDefinition } from "../../../lib/types";
4
4
  import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
5
- import { createAnthropicThreadManager, type AnthropicThreadManagerHooks } from "./thread-manager";
5
+ import {
6
+ createAnthropicThreadManager,
7
+ type AnthropicThreadManagerHooks,
8
+ } from "./thread-manager";
6
9
  import { getActivityContext } from "../../../lib/activity";
7
10
 
8
11
  export interface AnthropicModelInvokerConfig {
@@ -15,7 +18,7 @@ export interface AnthropicModelInvokerConfig {
15
18
  }
16
19
 
17
20
  function toAnthropicTools(
18
- tools: SerializableToolDefinition[],
21
+ tools: SerializableToolDefinition[]
19
22
  ): Anthropic.Messages.Tool[] {
20
23
  return tools.map((t) => ({
21
24
  name: t.name,
@@ -56,13 +59,19 @@ export function createAnthropicModelInvoker({
56
59
  hooks,
57
60
  }: AnthropicModelInvokerConfig) {
58
61
  return async function invokeAnthropicModel(
59
- config: ModelInvokerConfig,
62
+ config: ModelInvokerConfig
60
63
  ): Promise<AgentResponse<Anthropic.Messages.Message>> {
61
64
  const { threadId, threadKey, state } = config;
62
65
  const { heartbeat, signal } = getActivityContext();
63
66
 
64
- const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey, hooks });
65
- const { messages, system } = await thread.prepareForInvocation();
67
+ const thread = createAnthropicThreadManager({
68
+ redis,
69
+ threadId,
70
+ key: threadKey,
71
+ hooks,
72
+ });
73
+ const { messages, system, storedLength } =
74
+ await thread.prepareForInvocation();
66
75
 
67
76
  const anthropicTools = toAnthropicTools(state.tools);
68
77
  const tools = anthropicTools.length > 0 ? anthropicTools : undefined;
@@ -85,7 +94,7 @@ export function createAnthropicModelInvoker({
85
94
 
86
95
  const toolCalls = response.content.filter(
87
96
  (block): block is Anthropic.Messages.ToolUseBlock =>
88
- block.type === "tool_use",
97
+ block.type === "tool_use"
89
98
  );
90
99
 
91
100
  return {
@@ -98,9 +107,11 @@ export function createAnthropicModelInvoker({
98
107
  usage: {
99
108
  inputTokens: response.usage.input_tokens,
100
109
  outputTokens: response.usage.output_tokens,
101
- cachedWriteTokens: response.usage.cache_creation_input_tokens ?? undefined,
110
+ cachedWriteTokens:
111
+ response.usage.cache_creation_input_tokens ?? undefined,
102
112
  cachedReadTokens: response.usage.cache_read_input_tokens ?? undefined,
103
113
  },
114
+ threadLengthAtCall: storedLength,
104
115
  };
105
116
  };
106
117
  }
@@ -27,7 +27,11 @@ const ADAPTER_PREFIX = "anthropic";
27
27
 
28
28
  export function proxyAnthropicThreadOps(
29
29
  scope?: string,
30
- options?: Parameters<typeof createThreadOpsProxy>[2],
30
+ options?: Parameters<typeof createThreadOpsProxy>[2]
31
31
  ): ActivityInterfaceFor<ThreadOps<AnthropicContent>> {
32
- return createThreadOpsProxy(ADAPTER_PREFIX, scope, options) as ActivityInterfaceFor<ThreadOps<AnthropicContent>>;
32
+ return createThreadOpsProxy(
33
+ ADAPTER_PREFIX,
34
+ scope,
35
+ options
36
+ ) as ActivityInterfaceFor<ThreadOps<AnthropicContent>>;
33
37
  }
@@ -27,7 +27,10 @@ const userMsg: StoredMessage = {
27
27
 
28
28
  const assistantMsg: StoredMessage = {
29
29
  id: "msg-2",
30
- message: { role: "assistant", content: [{ type: "text", text: "Hi there!" }] },
30
+ message: {
31
+ role: "assistant",
32
+ content: [{ type: "text", text: "Hi there!" }],
33
+ },
31
34
  };
32
35
 
33
36
  describe("Anthropic thread manager hooks", () => {
@@ -40,7 +43,9 @@ describe("Anthropic thread manager hooks", () => {
40
43
  ...msg,
41
44
  message: {
42
45
  ...msg.message,
43
- content: [{ type: "text" as const, text: `[modified] ${firstBlock?.text}` }],
46
+ content: [
47
+ { type: "text" as const, text: `[modified] ${firstBlock?.text}` },
48
+ ],
44
49
  },
45
50
  };
46
51
  });
@@ -55,10 +60,18 @@ describe("Anthropic thread manager hooks", () => {
55
60
  const { messages, system } = await tm.prepareForInvocation();
56
61
 
57
62
  expect(hook).toHaveBeenCalledTimes(3);
58
- expect(hook).toHaveBeenCalledWith(systemMsg, 0, [systemMsg, userMsg, assistantMsg]);
63
+ expect(hook).toHaveBeenCalledWith(systemMsg, 0, [
64
+ systemMsg,
65
+ userMsg,
66
+ assistantMsg,
67
+ ]);
59
68
  expect(system).toBe("You are helpful.");
60
- expect(messages[0]?.content).toEqual([{ type: "text", text: "[modified] Hello" }]);
61
- expect(messages[1]?.content).toEqual([{ type: "text", text: "[modified] Hi there!" }]);
69
+ expect(messages[0]?.content).toEqual([
70
+ { type: "text", text: "[modified] Hello" },
71
+ ]);
72
+ expect(messages[1]?.content).toEqual([
73
+ { type: "text", text: "[modified] Hi there!" },
74
+ ]);
62
75
  });
63
76
 
64
77
  it("is not called when not configured", async () => {
@@ -90,7 +103,9 @@ describe("Anthropic thread manager hooks", () => {
90
103
  const { messages } = await tm.prepareForInvocation();
91
104
 
92
105
  expect(hook).toHaveBeenCalledTimes(2);
93
- expect(messages[0]?.content).toEqual([{ type: "text", text: "[post] done" }]);
106
+ expect(messages[0]?.content).toEqual([
107
+ { type: "text", text: "[post] done" },
108
+ ]);
94
109
  });
95
110
 
96
111
  it("receives the full prepared messages array", async () => {
@@ -105,7 +120,11 @@ describe("Anthropic thread manager hooks", () => {
105
120
 
106
121
  await tm.prepareForInvocation();
107
122
 
108
- const args = hook.mock.calls[0] as unknown as [unknown, number, unknown[]];
123
+ const args = hook.mock.calls[0] as unknown as [
124
+ unknown,
125
+ number,
126
+ unknown[],
127
+ ];
109
128
  expect(args[2]).toHaveLength(2);
110
129
  });
111
130
  });
@@ -9,9 +9,7 @@ import type {
9
9
  } from "../../../lib/thread/types";
10
10
 
11
11
  /** SDK-native content type for Anthropic human messages */
12
- export type AnthropicContent =
13
- | string
14
- | Anthropic.Messages.ContentBlockParam[];
12
+ export type AnthropicContent = string | Anthropic.Messages.ContentBlockParam[];
15
13
 
16
14
  /** SDK-native content type for Anthropic system prompts (supports cache_control blocks) */
17
15
  export type AnthropicSystemContent =
@@ -26,7 +24,10 @@ export interface StoredMessage {
26
24
  isSystem?: boolean;
27
25
  }
28
26
 
29
- export type AnthropicThreadManagerHooks = ThreadManagerHooks<StoredMessage, Anthropic.Messages.MessageParam>;
27
+ export type AnthropicThreadManagerHooks = ThreadManagerHooks<
28
+ StoredMessage,
29
+ Anthropic.Messages.MessageParam
30
+ >;
30
31
 
31
32
  export interface AnthropicThreadManagerConfig {
32
33
  redis: Redis;
@@ -40,14 +41,20 @@ export interface AnthropicThreadManagerConfig {
40
41
  export interface AnthropicInvocationPayload {
41
42
  messages: Anthropic.Messages.MessageParam[];
42
43
  system?: string | Anthropic.Messages.TextBlockParam[];
44
+ /** Number of stored messages loaded from Redis before preparation. */
45
+ storedLength: number;
43
46
  }
44
47
 
45
48
  /** Thread manager with Anthropic MessageParam convenience helpers */
46
- export interface AnthropicThreadManager
47
- extends ProviderThreadManager<StoredMessage, AnthropicContent, JsonValue, AnthropicSystemContent> {
49
+ export interface AnthropicThreadManager extends ProviderThreadManager<
50
+ StoredMessage,
51
+ AnthropicContent,
52
+ JsonValue,
53
+ AnthropicSystemContent
54
+ > {
48
55
  appendAssistantMessage(
49
56
  id: string,
50
- content: Anthropic.Messages.ContentBlock[],
57
+ content: Anthropic.Messages.ContentBlock[]
51
58
  ): Promise<void>;
52
59
  prepareForInvocation(): Promise<AnthropicInvocationPayload>;
53
60
  }
@@ -58,7 +65,7 @@ function storedMessageId(msg: StoredMessage): string {
58
65
 
59
66
  /** Normalise content into an array of ContentBlockParam */
60
67
  function toContentBlocks(
61
- content: AnthropicContent,
68
+ content: AnthropicContent
62
69
  ): Anthropic.Messages.ContentBlockParam[] {
63
70
  if (typeof content === "string") {
64
71
  return [{ type: "text", text: content }];
@@ -72,7 +79,7 @@ function toContentBlocks(
72
79
  * merging, multiple sequential tool-result messages would violate this.
73
80
  */
74
81
  function mergeConsecutiveMessages(
75
- messages: Anthropic.Messages.MessageParam[],
82
+ messages: Anthropic.Messages.MessageParam[]
76
83
  ): Anthropic.Messages.MessageParam[] {
77
84
  const merged: Anthropic.Messages.MessageParam[] = [];
78
85
  for (const msg of messages) {
@@ -88,9 +95,7 @@ function mergeConsecutiveMessages(
88
95
  } else {
89
96
  merged.push({
90
97
  ...msg,
91
- content: Array.isArray(msg.content)
92
- ? [...msg.content]
93
- : msg.content,
98
+ content: Array.isArray(msg.content) ? [...msg.content] : msg.content,
94
99
  });
95
100
  }
96
101
  }
@@ -103,7 +108,7 @@ function mergeConsecutiveMessages(
103
108
  * appending typed messages.
104
109
  */
105
110
  export function createAnthropicThreadManager(
106
- config: AnthropicThreadManagerConfig,
111
+ config: AnthropicThreadManagerConfig
107
112
  ): AnthropicThreadManager {
108
113
  const baseConfig: ThreadManagerConfig<StoredMessage> = {
109
114
  redis: config.redis,
@@ -117,49 +122,56 @@ export function createAnthropicThreadManager(
117
122
  const helpers: Omit<AnthropicThreadManager, keyof typeof base> = {
118
123
  async appendUserMessage(
119
124
  id: string,
120
- content: AnthropicContent,
125
+ content: AnthropicContent
121
126
  ): Promise<void> {
122
- await base.append([{
123
- id,
124
- message: { role: "user", content: toContentBlocks(content) },
125
- }]);
127
+ await base.append([
128
+ {
129
+ id,
130
+ message: { role: "user", content: toContentBlocks(content) },
131
+ },
132
+ ]);
126
133
  },
127
134
 
128
135
  async appendSystemMessage(
129
136
  id: string,
130
- content: AnthropicSystemContent,
137
+ content: AnthropicSystemContent
131
138
  ): Promise<void> {
132
139
  await base.initialize();
133
- await base.append([{
134
- id,
135
- // Stored under a user-role placeholder to satisfy the MessageParam
136
- // shape; the `isSystem` flag steers extraction in prepareForInvocation.
137
- message: {
138
- role: "user",
139
- content: content as Anthropic.Messages.MessageParam["content"],
140
+ await base.append([
141
+ {
142
+ id,
143
+ // Stored under a user-role placeholder to satisfy the MessageParam
144
+ // shape; the `isSystem` flag steers extraction in prepareForInvocation.
145
+ message: {
146
+ role: "user",
147
+ content: content as Anthropic.Messages.MessageParam["content"],
148
+ },
149
+ isSystem: true,
140
150
  },
141
- isSystem: true,
142
- }]);
151
+ ]);
143
152
  },
144
153
 
145
154
  async appendAssistantMessage(
146
155
  id: string,
147
- content: Anthropic.Messages.ContentBlock[],
156
+ content: Anthropic.Messages.ContentBlock[]
148
157
  ): Promise<void> {
149
- await base.append([{
150
- id,
151
- message: {
152
- role: "assistant",
153
- content: content as unknown as Anthropic.Messages.ContentBlockParam[],
158
+ await base.append([
159
+ {
160
+ id,
161
+ message: {
162
+ role: "assistant",
163
+ content:
164
+ content as unknown as Anthropic.Messages.ContentBlockParam[],
165
+ },
154
166
  },
155
- }]);
167
+ ]);
156
168
  },
157
169
 
158
170
  async appendToolResult(
159
171
  id: string,
160
172
  toolCallId: string,
161
173
  _toolName: string,
162
- content: JsonValue,
174
+ content: JsonValue
163
175
  ): Promise<void> {
164
176
  const toolContent =
165
177
  typeof content === "string"
@@ -167,17 +179,21 @@ export function createAnthropicThreadManager(
167
179
  : Array.isArray(content)
168
180
  ? (content as unknown as Anthropic.Messages.ToolResultBlockParam["content"])
169
181
  : JSON.stringify(content);
170
- await base.append([{
171
- id,
172
- message: {
173
- role: "user",
174
- content: [{
175
- type: "tool_result" as const,
176
- tool_use_id: toolCallId,
177
- content: toolContent,
178
- }],
182
+ await base.append([
183
+ {
184
+ id,
185
+ message: {
186
+ role: "user",
187
+ content: [
188
+ {
189
+ type: "tool_result" as const,
190
+ tool_use_id: toolCallId,
191
+ content: toolContent,
192
+ },
193
+ ],
194
+ },
179
195
  },
180
- }]);
196
+ ]);
181
197
  },
182
198
 
183
199
  async prepareForInvocation(): Promise<AnthropicInvocationPayload> {
@@ -206,6 +222,7 @@ export function createAnthropicThreadManager(
206
222
  ? messages.map((msg, i) => onPreparedMessage(msg, i, messages))
207
223
  : messages,
208
224
  ...(system ? { system } : {}),
225
+ storedLength: stored.length,
209
226
  };
210
227
  },
211
228
  };
@@ -200,7 +200,7 @@ export function createGoogleGenAIAdapter(
200
200
  threadId: string,
201
201
  id: string,
202
202
  message: Content,
203
- threadKey?: string,
203
+ threadKey?: string
204
204
  ): Promise<void> {
205
205
  const thread = createGoogleGenAIThreadManager({
206
206
  redis,
@@ -222,6 +222,19 @@ export function createGoogleGenAIAdapter(
222
222
  });
223
223
  await thread.fork(targetThreadId);
224
224
  },
225
+
226
+ async truncateThread(
227
+ threadId: string,
228
+ length: number,
229
+ threadKey?: string,
230
+ ): Promise<void> {
231
+ const thread = createGoogleGenAIThreadManager({
232
+ redis,
233
+ threadId,
234
+ key: threadKey,
235
+ });
236
+ await thread.truncate(length);
237
+ },
225
238
  };
226
239
 
227
240
  function createActivities<S extends string = "">(
@@ -240,7 +253,12 @@ export function createGoogleGenAIAdapter(
240
253
  model: string,
241
254
  client: GoogleGenAI
242
255
  ): ModelInvoker<Content> =>
243
- createGoogleGenAIModelInvoker({ redis, client, model, hooks: config.hooks });
256
+ createGoogleGenAIModelInvoker({
257
+ redis,
258
+ client,
259
+ model,
260
+ hooks: config.hooks,
261
+ });
244
262
 
245
263
  const invoker: ModelInvoker<Content> =
246
264
  config.model && config.client
@@ -1,8 +1,17 @@
1
1
  import type Redis from "ioredis";
2
- import type { GoogleGenAI, Content, FunctionDeclaration, Part, GenerateContentResponse } from "@google/genai";
2
+ import type {
3
+ GoogleGenAI,
4
+ Content,
5
+ FunctionDeclaration,
6
+ Part,
7
+ GenerateContentResponse,
8
+ } from "@google/genai";
3
9
  import type { SerializableToolDefinition } from "../../../lib/types";
4
10
  import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
5
- import { createGoogleGenAIThreadManager, type GoogleGenAIThreadManagerHooks } from "./thread-manager";
11
+ import {
12
+ createGoogleGenAIThreadManager,
13
+ type GoogleGenAIThreadManagerHooks,
14
+ } from "./thread-manager";
6
15
  import { getActivityContext } from "../../../lib/activity";
7
16
 
8
17
  export interface GoogleGenAIModelInvokerConfig {
@@ -13,7 +22,7 @@ export interface GoogleGenAIModelInvokerConfig {
13
22
  }
14
23
 
15
24
  function toFunctionDeclarations(
16
- tools: SerializableToolDefinition[],
25
+ tools: SerializableToolDefinition[]
17
26
  ): FunctionDeclaration[] {
18
27
  return tools.map((t) => ({
19
28
  name: t.name,
@@ -53,13 +62,18 @@ export function createGoogleGenAIModelInvoker({
53
62
  hooks,
54
63
  }: GoogleGenAIModelInvokerConfig) {
55
64
  return async function invokeGoogleGenAIModel(
56
- config: ModelInvokerConfig,
65
+ config: ModelInvokerConfig
57
66
  ): Promise<AgentResponse<Content>> {
58
67
  const { threadId, threadKey, state } = config;
59
68
  const { heartbeat, signal } = getActivityContext();
60
69
 
61
- const thread = createGoogleGenAIThreadManager({ redis, threadId, key: threadKey, hooks });
62
- const { contents, systemInstruction } =
70
+ const thread = createGoogleGenAIThreadManager({
71
+ redis,
72
+ threadId,
73
+ key: threadKey,
74
+ hooks,
75
+ });
76
+ const { contents, systemInstruction, storedLength } =
63
77
  await thread.prepareForInvocation();
64
78
 
65
79
  const functionDeclarations = toFunctionDeclarations(state.tools);
@@ -103,6 +117,7 @@ export function createGoogleGenAIModelInvoker({
103
117
  outputTokens: lastChunk.usageMetadata?.candidatesTokenCount,
104
118
  cachedReadTokens: lastChunk.usageMetadata?.cachedContentTokenCount,
105
119
  },
120
+ threadLengthAtCall: storedLength,
106
121
  };
107
122
  };
108
123
  }
@@ -125,6 +140,11 @@ export async function invokeGoogleGenAIModel({
125
140
  hooks?: GoogleGenAIThreadManagerHooks;
126
141
  config: ModelInvokerConfig;
127
142
  }): Promise<AgentResponse<Content>> {
128
- const invoker = createGoogleGenAIModelInvoker({ redis, client, model, hooks });
143
+ const invoker = createGoogleGenAIModelInvoker({
144
+ redis,
145
+ client,
146
+ model,
147
+ hooks,
148
+ });
129
149
  return invoker(config);
130
150
  }
@@ -27,7 +27,11 @@ const ADAPTER_PREFIX = "googleGenAI";
27
27
 
28
28
  export function proxyGoogleGenAIThreadOps(
29
29
  scope?: string,
30
- options?: Parameters<typeof createThreadOpsProxy>[2],
30
+ options?: Parameters<typeof createThreadOpsProxy>[2]
31
31
  ): ActivityInterfaceFor<ThreadOps<GoogleGenAIContent>> {
32
- return createThreadOpsProxy(ADAPTER_PREFIX, scope, options) as ActivityInterfaceFor<ThreadOps<GoogleGenAIContent>>;
32
+ return createThreadOpsProxy(
33
+ ADAPTER_PREFIX,
34
+ scope,
35
+ options
36
+ ) as ActivityInterfaceFor<ThreadOps<GoogleGenAIContent>>;
33
37
  }
@@ -39,7 +39,9 @@ describe("Google GenAI thread manager hooks", () => {
39
39
  ...msg,
40
40
  content: {
41
41
  ...msg.content,
42
- parts: [{ text: `[modified] ${msg.content.parts?.[0]?.text ?? ""}` }],
42
+ parts: [
43
+ { text: `[modified] ${msg.content.parts?.[0]?.text ?? ""}` },
44
+ ],
43
45
  },
44
46
  };
45
47
  });
@@ -54,7 +56,11 @@ describe("Google GenAI thread manager hooks", () => {
54
56
  const { contents, systemInstruction } = await tm.prepareForInvocation();
55
57
 
56
58
  expect(hook).toHaveBeenCalledTimes(3);
57
- expect(hook).toHaveBeenCalledWith(systemContent, 0, [systemContent, userContent, modelContent]);
59
+ expect(hook).toHaveBeenCalledWith(systemContent, 0, [
60
+ systemContent,
61
+ userContent,
62
+ modelContent,
63
+ ]);
58
64
  expect(systemInstruction).toEqual([{ text: "You are helpful." }]);
59
65
  expect(contents[0]?.parts?.[0]?.text).toBe("[modified] Hello");
60
66
  expect(contents[1]?.parts?.[0]?.text).toBe("[modified] Hi there!");
@@ -106,7 +112,11 @@ describe("Google GenAI thread manager hooks", () => {
106
112
 
107
113
  await tm.prepareForInvocation();
108
114
 
109
- const args = hook.mock.calls[0] as unknown as [Content, number, Content[]];
115
+ const args = hook.mock.calls[0] as unknown as [
116
+ Content,
117
+ number,
118
+ Content[],
119
+ ];
110
120
  expect(args[2]).toHaveLength(2);
111
121
  });
112
122
  });
@@ -20,7 +20,10 @@ export interface StoredContent {
20
20
  content: Content;
21
21
  }
22
22
 
23
- export type GoogleGenAIThreadManagerHooks = ThreadManagerHooks<StoredContent, Content>;
23
+ export type GoogleGenAIThreadManagerHooks = ThreadManagerHooks<
24
+ StoredContent,
25
+ Content
26
+ >;
24
27
 
25
28
  export interface GoogleGenAIThreadManagerConfig {
26
29
  redis: Redis;
@@ -34,11 +37,17 @@ export interface GoogleGenAIThreadManagerConfig {
34
37
  export interface GoogleGenAIInvocationPayload {
35
38
  contents: Content[];
36
39
  systemInstruction?: Part[];
40
+ /** Number of stored messages loaded from Redis before preparation. */
41
+ storedLength: number;
37
42
  }
38
43
 
39
44
  /** Thread manager with Google GenAI Content convenience helpers */
40
- export interface GoogleGenAIThreadManager
41
- extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse, GoogleGenAISystemContent> {
45
+ export interface GoogleGenAIThreadManager extends ProviderThreadManager<
46
+ StoredContent,
47
+ GoogleGenAIContent,
48
+ GoogleGenAIToolResponse,
49
+ GoogleGenAISystemContent
50
+ > {
42
51
  appendModelContent(id: string, parts: Part[]): Promise<void>;
43
52
  prepareForInvocation(): Promise<GoogleGenAIInvocationPayload>;
44
53
  }
@@ -56,7 +65,9 @@ function toParts(content: GoogleGenAIContent): Part[] {
56
65
  }
57
66
 
58
67
  /** Convert a string or object into a Record suitable for functionResponse.response */
59
- function toFunctionResponse(content: string | Record<string, unknown>): Record<string, unknown> {
68
+ function toFunctionResponse(
69
+ content: string | Record<string, unknown>
70
+ ): Record<string, unknown> {
60
71
  if (typeof content === "object") {
61
72
  return content;
62
73
  }
@@ -87,7 +98,7 @@ function mergeConsecutiveContents(contents: Content[]): Content[] {
87
98
  * appending typed Content messages.
88
99
  */
89
100
  export function createGoogleGenAIThreadManager(
90
- config: GoogleGenAIThreadManagerConfig,
101
+ config: GoogleGenAIThreadManagerConfig
91
102
  ): GoogleGenAIThreadManager {
92
103
  const baseConfig: ThreadManagerConfig<StoredContent> = {
93
104
  redis: config.redis,
@@ -101,54 +112,64 @@ export function createGoogleGenAIThreadManager(
101
112
  const helpers: Omit<GoogleGenAIThreadManager, keyof typeof base> = {
102
113
  async appendUserMessage(
103
114
  id: string,
104
- content: GoogleGenAIContent,
115
+ content: GoogleGenAIContent
105
116
  ): Promise<void> {
106
- await base.append([{
107
- id,
108
- content: { role: "user", parts: toParts(content) },
109
- }]);
117
+ await base.append([
118
+ {
119
+ id,
120
+ content: { role: "user", parts: toParts(content) },
121
+ },
122
+ ]);
110
123
  },
111
124
 
112
125
  async appendSystemMessage(
113
126
  id: string,
114
- content: GoogleGenAISystemContent,
127
+ content: GoogleGenAISystemContent
115
128
  ): Promise<void> {
116
129
  const parts: Part[] =
117
130
  typeof content === "string" ? [{ text: content }] : content;
118
131
  await base.initialize();
119
- await base.append([{
120
- id,
121
- content: { role: "system", parts },
122
- }]);
132
+ await base.append([
133
+ {
134
+ id,
135
+ content: { role: "system", parts },
136
+ },
137
+ ]);
123
138
  },
124
139
 
125
140
  async appendModelContent(id: string, parts: Part[]): Promise<void> {
126
- await base.append([{
127
- id,
128
- content: { role: "model", parts },
129
- }]);
141
+ await base.append([
142
+ {
143
+ id,
144
+ content: { role: "model", parts },
145
+ },
146
+ ]);
130
147
  },
131
148
 
132
149
  async appendToolResult(
133
150
  id: string,
134
151
  toolCallId: string,
135
152
  toolName: string,
136
- content: GoogleGenAIToolResponse,
153
+ content: GoogleGenAIToolResponse
137
154
  ): Promise<void> {
138
155
  const parts: Part[] = Array.isArray(content)
139
- ? content as Part[]
140
- : [{
141
- functionResponse: {
142
- id: toolCallId,
143
- name: toolName,
144
- response: toFunctionResponse(content),
156
+ ? (content as Part[])
157
+ : [
158
+ {
159
+ functionResponse: {
160
+ id: toolCallId,
161
+ name: toolName,
162
+ response: toFunctionResponse(content),
163
+ },
145
164
  },
146
- }];
147
-
148
- await base.append([{
149
- id,
150
- content: { role: "user", parts },
151
- }]);
165
+ ];
166
+
167
+ await base.append([
168
+ {
169
+ id,
170
+ content: { role: "user", parts },
171
+ },
172
+ ]);
152
173
  },
153
174
 
154
175
  async prepareForInvocation(): Promise<GoogleGenAIInvocationPayload> {
@@ -174,7 +195,10 @@ export function createGoogleGenAIThreadManager(
174
195
  contents: onPreparedMessage
175
196
  ? contents.map((msg, i) => onPreparedMessage(msg, i, contents))
176
197
  : contents,
177
- ...(systemInstruction && systemInstruction.length > 0 ? { systemInstruction } : {}),
198
+ ...(systemInstruction && systemInstruction.length > 0
199
+ ? { systemInstruction }
200
+ : {}),
201
+ storedLength: stored.length,
178
202
  };
179
203
  },
180
204
  };