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
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:
@@ -0,0 +1,372 @@
|
||||
import { filterUndefined, isRecordLike } from '@sim/utils/object'
|
||||
import type {
|
||||
InstantlyCampaign,
|
||||
InstantlyEmail,
|
||||
InstantlyLead,
|
||||
InstantlyLeadList,
|
||||
} from '@/tools/instantly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const INSTANTLY_API_BASE_URL = 'https://api.instantly.ai'
|
||||
|
||||
type InstantlyBaseParams = { apiKey: string }
|
||||
type JsonRecord = Record<string, unknown>
|
||||
|
||||
export const instantlyBaseParamFields = {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Instantly API key with the required V2 scopes',
|
||||
},
|
||||
} satisfies ToolConfig<InstantlyBaseParams>['params']
|
||||
|
||||
export const instantlyHeaders = (params: InstantlyBaseParams) => ({
|
||||
Authorization: `Bearer ${params.apiKey.trim()}`,
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
export function instantlyUrl(path: string, query?: Record<string, unknown>): string {
|
||||
const url = new URL(path, INSTANTLY_API_BASE_URL)
|
||||
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
url.searchParams.append(key, String(value))
|
||||
}
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
export function compactBody(values: Record<string, unknown>): Record<string, unknown> {
|
||||
return filterUndefined(values)
|
||||
}
|
||||
|
||||
export async function parseInstantlyResponse(response: Response): Promise<unknown> {
|
||||
const data = await parseJsonResponse(response)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
extractInstantlyError(data, `Instantly API request failed (${response.status})`)
|
||||
)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export function asRecord(value: unknown): JsonRecord {
|
||||
return isRecordLike(value) ? value : {}
|
||||
}
|
||||
|
||||
export function getItems(value: unknown): JsonRecord[] {
|
||||
const data = asRecord(value)
|
||||
return Array.isArray(data.items) ? data.items.map(asRecord) : []
|
||||
}
|
||||
|
||||
export function getNextStartingAfter(value: unknown): string | null {
|
||||
const data = asRecord(value)
|
||||
return asString(data.next_starting_after)
|
||||
}
|
||||
|
||||
export function mapLead(value: unknown): InstantlyLead {
|
||||
const lead = asRecord(value)
|
||||
|
||||
return {
|
||||
id: asString(lead.id),
|
||||
timestamp_created: asString(lead.timestamp_created),
|
||||
timestamp_updated: asString(lead.timestamp_updated),
|
||||
organization: asString(lead.organization),
|
||||
campaign: asString(lead.campaign),
|
||||
status: asNumber(lead.status),
|
||||
email: asString(lead.email),
|
||||
personalization: asString(lead.personalization),
|
||||
website: asString(lead.website),
|
||||
last_name: asString(lead.last_name),
|
||||
first_name: asString(lead.first_name),
|
||||
company_name: asString(lead.company_name),
|
||||
job_title: asString(lead.job_title),
|
||||
phone: asString(lead.phone),
|
||||
email_open_count: asNumber(lead.email_open_count),
|
||||
email_reply_count: asNumber(lead.email_reply_count),
|
||||
email_click_count: asNumber(lead.email_click_count),
|
||||
company_domain: asString(lead.company_domain),
|
||||
payload: isRecordLike(lead.payload) ? lead.payload : null,
|
||||
lt_interest_status: asNumber(lead.lt_interest_status),
|
||||
}
|
||||
}
|
||||
|
||||
export function mapCampaign(value: unknown): InstantlyCampaign {
|
||||
const campaign = asRecord(value)
|
||||
|
||||
return {
|
||||
id: asString(campaign.id),
|
||||
name: asString(campaign.name),
|
||||
pl_value: asNumber(campaign.pl_value),
|
||||
status: asNumber(campaign.status),
|
||||
is_evergreen: asBoolean(campaign.is_evergreen),
|
||||
timestamp_created: asString(campaign.timestamp_created),
|
||||
timestamp_updated: asString(campaign.timestamp_updated),
|
||||
email_gap: asNumber(campaign.email_gap),
|
||||
daily_limit: asNumber(campaign.daily_limit),
|
||||
daily_max_leads: asNumber(campaign.daily_max_leads),
|
||||
open_tracking: asBoolean(campaign.open_tracking),
|
||||
stop_on_reply: asBoolean(campaign.stop_on_reply),
|
||||
sequences: Array.isArray(campaign.sequences) ? campaign.sequences : [],
|
||||
campaign_schedule: isRecordLike(campaign.campaign_schedule) ? campaign.campaign_schedule : null,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapEmail(value: unknown): InstantlyEmail {
|
||||
const email = asRecord(value)
|
||||
const body = asRecord(email.body)
|
||||
|
||||
return {
|
||||
id: asString(email.id),
|
||||
timestamp_created: asString(email.timestamp_created),
|
||||
timestamp_email: asString(email.timestamp_email),
|
||||
message_id: asString(email.message_id),
|
||||
subject: asString(email.subject),
|
||||
from_address_email: asString(email.from_address_email),
|
||||
to_address_email_list: asString(email.to_address_email_list),
|
||||
cc_address_email_list: asString(email.cc_address_email_list),
|
||||
bcc_address_email_list: asString(email.bcc_address_email_list),
|
||||
reply_to: asString(email.reply_to),
|
||||
body: {
|
||||
text: asString(body.text),
|
||||
html: asString(body.html),
|
||||
},
|
||||
organization_id: asString(email.organization_id),
|
||||
campaign_id: asString(email.campaign_id),
|
||||
subsequence_id: asString(email.subsequence_id),
|
||||
list_id: asString(email.list_id),
|
||||
lead: asString(email.lead),
|
||||
lead_id: asString(email.lead_id),
|
||||
eaccount: asString(email.eaccount),
|
||||
ue_type: asNumber(email.ue_type),
|
||||
is_unread: asNumber(email.is_unread),
|
||||
is_auto_reply: asNumber(email.is_auto_reply),
|
||||
i_status: asNumber(email.i_status),
|
||||
thread_id: asString(email.thread_id),
|
||||
content_preview: asString(email.content_preview),
|
||||
}
|
||||
}
|
||||
|
||||
export function mapLeadList(value: unknown): InstantlyLeadList {
|
||||
const leadList = asRecord(value)
|
||||
|
||||
return {
|
||||
id: asString(leadList.id),
|
||||
organization_id: asString(leadList.organization_id),
|
||||
has_enrichment_task: asBoolean(leadList.has_enrichment_task),
|
||||
owned_by: asString(leadList.owned_by),
|
||||
name: asString(leadList.name),
|
||||
timestamp_created: asString(leadList.timestamp_created),
|
||||
}
|
||||
}
|
||||
|
||||
export const leadOutputs = {
|
||||
lead: {
|
||||
type: 'object',
|
||||
description: 'Lead details',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Lead ID', nullable: true },
|
||||
email: { type: 'string', description: 'Lead email address', nullable: true },
|
||||
first_name: { type: 'string', description: 'Lead first name', nullable: true },
|
||||
last_name: { type: 'string', description: 'Lead last name', nullable: true },
|
||||
company_name: { type: 'string', description: 'Lead company name', nullable: true },
|
||||
job_title: { type: 'string', description: 'Lead job title', nullable: true },
|
||||
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
|
||||
status: { type: 'number', description: 'Lead status', nullable: true },
|
||||
payload: { type: 'json', description: 'Lead custom variables', nullable: true },
|
||||
},
|
||||
},
|
||||
id: { type: 'string', description: 'Lead ID', optional: true },
|
||||
email_address: { type: 'string', description: 'Lead email address', optional: true },
|
||||
first_name: { type: 'string', description: 'Lead first name', optional: true },
|
||||
last_name: { type: 'string', description: 'Lead last name', optional: true },
|
||||
campaign: { type: 'string', description: 'Campaign ID', optional: true },
|
||||
status: { type: 'number', description: 'Lead status', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const leadsListOutputs = {
|
||||
leads: {
|
||||
type: 'array',
|
||||
description: 'List of leads',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Lead ID', nullable: true },
|
||||
email: { type: 'string', description: 'Lead email address', nullable: true },
|
||||
first_name: { type: 'string', description: 'Lead first name', nullable: true },
|
||||
last_name: { type: 'string', description: 'Lead last name', nullable: true },
|
||||
company_name: { type: 'string', description: 'Lead company name', nullable: true },
|
||||
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
|
||||
status: { type: 'number', description: 'Lead status', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of leads returned' },
|
||||
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const campaignOutputs = {
|
||||
campaign: {
|
||||
type: 'object',
|
||||
description: 'Campaign details',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Campaign ID', nullable: true },
|
||||
name: { type: 'string', description: 'Campaign name', nullable: true },
|
||||
status: { type: 'number', description: 'Campaign status', nullable: true },
|
||||
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
|
||||
daily_max_leads: { type: 'number', description: 'Daily max new leads', nullable: true },
|
||||
open_tracking: {
|
||||
type: 'boolean',
|
||||
description: 'Whether open tracking is enabled',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
id: { type: 'string', description: 'Campaign ID', optional: true },
|
||||
name: { type: 'string', description: 'Campaign name', optional: true },
|
||||
status: { type: 'number', description: 'Campaign status', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const campaignActionOutputs = {
|
||||
...campaignOutputs,
|
||||
message: { type: 'string', description: 'Confirmation message from Instantly', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const campaignsListOutputs = {
|
||||
campaigns: {
|
||||
type: 'array',
|
||||
description: 'List of campaigns',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Campaign ID', nullable: true },
|
||||
name: { type: 'string', description: 'Campaign name', nullable: true },
|
||||
status: { type: 'number', description: 'Campaign status', nullable: true },
|
||||
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of campaigns returned' },
|
||||
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const emailOutputs = {
|
||||
email: {
|
||||
type: 'object',
|
||||
description: 'Email details',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email ID', nullable: true },
|
||||
subject: { type: 'string', description: 'Email subject', nullable: true },
|
||||
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
|
||||
to_address_email_list: {
|
||||
type: 'string',
|
||||
description: 'Recipient email list',
|
||||
nullable: true,
|
||||
},
|
||||
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
|
||||
content_preview: { type: 'string', description: 'Email content preview', nullable: true },
|
||||
},
|
||||
},
|
||||
id: { type: 'string', description: 'Email ID', optional: true },
|
||||
subject: { type: 'string', description: 'Email subject', optional: true },
|
||||
thread_id: { type: 'string', description: 'Thread ID', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const emailsListOutputs = {
|
||||
emails: {
|
||||
type: 'array',
|
||||
description: 'List of emails',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email ID', nullable: true },
|
||||
subject: { type: 'string', description: 'Email subject', nullable: true },
|
||||
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
|
||||
lead: { type: 'string', description: 'Lead email', nullable: true },
|
||||
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of emails returned' },
|
||||
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const leadListOutputs = {
|
||||
lead_list: {
|
||||
type: 'object',
|
||||
description: 'Lead list details',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Lead list ID', nullable: true },
|
||||
organization_id: { type: 'string', description: 'Organization ID', nullable: true },
|
||||
has_enrichment_task: {
|
||||
type: 'boolean',
|
||||
description: 'Whether enrichment is enabled',
|
||||
nullable: true,
|
||||
},
|
||||
owned_by: { type: 'string', description: 'Owner user ID', nullable: true },
|
||||
name: { type: 'string', description: 'Lead list name', nullable: true },
|
||||
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
|
||||
},
|
||||
},
|
||||
id: { type: 'string', description: 'Lead list ID', optional: true },
|
||||
name: { type: 'string', description: 'Lead list name', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
export const leadListsListOutputs = {
|
||||
lead_lists: {
|
||||
type: 'array',
|
||||
description: 'List of lead lists',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Lead list ID', nullable: true },
|
||||
name: { type: 'string', description: 'Lead list name', nullable: true },
|
||||
has_enrichment_task: {
|
||||
type: 'boolean',
|
||||
description: 'Whether enrichment is enabled',
|
||||
nullable: true,
|
||||
},
|
||||
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of lead lists returned' },
|
||||
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
||||
} satisfies ToolConfig['outputs']
|
||||
|
||||
async function parseJsonResponse(response: Response): Promise<unknown> {
|
||||
try {
|
||||
return await response.json()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getMessage(value: unknown): string | null {
|
||||
const data = asRecord(value)
|
||||
return typeof data.message === 'string' ? data.message : null
|
||||
}
|
||||
|
||||
function extractInstantlyError(value: unknown, fallback: string): string {
|
||||
const data = asRecord(value)
|
||||
if (typeof data.message === 'string') return data.message
|
||||
if (typeof data.error === 'string') return data.error
|
||||
return fallback
|
||||
}
|
||||
|
||||
function asString(value: unknown): string | null {
|
||||
return typeof value === 'string' ? value : null
|
||||
}
|
||||
|
||||
function asNumber(value: unknown): number | null {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : null
|
||||
}
|
||||
|
||||
function asBoolean(value: unknown): boolean | null {
|
||||
return typeof value === 'boolean' ? value : null
|
||||
}
|
||||
Reference in New Issue
Block a user