@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.cjs CHANGED
@@ -34,22 +34,28 @@ __export(index_exports, {
34
34
  ActorType: () => ActorType,
35
35
  AlterSDKError: () => AlterSDKError,
36
36
  AlterVault: () => AlterVault,
37
+ BackendError: () => BackendError,
38
+ ConnectConfigError: () => ConnectConfigError,
39
+ ConnectDeniedError: () => ConnectDeniedError,
37
40
  ConnectFlowError: () => ConnectFlowError,
38
41
  ConnectResult: () => ConnectResult,
39
42
  ConnectSession: () => ConnectSession,
40
43
  ConnectTimeoutError: () => ConnectTimeoutError,
44
+ ConnectionDeletedError: () => ConnectionDeletedError,
45
+ ConnectionExpiredError: () => ConnectionExpiredError,
41
46
  ConnectionInfo: () => ConnectionInfo,
42
47
  ConnectionListResult: () => ConnectionListResult,
43
48
  ConnectionNotFoundError: () => ConnectionNotFoundError,
49
+ ConnectionRevokedError: () => ConnectionRevokedError,
44
50
  HttpMethod: () => HttpMethod,
45
51
  NetworkError: () => NetworkError,
46
52
  PolicyViolationError: () => PolicyViolationError,
47
53
  Provider: () => Provider,
48
54
  ProviderAPIError: () => ProviderAPIError,
55
+ ReAuthRequiredError: () => ReAuthRequiredError,
56
+ ScopeReauthRequiredError: () => ScopeReauthRequiredError,
49
57
  TimeoutError: () => TimeoutError,
50
- TokenExpiredError: () => TokenExpiredError,
51
- TokenResponse: () => TokenResponse,
52
- TokenRetrievalError: () => TokenRetrievalError
58
+ TokenResponse: () => TokenResponse
53
59
  });
54
60
  module.exports = __toCommonJS(index_exports);
55
61
 
@@ -72,40 +78,70 @@ var AlterSDKError = class extends Error {
72
78
  return this.message;
73
79
  }
74
80
  };
75
- var TokenRetrievalError = class extends AlterSDKError {
81
+ var BackendError = class extends AlterSDKError {
76
82
  constructor(message, details) {
77
83
  super(message, details);
78
- this.name = "TokenRetrievalError";
84
+ this.name = "BackendError";
79
85
  }
80
86
  };
81
- var PolicyViolationError = class extends TokenRetrievalError {
82
- policyError;
83
- constructor(message, policyError, details) {
87
+ var ReAuthRequiredError = class extends BackendError {
88
+ constructor(message, details) {
84
89
  super(message, details);
85
- this.name = "PolicyViolationError";
86
- this.policyError = policyError;
90
+ this.name = "ReAuthRequiredError";
87
91
  }
88
92
  };
89
- var ConnectionNotFoundError = class extends TokenRetrievalError {
93
+ var ConnectionExpiredError = class extends ReAuthRequiredError {
90
94
  constructor(message, details) {
91
95
  super(message, details);
92
- this.name = "ConnectionNotFoundError";
96
+ this.name = "ConnectionExpiredError";
93
97
  }
94
98
  };
95
- var TokenExpiredError = class extends TokenRetrievalError {
99
+ var ConnectionRevokedError = class extends ReAuthRequiredError {
96
100
  connectionId;
97
101
  constructor(message, connectionId, details) {
98
102
  super(message, details);
99
- this.name = "TokenExpiredError";
103
+ this.name = "ConnectionRevokedError";
100
104
  this.connectionId = connectionId;
101
105
  }
102
106
  };
107
+ var ConnectionDeletedError = class extends ReAuthRequiredError {
108
+ constructor(message, details) {
109
+ super(message, details);
110
+ this.name = "ConnectionDeletedError";
111
+ }
112
+ };
113
+ var ConnectionNotFoundError = class extends BackendError {
114
+ constructor(message, details) {
115
+ super(message, details);
116
+ this.name = "ConnectionNotFoundError";
117
+ }
118
+ };
119
+ var PolicyViolationError = class extends BackendError {
120
+ policyError;
121
+ constructor(message, policyError, details) {
122
+ super(message, details);
123
+ this.name = "PolicyViolationError";
124
+ this.policyError = policyError;
125
+ }
126
+ };
103
127
  var ConnectFlowError = class extends AlterSDKError {
104
128
  constructor(message, details) {
105
129
  super(message, details);
106
130
  this.name = "ConnectFlowError";
107
131
  }
108
132
  };
133
+ var ConnectDeniedError = class extends ConnectFlowError {
134
+ constructor(message, details) {
135
+ super(message, details);
136
+ this.name = "ConnectDeniedError";
137
+ }
138
+ };
139
+ var ConnectConfigError = class extends ConnectFlowError {
140
+ constructor(message, details) {
141
+ super(message, details);
142
+ this.name = "ConnectConfigError";
143
+ }
144
+ };
109
145
  var ConnectTimeoutError = class extends ConnectFlowError {
110
146
  constructor(message, details) {
111
147
  super(message, details);
@@ -122,6 +158,16 @@ var ProviderAPIError = class extends AlterSDKError {
122
158
  this.responseBody = responseBody;
123
159
  }
124
160
  };
161
+ var ScopeReauthRequiredError = class extends ProviderAPIError {
162
+ connectionId;
163
+ providerId;
164
+ constructor(message, connectionId, providerId, statusCode, responseBody, details) {
165
+ super(message, statusCode, responseBody, details);
166
+ this.name = "ScopeReauthRequiredError";
167
+ this.connectionId = connectionId;
168
+ this.providerId = providerId;
169
+ }
170
+ };
125
171
  var NetworkError = class extends AlterSDKError {
126
172
  constructor(message, details) {
127
173
  super(message, details);
@@ -161,6 +207,8 @@ var TokenResponse = class _TokenResponse {
161
207
  injectionFormat;
162
208
  /** Extra credentials for multi-part auth (e.g. AWS SigV4 secret_key, region) */
163
209
  additionalCredentials;
210
+ /** Additional injection rules for multi-header or query param auth */
211
+ additionalInjections;
164
212
  constructor(data) {
165
213
  this.tokenType = data.token_type ?? "Bearer";
166
214
  this.expiresIn = data.expires_in ?? null;
@@ -176,6 +224,7 @@ var TokenResponse = class _TokenResponse {
176
224
  } else {
177
225
  this.additionalCredentials = null;
178
226
  }
227
+ this.additionalInjections = data.additional_injections ?? null;
179
228
  Object.freeze(this);
180
229
  }
181
230
  /**
@@ -244,6 +293,7 @@ var ConnectionInfo = class {
244
293
  accountIdentifier;
245
294
  accountDisplayName;
246
295
  status;
296
+ scopeMismatch;
247
297
  expiresAt;
248
298
  createdAt;
249
299
  lastUsedAt;
@@ -254,6 +304,7 @@ var ConnectionInfo = class {
254
304
  this.accountIdentifier = data.account_identifier ?? null;
255
305
  this.accountDisplayName = data.account_display_name ?? null;
256
306
  this.status = data.status;
307
+ this.scopeMismatch = data.scope_mismatch ?? false;
257
308
  this.expiresAt = data.expires_at ?? null;
258
309
  this.createdAt = data.created_at;
259
310
  this.lastUsedAt = data.last_used_at ?? null;
@@ -267,6 +318,7 @@ var ConnectionInfo = class {
267
318
  account_identifier: this.accountIdentifier,
268
319
  account_display_name: this.accountDisplayName,
269
320
  status: this.status,
321
+ scope_mismatch: this.scopeMismatch,
270
322
  expires_at: this.expiresAt,
271
323
  created_at: this.createdAt,
272
324
  last_used_at: this.lastUsedAt
@@ -320,11 +372,13 @@ var ConnectResult = class {
320
372
  providerId;
321
373
  accountIdentifier;
322
374
  scopes;
375
+ connectionPolicy;
323
376
  constructor(data) {
324
377
  this.connectionId = data.connection_id;
325
378
  this.providerId = data.provider_id;
326
379
  this.accountIdentifier = data.account_identifier ?? null;
327
380
  this.scopes = data.scopes ?? [];
381
+ this.connectionPolicy = data.connection_policy ?? null;
328
382
  Object.freeze(this);
329
383
  }
330
384
  toJSON() {
@@ -332,7 +386,8 @@ var ConnectResult = class {
332
386
  connection_id: this.connectionId,
333
387
  provider_id: this.providerId,
334
388
  account_identifier: this.accountIdentifier,
335
- scopes: this.scopes
389
+ scopes: this.scopes,
390
+ connection_policy: this.connectionPolicy
336
391
  };
337
392
  }
338
393
  toString() {
@@ -598,10 +653,11 @@ function _extractAdditionalCredentials(token) {
598
653
  return _additionalCredsStore.get(token);
599
654
  }
600
655
  var _fetch;
601
- var SDK_VERSION = "0.4.0";
656
+ var SDK_VERSION = "0.5.0";
602
657
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
603
658
  var HTTP_FORBIDDEN = 403;
604
659
  var HTTP_NOT_FOUND = 404;
660
+ var HTTP_GONE = 410;
605
661
  var HTTP_BAD_REQUEST = 400;
606
662
  var HTTP_UNAUTHORIZED = 401;
607
663
  var HTTP_BAD_GATEWAY = 502;
@@ -657,6 +713,9 @@ var HttpClient = class {
657
713
  };
658
714
  if (options?.body !== void 0) {
659
715
  init.body = options.body;
716
+ if (!mergedHeaders["Content-Type"]) {
717
+ mergedHeaders["Content-Type"] = "application/json";
718
+ }
660
719
  } else if (options?.json !== void 0) {
661
720
  init.body = JSON.stringify(options.json);
662
721
  mergedHeaders["Content-Type"] = "application/json";
@@ -693,6 +752,8 @@ var AlterVault = class _AlterVault {
693
752
  #closed = false;
694
753
  /** Pending audit log promises (fire-and-forget) */
695
754
  #auditPromises = /* @__PURE__ */ new Set();
755
+ /** JWT identity resolution: callable that returns user token */
756
+ #userTokenGetter = null;
696
757
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
697
758
  // Public readonly properties (frozen by Object.freeze)
698
759
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -728,6 +789,7 @@ var AlterVault = class _AlterVault {
728
789
  this.#actorName = options.actorName;
729
790
  this.#actorVersion = options.actorVersion;
730
791
  this.#clientType = options.clientType;
792
+ this.#userTokenGetter = options.userTokenGetter ?? null;
731
793
  this.#framework = options.framework;
732
794
  if (!_fetch) {
733
795
  _fetch = globalThis.fetch;
@@ -902,6 +964,24 @@ ${contentHash}`;
902
964
  }
903
965
  return false;
904
966
  }
967
+ /**
968
+ * Resolve a value_source to the actual value for injection.
969
+ *
970
+ * @param valueSource - "token" or "additional_credentials.<field>"
971
+ * @param accessToken - The main credential value
972
+ * @param additionalCreds - Additional credentials from vault
973
+ * @returns The resolved value, or null if not available
974
+ */
975
+ static #resolveInjectionValue(valueSource, accessToken, additionalCreds) {
976
+ if (valueSource === "token") {
977
+ return accessToken;
978
+ }
979
+ if (valueSource.startsWith("additional_credentials.")) {
980
+ const field = valueSource.slice("additional_credentials.".length);
981
+ return additionalCreds?.[field] ?? null;
982
+ }
983
+ return null;
984
+ }
905
985
  static async #safeParseJson(response) {
906
986
  try {
907
987
  return await response.clone().json();
@@ -923,12 +1003,25 @@ ${contentHash}`;
923
1003
  }
924
1004
  if (response.status === HTTP_FORBIDDEN) {
925
1005
  const errorData = await _AlterVault.#safeParseJson(response);
1006
+ if (errorData.error === "connection_expired") {
1007
+ throw new ConnectionExpiredError(
1008
+ errorData.message ?? "Connection expired per TTL policy",
1009
+ errorData.details
1010
+ );
1011
+ }
926
1012
  throw new PolicyViolationError(
927
1013
  errorData.message ?? "Access denied by policy",
928
1014
  errorData.error,
929
1015
  errorData.details
930
1016
  );
931
1017
  }
1018
+ if (response.status === HTTP_GONE) {
1019
+ const errorData = await _AlterVault.#safeParseJson(response);
1020
+ throw new ConnectionDeletedError(
1021
+ errorData.message ?? "Connection has been deleted. A new connection_id will be issued on re-authorization.",
1022
+ errorData
1023
+ );
1024
+ }
932
1025
  if (response.status === HTTP_NOT_FOUND) {
933
1026
  const errorData = await _AlterVault.#safeParseJson(response);
934
1027
  throw new ConnectionNotFoundError(
@@ -938,35 +1031,35 @@ ${contentHash}`;
938
1031
  }
939
1032
  if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
940
1033
  const errorData = await _AlterVault.#safeParseJson(response);
941
- if (JSON.stringify(errorData).toLowerCase().includes("token_expired")) {
942
- throw new TokenExpiredError(
943
- errorData.message ?? "Token expired and refresh failed",
1034
+ if (errorData.error === "connection_revoked") {
1035
+ throw new ConnectionRevokedError(
1036
+ errorData.message ?? "Connection has been revoked. User must re-authorize.",
944
1037
  errorData.connection_id,
945
1038
  errorData
946
1039
  );
947
1040
  }
948
- throw new TokenRetrievalError(
1041
+ throw new BackendError(
949
1042
  errorData.message ?? `Backend error ${response.status}`,
950
1043
  errorData
951
1044
  );
952
1045
  }
953
1046
  if (response.status === HTTP_UNAUTHORIZED) {
954
1047
  const errorData = await _AlterVault.#safeParseJson(response);
955
- throw new TokenRetrievalError(
1048
+ throw new BackendError(
956
1049
  errorData.message ?? "Unauthorized \u2014 check your API key",
957
1050
  errorData
958
1051
  );
959
1052
  }
960
1053
  if (response.status === HTTP_INTERNAL_SERVER_ERROR || response.status === HTTP_SERVICE_UNAVAILABLE) {
961
1054
  const errorData = await _AlterVault.#safeParseJson(response);
962
- throw new TokenRetrievalError(
1055
+ throw new BackendError(
963
1056
  errorData.message ?? `Backend unavailable (HTTP ${response.status})`,
964
1057
  errorData
965
1058
  );
966
1059
  }
967
1060
  if (response.status >= HTTP_CLIENT_ERROR_START) {
968
1061
  const errorData = await _AlterVault.#safeParseJson(response);
969
- throw new TokenRetrievalError(
1062
+ throw new BackendError(
970
1063
  errorData.message ?? `Unexpected backend error (HTTP ${response.status})`,
971
1064
  errorData
972
1065
  );
@@ -978,24 +1071,52 @@ ${contentHash}`;
978
1071
  * This is a private method. Tokens are NEVER exposed to developers.
979
1072
  * Use request() instead, which handles tokens internally.
980
1073
  */
981
- async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId) {
1074
+ async #getUserToken() {
1075
+ if (!this.#userTokenGetter) {
1076
+ throw new AlterSDKError(
1077
+ "userTokenGetter is required for provider-based resolution. Pass userTokenGetter to AlterVault constructor."
1078
+ );
1079
+ }
1080
+ const result = this.#userTokenGetter();
1081
+ const token = result instanceof Promise ? await result : result;
1082
+ if (!token || typeof token !== "string") {
1083
+ throw new AlterSDKError("userTokenGetter must return a non-empty string");
1084
+ }
1085
+ return token;
1086
+ }
1087
+ async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId, provider, account) {
982
1088
  const actorHeaders = this.#getActorRequestHeaders(
983
1089
  runId,
984
1090
  threadId,
985
1091
  toolCallId
986
1092
  );
987
1093
  let response;
988
- const tokenBody = {
989
- connection_id: connectionId,
990
- reason: reason ?? null,
991
- request: requestMetadata ?? null
992
- };
1094
+ let tokenBody;
1095
+ if (provider) {
1096
+ const userToken = await this.#getUserToken();
1097
+ tokenBody = {
1098
+ provider_id: provider,
1099
+ user_token: userToken,
1100
+ reason: reason ?? null,
1101
+ request: requestMetadata ?? null
1102
+ };
1103
+ if (account) {
1104
+ tokenBody.account = account;
1105
+ }
1106
+ } else {
1107
+ tokenBody = {
1108
+ connection_id: connectionId,
1109
+ reason: reason ?? null,
1110
+ request: requestMetadata ?? null
1111
+ };
1112
+ }
993
1113
  const tokenPath = "/sdk/token";
994
- const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, JSON.stringify(tokenBody));
1114
+ const tokenBodyStr = JSON.stringify(tokenBody);
1115
+ const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, tokenBodyStr);
995
1116
  try {
996
1117
  response = await this.#alterClient.post(tokenPath, {
997
- json: tokenBody,
998
- headers: { ...actorHeaders, ...hmacHeaders }
1118
+ body: tokenBodyStr,
1119
+ headers: { ...actorHeaders, ...hmacHeaders, "Content-Type": "application/json" }
999
1120
  });
1000
1121
  } catch (error) {
1001
1122
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1010,7 +1131,7 @@ ${contentHash}`;
1010
1131
  { base_url: this.baseUrl }
1011
1132
  );
1012
1133
  }
1013
- throw new TokenRetrievalError(
1134
+ throw new BackendError(
1014
1135
  `Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
1015
1136
  { connection_id: connectionId, error: String(error) }
1016
1137
  );
@@ -1019,15 +1140,16 @@ ${contentHash}`;
1019
1140
  await this.#handleErrorResponse(response);
1020
1141
  const tokenData = await response.json();
1021
1142
  const typedData = tokenData;
1143
+ const scopeMismatch = typedData.scope_mismatch ?? false;
1022
1144
  const tokenResponse = new TokenResponse(typedData);
1023
1145
  if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
1024
- throw new TokenRetrievalError(
1146
+ throw new BackendError(
1025
1147
  `Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
1026
1148
  { connectionId: String(connectionId) }
1027
1149
  );
1028
1150
  }
1029
1151
  if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
1030
- throw new TokenRetrievalError(
1152
+ throw new BackendError(
1031
1153
  `Backend returned invalid injection_format (contains control characters)`,
1032
1154
  { connectionId: String(connectionId) }
1033
1155
  );
@@ -1036,7 +1158,7 @@ ${contentHash}`;
1036
1158
  if (typedData.additional_credentials) {
1037
1159
  _storeAdditionalCredentials(tokenResponse, typedData.additional_credentials);
1038
1160
  }
1039
- return tokenResponse;
1161
+ return { tokenResponse, scopeMismatch };
1040
1162
  }
1041
1163
  /**
1042
1164
  * Log an API call to the backend audit endpoint (INTERNAL).
@@ -1066,10 +1188,11 @@ ${contentHash}`;
1066
1188
  const actorHeaders = this.#getActorRequestHeaders(params.runId);
1067
1189
  const auditPath = "/sdk/oauth/audit/api-call";
1068
1190
  const auditBody = sanitized;
1069
- const auditHmac = this.#computeHmacHeaders("POST", auditPath, JSON.stringify(auditBody));
1191
+ const auditBodyStr = JSON.stringify(auditBody);
1192
+ const auditHmac = this.#computeHmacHeaders("POST", auditPath, auditBodyStr);
1070
1193
  const response = await this.#alterClient.post(auditPath, {
1071
- json: auditBody,
1072
- headers: { ...actorHeaders, ...auditHmac }
1194
+ body: auditBodyStr,
1195
+ headers: { ...actorHeaders, ...auditHmac, "Content-Type": "application/json" }
1073
1196
  });
1074
1197
  this.#cacheActorIdFromResponse(response);
1075
1198
  if (!response.ok) {
@@ -1141,12 +1264,22 @@ ${contentHash}`;
1141
1264
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
1142
1265
  );
1143
1266
  }
1267
+ const provider = options?.provider;
1268
+ const account = options?.account;
1269
+ if (!connectionId && !provider) {
1270
+ throw new AlterSDKError("Provide connectionId or options.provider");
1271
+ }
1272
+ if (connectionId && provider) {
1273
+ throw new AlterSDKError("Cannot provide both connectionId and options.provider");
1274
+ }
1275
+ const effectiveConnectionId = connectionId ?? null;
1276
+ let currentUrl = url;
1144
1277
  const runId = options?.runId ?? (0, import_node_crypto2.randomUUID)();
1145
1278
  const methodStr = String(method).toUpperCase();
1146
- const urlLower = url.toLowerCase();
1279
+ const urlLower = currentUrl.toLowerCase();
1147
1280
  if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
1148
1281
  throw new AlterSDKError(
1149
- `URL must start with https:// or http://, got: ${url.slice(0, 50)}`
1282
+ `URL must start with https:// or http://, got: ${currentUrl.slice(0, 50)}`
1150
1283
  );
1151
1284
  }
1152
1285
  if (options?.pathParams && Object.keys(options.pathParams).length > 0) {
@@ -1155,7 +1288,7 @@ ${contentHash}`;
1155
1288
  encodedParams[key] = encodeURIComponent(String(value));
1156
1289
  }
1157
1290
  try {
1158
- let resolvedUrl = url;
1291
+ let resolvedUrl = currentUrl;
1159
1292
  for (const [key, value] of Object.entries(encodedParams)) {
1160
1293
  const placeholder = `{${key}}`;
1161
1294
  if (!resolvedUrl.includes(placeholder)) {
@@ -1166,26 +1299,28 @@ ${contentHash}`;
1166
1299
  const remaining = resolvedUrl.match(/\{(\w+)\}/);
1167
1300
  if (remaining) {
1168
1301
  throw new AlterSDKError(
1169
- `Invalid URL template or missing path_params: '${remaining[1]}'. URL: ${url}, path_params: ${JSON.stringify(options.pathParams)}`
1302
+ `Invalid URL template or missing path_params: '${remaining[1]}'. URL: ${currentUrl}, path_params: ${JSON.stringify(options.pathParams)}`
1170
1303
  );
1171
1304
  }
1172
- url = resolvedUrl;
1305
+ currentUrl = resolvedUrl;
1173
1306
  } catch (error) {
1174
1307
  if (error instanceof AlterSDKError) {
1175
1308
  throw error;
1176
1309
  }
1177
1310
  throw new AlterSDKError(
1178
- `Invalid URL template or missing path_params: ${error instanceof Error ? error.message : String(error)}. URL: ${url}, path_params: ${JSON.stringify(options.pathParams)}`
1311
+ `Invalid URL template or missing path_params: ${error instanceof Error ? error.message : String(error)}. URL: ${currentUrl}, path_params: ${JSON.stringify(options.pathParams)}`
1179
1312
  );
1180
1313
  }
1181
1314
  }
1182
- const tokenResponse = await this.#getToken(
1183
- connectionId,
1315
+ const { tokenResponse, scopeMismatch } = await this.#getToken(
1316
+ effectiveConnectionId,
1184
1317
  options?.reason,
1185
- { method: methodStr, url },
1318
+ { method: methodStr, url: currentUrl },
1186
1319
  runId,
1187
1320
  options?.threadId,
1188
- options?.toolCallId
1321
+ options?.toolCallId,
1322
+ provider,
1323
+ account
1189
1324
  );
1190
1325
  const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
1191
1326
  const accessToken = _extractAccessToken(tokenResponse);
@@ -1202,14 +1337,14 @@ ${contentHash}`;
1202
1337
  }
1203
1338
  }
1204
1339
  if (!additionalCreds.secret_key) {
1205
- throw new TokenRetrievalError(
1340
+ throw new BackendError(
1206
1341
  "AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
1207
- { connection_id: connectionId }
1342
+ { connection_id: effectiveConnectionId }
1208
1343
  );
1209
1344
  }
1210
1345
  const awsHeaders = signAwsRequest({
1211
1346
  method: methodStr,
1212
- url,
1347
+ url: currentUrl,
1213
1348
  headers: requestHeaders,
1214
1349
  body: sigv4BodyStr,
1215
1350
  accessKeyId,
@@ -1228,6 +1363,25 @@ ${contentHash}`;
1228
1363
  }
1229
1364
  requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
1230
1365
  }
1366
+ const auditUrl = currentUrl;
1367
+ if (tokenResponse.additionalInjections && !isSigV4) {
1368
+ for (const rule of tokenResponse.additionalInjections) {
1369
+ const value = _AlterVault.#resolveInjectionValue(
1370
+ rule.value_source,
1371
+ accessToken,
1372
+ additionalCreds
1373
+ );
1374
+ if (!value) continue;
1375
+ if (!/^[A-Za-z][A-Za-z0-9\-_]*$/.test(rule.key)) continue;
1376
+ if (rule.target === "header") {
1377
+ requestHeaders[rule.key] = value;
1378
+ } else if (rule.target === "query_param") {
1379
+ const urlObj = new URL(currentUrl);
1380
+ urlObj.searchParams.set(rule.key, value);
1381
+ currentUrl = urlObj.toString();
1382
+ }
1383
+ }
1384
+ }
1231
1385
  if (!requestHeaders["User-Agent"]) {
1232
1386
  requestHeaders["User-Agent"] = SDK_USER_AGENT;
1233
1387
  }
@@ -1235,13 +1389,13 @@ ${contentHash}`;
1235
1389
  let response;
1236
1390
  try {
1237
1391
  if (isSigV4 && sigv4BodyStr != null) {
1238
- response = await this.#providerClient.request(methodStr, url, {
1392
+ response = await this.#providerClient.request(methodStr, currentUrl, {
1239
1393
  body: sigv4BodyStr,
1240
1394
  headers: requestHeaders,
1241
1395
  params: options?.queryParams
1242
1396
  });
1243
1397
  } else {
1244
- response = await this.#providerClient.request(methodStr, url, {
1398
+ response = await this.#providerClient.request(methodStr, currentUrl, {
1245
1399
  json: options?.json,
1246
1400
  headers: requestHeaders,
1247
1401
  params: options?.queryParams
@@ -1252,18 +1406,18 @@ ${contentHash}`;
1252
1406
  throw new TimeoutError(
1253
1407
  `Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
1254
1408
  {
1255
- connection_id: connectionId,
1409
+ connection_id: effectiveConnectionId,
1256
1410
  method: methodStr,
1257
- url
1411
+ url: currentUrl
1258
1412
  }
1259
1413
  );
1260
1414
  }
1261
1415
  throw new NetworkError(
1262
1416
  `Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
1263
1417
  {
1264
- connection_id: connectionId,
1418
+ connection_id: effectiveConnectionId,
1265
1419
  method: methodStr,
1266
- url,
1420
+ url: currentUrl,
1267
1421
  error: String(error)
1268
1422
  }
1269
1423
  );
@@ -1271,6 +1425,13 @@ ${contentHash}`;
1271
1425
  const latencyMs = Date.now() - startTime;
1272
1426
  const sigv4Sensitive = /* @__PURE__ */ new Set(["authorization", "x-amz-date", "x-amz-content-sha256"]);
1273
1427
  const stripHeaders = isSigV4 ? sigv4Sensitive : /* @__PURE__ */ new Set([injectionHeaderLower]);
1428
+ if (tokenResponse.additionalInjections && !isSigV4) {
1429
+ for (const rule of tokenResponse.additionalInjections) {
1430
+ if (rule.target === "header") {
1431
+ stripHeaders.add(rule.key.toLowerCase());
1432
+ }
1433
+ }
1434
+ }
1274
1435
  const auditHeaders = {};
1275
1436
  for (const [key, value] of Object.entries(requestHeaders)) {
1276
1437
  if (!stripHeaders.has(key.toLowerCase())) {
@@ -1284,9 +1445,9 @@ ${contentHash}`;
1284
1445
  });
1285
1446
  this.#scheduleAuditLog({
1286
1447
  connectionId: tokenResponse.connectionId,
1287
- providerId: tokenResponse.providerId || connectionId,
1448
+ providerId: tokenResponse.providerId || effectiveConnectionId || "",
1288
1449
  method: methodStr,
1289
- url,
1450
+ url: auditUrl,
1290
1451
  requestHeaders: auditHeaders,
1291
1452
  requestBody: options?.json ?? null,
1292
1453
  responseStatus: response.status,
@@ -1299,14 +1460,29 @@ ${contentHash}`;
1299
1460
  toolCallId: options?.toolCallId ?? null
1300
1461
  });
1301
1462
  if (response.status >= HTTP_CLIENT_ERROR_START) {
1463
+ if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
1464
+ throw new ScopeReauthRequiredError(
1465
+ "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.",
1466
+ tokenResponse.connectionId,
1467
+ tokenResponse.providerId,
1468
+ response.status,
1469
+ responseBody,
1470
+ {
1471
+ connection_id: tokenResponse.connectionId,
1472
+ provider_id: tokenResponse.providerId,
1473
+ method: methodStr,
1474
+ url: currentUrl
1475
+ }
1476
+ );
1477
+ }
1302
1478
  throw new ProviderAPIError(
1303
1479
  `Provider API returned error ${response.status}`,
1304
1480
  response.status,
1305
1481
  responseBody,
1306
1482
  {
1307
- connection_id: connectionId,
1483
+ connection_id: effectiveConnectionId,
1308
1484
  method: methodStr,
1309
- url
1485
+ url: currentUrl
1310
1486
  }
1311
1487
  );
1312
1488
  }
@@ -1331,12 +1507,23 @@ ${contentHash}`;
1331
1507
  limit: options?.limit ?? 100,
1332
1508
  offset: options?.offset ?? 0
1333
1509
  };
1510
+ if (this.#userTokenGetter) {
1511
+ try {
1512
+ listBody.user_token = await this.#getUserToken();
1513
+ } catch (err) {
1514
+ console.warn(
1515
+ "user_token_getter failed in listConnections, falling back to un-scoped listing:",
1516
+ err instanceof Error ? err.message : String(err)
1517
+ );
1518
+ }
1519
+ }
1334
1520
  const listPath = "/sdk/oauth/connections/list";
1335
- const listHmac = this.#computeHmacHeaders("POST", listPath, JSON.stringify(listBody));
1521
+ const listBodyStr = JSON.stringify(listBody);
1522
+ const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
1336
1523
  try {
1337
1524
  response = await this.#alterClient.post(listPath, {
1338
- json: listBody,
1339
- headers: { ...actorHeaders, ...listHmac }
1525
+ body: listBodyStr,
1526
+ headers: { ...actorHeaders, ...listHmac, "Content-Type": "application/json" }
1340
1527
  });
1341
1528
  } catch (error) {
1342
1529
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1382,24 +1569,37 @@ ${contentHash}`;
1382
1569
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
1383
1570
  );
1384
1571
  }
1385
- if (!options.endUser?.id) {
1386
- throw new AlterSDKError("endUser.id is required");
1387
- }
1388
1572
  const actorHeaders = this.#getActorRequestHeaders();
1389
1573
  let response;
1390
1574
  const sessionBody = {
1391
- end_user: options.endUser,
1392
1575
  allowed_providers: options.allowedProviders ?? null,
1393
1576
  return_url: options.returnUrl ?? null,
1394
1577
  allowed_origin: options.allowedOrigin ?? null,
1395
1578
  metadata: options.metadata ?? null
1396
1579
  };
1580
+ if (options.connectionPolicy) {
1581
+ sessionBody.connection_policy = {
1582
+ max_ttl_seconds: options.connectionPolicy.maxTtlSeconds ?? null,
1583
+ default_ttl_seconds: options.connectionPolicy.defaultTtlSeconds ?? null
1584
+ };
1585
+ }
1586
+ if (this.#userTokenGetter) {
1587
+ try {
1588
+ sessionBody.user_token = await this.#getUserToken();
1589
+ } catch (err) {
1590
+ console.warn(
1591
+ "userTokenGetter failed in createConnectSession, session will not have identity tagging:",
1592
+ err instanceof Error ? err.message : String(err)
1593
+ );
1594
+ }
1595
+ }
1397
1596
  const sessionPath = "/sdk/oauth/connect/session";
1398
- const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, JSON.stringify(sessionBody));
1597
+ const sessionBodyStr = JSON.stringify(sessionBody);
1598
+ const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, sessionBodyStr);
1399
1599
  try {
1400
1600
  response = await this.#alterClient.post(sessionPath, {
1401
- json: sessionBody,
1402
- headers: { ...actorHeaders, ...sessionHmac }
1601
+ body: sessionBodyStr,
1602
+ headers: { ...actorHeaders, ...sessionHmac, "Content-Type": "application/json" }
1403
1603
  });
1404
1604
  } catch (error) {
1405
1605
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1430,7 +1630,7 @@ ${contentHash}`;
1430
1630
  * server-side applications. It creates a Connect session, opens the
1431
1631
  * browser, and polls until the user completes OAuth.
1432
1632
  *
1433
- * @param options - Connect options (endUser is required)
1633
+ * @param options - Connect options
1434
1634
  * @returns Array of ConnectResult objects (one per connected provider)
1435
1635
  * @throws ConnectTimeoutError if the user doesn't complete within timeout
1436
1636
  * @throws ConnectFlowError if the user denies or provider returns error
@@ -1446,8 +1646,8 @@ ${contentHash}`;
1446
1646
  const pollInterval = options.pollInterval ?? 2;
1447
1647
  const openBrowser = options.openBrowser ?? true;
1448
1648
  const session = await this.createConnectSession({
1449
- endUser: options.endUser,
1450
- allowedProviders: options.providers
1649
+ allowedProviders: options.providers,
1650
+ connectionPolicy: options.connectionPolicy
1451
1651
  });
1452
1652
  if (openBrowser) {
1453
1653
  try {
@@ -1484,18 +1684,28 @@ ${contentHash}`;
1484
1684
  connection_id: conn.connection_id ?? "",
1485
1685
  provider_id: conn.provider_id ?? "",
1486
1686
  account_identifier: conn.account_identifier ?? null,
1487
- scopes: conn.scopes ?? []
1687
+ scopes: conn.scopes ?? [],
1688
+ connection_policy: conn.connection_policy ?? null
1488
1689
  })
1489
1690
  );
1490
1691
  }
1491
1692
  if (pollStatus === "error") {
1492
1693
  const err = pollResult.error;
1493
- throw new ConnectFlowError(
1494
- err?.error_message ?? "OAuth flow failed",
1495
- {
1496
- error_code: err?.error_code ?? "unknown_error"
1497
- }
1498
- );
1694
+ const errorCode = err?.error_code ?? "unknown_error";
1695
+ const errorMessage = err?.error_message ?? "OAuth flow failed";
1696
+ const errorDetails = { error_code: errorCode };
1697
+ if (errorCode === "connect_denied") {
1698
+ throw new ConnectDeniedError(errorMessage, errorDetails);
1699
+ }
1700
+ if ([
1701
+ "connect_config_error",
1702
+ "invalid_redirect_uri",
1703
+ "invalid_client",
1704
+ "unauthorized_client"
1705
+ ].includes(errorCode)) {
1706
+ throw new ConnectConfigError(errorMessage, errorDetails);
1707
+ }
1708
+ throw new ConnectFlowError(errorMessage, errorDetails);
1499
1709
  }
1500
1710
  if (pollStatus === "expired") {
1501
1711
  throw new ConnectFlowError(
@@ -1517,16 +1727,17 @@ ${contentHash}`;
1517
1727
  const actorHeaders = this.#getActorRequestHeaders();
1518
1728
  const pollPath = "/sdk/oauth/connect/session/poll";
1519
1729
  const pollBody = { session_token: sessionToken };
1730
+ const pollBodyStr = JSON.stringify(pollBody);
1520
1731
  const pollHmac = this.#computeHmacHeaders(
1521
1732
  "POST",
1522
1733
  pollPath,
1523
- JSON.stringify(pollBody)
1734
+ pollBodyStr
1524
1735
  );
1525
1736
  let response;
1526
1737
  try {
1527
1738
  response = await this.#alterClient.post(pollPath, {
1528
- json: pollBody,
1529
- headers: { ...actorHeaders, ...pollHmac }
1739
+ body: pollBodyStr,
1740
+ headers: { ...actorHeaders, ...pollHmac, "Content-Type": "application/json" }
1530
1741
  });
1531
1742
  } catch (error) {
1532
1743
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1574,10 +1785,72 @@ Object.freeze(AlterVault.prototype);
1574
1785
 
1575
1786
  // src/providers/enums.ts
1576
1787
  var Provider = /* @__PURE__ */ ((Provider2) => {
1577
- Provider2["GOOGLE"] = "google";
1788
+ Provider2["ACUITY_SCHEDULING"] = "acuity-scheduling";
1789
+ Provider2["ADOBE"] = "adobe";
1790
+ Provider2["AIRCALL"] = "aircall";
1791
+ Provider2["AIRTABLE"] = "airtable";
1792
+ Provider2["APOLLO"] = "apollo";
1793
+ Provider2["ASANA"] = "asana";
1794
+ Provider2["ATLASSIAN"] = "atlassian";
1795
+ Provider2["ATTIO"] = "attio";
1796
+ Provider2["AUTODESK"] = "autodesk";
1797
+ Provider2["BASECAMP"] = "basecamp";
1798
+ Provider2["BITBUCKET"] = "bitbucket";
1799
+ Provider2["BITLY"] = "bitly";
1800
+ Provider2["BOX"] = "box";
1801
+ Provider2["BREX"] = "brex";
1802
+ Provider2["CALENDLY"] = "calendly";
1803
+ Provider2["CAL_COM"] = "cal-com";
1804
+ Provider2["CANVA"] = "canva";
1805
+ Provider2["CLICKUP"] = "clickup";
1806
+ Provider2["CLOSE"] = "close";
1807
+ Provider2["CONSTANT_CONTACT"] = "constant-contact";
1808
+ Provider2["CONTENTFUL"] = "contentful";
1809
+ Provider2["DEEL"] = "deel";
1810
+ Provider2["DIALPAD"] = "dialpad";
1811
+ Provider2["DIGITALOCEAN"] = "digitalocean";
1812
+ Provider2["DISCORD"] = "discord";
1813
+ Provider2["DOCUSIGN"] = "docusign";
1814
+ Provider2["DROPBOX"] = "dropbox";
1815
+ Provider2["EBAY"] = "ebay";
1816
+ Provider2["EVENTBRITE"] = "eventbrite";
1817
+ Provider2["FACEBOOK"] = "facebook";
1818
+ Provider2["FIGMA"] = "figma";
1578
1819
  Provider2["GITHUB"] = "github";
1579
- Provider2["SLACK"] = "slack";
1820
+ Provider2["GOOGLE"] = "google";
1821
+ Provider2["HUBSPOT"] = "hubspot";
1822
+ Provider2["INSTAGRAM"] = "instagram";
1823
+ Provider2["LINEAR"] = "linear";
1824
+ Provider2["LINKEDIN"] = "linkedin";
1825
+ Provider2["MAILCHIMP"] = "mailchimp";
1826
+ Provider2["MERCURY"] = "mercury";
1827
+ Provider2["MICROSOFT"] = "microsoft";
1828
+ Provider2["MIRO"] = "miro";
1829
+ Provider2["MONDAY"] = "monday";
1830
+ Provider2["NOTION"] = "notion";
1831
+ Provider2["OUTREACH"] = "outreach";
1832
+ Provider2["PAGERDUTY"] = "pagerduty";
1833
+ Provider2["PAYPAL"] = "paypal";
1834
+ Provider2["PINTEREST"] = "pinterest";
1835
+ Provider2["PIPEDRIVE"] = "pipedrive";
1836
+ Provider2["QUICKBOOKS"] = "quickbooks";
1837
+ Provider2["RAMP"] = "ramp";
1838
+ Provider2["REDDIT"] = "reddit";
1839
+ Provider2["RINGCENTRAL"] = "ringcentral";
1840
+ Provider2["SALESFORCE"] = "salesforce";
1580
1841
  Provider2["SENTRY"] = "sentry";
1842
+ Provider2["SLACK"] = "slack";
1843
+ Provider2["SNAPCHAT"] = "snapchat";
1844
+ Provider2["SPOTIFY"] = "spotify";
1845
+ Provider2["SQUARE"] = "square";
1846
+ Provider2["SQUARESPACE"] = "squarespace";
1847
+ Provider2["STRIPE"] = "stripe";
1848
+ Provider2["TIKTOK"] = "tiktok";
1849
+ Provider2["TODOIST"] = "todoist";
1850
+ Provider2["TWITTER"] = "twitter";
1851
+ Provider2["TYPEFORM"] = "typeform";
1852
+ Provider2["WEBEX"] = "webex";
1853
+ Provider2["WEBFLOW"] = "webflow";
1581
1854
  return Provider2;
1582
1855
  })(Provider || {});
1583
1856
  var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
@@ -1596,20 +1869,26 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
1596
1869
  ActorType,
1597
1870
  AlterSDKError,
1598
1871
  AlterVault,
1872
+ BackendError,
1873
+ ConnectConfigError,
1874
+ ConnectDeniedError,
1599
1875
  ConnectFlowError,
1600
1876
  ConnectResult,
1601
1877
  ConnectSession,
1602
1878
  ConnectTimeoutError,
1879
+ ConnectionDeletedError,
1880
+ ConnectionExpiredError,
1603
1881
  ConnectionInfo,
1604
1882
  ConnectionListResult,
1605
1883
  ConnectionNotFoundError,
1884
+ ConnectionRevokedError,
1606
1885
  HttpMethod,
1607
1886
  NetworkError,
1608
1887
  PolicyViolationError,
1609
1888
  Provider,
1610
1889
  ProviderAPIError,
1890
+ ReAuthRequiredError,
1891
+ ScopeReauthRequiredError,
1611
1892
  TimeoutError,
1612
- TokenExpiredError,
1613
- TokenResponse,
1614
- TokenRetrievalError
1893
+ TokenResponse
1615
1894
  });