freddie 0.0.84 → 0.0.86
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.
|
|
3
|
+
"version": "0.0.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
|
|
6
6
|
"bin": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"plugsdk": "^1.0.15",
|
|
27
27
|
"xstate": "^5.31.0",
|
|
28
28
|
"zod": "^4.0.0",
|
|
29
|
-
"anentrypoint-design": "^0.0.
|
|
30
|
-
"acptoapi": "^1.0.
|
|
29
|
+
"anentrypoint-design": "^0.0.93",
|
|
30
|
+
"acptoapi": "^1.0.51"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|
|
33
33
|
"@libsql/darwin-arm64": "0.3.19",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveCallLLM } from '../../src/agent/llm_resolver.js'
|
|
2
|
+
import { flattenForOpenAI } from '../../src/agent/model-discovery.js'
|
|
2
3
|
import { logger } from '../../src/observability/log.js'
|
|
3
4
|
|
|
4
5
|
const log = logger('gui-llm-passthrough')
|
|
@@ -7,7 +8,9 @@ export default {
|
|
|
7
8
|
name: 'gui-llm-passthrough', surfaces: 'gui',
|
|
8
9
|
register({ gui }) {
|
|
9
10
|
gui.route('GET', '/v1/models', (_, res) => {
|
|
10
|
-
|
|
11
|
+
const data = flattenForOpenAI()
|
|
12
|
+
if (data.length === 0) data.push({ id: 'freddie/auto', object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'freddie' })
|
|
13
|
+
res.json({ object: 'list', data })
|
|
11
14
|
})
|
|
12
15
|
gui.route('POST', '/v1/chat/completions', async (req, res) => {
|
|
13
16
|
const { model, messages, tools, stream } = req.body || {}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { discoverAndPersist, listKnownProviders } from '../../src/agent/model-discovery.js'
|
|
2
2
|
import { PROVIDER_KEYS, DEFAULTS } from '../../src/agent/llm_resolver.js'
|
|
3
|
-
import { getConfigValue } from '../../src/config.js'
|
|
3
|
+
import { getConfigValue, saveConfigValue } from '../../src/config.js'
|
|
4
|
+
import { getStatus } from '../../src/agent/model-sampler.js'
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
name: 'gui-models-discover', surfaces: 'gui',
|
|
@@ -11,5 +12,21 @@ export default {
|
|
|
11
12
|
try { const provider = req.body?.provider; const result = await discoverAndPersist({ provider }); res.json(result) }
|
|
12
13
|
catch (e) { res.status(500).json({ error: String(e.message || e) }) }
|
|
13
14
|
})
|
|
15
|
+
gui.route('GET', '/api/models/queues', (_, res) => res.json(getConfigValue('agent.model_queues', {}) || {}))
|
|
16
|
+
gui.route('POST', '/api/models/queues', (req, res) => {
|
|
17
|
+
const { name, entries } = req.body || {}
|
|
18
|
+
if (!name || !Array.isArray(entries)) return res.status(400).json({ error: 'name and entries[] required' })
|
|
19
|
+
const queues = getConfigValue('agent.model_queues', {}) || {}
|
|
20
|
+
queues[name] = entries
|
|
21
|
+
saveConfigValue('agent.model_queues', queues)
|
|
22
|
+
res.json({ name, entries })
|
|
23
|
+
})
|
|
24
|
+
gui.route('DELETE', '/api/models/queues/:name', (req, res) => {
|
|
25
|
+
const queues = getConfigValue('agent.model_queues', {}) || {}
|
|
26
|
+
delete queues[req.params.name]
|
|
27
|
+
saveConfigValue('agent.model_queues', queues)
|
|
28
|
+
res.json({ ok: true })
|
|
29
|
+
})
|
|
30
|
+
gui.route('GET', '/api/models/sampler', (_, res) => res.json({ status: getStatus() }))
|
|
14
31
|
},
|
|
15
32
|
}
|
|
@@ -6,10 +6,60 @@ import { resolveKey } from './credential_sources.js'
|
|
|
6
6
|
|
|
7
7
|
const _require = createRequire(import.meta.url)
|
|
8
8
|
const sdk = _require('acptoapi')
|
|
9
|
+
const { streamClaude, CLAUDE_DEFAULT } = _require('acptoapi/lib/claude-client')
|
|
9
10
|
|
|
10
11
|
export const PROVIDER_KEYS = sdk.PROVIDER_KEYS
|
|
11
12
|
export const DEFAULTS = sdk.PROVIDER_DEFAULTS
|
|
12
13
|
|
|
14
|
+
const ACP_BACKENDS = {
|
|
15
|
+
kilo: { base: 'http://localhost:4780', providerID: 'kilo', defaultModel: 'x-ai/grok-code-fast-1:optimized:free' },
|
|
16
|
+
opencode: { base: 'http://localhost:4790', providerID: 'opencode', defaultModel: 'minimax-m2.5-free' },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function claudeCliChat(model, input) {
|
|
20
|
+
const userMsg = input.messages.filter(m => m.role === 'user').slice(-1)[0]?.content
|
|
21
|
+
const systemMsg = input.messages.filter(m => m.role === 'system').map(m => m.content).join('\n\n') || undefined
|
|
22
|
+
const prompt = typeof userMsg === 'string' ? userMsg : JSON.stringify(userMsg || '')
|
|
23
|
+
let content = ''; const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 120000)
|
|
24
|
+
try {
|
|
25
|
+
for await (const ev of streamClaude({ prompt, model: model || CLAUDE_DEFAULT, systemPrompt: systemMsg, signal: ctrl.signal })) {
|
|
26
|
+
if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) for (const part of ev.message.content) { if (part.type === 'text' && part.text) content += part.text }
|
|
27
|
+
if (ev.type === 'result' && typeof ev.result === 'string') content = ev.result
|
|
28
|
+
}
|
|
29
|
+
} finally { clearTimeout(t) }
|
|
30
|
+
return { content: content.trim(), tool_calls: [], raw: { provider: 'claude-cli', model } }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function acpChat(prefix, model, input) {
|
|
34
|
+
const b = ACP_BACKENDS[prefix]; if (!b) throw new Error(`unknown acp backend: ${prefix}`)
|
|
35
|
+
const sessRes = await fetch(`${b.base}/session`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}', signal: AbortSignal.timeout(5000) })
|
|
36
|
+
if (!sessRes.ok) throw new Error(`ACP ${prefix} /session ${sessRes.status}`)
|
|
37
|
+
const sessionId = (await sessRes.json()).id
|
|
38
|
+
const userMsg = input.messages.filter(m => m.role === 'user').slice(-1)[0]?.content || ''
|
|
39
|
+
const body = { parts: [{ type: 'text', text: String(userMsg) }] }
|
|
40
|
+
if (b.providerID === 'opencode') body.model = { providerID: 'opencode', modelID: model || b.defaultModel }
|
|
41
|
+
else { body.providerID = 'kilo'; body.modelID = model || b.defaultModel }
|
|
42
|
+
await fetch(`${b.base}/session/${sessionId}/message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: AbortSignal.timeout(120000) })
|
|
43
|
+
let content = ''
|
|
44
|
+
const evRes = await fetch(`${b.base}/event`, { method: 'GET', signal: AbortSignal.timeout(120000) })
|
|
45
|
+
if (!evRes.ok) throw new Error(`ACP ${prefix} /event ${evRes.status}`)
|
|
46
|
+
const reader = evRes.body.getReader(); const dec = new TextDecoder(); let buf = ''
|
|
47
|
+
while (true) {
|
|
48
|
+
const { value, done } = await reader.read(); if (done) break
|
|
49
|
+
buf += dec.decode(value, { stream: true }); let idx
|
|
50
|
+
while ((idx = buf.indexOf('\n\n')) >= 0) {
|
|
51
|
+
const raw = buf.slice(0, idx); buf = buf.slice(idx + 2)
|
|
52
|
+
if (!raw.startsWith('data: ')) continue
|
|
53
|
+
try { const ev = JSON.parse(raw.slice(6))
|
|
54
|
+
if (ev.properties?.sessionID && ev.properties.sessionID !== sessionId) continue
|
|
55
|
+
if (ev.properties?.part?.type === 'text' && ev.properties.part.text) content += ev.properties.part.text
|
|
56
|
+
if (ev.event === 'session.complete' || ev.properties?.complete) return { content: content.trim(), tool_calls: [], raw: { provider: prefix, model } }
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { content: content.trim(), tool_calls: [], raw: { provider: prefix, model } }
|
|
61
|
+
}
|
|
62
|
+
|
|
13
63
|
function toOpenAITools(schemas) {
|
|
14
64
|
if (!schemas?.length) return undefined
|
|
15
65
|
return schemas.map(s => ({ type: 'function', function: { name: s.name, description: s.description || '', parameters: s.parameters || { type: 'object', properties: {} } } }))
|
|
@@ -28,6 +78,8 @@ async function directOpenAICompatChat(url, apiKey, model, messages, tools) {
|
|
|
28
78
|
}
|
|
29
79
|
|
|
30
80
|
async function sdkChat(provider, model, input) {
|
|
81
|
+
if (provider === 'claude-cli') return await claudeCliChat(model, input)
|
|
82
|
+
if (provider === 'kilo' || provider === 'opencode') return await acpChat(provider, model, input)
|
|
31
83
|
const { resolveModel } = sdk
|
|
32
84
|
const r = resolveModel(`${provider}/${model}`)
|
|
33
85
|
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
@@ -51,6 +103,7 @@ async function sdkChat(provider, model, input) {
|
|
|
51
103
|
function tryParseJson(s) { try { return typeof s === 'string' ? JSON.parse(s) : (s || {}) } catch { return {} } }
|
|
52
104
|
|
|
53
105
|
async function hasKey(provider) {
|
|
106
|
+
if (provider === 'claude-cli' || provider === 'kilo' || provider === 'opencode') return true
|
|
54
107
|
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
55
108
|
return !!resolved.value
|
|
56
109
|
}
|
|
@@ -59,8 +112,28 @@ function defaultModel(provider) {
|
|
|
59
112
|
return DEFAULTS[provider] || ''
|
|
60
113
|
}
|
|
61
114
|
|
|
115
|
+
async function tryChain(entries, input, model) {
|
|
116
|
+
const errors = []
|
|
117
|
+
for (const pref of entries) {
|
|
118
|
+
const p = pref.provider; const m = pref.model || model || input.model || (DEFAULTS[p] || '')
|
|
119
|
+
if (!await hasKey(p) || !isAvailable(p)) continue
|
|
120
|
+
try { return await sdkChat(p, m, input) } catch (e) { markFailed(p); errors.push(`${p}: ${e.message}`) }
|
|
121
|
+
}
|
|
122
|
+
if (errors.length) throw new Error(`chain exhausted: ${errors.join('; ')}`)
|
|
123
|
+
throw new Error('chain empty: no available providers')
|
|
124
|
+
}
|
|
125
|
+
|
|
62
126
|
export function resolveCallLLM({ provider, model } = {}) {
|
|
63
127
|
return async (input) => {
|
|
128
|
+
const mdl = model || input.model
|
|
129
|
+
const queueMatch = typeof mdl === 'string' && /^queue\//.test(mdl)
|
|
130
|
+
if (queueMatch) {
|
|
131
|
+
const name = mdl.slice('queue/'.length)
|
|
132
|
+
const queues = getConfigValue('agent.model_queues', {}) || {}
|
|
133
|
+
const entries = Array.isArray(queues[name]) ? queues[name] : null
|
|
134
|
+
if (!entries || entries.length === 0) throw new Error(`queue not found or empty: ${name}`)
|
|
135
|
+
return await tryChain(entries, input, undefined)
|
|
136
|
+
}
|
|
64
137
|
const explicitProvider = provider || input.provider
|
|
65
138
|
|
|
66
139
|
if (explicitProvider && await hasKey(explicitProvider)) {
|
|
@@ -1,44 +1,94 @@
|
|
|
1
1
|
import { createRequire } from 'module'
|
|
2
|
-
import { PROVIDER_KEYS } from './llm_resolver.js'
|
|
3
2
|
import { resolveKey } from './credential_sources.js'
|
|
4
3
|
import { saveConfigValue, getConfigValue } from '../config.js'
|
|
5
4
|
import { logger } from '../observability/log.js'
|
|
6
5
|
|
|
7
6
|
const _require = createRequire(import.meta.url)
|
|
8
|
-
const
|
|
7
|
+
const { createModelProber } = _require('acptoapi/lib/model-prober')
|
|
8
|
+
const { BRANDS } = _require('acptoapi/lib/openai-brands')
|
|
9
9
|
const log = logger('model-discovery')
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
anthropic: { url: 'https://api.anthropic.com/v1/models', auth: k => ({ 'x-api-key': k, 'anthropic-version': '2023-06-01' }), pick: j => (j.data || []).map(m => m.id) },
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
groq: { url: 'https://api.groq.com/openai/v1/models', auth: k => ({ authorization: `Bearer ${k}` }), pick: j => (j.data || []).map(m => m.id) },
|
|
16
|
-
xai: { url: 'https://api.x.ai/v1/models', auth: k => ({ authorization: `Bearer ${k}` }), pick: j => (j.data || []).map(m => m.id) },
|
|
17
|
-
gemini: { url: 'https://generativelanguage.googleapis.com/v1beta/models', auth: () => ({}), keyParam: 'key', pick: j => (j.models || []).map(m => m.name.replace(/^models\//, '')) },
|
|
11
|
+
const EXTRA = {
|
|
12
|
+
anthropic: { url: 'https://api.anthropic.com/v1/models', envName: 'ANTHROPIC_API_KEY', auth: k => ({ 'x-api-key': k, 'anthropic-version': '2023-06-01' }), pick: j => (j.data || []).map(m => m.id) },
|
|
13
|
+
gemini: { url: 'https://generativelanguage.googleapis.com/v1beta/models', envName: 'GOOGLE_API_KEY', keyParam: 'key', auth: () => ({}), pick: j => (j.models || []).map(m => (m.name || '').replace(/^models\//, '')).filter(Boolean) },
|
|
14
|
+
ollama: { url: 'http://localhost:11434/api/tags', envName: null, auth: () => ({}), pick: j => (j.models || []).map(m => m.name || m.model).filter(Boolean) },
|
|
18
15
|
}
|
|
16
|
+
const ACP_BACKENDS = {
|
|
17
|
+
kilo: { url: 'http://localhost:4780/session', staticModels: ['x-ai/grok-code-fast-1:optimized:free'] },
|
|
18
|
+
opencode: { url: 'http://localhost:4790/session', staticModels: ['minimax-m2.5-free'] },
|
|
19
|
+
}
|
|
20
|
+
const CLI_BACKENDS = {
|
|
21
|
+
'claude-cli': { models: ['claude-haiku-4-5', 'claude-sonnet-4-6', 'claude-opus-4-7', 'haiku', 'sonnet', 'opus'] },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const prober = createModelProber()
|
|
19
25
|
|
|
20
|
-
async function
|
|
21
|
-
const ep = ENDPOINTS[provider]
|
|
22
|
-
if (!ep) return { provider, error: 'no_probe_endpoint' }
|
|
26
|
+
async function probeBrand(provider) {
|
|
23
27
|
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
24
|
-
const
|
|
28
|
+
const brand = BRANDS[provider]
|
|
29
|
+
const envKey = brand?.envKey
|
|
30
|
+
const key = resolved.value || (envKey ? process.env[envKey] : undefined)
|
|
25
31
|
if (!key) return { provider, error: 'no_key' }
|
|
32
|
+
try {
|
|
33
|
+
const r = await prober.probe(provider, key)
|
|
34
|
+
if (r.error) return { provider, error: r.error }
|
|
35
|
+
return { provider, models: r.models || [], last_ok_at: r.ts }
|
|
36
|
+
} catch (e) { return { provider, error: String(e.message || e) } }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function probeExtra(provider) {
|
|
40
|
+
const ep = EXTRA[provider]
|
|
41
|
+
if (!ep) return { provider, error: 'no_extra_endpoint' }
|
|
42
|
+
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
43
|
+
const key = resolved.value || (ep.envName ? process.env[ep.envName] : undefined)
|
|
44
|
+
if (!key && provider !== 'ollama') return { provider, error: 'no_key' }
|
|
26
45
|
try {
|
|
27
46
|
const url = ep.keyParam ? `${ep.url}?${ep.keyParam}=${encodeURIComponent(key)}` : ep.url
|
|
28
47
|
const headers = ep.auth(key)
|
|
29
|
-
const res = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(
|
|
48
|
+
const res = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(8000) })
|
|
30
49
|
if (!res.ok) { const t = await res.text(); return { provider, error: `${res.status}: ${t.slice(0, 200)}` } }
|
|
31
50
|
const json = await res.json()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
return { provider, models: ep.pick(json) || [], last_ok_at: Date.now() }
|
|
52
|
+
} catch (e) { return { provider, error: String(e.message || e) } }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function probeAcp(provider) {
|
|
56
|
+
const b = ACP_BACKENDS[provider]
|
|
57
|
+
try {
|
|
58
|
+
const r = await fetch(b.url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}', signal: AbortSignal.timeout(2000) })
|
|
59
|
+
if (!r.ok) return { provider, error: `${r.status}`, models: b.staticModels }
|
|
60
|
+
return { provider, models: b.staticModels, last_ok_at: Date.now() }
|
|
61
|
+
} catch (e) { return { provider, error: String(e.message || e), models: b.staticModels } }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function probeCli(provider) {
|
|
65
|
+
if (provider !== 'claude-cli') return { provider, error: 'unknown_cli', models: [] }
|
|
66
|
+
try {
|
|
67
|
+
const { spawn } = await import('node:child_process')
|
|
68
|
+
const ok = await new Promise(res => {
|
|
69
|
+
const p = spawn('claude', ['--version'], { stdio: 'ignore', shell: false })
|
|
70
|
+
const t = setTimeout(() => { p.kill(); res(false) }, 3000)
|
|
71
|
+
p.on('exit', c => { clearTimeout(t); res(c === 0) })
|
|
72
|
+
p.on('error', () => { clearTimeout(t); res(false) })
|
|
73
|
+
})
|
|
74
|
+
if (!ok) return { provider, error: 'claude_cli_not_available', models: [] }
|
|
75
|
+
return { provider, models: CLI_BACKENDS['claude-cli'].models, last_ok_at: Date.now() }
|
|
76
|
+
} catch (e) { return { provider, error: String(e.message || e), models: [] } }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function listKnownProviders() {
|
|
80
|
+
return [...Object.keys(BRANDS), ...Object.keys(EXTRA), ...Object.keys(ACP_BACKENDS), ...Object.keys(CLI_BACKENDS)]
|
|
37
81
|
}
|
|
38
82
|
|
|
39
83
|
export async function discoverModels({ provider } = {}) {
|
|
40
|
-
const providers = provider ? [provider] :
|
|
41
|
-
const results = await Promise.all(providers.map(p =>
|
|
84
|
+
const providers = provider ? [provider] : listKnownProviders()
|
|
85
|
+
const results = await Promise.all(providers.map(p => {
|
|
86
|
+
if (BRANDS[p]) return probeBrand(p)
|
|
87
|
+
if (EXTRA[p]) return probeExtra(p)
|
|
88
|
+
if (ACP_BACKENDS[p]) return probeAcp(p)
|
|
89
|
+
if (CLI_BACKENDS[p]) return probeCli(p)
|
|
90
|
+
return Promise.resolve({ provider: p, error: 'unknown_provider' })
|
|
91
|
+
}))
|
|
42
92
|
const byProvider = {}
|
|
43
93
|
for (const r of results) byProvider[r.provider] = r
|
|
44
94
|
log.info('discovered', { count: results.length, ok: results.filter(r => !r.error).length })
|
|
@@ -51,12 +101,23 @@ export async function discoverAndPersist({ provider } = {}) {
|
|
|
51
101
|
const merged = { ...existing }
|
|
52
102
|
for (const [p, r] of Object.entries(result)) {
|
|
53
103
|
if (!r.error) merged[p] = { models: r.models, last_ok_at: r.last_ok_at }
|
|
54
|
-
else merged[p] = { ...(existing[p] || {}), error: r.error, last_error_at: Date.now() }
|
|
104
|
+
else merged[p] = { ...(existing[p] || {}), models: r.models || (existing[p]?.models) || [], error: r.error, last_error_at: Date.now() }
|
|
55
105
|
}
|
|
56
106
|
saveConfigValue('agent.discovered_models', merged)
|
|
57
107
|
return result
|
|
58
108
|
}
|
|
59
109
|
|
|
60
|
-
export function
|
|
61
|
-
|
|
110
|
+
export function flattenForOpenAI() {
|
|
111
|
+
const cached = getConfigValue('agent.discovered_models', {}) || {}
|
|
112
|
+
const queues = getConfigValue('agent.model_queues', {}) || {}
|
|
113
|
+
const data = []
|
|
114
|
+
for (const [provider, info] of Object.entries(cached)) {
|
|
115
|
+
for (const model of (info.models || [])) {
|
|
116
|
+
data.push({ id: `${provider}/${model}`, object: 'model', created: Math.floor((info.last_ok_at || Date.now()) / 1000), owned_by: provider })
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const name of Object.keys(queues)) {
|
|
120
|
+
data.push({ id: `queue/${name}`, object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'queue' })
|
|
121
|
+
}
|
|
122
|
+
return data
|
|
62
123
|
}
|
package/src/config.js
CHANGED
|
@@ -4,9 +4,9 @@ import yaml from 'js-yaml'
|
|
|
4
4
|
import { getFreddieHome } from './home.js'
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_CONFIG = {
|
|
7
|
-
_config_version:
|
|
7
|
+
_config_version: 3,
|
|
8
8
|
display: { skin: 'default', tool_progress_command: false, background_process_notifications: 'all' },
|
|
9
|
-
agent: { provider: 'anthropic', model: '', max_iterations: 90, fallback_model: null, save_trajectories: false, model_preference: [] },
|
|
9
|
+
agent: { provider: 'anthropic', model: '', max_iterations: 90, fallback_model: null, save_trajectories: false, model_preference: [], model_queues: {}, discovered_models: {} },
|
|
10
10
|
memory: { provider: null },
|
|
11
11
|
skills: { config: {} },
|
|
12
12
|
terminal: { cwd: null },
|
|
@@ -18,6 +18,7 @@ export const DEFAULT_CONFIG = {
|
|
|
18
18
|
const MIGRATIONS = {
|
|
19
19
|
1: cfg => cfg,
|
|
20
20
|
2: cfg => { if (!cfg.agent) cfg.agent = {}; if (!Array.isArray(cfg.agent.model_preference)) cfg.agent.model_preference = []; return cfg },
|
|
21
|
+
3: cfg => { if (!cfg.agent) cfg.agent = {}; if (!cfg.agent.model_queues || typeof cfg.agent.model_queues !== 'object') cfg.agent.model_queues = {}; if (!cfg.agent.discovered_models || typeof cfg.agent.discovered_models !== 'object') cfg.agent.discovered_models = {}; return cfg },
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function configPath() { return path.join(getFreddieHome(), 'config.yaml') }
|