fix: keep DeepSeek Harness sessions on their own cache identity - #890
Merged
Conversation
…dentity DeepSeek Harness sends X-Deepseek-Harness-Session-Id on every turn, but no generic Session-Id header and no body session field, so the gateway derived no session identity for it at all. Two consequences, both of which destroy the upstream prefix cache: - requestSessionStickyHeaderKey did not recognize the header, so the sticky key stayed empty. applySessionPromptCacheKey then emitted no prompt_cache_key, and turns of one conversation drifted across auth bindings — upstream prefix caches are scoped per account, so a drifting turn is a guaranteed miss. - opencodeGoSessionID fell through to the auth ID, collapsing every concurrent session of one API key onto a single reasoning slot (reasoningCacheMaxEntriesPerKey == 1). Sessions overwrote each other and were injected with another session's reasoning_content, changing the request prefix on every turn. The sticky key already carries exactly the identity both paths need, so opencodeGoSessionID now consults it before the auth-ID fallback. Also stop a present-but-blank metadata value from short-circuiting the remaining candidates in opencodeGoSessionID. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ResolveSessionKey only consulted ExecutionSessionMetadataKey (websocket-only) and the generic Session-Id header, so clients that identify sessions with their own header — DeepSeek Harness, Grok, Codex — resolved no key at all and silently lost cross-turn image memory. The sticky key carries the same per-conversation identity and is safe here for the reason auth.ID is not: it is scoped to one conversation, not to an API key, so it cannot leak images between concurrent sessions. Existing sources keep their precedence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
DeepSeek Harness(dsh)经 opencode-go 渠道调用 deepseek-v4-flash 时,上游 prompt 缓存命中率极低且忽高忽低。
根因是 dsh 每轮都发
X-Deepseek-Harness-Session-Id,但不发通用的Session-Id,body 里也没有session_id/conversation_id。网关因此完全推导不出它的会话身份,导致两条独立的破坏路径:requestSessionStickyHeaderKey不认识该 header → sticky key 为空 →applySessionPromptCacheKey不注入prompt_cache_key,同一会话的请求在多个 auth 绑定间漂移。上游前缀缓存是按账号隔离的,漂移即必然 miss。opencodeGoSessionID三个来源全部落空(ExecutionSessionMetadataKey仅 websocket 路径设置),回退到auth.ID。而reasoningCacheMaxEntriesPerKey == 1,于是同一 API key 下所有并发会话共用一个 reasoning 槽位,互相覆盖,每轮被注入其它会话的reasoning_content,请求前缀持续变化。第 2 条影响更大,且在并发使用 subagent 时被显著放大。
改动
session_sticky.go:header 白名单补X-Deepseek-Harness-Session-Id。opencode_go_reasoning.go:opencodeGoSessionID在回退到auth.ID前先读 sticky key —— 该 key 本就携带所需的会话身份,无需新增机制。顺带修正空白 metadata 值会短路后续候选的问题。vision/session.go:ResolveSessionKey同源问题(header 类客户端拿不到 key,跨轮图片记忆静默失效)。sticky key 在此安全的理由与auth.ID不同:它按会话而非按 API key 划分,不会在并发会话间泄漏图片。三处均保持既有来源的优先级不变,只在原本必然失败的分支上补一条来源。
验证
opencodeGoSessionID4、ResolveSessionKey4。含关键回归:同一 auth 下不同会话必须得到不同 id。TestOpenCodeGoSessionID_FallbackToAuthID)无回归。./scripts/ci-pr.sh通过(退出码 0):结构检查、gofmt、secret scan、go vet、go test ./...、golangci-lint v1.64.5、go build。真实命中率需部署后用 dsh 实测
prompt_cache_hit_tokens确认;本 PR 仅在代码层面证明会话身份已正确传递。🤖 Generated with Claude Code