Skip to content

fix(providers): send OpenCode session attribution header - #114

Open
sambitcreate wants to merge 2 commits into
mainfrom
fix/opencode-session-attribution
Open

fix(providers): send OpenCode session attribution header#114
sambitcreate wants to merge 2 commits into
mainfrom
fix/opencode-session-attribution

Conversation

@sambitcreate

Copy link
Copy Markdown
Owner

Summary

Every generation on the OpenCode Zen Go provider (opencode-go) failed before inference with:

400 {"type":"MissingSessionID","message":"Error from provider (Console Go):
Request is missing x-opencode-session and cannot be routed efficiently."}

OpenCode's gateway requires a stable per-conversation x-opencode-session header on every inference request (docs; the operators filed the same ask with jcode, Kilo Code, and VS Code — "any stable UUID per conversation works", "all outbound inference HTTP requests"). A Hermes control matrix proved the session header alone flips the request 400 → 200.

Aiden's pinned @earendil-works/pi-ai cannot emit that header — no published version can (upstream fixed it only inside the pi coding-agent, pi#4847; the pi-ai package fix pi#9326 is unreleased). So Aiden owns the attribution.

Changes

  • New main/services/opencode-session-attribution.ts — attaches x-opencode-session: <conversation id> to the resolved runtime model. Gate: provider opencode / opencode-go / opencode-zen (forward-compat) or an exact opencode.ai base URL. Case-insensitive overwrite, clone-only (never mutates the shared catalog model), and ids are validated as bounded tokens so renderer-adjacent inputs can never carry CR/LF into a header.
  • Model-level seamresolveModelRuntimeWith / resolveModelRuntime / resolveBotModelRuntime / resolveCompactionModelMetadata grew an optional conversationId. Every pi-ai transport merges model.headers into the outgoing request, so one attachment covers chat turns, subagent children, isolated inference workers (headers survive the utility-process protocol), and the pinned-Bot dispatch path.
  • Threaded ids — chat turns (chat.id), Bot preparation, compaction (both engines), chat titles, advisor dispatch, RPiV-BtW side questions, vision analysis, bot avatars (requestId), and dictation cleanup (fresh UUID for conversation-less one-shots).

Testing

  • 15 new tests in main/services/opencode-session-attribution.test.ts: gate matrix (all provider ids, host matches, foreign/malformed URLs), blank/invalid ids, case-variant replacement, header preservation, catalog non-mutation, pinned-Bot dispatch, real wire-level delivery (local HTTP server + actual pi-ai completions transport), and native/custom resolver paths.
  • Registered in test, test:google-provider, test:coverage.
  • Verified locally: type-check clean; 201/201 across all 16 affected suites; npm run test:bots 440 pass. The 3 google-provider.test.ts failures on this Mac are pre-existing and environment-specific (verified identical on a stashed clean tree; CI is unaffected).

Notes

  • Reviewed by two independent review agents: correctness/security (wire merge order, no cross-conversation leakage, no duplicate headers) and coverage/regression (all inference paths enumerated; the source-text contract in bots.contract.test.ts was fixed in this branch).
  • No renderer/iOS/Android contract changes; no models.dev involvement.
  • Deliberately out of scope (tracked in .memory/PROJECT-HISTORY.md): preserving provider error status/type in production diagnostics, a live gateway probe (incl. /messages + /responses routes), and removing this shim once a published pi-ai ships pi#9326.

Every OpenCode Go/Zen generation failed before inference with
400 MissingSessionID: the OpenCode gateway requires a stable
per-conversation x-opencode-session header on every inference
request, and the pinned @earendil-works/pi-ai (no published
version emits it) left Aiden unable to send one.

Attach the header to the resolved runtime model via a new
main/services/opencode-session-attribution.ts helper (opencode,
opencode-go, opencode-zen, or any opencode.ai base URL), since
every pi-ai transport merges model.headers into the outgoing
request and subagent/isolated paths inherit the runtime model.
Thread an optional conversationId through the runtime resolvers
and pass it from chat turns, Bot preparation, compaction (both
engines), chat titles, advisor dispatch, RPiV-BtW side questions,
vision analysis, bot avatars, and dictation cleanup (fresh UUIDs
for conversation-less one-shots).

Tests cover the gate matrix, injection-safe bounded ids,
pinned-Bot dispatch, and real wire-level header delivery through
the completions transport; the suite is registered in test,
test:google-provider, and test:coverage.

@pullfrog pullfrog 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.

ℹ️ No critical issues — one wire-coverage suggestion inline, plus a nitpick.

Reviewed changes

  • New attribution modulemain/services/opencode-session-attribution.ts clones the resolved runtime model and merges x-opencode-session, gated on provider id (opencode / opencode-go / opencode-zen) or an exact opencode.ai base-URL host, with a bounded id grammar and case-insensitive replacement of any existing variant.
  • Resolver seamresolveModelRuntimeWith, resolveModelRuntime, resolveBotModelRuntime, and resolveCompactionModelMetadata accept an optional conversationId and apply attribution on both the Pi-native and custom-provider paths.
  • Threaded ids — chat turns (chat.id), Bot preparation (chat.id), both compaction engines, chat titles, advisor dispatch, BTW side questions, vision analysis, bot avatars (requestId), and dictation cleanup (fresh UUID) all pass a stable conversation or per-request id.
  • Tests — 15 new cases in main/services/opencode-session-attribution.test.ts covering the gate matrix, id validation, clone-only behavior, pinned-Bot dispatch, both resolver paths, and real wire delivery; registered in test, test:google-provider, and test:coverage.

ℹ️ Nitpicks

  • .papercuts/troubleshooting.md adds a subagent-artifacts/*_output.md note that is unrelated to this change; consider dropping it from this PR.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash𝕏

Comment thread main/services/opencode-session-attribution.test.ts Outdated
Address Pullfrog review feedback on PR #114: parameterize the
wire-level test across openai-completions, openai-responses, and
anthropic-messages so a pi-ai change that stops merging
model.headers fails loudly per transport, and drop the unrelated
troubleshooting note from this PR.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Pinned the session header on every OpenCode Go transport — the wire-level test now loops over openai-completions, openai-responses, and anthropic-messages, asserting per transport that the attributed request carries x-opencode-session while the unrelated provider does not.
  • Dropped the unrelated .papercuts note — the subagent-artifacts/*_output.md troubleshooting entry is no longer part of this PR.

The new coverage is genuine: each lazy transport wraps its own streamSimple implementation, so a transport that stops merging model.headers fails its own assertion. Verified at c1c2c6f — 15/15 in the suite, tsc --noEmit and eslint clean.

Pullfrog  | View workflow run | Using DeepSeek Flash𝕏

@pullfrog

pullfrog Bot commented Sep 12, 2026

Copy link
Copy Markdown

All feedback from review #5185375806 was already addressed at the branch HEAD (c1c2c6f) — no new changes were required. Verified the wire test now pins the session header on all three OpenCode Go transports (main/services/opencode-session-attribution.test.ts:190-225), the unrelated .papercuts note was dropped, and the thread is resolved. Ran npx tsx --test main/services/opencode-session-attribution.test.ts: 15/15 pass. Fresh re-read of the review found no new comments.

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Flash𝕏

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