refactor(usage): name the provider usage types and pass options objects

Follow the server conventions: the provider environments list returns a named
ProviderUsageEnvironment, and the registry's discover() and mergeReports() and
the e2e injectedUsageSources() helper take a single options object. No
behavior change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Qichang Zheng
2026-10-18 08:33:56 +08:00
co-authored by Claude Opus 5.5
parent 6d41463582
commit 88b7e096a6
4 changed files with 76 additions and 53 deletions
@@ -661,6 +661,16 @@ export interface AgentUsageSession {
sessionKey: string;
}
/** An enabled provider that sets its own env, as its sessions report it for usage. */
export interface ProviderUsageEnvironment {
/** The provider a custom provider extends, or its own id. */
provider: string;
/** The provider's label, used to name the logins found through it. */
label: string;
/** The environment the provider launches with. */
env: Record<string, string>;
}
export interface AgentSession {
usageSession?(): AgentUsageSession | null;
readonly provider: AgentProvider;
@@ -19,6 +19,7 @@ import {
type AgentProvider,
type FetchCatalogOptions,
type ProviderSnapshotEntry,
type ProviderUsageEnvironment,
} from "./agent-sdk-types.js";
import {
raceProviderRefreshAbort,
@@ -361,15 +362,8 @@ export class ProviderSnapshotManager {
return this.createAgentManagerState(this.generation.definitions, this.providerClients);
}
/**
* Enabled providers that set their own env, as their sessions report them for usage: the
* provider a custom provider extends, its own label, and the environment it launches with.
*/
listProviderEnvironments(): Array<{
provider: AgentProvider;
label: string;
env: Record<string, string>;
}> {
/** Enabled providers that set their own env, as their sessions report them for usage. */
listProviderEnvironments(): ProviderUsageEnvironment[] {
return Object.entries(this.generation.definitions).flatMap(([provider, definition]) => {
const runtimeSettings = definition.configuration?.runtimeSettings;
if (!definition.enabled || !runtimeSettings?.env) return [];
@@ -18,11 +18,23 @@ async function json(file: string, value: unknown) {
}
/** The real Claude and Codex readers, compiled as built-ins, with home, env and HTTP injected. */
async function injectedUsageSources(
root: string,
lookup: { home: string; env: Record<string, string> },
fetchApi: string,
): Promise<BuiltinPluginLoader> {
interface UsageLookup {
home: string;
env: Record<string, string>;
}
interface InjectedUsageSourcesOptions {
root: string;
lookup: UsageLookup;
/** Source of an `async function fetchApi(url, init)` the readers call instead of the network. */
fetchApi: string;
}
async function injectedUsageSources({
root,
lookup,
fetchApi,
}: InjectedUsageSourcesOptions): Promise<BuiltinPluginLoader> {
const plugins = path.join(root, "plugins");
for (const source of ["codex", "claude"]) {
const directory = path.join(plugins, `${source}-usage-source`);
@@ -233,10 +245,10 @@ test("built-in sources deduplicate cross-harness accounts and fall back without
JSON.stringify({ access: "fixture-omp-claude", expires: Date.now() + 60_000 }),
);
db.close();
const builtinPlugins = await injectedUsageSources(
root,
{ home, env },
`async function fetchApi(url, init) {
const builtinPlugins = await injectedUsageSources({
root: root,
lookup: { home, env },
fetchApi: `async function fetchApi(url, init) {
const token = new Headers(init.headers).get('Authorization');
if (String(url).endsWith('/profile')) {
const second = token === 'Bearer fixture-pi-claude';
@@ -246,7 +258,7 @@ test("built-in sources deduplicate cross-harness accounts and fall back without
const used = { 'Bearer fixture-codex': 11, 'Bearer fixture-opencode': 22, 'Bearer fixture-pi-codex': 33, 'Bearer fixture-omp-codex': 44, 'Bearer fixture-omp-claude': 66, 'Bearer fixture-pi-claude': 77 }[token] || 55;
return new Response(JSON.stringify({ rate_limit: { primary_window: { used_percent: used } }, five_hour: { utilization: used } }));
}`,
);
});
const daemon = await createTestPaseoDaemon({ pluginsEnabled: false, builtinPlugins });
const client = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
try {
@@ -336,10 +348,10 @@ test("#3589: host usage lists each enabled provider's own login without an agent
await json(path.join(dir("codex-work"), "auth.json"), {
tokens: { access_token: "fixture-codex-work", account_id: "chatgpt-work" },
});
const builtinPlugins = await injectedUsageSources(
root,
{ home, env: {} },
`async function fetchApi(url, init) {
const builtinPlugins = await injectedUsageSources({
root: root,
lookup: { home, env: {} },
fetchApi: `async function fetchApi(url, init) {
const token = new Headers(init.headers).get('Authorization');
if (String(url).endsWith('/profile')) {
const uuid = { 'Bearer fixture-work': 'work', 'Bearer fixture-off': 'off' }[token] || 'default';
@@ -347,7 +359,7 @@ test("#3589: host usage lists each enabled provider's own login without an agent
}
return new Response(JSON.stringify({ rate_limit: { primary_window: { used_percent: 1 } }, five_hour: { utilization: 1 } }));
}`,
);
});
const daemon = await createTestPaseoDaemon({
pluginsEnabled: false,
builtinPlugins,
@@ -427,16 +439,16 @@ test("#3589: a provider whose agents talk to another API host has no login in th
await json(path.join(root, "claude-work", ".credentials.json"), {
claudeAiOauth: { accessToken: "fixture-work" },
});
const builtinPlugins = await injectedUsageSources(
root,
{ home: path.join(root, "home"), env: {} },
`async function fetchApi(url) {
const builtinPlugins = await injectedUsageSources({
root: root,
lookup: { home: path.join(root, "home"), env: {} },
fetchApi: `async function fetchApi(url) {
if (String(url).endsWith('/profile')) {
return new Response(JSON.stringify({ account: { uuid: 'work' }, organization: { uuid: 'org' } }));
}
return new Response(JSON.stringify({ rate_limit: { primary_window: { used_percent: 1 } }, five_hour: { utilization: 1 } }));
}`,
);
});
const daemon = await createTestPaseoDaemon({
pluginsEnabled: false,
builtinPlugins,
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import type { AgentUsageSession } from "../../agent/agent-sdk-types.js";
import type { AgentUsageSession, ProviderUsageEnvironment } from "../../agent/agent-sdk-types.js";
import type { UsageScope } from "@getpaseo/plugin/server/usage";
import { z } from "zod";
import type { Logger } from "pino";
@@ -33,11 +33,19 @@ interface KnownReport {
export interface AgentUsageLookup {
hasAgent(id: string): boolean;
usageSession(id: string): AgentUsageSession | null;
/**
* Enabled providers that set their own env, with the label to show for their logins. Host
* listings discover these like a session.
*/
providerEnvironments?(): Array<Pick<AgentUsageSession, "provider" | "env"> & { label: string }>;
/** Enabled providers that set their own env. Host listings discover these like a session. */
providerEnvironments?(): ProviderUsageEnvironment[];
}
interface DiscoverOptions {
scope: UsageScope;
/** Names the logins found in this scope; defaults to the account's harness or the source label. */
harness?: string;
}
interface MergeReportsOptions {
into: Map<string, KnownReport>;
reports: Iterable<[string, KnownReport]>;
}
export interface ListUsageReportsOptions {
@@ -105,7 +113,7 @@ export class UsageSourceRegistry {
// its agents runs. The host's own logins come first in each account's fallback order.
// A login is named after the provider it was found through: a login that can't be read
// (an expired token has no account) is otherwise one more card that looks like the rest.
const scopes: Array<{ scope: UsageScope; harness?: string }> = [
const scopes: DiscoverOptions[] = [
{ scope: { kind: "global" } },
...(this.agents.providerEnvironments?.() ?? []).map(({ provider, label, env }) => ({
scope: { kind: "session" as const, provider, env },
@@ -113,10 +121,8 @@ export class UsageSourceRegistry {
})),
];
const defaults = new Map<string, KnownReport>();
for (const discovered of await Promise.all(
scopes.map(({ scope, harness }) => this.discover(scope, harness)),
))
mergeReports(defaults, discovered);
for (const discovered of await Promise.all(scopes.map((entry) => this.discover(entry))))
mergeReports({ into: defaults, reports: discovered });
this.defaults = defaults;
this.mergeKnown();
ids = [...this.known.keys()];
@@ -153,10 +159,12 @@ export class UsageSourceRegistry {
previous.model === session.model;
if (sameScope) return [...previous.reports.keys()];
const reports = await this.discover({
kind: "session",
provider: session.provider,
model: session.model,
env: session.env,
scope: {
kind: "session",
provider: session.provider,
model: session.model,
env: session.env,
},
});
// A query that finishes after a resume must not publish the previous launch's mapping.
if (this.agents.usageSession(agentId)?.sessionKey !== session.sessionKey) return [];
@@ -176,14 +184,16 @@ export class UsageSourceRegistry {
this.defaults,
...[...this.byAgent.values()].map((agent) => agent.reports),
]) {
mergeReports(
this.known,
[...reports].filter(([, report]) => this.sources.get(report.source.id) === report.source),
);
mergeReports({
into: this.known,
reports: [...reports].filter(
([, report]) => this.sources.get(report.source.id) === report.source,
),
});
}
}
private async discover(scope: UsageScope, harness?: string): Promise<Map<string, KnownReport>> {
private async discover({ scope, harness }: DiscoverOptions): Promise<Map<string, KnownReport>> {
const discovered = await Promise.all(
[...this.sources.values()].map(async (source) => {
const reports = new Map<string, KnownReport>();
@@ -355,10 +365,7 @@ function legacyError(report: UsageReport, now: number): string | null {
}
// An account found in several scopes is one report; its logins keep discovery order, once each.
function mergeReports(
into: Map<string, KnownReport>,
reports: Iterable<[string, KnownReport]>,
): void {
function mergeReports({ into, reports }: MergeReportsOptions): void {
for (const [id, report] of reports) {
const known = into.get(id);
if (!known) {