feat(tui): add usage report

This commit is contained in:
Felipe Coury
2026-05-21 15:09:14 -03:00
parent a6bedc8a7c
commit afafcc98ec
63 changed files with 2355 additions and 14 deletions
@@ -4030,6 +4030,24 @@
],
"type": "object"
},
"UsageRange": {
"enum": [
"day",
"week"
],
"type": "string"
},
"UsageReadParams": {
"properties": {
"range": {
"$ref": "#/definitions/UsageRange"
}
},
"required": [
"range"
],
"type": "object"
},
"UserInput": {
"oneOf": [
{
@@ -4829,6 +4847,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"usage/read"
],
"title": "Usage/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/UsageReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Usage/readRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -829,6 +829,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"usage/read"
],
"title": "Usage/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/UsageReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Usage/readRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -18555,6 +18579,169 @@
"title": "TurnSteerResponse",
"type": "object"
},
"UsageContributorKind": {
"enum": [
"skill",
"subagent",
"app",
"mcpServer",
"plugin"
],
"type": "string"
},
"UsageEntry": {
"properties": {
"attributedTokens": {
"format": "int64",
"type": "integer"
},
"id": {
"type": "string"
},
"kind": {
"$ref": "#/definitions/v2/UsageContributorKind"
},
"label": {
"type": "string"
},
"percentOfUsage": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"attributedTokens",
"id",
"kind",
"label",
"percentOfUsage"
],
"type": "object"
},
"UsageHeadline": {
"properties": {
"entry": {
"$ref": "#/definitions/v2/UsageEntry"
},
"note": {
"type": [
"string",
"null"
]
}
},
"required": [
"entry"
],
"type": "object"
},
"UsageRange": {
"enum": [
"day",
"week"
],
"type": "string"
},
"UsageReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"range": {
"$ref": "#/definitions/v2/UsageRange"
}
},
"required": [
"range"
],
"title": "UsageReadParams",
"type": "object"
},
"UsageReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"report": {
"$ref": "#/definitions/v2/UsageReport"
}
},
"required": [
"report"
],
"title": "UsageReadResponse",
"type": "object"
},
"UsageReport": {
"properties": {
"apps": {
"items": {
"$ref": "#/definitions/v2/UsageEntry"
},
"type": "array"
},
"generatedAt": {
"format": "int64",
"type": "integer"
},
"headline": {
"anyOf": [
{
"$ref": "#/definitions/v2/UsageHeadline"
},
{
"type": "null"
}
]
},
"mcpServers": {
"items": {
"$ref": "#/definitions/v2/UsageEntry"
},
"type": "array"
},
"plugins": {
"items": {
"$ref": "#/definitions/v2/UsageEntry"
},
"type": "array"
},
"range": {
"$ref": "#/definitions/v2/UsageRange"
},
"skills": {
"items": {
"$ref": "#/definitions/v2/UsageEntry"
},
"type": "array"
},
"subagents": {
"items": {
"$ref": "#/definitions/v2/UsageEntry"
},
"type": "array"
},
"totalTokens": {
"format": "int64",
"type": "integer"
},
"trackedFrom": {
"format": "int64",
"type": [
"integer",
"null"
]
}
},
"required": [
"apps",
"generatedAt",
"mcpServers",
"plugins",
"range",
"skills",
"subagents",
"totalTokens"
],
"type": "object"
},
"UserInput": {
"oneOf": [
{
@@ -1555,6 +1555,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"usage/read"
],
"title": "Usage/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/UsageReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Usage/readRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -16379,6 +16403,169 @@
"title": "TurnSteerResponse",
"type": "object"
},
"UsageContributorKind": {
"enum": [
"skill",
"subagent",
"app",
"mcpServer",
"plugin"
],
"type": "string"
},
"UsageEntry": {
"properties": {
"attributedTokens": {
"format": "int64",
"type": "integer"
},
"id": {
"type": "string"
},
"kind": {
"$ref": "#/definitions/UsageContributorKind"
},
"label": {
"type": "string"
},
"percentOfUsage": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"attributedTokens",
"id",
"kind",
"label",
"percentOfUsage"
],
"type": "object"
},
"UsageHeadline": {
"properties": {
"entry": {
"$ref": "#/definitions/UsageEntry"
},
"note": {
"type": [
"string",
"null"
]
}
},
"required": [
"entry"
],
"type": "object"
},
"UsageRange": {
"enum": [
"day",
"week"
],
"type": "string"
},
"UsageReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"range": {
"$ref": "#/definitions/UsageRange"
}
},
"required": [
"range"
],
"title": "UsageReadParams",
"type": "object"
},
"UsageReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"report": {
"$ref": "#/definitions/UsageReport"
}
},
"required": [
"report"
],
"title": "UsageReadResponse",
"type": "object"
},
"UsageReport": {
"properties": {
"apps": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"generatedAt": {
"format": "int64",
"type": "integer"
},
"headline": {
"anyOf": [
{
"$ref": "#/definitions/UsageHeadline"
},
{
"type": "null"
}
]
},
"mcpServers": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"plugins": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"range": {
"$ref": "#/definitions/UsageRange"
},
"skills": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"subagents": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"totalTokens": {
"format": "int64",
"type": "integer"
},
"trackedFrom": {
"format": "int64",
"type": [
"integer",
"null"
]
}
},
"required": [
"apps",
"generatedAt",
"mcpServers",
"plugins",
"range",
"skills",
"subagents",
"totalTokens"
],
"type": "object"
},
"UserInput": {
"oneOf": [
{
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"UsageRange": {
"enum": [
"day",
"week"
],
"type": "string"
}
},
"properties": {
"range": {
"$ref": "#/definitions/UsageRange"
}
},
"required": [
"range"
],
"title": "UsageReadParams",
"type": "object"
}
@@ -0,0 +1,152 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"UsageContributorKind": {
"enum": [
"skill",
"subagent",
"app",
"mcpServer",
"plugin"
],
"type": "string"
},
"UsageEntry": {
"properties": {
"attributedTokens": {
"format": "int64",
"type": "integer"
},
"id": {
"type": "string"
},
"kind": {
"$ref": "#/definitions/UsageContributorKind"
},
"label": {
"type": "string"
},
"percentOfUsage": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"attributedTokens",
"id",
"kind",
"label",
"percentOfUsage"
],
"type": "object"
},
"UsageHeadline": {
"properties": {
"entry": {
"$ref": "#/definitions/UsageEntry"
},
"note": {
"type": [
"string",
"null"
]
}
},
"required": [
"entry"
],
"type": "object"
},
"UsageRange": {
"enum": [
"day",
"week"
],
"type": "string"
},
"UsageReport": {
"properties": {
"apps": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"generatedAt": {
"format": "int64",
"type": "integer"
},
"headline": {
"anyOf": [
{
"$ref": "#/definitions/UsageHeadline"
},
{
"type": "null"
}
]
},
"mcpServers": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"plugins": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"range": {
"$ref": "#/definitions/UsageRange"
},
"skills": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"subagents": {
"items": {
"$ref": "#/definitions/UsageEntry"
},
"type": "array"
},
"totalTokens": {
"format": "int64",
"type": "integer"
},
"trackedFrom": {
"format": "int64",
"type": [
"integer",
"null"
]
}
},
"required": [
"apps",
"generatedAt",
"mcpServers",
"plugins",
"range",
"skills",
"subagents",
"totalTokens"
],
"type": "object"
}
},
"properties": {
"report": {
"$ref": "#/definitions/UsageReport"
}
},
"required": [
"report"
],
"title": "UsageReadResponse",
"type": "object"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UsageContributorKind = "skill" | "subagent" | "app" | "mcpServer" | "plugin";
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UsageContributorKind } from "./UsageContributorKind";
export type UsageEntry = { kind: UsageContributorKind, id: string, label: string, attributedTokens: number, percentOfUsage: number, };
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UsageEntry } from "./UsageEntry";
export type UsageHeadline = { entry: UsageEntry, note: string | null, };
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UsageRange = "day" | "week";
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UsageRange } from "./UsageRange";
export type UsageReadParams = { range: UsageRange, };
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UsageReport } from "./UsageReport";
export type UsageReadResponse = { report: UsageReport, };
@@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UsageEntry } from "./UsageEntry";
import type { UsageHeadline } from "./UsageHeadline";
import type { UsageRange } from "./UsageRange";
export type UsageReport = { range: UsageRange, generatedAt: number, trackedFrom: number | null, totalTokens: number, headline: UsageHeadline | null, skills: Array<UsageEntry>, subagents: Array<UsageEntry>, apps: Array<UsageEntry>, mcpServers: Array<UsageEntry>, plugins: Array<UsageEntry>, };
@@ -448,6 +448,13 @@ export type { TurnStartedNotification } from "./TurnStartedNotification";
export type { TurnStatus } from "./TurnStatus";
export type { TurnSteerParams } from "./TurnSteerParams";
export type { TurnSteerResponse } from "./TurnSteerResponse";
export type { UsageContributorKind } from "./UsageContributorKind";
export type { UsageEntry } from "./UsageEntry";
export type { UsageHeadline } from "./UsageHeadline";
export type { UsageRange } from "./UsageRange";
export type { UsageReadParams } from "./UsageReadParams";
export type { UsageReadResponse } from "./UsageReadResponse";
export type { UsageReport } from "./UsageReport";
export type { UserInput } from "./UserInput";
export type { WarningNotification } from "./WarningNotification";
export type { WebSearchAction } from "./WebSearchAction";
@@ -629,6 +629,11 @@ client_request_definitions! {
serialization: None,
response: v2::PluginListResponse,
},
UsageRead => "usage/read" {
params: v2::UsageReadParams,
serialization: None,
response: v2::UsageReadResponse,
},
PluginInstalled => "plugin/installed" {
params: v2::PluginInstalledParams,
serialization: None,
@@ -232,7 +232,9 @@ impl ThreadHistoryBuilder {
RolloutItem::EventMsg(event) => self.handle_event(event),
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
RolloutItem::ResponseItem(item) => self.handle_response_item(item),
RolloutItem::TurnContext(_) | RolloutItem::SessionMeta(_) => {}
RolloutItem::TurnContext(_)
| RolloutItem::SessionMeta(_)
| RolloutItem::UsageAttribution(_) => {}
}
}
@@ -24,6 +24,7 @@ mod review;
mod thread;
mod thread_data;
mod turn;
mod usage;
mod windows_sandbox;
pub use account::*;
@@ -51,6 +52,7 @@ pub use shared::*;
pub use thread::*;
pub use thread_data::*;
pub use turn::*;
pub use usage::*;
pub use windows_sandbox::*;
#[cfg(test)]
@@ -0,0 +1,89 @@
use codex_protocol::protocol::UsageContributorKind as CoreUsageContributorKind;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum UsageRange {
Day,
Week,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageReadParams {
pub range: UsageRange,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageEntry {
pub kind: UsageContributorKind,
pub id: String,
pub label: String,
#[ts(type = "number")]
pub attributed_tokens: i64,
pub percent_of_usage: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageHeadline {
pub entry: UsageEntry,
pub note: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageReport {
pub range: UsageRange,
#[ts(type = "number")]
pub generated_at: i64,
#[ts(type = "number | null")]
pub tracked_from: Option<i64>,
#[ts(type = "number")]
pub total_tokens: i64,
pub headline: Option<UsageHeadline>,
pub skills: Vec<UsageEntry>,
pub subagents: Vec<UsageEntry>,
pub apps: Vec<UsageEntry>,
pub mcp_servers: Vec<UsageEntry>,
pub plugins: Vec<UsageEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageReadResponse {
pub report: UsageReport,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum UsageContributorKind {
Skill,
Subagent,
App,
McpServer,
Plugin,
}
impl From<CoreUsageContributorKind> for UsageContributorKind {
fn from(value: CoreUsageContributorKind) -> Self {
match value {
CoreUsageContributorKind::Skill => Self::Skill,
CoreUsageContributorKind::Subagent => Self::Subagent,
CoreUsageContributorKind::App => Self::App,
CoreUsageContributorKind::McpServer => Self::McpServer,
CoreUsageContributorKind::Plugin => Self::Plugin,
}
}
}