mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
9be5528548
commit
98ebba1801
@@ -10,6 +10,8 @@
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **providers (codex):** fix the **Codex Responses WebSocket** path (`/v1/responses`), which regressed in v3.8.40 with a client-visible `Invalid JSON body` and bypassed the configured proxy. (1) #5591 — PR #5237 bumped the impersonation TLS profile to `chrome_149`, but `wreq-js@2.3.1` only supports up to `chrome_147`; the unknown profile produced a degenerate fingerprint and ChatGPT rejected the upstream upgrade. The Codex WS path is reverted to the proven `chrome_142` (the v3.8.39 value), and the over-bumped `grok-web`/`claude-web` profiles (masked by their circuit-breaker but silently dropping TLS impersonation) are restored to `chrome_146`. A new regression guard asserts every configured `chrome_*` profile exists in the installed `wreq-js` typings (`tests/unit/tls-profiles-valid-5591.test.mjs`). (2) #5611 — the upstream `wreq-js.websocket()` connect ignored the Proxy Registry, so a no-direct-egress Docker container failed with a DNS error; the prepare route now resolves the Global/provider proxy and threads it through to the WS connect. Regression guard in `tests/unit/responses-ws-proxy.test.mjs`. ([#5591](https://github.com/diegosouzapw/OmniRoute/issues/5591), [#5611](https://github.com/diegosouzapw/OmniRoute/issues/5611))
|
||||
|
||||
- **providers (GLM):** GLM **5.1 / 5.2** now keep the `system` role instead of having the system prompt folded into the first user turn. `roleNormalizer.ts` matched every `glm*` id with a blanket `startsWith("glm")` / `startsWith("glm-")` prefix, so the next-generation models — which z.ai documents as supporting the `system` role (GLM > 5.0) — were normalized as if they rejected it, degrading instruction-following. The matcher is now version-aware: it strips the system role only for bare `glm`, the 4.x family, and the 5.0 generation, and preserves it for `glm-5.1`/`glm-5.2` (and the Fireworks `glm-5p1` point alias). The ZenMux vendor-prefixed `z-ai/glm-*` compressed-history rule and the ERNIE rule are unchanged. Regression guards in `tests/unit/role-normalizer.test.ts`. ([#5610](https://github.com/diegosouzapw/OmniRoute/issues/5610))
|
||||
|
||||
- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1).
|
||||
|
||||
@@ -20,7 +20,7 @@ import { randomUUID } from "node:crypto";
|
||||
let clientPromise: Promise<unknown> | null = null;
|
||||
let exitHookInstalled = false;
|
||||
|
||||
const CLAUDE_PROFILE = "chrome_149"; // matches the Vivaldi/Chrome 149 UA we send
|
||||
const CLAUDE_PROFILE = "chrome_146"; // closest supported wreq-js profile (chrome_149 absent in 2.3.1, #5591)
|
||||
const DEFAULT_TIMEOUT_MS =
|
||||
Number.parseInt(process.env.OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS || "", 10) || 60_000;
|
||||
// Grace period added to the binding's wire-level timeout before our JS-level
|
||||
|
||||
@@ -25,7 +25,7 @@ import { randomUUID } from "node:crypto";
|
||||
let clientPromise: Promise<unknown> | null = null;
|
||||
let exitHookInstalled = false;
|
||||
|
||||
const GROK_PROFILE = "chrome_149"; // closest Chrome profile to the UA we send
|
||||
const GROK_PROFILE = "chrome_146"; // closest supported wreq-js profile (chrome_149 absent in 2.3.1, #5591)
|
||||
const DEFAULT_TIMEOUT_MS =
|
||||
Number.parseInt(process.env.OMNIROUTE_GROK_TLS_TIMEOUT_MS || "", 10) || 60_000;
|
||||
// Grace period added to the binding's wire-level timeout before our JS-level
|
||||
|
||||
@@ -615,11 +615,17 @@ class ResponsesWsSession {
|
||||
toStringOrNull(responseBody.service_tier) || toStringOrNull(responseBody.serviceTier),
|
||||
};
|
||||
|
||||
const upstream = await this.wsFactory(prepared.json.upstreamUrl, {
|
||||
browser: prepared.json.browser || "chrome_149",
|
||||
const wsOptions = {
|
||||
// #5591: chrome_149 is not a wreq-js 2.3.1 profile (max chrome_147); the
|
||||
// prepare route now sends chrome_142, this fallback matches it.
|
||||
browser: prepared.json.browser || "chrome_142",
|
||||
os: prepared.json.os || "windows",
|
||||
headers: prepared.json.headers || {},
|
||||
});
|
||||
};
|
||||
// #5611: forward the configured proxy so the upstream WS connect honors the
|
||||
// Proxy Registry in no-direct-egress deployments.
|
||||
if (prepared.json.proxy) wsOptions.proxy = prepared.json.proxy;
|
||||
const upstream = await this.wsFactory(prepared.json.upstreamUrl, wsOptions);
|
||||
|
||||
upstream.onmessage = (event) => {
|
||||
if (this.closed) return;
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
} from "@/lib/memory/settings";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
||||
import { logger } from "@omniroute/open-sse/utils/logger.ts";
|
||||
import { resolveProxy } from "@omniroute/open-sse/utils/networkProxy.ts";
|
||||
import { proxyConfigToUrl } from "@omniroute/open-sse/utils/proxyDispatcher.ts";
|
||||
|
||||
const CODEX_RESPONSES_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
|
||||
const executor = new CodexExecutor();
|
||||
@@ -408,16 +410,32 @@ async function prepare(body: JsonRecord) {
|
||||
|
||||
const headers = normalizeUpstreamHeaders(executor.buildHeaders(refreshedCredentials, true));
|
||||
|
||||
// #5611: apply the configured Global/provider proxy to the upstream Codex
|
||||
// Responses WebSocket too. The downstream client→OmniRoute hop works, but the
|
||||
// upstream wreq-js.websocket() connect previously ignored the Proxy Registry,
|
||||
// so a no-direct-egress container failed with a DNS lookup error.
|
||||
let proxy: string | undefined;
|
||||
try {
|
||||
proxy = proxyConfigToUrl(await resolveProxy(provider)) || undefined;
|
||||
} catch (err) {
|
||||
logger.warn(`[codex-responses-ws] proxy resolution failed: ${sanitizeErrorMessage(err)}`);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
upstreamUrl: CODEX_RESPONSES_WS_URL,
|
||||
browser: "chrome_149",
|
||||
// #5591: chrome_149 does not exist in wreq-js 2.3.1 (max chrome_147) → the
|
||||
// native layer yields a degenerate TLS fingerprint and ChatGPT rejects the
|
||||
// upgrade ("Invalid JSON body"). chrome_142 is the profile that shipped in
|
||||
// v3.8.39 and is confirmed working against this upstream.
|
||||
browser: "chrome_142",
|
||||
os: "windows",
|
||||
connectionId: refreshedCredentials.connectionId,
|
||||
provider,
|
||||
account: refreshedCredentials.email || null,
|
||||
model,
|
||||
headers,
|
||||
proxy,
|
||||
response: transformed,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ test("responses ws proxy prepares and forwards OpenAI Responses websocket events
|
||||
connectionId: "conn_1",
|
||||
provider: "codex",
|
||||
account: "codex@example.com",
|
||||
// #5611: prepare resolves the configured proxy and threads it through.
|
||||
proxy: "http://test-proxy:8888",
|
||||
model: "gpt-5.5",
|
||||
response: {
|
||||
...body.response,
|
||||
@@ -133,6 +135,11 @@ test("responses ws proxy prepares and forwards OpenAI Responses websocket events
|
||||
wsFactory: async (url, options) => {
|
||||
assert.equal(url, "wss://chatgpt.com/backend-api/codex/responses");
|
||||
assert.equal(options.headers.Authorization, "Bearer upstream-token");
|
||||
// #5591: prepare omits `browser`, so the fallback must be the supported
|
||||
// chrome_142 (not the non-existent chrome_149).
|
||||
assert.equal(options.browser, "chrome_142");
|
||||
// #5611: the configured proxy from prepare must reach the upstream connect.
|
||||
assert.equal(options.proxy, "http://test-proxy:8888");
|
||||
return fakeUpstream;
|
||||
},
|
||||
});
|
||||
|
||||
57
tests/unit/tls-profiles-valid-5591.test.mjs
Normal file
57
tests/unit/tls-profiles-valid-5591.test.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// #5591 regression guard: every chrome_* TLS impersonation profile referenced in
|
||||
// the source must be a real wreq-js BrowserProfile. PR #5237 set them to
|
||||
// "chrome_149", which does not exist in wreq-js 2.3.1 (the union tops out at
|
||||
// chrome_147) — the native layer then produced a degenerate fingerprint and the
|
||||
// Codex Responses WebSocket upstream rejected the upgrade ("Invalid JSON body").
|
||||
// This test reads the supported set straight from the installed wreq-js type
|
||||
// definitions, so it stays correct as the dependency is upgraded.
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
|
||||
function supportedProfiles() {
|
||||
const dts = fs.readFileSync(
|
||||
path.join(ROOT, "node_modules", "wreq-js", "dist", "wreq-js.d.ts"),
|
||||
"utf8"
|
||||
);
|
||||
return new Set([...dts.matchAll(/chrome_(\d+)/g)].map((m) => `chrome_${m[1]}`));
|
||||
}
|
||||
|
||||
// Source files that hand a `browser`/PROFILE value to wreq-js.
|
||||
const SOURCES = [
|
||||
"src/app/api/internal/codex-responses-ws/route.ts",
|
||||
"scripts/dev/responses-ws-proxy.mjs",
|
||||
"open-sse/services/grokTlsClient.ts",
|
||||
"open-sse/services/claudeTlsClient.ts",
|
||||
];
|
||||
|
||||
// Strip comments before scanning — explanatory comments may name the bad
|
||||
// profile ("chrome_149 absent in 2.3.1") without it ever reaching wreq-js.
|
||||
function stripComments(line) {
|
||||
return line.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*$/, "");
|
||||
}
|
||||
|
||||
test("#5591 all configured chrome_* TLS profiles exist in wreq-js", () => {
|
||||
const supported = supportedProfiles();
|
||||
assert.ok(supported.size > 0, "expected to parse chrome_* profiles from wreq-js d.ts");
|
||||
|
||||
for (const rel of SOURCES) {
|
||||
const lines = fs.readFileSync(path.join(ROOT, rel), "utf8").split("\n");
|
||||
lines.forEach((line, i) => {
|
||||
const code = stripComments(line);
|
||||
for (const m of code.matchAll(/chrome_(\d+)/g)) {
|
||||
const profile = `chrome_${m[1]}`;
|
||||
assert.ok(
|
||||
supported.has(profile),
|
||||
`${rel}:${i + 1} uses ${profile} which is NOT a wreq-js BrowserProfile ` +
|
||||
`(supported: ${[...supported].sort().join(", ")})`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user