← Public packages
@kentcdodds/cursor
Cursor API SDK for cloud agents, runs, artifacts, workers, models, repositories, and raw requests.
src/agent-usage.ts
145 lines · 4.1 KB · TypeScriptimport { cursorRequest } from './request.ts'
import { CursorApiError, trimString } from './lib/client.ts'
export type GetAgentUsageParams = {
agentId: string
runId?: string
}
export type AgentUsageTokens = {
inputTokens?: number
outputTokens?: number
cacheReadTokens?: number
cacheWriteTokens?: number
totalTokens?: number
[key: string]: unknown
}
export type AgentUsageCost = {
rawCostCents: number
chargedCents: number
rawUsd: number
chargedUsd: number
}
export type AgentUsageRun = {
id?: string
usage?: AgentUsageTokens
cost?: AgentUsageCost
[key: string]: unknown
}
export type AgentUsageResult = {
agentId: string
totalUsage?: AgentUsageTokens
cost?: AgentUsageCost
runs: AgentUsageRun[]
}
function asRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: null
}
function asNumber(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
}
function centsToUsd(cents: number): number {
return cents / 100
}
/**
* Slim API `cost` (cents) into billed dollars. Returns undefined when the
* billed record is missing — callers must not invent a dollar amount.
*/
function slimCost(value: unknown): AgentUsageCost | undefined {
const record = asRecord(value)
if (!record) return undefined
const rawCostCents = asNumber(record.rawCostCents)
const chargedCents = asNumber(record.chargedCents)
if (rawCostCents === undefined && chargedCents === undefined) return undefined
const raw = rawCostCents ?? 0
const charged = chargedCents ?? 0
return {
rawCostCents: raw,
chargedCents: charged,
rawUsd: centsToUsd(raw),
chargedUsd: centsToUsd(charged),
}
}
function slimUsage(value: unknown): AgentUsageTokens | undefined {
const record = asRecord(value)
if (!record) return undefined
return record as AgentUsageTokens
}
function slimRun(value: unknown): AgentUsageRun | null {
const record = asRecord(value)
if (!record) return null
const run: AgentUsageRun = {}
if (typeof record.id === 'string') run.id = record.id
const usage = slimUsage(record.usage ?? record.totalUsage)
if (usage) run.usage = usage
const cost = slimCost(record.cost)
if (cost) run.cost = cost
return run
}
/**
* Retrieve billed token usage for a Cursor Cloud Agent.
*
* Wraps `GET /v1/agents/{id}/usage` (optional `?runId=`). Dollars come from
* the API `cost` record (`rawCostCents` / `chargedCents`) — never estimate
* from token counts. `chargedCents === 0` means the tokens were plan-included.
*
* @param params.agentId - Cloud agent id (`bc-…`).
* @param params.runId - Optional run id to scope usage.
* @returns Slimmed usage: `{ agentId, totalUsage, cost?, runs }`.
* @example
* import { getAgentUsage } from 'kody:@kentcdodds/cursor/agent-usage'
* const usage = await getAgentUsage({ agentId: 'bc-…' })
* // => { agentId: 'bc-…', totalUsage: { totalTokens: 21456045 },
* // cost: { rawCostCents: 2500.9084, chargedCents: 0, rawUsd: 25.009084, chargedUsd: 0 },
* // runs: […] }
*
* @see https://cursor.com/docs/cloud-agent/api/endpoints
*/
export async function getAgentUsage(
params: GetAgentUsageParams,
): Promise<AgentUsageResult> {
const agentId = trimString(params.agentId)
if (!agentId) throw new Error('agentId is required')
const query: Record<string, string> = {}
const runId = trimString(params.runId)
if (runId) query.runId = runId
const result = await cursorRequest({
path: `/v1/agents/${encodeURIComponent(agentId)}/usage`,
query,
})
if (result.status < 200 || result.status >= 300) {
throw new CursorApiError(result.status, result.body)
}
const body = asRecord(result.body) ?? {}
const runs = Array.isArray(body.runs)
? body.runs.map(slimRun).filter((run): run is AgentUsageRun => run !== null)
: []
const slimmed: AgentUsageResult = {
agentId,
runs,
}
const totalUsage = slimUsage(body.totalUsage)
if (totalUsage) slimmed.totalUsage = totalUsage
const cost = slimCost(body.cost)
if (cost) slimmed.cost = cost
return slimmed
}
/** Default export: billed agent usage (tokens + API cost record). */
export default getAgentUsage