feat(server): pi module with upstream CRUD, server-side OAuth and token refresh

- /pi/providers, /pi/upstreams CRUD, test (auth-only by default, no cost), models
- subscription login via pi models.login with a suspended interaction: browser mode
  waits for the pasted redirect URL, device mode polls server-side; per-provider
  login lock, readable callback-port-in-use errors
- credential store locks the pi_upstream row (select for update) so api and worker
  refreshes never double-write a rotated refresh token
- refreshPiTokens task: refresh before expiry; failure marks expired and opens a
  relogin decision, closed again after a successful login
- runAgent picks upstreamId > agent_run.upstream_id > default upstream > legacy
  settings; credentials injected by the server, never logged

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 02:23:22 +08:00
1 parent 743fe2486d
commit fd3ffe5a8d
13 files changed
+1617 -26

No files matched your search

+19 -9
View File
@@ -58,15 +58,25 @@ describe("Api 注册表鉴权标注", () => {
.filter((e) => e.auth === "session")
.map((e) => `${e.method.toUpperCase()} ${e.path}`)
.sort();
expect(session).toEqual([
"GET /auth/totp",
"GET /files/download",
"PATCH /approvals/{id}",
"POST /approvals",
"POST /auth/password",
"POST /auth/totp",
"POST /files/trash/purge",
]);
expect(session).toEqual(
[
"GET /auth/totp",
"GET /files/download",
"PATCH /approvals/{id}",
"POST /approvals",
"POST /auth/password",
"POST /auth/totp",
"POST /files/trash/purge",
// W7-A Pi 上游:写、测试、订阅登录只接受控制台会话
"POST /pi/upstreams",
"PATCH /pi/upstreams/{id}",
"DELETE /pi/upstreams/{id}",
"POST /pi/upstreams/{id}/test",
"POST /pi/upstreams/{id}/oauth/start",
"POST /pi/upstreams/{id}/oauth/complete",
"POST /pi/upstreams/{id}/oauth/logout",
].sort(),
);
for (const e of listEndpoints().filter((x) => x.auth === "session")) expect(e.scope).toBe("admin");
});