@alter-ai/alter-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17,40 +17,70 @@ var AlterSDKError = class extends Error {
17
17
  return this.message;
18
18
  }
19
19
  };
20
- var TokenRetrievalError = class extends AlterSDKError {
20
+ var BackendError = class extends AlterSDKError {
21
21
  constructor(message, details) {
22
22
  super(message, details);
23
- this.name = "TokenRetrievalError";
23
+ this.name = "BackendError";
24
24
  }
25
25
  };
26
- var PolicyViolationError = class extends TokenRetrievalError {
27
- policyError;
28
- constructor(message, policyError, details) {
26
+ var ReAuthRequiredError = class extends BackendError {
27
+ constructor(message, details) {
29
28
  super(message, details);
30
- this.name = "PolicyViolationError";
31
- this.policyError = policyError;
29
+ this.name = "ReAuthRequiredError";
32
30
  }
33
31
  };
34
- var ConnectionNotFoundError = class extends TokenRetrievalError {
32
+ var ConnectionExpiredError = class extends ReAuthRequiredError {
35
33
  constructor(message, details) {
36
34
  super(message, details);
37
- this.name = "ConnectionNotFoundError";
35
+ this.name = "ConnectionExpiredError";
38
36
  }
39
37
  };
40
- var TokenExpiredError = class extends TokenRetrievalError {
38
+ var ConnectionRevokedError = class extends ReAuthRequiredError {
41
39
  connectionId;
42
40
  constructor(message, connectionId, details) {
43
41
  super(message, details);
44
- this.name = "TokenExpiredError";
42
+ this.name = "ConnectionRevokedError";
45
43
  this.connectionId = connectionId;
46
44
  }
47
45
  };
46
+ var ConnectionDeletedError = class extends ReAuthRequiredError {
47
+ constructor(message, details) {
48
+ super(message, details);
49
+ this.name = "ConnectionDeletedError";
50
+ }
51
+ };
52
+ var ConnectionNotFoundError = class extends BackendError {
53
+ constructor(message, details) {
54
+ super(message, details);
55
+ this.name = "ConnectionNotFoundError";
56
+ }
57
+ };
58
+ var PolicyViolationError = class extends BackendError {
59
+ policyError;
60
+ constructor(message, policyError, details) {
61
+ super(message, details);
62
+ this.name = "PolicyViolationError";
63
+ this.policyError = policyError;
64
+ }
65
+ };
48
66
  var ConnectFlowError = class extends AlterSDKError {
49
67
  constructor(message, details) {
50
68
  super(message, details);
51
69
  this.name = "ConnectFlowError";
52
70
  }
53
71
  };
72
+ var ConnectDeniedError = class extends ConnectFlowError {
73
+ constructor(message, details) {
74
+ super(message, details);
75
+ this.name = "ConnectDeniedError";
76
+ }
77
+ };
78
+ var ConnectConfigError = class extends ConnectFlowError {
79
+ constructor(message, details) {
80
+ super(message, details);
81
+ this.name = "ConnectConfigError";
82
+ }
83
+ };
54
84
  var ConnectTimeoutError = class extends ConnectFlowError {
55
85
  constructor(message, details) {
56
86
  super(message, details);
@@ -67,6 +97,16 @@ var ProviderAPIError = class extends AlterSDKError {
67
97
  this.responseBody = responseBody;
68
98
  }
69
99
  };
100
+ var ScopeReauthRequiredError = class extends ProviderAPIError {
101
+ connectionId;
102
+ providerId;
103
+ constructor(message, connectionId, providerId, statusCode, responseBody, details) {
104
+ super(message, statusCode, responseBody, details);
105
+ this.name = "ScopeReauthRequiredError";
106
+ this.connectionId = connectionId;
107
+ this.providerId = providerId;
108
+ }
109
+ };
70
110
  var NetworkError = class extends AlterSDKError {
71
111
  constructor(message, details) {
72
112
  super(message, details);
@@ -106,6 +146,8 @@ var TokenResponse = class _TokenResponse {
106
146
  injectionFormat;
107
147
  /** Extra credentials for multi-part auth (e.g. AWS SigV4 secret_key, region) */
108
148
  additionalCredentials;
149
+ /** Additional injection rules for multi-header or query param auth */
150
+ additionalInjections;
109
151
  constructor(data) {
110
152
  this.tokenType = data.token_type ?? "Bearer";
111
153
  this.expiresIn = data.expires_in ?? null;
@@ -121,6 +163,7 @@ var TokenResponse = class _TokenResponse {
121
163
  } else {
122
164
  this.additionalCredentials = null;
123
165
  }
166
+ this.additionalInjections = data.additional_injections ?? null;
124
167
  Object.freeze(this);
125
168
  }
126
169
  /**
@@ -189,6 +232,7 @@ var ConnectionInfo = class {
189
232
  accountIdentifier;
190
233
  accountDisplayName;
191
234
  status;
235
+ scopeMismatch;
192
236
  expiresAt;
193
237
  createdAt;
194
238
  lastUsedAt;
@@ -199,6 +243,7 @@ var ConnectionInfo = class {
199
243
  this.accountIdentifier = data.account_identifier ?? null;
200
244
  this.accountDisplayName = data.account_display_name ?? null;
201
245
  this.status = data.status;
246
+ this.scopeMismatch = data.scope_mismatch ?? false;
202
247
  this.expiresAt = data.expires_at ?? null;
203
248
  this.createdAt = data.created_at;
204
249
  this.lastUsedAt = data.last_used_at ?? null;
@@ -212,6 +257,7 @@ var ConnectionInfo = class {
212
257
  account_identifier: this.accountIdentifier,
213
258
  account_display_name: this.accountDisplayName,
214
259
  status: this.status,
260
+ scope_mismatch: this.scopeMismatch,
215
261
  expires_at: this.expiresAt,
216
262
  created_at: this.createdAt,
217
263
  last_used_at: this.lastUsedAt
@@ -265,11 +311,13 @@ var ConnectResult = class {
265
311
  providerId;
266
312
  accountIdentifier;
267
313
  scopes;
314
+ connectionPolicy;
268
315
  constructor(data) {
269
316
  this.connectionId = data.connection_id;
270
317
  this.providerId = data.provider_id;
271
318
  this.accountIdentifier = data.account_identifier ?? null;
272
319
  this.scopes = data.scopes ?? [];
320
+ this.connectionPolicy = data.connection_policy ?? null;
273
321
  Object.freeze(this);
274
322
  }
275
323
  toJSON() {
@@ -277,7 +325,8 @@ var ConnectResult = class {
277
325
  connection_id: this.connectionId,
278
326
  provider_id: this.providerId,
279
327
  account_identifier: this.accountIdentifier,
280
- scopes: this.scopes
328
+ scopes: this.scopes,
329
+ connection_policy: this.connectionPolicy
281
330
  };
282
331
  }
283
332
  toString() {
@@ -543,10 +592,11 @@ function _extractAdditionalCredentials(token) {
543
592
  return _additionalCredsStore.get(token);
544
593
  }
545
594
  var _fetch;
546
- var SDK_VERSION = "0.4.0";
595
+ var SDK_VERSION = "0.5.0";
547
596
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
548
597
  var HTTP_FORBIDDEN = 403;
549
598
  var HTTP_NOT_FOUND = 404;
599
+ var HTTP_GONE = 410;
550
600
  var HTTP_BAD_REQUEST = 400;
551
601
  var HTTP_UNAUTHORIZED = 401;
552
602
  var HTTP_BAD_GATEWAY = 502;
@@ -602,6 +652,9 @@ var HttpClient = class {
602
652
  };
603
653
  if (options?.body !== void 0) {
604
654
  init.body = options.body;
655
+ if (!mergedHeaders["Content-Type"]) {
656
+ mergedHeaders["Content-Type"] = "application/json";
657
+ }
605
658
  } else if (options?.json !== void 0) {
606
659
  init.body = JSON.stringify(options.json);
607
660
  mergedHeaders["Content-Type"] = "application/json";
@@ -638,6 +691,8 @@ var AlterVault = class _AlterVault {
638
691
  #closed = false;
639
692
  /** Pending audit log promises (fire-and-forget) */
640
693
  #auditPromises = /* @__PURE__ */ new Set();
694
+ /** JWT identity resolution: callable that returns user token */
695
+ #userTokenGetter = null;
641
696
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
642
697
  // Public readonly properties (frozen by Object.freeze)
643
698
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -673,6 +728,7 @@ var AlterVault = class _AlterVault {
673
728
  this.#actorName = options.actorName;
674
729
  this.#actorVersion = options.actorVersion;
675
730
  this.#clientType = options.clientType;
731
+ this.#userTokenGetter = options.userTokenGetter ?? null;
676
732
  this.#framework = options.framework;
677
733
  if (!_fetch) {
678
734
  _fetch = globalThis.fetch;
@@ -847,6 +903,24 @@ ${contentHash}`;
847
903
  }
848
904
  return false;
849
905
  }
906
+ /**
907
+ * Resolve a value_source to the actual value for injection.
908
+ *
909
+ * @param valueSource - "token" or "additional_credentials.<field>"
910
+ * @param accessToken - The main credential value
911
+ * @param additionalCreds - Additional credentials from vault
912
+ * @returns The resolved value, or null if not available
913
+ */
914
+ static #resolveInjectionValue(valueSource, accessToken, additionalCreds) {
915
+ if (valueSource === "token") {
916
+ return accessToken;
917
+ }
918
+ if (valueSource.startsWith("additional_credentials.")) {
919
+ const field = valueSource.slice("additional_credentials.".length);
920
+ return additionalCreds?.[field] ?? null;
921
+ }
922
+ return null;
923
+ }
850
924
  static async #safeParseJson(response) {
851
925
  try {
852
926
  return await response.clone().json();
@@ -868,12 +942,25 @@ ${contentHash}`;
868
942
  }
869
943
  if (response.status === HTTP_FORBIDDEN) {
870
944
  const errorData = await _AlterVault.#safeParseJson(response);
945
+ if (errorData.error === "connection_expired") {
946
+ throw new ConnectionExpiredError(
947
+ errorData.message ?? "Connection expired per TTL policy",
948
+ errorData.details
949
+ );
950
+ }
871
951
  throw new PolicyViolationError(
872
952
  errorData.message ?? "Access denied by policy",
873
953
  errorData.error,
874
954
  errorData.details
875
955
  );
876
956
  }
957
+ if (response.status === HTTP_GONE) {
958
+ const errorData = await _AlterVault.#safeParseJson(response);
959
+ throw new ConnectionDeletedError(
960
+ errorData.message ?? "Connection has been deleted. A new connection_id will be issued on re-authorization.",
961
+ errorData
962
+ );
963
+ }
877
964
  if (response.status === HTTP_NOT_FOUND) {
878
965
  const errorData = await _AlterVault.#safeParseJson(response);
879
966
  throw new ConnectionNotFoundError(
@@ -883,35 +970,35 @@ ${contentHash}`;
883
970
  }
884
971
  if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
885
972
  const errorData = await _AlterVault.#safeParseJson(response);
886
- if (JSON.stringify(errorData).toLowerCase().includes("token_expired")) {
887
- throw new TokenExpiredError(
888
- errorData.message ?? "Token expired and refresh failed",
973
+ if (errorData.error === "connection_revoked") {
974
+ throw new ConnectionRevokedError(
975
+ errorData.message ?? "Connection has been revoked. User must re-authorize.",
889
976
  errorData.connection_id,
890
977
  errorData
891
978
  );
892
979
  }
893
- throw new TokenRetrievalError(
980
+ throw new BackendError(
894
981
  errorData.message ?? `Backend error ${response.status}`,
895
982
  errorData
896
983
  );
897
984
  }
898
985
  if (response.status === HTTP_UNAUTHORIZED) {
899
986
  const errorData = await _AlterVault.#safeParseJson(response);
900
- throw new TokenRetrievalError(
987
+ throw new BackendError(
901
988
  errorData.message ?? "Unauthorized \u2014 check your API key",
902
989
  errorData
903
990
  );
904
991
  }
905
992
  if (response.status === HTTP_INTERNAL_SERVER_ERROR || response.status === HTTP_SERVICE_UNAVAILABLE) {
906
993
  const errorData = await _AlterVault.#safeParseJson(response);
907
- throw new TokenRetrievalError(
994
+ throw new BackendError(
908
995
  errorData.message ?? `Backend unavailable (HTTP ${response.status})`,
909
996
  errorData
910
997
  );
911
998
  }
912
999
  if (response.status >= HTTP_CLIENT_ERROR_START) {
913
1000
  const errorData = await _AlterVault.#safeParseJson(response);
914
- throw new TokenRetrievalError(
1001
+ throw new BackendError(
915
1002
  errorData.message ?? `Unexpected backend error (HTTP ${response.status})`,
916
1003
  errorData
917
1004
  );
@@ -923,24 +1010,52 @@ ${contentHash}`;
923
1010
  * This is a private method. Tokens are NEVER exposed to developers.
924
1011
  * Use request() instead, which handles tokens internally.
925
1012
  */
926
- async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId) {
1013
+ async #getUserToken() {
1014
+ if (!this.#userTokenGetter) {
1015
+ throw new AlterSDKError(
1016
+ "userTokenGetter is required for provider-based resolution. Pass userTokenGetter to AlterVault constructor."
1017
+ );
1018
+ }
1019
+ const result = this.#userTokenGetter();
1020
+ const token = result instanceof Promise ? await result : result;
1021
+ if (!token || typeof token !== "string") {
1022
+ throw new AlterSDKError("userTokenGetter must return a non-empty string");
1023
+ }
1024
+ return token;
1025
+ }
1026
+ async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId, provider, account) {
927
1027
  const actorHeaders = this.#getActorRequestHeaders(
928
1028
  runId,
929
1029
  threadId,
930
1030
  toolCallId
931
1031
  );
932
1032
  let response;
933
- const tokenBody = {
934
- connection_id: connectionId,
935
- reason: reason ?? null,
936
- request: requestMetadata ?? null
937
- };
1033
+ let tokenBody;
1034
+ if (provider) {
1035
+ const userToken = await this.#getUserToken();
1036
+ tokenBody = {
1037
+ provider_id: provider,
1038
+ user_token: userToken,
1039
+ reason: reason ?? null,
1040
+ request: requestMetadata ?? null
1041
+ };
1042
+ if (account) {
1043
+ tokenBody.account = account;
1044
+ }
1045
+ } else {
1046
+ tokenBody = {
1047
+ connection_id: connectionId,
1048
+ reason: reason ?? null,
1049
+ request: requestMetadata ?? null
1050
+ };
1051
+ }
938
1052
  const tokenPath = "/sdk/token";
939
- const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, JSON.stringify(tokenBody));
1053
+ const tokenBodyStr = JSON.stringify(tokenBody);
1054
+ const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, tokenBodyStr);
940
1055
  try {
941
1056
  response = await this.#alterClient.post(tokenPath, {
942
- json: tokenBody,
943
- headers: { ...actorHeaders, ...hmacHeaders }
1057
+ body: tokenBodyStr,
1058
+ headers: { ...actorHeaders, ...hmacHeaders, "Content-Type": "application/json" }
944
1059
  });
945
1060
  } catch (error) {
946
1061
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -955,7 +1070,7 @@ ${contentHash}`;
955
1070
  { base_url: this.baseUrl }
956
1071
  );
957
1072
  }
958
- throw new TokenRetrievalError(
1073
+ throw new BackendError(
959
1074
  `Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
960
1075
  { connection_id: connectionId, error: String(error) }
961
1076
  );
@@ -964,15 +1079,16 @@ ${contentHash}`;
964
1079
  await this.#handleErrorResponse(response);
965
1080
  const tokenData = await response.json();
966
1081
  const typedData = tokenData;
1082
+ const scopeMismatch = typedData.scope_mismatch ?? false;
967
1083
  const tokenResponse = new TokenResponse(typedData);
968
1084
  if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
969
- throw new TokenRetrievalError(
1085
+ throw new BackendError(
970
1086
  `Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
971
1087
  { connectionId: String(connectionId) }
972
1088
  );
973
1089
  }
974
1090
  if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
975
- throw new TokenRetrievalError(
1091
+ throw new BackendError(
976
1092
  `Backend returned invalid injection_format (contains control characters)`,
977
1093
  { connectionId: String(connectionId) }
978
1094
  );
@@ -981,7 +1097,7 @@ ${contentHash}`;
981
1097
  if (typedData.additional_credentials) {
982
1098
  _storeAdditionalCredentials(tokenResponse, typedData.additional_credentials);
983
1099
  }
984
- return tokenResponse;
1100
+ return { tokenResponse, scopeMismatch };
985
1101
  }
986
1102
  /**
987
1103
  * Log an API call to the backend audit endpoint (INTERNAL).
@@ -1011,10 +1127,11 @@ ${contentHash}`;
1011
1127
  const actorHeaders = this.#getActorRequestHeaders(params.runId);
1012
1128
  const auditPath = "/sdk/oauth/audit/api-call";
1013
1129
  const auditBody = sanitized;
1014
- const auditHmac = this.#computeHmacHeaders("POST", auditPath, JSON.stringify(auditBody));
1130
+ const auditBodyStr = JSON.stringify(auditBody);
1131
+ const auditHmac = this.#computeHmacHeaders("POST", auditPath, auditBodyStr);
1015
1132
  const response = await this.#alterClient.post(auditPath, {
1016
- json: auditBody,
1017
- headers: { ...actorHeaders, ...auditHmac }
1133
+ body: auditBodyStr,
1134
+ headers: { ...actorHeaders, ...auditHmac, "Content-Type": "application/json" }
1018
1135
  });
1019
1136
  this.#cacheActorIdFromResponse(response);
1020
1137
  if (!response.ok) {
@@ -1086,12 +1203,22 @@ ${contentHash}`;
1086
1203
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
1087
1204
  );
1088
1205
  }
1206
+ const provider = options?.provider;
1207
+ const account = options?.account;
1208
+ if (!connectionId && !provider) {
1209
+ throw new AlterSDKError("Provide connectionId or options.provider");
1210
+ }
1211
+ if (connectionId && provider) {
1212
+ throw new AlterSDKError("Cannot provide both connectionId and options.provider");
1213
+ }
1214
+ const effectiveConnectionId = connectionId ?? null;
1215
+ let currentUrl = url;
1089
1216
  const runId = options?.runId ?? randomUUID();
1090
1217
  const methodStr = String(method).toUpperCase();
1091
- const urlLower = url.toLowerCase();
1218
+ const urlLower = currentUrl.toLowerCase();
1092
1219
  if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
1093
1220
  throw new AlterSDKError(
1094
- `URL must start with https:// or http://, got: ${url.slice(0, 50)}`
1221
+ `URL must start with https:// or http://, got: ${currentUrl.slice(0, 50)}`
1095
1222
  );
1096
1223
  }
1097
1224
  if (options?.pathParams && Object.keys(options.pathParams).length > 0) {
@@ -1100,7 +1227,7 @@ ${contentHash}`;
1100
1227
  encodedParams[key] = encodeURIComponent(String(value));
1101
1228
  }
1102
1229
  try {
1103
- let resolvedUrl = url;
1230
+ let resolvedUrl = currentUrl;
1104
1231
  for (const [key, value] of Object.entries(encodedParams)) {
1105
1232
  const placeholder = `{${key}}`;
1106
1233
  if (!resolvedUrl.includes(placeholder)) {
@@ -1111,26 +1238,28 @@ ${contentHash}`;
1111
1238
  const remaining = resolvedUrl.match(/\{(\w+)\}/);
1112
1239
  if (remaining) {
1113
1240
  throw new AlterSDKError(
1114
- `Invalid URL template or missing path_params: '${remaining[1]}'. URL: ${url}, path_params: ${JSON.stringify(options.pathParams)}`
1241
+ `Invalid URL template or missing path_params: '${remaining[1]}'. URL: ${currentUrl}, path_params: ${JSON.stringify(options.pathParams)}`
1115
1242
  );
1116
1243
  }
1117
- url = resolvedUrl;
1244
+ currentUrl = resolvedUrl;
1118
1245
  } catch (error) {
1119
1246
  if (error instanceof AlterSDKError) {
1120
1247
  throw error;
1121
1248
  }
1122
1249
  throw new AlterSDKError(
1123
- `Invalid URL template or missing path_params: ${error instanceof Error ? error.message : String(error)}. URL: ${url}, path_params: ${JSON.stringify(options.pathParams)}`
1250
+ `Invalid URL template or missing path_params: ${error instanceof Error ? error.message : String(error)}. URL: ${currentUrl}, path_params: ${JSON.stringify(options.pathParams)}`
1124
1251
  );
1125
1252
  }
1126
1253
  }
1127
- const tokenResponse = await this.#getToken(
1128
- connectionId,
1254
+ const { tokenResponse, scopeMismatch } = await this.#getToken(
1255
+ effectiveConnectionId,
1129
1256
  options?.reason,
1130
- { method: methodStr, url },
1257
+ { method: methodStr, url: currentUrl },
1131
1258
  runId,
1132
1259
  options?.threadId,
1133
- options?.toolCallId
1260
+ options?.toolCallId,
1261
+ provider,
1262
+ account
1134
1263
  );
1135
1264
  const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
1136
1265
  const accessToken = _extractAccessToken(tokenResponse);
@@ -1147,14 +1276,14 @@ ${contentHash}`;
1147
1276
  }
1148
1277
  }
1149
1278
  if (!additionalCreds.secret_key) {
1150
- throw new TokenRetrievalError(
1279
+ throw new BackendError(
1151
1280
  "AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
1152
- { connection_id: connectionId }
1281
+ { connection_id: effectiveConnectionId }
1153
1282
  );
1154
1283
  }
1155
1284
  const awsHeaders = signAwsRequest({
1156
1285
  method: methodStr,
1157
- url,
1286
+ url: currentUrl,
1158
1287
  headers: requestHeaders,
1159
1288
  body: sigv4BodyStr,
1160
1289
  accessKeyId,
@@ -1173,6 +1302,25 @@ ${contentHash}`;
1173
1302
  }
1174
1303
  requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
1175
1304
  }
1305
+ const auditUrl = currentUrl;
1306
+ if (tokenResponse.additionalInjections && !isSigV4) {
1307
+ for (const rule of tokenResponse.additionalInjections) {
1308
+ const value = _AlterVault.#resolveInjectionValue(
1309
+ rule.value_source,
1310
+ accessToken,
1311
+ additionalCreds
1312
+ );
1313
+ if (!value) continue;
1314
+ if (!/^[A-Za-z][A-Za-z0-9\-_]*$/.test(rule.key)) continue;
1315
+ if (rule.target === "header") {
1316
+ requestHeaders[rule.key] = value;
1317
+ } else if (rule.target === "query_param") {
1318
+ const urlObj = new URL(currentUrl);
1319
+ urlObj.searchParams.set(rule.key, value);
1320
+ currentUrl = urlObj.toString();
1321
+ }
1322
+ }
1323
+ }
1176
1324
  if (!requestHeaders["User-Agent"]) {
1177
1325
  requestHeaders["User-Agent"] = SDK_USER_AGENT;
1178
1326
  }
@@ -1180,13 +1328,13 @@ ${contentHash}`;
1180
1328
  let response;
1181
1329
  try {
1182
1330
  if (isSigV4 && sigv4BodyStr != null) {
1183
- response = await this.#providerClient.request(methodStr, url, {
1331
+ response = await this.#providerClient.request(methodStr, currentUrl, {
1184
1332
  body: sigv4BodyStr,
1185
1333
  headers: requestHeaders,
1186
1334
  params: options?.queryParams
1187
1335
  });
1188
1336
  } else {
1189
- response = await this.#providerClient.request(methodStr, url, {
1337
+ response = await this.#providerClient.request(methodStr, currentUrl, {
1190
1338
  json: options?.json,
1191
1339
  headers: requestHeaders,
1192
1340
  params: options?.queryParams
@@ -1197,18 +1345,18 @@ ${contentHash}`;
1197
1345
  throw new TimeoutError(
1198
1346
  `Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
1199
1347
  {
1200
- connection_id: connectionId,
1348
+ connection_id: effectiveConnectionId,
1201
1349
  method: methodStr,
1202
- url
1350
+ url: currentUrl
1203
1351
  }
1204
1352
  );
1205
1353
  }
1206
1354
  throw new NetworkError(
1207
1355
  `Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
1208
1356
  {
1209
- connection_id: connectionId,
1357
+ connection_id: effectiveConnectionId,
1210
1358
  method: methodStr,
1211
- url,
1359
+ url: currentUrl,
1212
1360
  error: String(error)
1213
1361
  }
1214
1362
  );
@@ -1216,6 +1364,13 @@ ${contentHash}`;
1216
1364
  const latencyMs = Date.now() - startTime;
1217
1365
  const sigv4Sensitive = /* @__PURE__ */ new Set(["authorization", "x-amz-date", "x-amz-content-sha256"]);
1218
1366
  const stripHeaders = isSigV4 ? sigv4Sensitive : /* @__PURE__ */ new Set([injectionHeaderLower]);
1367
+ if (tokenResponse.additionalInjections && !isSigV4) {
1368
+ for (const rule of tokenResponse.additionalInjections) {
1369
+ if (rule.target === "header") {
1370
+ stripHeaders.add(rule.key.toLowerCase());
1371
+ }
1372
+ }
1373
+ }
1219
1374
  const auditHeaders = {};
1220
1375
  for (const [key, value] of Object.entries(requestHeaders)) {
1221
1376
  if (!stripHeaders.has(key.toLowerCase())) {
@@ -1229,9 +1384,9 @@ ${contentHash}`;
1229
1384
  });
1230
1385
  this.#scheduleAuditLog({
1231
1386
  connectionId: tokenResponse.connectionId,
1232
- providerId: tokenResponse.providerId || connectionId,
1387
+ providerId: tokenResponse.providerId || effectiveConnectionId || "",
1233
1388
  method: methodStr,
1234
- url,
1389
+ url: auditUrl,
1235
1390
  requestHeaders: auditHeaders,
1236
1391
  requestBody: options?.json ?? null,
1237
1392
  responseStatus: response.status,
@@ -1244,14 +1399,29 @@ ${contentHash}`;
1244
1399
  toolCallId: options?.toolCallId ?? null
1245
1400
  });
1246
1401
  if (response.status >= HTTP_CLIENT_ERROR_START) {
1402
+ if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
1403
+ throw new ScopeReauthRequiredError(
1404
+ "Provider API returned 403 and the connection's scopes don't match the provider config. The user needs to re-authorize to grant updated permissions.",
1405
+ tokenResponse.connectionId,
1406
+ tokenResponse.providerId,
1407
+ response.status,
1408
+ responseBody,
1409
+ {
1410
+ connection_id: tokenResponse.connectionId,
1411
+ provider_id: tokenResponse.providerId,
1412
+ method: methodStr,
1413
+ url: currentUrl
1414
+ }
1415
+ );
1416
+ }
1247
1417
  throw new ProviderAPIError(
1248
1418
  `Provider API returned error ${response.status}`,
1249
1419
  response.status,
1250
1420
  responseBody,
1251
1421
  {
1252
- connection_id: connectionId,
1422
+ connection_id: effectiveConnectionId,
1253
1423
  method: methodStr,
1254
- url
1424
+ url: currentUrl
1255
1425
  }
1256
1426
  );
1257
1427
  }
@@ -1276,12 +1446,23 @@ ${contentHash}`;
1276
1446
  limit: options?.limit ?? 100,
1277
1447
  offset: options?.offset ?? 0
1278
1448
  };
1449
+ if (this.#userTokenGetter) {
1450
+ try {
1451
+ listBody.user_token = await this.#getUserToken();
1452
+ } catch (err) {
1453
+ console.warn(
1454
+ "user_token_getter failed in listConnections, falling back to un-scoped listing:",
1455
+ err instanceof Error ? err.message : String(err)
1456
+ );
1457
+ }
1458
+ }
1279
1459
  const listPath = "/sdk/oauth/connections/list";
1280
- const listHmac = this.#computeHmacHeaders("POST", listPath, JSON.stringify(listBody));
1460
+ const listBodyStr = JSON.stringify(listBody);
1461
+ const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
1281
1462
  try {
1282
1463
  response = await this.#alterClient.post(listPath, {
1283
- json: listBody,
1284
- headers: { ...actorHeaders, ...listHmac }
1464
+ body: listBodyStr,
1465
+ headers: { ...actorHeaders, ...listHmac, "Content-Type": "application/json" }
1285
1466
  });
1286
1467
  } catch (error) {
1287
1468
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1327,24 +1508,37 @@ ${contentHash}`;
1327
1508
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
1328
1509
  );
1329
1510
  }
1330
- if (!options.endUser?.id) {
1331
- throw new AlterSDKError("endUser.id is required");
1332
- }
1333
1511
  const actorHeaders = this.#getActorRequestHeaders();
1334
1512
  let response;
1335
1513
  const sessionBody = {
1336
- end_user: options.endUser,
1337
1514
  allowed_providers: options.allowedProviders ?? null,
1338
1515
  return_url: options.returnUrl ?? null,
1339
1516
  allowed_origin: options.allowedOrigin ?? null,
1340
1517
  metadata: options.metadata ?? null
1341
1518
  };
1519
+ if (options.connectionPolicy) {
1520
+ sessionBody.connection_policy = {
1521
+ max_ttl_seconds: options.connectionPolicy.maxTtlSeconds ?? null,
1522
+ default_ttl_seconds: options.connectionPolicy.defaultTtlSeconds ?? null
1523
+ };
1524
+ }
1525
+ if (this.#userTokenGetter) {
1526
+ try {
1527
+ sessionBody.user_token = await this.#getUserToken();
1528
+ } catch (err) {
1529
+ console.warn(
1530
+ "userTokenGetter failed in createConnectSession, session will not have identity tagging:",
1531
+ err instanceof Error ? err.message : String(err)
1532
+ );
1533
+ }
1534
+ }
1342
1535
  const sessionPath = "/sdk/oauth/connect/session";
1343
- const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, JSON.stringify(sessionBody));
1536
+ const sessionBodyStr = JSON.stringify(sessionBody);
1537
+ const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, sessionBodyStr);
1344
1538
  try {
1345
1539
  response = await this.#alterClient.post(sessionPath, {
1346
- json: sessionBody,
1347
- headers: { ...actorHeaders, ...sessionHmac }
1540
+ body: sessionBodyStr,
1541
+ headers: { ...actorHeaders, ...sessionHmac, "Content-Type": "application/json" }
1348
1542
  });
1349
1543
  } catch (error) {
1350
1544
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1375,7 +1569,7 @@ ${contentHash}`;
1375
1569
  * server-side applications. It creates a Connect session, opens the
1376
1570
  * browser, and polls until the user completes OAuth.
1377
1571
  *
1378
- * @param options - Connect options (endUser is required)
1572
+ * @param options - Connect options
1379
1573
  * @returns Array of ConnectResult objects (one per connected provider)
1380
1574
  * @throws ConnectTimeoutError if the user doesn't complete within timeout
1381
1575
  * @throws ConnectFlowError if the user denies or provider returns error
@@ -1391,8 +1585,8 @@ ${contentHash}`;
1391
1585
  const pollInterval = options.pollInterval ?? 2;
1392
1586
  const openBrowser = options.openBrowser ?? true;
1393
1587
  const session = await this.createConnectSession({
1394
- endUser: options.endUser,
1395
- allowedProviders: options.providers
1588
+ allowedProviders: options.providers,
1589
+ connectionPolicy: options.connectionPolicy
1396
1590
  });
1397
1591
  if (openBrowser) {
1398
1592
  try {
@@ -1429,18 +1623,28 @@ ${contentHash}`;
1429
1623
  connection_id: conn.connection_id ?? "",
1430
1624
  provider_id: conn.provider_id ?? "",
1431
1625
  account_identifier: conn.account_identifier ?? null,
1432
- scopes: conn.scopes ?? []
1626
+ scopes: conn.scopes ?? [],
1627
+ connection_policy: conn.connection_policy ?? null
1433
1628
  })
1434
1629
  );
1435
1630
  }
1436
1631
  if (pollStatus === "error") {
1437
1632
  const err = pollResult.error;
1438
- throw new ConnectFlowError(
1439
- err?.error_message ?? "OAuth flow failed",
1440
- {
1441
- error_code: err?.error_code ?? "unknown_error"
1442
- }
1443
- );
1633
+ const errorCode = err?.error_code ?? "unknown_error";
1634
+ const errorMessage = err?.error_message ?? "OAuth flow failed";
1635
+ const errorDetails = { error_code: errorCode };
1636
+ if (errorCode === "connect_denied") {
1637
+ throw new ConnectDeniedError(errorMessage, errorDetails);
1638
+ }
1639
+ if ([
1640
+ "connect_config_error",
1641
+ "invalid_redirect_uri",
1642
+ "invalid_client",
1643
+ "unauthorized_client"
1644
+ ].includes(errorCode)) {
1645
+ throw new ConnectConfigError(errorMessage, errorDetails);
1646
+ }
1647
+ throw new ConnectFlowError(errorMessage, errorDetails);
1444
1648
  }
1445
1649
  if (pollStatus === "expired") {
1446
1650
  throw new ConnectFlowError(
@@ -1462,16 +1666,17 @@ ${contentHash}`;
1462
1666
  const actorHeaders = this.#getActorRequestHeaders();
1463
1667
  const pollPath = "/sdk/oauth/connect/session/poll";
1464
1668
  const pollBody = { session_token: sessionToken };
1669
+ const pollBodyStr = JSON.stringify(pollBody);
1465
1670
  const pollHmac = this.#computeHmacHeaders(
1466
1671
  "POST",
1467
1672
  pollPath,
1468
- JSON.stringify(pollBody)
1673
+ pollBodyStr
1469
1674
  );
1470
1675
  let response;
1471
1676
  try {
1472
1677
  response = await this.#alterClient.post(pollPath, {
1473
- json: pollBody,
1474
- headers: { ...actorHeaders, ...pollHmac }
1678
+ body: pollBodyStr,
1679
+ headers: { ...actorHeaders, ...pollHmac, "Content-Type": "application/json" }
1475
1680
  });
1476
1681
  } catch (error) {
1477
1682
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1519,10 +1724,72 @@ Object.freeze(AlterVault.prototype);
1519
1724
 
1520
1725
  // src/providers/enums.ts
1521
1726
  var Provider = /* @__PURE__ */ ((Provider2) => {
1522
- Provider2["GOOGLE"] = "google";
1727
+ Provider2["ACUITY_SCHEDULING"] = "acuity-scheduling";
1728
+ Provider2["ADOBE"] = "adobe";
1729
+ Provider2["AIRCALL"] = "aircall";
1730
+ Provider2["AIRTABLE"] = "airtable";
1731
+ Provider2["APOLLO"] = "apollo";
1732
+ Provider2["ASANA"] = "asana";
1733
+ Provider2["ATLASSIAN"] = "atlassian";
1734
+ Provider2["ATTIO"] = "attio";
1735
+ Provider2["AUTODESK"] = "autodesk";
1736
+ Provider2["BASECAMP"] = "basecamp";
1737
+ Provider2["BITBUCKET"] = "bitbucket";
1738
+ Provider2["BITLY"] = "bitly";
1739
+ Provider2["BOX"] = "box";
1740
+ Provider2["BREX"] = "brex";
1741
+ Provider2["CALENDLY"] = "calendly";
1742
+ Provider2["CAL_COM"] = "cal-com";
1743
+ Provider2["CANVA"] = "canva";
1744
+ Provider2["CLICKUP"] = "clickup";
1745
+ Provider2["CLOSE"] = "close";
1746
+ Provider2["CONSTANT_CONTACT"] = "constant-contact";
1747
+ Provider2["CONTENTFUL"] = "contentful";
1748
+ Provider2["DEEL"] = "deel";
1749
+ Provider2["DIALPAD"] = "dialpad";
1750
+ Provider2["DIGITALOCEAN"] = "digitalocean";
1751
+ Provider2["DISCORD"] = "discord";
1752
+ Provider2["DOCUSIGN"] = "docusign";
1753
+ Provider2["DROPBOX"] = "dropbox";
1754
+ Provider2["EBAY"] = "ebay";
1755
+ Provider2["EVENTBRITE"] = "eventbrite";
1756
+ Provider2["FACEBOOK"] = "facebook";
1757
+ Provider2["FIGMA"] = "figma";
1523
1758
  Provider2["GITHUB"] = "github";
1524
- Provider2["SLACK"] = "slack";
1759
+ Provider2["GOOGLE"] = "google";
1760
+ Provider2["HUBSPOT"] = "hubspot";
1761
+ Provider2["INSTAGRAM"] = "instagram";
1762
+ Provider2["LINEAR"] = "linear";
1763
+ Provider2["LINKEDIN"] = "linkedin";
1764
+ Provider2["MAILCHIMP"] = "mailchimp";
1765
+ Provider2["MERCURY"] = "mercury";
1766
+ Provider2["MICROSOFT"] = "microsoft";
1767
+ Provider2["MIRO"] = "miro";
1768
+ Provider2["MONDAY"] = "monday";
1769
+ Provider2["NOTION"] = "notion";
1770
+ Provider2["OUTREACH"] = "outreach";
1771
+ Provider2["PAGERDUTY"] = "pagerduty";
1772
+ Provider2["PAYPAL"] = "paypal";
1773
+ Provider2["PINTEREST"] = "pinterest";
1774
+ Provider2["PIPEDRIVE"] = "pipedrive";
1775
+ Provider2["QUICKBOOKS"] = "quickbooks";
1776
+ Provider2["RAMP"] = "ramp";
1777
+ Provider2["REDDIT"] = "reddit";
1778
+ Provider2["RINGCENTRAL"] = "ringcentral";
1779
+ Provider2["SALESFORCE"] = "salesforce";
1525
1780
  Provider2["SENTRY"] = "sentry";
1781
+ Provider2["SLACK"] = "slack";
1782
+ Provider2["SNAPCHAT"] = "snapchat";
1783
+ Provider2["SPOTIFY"] = "spotify";
1784
+ Provider2["SQUARE"] = "square";
1785
+ Provider2["SQUARESPACE"] = "squarespace";
1786
+ Provider2["STRIPE"] = "stripe";
1787
+ Provider2["TIKTOK"] = "tiktok";
1788
+ Provider2["TODOIST"] = "todoist";
1789
+ Provider2["TWITTER"] = "twitter";
1790
+ Provider2["TYPEFORM"] = "typeform";
1791
+ Provider2["WEBEX"] = "webex";
1792
+ Provider2["WEBFLOW"] = "webflow";
1526
1793
  return Provider2;
1527
1794
  })(Provider || {});
1528
1795
  var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
@@ -1540,20 +1807,26 @@ export {
1540
1807
  ActorType,
1541
1808
  AlterSDKError,
1542
1809
  AlterVault,
1810
+ BackendError,
1811
+ ConnectConfigError,
1812
+ ConnectDeniedError,
1543
1813
  ConnectFlowError,
1544
1814
  ConnectResult,
1545
1815
  ConnectSession,
1546
1816
  ConnectTimeoutError,
1817
+ ConnectionDeletedError,
1818
+ ConnectionExpiredError,
1547
1819
  ConnectionInfo,
1548
1820
  ConnectionListResult,
1549
1821
  ConnectionNotFoundError,
1822
+ ConnectionRevokedError,
1550
1823
  HttpMethod,
1551
1824
  NetworkError,
1552
1825
  PolicyViolationError,
1553
1826
  Provider,
1554
1827
  ProviderAPIError,
1828
+ ReAuthRequiredError,
1829
+ ScopeReauthRequiredError,
1555
1830
  TimeoutError,
1556
- TokenExpiredError,
1557
- TokenResponse,
1558
- TokenRetrievalError
1831
+ TokenResponse
1559
1832
  };