Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ The non-negotiable details:
for a compact handoff summary, and return exactly one synthetic `compaction` output item before
`response.completed`. Encrypt the summary with AES-256-GCM using a key derived from the stable
router token; on later DeepSeek requests, decrypt only DSCodex-prefixed compaction items and
restore them as assistant summary context. A compaction item that cannot be decrypted
restore them as user handoff context. Automatic compaction resumes the same turn without a
new user message; restoring the final summary as `assistant` while tools are enabled makes
DeepSeek demand a missing `reasoning_text` and reject the continuation. A compaction item that cannot be decrypted
(GPT-sealed after a provider switch, or a rotated token) must be dropped, never forwarded raw
to DeepSeek. Never route compaction through GPT or store the summary as plaintext in the
rollout file.
Expand Down
7 changes: 6 additions & 1 deletion src/proxy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,14 @@ function convertInputItem(item, compactionSecret) {
if (item.type === "compaction") {
const summary = openCompaction(item.encrypted_content, compactionSecret);
if (summary) {
// Automatic compaction resumes the same DeepSeek turn without a new user
// message. If the restored summary is the final `assistant` item and tools
// are enabled, DeepSeek treats it as its own prior thinking turn and rejects
// the continuation because no matching `reasoning_text` was replayed.
// Replay the compact summary as user handoff context instead.
return {
type: "message",
role: "assistant",
role: "user",
content: [{ type: "input_text", text: `[Compacted prior context]\n${summary}` }],
};
}
Expand Down
11 changes: 6 additions & 5 deletions test/proxy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -277,21 +277,22 @@ test("adapts Codex remote compaction v2 to a DeepSeek summary and restores it on
assert.equal(observed[0].model, "deepseek-flash");
assert.match(observed[0].input.at(-1).content[0].text, /compact handoff summary/i);

// Automatic compaction resumes the same turn. There is no new user message
// after the compaction item, and the continuation still has tools enabled.
const replayResponse = await fetch(route(proxyUrl), {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
model: "deepseek/deepseek-v4-pro",
input: [
compactItem,
{ type: "message", role: "user", content: [{ type: "input_text", text: "Continue" }] },
],
tools: [{ type: "function", name: "shell" }],
parallel_tool_calls: false,
input: [compactItem],
}),
});
assert.equal(replayResponse.status, 200);
await replayResponse.text();
assert.equal(observed[1].input.some((item) => item.type === "compaction"), false);
const restored = observed[1].input.find((item) => item.role === "assistant");
const restored = observed[1].input.find((item) => item.role === "user");
assert.match(restored.content[0].text, /Compacted prior context/);
assert.match(restored.content[0].text, /tests and a restart are still pending/);
const gptReplay = await fetch(route(proxyUrl), {
Expand Down
Loading