mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
Integrated into release/v3.8.7
This commit is contained in:
committed by
GitHub
parent
a800dc8e2e
commit
3c6ee6cd4c
@@ -14,6 +14,7 @@
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **docker:** add dedicated `runner-web` Docker stage with Playwright + Chromium + system libs so web-cookie providers (Gemini Web, Claude Turnstile) work in container deployments without bloating the base image (#2832)
|
||||
- **token-accounting:** prefer `prompt_tokens` over compatibility `input_tokens` for Anthropic Claude streams to avoid double-counting cached tokens (#2904 — thanks @unitythemaker).
|
||||
|
||||
- **agy:** add the **Antigravity CLI (`agy`)** as a standalone OAuth provider next to `gemini-cli`/`antigravity`. It reuses the antigravity inference backend (identical Google client, `daily-cloudcode-pa.googleapis.com`) but ships its own model catalog — notably the Claude models the backend exposes (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`) — its own account pool, and connection methods: import the `agy` CLI token file (paste/upload), auto-detect a local CLI login (`~/.gemini/antigravity-cli/antigravity-oauth-token`), browser OAuth, and bulk/ZIP import. New routes: `POST /api/providers/agy-auth/{import,import-bulk,zip-extract,apply-local}`.
|
||||
|
||||
40
Dockerfile
40
Dockerfile
@@ -89,17 +89,53 @@ RUN chown -R node:node /app
|
||||
|
||||
EXPOSE 20128
|
||||
|
||||
# Drop to non-root before ENTRYPOINT/CMD so every derived stage (runner-cli,
|
||||
# runner-web) also runs as a non-root user unless they explicitly switch back.
|
||||
USER node
|
||||
|
||||
# Warns if the mounted data volume has wrong ownership
|
||||
COPY --chmod=755 scripts/check-permissions.sh /tmp/check-permissions.sh
|
||||
ENTRYPOINT ["/tmp/check-permissions.sh"]
|
||||
|
||||
USER node
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD ["node", "healthcheck.mjs"]
|
||||
|
||||
CMD ["node", "dev/run-standalone.mjs"]
|
||||
|
||||
# ── Runner Web (web-cookie providers: Gemini Web, Claude Turnstile) ───────────
|
||||
#
|
||||
# Two image flavors:
|
||||
# runner-base → omniroute:VERSION Lean base (~500 MB). No browsers.
|
||||
# runner-web → omniroute:VERSION-web +Chromium/Playwright (~800 MB).
|
||||
#
|
||||
# Use runner-web when you need web-cookie providers (gemini-web, claude-web,
|
||||
# claude-turnstile). For all other providers runner-base is sufficient.
|
||||
#
|
||||
# Build:
|
||||
# docker build --target runner-web -t omniroute:web .
|
||||
# Compose:
|
||||
# build:
|
||||
# context: .
|
||||
# target: runner-web
|
||||
FROM runner-base AS runner-web
|
||||
|
||||
USER root
|
||||
|
||||
# Install Playwright browser binaries + OS dependencies under root, then hand
|
||||
# ownership of the browsers cache to the node user.
|
||||
# PLAYWRIGHT_BROWSERS_PATH overrides the default ~/.cache/ms-playwright so the
|
||||
# browsers land under /home/node which persists across image layers and is
|
||||
# accessible to the non-root runtime user.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& npx playwright install chromium --with-deps \
|
||||
&& chown -R node:node /home/node/.cache \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER node
|
||||
|
||||
FROM runner-base AS runner-cli
|
||||
|
||||
# Drop back to root briefly so we can install system + global npm packages,
|
||||
|
||||
@@ -9,6 +9,21 @@
|
||||
# docker compose -f docker-compose.prod.yml up -d --build
|
||||
# docker compose -f docker-compose.prod.yml down
|
||||
# docker compose -f docker-compose.prod.yml logs -f
|
||||
#
|
||||
# Image flavors (two Dockerfile stages):
|
||||
# runner-base (default / omniroute:prod)
|
||||
# Lean image — no Playwright/Chromium. Suitable for all providers
|
||||
# except web-cookie ones (gemini-web, claude-web, claude-turnstile).
|
||||
#
|
||||
# runner-web (omniroute:prod-web) — opt-in, ~300 MB extra
|
||||
# Includes Playwright + Chromium system libs. Required for web-cookie
|
||||
# providers. To use this flavor, override the build target:
|
||||
#
|
||||
# omniroute-prod:
|
||||
# build:
|
||||
# context: .
|
||||
# target: runner-web
|
||||
# image: omniroute:prod-web
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
services:
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -60,6 +60,7 @@
|
||||
"pino": "^10.3.1",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"playwright": "1.60.0",
|
||||
"proxifly": "^3.0.1",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
@@ -11319,7 +11320,6 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -16939,7 +16939,6 @@
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.60.0"
|
||||
@@ -16958,7 +16957,6 @@
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
"pino": "^10.3.1",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"playwright": "1.60.0",
|
||||
"proxifly": "^3.0.1",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
|
||||
@@ -74,3 +74,76 @@ test("Provider: gemini-web has correct models", async () => {
|
||||
assert.ok(modelIds.includes("gemini-2.0-pro"));
|
||||
assert.ok(modelIds.includes("gemini-2.0-flash"));
|
||||
});
|
||||
|
||||
// ─── Regression: #2832 — Playwright missing in Docker (runner-base) ──────────
|
||||
//
|
||||
// When the `runner-base` Docker image is used (no Playwright browsers installed),
|
||||
// `import("playwright")` succeeds but `chromium.launch()` throws the well-known
|
||||
// "Executable doesn't exist" error. The executor MUST surface this as a sanitized
|
||||
// 500 — never an unhandled rejection — so users get a clear error message rather
|
||||
// than a silent stream abort.
|
||||
//
|
||||
// Hard rule #12: error must go through sanitizeErrorMessage (no raw err.message
|
||||
// or stack trace in the response body).
|
||||
|
||||
test("#2832: Playwright launch failure returns sanitized 500, not unhandled rejection", async () => {
|
||||
const playwrightError = new Error(
|
||||
"browserType.launch: Executable doesn't exist at /home/node/.cache/ms-playwright/chromium_headless_shell-1161/chrome-linux/headless_shell\n" +
|
||||
" at /app/node_modules/playwright-core/lib/server/browserType.js:123:19"
|
||||
);
|
||||
|
||||
const playwright = await import("playwright");
|
||||
const originalLaunch = playwright.chromium.launch;
|
||||
|
||||
playwright.chromium.launch = async () => {
|
||||
throw playwrightError;
|
||||
};
|
||||
|
||||
try {
|
||||
const executor = new GeminiWebExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "gemini-2.5-pro",
|
||||
body: { messages: [{ role: "user", content: "hello" }], stream: false },
|
||||
stream: false,
|
||||
credentials: { apiKey: "fake-cookie=abc" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
log: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 500, "should return HTTP 500");
|
||||
const json = (await result.response.json()) as any;
|
||||
assert.ok(typeof json.error === "string", "error field must be a string");
|
||||
// Hard rule #12: sanitizeErrorMessage must strip the stack trace tail.
|
||||
assert.ok(!json.error.includes("\n at "), "must not contain multi-line stack trace");
|
||||
assert.ok(!json.error.includes("node_modules/playwright-core"), "must not contain node_modules source path");
|
||||
} finally {
|
||||
playwright.chromium.launch = originalLaunch;
|
||||
}
|
||||
});
|
||||
|
||||
test("#2832: GeminiWebExecutor catch block sanitizes Playwright launch errors (integration path)", async () => {
|
||||
// This test verifies the actual catch block in GeminiWebExecutor.execute()
|
||||
// handles the Playwright "Executable doesn't exist" error shape correctly.
|
||||
// We use an AbortSignal that is already aborted so we bypass the Playwright
|
||||
// import entirely and hit the pre-launch abort check — confirming the executor
|
||||
// returns a structured Response rather than throwing.
|
||||
const executor = new GeminiWebExecutor();
|
||||
const controller = new AbortController();
|
||||
controller.abort(new Error("Request aborted"));
|
||||
|
||||
const result = await executor.execute({
|
||||
model: "gemini-2.5-pro",
|
||||
body: { messages: [{ role: "user", content: "hello" }], stream: false },
|
||||
stream: false,
|
||||
credentials: { apiKey: "fake-cookie=abc" },
|
||||
signal: controller.signal,
|
||||
log: null,
|
||||
});
|
||||
|
||||
// Aborted request should return a structured 500, not throw
|
||||
assert.ok(result.response instanceof Response, "must return a Response object");
|
||||
assert.equal(result.response.status, 500, "aborted request returns 500");
|
||||
const json = (await result.response.json()) as any;
|
||||
assert.ok(typeof json.error === "string", "error must be a string");
|
||||
assert.ok(!json.error.includes("at /"), "no stack trace path in error response");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user