Skip to content

fix(opencode): serve the gated free tier from /inference/v1 and give placeholder tools non-empty schemas - #16826

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.52from
velizarofff:opencode-free-tier-inference
Oct 16, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.52from
velizarofff:opencode-free-tier-inference

Conversation

@velizarofff

Copy link
Copy Markdown
Contributor

Since 2026-10-14, standalone requests to OpenCode free-tier models (the ones isGatedFreeTierRequest gates) fail with 403 FreeTierError (#16328), while the official OpenCode CLI keeps working.

Two things changed upstream at once. Both were measured by capturing the official CLI 1.19.0 request and replaying it with exactly one element changed at a time (2026-10-15, anonymous lane, 5–10 s between requests).

1. The free tier moved off /zen/v1

The client now takes its base URL from the models.dev catalog (https://opencode.ai/inference/v1). The captured request on /inference/v1/chat/completions answers 200; byte-for-byte the same request on /zen/v1/chat/completions answers 403 FreeTierError.

2. The tools check now inspects parameter schemas

Replay (single change vs the captured baseline) Result
captured CLI request unchanged 200
same request on /zen/v1 403
no tools 403
only read with the client's real schema 200
only grep; only bash,edit,glob; any subset without read 403
read with any non-empty properties (filePath, file_path, x), with or without required 200
empty parameters on every tool 403
read + bash with properties: {} 403
read + bash{command} 200
read + webfetch{} / read + noop_tool{} 200
only foo with a non-empty schema 403
empty descriptions on all tools 200
one-line system prompt 200
router-like headers (UA 1.18.31, client: desktop, no session id) 200

So: a property-bearing read is required, an empty bash breaks the request, and empty schemas on names outside the quartet still pass. Descriptions, system prompt and headers do not matter.

What OmniRoute does today (release/v3.8.52)

buildUrl resolves this.config.baseUrl for every request format — the zen registry base — and the request contract injects placeholder tools (applyFreeTierRequestContract, mergeClientToolsWithObserved) with parameters: { type: "object", properties: {} }. The surface machinery added in 3.8.52 (surfaceFromBaseUrl) already tells the gated zen surface apart, but nothing uses it to change the base URL, so a standalone request violates both conditions of the new gate.

Fix

  • add FREE_TIER_INFERENCE_BASE_URL (https://opencode.ai/inference/v1) to opencodeFreeTierContract.ts, next to the surface constants;
  • buildUrl sends a gated free-tier request (zen surface, non-premium model) to that base for every request format. Paid models and opencode-go keep the registry base, which still serves them;
  • add placeholderToolParameters(name): injected quartet members get minimal schemas mirroring the client's argument names — bash{command}, glob{pattern}, grep{pattern}, read{filePath}, each with required — while names outside the quartet keep the empty schema. Used by both applyFreeTierRequestContract and mergeClientToolsWithObserved.

With an operator configuration like OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS=bash,glob,grep,read, a standalone request carrying no tools of its own now satisfies the tools half of the 2026-10-14 gate. The session half is still open: measured 2026-10-16, the same request with a session id in the pre-#16727 shape (12 arbitrary hex digits) is refused 403 on /inference/v1 as well, so a standalone client that relies on OmniRoute's synthesized x-opencode-session still needs the encoding half of #16727. A client that supplies its own valid session id (the official CLI does) is unaffected. I have that half ready on a second branch of the same fork and will keep it out of this PR unless maintainers want both here.

The same two changes (endpoint + non-empty placeholder schemas) are what unblocked a live self-hosted instance on 2026-10-15, in a build that also carries #15322; this PR is the minimal subset against the current default branch.

Out of scope

Tests

  • executor: free models now assert /inference/v1/...; paid (gpt-5-nano), unknown models and the Go tier keep their bases;
  • request contract: injected quartet members assert the minimal schemas; new unit tests for placeholderToolParameters and for an appended quartet member in mergeClientToolsWithObserved;
  • full tests/unit/opencode-*.test.ts suite (92 files, node 26) against release/v3.8.52 + this patch: 855 tests, 0 failures. The four baseline failures of v3.8.51 (accounts-per-request, geo-block-rotation, responses-first-byte-stall, transient-retry-delay) no longer reproduce on clean v3.8.52.

Addresses the endpoint and placeholder-schema halves of #16328.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants