mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Added ToolResponse interface to standardize block response format, updated executor to use response transformation specified in the tool it is using, confirmed agent & api tool working individually
This commit is contained in:
@@ -14,7 +14,7 @@ export const ApiBlock: BlockConfig = {
|
||||
access: ['http.request']
|
||||
},
|
||||
workflow: {
|
||||
outputType: 'json',
|
||||
outputType: 'any',
|
||||
inputs: {
|
||||
url: 'string',
|
||||
method: 'string',
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { JSX } from 'react'
|
||||
|
||||
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => 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'
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ChatParams, ChatResponse> = {
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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<ChatParams, ChatResponse> = {
|
||||
@@ -71,10 +71,13 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
@@ -120,23 +119,22 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
|
||||
}
|
||||
},
|
||||
|
||||
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<string, string> = {};
|
||||
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<RequestParams, RequestResponse> = {
|
||||
: '';
|
||||
return `${message} (${code})${details}`;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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<string, string>;
|
||||
properties?: Record<string, any>;
|
||||
limit?: number;
|
||||
after?: string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
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<ContactParams, ContactResponse | ContactResponse[]> = {
|
||||
export const contactsTool: ToolConfig<ContactsParams, ContactsResponse> = {
|
||||
id: 'hubspot.contacts',
|
||||
name: 'HubSpot Contacts',
|
||||
description: 'Manage HubSpot contacts - create, search, and update contact records',
|
||||
@@ -116,22 +112,12 @@ export const contactsTool: ToolConfig<ContactParams, ContactResponse | ContactRe
|
||||
}
|
||||
},
|
||||
|
||||
transformResponse: (data) => {
|
||||
if (Array.isArray(data.results)) {
|
||||
// Search response
|
||||
return data.results.map((contact: { id: string; properties: Record<string, any>; 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
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -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<ChatParams, ChatResponse> = {
|
||||
'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<ChatParams, ChatResponse> = {
|
||||
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,
|
||||
|
||||
@@ -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<string, any>;
|
||||
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<string, any>;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
export const opportunitiesTool: ToolConfig<OpportunityParams, OpportunityResponse | OpportunityResponse[]> = {
|
||||
interface OpportunityResponse extends ToolResponse {
|
||||
totalResults?: number;
|
||||
pagination?: {
|
||||
hasMore: boolean;
|
||||
offset: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const opportunitiesTool: ToolConfig<OpportunityParams, OpportunityResponse> = {
|
||||
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<OpportunityParams, OpportunityRespons
|
||||
type: 'number',
|
||||
description: 'Probability of closing (%)'
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Opportunity description'
|
||||
},
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Opportunity ID (required for updates)'
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
description: 'Additional opportunity fields'
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'SOQL query for searching opportunities'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
default: 100,
|
||||
description: 'Maximum number of records to return'
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
description: 'Offset for pagination'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
description: 'Data for the action'
|
||||
}
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
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<OpportunityParams, OpportunityRespons
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'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<OpportunityParams, OpportunityRespons
|
||||
...(params.amount && { Amount: params.amount }),
|
||||
...(params.closeDate && { CloseDate: params.closeDate }),
|
||||
...(params.probability && { Probability: params.probability }),
|
||||
...(params.description && { Description: params.description }),
|
||||
...params.properties
|
||||
};
|
||||
|
||||
@@ -131,36 +116,15 @@ export const opportunitiesTool: ToolConfig<OpportunityParams, OpportunityRespons
|
||||
}
|
||||
},
|
||||
|
||||
transformResponse: (data) => {
|
||||
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
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
|
||||
export interface ToolConfig<P = any, R = any> {
|
||||
export interface ToolResponse {
|
||||
output: any; // All tools must provide an output field
|
||||
[key: string]: any; // Tools can include additional metadata
|
||||
}
|
||||
|
||||
export interface ToolConfig<P = any, R extends ToolResponse = ToolResponse> {
|
||||
// Basic tool identification
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -24,6 +29,6 @@ export interface ToolConfig<P = any, R = any> {
|
||||
};
|
||||
|
||||
// Response handling
|
||||
transformResponse: (data: any) => R;
|
||||
transformResponse: (response: Response) => Promise<R>;
|
||||
transformError: (error: any) => string;
|
||||
}
|
||||
@@ -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<ChatParams, ChatResponse> = {
|
||||
@@ -69,11 +69,13 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user