mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 17:08:27 +00:00
Auth labels: handle token refs and share Pi credential conversion
This commit is contained in:
committed by
Peter Steinberger
parent
e1301c31e7
commit
cec404225d
@@ -1,25 +1,17 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { ensureAuthProfileStore } from "./auth-profiles.js";
|
||||
import type { AuthProfileCredential } from "./auth-profiles/types.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
import {
|
||||
piCredentialsEqual,
|
||||
resolvePiCredentialMapFromStore,
|
||||
type PiCredential,
|
||||
} from "./pi-auth-credentials.js";
|
||||
|
||||
/**
|
||||
* @deprecated Legacy bridge for older flows that still expect `agentDir/auth.json`.
|
||||
* Runtime auth resolution uses auth-profiles directly and should not depend on this module.
|
||||
*/
|
||||
type AuthJsonCredential =
|
||||
| {
|
||||
type: "api_key";
|
||||
key: string;
|
||||
}
|
||||
| {
|
||||
type: "oauth";
|
||||
access: string;
|
||||
refresh: string;
|
||||
expires: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
type AuthJsonCredential = PiCredential;
|
||||
|
||||
type AuthJsonShape = Record<string, AuthJsonCredential>;
|
||||
|
||||
@@ -36,75 +28,6 @@ async function readAuthJson(filePath: string): Promise<AuthJsonShape> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an OpenClaw auth-profiles credential to pi-coding-agent auth.json format.
|
||||
* Returns null if the credential cannot be converted.
|
||||
*/
|
||||
function convertCredential(cred: AuthProfileCredential): AuthJsonCredential | null {
|
||||
if (cred.type === "api_key") {
|
||||
const key = typeof cred.key === "string" ? cred.key.trim() : "";
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
return { type: "api_key", key };
|
||||
}
|
||||
|
||||
if (cred.type === "token") {
|
||||
// pi-coding-agent treats static tokens as api_key type
|
||||
const token = typeof cred.token === "string" ? cred.token.trim() : "";
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const expires =
|
||||
typeof (cred as { expires?: unknown }).expires === "number"
|
||||
? (cred as { expires: number }).expires
|
||||
: Number.NaN;
|
||||
if (Number.isFinite(expires) && expires > 0 && Date.now() >= expires) {
|
||||
return null;
|
||||
}
|
||||
return { type: "api_key", key: token };
|
||||
}
|
||||
|
||||
if (cred.type === "oauth") {
|
||||
const accessRaw = (cred as { access?: unknown }).access;
|
||||
const refreshRaw = (cred as { refresh?: unknown }).refresh;
|
||||
const expiresRaw = (cred as { expires?: unknown }).expires;
|
||||
|
||||
const access = typeof accessRaw === "string" ? accessRaw.trim() : "";
|
||||
const refresh = typeof refreshRaw === "string" ? refreshRaw.trim() : "";
|
||||
const expires = typeof expiresRaw === "number" ? expiresRaw : Number.NaN;
|
||||
|
||||
if (!access || !refresh || !Number.isFinite(expires) || expires <= 0) {
|
||||
return null;
|
||||
}
|
||||
return { type: "oauth", access, refresh, expires };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two auth.json credentials are equivalent.
|
||||
*/
|
||||
function credentialsEqual(a: AuthJsonCredential | undefined, b: AuthJsonCredential): boolean {
|
||||
if (!a || typeof a !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (a.type !== b.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.type === "api_key" && b.type === "api_key") {
|
||||
return a.key === b.key;
|
||||
}
|
||||
|
||||
if (a.type === "oauth" && b.type === "oauth") {
|
||||
return a.access === b.access && a.refresh === b.refresh && a.expires === b.expires;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* pi-coding-agent's ModelRegistry/AuthStorage expects credentials in auth.json.
|
||||
*
|
||||
@@ -123,31 +46,16 @@ export async function ensurePiAuthJsonFromAuthProfiles(agentDir: string): Promis
|
||||
}> {
|
||||
const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||
const authPath = path.join(agentDir, "auth.json");
|
||||
|
||||
// Group profiles by provider, taking the first valid profile for each
|
||||
const providerCredentials = new Map<string, AuthJsonCredential>();
|
||||
|
||||
for (const [, cred] of Object.entries(store.profiles)) {
|
||||
const provider = normalizeProviderId(String(cred.provider ?? "")).trim();
|
||||
if (!provider || providerCredentials.has(provider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const converted = convertCredential(cred);
|
||||
if (converted) {
|
||||
providerCredentials.set(provider, converted);
|
||||
}
|
||||
}
|
||||
|
||||
if (providerCredentials.size === 0) {
|
||||
const providerCredentials = resolvePiCredentialMapFromStore(store);
|
||||
if (Object.keys(providerCredentials).length === 0) {
|
||||
return { wrote: false, authPath };
|
||||
}
|
||||
|
||||
const existing = await readAuthJson(authPath);
|
||||
let changed = false;
|
||||
|
||||
for (const [provider, cred] of providerCredentials) {
|
||||
if (!credentialsEqual(existing[provider], cred)) {
|
||||
for (const [provider, cred] of Object.entries(providerCredentials)) {
|
||||
if (!piCredentialsEqual(existing[provider], cred)) {
|
||||
existing[provider] = cred;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user