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,183 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import { getProject } from '../../project/project.js';
|
|
5
|
+
import { apiError } from '../middleware/error.js';
|
|
6
|
+
import { safeResolve } from '../util/safe-path.js';
|
|
7
|
+
/** 递归收集文件列表(用于搜索)。 */
|
|
8
|
+
async function collectFiles(dir, basePath, maxDepth = 5) {
|
|
9
|
+
if (maxDepth < 0)
|
|
10
|
+
return [];
|
|
11
|
+
const results = [];
|
|
12
|
+
let entries;
|
|
13
|
+
try {
|
|
14
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
21
|
+
continue;
|
|
22
|
+
const fullPath = join(dir, entry.name);
|
|
23
|
+
const relPath = relative(basePath, fullPath);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
results.push({ path: relPath, type: 'directory' });
|
|
26
|
+
results.push(...(await collectFiles(fullPath, basePath, maxDepth - 1)));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
results.push({ path: relPath, type: 'file' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
function contentTypeFor(name) {
|
|
35
|
+
const ext = name.split('.').pop()?.toLowerCase() ?? '';
|
|
36
|
+
const map = {
|
|
37
|
+
png: 'image/png',
|
|
38
|
+
jpg: 'image/jpeg',
|
|
39
|
+
jpeg: 'image/jpeg',
|
|
40
|
+
gif: 'image/gif',
|
|
41
|
+
svg: 'image/svg+xml',
|
|
42
|
+
webp: 'image/webp',
|
|
43
|
+
pdf: 'application/pdf',
|
|
44
|
+
mp3: 'audio/mpeg',
|
|
45
|
+
wav: 'audio/wav',
|
|
46
|
+
ogg: 'audio/ogg',
|
|
47
|
+
m4a: 'audio/mp4',
|
|
48
|
+
flac: 'audio/flac',
|
|
49
|
+
mp4: 'video/mp4',
|
|
50
|
+
webm: 'video/webm',
|
|
51
|
+
mov: 'video/quicktime',
|
|
52
|
+
json: 'application/json; charset=utf-8',
|
|
53
|
+
html: 'text/html; charset=utf-8',
|
|
54
|
+
md: 'text/markdown; charset=utf-8',
|
|
55
|
+
txt: 'text/plain; charset=utf-8',
|
|
56
|
+
ts: 'text/plain; charset=utf-8',
|
|
57
|
+
js: 'text/plain; charset=utf-8',
|
|
58
|
+
};
|
|
59
|
+
return map[ext] ?? 'application/octet-stream';
|
|
60
|
+
}
|
|
61
|
+
function createFilesRoute(ctx) {
|
|
62
|
+
const app = new Hono();
|
|
63
|
+
// 列出目录
|
|
64
|
+
// projectId 指定时按对应项目 worktree 列出,否则回退 ctx.cwd(向后兼容)。
|
|
65
|
+
app.get('/', async (c) => {
|
|
66
|
+
const queryPath = c.req.query('path') ?? '.';
|
|
67
|
+
const projectId = c.req.query('projectId');
|
|
68
|
+
let root = ctx.cwd;
|
|
69
|
+
if (projectId) {
|
|
70
|
+
const project = await getProject(ctx.db, projectId);
|
|
71
|
+
if (!project) {
|
|
72
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
73
|
+
}
|
|
74
|
+
root = project.worktree;
|
|
75
|
+
}
|
|
76
|
+
const resolved = safeResolve(root, queryPath);
|
|
77
|
+
if (!resolved) {
|
|
78
|
+
return apiError(c, 403, 'FORBIDDEN', 'Path outside workspace');
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
82
|
+
const result = entries
|
|
83
|
+
.filter((e) => !e.name.startsWith('.'))
|
|
84
|
+
.map((e) => ({
|
|
85
|
+
name: e.name,
|
|
86
|
+
type: (e.isDirectory() ? 'directory' : 'file'),
|
|
87
|
+
}))
|
|
88
|
+
.sort((a, b) => {
|
|
89
|
+
if (a.type !== b.type)
|
|
90
|
+
return a.type === 'directory' ? -1 : 1;
|
|
91
|
+
return a.name.localeCompare(b.name);
|
|
92
|
+
});
|
|
93
|
+
return c.json(result);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return apiError(c, 404, 'NOT_FOUND', 'Directory not found');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// 搜索文件名
|
|
100
|
+
// projectId 指定时按对应项目 worktree 搜索,否则回退 ctx.cwd(向后兼容)。
|
|
101
|
+
app.get('/search', async (c) => {
|
|
102
|
+
const q = c.req.query('q');
|
|
103
|
+
if (!q) {
|
|
104
|
+
return apiError(c, 400, 'BAD_REQUEST', 'Query parameter q is required');
|
|
105
|
+
}
|
|
106
|
+
const projectId = c.req.query('projectId');
|
|
107
|
+
let root = ctx.cwd;
|
|
108
|
+
if (projectId) {
|
|
109
|
+
const project = await getProject(ctx.db, projectId);
|
|
110
|
+
if (!project) {
|
|
111
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
112
|
+
}
|
|
113
|
+
root = project.worktree;
|
|
114
|
+
}
|
|
115
|
+
const all = await collectFiles(root, root);
|
|
116
|
+
const lower = q.toLowerCase();
|
|
117
|
+
const matched = all.filter((f) => f.path.toLowerCase().includes(lower));
|
|
118
|
+
return c.json(matched);
|
|
119
|
+
});
|
|
120
|
+
// 读取文件
|
|
121
|
+
// projectId 指定时按对应项目 worktree 解析,否则回退 ctx.cwd(向后兼容)。
|
|
122
|
+
app.get('/*', async (c) => {
|
|
123
|
+
const path = c.req.path.replace(/^\/api\/files\//, '').replace(/^\//, '');
|
|
124
|
+
const raw = path.endsWith('/raw');
|
|
125
|
+
const filePath = raw ? path.slice(0, -'/raw'.length) : path;
|
|
126
|
+
const projectId = c.req.query('projectId');
|
|
127
|
+
let root = ctx.cwd;
|
|
128
|
+
if (projectId) {
|
|
129
|
+
const project = await getProject(ctx.db, projectId);
|
|
130
|
+
if (!project) {
|
|
131
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
132
|
+
}
|
|
133
|
+
root = project.worktree;
|
|
134
|
+
}
|
|
135
|
+
const resolved = safeResolve(root, filePath);
|
|
136
|
+
if (!resolved) {
|
|
137
|
+
return apiError(c, 403, 'FORBIDDEN', 'Path outside workspace');
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
if (raw) {
|
|
141
|
+
const buf = await readFile(resolved);
|
|
142
|
+
return c.body(buf, 200, {
|
|
143
|
+
'Content-Type': contentTypeFor(filePath),
|
|
144
|
+
'Cache-Control': 'no-store',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const content = await readFile(resolved, 'utf-8');
|
|
148
|
+
return c.json({ path, content });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return apiError(c, 404, 'NOT_FOUND', 'File not found');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// 写入文件
|
|
155
|
+
// projectId 指定时按对应项目 worktree 解析,否则回退 ctx.cwd(向后兼容)。
|
|
156
|
+
app.put('/*', async (c) => {
|
|
157
|
+
const path = c.req.path.replace(/^\/api\/files\//, '').replace(/^\//, '');
|
|
158
|
+
const projectId = c.req.query('projectId');
|
|
159
|
+
let root = ctx.cwd;
|
|
160
|
+
if (projectId) {
|
|
161
|
+
const project = await getProject(ctx.db, projectId);
|
|
162
|
+
if (!project) {
|
|
163
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
164
|
+
}
|
|
165
|
+
root = project.worktree;
|
|
166
|
+
}
|
|
167
|
+
const resolved = safeResolve(root, path);
|
|
168
|
+
if (!resolved) {
|
|
169
|
+
return apiError(c, 403, 'FORBIDDEN', 'Path outside workspace');
|
|
170
|
+
}
|
|
171
|
+
const body = await c.req.json();
|
|
172
|
+
try {
|
|
173
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
174
|
+
await writeFile(resolved, body.content, 'utf-8');
|
|
175
|
+
return c.json({ path, written: true });
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
return apiError(c, 500, 'WRITE_ERROR', `Failed to write file: ${String(err)}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return app;
|
|
182
|
+
}
|
|
183
|
+
export { createFilesRoute };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { ServerContext } from '../types.js';
|
|
3
|
+
/** 目录列表项。 */
|
|
4
|
+
type DirEntry = {
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 列出指定目录下的子目录(仅目录,不含文件)。
|
|
10
|
+
* 用于项目添加时的路径自动补全——可浏览文件系统任意位置。
|
|
11
|
+
*
|
|
12
|
+
* 输入 path 为空时返回 home 目录列表。
|
|
13
|
+
* `~` 开头自动展开为 home 目录。
|
|
14
|
+
*/
|
|
15
|
+
declare function listDirectories(dirPath: string): Promise<DirEntry[]>;
|
|
16
|
+
/** 递归搜索目录:从 directory 起收集 type=directory 的相对路径。
|
|
17
|
+
*
|
|
18
|
+
* - 起点经 expandPath 展开 `~`,可为任意绝对路径。
|
|
19
|
+
* - 跳过隐藏目录(`.` 开头)与 SKIP_DIRS。
|
|
20
|
+
* - 限深(默认 5)+ 数量上限 limit(默认 50),避免大目录爆炸。
|
|
21
|
+
* - 匹配:相对路径(小写)includes query(小写),可命中任意层级目录名。
|
|
22
|
+
*
|
|
23
|
+
* 服务端粗筛;客户端 fuzzysort 精排与排序。
|
|
24
|
+
*/
|
|
25
|
+
declare function searchDirectories(directory: string, query: string, limit?: number, maxDepth?: number): Promise<string[]>;
|
|
26
|
+
/** 把 `~/...` 或空路径展开为绝对路径。 */
|
|
27
|
+
declare function expandPath(input: string): string;
|
|
28
|
+
declare function createFilesystemRoute(ctx: ServerContext): Hono;
|
|
29
|
+
export { createFilesystemRoute, expandPath, listDirectories, searchDirectories };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { relative as relativePath, resolve as resolvePath } from 'node:path';
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
/**
|
|
6
|
+
* 列出指定目录下的子目录(仅目录,不含文件)。
|
|
7
|
+
* 用于项目添加时的路径自动补全——可浏览文件系统任意位置。
|
|
8
|
+
*
|
|
9
|
+
* 输入 path 为空时返回 home 目录列表。
|
|
10
|
+
* `~` 开头自动展开为 home 目录。
|
|
11
|
+
*/
|
|
12
|
+
async function listDirectories(dirPath) {
|
|
13
|
+
let entries;
|
|
14
|
+
try {
|
|
15
|
+
entries = await readdir(dirPath, { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const dirs = entries
|
|
21
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith('.'))
|
|
22
|
+
.map((e) => ({
|
|
23
|
+
name: e.name,
|
|
24
|
+
path: resolvePath(dirPath, e.name),
|
|
25
|
+
}))
|
|
26
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
27
|
+
return dirs;
|
|
28
|
+
}
|
|
29
|
+
/** 跳过的目录名(无价值、体量大或为噪音)。 */
|
|
30
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.cache']);
|
|
31
|
+
/** 递归搜索目录:从 directory 起收集 type=directory 的相对路径。
|
|
32
|
+
*
|
|
33
|
+
* - 起点经 expandPath 展开 `~`,可为任意绝对路径。
|
|
34
|
+
* - 跳过隐藏目录(`.` 开头)与 SKIP_DIRS。
|
|
35
|
+
* - 限深(默认 5)+ 数量上限 limit(默认 50),避免大目录爆炸。
|
|
36
|
+
* - 匹配:相对路径(小写)includes query(小写),可命中任意层级目录名。
|
|
37
|
+
*
|
|
38
|
+
* 服务端粗筛;客户端 fuzzysort 精排与排序。
|
|
39
|
+
*/
|
|
40
|
+
async function searchDirectories(directory, query, limit = 50, maxDepth = 5) {
|
|
41
|
+
const base = directory;
|
|
42
|
+
const lowerQuery = query.toLowerCase();
|
|
43
|
+
const results = [];
|
|
44
|
+
async function walk(dir, depth) {
|
|
45
|
+
if (results.length >= limit || depth > maxDepth)
|
|
46
|
+
return;
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (results.length >= limit)
|
|
56
|
+
return;
|
|
57
|
+
// 跳过隐藏目录与噪音目录
|
|
58
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || SKIP_DIRS.has(entry.name))
|
|
59
|
+
continue;
|
|
60
|
+
const fullPath = resolvePath(dir, entry.name);
|
|
61
|
+
const relPath = relativePath(base, fullPath);
|
|
62
|
+
if (!lowerQuery || relPath.toLowerCase().includes(lowerQuery)) {
|
|
63
|
+
results.push(relPath);
|
|
64
|
+
if (results.length >= limit)
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await walk(fullPath, depth + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await walk(base, 0);
|
|
71
|
+
return results.slice(0, limit);
|
|
72
|
+
}
|
|
73
|
+
/** 把 `~/...` 或空路径展开为绝对路径。 */
|
|
74
|
+
function expandPath(input) {
|
|
75
|
+
if (!input || input === '~')
|
|
76
|
+
return homedir();
|
|
77
|
+
if (input.startsWith('~/'))
|
|
78
|
+
return resolvePath(homedir(), input.slice(2));
|
|
79
|
+
return resolvePath(input);
|
|
80
|
+
}
|
|
81
|
+
function createFilesystemRoute(ctx) {
|
|
82
|
+
const app = new Hono();
|
|
83
|
+
// 列出目录下的子目录(用于项目路径自动补全)
|
|
84
|
+
app.get('/browse', async (c) => {
|
|
85
|
+
const rawPath = c.req.query('path') ?? '';
|
|
86
|
+
const target = expandPath(rawPath);
|
|
87
|
+
// 校验:ctx 可用于将来做沙箱限制(当前允许浏览任意路径)
|
|
88
|
+
void ctx;
|
|
89
|
+
const dirs = await listDirectories(target);
|
|
90
|
+
return c.json({ path: target, directories: dirs });
|
|
91
|
+
});
|
|
92
|
+
// 递归搜索目录(用于项目路径自动补全——命中深层目录)
|
|
93
|
+
app.get('/search', async (c) => {
|
|
94
|
+
const rawDir = c.req.query('directory') ?? '';
|
|
95
|
+
const q = c.req.query('q') ?? '';
|
|
96
|
+
const limitRaw = Number.parseInt(c.req.query('limit') ?? '', 10);
|
|
97
|
+
const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : 50;
|
|
98
|
+
const directory = expandPath(rawDir);
|
|
99
|
+
const items = await searchDirectories(directory, q, limit);
|
|
100
|
+
return c.json({ items });
|
|
101
|
+
});
|
|
102
|
+
// 列出 home 目录快捷入口
|
|
103
|
+
app.get('/home', (c) => {
|
|
104
|
+
return c.json({ path: homedir() });
|
|
105
|
+
});
|
|
106
|
+
return app;
|
|
107
|
+
}
|
|
108
|
+
export { createFilesystemRoute, expandPath, listDirectories, searchDirectories };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
function createHealthRoute() {
|
|
3
|
+
const app = new Hono();
|
|
4
|
+
app.get('/', (c) => {
|
|
5
|
+
return c.json({
|
|
6
|
+
status: 'ok',
|
|
7
|
+
version: '0.1.0',
|
|
8
|
+
timestamp: Date.now(),
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
return app;
|
|
12
|
+
}
|
|
13
|
+
export { createHealthRoute };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { apiError } from '../middleware/error.js';
|
|
3
|
+
/** GET/PUT /api/permissions — 全局授权模式(default 逐个确认 / auto 自动放行 ask 工具)。 */
|
|
4
|
+
function createPermissionsRoute(ctx) {
|
|
5
|
+
const app = new Hono();
|
|
6
|
+
// GET / — 当前授权模式
|
|
7
|
+
app.get('/', (c) => {
|
|
8
|
+
return c.json({ mode: ctx.permissionMode });
|
|
9
|
+
});
|
|
10
|
+
// PUT / — 运行时切换授权模式(仅本次运行生效,不回写 config;
|
|
11
|
+
// 重启后回到 config.permission.defaultMode 持久化默认值)
|
|
12
|
+
app.put('/', async (c) => {
|
|
13
|
+
const body = (await c.req.json().catch(() => null));
|
|
14
|
+
const mode = body?.mode;
|
|
15
|
+
if (mode !== 'default' && mode !== 'auto') {
|
|
16
|
+
return apiError(c, 400, 'INVALID_MODE', "mode 必须是 'default' 或 'auto'");
|
|
17
|
+
}
|
|
18
|
+
ctx.permissionMode = mode;
|
|
19
|
+
return c.json({ mode: ctx.permissionMode });
|
|
20
|
+
});
|
|
21
|
+
return app;
|
|
22
|
+
}
|
|
23
|
+
export { createPermissionsRoute };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { fromDirectory, getProject, listProjects, resolveProject, updateProjectName, } from '../../project/index.js';
|
|
3
|
+
import { apiError } from '../middleware/error.js';
|
|
4
|
+
import { expandPath } from './filesystem.js';
|
|
5
|
+
/** 实时取 git 分支(分支随时变,不存 DB)。 */
|
|
6
|
+
function withBranch(project) {
|
|
7
|
+
return { ...project, gitBranch: resolveProject(project.worktree).gitBranch };
|
|
8
|
+
}
|
|
9
|
+
function createProjectRoute(ctx) {
|
|
10
|
+
const app = new Hono();
|
|
11
|
+
// 解析目录并创建/更新项目记录
|
|
12
|
+
app.post('/from-directory', async (c) => {
|
|
13
|
+
const body = await c.req.json().catch(() => ({}));
|
|
14
|
+
const directory = body.directory;
|
|
15
|
+
if (!directory)
|
|
16
|
+
return apiError(c, 400, 'BAD_REQUEST', 'directory is required');
|
|
17
|
+
// 展开 ~ 前缀为 home 绝对路径(前端可能传 ~/... 形式)
|
|
18
|
+
const project = await fromDirectory(ctx.db, expandPath(directory));
|
|
19
|
+
return c.json(withBranch(project), 200);
|
|
20
|
+
});
|
|
21
|
+
// 列出所有项目
|
|
22
|
+
app.get('/', async (c) => {
|
|
23
|
+
const list = await listProjects(ctx.db);
|
|
24
|
+
return c.json(list.map(withBranch));
|
|
25
|
+
});
|
|
26
|
+
// 解析服务端 cwd 对应的项目(未注册则自动创建)
|
|
27
|
+
app.get('/current', async (c) => {
|
|
28
|
+
const project = await fromDirectory(ctx.db, ctx.cwd);
|
|
29
|
+
return c.json(withBranch(project), 200);
|
|
30
|
+
});
|
|
31
|
+
// 按 id 获取项目
|
|
32
|
+
app.get('/:id', async (c) => {
|
|
33
|
+
const project = await getProject(ctx.db, c.req.param('id'));
|
|
34
|
+
if (!project)
|
|
35
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
36
|
+
return c.json(withBranch(project));
|
|
37
|
+
});
|
|
38
|
+
// 更新项目名
|
|
39
|
+
app.patch('/:id', async (c) => {
|
|
40
|
+
const body = await c.req.json().catch(() => ({}));
|
|
41
|
+
const name = body.name;
|
|
42
|
+
if (!name)
|
|
43
|
+
return apiError(c, 400, 'BAD_REQUEST', 'name is required');
|
|
44
|
+
const project = await updateProjectName(ctx.db, c.req.param('id'), name);
|
|
45
|
+
if (!project)
|
|
46
|
+
return apiError(c, 404, 'NOT_FOUND', 'Project not found');
|
|
47
|
+
return c.json(withBranch(project));
|
|
48
|
+
});
|
|
49
|
+
return app;
|
|
50
|
+
}
|
|
51
|
+
export { createProjectRoute };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { ServerContext } from '../types.js';
|
|
3
|
+
type TestResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
models: string[];
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 用给定 baseURL/apiKey 探测 OpenAI 兼容的 /models 端点。
|
|
12
|
+
* baseURL 应自行包含版本路径(如 https://api.openai.com/v1)。
|
|
13
|
+
*/
|
|
14
|
+
declare function probeModels(baseURL: string, apiKey: string): Promise<TestResult>;
|
|
15
|
+
declare function createProviderRoute(ctx: ServerContext): Hono;
|
|
16
|
+
export { createProviderRoute, probeModels };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { decryptSecret } from '../../core/secret.js';
|
|
3
|
+
import { apiError } from '../middleware/error.js';
|
|
4
|
+
/**
|
|
5
|
+
* 用给定 baseURL/apiKey 探测 OpenAI 兼容的 /models 端点。
|
|
6
|
+
* baseURL 应自行包含版本路径(如 https://api.openai.com/v1)。
|
|
7
|
+
*/
|
|
8
|
+
async function probeModels(baseURL, apiKey) {
|
|
9
|
+
const base = baseURL.replace(/\/+$/, '');
|
|
10
|
+
const url = `${base}/models`;
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
method: 'GET',
|
|
16
|
+
headers: {
|
|
17
|
+
Accept: 'application/json',
|
|
18
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
19
|
+
},
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const text = await res.text().catch(() => '');
|
|
24
|
+
return { ok: false, error: `HTTP ${res.status} ${text.slice(0, 200) || res.statusText}` };
|
|
25
|
+
}
|
|
26
|
+
const json = (await res.json());
|
|
27
|
+
const models = Array.isArray(json.data)
|
|
28
|
+
? json.data.map((m) => m.id).filter((id) => typeof id === 'string')
|
|
29
|
+
: [];
|
|
30
|
+
return { ok: true, models };
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
return { ok: false, error: msg };
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function createProviderRoute(ctx) {
|
|
41
|
+
const app = new Hono();
|
|
42
|
+
// 列出已配置 providers(apiKey 脱敏,不含明文)
|
|
43
|
+
app.get('/', (c) => {
|
|
44
|
+
const providers = ctx.config.providers.map((p) => ({
|
|
45
|
+
name: p.name || p._tag || '',
|
|
46
|
+
protocol: p.protocol,
|
|
47
|
+
baseURL: p.baseURL ?? '',
|
|
48
|
+
hasKey: !!p.apiKey,
|
|
49
|
+
}));
|
|
50
|
+
return c.json({ providers, defaultProvider: ctx.config.defaultProvider });
|
|
51
|
+
});
|
|
52
|
+
// 连接测试:用请求体里的凭据探测 /models,不污染 registry
|
|
53
|
+
app.post('/test', async (c) => {
|
|
54
|
+
const body = await c.req.json().catch(() => ({}));
|
|
55
|
+
const { baseURL, apiKey } = body;
|
|
56
|
+
if (!baseURL)
|
|
57
|
+
return apiError(c, 400, 'BAD_REQUEST', 'baseURL is required');
|
|
58
|
+
// apiKey 可能是 Settings 页回传的 enc: 密文(保存后刷新、未重输时):探测前解密,
|
|
59
|
+
// 否则把 enc: 串当 Bearer token 发给上游必然 401,造成「保存了却测试失败」的误判。
|
|
60
|
+
const secret = apiKey ? decryptSecret(apiKey) : '';
|
|
61
|
+
const result = await probeModels(baseURL, secret);
|
|
62
|
+
return c.json(result, result.ok ? 200 : 200);
|
|
63
|
+
});
|
|
64
|
+
return app;
|
|
65
|
+
}
|
|
66
|
+
export { createProviderRoute, probeModels };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { createSummarizer, runCompaction } from '../../core/compact.js';
|
|
3
|
+
import { fromDirectory } from '../../project/index.js';
|
|
4
|
+
import { forkSession, getBranches, getTree } from '../../session/branch.js';
|
|
5
|
+
import { getMessages } from '../../session/message.js';
|
|
6
|
+
import { createSession, deleteSession, getLLMSegments, getSession, listSessions, listSessionsByProject, } from '../../session/session.js';
|
|
7
|
+
import { apiError } from '../middleware/error.js';
|
|
8
|
+
function createSessionRoute(ctx) {
|
|
9
|
+
const app = new Hono();
|
|
10
|
+
// 创建会话
|
|
11
|
+
app.post('/', async (c) => {
|
|
12
|
+
const body = await c.req.json().catch(() => ({}));
|
|
13
|
+
const title = body.title ?? 'New Session';
|
|
14
|
+
const directory = body.directory;
|
|
15
|
+
const explicitProjectId = body.projectId;
|
|
16
|
+
let projectId;
|
|
17
|
+
if (directory) {
|
|
18
|
+
const project = await fromDirectory(ctx.db, directory);
|
|
19
|
+
projectId = project.id;
|
|
20
|
+
}
|
|
21
|
+
else if (explicitProjectId) {
|
|
22
|
+
projectId = explicitProjectId;
|
|
23
|
+
}
|
|
24
|
+
const session = await createSession(ctx.db, title, projectId);
|
|
25
|
+
return c.json(session, 201);
|
|
26
|
+
});
|
|
27
|
+
// 列出会话
|
|
28
|
+
app.get('/', async (c) => {
|
|
29
|
+
const projectId = c.req.query('projectId');
|
|
30
|
+
const sessions = projectId
|
|
31
|
+
? await listSessionsByProject(ctx.db, projectId)
|
|
32
|
+
: await listSessions(ctx.db);
|
|
33
|
+
return c.json(sessions);
|
|
34
|
+
});
|
|
35
|
+
// 会话树
|
|
36
|
+
app.get('/tree', async (c) => {
|
|
37
|
+
const tree = await getTree(ctx.db);
|
|
38
|
+
return c.json(tree);
|
|
39
|
+
});
|
|
40
|
+
// 获取会话详情
|
|
41
|
+
app.get('/:id', async (c) => {
|
|
42
|
+
try {
|
|
43
|
+
const session = await getSession(ctx.db, c.req.param('id'));
|
|
44
|
+
if (!session) {
|
|
45
|
+
return apiError(c, 404, 'NOT_FOUND', 'Session not found');
|
|
46
|
+
}
|
|
47
|
+
return c.json(session);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return apiError(c, 404, 'NOT_FOUND', 'Session not found');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// 分支会话
|
|
54
|
+
app.post('/:id/fork', async (c) => {
|
|
55
|
+
const id = c.req.param('id');
|
|
56
|
+
const body = await c.req.json().catch(() => ({}));
|
|
57
|
+
const messageIndex = body.messageIndex ?? 0;
|
|
58
|
+
try {
|
|
59
|
+
const forked = await forkSession(ctx.db, id, messageIndex);
|
|
60
|
+
return c.json(forked, 201);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return apiError(c, 404, 'NOT_FOUND', 'Session not found');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// 删除会话
|
|
67
|
+
app.delete('/:id', async (c) => {
|
|
68
|
+
await deleteSession(ctx.db, c.req.param('id'));
|
|
69
|
+
return c.body(null, 204);
|
|
70
|
+
});
|
|
71
|
+
// 获取消息列表
|
|
72
|
+
app.get('/:id/messages', async (c) => {
|
|
73
|
+
const messages = await getMessages(ctx.db, c.req.param('id'));
|
|
74
|
+
return c.json(messages);
|
|
75
|
+
});
|
|
76
|
+
// 获取 LLM 调用分段(段首快照 + 段内轻量 calls):优先取活跃 run 的内存记录(实时),回退 DB 持久化
|
|
77
|
+
app.get('/:id/llm-details', async (c) => {
|
|
78
|
+
const run = ctx.agentManager.get(c.req.param('id'));
|
|
79
|
+
if (run)
|
|
80
|
+
return c.json(run.state.segments);
|
|
81
|
+
const persisted = await getLLMSegments(ctx.db, c.req.param('id'));
|
|
82
|
+
return c.json(persisted);
|
|
83
|
+
});
|
|
84
|
+
// 手动触发会话压缩(段切换确认弹窗「顺便压缩」调用)。用末段的 provider/model 构建摘要器。
|
|
85
|
+
app.post('/:id/compact', async (c) => {
|
|
86
|
+
const id = c.req.param('id');
|
|
87
|
+
let session;
|
|
88
|
+
try {
|
|
89
|
+
session = await getSession(ctx.db, id);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return apiError(c, 404, 'NOT_FOUND', 'Session not found');
|
|
93
|
+
}
|
|
94
|
+
if (!session)
|
|
95
|
+
return apiError(c, 404, 'NOT_FOUND', 'Session not found');
|
|
96
|
+
const segs = await getLLMSegments(ctx.db, id);
|
|
97
|
+
const lastSeg = segs[segs.length - 1];
|
|
98
|
+
const provider = lastSeg?.provider ?? ctx.config.defaultProvider;
|
|
99
|
+
const model = lastSeg?.model ?? ctx.config.defaultModel;
|
|
100
|
+
try {
|
|
101
|
+
const summarizer = createSummarizer(ctx.llmRegistry, provider, model, {});
|
|
102
|
+
const result = await runCompaction(ctx.db, id, summarizer, {
|
|
103
|
+
keepRecentTokens: ctx.config.compaction.keepRecentTokens,
|
|
104
|
+
});
|
|
105
|
+
return c.json(result);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return apiError(c, 500, 'COMPACTION_FAILED', e instanceof Error ? e.message : 'Compaction failed');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// 获取会话状态:内存有活跃 run → 返回其 status;否则查 DB lastRun。
|
|
112
|
+
// lastRun.status='running' 但无活跃 run → 服务重启被中断。
|
|
113
|
+
app.get('/:id/status', async (c) => {
|
|
114
|
+
const run = ctx.agentManager.get(c.req.param('id'));
|
|
115
|
+
if (run)
|
|
116
|
+
return c.json(run.state.status);
|
|
117
|
+
const session = await getSession(ctx.db, c.req.param('id'));
|
|
118
|
+
if (session?.metadata.lastRun?.status === 'running') {
|
|
119
|
+
return c.json({ _tag: 'interrupted' });
|
|
120
|
+
}
|
|
121
|
+
return c.json({ _tag: 'idle' });
|
|
122
|
+
});
|
|
123
|
+
// 获取分支
|
|
124
|
+
app.get('/:id/branches', async (c) => {
|
|
125
|
+
const branches = await getBranches(ctx.db, c.req.param('id'));
|
|
126
|
+
return c.json(branches);
|
|
127
|
+
});
|
|
128
|
+
return app;
|
|
129
|
+
}
|
|
130
|
+
export { createSessionRoute };
|