Repository navigation
feat(browser): adopt Obscura as primary headless browser engine with Chromium fallback - #12286
Merged
diegosouzapw merged 5 commits intoSep 3, 2026
Conversation
9 tasks done
Introduce Obscura (https://github.com/h4ckf0r0day/obscura) — a lightweight Rust headless browser (~30MB) — as the primary engine for the shared browser pool and the Cloudflare Playground executor. Obscura speaks CDP and is driven by Playwright's chromium.connectOverCDP, so no API or provider- facing behavior changes. Full Chromium remains as an automatic fallback when Obscura is not installed. Key changes: - New shared module open-sse/services/obscura.ts: spawns one obscura serve process lazily, shared across pool and executors for the process lifetime - browserPool.ts: tries Obscura first, falls back to Cloakbrowser then Chromium; tracks engine type in pool status; pendingContexts now have a 5-min TTL to prevent silent stale-entry leaks - cloudflare-playground.ts: calls connectObscuraBrowser() first, falls back to direct Chromium launch when unavailable - New integration tests: 5/5 pass including round-trip page load - docs/reference/ENVIRONMENT.md: document OBSCURA_BIN, OBSCURA_CDP_ENDPOINT and OBSCURA_PORT env vars Fixes cheaper-inference#12274
opensource-elearning
force-pushed
the
feat/obscura-browser-engine
branch
from
September 1, 2026 10:50
410c96b to
b01bb5e
Compare
Add OBSCURA_BIN, OBSCURA_CDP_ENDPOINT, and OBSCURA_PORT to .env.example, avoid treating system PATH as a documented env var, and externalize the browserPool sibling import in the isolated webpack createRequire test. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… browser pool The pool gained a headed-browser path (headedBrowser/headedLaunching, resolvePlainBrowserLaunchOptions, per-headless launch state) after this branch forked. Rather than replacing launchBrowser() with the single-browser form this branch was written against — which would have dropped headed support — the Obscura preference is reapplied inside launchBrowserInstance()'s headless branch, ahead of cloakbrowser and plain Chromium. A headed browser must be a real windowed Chromium, so the engine preference is headless-only.
diegosouzapw
merged commit Sep 3, 2026
8df944c
into
cheaper-inference:release/v3.8.51
4 of 7 checks passed
diegosouzapw
added a commit
to opensource-elearning/OmniRoute
that referenced
this pull request
Sep 3, 2026
…h's real wins Four files drifted, and the tip had already absorbed part of this branch's intent through better abstractions: - sseCollect.ts: the tip extracted stripObfuscationZeroWidth() into utils/zeroWidth.ts, which supersedes the local ZERO_WIDTH_RE (now removed). The hoisted TEXTUAL_TOOL_CALL_RE is kept — that regex was still inline in a hot path on the tip. - resultMemo.ts: the tip's memoStore() returns the stored clone so the common 'memoStore(k, r); return memoLookup(k)!' idiom skips a second multi-MB deep clone. That contract is kept (this branch reverted it to void); the JSON.parse(JSON.stringify()) round-trip is replaced with structuredClone() on both ends, which was this branch's actual perf point. - browserPool.ts: the tip now has a headed-browser path and the Obscura engine (cheaper-inference#12286). Kept both, plus this branch's pendingContexts TTL sweep, adapted to the tip's poolKey naming. - executeAttempt.ts: kept the tip's explanatory comment. Also fixes four typecheck errors this branch introduced: hasUnsupportedSignal was typed boolean but evaluated to string | boolean, and extractUsage()'s fast-path indexed c.response/c.message as unknown.
diegosouzapw
pushed a commit
that referenced
this pull request
Sep 3, 2026
…quadratic buffering (#12179) Validado sobre o tip de `release/v3.8.51` após reconciliar quatro arquivos que driftaram. Em parte o tip já tinha absorvido a intenção deste branch por abstrações melhores, então mantive a forma do tip e trouxe os ganhos que ainda eram reais: - **`sseCollect.ts`** — o tip extraiu `stripObfuscationZeroWidth()` para `utils/zeroWidth.ts`, o que supera o `ZERO_WIDTH_RE` local (removido). A içada de `TEXTUAL_TOOL_CALL_RE` foi mantida: essa regex ainda estava inline num caminho quente. - **`resultMemo.ts`** — o `memoStore()` do tip devolve o clone armazenado para que o idiom comum `memoStore(k, r); return memoLookup(k)!` evite um segundo deep-clone de vários MB. Esse contrato foi preservado (o branch o revertia para `void`), e o round-trip `JSON.parse(JSON.stringify())` virou `structuredClone()` nas duas pontas — que era o ponto de performance real do branch. - **`browserPool.ts`** — o tip agora tem caminho headed e o engine Obscura (#12286). Ambos preservados, mais a varredura de TTL do `pendingContexts` deste branch, adaptada ao nome `poolKey` do tip. - **`executeAttempt.ts`** — mantido o comentário explicativo do tip. Também corrigi **quatro erros de typecheck que o branch introduzia**: `hasUnsupportedSignal` estava tipado `boolean` mas avaliava para `string | boolean`, e o fast-path de `extractUsage()` indexava `c.response`/`c.message` como `unknown`. `typecheck:core` limpo e **482/482** nos testes de antigravity + compressão na própria branch. Obrigado, @opensource-elearning.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…Chromium fallback (cheaper-inference#12286) Validado sobre o tip de release/v3.8.51 após reconciliar o base-drift. O pool de browsers ganhou um caminho **headed** (`headedBrowser`/`headedLaunching`, `resolvePlainBrowserLaunchOptions`, estado de launch por modo) depois que esta branch forkou. O PR reescrevia `launchBrowser()` no modelo de browser único contra o qual foi escrito, o que teria **removido o suporte headed**. Em vez disso, reapliquei a preferência pelo Obscura dentro do ramo headless de `launchBrowserInstance()`, à frente do cloakbrowser e do Chromium puro — um browser headed precisa ser um Chromium com janela real, então a preferência de engine é só do caminho headless. `state.engine` alimenta `isStealth` e `getBrowserPoolStatus()`, e o shutdown zera o engine sem matar o servidor Obscura compartilhado (dono: `./obscura.ts`). typecheck:core limpo e 3/3 em tests/unit/obscura-integration.test.ts. Obrigado, @opensource-elearning.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…quadratic buffering (cheaper-inference#12179) Validado sobre o tip de `release/v3.8.51` após reconciliar quatro arquivos que driftaram. Em parte o tip já tinha absorvido a intenção deste branch por abstrações melhores, então mantive a forma do tip e trouxe os ganhos que ainda eram reais: - **`sseCollect.ts`** — o tip extraiu `stripObfuscationZeroWidth()` para `utils/zeroWidth.ts`, o que supera o `ZERO_WIDTH_RE` local (removido). A içada de `TEXTUAL_TOOL_CALL_RE` foi mantida: essa regex ainda estava inline num caminho quente. - **`resultMemo.ts`** — o `memoStore()` do tip devolve o clone armazenado para que o idiom comum `memoStore(k, r); return memoLookup(k)!` evite um segundo deep-clone de vários MB. Esse contrato foi preservado (o branch o revertia para `void`), e o round-trip `JSON.parse(JSON.stringify())` virou `structuredClone()` nas duas pontas — que era o ponto de performance real do branch. - **`browserPool.ts`** — o tip agora tem caminho headed e o engine Obscura (cheaper-inference#12286). Ambos preservados, mais a varredura de TTL do `pendingContexts` deste branch, adaptada ao nome `poolKey` do tip. - **`executeAttempt.ts`** — mantido o comentário explicativo do tip. Também corrigi **quatro erros de typecheck que o branch introduzia**: `hasUnsupportedSignal` estava tipado `boolean` mas avaliava para `string | boolean`, e o fast-path de `extractUsage()` indexava `c.response`/`c.message` como `unknown`. `typecheck:core` limpo e **482/482** nos testes de antigravity + compressão na própria branch. Obrigado, @opensource-elearning.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12274
Summary
Replaces the full Chromium/Cloakbrowser browser engine with Obscura (a ~30MB Rust headless browser) as the primary engine for the shared browser pool and the Cloudflare Playground executor. Obscura speaks the Chrome DevTools Protocol, so Playwright's
chromium.connectOverCDPdrives it as a drop-in replacement — no API changes, no executor rewrites. Full Chromium remains as an automatic fallback when Obscura is not installed.Problem
The current browser pool launches one full Chromium process (~150–400MB resident) per provider that needs browser-grade operations:
browserPool.ts→launchBrowser()cloudflare-playground.ts→PlaywrightCfTransport.start()On a VPS with limited RAM, holding 2 Chromium processes alongside the Node.js server and database causes OOM kills and session terminations. This is the memory profile before this change.
Solution
New shared module:
open-sse/services/obscura.tsOne
obscura serveprocess is spawned lazily on first use and shared for the server's lifetime. Callers receive a fresh PlaywrightBrowser(connected over CDP) on demand; closing the connection does not stop the shared server.Lifecycle management:
obscura serve --port <random> --host 127.0.0.1once, then caches the process/json/version(Obscura never completes a response for a bareGET /)OBSCURA_CDP_ENDPOINTenv var: use an already-running Obscura externally (the module does not own the process)OBSCURA_BINenv var: point to a non-standard binary locationOMNIROUTE_BROWSER_POOL=off: disables Obscura entirely, same as disabling the poolkillSharedObscuraServer(): terminates the shared process (process-lifetime cleanup)browserPool.tschangeslaunchBrowser()now tries Obscura first viaconnectObscuraBrowser(), then falls back to Cloakbrowser, then plain Chromium — the same fallback chain as before, just with Obscura at the front:Other improvements in
browserPool.ts:enginefield tracked inPoolStateand surfaced viagetBrowserPoolStatus()/getBrowserPoolMetrics()so operators can see which engine is activependingContextsmap now stores{ promise, createdAt }with a 5-minute TTL: stale pending entries (from failed context creations) are evicted, preventing silent leaksisStealthdetection includes Obscura:state.engine === "obscura" || state.cloakLaunch !== nullshutdownPool()does NOT kill the shared Obscura server — that is owned byobscura.tsand reused by executorscloudflare-playground.tschangesPlaywrightCfTransport.start()callsconnectObscuraBrowser()first. If available, the executor attaches to the shared Obscura server. If not, it falls back to launching its own Chromium as before.Installing Obscura
Obscura is an optional system binary — not an npm dependency. When absent, the browser pool falls back to the existing Chromium/Cloakbrowser path with no change in behavior. To get the memory savings, install the binary and place it on
PATH.Quick install (Linux x86_64):
All supported platforms (from the Obscura releases):
obscura-x86_64-linux.tar.gzobscura-aarch64-linux.tar.gzobscura-aarch64-macos.tar.gzobscura-x86_64-macos.tar.gzAlternative methods:
docker run -d --name obscura -p 127.0.0.1:9222:9222 h4ckf0r0day/obscuragit clone https://github.com/h4ckf0r0day/obscura && cd obscura && cargo build --release -p obscura-cli --bins --no-default-features(requires Rust 1.75+)yay -S obscura-browsernix-env -iA nixpkgs.obscuraNo action required if you do not install Obscura — everything works the same as before.
Documentation update
docs/reference/ENVIRONMENT.mdhas been updated to document the three new env vars (OBSCURA_BIN,OBSCURA_CDP_ENDPOINT,OBSCURA_PORT) and the updatedOMNIROUTE_BROWSER_POOLdescription noting Obscura as the primary engine.Memory comparison (verified)
The ~30MB figure is Obscura's reported resident memory (
RSS) measured withpsafter spawningobscura serve— verified on this deployment target (x86_64 Linux, AMD64).Scope — what this deliberately does NOT change
Browserobject still gets one. The only difference is which process backs it.ERR_BN_LIMIT418 responses) are a separate problem tracked in feat(providers): adopt Obscura as primary headless browser engine with Chromium/Chrome fallback #12274 and require a proxy rotation hook, not a browser engine change. That work is intentionally excluded from this PR.browserPoolAPI.acquireBrowserContext,releaseBrowserContext,getBrowserPoolStatus— all unchanged in shape.obscura servecommand-line interface. The module uses the documented--portand--hostflags only.Test plan
node --import tsx/esm --test tests/unit/obscura-integration.test.ts(Obscura binary installed)tsc -p tsconfig.json --noEmitnode --import tsx/esm --test tests/unit/obscura-integration.test.ts(Obscura binary absent)What the round-trip test covers
Spawns the shared
obscura serve, connects Playwright viachromium.connectOverCDP, creates a new context, navigates tohttps://example.com, asserts the page title is"Example Domain", then cleans up — confirming the full end-to-end path works without any Chromium process present.Verification
browserPool.tstypechecks cleanlyobscura.tstypechecks cleanlycloudflare-playground.tstypechecks cleanlygetBrowserPoolStatus()returns the newenginefieldkillSharedObscuraServer()does not run on pool shutdownpendingContextsTTL eviction prevents silent stale-entry leaks