feat(desktop): per-message usage stats UI (tokens, cost, TTFT, tok/s) (#10210)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -2533,7 +2533,9 @@ export type GooseSessionUpdate = ({
|
||||
sessionUpdate: 'usage_update';
|
||||
} & SessionUsageUpdate) | ({
|
||||
sessionUpdate: 'status_message';
|
||||
} & StatusMessageUpdate);
|
||||
} & StatusMessageUpdate) | ({
|
||||
sessionUpdate: 'message_usage';
|
||||
} & MessageUsageUpdate);
|
||||
|
||||
/**
|
||||
* Streaming context-window usage update for a session.
|
||||
@@ -2562,6 +2564,43 @@ export type StatusMessageUpdate = {
|
||||
status: StatusMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wire mirror of the conversation `MessageUsage` (this crate cannot depend on
|
||||
* goose-provider-types); field names and serde casing MUST stay in parity.
|
||||
*/
|
||||
export type MessageUsageData = {
|
||||
inputTokens?: number | null;
|
||||
outputTokens?: number | null;
|
||||
totalTokens?: number | null;
|
||||
cacheReadTokens?: number | null;
|
||||
cacheWriteTokens?: number | null;
|
||||
cost?: number | null;
|
||||
costSource?: CostSourceData | null;
|
||||
/**
|
||||
* Wall-clock generation time, used by the client for a tokens/sec readout.
|
||||
*/
|
||||
elapsedMs?: number | null;
|
||||
timeToFirstTokenMs?: number | null;
|
||||
/**
|
||||
* Usage from a compaction/summarization call rather than a normal turn.
|
||||
*/
|
||||
isCompaction?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wire mirror of the conversation `CostSource`.
|
||||
*/
|
||||
export type CostSourceData = 'provider_reported' | 'estimated';
|
||||
|
||||
/**
|
||||
* Per-message token usage/cost/timing, keyed by the message id used for
|
||||
* chunk matching. Sent live after a turn's messages and on replay.
|
||||
*/
|
||||
export type MessageUsageUpdate = {
|
||||
messageId?: string | null;
|
||||
usage: MessageUsageData;
|
||||
};
|
||||
|
||||
export type RequestRecipeParams_unstable = {
|
||||
sessionId: string;
|
||||
parameters: Array<RecipeParameterDto>;
|
||||
|
||||
@@ -2698,6 +2698,70 @@ export const zStatusMessageUpdate = z.object({
|
||||
status: zStatusMessage
|
||||
});
|
||||
|
||||
/**
|
||||
* Wire mirror of the conversation `CostSource`.
|
||||
*/
|
||||
export const zCostSourceData = z.union([
|
||||
z.literal('provider_reported'),
|
||||
z.literal('estimated')
|
||||
]);
|
||||
|
||||
/**
|
||||
* Wire mirror of the conversation `MessageUsage` (this crate cannot depend on
|
||||
* goose-provider-types); field names and serde casing MUST stay in parity.
|
||||
*/
|
||||
export const zMessageUsageData = z.object({
|
||||
inputTokens: z.union([
|
||||
z.number().int(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
outputTokens: z.union([
|
||||
z.number().int(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
totalTokens: z.union([
|
||||
z.number().int(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
cacheReadTokens: z.union([
|
||||
z.number().int(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
cacheWriteTokens: z.union([
|
||||
z.number().int(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
cost: z.union([
|
||||
z.number(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
costSource: z.union([
|
||||
zCostSourceData,
|
||||
z.null()
|
||||
]).optional(),
|
||||
elapsedMs: z.union([
|
||||
z.number().int().gte(0),
|
||||
z.null()
|
||||
]).optional(),
|
||||
timeToFirstTokenMs: z.union([
|
||||
z.number().int().gte(0),
|
||||
z.null()
|
||||
]).optional(),
|
||||
isCompaction: z.boolean().optional().default(false)
|
||||
});
|
||||
|
||||
/**
|
||||
* Per-message token usage/cost/timing, keyed by the message id used for
|
||||
* chunk matching. Sent live after a turn's messages and on replay.
|
||||
*/
|
||||
export const zMessageUsageUpdate = z.object({
|
||||
messageId: z.union([
|
||||
z.string(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
usage: zMessageUsageData
|
||||
});
|
||||
|
||||
/**
|
||||
* Discriminated union of goose-specific session update payloads.
|
||||
* Variant tag matches ACP's convention (`sessionUpdate: "<snake_case>"`).
|
||||
@@ -2712,7 +2776,10 @@ export const zGooseSessionUpdate = z.union([
|
||||
}).and(zSessionUsageUpdate),
|
||||
z.object({
|
||||
sessionUpdate: z.literal('status_message')
|
||||
}).and(zStatusMessageUpdate)
|
||||
}).and(zStatusMessageUpdate),
|
||||
z.object({
|
||||
sessionUpdate: z.literal('message_usage')
|
||||
}).and(zMessageUsageUpdate)
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user