Repository navigation
fix(opencode): normalize colliding fingerprint tool definitions in place - #4972
mostafa-binesh wants to merge 2 commits into
Conversation
OpenCode composes its `shell` tool description from `process.platform`, so a Windows or macOS client never matches the captured Linux definition. Every such request was treated as a conflicting caller tool: `shell` was renamed to `shell_client`, a placeholder `shell` was appended, and a system note told the model to avoid it. Besides leaking an internal rewrite into the prompt, the alias name is derived from the caller's tool names, so a client that already declares `shell_client` (a plugin, a harness shim) pushed the alias to `shell_client2`/3, and a call to the advertised name could reach the client as a tool it does not have. Adopt OpenCode's definition in place whenever the caller's parameters can carry it — the platform-wording case, and the common one. Genuinely different schemas keep the alias + restore path unchanged. - `read` on every platform, and `shell` on Linux, stay byte-identical and are still passed straight through. - Adds regression tests for platform drift, the plugin alias collision, and the schema-incompatible fallback.
Cherry-picked from decolua#4972. Co-Authored-By: [CL] <noreply@anthropic.com>
…t normalization, 403 direct replay - internal/proxy/opencode.go: TranslateOpenCodeSessionID now generates fresh timestamped session IDs instead of hash-derived ones. The free-tier gate rejects non-recent timestamps with 403 FreeTierError. - internal/translator/opencode_v2_fingerprint.go: port decolua/9router#4972 - normalize colliding fingerprint tool definitions in-place when arguments are compatible, instead of always aliasing to *_client. - internal/proxy/proxy.go: port decolua/9router#4988 - replay proxied 403 FreeTierError once on direct egress (unless strictProxy). - internal/providers/capabilities.go: context window 1M for muse-spark and nemotron free models.
… strategy in full Problem: the fork ran an older fingerprint contract than upstream frontier on the keyed lane and lacked both proxy escape hatches from issue decolua#4983. opencode-zen still sent the v1 UA plus a stub bash/glob/grep/read quartet with empty schemas and the v1-only x-opencode-request header, the fingerprint helper could not satisfy the gate when a caller tool reuses read/shell with platform-specific wording, and proxyAwareFetch had no way out of an egress the gate had flagged. Changes: port upstream PR decolua#4972 (in-place fingerprint normalization with alias fallback), decolua#4883 (UA opencode/latest/2.0.29/cli across both executors, registry and dashboard test; strict v2 channel matcher kept), decolua#4860 fingerprint/strategy for opencode-zen (verbatim read/shell via applyFingerprintTools, cli client, four session mirrors, drop x-opencode-request and x-api-key for free tier; /zen/v1 to /inference/v1 switch NOT taken), decolua#4987 (open-sse/utils/proxyDispatcher.js with opt-in socks5 dns=local, shared by proxyFetch and the dashboard proxy test, fail-closed ERR_PROXY_DNS_MODE) and decolua#4988 (replay a proxied 403 FreeTierError once on the direct egress, strictProxy contract preserved). undici ^7.19.2 to ^7.30.1. Version 0.5.108. Verification: full suite re-derived on this machine from a git stash -u baseline - failure sets byte-identical (95 = 95, 0 new), passes 3592 to 3617. Targeted opencode/proxy/session groups 214 pass. undici 7.30.1 exposes Socks5ProxyAgent and createSocks5Connection; the local-DNS dispatcher is exercised end to end against a local SOCKS5 plus TLS fixture (11 cases). Root cause of the free-tier 403 is egress-IP load-shedding (decolua#4983), so these ports restore request truth and add escape hatches but cannot make a flagged egress succeed.
luqman-v1
left a comment
There was a problem hiding this comment.
LGTM: normalizes colliding OpenCode fingerprint tool definitions in place when arguments match, avoiding unnecessary tool aliasing. Clean unit tests.
|
Thanks @mostafa-binesh for the careful write-up — merged into master. 🙏 One thing worth flagging for the record: this had to be merged manually. The PR base is Also noted in the commit message, as reviewers should know: normalizing in place replaces the caller's own This supersedes #5157 (closed), which fixed only the |
Summary
Fixes #4971.
OpenCode builds its
shelltool description fromprocess.platform:The fingerprint in
open-sse/utils/opencodeFingerprint.jsis a frozen capture of the Linux CLI (Commands run on Linux using bash.) andisFingerprintDefinition()compares with===. So a callershellfrom any non-Linux client never matches, and the pass treats a platform wording difference as a conflicting tool: it renamesshelltoshell_client, appends a placeholder, and injects "do not call the `shell` tool…" into the system prompt.And because the alias is numbered off the caller's own names (
taken), a client that already declaresshell_client/shell_client2pushes the alias toshell_client3— four shell-ish names on the wire for one real tool, and a call to the advertised name can arrive at the client as a tool it does not have.Change
concealFingerprintToolNames()now tries in-place normalization before falling back to the alias:description+parameterswith the fingerprint's and keep the caller's name.readstill passes through untouched on every platform, and so doesshellon Linux: both are byte-identical, so nothing is rewritten.The guard is deliberately name-level. Overwriting
parametersunconditionally would silently drop arguments the caller's handler expects — which is the reason the alias path exists at all:Result
For a real OpenCode client the wire is exactly what the gate asks for:
toolsread(exact),shell(Windows/pwsh)read, shell_client, shell+ placeholder noteread, shell— no alias, no map, no noteread,shell,shell_client,shell_client2read, shell_client3, shell_client, shell_client2, shellread, shell, shell_client, shell_client2shelldeclaring only{cmd}shell_client, read, shell+ noteThe placeholder note is not dead code: it still fires whenever a fingerprint name is genuinely absent and has to be appended.
Tests
Adds an
opencodeFingerprint — in-place normalizationblock covering:{name}) and nested ({function:{name}}) shapesread, shellshell_client3fabricated)All pre-existing assertions in
tests/unit/opencode-fingerprint.test.jsandtests/unit/opencode-free-tool-choice.test.jsare unaffected — every existing aliasing case uses a tool with noparametersat all, which the compatibility guard rejects by design.Security notes
WeakMap.FINGERPRINT_TOOL_DEFINITIONSobject references exactly asappendMissingFingerprintTools()already does — no new mutation surface is introduced.Docs
docs/ARCHITECTURE.mdgains aKnown Architectural Notesentry describing the pass and the frozen-capture platform caveat.