@vibe-lark/larkpal 0.1.56 → 0.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.mjs +367 -135
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -2987,23 +2987,6 @@ async function createLarkpalAgentAdapter() {
|
|
|
2987
2987
|
});
|
|
2988
2988
|
}
|
|
2989
2989
|
else log$29.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
|
|
2990
|
-
try {
|
|
2991
|
-
const { loadSkillsFromDir, registerSkills } = await import("@vibe-lark/larkpal-agent");
|
|
2992
|
-
const { fileURLToPath } = await import("node:url");
|
|
2993
|
-
const skillsDir = join(fileURLToPath(import.meta.resolve("@vibe-lark/larkpal-agent")).replace(/\/dist\/index\.js$/, ""), "src", "skills", "definitions");
|
|
2994
|
-
const skills = await loadSkillsFromDir(skillsDir);
|
|
2995
|
-
if (skills.length > 0) {
|
|
2996
|
-
registerSkills(registry, skills, llm);
|
|
2997
|
-
const skillTools = registry.getAll().filter((t) => t.name.startsWith("skill:"));
|
|
2998
|
-
adapter.registerTools(skillTools);
|
|
2999
|
-
log$29.info("Skill Meta-Tools 注册完成", {
|
|
3000
|
-
skillCount: skills.length,
|
|
3001
|
-
skillNames: skills.map((s) => s.name)
|
|
3002
|
-
});
|
|
3003
|
-
} else log$29.info("未找到 Skill 定义文件", { skillsDir });
|
|
3004
|
-
} catch (err) {
|
|
3005
|
-
log$29.warn("Skill 加载失败(非阻塞)", { error: err?.message });
|
|
3006
|
-
}
|
|
3007
2990
|
return adapter;
|
|
3008
2991
|
}
|
|
3009
2992
|
//#endregion
|
|
@@ -3637,18 +3620,93 @@ function summarizeString(value) {
|
|
|
3637
3620
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3638
3621
|
*/
|
|
3639
3622
|
const log$28 = larkLogger("chat/stream");
|
|
3640
|
-
const
|
|
3623
|
+
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3641
3624
|
/** 统一写 SSE 格式数据到响应流 */
|
|
3642
3625
|
function sendSSE(res, event, data) {
|
|
3643
3626
|
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
3644
3627
|
res.write(`event: ${event}\ndata: ${payload}\n\n`);
|
|
3645
3628
|
}
|
|
3646
3629
|
function isChatSessionRunning(sessionId, processManager) {
|
|
3647
|
-
return
|
|
3630
|
+
return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
|
|
3648
3631
|
}
|
|
3649
3632
|
function logChatAudit(event) {
|
|
3650
3633
|
log$28.info("[stream] Agent audit event", { ...event });
|
|
3651
3634
|
}
|
|
3635
|
+
function prepareSSE(res) {
|
|
3636
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
3637
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3638
|
+
res.setHeader("Connection", "keep-alive");
|
|
3639
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3640
|
+
res.flushHeaders();
|
|
3641
|
+
}
|
|
3642
|
+
function getActiveChatRun(sessionId) {
|
|
3643
|
+
const run = chatRunsBySession.get(sessionId);
|
|
3644
|
+
return run?.status === "running" ? run : void 0;
|
|
3645
|
+
}
|
|
3646
|
+
function serializeChatRun(run) {
|
|
3647
|
+
return {
|
|
3648
|
+
runId: run.runId,
|
|
3649
|
+
sessionId: run.sessionId,
|
|
3650
|
+
requestId: run.requestId,
|
|
3651
|
+
traceId: run.traceId,
|
|
3652
|
+
scenarioId: run.scenarioId,
|
|
3653
|
+
status: run.status,
|
|
3654
|
+
startedAt: run.startedAt,
|
|
3655
|
+
completedAt: run.completedAt,
|
|
3656
|
+
finalStatus: run.finalStatus,
|
|
3657
|
+
error: run.error,
|
|
3658
|
+
textLength: run.text.length
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
function isRunVisibleToUser(run, params) {
|
|
3662
|
+
return run.tenantKey === params.tenantKey && run.openId === params.openId;
|
|
3663
|
+
}
|
|
3664
|
+
function addRunClient(req, res, run) {
|
|
3665
|
+
run.clients.add(res);
|
|
3666
|
+
req.on("close", () => {
|
|
3667
|
+
run.clients.delete(res);
|
|
3668
|
+
log$28.info("[stream] 客户端断开连接", {
|
|
3669
|
+
sessionId: run.sessionId,
|
|
3670
|
+
runId: run.runId
|
|
3671
|
+
});
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
function sendRunStatus(res, run) {
|
|
3675
|
+
sendSSE(res, "run-status", serializeChatRun(run));
|
|
3676
|
+
}
|
|
3677
|
+
function broadcastRunEvent(run, event, data) {
|
|
3678
|
+
for (const client of Array.from(run.clients)) try {
|
|
3679
|
+
sendSSE(client, event, data);
|
|
3680
|
+
} catch (err) {
|
|
3681
|
+
run.clients.delete(client);
|
|
3682
|
+
log$28.warn("[stream] SSE 写入失败,移除客户端", {
|
|
3683
|
+
sessionId: run.sessionId,
|
|
3684
|
+
runId: run.runId,
|
|
3685
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
function broadcastRunStatus(run) {
|
|
3690
|
+
broadcastRunEvent(run, "run-status", serializeChatRun(run));
|
|
3691
|
+
}
|
|
3692
|
+
function endRunClients(run) {
|
|
3693
|
+
for (const client of Array.from(run.clients)) try {
|
|
3694
|
+
client.end();
|
|
3695
|
+
} catch {}
|
|
3696
|
+
run.clients.clear();
|
|
3697
|
+
}
|
|
3698
|
+
function queueRunMessage(run, messageStore, message, label) {
|
|
3699
|
+
run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
|
|
3700
|
+
await messageStore.appendMessage(message);
|
|
3701
|
+
}).catch((err) => {
|
|
3702
|
+
log$28.error(label, {
|
|
3703
|
+
sessionId: run.sessionId,
|
|
3704
|
+
runId: run.runId,
|
|
3705
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3706
|
+
});
|
|
3707
|
+
});
|
|
3708
|
+
return run.persistQueue;
|
|
3709
|
+
}
|
|
3652
3710
|
function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
|
|
3653
3711
|
return {
|
|
3654
3712
|
runtimeEventType: event.type,
|
|
@@ -3686,22 +3744,42 @@ function createChatRouter(config) {
|
|
|
3686
3744
|
router.post("/api/chat/stream", chatAuthMiddleware, async (req, res) => {
|
|
3687
3745
|
const { userId, openId, tenantKey } = res.locals.chatUser;
|
|
3688
3746
|
const body = req.body;
|
|
3689
|
-
if (!body.prompt || typeof body.prompt !== "string") {
|
|
3690
|
-
res.status(400).json({ error: "prompt is required and must be a string" });
|
|
3691
|
-
return;
|
|
3692
|
-
}
|
|
3693
3747
|
if (!tenantKey || !userId || !openId) {
|
|
3694
3748
|
res.status(401).json({ error: "Missing required chat identity: tenantKey, userId, openId" });
|
|
3695
3749
|
return;
|
|
3696
3750
|
}
|
|
3697
3751
|
const requestId = getGatewayRequestId(req.headers);
|
|
3698
3752
|
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
3753
|
+
let sessionId = body.session_id;
|
|
3754
|
+
const activeRun = sessionId ? getActiveChatRun(sessionId) : void 0;
|
|
3755
|
+
if (activeRun) {
|
|
3756
|
+
if (!isRunVisibleToUser(activeRun, {
|
|
3757
|
+
tenantKey,
|
|
3758
|
+
openId
|
|
3759
|
+
})) {
|
|
3760
|
+
res.status(403).json({ error: "Forbidden" });
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
prepareSSE(res);
|
|
3764
|
+
addRunClient(req, res, activeRun);
|
|
3765
|
+
sendSSE(res, "session", { sessionId });
|
|
3766
|
+
sendRunStatus(res, activeRun);
|
|
3767
|
+
log$28.info("[stream] 已重新连接到运行中的 run", {
|
|
3768
|
+
sessionId,
|
|
3769
|
+
runId: activeRun.runId
|
|
3770
|
+
});
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
if (!body.prompt || typeof body.prompt !== "string") {
|
|
3774
|
+
res.status(400).json({ error: "prompt is required and must be a string" });
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3699
3777
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
3700
3778
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
3701
3779
|
const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
|
|
3702
3780
|
log$28.info("[stream] 收到流式对话请求", {
|
|
3703
3781
|
userId,
|
|
3704
|
-
sessionId
|
|
3782
|
+
sessionId,
|
|
3705
3783
|
requestId,
|
|
3706
3784
|
traceId,
|
|
3707
3785
|
scenarioId,
|
|
@@ -3709,7 +3787,6 @@ function createChatRouter(config) {
|
|
|
3709
3787
|
hasSourceArtifacts: sourceArtifacts !== void 0,
|
|
3710
3788
|
promptLength: body.prompt.length
|
|
3711
3789
|
});
|
|
3712
|
-
let sessionId = body.session_id;
|
|
3713
3790
|
if (!sessionId) {
|
|
3714
3791
|
sessionId = (await messageStore.createSession({
|
|
3715
3792
|
id: `client_${openId}_${Date.now().toString(36)}`,
|
|
@@ -3723,22 +3800,85 @@ function createChatRouter(config) {
|
|
|
3723
3800
|
});
|
|
3724
3801
|
}
|
|
3725
3802
|
if (isChatSessionRunning(sessionId, processManager)) {
|
|
3726
|
-
log$28.warn("[stream] session
|
|
3803
|
+
log$28.warn("[stream] session 正在被非 Web Chat run 占用", {
|
|
3727
3804
|
tenantKey,
|
|
3728
3805
|
userId,
|
|
3729
3806
|
sessionId
|
|
3730
3807
|
});
|
|
3731
|
-
res
|
|
3732
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
3733
|
-
res.setHeader("Connection", "keep-alive");
|
|
3734
|
-
res.setHeader("X-Accel-Buffering", "no");
|
|
3735
|
-
res.flushHeaders();
|
|
3808
|
+
prepareSSE(res);
|
|
3736
3809
|
sendSSE(res, "session", { sessionId });
|
|
3737
3810
|
sendSSE(res, "error", { message: `Session ${sessionId} is already running` });
|
|
3738
3811
|
res.end();
|
|
3739
3812
|
return;
|
|
3740
3813
|
}
|
|
3741
|
-
|
|
3814
|
+
const runtimeConfig = buildAgentRuntimeConfig({
|
|
3815
|
+
requestId,
|
|
3816
|
+
traceId,
|
|
3817
|
+
sessionId,
|
|
3818
|
+
conversationId: sessionId,
|
|
3819
|
+
entrypoint: "chat",
|
|
3820
|
+
scenarioId,
|
|
3821
|
+
prompt: body.prompt,
|
|
3822
|
+
maxTurns: body.options?.maxTurns,
|
|
3823
|
+
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3824
|
+
model: process.env.CLAUDE_MODEL || void 0,
|
|
3825
|
+
attachments,
|
|
3826
|
+
sourceArtifacts,
|
|
3827
|
+
policy: body.options?.policy,
|
|
3828
|
+
metadata: body.options?.metadata,
|
|
3829
|
+
identity: {
|
|
3830
|
+
tenantKey,
|
|
3831
|
+
userId,
|
|
3832
|
+
openId,
|
|
3833
|
+
userName: userId
|
|
3834
|
+
}
|
|
3835
|
+
});
|
|
3836
|
+
const run = {
|
|
3837
|
+
runId: requestId,
|
|
3838
|
+
sessionId,
|
|
3839
|
+
requestId,
|
|
3840
|
+
traceId,
|
|
3841
|
+
scenarioId,
|
|
3842
|
+
tenantKey,
|
|
3843
|
+
userId,
|
|
3844
|
+
openId,
|
|
3845
|
+
status: "running",
|
|
3846
|
+
startedAt: Date.now(),
|
|
3847
|
+
text: "",
|
|
3848
|
+
clients: /* @__PURE__ */ new Set(),
|
|
3849
|
+
runtimeConfig,
|
|
3850
|
+
persistQueue: Promise.resolve()
|
|
3851
|
+
};
|
|
3852
|
+
const appendRunStatusMessage = () => {
|
|
3853
|
+
return queueRunMessage(run, messageStore, {
|
|
3854
|
+
sessionId,
|
|
3855
|
+
role: "tool",
|
|
3856
|
+
content: JSON.stringify({
|
|
3857
|
+
runtimeEventType: "run-status",
|
|
3858
|
+
run: serializeChatRun(run)
|
|
3859
|
+
}),
|
|
3860
|
+
channel: "web",
|
|
3861
|
+
metadata: {
|
|
3862
|
+
runId: run.runId,
|
|
3863
|
+
requestId,
|
|
3864
|
+
traceId,
|
|
3865
|
+
scenarioId,
|
|
3866
|
+
runtimeEventType: "run-status",
|
|
3867
|
+
finalStatus: run.finalStatus,
|
|
3868
|
+
errorMessage: run.error
|
|
3869
|
+
}
|
|
3870
|
+
}, "[stream] 保存 run status 消息失败");
|
|
3871
|
+
};
|
|
3872
|
+
const markRunTerminal = async (status, params) => {
|
|
3873
|
+
if (run.status !== "running") return false;
|
|
3874
|
+
run.status = status;
|
|
3875
|
+
run.completedAt = Date.now();
|
|
3876
|
+
run.finalStatus = params.finalStatus;
|
|
3877
|
+
run.error = params.error;
|
|
3878
|
+
broadcastRunStatus(run);
|
|
3879
|
+
await appendRunStatusMessage();
|
|
3880
|
+
return true;
|
|
3881
|
+
};
|
|
3742
3882
|
try {
|
|
3743
3883
|
await messageStore.appendMessage({
|
|
3744
3884
|
sessionId,
|
|
@@ -3746,6 +3886,7 @@ function createChatRouter(config) {
|
|
|
3746
3886
|
content: body.prompt,
|
|
3747
3887
|
channel: "web",
|
|
3748
3888
|
metadata: {
|
|
3889
|
+
runId: run.runId,
|
|
3749
3890
|
requestId,
|
|
3750
3891
|
traceId,
|
|
3751
3892
|
scenarioId,
|
|
@@ -3756,7 +3897,6 @@ function createChatRouter(config) {
|
|
|
3756
3897
|
}
|
|
3757
3898
|
});
|
|
3758
3899
|
} catch (err) {
|
|
3759
|
-
activeStreamSessions.delete(sessionId);
|
|
3760
3900
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3761
3901
|
log$28.error("[stream] 保存用户消息失败", {
|
|
3762
3902
|
sessionId,
|
|
@@ -3765,34 +3905,12 @@ function createChatRouter(config) {
|
|
|
3765
3905
|
res.status(500).json({ error: "Failed to append user message" });
|
|
3766
3906
|
return;
|
|
3767
3907
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
res
|
|
3771
|
-
|
|
3772
|
-
res.flushHeaders();
|
|
3908
|
+
chatRunsBySession.set(sessionId, run);
|
|
3909
|
+
appendRunStatusMessage();
|
|
3910
|
+
prepareSSE(res);
|
|
3911
|
+
addRunClient(req, res, run);
|
|
3773
3912
|
sendSSE(res, "session", { sessionId });
|
|
3774
|
-
|
|
3775
|
-
requestId,
|
|
3776
|
-
traceId,
|
|
3777
|
-
sessionId,
|
|
3778
|
-
conversationId: sessionId,
|
|
3779
|
-
entrypoint: "chat",
|
|
3780
|
-
scenarioId,
|
|
3781
|
-
prompt: body.prompt,
|
|
3782
|
-
maxTurns: body.options?.maxTurns,
|
|
3783
|
-
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3784
|
-
model: process.env.CLAUDE_MODEL || void 0,
|
|
3785
|
-
attachments,
|
|
3786
|
-
sourceArtifacts,
|
|
3787
|
-
policy: body.options?.policy,
|
|
3788
|
-
metadata: body.options?.metadata,
|
|
3789
|
-
identity: {
|
|
3790
|
-
tenantKey,
|
|
3791
|
-
userId,
|
|
3792
|
-
openId,
|
|
3793
|
-
userName: userId
|
|
3794
|
-
}
|
|
3795
|
-
});
|
|
3913
|
+
sendRunStatus(res, run);
|
|
3796
3914
|
const handleBlockedEvent = (event) => {
|
|
3797
3915
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3798
3916
|
log$28.warn("[stream] Agent blocked event", { ...sanitized });
|
|
@@ -3802,24 +3920,35 @@ function createChatRouter(config) {
|
|
|
3802
3920
|
errorCode: sanitized.reasonCode,
|
|
3803
3921
|
errorMessage: sanitized.reason,
|
|
3804
3922
|
metadata: {
|
|
3923
|
+
runId: run.runId,
|
|
3805
3924
|
traceId,
|
|
3806
3925
|
reasonCode: sanitized.reasonCode
|
|
3807
3926
|
}
|
|
3808
3927
|
}));
|
|
3928
|
+
queueRunMessage(run, messageStore, {
|
|
3929
|
+
sessionId,
|
|
3930
|
+
role: "tool",
|
|
3931
|
+
content: JSON.stringify({ blocked: sanitized }),
|
|
3932
|
+
channel: "web",
|
|
3933
|
+
metadata: {
|
|
3934
|
+
runId: run.runId,
|
|
3935
|
+
requestId,
|
|
3936
|
+
traceId,
|
|
3937
|
+
scenarioId,
|
|
3938
|
+
runtimeEventType: "blocked",
|
|
3939
|
+
toolName: sanitized.name,
|
|
3940
|
+
reasonCode: sanitized.reasonCode
|
|
3941
|
+
}
|
|
3942
|
+
}, "[stream] 保存 blocked 消息失败");
|
|
3943
|
+
broadcastRunEvent(run, "blocked", { event: sanitized });
|
|
3809
3944
|
};
|
|
3810
|
-
let accumText = "";
|
|
3811
|
-
let clientDisconnected = false;
|
|
3812
|
-
req.on("close", () => {
|
|
3813
|
-
clientDisconnected = true;
|
|
3814
|
-
log$28.info("[stream] 客户端断开连接", { sessionId });
|
|
3815
|
-
});
|
|
3816
3945
|
const handleRuntimeEvent = (event) => {
|
|
3817
3946
|
const eventSummary = summarizeForAudit(event);
|
|
3818
3947
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
3819
3948
|
resultSummary: eventSummary,
|
|
3820
3949
|
metadata: buildRuntimeEventAuditMetadata(event)
|
|
3821
3950
|
}));
|
|
3822
|
-
messageStore
|
|
3951
|
+
queueRunMessage(run, messageStore, {
|
|
3823
3952
|
sessionId,
|
|
3824
3953
|
role: "tool",
|
|
3825
3954
|
content: JSON.stringify({
|
|
@@ -3827,47 +3956,47 @@ function createChatRouter(config) {
|
|
|
3827
3956
|
event: eventSummary
|
|
3828
3957
|
}),
|
|
3829
3958
|
channel: "web",
|
|
3830
|
-
metadata:
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
});
|
|
3837
|
-
if (!clientDisconnected) sendSSE(res, "runtime-event", {
|
|
3959
|
+
metadata: {
|
|
3960
|
+
...buildRuntimeEventMessageMetadata(event, runtimeConfig),
|
|
3961
|
+
runId: run.runId
|
|
3962
|
+
}
|
|
3963
|
+
}, "[stream] 保存 runtime event 消息失败");
|
|
3964
|
+
broadcastRunEvent(run, "runtime-event", {
|
|
3838
3965
|
type: event.type,
|
|
3839
3966
|
event: eventSummary
|
|
3840
3967
|
});
|
|
3841
3968
|
};
|
|
3842
3969
|
const callbacks = {
|
|
3843
3970
|
onTextDelta: (text) => {
|
|
3844
|
-
|
|
3845
|
-
|
|
3971
|
+
run.text += text;
|
|
3972
|
+
broadcastRunEvent(run, "text-delta", { text });
|
|
3846
3973
|
},
|
|
3847
3974
|
onThinkingDelta: (text) => {
|
|
3848
|
-
|
|
3975
|
+
broadcastRunEvent(run, "thinking-delta", { text });
|
|
3849
3976
|
},
|
|
3850
3977
|
onToolUseStart: (toolName, toolInput) => {
|
|
3851
3978
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
|
|
3852
3979
|
toolName,
|
|
3853
3980
|
inputSummary: summarizeForAudit(toolInput)
|
|
3854
3981
|
}));
|
|
3855
|
-
messageStore
|
|
3982
|
+
queueRunMessage(run, messageStore, {
|
|
3856
3983
|
sessionId,
|
|
3857
3984
|
role: "tool",
|
|
3858
3985
|
content: JSON.stringify({
|
|
3859
3986
|
toolName,
|
|
3860
|
-
input: toolInput
|
|
3987
|
+
input: summarizeForAudit(toolInput)
|
|
3861
3988
|
}),
|
|
3862
3989
|
channel: "web",
|
|
3863
|
-
metadata: {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3990
|
+
metadata: {
|
|
3991
|
+
runId: run.runId,
|
|
3992
|
+
requestId,
|
|
3993
|
+
traceId,
|
|
3994
|
+
scenarioId,
|
|
3995
|
+
runtimeEventType: "tool-use-start",
|
|
3996
|
+
toolName
|
|
3997
|
+
}
|
|
3998
|
+
}, "[stream] 保存 tool 消息失败");
|
|
3999
|
+
broadcastRunEvent(run, "tool-use-start", {
|
|
3871
4000
|
toolName,
|
|
3872
4001
|
input: toolInput
|
|
3873
4002
|
});
|
|
@@ -3879,62 +4008,80 @@ function createChatRouter(config) {
|
|
|
3879
4008
|
resultSummary,
|
|
3880
4009
|
metadata: buildToolResultAuditMetadata(result)
|
|
3881
4010
|
}));
|
|
3882
|
-
|
|
4011
|
+
queueRunMessage(run, messageStore, {
|
|
4012
|
+
sessionId,
|
|
4013
|
+
role: "tool",
|
|
4014
|
+
content: JSON.stringify({
|
|
4015
|
+
toolUseId,
|
|
4016
|
+
result: resultSummary
|
|
4017
|
+
}),
|
|
4018
|
+
channel: "web",
|
|
4019
|
+
metadata: {
|
|
4020
|
+
runId: run.runId,
|
|
4021
|
+
requestId,
|
|
4022
|
+
traceId,
|
|
4023
|
+
scenarioId,
|
|
4024
|
+
runtimeEventType: "tool-result",
|
|
4025
|
+
toolCallId: toolUseId,
|
|
4026
|
+
toolResult: resultSummary
|
|
4027
|
+
}
|
|
4028
|
+
}, "[stream] 保存 tool result 消息失败");
|
|
4029
|
+
broadcastRunEvent(run, "tool-result", {
|
|
3883
4030
|
toolUseId,
|
|
3884
4031
|
result: resultSummary
|
|
3885
4032
|
});
|
|
3886
4033
|
},
|
|
3887
4034
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
3888
|
-
|
|
4035
|
+
broadcastRunEvent(run, "tool-progress", {
|
|
3889
4036
|
toolName,
|
|
3890
4037
|
elapsedSeconds
|
|
3891
4038
|
});
|
|
3892
4039
|
},
|
|
3893
4040
|
onTurnEnd: (stopReason) => {
|
|
3894
4041
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
|
|
3895
|
-
|
|
4042
|
+
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
3896
4043
|
},
|
|
3897
4044
|
onResult: async (result) => {
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
traceId,
|
|
3909
|
-
requestId
|
|
3910
|
-
}
|
|
3911
|
-
});
|
|
3912
|
-
} catch (err) {
|
|
3913
|
-
log$28.error("[stream] 保存 assistant 消息失败", {
|
|
3914
|
-
sessionId,
|
|
3915
|
-
error: err
|
|
3916
|
-
});
|
|
3917
|
-
}
|
|
3918
|
-
if (!clientDisconnected) {
|
|
3919
|
-
sendSSE(res, "done", {
|
|
3920
|
-
sessionId,
|
|
3921
|
-
result: result.subtype,
|
|
3922
|
-
text: accumText,
|
|
4045
|
+
const finalText = run.text || result.result || "";
|
|
4046
|
+
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4047
|
+
await queueRunMessage(run, messageStore, {
|
|
4048
|
+
sessionId,
|
|
4049
|
+
role: "assistant",
|
|
4050
|
+
content: finalText,
|
|
4051
|
+
channel: "web",
|
|
4052
|
+
metadata: {
|
|
4053
|
+
runId: run.runId,
|
|
4054
|
+
triggeredBy: "web",
|
|
3923
4055
|
durationMs: result.durationMs,
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4056
|
+
numTurns: result.numTurns,
|
|
4057
|
+
traceId,
|
|
4058
|
+
requestId,
|
|
4059
|
+
scenarioId,
|
|
4060
|
+
finalStatus: result.subtype,
|
|
4061
|
+
isError: result.isError
|
|
4062
|
+
}
|
|
4063
|
+
}, "[stream] 保存 assistant 消息失败");
|
|
4064
|
+
broadcastRunEvent(run, "done", {
|
|
4065
|
+
sessionId,
|
|
4066
|
+
runId: run.runId,
|
|
4067
|
+
result: result.subtype,
|
|
4068
|
+
text: finalText,
|
|
4069
|
+
durationMs: result.durationMs,
|
|
4070
|
+
totalCostUsd: result.totalCostUsd,
|
|
4071
|
+
numTurns: result.numTurns
|
|
4072
|
+
});
|
|
4073
|
+
endRunClients(run);
|
|
3929
4074
|
log$28.info("[stream] 对话完成", {
|
|
3930
4075
|
sessionId,
|
|
4076
|
+
runId: run.runId,
|
|
3931
4077
|
result: result.subtype,
|
|
3932
|
-
textLength:
|
|
4078
|
+
textLength: finalText.length,
|
|
3933
4079
|
durationMs: result.durationMs
|
|
3934
4080
|
});
|
|
3935
4081
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_finished", {
|
|
3936
4082
|
durationMs: result.durationMs,
|
|
3937
4083
|
metadata: {
|
|
4084
|
+
runId: run.runId,
|
|
3938
4085
|
subtype: result.subtype,
|
|
3939
4086
|
totalCostUsd: result.totalCostUsd,
|
|
3940
4087
|
numTurns: result.numTurns,
|
|
@@ -3945,22 +4092,45 @@ function createChatRouter(config) {
|
|
|
3945
4092
|
}
|
|
3946
4093
|
}));
|
|
3947
4094
|
},
|
|
3948
|
-
onError: (error) => {
|
|
4095
|
+
onError: async (error) => {
|
|
3949
4096
|
log$28.error("[stream] Runtime 执行出错", {
|
|
3950
4097
|
sessionId,
|
|
4098
|
+
runId: run.runId,
|
|
3951
4099
|
error: error.message
|
|
3952
4100
|
});
|
|
4101
|
+
await queueRunMessage(run, messageStore, {
|
|
4102
|
+
sessionId,
|
|
4103
|
+
role: "assistant",
|
|
4104
|
+
content: error.message,
|
|
4105
|
+
channel: "web",
|
|
4106
|
+
metadata: {
|
|
4107
|
+
runId: run.runId,
|
|
4108
|
+
triggeredBy: "web",
|
|
4109
|
+
traceId,
|
|
4110
|
+
requestId,
|
|
4111
|
+
scenarioId,
|
|
4112
|
+
finalStatus: "error",
|
|
4113
|
+
isError: true,
|
|
4114
|
+
errorMessage: error.message
|
|
4115
|
+
}
|
|
4116
|
+
}, "[stream] 保存 error 消息失败");
|
|
3953
4117
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "error", {
|
|
3954
4118
|
errorMessage: error.message,
|
|
3955
4119
|
metadata: {
|
|
4120
|
+
runId: run.runId,
|
|
3956
4121
|
traceId,
|
|
3957
4122
|
requestId
|
|
3958
4123
|
}
|
|
3959
4124
|
}));
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
}
|
|
4125
|
+
await markRunTerminal("failed", {
|
|
4126
|
+
finalStatus: "error",
|
|
4127
|
+
error: error.message
|
|
4128
|
+
});
|
|
4129
|
+
broadcastRunEvent(run, "error", {
|
|
4130
|
+
runId: run.runId,
|
|
4131
|
+
message: error.message
|
|
4132
|
+
});
|
|
4133
|
+
endRunClients(run);
|
|
3964
4134
|
},
|
|
3965
4135
|
onAuditEvent: (event) => {
|
|
3966
4136
|
logChatAudit({
|
|
@@ -3975,7 +4145,21 @@ function createChatRouter(config) {
|
|
|
3975
4145
|
resultSummary,
|
|
3976
4146
|
metadata: buildVerifierAuditMetadata(result)
|
|
3977
4147
|
}));
|
|
3978
|
-
|
|
4148
|
+
queueRunMessage(run, messageStore, {
|
|
4149
|
+
sessionId,
|
|
4150
|
+
role: "tool",
|
|
4151
|
+
content: JSON.stringify({ verifierResult: resultSummary }),
|
|
4152
|
+
channel: "web",
|
|
4153
|
+
metadata: {
|
|
4154
|
+
runId: run.runId,
|
|
4155
|
+
requestId,
|
|
4156
|
+
traceId,
|
|
4157
|
+
scenarioId,
|
|
4158
|
+
runtimeEventType: "verifier-result",
|
|
4159
|
+
verifierResult: resultSummary
|
|
4160
|
+
}
|
|
4161
|
+
}, "[stream] 保存 verifier result 消息失败");
|
|
4162
|
+
broadcastRunEvent(run, "verifier-result", { result: resultSummary });
|
|
3979
4163
|
},
|
|
3980
4164
|
onRuntimeEvent: handleRuntimeEvent,
|
|
3981
4165
|
onScenarioEvent: handleRuntimeEvent,
|
|
@@ -3984,6 +4168,7 @@ function createChatRouter(config) {
|
|
|
3984
4168
|
};
|
|
3985
4169
|
try {
|
|
3986
4170
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_started", { metadata: {
|
|
4171
|
+
runId: run.runId,
|
|
3987
4172
|
cwd: runtimeConfig.cwd,
|
|
3988
4173
|
maxTurns: runtimeConfig.maxTurns,
|
|
3989
4174
|
maxBudgetUsd: runtimeConfig.maxBudgetUsd,
|
|
@@ -3996,21 +4181,34 @@ function createChatRouter(config) {
|
|
|
3996
4181
|
} }));
|
|
3997
4182
|
log$28.info("[stream] 开始执行 prompt", {
|
|
3998
4183
|
sessionId,
|
|
4184
|
+
runId: run.runId,
|
|
3999
4185
|
cwd: runtimeConfig.cwd
|
|
4000
4186
|
});
|
|
4001
4187
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
4188
|
+
if (run.status === "running") {
|
|
4189
|
+
const errorMessage = "Runtime completed without a final result event";
|
|
4190
|
+
log$28.error("[stream] executePrompt 未返回终态回调", {
|
|
4191
|
+
sessionId,
|
|
4192
|
+
runId: run.runId
|
|
4193
|
+
});
|
|
4194
|
+
await markRunTerminal("failed", {
|
|
4195
|
+
finalStatus: "error",
|
|
4196
|
+
error: errorMessage
|
|
4197
|
+
});
|
|
4198
|
+
broadcastRunEvent(run, "error", {
|
|
4199
|
+
runId: run.runId,
|
|
4200
|
+
message: errorMessage
|
|
4201
|
+
});
|
|
4202
|
+
endRunClients(run);
|
|
4203
|
+
}
|
|
4002
4204
|
} catch (err) {
|
|
4003
4205
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4004
4206
|
log$28.error("[stream] executePrompt 异常", {
|
|
4005
4207
|
sessionId,
|
|
4208
|
+
runId: run.runId,
|
|
4006
4209
|
error: errorMessage
|
|
4007
4210
|
});
|
|
4008
|
-
if (
|
|
4009
|
-
sendSSE(res, "error", { message: errorMessage });
|
|
4010
|
-
res.end();
|
|
4011
|
-
}
|
|
4012
|
-
} finally {
|
|
4013
|
-
activeStreamSessions.delete(sessionId);
|
|
4211
|
+
if (run.status === "running") await callbacks.onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
4014
4212
|
}
|
|
4015
4213
|
});
|
|
4016
4214
|
router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
|
|
@@ -4033,10 +4231,17 @@ function createChatRouter(config) {
|
|
|
4033
4231
|
cursor,
|
|
4034
4232
|
limit
|
|
4035
4233
|
});
|
|
4234
|
+
const run = chatRunsBySession.get(sessionId);
|
|
4235
|
+
const visibleRun = run && isRunVisibleToUser(run, {
|
|
4236
|
+
tenantKey: chatUser.tenantKey,
|
|
4237
|
+
openId: chatUser.openId
|
|
4238
|
+
}) ? run : void 0;
|
|
4036
4239
|
res.json({
|
|
4037
4240
|
messages: result.messages,
|
|
4038
4241
|
nextCursor: result.nextCursor,
|
|
4039
|
-
sessionId
|
|
4242
|
+
sessionId,
|
|
4243
|
+
activeRun: visibleRun?.status === "running" ? serializeChatRun(visibleRun) : null,
|
|
4244
|
+
latestRun: visibleRun ? serializeChatRun(visibleRun) : null
|
|
4040
4245
|
});
|
|
4041
4246
|
} catch (err) {
|
|
4042
4247
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -4047,6 +4252,32 @@ function createChatRouter(config) {
|
|
|
4047
4252
|
res.status(500).json({ error: "Failed to fetch history" });
|
|
4048
4253
|
}
|
|
4049
4254
|
});
|
|
4255
|
+
router.get("/api/chat/runs", chatAuthMiddleware, async (req, res) => {
|
|
4256
|
+
const chatUser = res.locals.chatUser;
|
|
4257
|
+
const sessionId = req.query.session_id;
|
|
4258
|
+
if (!sessionId) {
|
|
4259
|
+
res.status(400).json({ error: "session_id is required" });
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
const run = chatRunsBySession.get(sessionId);
|
|
4263
|
+
if (!run || !isRunVisibleToUser(run, {
|
|
4264
|
+
tenantKey: chatUser.tenantKey,
|
|
4265
|
+
openId: chatUser.openId
|
|
4266
|
+
})) {
|
|
4267
|
+
res.json({
|
|
4268
|
+
sessionId,
|
|
4269
|
+
run: null,
|
|
4270
|
+
activeRun: null
|
|
4271
|
+
});
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
const serialized = serializeChatRun(run);
|
|
4275
|
+
res.json({
|
|
4276
|
+
sessionId,
|
|
4277
|
+
run: serialized,
|
|
4278
|
+
activeRun: run.status === "running" ? serialized : null
|
|
4279
|
+
});
|
|
4280
|
+
});
|
|
4050
4281
|
router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
|
|
4051
4282
|
const chatUser = res.locals.chatUser;
|
|
4052
4283
|
log$28.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
@@ -4096,6 +4327,7 @@ function createChatRouter(config) {
|
|
|
4096
4327
|
});
|
|
4097
4328
|
try {
|
|
4098
4329
|
await messageStore.deleteSession(sessionId);
|
|
4330
|
+
chatRunsBySession.delete(sessionId);
|
|
4099
4331
|
res.json({ success: true });
|
|
4100
4332
|
} catch (err) {
|
|
4101
4333
|
const errorMessage = err instanceof Error ? err.message : String(err);
|