freddie 0.0.123 → 0.0.125

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.123",
3
+ "version": "0.0.125",
4
4
  "type": "module",
5
5
  "description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
6
6
  "bin": {
@@ -18,16 +18,14 @@
18
18
  "start": "node bin/freddie.js",
19
19
  "test": "node test.js",
20
20
  "build:browser": "vite build --config vite.browser.config.js",
21
+ "build:browser-shim": "node scripts/browser-shim-build/build.mjs",
21
22
  "link-local-design": "node scripts/link-local-design.mjs"
22
23
  },
23
24
  "dependencies": {
24
- "@anentrypoint/libsql-plugkit-client": "github:AnEntrypoint/libsql-plugkit-client",
25
25
  "@libsql/client": "^0.5.0",
26
- "@mariozechner/pi-agent-core": "^0.70.6",
27
26
  "@mariozechner/pi-ai": "^0.70.6",
28
- "@mariozechner/pi-coding-agent": "^0.70.6",
29
27
  "@mariozechner/pi-tui": "^0.70.6",
30
- "acptoapi": "^1.0.140",
28
+ "acptoapi": "^1.0.144",
31
29
  "anentrypoint-design": "latest",
32
30
  "commander": "^14.0.0",
33
31
  "express": "^5.0.0",
@@ -36,10 +34,8 @@
36
34
  "gm-plugkit": "^2.0.1535",
37
35
  "gm-skill": "latest",
38
36
  "js-yaml": "^4.1.0",
39
- "libsql-plugkit-client": "^0.0.10",
40
37
  "plugsdk": "^1.0.20",
41
- "xstate": "^5.31.0",
42
- "zod": "^4.0.0"
38
+ "xstate": "^5.31.0"
43
39
  },
44
40
  "optionalDependencies": {
45
41
  "@libsql/darwin-arm64": "0.3.19",
@@ -85,6 +81,7 @@
85
81
  "xstate"
86
82
  ],
87
83
  "devDependencies": {
84
+ "esbuild": "^0.28.0",
88
85
  "vite": "^8.0.13"
89
86
  }
90
87
  }
@@ -11,10 +11,15 @@ const envVal = (k) => { try { return (typeof process !== 'undefined' && process.
11
11
  const ACPTOAPI_TIMEOUT_MS = Number(envVal('FREDDIE_LLM_TIMEOUT_MS')) || 240000
12
12
 
13
13
  export function getAcptoapiUrl() {
14
- // Retained for callers that still read a URL for display/logging purposes
15
- // (the dashboard health row, CLI banner) -- there is no listening port to
16
- // reach any more; this is informational only.
17
- return envVal('FREDDIE_LLM_URL') || 'in-process (acptoapi)'
14
+ // Returns the configured dialable acptoapi URL, or null when unset. Most
15
+ // callers (the dashboard health row, CLI banner) treat this as display/logging
16
+ // only -- there is no listening port required for the in-process callLLM()
17
+ // path below. However codex_responses_adapter.js, gemini_native_adapter.js,
18
+ // image_gen_provider.js, and model-discovery.js still fetch() this value as a
19
+ // live HTTP base and DO require a real dialable URL (FREDDIE_LLM_URL set) --
20
+ // they must guard against a null return rather than building a request
21
+ // against a placeholder string.
22
+ return envVal('FREDDIE_LLM_URL') || null
18
23
  }
19
24
 
20
25
  export function getAcptoapiModel() {
@@ -4,7 +4,9 @@ import { isCodexModel } from '../cli/codex_models.js'
4
4
 
5
5
  export async function chat({ input, model = 'o3-mini', tools = [], reasoning_effort = 'medium' } = {}) {
6
6
  if (!isCodexModel(model)) console.warn('[codex_responses] non-codex model: ' + model)
7
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
7
+ const url = getAcptoapiUrl()
8
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
9
+ const base = url.replace(/\/v1\/?$/, '')
8
10
  const r = await fetch(base + '/v1/responses', {
9
11
  method: 'POST',
10
12
  headers: { authorization: 'Bearer none', 'content-type': 'application/json' },
@@ -3,7 +3,9 @@ import { getAcptoapiUrl } from './acptoapi-bridge.js'
3
3
  import { adaptToolForGemini, adaptMessagesForGemini } from './gemini_schema.js'
4
4
 
5
5
  export async function chat({ messages, model = 'gemini-2.5-flash', tools = [] } = {}) {
6
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
6
+ const acptoapiUrl = getAcptoapiUrl()
7
+ if (!acptoapiUrl) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
8
+ const base = acptoapiUrl.replace(/\/v1\/?$/, '')
7
9
  const url = `${base}/v1beta/models/${model}:generateContent`
8
10
  const body = { contents: adaptMessagesForGemini(messages), ...(tools.length ? { tools: [{ function_declarations: tools.map(adaptToolForGemini) }] } : {}) }
9
11
  const r = await fetch(url, {
@@ -5,7 +5,9 @@ const PROVIDERS = ['openai', 'replicate', 'stability']
5
5
 
6
6
  export async function generate({ provider = 'openai', prompt, size, model } = {}) {
7
7
  if (!PROVIDERS.includes(provider)) throw new Error('unknown image provider: ' + provider)
8
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
8
+ const url = getAcptoapiUrl()
9
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
10
+ const base = url.replace(/\/v1\/?$/, '')
9
11
  const body = provider === 'replicate'
10
12
  ? { version: model || 'black-forest-labs/flux-schnell', input: { prompt } }
11
13
  : { model: model || 'gpt-image-1', prompt, size }
@@ -5,6 +5,22 @@ import { parseTextToolCalls } from './tool_call_text.js'
5
5
  import * as sdkNs from 'acptoapi'
6
6
  export { matrixUsable } from './model-matrix.js'
7
7
 
8
+ // Reachability memoization: bridgeReachable() now performs a REAL LLM call
9
+ // (acptoapi-bridge.js's isReachable() sends a live 'ping' completion), so
10
+ // calling it on every turn doubles LLM cost/latency. Cache the result for a
11
+ // short TTL so a burst of turns within the window reuses one probe. Does NOT
12
+ // touch acptoapi-bridge.js's exported isReachable -- health-check/dashboard
13
+ // callers still need a live, uncached probe.
14
+ let _lastReachable = { at: 0, ok: false }
15
+ const REACHABLE_TTL_MS = 5000
16
+ async function cachedReachable() {
17
+ const now = Date.now()
18
+ if (now - _lastReachable.at < REACHABLE_TTL_MS) return _lastReachable.ok
19
+ const ok = await bridgeReachable()
20
+ _lastReachable = { at: now, ok }
21
+ return ok
22
+ }
23
+
8
24
  // `acptoapi` is externalized by vite (browser) so the host environment
9
25
  // supplies it (thebird ships docs/lib/acptoapi-browser.js via importmap).
10
26
  // Node CLI gets the real CJS package. Defensive `|| {}` keeps the bundle
@@ -78,7 +94,7 @@ async function buildModel({ provider, model, inputModel }) {
78
94
  const keyed = Array.isArray(auto) ? auto.filter(l => { const p = l.model.split('/')[0]; const env = PROVIDER_KEYS[p]; return env && process.env[env] }) : []
79
95
  if (keyed.length) return keyed.map(l => l.model).join(', ')
80
96
  // No local provider keys — delegate to acptoapi if reachable.
81
- if (await bridgeReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
97
+ if (await cachedReachable()) return process.env.FREDDIE_LLM_MODEL || 'auto'
82
98
  return null
83
99
  }
84
100
 
@@ -87,12 +103,12 @@ export function resolveCallLLM({ provider, model } = {}) {
87
103
  const m = await buildModel({ provider, model, inputModel: input.model })
88
104
  if (!m) {
89
105
  const status = typeof sdk.getStatus === 'function' ? sdk.getStatus().map(s => `${s.provider}(ok=${s.ok},fails=${s.failCount})`).join(', ') : ''
90
- throw new Error('no LLM backend reachable: set a provider API key or start acptoapi (http://127.0.0.1:4800/v1)' + (status ? ' | sampler: ' + status : ''))
106
+ throw new Error('no LLM backend reachable: set a provider API key or FREDDIE_LLM_MODEL' + (status ? ' | sampler: ' + status : ''))
91
107
  }
92
108
  try {
93
109
  const isSimple = typeof m === 'string' && !m.includes(',') && !/^queue\//.test(m)
94
110
 
95
- if (isSimple && await bridgeReachable()) {
111
+ if (isSimple && await cachedReachable()) {
96
112
  return await bridgeCall({ ...input, model: m })
97
113
  }
98
114
 
@@ -105,7 +121,9 @@ export function resolveCallLLM({ provider, model } = {}) {
105
121
  if (m.includes(',') || /^queue\//.test(m)) opts.matrixSource = process.env.FREDDIE_MATRIX_URL || MATRIX_FILE
106
122
 
107
123
  if (typeof sdk.chat !== 'function') {
108
- // Browser context: no node-side sdk; route via HTTP bridge.
124
+ // Browser/no-sdk context: fall back to acptoapi-bridge's in-process
125
+ // call (may be a no-op/broken in true browser bundles since acptoapi
126
+ // is externalized for vite -- unverified post-rewrite, see build:browser).
109
127
  return await bridgeCall({ ...input, model: m })
110
128
  }
111
129
  const r = await sdk.chat(opts)
@@ -17,7 +17,9 @@ export function listKnownProviders() {
17
17
  }
18
18
 
19
19
  export async function discoverModels({ provider } = {}) {
20
- const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
20
+ const url = getAcptoapiUrl()
21
+ if (!url) throw new Error('FREDDIE_LLM_URL must be set for this adapter (acptoapi is in-process only otherwise)')
22
+ const base = url.replace(/\/v1\/?$/, '')
21
23
  try {
22
24
  const r = await fetch(base + '/v1/models', {
23
25
  headers: { authorization: 'Bearer none' },
package/src/host/index.js CHANGED
@@ -5,7 +5,7 @@ import { getFreddieHome } from '../home.js'
5
5
  import { applyActiveProjectFromRegistry } from '../projects.js'
6
6
 
7
7
  let _host = null
8
- let _loaded = false
8
+ let _loadPromise = null
9
9
 
10
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
11
  const REPO_PLUGINS = path.resolve(__dirname, '..', '..', 'plugins')
@@ -16,18 +16,24 @@ export function host() {
16
16
  }
17
17
 
18
18
  export async function bootHost(extraRoots = []) {
19
- const h = host()
20
- if (_loaded) return h
21
- _loaded = true
22
- if (!process.env.FREDDIE_HOME && !process.env.FREDDIE_PROFILE) applyActiveProjectFromRegistry()
23
- const roots = [REPO_PLUGINS, path.join(getFreddieHome(), 'plugins'), path.join(process.cwd(), '.freddie', 'plugins'), ...extraRoots]
24
- const plugins = await discoverPlugins(roots)
25
- await h.load(plugins)
26
- const ccRoots = [path.join(getFreddieHome(), 'cc-plugins'), path.join(process.cwd(), '.freddie', 'cc-plugins')]
27
- await h.loadCcPlugins(ccRoots)
28
- const extra = (process.env.FREDDIE_EXTRA_CC_ROOTS || '').split(path.delimiter).filter(Boolean)
29
- for (const r of [__dirname, process.cwd(), ...extra]) await h.loadCcFromNodeModules(r)
30
- return h
19
+ // Memoize the IN-FLIGHT promise, not a boolean flag: a boolean set true
20
+ // before the awaits below complete let concurrent callers observe a
21
+ // partially-loaded host. Returning the same promise means every caller
22
+ // (including ones that arrive mid-load) awaits the exact same completion.
23
+ if (_loadPromise) return _loadPromise
24
+ _loadPromise = (async () => {
25
+ const h = host()
26
+ if (!process.env.FREDDIE_HOME && !process.env.FREDDIE_PROFILE) applyActiveProjectFromRegistry()
27
+ const roots = [REPO_PLUGINS, path.join(getFreddieHome(), 'plugins'), path.join(process.cwd(), '.freddie', 'plugins'), ...extraRoots]
28
+ const plugins = await discoverPlugins(roots)
29
+ await h.load(plugins)
30
+ const ccRoots = [path.join(getFreddieHome(), 'cc-plugins'), path.join(process.cwd(), '.freddie', 'cc-plugins')]
31
+ await h.loadCcPlugins(ccRoots)
32
+ const extra = (process.env.FREDDIE_EXTRA_CC_ROOTS || '').split(path.delimiter).filter(Boolean)
33
+ for (const r of [__dirname, process.cwd(), ...extra]) await h.loadCcFromNodeModules(r)
34
+ return h
35
+ })()
36
+ return _loadPromise
31
37
  }
32
38
 
33
- export function resetHostForTests() { _host = null; _loaded = false }
39
+ export function resetHostForTests() { _host = null; _loadPromise = null }
@@ -1,24 +0,0 @@
1
-
2
- import path from 'path';
3
- const F='C:/dev/freddie';
4
- const R=p=>path.resolve(p);
5
- const ALIAS={
6
- [R(F+'/src/host/index.js')]: R(F+'/src/agent/__browser_shims/host.js'),
7
- [R(F+'/src/toolsets.js')]: R(F+'/src/agent/__browser_shims/toolsets.js'),
8
- [R(F+'/src/agent/llm_resolver.js')]: R(F+'/src/agent/__browser_shims/llm_resolver.js'),
9
- [R(F+'/src/observability/log.js')]: R(F+'/src/agent/__browser_shims/log.js'),
10
- [R(F+'/src/config.js')]: R(F+'/src/agent/__browser_shims/config.js'),
11
- [R(F+'/src/home.js')]: R(F+'/src/agent/__browser_shims/home.js'),
12
- };
13
- export default {
14
- name: 'freddie-browser-alias',
15
- setup(build){
16
- build.onResolve({filter: /.*/}, async args=>{
17
- if(args.path.startsWith('node:') || (!args.path.startsWith('.') && !path.isAbsolute(args.path))) return null;
18
- const resolved = path.resolve(args.resolveDir||'', args.path);
19
- const candidates=[resolved, resolved+'.js', resolved+'/index.js'];
20
- for(const c of candidates) if(ALIAS[c]) return { path: ALIAS[c] };
21
- return null;
22
- });
23
- }
24
- };
@@ -1,18 +0,0 @@
1
-
2
- import * as esbuild from 'esbuild';
3
- import alias from './alias-plugin.mjs';
4
- await esbuild.build({
5
- entryPoints: ['C:/dev/freddie/src/agent/__browser_shims/entry.js'],
6
- bundle: true,
7
- format: 'esm',
8
- platform: 'browser',
9
- conditions: ['browser','module','import'],
10
- outfile: 'C:/dev/thebird/docs/freddie-runtime.js',
11
- plugins: [alias],
12
- legalComments: 'none',
13
- minify: false,
14
- sourcemap: false,
15
- logLevel: 'info',
16
- external: [],
17
- });
18
- console.log('bundle ok');