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.
- package/dist/cjs/BaseClient.js +2 -2
- package/dist/cjs/api/resources/apiKeys/client/Client.d.ts +2 -2
- package/dist/cjs/api/resources/apiKeys/client/Client.js +2 -2
- package/dist/cjs/api/resources/apiKeys/types/AcceptDisclosure.d.ts +1 -1
- package/dist/cjs/api/resources/apiKeys/types/ApiKeyPermissions.d.ts +5 -5
- package/dist/cjs/api/resources/apiKeys/types/PublicKeyCredential.d.ts +2 -2
- package/dist/cjs/serialization/resources/apiKeys/types/ApiKeyPermissions.d.ts +2 -2
- package/dist/cjs/serialization/resources/apiKeys/types/ApiKeyPermissions.js +2 -2
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/wrapper/WebsocketsClient.d.ts +15 -1
- package/dist/cjs/wrapper/WebsocketsClient.js +26 -8
- package/dist/cjs/wrapper/reconnect.d.ts +22 -0
- package/dist/cjs/wrapper/reconnect.js +246 -0
- package/dist/esm/BaseClient.mjs +2 -2
- package/dist/esm/api/resources/apiKeys/client/Client.d.mts +2 -2
- package/dist/esm/api/resources/apiKeys/client/Client.mjs +2 -2
- package/dist/esm/api/resources/apiKeys/types/AcceptDisclosure.d.mts +1 -1
- package/dist/esm/api/resources/apiKeys/types/ApiKeyPermissions.d.mts +5 -5
- package/dist/esm/api/resources/apiKeys/types/PublicKeyCredential.d.mts +2 -2
- package/dist/esm/serialization/resources/apiKeys/types/ApiKeyPermissions.d.mts +2 -2
- package/dist/esm/serialization/resources/apiKeys/types/ApiKeyPermissions.mjs +2 -2
- package/dist/esm/version.d.mts +1 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/esm/wrapper/WebsocketsClient.d.mts +15 -1
- package/dist/esm/wrapper/WebsocketsClient.mjs +26 -8
- package/dist/esm/wrapper/reconnect.d.mts +22 -0
- package/dist/esm/wrapper/reconnect.mjs +208 -0
- package/dist/llms.txt +7 -7
- package/package.json +1 -1
- package/reference.md +2 -2
package/dist/cjs/BaseClient.js
CHANGED
|
@@ -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.
|
|
47
|
-
"User-Agent": "agentmail/0.5.
|
|
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 `
|
|
68
|
-
* `
|
|
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 `
|
|
282
|
-
* `
|
|
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 `
|
|
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
|
|
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
|
-
|
|
77
|
+
appConnect?: boolean;
|
|
78
78
|
/**
|
|
79
|
-
* Share the organization owner's name and email with
|
|
79
|
+
* Share the organization owner's name and email with apps at sign-in. One permission
|
|
80
80
|
* for both values.
|
|
81
81
|
*/
|
|
82
|
-
|
|
82
|
+
appShareOwner?: boolean;
|
|
83
83
|
/**
|
|
84
|
-
* Update accounts: disable or re-enable an inbox's sign-in at
|
|
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 `
|
|
7
|
-
* `
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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()),
|
package/dist/cjs/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.5.
|
|
1
|
+
export declare const SDK_VERSION = "0.5.35";
|
package/dist/cjs/version.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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((
|
|
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({},
|
|
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 = (
|
|
80
|
-
connectArgs = Object.assign(Object.assign({},
|
|
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
|
+
}
|
package/dist/esm/BaseClient.mjs
CHANGED
|
@@ -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.
|
|
10
|
-
"User-Agent": "agentmail/0.5.
|
|
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 `
|
|
68
|
-
* `
|
|
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 `
|
|
246
|
-
* `
|
|
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 `
|
|
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
|
|
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
|
-
|
|
77
|
+
appConnect?: boolean;
|
|
78
78
|
/**
|
|
79
|
-
* Share the organization owner's name and email with
|
|
79
|
+
* Share the organization owner's name and email with apps at sign-in. One permission
|
|
80
80
|
* for both values.
|
|
81
81
|
*/
|
|
82
|
-
|
|
82
|
+
appShareOwner?: boolean;
|
|
83
83
|
/**
|
|
84
|
-
* Update accounts: disable or re-enable an inbox's sign-in at
|
|
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 `
|
|
7
|
-
* `
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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()),
|
package/dist/esm/version.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.5.
|
|
1
|
+
export declare const SDK_VERSION = "0.5.35";
|
package/dist/esm/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.5.
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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({},
|
|
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 = (
|
|
44
|
-
connectArgs = Object.assign(Object.assign({},
|
|
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
|
|
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
|
|
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 >
|
|
205
|
-
- API Reference >
|
|
206
|
-
- API Reference >
|
|
207
|
-
- API Reference >
|
|
208
|
-
- API Reference >
|
|
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
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 `
|
|
1900
|
-
`
|
|
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>
|