diff --git a/blocks/blocks/api.ts b/blocks/blocks/api.ts index cd780746a2..3eddaac176 100644 --- a/blocks/blocks/api.ts +++ b/blocks/blocks/api.ts @@ -14,7 +14,7 @@ export const ApiBlock: BlockConfig = { access: ['http.request'] }, workflow: { - outputType: 'json', + outputType: 'any', inputs: { url: 'string', method: 'string', diff --git a/blocks/types.ts b/blocks/types.ts index 89a6595339..c81a2fef3a 100644 --- a/blocks/types.ts +++ b/blocks/types.ts @@ -3,7 +3,7 @@ import type { JSX } from 'react' export type BlockIcon = (props: SVGProps) => JSX.Element export type BlockCategory = 'basic' | 'advanced' -export type OutputType = 'string' | 'number' | 'json' | 'boolean' +export type OutputType = 'string' | 'number' | 'json' | 'boolean' | 'any' export type ParamType = 'string' | 'number' | 'boolean' | 'json' export type SubBlockType = 'short-input' | 'long-input' | 'dropdown' | 'slider' | 'table' | 'code' diff --git a/executor/index.ts b/executor/index.ts index 3aa428f306..2c07e52922 100644 --- a/executor/index.ts +++ b/executor/index.ts @@ -52,12 +52,11 @@ export class Executor { }); if (!response.ok) { - const error = await response.json(); + const error = await response.json().catch(() => ({ message: response.statusText })); throw new Error(tool.transformError(error)); } - const data = await response.json(); - const result = tool.transformResponse(data); + const result = await tool.transformResponse(response); // Validate the output matches the interface this.validateToolOutput(block, result); diff --git a/tools/anthropic/chat.ts b/tools/anthropic/chat.ts index 95c53d076f..1de27e47a2 100644 --- a/tools/anthropic/chat.ts +++ b/tools/anthropic/chat.ts @@ -1,4 +1,4 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; interface ChatParams { apiKey: string; @@ -10,8 +10,7 @@ interface ChatParams { stream?: boolean; } -interface ChatResponse { - output: string; +interface ChatResponse extends ToolResponse { tokens?: number; model: string; } @@ -68,10 +67,11 @@ export const chatTool: ToolConfig = { } }, - transformResponse: (data) => { + transformResponse: async (response: Response) => { + const data = await response.json(); return { - output: data.content[0].text, - tokens: data.usage?.input_tokens + data.usage?.output_tokens, + output: data.completion, + tokens: data.usage?.total_tokens, model: data.model }; }, diff --git a/tools/google/chat.ts b/tools/google/chat.ts index 8bff117ffd..9f6cce6cc4 100644 --- a/tools/google/chat.ts +++ b/tools/google/chat.ts @@ -1,4 +1,4 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; interface ChatParams { apiKey: string; @@ -10,10 +10,10 @@ interface ChatParams { topK?: number; } -interface ChatResponse { - output: string; +interface ChatResponse extends ToolResponse { tokens?: number; model: string; + safetyRatings?: any[]; } export const chatTool: ToolConfig = { @@ -71,10 +71,13 @@ export const chatTool: ToolConfig = { } }, - transformResponse: (data) => { + transformResponse: async (response: Response) => { + const data = await response.json(); return { output: data.candidates[0].content.parts[0].text, - model: 'gemini-pro' + tokens: data.usage?.totalTokens, + model: data.model, + safetyRatings: data.candidates[0].safetyRatings }; }, diff --git a/tools/http/request.ts b/tools/http/request.ts index 68e88aaa4a..ebff615bcd 100644 --- a/tools/http/request.ts +++ b/tools/http/request.ts @@ -1,4 +1,4 @@ -import { ToolConfig, HttpMethod } from '../types'; +import { ToolConfig, HttpMethod, ToolResponse } from '../types'; interface RequestParams { url: string; @@ -12,8 +12,7 @@ interface RequestParams { validateStatus?: (status: number) => boolean; } -interface RequestResponse { - data: any; +interface RequestResponse extends ToolResponse { status: number; headers: Record; } @@ -120,23 +119,22 @@ export const requestTool: ToolConfig = { } }, - transformResponse: (response) => { - // Try to parse response based on content-type - const contentType = response.headers?.['content-type'] || ''; - let data = response.data; + transformResponse: async (response: Response) => { + // Convert Headers to a plain object + const headers: Record = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); - if (contentType.includes('application/json')) { - try { - data = typeof response.data === 'string' ? JSON.parse(response.data) : response.data; - } catch (e) { - // Keep original data if parsing fails - } - } + // Parse response based on content type + const data = await (response.headers.get('content-type')?.includes('application/json') + ? response.json() + : response.text()); return { - data, + output: data, status: response.status, - headers: response.headers + headers }; }, @@ -148,4 +146,4 @@ export const requestTool: ToolConfig = { : ''; return `${message} (${code})${details}`; } -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/tools/hubspot/contacts.ts b/tools/hubspot/contacts.ts index 0c2a37ad49..d75f8ce23e 100644 --- a/tools/hubspot/contacts.ts +++ b/tools/hubspot/contacts.ts @@ -1,33 +1,29 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; -interface ContactParams { +interface ContactsParams { apiKey: string; - email: string; + action: 'create' | 'update' | 'search' | 'delete'; + id?: string; + email?: string; firstName?: string; lastName?: string; phone?: string; company?: string; - id?: string; - properties?: Record; + properties?: Record; limit?: number; after?: string; + data: Record; } -interface ContactResponse { - id: string; - properties: { - email: string; - firstname?: string; - lastname?: string; - phone?: string; - company?: string; - [key: string]: any; +interface ContactsResponse extends ToolResponse { + totalResults?: number; + pagination?: { + hasMore: boolean; + offset: number; }; - createdAt: string; - updatedAt: string; } -export const contactsTool: ToolConfig = { +export const contactsTool: ToolConfig = { id: 'hubspot.contacts', name: 'HubSpot Contacts', description: 'Manage HubSpot contacts - create, search, and update contact records', @@ -116,22 +112,12 @@ export const contactsTool: ToolConfig { - if (Array.isArray(data.results)) { - // Search response - return data.results.map((contact: { id: string; properties: Record; createdAt: string; updatedAt: string }) => ({ - id: contact.id, - properties: contact.properties, - createdAt: contact.createdAt, - updatedAt: contact.updatedAt - })); - } - // Single contact response + transformResponse: async (response: Response) => { + const data = await response.json(); return { - id: data.id, - properties: data.properties, - createdAt: data.createdAt, - updatedAt: data.updatedAt + output: data.results || data, + totalResults: data.total, + pagination: data.paging }; }, diff --git a/tools/openai/chat.ts b/tools/openai/chat.ts index c177cf1743..ce050180bc 100644 --- a/tools/openai/chat.ts +++ b/tools/openai/chat.ts @@ -1,4 +1,4 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; interface ChatParams { apiKey: string; @@ -12,8 +12,7 @@ interface ChatParams { stream?: boolean; } -interface ChatResponse { - output: string; +interface ChatResponse extends ToolResponse { tokens?: number; model: string; } @@ -55,7 +54,6 @@ export const chatTool: ToolConfig = { 'Authorization': `Bearer ${params.apiKey}` }), body: (params) => { - console.log('OpenAI Chat Tool - Request Params:', JSON.stringify(params, null, 2)); const body = { model: params.model || 'gpt-4o', messages: [ @@ -68,12 +66,12 @@ export const chatTool: ToolConfig = { presence_penalty: params.presencePenalty, stream: params.stream }; - console.log('OpenAI Chat Tool - Request Body:', JSON.stringify(body, null, 2)); return body; } }, - transformResponse: (data) => { + transformResponse: async (response: Response) => { + const data = await response.json(); if (data.choices?.[0]?.delta?.content) { return { output: data.choices[0].delta.content, diff --git a/tools/salesforce/opportunities.ts b/tools/salesforce/opportunities.ts index 5a95ba6c48..d47d0dc23f 100644 --- a/tools/salesforce/opportunities.ts +++ b/tools/salesforce/opportunities.ts @@ -1,55 +1,52 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; interface OpportunityParams { - instanceUrl: string; - accessToken: string; - name: string; - accountId?: string; - stage?: string; - amount?: number; - closeDate?: string; - probability?: number; - description?: string; + apiKey: string; + action: 'create' | 'update' | 'search' | 'delete'; id?: string; - properties?: Record; - query?: string; - limit?: number; -} - -interface OpportunityResponse { - id: string; - name: string; + name?: string; accountId?: string; stage?: string; amount?: number; closeDate?: string; probability?: number; - description?: string; - createdDate: string; - lastModifiedDate: string; - [key: string]: any; + properties?: Record; + limit?: number; + offset?: number; + data: Record; } -export const opportunitiesTool: ToolConfig = { +interface OpportunityResponse extends ToolResponse { + totalResults?: number; + pagination?: { + hasMore: boolean; + offset: number; + }; +} + +export const opportunitiesTool: ToolConfig = { id: 'salesforce.opportunities', name: 'Salesforce Opportunities', description: 'Manage Salesforce opportunities - create, query, and update opportunity records', version: '1.0.0', params: { - instanceUrl: { + apiKey: { type: 'string', required: true, - description: 'Salesforce instance URL' + description: 'Salesforce API key' }, - accessToken: { + action: { type: 'string', required: true, - description: 'Salesforce access token' + description: 'Action to perform (create, update, search, delete)' + }, + id: { + type: 'string', + description: 'Opportunity ID (required for updates)' }, name: { type: 'string', - required: true, description: 'Opportunity name' }, accountId: { @@ -72,35 +69,28 @@ export const opportunitiesTool: ToolConfig { - const baseUrl = `${params.instanceUrl}/services/data/v58.0/sobjects/Opportunity`; - if (params.query) { - return `${params.instanceUrl}/services/data/v58.0/query?q=${encodeURIComponent(params.query)}`; - } + const baseUrl = `${params.apiKey}@salesforce.com/services/data/v58.0/sobjects/Opportunity`; if (params.id) { return `${baseUrl}/${params.id}`; } @@ -109,13 +99,9 @@ export const opportunitiesTool: ToolConfig ({ 'Content-Type': 'application/json', - 'Authorization': `Bearer ${params.accessToken}` + 'Authorization': `Bearer ${params.apiKey}` }), body: (params) => { - if (params.query) { - return {}; // Empty body for queries - } - const fields = { Name: params.name, ...(params.accountId && { AccountId: params.accountId }), @@ -123,7 +109,6 @@ export const opportunitiesTool: ToolConfig { - if (data.records) { - // Query response - return data.records.map((record: any) => ({ - id: record.Id, - name: record.Name, - accountId: record.AccountId, - stage: record.StageName, - amount: record.Amount, - closeDate: record.CloseDate, - probability: record.Probability, - description: record.Description, - createdDate: record.CreatedDate, - lastModifiedDate: record.LastModifiedDate, - ...record - })); - } - // Single record response + transformResponse: async (response: Response) => { + const data = await response.json(); return { - id: data.id || data.Id, - name: data.name || data.Name, - accountId: data.accountId || data.AccountId, - stage: data.stage || data.StageName, - amount: data.amount || data.Amount, - closeDate: data.closeDate || data.CloseDate, - probability: data.probability || data.Probability, - description: data.description || data.Description, - createdDate: data.CreatedDate, - lastModifiedDate: data.LastModifiedDate, - ...data + output: data.records || data, + totalResults: data.totalSize, + pagination: { + hasMore: !data.done, + offset: data.nextRecordsUrl ? parseInt(data.nextRecordsUrl.split('-')[1]) : 0 + } }; }, diff --git a/tools/types.ts b/tools/types.ts index d8fbcfe8ae..7d7e0c1ac5 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -1,6 +1,11 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; -export interface ToolConfig

{ +export interface ToolResponse { + output: any; // All tools must provide an output field + [key: string]: any; // Tools can include additional metadata +} + +export interface ToolConfig

{ // Basic tool identification id: string; name: string; @@ -24,6 +29,6 @@ export interface ToolConfig

{ }; // Response handling - transformResponse: (data: any) => R; + transformResponse: (response: Response) => Promise; transformError: (error: any) => string; } \ No newline at end of file diff --git a/tools/xai/chat.ts b/tools/xai/chat.ts index c447a5ad84..151673f76f 100644 --- a/tools/xai/chat.ts +++ b/tools/xai/chat.ts @@ -1,4 +1,4 @@ -import { ToolConfig } from '../types'; +import { ToolConfig, ToolResponse } from '../types'; interface ChatParams { apiKey: string; @@ -11,10 +11,10 @@ interface ChatParams { presencePenalty?: number; } -interface ChatResponse { - output: string; +interface ChatResponse extends ToolResponse { tokens?: number; model: string; + reasoning?: string; } export const chatTool: ToolConfig = { @@ -69,11 +69,13 @@ export const chatTool: ToolConfig = { } }, - transformResponse: (data) => { + transformResponse: async (response: Response) => { + const data = await response.json(); return { output: data.choices[0].message.content, tokens: data.usage?.total_tokens, - model: data.model + model: data.model, + reasoning: data.choices[0]?.reasoning }; },