fix(docs): fix salesforce docs & update styling (#2248)

This commit is contained in:
Waleed
2025-12-08 12:08:53 -08:00
committed by GitHub
parent 598c3bc684
commit feb591867e
50 changed files with 4502 additions and 3251 deletions

View File

@@ -182,7 +182,11 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
tableOfContent={{
style: 'clerk',
enabled: true,
header: <div className='mb-2 font-medium text-sm'>On this page</div>,
header: (
<div key='toc-header' className='mb-2 font-medium text-sm'>
On this page
</div>
),
footer: <TOCFooter />,
single: false,
}}

View File

@@ -101,9 +101,6 @@ export default async function Layout({ children, params }: LayoutProps) {
<Navbar />
<DocsLayout
tree={source.pageTree[lang]}
themeSwitch={{
enabled: false,
}}
nav={{
title: (
<Image
@@ -128,7 +125,7 @@ export default async function Layout({ children, params }: LayoutProps) {
},
}}
containerProps={{
className: '!pt-10',
className: '!pt-0',
}}
>
{children}

View File

@@ -96,45 +96,48 @@ aside#nd-sidebar {
border-right: none !important;
}
/* Responsive sidebar positioning */
/* Mobile: Fumadocs handles drawer */
@media (min-width: 768px) and (max-width: 1024px) {
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
/* Fumadocs v16: Add sidebar placeholder styling for grid area */
[data-sidebar-placeholder] {
background: transparent !important;
}
/* Fumadocs v16: Hide sidebar panel (floating collapse button) */
[data-sidebar-panel] {
display: none !important;
}
/* Mobile only: Reduce gap between navbar and content */
@media (max-width: 1023px) {
#nd-docs-layout {
margin-top: -25px;
}
}
/* Desktop layout alignment */
@media (min-width: 1025px) {
[data-sidebar-container] {
margin-left: var(--sidebar-offset) !important;
}
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
}
/* TOC positioning - target all possible selectors */
[data-toc],
aside[data-toc],
div[data-toc],
.fd-toc,
#nd-toc,
nav[data-toc],
aside:has([role="complementary"]) {
right: var(--toc-offset) !important;
/* Desktop only: Apply custom navbar offset, sidebar width and margin offsets */
/* On mobile, let fumadocs handle the layout natively */
@media (min-width: 1024px) {
:root {
--fd-banner-height: 64px !important;
}
/* Alternative TOC container targeting */
[data-docs-page] > aside:last-child,
main ~ aside {
right: var(--toc-offset) !important;
#nd-docs-layout {
--fd-docs-height: calc(100dvh - 64px) !important;
--fd-sidebar-width: 300px !important;
margin-left: var(--sidebar-offset) !important;
margin-right: var(--toc-offset) !important;
}
/* Hide fumadocs nav on desktop - we use custom navbar there */
#nd-docs-layout > header {
display: none !important;
}
}
/* Sidebar spacing - compact like turborepo */
[data-sidebar-viewport] {
padding: 0.5rem 20px 12px;
/* Fumadocs v16: [data-sidebar-viewport] doesn't exist, target #nd-sidebar > div instead */
[data-sidebar-viewport],
#nd-sidebar > div {
padding: 0.5rem 12px 12px;
background: transparent !important;
background-color: transparent !important;
}
@@ -142,8 +145,9 @@ aside#nd-sidebar {
/* Override sidebar item styling to match Raindrop */
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
/* Exclude the small chevron-only toggle buttons */
#nd-sidebar a,
#nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
/* Using html prefix for higher specificity over Tailwind v4 utilities */
html #nd-sidebar a,
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
font-size: 0.9375rem !important; /* 15px to match Raindrop */
line-height: 1.4 !important;
padding: 0.5rem 0.75rem !important; /* More compact like Raindrop */
@@ -154,14 +158,14 @@ aside#nd-sidebar {
}
/* Dark mode sidebar text */
.dark #nd-sidebar a,
.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
html.dark #nd-sidebar a,
html.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(255, 255, 255, 0.6) !important;
}
/* Light mode sidebar text */
:root:not(.dark) #nd-sidebar a,
:root:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
html:not(.dark) #nd-sidebar a,
html:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(0, 0, 0, 0.6) !important;
}
@@ -194,7 +198,10 @@ aside#nd-sidebar {
}
/* Section headers should be slightly larger */
[data-sidebar-viewport] [data-separator] {
/* Fumadocs v16: Also target #nd-sidebar for compatibility */
[data-sidebar-viewport] [data-separator],
#nd-sidebar [data-separator],
#nd-sidebar p {
font-size: 0.75rem !important;
font-weight: 600 !important;
text-transform: uppercase !important;
@@ -218,61 +225,61 @@ aside#nd-sidebar {
}
/* Dark mode active state */
.dark #nd-sidebar a[data-active="true"],
.dark #nd-sidebar button[data-active="true"],
.dark #nd-sidebar a.bg-fd-primary\/10,
.dark #nd-sidebar a.text-fd-primary,
.dark #nd-sidebar a[class*="bg-fd-primary"],
.dark #nd-sidebar a[class*="text-fd-primary"],
.dark #nd-sidebar a.bg-purple-50\/80,
.dark #nd-sidebar a.text-purple-600,
.dark #nd-sidebar a[class*="bg-purple"],
.dark #nd-sidebar a[class*="text-purple"] {
html.dark #nd-sidebar a[data-active="true"],
html.dark #nd-sidebar button[data-active="true"],
html.dark #nd-sidebar a.bg-fd-primary\/10,
html.dark #nd-sidebar a.text-fd-primary,
html.dark #nd-sidebar a[class*="bg-fd-primary"],
html.dark #nd-sidebar a[class*="text-fd-primary"],
html.dark #nd-sidebar a.bg-purple-50\/80,
html.dark #nd-sidebar a.text-purple-600,
html.dark #nd-sidebar a[class*="bg-purple"],
html.dark #nd-sidebar a[class*="text-purple"] {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode active state */
:root:not(.dark) #nd-sidebar a[data-active="true"],
:root:not(.dark) #nd-sidebar button[data-active="true"],
:root:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
:root:not(.dark) #nd-sidebar a.text-fd-primary,
:root:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
:root:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80,
:root:not(.dark) #nd-sidebar a.text-purple-600,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"],
:root:not(.dark) #nd-sidebar a[class*="text-purple"] {
html:not(.dark) #nd-sidebar a[data-active="true"],
html:not(.dark) #nd-sidebar button[data-active="true"],
html:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
html:not(.dark) #nd-sidebar a.text-fd-primary,
html:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
html:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
html:not(.dark) #nd-sidebar a.bg-purple-50\/80,
html:not(.dark) #nd-sidebar a.text-purple-600,
html:not(.dark) #nd-sidebar a[class*="bg-purple"],
html:not(.dark) #nd-sidebar a[class*="text-purple"] {
background-color: rgba(0, 0, 0, 0.07) !important;
color: rgba(0, 0, 0, 0.9) !important;
}
/* Dark mode hover state */
.dark #nd-sidebar a:hover:not([data-active="true"]),
.dark #nd-sidebar button:hover:not([data-active="true"]) {
html.dark #nd-sidebar a:hover:not([data-active="true"]),
html.dark #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(255, 255, 255, 0.08) !important;
}
/* Light mode hover state */
:root:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
:root:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
html:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
html:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(0, 0, 0, 0.03) !important;
}
/* Dark mode - ensure active/selected items don't change on hover */
.dark #nd-sidebar a.bg-purple-50\/80:hover,
.dark #nd-sidebar a[class*="bg-purple"]:hover,
.dark #nd-sidebar a[data-active="true"]:hover,
.dark #nd-sidebar button[data-active="true"]:hover {
html.dark #nd-sidebar a.bg-purple-50\/80:hover,
html.dark #nd-sidebar a[class*="bg-purple"]:hover,
html.dark #nd-sidebar a[data-active="true"]:hover,
html.dark #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode - ensure active/selected items don't change on hover */
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
:root:not(.dark) #nd-sidebar a[data-active="true"]:hover,
:root:not(.dark) #nd-sidebar button[data-active="true"]:hover {
html:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
html:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
html:not(.dark) #nd-sidebar a[data-active="true"]:hover,
html:not(.dark) #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(0, 0, 0, 0.07) !important;
color: rgba(0, 0, 0, 0.9) !important;
}
@@ -351,7 +358,16 @@ aside[data-sidebar] > *:not([data-sidebar-viewport]) {
[data-sidebar] [data-title],
#nd-sidebar > a:first-child,
#nd-sidebar > div:first-child > a:first-child,
#nd-sidebar img[alt="Sim"] {
#nd-sidebar img[alt="Sim"],
/* Hide theme toggle at bottom of sidebar on desktop */
#nd-sidebar
> footer,
#nd-sidebar footer,
aside#nd-sidebar > *:last-child:not(div),
#nd-sidebar > button:last-child,
#nd-sidebar button[aria-label*="theme" i],
#nd-sidebar button[aria-label*="Theme"],
#nd-sidebar > div:last-child > button {
display: none !important;
visibility: hidden !important;
height: 0 !important;
@@ -498,13 +514,14 @@ main article,
============================================ */
/* Main content area - center and constrain like turborepo/raindrop */
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
main[data-main] {
max-width: var(--spacing-fd-container, 1400px);
margin-left: auto;
margin-right: auto;
padding-top: 1rem;
padding-left: calc(var(--sidebar-offset) + var(--content-gap));
padding-right: calc(var(--toc-offset) + var(--content-gap));
padding-left: var(--content-gap);
padding-right: var(--content-gap);
order: 1 !important;
}

View File

@@ -7,8 +7,23 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { cn } from '@/lib/utils'
const LANG_PREFIXES = ['/en', '/es', '/fr', '/de', '/ja', '/zh']
function stripLangPrefix(path: string): string {
for (const prefix of LANG_PREFIXES) {
if (path === prefix) return '/'
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
}
return path
}
function isActive(url: string, pathname: string, nested = true): boolean {
return url === pathname || (nested && pathname.startsWith(`${url}/`))
const normalizedPathname = stripLangPrefix(pathname)
const normalizedUrl = stripLangPrefix(url)
return (
normalizedUrl === normalizedPathname ||
(nested && normalizedPathname.startsWith(`${normalizedUrl}/`))
)
}
export function SidebarItem({ item }: { item: Item }) {
@@ -16,97 +31,158 @@ export function SidebarItem({ item }: { item: Item }) {
const active = isActive(item.url, pathname, false)
return (
<li className='mb-[0.0625rem] list-none'>
<Link
href={item.url}
className={cn(
'block rounded-md px-2.5 py-1.5 font-normal text-[13px] leading-tight transition-colors',
'text-gray-600 dark:text-gray-400',
!active && 'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
active &&
'bg-purple-50/80 font-medium text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
)}
>
{item.name}
</Link>
</li>
<Link
href={item.url}
data-active={active}
className={cn(
// Mobile styles (default)
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
'lg:text-gray-600 lg:dark:text-gray-400',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
)
}
export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) {
const pathname = usePathname()
const hasActiveChild = checkHasActiveChild(item, pathname)
const hasChildren = item.children.length > 0
const [open, setOpen] = useState(hasActiveChild)
useEffect(() => {
setOpen(hasActiveChild)
}, [hasActiveChild])
const active = item.index ? isActive(item.index.url, pathname, false) : false
if (item.index && !hasChildren) {
return (
<Link
href={item.index.url}
data-active={active}
className={cn(
// Mobile styles (default)
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
'lg:text-gray-600 lg:dark:text-gray-400',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
)
}
return (
<li className='mb-[0.0625rem] list-none'>
{item.index ? (
<div className='flex items-center gap-0.5'>
<div className='flex flex-col lg:mb-[0.0625rem]'>
<div className='flex w-full items-center lg:gap-0.5'>
{item.index ? (
<Link
href={item.index.url}
data-active={active}
className={cn(
'block flex-1 rounded-md px-2.5 py-1.5 font-medium text-[13px] leading-tight transition-colors',
'text-gray-800 dark:text-gray-200',
!isActive(item.index.url, pathname, false) &&
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
isActive(item.index.url, pathname, false) &&
'bg-purple-50/80 text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
// Mobile styles (default)
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:block lg:flex-1 lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-medium lg:text-[13px] lg:leading-tight',
'lg:text-gray-800 lg:dark:text-gray-200',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
) : (
<button
onClick={() => setOpen(!open)}
className='cursor-pointer rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
aria-label={open ? 'Collapse' : 'Expand'}
className={cn(
// Mobile styles (default)
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50',
// Desktop styles (lg+)
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-md lg:px-2.5 lg:py-1.5 lg:text-left lg:font-medium lg:text-[13px] lg:leading-tight',
'lg:text-gray-800 lg:hover:bg-gray-100/60 lg:dark:text-gray-200 lg:dark:hover:bg-gray-800/40'
)}
>
<span>{item.name}</span>
{/* Desktop-only chevron for non-index folders */}
<ChevronRight
className={cn(
'h-3 w-3 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
'ml-auto hidden h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out lg:block dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
</div>
) : (
<button
onClick={() => setOpen(!open)}
)}
{hasChildren && (
<button
onClick={() => setOpen(!open)}
className={cn(
// Mobile styles
'rounded p-1 hover:bg-fd-accent/50',
// Desktop styles
'lg:cursor-pointer lg:rounded lg:p-1 lg:transition-colors lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40'
)}
aria-label={open ? 'Collapse' : 'Expand'}
>
<ChevronRight
className={cn(
// Mobile styles
'h-4 w-4 transition-transform',
// Desktop styles
'lg:h-3 lg:w-3 lg:text-gray-400 lg:duration-200 lg:ease-in-out lg:dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
)}
</div>
{hasChildren && (
<div
className={cn(
'flex w-full cursor-pointer items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
'text-gray-800 dark:text-gray-200'
'overflow-hidden transition-all duration-200 ease-in-out',
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
)}
>
<span>{item.name}</span>
<ChevronRight
className={cn(
'ml-auto h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
{/* Mobile: simple indent */}
<div className='ml-4 flex flex-col gap-0.5 lg:hidden'>{children}</div>
{/* Desktop: styled with border */}
<ul className='mt-0.5 ml-2 hidden space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 lg:block dark:border-gray-700/60'>
{children}
</ul>
</div>
)}
<div
className={cn(
'overflow-hidden transition-all duration-200 ease-in-out',
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
)}
>
<ul className='mt-0.5 ml-2 space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 dark:border-gray-700/60'>
{children}
</ul>
</div>
</li>
</div>
)
}
export function SidebarSeparator({ item }: { item: Separator }) {
return (
<p className='mt-4 mb-1.5 px-2.5 font-semibold text-[10px] text-gray-500/80 uppercase tracking-wide dark:text-gray-500'>
<p
className={cn(
// Mobile styles
'mt-4 mb-2 px-2 font-medium text-fd-muted-foreground text-xs',
// Desktop styles
'lg:mt-4 lg:mb-1.5 lg:px-2.5 lg:font-semibold lg:text-[10px] lg:text-gray-500/80 lg:uppercase lg:tracking-wide lg:dark:text-gray-500'
)}
>
{item.name}
</p>
)

View File

@@ -20,7 +20,7 @@ export function Navbar() {
<div
className='relative flex w-full items-center justify-between'
style={{
paddingLeft: 'calc(var(--sidebar-offset) + 20px)',
paddingLeft: 'calc(var(--sidebar-offset) + 32px)',
paddingRight: 'calc(var(--toc-offset) + 60px)',
}}
>

View File

@@ -116,114 +116,114 @@ import {
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
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,
}

View File

@@ -72,7 +72,7 @@ For custom integrations, leverage our [MCP (Model Context Protocol) support](/mc
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## 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.

View File

@@ -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'

View File

@@ -0,0 +1,4 @@
{
"title": "Knowledgebase",
"pages": ["index", "tags"]
}

View File

@@ -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

View File

@@ -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 |

View File

@@ -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<any, any> = {
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<any, any> = {
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<string, any> = { 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<any, any> = {
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<string, any> = {}
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<any, any> = {
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' },
},
}

View File

@@ -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<string, any> = { 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<string, any> = {}
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' },
},
},
},
}

View File

@@ -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<string, any> = { 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' },
},
}

View File

@@ -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<string, any> = { 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' },
},
},
},
}

View File

@@ -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<string, any> = { 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' },
},
}

View File

@@ -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<string, any> = {
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' },
},
}

View File

@@ -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<string, any> = { 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' },
},
}

View File

@@ -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<any, any> = {
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<any, any> = {
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<any, any> = {
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' },
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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'

View File

@@ -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<any, any> = {
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<any, any> = {
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<string, any> = { 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<any, any> = {
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<string, any> = {}
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<any, any> = {
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' },
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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<any, any> = {
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<any, any> = {
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<string, any> = {
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<any, any> = {
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<string, any> = {}
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<any, any> = {
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' },
},
}

View File

@@ -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<any, any> = {
export const salesforceQueryTool: ToolConfig<SalesforceQueryParams, SalesforceQueryResponse> = {
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<any, any> = {
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<any, any> = {
'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<any, any> = {
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<any, any> = {
},
}
},
outputs: {
success: { type: 'boolean', description: 'Success status' },
output: {
@@ -97,270 +122,3 @@ export const salesforceQueryTool: ToolConfig<any, any> = {
},
},
}
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<any, any> = {
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<any, any> = {
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<any, any> = {
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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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<any, any> = {
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<any, any> = {
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<any, any> = {
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<any, any> = {
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' },
},
},
},
}

View File

@@ -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' },
},
},
},
}

View File

@@ -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<any, any> = {
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<any, any> = {
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<string, any> = { 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<any, any> = {
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<string, any> = {}
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<any, any> = {
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' },
},
}

View File

@@ -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<string, any> = {}
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' },
},
}

View File

@@ -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<string, any> = {}
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' },
},
},
},
}

View File

@@ -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<string, any> = {}
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' },
},
}

View File

@@ -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<string, any> = {}
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' },
},
}

View File

@@ -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<string, any> = {}
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' },
},
}