llm-simple-router 0.9.25 → 0.9.26

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.
@@ -4,6 +4,7 @@ import { buildUpstreamUrl } from "../proxy-core.js";
4
4
  import { _transportInternals, buildRequestOptions, } from "./http.js";
5
5
  const UPSTREAM_BAD_GATEWAY = 502;
6
6
  const BUFFER_SIZE_LIMIT = 4096;
7
+ const END_REPLY_TIMEOUT_MS = 1000;
7
8
  class StreamProxy {
8
9
  statusCode;
9
10
  sentUpstreamHeaders;
@@ -278,18 +279,34 @@ class StreamProxy {
278
279
  // 这保证了 inject() 返回时日志已经写入 DB。
279
280
  const metrics = this.collectMetrics(true);
280
281
  this.terminal("stream_success", { metrics }, true);
281
- // 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理
282
+ // 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理。
283
+ //
284
+ // 当 formatTransform 存在时(如 OpenAI→Anthropic 转换),Transform 链的 flush
285
+ // 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end(),
286
+ // 否则 formatTransform._flush() 中的 ensureTerminated()(发送 message_stop)
287
+ // 尚未执行,连接就被关闭,导致客户端收到 "stream ended before message_stop"。
282
288
  setImmediate(() => {
283
- this.pipeEntry.end();
284
- if (this.headersSent) {
285
- try {
286
- this.reply.raw.end();
287
- }
288
- catch { // eslint-disable-line taste/no-silent-catch
289
- // reply 可能已 destroyed,安全忽略
289
+ const endReply = () => {
290
+ if (this.headersSent) {
291
+ try {
292
+ this.reply.raw.end();
293
+ }
294
+ catch { // eslint-disable-line taste/no-silent-catch
295
+ // reply 可能已 destroyed,安全忽略
296
+ }
290
297
  }
298
+ this.cleanup();
299
+ };
300
+ this.pipeEntry.end();
301
+ if (this.formatTransform) {
302
+ // 等 passThrough end 事件触发(整个 transform 链 flush 完成后),再关闭 reply
303
+ this.passThrough.once("end", endReply);
304
+ // 安全超时兜底,防止 end 事件未触发导致连接挂起
305
+ setTimeout(endReply, END_REPLY_TIMEOUT_MS).unref();
306
+ }
307
+ else {
308
+ endReply();
291
309
  }
292
- this.cleanup();
293
310
  });
294
311
  }
295
312
  onUpstreamError(err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-simple-router",
3
- "version": "0.9.25",
3
+ "version": "0.9.26",
4
4
  "description": "LLM API proxy router with OpenAI/Anthropic support, model mapping, retry strategies, and admin dashboard",
5
5
  "license": "MIT",
6
6
  "author": "ZZzzswszzZZ",