Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import type { OAuthProvider } from '@/lib/oauth/oauth'
import { cn } from '@/lib/utils'
import { getAllBlocks } from '@/blocks'
import { supportsToolUsageControl } from '@/providers/model-capabilities'
import { getProviderFromModel } from '@/providers/utils'
import { getProviderFromModel, supportsToolUsageControl } from '@/providers/utils'
import { useCustomToolsStore } from '@/stores/custom-tools/store'
import { useGeneralStore } from '@/stores/settings/general/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
Expand Down
8 changes: 6 additions & 2 deletions apps/sim/app/w/[id]/hooks/use-workflow-execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,13 @@ export function useWorkflowExecution() {
const streamingBlockId = (result.metadata as any)?.streamingBlockId || null

for (const log of enrichedResult.logs) {
// Only update the specific agent block that was streamed
// Only update the specific LLM block (agent/router) that was streamed
const isStreamingBlock = streamingBlockId && log.blockId === streamingBlockId
if (isStreamingBlock && log.blockType === 'agent' && log.output?.response) {
if (
isStreamingBlock &&
(log.blockType === 'agent' || log.blockType === 'router') &&
log.output?.response
) {
log.output.response.content = streamContent
}
}
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/w/logs/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@ export function Sidebar({
{isWorkflowWithCost && (
<div className='border-t bg-muted p-3 text-muted-foreground text-xs'>
<p>
This is the total cost for all agent blocks in this workflow execution.
This is the total cost for all LLM-based blocks in this workflow
execution.
</p>
</div>
)}
Expand Down
36 changes: 12 additions & 24 deletions apps/sim/blocks/blocks/agent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { AgentIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
import { MODELS_TEMP_RANGE_0_1, MODELS_TEMP_RANGE_0_2 } from '@/providers/model-capabilities'
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
import {
getAllModelProviders,
getBaseModelProviders,
getHostedModels,
MODELS_TEMP_RANGE_0_1,
MODELS_TEMP_RANGE_0_2,
providers,
} from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
import type { BlockConfig } from '../types'
Expand Down Expand Up @@ -121,29 +127,11 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
// Hide API key for all OpenAI and Claude models when running on hosted version
// Hide API key for all hosted models when running on hosted version
condition: isHosted
? {
field: 'model',
// Include all OpenAI models and Claude models for which we don't show the API key field
value: [
// OpenAI models
'gpt-4o',
'o1',
'o1-mini',
'o1-preview',
'o3',
'o3-preview',
'o4-mini',
'gpt-4.1',
'gpt-4.1-nano',
'gpt-4.1-mini',
// Claude models
'claude-sonnet-4-0',
'claude-opus-4-0',
'claude-3-7-sonnet-latest',
'claude-3-5-sonnet-latest',
],
value: getHostedModels(),
not: true, // Show for all models EXCEPT those listed
}
: undefined, // Show for all models in non-hosted environments
Expand All @@ -158,7 +146,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
connectionDroppable: false,
condition: {
field: 'model',
value: ['azure/gpt-4o', 'azure/o3', 'azure/o4-mini', 'azure/gpt-4.1', 'azure/model-router'],
value: providers['azure-openai'].models,
},
},
{
Expand All @@ -170,7 +158,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
connectionDroppable: false,
condition: {
field: 'model',
value: ['azure/gpt-4o', 'azure/o3', 'azure/o4-mini', 'azure/gpt-4.1', 'azure/model-router'],
value: providers['azure-openai'].models,
},
},
{
Expand Down
17 changes: 16 additions & 1 deletion apps/sim/blocks/blocks/evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ChartBarIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
import type { ProviderId } from '@/providers/types'
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
import type { BlockConfig, ParamType } from '../types'
Expand All @@ -26,6 +27,11 @@ interface EvaluatorResponse extends ToolResponse {
completion?: number
total?: number
}
cost?: {
input: number
output: number
total: number
}
[metricName: string]: any // Allow dynamic metric fields
}
}
Expand Down Expand Up @@ -181,6 +187,13 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
condition: isHosted
? {
field: 'model',
value: getHostedModels(),
not: true,
}
: undefined,
},
{
id: 'systemPrompt',
Expand Down Expand Up @@ -299,6 +312,7 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
content: 'string',
model: 'string',
tokens: 'any',
cost: 'any',
},
dependsOn: {
subBlockId: 'metrics',
Expand All @@ -307,6 +321,7 @@ export const EvaluatorBlock: BlockConfig<EvaluatorResponse> = {
content: 'string',
model: 'string',
tokens: 'any',
cost: 'any',
},
whenFilled: 'json',
},
Expand Down
17 changes: 16 additions & 1 deletion apps/sim/blocks/blocks/router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConnectIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
import type { ProviderId } from '@/providers/types'
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
import type { BlockConfig } from '../types'
Expand All @@ -14,6 +15,11 @@ interface RouterResponse extends ToolResponse {
completion?: number
total?: number
}
cost?: {
input: number
output: number
total: number
}
selectedPath: {
blockId: string
blockType: string
Expand Down Expand Up @@ -125,6 +131,14 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
placeholder: 'Enter your API key',
password: true,
connectionDroppable: false,
// Hide API key for all hosted models when running on hosted version
condition: isHosted
? {
field: 'model',
value: getHostedModels(),
not: true, // Show for all models EXCEPT those listed
}
: undefined, // Show for all models in non-hosted environments
},
{
id: 'systemPrompt',
Expand Down Expand Up @@ -171,6 +185,7 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
content: 'string',
model: 'string',
tokens: 'any',
cost: 'any',
selectedPath: 'json',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ describe('EvaluatorBlockHandler', () => {
content: 'This is the content to evaluate.',
model: 'mock-model',
tokens: { prompt: 50, completion: 10, total: 60 },
cost: {
input: 0,
output: 0,
total: 0,
},
score1: 5,
score2: 8,
},
Expand Down
15 changes: 14 additions & 1 deletion apps/sim/executor/handlers/evaluator/evaluator-handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import type { BlockOutput } from '@/blocks/types'
import { getProviderFromModel } from '@/providers/utils'
import { calculateCost, getProviderFromModel } from '@/providers/utils'
import type { SerializedBlock } from '@/serializer/types'
import type { BlockHandler, ExecutionContext } from '../../types'

Expand Down Expand Up @@ -241,6 +241,14 @@ export class EvaluatorBlockHandler implements BlockHandler {
logger.error('Error extracting metric scores:', e)
}

// Calculate cost based on token usage, similar to how providers do it
const costCalculation = calculateCost(
result.model,
result.tokens?.prompt || 0,
result.tokens?.completion || 0,
false // Evaluator blocks don't typically use cached input
)

// Create result with metrics as direct fields for easy access
const outputResult = {
response: {
Expand All @@ -251,6 +259,11 @@ export class EvaluatorBlockHandler implements BlockHandler {
completion: result.tokens?.completion || 0,
total: result.tokens?.total || 0,
},
cost: {
input: costCalculation.input,
output: costCalculation.output,
total: costCalculation.total,
},
...metricScores,
},
}
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/executor/handlers/router/router-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ describe('RouterBlockHandler', () => {
content: 'Choose the best option.',
model: 'mock-model',
tokens: { prompt: 100, completion: 5, total: 105 },
cost: {
input: 0,
output: 0,
total: 0,
},
Comment thread
waleedlatif1 marked this conversation as resolved.
selectedPath: {
blockId: 'target-block-1',
blockType: 'target',
Expand Down
15 changes: 14 additions & 1 deletion apps/sim/executor/handlers/router/router-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import { generateRouterPrompt } from '@/blocks/blocks/router'
import type { BlockOutput } from '@/blocks/types'
import { getProviderFromModel } from '@/providers/utils'
import { calculateCost, getProviderFromModel } from '@/providers/utils'
import type { SerializedBlock } from '@/serializer/types'
import type { PathTracker } from '../../path'
import type { BlockHandler, ExecutionContext } from '../../types'
Expand Down Expand Up @@ -92,6 +92,14 @@ export class RouterBlockHandler implements BlockHandler {

const tokens = result.tokens || { prompt: 0, completion: 0, total: 0 }

// Calculate cost based on token usage, similar to how providers do it
const cost = calculateCost(
result.model,
tokens.prompt || 0,
tokens.completion || 0,
false // Router blocks don't typically use cached input
)

return {
response: {
content: inputs.prompt,
Expand All @@ -101,6 +109,11 @@ export class RouterBlockHandler implements BlockHandler {
completion: tokens.completion || 0,
total: tokens.total || 0,
},
cost: {
input: cost.input,
output: cost.output,
total: cost.total,
},
selectedPath: {
blockId: chosenBlock.id,
blockType: chosenBlock.type || 'unknown',
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/lib/email/mailer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('mailer', () => {
expect(mockSend).not.toHaveBeenCalled()
})

it('should handle Resend API errors', async () => {
it.concurrent('should handle Resend API errors', async () => {
mockSend.mockResolvedValue({
data: null,
error: { message: 'API rate limit exceeded' },
Expand All @@ -131,7 +131,7 @@ describe('mailer', () => {
expect(result.message).toBe('API rate limit exceeded')
})

it('should handle unexpected errors', async () => {
it.concurrent('should handle unexpected errors', async () => {
mockSend.mockRejectedValue(new Error('Network error'))

const result = await sendEmail(testEmailOptions)
Expand All @@ -140,7 +140,7 @@ describe('mailer', () => {
expect(result.message).toBe('Failed to send email')
})

it('should use custom from address when provided', async () => {
it.concurrent('should use custom from address when provided', async () => {
await sendEmail({
...testEmailOptions,
from: 'custom@example.com',
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('mailer', () => {
)
})

it('should replace unsubscribe token placeholders in HTML', async () => {
it.concurrent('should replace unsubscribe token placeholders in HTML', async () => {
const htmlWithPlaceholder = '<p>Content</p><a href="{{UNSUBSCRIBE_TOKEN}}">Unsubscribe</a>'

await sendEmail({
Expand Down
Loading