diff --git a/apps/fabro-web/app/routes/run-billing.tsx b/apps/fabro-web/app/routes/run-billing.tsx index 648b1796b..70fe0e5b2 100644 --- a/apps/fabro-web/app/routes/run-billing.tsx +++ b/apps/fabro-web/app/routes/run-billing.tsx @@ -97,6 +97,9 @@ function TokenBreakdown({ billing }: { billing: BilledTokenCounts }) { ))} +
+ Includes subagent tokens, priced at each subagent's model. +
); } diff --git a/docs/internal/events.md b/docs/internal/events.md index bcab1a858..b419d9396 100644 --- a/docs/internal/events.md +++ b/docs/internal/events.md @@ -422,6 +422,7 @@ Emitted when a workflow node finishes execution. | `usage.reasoning_tokens` | number? | Reasoning/thinking tokens | | `usage.speed` | string? | Speed tier | | `usage.cost` | number? | Estimated cost in USD | +| `billing_by_model` | array? | For an agent stage, the stage's billing split by model: the root session's route and each subagent's own model, a subagent whose model the catalog does not know billed at the root's. Each row has `model`, `tokens`, and `total_usd_micros`, and the rows sum to the stage's billing. Empty for stages without a coding agent and on events written before it existed | | `error` | string? | Error message (flattened from failure detail) | | `failure_class` | string? | `"transient_infra"`, `"deterministic"`, `"budget_exhausted"`, `"compilation_loop"`, `"canceled"`, `"structural"` | | `failure_signature` | string? | Dedup key for repeated failures | @@ -433,6 +434,12 @@ Emitted when a workflow node finishes execution. | `restart_failure_signatures` | object? | Restart failure signature counts | | `response` | string? | Full LLM or agent response text when produced by the stage | | `notes` | string? | Free-text notes | + +An agent stage's usage is its whole session tree's: the root session and +every subagent, live in `StageProjection.usage` and here at completion, both +read from the same fold of the stage's agent events. The root is priced at +its route and each subagent at its own model; where the provider reported a +cost, that cost stands. | `files_touched` | string[] | File paths modified | | `attempt` | number | Attempt number (1-based) | | `max_attempts` | number | Maximum attempts allowed | diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index aa06923ed..3757ae924 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -11227,6 +11227,18 @@ components: agent_control: $ref: "#/components/schemas/AgentControlState" description: Whether the agent is executing normally or waiting for steering after an interrupt. + billing_by_model: + type: array + items: + $ref: "#/components/schemas/BilledModelUsage" + default: [] + description: >- + The completed stage's `usage` split by model, as `stage.completed` + reported it: the root session's route and each subagent's own + model, a subagent whose model the catalog does not know billed at + the root's. Sums to `usage`. Empty while the stage runs and for + stages without a coding agent; the billing rollup then bills + `usage` to `model`. agent: oneOf: - $ref: "#/components/schemas/AgentSessionProjection" @@ -13355,6 +13367,26 @@ components: description: Billed USD amount in micros. example: 720000 + BilledModelUsage: + description: >- + Usage and cost billed to one model: one response, or one model's share + of a stage. + type: object + required: + - model + - tokens + properties: + model: + $ref: "#/components/schemas/BillingModelRef" + tokens: + $ref: "#/components/schemas/CompletionUsage" + total_usd_micros: + type: integer + format: int64 + description: >- + Cost for `tokens`, when the provider reported one or the catalog + could price them. Absent means no cost data, not zero. + BillingModelRef: description: Provider-qualified billing model identity used for cost estimates. type: object diff --git a/lib/foundation/fabro-api/build.rs b/lib/foundation/fabro-api/build.rs index d5c207a1c..72c597aaa 100644 --- a/lib/foundation/fabro-api/build.rs +++ b/lib/foundation/fabro-api/build.rs @@ -385,6 +385,7 @@ fn main() { &[], ), ("StageProjection", "fabro_types::StageProjection", &[]), + ("BilledModelUsage", "fabro_types::BilledModelUsage", &[]), ( "StageInferenceProjection", "fabro_types::StageInferenceProjection", diff --git a/lib/foundation/fabro-api/src/lib.rs b/lib/foundation/fabro-api/src/lib.rs index 485c72e20..c93eff100 100644 --- a/lib/foundation/fabro-api/src/lib.rs +++ b/lib/foundation/fabro-api/src/lib.rs @@ -39,39 +39,39 @@ pub mod types { }; pub use fabro_types::{ ActivatedSkill, AgentControlState, AgentEventProps, AgentMcpToolSummary, - AgentToolsAvailableProps, AskFabro, AuthMethod, AutomationRef, BilledTokenCounts, BlobHash, - CommandTermination, Conclusion, ContextWindowBreakdownItem, ContextWindowCategory, - ContextWindowCountMethod, ContextWindowSnapshot, ContextWindowStaleness, - ContextWindowWarning, CreateVariableRequest, DiffStats, DiffSummary, DirtyStatus, - EventEnvelope, ExecOutputTail, FailureCategory, FailureDetail, FailureSignature, - GitContext, GitRunTarget, GitRunTarget as AutomationGitWorkflowSource, IdpIdentity, - IntegrationConnectionKind, IntegrationConnectionState, IntegrationConnectionStatus, - IntegrationProvider, IntegrationStatus, InterviewOption, InterviewQuestionRecord, - LlmOutputKind, McpServerDraft as CreateMcpServerRequest, McpServerProjection, - McpServerReplace as ReplaceMcpServerRequest, McpServerStatus, McpServerView as McpServer, - McpTransportView, Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, - ModelRef as BillingModelRef, ModelTestMode, PairId, PairMessageId, PairMessageRecord, - PairMessageRequest, PairRecord, PairStartRequest, PairStatus, PairTarget, - PairTranscriptEntry, PairTranscriptResponse, ParallelBranchId, ParallelBranchResult, - PendingInterviewRecord, PermissionLevel, Principal, Provider, PullRequest, - PullRequestCreation, PullRequestCreationId, PullRequestCreationStatus, PullRequestDetails, - PullRequestDetailsStatus, PullRequestDetailsUnavailableReason, PullRequestLink, - PullRequestMeta, PullRequestResponse, QuestionType, RepositoryRef, ReviewTarget, - ReviewTargetKind, Run, RunApproval, RunApprovalState, RunClientProvenance, RunEvent, - RunEventDetailContentKind, RunEventDetailResponse, RunFailure, RunIntent, RunIntentArgs, - RunPairStatusResponse, RunProjection, RunProvenance, RunRunnableSource, RunSandbox, - RunSandboxFailure, RunSandboxInstance, RunSandboxKind, RunSandboxPlan, RunSandboxRuntime, - RunServerProvenance, RunSessionMetadata, RunSize, RunTarget, SandboxDetails, SandboxInfo, - SandboxListMeta, SandboxListResponse, SandboxProviderKind, SandboxProviderLookupError, - SandboxService, SandboxServiceListResponse, SecretMetadata, SecretType, ServerSettings, - SessionDetail, SessionId, SessionStatus, SessionSummary, SessionTurn, - SkillActivationSource, SkillSummary, SkillsProjection, StageCompletion, StageContextWindow, - StageContextWindowUnavailableReason, StageHandler, StageId, StageInferenceProjection, - StageModelUsage, StageOutcome, StageProjection, StageState, StageToolBatchProjection, - SubAgentProjection, SubAgentStatus, SystemActorKind, SystemIntegrationStatus, - SystemIntegrationsResponse, TodoListProjection, ToolCategory, ToolSource, ToolSummary, - TurnId, UpdateVariableRequest, UserPrincipal, Variable, VariableListResponse, WorkflowPath, - WorkflowSettings, WorkflowVersion, WorkflowVersionId, + AgentToolsAvailableProps, AskFabro, AuthMethod, AutomationRef, BilledModelUsage, + BilledTokenCounts, BlobHash, CommandTermination, Conclusion, ContextWindowBreakdownItem, + ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot, + ContextWindowStaleness, ContextWindowWarning, CreateVariableRequest, DiffStats, + DiffSummary, DirtyStatus, EventEnvelope, ExecOutputTail, FailureCategory, FailureDetail, + FailureSignature, GitContext, GitRunTarget, GitRunTarget as AutomationGitWorkflowSource, + IdpIdentity, IntegrationConnectionKind, IntegrationConnectionState, + IntegrationConnectionStatus, IntegrationProvider, IntegrationStatus, InterviewOption, + InterviewQuestionRecord, LlmOutputKind, McpServerDraft as CreateMcpServerRequest, + McpServerProjection, McpServerReplace as ReplaceMcpServerRequest, McpServerStatus, + McpServerView as McpServer, McpTransportView, Model, ModelControls, ModelCosts, + ModelFeatures, ModelLimits, ModelRef as BillingModelRef, ModelTestMode, PairId, + PairMessageId, PairMessageRecord, PairMessageRequest, PairRecord, PairStartRequest, + PairStatus, PairTarget, PairTranscriptEntry, PairTranscriptResponse, ParallelBranchId, + ParallelBranchResult, PendingInterviewRecord, PermissionLevel, Principal, Provider, + PullRequest, PullRequestCreation, PullRequestCreationId, PullRequestCreationStatus, + PullRequestDetails, PullRequestDetailsStatus, PullRequestDetailsUnavailableReason, + PullRequestLink, PullRequestMeta, PullRequestResponse, QuestionType, RepositoryRef, + ReviewTarget, ReviewTargetKind, Run, RunApproval, RunApprovalState, RunClientProvenance, + RunEvent, RunEventDetailContentKind, RunEventDetailResponse, RunFailure, RunIntent, + RunIntentArgs, RunPairStatusResponse, RunProjection, RunProvenance, RunRunnableSource, + RunSandbox, RunSandboxFailure, RunSandboxInstance, RunSandboxKind, RunSandboxPlan, + RunSandboxRuntime, RunServerProvenance, RunSessionMetadata, RunSize, RunTarget, + SandboxDetails, SandboxInfo, SandboxListMeta, SandboxListResponse, SandboxProviderKind, + SandboxProviderLookupError, SandboxService, SandboxServiceListResponse, SecretMetadata, + SecretType, ServerSettings, SessionDetail, SessionId, SessionStatus, SessionSummary, + SessionTurn, SkillActivationSource, SkillSummary, SkillsProjection, StageCompletion, + StageContextWindow, StageContextWindowUnavailableReason, StageHandler, StageId, + StageInferenceProjection, StageModelUsage, StageOutcome, StageProjection, StageState, + StageToolBatchProjection, SubAgentProjection, SubAgentStatus, SystemActorKind, + SystemIntegrationStatus, SystemIntegrationsResponse, TodoListProjection, ToolCategory, + ToolSource, ToolSummary, TurnId, UpdateVariableRequest, UserPrincipal, Variable, + VariableListResponse, WorkflowPath, WorkflowSettings, WorkflowVersion, WorkflowVersionId, }; pub use lithos_llm::catalog::{ModelHandle, ProviderId}; pub use lithos_llm::types::{ diff --git a/lib/foundation/fabro-api/tests/stage_projection_round_trip.rs b/lib/foundation/fabro-api/tests/stage_projection_round_trip.rs index 416c27b56..de27a4811 100644 --- a/lib/foundation/fabro-api/tests/stage_projection_round_trip.rs +++ b/lib/foundation/fabro-api/tests/stage_projection_round_trip.rs @@ -4,6 +4,7 @@ use fabro_api::types::{ ActivatedSkill as ApiActivatedSkill, AgentControlState as ApiAgentControlState, AgentMcpToolSummary as ApiAgentMcpToolSummary, AgentToolsAvailableProps as ApiAgentToolsAvailableProps, + BilledModelUsage as ApiBilledModelUsage, ContextWindowBreakdownItem as ApiContextWindowBreakdownItem, ContextWindowCategory as ApiContextWindowCategory, ContextWindowCountMethod as ApiContextWindowCountMethod, @@ -23,19 +24,91 @@ use fabro_api::types::{ }; use fabro_types::{ ActivatedSkill, AgentControlState, AgentMcpToolSummary, AgentToolsAvailableProps, - ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod, + BilledModelUsage, ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot, ContextWindowStaleness, ContextWindowWarning, LlmOutputKind, - McpServerProjection, McpServerStatus, ParallelBranchId, ParallelBranchResult, PermissionLevel, - SkillActivationSource, SkillSummary, SkillsProjection, StageContextWindow, + McpServerProjection, McpServerStatus, ModelRef, ParallelBranchId, ParallelBranchResult, + PermissionLevel, SkillActivationSource, SkillSummary, SkillsProjection, StageContextWindow, StageContextWindowUnavailableReason, StageId, StageInferenceProjection, StageProjection, StageToolBatchProjection, SubAgentProjection, SubAgentStatus, TodoListKind, TodoListProjection, ToolCategory, ToolSource, ToolSummary, }; +use lithos_llm::catalog::{ModelId, ProviderId}; +use lithos_llm::types::TokenCounts; use serde_json::json; #[test] fn stage_projection_reuses_canonical_type() { assert_same_type::