Skip to content

Commit be375ae

Browse files
Doctor-wuclaude
andauthored
feat: remove model selection capability (#21)
* feat: remove model selection capability - Remove /model slash command from Discord (agentControlCommands + handler) - Core runner no longer passes model to backends (claude --model / codex --model args not added) - /status (formatEnvironmentSummary, formatConfiguredEnvironment) shows "model: default" - conversationModels state map retained but unused by the runner - Update tests to reflect new behaviour * fix: remove remaining model selection code from feishu runtime and cards Remove ModelSelectionCard type, buildModelSelectionCard(), buildFeishuModelSelectionCardPayload(), and all model-related actions from session control cards. Clean up controller.ts model branch, conversation.ts model result, and corresponding tests across core and feishu packages. ENT-0 Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent 228b0cf commit be375ae

19 files changed

Lines changed: 32 additions & 567 deletions

File tree

packages/core/src/platform/__tests__/conversation.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
type ManagedBridgeTarget,
55
conversationBackend,
66
conversationEffort,
7-
conversationModels,
87
conversationSessions,
98
openThreadSessionBinding,
109
pendingBackendChanges,
@@ -22,7 +21,6 @@ describe('platform conversation setup and controls', () => {
2221
beforeEach(() => {
2322
conversationBackend.clear();
2423
conversationEffort.clear();
25-
conversationModels.clear();
2624
conversationSessions.clear();
2725
pendingBackendChanges.clear();
2826
threadSessionBindings.clear();
@@ -63,7 +61,7 @@ describe('platform conversation setup and controls', () => {
6361
});
6462
});
6563

66-
it('applies interrupt, done, backend confirmation, confirm, cancel, model, and effort actions', () => {
64+
it('applies interrupt, done, backend confirmation, confirm, cancel, and effort actions', () => {
6765
conversationBackend.set('conv-actions', 'claude');
6866
conversationSessions.set('conv-actions', 'session-1');
6967
openThreadSessionBinding({
@@ -130,16 +128,6 @@ describe('platform conversation setup and controls', () => {
130128
expect(threadSessionBindings.has('conv-actions')).toBe(false);
131129
expect(threadContinuationSnapshots.has('conv-actions')).toBe(false);
132130

133-
expect(applyConversationControlAction({
134-
conversationId: 'conv-actions',
135-
type: 'model',
136-
value: 'claude-3-7',
137-
})).toEqual({
138-
kind: 'model',
139-
conversationId: 'conv-actions',
140-
});
141-
expect(conversationModels.get('conv-actions')).toBe('claude-3-7');
142-
143131
expect(applyConversationControlAction({
144132
conversationId: 'conv-actions',
145133
type: 'effort',

packages/core/src/platform/conversation.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ export type ConversationControlResult =
5757
kind: 'cancel-backend';
5858
conversationId: string;
5959
}
60-
| {
61-
kind: 'model';
62-
conversationId: string;
63-
}
6460
| {
6561
kind: 'effort';
6662
conversationId: string;
@@ -165,13 +161,6 @@ export function applyConversationControlAction(action: ConversationControlAction
165161
};
166162
}
167163

168-
if (result.kind === 'model') {
169-
return {
170-
kind: 'model',
171-
conversationId: result.conversationId,
172-
};
173-
}
174-
175164
return {
176165
kind: 'effort',
177166
conversationId: result.conversationId,

packages/core/src/runtime/__tests__/conversation-runner.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('runConversationWithRenderer', () => {
8181
});
8282
});
8383

84-
it('clears stale configured models before starting a run', async () => {
84+
it('does not pass model to the session (model selection disabled)', async () => {
8585
conversationBackend.set('conv-stale-model', 'opencode');
8686
conversationModels.set('conv-stale-model', 'sonnet');
8787

@@ -99,12 +99,15 @@ describe('runConversationWithRenderer', () => {
9999

100100
expect(runConversationSession).toHaveBeenCalledWith('conv-stale-model', expect.objectContaining({
101101
backend: 'opencode',
102-
model: undefined,
103102
}));
104-
expect(conversationModels.has('conv-stale-model')).toBe(false);
103+
expect(runConversationSession).toHaveBeenCalledWith('conv-stale-model', expect.not.objectContaining({
104+
model: expect.anything(),
105+
}));
106+
// conversationModels state is kept but not consumed by the runner
107+
expect(conversationModels.get('conv-stale-model')).toBe('sonnet');
105108
});
106109

107-
it('preserves a configured model when the backend does not expose a model list', async () => {
110+
it('does not pass model to the session even when backend has no model list', async () => {
108111
conversationBackend.set('conv-opaque-model', 'opaque');
109112
conversationModels.set('conv-opaque-model', 'manual-model');
110113

@@ -122,8 +125,11 @@ describe('runConversationWithRenderer', () => {
122125

123126
expect(runConversationSession).toHaveBeenCalledWith('conv-opaque-model', expect.objectContaining({
124127
backend: 'opaque',
125-
model: 'manual-model',
126128
}));
129+
expect(runConversationSession).toHaveBeenCalledWith('conv-opaque-model', expect.not.objectContaining({
130+
model: expect.anything(),
131+
}));
132+
// conversationModels state is preserved (kept but unused by the runner)
127133
expect(conversationModels.get('conv-opaque-model')).toBe('manual-model');
128134
});
129135

packages/core/src/runtime/conversation-runner.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { randomUUID } from 'node:crypto';
22
import { runConversationSession } from '../agent/runtime.js';
3-
import { getBackendSupportedModels, isBackendModelSupported } from '../agent/backend.js';
43
import {
54
activeConversations,
65
conversationBackend,
@@ -84,28 +83,6 @@ function resolveBackendName(
8483
return existingBackend ?? 'claude';
8584
}
8685

87-
function resolveConfiguredModel(
88-
conversationId: string,
89-
backendName: BackendName,
90-
): string | undefined {
91-
const configuredModel = conversationModels.get(conversationId);
92-
if (!configuredModel) {
93-
return undefined;
94-
}
95-
96-
const supportedModels = getBackendSupportedModels(backendName);
97-
if (supportedModels.length === 0) {
98-
return configuredModel;
99-
}
100-
101-
if (isBackendModelSupported(backendName, configuredModel)) {
102-
return configuredModel;
103-
}
104-
105-
conversationModels.delete(conversationId);
106-
return undefined;
107-
}
108-
10986
function inferStopReason(error: string): ThreadContinuationStopReason {
11087
if (/timed out/i.test(error)) {
11188
return 'timeout';
@@ -175,7 +152,6 @@ export async function runConversationWithRenderer<TTarget, TTrigger = unknown>(
175152
prompt: options.prompt,
176153
sourceMessageId: options.sourceMessageId,
177154
}) ?? { prompt: options.prompt };
178-
const model = resolveConfiguredModel(conversationId, backendName);
179155
openThreadSessionBinding({ conversationId, backend: backendName });
180156
const resumeMode = resolveThreadResumeMode(conversationId);
181157

@@ -191,7 +167,6 @@ export async function runConversationWithRenderer<TTarget, TTrigger = unknown>(
191167
const events = runConversationSession(conversationId, {
192168
mode: options.mode ?? 'code',
193169
prompt,
194-
model,
195170
effort: conversationEffort.get(conversationId),
196171
cwd: runCwd,
197172
backend: options.backend ?? backendName,

packages/core/src/session-control/__tests__/controller.test.ts

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import {
1212
conversationBackend,
1313
conversationEffort,
14-
conversationModels,
1514
conversationSessions,
1615
pendingBackendChanges,
1716
threadContinuationSnapshots,
@@ -65,7 +64,6 @@ describe('session control controller', () => {
6564
resetConversationRuntimeForTests();
6665
conversationBackend.clear();
6766
conversationEffort.clear();
68-
conversationModels.clear();
6967
conversationSessions.clear();
7068
pendingBackendChanges.clear();
7169
threadSessionBindings.clear();
@@ -226,7 +224,6 @@ describe('session control controller', () => {
226224
conversationBackend.set('conv-confirm', 'claude');
227225
conversationSessions.set('conv-confirm', 'session-2');
228226
pendingBackendChanges.set('conv-confirm', 'codex');
229-
conversationModels.set('conv-confirm', 'sonnet');
230227

231228
expect(applySessionControlCommand({
232229
conversationId: 'conv-confirm',
@@ -243,50 +240,10 @@ describe('session control controller', () => {
243240
backend: 'codex',
244241
});
245242
expect(conversationBackend.get('conv-confirm')).toBe('codex');
246-
expect(conversationModels.has('conv-confirm')).toBe(false);
247243
expect(conversationSessions.has('conv-confirm')).toBe(false);
248244
expect(pendingBackendChanges.has('conv-confirm')).toBe(false);
249245
});
250246

251-
it('clears the previous model when switching to a backend with no supported-model list', () => {
252-
conversationBackend.set('conv-empty-models', 'claude');
253-
conversationModels.set('conv-empty-models', 'sonnet');
254-
255-
expect(applySessionControlCommand({
256-
conversationId: 'conv-empty-models',
257-
type: 'backend',
258-
value: 'opencode',
259-
})).toEqual({
260-
kind: 'backend',
261-
conversationId: 'conv-empty-models',
262-
stateChanged: true,
263-
persist: false,
264-
clearContinuation: false,
265-
requiresConfirmation: true,
266-
summaryKey: 'backend.confirm',
267-
currentBackend: 'claude',
268-
requestedBackend: 'opencode',
269-
});
270-
271-
pendingBackendChanges.set('conv-empty-models', 'opencode');
272-
273-
expect(applySessionControlCommand({
274-
conversationId: 'conv-empty-models',
275-
type: 'confirm-backend',
276-
value: 'opencode',
277-
})).toEqual({
278-
kind: 'confirm-backend',
279-
conversationId: 'conv-empty-models',
280-
backend: 'opencode',
281-
stateChanged: true,
282-
persist: true,
283-
clearContinuation: false,
284-
requiresConfirmation: false,
285-
summaryKey: 'backend.updated',
286-
});
287-
expect(conversationModels.has('conv-empty-models')).toBe(false);
288-
});
289-
290247
it('cancels a pending backend switch without persisting', () => {
291248
pendingBackendChanges.set('conv-cancel', 'codex');
292249

@@ -305,23 +262,7 @@ describe('session control controller', () => {
305262
expect(pendingBackendChanges.has('conv-cancel')).toBe(false);
306263
});
307264

308-
it('updates model and effort with persistence effects', () => {
309-
expect(applySessionControlCommand({
310-
conversationId: 'conv-settings',
311-
type: 'model',
312-
value: 'claude-3-7',
313-
})).toEqual({
314-
kind: 'model',
315-
conversationId: 'conv-settings',
316-
stateChanged: true,
317-
persist: true,
318-
clearContinuation: false,
319-
requiresConfirmation: false,
320-
summaryKey: 'model.updated',
321-
value: 'claude-3-7',
322-
});
323-
expect(conversationModels.get('conv-settings')).toBe('claude-3-7');
324-
265+
it('updates effort with persistence effects', () => {
325266
expect(applySessionControlCommand({
326267
conversationId: 'conv-settings',
327268
type: 'effort',

packages/core/src/session-control/controller.ts

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { isBackendModelSupported } from '../agent/backend.js';
21
import { interruptConversationRun } from '../agent/runtime.js';
32
import { closeThreadSession } from '../thread-session/manager.js';
43
import {
54
conversationBackend,
65
conversationEffort,
7-
conversationModels,
86
pendingBackendChanges,
97
} from '../state.js';
108
import type { SessionControlCommand, SessionControlResult } from './types.js';
@@ -23,23 +21,6 @@ function updateStringMap(
2321
return { stateChanged: true, persist: true };
2422
}
2523

26-
function clearModelIfUnsupported(
27-
conversationId: string,
28-
backend: string,
29-
): { cleared: boolean } {
30-
const model = conversationModels.get(conversationId);
31-
if (!model) {
32-
return { cleared: false };
33-
}
34-
35-
if (isBackendModelSupported(backend, model)) {
36-
return { cleared: false };
37-
}
38-
39-
conversationModels.delete(conversationId);
40-
return { cleared: true };
41-
}
42-
4324
export function applySessionControlCommand(command: SessionControlCommand): SessionControlResult {
4425
if (command.type === 'interrupt') {
4526
const interrupted = interruptConversationRun(command.conversationId);
@@ -90,13 +71,12 @@ export function applySessionControlCommand(command: SessionControlCommand): Sess
9071
const hadPendingChange = pendingBackendChanges.delete(command.conversationId);
9172
const backendChanged = currentBackend !== command.value;
9273
conversationBackend.set(command.conversationId, command.value);
93-
const { cleared } = clearModelIfUnsupported(command.conversationId, command.value);
9474

9575
return {
9676
kind: 'backend',
9777
conversationId: command.conversationId,
98-
stateChanged: backendChanged || hadPendingChange || cleared,
99-
persist: backendChanged || cleared,
78+
stateChanged: backendChanged || hadPendingChange,
79+
persist: backendChanged,
10080
clearContinuation: false,
10181
requiresConfirmation: false,
10282
summaryKey: 'backend.updated',
@@ -113,14 +93,13 @@ export function applySessionControlCommand(command: SessionControlCommand): Sess
11393
const cleared = closeThreadSession({ conversationId: command.conversationId });
11494
const clearContinuation = cleared.bindingCleared || cleared.snapshotCleared || cleared.sessionCleared;
11595
conversationBackend.set(command.conversationId, backend);
116-
const { cleared: modelCleared } = clearModelIfUnsupported(command.conversationId, backend);
11796

11897
return {
11998
kind: 'confirm-backend',
12099
conversationId: command.conversationId,
121100
backend,
122-
stateChanged: hadPendingChange || backendChanged || clearContinuation || modelCleared,
123-
persist: hadPendingChange || backendChanged || clearContinuation || modelCleared,
101+
stateChanged: hadPendingChange || backendChanged || clearContinuation,
102+
persist: hadPendingChange || backendChanged || clearContinuation,
124103
clearContinuation,
125104
requiresConfirmation: false,
126105
summaryKey: 'backend.updated',
@@ -140,24 +119,6 @@ export function applySessionControlCommand(command: SessionControlCommand): Sess
140119
};
141120
}
142121

143-
if (command.type === 'model') {
144-
const { stateChanged, persist } = updateStringMap(
145-
conversationModels,
146-
command.conversationId,
147-
command.value,
148-
);
149-
return {
150-
kind: 'model',
151-
conversationId: command.conversationId,
152-
value: command.value,
153-
stateChanged,
154-
persist,
155-
clearContinuation: false,
156-
requiresConfirmation: false,
157-
summaryKey: stateChanged ? 'model.updated' : 'model.noop',
158-
};
159-
}
160-
161122
const { stateChanged, persist } = updateStringMap(
162123
conversationEffort,
163124
command.conversationId,

packages/core/src/session-control/types.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export type SessionControlCommand =
66
| { conversationId: string; type: 'backend'; value: BackendName }
77
| { conversationId: string; type: 'confirm-backend'; value: BackendName }
88
| { conversationId: string; type: 'cancel-backend' }
9-
| { conversationId: string; type: 'model'; value: string }
109
| { conversationId: string; type: 'effort'; value: string };
1110

1211
type SessionControlResultBase = {
@@ -24,8 +23,6 @@ type SessionControlResultBase = {
2423
| 'backend.updated'
2524
| 'backend.cancelled'
2625
| 'backend.cancelled-noop'
27-
| 'model.updated'
28-
| 'model.noop'
2926
| 'effort.updated'
3027
| 'effort.noop';
3128
};
@@ -51,10 +48,6 @@ export type SessionControlResult =
5148
| (SessionControlResultBase & {
5249
kind: 'cancel-backend';
5350
})
54-
| (SessionControlResultBase & {
55-
kind: 'model';
56-
value: string;
57-
})
5851
| (SessionControlResultBase & {
5952
kind: 'effort';
6053
value: string;

0 commit comments

Comments
 (0)