chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,70 @@
import type {
EmailBisonActionResponse,
EmailBisonAttachLeadsParams,
} from '@/tools/emailbison/types'
import {
actionOutput,
actionOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const attachLeadsToCampaignTool: ToolConfig<
EmailBisonAttachLeadsParams,
EmailBisonActionResponse
> = {
id: 'emailbison_attach_leads_to_campaign',
name: 'Email Bison Attach Leads to Campaign',
description: 'Adds existing Email Bison leads to a campaign.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
campaignId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
leadIds: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Lead IDs to add to the campaign',
items: { type: 'number', description: 'Lead ID' },
},
allowParallelSending: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Force add leads already in sequence in other campaigns',
},
},
request: {
url: (params) =>
emailBisonUrl(
`/api/campaigns/${params.campaignId}/leads/attach-leads`,
{},
params.apiBaseUrl
),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
lead_ids: params.leadIds,
allow_parallel_sending: params.allowParallelSending,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign lead attachment result')
return {
success: true,
output: actionOutput(data),
}
},
outputs: actionOutputs,
}
@@ -0,0 +1,67 @@
import type {
EmailBisonActionResponse,
EmailBisonAttachTagsToLeadsParams,
} from '@/tools/emailbison/types'
import {
actionOutput,
actionOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const attachTagsToLeadsTool: ToolConfig<
EmailBisonAttachTagsToLeadsParams,
EmailBisonActionResponse
> = {
id: 'emailbison_attach_tags_to_leads',
name: 'Email Bison Attach Tags to Leads',
description: 'Attaches Email Bison tags to one or more leads.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
tagIds: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Tag IDs to attach',
items: { type: 'number', description: 'Tag ID' },
},
leadIds: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Lead IDs to tag',
items: { type: 'number', description: 'Lead ID' },
},
skipWebhooks: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip Email Bison webhooks for this action',
},
},
request: {
url: (params) => emailBisonUrl('/api/tags/attach-to-leads', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
tag_ids: params.tagIds,
lead_ids: params.leadIds,
skip_webhooks: params.skipWebhooks,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'tag attachment result')
return {
success: true,
output: actionOutput(data),
}
},
outputs: actionOutputs,
}
@@ -0,0 +1,58 @@
import type {
EmailBisonCampaignResponse,
EmailBisonCreateCampaignParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
mapCampaign,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const createCampaignTool: ToolConfig<
EmailBisonCreateCampaignParams,
EmailBisonCampaignResponse
> = {
id: 'emailbison_create_campaign',
name: 'Email Bison Create Campaign',
description: 'Creates a new Email Bison campaign.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Campaign name',
},
campaignType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign type: outbound or reply_followup',
},
},
request: {
url: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
name: params.name,
type: params.campaignType,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
+94
View File
@@ -0,0 +1,94 @@
import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const createLeadTool: ToolConfig<EmailBisonLeadMutationParams, EmailBisonLeadResponse> = {
id: 'emailbison_create_lead',
name: 'Email Bison Create Lead',
description: 'Creates a single lead in Email Bison.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
firstName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead first name',
},
lastName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead last name',
},
email: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead email address',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead job title',
},
company: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead company',
},
notes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional notes about the lead',
},
customVariables: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Custom variables to store on the lead',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Custom variable name' },
value: { type: 'string', description: 'Custom variable value' },
},
},
},
},
request: {
url: (params) => emailBisonUrl('/api/leads', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
first_name: params.firstName,
last_name: params.lastName,
email: params.email,
title: params.title,
company: params.company,
notes: params.notes,
custom_variables: params.customVariables,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+42
View File
@@ -0,0 +1,42 @@
import type { EmailBisonCreateTagParams, EmailBisonTagResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
mapTag,
tagOutputs,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const createTagTool: ToolConfig<EmailBisonCreateTagParams, EmailBisonTagResponse> = {
id: 'emailbison_create_tag',
name: 'Email Bison Create Tag',
description: 'Creates a new Email Bison tag.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Tag name',
},
},
request: {
url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) => jsonBody({ name: params.name }),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'tag')
return {
success: true,
output: mapTag(data),
}
},
outputs: tagOutputs,
}
+45
View File
@@ -0,0 +1,45 @@
import type { EmailBisonGetLeadParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const getLeadTool: ToolConfig<EmailBisonGetLeadParams, EmailBisonLeadResponse> = {
id: 'emailbison_get_lead',
name: 'Email Bison Get Lead',
description: 'Retrieves a lead by Email Bison lead ID or email address.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
leadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead ID or email address',
},
},
request: {
url: (params) =>
emailBisonUrl(
`/api/leads/${encodeURIComponent(params.leadId.trim())}`,
{},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+14
View File
@@ -0,0 +1,14 @@
export { attachLeadsToCampaignTool as emailBisonAttachLeadsToCampaignTool } from '@/tools/emailbison/attach_leads_to_campaign'
export { attachTagsToLeadsTool as emailBisonAttachTagsToLeadsTool } from '@/tools/emailbison/attach_tags_to_leads'
export { createCampaignTool as emailBisonCreateCampaignTool } from '@/tools/emailbison/create_campaign'
export { createLeadTool as emailBisonCreateLeadTool } from '@/tools/emailbison/create_lead'
export { createTagTool as emailBisonCreateTagTool } from '@/tools/emailbison/create_tag'
export { getLeadTool as emailBisonGetLeadTool } from '@/tools/emailbison/get_lead'
export { listCampaignsTool as emailBisonListCampaignsTool } from '@/tools/emailbison/list_campaigns'
export { listLeadsTool as emailBisonListLeadsTool } from '@/tools/emailbison/list_leads'
export { listRepliesTool as emailBisonListRepliesTool } from '@/tools/emailbison/list_replies'
export { listTagsTool as emailBisonListTagsTool } from '@/tools/emailbison/list_tags'
export * from '@/tools/emailbison/types'
export { updateCampaignTool as emailBisonUpdateCampaignTool } from '@/tools/emailbison/update_campaign'
export { updateCampaignStatusTool as emailBisonUpdateCampaignStatusTool } from '@/tools/emailbison/update_campaign_status'
export { updateLeadTool as emailBisonUpdateLeadTool } from '@/tools/emailbison/update_lead'
@@ -0,0 +1,42 @@
import type {
EmailBisonBaseParams,
EmailBisonListCampaignsResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listCampaignsOutputs,
mapCampaign,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listCampaignsTool: ToolConfig<EmailBisonBaseParams, EmailBisonListCampaignsResponse> =
{
id: 'emailbison_list_campaigns',
name: 'Email Bison List Campaigns',
description: 'Retrieves Email Bison campaigns.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
},
request: {
url: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'campaigns')
const campaigns = data.map(mapCampaign)
return {
success: true,
output: {
campaigns,
count: campaigns.length,
},
}
},
outputs: listCampaignsOutputs,
}
+85
View File
@@ -0,0 +1,85 @@
import type {
EmailBisonListLeadsParams,
EmailBisonListLeadsResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listLeadsOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listLeadsTool: ToolConfig<EmailBisonListLeadsParams, EmailBisonListLeadsResponse> = {
id: 'emailbison_list_leads',
name: 'Email Bison List Leads',
description: 'Retrieves leads from Email Bison with optional search and tag filters.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term for filtering leads',
},
campaignStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Lead campaign status filter: in_sequence, sequence_finished, sequence_stopped, never_contacted, or replied',
},
tagIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Tag IDs to include',
items: { type: 'number', description: 'Tag ID' },
},
excludedTagIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Tag IDs to exclude',
items: { type: 'number', description: 'Tag ID' },
},
withoutTags: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Only return leads without tags',
},
},
request: {
url: (params) =>
emailBisonUrl(
'/api/leads',
{
search: params.search,
'filters.lead_campaign_status': params.campaignStatus,
'filters.tag_ids': params.tagIds,
'filters.excluded_tag_ids': params.excludedTagIds,
'filters.without_tags': params.withoutTags,
},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'leads')
const leads = data.map(mapLead)
return {
success: true,
output: {
leads,
count: leads.length,
},
}
},
outputs: listLeadsOutputs,
}
+108
View File
@@ -0,0 +1,108 @@
import type {
EmailBisonListRepliesParams,
EmailBisonListRepliesResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listRepliesOutputs,
mapReply,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listRepliesTool: ToolConfig<
EmailBisonListRepliesParams,
EmailBisonListRepliesResponse
> = {
id: 'emailbison_list_replies',
name: 'Email Bison List Replies',
description:
'Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term for replies',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reply status: interested, automated_reply, or not_automated_reply',
},
folder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reply folder: inbox, sent, spam, bounced, or all',
},
read: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Filter by read state',
},
campaignId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
senderEmailId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Sender email ID',
},
leadId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Lead ID',
},
tagIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Tag IDs to filter replies by',
items: { type: 'number', description: 'Tag ID' },
},
},
request: {
url: (params) =>
emailBisonUrl(
'/api/replies',
{
search: params.search,
status: params.status,
folder: params.folder,
read: params.read,
campaign_id: params.campaignId,
sender_email_id: params.senderEmailId,
lead_id: params.leadId,
tag_ids: params.tagIds,
},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'replies')
const replies = data.map(mapReply)
return {
success: true,
output: {
replies,
count: replies.length,
},
}
},
outputs: listRepliesOutputs,
}
+38
View File
@@ -0,0 +1,38 @@
import type { EmailBisonBaseParams, EmailBisonListTagsResponse } from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listTagsOutputs,
mapTag,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listTagsTool: ToolConfig<EmailBisonBaseParams, EmailBisonListTagsResponse> = {
id: 'emailbison_list_tags',
name: 'Email Bison List Tags',
description: 'Retrieves all Email Bison tags for the authenticated workspace.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
},
request: {
url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'tags')
const tags = data.map(mapTag)
return {
success: true,
output: {
tags,
count: tags.length,
},
}
},
outputs: listTagsOutputs,
}
+254
View File
@@ -0,0 +1,254 @@
import type { ToolResponse } from '@/tools/types'
export interface EmailBisonBaseParams {
apiKey: string
apiBaseUrl: string
}
interface EmailBisonCustomVariable {
name: string | null
value: string | null
}
export interface EmailBisonLeadStats {
emails_sent: number | null
opens: number | null
replies: number | null
unique_replies: number | null
unique_opens: number | null
}
export interface EmailBisonLead {
id: number | null
first_name: string | null
last_name: string | null
email: string | null
title: string | null
company: string | null
notes: string | null
status: string | null
custom_variables: EmailBisonCustomVariable[]
lead_campaign_data: unknown[]
overall_stats: EmailBisonLeadStats
created_at: string | null
updated_at: string | null
}
export interface EmailBisonTag {
id: number | null
name: string | null
default: boolean | null
created_at?: string | null
updated_at?: string | null
}
export interface EmailBisonCampaignTag {
id: number | null
name: string | null
default: boolean | null
}
export interface EmailBisonCampaign {
id: number | null
uuid: string | null
name: string | null
type: string | null
status: string | null
emails_sent: number | null
opened: number | null
unique_opens: number | null
replied: number | null
unique_replies: number | null
bounced: number | null
unsubscribed: number | null
interested: number | null
total_leads_contacted: number | null
total_leads: number | null
max_emails_per_day: number | null
max_new_leads_per_day: number | null
plain_text: boolean | null
open_tracking: boolean | null
can_unsubscribe: boolean | null
unsubscribe_text: string | null
sequence_prioritization?: string | null
tags: EmailBisonCampaignTag[]
created_at: string | null
updated_at: string | null
}
export interface EmailBisonReplyAddress {
name: string | null
address: string | null
}
export interface EmailBisonReplyAttachment {
id: number | null
uuid: string | null
reply_id: number | null
file_name: string | null
download_url: string | null
created_at: string | null
updated_at: string | null
}
export interface EmailBisonReply {
id: number | null
uuid: string | null
folder: string | null
subject: string | null
read: boolean | null
interested: boolean | null
automated_reply: boolean | null
html_body: string | null
text_body: string | null
raw_body: string | null
headers: string | null
date_received: string | null
type: string | null
tracked_reply: boolean | null
scheduled_email_id: number | string | null
campaign_id: number | string | null
lead_id: number | null
sender_email_id: number | null
raw_message_id: string | null
from_name: string | null
from_email_address: string | null
primary_to_email_address: string | null
to: EmailBisonReplyAddress[]
cc: string | null
bcc: string | null
parent_id: number | string | null
attachments: EmailBisonReplyAttachment[]
created_at: string | null
updated_at: string | null
}
export interface EmailBisonListLeadsParams extends EmailBisonBaseParams {
search?: string
campaignStatus?: string
tagIds?: number[]
excludedTagIds?: number[]
withoutTags?: boolean
}
export interface EmailBisonGetLeadParams extends EmailBisonBaseParams {
leadId: string
}
export interface EmailBisonLeadMutationParams extends EmailBisonBaseParams {
leadId?: string
firstName: string
lastName: string
email: string
title?: string
company?: string
notes?: string
customVariables?: EmailBisonCustomVariable[]
}
export interface EmailBisonCreateCampaignParams extends EmailBisonBaseParams {
name: string
campaignType?: 'outbound' | 'reply_followup'
}
export interface EmailBisonUpdateCampaignParams extends EmailBisonBaseParams {
campaignId: number
name?: string
maxEmailsPerDay?: number
maxNewLeadsPerDay?: number
plainText?: boolean
openTracking?: boolean
reputationBuilding?: boolean
canUnsubscribe?: boolean
includeAutoRepliesInStats?: boolean
sequencePrioritization?: 'followups' | 'new_leads'
}
export interface EmailBisonCampaignStatusParams extends EmailBisonBaseParams {
campaignId: number
action: 'pause' | 'resume' | 'archive'
}
export interface EmailBisonAttachLeadsParams extends EmailBisonBaseParams {
campaignId: number
leadIds: number[]
allowParallelSending?: boolean
}
export interface EmailBisonListRepliesParams extends EmailBisonBaseParams {
search?: string
status?: string
folder?: string
read?: boolean
campaignId?: number
senderEmailId?: number
leadId?: number
tagIds?: number[]
}
export interface EmailBisonCreateTagParams extends EmailBisonBaseParams {
name: string
}
export interface EmailBisonAttachTagsToLeadsParams extends EmailBisonBaseParams {
tagIds: number[]
leadIds: number[]
skipWebhooks?: boolean
}
export interface EmailBisonListLeadsResponse extends ToolResponse {
output: {
leads: EmailBisonLead[]
count: number
}
}
export interface EmailBisonLeadResponse extends ToolResponse {
output: EmailBisonLead
}
export interface EmailBisonListCampaignsResponse extends ToolResponse {
output: {
campaigns: EmailBisonCampaign[]
count: number
}
}
export interface EmailBisonCampaignResponse extends ToolResponse {
output: EmailBisonCampaign
}
export interface EmailBisonActionResponse extends ToolResponse {
output: {
success: boolean
message: string | null
}
}
export interface EmailBisonListRepliesResponse extends ToolResponse {
output: {
replies: EmailBisonReply[]
count: number
}
}
export interface EmailBisonListTagsResponse extends ToolResponse {
output: {
tags: EmailBisonTag[]
count: number
}
}
export interface EmailBisonTagResponse extends ToolResponse {
output: EmailBisonTag
}
export type EmailBisonResponse =
| EmailBisonListLeadsResponse
| EmailBisonLeadResponse
| EmailBisonListCampaignsResponse
| EmailBisonCampaignResponse
| EmailBisonActionResponse
| EmailBisonListRepliesResponse
| EmailBisonListTagsResponse
| EmailBisonTagResponse
@@ -0,0 +1,114 @@
import type {
EmailBisonCampaignResponse,
EmailBisonUpdateCampaignParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
mapCampaign,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const updateCampaignTool: ToolConfig<
EmailBisonUpdateCampaignParams,
EmailBisonCampaignResponse
> = {
id: 'emailbison_update_campaign',
name: 'Email Bison Update Campaign',
description: 'Updates Email Bison campaign settings.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
campaignId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign name',
},
maxEmailsPerDay: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum emails per day',
},
maxNewLeadsPerDay: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum new leads per day',
},
plainText: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Send plain text emails',
},
openTracking: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable open tracking',
},
reputationBuilding: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable reputation building',
},
canUnsubscribe: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable unsubscribe link',
},
includeAutoRepliesInStats: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include auto replies in campaign stats',
},
sequencePrioritization: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sequence prioritization: followups or new_leads',
},
},
request: {
url: (params) =>
emailBisonUrl(`/api/campaigns/${params.campaignId}/update`, {}, params.apiBaseUrl),
method: 'PATCH',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
name: params.name,
max_emails_per_day: params.maxEmailsPerDay,
max_new_leads_per_day: params.maxNewLeadsPerDay,
plain_text: params.plainText,
open_tracking: params.openTracking,
reputation_building: params.reputationBuilding,
can_unsubscribe: params.canUnsubscribe,
include_auto_replies_in_stats: params.includeAutoRepliesInStats,
sequence_prioritization: params.sequencePrioritization,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
@@ -0,0 +1,64 @@
import type {
EmailBisonCampaignResponse,
EmailBisonCampaignStatusParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
mapCampaign,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
const CAMPAIGN_STATUS_ACTIONS = new Set(['pause', 'resume', 'archive'])
export const updateCampaignStatusTool: ToolConfig<
EmailBisonCampaignStatusParams,
EmailBisonCampaignResponse
> = {
id: 'emailbison_update_campaign_status',
name: 'Email Bison Update Campaign Status',
description: 'Pauses, resumes, or archives an Email Bison campaign.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
campaignId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
action: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Status action: pause, resume, or archive',
},
},
request: {
url: (params) => {
if (!CAMPAIGN_STATUS_ACTIONS.has(params.action)) {
throw new Error('Email Bison campaign status action must be pause, resume, or archive')
}
return emailBisonUrl(
`/api/campaigns/${params.campaignId}/${params.action}`,
{},
params.apiBaseUrl
)
},
method: 'PATCH',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
+106
View File
@@ -0,0 +1,106 @@
import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const updateLeadTool: ToolConfig<EmailBisonLeadMutationParams, EmailBisonLeadResponse> = {
id: 'emailbison_update_lead',
name: 'Email Bison Update Lead',
description:
'Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
leadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead ID or email address',
},
firstName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead first name',
},
lastName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead last name',
},
email: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead email address',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead job title',
},
company: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead company',
},
notes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional notes about the lead',
},
customVariables: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Custom variables to store on the lead',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Custom variable name' },
value: { type: 'string', description: 'Custom variable value' },
},
},
},
},
request: {
url: (params) =>
emailBisonUrl(
`/api/leads/${encodeURIComponent(params.leadId?.trim() ?? '')}`,
{},
params.apiBaseUrl
),
method: 'PUT',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
first_name: params.firstName,
last_name: params.lastName,
email: params.email,
title: params.title,
company: params.company,
notes: params.notes,
custom_variables: params.customVariables,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+479
View File
@@ -0,0 +1,479 @@
import { filterUndefined, isRecordLike } from '@sim/utils/object'
import type {
EmailBisonBaseParams,
EmailBisonCampaign,
EmailBisonCampaignTag,
EmailBisonLead,
EmailBisonLeadStats,
EmailBisonReply,
EmailBisonReplyAddress,
EmailBisonReplyAttachment,
EmailBisonTag,
} from '@/tools/emailbison/types'
import type { OutputProperty, ToolConfig } from '@/tools/types'
type QueryValue = string | number | boolean | Array<string | number> | undefined | null
interface EmailBisonEnvelope<T> {
data?: T
}
export function emailBisonHeaders(params: EmailBisonBaseParams): Record<string, string> {
return {
Authorization: `Bearer ${params.apiKey.trim()}`,
'Content-Type': 'application/json',
}
}
export const emailBisonBaseParamFields = {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Email Bison API token',
},
apiBaseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Email Bison instance URL that issued the token',
},
} satisfies ToolConfig['params']
export function emailBisonUrl(
path: string,
query: Record<string, QueryValue>,
baseUrl: string
): string {
const url = new URL(path, normalizeEmailBisonBaseUrl(baseUrl))
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === null || value === '') return
if (Array.isArray(value)) {
value.forEach((item) => {
url.searchParams.append(key, String(item))
})
return
}
url.searchParams.set(key, String(value))
})
return url.toString()
}
function normalizeEmailBisonBaseUrl(baseUrl: string): string {
const trimmedBaseUrl = baseUrl.trim()
if (!trimmedBaseUrl) {
throw new Error('Email Bison Instance URL is required')
}
const rawBaseUrl = /^https?:\/\//i.test(trimmedBaseUrl)
? trimmedBaseUrl
: `https://${trimmedBaseUrl}`
const parsed = new URL(rawBaseUrl)
if (parsed.protocol !== 'https:') {
throw new Error('Email Bison Instance URL must use HTTPS')
}
return parsed.origin
}
export function jsonBody(fields: Record<string, unknown>): Record<string, unknown> {
return filterUndefined(fields)
}
async function emailBisonData<T>(response: Response): Promise<T | null> {
const payload = (await response.json()) as EmailBisonEnvelope<T>
return payload.data ?? null
}
export async function emailBisonArrayData(response: Response, label: string): Promise<unknown[]> {
const data = await emailBisonData<unknown[]>(response)
if (!Array.isArray(data)) {
throw new Error(`Email Bison response did not include a valid ${label} array`)
}
return data
}
export async function emailBisonRecordData(response: Response, label: string): Promise<unknown> {
const data = await emailBisonData<unknown>(response)
if (!isRecordLike(data)) {
throw new Error(`Email Bison response did not include a valid ${label} object`)
}
return data
}
export function mapLead(value: unknown): EmailBisonLead {
const record = toRecord(value)
const stats = toRecord(record.overall_stats)
return {
id: toNullableNumber(record.id),
first_name: toStringOrNull(record.first_name),
last_name: toStringOrNull(record.last_name),
email: toStringOrNull(record.email),
title: toStringOrNull(record.title),
company: toStringOrNull(record.company),
notes: toStringOrNull(record.notes),
status: toStringOrNull(record.status),
custom_variables: toArray(record.custom_variables).map((item) => {
const variable = toRecord(item)
return {
name: toStringOrNull(variable.name),
value: toStringOrNull(variable.value),
}
}),
lead_campaign_data: toArray(record.lead_campaign_data),
overall_stats: mapLeadStats(stats),
created_at: toStringOrNull(record.created_at),
updated_at: toStringOrNull(record.updated_at),
}
}
export function mapCampaign(value: unknown): EmailBisonCampaign {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
uuid: toStringOrNull(record.uuid),
name: toStringOrNull(record.name),
type: toStringOrNull(record.type),
status: toStringOrNull(record.status),
emails_sent: toNullableNumber(record.emails_sent),
opened: toNullableNumber(record.opened),
unique_opens: toNullableNumber(record.unique_opens),
replied: toNullableNumber(record.replied),
unique_replies: toNullableNumber(record.unique_replies),
bounced: toNullableNumber(record.bounced),
unsubscribed: toNullableNumber(record.unsubscribed),
interested: toNullableNumber(record.interested),
total_leads_contacted: toNullableNumber(record.total_leads_contacted),
total_leads: toNullableNumber(record.total_leads),
max_emails_per_day: toNullableNumber(record.max_emails_per_day),
max_new_leads_per_day: toNullableNumber(record.max_new_leads_per_day),
plain_text: toNullableBoolean(record.plain_text),
open_tracking: toNullableBoolean(record.open_tracking),
can_unsubscribe: toNullableBoolean(record.can_unsubscribe),
unsubscribe_text: toStringOrNull(record.unsubscribe_text),
...(record.sequence_prioritization !== undefined && {
sequence_prioritization: toStringOrNull(record.sequence_prioritization),
}),
tags: toArray(record.tags).map(mapCampaignTag),
created_at: toStringOrNull(record.created_at),
updated_at: toStringOrNull(record.updated_at),
}
}
function mapCampaignTag(value: unknown): EmailBisonCampaignTag {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
name: toStringOrNull(record.name),
default: toNullableBoolean(record.default),
}
}
export function mapTag(value: unknown): EmailBisonTag {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
name: toStringOrNull(record.name),
default: toNullableBoolean(record.default),
created_at: toStringOrNull(record.created_at),
updated_at: toStringOrNull(record.updated_at),
}
}
export function mapReply(value: unknown): EmailBisonReply {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
uuid: toStringOrNull(record.uuid),
folder: toStringOrNull(record.folder),
subject: toStringOrNull(record.subject),
read: toNullableBoolean(record.read),
interested: toNullableBoolean(record.interested),
automated_reply: toNullableBoolean(record.automated_reply),
html_body: toStringOrNull(record.html_body),
text_body: toStringOrNull(record.text_body),
raw_body: toStringOrNull(record.raw_body),
headers: toStringOrNull(record.headers),
date_received: toStringOrNull(record.date_received),
type: toStringOrNull(record.type),
tracked_reply: toNullableBoolean(record.tracked_reply),
scheduled_email_id: toStringNumberOrNull(record.scheduled_email_id),
campaign_id: toStringNumberOrNull(record.campaign_id),
lead_id: toNullableNumber(record.lead_id),
sender_email_id: toNullableNumber(record.sender_email_id),
raw_message_id: toStringOrNull(record.raw_message_id),
from_name: toStringOrNull(record.from_name),
from_email_address: toStringOrNull(record.from_email_address),
primary_to_email_address: toStringOrNull(record.primary_to_email_address),
to: toArray(record.to).map(mapReplyAddress),
cc: toStringOrNull(record.cc),
bcc: toStringOrNull(record.bcc),
parent_id: toStringNumberOrNull(record.parent_id),
attachments: toArray(record.attachments).map(mapReplyAttachment),
created_at: toStringOrNull(record.created_at),
updated_at: toStringOrNull(record.updated_at),
}
}
export function actionOutput(value: unknown): { success: boolean; message: string | null } {
const record = toRecord(value)
return {
success: record.success === true,
message: toStringOrNull(record.message),
}
}
export const leadOutputs = {
id: { type: 'number', description: 'Lead ID' },
first_name: { type: 'string', description: 'Lead first name' },
last_name: { type: 'string', description: 'Lead last name' },
email: { type: 'string', description: 'Lead email address' },
title: { type: 'string', description: 'Lead title', optional: true },
company: { type: 'string', description: 'Lead company', optional: true },
notes: { type: 'string', description: 'Lead notes', optional: true },
status: { type: 'string', description: 'Lead status', optional: true },
custom_variables: {
type: 'array',
description: 'Lead custom variables',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Custom variable name' },
value: { type: 'string', description: 'Custom variable value', optional: true },
},
},
},
lead_campaign_data: {
type: 'array',
description: 'Lead campaign data returned by Email Bison',
},
overall_stats: {
type: 'object',
description: 'Lead engagement stats',
properties: {
emails_sent: { type: 'number', description: 'Emails sent' },
opens: { type: 'number', description: 'Email opens' },
replies: { type: 'number', description: 'Replies' },
unique_replies: { type: 'number', description: 'Unique replies' },
unique_opens: { type: 'number', description: 'Unique opens' },
},
},
created_at: { type: 'string', description: 'Lead creation timestamp', optional: true },
updated_at: { type: 'string', description: 'Lead update timestamp', optional: true },
} satisfies NonNullable<ToolConfig['outputs']>
const tagProperties = {
id: { type: 'number', description: 'Tag ID' },
name: { type: 'string', description: 'Tag name' },
default: { type: 'boolean', description: 'Whether this is a default tag' },
created_at: { type: 'string', description: 'Tag creation timestamp', optional: true },
updated_at: { type: 'string', description: 'Tag update timestamp', optional: true },
} satisfies Record<string, OutputProperty>
const campaignTagProperties = {
id: { type: 'number', description: 'Tag ID' },
name: { type: 'string', description: 'Tag name' },
default: { type: 'boolean', description: 'Whether this is a default tag' },
} satisfies Record<string, OutputProperty>
export const listLeadsOutputs = {
leads: {
type: 'array',
description: 'List of leads',
items: {
type: 'object',
properties: leadOutputs,
},
},
count: { type: 'number', description: 'Number of leads returned' },
} satisfies NonNullable<ToolConfig['outputs']>
export const campaignOutputs = {
id: { type: 'number', description: 'Campaign ID' },
uuid: { type: 'string', description: 'Campaign UUID', optional: true },
name: { type: 'string', description: 'Campaign name' },
type: { type: 'string', description: 'Campaign type', optional: true },
status: { type: 'string', description: 'Campaign status', optional: true },
emails_sent: { type: 'number', description: 'Emails sent' },
opened: { type: 'number', description: 'Total opens' },
unique_opens: { type: 'number', description: 'Unique opens' },
replied: { type: 'number', description: 'Total replies' },
unique_replies: { type: 'number', description: 'Unique replies' },
bounced: { type: 'number', description: 'Bounces' },
unsubscribed: { type: 'number', description: 'Unsubscribes' },
interested: { type: 'number', description: 'Interested replies' },
total_leads_contacted: { type: 'number', description: 'Total leads contacted' },
total_leads: { type: 'number', description: 'Total leads' },
max_emails_per_day: { type: 'number', description: 'Maximum emails per day', optional: true },
max_new_leads_per_day: {
type: 'number',
description: 'Maximum new leads per day',
optional: true,
},
plain_text: {
type: 'boolean',
description: 'Whether campaign emails are plain text',
optional: true,
},
open_tracking: {
type: 'boolean',
description: 'Whether open tracking is enabled',
optional: true,
},
can_unsubscribe: {
type: 'boolean',
description: 'Whether recipients can unsubscribe',
optional: true,
},
unsubscribe_text: { type: 'string', description: 'Unsubscribe text', optional: true },
tags: {
type: 'array',
description: 'Campaign tags',
items: { type: 'object', properties: campaignTagProperties },
},
created_at: { type: 'string', description: 'Campaign creation timestamp', optional: true },
updated_at: { type: 'string', description: 'Campaign update timestamp', optional: true },
} satisfies NonNullable<ToolConfig['outputs']>
export const listCampaignsOutputs = {
campaigns: {
type: 'array',
description: 'List of campaigns',
items: {
type: 'object',
properties: campaignOutputs,
},
},
count: { type: 'number', description: 'Number of campaigns returned' },
} satisfies NonNullable<ToolConfig['outputs']>
export const actionOutputs = {
success: { type: 'boolean', description: 'Whether the action succeeded' },
message: { type: 'string', description: 'Action message', optional: true },
} satisfies NonNullable<ToolConfig['outputs']>
export const listRepliesOutputs = {
replies: {
type: 'array',
description: 'List of replies',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Reply ID' },
subject: { type: 'string', description: 'Reply subject', optional: true },
text_body: { type: 'string', description: 'Reply text body', optional: true },
from_email_address: { type: 'string', description: 'Sender email', optional: true },
primary_to_email_address: {
type: 'string',
description: 'Primary recipient',
optional: true,
},
date_received: { type: 'string', description: 'Date received', optional: true },
interested: { type: 'boolean', description: 'Whether the reply is marked interested' },
read: { type: 'boolean', description: 'Whether the reply is read' },
},
},
},
count: { type: 'number', description: 'Number of replies returned' },
} satisfies NonNullable<ToolConfig['outputs']>
export const tagOutputs = {
id: { type: 'number', description: 'Tag ID' },
name: { type: 'string', description: 'Tag name' },
default: { type: 'boolean', description: 'Whether this is a default tag' },
created_at: { type: 'string', description: 'Tag creation timestamp', optional: true },
updated_at: { type: 'string', description: 'Tag update timestamp', optional: true },
} satisfies NonNullable<ToolConfig['outputs']>
export const listTagsOutputs = {
tags: {
type: 'array',
description: 'List of tags',
items: {
type: 'object',
properties: tagProperties,
},
},
count: { type: 'number', description: 'Number of tags returned' },
} satisfies NonNullable<ToolConfig['outputs']>
function mapLeadStats(record: Record<string, unknown>): EmailBisonLeadStats {
return {
emails_sent: toNullableNumber(record.emails_sent),
opens: toNullableNumber(record.opens),
replies: toNullableNumber(record.replies),
unique_replies: toNullableNumber(record.unique_replies),
unique_opens: toNullableNumber(record.unique_opens),
}
}
function mapReplyAddress(value: unknown): EmailBisonReplyAddress {
const record = toRecord(value)
return {
name: toStringOrNull(record.name),
address: toStringOrNull(record.address),
}
}
function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
uuid: toStringOrNull(record.uuid),
reply_id: toNullableNumber(record.reply_id),
file_name: toStringOrNull(record.file_name),
download_url: toStringOrNull(record.download_url),
created_at: toStringOrNull(record.created_at),
updated_at: toStringOrNull(record.updated_at),
}
}
function toRecord(value: unknown): Record<string, unknown> {
return isRecordLike(value) ? value : {}
}
function toArray(value: unknown): unknown[] {
return Array.isArray(value) ? value : []
}
function toStringOrNull(value: unknown): string | null {
if (value === undefined || value === null) return null
return String(value)
}
function toStringNumberOrNull(value: unknown): number | string | null {
if (typeof value === 'number' || typeof value === 'string') return value
return null
}
function toNumber(value: unknown): number | null {
if (typeof value === 'number') return value
if (typeof value === 'string') {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}
function toNullableNumber(value: unknown): number | null {
if (value === undefined || value === null) return null
return toNumber(value)
}
function toNullableBoolean(value: unknown): boolean | null {
if (value === undefined || value === null) return null
return typeof value === 'boolean' ? value : null
}