mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(custom-model): keep the launch model out of the stored config
Quick-start wrote a selection's launchModel into the session's stored <Mode>Config, which is what a clear falls back to, so a clear relaunched the CLI with the endpoint's model id on its native backend. Only API callers can reach that, since no page sends a clear. The first launch now takes the forced model from the session's selection, through the same _withCustomModelLaunchModel() the respawn path uses, so the stored config keeps the model chosen at create. That also carries the model onto a new tmux session for a selection applied to a running session, which the create path used to lose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fc25bf5f83
commit
a651616907
6 files changed
+95
-32
No files matched your search
@@ -204,6 +204,34 @@ describe('POST /api/quick-start: customModel (one-shot custom-model launch)', ()
|
||||
expect(restartSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("leaves the caller's own model in the stored config, which is what a later clear falls back to", async () => {
|
||||
const res = await quickStart({
|
||||
caseName: 'cm-pi-own-model',
|
||||
mode: 'pi',
|
||||
piConfig: { model: 'anthropic/claude-x' },
|
||||
customModel: { endpointId: 'ep1', modelId: 'qwen3' },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const { sessionId } = res.json();
|
||||
const session = ctx.sessions.get(sessionId) as unknown as Session;
|
||||
expect(session.getCustomModelForPersist()?.launchModel).toBe('custom/qwen3');
|
||||
expect(session.toState().piConfig?.model).toBe('anthropic/claude-x');
|
||||
});
|
||||
|
||||
it('stores no model at all for a caller that chose none', async () => {
|
||||
const res = await quickStart({
|
||||
caseName: 'cm-codex-no-model',
|
||||
mode: 'codex',
|
||||
customModel: { endpointId: 'ep1', modelId: 'qwen3' },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const session = ctx.sessions.get(res.json().sessionId) as unknown as Session;
|
||||
expect(session.getCustomModelForPersist()?.launchModel).toBe('qwen3');
|
||||
expect(session.toState().codexConfig?.model).toBeUndefined();
|
||||
});
|
||||
|
||||
it('404s for an unknown endpoint id', async () => {
|
||||
const res = await quickStart({
|
||||
caseName: 'cm-ghost',
|
||||
|
||||
@@ -195,6 +195,53 @@ describe('restartCli() must not kill a working pane', () => {
|
||||
});
|
||||
|
||||
describe('launchModel reaches the CLI through its own launch param', () => {
|
||||
/** A session the route just built: no pane yet, so its launch is `createSession`. */
|
||||
function freshSession(mode: SessionMode, extra: Record<string, unknown> = {}) {
|
||||
mkdirSync(workingDir, { recursive: true });
|
||||
const mux = new TmuxManager();
|
||||
const create = vi.spyOn(mux, 'createSession');
|
||||
vi.spyOn(mux, 'muxSessionExists').mockReturnValue(true);
|
||||
const respawn = vi.spyOn(mux, 'respawnPane').mockResolvedValue(4242);
|
||||
const session = new Session({ workingDir, mode, mux, useMux: true, ...extra });
|
||||
sessions.push(session);
|
||||
return { session, create, respawn };
|
||||
}
|
||||
|
||||
it('the first launch carries it too, and a clear relaunches on the model the session was created with', async () => {
|
||||
const { session, create, respawn } = freshSession('pi', { piConfig: { model: 'anthropic/claude-x' } });
|
||||
session.setCustomModel(
|
||||
{ endpointId: 'ep', modelId: 'qwen3', envKeys: ['HOME'], configDir: '/tmp/cfg', launchModel: 'custom/qwen3' },
|
||||
{ HOME: '/tmp/cfg' }
|
||||
);
|
||||
await session.startInteractive();
|
||||
expect(create.mock.calls[0][0].piConfig?.model).toBe('custom/qwen3');
|
||||
expect(session.toState().piConfig?.model).toBe('anthropic/claude-x');
|
||||
|
||||
session.setCustomModel(undefined);
|
||||
expect(await session.restartCli()).toBe(true);
|
||||
expect(respawn.mock.calls[0][0].piConfig?.model).toBe('anthropic/claude-x');
|
||||
});
|
||||
|
||||
it('a clear on a session created with no model relaunches with none', async () => {
|
||||
const { session, create, respawn } = freshSession('codex');
|
||||
session.setCustomModel(
|
||||
{ endpointId: 'ep', modelId: 'qwen3', envKeys: ['CODEX_HOME'], configDir: '/tmp/cfg', launchModel: 'qwen3' },
|
||||
{ CODEX_HOME: '/tmp/cfg' }
|
||||
);
|
||||
await session.startInteractive();
|
||||
expect(create.mock.calls[0][0].codexConfig?.model).toBe('qwen3');
|
||||
|
||||
session.setCustomModel(undefined);
|
||||
expect(await session.restartCli()).toBe(true);
|
||||
expect(respawn.mock.calls[0][0].codexConfig?.model).toBeUndefined();
|
||||
});
|
||||
|
||||
it('leaves the launch untouched without a selection', async () => {
|
||||
const { session, create } = freshSession('pi', { piConfig: { model: 'anthropic/claude-x', thinking: 'low' } });
|
||||
await session.startInteractive();
|
||||
expect(create.mock.calls[0][0].piConfig).toEqual({ model: 'anthropic/claude-x', thinking: 'low' });
|
||||
});
|
||||
|
||||
it('pi: the selection forces piConfig.model on the respawn options, leaving the stored config alone', async () => {
|
||||
const { session, respawn } = liveSession('pi', { piConfig: { model: 'anthropic/claude-x', thinking: 'low' } });
|
||||
session.setCustomModel(
|
||||
|
||||
Reference in new issue
Block a user