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>
42 lines
2.0 KiB
TypeScript
42 lines
2.0 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { loadPiAuthProviders } from "../src/pi/auth-providers.js";
|
|
import { buildPiChildEnv } from "../src/pi/provider-credentials.js";
|
|
|
|
test("loadPiAuthProviders returns the lowercased provider keys from the pi auth store", () => {
|
|
const read = () => JSON.stringify({ zai: { type: "api-key", key: "z" }, DeepSeek: { key: "d" } });
|
|
expect(loadPiAuthProviders({ agentDir: "/agent", read })).toEqual(new Set(["zai", "deepseek"]));
|
|
});
|
|
|
|
test("loadPiAuthProviders is empty when the auth store is absent or malformed", () => {
|
|
const enoent = () => { throw Object.assign(new Error("nope"), { code: "ENOENT" }); };
|
|
expect(loadPiAuthProviders({ read: enoent }).size).toBe(0);
|
|
expect(loadPiAuthProviders({ read: () => "not json" }).size).toBe(0);
|
|
expect(loadPiAuthProviders({ read: () => "[]" }).size).toBe(0);
|
|
});
|
|
|
|
test("a provider in the pi auth store gets no injected key and never opens the managed file", () => {
|
|
// The single managed key belongs to one provider; forcing it onto another
|
|
// provider's credential variable breaks auth. When pi can self-authenticate,
|
|
// the backend must skip injection entirely — and must not read the managed file.
|
|
const env = buildPiChildEnv({
|
|
ambient: { DEEPSEEK_API_KEY: "stale" },
|
|
provider: "deepseek",
|
|
authProviders: new Set(["deepseek"]),
|
|
credentialFile: "/managed/zai-key",
|
|
fsOps: {
|
|
lstat: () => { throw new Error("must not stat the managed key file"); },
|
|
open: () => { throw new Error("must not open the managed key file"); },
|
|
fstat: () => { throw new Error("unreachable"); },
|
|
read: () => "zai-key",
|
|
close: () => undefined,
|
|
},
|
|
});
|
|
expect(env.DEEPSEEK_API_KEY).toBeUndefined();
|
|
});
|
|
|
|
test("a non-local provider absent from the auth store still requires a managed credential", () => {
|
|
expect(() => buildPiChildEnv({
|
|
ambient: {}, provider: "deepseek", authProviders: new Set(["zai"]),
|
|
})).toThrow("model provider credential is unavailable");
|
|
});
|