@acosmi/sdk-ts 2.19.0 → 2.19.1

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.
@@ -149,6 +149,55 @@ var init_types = __esm({
149
149
  });
150
150
 
151
151
  // src/shared/errors.ts
152
+ function nullableID(value) {
153
+ return value === null || typeof value === "string";
154
+ }
155
+ function decodeGatewayErrorContract(value) {
156
+ if (value == null || typeof value !== "object") return null;
157
+ const v = value;
158
+ const version = v.errorContractVersion;
159
+ const domain = v.faultDomain;
160
+ const disposition = v.requestDisposition;
161
+ const errorCode = v.errorCode;
162
+ if (version !== 1 || typeof domain !== "string" || !gatewayFaultDomains.includes(domain)) return null;
163
+ if (typeof errorCode !== "string" || errorCode.length === 0) return null;
164
+ if (disposition !== "not_accepted" && disposition !== "accepted" && disposition !== "unknown") return null;
165
+ if (!nullableID(v.transportRequestId) || !nullableID(v.consumeRequestId) || !nullableID(v.providerRequestId)) return null;
166
+ if (typeof v.retryable !== "boolean") return null;
167
+ return {
168
+ errorContractVersion: 1,
169
+ faultDomain: domain,
170
+ errorCode,
171
+ transportRequestId: v.transportRequestId,
172
+ consumeRequestId: v.consumeRequestId,
173
+ providerRequestId: v.providerRequestId,
174
+ requestDisposition: disposition,
175
+ retryable: v.retryable
176
+ };
177
+ }
178
+ function readGatewayErrorContract(error) {
179
+ const direct = decodeGatewayErrorContract(error);
180
+ if (direct) return direct;
181
+ if (error == null || typeof error !== "object") return null;
182
+ const e = error;
183
+ const axios = decodeGatewayErrorContract(e.response?.data);
184
+ if (axios) return axios;
185
+ if (typeof e.body === "string") {
186
+ try {
187
+ return decodeGatewayErrorContract(JSON.parse(e.body));
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function isUserAccessTokenRejected(error) {
195
+ if (error == null || typeof error !== "object") return false;
196
+ const e = error;
197
+ const status = e.statusCode ?? e.response?.status;
198
+ const contract = readGatewayErrorContract(error);
199
+ return status === 401 && contract?.faultDomain === "user_auth" && contract.errorCode === "USER_ACCESS_TOKEN_INVALID" && contract.requestDisposition === "not_accepted";
200
+ }
152
201
  function isWindowLimitError(err) {
153
202
  if (!(err instanceof exports.HTTPError)) return false;
154
203
  if (err.errorCode === windowLimitErrorCode) return true;
@@ -176,7 +225,7 @@ function getWindowLimitStreamDetails(err) {
176
225
  windowOverridable: typeof e.windowOverridable === "boolean" ? e.windowOverridable : void 0
177
226
  };
178
227
  }
179
- exports.RateLimitError = void 0; exports.BusinessError = void 0; exports.OrderTerminalError = void 0; exports.ModelNotFoundError = void 0; exports.HTTPError = void 0; exports.NetworkError = void 0; exports.StreamError = void 0; var windowLimitErrorCode, windowLimitStreamCode;
228
+ exports.RateLimitError = void 0; exports.BusinessError = void 0; exports.OrderTerminalError = void 0; exports.ModelNotFoundError = void 0; exports.HTTPError = void 0; exports.NetworkError = void 0; exports.StreamError = void 0; var gatewayFaultDomains, windowLimitErrorCode, windowLimitStreamCode;
180
229
  var init_errors = __esm({
181
230
  "src/shared/errors.ts"() {
182
231
  exports.RateLimitError = class extends Error {
@@ -235,6 +284,13 @@ var init_errors = __esm({
235
284
  * false/缺失 = 周窗/显式禁止/灰度关 (硬等待, 无豁免路径, 老网关不返回时为 undefined)。
236
285
  */
237
286
  windowOverridable;
287
+ errorContractVersion;
288
+ faultDomain;
289
+ requestDisposition;
290
+ transportRequestId;
291
+ consumeRequestId;
292
+ providerRequestId;
293
+ retryable;
238
294
  constructor(statusCode, opts = {}) {
239
295
  let msg;
240
296
  if (opts.type) {
@@ -256,6 +312,13 @@ var init_errors = __esm({
256
312
  this.windowKind = opts.windowKind;
257
313
  this.windowResetAt = opts.windowResetAt;
258
314
  this.windowOverridable = opts.windowOverridable;
315
+ this.errorContractVersion = opts.errorContractVersion;
316
+ this.faultDomain = opts.faultDomain;
317
+ this.requestDisposition = opts.requestDisposition;
318
+ this.transportRequestId = opts.transportRequestId;
319
+ this.consumeRequestId = opts.consumeRequestId;
320
+ this.providerRequestId = opts.providerRequestId;
321
+ this.retryable = opts.retryable === true && opts.requestDisposition === "not_accepted";
259
322
  }
260
323
  };
261
324
  exports.NetworkError = class extends Error {
@@ -266,6 +329,7 @@ var init_errors = __esm({
266
329
  cause;
267
330
  timeout;
268
331
  eof;
332
+ requestDisposition = "unknown";
269
333
  constructor(op, url, cause, opts = {}) {
270
334
  const causeMsg = cause instanceof Error ? cause.message : cause != null ? String(cause) : "network error";
271
335
  super(`${op} ${url}: ${causeMsg}`);
@@ -286,6 +350,8 @@ var init_errors = __esm({
286
350
  exports.StreamError = class extends Error {
287
351
  /** 例: "empty_response" / "rate_limit" / "overloaded" / "" */
288
352
  code;
353
+ /** D23 gateway machine code; mirrors the legacy `code` field. */
354
+ errorCode;
289
355
  /** 例: "provider" / "settlement" */
290
356
  stage;
291
357
  /** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */
@@ -294,23 +360,47 @@ var init_errors = __esm({
294
360
  rawError;
295
361
  /** 客户端是否值得重试 */
296
362
  retryable;
363
+ errorContractVersion;
364
+ faultDomain;
365
+ requestDisposition;
366
+ transportRequestId;
367
+ consumeRequestId;
368
+ providerRequestId;
297
369
  constructor(opts = {}) {
298
370
  const code = opts.code ?? "";
299
371
  const stage = opts.stage ?? "";
300
372
  const userMessage = opts.message ?? "";
301
373
  const rawError = opts.rawError ?? "";
302
- const retryable = opts.retryable ?? false;
374
+ const retryable = opts.retryable === true && opts.requestDisposition === "not_accepted";
303
375
  const body = rawError !== "" ? rawError : userMessage;
304
376
  const msg = stage !== "" ? `stream failed: ${stage}: ${body}` : `stream failed: ${body}`;
305
377
  super(msg);
306
378
  this.name = "StreamError";
307
379
  this.code = code;
380
+ this.errorCode = code;
308
381
  this.stage = stage;
309
382
  this.userMessage = userMessage;
310
383
  this.rawError = rawError;
311
384
  this.retryable = retryable;
385
+ this.errorContractVersion = opts.errorContractVersion;
386
+ this.faultDomain = opts.faultDomain;
387
+ this.requestDisposition = opts.requestDisposition;
388
+ this.transportRequestId = opts.transportRequestId;
389
+ this.consumeRequestId = opts.consumeRequestId;
390
+ this.providerRequestId = opts.providerRequestId;
312
391
  }
313
392
  };
393
+ gatewayFaultDomains = [
394
+ "user_auth",
395
+ "caller_credentials",
396
+ "account_quota",
397
+ "account_permission",
398
+ "provider",
399
+ "gateway",
400
+ "transport",
401
+ "stream_ticket",
402
+ "protocol"
403
+ ];
314
404
  windowLimitErrorCode = "WINDOW_LIMIT_EXCEEDED";
315
405
  windowLimitStreamCode = "window_limit_exceeded";
316
406
  }
@@ -893,16 +983,90 @@ var init_openai = __esm({
893
983
  * 可能已被 text/tool 推进的 this.blockIndex (否则 content_block_stop 索引错配)。 */
894
984
  thinkingBlockIndex = 0;
895
985
  textStarted = false;
896
- /** OpenAI tool_call index → Anthropic block index */
986
+ /** OpenAI tool_call → Anthropic block index。键正常是 `tc.index`;上游省略
987
+ * index 时退化为 `id:<tool_call_id>`,两者都没有时沿用上一个键(见
988
+ * {@link resolveToolKey})。 */
897
989
  toolBlockIndex = /* @__PURE__ */ new Map();
898
990
  blockIndex = 0;
991
+ /** 每个 tool block 已发出的 `partial_json` 累积,用于识别「每片重发全量参数」
992
+ * 的上游(见 tool_calls 分支的累计判别)。 */
993
+ toolArgsAccum = /* @__PURE__ */ new Map();
994
+ /** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */
995
+ lastToolKey = null;
996
+ /** 已发出 message_delta/message_stop,避免 finish_reason 与 `[DONE]` 各收一次。 */
997
+ messageClosed = false;
998
+ /**
999
+ * 解析一条 tool_call delta 归属的块键。
1000
+ *
1001
+ * OpenAI 流式规范里 `index` 是必填,但兼容实现常有省略。此前这里直接用
1002
+ * `tc.index` 做 Map 键:两个都省略 index 的 tool_call 会共用键 `undefined`,
1003
+ * 于是只开一个块、两段参数拼进同一条 `partial_json` 流,产出 `{…}{…}` 这种
1004
+ * 必然非法的 JSON。这里按「index → id → 沿用上一个」三级降级,让至少一种
1005
+ * 稳定标识生效。
1006
+ */
1007
+ resolveToolKey(tc) {
1008
+ if (typeof tc.index === "number" && Number.isFinite(tc.index)) {
1009
+ this.lastToolKey = tc.index;
1010
+ return tc.index;
1011
+ }
1012
+ if (typeof tc.id === "string" && tc.id !== "") {
1013
+ const key = `id:${tc.id}`;
1014
+ this.lastToolKey = key;
1015
+ return key;
1016
+ }
1017
+ if (this.lastToolKey !== null) return this.lastToolKey;
1018
+ this.lastToolKey = 0;
1019
+ return 0;
1020
+ }
1021
+ /**
1022
+ * 关闭仍打开的 text / thinking / tool 块,并收口 message。
1023
+ *
1024
+ * 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发
1025
+ * `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个
1026
+ * 永不闭合的 tool_use 块。
1027
+ */
1028
+ closeOpenBlocks(events, stopReason) {
1029
+ if (this.messageClosed) return;
1030
+ this.messageClosed = true;
1031
+ if (this.textStarted) {
1032
+ events.push({
1033
+ event: "content_block_stop",
1034
+ data: JSON.stringify({ type: "content_block_stop", index: this.blockIndex })
1035
+ });
1036
+ this.textStarted = false;
1037
+ } else if (this.thinkingStarted && !this.thinkingStopped) {
1038
+ this.thinkingStopped = true;
1039
+ events.push({
1040
+ event: "content_block_stop",
1041
+ data: JSON.stringify({ type: "content_block_stop", index: this.thinkingBlockIndex })
1042
+ });
1043
+ }
1044
+ for (const idx of this.toolBlockIndex.values()) {
1045
+ events.push({
1046
+ event: "content_block_stop",
1047
+ data: JSON.stringify({ type: "content_block_stop", index: idx })
1048
+ });
1049
+ }
1050
+ events.push({
1051
+ event: "message_delta",
1052
+ data: JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason } })
1053
+ });
1054
+ events.push({
1055
+ event: "message_stop",
1056
+ data: JSON.stringify({ type: "message_stop" })
1057
+ });
1058
+ }
899
1059
  /**
900
1060
  * 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent
901
1061
  * 返回 { events, done }
902
1062
  */
903
1063
  convert(data) {
904
1064
  if (data === "[DONE]") {
905
- return { events: [], done: true };
1065
+ const events2 = [];
1066
+ if (this.messageStarted) {
1067
+ this.closeOpenBlocks(events2, "end_turn");
1068
+ }
1069
+ return { events: events2, done: true };
906
1070
  }
907
1071
  let chunk;
908
1072
  try {
@@ -974,7 +1138,9 @@ var init_openai = __esm({
974
1138
  events.push({ event: "content_block_delta", data: deltaJSON });
975
1139
  }
976
1140
  for (const tc of choice.delta.tool_calls ?? []) {
977
- if (!this.toolBlockIndex.has(tc.index)) {
1141
+ const fn = tc.function;
1142
+ const toolKey = this.resolveToolKey(tc);
1143
+ if (!this.toolBlockIndex.has(toolKey)) {
978
1144
  if (this.thinkingStarted && !this.thinkingStopped) {
979
1145
  this.thinkingStopped = true;
980
1146
  const stopJSON = JSON.stringify({
@@ -993,56 +1159,46 @@ var init_openai = __esm({
993
1159
  this.blockIndex++;
994
1160
  this.textStarted = false;
995
1161
  }
996
- this.toolBlockIndex.set(tc.index, this.blockIndex);
1162
+ this.toolBlockIndex.set(toolKey, this.blockIndex);
997
1163
  const blockJSON = JSON.stringify({
998
1164
  type: "content_block_start",
999
1165
  index: this.blockIndex,
1000
1166
  content_block: {
1001
1167
  type: "tool_use",
1002
1168
  id: tc.id,
1003
- name: tc.function.name,
1169
+ name: fn?.name,
1004
1170
  input: {}
1005
1171
  }
1006
1172
  });
1007
1173
  events.push({ event: "content_block_start", data: blockJSON });
1008
1174
  this.blockIndex++;
1009
1175
  }
1010
- if (tc.function.arguments && tc.function.arguments !== "") {
1011
- const idx = this.toolBlockIndex.get(tc.index);
1012
- const deltaJSON = JSON.stringify({
1013
- type: "content_block_delta",
1014
- index: idx,
1015
- delta: {
1016
- type: "input_json_delta",
1017
- partial_json: tc.function.arguments
1018
- }
1019
- });
1020
- events.push({ event: "content_block_delta", data: deltaJSON });
1176
+ if (fn?.arguments !== void 0 && fn.arguments !== null && fn.arguments !== "") {
1177
+ const rawArgs = typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments);
1178
+ const accum = this.toolArgsAccum.get(toolKey) ?? "";
1179
+ let emit = rawArgs;
1180
+ if (accum !== "" && rawArgs.length > accum.length && rawArgs.startsWith(accum)) {
1181
+ emit = rawArgs.slice(accum.length);
1182
+ this.toolArgsAccum.set(toolKey, rawArgs);
1183
+ } else {
1184
+ this.toolArgsAccum.set(toolKey, accum + rawArgs);
1185
+ }
1186
+ if (emit !== "") {
1187
+ const idx = this.toolBlockIndex.get(toolKey);
1188
+ const deltaJSON = JSON.stringify({
1189
+ type: "content_block_delta",
1190
+ index: idx,
1191
+ delta: {
1192
+ type: "input_json_delta",
1193
+ partial_json: emit
1194
+ }
1195
+ });
1196
+ events.push({ event: "content_block_delta", data: deltaJSON });
1197
+ }
1021
1198
  }
1022
1199
  }
1023
1200
  if (choice.finish_reason != null && choice.finish_reason !== "") {
1024
- if (this.textStarted) {
1025
- const stopJSON2 = JSON.stringify({
1026
- type: "content_block_stop",
1027
- index: this.blockIndex
1028
- });
1029
- events.push({ event: "content_block_stop", data: stopJSON2 });
1030
- } else if (this.thinkingStarted && !this.thinkingStopped) {
1031
- this.thinkingStopped = true;
1032
- const stopJSON2 = JSON.stringify({
1033
- type: "content_block_stop",
1034
- index: this.thinkingBlockIndex
1035
- });
1036
- events.push({ event: "content_block_stop", data: stopJSON2 });
1037
- }
1038
- for (const idx of this.toolBlockIndex.values()) {
1039
- const stopJSON2 = JSON.stringify({
1040
- type: "content_block_stop",
1041
- index: idx
1042
- });
1043
- events.push({ event: "content_block_stop", data: stopJSON2 });
1044
- }
1045
- let stopReason = "end_turn";
1201
+ let stopReason;
1046
1202
  switch (choice.finish_reason) {
1047
1203
  case "tool_calls":
1048
1204
  stopReason = "tool_use";
@@ -1050,14 +1206,13 @@ var init_openai = __esm({
1050
1206
  case "length":
1051
1207
  stopReason = "max_tokens";
1052
1208
  break;
1209
+ case "stop":
1210
+ stopReason = "end_turn";
1211
+ break;
1212
+ default:
1213
+ stopReason = choice.finish_reason;
1053
1214
  }
1054
- const deltaJSON = JSON.stringify({
1055
- type: "message_delta",
1056
- delta: { stop_reason: stopReason }
1057
- });
1058
- events.push({ event: "message_delta", data: deltaJSON });
1059
- const stopJSON = JSON.stringify({ type: "message_stop" });
1060
- events.push({ event: "message_stop", data: stopJSON });
1215
+ this.closeOpenBlocks(events, stopReason);
1061
1216
  }
1062
1217
  return { events, done: false };
1063
1218
  }
@@ -1118,6 +1273,560 @@ var init_adapters = __esm({
1118
1273
  }
1119
1274
  });
1120
1275
 
1276
+ // src/core/credentials.ts
1277
+ var uuid = () => globalThis.crypto.randomUUID();
1278
+ var pause = () => new Promise((resolve) => setTimeout(resolve, 25));
1279
+ var abort = (signal) => {
1280
+ if (signal?.aborted) throw new Error("aborted");
1281
+ };
1282
+ var sameRequestOwner = (snapshot, expected) => snapshot.storeInstanceId === expected.storeInstanceId && snapshot.authSessionId === expected.authSessionId && snapshot.principal?.issuer === expected.principal?.issuer && snapshot.principal?.subject === expected.principal?.subject && snapshot.principal?.organizationId === expected.principal?.organizationId;
1283
+ var CredentialLifecycle = class {
1284
+ constructor(store, protocol) {
1285
+ this.store = store;
1286
+ this.protocol = protocol;
1287
+ }
1288
+ store;
1289
+ protocol;
1290
+ listeners = /* @__PURE__ */ new Set();
1291
+ notificationKey = "";
1292
+ observations = /* @__PURE__ */ new Map();
1293
+ read(signal) {
1294
+ return this.store.readSnapshot(signal);
1295
+ }
1296
+ subscribe(listener) {
1297
+ this.listeners.add(listener);
1298
+ return () => {
1299
+ this.listeners.delete(listener);
1300
+ };
1301
+ }
1302
+ async notify(snapshot) {
1303
+ let current;
1304
+ try {
1305
+ current = await this.read();
1306
+ } catch {
1307
+ return;
1308
+ }
1309
+ if (current.storeInstanceId !== snapshot.storeInstanceId || current.revision !== snapshot.revision)
1310
+ return;
1311
+ const projection = {
1312
+ storeInstanceId: snapshot.storeInstanceId,
1313
+ authorityConfig: snapshot.authorityConfig && {
1314
+ serverURL: snapshot.authorityConfig.serverURL,
1315
+ issuer: snapshot.authorityConfig.issuer,
1316
+ oauthProfile: "desktop",
1317
+ authContractVersion: 2,
1318
+ errorContractVersion: 1
1319
+ },
1320
+ revision: snapshot.revision,
1321
+ authSessionId: snapshot.authSessionId,
1322
+ credentialState: snapshot.credentialState,
1323
+ reason: snapshot.reason,
1324
+ principal: snapshot.principal && {
1325
+ issuer: snapshot.principal.issuer,
1326
+ subject: snapshot.principal.subject,
1327
+ organizationId: snapshot.principal.organizationId
1328
+ },
1329
+ refreshOperation: snapshot.refreshOperation && {
1330
+ operationId: snapshot.refreshOperation.operationId,
1331
+ sessionId: snapshot.refreshOperation.sessionId,
1332
+ baseRevision: snapshot.refreshOperation.baseRevision,
1333
+ phase: snapshot.refreshOperation.phase,
1334
+ returnState: snapshot.refreshOperation.returnState,
1335
+ startedAt: snapshot.refreshOperation.startedAt,
1336
+ dispatchedAt: snapshot.refreshOperation.dispatchedAt,
1337
+ deadlineAt: snapshot.refreshOperation.deadlineAt
1338
+ },
1339
+ loginAttempt: snapshot.loginAttempt && {
1340
+ attemptId: snapshot.loginAttempt.attemptId,
1341
+ baseSessionId: snapshot.loginAttempt.baseSessionId,
1342
+ startedAt: snapshot.loginAttempt.startedAt
1343
+ },
1344
+ lastMutation: snapshot.lastMutation && {
1345
+ mutationId: snapshot.lastMutation.mutationId,
1346
+ operationId: snapshot.lastMutation.operationId,
1347
+ resultRevision: snapshot.lastMutation.resultRevision
1348
+ },
1349
+ lastLoginAttemptId: snapshot.lastLoginAttemptId,
1350
+ verifiedIdentity: snapshot.verifiedIdentity && {
1351
+ authSessionId: snapshot.verifiedIdentity.authSessionId,
1352
+ principal: {
1353
+ issuer: snapshot.verifiedIdentity.principal.issuer,
1354
+ subject: snapshot.verifiedIdentity.principal.subject,
1355
+ organizationId: snapshot.verifiedIdentity.principal.organizationId
1356
+ },
1357
+ displayName: snapshot.verifiedIdentity.displayName,
1358
+ avatarUrl: snapshot.verifiedIdentity.avatarUrl,
1359
+ email: snapshot.verifiedIdentity.email,
1360
+ imageUrl: snapshot.verifiedIdentity.imageUrl,
1361
+ accountCreatedAt: snapshot.verifiedIdentity.accountCreatedAt,
1362
+ requiresPhoneBinding: snapshot.verifiedIdentity.requiresPhoneBinding,
1363
+ hasExtraUsageEnabled: snapshot.verifiedIdentity.hasExtraUsageEnabled,
1364
+ billingType: snapshot.verifiedIdentity.billingType,
1365
+ subscriptionCreatedAt: snapshot.verifiedIdentity.subscriptionCreatedAt,
1366
+ rateLimitTier: snapshot.verifiedIdentity.rateLimitTier,
1367
+ organizationName: snapshot.verifiedIdentity.organizationName,
1368
+ verifiedAt: snapshot.verifiedIdentity.verifiedAt
1369
+ }
1370
+ };
1371
+ const key = JSON.stringify({ ...projection, revision: void 0, lastMutation: void 0 });
1372
+ if (key === this.notificationKey) return;
1373
+ this.notificationKey = key;
1374
+ for (const listener of this.listeners) {
1375
+ try {
1376
+ listener(structuredClone(projection));
1377
+ } catch {
1378
+ }
1379
+ }
1380
+ }
1381
+ async reconcile(signal) {
1382
+ const s = await this.read(signal);
1383
+ await this.notify(s);
1384
+ return s;
1385
+ }
1386
+ cas(s, changes, mutationId = uuid(), signal) {
1387
+ return this.store.compareAndSwap(
1388
+ {
1389
+ storeInstanceId: s.storeInstanceId,
1390
+ revision: s.revision,
1391
+ authSessionId: s.authSessionId,
1392
+ state: s.credentialState,
1393
+ operationId: s.refreshOperation?.operationId ?? s.loginAttempt?.attemptId ?? null
1394
+ },
1395
+ { ...s, ...changes },
1396
+ mutationId,
1397
+ signal
1398
+ );
1399
+ }
1400
+ async commit(s, changes, signal) {
1401
+ const r = await this.cas(s, changes, uuid(), signal);
1402
+ if (r.status === "storage_error") throw new Error("storage_unavailable");
1403
+ if (r.status === "committed") await this.notify(r.snapshot);
1404
+ return r;
1405
+ }
1406
+ async metadata(s, signal) {
1407
+ const m = await this.protocol.metadata(signal);
1408
+ if (m.crabcode_auth_contract_version !== 2 || m.gateway_error_contract_version !== 1 || !m.issuer)
1409
+ throw new Error("auth_contract_unsupported");
1410
+ if (s.authorityConfig && (s.authorityConfig.serverURL !== this.protocol.serverURL || s.authorityConfig.issuer !== m.issuer || s.authorityConfig.oauthProfile !== "desktop" || s.authorityConfig.authContractVersion !== 2 || s.authorityConfig.errorContractVersion !== 1))
1411
+ throw new Error("auth_contract_unsupported");
1412
+ return m;
1413
+ }
1414
+ async reserveLogin(signal) {
1415
+ const attemptId = uuid();
1416
+ let reserved;
1417
+ for (; ; ) {
1418
+ abort(signal);
1419
+ const s = await this.read(signal);
1420
+ if (s.credentialState !== "signed_out") throw new Error("local_logout_required");
1421
+ const r = await this.commit(s, { loginAttempt: { attemptId, baseSessionId: s.authSessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() } }, signal);
1422
+ if (r.status === "committed") {
1423
+ reserved = r.snapshot;
1424
+ break;
1425
+ }
1426
+ }
1427
+ try {
1428
+ const m = await this.metadata(reserved, signal);
1429
+ for (; ; ) {
1430
+ abort(signal);
1431
+ const current = await this.read(signal);
1432
+ if (current.storeInstanceId !== reserved.storeInstanceId || current.loginAttempt?.attemptId !== attemptId) throw new Error("superseded");
1433
+ if (current.authorityConfig) return { attemptId, metadata: m };
1434
+ const r = await this.commit(current, { authorityConfig: { serverURL: this.protocol.serverURL, issuer: m.issuer, oauthProfile: "desktop", authContractVersion: 2, errorContractVersion: 1 } }, signal);
1435
+ if (r.status === "committed") return { attemptId, metadata: m };
1436
+ }
1437
+ } catch (error) {
1438
+ await this.cancelLogin(attemptId);
1439
+ throw error;
1440
+ }
1441
+ }
1442
+ async installLogin(attemptId, tokens, signal) {
1443
+ this.validateTokens(tokens);
1444
+ for (; ; ) {
1445
+ abort(signal);
1446
+ const s = await this.read(signal);
1447
+ if (s.loginAttempt?.attemptId !== attemptId || s.loginAttempt.baseSessionId !== s.authSessionId)
1448
+ throw new Error("superseded");
1449
+ const r = await this.commit(
1450
+ s,
1451
+ {
1452
+ authSessionId: uuid(),
1453
+ tokenSet: tokens,
1454
+ credentialState: "pending_identity",
1455
+ principal: null,
1456
+ verifiedIdentity: null,
1457
+ loginAttempt: null,
1458
+ lastLoginAttemptId: attemptId,
1459
+ reason: "identity_unavailable"
1460
+ },
1461
+ signal
1462
+ );
1463
+ if (r.status === "committed") return this.bindIdentity(r.snapshot.authSessionId, signal);
1464
+ }
1465
+ }
1466
+ async assertLogin(attemptId, signal) {
1467
+ abort(signal);
1468
+ const s = await this.read(signal);
1469
+ if (s.loginAttempt?.attemptId !== attemptId) throw new Error("superseded");
1470
+ }
1471
+ async cancelLogin(attemptId) {
1472
+ const s = await this.read();
1473
+ if (s.loginAttempt?.attemptId === attemptId) await this.commit(s, { loginAttempt: null });
1474
+ }
1475
+ validateTokens(t) {
1476
+ if (!t.access_token?.trim() || !t.refresh_token?.trim() || !t.client_id?.trim() || t.server_url !== this.protocol.serverURL || !Number.isFinite(Date.parse(t.expires_at)) || Date.parse(t.expires_at) <= Date.now())
1477
+ throw new Error("invalid_response");
1478
+ }
1479
+ usable(s) {
1480
+ return !!s.tokenSet && Date.parse(s.tokenSet.expires_at) > Date.now() + 3e5;
1481
+ }
1482
+ async bindIdentity(sessionId, signal) {
1483
+ const s = await this.ensureSnapshot(signal, void 0, true);
1484
+ if (s.authSessionId !== sessionId) throw new Error("superseded");
1485
+ if (s.credentialState === "ready") return s;
1486
+ await this.metadata(s, signal);
1487
+ const beforeProfile = await this.read(signal);
1488
+ if (beforeProfile.storeInstanceId !== s.storeInstanceId || beforeProfile.authSessionId !== sessionId || beforeProfile.revision !== s.revision || beforeProfile.credentialState !== "pending_identity")
1489
+ throw new Error("superseded");
1490
+ let identity;
1491
+ try {
1492
+ identity = await this.protocol.profile(s.tokenSet, signal);
1493
+ } catch (error) {
1494
+ const message = error instanceof Error ? error.message : "";
1495
+ if (message === "invalid_scope" || message === "auth_contract_unsupported")
1496
+ await this.commit(s, { credentialState: "configuration_error", reason: message });
1497
+ throw error;
1498
+ }
1499
+ abort(signal);
1500
+ const current = await this.read(signal);
1501
+ if (current.storeInstanceId !== s.storeInstanceId || current.authSessionId !== sessionId || current.revision !== s.revision)
1502
+ throw new Error("superseded");
1503
+ if (!identity.subject) throw new Error("invalid_response");
1504
+ const principal = {
1505
+ issuer: s.authorityConfig.issuer,
1506
+ subject: identity.subject,
1507
+ organizationId: identity.organizationId
1508
+ };
1509
+ const r = await this.commit(
1510
+ s,
1511
+ {
1512
+ principal,
1513
+ verifiedIdentity: {
1514
+ authSessionId: sessionId,
1515
+ principal,
1516
+ displayName: identity.displayName,
1517
+ avatarUrl: identity.avatarUrl,
1518
+ email: identity.email,
1519
+ imageUrl: identity.imageUrl,
1520
+ accountCreatedAt: identity.accountCreatedAt,
1521
+ requiresPhoneBinding: identity.requiresPhoneBinding,
1522
+ hasExtraUsageEnabled: identity.hasExtraUsageEnabled,
1523
+ billingType: identity.billingType,
1524
+ subscriptionCreatedAt: identity.subscriptionCreatedAt,
1525
+ rateLimitTier: identity.rateLimitTier,
1526
+ organizationName: identity.organizationName,
1527
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
1528
+ },
1529
+ credentialState: "ready",
1530
+ reason: null
1531
+ },
1532
+ signal
1533
+ );
1534
+ if (r.status !== "committed") throw new Error("superseded");
1535
+ return r.snapshot;
1536
+ }
1537
+ async ensure(signal, rejected, expected) {
1538
+ const snapshot = await this.ensureSnapshot(signal, rejected, false, expected);
1539
+ this.observations.set(snapshot.tokenSet.access_token, snapshot);
1540
+ if (this.observations.size > 128)
1541
+ this.observations.delete(this.observations.keys().next().value);
1542
+ return snapshot.tokenSet.access_token;
1543
+ }
1544
+ async forceRefresh(signal, rejectedToken, expected) {
1545
+ const rejected = rejectedToken ? this.observations.get(rejectedToken) : await this.read(signal);
1546
+ if (rejectedToken && !rejected) throw new Error("credential_observation_unavailable");
1547
+ return this.ensure(signal, rejected, expected);
1548
+ }
1549
+ async ensureSnapshot(signal, rejected, pending = false, expected) {
1550
+ let session;
1551
+ let instance;
1552
+ let initialRevision;
1553
+ let refreshed = false;
1554
+ let initialAccess;
1555
+ for (; ; ) {
1556
+ abort(signal);
1557
+ const s = await this.read(signal);
1558
+ if (expected && !sameRequestOwner(s, expected)) throw new Error("superseded");
1559
+ if (s.authorityConfig && s.authorityConfig.serverURL !== this.protocol.serverURL)
1560
+ throw new Error("auth_contract_unsupported");
1561
+ if (session === void 0) {
1562
+ session = s.authSessionId;
1563
+ instance = s.storeInstanceId;
1564
+ initialRevision = s.revision;
1565
+ initialAccess = s.tokenSet?.access_token;
1566
+ }
1567
+ if (s.storeInstanceId !== instance || s.authSessionId !== session || rejected && (s.authSessionId !== rejected.authSessionId || s.storeInstanceId !== rejected.storeInstanceId))
1568
+ throw new Error("superseded");
1569
+ if (s.credentialState === "refresh_dispatched") {
1570
+ if (Date.now() >= Date.parse(s.refreshOperation.deadlineAt))
1571
+ await this.commit(s, {
1572
+ credentialState: "reauth_required",
1573
+ tokenSet: null,
1574
+ refreshOperation: null,
1575
+ reason: "refresh_outcome_unknown"
1576
+ });
1577
+ else await pause();
1578
+ continue;
1579
+ }
1580
+ if (s.credentialState === "refresh_reserved") {
1581
+ if (Date.now() >= Date.parse(s.refreshOperation.startedAt) + 3e4)
1582
+ await this.commit(s, {
1583
+ credentialState: s.refreshOperation.returnState,
1584
+ refreshOperation: null
1585
+ });
1586
+ else await pause();
1587
+ continue;
1588
+ }
1589
+ if (s.credentialState !== "ready" && !(pending && s.credentialState === "pending_identity"))
1590
+ throw new Error(`credential_unavailable:${s.credentialState}:${s.reason ?? ""}`);
1591
+ const live = !!s.tokenSet && Date.parse(s.tokenSet.expires_at) > Date.now();
1592
+ if (live && (refreshed || s.revision !== (rejected?.revision ?? initialRevision) && (rejected !== void 0 || s.tokenSet.access_token !== initialAccess)))
1593
+ return s;
1594
+ if (this.usable(s) && !rejected) return s;
1595
+ const metadata = await this.metadata(s, signal);
1596
+ abort(signal);
1597
+ const operationId = uuid();
1598
+ const r = await this.commit(
1599
+ s,
1600
+ {
1601
+ credentialState: "refresh_reserved",
1602
+ refreshOperation: {
1603
+ operationId,
1604
+ sessionId: s.authSessionId,
1605
+ baseRevision: s.revision,
1606
+ phase: "reserved",
1607
+ returnState: s.credentialState,
1608
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1609
+ dispatchedAt: null,
1610
+ deadlineAt: null
1611
+ }
1612
+ },
1613
+ signal
1614
+ );
1615
+ if (r.status !== "committed") continue;
1616
+ const owner = this.refresh(r.snapshot, metadata);
1617
+ await this.wait(owner, signal);
1618
+ refreshed = true;
1619
+ rejected = void 0;
1620
+ }
1621
+ }
1622
+ wait(promise, signal) {
1623
+ if (!signal) return promise;
1624
+ return new Promise((resolve, reject) => {
1625
+ const onAbort = () => {
1626
+ signal.removeEventListener("abort", onAbort);
1627
+ reject(new Error("aborted"));
1628
+ };
1629
+ signal.addEventListener("abort", onAbort, { once: true });
1630
+ promise.then(
1631
+ (value) => {
1632
+ signal.removeEventListener("abort", onAbort);
1633
+ resolve(value);
1634
+ },
1635
+ (error) => {
1636
+ signal.removeEventListener("abort", onAbort);
1637
+ reject(error);
1638
+ }
1639
+ );
1640
+ if (signal.aborted) onAbort();
1641
+ });
1642
+ }
1643
+ async refresh(reserved, metadata) {
1644
+ const dispatchedAt = Date.now();
1645
+ const r = await this.commit(reserved, {
1646
+ credentialState: "refresh_dispatched",
1647
+ refreshOperation: {
1648
+ ...reserved.refreshOperation,
1649
+ phase: "dispatched",
1650
+ dispatchedAt: new Date(dispatchedAt).toISOString(),
1651
+ deadlineAt: new Date(dispatchedAt + 3e4).toISOString()
1652
+ }
1653
+ });
1654
+ if (r.status !== "committed") return;
1655
+ const s = r.snapshot;
1656
+ const ctl = new AbortController();
1657
+ const timeout = setTimeout(() => ctl.abort(), 3e4);
1658
+ let tokens;
1659
+ try {
1660
+ const result = await this.wait(
1661
+ (async () => {
1662
+ const response = await this.protocol.fetch(metadata.token_endpoint, {
1663
+ method: "POST",
1664
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1665
+ body: new URLSearchParams({
1666
+ grant_type: "refresh_token",
1667
+ client_id: s.tokenSet.client_id,
1668
+ refresh_token: s.tokenSet.refresh_token
1669
+ }),
1670
+ signal: ctl.signal
1671
+ });
1672
+ const body2 = await response.json();
1673
+ return { response, body: body2 };
1674
+ })(),
1675
+ ctl.signal
1676
+ );
1677
+ if (!result.response.ok) {
1678
+ const code = result.body.error;
1679
+ let reason = "refresh_outcome_unknown";
1680
+ let state = "reauth_required";
1681
+ if (code === "invalid_grant") reason = code;
1682
+ else if (["invalid_client", "invalid_scope", "unsupported_grant_type"].includes(code)) {
1683
+ reason = code;
1684
+ state = "configuration_error";
1685
+ } else if (result.body.rotationOutcome === "not_committed" && ["temporarily_unavailable", "server_error"].includes(code)) {
1686
+ await this.commit(s, {
1687
+ credentialState: s.refreshOperation.returnState,
1688
+ refreshOperation: null
1689
+ });
1690
+ throw new Error("refresh_temporarily_unavailable");
1691
+ }
1692
+ await this.commit(s, {
1693
+ credentialState: state,
1694
+ tokenSet: null,
1695
+ refreshOperation: null,
1696
+ reason
1697
+ });
1698
+ throw new Error(reason);
1699
+ }
1700
+ const body = result.body;
1701
+ if (!Number.isFinite(body.expires_in) || body.expires_in <= 0)
1702
+ throw new Error("invalid_response");
1703
+ tokens = {
1704
+ ...s.tokenSet,
1705
+ access_token: body.access_token,
1706
+ refresh_token: body.refresh_token,
1707
+ expires_at: new Date(Date.now() + body.expires_in * 1e3).toISOString(),
1708
+ scope: body.scope ?? s.tokenSet.scope
1709
+ };
1710
+ this.validateTokens(tokens);
1711
+ } catch (error) {
1712
+ await this.commit(s, {
1713
+ credentialState: "reauth_required",
1714
+ tokenSet: null,
1715
+ refreshOperation: null,
1716
+ reason: "refresh_outcome_unknown"
1717
+ });
1718
+ throw error;
1719
+ } finally {
1720
+ clearTimeout(timeout);
1721
+ }
1722
+ const started = performance.now(), mutationId = uuid();
1723
+ for (; ; ) {
1724
+ if (Date.now() >= Date.parse(s.refreshOperation.deadlineAt)) {
1725
+ await this.commit(s, {
1726
+ credentialState: "reauth_required",
1727
+ tokenSet: null,
1728
+ refreshOperation: null,
1729
+ reason: "refresh_outcome_unknown"
1730
+ });
1731
+ void this.revoke(tokens, metadata);
1732
+ throw new Error("refresh_outcome_unknown");
1733
+ }
1734
+ const result = await this.cas(
1735
+ s,
1736
+ {
1737
+ tokenSet: tokens,
1738
+ credentialState: s.refreshOperation.returnState,
1739
+ refreshOperation: null
1740
+ },
1741
+ mutationId
1742
+ );
1743
+ if (result.status === "committed") {
1744
+ await this.notify(result.snapshot);
1745
+ return;
1746
+ }
1747
+ if (result.status === "superseded") {
1748
+ void this.revoke(tokens, metadata);
1749
+ return;
1750
+ }
1751
+ if (performance.now() - started >= 2e3) throw new Error("credential_persist_failed");
1752
+ await pause();
1753
+ }
1754
+ }
1755
+ async revoke(tokens, metadata, authority) {
1756
+ const ctl = new AbortController();
1757
+ const timer = setTimeout(() => ctl.abort(), 3e4);
1758
+ try {
1759
+ const m = metadata ?? await this.wait(authority ? this.metadata(authority, ctl.signal) : this.protocol.metadata(ctl.signal), ctl.signal);
1760
+ if (!m.revocation_endpoint) return "unsupported";
1761
+ const response = await this.wait(this.protocol.fetch(m.revocation_endpoint, {
1762
+ method: "POST",
1763
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1764
+ body: new URLSearchParams({ token: tokens.refresh_token, token_type_hint: "refresh_token" }),
1765
+ signal: ctl.signal
1766
+ }), ctl.signal);
1767
+ return response.ok ? "confirmed" : "failed";
1768
+ } catch {
1769
+ return "failed";
1770
+ } finally {
1771
+ clearTimeout(timer);
1772
+ }
1773
+ }
1774
+ async logout(signal, expected) {
1775
+ let s = await this.read(signal);
1776
+ if (expected && (s.storeInstanceId !== expected.storeInstanceId || s.authSessionId !== expected.authSessionId)) {
1777
+ return {
1778
+ logoutOperationId: uuid(),
1779
+ expectedAuthSessionId: expected.authSessionId,
1780
+ storeInstanceId: expected.storeInstanceId,
1781
+ revision: s.revision,
1782
+ status: "superseded"
1783
+ };
1784
+ }
1785
+ const session = s.authSessionId, instance = s.storeInstanceId, started = performance.now(), logoutOperationId = uuid();
1786
+ const receipt = () => ({
1787
+ logoutOperationId,
1788
+ expectedAuthSessionId: session,
1789
+ storeInstanceId: instance,
1790
+ revision: s.revision
1791
+ });
1792
+ for (; ; ) {
1793
+ abort(signal);
1794
+ if (s.storeInstanceId !== instance || s.authSessionId !== session)
1795
+ return { ...receipt(), status: "superseded" };
1796
+ if (s.credentialState === "signed_out" && !s.loginAttempt)
1797
+ return { ...receipt(), status: "already_signed_out" };
1798
+ const r = await this.cas(
1799
+ s,
1800
+ {
1801
+ credentialState: "signed_out",
1802
+ authSessionId: null,
1803
+ principal: null,
1804
+ tokenSet: null,
1805
+ refreshOperation: null,
1806
+ loginAttempt: null,
1807
+ lastLoginAttemptId: null,
1808
+ verifiedIdentity: null,
1809
+ reason: null
1810
+ },
1811
+ logoutOperationId,
1812
+ signal
1813
+ );
1814
+ if (r.status === "committed") {
1815
+ await this.notify(r.snapshot);
1816
+ return {
1817
+ ...receipt(),
1818
+ revision: r.snapshot.revision,
1819
+ status: "committed",
1820
+ revocation: s.tokenSet ? this.revoke(s.tokenSet, void 0, s) : Promise.resolve("unsupported")
1821
+ };
1822
+ }
1823
+ if (performance.now() - started >= 2e3) throw new Error("storage_busy");
1824
+ await pause();
1825
+ s = await this.read(signal);
1826
+ }
1827
+ }
1828
+ };
1829
+
1121
1830
  // src/core/client.ts
1122
1831
  init_types();
1123
1832
 
@@ -1557,33 +2266,35 @@ async function revokeToken(meta, token, signal, fetchImpl = globalThis.fetch) {
1557
2266
  }
1558
2267
  async function postToken(endpoint, data, signal, fetchImpl = globalThis.fetch) {
1559
2268
  const ctl = withTimeout(authTimeoutMs, signal);
1560
- let resp;
1561
2269
  try {
1562
- resp = await fetchImpl(endpoint, {
1563
- method: "POST",
1564
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1565
- body: data,
1566
- signal: ctl.signal
1567
- });
1568
- } catch (e) {
1569
- throw new Error(`token request: ${e instanceof Error ? e.message : String(e)}`);
1570
- } finally {
1571
- ctl.dispose();
1572
- }
1573
- if (!resp.ok) {
1574
- let errBody = {};
2270
+ let resp;
1575
2271
  try {
1576
- errBody = await resp.json();
1577
- } catch {
2272
+ resp = await fetchImpl(endpoint, {
2273
+ method: "POST",
2274
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2275
+ body: data,
2276
+ signal: ctl.signal
2277
+ });
2278
+ } catch (e) {
2279
+ throw new Error(`token request: ${e instanceof Error ? e.message : String(e)}`);
1578
2280
  }
1579
- const oauthError = typeof errBody.error === "string" ? errBody.error : "";
1580
- const errorDescription = typeof errBody.error_description === "string" ? errBody.error_description : "";
1581
- throw new OAuthTokenEndpointError(resp.status, oauthError, errorDescription);
1582
- }
1583
- try {
1584
- return await resp.json();
1585
- } catch (e) {
1586
- throw new Error(`token: decode: ${e instanceof Error ? e.message : String(e)}`);
2281
+ if (!resp.ok) {
2282
+ let errBody = {};
2283
+ try {
2284
+ errBody = await resp.json();
2285
+ } catch {
2286
+ }
2287
+ const oauthError = typeof errBody.error === "string" ? errBody.error : "";
2288
+ const errorDescription = typeof errBody.error_description === "string" ? errBody.error_description : "";
2289
+ throw new OAuthTokenEndpointError(resp.status, oauthError, errorDescription);
2290
+ }
2291
+ try {
2292
+ return await resp.json();
2293
+ } catch (e) {
2294
+ throw new Error(`token: decode: ${e instanceof Error ? e.message : String(e)}`);
2295
+ }
2296
+ } finally {
2297
+ ctl.dispose();
1587
2298
  }
1588
2299
  }
1589
2300
  function newTokenSet(resp, clientID, serverURL) {
@@ -2066,10 +2777,31 @@ function parseHTTPErrorWithHeader(statusCode, body, header) {
2066
2777
  } else if (typeof top.message === "string") {
2067
2778
  message = top.message;
2068
2779
  }
2780
+ const contractSource = errObj && typeof errObj === "object" ? errObj : top;
2069
2781
  if (typeof top.errorCode === "string") errorCode = top.errorCode;
2782
+ else if (typeof contractSource.errorCode === "string") errorCode = contractSource.errorCode;
2070
2783
  if (top.windowKind === "FIVE_HOUR" || top.windowKind === "WEEKLY") windowKind = top.windowKind;
2071
2784
  if (typeof top.windowResetAt === "string") windowResetAt = top.windowResetAt;
2072
2785
  if (typeof top.windowOverridable === "boolean") windowOverridable = top.windowOverridable;
2786
+ const disposition = contractSource.requestDisposition === "not_accepted" || contractSource.requestDisposition === "accepted" || contractSource.requestDisposition === "unknown" ? contractSource.requestDisposition : void 0;
2787
+ const domains = ["user_auth", "caller_credentials", "account_quota", "account_permission", "provider", "gateway", "transport", "stream_ticket", "protocol"];
2788
+ return new exports.HTTPError(statusCode, {
2789
+ type,
2790
+ message,
2791
+ retryAfter,
2792
+ body: bodyStr,
2793
+ errorCode,
2794
+ windowKind,
2795
+ windowResetAt,
2796
+ windowOverridable,
2797
+ errorContractVersion: contractSource.errorContractVersion === 1 ? 1 : void 0,
2798
+ faultDomain: typeof contractSource.faultDomain === "string" && domains.includes(contractSource.faultDomain) ? contractSource.faultDomain : void 0,
2799
+ requestDisposition: disposition,
2800
+ transportRequestId: typeof contractSource.transportRequestId === "string" ? contractSource.transportRequestId : null,
2801
+ consumeRequestId: typeof contractSource.consumeRequestId === "string" ? contractSource.consumeRequestId : null,
2802
+ providerRequestId: typeof contractSource.providerRequestId === "string" ? contractSource.providerRequestId : null,
2803
+ retryable: contractSource.retryable === true
2804
+ });
2073
2805
  }
2074
2806
  } catch {
2075
2807
  }
@@ -2131,7 +2863,19 @@ function parseStreamError(data) {
2131
2863
  if (code === "" && errObj.type) code = errObj.type;
2132
2864
  }
2133
2865
  }
2134
- return new exports.StreamError({ code, stage, message, rawError, retryable });
2866
+ return new exports.StreamError({
2867
+ code,
2868
+ stage,
2869
+ message,
2870
+ rawError,
2871
+ retryable,
2872
+ errorContractVersion: payload.errorContractVersion,
2873
+ faultDomain: payload.faultDomain,
2874
+ requestDisposition: payload.requestDisposition,
2875
+ transportRequestId: payload.transportRequestId,
2876
+ consumeRequestId: payload.consumeRequestId,
2877
+ providerRequestId: payload.providerRequestId
2878
+ });
2135
2879
  }
2136
2880
  function isOrderSuccess(status) {
2137
2881
  switch (status) {
@@ -2365,6 +3109,13 @@ var Client = class _Client {
2365
3109
  tokens = null;
2366
3110
  /** token 持久化 */
2367
3111
  store;
3112
+ credentialMode;
3113
+ versionedCredentialStore;
3114
+ accessTokenProvider;
3115
+ beforeCredentialInstall;
3116
+ credentialAuthority;
3117
+ credentialRequestOwner;
3118
+ lifecycle = null;
2368
3119
  /** fetch 实现 (默认 globalThis.fetch) */
2369
3120
  fetchImpl;
2370
3121
  /** 互斥锁 (TS 用 Promise chain 替代 sync.Mutex) */
@@ -2395,6 +3146,7 @@ var Client = class _Client {
2395
3146
  /** V29 系数缓存 (TTL 8s, listCoefficients 内部用) */
2396
3147
  coefCacheData = null;
2397
3148
  coefCacheTimeMs = 0;
3149
+ credentialOwnerKey = null;
2398
3150
  /** 串行化锁 (替代 Go sync.Mutex) */
2399
3151
  coefMu = Promise.resolve();
2400
3152
  constructor(cfg = {}) {
@@ -2405,8 +3157,92 @@ var Client = class _Client {
2405
3157
  this.oauthMetadataProfile = cfg.oauthMetadataProfile ?? "desktop";
2406
3158
  this.browserRefreshMode = cfg.browserRefreshMode ?? "direct";
2407
3159
  this.refreshProxyURL = cfg.refreshProxyURL ?? null;
2408
- this.store = cfg.store ?? defaultTokenStore();
3160
+ this.credentialMode = cfg.credentialMode ?? "legacy";
3161
+ this.credentialAuthority = new URL(this.serverURL).origin;
3162
+ if (this.credentialMode === "versioned" && !cfg.versionedCredentialStore) {
3163
+ throw new Error("versionedCredentialStore is required for credentialMode=versioned");
3164
+ }
3165
+ if (cfg.credentialRequestOwner && this.credentialMode !== "versioned") {
3166
+ throw new Error("credentialRequestOwner requires credentialMode=versioned");
3167
+ }
3168
+ if (this.credentialMode === "legacy" && cfg.versionedCredentialStore) {
3169
+ throw new Error("credentialMode=versioned is required when versionedCredentialStore is provided");
3170
+ }
3171
+ if (this.credentialMode === "external" && cfg.versionedCredentialStore) {
3172
+ throw new Error("versionedCredentialStore is incompatible with credentialMode=external");
3173
+ }
3174
+ if (this.credentialMode !== "external" && cfg.accessTokenProvider) {
3175
+ throw new Error("accessTokenProvider requires credentialMode=external");
3176
+ }
3177
+ if (this.credentialMode === "external" && !cfg.accessTokenProvider) {
3178
+ throw new Error("accessTokenProvider is required for credentialMode=external");
3179
+ }
3180
+ if (this.credentialMode !== "versioned" && cfg.beforeCredentialInstall) {
3181
+ throw new Error("beforeCredentialInstall requires credentialMode=versioned");
3182
+ }
3183
+ if (this.credentialMode !== "legacy") {
3184
+ const authority = new URL(this.serverURL).origin;
3185
+ for (const [name, override] of [["apiBaseURL", this.apiBaseURL], ["complianceBaseURL", this.complianceBaseURL]]) {
3186
+ if (override && new URL(override).origin !== authority) {
3187
+ throw new Error(`${name} must use the credential authority ${authority}`);
3188
+ }
3189
+ }
3190
+ }
3191
+ this.accessTokenProvider = cfg.accessTokenProvider;
3192
+ this.beforeCredentialInstall = cfg.beforeCredentialInstall;
3193
+ this.credentialRequestOwner = cfg.credentialRequestOwner ? {
3194
+ storeInstanceId: cfg.credentialRequestOwner.storeInstanceId,
3195
+ authSessionId: cfg.credentialRequestOwner.authSessionId,
3196
+ principal: cfg.credentialRequestOwner.principal ? {
3197
+ issuer: cfg.credentialRequestOwner.principal.issuer,
3198
+ subject: cfg.credentialRequestOwner.principal.subject,
3199
+ organizationId: cfg.credentialRequestOwner.principal.organizationId
3200
+ } : null
3201
+ } : null;
2409
3202
  this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
3203
+ this.versionedCredentialStore = cfg.versionedCredentialStore ?? null;
3204
+ if (this.versionedCredentialStore) this.lifecycle = new CredentialLifecycle(this.versionedCredentialStore, {
3205
+ serverURL: this.serverURL,
3206
+ fetch: this.fetchImpl,
3207
+ metadata: (signal) => discoverWithProfile(this.serverURL, "desktop", signal, this.fetchImpl),
3208
+ profile: async (tokens, signal) => {
3209
+ const gatewayRoot = this.serverURL.replace(/\/api\/v4$/, "");
3210
+ const profileURL = `${gatewayRoot}/api/oauth/profile`;
3211
+ this.assertCredentialURL(profileURL);
3212
+ const response = await this.fetchImpl(profileURL, { headers: { Authorization: `Bearer ${tokens.access_token}` }, signal });
3213
+ if (!response.ok) {
3214
+ const bodyBytes = response.body ? await readLimited(response.body, maxErrorBodySize) : new Uint8Array();
3215
+ const failure = parseHTTPErrorWithHeader(response.status, bodyBytes, response.headers);
3216
+ const contract = readGatewayErrorContract(failure);
3217
+ const code = contract?.errorCode ?? null;
3218
+ if (code === "INVALID_SCOPE") throw new Error("invalid_scope");
3219
+ if (code === "AUTH_CONTRACT_UNSUPPORTED") throw new Error("auth_contract_unsupported");
3220
+ if (code === "ACCOUNT_NOT_FOUND") throw new Error("account_permission");
3221
+ throw new Error("identity_unavailable");
3222
+ }
3223
+ const body = await response.json();
3224
+ const subject = body.account?.uuid;
3225
+ if (typeof subject !== "string" || !subject) throw new Error("invalid_response");
3226
+ const account = body.account;
3227
+ const organization = body.organization;
3228
+ return {
3229
+ subject,
3230
+ organizationId: organization?.uuid || null,
3231
+ ...typeof account.display_name === "string" ? { displayName: account.display_name } : {},
3232
+ ...[account.avatar_url, account.picture, account.image, body.avatar_url, body.picture, body.image].find((value) => typeof value === "string") !== void 0 ? { avatarUrl: [account.avatar_url, account.picture, account.image, body.avatar_url, body.picture, body.image].find((value) => typeof value === "string") } : {},
3233
+ ...typeof account.email === "string" ? { email: account.email } : {},
3234
+ ...typeof account.image_url === "string" ? { imageUrl: account.image_url } : {},
3235
+ ...typeof account.created_at === "string" ? { accountCreatedAt: account.created_at } : {},
3236
+ ...typeof account.requires_phone_binding === "boolean" ? { requiresPhoneBinding: account.requires_phone_binding } : {},
3237
+ ...typeof organization?.has_extra_usage_enabled === "boolean" ? { hasExtraUsageEnabled: organization.has_extra_usage_enabled } : {},
3238
+ ...typeof organization?.billing_type === "string" ? { billingType: organization.billing_type } : {},
3239
+ ...typeof organization?.subscription_created_at === "string" ? { subscriptionCreatedAt: organization.subscription_created_at } : {},
3240
+ ...typeof organization?.rate_limit_tier === "string" ? { rateLimitTier: organization.rate_limit_tier } : {},
3241
+ ...typeof organization?.name === "string" ? { organizationName: organization.name } : {}
3242
+ };
3243
+ }
3244
+ });
3245
+ this.store = cfg.store ?? defaultTokenStore();
2410
3246
  this.retryPolicy = effectivePolicy(cfg.retryPolicy ?? null);
2411
3247
  }
2412
3248
  /**
@@ -2415,6 +3251,11 @@ var Client = class _Client {
2415
3251
  */
2416
3252
  static async create(cfg = {}) {
2417
3253
  const c = new _Client(cfg);
3254
+ if (c.credentialMode === "external") return c;
3255
+ if (c.credentialMode === "versioned") {
3256
+ await c.reconcileCredentials();
3257
+ return c;
3258
+ }
2418
3259
  try {
2419
3260
  const tokens = await c.store.load();
2420
3261
  if (tokens) {
@@ -2428,6 +3269,63 @@ var Client = class _Client {
2428
3269
  }
2429
3270
  return c;
2430
3271
  }
3272
+ /** Read the durable authority. Available only in explicit versioned mode. */
3273
+ async getCredentialSnapshot(signal) {
3274
+ if (!this.versionedCredentialStore) {
3275
+ throw new Error("getCredentialSnapshot requires credentialMode=versioned");
3276
+ }
3277
+ return this.versionedCredentialStore.readSnapshot(signal);
3278
+ }
3279
+ /** Reconcile memory from durable state; storage errors never clear confirmed memory. */
3280
+ async reconcileCredentials(signal) {
3281
+ const snapshot = await this.lifecycle.reconcile(signal);
3282
+ this.adoptCredentialOwner(snapshot);
3283
+ this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
3284
+ return snapshot;
3285
+ }
3286
+ ownerKey(snapshot) {
3287
+ return `${snapshot.storeInstanceId}\0${snapshot.authSessionId ?? ""}\0${snapshot.principal?.issuer ?? ""}\0${snapshot.principal?.subject ?? ""}\0${snapshot.principal?.organizationId ?? ""}`;
3288
+ }
3289
+ adoptCredentialOwner(snapshot) {
3290
+ const next = this.ownerKey(snapshot);
3291
+ if (this.credentialOwnerKey !== null && this.credentialOwnerKey !== next) {
3292
+ this.modelCache = [];
3293
+ this.modelCacheTimeMs = 0;
3294
+ this.coefCacheData = null;
3295
+ this.coefCacheTimeMs = 0;
3296
+ }
3297
+ this.credentialOwnerKey = next;
3298
+ }
3299
+ async ensureCredential(signal) {
3300
+ if (!this.lifecycle) return this.ensureToken(signal);
3301
+ const token = await this.lifecycle.ensure(signal, void 0, this.credentialRequestOwner ?? void 0);
3302
+ const snapshot = await this.lifecycle.read(signal);
3303
+ this.adoptCredentialOwner(snapshot);
3304
+ this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
3305
+ return token;
3306
+ }
3307
+ subscribeCredentialState(listener) {
3308
+ if (!this.lifecycle) throw new Error("subscribeCredentialState requires credentialMode=versioned");
3309
+ return this.lifecycle.subscribe(listener);
3310
+ }
3311
+ async retryCredentialIdentity(signal) {
3312
+ const snapshot = await this.getCredentialSnapshot(signal);
3313
+ if (!snapshot.authSessionId) throw new Error("not authorized");
3314
+ const ready = await this.lifecycle.bindIdentity(snapshot.authSessionId, signal);
3315
+ this.adoptCredentialOwner(ready);
3316
+ this.tokens = ready.credentialState === "ready" ? ready.tokenSet : null;
3317
+ return ready;
3318
+ }
3319
+ async logoutCredential(signal, expected) {
3320
+ if (!this.lifecycle) throw new Error("logoutCredential requires credentialMode=versioned");
3321
+ const result = await this.lifecycle.logout(signal, expected);
3322
+ if (result.status === "committed" || result.status === "already_signed_out") {
3323
+ const current = await this.lifecycle.read(signal);
3324
+ this.adoptCredentialOwner(current);
3325
+ this.tokens = null;
3326
+ }
3327
+ return result;
3328
+ }
2431
3329
  // ===========================================================================
2432
3330
  // 授权生命周期
2433
3331
  // ===========================================================================
@@ -2478,6 +3376,44 @@ var Client = class _Client {
2478
3376
  return this.loginInternal(appName, scopes, { handler, ...opts }, signal);
2479
3377
  }
2480
3378
  async loginInternal(appName, scopes, opts, signal) {
3379
+ if (this.lifecycle) {
3380
+ const attempt = await this.lifecycle.reserveLogin(signal);
3381
+ let installHookRejected = false;
3382
+ try {
3383
+ const registration = await register(attempt.metadata, appName, signal, this.fetchImpl);
3384
+ await this.lifecycle.assertLogin(attempt.attemptId, signal);
3385
+ const authorization = await authorize(attempt.metadata, registration.client_id, scopes, { ...opts, handler: opts?.handler ?? void 0, signal });
3386
+ await this.lifecycle.assertLogin(attempt.attemptId, signal);
3387
+ const response = await exchangeCode(attempt.metadata, registration.client_id, authorization.result.code, authorization.result.redirectURI, authorization.verifier, signal, this.fetchImpl);
3388
+ if (!Number.isFinite(response.expires_in) || response.expires_in <= 0) throw new Error("invalid_response");
3389
+ await this.lifecycle.assertLogin(attempt.attemptId, signal);
3390
+ try {
3391
+ await this.beforeCredentialInstall?.({
3392
+ accessToken: response.access_token,
3393
+ attemptId: attempt.attemptId,
3394
+ serverURL: this.serverURL,
3395
+ clientId: registration.client_id
3396
+ }, signal);
3397
+ } catch (error) {
3398
+ installHookRejected = true;
3399
+ throw error;
3400
+ }
3401
+ await this.lifecycle.assertLogin(attempt.attemptId, signal);
3402
+ const ready = await this.lifecycle.installLogin(attempt.attemptId, newTokenSet(response, registration.client_id, this.serverURL), signal);
3403
+ const current = await this.getCredentialSnapshot(signal);
3404
+ if (current.revision !== ready.revision || current.authSessionId !== ready.authSessionId) throw new Error("superseded");
3405
+ this.tokens = ready.tokenSet;
3406
+ opts?.handler?.({ type: EventComplete, attemptId: attempt.attemptId });
3407
+ } catch (error) {
3408
+ const current = await this.getCredentialSnapshot();
3409
+ if (current.loginAttempt?.attemptId === attempt.attemptId || current.lastLoginAttemptId === attempt.attemptId) {
3410
+ opts?.handler?.(installHookRejected ? { type: EventError, attemptId: attempt.attemptId, err_code: "credential_install_rejected", error: "credential_install_rejected" } : { type: EventError, attemptId: attempt.attemptId, error: "credential_login_failed" });
3411
+ }
3412
+ await this.lifecycle.cancelLogin(attempt.attemptId);
3413
+ throw error;
3414
+ }
3415
+ return;
3416
+ }
2481
3417
  const handler = opts?.handler ?? void 0;
2482
3418
  const emit = (e) => {
2483
3419
  if (handler) handler(e);
@@ -2586,6 +3522,11 @@ var Client = class _Client {
2586
3522
  }
2587
3523
  /** 吊销 token 并清除本地存储 */
2588
3524
  async logout(signal) {
3525
+ if (this.lifecycle) {
3526
+ await this.logoutCredential(signal);
3527
+ await this.reconcileCredentials(signal);
3528
+ return;
3529
+ }
2589
3530
  const tokens = this.tokens;
2590
3531
  let meta = this.meta;
2591
3532
  this.tokens = null;
@@ -2623,6 +3564,13 @@ var Client = class _Client {
2623
3564
  * 避免应用启动期 "login + 多个 API 调用" 并发场景下 4+ 条 "not authorized" 误报.
2624
3565
  */
2625
3566
  async ensureToken(signal) {
3567
+ if (this.credentialMode === "external") {
3568
+ if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
3569
+ const token = await this.accessTokenProvider(signal);
3570
+ if (typeof token !== "string" || token.length === 0) throw new Error("external access token unavailable");
3571
+ return token;
3572
+ }
3573
+ if (this.lifecycle) return this.lifecycle.ensure(signal, void 0, this.credentialRequestOwner ?? void 0);
2626
3574
  let tokens = this.tokens;
2627
3575
  const ready = this.tokenReady.promise;
2628
3576
  const inFlight = this.loginInFlight;
@@ -2670,7 +3618,14 @@ var Client = class _Client {
2670
3618
  );
2671
3619
  }
2672
3620
  /** 强制刷新 token (用于 401 重试) */
2673
- async forceRefresh(signal) {
3621
+ async forceRefresh(signal, rejectedToken) {
3622
+ if (this.credentialMode === "external") throw new Error("external credentials cannot be refreshed by the SDK");
3623
+ if (this.lifecycle) {
3624
+ await this.lifecycle.forceRefresh(signal, rejectedToken, this.credentialRequestOwner ?? void 0);
3625
+ const snapshot = await this.lifecycle.read(signal);
3626
+ this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
3627
+ return;
3628
+ }
2674
3629
  return this.withMu(
2675
3630
  () => this.storeWithLock(async () => {
2676
3631
  await this.syncFromDisk();
@@ -2865,6 +3820,7 @@ var Client = class _Client {
2865
3820
  */
2866
3821
  async listModelsWithStatus(signal, opts) {
2867
3822
  const includeLocked = opts?.includeLocked === true;
3823
+ const requestOwner = this.lifecycle ? this.ownerKey(await this.getCredentialSnapshot(signal)) : null;
2868
3824
  const path = includeLocked ? "/managed-models?picker=1" : "/managed-models";
2869
3825
  const { result, headers } = await this.doJSONFull(
2870
3826
  "GET",
@@ -2873,6 +3829,11 @@ var Client = class _Client {
2873
3829
  signal
2874
3830
  );
2875
3831
  const normalized = normalizeInputModalities(result.data);
3832
+ if (requestOwner !== null) {
3833
+ const current = await this.getCredentialSnapshot(signal);
3834
+ if (this.ownerKey(current) !== requestOwner) throw new Error("superseded");
3835
+ this.adoptCredentialOwner(current);
3836
+ }
2876
3837
  if (!includeLocked) {
2877
3838
  this.modelCache = normalized;
2878
3839
  this.modelCacheTimeMs = Date.now();
@@ -3292,12 +4253,11 @@ var Client = class _Client {
3292
4253
  throw classifyTransport("POST " + endpoint, url, e);
3293
4254
  }
3294
4255
  if (resp.status === 401 && !retried) {
4256
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
4257
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
4258
+ if (!isUserAccessTokenRejected(authError)) throw authError;
3295
4259
  try {
3296
- await resp.body?.cancel();
3297
- } catch {
3298
- }
3299
- try {
3300
- await this.forceRefresh(signal);
4260
+ await this.forceRefresh(signal, token);
3301
4261
  } catch (refreshErr) {
3302
4262
  throw new Error(
3303
4263
  `stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3363,12 +4323,11 @@ var Client = class _Client {
3363
4323
  throw classifyTransport("POST " + endpoint, url, e);
3364
4324
  }
3365
4325
  if (resp.status === 401 && !retried) {
4326
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
4327
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
4328
+ if (!isUserAccessTokenRejected(authError)) throw authError;
3366
4329
  try {
3367
- await resp.body?.cancel();
3368
- } catch {
3369
- }
3370
- try {
3371
- await this.forceRefresh(signal);
4330
+ await this.forceRefresh(signal, token);
3372
4331
  } catch (refreshErr) {
3373
4332
  throw new Error(
3374
4333
  `messages stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3458,7 +4417,9 @@ var Client = class _Client {
3458
4417
  if (!base.endsWith("/api/v4")) {
3459
4418
  base += "/api/v4";
3460
4419
  }
3461
- return base + path;
4420
+ const url = base + path;
4421
+ this.assertCredentialURL(url);
4422
+ return url;
3462
4423
  }
3463
4424
  /**
3464
4425
  * Compliance API URL 拼接。
@@ -3471,7 +4432,14 @@ var Client = class _Client {
3471
4432
  */
3472
4433
  complianceURL(path) {
3473
4434
  const base = this.complianceBaseURL ?? this.serverURL + "/admin-api";
3474
- return base + path;
4435
+ const url = base + path;
4436
+ this.assertCredentialURL(url);
4437
+ return url;
4438
+ }
4439
+ assertCredentialURL(url) {
4440
+ if (this.credentialMode !== "legacy" && new URL(url).origin !== this.credentialAuthority) {
4441
+ throw new Error("credential request authority changed");
4442
+ }
3475
4443
  }
3476
4444
  /** GET/POST/... 通用 JSON 调用 (返回 result 已 typed) */
3477
4445
  async doJSON(method, path, body, signal) {
@@ -3500,12 +4468,11 @@ var Client = class _Client {
3500
4468
  ctl.signal
3501
4469
  );
3502
4470
  if (resp.status === 401 && !retried) {
4471
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
4472
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
4473
+ if (!isUserAccessTokenRejected(authError)) throw authError;
3503
4474
  try {
3504
- await resp.body?.cancel();
3505
- } catch {
3506
- }
3507
- try {
3508
- await this.forceRefresh(ctl.signal);
4475
+ await this.forceRefresh(ctl.signal, token);
3509
4476
  } catch (refreshErr) {
3510
4477
  throw new Error(
3511
4478
  `unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3570,12 +4537,11 @@ var Client = class _Client {
3570
4537
  ctl.signal
3571
4538
  );
3572
4539
  if (resp.status === 401 && !retried) {
4540
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
4541
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
4542
+ if (!isUserAccessTokenRejected(authError)) throw authError;
3573
4543
  try {
3574
- await resp.body?.cancel();
3575
- } catch {
3576
- }
3577
- try {
3578
- await this.forceRefresh(ctl.signal);
4544
+ await this.forceRefresh(ctl.signal, token);
3579
4545
  } catch (refreshErr) {
3580
4546
  throw new Error(
3581
4547
  `unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3650,6 +4616,7 @@ var Client = class _Client {
3650
4616
  * 6 处原始 fetch() 全部走此 helper
3651
4617
  */
3652
4618
  async doRequest(req, signal) {
4619
+ if (new Headers(req.headers).has("Authorization")) this.assertCredentialURL(req.url);
3653
4620
  try {
3654
4621
  return await this.fetchImpl(req.url, {
3655
4622
  method: req.method,
@@ -4647,12 +5614,11 @@ async function uploadSkillInternal(c, zipData, scope, intent, retried, signal) {
4647
5614
  throw classifyTransport("POST /skill-store/upload", url, e);
4648
5615
  }
4649
5616
  if (resp.status === 401 && !retried) {
5617
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
5618
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
5619
+ if (!isUserAccessTokenRejected(authError)) throw authError;
4650
5620
  try {
4651
- await resp.body?.cancel();
4652
- } catch {
4653
- }
4654
- try {
4655
- await c.forceRefresh(ctl.signal);
5621
+ await c.forceRefresh(ctl.signal, token);
4656
5622
  } catch (refreshErr) {
4657
5623
  throw new Error(
4658
5624
  `upload: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -4823,9 +5789,7 @@ Client.prototype.updateNotificationPreference = async function(typeCode, pref, s
4823
5789
 
4824
5790
  // src/notifications/ws.ts
4825
5791
  Client.prototype.connect = async function(cfg, signal) {
4826
- if (this.ws) {
4827
- await this.disconnect();
4828
- }
5792
+ const oldDisconnect = this.ws ? this.disconnect() : Promise.resolve();
4829
5793
  const noop = () => {
4830
5794
  };
4831
5795
  const filledCfg = {
@@ -4841,21 +5805,31 @@ Client.prototype.connect = async function(cfg, signal) {
4841
5805
  const done = new Promise((r) => {
4842
5806
  resolveDone = r;
4843
5807
  });
4844
- const abort = new AbortController();
5808
+ const abort2 = new AbortController();
4845
5809
  if (signal) {
4846
- if (signal.aborted) abort.abort();
4847
- else signal.addEventListener("abort", () => abort.abort());
5810
+ if (signal.aborted) abort2.abort();
5811
+ else signal.addEventListener("abort", () => abort2.abort());
4848
5812
  }
4849
5813
  const ws = {
4850
5814
  conn: null,
4851
5815
  cfg: filledCfg,
4852
- abort,
5816
+ abort: abort2,
4853
5817
  done,
4854
5818
  doneResolve: resolveDone,
4855
- connected: false
5819
+ connected: false,
5820
+ owner: null
4856
5821
  };
4857
- await wsConnectOnce(this, ws);
4858
5822
  this.ws = ws;
5823
+ try {
5824
+ await oldDisconnect;
5825
+ await assertCurrent(this, ws);
5826
+ await wsConnectOnce(this, ws);
5827
+ } catch (error) {
5828
+ ws.abort.abort();
5829
+ if (this.ws === ws) this.ws = null;
5830
+ ws.doneResolve();
5831
+ throw error;
5832
+ }
4859
5833
  void wsLoop(this, ws);
4860
5834
  };
4861
5835
  Client.prototype.disconnect = async function() {
@@ -4893,15 +5867,47 @@ function getWebSocketCtor() {
4893
5867
  }
4894
5868
  return WSCtor;
4895
5869
  }
5870
+ function sameOwner(a, b) {
5871
+ return a?.storeInstanceId === b?.storeInstanceId && a?.authSessionId === b?.authSessionId && a?.principal?.issuer === b?.principal?.issuer && a?.principal?.subject === b?.principal?.subject && a?.principal?.organizationId === b?.principal?.organizationId;
5872
+ }
5873
+ async function readOwner(c, signal) {
5874
+ if (c.credentialMode !== "versioned") return null;
5875
+ const snapshot = await c.getCredentialSnapshot(signal);
5876
+ return {
5877
+ storeInstanceId: snapshot.storeInstanceId,
5878
+ authSessionId: snapshot.authSessionId,
5879
+ principal: snapshot.principal
5880
+ };
5881
+ }
5882
+ async function assertCurrent(c, ws) {
5883
+ if (ws.abort.signal.aborted || c.ws !== ws) throw new Error("websocket connection superseded");
5884
+ if (ws.owner !== null && !sameOwner(ws.owner, await readOwner(c, ws.abort.signal))) {
5885
+ ws.abort.abort();
5886
+ throw new Error("websocket credential owner changed");
5887
+ }
5888
+ }
4896
5889
  async function wsConnectOnce(c, ws) {
5890
+ const observedOwner = await readOwner(c, ws.abort.signal);
5891
+ if (ws.owner === null) ws.owner = observedOwner;
5892
+ else if (!sameOwner(ws.owner, observedOwner)) throw new Error("websocket credential owner changed");
5893
+ await assertCurrent(c, ws);
4897
5894
  const url = wsURL(c);
4898
5895
  const WSCtor = getWebSocketCtor();
4899
- const ticketResp = await c.doJSON(
4900
- "POST",
4901
- "/ws/stream-ticket",
4902
- null,
4903
- ws.abort.signal
4904
- );
5896
+ const token = await c.ensureToken(ws.abort.signal);
5897
+ await assertCurrent(c, ws);
5898
+ const ticketURL = c.apiURL("/ws/stream-ticket");
5899
+ await assertCurrent(c, ws);
5900
+ const ticketHTTP = await c.doRequest({
5901
+ method: "POST",
5902
+ url: ticketURL,
5903
+ headers: { Authorization: `Bearer ${token}` }
5904
+ }, ws.abort.signal);
5905
+ if (!ticketHTTP.ok) {
5906
+ const body = ticketHTTP.body ? await readLimited(ticketHTTP.body, maxErrorBodySize) : new Uint8Array();
5907
+ throw parseHTTPErrorWithHeader(ticketHTTP.status, body, ticketHTTP.headers);
5908
+ }
5909
+ const ticketResp = await ticketHTTP.json();
5910
+ await assertCurrent(c, ws);
4905
5911
  const ticket = ticketResp.data.ticket;
4906
5912
  const u = new URL(url);
4907
5913
  u.searchParams.set("ticket", ticket);
@@ -4922,11 +5928,28 @@ async function wsConnectOnce(c, ws) {
4922
5928
  reject(new Error("dial: handshake timeout"));
4923
5929
  }
4924
5930
  }, 3e4);
5931
+ const abortHandshake = () => {
5932
+ clearTimeout(handshakeTimer);
5933
+ try {
5934
+ conn.close();
5935
+ } catch {
5936
+ }
5937
+ reject(new Error("websocket connection aborted"));
5938
+ };
5939
+ ws.abort.signal.addEventListener("abort", abortHandshake, { once: true });
4925
5940
  conn.addEventListener("open", () => {
5941
+ if (ws.abort.signal.aborted || c.ws !== ws) {
5942
+ try {
5943
+ conn.close();
5944
+ } catch {
5945
+ }
5946
+ return;
5947
+ }
4926
5948
  opened = true;
4927
5949
  });
4928
5950
  conn.addEventListener("error", (e) => {
4929
5951
  clearTimeout(handshakeTimer);
5952
+ ws.abort.signal.removeEventListener("abort", abortHandshake);
4930
5953
  reject(new Error(`dial: ${e.message ?? "connection error"}`));
4931
5954
  });
4932
5955
  conn.addEventListener("message", (e) => {
@@ -4935,6 +5958,7 @@ async function wsConnectOnce(c, ws) {
4935
5958
  const welcome = JSON.parse(msg);
4936
5959
  if (welcome.type !== "welcome") {
4937
5960
  clearTimeout(handshakeTimer);
5961
+ ws.abort.signal.removeEventListener("abort", abortHandshake);
4938
5962
  try {
4939
5963
  conn.close();
4940
5964
  } catch {
@@ -4942,31 +5966,34 @@ async function wsConnectOnce(c, ws) {
4942
5966
  reject(new Error(`unexpected first message: ${welcome.type}`));
4943
5967
  return;
4944
5968
  }
4945
- clearTimeout(handshakeTimer);
4946
- ws.conn = conn;
4947
- ws.connected = true;
4948
- if (ws.cfg.topics.length > 0) {
4949
- try {
4950
- conn.send(
4951
- JSON.stringify({
4952
- type: "subscribe",
4953
- topics: ws.cfg.topics
4954
- })
4955
- );
4956
- } catch (sendErr) {
4957
- ws.conn = null;
4958
- ws.connected = false;
5969
+ void assertCurrent(c, ws).then(() => {
5970
+ clearTimeout(handshakeTimer);
5971
+ ws.abort.signal.removeEventListener("abort", abortHandshake);
5972
+ ws.conn = conn;
5973
+ ws.connected = true;
5974
+ if (ws.cfg.topics.length > 0) {
4959
5975
  try {
4960
- conn.close();
4961
- } catch {
5976
+ conn.send(
5977
+ JSON.stringify({
5978
+ type: "subscribe",
5979
+ topics: ws.cfg.topics
5980
+ })
5981
+ );
5982
+ } catch (sendErr) {
5983
+ ws.conn = null;
5984
+ ws.connected = false;
5985
+ try {
5986
+ conn.close();
5987
+ } catch {
5988
+ }
5989
+ reject(new Error(`send subscribe: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`));
5990
+ return;
4962
5991
  }
4963
- reject(new Error(`send subscribe: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`));
4964
- return;
4965
5992
  }
4966
- }
4967
- ws.cfg.onConnect();
4968
- console.log(`[acosmi-sdk] websocket connected, connId=${welcome.connId ?? ""}`);
4969
- resolve();
5993
+ ws.cfg.onConnect();
5994
+ console.log(`[acosmi-sdk] websocket connected, connId=${welcome.connId ?? ""}`);
5995
+ resolve();
5996
+ }).catch(reject);
4970
5997
  } catch (parseErr) {
4971
5998
  clearTimeout(handshakeTimer);
4972
5999
  try {
@@ -4981,7 +6008,7 @@ async function wsConnectOnce(c, ws) {
4981
6008
  async function wsLoop(c, ws) {
4982
6009
  try {
4983
6010
  while (true) {
4984
- await wsReadLoop(ws);
6011
+ await wsReadLoop(c, ws);
4985
6012
  if (ws.abort.signal.aborted) return;
4986
6013
  if (ws.conn) {
4987
6014
  try {
@@ -5012,7 +6039,7 @@ async function wsLoop(c, ws) {
5012
6039
  ws.doneResolve();
5013
6040
  }
5014
6041
  }
5015
- async function wsReadLoop(ws) {
6042
+ async function wsReadLoop(c, ws) {
5016
6043
  const conn = ws.conn;
5017
6044
  if (!conn) return;
5018
6045
  return new Promise((resolve) => {
@@ -5020,10 +6047,17 @@ async function wsReadLoop(ws) {
5020
6047
  try {
5021
6048
  const data = e.data;
5022
6049
  const event = JSON.parse(data);
5023
- try {
5024
- ws.cfg.onEvent(event);
5025
- } catch {
5026
- }
6050
+ void assertCurrent(c, ws).then(() => {
6051
+ try {
6052
+ ws.cfg.onEvent(event);
6053
+ } catch {
6054
+ }
6055
+ }).catch(() => {
6056
+ try {
6057
+ conn.close();
6058
+ } catch {
6059
+ }
6060
+ });
5027
6061
  } catch {
5028
6062
  }
5029
6063
  };
@@ -5236,6 +6270,7 @@ function isTerminalRemoteEvent(ev) {
5236
6270
  }
5237
6271
 
5238
6272
  // src/agent-runs/client.ts
6273
+ init_errors();
5239
6274
  var agentRunsByClient = /* @__PURE__ */ new WeakMap();
5240
6275
  Object.defineProperty(Client.prototype, "agentRuns", {
5241
6276
  configurable: true,
@@ -5615,11 +6650,10 @@ var AgentRunsClient = class {
5615
6650
  }
5616
6651
  const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
5617
6652
  if (resp.status === 401 && opts.retryOn401 && !retried) {
5618
- try {
5619
- await resp.body?.cancel();
5620
- } catch {
5621
- }
5622
- await this.client.forceRefresh(signal);
6653
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
6654
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
6655
+ if (!isUserAccessTokenRejected(authError)) throw authError;
6656
+ await this.client.forceRefresh(signal, token);
5623
6657
  return this.requestRawInner(method, path, body, signal, opts, true);
5624
6658
  }
5625
6659
  if (resp.status < 200 || resp.status >= 300) {
@@ -6246,6 +7280,7 @@ function isComplianceBusinessError(err) {
6246
7280
  }
6247
7281
 
6248
7282
  // src/compliance/client.ts
7283
+ init_errors();
6249
7284
  var cache = /* @__PURE__ */ new WeakMap();
6250
7285
  Object.defineProperty(Client.prototype, "compliance", {
6251
7286
  configurable: true,
@@ -7062,11 +8097,10 @@ var ComplianceClient = class {
7062
8097
  }
7063
8098
  const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
7064
8099
  if (resp.status === 401 && opts.retryOn401 && !retried) {
7065
- try {
7066
- await resp.body?.cancel();
7067
- } catch {
7068
- }
7069
- await this.client.forceRefresh(signal);
8100
+ const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
8101
+ const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
8102
+ if (!isUserAccessTokenRejected(authError)) throw authError;
8103
+ await this.client.forceRefresh(signal, token);
7070
8104
  return this.executeJsonInner(method, path, body, signal, opts, true);
7071
8105
  }
7072
8106
  if (resp.status < 200 || resp.status >= 300) {
@@ -7987,6 +9021,7 @@ exports.isRegion = isRegion;
7987
9021
  exports.isSSECommentLine = isSSECommentLine;
7988
9022
  exports.isSSLError = isSSLError;
7989
9023
  exports.isTerminalRemoteEvent = isTerminalRemoteEvent;
9024
+ exports.isUserAccessTokenRejected = isUserAccessTokenRejected;
7990
9025
  exports.isValidTokenSet = isValidTokenSet;
7991
9026
  exports.isWindowLimitError = isWindowLimitError;
7992
9027
  exports.isWindowLimitStreamError = isWindowLimitStreamError;
@@ -8004,6 +9039,7 @@ exports.parseNotificationEvent = parseNotificationEvent;
8004
9039
  exports.parseRemoteControlEvent = parseRemoteControlEvent;
8005
9040
  exports.parseSettlement = parseSettlement;
8006
9041
  exports.parseSourcesEvent = parseSourcesEvent;
9042
+ exports.readGatewayErrorContract = readGatewayErrorContract;
8007
9043
  exports.refreshToken = refreshToken;
8008
9044
  exports.register = register;
8009
9045
  exports.registerWebOAuthClient = registerWebOAuthClient;