Repository navigation
fix(agent-runtime): send the provider's headers on the compaction request (#398) - #422
Merged
Merged
Conversation
pi-agent-core's `compact` assembles its own stream options and calls `Models.completeSimple` directly, so the context-compaction summary never passed the agent's `streamFn`, where every other request of a session picks up its outbound headers. On an OpenCode Go provider the gateway therefore answered 400 with a missing `x-opencode-session` on `/compact` while ordinary turns succeeded, and a provider row's own `headers` map was absent from the same call. `x-opencode-session` is a reserved key, so a user cannot supply it from the advanced header editor. `compact` takes the model collection as an argument, which is the one seam that reaches its request. Wrap the collection for that call: `completeSimple` now stamps the same merge `streamFn` applies, and every other member stays the collection's own. The summary carries the session's conversation id rather than the per-call id the harness mints when the caller has none, so it reaches the same gateway backend as the conversation it summarizes.
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.
Closes #398
Summary
Reproduced. Manual
/compacton an OpenCode Go provider sends a request withno outbound headers at all — not just a missing
x-opencode-session, whichis why the gateway answers 400. Ordinary turns on the same provider are fine.
The compaction summary is the one provider request the session does not own.
Every other request goes through the agent's
streamFn(
packages/agent-runtime/src/runtime.ts:1655), wherewithProviderHeaders( withOpenCodeSessionHeaders(…))merges the OpenCode routing headers, theprovider row's own
headersmap and the Copilot IDE headers. The summary doesnot:
generateCompaction(packages/agent-runtime/src/runtime.ts:5774) handsthis.modelsstraight to pi-agent-core'scompact(
packages/agent-runtime/src/runtime.ts:5780), andcompactassembles its ownSimpleStreamOptionsand callsModels.completeSimpledirectly. Nothing inthat path ever reaches
streamFn.The reporter's second observation is the expected half.
x-opencode-sessionisa reserved header key on both sides —
packages/agent-runtime/src/provider-headers.ts:37and
crates/host-core/src/providers/validation.rs:57, which is theis reservedrejection they saw in the advanced editor. The runtime is meant toown that header, so adding it by hand is correctly refused; the defect is that
the runtime was not adding it on this one request.
The change
compacttakes the model collection as an argument, and that is the only seamthat reaches its request. A new
packages/agent-runtime/src/compaction-request.tswraps the collection for thatcall:
completeSimpleapplies exactly the mergestreamFnapplies, and everyother member stays the collection's own, so a later pi-agent-core release that
calls something else keeps working unchanged.
One argument changes in
runtime.ts. Everything else in the diff is the newmodule, its test, the runtime test, and the specs plus their zh-CN mirrors per
AGENTS.md§11.Two notes on what the wrapped request now sends:
when the caller has none (
createSummaryRequestOptionsusesoptions.sessionId ?? uuidv7(), so a caller-supplied id was alwayspreferred). This matches what the session's turns already send, so the
summary reaches the same gateway backend as the conversation it summarizes,
and it matches how title generation, prompt enhancement and plugin
agent.completealready forwardsessionId.cacheRetention: "none"is untouched — the harness still isolates thesummary from the conversation's cache.
Automatic threshold compaction shares
generateCompaction, so it is fixed bythe same change; the issue reports the manual path because that is the one a
user triggers.
Evidence
Two tests in
packages/agent-runtime/src/runtime.test.tsdrive the realgenerateCompactionthrough the realcompact, with the model collectionreplaced by a recorder. They are the discriminating pair.
Base —
origin/main@0a1dd80ein a detached worktree, with only thisbranch's test block copied in:
headersisundefinedon the base — that is the 400, at the requestboundary — and the session id is the per-call UUID.
This branch:
The second runtime test is the control that keeps the change honest: a plain
OpenAI-compatible row gets its own
X-Teamheader and no OpenCode header.Validation
pnpm build:jspnpm --filter @pi-desktop/desktop typecheckpnpm lintpnpm -r --if-present testnode scripts/check-architecture.mjs(ARCHITECTURE_BASE=origin/main)pnpm docs:checkpnpm test:e2eand the othertest:e2e:*suitescargo fmt/cargo test -p host-core/cargo clippypnpm test:e2estops athost binary missing; tried: …/target/debug/pi-desktop-host-core.There is no Rust toolchain on the machine this was validated on (
cargo: command not found), sopi-desktop-host-corecannot be built here and no e2e suite ran.The unit coverage above drives the real compaction path end to end at the request
boundary, but it does not replace those runs; the remaining risk sits there and
with the post-integration E2E
AGENTS.md§15 requires.The Rust side is untouched by this diff —
validation.rsis cited only as thesource of the "reserved" message, not changed.
File and line references are against
main@0a1dd80e.中文摘要
复现了。在 OpenCode Go 供应商上手动
/compact,发出的请求完全没有任何自定义标头,不只是缺
x-opencode-session,所以网关直接返回 400;同一供应商的普通对话回合则一切正常。
原因是压缩摘要是会话唯一不经过自己请求边界的一次调用。其他请求都走 agent 的
streamFn(packages/agent-runtime/src/runtime.ts:1655),在那里withProviderHeaders(withOpenCodeSessionHeaders(…))会合入 OpenCode 路由标头、供应商行自己的
headers,以及 Copilot 的 IDE 标头。而generateCompaction(
packages/agent-runtime/src/runtime.ts:5774)把this.models直接交给pi-agent-core 的
compact,后者自行组装SimpleStreamOptions并调用Models.completeSimple,整条路径从不经过streamFn。Issue 里说的第二点是预期行为:
x-opencode-session在 TS 与 Rust 两侧都是保留键(
packages/agent-runtime/src/provider-headers.ts:37、crates/host-core/src/providers/validation.rs:57),高级配置里看到的提示就来自那里。这个标头本应由运行时负责注入,所以手工添加被拒绝是对的;真正的缺陷是运行时
在这一次请求上没有注入。
compact把模型集合作为参数传入,这是唯一能触及该请求的接口。新增的packages/agent-runtime/src/compaction-request.ts只为这次调用包装集合:completeSimple应用与streamFn完全相同的合并,其余成员保持原集合不变。runtime.ts只改了一个实参。该请求现在携带会话自己的对话 id,而不是 pi-agent-core在调用方没有 id 时生成的按次 UUID,因此摘要与被压缩的对话落在同一个网关后端;
cacheRetention: "none"保持不变。阈值触发的自动压缩共用同一函数,同样被修复。