zeitlich 0.2.24 → 0.2.26

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 (123) hide show
  1. package/dist/activities-BEJRyDVU.d.cts +137 -0
  2. package/dist/activities-LVQdLF6I.d.ts +137 -0
  3. package/dist/adapters/sandbox/bedrock/index.cjs +35 -10
  4. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  5. package/dist/adapters/sandbox/bedrock/index.d.cts +2 -2
  6. package/dist/adapters/sandbox/bedrock/index.d.ts +2 -2
  7. package/dist/adapters/sandbox/bedrock/index.js +35 -10
  8. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  9. package/dist/adapters/sandbox/bedrock/workflow.d.cts +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.d.ts +1 -1
  11. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  12. package/dist/adapters/sandbox/virtual/index.d.cts +8 -7
  13. package/dist/adapters/sandbox/virtual/index.d.ts +8 -7
  14. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  15. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -2
  16. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -2
  17. package/dist/adapters/thread/anthropic/index.cjs +356 -0
  18. package/dist/adapters/thread/anthropic/index.cjs.map +1 -0
  19. package/dist/adapters/thread/anthropic/index.d.cts +148 -0
  20. package/dist/adapters/thread/anthropic/index.d.ts +148 -0
  21. package/dist/adapters/thread/anthropic/index.js +351 -0
  22. package/dist/adapters/thread/anthropic/index.js.map +1 -0
  23. package/dist/adapters/thread/anthropic/workflow.cjs +38 -0
  24. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -0
  25. package/dist/adapters/thread/anthropic/workflow.d.cts +37 -0
  26. package/dist/adapters/thread/anthropic/workflow.d.ts +37 -0
  27. package/dist/adapters/thread/anthropic/workflow.js +36 -0
  28. package/dist/adapters/thread/anthropic/workflow.js.map +1 -0
  29. package/dist/adapters/thread/google-genai/index.cjs +95 -97
  30. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  31. package/dist/adapters/thread/google-genai/index.d.cts +9 -111
  32. package/dist/adapters/thread/google-genai/index.d.ts +9 -111
  33. package/dist/adapters/thread/google-genai/index.js +96 -97
  34. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  35. package/dist/adapters/thread/google-genai/workflow.cjs +9 -4
  36. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  37. package/dist/adapters/thread/google-genai/workflow.d.cts +10 -5
  38. package/dist/adapters/thread/google-genai/workflow.d.ts +10 -5
  39. package/dist/adapters/thread/google-genai/workflow.js +9 -4
  40. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  41. package/dist/adapters/thread/langchain/index.cjs +43 -60
  42. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  43. package/dist/adapters/thread/langchain/index.d.cts +24 -38
  44. package/dist/adapters/thread/langchain/index.d.ts +24 -38
  45. package/dist/adapters/thread/langchain/index.js +43 -60
  46. package/dist/adapters/thread/langchain/index.js.map +1 -1
  47. package/dist/adapters/thread/langchain/workflow.cjs +9 -4
  48. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  49. package/dist/adapters/thread/langchain/workflow.d.cts +10 -5
  50. package/dist/adapters/thread/langchain/workflow.d.ts +10 -5
  51. package/dist/adapters/thread/langchain/workflow.js +9 -4
  52. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  53. package/dist/index.cjs +30 -12
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +13 -12
  56. package/dist/index.d.ts +13 -12
  57. package/dist/index.js +31 -13
  58. package/dist/index.js.map +1 -1
  59. package/dist/proxy-BK1ydQt0.d.ts +24 -0
  60. package/dist/proxy-BMAsMHdp.d.cts +24 -0
  61. package/dist/{queries-DwBe2CAA.d.ts → queries-BCgJ9Sr5.d.ts} +1 -1
  62. package/dist/{queries-BYGBImeC.d.cts → queries-DwnE2bu3.d.cts} +1 -1
  63. package/dist/thread-manager-CH9krS3h.d.ts +37 -0
  64. package/dist/thread-manager-Czhpxbt6.d.ts +29 -0
  65. package/dist/thread-manager-DOnQzImf.d.cts +29 -0
  66. package/dist/thread-manager-b4DML-qu.d.cts +37 -0
  67. package/dist/{types-35POpVfa.d.ts → types-BDRDbm3h.d.cts} +22 -1
  68. package/dist/{types-d9NznUqd.d.ts → types-BdCdR41N.d.ts} +10 -0
  69. package/dist/{types-hmferhc2.d.ts → types-CvJyXDYt.d.ts} +44 -123
  70. package/dist/{types-LVKmCNds.d.ts → types-DFUNSYbj.d.ts} +1 -1
  71. package/dist/{types-Bf8KV0Ci.d.cts → types-DRnz-OZp.d.cts} +1 -1
  72. package/dist/{types-7PeMi1bD.d.cts → types-DSOefLpY.d.cts} +44 -123
  73. package/dist/{types-35POpVfa.d.cts → types-WNSeZbWa.d.ts} +22 -1
  74. package/dist/{types-DhTCEMhr.d.cts → types-ZHs2v9Ap.d.cts} +10 -0
  75. package/dist/{types-D_igp10o.d.cts → types-mCVxKIZb.d.cts} +233 -137
  76. package/dist/{types-D_igp10o.d.ts → types-mCVxKIZb.d.ts} +233 -137
  77. package/dist/workflow.cjs +28 -11
  78. package/dist/workflow.cjs.map +1 -1
  79. package/dist/workflow.d.cts +11 -11
  80. package/dist/workflow.d.ts +11 -11
  81. package/dist/workflow.js +29 -12
  82. package/dist/workflow.js.map +1 -1
  83. package/package.json +26 -1
  84. package/src/adapters/sandbox/bedrock/filesystem.ts +43 -10
  85. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +8 -3
  86. package/src/adapters/thread/anthropic/activities.ts +223 -0
  87. package/src/adapters/thread/anthropic/index.ts +44 -0
  88. package/src/adapters/thread/anthropic/model-invoker.ts +124 -0
  89. package/src/adapters/thread/anthropic/proxy.ts +33 -0
  90. package/src/adapters/thread/anthropic/thread-manager.ts +191 -0
  91. package/src/adapters/thread/google-genai/activities.ts +107 -32
  92. package/src/adapters/thread/google-genai/index.ts +3 -1
  93. package/src/adapters/thread/google-genai/model-invoker.ts +7 -40
  94. package/src/adapters/thread/google-genai/proxy.ts +6 -34
  95. package/src/adapters/thread/google-genai/thread-manager.ts +84 -104
  96. package/src/adapters/thread/langchain/activities.ts +53 -20
  97. package/src/adapters/thread/langchain/index.ts +3 -1
  98. package/src/adapters/thread/langchain/model-invoker.ts +7 -9
  99. package/src/adapters/thread/langchain/proxy.ts +6 -34
  100. package/src/adapters/thread/langchain/thread-manager.ts +44 -98
  101. package/src/index.ts +5 -1
  102. package/src/lib/activity.ts +4 -3
  103. package/src/lib/hooks/types.ts +12 -12
  104. package/src/lib/model/types.ts +2 -0
  105. package/src/lib/session/session-edge-cases.integration.test.ts +24 -6
  106. package/src/lib/session/session.ts +18 -14
  107. package/src/lib/session/types.ts +31 -14
  108. package/src/lib/subagent/handler.ts +20 -11
  109. package/src/lib/subagent/subagent.integration.test.ts +36 -4
  110. package/src/lib/subagent/types.ts +3 -2
  111. package/src/lib/thread/index.ts +2 -0
  112. package/src/lib/thread/manager.ts +4 -7
  113. package/src/lib/thread/proxy.ts +57 -0
  114. package/src/lib/thread/types.ts +31 -0
  115. package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +54 -0
  116. package/src/lib/tool-router/auto-append.ts +5 -2
  117. package/src/lib/tool-router/router-edge-cases.integration.test.ts +9 -5
  118. package/src/lib/tool-router/router.ts +13 -7
  119. package/src/lib/tool-router/types.ts +20 -13
  120. package/src/lib/tool-router/with-sandbox.ts +4 -3
  121. package/src/lib/types.ts +7 -14
  122. package/src/workflow.ts +0 -4
  123. package/tsup.config.ts +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -67,6 +67,26 @@
67
67
  "default": "./dist/adapters/thread/google-genai/workflow.js"
68
68
  }
69
69
  },
70
+ "./adapters/thread/anthropic": {
71
+ "import": {
72
+ "types": "./dist/adapters/thread/anthropic/index.d.ts",
73
+ "default": "./dist/adapters/thread/anthropic/index.js"
74
+ },
75
+ "require": {
76
+ "types": "./dist/adapters/thread/anthropic/index.d.ts",
77
+ "default": "./dist/adapters/thread/anthropic/index.js"
78
+ }
79
+ },
80
+ "./adapters/thread/anthropic/workflow": {
81
+ "import": {
82
+ "types": "./dist/adapters/thread/anthropic/workflow.d.ts",
83
+ "default": "./dist/adapters/thread/anthropic/workflow.js"
84
+ },
85
+ "require": {
86
+ "types": "./dist/adapters/thread/anthropic/workflow.d.ts",
87
+ "default": "./dist/adapters/thread/anthropic/workflow.js"
88
+ }
89
+ },
70
90
  "./adapters/sandbox/inmemory": {
71
91
  "import": {
72
92
  "types": "./dist/adapters/sandbox/inmemory/index.d.ts",
@@ -190,6 +210,7 @@
190
210
  "node": ">=18"
191
211
  },
192
212
  "devDependencies": {
213
+ "@anthropic-ai/sdk": "^0.80.0",
193
214
  "@aws-sdk/client-bedrock-agentcore": "^3.900.0",
194
215
  "@daytonaio/sdk": "^0.149.0",
195
216
  "@e2b/code-interpreter": "^2.3.3",
@@ -210,6 +231,7 @@
210
231
  "vitest": "^4.0.18"
211
232
  },
212
233
  "peerDependencies": {
234
+ "@anthropic-ai/sdk": ">=0.50.0",
213
235
  "@aws-sdk/client-bedrock-agentcore": "^3.900.0",
214
236
  "@daytonaio/sdk": ">=0.153.0",
215
237
  "@e2b/code-interpreter": "^2.3.3",
@@ -225,6 +247,9 @@
225
247
  "@daytonaio/sdk": {
226
248
  "optional": true
227
249
  },
250
+ "@anthropic-ai/sdk": {
251
+ "optional": true
252
+ },
228
253
  "@google/genai": {
229
254
  "optional": true
230
255
  },
@@ -67,8 +67,39 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
67
67
  this.workspaceBase = posix.resolve("/", workspaceBase);
68
68
  }
69
69
 
70
+ /**
71
+ * Resolve a caller-supplied path to an absolute path within the workspace.
72
+ * Used for shell commands that need full paths.
73
+ */
70
74
  private normalisePath(path: string): string {
71
- return posix.resolve(this.workspaceBase, path);
75
+ if (
76
+ posix.isAbsolute(path) &&
77
+ !path.startsWith(this.workspaceBase + "/") &&
78
+ path !== this.workspaceBase
79
+ ) {
80
+ path = path.replace(/^\/+/, "");
81
+ }
82
+ const resolved = posix.resolve(this.workspaceBase, path);
83
+ if (
84
+ !resolved.startsWith(this.workspaceBase + "/") &&
85
+ resolved !== this.workspaceBase
86
+ ) {
87
+ throw new Error(
88
+ `Invalid file path: "${resolved}" escapes workspace "${this.workspaceBase}"`
89
+ );
90
+ }
91
+ return resolved;
92
+ }
93
+
94
+ /**
95
+ * Return a workspace-relative path for Bedrock tool invocations
96
+ * (`writeFiles`, `readFiles`, `listFiles`, `removeFiles`), which
97
+ * reject absolute paths as "path traversal".
98
+ */
99
+ private toToolPath(path: string): string {
100
+ const abs = this.normalisePath(path);
101
+ const prefix = this.workspaceBase + "/";
102
+ return abs.startsWith(prefix) ? abs.slice(prefix.length) : abs;
72
103
  }
73
104
 
74
105
  private async invoke(
@@ -103,9 +134,9 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
103
134
  }
104
135
 
105
136
  async readFile(path: string): Promise<string> {
106
- const norm = this.normalisePath(path);
137
+ const rel = this.toToolPath(path);
107
138
  const result = await this.invoke("readFiles" as ToolNameType, {
108
- paths: [norm],
139
+ paths: [rel],
109
140
  });
110
141
 
111
142
  for (const block of result.content ?? []) {
@@ -116,9 +147,9 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
116
147
  }
117
148
 
118
149
  async readFileBuffer(path: string): Promise<Uint8Array> {
119
- const norm = this.normalisePath(path);
150
+ const rel = this.toToolPath(path);
120
151
  const result = await this.invoke("readFiles" as ToolNameType, {
121
- paths: [norm],
152
+ paths: [rel],
122
153
  });
123
154
 
124
155
  for (const block of result.content ?? []) {
@@ -132,12 +163,12 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
132
163
  }
133
164
 
134
165
  async writeFile(path: string, content: string | Uint8Array): Promise<void> {
135
- const norm = this.normalisePath(path);
166
+ const rel = this.toToolPath(path);
136
167
  const isText = typeof content === "string";
137
168
  const result = await this.invoke("writeFiles" as ToolNameType, {
138
169
  content: [
139
170
  {
140
- path: norm,
171
+ path: rel,
141
172
  ...(isText
142
173
  ? { text: content as string }
143
174
  : { blob: content as Uint8Array }),
@@ -203,9 +234,9 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
203
234
  }
204
235
 
205
236
  async readdir(path: string): Promise<string[]> {
206
- const norm = this.normalisePath(path);
237
+ const rel = this.toToolPath(path);
207
238
  const result = await this.invoke("listFiles" as ToolNameType, {
208
- directoryPath: norm,
239
+ directoryPath: rel,
209
240
  });
210
241
 
211
242
  const names: string[] = [];
@@ -215,6 +246,7 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
215
246
 
216
247
  if (names.length > 0) return names;
217
248
 
249
+ const norm = this.normalisePath(path);
218
250
  const { stdout, exitCode, stderr } = await this.execShell(
219
251
  `ls -1A "${norm}"`
220
252
  );
@@ -264,8 +296,9 @@ export class BedrockSandboxFileSystem implements SandboxFileSystem {
264
296
  return;
265
297
  }
266
298
 
299
+ const rel = this.toToolPath(path);
267
300
  const result = await this.invoke("removeFiles" as ToolNameType, {
268
- paths: [norm],
301
+ paths: [rel],
269
302
  });
270
303
  if (result.isError) {
271
304
  const msg =
@@ -1,6 +1,7 @@
1
1
  import type { WorkflowClient } from "@temporalio/client";
2
2
  import { queryParentWorkflowState } from "../../../lib/activity";
3
- import type { ActivityToolHandler } from "../../../lib/tool-router/types";
3
+ import type { JsonValue } from "../../../lib/state/types";
4
+ import type { ActivityToolHandler, RouterContext } from "../../../lib/tool-router/types";
4
5
  import type {
5
6
  FileEntryMetadata,
6
7
  TreeMutation,
@@ -47,17 +48,21 @@ export function withVirtualSandbox<
47
48
  TResult,
48
49
  TCtx,
49
50
  TMeta = FileEntryMetadata,
51
+ TToolResponse = JsonValue,
50
52
  >(
51
53
  client: WorkflowClient,
52
54
  provider: VirtualSandboxProvider<TCtx, TMeta>,
53
55
  handler: ActivityToolHandler<
54
56
  TArgs,
55
57
  TResult,
56
- VirtualSandboxContext<TCtx, TMeta>
58
+ VirtualSandboxContext<TCtx, TMeta>,
59
+ TToolResponse
57
60
  >
58
61
  ): ActivityToolHandler<
59
62
  TArgs,
60
- (TResult & { treeMutations: TreeMutation<TMeta>[] }) | null
63
+ (TResult & { treeMutations: TreeMutation<TMeta>[] }) | null,
64
+ RouterContext,
65
+ TToolResponse | string
61
66
  > {
62
67
  return async (args, context) => {
63
68
  const state =
@@ -0,0 +1,223 @@
1
+ import type Redis from "ioredis";
2
+ import type Anthropic from "@anthropic-ai/sdk";
3
+ import type { ToolResultConfig } from "../../../lib/types";
4
+ import type {
5
+ ActivityToolHandler,
6
+ RouterContext,
7
+ ToolHandlerResponse,
8
+ } from "../../../lib/tool-router/types";
9
+ import type {
10
+ ThreadOps,
11
+ PrefixedThreadOps,
12
+ ScopedPrefix,
13
+ } from "../../../lib/session/types";
14
+ import type { ModelInvoker } from "../../../lib/model";
15
+ import {
16
+ createAnthropicThreadManager,
17
+ type AnthropicContent,
18
+ } from "./thread-manager";
19
+ import {
20
+ createAnthropicModelInvoker,
21
+ type AnthropicModelInvokerConfig,
22
+ } from "./model-invoker";
23
+
24
+ const ADAPTER_PREFIX = "anthropic" as const;
25
+
26
+ export type AnthropicThreadOps<TScope extends string = ""> =
27
+ PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>, AnthropicContent>;
28
+
29
+ export interface AnthropicAdapterConfig {
30
+ redis: Redis;
31
+ client: Anthropic;
32
+ /** Default model name (e.g. 'claude-sonnet-4-20250514'). If omitted, use `createModelInvoker()` */
33
+ model?: string;
34
+ /** Maximum tokens to generate. Defaults to 16384. */
35
+ maxTokens?: number;
36
+ }
37
+
38
+ /**
39
+ * Tool response type accepted by the Anthropic adapter.
40
+ *
41
+ * Handlers can return:
42
+ * - **`string`** — plain text content for the tool result.
43
+ * - **`Anthropic.Messages.ToolResultBlockParam["content"]`** — array of content blocks
44
+ * (e.g. `{ type: "text", text: "..." }`, `{ type: "image", source: { ... } }`).
45
+ * Passed through as-is to the `tool_result` block.
46
+ */
47
+ export type AnthropicToolResponse = Anthropic.Messages.ToolResultBlockParam["content"];
48
+
49
+ export interface AnthropicAdapter {
50
+ /** Model invoker using the default model (only available when `model` was provided) */
51
+ invoker: ModelInvoker<Anthropic.Messages.Message>;
52
+ /** Create an invoker for a specific model name (for multi-model setups) */
53
+ createModelInvoker(
54
+ model: string,
55
+ maxTokens?: number,
56
+ ): ModelInvoker<Anthropic.Messages.Message>;
57
+ /**
58
+ * Create prefixed thread activities for registration on the worker.
59
+ *
60
+ * @param scope - Workflow name appended to the adapter prefix.
61
+ * Use different scopes for the main agent vs subagents to avoid collisions.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * adapter.createActivities("codingAgent")
66
+ * // → { anthropicCodingAgentInitializeThread, anthropicCodingAgentAppendHumanMessage, … }
67
+ *
68
+ * adapter.createActivities("researchAgent")
69
+ * // → { anthropicResearchAgentInitializeThread, … }
70
+ * ```
71
+ */
72
+ createActivities<S extends string = "">(
73
+ scope?: S,
74
+ ): AnthropicThreadOps<S>;
75
+
76
+ /**
77
+ * Identity wrapper that types a tool handler for this adapter.
78
+ * Constrains `toolResponse` to {@link AnthropicToolResponse}.
79
+ */
80
+ wrapHandler<TArgs, TResult, TContext extends RouterContext = RouterContext>(
81
+ handler: (
82
+ args: TArgs,
83
+ context: TContext,
84
+ ) => Promise<ToolHandlerResponse<TResult, AnthropicToolResponse>>,
85
+ ): ActivityToolHandler<TArgs, TResult, TContext, AnthropicToolResponse>;
86
+ }
87
+
88
+ /**
89
+ * Creates an Anthropic adapter that bundles thread operations and model
90
+ * invocation using the `@anthropic-ai/sdk`.
91
+ *
92
+ * Use `createActivities(scope)` to register scoped thread operations as
93
+ * Temporal activities on the worker. The `invoker` (or invokers created via
94
+ * `createModelInvoker`) should be wrapped with `createRunAgentActivity`.
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * import { createAnthropicAdapter } from 'zeitlich/adapters/thread/anthropic';
99
+ * import { createRunAgentActivity } from 'zeitlich';
100
+ * import Anthropic from '@anthropic-ai/sdk';
101
+ *
102
+ * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
103
+ * const adapter = createAnthropicAdapter({ redis, client, model: 'claude-sonnet-4-20250514' });
104
+ *
105
+ * export function createActivities(temporalClient: WorkflowClient) {
106
+ * return {
107
+ * ...adapter.createActivities("codingAgent"),
108
+ * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
109
+ * };
110
+ * }
111
+ * ```
112
+ *
113
+ * @example Multi-agent worker (main + subagent share the adapter)
114
+ * ```typescript
115
+ * export function createActivities(temporalClient: WorkflowClient) {
116
+ * return {
117
+ * ...adapter.createActivities("codingAgent"),
118
+ * ...adapter.createActivities("researchAgent"),
119
+ * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
120
+ * runResearchAgent: createRunAgentActivity(
121
+ * temporalClient,
122
+ * adapter.createModelInvoker('claude-sonnet-4-20250514'),
123
+ * ),
124
+ * };
125
+ * }
126
+ * ```
127
+ */
128
+ export function createAnthropicAdapter(
129
+ config: AnthropicAdapterConfig,
130
+ ): AnthropicAdapter {
131
+ const { redis, client } = config;
132
+
133
+ const threadOps: ThreadOps<AnthropicContent> = {
134
+ async initializeThread(threadId: string, threadKey?: string): Promise<void> {
135
+ const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
136
+ await thread.initialize();
137
+ },
138
+
139
+ async appendHumanMessage(
140
+ threadId: string,
141
+ id: string,
142
+ content: AnthropicContent,
143
+ threadKey?: string,
144
+ ): Promise<void> {
145
+ const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
146
+ await thread.appendUserMessage(id, content);
147
+ },
148
+
149
+ async appendSystemMessage(
150
+ threadId: string,
151
+ id: string,
152
+ content: string,
153
+ threadKey?: string,
154
+ ): Promise<void> {
155
+ const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
156
+ await thread.appendSystemMessage(id, content);
157
+ },
158
+
159
+ async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
160
+ const { threadId, threadKey, toolCallId, toolName, content } = cfg;
161
+ const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
162
+ await thread.appendToolResult(id, toolCallId, toolName, content);
163
+ },
164
+
165
+ async forkThread(
166
+ sourceThreadId: string,
167
+ targetThreadId: string,
168
+ threadKey?: string,
169
+ ): Promise<void> {
170
+ const thread = createAnthropicThreadManager({
171
+ redis,
172
+ threadId: sourceThreadId,
173
+ key: threadKey,
174
+ });
175
+ await thread.fork(targetThreadId);
176
+ },
177
+ };
178
+
179
+ function createActivities<S extends string = "">(
180
+ scope?: S,
181
+ ): AnthropicThreadOps<S> {
182
+ const prefix = scope
183
+ ? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`
184
+ : ADAPTER_PREFIX;
185
+ const cap = (s: string): string =>
186
+ s.charAt(0).toUpperCase() + s.slice(1);
187
+ return Object.fromEntries(
188
+ Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v]),
189
+ ) as AnthropicThreadOps<S>;
190
+ }
191
+
192
+ const makeInvoker = (
193
+ model: string,
194
+ maxTokens?: number,
195
+ ): ModelInvoker<Anthropic.Messages.Message> => {
196
+ const invokerConfig: AnthropicModelInvokerConfig = {
197
+ redis,
198
+ client,
199
+ model,
200
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
201
+ ...(config.maxTokens !== undefined && maxTokens === undefined
202
+ ? { maxTokens: config.maxTokens }
203
+ : {}),
204
+ };
205
+ return createAnthropicModelInvoker(invokerConfig);
206
+ };
207
+
208
+ const invoker: ModelInvoker<Anthropic.Messages.Message> = config.model
209
+ ? makeInvoker(config.model)
210
+ : ((() => {
211
+ throw new Error(
212
+ "No default model provided to createAnthropicAdapter. " +
213
+ "Either pass `model` in the config or use `createModelInvoker(model)` instead.",
214
+ );
215
+ }) as unknown as ModelInvoker<Anthropic.Messages.Message>);
216
+
217
+ return {
218
+ createActivities,
219
+ invoker,
220
+ createModelInvoker: makeInvoker,
221
+ wrapHandler: (handler) => handler,
222
+ };
223
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Anthropic adapter for Zeitlich.
3
+ *
4
+ * Provides a unified adapter that bundles thread management and model
5
+ * invocation using the `@anthropic-ai/sdk`.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import {
10
+ * createAnthropicAdapter,
11
+ * createAnthropicThreadManager,
12
+ * } from 'zeitlich/adapters/thread/anthropic';
13
+ * import Anthropic from '@anthropic-ai/sdk';
14
+ *
15
+ * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
16
+ * const adapter = createAnthropicAdapter({ redis, client, model: 'claude-sonnet-4-20250514' });
17
+ * ```
18
+ */
19
+
20
+ // Adapter (primary API)
21
+ export {
22
+ createAnthropicAdapter,
23
+ type AnthropicAdapter,
24
+ type AnthropicAdapterConfig,
25
+ type AnthropicThreadOps,
26
+ type AnthropicToolResponse,
27
+ } from "./activities";
28
+
29
+ // Thread manager
30
+ export {
31
+ createAnthropicThreadManager,
32
+ type AnthropicThreadManager,
33
+ type AnthropicThreadManagerConfig,
34
+ type AnthropicContent,
35
+ type AnthropicInvocationPayload,
36
+ type StoredMessage,
37
+ } from "./thread-manager";
38
+
39
+ // Model invoker (for advanced use — prefer adapter.createModelInvoker)
40
+ export {
41
+ createAnthropicModelInvoker,
42
+ invokeAnthropicModel,
43
+ type AnthropicModelInvokerConfig,
44
+ } from "./model-invoker";
@@ -0,0 +1,124 @@
1
+ import type Redis from "ioredis";
2
+ import type Anthropic from "@anthropic-ai/sdk";
3
+ import type { SerializableToolDefinition } from "../../../lib/types";
4
+ import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
5
+ import { createAnthropicThreadManager } from "./thread-manager";
6
+ import { v4 as uuidv4 } from "uuid";
7
+
8
+ export interface AnthropicModelInvokerConfig {
9
+ redis: Redis;
10
+ client: Anthropic;
11
+ model: string;
12
+ /** Maximum tokens to generate. Defaults to 16384. */
13
+ maxTokens?: number;
14
+ }
15
+
16
+ function toAnthropicTools(
17
+ tools: SerializableToolDefinition[],
18
+ ): Anthropic.Messages.Tool[] {
19
+ return tools.map((t) => ({
20
+ name: t.name,
21
+ description: t.description,
22
+ input_schema: t.schema as Anthropic.Messages.Tool.InputSchema,
23
+ }));
24
+ }
25
+
26
+ /**
27
+ * Creates an Anthropic model invoker that satisfies the generic
28
+ * `ModelInvoker<Anthropic.Messages.Message>` contract.
29
+ *
30
+ * Loads the conversation thread from Redis, invokes the Claude model via
31
+ * `client.messages.create`, appends the AI response, and returns
32
+ * a normalised AgentResponse.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { createAnthropicModelInvoker } from 'zeitlich/adapters/thread/anthropic';
37
+ * import { createRunAgentActivity } from 'zeitlich';
38
+ * import Anthropic from '@anthropic-ai/sdk';
39
+ *
40
+ * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
41
+ * const invoker = createAnthropicModelInvoker({
42
+ * redis,
43
+ * client,
44
+ * model: 'claude-sonnet-4-20250514',
45
+ * });
46
+ *
47
+ * return { runAgent: createRunAgentActivity(client, invoker) };
48
+ * ```
49
+ */
50
+ export function createAnthropicModelInvoker({
51
+ redis,
52
+ client,
53
+ model,
54
+ maxTokens = 16384,
55
+ }: AnthropicModelInvokerConfig) {
56
+ return async function invokeAnthropicModel(
57
+ config: ModelInvokerConfig,
58
+ ): Promise<AgentResponse<Anthropic.Messages.Message>> {
59
+ const { threadId, threadKey, state } = config;
60
+
61
+ const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
62
+ const { messages, system } = await thread.prepareForInvocation();
63
+
64
+ const anthropicTools = toAnthropicTools(state.tools);
65
+ const tools = anthropicTools.length > 0 ? anthropicTools : undefined;
66
+
67
+ const response = await client.messages.create({
68
+ model,
69
+ max_tokens: maxTokens,
70
+ messages,
71
+ ...(system ? { system } : {}),
72
+ ...(tools ? { tools } : {}),
73
+ });
74
+
75
+ await thread.appendAssistantMessage(uuidv4(), response.content);
76
+
77
+ const toolCalls = response.content.filter(
78
+ (block): block is Anthropic.Messages.ToolUseBlock =>
79
+ block.type === "tool_use",
80
+ );
81
+
82
+ return {
83
+ message: response,
84
+ rawToolCalls: toolCalls.map((tc) => ({
85
+ id: tc.id,
86
+ name: tc.name,
87
+ args: (tc.input as Record<string, unknown>) ?? {},
88
+ })),
89
+ usage: {
90
+ inputTokens: response.usage.input_tokens,
91
+ outputTokens: response.usage.output_tokens,
92
+ cachedWriteTokens: response.usage.cache_creation_input_tokens ?? undefined,
93
+ cachedReadTokens: response.usage.cache_read_input_tokens ?? undefined,
94
+ },
95
+ };
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Standalone function for one-shot Anthropic model invocation.
101
+ * Convenience wrapper around createAnthropicModelInvoker for cases
102
+ * where you don't need to reuse the invoker.
103
+ */
104
+ export async function invokeAnthropicModel({
105
+ redis,
106
+ client,
107
+ model,
108
+ maxTokens,
109
+ config,
110
+ }: {
111
+ redis: Redis;
112
+ client: Anthropic;
113
+ model: string;
114
+ maxTokens?: number;
115
+ config: ModelInvokerConfig;
116
+ }): Promise<AgentResponse<Anthropic.Messages.Message>> {
117
+ const invoker = createAnthropicModelInvoker({
118
+ redis,
119
+ client,
120
+ model,
121
+ maxTokens,
122
+ });
123
+ return invoker(config);
124
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Workflow-safe proxy for Anthropic thread operations.
3
+ *
4
+ * Import this from `zeitlich/adapters/thread/anthropic/workflow`
5
+ * in your Temporal workflow files.
6
+ *
7
+ * By default the scope is derived from `workflowInfo().workflowType`,
8
+ * so activities are automatically namespaced per workflow.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { proxyAnthropicThreadOps } from 'zeitlich/adapters/thread/anthropic/workflow';
13
+ *
14
+ * // Auto-scoped to the current workflow name
15
+ * const threadOps = proxyAnthropicThreadOps();
16
+ *
17
+ * // Explicit scope override
18
+ * const threadOps = proxyAnthropicThreadOps("customScope");
19
+ * ```
20
+ */
21
+ import { type ActivityInterfaceFor } from "@temporalio/workflow";
22
+ import type { ThreadOps } from "../../../lib/session/types";
23
+ import type { AnthropicContent } from "./thread-manager";
24
+ import { createThreadOpsProxy } from "../../../lib/thread/proxy";
25
+
26
+ const ADAPTER_PREFIX = "anthropic";
27
+
28
+ export function proxyAnthropicThreadOps(
29
+ scope?: string,
30
+ options?: Parameters<typeof createThreadOpsProxy>[2],
31
+ ): ActivityInterfaceFor<ThreadOps<AnthropicContent>> {
32
+ return createThreadOpsProxy(ADAPTER_PREFIX, scope, options) as ActivityInterfaceFor<ThreadOps<AnthropicContent>>;
33
+ }