11import { db , workflow } from '@sim/db'
22import { eq } from 'drizzle-orm'
33import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
4- import type { ExecutionEnvironment , ExecutionTrigger , WorkflowState } from '@/lib/logs/types'
4+ import type {
5+ ExecutionEnvironment ,
6+ ExecutionTrigger ,
7+ TraceSpan ,
8+ WorkflowState ,
9+ } from '@/lib/logs/types'
510import {
611 loadDeployedWorkflowState ,
712 loadWorkflowFromNormalizedTables ,
@@ -80,7 +85,22 @@ export async function loadDeployedWorkflowStateForLogging(
8085 }
8186}
8287
83- export function calculateCostSummary ( traceSpans : any [ ] ) : {
88+ type CostTraceSpan = Pick < TraceSpan , 'cost' | 'model' | 'tokens' > & {
89+ type ?: TraceSpan [ 'type' ]
90+ children ?: CostTraceSpan [ ]
91+ }
92+
93+ type BillableTraceSpan = CostTraceSpan & { cost : NonNullable < TraceSpan [ 'cost' ] > }
94+
95+ function hasBillableCost ( span : CostTraceSpan ) : span is BillableTraceSpan {
96+ return span . cost !== undefined
97+ }
98+
99+ function isModelBreakdownSpan ( span : CostTraceSpan ) : boolean {
100+ return span . type === 'model'
101+ }
102+
103+ export function calculateCostSummary ( traceSpans : CostTraceSpan [ ] | undefined ) : {
84104 totalCost : number
85105 totalInputCost : number
86106 totalOutputCost : number
@@ -122,17 +142,17 @@ export function calculateCostSummary(traceSpans: any[]): {
122142 * avoid double-counting. The parent cost is set by the provider response
123143 * (and is correctly zeroed by `executeProviderRequest` for BYOK calls);
124144 * model children only carry per-segment cost from the trace enrichers,
125- * which is unaware of BYOK status. Tool children do not carry cost and
126- * are unaffected .
145+ * which is unaware of BYOK status. Non-model children are still visited
146+ * so standalone nested costs remain billable .
127147 *
128148 * Spans without their own `cost` (e.g. parent workflow spans for
129149 * subworkflow blocks) still recurse so nested billable spans are counted.
130150 */
131- const collectCostSpans = ( spans : any [ ] ) : any [ ] => {
132- const costSpans : any [ ] = [ ]
151+ const collectCostSpans = ( spans : CostTraceSpan [ ] ) : BillableTraceSpan [ ] => {
152+ const costSpans : BillableTraceSpan [ ] = [ ]
133153
134154 for ( const span of spans ) {
135- const hasOwnCost = ! ! span . cost
155+ const hasOwnCost = hasBillableCost ( span )
136156 if ( hasOwnCost ) {
137157 costSpans . push ( span )
138158 }
@@ -142,7 +162,7 @@ export function calculateCostSummary(traceSpans: any[]): {
142162 // Parent already accounts for its model segments; only recurse into
143163 // non-model children (e.g. nested workflow spans) to find further
144164 // billable units.
145- const nonModelChildren = span . children . filter ( ( c : any ) => c ?. type !== 'model' )
165+ const nonModelChildren = span . children . filter ( ( child ) => ! isModelBreakdownSpan ( child ) )
146166 costSpans . push ( ...collectCostSpans ( nonModelChildren ) )
147167 } else {
148168 costSpans . push ( ...collectCostSpans ( span . children ) )
0 commit comments