@acosmi/sdk-ts 2.18.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) {
@@ -2319,6 +3063,22 @@ function notifyUpstreamActivity(cb) {
2319
3063
  } catch {
2320
3064
  }
2321
3065
  }
3066
+ var GATEWAY_REQUEST_ID_HEADER = "X-Acosmi-Request-Id";
3067
+ function readGatewayRequestID(headers) {
3068
+ const raw = headers.get(GATEWAY_REQUEST_ID_HEADER);
3069
+ if (!raw) return void 0;
3070
+ const trimmed = raw.trim();
3071
+ return trimmed === "" ? void 0 : trimmed;
3072
+ }
3073
+ function notifyGatewayRequestID(cb, headers) {
3074
+ if (!cb) return;
3075
+ const id = readGatewayRequestID(headers);
3076
+ if (id === void 0) return;
3077
+ try {
3078
+ cb(id);
3079
+ } catch {
3080
+ }
3081
+ }
2322
3082
  var DEFAULT_API_TIMEOUT_MS = 6e4;
2323
3083
  function newDeferred() {
2324
3084
  let resolve;
@@ -2349,6 +3109,13 @@ var Client = class _Client {
2349
3109
  tokens = null;
2350
3110
  /** token 持久化 */
2351
3111
  store;
3112
+ credentialMode;
3113
+ versionedCredentialStore;
3114
+ accessTokenProvider;
3115
+ beforeCredentialInstall;
3116
+ credentialAuthority;
3117
+ credentialRequestOwner;
3118
+ lifecycle = null;
2352
3119
  /** fetch 实现 (默认 globalThis.fetch) */
2353
3120
  fetchImpl;
2354
3121
  /** 互斥锁 (TS 用 Promise chain 替代 sync.Mutex) */
@@ -2379,6 +3146,7 @@ var Client = class _Client {
2379
3146
  /** V29 系数缓存 (TTL 8s, listCoefficients 内部用) */
2380
3147
  coefCacheData = null;
2381
3148
  coefCacheTimeMs = 0;
3149
+ credentialOwnerKey = null;
2382
3150
  /** 串行化锁 (替代 Go sync.Mutex) */
2383
3151
  coefMu = Promise.resolve();
2384
3152
  constructor(cfg = {}) {
@@ -2389,8 +3157,92 @@ var Client = class _Client {
2389
3157
  this.oauthMetadataProfile = cfg.oauthMetadataProfile ?? "desktop";
2390
3158
  this.browserRefreshMode = cfg.browserRefreshMode ?? "direct";
2391
3159
  this.refreshProxyURL = cfg.refreshProxyURL ?? null;
2392
- 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;
2393
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();
2394
3246
  this.retryPolicy = effectivePolicy(cfg.retryPolicy ?? null);
2395
3247
  }
2396
3248
  /**
@@ -2399,6 +3251,11 @@ var Client = class _Client {
2399
3251
  */
2400
3252
  static async create(cfg = {}) {
2401
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
+ }
2402
3259
  try {
2403
3260
  const tokens = await c.store.load();
2404
3261
  if (tokens) {
@@ -2412,6 +3269,63 @@ var Client = class _Client {
2412
3269
  }
2413
3270
  return c;
2414
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
+ }
2415
3329
  // ===========================================================================
2416
3330
  // 授权生命周期
2417
3331
  // ===========================================================================
@@ -2462,6 +3376,44 @@ var Client = class _Client {
2462
3376
  return this.loginInternal(appName, scopes, { handler, ...opts }, signal);
2463
3377
  }
2464
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
+ }
2465
3417
  const handler = opts?.handler ?? void 0;
2466
3418
  const emit = (e) => {
2467
3419
  if (handler) handler(e);
@@ -2570,6 +3522,11 @@ var Client = class _Client {
2570
3522
  }
2571
3523
  /** 吊销 token 并清除本地存储 */
2572
3524
  async logout(signal) {
3525
+ if (this.lifecycle) {
3526
+ await this.logoutCredential(signal);
3527
+ await this.reconcileCredentials(signal);
3528
+ return;
3529
+ }
2573
3530
  const tokens = this.tokens;
2574
3531
  let meta = this.meta;
2575
3532
  this.tokens = null;
@@ -2607,6 +3564,13 @@ var Client = class _Client {
2607
3564
  * 避免应用启动期 "login + 多个 API 调用" 并发场景下 4+ 条 "not authorized" 误报.
2608
3565
  */
2609
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);
2610
3574
  let tokens = this.tokens;
2611
3575
  const ready = this.tokenReady.promise;
2612
3576
  const inFlight = this.loginInFlight;
@@ -2654,7 +3618,14 @@ var Client = class _Client {
2654
3618
  );
2655
3619
  }
2656
3620
  /** 强制刷新 token (用于 401 重试) */
2657
- 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
+ }
2658
3629
  return this.withMu(
2659
3630
  () => this.storeWithLock(async () => {
2660
3631
  await this.syncFromDisk();
@@ -2849,6 +3820,7 @@ var Client = class _Client {
2849
3820
  */
2850
3821
  async listModelsWithStatus(signal, opts) {
2851
3822
  const includeLocked = opts?.includeLocked === true;
3823
+ const requestOwner = this.lifecycle ? this.ownerKey(await this.getCredentialSnapshot(signal)) : null;
2852
3824
  const path = includeLocked ? "/managed-models?picker=1" : "/managed-models";
2853
3825
  const { result, headers } = await this.doJSONFull(
2854
3826
  "GET",
@@ -2857,6 +3829,11 @@ var Client = class _Client {
2857
3829
  signal
2858
3830
  );
2859
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
+ }
2860
3837
  if (!includeLocked) {
2861
3838
  this.modelCache = normalized;
2862
3839
  this.modelCacheTimeMs = Date.now();
@@ -3026,7 +4003,7 @@ var Client = class _Client {
3026
4003
  * 响应的 tokenRemaining / callRemaining 字段来自服务端 Header, 反映结算后余额
3027
4004
  * v0.5.0: 根据 provider 自动路由到 /anthropic 或 /chat 端点
3028
4005
  */
3029
- async chat(modelID, req, signal) {
4006
+ async chat(modelID, req, signal, onGatewayRequestID) {
3030
4007
  const r = { ...req, stream: false };
3031
4008
  const ctl = withRequestTimeout(CHAT_REQUEST_TIMEOUT_MS, signal);
3032
4009
  try {
@@ -3039,6 +4016,7 @@ var Client = class _Client {
3039
4016
  ctl.signal,
3040
4017
  CHAT_REQUEST_TIMEOUT_MS
3041
4018
  );
4019
+ notifyGatewayRequestID(onGatewayRequestID, headers);
3042
4020
  const resp = adapter.parseResponse(result);
3043
4021
  const v1 = headers.get("X-Token-Remaining");
3044
4022
  if (v1) {
@@ -3159,28 +4137,29 @@ var Client = class _Client {
3159
4137
  * Anthropic → chatMessagesAnthropic (现有路径, POST /anthropic)
3160
4138
  * 其他厂商 → chatMessagesOpenAI (POST /chat, 响应转换为 AnthropicResponse)
3161
4139
  */
3162
- async chatMessages(modelID, req, signal) {
4140
+ async chatMessages(modelID, req, signal, onGatewayRequestID) {
3163
4141
  const m = await this.ensureModelCached(modelID, signal);
3164
4142
  const adapter = getAdapterForModel(m);
3165
4143
  if (adapter.format() === 0 /* Anthropic */) {
3166
- return this.chatMessagesAnthropic(modelID, req, adapter, signal);
4144
+ return this.chatMessagesAnthropic(modelID, req, adapter, signal, onGatewayRequestID);
3167
4145
  }
3168
- return this.chatMessagesOpenAI(modelID, req, adapter, signal);
4146
+ return this.chatMessagesOpenAI(modelID, req, adapter, signal, onGatewayRequestID);
3169
4147
  }
3170
- async chatMessagesAnthropic(modelID, req, adapter, signal) {
4148
+ async chatMessagesAnthropic(modelID, req, adapter, signal, onGatewayRequestID) {
3171
4149
  const r = { ...req, stream: false };
3172
4150
  const ctl = withRequestTimeout(CHAT_REQUEST_TIMEOUT_MS, signal);
3173
4151
  try {
3174
4152
  const caps = this.getCachedCapabilities(modelID) ?? zeroModelCapabilities();
3175
4153
  const body = adapter.buildRequestBody(caps, r);
3176
4154
  const data = JSON.stringify(body);
3177
- const { result } = await this.doJSONFullRaw(
4155
+ const { result, headers } = await this.doJSONFullRaw(
3178
4156
  "POST",
3179
4157
  `/managed-models/${encodeURIComponent(modelID)}/anthropic`,
3180
4158
  data,
3181
4159
  ctl.signal,
3182
4160
  CHAT_REQUEST_TIMEOUT_MS
3183
4161
  );
4162
+ notifyGatewayRequestID(onGatewayRequestID, headers);
3184
4163
  const rawStr = new TextDecoder().decode(result);
3185
4164
  try {
3186
4165
  const wrapper = JSON.parse(rawStr);
@@ -3205,7 +4184,7 @@ var Client = class _Client {
3205
4184
  ctl.dispose();
3206
4185
  }
3207
4186
  }
3208
- async chatMessagesOpenAI(modelID, req, adapter, signal) {
4187
+ async chatMessagesOpenAI(modelID, req, adapter, signal, onGatewayRequestID) {
3209
4188
  const r = { ...req, stream: false };
3210
4189
  const ctl = withRequestTimeout(CHAT_REQUEST_TIMEOUT_MS, signal);
3211
4190
  try {
@@ -3213,13 +4192,14 @@ var Client = class _Client {
3213
4192
  const body = adapter.buildRequestBody(caps, r);
3214
4193
  const data = JSON.stringify(body);
3215
4194
  const endpoint = `/managed-models/${encodeURIComponent(modelID)}${adapter.endpointSuffix()}`;
3216
- const { result } = await this.doJSONFullRaw(
4195
+ const { result, headers } = await this.doJSONFullRaw(
3217
4196
  "POST",
3218
4197
  endpoint,
3219
4198
  data,
3220
4199
  ctl.signal,
3221
4200
  CHAT_REQUEST_TIMEOUT_MS
3222
4201
  );
4202
+ notifyGatewayRequestID(onGatewayRequestID, headers);
3223
4203
  const { parseOpenAIResponseToAnthropic: parseOpenAIResponseToAnthropic2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
3224
4204
  return parseOpenAIResponseToAnthropic2(result);
3225
4205
  } finally {
@@ -3231,10 +4211,11 @@ var Client = class _Client {
3231
4211
  * v0.5.0: 根据 adapter 路由端点
3232
4212
  *
3233
4213
  * @param onUpstreamActivity 见 {@link UpstreamActivityCallback}
4214
+ * @param onGatewayRequestID 见 {@link GatewayRequestIDCallback}
3234
4215
  */
3235
- chatStream(modelID, req, signal, onUpstreamActivity) {
4216
+ chatStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID) {
3236
4217
  return {
3237
- [Symbol.asyncIterator]: () => this.chatStreamGen(modelID, req, signal, false, onUpstreamActivity)
4218
+ [Symbol.asyncIterator]: () => this.chatStreamGen(modelID, req, signal, false, onUpstreamActivity, onGatewayRequestID)
3238
4219
  };
3239
4220
  }
3240
4221
  /**
@@ -3243,13 +4224,14 @@ var Client = class _Client {
3243
4224
  * 无 started/settled/failed 自定义事件, 无 data: [DONE], message_stop 为自然结束
3244
4225
  *
3245
4226
  * @param onUpstreamActivity 见 {@link UpstreamActivityCallback}
4227
+ * @param onGatewayRequestID 见 {@link GatewayRequestIDCallback}
3246
4228
  */
3247
- chatMessagesStream(modelID, req, signal, onUpstreamActivity) {
4229
+ chatMessagesStream(modelID, req, signal, onUpstreamActivity, onGatewayRequestID) {
3248
4230
  return {
3249
- [Symbol.asyncIterator]: () => this.chatMessagesStreamGen(modelID, req, signal, false, onUpstreamActivity)
4231
+ [Symbol.asyncIterator]: () => this.chatMessagesStreamGen(modelID, req, signal, false, onUpstreamActivity, onGatewayRequestID)
3250
4232
  };
3251
4233
  }
3252
- async *chatStreamGen(modelID, req, signal, retried, onUpstreamActivity) {
4234
+ async *chatStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID) {
3253
4235
  const r = { ...req, stream: true };
3254
4236
  const { body, adapter } = await this.buildChatRequest(modelID, r, signal);
3255
4237
  const token = await this.ensureToken(signal);
@@ -3271,20 +4253,20 @@ var Client = class _Client {
3271
4253
  throw classifyTransport("POST " + endpoint, url, e);
3272
4254
  }
3273
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;
3274
4259
  try {
3275
- await resp.body?.cancel();
3276
- } catch {
3277
- }
3278
- try {
3279
- await this.forceRefresh(signal);
4260
+ await this.forceRefresh(signal, token);
3280
4261
  } catch (refreshErr) {
3281
4262
  throw new Error(
3282
4263
  `stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
3283
4264
  );
3284
4265
  }
3285
- yield* this.chatStreamGen(modelID, req, signal, true, onUpstreamActivity);
4266
+ yield* this.chatStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID);
3286
4267
  return;
3287
4268
  }
4269
+ notifyGatewayRequestID(onGatewayRequestID, resp.headers);
3288
4270
  if (!resp.ok) {
3289
4271
  const bodyBytes = await readLimited(resp.body, maxErrorBodySize);
3290
4272
  throw parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
@@ -3319,7 +4301,7 @@ var Client = class _Client {
3319
4301
  }
3320
4302
  }
3321
4303
  }
3322
- async *chatMessagesStreamGen(modelID, req, signal, retried, onUpstreamActivity) {
4304
+ async *chatMessagesStreamGen(modelID, req, signal, retried, onUpstreamActivity, onGatewayRequestID) {
3323
4305
  const r = { ...req, stream: true };
3324
4306
  const { body, adapter } = await this.buildChatRequest(modelID, r, signal);
3325
4307
  const token = await this.ensureToken(signal);
@@ -3341,20 +4323,20 @@ var Client = class _Client {
3341
4323
  throw classifyTransport("POST " + endpoint, url, e);
3342
4324
  }
3343
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;
3344
4329
  try {
3345
- await resp.body?.cancel();
3346
- } catch {
3347
- }
3348
- try {
3349
- await this.forceRefresh(signal);
4330
+ await this.forceRefresh(signal, token);
3350
4331
  } catch (refreshErr) {
3351
4332
  throw new Error(
3352
4333
  `messages stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
3353
4334
  );
3354
4335
  }
3355
- yield* this.chatMessagesStreamGen(modelID, req, signal, true, onUpstreamActivity);
4336
+ yield* this.chatMessagesStreamGen(modelID, req, signal, true, onUpstreamActivity, onGatewayRequestID);
3356
4337
  return;
3357
4338
  }
4339
+ notifyGatewayRequestID(onGatewayRequestID, resp.headers);
3358
4340
  if (!resp.ok) {
3359
4341
  const bodyBytes = await readLimited(resp.body, maxErrorBodySize);
3360
4342
  throw parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
@@ -3435,7 +4417,9 @@ var Client = class _Client {
3435
4417
  if (!base.endsWith("/api/v4")) {
3436
4418
  base += "/api/v4";
3437
4419
  }
3438
- return base + path;
4420
+ const url = base + path;
4421
+ this.assertCredentialURL(url);
4422
+ return url;
3439
4423
  }
3440
4424
  /**
3441
4425
  * Compliance API URL 拼接。
@@ -3448,7 +4432,14 @@ var Client = class _Client {
3448
4432
  */
3449
4433
  complianceURL(path) {
3450
4434
  const base = this.complianceBaseURL ?? this.serverURL + "/admin-api";
3451
- 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
+ }
3452
4443
  }
3453
4444
  /** GET/POST/... 通用 JSON 调用 (返回 result 已 typed) */
3454
4445
  async doJSON(method, path, body, signal) {
@@ -3477,12 +4468,11 @@ var Client = class _Client {
3477
4468
  ctl.signal
3478
4469
  );
3479
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;
3480
4474
  try {
3481
- await resp.body?.cancel();
3482
- } catch {
3483
- }
3484
- try {
3485
- await this.forceRefresh(ctl.signal);
4475
+ await this.forceRefresh(ctl.signal, token);
3486
4476
  } catch (refreshErr) {
3487
4477
  throw new Error(
3488
4478
  `unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3547,12 +4537,11 @@ var Client = class _Client {
3547
4537
  ctl.signal
3548
4538
  );
3549
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;
3550
4543
  try {
3551
- await resp.body?.cancel();
3552
- } catch {
3553
- }
3554
- try {
3555
- await this.forceRefresh(ctl.signal);
4544
+ await this.forceRefresh(ctl.signal, token);
3556
4545
  } catch (refreshErr) {
3557
4546
  throw new Error(
3558
4547
  `unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -3627,6 +4616,7 @@ var Client = class _Client {
3627
4616
  * 6 处原始 fetch() 全部走此 helper
3628
4617
  */
3629
4618
  async doRequest(req, signal) {
4619
+ if (new Headers(req.headers).has("Authorization")) this.assertCredentialURL(req.url);
3630
4620
  try {
3631
4621
  return await this.fetchImpl(req.url, {
3632
4622
  method: req.method,
@@ -4624,12 +5614,11 @@ async function uploadSkillInternal(c, zipData, scope, intent, retried, signal) {
4624
5614
  throw classifyTransport("POST /skill-store/upload", url, e);
4625
5615
  }
4626
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;
4627
5620
  try {
4628
- await resp.body?.cancel();
4629
- } catch {
4630
- }
4631
- try {
4632
- await c.forceRefresh(ctl.signal);
5621
+ await c.forceRefresh(ctl.signal, token);
4633
5622
  } catch (refreshErr) {
4634
5623
  throw new Error(
4635
5624
  `upload: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
@@ -4800,9 +5789,7 @@ Client.prototype.updateNotificationPreference = async function(typeCode, pref, s
4800
5789
 
4801
5790
  // src/notifications/ws.ts
4802
5791
  Client.prototype.connect = async function(cfg, signal) {
4803
- if (this.ws) {
4804
- await this.disconnect();
4805
- }
5792
+ const oldDisconnect = this.ws ? this.disconnect() : Promise.resolve();
4806
5793
  const noop = () => {
4807
5794
  };
4808
5795
  const filledCfg = {
@@ -4818,21 +5805,31 @@ Client.prototype.connect = async function(cfg, signal) {
4818
5805
  const done = new Promise((r) => {
4819
5806
  resolveDone = r;
4820
5807
  });
4821
- const abort = new AbortController();
5808
+ const abort2 = new AbortController();
4822
5809
  if (signal) {
4823
- if (signal.aborted) abort.abort();
4824
- else signal.addEventListener("abort", () => abort.abort());
5810
+ if (signal.aborted) abort2.abort();
5811
+ else signal.addEventListener("abort", () => abort2.abort());
4825
5812
  }
4826
5813
  const ws = {
4827
5814
  conn: null,
4828
5815
  cfg: filledCfg,
4829
- abort,
5816
+ abort: abort2,
4830
5817
  done,
4831
5818
  doneResolve: resolveDone,
4832
- connected: false
5819
+ connected: false,
5820
+ owner: null
4833
5821
  };
4834
- await wsConnectOnce(this, ws);
4835
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
+ }
4836
5833
  void wsLoop(this, ws);
4837
5834
  };
4838
5835
  Client.prototype.disconnect = async function() {
@@ -4870,15 +5867,47 @@ function getWebSocketCtor() {
4870
5867
  }
4871
5868
  return WSCtor;
4872
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
+ }
4873
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);
4874
5894
  const url = wsURL(c);
4875
5895
  const WSCtor = getWebSocketCtor();
4876
- const ticketResp = await c.doJSON(
4877
- "POST",
4878
- "/ws/stream-ticket",
4879
- null,
4880
- ws.abort.signal
4881
- );
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);
4882
5911
  const ticket = ticketResp.data.ticket;
4883
5912
  const u = new URL(url);
4884
5913
  u.searchParams.set("ticket", ticket);
@@ -4899,11 +5928,28 @@ async function wsConnectOnce(c, ws) {
4899
5928
  reject(new Error("dial: handshake timeout"));
4900
5929
  }
4901
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 });
4902
5940
  conn.addEventListener("open", () => {
5941
+ if (ws.abort.signal.aborted || c.ws !== ws) {
5942
+ try {
5943
+ conn.close();
5944
+ } catch {
5945
+ }
5946
+ return;
5947
+ }
4903
5948
  opened = true;
4904
5949
  });
4905
5950
  conn.addEventListener("error", (e) => {
4906
5951
  clearTimeout(handshakeTimer);
5952
+ ws.abort.signal.removeEventListener("abort", abortHandshake);
4907
5953
  reject(new Error(`dial: ${e.message ?? "connection error"}`));
4908
5954
  });
4909
5955
  conn.addEventListener("message", (e) => {
@@ -4912,6 +5958,7 @@ async function wsConnectOnce(c, ws) {
4912
5958
  const welcome = JSON.parse(msg);
4913
5959
  if (welcome.type !== "welcome") {
4914
5960
  clearTimeout(handshakeTimer);
5961
+ ws.abort.signal.removeEventListener("abort", abortHandshake);
4915
5962
  try {
4916
5963
  conn.close();
4917
5964
  } catch {
@@ -4919,31 +5966,34 @@ async function wsConnectOnce(c, ws) {
4919
5966
  reject(new Error(`unexpected first message: ${welcome.type}`));
4920
5967
  return;
4921
5968
  }
4922
- clearTimeout(handshakeTimer);
4923
- ws.conn = conn;
4924
- ws.connected = true;
4925
- if (ws.cfg.topics.length > 0) {
4926
- try {
4927
- conn.send(
4928
- JSON.stringify({
4929
- type: "subscribe",
4930
- topics: ws.cfg.topics
4931
- })
4932
- );
4933
- } catch (sendErr) {
4934
- ws.conn = null;
4935
- 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) {
4936
5975
  try {
4937
- conn.close();
4938
- } 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;
4939
5991
  }
4940
- reject(new Error(`send subscribe: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`));
4941
- return;
4942
5992
  }
4943
- }
4944
- ws.cfg.onConnect();
4945
- console.log(`[acosmi-sdk] websocket connected, connId=${welcome.connId ?? ""}`);
4946
- resolve();
5993
+ ws.cfg.onConnect();
5994
+ console.log(`[acosmi-sdk] websocket connected, connId=${welcome.connId ?? ""}`);
5995
+ resolve();
5996
+ }).catch(reject);
4947
5997
  } catch (parseErr) {
4948
5998
  clearTimeout(handshakeTimer);
4949
5999
  try {
@@ -4958,7 +6008,7 @@ async function wsConnectOnce(c, ws) {
4958
6008
  async function wsLoop(c, ws) {
4959
6009
  try {
4960
6010
  while (true) {
4961
- await wsReadLoop(ws);
6011
+ await wsReadLoop(c, ws);
4962
6012
  if (ws.abort.signal.aborted) return;
4963
6013
  if (ws.conn) {
4964
6014
  try {
@@ -4989,7 +6039,7 @@ async function wsLoop(c, ws) {
4989
6039
  ws.doneResolve();
4990
6040
  }
4991
6041
  }
4992
- async function wsReadLoop(ws) {
6042
+ async function wsReadLoop(c, ws) {
4993
6043
  const conn = ws.conn;
4994
6044
  if (!conn) return;
4995
6045
  return new Promise((resolve) => {
@@ -4997,10 +6047,17 @@ async function wsReadLoop(ws) {
4997
6047
  try {
4998
6048
  const data = e.data;
4999
6049
  const event = JSON.parse(data);
5000
- try {
5001
- ws.cfg.onEvent(event);
5002
- } catch {
5003
- }
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
+ });
5004
6061
  } catch {
5005
6062
  }
5006
6063
  };
@@ -5213,6 +6270,7 @@ function isTerminalRemoteEvent(ev) {
5213
6270
  }
5214
6271
 
5215
6272
  // src/agent-runs/client.ts
6273
+ init_errors();
5216
6274
  var agentRunsByClient = /* @__PURE__ */ new WeakMap();
5217
6275
  Object.defineProperty(Client.prototype, "agentRuns", {
5218
6276
  configurable: true,
@@ -5592,11 +6650,10 @@ var AgentRunsClient = class {
5592
6650
  }
5593
6651
  const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
5594
6652
  if (resp.status === 401 && opts.retryOn401 && !retried) {
5595
- try {
5596
- await resp.body?.cancel();
5597
- } catch {
5598
- }
5599
- 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);
5600
6657
  return this.requestRawInner(method, path, body, signal, opts, true);
5601
6658
  }
5602
6659
  if (resp.status < 200 || resp.status >= 300) {
@@ -6223,6 +7280,7 @@ function isComplianceBusinessError(err) {
6223
7280
  }
6224
7281
 
6225
7282
  // src/compliance/client.ts
7283
+ init_errors();
6226
7284
  var cache = /* @__PURE__ */ new WeakMap();
6227
7285
  Object.defineProperty(Client.prototype, "compliance", {
6228
7286
  configurable: true,
@@ -7039,11 +8097,10 @@ var ComplianceClient = class {
7039
8097
  }
7040
8098
  const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
7041
8099
  if (resp.status === 401 && opts.retryOn401 && !retried) {
7042
- try {
7043
- await resp.body?.cancel();
7044
- } catch {
7045
- }
7046
- 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);
7047
8104
  return this.executeJsonInner(method, path, body, signal, opts, true);
7048
8105
  }
7049
8106
  if (resp.status < 200 || resp.status >= 300) {
@@ -7871,6 +8928,7 @@ exports.FilterStatusFallbackTkdistSkew = FilterStatusFallbackTkdistSkew;
7871
8928
  exports.FilterStatusInternalBypass = FilterStatusInternalBypass;
7872
8929
  exports.FilterStatusOK = FilterStatusOK;
7873
8930
  exports.FilterStatusUnknown = FilterStatusUnknown;
8931
+ exports.GATEWAY_REQUEST_ID_HEADER = GATEWAY_REQUEST_ID_HEADER;
7874
8932
  exports.IdempotencyKeyHeader = IdempotencyKeyHeader;
7875
8933
  exports.InMemoryTokenStore = InMemoryTokenStore;
7876
8934
  exports.LocalStorageTokenStore = LocalStorageTokenStore;
@@ -7963,6 +9021,7 @@ exports.isRegion = isRegion;
7963
9021
  exports.isSSECommentLine = isSSECommentLine;
7964
9022
  exports.isSSLError = isSSLError;
7965
9023
  exports.isTerminalRemoteEvent = isTerminalRemoteEvent;
9024
+ exports.isUserAccessTokenRejected = isUserAccessTokenRejected;
7966
9025
  exports.isValidTokenSet = isValidTokenSet;
7967
9026
  exports.isWindowLimitError = isWindowLimitError;
7968
9027
  exports.isWindowLimitStreamError = isWindowLimitStreamError;
@@ -7980,6 +9039,7 @@ exports.parseNotificationEvent = parseNotificationEvent;
7980
9039
  exports.parseRemoteControlEvent = parseRemoteControlEvent;
7981
9040
  exports.parseSettlement = parseSettlement;
7982
9041
  exports.parseSourcesEvent = parseSourcesEvent;
9042
+ exports.readGatewayErrorContract = readGatewayErrorContract;
7983
9043
  exports.refreshToken = refreshToken;
7984
9044
  exports.register = register;
7985
9045
  exports.registerWebOAuthClient = registerWebOAuthClient;