Skip to content

Commit 790ef06

Browse files
authored
🤖 fix: use correct model for post-compaction continue messages (#723)
## Summary Fixes a regression where the `/compact` model (often a cheaper/faster model) was incorrectly used for the follow-up continue message instead of the user's selected workspace model. ## Regression Introduced in a31a2e0 which moved continue message logic to the backend message queue but failed to sanitize the options. ## Changes - Extended `CompactionRequestData` to carry `resumeModel` - Updated frontend/mobile command parsers to capture the current model before switching to compaction mode - Added `buildContinueMessageOptions` helper in backend to: - Strip compaction flags (`mode: 'compact'`, `maxOutputTokens`) - Restore the original `resumeModel` - Added tests for options sanitization and metadata round-tripping _Generated with `mux`_
1 parent ffb75ab commit 790ef06

File tree

8 files changed

+119
-3
lines changed

8 files changed

+119
-3
lines changed

mobile/src/utils/slashCommandHelpers.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe("buildMobileCompactionPayload", () => {
5656
model: "anthropic:claude-opus-4-1",
5757
maxOutputTokens: 800,
5858
continueMessage: parsed.continueMessage,
59+
resumeModel: baseOptions.model,
5960
});
6061
expect(payload.sendOptions.model).toBe("anthropic:claude-opus-4-1");
6162
expect(payload.sendOptions.mode).toBe("compact");

mobile/src/utils/slashCommandHelpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function buildMobileCompactionPayload(
5757
model: parsed.model,
5858
maxOutputTokens: parsed.maxOutputTokens,
5959
continueMessage: parsed.continueMessage,
60+
resumeModel: baseOptions.model,
6061
},
6162
};
6263

src/browser/utils/chatCommands.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { parseRuntimeString } from "./chatCommands";
1+
import { describe, expect, test, beforeEach } from "bun:test";
2+
import type { SendMessageOptions } from "@/common/types/ipc";
3+
import { parseRuntimeString, prepareCompactionMessage } from "./chatCommands";
4+
5+
// Simple mock for localStorage to satisfy resolveCompactionModel
6+
beforeEach(() => {
7+
globalThis.localStorage = {
8+
getItem: () => null,
9+
setItem: () => undefined,
10+
} as unknown as Storage;
11+
});
212

313
describe("parseRuntimeString", () => {
414
const workspaceName = "test-workspace";
@@ -84,3 +94,30 @@ describe("parseRuntimeString", () => {
8494
);
8595
});
8696
});
97+
98+
describe("prepareCompactionMessage", () => {
99+
const createBaseOptions = (): SendMessageOptions => ({
100+
model: "anthropic:claude-3-5-sonnet",
101+
thinkingLevel: "medium",
102+
toolPolicy: [],
103+
mode: "exec",
104+
});
105+
106+
test("embeds resumeModel from base send options", () => {
107+
const sendMessageOptions = createBaseOptions();
108+
const { metadata } = prepareCompactionMessage({
109+
workspaceId: "ws-1",
110+
maxOutputTokens: 4096,
111+
continueMessage: "Keep building",
112+
model: "anthropic:claude-3-5-haiku",
113+
sendMessageOptions,
114+
});
115+
116+
expect(metadata.type).toBe("compaction-request");
117+
if (metadata.type !== "compaction-request") {
118+
throw new Error("Expected compaction metadata");
119+
}
120+
121+
expect(metadata.parsed.resumeModel).toBe(sendMessageOptions.model);
122+
});
123+
});

src/browser/utils/chatCommands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export function prepareCompactionMessage(options: CompactionOptions): {
214214
model: effectiveModel,
215215
maxOutputTokens: options.maxOutputTokens,
216216
continueMessage: options.continueMessage,
217+
resumeModel: options.sendMessageOptions.model,
217218
};
218219

219220
const metadata: MuxFrontendMetadata = {

src/common/types/message.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface CompactionRequestData {
1010
model?: string; // Custom model override for compaction
1111
maxOutputTokens?: number;
1212
continueMessage?: string;
13+
resumeModel?: string; // Original workspace model to use after compaction continues
1314
}
1415

1516
// Frontend-specific metadata stored in muxMetadata field
@@ -103,6 +104,7 @@ export type DisplayedMessage =
103104
parsed: {
104105
maxOutputTokens?: number;
105106
continueMessage?: string;
107+
resumeModel?: string;
106108
};
107109
};
108110
}

src/node/services/agentSession.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Ok, Err } from "@/common/types/result";
2323
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
2424
import { createRuntime } from "@/node/runtime/runtimeFactory";
2525
import { MessageQueue } from "./messageQueue";
26+
import { buildContinueMessageOptions } from "./compactionContinueOptions";
2627
import type { StreamEndEvent, StreamAbortEvent } from "@/common/types/stream";
2728
import { CompactionHandler } from "./compactionHandler";
2829

@@ -336,8 +337,17 @@ export class AgentSession {
336337
const muxMeta = options?.muxMetadata;
337338
if (muxMeta?.type === "compaction-request" && muxMeta.parsed.continueMessage && options) {
338339
// Strip out edit-specific and compaction-specific fields so the queued message is a fresh user message
339-
const { muxMetadata, mode, editMessageId, ...continueOptions } = options;
340-
this.messageQueue.add(muxMeta.parsed.continueMessage, continueOptions);
340+
const { muxMetadata, mode, editMessageId, imageParts, ...rest } = options;
341+
const baseContinueOptions: SendMessageOptions = { ...rest };
342+
const sanitizedOptions = buildContinueMessageOptions(
343+
baseContinueOptions,
344+
muxMeta.parsed.resumeModel
345+
);
346+
const continuePayload =
347+
imageParts && imageParts.length > 0
348+
? { ...sanitizedOptions, imageParts }
349+
: sanitizedOptions;
350+
this.messageQueue.add(muxMeta.parsed.continueMessage, continuePayload);
341351
this.emitQueuedMessageChanged();
342352
}
343353

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, it } from "bun:test";
2+
import type { SendMessageOptions } from "@/common/types/ipc";
3+
import { buildContinueMessageOptions } from "./compactionContinueOptions";
4+
5+
const baseOptions = (): SendMessageOptions => ({
6+
model: "anthropic:claude-3-5-sonnet",
7+
thinkingLevel: "medium",
8+
toolPolicy: [],
9+
additionalSystemInstructions: "be helpful",
10+
mode: "compact",
11+
maxOutputTokens: 2048,
12+
});
13+
14+
describe("buildContinueMessageOptions", () => {
15+
it("uses resumeModel when provided and drops compact overrides", () => {
16+
const options = baseOptions();
17+
const result = buildContinueMessageOptions(options, "anthropic:claude-3-5-haiku");
18+
19+
expect(result).not.toBe(options);
20+
expect(result.model).toBe("anthropic:claude-3-5-haiku");
21+
expect(result.mode).toBeUndefined();
22+
expect(result.maxOutputTokens).toBeUndefined();
23+
expect(result.thinkingLevel).toBe("medium");
24+
expect(result.toolPolicy).toEqual([]);
25+
// Ensure original options untouched
26+
expect(options.model).toBe("anthropic:claude-3-5-sonnet");
27+
expect(options.mode).toBe("compact");
28+
expect(options.maxOutputTokens).toBe(2048);
29+
});
30+
31+
it("falls back to compaction model when resumeModel is missing", () => {
32+
const options = baseOptions();
33+
const result = buildContinueMessageOptions(options);
34+
35+
expect(result.model).toBe(options.model);
36+
});
37+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { SendMessageOptions } from "@/common/types/ipc";
2+
3+
/**
4+
* Build sanitized SendMessageOptions for auto-continue messages after compaction.
5+
*
6+
* - Drops compaction-specific overrides (mode="compact", maxOutputTokens)
7+
* - Removes frontend metadata (muxMetadata)
8+
* - Restores the original workspace model when provided
9+
*/
10+
export function buildContinueMessageOptions(
11+
options: SendMessageOptions,
12+
resumeModel?: string
13+
): SendMessageOptions {
14+
const {
15+
muxMetadata: _ignoredMetadata,
16+
maxOutputTokens: _ignoredMaxOutputTokens,
17+
mode: _ignoredMode,
18+
...rest
19+
} = options;
20+
21+
const nextModel = resumeModel ?? options.model;
22+
23+
return {
24+
...rest,
25+
model: nextModel,
26+
};
27+
}

0 commit comments

Comments
 (0)