zeitlich 0.2.11 → 0.2.13

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 (46) hide show
  1. package/README.md +313 -126
  2. package/dist/adapters/langchain/index.cjs +270 -0
  3. package/dist/adapters/langchain/index.cjs.map +1 -0
  4. package/dist/adapters/langchain/index.d.cts +132 -0
  5. package/dist/adapters/langchain/index.d.ts +132 -0
  6. package/dist/adapters/langchain/index.js +265 -0
  7. package/dist/adapters/langchain/index.js.map +1 -0
  8. package/dist/index.cjs +89 -209
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +62 -46
  11. package/dist/index.d.ts +62 -46
  12. package/dist/index.js +88 -208
  13. package/dist/index.js.map +1 -1
  14. package/dist/{workflow-BhjsEQc1.d.cts → model-invoker-y_zlyMqu.d.cts} +45 -482
  15. package/dist/{workflow-BhjsEQc1.d.ts → model-invoker-y_zlyMqu.d.ts} +45 -482
  16. package/dist/thread-manager-qc0g5Rvd.d.cts +39 -0
  17. package/dist/thread-manager-qc0g5Rvd.d.ts +39 -0
  18. package/dist/workflow.cjs +59 -27
  19. package/dist/workflow.cjs.map +1 -1
  20. package/dist/workflow.d.cts +459 -6
  21. package/dist/workflow.d.ts +459 -6
  22. package/dist/workflow.js +60 -29
  23. package/dist/workflow.js.map +1 -1
  24. package/package.json +17 -2
  25. package/src/adapters/langchain/activities.ts +120 -0
  26. package/src/adapters/langchain/index.ts +38 -0
  27. package/src/adapters/langchain/model-invoker.ts +102 -0
  28. package/src/adapters/langchain/thread-manager.ts +142 -0
  29. package/src/index.ts +24 -23
  30. package/src/lib/fs.ts +25 -0
  31. package/src/lib/model-invoker.ts +15 -75
  32. package/src/lib/session.ts +52 -21
  33. package/src/lib/state-manager.ts +23 -5
  34. package/src/lib/thread-id.ts +25 -0
  35. package/src/lib/thread-manager.ts +18 -142
  36. package/src/lib/tool-router.ts +12 -18
  37. package/src/lib/types.ts +26 -10
  38. package/src/lib/workflow-helpers.ts +50 -0
  39. package/src/tools/ask-user-question/handler.ts +25 -1
  40. package/src/tools/bash/handler.ts +13 -0
  41. package/src/tools/subagent/handler.ts +16 -5
  42. package/src/tools/subagent/tool.ts +34 -15
  43. package/src/workflow.ts +26 -7
  44. package/tsup.config.ts +1 -0
  45. package/src/activities.ts +0 -91
  46. package/src/plugin.ts +0 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -26,6 +26,16 @@
26
26
  "types": "./dist/workflow.d.ts",
27
27
  "default": "./dist/workflow.js"
28
28
  }
29
+ },
30
+ "./adapters/langchain": {
31
+ "import": {
32
+ "types": "./dist/adapters/langchain/index.d.ts",
33
+ "default": "./dist/adapters/langchain/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/adapters/langchain/index.d.ts",
37
+ "default": "./dist/adapters/langchain/index.js"
38
+ }
29
39
  }
30
40
  },
31
41
  "files": [
@@ -82,15 +92,20 @@
82
92
  "vitest": "^4.0.18"
83
93
  },
84
94
  "peerDependencies": {
95
+ "@langchain/core": ">=1.0.0",
85
96
  "ioredis": ">=5.0.0"
86
97
  },
98
+ "peerDependenciesMeta": {
99
+ "@langchain/core": {
100
+ "optional": true
101
+ }
102
+ },
87
103
  "type": "module",
88
104
  "bugs": {
89
105
  "url": "https://github.com/bead-ai/zeitlich/issues"
90
106
  },
91
107
  "homepage": "https://github.com/bead-ai/zeitlich#readme",
92
108
  "dependencies": {
93
- "@langchain/core": "^1.1.29",
94
109
  "@temporalio/common": "^1.15.0",
95
110
  "@temporalio/plugin": "^1.15.0",
96
111
  "@temporalio/workflow": "^1.15.0",
@@ -0,0 +1,120 @@
1
+ import type Redis from "ioredis";
2
+ import type { ThreadOps, ToolResultConfig } from "../../lib/types";
3
+ import type { MessageContent } from "@langchain/core/messages";
4
+ import type { ModelInvoker } from "../../lib/model-invoker";
5
+ import type { StoredMessage } from "@langchain/core/messages";
6
+ import type {
7
+ BaseChatModel,
8
+ BaseChatModelCallOptions,
9
+ BindToolsInput,
10
+ } from "@langchain/core/language_models/chat_models";
11
+ import { createLangChainThreadManager } from "./thread-manager";
12
+ import { createLangChainModelInvoker } from "./model-invoker";
13
+
14
+ type LangChainModel = BaseChatModel<
15
+ BaseChatModelCallOptions & { tools?: BindToolsInput }
16
+ >;
17
+
18
+ export interface LangChainAdapterConfig {
19
+ redis: Redis;
20
+ /** Optional default model — if omitted, use `createModelInvoker()` to create invokers later */
21
+ model?: LangChainModel;
22
+ }
23
+
24
+ export interface LangChainAdapter {
25
+ /** Thread operations (register these as Temporal activities on the worker) */
26
+ threadOps: ThreadOps;
27
+ /** Model invoker using the default model (only available when `model` was provided) */
28
+ invoker: ModelInvoker<StoredMessage>;
29
+ /** Create an invoker for a specific model (for multi-model setups) */
30
+ createModelInvoker(model: LangChainModel): ModelInvoker<StoredMessage>;
31
+ }
32
+
33
+ /**
34
+ * Creates a LangChain adapter that bundles thread operations and model
35
+ * invocation using a consistent message format (StoredMessage).
36
+ *
37
+ * The returned `threadOps` should be registered as Temporal activities on
38
+ * the worker. The `invoker` (or invokers created via `createModelInvoker`)
39
+ * should be wrapped with `createRunAgentActivity` for per-agent activities.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { createLangChainAdapter } from 'zeitlich/adapters/langchain';
44
+ * import { createRunAgentActivity } from 'zeitlich';
45
+ *
46
+ * const adapter = createLangChainAdapter({ redis, model });
47
+ *
48
+ * export function createActivities(client: WorkflowClient) {
49
+ * return {
50
+ * ...adapter.threadOps,
51
+ * runAgent: createRunAgentActivity(client, adapter.invoker),
52
+ * };
53
+ * }
54
+ * ```
55
+ *
56
+ * @example Multi-model setup
57
+ * ```typescript
58
+ * const adapter = createLangChainAdapter({ redis });
59
+ *
60
+ * export function createActivities(client: WorkflowClient) {
61
+ * return {
62
+ * ...adapter.threadOps,
63
+ * runResearchAgent: createRunAgentActivity(client, adapter.createModelInvoker(claude)),
64
+ * runWriterAgent: createRunAgentActivity(client, adapter.createModelInvoker(gpt4)),
65
+ * };
66
+ * }
67
+ * ```
68
+ */
69
+ export function createLangChainAdapter(
70
+ config: LangChainAdapterConfig
71
+ ): LangChainAdapter {
72
+ const { redis } = config;
73
+
74
+ const threadOps: ThreadOps = {
75
+ async initializeThread(threadId: string): Promise<void> {
76
+ const thread = createLangChainThreadManager({ redis, threadId });
77
+ await thread.initialize();
78
+ },
79
+
80
+ async appendHumanMessage(
81
+ threadId: string,
82
+ content: string | MessageContent
83
+ ): Promise<void> {
84
+ const thread = createLangChainThreadManager({ redis, threadId });
85
+ await thread.appendHumanMessage(content);
86
+ },
87
+
88
+ async appendSystemMessage(
89
+ threadId: string,
90
+ content: string
91
+ ): Promise<void> {
92
+ const thread = createLangChainThreadManager({ redis, threadId });
93
+ await thread.appendSystemMessage(content);
94
+ },
95
+
96
+ async appendToolResult(cfg: ToolResultConfig): Promise<void> {
97
+ const { threadId, toolCallId, content } = cfg;
98
+ const thread = createLangChainThreadManager({ redis, threadId });
99
+ await thread.appendToolMessage(content, toolCallId);
100
+ },
101
+ };
102
+
103
+ const makeInvoker = (model: LangChainModel): ModelInvoker<StoredMessage> =>
104
+ createLangChainModelInvoker({ redis, model });
105
+
106
+ const invoker: ModelInvoker<StoredMessage> = config.model
107
+ ? makeInvoker(config.model)
108
+ : ((() => {
109
+ throw new Error(
110
+ "No default model provided to createLangChainAdapter. " +
111
+ "Either pass `model` in the config or use `createModelInvoker(model)` instead."
112
+ );
113
+ }) as unknown as ModelInvoker<StoredMessage>);
114
+
115
+ return {
116
+ threadOps,
117
+ invoker,
118
+ createModelInvoker: makeInvoker,
119
+ };
120
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * LangChain adapter for Zeitlich.
3
+ *
4
+ * Provides a unified adapter that bundles thread management and model
5
+ * invocation using LangChain's StoredMessage format.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import {
10
+ * createLangChainAdapter,
11
+ * createLangChainThreadManager,
12
+ * } from 'zeitlich/adapters/langchain';
13
+ *
14
+ * const adapter = createLangChainAdapter({ redis, model });
15
+ * ```
16
+ */
17
+
18
+ // Adapter (primary API)
19
+ export {
20
+ createLangChainAdapter,
21
+ type LangChainAdapter,
22
+ type LangChainAdapterConfig,
23
+ } from "./activities";
24
+
25
+ // Thread manager
26
+ export {
27
+ createLangChainThreadManager,
28
+ type LangChainThreadManager,
29
+ type LangChainThreadManagerConfig,
30
+ type LangChainToolMessageContent,
31
+ } from "./thread-manager";
32
+
33
+ // Model invoker (for advanced use — prefer adapter.createModelInvoker)
34
+ export {
35
+ createLangChainModelInvoker,
36
+ invokeLangChainModel,
37
+ type LangChainModelInvokerConfig,
38
+ } from "./model-invoker";
@@ -0,0 +1,102 @@
1
+ import type Redis from "ioredis";
2
+ import type { AgentResponse } from "../../lib/types";
3
+ import type { ModelInvokerConfig } from "../../lib/model-invoker";
4
+ import { mapStoredMessagesToChatMessages } from "@langchain/core/messages";
5
+ import type { StoredMessage } from "@langchain/core/messages";
6
+ import { v4 as uuidv4 } from "uuid";
7
+ import type {
8
+ BaseChatModel,
9
+ BaseChatModelCallOptions,
10
+ BindToolsInput,
11
+ } from "@langchain/core/language_models/chat_models";
12
+ import { createLangChainThreadManager } from "./thread-manager";
13
+
14
+ export interface LangChainModelInvokerConfig {
15
+ redis: Redis;
16
+ model: BaseChatModel<BaseChatModelCallOptions & { tools?: BindToolsInput }>;
17
+ }
18
+
19
+ /**
20
+ * Creates a LangChain-based model invoker that satisfies the generic
21
+ * `ModelInvoker<StoredMessage>` contract.
22
+ *
23
+ * Loads the conversation thread from Redis, invokes a LangChain chat model,
24
+ * appends the AI response, and returns a normalised AgentResponse.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createLangChainModelInvoker } from 'zeitlich/adapters/langchain';
29
+ * import { createRunAgentActivity } from 'zeitlich';
30
+ * import { ChatAnthropic } from '@langchain/anthropic';
31
+ *
32
+ * const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });
33
+ * const invoker = createLangChainModelInvoker({ redis, model });
34
+ *
35
+ * // Wrap with createRunAgentActivity to use as runAgent activity:
36
+ * return { runAgent: createRunAgentActivity(client, invoker) };
37
+ * ```
38
+ */
39
+ export function createLangChainModelInvoker({
40
+ redis,
41
+ model,
42
+ }: LangChainModelInvokerConfig) {
43
+ return async function invokeLangChainModel(
44
+ config: ModelInvokerConfig
45
+ ): Promise<AgentResponse<StoredMessage>> {
46
+ const { threadId, agentName, state, metadata } = config;
47
+
48
+ const thread = createLangChainThreadManager({ redis, threadId });
49
+ const runId = uuidv4();
50
+
51
+ const messages = await thread.load();
52
+ const response = await model.invoke(
53
+ [...mapStoredMessagesToChatMessages(messages)],
54
+ {
55
+ runName: agentName,
56
+ runId,
57
+ metadata: { thread_id: threadId, ...metadata },
58
+ tools: state.tools,
59
+ }
60
+ );
61
+
62
+ await thread.append([response.toDict()]);
63
+
64
+ const toolCalls = response.tool_calls ?? [];
65
+
66
+ return {
67
+ message: response.toDict(),
68
+ rawToolCalls: toolCalls.map((tc) => ({
69
+ id: tc.id,
70
+ name: tc.name,
71
+ args: tc.args,
72
+ })),
73
+ usage: {
74
+ inputTokens: response.usage_metadata?.input_tokens,
75
+ outputTokens: response.usage_metadata?.output_tokens,
76
+ reasonTokens: response.usage_metadata?.output_token_details?.reasoning,
77
+ cachedWriteTokens:
78
+ response.usage_metadata?.input_token_details?.cache_creation,
79
+ cachedReadTokens:
80
+ response.usage_metadata?.input_token_details?.cache_read,
81
+ },
82
+ };
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Standalone function for one-shot LangChain model invocation.
88
+ * Convenience wrapper around createLangChainModelInvoker for cases where
89
+ * you don't need to reuse the invoker.
90
+ */
91
+ export async function invokeLangChainModel({
92
+ redis,
93
+ model,
94
+ config,
95
+ }: {
96
+ redis: Redis;
97
+ config: ModelInvokerConfig;
98
+ model: BaseChatModel<BaseChatModelCallOptions & { tools?: BindToolsInput }>;
99
+ }): Promise<AgentResponse<StoredMessage>> {
100
+ const invoker = createLangChainModelInvoker({ redis, model });
101
+ return invoker(config);
102
+ }
@@ -0,0 +1,142 @@
1
+ import type Redis from "ioredis";
2
+ import {
3
+ type $InferMessageContent,
4
+ AIMessage,
5
+ HumanMessage,
6
+ type MessageContent,
7
+ type MessageStructure,
8
+ type StoredMessage,
9
+ SystemMessage,
10
+ ToolMessage,
11
+ } from "@langchain/core/messages";
12
+ import { v4 as uuidv4 } from "uuid";
13
+ import {
14
+ createThreadManager,
15
+ type BaseThreadManager,
16
+ type ThreadManagerConfig,
17
+ } from "../../lib/thread-manager";
18
+
19
+ export type LangChainToolMessageContent = $InferMessageContent<
20
+ MessageStructure,
21
+ "tool"
22
+ >;
23
+
24
+ export interface LangChainThreadManagerConfig {
25
+ redis: Redis;
26
+ threadId: string;
27
+ /** Thread key, defaults to 'messages' */
28
+ key?: string;
29
+ }
30
+
31
+ /** Thread manager with LangChain StoredMessage convenience helpers */
32
+ export interface LangChainThreadManager extends BaseThreadManager<StoredMessage> {
33
+ createHumanMessage(content: string | MessageContent): StoredMessage;
34
+ createSystemMessage(content: string): StoredMessage;
35
+ createAIMessage(
36
+ content: string | MessageContent,
37
+ kwargs?: { header?: string; options?: string[]; multiSelect?: boolean },
38
+ ): StoredMessage;
39
+ createToolMessage(
40
+ content: LangChainToolMessageContent,
41
+ toolCallId: string,
42
+ ): StoredMessage;
43
+ appendHumanMessage(content: string | MessageContent): Promise<void>;
44
+ appendSystemMessage(content: string): Promise<void>;
45
+ appendToolMessage(
46
+ content: LangChainToolMessageContent,
47
+ toolCallId: string,
48
+ ): Promise<void>;
49
+ appendAIMessage(content: string | MessageContent): Promise<void>;
50
+ }
51
+
52
+ function storedMessageId(msg: StoredMessage): string {
53
+ return msg.data.id ?? "";
54
+ }
55
+
56
+ /**
57
+ * Creates a LangChain-specific thread manager that stores StoredMessage
58
+ * instances in Redis and provides convenience helpers for creating and
59
+ * appending typed LangChain messages.
60
+ */
61
+ export function createLangChainThreadManager(
62
+ config: LangChainThreadManagerConfig,
63
+ ): LangChainThreadManager {
64
+ const baseConfig: ThreadManagerConfig<StoredMessage> = {
65
+ redis: config.redis,
66
+ threadId: config.threadId,
67
+ key: config.key,
68
+ idOf: storedMessageId,
69
+ };
70
+
71
+ const base = createThreadManager(baseConfig);
72
+
73
+ const helpers = {
74
+ createHumanMessage(content: string | MessageContent): StoredMessage {
75
+ return new HumanMessage({
76
+ id: uuidv4(),
77
+ content: content as string,
78
+ }).toDict();
79
+ },
80
+
81
+ createSystemMessage(content: string): StoredMessage {
82
+ return new SystemMessage({
83
+ id: uuidv4(),
84
+ content: content as string,
85
+ }).toDict();
86
+ },
87
+
88
+ createAIMessage(
89
+ content: string,
90
+ kwargs?: { header?: string; options?: string[]; multiSelect?: boolean },
91
+ ): StoredMessage {
92
+ return new AIMessage({
93
+ id: uuidv4(),
94
+ content,
95
+ additional_kwargs: kwargs
96
+ ? {
97
+ header: kwargs.header,
98
+ options: kwargs.options,
99
+ multiSelect: kwargs.multiSelect,
100
+ }
101
+ : undefined,
102
+ }).toDict();
103
+ },
104
+
105
+ createToolMessage(
106
+ content: LangChainToolMessageContent,
107
+ toolCallId: string,
108
+ ): StoredMessage {
109
+ return new ToolMessage({
110
+ id: uuidv4(),
111
+ content: content as MessageContent,
112
+ tool_call_id: toolCallId,
113
+ }).toDict();
114
+ },
115
+
116
+ async appendHumanMessage(content: string | MessageContent): Promise<void> {
117
+ const message = helpers.createHumanMessage(content);
118
+ await base.append([message]);
119
+ },
120
+
121
+ async appendToolMessage(
122
+ content: LangChainToolMessageContent,
123
+ toolCallId: string,
124
+ ): Promise<void> {
125
+ const message = helpers.createToolMessage(content, toolCallId);
126
+ await base.append([message]);
127
+ },
128
+
129
+ async appendAIMessage(content: string | MessageContent): Promise<void> {
130
+ const message = helpers.createAIMessage(content as string);
131
+ await base.append([message]);
132
+ },
133
+
134
+ async appendSystemMessage(content: string): Promise<void> {
135
+ const message = helpers.createSystemMessage(content);
136
+ await base.initialize();
137
+ await base.append([message]);
138
+ },
139
+ };
140
+
141
+ return Object.assign(base, helpers);
142
+ }
package/src/index.ts CHANGED
@@ -1,18 +1,22 @@
1
1
  /**
2
2
  * Activity-side exports for use in Temporal activity code and worker setup.
3
3
  *
4
- * Import from 'zeitlich' in activity files and worker setup.
5
- * These exports may have external dependencies (Redis, LangChain).
6
- *
7
- * For workflow code, use 'zeitlich/workflow' instead.
4
+ * Import from `zeitlich` in activity files and worker setup.
5
+ * For LangChain-specific adapters (model invoker, thread manager, adapter),
6
+ * import from `zeitlich/adapters/langchain`.
7
+ * For workflow code, use `zeitlich/workflow` instead.
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
11
  * // In your activities file
12
- * import { invokeModel, createGlobHandler } from 'zeitlich';
12
+ * import {
13
+ * createBashHandler,
14
+ * createAskUserQuestionHandler,
15
+ * toTree,
16
+ * } from 'zeitlich';
13
17
  *
14
- * // In your worker file
15
- * import { ZeitlichPlugin } from 'zeitlich';
18
+ * // LangChain adapter
19
+ * import { createLangChainAdapter } from 'zeitlich/adapters/langchain';
16
20
  * ```
17
21
  */
18
22
 
@@ -20,20 +24,24 @@
20
24
  // (Activities can use these too)
21
25
  export * from "./workflow";
22
26
 
23
- // Plugin (requires Redis)
24
- export { ZeitlichPlugin } from "./plugin";
25
- export type { ZeitlichPluginOptions } from "./plugin";
27
+ // Thread manager (generic, framework-agnostic)
28
+ export { createThreadManager } from "./lib/thread-manager";
29
+ export type {
30
+ BaseThreadManager,
31
+ ThreadManagerConfig,
32
+ } from "./lib/thread-manager";
26
33
 
27
- // Shared activities (requires Redis)
28
- export { createSharedActivities } from "./activities";
29
- export type { ZeitlichSharedActivities } from "./activities";
34
+ // Model invoker contract (framework-agnostic)
35
+ export type { ModelInvoker, ModelInvokerConfig } from "./lib/model-invoker";
30
36
 
31
37
  // Auto-append wrapper for large tool results (activity-side only)
32
38
  export { withAutoAppend } from "./lib/tool-router";
33
39
 
34
- // Model invocation (requires Redis, LangChain)
35
- export { invokeModel } from "./lib/model-invoker";
36
- export type { InvokeModelConfig } from "./lib/model-invoker";
40
+ // Workflow state helpers (requires Temporal client)
41
+ export {
42
+ queryParentWorkflowState,
43
+ createRunAgentActivity,
44
+ } from "./lib/workflow-helpers";
37
45
 
38
46
  // Tool handlers (activity implementations)
39
47
  // All handlers follow the factory pattern: createXHandler(deps) => handler(args)
@@ -47,10 +55,3 @@ export { toTree } from "./lib/fs";
47
55
 
48
56
  // Skills (activity-side: filesystem provider)
49
57
  export { FileSystemSkillProvider } from "./lib/skills/fs-provider";
50
-
51
- export { createThreadManager } from "./lib/thread-manager";
52
- export type {
53
- BaseThreadManager,
54
- ThreadManager,
55
- ThreadManagerConfig,
56
- } from "./lib/thread-manager";
package/src/lib/fs.ts CHANGED
@@ -24,6 +24,31 @@ const printTree = async (
24
24
  return str;
25
25
  };
26
26
 
27
+ /**
28
+ * Generates a formatted file tree string from an `IFileSystem` instance.
29
+ * Useful for including filesystem context in agent prompts.
30
+ *
31
+ * @param fs - File system implementation (e.g. from `just-bash`)
32
+ * @param opts - Optional configuration for tree generation
33
+ * @param opts.dir - Root directory to start from (defaults to `/`)
34
+ * @param opts.separator - Path separator (`/` or `\\`, defaults to `/`)
35
+ * @param opts.depth - Maximum directory depth (defaults to 10)
36
+ * @param opts.sort - Sort entries alphabetically with directories first (defaults to true)
37
+ * @returns Formatted file tree string
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { toTree } from 'zeitlich';
42
+ *
43
+ * const fileTree = await toTree(inMemoryFileSystem);
44
+ * // Returns:
45
+ * // /
46
+ * // ├─ src/
47
+ * // │ ├─ index.ts
48
+ * // │ └─ utils.ts
49
+ * // └─ package.json
50
+ * ```
51
+ */
27
52
  export const toTree = async (
28
53
  fs: IFileSystem,
29
54
  opts: {
@@ -1,85 +1,25 @@
1
- import type Redis from "ioredis";
2
- import { createThreadManager } from "./thread-manager";
3
- import { agentQueryName, type AgentResponse, type BaseAgentState } from "./types";
4
- import { Context } from "@temporalio/activity";
5
- import type { WorkflowClient } from "@temporalio/client";
6
- import { mapStoredMessagesToChatMessages } from "@langchain/core/messages";
7
- import { v4 as uuidv4 } from "uuid";
8
- import type {
9
- BaseChatModel,
10
- BaseChatModelCallOptions,
11
- BindToolsInput,
12
- } from "@langchain/core/language_models/chat_models";
1
+ import type { AgentResponse, BaseAgentState } from "./types";
13
2
 
14
3
  /**
15
- * Configuration for invoking the model
4
+ * Configuration passed to a ModelInvoker.
5
+ * Includes the full agent state so adapters can read tools, system prompt,
6
+ * token usage, or any custom state fields for model configuration.
16
7
  */
17
- export interface InvokeModelConfig {
8
+ export interface ModelInvokerConfig {
18
9
  threadId: string;
19
10
  agentName: string;
11
+ state: BaseAgentState;
12
+ metadata?: Record<string, unknown>;
20
13
  }
21
14
 
22
15
  /**
23
- * Core model invocation logic - shared utility for workflow-specific activities
16
+ * Generic model invocation contract.
17
+ * Implementations load the thread, call the LLM, append the response,
18
+ * and return a normalised AgentResponse.
24
19
  *
25
- * @param options - Named options object
26
- * @param options.redis - Redis client for thread management
27
- * @param options.config - Model invocation configuration (threadId, agentName)
28
- * @param options.model - Pre-instantiated LangChain chat model
29
- * @param options.client - Temporal WorkflowClient for querying workflow state
30
- * @returns Agent response with message and metadata
20
+ * Framework adapters (e.g. `zeitlich/langchain`) provide concrete
21
+ * implementations of this type.
31
22
  */
32
- export async function invokeModel({
33
- redis,
34
- model,
35
- client,
36
- config: { threadId, agentName },
37
- }: {
38
- redis: Redis;
39
- client: WorkflowClient;
40
- config: InvokeModelConfig;
41
- model: BaseChatModel<BaseChatModelCallOptions & { tools?: BindToolsInput }>;
42
- }): Promise<AgentResponse> {
43
- const thread = createThreadManager({ redis, threadId });
44
- const runId = uuidv4();
45
-
46
- const info = Context.current().info; // Activity info
47
- const parentWorkflowId = info.workflowExecution.workflowId;
48
- const parentRunId = info.workflowExecution.runId;
49
-
50
- const handle = client.getHandle(parentWorkflowId, parentRunId);
51
- const { tools } = await handle.query<BaseAgentState>(agentQueryName(agentName));
52
-
53
- const messages = await thread.load();
54
- const response = await model.invoke(
55
- [...mapStoredMessagesToChatMessages(messages)],
56
- {
57
- runName: agentName,
58
- runId,
59
- metadata: { thread_id: threadId },
60
- tools,
61
- }
62
- );
63
-
64
- await thread.append([response.toDict()]);
65
-
66
- const toolCalls = response.tool_calls ?? [];
67
-
68
- return {
69
- message: response.toDict(),
70
- rawToolCalls: toolCalls.map((tc) => ({
71
- id: tc.id,
72
- name: tc.name,
73
- args: tc.args,
74
- })),
75
- usage: {
76
- inputTokens: response.usage_metadata?.input_tokens,
77
- outputTokens: response.usage_metadata?.output_tokens,
78
- reasonTokens: response.usage_metadata?.output_token_details?.reasoning,
79
- cachedWriteTokens:
80
- response.usage_metadata?.input_token_details?.cache_creation,
81
- cachedReadTokens:
82
- response.usage_metadata?.input_token_details?.cache_read,
83
- },
84
- };
85
- }
23
+ export type ModelInvoker<M = unknown> = (
24
+ config: ModelInvokerConfig
25
+ ) => Promise<AgentResponse<M>>;