

















@@ -1,9 +1,95 @@
11import fs from "node:fs";
223+const ERROR_DETAIL_TAIL_BYTES = 64 * 1024;
4+const REPLY_TEXT_PREVIEW_BYTES = 8 * 1024;
5+const REPLY_TEXT_PREVIEW_COUNT = 5;
6+const REQUEST_LOG_SCAN_CHUNK_BYTES = 64 * 1024;
7+const REQUEST_LOG_SCAN_CARRY_CHARS = 256;
8+const OPENAI_REQUEST_PATH_PATTERN = /\/v1\/(responses|chat\/completions)/u;
9+310function readTextFile(file) {
411return fs.readFileSync(file, "utf8");
512}
61314+function textByteLength(text) {
15+return Buffer.byteLength(text, "utf8");
16+}
17+18+function tailText(text, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
19+if (textByteLength(text) <= maxBytes) {
20+return text;
21+}
22+return Buffer.from(text, "utf8").subarray(-maxBytes).toString("utf8");
23+}
24+25+function summarizeReplyTexts(replyTexts) {
26+const previewStart = Math.max(0, replyTexts.length - REPLY_TEXT_PREVIEW_COUNT);
27+const recent = replyTexts.slice(previewStart).map((text, index) => ({
28+index: previewStart + index,
29+bytes: textByteLength(text),
30+tail: tailText(text, REPLY_TEXT_PREVIEW_BYTES),
31+}));
32+return JSON.stringify({ count: replyTexts.length, recent });
33+}
34+35+function readTextFileTail(file, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
36+let stat;
37+try {
38+stat = fs.statSync(file);
39+} catch {
40+return "";
41+}
42+if (!stat.isFile() || stat.size <= 0) {
43+return "";
44+}
45+46+const length = Math.min(maxBytes, stat.size);
47+const start = stat.size - length;
48+const fd = fs.openSync(file, "r");
49+try {
50+const buffer = Buffer.alloc(length);
51+const bytesRead = fs.readSync(fd, buffer, 0, length, start);
52+return buffer.subarray(0, bytesRead).toString("utf8");
53+} finally {
54+fs.closeSync(fd);
55+}
56+}
57+58+function fileContainsPattern(file, pattern) {
59+let stat;
60+try {
61+stat = fs.statSync(file);
62+} catch {
63+return false;
64+}
65+if (!stat.isFile() || stat.size <= 0) {
66+return false;
67+}
68+69+const fd = fs.openSync(file, "r");
70+try {
71+const buffer = Buffer.alloc(Math.min(REQUEST_LOG_SCAN_CHUNK_BYTES, stat.size));
72+let carry = "";
73+let offset = 0;
74+while (offset < stat.size) {
75+const bytesToRead = Math.min(buffer.length, stat.size - offset);
76+const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
77+if (bytesRead <= 0) {
78+break;
79+}
80+offset += bytesRead;
81+const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
82+if (pattern.test(text)) {
83+return true;
84+}
85+carry = text.slice(-REQUEST_LOG_SCAN_CARRY_CHARS);
86+}
87+return false;
88+} finally {
89+fs.closeSync(fd);
90+}
91+}
92+793function parseJson(text) {
894try {
995return JSON.parse(text);
@@ -112,15 +198,16 @@ export function assertAgentReplyContainsMarker(marker, outputPath) {
112198if (replyTexts.some((text) => text.includes(marker))) {
113199return;
114200}
201+const outputTail = tailText(output);
115202throw new Error(
116-`agent reply payload did not contain marker ${marker}. Reply payloads: ${JSON.stringify(replyTexts)}. Output: ${output}`,
203+`agent reply payload did not contain marker ${marker}. Reply payload summary: ${summarizeReplyTexts(replyTexts)}. Output tail: ${outputTail}`,
117204);
118205}
119206120207export function assertOpenAiRequestLogUsed(requestLogPath, label = "mock OpenAI server") {
121-const requestLog = fs.existsSync(requestLogPath) ? readTextFile(requestLogPath) : "";
122-if (/\/v1\/(responses|chat\/completions)/u.test(requestLog)) {
208+if (fileContainsPattern(requestLogPath, OPENAI_REQUEST_PATH_PATTERN)) {
123209return;
124210}
125-throw new Error(`${label} was not used. Requests: ${requestLog}`);
211+const requestLogTail = readTextFileTail(requestLogPath);
212+throw new Error(`${label} was not used. Request log tail: ${requestLogTail}`);
126213}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。