zeitlich 0.1.0
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/LICENSE +21 -0
- package/README.md +494 -0
- package/dist/index.d.mts +152 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +1623 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1582 -0
- package/dist/index.mjs.map +1 -0
- package/dist/workflow-DeVGEXSc.d.mts +1201 -0
- package/dist/workflow-DeVGEXSc.d.ts +1201 -0
- package/dist/workflow.d.mts +4 -0
- package/dist/workflow.d.ts +4 -0
- package/dist/workflow.js +762 -0
- package/dist/workflow.js.map +1 -0
- package/dist/workflow.mjs +734 -0
- package/dist/workflow.mjs.map +1 -0
- package/package.json +92 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1623 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var workflow = require('@temporalio/workflow');
|
|
4
|
+
var z2 = require('zod');
|
|
5
|
+
var minimatch = require('minimatch');
|
|
6
|
+
var plugin = require('@temporalio/plugin');
|
|
7
|
+
var messages = require('@langchain/core/messages');
|
|
8
|
+
var crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var z2__default = /*#__PURE__*/_interopDefault(z2);
|
|
13
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
14
|
+
|
|
15
|
+
// src/lib/session.ts
|
|
16
|
+
var TASK_TOOL = "Task";
|
|
17
|
+
function buildTaskDescription(subagents) {
|
|
18
|
+
const subagentList = subagents.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
19
|
+
return `Launch a new agent to handle complex, multi-step tasks autonomously.
|
|
20
|
+
|
|
21
|
+
The ${TASK_TOOL} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
|
|
22
|
+
|
|
23
|
+
Available agent types:
|
|
24
|
+
|
|
25
|
+
${subagentList}
|
|
26
|
+
|
|
27
|
+
When using the ${TASK_TOOL} tool, you must specify a subagent parameter to select which agent type to use.
|
|
28
|
+
|
|
29
|
+
Usage notes:
|
|
30
|
+
|
|
31
|
+
- Always include a short description (3-5 words) summarizing what the agent will do
|
|
32
|
+
- Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
33
|
+
- When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
|
|
34
|
+
- Each invocation starts fresh - provide a detailed task description with all necessary context.
|
|
35
|
+
- Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
|
|
36
|
+
- The agent's outputs should generally be trusted
|
|
37
|
+
- Clearly tell the agent what type of work you expect since it is not aware of the user's intent
|
|
38
|
+
- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
|
|
39
|
+
}
|
|
40
|
+
function createTaskTool(subagents) {
|
|
41
|
+
if (subagents.length === 0) {
|
|
42
|
+
throw new Error("createTaskTool requires at least one subagent");
|
|
43
|
+
}
|
|
44
|
+
const names = subagents.map((s) => s.name);
|
|
45
|
+
return {
|
|
46
|
+
name: TASK_TOOL,
|
|
47
|
+
description: buildTaskDescription(subagents),
|
|
48
|
+
schema: z2__default.default.object({
|
|
49
|
+
subagent: z2__default.default.enum(names).describe("The type of subagent to launch"),
|
|
50
|
+
description: z2__default.default.string().describe("A short (3-5 word) description of the task"),
|
|
51
|
+
prompt: z2__default.default.string().describe("The task for the agent to perform")
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function createTaskHandler(subagents) {
|
|
56
|
+
const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } = workflow.workflowInfo();
|
|
57
|
+
return async (args, _toolCallId) => {
|
|
58
|
+
const config = subagents.find((s) => s.name === args.subagent);
|
|
59
|
+
if (!config) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.name).join(", ")}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const childWorkflowId = `${parentWorkflowId}-${args.subagent}-${workflow.uuid4()}`;
|
|
65
|
+
const childResult = await workflow.executeChild(config.workflowType, {
|
|
66
|
+
workflowId: childWorkflowId,
|
|
67
|
+
args: [{ prompt: args.prompt }],
|
|
68
|
+
taskQueue: config.taskQueue ?? parentTaskQueue
|
|
69
|
+
});
|
|
70
|
+
const validated = config.resultSchema ? config.resultSchema.parse(childResult) : childResult;
|
|
71
|
+
const content = typeof validated === "string" ? validated : JSON.stringify(validated, null, 2);
|
|
72
|
+
return {
|
|
73
|
+
content,
|
|
74
|
+
result: {
|
|
75
|
+
result: validated,
|
|
76
|
+
childWorkflowId
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/lib/subagent-support.ts
|
|
83
|
+
function withSubagentSupport(userTools, config) {
|
|
84
|
+
if (config.subagents.length === 0) {
|
|
85
|
+
throw new Error("withSubagentSupport requires at least one subagent");
|
|
86
|
+
}
|
|
87
|
+
const taskTool = createTaskTool(config.subagents);
|
|
88
|
+
const taskHandler = createTaskHandler(config.subagents);
|
|
89
|
+
return {
|
|
90
|
+
tools: {
|
|
91
|
+
...userTools,
|
|
92
|
+
Task: taskTool
|
|
93
|
+
},
|
|
94
|
+
taskHandler
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function hasTaskTool(tools) {
|
|
98
|
+
return "Task" in tools;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/lib/session.ts
|
|
102
|
+
var createSession = async ({ threadId, agentName, maxTurns = 50, metadata = {} }, {
|
|
103
|
+
runAgent,
|
|
104
|
+
promptManager,
|
|
105
|
+
toolRouter,
|
|
106
|
+
toolRegistry,
|
|
107
|
+
hooks = {}
|
|
108
|
+
}) => {
|
|
109
|
+
const { initializeThread, appendHumanMessage, parseToolCalls } = workflow.proxyActivities({
|
|
110
|
+
startToCloseTimeout: "30m",
|
|
111
|
+
retry: {
|
|
112
|
+
maximumAttempts: 6,
|
|
113
|
+
initialInterval: "5s",
|
|
114
|
+
maximumInterval: "15m",
|
|
115
|
+
backoffCoefficient: 4
|
|
116
|
+
},
|
|
117
|
+
heartbeatTimeout: "5m"
|
|
118
|
+
});
|
|
119
|
+
const callSessionEnd = async (exitReason, turns) => {
|
|
120
|
+
if (hooks.onSessionEnd) {
|
|
121
|
+
await hooks.onSessionEnd({
|
|
122
|
+
threadId,
|
|
123
|
+
agentName,
|
|
124
|
+
exitReason,
|
|
125
|
+
turns,
|
|
126
|
+
metadata
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
runSession: async (prompt, stateManager) => {
|
|
132
|
+
if (hooks.onSessionStart) {
|
|
133
|
+
await hooks.onSessionStart({
|
|
134
|
+
threadId,
|
|
135
|
+
agentName,
|
|
136
|
+
metadata
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
await initializeThread(threadId);
|
|
140
|
+
await appendHumanMessage(
|
|
141
|
+
threadId,
|
|
142
|
+
await promptManager.buildContextMessage(prompt)
|
|
143
|
+
);
|
|
144
|
+
let exitReason = "completed";
|
|
145
|
+
try {
|
|
146
|
+
while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
|
|
147
|
+
stateManager.incrementTurns();
|
|
148
|
+
const currentTurn = stateManager.getTurns();
|
|
149
|
+
const { message, stopReason } = await runAgent(
|
|
150
|
+
{
|
|
151
|
+
threadId,
|
|
152
|
+
agentName,
|
|
153
|
+
metadata
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
systemPrompt: await promptManager.getSystemPrompt()
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
if (stopReason === "end_turn") {
|
|
160
|
+
stateManager.complete();
|
|
161
|
+
exitReason = "completed";
|
|
162
|
+
return message;
|
|
163
|
+
}
|
|
164
|
+
const rawToolCalls = await parseToolCalls(message);
|
|
165
|
+
const parsedToolCalls = rawToolCalls.map(
|
|
166
|
+
(tc) => toolRegistry.parseToolCall(tc)
|
|
167
|
+
);
|
|
168
|
+
await toolRouter.processToolCalls(parsedToolCalls, {
|
|
169
|
+
turn: currentTurn
|
|
170
|
+
});
|
|
171
|
+
if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
|
|
172
|
+
exitReason = "waiting_for_input";
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (stateManager.getTurns() >= maxTurns && stateManager.isRunning()) {
|
|
177
|
+
exitReason = "max_turns";
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
exitReason = "failed";
|
|
181
|
+
throw error;
|
|
182
|
+
} finally {
|
|
183
|
+
await callSessionEnd(exitReason, stateManager.getTurns());
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/lib/types.ts
|
|
191
|
+
function isTerminalStatus(status) {
|
|
192
|
+
return status === "COMPLETED" || status === "FAILED" || status === "CANCELLED";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/lib/state-manager.ts
|
|
196
|
+
function createAgentStateManager(config) {
|
|
197
|
+
let status = "RUNNING";
|
|
198
|
+
let version = 0;
|
|
199
|
+
let turns = 0;
|
|
200
|
+
const customState = { ...config?.initialState ?? {} };
|
|
201
|
+
function buildState() {
|
|
202
|
+
return {
|
|
203
|
+
status,
|
|
204
|
+
version,
|
|
205
|
+
turns,
|
|
206
|
+
...customState
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
getStatus() {
|
|
211
|
+
return status;
|
|
212
|
+
},
|
|
213
|
+
isRunning() {
|
|
214
|
+
return status === "RUNNING";
|
|
215
|
+
},
|
|
216
|
+
isTerminal() {
|
|
217
|
+
return isTerminalStatus(status);
|
|
218
|
+
},
|
|
219
|
+
getTurns() {
|
|
220
|
+
return turns;
|
|
221
|
+
},
|
|
222
|
+
getVersion() {
|
|
223
|
+
return version;
|
|
224
|
+
},
|
|
225
|
+
run() {
|
|
226
|
+
status = "RUNNING";
|
|
227
|
+
version++;
|
|
228
|
+
},
|
|
229
|
+
waitForInput() {
|
|
230
|
+
status = "WAITING_FOR_INPUT";
|
|
231
|
+
version++;
|
|
232
|
+
},
|
|
233
|
+
complete() {
|
|
234
|
+
status = "COMPLETED";
|
|
235
|
+
version++;
|
|
236
|
+
},
|
|
237
|
+
fail() {
|
|
238
|
+
status = "FAILED";
|
|
239
|
+
version++;
|
|
240
|
+
},
|
|
241
|
+
cancel() {
|
|
242
|
+
status = "CANCELLED";
|
|
243
|
+
version++;
|
|
244
|
+
},
|
|
245
|
+
incrementVersion() {
|
|
246
|
+
version++;
|
|
247
|
+
},
|
|
248
|
+
incrementTurns() {
|
|
249
|
+
turns++;
|
|
250
|
+
},
|
|
251
|
+
get(key) {
|
|
252
|
+
return customState[key];
|
|
253
|
+
},
|
|
254
|
+
set(key, value) {
|
|
255
|
+
customState[key] = value;
|
|
256
|
+
version++;
|
|
257
|
+
},
|
|
258
|
+
getCurrentState() {
|
|
259
|
+
return buildState();
|
|
260
|
+
},
|
|
261
|
+
shouldReturnFromWait(lastKnownVersion) {
|
|
262
|
+
return version > lastKnownVersion || isTerminalStatus(status);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
var AGENT_HANDLER_NAMES = {
|
|
267
|
+
getAgentState: "getAgentState",
|
|
268
|
+
waitForStateChange: "waitForStateChange",
|
|
269
|
+
addMessage: "addMessage"
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/lib/prompt-manager.ts
|
|
273
|
+
function createPromptManager(config) {
|
|
274
|
+
const { baseSystemPrompt, instructionsPrompt, buildContextMessage } = config;
|
|
275
|
+
async function resolvePrompt(prompt) {
|
|
276
|
+
if (typeof prompt === "function") {
|
|
277
|
+
return prompt();
|
|
278
|
+
}
|
|
279
|
+
return prompt;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
async getSystemPrompt() {
|
|
283
|
+
const base = await resolvePrompt(baseSystemPrompt);
|
|
284
|
+
const instructions = await resolvePrompt(instructionsPrompt);
|
|
285
|
+
return [base, instructions].join("\n");
|
|
286
|
+
},
|
|
287
|
+
async buildContextMessage(context) {
|
|
288
|
+
return buildContextMessage(context);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/lib/tool-registry.ts
|
|
294
|
+
function createToolRegistry(tools) {
|
|
295
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const [_key, tool] of Object.entries(tools)) {
|
|
297
|
+
toolMap.set(tool.name, tool);
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
parseToolCall(toolCall) {
|
|
301
|
+
const tool = toolMap.get(toolCall.name);
|
|
302
|
+
if (!tool) {
|
|
303
|
+
throw new Error(`Tool ${toolCall.name} not found`);
|
|
304
|
+
}
|
|
305
|
+
const parsedArgs = tool.schema.parse(toolCall.args);
|
|
306
|
+
return {
|
|
307
|
+
id: toolCall.id ?? "",
|
|
308
|
+
name: toolCall.name,
|
|
309
|
+
args: parsedArgs
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
getToolList() {
|
|
313
|
+
return Object.values(tools);
|
|
314
|
+
},
|
|
315
|
+
getTool(name) {
|
|
316
|
+
return tools[name];
|
|
317
|
+
},
|
|
318
|
+
hasTool(name) {
|
|
319
|
+
return toolMap.has(name);
|
|
320
|
+
},
|
|
321
|
+
getToolNames() {
|
|
322
|
+
return Array.from(toolMap.keys());
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/lib/tool-router.ts
|
|
328
|
+
function createToolRouter(options, handlers) {
|
|
329
|
+
const { parallel = true, threadId, appendToolResult, hooks } = options;
|
|
330
|
+
async function processToolCall(toolCall, turn) {
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
let effectiveArgs = toolCall.args;
|
|
333
|
+
if (hooks?.onPreToolUse) {
|
|
334
|
+
const preResult = await hooks.onPreToolUse({
|
|
335
|
+
toolCall,
|
|
336
|
+
threadId,
|
|
337
|
+
turn
|
|
338
|
+
});
|
|
339
|
+
if (preResult?.skip) {
|
|
340
|
+
await appendToolResult({
|
|
341
|
+
threadId,
|
|
342
|
+
toolCallId: toolCall.id,
|
|
343
|
+
content: JSON.stringify({
|
|
344
|
+
skipped: true,
|
|
345
|
+
reason: "Skipped by PreToolUse hook"
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
if (preResult?.modifiedArgs !== void 0) {
|
|
351
|
+
effectiveArgs = preResult.modifiedArgs;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const handler = handlers[toolCall.name];
|
|
355
|
+
let result;
|
|
356
|
+
let content;
|
|
357
|
+
try {
|
|
358
|
+
if (handler) {
|
|
359
|
+
const response = await handler(
|
|
360
|
+
effectiveArgs,
|
|
361
|
+
toolCall.id
|
|
362
|
+
);
|
|
363
|
+
result = response.result;
|
|
364
|
+
content = response.content;
|
|
365
|
+
} else {
|
|
366
|
+
result = { error: `Unknown tool: ${toolCall.name}` };
|
|
367
|
+
content = JSON.stringify(result, null, 2);
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (hooks?.onPostToolUseFailure) {
|
|
371
|
+
const failureResult = await hooks.onPostToolUseFailure({
|
|
372
|
+
toolCall,
|
|
373
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
374
|
+
threadId,
|
|
375
|
+
turn
|
|
376
|
+
});
|
|
377
|
+
if (failureResult?.fallbackContent !== void 0) {
|
|
378
|
+
content = failureResult.fallbackContent;
|
|
379
|
+
result = { error: String(error), recovered: true };
|
|
380
|
+
} else if (failureResult?.suppress) {
|
|
381
|
+
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
382
|
+
result = { error: String(error), suppressed: true };
|
|
383
|
+
} else {
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await appendToolResult({ threadId, toolCallId: toolCall.id, content });
|
|
391
|
+
const toolResult = {
|
|
392
|
+
toolCallId: toolCall.id,
|
|
393
|
+
name: toolCall.name,
|
|
394
|
+
result
|
|
395
|
+
};
|
|
396
|
+
if (hooks?.onPostToolUse) {
|
|
397
|
+
const durationMs = Date.now() - startTime;
|
|
398
|
+
await hooks.onPostToolUse({
|
|
399
|
+
toolCall,
|
|
400
|
+
result: toolResult,
|
|
401
|
+
threadId,
|
|
402
|
+
turn,
|
|
403
|
+
durationMs
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return toolResult;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
async processToolCalls(toolCalls, context) {
|
|
410
|
+
if (toolCalls.length === 0) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
const turn = context?.turn ?? 0;
|
|
414
|
+
if (parallel) {
|
|
415
|
+
const results2 = await Promise.all(
|
|
416
|
+
toolCalls.map((tc) => processToolCall(tc, turn))
|
|
417
|
+
);
|
|
418
|
+
return results2.filter(
|
|
419
|
+
(r) => r !== null
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
const results = [];
|
|
423
|
+
for (const toolCall of toolCalls) {
|
|
424
|
+
const result = await processToolCall(toolCall, turn);
|
|
425
|
+
if (result !== null) {
|
|
426
|
+
results.push(result);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return results;
|
|
430
|
+
},
|
|
431
|
+
async processToolCallsByName(toolCalls, toolName, handler) {
|
|
432
|
+
const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
|
|
433
|
+
if (matchingCalls.length === 0) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
const processOne = async (toolCall) => {
|
|
437
|
+
const response = await handler(
|
|
438
|
+
toolCall.args,
|
|
439
|
+
toolCall.id
|
|
440
|
+
);
|
|
441
|
+
await appendToolResult({
|
|
442
|
+
threadId,
|
|
443
|
+
toolCallId: toolCall.id,
|
|
444
|
+
content: response.content
|
|
445
|
+
});
|
|
446
|
+
return {
|
|
447
|
+
toolCallId: toolCall.id,
|
|
448
|
+
name: toolCall.name,
|
|
449
|
+
result: response.result
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
if (parallel) {
|
|
453
|
+
return Promise.all(matchingCalls.map(processOne));
|
|
454
|
+
}
|
|
455
|
+
const results = [];
|
|
456
|
+
for (const toolCall of matchingCalls) {
|
|
457
|
+
results.push(await processOne(toolCall));
|
|
458
|
+
}
|
|
459
|
+
return results;
|
|
460
|
+
},
|
|
461
|
+
filterByName(toolCalls, name) {
|
|
462
|
+
return toolCalls.filter(
|
|
463
|
+
(tc) => tc.name === name
|
|
464
|
+
);
|
|
465
|
+
},
|
|
466
|
+
hasToolCall(toolCalls, name) {
|
|
467
|
+
return toolCalls.some((tc) => tc.name === name);
|
|
468
|
+
},
|
|
469
|
+
getResultsByName(results, name) {
|
|
470
|
+
return results.filter(
|
|
471
|
+
(r) => r.name === name
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function hasNoOtherToolCalls(toolCalls, excludeName) {
|
|
477
|
+
return toolCalls.filter((tc) => tc.name !== excludeName).length === 0;
|
|
478
|
+
}
|
|
479
|
+
var askUserQuestionTool = {
|
|
480
|
+
name: "AskUserQuestion",
|
|
481
|
+
description: `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
482
|
+
|
|
483
|
+
1. Gather user preferences or requirements
|
|
484
|
+
2. Clarify ambiguous instructions
|
|
485
|
+
3. Get decisions on implementation choices as you work
|
|
486
|
+
4. Offer choices to the user about what direction to take.
|
|
487
|
+
|
|
488
|
+
Usage notes:
|
|
489
|
+
|
|
490
|
+
* Users will always be able to select "Other" to provide custom text input
|
|
491
|
+
* Use multiSelect: true to allow multiple answers to be selected for a question
|
|
492
|
+
* If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
|
|
493
|
+
`,
|
|
494
|
+
schema: z2__default.default.object({
|
|
495
|
+
questions: z2__default.default.array(
|
|
496
|
+
z2__default.default.object({
|
|
497
|
+
question: z2__default.default.string().describe("The full question text to display"),
|
|
498
|
+
header: z2__default.default.string().describe("Short label for the question (max 12 characters)"),
|
|
499
|
+
options: z2__default.default.array(
|
|
500
|
+
z2__default.default.object({
|
|
501
|
+
label: z2__default.default.string(),
|
|
502
|
+
description: z2__default.default.string()
|
|
503
|
+
})
|
|
504
|
+
).min(0).max(4).describe("Array of 0-4 choices, each with label and description"),
|
|
505
|
+
multiSelect: z2__default.default.boolean().describe("If true, users can select multiple options")
|
|
506
|
+
})
|
|
507
|
+
)
|
|
508
|
+
}),
|
|
509
|
+
strict: true
|
|
510
|
+
};
|
|
511
|
+
var globTool = {
|
|
512
|
+
name: "Glob",
|
|
513
|
+
description: `Search for files matching a glob pattern within the available file system.
|
|
514
|
+
|
|
515
|
+
Usage:
|
|
516
|
+
- Use glob patterns like "**/*.ts" to find all TypeScript files
|
|
517
|
+
- Use "docs/**" to find all files in the docs directory
|
|
518
|
+
- Patterns are matched against virtual paths in the file system
|
|
519
|
+
|
|
520
|
+
Examples:
|
|
521
|
+
- "*.md" - Find all markdown files in the root
|
|
522
|
+
- "**/*.test.ts" - Find all test files recursively
|
|
523
|
+
- "src/**/*.ts" - Find all TypeScript files in src directory
|
|
524
|
+
`,
|
|
525
|
+
schema: z2.z.object({
|
|
526
|
+
pattern: z2.z.string().describe("Glob pattern to match files against"),
|
|
527
|
+
root: z2.z.string().optional().describe("Optional root directory to search from")
|
|
528
|
+
}),
|
|
529
|
+
strict: true
|
|
530
|
+
};
|
|
531
|
+
var grepTool = {
|
|
532
|
+
name: "Grep",
|
|
533
|
+
description: `Search file contents for a pattern within the available file system.
|
|
534
|
+
|
|
535
|
+
Usage:
|
|
536
|
+
- Searches for a regex pattern across file contents
|
|
537
|
+
- Returns matching lines with file paths and line numbers
|
|
538
|
+
- Can filter by file patterns and limit results
|
|
539
|
+
|
|
540
|
+
Examples:
|
|
541
|
+
- Search for "TODO" in all files
|
|
542
|
+
- Search for function definitions with "function.*handleClick"
|
|
543
|
+
- Search case-insensitively with ignoreCase: true
|
|
544
|
+
`,
|
|
545
|
+
schema: z2.z.object({
|
|
546
|
+
pattern: z2.z.string().describe("Regex pattern to search for in file contents"),
|
|
547
|
+
ignoreCase: z2.z.boolean().optional().describe("Case-insensitive search (default: false)"),
|
|
548
|
+
maxMatches: z2.z.number().optional().describe("Maximum number of matches to return (default: 50)"),
|
|
549
|
+
includePatterns: z2.z.array(z2.z.string()).optional().describe("Glob patterns to include (e.g., ['*.ts', '*.js'])"),
|
|
550
|
+
excludePatterns: z2.z.array(z2.z.string()).optional().describe("Glob patterns to exclude (e.g., ['*.test.ts'])"),
|
|
551
|
+
contextLines: z2.z.number().optional().describe("Number of context lines to show around matches")
|
|
552
|
+
}),
|
|
553
|
+
strict: true
|
|
554
|
+
};
|
|
555
|
+
var readTool = {
|
|
556
|
+
name: "FileRead",
|
|
557
|
+
description: `Read file contents with optional pagination.
|
|
558
|
+
|
|
559
|
+
Usage:
|
|
560
|
+
- Provide the virtual path to the file you want to read
|
|
561
|
+
- Supports text files, images, and PDFs
|
|
562
|
+
- For large files, use offset and limit to read specific portions
|
|
563
|
+
|
|
564
|
+
The tool returns the file content in an appropriate format:
|
|
565
|
+
- Text files: Plain text content
|
|
566
|
+
- Images: Base64-encoded image data
|
|
567
|
+
- PDFs: Extracted text content
|
|
568
|
+
`,
|
|
569
|
+
schema: z2.z.object({
|
|
570
|
+
path: z2.z.string().describe("Virtual path to the file to read"),
|
|
571
|
+
offset: z2.z.number().optional().describe(
|
|
572
|
+
"Line number to start reading from (1-indexed, for text files)"
|
|
573
|
+
),
|
|
574
|
+
limit: z2.z.number().optional().describe("Maximum number of lines to read (for text files)")
|
|
575
|
+
}),
|
|
576
|
+
strict: true
|
|
577
|
+
};
|
|
578
|
+
var writeTool = {
|
|
579
|
+
name: "FileWrite",
|
|
580
|
+
description: `Create or overwrite a file with new content.
|
|
581
|
+
|
|
582
|
+
Usage:
|
|
583
|
+
- Provide the absolute virtual path to the file
|
|
584
|
+
- The file will be created if it doesn't exist
|
|
585
|
+
- If the file exists, it will be completely overwritten
|
|
586
|
+
|
|
587
|
+
IMPORTANT:
|
|
588
|
+
- You must read the file first (in this session) before writing to it
|
|
589
|
+
- This is an atomic write operation - the entire file is replaced
|
|
590
|
+
- Path must be absolute (e.g., "/docs/readme.md", not "docs/readme.md")
|
|
591
|
+
`,
|
|
592
|
+
schema: z2.z.object({
|
|
593
|
+
file_path: z2.z.string().describe("The absolute virtual path to the file to write"),
|
|
594
|
+
content: z2.z.string().describe("The content to write to the file")
|
|
595
|
+
}),
|
|
596
|
+
strict: true
|
|
597
|
+
};
|
|
598
|
+
var editTool = {
|
|
599
|
+
name: "FileEdit",
|
|
600
|
+
description: `Edit specific sections of a file by replacing text.
|
|
601
|
+
|
|
602
|
+
Usage:
|
|
603
|
+
- Provide the exact text to find and replace
|
|
604
|
+
- The old_string must match exactly (whitespace-sensitive)
|
|
605
|
+
- By default, only replaces the first occurrence
|
|
606
|
+
- Use replace_all: true to replace all occurrences
|
|
607
|
+
|
|
608
|
+
IMPORTANT:
|
|
609
|
+
- You must read the file first (in this session) before editing it
|
|
610
|
+
- old_string must be unique in the file (unless using replace_all)
|
|
611
|
+
- The operation fails if old_string is not found
|
|
612
|
+
- old_string and new_string must be different
|
|
613
|
+
`,
|
|
614
|
+
schema: z2.z.object({
|
|
615
|
+
file_path: z2.z.string().describe("The absolute virtual path to the file to modify"),
|
|
616
|
+
old_string: z2.z.string().describe("The exact text to replace"),
|
|
617
|
+
new_string: z2.z.string().describe(
|
|
618
|
+
"The text to replace it with (must be different from old_string)"
|
|
619
|
+
),
|
|
620
|
+
replace_all: z2.z.boolean().optional().describe(
|
|
621
|
+
"If true, replace all occurrences of old_string (default: false)"
|
|
622
|
+
)
|
|
623
|
+
}),
|
|
624
|
+
strict: true
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// src/lib/filesystem/types.ts
|
|
628
|
+
function fileContentToMessageContent(content) {
|
|
629
|
+
switch (content.type) {
|
|
630
|
+
case "text":
|
|
631
|
+
return [{ type: "text", text: content.content }];
|
|
632
|
+
case "image":
|
|
633
|
+
return [
|
|
634
|
+
{
|
|
635
|
+
type: "image_url",
|
|
636
|
+
image_url: {
|
|
637
|
+
url: `data:${content.mimeType};base64,${content.data}`
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
case "pdf":
|
|
642
|
+
return [{ type: "text", text: content.content }];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
var DEFAULT_HEADER = "Available files and directories:";
|
|
646
|
+
var DEFAULT_DESCRIPTION = "You have access to the following files. Use the Read, Glob, and Grep tools to explore them.";
|
|
647
|
+
function isExcluded(path, excludePatterns) {
|
|
648
|
+
if (!excludePatterns || excludePatterns.length === 0) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
return excludePatterns.some((pattern) => minimatch.minimatch(path, pattern));
|
|
652
|
+
}
|
|
653
|
+
function renderNode(node, options, depth, indent) {
|
|
654
|
+
const lines = [];
|
|
655
|
+
if (options.maxDepth !== void 0 && depth > options.maxDepth) {
|
|
656
|
+
return lines;
|
|
657
|
+
}
|
|
658
|
+
if (isExcluded(node.path, options.excludePatterns)) {
|
|
659
|
+
return lines;
|
|
660
|
+
}
|
|
661
|
+
const parts = node.path.split("/");
|
|
662
|
+
const name = parts[parts.length - 1] || node.path;
|
|
663
|
+
let line = indent;
|
|
664
|
+
if (node.type === "directory") {
|
|
665
|
+
line += `${name}/`;
|
|
666
|
+
} else {
|
|
667
|
+
line += name;
|
|
668
|
+
}
|
|
669
|
+
if (options.showMimeTypes && node.mimeType) {
|
|
670
|
+
line += ` [${node.mimeType}]`;
|
|
671
|
+
}
|
|
672
|
+
if (options.showDescriptions !== false && node.description) {
|
|
673
|
+
line += ` - ${node.description}`;
|
|
674
|
+
}
|
|
675
|
+
lines.push(line);
|
|
676
|
+
if (node.type === "directory" && node.children) {
|
|
677
|
+
const childIndent = indent + " ";
|
|
678
|
+
for (const child of node.children) {
|
|
679
|
+
lines.push(...renderNode(child, options, depth + 1, childIndent));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return lines;
|
|
683
|
+
}
|
|
684
|
+
function buildFileTreePrompt(nodes, options = {}) {
|
|
685
|
+
const header = options.headerText ?? DEFAULT_HEADER;
|
|
686
|
+
const description = options.descriptionText ?? DEFAULT_DESCRIPTION;
|
|
687
|
+
const lines = [];
|
|
688
|
+
for (const node of nodes) {
|
|
689
|
+
lines.push(...renderNode(node, options, 0, ""));
|
|
690
|
+
}
|
|
691
|
+
if (lines.length === 0) {
|
|
692
|
+
return `<file_system>
|
|
693
|
+
${header}
|
|
694
|
+
|
|
695
|
+
${description}
|
|
696
|
+
|
|
697
|
+
(no files available)
|
|
698
|
+
</file_system>`;
|
|
699
|
+
}
|
|
700
|
+
return `<file_system>
|
|
701
|
+
${header}
|
|
702
|
+
|
|
703
|
+
${lines.join("\n")}
|
|
704
|
+
</file_system>`;
|
|
705
|
+
}
|
|
706
|
+
function flattenFileTree(nodes) {
|
|
707
|
+
const paths = [];
|
|
708
|
+
function traverse(node) {
|
|
709
|
+
if (node.type === "file") {
|
|
710
|
+
paths.push(node.path);
|
|
711
|
+
}
|
|
712
|
+
if (node.children) {
|
|
713
|
+
for (const child of node.children) {
|
|
714
|
+
traverse(child);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
for (const node of nodes) {
|
|
719
|
+
traverse(node);
|
|
720
|
+
}
|
|
721
|
+
return paths;
|
|
722
|
+
}
|
|
723
|
+
function isPathInScope(path, scopedNodes) {
|
|
724
|
+
const allowedPaths = flattenFileTree(scopedNodes);
|
|
725
|
+
return allowedPaths.includes(path);
|
|
726
|
+
}
|
|
727
|
+
function findNodeByPath(path, nodes) {
|
|
728
|
+
for (const node of nodes) {
|
|
729
|
+
if (node.path === path) {
|
|
730
|
+
return node;
|
|
731
|
+
}
|
|
732
|
+
if (node.children) {
|
|
733
|
+
const found = findNodeByPath(path, node.children);
|
|
734
|
+
if (found) {
|
|
735
|
+
return found;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return void 0;
|
|
740
|
+
}
|
|
741
|
+
var BaseFileSystemProvider = class {
|
|
742
|
+
scopedNodes;
|
|
743
|
+
allowedPaths;
|
|
744
|
+
constructor(scopedNodes) {
|
|
745
|
+
this.scopedNodes = scopedNodes;
|
|
746
|
+
this.allowedPaths = new Set(flattenFileTree(scopedNodes));
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Validate that a path is within the allowed scope.
|
|
750
|
+
* Throws an error if the path is not allowed.
|
|
751
|
+
*/
|
|
752
|
+
validatePath(path) {
|
|
753
|
+
if (!this.allowedPaths.has(path)) {
|
|
754
|
+
throw new Error(`Path "${path}" is not within the allowed scope`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Filter paths by glob pattern.
|
|
759
|
+
*/
|
|
760
|
+
filterByPattern(paths, pattern, root) {
|
|
761
|
+
const effectivePattern = root ? `${root}/${pattern}` : pattern;
|
|
762
|
+
return paths.filter((path) => {
|
|
763
|
+
if (root && !path.startsWith(root)) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
return minimatch.minimatch(path, effectivePattern, { matchBase: true });
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Get all file paths in scope
|
|
771
|
+
*/
|
|
772
|
+
getAllPaths() {
|
|
773
|
+
return Array.from(this.allowedPaths);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Find FileNode by path
|
|
777
|
+
*/
|
|
778
|
+
findNode(path) {
|
|
779
|
+
const findInNodes = (nodes) => {
|
|
780
|
+
for (const node of nodes) {
|
|
781
|
+
if (node.path === path) {
|
|
782
|
+
return node;
|
|
783
|
+
}
|
|
784
|
+
if (node.children) {
|
|
785
|
+
const found = findInNodes(node.children);
|
|
786
|
+
if (found) return found;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return void 0;
|
|
790
|
+
};
|
|
791
|
+
return findInNodes(this.scopedNodes);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Read file as text for grep operations.
|
|
795
|
+
* Default implementation uses readFile, but can be overridden for efficiency.
|
|
796
|
+
*/
|
|
797
|
+
async readFileAsText(node) {
|
|
798
|
+
try {
|
|
799
|
+
const content = await this.readFile(node);
|
|
800
|
+
if (content.type === "text") {
|
|
801
|
+
return content.content;
|
|
802
|
+
}
|
|
803
|
+
if (content.type === "pdf") {
|
|
804
|
+
return content.content;
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// ============================================================================
|
|
812
|
+
// FileSystemProvider implementation
|
|
813
|
+
// ============================================================================
|
|
814
|
+
async glob(pattern, root) {
|
|
815
|
+
const allPaths = this.getAllPaths();
|
|
816
|
+
const matchingPaths = this.filterByPattern(allPaths, pattern, root);
|
|
817
|
+
return matchingPaths.map((path) => this.findNode(path)).filter((node) => node !== void 0);
|
|
818
|
+
}
|
|
819
|
+
async grep(pattern, options) {
|
|
820
|
+
const matches = [];
|
|
821
|
+
const maxMatches = options?.maxMatches ?? 50;
|
|
822
|
+
const flags = options?.ignoreCase ? "gi" : "g";
|
|
823
|
+
let regex;
|
|
824
|
+
try {
|
|
825
|
+
regex = new RegExp(pattern, flags);
|
|
826
|
+
} catch {
|
|
827
|
+
throw new Error(`Invalid regex pattern: ${pattern}`);
|
|
828
|
+
}
|
|
829
|
+
let paths = this.getAllPaths();
|
|
830
|
+
if (options?.includePatterns?.length) {
|
|
831
|
+
const includePatterns = options.includePatterns;
|
|
832
|
+
paths = paths.filter(
|
|
833
|
+
(path) => includePatterns.some((p) => minimatch.minimatch(path, p, { matchBase: true }))
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
if (options?.excludePatterns?.length) {
|
|
837
|
+
const excludePatterns = options.excludePatterns;
|
|
838
|
+
paths = paths.filter(
|
|
839
|
+
(path) => !excludePatterns.some((p) => minimatch.minimatch(path, p, { matchBase: true }))
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
for (const path of paths) {
|
|
843
|
+
if (matches.length >= maxMatches) break;
|
|
844
|
+
const node = this.findNode(path);
|
|
845
|
+
if (!node || node.type !== "file") continue;
|
|
846
|
+
const text = await this.readFileAsText(node);
|
|
847
|
+
if (!text) continue;
|
|
848
|
+
const lines = text.split("\n");
|
|
849
|
+
for (let i = 0; i < lines.length; i++) {
|
|
850
|
+
if (matches.length >= maxMatches) break;
|
|
851
|
+
const line = lines[i];
|
|
852
|
+
if (line === void 0) continue;
|
|
853
|
+
if (regex.test(line)) {
|
|
854
|
+
regex.lastIndex = 0;
|
|
855
|
+
const match = {
|
|
856
|
+
path,
|
|
857
|
+
lineNumber: i + 1,
|
|
858
|
+
line: line.trim()
|
|
859
|
+
};
|
|
860
|
+
if (options?.contextLines && options.contextLines > 0) {
|
|
861
|
+
const contextBefore = [];
|
|
862
|
+
const contextAfter = [];
|
|
863
|
+
for (let j = Math.max(0, i - options.contextLines); j < i; j++) {
|
|
864
|
+
const contextLine = lines[j];
|
|
865
|
+
if (contextLine !== void 0) {
|
|
866
|
+
contextBefore.push(contextLine.trim());
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (let j = i + 1; j <= Math.min(lines.length - 1, i + options.contextLines); j++) {
|
|
870
|
+
const contextLine = lines[j];
|
|
871
|
+
if (contextLine !== void 0) {
|
|
872
|
+
contextAfter.push(contextLine.trim());
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
match.contextBefore = contextBefore;
|
|
876
|
+
match.contextAfter = contextAfter;
|
|
877
|
+
}
|
|
878
|
+
matches.push(match);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return matches;
|
|
883
|
+
}
|
|
884
|
+
async read(path) {
|
|
885
|
+
this.validatePath(path);
|
|
886
|
+
const node = this.findNode(path);
|
|
887
|
+
if (!node) {
|
|
888
|
+
throw new Error(`File not found: ${path}`);
|
|
889
|
+
}
|
|
890
|
+
if (node.type !== "file") {
|
|
891
|
+
throw new Error(`Path is a directory, not a file: ${path}`);
|
|
892
|
+
}
|
|
893
|
+
return this.readFile(node);
|
|
894
|
+
}
|
|
895
|
+
async exists(path) {
|
|
896
|
+
return this.allowedPaths.has(path);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
var InMemoryFileSystemProvider = class _InMemoryFileSystemProvider extends BaseFileSystemProvider {
|
|
900
|
+
files;
|
|
901
|
+
constructor(scopedNodes, files) {
|
|
902
|
+
super(scopedNodes);
|
|
903
|
+
this.files = files;
|
|
904
|
+
}
|
|
905
|
+
async readFile(node) {
|
|
906
|
+
const content = this.files.get(node.path);
|
|
907
|
+
if (!content) {
|
|
908
|
+
throw new Error(`File not found in storage: ${node.path}`);
|
|
909
|
+
}
|
|
910
|
+
return content;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Add or update a file in the in-memory storage
|
|
914
|
+
*/
|
|
915
|
+
setFile(path, content) {
|
|
916
|
+
this.files.set(path, content);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Write text content to a file
|
|
920
|
+
*/
|
|
921
|
+
async write(path, content) {
|
|
922
|
+
this.validatePath(path);
|
|
923
|
+
this.files.set(path, { type: "text", content });
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Create an InMemoryFileSystemProvider from a simple object map
|
|
927
|
+
*/
|
|
928
|
+
static fromTextFiles(scopedNodes, files) {
|
|
929
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
930
|
+
for (const [path, content] of Object.entries(files)) {
|
|
931
|
+
fileMap.set(path, { type: "text", content });
|
|
932
|
+
}
|
|
933
|
+
return new _InMemoryFileSystemProvider(scopedNodes, fileMap);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
var CompositeFileSystemProvider = class extends BaseFileSystemProvider {
|
|
937
|
+
backends;
|
|
938
|
+
defaultBackend;
|
|
939
|
+
defaultResolver;
|
|
940
|
+
constructor(scopedNodes, config) {
|
|
941
|
+
super(scopedNodes);
|
|
942
|
+
this.backends = new Map(Object.entries(config.backends));
|
|
943
|
+
this.defaultBackend = config.defaultBackend;
|
|
944
|
+
this.defaultResolver = config.defaultResolver;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Get the backend name for a file node
|
|
948
|
+
*/
|
|
949
|
+
getBackendName(node) {
|
|
950
|
+
const backend = node.metadata?.backend;
|
|
951
|
+
if (typeof backend === "string") {
|
|
952
|
+
return backend;
|
|
953
|
+
}
|
|
954
|
+
return this.defaultBackend;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Resolve content using a resolver (function or provider)
|
|
958
|
+
*/
|
|
959
|
+
async resolveContent(resolver, node) {
|
|
960
|
+
if (typeof resolver === "function") {
|
|
961
|
+
return resolver(node);
|
|
962
|
+
}
|
|
963
|
+
return resolver.read(node.path);
|
|
964
|
+
}
|
|
965
|
+
async readFile(node) {
|
|
966
|
+
const backendName = this.getBackendName(node);
|
|
967
|
+
if (backendName) {
|
|
968
|
+
const config = this.backends.get(backendName);
|
|
969
|
+
if (config) {
|
|
970
|
+
return this.resolveContent(config.resolver, node);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (this.defaultResolver) {
|
|
974
|
+
return this.defaultResolver(node);
|
|
975
|
+
}
|
|
976
|
+
const availableBackends = Array.from(this.backends.keys()).join(", ");
|
|
977
|
+
throw new Error(
|
|
978
|
+
`No resolver for file "${node.path}". Backend: ${backendName ?? "(none)"}. Available backends: ${availableBackends || "(none)"}`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Add or update a backend configuration
|
|
983
|
+
*/
|
|
984
|
+
setBackend(name, config) {
|
|
985
|
+
this.backends.set(name, config);
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Remove a backend
|
|
989
|
+
*/
|
|
990
|
+
removeBackend(name) {
|
|
991
|
+
return this.backends.delete(name);
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Check if a backend exists
|
|
995
|
+
*/
|
|
996
|
+
hasBackend(name) {
|
|
997
|
+
return this.backends.has(name);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
// node_modules/uuid/dist/esm-node/stringify.js
|
|
1002
|
+
var byteToHex = [];
|
|
1003
|
+
for (let i = 0; i < 256; ++i) {
|
|
1004
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
1005
|
+
}
|
|
1006
|
+
function unsafeStringify(arr, offset = 0) {
|
|
1007
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
1008
|
+
}
|
|
1009
|
+
var rnds8Pool = new Uint8Array(256);
|
|
1010
|
+
var poolPtr = rnds8Pool.length;
|
|
1011
|
+
function rng() {
|
|
1012
|
+
if (poolPtr > rnds8Pool.length - 16) {
|
|
1013
|
+
crypto__default.default.randomFillSync(rnds8Pool);
|
|
1014
|
+
poolPtr = 0;
|
|
1015
|
+
}
|
|
1016
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
1017
|
+
}
|
|
1018
|
+
var native_default = {
|
|
1019
|
+
randomUUID: crypto__default.default.randomUUID
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
// node_modules/uuid/dist/esm-node/v4.js
|
|
1023
|
+
function v4(options, buf, offset) {
|
|
1024
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
1025
|
+
return native_default.randomUUID();
|
|
1026
|
+
}
|
|
1027
|
+
options = options || {};
|
|
1028
|
+
const rnds = options.random || (options.rng || rng)();
|
|
1029
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
1030
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
1031
|
+
if (buf) {
|
|
1032
|
+
offset = offset || 0;
|
|
1033
|
+
for (let i = 0; i < 16; ++i) {
|
|
1034
|
+
buf[offset + i] = rnds[i];
|
|
1035
|
+
}
|
|
1036
|
+
return buf;
|
|
1037
|
+
}
|
|
1038
|
+
return unsafeStringify(rnds);
|
|
1039
|
+
}
|
|
1040
|
+
var v4_default = v4;
|
|
1041
|
+
|
|
1042
|
+
// src/lib/thread-manager.ts
|
|
1043
|
+
var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
|
|
1044
|
+
function getThreadKey(threadId, key) {
|
|
1045
|
+
return `thread:${threadId}:${key}`;
|
|
1046
|
+
}
|
|
1047
|
+
function createThreadManager(config) {
|
|
1048
|
+
const { redis, threadId, key = "messages" } = config;
|
|
1049
|
+
const redisKey = getThreadKey(threadId, key);
|
|
1050
|
+
return {
|
|
1051
|
+
async initialize() {
|
|
1052
|
+
await redis.del(redisKey);
|
|
1053
|
+
},
|
|
1054
|
+
async load() {
|
|
1055
|
+
const data = await redis.lrange(redisKey, 0, -1);
|
|
1056
|
+
return data.map((item) => JSON.parse(item));
|
|
1057
|
+
},
|
|
1058
|
+
async append(messages) {
|
|
1059
|
+
if (messages.length > 0) {
|
|
1060
|
+
await redis.rpush(redisKey, ...messages.map((m) => JSON.stringify(m)));
|
|
1061
|
+
await redis.expire(redisKey, THREAD_TTL_SECONDS);
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
async delete() {
|
|
1065
|
+
await redis.del(redisKey);
|
|
1066
|
+
},
|
|
1067
|
+
createHumanMessage(content) {
|
|
1068
|
+
return new messages.HumanMessage({
|
|
1069
|
+
id: v4_default(),
|
|
1070
|
+
content
|
|
1071
|
+
}).toDict();
|
|
1072
|
+
},
|
|
1073
|
+
createAIMessage(content, kwargs) {
|
|
1074
|
+
return new messages.AIMessage({
|
|
1075
|
+
id: v4_default(),
|
|
1076
|
+
content,
|
|
1077
|
+
additional_kwargs: kwargs ? {
|
|
1078
|
+
header: kwargs.header,
|
|
1079
|
+
options: kwargs.options,
|
|
1080
|
+
multiSelect: kwargs.multiSelect
|
|
1081
|
+
} : void 0
|
|
1082
|
+
}).toDict();
|
|
1083
|
+
},
|
|
1084
|
+
createToolMessage(content, toolCallId) {
|
|
1085
|
+
return new messages.ToolMessage({
|
|
1086
|
+
// Cast needed due to langchain type compatibility
|
|
1087
|
+
content,
|
|
1088
|
+
tool_call_id: toolCallId
|
|
1089
|
+
}).toDict();
|
|
1090
|
+
},
|
|
1091
|
+
async appendHumanMessage(content) {
|
|
1092
|
+
const message = this.createHumanMessage(content);
|
|
1093
|
+
await this.append([message]);
|
|
1094
|
+
},
|
|
1095
|
+
async appendToolMessage(content, toolCallId) {
|
|
1096
|
+
const message = this.createToolMessage(content, toolCallId);
|
|
1097
|
+
await this.append([message]);
|
|
1098
|
+
},
|
|
1099
|
+
async appendAIMessage(content) {
|
|
1100
|
+
const message = this.createAIMessage(content);
|
|
1101
|
+
await this.append([message]);
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function createSharedActivities(redis) {
|
|
1106
|
+
return {
|
|
1107
|
+
async appendToolResult(config) {
|
|
1108
|
+
const { threadId, toolCallId, content } = config;
|
|
1109
|
+
const thread = createThreadManager({ redis, threadId });
|
|
1110
|
+
await thread.appendToolMessage(content, toolCallId);
|
|
1111
|
+
},
|
|
1112
|
+
async initializeThread(threadId) {
|
|
1113
|
+
const thread = createThreadManager({ redis, threadId });
|
|
1114
|
+
await thread.initialize();
|
|
1115
|
+
},
|
|
1116
|
+
async appendThreadMessages(threadId, messages) {
|
|
1117
|
+
const thread = createThreadManager({ redis, threadId });
|
|
1118
|
+
await thread.append(messages);
|
|
1119
|
+
},
|
|
1120
|
+
async appendHumanMessage(threadId, content) {
|
|
1121
|
+
const thread = createThreadManager({ redis, threadId });
|
|
1122
|
+
await thread.appendHumanMessage(content);
|
|
1123
|
+
},
|
|
1124
|
+
async parseToolCalls(storedMessage) {
|
|
1125
|
+
const message = messages.mapStoredMessageToChatMessage(storedMessage);
|
|
1126
|
+
const toolCalls = message.tool_calls ?? [];
|
|
1127
|
+
return toolCalls.map((toolCall) => ({
|
|
1128
|
+
id: toolCall.id,
|
|
1129
|
+
name: toolCall.name,
|
|
1130
|
+
args: toolCall.args
|
|
1131
|
+
}));
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// src/plugin.ts
|
|
1137
|
+
var ZeitlichPlugin = class extends plugin.SimplePlugin {
|
|
1138
|
+
constructor(options) {
|
|
1139
|
+
super({
|
|
1140
|
+
name: "ZeitlichPlugin",
|
|
1141
|
+
activities: createSharedActivities(options.redis)
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
async function invokeModel(redis, { threadId, agentName, tools }, model, { systemPrompt }) {
|
|
1146
|
+
const thread = createThreadManager({ redis, threadId });
|
|
1147
|
+
const runId = v4_default();
|
|
1148
|
+
const messages$1 = await thread.load();
|
|
1149
|
+
const response = await model.invoke(
|
|
1150
|
+
[
|
|
1151
|
+
new messages.SystemMessage(systemPrompt),
|
|
1152
|
+
...messages.mapStoredMessagesToChatMessages(messages$1)
|
|
1153
|
+
],
|
|
1154
|
+
{
|
|
1155
|
+
runName: agentName,
|
|
1156
|
+
runId,
|
|
1157
|
+
metadata: { thread_id: threadId },
|
|
1158
|
+
tools
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
await thread.append([response.toDict()]);
|
|
1162
|
+
return {
|
|
1163
|
+
message: response.toDict(),
|
|
1164
|
+
stopReason: response.response_metadata?.stop_reason ?? null,
|
|
1165
|
+
usage: {
|
|
1166
|
+
input_tokens: response.usage_metadata?.input_tokens,
|
|
1167
|
+
output_tokens: response.usage_metadata?.output_tokens,
|
|
1168
|
+
total_tokens: response.usage_metadata?.total_tokens
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
var handleAskUserQuestionToolResult = async (args) => {
|
|
1173
|
+
const messages$1 = args.questions.map(
|
|
1174
|
+
({ question, header, options, multiSelect }) => new messages.AIMessage({
|
|
1175
|
+
content: question,
|
|
1176
|
+
additional_kwargs: {
|
|
1177
|
+
header,
|
|
1178
|
+
options,
|
|
1179
|
+
multiSelect
|
|
1180
|
+
}
|
|
1181
|
+
}).toDict()
|
|
1182
|
+
);
|
|
1183
|
+
return { content: "Question submitted", result: { chatMessages: messages$1 } };
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/tools/glob/handler.ts
|
|
1187
|
+
function createGlobHandler(config) {
|
|
1188
|
+
return async (args) => {
|
|
1189
|
+
const { pattern, root } = args;
|
|
1190
|
+
try {
|
|
1191
|
+
const matches = await config.provider.glob(pattern, root);
|
|
1192
|
+
if (matches.length === 0) {
|
|
1193
|
+
return {
|
|
1194
|
+
content: `No files found matching pattern: ${pattern}`,
|
|
1195
|
+
result: { files: [] }
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const paths = matches.map((node) => node.path);
|
|
1199
|
+
const fileList = paths.map((p) => ` ${p}`).join("\n");
|
|
1200
|
+
return {
|
|
1201
|
+
content: `Found ${matches.length} file(s) matching "${pattern}":
|
|
1202
|
+
${fileList}`,
|
|
1203
|
+
result: { files: matches }
|
|
1204
|
+
};
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1207
|
+
return {
|
|
1208
|
+
content: `Error searching for files: ${message}`,
|
|
1209
|
+
result: { files: [] }
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/tools/grep/handler.ts
|
|
1216
|
+
function formatMatch(match, showContext) {
|
|
1217
|
+
const lines = [];
|
|
1218
|
+
if (showContext && match.contextBefore?.length) {
|
|
1219
|
+
for (let i = 0; i < match.contextBefore.length; i++) {
|
|
1220
|
+
const lineNum = match.lineNumber - match.contextBefore.length + i;
|
|
1221
|
+
lines.push(`${match.path}:${lineNum}-${match.contextBefore[i]}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
lines.push(`${match.path}:${match.lineNumber}:${match.line}`);
|
|
1225
|
+
if (showContext && match.contextAfter?.length) {
|
|
1226
|
+
for (let i = 0; i < match.contextAfter.length; i++) {
|
|
1227
|
+
const lineNum = match.lineNumber + 1 + i;
|
|
1228
|
+
lines.push(`${match.path}:${lineNum}-${match.contextAfter[i]}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return lines.join("\n");
|
|
1232
|
+
}
|
|
1233
|
+
function createGrepHandler(config) {
|
|
1234
|
+
return async (args) => {
|
|
1235
|
+
const {
|
|
1236
|
+
pattern,
|
|
1237
|
+
ignoreCase,
|
|
1238
|
+
maxMatches,
|
|
1239
|
+
includePatterns,
|
|
1240
|
+
excludePatterns,
|
|
1241
|
+
contextLines
|
|
1242
|
+
} = args;
|
|
1243
|
+
try {
|
|
1244
|
+
const matches = await config.provider.grep(pattern, {
|
|
1245
|
+
ignoreCase,
|
|
1246
|
+
maxMatches: maxMatches ?? 50,
|
|
1247
|
+
includePatterns,
|
|
1248
|
+
excludePatterns,
|
|
1249
|
+
contextLines
|
|
1250
|
+
});
|
|
1251
|
+
if (matches.length === 0) {
|
|
1252
|
+
return {
|
|
1253
|
+
content: `No matches found for pattern: ${pattern}`,
|
|
1254
|
+
result: { matches: [] }
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const showContext = contextLines !== void 0 && contextLines > 0;
|
|
1258
|
+
const formattedMatches = matches.map((m) => formatMatch(m, showContext)).join("\n");
|
|
1259
|
+
return {
|
|
1260
|
+
content: `Found ${matches.length} match(es) for "${pattern}":
|
|
1261
|
+
|
|
1262
|
+
${formattedMatches}`,
|
|
1263
|
+
result: { matches }
|
|
1264
|
+
};
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1267
|
+
return {
|
|
1268
|
+
content: `Error searching file contents: ${message}`,
|
|
1269
|
+
result: { matches: [] }
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/tools/read/handler.ts
|
|
1276
|
+
function applyTextRange(content, offset, limit) {
|
|
1277
|
+
if (offset === void 0 && limit === void 0) {
|
|
1278
|
+
return content;
|
|
1279
|
+
}
|
|
1280
|
+
const lines = content.split("\n");
|
|
1281
|
+
const startLine = offset !== void 0 ? Math.max(0, offset - 1) : 0;
|
|
1282
|
+
const endLine = limit !== void 0 ? startLine + limit : lines.length;
|
|
1283
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
1284
|
+
return selectedLines.map((line, i) => {
|
|
1285
|
+
const lineNum = (startLine + i + 1).toString().padStart(6, " ");
|
|
1286
|
+
return `${lineNum}|${line}`;
|
|
1287
|
+
}).join("\n");
|
|
1288
|
+
}
|
|
1289
|
+
function createReadHandler(config) {
|
|
1290
|
+
return async (args) => {
|
|
1291
|
+
const { path, offset, limit } = args;
|
|
1292
|
+
if (!isPathInScope(path, config.scopedNodes)) {
|
|
1293
|
+
return {
|
|
1294
|
+
content: [
|
|
1295
|
+
{
|
|
1296
|
+
type: "text",
|
|
1297
|
+
text: `Error: Path "${path}" is not within the available file system scope.`
|
|
1298
|
+
}
|
|
1299
|
+
],
|
|
1300
|
+
result: {
|
|
1301
|
+
path,
|
|
1302
|
+
content: {
|
|
1303
|
+
type: "text",
|
|
1304
|
+
content: "Error: Path is not within the available file system scope."
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
const exists = await config.provider.exists(path);
|
|
1311
|
+
if (!exists) {
|
|
1312
|
+
return {
|
|
1313
|
+
content: [
|
|
1314
|
+
{
|
|
1315
|
+
type: "text",
|
|
1316
|
+
text: `Error: File "${path}" does not exist.`
|
|
1317
|
+
}
|
|
1318
|
+
],
|
|
1319
|
+
result: {
|
|
1320
|
+
path,
|
|
1321
|
+
content: { type: "text", content: "Error: File does not exist." }
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
const fileContent = await config.provider.read(path);
|
|
1326
|
+
if (fileContent.type === "text") {
|
|
1327
|
+
const processedContent = applyTextRange(
|
|
1328
|
+
fileContent.content,
|
|
1329
|
+
offset,
|
|
1330
|
+
limit
|
|
1331
|
+
);
|
|
1332
|
+
let header = `File: ${path}`;
|
|
1333
|
+
if (offset !== void 0 || limit !== void 0) {
|
|
1334
|
+
const startLine = offset ?? 1;
|
|
1335
|
+
const endInfo = limit ? `, showing ${limit} lines` : "";
|
|
1336
|
+
header += ` (from line ${startLine}${endInfo})`;
|
|
1337
|
+
}
|
|
1338
|
+
return {
|
|
1339
|
+
content: [
|
|
1340
|
+
{
|
|
1341
|
+
type: "text",
|
|
1342
|
+
text: `${header}
|
|
1343
|
+
|
|
1344
|
+
${processedContent}`
|
|
1345
|
+
}
|
|
1346
|
+
],
|
|
1347
|
+
result: {
|
|
1348
|
+
path,
|
|
1349
|
+
content: {
|
|
1350
|
+
type: "text",
|
|
1351
|
+
content: `${header}
|
|
1352
|
+
|
|
1353
|
+
${processedContent}`
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const messageContent = fileContentToMessageContent(fileContent);
|
|
1359
|
+
return {
|
|
1360
|
+
content: [
|
|
1361
|
+
{
|
|
1362
|
+
type: "text",
|
|
1363
|
+
text: `File: ${path} (${fileContent.type})`
|
|
1364
|
+
},
|
|
1365
|
+
...messageContent
|
|
1366
|
+
],
|
|
1367
|
+
result: { path, content: fileContent }
|
|
1368
|
+
};
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1371
|
+
return {
|
|
1372
|
+
content: [
|
|
1373
|
+
{
|
|
1374
|
+
type: "text",
|
|
1375
|
+
text: `Error reading file "${path}": ${message}`
|
|
1376
|
+
}
|
|
1377
|
+
],
|
|
1378
|
+
result: {
|
|
1379
|
+
path,
|
|
1380
|
+
content: {
|
|
1381
|
+
type: "text",
|
|
1382
|
+
content: `Error reading file "${path}": ${message}`
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/tools/write/handler.ts
|
|
1391
|
+
function createWriteHandler(config) {
|
|
1392
|
+
return async (args) => {
|
|
1393
|
+
const { file_path, content } = args;
|
|
1394
|
+
if (!isPathInScope(file_path, config.scopedNodes)) {
|
|
1395
|
+
return {
|
|
1396
|
+
content: `Error: Path "${file_path}" is not within the available file system scope.`,
|
|
1397
|
+
result: {
|
|
1398
|
+
path: file_path,
|
|
1399
|
+
success: false,
|
|
1400
|
+
created: false,
|
|
1401
|
+
bytesWritten: 0
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (!config.skipReadCheck && !config.readFiles.has(file_path)) {
|
|
1406
|
+
const exists = await config.provider.exists(file_path);
|
|
1407
|
+
if (exists) {
|
|
1408
|
+
return {
|
|
1409
|
+
content: `Error: You must read "${file_path}" before writing to it. Use FileRead first.`,
|
|
1410
|
+
result: {
|
|
1411
|
+
path: file_path,
|
|
1412
|
+
success: false,
|
|
1413
|
+
created: false,
|
|
1414
|
+
bytesWritten: 0
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
try {
|
|
1420
|
+
const exists = await config.provider.exists(file_path);
|
|
1421
|
+
if (!config.provider.write) {
|
|
1422
|
+
return {
|
|
1423
|
+
content: `Error: The file system provider does not support write operations.`,
|
|
1424
|
+
result: {
|
|
1425
|
+
path: file_path,
|
|
1426
|
+
success: false,
|
|
1427
|
+
created: false,
|
|
1428
|
+
bytesWritten: 0
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
await config.provider.write(file_path, content);
|
|
1433
|
+
const bytesWritten = Buffer.byteLength(content, "utf-8");
|
|
1434
|
+
const action = exists ? "Updated" : "Created";
|
|
1435
|
+
return {
|
|
1436
|
+
content: `${action} file: ${file_path} (${bytesWritten} bytes)`,
|
|
1437
|
+
result: {
|
|
1438
|
+
path: file_path,
|
|
1439
|
+
success: true,
|
|
1440
|
+
created: !exists,
|
|
1441
|
+
bytesWritten
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1446
|
+
return {
|
|
1447
|
+
content: `Error writing file "${file_path}": ${message}`,
|
|
1448
|
+
result: {
|
|
1449
|
+
path: file_path,
|
|
1450
|
+
success: false,
|
|
1451
|
+
created: false,
|
|
1452
|
+
bytesWritten: 0
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/tools/edit/handler.ts
|
|
1460
|
+
function escapeRegExp(str) {
|
|
1461
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1462
|
+
}
|
|
1463
|
+
function createEditHandler(config) {
|
|
1464
|
+
return async (args) => {
|
|
1465
|
+
const { file_path, old_string, new_string, replace_all = false } = args;
|
|
1466
|
+
if (old_string === new_string) {
|
|
1467
|
+
return {
|
|
1468
|
+
content: `Error: old_string and new_string must be different.`,
|
|
1469
|
+
result: {
|
|
1470
|
+
path: file_path,
|
|
1471
|
+
success: false,
|
|
1472
|
+
replacements: 0
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
if (!isPathInScope(file_path, config.scopedNodes)) {
|
|
1477
|
+
return {
|
|
1478
|
+
content: `Error: Path "${file_path}" is not within the available file system scope.`,
|
|
1479
|
+
result: {
|
|
1480
|
+
path: file_path,
|
|
1481
|
+
success: false,
|
|
1482
|
+
replacements: 0
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
if (!config.skipReadCheck && !config.readFiles.has(file_path)) {
|
|
1487
|
+
return {
|
|
1488
|
+
content: `Error: You must read "${file_path}" before editing it. Use FileRead first.`,
|
|
1489
|
+
result: {
|
|
1490
|
+
path: file_path,
|
|
1491
|
+
success: false,
|
|
1492
|
+
replacements: 0
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
try {
|
|
1497
|
+
const exists = await config.provider.exists(file_path);
|
|
1498
|
+
if (!exists) {
|
|
1499
|
+
return {
|
|
1500
|
+
content: `Error: File "${file_path}" does not exist.`,
|
|
1501
|
+
result: {
|
|
1502
|
+
path: file_path,
|
|
1503
|
+
success: false,
|
|
1504
|
+
replacements: 0
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
if (!config.provider.write) {
|
|
1509
|
+
return {
|
|
1510
|
+
content: `Error: The file system provider does not support write operations.`,
|
|
1511
|
+
result: {
|
|
1512
|
+
path: file_path,
|
|
1513
|
+
success: false,
|
|
1514
|
+
replacements: 0
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
const fileContent = await config.provider.read(file_path);
|
|
1519
|
+
if (fileContent.type !== "text") {
|
|
1520
|
+
return {
|
|
1521
|
+
content: `Error: FileEdit only works with text files. "${file_path}" is ${fileContent.type}.`,
|
|
1522
|
+
result: {
|
|
1523
|
+
path: file_path,
|
|
1524
|
+
success: false,
|
|
1525
|
+
replacements: 0
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
const content = fileContent.content;
|
|
1530
|
+
if (!content.includes(old_string)) {
|
|
1531
|
+
return {
|
|
1532
|
+
content: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
|
|
1533
|
+
result: {
|
|
1534
|
+
path: file_path,
|
|
1535
|
+
success: false,
|
|
1536
|
+
replacements: 0
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
const escapedOldString = escapeRegExp(old_string);
|
|
1541
|
+
const globalRegex = new RegExp(escapedOldString, "g");
|
|
1542
|
+
const occurrences = (content.match(globalRegex) || []).length;
|
|
1543
|
+
if (!replace_all && occurrences > 1) {
|
|
1544
|
+
return {
|
|
1545
|
+
content: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
|
|
1546
|
+
result: {
|
|
1547
|
+
path: file_path,
|
|
1548
|
+
success: false,
|
|
1549
|
+
replacements: 0
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
let newContent;
|
|
1554
|
+
let replacements;
|
|
1555
|
+
if (replace_all) {
|
|
1556
|
+
newContent = content.split(old_string).join(new_string);
|
|
1557
|
+
replacements = occurrences;
|
|
1558
|
+
} else {
|
|
1559
|
+
const index = content.indexOf(old_string);
|
|
1560
|
+
newContent = content.slice(0, index) + new_string + content.slice(index + old_string.length);
|
|
1561
|
+
replacements = 1;
|
|
1562
|
+
}
|
|
1563
|
+
await config.provider.write(file_path, newContent);
|
|
1564
|
+
const summary = replace_all ? `Replaced ${replacements} occurrence(s)` : `Replaced 1 occurrence`;
|
|
1565
|
+
return {
|
|
1566
|
+
content: `${summary} in ${file_path}`,
|
|
1567
|
+
result: {
|
|
1568
|
+
path: file_path,
|
|
1569
|
+
success: true,
|
|
1570
|
+
replacements
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1575
|
+
return {
|
|
1576
|
+
content: `Error editing file "${file_path}": ${message}`,
|
|
1577
|
+
result: {
|
|
1578
|
+
path: file_path,
|
|
1579
|
+
success: false,
|
|
1580
|
+
replacements: 0
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
exports.AGENT_HANDLER_NAMES = AGENT_HANDLER_NAMES;
|
|
1588
|
+
exports.BaseFileSystemProvider = BaseFileSystemProvider;
|
|
1589
|
+
exports.CompositeFileSystemProvider = CompositeFileSystemProvider;
|
|
1590
|
+
exports.InMemoryFileSystemProvider = InMemoryFileSystemProvider;
|
|
1591
|
+
exports.ZeitlichPlugin = ZeitlichPlugin;
|
|
1592
|
+
exports.askUserQuestionTool = askUserQuestionTool;
|
|
1593
|
+
exports.buildFileTreePrompt = buildFileTreePrompt;
|
|
1594
|
+
exports.createAgentStateManager = createAgentStateManager;
|
|
1595
|
+
exports.createEditHandler = createEditHandler;
|
|
1596
|
+
exports.createGlobHandler = createGlobHandler;
|
|
1597
|
+
exports.createGrepHandler = createGrepHandler;
|
|
1598
|
+
exports.createPromptManager = createPromptManager;
|
|
1599
|
+
exports.createReadHandler = createReadHandler;
|
|
1600
|
+
exports.createSession = createSession;
|
|
1601
|
+
exports.createSharedActivities = createSharedActivities;
|
|
1602
|
+
exports.createTaskHandler = createTaskHandler;
|
|
1603
|
+
exports.createTaskTool = createTaskTool;
|
|
1604
|
+
exports.createToolRegistry = createToolRegistry;
|
|
1605
|
+
exports.createToolRouter = createToolRouter;
|
|
1606
|
+
exports.createWriteHandler = createWriteHandler;
|
|
1607
|
+
exports.editTool = editTool;
|
|
1608
|
+
exports.fileContentToMessageContent = fileContentToMessageContent;
|
|
1609
|
+
exports.findNodeByPath = findNodeByPath;
|
|
1610
|
+
exports.flattenFileTree = flattenFileTree;
|
|
1611
|
+
exports.globTool = globTool;
|
|
1612
|
+
exports.grepTool = grepTool;
|
|
1613
|
+
exports.handleAskUserQuestionToolResult = handleAskUserQuestionToolResult;
|
|
1614
|
+
exports.hasNoOtherToolCalls = hasNoOtherToolCalls;
|
|
1615
|
+
exports.hasTaskTool = hasTaskTool;
|
|
1616
|
+
exports.invokeModel = invokeModel;
|
|
1617
|
+
exports.isPathInScope = isPathInScope;
|
|
1618
|
+
exports.isTerminalStatus = isTerminalStatus;
|
|
1619
|
+
exports.readTool = readTool;
|
|
1620
|
+
exports.withSubagentSupport = withSubagentSupport;
|
|
1621
|
+
exports.writeTool = writeTool;
|
|
1622
|
+
//# sourceMappingURL=index.js.map
|
|
1623
|
+
//# sourceMappingURL=index.js.map
|