feat(tui): add provider auth status indicators to model dialog

Shows ✗ for expired and △ for expiring tokens next to provider names
in favorites, recent, and all providers sections.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Artur Do Lago
2026-01-11 01:54:24 +01:00
parent 8e5947edfa
commit d17e489f4b
2 changed files with 34 additions and 17 deletions
+11 -10
View File
@@ -126,8 +126,9 @@ Agent-core is a fork of OpenCode with three personas (Zee, Stanley, Johny) shari
- [x] MemoryStore high-level API for save/search/list
- [x] Collection management with auto-creation
- [x] Embedding generation (`src/memory/embedding.ts`)
- [x] Local BGE-M3 via vLLM (1024d)
- [x] OpenAI fallback
- [x] **Qwen3-Embedding-8B via Nebius API** (4096d, #1 on MTEB)
- [x] Voyage AI provider support
- [x] OpenAI/local fallback options
- [x] Conversation continuity across compactions (`src/personas/continuity.ts`)
- [x] ContinuityManager with session lifecycle
- [x] Key fact extraction (heuristic + LLM ready)
@@ -137,7 +138,7 @@ Agent-core is a fork of OpenCode with three personas (Zee, Stanley, Johny) shari
- [x] Namespace-based isolation
- [x] Shared Qdrant collections
- [x] Memory bridge for persona state (`src/personas/memory-bridge.ts`)
- [ ] Memory search MCP tool (not yet exposed via MCP)
- [x] Memory search MCP tool (`personas-memory` server)
### 3.2 Orchestration (Tiara) - COMPLETE
- [x] Drone spawning via Task tool (`src/personas/tiara.ts`)
@@ -158,21 +159,21 @@ Agent-core is a fork of OpenCode with three personas (Zee, Stanley, Johny) shari
- [x] Session lifecycle hook for auto-extraction
- [x] Heuristic and LLM-based extraction
### 3.3 MCP Servers
### 3.3 MCP Servers - COMPLETE
- [x] Context7 integration
- [ ] Custom MCP for personas (not yet implemented)
- [ ] Memory MCP (expose memory search to external tools)
- [ ] Calendar MCP (expose calendar to external tools)
- [ ] Portfolio MCP (expose portfolio to external tools)
- [x] Custom MCP for personas (`src/mcp/servers/`)
- [x] Memory MCP (`personas-memory`) - store, search, list, delete, stats
- [x] Calendar MCP (`personas-calendar`) - events, create, update, delete, free-time
- [x] Portfolio MCP (`personas-portfolio`) - status, positions, market-data, SEC, backtest
---
## Phase 4: TUI Improvements
### 4.1 Model Selection - MOSTLY COMPLETE
### 4.1 Model Selection - COMPLETE
- [x] Favorites system (`dialog-model.tsx` favorites support)
- [x] Recently used models (`local.model.recent()`)
- [ ] Provider status indicators (auth valid/expired)
- [x] Provider status indicators (auth valid/expired) - shows ✗/△ in model dialog
- [x] Cost display for non-free models (shows "Free" badge)
### 4.2 Agent Experience
@@ -8,6 +8,18 @@ import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { Keybind } from "@/util/keybind"
import * as fuzzysort from "fuzzysort"
/** Get auth status indicator for a provider */
function getAuthIndicator(
providerID: string,
authStatus: Record<string, { valid: boolean; expiringSoon: boolean; expiresIn: number | null }>,
): string {
const status = authStatus[providerID]
if (!status) return ""
if (!status.valid) return "✗ "
if (status.expiringSoon) return "△ "
return ""
}
export function useConnected() {
const sync = useSync()
return createMemo(() =>
@@ -37,6 +49,7 @@ export function DialogModel(props: { providerID?: string }) {
const showSections = showExtra() && needle.length === 0
const favorites = connected() ? local.model.favorite() : []
const recents = local.model.recent()
const authStatus = sync.data.provider_auth_status
const recentList = showSections
? recents.filter(
@@ -50,6 +63,7 @@ export function DialogModel(props: { providerID?: string }) {
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
const authIndicator = getAuthIndicator(provider.id, authStatus)
return [
{
key: item,
@@ -58,7 +72,7 @@ export function DialogModel(props: { providerID?: string }) {
modelID: model.id,
},
title: model.name ?? item.modelID,
description: provider.name,
description: authIndicator + provider.name,
category: "Favorites",
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
@@ -83,6 +97,7 @@ export function DialogModel(props: { providerID?: string }) {
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
const authIndicator = getAuthIndicator(provider.id, authStatus)
return [
{
key: item,
@@ -91,7 +106,7 @@ export function DialogModel(props: { providerID?: string }) {
modelID: model.id,
},
title: model.name ?? item.modelID,
description: provider.name,
description: authIndicator + provider.name,
category: "Recent",
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
@@ -116,8 +131,9 @@ export function DialogModel(props: { providerID?: string }) {
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) =>
pipe(
flatMap((provider) => {
const authIndicator = getAuthIndicator(provider.id, authStatus)
return pipe(
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
@@ -130,7 +146,7 @@ export function DialogModel(props: { providerID?: string }) {
return {
value,
title: info.name ?? model,
category: connected() ? provider.name : undefined,
category: connected() ? authIndicator + provider.name : undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect() {
@@ -159,8 +175,8 @@ export function DialogModel(props: { providerID?: string }) {
(x) => x.footer !== "Free",
(x) => x.title,
),
),
),
)
}),
)
const popularProviders = !connected()