mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 21:52:54 +00:00
feat(client): expose provider usage (#4062)
* feat(client): expose provider usage * fix(client): gate provider usage by capability
This commit is contained in:
@@ -83,7 +83,10 @@ function parseSentFrame(
|
||||
}
|
||||
|
||||
async function connectClient(
|
||||
features: Record<string, boolean> = { providersSnapshotCwd: true },
|
||||
features: Record<string, boolean> = {
|
||||
providerUsageList: true,
|
||||
providersSnapshotCwd: true,
|
||||
},
|
||||
): Promise<{ client: PaseoClient; ws: FakeWebSocket }> {
|
||||
vi.stubGlobal("WebSocket", FakeWebSocket);
|
||||
const client = createPaseoClient({
|
||||
@@ -1257,6 +1260,62 @@ test("provider actions delegate to existing provider RPCs and local snapshot upd
|
||||
provider: "codex",
|
||||
diagnostic: "Codex is ready.",
|
||||
});
|
||||
const usagePromise = client.providers.listUsage({
|
||||
requestId: "provider-usage-request",
|
||||
});
|
||||
expect(parseSentSessionMessage(ws.sent.at(-1))).toMatchObject({
|
||||
type: "provider.usage.list.request",
|
||||
requestId: "provider-usage-request",
|
||||
});
|
||||
ws.message(
|
||||
sessionMessage({
|
||||
type: "provider.usage.list.response",
|
||||
payload: {
|
||||
requestId: "provider-usage-request",
|
||||
fetchedAt: "2026-05-16T00:10:00.000Z",
|
||||
providers: [
|
||||
{
|
||||
providerId: "codex",
|
||||
displayName: "Codex",
|
||||
status: "available",
|
||||
planLabel: "pro",
|
||||
windows: [
|
||||
{
|
||||
id: "weekly",
|
||||
label: "Weekly limit",
|
||||
usedPct: 25,
|
||||
remainingPct: 75,
|
||||
resetsAt: "2026-05-20T00:00:00.000Z",
|
||||
tone: "ok",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(usagePromise).resolves.toEqual({
|
||||
requestId: "provider-usage-request",
|
||||
fetchedAt: "2026-05-16T00:10:00.000Z",
|
||||
providers: [
|
||||
{
|
||||
providerId: "codex",
|
||||
displayName: "Codex",
|
||||
status: "available",
|
||||
planLabel: "pro",
|
||||
windows: [
|
||||
{
|
||||
id: "weekly",
|
||||
label: "Weekly limit",
|
||||
usedPct: 25,
|
||||
remainingPct: 75,
|
||||
resetsAt: "2026-05-20T00:00:00.000Z",
|
||||
tone: "ok",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const snapshotUpdates: string[] = [];
|
||||
const snapshotModelDefaults: Array<string | undefined> = [];
|
||||
@@ -1307,6 +1366,18 @@ test("waitForReady requires canonical provider snapshot identity from the host",
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test("provider usage requires the advertised host capability", async () => {
|
||||
const { client, ws } = await connectClient({});
|
||||
const sentBeforeUsage = ws.sent.length;
|
||||
|
||||
await expect(client.providers.listUsage()).rejects.toThrow(
|
||||
"Update the host to list provider usage.",
|
||||
);
|
||||
expect(ws.sent).toHaveLength(sentBeforeUsage);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test("config actions delegate to existing daemon config RPCs", async () => {
|
||||
const { client, ws } = await connectClient();
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
MutableDaemonConfig,
|
||||
MutableDaemonConfigPatch,
|
||||
ProviderDiagnosticResponseMessage,
|
||||
ProviderUsageListResponseMessage,
|
||||
ProjectPlacementPayload,
|
||||
WorkspaceProjectDescriptorPayload,
|
||||
RefreshProvidersSnapshotResponseMessage,
|
||||
@@ -369,6 +370,10 @@ export type PaseoProviderSnapshotUpdate = Extract<
|
||||
>["payload"];
|
||||
export type PaseoProviderRefreshResult = RefreshProvidersSnapshotResponseMessage["payload"];
|
||||
export type PaseoProviderDiagnosticResult = ProviderDiagnosticResponseMessage["payload"];
|
||||
export type PaseoProviderUsageResult = ProviderUsageListResponseMessage["payload"];
|
||||
export interface PaseoProviderUsageOptions {
|
||||
requestId?: string;
|
||||
}
|
||||
|
||||
export interface PaseoProviderListOptions {
|
||||
cwd?: string;
|
||||
@@ -407,6 +412,7 @@ export interface PaseoProviderActions {
|
||||
provider: PaseoAgentProvider,
|
||||
options?: { requestId?: string },
|
||||
): Promise<PaseoProviderDiagnosticResult>;
|
||||
listUsage(options?: PaseoProviderUsageOptions): Promise<PaseoProviderUsageResult>;
|
||||
subscribe(handler: (update: PaseoProviderSnapshotUpdate) => void): () => void;
|
||||
}
|
||||
|
||||
@@ -545,6 +551,7 @@ export function createPaseoApi(daemonClient: DaemonClient): PaseoApi {
|
||||
waitForReady: (options) => waitForProvidersReady(daemonClient, options),
|
||||
refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
|
||||
diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
|
||||
listUsage: (options) => listProviderUsage(daemonClient, options),
|
||||
subscribe: (handler) =>
|
||||
daemonClient.on("providers_snapshot_update", (message) => {
|
||||
handler(message.payload);
|
||||
@@ -804,6 +811,17 @@ function parseProviderModel(selection: string): { provider: string; model: strin
|
||||
};
|
||||
}
|
||||
|
||||
function listProviderUsage(
|
||||
daemonClient: DaemonClient,
|
||||
options?: PaseoProviderUsageOptions,
|
||||
): Promise<PaseoProviderUsageResult> {
|
||||
// COMPAT(providerUsageList): added in v0.1.98, remove after 2027-02-28 once daemon floor >= v0.1.98.
|
||||
if (daemonClient.getLastServerInfoMessage()?.features?.providerUsageList !== true) {
|
||||
return Promise.reject(new Error("Update the host to list provider usage."));
|
||||
}
|
||||
return daemonClient.listProviderUsage(options);
|
||||
}
|
||||
|
||||
function waitForProvidersReady(
|
||||
daemonClient: DaemonClient,
|
||||
options: PaseoProviderWaitOptions = {},
|
||||
|
||||
@@ -184,6 +184,7 @@ Use `workspace.terminals.create(options?)` and `workspace.terminals.list(options
|
||||
| `listModes(provider, options?)` | Modes result | Discovers permission or operating modes. |
|
||||
| `listFeatures(draftConfig)` | Features result | Discovers features for the current draft provider configuration. |
|
||||
| `diagnostic(provider)` | Diagnostic result | Returns human-readable setup diagnostics. |
|
||||
| `listUsage(options?)` | `PaseoProviderUsageResult` | Returns normalized subscription windows, balances, and provider details. Rejects with an update-host error when unsupported. Options: `requestId`. |
|
||||
| `subscribe(handler)` | Unsubscribe function | Listens for catalog updates. |
|
||||
|
||||
## `client.config`
|
||||
|
||||
Reference in New Issue
Block a user