Merge pull request #10424 from manaflow-ai/feat/coderouter-status-session-counts

Expose per-account session counts and cooldowns in account listing
This commit is contained in:
Lawrence Chen
2026-08-19 04:13:14 -07:00
committed by GitHub
3 changed files with 75 additions and 16 deletions
+50 -16
View File
@@ -151,22 +151,56 @@ export async function deleteAccount(input: {
export async function listAccounts(
teamId: string,
): Promise<readonly CodeRouterAccountSummary[]> {
return await cloudDb()
.select({
id: coderouterAccounts.id,
provider: coderouterAccounts.provider,
providerAccountId: coderouterAccounts.providerAccountId,
label: coderouterAccounts.label,
state: coderouterAccounts.state,
credentialExpiresAt: coderouterAccounts.credentialExpiresAt,
lastFailureCode: coderouterAccounts.lastFailureCode,
})
.from(coderouterAccounts)
.where(eq(coderouterAccounts.teamId, teamId))
.then((rows) => rows.map((row) => ({
...row,
credentialExpiresAt: row.credentialExpiresAt?.toISOString() ?? null,
})));
const [rows, sessionCounts] = await Promise.all([
cloudDb()
.select({
id: coderouterAccounts.id,
provider: coderouterAccounts.provider,
providerAccountId: coderouterAccounts.providerAccountId,
label: coderouterAccounts.label,
state: coderouterAccounts.state,
credentialExpiresAt: coderouterAccounts.credentialExpiresAt,
lastFailureCode: coderouterAccounts.lastFailureCode,
cooldownUntil: coderouterAccounts.cooldownUntil,
})
.from(coderouterAccounts)
.where(eq(coderouterAccounts.teamId, teamId)),
countActiveSessionsByAccount(teamId),
]);
return rows.map((row) => ({
...row,
credentialExpiresAt: row.credentialExpiresAt?.toISOString() ?? null,
cooldownUntil: row.cooldownUntil?.toISOString() ?? null,
activeSessions: sessionCounts.get(row.id) ?? 0,
}));
}
/**
* Sessions bound per account with traffic inside the load window. Display
* only: a failure here must never take the status endpoint down.
*/
async function countActiveSessionsByAccount(
teamId: string,
): Promise<ReadonlyMap<string, number>> {
try {
const rows = await cloudDb()
.select({
accountId: coderouterSessionAccounts.accountId,
sessions: sql<number>`count(*)::int`,
})
.from(coderouterSessionAccounts)
.where(and(
eq(coderouterSessionAccounts.teamId, teamId),
gt(
coderouterSessionAccounts.lastSeenAt,
sql`now() - interval '${sql.raw(SESSION_BINDING_LOAD_WINDOW)}'`,
),
))
.groupBy(coderouterSessionAccounts.accountId);
return new Map(rows.map((row) => [row.accountId, Number(row.sessions)]));
} catch {
return new Map();
}
}
export async function listCoderouterTeamIds(): Promise<readonly string[]> {
+3
View File
@@ -41,4 +41,7 @@ export type CodeRouterAccountSummary = {
readonly state: "active" | "refreshing" | "expired" | "broken";
readonly credentialExpiresAt: string | null;
readonly lastFailureCode: string | null;
readonly cooldownUntil: string | null;
/** Sessions bound to this account with traffic in the recent window. */
readonly activeSessions: number;
};
@@ -2,6 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:tes
import { randomUUID } from "node:crypto";
import postgres, { type Sql } from "postgres";
import { closeCloudDbForTests } from "../db/client";
import { listAccounts } from "../services/coderouter/repository";
import {
bindSessionAccount,
claimAccountForPlacement,
@@ -213,6 +214,27 @@ describe("coderouter routing db behavior", () => {
expect(during?.sticky).toBe(true);
});
dbTest("account listing reports active session counts and cooldowns", async () => {
if (!sql) throw new Error("no sql client");
const accounts = await insertAccounts(2);
await bindSessionAccount(TEAM, "codex", "fresh-session-1", accounts[0] ?? "");
await bindSessionAccount(TEAM, "codex", "fresh-session-2", accounts[0] ?? "");
await bindSessionAccount(TEAM, "codex", "stale-session", accounts[1] ?? "");
await sql`
update coderouter_session_accounts
set last_seen_at = now() - interval '7 hours'
where session_key = 'stale-session'
`;
await markAccountCooldown(accounts[1] ?? "", 60_000);
const listed = await listAccounts(TEAM);
const byId = new Map(listed.map((account) => [account.id, account]));
expect(byId.get(accounts[0] ?? "")?.activeSessions).toBe(2);
expect(byId.get(accounts[0] ?? "")?.cooldownUntil).toBeNull();
// A binding idle beyond the load window no longer counts.
expect(byId.get(accounts[1] ?? "")?.activeSessions).toBe(0);
expect(byId.get(accounts[1] ?? "")?.cooldownUntil).not.toBeNull();
});
dbTest("routes without stickiness while the session table is missing", async () => {
if (!sql) throw new Error("no sql client");
await insertAccounts(2);