@vibe-lark/larkpal 0.1.56 → 0.1.57
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 -118
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -3637,18 +3637,93 @@ function summarizeString(value) {
|
|
|
3637
3637
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3638
3638
|
*/
|
|
3639
3639
|
const log$28 = larkLogger("chat/stream");
|
|
3640
|
-
const
|
|
3640
|
+
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3641
3641
|
/** 统一写 SSE 格式数据到响应流 */
|
|
3642
3642
|
function sendSSE(res, event, data) {
|
|
3643
3643
|
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
3644
3644
|
res.write(`event: ${event}\ndata: ${payload}\n\n`);
|
|
3645
3645
|
}
|
|
3646
3646
|
function isChatSessionRunning(sessionId, processManager) {
|
|
3647
|
-
return
|
|
3647
|
+
return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
|
|
3648
3648
|
}
|
|
3649
3649
|
function logChatAudit(event) {
|
|
3650
3650
|
log$28.info("[stream] Agent audit event", { ...event });
|
|
3651
3651
|
}
|
|
3652
|
+
function prepareSSE(res) {
|
|
3653
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
3654
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3655
|
+
res.setHeader("Connection", "keep-alive");
|
|
3656
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3657
|
+
res.flushHeaders();
|
|
3658
|
+
}
|
|
3659
|
+
function getActiveChatRun(sessionId) {
|
|
3660
|
+
const run = chatRunsBySession.get(sessionId);
|
|
3661
|
+
return run?.status === "running" ? run : void 0;
|
|
3662
|
+
}
|
|
3663
|
+
function serializeChatRun(run) {
|
|
3664
|
+
return {
|
|
3665
|
+
runId: run.runId,
|
|
3666
|
+
sessionId: run.sessionId,
|
|
3667
|
+
requestId: run.requestId,
|
|
3668
|
+
traceId: run.traceId,
|
|
3669
|
+
scenarioId: run.scenarioId,
|
|
3670
|
+
status: run.status,
|
|
3671
|
+
startedAt: run.startedAt,
|
|
3672
|
+
completedAt: run.completedAt,
|
|
3673
|
+
finalStatus: run.finalStatus,
|
|
3674
|
+
error: run.error,
|
|
3675
|
+
textLength: run.text.length
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
function isRunVisibleToUser(run, params) {
|
|
3679
|
+
return run.tenantKey === params.tenantKey && run.openId === params.openId;
|
|
3680
|
+
}
|
|
3681
|
+
function addRunClient(req, res, run) {
|
|
3682
|
+
run.clients.add(res);
|
|
3683
|
+
req.on("close", () => {
|
|
3684
|
+
run.clients.delete(res);
|
|
3685
|
+
log$28.info("[stream] 客户端断开连接", {
|
|
3686
|
+
sessionId: run.sessionId,
|
|
3687
|
+
runId: run.runId
|
|
3688
|
+
});
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
function sendRunStatus(res, run) {
|
|
3692
|
+
sendSSE(res, "run-status", serializeChatRun(run));
|
|
3693
|
+
}
|
|
3694
|
+
function broadcastRunEvent(run, event, data) {
|
|
3695
|
+
for (const client of Array.from(run.clients)) try {
|
|
3696
|
+
sendSSE(client, event, data);
|
|
3697
|
+
} catch (err) {
|
|
3698
|
+
run.clients.delete(client);
|
|
3699
|
+
log$28.warn("[stream] SSE 写入失败,移除客户端", {
|
|
3700
|
+
sessionId: run.sessionId,
|
|
3701
|
+
runId: run.runId,
|
|
3702
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
function broadcastRunStatus(run) {
|
|
3707
|
+
broadcastRunEvent(run, "run-status", serializeChatRun(run));
|
|
3708
|
+
}
|
|
3709
|
+
function endRunClients(run) {
|
|
3710
|
+
for (const client of Array.from(run.clients)) try {
|
|
3711
|
+
client.end();
|
|
3712
|
+
} catch {}
|
|
3713
|
+
run.clients.clear();
|
|
3714
|
+
}
|
|
3715
|
+
function queueRunMessage(run, messageStore, message, label) {
|
|
3716
|
+
run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
|
|
3717
|
+
await messageStore.appendMessage(message);
|
|
3718
|
+
}).catch((err) => {
|
|
3719
|
+
log$28.error(label, {
|
|
3720
|
+
sessionId: run.sessionId,
|
|
3721
|
+
runId: run.runId,
|
|
3722
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3723
|
+
});
|
|
3724
|
+
});
|
|
3725
|
+
return run.persistQueue;
|
|
3726
|
+
}
|
|
3652
3727
|
function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
|
|
3653
3728
|
return {
|
|
3654
3729
|
runtimeEventType: event.type,
|
|
@@ -3686,22 +3761,42 @@ function createChatRouter(config) {
|
|
|
3686
3761
|
router.post("/api/chat/stream", chatAuthMiddleware, async (req, res) => {
|
|
3687
3762
|
const { userId, openId, tenantKey } = res.locals.chatUser;
|
|
3688
3763
|
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
3764
|
if (!tenantKey || !userId || !openId) {
|
|
3694
3765
|
res.status(401).json({ error: "Missing required chat identity: tenantKey, userId, openId" });
|
|
3695
3766
|
return;
|
|
3696
3767
|
}
|
|
3697
3768
|
const requestId = getGatewayRequestId(req.headers);
|
|
3698
3769
|
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
3770
|
+
let sessionId = body.session_id;
|
|
3771
|
+
const activeRun = sessionId ? getActiveChatRun(sessionId) : void 0;
|
|
3772
|
+
if (activeRun) {
|
|
3773
|
+
if (!isRunVisibleToUser(activeRun, {
|
|
3774
|
+
tenantKey,
|
|
3775
|
+
openId
|
|
3776
|
+
})) {
|
|
3777
|
+
res.status(403).json({ error: "Forbidden" });
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
prepareSSE(res);
|
|
3781
|
+
addRunClient(req, res, activeRun);
|
|
3782
|
+
sendSSE(res, "session", { sessionId });
|
|
3783
|
+
sendRunStatus(res, activeRun);
|
|
3784
|
+
log$28.info("[stream] 已重新连接到运行中的 run", {
|
|
3785
|
+
sessionId,
|
|
3786
|
+
runId: activeRun.runId
|
|
3787
|
+
});
|
|
3788
|
+
return;
|
|
3789
|
+
}
|
|
3790
|
+
if (!body.prompt || typeof body.prompt !== "string") {
|
|
3791
|
+
res.status(400).json({ error: "prompt is required and must be a string" });
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3699
3794
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
3700
3795
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
3701
3796
|
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
3797
|
log$28.info("[stream] 收到流式对话请求", {
|
|
3703
3798
|
userId,
|
|
3704
|
-
sessionId
|
|
3799
|
+
sessionId,
|
|
3705
3800
|
requestId,
|
|
3706
3801
|
traceId,
|
|
3707
3802
|
scenarioId,
|
|
@@ -3709,7 +3804,6 @@ function createChatRouter(config) {
|
|
|
3709
3804
|
hasSourceArtifacts: sourceArtifacts !== void 0,
|
|
3710
3805
|
promptLength: body.prompt.length
|
|
3711
3806
|
});
|
|
3712
|
-
let sessionId = body.session_id;
|
|
3713
3807
|
if (!sessionId) {
|
|
3714
3808
|
sessionId = (await messageStore.createSession({
|
|
3715
3809
|
id: `client_${openId}_${Date.now().toString(36)}`,
|
|
@@ -3723,22 +3817,85 @@ function createChatRouter(config) {
|
|
|
3723
3817
|
});
|
|
3724
3818
|
}
|
|
3725
3819
|
if (isChatSessionRunning(sessionId, processManager)) {
|
|
3726
|
-
log$28.warn("[stream] session
|
|
3820
|
+
log$28.warn("[stream] session 正在被非 Web Chat run 占用", {
|
|
3727
3821
|
tenantKey,
|
|
3728
3822
|
userId,
|
|
3729
3823
|
sessionId
|
|
3730
3824
|
});
|
|
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();
|
|
3825
|
+
prepareSSE(res);
|
|
3736
3826
|
sendSSE(res, "session", { sessionId });
|
|
3737
3827
|
sendSSE(res, "error", { message: `Session ${sessionId} is already running` });
|
|
3738
3828
|
res.end();
|
|
3739
3829
|
return;
|
|
3740
3830
|
}
|
|
3741
|
-
|
|
3831
|
+
const runtimeConfig = buildAgentRuntimeConfig({
|
|
3832
|
+
requestId,
|
|
3833
|
+
traceId,
|
|
3834
|
+
sessionId,
|
|
3835
|
+
conversationId: sessionId,
|
|
3836
|
+
entrypoint: "chat",
|
|
3837
|
+
scenarioId,
|
|
3838
|
+
prompt: body.prompt,
|
|
3839
|
+
maxTurns: body.options?.maxTurns,
|
|
3840
|
+
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3841
|
+
model: process.env.CLAUDE_MODEL || void 0,
|
|
3842
|
+
attachments,
|
|
3843
|
+
sourceArtifacts,
|
|
3844
|
+
policy: body.options?.policy,
|
|
3845
|
+
metadata: body.options?.metadata,
|
|
3846
|
+
identity: {
|
|
3847
|
+
tenantKey,
|
|
3848
|
+
userId,
|
|
3849
|
+
openId,
|
|
3850
|
+
userName: userId
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
const run = {
|
|
3854
|
+
runId: requestId,
|
|
3855
|
+
sessionId,
|
|
3856
|
+
requestId,
|
|
3857
|
+
traceId,
|
|
3858
|
+
scenarioId,
|
|
3859
|
+
tenantKey,
|
|
3860
|
+
userId,
|
|
3861
|
+
openId,
|
|
3862
|
+
status: "running",
|
|
3863
|
+
startedAt: Date.now(),
|
|
3864
|
+
text: "",
|
|
3865
|
+
clients: /* @__PURE__ */ new Set(),
|
|
3866
|
+
runtimeConfig,
|
|
3867
|
+
persistQueue: Promise.resolve()
|
|
3868
|
+
};
|
|
3869
|
+
const appendRunStatusMessage = () => {
|
|
3870
|
+
return queueRunMessage(run, messageStore, {
|
|
3871
|
+
sessionId,
|
|
3872
|
+
role: "tool",
|
|
3873
|
+
content: JSON.stringify({
|
|
3874
|
+
runtimeEventType: "run-status",
|
|
3875
|
+
run: serializeChatRun(run)
|
|
3876
|
+
}),
|
|
3877
|
+
channel: "web",
|
|
3878
|
+
metadata: {
|
|
3879
|
+
runId: run.runId,
|
|
3880
|
+
requestId,
|
|
3881
|
+
traceId,
|
|
3882
|
+
scenarioId,
|
|
3883
|
+
runtimeEventType: "run-status",
|
|
3884
|
+
finalStatus: run.finalStatus,
|
|
3885
|
+
errorMessage: run.error
|
|
3886
|
+
}
|
|
3887
|
+
}, "[stream] 保存 run status 消息失败");
|
|
3888
|
+
};
|
|
3889
|
+
const markRunTerminal = async (status, params) => {
|
|
3890
|
+
if (run.status !== "running") return false;
|
|
3891
|
+
run.status = status;
|
|
3892
|
+
run.completedAt = Date.now();
|
|
3893
|
+
run.finalStatus = params.finalStatus;
|
|
3894
|
+
run.error = params.error;
|
|
3895
|
+
broadcastRunStatus(run);
|
|
3896
|
+
await appendRunStatusMessage();
|
|
3897
|
+
return true;
|
|
3898
|
+
};
|
|
3742
3899
|
try {
|
|
3743
3900
|
await messageStore.appendMessage({
|
|
3744
3901
|
sessionId,
|
|
@@ -3746,6 +3903,7 @@ function createChatRouter(config) {
|
|
|
3746
3903
|
content: body.prompt,
|
|
3747
3904
|
channel: "web",
|
|
3748
3905
|
metadata: {
|
|
3906
|
+
runId: run.runId,
|
|
3749
3907
|
requestId,
|
|
3750
3908
|
traceId,
|
|
3751
3909
|
scenarioId,
|
|
@@ -3756,7 +3914,6 @@ function createChatRouter(config) {
|
|
|
3756
3914
|
}
|
|
3757
3915
|
});
|
|
3758
3916
|
} catch (err) {
|
|
3759
|
-
activeStreamSessions.delete(sessionId);
|
|
3760
3917
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3761
3918
|
log$28.error("[stream] 保存用户消息失败", {
|
|
3762
3919
|
sessionId,
|
|
@@ -3765,34 +3922,12 @@ function createChatRouter(config) {
|
|
|
3765
3922
|
res.status(500).json({ error: "Failed to append user message" });
|
|
3766
3923
|
return;
|
|
3767
3924
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
res
|
|
3771
|
-
|
|
3772
|
-
res.flushHeaders();
|
|
3925
|
+
chatRunsBySession.set(sessionId, run);
|
|
3926
|
+
appendRunStatusMessage();
|
|
3927
|
+
prepareSSE(res);
|
|
3928
|
+
addRunClient(req, res, run);
|
|
3773
3929
|
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
|
-
});
|
|
3930
|
+
sendRunStatus(res, run);
|
|
3796
3931
|
const handleBlockedEvent = (event) => {
|
|
3797
3932
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3798
3933
|
log$28.warn("[stream] Agent blocked event", { ...sanitized });
|
|
@@ -3802,24 +3937,35 @@ function createChatRouter(config) {
|
|
|
3802
3937
|
errorCode: sanitized.reasonCode,
|
|
3803
3938
|
errorMessage: sanitized.reason,
|
|
3804
3939
|
metadata: {
|
|
3940
|
+
runId: run.runId,
|
|
3805
3941
|
traceId,
|
|
3806
3942
|
reasonCode: sanitized.reasonCode
|
|
3807
3943
|
}
|
|
3808
3944
|
}));
|
|
3945
|
+
queueRunMessage(run, messageStore, {
|
|
3946
|
+
sessionId,
|
|
3947
|
+
role: "tool",
|
|
3948
|
+
content: JSON.stringify({ blocked: sanitized }),
|
|
3949
|
+
channel: "web",
|
|
3950
|
+
metadata: {
|
|
3951
|
+
runId: run.runId,
|
|
3952
|
+
requestId,
|
|
3953
|
+
traceId,
|
|
3954
|
+
scenarioId,
|
|
3955
|
+
runtimeEventType: "blocked",
|
|
3956
|
+
toolName: sanitized.name,
|
|
3957
|
+
reasonCode: sanitized.reasonCode
|
|
3958
|
+
}
|
|
3959
|
+
}, "[stream] 保存 blocked 消息失败");
|
|
3960
|
+
broadcastRunEvent(run, "blocked", { event: sanitized });
|
|
3809
3961
|
};
|
|
3810
|
-
let accumText = "";
|
|
3811
|
-
let clientDisconnected = false;
|
|
3812
|
-
req.on("close", () => {
|
|
3813
|
-
clientDisconnected = true;
|
|
3814
|
-
log$28.info("[stream] 客户端断开连接", { sessionId });
|
|
3815
|
-
});
|
|
3816
3962
|
const handleRuntimeEvent = (event) => {
|
|
3817
3963
|
const eventSummary = summarizeForAudit(event);
|
|
3818
3964
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
3819
3965
|
resultSummary: eventSummary,
|
|
3820
3966
|
metadata: buildRuntimeEventAuditMetadata(event)
|
|
3821
3967
|
}));
|
|
3822
|
-
messageStore
|
|
3968
|
+
queueRunMessage(run, messageStore, {
|
|
3823
3969
|
sessionId,
|
|
3824
3970
|
role: "tool",
|
|
3825
3971
|
content: JSON.stringify({
|
|
@@ -3827,47 +3973,47 @@ function createChatRouter(config) {
|
|
|
3827
3973
|
event: eventSummary
|
|
3828
3974
|
}),
|
|
3829
3975
|
channel: "web",
|
|
3830
|
-
metadata:
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
});
|
|
3837
|
-
if (!clientDisconnected) sendSSE(res, "runtime-event", {
|
|
3976
|
+
metadata: {
|
|
3977
|
+
...buildRuntimeEventMessageMetadata(event, runtimeConfig),
|
|
3978
|
+
runId: run.runId
|
|
3979
|
+
}
|
|
3980
|
+
}, "[stream] 保存 runtime event 消息失败");
|
|
3981
|
+
broadcastRunEvent(run, "runtime-event", {
|
|
3838
3982
|
type: event.type,
|
|
3839
3983
|
event: eventSummary
|
|
3840
3984
|
});
|
|
3841
3985
|
};
|
|
3842
3986
|
const callbacks = {
|
|
3843
3987
|
onTextDelta: (text) => {
|
|
3844
|
-
|
|
3845
|
-
|
|
3988
|
+
run.text += text;
|
|
3989
|
+
broadcastRunEvent(run, "text-delta", { text });
|
|
3846
3990
|
},
|
|
3847
3991
|
onThinkingDelta: (text) => {
|
|
3848
|
-
|
|
3992
|
+
broadcastRunEvent(run, "thinking-delta", { text });
|
|
3849
3993
|
},
|
|
3850
3994
|
onToolUseStart: (toolName, toolInput) => {
|
|
3851
3995
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
|
|
3852
3996
|
toolName,
|
|
3853
3997
|
inputSummary: summarizeForAudit(toolInput)
|
|
3854
3998
|
}));
|
|
3855
|
-
messageStore
|
|
3999
|
+
queueRunMessage(run, messageStore, {
|
|
3856
4000
|
sessionId,
|
|
3857
4001
|
role: "tool",
|
|
3858
4002
|
content: JSON.stringify({
|
|
3859
4003
|
toolName,
|
|
3860
|
-
input: toolInput
|
|
4004
|
+
input: summarizeForAudit(toolInput)
|
|
3861
4005
|
}),
|
|
3862
4006
|
channel: "web",
|
|
3863
|
-
metadata: {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
4007
|
+
metadata: {
|
|
4008
|
+
runId: run.runId,
|
|
4009
|
+
requestId,
|
|
4010
|
+
traceId,
|
|
4011
|
+
scenarioId,
|
|
4012
|
+
runtimeEventType: "tool-use-start",
|
|
4013
|
+
toolName
|
|
4014
|
+
}
|
|
4015
|
+
}, "[stream] 保存 tool 消息失败");
|
|
4016
|
+
broadcastRunEvent(run, "tool-use-start", {
|
|
3871
4017
|
toolName,
|
|
3872
4018
|
input: toolInput
|
|
3873
4019
|
});
|
|
@@ -3879,62 +4025,80 @@ function createChatRouter(config) {
|
|
|
3879
4025
|
resultSummary,
|
|
3880
4026
|
metadata: buildToolResultAuditMetadata(result)
|
|
3881
4027
|
}));
|
|
3882
|
-
|
|
4028
|
+
queueRunMessage(run, messageStore, {
|
|
4029
|
+
sessionId,
|
|
4030
|
+
role: "tool",
|
|
4031
|
+
content: JSON.stringify({
|
|
4032
|
+
toolUseId,
|
|
4033
|
+
result: resultSummary
|
|
4034
|
+
}),
|
|
4035
|
+
channel: "web",
|
|
4036
|
+
metadata: {
|
|
4037
|
+
runId: run.runId,
|
|
4038
|
+
requestId,
|
|
4039
|
+
traceId,
|
|
4040
|
+
scenarioId,
|
|
4041
|
+
runtimeEventType: "tool-result",
|
|
4042
|
+
toolCallId: toolUseId,
|
|
4043
|
+
toolResult: resultSummary
|
|
4044
|
+
}
|
|
4045
|
+
}, "[stream] 保存 tool result 消息失败");
|
|
4046
|
+
broadcastRunEvent(run, "tool-result", {
|
|
3883
4047
|
toolUseId,
|
|
3884
4048
|
result: resultSummary
|
|
3885
4049
|
});
|
|
3886
4050
|
},
|
|
3887
4051
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
3888
|
-
|
|
4052
|
+
broadcastRunEvent(run, "tool-progress", {
|
|
3889
4053
|
toolName,
|
|
3890
4054
|
elapsedSeconds
|
|
3891
4055
|
});
|
|
3892
4056
|
},
|
|
3893
4057
|
onTurnEnd: (stopReason) => {
|
|
3894
4058
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
|
|
3895
|
-
|
|
4059
|
+
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
3896
4060
|
},
|
|
3897
4061
|
onResult: async (result) => {
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
numTurns: result.numTurns,
|
|
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,
|
|
4062
|
+
const finalText = run.text || result.result || "";
|
|
4063
|
+
await queueRunMessage(run, messageStore, {
|
|
4064
|
+
sessionId,
|
|
4065
|
+
role: "assistant",
|
|
4066
|
+
content: finalText,
|
|
4067
|
+
channel: "web",
|
|
4068
|
+
metadata: {
|
|
4069
|
+
runId: run.runId,
|
|
4070
|
+
triggeredBy: "web",
|
|
3923
4071
|
durationMs: result.durationMs,
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4072
|
+
numTurns: result.numTurns,
|
|
4073
|
+
traceId,
|
|
4074
|
+
requestId,
|
|
4075
|
+
scenarioId,
|
|
4076
|
+
finalStatus: result.subtype,
|
|
4077
|
+
isError: result.isError
|
|
4078
|
+
}
|
|
4079
|
+
}, "[stream] 保存 assistant 消息失败");
|
|
4080
|
+
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4081
|
+
broadcastRunEvent(run, "done", {
|
|
4082
|
+
sessionId,
|
|
4083
|
+
runId: run.runId,
|
|
4084
|
+
result: result.subtype,
|
|
4085
|
+
text: finalText,
|
|
4086
|
+
durationMs: result.durationMs,
|
|
4087
|
+
totalCostUsd: result.totalCostUsd,
|
|
4088
|
+
numTurns: result.numTurns
|
|
4089
|
+
});
|
|
4090
|
+
endRunClients(run);
|
|
3929
4091
|
log$28.info("[stream] 对话完成", {
|
|
3930
4092
|
sessionId,
|
|
4093
|
+
runId: run.runId,
|
|
3931
4094
|
result: result.subtype,
|
|
3932
|
-
textLength:
|
|
4095
|
+
textLength: finalText.length,
|
|
3933
4096
|
durationMs: result.durationMs
|
|
3934
4097
|
});
|
|
3935
4098
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_finished", {
|
|
3936
4099
|
durationMs: result.durationMs,
|
|
3937
4100
|
metadata: {
|
|
4101
|
+
runId: run.runId,
|
|
3938
4102
|
subtype: result.subtype,
|
|
3939
4103
|
totalCostUsd: result.totalCostUsd,
|
|
3940
4104
|
numTurns: result.numTurns,
|
|
@@ -3945,22 +4109,45 @@ function createChatRouter(config) {
|
|
|
3945
4109
|
}
|
|
3946
4110
|
}));
|
|
3947
4111
|
},
|
|
3948
|
-
onError: (error) => {
|
|
4112
|
+
onError: async (error) => {
|
|
3949
4113
|
log$28.error("[stream] Runtime 执行出错", {
|
|
3950
4114
|
sessionId,
|
|
4115
|
+
runId: run.runId,
|
|
3951
4116
|
error: error.message
|
|
3952
4117
|
});
|
|
4118
|
+
await queueRunMessage(run, messageStore, {
|
|
4119
|
+
sessionId,
|
|
4120
|
+
role: "assistant",
|
|
4121
|
+
content: error.message,
|
|
4122
|
+
channel: "web",
|
|
4123
|
+
metadata: {
|
|
4124
|
+
runId: run.runId,
|
|
4125
|
+
triggeredBy: "web",
|
|
4126
|
+
traceId,
|
|
4127
|
+
requestId,
|
|
4128
|
+
scenarioId,
|
|
4129
|
+
finalStatus: "error",
|
|
4130
|
+
isError: true,
|
|
4131
|
+
errorMessage: error.message
|
|
4132
|
+
}
|
|
4133
|
+
}, "[stream] 保存 error 消息失败");
|
|
3953
4134
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "error", {
|
|
3954
4135
|
errorMessage: error.message,
|
|
3955
4136
|
metadata: {
|
|
4137
|
+
runId: run.runId,
|
|
3956
4138
|
traceId,
|
|
3957
4139
|
requestId
|
|
3958
4140
|
}
|
|
3959
4141
|
}));
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
}
|
|
4142
|
+
await markRunTerminal("failed", {
|
|
4143
|
+
finalStatus: "error",
|
|
4144
|
+
error: error.message
|
|
4145
|
+
});
|
|
4146
|
+
broadcastRunEvent(run, "error", {
|
|
4147
|
+
runId: run.runId,
|
|
4148
|
+
message: error.message
|
|
4149
|
+
});
|
|
4150
|
+
endRunClients(run);
|
|
3964
4151
|
},
|
|
3965
4152
|
onAuditEvent: (event) => {
|
|
3966
4153
|
logChatAudit({
|
|
@@ -3975,7 +4162,21 @@ function createChatRouter(config) {
|
|
|
3975
4162
|
resultSummary,
|
|
3976
4163
|
metadata: buildVerifierAuditMetadata(result)
|
|
3977
4164
|
}));
|
|
3978
|
-
|
|
4165
|
+
queueRunMessage(run, messageStore, {
|
|
4166
|
+
sessionId,
|
|
4167
|
+
role: "tool",
|
|
4168
|
+
content: JSON.stringify({ verifierResult: resultSummary }),
|
|
4169
|
+
channel: "web",
|
|
4170
|
+
metadata: {
|
|
4171
|
+
runId: run.runId,
|
|
4172
|
+
requestId,
|
|
4173
|
+
traceId,
|
|
4174
|
+
scenarioId,
|
|
4175
|
+
runtimeEventType: "verifier-result",
|
|
4176
|
+
verifierResult: resultSummary
|
|
4177
|
+
}
|
|
4178
|
+
}, "[stream] 保存 verifier result 消息失败");
|
|
4179
|
+
broadcastRunEvent(run, "verifier-result", { result: resultSummary });
|
|
3979
4180
|
},
|
|
3980
4181
|
onRuntimeEvent: handleRuntimeEvent,
|
|
3981
4182
|
onScenarioEvent: handleRuntimeEvent,
|
|
@@ -3984,6 +4185,7 @@ function createChatRouter(config) {
|
|
|
3984
4185
|
};
|
|
3985
4186
|
try {
|
|
3986
4187
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_started", { metadata: {
|
|
4188
|
+
runId: run.runId,
|
|
3987
4189
|
cwd: runtimeConfig.cwd,
|
|
3988
4190
|
maxTurns: runtimeConfig.maxTurns,
|
|
3989
4191
|
maxBudgetUsd: runtimeConfig.maxBudgetUsd,
|
|
@@ -3996,21 +4198,34 @@ function createChatRouter(config) {
|
|
|
3996
4198
|
} }));
|
|
3997
4199
|
log$28.info("[stream] 开始执行 prompt", {
|
|
3998
4200
|
sessionId,
|
|
4201
|
+
runId: run.runId,
|
|
3999
4202
|
cwd: runtimeConfig.cwd
|
|
4000
4203
|
});
|
|
4001
4204
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
4205
|
+
if (run.status === "running") {
|
|
4206
|
+
const errorMessage = "Runtime completed without a final result event";
|
|
4207
|
+
log$28.error("[stream] executePrompt 未返回终态回调", {
|
|
4208
|
+
sessionId,
|
|
4209
|
+
runId: run.runId
|
|
4210
|
+
});
|
|
4211
|
+
await markRunTerminal("failed", {
|
|
4212
|
+
finalStatus: "error",
|
|
4213
|
+
error: errorMessage
|
|
4214
|
+
});
|
|
4215
|
+
broadcastRunEvent(run, "error", {
|
|
4216
|
+
runId: run.runId,
|
|
4217
|
+
message: errorMessage
|
|
4218
|
+
});
|
|
4219
|
+
endRunClients(run);
|
|
4220
|
+
}
|
|
4002
4221
|
} catch (err) {
|
|
4003
4222
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4004
4223
|
log$28.error("[stream] executePrompt 异常", {
|
|
4005
4224
|
sessionId,
|
|
4225
|
+
runId: run.runId,
|
|
4006
4226
|
error: errorMessage
|
|
4007
4227
|
});
|
|
4008
|
-
if (
|
|
4009
|
-
sendSSE(res, "error", { message: errorMessage });
|
|
4010
|
-
res.end();
|
|
4011
|
-
}
|
|
4012
|
-
} finally {
|
|
4013
|
-
activeStreamSessions.delete(sessionId);
|
|
4228
|
+
if (run.status === "running") await callbacks.onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
4014
4229
|
}
|
|
4015
4230
|
});
|
|
4016
4231
|
router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
|
|
@@ -4033,10 +4248,17 @@ function createChatRouter(config) {
|
|
|
4033
4248
|
cursor,
|
|
4034
4249
|
limit
|
|
4035
4250
|
});
|
|
4251
|
+
const run = chatRunsBySession.get(sessionId);
|
|
4252
|
+
const visibleRun = run && isRunVisibleToUser(run, {
|
|
4253
|
+
tenantKey: chatUser.tenantKey,
|
|
4254
|
+
openId: chatUser.openId
|
|
4255
|
+
}) ? run : void 0;
|
|
4036
4256
|
res.json({
|
|
4037
4257
|
messages: result.messages,
|
|
4038
4258
|
nextCursor: result.nextCursor,
|
|
4039
|
-
sessionId
|
|
4259
|
+
sessionId,
|
|
4260
|
+
activeRun: visibleRun?.status === "running" ? serializeChatRun(visibleRun) : null,
|
|
4261
|
+
latestRun: visibleRun ? serializeChatRun(visibleRun) : null
|
|
4040
4262
|
});
|
|
4041
4263
|
} catch (err) {
|
|
4042
4264
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -4047,6 +4269,32 @@ function createChatRouter(config) {
|
|
|
4047
4269
|
res.status(500).json({ error: "Failed to fetch history" });
|
|
4048
4270
|
}
|
|
4049
4271
|
});
|
|
4272
|
+
router.get("/api/chat/runs", chatAuthMiddleware, async (req, res) => {
|
|
4273
|
+
const chatUser = res.locals.chatUser;
|
|
4274
|
+
const sessionId = req.query.session_id;
|
|
4275
|
+
if (!sessionId) {
|
|
4276
|
+
res.status(400).json({ error: "session_id is required" });
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
const run = chatRunsBySession.get(sessionId);
|
|
4280
|
+
if (!run || !isRunVisibleToUser(run, {
|
|
4281
|
+
tenantKey: chatUser.tenantKey,
|
|
4282
|
+
openId: chatUser.openId
|
|
4283
|
+
})) {
|
|
4284
|
+
res.json({
|
|
4285
|
+
sessionId,
|
|
4286
|
+
run: null,
|
|
4287
|
+
activeRun: null
|
|
4288
|
+
});
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
const serialized = serializeChatRun(run);
|
|
4292
|
+
res.json({
|
|
4293
|
+
sessionId,
|
|
4294
|
+
run: serialized,
|
|
4295
|
+
activeRun: run.status === "running" ? serialized : null
|
|
4296
|
+
});
|
|
4297
|
+
});
|
|
4050
4298
|
router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
|
|
4051
4299
|
const chatUser = res.locals.chatUser;
|
|
4052
4300
|
log$28.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
@@ -4096,6 +4344,7 @@ function createChatRouter(config) {
|
|
|
4096
4344
|
});
|
|
4097
4345
|
try {
|
|
4098
4346
|
await messageStore.deleteSession(sessionId);
|
|
4347
|
+
chatRunsBySession.delete(sessionId);
|
|
4099
4348
|
res.json({ success: true });
|
|
4100
4349
|
} catch (err) {
|
|
4101
4350
|
const errorMessage = err instanceof Error ? err.message : String(err);
|