freddie 0.0.133 → 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.133",
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,22 +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
- // User's explicit model_preference is preserved in config order — they chose
78
- // those providers in that order and the SWE-bench re-rank would defeat the
79
- // purpose. orderByScore is only used for the auto-chain path (no preference).
80
- function orderByScore(links) {
81
- let pool
82
- try { pool = typeof sdk.buildAutoChain === 'function' ? sdk.buildAutoChain(undefined, { hasTools: true }) : [] } catch { pool = [] }
83
- if (!Array.isArray(pool) || !pool.length) return links
84
- const rank = new Map(pool.map((l, i) => [l.model, i]))
85
- return [...links].sort((a, b) => {
86
- const ra = rank.has(a) ? rank.get(a) : Infinity
87
- const rb = rank.has(b) ? rank.get(b) : Infinity
88
- if (ra !== rb) return ra - rb
89
- return links.indexOf(a) - links.indexOf(b)
90
- })
91
- }
92
-
93
99
  async function buildModel({ provider, model, inputModel }) {
94
100
  if (provider) return `${provider}/${model || DEFAULTS[provider] || ''}`.replace(/\/$/, '')
95
101
  if (model) return model
@@ -101,28 +107,58 @@ async function buildModel({ provider, model, inputModel }) {
101
107
  }
102
108
  return inputModel
103
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
+
104
117
  const pref = getConfigValue('agent.model_preference', [])
105
- if (Array.isArray(pref) && pref.length) {
106
- // Preserve user's explicit config order they chose these providers in
107
- // this sequence. Only filter out providers where the sampler reports them
108
- // as actively in backoff (ok===false) to avoid inserting definitely-dead
109
- // links. SWE-bench re-rank via orderByScore is NOT applied here since it
110
- // would override the user's deliberate ordering.
111
- const links = pref.map(p => `${p.provider}/${p.model || DEFAULTS[p.provider] || ''}`.replace(/\/$/, '')).filter(s => s.includes('/'))
112
- if (!links.length) return ''
113
- // Filter by sampler state if available
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) {
114
151
  const status = typeof sdk.getStatus === 'function' ? sdk.getStatus() : []
115
152
  if (status.length) {
116
153
  const blocked = new Set(status.filter(s => s.ok === false).map(s => s.provider))
117
- const filtered = links.filter(l => !blocked.has(l.split('/')[0]))
118
- if (filtered.length) return filtered.join(', ')
154
+ const filtered = chain.filter(l => !blocked.has(l.model.split('/')[0]))
155
+ if (filtered.length) return filtered.map(l => l.model).join(', ')
119
156
  }
120
- return links.join(', ')
157
+ return chain.map(l => l.model).join(', ')
121
158
  }
122
- const auto = typeof sdk.buildAutoChain === 'function' ? sdk.buildAutoChain(undefined) : []
123
- const keyed = Array.isArray(auto) ? auto.filter(l => { const p = l.model.split('/')[0]; const env = PROVIDER_KEYS[p]; return env && process.env[env] }) : []
124
- // Filter out providers in sampler backoff so the chain doesn't waste slots
125
- // on links that will be immediately skipped with sampler_backoff reason.
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] }) : []
126
162
  const status = typeof sdk.getStatus === 'function' ? sdk.getStatus() : []
127
163
  if (status.length && keyed.length) {
128
164
  const blocked = new Set(status.filter(s => s.ok === false).map(s => s.provider))
@@ -130,12 +166,15 @@ async function buildModel({ provider, model, inputModel }) {
130
166
  if (filtered.length) return filtered.map(l => l.model).join(', ')
131
167
  }
132
168
  if (keyed.length) return keyed.map(l => l.model).join(', ')
133
- // No local provider keys — delegate to acptoapi if reachable.
134
169
  if (await cachedReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
135
170
  return null
136
171
  }
137
172
 
138
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()
139
178
  return async (input) => {
140
179
  const m = await buildModel({ provider, model, inputModel: input.model })
141
180
  if (!m) {