freddie 0.0.132 → 0.0.134
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 +1 -1
- package/src/agent/llm_resolver.js +82 -29
package/package.json
CHANGED
|
@@ -4,6 +4,28 @@ import { callLLM as bridgeCall, isReachable as bridgeReachable } from './acptoap
|
|
|
4
4
|
import { parseTextToolCalls } from './tool_call_text.js'
|
|
5
5
|
import * as sdkNs from 'acptoapi'
|
|
6
6
|
export { matrixUsable } from './model-matrix.js'
|
|
7
|
+
import { createRequire } from 'node:module'
|
|
8
|
+
const _req = createRequire(import.meta.url)
|
|
9
|
+
// Fire async probe of file-based extra providers on first call so subsequent
|
|
10
|
+
// buildAutoChain calls find registered models from ~/.acptoapi/extra-providers.txt.
|
|
11
|
+
// sync loadFromCache (called inside buildAutoChain) picks up on-disk probe cache
|
|
12
|
+
// immediately; this async refresh updates the cache for future sessions.
|
|
13
|
+
let _warmExtraPromise = null
|
|
14
|
+
export async function warmExtraProviders() {
|
|
15
|
+
if (!_warmExtraPromise) {
|
|
16
|
+
try {
|
|
17
|
+
const extra = _req('acptoapi/lib/extra-providers')
|
|
18
|
+
if (extra && typeof extra.loadAndRegisterAsync === 'function') {
|
|
19
|
+
_warmExtraPromise = extra.loadAndRegisterAsync()
|
|
20
|
+
} else {
|
|
21
|
+
_warmExtraPromise = Promise.resolve()
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
_warmExtraPromise = Promise.resolve()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
await _warmExtraPromise
|
|
28
|
+
}
|
|
7
29
|
|
|
8
30
|
// Reachability memoization: bridgeReachable() now performs a REAL LLM call
|
|
9
31
|
// (acptoapi-bridge.js's isReachable() sends a live 'ping' completion), so
|
|
@@ -74,29 +96,6 @@ function adapt(result) {
|
|
|
74
96
|
// Mirror lib/named-chains.js BUILTIN — acptoapi resolves unknown names.
|
|
75
97
|
const NAMED_CHAIN_NAMES = new Set(['fast', 'cheap', 'smart', 'reasoning', 'free', 'local', 'auto'])
|
|
76
98
|
|
|
77
|
-
// A hand-typed agent.model_preference used to be joined in its literal config
|
|
78
|
-
// order, permanently overriding acptoapi's own swe_bench-score-aware ranking
|
|
79
|
-
// (buildAutoChain sorts by score + tool-capability tiering) with no
|
|
80
|
-
// reconciliation at all — witnessed picking a 62-score model ahead of a
|
|
81
|
-
// 79.6-score one purely because it was configured first. Re-rank the user's
|
|
82
|
-
// chosen providers using acptoapi's OWN scored pool (buildAutoChain is a
|
|
83
|
-
// sanctioned top-level export; this never reaches into unexported internals
|
|
84
|
-
// like lib/swe-bench-scores) so provider CHOICE stays user-controlled while
|
|
85
|
-
// ORDER reflects real capability. Unscored links keep their relative config
|
|
86
|
-
// order at the tail, matching acptoapi's own null-score handling.
|
|
87
|
-
function orderByScore(links) {
|
|
88
|
-
let pool
|
|
89
|
-
try { pool = typeof sdk.buildAutoChain === 'function' ? sdk.buildAutoChain(undefined, { hasTools: true }) : [] } catch { pool = [] }
|
|
90
|
-
if (!Array.isArray(pool) || !pool.length) return links
|
|
91
|
-
const rank = new Map(pool.map((l, i) => [l.model, i]))
|
|
92
|
-
return [...links].sort((a, b) => {
|
|
93
|
-
const ra = rank.has(a) ? rank.get(a) : Infinity
|
|
94
|
-
const rb = rank.has(b) ? rank.get(b) : Infinity
|
|
95
|
-
if (ra !== rb) return ra - rb
|
|
96
|
-
return links.indexOf(a) - links.indexOf(b)
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
|
|
100
99
|
async function buildModel({ provider, model, inputModel }) {
|
|
101
100
|
if (provider) return `${provider}/${model || DEFAULTS[provider] || ''}`.replace(/\/$/, '')
|
|
102
101
|
if (model) return model
|
|
@@ -108,20 +107,74 @@ async function buildModel({ provider, model, inputModel }) {
|
|
|
108
107
|
}
|
|
109
108
|
return inputModel
|
|
110
109
|
}
|
|
110
|
+
|
|
111
|
+
// Build intelligence-ranked auto-chain from ALL available providers (env
|
|
112
|
+
// keys + extra-providers.txt + ACP daemons). buildAutoChain already handles
|
|
113
|
+
// hasProvider filtering (env keys + sampler) and SWE-bench score ordering.
|
|
114
|
+
let chain = []
|
|
115
|
+
try { chain = typeof sdk.buildAutoChain === 'function' ? sdk.buildAutoChain(undefined, { hasTools: true }) : [] } catch {}
|
|
116
|
+
|
|
111
117
|
const pref = getConfigValue('agent.model_preference', [])
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
const prefModels = Array.isArray(pref) && pref.length
|
|
119
|
+
? pref.map(p => `${p.provider}/${p.model || DEFAULTS[p.provider] || ''}`.replace(/\/$/, '')).filter(s => s.includes('/'))
|
|
120
|
+
: []
|
|
121
|
+
|
|
122
|
+
if (prefModels.length && chain.length) {
|
|
123
|
+
// Merge user's model_preference into the intelligence-ranked auto-chain.
|
|
124
|
+
// Preference models not already in the chain are inserted at their
|
|
125
|
+
// SWE-bench score position. The result is ordered by intelligence score
|
|
126
|
+
// (not config order), so extra providers like nvidia, cerebras, google
|
|
127
|
+
// with high-scoring models naturally lead the chain.
|
|
128
|
+
const seen = new Set(chain.map(l => l.model))
|
|
129
|
+
const extras = prefModels.filter(m => !seen.has(m))
|
|
130
|
+
if (extras.length) {
|
|
131
|
+
const scored = chain.map(l => ({ model: l.model, score: l.swe_bench_score || 0 }))
|
|
132
|
+
for (const m of extras) {
|
|
133
|
+
const s = typeof sdk.getModelScore === 'function' ? sdk.getModelScore(m) : 0
|
|
134
|
+
scored.push({ model: m, score: s || 0 })
|
|
135
|
+
}
|
|
136
|
+
scored.sort((a, b) => b.score - a.score)
|
|
137
|
+
const allModels = scored.map(m => m.model)
|
|
138
|
+
const status = typeof sdk.getStatus === 'function' ? sdk.getStatus() : []
|
|
139
|
+
if (status.length) {
|
|
140
|
+
const blocked = new Set(status.filter(s => s.ok === false).map(s => s.provider))
|
|
141
|
+
const filtered = allModels.filter(m => !blocked.has(m.split('/')[0]))
|
|
142
|
+
if (filtered.length) return filtered.join(', ')
|
|
143
|
+
}
|
|
144
|
+
return allModels.join(', ')
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// All preference models already in auto-chain, or no extras added. Return
|
|
149
|
+
// the intelligence-ranked chain (not preference order).
|
|
150
|
+
if (prefModels.length && chain.length) {
|
|
151
|
+
const status = typeof sdk.getStatus === 'function' ? sdk.getStatus() : []
|
|
152
|
+
if (status.length) {
|
|
153
|
+
const blocked = new Set(status.filter(s => s.ok === false).map(s => s.provider))
|
|
154
|
+
const filtered = chain.filter(l => !blocked.has(l.model.split('/')[0]))
|
|
155
|
+
if (filtered.length) return filtered.map(l => l.model).join(', ')
|
|
156
|
+
}
|
|
157
|
+
return chain.map(l => l.model).join(', ')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// No model_preference: filter by env-key presence and sampler state.
|
|
161
|
+
const keyed = Array.isArray(chain) ? chain.filter(l => { const p = l.model.split('/')[0]; const env = PROVIDER_KEYS[p]; return env && process.env[env] }) : []
|
|
162
|
+
const status = typeof sdk.getStatus === 'function' ? sdk.getStatus() : []
|
|
163
|
+
if (status.length && keyed.length) {
|
|
164
|
+
const blocked = new Set(status.filter(s => s.ok === false).map(s => s.provider))
|
|
165
|
+
const filtered = keyed.filter(l => !blocked.has(l.model.split('/')[0]))
|
|
166
|
+
if (filtered.length) return filtered.map(l => l.model).join(', ')
|
|
115
167
|
}
|
|
116
|
-
const auto = typeof sdk.buildAutoChain === 'function' ? sdk.buildAutoChain(undefined) : []
|
|
117
|
-
const keyed = Array.isArray(auto) ? auto.filter(l => { const p = l.model.split('/')[0]; const env = PROVIDER_KEYS[p]; return env && process.env[env] }) : []
|
|
118
168
|
if (keyed.length) return keyed.map(l => l.model).join(', ')
|
|
119
|
-
// No local provider keys — delegate to acptoapi if reachable.
|
|
120
169
|
if (await cachedReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
|
|
121
170
|
return null
|
|
122
171
|
}
|
|
123
172
|
|
|
124
173
|
export function resolveCallLLM({ provider, model } = {}) {
|
|
174
|
+
// Fire async extra-provider probe on first call (non-blocking). The sync
|
|
175
|
+
// loadFromCache inside buildAutoChain picks up the previous run's probe
|
|
176
|
+
// cache immediately; this async refresh updates the cache for future turns.
|
|
177
|
+
warmExtraProviders()
|
|
125
178
|
return async (input) => {
|
|
126
179
|
const m = await buildModel({ provider, model, inputModel: input.model })
|
|
127
180
|
if (!m) {
|