Files
UHD_Media_Tool/packages/agent/src/pi/fake-upstream.ts
T
rootandClaude Opus 5.5 743fe2486d feat(agent): per-upstream pi Models with isolated credential stores
- PiUpstreamClient: one Models per upstream holding only its provider; credential
  store adapter maps to that upstream's secret, so several accounts of one provider
  coexist; host env is only read for authKind none (ambient cloud creds)
- provider catalog from builtinProviders() plus static oauth modes, env fields and
  subscription terms notes; openai-compatible custom endpoints
- fake upstream provider with mock OAuth (browser paste / device approval)
- PiAgentRuntime accepts an upstream; thinking level clamped to the model

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 02:23:09 +08:00

172 lines
5.9 KiB
TypeScript

import { randomBytes } from "node:crypto";
import {
type AssistantMessage,
createProvider,
type FauxResponseFactory,
fauxAssistantMessage,
fauxProvider,
type OAuthAuth,
type OAuthCredential,
type Provider,
type ProviderAuthInteraction,
} from "@earendil-works/pi-ai";
// provider = "fake" 的上游:faux 模型 + 模拟 OAuth(browser 粘 URL / device 轮询),不联网。
// 设置页联调与 OAuth 状态机测试用;行为由 FakeOAuthControl 控制(测试注入,缺省用进程级默认实例)。
export class FakeOAuthControl {
loginCalls = 0;
refreshCalls = 0;
/** refresh 抛 invalid_grant */
failRefresh = false;
/** refresh 前等待(并发测试用) */
refreshDelayMs = 0;
/** 新 token 的有效期 */
tokenTtlMs = 3600_000;
/** device 模式:多久后自动批准(null = 只能 approveDevice() 批准) */
deviceAutoApproveMs: number | null = 3000;
/** browser 模式:模拟回调端口被占用(pi 的 Anthropic 会直接 reject) */
browserListenError: string | null = null;
private approved = false;
approveDevice(): void {
this.approved = true;
}
/** @internal */
consumeApproval(): boolean {
if (!this.approved) return false;
this.approved = false;
return true;
}
}
export const DEFAULT_FAKE_OAUTH = new FakeOAuthControl();
const sleep = (ms: number, signal?: AbortSignal) =>
new Promise<void>((resolve, reject) => {
const t = setTimeout(resolve, ms);
signal?.addEventListener(
"abort",
() => {
clearTimeout(t);
reject(new Error("Login cancelled"));
},
{ once: true },
);
});
function parsePasted(input: string): { code?: string; state?: string } {
const v = input.trim();
try {
const u = new URL(v);
return { code: u.searchParams.get("code") ?? undefined, state: u.searchParams.get("state") ?? undefined };
} catch {
// code#state 或裸 code
}
if (v.includes("#")) {
const [code, state] = v.split("#", 2);
return { code: code || undefined, state: state || undefined };
}
if (v.includes("code=")) {
const p = new URLSearchParams(v);
return { code: p.get("code") ?? undefined, state: p.get("state") ?? undefined };
}
return { code: v || undefined };
}
function issue(control: FakeOAuthControl, account: string): OAuthCredential {
const n = randomBytes(6).toString("hex");
return {
type: "oauth",
access: `fake-access-${n}`,
refresh: `fake-refresh-${n}`,
expires: Date.now() + control.tokenTtlMs,
account,
};
}
export function fakeOAuth(control: FakeOAuthControl): OAuthAuth {
return {
name: "Fake OAuth",
isSubscription: false,
async login(interaction: ProviderAuthInteraction) {
control.loginCalls += 1;
const method = await interaction.prompt({
type: "select",
message: "Select fake login method:",
options: [
{ id: "browser", label: "Browser login" },
{ id: "device_code", label: "Device code login" },
],
});
if (method === "device_code") {
const userCode = `FAKE-${randomBytes(2).toString("hex").toUpperCase()}`;
interaction.notify({
type: "device_code",
userCode,
verificationUri: "https://fake-oauth.invalid/device",
intervalSeconds: 1,
expiresInSeconds: 900,
});
const started = Date.now();
for (;;) {
if (control.consumeApproval()) break;
if (control.deviceAutoApproveMs !== null && Date.now() - started >= control.deviceAutoApproveMs) break;
await sleep(25, interaction.signal);
}
interaction.notify({ type: "progress", message: "Device authorized" });
return issue(control, "fake-device-user");
}
if (control.browserListenError) throw new Error(control.browserListenError);
const state = randomBytes(8).toString("hex");
interaction.notify({
type: "auth_url",
url: `https://fake-oauth.invalid/authorize?state=${state}&redirect_uri=${encodeURIComponent("http://localhost:1455/auth/callback")}`,
instructions: "Complete login in your browser, then paste the final redirect URL here.",
});
const input = await interaction.prompt({
type: "manual_code",
message: "Paste the authorization code / redirect URL:",
placeholder: "http://localhost:1455/auth/callback",
});
const parsed = parsePasted(input);
if (!parsed.code) throw new Error("Missing authorization code");
if (parsed.state && parsed.state !== state) throw new Error("OAuth state mismatch");
interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
return issue(control, "fake-browser-user");
},
async refresh(credential, signal) {
control.refreshCalls += 1;
if (control.refreshDelayMs > 0) await sleep(control.refreshDelayMs, signal);
if (control.failRefresh) throw new Error("invalid_grant: refresh token revoked");
return issue(control, String(credential.account ?? "fake-user"));
},
async toAuth(credential) {
return { apiKey: credential.access };
},
};
}
/** provider id "fake" 的上游 provider:faux 流 + 始终就绪的 apiKey(authKind none)+ 模拟 OAuth */
export function fakeUpstreamProvider(control: FakeOAuthControl): Provider {
const faux = fauxProvider({
provider: "fake",
models: [{ id: "fake-model", contextWindow: 64_000, maxTokens: 4096, reasoning: true }],
tokensPerSecond: 0,
});
const factory: FauxResponseFactory = (): AssistantMessage => {
faux.appendResponses([factory]);
return fauxAssistantMessage("ok");
};
faux.setResponses([factory]);
const base = faux.provider;
return createProvider({
id: "fake",
name: "Fake",
auth: { apiKey: { name: "Fake", resolve: async () => ({ auth: {}, source: "fake" }) }, oauth: fakeOAuth(control) },
models: base.getModels(),
api: { stream: base.stream.bind(base), streamSimple: base.streamSimple.bind(base) } as never,
});
}