Existing projects that already speak the OpenAI or Anthropic wire format
can now route their single-shot Q&A traffic through a visible provider
session instead of a paid API, by pointing base_url at the daemon.
New daemon routes, behind the existing control bearer token:
POST /v1/openai/chat/completions
GET /v1/openai/models
POST /v1/anthropic/messages
The proxy ships disabled. `tokenless api-proxy <status|enable|disable>`
and an interactive setup step turn it on and pick how proxied turns map
onto provider conversations: `new-conversation` flattens the transcript
and starts a fresh conversation per request, while
`continue-conversation` fingerprints all but the final user turn and
reuses one provider conversation for that thread.
`model` must name the provider explicitly as `tokenless/<provider>`. An
unmapped model is rejected rather than redirected, so it stays visible
which account and which subscription answered. Tool, function, and
structured-output fields fail closed because visible pages expose no
equivalent control. `stream: true` replays each dialect's documented
event sequence as one terminal chunk, since a visible reply is only
readable once it has finished rendering. Reported usage is always zero:
Tokenless does not meter provider tokens.
Also adds Linux x64 to the managed browser catalog with pinned
CloakBrowser 146 and Chrome for Testing 146, discovers system browsers
from their Linux install paths instead of falling through to the Windows
branch, and extracts zip artifacts with unzip where GNU tar cannot read
them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What
Lets an existing project that already speaks the OpenAI or Anthropic wire format route its single-shot Q&A traffic through a visible provider session instead of a paid API, by pointing
base_urlat the daemon.New routes on the existing daemon HTTP server, behind the existing control bearer token:
/v1/openai/chat/completions/v1/openai/models/v1/anthropic/messagesBoth dialects normalize into one job submission that reuses
createManagedPlaywrightJobRequest→prompt.input/prompt.submit/response.read, then polls to a terminal state.This is a task-level bridge, not a drop-in API replacement. It trades throughput, latency, streaming, and tool use for cost.
Design decisions
Disabled by default. It accepts local API traffic, so enabling it without an explicit request would widen what the daemon answers.
tokenless api-proxy <status|enable|disable>plus an interactive setup step turn it on.Explicit
tokenless/<provider>model naming. An unmapped model such asgpt-4ois rejected rather than redirected, so it stays visible which account and which subscription answered.Two conversation modes, chosen at setup or with
--conversation-mode:new-conversationflattens the transcript into one prompt and starts a fresh provider conversation per request. Stateless.continue-conversationfingerprints every message except the final user turn and reuses one provider conversation for that thread. A caller that edits or truncates history starts a new conversation instead of appending to a transcript the provider no longer shares.Fails closed on what visible pages cannot do.
tools,tool_choice,functions,function_call, andresponse_formatreturn 400 — returning prose where the caller expects a tool call would be worse than an error.stream: truereplays each dialect's documented event sequence as one terminal chunk. A visible reply is only readable once it has finished rendering, so there is no partial text to forward; refusingstreamwould break otherwise compatible clients for no benefit.Reported
usageis always zero. Tokenless does not meter provider tokens — the reply is billed by the caller's own web subscription, so a fabricated count would be worse than none.Also included
Linux x64 support in the managed browser catalog, which was needed to exercise any of this on WSL:
cloak:linux-x64(CloakBrowser 146.0.7680.177, sha256 checked against the releaseSHA256SUMS) andmanaged-chromium:linux-x64(Chrome for Testing 146.0.7680.165). The second one is required becauseautois the default selection and would otherwise throwNo managed browser catalog entryon Linux.systemBrowserExecutablepreviously fell through to the WindowsLOCALAPPDATA/PROGRAMFILESbranch on Linux. It now discovers/opt/google/chrome/chromeand friends.tarfor every archive. GNU tar cannot read zip, somanaged-chromiumandautofailed on Linux withtar: This does not look like a tar archive. Zip now goes throughunzip, and the archive path-traversal check switches fromtar -tftounzip -Z1so the security check is not bypassed. Note: this makesunzipa hard dependency on Linux.Verification
Real boundaries only, no mocks:
test/api-proxy-local-http.integration.test.mjs— 8 tests, all passing, against a real daemon over real HTTP with a real filesystem.checksumVerified=true.developerrole accepted, non-text content parts rejected, each rejected field's message, sampling fields ignored rather than rejected, the 256-message cap, and Anthropic's role restriction. 9/9 held.npm run lintandnpm run api:checkclean.Pre-existing failures, unchanged by this branch:
test/package-contract.test.mjshas 7 andtest/local-web-control-plane.test.mjshas 1. Verified identical with the branch changes stashed; they stem frompackages/extensionexisting and global-install behavior.Docs
docs/api-proxy-integration.mdand itszh-CNpartner — the integration contract, written for an implementer.tokenless api-proxysections in bothCOMMANDSreferences.docs/READMEindexes updated..gitignoregains two allowlist entries:docs/*is allowlist-ignored, so the new docs would not have been committed while the tracked indexes linked to them.Known gap, not addressed here
Every non-auth failure returns
400/invalid_inputregardless of cause, so a client cannot tell a malformed request from a transient provider blocker and can only string-matchmessage. The integration doc says so explicitly and tells clients not to auto-retry. Mapping these onto 401/403/409/429/504 with a stable machine-readable code is worth a follow-up — a realprovider_rate_limitedfalse positive was already hit in testing.🤖 Generated with Claude Code