freddie 0.0.83 → 0.0.84
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.84",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"plugsdk": "^1.0.15",
|
|
27
27
|
"xstate": "^5.31.0",
|
|
28
28
|
"zod": "^4.0.0",
|
|
29
|
-
"anentrypoint-design": "^0.0.
|
|
29
|
+
"anentrypoint-design": "^0.0.91",
|
|
30
30
|
"acptoapi": "^1.0.50"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|
|
@@ -73,6 +73,15 @@ export default {
|
|
|
73
73
|
const out = await runBatch({ prompts, concurrency: Number(opts.concurrency), model: opts.model })
|
|
74
74
|
console.log('batch:', out.id, '\nfile:', out.file, '\nresults:', out.results.length)
|
|
75
75
|
} })
|
|
76
|
+
C({ name: 'models', description: 'Discover working models per provider key', args: [{ name: 'action', default: 'discover' }, { name: 'provider' }], action: async (action, provider) => {
|
|
77
|
+
const { discoverAndPersist, listKnownProviders } = await import('../../src/agent/model-discovery.js')
|
|
78
|
+
if (action === 'providers') { for (const p of listKnownProviders()) console.log(p); return }
|
|
79
|
+
const result = await discoverAndPersist({ provider })
|
|
80
|
+
for (const [p, r] of Object.entries(result)) {
|
|
81
|
+
if (r.error) console.log(`${p.padEnd(12)} ✗ ${r.error}`)
|
|
82
|
+
else console.log(`${p.padEnd(12)} ✓ ${r.models.length} models — ${r.models.slice(0, 5).join(', ')}${r.models.length > 5 ? ', …' : ''}`)
|
|
83
|
+
}
|
|
84
|
+
} })
|
|
76
85
|
C({ name: 'dashboard', description: 'Boot web dashboard', options: [{ flag: '--port <port>', default: '0' }, { flag: '--cwd <dir>', default: '' }], action: async (opts) => {
|
|
77
86
|
if (opts.cwd) { const p = process.platform === 'win32' ? opts.cwd.replace(/^\/([a-z])\//i, '$1:/') : opts.cwd; process.chdir(p) }
|
|
78
87
|
const { createDashboard } = await import('../../src/web/server.js')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { resolveCallLLM } from '../../src/agent/llm_resolver.js'
|
|
2
|
+
import { logger } from '../../src/observability/log.js'
|
|
3
|
+
|
|
4
|
+
const log = logger('gui-llm-passthrough')
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'gui-llm-passthrough', surfaces: 'gui',
|
|
8
|
+
register({ gui }) {
|
|
9
|
+
gui.route('GET', '/v1/models', (_, res) => {
|
|
10
|
+
res.json({ object: 'list', data: [{ id: 'freddie/auto', object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'freddie' }] })
|
|
11
|
+
})
|
|
12
|
+
gui.route('POST', '/v1/chat/completions', async (req, res) => {
|
|
13
|
+
const { model, messages, tools, stream } = req.body || {}
|
|
14
|
+
if (!Array.isArray(messages) || messages.length === 0) return res.status(400).json({ error: { message: 'messages required' } })
|
|
15
|
+
try {
|
|
16
|
+
let provider, mdl
|
|
17
|
+
if (typeof model === 'string' && model.includes('/')) {
|
|
18
|
+
const idx = model.indexOf('/'); provider = model.slice(0, idx); mdl = model.slice(idx + 1)
|
|
19
|
+
}
|
|
20
|
+
const call = resolveCallLLM({ provider, model: mdl })
|
|
21
|
+
const out = await call({ messages, tools: tools?.map(t => ({ name: t.function?.name, description: t.function?.description, parameters: t.function?.parameters })) || [], model: mdl })
|
|
22
|
+
const id = 'chatcmpl-' + Math.random().toString(36).slice(2, 12)
|
|
23
|
+
const created = Math.floor(Date.now() / 1000)
|
|
24
|
+
const choice = { index: 0, message: { role: 'assistant', content: out.content || '', ...(out.tool_calls?.length ? { tool_calls: out.tool_calls.map(tc => ({ id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments || {}) } })) } : {}) }, finish_reason: 'stop' }
|
|
25
|
+
if (stream) {
|
|
26
|
+
res.setHeader('content-type', 'text/event-stream')
|
|
27
|
+
res.setHeader('cache-control', 'no-cache')
|
|
28
|
+
res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model: model || 'freddie/auto', choices: [{ index: 0, delta: { role: 'assistant', content: out.content || '' }, finish_reason: null }] })}\n\n`)
|
|
29
|
+
res.write(`data: ${JSON.stringify({ id, object: 'chat.completion.chunk', created, model: model || 'freddie/auto', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] })}\n\n`)
|
|
30
|
+
res.write('data: [DONE]\n\n')
|
|
31
|
+
res.end()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
res.json({ id, object: 'chat.completion', created, model: model || 'freddie/auto', choices: [choice], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } })
|
|
35
|
+
} catch (e) {
|
|
36
|
+
log.error('chat-completions-failed', { error: String(e.message || e) })
|
|
37
|
+
res.status(500).json({ error: { message: String(e.message || e), type: 'upstream_error' } })
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { discoverAndPersist, listKnownProviders } from '../../src/agent/model-discovery.js'
|
|
2
|
+
import { PROVIDER_KEYS, DEFAULTS } from '../../src/agent/llm_resolver.js'
|
|
3
|
+
import { getConfigValue } from '../../src/config.js'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'gui-models-discover', surfaces: 'gui',
|
|
7
|
+
register({ gui }) {
|
|
8
|
+
gui.route('GET', '/api/models/providers', (_, res) => res.json({ providers: listKnownProviders(), keys: PROVIDER_KEYS, defaults: DEFAULTS }))
|
|
9
|
+
gui.route('GET', '/api/models/cached', (_, res) => res.json(getConfigValue('agent.discovered_models', {}) || {}))
|
|
10
|
+
gui.route('POST', '/api/models/discover', async (req, res) => {
|
|
11
|
+
try { const provider = req.body?.provider; const result = await discoverAndPersist({ provider }); res.json(result) }
|
|
12
|
+
catch (e) { res.status(500).json({ error: String(e.message || e) }) }
|
|
13
|
+
})
|
|
14
|
+
},
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
2
|
+
import { PROVIDER_KEYS } from './llm_resolver.js'
|
|
3
|
+
import { resolveKey } from './credential_sources.js'
|
|
4
|
+
import { saveConfigValue, getConfigValue } from '../config.js'
|
|
5
|
+
import { logger } from '../observability/log.js'
|
|
6
|
+
|
|
7
|
+
const _require = createRequire(import.meta.url)
|
|
8
|
+
const sdk = _require('acptoapi')
|
|
9
|
+
const log = logger('model-discovery')
|
|
10
|
+
|
|
11
|
+
const ENDPOINTS = {
|
|
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
|
+
openai: { url: 'https://api.openai.com/v1/models', auth: k => ({ authorization: `Bearer ${k}` }), pick: j => (j.data || []).map(m => m.id) },
|
|
14
|
+
openrouter: { url: 'https://openrouter.ai/api/v1/models', auth: k => ({ authorization: `Bearer ${k}` }), pick: j => (j.data || []).map(m => m.id) },
|
|
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\//, '')) },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function probeProvider(provider) {
|
|
21
|
+
const ep = ENDPOINTS[provider]
|
|
22
|
+
if (!ep) return { provider, error: 'no_probe_endpoint' }
|
|
23
|
+
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
24
|
+
const key = resolved.value || process.env[PROVIDER_KEYS[provider]]
|
|
25
|
+
if (!key) return { provider, error: 'no_key' }
|
|
26
|
+
try {
|
|
27
|
+
const url = ep.keyParam ? `${ep.url}?${ep.keyParam}=${encodeURIComponent(key)}` : ep.url
|
|
28
|
+
const headers = ep.auth(key)
|
|
29
|
+
const res = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(15000) })
|
|
30
|
+
if (!res.ok) { const t = await res.text(); return { provider, error: `${res.status}: ${t.slice(0, 200)}` } }
|
|
31
|
+
const json = await res.json()
|
|
32
|
+
const models = ep.pick(json) || []
|
|
33
|
+
return { provider, models, last_ok_at: Date.now() }
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return { provider, error: String(e.message || e) }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function discoverModels({ provider } = {}) {
|
|
40
|
+
const providers = provider ? [provider] : Object.keys(ENDPOINTS)
|
|
41
|
+
const results = await Promise.all(providers.map(p => probeProvider(p)))
|
|
42
|
+
const byProvider = {}
|
|
43
|
+
for (const r of results) byProvider[r.provider] = r
|
|
44
|
+
log.info('discovered', { count: results.length, ok: results.filter(r => !r.error).length })
|
|
45
|
+
return byProvider
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function discoverAndPersist({ provider } = {}) {
|
|
49
|
+
const result = await discoverModels({ provider })
|
|
50
|
+
const existing = getConfigValue('agent.discovered_models', {}) || {}
|
|
51
|
+
const merged = { ...existing }
|
|
52
|
+
for (const [p, r] of Object.entries(result)) {
|
|
53
|
+
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() }
|
|
55
|
+
}
|
|
56
|
+
saveConfigValue('agent.discovered_models', merged)
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function listKnownProviders() {
|
|
61
|
+
return Object.keys(ENDPOINTS)
|
|
62
|
+
}
|