diff --git a/src/agent/model.ts b/src/agent/model.ts index b3788f0..f8433bd 100644 --- a/src/agent/model.ts +++ b/src/agent/model.ts @@ -69,6 +69,16 @@ export function getModelUpdateArgs( return modelInfo?.updateArgs; } +/** + * Get a display-friendly name for a model by its handle + * @param handle - The full model handle (e.g., "anthropic/claude-sonnet-4-5-20250929") + * @returns The display name (e.g., "Sonnet 4.5") if found, null otherwise + */ +export function getModelDisplayName(handle: string): string | null { + const model = models.find((m) => m.handle === handle); + return model?.label ?? null; +} + /** * Resolve a model ID from the llm_config.model value * The llm_config.model is the model portion without the provider prefix diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 0c10930..de26186 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -24,6 +24,7 @@ import { getClient } from "../agent/client"; import { getCurrentAgentId, setCurrentAgentId } from "../agent/context"; import { type AgentProvenance, createAgent } from "../agent/create"; import { sendMessageStream } from "../agent/message"; +import { getModelDisplayName, getModelInfo } from "../agent/model"; import { SessionStats } from "../agent/stats"; import type { ApprovalContext } from "../permissions/analyzer"; import { type PermissionMode, permissionMode } from "../permissions/mode"; @@ -527,7 +528,10 @@ export default function App({ llmConfig?.model_endpoint_type && llmConfig?.model ? `${llmConfig.model_endpoint_type}/${llmConfig.model}` : (llmConfig?.model ?? null); - const currentModelDisplay = currentModelLabel?.split("/").pop() ?? null; + const currentModelDisplay = currentModelLabel + ? (getModelDisplayName(currentModelLabel) ?? + currentModelLabel.split("/").pop()) + : null; const currentModelProvider = llmConfig?.provider_name ?? null; // Token streaming preference (can be toggled at runtime) @@ -885,6 +889,18 @@ export default function App({ .last_run_completion; setAgentLastRunAt(lastRunCompletion ?? null); + // Derive model ID from llm_config for ModelSelector + const agentModelHandle = + agent.llm_config.model_endpoint_type && agent.llm_config.model + ? `${agent.llm_config.model_endpoint_type}/${agent.llm_config.model}` + : agent.llm_config.model; + const modelInfo = getModelInfo(agentModelHandle || ""); + if (modelInfo) { + setCurrentModelId(modelInfo.id); + } else { + setCurrentModelId(agentModelHandle || null); + } + // Detect current toolset from attached tools const { detectToolsetFromAgent } = await import("../tools/toolset"); const detected = await detectToolsetFromAgent(client, agentId); diff --git a/src/cli/commands/registry.ts b/src/cli/commands/registry.ts index b9e7c2d..08165f5 100644 --- a/src/cli/commands/registry.ts +++ b/src/cli/commands/registry.ts @@ -68,7 +68,7 @@ export const commands: Record = { }, }, "/clear": { - desc: "Clear conversation history", + desc: "Clear conversation history (keep memory)", order: 17, handler: () => { // Handled specially in App.tsx to access client and agent ID diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index d2063ec..7157981 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -15,7 +15,6 @@ import type { PermissionMode } from "../../permissions/mode"; import { permissionMode } from "../../permissions/mode"; import { ANTHROPIC_PROVIDER_NAME } from "../../providers/anthropic-provider"; import { settingsManager } from "../../settings-manager"; -import { getVersion } from "../../version"; import { charsToTokens, formatCompact } from "../helpers/format"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; @@ -26,7 +25,6 @@ import { ShimmerText } from "./ShimmerText"; // Type assertion for ink-spinner compatibility const Spinner = SpinnerLib as ComponentType<{ type?: string }>; -const appVersion = getVersion(); // Window for double-escape to clear input const ESC_CLEAR_WINDOW_MS = 2500; @@ -708,11 +706,22 @@ export function Input({ ) : ( - Press / for commands or @ for files + Press / for commands )} - - {`Letta Code v${appVersion} `} - {`[${currentModel ?? "unknown"}${currentModelProvider === ANTHROPIC_PROVIDER_NAME ? ` ${chalk.rgb(255, 199, 135)("claude pro/max")}` : ""}]`} + + + {agentName || "Unnamed"} + + + {` [${currentModel ?? "unknown"}]`} + diff --git a/src/cli/components/ModelSelector.tsx b/src/cli/components/ModelSelector.tsx index 58888de..700f68e 100644 --- a/src/cli/components/ModelSelector.tsx +++ b/src/cli/components/ModelSelector.tsx @@ -104,10 +104,16 @@ export function ModelSelector({ ); // Supported models: models.json entries that are available + // Featured models first, then non-featured, preserving JSON order within each group const supportedModels = useMemo(() => { if (availableHandles === undefined) return []; - if (availableHandles === null) return typedModels; // fallback - return typedModels.filter((m) => availableHandles.has(m.handle)); + const available = + availableHandles === null + ? typedModels // fallback + : typedModels.filter((m) => availableHandles.has(m.handle)); + const featured = available.filter((m) => m.isFeatured); + const nonFeatured = available.filter((m) => !m.isFeatured); + return [...featured, ...nonFeatured]; }, [typedModels, availableHandles]); // All other models: API handles not in models.json @@ -258,7 +264,7 @@ export function ModelSelector({ ); const getCategoryLabel = (cat: ModelCategory) => { - if (cat === "supported") return `Supported (${supportedModels.length})`; + if (cat === "supported") return `Recommended (${supportedModels.length})`; return `All Available Models (${otherModelHandles.length})`; }; @@ -277,6 +283,7 @@ export function ModelSelector({ {i > 0 && · } {model.label} - {isCurrent && ( - (current) - )} + {isCurrent && (current)} {model.description && ( {model.description} diff --git a/src/cli/components/SlashCommandAutocomplete.tsx b/src/cli/components/SlashCommandAutocomplete.tsx index b448a19..f1509c4 100644 --- a/src/cli/components/SlashCommandAutocomplete.tsx +++ b/src/cli/components/SlashCommandAutocomplete.tsx @@ -2,6 +2,7 @@ import { Text } from "ink"; import Link from "ink-link"; import { useEffect, useMemo, useState } from "react"; import { settingsManager } from "../../settings-manager"; +import { getVersion } from "../../version"; import { commands } from "../commands/registry"; import { useAutocompleteNavigation } from "../hooks/useAutocompleteNavigation"; import { AutocompleteBox, AutocompleteItem } from "./Autocomplete"; @@ -209,6 +210,7 @@ export function SlashCommandAutocomplete({ join our Discord ↗ + Version: Letta Code v{getVersion()} ); } diff --git a/src/cli/components/colors.ts b/src/cli/components/colors.ts index 6923477..76ddb5f 100644 --- a/src/cli/components/colors.ts +++ b/src/cli/components/colors.ts @@ -163,4 +163,9 @@ export const colors = { dim: "gray", bold: "white", }, + + // Footer bar + footer: { + agentName: brandColors.primaryAccent, + }, } as const; diff --git a/src/models.json b/src/models.json index fc17c06..2597725 100644 --- a/src/models.json +++ b/src/models.json @@ -2,8 +2,8 @@ { "id": "sonnet-4.5", "handle": "anthropic/claude-sonnet-4-5-20250929", - "label": "Claude Sonnet 4.5 (default)", - "description": "The recommended default model (currently Sonnet 4.5)", + "label": "Sonnet 4.5", + "description": "The recommended default model", "isDefault": true, "isFeatured": true, "updateArgs": { @@ -15,8 +15,8 @@ { "id": "sonnet-4.5-no-reasoning", "handle": "anthropic/claude-sonnet-4-5-20250929", - "label": "Claude Sonnet 4.5 (no reasoning)", - "description": "Sonnet 4.5 with extended thinking/reasoning explicitly disabled", + "label": "Sonnet 4.5", + "description": "Sonnet 4.5 with no reasoning (faster)", "updateArgs": { "enable_reasoner": false, "context_window": 180000, @@ -26,8 +26,8 @@ { "id": "opus", "handle": "anthropic/claude-opus-4-5-20251101", - "label": "Claude Opus 4.5", - "description": "Anthropic's newest flagship Opus 4.5 model", + "label": "Opus 4.5", + "description": "Anthropic's best model", "isFeatured": true, "updateArgs": { "context_window": 180000, @@ -38,7 +38,7 @@ { "id": "haiku", "handle": "anthropic/claude-haiku-4-5-20251001", - "label": "Claude Haiku 4.5", + "label": "Haiku 4.5", "description": "Anthropic's fastest model", "isFeatured": true, "updateArgs": { @@ -49,7 +49,7 @@ { "id": "sonnet-4.5-pro-max", "handle": "claude-pro-max/claude-sonnet-4-5-20250929", - "label": "Claude Sonnet 4.5 (Pro/Max)", + "label": "Sonnet 4.5", "description": "Sonnet 4.5 via Claude Pro/Max Plan", "updateArgs": { "context_window": 180000, @@ -60,8 +60,8 @@ { "id": "sonnet-4.5-no-reasoning-pro-max", "handle": "claude-pro-max/claude-sonnet-4-5-20250929", - "label": "Claude Sonnet 4.5 (Pro/Max, no reasoning)", - "description": "Sonnet 4.5 via Claude Pro/Max Plan with reasoning disabled", + "label": "Sonnet 4.5", + "description": "Sonnet 4.5 (no reasoning) via Claude Pro/Max Plan", "updateArgs": { "enable_reasoner": false, "context_window": 180000, @@ -71,7 +71,7 @@ { "id": "opus-pro-max", "handle": "claude-pro-max/claude-opus-4-5-20251101", - "label": "Claude Opus 4.5 (Pro/Max)", + "label": "Opus 4.5", "description": "Opus 4.5 via Claude Pro/Max Plan", "updateArgs": { "context_window": 180000, @@ -82,7 +82,7 @@ { "id": "haiku-pro-max", "handle": "claude-pro-max/claude-haiku-4-5-20251001", - "label": "Claude Haiku 4.5 (Pro/Max)", + "label": "Haiku 4.5", "description": "Haiku 4.5 via Claude Pro/Max Plan", "updateArgs": { "context_window": 180000, @@ -93,7 +93,7 @@ "id": "gpt-5-codex", "handle": "openai/gpt-5-codex", "label": "GPT-5-Codex", - "description": "A variant of GPT-5 optimized for agentic coding", + "description": "GPT-5 variant (med reasoning) optimized for coding", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -104,8 +104,8 @@ { "id": "gpt-5.2-none", "handle": "openai/gpt-5.2", - "label": "GPT-5.2 (none)", - "description": "OpenAI's latest model (no reasoning, fastest GPT-5.2 option)", + "label": "GPT-5.2", + "description": "Latest general-purpose GPT (no reasoning)", "updateArgs": { "reasoning_effort": "none", "verbosity": "medium", @@ -116,8 +116,8 @@ { "id": "gpt-5.2-low", "handle": "openai/gpt-5.2", - "label": "GPT-5.2 (low)", - "description": "OpenAI's latest model (some reasoning enabled)", + "label": "GPT-5.2", + "description": "Latest general-purpose GPT (low reasoning)", "updateArgs": { "reasoning_effort": "low", "verbosity": "medium", @@ -128,8 +128,8 @@ { "id": "gpt-5.2-medium", "handle": "openai/gpt-5.2", - "label": "GPT-5.2 (medium)", - "description": "OpenAI's latest model (using their recommended reasoning level)", + "label": "GPT-5.2", + "description": "Latest general-purpose GPT (med reasoning)", "isFeatured": true, "updateArgs": { "reasoning_effort": "medium", @@ -141,8 +141,8 @@ { "id": "gpt-5.2-high", "handle": "openai/gpt-5.2", - "label": "GPT-5.2 (high)", - "description": "OpenAI's latest model (high reasoning effort)", + "label": "GPT-5.2", + "description": "Latest general-purpose GPT (high reasoning)", "updateArgs": { "reasoning_effort": "high", "verbosity": "medium", @@ -153,8 +153,8 @@ { "id": "gpt-5.2-xhigh", "handle": "openai/gpt-5.2", - "label": "GPT-5.2 (extra-high)", - "description": "OpenAI's latest model (maximum reasoning depth)", + "label": "GPT-5.2", + "description": "Latest general-purpose GPT (max reasoning)", "updateArgs": { "reasoning_effort": "xhigh", "verbosity": "medium", @@ -165,8 +165,8 @@ { "id": "gpt-5.1-none", "handle": "openai/gpt-5.1", - "label": "GPT-5.1 (none)", - "description": "OpenAI's GPT-5.1 model (no reasoning, fastest GPT-5.1 option)", + "label": "GPT-5.1", + "description": "Legacy GPT-5.1 (no reasoning)", "updateArgs": { "reasoning_effort": "none", "verbosity": "medium", @@ -177,8 +177,8 @@ { "id": "gpt-5.1-low", "handle": "openai/gpt-5.1", - "label": "GPT-5.1 (low)", - "description": "OpenAI's GPT-5.1 model (some reasoning enabled)", + "label": "GPT-5.1", + "description": "Legacy GPT-5.1 (low reasoning)", "updateArgs": { "reasoning_effort": "low", "verbosity": "medium", @@ -189,8 +189,8 @@ { "id": "gpt-5.1-medium", "handle": "openai/gpt-5.1", - "label": "GPT-5.1 (medium)", - "description": "OpenAI's GPT-5.1 model (using their recommended reasoning level)", + "label": "GPT-5.1", + "description": "Legacy GPT-5.1 (med reasoning)", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -201,8 +201,8 @@ { "id": "gpt-5.1-high", "handle": "openai/gpt-5.1", - "label": "GPT-5.1 (high)", - "description": "OpenAI's GPT-5.1 model (maximum reasoning depth)", + "label": "GPT-5.1", + "description": "Legacy GPT-5.1 (high reasoning)", "updateArgs": { "reasoning_effort": "high", "verbosity": "medium", @@ -213,8 +213,8 @@ { "id": "gpt-5.1-codex-none", "handle": "openai/gpt-5.1-codex", - "label": "GPT-5.1-Codex (none)", - "description": "GPT-5.1-Codex with no reasoning (fastest Codex option)", + "label": "GPT-5.1-Codex", + "description": "GPT-5.1 variant (no reasoning) optimized for coding", "updateArgs": { "reasoning_effort": "none", "verbosity": "medium", @@ -225,8 +225,8 @@ { "id": "gpt-5.1-codex-medium", "handle": "openai/gpt-5.1-codex", - "label": "GPT-5.1-Codex (medium)", - "description": "GPT-5.1-Codex with recommended reasoning level", + "label": "GPT-5.1-Codex", + "description": "GPT-5.1 variant (med reasoning) optimized for coding", "isFeatured": true, "updateArgs": { "reasoning_effort": "medium", @@ -238,8 +238,8 @@ { "id": "gpt-5.1-codex-high", "handle": "openai/gpt-5.1-codex", - "label": "GPT-5.1-Codex (high)", - "description": "GPT-5.1-Codex with maximum reasoning depth", + "label": "GPT-5.1-Codex", + "description": "GPT-5.1 variant (max reasoning) optimized for coding", "updateArgs": { "reasoning_effort": "high", "verbosity": "medium", @@ -250,8 +250,8 @@ { "id": "gpt-5.1-codex-max-medium", "handle": "openai/gpt-5.1-codex-max", - "label": "GPT-5.1-Codex (max-medium)", - "description": "GPT-5.1-Codex with maximum capabilities enabled", + "label": "GPT-5.1-Codex-Max", + "description": "GPT-5.1-Codex 'Max' variant (med reasoning)", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -262,8 +262,8 @@ { "id": "gpt-5.1-codex-max-high", "handle": "openai/gpt-5.1-codex-max", - "label": "GPT-5.1-Codex (max-high)", - "description": "GPT-5.1-Codex with maximum capabilities enabled", + "label": "GPT-5.1-Codex-Max", + "description": "GPT-5.1-Codex 'Max' variant (high reasoning)", "updateArgs": { "reasoning_effort": "high", "verbosity": "medium", @@ -274,8 +274,8 @@ { "id": "gpt-5.1-codex-max-x-high", "handle": "openai/gpt-5.1-codex-max", - "label": "GPT-5.1-Codex (max-x-high)", - "description": "GPT-5.1-Codex with maximum capabilities enabled", + "label": "GPT-5.1-Codex-Max", + "description": "GPT-5.1-Codex 'Max' variant (extra-high reasoning)", "updateArgs": { "reasoning_effort": "xhigh", "verbosity": "medium", @@ -286,8 +286,8 @@ { "id": "gpt-5-minimal", "handle": "openai/gpt-5", - "label": "GPT-5 (minimal)", - "description": "OpenAI's latest model (limited reasoning, fastest GPT-5 option)", + "label": "GPT-5", + "description": "Legacy GPT-5 (minimal reasoning)", "updateArgs": { "reasoning_effort": "minimal", "verbosity": "medium", @@ -297,8 +297,8 @@ { "id": "gpt-5-low", "handle": "openai/gpt-5", - "label": "GPT-5 (low)", - "description": "OpenAI's latest model (some reasoning enabled)", + "label": "GPT-5", + "description": "Legacy GPT-5 (low reasoning)", "updateArgs": { "reasoning_effort": "low", "verbosity": "medium", @@ -308,8 +308,8 @@ { "id": "gpt-5-medium", "handle": "openai/gpt-5", - "label": "GPT-5 (medium)", - "description": "OpenAI's latest model (using their recommended reasoning level)", + "label": "GPT-5", + "description": "Legacy GPT-5 (med reasoning)", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -319,8 +319,8 @@ { "id": "gpt-5-high", "handle": "openai/gpt-5", - "label": "GPT-5 (high)", - "description": "OpenAI's latest model (maximum reasoning depth)", + "label": "GPT-5", + "description": "Legacy GPT-5 (high reasoning)", "updateArgs": { "reasoning_effort": "high", "verbosity": "medium", @@ -330,8 +330,8 @@ { "id": "gpt-5-mini-medium", "handle": "openai/gpt-5-mini-2025-08-07", - "label": "GPT-5-Mini (medium)", - "description": "OpenAI's latest mini model (using their recommended reasoning level)", + "label": "GPT-5-Mini", + "description": "GPT-5-Mini (medium reasoning)", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -341,8 +341,8 @@ { "id": "gpt-5-nano-medium", "handle": "openai/gpt-5-nano-2025-08-07", - "label": "GPT-5-Nano (medium)", - "description": "OpenAI's latest nano model (using their recommended reasoning level)", + "label": "GPT-5-Nano", + "description": "GPT-5-Nano (medium reasoning)", "updateArgs": { "reasoning_effort": "medium", "verbosity": "medium", @@ -353,7 +353,7 @@ "id": "glm-4.6", "handle": "zai/glm-4.6", "label": "GLM-4.6", - "description": "Zai's GLM-4.6 model", + "description": "Zai's legacy model", "updateArgs": { "context_window": 200000 } @@ -467,8 +467,8 @@ { "id": "gemini-3-vertex", "handle": "google_vertex/gemini-3-pro-preview", - "label": "Gemini 3 Pro (Vertex AI)", - "description": "Google's smartest Gemini 3 Pro model on Vertex AI", + "label": "Gemini 3 Pro", + "description": "Google's smartest Gemini 3 Pro model (via Vertex AI)", "updateArgs": { "context_window": 180000, "temperature": 1.0 } } ]