Skip to content

fix(ai): satisfy OpenCode Zen free-tier gate (403 FreeTierError) (#12306) - #12556

Open
palprateek wants to merge 25 commits into
can1357:mainfrom
palprateek:fix/opencode-free-tier-headers
Open

palprateek wants to merge 25 commits into
can1357:mainfrom
palprateek:fix/opencode-free-tier-headers

Conversation

@palprateek

@palprateek palprateek commented Sep 19, 2026 •

Copy link
Copy Markdown

What

I asked for the complete header fix — the canonical opencode/<version> User-Agent plus a stable ses_-shaped session token — implemented on a branch in this repo with tests, and after reviewing the diff extended it to the body-gate (tool-name) half and the 403 error scoping.

OpenCode's Zen/Go gateway refuses requests that don't carry OpenCode's own client
identity. All four gate conditions were verified against the live gateway:

  1. User-Agent leading token opencode/<version> — omp now sends opencode/1.18.31
    (explicit caller overrides like the Claude OAuth fingerprint still win)
  2. x-opencode-session matching ses_<12 hex><14 alnum> — now a deterministic token
    derived from the conversation id, so routing and prompt-cache pinning survive
    across turns; background traffic (usage polls, discovery) derives from the install id
  3. stream: true — omp's agent turns already stream
  4. The bash + read tool names in tools[] (any-two-of-five does not
    suffice — the default advisor roster read/grep/glob without bash
    is still refused) — stream() (single funnel behind
    stream/complete/streamSimple) pads calls missing either name (advisors,
    one-shot helpers) with stub entries for the missing pair member; stubs are wire-inert
    and never enter the tool registry

Also scoped the error: a 403 FreeTierError denial is model-scoped client policy, not
a credential problem — it no longer rotates sibling keys or invalidates the stored
credential, and the surfaced message explains the restriction instead of reading like
an auth failure. Shared contract lives in @oh-my-pi/pi-catalog/wire/opencode.

Why

fixes #12306 — community bisection there established all four conditions; two open PRs
(#12325, #12326) each implemented only conditions 1–2, which the bisection shows is
insufficient (tool-less auxiliary calls keep 403ing).

Testing

  • bun check clean; pi-ai 4863 pass, pi-catalog 1027/1027 (the 12 pre-existing
    pi-ai env failures reproduce on clean main)

  • Live gateway A/B (byte-identical bodies, only identity headers varied):

    Probe Identity Result
    free model old omp (omp/18.2.6 + raw UUID session) 403 FreeTierError
    free model this branch (opencode/1.18.31 + ses_ token) 429 FreeUsageLimitError (gate passed; quota limiter answered)
    isolation omp UA + correctly-shaped ses_ session 403 FreeTierError (UA independently required)
    paid SKU both identities 402 Insufficient funds (gate only guards free SKUs)

    A 429 is the pass condition: the quota limiter only evaluates after the identity
    gate accepts. Wire capture confirmed padding: tool-less requests left the client as
    bash,read.


  • bun check passes
  • Tested locally
  • CHANGELOG updated with the required attribution (if user-facing; internal issue fixes use issue links, external contributions add the PR link and contributor credit after creation)

palprateek and others added 4 commits September 20, 2026 01:55
…1357#12306)

OpenCode Zen/Go gate the contributor free tier on client identity: a
User-Agent whose leading token is opencode/<version> and an
x-opencode-session matching ses_<12 hex><14 alnum>. omp's omp/* UA and
raw session/install ids failed both checks, producing 403 FreeTierError
on every free-tier model.

applyInferenceHeaders now sends the canonical OpenCode UA and a stable
ses_ token derived from the conversation session id (routing and prompt
cache stay pinned across turns); conversation-less background traffic
(usage polls, model discovery) attributes through the same formatter
with the install id. The shared formatter lives in
packages/catalog/src/wire/opencode.ts so catalog discovery and pi-ai
send identical identity. Explicit caller UA overrides (Claude OAuth
fingerprint) still win.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The can1357#12306 gate is body-shaped as well as header-shaped: it requires
stream:true and at least five OpenCode core tool names (bash, edit,
glob, grep, read) in tools[], so tool-less auxiliary calls (advisors,
one-shot helpers) were still refused with 403 FreeTierError after the
client-identity fix. stream() now pads such requests with stub entries
for the missing names via withOpenCodeGateTools — empty description,
inert {type:"object"} schema, strict:false — wire-shape filler that
never joins the caller's tool registry. Agent rosters that already
carry the names pass through untouched (same array identity).

When a denial does surface, isOpencodeFreeTierGateMessage classifies
it as model-scoped client policy rather than a credential fault: the
key keeps serving paid SKUs, so the 403 no longer rotates sibling
credentials (isAuthRetryableError and stream()'s rotatable-403 arm) or
reads as an auth failure, and formatMessage rewrites the raw 403 into
an explanation that preserves the classifier markers. google-shared
now passes provider into finalize() so the zen google-transport lane
gets the same treatment.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
… rotation

isDirectCredentialRotationError rotated sibling keys on any 403, but the
OpenCode free-tier gate denial is model-scoped client policy: sibling keys
fail identically and the credential keeps serving paid SKUs. Extends the
same exclusion isAuthRetryableError already applies.

Live-gateway verified: old identity 403s with FreeTierError; branch
identity passes the gate (rate limiter answers instead).

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@ForeverInLaw

Copy link
Copy Markdown

≥5 OpenCode core tool names in tools[]

Only two are needed: bash and read

#12306 (comment)

@palprateek

Copy link
Copy Markdown
Author

Addressed in 668b66c: the gate check was weakened from all five tool names to any two. withOpenCodeGateTools now pads tool-less calls with exactly bash + read and leaves any roster already carrying two of the five (bash, edit, glob, grep, read) untouched. Gate tests updated to the two-name threshold; bun check clean, opencode suites green (98 pass).

@roboomp roboomp added auth Authentication and login flows fix provider:opencode Provider-specific issues for opencode providers LLM provider-specific issues review:p1 triaged labels Sep 22, 2026

@roboomp roboomp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @palprateek — thorough, well-scoped fix for the OpenCode free-tier 403 with live A/B evidence, changelog entries in both touched packages, docs, and contract-level tests. Rank: P1 — the four-part fix (canonical opencode/* UA, deterministic ses_ session token via toOpenCodeSessionToken, tool-name body padding, non-rotating/model-scoped error classification) is correct and conventional; one behavioral tradeoff deserves a maintainer decision before merge.

Findings:

  • stream.ts (should-fix / question): withOpenCodeGateTools advertises real tool names (bash/edit/glob/grep/read) with empty descriptions on every OpenCode request, including tool-less auxiliary calls. Nothing sets toolChoice, so the model can emit a phantom tool call on an advisor/one-shot turn whose caller expects text and has no exec handler — yielding empty .text. Net still positive vs a hard 403, but worth confirming toolChoice: "none" (or equivalent) was considered, plus a test that auxiliary completions still return text.
  • google-shared.ts (nit): the added provider arg to finalize is unrelated to #12306 (Google never matches the opencode/copilot/cline/ollama rewrites); harmless, just widens the diff.

Correctness spot-checks passed: applyInferenceHeaders preserves prior behavior for non-OpenCode/no-session paths; toOpenCodeSessionToken output satisfies OPENCODE_SESSION_TOKEN_PATTERN (26-char shape); Claude OAuth fingerprint still wins via setHeaderIfAbsent; padding funnels through streamDispatch for stream/complete/streamSimple. Minor doc drift: the stream.ts comment says the gate "requires at least five" tool names while the PR body says the gate now needs only two — padding all five is a safe superset, so no action needed.

Not anchored to diff

  • packages/ai/src/stream.ts:958 — should-fix / maintainer question: withOpenCodeGateTools advertises real tool names (bash, edit, glob, grep, read) with empty descriptions on every OpenCode request, including tool-less auxiliary calls (advisors, one-shot helpers via streamSimple/completeSimple). The stubs are wire-inert for the gate, but they are still valid tool declarations the model sees — nothing here sets toolChoice, so the model can emit a bash/read tool call on an auxiliary turn whose caller has no exec handler and expects text. The result is an AssistantMessage with a tool call and empty .text, i.e. a silently broken advisor/summary for OpenCode free models. Before padding, those calls hard-403; after, they mostly work but carry this new failure mode. Was forcing toolChoice: "none" (or otherwise suppressing invocation) considered for the padded case? A test that a tool-less auxiliary completion still yields text (not a phantom tool call) would lock this down.

Comment thread packages/ai/src/providers/google-shared.ts
Addresses roboomp review on can1357#12556: stub gate tools must not become
callable work on auxiliary turns that expect text.
@palprateek

Copy link
Copy Markdown
Author

Addressed the roboomp review in 8711894: padded tool-less OpenCode calls now pin toolChoice to none (explicit caller choices still win; rosters carrying real tools are untouched so agent turns keep full tool use). The gate reads tools[], not tool_choice, so the pin is gate-neutral. New tests assert the wire body (bash,read + tool_choice:none) and that the auxiliary turn still returns text. bun check clean; branch re-merged onto latest main and MERGEABLE.

@palprateek

Copy link
Copy Markdown
Author

@can1357 — ready for your gates: branch MERGEABLE at 6323af0, 0 unresolved threads, roboomp's findings addressed (toolChoice:none pin + google-shared rationale). Needs you: (1) approve the pending fork workflow runs (CI 35737735017, OMP Nix 35737735116 — action_required), (2) review approval. bun check clean; targeted suites green.

@palprateek

Copy link
Copy Markdown
Author

@roboomp please re-review the latest head — your stream.ts toolChoice question is fixed (tool_choice:none pin in 8711894, plus wire tests) and the google-shared nit is answered in its thread. Latest head: 6323af0.

@palprateek

Copy link
Copy Markdown
Author

Rebased onto latest main (merge cf6fce4) — GitHub now reports MERGEABLE, 0 unresolved threads. bun check clean; gate suites green (pi-ai 75 pass across the 4 OpenCode/auth suites, pi-catalog opencode-provider 26 pass).

Still needs a maintainer: (1) approve the fork workflow runs (action_required), (2) review approval — roboomp's stream.ts toolChoice question was fixed in 8711894 with wire tests. @can1357

Resolve inference-headers.ts: keep Vercel gateway attribution from main
alongside the OpenCode free-tier gate identity (canonical UA + ses_
session token, background-traffic fallback).
@palprateek

Copy link
Copy Markdown
Author

Rebased onto latest main (merge 120995f) — GitHub now reports MERGEABLE, 0 unresolved threads. bun run check:ts clean; gate suites green (101 pass across the 5 OpenCode/auth suites, plus Vercel attribution 7 pass).

Still needs a maintainer: (1) approve the fork workflow runs (36322064606 CI, 36322064604 OMP Nix — action_required), (2) review approval — roboomp's stream.ts toolChoice question was fixed in 8711894 with wire tests. @can1357

Keep the strengthened usage-attribution test over main's deletion: aa5aab0
dropped the old existence-only assertions per the testing policy, but this
branch's version asserts the exact gateway wire shape (canonical
OPENCODE_USER_AGENT + OPENCODE_SESSION_TOKEN_PATTERN match) the free-tier
gate enforces, so it defends an external contract rather than echoing
internals.
@palprateek

Copy link
Copy Markdown
Author

Re-merged onto latest main (3b9375b) — MERGEABLE again, 0 unresolved threads. One resolution note: kept the strengthened usage-attribution test over aa5aab0's deletion — it now asserts the exact gateway wire shape the gate enforces, not the old existence checks. Suites green (102 pass), check:ts clean.

Still needs a maintainer: (1) approve fork runs (36546257086 CI, 36546257057 OMP Nix — action_required), (2) review approval. @can1357

NoelJacob added a commit to NoelJacob/oh-my-pi that referenced this pull request Oct 3, 2026
commit 3b9375b
Merge: 120995f d1932a6
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 29 14:29:36 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    Keep the strengthened usage-attribution test over main's deletion: aa5aab0
    dropped the old existence-only assertions per the testing policy, but this
    branch's version asserts the exact gateway wire shape (canonical
    OPENCODE_USER_AGENT + OPENCODE_SESSION_TOKEN_PATTERN match) the free-tier
    gate enforces, so it defends an external contract rather than echoing
    internals.

commit 120995f
Merge: cf6fce4 7695ddb
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 27 18:50:20 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    Resolve inference-headers.ts: keep Vercel gateway attribution from main
    alongside the OpenCode free-tier gate identity (canonical UA + ses_
    session token, background-traffic fallback).

commit cf6fce4
Merge: 6323af0 7ae76f8
Author: Prateek <palprateek2004@gmail.com>
Date:   Wed Sep 23 13:17:44 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 6323af0
Merge: 8711894 0f9139f
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 22 19:34:04 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 8711894
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 22 19:31:27 2026 +0530

    fix(ai): pin tool_choice none on padded tool-less OpenCode calls

    Addresses roboomp review on can1357#12556: stub gate tools must not become
    callable work on auxiliary turns that expect text.

commit b45304b
Merge: 668b66c 5e0fc86
Author: Prateek <palprateek2004@gmail.com>
Date:   Mon Sep 21 23:14:55 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 668b66c
Author: Prateek <palprateek2004@gmail.com>
Date:   Mon Sep 21 23:03:18 2026 +0530

    fix(ai): pad OpenCode gate to two tool names (bash+read)

commit 8f36df2
Merge: 2bcaf7b b0651dc
Author: Prateek Pal <101188666+palprateek@users.noreply.github.com>
Date:   Sun Sep 20 23:27:03 2026 +0530

    Merge branch 'can1357:main' into fix/opencode-free-tier-headers

commit 2bcaf7b
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 03:03:59 2026 +0530

    docs: add PR attribution to OpenCode gate changelog entries

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit 93ad4b5
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 02:56:49 2026 +0530

    fix(ai): exempt OpenCode free-tier gate denial from direct credential rotation

    isDirectCredentialRotationError rotated sibling keys on any 403, but the
    OpenCode free-tier gate denial is model-scoped client policy: sibling keys
    fail identically and the credential keeps serving paid SKUs. Extends the
    same exclusion isAuthRetryableError already applies.

    Live-gateway verified: old identity 403s with FreeTierError; branch
    identity passes the gate (rate limiter answers instead).

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit 713e154
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 02:37:49 2026 +0530

    fix(ai): satisfy OpenCode free-tier body gate and scope its 403 denial

    The can1357#12306 gate is body-shaped as well as header-shaped: it requires
    stream:true and at least five OpenCode core tool names (bash, edit,
    glob, grep, read) in tools[], so tool-less auxiliary calls (advisors,
    one-shot helpers) were still refused with 403 FreeTierError after the
    client-identity fix. stream() now pads such requests with stub entries
    for the missing names via withOpenCodeGateTools — empty description,
    inert {type:"object"} schema, strict:false — wire-shape filler that
    never joins the caller's tool registry. Agent rosters that already
    carry the names pass through untouched (same array identity).

    When a denial does surface, isOpencodeFreeTierGateMessage classifies
    it as model-scoped client policy rather than a credential fault: the
    key keeps serving paid SKUs, so the 403 no longer rotates sibling
    credentials (isAuthRetryableError and stream()'s rotatable-403 arm) or
    reads as an auth failure, and formatMessage rewrites the raw 403 into
    an explanation that preserves the classifier markers. google-shared
    now passes provider into finalize() so the zen google-transport lane
    gets the same treatment.

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit b4142c9
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 01:55:11 2026 +0530

    fix(ai): send OpenCode client identity to satisfy free-tier gate (can1357#12306)

    OpenCode Zen/Go gate the contributor free tier on client identity: a
    User-Agent whose leading token is opencode/<version> and an
    x-opencode-session matching ses_<12 hex><14 alnum>. omp's omp/* UA and
    raw session/install ids failed both checks, producing 403 FreeTierError
    on every free-tier model.

    applyInferenceHeaders now sends the canonical OpenCode UA and a stable
    ses_ token derived from the conversation session id (routing and prompt
    cache stay pinned across turns); conversation-less background traffic
    (usage polls, model discovery) attributes through the same formatter
    with the install id. The shared formatter lives in
    packages/catalog/src/wire/opencode.ts so catalog discovery and pi-ai
    send identical identity. Explicit caller UA overrides (Claude OAuth
    fingerprint) still win.

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>
Docs: keep this branch's free-tier gating bullet; accept main's KDL-driven
consolidation of the pre-existing whenThinking/big-pickle notes.
@palprateek

Copy link
Copy Markdown
Author

Re-merged onto latest main (b53c11c) — MERGEABLE, 0 unresolved threads. Docs resolution: kept this PR's free-tier gating bullet; accepted main's consolidation of the pre-existing whenThinking/big-pickle notes. Suites green (102 pass).

Still needs a maintainer: (1) approve fork runs (37117180238 CI, 37117180217 OMP Nix — action_required), (2) review approval. @can1357

streamDispatch: layer the OpenCode gate-tool padding on top of main's
withSupportedSamplingParams wrapper.
@palprateek

Copy link
Copy Markdown
Author

Re-merged onto latest main (9667937) — MERGEABLE, 0 unresolved threads. Resolution: gate-tool padding now layers on top of main's withSupportedSamplingParams wrapper in streamDispatch. check:ts clean, suites green (102 pass).

Still needs a maintainer: (1) approve fork runs (37123450011 CI, 37123450010 OMP Nix — action_required), (2) review approval. @can1357

NoelJacob added a commit to NoelJacob/oh-my-pi that referenced this pull request Oct 4, 2026
commit 9667937
Merge: b53c11c d0cc523
Author: Prateek <palprateek2004@gmail.com>
Date:   Sat Oct 3 18:06:56 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    streamDispatch: layer the OpenCode gate-tool padding on top of main's
    withSupportedSamplingParams wrapper.

commit b53c11c
Merge: 3b9375b 9348320
Author: Prateek <palprateek2004@gmail.com>
Date:   Sat Oct 3 16:10:00 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    Docs: keep this branch's free-tier gating bullet; accept main's KDL-driven
    consolidation of the pre-existing whenThinking/big-pickle notes.

commit 3b9375b
Merge: 120995f d1932a6
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 29 14:29:36 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    Keep the strengthened usage-attribution test over main's deletion: aa5aab0
    dropped the old existence-only assertions per the testing policy, but this
    branch's version asserts the exact gateway wire shape (canonical
    OPENCODE_USER_AGENT + OPENCODE_SESSION_TOKEN_PATTERN match) the free-tier
    gate enforces, so it defends an external contract rather than echoing
    internals.

commit 120995f
Merge: cf6fce4 7695ddb
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 27 18:50:20 2026 +0530

    Merge origin/main into fix/opencode-free-tier-headers

    Resolve inference-headers.ts: keep Vercel gateway attribution from main
    alongside the OpenCode free-tier gate identity (canonical UA + ses_
    session token, background-traffic fallback).

commit cf6fce4
Merge: 6323af0 7ae76f8
Author: Prateek <palprateek2004@gmail.com>
Date:   Wed Sep 23 13:17:44 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 6323af0
Merge: 8711894 0f9139f
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 22 19:34:04 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 8711894
Author: Prateek <palprateek2004@gmail.com>
Date:   Tue Sep 22 19:31:27 2026 +0530

    fix(ai): pin tool_choice none on padded tool-less OpenCode calls

    Addresses roboomp review on can1357#12556: stub gate tools must not become
    callable work on auxiliary turns that expect text.

commit b45304b
Merge: 668b66c 5e0fc86
Author: Prateek <palprateek2004@gmail.com>
Date:   Mon Sep 21 23:14:55 2026 +0530

    Merge remote-tracking branch 'origin/main' into fix/opencode-free-tier-headers

commit 668b66c
Author: Prateek <palprateek2004@gmail.com>
Date:   Mon Sep 21 23:03:18 2026 +0530

    fix(ai): pad OpenCode gate to two tool names (bash+read)

commit 8f36df2
Merge: 2bcaf7b b0651dc
Author: Prateek Pal <101188666+palprateek@users.noreply.github.com>
Date:   Sun Sep 20 23:27:03 2026 +0530

    Merge branch 'can1357:main' into fix/opencode-free-tier-headers

commit 2bcaf7b
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 03:03:59 2026 +0530

    docs: add PR attribution to OpenCode gate changelog entries

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit 93ad4b5
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 02:56:49 2026 +0530

    fix(ai): exempt OpenCode free-tier gate denial from direct credential rotation

    isDirectCredentialRotationError rotated sibling keys on any 403, but the
    OpenCode free-tier gate denial is model-scoped client policy: sibling keys
    fail identically and the credential keeps serving paid SKUs. Extends the
    same exclusion isAuthRetryableError already applies.

    Live-gateway verified: old identity 403s with FreeTierError; branch
    identity passes the gate (rate limiter answers instead).

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit 713e154
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 02:37:49 2026 +0530

    fix(ai): satisfy OpenCode free-tier body gate and scope its 403 denial

    The can1357#12306 gate is body-shaped as well as header-shaped: it requires
    stream:true and at least five OpenCode core tool names (bash, edit,
    glob, grep, read) in tools[], so tool-less auxiliary calls (advisors,
    one-shot helpers) were still refused with 403 FreeTierError after the
    client-identity fix. stream() now pads such requests with stub entries
    for the missing names via withOpenCodeGateTools — empty description,
    inert {type:"object"} schema, strict:false — wire-shape filler that
    never joins the caller's tool registry. Agent rosters that already
    carry the names pass through untouched (same array identity).

    When a denial does surface, isOpencodeFreeTierGateMessage classifies
    it as model-scoped client policy rather than a credential fault: the
    key keeps serving paid SKUs, so the 403 no longer rotates sibling
    credentials (isAuthRetryableError and stream()'s rotatable-403 arm) or
    reads as an auth failure, and formatMessage rewrites the raw 403 into
    an explanation that preserves the classifier markers. google-shared
    now passes provider into finalize() so the zen google-transport lane
    gets the same treatment.

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>

commit b4142c9
Author: Prateek <palprateek2004@gmail.com>
Date:   Sun Sep 20 01:55:11 2026 +0530

    fix(ai): send OpenCode client identity to satisfy free-tier gate (can1357#12306)

    OpenCode Zen/Go gate the contributor free tier on client identity: a
    User-Agent whose leading token is opencode/<version> and an
    x-opencode-session matching ses_<12 hex><14 alnum>. omp's omp/* UA and
    raw session/install ids failed both checks, producing 403 FreeTierError
    on every free-tier model.

    applyInferenceHeaders now sends the canonical OpenCode UA and a stable
    ses_ token derived from the conversation session id (routing and prompt
    cache stay pinned across turns); conversation-less background traffic
    (usage polls, model discovery) attributes through the same formatter
    with the install id. The shared formatter lives in
    packages/catalog/src/wire/opencode.ts so catalog discovery and pi-ai
    send identical identity. Explicit caller UA overrides (Claude OAuth
    fingerprint) still win.

    🤖 Generated with Codebuff
    Co-Authored-By: Codebuff <noreply@codebuff.com>
stream.ts: keep gate padding; adopt main's in-flight limit plumbing and
drop the now-unused $pickenv/providerEntries imports.
@palprateek

Copy link
Copy Markdown
Author

Re-merged onto latest main (fcdfa63) — MERGEABLE, 0 unresolved threads. Resolution: adopted main's in-flight-limit plumbing and import cleanup in stream.ts, gate padding intact. check:ts clean, suites green (102 pass).

Still needs a maintainer: (1) approve fork runs (37628087751 CI, 37628087677 OMP Nix — action_required), (2) review approval. @can1357

@NoelJacob

NoelJacob commented Oct 11, 2026 •

Copy link
Copy Markdown

Warning: advisor: Advisor unavailable for opencode-zen/big-pickle: OpenCode rejected this request
with 403 FreeTierError: its free tier can only be used from within OpenCode. Your API key is valid —
paid models on the same key keep working. Pick a paid model with /model; if this model is expected
to work in omp, the gateway's gate has likely changed and the regression should be reported.

It seems the endpoint mandatorily needs read and bash tool.

The adviser does not work with the latest commit and this pull request merged on top

… names

The default advisor roster (advise/read/grep/glob) carries three of the
five known core names but no bash, and the gateway still answers 403 —
the body gate mandatorily requires bash+read. Pad whichever of the pair
is missing instead of counting any-two-of-five.
@palprateek

Copy link
Copy Markdown
Author

@NoelJacob — good catch, confirmed and fixed in d4938e2: the padding only fired below two-of-five, but your advisor roster carries read/grep/glob with no bash, so nothing was padded and the gate still refused it. The rule is now the specific bash+read pair (matching your finding): the advisor gets a bash stub with no tool_choice pin since it owns real tools. Regression test included. Could you re-test the advisor on this head?

@palprateek

Copy link
Copy Markdown
Author

Re-merged onto latest main (13c7e36) — MERGEABLE, 0 unresolved threads, clean auto-merge. Suites green (111 pass), check:ts clean. Includes the bash+read pair fix for the advisor 403.

Still needs a maintainer: (1) approve fork runs (38123553676 CI, 38123553639 OMP Nix — action_required), (2) review approval. @can1357

@NoelJacob

Copy link
Copy Markdown

Port the withOpenCodeGateShape helper (contributed by NoelJacob): pad
missing bash/read in streamSimpleRequest as well as streamDispatch, and
keep the tool_choice:none pin off openai-responses, which 400s on
anything but auto.
@palprateek

Copy link
Copy Markdown
Author

Reviewed and ported in 6a92d10 — thank you, all three parts were valuable: (1) your October partial-combo bisection is now cited in the wire comment as the evidence for the pair rule, (2) withOpenCodeGateShape is now shared by both funnels (plus your funnel-coverage tests), (3) the none pin stays off openai-responses. Credited you in the changelog entry. Could you verify the advisor works on this head and confirm the Responses-400 concern from your testing? @NoelJacob

@NoelJacob

Copy link
Copy Markdown

Yes it works

@palprateek

Copy link
Copy Markdown
Author

@roboomp @can1357 — requesting re-review on the latest head (6f3276c, re-merged onto main; mergeability recomputing). Since your last look: (1) the gate rule is now the specific bash+read pair, not any-two-of-five — NoelJacob's advisor 403 proved a read/grep/glob roster without bash still 403s; (2) the padding logic is now the shared withOpenCodeGateShape helper applied in both dispatch funnels, with the tool_choice:none pin kept off openai-responses (NoelJacob collab, credited in changelog). Suites green (113 pass), check:ts clean, 0 unresolved threads. (Note: I can't re-request via the API — no permission — so this comment is the request.)

@palprateek
palprateek requested a review from roboomp October 11, 2026 17:21
@VeilSilence

Copy link
Copy Markdown

Despite all of this, today I hit issue again. Trying to figure what/why cause this.

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

Labels

auth Authentication and login flows fix provider:opencode Provider-specific issues for opencode providers LLM provider-specific issues review:p1 triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

opencode-zen: muse-spark-1.3-contributor-free returns 403 FreeTierError in OMP while working in OpenCode

5 participants