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.
- package/README.md +313 -126
- package/dist/adapters/langchain/index.cjs +270 -0
- package/dist/adapters/langchain/index.cjs.map +1 -0
- package/dist/adapters/langchain/index.d.cts +132 -0
- package/dist/adapters/langchain/index.d.ts +132 -0
- package/dist/adapters/langchain/index.js +265 -0
- package/dist/adapters/langchain/index.js.map +1 -0
- package/dist/index.cjs +89 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -46
- package/dist/index.d.ts +62 -46
- package/dist/index.js +88 -208
- package/dist/index.js.map +1 -1
- package/dist/{workflow-BhjsEQc1.d.cts → model-invoker-y_zlyMqu.d.cts} +45 -482
- package/dist/{workflow-BhjsEQc1.d.ts → model-invoker-y_zlyMqu.d.ts} +45 -482
- package/dist/thread-manager-qc0g5Rvd.d.cts +39 -0
- package/dist/thread-manager-qc0g5Rvd.d.ts +39 -0
- package/dist/workflow.cjs +59 -27
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +459 -6
- package/dist/workflow.d.ts +459 -6
- package/dist/workflow.js +60 -29
- package/dist/workflow.js.map +1 -1
- package/package.json +17 -2
- package/src/adapters/langchain/activities.ts +120 -0
- package/src/adapters/langchain/index.ts +38 -0
- package/src/adapters/langchain/model-invoker.ts +102 -0
- package/src/adapters/langchain/thread-manager.ts +142 -0
- package/src/index.ts +24 -23
- package/src/lib/fs.ts +25 -0
- package/src/lib/model-invoker.ts +15 -75
- package/src/lib/session.ts +52 -21
- package/src/lib/state-manager.ts +23 -5
- package/src/lib/thread-id.ts +25 -0
- package/src/lib/thread-manager.ts +18 -142
- package/src/lib/tool-router.ts +12 -18
- package/src/lib/types.ts +26 -10
- package/src/lib/workflow-helpers.ts +50 -0
- package/src/tools/ask-user-question/handler.ts +25 -1
- package/src/tools/bash/handler.ts +13 -0
- package/src/tools/subagent/handler.ts +16 -5
- package/src/tools/subagent/tool.ts +34 -15
- package/src/workflow.ts +26 -7
- package/tsup.config.ts +1 -0
- package/src/activities.ts +0 -91
- package/src/plugin.ts +0 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeitlich",
|
|
3
|
-
"version": "0.2.
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* For workflow code, use
|
|
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 {
|
|
12
|
+
* import {
|
|
13
|
+
* createBashHandler,
|
|
14
|
+
* createAskUserQuestionHandler,
|
|
15
|
+
* toTree,
|
|
16
|
+
* } from 'zeitlich';
|
|
13
17
|
*
|
|
14
|
-
* //
|
|
15
|
-
* import {
|
|
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
|
-
//
|
|
24
|
-
export {
|
|
25
|
-
export type {
|
|
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
|
-
//
|
|
28
|
-
export {
|
|
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
|
-
//
|
|
35
|
-
export {
|
|
36
|
-
|
|
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: {
|
package/src/lib/model-invoker.ts
CHANGED
|
@@ -1,85 +1,25 @@
|
|
|
1
|
-
import type
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
26
|
-
*
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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>>;
|