Skip to content

feat: api response block and implementation #510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 23, 2025
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
7 changes: 7 additions & 0 deletions apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,10 @@ export const SlackIcon = (props: SVGProps<SVGSVGElement>) => (
</g>
</svg>
)

export const ResponseIcon = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' {...props}>
<path d='M20 18v-2a4 4 0 0 0-4-4H4' />
<path d='m9 17-5-5 5-5' />
</svg>
)
17 changes: 16 additions & 1 deletion apps/docs/components/ui/block-types.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { cn } from '@/lib/utils'
import { AgentIcon, ApiIcon, ChartBarIcon, CodeIcon, ConditionalIcon, ConnectIcon } from '../icons'
import {
AgentIcon,
ApiIcon,
ChartBarIcon,
CodeIcon,
ConditionalIcon,
ConnectIcon,
ResponseIcon,
} from '../icons'

// Custom Feature component specifically for BlockTypes to handle the 6-item layout
const BlockFeature = ({
Expand Down Expand Up @@ -127,6 +135,13 @@ export function BlockTypes() {
icon: <ChartBarIcon className='h-6 w-6' />,
href: '/blocks/evaluator',
},
{
title: 'Response',
description:
'Send a response back to the caller with customizable data, status, and headers.',
icon: <ResponseIcon className='h-6 w-6' />,
href: '/blocks/response',
},
]

const totalItems = features.length
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/blocks/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Blocks",
"pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow"]
"pages": ["agent", "api", "condition", "function", "evaluator", "router", "response", "workflow"]
}
188 changes: 188 additions & 0 deletions apps/docs/content/docs/blocks/response.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
---
title: Response
description: Send a structured response back to API calls
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { ThemeImage } from '@/components/ui/theme-image'

The Response block is the final component in API-enabled workflows that transforms your workflow's variables into a structured HTTP response. This block serves as the endpoint that returns data, status codes, and headers back to API callers.

<ThemeImage
lightSrc="/static/light/response-light.png"
darkSrc="/static/dark/response-dark.png"
alt="Response Block"
width={430}
height={784}
/>

<Callout type="info">
Response blocks are terminal blocks - they mark the end of a workflow execution and cannot have further connections.
</Callout>

## Overview

The Response block serves as the final output mechanism for API workflows, enabling you to:

<Steps>
<Step>
<strong>Return structured data</strong>: Transform workflow variables into JSON responses
</Step>
<Step>
<strong>Set HTTP status codes</strong>: Control the response status (200, 400, 500, etc.)
</Step>
<Step>
<strong>Configure headers</strong>: Add custom HTTP headers to the response
</Step>
<Step>
<strong>Reference variables</strong>: Use workflow variables dynamically in the response
</Step>
</Steps>

## Configuration Options

### Response Data

The response data is the main content that will be sent back to the API caller. This should be formatted as JSON and can include:

- Static values
- Dynamic references to workflow variables using the `<variable.name>` syntax
- Nested objects and arrays
- Any valid JSON structure

### Status Code

Set the HTTP status code for the response. Common status codes include:

<Tabs items={['Success (2xx)', 'Client Error (4xx)', 'Server Error (5xx)']}>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li><strong>200</strong>: OK - Standard success response</li>
<li><strong>201</strong>: Created - Resource successfully created</li>
<li><strong>204</strong>: No Content - Success with no response body</li>
</ul>
</Tab>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li><strong>400</strong>: Bad Request - Invalid request parameters</li>
<li><strong>401</strong>: Unauthorized - Authentication required</li>
<li><strong>404</strong>: Not Found - Resource doesn't exist</li>
<li><strong>422</strong>: Unprocessable Entity - Validation errors</li>
</ul>
</Tab>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li><strong>500</strong>: Internal Server Error - Server-side error</li>
<li><strong>502</strong>: Bad Gateway - External service error</li>
<li><strong>503</strong>: Service Unavailable - Service temporarily down</li>
</ul>
</Tab>
</Tabs>

<p className="mt-4 text-sm text-gray-600 dark:text-gray-400">
Default status code is 200 if not specified.
</p>

### Response Headers

Configure additional HTTP headers to include in the response.

Headers are configured as key-value pairs:

| Key | Value |
|-----|-------|
| Content-Type | application/json |
| Cache-Control | no-cache |
| X-API-Version | 1.0 |

## Inputs and Outputs

<Tabs items={['Inputs', 'Outputs']}>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>data</strong> (JSON, optional): The JSON data to send in the response body
</li>
<li>
<strong>status</strong> (number, optional): HTTP status code (default: 200)
</li>
<li>
<strong>headers</strong> (JSON, optional): Additional response headers
</li>
</ul>
</Tab>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>response</strong>: Complete response object containing:
<ul className="list-disc space-y-1 pl-6 mt-2">
<li><strong>data</strong>: The response body data</li>
<li><strong>status</strong>: HTTP status code</li>
<li><strong>headers</strong>: Response headers</li>
</ul>
</li>
</ul>
</Tab>
</Tabs>

## Variable References

Use the `<variable.name>` syntax to dynamically insert workflow variables into your response:

```json
{
"user": {
"id": "<variable.userId>",
"name": "<variable.userName>",
"email": "<variable.userEmail>"
},
"query": "<variable.searchQuery>",
"results": "<variable.searchResults>",
"totalFound": "<variable.resultCount>",
"processingTime": "<variable.executionTime>ms"
}
```

<Callout type="warning">
Variable names are case-sensitive and must match exactly with the variables available in your workflow.
</Callout>

## Example Usage

Here's an example of how a Response block might be configured for a user search API:

```yaml
data: |
{
"success": true,
"data": {
"users": "<variable.searchResults>",
"pagination": {
"page": "<variable.currentPage>",
"limit": "<variable.pageSize>",
"total": "<variable.totalUsers>"
}
},
"query": {
"searchTerm": "<variable.searchTerm>",
"filters": "<variable.appliedFilters>"
},
"timestamp": "<variable.timestamp>"
}
status: 200
headers:
- key: X-Total-Count
value: <variable.totalUsers>
- key: Cache-Control
value: public, max-age=300
```

## Best Practices

- **Use meaningful status codes**: Choose appropriate HTTP status codes that accurately reflect the outcome of the workflow
- **Structure your responses consistently**: Maintain a consistent JSON structure across all your API endpoints for better developer experience
- **Include relevant metadata**: Add timestamps and version information to help with debugging and monitoring
- **Handle errors gracefully**: Use conditional logic in your workflow to set appropriate error responses with descriptive messages
- **Validate variable references**: Ensure all referenced variables exist and contain the expected data types before the Response block executes
Binary file added apps/docs/public/static/dark/response-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions apps/sim/app/api/codegen/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type GenerationType =
| 'javascript-function-body'
| 'typescript-function-body'
| 'custom-tool-schema'
| 'json-object'

// Define the structure for a single message in the history
interface ChatMessage {
Expand Down Expand Up @@ -281,6 +282,24 @@ if (!response.ok) {
const data: unknown = await response.json()
// Add type checking/assertion if necessary
return data // Ensure you return a value if expected`,

'json-object': `You are an expert JSON programmer.
Generate ONLY the raw JSON object based on the user's request.
The output MUST be a single, valid JSON object, starting with { and ending with }.

Do not include any explanations, markdown formatting, or other text outside the JSON object.

You have access to the following variables you can use to generate the JSON body:
- 'params' (object): Contains input parameters derived from the JSON schema. Access these directly using the parameter name wrapped in angle brackets, e.g., '<paramName>'. Do NOT use 'params.paramName'.
- 'environmentVariables' (object): Contains environment variables. Reference these using the double curly brace syntax: '{{ENV_VAR_NAME}}'. Do NOT use 'environmentVariables.VAR_NAME' or env.

Example:
{
"name": "<block.agent.response.content>",
"age": <block.function.output.age>,
"success": true
}
`,
}

export async function POST(req: NextRequest) {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/api/workflows/[id]/execute/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe('Workflow Execution API Route', () => {
// Mock workflow run counts
vi.doMock('@/lib/workflows/utils', () => ({
updateWorkflowRunCounts: vi.fn().mockResolvedValue(undefined),
workflowHasResponseBlock: vi.fn().mockReturnValue(false),
}))

// Mock database
Expand Down
20 changes: 19 additions & 1 deletion apps/sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { persistExecutionError, persistExecutionLogs } from '@/lib/logs/executio
import { buildTraceSpans } from '@/lib/logs/trace-spans'
import { checkServerSideUsageLimits } from '@/lib/usage-monitor'
import { decryptSecret } from '@/lib/utils'
import { updateWorkflowRunCounts } from '@/lib/workflows/utils'
import {
createHttpResponseFromBlock,
updateWorkflowRunCounts,
workflowHasResponseBlock,
} from '@/lib/workflows/utils'
import { db } from '@/db'
import { environment, userStats } from '@/db/schema'
import { Executor } from '@/executor'
Expand Down Expand Up @@ -304,6 +308,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
}

const result = await executeWorkflow(validation.workflow, requestId)

// Check if the workflow execution contains a response block output
const hasResponseBlock = workflowHasResponseBlock(result)
if (hasResponseBlock) {
return createHttpResponseFromBlock(result)
}

return createSuccessResponse(result)
} catch (error: any) {
logger.error(`[${requestId}] Error executing workflow: ${id}`, error)
Expand Down Expand Up @@ -357,6 +368,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{

// Execute workflow with the structured input
const result = await executeWorkflow(validation.workflow, requestId, input)

// Check if the workflow execution contains a response block output
const hasResponseBlock = workflowHasResponseBlock(result)
if (hasResponseBlock) {
return createHttpResponseFromBlock(result)
}

return createSuccessResponse(result)
} catch (error: any) {
logger.error(`[${requestId}] Error executing workflow: ${id}`, error)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactElement } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Wand2 } from 'lucide-react'
import { highlight, languages } from 'prismjs'
import 'prismjs/components/prism-javascript'
Expand All @@ -24,7 +24,7 @@ interface CodeProps {
isConnecting: boolean
placeholder?: string
language?: 'javascript' | 'json'
generationType?: 'javascript-function-body' | 'json-schema'
generationType?: 'javascript-function-body' | 'json-schema' | 'json-object'
value?: string
isPreview?: boolean
previewValue?: string | null
Expand Down Expand Up @@ -62,10 +62,16 @@ export function Code({
disabled = false,
}: CodeProps) {
// Determine the AI prompt placeholder based on language
const aiPromptPlaceholder =
language === 'json'
? 'Describe the JSON schema to generate...'
: 'Describe the JavaScript code to generate...'
const aiPromptPlaceholder = useMemo(() => {
switch (generationType) {
case 'json-schema':
return 'Describe the JSON schema to generate...'
case 'json-object':
return 'Describe the JSON object to generate...'
default:
return 'Describe the JavaScript code to generate...'
}
}, [generationType])

// State management
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
Expand Down
Loading