From 8db1e3206d21996c83dc84400d073749a1cac208 Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:24:47 +0100 Subject: [PATCH 1/2] feat(config): support ROUTSTRD_MODELS_PUBKEY env override Let operators set the routstr-21 models pubkey via the ROUTSTRD_MODELS_PUBKEY environment variable instead of editing config.json. Env overrides take precedence over config.json in both loadDaemonConfig() and loadDaemonConfigSync(). --- src/daemon/config-store.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/daemon/config-store.ts b/src/daemon/config-store.ts index c6b155f..2c9c08d 100644 --- a/src/daemon/config-store.ts +++ b/src/daemon/config-store.ts @@ -69,17 +69,30 @@ function repairConfigPermissions(): void { } } +/** + * Environment-variable overrides take precedence over config.json so operators + * can set values via Cloudron/docker env (e.g. ROUTSTRD_MODELS_PUBKEY) without + * hand-editing the config file. + */ +function envConfigOverrides(): Partial { + const overrides: Partial = {}; + if (process.env.ROUTSTRD_MODELS_PUBKEY) { + overrides.routstrModelsPubkey = process.env.ROUTSTRD_MODELS_PUBKEY; + } + return overrides; +} + export async function loadDaemonConfig(): Promise { try { if (existsSync(CONFIG_FILE)) { repairConfigPermissions(); const content = await Bun.file(CONFIG_FILE).text(); - return { ...DEFAULT_CONFIG, ...JSON.parse(content) }; + return { ...DEFAULT_CONFIG, ...JSON.parse(content), ...envConfigOverrides() }; } } catch (error) { logger.error("Failed to load config:", error); } - return DEFAULT_CONFIG; + return { ...DEFAULT_CONFIG, ...envConfigOverrides() }; } export function loadDaemonConfigSync(): RoutstrdConfig { @@ -87,12 +100,12 @@ export function loadDaemonConfigSync(): RoutstrdConfig { if (existsSync(CONFIG_FILE)) { repairConfigPermissions(); const content = readFileSync(CONFIG_FILE, "utf-8"); - return { ...DEFAULT_CONFIG, ...JSON.parse(content) }; + return { ...DEFAULT_CONFIG, ...JSON.parse(content), ...envConfigOverrides() }; } } catch (error) { logger.error("Failed to load config:", error); } - return DEFAULT_CONFIG; + return { ...DEFAULT_CONFIG, ...envConfigOverrides() }; } /** From 89a5254939b57da751e3a00bdd373283979feab6 Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:26:11 +0100 Subject: [PATCH 2/2] feat(pi): map daemon input modalities and max tokens into Pi model entries --- src/integrations/pi.ts | 28 +++++++++++++++++++++++++++- src/integrations/registry.ts | 9 +++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/integrations/pi.ts b/src/integrations/pi.ts index fa4f2c6..314b524 100644 --- a/src/integrations/pi.ts +++ b/src/integrations/pi.ts @@ -8,7 +8,10 @@ import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client"; type PiModelEntry = { id: string; contextWindow?: number; - // Preserve arbitrary manual fields (reasoning, thinkingLevelMap, compat, cost, maxTokens, etc.) + name?: string; + input?: string[]; + maxTokens?: number; + // Preserve arbitrary manual fields (reasoning, thinkingLevelMap, compat, cost, etc.) [key: string]: unknown; }; @@ -75,8 +78,31 @@ export async function installPiIntegration( const entry: PiModelEntry = previous ? { ...previous } : { id: model.id }; entry.id = model.id; + if (model.context_length !== undefined && model.context_length > 0) { entry.contextWindow = model.context_length; + } else { + delete entry.contextWindow; + } + + if (model.name) { + entry.name = model.name; + } else { + delete entry.name; + } + + // Map the daemon's input modalities to Pi's ["text", "image"] vocabulary. + const mods = model.architecture?.input_modalities ?? []; + const input: string[] = []; + if (mods.includes("text")) input.push("text"); + if (mods.includes("image")) input.push("image"); + entry.input = input; + + const maxTokens = model.top_provider?.max_completion_tokens; + if (maxTokens !== undefined && maxTokens !== null && maxTokens > 0) { + entry.maxTokens = maxTokens; + } else { + delete entry.maxTokens; } return entry; diff --git a/src/integrations/registry.ts b/src/integrations/registry.ts index b16ac5d..00935d2 100644 --- a/src/integrations/registry.ts +++ b/src/integrations/registry.ts @@ -18,6 +18,15 @@ export type RoutstrModel = { id: string; name?: string; context_length?: number; + architecture?: { + modality?: string; + input_modalities?: string[]; + output_modalities?: string[]; + }; + top_provider?: { + context_length?: number; + max_completion_tokens?: number; + }; }; export type IntegrationFn = (