Skip to content

Commit 4d39b5a

Browse files
committed
🤖 Add Haiku 4-5 support and centralize default model logic
- Add claude-haiku-4-5 to MODEL_ABBREVIATIONS with 'haiku' shortcut - Add pricing/config for Haiku 4-5 to models-extra.ts ($1/$5 per million tokens) - Reorder MODEL_ABBREVIATIONS to put sonnet first (becomes default for new chats) - Centralize default model logic: created getDefaultModelFromLRU() in useModelLRU.ts - Remove all imports of defaultModel except in models.ts and useModelLRU.ts - Update all code paths to use LRU for default model selection: - useSendMessageOptions hook - getSendOptionsFromStorage (non-hook) - useAIViewKeybinds - Debug scripts (costs, agentSessionCli, send-message) - Update models.json with latest model pricing data This makes the system less prescriptive - the most recently used model becomes the default for new chats, creating a natural user-driven flow.
1 parent 16ca4e5 commit 4d39b5a

File tree

10 files changed

+23284
-19
lines changed

10 files changed

+23284
-19
lines changed

src/debug/agentSessionCli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
type SendMessageOptions,
2323
type WorkspaceChatMessage,
2424
} from "@/types/ipc";
25-
import { defaultModel } from "@/utils/ai/models";
25+
import { getDefaultModelFromLRU } from "@/hooks/useModelLRU";
2626
import { ensureProvidersConfig } from "@/utils/providers/ensureProvidersConfig";
2727
import { modeToToolPolicy, PLAN_MODE_INSTRUCTION } from "@/utils/ui/modeUtils";
2828
import { extractAssistantText, extractReasoning, extractToolCalls } from "@/debug/chatExtractors";
@@ -184,7 +184,7 @@ async function main(): Promise<void> {
184184
throw new Error("Message must be provided via --message or stdin");
185185
}
186186

187-
const model = values.model && values.model.trim().length > 0 ? values.model.trim() : defaultModel;
187+
const model = values.model && values.model.trim().length > 0 ? values.model.trim() : getDefaultModelFromLRU();
188188
const timeoutMs = parseTimeout(values.timeout);
189189
const thinkingLevel = parseThinkingLevel(values["thinking-level"]);
190190
const initialMode = parseMode(values.mode);

src/debug/costs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from "path";
33
import { defaultConfig } from "@/config";
44
import type { CmuxMessage } from "@/types/message";
55
import { calculateTokenStats } from "@/utils/tokens/tokenStatsCalculator";
6-
import { defaultModel } from "@/utils/ai/models";
6+
import { getDefaultModelFromLRU } from "@/hooks/useModelLRU";
77

88
/**
99
* Debug command to display cost/token statistics for a workspace
@@ -35,7 +35,7 @@ export function costsCommand(workspaceId: string) {
3535

3636
// Detect model from first assistant message
3737
const firstAssistantMessage = messages.find((msg) => msg.role === "assistant");
38-
const model = firstAssistantMessage?.metadata?.model ?? defaultModel;
38+
const model = firstAssistantMessage?.metadata?.model ?? getDefaultModelFromLRU();
3939

4040
// Calculate stats using shared logic (now synchronous)
4141
const stats = calculateTokenStats(messages, model);

src/debug/send-message.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from "path";
33
import { defaultConfig } from "@/config";
44
import type { CmuxMessage } from "@/types/message";
55
import type { SendMessageOptions } from "@/types/ipc";
6-
import { defaultModel } from "@/utils/ai/models";
6+
import { getDefaultModelFromLRU } from "@/hooks/useModelLRU";
77

88
/**
99
* Debug command to send a message to a workspace, optionally editing an existing message
@@ -103,7 +103,7 @@ export function sendMessageCommand(
103103

104104
// Prepare options
105105
const options: SendMessageOptions = {
106-
model: defaultModel,
106+
model: getDefaultModelFromLRU(),
107107
};
108108

109109
if (editMessageId) {

src/hooks/useAIViewKeybinds.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { updatePersistedState, readPersistedState } from "@/hooks/usePersistedSt
66
import type { ThinkingLevel, ThinkingLevelOn } from "@/types/thinking";
77
import { DEFAULT_THINKING_LEVEL } from "@/types/thinking";
88
import { getThinkingPolicyForModel } from "@/utils/thinking/policy";
9-
import { defaultModel } from "@/utils/ai/models";
9+
import { getDefaultModelFromLRU } from "@/hooks/useModelLRU";
1010

1111
interface UseAIViewKeybindsParams {
1212
workspaceId: string;
@@ -66,10 +66,10 @@ export function useAIViewKeybinds({
6666
e.preventDefault();
6767

6868
// Get selected model from localStorage (what user sees in UI)
69-
// Fall back to message history model, then to default model
69+
// Fall back to message history model, then to most recent model from LRU
7070
// This matches the same logic as useSendMessageOptions
7171
const selectedModel = readPersistedState<string | null>(getModelKey(workspaceId), null);
72-
const modelToUse = selectedModel ?? currentModel ?? defaultModel;
72+
const modelToUse = selectedModel ?? currentModel ?? getDefaultModelFromLRU();
7373

7474
// Storage key for remembering this model's last-used active thinking level
7575
const lastThinkingKey = getLastThinkingByModelKey(modelToUse);

src/hooks/useModelLRU.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { useCallback, useEffect } from "react";
2-
import { usePersistedState } from "./usePersistedState";
2+
import { usePersistedState, readPersistedState } from "./usePersistedState";
33
import { MODEL_ABBREVIATIONS } from "@/utils/slashCommands/registry";
4+
import { defaultModel } from "@/utils/ai/models";
45

56
const MAX_LRU_SIZE = 8;
67
const LRU_KEY = "model-lru";
78

89
// Default models from abbreviations (for initial LRU population)
910
const DEFAULT_MODELS = Object.values(MODEL_ABBREVIATIONS);
1011

12+
/**
13+
* Get the default model from LRU (non-hook version for use outside React)
14+
* This is the ONLY place that reads from LRU outside of the hook.
15+
*
16+
* @returns The most recently used model, or defaultModel if LRU is empty
17+
*/
18+
export function getDefaultModelFromLRU(): string {
19+
const lru = readPersistedState<string[]>(LRU_KEY, []);
20+
return lru[0] ?? defaultModel;
21+
}
22+
1123
/**
1224
* Hook to manage a Least Recently Used (LRU) cache of AI models.
1325
* Stores up to 8 recently used models in localStorage.

src/hooks/useSendMessageOptions.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { use1MContext } from "./use1MContext";
22
import { useThinkingLevel } from "./useThinkingLevel";
33
import { useMode } from "@/contexts/ModeContext";
44
import { usePersistedState } from "./usePersistedState";
5+
import { useModelLRU } from "./useModelLRU";
56
import { modeToToolPolicy, PLAN_MODE_INSTRUCTION } from "@/utils/ui/modeUtils";
6-
import { defaultModel } from "@/utils/ai/models";
77
import { getModelKey } from "@/constants/storage";
88
import type { SendMessageOptions } from "@/types/ipc";
99
import type { UIMode } from "@/types/mode";
@@ -19,13 +19,14 @@ function constructSendMessageOptions(
1919
mode: UIMode,
2020
thinkingLevel: ThinkingLevel,
2121
preferredModel: string | null | undefined,
22-
use1M: boolean
22+
use1M: boolean,
23+
fallbackModel: string
2324
): SendMessageOptions {
2425
const additionalSystemInstructions = mode === "plan" ? PLAN_MODE_INSTRUCTION : undefined;
2526

2627
// Ensure model is always a valid string (defensive against corrupted localStorage)
2728
const model =
28-
typeof preferredModel === "string" && preferredModel ? preferredModel : defaultModel;
29+
typeof preferredModel === "string" && preferredModel ? preferredModel : fallbackModel;
2930

3031
// Enforce thinking policy at the UI boundary as well (e.g., gpt-5-pro → high only)
3132
const uiThinking = enforceThinkingPolicy(model, thinkingLevel);
@@ -58,13 +59,14 @@ export function useSendMessageOptions(workspaceId: string): SendMessageOptions {
5859
const [use1M] = use1MContext();
5960
const [thinkingLevel] = useThinkingLevel();
6061
const [mode] = useMode();
62+
const { recentModels } = useModelLRU();
6163
const [preferredModel] = usePersistedState<string>(
6264
getModelKey(workspaceId),
63-
defaultModel,
65+
recentModels[0], // Most recently used model (LRU is never empty)
6466
{ listener: true } // Listen for changes from ModelSelector and other sources
6567
);
6668

67-
return constructSendMessageOptions(mode, thinkingLevel, preferredModel, use1M);
69+
return constructSendMessageOptions(mode, thinkingLevel, preferredModel, use1M, recentModels[0]);
6870
}
6971

7072
/**

src/utils/messages/sendOptions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import {
44
getModeKey,
55
USE_1M_CONTEXT_KEY,
66
} from "@/constants/storage";
7-
import { defaultModel } from "@/utils/ai/models";
87
import { modeToToolPolicy, PLAN_MODE_INSTRUCTION } from "@/utils/ui/modeUtils";
98
import { readPersistedState } from "@/hooks/usePersistedState";
109
import type { SendMessageOptions } from "@/types/ipc";
1110
import type { UIMode } from "@/types/mode";
1211
import type { ThinkingLevel } from "@/types/thinking";
1312
import { enforceThinkingPolicy } from "@/utils/thinking/policy";
13+
import { getDefaultModelFromLRU } from "@/hooks/useModelLRU";
1414

1515
/**
1616
* Get send options from localStorage
@@ -20,8 +20,8 @@ import { enforceThinkingPolicy } from "@/utils/thinking/policy";
2020
* This ensures DRY - single source of truth for option extraction.
2121
*/
2222
export function getSendOptionsFromStorage(workspaceId: string): SendMessageOptions {
23-
// Read model preference (workspace-specific)
24-
const model = readPersistedState<string>(getModelKey(workspaceId), defaultModel);
23+
// Read model preference (workspace-specific), fallback to most recent from LRU
24+
const model = readPersistedState<string>(getModelKey(workspaceId), getDefaultModelFromLRU());
2525

2626
// Read thinking level (workspace-specific)
2727
const thinkingLevel = readPersistedState<ThinkingLevel>(

0 commit comments

Comments
 (0)