- add a dedicated OpenCode Go executor that always sends x-opencode-session - preserve a valid caller-provided native OpenCode session header - translate downstream Agent session IDs into opaque, stable, Agent-scoped IDs - forward the original provider session seed and client tool on both initial and credential-refresh requests
8.8 KiB
OpenCode Go Session Header Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Send a stable, conversation-scoped x-opencode-session header on every OpenCode Go request and install the patched CLI locally.
Architecture: Add a dedicated OpenCodeGoExecutor extending DefaultExecutor. chatCore passes the provider-scoped session resolved from the original request plus the detected client tool; the executor derives a request-local upstream session and delegates all existing transport, authentication, retry, and proxy behavior to DefaultExecutor.
Tech Stack: Node.js ESM, Vitest, Next.js, npm CLI packaging, GitHub CLI.
Global Constraints
- Apply the header to OpenCode Go chat completions, Claude Messages, and OpenAI Responses transports.
- Preserve a valid native
x-opencode-session; hash all translated non-OpenCode identities toses_<32 lowercase hex>. - Namespace translated identities by detected client tool, using
genericwhen unknown. - Do not keep mutable per-request session state on the executor singleton or mutate the caller's credentials object.
- Do not change OpenCode Go models, routing, reasoning, tool behavior, dependencies, or unrelated providers.
- Reuse upstream issue #3759 instead of creating a duplicate issue.
Task 1: Add Failing OpenCode Go Session Tests
Files:
- Create:
tests/unit/opencode-go-session.test.js
Interfaces:
-
Consumes:
getExecutor(provider)andDefaultExecutor.buildHeaders(credentials, stream, url, model). -
Produces: the required public behavior for
OpenCodeGoExecutor.prepareRequestCredentials({ body, credentials, providerSessionId, clientTool })andOpenCodeGoExecutor.execute(args). -
Step 1: Write the failing tests
Create a Vitest suite that mocks proxyAwareFetch, obtains getExecutor("opencode-go"), and asserts:
const prepared = executor.prepareRequestCredentials({
body: { messages: [{ role: "user", content: "hello" }] },
credentials: { apiKey: "test-key", connectionId: "conn-a", rawHeaders: {} },
providerSessionId: "conversation-a",
clientTool: "claude",
});
expect(prepared).not.toBe(credentials);
expect(prepared._opencodeGoSession).toMatch(/^ses_[0-9a-f]{32}$/);
expect(credentials).not.toHaveProperty("_opencodeGoSession");
Cover native header preservation, stable values across all three runtime transports, different conversation IDs, different client tools using the same ID, connection fallback, no singleton state, no header on DefaultExecutor("openai"), and the final fetch headers returned by execute().
- Step 2: Run the focused test and verify RED
Run:
npx vitest run --config tests/vitest.config.js tests/unit/opencode-go-session.test.js
Expected: FAIL because getExecutor("opencode-go") still returns DefaultExecutor and prepareRequestCredentials does not exist.
- Step 3: Commit the failing test
git add tests/unit/opencode-go-session.test.js
git commit -m "test: cover OpenCode Go session headers"
Task 2: Implement the Dedicated Executor
Files:
- Create:
open-sse/executors/opencode-go.js - Modify:
open-sse/executors/index.js
Interfaces:
-
Consumes:
DefaultExecutor,resolveSessionId(), requestcredentials.rawHeaders,providerSessionId, andclientTool. -
Produces:
OpenCodeGoExecutor,prepareRequestCredentials(), and anexecute()override that delegates with cloned credentials. -
Step 1: Add the minimal executor implementation
Implement these rules:
function translatedSessionId(sessionId, clientTool) {
const digest = crypto
.createHash("sha256")
.update(`opencode-go\0${clientTool || "generic"}\0${sessionId}`)
.digest("hex")
.slice(0, 32);
return `ses_${digest}`;
}
prepareRequestCredentials() must read a case-insensitive native
x-opencode-session with the same non-empty, 256-character cap used by the
session manager. Otherwise it uses providerSessionId or calls
resolveSessionId({ headers, body, connectionId, scope: "opencode-go" }), then
returns { ...credentials, _opencodeGoSession: value }.
execute(args) must call prepareRequestCredentials(args) and delegate using
super.execute({ ...args, credentials: prepared }). buildHeaders() must call
super.buildHeaders() and add the prepared session, with a connection-scoped
fallback for direct callers.
Register new OpenCodeGoExecutor() under "opencode-go" and export the class.
- Step 2: Run the focused test and verify partial GREEN
Run:
npx vitest run --config tests/vitest.config.js tests/unit/opencode-go-session.test.js
Expected: executor-level tests pass; any chatCore-context assertion remains failing until Task 3.
- Step 3: Commit the executor
git add open-sse/executors/opencode-go.js open-sse/executors/index.js tests/unit/opencode-go-session.test.js
git commit -m "fix(opencode-go): add stable session header executor"
Task 3: Pass Original Request Session Context
Files:
- Modify:
open-sse/handlers/chatCore.js - Modify:
tests/unit/opencode-go-session.test.js
Interfaces:
-
Consumes: existing
sessionSeedandclientToolvariables inhandleChatCore(). -
Produces:
providerSessionIdandclientToolfields on both initial and refreshed-credential calls toexecutor.execute(). -
Step 1: Add or enable the failing integration assertion
Use a mocked executor or source request containing a body-only session_id and
assert the executor receives the provider-scoped session resolved before
translation.
- Step 2: Run the focused test and verify RED
Run:
npx vitest run --config tests/vitest.config.js tests/unit/opencode-go-session.test.js
Expected: FAIL because handleChatCore() does not pass providerSessionId or
clientTool to executor.execute().
- Step 3: Pass the request context
Add the same fields to both executor calls:
executor.execute({
model,
body: translatedBody,
stream,
credentials,
providerSessionId: sessionSeed,
clientTool,
signal: streamController.signal,
log,
proxyOptions,
});
- Step 4: Run focused and neighboring tests
Run:
npx vitest run --config tests/vitest.config.js \
tests/unit/opencode-go-session.test.js \
tests/unit/opencode-go-models.test.js \
tests/unit/session-manager.test.js \
tests/unit/executor-const-guard.test.js
Expected: PASS with zero failed tests.
- Step 5: Commit the context wiring
git add open-sse/handlers/chatCore.js tests/unit/opencode-go-session.test.js
git commit -m "fix(chat): forward provider session context"
Task 4: Verify and Install the Local CLI Package
Files:
- Generated:
9router-0.5.65.tgz - Packaged output:
cli/app/server.js
Interfaces:
-
Consumes: completed source changes and existing CLI build scripts.
-
Produces: a globally installed patched
9router@0.5.65. -
Step 1: Run source verification
git diff --check origin/master...HEAD
npx vitest run --config tests/vitest.config.js tests/unit/
npm run build
Expected: every command exits zero. Record any pre-existing full-suite failures separately rather than hiding them.
- Step 2: Build and package the CLI
npm --prefix cli run build
npm --prefix cli pack -- --pack-destination ..
Expected: 9router-0.5.65.tgz exists and contains the patched bundled server.
- Step 3: Replace the global npm installation
npm install -g ./9router-0.5.65.tgz
Expected: /opt/homebrew/lib/node_modules/9router/package.json reports 0.5.65
and the installed bundle contains x-opencode-session plus the new executor.
- Step 4: Commit any required package-source adjustment
Do not commit generated tarballs or CLI build artifacts unless the repository already tracks and requires them.
Task 5: Publish the Upstream Pull Request
Files:
- No additional source files unless verification finds a required correction.
Interfaces:
-
Consumes: verified branch commits and GitHub issue #3759.
-
Produces: a fork branch and a PR against
decolua/9router:master. -
Step 1: Create or repair the GitHub fork remote
Use gh repo fork decolua/9router --remote if the current fork remote remains
missing, then push fix/opencode-go-session-header.
- Step 2: Create the PR
Use title:
fix(opencode-go): send stable session header
The body must include the root cause, downstream-session translation policy,
three covered transports, concurrency behavior, verification evidence,
Fixes #3759, and a note that this PR is intentionally narrower than #3780.
- Step 3: Verify the published PR
Run gh pr view --json number,title,state,url,headRefName,baseRefName and report
the issue and PR URLs.