fix(opencode): align Zen User-Agent with upstream OpenCode client#738
fix(opencode): align Zen User-Agent with upstream OpenCode client#738Astro-Han wants to merge 2 commits into
Conversation
📝 WalkthroughWalkthroughThis PR adds upstream opencode-ai package version detection and propagates it through the build system and LLM provider headers. The script package resolves the upstream version from npm registry or environment, core types declare and export the constant, the build system injects it as compile-time values, and LLM requests use it in User-Agent headers. ChangesUpstream Version Tracking
🎯 2 (Simple) | ⏱️ ~12 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the User-Agent header to LLM requests and adds a corresponding test case to ensure headers are correctly transmitted. Feedback suggests refactoring the header assignment to reduce duplication across conditional branches.
| "x-opencode-session": input.sessionID, | ||
| "x-opencode-request": input.user.id, | ||
| "x-opencode-client": Flag.OPENCODE_CLIENT, | ||
| "User-Agent": `opencode/${Installation.VERSION}`, |
PawWork ships under its own CalVer (e.g. 2026.5.18), but upstream Zen `checkHeaders` likely gates the normal free tier on a `User-Agent` that matches the official OpenCode client shape (`opencode/<semver>`). The prior commit only fixed the `opencode/` prefix; the version suffix still did not match the upstream `opencode-ai` npm release line, so PawWork requests would still look like an unknown client and could be routed to the lower `dailyRequestsFallback` quota. Inject a separate `OPENCODE_UPSTREAM_VERSION` build-time constant fetched from `registry.npmjs.org/opencode-ai/latest` (with an `OPENCODE_UPSTREAM_VERSION` env override for offline or pinned CI), expose it as `Installation.UPSTREAM_VERSION`, and use it only for the `opencode*` provider request `User-Agent`. The user-facing PawWork version, update channel, telemetry, and the non-`opencode*` provider UA are unchanged. Verification: - bun --cwd packages/opencode test test/session/llm.test.ts -> 21 passed - bun --cwd packages/opencode test test/script/build-node.test.ts -> 1 passed (locks in the new define) - bun --cwd packages/opencode run typecheck -> ok - bun --cwd packages/core run typecheck -> ok Refs #737
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/opencode/test/session/llm.test.ts`:
- Line 608: The test currently asserts the User-Agent header starts with
`opencode/${Installation.UPSTREAM_VERSION} ` (note the trailing space) which
fails when the header is exactly `opencode/<version>`; update the assertion that
checks capture.headers.get("User-Agent") to use a prefix check without the
forced trailing space (e.g. use
startsWith(`opencode/${Installation.UPSTREAM_VERSION}`) or an equivalent regex)
so both exact-match and space-suffixed forms pass; ensure the same reference to
Installation.UPSTREAM_VERSION is used so the version check remains accurate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 95378d57-443a-4ec2-a668-df7e4fe0093e
📒 Files selected for processing (8)
packages/core/src/installation/version.tspackages/opencode/script/build-node.tspackages/opencode/script/build.tspackages/opencode/src/installation/index.tspackages/opencode/src/session/llm.tspackages/opencode/test/script/build-node.test.tspackages/opencode/test/session/llm.test.tspackages/script/src/index.ts
| }) | ||
|
|
||
| const capture = await request | ||
| expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION} `)).toBe(true) |
There was a problem hiding this comment.
Make the User-Agent prefix assertion tolerant of exact-match format.
Line 608 currently requires a trailing space after the version, so it fails if the header is exactly opencode/<version>. Check the prefix without forcing that space.
Suggested fix
- expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION} `)).toBe(true)
+ expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION}`)).toBe(true)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION} `)).toBe(true) | |
| expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION}`)).toBe(true) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/opencode/test/session/llm.test.ts` at line 608, The test currently
asserts the User-Agent header starts with
`opencode/${Installation.UPSTREAM_VERSION} ` (note the trailing space) which
fails when the header is exactly `opencode/<version>`; update the assertion that
checks capture.headers.get("User-Agent") to use a prefix check without the
forced trailing space (e.g. use
startsWith(`opencode/${Installation.UPSTREAM_VERSION}`) or an equivalent regex)
so both exact-match and space-suffixed forms pass; ensure the same reference to
Installation.UPSTREAM_VERSION is used so the version check remains accurate.
|
Closing without merge. Researched how peer projects (Cursor, Windsurf, Apollo / RIF / Tweetbot, Brave, Cline, Roo Code, Continue, Aider, acheong08/ChatGPT) handled comparable upstream-gated-service situations. Two findings make this direction wrong, not just risky:
Additional points covered by the research:
#737 will be reframed away from "isolation audit" toward "move PawWork off Zen as default + establish a friendly relationship with upstream." Followup issues will track the BYO-key onboarding work. Full research notes are in the project's local research log (not in this repo). |
Summary
Make PawWork's
opencode*provider requests look like the official OpenCode client on the wire: send aUser-Agentwhose prefix and version both match upstream OpenCode (opencode/<upstream-semver>), instead ofopencode/<PawWork-CalVer>.Why
PawWork is positioned as a fork that should inherit the same free quota as upstream OpenCode. Two users hit
Free usage exceeded, subscribe to Go https://opencode.ai/gowithin a short window, with retry-after values around 50000s, which lines up with the lowerdailyRequestsFallbackbucket rather than the normal free daily limit. Upstream Zen now appears to run requests through a privatecheckHeadersstep before choosing which bucket to charge, and PawWork's request shape did not match: theopencode*provider branch insession/llm.tswas missingUser-Agententirely, so the AI SDK was sendingai-sdk/openai-compatible/...as the agent, and even after adding the prefix the version suffix was still PawWork's CalVer (2026.5.18), which cannot parse as a semver in upstream's known release line (opencode-ainpm latest is currently1.15.4). Both mismatches are independently sufficient to fail a strictcheckHeadersmatch. This PR closes both. We cannot proveUser-Agentis the only condition since upstreamcheckHeadersis private, so this is "match the highest-probability mismatch" rather than "proven root cause".Related Issue
Refs #737
Human Review Status
Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.
Review Focus
Whether the upstream-version injection path is right:
Script.upstreamVersionalways fetchesregistry.npmjs.org/opencode-ai/latestat build time (withOPENCODE_UPSTREAM_VERSIONenv override), Bundefinewrites it into bothbuild.tsandbuild-node.tsbundles,Installation.UPSTREAM_VERSIONfalls back toInstallation.VERSIONin dev/test, and only theopencode*providerUser-Agentconsumes it. Product-facing version, update channel, telemetry, and the non-opencode*provider UA are all unchanged.Risk Notes
Build-time network dependency: release builds will fail loudly if
registry.npmjs.orgis unreachable. TheOPENCODE_UPSTREAM_VERSIONenv override gives CI an escape hatch (and we should consider adding a pinned fallback inpackage.jsonif this becomes flaky in practice). The constant is frozen at build time, so if upstream rotates an allowlist and we lag, a patch release is needed to pick up a fresh value. Not a regression risk: in dev/test the fallback toInstallation.VERSIONkeeps the local UA shape stable.How To Verify
A real A/B against upstream Zen's quota decision still cannot be observed without upstream logs or a live failing-network rerun after release. That residual uncertainty stays with #737.
Screenshots or Recordings
Not applicable; no visible UI change.
Checklist
Summary by CodeRabbit
Release Notes