@alter-ai/alter-sdk 0.3.1 → 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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/client.ts
2
- import { createHash, createHmac, randomUUID } from "crypto";
2
+ import { createHash as createHash2, createHmac as createHmac2, randomUUID } from "crypto";
3
3
 
4
4
  // src/exceptions.ts
5
5
  var AlterSDKError = class extends Error {
@@ -45,6 +45,18 @@ var TokenExpiredError = class extends TokenRetrievalError {
45
45
  this.connectionId = connectionId;
46
46
  }
47
47
  };
48
+ var ConnectFlowError = class extends AlterSDKError {
49
+ constructor(message, details) {
50
+ super(message, details);
51
+ this.name = "ConnectFlowError";
52
+ }
53
+ };
54
+ var ConnectTimeoutError = class extends ConnectFlowError {
55
+ constructor(message, details) {
56
+ super(message, details);
57
+ this.name = "ConnectTimeoutError";
58
+ }
59
+ };
48
60
  var ProviderAPIError = class extends AlterSDKError {
49
61
  statusCode;
50
62
  responseBody;
@@ -92,6 +104,8 @@ var TokenResponse = class _TokenResponse {
92
104
  injectionHeader;
93
105
  /** Header value format with {token} placeholder (e.g., "Bearer {token}", "{token}") */
94
106
  injectionFormat;
107
+ /** Extra credentials for multi-part auth (e.g. AWS SigV4 secret_key, region) */
108
+ additionalCredentials;
95
109
  constructor(data) {
96
110
  this.tokenType = data.token_type ?? "Bearer";
97
111
  this.expiresIn = data.expires_in ?? null;
@@ -101,6 +115,12 @@ var TokenResponse = class _TokenResponse {
101
115
  this.providerId = data.provider_id ?? "";
102
116
  this.injectionHeader = data.injection_header ?? "Authorization";
103
117
  this.injectionFormat = data.injection_format ?? "Bearer {token}";
118
+ if (data.additional_credentials) {
119
+ const { secret_key: _, ...safeCredentials } = data.additional_credentials;
120
+ this.additionalCredentials = Object.keys(safeCredentials).length > 0 ? safeCredentials : null;
121
+ } else {
122
+ this.additionalCredentials = null;
123
+ }
104
124
  Object.freeze(this);
105
125
  }
106
126
  /**
@@ -240,12 +260,38 @@ var ConnectionListResult = class {
240
260
  Object.freeze(this);
241
261
  }
242
262
  };
263
+ var ConnectResult = class {
264
+ connectionId;
265
+ providerId;
266
+ accountIdentifier;
267
+ scopes;
268
+ constructor(data) {
269
+ this.connectionId = data.connection_id;
270
+ this.providerId = data.provider_id;
271
+ this.accountIdentifier = data.account_identifier ?? null;
272
+ this.scopes = data.scopes ?? [];
273
+ Object.freeze(this);
274
+ }
275
+ toJSON() {
276
+ return {
277
+ connection_id: this.connectionId,
278
+ provider_id: this.providerId,
279
+ account_identifier: this.accountIdentifier,
280
+ scopes: this.scopes
281
+ };
282
+ }
283
+ toString() {
284
+ return `ConnectResult(connection_id=${this.connectionId}, provider=${this.providerId})`;
285
+ }
286
+ };
243
287
  var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
244
288
  "authorization",
245
289
  "cookie",
246
290
  "set-cookie",
247
291
  "x-api-key",
248
- "x-auth-token"
292
+ "x-auth-token",
293
+ "x-amz-date",
294
+ "x-amz-content-sha256"
249
295
  ]);
250
296
  var APICallAuditLog = class {
251
297
  connectionId;
@@ -319,11 +365,171 @@ var APICallAuditLog = class {
319
365
  }
320
366
  };
321
367
 
368
+ // src/aws-sig-v4.ts
369
+ import { createHash, createHmac } from "crypto";
370
+ var ALGORITHM = "AWS4-HMAC-SHA256";
371
+ var AWS_HOST_RE = /^(?<service>[a-z0-9-]+)\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
372
+ var S3_VIRTUAL_HOST_RE = /^[^.]+\.s3\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
373
+ function hmacSha256(key, message) {
374
+ return createHmac("sha256", key).update(message, "utf8").digest();
375
+ }
376
+ function sha256Hex(data) {
377
+ const hash = createHash("sha256");
378
+ if (typeof data === "string") {
379
+ hash.update(data, "utf8");
380
+ } else {
381
+ hash.update(data);
382
+ }
383
+ return hash.digest("hex");
384
+ }
385
+ function detectServiceAndRegion(hostname) {
386
+ const lower = hostname.toLowerCase();
387
+ const m = AWS_HOST_RE.exec(lower);
388
+ if (m?.groups) {
389
+ return { service: m.groups.service, region: m.groups.region };
390
+ }
391
+ const s3m = S3_VIRTUAL_HOST_RE.exec(lower);
392
+ if (s3m?.groups) {
393
+ return { service: "s3", region: s3m.groups.region };
394
+ }
395
+ return { service: null, region: null };
396
+ }
397
+ function deriveSigningKey(secretKey, dateStamp, region, service) {
398
+ const kDate = hmacSha256(Buffer.from("AWS4" + secretKey, "utf8"), dateStamp);
399
+ const kRegion = hmacSha256(kDate, region);
400
+ const kService = hmacSha256(kRegion, service);
401
+ const kSigning = hmacSha256(kService, "aws4_request");
402
+ return kSigning;
403
+ }
404
+ function canonicalUri(path) {
405
+ if (!path) return "/";
406
+ if (!path.startsWith("/")) path = "/" + path;
407
+ const segments = path.split("/");
408
+ return segments.map(
409
+ (seg) => encodeURIComponent(seg).replace(
410
+ /[!'()*]/g,
411
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
412
+ )
413
+ ).join("/");
414
+ }
415
+ function canonicalQueryString(query) {
416
+ if (!query) return "";
417
+ const sorted = [];
418
+ for (const pair of query.split("&")) {
419
+ const eqIdx = pair.indexOf("=");
420
+ if (eqIdx === -1) {
421
+ sorted.push([decodeURIComponent(pair), ""]);
422
+ } else {
423
+ sorted.push([
424
+ decodeURIComponent(pair.slice(0, eqIdx)),
425
+ decodeURIComponent(pair.slice(eqIdx + 1))
426
+ ]);
427
+ }
428
+ }
429
+ sorted.sort((a, b) => {
430
+ if (a[0] < b[0]) return -1;
431
+ if (a[0] > b[0]) return 1;
432
+ if (a[1] < b[1]) return -1;
433
+ if (a[1] > b[1]) return 1;
434
+ return 0;
435
+ });
436
+ const sigv4Encode = (s) => encodeURIComponent(s).replace(
437
+ /[!'()*]/g,
438
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
439
+ );
440
+ return sorted.map(([k, v]) => `${sigv4Encode(k)}=${sigv4Encode(v)}`).join("&");
441
+ }
442
+ function canonicalHeadersAndSigned(headers) {
443
+ const canonical = {};
444
+ for (const [name, value] of Object.entries(headers)) {
445
+ const lowerName = name.toLowerCase().trim();
446
+ const trimmedValue = value.replace(/\s+/g, " ").trim();
447
+ canonical[lowerName] = trimmedValue;
448
+ }
449
+ const sortedNames = Object.keys(canonical).sort();
450
+ const canonicalStr = sortedNames.map((name) => `${name}:${canonical[name]}
451
+ `).join("");
452
+ const signedStr = sortedNames.join(";");
453
+ return { canonicalHeaders: canonicalStr, signedHeaders: signedStr };
454
+ }
455
+ function signAwsRequest(opts) {
456
+ const parsed = new URL(opts.url);
457
+ const hostname = parsed.hostname;
458
+ let { region, service } = opts;
459
+ if (region == null || service == null) {
460
+ const detected = detectServiceAndRegion(hostname);
461
+ if (region == null) region = detected.region;
462
+ if (service == null) service = detected.service;
463
+ }
464
+ if (!region) {
465
+ throw new Error(
466
+ `Cannot determine AWS region from URL '${opts.url}'. Pass region explicitly via additional_credentials.`
467
+ );
468
+ }
469
+ if (!service) {
470
+ throw new Error(
471
+ `Cannot determine AWS service from URL '${opts.url}'. Pass service explicitly via additional_credentials.`
472
+ );
473
+ }
474
+ const timestamp = opts.timestamp ?? /* @__PURE__ */ new Date();
475
+ const amzDate = timestamp.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
476
+ const dateStamp = amzDate.slice(0, 8);
477
+ const bodyBytes = opts.body != null ? typeof opts.body === "string" ? Buffer.from(opts.body, "utf8") : opts.body : Buffer.alloc(0);
478
+ const payloadHash = sha256Hex(bodyBytes);
479
+ const headersToSign = {};
480
+ const hasHost = Object.keys(opts.headers).some(
481
+ (k) => k.toLowerCase() === "host"
482
+ );
483
+ if (!hasHost) {
484
+ const port = parsed.port;
485
+ headersToSign["host"] = port && port !== "80" && port !== "443" ? `${hostname}:${port}` : hostname;
486
+ } else {
487
+ for (const [k, v] of Object.entries(opts.headers)) {
488
+ if (k.toLowerCase() === "host") {
489
+ headersToSign["host"] = v;
490
+ break;
491
+ }
492
+ }
493
+ }
494
+ headersToSign["x-amz-date"] = amzDate;
495
+ headersToSign["x-amz-content-sha256"] = payloadHash;
496
+ const canonicalUriStr = canonicalUri(parsed.pathname);
497
+ const canonicalQs = canonicalQueryString(parsed.search.replace(/^\?/, ""));
498
+ const { canonicalHeaders, signedHeaders } = canonicalHeadersAndSigned(headersToSign);
499
+ const canonicalRequest = [
500
+ opts.method.toUpperCase(),
501
+ canonicalUriStr,
502
+ canonicalQs,
503
+ canonicalHeaders,
504
+ signedHeaders,
505
+ payloadHash
506
+ ].join("\n");
507
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
508
+ const stringToSign = [
509
+ ALGORITHM,
510
+ amzDate,
511
+ credentialScope,
512
+ sha256Hex(canonicalRequest)
513
+ ].join("\n");
514
+ const signingKey = deriveSigningKey(opts.secretKey, dateStamp, region, service);
515
+ const signature = createHmac("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
516
+ const authorization = `${ALGORITHM} Credential=${opts.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
517
+ return {
518
+ Authorization: authorization,
519
+ "x-amz-date": amzDate,
520
+ "x-amz-content-sha256": payloadHash
521
+ };
522
+ }
523
+
322
524
  // src/client.ts
323
525
  var _tokenStore = /* @__PURE__ */ new WeakMap();
526
+ var _additionalCredsStore = /* @__PURE__ */ new WeakMap();
324
527
  function _storeAccessToken(token, accessToken) {
325
528
  _tokenStore.set(token, accessToken);
326
529
  }
530
+ function _storeAdditionalCredentials(token, creds) {
531
+ _additionalCredsStore.set(token, creds);
532
+ }
327
533
  function _extractAccessToken(token) {
328
534
  const value = _tokenStore.get(token);
329
535
  if (value === void 0) {
@@ -333,8 +539,11 @@ function _extractAccessToken(token) {
333
539
  }
334
540
  return value;
335
541
  }
542
+ function _extractAdditionalCredentials(token) {
543
+ return _additionalCredsStore.get(token);
544
+ }
336
545
  var _fetch;
337
- var SDK_VERSION = "0.3.0";
546
+ var SDK_VERSION = "0.4.0";
338
547
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
339
548
  var HTTP_FORBIDDEN = 403;
340
549
  var HTTP_NOT_FOUND = 404;
@@ -391,7 +600,9 @@ var HttpClient = class {
391
600
  headers: mergedHeaders,
392
601
  signal: controller.signal
393
602
  };
394
- if (options?.json !== void 0) {
603
+ if (options?.body !== void 0) {
604
+ init.body = options.body;
605
+ } else if (options?.json !== void 0) {
395
606
  init.body = JSON.stringify(options.json);
396
607
  mergedHeaders["Content-Type"] = "application/json";
397
608
  }
@@ -454,7 +665,7 @@ var AlterVault = class _AlterVault {
454
665
  for (const [name, value] of actorStrings) {
455
666
  _AlterVault.#validateActorString(value, name);
456
667
  }
457
- this.#hmacKey = createHmac("sha256", options.apiKey).update("alter-signing-v1").digest();
668
+ this.#hmacKey = createHmac2("sha256", options.apiKey).update("alter-signing-v1").digest();
458
669
  this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterai.dev").replace(/\/+$/, "");
459
670
  const timeoutMs = options.timeout ?? 3e4;
460
671
  this.#actorType = options.actorType;
@@ -558,12 +769,12 @@ var AlterVault = class _AlterVault {
558
769
  */
559
770
  #computeHmacHeaders(method, path, body) {
560
771
  const timestamp = String(Math.floor(Date.now() / 1e3));
561
- const contentHash = createHash("sha256").update(body ?? "").digest("hex");
772
+ const contentHash = createHash2("sha256").update(body ?? "").digest("hex");
562
773
  const stringToSign = `${method.toUpperCase()}
563
774
  ${path}
564
775
  ${timestamp}
565
776
  ${contentHash}`;
566
- const signature = createHmac("sha256", this.#hmacKey).update(stringToSign).digest("hex");
777
+ const signature = createHmac2("sha256", this.#hmacKey).update(stringToSign).digest("hex");
567
778
  return {
568
779
  "X-Alter-Timestamp": timestamp,
569
780
  "X-Alter-Content-SHA256": contentHash,
@@ -767,6 +978,9 @@ ${contentHash}`;
767
978
  );
768
979
  }
769
980
  _storeAccessToken(tokenResponse, typedData.access_token);
981
+ if (typedData.additional_credentials) {
982
+ _storeAdditionalCredentials(tokenResponse, typedData.additional_credentials);
983
+ }
770
984
  return tokenResponse;
771
985
  }
772
986
  /**
@@ -918,28 +1132,66 @@ ${contentHash}`;
918
1132
  options?.threadId,
919
1133
  options?.toolCallId
920
1134
  );
921
- const injectionHeaderLower = tokenResponse.injectionHeader.toLowerCase();
922
- if (options?.extraHeaders && Object.keys(options.extraHeaders).some(
923
- (k) => k.toLowerCase() === injectionHeaderLower
924
- )) {
925
- console.warn(
926
- `extraHeaders contains '${tokenResponse.injectionHeader}' which will be overwritten with the auto-injected credential`
927
- );
928
- }
929
1135
  const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
930
1136
  const accessToken = _extractAccessToken(tokenResponse);
931
- requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
1137
+ const injectionHeaderLower = tokenResponse.injectionHeader.toLowerCase();
1138
+ const additionalCreds = _extractAdditionalCredentials(tokenResponse);
1139
+ const isSigV4 = tokenResponse.injectionFormat.startsWith("AWS4-HMAC-SHA256") && additionalCreds != null;
1140
+ let sigv4BodyStr = null;
1141
+ if (isSigV4) {
1142
+ const accessKeyId = accessToken;
1143
+ if (options?.json != null) {
1144
+ sigv4BodyStr = JSON.stringify(options.json);
1145
+ if (!requestHeaders["Content-Type"]) {
1146
+ requestHeaders["Content-Type"] = "application/json";
1147
+ }
1148
+ }
1149
+ if (!additionalCreds.secret_key) {
1150
+ throw new TokenRetrievalError(
1151
+ "AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
1152
+ { connection_id: connectionId }
1153
+ );
1154
+ }
1155
+ const awsHeaders = signAwsRequest({
1156
+ method: methodStr,
1157
+ url,
1158
+ headers: requestHeaders,
1159
+ body: sigv4BodyStr,
1160
+ accessKeyId,
1161
+ secretKey: additionalCreds.secret_key,
1162
+ region: additionalCreds.region ?? null,
1163
+ service: additionalCreds.service ?? null
1164
+ });
1165
+ Object.assign(requestHeaders, awsHeaders);
1166
+ } else {
1167
+ if (options?.extraHeaders && Object.keys(options.extraHeaders).some(
1168
+ (k) => k.toLowerCase() === injectionHeaderLower
1169
+ )) {
1170
+ console.warn(
1171
+ `extraHeaders contains '${tokenResponse.injectionHeader}' which will be overwritten with the auto-injected credential`
1172
+ );
1173
+ }
1174
+ requestHeaders[tokenResponse.injectionHeader] = tokenResponse.injectionFormat.replace("{token}", accessToken);
1175
+ }
932
1176
  if (!requestHeaders["User-Agent"]) {
933
1177
  requestHeaders["User-Agent"] = SDK_USER_AGENT;
934
1178
  }
935
1179
  const startTime = Date.now();
936
1180
  let response;
937
1181
  try {
938
- response = await this.#providerClient.request(methodStr, url, {
939
- json: options?.json,
940
- headers: requestHeaders,
941
- params: options?.queryParams
942
- });
1182
+ if (isSigV4 && sigv4BodyStr != null) {
1183
+ response = await this.#providerClient.request(methodStr, url, {
1184
+ body: sigv4BodyStr,
1185
+ headers: requestHeaders,
1186
+ params: options?.queryParams
1187
+ });
1188
+ } else {
1189
+ response = await this.#providerClient.request(methodStr, url, {
1190
+ json: options?.json,
1191
+ headers: requestHeaders,
1192
+ params: options?.queryParams
1193
+ });
1194
+ }
943
1195
  } catch (error) {
944
1196
  if (_AlterVault.#isTimeoutOrAbortError(error)) {
945
1197
  throw new TimeoutError(
@@ -962,9 +1214,11 @@ ${contentHash}`;
962
1214
  );
963
1215
  }
964
1216
  const latencyMs = Date.now() - startTime;
1217
+ const sigv4Sensitive = /* @__PURE__ */ new Set(["authorization", "x-amz-date", "x-amz-content-sha256"]);
1218
+ const stripHeaders = isSigV4 ? sigv4Sensitive : /* @__PURE__ */ new Set([injectionHeaderLower]);
965
1219
  const auditHeaders = {};
966
1220
  for (const [key, value] of Object.entries(requestHeaders)) {
967
- if (key.toLowerCase() !== injectionHeaderLower) {
1221
+ if (!stripHeaders.has(key.toLowerCase())) {
968
1222
  auditHeaders[key] = value;
969
1223
  }
970
1224
  }
@@ -1114,6 +1368,132 @@ ${contentHash}`;
1114
1368
  const data = await response.json();
1115
1369
  return new ConnectSession(data);
1116
1370
  }
1371
+ /**
1372
+ * Open OAuth in the user's browser and wait for completion.
1373
+ *
1374
+ * This is the headless connect flow for CLI tools, scripts, and
1375
+ * server-side applications. It creates a Connect session, opens the
1376
+ * browser, and polls until the user completes OAuth.
1377
+ *
1378
+ * @param options - Connect options (endUser is required)
1379
+ * @returns Array of ConnectResult objects (one per connected provider)
1380
+ * @throws ConnectTimeoutError if the user doesn't complete within timeout
1381
+ * @throws ConnectFlowError if the user denies or provider returns error
1382
+ * @throws AlterSDKError if SDK is closed or session creation fails
1383
+ */
1384
+ async connect(options) {
1385
+ if (this.#closed) {
1386
+ throw new AlterSDKError(
1387
+ "SDK instance has been closed. Create a new AlterVault instance to make requests."
1388
+ );
1389
+ }
1390
+ const timeout = options.timeout ?? 300;
1391
+ const pollInterval = options.pollInterval ?? 2;
1392
+ const openBrowser = options.openBrowser ?? true;
1393
+ const session = await this.createConnectSession({
1394
+ endUser: options.endUser,
1395
+ allowedProviders: options.providers
1396
+ });
1397
+ if (openBrowser) {
1398
+ try {
1399
+ const openModule = await import("open");
1400
+ const openFn = openModule.default;
1401
+ if (openFn) {
1402
+ await openFn(session.connectUrl);
1403
+ } else {
1404
+ console.log(
1405
+ `Open this URL to authorize: ${session.connectUrl}`
1406
+ );
1407
+ }
1408
+ } catch {
1409
+ console.log(
1410
+ `Open this URL to authorize: ${session.connectUrl}`
1411
+ );
1412
+ }
1413
+ } else {
1414
+ console.log(
1415
+ `Open this URL to authorize: ${session.connectUrl}`
1416
+ );
1417
+ }
1418
+ const deadline = Date.now() + timeout * 1e3;
1419
+ while (Date.now() < deadline) {
1420
+ await new Promise(
1421
+ (resolve) => setTimeout(resolve, pollInterval * 1e3)
1422
+ );
1423
+ const pollResult = await this.#pollSession(session.sessionToken);
1424
+ const pollStatus = pollResult.status;
1425
+ if (pollStatus === "completed") {
1426
+ const connectionsData = pollResult.connections ?? [];
1427
+ return connectionsData.map(
1428
+ (conn) => new ConnectResult({
1429
+ connection_id: conn.connection_id ?? "",
1430
+ provider_id: conn.provider_id ?? "",
1431
+ account_identifier: conn.account_identifier ?? null,
1432
+ scopes: conn.scopes ?? []
1433
+ })
1434
+ );
1435
+ }
1436
+ if (pollStatus === "error") {
1437
+ const err = pollResult.error;
1438
+ throw new ConnectFlowError(
1439
+ err?.error_message ?? "OAuth flow failed",
1440
+ {
1441
+ error_code: err?.error_code ?? "unknown_error"
1442
+ }
1443
+ );
1444
+ }
1445
+ if (pollStatus === "expired") {
1446
+ throw new ConnectFlowError(
1447
+ "Connect session expired before OAuth was completed"
1448
+ );
1449
+ }
1450
+ }
1451
+ throw new ConnectTimeoutError(
1452
+ `OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
1453
+ { timeout }
1454
+ );
1455
+ }
1456
+ /**
1457
+ * Poll the Connect session for completion status (INTERNAL).
1458
+ *
1459
+ * Makes an HMAC-signed POST to the poll endpoint.
1460
+ */
1461
+ async #pollSession(sessionToken) {
1462
+ const actorHeaders = this.#getActorRequestHeaders();
1463
+ const pollPath = "/sdk/oauth/connect/session/poll";
1464
+ const pollBody = { session_token: sessionToken };
1465
+ const pollHmac = this.#computeHmacHeaders(
1466
+ "POST",
1467
+ pollPath,
1468
+ JSON.stringify(pollBody)
1469
+ );
1470
+ let response;
1471
+ try {
1472
+ response = await this.#alterClient.post(pollPath, {
1473
+ json: pollBody,
1474
+ headers: { ...actorHeaders, ...pollHmac }
1475
+ });
1476
+ } catch (error) {
1477
+ if (_AlterVault.#isTimeoutOrAbortError(error)) {
1478
+ throw new TimeoutError(
1479
+ `Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
1480
+ { base_url: this.baseUrl }
1481
+ );
1482
+ }
1483
+ if (error instanceof TypeError) {
1484
+ throw new NetworkError(
1485
+ `Failed to connect to Alter Vault backend: ${error.message}`,
1486
+ { base_url: this.baseUrl }
1487
+ );
1488
+ }
1489
+ throw new AlterSDKError(
1490
+ `Failed to poll connect session: ${error instanceof Error ? error.message : String(error)}`
1491
+ );
1492
+ }
1493
+ this.#cacheActorIdFromResponse(response);
1494
+ await this.#handleErrorResponse(response);
1495
+ return await response.json();
1496
+ }
1117
1497
  /**
1118
1498
  * Close HTTP clients and release resources.
1119
1499
  * Waits for any pending audit tasks before closing.
@@ -1160,7 +1540,10 @@ export {
1160
1540
  ActorType,
1161
1541
  AlterSDKError,
1162
1542
  AlterVault,
1543
+ ConnectFlowError,
1544
+ ConnectResult,
1163
1545
  ConnectSession,
1546
+ ConnectTimeoutError,
1164
1547
  ConnectionInfo,
1165
1548
  ConnectionListResult,
1166
1549
  ConnectionNotFoundError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alter-ai/alter-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Official TypeScript SDK for Alter Vault — OAuth token management with policy enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",