zeitlich 0.2.30 → 0.2.32
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/dist/{activities-BeveyY9b.d.cts → activities-DA-bQM12.d.cts} +2 -2
- package/dist/{activities-NT3rcw66.d.ts → activities-FIXVz7DT.d.ts} +2 -2
- package/dist/adapters/thread/anthropic/index.cjs +4 -48
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +6 -6
- package/dist/adapters/thread/anthropic/index.d.ts +6 -6
- package/dist/adapters/thread/anthropic/index.js +4 -48
- 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 +8 -48
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +6 -6
- package/dist/adapters/thread/google-genai/index.d.ts +6 -6
- package/dist/adapters/thread/google-genai/index.js +8 -48
- 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 +5 -1
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +6 -5
- package/dist/adapters/thread/langchain/index.d.ts +6 -5
- package/dist/adapters/thread/langchain/index.js +5 -1
- 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 +34 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +34 -7
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BgswT47M.d.ts → proxy-Br4unLTC.d.ts} +1 -1
- package/dist/{proxy-OJihshQF.d.cts → proxy-CTCYWjkr.d.cts} +1 -1
- package/dist/{thread-manager-lfN0V-gH.d.cts → thread-manager-CUubPYPH.d.cts} +1 -1
- package/dist/{thread-manager-DH0zv05W.d.cts → thread-manager-Cv_BR28i.d.cts} +1 -1
- package/dist/{thread-manager-iUplxEZt.d.ts → thread-manager-DKWxHUzD.d.ts} +1 -1
- package/dist/{thread-manager-BS477gj8.d.ts → thread-manager-YJLoc1vH.d.ts} +1 -1
- package/dist/{types-DVdT5ybA.d.cts → types-Bpq5fDI5.d.cts} +13 -5
- package/dist/{types-CCIc7Eam.d.ts → types-BxiT8w9d.d.ts} +1 -1
- package/dist/{types-D90Q5aOh.d.ts → types-CheCTLeV.d.ts} +13 -5
- package/dist/{types-DgIVPOa1.d.cts → types-NJDyMyUx.d.cts} +1 -1
- package/dist/{workflow-Cj4DxGdM.d.cts → workflow-BWKQcz9d.d.cts} +1 -1
- package/dist/{workflow-CzrBdCcJ.d.ts → workflow-D8wK7TJY.d.ts} +1 -1
- package/dist/workflow.cjs +17 -2
- 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 +17 -2
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/thread/anthropic/activities.ts +10 -0
- package/src/adapters/thread/anthropic/model-invoker.ts +2 -5
- package/src/adapters/thread/google-genai/activities.ts +14 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +2 -5
- package/src/adapters/thread/langchain/activities.ts +11 -0
- package/src/adapters/thread/langchain/model-invoker.ts +2 -3
- package/src/lib/.env +1 -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 +19 -1
- package/src/lib/session/types.ts +7 -0
- package/src/lib/thread/proxy.ts +1 -0
- package/src/lib/types.ts +3 -0
- package/src/lib/virtual-fs/filesystem.ts +15 -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 +3 -2
- package/src/lib/virtual-fs/virtual-fs.test.ts +64 -0
- package/src/lib/virtual-fs/with-virtual-fs.ts +4 -3
- package/src/tools/bash/.env +1 -0
package/package.json
CHANGED
|
@@ -164,6 +164,16 @@ export function createAnthropicAdapter(
|
|
|
164
164
|
await thread.appendToolResult(id, toolCallId, toolName, content);
|
|
165
165
|
},
|
|
166
166
|
|
|
167
|
+
async appendAgentMessage(
|
|
168
|
+
threadId: string,
|
|
169
|
+
id: string,
|
|
170
|
+
message: Anthropic.Messages.Message,
|
|
171
|
+
threadKey?: string,
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
const thread = createAnthropicThreadManager({ redis, threadId, key: threadKey });
|
|
174
|
+
await thread.appendAssistantMessage(id, message.content);
|
|
175
|
+
},
|
|
176
|
+
|
|
167
177
|
async forkThread(
|
|
168
178
|
sourceThreadId: string,
|
|
169
179
|
targetThreadId: string,
|
|
@@ -3,7 +3,6 @@ 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 { v4 as uuidv4 } from "uuid";
|
|
7
6
|
|
|
8
7
|
export interface AnthropicModelInvokerConfig {
|
|
9
8
|
redis: Redis;
|
|
@@ -29,8 +28,8 @@ function toAnthropicTools(
|
|
|
29
28
|
* `ModelInvoker<Anthropic.Messages.Message>` contract.
|
|
30
29
|
*
|
|
31
30
|
* Loads the conversation thread from Redis, invokes the Claude model via
|
|
32
|
-
* `client.messages.create`,
|
|
33
|
-
*
|
|
31
|
+
* `client.messages.create`, and returns a normalised AgentResponse.
|
|
32
|
+
* The caller is responsible for appending the response to the thread.
|
|
34
33
|
*
|
|
35
34
|
* @example
|
|
36
35
|
* ```typescript
|
|
@@ -74,8 +73,6 @@ export function createAnthropicModelInvoker({
|
|
|
74
73
|
...(tools ? { tools } : {}),
|
|
75
74
|
});
|
|
76
75
|
|
|
77
|
-
await thread.appendAssistantMessage(uuidv4(), response.content);
|
|
78
|
-
|
|
79
76
|
const toolCalls = response.content.filter(
|
|
80
77
|
(block): block is Anthropic.Messages.ToolUseBlock =>
|
|
81
78
|
block.type === "tool_use",
|
|
@@ -194,6 +194,20 @@ export function createGoogleGenAIAdapter(
|
|
|
194
194
|
);
|
|
195
195
|
},
|
|
196
196
|
|
|
197
|
+
async appendAgentMessage(
|
|
198
|
+
threadId: string,
|
|
199
|
+
id: string,
|
|
200
|
+
message: Content,
|
|
201
|
+
threadKey?: string,
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const thread = createGoogleGenAIThreadManager({
|
|
204
|
+
redis,
|
|
205
|
+
threadId,
|
|
206
|
+
key: threadKey,
|
|
207
|
+
});
|
|
208
|
+
await thread.appendModelContent(id, message.parts ?? []);
|
|
209
|
+
},
|
|
210
|
+
|
|
197
211
|
async forkThread(
|
|
198
212
|
sourceThreadId: string,
|
|
199
213
|
targetThreadId: string,
|
|
@@ -3,7 +3,6 @@ import type { GoogleGenAI, Content, FunctionDeclaration } 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 { v4 as uuidv4 } from "uuid";
|
|
7
6
|
|
|
8
7
|
export interface GoogleGenAIModelInvokerConfig {
|
|
9
8
|
redis: Redis;
|
|
@@ -27,8 +26,8 @@ function toFunctionDeclarations(
|
|
|
27
26
|
* `ModelInvoker<Content>` contract.
|
|
28
27
|
*
|
|
29
28
|
* Loads the conversation thread from Redis, invokes the Gemini model via
|
|
30
|
-
* `client.models.generateContent`,
|
|
31
|
-
*
|
|
29
|
+
* `client.models.generateContent`, and returns a normalised AgentResponse.
|
|
30
|
+
* The caller is responsible for appending the response to the thread.
|
|
32
31
|
*
|
|
33
32
|
* @example
|
|
34
33
|
* ```typescript
|
|
@@ -77,8 +76,6 @@ export function createGoogleGenAIModelInvoker({
|
|
|
77
76
|
const responseParts = response.candidates?.[0]?.content?.parts ?? [];
|
|
78
77
|
const modelContent: Content = { role: "model", parts: responseParts };
|
|
79
78
|
|
|
80
|
-
await thread.appendModelContent(uuidv4(), responseParts);
|
|
81
|
-
|
|
82
79
|
const functionCalls = response.functionCalls ?? [];
|
|
83
80
|
|
|
84
81
|
return {
|
|
@@ -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,
|
|
@@ -17,7 +17,8 @@ export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> =
|
|
|
17
17
|
* `ModelInvoker<StoredMessage>` contract.
|
|
18
18
|
*
|
|
19
19
|
* Loads the conversation thread from Redis, invokes a LangChain chat model,
|
|
20
|
-
*
|
|
20
|
+
* and returns a normalised AgentResponse.
|
|
21
|
+
* The caller is responsible for appending the response to the thread.
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
24
|
* ```typescript
|
|
@@ -54,8 +55,6 @@ export function createLangChainModelInvoker<TModel extends BaseChatModel<any> =
|
|
|
54
55
|
},
|
|
55
56
|
);
|
|
56
57
|
|
|
57
|
-
await thread.append([response.toDict()]);
|
|
58
|
-
|
|
59
58
|
const toolCalls = response.tool_calls ?? [];
|
|
60
59
|
|
|
61
60
|
return {
|
package/src/lib/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9
|
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
|
|
|
@@ -270,8 +271,23 @@ export async function createSession<
|
|
|
270
271
|
});
|
|
271
272
|
}
|
|
272
273
|
const result = await virtualFsOps.resolveFileTree(virtualFsConfig.ctx);
|
|
274
|
+
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
275
|
+
const fileTree = skillFiles
|
|
276
|
+
? [
|
|
277
|
+
...result.fileTree,
|
|
278
|
+
...Object.entries(skillFiles).map(([path, content]) => ({
|
|
279
|
+
id: `skill:${path}`,
|
|
280
|
+
path,
|
|
281
|
+
size: content.length,
|
|
282
|
+
mtime: new Date().toISOString(),
|
|
283
|
+
metadata: {},
|
|
284
|
+
})),
|
|
285
|
+
]
|
|
286
|
+
: result.fileTree;
|
|
273
287
|
stateManager.mergeUpdate({
|
|
274
|
-
fileTree
|
|
288
|
+
fileTree,
|
|
289
|
+
virtualFsCtx: virtualFsConfig.ctx,
|
|
290
|
+
...(skillFiles && { inlineFiles: skillFiles }),
|
|
275
291
|
} as Partial<AgentState<TState>>);
|
|
276
292
|
}
|
|
277
293
|
|
|
@@ -341,6 +357,8 @@ export async function createSession<
|
|
|
341
357
|
metadata,
|
|
342
358
|
});
|
|
343
359
|
|
|
360
|
+
await appendAgentMessage(threadId, uuid4(), message, threadKey);
|
|
361
|
+
|
|
344
362
|
if (usage) {
|
|
345
363
|
stateManager.updateUsage(usage);
|
|
346
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,
|
package/src/lib/thread/proxy.ts
CHANGED
|
@@ -51,6 +51,7 @@ export function createThreadOpsProxy(
|
|
|
51
51
|
initializeThread: acts[p("initializeThread")],
|
|
52
52
|
appendHumanMessage: acts[p("appendHumanMessage")],
|
|
53
53
|
appendToolResult: acts[p("appendToolResult")],
|
|
54
|
+
appendAgentMessage: acts[p("appendAgentMessage")],
|
|
54
55
|
appendSystemMessage: acts[p("appendSystemMessage")],
|
|
55
56
|
forkThread: acts[p("forkThread")],
|
|
56
57
|
} as ActivityInterfaceFor<ThreadOps>;
|
package/src/lib/types.ts
CHANGED
|
@@ -25,6 +25,9 @@ export interface BaseAgentState {
|
|
|
25
25
|
turns: number;
|
|
26
26
|
tasks: Map<string, WorkflowTask>;
|
|
27
27
|
fileTree: FileEntry[];
|
|
28
|
+
/** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
|
|
29
|
+
inlineFiles?: Record<string, string>;
|
|
30
|
+
virtualFsCtx?: unknown;
|
|
28
31
|
systemPrompt?: string;
|
|
29
32
|
totalInputTokens: number;
|
|
30
33
|
totalOutputTokens: number;
|
|
@@ -60,17 +60,28 @@ export class VirtualFileSystem<
|
|
|
60
60
|
private directories: Set<string>;
|
|
61
61
|
private mutations: TreeMutation<TMeta>[] = [];
|
|
62
62
|
|
|
63
|
+
private inlineFiles: Map<string, string>;
|
|
64
|
+
|
|
63
65
|
constructor(
|
|
64
66
|
tree: FileEntry<TMeta>[],
|
|
65
67
|
private resolver: FileResolver<TCtx, TMeta>,
|
|
66
68
|
private ctx: TCtx,
|
|
67
69
|
workspaceBase = "/",
|
|
70
|
+
inlineFiles?: Record<string, string>,
|
|
68
71
|
) {
|
|
69
72
|
this.workspaceBase = normalisePath(workspaceBase);
|
|
70
73
|
this.entries = new Map(
|
|
71
74
|
tree.map((e) => [normalisePath(e.path, this.workspaceBase), e])
|
|
72
75
|
);
|
|
73
76
|
this.directories = inferDirectories(tree, this.workspaceBase);
|
|
77
|
+
this.inlineFiles = new Map(
|
|
78
|
+
inlineFiles
|
|
79
|
+
? Object.entries(inlineFiles).map(([p, c]) => [
|
|
80
|
+
normalisePath(p, this.workspaceBase),
|
|
81
|
+
c,
|
|
82
|
+
])
|
|
83
|
+
: []
|
|
84
|
+
);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
/** Return all mutations accumulated during this invocation. */
|
|
@@ -89,6 +100,8 @@ export class VirtualFileSystem<
|
|
|
89
100
|
|
|
90
101
|
async readFile(path: string): Promise<string> {
|
|
91
102
|
const norm = normalisePath(path, this.workspaceBase);
|
|
103
|
+
const inline = this.inlineFiles.get(norm);
|
|
104
|
+
if (inline !== undefined) return inline;
|
|
92
105
|
const entry = this.entries.get(norm);
|
|
93
106
|
if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
|
|
94
107
|
return this.resolver.readFile(entry.id, this.ctx, entry.metadata);
|
|
@@ -96,6 +109,8 @@ export class VirtualFileSystem<
|
|
|
96
109
|
|
|
97
110
|
async readFileBuffer(path: string): Promise<Uint8Array> {
|
|
98
111
|
const norm = normalisePath(path, this.workspaceBase);
|
|
112
|
+
const inline = this.inlineFiles.get(norm);
|
|
113
|
+
if (inline !== undefined) return new TextEncoder().encode(inline);
|
|
99
114
|
const entry = this.entries.get(norm);
|
|
100
115
|
if (!entry) throw new Error(`ENOENT: no such file: ${path}`);
|
|
101
116
|
return this.resolver.readFileBuffer(entry.id, this.ctx, entry.metadata);
|
|
@@ -30,7 +30,7 @@ export function createVirtualFsActivities<
|
|
|
30
30
|
TMeta = FileEntryMetadata,
|
|
31
31
|
>(
|
|
32
32
|
resolver: FileResolver<TCtx, TMeta>,
|
|
33
|
-
scope: S
|
|
33
|
+
scope: S
|
|
34
34
|
): PrefixedVirtualFsOps<S, TCtx, TMeta> {
|
|
35
35
|
const ops: VirtualFsOps<TCtx, TMeta> = {
|
|
36
36
|
resolveFileTree: async (ctx: TCtx) => {
|
|
@@ -39,10 +39,10 @@ export function createVirtualFsActivities<
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const prefix = scope
|
|
42
|
+
const prefix = `virtualFs${scope.charAt(0).toUpperCase()}${scope.slice(1)}`;
|
|
43
43
|
const cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
|
|
44
44
|
|
|
45
45
|
return Object.fromEntries(
|
|
46
|
-
Object.entries(ops).map(([k, v]) => [`${prefix}${cap(k)}`, v])
|
|
46
|
+
Object.entries(ops).map(([k, v]) => [`${prefix}${cap(k)}`, v])
|
|
47
47
|
) as PrefixedVirtualFsOps<S, TCtx, TMeta>;
|
|
48
48
|
}
|
|
@@ -18,7 +18,7 @@ import type { VirtualFsOps } from "./types";
|
|
|
18
18
|
|
|
19
19
|
export function proxyVirtualFsOps<TCtx = unknown>(
|
|
20
20
|
scope?: string,
|
|
21
|
-
options?: Parameters<typeof proxyActivities>[0]
|
|
21
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
22
22
|
): VirtualFsOps<TCtx> {
|
|
23
23
|
const resolvedScope = scope ?? workflowInfo().workflowType;
|
|
24
24
|
|
|
@@ -32,10 +32,10 @@ export function proxyVirtualFsOps<TCtx = unknown>(
|
|
|
32
32
|
maximumInterval: "30s",
|
|
33
33
|
backoffCoefficient: 2,
|
|
34
34
|
},
|
|
35
|
-
}
|
|
35
|
+
}
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
const prefix = resolvedScope
|
|
38
|
+
const prefix = `virtualFs${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
|
|
39
39
|
const p = (key: string): string =>
|
|
40
40
|
`${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
41
41
|
|
|
@@ -118,8 +118,9 @@ export type PrefixedVirtualFsOps<
|
|
|
118
118
|
*/
|
|
119
119
|
export interface VirtualFsState<TCtx = unknown, TMeta = FileEntryMetadata> {
|
|
120
120
|
fileTree: FileEntry<TMeta>[];
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
virtualFsCtx: TCtx;
|
|
122
|
+
/** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
|
|
123
|
+
inlineFiles?: Record<string, string>;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// ============================================================================
|
|
@@ -325,6 +325,70 @@ describe("VirtualFileSystem", () => {
|
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
327
|
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// VirtualFileSystem — inlineFiles
|
|
330
|
+
// ============================================================================
|
|
331
|
+
|
|
332
|
+
describe("VirtualFileSystem — inlineFiles", () => {
|
|
333
|
+
const skillEntry: FileEntry = {
|
|
334
|
+
id: "skill:code-review:checklist.md",
|
|
335
|
+
path: "/skills/code-review/checklist.md",
|
|
336
|
+
size: 18,
|
|
337
|
+
mtime: "2025-01-01T00:00:00.000Z",
|
|
338
|
+
metadata: {},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const inlineFiles: Record<string, string> = {
|
|
342
|
+
"/skills/code-review/checklist.md": "# Review checklist",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
function createFsWithInline() {
|
|
346
|
+
const { resolver } = createMockResolver();
|
|
347
|
+
return new VirtualFileSystem(
|
|
348
|
+
[...sampleTree, skillEntry],
|
|
349
|
+
resolver,
|
|
350
|
+
ctx,
|
|
351
|
+
"/",
|
|
352
|
+
inlineFiles,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
it("readFile returns inline content instead of hitting the resolver", async () => {
|
|
357
|
+
const fs = createFsWithInline();
|
|
358
|
+
const content = await fs.readFile("/skills/code-review/checklist.md");
|
|
359
|
+
expect(content).toBe("# Review checklist");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("readFileBuffer returns inline content as Uint8Array", async () => {
|
|
363
|
+
const fs = createFsWithInline();
|
|
364
|
+
const buf = await fs.readFileBuffer("/skills/code-review/checklist.md");
|
|
365
|
+
expect(new TextDecoder().decode(buf)).toBe("# Review checklist");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("inline files are visible in readdir", async () => {
|
|
369
|
+
const fs = createFsWithInline();
|
|
370
|
+
const names = await fs.readdir("/skills/code-review");
|
|
371
|
+
expect(names).toContain("checklist.md");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("inline file directories are inferred", async () => {
|
|
375
|
+
const fs = createFsWithInline();
|
|
376
|
+
expect(await fs.exists("/skills")).toBe(true);
|
|
377
|
+
expect(await fs.exists("/skills/code-review")).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("readFile still falls back to resolver for non-inline files", async () => {
|
|
381
|
+
const fs = createFsWithInline();
|
|
382
|
+
const content = await fs.readFile("/src/index.ts");
|
|
383
|
+
expect(content).toBe('console.log("hello");');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("readFile throws ENOENT for paths not in tree or inline", async () => {
|
|
387
|
+
const fs = createFsWithInline();
|
|
388
|
+
await expect(fs.readFile("/nope.txt")).rejects.toThrow("ENOENT");
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
328
392
|
// ============================================================================
|
|
329
393
|
// createVirtualFsActivities
|
|
330
394
|
// ============================================================================
|
|
@@ -66,7 +66,7 @@ export function withVirtualFs<
|
|
|
66
66
|
const state =
|
|
67
67
|
await queryParentWorkflowState<VirtualFsState<TCtx, TMeta>>(client);
|
|
68
68
|
|
|
69
|
-
const { fileTree,
|
|
69
|
+
const { fileTree, virtualFsCtx, inlineFiles } = state;
|
|
70
70
|
if (!fileTree) {
|
|
71
71
|
return {
|
|
72
72
|
toolResponse: `Error: No fileTree in agent state. The ${context.toolName} tool requires a virtual filesystem.`,
|
|
@@ -77,8 +77,9 @@ export function withVirtualFs<
|
|
|
77
77
|
const virtualFs = new VirtualFileSystem(
|
|
78
78
|
fileTree,
|
|
79
79
|
resolver,
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
virtualFsCtx,
|
|
81
|
+
"/",
|
|
82
|
+
inlineFiles
|
|
82
83
|
);
|
|
83
84
|
const response = await handler(args, { ...context, virtualFs });
|
|
84
85
|
const mutations = virtualFs.getMutations();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9
|