zeitlich 0.2.32 → 0.2.33

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 (52) hide show
  1. package/README.md +11 -10
  2. package/dist/{activities-FIXVz7DT.d.ts → activities-YBD5BaHh.d.ts} +4 -3
  3. package/dist/{activities-DA-bQM12.d.cts → activities-fnX8-vhR.d.cts} +4 -3
  4. package/dist/adapters/thread/anthropic/index.cjs +18 -2
  5. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  6. package/dist/adapters/thread/anthropic/index.d.cts +7 -6
  7. package/dist/adapters/thread/anthropic/index.d.ts +7 -6
  8. package/dist/adapters/thread/anthropic/index.js +18 -2
  9. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  10. package/dist/adapters/thread/google-genai/index.cjs +29 -8
  11. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  12. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  13. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  14. package/dist/adapters/thread/google-genai/index.js +29 -8
  15. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  16. package/dist/adapters/thread/google-genai/workflow.d.cts +1 -1
  17. package/dist/adapters/thread/google-genai/workflow.d.ts +1 -1
  18. package/dist/adapters/thread/langchain/index.cjs +42 -23
  19. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  20. package/dist/adapters/thread/langchain/index.d.cts +8 -6
  21. package/dist/adapters/thread/langchain/index.d.ts +8 -6
  22. package/dist/adapters/thread/langchain/index.js +42 -23
  23. package/dist/adapters/thread/langchain/index.js.map +1 -1
  24. package/dist/index.cjs +34 -4
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +24 -9
  27. package/dist/index.d.ts +24 -9
  28. package/dist/index.js +33 -5
  29. package/dist/index.js.map +1 -1
  30. package/dist/{workflow-D8wK7TJY.d.ts → workflow-D9nNERvs.d.ts} +30 -2
  31. package/dist/{workflow-BWKQcz9d.d.cts → workflow-Od9vx5Jk.d.cts} +30 -2
  32. package/dist/workflow.cjs +18 -0
  33. package/dist/workflow.cjs.map +1 -1
  34. package/dist/workflow.d.cts +1 -1
  35. package/dist/workflow.d.ts +1 -1
  36. package/dist/workflow.js +18 -1
  37. package/dist/workflow.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/adapters/thread/anthropic/activities.ts +4 -3
  40. package/src/adapters/thread/anthropic/model-invoker.ts +15 -5
  41. package/src/adapters/thread/google-genai/activities.ts +4 -3
  42. package/src/adapters/thread/google-genai/model-invoker.ts +24 -11
  43. package/src/adapters/thread/langchain/activities.ts +3 -3
  44. package/src/adapters/thread/langchain/model-invoker.ts +63 -34
  45. package/src/index.ts +1 -0
  46. package/src/lib/activity.ts +36 -9
  47. package/src/lib/model/helpers.ts +1 -0
  48. package/src/lib/model/index.ts +1 -0
  49. package/src/lib/model/proxy.ts +50 -0
  50. package/src/workflow.ts +3 -0
  51. package/src/lib/.env +0 -1
  52. package/src/tools/bash/.env +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.32",
3
+ "version": "0.2.33",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -107,7 +107,7 @@ export interface AnthropicAdapter {
107
107
  * export function createActivities(temporalClient: WorkflowClient) {
108
108
  * return {
109
109
  * ...adapter.createActivities("codingAgent"),
110
- * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
110
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
111
111
  * };
112
112
  * }
113
113
  * ```
@@ -118,10 +118,11 @@ export interface AnthropicAdapter {
118
118
  * return {
119
119
  * ...adapter.createActivities("codingAgent"),
120
120
  * ...adapter.createActivities("researchAgent"),
121
- * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
122
- * runResearchAgent: createRunAgentActivity(
121
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
122
+ * ...createRunAgentActivity(
123
123
  * temporalClient,
124
124
  * adapter.createModelInvoker('claude-sonnet-4-20250514'),
125
+ * "researchAgent",
125
126
  * ),
126
127
  * };
127
128
  * }
@@ -3,6 +3,7 @@ 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
5
  import { createAnthropicThreadManager, type AnthropicThreadManagerHooks } from "./thread-manager";
6
+ import { getActivityContext } from "../../../lib/activity";
6
7
 
7
8
  export interface AnthropicModelInvokerConfig {
8
9
  redis: Redis;
@@ -27,8 +28,8 @@ function toAnthropicTools(
27
28
  * Creates an Anthropic model invoker that satisfies the generic
28
29
  * `ModelInvoker<Anthropic.Messages.Message>` contract.
29
30
  *
30
- * Loads the conversation thread from Redis, invokes the Claude model via
31
- * `client.messages.create`, and returns a normalised AgentResponse.
31
+ * Internally streams the response and emits Temporal heartbeats on each
32
+ * event so that long-running LLM calls remain visible to the scheduler.
32
33
  * The caller is responsible for appending the response to the thread.
33
34
  *
34
35
  * @example
@@ -44,7 +45,7 @@ function toAnthropicTools(
44
45
  * model: 'claude-sonnet-4-20250514',
45
46
  * });
46
47
  *
47
- * return { runAgent: createRunAgentActivity(client, invoker) };
48
+ * return { ...createRunAgentActivity(client, invoker, "myAgent") };
48
49
  * ```
49
50
  */
50
51
  export function createAnthropicModelInvoker({
@@ -58,6 +59,7 @@ export function createAnthropicModelInvoker({
58
59
  config: ModelInvokerConfig,
59
60
  ): Promise<AgentResponse<Anthropic.Messages.Message>> {
60
61
  const { threadId, threadKey, state } = config;
62
+ const { heartbeat, signal } = getActivityContext();
61
63
 
62
64
  const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey, hooks });
63
65
  const { messages, system } = await thread.prepareForInvocation();
@@ -65,13 +67,21 @@ export function createAnthropicModelInvoker({
65
67
  const anthropicTools = toAnthropicTools(state.tools);
66
68
  const tools = anthropicTools.length > 0 ? anthropicTools : undefined;
67
69
 
68
- const response = await client.messages.create({
70
+ const params: Anthropic.MessageCreateParams = {
69
71
  model,
70
72
  max_tokens: maxTokens,
71
73
  messages,
72
74
  ...(system ? { system } : {}),
73
75
  ...(tools ? { tools } : {}),
74
- });
76
+ };
77
+
78
+ const stream = client.messages.stream(params, { signal });
79
+
80
+ for await (const _event of stream) {
81
+ heartbeat?.();
82
+ }
83
+
84
+ const response: Anthropic.Messages.Message = await stream.finalMessage();
75
85
 
76
86
  const toolCalls = response.content.filter(
77
87
  (block): block is Anthropic.Messages.ToolUseBlock =>
@@ -113,7 +113,7 @@ export interface GoogleGenAIAdapter {
113
113
  * export function createActivities(temporalClient: WorkflowClient) {
114
114
  * return {
115
115
  * ...adapter.createActivities("codingAgent"),
116
- * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
116
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
117
117
  * };
118
118
  * }
119
119
  * ```
@@ -124,10 +124,11 @@ export interface GoogleGenAIAdapter {
124
124
  * return {
125
125
  * ...adapter.createActivities("codingAgent"),
126
126
  * ...adapter.createActivities("researchAgent"),
127
- * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
128
- * runResearchAgent: createRunAgentActivity(
127
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
128
+ * ...createRunAgentActivity(
129
129
  * temporalClient,
130
130
  * adapter.createModelInvoker('gemini-2.5-pro'),
131
+ * "researchAgent",
131
132
  * ),
132
133
  * };
133
134
  * }
@@ -1,8 +1,9 @@
1
1
  import type Redis from "ioredis";
2
- import type { GoogleGenAI, Content, FunctionDeclaration } from "@google/genai";
2
+ import type { GoogleGenAI, Content, FunctionDeclaration, Part, GenerateContentResponse } from "@google/genai";
3
3
  import type { SerializableToolDefinition } from "../../../lib/types";
4
4
  import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
5
5
  import { createGoogleGenAIThreadManager, type GoogleGenAIThreadManagerHooks } from "./thread-manager";
6
+ import { getActivityContext } from "../../../lib/activity";
6
7
 
7
8
  export interface GoogleGenAIModelInvokerConfig {
8
9
  redis: Redis;
@@ -25,8 +26,8 @@ function toFunctionDeclarations(
25
26
  * Creates a Google GenAI model invoker that satisfies the generic
26
27
  * `ModelInvoker<Content>` contract.
27
28
  *
28
- * Loads the conversation thread from Redis, invokes the Gemini model via
29
- * `client.models.generateContent`, and returns a normalised AgentResponse.
29
+ * Internally streams the response and emits Temporal heartbeats on each
30
+ * chunk so that long-running LLM calls remain visible to the scheduler.
30
31
  * The caller is responsible for appending the response to the thread.
31
32
  *
32
33
  * @example
@@ -42,7 +43,7 @@ function toFunctionDeclarations(
42
43
  * model: 'gemini-2.5-flash',
43
44
  * });
44
45
  *
45
- * return { runAgent: createRunAgentActivity(client, invoker) };
46
+ * return { ...createRunAgentActivity(client, invoker, "myAgent") };
46
47
  * ```
47
48
  */
48
49
  export function createGoogleGenAIModelInvoker({
@@ -55,6 +56,7 @@ export function createGoogleGenAIModelInvoker({
55
56
  config: ModelInvokerConfig,
56
57
  ): Promise<AgentResponse<Content>> {
57
58
  const { threadId, threadKey, state } = config;
59
+ const { heartbeat, signal } = getActivityContext();
58
60
 
59
61
  const thread = createGoogleGenAIThreadManager({ redis, threadId, key: threadKey, hooks });
60
62
  const { contents, systemInstruction } =
@@ -64,19 +66,30 @@ export function createGoogleGenAIModelInvoker({
64
66
  const tools =
65
67
  functionDeclarations.length > 0 ? [{ functionDeclarations }] : undefined;
66
68
 
67
- const response = await client.models.generateContent({
69
+ const stream = await client.models.generateContentStream({
68
70
  model,
69
71
  contents,
70
72
  config: {
71
73
  ...(systemInstruction ? { systemInstruction } : {}),
72
74
  ...(tools ? { tools } : {}),
75
+ abortSignal: signal,
73
76
  },
74
77
  });
75
78
 
76
- const responseParts = response.candidates?.[0]?.content?.parts ?? [];
77
- const modelContent: Content = { role: "model", parts: responseParts };
79
+ const allParts: Part[] = [];
80
+ let lastChunk: GenerateContentResponse | undefined;
81
+ for await (const chunk of stream) {
82
+ lastChunk = chunk;
83
+ allParts.push(...(chunk.candidates?.[0]?.content?.parts ?? []));
84
+ heartbeat?.();
85
+ }
78
86
 
79
- const functionCalls = response.functionCalls ?? [];
87
+ if (!lastChunk) {
88
+ throw new Error("Google GenAI stream ended without producing any chunks");
89
+ }
90
+
91
+ const modelContent: Content = { role: "model", parts: allParts };
92
+ const functionCalls = lastChunk.functionCalls ?? [];
80
93
 
81
94
  return {
82
95
  message: modelContent,
@@ -86,9 +99,9 @@ export function createGoogleGenAIModelInvoker({
86
99
  args: fc.args ?? {},
87
100
  })),
88
101
  usage: {
89
- inputTokens: response.usageMetadata?.promptTokenCount,
90
- outputTokens: response.usageMetadata?.candidatesTokenCount,
91
- cachedReadTokens: response.usageMetadata?.cachedContentTokenCount,
102
+ inputTokens: lastChunk.usageMetadata?.promptTokenCount,
103
+ outputTokens: lastChunk.usageMetadata?.candidatesTokenCount,
104
+ cachedReadTokens: lastChunk.usageMetadata?.cachedContentTokenCount,
92
105
  },
93
106
  };
94
107
  };
@@ -94,7 +94,7 @@ export interface LangChainAdapter {
94
94
  * export function createActivities(client: WorkflowClient) {
95
95
  * return {
96
96
  * ...adapter.createActivities("codingAgent"),
97
- * runCodingAgent: createRunAgentActivity(client, adapter.invoker),
97
+ * ...createRunAgentActivity(client, adapter.invoker, "codingAgent"),
98
98
  * };
99
99
  * }
100
100
  * ```
@@ -105,8 +105,8 @@ export interface LangChainAdapter {
105
105
  * return {
106
106
  * ...adapter.createActivities("codingAgent"),
107
107
  * ...adapter.createActivities("researchAgent"),
108
- * runCodingAgent: createRunAgentActivity(client, adapter.invoker),
109
- * runResearchAgent: createRunAgentActivity(client, adapter.createModelInvoker(claude)),
108
+ * ...createRunAgentActivity(client, adapter.invoker, "codingAgent"),
109
+ * ...createRunAgentActivity(client, adapter.createModelInvoker(claude), "researchAgent"),
110
110
  * };
111
111
  * }
112
112
  * ```
@@ -3,10 +3,16 @@ import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
3
3
  import type { StoredMessage } from "@langchain/core/messages";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
6
- import { createLangChainThreadManager, type LangChainThreadManagerHooks } from "./thread-manager";
6
+ import {
7
+ createLangChainThreadManager,
8
+ type LangChainThreadManagerHooks,
9
+ } from "./thread-manager";
10
+ import { getActivityContext } from "../../../lib/activity";
7
11
 
8
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> = BaseChatModel<any>> {
13
+ export interface LangChainModelInvokerConfig<
14
+ TModel extends BaseChatModel<any> = BaseChatModel<any>,
15
+ > {
10
16
  redis: Redis;
11
17
  model: TModel;
12
18
  hooks?: LangChainThreadManagerHooks;
@@ -16,8 +22,10 @@ export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> =
16
22
  * Creates a LangChain-based model invoker that satisfies the generic
17
23
  * `ModelInvoker<StoredMessage>` contract.
18
24
  *
19
- * Loads the conversation thread from Redis, invokes a LangChain chat model,
20
- * and returns a normalised AgentResponse.
25
+ * Uses interval-based Temporal heartbeats during model.invoke() to keep
26
+ * long-running LLM calls visible to the scheduler. LangChain's streaming
27
+ * chunk accumulation is unreliable across providers (e.g. reasoning_content
28
+ * blocks don't merge correctly), so we use invoke() for correctness.
21
29
  * The caller is responsible for appending the response to the thread.
22
30
  *
23
31
  * @example
@@ -29,51 +37,70 @@ export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> =
29
37
  * const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });
30
38
  * const invoker = createLangChainModelInvoker({ redis, model });
31
39
  *
32
- * return { runAgent: createRunAgentActivity(client, invoker) };
40
+ * return { ...createRunAgentActivity(client, invoker, "myAgent") };
33
41
  * ```
34
42
  */
35
43
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- export function createLangChainModelInvoker<TModel extends BaseChatModel<any> = BaseChatModel<any>>(
37
- { redis, model, hooks }: LangChainModelInvokerConfig<TModel>,
38
- ) {
44
+ export function createLangChainModelInvoker<
45
+ TModel extends BaseChatModel<any> = BaseChatModel<any>,
46
+ >({ redis, model, hooks }: LangChainModelInvokerConfig<TModel>) {
39
47
  return async function invokeLangChainModel(
40
- config: ModelInvokerConfig,
48
+ config: ModelInvokerConfig
41
49
  ): Promise<AgentResponse<StoredMessage>> {
42
50
  const { threadId, threadKey, agentName, state, metadata } = config;
51
+ const { heartbeat, signal } = getActivityContext();
43
52
 
44
- const thread = createLangChainThreadManager({ redis, threadId, key: threadKey, hooks });
53
+ const thread = createLangChainThreadManager({
54
+ redis,
55
+ threadId,
56
+ key: threadKey,
57
+ hooks,
58
+ });
45
59
  const runId = uuidv4();
46
60
 
47
61
  const { messages } = await thread.prepareForInvocation();
48
- const response = await model.invoke(
49
- messages,
50
- {
62
+
63
+ const heartbeatInterval = heartbeat
64
+ ? setInterval(() => heartbeat(), 30_000)
65
+ : undefined;
66
+
67
+ try {
68
+ const response = await model.invoke(messages, {
51
69
  runName: agentName,
52
70
  runId,
53
71
  metadata: { thread_id: `${agentName}-${threadId}`, ...metadata },
54
72
  tools: state.tools,
55
- },
56
- );
73
+ signal,
74
+ });
75
+
76
+ const toolCalls = response.tool_calls ?? [];
57
77
 
58
- const toolCalls = response.tool_calls ?? [];
78
+ const providerUsage =
79
+ (response.response_metadata?.usage as Record<string, unknown>) ?? {};
59
80
 
60
- return {
61
- message: response.toDict(),
62
- rawToolCalls: toolCalls.map((tc) => ({
63
- id: tc.id,
64
- name: tc.name,
65
- args: tc.args,
66
- })),
67
- usage: {
68
- inputTokens: response.usage_metadata?.input_tokens,
69
- outputTokens: response.usage_metadata?.output_tokens,
70
- reasonTokens: response.usage_metadata?.output_token_details?.reasoning,
71
- cachedWriteTokens:
72
- response.usage_metadata?.input_token_details?.cache_creation,
73
- cachedReadTokens:
74
- response.usage_metadata?.input_token_details?.cache_read,
75
- },
76
- };
81
+ return {
82
+ message: response.toDict(),
83
+ rawToolCalls: toolCalls.map((tc) => ({
84
+ id: tc.id,
85
+ name: tc.name,
86
+ args: tc.args,
87
+ })),
88
+ usage: {
89
+ inputTokens: response.usage_metadata?.input_tokens,
90
+ outputTokens: response.usage_metadata?.output_tokens,
91
+ reasonTokens:
92
+ response.usage_metadata?.output_token_details?.reasoning,
93
+ cachedWriteTokens:
94
+ response.usage_metadata?.input_token_details?.cache_creation ||
95
+ (providerUsage.cacheWriteInputTokens as number | undefined),
96
+ cachedReadTokens:
97
+ response.usage_metadata?.input_token_details?.cache_read ||
98
+ (providerUsage.cacheReadInputTokens as number | undefined),
99
+ },
100
+ };
101
+ } finally {
102
+ if (heartbeatInterval) clearInterval(heartbeatInterval);
103
+ }
77
104
  };
78
105
  }
79
106
 
@@ -83,7 +110,9 @@ export function createLangChainModelInvoker<TModel extends BaseChatModel<any> =
83
110
  * you don't need to reuse the invoker.
84
111
  */
85
112
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- export async function invokeLangChainModel<TModel extends BaseChatModel<any> = BaseChatModel<any>>({
113
+ export async function invokeLangChainModel<
114
+ TModel extends BaseChatModel<any> = BaseChatModel<any>,
115
+ >({
87
116
  redis,
88
117
  model,
89
118
  hooks,
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export {
52
52
  queryParentWorkflowState,
53
53
  createRunAgentActivity,
54
54
  withParentWorkflowState,
55
+ getActivityContext,
55
56
  } from "./lib/activity";
56
57
  export type { AgentStateContext } from "./lib/activity";
57
58
 
@@ -8,6 +8,22 @@ import type {
8
8
  ToolHandlerResponse,
9
9
  } from "./tool-router/types";
10
10
 
11
+ /**
12
+ * Safely retrieve Temporal activity heartbeat and cancellation signal.
13
+ * Returns empty object when called outside a Temporal activity (e.g. tests).
14
+ */
15
+ export function getActivityContext(): {
16
+ heartbeat?: () => void;
17
+ signal?: AbortSignal;
18
+ } {
19
+ try {
20
+ const ctx = Context.current();
21
+ return { heartbeat: () => ctx.heartbeat(), signal: ctx.cancellationSignal };
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+
11
27
  /**
12
28
  * Query the parent workflow's state from within an activity.
13
29
  * Resolves the workflow handle from the current activity context.
@@ -24,25 +40,36 @@ export async function queryParentWorkflowState<T>(
24
40
  }
25
41
 
26
42
  /**
27
- * Wraps a handler into a `RunAgentActivity` by auto-fetching the parent
28
- * workflow's agent state before each invocation.
43
+ * Wraps a handler into a scope-prefixed `RunAgentActivity` by auto-fetching
44
+ * the parent workflow's agent state before each invocation.
45
+ *
46
+ * Returns a `Record` with a single key `run<Scope>` so it can be spread
47
+ * into the activities object alongside adapter activities.
48
+ *
49
+ * @param scope - Workflow scope used to derive the activity name.
50
+ * `"myAgentWorkflow"` produces `{ runMyAgentWorkflow: fn }`.
29
51
  *
30
52
  * @example
31
53
  * ```typescript
32
54
  * import { createRunAgentActivity } from 'zeitlich';
33
- * import { createLangChainModelInvoker } from 'zeitlich/adapters/thread/langchain';
34
55
  *
35
- * const invoker = createLangChainModelInvoker({ redis, model });
36
- * return { runAgent: createRunAgentActivity(client, invoker) };
56
+ * return {
57
+ * ...adapter.createActivities("myAgentWorkflow"),
58
+ * ...createRunAgentActivity(client, adapter.invoker, "myAgentWorkflow"),
59
+ * };
37
60
  * ```
38
61
  */
39
62
  export function createRunAgentActivity<R, S extends BaseAgentState = BaseAgentState>(
40
63
  client: WorkflowClient,
41
64
  handler: (config: RunAgentConfig & { state: S }) => Promise<R>,
42
- ): (config: RunAgentConfig) => Promise<R> {
43
- return async (config: RunAgentConfig) => {
44
- const state = await queryParentWorkflowState<S>(client);
45
- return handler({ ...config, state });
65
+ scope: string,
66
+ ): Record<string, (config: RunAgentConfig) => Promise<R>> {
67
+ const name = `run${scope.charAt(0).toUpperCase()}${scope.slice(1)}`;
68
+ return {
69
+ [name]: async (config: RunAgentConfig) => {
70
+ const state = await queryParentWorkflowState<S>(client);
71
+ return handler({ ...config, state });
72
+ },
46
73
  };
47
74
  }
48
75
 
@@ -2,5 +2,6 @@ export {
2
2
  queryParentWorkflowState,
3
3
  createRunAgentActivity,
4
4
  withParentWorkflowState,
5
+ getActivityContext,
5
6
  } from "../activity";
6
7
  export type { AgentStateContext } from "../activity";
@@ -2,6 +2,7 @@ export {
2
2
  queryParentWorkflowState,
3
3
  createRunAgentActivity,
4
4
  withParentWorkflowState,
5
+ getActivityContext,
5
6
  } from "./helpers";
6
7
  export type { AgentStateContext } from "./helpers";
7
8
 
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Workflow-safe proxy for runAgent activities with LLM-optimised defaults.
3
+ *
4
+ * Resolves the activity name from the scope using the same convention as
5
+ * {@link createRunAgentActivity}: `run<Scope>`.
6
+ * When no scope is provided, defaults to `workflowInfo().workflowType`.
7
+ *
8
+ * Import this from `zeitlich/workflow` in your Temporal workflow files.
9
+ *
10
+ * @typeParam M - SDK-native message type (e.g. `StoredMessage` for LangChain,
11
+ * `Anthropic.Messages.Message` for Anthropic, `Content` for Google GenAI).
12
+ * Must be provided for `SessionResult.finalMessage` to be correctly typed.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { proxyRunAgent } from 'zeitlich/workflow';
17
+ * import type { StoredMessage } from '@langchain/core/messages';
18
+ *
19
+ * // Auto-scoped to the current workflow name
20
+ * const runAgent = proxyRunAgent<StoredMessage>();
21
+ *
22
+ * // Explicit scope for subagents
23
+ * const runResearcher = proxyRunAgent<StoredMessage>("Researcher");
24
+ * ```
25
+ */
26
+ import { proxyActivities, workflowInfo } from "@temporalio/workflow";
27
+ import type { AgentResponse } from "./types";
28
+ import type { RunAgentConfig } from "../types";
29
+
30
+ export function proxyRunAgent<M = unknown>(
31
+ scope?: string,
32
+ options?: Parameters<typeof proxyActivities>[0],
33
+ ): (config: RunAgentConfig) => Promise<AgentResponse<M>> {
34
+ const resolvedScope = scope ?? workflowInfo().workflowType;
35
+ const name = `run${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
38
+ options ?? {
39
+ startToCloseTimeout: "10m",
40
+ heartbeatTimeout: "1m",
41
+ retry: {
42
+ maximumAttempts: 3,
43
+ initialInterval: "10s",
44
+ maximumInterval: "2m",
45
+ backoffCoefficient: 3,
46
+ },
47
+ },
48
+ );
49
+ return acts[name] as (config: RunAgentConfig) => Promise<AgentResponse<M>>;
50
+ }
package/src/workflow.ts CHANGED
@@ -133,6 +133,9 @@ export type {
133
133
  ModelInvokerConfig,
134
134
  } from "./lib/model";
135
135
 
136
+ // Model proxy (workflow-safe proxy with LLM-optimised defaults)
137
+ export { proxyRunAgent } from "./lib/model/proxy";
138
+
136
139
  // Subagent types
137
140
  export type {
138
141
  SubagentConfig,
package/src/lib/.env DELETED
@@ -1 +0,0 @@
1
- E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9
@@ -1 +0,0 @@
1
- E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9