mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
Added reverse proxy to resolve CORS issues, now all external API calls are routed through /api/proxy, API keys & credentials are now all on the server
This commit is contained in:
110
app/api/proxy/route.ts
Normal file
110
app/api/proxy/route.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getTool } from '@/tools'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
// Expecting a tool identifier and the validated parameters
|
||||
const { toolId, params } = await request.json()
|
||||
|
||||
// Look up the tool config from the registry
|
||||
const tool = getTool(toolId)
|
||||
if (!tool) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Tool not found: ${toolId}`,
|
||||
details: { toolId }
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Destructure the request configuration from the tool
|
||||
const { url: urlOrFn, method: defaultMethod, headers: headersFn, body: bodyFn } = tool.request
|
||||
|
||||
// Compute the external URL
|
||||
const url = typeof urlOrFn === 'function' ? urlOrFn(params) : urlOrFn
|
||||
|
||||
// If the params override the method, allow it (or use the default)
|
||||
const method = params.method || defaultMethod || 'GET'
|
||||
|
||||
// Compute headers using the tool's function
|
||||
const headers = headersFn ? headersFn(params) : {}
|
||||
|
||||
// Build request body if needed
|
||||
const hasBody = method !== 'GET' && method !== 'HEAD' && !!bodyFn
|
||||
const body = hasBody ? JSON.stringify(bodyFn!(params)) : undefined
|
||||
|
||||
// Execute external fetch from the server side
|
||||
const externalResponse = await fetch(url, { method, headers, body })
|
||||
|
||||
// If the response is not OK, transform the error
|
||||
if (!externalResponse.ok) {
|
||||
const errorContent = await externalResponse.json().catch(() => ({
|
||||
message: externalResponse.statusText
|
||||
}))
|
||||
|
||||
// Pass the complete error response to transformError
|
||||
const error = tool.transformError
|
||||
? tool.transformError({
|
||||
...errorContent,
|
||||
status: externalResponse.status,
|
||||
statusText: externalResponse.statusText,
|
||||
// Include raw headers as they were sent
|
||||
headers: {
|
||||
authorization: externalResponse.headers.get('authorization'),
|
||||
'content-type': externalResponse.headers.get('content-type')
|
||||
}
|
||||
})
|
||||
: errorContent.message || 'External API error'
|
||||
|
||||
// Return error in a format that matches the ToolResponse type
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
output: {},
|
||||
error: error
|
||||
}, {
|
||||
status: externalResponse.status
|
||||
})
|
||||
}
|
||||
|
||||
// Transform the response if needed
|
||||
const transformResponse =
|
||||
tool.transformResponse ||
|
||||
(async (resp: Response) => ({
|
||||
success: true,
|
||||
output: await resp.json(),
|
||||
}))
|
||||
const result = await transformResponse(externalResponse)
|
||||
|
||||
if (!result.success) {
|
||||
const error = tool.transformError
|
||||
? tool.transformError(result)
|
||||
: 'Tool returned an error'
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
output: {},
|
||||
error: error
|
||||
}, {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error: any) {
|
||||
console.error('Proxy route error:', error)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error.message || 'Internal server error',
|
||||
details: {
|
||||
name: error.name,
|
||||
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
||||
}
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
ExecutionResult,
|
||||
BlockLog
|
||||
} from './types'
|
||||
import { tools } from '@/tools'
|
||||
import { tools, executeTool } from '@/tools'
|
||||
|
||||
export class Executor {
|
||||
constructor(
|
||||
@@ -219,59 +219,17 @@ export class Executor {
|
||||
throw new Error(`Tool not found: ${toolId}`)
|
||||
}
|
||||
|
||||
// Merge block's static params with dynamic inputs
|
||||
// Merge block's static params with dynamic inputs and validate them
|
||||
const validatedParams = this.validateToolParams(tool, {
|
||||
...block.config.params,
|
||||
...inputs,
|
||||
})
|
||||
|
||||
if (!tool.request) {
|
||||
throw new Error(`Tool "${toolId}" has no request config.`)
|
||||
}
|
||||
|
||||
const { url: urlOrFn, method: defaultMethod, headers: headersFn, body: bodyFn } =
|
||||
tool.request
|
||||
|
||||
// Build the URL
|
||||
const url = typeof urlOrFn === 'function' ? urlOrFn(validatedParams) : urlOrFn
|
||||
// Determine HTTP method
|
||||
const methodFromParams =
|
||||
typeof validatedParams.method === 'object'
|
||||
? validatedParams.method.method
|
||||
: validatedParams.method
|
||||
const method = methodFromParams || defaultMethod || 'GET'
|
||||
|
||||
// Safely compute headers
|
||||
const headers = headersFn?.(validatedParams) ?? {}
|
||||
|
||||
// Build body if needed
|
||||
const bodyNeeded = method !== 'GET' && method !== 'HEAD' && !!bodyFn
|
||||
const body = bodyNeeded
|
||||
? JSON.stringify(bodyFn!(validatedParams))
|
||||
: undefined
|
||||
|
||||
// Perform fetch()
|
||||
const response = await fetch(url || '', { method, headers, body })
|
||||
if (!response.ok) {
|
||||
// In case there is a custom transformError
|
||||
const transformError = tool.transformError ?? (() => 'Unknown error')
|
||||
const errorBody = await response.json().catch(() => ({
|
||||
message: response.statusText,
|
||||
}))
|
||||
throw new Error(transformError(errorBody))
|
||||
}
|
||||
|
||||
// Transform the response
|
||||
const transformResponse =
|
||||
tool.transformResponse ??
|
||||
(async (resp: Response) => ({
|
||||
success: true,
|
||||
output: await resp.json(),
|
||||
}))
|
||||
|
||||
const result = await transformResponse(response)
|
||||
// Call centralized reverse proxy endpoint via executeTool().
|
||||
const result = await executeTool(toolId, validatedParams)
|
||||
|
||||
if (!result.success) {
|
||||
const transformError = tool.transformError ?? (() => 'Tool returned an error object')
|
||||
const transformError = tool.transformError ?? (() => 'Tool returned an error')
|
||||
throw new Error(transformError(result))
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
|
||||
type: 'number',
|
||||
default: 0.7,
|
||||
description: 'Controls randomness in the response'
|
||||
},
|
||||
maxTokens: {
|
||||
type: 'number',
|
||||
default: 4096,
|
||||
description: 'Maximum number of tokens to generate'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -61,41 +66,46 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
|
||||
'anthropic-version': '2023-06-01'
|
||||
}),
|
||||
body: (params) => {
|
||||
const messages = [
|
||||
{ role: 'user', content: params.systemPrompt }
|
||||
]
|
||||
const messages = []
|
||||
|
||||
// Add user message if context is provided
|
||||
if (params.context) {
|
||||
messages.push({ role: 'user', content: params.context })
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: params.context
|
||||
})
|
||||
}
|
||||
|
||||
const body = {
|
||||
return {
|
||||
model: params.model || 'claude-3-5-sonnet-20241022',
|
||||
messages,
|
||||
temperature: params.temperature,
|
||||
max_tokens: params.maxTokens,
|
||||
top_p: params.topP,
|
||||
stream: params.stream
|
||||
}
|
||||
return body
|
||||
system: params.systemPrompt,
|
||||
temperature: params.temperature || 0.7,
|
||||
max_tokens: params.maxTokens || 4096
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.content) {
|
||||
throw new Error('Unable to extract content from Anthropic API response')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: data.completion,
|
||||
content: data.content[0].text,
|
||||
model: data.model,
|
||||
tokens: data.usage?.total_tokens
|
||||
tokens: data.usage?.input_tokens + data.usage?.output_tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
const message = error.error?.message || error.message
|
||||
const code = error.error?.type || error.code
|
||||
return `${message} (${code})`
|
||||
const message = error.error?.message || error.message
|
||||
const code = error.error?.type || error.code
|
||||
return `${message} (${code})`
|
||||
}
|
||||
}
|
||||
@@ -45,43 +45,40 @@ export function getTool(toolId: string): ToolConfig | undefined {
|
||||
return tools[toolId]
|
||||
}
|
||||
|
||||
// Execute a tool with parameters
|
||||
// Execute a tool by calling the reverse proxy endpoint.
|
||||
export async function executeTool(
|
||||
toolId: string,
|
||||
params: Record<string, any>
|
||||
): Promise<ToolResponse> {
|
||||
const tool = getTool(toolId)
|
||||
|
||||
if (!tool) {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: `Tool not found: ${toolId}`
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the URL (which might be a function or string)
|
||||
const url = typeof tool.request.url === 'function'
|
||||
? tool.request.url(params)
|
||||
: tool.request.url
|
||||
|
||||
// Make the HTTP request
|
||||
const response = await fetch(url, {
|
||||
method: tool.request.method,
|
||||
headers: tool.request.headers(params),
|
||||
body: tool.request.body ? JSON.stringify(tool.request.body(params)) : undefined
|
||||
})
|
||||
|
||||
// Transform the response
|
||||
const result = await tool.transformResponse(response)
|
||||
const response = await fetch('/api/proxy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ toolId, params }),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (!result.success) {
|
||||
// Format error message to include details if available
|
||||
const errorMessage = result.details
|
||||
? `${result.error} (${JSON.stringify(result.details)})`
|
||||
: result.error
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Tool execution error:', error)
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: tool.transformError(error)
|
||||
error: `Error executing tool: ${error.message || 'Unknown error'}`
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user