On login the gateway now captures user.id from /v1/auth/login, derives
the user's MinIO bucket (user-<id>-files), and persists both in
~/.a2a/credentials.json. UpstreamAgent.callTool injects
``params._meta = { cp_jwt, cp_url, bucket }`` on every tools/call so
upstream agents see the same caller context the platform orchestrator
provides. Unauthenticated clients omit _meta — back-compat preserved.
Requires a2a-pack with the matching _meta handler in
``a2a_pack/mcp/server.py`` (companion change in apps/a2a).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
/**
|
|
* Reader for ~/.a2a/credentials.json — same file the Python `a2a` CLI writes.
|
|
*
|
|
* We deliberately *only read* this file (and overwrite it on `a2amcp login`)
|
|
* so a single login flow is shared between the Python and Node tooling.
|
|
*/
|
|
import { promises as fs } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
export const DEFAULT_API_URL = "https://api.a2acloud.io";
|
|
|
|
export interface Credentials {
|
|
apiUrl: string;
|
|
token: string;
|
|
email: string;
|
|
userId?: number;
|
|
bucket?: string;
|
|
}
|
|
|
|
const credsDir = () => path.join(os.homedir(), ".a2a");
|
|
const credsFile = () => path.join(credsDir(), "credentials.json");
|
|
|
|
export async function loadCredentials(): Promise<Credentials | null> {
|
|
try {
|
|
const raw = await fs.readFile(credsFile(), "utf8");
|
|
const data = JSON.parse(raw);
|
|
if (!data?.token) return null;
|
|
return {
|
|
apiUrl: data.api_url ?? DEFAULT_API_URL,
|
|
token: data.token,
|
|
email: data.email ?? "",
|
|
userId: typeof data.user_id === "number" ? data.user_id : undefined,
|
|
bucket: typeof data.bucket === "string" ? data.bucket : undefined,
|
|
};
|
|
} catch (err: any) {
|
|
if (err?.code === "ENOENT") return null;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export async function saveCredentials(c: Credentials): Promise<void> {
|
|
await fs.mkdir(credsDir(), { recursive: true });
|
|
const out: Record<string, unknown> = {
|
|
api_url: c.apiUrl,
|
|
token: c.token,
|
|
email: c.email,
|
|
};
|
|
if (c.userId !== undefined) out.user_id = c.userId;
|
|
if (c.bucket !== undefined) out.bucket = c.bucket;
|
|
await fs.writeFile(credsFile(), JSON.stringify(out), { mode: 0o600 });
|
|
}
|
|
|
|
export async function clearCredentials(): Promise<boolean> {
|
|
try {
|
|
await fs.unlink(credsFile());
|
|
return true;
|
|
} catch (err: any) {
|
|
if (err?.code === "ENOENT") return false;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export function resolveApiUrl(override?: string): string {
|
|
if (override) return override;
|
|
if (process.env.A2A_API_URL) return process.env.A2A_API_URL;
|
|
return DEFAULT_API_URL;
|
|
}
|