zeitlich 0.2.31 → 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.
- package/README.md +11 -10
- package/dist/{activities-DRSdt8Y3.d.ts → activities-YBD5BaHh.d.ts} +6 -5
- package/dist/{activities-qPkJDAiq.d.cts → activities-fnX8-vhR.d.cts} +6 -5
- package/dist/adapters/thread/anthropic/index.cjs +19 -47
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +12 -11
- package/dist/adapters/thread/anthropic/index.d.ts +12 -11
- package/dist/adapters/thread/anthropic/index.js +19 -47
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +1 -0
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +4 -4
- package/dist/adapters/thread/anthropic/workflow.d.ts +4 -4
- package/dist/adapters/thread/anthropic/workflow.js +1 -0
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +34 -53
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +8 -8
- package/dist/adapters/thread/google-genai/index.d.ts +8 -8
- package/dist/adapters/thread/google-genai/index.js +34 -53
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +1 -0
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +4 -4
- package/dist/adapters/thread/google-genai/workflow.d.ts +4 -4
- package/dist/adapters/thread/google-genai/workflow.js +1 -0
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +47 -24
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +13 -10
- package/dist/adapters/thread/langchain/index.d.ts +13 -10
- package/dist/adapters/thread/langchain/index.js +47 -24
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +1 -0
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +4 -4
- package/dist/adapters/thread/langchain/workflow.d.ts +4 -4
- package/dist/adapters/thread/langchain/workflow.js +1 -0
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +42 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -13
- package/dist/index.d.ts +28 -13
- package/dist/index.js +41 -10
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BkvkV2oU.d.ts → proxy-Br4unLTC.d.ts} +1 -1
- package/dist/{proxy-BDQ3Rj6R.d.cts → proxy-CTCYWjkr.d.cts} +1 -1
- package/dist/{thread-manager-BLgvv9Gf.d.cts → thread-manager-CUubPYPH.d.cts} +1 -1
- package/dist/{thread-manager-DowU4ntB.d.cts → thread-manager-Cv_BR28i.d.cts} +1 -1
- package/dist/{thread-manager-Cv82H1wi.d.ts → thread-manager-DKWxHUzD.d.ts} +1 -1
- package/dist/{thread-manager-HsAYkyAV.d.ts → thread-manager-YJLoc1vH.d.ts} +1 -1
- package/dist/{types-CjeGWQm1.d.cts → types-Bpq5fDI5.d.cts} +7 -4
- package/dist/{types-D6UKZZtj.d.ts → types-BxiT8w9d.d.ts} +1 -1
- package/dist/{types-BmS-Huc0.d.ts → types-CheCTLeV.d.ts} +7 -4
- package/dist/{types-e_38QaKo.d.cts → types-NJDyMyUx.d.cts} +1 -1
- package/dist/{workflow-CTcrPZAV.d.ts → workflow-D9nNERvs.d.ts} +30 -2
- package/dist/{workflow-CNshfqSO.d.cts → workflow-Od9vx5Jk.d.cts} +30 -2
- package/dist/workflow.cjs +22 -1
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +2 -2
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +22 -2
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/thread/anthropic/activities.ts +14 -3
- package/src/adapters/thread/anthropic/model-invoker.ts +15 -8
- package/src/adapters/thread/google-genai/activities.ts +18 -3
- package/src/adapters/thread/google-genai/model-invoker.ts +24 -14
- package/src/adapters/thread/langchain/activities.ts +14 -3
- package/src/adapters/thread/langchain/model-invoker.ts +63 -35
- package/src/index.ts +1 -0
- package/src/lib/activity.ts +36 -9
- package/src/lib/model/helpers.ts +1 -0
- package/src/lib/model/index.ts +1 -0
- package/src/lib/model/proxy.ts +50 -0
- package/src/lib/model/types.ts +3 -2
- package/src/lib/session/session-edge-cases.integration.test.ts +6 -0
- package/src/lib/session/session.integration.test.ts +3 -0
- package/src/lib/session/session.ts +4 -0
- package/src/lib/session/types.ts +7 -0
- package/src/lib/thread/proxy.ts +1 -0
- package/src/lib/types.ts +1 -0
- package/src/lib/virtual-fs/manager.ts +3 -3
- package/src/lib/virtual-fs/proxy.ts +3 -3
- package/src/lib/virtual-fs/types.ts +1 -2
- package/src/lib/virtual-fs/with-virtual-fs.ts +4 -4
- package/src/workflow.ts +3 -0
package/package.json
CHANGED
|
@@ -107,7 +107,7 @@ export interface AnthropicAdapter {
|
|
|
107
107
|
* export function createActivities(temporalClient: WorkflowClient) {
|
|
108
108
|
* return {
|
|
109
109
|
* ...adapter.createActivities("codingAgent"),
|
|
110
|
-
*
|
|
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
|
-
*
|
|
122
|
-
*
|
|
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
|
* }
|
|
@@ -164,6 +165,16 @@ export function createAnthropicAdapter(
|
|
|
164
165
|
await thread.appendToolResult(id, toolCallId, toolName, content);
|
|
165
166
|
},
|
|
166
167
|
|
|
168
|
+
async appendAgentMessage(
|
|
169
|
+
threadId: string,
|
|
170
|
+
id: string,
|
|
171
|
+
message: Anthropic.Messages.Message,
|
|
172
|
+
threadKey?: string,
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
|
|
175
|
+
await thread.appendAssistantMessage(id, message.content);
|
|
176
|
+
},
|
|
177
|
+
|
|
167
178
|
async forkThread(
|
|
168
179
|
sourceThreadId: string,
|
|
169
180
|
targetThreadId: string,
|
|
@@ -3,7 +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 {
|
|
6
|
+
import { getActivityContext } from "../../../lib/activity";
|
|
7
7
|
|
|
8
8
|
export interface AnthropicModelInvokerConfig {
|
|
9
9
|
redis: Redis;
|
|
@@ -28,9 +28,9 @@ function toAnthropicTools(
|
|
|
28
28
|
* Creates an Anthropic model invoker that satisfies the generic
|
|
29
29
|
* `ModelInvoker<Anthropic.Messages.Message>` contract.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Internally streams the response and emits Temporal heartbeats on each
|
|
32
|
+
* event so that long-running LLM calls remain visible to the scheduler.
|
|
33
|
+
* The caller is responsible for appending the response to the thread.
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
36
|
* ```typescript
|
|
@@ -45,7 +45,7 @@ function toAnthropicTools(
|
|
|
45
45
|
* model: 'claude-sonnet-4-20250514',
|
|
46
46
|
* });
|
|
47
47
|
*
|
|
48
|
-
* return {
|
|
48
|
+
* return { ...createRunAgentActivity(client, invoker, "myAgent") };
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
51
|
export function createAnthropicModelInvoker({
|
|
@@ -59,6 +59,7 @@ export function createAnthropicModelInvoker({
|
|
|
59
59
|
config: ModelInvokerConfig,
|
|
60
60
|
): Promise<AgentResponse<Anthropic.Messages.Message>> {
|
|
61
61
|
const { threadId, threadKey, state } = config;
|
|
62
|
+
const { heartbeat, signal } = getActivityContext();
|
|
62
63
|
|
|
63
64
|
const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey, hooks });
|
|
64
65
|
const { messages, system } = await thread.prepareForInvocation();
|
|
@@ -66,15 +67,21 @@ export function createAnthropicModelInvoker({
|
|
|
66
67
|
const anthropicTools = toAnthropicTools(state.tools);
|
|
67
68
|
const tools = anthropicTools.length > 0 ? anthropicTools : undefined;
|
|
68
69
|
|
|
69
|
-
const
|
|
70
|
+
const params: Anthropic.MessageCreateParams = {
|
|
70
71
|
model,
|
|
71
72
|
max_tokens: maxTokens,
|
|
72
73
|
messages,
|
|
73
74
|
...(system ? { system } : {}),
|
|
74
75
|
...(tools ? { tools } : {}),
|
|
75
|
-
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const stream = client.messages.stream(params, { signal });
|
|
79
|
+
|
|
80
|
+
for await (const _event of stream) {
|
|
81
|
+
heartbeat?.();
|
|
82
|
+
}
|
|
76
83
|
|
|
77
|
-
await
|
|
84
|
+
const response: Anthropic.Messages.Message = await stream.finalMessage();
|
|
78
85
|
|
|
79
86
|
const toolCalls = response.content.filter(
|
|
80
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
|
-
*
|
|
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
|
-
*
|
|
128
|
-
*
|
|
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
|
* }
|
|
@@ -194,6 +195,20 @@ export function createGoogleGenAIAdapter(
|
|
|
194
195
|
);
|
|
195
196
|
},
|
|
196
197
|
|
|
198
|
+
async appendAgentMessage(
|
|
199
|
+
threadId: string,
|
|
200
|
+
id: string,
|
|
201
|
+
message: Content,
|
|
202
|
+
threadKey?: string,
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
const thread = createGoogleGenAIThreadManager({
|
|
205
|
+
redis,
|
|
206
|
+
threadId,
|
|
207
|
+
key: threadKey,
|
|
208
|
+
});
|
|
209
|
+
await thread.appendModelContent(id, message.parts ?? []);
|
|
210
|
+
},
|
|
211
|
+
|
|
197
212
|
async forkThread(
|
|
198
213
|
sourceThreadId: string,
|
|
199
214
|
targetThreadId: string,
|
|
@@ -1,9 +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 {
|
|
6
|
+
import { getActivityContext } from "../../../lib/activity";
|
|
7
7
|
|
|
8
8
|
export interface GoogleGenAIModelInvokerConfig {
|
|
9
9
|
redis: Redis;
|
|
@@ -26,9 +26,9 @@ function toFunctionDeclarations(
|
|
|
26
26
|
* Creates a Google GenAI model invoker that satisfies the generic
|
|
27
27
|
* `ModelInvoker<Content>` contract.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Internally streams the response and emits Temporal heartbeats on each
|
|
30
|
+
* chunk so that long-running LLM calls remain visible to the scheduler.
|
|
31
|
+
* The caller is responsible for appending the response to the thread.
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
34
34
|
* ```typescript
|
|
@@ -43,7 +43,7 @@ function toFunctionDeclarations(
|
|
|
43
43
|
* model: 'gemini-2.5-flash',
|
|
44
44
|
* });
|
|
45
45
|
*
|
|
46
|
-
* return {
|
|
46
|
+
* return { ...createRunAgentActivity(client, invoker, "myAgent") };
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
49
|
export function createGoogleGenAIModelInvoker({
|
|
@@ -56,6 +56,7 @@ export function createGoogleGenAIModelInvoker({
|
|
|
56
56
|
config: ModelInvokerConfig,
|
|
57
57
|
): Promise<AgentResponse<Content>> {
|
|
58
58
|
const { threadId, threadKey, state } = config;
|
|
59
|
+
const { heartbeat, signal } = getActivityContext();
|
|
59
60
|
|
|
60
61
|
const thread = createGoogleGenAIThreadManager({ redis, threadId, key: threadKey, hooks });
|
|
61
62
|
const { contents, systemInstruction } =
|
|
@@ -65,21 +66,30 @@ export function createGoogleGenAIModelInvoker({
|
|
|
65
66
|
const tools =
|
|
66
67
|
functionDeclarations.length > 0 ? [{ functionDeclarations }] : undefined;
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
const stream = await client.models.generateContentStream({
|
|
69
70
|
model,
|
|
70
71
|
contents,
|
|
71
72
|
config: {
|
|
72
73
|
...(systemInstruction ? { systemInstruction } : {}),
|
|
73
74
|
...(tools ? { tools } : {}),
|
|
75
|
+
abortSignal: signal,
|
|
74
76
|
},
|
|
75
77
|
});
|
|
76
78
|
|
|
77
|
-
const
|
|
78
|
-
|
|
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
|
+
}
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
if (!lastChunk) {
|
|
88
|
+
throw new Error("Google GenAI stream ended without producing any chunks");
|
|
89
|
+
}
|
|
81
90
|
|
|
82
|
-
const
|
|
91
|
+
const modelContent: Content = { role: "model", parts: allParts };
|
|
92
|
+
const functionCalls = lastChunk.functionCalls ?? [];
|
|
83
93
|
|
|
84
94
|
return {
|
|
85
95
|
message: modelContent,
|
|
@@ -89,9 +99,9 @@ export function createGoogleGenAIModelInvoker({
|
|
|
89
99
|
args: fc.args ?? {},
|
|
90
100
|
})),
|
|
91
101
|
usage: {
|
|
92
|
-
inputTokens:
|
|
93
|
-
outputTokens:
|
|
94
|
-
cachedReadTokens:
|
|
102
|
+
inputTokens: lastChunk.usageMetadata?.promptTokenCount,
|
|
103
|
+
outputTokens: lastChunk.usageMetadata?.candidatesTokenCount,
|
|
104
|
+
cachedReadTokens: lastChunk.usageMetadata?.cachedContentTokenCount,
|
|
95
105
|
},
|
|
96
106
|
};
|
|
97
107
|
};
|
|
@@ -94,7 +94,7 @@ export interface LangChainAdapter {
|
|
|
94
94
|
* export function createActivities(client: WorkflowClient) {
|
|
95
95
|
* return {
|
|
96
96
|
* ...adapter.createActivities("codingAgent"),
|
|
97
|
-
*
|
|
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
|
-
*
|
|
109
|
-
*
|
|
108
|
+
* ...createRunAgentActivity(client, adapter.invoker, "codingAgent"),
|
|
109
|
+
* ...createRunAgentActivity(client, adapter.createModelInvoker(claude), "researchAgent"),
|
|
110
110
|
* };
|
|
111
111
|
* }
|
|
112
112
|
* ```
|
|
@@ -148,6 +148,17 @@ export function createLangChainAdapter(
|
|
|
148
148
|
await thread.appendToolResult(id, toolCallId, "", content);
|
|
149
149
|
},
|
|
150
150
|
|
|
151
|
+
async appendAgentMessage(
|
|
152
|
+
threadId: string,
|
|
153
|
+
id: string,
|
|
154
|
+
message: StoredMessage,
|
|
155
|
+
threadKey?: string,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
|
|
158
|
+
const patched = { ...message, data: { ...message.data, id } };
|
|
159
|
+
await thread.append([patched]);
|
|
160
|
+
},
|
|
161
|
+
|
|
151
162
|
async forkThread(
|
|
152
163
|
sourceThreadId: string,
|
|
153
164
|
targetThreadId: string,
|
|
@@ -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 {
|
|
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<
|
|
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,11 @@ 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
|
-
*
|
|
20
|
-
*
|
|
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.
|
|
29
|
+
* The caller is responsible for appending the response to the thread.
|
|
21
30
|
*
|
|
22
31
|
* @example
|
|
23
32
|
* ```typescript
|
|
@@ -28,53 +37,70 @@ export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> =
|
|
|
28
37
|
* const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });
|
|
29
38
|
* const invoker = createLangChainModelInvoker({ redis, model });
|
|
30
39
|
*
|
|
31
|
-
* return {
|
|
40
|
+
* return { ...createRunAgentActivity(client, invoker, "myAgent") };
|
|
32
41
|
* ```
|
|
33
42
|
*/
|
|
34
43
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
export function createLangChainModelInvoker<
|
|
36
|
-
|
|
37
|
-
) {
|
|
44
|
+
export function createLangChainModelInvoker<
|
|
45
|
+
TModel extends BaseChatModel<any> = BaseChatModel<any>,
|
|
46
|
+
>({ redis, model, hooks }: LangChainModelInvokerConfig<TModel>) {
|
|
38
47
|
return async function invokeLangChainModel(
|
|
39
|
-
config: ModelInvokerConfig
|
|
48
|
+
config: ModelInvokerConfig
|
|
40
49
|
): Promise<AgentResponse<StoredMessage>> {
|
|
41
50
|
const { threadId, threadKey, agentName, state, metadata } = config;
|
|
51
|
+
const { heartbeat, signal } = getActivityContext();
|
|
42
52
|
|
|
43
|
-
const thread = createLangChainThreadManager({
|
|
53
|
+
const thread = createLangChainThreadManager({
|
|
54
|
+
redis,
|
|
55
|
+
threadId,
|
|
56
|
+
key: threadKey,
|
|
57
|
+
hooks,
|
|
58
|
+
});
|
|
44
59
|
const runId = uuidv4();
|
|
45
60
|
|
|
46
61
|
const { messages } = await thread.prepareForInvocation();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
|
|
63
|
+
const heartbeatInterval = heartbeat
|
|
64
|
+
? setInterval(() => heartbeat(), 30_000)
|
|
65
|
+
: undefined;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const response = await model.invoke(messages, {
|
|
50
69
|
runName: agentName,
|
|
51
70
|
runId,
|
|
52
71
|
metadata: { thread_id: `${agentName}-${threadId}`, ...metadata },
|
|
53
72
|
tools: state.tools,
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
signal,
|
|
74
|
+
});
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
const toolCalls = response.tool_calls ?? [];
|
|
58
77
|
|
|
59
|
-
|
|
78
|
+
const providerUsage =
|
|
79
|
+
(response.response_metadata?.usage as Record<string, unknown>) ?? {};
|
|
60
80
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}
|
|
78
104
|
};
|
|
79
105
|
}
|
|
80
106
|
|
|
@@ -84,7 +110,9 @@ export function createLangChainModelInvoker<TModel extends BaseChatModel<any> =
|
|
|
84
110
|
* you don't need to reuse the invoker.
|
|
85
111
|
*/
|
|
86
112
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
-
export async function invokeLangChainModel<
|
|
113
|
+
export async function invokeLangChainModel<
|
|
114
|
+
TModel extends BaseChatModel<any> = BaseChatModel<any>,
|
|
115
|
+
>({
|
|
88
116
|
redis,
|
|
89
117
|
model,
|
|
90
118
|
hooks,
|
package/src/index.ts
CHANGED
package/src/lib/activity.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
package/src/lib/model/helpers.ts
CHANGED
package/src/lib/model/index.ts
CHANGED
|
@@ -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/lib/model/types.ts
CHANGED
|
@@ -33,8 +33,9 @@ export interface ModelInvokerConfig {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Generic model invocation contract.
|
|
36
|
-
* Implementations load the thread, call the LLM,
|
|
37
|
-
*
|
|
36
|
+
* Implementations load the thread, call the LLM, and return a normalised
|
|
37
|
+
* AgentResponse. The caller (workflow) is responsible for appending the
|
|
38
|
+
* response to the thread with a deterministic ID.
|
|
38
39
|
*
|
|
39
40
|
* Framework adapters (e.g. `zeitlich/langchain`) provide concrete
|
|
40
41
|
* implementations of this type.
|
|
@@ -88,6 +88,9 @@ function createMockThreadOps() {
|
|
|
88
88
|
appendSystemMessage: async (threadId, id, content) => {
|
|
89
89
|
log.push({ op: "appendSystemMessage", args: [threadId, id, content] });
|
|
90
90
|
},
|
|
91
|
+
appendAgentMessage: async (threadId, id, message) => {
|
|
92
|
+
log.push({ op: "appendAgentMessage", args: [threadId, id, message] });
|
|
93
|
+
},
|
|
91
94
|
forkThread: async (source, target) => {
|
|
92
95
|
log.push({ op: "forkThread", args: [source, target] });
|
|
93
96
|
},
|
|
@@ -752,6 +755,9 @@ describe("createSession edge cases", () => {
|
|
|
752
755
|
appendSystemMessage: async (threadId, id, content) => {
|
|
753
756
|
log.push({ op: "appendSystemMessage", args: [threadId, id, content] });
|
|
754
757
|
},
|
|
758
|
+
appendAgentMessage: async (threadId, id, message) => {
|
|
759
|
+
log.push({ op: "appendAgentMessage", args: [threadId, id, message] });
|
|
760
|
+
},
|
|
755
761
|
forkThread: async (source, target) => {
|
|
756
762
|
log.push({ op: "forkThread", args: [source, target] });
|
|
757
763
|
},
|
|
@@ -99,6 +99,9 @@ function createMockThreadOps() {
|
|
|
99
99
|
appendSystemMessage: async (threadId, id, content) => {
|
|
100
100
|
log.push({ op: "appendSystemMessage", args: [threadId, id, content] });
|
|
101
101
|
},
|
|
102
|
+
appendAgentMessage: async (threadId, id, message) => {
|
|
103
|
+
log.push({ op: "appendAgentMessage", args: [threadId, id, message] });
|
|
104
|
+
},
|
|
102
105
|
forkThread: async (source, target) => {
|
|
103
106
|
log.push({ op: "forkThread", args: [source, target] });
|
|
104
107
|
},
|
|
@@ -139,6 +139,7 @@ export async function createSession<
|
|
|
139
139
|
appendHumanMessage,
|
|
140
140
|
initializeThread,
|
|
141
141
|
appendSystemMessage,
|
|
142
|
+
appendAgentMessage,
|
|
142
143
|
forkThread,
|
|
143
144
|
} = threadOps;
|
|
144
145
|
|
|
@@ -285,6 +286,7 @@ export async function createSession<
|
|
|
285
286
|
: result.fileTree;
|
|
286
287
|
stateManager.mergeUpdate({
|
|
287
288
|
fileTree,
|
|
289
|
+
virtualFsCtx: virtualFsConfig.ctx,
|
|
288
290
|
...(skillFiles && { inlineFiles: skillFiles }),
|
|
289
291
|
} as Partial<AgentState<TState>>);
|
|
290
292
|
}
|
|
@@ -355,6 +357,8 @@ export async function createSession<
|
|
|
355
357
|
metadata,
|
|
356
358
|
});
|
|
357
359
|
|
|
360
|
+
await appendAgentMessage(threadId, uuid4(), message, threadKey);
|
|
361
|
+
|
|
358
362
|
if (usage) {
|
|
359
363
|
stateManager.updateUsage(usage);
|
|
360
364
|
}
|
package/src/lib/session/types.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface ThreadOps<TContent = string> {
|
|
|
39
39
|
): Promise<void>;
|
|
40
40
|
/** Append a tool result to the thread */
|
|
41
41
|
appendToolResult(id: string, config: ToolResultConfig): Promise<void>;
|
|
42
|
+
/** Append the model's response to the thread */
|
|
43
|
+
appendAgentMessage(
|
|
44
|
+
threadId: string,
|
|
45
|
+
id: string,
|
|
46
|
+
message: unknown,
|
|
47
|
+
threadKey?: string
|
|
48
|
+
): Promise<void>;
|
|
42
49
|
/** Append a system message to the thread */
|
|
43
50
|
appendSystemMessage(
|
|
44
51
|
threadId: string,
|