@dench.com/cli 3.5.6-staging.344 → 3.6.0-staging.356

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,17 @@
19
19
  * stays lossless for the playground.
20
20
  */
21
21
  import { type ZodTypeAny, z } from "zod";
22
+ import {
23
+ checkCampaignSteps,
24
+ checkStepEdits,
25
+ LINKEDIN_REACTIONS,
26
+ MAX_CAMPAIGN_STEPS,
27
+ MAX_INMAIL_BODY_CHARS,
28
+ MAX_INMAIL_SUBJECT_CHARS,
29
+ MAX_LIKE_POST_AGE_DAYS,
30
+ MAX_WAIT_HOURS,
31
+ MIN_WITHDRAW_AFTER_HOURS,
32
+ } from "./campaign-steps";
22
33
 
23
34
  // ---------------------------------------------------------------------------
24
35
  // Shared enums + fragments (mirror convex validators)
@@ -330,6 +341,185 @@ const campaignSenderPools = {
330
341
  ),
331
342
  };
332
343
 
344
+ const campaignLineVariable = z
345
+ .object({
346
+ key: z
347
+ .string()
348
+ .describe(
349
+ "The {{key}} used in steps: a letter, then letters, digits or _ (up to 40). Not a built-in field like firstName or company.",
350
+ ),
351
+ label: z.string().describe("The line's name, up to 60 characters."),
352
+ prompt: z
353
+ .string()
354
+ .describe("What the writer should say, up to 600 characters."),
355
+ fallback: z
356
+ .string()
357
+ .describe(
358
+ "Sent when there is nothing specific to say; up to 300 characters, no {{variables}}.",
359
+ ),
360
+ fallbackFrom: z
361
+ .string()
362
+ .optional()
363
+ .describe("A field to use before the fallback, e.g. Company."),
364
+ maxChars: z
365
+ .number()
366
+ .int()
367
+ .min(20)
368
+ .max(300)
369
+ .optional()
370
+ .describe("The longest line, default 200 characters."),
371
+ })
372
+ .strict()
373
+ .describe("One personalized line.");
374
+
375
+ const campaignLineTemplate = z
376
+ .object({
377
+ variables: z.array(campaignLineVariable).min(1).max(5),
378
+ context: z
379
+ .object({
380
+ sells: z.string().describe("What you sell, up to 1000 characters."),
381
+ offer: z
382
+ .string()
383
+ .optional()
384
+ .describe("The offer or call to action, up to 300 characters."),
385
+ })
386
+ .strict(),
387
+ })
388
+ .strict();
389
+
390
+ const campaignLineBackfill = z.object({
391
+ version: z.number(),
392
+ eligible: z.number().describe("People who would get a line."),
393
+ estimatedCents: z.number().describe("About 3 cents a person."),
394
+ needsConfirm: z
395
+ .boolean()
396
+ .describe(
397
+ "True when nothing was written: confirm with campaigns.personalization.write and confirmed: true.",
398
+ ),
399
+ queued: z.number().describe("People whose lines are being written now."),
400
+ });
401
+
402
+ const campaignPreviewedLine = z
403
+ .object({
404
+ key: z.string().describe("The line's {{key}}."),
405
+ label: z.string(),
406
+ value: z
407
+ .string()
408
+ .describe("What this person gets: the written line, or the fallback."),
409
+ state: z
410
+ .enum([
411
+ "written",
412
+ "edited",
413
+ "writing",
414
+ "paused",
415
+ "fallback",
416
+ "not_written",
417
+ ])
418
+ .describe(
419
+ "written or edited: the line is final. fallback: nothing specific was found, so the fallback is used. writing, paused and not_written: the fallback shows until the line is written.",
420
+ ),
421
+ source: z
422
+ .enum(["company", "news", "signal", "profile", "fallback"])
423
+ .optional()
424
+ .describe("What the line was written from."),
425
+ maxChars: z.number(),
426
+ rewriting: z
427
+ .enum(["writing", "paused"])
428
+ .optional()
429
+ .describe("A new line is being written over the current one."),
430
+ })
431
+ .passthrough();
432
+
433
+ const campaignMessagePreview = z
434
+ .object({
435
+ channel: z.enum(["email", "linkedin"]),
436
+ subject: z.string().optional().describe("Email steps."),
437
+ textBody: z.string().optional().describe("Email steps: plain text."),
438
+ htmlBody: z.string().optional().describe("Email steps: HTML."),
439
+ messageBody: z
440
+ .string()
441
+ .optional()
442
+ .describe(
443
+ "LinkedIn steps: the note or message, or a line saying no note or message is sent.",
444
+ ),
445
+ fromName: z.string().optional().describe("The sending account."),
446
+ fromEmail: z.string().optional(),
447
+ toName: z.string(),
448
+ toEmail: z.string().optional(),
449
+ missingFields: z
450
+ .array(z.string())
451
+ .describe(
452
+ "{{merge fields}} this person has no value for; fill them with campaigns.people.setField.",
453
+ ),
454
+ warnings: z.array(z.string()),
455
+ skippedReason: z
456
+ .string()
457
+ .optional()
458
+ .describe(
459
+ "Set when the person would skip this step, e.g. no email address.",
460
+ ),
461
+ highlights: z.array(z.string()).optional(),
462
+ personalized: z.array(campaignPreviewedLine).optional(),
463
+ })
464
+ .passthrough();
465
+
466
+ const campaignSenderLabel = z
467
+ .object({
468
+ accountId: z.string(),
469
+ label: z.string().describe("The mailbox address or LinkedIn name."),
470
+ status: z.string(),
471
+ })
472
+ .nullable();
473
+
474
+ const campaignMembership = z
475
+ .object({
476
+ enrollment: z
477
+ .record(z.string(), z.unknown())
478
+ .describe(
479
+ "The person in that campaign: _id, sequenceId, status, currentStepIndex, repliedAt.",
480
+ ),
481
+ sequenceName: z.string(),
482
+ isOwner: z.boolean(),
483
+ canManage: z.boolean(),
484
+ senders: z
485
+ .object({ email: campaignSenderLabel, linkedin: campaignSenderLabel })
486
+ .describe("The accounts that send to this person."),
487
+ })
488
+ .passthrough();
489
+
490
+ const campaignMailboxSummary = z
491
+ .object({
492
+ _id: z
493
+ .string()
494
+ .describe("The emailMailboxAccountId to put in emailMailboxAccountIds."),
495
+ email: z.string(),
496
+ provider: z.enum(["gmail", "outlook"]),
497
+ status: z.enum(["running", "disconnected", "errored"]),
498
+ statusReason: z.string().optional(),
499
+ warmupStatus: z
500
+ .enum(["skipped", "pending_auth", "warming", "ready", "error"])
501
+ .describe(
502
+ "ready or skipped: sends at its cap. warming: sends less until readyAt. pending_auth and error need the owner in the app.",
503
+ ),
504
+ readyAt: z
505
+ .number()
506
+ .optional()
507
+ .describe(
508
+ "When warm-up finished, or is expected to finish while warming (epoch ms).",
509
+ ),
510
+ dailyColdSendCap: z
511
+ .number()
512
+ .describe(
513
+ "The most cold emails a day; the mailbox owner sets it in the app.",
514
+ ),
515
+ remainingToday: z.number().describe("Cold emails it can still send today."),
516
+ ownerName: z.string(),
517
+ isOwner: z.boolean(),
518
+ visibility: z.enum(["private", "shared"]),
519
+ })
520
+ .passthrough()
521
+ .describe("One mailbox a campaign can send from.");
522
+
333
523
  const campaignLinkedinSenderSummary = z
334
524
  .object({
335
525
  linkedinAccountId: z
@@ -405,6 +595,8 @@ const campaignStepType = z
405
595
  "linkedin_visit",
406
596
  "linkedin_connection",
407
597
  "linkedin_message",
598
+ "linkedin_like_post",
599
+ "linkedin_inmail",
408
600
  ])
409
601
  .describe("Step type.");
410
602
 
@@ -425,8 +617,18 @@ const campaignStepInput = z
425
617
  .string()
426
618
  .optional()
427
619
  .describe("email steps: subject; {{Column}} merge tokens allowed."),
428
- emailHtmlBody: z.string().optional(),
429
- emailTextBody: z.string().optional(),
620
+ emailHtmlBody: z
621
+ .string()
622
+ .optional()
623
+ .describe(
624
+ "email steps: HTML body. Sent instead of emailTextBody when both are set, so when you edit the text, update or drop this too.",
625
+ ),
626
+ emailTextBody: z
627
+ .string()
628
+ .optional()
629
+ .describe(
630
+ "email steps: plain-text body; used as the email when there is no emailHtmlBody.",
631
+ ),
430
632
  emailReplyToPrevious: z
431
633
  .boolean()
432
634
  .optional()
@@ -437,7 +639,7 @@ const campaignStepInput = z
437
639
  .enum(["always", "connected", "not_connected"])
438
640
  .optional()
439
641
  .describe(
440
- "Run this step only when the person is / is not a LinkedIn connection.",
642
+ "email and linkedin_like_post only: always (default), connected or not_connected. InMail automatically skips connected people; do not set a condition on it.",
441
643
  ),
442
644
  linkedinAccountId: z
443
645
  .string()
@@ -449,7 +651,7 @@ const campaignStepInput = z
449
651
  .string()
450
652
  .optional()
451
653
  .describe(
452
- "linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the message (up to 3000 chars).",
654
+ `linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the required message (up to 3000 chars); linkedin_inmail: the required message (up to ${MAX_INMAIL_BODY_CHARS} chars).`,
453
655
  ),
454
656
  linkedinMessageMode: z
455
657
  .enum(["skip_if_not_connected", "wait_until_connected"])
@@ -459,13 +661,113 @@ const campaignStepInput = z
459
661
  ),
460
662
  connectionTimeoutHours: z
461
663
  .number()
664
+ .min(1)
665
+ .max(MAX_WAIT_HOURS)
666
+ .optional()
667
+ .describe(
668
+ `linkedin_message with linkedinMessageMode=wait_until_connected: wait 1-${MAX_WAIT_HOURS} hours for acceptance (default 720). After a connection step this mode is the default.`,
669
+ ),
670
+ likeReaction: z
671
+ .enum(LINKEDIN_REACTIONS)
672
+ .optional()
673
+ .describe(
674
+ "linkedin_like_post only: the reaction to leave (default like).",
675
+ ),
676
+ likeMaxPostAgeDays: z
677
+ .number()
678
+ .int()
679
+ .min(1)
680
+ .max(MAX_LIKE_POST_AGE_DAYS)
681
+ .optional()
682
+ .describe(
683
+ `linkedin_like_post only: select the newest eligible original post from the last 1-${MAX_LIKE_POST_AGE_DAYS} days (default 30).`,
684
+ ),
685
+ likeSkipNextWait: z
686
+ .boolean()
462
687
  .optional()
463
688
  .describe(
464
- "linkedin_message with linkedinMessageMode=wait_until_connected: give up waiting for the accept after this many hours (default 720).",
689
+ "linkedin_like_post only: skip consecutive following wait steps whenever the reaction step is skipped (default true). False keeps those waits.",
690
+ ),
691
+ inmailSubject: z
692
+ .string()
693
+ .optional()
694
+ .describe(
695
+ `linkedin_inmail only: optional subject, up to ${MAX_INMAIL_SUBJECT_CHARS} characters; merge tokens allowed. Clear with an empty string.`,
696
+ ),
697
+ withdrawAfterHours: z
698
+ .number()
699
+ .min(MIN_WITHDRAW_AFTER_HOURS)
700
+ .max(MAX_WAIT_HOURS)
701
+ .nullable()
702
+ .optional()
703
+ .describe(
704
+ `linkedin_connection only: withdraw a still-pending invitation sent by this campaign after ${MIN_WITHDRAW_AFTER_HOURS}-${MAX_WAIT_HOURS} hours (7-90 days). Omitted means off on creation; null turns it off on edit.`,
465
705
  ),
466
706
  })
467
707
  .describe("One campaign step, in order.");
468
708
 
709
+ const campaignTimelineRun = z
710
+ .object({
711
+ _id: z.string(),
712
+ stepId: z.string().describe("The step's _id, as in steps[]."),
713
+ stepIndex: z.number().describe("The step's place, from 0."),
714
+ type: campaignStepType,
715
+ status: z
716
+ .enum([
717
+ "queued",
718
+ "running",
719
+ "waiting",
720
+ "retry",
721
+ "sent",
722
+ "skipped",
723
+ "failed",
724
+ "unknown",
725
+ ])
726
+ .describe(
727
+ "sent: done (a wait that finished is sent too). skipped: see result.reason. retry: will be tried again at retryAt. unknown: needs a look.",
728
+ ),
729
+ scheduledAt: z.number().optional(),
730
+ startedAt: z.number().optional(),
731
+ waitUntil: z.number().optional(),
732
+ retryAt: z.number().optional(),
733
+ completedAt: z.number().optional(),
734
+ emailMessageId: z
735
+ .string()
736
+ .optional()
737
+ .describe("The email this run sent, in emailMessages[]."),
738
+ lastError: z.string().optional(),
739
+ result: z
740
+ .unknown()
741
+ .optional()
742
+ .describe("For a skipped step, { reason } says why."),
743
+ })
744
+ .passthrough();
745
+
746
+ const campaignPersonTimeline = z
747
+ .object({
748
+ enrollment: z
749
+ .record(z.string(), z.unknown())
750
+ .describe("The person in this campaign: status, lastError, repliedAt."),
751
+ sequence: z.record(z.string(), z.unknown()).nullable(),
752
+ steps: z
753
+ .array(campaignStepInput.passthrough())
754
+ .describe("The campaign's steps in order, each with its _id."),
755
+ runs: z
756
+ .array(campaignTimelineRun)
757
+ .describe("One run for each step the person has reached, in order."),
758
+ emailMessages: z
759
+ .array(z.record(z.string(), z.unknown()))
760
+ .describe(
761
+ "The emails those runs sent: subject, textBody, status, sentAt, firstOpenedAt, firstClickedAt.",
762
+ ),
763
+ linkedinMessages: z
764
+ .array(z.record(z.string(), z.unknown()))
765
+ .describe(
766
+ "The LinkedIn messages those runs sent: text, sentAt, deliveredAt, readAt, outreachStepRunId.",
767
+ ),
768
+ })
769
+ .passthrough();
770
+
469
771
  const campaignStatus = z
470
772
  .enum(["draft", "active", "paused", "completed", "archived"])
471
773
  .describe("Campaign lifecycle status.");
@@ -1355,7 +1657,7 @@ const exaResultItem = z
1355
1657
  ),
1356
1658
  })
1357
1659
  .passthrough()
1358
- .describe("An Exa search/contents result (provider passthrough).");
1660
+ .describe("A web search result.");
1359
1661
 
1360
1662
  const exaSearchResponse = z
1361
1663
  .object({
@@ -1366,7 +1668,7 @@ const exaSearchResponse = z
1366
1668
  costDollars: z.record(z.string(), z.unknown()).optional(),
1367
1669
  })
1368
1670
  .passthrough()
1369
- .describe("Exa response passed through unchanged.");
1671
+ .describe("A web search response.");
1370
1672
 
1371
1673
  const exaAnswerResponse = z
1372
1674
  .object({
@@ -1376,7 +1678,7 @@ const exaAnswerResponse = z
1376
1678
  })
1377
1679
  .passthrough()
1378
1680
  .describe(
1379
- "Exa /answer response (provider passthrough). With stream: true the response is text/event-stream instead.",
1681
+ "An answer with citations. With stream: true the response is text/event-stream instead.",
1380
1682
  );
1381
1683
 
1382
1684
  const imageEnvelope = z
@@ -1428,7 +1730,7 @@ const composioConnection = z
1428
1730
  reconnect: z.record(z.string(), z.unknown()).optional(),
1429
1731
  })
1430
1732
  .passthrough()
1431
- .describe("A normalized Composio connection.");
1733
+ .describe("A connected app.");
1432
1734
 
1433
1735
  const composioConnectionsResponse = z
1434
1736
  .object({
@@ -1436,7 +1738,11 @@ const composioConnectionsResponse = z
1436
1738
  connections: z
1437
1739
  .array(composioConnection)
1438
1740
  .describe("Alias of items for older clients."),
1439
- raw: z.unknown().describe("Raw Composio payload."),
1741
+ raw: z
1742
+ .unknown()
1743
+ .describe(
1744
+ "Deprecated: the unprocessed provider response. Do not depend on it.",
1745
+ ),
1440
1746
  })
1441
1747
  .passthrough();
1442
1748
 
@@ -1484,7 +1790,7 @@ const toolSearchResponse = z
1484
1790
  execution_ref_version: z.number().optional(),
1485
1791
  })
1486
1792
  .passthrough()
1487
- .describe("Composio tool-router search response with gateway enrichments.");
1793
+ .describe("Tool search response with gateway enrichments.");
1488
1794
 
1489
1795
  const toolRunResponse = z
1490
1796
  .object({
@@ -1501,7 +1807,7 @@ const toolRunResponse = z
1501
1807
  account: z.string().optional(),
1502
1808
  })
1503
1809
  .passthrough()
1504
- .describe("Composio execution result with gateway metadata.");
1810
+ .describe("Tool execution result with gateway metadata.");
1505
1811
 
1506
1812
  const enrichmentMetadata = z
1507
1813
  .object({
@@ -1526,6 +1832,21 @@ const enrichmentMetadata = z
1526
1832
  .passthrough()
1527
1833
  .optional();
1528
1834
 
1835
+ const searchBillingResponse = z.object({
1836
+ estimatedCents: z
1837
+ .number()
1838
+ .int()
1839
+ .nonnegative()
1840
+ .describe("Search cost ceiling authorized by Dench billing, in cents."),
1841
+ chargedCents: z
1842
+ .number()
1843
+ .int()
1844
+ .nonnegative()
1845
+ .describe(
1846
+ "Actual workspace search charge reported by Dench billing, in cents.",
1847
+ ),
1848
+ });
1849
+
1529
1850
  const enrichedPeopleResponse = z
1530
1851
  .object({
1531
1852
  source: z.enum(["live", "cache"]),
@@ -1547,9 +1868,10 @@ const enrichedPeopleResponse = z
1547
1868
  })
1548
1869
  .passthrough(),
1549
1870
  )
1550
- .describe("Provider results normalized to the CRM upsert shape."),
1871
+ .describe("Search results normalized to the CRM upsert shape."),
1551
1872
  resolvedPersonIds: z.array(z.string()),
1552
1873
  creditsCharged: z.number(),
1874
+ billing: searchBillingResponse.optional(),
1553
1875
  metadata: enrichmentMetadata,
1554
1876
  })
1555
1877
  .passthrough();
@@ -1572,9 +1894,10 @@ const enrichedCompaniesResponse = z
1572
1894
  })
1573
1895
  .passthrough(),
1574
1896
  )
1575
- .describe("Provider results normalized to the CRM upsert shape."),
1897
+ .describe("Search results normalized to the CRM upsert shape."),
1576
1898
  resolvedCompanyIds: z.array(z.string()),
1577
1899
  creditsCharged: z.number(),
1900
+ billing: searchBillingResponse.optional(),
1578
1901
  metadata: enrichmentMetadata,
1579
1902
  })
1580
1903
  .passthrough();
@@ -1735,7 +2058,7 @@ const composioToolRun = z.object({
1735
2058
  tool_slug: z
1736
2059
  .string()
1737
2060
  .optional()
1738
- .describe("Composio tool slug, e.g. GMAIL_FETCH_EMAILS."),
2061
+ .describe("Tool slug, e.g. GMAIL_FETCH_EMAILS."),
1739
2062
  arguments: z
1740
2063
  .record(z.string(), z.unknown())
1741
2064
  .optional()
@@ -1907,6 +2230,90 @@ const peopleSearch = z.object({
1907
2230
  preferCache: z.boolean().optional(),
1908
2231
  });
1909
2232
 
2233
+ export const campaignAudienceFiltersSchema = peopleSearch
2234
+ .omit({
2235
+ limit: true,
2236
+ offset: true,
2237
+ pageToken: true,
2238
+ })
2239
+ .strict();
2240
+
2241
+ const campaignSignal = z.enum([
2242
+ "post_engagement",
2243
+ "keyword",
2244
+ "hiring",
2245
+ "funding",
2246
+ "launch",
2247
+ "new_leader",
2248
+ ]);
2249
+ const campaignTargeting = z.object({
2250
+ titles: z.array(z.string()),
2251
+ locations: z.array(z.string()),
2252
+ companySizes: z.array(z.string()),
2253
+ industries: z.array(z.string()),
2254
+ excludes: z.array(z.string()),
2255
+ companies: z.array(z.string()).optional(),
2256
+ });
2257
+ const campaignSignalTerms = z.object({
2258
+ post_engagement: z.array(z.string()).optional(),
2259
+ keyword: z.array(z.string()).optional(),
2260
+ hiring: z.array(z.string()).optional(),
2261
+ });
2262
+ const campaignClientRequestId = z
2263
+ .string()
2264
+ .trim()
2265
+ .min(8)
2266
+ .max(64)
2267
+ .describe(
2268
+ "Your stable id for this request, 8-64 characters. Retrying with the same id returns the same draft instead of creating another.",
2269
+ );
2270
+ const campaignRecipient = z
2271
+ .object({
2272
+ email: z.string().optional(),
2273
+ linkedinProfileUrl: z.string().optional(),
2274
+ displayName: z.string().optional(),
2275
+ personalization: z
2276
+ .record(z.string(), z.unknown())
2277
+ .optional()
2278
+ .describe(
2279
+ 'Merge values keyed by name, e.g. {"Company":"Acme"} for {{Company}}.',
2280
+ ),
2281
+ })
2282
+ .strict()
2283
+ .describe(
2284
+ "One person. Needs email and/or linkedinProfileUrl; unknown keys are rejected, not dropped.",
2285
+ );
2286
+ const campaignBatchResult = z.object({
2287
+ added: z.number(),
2288
+ requested: z.number(),
2289
+ cappedAtLimit: z.number().nullable(),
2290
+ duplicates: z.number(),
2291
+ duplicatesElsewhere: z.number(),
2292
+ suppressed: z.number(),
2293
+ invalid: z.number(),
2294
+ skippedNoIdentity: z.number(),
2295
+ skippedMissingInfo: z.number(),
2296
+ missingInfoSample: z.unknown().optional(),
2297
+ });
2298
+ const campaignSearchPage = z.object({
2299
+ rows: z.array(z.record(z.string(), z.unknown())),
2300
+ people: z.array(z.unknown()),
2301
+ recipients: z.array(campaignRecipient),
2302
+ metadata: z.record(z.string(), z.unknown()),
2303
+ estimatedCents: z
2304
+ .number()
2305
+ .nullable()
2306
+ .describe(
2307
+ "The search cost ceiling reported by Dench billing, in cents. Null means the receipt is unavailable.",
2308
+ ),
2309
+ chargedCents: z
2310
+ .number()
2311
+ .nullable()
2312
+ .describe(
2313
+ "The actual search charge reported by Dench billing, in cents. Null means the receipt is unavailable; results remain valid.",
2314
+ ),
2315
+ });
2316
+
1910
2317
  const companySearch = z.object({
1911
2318
  domain: z.string().optional(),
1912
2319
  name: z.string().optional(),
@@ -2341,11 +2748,178 @@ const analyticsPanelResult = z.object({
2341
2748
  // ---------------------------------------------------------------------------
2342
2749
 
2343
2750
  export const requestSchemas: Record<string, ZodTypeAny> = {
2751
+ "lists.save": z
2752
+ .object({
2753
+ createdFrom: z
2754
+ .enum([
2755
+ "find_people",
2756
+ "find_companies",
2757
+ "find_jobs",
2758
+ "mixed",
2759
+ "watch_fire",
2760
+ "generate_image",
2761
+ ])
2762
+ .describe(
2763
+ "Use find_people, find_companies or find_jobs for a prospect list.",
2764
+ ),
2765
+ title: z.string().min(1).describe("Name of the new list."),
2766
+ saved: z
2767
+ .boolean()
2768
+ .optional()
2769
+ .describe(
2770
+ "True makes the list visible in Find. Omitted saves an unlisted draft that can still be read or added to a campaign by id.",
2771
+ ),
2772
+ filters: z
2773
+ .unknown()
2774
+ .optional()
2775
+ .describe(
2776
+ "Original search filters to retain with the list. Saving does not run a search.",
2777
+ ),
2778
+ people: z
2779
+ .array(z.record(z.string(), z.unknown()))
2780
+ .optional()
2781
+ .describe(
2782
+ "Complete person records from a search, including existing contact details and custom fields.",
2783
+ ),
2784
+ companies: z
2785
+ .array(z.record(z.string(), z.unknown()))
2786
+ .optional()
2787
+ .describe("Complete company records from a search."),
2788
+ jobs: z
2789
+ .array(z.record(z.string(), z.unknown()))
2790
+ .optional()
2791
+ .describe("Complete job records from a search."),
2792
+ })
2793
+ .strict(),
2794
+ "lists.update": z
2795
+ .object({
2796
+ artifactId: z.string().describe("The existing list id."),
2797
+ title: z.string().min(1).optional(),
2798
+ filters: z
2799
+ .unknown()
2800
+ .optional()
2801
+ .describe("Replacement filter metadata; does not run a new search."),
2802
+ people: z
2803
+ .array(z.record(z.string(), z.unknown()))
2804
+ .optional()
2805
+ .describe(
2806
+ "Replacement people. Omit to preserve existing rows; an empty array clears them.",
2807
+ ),
2808
+ companies: z
2809
+ .array(z.record(z.string(), z.unknown()))
2810
+ .optional()
2811
+ .describe("Replacement companies. Omit to preserve existing rows."),
2812
+ jobs: z
2813
+ .array(z.record(z.string(), z.unknown()))
2814
+ .optional()
2815
+ .describe("Replacement jobs. Omit to preserve existing rows."),
2816
+ })
2817
+ .strict(),
2344
2818
  // Meta
2345
2819
  "commands.list": noInput,
2346
2820
 
2347
2821
  // Outreach campaigns (dench campaign)
2822
+ "campaigns.drafts.create": z
2823
+ .object({
2824
+ clientRequestId: campaignClientRequestId,
2825
+ name: z.string().max(120).optional(),
2826
+ timezone: z
2827
+ .string()
2828
+ .optional()
2829
+ .describe(
2830
+ "IANA timezone, e.g. America/Los_Angeles. Sets a Mon-Fri 9-18 send window in it; without one the draft has no send window.",
2831
+ ),
2832
+ })
2833
+ .strict(),
2834
+ "campaigns.audience.search": z
2835
+ .object({
2836
+ sequenceId: z.string().optional(),
2837
+ filters: campaignAudienceFiltersSchema,
2838
+ limit: z.number().int().min(1).max(100).optional(),
2839
+ pageToken: z.string().optional(),
2840
+ })
2841
+ .strict(),
2842
+ "campaigns.people.addFromSearch": z
2843
+ .object({
2844
+ sequenceId: z.string(),
2845
+ filters: campaignAudienceFiltersSchema,
2846
+ limit: z.number().int().min(1).max(50).optional(),
2847
+ pageToken: z.string().optional(),
2848
+ checkDuplicatesAcrossWorkspace: z.boolean().optional(),
2849
+ })
2850
+ .strict(),
2851
+ "campaigns.people.addBatch": z
2852
+ .object({
2853
+ sequenceId: z.string(),
2854
+ recipients: z.array(campaignRecipient).min(1).max(50),
2855
+ newRecordsObject: z.enum(["lead", "people"]).optional(),
2856
+ checkDuplicatesAcrossWorkspace: z.boolean().optional(),
2857
+ })
2858
+ .strict(),
2859
+ "campaigns.sources.get": z.object({ sequenceId: z.string() }).strict(),
2860
+ "campaigns.sources.signals": z.object({ sequenceId: z.string() }).strict(),
2861
+ "campaigns.sources.update": z
2862
+ .object({
2863
+ sequenceId: z.string(),
2864
+ status: z.enum(["active", "paused"]).optional(),
2865
+ cadence: z.enum(["daily", "weekly", "monthly"]).optional(),
2866
+ findMoreMatching: z.boolean().optional(),
2867
+ targeting: campaignTargeting.optional(),
2868
+ signal: z
2869
+ .object({ signal: campaignSignal, enabled: z.boolean() })
2870
+ .optional(),
2871
+ terms: z
2872
+ .object({
2873
+ signal: z.enum(["post_engagement", "keyword", "hiring"]),
2874
+ values: z.array(z.string()),
2875
+ })
2876
+ .optional(),
2877
+ })
2878
+ .strict(),
2879
+ "campaigns.sources.preview": z
2880
+ .object({
2881
+ sequenceId: z.string(),
2882
+ signal: campaignSignal,
2883
+ terms: z.array(z.string()).optional(),
2884
+ })
2885
+ .strict(),
2886
+ "campaigns.builder.profile": noInput,
2887
+ "campaigns.builder.save": z
2888
+ .object({
2889
+ clientRequestId: campaignClientRequestId,
2890
+ targeting: campaignTargeting,
2891
+ signals: z.array(campaignSignal),
2892
+ signalTerms: campaignSignalTerms.optional(),
2893
+ })
2894
+ .strict(),
2895
+ "campaigns.builder.get": z.object({ draftId: z.string() }).strict(),
2896
+ "campaigns.builder.build": z.object({ draftId: z.string() }).strict(),
2897
+ "campaigns.builder.create": z
2898
+ .object({
2899
+ draftId: z.string(),
2900
+ name: z.string().max(120).optional(),
2901
+ timezone: z.string().optional(),
2902
+ })
2903
+ .strict(),
2904
+ "campaigns.builder.opener": z
2905
+ .object({ draftId: z.string(), leadId: z.string(), opener: z.string() })
2906
+ .strict(),
2907
+ "campaigns.builder.autofill": z
2908
+ .object({ description: z.string().min(3).max(300) })
2909
+ .strict(),
2910
+ "campaigns.builder.terms": z
2911
+ .object({ targeting: campaignTargeting })
2912
+ .strict(),
2913
+ "campaigns.builder.estimate": z
2914
+ .object({
2915
+ kind: z.enum(["people", "week"]),
2916
+ targeting: campaignTargeting,
2917
+ signals: z.array(campaignSignal).optional(),
2918
+ signalTerms: campaignSignalTerms.optional(),
2919
+ })
2920
+ .strict(),
2348
2921
  "campaigns.accounts.linkedin": noInput,
2922
+ "campaigns.accounts.email": noInput,
2349
2923
  "campaigns.list": z.object({
2350
2924
  includeArchived: z
2351
2925
  .boolean()
@@ -2360,6 +2934,13 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
2360
2934
  .optional()
2361
2935
  .describe("IANA timezone for the default Mon-Fri 9-18 send window."),
2362
2936
  }),
2937
+ "campaigns.duplicate": z
2938
+ .object({
2939
+ sequenceId: z
2940
+ .string()
2941
+ .describe("The campaign to copy; any status, including archived."),
2942
+ })
2943
+ .strict(),
2363
2944
  "campaigns.get": z.object({ sequenceId: z.string() }),
2364
2945
  "campaigns.analytics": z.object({ sequenceId: z.string() }),
2365
2946
  "campaigns.live": z.object({ sequenceId: z.string() }),
@@ -2415,10 +2996,140 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
2415
2996
  .optional()
2416
2997
  .describe("Cold emails per day, 1-100."),
2417
2998
  }),
2418
- "campaigns.steps.replace": z.object({
2419
- sequenceId: z.string(),
2420
- steps: z.array(campaignStepInput).min(1).max(20),
2421
- }),
2999
+ "campaigns.steps.replace": z
3000
+ .object({
3001
+ sequenceId: z.string(),
3002
+ steps: z
3003
+ .array(
3004
+ campaignStepInput.strict().describe("One campaign step, in order."),
3005
+ )
3006
+ .min(1)
3007
+ .max(MAX_CAMPAIGN_STEPS)
3008
+ .describe(
3009
+ "Every step, in send order. Rejected, not trimmed: a field the step type would drop (onlyIf off email/post-like steps, a body on a visit, reply-in-thread on the first email), a missing subject or required body, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age values, and {TOKEN} single braces, which would be sent as typed.",
3010
+ ),
3011
+ allowSingleBraces: z
3012
+ .boolean()
3013
+ .optional()
3014
+ .describe(
3015
+ "Send {single braces} exactly as typed instead of rejecting them. Merge tokens always use {{double braces}}.",
3016
+ ),
3017
+ })
3018
+ .strict()
3019
+ .superRefine((input, ctx) => {
3020
+ for (const issue of checkCampaignSteps(input.steps, {
3021
+ allowSingleBraces: input.allowSingleBraces,
3022
+ }).errors)
3023
+ ctx.addIssue({
3024
+ code: "custom",
3025
+ message: issue.message,
3026
+ path: issue.step
3027
+ ? ["steps", issue.step - 1, ...(issue.field ? [issue.field] : [])]
3028
+ : ["steps"],
3029
+ });
3030
+ }),
3031
+ "campaigns.steps.update": z
3032
+ .object({
3033
+ sequenceId: z.string(),
3034
+ steps: z
3035
+ .array(
3036
+ campaignStepInput
3037
+ .partial()
3038
+ .extend({
3039
+ stepId: z
3040
+ .string()
3041
+ .describe("The step's _id from campaigns.get steps[]."),
3042
+ expectedUpdatedAt: z
3043
+ .number()
3044
+ .describe(
3045
+ "The step's updatedAt when you read it. If someone saved it since, the edit is refused instead of overwriting theirs.",
3046
+ ),
3047
+ type: campaignStepType.describe(
3048
+ "The step's current type. A launched step's type can't change.",
3049
+ ),
3050
+ })
3051
+ .strict()
3052
+ .describe(
3053
+ "One step edit: only the fields given change. Clear text with an empty string, onlyIf with always, emailReplyToPrevious with false and withdrawAfterHours with null. Like settings are optional; omitted values stay unchanged.",
3054
+ ),
3055
+ )
3056
+ .min(1)
3057
+ .max(MAX_CAMPAIGN_STEPS)
3058
+ .describe(
3059
+ "The steps to change. Rejected, not trimmed: a field the step type would drop, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age values, and {TOKEN} single braces.",
3060
+ ),
3061
+ allowSingleBraces: z
3062
+ .boolean()
3063
+ .optional()
3064
+ .describe(
3065
+ "Send {single braces} exactly as typed instead of rejecting them. Merge tokens always use {{double braces}}.",
3066
+ ),
3067
+ })
3068
+ .strict()
3069
+ .superRefine((input, ctx) => {
3070
+ const edits = input.steps.map(
3071
+ ({ stepId: _stepId, expectedUpdatedAt: _expected, ...edit }) => edit,
3072
+ );
3073
+ for (const issue of checkStepEdits(edits, {
3074
+ allowSingleBraces: input.allowSingleBraces,
3075
+ }).errors)
3076
+ ctx.addIssue({
3077
+ code: "custom",
3078
+ message: issue.message,
3079
+ path: issue.step
3080
+ ? ["steps", issue.step - 1, ...(issue.field ? [issue.field] : [])]
3081
+ : ["steps"],
3082
+ });
3083
+ }),
3084
+ "campaigns.personalization.get": z
3085
+ .object({ sequenceId: z.string() })
3086
+ .strict(),
3087
+ "campaigns.personalization.set": z
3088
+ .object({
3089
+ sequenceId: z.string(),
3090
+ template: campaignLineTemplate
3091
+ .nullable()
3092
+ .describe("The lines and context; null turns personalized lines off."),
3093
+ })
3094
+ .strict(),
3095
+ "campaigns.personalization.clear": z
3096
+ .object({ sequenceId: z.string() })
3097
+ .strict(),
3098
+ "campaigns.personalization.write": z
3099
+ .object({
3100
+ sequenceId: z.string(),
3101
+ mode: z
3102
+ .enum(["missing", "all"])
3103
+ .optional()
3104
+ .describe(
3105
+ "missing (default): only people without lines. all: rewrite everyone's.",
3106
+ ),
3107
+ confirmed: z
3108
+ .boolean()
3109
+ .optional()
3110
+ .describe(
3111
+ "true after the user accepts the estimate from needsConfirm.",
3112
+ ),
3113
+ })
3114
+ .strict(),
3115
+ "campaigns.personalization.edit": z
3116
+ .object({
3117
+ enrollmentId: z.string().describe("From dench campaign people."),
3118
+ key: z.string().describe("The line's key, as in {{key}}."),
3119
+ value: z
3120
+ .string()
3121
+ .describe("The line to send; an empty string undoes the edit."),
3122
+ })
3123
+ .strict(),
3124
+ "campaigns.personalization.rewrite": z
3125
+ .object({
3126
+ enrollmentId: z.string().describe("From dench campaign people."),
3127
+ key: z
3128
+ .string()
3129
+ .optional()
3130
+ .describe("One line's key; leave out to rewrite all of their lines."),
3131
+ })
3132
+ .strict(),
2422
3133
  "campaigns.people.addFromCrm": z.object({
2423
3134
  sequenceId: z.string(),
2424
3135
  objectName: z
@@ -2465,6 +3176,19 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
2465
3176
  personalization: z.record(z.string(), z.unknown()).optional(),
2466
3177
  checkDuplicatesAcrossWorkspace: z.boolean().optional(),
2467
3178
  }),
3179
+ "campaigns.preview": z
3180
+ .object({
3181
+ sequenceId: z.string(),
3182
+ enrollmentId: z
3183
+ .string()
3184
+ .describe("The person to preview, from dench campaign people."),
3185
+ stepId: z
3186
+ .string()
3187
+ .describe(
3188
+ "An email or LinkedIn step, its _id from campaigns.get steps[]. A wait step sends nothing.",
3189
+ ),
3190
+ })
3191
+ .strict(),
2468
3192
  "campaigns.launch.preview": z.object({ sequenceId: z.string() }),
2469
3193
  "campaigns.launch": z.object({
2470
3194
  sequenceId: z.string(),
@@ -2493,6 +3217,59 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
2493
3217
  }),
2494
3218
  "campaigns.people.stop": z.object({ enrollmentId: z.string() }),
2495
3219
  "campaigns.people.resume": z.object({ enrollmentId: z.string() }),
3220
+ "campaigns.people.sync": z.object({ sequenceId: z.string() }).strict(),
3221
+ "campaigns.people.resumeAll": z
3222
+ .object({
3223
+ sequenceId: z.string().describe("An active campaign."),
3224
+ })
3225
+ .strict(),
3226
+ "campaigns.people.setEmail": z
3227
+ .object({
3228
+ enrollmentId: z.string().describe("From dench campaign people."),
3229
+ email: z.string().describe("The address email steps should use."),
3230
+ })
3231
+ .strict(),
3232
+ "campaigns.people.setLinkedin": z
3233
+ .object({
3234
+ enrollmentId: z.string().describe("From dench campaign people."),
3235
+ linkedinProfileUrl: z
3236
+ .string()
3237
+ .describe(
3238
+ "A LinkedIn member profile URL, e.g. https://linkedin.com/in/ada.",
3239
+ ),
3240
+ })
3241
+ .strict(),
3242
+ "campaigns.people.setField": z
3243
+ .object({
3244
+ enrollmentId: z.string().describe("From dench campaign people."),
3245
+ key: z
3246
+ .string()
3247
+ .describe(
3248
+ "The merge token name without braces, e.g. Company for {{Company}} or fund for {{fund}}.",
3249
+ ),
3250
+ value: z.string().describe("Up to 2000 characters."),
3251
+ })
3252
+ .strict(),
3253
+ "campaigns.pipeline": z.object({ sequenceId: z.string() }).strict(),
3254
+ "campaigns.people.forRecord": z
3255
+ .object({
3256
+ entryId: z
3257
+ .string()
3258
+ .describe("A CRM record's _id: a person, or a lead created from one."),
3259
+ })
3260
+ .strict(),
3261
+ "campaigns.people.timeline": z
3262
+ .object({
3263
+ enrollmentId: z.string().describe("From dench campaign people."),
3264
+ })
3265
+ .strict(),
3266
+ "campaigns.people.remove": z.object({
3267
+ enrollmentId: z
3268
+ .string()
3269
+ .describe(
3270
+ "From dench campaign people; the person must never have been contacted.",
3271
+ ),
3272
+ }),
2496
3273
 
2497
3274
  // Auth
2498
3275
  "auth.signin.create": z.object({
@@ -4007,11 +4784,71 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
4007
4784
  // ---------------------------------------------------------------------------
4008
4785
 
4009
4786
  export const responseSchemas: Record<string, ZodTypeAny> = {
4787
+ "lists.save": z.object({
4788
+ artifactId: z.string(),
4789
+ filePath: z.string(),
4790
+ rowCount: z.number(),
4791
+ previewRows: z.array(z.unknown()),
4792
+ title: z.string(),
4793
+ createdFrom: z.string(),
4794
+ }),
4795
+ "lists.update": z.object({
4796
+ artifactId: z.string(),
4797
+ rowCount: z.number(),
4798
+ previewRows: z.array(z.unknown()),
4799
+ title: z.string(),
4800
+ }),
4801
+ "campaigns.drafts.create": z.object({ sequenceId: z.string() }),
4802
+ "campaigns.audience.search": campaignSearchPage,
4803
+ "campaigns.people.addBatch": campaignBatchResult,
4804
+ "campaigns.people.addFromSearch": campaignSearchPage
4805
+ .merge(campaignBatchResult)
4806
+ .extend({ found: z.number(), skippedSearchProfiles: z.number() }),
4807
+ "campaigns.sources.get": z.record(z.string(), z.unknown()).nullable(),
4808
+ "campaigns.sources.signals": z.record(z.string(), z.unknown()).nullable(),
4809
+ "campaigns.sources.update": z.null(),
4810
+ "campaigns.sources.preview": z.object({ ok: z.boolean() }).passthrough(),
4811
+ "campaigns.builder.profile": z
4812
+ .object({ hasProfile: z.boolean(), targeting: campaignTargeting })
4813
+ .passthrough(),
4814
+ "campaigns.builder.save": z.object({ draftId: z.string() }),
4815
+ "campaigns.builder.get": z
4816
+ .object({
4817
+ status: z.string(),
4818
+ targeting: campaignTargeting,
4819
+ signals: z.array(campaignSignal),
4820
+ people: z.array(z.record(z.string(), z.unknown())),
4821
+ })
4822
+ .passthrough(),
4823
+ "campaigns.builder.build": z.object({
4824
+ ok: z.boolean(),
4825
+ started: z.boolean(),
4826
+ }),
4827
+ "campaigns.builder.create": z.object({
4828
+ sequenceId: z.string(),
4829
+ enrolled: z.number(),
4830
+ }),
4831
+ "campaigns.builder.opener": z.null(),
4832
+ "campaigns.builder.autofill": z
4833
+ .object({ targeting: campaignTargeting })
4834
+ .passthrough(),
4835
+ "campaigns.builder.terms": z
4836
+ .object({ signalTerms: campaignSignalTerms })
4837
+ .passthrough(),
4838
+ "campaigns.builder.estimate": z
4839
+ .object({ count: z.number().nullable() })
4840
+ .passthrough(),
4010
4841
  // Meta
4011
4842
  // Outreach campaigns (dench campaign)
4012
4843
  "campaigns.accounts.linkedin": z.array(campaignLinkedinSenderSummary),
4844
+ "campaigns.accounts.email": z.array(campaignMailboxSummary),
4013
4845
  "campaigns.list": z.array(campaignRow),
4014
4846
  "campaigns.create": z.object({ sequenceId: z.string() }),
4847
+ "campaigns.duplicate": z.object({
4848
+ sequenceId: z
4849
+ .string()
4850
+ .describe("The new draft. Rename it with campaigns.update."),
4851
+ }),
4015
4852
  "campaigns.get": z
4016
4853
  .object({
4017
4854
  sequence: campaignRow,
@@ -4077,8 +4914,43 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
4077
4914
  ok: z.boolean(),
4078
4915
  stepCount: z.number(),
4079
4916
  }),
4917
+ "campaigns.steps.update": z.object({
4918
+ ok: z.boolean(),
4919
+ stepCount: z.number().describe("How many steps were edited."),
4920
+ }),
4921
+ "campaigns.personalization.get": z
4922
+ .object({
4923
+ template: campaignLineTemplate
4924
+ .extend({ version: z.number(), updatedAt: z.number() })
4925
+ .passthrough()
4926
+ .nullable(),
4927
+ counts: z.object({
4928
+ done: z.number(),
4929
+ pending: z.number(),
4930
+ failed: z.number(),
4931
+ total: z.number(),
4932
+ }),
4933
+ paused: z.boolean(),
4934
+ notice: z
4935
+ .object({ code: z.literal("insufficient_credits"), at: z.number() })
4936
+ .nullable(),
4937
+ suggestedContext: z.object({ sells: z.string().optional() }),
4938
+ estimatedCentsPerPerson: z.number(),
4939
+ eligible: z.number(),
4940
+ })
4941
+ .passthrough(),
4942
+ "campaigns.personalization.set": campaignLineBackfill,
4943
+ "campaigns.personalization.clear": campaignLineBackfill,
4944
+ "campaigns.personalization.write": campaignLineBackfill,
4945
+ "campaigns.personalization.edit": okResponse,
4946
+ "campaigns.personalization.rewrite": z.object({
4947
+ queued: z
4948
+ .boolean()
4949
+ .describe("False when new lines are already being written."),
4950
+ }),
4080
4951
  "campaigns.people.addFromCrm": campaignEnrollResult,
4081
4952
  "campaigns.people.add": z.object({ enrollmentId: z.string() }),
4953
+ "campaigns.preview": campaignMessagePreview,
4082
4954
  "campaigns.launch.preview": campaignReadiness,
4083
4955
  "campaigns.launch": z.object({
4084
4956
  ok: z.boolean(),
@@ -4096,6 +4968,37 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
4096
4968
  .passthrough(),
4097
4969
  "campaigns.people.stop": okResponse,
4098
4970
  "campaigns.people.resume": okResponse,
4971
+ "campaigns.people.sync": z.object({
4972
+ updated: z
4973
+ .number()
4974
+ .describe("How many people gained a detail from their CRM record."),
4975
+ }),
4976
+ "campaigns.people.resumeAll": z.object({
4977
+ resumed: z.number().describe("How many paused people were resumed."),
4978
+ }),
4979
+ "campaigns.people.setEmail": okResponse,
4980
+ "campaigns.people.setLinkedin": okResponse,
4981
+ "campaigns.people.setField": okResponse,
4982
+ "campaigns.pipeline": z
4983
+ .object({
4984
+ total: z.number().describe("People in the campaign."),
4985
+ statuses: z
4986
+ .array(
4987
+ z.object({
4988
+ name: z.string().describe("A lead Status, as named in the CRM."),
4989
+ color: z.string(),
4990
+ count: z.number().describe("Enrolled leads at this Status."),
4991
+ }),
4992
+ )
4993
+ .describe("The workspace's lead Statuses, in pipeline order."),
4994
+ other: z
4995
+ .number()
4996
+ .describe("People with no lead, or a Status outside the pipeline."),
4997
+ })
4998
+ .nullable(),
4999
+ "campaigns.people.forRecord": z.array(campaignMembership),
5000
+ "campaigns.people.timeline": campaignPersonTimeline,
5001
+ "campaigns.people.remove": okResponse,
4099
5002
 
4100
5003
  "commands.list": z.object({
4101
5004
  operations: z.array(
@@ -4107,7 +5010,7 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
4107
5010
  aliases: z.array(z.string()),
4108
5011
  method: z.string(),
4109
5012
  path: z.string(),
4110
- backend: z.enum(["convex", "gateway", "custom", "local"]),
5013
+ backend: z.enum(["api", "gateway", "custom", "local"]),
4111
5014
  deprecated: z.boolean(),
4112
5015
  }),
4113
5016
  ),
@@ -4964,7 +5867,7 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
4964
5867
  connection_id: z.string().optional(),
4965
5868
  })
4966
5869
  .passthrough()
4967
- .describe("Composio connect-link response (provider passthrough)."),
5870
+ .describe("Connect-link response."),
4968
5871
  "tool.search": toolSearchResponse,
4969
5872
  "tool.run": toolRunResponse,
4970
5873
  "tool.disconnect": z
@@ -4973,7 +5876,7 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
4973
5876
  deleted: z.boolean().optional(),
4974
5877
  })
4975
5878
  .passthrough()
4976
- .describe("Composio delete response (provider passthrough)."),
5879
+ .describe("Delete response."),
4977
5880
  "tool.labels.list": z.object({
4978
5881
  labels: z.array(
4979
5882
  z.object({ composioConnectionId: z.string(), displayName: z.string() }),
@@ -5062,6 +5965,19 @@ export type ConvexExtras = {
5062
5965
  };
5063
5966
 
5064
5967
  export const convexExtras: Record<string, ConvexExtras> = {
5968
+ "campaigns.steps.replace": {
5969
+ transformArgs: ({ allowSingleBraces: _allowSingleBraces, ...input }) =>
5970
+ input,
5971
+ },
5972
+ "campaigns.steps.update": {
5973
+ transformArgs: ({ allowSingleBraces: _allowSingleBraces, ...input }) =>
5974
+ input,
5975
+ },
5976
+ "campaigns.personalization.clear": { fixedArgs: { template: null } },
5977
+ "campaigns.personalization.write": {
5978
+ transformArgs: (input) => ({ ...input, mode: input.mode ?? "missing" }),
5979
+ },
5980
+ "campaigns.preview": { fixedArgs: { stepIndex: 0 } },
5065
5981
  // The events query paginates with Convex's `paginationOpts`; the flat
5066
5982
  // `numItems` / `cursor` query params are folded into it here.
5067
5983
  "campaigns.events": {
@@ -5222,6 +6138,41 @@ export const requestExamples: Record<string, unknown> = {
5222
6138
  },
5223
6139
  ],
5224
6140
  },
6141
+ "campaigns.steps.update": {
6142
+ sequenceId: "<sequenceId>",
6143
+ steps: [
6144
+ {
6145
+ stepId: "<stepId>",
6146
+ expectedUpdatedAt: 1765526400000,
6147
+ type: "email",
6148
+ emailSubject: "A quicker question, {{First name}}",
6149
+ },
6150
+ {
6151
+ stepId: "<waitStepId>",
6152
+ expectedUpdatedAt: 1765526400000,
6153
+ type: "wait",
6154
+ waitHours: 96,
6155
+ },
6156
+ ],
6157
+ },
6158
+ "campaigns.personalization.set": {
6159
+ sequenceId: "<sequenceId>",
6160
+ template: {
6161
+ variables: [
6162
+ {
6163
+ key: "companyHook",
6164
+ label: "Company hook",
6165
+ prompt:
6166
+ "One sentence about something specific their company does or recently did. 8 to 18 words.",
6167
+ fallback: "Your team caught my eye.",
6168
+ maxChars: 160,
6169
+ },
6170
+ ],
6171
+ context: {
6172
+ sells: "Senior engineers who join your team in your time zone.",
6173
+ },
6174
+ },
6175
+ },
5225
6176
  "campaigns.people.addFromCrm": {
5226
6177
  sequenceId: "<sequenceId>",
5227
6178
  objectName: "lead",
@@ -5549,6 +6500,118 @@ const workspaceStatusExample = {
5549
6500
  };
5550
6501
 
5551
6502
  export const responseExamples: Record<string, unknown> = {
6503
+ "campaigns.pipeline": {
6504
+ total: 12,
6505
+ statuses: [
6506
+ { name: "New", color: "#94a3b8", count: 3 },
6507
+ { name: "Contacted", color: "#3b82f6", count: 5 },
6508
+ { name: "Replied", color: "#22c55e", count: 2 },
6509
+ ],
6510
+ other: 2,
6511
+ },
6512
+ "campaigns.people.forRecord": [
6513
+ {
6514
+ enrollment: {
6515
+ _id: "<enrollmentId>",
6516
+ sequenceId: "<sequenceId>",
6517
+ displayName: "Grace Hopper",
6518
+ status: "active",
6519
+ currentStepIndex: 1,
6520
+ },
6521
+ sequenceName: "Founder outreach",
6522
+ isOwner: true,
6523
+ canManage: true,
6524
+ senders: {
6525
+ email: {
6526
+ accountId: "<emailMailboxAccountId>",
6527
+ label: "ada@example.com",
6528
+ status: "running",
6529
+ },
6530
+ linkedin: null,
6531
+ },
6532
+ },
6533
+ ],
6534
+ "campaigns.people.timeline": {
6535
+ enrollment: {
6536
+ _id: "<enrollmentId>",
6537
+ displayName: "Grace Hopper",
6538
+ status: "active",
6539
+ },
6540
+ sequence: { _id: "<sequenceId>", name: "Founder outreach" },
6541
+ steps: [
6542
+ { _id: "<stepId1>", order: 0, type: "email", emailSubject: "Hello" },
6543
+ { _id: "<stepId2>", order: 1, type: "wait", waitHours: 72 },
6544
+ ],
6545
+ runs: [
6546
+ {
6547
+ _id: "<runId1>",
6548
+ stepId: "<stepId1>",
6549
+ stepIndex: 0,
6550
+ type: "email",
6551
+ status: "sent",
6552
+ completedAt: 1765526400000,
6553
+ emailMessageId: "<emailId1>",
6554
+ },
6555
+ {
6556
+ _id: "<runId2>",
6557
+ stepId: "<stepId2>",
6558
+ stepIndex: 1,
6559
+ type: "wait",
6560
+ status: "waiting",
6561
+ waitUntil: 1765785600000,
6562
+ },
6563
+ ],
6564
+ emailMessages: [
6565
+ {
6566
+ _id: "<emailId1>",
6567
+ subject: "Hello",
6568
+ textBody: "Hi Grace, ...",
6569
+ status: "opened",
6570
+ sentAt: 1765526400000,
6571
+ firstOpenedAt: 1765530000000,
6572
+ },
6573
+ ],
6574
+ linkedinMessages: [],
6575
+ },
6576
+ "campaigns.preview": {
6577
+ channel: "email",
6578
+ subject: "A quicker question, Grace",
6579
+ textBody:
6580
+ "Hi Grace,\n\nYour team at Acme is hiring senior engineers. We place them in your time zone within two weeks.\n\nWorth a chat?",
6581
+ htmlBody:
6582
+ "<p>Hi Grace,</p><p>Your team at Acme is hiring senior engineers. We place them in your time zone within two weeks.</p><p>Worth a chat?</p>",
6583
+ fromName: "Ada Lovelace",
6584
+ fromEmail: "ada@example.com",
6585
+ toName: "Grace Hopper",
6586
+ toEmail: "grace@acme.com",
6587
+ missingFields: [],
6588
+ warnings: [],
6589
+ personalized: [
6590
+ {
6591
+ key: "companyHook",
6592
+ label: "Company hook",
6593
+ value: "Acme is hiring senior engineers.",
6594
+ state: "written",
6595
+ source: "company",
6596
+ maxChars: 160,
6597
+ },
6598
+ ],
6599
+ },
6600
+ "campaigns.accounts.email": [
6601
+ {
6602
+ _id: "<emailMailboxAccountId>",
6603
+ email: "ada@example.com",
6604
+ provider: "gmail",
6605
+ status: "running",
6606
+ warmupStatus: "warming",
6607
+ readyAt: 1765526400000,
6608
+ dailyColdSendCap: 30,
6609
+ remainingToday: 12,
6610
+ ownerName: "Ada",
6611
+ isOwner: true,
6612
+ visibility: "shared",
6613
+ },
6614
+ ],
5552
6615
  "campaigns.accounts.linkedin": [
5553
6616
  {
5554
6617
  linkedinAccountId: "<linkedinAccountId>",
@@ -5580,7 +6643,7 @@ export const responseExamples: Record<string, unknown> = {
5580
6643
  aliases: [],
5581
6644
  method: "POST",
5582
6645
  path: "/crm/objects/{objectName}/entries",
5583
- backend: "convex",
6646
+ backend: "api",
5584
6647
  deprecated: false,
5585
6648
  },
5586
6649
  ],
@@ -6246,6 +7309,7 @@ export const responseExamples: Record<string, unknown> = {
6246
7309
  ],
6247
7310
  resolvedPersonIds: ["per_abc123"],
6248
7311
  creditsCharged: 1,
7312
+ billing: { estimatedCents: 1, chargedCents: 1 },
6249
7313
  metadata: { total: 1, offset: 0 },
6250
7314
  },
6251
7315
  "crm.companies.search": {
@@ -6261,6 +7325,7 @@ export const responseExamples: Record<string, unknown> = {
6261
7325
  ],
6262
7326
  resolvedCompanyIds: ["com_abc123"],
6263
7327
  creditsCharged: 1,
7328
+ billing: { estimatedCents: 1, chargedCents: 1 },
6264
7329
  metadata: { total: 1, offset: 0 },
6265
7330
  },
6266
7331
  "enrichment.jobs.search": {
@@ -6681,8 +7746,7 @@ export const responseExamples: Record<string, unknown> = {
6681
7746
  downloadable: true,
6682
7747
  },
6683
7748
  "files.write": { path: "/reports/q2.md", contentHash: "9f86d081884c7d65" },
6684
- "files.downloadUrl":
6685
- "https://happy-animal-123.convex.cloud/api/storage/abc123?download=README.md",
7749
+ "files.downloadUrl": "https://files.example.com/abc123?download=README.md",
6686
7750
  "files.delete": { path: "/README.md" },
6687
7751
  "files.move": {
6688
7752
  ok: true,
@@ -6741,7 +7805,7 @@ export const responseExamples: Record<string, unknown> = {
6741
7805
  },
6742
7806
  "image.generate": {
6743
7807
  ok: true,
6744
- model: "gpt-image-2",
7808
+ model: "image-model",
6745
7809
  endpoint: "generations",
6746
7810
  prompt: "A minimal flat-vector app icon",
6747
7811
  quality: "auto",
@@ -6751,13 +7815,13 @@ export const responseExamples: Record<string, unknown> = {
6751
7815
  estimatedCostUsd: 0.053,
6752
7816
  b64_json: "iVBORw0KGgoAAAANS...",
6753
7817
  path: "/image-generations/20260612-app-icon.png",
6754
- signedUrl: "https://happy-animal-123.convex.cloud/api/storage/img123",
7818
+ signedUrl: "https://files.example.com/img123",
6755
7819
  sizeBytes: 245760,
6756
7820
  usage: { inputTokens: 12, outputTokens: 1056, totalTokens: 1068 },
6757
7821
  },
6758
7822
  "image.edit": {
6759
7823
  ok: true,
6760
- model: "gpt-image-2",
7824
+ model: "image-model",
6761
7825
  endpoint: "edits",
6762
7826
  prompt: "Make the background transparent",
6763
7827
  quality: "auto",
@@ -6767,7 +7831,7 @@ export const responseExamples: Record<string, unknown> = {
6767
7831
  estimatedCostUsd: 0.066,
6768
7832
  b64_json: "iVBORw0KGgoAAAANS...",
6769
7833
  path: "/image-generations/20260612-app-icon-edit.png",
6770
- signedUrl: "https://happy-animal-123.convex.cloud/api/storage/img124",
7834
+ signedUrl: "https://files.example.com/img124",
6771
7835
  sizeBytes: 198432,
6772
7836
  },
6773
7837
  "integrations.list": {
@@ -6829,7 +7893,7 @@ export const responseExamples: Record<string, unknown> = {
6829
7893
  raw: [],
6830
7894
  },
6831
7895
  "tool.connect": {
6832
- redirect_url: "https://backend.composio.dev/s/abc123",
7896
+ redirect_url: "https://connect.dench.com/s/abc123",
6833
7897
  connection_id: "ca_github01",
6834
7898
  },
6835
7899
  "tool.search": {