mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 13:34:06 +00:00
fix(scripts): cap issue labeler response bodies
This commit is contained in:
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { isRecord } from "../src/utils.js";
|
||||
import { readBoundedResponseText as readBoundedBodyText } from "./lib/bounded-response.ts";
|
||||
import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts";
|
||||
|
||||
function writeStdoutLine(message = ""): void {
|
||||
@@ -22,6 +23,7 @@ const WORK_BATCH_SIZE = 500;
|
||||
const STATE_VERSION = 1;
|
||||
const DEFAULT_OPENAI_TIMEOUT_MS = 60_000;
|
||||
const OPENAI_ERROR_BODY_MAX_CHARS = 4096;
|
||||
const OPENAI_RESPONSE_BODY_MAX_BYTES = 256 * 1024;
|
||||
const STATE_FILE_NAME = "issue-labeler-state.json";
|
||||
const CONFIG_BASE_DIR = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
||||
const STATE_FILE_PATH = join(CONFIG_BASE_DIR, "openclaw", STATE_FILE_NAME);
|
||||
@@ -322,6 +324,19 @@ async function readBoundedResponseText(
|
||||
return truncated ? `${text}\n[truncated]` : text;
|
||||
}
|
||||
|
||||
async function readBoundedOpenAIJson(
|
||||
response: Response,
|
||||
maxBytes = OPENAI_RESPONSE_BODY_MAX_BYTES,
|
||||
): Promise<OpenAIResponse> {
|
||||
const text = await readBoundedBodyText(response, "OpenAI classification", maxBytes, {
|
||||
createTooLargeError: (message) =>
|
||||
Object.assign(new Error(message), {
|
||||
code: "ETOOBIG",
|
||||
}),
|
||||
});
|
||||
return JSON.parse(text) as OpenAIResponse;
|
||||
}
|
||||
|
||||
function logHeader(title: string) {
|
||||
writeStdoutLine(`\n${title}`);
|
||||
writeStdoutLine("=".repeat(title.length));
|
||||
@@ -729,7 +744,7 @@ async function classifyItem(
|
||||
throw new Error(`OpenAI request failed (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as OpenAIResponse;
|
||||
return await readBoundedOpenAIJson(response);
|
||||
},
|
||||
);
|
||||
const rawText = extractResponseText(payload);
|
||||
@@ -993,6 +1008,7 @@ async function main() {
|
||||
export const testing = {
|
||||
classifyItem,
|
||||
normalizeClassification,
|
||||
readBoundedOpenAIJson,
|
||||
readBoundedResponseText,
|
||||
resolveOpenAITimeoutMs,
|
||||
};
|
||||
|
||||
@@ -10,17 +10,16 @@ const labelItem = {
|
||||
|
||||
describe("label-open-issues helpers", () => {
|
||||
it("classifies items from OpenAI structured response text", async () => {
|
||||
const response = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
const response = new Response(
|
||||
JSON.stringify({
|
||||
output_text: JSON.stringify({
|
||||
category: "bug",
|
||||
isSupport: true,
|
||||
isSkillOnly: false,
|
||||
}),
|
||||
}),
|
||||
} as Response;
|
||||
{ status: 200 },
|
||||
);
|
||||
|
||||
await expect(
|
||||
testing.classifyItem(labelItem, "issue", {
|
||||
@@ -55,11 +54,7 @@ describe("label-open-issues helpers", () => {
|
||||
});
|
||||
|
||||
it("times out stalled OpenAI classification body reads", async () => {
|
||||
const response = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => new Promise(() => {}),
|
||||
} as Response;
|
||||
const response = new Response(new ReadableStream({}), { status: 200 });
|
||||
const request = testing.classifyItem(labelItem, "issue", {
|
||||
apiKey: "test-key",
|
||||
model: "test-model",
|
||||
@@ -96,6 +91,53 @@ describe("label-open-issues helpers", () => {
|
||||
expect(message.length).toBeLessThan(4300);
|
||||
});
|
||||
|
||||
it("reads bounded OpenAI classification JSON responses", async () => {
|
||||
await expect(
|
||||
testing.readBoundedOpenAIJson(new Response('{"output_text":"{}"}'), 1024),
|
||||
).resolves.toEqual({ output_text: "{}" });
|
||||
});
|
||||
|
||||
it("rejects oversized OpenAI classification JSON responses by content length", async () => {
|
||||
let canceled = false;
|
||||
const response = new Response(
|
||||
new ReadableStream({
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"content-length": "1025",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await expect(testing.readBoundedOpenAIJson(response, 1024)).rejects.toMatchObject({
|
||||
code: "ETOOBIG",
|
||||
message: "OpenAI classification response body exceeded 1024 bytes",
|
||||
});
|
||||
expect(canceled).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects oversized streamed OpenAI classification JSON responses", async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const response = new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode('{"output_text":"'));
|
||||
controller.enqueue(encoder.encode("x".repeat(1024)));
|
||||
controller.enqueue(encoder.encode('"}'));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(testing.readBoundedOpenAIJson(response, 1024)).rejects.toMatchObject({
|
||||
code: "ETOOBIG",
|
||||
message: "OpenAI classification response body exceeded 1024 bytes",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid OpenAI classification timeout values", () => {
|
||||
expect(testing.resolveOpenAITimeoutMs("250")).toBe(250);
|
||||
expect(() => testing.resolveOpenAITimeoutMs("slow")).toThrow(
|
||||
|
||||
Reference in New Issue
Block a user