feat(cloud): attribute planner token usage
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
TaskListResponse,
|
||||
TaskSubmissionPayload,
|
||||
TaskStatus,
|
||||
TokenUsageEvent,
|
||||
UserListResponse,
|
||||
UserSubmissionPolicy,
|
||||
} from "./types";
|
||||
@@ -204,6 +205,12 @@ export function getHostTokenUsage(hostId: string): Promise<HostTokenUsageSummary
|
||||
);
|
||||
}
|
||||
|
||||
export function listHostTokenUsageEvents(hostId: string): Promise<TokenUsageEvent[]> {
|
||||
return request<TokenUsageEvent[]>(
|
||||
`/v1/hosts/${encodeURIComponent(hostId)}/token-usage-events?limit=20&offset=0`,
|
||||
);
|
||||
}
|
||||
|
||||
export function listTasks(options?: {
|
||||
status?: TaskStatus;
|
||||
limit?: number;
|
||||
|
||||
@@ -134,3 +134,17 @@ export interface HostTokenUsageSummary {
|
||||
reserved_tokens: number;
|
||||
remaining_tokens: number | null;
|
||||
}
|
||||
|
||||
export interface TokenUsageEvent {
|
||||
id: string;
|
||||
host_id: string;
|
||||
usage_day: string;
|
||||
task_id: string | null;
|
||||
attempt: number | null;
|
||||
provider: string;
|
||||
model: string;
|
||||
input_tokens: number | null;
|
||||
output_tokens: number | null;
|
||||
total_tokens: number;
|
||||
occurred_at: string;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
createUser,
|
||||
getHostGovernancePolicy,
|
||||
getHostTokenUsage,
|
||||
listHostTokenUsageEvents,
|
||||
getUserSubmissionPolicy,
|
||||
listDevices,
|
||||
listHosts,
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
DeviceRecord,
|
||||
HostRecord,
|
||||
HostTokenUsageSummary,
|
||||
TokenUsageEvent,
|
||||
UserRole,
|
||||
} from "../types";
|
||||
|
||||
@@ -54,6 +56,7 @@ const hostSelfSubmissionEnabled = ref(true);
|
||||
const hostMaxActiveTasks = ref("");
|
||||
const hostDailyTokenBudget = ref("");
|
||||
const hostUsage = ref<HostTokenUsageSummary | null>(null);
|
||||
const hostUsageEvents = ref<TokenUsageEvent[]>([]);
|
||||
|
||||
const selectedUser = computed(
|
||||
() => users.value.find((user) => user.id === selectedUserId.value) ?? null,
|
||||
@@ -103,11 +106,13 @@ async function loadHostPolicy() {
|
||||
if (!props.canAdminGovernance || !selectedHostId.value) return;
|
||||
errorMessage.value = "";
|
||||
try {
|
||||
const [policy, usage] = await Promise.all([
|
||||
const [policy, usage, events] = await Promise.all([
|
||||
getHostGovernancePolicy(selectedHostId.value),
|
||||
getHostTokenUsage(selectedHostId.value),
|
||||
listHostTokenUsageEvents(selectedHostId.value),
|
||||
]);
|
||||
hostUsage.value = usage;
|
||||
hostUsageEvents.value = events;
|
||||
hostPolicyRevision.value = policy.revision;
|
||||
hostSelfSubmissionEnabled.value = policy.self_submission_enabled;
|
||||
hostMaxActiveTasks.value = policy.max_active_tasks?.toString() ?? "";
|
||||
@@ -118,7 +123,12 @@ async function loadHostPolicy() {
|
||||
hostSelfSubmissionEnabled.value = true;
|
||||
hostMaxActiveTasks.value = "";
|
||||
hostDailyTokenBudget.value = "";
|
||||
hostUsage.value = await getHostTokenUsage(selectedHostId.value);
|
||||
const [usage, events] = await Promise.all([
|
||||
getHostTokenUsage(selectedHostId.value),
|
||||
listHostTokenUsageEvents(selectedHostId.value),
|
||||
]);
|
||||
hostUsage.value = usage;
|
||||
hostUsageEvents.value = events;
|
||||
return;
|
||||
}
|
||||
showError(error, "failed to load Host policy");
|
||||
@@ -264,7 +274,12 @@ async function saveHostPolicy() {
|
||||
expected_revision: hostPolicyRevision.value ?? 0,
|
||||
});
|
||||
hostPolicyRevision.value = policy.revision;
|
||||
hostUsage.value = await getHostTokenUsage(selectedHostId.value);
|
||||
const [usage, events] = await Promise.all([
|
||||
getHostTokenUsage(selectedHostId.value),
|
||||
listHostTokenUsageEvents(selectedHostId.value),
|
||||
]);
|
||||
hostUsage.value = usage;
|
||||
hostUsageEvents.value = events;
|
||||
successMessage.value = `Host policy saved (revision ${policy.revision})`;
|
||||
} catch (error) {
|
||||
showError(error, "failed to save Host policy");
|
||||
@@ -375,6 +390,18 @@ onMounted(() => void refresh());
|
||||
{{ hostUsage.usage_day }}: used {{ hostUsage.used_tokens }}, reserved {{ hostUsage.reserved_tokens }},
|
||||
remaining {{ hostUsage.remaining_tokens ?? "unmetered" }} tokens.
|
||||
</p>
|
||||
<table v-if="hostUsageEvents.length">
|
||||
<thead><tr><th>Time</th><th>Provider / model</th><th>Tokens</th><th>Task</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="event in hostUsageEvents" :key="event.id">
|
||||
<td class="dim">{{ new Date(event.occurred_at).toLocaleString() }}</td>
|
||||
<td>{{ event.provider }} <span class="dim">{{ event.model }}</span></td>
|
||||
<td>{{ event.total_tokens }}</td>
|
||||
<td class="dim">{{ event.task_id ?? "local" }}<span v-if="event.attempt"> / #{{ event.attempt }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-else class="muted">No Cloud-proxy usage events recorded for this Host.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user