fix: parse escaped retryable error messages
This commit is contained in:
+8
-1
@@ -169,6 +169,13 @@ function normalizePath(inputPath) {
|
|||||||
return trimmed || "/";
|
return trimmed || "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expandEscapedLineBreaks(value) {
|
||||||
|
return `${value ?? ""}`
|
||||||
|
.replace(/\\r\\n/g, "\n")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\r/g, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
function flattenValues(value) {
|
function flattenValues(value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.flatMap((item) => flattenValues(item));
|
return value.flatMap((item) => flattenValues(item));
|
||||||
@@ -503,7 +510,7 @@ function normalizePhraseList(values, fallback = []) {
|
|||||||
const normalized = flattenValues(source)
|
const normalized = flattenValues(source)
|
||||||
.flatMap((value) => {
|
.flatMap((value) => {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return value.split(/\r?\n/);
|
return expandEscapedLineBreaks(value).split(/\r?\n/);
|
||||||
}
|
}
|
||||||
return [value];
|
return [value];
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ export const DEFAULT_HEALTH_PATH = "/__codex_retry_gateway/health";
|
|||||||
export const DEFAULT_REASONING_MATCH_MODE = "formula_518n_minus_2";
|
export const DEFAULT_REASONING_MATCH_MODE = "formula_518n_minus_2";
|
||||||
export const DEFAULT_REASONING_EQUALS = [516, 1034, 1552];
|
export const DEFAULT_REASONING_EQUALS = [516, 1034, 1552];
|
||||||
|
|
||||||
|
export function expandEscapedLineBreaks(value) {
|
||||||
|
return `${value ?? ""}`
|
||||||
|
.replace(/\\r\\n/g, "\n")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\r/g, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
function escapeRegExp(value) {
|
function escapeRegExp(value) {
|
||||||
return `${value}`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
return `${value}`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
}
|
}
|
||||||
@@ -204,7 +211,7 @@ export function normalizePhraseArray(values, fallback = []) {
|
|||||||
const source = values === undefined || values === null ? fallback : values;
|
const source = values === undefined || values === null ? fallback : values;
|
||||||
const queue = Array.isArray(source) ? source.flat(Infinity) : [source];
|
const queue = Array.isArray(source) ? source.flat(Infinity) : [source];
|
||||||
const normalized = queue
|
const normalized = queue
|
||||||
.flatMap((value) => (typeof value === "string" ? value.split(/\r?\n/) : [value]))
|
.flatMap((value) => (typeof value === "string" ? expandEscapedLineBreaks(value).split(/\r?\n/) : [value]))
|
||||||
.map((value) => `${value ?? ""}`.trim())
|
.map((value) => `${value ?? ""}`.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
DEFAULT_REASONING_MATCH_MODE,
|
DEFAULT_REASONING_MATCH_MODE,
|
||||||
DEFAULT_STATE_ROOT,
|
DEFAULT_STATE_ROOT,
|
||||||
ensureDirectory,
|
ensureDirectory,
|
||||||
|
expandEscapedLineBreaks,
|
||||||
getCodexProviderContext,
|
getCodexProviderContext,
|
||||||
getGatewayBaseUrl,
|
getGatewayBaseUrl,
|
||||||
getGatewayStatePaths,
|
getGatewayStatePaths,
|
||||||
@@ -47,13 +48,16 @@ function parseEnvFile(content) {
|
|||||||
}
|
}
|
||||||
const key = line.slice(0, separatorIndex).trim();
|
const key = line.slice(0, separatorIndex).trim();
|
||||||
let value = line.slice(separatorIndex + 1).trim();
|
let value = line.slice(separatorIndex + 1).trim();
|
||||||
if (
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
(value.startsWith('"') && value.endsWith('"')) ||
|
try {
|
||||||
(value.startsWith("'") && value.endsWith("'"))
|
value = JSON.parse(value);
|
||||||
) {
|
} catch {
|
||||||
value = value.slice(1, -1);
|
value = value.slice(1, -1);
|
||||||
}
|
}
|
||||||
parsed[key] = value;
|
} else if (value.startsWith("'") && value.endsWith("'")) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
parsed[key] = typeof value === "string" ? expandEscapedLineBreaks(value) : value;
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
|
import { normalizePhraseArray } from "./admin-lib.mjs";
|
||||||
|
|
||||||
const gatewayRoot = path.resolve(import.meta.dirname, "..");
|
const gatewayRoot = path.resolve(import.meta.dirname, "..");
|
||||||
const gatewayEntry = path.join(gatewayRoot, "gateway.mjs");
|
const gatewayEntry = path.join(gatewayRoot, "gateway.mjs");
|
||||||
|
|
||||||
@@ -464,6 +466,11 @@ async function readSseUntilClose(url, requestBody) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
|
assert(
|
||||||
|
JSON.stringify(normalizePhraseArray("a\\nb", [])) === JSON.stringify(["a", "b"]),
|
||||||
|
"normalizePhraseArray 未拆分字面量换行",
|
||||||
|
);
|
||||||
|
|
||||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "codex-retry-gateway-"));
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "codex-retry-gateway-"));
|
||||||
const upstreamPort = await getFreePort();
|
const upstreamPort = await getFreePort();
|
||||||
const gatewayPort = await getFreePort();
|
const gatewayPort = await getFreePort();
|
||||||
@@ -1103,6 +1110,30 @@ async function run() {
|
|||||||
assert(metricsAfterRestart?.metrics?.total_proxy_request_count >= metricsBeforeRestart?.metrics?.total_proxy_request_count, "重启后 total_proxy_request_count 未保留");
|
assert(metricsAfterRestart?.metrics?.total_proxy_request_count >= metricsBeforeRestart?.metrics?.total_proxy_request_count, "重启后 total_proxy_request_count 未保留");
|
||||||
assert(metricsAfterRestart?.metrics?.persistent_since, "重启后未返回 persistent_since");
|
assert(metricsAfterRestart?.metrics?.persistent_since, "重启后未返回 persistent_since");
|
||||||
|
|
||||||
|
config.retryable_error_messages = [
|
||||||
|
"Selected model is at capacity. Please try a different model.\\nstream disconnected before completion: Concurrency limit exceeded for account, please retry later",
|
||||||
|
];
|
||||||
|
await writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
||||||
|
gateway.child.kill();
|
||||||
|
await once(gateway.child, "exit");
|
||||||
|
gateway = startGateway(configPath, logPath);
|
||||||
|
await waitForHealth(`http://127.0.0.1:${gatewayPort}${config.health_path}`);
|
||||||
|
|
||||||
|
const escapedNewlineCapacityResponse = await fetch(`http://127.0.0.1:${gatewayPort}/responses`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ test_capacity_before_success_times: 2, test_capacity_status: 200, test_reasoning_tokens: 128 }),
|
||||||
|
});
|
||||||
|
const escapedNewlineCapacityBody = await escapedNewlineCapacityResponse.json();
|
||||||
|
assert(
|
||||||
|
escapedNewlineCapacityResponse.status === 200,
|
||||||
|
`字面量换行 retryable_error_messages 下 200+capacity 未自动恢复: ${escapedNewlineCapacityResponse.status}`,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
escapedNewlineCapacityBody?.usage?.output_tokens_details?.reasoning_tokens === 128,
|
||||||
|
"字面量换行 retryable_error_messages 恢复后的返回体异常",
|
||||||
|
);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 120));
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
||||||
const logText = await readFile(logPath, "utf8");
|
const logText = await readFile(logPath, "utf8");
|
||||||
assert(
|
assert(
|
||||||
|
|||||||
Reference in New Issue
Block a user