Skip to content

fix(opencode-zen): align free-tier v2 fingerprint with PR #4860 - #1

Merged
Yoganataa merged 5 commits into
devfrom
fix/opencode-zen-v2-pr4860
Oct 15, 2026
Merged

Yoganataa merged 5 commits into
devfrom
fix/opencode-zen-v2-pr4860

Conversation

@Yoganataa

@Yoganataa Yoganataa commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Ports the relevant changes from decolua/9router#4860 onto this fork's existing dev branch, which already contains the OpenCode Free v2 implementation from the decolua#4842 line.

Changes

  • Align OpenCodeZenExecutor with the OpenCode v2 User-Agent format, using this fork's current 2.0.29 version.
  • Default x-opencode-client to cli and add x-opencode-project, x-session-affinity, and x-session-id while mirroring the same session value across session headers.
  • Replace Zen's legacy bash/glob/grep/read stub tool declarations with shared canonical read/shell fingerprint handling for Chat Completions and Responses.
  • Add unit tests for Zen headers, canonical tool schemas, and name-conflict aliasing.

Scope

Keeps the existing Zen /zen/v1 endpoints unchanged; this patch ports the Zen executor changes from decolua#4860 and does not reapply the overlapping decolua#4806/decolua#4840/decolua#4842 patches.

Validation

Unit tests have been added. CI results should be checked before merging.

Summary by Sourcery

Align OpenCode Zen with the v2 free-tier client fingerprint while preserving existing Zen endpoints.

Bug Fixes:

  • Align OpenCode Zen free-tier requests with the v2 client fingerprint to prevent compatibility and access issues.

Enhancements:

  • Update Zen headers and session propagation for the v2 CLI format.
  • Use shared canonical read and shell tool fingerprints across Chat Completions and Responses requests, including conflict aliasing.

Build:

  • Bump the application and CLI versions to 0.5.103.

Tests:

  • Add unit coverage for Zen v2 headers, mirrored session identifiers, canonical tool schemas, and tool-name conflict handling.

@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Ports the OpenCode Free-tier v2 fingerprint changes into the Zen executor while retaining its existing endpoints, centralizing canonical tool handling, updating client/session headers, and adding focused unit tests for the new request shape.

Sequence diagram for OpenCode Zen request fingerprinting

sequenceDiagram
    participant Client
    participant ZenExecutor as OpenCodeZenExecutor
    participant Fingerprint as applyFingerprintTools
    participant ZenAPI as OpenCodeZenAPI

    Client->>ZenExecutor: prepareRequestHeaders()
    ZenExecutor->>ZenExecutor: resolve session
    ZenExecutor-->>Client: User-Agent, client, project, session headers
    Client->>ZenExecutor: transformRequestBody(body, model)
    ZenExecutor->>ZenExecutor: set stream=true
    alt Chat Completions
        ZenExecutor->>Fingerprint: applyFingerprintTools(body, false)
    else Responses
        ZenExecutor->>Fingerprint: applyFingerprintTools(body, true)
    end
    ZenExecutor->>ZenAPI: request with canonical fingerprint tools
Loading

File-Level Changes

Change Details Files
Updates Zen request fingerprinting to match OpenCode Free-tier v2 client behavior.
  • Uses the opencode/latest/2.0.29/cli User-Agent and requires v2-format User-Agents when forwarding downstream values.
  • Defaults the client and project headers to cli and global.
  • Generates or reuses one session value and mirrors it across all OpenCode and session-affinity headers.
  • Replaces local legacy tool stubs with shared canonical read/shell fingerprint injection for Chat Completions and Responses.
  • Preserves existing custom tools through conflict aliasing and applies the required stream and Responses normalization behavior.
open-sse/executors/opencode-zen.js
Adds unit coverage for the v2 headers, session propagation, canonical tools, and conflict handling.
  • Verifies fallback User-Agent, client/project defaults, and matching session headers.
  • Checks Chat Completions receive aliased/custom plus canonical tools and required read parameters.
  • Checks Responses receive canonical tools, stream behavior, and the generated instruction text.
tests/unit/opencode-session.test.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T10:19:38.192857Z 7158dd6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai sourcery-ai 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.

Sourcery assessment

Approved.

@sourcery-ai sourcery-ai 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.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="open-sse/executors/opencode-zen.js" line_range="233" />
<code_context>
     if (out && typeof out === "object") out.stream = true;
     if (!isResponsesModel(model || body?.model)) {
-      ensureChatFingerprintTools(out);
+      if (out && typeof out === "object") applyFingerprintTools(out, false);
       return out;
     }
</code_context>
<issue_to_address>
**Retries lose tool-name aliases**

When a Zen request with a conflicting caller `read` or `shell` tool receives a retryable response before succeeding, the first transform renames the caller’s conflicting `read` or `shell` tool and records the alias map. A retry transforms that already-mutated body, finds no conflict, and replaces the recorded map with an empty one; the final response then exposes the alias instead of the caller’s tool name.

Preserve the original alias map across retries, or make retries transform a fresh copy of the original request body.

Also at `open-sse/executors/opencode-zen.js:259`.
</issue_to_address>

### Comment 2
<location path="tests/unit/opencode-session.test.js" line_range="355" />
<code_context>
+    });
+    expect(chat.stream).toBe(true);
+    expect(chat.tools.map((t) => t.function?.name)).toEqual(["read_client", "read", "shell"]);
+    expect(chat.tools[1].function.parameters.required).toEqual(["path"]);
+    expect(chat.messages[0].role).toBe("system");
+
</code_context>
<issue_to_address>
**Broken tool schemas pass fingerprint test**

When an emitted `read` tool retains `required: ["path"]` but has an incorrect description or other schema fields, the Zen fingerprint test checks only that the appended `read` tool requires `path`; a wrong description or other incorrect schema fields still pass both endpoint checks, so the test reports broken fingerprints as verbatim.

Assert the complete expected canonical definitions for the appended `read` and `shell` tools on both endpoint paths.

Also at `tests/unit/opencode-session.test.js:365`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and requests that lack a qualifying downstream user-agent now claim the v2 CLI identity and send additional session-affinity headers, which could change how the upstream free-tier gate grants access or routes sessions. Reverting stops future requests from using that fingerprint, but cannot undo upstream requests already accepted or routed under it.

Blocking findings: open-sse/executors/opencode-zen.js:233, tests/unit/opencode-session.test.js:355


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

if (out && typeof out === "object") out.stream = true;
if (!isResponsesModel(model || body?.model)) {
ensureChatFingerprintTools(out);
if (out && typeof out === "object") applyFingerprintTools(out, false);

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 · Retries lose tool-name aliases

When a Zen request with a conflicting caller read or shell tool receives a retryable response before succeeding, the first transform renames the caller’s conflicting read or shell tool and records the alias map. A retry transforms that already-mutated body, finds no conflict, and replaces the recorded map with an empty one; the final response then exposes the alias instead of the caller’s tool name.

Preserve the original alias map across retries, or make retries transform a fresh copy of the original request body.

Also at open-sse/executors/opencode-zen.js:259.

Prompt for AI agents
In `open-sse/executors/opencode-zen.js` at line 233:

**Retries lose tool-name aliases**

When a Zen request with a conflicting caller `read` or `shell` tool receives a retryable response before succeeding, the first transform renames the caller’s conflicting `read` or `shell` tool and records the alias map. A retry transforms that already-mutated body, finds no conflict, and replaces the recorded map with an empty one; the final response then exposes the alias instead of the caller’s tool name.

Preserve the original alias map across retries, or make retries transform a fresh copy of the original request body.

Also at `open-sse/executors/opencode-zen.js:259`.

});
expect(chat.stream).toBe(true);
expect(chat.tools.map((t) => t.function?.name)).toEqual(["read_client", "read", "shell"]);
expect(chat.tools[1].function.parameters.required).toEqual(["path"]);

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 · Broken tool schemas pass fingerprint test

When an emitted read tool retains required: ["path"] but has an incorrect description or other schema fields, the Zen fingerprint test checks only that the appended read tool requires path; a wrong description or other incorrect schema fields still pass both endpoint checks, so the test reports broken fingerprints as verbatim.

Assert the complete expected canonical definitions for the appended read and shell tools on both endpoint paths.

Also at tests/unit/opencode-session.test.js:365.

Prompt for AI agents
In `tests/unit/opencode-session.test.js` at line 355:

**Broken tool schemas pass fingerprint test**

When an emitted `read` tool retains `required: ["path"]` but has an incorrect description or other schema fields, the Zen fingerprint test checks only that the appended `read` tool requires `path`; a wrong description or other incorrect schema fields still pass both endpoint checks, so the test reports broken fingerprints as verbatim.

Assert the complete expected canonical definitions for the appended `read` and `shell` tools on both endpoint paths.

Also at `tests/unit/opencode-session.test.js:365`.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7158dd61fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (out && typeof out === "object") out.stream = true;
if (!isResponsesModel(model || body?.model)) {
ensureChatFingerprintTools(out);
if (out && typeof out === "object") applyFingerprintTools(out, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retain alias mapping when the base transform clones the request

When a Zen chat request targets a Kimi or DeepSeek model and declares a caller tool named read or shell, super.transformRequest runs the reasoning injector and returns a new body object. This call records the fingerprint alias map only on out, but chatCore later retrieves it exclusively from the original translatedBody; consequently an upstream read_client/shell_client call is returned to the client without restoration to its declared tool name, preventing the client from dispatching the tool or submitting a matching follow-up result. Record the map on the original request as well, or have the handler read it from the transformed body.

Useful? React with 👍 / 👎.

@Yoganataa
Yoganataa merged commit d77edb7 into dev Oct 15, 2026
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.

1 participant