c0de-agent 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli/commands/acp.d.ts +19 -0
- package/dist/cli/commands/acp.js +50 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.js +26 -0
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.js +77 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +14 -0
- package/dist/cli/commands/plugin.d.ts +11 -0
- package/dist/cli/commands/plugin.js +31 -0
- package/dist/cli/commands/serve.d.ts +18 -0
- package/dist/cli/commands/serve.js +35 -0
- package/dist/cli/commands/update.d.ts +24 -0
- package/dist/cli/commands/update.js +40 -0
- package/dist/cli/deps.d.ts +19 -0
- package/dist/cli/deps.js +57 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +149 -0
- package/dist/cli/modes/acp.d.ts +18 -0
- package/dist/cli/modes/acp.js +57 -0
- package/dist/cli/modes/print.d.ts +16 -0
- package/dist/cli/modes/print.js +30 -0
- package/dist/cli/parser.d.ts +19 -0
- package/dist/cli/parser.js +28 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +23 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +18 -0
- package/dist/core/agent.d.ts +12 -0
- package/dist/core/agent.js +95 -0
- package/dist/core/agents/builtin.d.ts +6 -0
- package/dist/core/agents/builtin.js +76 -0
- package/dist/core/agents/discovery.d.ts +6 -0
- package/dist/core/agents/discovery.js +96 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +4 -0
- package/dist/core/agents/parallel.d.ts +13 -0
- package/dist/core/agents/parallel.js +37 -0
- package/dist/core/agents/registry.d.ts +4 -0
- package/dist/core/agents/registry.js +23 -0
- package/dist/core/agents/types.d.ts +37 -0
- package/dist/core/agents/types.js +1 -0
- package/dist/core/compact.d.ts +11 -0
- package/dist/core/compact.js +31 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +84 -0
- package/dist/core/context.d.ts +23 -0
- package/dist/core/context.js +93 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +11 -0
- package/dist/core/loop.d.ts +25 -0
- package/dist/core/loop.js +651 -0
- package/dist/core/metrics.d.ts +27 -0
- package/dist/core/metrics.js +94 -0
- package/dist/core/prompt-registry.d.ts +21 -0
- package/dist/core/prompt-registry.js +200 -0
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.js +13 -0
- package/dist/core/secret.d.ts +12 -0
- package/dist/core/secret.js +51 -0
- package/dist/core/slash.d.ts +15 -0
- package/dist/core/slash.js +114 -0
- package/dist/core/steering.d.ts +8 -0
- package/dist/core/steering.js +15 -0
- package/dist/core/title.d.ts +42 -0
- package/dist/core/title.js +115 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +68 -0
- package/dist/dap/index.d.ts +6 -0
- package/dist/dap/index.js +4 -0
- package/dist/dap/protocol.d.ts +40 -0
- package/dist/dap/protocol.js +132 -0
- package/dist/dap/session.d.ts +23 -0
- package/dist/dap/session.js +130 -0
- package/dist/dap/transport.d.ts +8 -0
- package/dist/dap/transport.js +30 -0
- package/dist/dap/types.d.ts +50 -0
- package/dist/dap/types.js +2 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +39 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.js +4 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +15 -0
- package/dist/db/schema.d.ts +965 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/types.d.ts +11 -0
- package/dist/db/types.js +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/protocols/openai-compat.d.ts +139 -0
- package/dist/llm/protocols/openai-compat.js +214 -0
- package/dist/llm/protocols/utils/index.d.ts +2 -0
- package/dist/llm/protocols/utils/index.js +2 -0
- package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
- package/dist/llm/protocols/utils/lifecycle.js +46 -0
- package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
- package/dist/llm/protocols/utils/tool-stream.js +94 -0
- package/dist/llm/provider-error.d.ts +5 -0
- package/dist/llm/provider-error.js +33 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.js +191 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +107 -0
- package/dist/llm/retry.d.ts +37 -0
- package/dist/llm/retry.js +82 -0
- package/dist/llm/routing.d.ts +36 -0
- package/dist/llm/routing.js +53 -0
- package/dist/llm/schema/errors.d.ts +88 -0
- package/dist/llm/schema/errors.js +33 -0
- package/dist/llm/schema/events.d.ts +135 -0
- package/dist/llm/schema/events.js +35 -0
- package/dist/llm/schema/ids.d.ts +22 -0
- package/dist/llm/schema/ids.js +3 -0
- package/dist/llm/schema/index.d.ts +5 -0
- package/dist/llm/schema/index.js +5 -0
- package/dist/llm/schema/messages.d.ts +97 -0
- package/dist/llm/schema/messages.js +18 -0
- package/dist/llm/schema/options.d.ts +54 -0
- package/dist/llm/schema/options.js +54 -0
- package/dist/llm/token.d.ts +2 -0
- package/dist/llm/token.js +13 -0
- package/dist/llm/transport.d.ts +31 -0
- package/dist/llm/transport.js +165 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/plugins/builtin.d.ts +6 -0
- package/dist/plugins/builtin.js +54 -0
- package/dist/plugins/hooks.d.ts +3 -0
- package/dist/plugins/hooks.js +79 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/init.d.ts +23 -0
- package/dist/plugins/init.js +30 -0
- package/dist/plugins/lifecycle.d.ts +6 -0
- package/dist/plugins/lifecycle.js +74 -0
- package/dist/plugins/loader.d.ts +16 -0
- package/dist/plugins/loader.js +68 -0
- package/dist/plugins/logger.d.ts +4 -0
- package/dist/plugins/logger.js +31 -0
- package/dist/plugins/registry.d.ts +7 -0
- package/dist/plugins/registry.js +24 -0
- package/dist/plugins/types.d.ts +113 -0
- package/dist/plugins/types.js +1 -0
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.js +131 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +23 -0
- package/dist/project/project.js +87 -0
- package/dist/project/resolve.d.ts +8 -0
- package/dist/project/resolve.js +81 -0
- package/dist/server/agent-manager.d.ts +32 -0
- package/dist/server/agent-manager.js +54 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +75 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +47 -0
- package/dist/server/dev.d.ts +11 -0
- package/dist/server/dev.js +83 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +7 -0
- package/dist/server/middleware/auth.d.ts +15 -0
- package/dist/server/middleware/auth.js +29 -0
- package/dist/server/middleware/cors.d.ts +16 -0
- package/dist/server/middleware/cors.js +44 -0
- package/dist/server/middleware/error.d.ts +15 -0
- package/dist/server/middleware/error.js +11 -0
- package/dist/server/permission/interactive.d.ts +24 -0
- package/dist/server/permission/interactive.js +56 -0
- package/dist/server/permission/store.d.ts +30 -0
- package/dist/server/permission/store.js +47 -0
- package/dist/server/routes/agent.d.ts +4 -0
- package/dist/server/routes/agent.js +19 -0
- package/dist/server/routes/catalog.d.ts +42 -0
- package/dist/server/routes/catalog.js +194 -0
- package/dist/server/routes/chat.d.ts +8 -0
- package/dist/server/routes/chat.js +282 -0
- package/dist/server/routes/commands.d.ts +5 -0
- package/dist/server/routes/commands.js +17 -0
- package/dist/server/routes/config.d.ts +4 -0
- package/dist/server/routes/config.js +27 -0
- package/dist/server/routes/files.d.ts +4 -0
- package/dist/server/routes/files.js +183 -0
- package/dist/server/routes/filesystem.d.ts +29 -0
- package/dist/server/routes/filesystem.js +108 -0
- package/dist/server/routes/health.d.ts +3 -0
- package/dist/server/routes/health.js +13 -0
- package/dist/server/routes/permissions.d.ts +5 -0
- package/dist/server/routes/permissions.js +23 -0
- package/dist/server/routes/project.d.ts +4 -0
- package/dist/server/routes/project.js +51 -0
- package/dist/server/routes/provider.d.ts +16 -0
- package/dist/server/routes/provider.js +66 -0
- package/dist/server/routes/session.d.ts +4 -0
- package/dist/server/routes/session.js +130 -0
- package/dist/server/routes/tool.d.ts +4 -0
- package/dist/server/routes/tool.js +28 -0
- package/dist/server/routes/update.d.ts +14 -0
- package/dist/server/routes/update.js +49 -0
- package/dist/server/server.d.ts +43 -0
- package/dist/server/server.js +197 -0
- package/dist/server/types.d.ts +73 -0
- package/dist/server/types.js +2 -0
- package/dist/server/util/safe-path.d.ts +7 -0
- package/dist/server/util/safe-path.js +7 -0
- package/dist/session/archive.d.ts +15 -0
- package/dist/session/archive.js +129 -0
- package/dist/session/branch.d.ts +9 -0
- package/dist/session/branch.js +63 -0
- package/dist/session/compaction.d.ts +27 -0
- package/dist/session/compaction.js +192 -0
- package/dist/session/context.d.ts +16 -0
- package/dist/session/context.js +173 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +10 -0
- package/dist/session/message.d.ts +22 -0
- package/dist/session/message.js +145 -0
- package/dist/session/session.d.ts +37 -0
- package/dist/session/session.js +144 -0
- package/dist/session/snapshot.d.ts +11 -0
- package/dist/session/snapshot.js +60 -0
- package/dist/session/squash.d.ts +9 -0
- package/dist/session/squash.js +88 -0
- package/dist/session/token.d.ts +10 -0
- package/dist/session/token.js +33 -0
- package/dist/session/types.d.ts +122 -0
- package/dist/session/types.js +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/types/agent.d.ts +202 -0
- package/dist/shared/types/agent.js +1 -0
- package/dist/shared/types/base.d.ts +32 -0
- package/dist/shared/types/base.js +1 -0
- package/dist/shared/types/config.d.ts +104 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/shared/types/index.d.ts +6 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/llm.d.ts +122 -0
- package/dist/shared/types/llm.js +1 -0
- package/dist/shared/types/message.d.ts +71 -0
- package/dist/shared/types/message.js +1 -0
- package/dist/shared/types/tool.d.ts +134 -0
- package/dist/shared/types/tool.js +1 -0
- package/dist/shared/utils/path.d.ts +15 -0
- package/dist/shared/utils/path.js +23 -0
- package/dist/shared/utils.d.ts +5 -0
- package/dist/shared/utils.js +10 -0
- package/dist/tools/builtin/bash.d.ts +12 -0
- package/dist/tools/builtin/bash.js +121 -0
- package/dist/tools/builtin/dap.d.ts +23 -0
- package/dist/tools/builtin/dap.js +231 -0
- package/dist/tools/builtin/edit.d.ts +12 -0
- package/dist/tools/builtin/edit.js +151 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +120 -0
- package/dist/tools/builtin/grep.d.ts +6 -0
- package/dist/tools/builtin/grep.js +141 -0
- package/dist/tools/builtin/read.d.ts +6 -0
- package/dist/tools/builtin/read.js +73 -0
- package/dist/tools/builtin/resolvers.d.ts +12 -0
- package/dist/tools/builtin/resolvers.js +79 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +100 -0
- package/dist/tools/builtin/write.d.ts +6 -0
- package/dist/tools/builtin/write.js +36 -0
- package/dist/tools/builtin/yield.d.ts +9 -0
- package/dist/tools/builtin/yield.js +40 -0
- package/dist/tools/executor.d.ts +9 -0
- package/dist/tools/executor.js +54 -0
- package/dist/tools/hashline/index.d.ts +2 -0
- package/dist/tools/hashline/index.js +3 -0
- package/dist/tools/hashline/patch.d.ts +48 -0
- package/dist/tools/hashline/patch.js +201 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.js +55 -0
- package/dist/tools/permission.d.ts +13 -0
- package/dist/tools/permission.js +39 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +54 -0
- package/dist/tools/resolver.d.ts +15 -0
- package/dist/tools/resolver.js +47 -0
- package/dist/tools/truncate.d.ts +8 -0
- package/dist/tools/truncate.js +50 -0
- package/dist/tools/types.d.ts +101 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/validate.d.ts +8 -0
- package/dist/tools/validate.js +130 -0
- package/dist/tools/websearch/fetch.d.ts +11 -0
- package/dist/tools/websearch/fetch.js +21 -0
- package/dist/tools/websearch/index.d.ts +19 -0
- package/dist/tools/websearch/index.js +78 -0
- package/dist/tools/websearch/providers/brave.d.ts +5 -0
- package/dist/tools/websearch/providers/brave.js +53 -0
- package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
- package/dist/tools/websearch/providers/duckduckgo.js +75 -0
- package/dist/tools/websearch/providers/tavily.d.ts +11 -0
- package/dist/tools/websearch/providers/tavily.js +89 -0
- package/dist/tools/websearch/types.d.ts +51 -0
- package/dist/tools/websearch/types.js +18 -0
- package/dist/tools/websearch/websearch.d.ts +10 -0
- package/dist/tools/websearch/websearch.js +48 -0
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +6 -0
- package/dist/update/ipc.d.ts +15 -0
- package/dist/update/ipc.js +41 -0
- package/dist/update/scheduler.d.ts +34 -0
- package/dist/update/scheduler.js +57 -0
- package/dist/update/snapshot.d.ts +39 -0
- package/dist/update/snapshot.js +99 -0
- package/dist/update/updater.d.ts +48 -0
- package/dist/update/updater.js +83 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +49 -0
- package/drizzle/0000_magical_nightshade.sql +55 -0
- package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
- package/drizzle/0002_amusing_micromacro.sql +12 -0
- package/drizzle/0003_regular_purifiers.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +448 -0
- package/drizzle/meta/0001_snapshot.json +539 -0
- package/drizzle/meta/0002_snapshot.json +633 -0
- package/drizzle/meta/0003_snapshot.json +645 -0
- package/drizzle/meta/_journal.json +34 -0
- package/package.json +100 -8
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getTool } from './registry.js';
|
|
2
|
+
import { truncateOutput } from './truncate.js';
|
|
3
|
+
import { validateInput } from './validate.js';
|
|
4
|
+
/**
|
|
5
|
+
* Execute a tool by name with full pipeline:
|
|
6
|
+
* find tool → validate input → check permission → execute → truncate output.
|
|
7
|
+
*
|
|
8
|
+
* Returns the ToolResult. Never throws — all errors become { _tag: 'error' }.
|
|
9
|
+
*/
|
|
10
|
+
export async function executeTool(registry, name, input, ctx, permissionChecker) {
|
|
11
|
+
// 0. Check abort signal
|
|
12
|
+
if (ctx.abort.aborted) {
|
|
13
|
+
return { _tag: 'error', error: 'Operation aborted before execution' };
|
|
14
|
+
}
|
|
15
|
+
// 1. Find tool
|
|
16
|
+
const tool = getTool(registry, name, { config: {}, cwd: ctx.cwd });
|
|
17
|
+
if (!tool) {
|
|
18
|
+
return { _tag: 'error', error: `Tool not found: ${name}` };
|
|
19
|
+
}
|
|
20
|
+
// 2. Validate input against JSON Schema
|
|
21
|
+
const validation = validateInput(tool.parameters, input);
|
|
22
|
+
if (!validation.valid) {
|
|
23
|
+
return { _tag: 'error', error: `Invalid input for "${name}": ${validation.error}` };
|
|
24
|
+
}
|
|
25
|
+
// 3. Check permission
|
|
26
|
+
const permission = await permissionChecker.check(tool, input, ctx);
|
|
27
|
+
if (permission._tag === 'deny') {
|
|
28
|
+
return { _tag: 'error', error: `Permission denied: ${permission.reason}` };
|
|
29
|
+
}
|
|
30
|
+
if (permission._tag === 'ask') {
|
|
31
|
+
return { _tag: 'permission_required', reason: permission.reason };
|
|
32
|
+
}
|
|
33
|
+
// 4. Execute
|
|
34
|
+
try {
|
|
35
|
+
const result = await tool.execute(input, ctx);
|
|
36
|
+
// 5. Truncate large output
|
|
37
|
+
if (result._tag === 'success') {
|
|
38
|
+
const truncated = truncateOutput(result.output);
|
|
39
|
+
if (truncated.truncated) {
|
|
40
|
+
return {
|
|
41
|
+
_tag: 'truncated',
|
|
42
|
+
output: truncated.output,
|
|
43
|
+
truncated: true,
|
|
44
|
+
totalLines: truncated.totalLines,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return { _tag: 'error', error: `Tool "${name}" failed: ${message}` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
type PatchOp = {
|
|
2
|
+
_tag: 'SWAP';
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
content: string;
|
|
6
|
+
} | {
|
|
7
|
+
_tag: 'DEL';
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
} | {
|
|
11
|
+
_tag: 'INS_PRE';
|
|
12
|
+
line: number;
|
|
13
|
+
content: string;
|
|
14
|
+
} | {
|
|
15
|
+
_tag: 'INS_POST';
|
|
16
|
+
line: number;
|
|
17
|
+
content: string;
|
|
18
|
+
} | {
|
|
19
|
+
_tag: 'INS_HEAD';
|
|
20
|
+
content: string;
|
|
21
|
+
} | {
|
|
22
|
+
_tag: 'INS_TAIL';
|
|
23
|
+
content: string;
|
|
24
|
+
};
|
|
25
|
+
type ParsedPatch = {
|
|
26
|
+
path: string;
|
|
27
|
+
hash: string;
|
|
28
|
+
operations: PatchOp[];
|
|
29
|
+
};
|
|
30
|
+
type ApplyResult = {
|
|
31
|
+
_tag: 'success';
|
|
32
|
+
content: string;
|
|
33
|
+
} | {
|
|
34
|
+
_tag: 'hash_mismatch';
|
|
35
|
+
expected: string;
|
|
36
|
+
actual: string;
|
|
37
|
+
} | {
|
|
38
|
+
_tag: 'line_not_found';
|
|
39
|
+
operation: PatchOp;
|
|
40
|
+
};
|
|
41
|
+
/** 4 位 hex 内容哈希(sha256 取前 4 位)。与 session 快照哈希算法同源。 */
|
|
42
|
+
declare function computeHash(content: string): string;
|
|
43
|
+
/** 解析补丁文本为一个或多个 ParsedPatch(按 `[path#hash]` 头分块)。 */
|
|
44
|
+
declare function parsePatch(input: string): ParsedPatch[];
|
|
45
|
+
/** 应用一个补丁到文件内容。先校验哈希,再按锚点降序应用操作。 */
|
|
46
|
+
declare function applyPatch(file: string, patch: ParsedPatch): ApplyResult;
|
|
47
|
+
export type { ApplyResult, ParsedPatch, PatchOp };
|
|
48
|
+
export { applyPatch, computeHash, parsePatch };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
// ── computeHash ──────────────────────────────────────────
|
|
3
|
+
/** 4 位 hex 内容哈希(sha256 取前 4 位)。与 session 快照哈希算法同源。 */
|
|
4
|
+
function computeHash(content) {
|
|
5
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 4);
|
|
6
|
+
}
|
|
7
|
+
// ── parsePatch ───────────────────────────────────────────
|
|
8
|
+
const HEADER_RE = /^\[(.+?)#([0-9a-fA-F]+)\]\s*$/;
|
|
9
|
+
/** 解析一个 `[path#hash]` 块内的操作序列。lines 为该块头之后的行。 */
|
|
10
|
+
function parseOps(lines) {
|
|
11
|
+
const ops = [];
|
|
12
|
+
let i = 0;
|
|
13
|
+
while (i < lines.length) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
if (!line || line.trim() === '') {
|
|
16
|
+
i++;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const tokens = line.split(/\s+/);
|
|
20
|
+
const head = tokens[0];
|
|
21
|
+
if (!head)
|
|
22
|
+
throw new Error('hashline: empty operation line');
|
|
23
|
+
// 收集到下一个分隔符 `---`(或块尾)的内容行
|
|
24
|
+
const collectContent = () => {
|
|
25
|
+
const body = [];
|
|
26
|
+
let j = i + 1;
|
|
27
|
+
while (j < lines.length && lines[j] !== '---') {
|
|
28
|
+
body.push(lines[j] ?? '');
|
|
29
|
+
j++;
|
|
30
|
+
}
|
|
31
|
+
return { content: body.join('\n'), next: j + 1 };
|
|
32
|
+
};
|
|
33
|
+
// 无内容体(DEL)也吃掉到 `---`
|
|
34
|
+
const skipToSeparator = () => {
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
while (j < lines.length && lines[j] !== '---')
|
|
37
|
+
j++;
|
|
38
|
+
return j + 1;
|
|
39
|
+
};
|
|
40
|
+
if (head === 'SWAP') {
|
|
41
|
+
const [s, e] = parseRange(tokens[1]);
|
|
42
|
+
const { content, next } = collectContent();
|
|
43
|
+
ops.push({ _tag: 'SWAP', start: s, end: e ?? s, content });
|
|
44
|
+
i = next;
|
|
45
|
+
}
|
|
46
|
+
else if (head === 'DEL') {
|
|
47
|
+
const [s, e] = parseRange(tokens[1]);
|
|
48
|
+
ops.push({ _tag: 'DEL', start: s, end: e ?? s });
|
|
49
|
+
i = skipToSeparator();
|
|
50
|
+
}
|
|
51
|
+
else if (head === 'INS.PRE') {
|
|
52
|
+
const { content, next } = collectContent();
|
|
53
|
+
ops.push({ _tag: 'INS_PRE', line: Number(tokens[1]), content });
|
|
54
|
+
i = next;
|
|
55
|
+
}
|
|
56
|
+
else if (head === 'INS.POST') {
|
|
57
|
+
const { content, next } = collectContent();
|
|
58
|
+
ops.push({ _tag: 'INS_POST', line: Number(tokens[1]), content });
|
|
59
|
+
i = next;
|
|
60
|
+
}
|
|
61
|
+
else if (head === 'INS.HEAD') {
|
|
62
|
+
const { content, next } = collectContent();
|
|
63
|
+
ops.push({ _tag: 'INS_HEAD', content });
|
|
64
|
+
i = next;
|
|
65
|
+
}
|
|
66
|
+
else if (head === 'INS.TAIL') {
|
|
67
|
+
const { content, next } = collectContent();
|
|
68
|
+
ops.push({ _tag: 'INS_TAIL', content });
|
|
69
|
+
i = next;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`hashline: unknown operation "${head}"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return ops;
|
|
76
|
+
}
|
|
77
|
+
/** 解析 `start` 或 `start-end`,返回 [start, end?](1-indexed)。 */
|
|
78
|
+
function parseRange(spec) {
|
|
79
|
+
if (!spec)
|
|
80
|
+
throw new Error('hashline: missing line range');
|
|
81
|
+
if (spec.includes('-')) {
|
|
82
|
+
const [a, b] = spec.split('-');
|
|
83
|
+
return [Number(a), Number(b)];
|
|
84
|
+
}
|
|
85
|
+
return [Number(spec), undefined];
|
|
86
|
+
}
|
|
87
|
+
/** 解析补丁文本为一个或多个 ParsedPatch(按 `[path#hash]` 头分块)。 */
|
|
88
|
+
function parsePatch(input) {
|
|
89
|
+
const lines = input.split('\n');
|
|
90
|
+
const patches = [];
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < lines.length) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
if (!line || line.trim() === '') {
|
|
95
|
+
i++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const m = HEADER_RE.exec(line);
|
|
99
|
+
if (!m)
|
|
100
|
+
throw new Error(`hashline: malformed header "${line}"`);
|
|
101
|
+
const path = m[1] ?? '';
|
|
102
|
+
const hash = m[2] ?? '';
|
|
103
|
+
const block = lines.slice(i + 1);
|
|
104
|
+
const endIdx = block.indexOf(''); // 块以空行或文件尾分隔(其实下一头才分隔)
|
|
105
|
+
// 操作持续到下一个头或文件尾:找到下一个 HEADER_RE 匹配行
|
|
106
|
+
let nextHead = block.length;
|
|
107
|
+
for (let k = 0; k < block.length; k++) {
|
|
108
|
+
if (HEADER_RE.test((block[k] ?? '').trim())) {
|
|
109
|
+
nextHead = k;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
void endIdx;
|
|
114
|
+
const opsLines = block.slice(0, nextHead);
|
|
115
|
+
patches.push({ path, hash, operations: parseOps(opsLines) });
|
|
116
|
+
i = i + 1 + nextHead;
|
|
117
|
+
}
|
|
118
|
+
return patches;
|
|
119
|
+
}
|
|
120
|
+
// ── applyPatch ───────────────────────────────────────────
|
|
121
|
+
/** 每个操作基于原文件的"锚点行号",用于排序使从后往前应用。 */
|
|
122
|
+
function anchor(op, lineCount) {
|
|
123
|
+
switch (op._tag) {
|
|
124
|
+
case 'SWAP':
|
|
125
|
+
case 'DEL':
|
|
126
|
+
return op.start;
|
|
127
|
+
case 'INS_PRE':
|
|
128
|
+
return op.line;
|
|
129
|
+
case 'INS_POST':
|
|
130
|
+
return op.line + 1;
|
|
131
|
+
case 'INS_HEAD':
|
|
132
|
+
return 1;
|
|
133
|
+
case 'INS_TAIL':
|
|
134
|
+
return lineCount + 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** 校验操作的行号范围是否落在 [1, lineCount]。 */
|
|
138
|
+
function inBounds(op, lineCount) {
|
|
139
|
+
switch (op._tag) {
|
|
140
|
+
case 'SWAP':
|
|
141
|
+
case 'DEL':
|
|
142
|
+
return op.start >= 1 && op.end <= lineCount && op.start <= op.end;
|
|
143
|
+
case 'INS_PRE':
|
|
144
|
+
case 'INS_POST':
|
|
145
|
+
return op.line >= 1 && op.line <= lineCount;
|
|
146
|
+
case 'INS_HEAD':
|
|
147
|
+
case 'INS_TAIL':
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** 应用一个补丁到文件内容。先校验哈希,再按锚点降序应用操作。 */
|
|
152
|
+
function applyPatch(file, patch) {
|
|
153
|
+
const actual = computeHash(file);
|
|
154
|
+
if (actual !== patch.hash) {
|
|
155
|
+
return { _tag: 'hash_mismatch', expected: patch.hash, actual };
|
|
156
|
+
}
|
|
157
|
+
// 末尾换行保留策略:按 \n 拆分,末尾空串代表文件以换行结尾。
|
|
158
|
+
const hadTrailingNewline = file.endsWith('\n');
|
|
159
|
+
const src = hadTrailingNewline ? file.slice(0, -1) : file;
|
|
160
|
+
const lines = src.split('\n');
|
|
161
|
+
const lineCount = lines.length;
|
|
162
|
+
// 先全部校验范围,任一越界即 line_not_found(保持原文件不变)
|
|
163
|
+
for (const op of patch.operations) {
|
|
164
|
+
if (!inBounds(op, lineCount)) {
|
|
165
|
+
return { _tag: 'line_not_found', operation: op };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 按锚点降序应用:高行号先改,低行号锚点不受影响
|
|
169
|
+
const ordered = [...patch.operations].sort((a, b) => {
|
|
170
|
+
const lineCountFinal = lineCount;
|
|
171
|
+
return anchor(b, lineCountFinal) - anchor(a, lineCountFinal);
|
|
172
|
+
});
|
|
173
|
+
for (const op of ordered) {
|
|
174
|
+
const contentLines = 'content' in op && op.content !== '' ? op.content.split('\n') : [];
|
|
175
|
+
switch (op._tag) {
|
|
176
|
+
case 'SWAP':
|
|
177
|
+
lines.splice(op.start - 1, op.end - op.start + 1, ...contentLines);
|
|
178
|
+
break;
|
|
179
|
+
case 'DEL':
|
|
180
|
+
lines.splice(op.start - 1, op.end - op.start + 1);
|
|
181
|
+
break;
|
|
182
|
+
case 'INS_PRE':
|
|
183
|
+
lines.splice(op.line - 1, 0, ...contentLines);
|
|
184
|
+
break;
|
|
185
|
+
case 'INS_POST':
|
|
186
|
+
lines.splice(op.line, 0, ...contentLines);
|
|
187
|
+
break;
|
|
188
|
+
case 'INS_HEAD':
|
|
189
|
+
lines.unshift(...contentLines);
|
|
190
|
+
break;
|
|
191
|
+
case 'INS_TAIL':
|
|
192
|
+
lines.push(...contentLines);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let result = lines.join('\n');
|
|
197
|
+
if (hadTrailingNewline)
|
|
198
|
+
result += '\n';
|
|
199
|
+
return { _tag: 'success', content: result };
|
|
200
|
+
}
|
|
201
|
+
export { applyPatch, computeHash, parsePatch };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type { JSONSchema } from '../shared/types/base.js';
|
|
2
|
+
export type { ChatTool } from '../shared/types/llm.js';
|
|
3
|
+
export type { ToolContext, ToolDef, ToolExecutor, ToolMode, ToolPermission, ToolResult, } from '../shared/types/tool.js';
|
|
4
|
+
export { bashTool } from './builtin/bash.js';
|
|
5
|
+
export { dapTools } from './builtin/dap.js';
|
|
6
|
+
export { editTool } from './builtin/edit.js';
|
|
7
|
+
export { globTool, globToRegex } from './builtin/glob.js';
|
|
8
|
+
export { grepTool } from './builtin/grep.js';
|
|
9
|
+
export { readTool } from './builtin/read.js';
|
|
10
|
+
export { createDefaultURLRegistry, createFileResolver, createSkillResolver, } from './builtin/resolvers.js';
|
|
11
|
+
export { taskTool } from './builtin/task.js';
|
|
12
|
+
export { writeTool } from './builtin/write.js';
|
|
13
|
+
export { yieldTool } from './builtin/yield.js';
|
|
14
|
+
export { executeTool } from './executor.js';
|
|
15
|
+
export { autoAllowChecker, createPermissionChecker } from './permission.js';
|
|
16
|
+
export { createToolRegistry, getTool, getToolSchemas, listTools, registerTool, registerToolFactory, } from './registry.js';
|
|
17
|
+
export { DEFAULT_TRUNCATE_OPTIONS, truncateOutput } from './truncate.js';
|
|
18
|
+
export type { BashInput, EditInput, GlobInput, GrepInput, GrepMatch, PermissionChecker, PermissionResult, ReadInput, ToolFactory, ToolFactoryContext, ToolRegistry, TruncateOptions, TruncateResult, ValidationResult, WriteInput, } from './types.js';
|
|
19
|
+
export { validateInput } from './validate.js';
|
|
20
|
+
export { formatForLLM, resolveProvider, runWebSearch } from './websearch/index.js';
|
|
21
|
+
export type { WebSearchProvider, WebSearchProviderId, WebSearchResponse, WebSearchSource, } from './websearch/types.js';
|
|
22
|
+
export { createWebSearchTool } from './websearch/websearch.js';
|
|
23
|
+
import type { Config } from '../shared/types/config.js';
|
|
24
|
+
/**
|
|
25
|
+
* Create a registry pre-loaded with all builtin tools:
|
|
26
|
+
* read, write, edit, glob, grep, bash, task, websearch, and the debug_* set.
|
|
27
|
+
*
|
|
28
|
+
* @param config 可选配置;websearch 工具按 config.websearch 构造。省略时用 DEFAULT_CONFIG。
|
|
29
|
+
*/
|
|
30
|
+
export declare function createDefaultRegistry(config?: Config): import("./types.js").ToolRegistry;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Tools package: tool registry, executor, validation, truncation, and builtin tools.
|
|
2
|
+
export { bashTool } from './builtin/bash.js';
|
|
3
|
+
export { dapTools } from './builtin/dap.js';
|
|
4
|
+
export { editTool } from './builtin/edit.js';
|
|
5
|
+
export { globTool, globToRegex } from './builtin/glob.js';
|
|
6
|
+
export { grepTool } from './builtin/grep.js';
|
|
7
|
+
export { readTool } from './builtin/read.js';
|
|
8
|
+
// ── Builtin tools ───────────────────────────────────────────
|
|
9
|
+
export { createDefaultURLRegistry, createFileResolver, createSkillResolver, } from './builtin/resolvers.js';
|
|
10
|
+
export { taskTool } from './builtin/task.js';
|
|
11
|
+
export { writeTool } from './builtin/write.js';
|
|
12
|
+
export { yieldTool } from './builtin/yield.js';
|
|
13
|
+
export { executeTool } from './executor.js';
|
|
14
|
+
export { autoAllowChecker, createPermissionChecker } from './permission.js';
|
|
15
|
+
// ── Framework ───────────────────────────────────────────────
|
|
16
|
+
export { createToolRegistry, getTool, getToolSchemas, listTools, registerTool, registerToolFactory, } from './registry.js';
|
|
17
|
+
export { DEFAULT_TRUNCATE_OPTIONS, truncateOutput } from './truncate.js';
|
|
18
|
+
export { validateInput } from './validate.js';
|
|
19
|
+
// ── Websearch ───────────────────────────────────────────────
|
|
20
|
+
export { formatForLLM, resolveProvider, runWebSearch } from './websearch/index.js';
|
|
21
|
+
export { createWebSearchTool } from './websearch/websearch.js';
|
|
22
|
+
// ── Default registry ────────────────────────────────────────
|
|
23
|
+
import { DEFAULT_CONFIG } from '../core/config.js';
|
|
24
|
+
import { bashTool } from './builtin/bash.js';
|
|
25
|
+
import { dapTools } from './builtin/dap.js';
|
|
26
|
+
import { editTool } from './builtin/edit.js';
|
|
27
|
+
import { globTool } from './builtin/glob.js';
|
|
28
|
+
import { grepTool } from './builtin/grep.js';
|
|
29
|
+
import { readTool } from './builtin/read.js';
|
|
30
|
+
import { taskTool } from './builtin/task.js';
|
|
31
|
+
import { writeTool } from './builtin/write.js';
|
|
32
|
+
import { yieldTool } from './builtin/yield.js';
|
|
33
|
+
import { createToolRegistry, registerTool } from './registry.js';
|
|
34
|
+
import { createWebSearchTool } from './websearch/websearch.js';
|
|
35
|
+
/**
|
|
36
|
+
* Create a registry pre-loaded with all builtin tools:
|
|
37
|
+
* read, write, edit, glob, grep, bash, task, websearch, and the debug_* set.
|
|
38
|
+
*
|
|
39
|
+
* @param config 可选配置;websearch 工具按 config.websearch 构造。省略时用 DEFAULT_CONFIG。
|
|
40
|
+
*/
|
|
41
|
+
export function createDefaultRegistry(config = DEFAULT_CONFIG) {
|
|
42
|
+
const reg = createToolRegistry();
|
|
43
|
+
registerTool(reg, readTool);
|
|
44
|
+
registerTool(reg, writeTool);
|
|
45
|
+
registerTool(reg, editTool);
|
|
46
|
+
registerTool(reg, globTool);
|
|
47
|
+
registerTool(reg, grepTool);
|
|
48
|
+
registerTool(reg, bashTool);
|
|
49
|
+
registerTool(reg, taskTool);
|
|
50
|
+
registerTool(reg, yieldTool);
|
|
51
|
+
registerTool(reg, createWebSearchTool(config.websearch));
|
|
52
|
+
for (const tool of dapTools)
|
|
53
|
+
registerTool(reg, tool);
|
|
54
|
+
return reg;
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PermissionChecker } from './types.js';
|
|
2
|
+
/** Permission checker configuration. */
|
|
3
|
+
type PermissionConfig = {
|
|
4
|
+
/** Tool names that are always allowed, regardless of their declared permission. */
|
|
5
|
+
alwaysAllow?: string[];
|
|
6
|
+
/** Tool names that are always denied, regardless of their declared permission. */
|
|
7
|
+
alwaysDeny?: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Default checker: allows auto, asks for ask, denies deny. No persistent state. */
|
|
10
|
+
export declare const autoAllowChecker: PermissionChecker;
|
|
11
|
+
/** Create a permission checker with configurable allow/deny lists. */
|
|
12
|
+
export declare function createPermissionChecker(config: PermissionConfig): PermissionChecker;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
function checkPermission(tool, _input) {
|
|
3
|
+
switch (tool.permission) {
|
|
4
|
+
case 'auto':
|
|
5
|
+
return { _tag: 'allow' };
|
|
6
|
+
case 'deny':
|
|
7
|
+
return { _tag: 'deny', reason: `Tool "${tool.name}" is disabled` };
|
|
8
|
+
case 'ask':
|
|
9
|
+
return {
|
|
10
|
+
_tag: 'ask',
|
|
11
|
+
reason: `Tool "${tool.name}" requires confirmation`,
|
|
12
|
+
toolCallId: randomUUID(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Default checker: allows auto, asks for ask, denies deny. No persistent state. */
|
|
17
|
+
export const autoAllowChecker = {
|
|
18
|
+
check: async (_tool, _input, _ctx) => {
|
|
19
|
+
return checkPermission(_tool, _input);
|
|
20
|
+
},
|
|
21
|
+
confirm: (_toolCallId, _approved) => { },
|
|
22
|
+
};
|
|
23
|
+
/** Create a permission checker with configurable allow/deny lists. */
|
|
24
|
+
export function createPermissionChecker(config) {
|
|
25
|
+
const allowSet = new Set(config.alwaysAllow ?? []);
|
|
26
|
+
const denySet = new Set(config.alwaysDeny ?? []);
|
|
27
|
+
return {
|
|
28
|
+
check: async (tool, _input, _ctx) => {
|
|
29
|
+
if (denySet.has(tool.name)) {
|
|
30
|
+
return { _tag: 'deny', reason: `Tool "${tool.name}" is disabled by configuration` };
|
|
31
|
+
}
|
|
32
|
+
if (allowSet.has(tool.name)) {
|
|
33
|
+
return { _tag: 'allow' };
|
|
34
|
+
}
|
|
35
|
+
return checkPermission(tool, _input);
|
|
36
|
+
},
|
|
37
|
+
confirm: (_toolCallId, _approved) => { },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChatTool } from '../shared/types/llm.js';
|
|
2
|
+
import type { ToolDef } from '../shared/types/tool.js';
|
|
3
|
+
import type { ToolFactory, ToolFactoryContext, ToolRegistry } from './types.js';
|
|
4
|
+
/** Create an empty tool registry. */
|
|
5
|
+
export declare function createToolRegistry(): ToolRegistry;
|
|
6
|
+
/** Register a fully-constructed tool definition (eager). */
|
|
7
|
+
export declare function registerTool(registry: ToolRegistry, tool: ToolDef): void;
|
|
8
|
+
/** Register a lazy factory that constructs the tool on first access. */
|
|
9
|
+
export declare function registerToolFactory(registry: ToolRegistry, name: string, factory: ToolFactory): void;
|
|
10
|
+
/** Get a tool by name. Triggers and caches lazy factories. Returns undefined if not found. */
|
|
11
|
+
export declare function getTool(registry: ToolRegistry, name: string, ctx?: ToolFactoryContext): ToolDef | undefined;
|
|
12
|
+
/** List all tools. Triggers and caches all lazy factories. */
|
|
13
|
+
export declare function listTools(registry: ToolRegistry, ctx?: ToolFactoryContext): ToolDef[];
|
|
14
|
+
/** Convert registered tools to ChatTool[] for sending to the LLM. */
|
|
15
|
+
export declare function getToolSchemas(registry: ToolRegistry, ctx?: ToolFactoryContext): ChatTool[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Create an empty tool registry. */
|
|
2
|
+
export function createToolRegistry() {
|
|
3
|
+
return { tools: new Map(), factories: new Map() };
|
|
4
|
+
}
|
|
5
|
+
/** Register a fully-constructed tool definition (eager). */
|
|
6
|
+
export function registerTool(registry, tool) {
|
|
7
|
+
registry.tools.set(tool.name, tool);
|
|
8
|
+
}
|
|
9
|
+
/** Register a lazy factory that constructs the tool on first access. */
|
|
10
|
+
export function registerToolFactory(registry, name, factory) {
|
|
11
|
+
registry.factories.set(name, factory);
|
|
12
|
+
}
|
|
13
|
+
/** Get a tool by name. Triggers and caches lazy factories. Returns undefined if not found. */
|
|
14
|
+
export function getTool(registry, name, ctx) {
|
|
15
|
+
// Check eager tools first
|
|
16
|
+
const eager = registry.tools.get(name);
|
|
17
|
+
if (eager)
|
|
18
|
+
return eager;
|
|
19
|
+
// Check lazy factories
|
|
20
|
+
const factory = registry.factories.get(name);
|
|
21
|
+
if (factory && ctx) {
|
|
22
|
+
const tool = factory(ctx);
|
|
23
|
+
if (tool) {
|
|
24
|
+
registry.tools.set(name, tool); // cache
|
|
25
|
+
registry.factories.delete(name);
|
|
26
|
+
return tool;
|
|
27
|
+
}
|
|
28
|
+
// Factory returned null → remove
|
|
29
|
+
registry.factories.delete(name);
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/** List all tools. Triggers and caches all lazy factories. */
|
|
34
|
+
export function listTools(registry, ctx) {
|
|
35
|
+
// Materialize factories if context is provided
|
|
36
|
+
if (ctx) {
|
|
37
|
+
for (const [name, factory] of registry.factories) {
|
|
38
|
+
const tool = factory(ctx);
|
|
39
|
+
if (tool) {
|
|
40
|
+
registry.tools.set(name, tool);
|
|
41
|
+
}
|
|
42
|
+
registry.factories.delete(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return Array.from(registry.tools.values());
|
|
46
|
+
}
|
|
47
|
+
/** Convert registered tools to ChatTool[] for sending to the LLM. */
|
|
48
|
+
export function getToolSchemas(registry, ctx) {
|
|
49
|
+
return listTools(registry, ctx).map((t) => ({
|
|
50
|
+
name: t.name,
|
|
51
|
+
description: t.description,
|
|
52
|
+
parameters: t.parameters,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResolveResult, URLRegistry, URLResolveContext, URLResolver } from '../shared/types/tool.js';
|
|
2
|
+
/** Create an empty URL resolver registry. */
|
|
3
|
+
declare function createURLRegistry(): URLRegistry;
|
|
4
|
+
/** Register a resolver for its scheme. First registration for a scheme wins;
|
|
5
|
+
* later registrations are ignored (stable dispatch, no accidental overrides). */
|
|
6
|
+
declare function registerURLResolver(registry: URLRegistry, resolver: URLResolver): void;
|
|
7
|
+
/** Does `path` carry a `scheme://` prefix? Plain file paths return false. */
|
|
8
|
+
declare function isURLPath(path: string): boolean;
|
|
9
|
+
/** Extract the scheme from a `scheme://…` path, or null for plain file paths.
|
|
10
|
+
* Scheme is the run of [a-z0-9+.-] before the first `://`. */
|
|
11
|
+
declare function parseScheme(path: string): string | null;
|
|
12
|
+
/** Resolve a `scheme://…` URL via the registry.
|
|
13
|
+
* Returns { _tag: 'error' } for unregistered schemes. */
|
|
14
|
+
declare function resolveURL(registry: URLRegistry, url: string, ctx: URLResolveContext): Promise<ResolveResult>;
|
|
15
|
+
export { createURLRegistry, isURLPath, parseScheme, registerURLResolver, resolveURL };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Create an empty URL resolver registry. */
|
|
2
|
+
function createURLRegistry() {
|
|
3
|
+
return { resolvers: new Map() };
|
|
4
|
+
}
|
|
5
|
+
/** Register a resolver for its scheme. First registration for a scheme wins;
|
|
6
|
+
* later registrations are ignored (stable dispatch, no accidental overrides). */
|
|
7
|
+
function registerURLResolver(registry, resolver) {
|
|
8
|
+
if (!registry.resolvers.has(resolver.scheme)) {
|
|
9
|
+
registry.resolvers.set(resolver.scheme, resolver);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Does `path` carry a `scheme://` prefix? Plain file paths return false. */
|
|
13
|
+
function isURLPath(path) {
|
|
14
|
+
return parseScheme(path) !== null;
|
|
15
|
+
}
|
|
16
|
+
/** Extract the scheme from a `scheme://…` path, or null for plain file paths.
|
|
17
|
+
* Scheme is the run of [a-z0-9+.-] before the first `://`. */
|
|
18
|
+
function parseScheme(path) {
|
|
19
|
+
const idx = path.indexOf('://');
|
|
20
|
+
if (idx <= 0)
|
|
21
|
+
return null;
|
|
22
|
+
const scheme = path.slice(0, idx);
|
|
23
|
+
// Schemes are ASCII alphanumeric plus +, -, . (RFC 3986).
|
|
24
|
+
if (!/^[a-z0-9+.-]+$/i.test(scheme))
|
|
25
|
+
return null;
|
|
26
|
+
return scheme.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
/** Resolve a `scheme://…` URL via the registry.
|
|
29
|
+
* Returns { _tag: 'error' } for unregistered schemes. */
|
|
30
|
+
async function resolveURL(registry, url, ctx) {
|
|
31
|
+
const scheme = parseScheme(url);
|
|
32
|
+
if (scheme === null) {
|
|
33
|
+
return { _tag: 'error', error: `Not a URL path: ${url}` };
|
|
34
|
+
}
|
|
35
|
+
const resolver = registry.resolvers.get(scheme);
|
|
36
|
+
if (!resolver) {
|
|
37
|
+
return { _tag: 'error', error: `No resolver registered for scheme "${scheme}://"` };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const content = await resolver.resolve(url, ctx);
|
|
41
|
+
return { _tag: 'ok', content };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { _tag: 'error', error: e instanceof Error ? e.message : String(e) };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export { createURLRegistry, isURLPath, parseScheme, registerURLResolver, resolveURL };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TruncateOptions, TruncateResult } from './types.js';
|
|
2
|
+
/** Default truncation thresholds — tuned for LLM context windows. */
|
|
3
|
+
export declare const DEFAULT_TRUNCATE_OPTIONS: TruncateOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Truncate output to fit within line and character limits.
|
|
6
|
+
* Preserves head and tail, inserting a marker for omitted content.
|
|
7
|
+
*/
|
|
8
|
+
export declare function truncateOutput(output: string, opts?: TruncateOptions): TruncateResult;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Default truncation thresholds — tuned for LLM context windows. */
|
|
2
|
+
export const DEFAULT_TRUNCATE_OPTIONS = {
|
|
3
|
+
maxLines: 2000,
|
|
4
|
+
maxChars: 100_000,
|
|
5
|
+
headLines: 50,
|
|
6
|
+
tailLines: 50,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Truncate output to fit within line and character limits.
|
|
10
|
+
* Preserves head and tail, inserting a marker for omitted content.
|
|
11
|
+
*/
|
|
12
|
+
export function truncateOutput(output, opts = DEFAULT_TRUNCATE_OPTIONS) {
|
|
13
|
+
if (output === '') {
|
|
14
|
+
return { output: '', truncated: false, totalLines: 0, totalChars: 0 };
|
|
15
|
+
}
|
|
16
|
+
const lines = output.split('\n');
|
|
17
|
+
const totalLines = lines.length;
|
|
18
|
+
const totalChars = output.length;
|
|
19
|
+
// Check if truncation is needed
|
|
20
|
+
const needsLineTrunc = totalLines > opts.maxLines;
|
|
21
|
+
const needsCharTrunc = totalChars > opts.maxChars;
|
|
22
|
+
if (!needsLineTrunc && !needsCharTrunc) {
|
|
23
|
+
return { output, truncated: false, totalLines, totalChars };
|
|
24
|
+
}
|
|
25
|
+
// Line-based truncation takes priority
|
|
26
|
+
if (needsLineTrunc) {
|
|
27
|
+
const head = lines.slice(0, opts.headLines);
|
|
28
|
+
const tailStart = totalLines - opts.tailLines;
|
|
29
|
+
// If tail would overlap head, show only head + marker
|
|
30
|
+
if (tailStart <= opts.headLines) {
|
|
31
|
+
const omitted = totalLines - head.length;
|
|
32
|
+
const marker = `[... ${omitted} lines truncated ...]`;
|
|
33
|
+
return { output: [...head, marker].join('\n'), truncated: true, totalLines, totalChars };
|
|
34
|
+
}
|
|
35
|
+
const tail = lines.slice(tailStart);
|
|
36
|
+
const omitted = totalLines - head.length - tail.length;
|
|
37
|
+
const marker = `[... ${omitted} lines truncated ...]`;
|
|
38
|
+
const result = [...head, marker, ...tail].join('\n');
|
|
39
|
+
return { output: result, truncated: true, totalLines, totalChars };
|
|
40
|
+
}
|
|
41
|
+
// Char-based truncation (keep proportional head/tail of chars)
|
|
42
|
+
const keepChars = opts.maxChars;
|
|
43
|
+
const headChars = Math.floor(keepChars * 0.5);
|
|
44
|
+
const tailChars = keepChars - headChars;
|
|
45
|
+
const head = output.slice(0, headChars);
|
|
46
|
+
const tail = output.slice(totalChars - tailChars);
|
|
47
|
+
const omitted = totalChars - keepChars;
|
|
48
|
+
const marker = `\n[... ${omitted} chars truncated ...]\n`;
|
|
49
|
+
return { output: head + marker + tail, truncated: true, totalLines, totalChars };
|
|
50
|
+
}
|