@musnows/scriverse 0.9.2 → 0.9.3

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/app.js CHANGED
@@ -835,6 +835,7 @@ const characterExtractionApplySchema = z.object({
835
835
  }
836
836
  });
837
837
  export const RUNTIME_BACKUP_IDLE_TIMEOUT_MS = 9_000;
838
+ export const AI_CLIENT_STREAM_HEARTBEAT_INTERVAL_MS = 15_000;
838
839
  function data(response, value, status = 200) {
839
840
  response.status(status).json({ data: value });
840
841
  }
@@ -1183,6 +1184,10 @@ function redactVersionSnapshots(value, mapper) {
1183
1184
  export function createRuntime(options) {
1184
1185
  const uploadLimits = options.uploadLimits ?? DEFAULT_IMAGE_UPLOAD_LIMITS;
1185
1186
  const aiChatTabLimit = options.aiChatTabLimit ?? DEFAULT_AI_CHAT_TAB_LIMIT;
1187
+ const requestedAiStreamHeartbeatIntervalMs = Number(options.aiStreamHeartbeatIntervalMs);
1188
+ const aiStreamHeartbeatIntervalMs = Number.isFinite(requestedAiStreamHeartbeatIntervalMs)
1189
+ ? Math.max(1, Math.trunc(requestedAiStreamHeartbeatIntervalMs))
1190
+ : AI_CLIENT_STREAM_HEARTBEAT_INTERVAL_MS;
1186
1191
  logger.info("runtime.initializing", {
1187
1192
  databasePath: options.databasePath,
1188
1193
  serveUi: options.serveUi ?? true,
@@ -3200,16 +3205,30 @@ export function createRuntime(options) {
3200
3205
  const modelId = resolveConversationModelId(request.params.workId, conversationId, input.modelId);
3201
3206
  const permissions = requestPermissions(request, request.params.workId);
3202
3207
  const controller = new AbortController();
3203
- response.on("close", () => {
3204
- if (!response.writableEnded)
3205
- controller.abort(new Error("浏览器已中断流式请求"));
3206
- });
3207
3208
  let streamRequestId = null;
3208
3209
  let streamRequestFinished = false;
3209
3210
  let lastStreamLeaseTouchAt = Date.now();
3211
+ let streamHeartbeatTimer = null;
3210
3212
  let preparedContext = null;
3211
3213
  let preparedConversation = null;
3212
3214
  let preparedChatImageAttachments = [];
3215
+ const touchStreamLease = () => {
3216
+ if (streamRequestId && Date.now() - lastStreamLeaseTouchAt >= 30_000) {
3217
+ store.touchAiConversationStreamRequest(streamRequestId);
3218
+ lastStreamLeaseTouchAt = Date.now();
3219
+ }
3220
+ };
3221
+ const stopStreamHeartbeat = () => {
3222
+ if (!streamHeartbeatTimer)
3223
+ return;
3224
+ clearInterval(streamHeartbeatTimer);
3225
+ streamHeartbeatTimer = null;
3226
+ };
3227
+ response.on("close", () => {
3228
+ stopStreamHeartbeat();
3229
+ if (!response.writableEnded)
3230
+ controller.abort(new Error("浏览器已中断流式请求"));
3231
+ });
3213
3232
  const startStream = () => {
3214
3233
  if (response.headersSent)
3215
3234
  return;
@@ -3219,12 +3238,15 @@ export function createRuntime(options) {
3219
3238
  response.setHeader("Connection", "keep-alive");
3220
3239
  response.setHeader("X-Accel-Buffering", "no");
3221
3240
  response.flushHeaders();
3241
+ streamHeartbeatTimer = setInterval(() => {
3242
+ touchStreamLease();
3243
+ if (!response.writableEnded && !response.destroyed)
3244
+ response.write(": heartbeat\n\n");
3245
+ }, aiStreamHeartbeatIntervalMs);
3246
+ streamHeartbeatTimer.unref();
3222
3247
  };
3223
3248
  const sendEvent = (event, payload) => {
3224
- if (streamRequestId && Date.now() - lastStreamLeaseTouchAt >= 30_000) {
3225
- store.touchAiConversationStreamRequest(streamRequestId);
3226
- lastStreamLeaseTouchAt = Date.now();
3227
- }
3249
+ touchStreamLease();
3228
3250
  if (!response.writableEnded && !response.destroyed)
3229
3251
  response.write(`event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`);
3230
3252
  };
@@ -3402,6 +3424,7 @@ export function createRuntime(options) {
3402
3424
  }
3403
3425
  }
3404
3426
  finally {
3427
+ stopStreamHeartbeat();
3405
3428
  if (streamRequestId && !streamRequestFinished && !stopping) {
3406
3429
  store.finishAiConversationStreamRequest(streamRequestId, "cancelled", "stream_closed");
3407
3430
  streamRequestFinished = true;