@di-code/coding-agent 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/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/models-loader.d.ts +4 -0
- package/dist/config/models-loader.d.ts.map +1 -0
- package/dist/config/models-loader.js +134 -0
- package/dist/config/models-loader.js.map +1 -0
- package/dist/config/models-types.d.ts +23 -0
- package/dist/config/models-types.d.ts.map +1 -0
- package/dist/config/models-types.js +2 -0
- package/dist/config/models-types.js.map +1 -0
- package/dist/config/settings-loader.d.ts +3 -0
- package/dist/config/settings-loader.d.ts.map +1 -0
- package/dist/config/settings-loader.js +130 -0
- package/dist/config/settings-loader.js.map +1 -0
- package/dist/config/settings-types.d.ts +21 -0
- package/dist/config/settings-types.d.ts.map +1 -0
- package/dist/config/settings-types.js +2 -0
- package/dist/config/settings-types.js.map +1 -0
- package/dist/core/context-builder.d.ts +8 -0
- package/dist/core/context-builder.d.ts.map +1 -0
- package/dist/core/context-builder.js +46 -0
- package/dist/core/context-builder.js.map +1 -0
- package/dist/core/model-catalog-store.d.ts +8 -0
- package/dist/core/model-catalog-store.d.ts.map +1 -0
- package/dist/core/model-catalog-store.js +68 -0
- package/dist/core/model-catalog-store.js.map +1 -0
- package/dist/core/model-composer.d.ts +4 -0
- package/dist/core/model-composer.d.ts.map +1 -0
- package/dist/core/model-composer.js +37 -0
- package/dist/core/model-composer.js.map +1 -0
- package/dist/core/session/session-manager.d.ts +44 -0
- package/dist/core/session/session-manager.d.ts.map +1 -0
- package/dist/core/session/session-manager.js +138 -0
- package/dist/core/session/session-manager.js.map +1 -0
- package/dist/core/session/session-storage.d.ts +11 -0
- package/dist/core/session/session-storage.d.ts.map +1 -0
- package/dist/core/session/session-storage.js +382 -0
- package/dist/core/session/session-storage.js.map +1 -0
- package/dist/core/session/types.d.ts +65 -0
- package/dist/core/session/types.d.ts.map +1 -0
- package/dist/core/session/types.js +28 -0
- package/dist/core/session/types.js.map +1 -0
- package/dist/core/session.d.ts +80 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +262 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/tools/bash.d.ts +39 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +208 -0
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit.d.ts +18 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js +107 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts +2 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-queue.js +24 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -0
- package/dist/core/tools/path-boundary.d.ts +3 -0
- package/dist/core/tools/path-boundary.d.ts.map +1 -0
- package/dist/core/tools/path-boundary.js +62 -0
- package/dist/core/tools/path-boundary.js.map +1 -0
- package/dist/core/tools/read.d.ts +17 -0
- package/dist/core/tools/read.d.ts.map +1 -0
- package/dist/core/tools/read.js +132 -0
- package/dist/core/tools/read.js.map +1 -0
- package/dist/core/tools/write.d.ts +17 -0
- package/dist/core/tools/write.d.ts.map +1 -0
- package/dist/core/tools/write.js +45 -0
- package/dist/core/tools/write.js.map +1 -0
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +27 -0
- package/dist/entry.js.map +1 -0
- package/dist/extensions/index.d.ts +5 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +3 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/runtime.d.ts +46 -0
- package/dist/extensions/runtime.d.ts.map +1 -0
- package/dist/extensions/runtime.js +179 -0
- package/dist/extensions/runtime.js.map +1 -0
- package/dist/extensions/trust.d.ts +8 -0
- package/dist/extensions/trust.d.ts.map +1 -0
- package/dist/extensions/trust.js +59 -0
- package/dist/extensions/trust.js.map +1 -0
- package/dist/extensions/types.d.ts +46 -0
- package/dist/extensions/types.d.ts.map +1 -0
- package/dist/extensions/types.js +2 -0
- package/dist/extensions/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +19 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +198 -0
- package/dist/main.js.map +1 -0
- package/dist/modes/interactive-components.d.ts +41 -0
- package/dist/modes/interactive-components.d.ts.map +1 -0
- package/dist/modes/interactive-components.js +195 -0
- package/dist/modes/interactive-components.js.map +1 -0
- package/dist/modes/interactive-layout.d.ts +20 -0
- package/dist/modes/interactive-layout.d.ts.map +1 -0
- package/dist/modes/interactive-layout.js +34 -0
- package/dist/modes/interactive-layout.js.map +1 -0
- package/dist/modes/interactive-state.d.ts +61 -0
- package/dist/modes/interactive-state.d.ts.map +1 -0
- package/dist/modes/interactive-state.js +201 -0
- package/dist/modes/interactive-state.js.map +1 -0
- package/dist/modes/interactive.d.ts +61 -0
- package/dist/modes/interactive.d.ts.map +1 -0
- package/dist/modes/interactive.js +436 -0
- package/dist/modes/interactive.js.map +1 -0
- package/dist/modes/json.d.ts +14 -0
- package/dist/modes/json.d.ts.map +1 -0
- package/dist/modes/json.js +26 -0
- package/dist/modes/json.js.map +1 -0
- package/dist/modes/print.d.ts +10 -0
- package/dist/modes/print.d.ts.map +1 -0
- package/dist/modes/print.js +28 -0
- package/dist/modes/print.js.map +1 -0
- package/dist/provider-onboarding.d.ts +13 -0
- package/dist/provider-onboarding.d.ts.map +1 -0
- package/dist/provider-onboarding.js +162 -0
- package/dist/provider-onboarding.js.map +1 -0
- package/dist/rpc/client.d.ts +38 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +153 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/rpc/index.d.ts +5 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +5 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/rpc/jsonl.d.ts +17 -0
- package/dist/rpc/jsonl.d.ts.map +1 -0
- package/dist/rpc/jsonl.js +52 -0
- package/dist/rpc/jsonl.js.map +1 -0
- package/dist/rpc/protocol.d.ts +80 -0
- package/dist/rpc/protocol.d.ts.map +1 -0
- package/dist/rpc/protocol.js +231 -0
- package/dist/rpc/protocol.js.map +1 -0
- package/dist/rpc/server.d.ts +41 -0
- package/dist/rpc/server.d.ts.map +1 -0
- package/dist/rpc/server.js +186 -0
- package/dist/rpc/server.js.map +1 -0
- package/dist/rpc-entry.d.ts +3 -0
- package/dist/rpc-entry.d.ts.map +1 -0
- package/dist/rpc-entry.js +28 -0
- package/dist/rpc-entry.js.map +1 -0
- package/dist/startup.d.ts +24 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +227 -0
- package/dist/startup.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile } from "node:fs/promises";
|
|
2
|
+
import { Type } from "@di-code/ai";
|
|
3
|
+
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
4
|
+
import { resolveAllowedFilePath } from "./path-boundary.js";
|
|
5
|
+
export const editParameters = Type.Object({
|
|
6
|
+
path: Type.String({ minLength: 1 }),
|
|
7
|
+
oldText: Type.String({ minLength: 1 }),
|
|
8
|
+
newText: Type.String(),
|
|
9
|
+
});
|
|
10
|
+
const defaultEditOperations = {
|
|
11
|
+
async readFile(filePath) {
|
|
12
|
+
return fsReadFile(filePath);
|
|
13
|
+
},
|
|
14
|
+
async writeFile(filePath, content) {
|
|
15
|
+
await fsWriteFile(filePath, content, "utf8");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
function throwIfAborted(signal) {
|
|
19
|
+
if (signal?.aborted)
|
|
20
|
+
throw new Error("Operation aborted");
|
|
21
|
+
}
|
|
22
|
+
function normalizeLineEndings(text) {
|
|
23
|
+
return text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
24
|
+
}
|
|
25
|
+
function restoreLineEndings(text, ending) {
|
|
26
|
+
return ending === "\n" ? text : text.replaceAll("\n", "\r\n");
|
|
27
|
+
}
|
|
28
|
+
function detectLineEnding(text) {
|
|
29
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
30
|
+
}
|
|
31
|
+
function countMatches(text, search) {
|
|
32
|
+
let count = 0;
|
|
33
|
+
let offset = 0;
|
|
34
|
+
while (true) {
|
|
35
|
+
const index = text.indexOf(search, offset);
|
|
36
|
+
if (index === -1)
|
|
37
|
+
return count;
|
|
38
|
+
count += 1;
|
|
39
|
+
offset = index + 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function replaceOnce(text, oldText, newText) {
|
|
43
|
+
const index = text.indexOf(oldText);
|
|
44
|
+
return `${text.slice(0, index)}${newText}${text.slice(index + oldText.length)}`;
|
|
45
|
+
}
|
|
46
|
+
function hasUtf8Bom(bytes) {
|
|
47
|
+
return bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf;
|
|
48
|
+
}
|
|
49
|
+
function containsNulByte(bytes) {
|
|
50
|
+
const sampleLength = Math.min(bytes.length, 8 * 1024);
|
|
51
|
+
for (let index = 0; index < sampleLength; index++) {
|
|
52
|
+
if (bytes[index] === 0)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
function decodeUtf8(bytes, path) {
|
|
58
|
+
const text = bytes.toString("utf8");
|
|
59
|
+
if (!Buffer.from(text, "utf8").equals(bytes))
|
|
60
|
+
throw new Error(`File is not valid UTF-8: ${path}`);
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
export function createEditTool(allowedRoot, options = {}) {
|
|
64
|
+
const operations = options.operations ?? defaultEditOperations;
|
|
65
|
+
return {
|
|
66
|
+
name: "edit",
|
|
67
|
+
description: "Replace one unique exact text block in a UTF-8 text file inside the allowed root.",
|
|
68
|
+
parameters: editParameters,
|
|
69
|
+
async execute(_toolCallId, parameters, signal) {
|
|
70
|
+
throwIfAborted(signal);
|
|
71
|
+
if (parameters.path.length === 0)
|
|
72
|
+
throw new Error("path must not be empty");
|
|
73
|
+
if (parameters.oldText.length === 0)
|
|
74
|
+
throw new Error("oldText must not be empty");
|
|
75
|
+
const absolutePath = await resolveAllowedFilePath(parameters.path, allowedRoot);
|
|
76
|
+
return withFileMutationQueue(absolutePath, async () => {
|
|
77
|
+
throwIfAborted(signal);
|
|
78
|
+
const originalBytes = await operations.readFile(absolutePath);
|
|
79
|
+
throwIfAborted(signal);
|
|
80
|
+
if (containsNulByte(originalBytes))
|
|
81
|
+
throw new Error("Binary files are not supported by edit");
|
|
82
|
+
const hasBom = hasUtf8Bom(originalBytes);
|
|
83
|
+
const originalText = decodeUtf8(originalBytes, parameters.path).replace(/^\uFEFF/, "");
|
|
84
|
+
const ending = detectLineEnding(originalText);
|
|
85
|
+
const content = normalizeLineEndings(originalText);
|
|
86
|
+
const oldText = normalizeLineEndings(parameters.oldText);
|
|
87
|
+
const newText = normalizeLineEndings(parameters.newText);
|
|
88
|
+
const matches = countMatches(content, oldText);
|
|
89
|
+
if (matches === 0)
|
|
90
|
+
throw new Error(`Text to replace was not found in ${parameters.path}`);
|
|
91
|
+
if (matches > 1)
|
|
92
|
+
throw new Error(`Text to replace is ambiguous in ${parameters.path}`);
|
|
93
|
+
const updatedText = restoreLineEndings(replaceOnce(content, oldText, newText), ending);
|
|
94
|
+
throwIfAborted(signal);
|
|
95
|
+
const currentBytes = await operations.readFile(absolutePath);
|
|
96
|
+
throwIfAborted(signal);
|
|
97
|
+
if (!currentBytes.equals(originalBytes)) {
|
|
98
|
+
throw new Error(`File changed during edit: ${parameters.path}`);
|
|
99
|
+
}
|
|
100
|
+
await operations.writeFile(absolutePath, `${hasBom ? "\uFEFF" : ""}${updatedText}`);
|
|
101
|
+
throwIfAborted(signal);
|
|
102
|
+
return [{ type: "text", text: `Successfully replaced text in ${parameters.path}` }];
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile as fsReadFile, writeFile as fsWriteFile } from \"node:fs/promises\";\nimport type { AgentTool } from \"@di-code/agent\";\nimport { type Static, type ToolResultContent, Type } from \"@di-code/ai\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveAllowedFilePath } from \"./path-boundary.ts\";\n\nexport const editParameters = Type.Object({\n\tpath: Type.String({ minLength: 1 }),\n\toldText: Type.String({ minLength: 1 }),\n\tnewText: Type.String(),\n});\n\nexport type EditParameters = Static<typeof editParameters>;\n\nexport interface EditOperations {\n\treadFile(filePath: string): Promise<Buffer>;\n\twriteFile(filePath: string, content: string): Promise<void>;\n}\n\nexport interface EditToolOptions {\n\treadonly operations?: EditOperations;\n}\n\nexport type EditTool = AgentTool<typeof editParameters>;\n\nconst defaultEditOperations: EditOperations = {\n\tasync readFile(filePath) {\n\t\treturn fsReadFile(filePath);\n\t},\n\tasync writeFile(filePath, content) {\n\t\tawait fsWriteFile(filePath, content, \"utf8\");\n\t},\n};\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n}\n\nfunction normalizeLineEndings(text: string): string {\n\treturn text.replaceAll(\"\\r\\n\", \"\\n\").replaceAll(\"\\r\", \"\\n\");\n}\n\nfunction restoreLineEndings(text: string, ending: \"\\n\" | \"\\r\\n\"): string {\n\treturn ending === \"\\n\" ? text : text.replaceAll(\"\\n\", \"\\r\\n\");\n}\n\nfunction detectLineEnding(text: string): \"\\n\" | \"\\r\\n\" {\n\treturn text.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n}\n\nfunction countMatches(text: string, search: string): number {\n\tlet count = 0;\n\tlet offset = 0;\n\twhile (true) {\n\t\tconst index = text.indexOf(search, offset);\n\t\tif (index === -1) return count;\n\t\tcount += 1;\n\t\toffset = index + 1;\n\t}\n}\n\nfunction replaceOnce(text: string, oldText: string, newText: string): string {\n\tconst index = text.indexOf(oldText);\n\treturn `${text.slice(0, index)}${newText}${text.slice(index + oldText.length)}`;\n}\n\nfunction hasUtf8Bom(bytes: Buffer): boolean {\n\treturn bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf;\n}\n\nfunction containsNulByte(bytes: Buffer): boolean {\n\tconst sampleLength = Math.min(bytes.length, 8 * 1024);\n\tfor (let index = 0; index < sampleLength; index++) {\n\t\tif (bytes[index] === 0) return true;\n\t}\n\treturn false;\n}\n\nfunction decodeUtf8(bytes: Buffer, path: string): string {\n\tconst text = bytes.toString(\"utf8\");\n\tif (!Buffer.from(text, \"utf8\").equals(bytes)) throw new Error(`File is not valid UTF-8: ${path}`);\n\treturn text;\n}\n\nexport function createEditTool(allowedRoot: string, options: EditToolOptions = {}): EditTool {\n\tconst operations = options.operations ?? defaultEditOperations;\n\n\treturn {\n\t\tname: \"edit\",\n\t\tdescription: \"Replace one unique exact text block in a UTF-8 text file inside the allowed root.\",\n\t\tparameters: editParameters,\n\t\tasync execute(_toolCallId, parameters, signal): Promise<ToolResultContent[]> {\n\t\t\tthrowIfAborted(signal);\n\t\t\tif (parameters.path.length === 0) throw new Error(\"path must not be empty\");\n\t\t\tif (parameters.oldText.length === 0) throw new Error(\"oldText must not be empty\");\n\t\t\tconst absolutePath = await resolveAllowedFilePath(parameters.path, allowedRoot);\n\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tconst originalBytes = await operations.readFile(absolutePath);\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tif (containsNulByte(originalBytes)) throw new Error(\"Binary files are not supported by edit\");\n\t\t\t\tconst hasBom = hasUtf8Bom(originalBytes);\n\t\t\t\tconst originalText = decodeUtf8(originalBytes, parameters.path).replace(/^\\uFEFF/, \"\");\n\t\t\t\tconst ending = detectLineEnding(originalText);\n\t\t\t\tconst content = normalizeLineEndings(originalText);\n\t\t\t\tconst oldText = normalizeLineEndings(parameters.oldText);\n\t\t\t\tconst newText = normalizeLineEndings(parameters.newText);\n\t\t\t\tconst matches = countMatches(content, oldText);\n\t\t\t\tif (matches === 0) throw new Error(`Text to replace was not found in ${parameters.path}`);\n\t\t\t\tif (matches > 1) throw new Error(`Text to replace is ambiguous in ${parameters.path}`);\n\n\t\t\t\tconst updatedText = restoreLineEndings(replaceOnce(content, oldText, newText), ending);\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tconst currentBytes = await operations.readFile(absolutePath);\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tif (!currentBytes.equals(originalBytes)) {\n\t\t\t\t\tthrow new Error(`File changed during edit: ${parameters.path}`);\n\t\t\t\t}\n\t\t\t\tawait operations.writeFile(absolutePath, `${hasBom ? \"\\uFEFF\" : \"\"}${updatedText}`);\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\treturn [{ type: \"text\", text: `Successfully replaced text in ${parameters.path}` }];\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-mutation-queue.d.ts","sourceRoot":"","sources":["../../../src/core/tools/file-mutation-queue.ts"],"names":[],"mappings":"AAIA,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAqBxG"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
const fileMutationQueues = new Map();
|
|
3
|
+
export async function withFileMutationQueue(filePath, operation) {
|
|
4
|
+
const resolvedPath = resolve(filePath);
|
|
5
|
+
const key = process.platform === "win32" ? resolvedPath.toLowerCase() : resolvedPath;
|
|
6
|
+
const currentQueue = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
7
|
+
let releaseNext;
|
|
8
|
+
const nextQueue = new Promise((resolveQueue) => {
|
|
9
|
+
releaseNext = resolveQueue;
|
|
10
|
+
});
|
|
11
|
+
const chainedQueue = currentQueue.then(() => nextQueue);
|
|
12
|
+
fileMutationQueues.set(key, chainedQueue);
|
|
13
|
+
await currentQueue;
|
|
14
|
+
try {
|
|
15
|
+
return await operation();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
releaseNext();
|
|
19
|
+
if (fileMutationQueues.get(key) === chainedQueue) {
|
|
20
|
+
fileMutationQueues.delete(key);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=file-mutation-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-mutation-queue.js","sourceRoot":"","sources":["../../../src/core/tools/file-mutation-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAyB,CAAC;AAE5D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAI,QAAgB,EAAE,SAA2B;IAC3F,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IACrF,MAAM,YAAY,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAEtE,IAAI,WAAwB,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,OAAO,CAAO,CAAC,YAAY,EAAE,EAAE;QACpD,WAAW,GAAG,YAAY,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAE1C,MAAM,YAAY,CAAC;IACnB,IAAI,CAAC;QACJ,OAAO,MAAM,SAAS,EAAE,CAAC;IAC1B,CAAC;YAAS,CAAC;QACV,WAAW,EAAE,CAAC;QACd,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,YAAY,EAAE,CAAC;YAClD,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;AACF,CAAC","sourcesContent":["import { resolve } from \"node:path\";\n\nconst fileMutationQueues = new Map<string, Promise<void>>();\n\nexport async function withFileMutationQueue<T>(filePath: string, operation: () => Promise<T>): Promise<T> {\n\tconst resolvedPath = resolve(filePath);\n\tconst key = process.platform === \"win32\" ? resolvedPath.toLowerCase() : resolvedPath;\n\tconst currentQueue = fileMutationQueues.get(key) ?? Promise.resolve();\n\n\tlet releaseNext!: () => void;\n\tconst nextQueue = new Promise<void>((resolveQueue) => {\n\t\treleaseNext = resolveQueue;\n\t});\n\tconst chainedQueue = currentQueue.then(() => nextQueue);\n\tfileMutationQueues.set(key, chainedQueue);\n\n\tawait currentQueue;\n\ttry {\n\t\treturn await operation();\n\t} finally {\n\t\treleaseNext();\n\t\tif (fileMutationQueues.get(key) === chainedQueue) {\n\t\t\tfileMutationQueues.delete(key);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-boundary.d.ts","sourceRoot":"","sources":["../../../src/core/tools/path-boundary.ts"],"names":[],"mappings":"AAoBA,wBAAsB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCxG;AAED,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOpG"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
function assertInsideRoot(root, target) {
|
|
4
|
+
const fromRoot = relative(root, target);
|
|
5
|
+
if (fromRoot === "" || (fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot))) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
throw new Error("Path is outside the allowed root");
|
|
9
|
+
}
|
|
10
|
+
function isMissingPathError(error) {
|
|
11
|
+
return (typeof error === "object" &&
|
|
12
|
+
error !== null &&
|
|
13
|
+
"code" in error &&
|
|
14
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
15
|
+
}
|
|
16
|
+
export async function resolveAllowedMutationPath(inputPath, allowedRoot) {
|
|
17
|
+
const rootReal = await realpath(allowedRoot);
|
|
18
|
+
const candidate = resolve(rootReal, inputPath);
|
|
19
|
+
assertInsideRoot(rootReal, candidate);
|
|
20
|
+
const missingSegments = [];
|
|
21
|
+
let current = candidate;
|
|
22
|
+
while (true) {
|
|
23
|
+
try {
|
|
24
|
+
const currentReal = await realpath(current);
|
|
25
|
+
assertInsideRoot(rootReal, currentReal);
|
|
26
|
+
const target = resolve(currentReal, ...missingSegments.reverse());
|
|
27
|
+
assertInsideRoot(rootReal, target);
|
|
28
|
+
return target;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!isMissingPathError(error))
|
|
32
|
+
throw error;
|
|
33
|
+
let entryExists = false;
|
|
34
|
+
try {
|
|
35
|
+
const entry = await lstat(current);
|
|
36
|
+
if (entry.isSymbolicLink())
|
|
37
|
+
throw new Error("Path is outside the allowed root");
|
|
38
|
+
entryExists = true;
|
|
39
|
+
}
|
|
40
|
+
catch (entryError) {
|
|
41
|
+
if (!isMissingPathError(entryError))
|
|
42
|
+
throw entryError;
|
|
43
|
+
}
|
|
44
|
+
if (entryExists)
|
|
45
|
+
continue;
|
|
46
|
+
const parent = dirname(current);
|
|
47
|
+
if (parent === current)
|
|
48
|
+
throw error;
|
|
49
|
+
missingSegments.push(basename(current));
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function resolveAllowedFilePath(inputPath, allowedRoot) {
|
|
55
|
+
const rootReal = await realpath(allowedRoot);
|
|
56
|
+
const candidate = resolve(rootReal, inputPath);
|
|
57
|
+
assertInsideRoot(rootReal, candidate);
|
|
58
|
+
const targetReal = await realpath(candidate);
|
|
59
|
+
assertInsideRoot(rootReal, targetReal);
|
|
60
|
+
return targetReal;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=path-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-boundary.js","sourceRoot":"","sources":["../../../src/core/tools/path-boundary.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, realpath } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\n\nfunction assertInsideRoot(root: string, target: string): void {\n\tconst fromRoot = relative(root, target);\n\tif (fromRoot === \"\" || (fromRoot !== \"..\" && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot))) {\n\t\treturn;\n\t}\n\tthrow new Error(\"Path is outside the allowed root\");\n}\n\nfunction isMissingPathError(error: unknown): boolean {\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t\"code\" in error &&\n\t\t(error.code === \"ENOENT\" || error.code === \"ENOTDIR\")\n\t);\n}\n\nexport async function resolveAllowedMutationPath(inputPath: string, allowedRoot: string): Promise<string> {\n\tconst rootReal = await realpath(allowedRoot);\n\tconst candidate = resolve(rootReal, inputPath);\n\tassertInsideRoot(rootReal, candidate);\n\n\tconst missingSegments: string[] = [];\n\tlet current = candidate;\n\n\twhile (true) {\n\t\ttry {\n\t\t\tconst currentReal = await realpath(current);\n\t\t\tassertInsideRoot(rootReal, currentReal);\n\t\t\tconst target = resolve(currentReal, ...missingSegments.reverse());\n\t\t\tassertInsideRoot(rootReal, target);\n\t\t\treturn target;\n\t\t} catch (error) {\n\t\t\tif (!isMissingPathError(error)) throw error;\n\t\t\tlet entryExists = false;\n\t\t\ttry {\n\t\t\t\tconst entry = await lstat(current);\n\t\t\t\tif (entry.isSymbolicLink()) throw new Error(\"Path is outside the allowed root\");\n\t\t\t\tentryExists = true;\n\t\t\t} catch (entryError) {\n\t\t\t\tif (!isMissingPathError(entryError)) throw entryError;\n\t\t\t}\n\t\t\tif (entryExists) continue;\n\t\t\tconst parent = dirname(current);\n\t\t\tif (parent === current) throw error;\n\t\t\tmissingSegments.push(basename(current));\n\t\t\tcurrent = parent;\n\t\t}\n\t}\n}\n\nexport async function resolveAllowedFilePath(inputPath: string, allowedRoot: string): Promise<string> {\n\tconst rootReal = await realpath(allowedRoot);\n\tconst candidate = resolve(rootReal, inputPath);\n\tassertInsideRoot(rootReal, candidate);\n\tconst targetReal = await realpath(candidate);\n\tassertInsideRoot(rootReal, targetReal);\n\treturn targetReal;\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentTool } from "@di-code/agent";
|
|
2
|
+
import { type Static, Type } from "@di-code/ai";
|
|
3
|
+
export declare const DEFAULT_READ_MAX_LINES = 2000;
|
|
4
|
+
export declare const DEFAULT_READ_MAX_BYTES: number;
|
|
5
|
+
export declare const readParameters: Type.TObject<{
|
|
6
|
+
path: Type.TString;
|
|
7
|
+
offset: Type.TOptional<Type.TInteger>;
|
|
8
|
+
limit: Type.TOptional<Type.TInteger>;
|
|
9
|
+
}>;
|
|
10
|
+
export type ReadParameters = Static<typeof readParameters>;
|
|
11
|
+
export interface ReadToolOptions {
|
|
12
|
+
readonly maxLines?: number;
|
|
13
|
+
readonly maxBytes?: number;
|
|
14
|
+
}
|
|
15
|
+
export type ReadTool = AgentTool<typeof readParameters>;
|
|
16
|
+
export declare function createReadTool(allowedRoot: string, options?: ReadToolOptions): ReadTool;
|
|
17
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,MAAM,EAA0B,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxE,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAC5C,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,cAAc,CAAC,CAAC;AAuGxD,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA8B3F"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Type } from "@di-code/ai";
|
|
3
|
+
import { resolveAllowedFilePath } from "./path-boundary.js";
|
|
4
|
+
export const DEFAULT_READ_MAX_LINES = 2_000;
|
|
5
|
+
export const DEFAULT_READ_MAX_BYTES = 50 * 1024;
|
|
6
|
+
export const readParameters = Type.Object({
|
|
7
|
+
path: Type.String({ minLength: 1 }),
|
|
8
|
+
offset: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
9
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
10
|
+
});
|
|
11
|
+
function assertPositiveInteger(name, value) {
|
|
12
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 1)) {
|
|
13
|
+
throw new Error(`${name} must be a positive integer`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function splitLines(text) {
|
|
17
|
+
if (text.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const lines = text.split("\n");
|
|
20
|
+
if (text.endsWith("\n"))
|
|
21
|
+
lines.pop();
|
|
22
|
+
return lines;
|
|
23
|
+
}
|
|
24
|
+
function selectUserWindow(lines, offset, limit) {
|
|
25
|
+
if (lines.length === 0) {
|
|
26
|
+
if (offset > 1)
|
|
27
|
+
throw new Error(`Offset ${offset} is beyond end of file (0 lines total)`);
|
|
28
|
+
return { content: "", startLine: 1, endLine: 0, totalLines: 0, truncatedBy: null };
|
|
29
|
+
}
|
|
30
|
+
const startIndex = offset - 1;
|
|
31
|
+
if (startIndex >= lines.length) {
|
|
32
|
+
throw new Error(`Offset ${offset} is beyond end of file (${lines.length} lines total)`);
|
|
33
|
+
}
|
|
34
|
+
const available = lines.slice(startIndex);
|
|
35
|
+
const selected = limit === undefined ? available : available.slice(0, limit);
|
|
36
|
+
const endLine = offset + selected.length - 1;
|
|
37
|
+
return {
|
|
38
|
+
content: selected.join("\n"),
|
|
39
|
+
startLine: offset,
|
|
40
|
+
endLine,
|
|
41
|
+
totalLines: lines.length,
|
|
42
|
+
truncatedBy: limit !== undefined && selected.length < available.length ? "limit" : null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function applyOutputLimits(window, maxLines, maxBytes) {
|
|
46
|
+
if (window.content === "")
|
|
47
|
+
return window;
|
|
48
|
+
const lines = window.content.split("\n");
|
|
49
|
+
const selected = [];
|
|
50
|
+
let bytes = 0;
|
|
51
|
+
let truncatedBy = null;
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
if (selected.length >= maxLines) {
|
|
54
|
+
truncatedBy = "lines";
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const separatorBytes = selected.length === 0 ? 0 : 1;
|
|
58
|
+
const nextBytes = bytes + separatorBytes + Buffer.byteLength(line, "utf8");
|
|
59
|
+
if (nextBytes > maxBytes) {
|
|
60
|
+
if (selected.length === 0)
|
|
61
|
+
throw new Error("A single line exceeds the read byte limit");
|
|
62
|
+
truncatedBy = "bytes";
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
selected.push(line);
|
|
66
|
+
bytes = nextBytes;
|
|
67
|
+
}
|
|
68
|
+
if (truncatedBy === null && selected.length < lines.length)
|
|
69
|
+
truncatedBy = "lines";
|
|
70
|
+
if (truncatedBy === null)
|
|
71
|
+
return window;
|
|
72
|
+
return {
|
|
73
|
+
...window,
|
|
74
|
+
content: selected.join("\n"),
|
|
75
|
+
endLine: window.startLine + selected.length - 1,
|
|
76
|
+
truncatedBy,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function formatByteLimit(maxBytes) {
|
|
80
|
+
if (maxBytes % 1024 === 0)
|
|
81
|
+
return `${maxBytes / 1024} KiB`;
|
|
82
|
+
return `${maxBytes} bytes`;
|
|
83
|
+
}
|
|
84
|
+
function appendContinuation(window, maxBytes) {
|
|
85
|
+
if (window.truncatedBy === null)
|
|
86
|
+
return window.content;
|
|
87
|
+
const nextOffset = window.endLine + 1;
|
|
88
|
+
if (window.truncatedBy === "limit") {
|
|
89
|
+
const remaining = window.totalLines - window.endLine;
|
|
90
|
+
return `${window.content}\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
|
|
91
|
+
}
|
|
92
|
+
const reason = window.truncatedBy === "bytes" ? ` (${formatByteLimit(maxBytes)} limit)` : "";
|
|
93
|
+
return `${window.content}\n\n[Showing lines ${window.startLine}-${window.endLine} of ${window.totalLines}${reason}. Use offset=${nextOffset} to continue.]`;
|
|
94
|
+
}
|
|
95
|
+
function containsNulByte(buffer) {
|
|
96
|
+
const sampleLength = Math.min(buffer.length, 8 * 1024);
|
|
97
|
+
for (let index = 0; index < sampleLength; index++) {
|
|
98
|
+
if (buffer[index] === 0)
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
export function createReadTool(allowedRoot, options = {}) {
|
|
104
|
+
const maxLines = options.maxLines ?? DEFAULT_READ_MAX_LINES;
|
|
105
|
+
const maxBytes = options.maxBytes ?? DEFAULT_READ_MAX_BYTES;
|
|
106
|
+
assertPositiveInteger("maxLines", maxLines);
|
|
107
|
+
assertPositiveInteger("maxBytes", maxBytes);
|
|
108
|
+
return {
|
|
109
|
+
name: "read",
|
|
110
|
+
description: "Read a UTF-8 text file inside the allowed root. Use offset and limit for large files.",
|
|
111
|
+
parameters: readParameters,
|
|
112
|
+
async execute(_toolCallId, parameters, signal) {
|
|
113
|
+
if (signal?.aborted)
|
|
114
|
+
throw new Error("Operation aborted");
|
|
115
|
+
if (parameters.path.length === 0)
|
|
116
|
+
throw new Error("path must not be empty");
|
|
117
|
+
assertPositiveInteger("offset", parameters.offset);
|
|
118
|
+
assertPositiveInteger("limit", parameters.limit);
|
|
119
|
+
const absolutePath = await resolveAllowedFilePath(parameters.path, allowedRoot);
|
|
120
|
+
if (signal?.aborted)
|
|
121
|
+
throw new Error("Operation aborted");
|
|
122
|
+
const buffer = await readFile(absolutePath);
|
|
123
|
+
if (containsNulByte(buffer)) {
|
|
124
|
+
throw new Error("Binary files are not supported by read");
|
|
125
|
+
}
|
|
126
|
+
const userWindow = selectUserWindow(splitLines(buffer.toString("utf8")), parameters.offset ?? 1, parameters.limit);
|
|
127
|
+
const boundedWindow = applyOutputLimits(userWindow, maxLines, maxBytes);
|
|
128
|
+
return [{ type: "text", text: appendContinuation(boundedWindow, maxBytes) }];
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { AgentTool } from \"@di-code/agent\";\nimport { type Static, type ToolResultContent, Type } from \"@di-code/ai\";\nimport { resolveAllowedFilePath } from \"./path-boundary.ts\";\n\nexport const DEFAULT_READ_MAX_LINES = 2_000;\nexport const DEFAULT_READ_MAX_BYTES = 50 * 1024;\n\nexport const readParameters = Type.Object({\n\tpath: Type.String({ minLength: 1 }),\n\toffset: Type.Optional(Type.Integer({ minimum: 1 })),\n\tlimit: Type.Optional(Type.Integer({ minimum: 1 })),\n});\n\nexport type ReadParameters = Static<typeof readParameters>;\n\nexport interface ReadToolOptions {\n\treadonly maxLines?: number;\n\treadonly maxBytes?: number;\n}\n\nexport type ReadTool = AgentTool<typeof readParameters>;\n\ninterface TextWindow {\n\treadonly content: string;\n\treadonly startLine: number;\n\treadonly endLine: number;\n\treadonly totalLines: number;\n\treadonly truncatedBy: \"limit\" | \"lines\" | \"bytes\" | null;\n}\n\nfunction assertPositiveInteger(name: string, value: number | undefined): void {\n\tif (value !== undefined && (!Number.isInteger(value) || value < 1)) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n}\n\nfunction splitLines(text: string): string[] {\n\tif (text.length === 0) return [];\n\tconst lines = text.split(\"\\n\");\n\tif (text.endsWith(\"\\n\")) lines.pop();\n\treturn lines;\n}\n\nfunction selectUserWindow(lines: readonly string[], offset: number, limit: number | undefined): TextWindow {\n\tif (lines.length === 0) {\n\t\tif (offset > 1) throw new Error(`Offset ${offset} is beyond end of file (0 lines total)`);\n\t\treturn { content: \"\", startLine: 1, endLine: 0, totalLines: 0, truncatedBy: null };\n\t}\n\n\tconst startIndex = offset - 1;\n\tif (startIndex >= lines.length) {\n\t\tthrow new Error(`Offset ${offset} is beyond end of file (${lines.length} lines total)`);\n\t}\n\n\tconst available = lines.slice(startIndex);\n\tconst selected = limit === undefined ? available : available.slice(0, limit);\n\tconst endLine = offset + selected.length - 1;\n\treturn {\n\t\tcontent: selected.join(\"\\n\"),\n\t\tstartLine: offset,\n\t\tendLine,\n\t\ttotalLines: lines.length,\n\t\ttruncatedBy: limit !== undefined && selected.length < available.length ? \"limit\" : null,\n\t};\n}\n\nfunction applyOutputLimits(window: TextWindow, maxLines: number, maxBytes: number): TextWindow {\n\tif (window.content === \"\") return window;\n\tconst lines = window.content.split(\"\\n\");\n\tconst selected: string[] = [];\n\tlet bytes = 0;\n\tlet truncatedBy: \"lines\" | \"bytes\" | null = null;\n\n\tfor (const line of lines) {\n\t\tif (selected.length >= maxLines) {\n\t\t\ttruncatedBy = \"lines\";\n\t\t\tbreak;\n\t\t}\n\t\tconst separatorBytes = selected.length === 0 ? 0 : 1;\n\t\tconst nextBytes = bytes + separatorBytes + Buffer.byteLength(line, \"utf8\");\n\t\tif (nextBytes > maxBytes) {\n\t\t\tif (selected.length === 0) throw new Error(\"A single line exceeds the read byte limit\");\n\t\t\ttruncatedBy = \"bytes\";\n\t\t\tbreak;\n\t\t}\n\t\tselected.push(line);\n\t\tbytes = nextBytes;\n\t}\n\n\tif (truncatedBy === null && selected.length < lines.length) truncatedBy = \"lines\";\n\tif (truncatedBy === null) return window;\n\treturn {\n\t\t...window,\n\t\tcontent: selected.join(\"\\n\"),\n\t\tendLine: window.startLine + selected.length - 1,\n\t\ttruncatedBy,\n\t};\n}\n\nfunction formatByteLimit(maxBytes: number): string {\n\tif (maxBytes % 1024 === 0) return `${maxBytes / 1024} KiB`;\n\treturn `${maxBytes} bytes`;\n}\n\nfunction appendContinuation(window: TextWindow, maxBytes: number): string {\n\tif (window.truncatedBy === null) return window.content;\n\tconst nextOffset = window.endLine + 1;\n\tif (window.truncatedBy === \"limit\") {\n\t\tconst remaining = window.totalLines - window.endLine;\n\t\treturn `${window.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t}\n\tconst reason = window.truncatedBy === \"bytes\" ? ` (${formatByteLimit(maxBytes)} limit)` : \"\";\n\treturn `${window.content}\\n\\n[Showing lines ${window.startLine}-${window.endLine} of ${window.totalLines}${reason}. Use offset=${nextOffset} to continue.]`;\n}\n\nfunction containsNulByte(buffer: Buffer): boolean {\n\tconst sampleLength = Math.min(buffer.length, 8 * 1024);\n\tfor (let index = 0; index < sampleLength; index++) {\n\t\tif (buffer[index] === 0) return true;\n\t}\n\treturn false;\n}\n\nexport function createReadTool(allowedRoot: string, options: ReadToolOptions = {}): ReadTool {\n\tconst maxLines = options.maxLines ?? DEFAULT_READ_MAX_LINES;\n\tconst maxBytes = options.maxBytes ?? DEFAULT_READ_MAX_BYTES;\n\tassertPositiveInteger(\"maxLines\", maxLines);\n\tassertPositiveInteger(\"maxBytes\", maxBytes);\n\n\treturn {\n\t\tname: \"read\",\n\t\tdescription: \"Read a UTF-8 text file inside the allowed root. Use offset and limit for large files.\",\n\t\tparameters: readParameters,\n\t\tasync execute(_toolCallId, parameters, signal): Promise<ToolResultContent[]> {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tif (parameters.path.length === 0) throw new Error(\"path must not be empty\");\n\t\t\tassertPositiveInteger(\"offset\", parameters.offset);\n\t\t\tassertPositiveInteger(\"limit\", parameters.limit);\n\t\t\tconst absolutePath = await resolveAllowedFilePath(parameters.path, allowedRoot);\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst buffer = await readFile(absolutePath);\n\t\t\tif (containsNulByte(buffer)) {\n\t\t\t\tthrow new Error(\"Binary files are not supported by read\");\n\t\t\t}\n\t\t\tconst userWindow = selectUserWindow(\n\t\t\t\tsplitLines(buffer.toString(\"utf8\")),\n\t\t\t\tparameters.offset ?? 1,\n\t\t\t\tparameters.limit,\n\t\t\t);\n\t\t\tconst boundedWindow = applyOutputLimits(userWindow, maxLines, maxBytes);\n\t\t\treturn [{ type: \"text\", text: appendContinuation(boundedWindow, maxBytes) }];\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentTool } from "@di-code/agent";
|
|
2
|
+
import { type Static, Type } from "@di-code/ai";
|
|
3
|
+
export declare const writeParameters: Type.TObject<{
|
|
4
|
+
path: Type.TString;
|
|
5
|
+
content: Type.TString;
|
|
6
|
+
}>;
|
|
7
|
+
export type WriteParameters = Static<typeof writeParameters>;
|
|
8
|
+
export interface WriteOperations {
|
|
9
|
+
mkdir(directory: string): Promise<void>;
|
|
10
|
+
writeFile(filePath: string, content: string): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface WriteToolOptions {
|
|
13
|
+
readonly operations?: WriteOperations;
|
|
14
|
+
}
|
|
15
|
+
export type WriteTool = AgentTool<typeof writeParameters>;
|
|
16
|
+
export declare function createWriteTool(allowedRoot: string, options?: WriteToolOptions): WriteTool;
|
|
17
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,MAAM,EAA0B,IAAI,EAAE,MAAM,aAAa,CAAC;AAIxE,eAAO,MAAM,eAAe;;;EAG1B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC/B,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;CACtC;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,eAAe,CAAC,CAAC;AAe1D,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAwB9F"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { Type } from "@di-code/ai";
|
|
4
|
+
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
5
|
+
import { resolveAllowedMutationPath } from "./path-boundary.js";
|
|
6
|
+
export const writeParameters = Type.Object({
|
|
7
|
+
path: Type.String({ minLength: 1 }),
|
|
8
|
+
content: Type.String(),
|
|
9
|
+
});
|
|
10
|
+
const defaultWriteOperations = {
|
|
11
|
+
async mkdir(directory) {
|
|
12
|
+
await fsMkdir(directory, { recursive: true });
|
|
13
|
+
},
|
|
14
|
+
async writeFile(filePath, content) {
|
|
15
|
+
await fsWriteFile(filePath, content, "utf8");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
function throwIfAborted(signal) {
|
|
19
|
+
if (signal?.aborted)
|
|
20
|
+
throw new Error("Operation aborted");
|
|
21
|
+
}
|
|
22
|
+
export function createWriteTool(allowedRoot, options = {}) {
|
|
23
|
+
const operations = options.operations ?? defaultWriteOperations;
|
|
24
|
+
return {
|
|
25
|
+
name: "write",
|
|
26
|
+
description: "Create or completely overwrite a UTF-8 text file inside the allowed root.",
|
|
27
|
+
parameters: writeParameters,
|
|
28
|
+
async execute(_toolCallId, parameters, signal) {
|
|
29
|
+
throwIfAborted(signal);
|
|
30
|
+
if (parameters.path.length === 0)
|
|
31
|
+
throw new Error("path must not be empty");
|
|
32
|
+
const absolutePath = await resolveAllowedMutationPath(parameters.path, allowedRoot);
|
|
33
|
+
return withFileMutationQueue(absolutePath, async () => {
|
|
34
|
+
throwIfAborted(signal);
|
|
35
|
+
await operations.mkdir(dirname(absolutePath));
|
|
36
|
+
throwIfAborted(signal);
|
|
37
|
+
await operations.writeFile(absolutePath, parameters.content);
|
|
38
|
+
throwIfAborted(signal);
|
|
39
|
+
const bytes = Buffer.byteLength(parameters.content, "utf8");
|
|
40
|
+
return [{ type: "text", text: `Successfully wrote ${bytes} bytes to ${parameters.path}` }];
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAuC,IAAI,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAEhE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;IAC1C,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACnC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC;AAeH,MAAM,sBAAsB,GAAoB;IAC/C,KAAK,CAAC,KAAK,CAAC,SAAS;QACpB,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO;QAChC,MAAM,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;CACD,CAAC;AAEF,SAAS,cAAc,CAAC,MAA+B;IACtD,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,UAA4B,EAAE;IAClF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,sBAAsB,CAAC;IAEhE,OAAO;QACN,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,2EAA2E;QACxF,UAAU,EAAE,eAAe;QAC3B,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,MAAM;YAC5C,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;YAC5E,MAAM,YAAY,GAAG,MAAM,0BAA0B,CAAC,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAEpF,OAAO,qBAAqB,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;gBACrD,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;gBAC9C,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,MAAM,UAAU,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBAC7D,cAAc,CAAC,MAAM,CAAC,CAAC;gBAEvB,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5D,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,KAAK,aAAa,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC5F,CAAC,CAAC,CAAC;QACJ,CAAC;KACD,CAAC;AACH,CAAC","sourcesContent":["import { mkdir as fsMkdir, writeFile as fsWriteFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { AgentTool } from \"@di-code/agent\";\nimport { type Static, type ToolResultContent, Type } from \"@di-code/ai\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveAllowedMutationPath } from \"./path-boundary.ts\";\n\nexport const writeParameters = Type.Object({\n\tpath: Type.String({ minLength: 1 }),\n\tcontent: Type.String(),\n});\n\nexport type WriteParameters = Static<typeof writeParameters>;\n\nexport interface WriteOperations {\n\tmkdir(directory: string): Promise<void>;\n\twriteFile(filePath: string, content: string): Promise<void>;\n}\n\nexport interface WriteToolOptions {\n\treadonly operations?: WriteOperations;\n}\n\nexport type WriteTool = AgentTool<typeof writeParameters>;\n\nconst defaultWriteOperations: WriteOperations = {\n\tasync mkdir(directory) {\n\t\tawait fsMkdir(directory, { recursive: true });\n\t},\n\tasync writeFile(filePath, content) {\n\t\tawait fsWriteFile(filePath, content, \"utf8\");\n\t},\n};\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n}\n\nexport function createWriteTool(allowedRoot: string, options: WriteToolOptions = {}): WriteTool {\n\tconst operations = options.operations ?? defaultWriteOperations;\n\n\treturn {\n\t\tname: \"write\",\n\t\tdescription: \"Create or completely overwrite a UTF-8 text file inside the allowed root.\",\n\t\tparameters: writeParameters,\n\t\tasync execute(_toolCallId, parameters, signal): Promise<ToolResultContent[]> {\n\t\t\tthrowIfAborted(signal);\n\t\t\tif (parameters.path.length === 0) throw new Error(\"path must not be empty\");\n\t\t\tconst absolutePath = await resolveAllowedMutationPath(parameters.path, allowedRoot);\n\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tawait operations.mkdir(dirname(absolutePath));\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tawait operations.writeFile(absolutePath, parameters.content);\n\t\t\t\tthrowIfAborted(signal);\n\n\t\t\t\tconst bytes = Buffer.byteLength(parameters.content, \"utf8\");\n\t\t\t\treturn [{ type: \"text\", text: `Successfully wrote ${bytes} bytes to ${parameters.path}` }];\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":""}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ProcessTerminal } from "@di-code/tui";
|
|
3
|
+
import packageMetadata from "../package.json" with { type: "json" };
|
|
4
|
+
import { runMain } from "./main.js";
|
|
5
|
+
import { runProviderOnboarding, shouldStartProviderOnboarding } from "./provider-onboarding.js";
|
|
6
|
+
import { loadStartupConfiguration, resolveStartupArgs, resolveStartupRuntime } from "./startup.js";
|
|
7
|
+
try {
|
|
8
|
+
const configuration = await loadStartupConfiguration(process.cwd());
|
|
9
|
+
process.exitCode = await runMain(resolveStartupArgs(process.argv.slice(2)), {
|
|
10
|
+
version: packageMetadata.version,
|
|
11
|
+
createRuntime: (command) => {
|
|
12
|
+
const isInteractiveTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
13
|
+
if (shouldStartProviderOnboarding(command, isInteractiveTerminal, configuration)) {
|
|
14
|
+
return runProviderOnboarding({ configuration, terminal: new ProcessTerminal() });
|
|
15
|
+
}
|
|
16
|
+
return resolveStartupRuntime(configuration.environment, configuration.providers);
|
|
17
|
+
},
|
|
18
|
+
stdout: (text) => process.stdout.write(text),
|
|
19
|
+
stderr: (text) => process.stderr.write(text),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
const message = cause instanceof Error ? cause.message : "Unknown startup error";
|
|
24
|
+
process.stderr.write(`${message}\n`);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,eAAe,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAEnG,IAAI,CAAC;IACJ,MAAM,aAAa,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,QAAQ,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC3E,OAAO,EAAE,eAAe,CAAC,OAAO;QAChC,aAAa,EAAE,CAAC,OAAO,EAAE,EAAE;YAC1B,MAAM,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnF,IAAI,6BAA6B,CAAC,OAAO,EAAE,qBAAqB,EAAE,aAAa,CAAC,EAAE,CAAC;gBAClF,OAAO,qBAAqB,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,eAAe,EAAE,EAAE,CAAC,CAAC;YAClF,CAAC;YACD,OAAO,qBAAqB,CAAC,aAAa,CAAC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;KAC5C,CAAC,CAAC;AACJ,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC;IACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACtB,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport { ProcessTerminal } from \"@di-code/tui\";\nimport packageMetadata from \"../package.json\" with { type: \"json\" };\nimport { runMain } from \"./main.ts\";\nimport { runProviderOnboarding, shouldStartProviderOnboarding } from \"./provider-onboarding.ts\";\nimport { loadStartupConfiguration, resolveStartupArgs, resolveStartupRuntime } from \"./startup.ts\";\n\ntry {\n\tconst configuration = await loadStartupConfiguration(process.cwd());\n\tprocess.exitCode = await runMain(resolveStartupArgs(process.argv.slice(2)), {\n\t\tversion: packageMetadata.version,\n\t\tcreateRuntime: (command) => {\n\t\t\tconst isInteractiveTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n\t\t\tif (shouldStartProviderOnboarding(command, isInteractiveTerminal, configuration)) {\n\t\t\t\treturn runProviderOnboarding({ configuration, terminal: new ProcessTerminal() });\n\t\t\t}\n\t\t\treturn resolveStartupRuntime(configuration.environment, configuration.providers);\n\t\t},\n\t\tstdout: (text) => process.stdout.write(text),\n\t\tstderr: (text) => process.stderr.write(text),\n\t});\n} catch (cause) {\n\tconst message = cause instanceof Error ? cause.message : \"Unknown startup error\";\n\tprocess.stderr.write(`${message}\\n`);\n\tprocess.exitCode = 1;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ExtensionDiagnostic, ExtensionDiagnosticStage, ExtensionHostOptions, ExtensionLoadOptions, ExtensionLoadResult, LoadedExtension, } from "./runtime.ts";
|
|
2
|
+
export { createExtensionHost, ExtensionHost, loadExtensions } from "./runtime.ts";
|
|
3
|
+
export { ProjectTrustManager } from "./trust.ts";
|
|
4
|
+
export type { ExtensionAPI, ExtensionCommand, ExtensionCommandContext, ExtensionContext, ExtensionEvent, ExtensionEventHandler, ExtensionEventMap, ExtensionFactory, ExtensionMode, ExtensionReadOnlyTool, SessionShutdownEvent, SessionStartEvent, } from "./types.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EACX,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,GACjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export type {\n\tExtensionDiagnostic,\n\tExtensionDiagnosticStage,\n\tExtensionHostOptions,\n\tExtensionLoadOptions,\n\tExtensionLoadResult,\n\tLoadedExtension,\n} from \"./runtime.ts\";\nexport { createExtensionHost, ExtensionHost, loadExtensions } from \"./runtime.ts\";\nexport { ProjectTrustManager } from \"./trust.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommand,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionEvent,\n\tExtensionEventHandler,\n\tExtensionEventMap,\n\tExtensionFactory,\n\tExtensionMode,\n\tExtensionReadOnlyTool,\n\tSessionShutdownEvent,\n\tSessionStartEvent,\n} from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProjectTrustManager } from "./trust.ts";
|
|
2
|
+
import type { ExtensionCommand, ExtensionContext, ExtensionEventMap, ExtensionFactory, ExtensionReadOnlyTool } from "./types.ts";
|
|
3
|
+
export interface ExtensionHostOptions {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly mode: ExtensionContext["mode"];
|
|
6
|
+
readonly projectTrusted: boolean;
|
|
7
|
+
readonly abort?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface LoadedExtension {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
}
|
|
12
|
+
export type ExtensionDiagnosticStage = "discover" | "import" | "factory" | "register";
|
|
13
|
+
export interface ExtensionDiagnostic {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly stage: ExtensionDiagnosticStage;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ExtensionLoadOptions {
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly projectTrusted?: boolean;
|
|
21
|
+
readonly trustManager?: ProjectTrustManager;
|
|
22
|
+
readonly paths?: readonly string[];
|
|
23
|
+
readonly mode?: ExtensionContext["mode"];
|
|
24
|
+
}
|
|
25
|
+
export interface ExtensionLoadResult {
|
|
26
|
+
readonly host: ExtensionHost;
|
|
27
|
+
readonly loaded: readonly LoadedExtension[];
|
|
28
|
+
readonly diagnostics: readonly ExtensionDiagnostic[];
|
|
29
|
+
}
|
|
30
|
+
export declare class ExtensionHost {
|
|
31
|
+
private readonly context;
|
|
32
|
+
private readonly commands;
|
|
33
|
+
private readonly tools;
|
|
34
|
+
private readonly handlers;
|
|
35
|
+
constructor(options: ExtensionHostOptions);
|
|
36
|
+
get extensionContext(): ExtensionContext;
|
|
37
|
+
listCommands(): readonly ExtensionCommand[];
|
|
38
|
+
listTools(): readonly ExtensionReadOnlyTool[];
|
|
39
|
+
registerExtension(_path: string, factory: ExtensionFactory): Promise<void>;
|
|
40
|
+
emit<E extends keyof ExtensionEventMap>(event: ExtensionEventMap[E]): Promise<void>;
|
|
41
|
+
runCommand(name: string, args: string): Promise<void>;
|
|
42
|
+
runTool(name: string, toolCallId: string, input: unknown): Promise<readonly import("@di-code/ai").ToolResultContent[]>;
|
|
43
|
+
}
|
|
44
|
+
export declare function loadExtensions(options: ExtensionLoadOptions): Promise<ExtensionLoadResult>;
|
|
45
|
+
export declare function createExtensionHost(options: ExtensionHostOptions): ExtensionHost;
|
|
46
|
+
//# sourceMappingURL=runtime.d.ts.map
|