mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
test: slim pi auth discovery tests
This commit is contained in:
72
src/agents/pi-auth-discovery-core.ts
Normal file
72
src/agents/pi-auth-discovery-core.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from "node:fs";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { resolveProviderEnvApiKeyCandidates } from "./model-auth-env-vars.js";
|
||||
import { resolveEnvApiKey } from "./model-auth-env.js";
|
||||
import type { PiCredentialMap } from "./pi-auth-credentials.js";
|
||||
|
||||
export function addEnvBackedPiCredentials(
|
||||
credentials: PiCredentialMap,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): PiCredentialMap {
|
||||
const next = { ...credentials };
|
||||
// pi-coding-agent hides providers from its registry when auth storage lacks
|
||||
// a matching credential entry. Mirror env-backed provider auth here so
|
||||
// live/model discovery sees the same providers runtime auth can use.
|
||||
for (const provider of Object.keys(resolveProviderEnvApiKeyCandidates({ env }))) {
|
||||
if (next[provider]) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveEnvApiKey(provider, env);
|
||||
if (!resolved?.apiKey) {
|
||||
continue;
|
||||
}
|
||||
next[provider] = {
|
||||
type: "api_key",
|
||||
key: resolved.apiKey,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export function scrubLegacyStaticAuthJsonEntriesForDiscovery(pathname: string): void {
|
||||
if (process.env.OPENCLAW_AUTH_STORE_READONLY === "1") {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(pathname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(fs.readFileSync(pathname, "utf8")) as unknown;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(parsed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
for (const [provider, value] of Object.entries(parsed)) {
|
||||
if (!isRecord(value)) {
|
||||
continue;
|
||||
}
|
||||
if (value.type !== "api_key") {
|
||||
continue;
|
||||
}
|
||||
delete parsed[provider];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(parsed).length === 0) {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(pathname, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
||||
fs.chmodSync(pathname, 0o600);
|
||||
}
|
||||
50
src/agents/pi-auth-discovery.ts
Normal file
50
src/agents/pi-auth-discovery.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { resolveProviderSyntheticAuthWithPlugin } from "../plugins/provider-runtime.js";
|
||||
import { resolveRuntimeSyntheticAuthProviderRefs } from "../plugins/synthetic-auth.runtime.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
loadAuthProfileStoreForSecretsRuntime,
|
||||
} from "./auth-profiles/store.js";
|
||||
import { resolvePiCredentialMapFromStore, type PiCredentialMap } from "./pi-auth-credentials.js";
|
||||
import { addEnvBackedPiCredentials } from "./pi-auth-discovery-core.js";
|
||||
|
||||
export type DiscoverAuthStorageOptions = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export function resolvePiCredentialsForDiscovery(
|
||||
agentDir: string,
|
||||
options?: DiscoverAuthStorageOptions,
|
||||
): PiCredentialMap {
|
||||
const store =
|
||||
options?.readOnly === true
|
||||
? loadAuthProfileStoreForSecretsRuntime(agentDir)
|
||||
: ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||
const credentials = addEnvBackedPiCredentials(resolvePiCredentialMapFromStore(store));
|
||||
for (const provider of resolveRuntimeSyntheticAuthProviderRefs()) {
|
||||
if (credentials[provider]) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveProviderSyntheticAuthWithPlugin({
|
||||
provider,
|
||||
context: {
|
||||
config: undefined,
|
||||
provider,
|
||||
providerConfig: undefined,
|
||||
},
|
||||
});
|
||||
const apiKey = resolved?.apiKey?.trim();
|
||||
if (!apiKey) {
|
||||
continue;
|
||||
}
|
||||
credentials[provider] = {
|
||||
type: "api_key",
|
||||
key: apiKey,
|
||||
};
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
export {
|
||||
addEnvBackedPiCredentials,
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
||||
} from "./pi-auth-discovery-core.js";
|
||||
@@ -6,7 +6,7 @@ import { resolvePiCredentialMapFromStore } from "./pi-auth-credentials.js";
|
||||
import {
|
||||
addEnvBackedPiCredentials,
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
||||
} from "./pi-model-discovery.js";
|
||||
} from "./pi-auth-discovery-core.js";
|
||||
|
||||
vi.mock("./model-auth-env-vars.js", () => ({
|
||||
resolveProviderEnvApiKeyCandidates: () => ({
|
||||
|
||||
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { saveAuthProfileStore } from "./auth-profiles/store.js";
|
||||
|
||||
const resolveRuntimeSyntheticAuthProviderRefs = vi.hoisted(() => vi.fn(() => ["claude-cli"]));
|
||||
|
||||
@@ -30,7 +29,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
resolveExternalAuthProfilesWithPlugins: () => [],
|
||||
}));
|
||||
|
||||
let discoverAuthStorage: typeof import("./pi-model-discovery.js").discoverAuthStorage;
|
||||
let resolvePiCredentialsForDiscovery: typeof import("./pi-auth-discovery.js").resolvePiCredentialsForDiscovery;
|
||||
|
||||
async function withAgentDir(run: (agentDir: string) => Promise<void>): Promise<void> {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-pi-synthetic-auth-"));
|
||||
@@ -43,7 +42,7 @@ async function withAgentDir(run: (agentDir: string) => Promise<void>): Promise<v
|
||||
|
||||
describe("pi model discovery synthetic auth", () => {
|
||||
beforeAll(async () => {
|
||||
({ discoverAuthStorage } = await import("./pi-model-discovery.js"));
|
||||
({ resolvePiCredentialsForDiscovery } = await import("./pi-auth-discovery.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -57,17 +56,9 @@ describe("pi model discovery synthetic auth", () => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("mirrors plugin-owned synthetic cli auth into pi auth storage", async () => {
|
||||
it("mirrors plugin-owned synthetic cli auth into pi credential discovery", async () => {
|
||||
await withAgentDir(async (agentDir) => {
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {},
|
||||
},
|
||||
agentDir,
|
||||
);
|
||||
|
||||
const authStorage = discoverAuthStorage(agentDir);
|
||||
const credentials = resolvePiCredentialsForDiscovery(agentDir, { readOnly: true });
|
||||
|
||||
expect(resolveRuntimeSyntheticAuthProviderRefs).toHaveBeenCalled();
|
||||
expect(resolveProviderSyntheticAuthWithPlugin).toHaveBeenCalledWith({
|
||||
@@ -78,8 +69,10 @@ describe("pi model discovery synthetic auth", () => {
|
||||
providerConfig: undefined,
|
||||
},
|
||||
});
|
||||
expect(authStorage.hasAuth("claude-cli")).toBe(true);
|
||||
await expect(authStorage.getApiKey("claude-cli")).resolves.toBe("claude-cli-access-token");
|
||||
expect(credentials["claude-cli"]).toEqual({
|
||||
type: "api_key",
|
||||
key: "claude-cli-access-token",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import * as PiCodingAgent from "@mariozechner/pi-coding-agent";
|
||||
@@ -11,17 +10,14 @@ import {
|
||||
applyProviderResolvedModelCompatWithPlugins,
|
||||
applyProviderResolvedTransportWithPlugin,
|
||||
normalizeProviderResolvedModelWithPlugin,
|
||||
resolveProviderSyntheticAuthWithPlugin,
|
||||
} from "../plugins/provider-runtime.js";
|
||||
import { resolveRuntimeSyntheticAuthProviderRefs } from "../plugins/synthetic-auth.runtime.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import type { PiCredentialMap } from "./pi-auth-credentials.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
loadAuthProfileStoreForSecretsRuntime,
|
||||
} from "./auth-profiles/store.js";
|
||||
import { resolveProviderEnvApiKeyCandidates } from "./model-auth-env-vars.js";
|
||||
import { resolveEnvApiKey } from "./model-auth-env.js";
|
||||
import { resolvePiCredentialMapFromStore, type PiCredentialMap } from "./pi-auth-credentials.js";
|
||||
resolvePiCredentialsForDiscovery,
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
||||
type DiscoverAuthStorageOptions,
|
||||
} from "./pi-auth-discovery.js";
|
||||
import { normalizeProviderId } from "./provider-id.js";
|
||||
|
||||
const PiAuthStorageClass = PiCodingAgent.AuthStorage;
|
||||
@@ -168,49 +164,6 @@ function createOpenClawModelRegistry(
|
||||
return registry;
|
||||
}
|
||||
|
||||
export function scrubLegacyStaticAuthJsonEntriesForDiscovery(pathname: string): void {
|
||||
if (process.env.OPENCLAW_AUTH_STORE_READONLY === "1") {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(pathname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(fs.readFileSync(pathname, "utf8")) as unknown;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(parsed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
for (const [provider, value] of Object.entries(parsed)) {
|
||||
if (!isRecord(value)) {
|
||||
continue;
|
||||
}
|
||||
if (value.type !== "api_key") {
|
||||
continue;
|
||||
}
|
||||
delete parsed[provider];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(parsed).length === 0) {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(pathname, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
||||
fs.chmodSync(pathname, 0o600);
|
||||
}
|
||||
|
||||
function createAuthStorage(AuthStorageLike: unknown, path: string, creds: PiCredentialMap) {
|
||||
const withInMemory = AuthStorageLike as { inMemory?: (data?: unknown) => unknown };
|
||||
if (typeof withInMemory.inMemory === "function") {
|
||||
@@ -256,67 +209,6 @@ function createAuthStorage(AuthStorageLike: unknown, path: string, creds: PiCred
|
||||
return withRuntimeOverride;
|
||||
}
|
||||
|
||||
export function addEnvBackedPiCredentials(
|
||||
credentials: PiCredentialMap,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): PiCredentialMap {
|
||||
const next = { ...credentials };
|
||||
// pi-coding-agent hides providers from its registry when auth storage lacks
|
||||
// a matching credential entry. Mirror env-backed provider auth here so
|
||||
// live/model discovery sees the same providers runtime auth can use.
|
||||
for (const provider of Object.keys(resolveProviderEnvApiKeyCandidates({ env }))) {
|
||||
if (next[provider]) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveEnvApiKey(provider, env);
|
||||
if (!resolved?.apiKey) {
|
||||
continue;
|
||||
}
|
||||
next[provider] = {
|
||||
type: "api_key",
|
||||
key: resolved.apiKey,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
type DiscoverAuthStorageOptions = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export function resolvePiCredentialsForDiscovery(
|
||||
agentDir: string,
|
||||
options?: DiscoverAuthStorageOptions,
|
||||
): PiCredentialMap {
|
||||
const store =
|
||||
options?.readOnly === true
|
||||
? loadAuthProfileStoreForSecretsRuntime(agentDir)
|
||||
: ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||
const credentials = addEnvBackedPiCredentials(resolvePiCredentialMapFromStore(store));
|
||||
for (const provider of resolveRuntimeSyntheticAuthProviderRefs()) {
|
||||
if (credentials[provider]) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveProviderSyntheticAuthWithPlugin({
|
||||
provider,
|
||||
context: {
|
||||
config: undefined,
|
||||
provider,
|
||||
providerConfig: undefined,
|
||||
},
|
||||
});
|
||||
const apiKey = resolved?.apiKey?.trim();
|
||||
if (!apiKey) {
|
||||
continue;
|
||||
}
|
||||
credentials[provider] = {
|
||||
type: "api_key",
|
||||
key: apiKey,
|
||||
};
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
// Compatibility helpers for pi-coding-agent 0.50+ (discover* helpers removed).
|
||||
export function discoverAuthStorage(
|
||||
agentDir: string,
|
||||
@@ -342,3 +234,10 @@ export function discoverModels(
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
addEnvBackedPiCredentials,
|
||||
resolvePiCredentialsForDiscovery,
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery,
|
||||
type DiscoverAuthStorageOptions,
|
||||
} from "./pi-auth-discovery.js";
|
||||
|
||||
Reference in New Issue
Block a user