zeitlich 0.2.13 → 0.2.14
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 +49 -38
- package/dist/adapters/sandbox/daytona/index.cjs +205 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
- package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
- package/dist/adapters/sandbox/daytona/index.js +202 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
- package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
- package/dist/adapters/sandbox/inmemory/index.js +172 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +405 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
- package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
- package/dist/adapters/sandbox/virtual/index.js +400 -0
- package/dist/adapters/sandbox/virtual/index.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +284 -0
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/index.d.cts +145 -0
- package/dist/adapters/thread/google-genai/index.d.ts +145 -0
- package/dist/adapters/thread/google-genai/index.js +278 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
- package/dist/adapters/thread/langchain/index.cjs.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +816 -545
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -74
- package/dist/index.d.ts +235 -74
- package/dist/index.js +804 -540
- package/dist/index.js.map +1 -1
- package/dist/types-B4C9txdq.d.ts +389 -0
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
- package/dist/types-BMXzv7TN.d.cts +476 -0
- package/dist/types-BMXzv7TN.d.ts +476 -0
- package/dist/types-BVP87m_W.d.cts +121 -0
- package/dist/types-CDubRtad.d.cts +115 -0
- package/dist/types-CDubRtad.d.ts +115 -0
- package/dist/types-CwwgQ_9H.d.ts +121 -0
- package/dist/types-GpMU4b0w.d.cts +389 -0
- package/dist/workflow.cjs +444 -318
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +271 -222
- package/dist/workflow.d.ts +271 -222
- package/dist/workflow.js +440 -316
- package/dist/workflow.js.map +1 -1
- package/package.json +59 -6
- package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
- package/src/adapters/sandbox/daytona/index.ts +149 -0
- package/src/adapters/sandbox/daytona/types.ts +34 -0
- package/src/adapters/sandbox/inmemory/index.ts +213 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
- package/src/adapters/sandbox/virtual/index.ts +88 -0
- package/src/adapters/sandbox/virtual/mutations.ts +38 -0
- package/src/adapters/sandbox/virtual/provider.ts +101 -0
- package/src/adapters/sandbox/virtual/tree.ts +82 -0
- package/src/adapters/sandbox/virtual/types.ts +127 -0
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
- package/src/adapters/thread/google-genai/activities.ts +121 -0
- package/src/adapters/thread/google-genai/index.ts +41 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
- package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
- package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
- package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
- package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
- package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
- package/src/index.ts +32 -24
- package/src/lib/activity.ts +87 -0
- package/src/lib/hooks/index.ts +11 -0
- package/src/lib/hooks/types.ts +98 -0
- package/src/lib/model/helpers.ts +6 -0
- package/src/lib/model/index.ts +13 -0
- package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
- package/src/lib/sandbox/index.ts +19 -0
- package/src/lib/sandbox/manager.ts +76 -0
- package/src/lib/sandbox/sandbox.test.ts +158 -0
- package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
- package/src/lib/sandbox/types.ts +164 -0
- package/src/lib/session/index.ts +11 -0
- package/src/lib/{session.ts → session/session.ts} +76 -48
- package/src/lib/session/types.ts +93 -0
- package/src/lib/skills/fs-provider.ts +16 -15
- package/src/lib/skills/handler.ts +31 -0
- package/src/lib/skills/index.ts +5 -1
- package/src/lib/skills/register.ts +20 -0
- package/src/lib/skills/tool.ts +47 -0
- package/src/lib/state/index.ts +9 -0
- package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
- package/src/lib/state/types.ts +134 -0
- package/src/lib/subagent/define.ts +71 -0
- package/src/lib/subagent/handler.ts +99 -0
- package/src/lib/subagent/index.ts +13 -0
- package/src/lib/subagent/register.ts +53 -0
- package/src/lib/subagent/tool.ts +80 -0
- package/src/lib/subagent/types.ts +92 -0
- package/src/lib/thread/index.ts +7 -0
- package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
- package/src/lib/thread/types.ts +33 -0
- package/src/lib/tool-router/auto-append.ts +55 -0
- package/src/lib/tool-router/index.ts +41 -0
- package/src/lib/tool-router/router.ts +462 -0
- package/src/lib/tool-router/types.ts +478 -0
- package/src/lib/tool-router/with-sandbox.ts +70 -0
- package/src/lib/types.ts +5 -382
- package/src/tools/bash/bash.test.ts +53 -55
- package/src/tools/bash/handler.ts +23 -51
- package/src/tools/edit/handler.ts +67 -81
- package/src/tools/glob/handler.ts +60 -17
- package/src/tools/read-file/handler.ts +67 -0
- package/src/tools/read-skill/handler.ts +1 -31
- package/src/tools/read-skill/tool.ts +5 -47
- package/src/tools/subagent/handler.ts +1 -100
- package/src/tools/subagent/tool.ts +5 -93
- package/src/tools/task-create/handler.ts +1 -1
- package/src/tools/task-get/handler.ts +1 -1
- package/src/tools/task-list/handler.ts +1 -1
- package/src/tools/task-update/handler.ts +1 -1
- package/src/tools/write-file/handler.ts +47 -0
- package/src/workflow.ts +88 -47
- package/tsup.config.ts +8 -1
- package/dist/adapters/langchain/index.cjs.map +0 -1
- package/dist/adapters/langchain/index.js.map +0 -1
- package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
- package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
- package/src/lib/tool-router.ts +0 -977
- package/src/lib/workflow-helpers.ts +0 -50
- /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import type Redis from "ioredis";
|
|
2
|
-
import type { AgentResponse } from "
|
|
3
|
-
import type { ModelInvokerConfig } from "
|
|
2
|
+
import type { AgentResponse } from "../../../lib/model";
|
|
3
|
+
import type { ModelInvokerConfig } from "../../../lib/model";
|
|
4
4
|
import { mapStoredMessagesToChatMessages } from "@langchain/core/messages";
|
|
5
5
|
import type { StoredMessage } from "@langchain/core/messages";
|
|
6
6
|
import { v4 as uuidv4 } from "uuid";
|
|
7
|
-
import type {
|
|
8
|
-
BaseChatModel,
|
|
9
|
-
BaseChatModelCallOptions,
|
|
10
|
-
BindToolsInput,
|
|
11
|
-
} from "@langchain/core/language_models/chat_models";
|
|
7
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
12
8
|
import { createLangChainThreadManager } from "./thread-manager";
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> = BaseChatModel<any>> {
|
|
15
12
|
redis: Redis;
|
|
16
|
-
model:
|
|
13
|
+
model: TModel;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
/**
|
|
@@ -25,21 +22,20 @@ export interface LangChainModelInvokerConfig {
|
|
|
25
22
|
*
|
|
26
23
|
* @example
|
|
27
24
|
* ```typescript
|
|
28
|
-
* import { createLangChainModelInvoker } from 'zeitlich/adapters/langchain';
|
|
25
|
+
* import { createLangChainModelInvoker } from 'zeitlich/adapters/thread/langchain';
|
|
29
26
|
* import { createRunAgentActivity } from 'zeitlich';
|
|
30
27
|
* import { ChatAnthropic } from '@langchain/anthropic';
|
|
31
28
|
*
|
|
32
29
|
* const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });
|
|
33
30
|
* const invoker = createLangChainModelInvoker({ redis, model });
|
|
34
31
|
*
|
|
35
|
-
* // Wrap with createRunAgentActivity to use as runAgent activity:
|
|
36
32
|
* return { runAgent: createRunAgentActivity(client, invoker) };
|
|
37
33
|
* ```
|
|
38
34
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
model
|
|
42
|
-
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
export function createLangChainModelInvoker<TModel extends BaseChatModel<any> = BaseChatModel<any>>(
|
|
37
|
+
{ redis, model }: LangChainModelInvokerConfig<TModel>,
|
|
38
|
+
) {
|
|
43
39
|
return async function invokeLangChainModel(
|
|
44
40
|
config: ModelInvokerConfig
|
|
45
41
|
): Promise<AgentResponse<StoredMessage>> {
|
|
@@ -54,7 +50,7 @@ export function createLangChainModelInvoker({
|
|
|
54
50
|
{
|
|
55
51
|
runName: agentName,
|
|
56
52
|
runId,
|
|
57
|
-
metadata: { thread_id: threadId
|
|
53
|
+
metadata: { thread_id: `${agentName}-${threadId}`, ...metadata },
|
|
58
54
|
tools: state.tools,
|
|
59
55
|
}
|
|
60
56
|
);
|
|
@@ -88,14 +84,15 @@ export function createLangChainModelInvoker({
|
|
|
88
84
|
* Convenience wrapper around createLangChainModelInvoker for cases where
|
|
89
85
|
* you don't need to reuse the invoker.
|
|
90
86
|
*/
|
|
91
|
-
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
export async function invokeLangChainModel<TModel extends BaseChatModel<any> = BaseChatModel<any>>({
|
|
92
89
|
redis,
|
|
93
90
|
model,
|
|
94
91
|
config,
|
|
95
92
|
}: {
|
|
96
93
|
redis: Redis;
|
|
97
94
|
config: ModelInvokerConfig;
|
|
98
|
-
model:
|
|
95
|
+
model: TModel;
|
|
99
96
|
}): Promise<AgentResponse<StoredMessage>> {
|
|
100
97
|
const invoker = createLangChainModelInvoker({ redis, model });
|
|
101
98
|
return invoker(config);
|
package/src/index.ts
CHANGED
|
@@ -3,20 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Import from `zeitlich` in activity files and worker setup.
|
|
5
5
|
* For LangChain-specific adapters (model invoker, thread manager, adapter),
|
|
6
|
-
* import from `zeitlich/adapters/langchain`.
|
|
6
|
+
* import from `zeitlich/adapters/thread/langchain`.
|
|
7
7
|
* For workflow code, use `zeitlich/workflow` instead.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
11
11
|
* // In your activities file
|
|
12
12
|
* import {
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* SandboxManager,
|
|
14
|
+
* withSandbox,
|
|
15
|
+
* bashHandler,
|
|
16
|
+
* editHandler,
|
|
15
17
|
* toTree,
|
|
16
18
|
* } from 'zeitlich';
|
|
17
19
|
*
|
|
20
|
+
* // In-memory sandbox adapter
|
|
21
|
+
* import { InMemorySandboxProvider } from 'zeitlich/adapters/sandbox/inmemory';
|
|
22
|
+
*
|
|
18
23
|
* // LangChain adapter
|
|
19
|
-
* import { createLangChainAdapter } from 'zeitlich/adapters/langchain';
|
|
24
|
+
* import { createLangChainAdapter } from 'zeitlich/adapters/thread/langchain';
|
|
20
25
|
* ```
|
|
21
26
|
*/
|
|
22
27
|
|
|
@@ -24,34 +29,37 @@
|
|
|
24
29
|
// (Activities can use these too)
|
|
25
30
|
export * from "./workflow";
|
|
26
31
|
|
|
32
|
+
// Skills (activity-side: filesystem provider uses node:path)
|
|
33
|
+
export { FileSystemSkillProvider } from "./lib/skills/fs-provider";
|
|
34
|
+
|
|
27
35
|
// Thread manager (generic, framework-agnostic)
|
|
28
|
-
export { createThreadManager } from "./lib/thread
|
|
29
|
-
export type {
|
|
30
|
-
BaseThreadManager,
|
|
31
|
-
ThreadManagerConfig,
|
|
32
|
-
} from "./lib/thread-manager";
|
|
36
|
+
export { createThreadManager } from "./lib/thread";
|
|
37
|
+
export type { BaseThreadManager, ThreadManagerConfig } from "./lib/thread";
|
|
33
38
|
|
|
34
39
|
// Model invoker contract (framework-agnostic)
|
|
35
|
-
export type { ModelInvoker, ModelInvokerConfig } from "./lib/model
|
|
40
|
+
export type { ModelInvoker, ModelInvokerConfig } from "./lib/model";
|
|
36
41
|
|
|
37
|
-
//
|
|
38
|
-
export { withAutoAppend } from "./lib/tool-router";
|
|
42
|
+
// Activity-side handler wrappers
|
|
43
|
+
export { withAutoAppend, withSandbox } from "./lib/tool-router";
|
|
44
|
+
export type { SandboxContext } from "./lib/tool-router";
|
|
39
45
|
|
|
40
|
-
//
|
|
46
|
+
// Activity-side wrappers (requires Temporal client)
|
|
41
47
|
export {
|
|
42
48
|
queryParentWorkflowState,
|
|
43
49
|
createRunAgentActivity,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// All handlers follow the factory pattern: createXHandler(deps) => handler(args)
|
|
48
|
-
export { createGlobHandler } from "./tools/glob/handler";
|
|
49
|
-
|
|
50
|
-
export { createEditHandler } from "./tools/edit/handler";
|
|
50
|
+
withParentWorkflowState,
|
|
51
|
+
} from "./lib/activity";
|
|
52
|
+
export type { AgentStateContext } from "./lib/activity";
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
// Sandbox (activity-side: manager)
|
|
55
|
+
export { SandboxManager } from "./lib/sandbox/manager";
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
// Tool handlers (activity implementations)
|
|
58
|
+
// Wrap sandbox handlers with withSandbox(manager, handler) at registration time
|
|
59
|
+
export { bashHandler } from "./tools/bash/handler";
|
|
60
|
+
export { editHandler } from "./tools/edit/handler";
|
|
61
|
+
export { globHandler } from "./tools/glob/handler";
|
|
62
|
+
export { readFileHandler } from "./tools/read-file/handler";
|
|
63
|
+
export { writeFileHandler } from "./tools/write-file/handler";
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
export { FileSystemSkillProvider } from "./lib/skills/fs-provider";
|
|
65
|
+
export { toTree } from "./lib/sandbox";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Context } from "@temporalio/activity";
|
|
2
|
+
import type { WorkflowClient } from "@temporalio/client";
|
|
3
|
+
import type { BaseAgentState, RunAgentConfig } from "./types";
|
|
4
|
+
import type {
|
|
5
|
+
ActivityToolHandler,
|
|
6
|
+
RouterContext,
|
|
7
|
+
ToolHandlerResponse,
|
|
8
|
+
} from "./tool-router/types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Query the parent workflow's state from within an activity.
|
|
12
|
+
* Resolves the workflow handle from the current activity context.
|
|
13
|
+
*/
|
|
14
|
+
export async function queryParentWorkflowState<T>(
|
|
15
|
+
client: WorkflowClient
|
|
16
|
+
): Promise<T> {
|
|
17
|
+
const { workflowExecution } = Context.current().info;
|
|
18
|
+
const handle = client.getHandle(
|
|
19
|
+
workflowExecution.workflowId,
|
|
20
|
+
workflowExecution.runId
|
|
21
|
+
);
|
|
22
|
+
return handle.query<T>("getAgentState");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a handler into a `RunAgentActivity` by auto-fetching the parent
|
|
27
|
+
* workflow's agent state before each invocation.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { createRunAgentActivity } from 'zeitlich';
|
|
32
|
+
* import { createLangChainModelInvoker } from 'zeitlich/adapters/thread/langchain';
|
|
33
|
+
*
|
|
34
|
+
* const invoker = createLangChainModelInvoker({ redis, model });
|
|
35
|
+
* return { runAgent: createRunAgentActivity(client, invoker) };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function createRunAgentActivity<R, S extends BaseAgentState = BaseAgentState>(
|
|
39
|
+
client: WorkflowClient,
|
|
40
|
+
handler: (config: RunAgentConfig & { state: S }) => Promise<R>,
|
|
41
|
+
): (config: RunAgentConfig) => Promise<R> {
|
|
42
|
+
return async (config: RunAgentConfig) => {
|
|
43
|
+
const state = await queryParentWorkflowState<S>(client);
|
|
44
|
+
return handler({ ...config, state });
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Context injected into tool handlers created via {@link withParentWorkflowState}.
|
|
50
|
+
*/
|
|
51
|
+
export interface AgentStateContext<S extends BaseAgentState = BaseAgentState> extends RouterContext {
|
|
52
|
+
state: S;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wraps a tool handler into an `ActivityToolHandler` by auto-fetching the
|
|
57
|
+
* parent workflow's agent state before each invocation.
|
|
58
|
+
*
|
|
59
|
+
* @typeParam S - Custom agent state type (defaults to `BaseAgentState`)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { withParentWorkflowState, type AgentStateContext } from 'zeitlich';
|
|
64
|
+
*
|
|
65
|
+
* // With custom state:
|
|
66
|
+
* interface MyState extends BaseAgentState { customField: string }
|
|
67
|
+
* const myHandler = withParentWorkflowState<MyArgs, MyResult, MyState>(
|
|
68
|
+
* client,
|
|
69
|
+
* async (args, ctx) => {
|
|
70
|
+
* console.log(ctx.state.customField);
|
|
71
|
+
* return { toolResponse: 'done', data: null };
|
|
72
|
+
* },
|
|
73
|
+
* );
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function withParentWorkflowState<TArgs, TResult, S extends BaseAgentState = BaseAgentState>(
|
|
77
|
+
client: WorkflowClient,
|
|
78
|
+
handler: (
|
|
79
|
+
args: TArgs,
|
|
80
|
+
context: AgentStateContext<S>,
|
|
81
|
+
) => Promise<ToolHandlerResponse<TResult>>,
|
|
82
|
+
): ActivityToolHandler<TArgs, TResult> {
|
|
83
|
+
return async (args, context) => {
|
|
84
|
+
const state = await queryParentWorkflowState<S>(client);
|
|
85
|
+
return handler(args, { ...context, state });
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
SessionStartHookContext,
|
|
3
|
+
SessionStartHook,
|
|
4
|
+
SessionEndHookContext,
|
|
5
|
+
SessionEndHook,
|
|
6
|
+
PreHumanMessageAppendHookContext,
|
|
7
|
+
PreHumanMessageAppendHook,
|
|
8
|
+
PostHumanMessageAppendHookContext,
|
|
9
|
+
PostHumanMessageAppendHook,
|
|
10
|
+
Hooks,
|
|
11
|
+
} from "./types";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { MessageContent, SessionExitReason } from "../types";
|
|
2
|
+
import type {
|
|
3
|
+
ToolMap,
|
|
4
|
+
ToolRouterHooks,
|
|
5
|
+
} from "../tool-router/types";
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Session Lifecycle Hooks
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context for SessionStart hook - called when session begins
|
|
13
|
+
*/
|
|
14
|
+
export interface SessionStartHookContext {
|
|
15
|
+
threadId: string;
|
|
16
|
+
agentName: string;
|
|
17
|
+
metadata: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* SessionStart hook - called when session begins
|
|
22
|
+
*/
|
|
23
|
+
export type SessionStartHook = (
|
|
24
|
+
ctx: SessionStartHookContext
|
|
25
|
+
) => void | Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Context for SessionEnd hook - called when session ends
|
|
29
|
+
*/
|
|
30
|
+
export interface SessionEndHookContext {
|
|
31
|
+
threadId: string;
|
|
32
|
+
agentName: string;
|
|
33
|
+
exitReason: SessionExitReason;
|
|
34
|
+
turns: number;
|
|
35
|
+
metadata: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SessionEnd hook - called when session ends
|
|
40
|
+
*/
|
|
41
|
+
export type SessionEndHook = (
|
|
42
|
+
ctx: SessionEndHookContext
|
|
43
|
+
) => void | Promise<void>;
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Message Lifecycle Hooks
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Context for PreHumanMessageAppend hook - called before each human message is appended to the thread
|
|
51
|
+
*/
|
|
52
|
+
export interface PreHumanMessageAppendHookContext {
|
|
53
|
+
message: MessageContent;
|
|
54
|
+
threadId: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* PreHumanMessageAppend hook - called before each human message is appended to the thread
|
|
59
|
+
*/
|
|
60
|
+
export type PreHumanMessageAppendHook = (
|
|
61
|
+
ctx: PreHumanMessageAppendHookContext
|
|
62
|
+
) => void | Promise<void>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Context for PostHumanMessageAppend hook - called after each human message is appended to the thread
|
|
66
|
+
*/
|
|
67
|
+
export interface PostHumanMessageAppendHookContext {
|
|
68
|
+
message: MessageContent;
|
|
69
|
+
threadId: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* PostHumanMessageAppend hook - called after each human message is appended to the thread
|
|
74
|
+
*/
|
|
75
|
+
export type PostHumanMessageAppendHook = (
|
|
76
|
+
ctx: PostHumanMessageAppendHookContext
|
|
77
|
+
) => void | Promise<void>;
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Combined Hooks Interface
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Full hooks interface for a session — combines tool execution hooks
|
|
85
|
+
* (consumed by the router) with session/message lifecycle hooks
|
|
86
|
+
* (consumed directly by the session).
|
|
87
|
+
*/
|
|
88
|
+
export interface Hooks<T extends ToolMap, TResult = unknown>
|
|
89
|
+
extends ToolRouterHooks<T, TResult> {
|
|
90
|
+
/** Called before each human message is appended to the thread */
|
|
91
|
+
onPreHumanMessageAppend?: PreHumanMessageAppendHook;
|
|
92
|
+
/** Called after each human message is appended to the thread */
|
|
93
|
+
onPostHumanMessageAppend?: PostHumanMessageAppendHook;
|
|
94
|
+
/** Called when session starts */
|
|
95
|
+
onSessionStart?: SessionStartHook;
|
|
96
|
+
/** Called when session ends */
|
|
97
|
+
onSessionEnd?: SessionEndHook;
|
|
98
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
queryParentWorkflowState,
|
|
3
|
+
createRunAgentActivity,
|
|
4
|
+
withParentWorkflowState,
|
|
5
|
+
} from "./helpers";
|
|
6
|
+
export type { AgentStateContext } from "./helpers";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
AgentResponse,
|
|
10
|
+
RunAgentActivity,
|
|
11
|
+
ModelInvokerConfig,
|
|
12
|
+
ModelInvoker,
|
|
13
|
+
} from "./types";
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TokenUsage, BaseAgentState, RunAgentConfig } from "../types";
|
|
2
|
+
import type { RawToolCall } from "../tool-router/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Agent response from LLM invocation
|
|
6
|
+
*/
|
|
7
|
+
export interface AgentResponse<M = unknown> {
|
|
8
|
+
message: M;
|
|
9
|
+
rawToolCalls: RawToolCall[];
|
|
10
|
+
usage?: TokenUsage;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Type signature for workflow-specific runAgent activity
|
|
15
|
+
*/
|
|
16
|
+
export type RunAgentActivity<M = unknown> = (
|
|
17
|
+
config: RunAgentConfig
|
|
18
|
+
) => Promise<AgentResponse<M>>;
|
|
2
19
|
|
|
3
20
|
/**
|
|
4
21
|
* Configuration passed to a ModelInvoker.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { SandboxManager } from "./manager";
|
|
2
|
+
export { toTree } from "./tree";
|
|
3
|
+
export type {
|
|
4
|
+
Sandbox,
|
|
5
|
+
SandboxCapabilities,
|
|
6
|
+
SandboxCreateOptions,
|
|
7
|
+
SandboxFileSystem,
|
|
8
|
+
SandboxOps,
|
|
9
|
+
SandboxProvider,
|
|
10
|
+
SandboxSnapshot,
|
|
11
|
+
ExecOptions,
|
|
12
|
+
ExecResult,
|
|
13
|
+
DirentEntry,
|
|
14
|
+
FileStat,
|
|
15
|
+
} from "./types";
|
|
16
|
+
export {
|
|
17
|
+
SandboxNotFoundError,
|
|
18
|
+
SandboxNotSupportedError,
|
|
19
|
+
} from "./types";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Sandbox,
|
|
3
|
+
SandboxCreateOptions,
|
|
4
|
+
SandboxOps,
|
|
5
|
+
SandboxProvider,
|
|
6
|
+
SandboxSnapshot,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stateless facade over a {@link SandboxProvider}.
|
|
11
|
+
*
|
|
12
|
+
* Delegates all lifecycle operations to the provider, which is responsible
|
|
13
|
+
* for its own instance management strategy (e.g. in-memory map, remote API).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const manager = new SandboxManager(new InMemorySandboxProvider());
|
|
18
|
+
* const activities = {
|
|
19
|
+
* ...manager.createActivities(),
|
|
20
|
+
* bashHandler: withSandbox(manager, bashHandler),
|
|
21
|
+
* };
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class SandboxManager<
|
|
25
|
+
TOptions extends SandboxCreateOptions = SandboxCreateOptions,
|
|
26
|
+
TSandbox extends Sandbox = Sandbox,
|
|
27
|
+
> {
|
|
28
|
+
constructor(private provider: SandboxProvider<TOptions, TSandbox>) {}
|
|
29
|
+
|
|
30
|
+
async create(
|
|
31
|
+
options?: TOptions
|
|
32
|
+
): Promise<{ sandboxId: string; stateUpdate?: Record<string, unknown> }> {
|
|
33
|
+
const { sandbox, stateUpdate } = await this.provider.create(options);
|
|
34
|
+
return { sandboxId: sandbox.id, ...(stateUpdate && { stateUpdate }) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getSandbox(id: string): Promise<TSandbox> {
|
|
38
|
+
return this.provider.get(id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async destroy(id: string): Promise<void> {
|
|
42
|
+
await this.provider.destroy(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async snapshot(id: string): Promise<SandboxSnapshot> {
|
|
46
|
+
return this.provider.snapshot(id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async restore(snapshot: SandboxSnapshot): Promise<string> {
|
|
50
|
+
const sandbox = await this.provider.restore(snapshot);
|
|
51
|
+
return sandbox.id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns Temporal activity functions matching {@link SandboxOps}.
|
|
56
|
+
* Spread these into your worker's activity map.
|
|
57
|
+
*/
|
|
58
|
+
createActivities(): SandboxOps<TOptions> {
|
|
59
|
+
return {
|
|
60
|
+
createSandbox: async (
|
|
61
|
+
options?: TOptions
|
|
62
|
+
): Promise<{
|
|
63
|
+
sandboxId: string;
|
|
64
|
+
stateUpdate?: Record<string, unknown>;
|
|
65
|
+
}> => {
|
|
66
|
+
return this.create(options);
|
|
67
|
+
},
|
|
68
|
+
destroySandbox: async (sandboxId: string): Promise<void> => {
|
|
69
|
+
await this.destroy(sandboxId);
|
|
70
|
+
},
|
|
71
|
+
snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
|
|
72
|
+
return this.snapshot(sandboxId);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from "vitest";
|
|
2
|
+
import { SandboxManager } from "./manager";
|
|
3
|
+
import { InMemorySandboxProvider } from "../../adapters/sandbox/inmemory/index";
|
|
4
|
+
import { SandboxNotFoundError } from "./types";
|
|
5
|
+
|
|
6
|
+
describe("SandboxManager", () => {
|
|
7
|
+
let manager: SandboxManager;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
manager = new SandboxManager(new InMemorySandboxProvider());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("creates a sandbox and returns an id", async () => {
|
|
14
|
+
const { sandboxId } = await manager.create();
|
|
15
|
+
expect(sandboxId).toBeTruthy();
|
|
16
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
17
|
+
expect(sandbox.id).toBe(sandboxId);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("creates a sandbox with a custom id", async () => {
|
|
21
|
+
const { sandboxId } = await manager.create({ id: "my-sandbox" });
|
|
22
|
+
expect(sandboxId).toBe("my-sandbox");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("gets an existing sandbox", async () => {
|
|
26
|
+
const { sandboxId } = await manager.create();
|
|
27
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
28
|
+
expect(sandbox.id).toBe(sandboxId);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("throws SandboxNotFoundError for unknown id", async () => {
|
|
32
|
+
await expect(manager.getSandbox("nonexistent")).rejects.toThrow(
|
|
33
|
+
SandboxNotFoundError,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("destroys a sandbox", async () => {
|
|
38
|
+
const { sandboxId } = await manager.create();
|
|
39
|
+
await manager.getSandbox(sandboxId);
|
|
40
|
+
await manager.destroy(sandboxId);
|
|
41
|
+
await expect(manager.getSandbox(sandboxId)).rejects.toThrow(SandboxNotFoundError);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("destroy is idempotent for unknown ids", async () => {
|
|
45
|
+
await expect(manager.destroy("nonexistent")).resolves.not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("snapshots and restores a sandbox", async () => {
|
|
49
|
+
const { sandboxId } = await manager.create({
|
|
50
|
+
initialFiles: { "/data.txt": "hello" },
|
|
51
|
+
});
|
|
52
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
53
|
+
await sandbox.fs.writeFile("/extra.txt", "world");
|
|
54
|
+
|
|
55
|
+
const snapshot = await manager.snapshot(sandboxId);
|
|
56
|
+
expect(snapshot.sandboxId).toBe(sandboxId);
|
|
57
|
+
expect(snapshot.providerId).toBe("inmemory");
|
|
58
|
+
|
|
59
|
+
await manager.destroy(sandboxId);
|
|
60
|
+
await expect(manager.getSandbox(sandboxId)).rejects.toThrow(SandboxNotFoundError);
|
|
61
|
+
|
|
62
|
+
const restoredId = await manager.restore(snapshot);
|
|
63
|
+
expect(restoredId).toBe(sandboxId);
|
|
64
|
+
const restored = await manager.getSandbox(restoredId);
|
|
65
|
+
const content = await restored.fs.readFile("/data.txt");
|
|
66
|
+
expect(content).toBe("hello");
|
|
67
|
+
const extra = await restored.fs.readFile("/extra.txt");
|
|
68
|
+
expect(extra).toBe("world");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("createActivities returns SandboxOps-shaped object", async () => {
|
|
72
|
+
const activities = manager.createActivities();
|
|
73
|
+
expect(activities.createSandbox).toBeTypeOf("function");
|
|
74
|
+
expect(activities.destroySandbox).toBeTypeOf("function");
|
|
75
|
+
expect(activities.snapshotSandbox).toBeTypeOf("function");
|
|
76
|
+
|
|
77
|
+
const { sandboxId } = await activities.createSandbox();
|
|
78
|
+
await expect(manager.getSandbox(sandboxId)).resolves.toBeTruthy();
|
|
79
|
+
|
|
80
|
+
await activities.destroySandbox(sandboxId);
|
|
81
|
+
await expect(manager.getSandbox(sandboxId)).rejects.toThrow(
|
|
82
|
+
SandboxNotFoundError,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("InMemorySandboxProvider", () => {
|
|
88
|
+
let manager: SandboxManager;
|
|
89
|
+
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
manager = new SandboxManager(new InMemorySandboxProvider());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("creates sandbox with initial files", async () => {
|
|
95
|
+
const { sandboxId } = await manager.create({
|
|
96
|
+
initialFiles: {
|
|
97
|
+
"/src/index.ts": 'console.log("hello");',
|
|
98
|
+
"/README.md": "# Hello",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
102
|
+
const content = await sandbox.fs.readFile("/src/index.ts");
|
|
103
|
+
expect(content).toBe('console.log("hello");');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("supports filesystem operations", async () => {
|
|
107
|
+
const { sandboxId } = await manager.create();
|
|
108
|
+
const { fs } = await manager.getSandbox(sandboxId);
|
|
109
|
+
|
|
110
|
+
await fs.writeFile("/test.txt", "hello");
|
|
111
|
+
expect(await fs.exists("/test.txt")).toBe(true);
|
|
112
|
+
expect(await fs.readFile("/test.txt")).toBe("hello");
|
|
113
|
+
|
|
114
|
+
await fs.appendFile("/test.txt", " world");
|
|
115
|
+
expect(await fs.readFile("/test.txt")).toBe("hello world");
|
|
116
|
+
|
|
117
|
+
await fs.mkdir("/mydir", { recursive: true });
|
|
118
|
+
expect(await fs.exists("/mydir")).toBe(true);
|
|
119
|
+
|
|
120
|
+
const stat = await fs.stat("/test.txt");
|
|
121
|
+
expect(stat.isFile).toBe(true);
|
|
122
|
+
expect(stat.isDirectory).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("supports shell execution", async () => {
|
|
126
|
+
const { sandboxId } = await manager.create({
|
|
127
|
+
initialFiles: { "/data.txt": "hello world" },
|
|
128
|
+
});
|
|
129
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
130
|
+
|
|
131
|
+
const result = await sandbox.exec("cat /data.txt");
|
|
132
|
+
expect(result.exitCode).toBe(0);
|
|
133
|
+
expect(result.stdout).toBe("hello world");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("reports correct capabilities", async () => {
|
|
137
|
+
const { sandboxId } = await manager.create();
|
|
138
|
+
const sandbox = await manager.getSandbox(sandboxId);
|
|
139
|
+
expect(sandbox.capabilities).toEqual({
|
|
140
|
+
filesystem: true,
|
|
141
|
+
execution: true,
|
|
142
|
+
persistence: true,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("readdirWithFileTypes works", async () => {
|
|
147
|
+
const { sandboxId } = await manager.create({
|
|
148
|
+
initialFiles: {
|
|
149
|
+
"/dir/a.txt": "a",
|
|
150
|
+
"/dir/b.txt": "b",
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const { fs } = await manager.getSandbox(sandboxId);
|
|
154
|
+
const entries = await fs.readdirWithFileTypes("/dir");
|
|
155
|
+
expect(entries.length).toBe(2);
|
|
156
|
+
expect(entries.every((e) => e.isFile)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|