import * as z from "zod" import { defineAction } from "../../../automation/actions" import { apolloAccountOptions, getApolloApi } from "../lib/api" import { APOLLO_EMAIL_ACCOUNT_SCHEMA, APOLLO_PAGINATION_SCHEMA, APOLLO_PROVIDER_EMAIL_ACCOUNT_SCHEMA, APOLLO_PROVIDER_PAGINATION_SCHEMA, APOLLO_PROVIDER_USER_SCHEMA, APOLLO_USER_SCHEMA, toApolloEmailAccount, toApolloPagination, toApolloUser, } from "../lib/schemas" const APOLLO_CREDIT_USAGE_SCHEMA = z.object({ /** AI credits available during the current allowance period. */ aiAllowance: z.number().int().nonnegative().optional(), /** AI credits consumed during the current allowance period. */ aiUsed: z.number().int().nonnegative().optional(), /** Direct-dial credits available during the current allowance period. */ directDialAllowance: z.number().int().nonnegative().optional(), /** Direct-dial credits consumed during the current allowance period. */ directDialUsed: z.number().int().nonnegative().optional(), /** Export credits available during the current allowance period. */ exportAllowance: z.number().int().nonnegative().optional(), /** Export credits consumed during the current allowance period. */ exportUsed: z.number().int().nonnegative().optional(), /** Lead credits available during the current allowance period. */ leadAllowance: z.number().int().nonnegative().optional(), /** Remaining lead credits reported by Apollo. */ leadRemaining: z.number().int().nonnegative().optional(), /** Lead credits consumed during the current allowance period. */ leadUsed: z.number().int().nonnegative().optional(), /** Power-up credits available during the current allowance period. */ powerUpAllowance: z.number().int().nonnegative().optional(), /** Power-up credits consumed during the current allowance period. */ powerUpUsed: z.number().int().nonnegative().optional(), /** Total credits consumed under Apollo's unified-credit model. */ unifiedUsed: z.number().int().nonnegative().optional(), }) const APOLLO_CURRENT_USER_SCHEMA = APOLLO_USER_SCHEMA.extend({ /** Optional credit balances and usage requested from Apollo. */ creditUsage: APOLLO_CREDIT_USAGE_SCHEMA.optional(), }) const APOLLO_CURRENT_USER_RESPONSE_SCHEMA = APOLLO_PROVIDER_USER_SCHEMA.extend({ effective_num_ai_credits: z.number().int().nonnegative().nullish(), effective_num_direct_dial_credits: z.number().int().nonnegative().nullish(), effective_num_export_credits: z.number().int().nonnegative().nullish(), effective_num_lead_credits: z.number().int().nonnegative().nullish(), effective_num_power_up_credits: z.number().int().nonnegative().nullish(), num_ai_credits_used: z.number().int().nonnegative().nullish(), num_credits_remaining: z.number().int().nonnegative().nullish(), num_direct_dial_credits_used: z.number().int().nonnegative().nullish(), num_export_credits_used: z.number().int().nonnegative().nullish(), num_lead_credits_used: z.number().int().nonnegative().nullish(), num_power_up_credits_used: z.number().int().nonnegative().nullish(), total_unified_credits_used: z.number().int().nonnegative().nullish(), }) const APOLLO_USER_PAGE_RESPONSE_SCHEMA = z.looseObject({ pagination: APOLLO_PROVIDER_PAGINATION_SCHEMA, users: APOLLO_PROVIDER_USER_SCHEMA.array(), }) const APOLLO_EMAIL_ACCOUNTS_RESPONSE_SCHEMA = z.looseObject({ email_accounts: APOLLO_PROVIDER_EMAIL_ACCOUNT_SCHEMA.array(), }) const APOLLO_FIELD_SOURCE_SCHEMA = z.enum(["crm_synced", "custom", "system"]) const APOLLO_PROVIDER_FIELD_SCHEMA = z.looseObject({ context: z.union([z.string(), z.string().array()]).nullish(), created_at: z.iso.datetime({ offset: true }).nullish(), id: z.string(), label: z.string(), meta: z.record(z.string(), z.json()).nullish(), modality: z.string(), project_workspace_id: z.string().nullish(), source: z.string().nullish(), type: z.string(), updated_at: z.iso.datetime({ offset: true }).nullish(), }) const APOLLO_FIELD_SCHEMA = z.object({ /** Apollo contexts in which the field is available. */ contexts: z.string().array(), /** Time at which Apollo created the field. */ createdAt: z.date().optional(), /** Stable system or custom field identifier. */ fieldId: z.string(), /** Human-readable field label. */ label: z.string(), /** Extended provider field configuration. */ meta: z.record(z.string(), z.json()).optional(), /** Apollo resource type that owns the field. */ modality: z.string(), /** Provider field source. */ source: z.string().optional(), /** Provider field data type. */ type: z.string(), /** Time at which Apollo last updated the field. */ updatedAt: z.date().optional(), /** Project workspace that owns the field, when applicable. */ workspaceId: z.string().optional(), }) const APOLLO_FIELDS_RESPONSE_SCHEMA = z.looseObject({ fields: APOLLO_PROVIDER_FIELD_SCHEMA.array(), }) const APOLLO_PROVIDER_USAGE_WINDOW_SCHEMA = z.looseObject({ consumed: z.number().int().nonnegative(), left_over: z.number().int().nonnegative(), limit: z.number().int().nonnegative(), }) const APOLLO_PROVIDER_USAGE_LIMIT_SCHEMA = z.looseObject({ day: APOLLO_PROVIDER_USAGE_WINDOW_SCHEMA.optional(), hour: APOLLO_PROVIDER_USAGE_WINDOW_SCHEMA.optional(), minute: APOLLO_PROVIDER_USAGE_WINDOW_SCHEMA.optional(), }) const APOLLO_PROVIDER_API_USAGE_SCHEMA = z.record( z.string(), APOLLO_PROVIDER_USAGE_LIMIT_SCHEMA, ) const APOLLO_USAGE_WINDOW_SCHEMA = z.object({ /** Requests consumed in this window. */ consumed: z.number().int().nonnegative(), /** Requests remaining in this window. */ remaining: z.number().int().nonnegative(), /** Maximum requests allowed in this window. */ limit: z.number().int().nonnegative(), }) const APOLLO_API_USAGE_SCHEMA = z.object({ /** Provider operation and route key. */ endpoint: z.string(), /** Daily API limit and consumption. */ day: APOLLO_USAGE_WINDOW_SCHEMA.optional(), /** Hourly API limit and consumption. */ hour: APOLLO_USAGE_WINDOW_SCHEMA.optional(), /** Per-minute API limit and consumption. */ minute: APOLLO_USAGE_WINDOW_SCHEMA.optional(), }) /** Gets the Apollo profile associated with the connected account. */ export const getCurrentApolloUser = defineAction("Get current Apollo user") .describe("Gets the user profile associated with the connected account.") .account("apollo", apolloAccountOptions("read_user_profile")) .input( z.object({ /** Include current credit allowances and consumption. */ includeCreditUsage: z.boolean().prefault(false), }), ) .output(APOLLO_CURRENT_USER_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const profile = await getApolloApi(account).request("/users/api_profile", { query: { include_credit_usage: input.includeCreditUsage }, responseSchema: APOLLO_CURRENT_USER_RESPONSE_SCHEMA, }) return { ...toApolloUser(profile), ...(input.includeCreditUsage && { creditUsage: { aiAllowance: profile.effective_num_ai_credits ?? undefined, aiUsed: profile.num_ai_credits_used ?? undefined, directDialAllowance: profile.effective_num_direct_dial_credits ?? undefined, directDialUsed: profile.num_direct_dial_credits_used ?? undefined, exportAllowance: profile.effective_num_export_credits ?? undefined, exportUsed: profile.num_export_credits_used ?? undefined, leadAllowance: profile.effective_num_lead_credits ?? undefined, leadRemaining: profile.num_credits_remaining ?? undefined, leadUsed: profile.num_lead_credits_used ?? undefined, powerUpAllowance: profile.effective_num_power_up_credits ?? undefined, powerUpUsed: profile.num_power_up_credits_used ?? undefined, unifiedUsed: profile.total_unified_credits_used ?? undefined, }, }), } }) /** Lists users in the connected Apollo workspace. */ export const listApolloUsers = defineAction("List Apollo users") .describe("Lists a page of users in the connected Apollo workspace.") .account("apollo", apolloAccountOptions("users_list")) .input( z.object({ /** One-based result page. */ page: z.number().int().min(1).prefault(1), /** Number of users to request per page. */ perPage: z.number().int().min(1).max(100).prefault(100), }), ) .output( z.object({ pagination: APOLLO_PAGINATION_SCHEMA, users: APOLLO_USER_SCHEMA.array(), }), ) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const result = await getApolloApi(account).request("/users/search", { query: { page: input.page, per_page: input.perPage }, responseSchema: APOLLO_USER_PAGE_RESPONSE_SCHEMA, }) return { pagination: toApolloPagination(result.pagination), users: result.users.map(toApolloUser), } }) /** Lists linked Apollo sending mailboxes. */ export const listApolloEmailAccounts = defineAction( "List Apollo email accounts", ) .describe("Lists email accounts connected to the Apollo workspace.") .account("apollo", apolloAccountOptions("email_accounts_list")) .output(z.object({ emailAccounts: APOLLO_EMAIL_ACCOUNT_SCHEMA.array() })) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => ({ emailAccounts: ( await getApolloApi(account).request("/email_accounts", { responseSchema: APOLLO_EMAIL_ACCOUNTS_RESPONSE_SCHEMA, }) ).email_accounts.map(toApolloEmailAccount), })) /** Lists system, custom, and CRM-synced Apollo fields. */ export const listApolloFields = defineAction("List Apollo fields") .describe("Lists fields available in the connected Apollo workspace.") .account("apollo", apolloAccountOptions("custom_fields_list")) .input( z.object({ /** Limit fields to one provider source. */ source: APOLLO_FIELD_SOURCE_SCHEMA.optional(), }), ) .output(z.object({ fields: APOLLO_FIELD_SCHEMA.array() })) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ({ fields: ( await getApolloApi(account).request("/fields", { query: { source: input.source }, responseSchema: APOLLO_FIELDS_RESPONSE_SCHEMA, }) ).fields.map(toApolloField), })) /** Gets Apollo API consumption and limits by provider endpoint. */ export const getApolloApiUsage = defineAction("Get Apollo API usage") .describe("Gets current Apollo API rate limits and consumption.") .account("apollo", apolloAccountOptions("api_usage_stats_read")) .output(z.object({ endpoints: APOLLO_API_USAGE_SCHEMA.array() })) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => ({ endpoints: Object.entries( await getApolloApi(account).request("/usage_stats/api_usage_stats", { method: "POST", responseSchema: APOLLO_PROVIDER_API_USAGE_SCHEMA, }), ).map(([endpoint, usage]) => ({ endpoint, ...(usage.day && { day: toApolloUsageWindow(usage.day) }), ...(usage.hour && { hour: toApolloUsageWindow(usage.hour) }), ...(usage.minute && { minute: toApolloUsageWindow(usage.minute) }), })), })) /** * Normalizes one Apollo field. * * @param field - Provider field response. */ function toApolloField(field: z.output) { return { contexts: typeof field.context === "string" ? [field.context] : (field.context ?? []), createdAt: field.created_at ? new Date(field.created_at) : undefined, fieldId: field.id, label: field.label, meta: field.meta ?? undefined, modality: field.modality, source: field.source ?? undefined, type: field.type, updatedAt: field.updated_at ? new Date(field.updated_at) : undefined, workspaceId: field.project_workspace_id ?? undefined, } } /** * Normalizes one Apollo API usage window. * * @param usage - Provider usage window. */ function toApolloUsageWindow( usage: z.output, ) { return { consumed: usage.consumed, limit: usage.limit, remaining: usage.left_over, } }