@turingflow/agent-kit 0.0.0-stage → 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 +546 -2
- package/dist/agent.d.ts +27 -0
- package/dist/agent.js +31 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +105 -0
- package/dist/handlers.d.ts +92 -0
- package/dist/handlers.js +211 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -0
- package/dist/sdk.d.ts +106 -0
- package/dist/sdk.js +309 -0
- package/dist/server/agent.d.ts +27 -0
- package/dist/server/agent.js +31 -0
- package/dist/server/artifact-hooks.d.ts +6 -0
- package/dist/server/artifact-hooks.js +37 -0
- package/dist/server/artifact-tool-tracker.d.ts +24 -0
- package/dist/server/artifact-tool-tracker.js +347 -0
- package/dist/server/artifacts.d.ts +227 -0
- package/dist/server/artifacts.js +1061 -0
- package/dist/server/auth/config.d.ts +34 -0
- package/dist/server/auth/config.js +82 -0
- package/dist/server/auth/db.d.ts +159 -0
- package/dist/server/auth/db.js +680 -0
- package/dist/server/auth/handlers.d.ts +27 -0
- package/dist/server/auth/handlers.js +297 -0
- package/dist/server/auth/index.d.ts +15 -0
- package/dist/server/auth/index.js +14 -0
- package/dist/server/auth/login-options.d.ts +14 -0
- package/dist/server/auth/login-options.js +50 -0
- package/dist/server/auth/providers/dingtalk.d.ts +16 -0
- package/dist/server/auth/providers/dingtalk.js +177 -0
- package/dist/server/auth/providers/feishu.d.ts +15 -0
- package/dist/server/auth/providers/feishu.js +100 -0
- package/dist/server/auth/providers/index.d.ts +13 -0
- package/dist/server/auth/providers/index.js +24 -0
- package/dist/server/auth/session.d.ts +48 -0
- package/dist/server/auth/session.js +108 -0
- package/dist/server/auth/types.d.ts +118 -0
- package/dist/server/auth/types.js +7 -0
- package/dist/server/browser-agent-mcp.d.ts +67 -0
- package/dist/server/browser-agent-mcp.js +608 -0
- package/dist/server/cli.d.ts +6 -0
- package/dist/server/cli.js +112 -0
- package/dist/server/debug-log.d.ts +25 -0
- package/dist/server/debug-log.js +86 -0
- package/dist/server/handlers.d.ts +141 -0
- package/dist/server/handlers.js +523 -0
- package/dist/server/index.d.ts +30 -0
- package/dist/server/index.js +28 -0
- package/dist/server/kit-api.d.ts +23 -0
- package/dist/server/kit-api.js +153 -0
- package/dist/server/model-sources.d.ts +32 -0
- package/dist/server/model-sources.js +58 -0
- package/dist/server/oa.d.ts +87 -0
- package/dist/server/oa.js +397 -0
- package/dist/server/providers/openrouter.d.ts +51 -0
- package/dist/server/providers/openrouter.js +283 -0
- package/dist/server/render-markdown.d.ts +2 -0
- package/dist/server/render-markdown.js +50 -0
- package/dist/server/sdk.d.ts +259 -0
- package/dist/server/sdk.js +1991 -0
- package/dist/server/session-access.d.ts +5 -0
- package/dist/server/session-access.js +68 -0
- package/dist/server/session-fork.d.ts +10 -0
- package/dist/server/session-fork.js +142 -0
- package/dist/server/session-store.d.ts +56 -0
- package/dist/server/session-store.js +148 -0
- package/dist/server/sessions.d.ts +24 -0
- package/dist/server/sessions.js +541 -0
- package/dist/server/share.d.ts +15 -0
- package/dist/server/share.js +433 -0
- package/dist/server/tool-activity.d.ts +2 -0
- package/dist/server/tool-activity.js +26 -0
- package/dist/server/transcription/xiaomi-mimo.d.ts +8 -0
- package/dist/server/transcription/xiaomi-mimo.js +86 -0
- package/dist/server/upload.d.ts +13 -0
- package/dist/server/upload.js +81 -0
- package/dist/server/user-files.d.ts +28 -0
- package/dist/server/user-files.js +311 -0
- package/dist/server/voice.d.ts +25 -0
- package/dist/server/voice.js +61 -0
- package/dist/server/web-fetch.d.ts +71 -0
- package/dist/server/web-fetch.js +330 -0
- package/dist/styles.css +3354 -0
- package/dist/transcription/xiaomi-mimo.d.ts +8 -0
- package/dist/transcription/xiaomi-mimo.js +86 -0
- package/dist/ui/index.d.ts +352 -0
- package/dist/ui/index.js +7730 -0
- package/dist/upload.d.ts +13 -0
- package/dist/upload.js +81 -0
- package/dist/voice.d.ts +25 -0
- package/dist/voice.js +61 -0
- package/package.json +74 -4
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-kit · 服务端端点工厂(Next.js App Router)
|
|
3
|
+
*
|
|
4
|
+
* 第三方 Next.js 工程引入后,即可获得与 web 完全一致的四个端点能力,
|
|
5
|
+
* 无需重新实现上传 / 语音令牌 / ASR 转写等通用逻辑:
|
|
6
|
+
*
|
|
7
|
+
* // app/api/chat/route.js
|
|
8
|
+
* import { createChatHandler } from "agent-kit";
|
|
9
|
+
* export const runtime = "nodejs";
|
|
10
|
+
* export const { POST } = createChatHandler();
|
|
11
|
+
*
|
|
12
|
+
* // app/api/upload/route.js
|
|
13
|
+
* import { createUploadHandler } from "agent-kit";
|
|
14
|
+
* export const { POST } = createUploadHandler();
|
|
15
|
+
*
|
|
16
|
+
* // app/api/voice/token/route.js
|
|
17
|
+
* import { createVoiceTokenHandler } from "agent-kit";
|
|
18
|
+
* export const { POST } = createVoiceTokenHandler({ userResolver: async (req) => currentUser });
|
|
19
|
+
*
|
|
20
|
+
* // app/api/voice/transcribe/route.js
|
|
21
|
+
* import { createVoiceTranscribeHandler } from "agent-kit";
|
|
22
|
+
* export const { POST } = createVoiceTranscribeHandler();
|
|
23
|
+
*
|
|
24
|
+
* 前端 ChatWindow 的默认端点路径(/api/chat、/api/upload、/api/voice/*)与之对应,零改动接入。
|
|
25
|
+
*/
|
|
26
|
+
import { createAgentStream, createAgentSessionStream, resolvePendingAsk, steerSession } from "./sdk.js";
|
|
27
|
+
import { BrowserAgentService } from "./browser-agent-mcp.js";
|
|
28
|
+
import { startSessionSweeper } from "./session-store.js";
|
|
29
|
+
import { assertChatAccess, assertSessionAccess } from "./session-access.js";
|
|
30
|
+
import { slog } from "./debug-log.js";
|
|
31
|
+
import { createUploadHandler } from "./upload.js";
|
|
32
|
+
import { createVoiceToken, verifyVoiceToken, transcribeVoice } from "./voice.js";
|
|
33
|
+
import { readArtifact, listSessionArtifacts, ensureSessionDir, artifactRoot } from "./artifacts.js";
|
|
34
|
+
import { verifySession } from "./auth/handlers.js";
|
|
35
|
+
import { loadAuthConfig } from "./auth/config.js";
|
|
36
|
+
import { setSessionTitleIfNotUserRenamed, isDbEnabled, getAgentSession } from "./auth/db.js";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import fs from "node:fs";
|
|
39
|
+
/** Windows 绝对路径 → CLI 可用的 bash 风格路径:D:\a\b → /d/a/b(CodeBuddy CLI 在 Windows 上执行工具时使用 bash 风格路径) */
|
|
40
|
+
function toPosixPath(p) {
|
|
41
|
+
const m = String(p).match(/^([A-Za-z]):[\\/](.*)$/s);
|
|
42
|
+
if (m)
|
|
43
|
+
return `/${m[1].toLowerCase()}/${m[2].replace(/\\/g, "/")}`;
|
|
44
|
+
return String(p).replace(/\\/g, "/");
|
|
45
|
+
}
|
|
46
|
+
/** POST /api/chat:消息数组 → agent-kit 订阅对话流 → SSE 事件流(text/thinking/done/error) */
|
|
47
|
+
export function createChatHandler(config = {}) {
|
|
48
|
+
// 附件真实目录:默认与 createUploadHandler 一致(<cwd>/public/uploads)。
|
|
49
|
+
// 注入给模型的路径一律用虚拟前缀 /attachments/<文件名>(相对 CLI cwd,真实存在于 <cwd>/attachments/):
|
|
50
|
+
// ① chat 端点会把附件复制到 <cwd>/attachments/,模型 Read 虚拟路径即直接命中,零路径改写依赖;
|
|
51
|
+
// ② 模型上下文/思考/正文中出现的只有无害的虚拟路径,真实磁盘结构永不进入模型。
|
|
52
|
+
const attachmentRealDir = config.attachmentBaseDir ?? path.join(process.cwd(), "public", "uploads");
|
|
53
|
+
const attachmentUrlPrefix = (config.attachmentUrlPrefix ?? "/uploads").replace(/\/+$/, "");
|
|
54
|
+
// 附件相对可读路径统一为 attachments/<rel>(见 stageVirtualCopy 注释)
|
|
55
|
+
// 附件虚拟路径按会话隔离:模型只能 Read 本会话的附件。
|
|
56
|
+
// 实际可读路径由 stageVirtualCopy 决定(相对 CLI cwd 的 attachments/<rel>,
|
|
57
|
+
// 见 stageVirtualCopy 注释——SDK/CLI 无 pathAliases 能力,绝对虚拟路径在 Windows 上读不到)。
|
|
58
|
+
// existingContent:消息里已有内容(前端通常已把 "[附件] 文件名" 拼进用户消息),
|
|
59
|
+
// 已含附件名时不再重复拼接(避免同一附件名出现两次、浪费模型上下文)。
|
|
60
|
+
function attachmentHint(attachment, existingContent = "", readPath = "") {
|
|
61
|
+
if (!attachment || typeof attachment !== "object")
|
|
62
|
+
return "";
|
|
63
|
+
const url = String(attachment.url ?? "");
|
|
64
|
+
if (!url.startsWith(attachmentUrlPrefix))
|
|
65
|
+
return "";
|
|
66
|
+
const rel = url.slice(attachmentUrlPrefix.length).replace(/^\/+/, "");
|
|
67
|
+
const name = String(attachment.name ?? path.basename(url));
|
|
68
|
+
const nameAlreadyShown = existingContent.includes(`[附件] ${name}`) || existingContent.includes(`[附件]${name}`);
|
|
69
|
+
const finalReadPath = readPath || `attachments/${rel}`;
|
|
70
|
+
return `\n\n${nameAlreadyShown ? "" : `[附件] ${name}\n`}该附件已上传到服务端(工作区附件目录,具体路径无需关心)。\n需要分析该附件时,请直接用 Read 工具读取:${finalReadPath}(直接读取即可完成分析,无需查找其他位置)`;
|
|
71
|
+
}
|
|
72
|
+
/** 把附件复制到 CLI 进程 cwd 下的 attachments/<rel>(模型 Read 相对路径直接命中)。
|
|
73
|
+
* 关键背景:CodeBuddy SDK/CLI 没有 pathAliases 能力,canUseTool 的 updatedInput 改写
|
|
74
|
+
* 对 Read/Glob 也不生效(实测越界路径未被拦截);绝对虚拟路径 /attachments/<sid>/<rel>
|
|
75
|
+
* 在 Windows 上会被解析到盘符根(C:\attachments\...),永远 "File does not exist"。
|
|
76
|
+
* 因此附件必须放进模型真实的工作目录:
|
|
77
|
+
* - web 沙箱(无工作区):CLI cwd = <用户 .agent>/<sid>(见 sdk.ts cwdOverride),
|
|
78
|
+
* 附件放到 <cwd>/attachments/<rel>,提示相对路径 attachments/<rel>;
|
|
79
|
+
* - 有工作区(桌面 app 场景):CLI cwd = 工作区根,同样放 <cwd>/attachments/<rel>。
|
|
80
|
+
* 返回相对可读路径(空串 = 无附件)。 */
|
|
81
|
+
async function stageVirtualCopy(rel, sid, workspaceDir) {
|
|
82
|
+
const cliCwd = workspaceDir && workspaceDir.trim()
|
|
83
|
+
? path.resolve(workspaceDir.trim())
|
|
84
|
+
: path.join(artifactRoot(), sid);
|
|
85
|
+
const dstDir = path.join(cliCwd, "attachments");
|
|
86
|
+
const dst = path.join(dstDir, rel);
|
|
87
|
+
try {
|
|
88
|
+
await fs.promises.mkdir(dstDir, { recursive: true });
|
|
89
|
+
await fs.promises.copyFile(path.join(attachmentRealDir, rel), dst);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// 复制失败不阻塞对话(模型仍可自行定位文件,protectedPaths 精确兜底防泄露)
|
|
93
|
+
console.error(`[agent-kit] stageVirtualCopy failed: ${err instanceof Error ? err.message : err}`);
|
|
94
|
+
}
|
|
95
|
+
return `attachments/${rel}`;
|
|
96
|
+
}
|
|
97
|
+
/** 提取附件相对路径(url 去掉前缀后的部分),无附件/前缀不符时返回空串 */
|
|
98
|
+
function attachmentRel(attachment) {
|
|
99
|
+
if (!attachment || typeof attachment !== "object")
|
|
100
|
+
return "";
|
|
101
|
+
const url = String(attachment.url ?? "");
|
|
102
|
+
if (!url.startsWith(attachmentUrlPrefix))
|
|
103
|
+
return "";
|
|
104
|
+
return url.slice(attachmentUrlPrefix.length).replace(/^\/+/, "");
|
|
105
|
+
}
|
|
106
|
+
async function POST(request) {
|
|
107
|
+
const body = (await request.json().catch(() => ({})));
|
|
108
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
109
|
+
if (messages.length === 0) {
|
|
110
|
+
return new Response("请先输入内容。", {
|
|
111
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// 用户选择的模型:前端模型下拉(agent 界面配置了 AGENT_MODEL_LIST 才显示)。
|
|
115
|
+
// 覆盖默认模型,session 流会在复用会话时 setModel 运行中切换,确保实际生效。
|
|
116
|
+
const userModel = typeof body.model === "string" && body.model.trim() ? body.model.trim() : undefined;
|
|
117
|
+
// 多轮上下文续接:前端每会话生成稳定 sessionId,首轮 resume=false(新建),
|
|
118
|
+
// 后续轮 resume=true(按 sessionId 加载历史 transcript)。无 sessionId 时不续接。
|
|
119
|
+
const sessionId = typeof body.sessionId === "string" && body.sessionId ? body.sessionId : undefined;
|
|
120
|
+
const resume = Boolean(sessionId) && body.resume === true;
|
|
121
|
+
// —— 安全隔离:会话归属校验(登录用户 DB 归属)——
|
|
122
|
+
// 无 sessionId 的 query 单发不涉及跨会话访问,跳过校验。
|
|
123
|
+
if (sessionId) {
|
|
124
|
+
const access = await assertChatAccess(request, sessionId, resume);
|
|
125
|
+
if (!access) {
|
|
126
|
+
// 诊断锚点:403 只有两种成因(未登录 / 库里查不到该 sessionId),
|
|
127
|
+
// 且后者在「前端注册用 sid、发送用 sdkSid」不一致时也会命中——必须能区分,
|
|
128
|
+
// 否则前端只会显示笼统的"会话不属于当前用户",无从下手。
|
|
129
|
+
const u = verifySession(request, loadAuthConfig());
|
|
130
|
+
const dbOn = isDbEnabled();
|
|
131
|
+
let inDb = null;
|
|
132
|
+
if (u?.sub && dbOn) {
|
|
133
|
+
try {
|
|
134
|
+
const row = await getAgentSession(u.sub, sessionId);
|
|
135
|
+
inDb = row ? "found" : "missing";
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
inDb = "query-error:" + (e instanceof Error ? e.message : String(e));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
console.error(`[agent-kit] chat 403: reason=${!u?.sub ? "unauthenticated" : dbOn ? `session-${inDb}` : "db-disabled"}` +
|
|
142
|
+
` resume=${resume} sid=${sessionId} user=${u?.sub ?? "(none)"}`);
|
|
143
|
+
return Response.json({ error: "无权限访问该会话(会话不属于当前用户,或未登录)" }, { status: 403 });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// —— 工作目录(沙箱范围,本地 app)——
|
|
147
|
+
// 权威来源:会话记录里的 workspace_dir(服务端持久化,切会话/重启后仍生效);
|
|
148
|
+
// 首轮注册竞态(会话行还没落库)时回退请求体里的 workspaceDir。
|
|
149
|
+
const bodyWs = typeof body.workspaceDir === "string" && body.workspaceDir.trim()
|
|
150
|
+
? body.workspaceDir.trim()
|
|
151
|
+
: undefined;
|
|
152
|
+
let workspaceDir = bodyWs;
|
|
153
|
+
// 会话归属的用户主体键(sub,titleSink 等流外闭包使用;try 内解析失败时为 undefined)
|
|
154
|
+
let verifiedUserId;
|
|
155
|
+
if (sessionId) {
|
|
156
|
+
try {
|
|
157
|
+
const u = verifySession(request, loadAuthConfig());
|
|
158
|
+
if (u?.sub)
|
|
159
|
+
verifiedUserId = u.sub;
|
|
160
|
+
if (u?.sub && isDbEnabled()) {
|
|
161
|
+
const row = await getAgentSession(u.sub, sessionId);
|
|
162
|
+
if (row)
|
|
163
|
+
workspaceDir = row.workspaceDir ?? workspaceDir;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* DB 查询失败不阻塞对话(本地 app 路径已由 canUseTool 兜底) */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// 附件复制到 CLI 真实 cwd 下(模型 Read 相对路径 attachments/<rel> 直接命中,
|
|
171
|
+
// 零路径改写依赖;绝对虚拟路径 /attachments/... 在 Windows 上读不到,见 stageVirtualCopy 注释);
|
|
172
|
+
// protectedPaths 同时登记 bash 风格与 Windows 风格的完整真实路径(模型若复述完整路径会精确换回虚拟路径),
|
|
173
|
+
// 精确字符串匹配,用户/文档中的合法路径不受影响。
|
|
174
|
+
const rel = attachmentRel(body.attachment);
|
|
175
|
+
let readPath = "";
|
|
176
|
+
if (rel && sessionId)
|
|
177
|
+
readPath = await stageVirtualCopy(rel, sessionId, workspaceDir);
|
|
178
|
+
const realAbs = rel ? path.join(attachmentRealDir, rel) : "";
|
|
179
|
+
// 把附件可读路径提示追加到最后一条 user 消息:模型用 Read 读相对路径即可。
|
|
180
|
+
// existingContent 传最近一条 user 消息原文,附件名已含在内时 hint 不再重复拼接。
|
|
181
|
+
let hint = "";
|
|
182
|
+
{
|
|
183
|
+
let lastUserContent = "";
|
|
184
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
185
|
+
const m = messages[i];
|
|
186
|
+
if (m?.role === "user") {
|
|
187
|
+
lastUserContent = String(m.content ?? "");
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
hint = attachmentHint(body.attachment, lastUserContent, readPath);
|
|
192
|
+
}
|
|
193
|
+
if (hint) {
|
|
194
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
195
|
+
const m = messages[i];
|
|
196
|
+
if (m?.role === "user") {
|
|
197
|
+
m.content = String(m.content ?? "") + hint;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// protectedPaths 同时登记 bash 风格与 Windows 风格的完整真实路径(模型若复述完整路径会精确换回虚拟路径),
|
|
203
|
+
// 精确字符串匹配,用户/文档中的合法路径不受影响。
|
|
204
|
+
const protectedPaths = rel ? [toPosixPath(realAbs), realAbs] : [];
|
|
205
|
+
// Session 常驻模式(有 sessionId):会话跨轮复用同一 CLI 进程,支持运行中 steer;
|
|
206
|
+
// 无 sessionId 时退化为 query 单发(旧行为)。
|
|
207
|
+
slog(sessionId, "http.chat", {
|
|
208
|
+
messages: messages.length,
|
|
209
|
+
resume,
|
|
210
|
+
attachment: body.attachment ? body.attachment.name ?? null : null,
|
|
211
|
+
lastUserPreview: (() => {
|
|
212
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
213
|
+
const m = messages[i];
|
|
214
|
+
if (m?.role === "user")
|
|
215
|
+
return String(m.content ?? "").slice(0, 500);
|
|
216
|
+
}
|
|
217
|
+
return "";
|
|
218
|
+
})(),
|
|
219
|
+
}, "info");
|
|
220
|
+
startSessionSweeper();
|
|
221
|
+
const stream = sessionId
|
|
222
|
+
? createAgentSessionStream({
|
|
223
|
+
messages: messages,
|
|
224
|
+
signal: request.signal,
|
|
225
|
+
config: { ...config, model: userModel ?? config.model, protectedPaths, sessionId, workspaceDir },
|
|
226
|
+
titleSink: (title) => {
|
|
227
|
+
// SDK 自动命名标题落库:用户手动改过(user_renamed)则不覆盖
|
|
228
|
+
if (verifiedUserId && isDbEnabled() && sessionId) {
|
|
229
|
+
void setSessionTitleIfNotUserRenamed(verifiedUserId, sessionId, title).catch(() => { });
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
: createAgentStream({
|
|
234
|
+
messages: messages,
|
|
235
|
+
signal: request.signal,
|
|
236
|
+
config: { ...config, model: userModel ?? config.model, protectedPaths, sessionId, resume, workspaceDir },
|
|
237
|
+
titleSink: (title) => {
|
|
238
|
+
if (verifiedUserId && isDbEnabled() && sessionId) {
|
|
239
|
+
void setSessionTitleIfNotUserRenamed(verifiedUserId, sessionId, title).catch(() => { });
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
return new Response(stream, {
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
246
|
+
"Cache-Control": "no-store",
|
|
247
|
+
"X-Accel-Buffering": "no",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return { POST };
|
|
252
|
+
}
|
|
253
|
+
/** POST /api/chat/answer:前端提交 AskUserQuestion 答案。
|
|
254
|
+
* body: { sessionId, answers: { "0": "选项label", "1": "..." } }
|
|
255
|
+
* 解开挂起在 canUseTool 里的 Promise,让 CLI 把答案作为 tool_result 喂回模型,模型在同一轮继续。 */
|
|
256
|
+
export function createChatAnswerHandler() {
|
|
257
|
+
async function POST(request) {
|
|
258
|
+
const body = (await request.json().catch(() => ({})));
|
|
259
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
260
|
+
const answers = body.answers && typeof body.answers === "object" && !Array.isArray(body.answers)
|
|
261
|
+
? body.answers
|
|
262
|
+
: null;
|
|
263
|
+
if (!sessionId || !answers) {
|
|
264
|
+
return Response.json({ error: "缺少 sessionId 或 answers" }, { status: 400 });
|
|
265
|
+
}
|
|
266
|
+
// 安全隔离:只能向自己的会话提交答案。
|
|
267
|
+
// 注:此处 resume 传 false(与 chat 首轮一致)——answer 发生在本就已鉴权的活动会话内,
|
|
268
|
+
// 挂起的提问是进程内临时态,无需再用 resume=true 强制要求会话已在 DB 落库,避免“能聊天却提交答案 403”。
|
|
269
|
+
const access = await assertChatAccess(request, sessionId, false);
|
|
270
|
+
if (!access) {
|
|
271
|
+
return Response.json({ error: "无权限访问该会话" }, { status: 403 });
|
|
272
|
+
}
|
|
273
|
+
slog(sessionId, "http.answer", { answers }, "info");
|
|
274
|
+
const ok = resolvePendingAsk(sessionId, answers);
|
|
275
|
+
slog(sessionId, "http.answer.result", { ok }, "info");
|
|
276
|
+
return Response.json({ ok });
|
|
277
|
+
}
|
|
278
|
+
return { POST };
|
|
279
|
+
}
|
|
280
|
+
/** POST /api/chat/steer:运行中插入消息(steer)。
|
|
281
|
+
* body: { sessionId, text }
|
|
282
|
+
* 走 Session 常驻会话的 CLI steer 通道(stream-json control_request,CLI >= 2.140.0):
|
|
283
|
+
* 消息进入 SteerInputBuffer,在「下一次模型请求前」注入当前轮,不打断正在执行的工具。
|
|
284
|
+
* 返回 { ok, reason }:ok=true 已插入;ok=false 时 reason 说明(no_session/no_transport/
|
|
285
|
+
* no_session_id/idle/stale/error)。idle 表示当前轮已结束(消息应按正常轮次发送)。 */
|
|
286
|
+
export function createChatSteerHandler() {
|
|
287
|
+
async function POST(request) {
|
|
288
|
+
const body = (await request.json().catch(() => ({})));
|
|
289
|
+
const sessionId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
290
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
291
|
+
if (!sessionId || !text) {
|
|
292
|
+
return Response.json({ error: "缺少 sessionId 或 text" }, { status: 400 });
|
|
293
|
+
}
|
|
294
|
+
// 安全隔离:只能向自己的会话插入消息
|
|
295
|
+
const access = await assertChatAccess(request, sessionId, true);
|
|
296
|
+
if (!access) {
|
|
297
|
+
return Response.json({ error: "无权限访问该会话" }, { status: 403 });
|
|
298
|
+
}
|
|
299
|
+
slog(sessionId, "http.steer", { textLen: text.length, text }, "info");
|
|
300
|
+
const result = await steerSession(sessionId, text);
|
|
301
|
+
return Response.json({ ok: result.steered, reason: result.reason ?? null });
|
|
302
|
+
}
|
|
303
|
+
return { POST };
|
|
304
|
+
}
|
|
305
|
+
export function createVoiceTokenHandler(options = {}) {
|
|
306
|
+
async function POST(request) {
|
|
307
|
+
const user = (await options.userResolver?.(request)) ?? { userId: "dev-user", name: "测试用户" };
|
|
308
|
+
return Response.json(createVoiceToken(user, { ttlSeconds: options.ttlSeconds }));
|
|
309
|
+
}
|
|
310
|
+
return { POST };
|
|
311
|
+
}
|
|
312
|
+
/** POST /api/chat/browser-answer:control_browser 人工接管(图形验证码等)答案回传。
|
|
313
|
+
* body: { sessionId: browser 会话 id(browser_ask 事件 data.id), text, chatSessionId }
|
|
314
|
+
* 先校验会话归属(只能给自己的会话回答案),再把文本注入 BrowserAgentService 的等待者。
|
|
315
|
+
* browser 会话 id 为随机短时令牌、不可枚举,且本端点只做「注入文本」单一动作,无数据读取面。 */
|
|
316
|
+
export function createBrowserAnswerHandler() {
|
|
317
|
+
async function POST(request) {
|
|
318
|
+
const body = (await request.json().catch(() => ({})));
|
|
319
|
+
const browserId = typeof body.sessionId === "string" ? body.sessionId : "";
|
|
320
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
321
|
+
const chatSessionId = typeof body.chatSessionId === "string" ? body.chatSessionId : "";
|
|
322
|
+
if (!browserId || !text) {
|
|
323
|
+
return Response.json({ error: "缺少 sessionId 或 text" }, { status: 400 });
|
|
324
|
+
}
|
|
325
|
+
// 安全隔离:只能给自己的会话提交人工接管答案
|
|
326
|
+
if (chatSessionId) {
|
|
327
|
+
const access = await assertChatAccess(request, chatSessionId, true);
|
|
328
|
+
if (!access) {
|
|
329
|
+
return Response.json({ error: "无权限访问该会话" }, { status: 403 });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const ok = BrowserAgentService.get().input(browserId, text);
|
|
333
|
+
slog(browserId, "http.browser-answer", { ok, textLen: text.length }, "info");
|
|
334
|
+
return Response.json({ ok });
|
|
335
|
+
}
|
|
336
|
+
return { POST };
|
|
337
|
+
}
|
|
338
|
+
const MAX_AUDIO_BYTES = 20 * 1024 * 1024;
|
|
339
|
+
/** POST /api/voice/transcribe:校验令牌 → ASR 转写 → { text } */
|
|
340
|
+
export function createVoiceTranscribeHandler(options = {}) {
|
|
341
|
+
async function POST(request) {
|
|
342
|
+
const header = request.headers.get("authorization") || "";
|
|
343
|
+
const match = header.match(/^Bearer\s+(.+)$/i);
|
|
344
|
+
const auth = verifyVoiceToken(match?.[1] ?? null);
|
|
345
|
+
if (!auth) {
|
|
346
|
+
return Response.json({ error: "语音令牌无效或已过期,请重新获取" }, { status: 401 });
|
|
347
|
+
}
|
|
348
|
+
let form;
|
|
349
|
+
try {
|
|
350
|
+
form = await request.formData();
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return Response.json({ error: "请求格式错误" }, { status: 400 });
|
|
354
|
+
}
|
|
355
|
+
const file = form.get("file");
|
|
356
|
+
if (!file || typeof file === "string" || !file.size) {
|
|
357
|
+
return Response.json({ error: "没有收到录音数据" }, { status: 400 });
|
|
358
|
+
}
|
|
359
|
+
if (file.size > MAX_AUDIO_BYTES) {
|
|
360
|
+
return Response.json({ error: "录音文件过大(超过 20MB)" }, { status: 413 });
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const result = await transcribeVoice({
|
|
364
|
+
bytes: Buffer.from(await file.arrayBuffer()),
|
|
365
|
+
mimeType: file.type,
|
|
366
|
+
fileName: file.name,
|
|
367
|
+
});
|
|
368
|
+
const text = (result.text || "").trim();
|
|
369
|
+
if (!text) {
|
|
370
|
+
return Response.json({ error: "没有识别到清晰的语音", code: "ASR_EMPTY_TRANSCRIPT" }, { status: 422 });
|
|
371
|
+
}
|
|
372
|
+
return Response.json({ text });
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.error("[agent-kit voice/transcribe] failed", error);
|
|
376
|
+
return Response.json({ error: error instanceof Error ? error.message : "语音识别暂时不可用,请稍后重试" }, { status: 502 });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return { POST };
|
|
380
|
+
}
|
|
381
|
+
/** 一键获得全部端点:{ chat, chatAnswer, chatSteer, upload, voiceToken, voiceTranscribe, artifact } */
|
|
382
|
+
export function agentKitHandlers(options = {}) {
|
|
383
|
+
return {
|
|
384
|
+
chat: createChatHandler(options.chat),
|
|
385
|
+
chatAnswer: createChatAnswerHandler(),
|
|
386
|
+
chatSteer: createChatSteerHandler(),
|
|
387
|
+
browserAnswer: createBrowserAnswerHandler(),
|
|
388
|
+
upload: createUploadHandler(options.upload),
|
|
389
|
+
voiceToken: createVoiceTokenHandler(options.voice),
|
|
390
|
+
voiceTranscribe: createVoiceTranscribeHandler(options.voice),
|
|
391
|
+
artifact: createArtifactHandler(),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* GET /api/artifact?session=<sessionId>:产物端点,三种形态:
|
|
396
|
+
* 1. 不带 file 参数但带 list=1 → 返回该会话全部产物 JSON({ artifacts: [{id,name,type,size}] }),
|
|
397
|
+
* 供「查看所有产物 (N)」按钮与右侧产物列表面板使用(递归含子目录)。
|
|
398
|
+
* 2. ?session=&file=<id> → 按会话隔离返回产物文件内容(预览:iframe / img / 文本)。
|
|
399
|
+
* 3. 在上述基础上带 download=1 → Content-Disposition: attachment,浏览器直接下载。
|
|
400
|
+
* 服务端二次校验文件必须落在 <sessionId> 目录内(readArtifact 防穿越),
|
|
401
|
+
* HTML 以 CSP sandbox 返回,前端再以 sandbox iframe 渲染,杜绝脚本执行与跨域访问。
|
|
402
|
+
*/
|
|
403
|
+
export function createArtifactHandler() {
|
|
404
|
+
async function GET(request) {
|
|
405
|
+
const url = new URL(request.url);
|
|
406
|
+
const session = url.searchParams.get("session") || "";
|
|
407
|
+
if (!session) {
|
|
408
|
+
return new Response("缺少 session 参数", { status: 400 });
|
|
409
|
+
}
|
|
410
|
+
// 安全隔离:登录用户只能访问自己名下的会话(DB 归属)
|
|
411
|
+
const access = await assertSessionAccess(request, session);
|
|
412
|
+
if (!access) {
|
|
413
|
+
return new Response("无权限访问该会话的产物(会话不属于当前用户,或票据无效/过期)", { status: 403 });
|
|
414
|
+
}
|
|
415
|
+
// 工作区模式:产物根随会话的 workspace_dir 变化(本地 app 选择目录后产物落在工作区内)
|
|
416
|
+
let workspaceDir;
|
|
417
|
+
try {
|
|
418
|
+
const u = verifySession(request, loadAuthConfig());
|
|
419
|
+
if (u?.sub && isDbEnabled()) {
|
|
420
|
+
const row = await getAgentSession(u.sub, session);
|
|
421
|
+
if (row)
|
|
422
|
+
workspaceDir = row.workspaceDir ?? undefined;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
/* 解析失败按无工作区处理 */
|
|
427
|
+
}
|
|
428
|
+
// 列表模式:?list=1
|
|
429
|
+
if (url.searchParams.get("list") !== null) {
|
|
430
|
+
const artifacts = await listSessionArtifacts(session, workspaceDir);
|
|
431
|
+
return Response.json({ artifacts });
|
|
432
|
+
}
|
|
433
|
+
// 打开产物目录:?dir=1(仅本地 app 前端调用)→ 返回「跟随文件」的目录:
|
|
434
|
+
// 有产物时返回最新产物的真实所在目录(模型写到任意位置的文件 → 其父目录;
|
|
435
|
+
// 会话目录内文件 → 会话目录),无产物时回落本会话产物目录。前端(Electron preload 桥)
|
|
436
|
+
// 据此调用主进程 shell.openPath 打开资源管理器。
|
|
437
|
+
if (url.searchParams.get("dir") !== null) {
|
|
438
|
+
const artifacts = await listSessionArtifacts(session, workspaceDir);
|
|
439
|
+
const latest = Array.isArray(artifacts) ? artifacts.find((a) => a.dir) : undefined;
|
|
440
|
+
const dir = latest?.dir ?? (await ensureSessionDir(session, workspaceDir));
|
|
441
|
+
return Response.json({ dir });
|
|
442
|
+
}
|
|
443
|
+
const file = url.searchParams.get("file") || "";
|
|
444
|
+
if (!file) {
|
|
445
|
+
return new Response("缺少 file 参数", { status: 400 });
|
|
446
|
+
}
|
|
447
|
+
const result = await readArtifact(session, file, workspaceDir);
|
|
448
|
+
if (!result) {
|
|
449
|
+
return new Response("文件不存在或无访问权限", { status: 404 });
|
|
450
|
+
}
|
|
451
|
+
const headers = {
|
|
452
|
+
"Content-Type": result.mime,
|
|
453
|
+
"Cache-Control": "private, no-store",
|
|
454
|
+
"X-Content-Type-Options": "nosniff",
|
|
455
|
+
};
|
|
456
|
+
// 下载模式:Content-Disposition attachment(文件名取最后一段,RFC 5987 编码支持中文)
|
|
457
|
+
if (url.searchParams.get("download") !== null) {
|
|
458
|
+
const fileName = file.split("/").pop() || "artifact";
|
|
459
|
+
headers["Content-Disposition"] = `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// HTML 文档沙箱化:禁止脚本与同域访问;其余类型收紧资源加载
|
|
463
|
+
if (result.mime.startsWith("text/html")) {
|
|
464
|
+
headers["Content-Security-Policy"] = "sandbox";
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
headers["Content-Security-Policy"] =
|
|
468
|
+
"default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; font-src 'self' data:";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Buffer → Uint8Array 视图(零拷贝)后再作为响应体:规避 @types/node 26 的 Buffer 与 BodyInit 类型不兼容
|
|
472
|
+
return new Response(new Uint8Array(result.buf), { headers });
|
|
473
|
+
}
|
|
474
|
+
return { GET };
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Catch-all 单路由:一个文件承载对话 / 上传 / 语音 / 产物端点。
|
|
478
|
+
* 第三方 Next.js 工程只需建一个文件(其余 /api 路径仍可自由使用,Next 静态路由优先):
|
|
479
|
+
*
|
|
480
|
+
* // app/api/[...agent]/route.js
|
|
481
|
+
* import { createAgentApiRoute } from "agent-kit";
|
|
482
|
+
* export const runtime = "nodejs";
|
|
483
|
+
* export const POST = createAgentApiRoute({ chat: { continueSession: false } });
|
|
484
|
+
*
|
|
485
|
+
* 内部按路径段分发:/api/chat、/api/upload、/api/voice/token、/api/voice/transcribe;
|
|
486
|
+
* 未知路径返回 404。
|
|
487
|
+
*
|
|
488
|
+
* 注意:若要把会话 / 分享 / 认证等全部端点也一起挂载(应用端零新增路由文件),
|
|
489
|
+
* 改用 createAgentKitApi(同包导出),它是本函数 + 其余端点工厂的统一封装。
|
|
490
|
+
*/
|
|
491
|
+
export function createAgentApiRoute(options = {}) {
|
|
492
|
+
const handlers = agentKitHandlers(options);
|
|
493
|
+
async function POST(request, context = {}) {
|
|
494
|
+
const params = (await Promise.resolve(context.params ?? {}));
|
|
495
|
+
const segments = params.agent ?? [];
|
|
496
|
+
const [first, second] = segments;
|
|
497
|
+
if (first === "chat" && second === "answer")
|
|
498
|
+
return handlers.chatAnswer.POST(request);
|
|
499
|
+
if (first === "chat" && second === "steer")
|
|
500
|
+
return handlers.chatSteer.POST(request);
|
|
501
|
+
if (first === "chat" && second === "browser-answer")
|
|
502
|
+
return handlers.browserAnswer.POST(request);
|
|
503
|
+
if (first === "chat")
|
|
504
|
+
return handlers.chat.POST(request);
|
|
505
|
+
if (first === "upload")
|
|
506
|
+
return handlers.upload.POST(request);
|
|
507
|
+
if (first === "voice" && second === "token")
|
|
508
|
+
return handlers.voiceToken.POST(request);
|
|
509
|
+
if (first === "voice" && second === "transcribe")
|
|
510
|
+
return handlers.voiceTranscribe.POST(request);
|
|
511
|
+
return Response.json({ error: "未找到该端点(agent-kit 支持 /api/chat、/api/chat/answer、/api/chat/steer、/api/chat/browser-answer、/api/upload、/api/voice/token、/api/voice/transcribe)" }, { status: 404 });
|
|
512
|
+
}
|
|
513
|
+
// iframe / 浏览器直接加载产物走 GET(仅 artifact 端点需要)
|
|
514
|
+
async function GET(request, context = {}) {
|
|
515
|
+
const params = (await Promise.resolve(context.params ?? {}));
|
|
516
|
+
const segments = params.agent ?? [];
|
|
517
|
+
const [first] = segments;
|
|
518
|
+
if (first === "artifact")
|
|
519
|
+
return handlers.artifact.GET(request);
|
|
520
|
+
return Response.json({ error: "未找到该端点" }, { status: 404 });
|
|
521
|
+
}
|
|
522
|
+
return { POST, GET };
|
|
523
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-kit · 入口
|
|
3
|
+
* 服务端 SDK:基于 CodeBuddy Agent SDK 的订阅式对话封装(模型默认 hy3),
|
|
4
|
+
* 外加即插即用的 Next.js 端点工厂(对话/上传/语音)。
|
|
5
|
+
*/
|
|
6
|
+
export { runSdkQuery, createAgentStream, createAgentSessionStream, defaultCanUseTool, withSessionDirGuard, withSandboxBoundary, FILE_TOOLS, WEB_DISALLOWED_TOOLS, resolvePendingAsk, steerSession, closeSession, AGENT_SERVICE_UNAVAILABLE, WEB_FETCH_TOOL_NAME } from "./sdk.js";
|
|
7
|
+
export type { SdkConfig } from "./sdk.js";
|
|
8
|
+
export { assertSessionAccess, assertChatAccess } from "./session-access.js";
|
|
9
|
+
export type { SessionAccessKind } from "./session-access.js";
|
|
10
|
+
export { Agent, createAgent } from "./agent.js";
|
|
11
|
+
export type { AgentConfig } from "./agent.js";
|
|
12
|
+
export { createWebFetchMcpServer, WebFetchService } from "./web-fetch.js";
|
|
13
|
+
export type { WebFetchOptions, WebFetchResult, WebFetchImage } from "./web-fetch.js";
|
|
14
|
+
export { createBrowserAgentMcpServer, BROWSER_AGENT_TOOL_NAME, BrowserAgentService } from "./browser-agent-mcp.js";
|
|
15
|
+
export type { BrowserTaskResult } from "./browser-agent-mcp.js";
|
|
16
|
+
export { readArtifact, listSessionArtifacts, mimeFor, ensureSessionDir } from "./artifacts.js";
|
|
17
|
+
export { createChatHandler, createChatAnswerHandler, createChatSteerHandler, createBrowserAnswerHandler, createVoiceTokenHandler, createVoiceTranscribeHandler, agentKitHandlers, createAgentApiRoute, createArtifactHandler, } from "./handlers.js";
|
|
18
|
+
export type { AgentKitHandlersOptions, VoiceHandlerOptions } from "./handlers.js";
|
|
19
|
+
export { createAgentKitApi } from "./kit-api.js";
|
|
20
|
+
export type { AgentKitApiOptions } from "./kit-api.js";
|
|
21
|
+
export { createUploadHandler } from "./upload.js";
|
|
22
|
+
export type { UploadOptions } from "./upload.js";
|
|
23
|
+
export { getModelOptions, readCodeBuddyModels } from "./model-sources.js";
|
|
24
|
+
export type { ServerModelOption } from "./model-sources.js";
|
|
25
|
+
export * from "./auth/index.js";
|
|
26
|
+
export { createSessionHandlers } from "./sessions.js";
|
|
27
|
+
export { createShareHandlers } from "./share.js";
|
|
28
|
+
export type { ShareHandlers } from "./share.js";
|
|
29
|
+
export { createUserFilesHandlers, userFilesRoot, userKeyFor, safeUserFilePath } from "./user-files.js";
|
|
30
|
+
export type { UserFileEntry } from "./user-files.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-kit · 入口
|
|
3
|
+
* 服务端 SDK:基于 CodeBuddy Agent SDK 的订阅式对话封装(模型默认 hy3),
|
|
4
|
+
* 外加即插即用的 Next.js 端点工厂(对话/上传/语音)。
|
|
5
|
+
*/
|
|
6
|
+
export { runSdkQuery, createAgentStream, createAgentSessionStream, defaultCanUseTool, withSessionDirGuard, withSandboxBoundary, FILE_TOOLS, WEB_DISALLOWED_TOOLS, resolvePendingAsk, steerSession, closeSession, AGENT_SERVICE_UNAVAILABLE, WEB_FETCH_TOOL_NAME } from "./sdk.js";
|
|
7
|
+
// 会话访问控制(安全隔离):登录用户 DB 归属
|
|
8
|
+
export { assertSessionAccess, assertChatAccess } from "./session-access.js";
|
|
9
|
+
export { Agent, createAgent } from "./agent.js";
|
|
10
|
+
export { createWebFetchMcpServer, WebFetchService } from "./web-fetch.js";
|
|
11
|
+
export { createBrowserAgentMcpServer, BROWSER_AGENT_TOOL_NAME, BrowserAgentService } from "./browser-agent-mcp.js";
|
|
12
|
+
// 产物读取(Electron 本地版等非 Next.js 宿主复用):按会话隔离读文件 / 列产物清单
|
|
13
|
+
export { readArtifact, listSessionArtifacts, mimeFor, ensureSessionDir } from "./artifacts.js";
|
|
14
|
+
// 端点工厂:第三方 Next.js 应用一行引入即可复用 chat/upload/voice 端点
|
|
15
|
+
export { createChatHandler, createChatAnswerHandler, createChatSteerHandler, createBrowserAnswerHandler, createVoiceTokenHandler, createVoiceTranscribeHandler, agentKitHandlers, createAgentApiRoute, createArtifactHandler, } from "./handlers.js";
|
|
16
|
+
// 统一 API 路由工厂:单 catch-all 挂载全部端点(对话/会话/分享/认证),应用端零新增代码
|
|
17
|
+
export { createAgentKitApi } from "./kit-api.js";
|
|
18
|
+
export { createUploadHandler } from "./upload.js";
|
|
19
|
+
// 模型来源解析(通用能力):models.json 自定义模型优先,否则 env AGENT_MODEL_LIST 兜底
|
|
20
|
+
export { getModelOptions, readCodeBuddyModels } from "./model-sources.js";
|
|
21
|
+
// 认证(钉钉 / 飞书),可复用:抽象层 + 具体实现 + 路由工厂 + 用户持久化
|
|
22
|
+
export * from "./auth/index.js";
|
|
23
|
+
// 会话管理(侧边栏会话列表增删改查)
|
|
24
|
+
export { createSessionHandlers } from "./sessions.js";
|
|
25
|
+
// 会话分享(外部只读访问 + 发送到 OA)
|
|
26
|
+
export { createShareHandlers } from "./share.js";
|
|
27
|
+
// 用户文件区(以登录用户 id 为目录名的用户可操作区:浏览/上传/下载/新建/重命名/删除)
|
|
28
|
+
export { createUserFilesHandlers, userFilesRoot, userKeyFor, safeUserFilePath } from "./user-files.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AgentKitHandlersOptions } from "./handlers.js";
|
|
2
|
+
import type { LoginHandlersOptions } from "./auth/handlers.js";
|
|
3
|
+
export interface AgentKitApiOptions {
|
|
4
|
+
/** 透传给对话 / 上传 / 语音端点工厂(含 allowBash、fallbackModel、userResolver 等) */
|
|
5
|
+
agent?: AgentKitHandlersOptions;
|
|
6
|
+
/** 透传给认证端点工厂(覆盖 env 读取的认证配置) */
|
|
7
|
+
login?: LoginHandlersOptions;
|
|
8
|
+
}
|
|
9
|
+
/** Next.js 路由上下文(params 在 Next 15 为 Promise,需兼容) */
|
|
10
|
+
interface KitRouteContext {
|
|
11
|
+
params?: {
|
|
12
|
+
path?: string[];
|
|
13
|
+
} | Promise<{
|
|
14
|
+
path?: string[];
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createAgentKitApi(options?: AgentKitApiOptions): {
|
|
18
|
+
GET: (request: Request, context?: KitRouteContext) => Promise<Response>;
|
|
19
|
+
POST: (request: Request, context?: KitRouteContext) => Promise<Response>;
|
|
20
|
+
PATCH: (request: Request, context?: KitRouteContext) => Promise<Response>;
|
|
21
|
+
DELETE: (request: Request, context?: KitRouteContext) => Promise<Response>;
|
|
22
|
+
};
|
|
23
|
+
export {};
|