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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freddie",
3
- "version": "0.0.132",
3
+ "version": "0.0.134",
4
4
  "type": "module",
5
5
  "description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
6
6
  "bin": {
@@ -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
- if (Array.isArray(pref) && pref.length) {
113
- const links = pref.map(p => `${p.provider}/${p.model || DEFAULTS[p.provider] || ''}`.replace(/\/$/, '')).filter(s => s.includes('/'))
114
- if (links.length) return orderByScore(links).join(', ')
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) {