@alter-ai/alter-sdk 0.2.2 → 0.4.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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,15 +17,27 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  APICallAuditLog: () => APICallAuditLog,
34
+ ActorType: () => ActorType,
24
35
  AlterSDKError: () => AlterSDKError,
25
36
  AlterVault: () => AlterVault,
37
+ ConnectFlowError: () => ConnectFlowError,
38
+ ConnectResult: () => ConnectResult,
26
39
  ConnectSession: () => ConnectSession,
40
+ ConnectTimeoutError: () => ConnectTimeoutError,
27
41
  ConnectionInfo: () => ConnectionInfo,
28
42
  ConnectionListResult: () => ConnectionListResult,
29
43
  ConnectionNotFoundError: () => ConnectionNotFoundError,
@@ -39,6 +53,9 @@ __export(index_exports, {
39
53
  });
40
54
  module.exports = __toCommonJS(index_exports);
41
55
 
56
+ // src/client.ts
57
+ var import_node_crypto2 = require("crypto");
58
+
42
59
  // src/exceptions.ts
43
60
  var AlterSDKError = class extends Error {
44
61
  details;
@@ -83,6 +100,18 @@ var TokenExpiredError = class extends TokenRetrievalError {
83
100
  this.connectionId = connectionId;
84
101
  }
85
102
  };
103
+ var ConnectFlowError = class extends AlterSDKError {
104
+ constructor(message, details) {
105
+ super(message, details);
106
+ this.name = "ConnectFlowError";
107
+ }
108
+ };
109
+ var ConnectTimeoutError = class extends ConnectFlowError {
110
+ constructor(message, details) {
111
+ super(message, details);
112
+ this.name = "ConnectTimeoutError";
113
+ }
114
+ };
86
115
  var ProviderAPIError = class extends AlterSDKError {
87
116
  statusCode;
88
117
  responseBody;
@@ -107,6 +136,12 @@ var TimeoutError = class extends NetworkError {
107
136
  };
108
137
 
109
138
  // src/models.ts
139
+ var ActorType = /* @__PURE__ */ ((ActorType2) => {
140
+ ActorType2["BACKEND_SERVICE"] = "backend_service";
141
+ ActorType2["AI_AGENT"] = "ai_agent";
142
+ ActorType2["MCP_SERVER"] = "mcp_server";
143
+ return ActorType2;
144
+ })(ActorType || {});
110
145
  var TokenResponse = class _TokenResponse {
111
146
  /** Token type (usually "Bearer") */
112
147
  tokenType;
@@ -118,12 +153,29 @@ var TokenResponse = class _TokenResponse {
118
153
  scopes;
119
154
  /** Connection ID that provided this token */
120
155
  connectionId;
156
+ /** Provider ID (google, github, etc.) */
157
+ providerId;
158
+ /** HTTP header name for credential injection (e.g., "Authorization", "X-API-Key") */
159
+ injectionHeader;
160
+ /** Header value format with {token} placeholder (e.g., "Bearer {token}", "{token}") */
161
+ injectionFormat;
162
+ /** Extra credentials for multi-part auth (e.g. AWS SigV4 secret_key, region) */
163
+ additionalCredentials;
121
164
  constructor(data) {
122
165
  this.tokenType = data.token_type ?? "Bearer";
123
166
  this.expiresIn = data.expires_in ?? null;
124
167
  this.expiresAt = data.expires_at ? _TokenResponse.parseExpiresAt(data.expires_at) : null;
125
168
  this.scopes = data.scopes ?? [];
126
169
  this.connectionId = data.connection_id;
170
+ this.providerId = data.provider_id ?? "";
171
+ this.injectionHeader = data.injection_header ?? "Authorization";
172
+ this.injectionFormat = data.injection_format ?? "Bearer {token}";
173
+ if (data.additional_credentials) {
174
+ const { secret_key: _, ...safeCredentials } = data.additional_credentials;
175
+ this.additionalCredentials = Object.keys(safeCredentials).length > 0 ? safeCredentials : null;
176
+ } else {
177
+ this.additionalCredentials = null;
178
+ }
127
179
  Object.freeze(this);
128
180
  }
129
181
  /**
@@ -188,7 +240,6 @@ var TokenResponse = class _TokenResponse {
188
240
  var ConnectionInfo = class {
189
241
  id;
190
242
  providerId;
191
- attributes;
192
243
  scopes;
193
244
  accountIdentifier;
194
245
  accountDisplayName;
@@ -199,7 +250,6 @@ var ConnectionInfo = class {
199
250
  constructor(data) {
200
251
  this.id = data.id;
201
252
  this.providerId = data.provider_id;
202
- this.attributes = data.attributes ?? {};
203
253
  this.scopes = data.scopes ?? [];
204
254
  this.accountIdentifier = data.account_identifier ?? null;
205
255
  this.accountDisplayName = data.account_display_name ?? null;
@@ -213,7 +263,6 @@ var ConnectionInfo = class {
213
263
  return {
214
264
  id: this.id,
215
265
  provider_id: this.providerId,
216
- attributes: this.attributes,
217
266
  scopes: this.scopes,
218
267
  account_identifier: this.accountIdentifier,
219
268
  account_display_name: this.accountDisplayName,
@@ -266,12 +315,38 @@ var ConnectionListResult = class {
266
315
  Object.freeze(this);
267
316
  }
268
317
  };
318
+ var ConnectResult = class {
319
+ connectionId;
320
+ providerId;
321
+ accountIdentifier;
322
+ scopes;
323
+ constructor(data) {
324
+ this.connectionId = data.connection_id;
325
+ this.providerId = data.provider_id;
326
+ this.accountIdentifier = data.account_identifier ?? null;
327
+ this.scopes = data.scopes ?? [];
328
+ Object.freeze(this);
329
+ }
330
+ toJSON() {
331
+ return {
332
+ connection_id: this.connectionId,
333
+ provider_id: this.providerId,
334
+ account_identifier: this.accountIdentifier,
335
+ scopes: this.scopes
336
+ };
337
+ }
338
+ toString() {
339
+ return `ConnectResult(connection_id=${this.connectionId}, provider=${this.providerId})`;
340
+ }
341
+ };
269
342
  var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
270
343
  "authorization",
271
344
  "cookie",
272
345
  "set-cookie",
273
346
  "x-api-key",
274
- "x-auth-token"
347
+ "x-auth-token",
348
+ "x-amz-date",
349
+ "x-amz-content-sha256"
275
350
  ]);
276
351
  var APICallAuditLog = class {
277
352
  connectionId;
@@ -345,11 +420,171 @@ var APICallAuditLog = class {
345
420
  }
346
421
  };
347
422
 
423
+ // src/aws-sig-v4.ts
424
+ var import_node_crypto = require("crypto");
425
+ var ALGORITHM = "AWS4-HMAC-SHA256";
426
+ var AWS_HOST_RE = /^(?<service>[a-z0-9-]+)\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
427
+ var S3_VIRTUAL_HOST_RE = /^[^.]+\.s3\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
428
+ function hmacSha256(key, message) {
429
+ return (0, import_node_crypto.createHmac)("sha256", key).update(message, "utf8").digest();
430
+ }
431
+ function sha256Hex(data) {
432
+ const hash = (0, import_node_crypto.createHash)("sha256");
433
+ if (typeof data === "string") {
434
+ hash.update(data, "utf8");
435
+ } else {
436
+ hash.update(data);
437
+ }
438
+ return hash.digest("hex");
439
+ }
440
+ function detectServiceAndRegion(hostname) {
441
+ const lower = hostname.toLowerCase();
442
+ const m = AWS_HOST_RE.exec(lower);
443
+ if (m?.groups) {
444
+ return { service: m.groups.service, region: m.groups.region };
445
+ }
446
+ const s3m = S3_VIRTUAL_HOST_RE.exec(lower);
447
+ if (s3m?.groups) {
448
+ return { service: "s3", region: s3m.groups.region };
449
+ }
450
+ return { service: null, region: null };
451
+ }
452
+ function deriveSigningKey(secretKey, dateStamp, region, service) {
453
+ const kDate = hmacSha256(Buffer.from("AWS4" + secretKey, "utf8"), dateStamp);
454
+ const kRegion = hmacSha256(kDate, region);
455
+ const kService = hmacSha256(kRegion, service);
456
+ const kSigning = hmacSha256(kService, "aws4_request");
457
+ return kSigning;
458
+ }
459
+ function canonicalUri(path) {
460
+ if (!path) return "/";
461
+ if (!path.startsWith("/")) path = "/" + path;
462
+ const segments = path.split("/");
463
+ return segments.map(
464
+ (seg) => encodeURIComponent(seg).replace(
465
+ /[!'()*]/g,
466
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
467
+ )
468
+ ).join("/");
469
+ }
470
+ function canonicalQueryString(query) {
471
+ if (!query) return "";
472
+ const sorted = [];
473
+ for (const pair of query.split("&")) {
474
+ const eqIdx = pair.indexOf("=");
475
+ if (eqIdx === -1) {
476
+ sorted.push([decodeURIComponent(pair), ""]);
477
+ } else {
478
+ sorted.push([
479
+ decodeURIComponent(pair.slice(0, eqIdx)),
480
+ decodeURIComponent(pair.slice(eqIdx + 1))
481
+ ]);
482
+ }
483
+ }
484
+ sorted.sort((a, b) => {
485
+ if (a[0] < b[0]) return -1;
486
+ if (a[0] > b[0]) return 1;
487
+ if (a[1] < b[1]) return -1;
488
+ if (a[1] > b[1]) return 1;
489
+ return 0;
490
+ });
491
+ const sigv4Encode = (s) => encodeURIComponent(s).replace(
492
+ /[!'()*]/g,
493
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
494
+ );
495
+ return sorted.map(([k, v]) => `${sigv4Encode(k)}=${sigv4Encode(v)}`).join("&");
496
+ }
497
+ function canonicalHeadersAndSigned(headers) {
498
+ const canonical = {};
499
+ for (const [name, value] of Object.entries(headers)) {
500
+ const lowerName = name.toLowerCase().trim();
501
+ const trimmedValue = value.replace(/\s+/g, " ").trim();
502
+ canonical[lowerName] = trimmedValue;
503
+ }
504
+ const sortedNames = Object.keys(canonical).sort();
505
+ const canonicalStr = sortedNames.map((name) => `${name}:${canonical[name]}
506
+ `).join("");
507
+ const signedStr = sortedNames.join(";");
508
+ return { canonicalHeaders: canonicalStr, signedHeaders: signedStr };
509
+ }
510
+ function signAwsRequest(opts) {
511
+ const parsed = new URL(opts.url);
512
+ const hostname = parsed.hostname;
513
+ let { region, service } = opts;
514
+ if (region == null || service == null) {
515
+ const detected = detectServiceAndRegion(hostname);
516
+ if (region == null) region = detected.region;
517
+ if (service == null) service = detected.service;
518
+ }
519
+ if (!region) {
520
+ throw new Error(
521
+ `Cannot determine AWS region from URL '${opts.url}'. Pass region explicitly via additional_credentials.`
522
+ );
523
+ }
524
+ if (!service) {
525
+ throw new Error(
526
+ `Cannot determine AWS service from URL '${opts.url}'. Pass service explicitly via additional_credentials.`
527
+ );
528
+ }
529
+ const timestamp = opts.timestamp ?? /* @__PURE__ */ new Date();
530
+ const amzDate = timestamp.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
531
+ const dateStamp = amzDate.slice(0, 8);
532
+ const bodyBytes = opts.body != null ? typeof opts.body === "string" ? Buffer.from(opts.body, "utf8") : opts.body : Buffer.alloc(0);
533
+ const payloadHash = sha256Hex(bodyBytes);
534
+ const headersToSign = {};
535
+ const hasHost = Object.keys(opts.headers).some(
536
+ (k) => k.toLowerCase() === "host"
537
+ );
538
+ if (!hasHost) {
539
+ const port = parsed.port;
540
+ headersToSign["host"] = port && port !== "80" && port !== "443" ? `${hostname}:${port}` : hostname;
541
+ } else {
542
+ for (const [k, v] of Object.entries(opts.headers)) {
543
+ if (k.toLowerCase() === "host") {
544
+ headersToSign["host"] = v;
545
+ break;
546
+ }
547
+ }
548
+ }
549
+ headersToSign["x-amz-date"] = amzDate;
550
+ headersToSign["x-amz-content-sha256"] = payloadHash;
551
+ const canonicalUriStr = canonicalUri(parsed.pathname);
552
+ const canonicalQs = canonicalQueryString(parsed.search.replace(/^\?/, ""));
553
+ const { canonicalHeaders, signedHeaders } = canonicalHeadersAndSigned(headersToSign);
554
+ const canonicalRequest = [
555
+ opts.method.toUpperCase(),
556
+ canonicalUriStr,
557
+ canonicalQs,
558
+ canonicalHeaders,
559
+ signedHeaders,
560
+ payloadHash
561
+ ].join("\n");
562
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
563
+ const stringToSign = [
564
+ ALGORITHM,
565
+ amzDate,
566
+ credentialScope,
567
+ sha256Hex(canonicalRequest)
568
+ ].join("\n");
569
+ const signingKey = deriveSigningKey(opts.secretKey, dateStamp, region, service);
570
+ const signature = (0, import_node_crypto.createHmac)("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
571
+ const authorization = `${ALGORITHM} Credential=${opts.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
572
+ return {
573
+ Authorization: authorization,
574
+ "x-amz-date": amzDate,
575
+ "x-amz-content-sha256": payloadHash
576
+ };
577
+ }
578
+
348
579
  // src/client.ts
349
580
  var _tokenStore = /* @__PURE__ */ new WeakMap();
581
+ var _additionalCredsStore = /* @__PURE__ */ new WeakMap();
350
582
  function _storeAccessToken(token, accessToken) {
351
583
  _tokenStore.set(token, accessToken);
352
584
  }
585
+ function _storeAdditionalCredentials(token, creds) {
586
+ _additionalCredsStore.set(token, creds);
587
+ }
353
588
  function _extractAccessToken(token) {
354
589
  const value = _tokenStore.get(token);
355
590
  if (value === void 0) {
@@ -359,10 +594,12 @@ function _extractAccessToken(token) {
359
594
  }
360
595
  return value;
361
596
  }
597
+ function _extractAdditionalCredentials(token) {
598
+ return _additionalCredsStore.get(token);
599
+ }
362
600
  var _fetch;
363
- var SDK_VERSION = "0.2.2";
601
+ var SDK_VERSION = "0.4.0";
364
602
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
365
- var VALID_ACTOR_TYPES = ["ai_agent", "mcp_server"];
366
603
  var HTTP_FORBIDDEN = 403;
367
604
  var HTTP_NOT_FOUND = 404;
368
605
  var HTTP_BAD_REQUEST = 400;
@@ -418,7 +655,9 @@ var HttpClient = class {
418
655
  headers: mergedHeaders,
419
656
  signal: controller.signal
420
657
  };
421
- if (options?.json !== void 0) {
658
+ if (options?.body !== void 0) {
659
+ init.body = options.body;
660
+ } else if (options?.json !== void 0) {
422
661
  init.body = JSON.stringify(options.json);
423
662
  mergedHeaders["Content-Type"] = "application/json";
424
663
  }
@@ -442,6 +681,8 @@ var AlterVault = class _AlterVault {
442
681
  // SECURITY LAYER 4: ES2022 private fields — truly private at runtime.
443
682
  // These are NOT accessible via (obj as any), Object.keys(), or prototype.
444
683
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
684
+ /** HMAC signing key (derived from API key using AWS SigV4 pattern, raw bytes) */
685
+ #hmacKey;
445
686
  /** HTTP Client for Alter Backend (has x-api-key) */
446
687
  #alterClient;
447
688
  /** HTTP Client for External Provider APIs (NO x-api-key) */
@@ -479,10 +720,8 @@ var AlterVault = class _AlterVault {
479
720
  for (const [name, value] of actorStrings) {
480
721
  _AlterVault.#validateActorString(value, name);
481
722
  }
482
- this.baseUrl = (options.baseUrl ?? "https://api.alter.com").replace(
483
- /\/+$/,
484
- ""
485
- );
723
+ this.#hmacKey = (0, import_node_crypto2.createHmac)("sha256", options.apiKey).update("alter-signing-v1").digest();
724
+ this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterai.dev").replace(/\/+$/, "");
486
725
  const timeoutMs = options.timeout ?? 3e4;
487
726
  this.#actorType = options.actorType;
488
727
  this.#actorIdentifier = options.actorIdentifier;
@@ -535,14 +774,20 @@ var AlterVault = class _AlterVault {
535
774
  if (!apiKey.startsWith("alter_key_")) {
536
775
  throw new AlterSDKError("api_key must start with 'alter_key_'");
537
776
  }
538
- if (actorType && !VALID_ACTOR_TYPES.includes(actorType)) {
539
- throw new AlterSDKError("actor_type must be 'ai_agent' or 'mcp_server'");
777
+ if (!actorType) {
778
+ throw new AlterSDKError(
779
+ "actor_type is required (use ActorType.AI_AGENT, ActorType.MCP_SERVER, or ActorType.BACKEND_SERVICE)"
780
+ );
540
781
  }
541
- if (actorType && !actorIdentifier) {
782
+ const validValues = Object.values(ActorType);
783
+ if (!validValues.includes(String(actorType))) {
542
784
  throw new AlterSDKError(
543
- "actor_identifier is required when actor_type is set"
785
+ `actor_type must be one of ${JSON.stringify(validValues.sort())}, got '${String(actorType)}'`
544
786
  );
545
787
  }
788
+ if (!actorIdentifier) {
789
+ throw new AlterSDKError("actor_identifier is required");
790
+ }
546
791
  }
547
792
  /**
548
793
  * Build default headers for the Alter backend HTTP client.
@@ -569,6 +814,28 @@ var AlterVault = class _AlterVault {
569
814
  }
570
815
  return headers;
571
816
  }
817
+ /**
818
+ * Compute HMAC-SHA256 signature headers for an Alter backend request.
819
+ *
820
+ * String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nCONTENT_HASH
821
+ *
822
+ * The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
823
+ * Currently all SDK→backend calls are POSTs without query params, so the path is clean.
824
+ */
825
+ #computeHmacHeaders(method, path, body) {
826
+ const timestamp = String(Math.floor(Date.now() / 1e3));
827
+ const contentHash = (0, import_node_crypto2.createHash)("sha256").update(body ?? "").digest("hex");
828
+ const stringToSign = `${method.toUpperCase()}
829
+ ${path}
830
+ ${timestamp}
831
+ ${contentHash}`;
832
+ const signature = (0, import_node_crypto2.createHmac)("sha256", this.#hmacKey).update(stringToSign).digest("hex");
833
+ return {
834
+ "X-Alter-Timestamp": timestamp,
835
+ "X-Alter-Content-SHA256": contentHash,
836
+ "X-Alter-Signature": signature
837
+ };
838
+ }
572
839
  /**
573
840
  * Build per-request actor headers for instance tracking.
574
841
  */
@@ -665,7 +932,7 @@ var AlterVault = class _AlterVault {
665
932
  if (response.status === HTTP_NOT_FOUND) {
666
933
  const errorData = await _AlterVault.#safeParseJson(response);
667
934
  throw new ConnectionNotFoundError(
668
- errorData.message ?? "OAuth connection not found for these attributes",
935
+ errorData.message ?? "OAuth connection not found for the given connection_id",
669
936
  errorData
670
937
  );
671
938
  }
@@ -711,22 +978,24 @@ var AlterVault = class _AlterVault {
711
978
  * This is a private method. Tokens are NEVER exposed to developers.
712
979
  * Use request() instead, which handles tokens internally.
713
980
  */
714
- async #getToken(providerId, attributes, reason, requestMetadata, runId, threadId, toolCallId) {
981
+ async #getToken(connectionId, reason, requestMetadata, runId, threadId, toolCallId) {
715
982
  const actorHeaders = this.#getActorRequestHeaders(
716
983
  runId,
717
984
  threadId,
718
985
  toolCallId
719
986
  );
720
987
  let response;
988
+ const tokenBody = {
989
+ connection_id: connectionId,
990
+ reason: reason ?? null,
991
+ request: requestMetadata ?? null
992
+ };
993
+ const tokenPath = "/sdk/token";
994
+ const hmacHeaders = this.#computeHmacHeaders("POST", tokenPath, JSON.stringify(tokenBody));
721
995
  try {
722
- response = await this.#alterClient.post("/oauth/token", {
723
- json: {
724
- provider_id: providerId,
725
- attributes,
726
- reason: reason ?? null,
727
- request: requestMetadata ?? null
728
- },
729
- headers: actorHeaders
996
+ response = await this.#alterClient.post(tokenPath, {
997
+ json: tokenBody,
998
+ headers: { ...actorHeaders, ...hmacHeaders }
730
999
  });
731
1000
  } catch (error) {
732
1001
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -743,7 +1012,7 @@ var AlterVault = class _AlterVault {
743
1012
  }
744
1013
  throw new TokenRetrievalError(
745
1014
  `Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
746
- { provider_id: providerId, error: String(error) }
1015
+ { connection_id: connectionId, error: String(error) }
747
1016
  );
748
1017
  }
749
1018
  this.#cacheActorIdFromResponse(response);
@@ -751,7 +1020,22 @@ var AlterVault = class _AlterVault {
751
1020
  const tokenData = await response.json();
752
1021
  const typedData = tokenData;
753
1022
  const tokenResponse = new TokenResponse(typedData);
1023
+ if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
1024
+ throw new TokenRetrievalError(
1025
+ `Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
1026
+ { connectionId: String(connectionId) }
1027
+ );
1028
+ }
1029
+ if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
1030
+ throw new TokenRetrievalError(
1031
+ `Backend returned invalid injection_format (contains control characters)`,
1032
+ { connectionId: String(connectionId) }
1033
+ );
1034
+ }
754
1035
  _storeAccessToken(tokenResponse, typedData.access_token);
1036
+ if (typedData.additional_credentials) {
1037
+ _storeAdditionalCredentials(tokenResponse, typedData.additional_credentials);
1038
+ }
755
1039
  return tokenResponse;
756
1040
  }
757
1041
  /**
@@ -779,10 +1063,13 @@ var AlterVault = class _AlterVault {
779
1063
  toolCallId: params.toolCallId
780
1064
  });
781
1065
  const sanitized = auditLog.sanitize();
782
- const actorHeaders = this.#getActorRequestHeaders();
783
- const response = await this.#alterClient.post("/oauth/audit/api-call", {
784
- json: sanitized,
785
- headers: actorHeaders
1066
+ const actorHeaders = this.#getActorRequestHeaders(params.runId);
1067
+ const auditPath = "/sdk/oauth/audit/api-call";
1068
+ const auditBody = sanitized;
1069
+ const auditHmac = this.#computeHmacHeaders("POST", auditPath, JSON.stringify(auditBody));
1070
+ const response = await this.#alterClient.post(auditPath, {
1071
+ json: auditBody,
1072
+ headers: { ...actorHeaders, ...auditHmac }
786
1073
  });
787
1074
  this.#cacheActorIdFromResponse(response);
788
1075
  if (!response.ok) {
@@ -848,13 +1135,13 @@ var AlterVault = class _AlterVault {
848
1135
  * 4. Logs the call for audit (fire-and-forget)
849
1136
  * 5. Returns the raw response
850
1137
  */
851
- async request(provider, method, url, options) {
1138
+ async request(connectionId, method, url, options) {
852
1139
  if (this.#closed) {
853
1140
  throw new AlterSDKError(
854
1141
  "SDK instance has been closed. Create a new AlterVault instance to make requests."
855
1142
  );
856
1143
  }
857
- const providerStr = String(provider);
1144
+ const runId = options?.runId ?? (0, import_node_crypto2.randomUUID)();
858
1145
  const methodStr = String(method).toUpperCase();
859
1146
  const urlLower = url.toLowerCase();
860
1147
  if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
@@ -862,7 +1149,7 @@ var AlterVault = class _AlterVault {
862
1149
  `URL must start with https:// or http://, got: ${url.slice(0, 50)}`
863
1150
  );
864
1151
  }
865
- if (options.pathParams && Object.keys(options.pathParams).length > 0) {
1152
+ if (options?.pathParams && Object.keys(options.pathParams).length > 0) {
866
1153
  const encodedParams = {};
867
1154
  for (const [key, value] of Object.entries(options.pathParams)) {
868
1155
  encodedParams[key] = encodeURIComponent(String(value));
@@ -892,39 +1179,80 @@ var AlterVault = class _AlterVault {
892
1179
  );
893
1180
  }
894
1181
  }
895
- if (options.extraHeaders && "Authorization" in options.extraHeaders) {
896
- console.warn(
897
- "extraHeaders contains 'Authorization' which will be overwritten with the auto-injected Bearer token"
898
- );
899
- }
900
1182
  const tokenResponse = await this.#getToken(
901
- providerStr,
902
- options.user,
903
- options.reason,
1183
+ connectionId,
1184
+ options?.reason,
904
1185
  { method: methodStr, url },
905
- options.runId,
906
- options.threadId,
907
- options.toolCallId
1186
+ runId,
1187
+ options?.threadId,
1188
+ options?.toolCallId
908
1189
  );
909
- const requestHeaders = options.extraHeaders ? { ...options.extraHeaders } : {};
910
- requestHeaders["Authorization"] = `Bearer ${_extractAccessToken(tokenResponse)}`;
1190
+ const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
1191
+ const accessToken = _extractAccessToken(tokenResponse);
1192
+ const injectionHeaderLower = tokenResponse.injectionHeader.toLowerCase();
1193
+ const additionalCreds = _extractAdditionalCredentials(tokenResponse);
1194
+ const isSigV4 = tokenResponse.injectionFormat.startsWith("AWS4-HMAC-SHA256") && additionalCreds != null;
1195
+ let sigv4BodyStr = null;
1196
+ if (isSigV4) {
1197
+ const accessKeyId = accessToken;
1198
+ if (options?.json != null) {
1199
+ sigv4BodyStr = JSON.stringify(options.json);
1200
+ if (!requestHeaders["Content-Type"]) {
1201
+ requestHeaders["Content-Type"] = "application/json";
1202
+ }
1203
+ }
1204
+ if (!additionalCreds.secret_key) {
1205
+ throw new TokenRetrievalError(
1206
+ "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 }
1208
+ );
1209
+ }
1210
+ const awsHeaders = signAwsRequest({
1211
+ method: methodStr,
1212
+ url,
1213
+ headers: requestHeaders,
1214
+ body: sigv4BodyStr,
1215
+ accessKeyId,
1216
+ secretKey: additionalCreds.secret_key,
1217
+ region: additionalCreds.region ?? null,
1218
+ service: additionalCreds.service ?? null
1219
+ });
1220
+ Object.assign(requestHeaders, awsHeaders);
1221
+ } else {
1222
+ if (options?.extraHeaders && Object.keys(options.extraHeaders).some(
1223
+ (k) => k.toLowerCase() === injectionHeaderLower
1224
+ )) {
1225
+ console.warn(
1226
+ `extraHeaders contains '${tokenResponse.injectionHeader}' which will be overwritten with the auto-injected credential`
1227
+ );
1228
+ }
1229
+ requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
1230
+ }
911
1231
  if (!requestHeaders["User-Agent"]) {
912
1232
  requestHeaders["User-Agent"] = SDK_USER_AGENT;
913
1233
  }
914
1234
  const startTime = Date.now();
915
1235
  let response;
916
1236
  try {
917
- response = await this.#providerClient.request(methodStr, url, {
918
- json: options.json,
919
- headers: requestHeaders,
920
- params: options.queryParams
921
- });
1237
+ if (isSigV4 && sigv4BodyStr != null) {
1238
+ response = await this.#providerClient.request(methodStr, url, {
1239
+ body: sigv4BodyStr,
1240
+ headers: requestHeaders,
1241
+ params: options?.queryParams
1242
+ });
1243
+ } else {
1244
+ response = await this.#providerClient.request(methodStr, url, {
1245
+ json: options?.json,
1246
+ headers: requestHeaders,
1247
+ params: options?.queryParams
1248
+ });
1249
+ }
922
1250
  } catch (error) {
923
1251
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
924
1252
  throw new TimeoutError(
925
1253
  `Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
926
1254
  {
927
- provider: providerStr,
1255
+ connection_id: connectionId,
928
1256
  method: methodStr,
929
1257
  url
930
1258
  }
@@ -933,7 +1261,7 @@ var AlterVault = class _AlterVault {
933
1261
  throw new NetworkError(
934
1262
  `Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
935
1263
  {
936
- provider: providerStr,
1264
+ connection_id: connectionId,
937
1265
  method: methodStr,
938
1266
  url,
939
1267
  error: String(error)
@@ -941,9 +1269,11 @@ var AlterVault = class _AlterVault {
941
1269
  );
942
1270
  }
943
1271
  const latencyMs = Date.now() - startTime;
1272
+ const sigv4Sensitive = /* @__PURE__ */ new Set(["authorization", "x-amz-date", "x-amz-content-sha256"]);
1273
+ const stripHeaders = isSigV4 ? sigv4Sensitive : /* @__PURE__ */ new Set([injectionHeaderLower]);
944
1274
  const auditHeaders = {};
945
1275
  for (const [key, value] of Object.entries(requestHeaders)) {
946
- if (key.toLowerCase() !== "authorization") {
1276
+ if (!stripHeaders.has(key.toLowerCase())) {
947
1277
  auditHeaders[key] = value;
948
1278
  }
949
1279
  }
@@ -954,19 +1284,19 @@ var AlterVault = class _AlterVault {
954
1284
  });
955
1285
  this.#scheduleAuditLog({
956
1286
  connectionId: tokenResponse.connectionId,
957
- providerId: providerStr,
1287
+ providerId: tokenResponse.providerId || connectionId,
958
1288
  method: methodStr,
959
1289
  url,
960
1290
  requestHeaders: auditHeaders,
961
- requestBody: options.json ?? null,
1291
+ requestBody: options?.json ?? null,
962
1292
  responseStatus: response.status,
963
1293
  responseHeaders,
964
1294
  responseBody,
965
1295
  latencyMs,
966
- reason: options.reason ?? null,
967
- runId: options.runId ?? null,
968
- threadId: options.threadId ?? null,
969
- toolCallId: options.toolCallId ?? null
1296
+ reason: options?.reason ?? null,
1297
+ runId,
1298
+ threadId: options?.threadId ?? null,
1299
+ toolCallId: options?.toolCallId ?? null
970
1300
  });
971
1301
  if (response.status >= HTTP_CLIENT_ERROR_START) {
972
1302
  throw new ProviderAPIError(
@@ -974,7 +1304,7 @@ var AlterVault = class _AlterVault {
974
1304
  response.status,
975
1305
  responseBody,
976
1306
  {
977
- provider: providerStr,
1307
+ connection_id: connectionId,
978
1308
  method: methodStr,
979
1309
  url
980
1310
  }
@@ -996,14 +1326,17 @@ var AlterVault = class _AlterVault {
996
1326
  }
997
1327
  const actorHeaders = this.#getActorRequestHeaders();
998
1328
  let response;
1329
+ const listBody = {
1330
+ provider_id: options?.providerId ?? null,
1331
+ limit: options?.limit ?? 100,
1332
+ offset: options?.offset ?? 0
1333
+ };
1334
+ const listPath = "/sdk/oauth/connections/list";
1335
+ const listHmac = this.#computeHmacHeaders("POST", listPath, JSON.stringify(listBody));
999
1336
  try {
1000
- response = await this.#alterClient.post("/oauth/connections/list", {
1001
- json: {
1002
- provider_id: options?.providerId ?? null,
1003
- limit: options?.limit ?? 100,
1004
- offset: options?.offset ?? 0
1005
- },
1006
- headers: actorHeaders
1337
+ response = await this.#alterClient.post(listPath, {
1338
+ json: listBody,
1339
+ headers: { ...actorHeaders, ...listHmac }
1007
1340
  });
1008
1341
  } catch (error) {
1009
1342
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1054,17 +1387,19 @@ var AlterVault = class _AlterVault {
1054
1387
  }
1055
1388
  const actorHeaders = this.#getActorRequestHeaders();
1056
1389
  let response;
1390
+ const sessionBody = {
1391
+ end_user: options.endUser,
1392
+ allowed_providers: options.allowedProviders ?? null,
1393
+ return_url: options.returnUrl ?? null,
1394
+ allowed_origin: options.allowedOrigin ?? null,
1395
+ metadata: options.metadata ?? null
1396
+ };
1397
+ const sessionPath = "/sdk/oauth/connect/session";
1398
+ const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, JSON.stringify(sessionBody));
1057
1399
  try {
1058
- response = await this.#alterClient.post("/oauth/connect/session", {
1059
- json: {
1060
- end_user: options.endUser,
1061
- attributes: options.attributes ?? null,
1062
- allowed_providers: options.allowedProviders ?? null,
1063
- return_url: options.returnUrl ?? null,
1064
- allowed_origin: options.allowedOrigin ?? null,
1065
- metadata: options.metadata ?? null
1066
- },
1067
- headers: actorHeaders
1400
+ response = await this.#alterClient.post(sessionPath, {
1401
+ json: sessionBody,
1402
+ headers: { ...actorHeaders, ...sessionHmac }
1068
1403
  });
1069
1404
  } catch (error) {
1070
1405
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
@@ -1088,6 +1423,132 @@ var AlterVault = class _AlterVault {
1088
1423
  const data = await response.json();
1089
1424
  return new ConnectSession(data);
1090
1425
  }
1426
+ /**
1427
+ * Open OAuth in the user's browser and wait for completion.
1428
+ *
1429
+ * This is the headless connect flow for CLI tools, scripts, and
1430
+ * server-side applications. It creates a Connect session, opens the
1431
+ * browser, and polls until the user completes OAuth.
1432
+ *
1433
+ * @param options - Connect options (endUser is required)
1434
+ * @returns Array of ConnectResult objects (one per connected provider)
1435
+ * @throws ConnectTimeoutError if the user doesn't complete within timeout
1436
+ * @throws ConnectFlowError if the user denies or provider returns error
1437
+ * @throws AlterSDKError if SDK is closed or session creation fails
1438
+ */
1439
+ async connect(options) {
1440
+ if (this.#closed) {
1441
+ throw new AlterSDKError(
1442
+ "SDK instance has been closed. Create a new AlterVault instance to make requests."
1443
+ );
1444
+ }
1445
+ const timeout = options.timeout ?? 300;
1446
+ const pollInterval = options.pollInterval ?? 2;
1447
+ const openBrowser = options.openBrowser ?? true;
1448
+ const session = await this.createConnectSession({
1449
+ endUser: options.endUser,
1450
+ allowedProviders: options.providers
1451
+ });
1452
+ if (openBrowser) {
1453
+ try {
1454
+ const openModule = await import("open");
1455
+ const openFn = openModule.default;
1456
+ if (openFn) {
1457
+ await openFn(session.connectUrl);
1458
+ } else {
1459
+ console.log(
1460
+ `Open this URL to authorize: ${session.connectUrl}`
1461
+ );
1462
+ }
1463
+ } catch {
1464
+ console.log(
1465
+ `Open this URL to authorize: ${session.connectUrl}`
1466
+ );
1467
+ }
1468
+ } else {
1469
+ console.log(
1470
+ `Open this URL to authorize: ${session.connectUrl}`
1471
+ );
1472
+ }
1473
+ const deadline = Date.now() + timeout * 1e3;
1474
+ while (Date.now() < deadline) {
1475
+ await new Promise(
1476
+ (resolve) => setTimeout(resolve, pollInterval * 1e3)
1477
+ );
1478
+ const pollResult = await this.#pollSession(session.sessionToken);
1479
+ const pollStatus = pollResult.status;
1480
+ if (pollStatus === "completed") {
1481
+ const connectionsData = pollResult.connections ?? [];
1482
+ return connectionsData.map(
1483
+ (conn) => new ConnectResult({
1484
+ connection_id: conn.connection_id ?? "",
1485
+ provider_id: conn.provider_id ?? "",
1486
+ account_identifier: conn.account_identifier ?? null,
1487
+ scopes: conn.scopes ?? []
1488
+ })
1489
+ );
1490
+ }
1491
+ if (pollStatus === "error") {
1492
+ 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
+ );
1499
+ }
1500
+ if (pollStatus === "expired") {
1501
+ throw new ConnectFlowError(
1502
+ "Connect session expired before OAuth was completed"
1503
+ );
1504
+ }
1505
+ }
1506
+ throw new ConnectTimeoutError(
1507
+ `OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
1508
+ { timeout }
1509
+ );
1510
+ }
1511
+ /**
1512
+ * Poll the Connect session for completion status (INTERNAL).
1513
+ *
1514
+ * Makes an HMAC-signed POST to the poll endpoint.
1515
+ */
1516
+ async #pollSession(sessionToken) {
1517
+ const actorHeaders = this.#getActorRequestHeaders();
1518
+ const pollPath = "/sdk/oauth/connect/session/poll";
1519
+ const pollBody = { session_token: sessionToken };
1520
+ const pollHmac = this.#computeHmacHeaders(
1521
+ "POST",
1522
+ pollPath,
1523
+ JSON.stringify(pollBody)
1524
+ );
1525
+ let response;
1526
+ try {
1527
+ response = await this.#alterClient.post(pollPath, {
1528
+ json: pollBody,
1529
+ headers: { ...actorHeaders, ...pollHmac }
1530
+ });
1531
+ } catch (error) {
1532
+ if (_AlterVault.#isTimeoutOrAbortError(error)) {
1533
+ throw new TimeoutError(
1534
+ `Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
1535
+ { base_url: this.baseUrl }
1536
+ );
1537
+ }
1538
+ if (error instanceof TypeError) {
1539
+ throw new NetworkError(
1540
+ `Failed to connect to Alter Vault backend: ${error.message}`,
1541
+ { base_url: this.baseUrl }
1542
+ );
1543
+ }
1544
+ throw new AlterSDKError(
1545
+ `Failed to poll connect session: ${error instanceof Error ? error.message : String(error)}`
1546
+ );
1547
+ }
1548
+ this.#cacheActorIdFromResponse(response);
1549
+ await this.#handleErrorResponse(response);
1550
+ return await response.json();
1551
+ }
1091
1552
  /**
1092
1553
  * Close HTTP clients and release resources.
1093
1554
  * Waits for any pending audit tasks before closing.
@@ -1116,8 +1577,6 @@ var Provider = /* @__PURE__ */ ((Provider2) => {
1116
1577
  Provider2["GOOGLE"] = "google";
1117
1578
  Provider2["GITHUB"] = "github";
1118
1579
  Provider2["SLACK"] = "slack";
1119
- Provider2["MICROSOFT"] = "microsoft";
1120
- Provider2["SALESFORCE"] = "salesforce";
1121
1580
  Provider2["SENTRY"] = "sentry";
1122
1581
  return Provider2;
1123
1582
  })(Provider || {});
@@ -1134,9 +1593,13 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
1134
1593
  // Annotate the CommonJS export names for ESM import in node:
1135
1594
  0 && (module.exports = {
1136
1595
  APICallAuditLog,
1596
+ ActorType,
1137
1597
  AlterSDKError,
1138
1598
  AlterVault,
1599
+ ConnectFlowError,
1600
+ ConnectResult,
1139
1601
  ConnectSession,
1602
+ ConnectTimeoutError,
1140
1603
  ConnectionInfo,
1141
1604
  ConnectionListResult,
1142
1605
  ConnectionNotFoundError,