mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(docs): fix salesforce docs & update styling (#2248)
This commit is contained in:
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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)',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
4
apps/docs/content/docs/en/knowledgebase/meta.json
Normal file
4
apps/docs/content/docs/en/knowledgebase/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Knowledgebase",
|
||||
"pages": ["index", "tags"]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
141
apps/sim/tools/salesforce/create_case.ts
Normal file
141
apps/sim/tools/salesforce/create_case.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
188
apps/sim/tools/salesforce/create_contact.ts
Normal file
188
apps/sim/tools/salesforce/create_contact.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
129
apps/sim/tools/salesforce/create_lead.ts
Normal file
129
apps/sim/tools/salesforce/create_lead.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
132
apps/sim/tools/salesforce/create_opportunity.ts
Normal file
132
apps/sim/tools/salesforce/create_opportunity.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
141
apps/sim/tools/salesforce/create_task.ts
Normal file
141
apps/sim/tools/salesforce/create_task.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
88
apps/sim/tools/salesforce/delete_case.ts
Normal file
88
apps/sim/tools/salesforce/delete_case.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
88
apps/sim/tools/salesforce/delete_contact.ts
Normal file
88
apps/sim/tools/salesforce/delete_contact.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/salesforce/delete_lead.ts
Normal file
74
apps/sim/tools/salesforce/delete_lead.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/salesforce/delete_opportunity.ts
Normal file
74
apps/sim/tools/salesforce/delete_opportunity.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
88
apps/sim/tools/salesforce/delete_task.ts
Normal file
88
apps/sim/tools/salesforce/delete_task.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
140
apps/sim/tools/salesforce/describe_object.ts
Normal file
140
apps/sim/tools/salesforce/describe_object.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
128
apps/sim/tools/salesforce/get_cases.ts
Normal file
128
apps/sim/tools/salesforce/get_cases.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
169
apps/sim/tools/salesforce/get_contacts.ts
Normal file
169
apps/sim/tools/salesforce/get_contacts.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
118
apps/sim/tools/salesforce/get_dashboard.ts
Normal file
118
apps/sim/tools/salesforce/get_dashboard.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
132
apps/sim/tools/salesforce/get_leads.ts
Normal file
132
apps/sim/tools/salesforce/get_leads.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
131
apps/sim/tools/salesforce/get_opportunities.ts
Normal file
131
apps/sim/tools/salesforce/get_opportunities.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
109
apps/sim/tools/salesforce/get_report.ts
Normal file
109
apps/sim/tools/salesforce/get_report.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
139
apps/sim/tools/salesforce/get_tasks.ts
Normal file
139
apps/sim/tools/salesforce/get_tasks.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
114
apps/sim/tools/salesforce/list_dashboards.ts
Normal file
114
apps/sim/tools/salesforce/list_dashboards.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
106
apps/sim/tools/salesforce/list_objects.ts
Normal file
106
apps/sim/tools/salesforce/list_objects.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
98
apps/sim/tools/salesforce/list_report_types.ts
Normal file
98
apps/sim/tools/salesforce/list_report_types.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
130
apps/sim/tools/salesforce/list_reports.ts
Normal file
130
apps/sim/tools/salesforce/list_reports.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
127
apps/sim/tools/salesforce/query_more.ts
Normal file
127
apps/sim/tools/salesforce/query_more.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
120
apps/sim/tools/salesforce/refresh_dashboard.ts
Normal file
120
apps/sim/tools/salesforce/refresh_dashboard.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
162
apps/sim/tools/salesforce/run_report.ts
Normal file
162
apps/sim/tools/salesforce/run_report.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
125
apps/sim/tools/salesforce/update_case.ts
Normal file
125
apps/sim/tools/salesforce/update_case.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
192
apps/sim/tools/salesforce/update_contact.ts
Normal file
192
apps/sim/tools/salesforce/update_contact.ts
Normal 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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
133
apps/sim/tools/salesforce/update_lead.ts
Normal file
133
apps/sim/tools/salesforce/update_lead.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
132
apps/sim/tools/salesforce/update_opportunity.ts
Normal file
132
apps/sim/tools/salesforce/update_opportunity.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
133
apps/sim/tools/salesforce/update_task.ts
Normal file
133
apps/sim/tools/salesforce/update_task.ts
Normal 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' },
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user