@schlessera/brain-ui-server 0.4.0 → 0.5.1

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/README.md CHANGED
@@ -50,16 +50,30 @@ different options reconfigures the first app rather than creating a second.
50
50
  index.
51
51
  - **Render seam** — `POST /api/render` answers 501 unless the deployment
52
52
  injects a renderer (see `@schlessera/brain-render-puppeteer`).
53
+ - **Model catalog** — Anthropic model discovery (via
54
+ `@schlessera/brain-backend-claude`) behind `GET /api/models`, with a
55
+ server-side hidden set (`PUT /api/models/hidden`) and a manual
56
+ `POST /api/models/refresh`. `/api/providers` serves the same roster minus the
57
+ hidden entries; hidden profiles still resolve for sessions pinned to them.
53
58
 
54
59
  ## Environment
55
60
 
56
61
  The package reads the same env contract the brain-ui deployment documents:
57
62
  `AUTH_MODE`, `BRAIN_UI_PASSWORD_HASH`, `COOKIE_SECRET`, `TRUST_PROXY`,
58
63
  `WEBAUTHN_*`, `BRAIN_PATH`, `DB_PATH`, `AGENT_BACKEND`,
59
- `BRAIN_UI_CLAUDE_PROFILES`, `MAX_CONCURRENT_SESSIONS`, `DEEPGRAM_API_KEY`,
64
+ `BRAIN_UI_CLAUDE_PROFILES`, `BRAIN_UI_MODEL_DISCOVERY`,
65
+ `BRAIN_UI_MODEL_TTL_HOURS`, `MAX_CONCURRENT_SESSIONS`, `DEEPGRAM_API_KEY`,
60
66
  `VOICE_*`, `NOMINATIM_*`, `ALLOWED_ORIGINS`. `createApp()` options win over
61
67
  env where both exist.
62
68
 
69
+ Model discovery is on by default and needs no configuration beyond the Claude
70
+ credential the agent already uses (`CLAUDE_CODE_OAUTH_TOKEN` or
71
+ `ANTHROPIC_API_KEY`). `BRAIN_UI_MODEL_DISCOVERY=0` turns it off — leaving the
72
+ picker to `BRAIN_UI_CLAUDE_PROFILES` alone — and `BRAIN_UI_MODEL_TTL_HOURS`
73
+ (default 24) sets how long a discovered roster is served before a background
74
+ refresh. It defaults to OFF under a test runner (`NODE_ENV=test`) so suites
75
+ don't depend on network access; set the var explicitly to opt in.
76
+
63
77
  ## Versioning
64
78
 
65
79
  Versions in lockstep with all `@schlessera/brain-*` packages.
@@ -1,5 +1,13 @@
1
+ import { type ModelSource } from "@schlessera/brain-backend-claude";
1
2
  import type { ProviderInfo } from "@schlessera/brain-ui-sdk";
2
3
  import type { AgentBackend, BackendCapabilities } from "@schlessera/brain-ui-sdk/server";
4
+ /** Drop the memo so the next read reflects a refresh or a settings change. */
5
+ export declare function invalidateProfiles(): void;
6
+ /**
7
+ * The Claude backend's discovery source, once the registry is built. Null when
8
+ * discovery is disabled or the deployment runs a different backend.
9
+ */
10
+ export declare function getModelSource(): Promise<ModelSource | null>;
3
11
  /** All configured backends, with the default backend first. */
4
12
  export declare function getBackends(): Promise<AgentBackend[]>;
5
13
  /** Find a configured backend by its stable id. */
@@ -12,8 +20,15 @@ export declare function getDefaultBackendId(): Promise<string>;
12
20
  export declare function getBackendForProfile(profileId: string): Promise<AgentBackend | undefined>;
13
21
  /** Resolve a persisted backend id, falling back for legacy/unknown sessions. */
14
22
  export declare function getBackendForSession(backendId: string | null | undefined): Promise<AgentBackend>;
15
- /** List every available profile, tagged with its owning backend id. */
16
- export declare function listAllProviders(): Promise<ProviderInfo[]>;
23
+ /**
24
+ * Every available profile, tagged with its owning backend id.
25
+ *
26
+ * Hidden profiles are omitted by default (this feeds the picker); the settings
27
+ * screen passes `includeHidden` to render the full catalog.
28
+ */
29
+ export declare function listAllProviders(options?: {
30
+ includeHidden?: boolean;
31
+ }): Promise<ProviderInfo[]>;
17
32
  /** Per-backend capability metadata exposed by the providers route. */
18
33
  export declare function getBackendsInfo(): Promise<Record<string, {
19
34
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;AAkHzC,+DAA+D;AAC/D,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAE3D;AAED,kDAAkD;AAClD,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAEnC;AAED,4DAA4D;AAC5D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAG/D;AAED,4CAA4C;AAC5C,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3D;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAInC;AAED,gFAAgF;AAChF,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,OAAO,CAAC,YAAY,CAAC,CAMvB;AAED,uEAAuE;AACvE,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAQhE;AAED,sEAAsE;AACtE,wBAAsB,eAAe,IAAI,OAAO,CAC9C,MAAM,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAClE,CAQA;AAED,0EAA0E;AAC1E,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE9D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EAAE,EACxB,gBAAgB,SAAwB,GACvC,IAAI,CAEN"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;AA6FzC,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAGlE;AAwHD,+DAA+D;AAC/D,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAE3D;AAED,kDAAkD;AAClD,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAEnC;AAED,4DAA4D;AAC5D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAG/D;AAED,4CAA4C;AAC5C,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE3D;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAOnC;AAED,gFAAgF;AAChF,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,OAAO,CAAC,YAAY,CAAC,CAMvB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,YAAY,EAAE,CAAC,CAUzB;AAED,sEAAsE;AACtE,wBAAsB,eAAe,IAAI,OAAO,CAC9C,MAAM,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAClE,CAQA;AAED,0EAA0E;AAC1E,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,IAAI,CAI3C;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAG9D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EAAE,EACxB,gBAAgB,SAAwB,GACvC,IAAI,CAGN"}
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "module";
2
- import { createClaudeBackend, defineProfiles, } from "@schlessera/brain-backend-claude";
2
+ import { createClaudeBackend, createModelSource, defineProfiles, } from "@schlessera/brain-backend-claude";
3
+ import { getHiddenModelIds } from "../db/settings.js";
3
4
  /**
4
5
  * Backend registry for the deployment. Claude is always available.
5
6
  * `AGENT_BACKEND=pi` retains the legacy single-backend deployment mode when the
@@ -16,7 +17,10 @@ const BRAIN_PATH = process.env.BRAIN_PATH || `${process.env.HOME || "/root"}/bra
16
17
  // /usr/local/bin/claude in the container). The Agent SDK can't always
17
18
  // auto-discover it, so hand it the explicit path; override with CLAUDE_CODE_PATH.
18
19
  const CLAUDE_CODE_PATH = process.env.CLAUDE_CODE_PATH || "/usr/local/bin/claude";
20
+ const PROFILE_MEMO_MS = 5_000;
19
21
  let cachedRegistry = null;
22
+ let profileSnapshot = null;
23
+ let modelSource = null;
20
24
  async function createRegistry(backends, defaultBackendId) {
21
25
  if (backends.length === 0) {
22
26
  throw new Error("Backend registry requires at least one backend.");
@@ -29,29 +33,95 @@ async function createRegistry(backends, defaultBackendId) {
29
33
  byId.get(resolvedDefaultId),
30
34
  ...backends.filter((backend) => backend.id !== resolvedDefaultId),
31
35
  ];
32
- const listedProfiles = await Promise.all(ordered.map(async (backend) => ({
33
- backend,
34
- profiles: await backend.listProfiles(),
35
- })));
36
- const profileOwners = new Map();
37
- const profiles = new Map();
38
- for (const listed of listedProfiles) {
39
- profiles.set(listed.backend.id, listed.profiles);
40
- for (const profile of listed.profiles) {
41
- const owner = profileOwners.get(profile.id);
42
- if (owner && owner !== listed.backend.id) {
43
- throw new Error(`Profile id collision across backends: "${profile.id}" is exposed by "${owner}" and "${listed.backend.id}".`);
36
+ return { backends: ordered, byId, defaultBackendId: resolvedDefaultId };
37
+ }
38
+ /** Recompute the profile roster (memoized), asserting cross-backend id uniqueness. */
39
+ async function getProfileSnapshot() {
40
+ if (profileSnapshot && Date.now() - profileSnapshot.at < PROFILE_MEMO_MS) {
41
+ return profileSnapshot;
42
+ }
43
+ const registry = await getRegistry();
44
+ const byBackend = new Map();
45
+ const owners = new Map();
46
+ for (const backend of registry.backends) {
47
+ const profiles = await backend.listProfiles();
48
+ byBackend.set(backend.id, profiles);
49
+ for (const profile of profiles) {
50
+ const owner = owners.get(profile.id);
51
+ if (owner && owner !== backend.id) {
52
+ throw new Error(`Profile id collision across backends: "${profile.id}" is exposed by "${owner}" and "${backend.id}".`);
44
53
  }
45
- profileOwners.set(profile.id, listed.backend.id);
54
+ owners.set(profile.id, backend.id);
46
55
  }
47
56
  }
48
- return {
49
- backends: ordered,
50
- byId,
51
- defaultBackendId: resolvedDefaultId,
52
- profileOwners,
53
- profiles,
54
- };
57
+ profileSnapshot = { at: Date.now(), byBackend, owners };
58
+ return profileSnapshot;
59
+ }
60
+ /** Drop the memo so the next read reflects a refresh or a settings change. */
61
+ export function invalidateProfiles() {
62
+ profileSnapshot = null;
63
+ }
64
+ /**
65
+ * The Claude backend's discovery source, once the registry is built. Null when
66
+ * discovery is disabled or the deployment runs a different backend.
67
+ */
68
+ export async function getModelSource() {
69
+ await getRegistry();
70
+ return modelSource;
71
+ }
72
+ /**
73
+ * Hidden profile ids, from the settings table. Presentation only — a hidden
74
+ * profile still resolves for sessions pinned to it. A db read failure must not
75
+ * take the picker down, so it degrades to "nothing hidden".
76
+ */
77
+ function hiddenIds() {
78
+ try {
79
+ return new Set(getHiddenModelIds());
80
+ }
81
+ catch (err) {
82
+ console.warn(`[models] Could not read hidden models: ${err instanceof Error ? err.message : String(err)}`);
83
+ return new Set();
84
+ }
85
+ }
86
+ /**
87
+ * Model discovery is on unless explicitly disabled — except under a test
88
+ * runner, where the default flips to off: discovery is a live call to the
89
+ * Anthropic API, and a suite that silently depends on network access (and on
90
+ * whatever credentials the developer happens to have exported) is flaky by
91
+ * construction. Tests that want it set BRAIN_UI_MODEL_DISCOVERY=1 explicitly.
92
+ */
93
+ function modelDiscoveryEnabled() {
94
+ const raw = process.env.BRAIN_UI_MODEL_DISCOVERY?.trim().toLowerCase();
95
+ if (raw)
96
+ return !(raw === "0" || raw === "off" || raw === "false");
97
+ return process.env.NODE_ENV !== "test";
98
+ }
99
+ /** How long a discovery result stays fresh. Default 24h. */
100
+ function modelTtlMs() {
101
+ const raw = Number(process.env.BRAIN_UI_MODEL_TTL_HOURS);
102
+ const hours = Number.isFinite(raw) && raw > 0 ? raw : 24;
103
+ return hours * 60 * 60 * 1000;
104
+ }
105
+ /**
106
+ * Declared profiles (built-in default + BRAIN_UI_CLAUDE_PROFILES) plus every
107
+ * discovered model whose id isn't already declared. Declared wins: the env stays
108
+ * an override mechanism, and a hand-pinned entry keeps its label and endpoint.
109
+ *
110
+ * Memoized on the discovered array's identity — `createModelSource` swaps the
111
+ * array only on a successful refresh, so steady state is one comparison.
112
+ */
113
+ let mergeCache = null;
114
+ function mergeDiscovered(declared, discovered) {
115
+ if (mergeCache &&
116
+ mergeCache.declared === declared &&
117
+ mergeCache.discovered === discovered) {
118
+ return mergeCache.result;
119
+ }
120
+ const declaredIds = new Set(declared.map((profile) => profile.id));
121
+ const extra = discovered.filter((input) => !declaredIds.has(input.id));
122
+ const result = [...declared, ...defineProfiles(extra)];
123
+ mergeCache = { declared, discovered, result };
124
+ return result;
55
125
  }
56
126
  async function buildRegistry() {
57
127
  const primary = (process.env.AGENT_BACKEND || "claude")
@@ -61,10 +131,20 @@ async function buildRegistry() {
61
131
  const pi = buildPiBackend();
62
132
  return createRegistry([pi], pi.id);
63
133
  }
134
+ // Declared profiles are resolved EAGERLY so a malformed
135
+ // BRAIN_UI_CLAUDE_PROFILES still fails at boot rather than on first request.
136
+ const declared = loadClaudeProfiles();
137
+ modelSource = createModelSource({
138
+ brainPath: BRAIN_PATH,
139
+ enabled: modelDiscoveryEnabled(),
140
+ ttlMs: modelTtlMs(),
141
+ });
64
142
  const claude = createClaudeBackend({
65
143
  brainPath: BRAIN_PATH,
66
144
  claudeCodePath: CLAUDE_CODE_PATH,
67
- profiles: loadClaudeProfiles(),
145
+ // A function, not an array: discovery refreshes in the background and the
146
+ // new roster has to be visible without restarting the process.
147
+ profiles: () => mergeDiscovered(declared, modelSource?.list() ?? []),
68
148
  });
69
149
  const backends = [claude];
70
150
  // AGENT_BACKEND must name a configured backend. The in-process options are
@@ -102,7 +182,10 @@ export async function getDefaultBackendId() {
102
182
  /** Resolve the backend that owns a globally unique profile id. */
103
183
  export async function getBackendForProfile(profileId) {
104
184
  const registry = await getRegistry();
105
- const backendId = registry.profileOwners.get(profileId);
185
+ // Deliberately resolved against the UNFILTERED roster: a session pinned to a
186
+ // profile the user later hid must keep running.
187
+ const snapshot = await getProfileSnapshot();
188
+ const backendId = snapshot.owners.get(profileId);
106
189
  return backendId ? registry.byId.get(backendId) : undefined;
107
190
  }
108
191
  /** Resolve a persisted backend id, falling back for legacy/unknown sessions. */
@@ -114,13 +197,19 @@ export async function getBackendForSession(backendId) {
114
197
  }
115
198
  return getDefaultBackend();
116
199
  }
117
- /** List every available profile, tagged with its owning backend id. */
118
- export async function listAllProviders() {
200
+ /**
201
+ * Every available profile, tagged with its owning backend id.
202
+ *
203
+ * Hidden profiles are omitted by default (this feeds the picker); the settings
204
+ * screen passes `includeHidden` to render the full catalog.
205
+ */
206
+ export async function listAllProviders(options = {}) {
119
207
  const registry = await getRegistry();
120
- return registry.backends.flatMap((backend) => (registry.profiles.get(backend.id) ?? []).map((profile) => ({
121
- ...profile,
122
- backendId: backend.id,
123
- })));
208
+ const snapshot = await getProfileSnapshot();
209
+ const hidden = options.includeHidden ? new Set() : hiddenIds();
210
+ return registry.backends.flatMap((backend) => (snapshot.byBackend.get(backend.id) ?? [])
211
+ .filter((profile) => !hidden.has(profile.id))
212
+ .map((profile) => ({ ...profile, backendId: backend.id })));
124
213
  }
125
214
  /** Per-backend capability metadata exposed by the providers route. */
126
215
  export async function getBackendsInfo() {
@@ -137,14 +226,18 @@ export async function getBackend() {
137
226
  /** Test seam: drop the cached registry so later access rebuilds from env. */
138
227
  export function resetBackendForTests() {
139
228
  cachedRegistry = null;
229
+ profileSnapshot = null;
230
+ modelSource = null;
140
231
  }
141
232
  /** Test seam: install one fake backend as the complete registry. */
142
233
  export function setBackendForTests(backend) {
143
234
  cachedRegistry = createRegistry([backend], backend.id);
235
+ profileSnapshot = null;
144
236
  }
145
237
  /** Test seam: install a complete fake registry with an explicit default. */
146
238
  export function setBackendsForTests(backends, defaultBackendId = backends[0]?.id ?? "") {
147
239
  cachedRegistry = createRegistry(backends, defaultBackendId);
240
+ profileSnapshot = null;
148
241
  }
149
242
  // The built-in default profile's model. The Claude backend historically pinned
150
243
  // the default to a specific model; after the SDK extraction the pin was lost and
@@ -161,6 +254,7 @@ function builtinDefaultProfiles() {
161
254
  vendor: "anthropic",
162
255
  model: BUILTIN_DEFAULT_MODEL,
163
256
  modelAliases: true,
257
+ source: "builtin",
164
258
  },
165
259
  ]);
166
260
  }
@@ -200,7 +294,11 @@ function loadClaudeProfiles() {
200
294
  }
201
295
  seen.add(input.id);
202
296
  }
203
- return [...base, ...defineProfiles(inputs)];
297
+ const declared = inputs.map((input) => ({
298
+ source: "declared",
299
+ ...input,
300
+ }));
301
+ return [...base, ...defineProfiles(declared)];
204
302
  }
205
303
  /**
206
304
  * Optional pi backend. @schlessera/brain-backend-pi is NOT a hard dependency — a
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/agent/backend.ts"],"names":[],"mappings":"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"}
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAkB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EAuFvD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EAwFvD"}
package/dist/app.js CHANGED
@@ -10,6 +10,7 @@ import { voiceRoutes } from "./routes/voice.js";
10
10
  import { filesRoutes } from "./routes/files.js";
11
11
  import { createRenderRoutes } from "./routes/render.js";
12
12
  import { providerRoutes } from "./routes/providers.js";
13
+ import { modelRoutes } from "./routes/models.js";
13
14
  import { resolveAuthMode, assertAuthConfig, authGuard, authRoutes, isWsAuthorized, } from "./middleware/auth.js";
14
15
  import { passkeyPublicRoutes, passkeyManagementRoutes, passwordLoginDisabled, assertPasskeyConfig, } from "./middleware/passkeys.js";
15
16
  import { configureDb } from "./db/client.js";
@@ -89,6 +90,7 @@ export function createApp(options = {}) {
89
90
  app.route("/api", filesRoutes);
90
91
  app.route("/api", createRenderRoutes(options.renderer));
91
92
  app.route("/api", providerRoutes);
93
+ app.route("/api", modelRoutes);
92
94
  // WebSocket endpoint. Browsers can't set headers on the WS handshake, so the
93
95
  // upgrade authenticates via the session cookie (or IP/proxy header) INSIDE
94
96
  // the handler — no header-modifying middleware may sit on this route.
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function getSetting<T>(key: string, fallback: T): T;
2
+ export declare function setSetting(key: string, value: unknown): void;
3
+ /** Profile ids the user keeps out of the model picker. */
4
+ export declare function getHiddenModelIds(): string[];
5
+ /** Replace the hidden set (the client always sends the full list, not a delta). */
6
+ export declare function setHiddenModelIds(ids: string[]): void;
7
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/db/settings.ts"],"names":[],"mappings":"AAYA,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAWzD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAO5D;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAI5C;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAGrD"}
@@ -0,0 +1,42 @@
1
+ // Server-side settings: a JSON-per-key KV table (migration 006).
2
+ //
3
+ // These are deployment-wide preferences that must follow the user across
4
+ // devices, so they live in SQLite on the persisted volume rather than in the
5
+ // browser. Values are stored as JSON text; readers are total — a missing or
6
+ // corrupt row degrades to the caller's fallback rather than throwing, because
7
+ // a bad preference must never take a route down.
8
+ import { getDb } from "./client.js";
9
+ const HIDDEN_MODELS_KEY = "models.hidden";
10
+ export function getSetting(key, fallback) {
11
+ const row = getDb()
12
+ .query("SELECT value FROM settings WHERE key = ?")
13
+ .get(key);
14
+ if (!row)
15
+ return fallback;
16
+ try {
17
+ return JSON.parse(row.value);
18
+ }
19
+ catch {
20
+ console.warn(`[settings] Corrupt JSON for "${key}"; using fallback`);
21
+ return fallback;
22
+ }
23
+ }
24
+ export function setSetting(key, value) {
25
+ getDb()
26
+ .prepare(`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)
27
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
28
+ .run(key, JSON.stringify(value), Date.now());
29
+ }
30
+ /** Profile ids the user keeps out of the model picker. */
31
+ export function getHiddenModelIds() {
32
+ const value = getSetting(HIDDEN_MODELS_KEY, []);
33
+ if (!Array.isArray(value))
34
+ return [];
35
+ return value.filter((id) => typeof id === "string");
36
+ }
37
+ /** Replace the hidden set (the client always sends the full list, not a delta). */
38
+ export function setHiddenModelIds(ids) {
39
+ const unique = [...new Set(ids.filter((id) => typeof id === "string" && id))];
40
+ setSetting(HIDDEN_MODELS_KEY, unique);
41
+ }
42
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/db/settings.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,iDAAiD;AAEjD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAE1C,MAAM,UAAU,UAAU,CAAI,GAAW,EAAE,QAAW;IACpD,MAAM,GAAG,GAAG,KAAK,EAAE;SAChB,KAAK,CAAC,0CAA0C,CAAC;SACjD,GAAG,CAAC,GAAG,CAA6B,CAAC;IACxC,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAM,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,gCAAgC,GAAG,mBAAmB,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,KAAc;IACpD,KAAK,EAAE;SACJ,OAAO,CACN;+FACyF,CAC1F;SACA,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,UAAU,CAAU,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,GAAa;IAC7C,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9E,UAAU,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC"}
@@ -21,7 +21,7 @@ export declare const filesRoutes: import("hono/hono-base").HonoBase<import("hono
21
21
  size?: number | undefined;
22
22
  };
23
23
  outputFormat: "json";
24
- status: 400 | 404 | 413 | 500;
24
+ status: 500 | 400 | 404 | 413;
25
25
  };
26
26
  };
27
27
  } & {
@@ -42,7 +42,7 @@ export declare const filesRoutes: import("hono/hono-base").HonoBase<import("hono
42
42
  size?: number | undefined;
43
43
  };
44
44
  outputFormat: "json";
45
- status: 400 | 404 | 413 | 500;
45
+ status: 500 | 400 | 404 | 413;
46
46
  } | {
47
47
  input: {};
48
48
  output: {
@@ -71,7 +71,7 @@ export declare const filesRoutes: import("hono/hono-base").HonoBase<import("hono
71
71
  size?: number | undefined;
72
72
  };
73
73
  outputFormat: "json";
74
- status: 400 | 404 | 413 | 500;
74
+ status: 500 | 400 | 404 | 413;
75
75
  } | {
76
76
  input: {};
77
77
  output: {
@@ -0,0 +1,91 @@
1
+ export declare const modelRoutes: import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
2
+ "/models": {
3
+ $get: {
4
+ input: {};
5
+ output: {
6
+ models: {
7
+ hidden: boolean;
8
+ id: string;
9
+ label: string;
10
+ vendor?: string | undefined;
11
+ backendId?: string | undefined;
12
+ contextWindow?: number | undefined;
13
+ source?: "builtin" | "declared" | "discovered" | undefined;
14
+ }[];
15
+ refreshedAt: number | null;
16
+ stale: boolean;
17
+ discovery: {
18
+ enabled: boolean;
19
+ error?: string | undefined;
20
+ };
21
+ };
22
+ outputFormat: "json";
23
+ status: import("hono/utils/http-status").ContentfulStatusCode;
24
+ };
25
+ };
26
+ } & {
27
+ "/models/hidden": {
28
+ $put: {
29
+ input: {};
30
+ output: {
31
+ models: {
32
+ hidden: boolean;
33
+ id: string;
34
+ label: string;
35
+ vendor?: string | undefined;
36
+ backendId?: string | undefined;
37
+ contextWindow?: number | undefined;
38
+ source?: "builtin" | "declared" | "discovered" | undefined;
39
+ }[];
40
+ refreshedAt: number | null;
41
+ stale: boolean;
42
+ discovery: {
43
+ enabled: boolean;
44
+ error?: string | undefined;
45
+ };
46
+ };
47
+ outputFormat: "json";
48
+ status: import("hono/utils/http-status").ContentfulStatusCode;
49
+ } | {
50
+ input: {};
51
+ output: {
52
+ error: string;
53
+ };
54
+ outputFormat: "json";
55
+ status: 400;
56
+ };
57
+ };
58
+ } & {
59
+ "/models/refresh": {
60
+ $post: {
61
+ input: {};
62
+ output: {
63
+ models: {
64
+ hidden: boolean;
65
+ id: string;
66
+ label: string;
67
+ vendor?: string | undefined;
68
+ backendId?: string | undefined;
69
+ contextWindow?: number | undefined;
70
+ source?: "builtin" | "declared" | "discovered" | undefined;
71
+ }[];
72
+ refreshedAt: number | null;
73
+ stale: boolean;
74
+ discovery: {
75
+ enabled: boolean;
76
+ error?: string | undefined;
77
+ };
78
+ };
79
+ outputFormat: "json";
80
+ status: import("hono/utils/http-status").ContentfulStatusCode;
81
+ } | {
82
+ input: {};
83
+ output: {
84
+ error: string;
85
+ };
86
+ outputFormat: "json";
87
+ status: 409;
88
+ };
89
+ };
90
+ }, "/", "/models/refresh">;
91
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/routes/models.ts"],"names":[],"mappings":"AAsCA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA8CpB,CAAC"}
@@ -0,0 +1,61 @@
1
+ // Model catalog routes — the settings screen's view of the picker.
2
+ //
3
+ // `/api/providers` answers the picker and omits hidden profiles. These routes
4
+ // answer the settings screen and therefore show everything, each row tagged
5
+ // with its visibility, plus discovery freshness so the UI can say when the list
6
+ // was last refreshed and why it might be stale.
7
+ import { Hono } from "hono";
8
+ import { getModelSource, invalidateProfiles, listAllProviders, } from "../agent/backend.js";
9
+ import { getHiddenModelIds, setHiddenModelIds } from "../db/settings.js";
10
+ async function buildCatalog() {
11
+ const source = await getModelSource();
12
+ const hidden = new Set(getHiddenModelIds());
13
+ const models = (await listAllProviders({ includeHidden: true })).map((profile) => ({ ...profile, hidden: hidden.has(profile.id) }));
14
+ const state = source?.state();
15
+ return {
16
+ models,
17
+ refreshedAt: state?.refreshedAt ?? null,
18
+ stale: state?.stale ?? false,
19
+ discovery: {
20
+ enabled: state?.enabled ?? false,
21
+ ...(state?.error !== undefined ? { error: state.error } : {}),
22
+ },
23
+ };
24
+ }
25
+ export const modelRoutes = new Hono()
26
+ .get("/models", async (c) => {
27
+ // Serves the cached roster immediately and refreshes behind the response
28
+ // when stale; only a cold start (nothing cached) waits on the network.
29
+ const source = await getModelSource();
30
+ await source?.ensureFresh();
31
+ return c.json(await buildCatalog());
32
+ })
33
+ .put("/models/hidden", async (c) => {
34
+ const body = (await c.req.json().catch(() => null));
35
+ const hidden = body?.hidden;
36
+ if (!Array.isArray(hidden) ||
37
+ hidden.some((id) => typeof id !== "string")) {
38
+ return c.json({ error: "hidden must be an array of profile ids" }, 400);
39
+ }
40
+ setHiddenModelIds(hidden);
41
+ // The picker reads through a memo — drop it so the change is immediate.
42
+ invalidateProfiles();
43
+ return c.json(await buildCatalog());
44
+ })
45
+ .post("/models/refresh", async (c) => {
46
+ const source = await getModelSource();
47
+ if (!source) {
48
+ return c.json({ error: "Model discovery is not available on this backend" }, 409);
49
+ }
50
+ try {
51
+ await source.refresh();
52
+ }
53
+ catch (err) {
54
+ // The previous roster is still served; report the failure in the payload
55
+ // rather than 500ing, so the settings screen can show it inline.
56
+ console.warn(`[models] Refresh failed: ${err instanceof Error ? err.message : String(err)}`);
57
+ }
58
+ invalidateProfiles();
59
+ return c.json(await buildCatalog());
60
+ });
61
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/routes/models.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ export declare const providerRoutes: import("hono/hono-base").HonoBase<import("h
8
8
  label: string;
9
9
  vendor?: string | undefined;
10
10
  backendId?: string | undefined;
11
+ contextWindow?: number | undefined;
12
+ source?: "builtin" | "declared" | "discovered" | undefined;
11
13
  }[];
12
14
  backends: {
13
15
  [x: string]: {
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/routes/providers.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAKzB,CAAC"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/routes/providers.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAWzB,CAAC"}
@@ -1,6 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import { getBackendsInfo, listAllProviders } from "../agent/backend.js";
2
+ import { getBackendsInfo, getModelSource, listAllProviders, } from "../agent/backend.js";
3
3
  export const providerRoutes = new Hono().get("/providers", async (c) => {
4
+ // Keeps the roster current without a restart: serves the cached list and
5
+ // refreshes behind the response when it has gone stale. Only a cold start
6
+ // (nothing cached yet) waits on the Models API.
7
+ const source = await getModelSource();
8
+ await source?.ensureFresh();
4
9
  return c.json({
5
10
  providers: await listAllProviders(),
6
11
  backends: await getBackendsInfo(),
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/routes/providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACrE,OAAO,CAAC,CAAC,IAAI,CAAC;QACZ,SAAS,EAAE,MAAM,gBAAgB,EAAE;QACnC,QAAQ,EAAE,MAAM,eAAe,EAAE;KAClC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/routes/providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,cAAc,EACd,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,gDAAgD;IAChD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;IACtC,MAAM,MAAM,EAAE,WAAW,EAAE,CAAC;IAE5B,OAAO,CAAC,CAAC,IAAI,CAAC;QACZ,SAAS,EAAE,MAAM,gBAAgB,EAAE;QACnC,QAAQ,EAAE,MAAM,eAAe,EAAE;KAClC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ -- Generic server-side settings, stored as a JSON-per-key KV table so a new
2
+ -- preference does not need its own migration.
3
+ --
4
+ -- Keys in use:
5
+ -- models.hidden -> string[] of profile ids kept out of the model picker
6
+ CREATE TABLE IF NOT EXISTS settings (
7
+ key TEXT PRIMARY KEY,
8
+ value TEXT NOT NULL,
9
+ updated_at INTEGER NOT NULL
10
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schlessera/brain-ui-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "brain-kit chat-UI server: Hono app factory, WebSocket turn coordinator, auth (password/passkeys/tailscale/proxy), session catalog, and brain/files/voice routes",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -45,8 +45,8 @@
45
45
  "prepublishOnly": "bun ../../scripts/check-dist.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@schlessera/brain-backend-claude": "0.4.0",
49
- "@schlessera/brain-ui-sdk": "0.4.0",
48
+ "@schlessera/brain-backend-claude": "0.5.1",
49
+ "@schlessera/brain-ui-sdk": "0.5.1",
50
50
  "@simplewebauthn/server": "^13.3.2",
51
51
  "hono": "^4",
52
52
  "ignore": "^7.0.5",
@@ -54,7 +54,7 @@
54
54
  "zod": "^4.4.3"
55
55
  },
56
56
  "peerDependencies": {
57
- "@schlessera/brain-backend-pi": "0.4.0",
57
+ "@schlessera/brain-backend-pi": "*",
58
58
  "@types/bun": ">=1.3.0"
59
59
  },
60
60
  "peerDependenciesMeta": {
@@ -1,10 +1,13 @@
1
1
  import { createRequire } from "module";
2
2
  import {
3
3
  createClaudeBackend,
4
+ createModelSource,
4
5
  defineProfiles,
5
6
  type InferenceProfile,
6
7
  type InferenceProfileInput,
8
+ type ModelSource,
7
9
  } from "@schlessera/brain-backend-claude";
10
+ import { getHiddenModelIds } from "../db/settings.js";
8
11
  import type { ProviderInfo } from "@schlessera/brain-ui-sdk";
9
12
  import type {
10
13
  AgentBackend,
@@ -35,11 +38,24 @@ interface BackendRegistry {
35
38
  backends: AgentBackend[];
36
39
  byId: Map<string, AgentBackend>;
37
40
  defaultBackendId: string;
38
- profileOwners: Map<string, string>;
39
- profiles: Map<string, ProviderInfo[]>;
40
41
  }
41
42
 
43
+ /**
44
+ * Profiles are NOT part of the registry snapshot: a backend's roster can grow
45
+ * while the process runs (model discovery refreshing behind a request), so they
46
+ * are recomputed behind a short memo and can be invalidated explicitly.
47
+ */
48
+ interface ProfileSnapshot {
49
+ at: number;
50
+ byBackend: Map<string, ProviderInfo[]>;
51
+ owners: Map<string, string>;
52
+ }
53
+
54
+ const PROFILE_MEMO_MS = 5_000;
55
+
42
56
  let cachedRegistry: Promise<BackendRegistry> | null = null;
57
+ let profileSnapshot: ProfileSnapshot | null = null;
58
+ let modelSource: ModelSource | null = null;
43
59
 
44
60
  async function createRegistry(
45
61
  backends: AgentBackend[],
@@ -57,35 +73,121 @@ async function createRegistry(
57
73
  byId.get(resolvedDefaultId)!,
58
74
  ...backends.filter((backend) => backend.id !== resolvedDefaultId),
59
75
  ];
60
- const listedProfiles = await Promise.all(
61
- ordered.map(async (backend) => ({
62
- backend,
63
- profiles: await backend.listProfiles(),
64
- }))
65
- );
66
- const profileOwners = new Map<string, string>();
67
- const profiles = new Map<string, ProviderInfo[]>();
68
-
69
- for (const listed of listedProfiles) {
70
- profiles.set(listed.backend.id, listed.profiles);
71
- for (const profile of listed.profiles) {
72
- const owner = profileOwners.get(profile.id);
73
- if (owner && owner !== listed.backend.id) {
76
+
77
+ return { backends: ordered, byId, defaultBackendId: resolvedDefaultId };
78
+ }
79
+
80
+ /** Recompute the profile roster (memoized), asserting cross-backend id uniqueness. */
81
+ async function getProfileSnapshot(): Promise<ProfileSnapshot> {
82
+ if (profileSnapshot && Date.now() - profileSnapshot.at < PROFILE_MEMO_MS) {
83
+ return profileSnapshot;
84
+ }
85
+
86
+ const registry = await getRegistry();
87
+ const byBackend = new Map<string, ProviderInfo[]>();
88
+ const owners = new Map<string, string>();
89
+
90
+ for (const backend of registry.backends) {
91
+ const profiles = await backend.listProfiles();
92
+ byBackend.set(backend.id, profiles);
93
+ for (const profile of profiles) {
94
+ const owner = owners.get(profile.id);
95
+ if (owner && owner !== backend.id) {
74
96
  throw new Error(
75
- `Profile id collision across backends: "${profile.id}" is exposed by "${owner}" and "${listed.backend.id}".`
97
+ `Profile id collision across backends: "${profile.id}" is exposed by "${owner}" and "${backend.id}".`
76
98
  );
77
99
  }
78
- profileOwners.set(profile.id, listed.backend.id);
100
+ owners.set(profile.id, backend.id);
79
101
  }
80
102
  }
81
103
 
82
- return {
83
- backends: ordered,
84
- byId,
85
- defaultBackendId: resolvedDefaultId,
86
- profileOwners,
87
- profiles,
88
- };
104
+ profileSnapshot = { at: Date.now(), byBackend, owners };
105
+ return profileSnapshot;
106
+ }
107
+
108
+ /** Drop the memo so the next read reflects a refresh or a settings change. */
109
+ export function invalidateProfiles(): void {
110
+ profileSnapshot = null;
111
+ }
112
+
113
+ /**
114
+ * The Claude backend's discovery source, once the registry is built. Null when
115
+ * discovery is disabled or the deployment runs a different backend.
116
+ */
117
+ export async function getModelSource(): Promise<ModelSource | null> {
118
+ await getRegistry();
119
+ return modelSource;
120
+ }
121
+
122
+ /**
123
+ * Hidden profile ids, from the settings table. Presentation only — a hidden
124
+ * profile still resolves for sessions pinned to it. A db read failure must not
125
+ * take the picker down, so it degrades to "nothing hidden".
126
+ */
127
+ function hiddenIds(): Set<string> {
128
+ try {
129
+ return new Set(getHiddenModelIds());
130
+ } catch (err) {
131
+ console.warn(
132
+ `[models] Could not read hidden models: ${
133
+ err instanceof Error ? err.message : String(err)
134
+ }`
135
+ );
136
+ return new Set();
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Model discovery is on unless explicitly disabled — except under a test
142
+ * runner, where the default flips to off: discovery is a live call to the
143
+ * Anthropic API, and a suite that silently depends on network access (and on
144
+ * whatever credentials the developer happens to have exported) is flaky by
145
+ * construction. Tests that want it set BRAIN_UI_MODEL_DISCOVERY=1 explicitly.
146
+ */
147
+ function modelDiscoveryEnabled(): boolean {
148
+ const raw = process.env.BRAIN_UI_MODEL_DISCOVERY?.trim().toLowerCase();
149
+ if (raw) return !(raw === "0" || raw === "off" || raw === "false");
150
+ return process.env.NODE_ENV !== "test";
151
+ }
152
+
153
+ /** How long a discovery result stays fresh. Default 24h. */
154
+ function modelTtlMs(): number {
155
+ const raw = Number(process.env.BRAIN_UI_MODEL_TTL_HOURS);
156
+ const hours = Number.isFinite(raw) && raw > 0 ? raw : 24;
157
+ return hours * 60 * 60 * 1000;
158
+ }
159
+
160
+ /**
161
+ * Declared profiles (built-in default + BRAIN_UI_CLAUDE_PROFILES) plus every
162
+ * discovered model whose id isn't already declared. Declared wins: the env stays
163
+ * an override mechanism, and a hand-pinned entry keeps its label and endpoint.
164
+ *
165
+ * Memoized on the discovered array's identity — `createModelSource` swaps the
166
+ * array only on a successful refresh, so steady state is one comparison.
167
+ */
168
+ let mergeCache: {
169
+ declared: InferenceProfile[];
170
+ discovered: InferenceProfileInput[];
171
+ result: InferenceProfile[];
172
+ } | null = null;
173
+
174
+ function mergeDiscovered(
175
+ declared: InferenceProfile[],
176
+ discovered: InferenceProfileInput[]
177
+ ): InferenceProfile[] {
178
+ if (
179
+ mergeCache &&
180
+ mergeCache.declared === declared &&
181
+ mergeCache.discovered === discovered
182
+ ) {
183
+ return mergeCache.result;
184
+ }
185
+
186
+ const declaredIds = new Set(declared.map((profile) => profile.id));
187
+ const extra = discovered.filter((input) => !declaredIds.has(input.id));
188
+ const result = [...declared, ...defineProfiles(extra)];
189
+ mergeCache = { declared, discovered, result };
190
+ return result;
89
191
  }
90
192
 
91
193
  async function buildRegistry(): Promise<BackendRegistry> {
@@ -98,10 +200,22 @@ async function buildRegistry(): Promise<BackendRegistry> {
98
200
  return createRegistry([pi], pi.id);
99
201
  }
100
202
 
203
+ // Declared profiles are resolved EAGERLY so a malformed
204
+ // BRAIN_UI_CLAUDE_PROFILES still fails at boot rather than on first request.
205
+ const declared = loadClaudeProfiles();
206
+
207
+ modelSource = createModelSource({
208
+ brainPath: BRAIN_PATH,
209
+ enabled: modelDiscoveryEnabled(),
210
+ ttlMs: modelTtlMs(),
211
+ });
212
+
101
213
  const claude = createClaudeBackend({
102
214
  brainPath: BRAIN_PATH,
103
215
  claudeCodePath: CLAUDE_CODE_PATH,
104
- profiles: loadClaudeProfiles(),
216
+ // A function, not an array: discovery refreshes in the background and the
217
+ // new roster has to be visible without restarting the process.
218
+ profiles: () => mergeDiscovered(declared, modelSource?.list() ?? []),
105
219
  });
106
220
  const backends = [claude];
107
221
 
@@ -151,7 +265,10 @@ export async function getBackendForProfile(
151
265
  profileId: string
152
266
  ): Promise<AgentBackend | undefined> {
153
267
  const registry = await getRegistry();
154
- const backendId = registry.profileOwners.get(profileId);
268
+ // Deliberately resolved against the UNFILTERED roster: a session pinned to a
269
+ // profile the user later hid must keep running.
270
+ const snapshot = await getProfileSnapshot();
271
+ const backendId = snapshot.owners.get(profileId);
155
272
  return backendId ? registry.byId.get(backendId) : undefined;
156
273
  }
157
274
 
@@ -166,14 +283,23 @@ export async function getBackendForSession(
166
283
  return getDefaultBackend();
167
284
  }
168
285
 
169
- /** List every available profile, tagged with its owning backend id. */
170
- export async function listAllProviders(): Promise<ProviderInfo[]> {
286
+ /**
287
+ * Every available profile, tagged with its owning backend id.
288
+ *
289
+ * Hidden profiles are omitted by default (this feeds the picker); the settings
290
+ * screen passes `includeHidden` to render the full catalog.
291
+ */
292
+ export async function listAllProviders(
293
+ options: { includeHidden?: boolean } = {}
294
+ ): Promise<ProviderInfo[]> {
171
295
  const registry = await getRegistry();
296
+ const snapshot = await getProfileSnapshot();
297
+ const hidden = options.includeHidden ? new Set<string>() : hiddenIds();
298
+
172
299
  return registry.backends.flatMap((backend) =>
173
- (registry.profiles.get(backend.id) ?? []).map((profile) => ({
174
- ...profile,
175
- backendId: backend.id,
176
- }))
300
+ (snapshot.byBackend.get(backend.id) ?? [])
301
+ .filter((profile) => !hidden.has(profile.id))
302
+ .map((profile) => ({ ...profile, backendId: backend.id }))
177
303
  );
178
304
  }
179
305
 
@@ -198,11 +324,14 @@ export async function getBackend(): Promise<AgentBackend> {
198
324
  /** Test seam: drop the cached registry so later access rebuilds from env. */
199
325
  export function resetBackendForTests(): void {
200
326
  cachedRegistry = null;
327
+ profileSnapshot = null;
328
+ modelSource = null;
201
329
  }
202
330
 
203
331
  /** Test seam: install one fake backend as the complete registry. */
204
332
  export function setBackendForTests(backend: AgentBackend): void {
205
333
  cachedRegistry = createRegistry([backend], backend.id);
334
+ profileSnapshot = null;
206
335
  }
207
336
 
208
337
  /** Test seam: install a complete fake registry with an explicit default. */
@@ -211,6 +340,7 @@ export function setBackendsForTests(
211
340
  defaultBackendId = backends[0]?.id ?? ""
212
341
  ): void {
213
342
  cachedRegistry = createRegistry(backends, defaultBackendId);
343
+ profileSnapshot = null;
214
344
  }
215
345
 
216
346
  // The built-in default profile's model. The Claude backend historically pinned
@@ -230,6 +360,7 @@ function builtinDefaultProfiles(): InferenceProfile[] {
230
360
  vendor: "anthropic",
231
361
  model: BUILTIN_DEFAULT_MODEL,
232
362
  modelAliases: true,
363
+ source: "builtin",
233
364
  },
234
365
  ]);
235
366
  }
@@ -279,7 +410,11 @@ function loadClaudeProfiles(): InferenceProfile[] {
279
410
  seen.add(input.id);
280
411
  }
281
412
 
282
- return [...base, ...defineProfiles(inputs as InferenceProfileInput[])];
413
+ const declared = (inputs as InferenceProfileInput[]).map((input) => ({
414
+ source: "declared" as const,
415
+ ...input,
416
+ }));
417
+ return [...base, ...defineProfiles(declared)];
283
418
  }
284
419
 
285
420
  /**
package/src/app.ts CHANGED
@@ -11,6 +11,7 @@ import { voiceRoutes } from "./routes/voice.js";
11
11
  import { filesRoutes } from "./routes/files.js";
12
12
  import { createRenderRoutes, type AppRenderer } from "./routes/render.js";
13
13
  import { providerRoutes } from "./routes/providers.js";
14
+ import { modelRoutes } from "./routes/models.js";
14
15
  import {
15
16
  resolveAuthMode,
16
17
  assertAuthConfig,
@@ -130,6 +131,7 @@ export function createApp(options: CreateAppOptions = {}) {
130
131
  app.route("/api", filesRoutes);
131
132
  app.route("/api", createRenderRoutes(options.renderer));
132
133
  app.route("/api", providerRoutes);
134
+ app.route("/api", modelRoutes);
133
135
 
134
136
  // WebSocket endpoint. Browsers can't set headers on the WS handshake, so the
135
137
  // upgrade authenticates via the session cookie (or IP/proxy header) INSIDE
@@ -0,0 +1,46 @@
1
+ // Server-side settings: a JSON-per-key KV table (migration 006).
2
+ //
3
+ // These are deployment-wide preferences that must follow the user across
4
+ // devices, so they live in SQLite on the persisted volume rather than in the
5
+ // browser. Values are stored as JSON text; readers are total — a missing or
6
+ // corrupt row degrades to the caller's fallback rather than throwing, because
7
+ // a bad preference must never take a route down.
8
+
9
+ import { getDb } from "./client.js";
10
+
11
+ const HIDDEN_MODELS_KEY = "models.hidden";
12
+
13
+ export function getSetting<T>(key: string, fallback: T): T {
14
+ const row = getDb()
15
+ .query("SELECT value FROM settings WHERE key = ?")
16
+ .get(key) as { value: string } | null;
17
+ if (!row) return fallback;
18
+ try {
19
+ return JSON.parse(row.value) as T;
20
+ } catch {
21
+ console.warn(`[settings] Corrupt JSON for "${key}"; using fallback`);
22
+ return fallback;
23
+ }
24
+ }
25
+
26
+ export function setSetting(key: string, value: unknown): void {
27
+ getDb()
28
+ .prepare(
29
+ `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)
30
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
31
+ )
32
+ .run(key, JSON.stringify(value), Date.now());
33
+ }
34
+
35
+ /** Profile ids the user keeps out of the model picker. */
36
+ export function getHiddenModelIds(): string[] {
37
+ const value = getSetting<unknown>(HIDDEN_MODELS_KEY, []);
38
+ if (!Array.isArray(value)) return [];
39
+ return value.filter((id): id is string => typeof id === "string");
40
+ }
41
+
42
+ /** Replace the hidden set (the client always sends the full list, not a delta). */
43
+ export function setHiddenModelIds(ids: string[]): void {
44
+ const unique = [...new Set(ids.filter((id) => typeof id === "string" && id))];
45
+ setSetting(HIDDEN_MODELS_KEY, unique);
46
+ }
@@ -0,0 +1,85 @@
1
+ // Model catalog routes — the settings screen's view of the picker.
2
+ //
3
+ // `/api/providers` answers the picker and omits hidden profiles. These routes
4
+ // answer the settings screen and therefore show everything, each row tagged
5
+ // with its visibility, plus discovery freshness so the UI can say when the list
6
+ // was last refreshed and why it might be stale.
7
+
8
+ import { Hono } from "hono";
9
+ import type {
10
+ ModelCatalogEntry,
11
+ ModelCatalogResponse,
12
+ } from "@schlessera/brain-ui-sdk";
13
+ import {
14
+ getModelSource,
15
+ invalidateProfiles,
16
+ listAllProviders,
17
+ } from "../agent/backend.js";
18
+ import { getHiddenModelIds, setHiddenModelIds } from "../db/settings.js";
19
+
20
+ async function buildCatalog(): Promise<ModelCatalogResponse> {
21
+ const source = await getModelSource();
22
+ const hidden = new Set(getHiddenModelIds());
23
+ const models: ModelCatalogEntry[] = (
24
+ await listAllProviders({ includeHidden: true })
25
+ ).map((profile) => ({ ...profile, hidden: hidden.has(profile.id) }));
26
+
27
+ const state = source?.state();
28
+ return {
29
+ models,
30
+ refreshedAt: state?.refreshedAt ?? null,
31
+ stale: state?.stale ?? false,
32
+ discovery: {
33
+ enabled: state?.enabled ?? false,
34
+ ...(state?.error !== undefined ? { error: state.error } : {}),
35
+ },
36
+ };
37
+ }
38
+
39
+ export const modelRoutes = new Hono()
40
+ .get("/models", async (c) => {
41
+ // Serves the cached roster immediately and refreshes behind the response
42
+ // when stale; only a cold start (nothing cached) waits on the network.
43
+ const source = await getModelSource();
44
+ await source?.ensureFresh();
45
+ return c.json(await buildCatalog());
46
+ })
47
+
48
+ .put("/models/hidden", async (c) => {
49
+ const body = (await c.req.json().catch(() => null)) as unknown;
50
+ const hidden = (body as { hidden?: unknown } | null)?.hidden;
51
+ if (
52
+ !Array.isArray(hidden) ||
53
+ hidden.some((id) => typeof id !== "string")
54
+ ) {
55
+ return c.json({ error: "hidden must be an array of profile ids" }, 400);
56
+ }
57
+
58
+ setHiddenModelIds(hidden as string[]);
59
+ // The picker reads through a memo — drop it so the change is immediate.
60
+ invalidateProfiles();
61
+ return c.json(await buildCatalog());
62
+ })
63
+
64
+ .post("/models/refresh", async (c) => {
65
+ const source = await getModelSource();
66
+ if (!source) {
67
+ return c.json(
68
+ { error: "Model discovery is not available on this backend" },
69
+ 409
70
+ );
71
+ }
72
+ try {
73
+ await source.refresh();
74
+ } catch (err) {
75
+ // The previous roster is still served; report the failure in the payload
76
+ // rather than 500ing, so the settings screen can show it inline.
77
+ console.warn(
78
+ `[models] Refresh failed: ${
79
+ err instanceof Error ? err.message : String(err)
80
+ }`
81
+ );
82
+ }
83
+ invalidateProfiles();
84
+ return c.json(await buildCatalog());
85
+ });
@@ -1,7 +1,17 @@
1
1
  import { Hono } from "hono";
2
- import { getBackendsInfo, listAllProviders } from "../agent/backend.js";
2
+ import {
3
+ getBackendsInfo,
4
+ getModelSource,
5
+ listAllProviders,
6
+ } from "../agent/backend.js";
3
7
 
4
8
  export const providerRoutes = new Hono().get("/providers", async (c) => {
9
+ // Keeps the roster current without a restart: serves the cached list and
10
+ // refreshes behind the response when it has gone stale. Only a cold start
11
+ // (nothing cached yet) waits on the Models API.
12
+ const source = await getModelSource();
13
+ await source?.ensureFresh();
14
+
5
15
  return c.json({
6
16
  providers: await listAllProviders(),
7
17
  backends: await getBackendsInfo(),