Files
ThothII/backend/src/pi/auth-providers.ts
T
marcopanandClaude Opus 4.8 c5fd03ed84 feat(backend): let pi self-authenticate providers from its own auth store
The backend injects a single managed model key (THT_MODEL_API_KEY[_FILE]) as
the selected provider's env var, but that key belongs to one provider — so
selecting a second cloud provider (e.g. DeepSeek while the managed key is zai's)
forced the wrong key onto it and failed auth. This is why the model could not be
switched to DeepSeek.

When the selected provider is present in pi's own auth store
(~/.pi/agent/auth.json), skip injection and let pi resolve that provider's key
itself. Deployments without an auth store (containers) yield an empty set, so the
managed-key injection stays authoritative and fail-fast there. authProviders is
injectable into PiProcessManager for deterministic tests.

Verified live: GLM 5.2, DeepSeek V4 Flash, and aritmolab Qwen3.6 all operate through the ThothII model selector.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:26:18 +02:00

33 lines
1.3 KiB
TypeScript

import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
/**
* Providers pi can authenticate on its own from `~/.pi/agent/auth.json`.
*
* The backend injects a single managed model key (`THT_MODEL_API_KEY[_FILE]`),
* which belongs to exactly one provider. Forcing that one key onto a different
* provider's credential variable breaks its auth. So when the selected provider
* is present in pi's own auth store, the backend skips injection and lets pi
* resolve that provider's key itself. An absent/malformed store (e.g. a
* containerized deployment that ships no auth.json) yields an empty set, which
* keeps the managed-key path authoritative there.
*/
export function loadPiAuthProviders(
opts: { agentDir?: string; read?: (path: string) => string } = {},
): Set<string> {
const agentDir = opts.agentDir ?? join(homedir(), ".pi", "agent");
const read = opts.read ?? ((path: string) => readFileSync(path, "utf8"));
try {
const raw: unknown = JSON.parse(read(join(agentDir, "auth.json")));
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return new Set();
return new Set(
Object.keys(raw as Record<string, unknown>)
.map((key) => key.trim().toLowerCase())
.filter((key) => key.length > 0),
);
} catch {
return new Set();
}
}