cache.go

v0.7.0
Doc Versions Source
1
package cache
2
3
import (
4
	"database/sql"
5
	"os"
6
	"path/filepath"
7
	"time"
8
9
	_ "modernc.org/sqlite"
10
)
11
12
const defaultTTL = 6 * time.Hour
13
14
// DB wraps a SQLite database used for caching model lists and tracking used models.
15
type DB struct {
16
	db *sql.DB
17
}
18
19
// UsedModel represents a previously selected OpenRouter model.
20
type UsedModel struct {
21
	ID   string
22
	Name string
23
}
24
25
// Open creates or opens the cache database in the XDG config directory.
26
func Open() (*DB, error) {
27
	dir := os.Getenv("XDG_CONFIG_HOME")
28
	if dir == "" {
29
		home, _ := os.UserHomeDir()
30
		dir = filepath.Join(home, ".config")
31
	}
32
	p := filepath.Join(dir, "claudio", "cache.db")
33
	if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
34
		return nil, err
35
	}
36
37
	db, err := sql.Open("sqlite", p)
38
	if err != nil {
39
		return nil, err
40
	}
41
42
	_, _ = db.Exec("PRAGMA journal_mode=WAL")
43
44
	_, err = db.Exec(`
45
		CREATE TABLE IF NOT EXISTS models_cache (
46
			provider TEXT PRIMARY KEY,
47
			data     BLOB NOT NULL,
48
			updated  INTEGER NOT NULL
49
		);
50
		CREATE TABLE IF NOT EXISTS used_models (
51
			id      TEXT PRIMARY KEY,
52
			name    TEXT NOT NULL,
53
			used_at INTEGER NOT NULL
54
		);
55
		CREATE TABLE IF NOT EXISTS used_models_v2 (
56
			provider TEXT NOT NULL,
57
			id       TEXT NOT NULL,
58
			name     TEXT NOT NULL,
59
			used_at  INTEGER NOT NULL,
60
			PRIMARY KEY (provider, id)
61
		);
62
		CREATE TABLE IF NOT EXISTS ollama_show_cache (
63
			provider     TEXT NOT NULL,
64
			model        TEXT NOT NULL,
65
			modified_at  TEXT NOT NULL,
66
			ctx_length   INTEGER NOT NULL,
67
			capabilities TEXT NOT NULL DEFAULT '',
68
			PRIMARY KEY (provider, model)
69
		);
70
	`)
71
	if err != nil {
72
		db.Close()
73
		return nil, err
74
	}
75
76
	// Migrate: add capabilities column if missing (existing DBs).
77
	_, _ = db.Exec(`ALTER TABLE ollama_show_cache ADD COLUMN capabilities TEXT NOT NULL DEFAULT ''`)
78
79
	return &DB{db: db}, nil
80
}
81
82
// Close closes the database connection.
83
func (d *DB) Close() error {
84
	return d.db.Close()
85
}
86
87
// GetModels returns a cached model list for the given provider.
88
// Returns nil, false if the cache is empty or expired.
89
func (d *DB) GetModels(provider string) ([]byte, bool) {
90
	var data []byte
91
	var updated int64
92
	err := d.db.QueryRow(
93
		"SELECT data, updated FROM models_cache WHERE provider = ?", provider,
94
	).Scan(&data, &updated)
95
	if err != nil {
96
		return nil, false
97
	}
98
	if time.Since(time.Unix(updated, 0)) > defaultTTL {
99
		return nil, false
100
	}
101
	return data, true
102
}
103
104
// SetModels stores a model list in the cache for the given provider.
105
func (d *DB) SetModels(provider string, data []byte) error {
106
	_, err := d.db.Exec(
107
		"INSERT OR REPLACE INTO models_cache (provider, data, updated) VALUES (?, ?, ?)",
108
		provider, data, time.Now().Unix(),
109
	)
110
	return err
111
}
112
113
// GetOllamaModelInfo returns cached /api/show info for an Ollama model.
114
func (d *DB) GetOllamaModelInfo(provider, model string) (modifiedAt string, contextLength int, capabilities string, ok bool) {
115
	err := d.db.QueryRow(
116
		"SELECT modified_at, ctx_length, capabilities FROM ollama_show_cache WHERE provider = ? AND model = ?",
117
		provider, model,
118
	).Scan(&modifiedAt, &contextLength, &capabilities)
119
	if err != nil {
120
		return "", 0, "", false
121
	}
122
	return modifiedAt, contextLength, capabilities, true
123
}
124
125
// SetOllamaModelInfo caches /api/show info for an Ollama model.
126
func (d *DB) SetOllamaModelInfo(provider, model, modifiedAt string, contextLength int, capabilities string) error {
127
	_, err := d.db.Exec(
128
		"INSERT OR REPLACE INTO ollama_show_cache (provider, model, modified_at, ctx_length, capabilities) VALUES (?, ?, ?, ?, ?)",
129
		provider, model, modifiedAt, contextLength, capabilities,
130
	)
131
	return err
132
}
133
134
// DropProviderCache removes the cached model list and recently used models for a specific provider.
135
func (d *DB) DropProviderCache(provider string) error {
136
	_, err := d.db.Exec("DELETE FROM models_cache WHERE provider = ?", provider)
137
	if err != nil {
138
		return err
139
	}
140
	_, err = d.db.Exec("DELETE FROM used_models_v2 WHERE provider = ?", provider)
141
	if err != nil {
142
		return err
143
	}
144
	_, err = d.db.Exec("DELETE FROM ollama_show_cache WHERE provider = ?", provider)
145
	return err
146
}
147
148
// DropAllCaches removes all cached model lists, recently used models, and ollama show caches.
149
func (d *DB) DropAllCaches() error {
150
	_, err := d.db.Exec("DELETE FROM models_cache")
151
	if err != nil {
152
		return err
153
	}
154
	_, err = d.db.Exec("DELETE FROM used_models")
155
	if err != nil {
156
		return err
157
	}
158
	_, err = d.db.Exec("DELETE FROM used_models_v2")
159
	if err != nil {
160
		return err
161
	}
162
	_, err = d.db.Exec("DELETE FROM ollama_show_cache")
163
	return err
164
}
165
166
// AddUsedModel records a model as recently used (legacy, no provider).
167
func (d *DB) AddUsedModel(id, name string) error {
168
	_, err := d.db.Exec(
169
		"INSERT OR REPLACE INTO used_models (id, name, used_at) VALUES (?, ?, ?)",
170
		id, name, time.Now().Unix(),
171
	)
172
	return err
173
}
174
175
// UsedModels returns all previously used models, most recent first (legacy, no provider).
176
func (d *DB) UsedModels() ([]UsedModel, error) {
177
	rows, err := d.db.Query(
178
		"SELECT id, name FROM used_models ORDER BY used_at DESC",
179
	)
180
	if err != nil {
181
		return nil, err
182
	}
183
	defer rows.Close()
184
185
	var models []UsedModel
186
	for rows.Next() {
187
		var m UsedModel
188
		if err := rows.Scan(&m.ID, &m.Name); err != nil {
189
			return nil, err
190
		}
191
		models = append(models, m)
192
	}
193
	return models, rows.Err()
194
}
195
196
// AddUsedModelForProvider records a model as recently used for a specific provider.
197
func (d *DB) AddUsedModelForProvider(provider, id, name string) error {
198
	_, err := d.db.Exec(
199
		"INSERT OR REPLACE INTO used_models_v2 (provider, id, name, used_at) VALUES (?, ?, ?, ?)",
200
		provider, id, name, time.Now().Unix(),
201
	)
202
	return err
203
}
204
205
// UsedModelsForProvider returns previously used models for a provider, most recent first.
206
func (d *DB) UsedModelsForProvider(provider string) ([]UsedModel, error) {
207
	rows, err := d.db.Query(
208
		"SELECT id, name FROM used_models_v2 WHERE provider = ? ORDER BY used_at DESC",
209
		provider,
210
	)
211
	if err != nil {
212
		return nil, err
213
	}
214
	defer rows.Close()
215
216
	var models []UsedModel
217
	for rows.Next() {
218
		var m UsedModel
219
		if err := rows.Scan(&m.ID, &m.Name); err != nil {
220
			return nil, err
221
		}
222
		models = append(models, m)
223
	}
224
	return models, rows.Err()
225
}
226

Source Files