fix(frontend): resolve issue preventing cost from displaying (V1) (#11798)

This commit is contained in:
Hiep Le 2025-11-26 19:39:07 +07:00 committed by GitHub
parent 2e295073ae
commit 3219834e35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 4 deletions

View File

@ -12,7 +12,8 @@ export function ContextWindowSection({
}: ContextWindowSectionProps) {
const { t } = useTranslation();
const usagePercentage = (perTurnToken / contextWindow) * 100;
const usagePercentage =
contextWindow > 0 ? (perTurnToken / contextWindow) * 100 : 0;
const progressWidth = Math.min(100, usagePercentage);
return (

View File

@ -22,10 +22,12 @@ import {
isConversationStateUpdateEvent,
isFullStateConversationStateUpdateEvent,
isAgentStatusConversationStateUpdateEvent,
isStatsConversationStateUpdateEvent,
isExecuteBashActionEvent,
isExecuteBashObservationEvent,
isConversationErrorEvent,
} from "#/types/v1/type-guards";
import { ConversationStateUpdateEventStats } from "#/types/v1/core/events/conversation-state-event";
import { handleActionEventCacheInvalidation } from "#/utils/cache-utils";
import { buildWebSocketUrl } from "#/utils/websocket-url";
import type {
@ -36,6 +38,7 @@ import EventService from "#/api/event-service/event-service.api";
import { useConversationStore } from "#/state/conversation-store";
import { isBudgetOrCreditError } from "#/utils/error-handler";
import { useTracking } from "#/hooks/use-tracking";
import useMetricsStore from "#/stores/metrics-store";
// eslint-disable-next-line @typescript-eslint/naming-convention
export type V1_WebSocketConnectionState =
@ -105,6 +108,37 @@ export function ConversationWebSocketProvider({
const receivedEventCountRefMain = useRef(0);
const receivedEventCountRefPlanning = useRef(0);
// Helper function to update metrics from stats event
const updateMetricsFromStats = useCallback(
(event: ConversationStateUpdateEventStats) => {
if (event.value.usage_to_metrics?.agent) {
const agentMetrics = event.value.usage_to_metrics.agent;
const metrics = {
cost: agentMetrics.accumulated_cost,
max_budget_per_task: agentMetrics.max_budget_per_task ?? null,
usage: agentMetrics.accumulated_token_usage
? {
prompt_tokens:
agentMetrics.accumulated_token_usage.prompt_tokens,
completion_tokens:
agentMetrics.accumulated_token_usage.completion_tokens,
cache_read_tokens:
agentMetrics.accumulated_token_usage.cache_read_tokens,
cache_write_tokens:
agentMetrics.accumulated_token_usage.cache_write_tokens,
context_window:
agentMetrics.accumulated_token_usage.context_window,
per_turn_token:
agentMetrics.accumulated_token_usage.per_turn_token,
}
: null,
};
useMetricsStore.getState().setMetrics(metrics);
}
},
[],
);
// Build WebSocket URL from props
// Only build URL if we have both conversationId and conversationUrl
// This prevents connection attempts during task polling phase
@ -287,6 +321,9 @@ export function ConversationWebSocketProvider({
if (isAgentStatusConversationStateUpdateEvent(event)) {
setExecutionStatus(event.value);
}
if (isStatsConversationStateUpdateEvent(event)) {
updateMetricsFromStats(event);
}
}
// Handle ExecuteBashAction events - add command as input to terminal
@ -320,6 +357,7 @@ export function ConversationWebSocketProvider({
setExecutionStatus,
appendInput,
appendOutput,
updateMetricsFromStats,
],
);
@ -381,6 +419,9 @@ export function ConversationWebSocketProvider({
if (isAgentStatusConversationStateUpdateEvent(event)) {
setExecutionStatus(event.value);
}
if (isStatsConversationStateUpdateEvent(event)) {
updateMetricsFromStats(event);
}
}
// Handle ExecuteBashAction events - add command as input to terminal
@ -414,6 +455,7 @@ export function ConversationWebSocketProvider({
setExecutionStatus,
appendInput,
appendOutput,
updateMetricsFromStats,
],
);

View File

@ -1,11 +1,63 @@
import { BaseEvent } from "../base/event";
import { V1ExecutionStatus } from "../base/common";
/**
* Token usage metrics for LLM calls
*/
export interface TokenUsage {
model: string;
prompt_tokens: number;
completion_tokens: number;
cache_read_tokens: number;
cache_write_tokens: number;
reasoning_tokens: number;
context_window: number;
per_turn_token: number;
response_id: string;
}
/**
* LLM metrics for a specific component (agent or condenser)
*/
export interface LLMMetrics {
model_name: string;
accumulated_cost: number;
max_budget_per_task: number | null;
accumulated_token_usage: TokenUsage;
costs: Array<{
model: string;
cost: number;
timestamp: number;
}>;
response_latencies: Array<{
model: string;
latency: number;
response_id: string;
}>;
token_usages: TokenUsage[];
}
/**
* Usage metrics mapping for different components
*/
export interface UsageToMetrics {
agent: LLMMetrics;
condenser: LLMMetrics;
}
/**
* Stats containing usage metrics
*/
export interface ConversationStats {
usage_to_metrics: UsageToMetrics;
}
/**
* Conversation state value types
*/
export interface ConversationState {
execution_status: V1ExecutionStatus;
stats?: ConversationStats;
// Add other conversation state fields here as needed
}
@ -19,12 +71,12 @@ interface ConversationStateUpdateEventBase extends BaseEvent {
* Unique key for this state update event.
* Can be "full_state" for full state snapshots or field names for partial updates.
*/
key: "full_state" | "execution_status"; // Extend with other keys as needed
key: "full_state" | "execution_status" | "stats"; // Extend with other keys as needed
/**
* Conversation state updates
*/
value: ConversationState | V1ExecutionStatus;
value: ConversationState | V1ExecutionStatus | ConversationStats;
}
// Narrowed interfaces for full state update event
@ -41,10 +93,18 @@ export interface ConversationStateUpdateEventAgentStatus
value: V1ExecutionStatus;
}
// Narrowed interface for stats update event
export interface ConversationStateUpdateEventStats
extends ConversationStateUpdateEventBase {
key: "stats";
value: ConversationStats;
}
// Conversation state update event - contains conversation state updates
export type ConversationStateUpdateEvent =
| ConversationStateUpdateEventFullState
| ConversationStateUpdateEventAgentStatus;
| ConversationStateUpdateEventAgentStatus
| ConversationStateUpdateEventStats;
// Conversation error event - contains error information
export interface ConversationErrorEvent extends BaseEvent {

View File

@ -14,6 +14,7 @@ import {
ConversationStateUpdateEvent,
ConversationStateUpdateEventAgentStatus,
ConversationStateUpdateEventFullState,
ConversationStateUpdateEventStats,
ConversationErrorEvent,
} from "./core/events/conversation-state-event";
import { SystemPromptEvent } from "./core/events/system-event";
@ -144,6 +145,10 @@ export const isAgentStatusConversationStateUpdateEvent = (
): event is ConversationStateUpdateEventAgentStatus =>
event.key === "execution_status";
export const isStatsConversationStateUpdateEvent = (
event: ConversationStateUpdateEvent,
): event is ConversationStateUpdateEventStats => event.key === "stats";
/**
* Type guard function to check if an event is a conversation error event
*/