@link-assistant/agent 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -67,11 +67,74 @@ export namespace ModelsDev {
67
67
 
68
68
  export type Provider = z.infer<typeof Provider>;
69
69
 
70
+ /**
71
+ * Cache staleness threshold in milliseconds (1 hour).
72
+ * If the cache is older than this, we await the refresh before using the data.
73
+ */
74
+ const CACHE_STALE_THRESHOLD_MS = 60 * 60 * 1000;
75
+
76
+ /**
77
+ * Get the models database, refreshing from models.dev if needed.
78
+ *
79
+ * This function handles cache staleness properly:
80
+ * - If cache doesn't exist: await refresh to ensure fresh data
81
+ * - If cache is stale (> 1 hour old): await refresh to ensure up-to-date models
82
+ * - If cache is fresh: trigger background refresh but use cached data immediately
83
+ *
84
+ * This prevents ProviderModelNotFoundError when:
85
+ * - User runs agent for the first time (no cache)
86
+ * - User has outdated cache missing new models like kimi-k2.5-free
87
+ *
88
+ * @see https://github.com/link-assistant/agent/issues/175
89
+ */
70
90
  export async function get() {
71
- refresh();
72
91
  const file = Bun.file(filepath);
92
+
93
+ // Check if cache exists and get its modification time
94
+ const exists = await file.exists();
95
+
96
+ if (!exists) {
97
+ // No cache - must await refresh to get initial data
98
+ log.info(() => ({
99
+ message: 'no cache found, awaiting refresh',
100
+ path: filepath,
101
+ }));
102
+ await refresh();
103
+ } else {
104
+ // Check if cache is stale
105
+ const stats = await file.stat().catch(() => null);
106
+ const mtime = stats?.mtime?.getTime() ?? 0;
107
+ const isStale = Date.now() - mtime > CACHE_STALE_THRESHOLD_MS;
108
+
109
+ if (isStale) {
110
+ // Stale cache - await refresh to get updated model list
111
+ log.info(() => ({
112
+ message: 'cache is stale, awaiting refresh',
113
+ path: filepath,
114
+ age: Date.now() - mtime,
115
+ threshold: CACHE_STALE_THRESHOLD_MS,
116
+ }));
117
+ await refresh();
118
+ } else {
119
+ // Fresh cache - trigger background refresh but don't wait
120
+ log.info(() => ({
121
+ message: 'cache is fresh, triggering background refresh',
122
+ path: filepath,
123
+ age: Date.now() - mtime,
124
+ }));
125
+ refresh();
126
+ }
127
+ }
128
+
129
+ // Now read the cache file
73
130
  const result = await file.json().catch(() => {});
74
131
  if (result) return result as Record<string, Provider>;
132
+
133
+ // Fallback to bundled data if cache read failed
134
+ log.warn(() => ({
135
+ message: 'cache read failed, using bundled data',
136
+ path: filepath,
137
+ }));
75
138
  const json = await data();
76
139
  return JSON.parse(json) as Record<string, Provider>;
77
140
  }