Skip to content
Merged
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
23 changes: 15 additions & 8 deletions src/main/lib/trpc/routers/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1531,13 +1531,18 @@ export const claudeRouter = router({
}

const rawResolvedModel = finalCustomConfig?.model || input.model
// Opus 1M: the UI exposes `opus[1m]` as a distinct model, but the
// Claude CLI only understands the `opus` shortcut. To opt into the
// 1M context window we strip the `[1m]` suffix for the model field
// and enable the matching beta via ANTHROPIC_BETAS on the child env.
const isOpus1M = rawResolvedModel === "opus[1m]"
const resolvedModel = isOpus1M ? "opus" : rawResolvedModel
if (isOpus1M) {
// 1M context: the UI exposes `opus[1m]` and `sonnet[1m]` as
// distinct models, but the Claude CLI only understands the base
// shortcuts (`opus`, `sonnet`). Strip the `[1m]` suffix for the
// model field and enable the shared beta via ANTHROPIC_BETAS on
// the child env.
const has1MSuffix =
typeof rawResolvedModel === "string" &&
rawResolvedModel.endsWith("[1m]")
const resolvedModel = has1MSuffix
? rawResolvedModel!.slice(0, -4)
: rawResolvedModel
if (has1MSuffix) {
const envAsRecord = finalEnv as Record<string, string>
const existingBetas = envAsRecord.ANTHROPIC_BETAS
const betaSlug = "context-1m-2025-08-07"
Expand All @@ -1554,7 +1559,7 @@ export const claudeRouter = router({
: betaSlug
envAsRecord.ANTHROPIC_BETAS = merged
console.log(
`[claude] Opus 1M context enabled — ANTHROPIC_BETAS=${merged}`,
`[claude] 1M context enabled for ${resolvedModel} — ANTHROPIC_BETAS=${merged}`,
)
}

Expand Down Expand Up @@ -2327,6 +2332,7 @@ ${prompt}
rawErrorCode,
sessionId: msgAny.session_id,
messageId: msgAny.message?.id,
model: rawResolvedModel,
},
} as UIMessageChunk)
}
Expand Down Expand Up @@ -2669,6 +2675,7 @@ ${prompt}
cwd: input.cwd,
mode: input.mode,
stderr: stderrOutput || "(no stderr captured)",
model: rawResolvedModel,
},
} as UIMessageChunk)
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/lib/trpc/routers/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const AUTH_HINTS = [
"401",
"403",
]
const DEFAULT_CODEX_MODEL = "gpt-5.3-codex/high"
const DEFAULT_CODEX_MODEL = "gpt-5.4/high"
const CODEX_MCP_TOOLS_FETCH_TIMEOUT_MS = 40_000
const CODEX_USAGE_POLL_ATTEMPTS = 3
const CODEX_USAGE_POLL_INTERVAL_MS = 200
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/features/agents/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ const AVAILABLE_CLAUDE_MODEL_IDS = [
"opus",
"opus[1m]",
"sonnet",
"sonnet[1m]",
"haiku",
] as const

Expand Down Expand Up @@ -255,7 +256,7 @@ export const defaultReviewModeModelAtom = atomWithStorage<string>(

export const lastSelectedCodexModelIdAtom = atomWithStorage<string>(
"agents:lastSelectedCodexModelId",
"gpt-5.3-codex",
"gpt-5.4",
undefined,
{ getOnInit: true },
)
Expand Down Expand Up @@ -463,6 +464,7 @@ export const MODEL_ID_MAP: Record<string, string> = {
opus: "opus",
"opus[1m]": "opus[1m]",
sonnet: "sonnet",
"sonnet[1m]": "sonnet[1m]",
haiku: "haiku",
}

Expand Down
10 changes: 10 additions & 0 deletions src/renderer/features/agents/components/agent-model-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,8 @@ export function AgentModelSelector({
const selected = isItemSelected(item)
const disabled = isItemDisabled(item)
const crossProvider = isItemCrossProvider(item)
const is1M =
item.type === "claude" && item.model.id.endsWith("[1m]")
return (
<CommandItem
key={getItemKey(item)}
Expand All @@ -617,6 +619,14 @@ export function AgentModelSelector({
>
{getItemIcon(item)}
<span className="truncate flex-1">{getItemLabel(item)}</span>
{is1M && (
<span
title="1M context window — significantly higher token cost"
className="text-[10px] font-medium text-amber-600 dark:text-amber-500 shrink-0"
>
1M · higher cost
</span>
)}
{crossProvider && (
<span className="text-[10px] text-muted-foreground shrink-0">New chat</span>
)}
Expand Down
44 changes: 36 additions & 8 deletions src/renderer/features/agents/lib/ipc-chat-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
subChatClaudeThinkingAtomFamily,
subChatModelIdAtomFamily,
} from "../atoms"
import { setSubChatModel } from "./model-switching"
import { useAgentSubChatStore } from "../stores/sub-chat-store"
import type { AgentMessageMetadata } from "../ui/agent-message-usage"

Expand Down Expand Up @@ -424,16 +425,43 @@ export class IPCChatTransport implements ChatTransport<UIMessage> {
? rawDescription.slice(0, 300) + "..."
: rawDescription

// Surface a clearer fallback action when a 1M-context model
// hits a rate-limit / context error. Lets the user recover
// with one click instead of digging through model settings.
const erroredModel: string | undefined = chunk.debugInfo?.model
const is1MModel =
typeof erroredModel === "string" && erroredModel.endsWith("[1m]")
const isRateOrContextError =
category === "RATE_LIMIT" || category === "RATE_LIMIT_SDK"
const subChatId = this.config.subChatId
const offerFallback =
is1MModel && isRateOrContextError && Boolean(subChatId)
const fallbackModelId = erroredModel?.replace(/\[1m\]$/, "")

const action = offerFallback && fallbackModelId
? {
label: `Switch to ${fallbackModelId}`,
onClick: () => {
setSubChatModel(subChatId, fallbackModelId)
toast.success(`Switched to ${fallbackModelId}`)
},
}
: {
label: "Copy Error",
onClick: () => {
navigator.clipboard.writeText(errorDetails)
toast.success("Error details copied to clipboard")
},
}

const finalDescription = offerFallback
? `${description} 1M-context models share a tighter quota — try the standard 200K model.`
: description

toast.error(title, {
description,
description: finalDescription,
duration: 12000,
action: {
label: "Copy Error",
onClick: () => {
navigator.clipboard.writeText(errorDetails)
toast.success("Error details copied to clipboard")
},
},
action,
})
}

Expand Down
16 changes: 16 additions & 0 deletions src/renderer/features/agents/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export const CLAUDE_MODELS = [
version: "4.6",
thinkings: ["off", "low", "medium", "high"] as ClaudeThinkingLevel[],
},
{
id: "sonnet[1m]",
name: "Sonnet",
version: "4.6 1M",
thinkings: ["off", "low", "medium", "high"] as ClaudeThinkingLevel[],
},
{
id: "haiku",
name: "Haiku",
Expand All @@ -43,6 +49,16 @@ export function formatClaudeThinkingLabel(thinking: ClaudeThinkingLevel): string
export type CodexThinkingLevel = "low" | "medium" | "high" | "xhigh"

export const CODEX_MODELS = [
{
id: "gpt-5.4",
name: "GPT-5.4",
thinkings: ["low", "medium", "high", "xhigh"] as CodexThinkingLevel[],
},
{
id: "gpt-5.4-mini",
name: "GPT-5.4 mini",
thinkings: ["low", "medium", "high"] as CodexThinkingLevel[],
},
{
id: "gpt-5.3-codex",
name: "Codex 5.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const CONTEXT_WINDOWS = {
opus: 200_000,
"opus[1m]": 1_000_000,
sonnet: 200_000,
"sonnet[1m]": 1_000_000,
haiku: 200_000,
} as const

Expand Down