mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat: added postprocessing to tools for tools that need multiple api calls in succession
This commit is contained in:
+34
-5
@@ -16,13 +16,42 @@ export async function POST(request: Request) {
|
||||
throw new Error(`Tool not found: ${toolId}`)
|
||||
}
|
||||
|
||||
// Use executeTool with skipProxy=true to prevent recursive proxy calls
|
||||
const result = await executeTool(toolId, params, true)
|
||||
// Use executeTool with skipProxy=true to prevent recursive proxy calls, and skipPostProcess=true to prevent duplicate post-processing
|
||||
const result = await executeTool(toolId, params, true, true)
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(
|
||||
tool.transformError ? tool.transformError(result) : 'Tool returned an error'
|
||||
)
|
||||
if (tool.transformError) {
|
||||
try {
|
||||
const errorResult = tool.transformError(result)
|
||||
|
||||
// Handle both string and Promise return types
|
||||
if (typeof errorResult === 'string') {
|
||||
throw new Error(errorResult)
|
||||
} else {
|
||||
// It's a Promise, await it
|
||||
const transformedError = await errorResult
|
||||
// If it's a string or has an error property, use it
|
||||
if (typeof transformedError === 'string') {
|
||||
throw new Error(transformedError)
|
||||
} else if (
|
||||
transformedError &&
|
||||
typeof transformedError === 'object' &&
|
||||
'error' in transformedError
|
||||
) {
|
||||
throw new Error(transformedError.error || 'Tool returned an error')
|
||||
}
|
||||
// Fallback
|
||||
throw new Error('Tool returned an error')
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw e
|
||||
}
|
||||
throw new Error('Tool returned an error')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Tool returned an error')
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
|
||||
+7
-1
@@ -164,7 +164,13 @@ export interface Tool<P = any, O = Record<string, any>> {
|
||||
error?: string
|
||||
}>
|
||||
|
||||
transformError?: (error: any) => string // Function to format error messages
|
||||
transformError?: (error: any) =>
|
||||
| string
|
||||
| Promise<{
|
||||
success: boolean
|
||||
output: O
|
||||
error?: string
|
||||
}> // Function to format error messages
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+58
-10
@@ -50,6 +50,44 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
return requestBody
|
||||
},
|
||||
},
|
||||
postProcess: async (result, params, executeTool) => {
|
||||
// Only add content if it was provided and not already added during creation
|
||||
// The Google Docs API doesn't directly support content in the create request,
|
||||
// so we need to add it separately via the write tool
|
||||
if (result.success && params.content) {
|
||||
console.log('Google Docs create - Post-processing: Adding content to document')
|
||||
const documentId = result.output.metadata.documentId
|
||||
|
||||
if (documentId) {
|
||||
try {
|
||||
const writeParams = {
|
||||
accessToken: params.accessToken,
|
||||
documentId: documentId,
|
||||
content: params.content,
|
||||
}
|
||||
console.log('Content to add:', params.content)
|
||||
|
||||
// Use the write tool to add content
|
||||
const writeResult = await executeTool('google_docs_write', writeParams)
|
||||
|
||||
if (!writeResult.success) {
|
||||
console.warn(
|
||||
'Failed to add content to document, but document was created:',
|
||||
writeResult.error
|
||||
)
|
||||
} else {
|
||||
console.log('Google Docs create - Content added successfully')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error adding content to document:', error)
|
||||
// Don't fail the overall operation if adding content fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the original result regardless of post-processing outcome
|
||||
return result
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let errorText = ''
|
||||
@@ -67,7 +105,9 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
try {
|
||||
// Get the response data
|
||||
const responseText = await response.text()
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
const documentId = data.documentId
|
||||
const title = data.title
|
||||
|
||||
@@ -86,19 +126,27 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Google Docs create - Error processing response:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
if (error.message) {
|
||||
return error.message
|
||||
}
|
||||
return (
|
||||
JSON.stringify(error, null, 2) || 'An error occurred while creating Google Docs document'
|
||||
)
|
||||
}
|
||||
transformError: async (error) => {
|
||||
console.error('Google Docs create - Transform error:', error)
|
||||
|
||||
return error.toString() || 'An error occurred while creating Google Docs document'
|
||||
const errorMessage =
|
||||
typeof error === 'object' && error !== null
|
||||
? error.message || JSON.stringify(error, null, 2)
|
||||
: error.toString() || 'An error occurred while creating Google Docs document'
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
metadata: {
|
||||
documentId: '',
|
||||
title: '',
|
||||
},
|
||||
},
|
||||
error: errorMessage,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
+16
-4
@@ -70,11 +70,23 @@ export const readTool: ToolConfig<GoogleDocsToolParams, GoogleDocsReadResponse>
|
||||
},
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
return JSON.stringify(error) || 'An error occurred while reading from Google Docs'
|
||||
transformError: async (error) => {
|
||||
const errorMessage =
|
||||
typeof error === 'object' && error !== null
|
||||
? error.message || JSON.stringify(error, null, 2)
|
||||
: error.toString() || 'An error occurred while reading Google Docs document'
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
documentId: '',
|
||||
title: '',
|
||||
},
|
||||
},
|
||||
error: errorMessage,
|
||||
}
|
||||
return error.message || 'An error occurred while reading from Google Docs'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+19
-12
@@ -48,14 +48,13 @@ export const writeTool: ToolConfig<GoogleDocsToolParams, GoogleDocsWriteResponse
|
||||
}
|
||||
|
||||
// Following the exact format from the Google Docs API examples
|
||||
// Note: We're not including tabId since it's optional for the main document body
|
||||
// Always insert at the end of the document to avoid duplication
|
||||
// See: https://developers.google.com/docs/api/reference/rest/v1/documents/request#InsertTextRequest
|
||||
const requestBody = {
|
||||
requests: [
|
||||
{
|
||||
insertText: {
|
||||
location: {
|
||||
index: 1,
|
||||
},
|
||||
endOfSegmentLocation: {},
|
||||
text: params.content,
|
||||
},
|
||||
},
|
||||
@@ -117,14 +116,22 @@ export const writeTool: ToolConfig<GoogleDocsToolParams, GoogleDocsWriteResponse
|
||||
throw error
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
if (error.message) {
|
||||
return error.message
|
||||
}
|
||||
return JSON.stringify(error, null, 2) || 'An error occurred while writing to Google Docs'
|
||||
}
|
||||
transformError: async (error) => {
|
||||
const errorMessage =
|
||||
typeof error === 'object' && error !== null
|
||||
? error.message || JSON.stringify(error, null, 2)
|
||||
: error.toString() || 'An error occurred while writing to Google Docs'
|
||||
|
||||
return error.toString() || 'An error occurred while writing to Google Docs'
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
updatedContent: false,
|
||||
metadata: {
|
||||
documentId: '',
|
||||
title: '',
|
||||
},
|
||||
},
|
||||
error: errorMessage,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
+79
-7
@@ -279,7 +279,7 @@ function getCustomTool(customToolId: string): ToolConfig | undefined {
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
transformError: (error: any) =>
|
||||
transformError: async (error: any) =>
|
||||
`Custom tool execution error: ${error.message || 'Unknown error'}`,
|
||||
}
|
||||
}
|
||||
@@ -288,7 +288,8 @@ function getCustomTool(customToolId: string): ToolConfig | undefined {
|
||||
export async function executeTool(
|
||||
toolId: string,
|
||||
params: Record<string, any>,
|
||||
skipProxy = false
|
||||
skipProxy = false,
|
||||
skipPostProcess = false
|
||||
): Promise<ToolResponse> {
|
||||
try {
|
||||
const tool = getTool(toolId)
|
||||
@@ -313,11 +314,36 @@ export async function executeTool(
|
||||
// For internal routes or when skipProxy is true, call the API directly
|
||||
if (tool.request.isInternalRoute || skipProxy) {
|
||||
const result = await handleInternalRequest(toolId, tool, params)
|
||||
|
||||
// Apply post-processing if available and not skipped
|
||||
if (tool.postProcess && result.success && !skipPostProcess) {
|
||||
try {
|
||||
return await tool.postProcess(result, params, executeTool)
|
||||
} catch (error) {
|
||||
console.error(`Error in post-processing for tool ${toolId}:`, error)
|
||||
// Return original result if post-processing fails
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// For external APIs, use the proxy
|
||||
return await handleProxyRequest(toolId, params)
|
||||
const result = await handleProxyRequest(toolId, params)
|
||||
|
||||
// Apply post-processing if available and not skipped
|
||||
if (tool.postProcess && result.success && !skipPostProcess) {
|
||||
try {
|
||||
return await tool.postProcess(result, params, executeTool)
|
||||
} catch (error) {
|
||||
console.error(`Error in post-processing for tool ${toolId}:`, error)
|
||||
// Return original result if post-processing fails
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: any) {
|
||||
console.error(`Error executing tool ${toolId}:`, error)
|
||||
|
||||
@@ -396,12 +422,58 @@ async function handleInternalRequest(
|
||||
error: undefined,
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Error executing internal tool ${toolId}:`, error)
|
||||
|
||||
// Use the tool's error transformer if available
|
||||
if (tool.transformError) {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: tool.transformError(error),
|
||||
try {
|
||||
const errorResult = tool.transformError(error)
|
||||
|
||||
// Handle both string and Promise return types
|
||||
if (typeof errorResult === 'string') {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: errorResult,
|
||||
}
|
||||
} else {
|
||||
// It's a Promise, await it
|
||||
const transformedError = await errorResult
|
||||
// If it's a string or has an error property, use it
|
||||
if (typeof transformedError === 'string') {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: transformedError,
|
||||
}
|
||||
} else if (transformedError && typeof transformedError === 'object') {
|
||||
// If it's already a ToolResponse, return it directly
|
||||
if ('success' in transformedError) {
|
||||
return transformedError
|
||||
}
|
||||
// If it has an error property, use it
|
||||
if ('error' in transformedError) {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: transformedError.error,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Unknown error',
|
||||
}
|
||||
}
|
||||
} catch (transformError) {
|
||||
console.error(`Error transforming error for tool ${toolId}:`, transformError)
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: error.message || 'Unknown error',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -14,7 +14,7 @@ export interface OAuthConfig {
|
||||
additionalScopes?: string[] // Additional scopes required for the tool
|
||||
}
|
||||
|
||||
export interface ToolConfig<P = any, R extends ToolResponse = ToolResponse> {
|
||||
export interface ToolConfig<P = any, R = any> {
|
||||
// Basic tool identification
|
||||
id: string
|
||||
name: string
|
||||
@@ -48,9 +48,16 @@ export interface ToolConfig<P = any, R extends ToolResponse = ToolResponse> {
|
||||
// Direct execution in browser (optional) - bypasses HTTP request
|
||||
directExecution?: (params: P) => Promise<R | undefined>
|
||||
|
||||
// Post-processing (optional) - allows additional processing after the initial request
|
||||
postProcess?: (
|
||||
result: R extends ToolResponse ? R : ToolResponse,
|
||||
params: P,
|
||||
executeTool: (toolId: string, params: Record<string, any>) => Promise<ToolResponse>
|
||||
) => Promise<R extends ToolResponse ? R : ToolResponse>
|
||||
|
||||
// Response handling
|
||||
transformResponse: (response: Response) => Promise<R>
|
||||
transformError: (error: any) => string
|
||||
transformResponse?: (response: Response) => Promise<R>
|
||||
transformError?: (error: any) => string | Promise<R>
|
||||
}
|
||||
|
||||
export interface TableRow {
|
||||
|
||||
+33
-5
@@ -77,12 +77,40 @@ export async function executeRequest(
|
||||
}
|
||||
|
||||
// Use the tool's error transformer or a default message
|
||||
const error = tool.transformError
|
||||
? tool.transformError(errorContent)
|
||||
: errorContent.message || `${toolId} API error: ${externalResponse.statusText}`
|
||||
if (tool.transformError) {
|
||||
try {
|
||||
const errorResult = tool.transformError(errorContent)
|
||||
|
||||
console.error(`${toolId} error:`, error)
|
||||
throw new Error(error)
|
||||
// Handle both string and Promise return types
|
||||
if (typeof errorResult === 'string') {
|
||||
throw new Error(errorResult)
|
||||
} else {
|
||||
// It's a Promise, await it
|
||||
const transformedError = await errorResult
|
||||
// If it's a string or has an error property, use it
|
||||
if (typeof transformedError === 'string') {
|
||||
throw new Error(transformedError)
|
||||
} else if (
|
||||
transformedError &&
|
||||
typeof transformedError === 'object' &&
|
||||
'error' in transformedError
|
||||
) {
|
||||
throw new Error(transformedError.error || 'Tool returned an error')
|
||||
}
|
||||
// Fallback
|
||||
throw new Error('Tool returned an error')
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw e
|
||||
}
|
||||
throw new Error(`${toolId} API error: ${externalResponse.statusText}`)
|
||||
}
|
||||
} else {
|
||||
const error = errorContent.message || `${toolId} API error: ${externalResponse.statusText}`
|
||||
console.error(`${toolId} error:`, error)
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const transformResponse =
|
||||
|
||||
Reference in New Issue
Block a user