feat(sdk): add get_usage_info() control request to CLI and both SDKs - #6473
feat(sdk): add get_usage_info() control request to CLI and both SDKs#6473juhuan wants to merge 1 commit into
Conversation
Add a new `get_usage_info` control request that returns local usage statistics from the CLI's usage history service. Data includes token totals, per-model breakdown, daily trends, and heatmap data. CLI: - Add `get_usage_info` routing in ControlDispatcher - Add `handleGetUsageInfo` in SystemController (calls loadUsageDashboard) - Add `CLIControlGetUsageInfoRequest` type Python SDK: - Add `get_usage_info(range="today")` method to Query - Add `CLIControlGetUsageInfoRequest` protocol type TypeScript SDK: - Add `GET_USAGE_INFO` to ControlRequestType enum - Add `getUsageInfo(range)` method to Query - Add `CLIControlGetUsageInfoRequest` interface
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks for the PR, @juhuan!
Unfortunately the PR body doesn't follow the required template. Several required sections are missing:
- Why it's needed — what's the motivation or user-facing benefit?
- Reviewer Test Plan —
How to verify,Evidence (Before & After),Tested on,Environment - Risk & Scope — tradeoffs, out-of-scope items, breaking changes
- Linked Issues — any related issue this addresses
The "Summary" and "Test plan" headings don't match the template structure. Could you fill in the template sections so reviewers have enough context to evaluate the change?
中文说明
感谢贡献 @juhuan!
PR 描述缺少模板中的必填部分:
- Why it's needed — 动机或用户价值是什么?
- Reviewer Test Plan — 包括
How to verify、Evidence (Before & After)、Tested on、Environment - Risk & Scope — 取舍、不在范围内的内容、破坏性变更
- Linked Issues — 是否有关联的 issue?
"Summary" 和 "Test plan" 标题与模板结构不匹配。请补充模板中的各个部分,方便 reviewer 评估。
— Qwen Code · qwen3.7-max
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Also noting:
- No tests were added for
get_usage_infoin any package (CLI controller, TS SDK, or Python SDK). Consider adding at minimum a unit test forSystemController.handleGetUsageInfoand SDK wire-format tests for bothgetUsageInfo()/get_usage_info(). - Missing capability advertisement:
buildControlCapabilities()hascan_get_context_usage: truebut no correspondingcan_get_usage_infoentry. SDK clients using capability discovery won't know this feature exists. - Missing Python sync wrapper:
SyncQueryinsync_query.pywraps every other control request method but notget_usage_info().
|
|
||
| class CLIControlGetUsageInfoRequest(TypedDict): | ||
| subtype: Literal["get_usage_info"] | ||
| range: NotRequired[str] |
There was a problem hiding this comment.
[Suggestion] range is typed as NotRequired[str], accepting any string. The TypeScript SDK constrains it to 'today' | 'week' | 'month' | 'all', and Literal is already imported in this file. An invalid value (e.g., "daily") silently falls through to all-time data in getTimeRangeBounds's default arm.
| range: NotRequired[str] | |
| range: NotRequired[Literal["today", "week", "month", "all"]] |
— qwen3.7-max via Qwen Code /review
| return await self._send_control_request("mcp_server_status") | ||
|
|
||
| async def get_usage_info( | ||
| self, range: str = "today" |
There was a problem hiding this comment.
[Suggestion] Same issue as protocol.py — range should use Literal to match the TypeScript SDK's union type. Add Literal to the typing imports and constrain this parameter:
| self, range: str = "today" | |
| async def get_usage_info( | |
| self, range: Literal["today", "week", "month", "all"] = "today" | |
| ) -> dict[str, Any] | None: |
— qwen3.7-max via Qwen Code /review
| throw new Error('Request aborted'); | ||
| } | ||
|
|
||
| const range = payload.range ?? 'today'; |
There was a problem hiding this comment.
[Suggestion] No runtime validation of range before passing to loadUsageDashboard. The as CLIControlGetUsageInfoRequest cast is compile-time only — the payload arrives as JSON over the wire. getTimeRangeBounds has a default switch arm that returns new Date(0), so an invalid range string silently returns all historical data. Add a guard:
| const range = payload.range ?? 'today'; | |
| const range = payload.range ?? 'today'; | |
| const validRanges = ['today', 'week', 'month', 'all'] as const; | |
| if (!(validRanges as readonly string[]).includes(range)) { | |
| throw new Error(`Invalid range: ${range}. Must be one of: ${validRanges.join(', ')}`); | |
| } |
— qwen3.7-max via Qwen Code /review
| } | ||
|
|
||
| const range = payload.range ?? 'today'; | ||
| const dashboard = await loadUsageDashboard({ range }); |
There was a problem hiding this comment.
[Suggestion] No caching. loadUsageDashboard reads and parses the entire usage_record.jsonl from disk on every call. The JSDoc on loadUsageDashboard explicitly recommends caching: "The load can be I/O heavy on large histories, so callers should cache". The daemon route in routes/usage-stats.ts implements a 60s TTL cache — this control-request path should do the same.
— qwen3.7-max via Qwen Code /review
| } | ||
|
|
||
| return { | ||
| subtype: 'get_usage_info', |
There was a problem hiding this comment.
[Suggestion] ...dashboard spreads the entire UsageDashboard interface (which includes a 183-day heatmap, all models, all skills) into the wire response. Any future field added to UsageDashboard (e.g., per-project data, raw records) auto-leaks into the SDK response with no compile-time warning. Consider explicitly picking fields or defining a response DTO to create a deliberate boundary.
— qwen3.7-max via Qwen Code /review
💡 Suggestion: Consolidate SDK PRsHi @juhuan, thanks for the comprehensive SDK work! We noticed you have 15 open PRs that all modify the same core files ( The problem
Suggestion: regroup into 2 PRsWe recommend closing the current 15 PRs and reopening them as 2 consolidated PRs: PR 1 — Covers pure SDK-side option additions (~9 current PRs):
PR 2 — Covers features that also involve CLI-side
This keeps a reasonable separation of concerns while eliminating the merge-conflict chain and making review much more manageable. /cc @juhuan |
|
Closing in favor of consolidated PRs (see suggestion comment above). Please reopen as 2 grouped PRs. |
Summary
Add a new
get_usage_infocontrol request that returns local usage statistics from the CLI's usage history service. Data includes token totals, per-model breakdown, daily trends, and heatmap data — the same data the/statsinteractive command shows.CLI:
get_usage_infotoSystemControllerinControlDispatcherhandleGetUsageInfoinSystemControllercallsloadUsageDashboard()from coreCLIControlGetUsageInfoRequesttype with optionalrangefieldPython SDK:
get_usage_info(range="today")method onQuery+CLIControlGetUsageInfoRequestprotocol typeTypeScript SDK:
getUsageInfo(range)method onQuery+GET_USAGE_INFOinControlRequestTypeenum +CLIControlGetUsageInfoRequestinterfaceTest plan
pytest— 58 passed)tsc --noEmit)vitest run— 1163 passed)