agentmail 0.5.33 → 0.5.35

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 (31) hide show
  1. package/dist/cjs/BaseClient.js +2 -2
  2. package/dist/cjs/api/resources/apiKeys/client/Client.d.ts +2 -2
  3. package/dist/cjs/api/resources/apiKeys/client/Client.js +2 -2
  4. package/dist/cjs/api/resources/apiKeys/types/AcceptDisclosure.d.ts +1 -1
  5. package/dist/cjs/api/resources/apiKeys/types/ApiKeyPermissions.d.ts +5 -5
  6. package/dist/cjs/api/resources/apiKeys/types/PublicKeyCredential.d.ts +2 -2
  7. package/dist/cjs/serialization/resources/apiKeys/types/ApiKeyPermissions.d.ts +2 -2
  8. package/dist/cjs/serialization/resources/apiKeys/types/ApiKeyPermissions.js +2 -2
  9. package/dist/cjs/version.d.ts +1 -1
  10. package/dist/cjs/version.js +1 -1
  11. package/dist/cjs/wrapper/WebsocketsClient.d.ts +15 -1
  12. package/dist/cjs/wrapper/WebsocketsClient.js +26 -8
  13. package/dist/cjs/wrapper/reconnect.d.ts +22 -0
  14. package/dist/cjs/wrapper/reconnect.js +246 -0
  15. package/dist/esm/BaseClient.mjs +2 -2
  16. package/dist/esm/api/resources/apiKeys/client/Client.d.mts +2 -2
  17. package/dist/esm/api/resources/apiKeys/client/Client.mjs +2 -2
  18. package/dist/esm/api/resources/apiKeys/types/AcceptDisclosure.d.mts +1 -1
  19. package/dist/esm/api/resources/apiKeys/types/ApiKeyPermissions.d.mts +5 -5
  20. package/dist/esm/api/resources/apiKeys/types/PublicKeyCredential.d.mts +2 -2
  21. package/dist/esm/serialization/resources/apiKeys/types/ApiKeyPermissions.d.mts +2 -2
  22. package/dist/esm/serialization/resources/apiKeys/types/ApiKeyPermissions.mjs +2 -2
  23. package/dist/esm/version.d.mts +1 -1
  24. package/dist/esm/version.mjs +1 -1
  25. package/dist/esm/wrapper/WebsocketsClient.d.mts +15 -1
  26. package/dist/esm/wrapper/WebsocketsClient.mjs +26 -8
  27. package/dist/esm/wrapper/reconnect.d.mts +22 -0
  28. package/dist/esm/wrapper/reconnect.mjs +208 -0
  29. package/dist/llms.txt +7 -7
  30. package/package.json +1 -1
  31. package/reference.md +2 -2
@@ -43,8 +43,8 @@ function normalizeClientOptions(options) {
43
43
  const headers = (0, headers_js_1.mergeHeaders)({
44
44
  "X-Fern-Language": "JavaScript",
45
45
  "X-Fern-SDK-Name": "agentmail",
46
- "X-Fern-SDK-Version": "0.5.33",
47
- "User-Agent": "agentmail/0.5.33",
46
+ "X-Fern-SDK-Version": "0.5.35",
47
+ "User-Agent": "agentmail/0.5.35",
48
48
  "X-Fern-Runtime": core.RUNTIME.type,
49
49
  "X-Fern-Runtime-Version": core.RUNTIME.version,
50
50
  }, options === null || options === void 0 ? void 0 : options.headers);
@@ -64,8 +64,8 @@ export declare class ApiKeysClient {
64
64
  private __create;
65
65
  /**
66
66
  * Renames a credential or changes its permissions. Public keys also resolve
67
- * by `client_id`; a sign-in key accepts only `provider_connect` and
68
- * `provider_share_owner`.
67
+ * by `client_id`; a sign-in key accepts only `app_connect` and
68
+ * `app_share_owner`.
69
69
  *
70
70
  * @param {AgentMail.ApiKeyId} api_key_id
71
71
  * @param {AgentMail.UpdateApiKeyRequest} request
@@ -278,8 +278,8 @@ class ApiKeysClient {
278
278
  }
279
279
  /**
280
280
  * Renames a credential or changes its permissions. Public keys also resolve
281
- * by `client_id`; a sign-in key accepts only `provider_connect` and
282
- * `provider_share_owner`.
281
+ * by `client_id`; a sign-in key accepts only `app_connect` and
282
+ * `app_share_owner`.
283
283
  *
284
284
  * @param {AgentMail.ApiKeyId} api_key_id
285
285
  * @param {AgentMail.UpdateApiKeyRequest} request
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Accept the app's disclosure on the agent's behalf, skipping the
3
- * first-use disclosure page. Requires `provider_share_owner` when owner
3
+ * first-use disclosure page. Requires `app_share_owner` when owner
4
4
  * scopes are involved.
5
5
  */
6
6
  export type AcceptDisclosure = boolean;
@@ -71,17 +71,17 @@ export interface ApiKeyPermissions {
71
71
  /** Delete API keys. */
72
72
  apiKeyDelete?: boolean;
73
73
  /**
74
- * Sign in to providers as an inbox: connect a provider, authorize an inbox, and mint the
74
+ * Sign in to apps as an inbox: connect an app, authorize an inbox, and mint the
75
75
  * sign-in keys. Omitted on a new bearer key means false, whatever else the key holds.
76
76
  */
77
- providerConnect?: boolean;
77
+ appConnect?: boolean;
78
78
  /**
79
- * Share the organization owner's name and email with providers at sign-in. One permission
79
+ * Share the organization owner's name and email with apps at sign-in. One permission
80
80
  * for both values.
81
81
  */
82
- providerShareOwner?: boolean;
82
+ appShareOwner?: boolean;
83
83
  /**
84
- * Update accounts: disable or re-enable an inbox's sign-in at a provider. Reading accounts
84
+ * Update accounts: disable or re-enable an inbox's sign-in at an app. Reading accounts
85
85
  * needs only `inbox_read`.
86
86
  */
87
87
  accountUpdate?: boolean;
@@ -3,8 +3,8 @@ import type * as AgentMail from "../../../index.js";
3
3
  * An AgentID sign-in credential, scoped like a bearer key; `api_key_id` is
4
4
  * the JWS `kid`. A sign-in key carries `status`, gains `public_key` once
5
5
  * the client has proved it, expires 30 days after
6
- * activation, and carries exactly `provider_connect` and
7
- * `provider_share_owner`, snapshotted from the bearer key that created it
6
+ * activation, and carries exactly `app_connect` and
7
+ * `app_share_owner`, snapshotted from the bearer key that created it
8
8
  * and enforced from the key itself.
9
9
  */
10
10
  export interface PublicKeyCredential {
@@ -38,8 +38,8 @@ export declare namespace ApiKeyPermissions {
38
38
  api_key_create?: boolean | null;
39
39
  api_key_update?: boolean | null;
40
40
  api_key_delete?: boolean | null;
41
- provider_connect?: boolean | null;
42
- provider_share_owner?: boolean | null;
41
+ app_connect?: boolean | null;
42
+ app_share_owner?: boolean | null;
43
43
  account_update?: boolean | null;
44
44
  pod_read?: boolean | null;
45
45
  pod_create?: boolean | null;
@@ -71,8 +71,8 @@ exports.ApiKeyPermissions = core.serialization.object({
71
71
  apiKeyCreate: core.serialization.property("api_key_create", core.serialization.boolean().optional()),
72
72
  apiKeyUpdate: core.serialization.property("api_key_update", core.serialization.boolean().optional()),
73
73
  apiKeyDelete: core.serialization.property("api_key_delete", core.serialization.boolean().optional()),
74
- providerConnect: core.serialization.property("provider_connect", core.serialization.boolean().optional()),
75
- providerShareOwner: core.serialization.property("provider_share_owner", core.serialization.boolean().optional()),
74
+ appConnect: core.serialization.property("app_connect", core.serialization.boolean().optional()),
75
+ appShareOwner: core.serialization.property("app_share_owner", core.serialization.boolean().optional()),
76
76
  accountUpdate: core.serialization.property("account_update", core.serialization.boolean().optional()),
77
77
  podRead: core.serialization.property("pod_read", core.serialization.boolean().optional()),
78
78
  podCreate: core.serialization.property("pod_create", core.serialization.boolean().optional()),
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.5.33";
1
+ export declare const SDK_VERSION = "0.5.35";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SDK_VERSION = void 0;
4
- exports.SDK_VERSION = "0.5.33";
4
+ exports.SDK_VERSION = "0.5.35";
@@ -1,10 +1,24 @@
1
1
  import { WebsocketsClient as FernWebsocketsClient } from "../api/resources/websockets/client/Client.js";
2
2
  import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.js";
3
+ import { type ReconnectPolicy } from "./reconnect.js";
3
4
  export type GetPaymentCredentials = (wsUrl: string) => Promise<Record<string, string>>;
4
5
  export declare class WebsocketsClient extends FernWebsocketsClient {
5
6
  private readonly _getPaymentCredentials;
6
- constructor(options: FernWebsocketsClient.Options, getPaymentCredentials?: GetPaymentCredentials);
7
+ private readonly _reconnectPolicy;
8
+ constructor(options: FernWebsocketsClient.Options, getPaymentCredentials?: GetPaymentCredentials, reconnectPolicy?: ReconnectPolicy);
7
9
  connect(args?: FernWebsocketsClient.ConnectArgs & {
10
+ /** Resolve only once the connection is open. Defaults to true. */
8
11
  waitForOpen?: boolean;
12
+ /**
13
+ * Keep the socket connected and subscribed. Whenever the connection drops, reconnect with
14
+ * exponential backoff (full jitter, 1 s doubling up to 60 s) and re-send every subscription
15
+ * sent through `sendSubscribe` on the new connection; `reconnectAttempts` then only bounds
16
+ * the generated socket's own quick retries, not the total. Stops on `close()`, on
17
+ * `abortSignal`, or when the server refuses the handshake with 401, 402 or 403, which is
18
+ * reported to the `error` handler. x402/MPP sockets reuse the payment credential minted by
19
+ * this call on every reconnect. Defaults to true. `false` (or `reconnectAttempts: 0`) keeps
20
+ * only the generated socket's own limited retries and does not re-subscribe.
21
+ */
22
+ autoReconnect?: boolean;
9
23
  }): Promise<WebsocketsSocket>;
10
24
  }
@@ -57,31 +57,49 @@ exports.WebsocketsClient = void 0;
57
57
  const Client_js_1 = require("../api/resources/websockets/client/Client.js");
58
58
  const core = __importStar(require("../core/index.js"));
59
59
  const environments = __importStar(require("../environments.js"));
60
+ const version_js_1 = require("../version.js");
61
+ const reconnect_js_1 = require("./reconnect.js");
62
+ // The generated client's default. Always passed explicitly: auto-reconnect has to know the generated
63
+ // socket's exact retry budget to take over before it runs out.
64
+ const DEFAULT_RECONNECT_ATTEMPTS = 30;
65
+ // Lets the server tell SDK releases apart on the handshake (Node only; browsers cannot set handshake
66
+ // headers). Caller-supplied headers win.
67
+ const SDK_HEADERS = { "X-Fern-SDK-Name": "agentmail", "X-Fern-SDK-Version": version_js_1.SDK_VERSION };
60
68
  class WebsocketsClient extends Client_js_1.WebsocketsClient {
61
- constructor(options, getPaymentCredentials) {
69
+ constructor(options, getPaymentCredentials, reconnectPolicy = reconnect_js_1.DEFAULT_RECONNECT_POLICY) {
62
70
  super(options);
63
71
  this._getPaymentCredentials = getPaymentCredentials;
72
+ this._reconnectPolicy = reconnectPolicy;
64
73
  }
65
74
  connect() {
66
75
  const _super = Object.create(null, {
67
76
  connect: { get: () => super.connect }
68
77
  });
69
78
  return __awaiter(this, arguments, void 0, function* (args = {}) {
70
- var _a, _b, _c;
71
- const { waitForOpen = true } = args, rest = __rest(args, ["waitForOpen"]);
72
- let connectArgs = rest;
79
+ var _a, _b, _c, _d;
80
+ const { waitForOpen = true, autoReconnect = true } = args, rest = __rest(args, ["waitForOpen", "autoReconnect"]);
81
+ const reconnectAttempts = (_a = rest.reconnectAttempts) !== null && _a !== void 0 ? _a : DEFAULT_RECONNECT_ATTEMPTS;
82
+ let connectArgs = Object.assign(Object.assign({}, rest), { reconnectAttempts, headers: Object.assign(Object.assign({}, SDK_HEADERS), rest.headers) });
73
83
  if (this._getPaymentCredentials) {
74
- const wsUrl = core.url.join((_a = (yield core.Supplier.get(this._options.baseUrl))) !== null && _a !== void 0 ? _a : ((_b = (yield core.Supplier.get(this._options.environment))) !== null && _b !== void 0 ? _b : environments.AgentMailEnvironment.Prod).websockets, "/v0");
84
+ const wsUrl = core.url.join((_b = (yield core.Supplier.get(this._options.baseUrl))) !== null && _b !== void 0 ? _b : ((_c = (yield core.Supplier.get(this._options.environment))) !== null && _c !== void 0 ? _c : environments.AgentMailEnvironment.Prod)
85
+ .websockets, "/v0");
75
86
  const credentials = yield this._getPaymentCredentials(wsUrl);
76
- connectArgs = Object.assign(Object.assign({}, rest), { queryParams: Object.assign(Object.assign({}, credentials), rest.queryParams) });
87
+ connectArgs = Object.assign(Object.assign({}, connectArgs), { queryParams: Object.assign(Object.assign({}, credentials), rest.queryParams) });
77
88
  }
78
89
  else if (!rest.apiKey) {
79
- const apiKey = (_c = (yield core.Supplier.get(this._options.apiKey))) !== null && _c !== void 0 ? _c : process.env.AGENTMAIL_API_KEY;
80
- connectArgs = Object.assign(Object.assign({}, rest), { apiKey });
90
+ const apiKey = (_d = (yield core.Supplier.get(this._options.apiKey))) !== null && _d !== void 0 ? _d : process.env.AGENTMAIL_API_KEY;
91
+ connectArgs = Object.assign(Object.assign({}, connectArgs), { apiKey });
81
92
  }
82
93
  const socket = yield _super.connect.call(this, connectArgs);
83
94
  if (waitForOpen)
84
95
  yield socket.waitForOpen();
96
+ if (autoReconnect && reconnectAttempts > 0) {
97
+ (0, reconnect_js_1.enableAutoReconnect)(socket, {
98
+ maxRetries: reconnectAttempts,
99
+ abortSignal: rest.abortSignal,
100
+ policy: this._reconnectPolicy,
101
+ });
102
+ }
85
103
  return socket;
86
104
  });
87
105
  }
@@ -0,0 +1,22 @@
1
+ import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.js";
2
+ export interface ReconnectPolicy {
3
+ baseDelayMs: number;
4
+ maxDelayMs: number;
5
+ /** How long a connection must stay open before the backoff starts over. */
6
+ stableAfterMs: number;
7
+ }
8
+ export declare const DEFAULT_RECONNECT_POLICY: ReconnectPolicy;
9
+ /** Exponential backoff with full jitter: uniform in [0, min(maxDelayMs, baseDelayMs * 2^attempt)). */
10
+ export declare function backoffDelay(attempt: number, policy: ReconnectPolicy, random?: () => number): number;
11
+ export interface AutoReconnectOptions {
12
+ /** The generated socket's own retry budget (`reconnectAttempts`). */
13
+ maxRetries: number;
14
+ abortSignal?: AbortSignal;
15
+ policy: ReconnectPolicy;
16
+ }
17
+ /**
18
+ * Keeps `socket` connected and subscribed: re-sends every subscription on each reconnect, takes over
19
+ * reconnecting whenever the generated socket gives up, and stops on close(), abort, or a terminal
20
+ * handshake refusal.
21
+ */
22
+ export declare function enableAutoReconnect(socket: WebsocketsSocket, options: AutoReconnectOptions): void;
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __rest = (this && this.__rest) || function (s, e) {
36
+ var t = {};
37
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
38
+ t[p] = s[p];
39
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
40
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
41
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
42
+ t[p[i]] = s[p[i]];
43
+ }
44
+ return t;
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.DEFAULT_RECONNECT_POLICY = void 0;
48
+ exports.backoffDelay = backoffDelay;
49
+ exports.enableAutoReconnect = enableAutoReconnect;
50
+ const index_js_1 = require("../core/index.js");
51
+ const Events = __importStar(require("../core/websocket/events.js"));
52
+ exports.DEFAULT_RECONNECT_POLICY = { baseDelayMs: 1000, maxDelayMs: 60000, stableAfterMs: 5000 };
53
+ /** Exponential backoff with full jitter: uniform in [0, min(maxDelayMs, baseDelayMs * 2^attempt)). */
54
+ function backoffDelay(attempt, policy, random = Math.random) {
55
+ return random() * Math.min(policy.maxDelayMs, policy.baseDelayMs * Math.pow(2, attempt));
56
+ }
57
+ const { OPEN, CLOSING, CLOSED } = index_js_1.ReconnectingWebSocket.ReadyState;
58
+ // ws reports a refused handshake as "Unexpected server response: <status>". Browsers expose no status,
59
+ // so there every refusal looks transient and is retried on the capped backoff.
60
+ const HANDSHAKE_STATUS = /^Unexpected server response: (\d{3})\b/;
61
+ // Refusals that retrying with the same credentials cannot fix. 402 comes from the x402/MPP proxies,
62
+ // whose payment credential is minted once per connect() call and reused on reconnects. The server
63
+ // also answers 403 when the organization is at its connection cap, which a retry could outlast.
64
+ const TERMINAL_FAILURES = new Map([
65
+ ["401", "WebSocket authentication failed (401): the API key is missing, invalid, or was deleted."],
66
+ [
67
+ "402",
68
+ "WebSocket payment required (402): the payment credential minted by connect() is no longer accepted; open a new socket with client.websockets.connect() to pay again.",
69
+ ],
70
+ [
71
+ "403",
72
+ "WebSocket authentication failed (403): the API key is invalid or was deleted, or the organization is at its WebSocket connection limit.",
73
+ ],
74
+ ]);
75
+ /**
76
+ * Keeps `socket` connected and subscribed: re-sends every subscription on each reconnect, takes over
77
+ * reconnecting whenever the generated socket gives up, and stops on close(), abort, or a terminal
78
+ * handshake refusal.
79
+ */
80
+ function enableAutoReconnect(socket, options) {
81
+ const { maxRetries, abortSignal, policy } = options;
82
+ const rws = socket.socket;
83
+ const sendSubscribe = socket.sendSubscribe.bind(socket);
84
+ const closeSocket = rws.close.bind(rws);
85
+ const reconnectSocket = rws.reconnect.bind(rws);
86
+ // Keyed by canonical JSON, in the order last sent. The server keeps one subscription per target and
87
+ // the last filter sent for it, so a new message takes its targets out of every earlier one: a
88
+ // replay can never put back a filter the caller has since changed.
89
+ const subscriptions = new Map();
90
+ // Keys already sent on the current connection, e.g. by the user's own open handler.
91
+ const sentOnConnection = new Set();
92
+ let opened = rws.readyState === OPEN;
93
+ let openedAt = opened ? Date.now() : undefined;
94
+ let stopped = false;
95
+ let attempt = 0;
96
+ let timer;
97
+ let emitting = false;
98
+ const isStopped = () => stopped || (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) === true;
99
+ const cancelTimer = () => {
100
+ clearTimeout(timer);
101
+ timer = undefined;
102
+ };
103
+ const stop = () => {
104
+ stopped = true;
105
+ cancelTimer();
106
+ };
107
+ const emitError = (message) => {
108
+ var _a;
109
+ const event = new Events.ErrorEvent(new Error(message), rws);
110
+ emitting = true;
111
+ try {
112
+ (_a = rws.onerror) === null || _a === void 0 ? void 0 : _a.call(rws, event);
113
+ rws.dispatchEvent(event);
114
+ }
115
+ finally {
116
+ emitting = false;
117
+ }
118
+ };
119
+ const scheduleReconnect = () => {
120
+ cancelTimer();
121
+ timer = setTimeout(() => {
122
+ timer = undefined;
123
+ if (isStopped())
124
+ return;
125
+ // reconnect() on a socket that is not CLOSED closes it with 1000 and then never connects.
126
+ if (rws.readyState === CLOSED)
127
+ reconnectSocket();
128
+ else if (rws.readyState === CLOSING)
129
+ scheduleReconnect();
130
+ }, backoffDelay(attempt++, policy));
131
+ };
132
+ rws.addEventListener("open", () => {
133
+ cancelTimer();
134
+ openedAt = Date.now();
135
+ if (!opened) {
136
+ opened = true;
137
+ return;
138
+ }
139
+ for (const [key, message] of subscriptions) {
140
+ if (isStopped() || rws.readyState !== OPEN)
141
+ return;
142
+ if (sentOnConnection.has(key))
143
+ continue;
144
+ try {
145
+ sendSubscribe(message);
146
+ sentOnConnection.add(key);
147
+ }
148
+ catch (error) {
149
+ emitError(`Failed to re-subscribe after reconnecting: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ }
152
+ });
153
+ rws.addEventListener("close", (event) => {
154
+ sentOnConnection.clear();
155
+ if (openedAt !== undefined && Date.now() - openedAt >= policy.stableAfterMs)
156
+ attempt = 0;
157
+ openedAt = undefined;
158
+ if (isStopped())
159
+ return;
160
+ if (event.code !== 1000) {
161
+ // The generated socket retries these closes itself, up to maxRetries times.
162
+ if (rws.retryCount < maxRetries)
163
+ return;
164
+ // It has just scheduled its last retry. If that one fails too, it keeps its connect lock
165
+ // and ignores reconnect() for good, so cancel it and take over.
166
+ closeSocket();
167
+ }
168
+ scheduleReconnect();
169
+ });
170
+ rws.addEventListener("error", (event) => {
171
+ var _a;
172
+ if (emitting || isStopped())
173
+ return;
174
+ const status = (_a = HANDSHAKE_STATUS.exec(event.message)) === null || _a === void 0 ? void 0 : _a[1];
175
+ const failure = status === undefined ? undefined : TERMINAL_FAILURES.get(status);
176
+ if (failure === undefined)
177
+ return;
178
+ stop();
179
+ emitError(`${failure} Not reconnecting.`);
180
+ });
181
+ abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", stop, { once: true });
182
+ // Patched on the generated socket so WebsocketsSocket.close()/connect() and direct
183
+ // socket.socket.close()/reconnect() calls all count as the user's intent.
184
+ rws.close = (code, reason) => {
185
+ stop();
186
+ subscriptions.clear();
187
+ closeSocket(code, reason);
188
+ };
189
+ rws.reconnect = (code, reason) => {
190
+ stopped = false;
191
+ attempt = 0;
192
+ cancelTimer();
193
+ reconnectSocket(code, reason);
194
+ };
195
+ socket.sendSubscribe = (message) => {
196
+ sendSubscribe(message);
197
+ const recorded = JSON.parse(JSON.stringify(message));
198
+ const covered = new Set(targetsOf(recorded));
199
+ const kept = [...subscriptions].flatMap(([key, cached]) => {
200
+ const remaining = withoutTargets(cached, covered);
201
+ if (remaining === undefined)
202
+ return [];
203
+ return [[remaining === cached ? key : subscriptionKey(remaining), remaining]];
204
+ });
205
+ const key = subscriptionKey(recorded);
206
+ subscriptions.clear();
207
+ for (const [keptKey, keptMessage] of kept)
208
+ if (keptKey !== key)
209
+ subscriptions.set(keptKey, keptMessage);
210
+ subscriptions.set(key, recorded);
211
+ sentOnConnection.add(key);
212
+ };
213
+ }
214
+ // What a subscribe message sets on the server: one subscription per inbox and per pod it names, or the
215
+ // credential's own scope when it names neither. That default resolves server-side to an inbox, pod or
216
+ // organization the SDK cannot see, so it is tracked as its own target.
217
+ function targetsOf(message) {
218
+ var _a, _b;
219
+ if (message.inboxIds === undefined && message.podIds === undefined)
220
+ return ["default"];
221
+ return [...((_a = message.inboxIds) !== null && _a !== void 0 ? _a : []).map((id) => `inbox:${id}`), ...((_b = message.podIds) !== null && _b !== void 0 ? _b : []).map((id) => `pod:${id}`)];
222
+ }
223
+ // `message` without the covered targets; undefined once none of its targets is left.
224
+ function withoutTargets(message, covered) {
225
+ const targets = targetsOf(message);
226
+ if (!targets.some((target) => covered.has(target)))
227
+ return message;
228
+ if (targets.every((target) => covered.has(target)))
229
+ return undefined;
230
+ const { inboxIds, podIds } = message, rest = __rest(message, ["inboxIds", "podIds"]);
231
+ const keptInboxIds = inboxIds === null || inboxIds === void 0 ? void 0 : inboxIds.filter((id) => !covered.has(`inbox:${id}`));
232
+ const keptPodIds = podIds === null || podIds === void 0 ? void 0 : podIds.filter((id) => !covered.has(`pod:${id}`));
233
+ return Object.assign(Object.assign(Object.assign({}, rest), ((keptInboxIds === null || keptInboxIds === void 0 ? void 0 : keptInboxIds.length) ? { inboxIds: keptInboxIds } : {})), ((keptPodIds === null || keptPodIds === void 0 ? void 0 : keptPodIds.length) ? { podIds: keptPodIds } : {}));
234
+ }
235
+ // Canonical JSON (sorted object keys), so the same subscription written in a different key order dedupes.
236
+ function subscriptionKey(message) {
237
+ return JSON.stringify(message, (_key, value) => {
238
+ if (value === null || typeof value !== "object" || Array.isArray(value))
239
+ return value;
240
+ const sorted = {};
241
+ for (const key of Object.keys(value).sort()) {
242
+ sorted[key] = value[key];
243
+ }
244
+ return sorted;
245
+ });
246
+ }
@@ -6,8 +6,8 @@ export function normalizeClientOptions(options) {
6
6
  const headers = mergeHeaders({
7
7
  "X-Fern-Language": "JavaScript",
8
8
  "X-Fern-SDK-Name": "agentmail",
9
- "X-Fern-SDK-Version": "0.5.33",
10
- "User-Agent": "agentmail/0.5.33",
9
+ "X-Fern-SDK-Version": "0.5.35",
10
+ "User-Agent": "agentmail/0.5.35",
11
11
  "X-Fern-Runtime": core.RUNTIME.type,
12
12
  "X-Fern-Runtime-Version": core.RUNTIME.version,
13
13
  }, options === null || options === void 0 ? void 0 : options.headers);
@@ -64,8 +64,8 @@ export declare class ApiKeysClient {
64
64
  private __create;
65
65
  /**
66
66
  * Renames a credential or changes its permissions. Public keys also resolve
67
- * by `client_id`; a sign-in key accepts only `provider_connect` and
68
- * `provider_share_owner`.
67
+ * by `client_id`; a sign-in key accepts only `app_connect` and
68
+ * `app_share_owner`.
69
69
  *
70
70
  * @param {AgentMail.ApiKeyId} api_key_id
71
71
  * @param {AgentMail.UpdateApiKeyRequest} request
@@ -242,8 +242,8 @@ export class ApiKeysClient {
242
242
  }
243
243
  /**
244
244
  * Renames a credential or changes its permissions. Public keys also resolve
245
- * by `client_id`; a sign-in key accepts only `provider_connect` and
246
- * `provider_share_owner`.
245
+ * by `client_id`; a sign-in key accepts only `app_connect` and
246
+ * `app_share_owner`.
247
247
  *
248
248
  * @param {AgentMail.ApiKeyId} api_key_id
249
249
  * @param {AgentMail.UpdateApiKeyRequest} request
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Accept the app's disclosure on the agent's behalf, skipping the
3
- * first-use disclosure page. Requires `provider_share_owner` when owner
3
+ * first-use disclosure page. Requires `app_share_owner` when owner
4
4
  * scopes are involved.
5
5
  */
6
6
  export type AcceptDisclosure = boolean;
@@ -71,17 +71,17 @@ export interface ApiKeyPermissions {
71
71
  /** Delete API keys. */
72
72
  apiKeyDelete?: boolean;
73
73
  /**
74
- * Sign in to providers as an inbox: connect a provider, authorize an inbox, and mint the
74
+ * Sign in to apps as an inbox: connect an app, authorize an inbox, and mint the
75
75
  * sign-in keys. Omitted on a new bearer key means false, whatever else the key holds.
76
76
  */
77
- providerConnect?: boolean;
77
+ appConnect?: boolean;
78
78
  /**
79
- * Share the organization owner's name and email with providers at sign-in. One permission
79
+ * Share the organization owner's name and email with apps at sign-in. One permission
80
80
  * for both values.
81
81
  */
82
- providerShareOwner?: boolean;
82
+ appShareOwner?: boolean;
83
83
  /**
84
- * Update accounts: disable or re-enable an inbox's sign-in at a provider. Reading accounts
84
+ * Update accounts: disable or re-enable an inbox's sign-in at an app. Reading accounts
85
85
  * needs only `inbox_read`.
86
86
  */
87
87
  accountUpdate?: boolean;
@@ -3,8 +3,8 @@ import type * as AgentMail from "../../../index.mjs";
3
3
  * An AgentID sign-in credential, scoped like a bearer key; `api_key_id` is
4
4
  * the JWS `kid`. A sign-in key carries `status`, gains `public_key` once
5
5
  * the client has proved it, expires 30 days after
6
- * activation, and carries exactly `provider_connect` and
7
- * `provider_share_owner`, snapshotted from the bearer key that created it
6
+ * activation, and carries exactly `app_connect` and
7
+ * `app_share_owner`, snapshotted from the bearer key that created it
8
8
  * and enforced from the key itself.
9
9
  */
10
10
  export interface PublicKeyCredential {
@@ -38,8 +38,8 @@ export declare namespace ApiKeyPermissions {
38
38
  api_key_create?: boolean | null;
39
39
  api_key_update?: boolean | null;
40
40
  api_key_delete?: boolean | null;
41
- provider_connect?: boolean | null;
42
- provider_share_owner?: boolean | null;
41
+ app_connect?: boolean | null;
42
+ app_share_owner?: boolean | null;
43
43
  account_update?: boolean | null;
44
44
  pod_read?: boolean | null;
45
45
  pod_create?: boolean | null;
@@ -35,8 +35,8 @@ export const ApiKeyPermissions = core.serialization.object({
35
35
  apiKeyCreate: core.serialization.property("api_key_create", core.serialization.boolean().optional()),
36
36
  apiKeyUpdate: core.serialization.property("api_key_update", core.serialization.boolean().optional()),
37
37
  apiKeyDelete: core.serialization.property("api_key_delete", core.serialization.boolean().optional()),
38
- providerConnect: core.serialization.property("provider_connect", core.serialization.boolean().optional()),
39
- providerShareOwner: core.serialization.property("provider_share_owner", core.serialization.boolean().optional()),
38
+ appConnect: core.serialization.property("app_connect", core.serialization.boolean().optional()),
39
+ appShareOwner: core.serialization.property("app_share_owner", core.serialization.boolean().optional()),
40
40
  accountUpdate: core.serialization.property("account_update", core.serialization.boolean().optional()),
41
41
  podRead: core.serialization.property("pod_read", core.serialization.boolean().optional()),
42
42
  podCreate: core.serialization.property("pod_create", core.serialization.boolean().optional()),
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.5.33";
1
+ export declare const SDK_VERSION = "0.5.35";
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.5.33";
1
+ export const SDK_VERSION = "0.5.35";
@@ -1,10 +1,24 @@
1
1
  import { WebsocketsClient as FernWebsocketsClient } from "../api/resources/websockets/client/Client.mjs";
2
2
  import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.mjs";
3
+ import { type ReconnectPolicy } from "./reconnect.mjs";
3
4
  export type GetPaymentCredentials = (wsUrl: string) => Promise<Record<string, string>>;
4
5
  export declare class WebsocketsClient extends FernWebsocketsClient {
5
6
  private readonly _getPaymentCredentials;
6
- constructor(options: FernWebsocketsClient.Options, getPaymentCredentials?: GetPaymentCredentials);
7
+ private readonly _reconnectPolicy;
8
+ constructor(options: FernWebsocketsClient.Options, getPaymentCredentials?: GetPaymentCredentials, reconnectPolicy?: ReconnectPolicy);
7
9
  connect(args?: FernWebsocketsClient.ConnectArgs & {
10
+ /** Resolve only once the connection is open. Defaults to true. */
8
11
  waitForOpen?: boolean;
12
+ /**
13
+ * Keep the socket connected and subscribed. Whenever the connection drops, reconnect with
14
+ * exponential backoff (full jitter, 1 s doubling up to 60 s) and re-send every subscription
15
+ * sent through `sendSubscribe` on the new connection; `reconnectAttempts` then only bounds
16
+ * the generated socket's own quick retries, not the total. Stops on `close()`, on
17
+ * `abortSignal`, or when the server refuses the handshake with 401, 402 or 403, which is
18
+ * reported to the `error` handler. x402/MPP sockets reuse the payment credential minted by
19
+ * this call on every reconnect. Defaults to true. `false` (or `reconnectAttempts: 0`) keeps
20
+ * only the generated socket's own limited retries and does not re-subscribe.
21
+ */
22
+ autoReconnect?: boolean;
9
23
  }): Promise<WebsocketsSocket>;
10
24
  }
@@ -21,31 +21,49 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  import { WebsocketsClient as FernWebsocketsClient } from "../api/resources/websockets/client/Client.mjs";
22
22
  import * as core from "../core/index.mjs";
23
23
  import * as environments from "../environments.mjs";
24
+ import { SDK_VERSION } from "../version.mjs";
25
+ import { DEFAULT_RECONNECT_POLICY, enableAutoReconnect } from "./reconnect.mjs";
26
+ // The generated client's default. Always passed explicitly: auto-reconnect has to know the generated
27
+ // socket's exact retry budget to take over before it runs out.
28
+ const DEFAULT_RECONNECT_ATTEMPTS = 30;
29
+ // Lets the server tell SDK releases apart on the handshake (Node only; browsers cannot set handshake
30
+ // headers). Caller-supplied headers win.
31
+ const SDK_HEADERS = { "X-Fern-SDK-Name": "agentmail", "X-Fern-SDK-Version": SDK_VERSION };
24
32
  export class WebsocketsClient extends FernWebsocketsClient {
25
- constructor(options, getPaymentCredentials) {
33
+ constructor(options, getPaymentCredentials, reconnectPolicy = DEFAULT_RECONNECT_POLICY) {
26
34
  super(options);
27
35
  this._getPaymentCredentials = getPaymentCredentials;
36
+ this._reconnectPolicy = reconnectPolicy;
28
37
  }
29
38
  connect() {
30
39
  const _super = Object.create(null, {
31
40
  connect: { get: () => super.connect }
32
41
  });
33
42
  return __awaiter(this, arguments, void 0, function* (args = {}) {
34
- var _a, _b, _c;
35
- const { waitForOpen = true } = args, rest = __rest(args, ["waitForOpen"]);
36
- let connectArgs = rest;
43
+ var _a, _b, _c, _d;
44
+ const { waitForOpen = true, autoReconnect = true } = args, rest = __rest(args, ["waitForOpen", "autoReconnect"]);
45
+ const reconnectAttempts = (_a = rest.reconnectAttempts) !== null && _a !== void 0 ? _a : DEFAULT_RECONNECT_ATTEMPTS;
46
+ let connectArgs = Object.assign(Object.assign({}, rest), { reconnectAttempts, headers: Object.assign(Object.assign({}, SDK_HEADERS), rest.headers) });
37
47
  if (this._getPaymentCredentials) {
38
- const wsUrl = core.url.join((_a = (yield core.Supplier.get(this._options.baseUrl))) !== null && _a !== void 0 ? _a : ((_b = (yield core.Supplier.get(this._options.environment))) !== null && _b !== void 0 ? _b : environments.AgentMailEnvironment.Prod).websockets, "/v0");
48
+ const wsUrl = core.url.join((_b = (yield core.Supplier.get(this._options.baseUrl))) !== null && _b !== void 0 ? _b : ((_c = (yield core.Supplier.get(this._options.environment))) !== null && _c !== void 0 ? _c : environments.AgentMailEnvironment.Prod)
49
+ .websockets, "/v0");
39
50
  const credentials = yield this._getPaymentCredentials(wsUrl);
40
- connectArgs = Object.assign(Object.assign({}, rest), { queryParams: Object.assign(Object.assign({}, credentials), rest.queryParams) });
51
+ connectArgs = Object.assign(Object.assign({}, connectArgs), { queryParams: Object.assign(Object.assign({}, credentials), rest.queryParams) });
41
52
  }
42
53
  else if (!rest.apiKey) {
43
- const apiKey = (_c = (yield core.Supplier.get(this._options.apiKey))) !== null && _c !== void 0 ? _c : process.env.AGENTMAIL_API_KEY;
44
- connectArgs = Object.assign(Object.assign({}, rest), { apiKey });
54
+ const apiKey = (_d = (yield core.Supplier.get(this._options.apiKey))) !== null && _d !== void 0 ? _d : process.env.AGENTMAIL_API_KEY;
55
+ connectArgs = Object.assign(Object.assign({}, connectArgs), { apiKey });
45
56
  }
46
57
  const socket = yield _super.connect.call(this, connectArgs);
47
58
  if (waitForOpen)
48
59
  yield socket.waitForOpen();
60
+ if (autoReconnect && reconnectAttempts > 0) {
61
+ enableAutoReconnect(socket, {
62
+ maxRetries: reconnectAttempts,
63
+ abortSignal: rest.abortSignal,
64
+ policy: this._reconnectPolicy,
65
+ });
66
+ }
49
67
  return socket;
50
68
  });
51
69
  }
@@ -0,0 +1,22 @@
1
+ import type { WebsocketsSocket } from "../api/resources/websockets/client/Socket.mjs";
2
+ export interface ReconnectPolicy {
3
+ baseDelayMs: number;
4
+ maxDelayMs: number;
5
+ /** How long a connection must stay open before the backoff starts over. */
6
+ stableAfterMs: number;
7
+ }
8
+ export declare const DEFAULT_RECONNECT_POLICY: ReconnectPolicy;
9
+ /** Exponential backoff with full jitter: uniform in [0, min(maxDelayMs, baseDelayMs * 2^attempt)). */
10
+ export declare function backoffDelay(attempt: number, policy: ReconnectPolicy, random?: () => number): number;
11
+ export interface AutoReconnectOptions {
12
+ /** The generated socket's own retry budget (`reconnectAttempts`). */
13
+ maxRetries: number;
14
+ abortSignal?: AbortSignal;
15
+ policy: ReconnectPolicy;
16
+ }
17
+ /**
18
+ * Keeps `socket` connected and subscribed: re-sends every subscription on each reconnect, takes over
19
+ * reconnecting whenever the generated socket gives up, and stops on close(), abort, or a terminal
20
+ * handshake refusal.
21
+ */
22
+ export declare function enableAutoReconnect(socket: WebsocketsSocket, options: AutoReconnectOptions): void;
@@ -0,0 +1,208 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { ReconnectingWebSocket } from "../core/index.mjs";
13
+ import * as Events from "../core/websocket/events.mjs";
14
+ export const DEFAULT_RECONNECT_POLICY = { baseDelayMs: 1000, maxDelayMs: 60000, stableAfterMs: 5000 };
15
+ /** Exponential backoff with full jitter: uniform in [0, min(maxDelayMs, baseDelayMs * 2^attempt)). */
16
+ export function backoffDelay(attempt, policy, random = Math.random) {
17
+ return random() * Math.min(policy.maxDelayMs, policy.baseDelayMs * Math.pow(2, attempt));
18
+ }
19
+ const { OPEN, CLOSING, CLOSED } = ReconnectingWebSocket.ReadyState;
20
+ // ws reports a refused handshake as "Unexpected server response: <status>". Browsers expose no status,
21
+ // so there every refusal looks transient and is retried on the capped backoff.
22
+ const HANDSHAKE_STATUS = /^Unexpected server response: (\d{3})\b/;
23
+ // Refusals that retrying with the same credentials cannot fix. 402 comes from the x402/MPP proxies,
24
+ // whose payment credential is minted once per connect() call and reused on reconnects. The server
25
+ // also answers 403 when the organization is at its connection cap, which a retry could outlast.
26
+ const TERMINAL_FAILURES = new Map([
27
+ ["401", "WebSocket authentication failed (401): the API key is missing, invalid, or was deleted."],
28
+ [
29
+ "402",
30
+ "WebSocket payment required (402): the payment credential minted by connect() is no longer accepted; open a new socket with client.websockets.connect() to pay again.",
31
+ ],
32
+ [
33
+ "403",
34
+ "WebSocket authentication failed (403): the API key is invalid or was deleted, or the organization is at its WebSocket connection limit.",
35
+ ],
36
+ ]);
37
+ /**
38
+ * Keeps `socket` connected and subscribed: re-sends every subscription on each reconnect, takes over
39
+ * reconnecting whenever the generated socket gives up, and stops on close(), abort, or a terminal
40
+ * handshake refusal.
41
+ */
42
+ export function enableAutoReconnect(socket, options) {
43
+ const { maxRetries, abortSignal, policy } = options;
44
+ const rws = socket.socket;
45
+ const sendSubscribe = socket.sendSubscribe.bind(socket);
46
+ const closeSocket = rws.close.bind(rws);
47
+ const reconnectSocket = rws.reconnect.bind(rws);
48
+ // Keyed by canonical JSON, in the order last sent. The server keeps one subscription per target and
49
+ // the last filter sent for it, so a new message takes its targets out of every earlier one: a
50
+ // replay can never put back a filter the caller has since changed.
51
+ const subscriptions = new Map();
52
+ // Keys already sent on the current connection, e.g. by the user's own open handler.
53
+ const sentOnConnection = new Set();
54
+ let opened = rws.readyState === OPEN;
55
+ let openedAt = opened ? Date.now() : undefined;
56
+ let stopped = false;
57
+ let attempt = 0;
58
+ let timer;
59
+ let emitting = false;
60
+ const isStopped = () => stopped || (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) === true;
61
+ const cancelTimer = () => {
62
+ clearTimeout(timer);
63
+ timer = undefined;
64
+ };
65
+ const stop = () => {
66
+ stopped = true;
67
+ cancelTimer();
68
+ };
69
+ const emitError = (message) => {
70
+ var _a;
71
+ const event = new Events.ErrorEvent(new Error(message), rws);
72
+ emitting = true;
73
+ try {
74
+ (_a = rws.onerror) === null || _a === void 0 ? void 0 : _a.call(rws, event);
75
+ rws.dispatchEvent(event);
76
+ }
77
+ finally {
78
+ emitting = false;
79
+ }
80
+ };
81
+ const scheduleReconnect = () => {
82
+ cancelTimer();
83
+ timer = setTimeout(() => {
84
+ timer = undefined;
85
+ if (isStopped())
86
+ return;
87
+ // reconnect() on a socket that is not CLOSED closes it with 1000 and then never connects.
88
+ if (rws.readyState === CLOSED)
89
+ reconnectSocket();
90
+ else if (rws.readyState === CLOSING)
91
+ scheduleReconnect();
92
+ }, backoffDelay(attempt++, policy));
93
+ };
94
+ rws.addEventListener("open", () => {
95
+ cancelTimer();
96
+ openedAt = Date.now();
97
+ if (!opened) {
98
+ opened = true;
99
+ return;
100
+ }
101
+ for (const [key, message] of subscriptions) {
102
+ if (isStopped() || rws.readyState !== OPEN)
103
+ return;
104
+ if (sentOnConnection.has(key))
105
+ continue;
106
+ try {
107
+ sendSubscribe(message);
108
+ sentOnConnection.add(key);
109
+ }
110
+ catch (error) {
111
+ emitError(`Failed to re-subscribe after reconnecting: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ }
114
+ });
115
+ rws.addEventListener("close", (event) => {
116
+ sentOnConnection.clear();
117
+ if (openedAt !== undefined && Date.now() - openedAt >= policy.stableAfterMs)
118
+ attempt = 0;
119
+ openedAt = undefined;
120
+ if (isStopped())
121
+ return;
122
+ if (event.code !== 1000) {
123
+ // The generated socket retries these closes itself, up to maxRetries times.
124
+ if (rws.retryCount < maxRetries)
125
+ return;
126
+ // It has just scheduled its last retry. If that one fails too, it keeps its connect lock
127
+ // and ignores reconnect() for good, so cancel it and take over.
128
+ closeSocket();
129
+ }
130
+ scheduleReconnect();
131
+ });
132
+ rws.addEventListener("error", (event) => {
133
+ var _a;
134
+ if (emitting || isStopped())
135
+ return;
136
+ const status = (_a = HANDSHAKE_STATUS.exec(event.message)) === null || _a === void 0 ? void 0 : _a[1];
137
+ const failure = status === undefined ? undefined : TERMINAL_FAILURES.get(status);
138
+ if (failure === undefined)
139
+ return;
140
+ stop();
141
+ emitError(`${failure} Not reconnecting.`);
142
+ });
143
+ abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", stop, { once: true });
144
+ // Patched on the generated socket so WebsocketsSocket.close()/connect() and direct
145
+ // socket.socket.close()/reconnect() calls all count as the user's intent.
146
+ rws.close = (code, reason) => {
147
+ stop();
148
+ subscriptions.clear();
149
+ closeSocket(code, reason);
150
+ };
151
+ rws.reconnect = (code, reason) => {
152
+ stopped = false;
153
+ attempt = 0;
154
+ cancelTimer();
155
+ reconnectSocket(code, reason);
156
+ };
157
+ socket.sendSubscribe = (message) => {
158
+ sendSubscribe(message);
159
+ const recorded = JSON.parse(JSON.stringify(message));
160
+ const covered = new Set(targetsOf(recorded));
161
+ const kept = [...subscriptions].flatMap(([key, cached]) => {
162
+ const remaining = withoutTargets(cached, covered);
163
+ if (remaining === undefined)
164
+ return [];
165
+ return [[remaining === cached ? key : subscriptionKey(remaining), remaining]];
166
+ });
167
+ const key = subscriptionKey(recorded);
168
+ subscriptions.clear();
169
+ for (const [keptKey, keptMessage] of kept)
170
+ if (keptKey !== key)
171
+ subscriptions.set(keptKey, keptMessage);
172
+ subscriptions.set(key, recorded);
173
+ sentOnConnection.add(key);
174
+ };
175
+ }
176
+ // What a subscribe message sets on the server: one subscription per inbox and per pod it names, or the
177
+ // credential's own scope when it names neither. That default resolves server-side to an inbox, pod or
178
+ // organization the SDK cannot see, so it is tracked as its own target.
179
+ function targetsOf(message) {
180
+ var _a, _b;
181
+ if (message.inboxIds === undefined && message.podIds === undefined)
182
+ return ["default"];
183
+ return [...((_a = message.inboxIds) !== null && _a !== void 0 ? _a : []).map((id) => `inbox:${id}`), ...((_b = message.podIds) !== null && _b !== void 0 ? _b : []).map((id) => `pod:${id}`)];
184
+ }
185
+ // `message` without the covered targets; undefined once none of its targets is left.
186
+ function withoutTargets(message, covered) {
187
+ const targets = targetsOf(message);
188
+ if (!targets.some((target) => covered.has(target)))
189
+ return message;
190
+ if (targets.every((target) => covered.has(target)))
191
+ return undefined;
192
+ const { inboxIds, podIds } = message, rest = __rest(message, ["inboxIds", "podIds"]);
193
+ const keptInboxIds = inboxIds === null || inboxIds === void 0 ? void 0 : inboxIds.filter((id) => !covered.has(`inbox:${id}`));
194
+ const keptPodIds = podIds === null || podIds === void 0 ? void 0 : podIds.filter((id) => !covered.has(`pod:${id}`));
195
+ return Object.assign(Object.assign(Object.assign({}, rest), ((keptInboxIds === null || keptInboxIds === void 0 ? void 0 : keptInboxIds.length) ? { inboxIds: keptInboxIds } : {})), ((keptPodIds === null || keptPodIds === void 0 ? void 0 : keptPodIds.length) ? { podIds: keptPodIds } : {}));
196
+ }
197
+ // Canonical JSON (sorted object keys), so the same subscription written in a different key order dedupes.
198
+ function subscriptionKey(message) {
199
+ return JSON.stringify(message, (_key, value) => {
200
+ if (value === null || typeof value !== "object" || Array.isArray(value))
201
+ return value;
202
+ const sorted = {};
203
+ for (const key of Object.keys(value).sort()) {
204
+ sorted[key] = value[key];
205
+ }
206
+ return sorted;
207
+ });
208
+ }
package/dist/llms.txt CHANGED
@@ -36,9 +36,9 @@
36
36
  - [MPP](https://docs.agentmail.to/integrations/mpp.md): AgentMail's MPP integration for machine-to-machine payments via Stripe
37
37
  - [Integrate LiveKit Agents](https://docs.agentmail.to/integrate-livekit-agents.md): A step-by-step guide to integrate with the LiveKit Agents SDK.
38
38
  - [Sim.ai](https://docs.agentmail.to/integrations/sim.md): AgentMail's Sim.ai integration
39
- - [AgentID Sign-In](https://docs.agentmail.to/agentid-sign-in.md): Connect an inbox to a provider, authorize a pending AgentID sign-in, and manage sign-in keys with the AgentMail API and CLI.
39
+ - [AgentID Sign-In](https://docs.agentmail.to/agentid-sign-in.md): Connect an inbox to an app, authorize a pending AgentID sign-in, and manage sign-in keys with the AgentMail API and CLI.
40
40
  - [AgentID Public-Key Authentication](https://docs.agentmail.to/agentid-public-key-authentication.md): Register a public P-256 key, choose its scope and permissions, and manage it through the current AgentMail API Keys endpoints.
41
- - [AgentID in Claude, ChatGPT, and Cursor](https://docs.agentmail.to/agentid-in-assistants.md): Ask Claude, ChatGPT, Cursor, Claude Code, or Codex to create an account for your agent at a provider such as Firecrawl, get its API key, and see where your agent already has accounts, using AgentMail and AgentID.
41
+ - [AgentID in Claude, ChatGPT, and Cursor](https://docs.agentmail.to/agentid-in-assistants.md): Ask Claude, ChatGPT, Cursor, Claude Code, or Codex to create an account for your agent at an app such as Firecrawl, get its API key, and see where your agent already has accounts, using AgentMail and AgentID.
42
42
  - [Guide: Sending & Receiving Email](https://docs.agentmail.to/sending-receiving-email.md): A step-by-step guide to the practical workflow of sending initial emails and handling replies to have a full conversation.
43
43
  - [Reply & Attachment Extraction](https://docs.agentmail.to/reply-extraction.md): AgentMail strips quoted history and boilerplate from every message into extracted_text and extracted_html, and extracts plain text from PDF, Word, Excel, and text attachments. No Talon or reply-parsing library required.
44
44
  - [IMAP & SMTP](https://docs.agentmail.to/imap-smtp.md): Configure IMAP and SMTP to access your AgentMail inboxes using email clients or programmatic access.
@@ -201,11 +201,11 @@
201
201
  - API Reference > API Keys [Create API Key](https://docs.agentmail.to/api-reference/api-keys/create.md)
202
202
  - API Reference > API Keys [Update API Key](https://docs.agentmail.to/api-reference/api-keys/update.md)
203
203
  - API Reference > API Keys [Delete API Key](https://docs.agentmail.to/api-reference/api-keys/delete.md)
204
- - API Reference > Providers [List Providers](https://docs.agentmail.to/api-reference/providers/list.md)
205
- - API Reference > Providers [Search Providers](https://docs.agentmail.to/api-reference/providers/search.md)
206
- - API Reference > Providers [Get Provider](https://docs.agentmail.to/api-reference/providers/get.md)
207
- - API Reference > Providers [List Provider Accounts](https://docs.agentmail.to/api-reference/providers/list-accounts.md)
208
- - API Reference > Providers [Connect Provider](https://docs.agentmail.to/api-reference/providers/connect.md)
204
+ - API Reference > Apps [List Apps](https://docs.agentmail.to/api-reference/apps/list.md)
205
+ - API Reference > Apps [Search Apps](https://docs.agentmail.to/api-reference/apps/search.md)
206
+ - API Reference > Apps [Get App](https://docs.agentmail.to/api-reference/apps/get.md)
207
+ - API Reference > Apps [List App Accounts](https://docs.agentmail.to/api-reference/apps/list-accounts.md)
208
+ - API Reference > Apps [Connect App](https://docs.agentmail.to/api-reference/apps/connect.md)
209
209
  - API Reference > Accounts [List Accounts](https://docs.agentmail.to/api-reference/accounts/list.md)
210
210
  - API Reference > Accounts [Get Account](https://docs.agentmail.to/api-reference/accounts/get.md)
211
211
  - API Reference > Accounts [Update Account](https://docs.agentmail.to/api-reference/accounts/update.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmail",
3
- "version": "0.5.33",
3
+ "version": "0.5.35",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
package/reference.md CHANGED
@@ -1896,8 +1896,8 @@ await client.apiKeys.create({});
1896
1896
  <dd>
1897
1897
 
1898
1898
  Renames a credential or changes its permissions. Public keys also resolve
1899
- by `client_id`; a sign-in key accepts only `provider_connect` and
1900
- `provider_share_owner`.
1899
+ by `client_id`; a sign-in key accepts only `app_connect` and
1900
+ `app_share_owner`.
1901
1901
  </dd>
1902
1902
  </dl>
1903
1903
  </dd>