- {item.index ? (
-
+
+
+ {item.index ? (
{item.name}
+ ) : (
-
- ) : (
-
+ {hasChildren && (
+
-
{item.name}
-
-
+ {/* Mobile: simple indent */}
+
{children}
+ {/* Desktop: styled with border */}
+
+
)}
-
-
+
)
}
export function SidebarSeparator({ item }: { item: Separator }) {
return (
-
+
{item.name}
)
diff --git a/apps/docs/components/navbar/navbar.tsx b/apps/docs/components/navbar/navbar.tsx
index 59c75baea0..bbddf45e61 100644
--- a/apps/docs/components/navbar/navbar.tsx
+++ b/apps/docs/components/navbar/navbar.tsx
@@ -20,7 +20,7 @@ export function Navbar() {
diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts
index f9b546e686..fb56fb1dbf 100644
--- a/apps/docs/components/ui/icon-mapping.ts
+++ b/apps/docs/components/ui/icon-mapping.ts
@@ -116,114 +116,114 @@ import {
type IconComponent = ComponentType>
export const blockTypeToIconMap: Record = {
- calendly: CalendlyIcon,
- mailchimp: MailchimpIcon,
- postgresql: PostgresIcon,
- twilio_voice: TwilioIcon,
- elasticsearch: ElasticsearchIcon,
- rds: RDSIcon,
- translate: TranslateIcon,
- dynamodb: DynamoDBIcon,
- wordpress: WordpressIcon,
- tavily: TavilyIcon,
- zendesk: ZendeskIcon,
- stagehand_agent: StagehandIcon,
- youtube: YouTubeIcon,
- supabase: SupabaseIcon,
- vision: EyeIcon,
zoom: ZoomIcon,
- confluence: ConfluenceIcon,
- arxiv: ArxivIcon,
- webflow: WebflowIcon,
- pinecone: PineconeIcon,
- apollo: ApolloIcon,
- whatsapp: WhatsAppIcon,
- typeform: TypeformIcon,
- qdrant: QdrantIcon,
- shopify: ShopifyIcon,
- asana: AsanaIcon,
- apify: ApifyIcon,
- memory: BrainIcon,
- gitlab: GitLabIcon,
- polymarket: PolymarketIcon,
- serper: SerperIcon,
- linear: LinearIcon,
- exa: ExaAIIcon,
- telegram: TelegramIcon,
- salesforce: SalesforceIcon,
- hubspot: HubspotIcon,
- hunter: HunterIOIcon,
- linkup: LinkupIcon,
- mongodb: MongoDBIcon,
- pylon: PylonIcon,
- airtable: AirtableIcon,
- discord: DiscordIcon,
- ahrefs: AhrefsIcon,
- neo4j: Neo4jIcon,
- tts: TTSIcon,
- jina: JinaAIIcon,
- google_docs: GoogleDocsIcon,
- perplexity: PerplexityIcon,
- google_search: GoogleIcon,
- x: xIcon,
- kalshi: KalshiIcon,
- google_calendar: GoogleCalendarIcon,
zep: ZepIcon,
- posthog: PosthogIcon,
- grafana: GrafanaIcon,
- google_slides: GoogleSlidesIcon,
- microsoft_planner: MicrosoftPlannerIcon,
- thinking: BrainIcon,
- pipedrive: PipedriveIcon,
- dropbox: DropboxIcon,
- stagehand: StagehandIcon,
- google_forms: GoogleFormsIcon,
- file: DocumentIcon,
- mistral_parse: MistralIcon,
- gmail: GmailIcon,
- openai: OpenAIIcon,
- outlook: OutlookIcon,
- incidentio: IncidentioIcon,
- onedrive: MicrosoftOneDriveIcon,
- resend: ResendIcon,
- google_vault: GoogleVaultIcon,
- sharepoint: MicrosoftSharepointIcon,
- huggingface: HuggingFaceIcon,
- sendgrid: SendgridIcon,
- video_generator: VideoIcon,
- smtp: SmtpIcon,
- google_groups: GoogleGroupsIcon,
- mailgun: MailgunIcon,
- clay: ClayIcon,
- jira: JiraIcon,
- search: SearchIcon,
- linkedin: LinkedInIcon,
- wealthbox: WealthboxIcon,
- notion: NotionIcon,
- elevenlabs: ElevenLabsIcon,
- microsoft_teams: MicrosoftTeamsIcon,
- github: GithubIcon,
- ssh: SshIcon,
- google_drive: GoogleDriveIcon,
- sentry: SentryIcon,
- reddit: RedditIcon,
- parallel_ai: ParallelIcon,
- stripe: StripeIcon,
- s3: S3Icon,
- trello: TrelloIcon,
- mem0: Mem0Icon,
- knowledge: PackageSearchIcon,
- intercom: IntercomIcon,
- twilio_sms: TwilioIcon,
- slack: SlackIcon,
- datadog: DatadogIcon,
- microsoft_excel: MicrosoftExcelIcon,
- image_generator: ImageIcon,
- google_sheets: GoogleSheetsIcon,
+ zendesk: ZendeskIcon,
+ youtube: YouTubeIcon,
+ x: xIcon,
+ wordpress: WordpressIcon,
wikipedia: WikipediaIcon,
- cursor: CursorIcon,
- firecrawl: FirecrawlIcon,
- mysql: MySQLIcon,
- browser_use: BrowserUseIcon,
+ whatsapp: WhatsAppIcon,
+ webflow: WebflowIcon,
+ wealthbox: WealthboxIcon,
+ vision: EyeIcon,
+ video_generator: VideoIcon,
+ typeform: TypeformIcon,
+ twilio_voice: TwilioIcon,
+ twilio_sms: TwilioIcon,
+ tts: TTSIcon,
+ trello: TrelloIcon,
+ translate: TranslateIcon,
+ thinking: BrainIcon,
+ telegram: TelegramIcon,
+ tavily: TavilyIcon,
+ supabase: SupabaseIcon,
stt: STTIcon,
+ stripe: StripeIcon,
+ stagehand_agent: StagehandIcon,
+ stagehand: StagehandIcon,
+ ssh: SshIcon,
+ smtp: SmtpIcon,
+ slack: SlackIcon,
+ shopify: ShopifyIcon,
+ sharepoint: MicrosoftSharepointIcon,
+ serper: SerperIcon,
+ sentry: SentryIcon,
+ sendgrid: SendgridIcon,
+ search: SearchIcon,
+ salesforce: SalesforceIcon,
+ s3: S3Icon,
+ resend: ResendIcon,
+ reddit: RedditIcon,
+ rds: RDSIcon,
+ qdrant: QdrantIcon,
+ pylon: PylonIcon,
+ posthog: PosthogIcon,
+ postgresql: PostgresIcon,
+ polymarket: PolymarketIcon,
+ pipedrive: PipedriveIcon,
+ pinecone: PineconeIcon,
+ perplexity: PerplexityIcon,
+ parallel_ai: ParallelIcon,
+ outlook: OutlookIcon,
+ openai: OpenAIIcon,
+ onedrive: MicrosoftOneDriveIcon,
+ notion: NotionIcon,
+ neo4j: Neo4jIcon,
+ mysql: MySQLIcon,
+ mongodb: MongoDBIcon,
+ mistral_parse: MistralIcon,
+ microsoft_teams: MicrosoftTeamsIcon,
+ microsoft_planner: MicrosoftPlannerIcon,
+ microsoft_excel: MicrosoftExcelIcon,
+ memory: BrainIcon,
+ mem0: Mem0Icon,
+ mailgun: MailgunIcon,
+ mailchimp: MailchimpIcon,
+ linkup: LinkupIcon,
+ linkedin: LinkedInIcon,
+ linear: LinearIcon,
+ knowledge: PackageSearchIcon,
+ kalshi: KalshiIcon,
+ jira: JiraIcon,
+ jina: JinaAIIcon,
+ intercom: IntercomIcon,
+ incidentio: IncidentioIcon,
+ image_generator: ImageIcon,
+ hunter: HunterIOIcon,
+ huggingface: HuggingFaceIcon,
+ hubspot: HubspotIcon,
+ grafana: GrafanaIcon,
+ google_vault: GoogleVaultIcon,
+ google_slides: GoogleSlidesIcon,
+ google_sheets: GoogleSheetsIcon,
+ google_groups: GoogleGroupsIcon,
+ google_forms: GoogleFormsIcon,
+ google_drive: GoogleDriveIcon,
+ google_docs: GoogleDocsIcon,
+ google_calendar: GoogleCalendarIcon,
+ google_search: GoogleIcon,
+ gmail: GmailIcon,
+ gitlab: GitLabIcon,
+ github: GithubIcon,
+ firecrawl: FirecrawlIcon,
+ file: DocumentIcon,
+ exa: ExaAIIcon,
+ elevenlabs: ElevenLabsIcon,
+ elasticsearch: ElasticsearchIcon,
+ dynamodb: DynamoDBIcon,
+ dropbox: DropboxIcon,
+ discord: DiscordIcon,
+ datadog: DatadogIcon,
+ cursor: CursorIcon,
+ confluence: ConfluenceIcon,
+ clay: ClayIcon,
+ calendly: CalendlyIcon,
+ browser_use: BrowserUseIcon,
+ asana: AsanaIcon,
+ arxiv: ArxivIcon,
+ apollo: ApolloIcon,
+ apify: ApifyIcon,
+ airtable: AirtableIcon,
+ ahrefs: AhrefsIcon,
}
diff --git a/apps/docs/content/docs/en/introduction/index.mdx b/apps/docs/content/docs/en/introduction/index.mdx
index 22a9617010..0843c61f12 100644
--- a/apps/docs/content/docs/en/introduction/index.mdx
+++ b/apps/docs/content/docs/en/introduction/index.mdx
@@ -72,7 +72,7 @@ For custom integrations, leverage our [MCP (Model Context Protocol) support](/mc
-## AI-Powered Copilot
+## Copilot
**Ask Questions & Get Guidance**
Copilot answers questions about Sim, explains your workflows, and provides suggestions for improvements. Use the `@` symbol to reference workflows, blocks, documentation, knowledge, and logs for contextual assistance.
diff --git a/apps/docs/content/docs/en/knowledgebase/index.mdx b/apps/docs/content/docs/en/knowledgebase/index.mdx
index 281d0efa3b..1a3874510f 100644
--- a/apps/docs/content/docs/en/knowledgebase/index.mdx
+++ b/apps/docs/content/docs/en/knowledgebase/index.mdx
@@ -1,5 +1,6 @@
---
-title: Knowledgebase
+title: Overview
+description: Upload, process, and search through your documents with intelligent vector search and chunking
---
import { Video } from '@/components/ui/video'
diff --git a/apps/docs/content/docs/en/knowledgebase/meta.json b/apps/docs/content/docs/en/knowledgebase/meta.json
new file mode 100644
index 0000000000..7b6b62f779
--- /dev/null
+++ b/apps/docs/content/docs/en/knowledgebase/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Knowledgebase",
+ "pages": ["index", "tags"]
+}
diff --git a/apps/docs/content/docs/en/tools/cursor.mdx b/apps/docs/content/docs/en/tools/cursor.mdx
index f7463ae4fb..77bab828b0 100644
--- a/apps/docs/content/docs/en/tools/cursor.mdx
+++ b/apps/docs/content/docs/en/tools/cursor.mdx
@@ -10,6 +10,29 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#1E1E1E"
/>
+{/* MANUAL-CONTENT-START:intro */}
+[Cursor](https://www.cursor.so/) is an AI IDE and cloud-based platform that lets you launch and manage powerful AI agents able to work directly on your GitHub repositories. Cursor agents can automate development tasks, enhance your team's productivity, and collaborate with you by making code changes, responding to natural language instructions, and maintaining conversation history about their activities.
+
+With Cursor, you can:
+
+- **Launch cloud agents for codebases**: Instantly create new AI agents that work on your repositories in the cloud
+- **Delegate coding tasks using natural language**: Guide agents with written instructions, amendments, and clarifications
+- **Monitor progress and outputs**: Retrieve agent status, view detailed results, and inspect current or completed tasks
+- **Access full conversation history**: Review all prompts and AI responses for transparency and auditability
+- **Control and manage agent lifecycle**: List active agents, terminate agents, and manage API-based agent launches and follow-ups
+
+In Sim, the Cursor integration enables your agents and workflows to interact programmatically with Cursor cloud agents. This means you can use Sim to:
+
+- List all cloud agents and browse their current state (`cursor_list_agents`)
+- Retrieve up-to-date status and outputs for any agent (`cursor_get_agent`)
+- View the full conversation history for any coding agent (`cursor_get_conversation`)
+- Add follow-up instructions or new prompts to a running agent
+- Manage and terminate agents as needed
+
+This integration helps you combine the flexible intelligence of Sim agents with the powerful development automation capabilities of Cursor, making it possible to scale AI-driven development across your projects.
+{/* MANUAL-CONTENT-END */}
+
+
## Usage Instructions
Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.
@@ -109,7 +132,7 @@ Add a follow-up instruction to an existing cloud agent.
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
-| `promptText` | string | Yes | The follow-up instruction text for the agent |
+| `followupPromptText` | string | Yes | The follow-up instruction text for the agent |
| `promptImages` | string | No | JSON array of image objects with base64 data and dimensions \(max 5\) |
#### Output
diff --git a/apps/docs/content/docs/en/tools/salesforce.mdx b/apps/docs/content/docs/en/tools/salesforce.mdx
index c41c27ba4b..870c6161d2 100644
--- a/apps/docs/content/docs/en/tools/salesforce.mdx
+++ b/apps/docs/content/docs/en/tools/salesforce.mdx
@@ -138,381 +138,609 @@ Delete an account from Salesforce CRM
### `salesforce_get_contacts`
+Get contact(s) from Salesforce - single contact if ID provided, or list if not
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | No | Contact ID \(if provided, returns single contact\) |
+| `limit` | string | No | Number of results \(default: 100, max: 2000\). Only for list query. |
+| `fields` | string | No | Comma-separated fields \(e.g., "Id,FirstName,LastName,Email,Phone"\) |
+| `orderBy` | string | No | Order by field \(e.g., "LastName ASC"\). Only for list query. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Contact\(s\) data |
### `salesforce_create_contact`
+Create a new contact in Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `lastName` | string | Yes | Last name \(required\) |
+| `firstName` | string | No | First name |
+| `email` | string | No | Email address |
+| `phone` | string | No | Phone number |
+| `accountId` | string | No | Account ID to associate contact with |
+| `title` | string | No | No description |
+| `department` | string | No | Department |
+| `mailingStreet` | string | No | Mailing street |
+| `mailingCity` | string | No | Mailing city |
+| `mailingState` | string | No | Mailing state |
+| `mailingPostalCode` | string | No | Mailing postal code |
+| `mailingCountry` | string | No | Mailing country |
+| `description` | string | No | Contact description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Created contact data |
### `salesforce_update_contact`
+Update an existing contact in Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | Yes | Contact ID to update \(required\) |
+| `lastName` | string | No | Last name |
+| `firstName` | string | No | First name |
+| `email` | string | No | Email address |
+| `phone` | string | No | Phone number |
+| `accountId` | string | No | Account ID to associate with |
+| `title` | string | No | No description |
+| `department` | string | No | Department |
+| `mailingStreet` | string | No | Mailing street |
+| `mailingCity` | string | No | Mailing city |
+| `mailingState` | string | No | Mailing state |
+| `mailingPostalCode` | string | No | Mailing postal code |
+| `mailingCountry` | string | No | Mailing country |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Updated contact data |
### `salesforce_delete_contact`
+Delete a contact from Salesforce CRM
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `contactId` | string | Yes | Contact ID to delete \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `output` | object | Deleted contact data |
### `salesforce_get_leads`
+Get lead(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | No | Lead ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Lead data |
### `salesforce_create_lead`
+Create a new lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `lastName` | string | Yes | Last name \(required\) |
+| `company` | string | Yes | Company \(required\) |
+| `firstName` | string | No | First name |
+| `email` | string | No | No description |
+| `phone` | string | No | No description |
+| `status` | string | No | Lead status |
+| `leadSource` | string | No | Lead source |
+| `title` | string | No | No description |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created lead |
### `salesforce_update_lead`
+Update an existing lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | Yes | Lead ID \(required\) |
+| `lastName` | string | No | Last name |
+| `company` | string | No | No description |
+| `firstName` | string | No | First name |
+| `email` | string | No | No description |
+| `phone` | string | No | No description |
+| `status` | string | No | Lead status |
+| `leadSource` | string | No | Lead source |
+| `title` | string | No | No description |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated lead |
### `salesforce_delete_lead`
+Delete a lead
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `leadId` | string | Yes | Lead ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted lead |
### `salesforce_get_opportunities`
+Get opportunity(ies) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | No | Opportunity ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Opportunity data |
### `salesforce_create_opportunity`
+Create a new opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `name` | string | Yes | Opportunity name \(required\) |
+| `stageName` | string | Yes | Stage name \(required\) |
+| `closeDate` | string | Yes | Close date YYYY-MM-DD \(required\) |
+| `accountId` | string | No | Account ID |
+| `amount` | string | No | Amount \(number\) |
+| `probability` | string | No | Probability \(0-100\) |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created opportunity |
### `salesforce_update_opportunity`
+Update an existing opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | Yes | Opportunity ID \(required\) |
+| `name` | string | No | Opportunity name |
+| `stageName` | string | No | Stage name |
+| `closeDate` | string | No | Close date YYYY-MM-DD |
+| `accountId` | string | No | Account ID |
+| `amount` | string | No | No description |
+| `probability` | string | No | Probability \(0-100\) |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated opportunity |
### `salesforce_delete_opportunity`
+Delete an opportunity
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `opportunityId` | string | Yes | Opportunity ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted opportunity |
### `salesforce_get_cases`
+Get case(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | No | Case ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Case data |
### `salesforce_create_case`
+Create a new case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `subject` | string | Yes | Case subject \(required\) |
+| `status` | string | No | Status \(e.g., New, Working, Escalated\) |
+| `priority` | string | No | Priority \(e.g., Low, Medium, High\) |
+| `origin` | string | No | Origin \(e.g., Phone, Email, Web\) |
+| `contactId` | string | No | Contact ID |
+| `accountId` | string | No | Account ID |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created case |
### `salesforce_update_case`
+Update an existing case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | Yes | Case ID \(required\) |
+| `subject` | string | No | Case subject |
+| `status` | string | No | Status |
+| `priority` | string | No | Priority |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated case |
### `salesforce_delete_case`
+Delete a case
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `caseId` | string | Yes | Case ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted case |
### `salesforce_get_tasks`
+Get task(s) from Salesforce
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | No | Task ID \(optional\) |
+| `limit` | string | No | Max results \(default: 100\) |
+| `fields` | string | No | Comma-separated fields |
+| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Task data |
### `salesforce_create_task`
+Create a new task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `subject` | string | Yes | Task subject \(required\) |
+| `status` | string | No | Status \(e.g., Not Started, In Progress, Completed\) |
+| `priority` | string | No | Priority \(e.g., Low, Normal, High\) |
+| `activityDate` | string | No | Due date YYYY-MM-DD |
+| `whoId` | string | No | Related Contact/Lead ID |
+| `whatId` | string | No | Related Account/Opportunity ID |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Created task |
### `salesforce_update_task`
+Update an existing task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | Yes | Task ID \(required\) |
+| `subject` | string | No | Task subject |
+| `status` | string | No | Status |
+| `priority` | string | No | Priority |
+| `activityDate` | string | No | Due date YYYY-MM-DD |
+| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Updated task |
### `salesforce_delete_task`
+Delete a task
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `taskId` | string | Yes | Task ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success |
+| `output` | object | Deleted task |
### `salesforce_list_reports`
+Get a list of reports accessible by the current user
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `folderName` | string | No | Filter by folder name |
+| `searchTerm` | string | No | Search term to filter reports by name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Reports data |
### `salesforce_get_report`
+Get metadata and describe information for a specific report
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `reportId` | string | Yes | Report ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Report metadata |
### `salesforce_run_report`
+Execute a report and retrieve the results
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `reportId` | string | Yes | Report ID \(required\) |
+| `includeDetails` | string | No | Include detail rows \(true/false, default: true\) |
+| `filters` | string | No | JSON string of report filters to apply |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Report results |
### `salesforce_list_report_types`
+Get a list of available report types
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Report types data |
### `salesforce_list_dashboards`
+Get a list of dashboards accessible by the current user
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `folderName` | string | No | Filter by folder name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Dashboards data |
### `salesforce_get_dashboard`
+Get details and results for a specific dashboard
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `dashboardId` | string | Yes | Dashboard ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Dashboard data |
### `salesforce_refresh_dashboard`
+Refresh a dashboard to get the latest data
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `dashboardId` | string | Yes | Dashboard ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Refreshed dashboard data |
### `salesforce_query`
@@ -535,45 +763,59 @@ Execute a custom SOQL query to retrieve data from Salesforce
### `salesforce_query_more`
+Retrieve additional query results using the nextRecordsUrl from a previous query
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `nextRecordsUrl` | string | Yes | The nextRecordsUrl from a previous query response |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Query results |
### `salesforce_describe_object`
+Get metadata and field information for a Salesforce object
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
+| `objectName` | string | Yes | API name of the object \(e.g., Account, Contact, Lead, Custom_Object__c\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Object metadata |
### `salesforce_list_objects`
+Get a list of all available Salesforce objects
+
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
+| `idToken` | string | No | No description |
+| `instanceUrl` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
-| `success` | boolean | Operation success status |
-| `output` | json | Operation result data |
+| `success` | boolean | Success status |
+| `output` | object | Objects list |
diff --git a/apps/sim/tools/salesforce/cases.ts b/apps/sim/tools/salesforce/cases.ts
deleted file mode 100644
index a3f27437ef..0000000000
--- a/apps/sim/tools/salesforce/cases.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-
-const logger = createLogger('SalesforceCases')
-
-function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
- if (instanceUrl) return instanceUrl
- if (idToken) {
- try {
- const base64Url = idToken.split('.')[1]
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
- const jsonPayload = decodeURIComponent(
- atob(base64)
- .split('')
- .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
- .join('')
- )
- const decoded = JSON.parse(jsonPayload)
- if (decoded.profile) {
- const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
- if (match) return match[1]
- } else if (decoded.sub) {
- const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
- if (match && match[1] !== 'https://login.salesforce.com') return match[1]
- }
- } catch (error) {
- logger.error('Failed to decode Salesforce idToken', { error })
- }
- }
- throw new Error('Salesforce instance URL is required but not provided')
-}
-
-// Get Cases
-export const salesforceGetCasesTool: ToolConfig = {
- id: 'salesforce_get_cases',
- name: 'Get Cases from Salesforce',
- description: 'Get case(s) from Salesforce',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- caseId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Case ID (optional)',
- },
- limit: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Max results (default: 100)',
- },
- fields: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Comma-separated fields',
- },
- orderBy: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Order by field',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- if (params.caseId) {
- const fields =
- params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
- return `${instanceUrl}/services/data/v59.0/sobjects/Case/${params.caseId}?fields=${fields}`
- }
- const limit = params.limit ? Number.parseInt(params.limit) : 100
- const fields =
- params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
- const orderBy = params.orderBy || 'CreatedDate DESC'
- const query = `SELECT ${fields} FROM Case ORDER BY ${orderBy} LIMIT ${limit}`
- return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch cases')
- if (params.caseId) {
- return {
- success: true,
- output: { case: data, metadata: { operation: 'get_cases' }, success: true },
- }
- }
- const cases = data.records || []
- return {
- success: true,
- output: {
- cases,
- paging: {
- nextRecordsUrl: data.nextRecordsUrl,
- totalSize: data.totalSize || cases.length,
- done: data.done !== false,
- },
- metadata: { operation: 'get_cases', totalReturned: cases.length, hasMore: !data.done },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Case data' },
- },
-}
-
-// Create Case
-export const salesforceCreateCaseTool: ToolConfig = {
- id: 'salesforce_create_case',
- name: 'Create Case in Salesforce',
- description: 'Create a new case',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- subject: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Case subject (required)',
- },
- status: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Status (e.g., New, Working, Escalated)',
- },
- priority: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Priority (e.g., Low, Medium, High)',
- },
- origin: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Origin (e.g., Phone, Email, Web)',
- },
- contactId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Contact ID',
- },
- accountId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Account ID',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case`,
- method: 'POST',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = { Subject: params.subject }
- if (params.status) body.Status = params.status
- if (params.priority) body.Priority = params.priority
- if (params.origin) body.Origin = params.origin
- if (params.contactId) body.ContactId = params.contactId
- if (params.accountId) body.AccountId = params.accountId
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create case')
- return {
- success: true,
- output: {
- id: data.id,
- success: data.success,
- created: true,
- metadata: { operation: 'create_case' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Created case' },
- },
-}
-
-// Update Case
-export const salesforceUpdateCaseTool: ToolConfig = {
- id: 'salesforce_update_case',
- name: 'Update Case in Salesforce',
- description: 'Update an existing case',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- caseId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Case ID (required)',
- },
- subject: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Case subject',
- },
- status: { type: 'string', required: false, visibility: 'user-only', description: 'Status' },
- priority: { type: 'string', required: false, visibility: 'user-only', description: 'Priority' },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
- method: 'PATCH',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {}
- if (params.subject) body.Subject = params.subject
- if (params.status) body.Status = params.status
- if (params.priority) body.Priority = params.priority
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json()
- throw new Error(data[0]?.message || data.message || 'Failed to update case')
- }
- return {
- success: true,
- output: { id: params.caseId, updated: true, metadata: { operation: 'update_case' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Updated case' },
- },
-}
-
-// Delete Case
-export const salesforceDeleteCaseTool: ToolConfig = {
- id: 'salesforce_delete_case',
- name: 'Delete Case from Salesforce',
- description: 'Delete a case',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- caseId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Case ID (required)',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
- method: 'DELETE',
- headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json().catch(() => ({}))
- throw new Error(data[0]?.message || data.message || 'Failed to delete case')
- }
- return {
- success: true,
- output: { id: params.caseId, deleted: true, metadata: { operation: 'delete_case' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Deleted case' },
- },
-}
diff --git a/apps/sim/tools/salesforce/contacts.ts b/apps/sim/tools/salesforce/contacts.ts
deleted file mode 100644
index fa27075419..0000000000
--- a/apps/sim/tools/salesforce/contacts.ts
+++ /dev/null
@@ -1,658 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-
-const logger = createLogger('SalesforceContacts')
-
-// Helper to extract instance URL from idToken
-function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
- if (instanceUrl) return instanceUrl
-
- if (idToken) {
- try {
- const base64Url = idToken.split('.')[1]
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
- const jsonPayload = decodeURIComponent(
- atob(base64)
- .split('')
- .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
- .join('')
- )
- const decoded = JSON.parse(jsonPayload)
-
- if (decoded.profile) {
- const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
- if (match) return match[1]
- } else if (decoded.sub) {
- const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
- if (match && match[1] !== 'https://login.salesforce.com') {
- return match[1]
- }
- }
- } catch (error) {
- logger.error('Failed to decode Salesforce idToken', { error })
- }
- }
-
- throw new Error('Salesforce instance URL is required but not provided')
-}
-
-// Get Contacts (with optional contactId)
-export interface SalesforceGetContactsParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- contactId?: string
- limit?: string
- fields?: string
- orderBy?: string
-}
-
-export interface SalesforceGetContactsResponse {
- success: boolean
- output: {
- contacts?: any[]
- contact?: any
- paging?: {
- nextRecordsUrl?: string
- totalSize: number
- done: boolean
- }
- metadata: {
- operation: 'get_contacts'
- totalReturned?: number
- hasMore?: boolean
- singleContact?: boolean
- }
- success: boolean
- }
-}
-
-export const salesforceGetContactsTool: ToolConfig<
- SalesforceGetContactsParams,
- SalesforceGetContactsResponse
-> = {
- id: 'salesforce_get_contacts',
- name: 'Get Contacts from Salesforce',
- description: 'Get contact(s) from Salesforce - single contact if ID provided, or list if not',
- version: '1.0.0',
-
- oauth: {
- required: true,
- provider: 'salesforce',
- },
-
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- contactId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Contact ID (if provided, returns single contact)',
- },
- limit: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Number of results (default: 100, max: 2000). Only for list query.',
- },
- fields: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Comma-separated fields (e.g., "Id,FirstName,LastName,Email,Phone")',
- },
- orderBy: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Order by field (e.g., "LastName ASC"). Only for list query.',
- },
- },
-
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
-
- // Single contact by ID
- if (params.contactId) {
- const fields =
- params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
- return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}?fields=${fields}`
- }
-
- // List contacts with SOQL query
- const limit = params.limit ? Number.parseInt(params.limit) : 100
- const fields = params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
- const orderBy = params.orderBy || 'LastName ASC'
- const query = `SELECT ${fields} FROM Contact ORDER BY ${orderBy} LIMIT ${limit}`
- const encodedQuery = encodeURIComponent(query)
-
- return `${instanceUrl}/services/data/v59.0/query?q=${encodedQuery}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
-
- transformResponse: async (response: Response, params) => {
- const data = await response.json()
-
- if (!response.ok) {
- logger.error('Salesforce API request failed', { data, status: response.status })
- throw new Error(
- data[0]?.message || data.message || 'Failed to fetch contacts from Salesforce'
- )
- }
-
- // Single contact response
- if (params?.contactId) {
- return {
- success: true,
- output: {
- contact: data,
- metadata: {
- operation: 'get_contacts' as const,
- singleContact: true,
- },
- success: true,
- },
- }
- }
-
- // List contacts response
- const contacts = data.records || []
- return {
- success: true,
- output: {
- contacts,
- paging: {
- nextRecordsUrl: data.nextRecordsUrl,
- totalSize: data.totalSize || contacts.length,
- done: data.done !== false,
- },
- metadata: {
- operation: 'get_contacts' as const,
- totalReturned: contacts.length,
- hasMore: !data.done,
- singleContact: false,
- },
- success: true,
- },
- }
- },
-
- outputs: {
- success: { type: 'boolean', description: 'Operation success status' },
- output: {
- type: 'object',
- description: 'Contact(s) data',
- properties: {
- contacts: { type: 'array', description: 'Array of contacts (list query)' },
- contact: { type: 'object', description: 'Single contact (by ID)' },
- paging: { type: 'object', description: 'Pagination info (list query)' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success' },
- },
- },
- },
-}
-
-// Create Contact
-export interface SalesforceCreateContactParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- lastName: string
- firstName?: string
- email?: string
- phone?: string
- accountId?: string
- title?: string
- department?: string
- mailingStreet?: string
- mailingCity?: string
- mailingState?: string
- mailingPostalCode?: string
- mailingCountry?: string
- description?: string
-}
-
-export interface SalesforceCreateContactResponse {
- success: boolean
- output: {
- id: string
- success: boolean
- created: boolean
- metadata: { operation: 'create_contact' }
- }
-}
-
-export const salesforceCreateContactTool: ToolConfig<
- SalesforceCreateContactParams,
- SalesforceCreateContactResponse
-> = {
- id: 'salesforce_create_contact',
- name: 'Create Contact in Salesforce',
- description: 'Create a new contact in Salesforce CRM',
- version: '1.0.0',
-
- oauth: { required: true, provider: 'salesforce' },
-
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- lastName: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Last name (required)',
- },
- firstName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'First name',
- },
- email: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Email address',
- },
- phone: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Phone number',
- },
- accountId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Account ID to associate contact with',
- },
- title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
- department: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Department',
- },
- mailingStreet: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing street',
- },
- mailingCity: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing city',
- },
- mailingState: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing state',
- },
- mailingPostalCode: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing postal code',
- },
- mailingCountry: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing country',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Contact description',
- },
- },
-
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/sobjects/Contact`
- },
- method: 'POST',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = { LastName: params.lastName }
-
- if (params.firstName) body.FirstName = params.firstName
- if (params.email) body.Email = params.email
- if (params.phone) body.Phone = params.phone
- if (params.accountId) body.AccountId = params.accountId
- if (params.title) body.Title = params.title
- if (params.department) body.Department = params.department
- if (params.mailingStreet) body.MailingStreet = params.mailingStreet
- if (params.mailingCity) body.MailingCity = params.mailingCity
- if (params.mailingState) body.MailingState = params.mailingState
- if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
- if (params.mailingCountry) body.MailingCountry = params.mailingCountry
- if (params.description) body.Description = params.description
-
- return body
- },
- },
-
- transformResponse: async (response: Response) => {
- const data = await response.json()
-
- if (!response.ok) {
- logger.error('Salesforce API request failed', { data, status: response.status })
- throw new Error(data[0]?.message || data.message || 'Failed to create contact in Salesforce')
- }
-
- return {
- success: true,
- output: {
- id: data.id,
- success: data.success,
- created: true,
- metadata: { operation: 'create_contact' as const },
- },
- }
- },
-
- outputs: {
- success: { type: 'boolean', description: 'Operation success status' },
- output: {
- type: 'object',
- description: 'Created contact data',
- properties: {
- id: { type: 'string', description: 'Created contact ID' },
- success: { type: 'boolean', description: 'Salesforce operation success' },
- created: { type: 'boolean', description: 'Whether contact was created' },
- metadata: { type: 'object', description: 'Operation metadata' },
- },
- },
- },
-}
-
-// Update Contact
-export interface SalesforceUpdateContactParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- contactId: string
- lastName?: string
- firstName?: string
- email?: string
- phone?: string
- accountId?: string
- title?: string
- department?: string
- mailingStreet?: string
- mailingCity?: string
- mailingState?: string
- mailingPostalCode?: string
- mailingCountry?: string
- description?: string
-}
-
-export interface SalesforceUpdateContactResponse {
- success: boolean
- output: {
- id: string
- updated: boolean
- metadata: { operation: 'update_contact' }
- }
-}
-
-export const salesforceUpdateContactTool: ToolConfig<
- SalesforceUpdateContactParams,
- SalesforceUpdateContactResponse
-> = {
- id: 'salesforce_update_contact',
- name: 'Update Contact in Salesforce',
- description: 'Update an existing contact in Salesforce CRM',
- version: '1.0.0',
-
- oauth: { required: true, provider: 'salesforce' },
-
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- contactId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Contact ID to update (required)',
- },
- lastName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Last name',
- },
- firstName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'First name',
- },
- email: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Email address',
- },
- phone: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Phone number',
- },
- accountId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Account ID to associate with',
- },
- title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
- department: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Department',
- },
- mailingStreet: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing street',
- },
- mailingCity: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing city',
- },
- mailingState: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing state',
- },
- mailingPostalCode: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing postal code',
- },
- mailingCountry: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Mailing country',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
-
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
- },
- method: 'PATCH',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {}
-
- if (params.lastName) body.LastName = params.lastName
- if (params.firstName) body.FirstName = params.firstName
- if (params.email) body.Email = params.email
- if (params.phone) body.Phone = params.phone
- if (params.accountId) body.AccountId = params.accountId
- if (params.title) body.Title = params.title
- if (params.department) body.Department = params.department
- if (params.mailingStreet) body.MailingStreet = params.mailingStreet
- if (params.mailingCity) body.MailingCity = params.mailingCity
- if (params.mailingState) body.MailingState = params.mailingState
- if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
- if (params.mailingCountry) body.MailingCountry = params.mailingCountry
- if (params.description) body.Description = params.description
-
- return body
- },
- },
-
- transformResponse: async (response: Response, params) => {
- if (!response.ok) {
- const data = await response.json()
- logger.error('Salesforce API request failed', { data, status: response.status })
- throw new Error(data[0]?.message || data.message || 'Failed to update contact in Salesforce')
- }
-
- return {
- success: true,
- output: {
- id: params?.contactId || '',
- updated: true,
- metadata: { operation: 'update_contact' as const },
- },
- }
- },
-
- outputs: {
- success: { type: 'boolean', description: 'Operation success status' },
- output: {
- type: 'object',
- description: 'Updated contact data',
- properties: {
- id: { type: 'string', description: 'Updated contact ID' },
- updated: { type: 'boolean', description: 'Whether contact was updated' },
- metadata: { type: 'object', description: 'Operation metadata' },
- },
- },
- },
-}
-
-// Delete Contact
-export interface SalesforceDeleteContactParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- contactId: string
-}
-
-export interface SalesforceDeleteContactResponse {
- success: boolean
- output: {
- id: string
- deleted: boolean
- metadata: { operation: 'delete_contact' }
- }
-}
-
-export const salesforceDeleteContactTool: ToolConfig<
- SalesforceDeleteContactParams,
- SalesforceDeleteContactResponse
-> = {
- id: 'salesforce_delete_contact',
- name: 'Delete Contact from Salesforce',
- description: 'Delete a contact from Salesforce CRM',
- version: '1.0.0',
-
- oauth: { required: true, provider: 'salesforce' },
-
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- contactId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Contact ID to delete (required)',
- },
- },
-
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
- },
- method: 'DELETE',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- }),
- },
-
- transformResponse: async (response: Response, params) => {
- if (!response.ok) {
- const data = await response.json().catch(() => ({}))
- logger.error('Salesforce API request failed', { data, status: response.status })
- throw new Error(
- data[0]?.message || data.message || 'Failed to delete contact from Salesforce'
- )
- }
-
- return {
- success: true,
- output: {
- id: params?.contactId || '',
- deleted: true,
- metadata: { operation: 'delete_contact' as const },
- },
- }
- },
-
- outputs: {
- success: { type: 'boolean', description: 'Operation success status' },
- output: {
- type: 'object',
- description: 'Deleted contact data',
- properties: {
- id: { type: 'string', description: 'Deleted contact ID' },
- deleted: { type: 'boolean', description: 'Whether contact was deleted' },
- metadata: { type: 'object', description: 'Operation metadata' },
- },
- },
- },
-}
diff --git a/apps/sim/tools/salesforce/create_case.ts b/apps/sim/tools/salesforce/create_case.ts
new file mode 100644
index 0000000000..251ba91c23
--- /dev/null
+++ b/apps/sim/tools/salesforce/create_case.ts
@@ -0,0 +1,141 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceCreateCaseParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ subject: string
+ status?: string
+ priority?: string
+ origin?: string
+ contactId?: string
+ accountId?: string
+ description?: string
+}
+
+export interface SalesforceCreateCaseResponse {
+ success: boolean
+ output: {
+ id: string
+ success: boolean
+ created: boolean
+ metadata: {
+ operation: 'create_case'
+ }
+ }
+}
+
+export const salesforceCreateCaseTool: ToolConfig<
+ SalesforceCreateCaseParams,
+ SalesforceCreateCaseResponse
+> = {
+ id: 'salesforce_create_case',
+ name: 'Create Case in Salesforce',
+ description: 'Create a new case',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ subject: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Case subject (required)',
+ },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Status (e.g., New, Working, Escalated)',
+ },
+ priority: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Priority (e.g., Low, Medium, High)',
+ },
+ origin: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Origin (e.g., Phone, Email, Web)',
+ },
+ contactId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Contact ID',
+ },
+ accountId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Account ID',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case`,
+ method: 'POST',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = { Subject: params.subject }
+ if (params.status) body.Status = params.status
+ if (params.priority) body.Priority = params.priority
+ if (params.origin) body.Origin = params.origin
+ if (params.contactId) body.ContactId = params.contactId
+ if (params.accountId) body.AccountId = params.accountId
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create case')
+ return {
+ success: true,
+ output: {
+ id: data.id,
+ success: data.success,
+ created: true,
+ metadata: { operation: 'create_case' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Created case' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/create_contact.ts b/apps/sim/tools/salesforce/create_contact.ts
new file mode 100644
index 0000000000..72a62d9292
--- /dev/null
+++ b/apps/sim/tools/salesforce/create_contact.ts
@@ -0,0 +1,188 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceContacts')
+
+export interface SalesforceCreateContactParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ lastName: string
+ firstName?: string
+ email?: string
+ phone?: string
+ accountId?: string
+ title?: string
+ department?: string
+ mailingStreet?: string
+ mailingCity?: string
+ mailingState?: string
+ mailingPostalCode?: string
+ mailingCountry?: string
+ description?: string
+}
+
+export interface SalesforceCreateContactResponse {
+ success: boolean
+ output: {
+ id: string
+ success: boolean
+ created: boolean
+ metadata: { operation: 'create_contact' }
+ }
+}
+
+export const salesforceCreateContactTool: ToolConfig<
+ SalesforceCreateContactParams,
+ SalesforceCreateContactResponse
+> = {
+ id: 'salesforce_create_contact',
+ name: 'Create Contact in Salesforce',
+ description: 'Create a new contact in Salesforce CRM',
+ version: '1.0.0',
+
+ oauth: { required: true, provider: 'salesforce' },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ lastName: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Last name (required)',
+ },
+ firstName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'First name',
+ },
+ email: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Email address',
+ },
+ phone: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Phone number',
+ },
+ accountId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Account ID to associate contact with',
+ },
+ title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
+ department: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Department',
+ },
+ mailingStreet: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing street',
+ },
+ mailingCity: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing city',
+ },
+ mailingState: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing state',
+ },
+ mailingPostalCode: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing postal code',
+ },
+ mailingCountry: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing country',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Contact description',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/sobjects/Contact`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = { LastName: params.lastName }
+
+ if (params.firstName) body.FirstName = params.firstName
+ if (params.email) body.Email = params.email
+ if (params.phone) body.Phone = params.phone
+ if (params.accountId) body.AccountId = params.accountId
+ if (params.title) body.Title = params.title
+ if (params.department) body.Department = params.department
+ if (params.mailingStreet) body.MailingStreet = params.mailingStreet
+ if (params.mailingCity) body.MailingCity = params.mailingCity
+ if (params.mailingState) body.MailingState = params.mailingState
+ if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
+ if (params.mailingCountry) body.MailingCountry = params.mailingCountry
+ if (params.description) body.Description = params.description
+
+ return body
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ const data = await response.json()
+
+ if (!response.ok) {
+ logger.error('Salesforce API request failed', { data, status: response.status })
+ throw new Error(data[0]?.message || data.message || 'Failed to create contact in Salesforce')
+ }
+
+ return {
+ success: true,
+ output: {
+ id: data.id,
+ success: data.success,
+ created: true,
+ metadata: { operation: 'create_contact' as const },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Operation success status' },
+ output: {
+ type: 'object',
+ description: 'Created contact data',
+ properties: {
+ id: { type: 'string', description: 'Created contact ID' },
+ success: { type: 'boolean', description: 'Salesforce operation success' },
+ created: { type: 'boolean', description: 'Whether contact was created' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/create_lead.ts b/apps/sim/tools/salesforce/create_lead.ts
new file mode 100644
index 0000000000..1ffd1b5a43
--- /dev/null
+++ b/apps/sim/tools/salesforce/create_lead.ts
@@ -0,0 +1,129 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceCreateLeadParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ lastName: string
+ company: string
+ firstName?: string
+ email?: string
+ phone?: string
+ status?: string
+ leadSource?: string
+ title?: string
+ description?: string
+}
+
+export interface SalesforceCreateLeadResponse {
+ success: boolean
+ output: {
+ id: string
+ success: boolean
+ created: boolean
+ metadata: {
+ operation: 'create_lead'
+ }
+ }
+}
+
+export const salesforceCreateLeadTool: ToolConfig<
+ SalesforceCreateLeadParams,
+ SalesforceCreateLeadResponse
+> = {
+ id: 'salesforce_create_lead',
+ name: 'Create Lead in Salesforce',
+ description: 'Create a new lead',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ lastName: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Last name (required)',
+ },
+ company: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Company (required)',
+ },
+ firstName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'First name',
+ },
+ email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
+ phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Lead status',
+ },
+ leadSource: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Lead source',
+ },
+ title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead`,
+ method: 'POST',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = { LastName: params.lastName, Company: params.company }
+ if (params.firstName) body.FirstName = params.firstName
+ if (params.email) body.Email = params.email
+ if (params.phone) body.Phone = params.phone
+ if (params.status) body.Status = params.status
+ if (params.leadSource) body.LeadSource = params.leadSource
+ if (params.title) body.Title = params.title
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create lead')
+ return {
+ success: true,
+ output: {
+ id: data.id,
+ success: data.success,
+ created: true,
+ metadata: { operation: 'create_lead' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Created lead' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/create_opportunity.ts b/apps/sim/tools/salesforce/create_opportunity.ts
new file mode 100644
index 0000000000..aebc55c7f8
--- /dev/null
+++ b/apps/sim/tools/salesforce/create_opportunity.ts
@@ -0,0 +1,132 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceCreateOpportunityParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ name: string
+ stageName: string
+ closeDate: string
+ accountId?: string
+ amount?: string
+ probability?: string
+ description?: string
+}
+
+export interface SalesforceCreateOpportunityResponse {
+ success: boolean
+ output: {
+ id: string
+ success: boolean
+ created: boolean
+ metadata: {
+ operation: 'create_opportunity'
+ }
+ }
+}
+
+export const salesforceCreateOpportunityTool: ToolConfig<
+ SalesforceCreateOpportunityParams,
+ SalesforceCreateOpportunityResponse
+> = {
+ id: 'salesforce_create_opportunity',
+ name: 'Create Opportunity in Salesforce',
+ description: 'Create a new opportunity',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ name: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Opportunity name (required)',
+ },
+ stageName: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Stage name (required)',
+ },
+ closeDate: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Close date YYYY-MM-DD (required)',
+ },
+ accountId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Account ID',
+ },
+ amount: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Amount (number)',
+ },
+ probability: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Probability (0-100)',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity`,
+ method: 'POST',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {
+ Name: params.name,
+ StageName: params.stageName,
+ CloseDate: params.closeDate,
+ }
+ if (params.accountId) body.AccountId = params.accountId
+ if (params.amount) body.Amount = Number.parseFloat(params.amount)
+ if (params.probability) body.Probability = Number.parseInt(params.probability)
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok)
+ throw new Error(data[0]?.message || data.message || 'Failed to create opportunity')
+ return {
+ success: true,
+ output: {
+ id: data.id,
+ success: data.success,
+ created: true,
+ metadata: { operation: 'create_opportunity' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Created opportunity' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/create_task.ts b/apps/sim/tools/salesforce/create_task.ts
new file mode 100644
index 0000000000..b00ff719f3
--- /dev/null
+++ b/apps/sim/tools/salesforce/create_task.ts
@@ -0,0 +1,141 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceCreateTaskParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ subject: string
+ status?: string
+ priority?: string
+ activityDate?: string
+ whoId?: string
+ whatId?: string
+ description?: string
+}
+
+export interface SalesforceCreateTaskResponse {
+ success: boolean
+ output: {
+ id: string
+ success: boolean
+ created: boolean
+ metadata: {
+ operation: 'create_task'
+ }
+ }
+}
+
+export const salesforceCreateTaskTool: ToolConfig<
+ SalesforceCreateTaskParams,
+ SalesforceCreateTaskResponse
+> = {
+ id: 'salesforce_create_task',
+ name: 'Create Task in Salesforce',
+ description: 'Create a new task',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ subject: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Task subject (required)',
+ },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Status (e.g., Not Started, In Progress, Completed)',
+ },
+ priority: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Priority (e.g., Low, Normal, High)',
+ },
+ activityDate: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Due date YYYY-MM-DD',
+ },
+ whoId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Related Contact/Lead ID',
+ },
+ whatId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Related Account/Opportunity ID',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task`,
+ method: 'POST',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = { Subject: params.subject }
+ if (params.status) body.Status = params.status
+ if (params.priority) body.Priority = params.priority
+ if (params.activityDate) body.ActivityDate = params.activityDate
+ if (params.whoId) body.WhoId = params.whoId
+ if (params.whatId) body.WhatId = params.whatId
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create task')
+ return {
+ success: true,
+ output: {
+ id: data.id,
+ success: data.success,
+ created: true,
+ metadata: { operation: 'create_task' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Created task' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/dashboards.ts b/apps/sim/tools/salesforce/dashboards.ts
deleted file mode 100644
index 20d61fd6fa..0000000000
--- a/apps/sim/tools/salesforce/dashboards.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-import { extractErrorMessage, getInstanceUrl } from './utils'
-
-const logger = createLogger('SalesforceDashboards')
-
-export interface SalesforceListDashboardsParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- folderName?: string
-}
-
-/**
- * List all dashboards accessible by the current user
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_getbasic_dashboardlist.htm
- */
-export const salesforceListDashboardsTool: ToolConfig = {
- id: 'salesforce_list_dashboards',
- name: 'List Dashboards from Salesforce',
- description: 'Get a list of dashboards accessible by the current user',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- folderName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Filter by folder name',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/dashboards`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- 'Failed to list dashboards from Salesforce'
- )
- logger.error('Failed to list dashboards', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- let dashboards = data.dashboards || data || []
-
- // Filter by folder name if provided
- if (params.folderName) {
- dashboards = dashboards.filter((dashboard: any) =>
- dashboard.folderName?.toLowerCase().includes(params.folderName.toLowerCase())
- )
- }
-
- return {
- success: true,
- output: {
- dashboards,
- metadata: {
- operation: 'list_dashboards',
- totalReturned: dashboards.length,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Dashboards data',
- properties: {
- dashboards: { type: 'array', description: 'Array of dashboard objects' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-export interface SalesforceGetDashboardParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- dashboardId: string
-}
-
-/**
- * Get details for a specific dashboard
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_dashboard_results.htm
- */
-export const salesforceGetDashboardTool: ToolConfig = {
- id: 'salesforce_get_dashboard',
- name: 'Get Dashboard from Salesforce',
- description: 'Get details and results for a specific dashboard',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- dashboardId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Dashboard ID (required)',
- },
- },
- request: {
- url: (params) => {
- if (!params.dashboardId || params.dashboardId.trim() === '') {
- throw new Error('Dashboard ID is required. Please provide a valid Salesforce Dashboard ID.')
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/dashboards/${params.dashboardId}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- `Failed to get dashboard ID: ${params.dashboardId}`
- )
- logger.error('Failed to get dashboard', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- dashboard: data,
- dashboardId: params.dashboardId,
- components: data.componentData || [],
- metadata: {
- operation: 'get_dashboard',
- dashboardName: data.name,
- folderId: data.folderId,
- runningUser: data.runningUser,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Dashboard data',
- properties: {
- dashboard: { type: 'object', description: 'Dashboard details' },
- dashboardId: { type: 'string', description: 'Dashboard ID' },
- components: { type: 'array', description: 'Dashboard component data' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-/**
- * Refresh a dashboard to get latest data
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_refresh_dashboard.htm
- */
-export const salesforceRefreshDashboardTool: ToolConfig = {
- id: 'salesforce_refresh_dashboard',
- name: 'Refresh Dashboard in Salesforce',
- description: 'Refresh a dashboard to get the latest data',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- dashboardId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Dashboard ID (required)',
- },
- },
- request: {
- url: (params) => {
- if (!params.dashboardId || params.dashboardId.trim() === '') {
- throw new Error('Dashboard ID is required. Please provide a valid Salesforce Dashboard ID.')
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/dashboards/${params.dashboardId}`
- },
- method: 'PUT',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: () => ({}),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- `Failed to refresh dashboard ID: ${params.dashboardId}`
- )
- logger.error('Failed to refresh dashboard', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- dashboard: data,
- dashboardId: params.dashboardId,
- components: data.componentData || [],
- status: data.status,
- metadata: {
- operation: 'refresh_dashboard',
- dashboardName: data.name,
- refreshDate: data.refreshDate,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Refreshed dashboard data',
- properties: {
- dashboard: { type: 'object', description: 'Dashboard details' },
- dashboardId: { type: 'string', description: 'Dashboard ID' },
- components: { type: 'array', description: 'Dashboard component data' },
- status: { type: 'object', description: 'Dashboard status' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
diff --git a/apps/sim/tools/salesforce/delete_case.ts b/apps/sim/tools/salesforce/delete_case.ts
new file mode 100644
index 0000000000..cefb38ed01
--- /dev/null
+++ b/apps/sim/tools/salesforce/delete_case.ts
@@ -0,0 +1,88 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceDeleteCaseParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ caseId: string
+}
+
+export interface SalesforceDeleteCaseResponse {
+ success: boolean
+ output: {
+ id: string
+ deleted: boolean
+ metadata: {
+ operation: 'delete_case'
+ }
+ }
+}
+
+export const salesforceDeleteCaseTool: ToolConfig<
+ SalesforceDeleteCaseParams,
+ SalesforceDeleteCaseResponse
+> = {
+ id: 'salesforce_delete_case',
+ name: 'Delete Case from Salesforce',
+ description: 'Delete a case',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ caseId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Case ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
+ method: 'DELETE',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ throw new Error(data[0]?.message || data.message || 'Failed to delete case')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.caseId || '',
+ deleted: true,
+ metadata: { operation: 'delete_case' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Deleted case' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/delete_contact.ts b/apps/sim/tools/salesforce/delete_contact.ts
new file mode 100644
index 0000000000..a15c3a7dbb
--- /dev/null
+++ b/apps/sim/tools/salesforce/delete_contact.ts
@@ -0,0 +1,88 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceContacts')
+
+export interface SalesforceDeleteContactParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ contactId: string
+}
+
+export interface SalesforceDeleteContactResponse {
+ success: boolean
+ output: {
+ id: string
+ deleted: boolean
+ metadata: { operation: 'delete_contact' }
+ }
+}
+
+export const salesforceDeleteContactTool: ToolConfig<
+ SalesforceDeleteContactParams,
+ SalesforceDeleteContactResponse
+> = {
+ id: 'salesforce_delete_contact',
+ name: 'Delete Contact from Salesforce',
+ description: 'Delete a contact from Salesforce CRM',
+ version: '1.0.0',
+
+ oauth: { required: true, provider: 'salesforce' },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ contactId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Contact ID to delete (required)',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
+ },
+ method: 'DELETE',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ }),
+ },
+
+ transformResponse: async (response: Response, params?) => {
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ logger.error('Salesforce API request failed', { data, status: response.status })
+ throw new Error(
+ data[0]?.message || data.message || 'Failed to delete contact from Salesforce'
+ )
+ }
+
+ return {
+ success: true,
+ output: {
+ id: params?.contactId || '',
+ deleted: true,
+ metadata: { operation: 'delete_contact' as const },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Operation success status' },
+ output: {
+ type: 'object',
+ description: 'Deleted contact data',
+ properties: {
+ id: { type: 'string', description: 'Deleted contact ID' },
+ deleted: { type: 'boolean', description: 'Whether contact was deleted' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/delete_lead.ts b/apps/sim/tools/salesforce/delete_lead.ts
new file mode 100644
index 0000000000..9f79b33940
--- /dev/null
+++ b/apps/sim/tools/salesforce/delete_lead.ts
@@ -0,0 +1,74 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceDeleteLeadParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ leadId: string
+}
+
+export interface SalesforceDeleteLeadResponse {
+ success: boolean
+ output: {
+ id: string
+ deleted: boolean
+ metadata: {
+ operation: 'delete_lead'
+ }
+ }
+}
+
+export const salesforceDeleteLeadTool: ToolConfig<
+ SalesforceDeleteLeadParams,
+ SalesforceDeleteLeadResponse
+> = {
+ id: 'salesforce_delete_lead',
+ name: 'Delete Lead from Salesforce',
+ description: 'Delete a lead',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ leadId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Lead ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
+ method: 'DELETE',
+ headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ throw new Error(data[0]?.message || data.message || 'Failed to delete lead')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.leadId || '',
+ deleted: true,
+ metadata: { operation: 'delete_lead' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Deleted lead' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/delete_opportunity.ts b/apps/sim/tools/salesforce/delete_opportunity.ts
new file mode 100644
index 0000000000..31258d8677
--- /dev/null
+++ b/apps/sim/tools/salesforce/delete_opportunity.ts
@@ -0,0 +1,74 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceDeleteOpportunityParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ opportunityId: string
+}
+
+export interface SalesforceDeleteOpportunityResponse {
+ success: boolean
+ output: {
+ id: string
+ deleted: boolean
+ metadata: {
+ operation: 'delete_opportunity'
+ }
+ }
+}
+
+export const salesforceDeleteOpportunityTool: ToolConfig<
+ SalesforceDeleteOpportunityParams,
+ SalesforceDeleteOpportunityResponse
+> = {
+ id: 'salesforce_delete_opportunity',
+ name: 'Delete Opportunity from Salesforce',
+ description: 'Delete an opportunity',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ opportunityId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Opportunity ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
+ method: 'DELETE',
+ headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ throw new Error(data[0]?.message || data.message || 'Failed to delete opportunity')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.opportunityId || '',
+ deleted: true,
+ metadata: { operation: 'delete_opportunity' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Deleted opportunity' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/delete_task.ts b/apps/sim/tools/salesforce/delete_task.ts
new file mode 100644
index 0000000000..6f6cc405b0
--- /dev/null
+++ b/apps/sim/tools/salesforce/delete_task.ts
@@ -0,0 +1,88 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceDeleteTaskParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ taskId: string
+}
+
+export interface SalesforceDeleteTaskResponse {
+ success: boolean
+ output: {
+ id: string
+ deleted: boolean
+ metadata: {
+ operation: 'delete_task'
+ }
+ }
+}
+
+export const salesforceDeleteTaskTool: ToolConfig<
+ SalesforceDeleteTaskParams,
+ SalesforceDeleteTaskResponse
+> = {
+ id: 'salesforce_delete_task',
+ name: 'Delete Task from Salesforce',
+ description: 'Delete a task',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ taskId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Task ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
+ method: 'DELETE',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ throw new Error(data[0]?.message || data.message || 'Failed to delete task')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.taskId || '',
+ deleted: true,
+ metadata: { operation: 'delete_task' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Deleted task' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/describe_object.ts b/apps/sim/tools/salesforce/describe_object.ts
new file mode 100644
index 0000000000..a9087e0ee8
--- /dev/null
+++ b/apps/sim/tools/salesforce/describe_object.ts
@@ -0,0 +1,140 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceQuery')
+
+export interface SalesforceDescribeObjectParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ objectName: string
+}
+
+export interface SalesforceDescribeObjectResponse {
+ success: boolean
+ output: {
+ objectName: string
+ label?: string
+ labelPlural?: string
+ fields?: any[]
+ keyPrefix?: string
+ queryable?: boolean
+ createable?: boolean
+ updateable?: boolean
+ deletable?: boolean
+ childRelationships?: any[]
+ recordTypeInfos?: any[]
+ metadata: {
+ operation: 'describe_object'
+ fieldCount: number
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Describe a Salesforce object to get its metadata/fields
+ * Useful for discovering available fields for queries
+ */
+export const salesforceDescribeObjectTool: ToolConfig<
+ SalesforceDescribeObjectParams,
+ SalesforceDescribeObjectResponse
+> = {
+ id: 'salesforce_describe_object',
+ name: 'Describe Salesforce Object',
+ description: 'Get metadata and field information for a Salesforce object',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ objectName: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'API name of the object (e.g., Account, Contact, Lead, Custom_Object__c)',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.objectName || params.objectName.trim() === '') {
+ throw new Error(
+ 'Object Name is required. Please provide a valid Salesforce object API name (e.g., Account, Contact, Lead).'
+ )
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/sobjects/${params.objectName}/describe`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ `Failed to describe object: ${params?.objectName}`
+ )
+ logger.error('Failed to describe object', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ objectName: params?.objectName || '',
+ label: data.label,
+ labelPlural: data.labelPlural,
+ fields: data.fields,
+ keyPrefix: data.keyPrefix,
+ queryable: data.queryable,
+ createable: data.createable,
+ updateable: data.updateable,
+ deletable: data.deletable,
+ childRelationships: data.childRelationships,
+ recordTypeInfos: data.recordTypeInfos,
+ metadata: {
+ operation: 'describe_object',
+ fieldCount: data.fields?.length || 0,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Object metadata',
+ properties: {
+ objectName: { type: 'string', description: 'API name of the object' },
+ label: { type: 'string', description: 'Display label' },
+ labelPlural: { type: 'string', description: 'Plural display label' },
+ fields: { type: 'array', description: 'Array of field definitions' },
+ keyPrefix: { type: 'string', description: 'ID prefix for this object type' },
+ queryable: { type: 'boolean', description: 'Whether object can be queried' },
+ createable: { type: 'boolean', description: 'Whether records can be created' },
+ updateable: { type: 'boolean', description: 'Whether records can be updated' },
+ deletable: { type: 'boolean', description: 'Whether records can be deleted' },
+ childRelationships: { type: 'array', description: 'Child relationship definitions' },
+ recordTypeInfos: { type: 'array', description: 'Record type information' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_cases.ts b/apps/sim/tools/salesforce/get_cases.ts
new file mode 100644
index 0000000000..cd65f84790
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_cases.ts
@@ -0,0 +1,128 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceGetCasesParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ caseId?: string
+ limit?: string
+ fields?: string
+ orderBy?: string
+}
+
+export interface SalesforceGetCasesResponse {
+ success: boolean
+ output: {
+ case?: any
+ cases?: any[]
+ paging?: {
+ nextRecordsUrl?: string
+ totalSize: number
+ done: boolean
+ }
+ metadata: {
+ operation: 'get_cases'
+ totalReturned?: number
+ hasMore?: boolean
+ }
+ success: boolean
+ }
+}
+
+export const salesforceGetCasesTool: ToolConfig<
+ SalesforceGetCasesParams,
+ SalesforceGetCasesResponse
+> = {
+ id: 'salesforce_get_cases',
+ name: 'Get Cases from Salesforce',
+ description: 'Get case(s) from Salesforce',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ caseId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Case ID (optional)',
+ },
+ limit: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Max results (default: 100)',
+ },
+ fields: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Comma-separated fields',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Order by field',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ if (params.caseId) {
+ const fields =
+ params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
+ return `${instanceUrl}/services/data/v59.0/sobjects/Case/${params.caseId}?fields=${fields}`
+ }
+ const limit = params.limit ? Number.parseInt(params.limit) : 100
+ const fields =
+ params.fields || 'Id,CaseNumber,Subject,Status,Priority,Origin,ContactId,AccountId'
+ const orderBy = params.orderBy || 'CreatedDate DESC'
+ const query = `SELECT ${fields} FROM Case ORDER BY ${orderBy} LIMIT ${limit}`
+ return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch cases')
+ if (params?.caseId) {
+ return {
+ success: true,
+ output: { case: data, metadata: { operation: 'get_cases' }, success: true },
+ }
+ }
+ const cases = data.records || []
+ return {
+ success: true,
+ output: {
+ cases,
+ paging: {
+ nextRecordsUrl: data.nextRecordsUrl,
+ totalSize: data.totalSize || cases.length,
+ done: data.done !== false,
+ },
+ metadata: { operation: 'get_cases', totalReturned: cases.length, hasMore: !data.done },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Case data' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_contacts.ts b/apps/sim/tools/salesforce/get_contacts.ts
new file mode 100644
index 0000000000..679d3a32d6
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_contacts.ts
@@ -0,0 +1,169 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceContacts')
+
+export interface SalesforceGetContactsParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ contactId?: string
+ limit?: string
+ fields?: string
+ orderBy?: string
+}
+
+export interface SalesforceGetContactsResponse {
+ success: boolean
+ output: {
+ contacts?: any[]
+ contact?: any
+ paging?: {
+ nextRecordsUrl?: string
+ totalSize: number
+ done: boolean
+ }
+ metadata: {
+ operation: 'get_contacts'
+ totalReturned?: number
+ hasMore?: boolean
+ singleContact?: boolean
+ }
+ success: boolean
+ }
+}
+
+export const salesforceGetContactsTool: ToolConfig<
+ SalesforceGetContactsParams,
+ SalesforceGetContactsResponse
+> = {
+ id: 'salesforce_get_contacts',
+ name: 'Get Contacts from Salesforce',
+ description: 'Get contact(s) from Salesforce - single contact if ID provided, or list if not',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ contactId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Contact ID (if provided, returns single contact)',
+ },
+ limit: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Number of results (default: 100, max: 2000). Only for list query.',
+ },
+ fields: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Comma-separated fields (e.g., "Id,FirstName,LastName,Email,Phone")',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Order by field (e.g., "LastName ASC"). Only for list query.',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+
+ // Single contact by ID
+ if (params.contactId) {
+ const fields =
+ params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
+ return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}?fields=${fields}`
+ }
+
+ // List contacts with SOQL query
+ const limit = params.limit ? Number.parseInt(params.limit) : 100
+ const fields = params.fields || 'Id,FirstName,LastName,Email,Phone,AccountId,Title,Department'
+ const orderBy = params.orderBy || 'LastName ASC'
+ const query = `SELECT ${fields} FROM Contact ORDER BY ${orderBy} LIMIT ${limit}`
+ const encodedQuery = encodeURIComponent(query)
+
+ return `${instanceUrl}/services/data/v59.0/query?q=${encodedQuery}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response: Response, params?) => {
+ const data = await response.json()
+
+ if (!response.ok) {
+ logger.error('Salesforce API request failed', { data, status: response.status })
+ throw new Error(
+ data[0]?.message || data.message || 'Failed to fetch contacts from Salesforce'
+ )
+ }
+
+ // Single contact response
+ if (params?.contactId) {
+ return {
+ success: true,
+ output: {
+ contact: data,
+ metadata: {
+ operation: 'get_contacts' as const,
+ singleContact: true,
+ },
+ success: true,
+ },
+ }
+ }
+
+ // List contacts response
+ const contacts = data.records || []
+ return {
+ success: true,
+ output: {
+ contacts,
+ paging: {
+ nextRecordsUrl: data.nextRecordsUrl,
+ totalSize: data.totalSize || contacts.length,
+ done: data.done !== false,
+ },
+ metadata: {
+ operation: 'get_contacts' as const,
+ totalReturned: contacts.length,
+ hasMore: !data.done,
+ singleContact: false,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Operation success status' },
+ output: {
+ type: 'object',
+ description: 'Contact(s) data',
+ properties: {
+ contacts: { type: 'array', description: 'Array of contacts (list query)' },
+ contact: { type: 'object', description: 'Single contact (by ID)' },
+ paging: { type: 'object', description: 'Pagination info (list query)' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_dashboard.ts b/apps/sim/tools/salesforce/get_dashboard.ts
new file mode 100644
index 0000000000..b5824f26e0
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_dashboard.ts
@@ -0,0 +1,118 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceDashboards')
+
+export interface SalesforceGetDashboardParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ dashboardId: string
+}
+
+export interface SalesforceGetDashboardResponse {
+ success: boolean
+ output: {
+ dashboard: any
+ dashboardId: string
+ components: any[]
+ metadata: {
+ operation: 'get_dashboard'
+ dashboardName?: string
+ folderId?: string
+ runningUser?: any
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Get details for a specific dashboard
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_dashboard_results.htm
+ */
+export const salesforceGetDashboardTool: ToolConfig<
+ SalesforceGetDashboardParams,
+ SalesforceGetDashboardResponse
+> = {
+ id: 'salesforce_get_dashboard',
+ name: 'Get Dashboard from Salesforce',
+ description: 'Get details and results for a specific dashboard',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ dashboardId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Dashboard ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.dashboardId || params.dashboardId.trim() === '') {
+ throw new Error('Dashboard ID is required. Please provide a valid Salesforce Dashboard ID.')
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/dashboards/${params.dashboardId}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ `Failed to get dashboard ID: ${params?.dashboardId}`
+ )
+ logger.error('Failed to get dashboard', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ dashboard: data,
+ dashboardId: params?.dashboardId || '',
+ components: data.componentData || [],
+ metadata: {
+ operation: 'get_dashboard',
+ dashboardName: data.name,
+ folderId: data.folderId,
+ runningUser: data.runningUser,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Dashboard data',
+ properties: {
+ dashboard: { type: 'object', description: 'Dashboard details' },
+ dashboardId: { type: 'string', description: 'Dashboard ID' },
+ components: { type: 'array', description: 'Dashboard component data' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_leads.ts b/apps/sim/tools/salesforce/get_leads.ts
new file mode 100644
index 0000000000..93bda3453b
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_leads.ts
@@ -0,0 +1,132 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceGetLeadsParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ leadId?: string
+ limit?: string
+ fields?: string
+ orderBy?: string
+}
+
+export interface SalesforceGetLeadsResponse {
+ success: boolean
+ output: {
+ lead?: any
+ leads?: any[]
+ paging?: {
+ nextRecordsUrl?: string
+ totalSize: number
+ done: boolean
+ }
+ metadata: {
+ operation: 'get_leads'
+ totalReturned?: number
+ hasMore?: boolean
+ singleLead?: boolean
+ }
+ success: boolean
+ }
+}
+
+export const salesforceGetLeadsTool: ToolConfig<
+ SalesforceGetLeadsParams,
+ SalesforceGetLeadsResponse
+> = {
+ id: 'salesforce_get_leads',
+ name: 'Get Leads from Salesforce',
+ description: 'Get lead(s) from Salesforce',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ leadId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Lead ID (optional)',
+ },
+ limit: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Max results (default: 100)',
+ },
+ fields: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Comma-separated fields',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Order by field',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ if (params.leadId) {
+ const fields =
+ params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
+ return `${instanceUrl}/services/data/v59.0/sobjects/Lead/${params.leadId}?fields=${fields}`
+ }
+ const limit = params.limit ? Number.parseInt(params.limit) : 100
+ const fields = params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
+ const orderBy = params.orderBy || 'LastName ASC'
+ const query = `SELECT ${fields} FROM Lead ORDER BY ${orderBy} LIMIT ${limit}`
+ return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch leads')
+ if (params?.leadId) {
+ return {
+ success: true,
+ output: {
+ lead: data,
+ metadata: { operation: 'get_leads', singleLead: true },
+ success: true,
+ },
+ }
+ }
+ const leads = data.records || []
+ return {
+ success: true,
+ output: {
+ leads,
+ paging: {
+ nextRecordsUrl: data.nextRecordsUrl,
+ totalSize: data.totalSize || leads.length,
+ done: data.done !== false,
+ },
+ metadata: { operation: 'get_leads', totalReturned: leads.length, hasMore: !data.done },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: { type: 'object', description: 'Lead data' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_opportunities.ts b/apps/sim/tools/salesforce/get_opportunities.ts
new file mode 100644
index 0000000000..0330abdd45
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_opportunities.ts
@@ -0,0 +1,131 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceGetOpportunitiesParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ opportunityId?: string
+ limit?: string
+ fields?: string
+ orderBy?: string
+}
+
+export interface SalesforceGetOpportunitiesResponse {
+ success: boolean
+ output: {
+ opportunity?: any
+ opportunities?: any[]
+ paging?: {
+ nextRecordsUrl?: string
+ totalSize: number
+ done: boolean
+ }
+ metadata: {
+ operation: 'get_opportunities'
+ totalReturned?: number
+ hasMore?: boolean
+ }
+ success: boolean
+ }
+}
+
+export const salesforceGetOpportunitiesTool: ToolConfig<
+ SalesforceGetOpportunitiesParams,
+ SalesforceGetOpportunitiesResponse
+> = {
+ id: 'salesforce_get_opportunities',
+ name: 'Get Opportunities from Salesforce',
+ description: 'Get opportunity(ies) from Salesforce',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ opportunityId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Opportunity ID (optional)',
+ },
+ limit: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Max results (default: 100)',
+ },
+ fields: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Comma-separated fields',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Order by field',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ if (params.opportunityId) {
+ const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
+ return `${instanceUrl}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}?fields=${fields}`
+ }
+ const limit = params.limit ? Number.parseInt(params.limit) : 100
+ const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
+ const orderBy = params.orderBy || 'CloseDate DESC'
+ const query = `SELECT ${fields} FROM Opportunity ORDER BY ${orderBy} LIMIT ${limit}`
+ return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok)
+ throw new Error(data[0]?.message || data.message || 'Failed to fetch opportunities')
+ if (params?.opportunityId) {
+ return {
+ success: true,
+ output: { opportunity: data, metadata: { operation: 'get_opportunities' }, success: true },
+ }
+ }
+ const opportunities = data.records || []
+ return {
+ success: true,
+ output: {
+ opportunities,
+ paging: {
+ nextRecordsUrl: data.nextRecordsUrl,
+ totalSize: data.totalSize || opportunities.length,
+ done: data.done !== false,
+ },
+ metadata: {
+ operation: 'get_opportunities',
+ totalReturned: opportunities.length,
+ hasMore: !data.done,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Opportunity data' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_report.ts b/apps/sim/tools/salesforce/get_report.ts
new file mode 100644
index 0000000000..e1f0c08dfd
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_report.ts
@@ -0,0 +1,109 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceReports')
+
+export interface SalesforceGetReportParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ reportId: string
+}
+
+export interface SalesforceGetReportResponse {
+ success: boolean
+ output: {
+ report: any
+ reportId: string
+ metadata: {
+ operation: 'get_report'
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Get metadata for a specific report
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportmetadata.htm
+ */
+export const salesforceGetReportTool: ToolConfig<
+ SalesforceGetReportParams,
+ SalesforceGetReportResponse
+> = {
+ id: 'salesforce_get_report',
+ name: 'Get Report Metadata from Salesforce',
+ description: 'Get metadata and describe information for a specific report',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ reportId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Report ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.reportId || params.reportId.trim() === '') {
+ throw new Error('Report ID is required. Please provide a valid Salesforce Report ID.')
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/reports/${params.reportId}/describe`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ `Failed to get report metadata for report ID: ${params?.reportId}`
+ )
+ logger.error('Failed to get report metadata', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ report: data,
+ reportId: params?.reportId || '',
+ metadata: {
+ operation: 'get_report',
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Report metadata',
+ properties: {
+ report: { type: 'object', description: 'Report metadata object' },
+ reportId: { type: 'string', description: 'Report ID' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/get_tasks.ts b/apps/sim/tools/salesforce/get_tasks.ts
new file mode 100644
index 0000000000..791a5ab211
--- /dev/null
+++ b/apps/sim/tools/salesforce/get_tasks.ts
@@ -0,0 +1,139 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceGetTasksParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ taskId?: string
+ limit?: string
+ fields?: string
+ orderBy?: string
+}
+
+export interface SalesforceGetTasksResponse {
+ success: boolean
+ output: {
+ task?: any
+ tasks?: any[]
+ paging?: {
+ nextRecordsUrl?: string
+ totalSize: number
+ done: boolean
+ }
+ metadata: {
+ operation: 'get_tasks'
+ totalReturned?: number
+ hasMore?: boolean
+ }
+ success: boolean
+ }
+}
+
+export const salesforceGetTasksTool: ToolConfig<
+ SalesforceGetTasksParams,
+ SalesforceGetTasksResponse
+> = {
+ id: 'salesforce_get_tasks',
+ name: 'Get Tasks from Salesforce',
+ description: 'Get task(s) from Salesforce',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ taskId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Task ID (optional)',
+ },
+ limit: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Max results (default: 100)',
+ },
+ fields: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Comma-separated fields',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Order by field',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ if (params.taskId) {
+ const fields =
+ params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
+ return `${instanceUrl}/services/data/v59.0/sobjects/Task/${params.taskId}?fields=${fields}`
+ }
+ const limit = params.limit ? Number.parseInt(params.limit) : 100
+ const fields = params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
+ const orderBy = params.orderBy || 'ActivityDate DESC'
+ const query = `SELECT ${fields} FROM Task ORDER BY ${orderBy} LIMIT ${limit}`
+ return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch tasks')
+ if (params?.taskId) {
+ return {
+ success: true,
+ output: { task: data, metadata: { operation: 'get_tasks' }, success: true },
+ }
+ }
+ const tasks = data.records || []
+ return {
+ success: true,
+ output: {
+ tasks,
+ paging: {
+ nextRecordsUrl: data.nextRecordsUrl,
+ totalSize: data.totalSize || tasks.length,
+ done: data.done !== false,
+ },
+ metadata: { operation: 'get_tasks', totalReturned: tasks.length, hasMore: !data.done },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Task data' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/index.ts b/apps/sim/tools/salesforce/index.ts
index 58071d91af..6ffc3adc68 100644
--- a/apps/sim/tools/salesforce/index.ts
+++ b/apps/sim/tools/salesforce/index.ts
@@ -1,51 +1,35 @@
-export {
- salesforceCreateCaseTool,
- salesforceDeleteCaseTool,
- salesforceGetCasesTool,
- salesforceUpdateCaseTool,
-} from './cases'
-export {
- salesforceCreateContactTool,
- salesforceDeleteContactTool,
- salesforceGetContactsTool,
- salesforceUpdateContactTool,
-} from './contacts'
export { salesforceCreateAccountTool } from './create_account'
-export {
- salesforceGetDashboardTool,
- salesforceListDashboardsTool,
- salesforceRefreshDashboardTool,
-} from './dashboards'
+export { salesforceCreateCaseTool } from './create_case'
+export { salesforceCreateContactTool } from './create_contact'
+export { salesforceCreateLeadTool } from './create_lead'
+export { salesforceCreateOpportunityTool } from './create_opportunity'
+export { salesforceCreateTaskTool } from './create_task'
export { salesforceDeleteAccountTool } from './delete_account'
+export { salesforceDeleteCaseTool } from './delete_case'
+export { salesforceDeleteContactTool } from './delete_contact'
+export { salesforceDeleteLeadTool } from './delete_lead'
+export { salesforceDeleteOpportunityTool } from './delete_opportunity'
+export { salesforceDeleteTaskTool } from './delete_task'
+export { salesforceDescribeObjectTool } from './describe_object'
export { salesforceGetAccountsTool } from './get_accounts'
-export {
- salesforceCreateLeadTool,
- salesforceDeleteLeadTool,
- salesforceGetLeadsTool,
- salesforceUpdateLeadTool,
-} from './leads'
-export {
- salesforceCreateOpportunityTool,
- salesforceDeleteOpportunityTool,
- salesforceGetOpportunitiesTool,
- salesforceUpdateOpportunityTool,
-} from './opportunities'
-export {
- salesforceDescribeObjectTool,
- salesforceListObjectsTool,
- salesforceQueryMoreTool,
- salesforceQueryTool,
-} from './query'
-export {
- salesforceGetReportTool,
- salesforceListReportsTool,
- salesforceListReportTypesTool,
- salesforceRunReportTool,
-} from './reports'
-export {
- salesforceCreateTaskTool,
- salesforceDeleteTaskTool,
- salesforceGetTasksTool,
- salesforceUpdateTaskTool,
-} from './tasks'
+export { salesforceGetCasesTool } from './get_cases'
+export { salesforceGetContactsTool } from './get_contacts'
+export { salesforceGetDashboardTool } from './get_dashboard'
+export { salesforceGetLeadsTool } from './get_leads'
+export { salesforceGetOpportunitiesTool } from './get_opportunities'
+export { salesforceGetReportTool } from './get_report'
+export { salesforceGetTasksTool } from './get_tasks'
+export { salesforceListDashboardsTool } from './list_dashboards'
+export { salesforceListObjectsTool } from './list_objects'
+export { salesforceListReportTypesTool } from './list_report_types'
+export { salesforceListReportsTool } from './list_reports'
+export { salesforceQueryTool } from './query'
+export { salesforceQueryMoreTool } from './query_more'
+export { salesforceRefreshDashboardTool } from './refresh_dashboard'
+export { salesforceRunReportTool } from './run_report'
export { salesforceUpdateAccountTool } from './update_account'
+export { salesforceUpdateCaseTool } from './update_case'
+export { salesforceUpdateContactTool } from './update_contact'
+export { salesforceUpdateLeadTool } from './update_lead'
+export { salesforceUpdateOpportunityTool } from './update_opportunity'
+export { salesforceUpdateTaskTool } from './update_task'
diff --git a/apps/sim/tools/salesforce/leads.ts b/apps/sim/tools/salesforce/leads.ts
deleted file mode 100644
index 13cbfe4af3..0000000000
--- a/apps/sim/tools/salesforce/leads.ts
+++ /dev/null
@@ -1,351 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-
-const logger = createLogger('SalesforceLeads')
-
-function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
- if (instanceUrl) return instanceUrl
- if (idToken) {
- try {
- const base64Url = idToken.split('.')[1]
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
- const jsonPayload = decodeURIComponent(
- atob(base64)
- .split('')
- .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
- .join('')
- )
- const decoded = JSON.parse(jsonPayload)
- if (decoded.profile) {
- const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
- if (match) return match[1]
- } else if (decoded.sub) {
- const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
- if (match && match[1] !== 'https://login.salesforce.com') return match[1]
- }
- } catch (error) {
- logger.error('Failed to decode Salesforce idToken', { error })
- }
- }
- throw new Error('Salesforce instance URL is required but not provided')
-}
-
-// Get Leads
-export interface SalesforceGetLeadsParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- leadId?: string
- limit?: string
- fields?: string
- orderBy?: string
-}
-
-export const salesforceGetLeadsTool: ToolConfig = {
- id: 'salesforce_get_leads',
- name: 'Get Leads from Salesforce',
- description: 'Get lead(s) from Salesforce',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- leadId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Lead ID (optional)',
- },
- limit: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Max results (default: 100)',
- },
- fields: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Comma-separated fields',
- },
- orderBy: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Order by field',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- if (params.leadId) {
- const fields =
- params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
- return `${instanceUrl}/services/data/v59.0/sobjects/Lead/${params.leadId}?fields=${fields}`
- }
- const limit = params.limit ? Number.parseInt(params.limit) : 100
- const fields = params.fields || 'Id,FirstName,LastName,Company,Email,Phone,Status,LeadSource'
- const orderBy = params.orderBy || 'LastName ASC'
- const query = `SELECT ${fields} FROM Lead ORDER BY ${orderBy} LIMIT ${limit}`
- return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch leads')
- if (params.leadId) {
- return {
- success: true,
- output: {
- lead: data,
- metadata: { operation: 'get_leads', singleLead: true },
- success: true,
- },
- }
- }
- const leads = data.records || []
- return {
- success: true,
- output: {
- leads,
- paging: {
- nextRecordsUrl: data.nextRecordsUrl,
- totalSize: data.totalSize || leads.length,
- done: data.done !== false,
- },
- metadata: { operation: 'get_leads', totalReturned: leads.length, hasMore: !data.done },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: { type: 'object', description: 'Lead data' },
- },
-}
-
-// Create Lead
-export const salesforceCreateLeadTool: ToolConfig = {
- id: 'salesforce_create_lead',
- name: 'Create Lead in Salesforce',
- description: 'Create a new lead',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- lastName: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Last name (required)',
- },
- company: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Company (required)',
- },
- firstName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'First name',
- },
- email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
- phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
- status: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Lead status',
- },
- leadSource: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Lead source',
- },
- title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead`,
- method: 'POST',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = { LastName: params.lastName, Company: params.company }
- if (params.firstName) body.FirstName = params.firstName
- if (params.email) body.Email = params.email
- if (params.phone) body.Phone = params.phone
- if (params.status) body.Status = params.status
- if (params.leadSource) body.LeadSource = params.leadSource
- if (params.title) body.Title = params.title
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create lead')
- return {
- success: true,
- output: {
- id: data.id,
- success: data.success,
- created: true,
- metadata: { operation: 'create_lead' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Created lead' },
- },
-}
-
-// Update Lead
-export const salesforceUpdateLeadTool: ToolConfig = {
- id: 'salesforce_update_lead',
- name: 'Update Lead in Salesforce',
- description: 'Update an existing lead',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- leadId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Lead ID (required)',
- },
- lastName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Last name',
- },
- company: { type: 'string', required: false, visibility: 'user-only', description: 'Company' },
- firstName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'First name',
- },
- email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
- phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
- status: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Lead status',
- },
- leadSource: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Lead source',
- },
- title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
- method: 'PATCH',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {}
- if (params.lastName) body.LastName = params.lastName
- if (params.company) body.Company = params.company
- if (params.firstName) body.FirstName = params.firstName
- if (params.email) body.Email = params.email
- if (params.phone) body.Phone = params.phone
- if (params.status) body.Status = params.status
- if (params.leadSource) body.LeadSource = params.leadSource
- if (params.title) body.Title = params.title
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json()
- throw new Error(data[0]?.message || data.message || 'Failed to update lead')
- }
- return {
- success: true,
- output: { id: params.leadId, updated: true, metadata: { operation: 'update_lead' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Updated lead' },
- },
-}
-
-// Delete Lead
-export const salesforceDeleteLeadTool: ToolConfig = {
- id: 'salesforce_delete_lead',
- name: 'Delete Lead from Salesforce',
- description: 'Delete a lead',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- leadId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Lead ID (required)',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
- method: 'DELETE',
- headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json().catch(() => ({}))
- throw new Error(data[0]?.message || data.message || 'Failed to delete lead')
- }
- return {
- success: true,
- output: { id: params.leadId, deleted: true, metadata: { operation: 'delete_lead' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Deleted lead' },
- },
-}
diff --git a/apps/sim/tools/salesforce/list_dashboards.ts b/apps/sim/tools/salesforce/list_dashboards.ts
new file mode 100644
index 0000000000..62a5fc42a1
--- /dev/null
+++ b/apps/sim/tools/salesforce/list_dashboards.ts
@@ -0,0 +1,114 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceDashboards')
+
+export interface SalesforceListDashboardsParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ folderName?: string
+}
+
+export interface SalesforceListDashboardsResponse {
+ success: boolean
+ output: {
+ dashboards: any[]
+ metadata: {
+ operation: 'list_dashboards'
+ totalReturned: number
+ }
+ success: boolean
+ }
+}
+
+/**
+ * List all dashboards accessible by the current user
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_getbasic_dashboardlist.htm
+ */
+export const salesforceListDashboardsTool: ToolConfig<
+ SalesforceListDashboardsParams,
+ SalesforceListDashboardsResponse
+> = {
+ id: 'salesforce_list_dashboards',
+ name: 'List Dashboards from Salesforce',
+ description: 'Get a list of dashboards accessible by the current user',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ folderName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Filter by folder name',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/dashboards`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ 'Failed to list dashboards from Salesforce'
+ )
+ logger.error('Failed to list dashboards', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ let dashboards = data.dashboards || data || []
+
+ // Filter by folder name if provided
+ if (params?.folderName) {
+ dashboards = dashboards.filter((dashboard: any) =>
+ dashboard.folderName?.toLowerCase().includes(params.folderName!.toLowerCase())
+ )
+ }
+
+ return {
+ success: true,
+ output: {
+ dashboards,
+ metadata: {
+ operation: 'list_dashboards',
+ totalReturned: dashboards.length,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Dashboards data',
+ properties: {
+ dashboards: { type: 'array', description: 'Array of dashboard objects' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/list_objects.ts b/apps/sim/tools/salesforce/list_objects.ts
new file mode 100644
index 0000000000..4efedefa6e
--- /dev/null
+++ b/apps/sim/tools/salesforce/list_objects.ts
@@ -0,0 +1,106 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceQuery')
+
+export interface SalesforceListObjectsParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+}
+
+export interface SalesforceListObjectsResponse {
+ success: boolean
+ output: {
+ objects: any[]
+ encoding?: string
+ maxBatchSize?: number
+ metadata: {
+ operation: 'list_objects'
+ totalReturned: number
+ }
+ success: boolean
+ }
+}
+
+/**
+ * List all available Salesforce objects
+ * Useful for discovering what objects are available
+ */
+export const salesforceListObjectsTool: ToolConfig<
+ SalesforceListObjectsParams,
+ SalesforceListObjectsResponse
+> = {
+ id: 'salesforce_list_objects',
+ name: 'List Salesforce Objects',
+ description: 'Get a list of all available Salesforce objects',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/sobjects`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ 'Failed to list Salesforce objects'
+ )
+ logger.error('Failed to list objects', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ const objects = data.sobjects || []
+
+ return {
+ success: true,
+ output: {
+ objects,
+ encoding: data.encoding,
+ maxBatchSize: data.maxBatchSize,
+ metadata: {
+ operation: 'list_objects',
+ totalReturned: objects.length,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Objects list',
+ properties: {
+ objects: { type: 'array', description: 'Array of available Salesforce objects' },
+ encoding: { type: 'string', description: 'Encoding used' },
+ maxBatchSize: { type: 'number', description: 'Maximum batch size' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/list_report_types.ts b/apps/sim/tools/salesforce/list_report_types.ts
new file mode 100644
index 0000000000..70f3c09167
--- /dev/null
+++ b/apps/sim/tools/salesforce/list_report_types.ts
@@ -0,0 +1,98 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceReports')
+
+export interface SalesforceListReportTypesParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+}
+
+export interface SalesforceListReportTypesResponse {
+ success: boolean
+ output: {
+ reportTypes: any[]
+ metadata: {
+ operation: 'list_report_types'
+ totalReturned: number
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Get list of available report types
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_list_reporttypes.htm
+ */
+export const salesforceListReportTypesTool: ToolConfig<
+ SalesforceListReportTypesParams,
+ SalesforceListReportTypesResponse
+> = {
+ id: 'salesforce_list_report_types',
+ name: 'List Report Types from Salesforce',
+ description: 'Get a list of available report types',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/reportTypes`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ 'Failed to list report types from Salesforce'
+ )
+ logger.error('Failed to list report types', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ reportTypes: data,
+ metadata: {
+ operation: 'list_report_types',
+ totalReturned: Array.isArray(data) ? data.length : 0,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Report types data',
+ properties: {
+ reportTypes: { type: 'array', description: 'Array of report type objects' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/list_reports.ts b/apps/sim/tools/salesforce/list_reports.ts
new file mode 100644
index 0000000000..09550383aa
--- /dev/null
+++ b/apps/sim/tools/salesforce/list_reports.ts
@@ -0,0 +1,130 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceReports')
+
+export interface SalesforceListReportsParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ folderName?: string
+ searchTerm?: string
+}
+
+export interface SalesforceListReportsResponse {
+ success: boolean
+ output: {
+ reports: any[]
+ metadata: {
+ operation: 'list_reports'
+ totalReturned: number
+ }
+ success: boolean
+ }
+}
+
+/**
+ * List all reports accessible by the current user
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportlist.htm
+ */
+export const salesforceListReportsTool: ToolConfig<
+ SalesforceListReportsParams,
+ SalesforceListReportsResponse
+> = {
+ id: 'salesforce_list_reports',
+ name: 'List Reports from Salesforce',
+ description: 'Get a list of reports accessible by the current user',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ folderName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Filter by folder name',
+ },
+ searchTerm: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Search term to filter reports by name',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/reports`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ 'Failed to list reports from Salesforce'
+ )
+ logger.error('Failed to list reports', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ let reports = data || []
+
+ // Filter by folder name if provided
+ if (params?.folderName) {
+ reports = reports.filter((report: any) =>
+ report.folderName?.toLowerCase().includes(params.folderName!.toLowerCase())
+ )
+ }
+
+ // Filter by search term if provided
+ if (params?.searchTerm) {
+ reports = reports.filter(
+ (report: any) =>
+ report.name?.toLowerCase().includes(params.searchTerm!.toLowerCase()) ||
+ report.description?.toLowerCase().includes(params.searchTerm!.toLowerCase())
+ )
+ }
+
+ return {
+ success: true,
+ output: {
+ reports,
+ metadata: {
+ operation: 'list_reports',
+ totalReturned: reports.length,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Reports data',
+ properties: {
+ reports: { type: 'array', description: 'Array of report objects' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/opportunities.ts b/apps/sim/tools/salesforce/opportunities.ts
deleted file mode 100644
index bf4df7bd42..0000000000
--- a/apps/sim/tools/salesforce/opportunities.ts
+++ /dev/null
@@ -1,355 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-
-const logger = createLogger('SalesforceOpportunities')
-
-function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
- if (instanceUrl) return instanceUrl
- if (idToken) {
- try {
- const base64Url = idToken.split('.')[1]
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
- const jsonPayload = decodeURIComponent(
- atob(base64)
- .split('')
- .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
- .join('')
- )
- const decoded = JSON.parse(jsonPayload)
- if (decoded.profile) {
- const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
- if (match) return match[1]
- } else if (decoded.sub) {
- const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
- if (match && match[1] !== 'https://login.salesforce.com') return match[1]
- }
- } catch (error) {
- logger.error('Failed to decode Salesforce idToken', { error })
- }
- }
- throw new Error('Salesforce instance URL is required but not provided')
-}
-
-// Get Opportunities
-export const salesforceGetOpportunitiesTool: ToolConfig = {
- id: 'salesforce_get_opportunities',
- name: 'Get Opportunities from Salesforce',
- description: 'Get opportunity(ies) from Salesforce',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- opportunityId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Opportunity ID (optional)',
- },
- limit: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Max results (default: 100)',
- },
- fields: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Comma-separated fields',
- },
- orderBy: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Order by field',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- if (params.opportunityId) {
- const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
- return `${instanceUrl}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}?fields=${fields}`
- }
- const limit = params.limit ? Number.parseInt(params.limit) : 100
- const fields = params.fields || 'Id,Name,AccountId,Amount,StageName,CloseDate,Probability'
- const orderBy = params.orderBy || 'CloseDate DESC'
- const query = `SELECT ${fields} FROM Opportunity ORDER BY ${orderBy} LIMIT ${limit}`
- return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok)
- throw new Error(data[0]?.message || data.message || 'Failed to fetch opportunities')
- if (params.opportunityId) {
- return {
- success: true,
- output: { opportunity: data, metadata: { operation: 'get_opportunities' }, success: true },
- }
- }
- const opportunities = data.records || []
- return {
- success: true,
- output: {
- opportunities,
- paging: {
- nextRecordsUrl: data.nextRecordsUrl,
- totalSize: data.totalSize || opportunities.length,
- done: data.done !== false,
- },
- metadata: {
- operation: 'get_opportunities',
- totalReturned: opportunities.length,
- hasMore: !data.done,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Opportunity data' },
- },
-}
-
-// Create Opportunity
-export const salesforceCreateOpportunityTool: ToolConfig = {
- id: 'salesforce_create_opportunity',
- name: 'Create Opportunity in Salesforce',
- description: 'Create a new opportunity',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- name: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Opportunity name (required)',
- },
- stageName: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Stage name (required)',
- },
- closeDate: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Close date YYYY-MM-DD (required)',
- },
- accountId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Account ID',
- },
- amount: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Amount (number)',
- },
- probability: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Probability (0-100)',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity`,
- method: 'POST',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {
- Name: params.name,
- StageName: params.stageName,
- CloseDate: params.closeDate,
- }
- if (params.accountId) body.AccountId = params.accountId
- if (params.amount) body.Amount = Number.parseFloat(params.amount)
- if (params.probability) body.Probability = Number.parseInt(params.probability)
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok)
- throw new Error(data[0]?.message || data.message || 'Failed to create opportunity')
- return {
- success: true,
- output: {
- id: data.id,
- success: data.success,
- created: true,
- metadata: { operation: 'create_opportunity' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Created opportunity' },
- },
-}
-
-// Update Opportunity
-export const salesforceUpdateOpportunityTool: ToolConfig = {
- id: 'salesforce_update_opportunity',
- name: 'Update Opportunity in Salesforce',
- description: 'Update an existing opportunity',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- opportunityId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Opportunity ID (required)',
- },
- name: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Opportunity name',
- },
- stageName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Stage name',
- },
- closeDate: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Close date YYYY-MM-DD',
- },
- accountId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Account ID',
- },
- amount: { type: 'string', required: false, visibility: 'user-only', description: 'Amount' },
- probability: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Probability (0-100)',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
- method: 'PATCH',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {}
- if (params.name) body.Name = params.name
- if (params.stageName) body.StageName = params.stageName
- if (params.closeDate) body.CloseDate = params.closeDate
- if (params.accountId) body.AccountId = params.accountId
- if (params.amount) body.Amount = Number.parseFloat(params.amount)
- if (params.probability) body.Probability = Number.parseInt(params.probability)
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json()
- throw new Error(data[0]?.message || data.message || 'Failed to update opportunity')
- }
- return {
- success: true,
- output: {
- id: params.opportunityId,
- updated: true,
- metadata: { operation: 'update_opportunity' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Updated opportunity' },
- },
-}
-
-// Delete Opportunity
-export const salesforceDeleteOpportunityTool: ToolConfig = {
- id: 'salesforce_delete_opportunity',
- name: 'Delete Opportunity from Salesforce',
- description: 'Delete an opportunity',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- opportunityId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Opportunity ID (required)',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
- method: 'DELETE',
- headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json().catch(() => ({}))
- throw new Error(data[0]?.message || data.message || 'Failed to delete opportunity')
- }
- return {
- success: true,
- output: {
- id: params.opportunityId,
- deleted: true,
- metadata: { operation: 'delete_opportunity' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Deleted opportunity' },
- },
-}
diff --git a/apps/sim/tools/salesforce/query.ts b/apps/sim/tools/salesforce/query.ts
index fbf2954236..29c5a9032b 100644
--- a/apps/sim/tools/salesforce/query.ts
+++ b/apps/sim/tools/salesforce/query.ts
@@ -11,16 +11,38 @@ export interface SalesforceQueryParams {
query: string
}
+export interface SalesforceQueryResponse {
+ success: boolean
+ output: {
+ records: any[]
+ totalSize: number
+ done: boolean
+ nextRecordsUrl?: string
+ query: string
+ metadata: {
+ operation: 'query'
+ totalReturned: number
+ hasMore: boolean
+ }
+ success: boolean
+ }
+}
+
/**
* Execute a custom SOQL query
* @see https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm
*/
-export const salesforceQueryTool: ToolConfig = {
+export const salesforceQueryTool: ToolConfig = {
id: 'salesforce_query',
name: 'Run SOQL Query in Salesforce',
description: 'Execute a custom SOQL query to retrieve data from Salesforce',
version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
params: {
accessToken: { type: 'string', required: true, visibility: 'hidden' },
idToken: { type: 'string', required: false, visibility: 'hidden' },
@@ -32,6 +54,7 @@ export const salesforceQueryTool: ToolConfig = {
description: 'SOQL query to execute (e.g., SELECT Id, Name FROM Account LIMIT 10)',
},
},
+
request: {
url: (params) => {
if (!params.query || params.query.trim() === '') {
@@ -49,7 +72,8 @@ export const salesforceQueryTool: ToolConfig = {
'Content-Type': 'application/json',
}),
},
- transformResponse: async (response, params) => {
+
+ transformResponse: async (response, params?) => {
const data = await response.json()
if (!response.ok) {
const errorMessage = extractErrorMessage(
@@ -70,7 +94,7 @@ export const salesforceQueryTool: ToolConfig = {
totalSize: data.totalSize || records.length,
done: data.done !== false,
nextRecordsUrl: data.nextRecordsUrl,
- query: params.query,
+ query: params?.query || '',
metadata: {
operation: 'query',
totalReturned: records.length,
@@ -80,6 +104,7 @@ export const salesforceQueryTool: ToolConfig = {
},
}
},
+
outputs: {
success: { type: 'boolean', description: 'Success status' },
output: {
@@ -97,270 +122,3 @@ export const salesforceQueryTool: ToolConfig = {
},
},
}
-
-export interface SalesforceQueryMoreParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- nextRecordsUrl: string
-}
-
-/**
- * Retrieve additional query results using the nextRecordsUrl
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm
- */
-export const salesforceQueryMoreTool: ToolConfig = {
- id: 'salesforce_query_more',
- name: 'Get More Query Results from Salesforce',
- description: 'Retrieve additional query results using the nextRecordsUrl from a previous query',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- nextRecordsUrl: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'The nextRecordsUrl from a previous query response',
- },
- },
- request: {
- url: (params) => {
- if (!params.nextRecordsUrl || params.nextRecordsUrl.trim() === '') {
- throw new Error(
- 'Next Records URL is required. This should be the nextRecordsUrl value from a previous query response.'
- )
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- // nextRecordsUrl is typically a relative path like /services/data/v59.0/query/01g...
- const nextUrl = params.nextRecordsUrl.startsWith('/')
- ? params.nextRecordsUrl
- : `/${params.nextRecordsUrl}`
- return `${instanceUrl}${nextUrl}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- 'Failed to get more query results'
- )
- logger.error('Failed to get more query results', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- const records = data.records || []
-
- return {
- success: true,
- output: {
- records,
- totalSize: data.totalSize || records.length,
- done: data.done !== false,
- nextRecordsUrl: data.nextRecordsUrl,
- metadata: {
- operation: 'query_more',
- totalReturned: records.length,
- hasMore: !data.done,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Query results',
- properties: {
- records: { type: 'array', description: 'Array of record objects' },
- totalSize: { type: 'number', description: 'Total number of records matching query' },
- done: { type: 'boolean', description: 'Whether all records have been returned' },
- nextRecordsUrl: { type: 'string', description: 'URL to fetch next batch of records' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-export interface SalesforceDescribeObjectParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- objectName: string
-}
-
-/**
- * Describe a Salesforce object to get its metadata/fields
- * Useful for discovering available fields for queries
- */
-export const salesforceDescribeObjectTool: ToolConfig = {
- id: 'salesforce_describe_object',
- name: 'Describe Salesforce Object',
- description: 'Get metadata and field information for a Salesforce object',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- objectName: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'API name of the object (e.g., Account, Contact, Lead, Custom_Object__c)',
- },
- },
- request: {
- url: (params) => {
- if (!params.objectName || params.objectName.trim() === '') {
- throw new Error(
- 'Object Name is required. Please provide a valid Salesforce object API name (e.g., Account, Contact, Lead).'
- )
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/sobjects/${params.objectName}/describe`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- `Failed to describe object: ${params.objectName}`
- )
- logger.error('Failed to describe object', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- objectName: params.objectName,
- label: data.label,
- labelPlural: data.labelPlural,
- fields: data.fields,
- keyPrefix: data.keyPrefix,
- queryable: data.queryable,
- createable: data.createable,
- updateable: data.updateable,
- deletable: data.deletable,
- childRelationships: data.childRelationships,
- recordTypeInfos: data.recordTypeInfos,
- metadata: {
- operation: 'describe_object',
- fieldCount: data.fields?.length || 0,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Object metadata',
- properties: {
- objectName: { type: 'string', description: 'API name of the object' },
- label: { type: 'string', description: 'Display label' },
- labelPlural: { type: 'string', description: 'Plural display label' },
- fields: { type: 'array', description: 'Array of field definitions' },
- keyPrefix: { type: 'string', description: 'ID prefix for this object type' },
- queryable: { type: 'boolean', description: 'Whether object can be queried' },
- createable: { type: 'boolean', description: 'Whether records can be created' },
- updateable: { type: 'boolean', description: 'Whether records can be updated' },
- deletable: { type: 'boolean', description: 'Whether records can be deleted' },
- childRelationships: { type: 'array', description: 'Child relationship definitions' },
- recordTypeInfos: { type: 'array', description: 'Record type information' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-/**
- * List all available Salesforce objects
- * Useful for discovering what objects are available
- */
-export const salesforceListObjectsTool: ToolConfig = {
- id: 'salesforce_list_objects',
- name: 'List Salesforce Objects',
- description: 'Get a list of all available Salesforce objects',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/sobjects`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- 'Failed to list Salesforce objects'
- )
- logger.error('Failed to list objects', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- const objects = data.sobjects || []
-
- return {
- success: true,
- output: {
- objects,
- encoding: data.encoding,
- maxBatchSize: data.maxBatchSize,
- metadata: {
- operation: 'list_objects',
- totalReturned: objects.length,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Objects list',
- properties: {
- objects: { type: 'array', description: 'Array of available Salesforce objects' },
- encoding: { type: 'string', description: 'Encoding used' },
- maxBatchSize: { type: 'number', description: 'Maximum batch size' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
diff --git a/apps/sim/tools/salesforce/query_more.ts b/apps/sim/tools/salesforce/query_more.ts
new file mode 100644
index 0000000000..bae4b01474
--- /dev/null
+++ b/apps/sim/tools/salesforce/query_more.ts
@@ -0,0 +1,127 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceQuery')
+
+export interface SalesforceQueryMoreParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ nextRecordsUrl: string
+}
+
+export interface SalesforceQueryMoreResponse {
+ success: boolean
+ output: {
+ records: any[]
+ totalSize: number
+ done: boolean
+ nextRecordsUrl?: string
+ metadata: {
+ operation: 'query_more'
+ totalReturned: number
+ hasMore: boolean
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Retrieve additional query results using the nextRecordsUrl
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm
+ */
+export const salesforceQueryMoreTool: ToolConfig<
+ SalesforceQueryMoreParams,
+ SalesforceQueryMoreResponse
+> = {
+ id: 'salesforce_query_more',
+ name: 'Get More Query Results from Salesforce',
+ description: 'Retrieve additional query results using the nextRecordsUrl from a previous query',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ nextRecordsUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'The nextRecordsUrl from a previous query response',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.nextRecordsUrl || params.nextRecordsUrl.trim() === '') {
+ throw new Error(
+ 'Next Records URL is required. This should be the nextRecordsUrl value from a previous query response.'
+ )
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ // nextRecordsUrl is typically a relative path like /services/data/v59.0/query/01g...
+ const nextUrl = params.nextRecordsUrl.startsWith('/')
+ ? params.nextRecordsUrl
+ : `/${params.nextRecordsUrl}`
+ return `${instanceUrl}${nextUrl}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ },
+
+ transformResponse: async (response) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ 'Failed to get more query results'
+ )
+ logger.error('Failed to get more query results', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ const records = data.records || []
+
+ return {
+ success: true,
+ output: {
+ records,
+ totalSize: data.totalSize || records.length,
+ done: data.done !== false,
+ nextRecordsUrl: data.nextRecordsUrl,
+ metadata: {
+ operation: 'query_more',
+ totalReturned: records.length,
+ hasMore: !data.done,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Query results',
+ properties: {
+ records: { type: 'array', description: 'Array of record objects' },
+ totalSize: { type: 'number', description: 'Total number of records matching query' },
+ done: { type: 'boolean', description: 'Whether all records have been returned' },
+ nextRecordsUrl: { type: 'string', description: 'URL to fetch next batch of records' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/refresh_dashboard.ts b/apps/sim/tools/salesforce/refresh_dashboard.ts
new file mode 100644
index 0000000000..6fb355cafd
--- /dev/null
+++ b/apps/sim/tools/salesforce/refresh_dashboard.ts
@@ -0,0 +1,120 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceDashboards')
+
+export interface SalesforceRefreshDashboardParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ dashboardId: string
+}
+
+export interface SalesforceRefreshDashboardResponse {
+ success: boolean
+ output: {
+ dashboard: any
+ dashboardId: string
+ components: any[]
+ status?: any
+ metadata: {
+ operation: 'refresh_dashboard'
+ dashboardName?: string
+ refreshDate?: string
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Refresh a dashboard to get latest data
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_refresh_dashboard.htm
+ */
+export const salesforceRefreshDashboardTool: ToolConfig<
+ SalesforceRefreshDashboardParams,
+ SalesforceRefreshDashboardResponse
+> = {
+ id: 'salesforce_refresh_dashboard',
+ name: 'Refresh Dashboard in Salesforce',
+ description: 'Refresh a dashboard to get the latest data',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ dashboardId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Dashboard ID (required)',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.dashboardId || params.dashboardId.trim() === '') {
+ throw new Error('Dashboard ID is required. Please provide a valid Salesforce Dashboard ID.')
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/analytics/dashboards/${params.dashboardId}`
+ },
+ method: 'PUT',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: () => ({}),
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ `Failed to refresh dashboard ID: ${params?.dashboardId}`
+ )
+ logger.error('Failed to refresh dashboard', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ dashboard: data,
+ dashboardId: params?.dashboardId || '',
+ components: data.componentData || [],
+ status: data.status,
+ metadata: {
+ operation: 'refresh_dashboard',
+ dashboardName: data.name,
+ refreshDate: data.refreshDate,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Refreshed dashboard data',
+ properties: {
+ dashboard: { type: 'object', description: 'Dashboard details' },
+ dashboardId: { type: 'string', description: 'Dashboard ID' },
+ components: { type: 'array', description: 'Dashboard component data' },
+ status: { type: 'object', description: 'Dashboard status' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/reports.ts b/apps/sim/tools/salesforce/reports.ts
deleted file mode 100644
index 300f43ff5c..0000000000
--- a/apps/sim/tools/salesforce/reports.ts
+++ /dev/null
@@ -1,378 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-import { extractErrorMessage, getInstanceUrl } from './utils'
-
-const logger = createLogger('SalesforceReports')
-
-export interface SalesforceListReportsParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- folderName?: string
- searchTerm?: string
-}
-
-/**
- * List all reports accessible by the current user
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportlist.htm
- */
-export const salesforceListReportsTool: ToolConfig = {
- id: 'salesforce_list_reports',
- name: 'List Reports from Salesforce',
- description: 'Get a list of reports accessible by the current user',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- folderName: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Filter by folder name',
- },
- searchTerm: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Search term to filter reports by name',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/reports`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- 'Failed to list reports from Salesforce'
- )
- logger.error('Failed to list reports', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- let reports = data || []
-
- // Filter by folder name if provided
- if (params.folderName) {
- reports = reports.filter((report: any) =>
- report.folderName?.toLowerCase().includes(params.folderName.toLowerCase())
- )
- }
-
- // Filter by search term if provided
- if (params.searchTerm) {
- reports = reports.filter(
- (report: any) =>
- report.name?.toLowerCase().includes(params.searchTerm.toLowerCase()) ||
- report.description?.toLowerCase().includes(params.searchTerm?.toLowerCase())
- )
- }
-
- return {
- success: true,
- output: {
- reports,
- metadata: {
- operation: 'list_reports',
- totalReturned: reports.length,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Reports data',
- properties: {
- reports: { type: 'array', description: 'Array of report objects' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-export interface SalesforceGetReportParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- reportId: string
-}
-
-/**
- * Get metadata for a specific report
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportmetadata.htm
- */
-export const salesforceGetReportTool: ToolConfig = {
- id: 'salesforce_get_report',
- name: 'Get Report Metadata from Salesforce',
- description: 'Get metadata and describe information for a specific report',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- reportId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Report ID (required)',
- },
- },
- request: {
- url: (params) => {
- if (!params.reportId || params.reportId.trim() === '') {
- throw new Error('Report ID is required. Please provide a valid Salesforce Report ID.')
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/reports/${params.reportId}/describe`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- `Failed to get report metadata for report ID: ${params.reportId}`
- )
- logger.error('Failed to get report metadata', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- report: data,
- reportId: params.reportId,
- metadata: {
- operation: 'get_report',
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Report metadata',
- properties: {
- report: { type: 'object', description: 'Report metadata object' },
- reportId: { type: 'string', description: 'Report ID' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-export interface SalesforceRunReportParams {
- accessToken: string
- idToken?: string
- instanceUrl?: string
- reportId: string
- includeDetails?: string
- filters?: string
-}
-
-/**
- * Run a report and return the results
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportdata.htm
- */
-export const salesforceRunReportTool: ToolConfig = {
- id: 'salesforce_run_report',
- name: 'Run Report in Salesforce',
- description: 'Execute a report and retrieve the results',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- reportId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Report ID (required)',
- },
- includeDetails: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Include detail rows (true/false, default: true)',
- },
- filters: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'JSON string of report filters to apply',
- },
- },
- request: {
- url: (params) => {
- if (!params.reportId || params.reportId.trim() === '') {
- throw new Error('Report ID is required. Please provide a valid Salesforce Report ID.')
- }
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- const includeDetails = params.includeDetails !== 'false'
- return `${instanceUrl}/services/data/v59.0/analytics/reports/${params.reportId}?includeDetails=${includeDetails}`
- },
- // Use GET for simple report runs, POST only when filters are provided
- method: (params) => (params.filters ? 'POST' : 'GET'),
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- // Only send a body when filters are provided (POST request)
- if (params.filters) {
- try {
- const filters = JSON.parse(params.filters)
- return { reportMetadata: { reportFilters: filters } }
- } catch (e) {
- throw new Error(
- `Invalid report filters JSON: ${e instanceof Error ? e.message : 'Parse error'}. Please provide a valid JSON array of filter objects.`
- )
- }
- }
- // Return undefined for GET requests (no body)
- return undefined as any
- },
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- `Failed to run report ID: ${params.reportId}`
- )
- logger.error('Failed to run report', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- reportId: params.reportId,
- reportMetadata: data.reportMetadata,
- reportExtendedMetadata: data.reportExtendedMetadata,
- factMap: data.factMap,
- groupingsDown: data.groupingsDown,
- groupingsAcross: data.groupingsAcross,
- hasDetailRows: data.hasDetailRows,
- allData: data.allData,
- metadata: {
- operation: 'run_report',
- reportName: data.reportMetadata?.name,
- reportFormat: data.reportMetadata?.reportFormat,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Report results',
- properties: {
- reportId: { type: 'string', description: 'Report ID' },
- reportMetadata: { type: 'object', description: 'Report metadata' },
- reportExtendedMetadata: { type: 'object', description: 'Extended metadata' },
- factMap: { type: 'object', description: 'Report data organized by groupings' },
- groupingsDown: { type: 'object', description: 'Row groupings' },
- groupingsAcross: { type: 'object', description: 'Column groupings' },
- hasDetailRows: { type: 'boolean', description: 'Whether report has detail rows' },
- allData: { type: 'boolean', description: 'Whether all data is returned' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
-
-/**
- * Get list of available report types
- * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_list_reporttypes.htm
- */
-export const salesforceListReportTypesTool: ToolConfig = {
- id: 'salesforce_list_report_types',
- name: 'List Report Types from Salesforce',
- description: 'Get a list of available report types',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- return `${instanceUrl}/services/data/v59.0/analytics/reportTypes`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) {
- const errorMessage = extractErrorMessage(
- data,
- response.status,
- 'Failed to list report types from Salesforce'
- )
- logger.error('Failed to list report types', { data, status: response.status })
- throw new Error(errorMessage)
- }
-
- return {
- success: true,
- output: {
- reportTypes: data,
- metadata: {
- operation: 'list_report_types',
- totalReturned: Array.isArray(data) ? data.length : 0,
- },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success status' },
- output: {
- type: 'object',
- description: 'Report types data',
- properties: {
- reportTypes: { type: 'array', description: 'Array of report type objects' },
- metadata: { type: 'object', description: 'Operation metadata' },
- success: { type: 'boolean', description: 'Operation success status' },
- },
- },
- },
-}
diff --git a/apps/sim/tools/salesforce/run_report.ts b/apps/sim/tools/salesforce/run_report.ts
new file mode 100644
index 0000000000..cca98d1ed3
--- /dev/null
+++ b/apps/sim/tools/salesforce/run_report.ts
@@ -0,0 +1,162 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { extractErrorMessage, getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceReports')
+
+export interface SalesforceRunReportParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ reportId: string
+ includeDetails?: string
+ filters?: string
+}
+
+export interface SalesforceRunReportResponse {
+ success: boolean
+ output: {
+ reportId: string
+ reportMetadata?: any
+ reportExtendedMetadata?: any
+ factMap?: any
+ groupingsDown?: any
+ groupingsAcross?: any
+ hasDetailRows?: boolean
+ allData?: boolean
+ metadata: {
+ operation: 'run_report'
+ reportName?: string
+ reportFormat?: string
+ }
+ success: boolean
+ }
+}
+
+/**
+ * Run a report and return the results
+ * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportdata.htm
+ */
+export const salesforceRunReportTool: ToolConfig<
+ SalesforceRunReportParams,
+ SalesforceRunReportResponse
+> = {
+ id: 'salesforce_run_report',
+ name: 'Run Report in Salesforce',
+ description: 'Execute a report and retrieve the results',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ reportId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Report ID (required)',
+ },
+ includeDetails: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Include detail rows (true/false, default: true)',
+ },
+ filters: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'JSON string of report filters to apply',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ if (!params.reportId || params.reportId.trim() === '') {
+ throw new Error('Report ID is required. Please provide a valid Salesforce Report ID.')
+ }
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ const includeDetails = params.includeDetails !== 'false'
+ return `${instanceUrl}/services/data/v59.0/analytics/reports/${params.reportId}?includeDetails=${includeDetails}`
+ },
+ // Use GET for simple report runs, POST only when filters are provided
+ method: (params) => (params.filters ? 'POST' : 'GET'),
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ // Only send a body when filters are provided (POST request)
+ if (params.filters) {
+ try {
+ const filters = JSON.parse(params.filters)
+ return { reportMetadata: { reportFilters: filters } }
+ } catch (e) {
+ throw new Error(
+ `Invalid report filters JSON: ${e instanceof Error ? e.message : 'Parse error'}. Please provide a valid JSON array of filter objects.`
+ )
+ }
+ }
+ // Return undefined for GET requests (no body)
+ return undefined as any
+ },
+ },
+
+ transformResponse: async (response, params?) => {
+ const data = await response.json()
+ if (!response.ok) {
+ const errorMessage = extractErrorMessage(
+ data,
+ response.status,
+ `Failed to run report ID: ${params?.reportId}`
+ )
+ logger.error('Failed to run report', { data, status: response.status })
+ throw new Error(errorMessage)
+ }
+
+ return {
+ success: true,
+ output: {
+ reportId: params?.reportId || '',
+ reportMetadata: data.reportMetadata,
+ reportExtendedMetadata: data.reportExtendedMetadata,
+ factMap: data.factMap,
+ groupingsDown: data.groupingsDown,
+ groupingsAcross: data.groupingsAcross,
+ hasDetailRows: data.hasDetailRows,
+ allData: data.allData,
+ metadata: {
+ operation: 'run_report',
+ reportName: data.reportMetadata?.name,
+ reportFormat: data.reportMetadata?.reportFormat,
+ },
+ success: true,
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success status' },
+ output: {
+ type: 'object',
+ description: 'Report results',
+ properties: {
+ reportId: { type: 'string', description: 'Report ID' },
+ reportMetadata: { type: 'object', description: 'Report metadata' },
+ reportExtendedMetadata: { type: 'object', description: 'Extended metadata' },
+ factMap: { type: 'object', description: 'Report data organized by groupings' },
+ groupingsDown: { type: 'object', description: 'Row groupings' },
+ groupingsAcross: { type: 'object', description: 'Column groupings' },
+ hasDetailRows: { type: 'boolean', description: 'Whether report has detail rows' },
+ allData: { type: 'boolean', description: 'Whether all data is returned' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ success: { type: 'boolean', description: 'Operation success status' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/tasks.ts b/apps/sim/tools/salesforce/tasks.ts
deleted file mode 100644
index 3f3d9cedc8..0000000000
--- a/apps/sim/tools/salesforce/tasks.ts
+++ /dev/null
@@ -1,321 +0,0 @@
-import { createLogger } from '@/lib/logs/console/logger'
-import type { ToolConfig } from '@/tools/types'
-
-const logger = createLogger('SalesforceTasks')
-
-function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
- if (instanceUrl) return instanceUrl
- if (idToken) {
- try {
- const base64Url = idToken.split('.')[1]
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
- const jsonPayload = decodeURIComponent(
- atob(base64)
- .split('')
- .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
- .join('')
- )
- const decoded = JSON.parse(jsonPayload)
- if (decoded.profile) {
- const match = decoded.profile.match(/^(https:\/\/[^/]+)/)
- if (match) return match[1]
- } else if (decoded.sub) {
- const match = decoded.sub.match(/^(https:\/\/[^/]+)/)
- if (match && match[1] !== 'https://login.salesforce.com') return match[1]
- }
- } catch (error) {
- logger.error('Failed to decode Salesforce idToken', { error })
- }
- }
- throw new Error('Salesforce instance URL is required but not provided')
-}
-
-// Get Tasks
-export const salesforceGetTasksTool: ToolConfig = {
- id: 'salesforce_get_tasks',
- name: 'Get Tasks from Salesforce',
- description: 'Get task(s) from Salesforce',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- taskId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Task ID (optional)',
- },
- limit: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Max results (default: 100)',
- },
- fields: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Comma-separated fields',
- },
- orderBy: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Order by field',
- },
- },
- request: {
- url: (params) => {
- const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
- if (params.taskId) {
- const fields =
- params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
- return `${instanceUrl}/services/data/v59.0/sobjects/Task/${params.taskId}?fields=${fields}`
- }
- const limit = params.limit ? Number.parseInt(params.limit) : 100
- const fields = params.fields || 'Id,Subject,Status,Priority,ActivityDate,WhoId,WhatId,OwnerId'
- const orderBy = params.orderBy || 'ActivityDate DESC'
- const query = `SELECT ${fields} FROM Task ORDER BY ${orderBy} LIMIT ${limit}`
- return `${instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(query)}`
- },
- method: 'GET',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- },
- transformResponse: async (response, params) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to fetch tasks')
- if (params.taskId) {
- return {
- success: true,
- output: { task: data, metadata: { operation: 'get_tasks' }, success: true },
- }
- }
- const tasks = data.records || []
- return {
- success: true,
- output: {
- tasks,
- paging: {
- nextRecordsUrl: data.nextRecordsUrl,
- totalSize: data.totalSize || tasks.length,
- done: data.done !== false,
- },
- metadata: { operation: 'get_tasks', totalReturned: tasks.length, hasMore: !data.done },
- success: true,
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Task data' },
- },
-}
-
-// Create Task
-export const salesforceCreateTaskTool: ToolConfig = {
- id: 'salesforce_create_task',
- name: 'Create Task in Salesforce',
- description: 'Create a new task',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- subject: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Task subject (required)',
- },
- status: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Status (e.g., Not Started, In Progress, Completed)',
- },
- priority: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Priority (e.g., Low, Normal, High)',
- },
- activityDate: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Due date YYYY-MM-DD',
- },
- whoId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Related Contact/Lead ID',
- },
- whatId: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Related Account/Opportunity ID',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task`,
- method: 'POST',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = { Subject: params.subject }
- if (params.status) body.Status = params.status
- if (params.priority) body.Priority = params.priority
- if (params.activityDate) body.ActivityDate = params.activityDate
- if (params.whoId) body.WhoId = params.whoId
- if (params.whatId) body.WhatId = params.whatId
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response) => {
- const data = await response.json()
- if (!response.ok) throw new Error(data[0]?.message || data.message || 'Failed to create task')
- return {
- success: true,
- output: {
- id: data.id,
- success: data.success,
- created: true,
- metadata: { operation: 'create_task' },
- },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Created task' },
- },
-}
-
-// Update Task
-export const salesforceUpdateTaskTool: ToolConfig = {
- id: 'salesforce_update_task',
- name: 'Update Task in Salesforce',
- description: 'Update an existing task',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- taskId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Task ID (required)',
- },
- subject: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Task subject',
- },
- status: { type: 'string', required: false, visibility: 'user-only', description: 'Status' },
- priority: { type: 'string', required: false, visibility: 'user-only', description: 'Priority' },
- activityDate: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Due date YYYY-MM-DD',
- },
- description: {
- type: 'string',
- required: false,
- visibility: 'user-only',
- description: 'Description',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
- method: 'PATCH',
- headers: (params) => ({
- Authorization: `Bearer ${params.accessToken}`,
- 'Content-Type': 'application/json',
- }),
- body: (params) => {
- const body: Record = {}
- if (params.subject) body.Subject = params.subject
- if (params.status) body.Status = params.status
- if (params.priority) body.Priority = params.priority
- if (params.activityDate) body.ActivityDate = params.activityDate
- if (params.description) body.Description = params.description
- return body
- },
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json()
- throw new Error(data[0]?.message || data.message || 'Failed to update task')
- }
- return {
- success: true,
- output: { id: params.taskId, updated: true, metadata: { operation: 'update_task' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Updated task' },
- },
-}
-
-// Delete Task
-export const salesforceDeleteTaskTool: ToolConfig = {
- id: 'salesforce_delete_task',
- name: 'Delete Task from Salesforce',
- description: 'Delete a task',
- version: '1.0.0',
- oauth: { required: true, provider: 'salesforce' },
- params: {
- accessToken: { type: 'string', required: true, visibility: 'hidden' },
- idToken: { type: 'string', required: false, visibility: 'hidden' },
- instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
- taskId: {
- type: 'string',
- required: true,
- visibility: 'user-only',
- description: 'Task ID (required)',
- },
- },
- request: {
- url: (params) =>
- `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
- method: 'DELETE',
- headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
- },
- transformResponse: async (response, params) => {
- if (!response.ok) {
- const data = await response.json().catch(() => ({}))
- throw new Error(data[0]?.message || data.message || 'Failed to delete task')
- }
- return {
- success: true,
- output: { id: params.taskId, deleted: true, metadata: { operation: 'delete_task' } },
- }
- },
- outputs: {
- success: { type: 'boolean', description: 'Success' },
- output: { type: 'object', description: 'Deleted task' },
- },
-}
diff --git a/apps/sim/tools/salesforce/update_case.ts b/apps/sim/tools/salesforce/update_case.ts
new file mode 100644
index 0000000000..67f5bdf6cb
--- /dev/null
+++ b/apps/sim/tools/salesforce/update_case.ts
@@ -0,0 +1,125 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceUpdateCaseParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ caseId: string
+ subject?: string
+ status?: string
+ priority?: string
+ description?: string
+}
+
+export interface SalesforceUpdateCaseResponse {
+ success: boolean
+ output: {
+ id: string
+ updated: boolean
+ metadata: {
+ operation: 'update_case'
+ }
+ }
+}
+
+export const salesforceUpdateCaseTool: ToolConfig<
+ SalesforceUpdateCaseParams,
+ SalesforceUpdateCaseResponse
+> = {
+ id: 'salesforce_update_case',
+ name: 'Update Case in Salesforce',
+ description: 'Update an existing case',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ caseId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Case ID (required)',
+ },
+ subject: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Case subject',
+ },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Status',
+ },
+ priority: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Priority',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Case/${params.caseId}`,
+ method: 'PATCH',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {}
+ if (params.subject) body.Subject = params.subject
+ if (params.status) body.Status = params.status
+ if (params.priority) body.Priority = params.priority
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json()
+ throw new Error(data[0]?.message || data.message || 'Failed to update case')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.caseId || '',
+ updated: true,
+ metadata: { operation: 'update_case' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Updated case' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/update_contact.ts b/apps/sim/tools/salesforce/update_contact.ts
new file mode 100644
index 0000000000..6d64c705fc
--- /dev/null
+++ b/apps/sim/tools/salesforce/update_contact.ts
@@ -0,0 +1,192 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+const logger = createLogger('SalesforceContacts')
+
+export interface SalesforceUpdateContactParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ contactId: string
+ lastName?: string
+ firstName?: string
+ email?: string
+ phone?: string
+ accountId?: string
+ title?: string
+ department?: string
+ mailingStreet?: string
+ mailingCity?: string
+ mailingState?: string
+ mailingPostalCode?: string
+ mailingCountry?: string
+ description?: string
+}
+
+export interface SalesforceUpdateContactResponse {
+ success: boolean
+ output: {
+ id: string
+ updated: boolean
+ metadata: { operation: 'update_contact' }
+ }
+}
+
+export const salesforceUpdateContactTool: ToolConfig<
+ SalesforceUpdateContactParams,
+ SalesforceUpdateContactResponse
+> = {
+ id: 'salesforce_update_contact',
+ name: 'Update Contact in Salesforce',
+ description: 'Update an existing contact in Salesforce CRM',
+ version: '1.0.0',
+
+ oauth: { required: true, provider: 'salesforce' },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ contactId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Contact ID to update (required)',
+ },
+ lastName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Last name',
+ },
+ firstName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'First name',
+ },
+ email: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Email address',
+ },
+ phone: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Phone number',
+ },
+ accountId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Account ID to associate with',
+ },
+ title: { type: 'string', required: false, visibility: 'user-only', description: 'Job title' },
+ department: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Department',
+ },
+ mailingStreet: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing street',
+ },
+ mailingCity: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing city',
+ },
+ mailingState: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing state',
+ },
+ mailingPostalCode: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing postal code',
+ },
+ mailingCountry: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Mailing country',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const instanceUrl = getInstanceUrl(params.idToken, params.instanceUrl)
+ return `${instanceUrl}/services/data/v59.0/sobjects/Contact/${params.contactId}`
+ },
+ method: 'PATCH',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {}
+
+ if (params.lastName) body.LastName = params.lastName
+ if (params.firstName) body.FirstName = params.firstName
+ if (params.email) body.Email = params.email
+ if (params.phone) body.Phone = params.phone
+ if (params.accountId) body.AccountId = params.accountId
+ if (params.title) body.Title = params.title
+ if (params.department) body.Department = params.department
+ if (params.mailingStreet) body.MailingStreet = params.mailingStreet
+ if (params.mailingCity) body.MailingCity = params.mailingCity
+ if (params.mailingState) body.MailingState = params.mailingState
+ if (params.mailingPostalCode) body.MailingPostalCode = params.mailingPostalCode
+ if (params.mailingCountry) body.MailingCountry = params.mailingCountry
+ if (params.description) body.Description = params.description
+
+ return body
+ },
+ },
+
+ transformResponse: async (response: Response, params?) => {
+ if (!response.ok) {
+ const data = await response.json()
+ logger.error('Salesforce API request failed', { data, status: response.status })
+ throw new Error(data[0]?.message || data.message || 'Failed to update contact in Salesforce')
+ }
+
+ return {
+ success: true,
+ output: {
+ id: params?.contactId || '',
+ updated: true,
+ metadata: { operation: 'update_contact' as const },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Operation success status' },
+ output: {
+ type: 'object',
+ description: 'Updated contact data',
+ properties: {
+ id: { type: 'string', description: 'Updated contact ID' },
+ updated: { type: 'boolean', description: 'Whether contact was updated' },
+ metadata: { type: 'object', description: 'Operation metadata' },
+ },
+ },
+ },
+}
diff --git a/apps/sim/tools/salesforce/update_lead.ts b/apps/sim/tools/salesforce/update_lead.ts
new file mode 100644
index 0000000000..612189a7b5
--- /dev/null
+++ b/apps/sim/tools/salesforce/update_lead.ts
@@ -0,0 +1,133 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceUpdateLeadParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ leadId: string
+ lastName?: string
+ company?: string
+ firstName?: string
+ email?: string
+ phone?: string
+ status?: string
+ leadSource?: string
+ title?: string
+ description?: string
+}
+
+export interface SalesforceUpdateLeadResponse {
+ success: boolean
+ output: {
+ id: string
+ updated: boolean
+ metadata: {
+ operation: 'update_lead'
+ }
+ }
+}
+
+export const salesforceUpdateLeadTool: ToolConfig<
+ SalesforceUpdateLeadParams,
+ SalesforceUpdateLeadResponse
+> = {
+ id: 'salesforce_update_lead',
+ name: 'Update Lead in Salesforce',
+ description: 'Update an existing lead',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ leadId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Lead ID (required)',
+ },
+ lastName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Last name',
+ },
+ company: { type: 'string', required: false, visibility: 'user-only', description: 'Company' },
+ firstName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'First name',
+ },
+ email: { type: 'string', required: false, visibility: 'user-only', description: 'Email' },
+ phone: { type: 'string', required: false, visibility: 'user-only', description: 'Phone' },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Lead status',
+ },
+ leadSource: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Lead source',
+ },
+ title: { type: 'string', required: false, visibility: 'user-only', description: 'Title' },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Lead/${params.leadId}`,
+ method: 'PATCH',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {}
+ if (params.lastName) body.LastName = params.lastName
+ if (params.company) body.Company = params.company
+ if (params.firstName) body.FirstName = params.firstName
+ if (params.email) body.Email = params.email
+ if (params.phone) body.Phone = params.phone
+ if (params.status) body.Status = params.status
+ if (params.leadSource) body.LeadSource = params.leadSource
+ if (params.title) body.Title = params.title
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json()
+ throw new Error(data[0]?.message || data.message || 'Failed to update lead')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.leadId || '',
+ updated: true,
+ metadata: { operation: 'update_lead' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Updated lead' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/update_opportunity.ts b/apps/sim/tools/salesforce/update_opportunity.ts
new file mode 100644
index 0000000000..70b2dce0d2
--- /dev/null
+++ b/apps/sim/tools/salesforce/update_opportunity.ts
@@ -0,0 +1,132 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceUpdateOpportunityParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ opportunityId: string
+ name?: string
+ stageName?: string
+ closeDate?: string
+ accountId?: string
+ amount?: string
+ probability?: string
+ description?: string
+}
+
+export interface SalesforceUpdateOpportunityResponse {
+ success: boolean
+ output: {
+ id: string
+ updated: boolean
+ metadata: {
+ operation: 'update_opportunity'
+ }
+ }
+}
+
+export const salesforceUpdateOpportunityTool: ToolConfig<
+ SalesforceUpdateOpportunityParams,
+ SalesforceUpdateOpportunityResponse
+> = {
+ id: 'salesforce_update_opportunity',
+ name: 'Update Opportunity in Salesforce',
+ description: 'Update an existing opportunity',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: { type: 'string', required: true, visibility: 'hidden' },
+ idToken: { type: 'string', required: false, visibility: 'hidden' },
+ instanceUrl: { type: 'string', required: false, visibility: 'hidden' },
+ opportunityId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Opportunity ID (required)',
+ },
+ name: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Opportunity name',
+ },
+ stageName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Stage name',
+ },
+ closeDate: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Close date YYYY-MM-DD',
+ },
+ accountId: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Account ID',
+ },
+ amount: { type: 'string', required: false, visibility: 'user-only', description: 'Amount' },
+ probability: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Probability (0-100)',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Opportunity/${params.opportunityId}`,
+ method: 'PATCH',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {}
+ if (params.name) body.Name = params.name
+ if (params.stageName) body.StageName = params.stageName
+ if (params.closeDate) body.CloseDate = params.closeDate
+ if (params.accountId) body.AccountId = params.accountId
+ if (params.amount) body.Amount = Number.parseFloat(params.amount)
+ if (params.probability) body.Probability = Number.parseInt(params.probability)
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json()
+ throw new Error(data[0]?.message || data.message || 'Failed to update opportunity')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.opportunityId || '',
+ updated: true,
+ metadata: { operation: 'update_opportunity' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Updated opportunity' },
+ },
+}
diff --git a/apps/sim/tools/salesforce/update_task.ts b/apps/sim/tools/salesforce/update_task.ts
new file mode 100644
index 0000000000..0dd62f824a
--- /dev/null
+++ b/apps/sim/tools/salesforce/update_task.ts
@@ -0,0 +1,133 @@
+import type { ToolConfig } from '@/tools/types'
+import { getInstanceUrl } from './utils'
+
+export interface SalesforceUpdateTaskParams {
+ accessToken: string
+ idToken?: string
+ instanceUrl?: string
+ taskId: string
+ subject?: string
+ status?: string
+ priority?: string
+ activityDate?: string
+ description?: string
+}
+
+export interface SalesforceUpdateTaskResponse {
+ success: boolean
+ output: {
+ id: string
+ updated: boolean
+ metadata: {
+ operation: 'update_task'
+ }
+ }
+}
+
+export const salesforceUpdateTaskTool: ToolConfig<
+ SalesforceUpdateTaskParams,
+ SalesforceUpdateTaskResponse
+> = {
+ id: 'salesforce_update_task',
+ name: 'Update Task in Salesforce',
+ description: 'Update an existing task',
+ version: '1.0.0',
+
+ oauth: {
+ required: true,
+ provider: 'salesforce',
+ },
+
+ params: {
+ accessToken: {
+ type: 'string',
+ required: true,
+ visibility: 'hidden',
+ },
+ idToken: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ instanceUrl: {
+ type: 'string',
+ required: false,
+ visibility: 'hidden',
+ },
+ taskId: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Task ID (required)',
+ },
+ subject: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Task subject',
+ },
+ status: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Status',
+ },
+ priority: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Priority',
+ },
+ activityDate: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Due date YYYY-MM-DD',
+ },
+ description: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Description',
+ },
+ },
+
+ request: {
+ url: (params) =>
+ `${getInstanceUrl(params.idToken, params.instanceUrl)}/services/data/v59.0/sobjects/Task/${params.taskId}`,
+ method: 'PATCH',
+ headers: (params) => ({
+ Authorization: `Bearer ${params.accessToken}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {}
+ if (params.subject) body.Subject = params.subject
+ if (params.status) body.Status = params.status
+ if (params.priority) body.Priority = params.priority
+ if (params.activityDate) body.ActivityDate = params.activityDate
+ if (params.description) body.Description = params.description
+ return body
+ },
+ },
+
+ transformResponse: async (response, params?) => {
+ if (!response.ok) {
+ const data = await response.json()
+ throw new Error(data[0]?.message || data.message || 'Failed to update task')
+ }
+ return {
+ success: true,
+ output: {
+ id: params?.taskId || '',
+ updated: true,
+ metadata: { operation: 'update_task' },
+ },
+ }
+ },
+
+ outputs: {
+ success: { type: 'boolean', description: 'Success' },
+ output: { type: 'object', description: 'Updated task' },
+ },
+}