@automate.ax/integration-contracts 0.151.0 → 0.152.0

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.
@@ -8,6 +8,7 @@ import type { brevoTriggerContracts } from "./brevo/index.js";
8
8
  import type { calcomTriggerContracts } from "./calcom/index.js";
9
9
  import type { convexTriggerContracts } from "./convex/index.js";
10
10
  import type { discordTriggerContracts } from "./discord/index.js";
11
+ import type { elevenLabsTriggerContracts } from "./elevenlabs/index.js";
11
12
  import type { closeTriggerContracts } from "./close/index.js";
12
13
  import type { cloudflareTriggerContracts } from "./cloudflare/index.js";
13
14
  import type { clickUpTriggerContracts } from "./clickup/index.js";
@@ -39,7 +40,7 @@ import type { vercelTriggerContracts } from "./vercel/index.js";
39
40
  import type { webflowTriggerContracts } from "./webflow/index.js";
40
41
  import type { whatsappTriggerContracts } from "./whatsapp/index.js";
41
42
  import type { z } from "zod";
42
- export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof calcomTriggerContracts & typeof convexTriggerContracts & typeof discordTriggerContracts & typeof closeTriggerContracts & typeof clickUpTriggerContracts & typeof calendlyTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleDriveTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleAdsTriggerContracts & typeof googleSheetsTriggerContracts & typeof hubspotTriggerContracts & typeof highLevelTriggerContracts & typeof kitTriggerContracts & typeof linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof tallyTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
43
+ export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof calcomTriggerContracts & typeof convexTriggerContracts & typeof discordTriggerContracts & typeof elevenLabsTriggerContracts & typeof closeTriggerContracts & typeof clickUpTriggerContracts & typeof calendlyTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleDriveTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleAdsTriggerContracts & typeof googleSheetsTriggerContracts & typeof hubspotTriggerContracts & typeof highLevelTriggerContracts & typeof kitTriggerContracts & typeof linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof tallyTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
43
44
  export type IntegrationTriggerType = keyof TriggerContractMap;
44
45
  /** Canonical authoring configuration for one integration trigger type. */
45
46
  export type TriggerConfig<TType extends IntegrationTriggerType> = z.input<TriggerContractMap[TType]["configSchema"]> extends Record<string, never> ? object : z.input<TriggerContractMap[TType]["configSchema"]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/integration-contracts",
3
- "version": "0.151.0",
3
+ "version": "0.152.0",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,6 +33,7 @@
33
33
  "./meta-ads": "./src/meta-ads/index.ts",
34
34
  "./convex": "./src/convex/index.ts",
35
35
  "./discord": "./src/discord/index.ts",
36
+ "./elevenlabs": "./src/elevenlabs/index.ts",
36
37
  "./close": "./src/close/index.ts",
37
38
  "./closebot": "./src/closebot/index.ts",
38
39
  "./gmail": "./src/gmail/index.ts",
@@ -71,7 +72,7 @@
71
72
  },
72
73
  "dependencies": {
73
74
  "@anthropic-ai/sdk": "0.123.0",
74
- "@automate.ax/codec": "0.151.0",
75
+ "@automate.ax/codec": "0.152.0",
75
76
  "@cfworker/json-schema": "^4.1.1",
76
77
  "@googleapis/calendar": "^16.0.0",
77
78
  "@googleapis/forms": "^6.0.1",
@@ -203,6 +204,11 @@
203
204
  "types": "./dist/discord/index.d.ts",
204
205
  "default": "./dist/discord/index.js"
205
206
  },
207
+ "./elevenlabs": {
208
+ "bun": "./src/elevenlabs/index.ts",
209
+ "types": "./dist/elevenlabs/index.d.ts",
210
+ "default": "./dist/elevenlabs/index.js"
211
+ },
206
212
  "./close": {
207
213
  "bun": "./src/close/index.ts",
208
214
  "types": "./dist/close/index.d.ts",
@@ -0,0 +1,341 @@
1
+ import type { Encodable } from "@automate.ax/codec"
2
+ import { encodableSchema } from "@automate.ax/codec"
3
+ import * as z from "zod"
4
+
5
+ const ELEVENLABS_API_BASE_URL = "https://api.elevenlabs.io/"
6
+ const ELEVENLABS_API_ORIGIN = new URL(ELEVENLABS_API_BASE_URL).origin
7
+ const ELEVENLABS_SECRET_SCHEMA = z.object({ apiKey: z.string().min(1) })
8
+
9
+ export interface ElevenLabsRequestOptions<TSchema extends z.ZodType> {
10
+ body?: Encodable
11
+ method?: "DELETE" | "GET" | "PATCH" | "POST"
12
+ query?: Record<
13
+ string,
14
+ boolean | number | readonly string[] | string | undefined
15
+ >
16
+ responseSchema: TSchema
17
+ }
18
+
19
+ /** Structured ElevenLabs REST failure. */
20
+ export class ElevenLabsApiError extends Error {
21
+ readonly body?: Encodable
22
+ readonly retryAfter?: number
23
+ readonly status: number
24
+
25
+ /**
26
+ * Creates a structured ElevenLabs API failure.
27
+ *
28
+ * @param options Failure details.
29
+ * @param options.body Parsed provider response body.
30
+ * @param options.retryAfter Retry delay in seconds.
31
+ * @param options.status HTTP status.
32
+ */
33
+ constructor(options: {
34
+ body?: Encodable
35
+ retryAfter?: number
36
+ status: number
37
+ }) {
38
+ super(
39
+ getErrorMessage(options.body) ??
40
+ `ElevenLabs API request failed with status ${options.status}.`,
41
+ )
42
+ this.name = "ElevenLabsApiError"
43
+ this.body = options.body
44
+ this.retryAfter = options.retryAfter
45
+ this.status = options.status
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Creates an authenticated ElevenLabs REST client.
51
+ *
52
+ * @param secret Account secret containing an ElevenLabs API key.
53
+ */
54
+ export function getElevenLabsApi(secret: Record<string, unknown>) {
55
+ const { apiKey } = ELEVENLABS_SECRET_SCHEMA.parse(secret)
56
+
57
+ /**
58
+ * Sends one authenticated provider request and validates HTTP status.
59
+ *
60
+ * @param path Versioned or v1-relative API path.
61
+ * @param options Request options.
62
+ * @param options.body Encoded request body.
63
+ * @param options.headers Additional request headers.
64
+ * @param options.method HTTP method.
65
+ * @param options.query Query parameters.
66
+ */
67
+ async function fetchResponse(
68
+ path: string,
69
+ options: {
70
+ body?: RequestInit["body"]
71
+ headers?: RequestInit["headers"]
72
+ method?: "DELETE" | "GET" | "PATCH" | "POST"
73
+ query?: Record<
74
+ string,
75
+ boolean | number | readonly string[] | string | undefined
76
+ >
77
+ },
78
+ ) {
79
+ const headers = new Headers(options.headers)
80
+ headers.set("xi-api-key", apiKey)
81
+ const response = await fetch(createElevenLabsUrl(path, options.query), {
82
+ body: options.body,
83
+ headers,
84
+ method: options.method ?? "GET",
85
+ })
86
+ if (!response.ok) {
87
+ const body = encodableSchema.safeParse(
88
+ fromElevenLabs(parseJson(await response.text())),
89
+ )
90
+ throw new ElevenLabsApiError({
91
+ ...(body.success && { body: body.data }),
92
+ retryAfter: parseRetryAfter(response.headers.get("retry-after")),
93
+ status: response.status,
94
+ })
95
+ }
96
+ return response
97
+ }
98
+
99
+ return {
100
+ async request<TSchema extends z.ZodType>(
101
+ path: string,
102
+ options: ElevenLabsRequestOptions<TSchema>,
103
+ ): Promise<z.output<TSchema>> {
104
+ return options.responseSchema.parse(
105
+ fromElevenLabs(
106
+ parseJson(
107
+ await (
108
+ await fetchResponse(path, {
109
+ body:
110
+ options.body === undefined
111
+ ? undefined
112
+ : JSON.stringify(toElevenLabs(options.body)),
113
+ headers:
114
+ options.body === undefined
115
+ ? { Accept: "application/json" }
116
+ : {
117
+ Accept: "application/json",
118
+ "Content-Type": "application/json",
119
+ },
120
+ method: options.method,
121
+ query: options.query,
122
+ })
123
+ ).text(),
124
+ ),
125
+ ),
126
+ )
127
+ },
128
+
129
+ async requestFile(
130
+ path: string,
131
+ options: Omit<ElevenLabsRequestOptions<z.ZodType>, "responseSchema"> & {
132
+ filename: string
133
+ },
134
+ ) {
135
+ const response = await fetchResponse(path, {
136
+ body:
137
+ options.body === undefined
138
+ ? undefined
139
+ : JSON.stringify(toElevenLabs(options.body)),
140
+ headers:
141
+ options.body === undefined
142
+ ? undefined
143
+ : { "Content-Type": "application/json" },
144
+ method: options.method,
145
+ query: options.query,
146
+ })
147
+ const contentType =
148
+ response.headers.get("content-type") ?? "application/octet-stream"
149
+ return new File(
150
+ [await response.blob()],
151
+ contentType.startsWith("video/mp4")
152
+ ? options.filename.replace(/\.[^.]+$/, ".mp4")
153
+ : options.filename,
154
+ {
155
+ type: contentType,
156
+ },
157
+ )
158
+ },
159
+
160
+ async requestForm<TSchema extends z.ZodType>(
161
+ path: string,
162
+ form: FormData,
163
+ options: {
164
+ query?: ElevenLabsRequestOptions<TSchema>["query"]
165
+ responseSchema: TSchema
166
+ },
167
+ ): Promise<z.output<TSchema>> {
168
+ return options.responseSchema.parse(
169
+ fromElevenLabs(
170
+ parseJson(
171
+ await (
172
+ await fetchResponse(path, {
173
+ body: form,
174
+ method: "POST",
175
+ query: options.query,
176
+ })
177
+ ).text(),
178
+ ),
179
+ ),
180
+ )
181
+ },
182
+
183
+ async requestFormFile(
184
+ path: string,
185
+ form: FormData,
186
+ options: {
187
+ filename: string
188
+ query?: ElevenLabsRequestOptions<z.ZodType>["query"]
189
+ },
190
+ ) {
191
+ const response = await fetchResponse(path, {
192
+ body: form,
193
+ method: "POST",
194
+ query: options.query,
195
+ })
196
+ const contentType =
197
+ response.headers.get("content-type") ?? "application/octet-stream"
198
+ return new File(
199
+ [await response.blob()],
200
+ contentType.startsWith("video/mp4")
201
+ ? options.filename.replace(/\.[^.]+$/, ".mp4")
202
+ : options.filename,
203
+ { type: contentType },
204
+ )
205
+ },
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Converts camelCase values into ElevenLabs wire names.
211
+ *
212
+ * @param value Public value.
213
+ */
214
+ export function toElevenLabs(value: Encodable): Encodable {
215
+ return encodeElevenLabsValue(value, false)
216
+ }
217
+
218
+ /**
219
+ * Converts one value while optionally preserving opaque object keys.
220
+ *
221
+ * @param value Public value.
222
+ * @param preserveKeys Whether object keys are user-defined identifiers.
223
+ */
224
+ function encodeElevenLabsValue(
225
+ value: Encodable,
226
+ preserveKeys: boolean,
227
+ ): Encodable {
228
+ if (value instanceof Date) return value.toISOString()
229
+ if (Array.isArray(value)) {
230
+ return value.map((item) => encodeElevenLabsValue(item, preserveKeys))
231
+ }
232
+ if (!isPlainObject(value)) return value
233
+ return Object.fromEntries(
234
+ Object.entries(value).map(([key, item]) => {
235
+ const encodedKey = preserveKeys
236
+ ? key
237
+ : key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
238
+ return [
239
+ encodedKey,
240
+ encodeElevenLabsValue(
241
+ item,
242
+ preserveKeys || encodedKey === "dynamic_variables",
243
+ ),
244
+ ]
245
+ }),
246
+ )
247
+ }
248
+
249
+ /**
250
+ * Converts ElevenLabs wire names into camelCase values.
251
+ *
252
+ * @param value Provider value.
253
+ */
254
+ export function fromElevenLabs(value: unknown): unknown {
255
+ if (Array.isArray(value)) return value.map(fromElevenLabs)
256
+ if (!isPlainObject(value)) return value
257
+ return Object.fromEntries(
258
+ Object.entries(value).map(([key, item]) => [
259
+ key.replace(/_([a-z0-9])/g, (_, letter: string) => letter.toUpperCase()),
260
+ fromElevenLabs(item),
261
+ ]),
262
+ )
263
+ }
264
+
265
+ /**
266
+ * Creates a same-origin ElevenLabs URL with encoded query parameters.
267
+ *
268
+ * @param path Versioned or v1-relative API path.
269
+ * @param query Query parameters.
270
+ * @throws When a path resolves outside the ElevenLabs API origin.
271
+ */
272
+ function createElevenLabsUrl(
273
+ path: string,
274
+ query?: ElevenLabsRequestOptions<z.ZodType>["query"],
275
+ ) {
276
+ const requestedPath = path.replace(/^\//, "")
277
+ const url = new URL(
278
+ /^v\d+\//.test(requestedPath) ? requestedPath : `v1/${requestedPath}`,
279
+ ELEVENLABS_API_BASE_URL,
280
+ )
281
+ if (url.origin !== ELEVENLABS_API_ORIGIN) {
282
+ throw new Error("ElevenLabs API paths must use the ElevenLabs API origin.")
283
+ }
284
+ for (const [name, value] of Object.entries(query ?? {})) {
285
+ if (value === undefined) continue
286
+ const key = name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
287
+ if (Array.isArray(value)) {
288
+ for (const item of value) url.searchParams.append(key, item)
289
+ } else {
290
+ url.searchParams.set(key, String(value))
291
+ }
292
+ }
293
+ return url
294
+ }
295
+
296
+ /**
297
+ * Parses a JSON response without masking the original HTTP status.
298
+ *
299
+ * @param value Response text.
300
+ */
301
+ function parseJson(value: string): unknown {
302
+ try {
303
+ return JSON.parse(value)
304
+ } catch {
305
+ return undefined
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Parses a numeric Retry-After header in seconds.
311
+ *
312
+ * @param value Header value.
313
+ */
314
+ function parseRetryAfter(value: string | null) {
315
+ if (value === null) return undefined
316
+ const seconds = Number(value)
317
+ return Number.isFinite(seconds) ? seconds : undefined
318
+ }
319
+
320
+ /**
321
+ * Extracts the provider's most useful structured error message.
322
+ *
323
+ * @param body Parsed response body.
324
+ */
325
+ function getErrorMessage(body: Encodable | undefined) {
326
+ if (!isPlainObject(body)) return undefined
327
+ if (typeof body.message === "string") return body.message
328
+ if (isPlainObject(body.detail) && typeof body.detail.message === "string") {
329
+ return body.detail.message
330
+ }
331
+ return undefined
332
+ }
333
+
334
+ /**
335
+ * Checks whether a value is a non-array object.
336
+ *
337
+ * @param value Candidate value.
338
+ */
339
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
340
+ return typeof value === "object" && value !== null && !Array.isArray(value)
341
+ }
@@ -0,0 +1,58 @@
1
+ import * as z from "zod"
2
+ import {
3
+ ELEVENLABS_FLOW_GENERATION_EVENT_SCHEMA,
4
+ ELEVENLABS_TRANSCRIPTION_EVENT_SCHEMA,
5
+ ELEVENLABS_VOICE_REMOVAL_EVENT_SCHEMA,
6
+ ELEVENLABS_WEBHOOK_EVENT_SCHEMA,
7
+ } from "./schemas"
8
+
9
+ export const ELEVENLABS_TRIGGER_CONFIG_SCHEMA = z.object({})
10
+
11
+ export const elevenLabsTriggerContracts = {
12
+ "elevenlabs.event": {
13
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
14
+ eventSchema: ELEVENLABS_WEBHOOK_EVENT_SCHEMA,
15
+ },
16
+ "elevenlabs.flowGenerationCompleted": {
17
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
18
+ eventSchema: ELEVENLABS_FLOW_GENERATION_EVENT_SCHEMA.refine(
19
+ ({ data }) => data.status === "completed",
20
+ ),
21
+ },
22
+ "elevenlabs.flowGenerationEvent": {
23
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
24
+ eventSchema: ELEVENLABS_FLOW_GENERATION_EVENT_SCHEMA,
25
+ },
26
+ "elevenlabs.flowGenerationFailed": {
27
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
28
+ eventSchema: ELEVENLABS_FLOW_GENERATION_EVENT_SCHEMA.refine(
29
+ ({ data }) => data.status === "failed",
30
+ ),
31
+ },
32
+ "elevenlabs.transcriptionCompleted": {
33
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
34
+ eventSchema: ELEVENLABS_TRANSCRIPTION_EVENT_SCHEMA,
35
+ },
36
+ "elevenlabs.voiceRemovalEvent": {
37
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
38
+ eventSchema: ELEVENLABS_VOICE_REMOVAL_EVENT_SCHEMA,
39
+ },
40
+ "elevenlabs.voiceRemovalScheduled": {
41
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
42
+ eventSchema: ELEVENLABS_VOICE_REMOVAL_EVENT_SCHEMA.extend({
43
+ type: z.literal("voice_removal_notice"),
44
+ }),
45
+ },
46
+ "elevenlabs.voiceRemovalWithdrawn": {
47
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
48
+ eventSchema: ELEVENLABS_VOICE_REMOVAL_EVENT_SCHEMA.extend({
49
+ type: z.literal("voice_removal_notice_withdrawn"),
50
+ }),
51
+ },
52
+ "elevenlabs.voiceRemoved": {
53
+ configSchema: ELEVENLABS_TRIGGER_CONFIG_SCHEMA,
54
+ eventSchema: ELEVENLABS_VOICE_REMOVAL_EVENT_SCHEMA.extend({
55
+ type: z.literal("voice_removed"),
56
+ }),
57
+ },
58
+ } as const
@@ -0,0 +1,3 @@
1
+ export * from "./api"
2
+ export * from "./events"
3
+ export * from "./schemas"