chore: normalize formatting (2-space → tabs) in stream-error-patterns files
Re-tab only — no logic changes. Follows the repo's tab-based formatting for these files, matching the CommandCode executor/translator style. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
@@ -115,7 +115,10 @@ describe("commandcode executor — early-error peek", () => {
|
||||
c.close();
|
||||
},
|
||||
});
|
||||
const res = await peekForUpstreamError(new Response(body, { status: 200 }), "m");
|
||||
const res = await peekForUpstreamError(
|
||||
new Response(body, { status: 200 }),
|
||||
"m",
|
||||
);
|
||||
const text = await res.text();
|
||||
expect(text).toContain("café");
|
||||
expect(text).not.toContain("\uFFFD");
|
||||
|
||||
@@ -3,73 +3,88 @@ import { handleForcedSSEToJson } from "../../open-sse/handlers/chatCore/sseToJso
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const sseResponse = (chunks) => {
|
||||
const body = new ReadableStream({
|
||||
start(c) {
|
||||
for (const ch of chunks) c.enqueue(encoder.encode(`data: ${JSON.stringify(ch)}\n\n`));
|
||||
c.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
c.close();
|
||||
},
|
||||
});
|
||||
return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });
|
||||
const body = new ReadableStream({
|
||||
start(c) {
|
||||
for (const ch of chunks)
|
||||
c.enqueue(encoder.encode(`data: ${JSON.stringify(ch)}\n\n`));
|
||||
c.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
c.close();
|
||||
},
|
||||
});
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
});
|
||||
};
|
||||
|
||||
const mkChunk = (content, finish = null) => ({
|
||||
id: "x",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "m",
|
||||
choices: [{ index: 0, delta: content ? { content } : {}, finish_reason: finish }],
|
||||
id: "x",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "m",
|
||||
choices: [
|
||||
{ index: 0, delta: content ? { content } : {}, finish_reason: finish },
|
||||
],
|
||||
});
|
||||
|
||||
const baseCtx = {
|
||||
provider: "fakeprovider",
|
||||
model: "m",
|
||||
body: { stream: false },
|
||||
stream: true,
|
||||
translatedBody: null,
|
||||
finalBody: null,
|
||||
requestStartTime: Date.now(),
|
||||
connectionId: "c1",
|
||||
apiKey: null,
|
||||
clientRawRequest: null,
|
||||
onRequestSuccess: null,
|
||||
pxpipe: null,
|
||||
reqTag: "",
|
||||
log: null,
|
||||
trackDone: () => {},
|
||||
appendLog: () => {},
|
||||
reqLogger: null,
|
||||
toolNameMap: null,
|
||||
sourceFormat: "openai",
|
||||
provider: "fakeprovider",
|
||||
model: "m",
|
||||
body: { stream: false },
|
||||
stream: true,
|
||||
translatedBody: null,
|
||||
finalBody: null,
|
||||
requestStartTime: Date.now(),
|
||||
connectionId: "c1",
|
||||
apiKey: null,
|
||||
clientRawRequest: null,
|
||||
onRequestSuccess: null,
|
||||
pxpipe: null,
|
||||
reqTag: "",
|
||||
log: null,
|
||||
trackDone: () => {},
|
||||
appendLog: () => {},
|
||||
reqLogger: null,
|
||||
toolNameMap: null,
|
||||
sourceFormat: "openai",
|
||||
};
|
||||
|
||||
describe("Layer 1 — non-streaming stream error patterns", () => {
|
||||
it("returns a 502 error result when content matches a configured pattern", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { fakeprovider: ["Network connection lost"] },
|
||||
providerResponse: sseResponse([mkChunk("Network connection lost."), mkChunk(null, "stop")]),
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.status).toBe(502);
|
||||
expect(result.error).toContain("Network connection lost");
|
||||
});
|
||||
it("returns a 502 error result when content matches a configured pattern", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { fakeprovider: ["Network connection lost"] },
|
||||
providerResponse: sseResponse([
|
||||
mkChunk("Network connection lost."),
|
||||
mkChunk(null, "stop"),
|
||||
]),
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.status).toBe(502);
|
||||
expect(result.error).toContain("Network connection lost");
|
||||
});
|
||||
|
||||
it("succeeds when content does not match", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { fakeprovider: ["Network connection lost"] },
|
||||
providerResponse: sseResponse([mkChunk("hello world"), mkChunk(null, "stop")]),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
it("succeeds when content does not match", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { fakeprovider: ["Network connection lost"] },
|
||||
providerResponse: sseResponse([
|
||||
mkChunk("hello world"),
|
||||
mkChunk(null, "stop"),
|
||||
]),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("ignores patterns for other providers", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { otherprovider: ["hello"] },
|
||||
providerResponse: sseResponse([mkChunk("hello world"), mkChunk(null, "stop")]),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
it("ignores patterns for other providers", async () => {
|
||||
const result = await handleForcedSSEToJson({
|
||||
...baseCtx,
|
||||
streamErrorPatterns: { otherprovider: ["hello"] },
|
||||
providerResponse: sseResponse([
|
||||
mkChunk("hello world"),
|
||||
mkChunk(null, "stop"),
|
||||
]),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,50 +1,70 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parsePatterns, matchStreamErrorPatterns, streamStatusForContent } from "../../open-sse/utils/streamErrorPatterns.js";
|
||||
import {
|
||||
parsePatterns,
|
||||
matchStreamErrorPatterns,
|
||||
streamStatusForContent,
|
||||
} from "../../open-sse/utils/streamErrorPatterns.js";
|
||||
|
||||
describe("streamErrorPatterns util", () => {
|
||||
it("matches plain text as case-insensitive substring", () => {
|
||||
expect(matchStreamErrorPatterns(["Network connection lost"], "network CONNECTION LOST.")).toBe("Network connection lost");
|
||||
expect(matchStreamErrorPatterns(["server_error"], "some normal content")).toBeNull();
|
||||
});
|
||||
it("matches plain text as case-insensitive substring", () => {
|
||||
expect(
|
||||
matchStreamErrorPatterns(
|
||||
["Network connection lost"],
|
||||
"network CONNECTION LOST.",
|
||||
),
|
||||
).toBe("Network connection lost");
|
||||
expect(
|
||||
matchStreamErrorPatterns(["server_error"], "some normal content"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("matches /regex/flags", () => {
|
||||
expect(matchStreamErrorPatterns(["/generation failed.*retry/i"], "GENERATION FAILED. please RETRY")).toBe("/generation failed.*retry/i");
|
||||
expect(matchStreamErrorPatterns(["/\\d+ tokens/"], "used 123 tokens")).toBe("/\\d+ tokens/");
|
||||
});
|
||||
it("matches /regex/flags", () => {
|
||||
expect(
|
||||
matchStreamErrorPatterns(
|
||||
["/generation failed.*retry/i"],
|
||||
"GENERATION FAILED. please RETRY",
|
||||
),
|
||||
).toBe("/generation failed.*retry/i");
|
||||
expect(matchStreamErrorPatterns(["/\\d+ tokens/"], "used 123 tokens")).toBe(
|
||||
"/\\d+ tokens/",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips invalid regex and empty entries", () => {
|
||||
expect(matchStreamErrorPatterns(["/[unclosed/", "", " "], "anything")).toBeNull();
|
||||
});
|
||||
it("skips invalid regex and empty entries", () => {
|
||||
expect(
|
||||
matchStreamErrorPatterns(["/[unclosed/", "", " "], "anything"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for empty patterns or text", () => {
|
||||
expect(matchStreamErrorPatterns([], "x")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(["x"], "")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(null, "x")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(undefined, "x")).toBeNull();
|
||||
});
|
||||
it("returns null for empty patterns or text", () => {
|
||||
expect(matchStreamErrorPatterns([], "x")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(["x"], "")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(null, "x")).toBeNull();
|
||||
expect(matchStreamErrorPatterns(undefined, "x")).toBeNull();
|
||||
});
|
||||
|
||||
it("regex matching is stateless across calls", () => {
|
||||
const pats = ["/error/i"];
|
||||
expect(matchStreamErrorPatterns(pats, "ERROR")).toBe("/error/i");
|
||||
expect(matchStreamErrorPatterns(pats, "ERROR")).toBe("/error/i");
|
||||
});
|
||||
it("regex matching is stateless across calls", () => {
|
||||
const pats = ["/error/i"];
|
||||
expect(matchStreamErrorPatterns(pats, "ERROR")).toBe("/error/i");
|
||||
expect(matchStreamErrorPatterns(pats, "ERROR")).toBe("/error/i");
|
||||
});
|
||||
|
||||
it("parsePatterns normalizes entries", () => {
|
||||
const parsed = parsePatterns(["Plain", "/re/g", "", "/bad["]);
|
||||
expect(parsed.length).toBe(3);
|
||||
expect(parsed[0]).toEqual({ text: "plain", raw: "Plain" });
|
||||
expect(parsed[1].regex).toBeInstanceOf(RegExp);
|
||||
expect(parsed[2]).toEqual({ text: "/bad[", raw: "/bad[" });
|
||||
});
|
||||
it("parsePatterns normalizes entries", () => {
|
||||
const parsed = parsePatterns(["Plain", "/re/g", "", "/bad["]);
|
||||
expect(parsed.length).toBe(3);
|
||||
expect(parsed[0]).toEqual({ text: "plain", raw: "Plain" });
|
||||
expect(parsed[1].regex).toBeInstanceOf(RegExp);
|
||||
expect(parsed[2]).toEqual({ text: "/bad[", raw: "/bad[" });
|
||||
});
|
||||
|
||||
it("skips entries that look like regex but fail to compile", () => {
|
||||
const parsed = parsePatterns(["/[unclosed/", "/ok/g"]);
|
||||
expect(parsed.length).toBe(1);
|
||||
expect(parsed[0].raw).toBe("/ok/g");
|
||||
});
|
||||
it("skips entries that look like regex but fail to compile", () => {
|
||||
const parsed = parsePatterns(["/[unclosed/", "/ok/g"]);
|
||||
expect(parsed.length).toBe(1);
|
||||
expect(parsed[0].raw).toBe("/ok/g");
|
||||
});
|
||||
|
||||
it("streamStatusForContent maps match to error status", () => {
|
||||
expect(streamStatusForContent(["boom"], "a boom happened")).toBe("error");
|
||||
expect(streamStatusForContent(["boom"], "all good")).toBe("success");
|
||||
});
|
||||
it("streamStatusForContent maps match to error status", () => {
|
||||
expect(streamStatusForContent(["boom"], "a boom happened")).toBe("error");
|
||||
expect(streamStatusForContent(["boom"], "all good")).toBe("success");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user