freddie 0.0.92 → 0.0.93
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
### Added
|
|
4
|
+
- Dynamic per-(provider × model × access_mode) availability matrix system. New `scripts/build-model-availability.js` enumerates models via existing `discoverModels()` and cross-probes each (model, mode) cell across 7 modes: `direct_api`, `acptoapi_passthrough` (:4800), `freddie_v1` (:4900), `kilo_acp` (:4780), `opencode_acp` (:4790), `claude_cli`, `freddie_agent_loop`. Sampler-aware (`markFailed` on per-cell failure), per-cell timeout 15s, per-provider model cap 5 (both env-tunable). Output: `.gm/model-availability.json` with `{timestamp, config, daemons, providers[].models[].modes{}, sampler, summary}`. Every cell is one of `{ok:true, latency_ms, excerpt}`, `{ok:false, latency_ms, error}`, or `{ok:false, skipped:true, reason}` — no blanks. Witnessed 2026-05-13: 23 providers × up to 5 models × 7 modes; 3 models green-in-any-mode (groq/gpt-oss-20b + claude-cli/haiku + claude-cli/sonnet); 8 individual cells green.
|
|
5
|
+
- `src/agent/model-matrix.js` (28L): `loadMatrix()` + `matrixUsable(provider, model)`. 24h TTL on consumption.
|
|
6
|
+
- `src/agent/llm_resolver.js`: `buildAutoChain` now consults `matrixUsable` and drops links marked `ok:false in all modes`. Matrix-absent path unchanged (preserves existing behavior). Side effect: stale defaults like `nvidia/deepseek-r1` (410 Gone) auto-drop without editing static lists.
|
|
7
|
+
- `plugins/gui-models-discover/plugin.js`: 3 new endpoints — `GET /api/models/availability` (200 with full matrix, 404 if absent), `GET /api/models/availability/summary` (lightweight), `POST /api/models/availability/rebuild` (202 background spawn, 409 if rebuild in flight).
|
|
8
|
+
- `scripts/validate-llm-providers.js`: now invokes the matrix builder by default. `--single-shot` retains the legacy one-model-per-provider behavior; `--with-single-shot` runs both.
|
|
9
|
+
- `test.js`: asserts both `scripts/build-model-availability.js` exists and the `/api/models/availability` endpoint returns 200|404 with valid schema when present.
|
|
10
|
+
|
|
3
11
|
### Refactored
|
|
4
12
|
- `src/host/host.js`: createHost split from 111L → 24L body. Helper factories (`makePi`, `makeGui`, `makeCcHooks`, `makeHooksRegistry`, `makeCcLoaders`, plus `reg`/`guard`/`scopedCfg`/`nullStore`) extracted to new `src/host/host_helpers.js`. host.js drops from 197L → 64L, host_helpers.js is 152L. Both well under the 200L hard cap. Witnessed: test.js 12/12 green, plugins>=100, platforms>=18, memory>=8, surface guard + cycle errors still throw.
|
|
5
13
|
- `test.js`: trimmed from 202L → 199L (within the 200L cap) by collapsing redundant blank lines and joining the final two control statements. Every assertion preserved. Witnessed 12/12 green.
|
package/package.json
CHANGED
|
@@ -2,6 +2,12 @@ import { discoverAndPersist, listKnownProviders } from '../../src/agent/model-di
|
|
|
2
2
|
import { PROVIDER_KEYS, DEFAULTS } from '../../src/agent/llm_resolver.js'
|
|
3
3
|
import { getConfigValue, saveConfigValue } from '../../src/config.js'
|
|
4
4
|
import { getStatus } from '../../src/agent/model-sampler.js'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { spawn } from 'node:child_process'
|
|
8
|
+
|
|
9
|
+
const MATRIX_PATH = path.resolve(new URL('.', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'), '..', '..', '.gm', 'model-availability.json')
|
|
10
|
+
let _rebuildInFlight = null
|
|
5
11
|
|
|
6
12
|
export default {
|
|
7
13
|
name: 'gui-models-discover', surfaces: 'gui',
|
|
@@ -28,5 +34,22 @@ export default {
|
|
|
28
34
|
res.json({ ok: true })
|
|
29
35
|
})
|
|
30
36
|
gui.route('GET', '/api/models/sampler', (_, res) => res.json({ status: getStatus() }))
|
|
37
|
+
gui.route('GET', '/api/models/availability', (_, res) => {
|
|
38
|
+
if (!fs.existsSync(MATRIX_PATH)) return res.status(404).json({ error: 'not_found', hint: 'run: node scripts/build-model-availability.js' })
|
|
39
|
+
try { res.json(JSON.parse(fs.readFileSync(MATRIX_PATH, 'utf8'))) }
|
|
40
|
+
catch (e) { res.status(500).json({ error: String(e.message || e) }) }
|
|
41
|
+
})
|
|
42
|
+
gui.route('GET', '/api/models/availability/summary', (_, res) => {
|
|
43
|
+
if (!fs.existsSync(MATRIX_PATH)) return res.status(404).json({ error: 'not_found' })
|
|
44
|
+
try { const j = JSON.parse(fs.readFileSync(MATRIX_PATH, 'utf8')); res.json({ timestamp: j.timestamp, daemons: j.daemons, summary: j.summary }) }
|
|
45
|
+
catch (e) { res.status(500).json({ error: String(e.message || e) }) }
|
|
46
|
+
})
|
|
47
|
+
gui.route('POST', '/api/models/availability/rebuild', (_, res) => {
|
|
48
|
+
if (_rebuildInFlight && !_rebuildInFlight.killed) return res.status(409).json({ error: 'rebuild_in_progress', pid: _rebuildInFlight.pid })
|
|
49
|
+
const script = path.resolve(path.dirname(MATRIX_PATH), '..', 'scripts', 'build-model-availability.js')
|
|
50
|
+
_rebuildInFlight = spawn(process.execPath, [script], { detached: true, stdio: 'ignore', cwd: path.dirname(path.dirname(MATRIX_PATH)) })
|
|
51
|
+
_rebuildInFlight.unref()
|
|
52
|
+
res.status(202).json({ ok: true, pid: _rebuildInFlight.pid, jobId: String(Date.now()) })
|
|
53
|
+
})
|
|
31
54
|
},
|
|
32
55
|
}
|
|
@@ -3,6 +3,8 @@ import { callLLM as acptoapiCall, isReachable as acptoapiReachable } from './acp
|
|
|
3
3
|
import { isAvailable, markFailed, getStatus } from './model-sampler.js'
|
|
4
4
|
import { getConfigValue } from '../config.js'
|
|
5
5
|
import { resolveKey } from './credential_sources.js'
|
|
6
|
+
import { matrixUsable } from './model-matrix.js'
|
|
7
|
+
export { matrixUsable } from './model-matrix.js'
|
|
6
8
|
|
|
7
9
|
const _require = createRequire(import.meta.url)
|
|
8
10
|
const sdk = _require('acptoapi')
|
|
@@ -174,7 +176,10 @@ export function resolveCallLLM({ provider, model } = {}) {
|
|
|
174
176
|
const links = sdk.buildAutoChain(model || input.model)
|
|
175
177
|
const availableLinks = (await Promise.all(links.map(async l => {
|
|
176
178
|
const prefix = l.model.split('/')[0]
|
|
177
|
-
|
|
179
|
+
if (!(await hasKey(prefix)) || !isAvailable(prefix)) return null
|
|
180
|
+
const mu = matrixUsable(prefix, l.model.replace(/^[^/]+\//, ''))
|
|
181
|
+
if (mu === false) return null
|
|
182
|
+
return l
|
|
178
183
|
}))).filter(Boolean)
|
|
179
184
|
|
|
180
185
|
for (const link of availableLinks) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
const MATRIX_PATH = path.resolve(new URL('.', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'), '..', '..', '.gm', 'model-availability.json')
|
|
5
|
+
const MATRIX_TTL_MS = 24 * 60 * 60 * 1000
|
|
6
|
+
let _cache = null
|
|
7
|
+
|
|
8
|
+
export function loadMatrix() {
|
|
9
|
+
if (_cache && Date.now() - _cache.loadedAt < 60_000) return _cache.data
|
|
10
|
+
if (!fs.existsSync(MATRIX_PATH)) return null
|
|
11
|
+
try {
|
|
12
|
+
const data = JSON.parse(fs.readFileSync(MATRIX_PATH, 'utf8'))
|
|
13
|
+
if (Date.now() - new Date(data.timestamp).getTime() > MATRIX_TTL_MS) return null
|
|
14
|
+
_cache = { data, loadedAt: Date.now() }
|
|
15
|
+
return data
|
|
16
|
+
} catch { return null }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function matrixUsable(provider, model) {
|
|
20
|
+
const m = loadMatrix(); if (!m) return null
|
|
21
|
+
const p = m.providers.find(x => x.id === provider); if (!p) return null
|
|
22
|
+
if (!model) return p.models.some(mm => mm.usable_in_any_mode)
|
|
23
|
+
const mm = p.models.find(x => x.id === model || x.id === model.replace(/^[^/]+\//, ''))
|
|
24
|
+
return mm ? mm.usable_in_any_mode : null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const MATRIX_FILE = MATRIX_PATH
|