@uncensoredcode/openbridge 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/README.md +117 -0
- package/bin/openbridge.js +10 -0
- package/package.json +85 -0
- package/packages/cli/dist/args.d.ts +30 -0
- package/packages/cli/dist/args.js +160 -0
- package/packages/cli/dist/cli.d.ts +2 -0
- package/packages/cli/dist/cli.js +9 -0
- package/packages/cli/dist/index.d.ts +26 -0
- package/packages/cli/dist/index.js +76 -0
- package/packages/runtime/dist/assistant-protocol.d.ts +34 -0
- package/packages/runtime/dist/assistant-protocol.js +121 -0
- package/packages/runtime/dist/execution/in-process.d.ts +14 -0
- package/packages/runtime/dist/execution/in-process.js +45 -0
- package/packages/runtime/dist/execution/types.d.ts +49 -0
- package/packages/runtime/dist/execution/types.js +20 -0
- package/packages/runtime/dist/index.d.ts +86 -0
- package/packages/runtime/dist/index.js +60 -0
- package/packages/runtime/dist/normalizers/index.d.ts +6 -0
- package/packages/runtime/dist/normalizers/index.js +12 -0
- package/packages/runtime/dist/normalizers/legacy-packet.d.ts +6 -0
- package/packages/runtime/dist/normalizers/legacy-packet.js +131 -0
- package/packages/runtime/dist/output-sanitizer.d.ts +23 -0
- package/packages/runtime/dist/output-sanitizer.js +78 -0
- package/packages/runtime/dist/packet-extractor.d.ts +17 -0
- package/packages/runtime/dist/packet-extractor.js +43 -0
- package/packages/runtime/dist/packet-normalizer.d.ts +21 -0
- package/packages/runtime/dist/packet-normalizer.js +47 -0
- package/packages/runtime/dist/prompt-compiler.d.ts +28 -0
- package/packages/runtime/dist/prompt-compiler.js +301 -0
- package/packages/runtime/dist/protocol.d.ts +44 -0
- package/packages/runtime/dist/protocol.js +165 -0
- package/packages/runtime/dist/provider-failure.d.ts +52 -0
- package/packages/runtime/dist/provider-failure.js +236 -0
- package/packages/runtime/dist/provider.d.ts +40 -0
- package/packages/runtime/dist/provider.js +1 -0
- package/packages/runtime/dist/runtime.d.ts +86 -0
- package/packages/runtime/dist/runtime.js +462 -0
- package/packages/runtime/dist/session-bound-provider.d.ts +52 -0
- package/packages/runtime/dist/session-bound-provider.js +366 -0
- package/packages/runtime/dist/tool-name-aliases.d.ts +5 -0
- package/packages/runtime/dist/tool-name-aliases.js +13 -0
- package/packages/runtime/dist/tools/bash.d.ts +9 -0
- package/packages/runtime/dist/tools/bash.js +157 -0
- package/packages/runtime/dist/tools/edit.d.ts +9 -0
- package/packages/runtime/dist/tools/edit.js +94 -0
- package/packages/runtime/dist/tools/index.d.ts +39 -0
- package/packages/runtime/dist/tools/index.js +27 -0
- package/packages/runtime/dist/tools/list-dir.d.ts +9 -0
- package/packages/runtime/dist/tools/list-dir.js +127 -0
- package/packages/runtime/dist/tools/read.d.ts +9 -0
- package/packages/runtime/dist/tools/read.js +56 -0
- package/packages/runtime/dist/tools/registry.d.ts +15 -0
- package/packages/runtime/dist/tools/registry.js +38 -0
- package/packages/runtime/dist/tools/runtime-path.d.ts +7 -0
- package/packages/runtime/dist/tools/runtime-path.js +22 -0
- package/packages/runtime/dist/tools/search-files.d.ts +9 -0
- package/packages/runtime/dist/tools/search-files.js +149 -0
- package/packages/runtime/dist/tools/text-file.d.ts +32 -0
- package/packages/runtime/dist/tools/text-file.js +101 -0
- package/packages/runtime/dist/tools/workspace-path.d.ts +17 -0
- package/packages/runtime/dist/tools/workspace-path.js +70 -0
- package/packages/runtime/dist/tools/write.d.ts +9 -0
- package/packages/runtime/dist/tools/write.js +59 -0
- package/packages/server/dist/bridge/bridge-model-catalog.d.ts +56 -0
- package/packages/server/dist/bridge/bridge-model-catalog.js +100 -0
- package/packages/server/dist/bridge/bridge-runtime-service.d.ts +61 -0
- package/packages/server/dist/bridge/bridge-runtime-service.js +1386 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.d.ts +127 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.js +1026 -0
- package/packages/server/dist/bridge/index.d.ts +335 -0
- package/packages/server/dist/bridge/index.js +45 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.d.ts +69 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.js +186 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.d.ts +53 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.js +973 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.d.ts +17 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.js +95 -0
- package/packages/server/dist/bridge/providers/provider-streams.d.ts +80 -0
- package/packages/server/dist/bridge/providers/provider-streams.js +844 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.d.ts +194 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.js +198 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.d.ts +30 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.js +151 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.d.ts +36 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.js +164 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.d.ts +23 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.js +548 -0
- package/packages/server/dist/bridge/stores/provider-store.d.ts +94 -0
- package/packages/server/dist/bridge/stores/provider-store.js +143 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.d.ts +7 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.js +26 -0
- package/packages/server/dist/bridge/stores/session-package-store.d.ts +286 -0
- package/packages/server/dist/bridge/stores/session-package-store.js +1527 -0
- package/packages/server/dist/bridge/stores/session-store.d.ts +120 -0
- package/packages/server/dist/bridge/stores/session-store.js +139 -0
- package/packages/server/dist/cli/index.d.ts +9 -0
- package/packages/server/dist/cli/index.js +6 -0
- package/packages/server/dist/cli/main.d.ts +2 -0
- package/packages/server/dist/cli/main.js +9 -0
- package/packages/server/dist/cli/run-bridge-server-cli.d.ts +54 -0
- package/packages/server/dist/cli/run-bridge-server-cli.js +371 -0
- package/packages/server/dist/client/bridge-api-client.d.ts +61 -0
- package/packages/server/dist/client/bridge-api-client.js +267 -0
- package/packages/server/dist/client/index.d.ts +11 -0
- package/packages/server/dist/client/index.js +11 -0
- package/packages/server/dist/config/bridge-server-config.d.ts +52 -0
- package/packages/server/dist/config/bridge-server-config.js +118 -0
- package/packages/server/dist/config/index.d.ts +20 -0
- package/packages/server/dist/config/index.js +8 -0
- package/packages/server/dist/http/bridge-api-route-context.d.ts +14 -0
- package/packages/server/dist/http/bridge-api-route-context.js +1 -0
- package/packages/server/dist/http/create-bridge-api-server.d.ts +72 -0
- package/packages/server/dist/http/create-bridge-api-server.js +225 -0
- package/packages/server/dist/http/index.d.ts +5 -0
- package/packages/server/dist/http/index.js +5 -0
- package/packages/server/dist/http/parse-request.d.ts +6 -0
- package/packages/server/dist/http/parse-request.js +27 -0
- package/packages/server/dist/http/register-bridge-api-routes.d.ts +7 -0
- package/packages/server/dist/http/register-bridge-api-routes.js +17 -0
- package/packages/server/dist/http/routes/admin-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/admin-routes.js +135 -0
- package/packages/server/dist/http/routes/chat-completions-route.d.ts +7 -0
- package/packages/server/dist/http/routes/chat-completions-route.js +49 -0
- package/packages/server/dist/http/routes/health-routes.d.ts +6 -0
- package/packages/server/dist/http/routes/health-routes.js +7 -0
- package/packages/server/dist/http/routes/message-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/message-routes.js +7 -0
- package/packages/server/dist/index.d.ts +85 -0
- package/packages/server/dist/index.js +28 -0
- package/packages/server/dist/security/bridge-auth.d.ts +9 -0
- package/packages/server/dist/security/bridge-auth.js +41 -0
- package/packages/server/dist/security/cors-policy.d.ts +5 -0
- package/packages/server/dist/security/cors-policy.js +34 -0
- package/packages/server/dist/security/index.d.ts +16 -0
- package/packages/server/dist/security/index.js +12 -0
- package/packages/server/dist/security/redact-sensitive-values.d.ts +19 -0
- package/packages/server/dist/security/redact-sensitive-values.js +67 -0
- package/packages/server/dist/shared/api-schema.d.ts +133 -0
- package/packages/server/dist/shared/api-schema.js +1 -0
- package/packages/server/dist/shared/bridge-api-error.d.ts +17 -0
- package/packages/server/dist/shared/bridge-api-error.js +19 -0
- package/packages/server/dist/shared/index.d.ts +7 -0
- package/packages/server/dist/shared/index.js +7 -0
- package/packages/server/dist/shared/output.d.ts +5 -0
- package/packages/server/dist/shared/output.js +14 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const toolsModule: {
|
|
2
|
+
createBashTool: (options: {
|
|
3
|
+
runtimeRoot: string;
|
|
4
|
+
}) => import("../index.ts").RuntimeTool;
|
|
5
|
+
createEditTool: (options: {
|
|
6
|
+
runtimeRoot: string;
|
|
7
|
+
}) => import("../index.ts").RuntimeTool;
|
|
8
|
+
createListDirTool: (options: {
|
|
9
|
+
workspaceRoot: string;
|
|
10
|
+
}) => import("../index.ts").RuntimeTool;
|
|
11
|
+
createReadTool: (options: {
|
|
12
|
+
runtimeRoot: string;
|
|
13
|
+
}) => import("../index.ts").RuntimeTool;
|
|
14
|
+
createDefaultRuntimeTools: (runtimeRoot: string) => import("../index.ts").RuntimeTool[];
|
|
15
|
+
createRuntimeTools: (input: {
|
|
16
|
+
profile?: import("./registry.ts").RuntimeToolProfile;
|
|
17
|
+
runtimeRoot: string;
|
|
18
|
+
workspaceRoot?: string;
|
|
19
|
+
}) => import("../index.ts").RuntimeTool[];
|
|
20
|
+
createSecondaryRuntimeTools: (workspaceRoot: string) => import("../index.ts").RuntimeTool[];
|
|
21
|
+
createSearchFilesTool: (options: {
|
|
22
|
+
workspaceRoot: string;
|
|
23
|
+
}) => import("../index.ts").RuntimeTool;
|
|
24
|
+
MAX_FILE_READ_BYTES: number;
|
|
25
|
+
MAX_FILE_WRITE_BYTES: number;
|
|
26
|
+
ensureRuntimeRoot: (runtimeRoot: string) => Promise<string>;
|
|
27
|
+
resolveRuntimePath: (runtimeRoot: string, requestedPath: string) => Promise<string>;
|
|
28
|
+
ensureWorkspaceRoot: (workspaceRoot: string) => Promise<string>;
|
|
29
|
+
resolveWorkspacePath: (input: {
|
|
30
|
+
workspaceRoot: string;
|
|
31
|
+
requestedPath: string;
|
|
32
|
+
kind: "read" | "write";
|
|
33
|
+
}) => Promise<import("./workspace-path.ts").ResolvedWorkspacePath>;
|
|
34
|
+
createWriteTool: (options: {
|
|
35
|
+
runtimeRoot: string;
|
|
36
|
+
}) => import("../index.ts").RuntimeTool;
|
|
37
|
+
};
|
|
38
|
+
export type { RuntimeToolProfile } from "./registry.ts";
|
|
39
|
+
export type { ResolvedWorkspacePath } from "./workspace-path.ts";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { bashModule } from "./bash.js";
|
|
2
|
+
import { editModule } from "./edit.js";
|
|
3
|
+
import { listDirModule } from "./list-dir.js";
|
|
4
|
+
import { readModule } from "./read.js";
|
|
5
|
+
import { registryModule } from "./registry.js";
|
|
6
|
+
import { runtimePathModule } from "./runtime-path.js";
|
|
7
|
+
import { searchFilesModule } from "./search-files.js";
|
|
8
|
+
import { textFileModule } from "./text-file.js";
|
|
9
|
+
import { workspacePathModule } from "./workspace-path.js";
|
|
10
|
+
import { writeModule } from "./write.js";
|
|
11
|
+
export const toolsModule = {
|
|
12
|
+
createBashTool: bashModule.createBashTool,
|
|
13
|
+
createEditTool: editModule.createEditTool,
|
|
14
|
+
createListDirTool: listDirModule.createListDirTool,
|
|
15
|
+
createReadTool: readModule.createReadTool,
|
|
16
|
+
createDefaultRuntimeTools: registryModule.createDefaultRuntimeTools,
|
|
17
|
+
createRuntimeTools: registryModule.createRuntimeTools,
|
|
18
|
+
createSecondaryRuntimeTools: registryModule.createSecondaryRuntimeTools,
|
|
19
|
+
createSearchFilesTool: searchFilesModule.createSearchFilesTool,
|
|
20
|
+
MAX_FILE_READ_BYTES: textFileModule.MAX_FILE_READ_BYTES,
|
|
21
|
+
MAX_FILE_WRITE_BYTES: textFileModule.MAX_FILE_WRITE_BYTES,
|
|
22
|
+
ensureRuntimeRoot: runtimePathModule.ensureRuntimeRoot,
|
|
23
|
+
resolveRuntimePath: runtimePathModule.resolveRuntimePath,
|
|
24
|
+
ensureWorkspaceRoot: workspacePathModule.ensureWorkspaceRoot,
|
|
25
|
+
resolveWorkspacePath: workspacePathModule.resolveWorkspacePath,
|
|
26
|
+
createWriteTool: writeModule.createWriteTool
|
|
27
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RuntimeTool } from "../execution/types.ts";
|
|
2
|
+
type ListDirToolOptions = {
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
};
|
|
5
|
+
declare function createListDirTool(options: ListDirToolOptions): RuntimeTool;
|
|
6
|
+
export declare const listDirModule: {
|
|
7
|
+
createListDirTool: typeof createListDirTool;
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { executionTypesModule } from "../execution/types.js";
|
|
4
|
+
import { textFileModule } from "./text-file.js";
|
|
5
|
+
import { workspacePathModule } from "./workspace-path.js";
|
|
6
|
+
const { ToolExecutionError } = executionTypesModule;
|
|
7
|
+
const { MAX_LIST_DIR_ENTRIES } = textFileModule;
|
|
8
|
+
const { resolveWorkspacePath } = workspacePathModule;
|
|
9
|
+
function createListDirTool(options) {
|
|
10
|
+
return {
|
|
11
|
+
definition: {
|
|
12
|
+
name: "list_dir",
|
|
13
|
+
description: "List one workspace directory without recursion. Returns compact metadata for each entry.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
include_hidden: {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
description: "Include dotfiles and dot-directories when true. Defaults to false."
|
|
20
|
+
},
|
|
21
|
+
path: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Workspace-relative directory path to inspect. Defaults to the workspace root."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: []
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async execute(args) {
|
|
30
|
+
const target = await resolveWorkspacePath({
|
|
31
|
+
workspaceRoot: options.workspaceRoot,
|
|
32
|
+
requestedPath: optionalString(args, "path") ?? ".",
|
|
33
|
+
kind: "read"
|
|
34
|
+
});
|
|
35
|
+
const includeHidden = optionalBoolean(args, "include_hidden", false);
|
|
36
|
+
try {
|
|
37
|
+
const entries = await readdir(target.absolutePath, { withFileTypes: true });
|
|
38
|
+
const visibleEntries = entries
|
|
39
|
+
.filter((entry) => includeHidden || !entry.name.startsWith("."))
|
|
40
|
+
.sort(compareDirectoryEntries);
|
|
41
|
+
const limitedEntries = visibleEntries.slice(0, MAX_LIST_DIR_ENTRIES);
|
|
42
|
+
const results = await Promise.all(limitedEntries.map(async (entry) => {
|
|
43
|
+
const entryPath = path.join(target.absolutePath, entry.name);
|
|
44
|
+
const stats = await lstat(entryPath);
|
|
45
|
+
return {
|
|
46
|
+
name: entry.name,
|
|
47
|
+
relative_path: path.relative(target.workspaceRoot, entryPath) || ".",
|
|
48
|
+
type: getEntryType(stats),
|
|
49
|
+
size: stats.size
|
|
50
|
+
};
|
|
51
|
+
}));
|
|
52
|
+
return {
|
|
53
|
+
path: target.relativePath,
|
|
54
|
+
truncated: visibleEntries.length > MAX_LIST_DIR_ENTRIES,
|
|
55
|
+
entries: results
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code === "ENOTDIR") {
|
|
60
|
+
throw new ToolExecutionError("invalid_type", `Path "${target.relativePath}" is not a directory.`);
|
|
61
|
+
}
|
|
62
|
+
if (error.code === "ENOENT") {
|
|
63
|
+
throw new ToolExecutionError("not_found", `Directory does not exist: ${target.relativePath}`);
|
|
64
|
+
}
|
|
65
|
+
throw new ToolExecutionError("io_error", `Unable to list directory "${target.relativePath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function compareDirectoryEntries(left, right) {
|
|
71
|
+
if (left.isDirectory() !== right.isDirectory()) {
|
|
72
|
+
return left.isDirectory() ? -1 : 1;
|
|
73
|
+
}
|
|
74
|
+
return compareNames(left.name, right.name);
|
|
75
|
+
}
|
|
76
|
+
function compareNames(left, right) {
|
|
77
|
+
const leftKey = left.toLowerCase();
|
|
78
|
+
const rightKey = right.toLowerCase();
|
|
79
|
+
if (leftKey < rightKey) {
|
|
80
|
+
return -1;
|
|
81
|
+
}
|
|
82
|
+
if (leftKey > rightKey) {
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (left < right) {
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
if (left > right) {
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
function getEntryType(stats) {
|
|
94
|
+
if (stats.isSymbolicLink()) {
|
|
95
|
+
return "symlink";
|
|
96
|
+
}
|
|
97
|
+
if (stats.isDirectory()) {
|
|
98
|
+
return "dir";
|
|
99
|
+
}
|
|
100
|
+
if (stats.isFile()) {
|
|
101
|
+
return "file";
|
|
102
|
+
}
|
|
103
|
+
return "other";
|
|
104
|
+
}
|
|
105
|
+
function optionalString(args, key) {
|
|
106
|
+
const value = args[key];
|
|
107
|
+
if (value === undefined) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
throw new ToolExecutionError("invalid_arguments", `${key} must be a string.`);
|
|
114
|
+
}
|
|
115
|
+
function optionalBoolean(args, key, fallback) {
|
|
116
|
+
const value = args[key];
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === "boolean") {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
throw new ToolExecutionError("invalid_arguments", `${key} must be a boolean.`);
|
|
124
|
+
}
|
|
125
|
+
export const listDirModule = {
|
|
126
|
+
createListDirTool
|
|
127
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RuntimeTool } from "../execution/types.ts";
|
|
2
|
+
type ReadToolOptions = {
|
|
3
|
+
runtimeRoot: string;
|
|
4
|
+
};
|
|
5
|
+
declare function createReadTool(options: ReadToolOptions): RuntimeTool;
|
|
6
|
+
export declare const readModule: {
|
|
7
|
+
createReadTool: typeof createReadTool;
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { executionTypesModule } from "../execution/types.js";
|
|
2
|
+
import { runtimePathModule } from "./runtime-path.js";
|
|
3
|
+
import { textFileModule } from "./text-file.js";
|
|
4
|
+
const { ToolExecutionError } = executionTypesModule;
|
|
5
|
+
const { MAX_FILE_READ_BYTES, readTextFileWithinLimit } = textFileModule;
|
|
6
|
+
const { resolveRuntimePath } = runtimePathModule;
|
|
7
|
+
function createReadTool(options) {
|
|
8
|
+
return {
|
|
9
|
+
definition: {
|
|
10
|
+
name: "read",
|
|
11
|
+
description: "Read a UTF-8 text file from the local filesystem.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
path: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Absolute path or path relative to the runtime root."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: ["path"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async execute(args) {
|
|
24
|
+
const resolvedPath = await resolveRuntimePath(options.runtimeRoot, requireString(args, "path"));
|
|
25
|
+
try {
|
|
26
|
+
const { content, bytes } = await readTextFileWithinLimit({
|
|
27
|
+
absolutePath: resolvedPath,
|
|
28
|
+
relativePath: resolvedPath,
|
|
29
|
+
maxBytes: MAX_FILE_READ_BYTES,
|
|
30
|
+
operation: "read"
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
path: resolvedPath,
|
|
34
|
+
bytes,
|
|
35
|
+
content
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof ToolExecutionError) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
throw new ToolExecutionError("io_error", `Unable to read file "${resolvedPath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function requireString(args, key) {
|
|
48
|
+
const value = args[key];
|
|
49
|
+
if (typeof value === "string" && value.trim()) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
throw new ToolExecutionError("invalid_arguments", `${key} must be a non-empty string.`);
|
|
53
|
+
}
|
|
54
|
+
export const readModule = {
|
|
55
|
+
createReadTool
|
|
56
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RuntimeTool } from "../execution/types.ts";
|
|
2
|
+
type RuntimeToolProfile = "default" | "workspace";
|
|
3
|
+
declare function createDefaultRuntimeTools(runtimeRoot: string): RuntimeTool[];
|
|
4
|
+
declare function createSecondaryRuntimeTools(workspaceRoot: string): RuntimeTool[];
|
|
5
|
+
declare function createRuntimeTools(input: {
|
|
6
|
+
profile?: RuntimeToolProfile;
|
|
7
|
+
runtimeRoot: string;
|
|
8
|
+
workspaceRoot?: string;
|
|
9
|
+
}): RuntimeTool[];
|
|
10
|
+
export declare const registryModule: {
|
|
11
|
+
createDefaultRuntimeTools: typeof createDefaultRuntimeTools;
|
|
12
|
+
createSecondaryRuntimeTools: typeof createSecondaryRuntimeTools;
|
|
13
|
+
createRuntimeTools: typeof createRuntimeTools;
|
|
14
|
+
};
|
|
15
|
+
export type { RuntimeToolProfile };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { bashModule } from "./bash.js";
|
|
2
|
+
import { editModule } from "./edit.js";
|
|
3
|
+
import { listDirModule } from "./list-dir.js";
|
|
4
|
+
import { readModule } from "./read.js";
|
|
5
|
+
import { searchFilesModule } from "./search-files.js";
|
|
6
|
+
import { writeModule } from "./write.js";
|
|
7
|
+
const { createBashTool } = bashModule;
|
|
8
|
+
const { createEditTool } = editModule;
|
|
9
|
+
const { createListDirTool } = listDirModule;
|
|
10
|
+
const { createReadTool } = readModule;
|
|
11
|
+
const { createSearchFilesTool } = searchFilesModule;
|
|
12
|
+
const { createWriteTool } = writeModule;
|
|
13
|
+
function createDefaultRuntimeTools(runtimeRoot) {
|
|
14
|
+
return [
|
|
15
|
+
createReadTool({ runtimeRoot }),
|
|
16
|
+
createWriteTool({ runtimeRoot }),
|
|
17
|
+
createEditTool({ runtimeRoot }),
|
|
18
|
+
createBashTool({ runtimeRoot })
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function createSecondaryRuntimeTools(workspaceRoot) {
|
|
22
|
+
return [createListDirTool({ workspaceRoot }), createSearchFilesTool({ workspaceRoot })];
|
|
23
|
+
}
|
|
24
|
+
function createRuntimeTools(input) {
|
|
25
|
+
const defaultTools = createDefaultRuntimeTools(input.runtimeRoot);
|
|
26
|
+
if ((input.profile ?? "default") !== "workspace") {
|
|
27
|
+
return defaultTools;
|
|
28
|
+
}
|
|
29
|
+
return [
|
|
30
|
+
...defaultTools,
|
|
31
|
+
...createSecondaryRuntimeTools(input.workspaceRoot ?? input.runtimeRoot)
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
export const registryModule = {
|
|
35
|
+
createDefaultRuntimeTools,
|
|
36
|
+
createSecondaryRuntimeTools,
|
|
37
|
+
createRuntimeTools
|
|
38
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare function ensureRuntimeRoot(runtimeRoot: string): Promise<string>;
|
|
2
|
+
declare function resolveRuntimePath(runtimeRoot: string, requestedPath: string): Promise<string>;
|
|
3
|
+
export declare const runtimePathModule: {
|
|
4
|
+
ensureRuntimeRoot: typeof ensureRuntimeRoot;
|
|
5
|
+
resolveRuntimePath: typeof resolveRuntimePath;
|
|
6
|
+
};
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdir, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { executionTypesModule } from "../execution/types.js";
|
|
4
|
+
const { ToolExecutionError } = executionTypesModule;
|
|
5
|
+
async function ensureRuntimeRoot(runtimeRoot) {
|
|
6
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
7
|
+
return realpath(runtimeRoot);
|
|
8
|
+
}
|
|
9
|
+
async function resolveRuntimePath(runtimeRoot, requestedPath) {
|
|
10
|
+
const normalizedPath = requestedPath.trim();
|
|
11
|
+
if (!normalizedPath) {
|
|
12
|
+
throw new ToolExecutionError("invalid_arguments", "path must be a non-empty string.");
|
|
13
|
+
}
|
|
14
|
+
const basePath = await ensureRuntimeRoot(runtimeRoot);
|
|
15
|
+
return path.isAbsolute(normalizedPath)
|
|
16
|
+
? path.resolve(normalizedPath)
|
|
17
|
+
: path.resolve(basePath, normalizedPath);
|
|
18
|
+
}
|
|
19
|
+
export const runtimePathModule = {
|
|
20
|
+
ensureRuntimeRoot,
|
|
21
|
+
resolveRuntimePath
|
|
22
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RuntimeTool } from "../execution/types.ts";
|
|
2
|
+
type SearchFilesToolOptions = {
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
};
|
|
5
|
+
declare function createSearchFilesTool(options: SearchFilesToolOptions): RuntimeTool;
|
|
6
|
+
export declare const searchFilesModule: {
|
|
7
|
+
createSearchFilesTool: typeof createSearchFilesTool;
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { executionTypesModule } from "../execution/types.js";
|
|
4
|
+
import { textFileModule } from "./text-file.js";
|
|
5
|
+
import { workspacePathModule } from "./workspace-path.js";
|
|
6
|
+
const { ToolExecutionError } = executionTypesModule;
|
|
7
|
+
const { MAX_SEARCH_FILE_BYTES, MAX_SEARCH_RESULTS, readTextFileWithinLimit, truncateSnippet } = textFileModule;
|
|
8
|
+
const { ensureWorkspaceRoot } = workspacePathModule;
|
|
9
|
+
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
10
|
+
".git",
|
|
11
|
+
"node_modules",
|
|
12
|
+
"dist",
|
|
13
|
+
"build",
|
|
14
|
+
".next",
|
|
15
|
+
"coverage"
|
|
16
|
+
]);
|
|
17
|
+
function createSearchFilesTool(options) {
|
|
18
|
+
return {
|
|
19
|
+
definition: {
|
|
20
|
+
name: "search_files",
|
|
21
|
+
description: "Search UTF-8 text files in the workspace for a literal query string.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
query: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Literal text to search for across workspace files."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ["query"]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
async execute(args) {
|
|
34
|
+
const query = requireQuery(args);
|
|
35
|
+
const workspaceRoot = await ensureWorkspaceRoot(options.workspaceRoot);
|
|
36
|
+
const results = [];
|
|
37
|
+
let truncated = false;
|
|
38
|
+
await walkWorkspace(workspaceRoot, workspaceRoot, query, results, () => {
|
|
39
|
+
truncated = true;
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
query,
|
|
43
|
+
truncated,
|
|
44
|
+
results
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function walkWorkspace(workspaceRoot, currentDirectory, query, results, onLimitReached) {
|
|
50
|
+
const entries = (await readdir(currentDirectory, { withFileTypes: true })).sort(compareEntries);
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isSymbolicLink()) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const entryPath = path.join(currentDirectory, entry.name);
|
|
56
|
+
const relativePath = path.relative(workspaceRoot, entryPath) || ".";
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
if (IGNORED_DIRECTORY_NAMES.has(entry.name)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
await walkWorkspace(workspaceRoot, entryPath, query, results, onLimitReached);
|
|
62
|
+
if (results.length >= MAX_SEARCH_RESULTS) {
|
|
63
|
+
onLimitReached();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.isFile()) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const matches = await searchFile(relativePath, entryPath, query);
|
|
72
|
+
for (const match of matches) {
|
|
73
|
+
results.push(match);
|
|
74
|
+
if (results.length >= MAX_SEARCH_RESULTS) {
|
|
75
|
+
onLimitReached();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function searchFile(relativePath, absolutePath, query) {
|
|
82
|
+
try {
|
|
83
|
+
const { content } = await readTextFileWithinLimit({
|
|
84
|
+
absolutePath,
|
|
85
|
+
relativePath,
|
|
86
|
+
maxBytes: MAX_SEARCH_FILE_BYTES,
|
|
87
|
+
operation: "search"
|
|
88
|
+
});
|
|
89
|
+
const lines = content.split(/\r?\n/u);
|
|
90
|
+
const matches = [];
|
|
91
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
92
|
+
if (!lines[index]?.includes(query)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
matches.push({
|
|
96
|
+
relative_path: relativePath,
|
|
97
|
+
line_number: index + 1,
|
|
98
|
+
snippet: truncateSnippet(lines[index] ?? "")
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return matches;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error instanceof ToolExecutionError &&
|
|
105
|
+
(error.code === "binary_file" ||
|
|
106
|
+
error.code === "file_too_large" ||
|
|
107
|
+
error.code === "invalid_type")) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function compareEntries(left, right) {
|
|
114
|
+
if (left.isDirectory() !== right.isDirectory()) {
|
|
115
|
+
return left.isDirectory() ? -1 : 1;
|
|
116
|
+
}
|
|
117
|
+
return compareNames(left.name, right.name);
|
|
118
|
+
}
|
|
119
|
+
function compareNames(left, right) {
|
|
120
|
+
const leftKey = left.toLowerCase();
|
|
121
|
+
const rightKey = right.toLowerCase();
|
|
122
|
+
if (leftKey < rightKey) {
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
if (leftKey > rightKey) {
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
if (left < right) {
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
if (left > right) {
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
function requireQuery(args) {
|
|
137
|
+
const value = args.query;
|
|
138
|
+
if (typeof value !== "string") {
|
|
139
|
+
throw new ToolExecutionError("invalid_arguments", "query must be a string.");
|
|
140
|
+
}
|
|
141
|
+
const query = value.trim();
|
|
142
|
+
if (!query) {
|
|
143
|
+
throw new ToolExecutionError("invalid_arguments", "query must be a non-empty string.");
|
|
144
|
+
}
|
|
145
|
+
return query;
|
|
146
|
+
}
|
|
147
|
+
export const searchFilesModule = {
|
|
148
|
+
createSearchFilesTool
|
|
149
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type ReadTextFileWithinLimitInput = {
|
|
2
|
+
absolutePath: string;
|
|
3
|
+
relativePath: string;
|
|
4
|
+
maxBytes: number;
|
|
5
|
+
operation: "read" | "search" | "write";
|
|
6
|
+
};
|
|
7
|
+
declare function readTextFileWithinLimit(input: ReadTextFileWithinLimitInput): Promise<{
|
|
8
|
+
content: string;
|
|
9
|
+
bytes: number;
|
|
10
|
+
}>;
|
|
11
|
+
declare function assertWritableTextTarget(input: {
|
|
12
|
+
absolutePath: string;
|
|
13
|
+
relativePath: string;
|
|
14
|
+
maxBytes: number;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
declare function assertTextContent(content: string, relativePath: string, maxBytes: number): number;
|
|
17
|
+
declare function writeTextFileAtomic(absolutePath: string, content: string): Promise<void>;
|
|
18
|
+
declare function truncateSnippet(line: string, maxChars?: number): string;
|
|
19
|
+
export declare const textFileModule: {
|
|
20
|
+
MAX_FILE_READ_BYTES: number;
|
|
21
|
+
MAX_FILE_WRITE_BYTES: number;
|
|
22
|
+
MAX_LIST_DIR_ENTRIES: number;
|
|
23
|
+
MAX_SEARCH_FILE_BYTES: number;
|
|
24
|
+
MAX_SEARCH_RESULTS: number;
|
|
25
|
+
MAX_SEARCH_SNIPPET_CHARS: number;
|
|
26
|
+
readTextFileWithinLimit: typeof readTextFileWithinLimit;
|
|
27
|
+
assertWritableTextTarget: typeof assertWritableTextTarget;
|
|
28
|
+
assertTextContent: typeof assertTextContent;
|
|
29
|
+
writeTextFileAtomic: typeof writeTextFileAtomic;
|
|
30
|
+
truncateSnippet: typeof truncateSnippet;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { executionTypesModule } from "../execution/types.js";
|
|
3
|
+
const { ToolExecutionError } = executionTypesModule;
|
|
4
|
+
const MAX_FILE_READ_BYTES = 64 * 1024;
|
|
5
|
+
const MAX_FILE_WRITE_BYTES = 64 * 1024;
|
|
6
|
+
const MAX_LIST_DIR_ENTRIES = 200;
|
|
7
|
+
const MAX_SEARCH_FILE_BYTES = 256 * 1024;
|
|
8
|
+
const MAX_SEARCH_RESULTS = 50;
|
|
9
|
+
const MAX_SEARCH_SNIPPET_CHARS = 240;
|
|
10
|
+
const UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
11
|
+
async function readTextFileWithinLimit(input) {
|
|
12
|
+
const details = await statExistingPath(input.absolutePath, input.relativePath);
|
|
13
|
+
if (!details.isFile()) {
|
|
14
|
+
throw new ToolExecutionError("invalid_type", `Path "${input.relativePath}" is not a file.`);
|
|
15
|
+
}
|
|
16
|
+
if (details.size > input.maxBytes) {
|
|
17
|
+
throw new ToolExecutionError("file_too_large", `File "${input.relativePath}" is ${details.size} bytes, which exceeds the ${input.maxBytes}-byte limit for ${input.operation}.`);
|
|
18
|
+
}
|
|
19
|
+
const buffer = await readFile(input.absolutePath);
|
|
20
|
+
const content = decodeTextBuffer(buffer, input.relativePath);
|
|
21
|
+
return {
|
|
22
|
+
content,
|
|
23
|
+
bytes: buffer.byteLength
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function assertWritableTextTarget(input) {
|
|
27
|
+
try {
|
|
28
|
+
await readTextFileWithinLimit({
|
|
29
|
+
absolutePath: input.absolutePath,
|
|
30
|
+
relativePath: input.relativePath,
|
|
31
|
+
maxBytes: input.maxBytes,
|
|
32
|
+
operation: "write"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof ToolExecutionError && error.code === "not_found") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function assertTextContent(content, relativePath, maxBytes) {
|
|
43
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
44
|
+
if (bytes > maxBytes) {
|
|
45
|
+
throw new ToolExecutionError("file_too_large", `Content for "${relativePath}" is ${bytes} bytes, which exceeds the ${maxBytes}-byte write limit.`);
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
async function writeTextFileAtomic(absolutePath, content) {
|
|
50
|
+
const tempPath = `${absolutePath}.bridge-runtime-tmp-${process.pid}-${Date.now()}`;
|
|
51
|
+
try {
|
|
52
|
+
await writeFile(tempPath, content, { encoding: "utf8", flag: "wx" });
|
|
53
|
+
await rename(tempPath, absolutePath);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
await unlink(tempPath).catch(() => undefined);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function truncateSnippet(line, maxChars = MAX_SEARCH_SNIPPET_CHARS) {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (trimmed.length <= maxChars) {
|
|
63
|
+
return trimmed;
|
|
64
|
+
}
|
|
65
|
+
return `${trimmed.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
66
|
+
}
|
|
67
|
+
function decodeTextBuffer(buffer, relativePath) {
|
|
68
|
+
if (buffer.includes(0)) {
|
|
69
|
+
throw new ToolExecutionError("binary_file", `File "${relativePath}" appears to be binary.`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return UTF8_DECODER.decode(buffer);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new ToolExecutionError("binary_file", `File "${relativePath}" is not valid UTF-8 text.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function statExistingPath(absolutePath, relativePath) {
|
|
79
|
+
try {
|
|
80
|
+
return await stat(absolutePath);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code === "ENOENT") {
|
|
84
|
+
throw new ToolExecutionError("not_found", `File does not exist: ${relativePath}`);
|
|
85
|
+
}
|
|
86
|
+
throw new ToolExecutionError("io_error", `Unable to inspect "${relativePath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export const textFileModule = {
|
|
90
|
+
MAX_FILE_READ_BYTES,
|
|
91
|
+
MAX_FILE_WRITE_BYTES,
|
|
92
|
+
MAX_LIST_DIR_ENTRIES,
|
|
93
|
+
MAX_SEARCH_FILE_BYTES,
|
|
94
|
+
MAX_SEARCH_RESULTS,
|
|
95
|
+
MAX_SEARCH_SNIPPET_CHARS,
|
|
96
|
+
readTextFileWithinLimit,
|
|
97
|
+
assertWritableTextTarget,
|
|
98
|
+
assertTextContent,
|
|
99
|
+
writeTextFileAtomic,
|
|
100
|
+
truncateSnippet
|
|
101
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type ResolveWorkspacePathInput = {
|
|
2
|
+
workspaceRoot: string;
|
|
3
|
+
requestedPath: string;
|
|
4
|
+
kind: "read" | "write";
|
|
5
|
+
};
|
|
6
|
+
type ResolvedWorkspacePath = {
|
|
7
|
+
absolutePath: string;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
workspaceRoot: string;
|
|
10
|
+
};
|
|
11
|
+
declare function ensureWorkspaceRoot(workspaceRoot: string): Promise<string>;
|
|
12
|
+
declare function resolveWorkspacePath(input: ResolveWorkspacePathInput): Promise<ResolvedWorkspacePath>;
|
|
13
|
+
export declare const workspacePathModule: {
|
|
14
|
+
ensureWorkspaceRoot: typeof ensureWorkspaceRoot;
|
|
15
|
+
resolveWorkspacePath: typeof resolveWorkspacePath;
|
|
16
|
+
};
|
|
17
|
+
export type { ResolvedWorkspacePath };
|