|
|
@@ -53,6 +53,9 @@ export class ChatBridgeService {
|
|
|
|
|
|
#options: ChatBridgeOptions | null = null;
|
|
|
|
|
|
+ /** 诊断里区分同一轮里的多次上游请求 */
|
|
|
+ #seq = 0;
|
|
|
+
|
|
|
/** 装配层(index.ts)注入的诊断出口;start 时的 onDiagnostic 优先 */
|
|
|
#diagnostic: DiagnosticFn | null = null;
|
|
|
|
|
|
@@ -140,6 +143,15 @@ export class ChatBridgeService {
|
|
|
|
|
|
const chatReq = responsesRequestToChat(responsesReq, (line) => this.#diag(line));
|
|
|
const upstream = `${options.upstreamBaseUrl.replace(/\/+$/u, '')}/chat/completions`;
|
|
|
+ const id = ++this.#seq;
|
|
|
+ const t0 = Date.now();
|
|
|
+ const secs = (at: number = Date.now()): string => `${((at - t0) / 1000).toFixed(1)}s`;
|
|
|
+ const inputText = typeof responsesReq.input === 'string' ? responsesReq.input : JSON.stringify(responsesReq.input ?? '');
|
|
|
+ const messages = Array.isArray(chatReq.messages) ? chatReq.messages : [];
|
|
|
+ this.#diag(
|
|
|
+ `chat-bridge: #${id} 起 round model=${responsesReq.model ?? '?'} stream=${chatReq.stream ? 'Y' : 'N'} ` +
|
|
|
+ `tools=${Array.isArray(chatReq.tools) ? chatReq.tools.length : 0} messages=${messages.length} 输入≈${inputText.length}字 → ${upstream}`,
|
|
|
+ );
|
|
|
|
|
|
let upstreamRes: Response;
|
|
|
try {
|
|
|
@@ -150,14 +162,14 @@ export class ChatBridgeService {
|
|
|
});
|
|
|
} catch (error) {
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
- this.#diag(`chat-bridge: 上游不可达:${message}`);
|
|
|
+ this.#diag(`chat-bridge: #${id} 上游不可达(${secs()}):${message}`);
|
|
|
this.#failJson(res, 502, `桥接上游不可达:${message}`);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!upstreamRes.ok) {
|
|
|
const text = await upstreamRes.text().catch(() => '');
|
|
|
- this.#diag(`chat-bridge: 上游 HTTP ${upstreamRes.status}:${text.slice(0, 300)}`);
|
|
|
+ this.#diag(`chat-bridge: #${id} 上游 HTTP ${upstreamRes.status}(${secs()}):${text.slice(0, 300)}`);
|
|
|
this.#failJson(res, upstreamRes.status, `桥接上游返回 HTTP ${upstreamRes.status}:${text.slice(0, 500)}`);
|
|
|
return;
|
|
|
}
|
|
|
@@ -167,7 +179,9 @@ export class ChatBridgeService {
|
|
|
const chatJson = (await upstreamRes.json()) as Parameters<typeof chatResponseToResponses>[0];
|
|
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
|
res.end(JSON.stringify(chatResponseToResponses(chatJson, responsesReq)));
|
|
|
+ this.#diag(`chat-bridge: #${id} 非流式完成(${secs()})`);
|
|
|
} catch (error) {
|
|
|
+ this.#diag(`chat-bridge: #${id} 非流式解析失败(${secs()})`);
|
|
|
this.#failJson(res, 502, `桥接解析上游响应失败:${error instanceof Error ? error.message : String(error)}`);
|
|
|
}
|
|
|
return;
|
|
|
@@ -180,6 +194,20 @@ export class ChatBridgeService {
|
|
|
connection: 'keep-alive',
|
|
|
});
|
|
|
const translator = new ResponsesSseTranslator(responsesReq, (line) => this.#diag(line));
|
|
|
+ let bytes = 0;
|
|
|
+ let events = 0;
|
|
|
+ let aborted = false;
|
|
|
+ const write = (items: BridgeSseEvent[]): void => {
|
|
|
+ events += items.length;
|
|
|
+ writeSseEvents(res, items);
|
|
|
+ };
|
|
|
+ // Codex 提前挂断是这次排查的关键信号:没有这行就是上游自己停了
|
|
|
+ res.once('close', () => {
|
|
|
+ if (!res.writableEnded) {
|
|
|
+ aborted = true;
|
|
|
+ this.#diag(`chat-bridge: #${id} Codex 提前断开(${secs()},已发 ${events} 个事件)`);
|
|
|
+ }
|
|
|
+ });
|
|
|
try {
|
|
|
if (!upstreamRes.body) throw new Error('上游响应没有 body');
|
|
|
const reader = upstreamRes.body.getReader();
|
|
|
@@ -187,14 +215,16 @@ export class ChatBridgeService {
|
|
|
for (;;) {
|
|
|
const { done, value } = await reader.read();
|
|
|
if (done) break;
|
|
|
- writeSseEvents(res, translator.push(decoder.decode(value, { stream: true })));
|
|
|
+ bytes += value?.byteLength ?? 0;
|
|
|
+ write(translator.push(decoder.decode(value, { stream: true })));
|
|
|
}
|
|
|
- writeSseEvents(res, translator.push(decoder.decode()));
|
|
|
- writeSseEvents(res, translator.finish());
|
|
|
+ write(translator.push(decoder.decode()));
|
|
|
+ write(translator.finish());
|
|
|
+ this.#diag(`chat-bridge: #${id} 转发完成(${secs()},上游 ${bytes}B → ${events} 个事件)`);
|
|
|
} catch (error) {
|
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
|
- this.#diag(`chat-bridge: 流式转发中断:${message}`);
|
|
|
- writeSseEvents(res, translator.fail(message));
|
|
|
+ this.#diag(`chat-bridge: #${id} 流式转发中断(${secs()}):${message}`);
|
|
|
+ write(translator.fail(message));
|
|
|
}
|
|
|
res.end();
|
|
|
}
|