ModelRouter now supports multiple tier-change listeners via addOnTierChange(),
SessionBridge subscribes to tier changes and propagates them to all WebChat
agents (both existing and newly created), and the fullscreen TUI now also
updates the agent's tier when switching models (matching minimal TUI behavior).
Persist /model tier choice to ~/.local/share/flynn/preferences.json so
it survives restarts. Decode HTML entities (e.g. ') in markdown
renderer output. Suppress noisy logger.info and punycode deprecation
warnings in TUI startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace console.debug/log/warn calls in model router, retry, and daemon
startup with a structured logger that respects a configurable log_level.
Default level is 'info', suppressing verbose fallback debug messages in
the TUI while keeping them available via config when needed.
- Add src/logger.ts with debug/info/warn/error/silent levels
- Wire log_level into config schema (default: 'info')
- Initialize log level in both daemon and TUI startup paths
- Convert all console.debug in router.ts and retry.ts to logger.debug
- Convert console.log/warn in daemon/models.ts to logger.info/warn
Check model capabilities via /api/show before sending tools.
Models without 'tools' capability get requests without tools
(they can still answer, just without tool use). Result is cached
per client instance. Defense-in-depth: 'does not support' added
to retry nonRetryablePatterns to avoid wasting retries on
permanent errors.
- Ollama: pass tools to API, parse tool_calls responses, handle thinking field from reasoning models (deepseek-r1, glm-4.7-flash)
- llama.cpp: pass tools via OpenAI-compatible endpoint, parse tool_calls, accumulate streaming tool call deltas
- Both clients now set stopReason to 'tool_use' when tool calls are present
- Tests: 12 new tests (8 Ollama + 5 llama.cpp, total 983→995)
Two issues prevented the GitHub Models fallback from working:
1. The X-GitHub-Api-Version: 2022-11-28 header caused '400 invalid
apiVersion' errors. The Copilot chat completions endpoint does not
use this header — removed from both constructor and rebuildClient.
2. The anthropicToGitHubModel mapping was incomplete: it only knew
three models and the generic date-stripping fallback produced wrong
names (e.g. 'claude-sonnet-4-5' instead of 'claude-sonnet-4.5').
GitHub Copilot uses dots for sub-versions, not hyphens.
Updated with explicit mappings for all current models (sonnet 4,
4.5; opus 4, 4.5, 4.6; haiku 4.5) and a smarter generic fallback
that converts digit-hyphen-digit to digit.digit at the end.
3. createClientFromConfig now auto-maps Anthropic-style model names
when the provider is 'github', so users can copy model names from
their Anthropic config into fallback blocks without manual renaming.
Five additive features with no breaking changes:
- Tool groups: group:fs, group:runtime, group:web, group:memory syntactic
sugar for allow/deny lists in tool policy config
- Typing indicators: Discord sendTyping() and WhatsApp sendStateTyping()
on message receipt for better UX feedback
- Session pruning: TTL-based auto-cleanup via sessions.ttl config with
hourly daemon timer and SQLite GROUP BY pruning
- /verbose command: TUI command parser toggle for raw streaming display
- !!think prefix: per-message extended thinking mode wired through
Anthropic (budget_tokens), OpenAI/GitHub (reasoning_effort), and
Gemini (thinkingConfig) providers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The router now accepts a tierFallbacks map so each model tier can have
its own fallback providers. Tier fallbacks are tried before the global
fallback chain in both chat() and chatStream().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GitHubModelsClient now lazily resolves tokens at first API call. If no
token exists (env var, stored OAuth, or config), it triggers the OAuth
device flow automatically via an onLoginRequired callback wired in both
the TUI and daemon entry points.
Add a new 'github' model provider backed by the Copilot API
(api.githubcopilot.com), with OAuth device flow for authentication.
- New src/auth/github.ts: device flow login, token storage at
~/.config/flynn/auth.json with 0600 permissions
- New src/models/github.ts: OpenAI-compatible client with streaming,
tool calling, and Copilot-specific headers
- Add 'github' to provider enum in config schema
- Register provider in daemon factory and TUI client factory
- Refactor TUI to use provider-agnostic client factory (was hardcoded
to AnthropicClient for all tiers)
- Add /login command to TUI for interactive OAuth authorization
- Add Copilot model cost tracking entries
Widen Message.content from string to string | MessageContentPart[] to support
multimodal content. Add Attachment type to channel layer, media conversion
utilities, and image extraction to all channel adapters (Telegram, Discord,
Slack, WhatsApp). Update all model clients (Anthropic, OpenAI, Gemini, Bedrock)
to convert structured content to provider-specific formats. Fix downstream
consumers (tokens, compaction, TUI, local models) to handle the widened type
via getMessageText() helper.
Add native GeminiClient using @google/generative-ai SDK and BedrockClient
using @aws-sdk/client-bedrock-runtime. Replace the previous Gemini fallback
(OpenAI-compatible shim) with the real implementation. Add OpenRouter as a
provider option (OpenAI-compatible with custom baseURL). Update model costs,
doctor CLI checks, and client factory tests.
- Extract createClientFromConfig() to dispatch on provider field instead
of hardcoding all tiers as AnthropicClient
- Add fallback/fallbackReason metadata to ChatResponse and ChatStreamEvent
so callers know when a fallback model was used
- Enhance doctor check to report full model stack and warn on missing
API keys for cloud providers
- Log fallback warnings in NativeAgent and display them in TUI
- Support tier names and local_providers entries in fallback_chain
- Add 8 tests for createClientFromConfig covering all provider types
Implements ModelClient interface with OpenAI SDK to support GPT models
as fallback when local inference is unavailable. Includes tests with
mocked OpenAI API responses.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>