Skip to content

fix(opencode): align Zen User-Agent with upstream OpenCode client#738

Closed
Astro-Han wants to merge 2 commits into
devfrom
codex/fix-i737-opencode-ua
Closed

fix(opencode): align Zen User-Agent with upstream OpenCode client#738
Astro-Han wants to merge 2 commits into
devfrom
codex/fix-i737-opencode-ua

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

Make PawWork's opencode* provider requests look like the official OpenCode client on the wire: send a User-Agent whose prefix and version both match upstream OpenCode (opencode/<upstream-semver>), instead of opencode/<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/go within a short window, with retry-after values around 50000s, which lines up with the lower dailyRequestsFallback bucket rather than the normal free daily limit. Upstream Zen now appears to run requests through a private checkHeaders step before choosing which bucket to charge, and PawWork's request shape did not match: the opencode* provider branch in session/llm.ts was missing User-Agent entirely, so the AI SDK was sending ai-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-ai npm latest is currently 1.15.4). Both mismatches are independently sufficient to fail a strict checkHeaders match. This PR closes both. We cannot prove User-Agent is the only condition since upstream checkHeaders is 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.upstreamVersion always fetches registry.npmjs.org/opencode-ai/latest at build time (with OPENCODE_UPSTREAM_VERSION env override), Bun define writes it into both build.ts and build-node.ts bundles, Installation.UPSTREAM_VERSION falls back to Installation.VERSION in dev/test, and only the opencode* provider User-Agent consumes 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.org is unreachable. The OPENCODE_UPSTREAM_VERSION env override gives CI an escape hatch (and we should consider adding a pinned fallback in package.json if 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 to Installation.VERSION keeps the local UA shape stable.

How To Verify

Focused regression test: bun --cwd packages/opencode test test/session/llm.test.ts -t "sends upstream OpenCode user agent" -> 1 passed
Full LLM session test: bun --cwd packages/opencode test test/session/llm.test.ts -> 21 passed
Build define contract: bun --cwd packages/opencode test test/script/build-node.test.ts -> 1 passed (locks in the new define key)
Typecheck opencode: bun --cwd packages/opencode run typecheck -> ok
Typecheck core: bun --cwd packages/core run typecheck -> ok
Diff check: git diff --check -> ok

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

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has exactly one type label (`bug`, `enhancement`, `task`, or `documentation`), at least one primary routing label (`app`, `ui`, `platform`, `harness`, or `ci`), and exactly one priority label (`P0` to `P3`), or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting `dev`, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

Release Notes

  • New Features
    • Added upstream OpenCode version tracking throughout the system
    • Upstream version is now automatically included in LLM provider request headers for better service identification

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This 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.

Changes

Upstream Version Tracking

Layer / File(s) Summary
Upstream version resolution infrastructure
packages/script/src/index.ts
Script package adds UPSTREAM_VERSION async resolver that fetches from npm registry or env override, and exports Script.upstreamVersion getter.
Version type declarations and exports
packages/core/src/installation/version.ts
Core package declares global OPENCODE_UPSTREAM_VERSION and exports InstallationUpstreamVersion with fallback logic.
Installation module exports
packages/opencode/src/installation/index.ts
Installation module imports InstallationUpstreamVersion and re-exports as Installation.UPSTREAM_VERSION.
Build system integration
packages/opencode/script/build-node.ts, packages/opencode/script/build.ts
Node and binary build scripts inject OPENCODE_UPSTREAM_VERSION as compile-time constant via Bun's define configuration.
LLM provider header integration
packages/opencode/src/session/llm.ts
LLM session service sets User-Agent header for opencode provider requests using Installation.UPSTREAM_VERSION.
Test coverage for upstream version
packages/opencode/test/script/build-node.test.ts, packages/opencode/test/session/llm.test.ts
Build-node and LLM session tests verify OPENCODE_UPSTREAM_VERSION injection and upstream provider request headers with identification.

🎯 2 (Simple) | ⏱️ ~12 minutes

upstream

A version upstream flies through the air,
From npm's registry, oh so fair,
Through build and script, it travels wide,
In headers and types, with code as guide, 🐰✨
The system now knows what packages provide!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the main change: aligning the User-Agent for OpenCode requests with upstream client version information.
Description check ✅ Passed The pull request description comprehensively follows the required template with all major sections completed: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, Screenshots/Recordings, and Checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-i737-opencode-ua

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Astro-Han Astro-Han added bug Something isn't working app Application behavior and product flows P1 High priority labels May 18, 2026
@github-actions github-actions Bot added the harness Model harness, prompts, tool descriptions, and session mechanics label May 18, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@Astro-Han Astro-Han removed the harness Model harness, prompts, tool descriptions, and session mechanics label May 18, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/opencode/src/session/llm.ts Outdated
"x-opencode-session": input.sessionID,
"x-opencode-request": input.user.id,
"x-opencode-client": Flag.OPENCODE_CLIENT,
"User-Agent": `opencode/${Installation.VERSION}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The User-Agent header is now identical in both branches of the ternary operator (see line 396). To improve maintainability and reduce duplication, consider moving this header to the base headers object.

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
@github-actions github-actions Bot added harness Model harness, prompts, tool descriptions, and session mechanics and removed app Application behavior and product flows labels May 18, 2026
@Astro-Han Astro-Han changed the title fix(opencode): send upstream user agent for Zen requests fix(opencode): align Zen User-Agent with upstream OpenCode client May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 18b9429 and 850235f.

📒 Files selected for processing (8)
  • packages/core/src/installation/version.ts
  • packages/opencode/script/build-node.ts
  • packages/opencode/script/build.ts
  • packages/opencode/src/installation/index.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/test/script/build-node.test.ts
  • packages/opencode/test/session/llm.test.ts
  • packages/script/src/index.ts

})

const capture = await request
expect(capture.headers.get("User-Agent")?.startsWith(`opencode/${Installation.UPSTREAM_VERSION} `)).toBe(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

@Astro-Han

Copy link
Copy Markdown
Owner Author

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:

  1. Upstream Zen ToS already restricts the underlying pattern. The ToS at https://opencode.ai/legal/terms-of-service states use must be "for your own internal use, and not on behalf of or for the benefit of any third party." PawWork routing end-user traffic through Zen violates that clause regardless of User-Agent shape. This PR would conceal the pattern, not address it. Adding "fraudulent, deceptive" client identity on top of an existing ToS violation makes the position worse, not better.

  2. No commercial-scale fork or third-party client in the surveyed precedents chose to identity-spoof into an upstream-gated service. Cursor / Windsurf migrated transparently to OpenVSX. Apollo / RIF / Tweetbot shut down. Brave built Brave Search instead. Cline / Roo Code / Continue / Aider all run BYO-key without piggybacking upstream. The projects that did spoof (e.g. acheong08/ChatGPT) saw bulk user bans.

Additional points covered by the research:

  • The upstream checkHeaders configuration is intentionally stored in deployment JSON rather than source, so even with the upstream-version injection on top of the original UA fix, there is no guarantee PawWork would actually clear the gate. The proposed change would risk the downside without proving the upside.
  • A friendly-fork path is open: switch the default away from Zen, ship a BYO-key onboarding flow (OpenRouter / Groq / Gemini free tiers), optionally embed a small Groq/Cerebras org-key for new-user trial messages with clear attribution, and reach out to anomalyco on Discord with a transparent partnership ask.

#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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working harness Model harness, prompts, tool descriptions, and session mechanics P1 High priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant