@zapier/zapier-sdk 0.68.0 → 0.69.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.
Files changed (35) hide show
  1. package/AGENTS.md +321 -0
  2. package/CHANGELOG.md +15 -0
  3. package/CLAUDE.md +3 -319
  4. package/README.md +17 -17
  5. package/dist/api/client.d.ts.map +1 -1
  6. package/dist/api/client.js +108 -3
  7. package/dist/api/error-classification.d.ts +12 -0
  8. package/dist/api/error-classification.d.ts.map +1 -0
  9. package/dist/api/error-classification.js +18 -0
  10. package/dist/api/index.d.ts +2 -0
  11. package/dist/api/index.d.ts.map +1 -1
  12. package/dist/api/index.js +3 -0
  13. package/dist/api/sse-parser.d.ts +17 -0
  14. package/dist/api/sse-parser.d.ts.map +1 -0
  15. package/dist/api/sse-parser.js +67 -0
  16. package/dist/api/types.d.ts +16 -0
  17. package/dist/api/types.d.ts.map +1 -1
  18. package/dist/experimental.cjs +356 -67
  19. package/dist/experimental.d.mts +2 -2
  20. package/dist/experimental.mjs +356 -67
  21. package/dist/{index-oRnHsPn5.d.mts → index-DC31DAP2.d.mts} +20 -1
  22. package/dist/{index-oRnHsPn5.d.ts → index-DC31DAP2.d.ts} +20 -1
  23. package/dist/index.cjs +131 -7
  24. package/dist/index.d.mts +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.mjs +131 -7
  28. package/dist/plugins/triggers/drainTriggerInbox/schemas.js +2 -2
  29. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts +31 -6
  30. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts.map +1 -1
  31. package/dist/plugins/triggers/watchTriggerInbox/index.js +272 -65
  32. package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts +30 -0
  33. package/dist/plugins/triggers/watchTriggerInbox/sse.d.ts.map +1 -0
  34. package/dist/plugins/triggers/watchTriggerInbox/sse.js +38 -0
  35. package/package.json +2 -1
@@ -4,46 +4,213 @@ import { requireOnMessage, resolveConcurrencyAndLease, runDrainPass, } from "../
4
4
  import { resolveTriggerInboxId } from "../utils";
5
5
  import { triggerInboxResolver } from "../../../resolvers";
6
6
  import { triggersDefaults } from "../shared";
7
- const POLL_STAGES = [
8
- [125, 125],
9
- [375, 250],
10
- [875, 500],
11
- [10000, 1000],
12
- [30000, 2500],
13
- [60000, 5000],
14
- [180000, 10000],
15
- ];
16
- const DEFAULT_MAX_DRAIN_INTERVAL_MS = 60000;
17
- function pickPollIntervalMs(elapsedMs, maxIntervalMs) {
18
- const stage = POLL_STAGES.find(([threshold]) => elapsedMs < threshold);
19
- const interval = stage ? stage[1] : maxIntervalMs;
20
- return Math.min(interval, maxIntervalMs);
7
+ import { isAbortError, combineAbortSignals } from "../../../utils/abort-utils";
8
+ import { sleep } from "../../../utils/retry-utils";
9
+ import { isPermanentHttpError } from "../../../api";
10
+ import { ZapierError } from "../../../types/errors";
11
+ import { readInboxEvents } from "./sse";
12
+ // First entry is the floor, not 0: a server that closes the stream immediately
13
+ // (instant EOF, empty body) must not be able to reconnect with no delay, or it
14
+ // busy-loops. Repeated instant closes escalate through the later entries (up to
15
+ // 5 s); only a clean end after a healthy, long-lived connection resets to the
16
+ // floor see SSE_HEALTHY_CONNECTION_MS.
17
+ const SSE_RECONNECT_BACKOFF_MS = [500, 1000, 2000, 5000];
18
+ const DEFAULT_SAFETY_DRAIN_INTERVAL_MS = 300000; // 5 minutes
19
+ // A clean stream end only resets the reconnect backoff to the floor if the
20
+ // connection stayed open at least this long. A connection that lived longer
21
+ // than our longest reconnect backoff is "healthy", so resetting gives fast
22
+ // recovery from routine server-side cycling (e.g. JWT expiry); a stream that
23
+ // closes sooner is treated as unstable and escalates like any other.
24
+ const SSE_HEALTHY_CONNECTION_MS = 5000;
25
+ /**
26
+ * Coalescing wake latch between the drain producers (SSE connect, SSE frames,
27
+ * the safety timer, abort) and the single drain consumer. A `request()` that
28
+ * lands while the consumer is mid-drain is not lost: `pending` stays set, so
29
+ * the consumer's next `waitForRequest()` returns immediately and runs one more
30
+ * drain. Mirrors `createWaiter` in drainTriggerInbox/pipeline.ts.
31
+ *
32
+ * Exported for unit testing the coalescing / no-lost-wakeup semantics.
33
+ */
34
+ export function createDrainLatch() {
35
+ let pending = false;
36
+ const make = () => {
37
+ let resolve;
38
+ const promise = new Promise((r) => {
39
+ resolve = r;
40
+ });
41
+ return { promise, resolve };
42
+ };
43
+ let current = make();
44
+ return {
45
+ request() {
46
+ pending = true;
47
+ const prev = current;
48
+ current = make();
49
+ prev.resolve();
50
+ },
51
+ async waitForRequest() {
52
+ if (pending) {
53
+ pending = false;
54
+ return;
55
+ }
56
+ await current.promise;
57
+ pending = false;
58
+ },
59
+ };
60
+ }
61
+ /**
62
+ * Sole caller of `runDrainPass`, so drains are serial and never overlap. Waits
63
+ * for a request, drains the inbox to empty, repeats. The first pass carries
64
+ * `firstFetch: true` (preserving the initialization_failure check). Returns how
65
+ * the watch ended so the caller can resolve or re-throw.
66
+ */
67
+ async function drainRunner({ drainOptions, drainRequest, signal, }) {
68
+ let firstFetch = true;
69
+ while (!signal.aborted) {
70
+ await drainRequest.waitForRequest();
71
+ if (signal.aborted)
72
+ return { kind: "aborted" };
73
+ let abortedFromCallback = false;
74
+ try {
75
+ ({ abortedFromCallback } = await runDrainPass({
76
+ ...drainOptions,
77
+ firstFetch,
78
+ }));
79
+ }
80
+ catch (error) {
81
+ return { kind: "error", error };
82
+ }
83
+ firstFetch = false;
84
+ if (abortedFromCallback)
85
+ return { kind: "abortedFromCallback" };
86
+ }
87
+ return { kind: "aborted" };
88
+ }
89
+ /** statusCode off any ZapierError (ZapierApiError, ZapierApprovalError, …). */
90
+ function sseErrorStatusCode(err) {
91
+ return err instanceof ZapierError ? err.statusCode : undefined;
21
92
  }
22
- function sleepOrAbort(ms, signal) {
23
- if (signal?.aborted)
24
- return Promise.resolve();
25
- return new Promise((resolve) => {
26
- let settled = false;
27
- const settle = () => {
28
- if (settled)
93
+ function sseErrorMessage(err) {
94
+ return err instanceof Error ? err.message : String(err);
95
+ }
96
+ /**
97
+ * Holds one persistent SSE connection open and requests a drain on connect
98
+ * (catch-up) and on every frame, keeping the socket open across drains so no
99
+ * notification window is missed. Reconnects with backoff on clean end or
100
+ * transient error. A 4xx is permanent for now (auth/permission/bad request);
101
+ * rather than hammer the endpoint or abandon SSE, it waits a full safety
102
+ * interval before retrying so a transient misconfiguration still self-heals,
103
+ * while the independent safety timer keeps draining. (429 never lands here —
104
+ * api.fetch retries it upstream as ZapierRateLimitError, treated as transient.)
105
+ *
106
+ * Failures are printed to stderr (never stdout, so `--json` NDJSON stays clean):
107
+ * permanent degradation warns once on entering the degraded state (silent on
108
+ * the per-safety-interval retries, warning again only after a reconnect then
109
+ * re-failure); transient reconnects are dim diagnostics printed only under
110
+ * `debug`. Clean stream ends (e.g. routine JWT-expiry cycling) print nothing,
111
+ * so a reconnect notice always means an error broke the stream.
112
+ */
113
+ async function sseLoop({ api, inboxId, drainRequest, safetyDrainMs, signal, debug, }) {
114
+ let attempt = 0;
115
+ // Whether we've already warned about the current permanent-failure spell.
116
+ // Warn once on entering it; stay silent on retries; re-arm on a successful
117
+ // reconnect (onOpen) — the only way out, since a permanent 4xx throws before
118
+ // onOpen fires. Without this a standing 4xx prints the same warning every
119
+ // safety interval forever.
120
+ let degraded = false;
121
+ while (!signal.aborted) {
122
+ let connected = false;
123
+ let connectedAt = 0;
124
+ let transientError;
125
+ try {
126
+ for await (const _event of readInboxEvents({
127
+ api,
128
+ inboxId,
129
+ signal,
130
+ // SSE is edge-triggered with no replay, so draining on connect is the
131
+ // only way to pick up messages that arrived before this connection —
132
+ // including the window left by a prior disconnect.
133
+ onOpen: () => {
134
+ connected = true;
135
+ connectedAt = Date.now();
136
+ degraded = false;
137
+ drainRequest.request();
138
+ },
139
+ })) {
140
+ drainRequest.request();
141
+ }
142
+ // Clean stream end. Reset to the floor only if the connection stayed open
143
+ // long enough to count as healthy (e.g. routine JWT-expiry cycling), so a
144
+ // server that accepts the connection and immediately closes it escalates
145
+ // through the backoff below instead of reconnecting at a fixed 500 ms.
146
+ if (connected && Date.now() - connectedAt >= SSE_HEALTHY_CONNECTION_MS) {
147
+ attempt = 0;
148
+ }
149
+ }
150
+ catch (err) {
151
+ if (signal.aborted || isAbortError(err))
29
152
  return;
30
- settled = true;
31
- clearTimeout(timer);
32
- signal?.removeEventListener("abort", settle);
33
- resolve();
34
- };
35
- const timer = setTimeout(settle, ms);
36
- signal?.addEventListener("abort", settle, { once: true });
37
- });
153
+ if (isPermanentHttpError(err)) {
154
+ if (!degraded) {
155
+ const statusCode = sseErrorStatusCode(err);
156
+ const errorMsg = sseErrorMessage(err);
157
+ const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
158
+ console.warn(`[zapier-sdk] Real-time wake-ups for inbox ${inboxId}${httpPart} ` +
159
+ `paused: ${errorMsg}. Falling back to the periodic safety drain.`);
160
+ degraded = true;
161
+ }
162
+ attempt = 0;
163
+ if (signal.aborted)
164
+ return;
165
+ await sleep(safetyDrainMs, signal);
166
+ continue;
167
+ }
168
+ // A connection that opened then dropped proves the endpoint is reachable,
169
+ // so reset to the floor; a failed connect grows the backoff instead.
170
+ if (connected)
171
+ attempt = 0;
172
+ transientError = err;
173
+ }
174
+ if (signal.aborted)
175
+ return;
176
+ const delay = SSE_RECONNECT_BACKOFF_MS[Math.min(attempt, SSE_RECONNECT_BACKOFF_MS.length - 1)];
177
+ attempt = Math.min(attempt + 1, SSE_RECONNECT_BACKOFF_MS.length - 1);
178
+ if (transientError !== undefined && debug) {
179
+ const statusCode = sseErrorStatusCode(transientError);
180
+ const errorMsg = sseErrorMessage(transientError);
181
+ const httpPart = statusCode !== undefined ? ` (HTTP ${statusCode})` : "";
182
+ console.error(`[zapier-sdk] Reconnecting real-time wake-ups for inbox ${inboxId} ` +
183
+ `(attempt ${attempt}, retry in ${delay}ms)${httpPart}: ${errorMsg}`);
184
+ }
185
+ await sleep(delay, signal);
186
+ }
187
+ }
188
+ /**
189
+ * Requests a drain every `safetyDrainMs`, independent of SSE state. Guarantees
190
+ * forward progress when SSE frames are missed, the connection drops undetected,
191
+ * or SSE is degraded. The timer is ref'd (like the prior poll loop) so the
192
+ * watch keeps the process alive until aborted — that is what makes this a real
193
+ * backstop even when SSE is parked on a permanent failure with no open socket.
194
+ */
195
+ async function safetyTimerLoop({ safetyDrainMs, drainRequest, signal, }) {
196
+ while (!signal.aborted) {
197
+ await sleep(safetyDrainMs, signal);
198
+ if (signal.aborted)
199
+ return;
200
+ drainRequest.request();
201
+ }
38
202
  }
39
203
  /**
40
204
  * `watchTriggerInbox` is the continuous-consumption variant of
41
205
  * `drainTriggerInbox`. Same options surface, same callback shape;
42
- * the difference is that an empty lease triggers poll-and-wait
43
- * (with backoff) rather than ending the command. Resolves cleanly
44
- * on `signal` abort or when a callback throws
45
- * `ZapierAbortDrainSignal`; rejects on a fatal error or a
46
- * fail-fast handler error (when `continueOnError` is false).
206
+ * the difference is that after draining the inbox, it subscribes to
207
+ * a Server-Sent Events stream to wake on new-message notifications
208
+ * rather than polling. A periodic safety drain fires every
209
+ * `maxDrainIntervalSeconds` seconds (default: 300) to guarantee
210
+ * forward progress if SSE events are missed or the connection drops
211
+ * undetected. Resolves cleanly on `signal` abort or when a callback
212
+ * throws `ZapierAbortDrainSignal`; rejects on a fatal error or a
213
+ * fail-fast handler error.
47
214
  */
48
215
  export const watchTriggerInboxPlugin = definePlugin((sdk) => {
49
216
  async function watchTriggerInbox(options) {
@@ -53,38 +220,78 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
53
220
  api: sdk.context.api,
54
221
  inbox: options.inbox,
55
222
  });
56
- const maxDrainIntervalMs = options.maxDrainIntervalSeconds !== undefined
223
+ if (options.signal?.aborted)
224
+ return;
225
+ const safetyDrainMs = options.maxDrainIntervalSeconds !== undefined
57
226
  ? options.maxDrainIntervalSeconds * 1000
58
- : DEFAULT_MAX_DRAIN_INTERVAL_MS;
59
- let firstFetch = true;
60
- let lastNonEmptyAt = Date.now();
61
- while (!options.signal?.aborted) {
62
- const outcome = await runDrainPass({
63
- sdk,
64
- inboxId,
65
- onMessage,
66
- concurrency,
67
- leaseLimit,
68
- leaseSeconds: options.leaseSeconds,
69
- maxMessages: undefined,
70
- releaseOnError: options.releaseOnError ?? false,
71
- continueOnError: options.continueOnError ?? false,
72
- onError: options.onError,
73
- signal: options.signal,
74
- firstFetch,
75
- });
76
- firstFetch = false;
77
- if (options.signal?.aborted)
78
- return;
79
- if (outcome.abortedFromCallback)
80
- return;
81
- if (outcome.processed > 0) {
82
- lastNonEmptyAt = Date.now();
83
- continue;
84
- }
85
- const elapsedMs = Date.now() - lastNonEmptyAt;
86
- await sleepOrAbort(pickPollIntervalMs(elapsedMs, maxDrainIntervalMs), options.signal);
227
+ : DEFAULT_SAFETY_DRAIN_INTERVAL_MS;
228
+ // Internal stop lets the drain runner's terminal states (a handler's
229
+ // ZapierAbortDrainSignal, a fatal error) tear down the background SSE and
230
+ // safety loops. Combined with the caller's signal so either stops all.
231
+ const stop = new AbortController();
232
+ const combined = combineAbortSignals({
233
+ handles: [
234
+ ...(options.signal
235
+ ? [{ signal: options.signal, dispose: () => { } }]
236
+ : []),
237
+ { signal: stop.signal, dispose: () => { } },
238
+ ],
239
+ });
240
+ const signal = combined?.signal ?? stop.signal;
241
+ const drainRequest = createDrainLatch();
242
+ // Abort wakes the idle drain runner so it observes the abort and exits;
243
+ // the latch otherwise only resolves on a drain request.
244
+ signal.addEventListener("abort", () => drainRequest.request(), {
245
+ once: true,
246
+ });
247
+ // Real-time health is printed to stderr by the SSE loop (warnings always,
248
+ // reconnect diagnostics only under `debug`), so every consumer surfaces it
249
+ // without wiring anything — the watcher never routes this through onEvent.
250
+ const debug = sdk.context.options.debug === true;
251
+ const drainOptions = {
252
+ sdk,
253
+ inboxId,
254
+ onMessage,
255
+ concurrency,
256
+ leaseLimit,
257
+ leaseSeconds: options.leaseSeconds,
258
+ maxMessages: undefined,
259
+ releaseOnError: options.releaseOnError ?? false,
260
+ continueOnError: options.continueOnError ?? false,
261
+ onError: options.onError,
262
+ signal,
263
+ };
264
+ // Unconditional initial drain (firstFetch: true): handle anything already
265
+ // in the inbox even if SSE never connects (e.g. a permanent 4xx). SSE and
266
+ // the safety timer then layer on additional catch-up drains.
267
+ drainRequest.request();
268
+ const runnerDone = drainRunner({ drainOptions, drainRequest, signal });
269
+ const sseDone = sseLoop({
270
+ api: sdk.context.api,
271
+ inboxId,
272
+ drainRequest,
273
+ safetyDrainMs,
274
+ signal,
275
+ debug,
276
+ }).catch(() => { });
277
+ const safetyDone = safetyTimerLoop({
278
+ safetyDrainMs,
279
+ drainRequest,
280
+ signal,
281
+ }).catch(() => { });
282
+ let end;
283
+ try {
284
+ end = await runnerDone;
285
+ }
286
+ finally {
287
+ // Stop the producers and wait for them to unwind so no fetch or timer
288
+ // outlives this call, then release the combined-signal listeners.
289
+ stop.abort();
290
+ await Promise.all([sseDone, safetyDone]);
291
+ combined?.dispose();
87
292
  }
293
+ if (end.kind === "error")
294
+ throw end.error;
88
295
  }
89
296
  return {
90
297
  watchTriggerInbox,
@@ -93,7 +300,7 @@ export const watchTriggerInboxPlugin = definePlugin((sdk) => {
93
300
  watchTriggerInbox: {
94
301
  ...triggersDefaults,
95
302
  type: "create",
96
- description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then poll with backoff for new arrivals, until aborted. Resolves cleanly on signal abort or ZapierAbortDrainSignal from a handler; rejects on fatal SDK errors or fail-fast handler errors.",
303
+ description: "Continuously consume a trigger inbox: drain currently-available messages via onMessage, then subscribe to SSE notifications for new arrivals until aborted. A periodic safety drain runs every maxDrainIntervalSeconds (default: 300) to guarantee forward progress if SSE events are missed. Resolves cleanly on signal abort or ZapierAbortDrainSignal from a handler; rejects on fatal SDK errors or fail-fast handler errors. Real-time wake-up health is reported on stderr: a warning when wake-ups pause and the watch falls back to the safety drain, plus (with debug) transient reconnect notices.",
97
304
  itemType: "void",
98
305
  // See drainTriggerInbox: override the doc generator's default
99
306
  // suffix so the rendered return type is Promise<void>, not
@@ -0,0 +1,30 @@
1
+ import type { ApiClient } from "../../../api";
2
+ interface InboxSseEvent {
3
+ inbox_id: string;
4
+ count?: number;
5
+ }
6
+ /**
7
+ * Streams the trigger inbox events endpoint and yields the notification frames
8
+ * for this inbox. The transport (SSE connection, parsing, reconnect-able error
9
+ * mapping, abort/cleanup) lives in `api.fetchStream`; this only adds the inbox
10
+ * URL and the `inbox_id` filter.
11
+ *
12
+ * `onOpen` fires once the connection is live (response ok, body present) and
13
+ * before the first frame. The endpoint is edge-triggered with no replay, so a
14
+ * caller must drain on `onOpen` to pick up messages that arrived before this
15
+ * connection existed — a frame alone never covers that window.
16
+ *
17
+ * The generator returns (without throwing) when the signal is aborted, when
18
+ * the server closes the connection (JWT expiry, clean shutdown), or when the
19
+ * body is empty. For a non-ok response `api.fetchStream` throws a `ZapierError`
20
+ * carrying the HTTP `statusCode`; `isPermanentHttpError` classifies it so the
21
+ * caller can tell permanent (4xx) failures from transient ones.
22
+ */
23
+ export declare function readInboxEvents({ api, inboxId, signal, onOpen, }: {
24
+ api: ApiClient;
25
+ inboxId: string;
26
+ signal?: AbortSignal;
27
+ onOpen?: () => void;
28
+ }): AsyncGenerator<InboxSseEvent>;
29
+ export {};
30
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/watchTriggerInbox/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IAIjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAuB,eAAe,CAAC,EACrC,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,GAAG,EAAE,SAAS,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,GAAG,cAAc,CAAC,aAAa,CAAC,CAyBhC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Streams the trigger inbox events endpoint and yields the notification frames
3
+ * for this inbox. The transport (SSE connection, parsing, reconnect-able error
4
+ * mapping, abort/cleanup) lives in `api.fetchStream`; this only adds the inbox
5
+ * URL and the `inbox_id` filter.
6
+ *
7
+ * `onOpen` fires once the connection is live (response ok, body present) and
8
+ * before the first frame. The endpoint is edge-triggered with no replay, so a
9
+ * caller must drain on `onOpen` to pick up messages that arrived before this
10
+ * connection existed — a frame alone never covers that window.
11
+ *
12
+ * The generator returns (without throwing) when the signal is aborted, when
13
+ * the server closes the connection (JWT expiry, clean shutdown), or when the
14
+ * body is empty. For a non-ok response `api.fetchStream` throws a `ZapierError`
15
+ * carrying the HTTP `statusCode`; `isPermanentHttpError` classifies it so the
16
+ * caller can tell permanent (4xx) failures from transient ones.
17
+ */
18
+ export async function* readInboxEvents({ api, inboxId, signal, onOpen, }) {
19
+ for await (const message of api.fetchStream(`/trigger-inbox/api/v1/inboxes/${encodeURIComponent(inboxId)}/events`, { method: "GET", signal, authRequired: true, onOpen })) {
20
+ let parsed;
21
+ try {
22
+ parsed = JSON.parse(message.data);
23
+ }
24
+ catch {
25
+ continue;
26
+ }
27
+ if (typeof parsed === "object" &&
28
+ parsed !== null &&
29
+ typeof parsed.inbox_id === "string" &&
30
+ // Only wake on a frame for this inbox. Case-insensitive: the endpoint
31
+ // echoes the canonical lowercase UUID, but resolveTriggerInboxId passes
32
+ // a UUID-shaped `inbox` through unchanged, so its casing may differ.
33
+ parsed.inbox_id.toLowerCase() ===
34
+ inboxId.toLowerCase()) {
35
+ yield parsed;
36
+ }
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk",
3
- "version": "0.68.0",
3
+ "version": "0.69.0",
4
4
  "description": "Complete Zapier SDK - combines all Zapier SDK packages",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -61,6 +61,7 @@
61
61
  "files": [
62
62
  "dist",
63
63
  "README.md",
64
+ "AGENTS.md",
64
65
  "CLAUDE.md",
65
66
  "CHANGELOG.md"
66
67
  ],