feat(execution): base execution charge of 0.001/execution (#817)

This commit is contained in:
Vikhyath Mondreti
2025-07-29 11:25:50 -07:00
committed by GitHub
parent 08720d926c
commit 13608a8bbc
5 changed files with 45 additions and 58 deletions

View File

@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button'
import { CopyButton } from '@/components/ui/copy-button' import { CopyButton } from '@/components/ui/copy-button'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
import { redactApiKeys } from '@/lib/utils' import { redactApiKeys } from '@/lib/utils'
import { FrozenCanvasModal } from '@/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas-modal' import { FrozenCanvasModal } from '@/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas-modal'
import LogMarkdownRenderer from '@/app/workspace/[workspaceId]/logs/components/sidebar/components/markdown-renderer' import LogMarkdownRenderer from '@/app/workspace/[workspaceId]/logs/components/sidebar/components/markdown-renderer'
@@ -254,14 +255,10 @@ export function Sidebar({
}, [log]) }, [log])
// Helper to determine if we have cost information to display // Helper to determine if we have cost information to display
// All workflow executions now have cost info (base charge + any model costs)
const hasCostInfo = useMemo(() => { const hasCostInfo = useMemo(() => {
return !!( return isWorkflowExecutionLog && log?.metadata?.cost
log?.metadata?.cost && }, [log, isWorkflowExecutionLog])
((log.metadata.cost.input && log.metadata.cost.input > 0) ||
(log.metadata.cost.output && log.metadata.cost.output > 0) ||
(log.metadata.cost.total && log.metadata.cost.total > 0))
)
}, [log])
const isWorkflowWithCost = useMemo(() => { const isWorkflowWithCost = useMemo(() => {
return isWorkflowExecutionLog && hasCostInfo return isWorkflowExecutionLog && hasCostInfo
@@ -492,49 +489,6 @@ export function Sidebar({
</div> </div>
)} )}
{/* Enhanced Cost - only show for enhanced logs with actual cost data */}
{log.metadata?.enhanced && hasCostInfo && (
<div>
<h3 className='mb-1 font-medium text-muted-foreground text-xs'>
Cost Breakdown
</h3>
<div className='space-y-1 text-sm'>
{(log.metadata?.cost?.total ?? 0) > 0 && (
<div className='flex justify-between'>
<span>Total Cost:</span>
<span className='font-medium'>
${log.metadata?.cost?.total?.toFixed(4)}
</span>
</div>
)}
{(log.metadata?.cost?.input ?? 0) > 0 && (
<div className='flex justify-between'>
<span>Input Cost:</span>
<span className='text-muted-foreground'>
${log.metadata?.cost?.input?.toFixed(4)}
</span>
</div>
)}
{(log.metadata?.cost?.output ?? 0) > 0 && (
<div className='flex justify-between'>
<span>Output Cost:</span>
<span className='text-muted-foreground'>
${log.metadata?.cost?.output?.toFixed(4)}
</span>
</div>
)}
{(log.metadata?.cost?.tokens?.total ?? 0) > 0 && (
<div className='flex justify-between'>
<span>Total Tokens:</span>
<span className='text-muted-foreground'>
{log.metadata?.cost?.tokens?.total?.toLocaleString()}
</span>
</div>
)}
</div>
</div>
)}
{/* Frozen Canvas Button - only show for workflow execution logs with execution ID */} {/* Frozen Canvas Button - only show for workflow execution logs with execution ID */}
{isWorkflowExecutionLog && log.executionId && ( {isWorkflowExecutionLog && log.executionId && (
<div> <div>
@@ -588,17 +542,23 @@ export function Sidebar({
{/* Cost Information (moved to bottom) */} {/* Cost Information (moved to bottom) */}
{hasCostInfo && ( {hasCostInfo && (
<div> <div>
<h3 className='mb-1 font-medium text-muted-foreground text-xs'>Models</h3> <h3 className='mb-1 font-medium text-muted-foreground text-xs'>
Cost Breakdown
</h3>
<div className='overflow-hidden rounded-md border'> <div className='overflow-hidden rounded-md border'>
<div className='space-y-2 p-3'> <div className='space-y-2 p-3'>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Input:</span> <span className='text-muted-foreground text-sm'>Base Execution:</span>
<span className='text-sm'>{formatCost(BASE_EXECUTION_CHARGE)}</span>
</div>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Model Input:</span>
<span className='text-sm'> <span className='text-sm'>
{formatCost(log.metadata?.cost?.input || 0)} {formatCost(log.metadata?.cost?.input || 0)}
</span> </span>
</div> </div>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Output:</span> <span className='text-muted-foreground text-sm'>Model Output:</span>
<span className='text-sm'> <span className='text-sm'>
{formatCost(log.metadata?.cost?.output || 0)} {formatCost(log.metadata?.cost?.output || 0)}
</span> </span>
@@ -677,8 +637,8 @@ export function Sidebar({
{isWorkflowWithCost && ( {isWorkflowWithCost && (
<div className='border-t bg-muted p-3 text-muted-foreground text-xs'> <div className='border-t bg-muted p-3 text-muted-foreground text-xs'>
<p> <p>
This is the total cost for all LLM-based blocks in this workflow Total cost includes a base execution charge of{' '}
execution. {formatCost(BASE_EXECUTION_CHARGE)} plus any model usage costs.
</p> </p>
</div> </div>
)} )}

View File

@@ -0,0 +1,9 @@
/**
* Billing and cost constants shared between client and server code
*/
/**
* Base charge applied to every workflow execution
* This charge is applied regardless of whether the workflow uses AI models
*/
export const BASE_EXECUTION_CHARGE = 0.001

View File

@@ -119,6 +119,8 @@ export class ExecutionLogger implements IExecutionLoggerService {
totalTokens: number totalTokens: number
totalPromptTokens: number totalPromptTokens: number
totalCompletionTokens: number totalCompletionTokens: number
baseExecutionCharge: number
modelCost: number
models: Record< models: Record<
string, string,
{ {
@@ -263,6 +265,8 @@ export class ExecutionLogger implements IExecutionLoggerService {
totalTokens: number totalTokens: number
totalPromptTokens: number totalPromptTokens: number
totalCompletionTokens: number totalCompletionTokens: number
baseExecutionCharge: number
modelCost: number
}, },
trigger: ExecutionTrigger['type'] trigger: ExecutionTrigger['type']
): Promise<void> { ): Promise<void> {
@@ -286,7 +290,8 @@ export class ExecutionLogger implements IExecutionLoggerService {
const userId = workflowRecord.userId const userId = workflowRecord.userId
const costMultiplier = getCostMultiplier() const costMultiplier = getCostMultiplier()
const costToStore = costSummary.totalCost * costMultiplier // Apply cost multiplier only to model costs, not base execution charge
const costToStore = costSummary.baseExecutionCharge + costSummary.modelCost * costMultiplier
// Check if user stats record exists // Check if user stats record exists
const userStatsRecords = await db.select().from(userStats).where(eq(userStats.userId, userId)) const userStatsRecords = await db.select().from(userStats).where(eq(userStats.userId, userId))

View File

@@ -1,3 +1,4 @@
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
import type { ExecutionEnvironment, ExecutionTrigger, WorkflowState } from '@/lib/logs/types' import type { ExecutionEnvironment, ExecutionTrigger, WorkflowState } from '@/lib/logs/types'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
@@ -53,6 +54,8 @@ export function calculateCostSummary(traceSpans: any[]): {
totalTokens: number totalTokens: number
totalPromptTokens: number totalPromptTokens: number
totalCompletionTokens: number totalCompletionTokens: number
baseExecutionCharge: number
modelCost: number
models: Record< models: Record<
string, string,
{ {
@@ -65,12 +68,14 @@ export function calculateCostSummary(traceSpans: any[]): {
} { } {
if (!traceSpans || traceSpans.length === 0) { if (!traceSpans || traceSpans.length === 0) {
return { return {
totalCost: 0, totalCost: BASE_EXECUTION_CHARGE,
totalInputCost: 0, totalInputCost: 0,
totalOutputCost: 0, totalOutputCost: 0,
totalTokens: 0, totalTokens: 0,
totalPromptTokens: 0, totalPromptTokens: 0,
totalCompletionTokens: 0, totalCompletionTokens: 0,
baseExecutionCharge: BASE_EXECUTION_CHARGE,
modelCost: 0,
models: {}, models: {},
} }
} }
@@ -139,6 +144,9 @@ export function calculateCostSummary(traceSpans: any[]): {
} }
} }
const modelCost = totalCost
totalCost += BASE_EXECUTION_CHARGE
return { return {
totalCost, totalCost,
totalInputCost, totalInputCost,
@@ -146,6 +154,8 @@ export function calculateCostSummary(traceSpans: any[]): {
totalTokens, totalTokens,
totalPromptTokens, totalPromptTokens,
totalCompletionTokens, totalCompletionTokens,
baseExecutionCharge: BASE_EXECUTION_CHARGE,
modelCost,
models, models,
} }
} }

View File

@@ -1,3 +1,4 @@
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
import { createLogger } from '@/lib/logs/console/logger' import { createLogger } from '@/lib/logs/console/logger'
import { executionLogger } from '@/lib/logs/execution/logger' import { executionLogger } from '@/lib/logs/execution/logger'
import { import {
@@ -117,12 +118,14 @@ export class LoggingSession {
async completeWithError(error?: any): Promise<void> { async completeWithError(error?: any): Promise<void> {
try { try {
const costSummary = { const costSummary = {
totalCost: 0, totalCost: BASE_EXECUTION_CHARGE,
totalInputCost: 0, totalInputCost: 0,
totalOutputCost: 0, totalOutputCost: 0,
totalTokens: 0, totalTokens: 0,
totalPromptTokens: 0, totalPromptTokens: 0,
totalCompletionTokens: 0, totalCompletionTokens: 0,
baseExecutionCharge: BASE_EXECUTION_CHARGE,
modelCost: 0,
models: {}, models: {},
} }