Skip to content

feat: scope OmniRoute session per project and allow opting out of Memory - #50

Open
ahmet-cetinkaya wants to merge 1 commit into
Alph4d0g:mainfrom
ahmet-cetinkaya:feat/project-scoped-session-id
Open

ahmet-cetinkaya wants to merge 1 commit into
Alph4d0g:mainfrom
ahmet-cetinkaya:feat/project-scoped-session-id

Conversation

@ahmet-cetinkaya

@ahmet-cetinkaya ahmet-cetinkaya commented Sep 5, 2026 •

Copy link
Copy Markdown

🚀 Context

OmniRoute scopes per-project server-side state — most visibly its Memory feature — by the x-omniroute-session-id request header. createFetchInterceptor only ever set Authorization and Content-Type, so OmniRoute fell back to a fresh per-request id (pipelineSessionId = explicitSessionIdHeader || skillRequestId).

For anyone running OpenCode against a single OmniRoute API key across several repositories, memory extracted while working on Project A can be injected into an unrelated Project B session. There was also no way to opt out client-side, since OmniRoute's Memory toggle is server-side and shared by every client of that instance.

Important

Sending a stable session id does not by itself fully close the leak. OmniRoute's retrieval path currently hardcodes scope: 'apiKey' in toMemoryRetrievalConfig() and ignores session scope — that half needs an upstream fix in OmniRoute itself. This PR is the client-side prerequisite: without a stable per-project id sent by the client, no server-side session scoping can ever work for OpenCode users.

🔗 Related


⚙️ Implementation Details

Project-scoped session id (default on). The interceptor now sends x-omniroute-session-id: opencode-<sha256(cwd) first 16 hex>.

  • The cwd is hashed, never sent verbatim, so local filesystem paths do not leave the machine.
  • Computed once per loader call rather than per request — the working directory does not change mid-session.
  • A caller-supplied x-omniroute-session-id always wins; the plugin only fills in a default.
  • sessionScope: 'off' restores the previous behavior.

disableMemory option (default off). When true, sends x-omniroute-no-memory: true on every intercepted request, which disables memory and skill injection server-side for that request. This lets one client opt out without touching the shared server setting.

Both options are additive and default to behavior that is either unchanged (disableMemory) or strictly safer (sessionScope).

flowchart LR
    A[OpenCode request] --> B{OmniRoute URL?}
    B -- no --> C[pass through untouched]
    B -- yes --> D[set Authorization + Content-Type]
    D --> E{sessionScope = off?}
    E -- yes --> G
    E -- no --> F{caller already<br/>sent session id?}
    F -- yes --> G[keep caller value]
    F -- no --> H["set opencode-sha256(cwd)"]
    G --> I{disableMemory?}
    H --> I
    I -- yes --> J[set x-omniroute-no-memory: true]
    I -- no --> K[forward request]
    J --> K
Loading
File Change
src/plugin.ts resolveProjectSessionId(), getSessionScope(), header wiring in createFetchInterceptor
src/types.ts OmniRouteSessionScope type; sessionScope + disableMemory on OmniRouteConfig
src/constants.ts Header names and session id prefix
index.ts Export OmniRouteSessionScope alongside the other public types
test/plugin.test.mjs 5 new tests + session-id assertion on the existing header test
README.md Config table rows, two new sections, updated OmniRouteConfig

📋 Checklist for Reviewer

  • Tests passed locally — npm test 71/71 (66 existing + 5 new), npm run check:exports clean.
  • Commit history is clean and descriptive — single Conventional Commit.
  • Documentation updated — README config table, two new sections, updated type block.
  • Code quality standards met — TypeScript strict build passes, no any, follows the repo's Headers / URL-handling conventions from AGENTS.md.
Verification detail

New tests cover: header present and correctly shaped by default, stability across repeated loader calls in one project, sessionScope: 'off' omits it, caller-supplied header preserved, disableMemory: true sends the no-memory header, and non-OmniRoute URLs get neither header.

Beyond the suite, verified against a real OmniRoute v3.8.50 instance: a /v1/chat/completions request carrying both headers returns 200. Also confirmed independently that the emitted id equals a separately computed sha256(cwd) prefix, contains no raw path, and differs across project directories.

OmniRoute scopes per-project server-side state (notably Memory) by the
x-omniroute-session-id header. The plugin never sent one, so OmniRoute fell
back to a fresh per-request id and state extracted while working on one
project could surface in an unrelated project sharing the same API key.

Send a stable id derived from the current working directory by default,
hashing the path so it never leaves the machine. An explicit caller-supplied
header still wins, and sessionScope: 'off' restores the previous behavior.

Also add a disableMemory option that sends x-omniroute-no-memory: true, so a
client can opt out of Memory without changing the server-side setting shared
by every client of that instance.

Refs Alph4d0g#49
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.

feat: send x-omniroute-session-id (and honor x-omniroute-no-memory) to scope OmniRoute Memory per project

1 participant