Pi token refresh (10 min) and auth session cleanup (1 h) no longer run their own setInterval; both register in the maintain group of the schedules registry with the same default intervals. Co-Authored-By: Claude Opus 5.5 <[email protected]>
98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { FakeOAuthControl } from "@umt/agent";
|
|
import { defineTask } from "@umt/tasks";
|
|
import { z } from "zod";
|
|
import { registerScheduledJob } from "../schedules/registry.ts";
|
|
import type { ServerContext, ServerModule } from "../types.ts";
|
|
import { type OAuthOptions, OAuthSessions } from "./oauth.ts";
|
|
import { refreshPiTokens } from "./refresh.ts";
|
|
import { registerPiRoutes } from "./routes.ts";
|
|
|
|
export { type RunUpstream, resolveRunUpstream } from "./service.ts";
|
|
|
|
/**
|
|
* W7-A Pi 模型上游:pi_upstream CRUD、能力表、测试、模型表、服务端订阅登录(挂起式 interaction)、
|
|
* 持久任务 refreshPiTokens(周期登记为定时任务 pi.token_refresh;OAuth 过期前刷新,行锁内写回)。runAgent 经 ./service.ts 的 resolveRunUpstream 取上游。
|
|
* 用法见 docs/runbook/agent.md「上游与登录」。
|
|
*/
|
|
export interface PiModuleOptions {
|
|
/** provider = fake 的模拟 OAuth(测试注入) */
|
|
fakeOAuth?: FakeOAuthControl;
|
|
oauth?: Omit<OAuthOptions, "client">;
|
|
/** 定时任务 pi.token_refresh 的默认周期(默认 10 分钟;<=0 不登记) */
|
|
refreshIntervalMs?: number;
|
|
/** 剩余有效期少于它就刷新(默认 30 分钟) */
|
|
refreshWindowMs?: number;
|
|
}
|
|
|
|
export function createPiModule(
|
|
opts: PiModuleOptions = {},
|
|
): ServerModule & { sessions(ctx: ServerContext): OAuthSessions } {
|
|
const client = opts.fakeOAuth ? { fakeOAuth: opts.fakeOAuth } : undefined;
|
|
const sessions = new WeakMap<ServerContext, OAuthSessions>();
|
|
const sessionsFor = (ctx: ServerContext) => {
|
|
let s = sessions.get(ctx);
|
|
if (!s) {
|
|
s = new OAuthSessions(ctx, { ...opts.oauth, ...(client ? { client } : {}) });
|
|
sessions.set(ctx, s);
|
|
}
|
|
return s;
|
|
};
|
|
const live: OAuthSessions[] = [];
|
|
const interval = opts.refreshIntervalMs ?? 10 * 60_000;
|
|
const windowMs = opts.refreshWindowMs ?? 30 * 60_000;
|
|
|
|
return {
|
|
name: "pi",
|
|
sessions: sessionsFor,
|
|
registerRoutes(app, ctx) {
|
|
registerPiRoutes(app, ctx, {
|
|
oauth: () => {
|
|
const s = sessionsFor(ctx);
|
|
if (!live.includes(s)) live.push(s);
|
|
return s;
|
|
},
|
|
...(client ? { client } : {}),
|
|
});
|
|
},
|
|
registerTasks(registry, ctx) {
|
|
registry.register(
|
|
defineTask({
|
|
name: "refreshPiTokens",
|
|
version: 1,
|
|
inputSchema: z.object({}).passthrough(),
|
|
retryPolicy: { maximumAttempts: 2, initialInterval: "10s", backoffCoefficient: 2, maximumInterval: "60s" },
|
|
handler: async (t) =>
|
|
t.step.run("refresh", () => refreshPiTokens(ctx, { windowMs, ...(client ? { client } : {}) })),
|
|
}),
|
|
);
|
|
},
|
|
/** 刷新周期登记为定时任务 pi.token_refresh(维护组,由 schedules 调度器驱动),执行体只入队持久任务 */
|
|
start(ctx) {
|
|
if (interval <= 0) return;
|
|
registerScheduledJob(ctx, {
|
|
key: "pi.token_refresh",
|
|
defaults: {
|
|
name: "上游登录刷新",
|
|
description: "订阅登录即将过期的 Agent 上游,提前刷新 token",
|
|
group: "maintain",
|
|
schedule: { type: "interval", everyMs: interval },
|
|
},
|
|
run: async (c) => {
|
|
const r = await ctx.engine.enqueue(
|
|
"refreshPiTokens",
|
|
{},
|
|
{ idempotencyKey: `refreshPiTokens:${c.slot}`, traceId: randomUUID() },
|
|
);
|
|
return { summary: r.deduplicated ? "本周期已入队" : "已入队刷新" };
|
|
},
|
|
});
|
|
},
|
|
async stop() {
|
|
await Promise.all(live.splice(0).map((s) => s.shutdown()));
|
|
},
|
|
};
|
|
}
|
|
|
|
export const piModule = createPiModule();
|