@alter-ai/alter-sdk 0.6.0 → 0.7.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
@@ -1,5 +1,5 @@
1
1
  // src/client.ts
2
- import { createHash as createHash2, createHmac as createHmac2, randomUUID } from "crypto";
2
+ import { createHash as createHash2, createHmac as createHmac2, randomBytes, randomUUID } from "crypto";
3
3
 
4
4
  // src/exceptions.ts
5
5
  var AlterSDKError = class extends Error {
@@ -29,30 +29,38 @@ var ReAuthRequiredError = class extends BackendError {
29
29
  this.name = "ReAuthRequiredError";
30
30
  }
31
31
  };
32
- var ConnectionExpiredError = class extends ReAuthRequiredError {
32
+ var GrantExpiredError = class extends ReAuthRequiredError {
33
33
  constructor(message, details) {
34
34
  super(message, details);
35
- this.name = "ConnectionExpiredError";
35
+ this.name = "GrantExpiredError";
36
36
  }
37
37
  };
38
- var ConnectionRevokedError = class extends ReAuthRequiredError {
39
- connectionId;
40
- constructor(message, connectionId, details) {
38
+ var GrantRevokedError = class extends ReAuthRequiredError {
39
+ grantId;
40
+ constructor(message, grantId, details) {
41
41
  super(message, details);
42
- this.name = "ConnectionRevokedError";
43
- this.connectionId = connectionId;
42
+ this.name = "GrantRevokedError";
43
+ this.grantId = grantId;
44
44
  }
45
45
  };
46
- var ConnectionDeletedError = class extends ReAuthRequiredError {
46
+ var CredentialRevokedError = class extends ReAuthRequiredError {
47
+ grantId;
48
+ constructor(message, grantId, details) {
49
+ super(message, details);
50
+ this.name = "CredentialRevokedError";
51
+ this.grantId = grantId;
52
+ }
53
+ };
54
+ var GrantDeletedError = class extends ReAuthRequiredError {
47
55
  constructor(message, details) {
48
56
  super(message, details);
49
- this.name = "ConnectionDeletedError";
57
+ this.name = "GrantDeletedError";
50
58
  }
51
59
  };
52
- var ConnectionNotFoundError = class extends BackendError {
60
+ var GrantNotFoundError = class extends BackendError {
53
61
  constructor(message, details) {
54
62
  super(message, details);
55
- this.name = "ConnectionNotFoundError";
63
+ this.name = "GrantNotFoundError";
56
64
  }
57
65
  };
58
66
  var PolicyViolationError = class extends BackendError {
@@ -98,12 +106,12 @@ var ProviderAPIError = class extends AlterSDKError {
98
106
  }
99
107
  };
100
108
  var ScopeReauthRequiredError = class extends ProviderAPIError {
101
- connectionId;
109
+ grantId;
102
110
  providerId;
103
- constructor(message, connectionId, providerId, statusCode, responseBody, details) {
111
+ constructor(message, grantId, providerId, statusCode, responseBody, details) {
104
112
  super(message, statusCode, responseBody, details);
105
113
  this.name = "ScopeReauthRequiredError";
106
- this.connectionId = connectionId;
114
+ this.grantId = grantId;
107
115
  this.providerId = providerId;
108
116
  }
109
117
  };
@@ -124,7 +132,6 @@ var TimeoutError = class extends NetworkError {
124
132
  var ActorType = /* @__PURE__ */ ((ActorType2) => {
125
133
  ActorType2["BACKEND_SERVICE"] = "backend_service";
126
134
  ActorType2["AI_AGENT"] = "ai_agent";
127
- ActorType2["MCP_SERVER"] = "mcp_server";
128
135
  return ActorType2;
129
136
  })(ActorType || {});
130
137
  var TokenResponse = class _TokenResponse {
@@ -136,8 +143,8 @@ var TokenResponse = class _TokenResponse {
136
143
  expiresAt;
137
144
  /** OAuth scopes granted */
138
145
  scopes;
139
- /** Connection ID that provided this token */
140
- connectionId;
146
+ /** Grant ID that provided this token */
147
+ grantId;
141
148
  /** Provider ID (google, github, etc.) */
142
149
  providerId;
143
150
  /** HTTP header name for credential injection (e.g., "Authorization", "X-API-Key") */
@@ -153,8 +160,8 @@ var TokenResponse = class _TokenResponse {
153
160
  this.expiresIn = data.expires_in ?? null;
154
161
  this.expiresAt = data.expires_at ? _TokenResponse.parseExpiresAt(data.expires_at) : null;
155
162
  this.scopes = data.scopes ?? [];
156
- this.connectionId = data.connection_id;
157
- this.providerId = data.provider_id ?? "";
163
+ this.grantId = data.grant_id;
164
+ this.providerId = data.provider_id ?? null;
158
165
  this.injectionHeader = data.injection_header ?? "Authorization";
159
166
  this.injectionFormat = data.injection_format ?? "Bearer {token}";
160
167
  if (data.additional_credentials) {
@@ -209,7 +216,7 @@ var TokenResponse = class _TokenResponse {
209
216
  expires_in: this.expiresIn,
210
217
  expires_at: this.expiresAt?.toISOString() ?? null,
211
218
  scopes: this.scopes,
212
- connection_id: this.connectionId,
219
+ grant_id: this.grantId,
213
220
  provider_id: this.providerId
214
221
  };
215
222
  }
@@ -217,7 +224,7 @@ var TokenResponse = class _TokenResponse {
217
224
  * Custom string representation — EXCLUDES access_token for security.
218
225
  */
219
226
  toString() {
220
- return `TokenResponse(connection_id=${this.connectionId}, token_type=${this.tokenType}, scopes=[${this.scopes.join(", ")}])`;
227
+ return `TokenResponse(grant_id=${this.grantId}, token_type=${this.tokenType}, scopes=[${this.scopes.join(", ")}])`;
221
228
  }
222
229
  /**
223
230
  * Custom Node.js inspect output — EXCLUDES access_token for security.
@@ -226,8 +233,8 @@ var TokenResponse = class _TokenResponse {
226
233
  return this.toString();
227
234
  }
228
235
  };
229
- var ConnectionInfo = class {
230
- connectionId;
236
+ var GrantInfo = class {
237
+ grantId;
231
238
  providerId;
232
239
  scopes;
233
240
  accountIdentifier;
@@ -238,7 +245,7 @@ var ConnectionInfo = class {
238
245
  createdAt;
239
246
  lastUsedAt;
240
247
  constructor(data) {
241
- this.connectionId = data.connection_id;
248
+ this.grantId = data.grant_id;
242
249
  this.providerId = data.provider_id;
243
250
  this.scopes = data.scopes ?? [];
244
251
  this.accountIdentifier = data.account_identifier ?? null;
@@ -252,7 +259,7 @@ var ConnectionInfo = class {
252
259
  }
253
260
  toJSON() {
254
261
  return {
255
- connection_id: this.connectionId,
262
+ grant_id: this.grantId,
256
263
  provider_id: this.providerId,
257
264
  scopes: this.scopes,
258
265
  account_identifier: this.accountIdentifier,
@@ -265,7 +272,7 @@ var ConnectionInfo = class {
265
272
  };
266
273
  }
267
274
  toString() {
268
- return `ConnectionInfo(connection_id=${this.connectionId}, provider=${this.providerId}, status=${this.status})`;
275
+ return `GrantInfo(grant_id=${this.grantId}, provider=${this.providerId}, status=${this.status})`;
269
276
  }
270
277
  };
271
278
  var ConnectSession = class {
@@ -292,60 +299,91 @@ var ConnectSession = class {
292
299
  return `ConnectSession(url=${this.connectUrl}, expires_in=${this.expiresIn})`;
293
300
  }
294
301
  };
295
- var ConnectionListResult = class {
296
- connections;
302
+ var GrantListResult = class {
303
+ grants;
297
304
  total;
298
305
  limit;
299
306
  offset;
300
307
  hasMore;
301
308
  constructor(data) {
302
- this.connections = data.connections;
309
+ this.grants = data.grants;
303
310
  this.total = data.total;
304
311
  this.limit = data.limit;
305
312
  this.offset = data.offset;
306
313
  this.hasMore = data.has_more;
307
314
  Object.freeze(this);
308
315
  }
316
+ /**
317
+ * Custom JSON serialization — snake_case wire format.
318
+ */
319
+ toJSON() {
320
+ return {
321
+ grants: this.grants.map((g) => g.toJSON()),
322
+ total: this.total,
323
+ limit: this.limit,
324
+ offset: this.offset,
325
+ has_more: this.hasMore
326
+ };
327
+ }
309
328
  };
310
329
  var ConnectResult = class {
311
- connectionId;
330
+ grantId;
312
331
  providerId;
313
332
  accountIdentifier;
314
333
  scopes;
315
- connectionPolicy;
334
+ grantPolicy;
316
335
  constructor(data) {
317
- this.connectionId = data.connection_id;
336
+ this.grantId = data.grant_id;
318
337
  this.providerId = data.provider_id;
319
338
  this.accountIdentifier = data.account_identifier ?? null;
320
339
  this.scopes = data.scopes ?? [];
321
- const rawPolicy = data.connection_policy;
340
+ const rawPolicy = data.grant_policy;
322
341
  if (rawPolicy) {
323
342
  const raw = rawPolicy;
324
- this.connectionPolicy = Object.freeze({
343
+ this.grantPolicy = Object.freeze({
325
344
  expiresAt: raw["expires_at"] !== void 0 ? raw["expires_at"] : rawPolicy.expiresAt ?? null,
326
345
  createdBy: raw["created_by"] !== void 0 ? raw["created_by"] : rawPolicy.createdBy ?? null,
327
346
  createdAt: raw["created_at"] !== void 0 ? raw["created_at"] : rawPolicy.createdAt ?? null
328
347
  });
329
348
  } else {
330
- this.connectionPolicy = null;
349
+ this.grantPolicy = null;
331
350
  }
332
351
  Object.freeze(this);
333
352
  }
334
353
  toJSON() {
335
354
  return {
336
- connection_id: this.connectionId,
355
+ grant_id: this.grantId,
337
356
  provider_id: this.providerId,
338
357
  account_identifier: this.accountIdentifier,
339
358
  scopes: this.scopes,
340
- connection_policy: this.connectionPolicy ? {
341
- expires_at: this.connectionPolicy.expiresAt ?? null,
342
- created_by: this.connectionPolicy.createdBy ?? null,
343
- created_at: this.connectionPolicy.createdAt ?? null
359
+ grant_policy: this.grantPolicy ? {
360
+ expires_at: this.grantPolicy.expiresAt ?? null,
361
+ created_by: this.grantPolicy.createdBy ?? null,
362
+ created_at: this.grantPolicy.createdAt ?? null
344
363
  } : null
345
364
  };
346
365
  }
347
366
  toString() {
348
- return `ConnectResult(connection_id=${this.connectionId}, provider=${this.providerId})`;
367
+ return `ConnectResult(grant_id=${this.grantId}, provider=${this.providerId})`;
368
+ }
369
+ };
370
+ var AuthResult = class {
371
+ userToken;
372
+ userInfo;
373
+ constructor(data) {
374
+ this.userToken = data.user_token;
375
+ this.userInfo = data.user_info ?? {};
376
+ Object.freeze(this);
377
+ }
378
+ toJSON() {
379
+ return {
380
+ user_token: this.userToken,
381
+ user_info: this.userInfo
382
+ };
383
+ }
384
+ toString() {
385
+ const sub = this.userInfo?.sub ?? "unknown";
386
+ return `AuthResult(sub=${sub})`;
349
387
  }
350
388
  };
351
389
  var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
@@ -359,7 +397,7 @@ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
359
397
  "x-amz-security-token"
360
398
  ]);
361
399
  var APICallAuditLog = class {
362
- connectionId;
400
+ grantId;
363
401
  providerId;
364
402
  method;
365
403
  url;
@@ -378,22 +416,28 @@ var APICallAuditLog = class {
378
416
  threadId;
379
417
  /** Tool invocation ID for actor tracking */
380
418
  toolCallId;
419
+ /** Specific tool being invoked (e.g., "read_calendar") */
420
+ toolId;
421
+ /** Tool bundle identifier (e.g., "google-calendar-mcp-server") */
422
+ toolBundleId;
381
423
  constructor(data) {
382
- this.connectionId = data.connectionId;
383
- this.providerId = data.providerId;
424
+ this.grantId = data.grant_id;
425
+ this.providerId = data.provider_id;
384
426
  this.method = data.method;
385
427
  this.url = data.url;
386
- this.requestHeaders = data.requestHeaders ?? null;
387
- this.requestBody = data.requestBody ?? null;
388
- this.responseStatus = data.responseStatus;
389
- this.responseHeaders = data.responseHeaders ?? null;
390
- this.responseBody = data.responseBody ?? null;
391
- this.latencyMs = data.latencyMs;
428
+ this.requestHeaders = data.request_headers ?? null;
429
+ this.requestBody = data.request_body ?? null;
430
+ this.responseStatus = data.response_status;
431
+ this.responseHeaders = data.response_headers ?? null;
432
+ this.responseBody = data.response_body ?? null;
433
+ this.latencyMs = data.latency_ms;
392
434
  this.timestamp = /* @__PURE__ */ new Date();
393
435
  this.reason = data.reason ?? null;
394
- this.runId = data.runId ?? null;
395
- this.threadId = data.threadId ?? null;
396
- this.toolCallId = data.toolCallId ?? null;
436
+ this.runId = data.run_id ?? null;
437
+ this.threadId = data.thread_id ?? null;
438
+ this.toolCallId = data.tool_call_id ?? null;
439
+ this.toolId = data.tool_id ?? null;
440
+ this.toolBundleId = data.tool_bundle_id ?? null;
397
441
  }
398
442
  /**
399
443
  * Sanitize sensitive data before sending.
@@ -402,7 +446,7 @@ var APICallAuditLog = class {
402
446
  */
403
447
  sanitize() {
404
448
  const sanitized = {
405
- connection_id: this.connectionId,
449
+ grant_id: this.grantId,
406
450
  provider_id: this.providerId,
407
451
  method: this.method,
408
452
  url: this.url,
@@ -415,7 +459,9 @@ var APICallAuditLog = class {
415
459
  reason: this.reason,
416
460
  run_id: this.runId,
417
461
  thread_id: this.threadId,
418
- tool_call_id: this.toolCallId
462
+ tool_call_id: this.toolCallId,
463
+ tool_id: this.toolId,
464
+ tool_bundle_id: this.toolBundleId
419
465
  };
420
466
  return sanitized;
421
467
  }
@@ -614,7 +660,7 @@ function _extractAdditionalCredentials(token) {
614
660
  return _additionalCredsStore.get(token);
615
661
  }
616
662
  var _fetch;
617
- var SDK_VERSION = "0.6.0";
663
+ var SDK_VERSION = "0.7.0";
618
664
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
619
665
  var HTTP_FORBIDDEN = 403;
620
666
  var HTTP_NOT_FOUND = 404;
@@ -726,6 +772,7 @@ var AlterVault = class _AlterVault {
726
772
  #actorVersion;
727
773
  #clientType;
728
774
  #framework;
775
+ #toolBundleId;
729
776
  constructor(options) {
730
777
  _AlterVault.#validateInitParams(
731
778
  options.apiKey,
@@ -737,13 +784,14 @@ var AlterVault = class _AlterVault {
737
784
  ["actorName", options.actorName],
738
785
  ["actorVersion", options.actorVersion],
739
786
  ["clientType", options.clientType],
740
- ["framework", options.framework]
787
+ ["framework", options.framework],
788
+ ["toolBundleId", options.toolBundleId]
741
789
  ];
742
790
  for (const [name, value] of actorStrings) {
743
791
  _AlterVault.#validateActorString(value, name);
744
792
  }
745
793
  this.#hmacKey = createHmac2("sha256", options.apiKey).update("alter-signing-v1").digest();
746
- this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterai.dev").replace(/\/+$/, "");
794
+ this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterauth.com").replace(/\/+$/, "");
747
795
  const timeoutMs = options.timeout ?? 3e4;
748
796
  this.#actorType = options.actorType;
749
797
  this.#actorIdentifier = options.actorIdentifier;
@@ -752,6 +800,7 @@ var AlterVault = class _AlterVault {
752
800
  this.#clientType = options.clientType;
753
801
  this.#userTokenGetter = options.userTokenGetter ?? null;
754
802
  this.#framework = options.framework;
803
+ this.#toolBundleId = options.toolBundleId;
755
804
  if (!_fetch) {
756
805
  _fetch = globalThis.fetch;
757
806
  }
@@ -799,7 +848,7 @@ var AlterVault = class _AlterVault {
799
848
  }
800
849
  if (!actorType) {
801
850
  throw new AlterSDKError(
802
- "actor_type is required (use ActorType.AI_AGENT, ActorType.MCP_SERVER, or ActorType.BACKEND_SERVICE)"
851
+ "actor_type is required (use ActorType.AI_AGENT or ActorType.BACKEND_SERVICE)"
803
852
  );
804
853
  }
805
854
  const validValues = Object.values(ActorType);
@@ -828,7 +877,8 @@ var AlterVault = class _AlterVault {
828
877
  "X-Alter-Actor-Name": this.#actorName,
829
878
  "X-Alter-Actor-Version": this.#actorVersion,
830
879
  "X-Alter-Client-Type": this.#clientType,
831
- "X-Alter-Framework": this.#framework
880
+ "X-Alter-Framework": this.#framework,
881
+ "X-Alter-Tool-Bundle-ID": this.#toolBundleId
832
882
  };
833
883
  for (const [key, value] of Object.entries(actorHeaders)) {
834
884
  if (value) {
@@ -840,21 +890,24 @@ var AlterVault = class _AlterVault {
840
890
  /**
841
891
  * Compute HMAC-SHA256 signature headers for an Alter backend request.
842
892
  *
843
- * String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nCONTENT_HASH
893
+ * String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nNONCE\nCONTENT_HASH
844
894
  *
845
895
  * The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
846
- * Currently all SDKbackend calls are POSTs without query params, so the path is clean.
896
+ * Currently all SDK->backend calls are POSTs without query params, so the path is clean.
847
897
  */
848
898
  #computeHmacHeaders(method, path, body) {
849
899
  const timestamp = String(Math.floor(Date.now() / 1e3));
900
+ const nonce = randomBytes(16).toString("hex");
850
901
  const contentHash = createHash2("sha256").update(body ?? "").digest("hex");
851
902
  const stringToSign = `${method.toUpperCase()}
852
903
  ${path}
853
904
  ${timestamp}
905
+ ${nonce}
854
906
  ${contentHash}`;
855
907
  const signature = createHmac2("sha256", this.#hmacKey).update(stringToSign).digest("hex");
856
908
  return {
857
909
  "X-Alter-Timestamp": timestamp,
910
+ "X-Alter-Nonce": nonce,
858
911
  "X-Alter-Content-SHA256": contentHash,
859
912
  "X-Alter-Signature": signature
860
913
  };
@@ -862,7 +915,7 @@ ${contentHash}`;
862
915
  /**
863
916
  * Build per-request actor headers for instance tracking.
864
917
  */
865
- #getActorRequestHeaders(runId, threadId, toolCallId) {
918
+ #getActorRequestHeaders(runId, threadId, toolCallId, toolId, toolBundleId) {
866
919
  const headers = {};
867
920
  if (this.#cachedActorId) {
868
921
  headers["X-Alter-Actor-ID"] = this.#cachedActorId;
@@ -892,6 +945,24 @@ ${contentHash}`;
892
945
  );
893
946
  }
894
947
  }
948
+ if (toolId) {
949
+ if (toolId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolId)) {
950
+ headers["X-Alter-Tool-ID"] = toolId;
951
+ } else {
952
+ console.warn(
953
+ "Invalid tool_id (too long or invalid chars), skipping"
954
+ );
955
+ }
956
+ }
957
+ if (toolBundleId) {
958
+ if (toolBundleId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolBundleId)) {
959
+ headers["X-Alter-Tool-Bundle-ID"] = toolBundleId;
960
+ } else {
961
+ console.warn(
962
+ "Invalid tool_bundle_id (too long or invalid chars), skipping"
963
+ );
964
+ }
965
+ }
895
966
  return headers;
896
967
  }
897
968
  /**
@@ -943,9 +1014,20 @@ ${contentHash}`;
943
1014
  }
944
1015
  return null;
945
1016
  }
1017
+ /**
1018
+ * Parse JSON from response, unwrapping FastAPI's HTTPException envelope.
1019
+ *
1020
+ * FastAPI wraps `HTTPException.detail` in `{"detail": ...}`. When the
1021
+ * inner detail is an object we unwrap it so callers can access error
1022
+ * fields (`error`, `message`, `details`) directly.
1023
+ */
946
1024
  static async #safeParseJson(response) {
947
1025
  try {
948
- return await response.clone().json();
1026
+ const data = await response.clone().json();
1027
+ if ("detail" in data && typeof data.detail === "object" && data.detail !== null && !Array.isArray(data.detail)) {
1028
+ return data.detail;
1029
+ }
1030
+ return data;
949
1031
  } catch {
950
1032
  try {
951
1033
  const text = await response.clone().text();
@@ -964,38 +1046,69 @@ ${contentHash}`;
964
1046
  }
965
1047
  if (response.status === HTTP_FORBIDDEN) {
966
1048
  const errorData = await _AlterVault.#safeParseJson(response);
967
- if (errorData.error === "connection_expired") {
968
- throw new ConnectionExpiredError(
969
- errorData.message ?? "Connection expired per TTL policy",
1049
+ const errorCode = errorData.error;
1050
+ if (errorCode === "grant_expired") {
1051
+ throw new GrantExpiredError(
1052
+ errorData.message ?? "Grant expired per TTL policy",
970
1053
  errorData.details
971
1054
  );
972
1055
  }
1056
+ if (errorCode === "grant_revoked") {
1057
+ throw new GrantRevokedError(
1058
+ errorData.message ?? "Grant has been revoked. User must re-authorize.",
1059
+ errorData.grant_id
1060
+ );
1061
+ }
1062
+ if (errorCode === "credential_revoked") {
1063
+ throw new CredentialRevokedError(
1064
+ errorData.message ?? "Credential has been revoked. User must re-authorize.",
1065
+ errorData.grant_id
1066
+ );
1067
+ }
1068
+ if (errorData.error === "scope_mismatch") {
1069
+ const details = errorData.details ?? {};
1070
+ throw new ScopeReauthRequiredError(
1071
+ errorData.message ?? "Grant is missing required scopes",
1072
+ details.grant_id ?? void 0,
1073
+ details.provider_id ?? void 0,
1074
+ response.status,
1075
+ JSON.stringify(errorData),
1076
+ details
1077
+ );
1078
+ }
973
1079
  throw new PolicyViolationError(
974
1080
  errorData.message ?? "Access denied by policy",
975
- errorData.error,
1081
+ errorCode,
976
1082
  errorData.details
977
1083
  );
978
1084
  }
979
1085
  if (response.status === HTTP_GONE) {
980
1086
  const errorData = await _AlterVault.#safeParseJson(response);
981
- throw new ConnectionDeletedError(
982
- errorData.message ?? "Connection has been deleted. A new connection_id will be issued on re-authorization.",
1087
+ throw new GrantDeletedError(
1088
+ errorData.message ?? "Grant has been deleted. A new grant_id will be issued on re-authorization.",
983
1089
  errorData
984
1090
  );
985
1091
  }
986
1092
  if (response.status === HTTP_NOT_FOUND) {
987
1093
  const errorData = await _AlterVault.#safeParseJson(response);
988
- throw new ConnectionNotFoundError(
989
- errorData.message ?? "OAuth connection not found for the given connection_id",
1094
+ throw new GrantNotFoundError(
1095
+ errorData.message ?? "OAuth grant not found for the given grant_id",
990
1096
  errorData
991
1097
  );
992
1098
  }
993
1099
  if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
994
1100
  const errorData = await _AlterVault.#safeParseJson(response);
995
- if (errorData.error === "connection_revoked") {
996
- throw new ConnectionRevokedError(
997
- errorData.message ?? "Connection has been revoked. User must re-authorize.",
998
- errorData.connection_id,
1101
+ if (errorData.error === "grant_revoked") {
1102
+ throw new GrantRevokedError(
1103
+ errorData.message ?? "Grant has been revoked. User must re-authorize.",
1104
+ errorData.grant_id,
1105
+ errorData
1106
+ );
1107
+ }
1108
+ if (errorData.error === "credential_revoked") {
1109
+ throw new CredentialRevokedError(
1110
+ errorData.message ?? "Underlying credential has been revoked. User must re-authorize.",
1111
+ errorData.grant_id,
999
1112
  errorData
1000
1113
  );
1001
1114
  }
@@ -1045,11 +1158,13 @@ ${contentHash}`;
1045
1158
  }
1046
1159
  return token;
1047
1160
  }
1048
- async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId, provider, account) {
1161
+ async #getToken(grantId, reason, requestMetadata, runId, threadId, toolCallId, toolId, provider, account, toolBundleId) {
1049
1162
  const actorHeaders = this.#getActorRequestHeaders(
1050
1163
  runId,
1051
1164
  threadId,
1052
- toolCallId
1165
+ toolCallId,
1166
+ toolId,
1167
+ toolBundleId
1053
1168
  );
1054
1169
  let response;
1055
1170
  let tokenBody;
@@ -1066,7 +1181,7 @@ ${contentHash}`;
1066
1181
  }
1067
1182
  } else {
1068
1183
  tokenBody = {
1069
- connection_id: connectionId,
1184
+ grant_id: grantId,
1070
1185
  reason: reason ?? null,
1071
1186
  request: requestMetadata ?? null
1072
1187
  };
@@ -1094,7 +1209,7 @@ ${contentHash}`;
1094
1209
  }
1095
1210
  throw new BackendError(
1096
1211
  `Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
1097
- { connection_id: connectionId, error: String(error) }
1212
+ { grant_id: grantId, error: String(error) }
1098
1213
  );
1099
1214
  }
1100
1215
  this.#cacheActorIdFromResponse(response);
@@ -1106,13 +1221,13 @@ ${contentHash}`;
1106
1221
  if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
1107
1222
  throw new BackendError(
1108
1223
  `Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
1109
- { connectionId: String(connectionId) }
1224
+ { grantId: String(grantId) }
1110
1225
  );
1111
1226
  }
1112
1227
  if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
1113
1228
  throw new BackendError(
1114
1229
  `Backend returned invalid injection_format (contains control characters)`,
1115
- { connectionId: String(connectionId) }
1230
+ { grantId: String(grantId) }
1116
1231
  );
1117
1232
  }
1118
1233
  _storeAccessToken(tokenResponse, typedData.access_token);
@@ -1130,20 +1245,22 @@ ${contentHash}`;
1130
1245
  async #logApiCall(params) {
1131
1246
  try {
1132
1247
  const auditLog = new APICallAuditLog({
1133
- connectionId: params.connectionId,
1134
- providerId: params.providerId,
1248
+ grant_id: params.grantId,
1249
+ provider_id: params.providerId,
1135
1250
  method: params.method,
1136
1251
  url: params.url,
1137
- requestHeaders: params.requestHeaders,
1138
- requestBody: params.requestBody,
1139
- responseStatus: params.responseStatus,
1140
- responseHeaders: params.responseHeaders,
1141
- responseBody: params.responseBody,
1142
- latencyMs: params.latencyMs,
1252
+ request_headers: params.requestHeaders,
1253
+ request_body: params.requestBody,
1254
+ response_status: params.responseStatus,
1255
+ response_headers: params.responseHeaders,
1256
+ response_body: params.responseBody,
1257
+ latency_ms: params.latencyMs,
1143
1258
  reason: params.reason,
1144
- runId: params.runId,
1145
- threadId: params.threadId,
1146
- toolCallId: params.toolCallId
1259
+ run_id: params.runId,
1260
+ thread_id: params.threadId,
1261
+ tool_call_id: params.toolCallId,
1262
+ tool_id: params.toolId,
1263
+ tool_bundle_id: params.toolBundleId
1147
1264
  });
1148
1265
  const sanitized = auditLog.sanitize();
1149
1266
  const actorHeaders = this.#getActorRequestHeaders(params.runId);
@@ -1219,7 +1336,7 @@ ${contentHash}`;
1219
1336
  * 4. Logs the call for audit (fire-and-forget)
1220
1337
  * 5. Returns the raw response
1221
1338
  */
1222
- async request(connectionId, method, url, options) {
1339
+ async request(grantId, method, url, options) {
1223
1340
  if (this.#closed) {
1224
1341
  throw new AlterSDKError(
1225
1342
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
@@ -1227,13 +1344,13 @@ ${contentHash}`;
1227
1344
  }
1228
1345
  const provider = options?.provider;
1229
1346
  const account = options?.account;
1230
- if (!connectionId && !provider) {
1231
- throw new AlterSDKError("Provide connectionId or options.provider");
1347
+ if (!grantId && !provider) {
1348
+ throw new AlterSDKError("Provide grantId or options.provider");
1232
1349
  }
1233
- if (connectionId && provider) {
1234
- throw new AlterSDKError("Cannot provide both connectionId and options.provider");
1350
+ if (grantId && provider) {
1351
+ throw new AlterSDKError("Cannot provide both grantId and options.provider");
1235
1352
  }
1236
- const effectiveConnectionId = connectionId ?? null;
1353
+ const effectiveGrantId = grantId ?? null;
1237
1354
  let currentUrl = url;
1238
1355
  const runId = options?.runId ?? randomUUID();
1239
1356
  const methodStr = String(method).toUpperCase();
@@ -1274,14 +1391,16 @@ ${contentHash}`;
1274
1391
  }
1275
1392
  }
1276
1393
  const { tokenResponse, scopeMismatch } = await this.#getToken(
1277
- effectiveConnectionId,
1394
+ effectiveGrantId,
1278
1395
  options?.reason,
1279
1396
  { method: methodStr, url: currentUrl },
1280
1397
  runId,
1281
1398
  options?.threadId,
1282
1399
  options?.toolCallId,
1400
+ options?.toolId,
1283
1401
  provider,
1284
- account
1402
+ account,
1403
+ options?.toolBundleId
1285
1404
  );
1286
1405
  const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
1287
1406
  const accessToken = _extractAccessToken(tokenResponse);
@@ -1300,7 +1419,7 @@ ${contentHash}`;
1300
1419
  if (!additionalCreds.secret_key) {
1301
1420
  throw new BackendError(
1302
1421
  "AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
1303
- { connection_id: effectiveConnectionId }
1422
+ { grant_id: effectiveGrantId }
1304
1423
  );
1305
1424
  }
1306
1425
  const awsHeaders = signAwsRequest({
@@ -1367,7 +1486,7 @@ ${contentHash}`;
1367
1486
  throw new TimeoutError(
1368
1487
  `Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
1369
1488
  {
1370
- connection_id: effectiveConnectionId,
1489
+ grant_id: effectiveGrantId,
1371
1490
  method: methodStr,
1372
1491
  url: currentUrl
1373
1492
  }
@@ -1376,7 +1495,7 @@ ${contentHash}`;
1376
1495
  throw new NetworkError(
1377
1496
  `Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
1378
1497
  {
1379
- connection_id: effectiveConnectionId,
1498
+ grant_id: effectiveGrantId,
1380
1499
  method: methodStr,
1381
1500
  url: currentUrl,
1382
1501
  error: String(error)
@@ -1405,8 +1524,8 @@ ${contentHash}`;
1405
1524
  responseHeaders[key] = value;
1406
1525
  });
1407
1526
  this.#scheduleAuditLog({
1408
- connectionId: tokenResponse.connectionId,
1409
- providerId: tokenResponse.providerId || effectiveConnectionId || "",
1527
+ grantId: tokenResponse.grantId,
1528
+ providerId: tokenResponse.providerId || effectiveGrantId || "",
1410
1529
  method: methodStr,
1411
1530
  url: auditUrl,
1412
1531
  requestHeaders: auditHeaders,
@@ -1418,18 +1537,20 @@ ${contentHash}`;
1418
1537
  reason: options?.reason ?? null,
1419
1538
  runId,
1420
1539
  threadId: options?.threadId ?? null,
1421
- toolCallId: options?.toolCallId ?? null
1540
+ toolCallId: options?.toolCallId ?? null,
1541
+ toolId: options?.toolId ?? null,
1542
+ toolBundleId: options?.toolBundleId ?? null
1422
1543
  });
1423
1544
  if (response.status >= HTTP_CLIENT_ERROR_START) {
1424
1545
  if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
1425
1546
  throw new ScopeReauthRequiredError(
1426
- "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.",
1427
- tokenResponse.connectionId,
1428
- tokenResponse.providerId,
1547
+ "Provider API returned 403 and the grant's scopes don't match the provider config. The user needs to re-authorize to grant updated permissions.",
1548
+ tokenResponse.grantId,
1549
+ tokenResponse.providerId ?? void 0,
1429
1550
  response.status,
1430
1551
  responseBody,
1431
1552
  {
1432
- connection_id: tokenResponse.connectionId,
1553
+ grant_id: tokenResponse.grantId,
1433
1554
  provider_id: tokenResponse.providerId,
1434
1555
  method: methodStr,
1435
1556
  url: currentUrl
@@ -1441,7 +1562,7 @@ ${contentHash}`;
1441
1562
  response.status,
1442
1563
  responseBody,
1443
1564
  {
1444
- connection_id: effectiveConnectionId,
1565
+ grant_id: effectiveGrantId,
1445
1566
  method: methodStr,
1446
1567
  url: currentUrl
1447
1568
  }
@@ -1450,12 +1571,12 @@ ${contentHash}`;
1450
1571
  return response;
1451
1572
  }
1452
1573
  /**
1453
- * List OAuth connections for this app.
1574
+ * List OAuth grants for this app.
1454
1575
  *
1455
- * Returns paginated connection metadata (no tokens).
1576
+ * Returns paginated grant metadata (no tokens).
1456
1577
  * Useful for discovering which services a user has connected.
1457
1578
  */
1458
- async listConnections(options) {
1579
+ async listGrants(options) {
1459
1580
  if (this.#closed) {
1460
1581
  throw new AlterSDKError(
1461
1582
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
@@ -1473,12 +1594,12 @@ ${contentHash}`;
1473
1594
  listBody.user_token = await this.#getUserToken();
1474
1595
  } catch (err) {
1475
1596
  console.warn(
1476
- "user_token_getter failed in listConnections, falling back to un-scoped listing:",
1597
+ "user_token_getter failed in listGrants, falling back to un-scoped listing:",
1477
1598
  err instanceof Error ? err.message : String(err)
1478
1599
  );
1479
1600
  }
1480
1601
  }
1481
- const listPath = "/sdk/oauth/connections/list";
1602
+ const listPath = "/sdk/oauth/grants/list";
1482
1603
  const listBodyStr = JSON.stringify(listBody);
1483
1604
  const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
1484
1605
  try {
@@ -1500,19 +1621,19 @@ ${contentHash}`;
1500
1621
  );
1501
1622
  }
1502
1623
  throw new AlterSDKError(
1503
- `Failed to list connections: ${error instanceof Error ? error.message : String(error)}`
1624
+ `Failed to list grants: ${error instanceof Error ? error.message : String(error)}`
1504
1625
  );
1505
1626
  }
1506
1627
  this.#cacheActorIdFromResponse(response);
1507
1628
  await this.#handleErrorResponse(response);
1508
1629
  const data = await response.json();
1509
- const connections = data.connections.map(
1510
- (c) => new ConnectionInfo(
1511
- c
1630
+ const grants = data.grants.map(
1631
+ (g) => new GrantInfo(
1632
+ g
1512
1633
  )
1513
1634
  );
1514
- return new ConnectionListResult({
1515
- connections,
1635
+ return new GrantListResult({
1636
+ grants,
1516
1637
  total: data.total,
1517
1638
  limit: data.limit,
1518
1639
  offset: data.offset,
@@ -1538,10 +1659,10 @@ ${contentHash}`;
1538
1659
  allowed_origin: options.allowedOrigin ?? null,
1539
1660
  metadata: options.metadata ?? null
1540
1661
  };
1541
- if (options.connectionPolicy) {
1542
- sessionBody.connection_policy = {
1543
- max_ttl_seconds: options.connectionPolicy.maxTtlSeconds ?? null,
1544
- default_ttl_seconds: options.connectionPolicy.defaultTtlSeconds ?? null
1662
+ if (options.grantPolicy) {
1663
+ sessionBody.grant_policy = {
1664
+ max_ttl_seconds: options.grantPolicy.maxTtlSeconds ?? null,
1665
+ default_ttl_seconds: options.grantPolicy.defaultTtlSeconds ?? null
1545
1666
  };
1546
1667
  }
1547
1668
  if (this.#userTokenGetter) {
@@ -1608,7 +1729,7 @@ ${contentHash}`;
1608
1729
  const openBrowser = options.openBrowser ?? true;
1609
1730
  const session = await this.createConnectSession({
1610
1731
  allowedProviders: options.providers,
1611
- connectionPolicy: options.connectionPolicy
1732
+ grantPolicy: options.grantPolicy
1612
1733
  });
1613
1734
  if (openBrowser) {
1614
1735
  try {
@@ -1639,14 +1760,14 @@ ${contentHash}`;
1639
1760
  const pollResult = await this.#pollSession(session.sessionToken);
1640
1761
  const pollStatus = pollResult.status;
1641
1762
  if (pollStatus === "completed") {
1642
- const connectionsData = pollResult.connections ?? [];
1643
- return connectionsData.map(
1644
- (conn) => new ConnectResult({
1645
- connection_id: conn.connection_id ?? "",
1646
- provider_id: conn.provider_id ?? "",
1647
- account_identifier: conn.account_identifier ?? null,
1648
- scopes: conn.scopes ?? [],
1649
- connection_policy: conn.connection_policy ?? null
1763
+ const grantsData = pollResult.grants ?? [];
1764
+ return grantsData.map(
1765
+ (grant) => new ConnectResult({
1766
+ grant_id: grant.grant_id ?? "",
1767
+ provider_id: grant.provider_id ?? "",
1768
+ account_identifier: grant.account_identifier ?? null,
1769
+ scopes: grant.scopes ?? [],
1770
+ grant_policy: grant.grant_policy ?? null
1650
1771
  })
1651
1772
  );
1652
1773
  }
@@ -1673,12 +1794,133 @@ ${contentHash}`;
1673
1794
  "Connect session expired before OAuth was completed"
1674
1795
  );
1675
1796
  }
1797
+ if (pollStatus !== "pending") {
1798
+ throw new ConnectFlowError(
1799
+ `Unexpected poll status from server: ${pollStatus}`,
1800
+ { status: pollStatus }
1801
+ );
1802
+ }
1676
1803
  }
1677
1804
  throw new ConnectTimeoutError(
1678
1805
  `OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
1679
1806
  { timeout }
1680
1807
  );
1681
1808
  }
1809
+ /**
1810
+ * Trigger IDP login for end user via browser.
1811
+ *
1812
+ * Opens the app's configured IDP login page in the user's default browser.
1813
+ * Polls for completion and returns the user's IDP JWT token.
1814
+ *
1815
+ * After authenticate() completes, the returned userToken is automatically
1816
+ * used for subsequent request() calls that use identity resolution.
1817
+ *
1818
+ * @param options - Optional configuration (timeout in milliseconds, default 300000 = 5 min)
1819
+ * @returns AuthResult with userToken and userInfo
1820
+ * @throws ConnectTimeoutError if authentication times out
1821
+ * @throws AlterSDKError if session creation or authentication fails
1822
+ */
1823
+ async authenticate(options) {
1824
+ if (this.#closed) {
1825
+ throw new AlterSDKError(
1826
+ "SDK instance has been closed. Create a new AlterVault instance to make requests."
1827
+ );
1828
+ }
1829
+ const timeoutMs = options?.timeout ?? 3e5;
1830
+ const actorHeaders = this.#getActorRequestHeaders();
1831
+ const sessionPath = "/sdk/auth/session";
1832
+ const sessionBodyStr = JSON.stringify({});
1833
+ const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, sessionBodyStr);
1834
+ let sessionResp;
1835
+ try {
1836
+ sessionResp = await this.#alterClient.post(sessionPath, {
1837
+ body: sessionBodyStr,
1838
+ headers: { ...actorHeaders, ...sessionHmac, "Content-Type": "application/json" }
1839
+ });
1840
+ } catch (error) {
1841
+ if (_AlterVault.#isTimeoutOrAbortError(error)) {
1842
+ throw new TimeoutError(
1843
+ `Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
1844
+ { base_url: this.baseUrl }
1845
+ );
1846
+ }
1847
+ if (error instanceof TypeError) {
1848
+ throw new NetworkError(
1849
+ `Failed to connect to Alter Vault backend: ${error.message}`,
1850
+ { base_url: this.baseUrl }
1851
+ );
1852
+ }
1853
+ throw new AlterSDKError(
1854
+ `Failed to create auth session: ${error instanceof Error ? error.message : String(error)}`
1855
+ );
1856
+ }
1857
+ this.#cacheActorIdFromResponse(sessionResp);
1858
+ await this.#handleErrorResponse(sessionResp);
1859
+ const sessionData = await sessionResp.json();
1860
+ try {
1861
+ const openModule = await import("open");
1862
+ const openFn = openModule.default;
1863
+ if (openFn) {
1864
+ await openFn(sessionData.auth_url);
1865
+ } else {
1866
+ console.log(
1867
+ `Open this URL to authenticate: ${sessionData.auth_url}`
1868
+ );
1869
+ }
1870
+ } catch {
1871
+ console.log(
1872
+ `Open this URL to authenticate: ${sessionData.auth_url}`
1873
+ );
1874
+ }
1875
+ const startTime = Date.now();
1876
+ while (true) {
1877
+ if (Date.now() - startTime > timeoutMs) {
1878
+ throw new ConnectTimeoutError(
1879
+ "Authentication timed out",
1880
+ { timeout: timeoutMs }
1881
+ );
1882
+ }
1883
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
1884
+ const pollPath = "/sdk/auth/poll";
1885
+ const pollBodyStr = JSON.stringify({ session_token: sessionData.session_token });
1886
+ const pollHmac = this.#computeHmacHeaders("POST", pollPath, pollBodyStr);
1887
+ let pollResp;
1888
+ try {
1889
+ pollResp = await this.#alterClient.post(pollPath, {
1890
+ body: pollBodyStr,
1891
+ headers: { ...actorHeaders, ...pollHmac, "Content-Type": "application/json" }
1892
+ });
1893
+ } catch {
1894
+ continue;
1895
+ }
1896
+ this.#cacheActorIdFromResponse(pollResp);
1897
+ if (!pollResp.ok) {
1898
+ continue;
1899
+ }
1900
+ const pollData = await pollResp.json();
1901
+ if (pollData.status === "completed") {
1902
+ const userToken = pollData.user_token;
1903
+ if (!userToken) {
1904
+ throw new AlterSDKError(
1905
+ "Authentication completed but no user token was returned by the IDP"
1906
+ );
1907
+ }
1908
+ this.#userTokenGetter = () => userToken;
1909
+ return new AuthResult({
1910
+ user_token: userToken,
1911
+ user_info: pollData.user_info
1912
+ });
1913
+ }
1914
+ if (pollData.status === "error") {
1915
+ throw new AlterSDKError(
1916
+ pollData.error_message ?? "Authentication failed"
1917
+ );
1918
+ }
1919
+ if (pollData.status === "expired") {
1920
+ throw new AlterSDKError("Authentication session expired");
1921
+ }
1922
+ }
1923
+ }
1682
1924
  /**
1683
1925
  * Poll the Connect session for completion status (INTERNAL).
1684
1926
  *
@@ -1829,6 +2071,7 @@ export {
1829
2071
  ActorType,
1830
2072
  AlterSDKError,
1831
2073
  AlterVault,
2074
+ AuthResult,
1832
2075
  BackendError,
1833
2076
  ConnectConfigError,
1834
2077
  ConnectDeniedError,
@@ -1836,12 +2079,13 @@ export {
1836
2079
  ConnectResult,
1837
2080
  ConnectSession,
1838
2081
  ConnectTimeoutError,
1839
- ConnectionDeletedError,
1840
- ConnectionExpiredError,
1841
- ConnectionInfo,
1842
- ConnectionListResult,
1843
- ConnectionNotFoundError,
1844
- ConnectionRevokedError,
2082
+ CredentialRevokedError,
2083
+ GrantDeletedError,
2084
+ GrantExpiredError,
2085
+ GrantInfo,
2086
+ GrantListResult,
2087
+ GrantNotFoundError,
2088
+ GrantRevokedError,
1845
2089
  HttpMethod,
1846
2090
  NetworkError,
1847
2091
  PolicyViolationError,