single source of truth for pi config via ~/.pi/agent

auth.json stores api keys, models.json stores local providers directly.
no more copies, sync layers, or per-user config generation.
docker containers mount pi config read-only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 18:42:30 +00:00
co-authored by Claude Opus 4.6
parent 500a70910e
commit 11845fbae5
12 changed files with 302 additions and 580 deletions
+2 -2
View File
@@ -24,7 +24,7 @@ const parseSize = (s?: string): number => {
return Math.round(num);
};
export async function listPiModels(envKeys: Record<string, string>): Promise<ModelInfo[]> {
export async function listPiModels(): Promise<ModelInfo[]> {
if (cachedModels && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
return cachedModels;
}
@@ -33,7 +33,7 @@ export async function listPiModels(envKeys: Record<string, string>): Promise<Mod
const proc = Bun.spawn(['pi', '--list-models'], {
stdout: 'pipe',
stderr: 'pipe',
env: { ...process.env, ...envKeys, PI_CODING_AGENT_DIR: PI_CONFIG_DIR },
env: { ...process.env, PI_CODING_AGENT_DIR: PI_CONFIG_DIR },
});
const output = await new Response(proc.stdout).text();
+1 -9
View File
@@ -3,7 +3,6 @@ import { homedir } from "node:os";
import { readdirSync, existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
import type { Subprocess } from "bun";
import type { PiEvent, MessageCost } from "./types";
import { readApiKeys } from "../server-settings/pi-mono";
import { readSearxngConfig } from "../server-settings/searxng";
import { PI_CONFIG_DIR, DATA_PATH, SERVER_CONFIG_DIR, getHomeDir, getGlobalSkillsDir, getUserSkillsDir, getGlobalExtensionsDir, getUserExtensionsDir, getGlobalToolsDir, getUserToolsDir, getNativeResourcesDir, getGlobalResourcesDir } from "../../data-path";
import { ensureDockerContainer } from "../terminal/websocket";
@@ -208,12 +207,10 @@ export async function spawnPi(
if (sandbox) {
const container = await ensureDockerContainer(sandbox.email, sandbox.userId, sandbox.homeDir, sandbox.username);
const storedKeys = await readApiKeys();
const searxng = await readSearxngConfig();
const dockerPath = Bun.which('docker') ?? 'docker';
const containerId = container.dockerId;
const containerHome = `/home/${sandbox.username}`;
const containerPiConfig = `${containerHome}/.pi/agent`;
// Collect skill/extension flags using container-side paths
const skillFlags = collectSkillFlags(sandbox.email, {
@@ -244,7 +241,7 @@ export async function spawnPi(
await ensureGoogleTokenFile(sandbox.userId, sandbox.email);
const envFlags = [
'-e', `PI_CODING_AGENT_DIR=${containerPiConfig}`,
'-e', `PI_CODING_AGENT_DIR=/officer/pi-config`,
'-e', `HOME=${containerHome}`,
'-e', `OFFICER_USER_HOME=${containerHome}`,
'-e', `OFFICER_USER_ROOT=/officer/user`,
@@ -255,9 +252,6 @@ export async function spawnPi(
'-e', `OFFICER_GOOGLE_TOKEN_PATH=/officer/user/integrations/google.json`,
'-e', `OFFICER_EMAIL_DB=/officer/emails.db`,
];
for (const [key, value] of Object.entries(storedKeys)) {
if (value?.trim()) envFlags.push('-e', `${key}=${value.trim()}`);
}
const rel = relative(sandbox.homeDir, cwd);
const workdir = rel && !rel.startsWith('..') ? join(containerHome, rel) : containerHome;
@@ -281,7 +275,6 @@ export async function spawnPi(
extensions: extensionFlags.filter((f) => f !== '--extension').length,
});
} else {
const storedKeys = await readApiKeys();
const searxng = await readSearxngConfig();
const skillFlags = collectSkillFlags(email);
const extensionFlags = collectExtensionFlags(email);
@@ -308,7 +301,6 @@ export async function spawnPi(
stderr: 'pipe',
env: {
...process.env,
...storedKeys,
HOME: getHomeDir(email),
OFFICER_USER_HOME: getHomeDir(email),
OFFICER_USER_ROOT: join(DATA_PATH, email),
+2 -3
View File
@@ -1,7 +1,7 @@
import type { Context } from 'hono';
import { createRouter } from '../../create-router';
import * as storage from './storage';
import { readApiKeys, readLocalProviders } from '../server-settings/pi-mono';
import { readLocalProviders } from '../server-settings/pi-mono';
import { readSttConfig } from '../server-settings/stt';
import { listPiModels } from './list-models';
import { getHomeDir } from '../../data-path';
@@ -21,8 +21,7 @@ export const piRestRouter = createRouter();
*/
piRestRouter.get('/pi/models', async (ctx: Context) => {
try {
const storedKeys = await readApiKeys();
const models = await listPiModels(storedKeys);
const models = await listPiModels();
// Build providerNames map for officer-local-* providers
const providerNames: Record<string, string> = {};