From aa29f28c7362db766c5d057ac8340ac6cab9a2f2 Mon Sep 17 00:00:00 2001 From: Amolith Date: Tue, 25 Nov 2025 19:22:17 -0700 Subject: [PATCH] feat(cache): add persistent model preferences Introduces XDG-compliant cache for storing user model preferences across sessions. Cache is stored in $XDG_CONFIG_HOME/synu/models.conf using simple key=value format (agent.slot = model_id). Claude agent now checks cache before falling back to hardcoded defaults, and interactive mode can save selections. Defaults renamed to "fallback" to clarify they're only used when no cache entry exists. Assisted-by: Claude Sonnet 4.5 via Crush --- functions/_synu_agents/claude.fish | 59 +++++++++++++++++++++++++----- functions/_synu_cache.fish | 54 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 functions/_synu_cache.fish diff --git a/functions/_synu_agents/claude.fish b/functions/_synu_agents/claude.fish index 8dca740f58a70eff9c9fc1278287d43c48b1059d..370449b4f139f692d5e8925b846b1524b1ee1e38 100644 --- a/functions/_synu_agents/claude.fish +++ b/functions/_synu_agents/claude.fish @@ -5,11 +5,25 @@ # Claude Code agent definition for synu # Provides model configuration for routing through Synthetic API -# Default models -set -g _synu_claude_default_opus "hf:moonshotai/Kimi-K2-Thinking" -set -g _synu_claude_default_sonnet "hf:MiniMaxAI/MiniMax-M2" -set -g _synu_claude_default_haiku "hf:deepseek-ai/DeepSeek-V3.1-Terminus" -set -g _synu_claude_default_subagent "hf:MiniMaxAI/MiniMax-M2" +# Source cache functions +source (status dirname)/../_synu_cache.fish + +# Fallback defaults (used when no cache entry exists) +set -g _synu_claude_fallback_opus "hf:moonshotai/Kimi-K2-Thinking" +set -g _synu_claude_fallback_sonnet "hf:MiniMaxAI/MiniMax-M2" +set -g _synu_claude_fallback_haiku "hf:deepseek-ai/DeepSeek-V3.1-Terminus" +set -g _synu_claude_fallback_agent "hf:MiniMaxAI/MiniMax-M2" + +function _synu_claude_default --description "Get default model: _synu_claude_default slot" + set -l slot $argv[1] + set -l cached (_synu_cache_get claude $slot) + if test $status -eq 0 + echo $cached + else + set -l var_name _synu_claude_fallback_$slot + echo $$var_name + end +end function _synu_agent_claude_flags --description "Return argparse-compatible flag specification" echo "L/large=" @@ -25,11 +39,11 @@ function _synu_agent_claude_configure --description "Configure Claude Code envir argparse 'L/large=' 'l/light=' 'o/opus=' 's/sonnet=' 'H/haiku=' 'a/agent=' -- $argv or return 1 - # Start with defaults - set -l opus_model $_synu_claude_default_opus - set -l sonnet_model $_synu_claude_default_sonnet - set -l haiku_model $_synu_claude_default_haiku - set -l subagent_model $_synu_claude_default_subagent + # Start with defaults (from cache or fallback) + set -l opus_model (_synu_claude_default opus) + set -l sonnet_model (_synu_claude_default sonnet) + set -l haiku_model (_synu_claude_default haiku) + set -l subagent_model (_synu_claude_default agent) # Apply group overrides if set -q _flag_large @@ -173,6 +187,31 @@ function _synu_agent_claude_interactive --description "Interactive model selecti end end + # Offer to save as defaults + if test (count $flags) -gt 0 + if gum confirm "Save as default for 'claude'?" + for flag in $flags + # Parse --key=value format + set -l parts (string match -r '^--([^=]+)=(.+)$' $flag) + if test -n "$parts[2]" + set -l key $parts[2] + set -l value $parts[3] + # Expand group flags to individual slots + switch $key + case large + _synu_cache_set claude opus $value + _synu_cache_set claude sonnet $value + _synu_cache_set claude agent $value + case light + _synu_cache_set claude haiku $value + case '*' + _synu_cache_set claude $key $value + end + end + end + end + end + # Output flags for caller to use echo $flags end diff --git a/functions/_synu_cache.fish b/functions/_synu_cache.fish new file mode 100644 index 0000000000000000000000000000000000000000..3050ae8c8f60dedebba1215100cdc2e054b1cf80 --- /dev/null +++ b/functions/_synu_cache.fish @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: Amolith +# +# SPDX-License-Identifier: Unlicense + +# Cache management for synu model preferences +# File format: agent.slot = model_id (one per line, # comments allowed) + +set -g _synu_cache_file (set -q XDG_CONFIG_HOME; and echo "$XDG_CONFIG_HOME"; or echo "$HOME/.config")"/synu/models.conf" + +function _synu_cache_get --description "Get a cached value: _synu_cache_get agent slot" + set -l agent $argv[1] + set -l slot $argv[2] + set -l key "$agent.$slot" + + if not test -f "$_synu_cache_file" + return 1 + end + + # Match "key = value" or "key=value", ignoring comments and whitespace + set -l match (string match -r "^$key\\s*=\\s*(.+)" < "$_synu_cache_file") + if test -n "$match[2]" + string trim "$match[2]" + return 0 + end + return 1 +end + +function _synu_cache_set --description "Set a cached value: _synu_cache_set agent slot value" + set -l agent $argv[1] + set -l slot $argv[2] + set -l value $argv[3] + set -l key "$agent.$slot" + + # Ensure directory exists + mkdir -p (dirname "$_synu_cache_file") + + if not test -f "$_synu_cache_file" + # Create new file with header + echo "# synu model preferences" > "$_synu_cache_file" + echo "# Format: agent.slot = model_id" >> "$_synu_cache_file" + echo "" >> "$_synu_cache_file" + end + + # Check if key already exists + if string match -rq "^$key\\s*=" < "$_synu_cache_file" + # Replace existing line + set -l tmp (mktemp) + string replace -r "^$key\\s*=.*" "$key = $value" < "$_synu_cache_file" > "$tmp" + mv "$tmp" "$_synu_cache_file" + else + # Append new line + echo "$key = $value" >> "$_synu_cache_file" + end +end