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