@usearete/sdk 0.26.0 → 0.28.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
@@ -4,6 +4,97 @@ var pako = require('pako');
4
4
  var buffer = require('buffer');
5
5
 
6
6
  const DEFAULT_MAX_ENTRIES_PER_VIEW = 10000;
7
+ const API_PROBLEM_SCHEMA_VERSION = 1;
8
+ const CLAIM_AGENT_MATERIALIZER_PATH = '/api/agents/me/claim-links';
9
+ function isRecord$1(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ function isSafeUnsignedInteger(value) {
13
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
14
+ }
15
+ function parseUsageLimit(value) {
16
+ if (!isRecord$1(value)
17
+ || typeof value['unit'] !== 'string'
18
+ || !isSafeUnsignedInteger(value['used'])
19
+ || !isSafeUnsignedInteger(value['limit'])
20
+ || (value['resetsAt'] !== undefined
21
+ && value['resetsAt'] !== null
22
+ && typeof value['resetsAt'] !== 'string')) {
23
+ return undefined;
24
+ }
25
+ return {
26
+ unit: value['unit'],
27
+ used: value['used'],
28
+ limit: value['limit'],
29
+ ...(value['resetsAt'] !== undefined ? { resetsAt: value['resetsAt'] } : {}),
30
+ };
31
+ }
32
+ function parseRecoveryAction(value) {
33
+ if (!isRecord$1(value) || typeof value['type'] !== 'string') {
34
+ return undefined;
35
+ }
36
+ if ((value['label'] !== undefined && typeof value['label'] !== 'string')
37
+ || (value['method'] !== undefined && typeof value['method'] !== 'string')
38
+ || (value['path'] !== undefined && typeof value['path'] !== 'string')) {
39
+ return undefined;
40
+ }
41
+ return value;
42
+ }
43
+ function isClaimAgentRecoveryAction(action) {
44
+ return action?.type === 'claim_agent'
45
+ && action.method === 'POST'
46
+ && action.path === CLAIM_AGENT_MATERIALIZER_PATH;
47
+ }
48
+ function parseApiProblem(value) {
49
+ if (!isRecord$1(value) || typeof value['error'] !== 'string') {
50
+ return undefined;
51
+ }
52
+ if (value['schemaVersion'] !== undefined
53
+ && (!isSafeUnsignedInteger(value['schemaVersion']) || value['schemaVersion'] < 1)) {
54
+ return undefined;
55
+ }
56
+ if ((value['code'] !== undefined && typeof value['code'] !== 'string')
57
+ || (value['retryable'] !== undefined && typeof value['retryable'] !== 'boolean')
58
+ || (value['requestId'] !== undefined && typeof value['requestId'] !== 'string')
59
+ || (value['retryAfterSeconds'] !== undefined
60
+ && value['retryAfterSeconds'] !== null
61
+ && !isSafeUnsignedInteger(value['retryAfterSeconds']))) {
62
+ return undefined;
63
+ }
64
+ const usage = value['usage'] === undefined ? undefined : parseUsageLimit(value['usage']);
65
+ const action = value['action'] === undefined ? undefined : parseRecoveryAction(value['action']);
66
+ if ((value['usage'] !== undefined && !usage) || (value['action'] !== undefined && !action)) {
67
+ return undefined;
68
+ }
69
+ return {
70
+ ...value,
71
+ error: value['error'],
72
+ ...(usage ? { usage } : {}),
73
+ ...(action ? { action } : {}),
74
+ };
75
+ }
76
+ function isSafeClaimActionUrl(action, expectedOrigin) {
77
+ if (action.type !== 'claim_agent')
78
+ return false;
79
+ try {
80
+ const url = new URL(action.url);
81
+ const origin = new URL(expectedOrigin);
82
+ const loopback = url.hostname === 'localhost'
83
+ || url.hostname === '127.0.0.1'
84
+ || url.hostname === '[::1]';
85
+ const secure = url.protocol === 'https:' || (url.protocol === 'http:' && loopback);
86
+ return secure
87
+ && url.origin === origin.origin
88
+ && url.username === ''
89
+ && url.password === ''
90
+ && url.pathname === '/claim'
91
+ && url.search === ''
92
+ && url.hash.length > 1;
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
7
98
  const DEFAULT_CONFIG = {
8
99
  autoReconnect: true,
9
100
  reconnectIntervals: [1000, 2000, 4000, 8000, 16000],
@@ -45,6 +136,17 @@ class AreteError extends Error {
45
136
  this.details = details;
46
137
  this.name = 'AreteError';
47
138
  }
139
+ apiProblem() {
140
+ if (!isRecord$1(this.details))
141
+ return undefined;
142
+ return parseApiProblem(this.details['apiProblem']);
143
+ }
144
+ recoveryAction() {
145
+ return this.apiProblem()?.action;
146
+ }
147
+ retryAfter() {
148
+ return this.apiProblem()?.retryAfterSeconds ?? undefined;
149
+ }
48
150
  }
49
151
  /**
50
152
  * Parse a kebab-case error code string (from X-Error-Code header) to AuthErrorCode.
@@ -109,6 +211,7 @@ const AUTH_ERROR_CODES_BY_WIRE = {
109
211
  'secret-key-required': 'SECRET_KEY_REQUIRED',
110
212
  'deployment-access-denied': 'DEPLOYMENT_ACCESS_DENIED',
111
213
  'quota-exceeded': 'QUOTA_EXCEEDED',
214
+ 'agent-claim-required': 'AGENT_CLAIM_REQUIRED',
112
215
  'stack-version-retired': 'STACK_VERSION_RETIRED',
113
216
  'stack-version-unknown': 'STACK_VERSION_UNKNOWN',
114
217
  };
@@ -1187,9 +1290,11 @@ class ConnectionManager {
1187
1290
  if (!response.ok) {
1188
1291
  const rawError = await response.text();
1189
1292
  let parsedError;
1293
+ let parsedValue;
1190
1294
  if (rawError) {
1191
1295
  try {
1192
- parsedError = JSON.parse(rawError);
1296
+ parsedValue = JSON.parse(rawError);
1297
+ parsedError = parsedValue;
1193
1298
  }
1194
1299
  catch {
1195
1300
  parsedError = undefined;
@@ -1205,19 +1310,32 @@ class ConnectionManager {
1205
1310
  const errorMessage = typeof parsedError?.error === 'string' && parsedError.error.length > 0
1206
1311
  ? parsedError.error
1207
1312
  : rawError || response.statusText || 'Authentication request failed';
1313
+ const retryAfterHeader = response.headers.get('Retry-After');
1314
+ const retryAfterSeconds = retryAfterHeader && /^\d+$/.test(retryAfterHeader)
1315
+ ? Number(retryAfterHeader)
1316
+ : undefined;
1317
+ const parsedProblem = parseApiProblem(parsedValue);
1318
+ const apiProblem = parsedProblem
1319
+ ? {
1320
+ ...parsedProblem,
1321
+ ...(retryAfterSeconds !== undefined ? { retryAfterSeconds } : {}),
1322
+ }
1323
+ : undefined;
1208
1324
  if (isStackVersionRefusalCode(errorCode)) {
1209
1325
  const refusal = parseStackVersionRefusal(parsedError);
1210
1326
  throw new AreteError(describeStackVersionRefusal(`Token endpoint returned ${response.status}: ${errorMessage}`, refusal), errorCode, {
1211
1327
  status: response.status,
1212
1328
  wireErrorCode,
1213
1329
  responseBody: rawError || null,
1330
+ apiProblem,
1214
1331
  ...refusal,
1215
1332
  });
1216
1333
  }
1217
1334
  throw new AreteError(`Token endpoint returned ${response.status}: ${errorMessage}`, errorCode, {
1218
1335
  status: response.status,
1219
1336
  wireErrorCode,
1220
- responseBody: rawError || null,
1337
+ apiProblem,
1338
+ retryAfterSeconds,
1221
1339
  });
1222
1340
  }
1223
1341
  const data = (await response.json());
@@ -1478,6 +1596,8 @@ class ConnectionManager {
1478
1596
  code: parseWireErrorCode(message.code),
1479
1597
  retryable: message.retryable ?? false,
1480
1598
  retryAfter: message.retryAfter ?? message.retry_after,
1599
+ usage: message.usage,
1600
+ action: message.action,
1481
1601
  suggestedAction: message.suggestedAction ?? message.suggested_action,
1482
1602
  docsUrl: message.docsUrl ?? message.docs_url,
1483
1603
  fatal: message.fatal,
@@ -1839,7 +1959,12 @@ class ConnectionManager {
1839
1959
  * with the `reconnecting` state and kept for the terminal error if the
1840
1960
  * attempts run out, so a server's close reason is never dropped.
1841
1961
  */
1842
- handleReconnect(reason) {
1962
+ handleReconnect(error) {
1963
+ const reason = typeof error === 'string'
1964
+ ? error
1965
+ : error instanceof Error
1966
+ ? error.message
1967
+ : undefined;
1843
1968
  if (reason !== undefined) {
1844
1969
  this.lastDisconnectReason = reason;
1845
1970
  }
@@ -1847,6 +1972,13 @@ class ConnectionManager {
1847
1972
  this.updateState('error', 'Automatic reconnection is disabled');
1848
1973
  return;
1849
1974
  }
1975
+ if (error instanceof AreteError) {
1976
+ const problem = error.apiProblem();
1977
+ if (problem?.action || problem?.retryable === false) {
1978
+ this.updateState('error', error.message);
1979
+ return;
1980
+ }
1981
+ }
1850
1982
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1851
1983
  const lastReason = this.lastDisconnectReason;
1852
1984
  this.updateState('error', `Max reconnection attempts (${this.reconnectAttempts}) reached`
@@ -1855,14 +1987,18 @@ class ConnectionManager {
1855
1987
  }
1856
1988
  this.updateState('reconnecting', this.lastDisconnectReason);
1857
1989
  const attemptIndex = Math.min(this.reconnectAttempts, this.reconnectIntervals.length - 1);
1858
- const delay = this.reconnectIntervals[attemptIndex] ?? 1000;
1990
+ const configuredDelay = this.reconnectIntervals[attemptIndex] ?? 1000;
1991
+ const serverDelay = error instanceof AreteError
1992
+ ? (error.retryAfter() ?? 0) * 1000
1993
+ : 0;
1994
+ const delay = Math.max(configuredDelay, serverDelay);
1859
1995
  this.reconnectAttempts++;
1860
1996
  this.reconnectTimeout = setTimeout(() => {
1861
- this.connect(true).catch((error) => {
1997
+ this.connect(true).catch((connectError) => {
1862
1998
  // Once a socket exists, its close event owns the next retry. Token
1863
1999
  // acquisition and socket construction can fail before that point.
1864
2000
  if (this.ws === null && this.currentState !== 'disconnected') {
1865
- this.recoverFromFailedConnect(error);
2001
+ this.recoverFromFailedConnect(connectError);
1866
2002
  }
1867
2003
  });
1868
2004
  }, delay);
@@ -1873,7 +2009,7 @@ class ConnectionManager {
1873
2009
  // connect() already reported the refusal as the terminal error state.
1874
2010
  return;
1875
2011
  }
1876
- this.handleReconnect(error instanceof Error ? error.message : undefined);
2012
+ this.handleReconnect(error);
1877
2013
  }
1878
2014
  clearReconnectTimeout() {
1879
2015
  if (this.reconnectTimeout) {
@@ -8663,9 +8799,11 @@ function stringifyBigints(value) {
8663
8799
 
8664
8800
  const Arete = Object.assign(Arete$1, { session: createSession });
8665
8801
 
8802
+ exports.API_PROBLEM_SCHEMA_VERSION = API_PROBLEM_SCHEMA_VERSION;
8666
8803
  exports.ASSOCIATED_TOKEN_PROGRAM_ADDRESS = ASSOCIATED_TOKEN_PROGRAM_ADDRESS;
8667
8804
  exports.Arete = Arete;
8668
8805
  exports.AreteError = AreteError;
8806
+ exports.CLAIM_AGENT_MATERIALIZER_PATH = CLAIM_AGENT_MATERIALIZER_PATH;
8669
8807
  exports.ConnectionManager = ConnectionManager;
8670
8808
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
8671
8809
  exports.DEFAULT_INITIAL_DATA_TIMEOUT_MS = DEFAULT_INITIAL_DATA_TIMEOUT_MS;
@@ -8751,10 +8889,12 @@ exports.getTransactionFailureOutcome = getTransactionFailureOutcome;
8751
8889
  exports.hostedWebsocketSuffixes = hostedWebsocketSuffixes;
8752
8890
  exports.inspectPreparedOperation = inspectPreparedOperation;
8753
8891
  exports.instructionOperation = instructionOperation;
8892
+ exports.isClaimAgentRecoveryAction = isClaimAgentRecoveryAction;
8754
8893
  exports.isEntityFrame = isEntityFrame;
8755
8894
  exports.isErrorFrame = isErrorFrame;
8756
8895
  exports.isHostedAreteEndpoint = isHostedAreteEndpoint;
8757
8896
  exports.isReplayErrorCode = isReplayErrorCode;
8897
+ exports.isSafeClaimActionUrl = isSafeClaimActionUrl;
8758
8898
  exports.isSameProgramSdk = isSameProgramSdk;
8759
8899
  exports.isSnapshotFrame = isSnapshotFrame;
8760
8900
  exports.isStackVersionRefusalCode = isStackVersionRefusalCode;
@@ -8766,6 +8906,7 @@ exports.normalizeSubscription = normalizeSubscription;
8766
8906
  exports.normalizeSubscriptionQuery = normalizeSubscriptionQuery;
8767
8907
  exports.normalizeSubscriptionRequest = normalizeSubscriptionRequest;
8768
8908
  exports.normalizeTransactionError = normalizeTransactionError;
8909
+ exports.parseApiProblem = parseApiProblem;
8769
8910
  exports.parseFrame = parseFrame;
8770
8911
  exports.parseFrameFromBlob = parseFrameFromBlob;
8771
8912
  exports.parseInstructionError = parseInstructionError;