@acosmi/sdk-ts 2.19.0 → 2.19.2
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/CHANGELOG.md +16 -0
- package/README.md +2 -1
- package/dist/browser/index.mjs +1205 -160
- package/dist/browser/index.mjs.map +1 -1
- package/dist/index.mjs +1205 -160
- package/dist/index.mjs.map +1 -1
- package/dist/node/adapters/anthropic.cjs +2 -0
- package/dist/node/adapters/anthropic.cjs.map +1 -1
- package/dist/node/adapters/anthropic.d.cts +1 -1
- package/dist/node/adapters/anthropic.d.ts +1 -1
- package/dist/node/adapters/anthropic.mjs +2 -0
- package/dist/node/adapters/anthropic.mjs.map +1 -1
- package/dist/node/adapters/openai.cjs +122 -49
- package/dist/node/adapters/openai.cjs.map +1 -1
- package/dist/node/adapters/openai.d.cts +2 -2
- package/dist/node/adapters/openai.d.ts +2 -2
- package/dist/node/adapters/openai.mjs +122 -49
- package/dist/node/adapters/openai.mjs.map +1 -1
- package/dist/node/{index-C2oh157O.d.cts → index-CqxPqbsl.d.cts} +2 -1
- package/dist/node/{index-C2oh157O.d.ts → index-CqxPqbsl.d.ts} +2 -1
- package/dist/node/index.cjs +1206 -159
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +346 -151
- package/dist/node/index.d.ts +346 -151
- package/dist/node/index.mjs +1205 -160
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/{openai-DR_KhEdM.d.cts → openai-Ce85qh14.d.cts} +30 -3
- package/dist/node/{openai-Cyvi8g6B.d.ts → openai-mkTSir6E.d.ts} +30 -3
- package/package.json +1 -1
package/dist/node/index.cjs
CHANGED
|
@@ -134,12 +134,13 @@ function parseSettlement(ev) {
|
|
|
134
134
|
callRemaining: s.callRemaining ?? -1
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
-
exports.BucketClassCommercial = void 0; exports.BucketClassGeneric = void 0; exports.ThinkingOff = void 0; exports.ThinkingHigh = void 0; exports.ThinkingMax = void 0; exports.ThinkingHighMinMaxTokens = void 0; exports.ThinkingMaxFallbackMaxTokens = void 0; exports.ServerToolTypeWebSearch = void 0;
|
|
137
|
+
exports.BucketClassCommercial = void 0; exports.BucketClassGeneric = void 0; exports.ThinkingOff = void 0; exports.ThinkingLow = void 0; exports.ThinkingHigh = void 0; exports.ThinkingMax = void 0; exports.ThinkingHighMinMaxTokens = void 0; exports.ThinkingMaxFallbackMaxTokens = void 0; exports.ServerToolTypeWebSearch = void 0;
|
|
138
138
|
var init_types = __esm({
|
|
139
139
|
"src/models/types.ts"() {
|
|
140
140
|
exports.BucketClassCommercial = "COMMERCIAL";
|
|
141
141
|
exports.BucketClassGeneric = "GENERIC";
|
|
142
142
|
exports.ThinkingOff = "off";
|
|
143
|
+
exports.ThinkingLow = "low";
|
|
143
144
|
exports.ThinkingHigh = "high";
|
|
144
145
|
exports.ThinkingMax = "max";
|
|
145
146
|
exports.ThinkingHighMinMaxTokens = 32e3;
|
|
@@ -149,6 +150,55 @@ var init_types = __esm({
|
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
// src/shared/errors.ts
|
|
153
|
+
function nullableID(value) {
|
|
154
|
+
return value === null || typeof value === "string";
|
|
155
|
+
}
|
|
156
|
+
function decodeGatewayErrorContract(value) {
|
|
157
|
+
if (value == null || typeof value !== "object") return null;
|
|
158
|
+
const v = value;
|
|
159
|
+
const version = v.errorContractVersion;
|
|
160
|
+
const domain = v.faultDomain;
|
|
161
|
+
const disposition = v.requestDisposition;
|
|
162
|
+
const errorCode = v.errorCode;
|
|
163
|
+
if (version !== 1 || typeof domain !== "string" || !gatewayFaultDomains.includes(domain)) return null;
|
|
164
|
+
if (typeof errorCode !== "string" || errorCode.length === 0) return null;
|
|
165
|
+
if (disposition !== "not_accepted" && disposition !== "accepted" && disposition !== "unknown") return null;
|
|
166
|
+
if (!nullableID(v.transportRequestId) || !nullableID(v.consumeRequestId) || !nullableID(v.providerRequestId)) return null;
|
|
167
|
+
if (typeof v.retryable !== "boolean") return null;
|
|
168
|
+
return {
|
|
169
|
+
errorContractVersion: 1,
|
|
170
|
+
faultDomain: domain,
|
|
171
|
+
errorCode,
|
|
172
|
+
transportRequestId: v.transportRequestId,
|
|
173
|
+
consumeRequestId: v.consumeRequestId,
|
|
174
|
+
providerRequestId: v.providerRequestId,
|
|
175
|
+
requestDisposition: disposition,
|
|
176
|
+
retryable: v.retryable
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function readGatewayErrorContract(error) {
|
|
180
|
+
const direct = decodeGatewayErrorContract(error);
|
|
181
|
+
if (direct) return direct;
|
|
182
|
+
if (error == null || typeof error !== "object") return null;
|
|
183
|
+
const e = error;
|
|
184
|
+
const axios = decodeGatewayErrorContract(e.response?.data);
|
|
185
|
+
if (axios) return axios;
|
|
186
|
+
if (typeof e.body === "string") {
|
|
187
|
+
try {
|
|
188
|
+
return decodeGatewayErrorContract(JSON.parse(e.body));
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
function isUserAccessTokenRejected(error) {
|
|
196
|
+
if (error == null || typeof error !== "object") return false;
|
|
197
|
+
const e = error;
|
|
198
|
+
const status = e.statusCode ?? e.response?.status;
|
|
199
|
+
const contract = readGatewayErrorContract(error);
|
|
200
|
+
return status === 401 && contract?.faultDomain === "user_auth" && contract.errorCode === "USER_ACCESS_TOKEN_INVALID" && contract.requestDisposition === "not_accepted";
|
|
201
|
+
}
|
|
152
202
|
function isWindowLimitError(err) {
|
|
153
203
|
if (!(err instanceof exports.HTTPError)) return false;
|
|
154
204
|
if (err.errorCode === windowLimitErrorCode) return true;
|
|
@@ -176,7 +226,7 @@ function getWindowLimitStreamDetails(err) {
|
|
|
176
226
|
windowOverridable: typeof e.windowOverridable === "boolean" ? e.windowOverridable : void 0
|
|
177
227
|
};
|
|
178
228
|
}
|
|
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;
|
|
229
|
+
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
230
|
var init_errors = __esm({
|
|
181
231
|
"src/shared/errors.ts"() {
|
|
182
232
|
exports.RateLimitError = class extends Error {
|
|
@@ -235,6 +285,13 @@ var init_errors = __esm({
|
|
|
235
285
|
* false/缺失 = 周窗/显式禁止/灰度关 (硬等待, 无豁免路径, 老网关不返回时为 undefined)。
|
|
236
286
|
*/
|
|
237
287
|
windowOverridable;
|
|
288
|
+
errorContractVersion;
|
|
289
|
+
faultDomain;
|
|
290
|
+
requestDisposition;
|
|
291
|
+
transportRequestId;
|
|
292
|
+
consumeRequestId;
|
|
293
|
+
providerRequestId;
|
|
294
|
+
retryable;
|
|
238
295
|
constructor(statusCode, opts = {}) {
|
|
239
296
|
let msg;
|
|
240
297
|
if (opts.type) {
|
|
@@ -256,6 +313,13 @@ var init_errors = __esm({
|
|
|
256
313
|
this.windowKind = opts.windowKind;
|
|
257
314
|
this.windowResetAt = opts.windowResetAt;
|
|
258
315
|
this.windowOverridable = opts.windowOverridable;
|
|
316
|
+
this.errorContractVersion = opts.errorContractVersion;
|
|
317
|
+
this.faultDomain = opts.faultDomain;
|
|
318
|
+
this.requestDisposition = opts.requestDisposition;
|
|
319
|
+
this.transportRequestId = opts.transportRequestId;
|
|
320
|
+
this.consumeRequestId = opts.consumeRequestId;
|
|
321
|
+
this.providerRequestId = opts.providerRequestId;
|
|
322
|
+
this.retryable = opts.retryable === true && opts.requestDisposition === "not_accepted";
|
|
259
323
|
}
|
|
260
324
|
};
|
|
261
325
|
exports.NetworkError = class extends Error {
|
|
@@ -266,6 +330,7 @@ var init_errors = __esm({
|
|
|
266
330
|
cause;
|
|
267
331
|
timeout;
|
|
268
332
|
eof;
|
|
333
|
+
requestDisposition = "unknown";
|
|
269
334
|
constructor(op, url, cause, opts = {}) {
|
|
270
335
|
const causeMsg = cause instanceof Error ? cause.message : cause != null ? String(cause) : "network error";
|
|
271
336
|
super(`${op} ${url}: ${causeMsg}`);
|
|
@@ -286,6 +351,8 @@ var init_errors = __esm({
|
|
|
286
351
|
exports.StreamError = class extends Error {
|
|
287
352
|
/** 例: "empty_response" / "rate_limit" / "overloaded" / "" */
|
|
288
353
|
code;
|
|
354
|
+
/** D23 gateway machine code; mirrors the legacy `code` field. */
|
|
355
|
+
errorCode;
|
|
289
356
|
/** 例: "provider" / "settlement" */
|
|
290
357
|
stage;
|
|
291
358
|
/** 用户友好提示 (中文); 历史字段, 与 rawError 区分 */
|
|
@@ -294,23 +361,47 @@ var init_errors = __esm({
|
|
|
294
361
|
rawError;
|
|
295
362
|
/** 客户端是否值得重试 */
|
|
296
363
|
retryable;
|
|
364
|
+
errorContractVersion;
|
|
365
|
+
faultDomain;
|
|
366
|
+
requestDisposition;
|
|
367
|
+
transportRequestId;
|
|
368
|
+
consumeRequestId;
|
|
369
|
+
providerRequestId;
|
|
297
370
|
constructor(opts = {}) {
|
|
298
371
|
const code = opts.code ?? "";
|
|
299
372
|
const stage = opts.stage ?? "";
|
|
300
373
|
const userMessage = opts.message ?? "";
|
|
301
374
|
const rawError = opts.rawError ?? "";
|
|
302
|
-
const retryable = opts.retryable
|
|
375
|
+
const retryable = opts.retryable === true && opts.requestDisposition === "not_accepted";
|
|
303
376
|
const body = rawError !== "" ? rawError : userMessage;
|
|
304
377
|
const msg = stage !== "" ? `stream failed: ${stage}: ${body}` : `stream failed: ${body}`;
|
|
305
378
|
super(msg);
|
|
306
379
|
this.name = "StreamError";
|
|
307
380
|
this.code = code;
|
|
381
|
+
this.errorCode = code;
|
|
308
382
|
this.stage = stage;
|
|
309
383
|
this.userMessage = userMessage;
|
|
310
384
|
this.rawError = rawError;
|
|
311
385
|
this.retryable = retryable;
|
|
386
|
+
this.errorContractVersion = opts.errorContractVersion;
|
|
387
|
+
this.faultDomain = opts.faultDomain;
|
|
388
|
+
this.requestDisposition = opts.requestDisposition;
|
|
389
|
+
this.transportRequestId = opts.transportRequestId;
|
|
390
|
+
this.consumeRequestId = opts.consumeRequestId;
|
|
391
|
+
this.providerRequestId = opts.providerRequestId;
|
|
312
392
|
}
|
|
313
393
|
};
|
|
394
|
+
gatewayFaultDomains = [
|
|
395
|
+
"user_auth",
|
|
396
|
+
"caller_credentials",
|
|
397
|
+
"account_quota",
|
|
398
|
+
"account_permission",
|
|
399
|
+
"provider",
|
|
400
|
+
"gateway",
|
|
401
|
+
"transport",
|
|
402
|
+
"stream_ticket",
|
|
403
|
+
"protocol"
|
|
404
|
+
];
|
|
314
405
|
windowLimitErrorCode = "WINDOW_LIMIT_EXCEEDED";
|
|
315
406
|
windowLimitStreamCode = "window_limit_exceeded";
|
|
316
407
|
}
|
|
@@ -416,6 +507,8 @@ function resolveThinkingLevel(body, req, caps) {
|
|
|
416
507
|
}
|
|
417
508
|
if (caps.supports_effort) {
|
|
418
509
|
let effortLevel = "high";
|
|
510
|
+
if (level === "low") effortLevel = "low";
|
|
511
|
+
if (level === "medium" || level === "xhigh") effortLevel = level;
|
|
419
512
|
if (level === exports.ThinkingMax && caps.supports_max_effort) {
|
|
420
513
|
effortLevel = "max";
|
|
421
514
|
}
|
|
@@ -593,23 +686,28 @@ __export(openai_exports, {
|
|
|
593
686
|
resolveOpenAIReasoningEffort: () => resolveOpenAIReasoningEffort,
|
|
594
687
|
resolveOpenAIResponseFormat: () => resolveOpenAIResponseFormat
|
|
595
688
|
});
|
|
596
|
-
function resolveOpenAIReasoningEffort(req) {
|
|
689
|
+
function resolveOpenAIReasoningEffort(req, supportsMax = false) {
|
|
597
690
|
if (req.effort && req.effort.level !== "") {
|
|
598
691
|
switch (req.effort.level) {
|
|
599
692
|
case "low":
|
|
600
693
|
case "medium":
|
|
601
694
|
case "high":
|
|
695
|
+
case "xhigh":
|
|
602
696
|
return req.effort.level;
|
|
603
697
|
case "max":
|
|
604
|
-
return "high";
|
|
698
|
+
return supportsMax ? "max" : "high";
|
|
605
699
|
}
|
|
606
700
|
}
|
|
607
701
|
if (req.thinking) {
|
|
608
702
|
switch (req.thinking.level) {
|
|
703
|
+
case "low":
|
|
704
|
+
case "medium":
|
|
705
|
+
case "xhigh":
|
|
706
|
+
return req.thinking.level;
|
|
609
707
|
case exports.ThinkingHigh:
|
|
610
708
|
return "high";
|
|
611
709
|
case exports.ThinkingMax:
|
|
612
|
-
return "high";
|
|
710
|
+
return supportsMax ? "max" : "high";
|
|
613
711
|
case exports.ThinkingOff:
|
|
614
712
|
return "";
|
|
615
713
|
}
|
|
@@ -813,10 +911,13 @@ var init_openai = __esm({
|
|
|
813
911
|
if (req.tools != null) {
|
|
814
912
|
body["tools"] = req.tools;
|
|
815
913
|
}
|
|
816
|
-
const eff = resolveOpenAIReasoningEffort(req);
|
|
914
|
+
const eff = resolveOpenAIReasoningEffort(req, _caps.supports_max_effort);
|
|
817
915
|
if (eff !== "") {
|
|
818
916
|
body["reasoning_effort"] = eff;
|
|
819
917
|
}
|
|
918
|
+
if (_caps.supports_thinking && req.thinking?.level === exports.ThinkingOff) {
|
|
919
|
+
body["thinking"] = { type: "disabled" };
|
|
920
|
+
}
|
|
820
921
|
if (req.speed && req.speed !== "") {
|
|
821
922
|
body["speed"] = req.speed;
|
|
822
923
|
}
|
|
@@ -893,16 +994,90 @@ var init_openai = __esm({
|
|
|
893
994
|
* 可能已被 text/tool 推进的 this.blockIndex (否则 content_block_stop 索引错配)。 */
|
|
894
995
|
thinkingBlockIndex = 0;
|
|
895
996
|
textStarted = false;
|
|
896
|
-
/** OpenAI tool_call
|
|
997
|
+
/** OpenAI tool_call 键 → Anthropic block index。键正常是 `tc.index`;上游省略
|
|
998
|
+
* index 时退化为 `id:<tool_call_id>`,两者都没有时沿用上一个键(见
|
|
999
|
+
* {@link resolveToolKey})。 */
|
|
897
1000
|
toolBlockIndex = /* @__PURE__ */ new Map();
|
|
898
1001
|
blockIndex = 0;
|
|
1002
|
+
/** 每个 tool block 已发出的 `partial_json` 累积,用于识别「每片重发全量参数」
|
|
1003
|
+
* 的上游(见 tool_calls 分支的累计判别)。 */
|
|
1004
|
+
toolArgsAccum = /* @__PURE__ */ new Map();
|
|
1005
|
+
/** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */
|
|
1006
|
+
lastToolKey = null;
|
|
1007
|
+
/** 已发出 message_delta/message_stop,避免 finish_reason 与 `[DONE]` 各收一次。 */
|
|
1008
|
+
messageClosed = false;
|
|
1009
|
+
/**
|
|
1010
|
+
* 解析一条 tool_call delta 归属的块键。
|
|
1011
|
+
*
|
|
1012
|
+
* OpenAI 流式规范里 `index` 是必填,但兼容实现常有省略。此前这里直接用
|
|
1013
|
+
* `tc.index` 做 Map 键:两个都省略 index 的 tool_call 会共用键 `undefined`,
|
|
1014
|
+
* 于是只开一个块、两段参数拼进同一条 `partial_json` 流,产出 `{…}{…}` 这种
|
|
1015
|
+
* 必然非法的 JSON。这里按「index → id → 沿用上一个」三级降级,让至少一种
|
|
1016
|
+
* 稳定标识生效。
|
|
1017
|
+
*/
|
|
1018
|
+
resolveToolKey(tc) {
|
|
1019
|
+
if (typeof tc.index === "number" && Number.isFinite(tc.index)) {
|
|
1020
|
+
this.lastToolKey = tc.index;
|
|
1021
|
+
return tc.index;
|
|
1022
|
+
}
|
|
1023
|
+
if (typeof tc.id === "string" && tc.id !== "") {
|
|
1024
|
+
const key = `id:${tc.id}`;
|
|
1025
|
+
this.lastToolKey = key;
|
|
1026
|
+
return key;
|
|
1027
|
+
}
|
|
1028
|
+
if (this.lastToolKey !== null) return this.lastToolKey;
|
|
1029
|
+
this.lastToolKey = 0;
|
|
1030
|
+
return 0;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* 关闭仍打开的 text / thinking / tool 块,并收口 message。
|
|
1034
|
+
*
|
|
1035
|
+
* 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发
|
|
1036
|
+
* `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个
|
|
1037
|
+
* 永不闭合的 tool_use 块。
|
|
1038
|
+
*/
|
|
1039
|
+
closeOpenBlocks(events, stopReason) {
|
|
1040
|
+
if (this.messageClosed) return;
|
|
1041
|
+
this.messageClosed = true;
|
|
1042
|
+
if (this.textStarted) {
|
|
1043
|
+
events.push({
|
|
1044
|
+
event: "content_block_stop",
|
|
1045
|
+
data: JSON.stringify({ type: "content_block_stop", index: this.blockIndex })
|
|
1046
|
+
});
|
|
1047
|
+
this.textStarted = false;
|
|
1048
|
+
} else if (this.thinkingStarted && !this.thinkingStopped) {
|
|
1049
|
+
this.thinkingStopped = true;
|
|
1050
|
+
events.push({
|
|
1051
|
+
event: "content_block_stop",
|
|
1052
|
+
data: JSON.stringify({ type: "content_block_stop", index: this.thinkingBlockIndex })
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
for (const idx of this.toolBlockIndex.values()) {
|
|
1056
|
+
events.push({
|
|
1057
|
+
event: "content_block_stop",
|
|
1058
|
+
data: JSON.stringify({ type: "content_block_stop", index: idx })
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
events.push({
|
|
1062
|
+
event: "message_delta",
|
|
1063
|
+
data: JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason } })
|
|
1064
|
+
});
|
|
1065
|
+
events.push({
|
|
1066
|
+
event: "message_stop",
|
|
1067
|
+
data: JSON.stringify({ type: "message_stop" })
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
899
1070
|
/**
|
|
900
1071
|
* 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent
|
|
901
1072
|
* 返回 { events, done }
|
|
902
1073
|
*/
|
|
903
1074
|
convert(data) {
|
|
904
1075
|
if (data === "[DONE]") {
|
|
905
|
-
|
|
1076
|
+
const events2 = [];
|
|
1077
|
+
if (this.messageStarted) {
|
|
1078
|
+
this.closeOpenBlocks(events2, "end_turn");
|
|
1079
|
+
}
|
|
1080
|
+
return { events: events2, done: true };
|
|
906
1081
|
}
|
|
907
1082
|
let chunk;
|
|
908
1083
|
try {
|
|
@@ -974,7 +1149,9 @@ var init_openai = __esm({
|
|
|
974
1149
|
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
975
1150
|
}
|
|
976
1151
|
for (const tc of choice.delta.tool_calls ?? []) {
|
|
977
|
-
|
|
1152
|
+
const fn = tc.function;
|
|
1153
|
+
const toolKey = this.resolveToolKey(tc);
|
|
1154
|
+
if (!this.toolBlockIndex.has(toolKey)) {
|
|
978
1155
|
if (this.thinkingStarted && !this.thinkingStopped) {
|
|
979
1156
|
this.thinkingStopped = true;
|
|
980
1157
|
const stopJSON = JSON.stringify({
|
|
@@ -993,56 +1170,46 @@ var init_openai = __esm({
|
|
|
993
1170
|
this.blockIndex++;
|
|
994
1171
|
this.textStarted = false;
|
|
995
1172
|
}
|
|
996
|
-
this.toolBlockIndex.set(
|
|
1173
|
+
this.toolBlockIndex.set(toolKey, this.blockIndex);
|
|
997
1174
|
const blockJSON = JSON.stringify({
|
|
998
1175
|
type: "content_block_start",
|
|
999
1176
|
index: this.blockIndex,
|
|
1000
1177
|
content_block: {
|
|
1001
1178
|
type: "tool_use",
|
|
1002
1179
|
id: tc.id,
|
|
1003
|
-
name:
|
|
1180
|
+
name: fn?.name,
|
|
1004
1181
|
input: {}
|
|
1005
1182
|
}
|
|
1006
1183
|
});
|
|
1007
1184
|
events.push({ event: "content_block_start", data: blockJSON });
|
|
1008
1185
|
this.blockIndex++;
|
|
1009
1186
|
}
|
|
1010
|
-
if (
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1187
|
+
if (fn?.arguments !== void 0 && fn.arguments !== null && fn.arguments !== "") {
|
|
1188
|
+
const rawArgs = typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments);
|
|
1189
|
+
const accum = this.toolArgsAccum.get(toolKey) ?? "";
|
|
1190
|
+
let emit = rawArgs;
|
|
1191
|
+
if (accum !== "" && rawArgs.length > accum.length && rawArgs.startsWith(accum)) {
|
|
1192
|
+
emit = rawArgs.slice(accum.length);
|
|
1193
|
+
this.toolArgsAccum.set(toolKey, rawArgs);
|
|
1194
|
+
} else {
|
|
1195
|
+
this.toolArgsAccum.set(toolKey, accum + rawArgs);
|
|
1196
|
+
}
|
|
1197
|
+
if (emit !== "") {
|
|
1198
|
+
const idx = this.toolBlockIndex.get(toolKey);
|
|
1199
|
+
const deltaJSON = JSON.stringify({
|
|
1200
|
+
type: "content_block_delta",
|
|
1201
|
+
index: idx,
|
|
1202
|
+
delta: {
|
|
1203
|
+
type: "input_json_delta",
|
|
1204
|
+
partial_json: emit
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
1208
|
+
}
|
|
1021
1209
|
}
|
|
1022
1210
|
}
|
|
1023
1211
|
if (choice.finish_reason != null && choice.finish_reason !== "") {
|
|
1024
|
-
|
|
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";
|
|
1212
|
+
let stopReason;
|
|
1046
1213
|
switch (choice.finish_reason) {
|
|
1047
1214
|
case "tool_calls":
|
|
1048
1215
|
stopReason = "tool_use";
|
|
@@ -1050,14 +1217,13 @@ var init_openai = __esm({
|
|
|
1050
1217
|
case "length":
|
|
1051
1218
|
stopReason = "max_tokens";
|
|
1052
1219
|
break;
|
|
1220
|
+
case "stop":
|
|
1221
|
+
stopReason = "end_turn";
|
|
1222
|
+
break;
|
|
1223
|
+
default:
|
|
1224
|
+
stopReason = choice.finish_reason;
|
|
1053
1225
|
}
|
|
1054
|
-
|
|
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 });
|
|
1226
|
+
this.closeOpenBlocks(events, stopReason);
|
|
1061
1227
|
}
|
|
1062
1228
|
return { events, done: false };
|
|
1063
1229
|
}
|
|
@@ -1118,6 +1284,560 @@ var init_adapters = __esm({
|
|
|
1118
1284
|
}
|
|
1119
1285
|
});
|
|
1120
1286
|
|
|
1287
|
+
// src/core/credentials.ts
|
|
1288
|
+
var uuid = () => globalThis.crypto.randomUUID();
|
|
1289
|
+
var pause = () => new Promise((resolve) => setTimeout(resolve, 25));
|
|
1290
|
+
var abort = (signal) => {
|
|
1291
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
1292
|
+
};
|
|
1293
|
+
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;
|
|
1294
|
+
var CredentialLifecycle = class {
|
|
1295
|
+
constructor(store, protocol) {
|
|
1296
|
+
this.store = store;
|
|
1297
|
+
this.protocol = protocol;
|
|
1298
|
+
}
|
|
1299
|
+
store;
|
|
1300
|
+
protocol;
|
|
1301
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1302
|
+
notificationKey = "";
|
|
1303
|
+
observations = /* @__PURE__ */ new Map();
|
|
1304
|
+
read(signal) {
|
|
1305
|
+
return this.store.readSnapshot(signal);
|
|
1306
|
+
}
|
|
1307
|
+
subscribe(listener) {
|
|
1308
|
+
this.listeners.add(listener);
|
|
1309
|
+
return () => {
|
|
1310
|
+
this.listeners.delete(listener);
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
async notify(snapshot) {
|
|
1314
|
+
let current;
|
|
1315
|
+
try {
|
|
1316
|
+
current = await this.read();
|
|
1317
|
+
} catch {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (current.storeInstanceId !== snapshot.storeInstanceId || current.revision !== snapshot.revision)
|
|
1321
|
+
return;
|
|
1322
|
+
const projection = {
|
|
1323
|
+
storeInstanceId: snapshot.storeInstanceId,
|
|
1324
|
+
authorityConfig: snapshot.authorityConfig && {
|
|
1325
|
+
serverURL: snapshot.authorityConfig.serverURL,
|
|
1326
|
+
issuer: snapshot.authorityConfig.issuer,
|
|
1327
|
+
oauthProfile: "desktop",
|
|
1328
|
+
authContractVersion: 2,
|
|
1329
|
+
errorContractVersion: 1
|
|
1330
|
+
},
|
|
1331
|
+
revision: snapshot.revision,
|
|
1332
|
+
authSessionId: snapshot.authSessionId,
|
|
1333
|
+
credentialState: snapshot.credentialState,
|
|
1334
|
+
reason: snapshot.reason,
|
|
1335
|
+
principal: snapshot.principal && {
|
|
1336
|
+
issuer: snapshot.principal.issuer,
|
|
1337
|
+
subject: snapshot.principal.subject,
|
|
1338
|
+
organizationId: snapshot.principal.organizationId
|
|
1339
|
+
},
|
|
1340
|
+
refreshOperation: snapshot.refreshOperation && {
|
|
1341
|
+
operationId: snapshot.refreshOperation.operationId,
|
|
1342
|
+
sessionId: snapshot.refreshOperation.sessionId,
|
|
1343
|
+
baseRevision: snapshot.refreshOperation.baseRevision,
|
|
1344
|
+
phase: snapshot.refreshOperation.phase,
|
|
1345
|
+
returnState: snapshot.refreshOperation.returnState,
|
|
1346
|
+
startedAt: snapshot.refreshOperation.startedAt,
|
|
1347
|
+
dispatchedAt: snapshot.refreshOperation.dispatchedAt,
|
|
1348
|
+
deadlineAt: snapshot.refreshOperation.deadlineAt
|
|
1349
|
+
},
|
|
1350
|
+
loginAttempt: snapshot.loginAttempt && {
|
|
1351
|
+
attemptId: snapshot.loginAttempt.attemptId,
|
|
1352
|
+
baseSessionId: snapshot.loginAttempt.baseSessionId,
|
|
1353
|
+
startedAt: snapshot.loginAttempt.startedAt
|
|
1354
|
+
},
|
|
1355
|
+
lastMutation: snapshot.lastMutation && {
|
|
1356
|
+
mutationId: snapshot.lastMutation.mutationId,
|
|
1357
|
+
operationId: snapshot.lastMutation.operationId,
|
|
1358
|
+
resultRevision: snapshot.lastMutation.resultRevision
|
|
1359
|
+
},
|
|
1360
|
+
lastLoginAttemptId: snapshot.lastLoginAttemptId,
|
|
1361
|
+
verifiedIdentity: snapshot.verifiedIdentity && {
|
|
1362
|
+
authSessionId: snapshot.verifiedIdentity.authSessionId,
|
|
1363
|
+
principal: {
|
|
1364
|
+
issuer: snapshot.verifiedIdentity.principal.issuer,
|
|
1365
|
+
subject: snapshot.verifiedIdentity.principal.subject,
|
|
1366
|
+
organizationId: snapshot.verifiedIdentity.principal.organizationId
|
|
1367
|
+
},
|
|
1368
|
+
displayName: snapshot.verifiedIdentity.displayName,
|
|
1369
|
+
avatarUrl: snapshot.verifiedIdentity.avatarUrl,
|
|
1370
|
+
email: snapshot.verifiedIdentity.email,
|
|
1371
|
+
imageUrl: snapshot.verifiedIdentity.imageUrl,
|
|
1372
|
+
accountCreatedAt: snapshot.verifiedIdentity.accountCreatedAt,
|
|
1373
|
+
requiresPhoneBinding: snapshot.verifiedIdentity.requiresPhoneBinding,
|
|
1374
|
+
hasExtraUsageEnabled: snapshot.verifiedIdentity.hasExtraUsageEnabled,
|
|
1375
|
+
billingType: snapshot.verifiedIdentity.billingType,
|
|
1376
|
+
subscriptionCreatedAt: snapshot.verifiedIdentity.subscriptionCreatedAt,
|
|
1377
|
+
rateLimitTier: snapshot.verifiedIdentity.rateLimitTier,
|
|
1378
|
+
organizationName: snapshot.verifiedIdentity.organizationName,
|
|
1379
|
+
verifiedAt: snapshot.verifiedIdentity.verifiedAt
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
const key = JSON.stringify({ ...projection, revision: void 0, lastMutation: void 0 });
|
|
1383
|
+
if (key === this.notificationKey) return;
|
|
1384
|
+
this.notificationKey = key;
|
|
1385
|
+
for (const listener of this.listeners) {
|
|
1386
|
+
try {
|
|
1387
|
+
listener(structuredClone(projection));
|
|
1388
|
+
} catch {
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async reconcile(signal) {
|
|
1393
|
+
const s = await this.read(signal);
|
|
1394
|
+
await this.notify(s);
|
|
1395
|
+
return s;
|
|
1396
|
+
}
|
|
1397
|
+
cas(s, changes, mutationId = uuid(), signal) {
|
|
1398
|
+
return this.store.compareAndSwap(
|
|
1399
|
+
{
|
|
1400
|
+
storeInstanceId: s.storeInstanceId,
|
|
1401
|
+
revision: s.revision,
|
|
1402
|
+
authSessionId: s.authSessionId,
|
|
1403
|
+
state: s.credentialState,
|
|
1404
|
+
operationId: s.refreshOperation?.operationId ?? s.loginAttempt?.attemptId ?? null
|
|
1405
|
+
},
|
|
1406
|
+
{ ...s, ...changes },
|
|
1407
|
+
mutationId,
|
|
1408
|
+
signal
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
async commit(s, changes, signal) {
|
|
1412
|
+
const r = await this.cas(s, changes, uuid(), signal);
|
|
1413
|
+
if (r.status === "storage_error") throw new Error("storage_unavailable");
|
|
1414
|
+
if (r.status === "committed") await this.notify(r.snapshot);
|
|
1415
|
+
return r;
|
|
1416
|
+
}
|
|
1417
|
+
async metadata(s, signal) {
|
|
1418
|
+
const m = await this.protocol.metadata(signal);
|
|
1419
|
+
if (m.crabcode_auth_contract_version !== 2 || m.gateway_error_contract_version !== 1 || !m.issuer)
|
|
1420
|
+
throw new Error("auth_contract_unsupported");
|
|
1421
|
+
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))
|
|
1422
|
+
throw new Error("auth_contract_unsupported");
|
|
1423
|
+
return m;
|
|
1424
|
+
}
|
|
1425
|
+
async reserveLogin(signal) {
|
|
1426
|
+
const attemptId = uuid();
|
|
1427
|
+
let reserved;
|
|
1428
|
+
for (; ; ) {
|
|
1429
|
+
abort(signal);
|
|
1430
|
+
const s = await this.read(signal);
|
|
1431
|
+
if (s.credentialState !== "signed_out") throw new Error("local_logout_required");
|
|
1432
|
+
const r = await this.commit(s, { loginAttempt: { attemptId, baseSessionId: s.authSessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() } }, signal);
|
|
1433
|
+
if (r.status === "committed") {
|
|
1434
|
+
reserved = r.snapshot;
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
const m = await this.metadata(reserved, signal);
|
|
1440
|
+
for (; ; ) {
|
|
1441
|
+
abort(signal);
|
|
1442
|
+
const current = await this.read(signal);
|
|
1443
|
+
if (current.storeInstanceId !== reserved.storeInstanceId || current.loginAttempt?.attemptId !== attemptId) throw new Error("superseded");
|
|
1444
|
+
if (current.authorityConfig) return { attemptId, metadata: m };
|
|
1445
|
+
const r = await this.commit(current, { authorityConfig: { serverURL: this.protocol.serverURL, issuer: m.issuer, oauthProfile: "desktop", authContractVersion: 2, errorContractVersion: 1 } }, signal);
|
|
1446
|
+
if (r.status === "committed") return { attemptId, metadata: m };
|
|
1447
|
+
}
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
await this.cancelLogin(attemptId);
|
|
1450
|
+
throw error;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
async installLogin(attemptId, tokens, signal) {
|
|
1454
|
+
this.validateTokens(tokens);
|
|
1455
|
+
for (; ; ) {
|
|
1456
|
+
abort(signal);
|
|
1457
|
+
const s = await this.read(signal);
|
|
1458
|
+
if (s.loginAttempt?.attemptId !== attemptId || s.loginAttempt.baseSessionId !== s.authSessionId)
|
|
1459
|
+
throw new Error("superseded");
|
|
1460
|
+
const r = await this.commit(
|
|
1461
|
+
s,
|
|
1462
|
+
{
|
|
1463
|
+
authSessionId: uuid(),
|
|
1464
|
+
tokenSet: tokens,
|
|
1465
|
+
credentialState: "pending_identity",
|
|
1466
|
+
principal: null,
|
|
1467
|
+
verifiedIdentity: null,
|
|
1468
|
+
loginAttempt: null,
|
|
1469
|
+
lastLoginAttemptId: attemptId,
|
|
1470
|
+
reason: "identity_unavailable"
|
|
1471
|
+
},
|
|
1472
|
+
signal
|
|
1473
|
+
);
|
|
1474
|
+
if (r.status === "committed") return this.bindIdentity(r.snapshot.authSessionId, signal);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async assertLogin(attemptId, signal) {
|
|
1478
|
+
abort(signal);
|
|
1479
|
+
const s = await this.read(signal);
|
|
1480
|
+
if (s.loginAttempt?.attemptId !== attemptId) throw new Error("superseded");
|
|
1481
|
+
}
|
|
1482
|
+
async cancelLogin(attemptId) {
|
|
1483
|
+
const s = await this.read();
|
|
1484
|
+
if (s.loginAttempt?.attemptId === attemptId) await this.commit(s, { loginAttempt: null });
|
|
1485
|
+
}
|
|
1486
|
+
validateTokens(t) {
|
|
1487
|
+
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())
|
|
1488
|
+
throw new Error("invalid_response");
|
|
1489
|
+
}
|
|
1490
|
+
usable(s) {
|
|
1491
|
+
return !!s.tokenSet && Date.parse(s.tokenSet.expires_at) > Date.now() + 3e5;
|
|
1492
|
+
}
|
|
1493
|
+
async bindIdentity(sessionId, signal) {
|
|
1494
|
+
const s = await this.ensureSnapshot(signal, void 0, true);
|
|
1495
|
+
if (s.authSessionId !== sessionId) throw new Error("superseded");
|
|
1496
|
+
if (s.credentialState === "ready") return s;
|
|
1497
|
+
await this.metadata(s, signal);
|
|
1498
|
+
const beforeProfile = await this.read(signal);
|
|
1499
|
+
if (beforeProfile.storeInstanceId !== s.storeInstanceId || beforeProfile.authSessionId !== sessionId || beforeProfile.revision !== s.revision || beforeProfile.credentialState !== "pending_identity")
|
|
1500
|
+
throw new Error("superseded");
|
|
1501
|
+
let identity;
|
|
1502
|
+
try {
|
|
1503
|
+
identity = await this.protocol.profile(s.tokenSet, signal);
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
const message = error instanceof Error ? error.message : "";
|
|
1506
|
+
if (message === "invalid_scope" || message === "auth_contract_unsupported")
|
|
1507
|
+
await this.commit(s, { credentialState: "configuration_error", reason: message });
|
|
1508
|
+
throw error;
|
|
1509
|
+
}
|
|
1510
|
+
abort(signal);
|
|
1511
|
+
const current = await this.read(signal);
|
|
1512
|
+
if (current.storeInstanceId !== s.storeInstanceId || current.authSessionId !== sessionId || current.revision !== s.revision)
|
|
1513
|
+
throw new Error("superseded");
|
|
1514
|
+
if (!identity.subject) throw new Error("invalid_response");
|
|
1515
|
+
const principal = {
|
|
1516
|
+
issuer: s.authorityConfig.issuer,
|
|
1517
|
+
subject: identity.subject,
|
|
1518
|
+
organizationId: identity.organizationId
|
|
1519
|
+
};
|
|
1520
|
+
const r = await this.commit(
|
|
1521
|
+
s,
|
|
1522
|
+
{
|
|
1523
|
+
principal,
|
|
1524
|
+
verifiedIdentity: {
|
|
1525
|
+
authSessionId: sessionId,
|
|
1526
|
+
principal,
|
|
1527
|
+
displayName: identity.displayName,
|
|
1528
|
+
avatarUrl: identity.avatarUrl,
|
|
1529
|
+
email: identity.email,
|
|
1530
|
+
imageUrl: identity.imageUrl,
|
|
1531
|
+
accountCreatedAt: identity.accountCreatedAt,
|
|
1532
|
+
requiresPhoneBinding: identity.requiresPhoneBinding,
|
|
1533
|
+
hasExtraUsageEnabled: identity.hasExtraUsageEnabled,
|
|
1534
|
+
billingType: identity.billingType,
|
|
1535
|
+
subscriptionCreatedAt: identity.subscriptionCreatedAt,
|
|
1536
|
+
rateLimitTier: identity.rateLimitTier,
|
|
1537
|
+
organizationName: identity.organizationName,
|
|
1538
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1539
|
+
},
|
|
1540
|
+
credentialState: "ready",
|
|
1541
|
+
reason: null
|
|
1542
|
+
},
|
|
1543
|
+
signal
|
|
1544
|
+
);
|
|
1545
|
+
if (r.status !== "committed") throw new Error("superseded");
|
|
1546
|
+
return r.snapshot;
|
|
1547
|
+
}
|
|
1548
|
+
async ensure(signal, rejected, expected) {
|
|
1549
|
+
const snapshot = await this.ensureSnapshot(signal, rejected, false, expected);
|
|
1550
|
+
this.observations.set(snapshot.tokenSet.access_token, snapshot);
|
|
1551
|
+
if (this.observations.size > 128)
|
|
1552
|
+
this.observations.delete(this.observations.keys().next().value);
|
|
1553
|
+
return snapshot.tokenSet.access_token;
|
|
1554
|
+
}
|
|
1555
|
+
async forceRefresh(signal, rejectedToken, expected) {
|
|
1556
|
+
const rejected = rejectedToken ? this.observations.get(rejectedToken) : await this.read(signal);
|
|
1557
|
+
if (rejectedToken && !rejected) throw new Error("credential_observation_unavailable");
|
|
1558
|
+
return this.ensure(signal, rejected, expected);
|
|
1559
|
+
}
|
|
1560
|
+
async ensureSnapshot(signal, rejected, pending = false, expected) {
|
|
1561
|
+
let session;
|
|
1562
|
+
let instance;
|
|
1563
|
+
let initialRevision;
|
|
1564
|
+
let refreshed = false;
|
|
1565
|
+
let initialAccess;
|
|
1566
|
+
for (; ; ) {
|
|
1567
|
+
abort(signal);
|
|
1568
|
+
const s = await this.read(signal);
|
|
1569
|
+
if (expected && !sameRequestOwner(s, expected)) throw new Error("superseded");
|
|
1570
|
+
if (s.authorityConfig && s.authorityConfig.serverURL !== this.protocol.serverURL)
|
|
1571
|
+
throw new Error("auth_contract_unsupported");
|
|
1572
|
+
if (session === void 0) {
|
|
1573
|
+
session = s.authSessionId;
|
|
1574
|
+
instance = s.storeInstanceId;
|
|
1575
|
+
initialRevision = s.revision;
|
|
1576
|
+
initialAccess = s.tokenSet?.access_token;
|
|
1577
|
+
}
|
|
1578
|
+
if (s.storeInstanceId !== instance || s.authSessionId !== session || rejected && (s.authSessionId !== rejected.authSessionId || s.storeInstanceId !== rejected.storeInstanceId))
|
|
1579
|
+
throw new Error("superseded");
|
|
1580
|
+
if (s.credentialState === "refresh_dispatched") {
|
|
1581
|
+
if (Date.now() >= Date.parse(s.refreshOperation.deadlineAt))
|
|
1582
|
+
await this.commit(s, {
|
|
1583
|
+
credentialState: "reauth_required",
|
|
1584
|
+
tokenSet: null,
|
|
1585
|
+
refreshOperation: null,
|
|
1586
|
+
reason: "refresh_outcome_unknown"
|
|
1587
|
+
});
|
|
1588
|
+
else await pause();
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (s.credentialState === "refresh_reserved") {
|
|
1592
|
+
if (Date.now() >= Date.parse(s.refreshOperation.startedAt) + 3e4)
|
|
1593
|
+
await this.commit(s, {
|
|
1594
|
+
credentialState: s.refreshOperation.returnState,
|
|
1595
|
+
refreshOperation: null
|
|
1596
|
+
});
|
|
1597
|
+
else await pause();
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (s.credentialState !== "ready" && !(pending && s.credentialState === "pending_identity"))
|
|
1601
|
+
throw new Error(`credential_unavailable:${s.credentialState}:${s.reason ?? ""}`);
|
|
1602
|
+
const live = !!s.tokenSet && Date.parse(s.tokenSet.expires_at) > Date.now();
|
|
1603
|
+
if (live && (refreshed || s.revision !== (rejected?.revision ?? initialRevision) && (rejected !== void 0 || s.tokenSet.access_token !== initialAccess)))
|
|
1604
|
+
return s;
|
|
1605
|
+
if (this.usable(s) && !rejected) return s;
|
|
1606
|
+
const metadata = await this.metadata(s, signal);
|
|
1607
|
+
abort(signal);
|
|
1608
|
+
const operationId = uuid();
|
|
1609
|
+
const r = await this.commit(
|
|
1610
|
+
s,
|
|
1611
|
+
{
|
|
1612
|
+
credentialState: "refresh_reserved",
|
|
1613
|
+
refreshOperation: {
|
|
1614
|
+
operationId,
|
|
1615
|
+
sessionId: s.authSessionId,
|
|
1616
|
+
baseRevision: s.revision,
|
|
1617
|
+
phase: "reserved",
|
|
1618
|
+
returnState: s.credentialState,
|
|
1619
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1620
|
+
dispatchedAt: null,
|
|
1621
|
+
deadlineAt: null
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
signal
|
|
1625
|
+
);
|
|
1626
|
+
if (r.status !== "committed") continue;
|
|
1627
|
+
const owner = this.refresh(r.snapshot, metadata);
|
|
1628
|
+
await this.wait(owner, signal);
|
|
1629
|
+
refreshed = true;
|
|
1630
|
+
rejected = void 0;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
wait(promise, signal) {
|
|
1634
|
+
if (!signal) return promise;
|
|
1635
|
+
return new Promise((resolve, reject) => {
|
|
1636
|
+
const onAbort = () => {
|
|
1637
|
+
signal.removeEventListener("abort", onAbort);
|
|
1638
|
+
reject(new Error("aborted"));
|
|
1639
|
+
};
|
|
1640
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1641
|
+
promise.then(
|
|
1642
|
+
(value) => {
|
|
1643
|
+
signal.removeEventListener("abort", onAbort);
|
|
1644
|
+
resolve(value);
|
|
1645
|
+
},
|
|
1646
|
+
(error) => {
|
|
1647
|
+
signal.removeEventListener("abort", onAbort);
|
|
1648
|
+
reject(error);
|
|
1649
|
+
}
|
|
1650
|
+
);
|
|
1651
|
+
if (signal.aborted) onAbort();
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
async refresh(reserved, metadata) {
|
|
1655
|
+
const dispatchedAt = Date.now();
|
|
1656
|
+
const r = await this.commit(reserved, {
|
|
1657
|
+
credentialState: "refresh_dispatched",
|
|
1658
|
+
refreshOperation: {
|
|
1659
|
+
...reserved.refreshOperation,
|
|
1660
|
+
phase: "dispatched",
|
|
1661
|
+
dispatchedAt: new Date(dispatchedAt).toISOString(),
|
|
1662
|
+
deadlineAt: new Date(dispatchedAt + 3e4).toISOString()
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
if (r.status !== "committed") return;
|
|
1666
|
+
const s = r.snapshot;
|
|
1667
|
+
const ctl = new AbortController();
|
|
1668
|
+
const timeout = setTimeout(() => ctl.abort(), 3e4);
|
|
1669
|
+
let tokens;
|
|
1670
|
+
try {
|
|
1671
|
+
const result = await this.wait(
|
|
1672
|
+
(async () => {
|
|
1673
|
+
const response = await this.protocol.fetch(metadata.token_endpoint, {
|
|
1674
|
+
method: "POST",
|
|
1675
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1676
|
+
body: new URLSearchParams({
|
|
1677
|
+
grant_type: "refresh_token",
|
|
1678
|
+
client_id: s.tokenSet.client_id,
|
|
1679
|
+
refresh_token: s.tokenSet.refresh_token
|
|
1680
|
+
}),
|
|
1681
|
+
signal: ctl.signal
|
|
1682
|
+
});
|
|
1683
|
+
const body2 = await response.json();
|
|
1684
|
+
return { response, body: body2 };
|
|
1685
|
+
})(),
|
|
1686
|
+
ctl.signal
|
|
1687
|
+
);
|
|
1688
|
+
if (!result.response.ok) {
|
|
1689
|
+
const code = result.body.error;
|
|
1690
|
+
let reason = "refresh_outcome_unknown";
|
|
1691
|
+
let state = "reauth_required";
|
|
1692
|
+
if (code === "invalid_grant") reason = code;
|
|
1693
|
+
else if (["invalid_client", "invalid_scope", "unsupported_grant_type"].includes(code)) {
|
|
1694
|
+
reason = code;
|
|
1695
|
+
state = "configuration_error";
|
|
1696
|
+
} else if (result.body.rotationOutcome === "not_committed" && ["temporarily_unavailable", "server_error"].includes(code)) {
|
|
1697
|
+
await this.commit(s, {
|
|
1698
|
+
credentialState: s.refreshOperation.returnState,
|
|
1699
|
+
refreshOperation: null
|
|
1700
|
+
});
|
|
1701
|
+
throw new Error("refresh_temporarily_unavailable");
|
|
1702
|
+
}
|
|
1703
|
+
await this.commit(s, {
|
|
1704
|
+
credentialState: state,
|
|
1705
|
+
tokenSet: null,
|
|
1706
|
+
refreshOperation: null,
|
|
1707
|
+
reason
|
|
1708
|
+
});
|
|
1709
|
+
throw new Error(reason);
|
|
1710
|
+
}
|
|
1711
|
+
const body = result.body;
|
|
1712
|
+
if (!Number.isFinite(body.expires_in) || body.expires_in <= 0)
|
|
1713
|
+
throw new Error("invalid_response");
|
|
1714
|
+
tokens = {
|
|
1715
|
+
...s.tokenSet,
|
|
1716
|
+
access_token: body.access_token,
|
|
1717
|
+
refresh_token: body.refresh_token,
|
|
1718
|
+
expires_at: new Date(Date.now() + body.expires_in * 1e3).toISOString(),
|
|
1719
|
+
scope: body.scope ?? s.tokenSet.scope
|
|
1720
|
+
};
|
|
1721
|
+
this.validateTokens(tokens);
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
await this.commit(s, {
|
|
1724
|
+
credentialState: "reauth_required",
|
|
1725
|
+
tokenSet: null,
|
|
1726
|
+
refreshOperation: null,
|
|
1727
|
+
reason: "refresh_outcome_unknown"
|
|
1728
|
+
});
|
|
1729
|
+
throw error;
|
|
1730
|
+
} finally {
|
|
1731
|
+
clearTimeout(timeout);
|
|
1732
|
+
}
|
|
1733
|
+
const started = performance.now(), mutationId = uuid();
|
|
1734
|
+
for (; ; ) {
|
|
1735
|
+
if (Date.now() >= Date.parse(s.refreshOperation.deadlineAt)) {
|
|
1736
|
+
await this.commit(s, {
|
|
1737
|
+
credentialState: "reauth_required",
|
|
1738
|
+
tokenSet: null,
|
|
1739
|
+
refreshOperation: null,
|
|
1740
|
+
reason: "refresh_outcome_unknown"
|
|
1741
|
+
});
|
|
1742
|
+
void this.revoke(tokens, metadata);
|
|
1743
|
+
throw new Error("refresh_outcome_unknown");
|
|
1744
|
+
}
|
|
1745
|
+
const result = await this.cas(
|
|
1746
|
+
s,
|
|
1747
|
+
{
|
|
1748
|
+
tokenSet: tokens,
|
|
1749
|
+
credentialState: s.refreshOperation.returnState,
|
|
1750
|
+
refreshOperation: null
|
|
1751
|
+
},
|
|
1752
|
+
mutationId
|
|
1753
|
+
);
|
|
1754
|
+
if (result.status === "committed") {
|
|
1755
|
+
await this.notify(result.snapshot);
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (result.status === "superseded") {
|
|
1759
|
+
void this.revoke(tokens, metadata);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
if (performance.now() - started >= 2e3) throw new Error("credential_persist_failed");
|
|
1763
|
+
await pause();
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
async revoke(tokens, metadata, authority) {
|
|
1767
|
+
const ctl = new AbortController();
|
|
1768
|
+
const timer = setTimeout(() => ctl.abort(), 3e4);
|
|
1769
|
+
try {
|
|
1770
|
+
const m = metadata ?? await this.wait(authority ? this.metadata(authority, ctl.signal) : this.protocol.metadata(ctl.signal), ctl.signal);
|
|
1771
|
+
if (!m.revocation_endpoint) return "unsupported";
|
|
1772
|
+
const response = await this.wait(this.protocol.fetch(m.revocation_endpoint, {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1775
|
+
body: new URLSearchParams({ token: tokens.refresh_token, token_type_hint: "refresh_token" }),
|
|
1776
|
+
signal: ctl.signal
|
|
1777
|
+
}), ctl.signal);
|
|
1778
|
+
return response.ok ? "confirmed" : "failed";
|
|
1779
|
+
} catch {
|
|
1780
|
+
return "failed";
|
|
1781
|
+
} finally {
|
|
1782
|
+
clearTimeout(timer);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
async logout(signal, expected) {
|
|
1786
|
+
let s = await this.read(signal);
|
|
1787
|
+
if (expected && (s.storeInstanceId !== expected.storeInstanceId || s.authSessionId !== expected.authSessionId)) {
|
|
1788
|
+
return {
|
|
1789
|
+
logoutOperationId: uuid(),
|
|
1790
|
+
expectedAuthSessionId: expected.authSessionId,
|
|
1791
|
+
storeInstanceId: expected.storeInstanceId,
|
|
1792
|
+
revision: s.revision,
|
|
1793
|
+
status: "superseded"
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
const session = s.authSessionId, instance = s.storeInstanceId, started = performance.now(), logoutOperationId = uuid();
|
|
1797
|
+
const receipt = () => ({
|
|
1798
|
+
logoutOperationId,
|
|
1799
|
+
expectedAuthSessionId: session,
|
|
1800
|
+
storeInstanceId: instance,
|
|
1801
|
+
revision: s.revision
|
|
1802
|
+
});
|
|
1803
|
+
for (; ; ) {
|
|
1804
|
+
abort(signal);
|
|
1805
|
+
if (s.storeInstanceId !== instance || s.authSessionId !== session)
|
|
1806
|
+
return { ...receipt(), status: "superseded" };
|
|
1807
|
+
if (s.credentialState === "signed_out" && !s.loginAttempt)
|
|
1808
|
+
return { ...receipt(), status: "already_signed_out" };
|
|
1809
|
+
const r = await this.cas(
|
|
1810
|
+
s,
|
|
1811
|
+
{
|
|
1812
|
+
credentialState: "signed_out",
|
|
1813
|
+
authSessionId: null,
|
|
1814
|
+
principal: null,
|
|
1815
|
+
tokenSet: null,
|
|
1816
|
+
refreshOperation: null,
|
|
1817
|
+
loginAttempt: null,
|
|
1818
|
+
lastLoginAttemptId: null,
|
|
1819
|
+
verifiedIdentity: null,
|
|
1820
|
+
reason: null
|
|
1821
|
+
},
|
|
1822
|
+
logoutOperationId,
|
|
1823
|
+
signal
|
|
1824
|
+
);
|
|
1825
|
+
if (r.status === "committed") {
|
|
1826
|
+
await this.notify(r.snapshot);
|
|
1827
|
+
return {
|
|
1828
|
+
...receipt(),
|
|
1829
|
+
revision: r.snapshot.revision,
|
|
1830
|
+
status: "committed",
|
|
1831
|
+
revocation: s.tokenSet ? this.revoke(s.tokenSet, void 0, s) : Promise.resolve("unsupported")
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
if (performance.now() - started >= 2e3) throw new Error("storage_busy");
|
|
1835
|
+
await pause();
|
|
1836
|
+
s = await this.read(signal);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1121
1841
|
// src/core/client.ts
|
|
1122
1842
|
init_types();
|
|
1123
1843
|
|
|
@@ -1557,33 +2277,35 @@ async function revokeToken(meta, token, signal, fetchImpl = globalThis.fetch) {
|
|
|
1557
2277
|
}
|
|
1558
2278
|
async function postToken(endpoint, data, signal, fetchImpl = globalThis.fetch) {
|
|
1559
2279
|
const ctl = withTimeout(authTimeoutMs, signal);
|
|
1560
|
-
let resp;
|
|
1561
2280
|
try {
|
|
1562
|
-
resp
|
|
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 = {};
|
|
2281
|
+
let resp;
|
|
1575
2282
|
try {
|
|
1576
|
-
|
|
1577
|
-
|
|
2283
|
+
resp = await fetchImpl(endpoint, {
|
|
2284
|
+
method: "POST",
|
|
2285
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2286
|
+
body: data,
|
|
2287
|
+
signal: ctl.signal
|
|
2288
|
+
});
|
|
2289
|
+
} catch (e) {
|
|
2290
|
+
throw new Error(`token request: ${e instanceof Error ? e.message : String(e)}`);
|
|
1578
2291
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2292
|
+
if (!resp.ok) {
|
|
2293
|
+
let errBody = {};
|
|
2294
|
+
try {
|
|
2295
|
+
errBody = await resp.json();
|
|
2296
|
+
} catch {
|
|
2297
|
+
}
|
|
2298
|
+
const oauthError = typeof errBody.error === "string" ? errBody.error : "";
|
|
2299
|
+
const errorDescription = typeof errBody.error_description === "string" ? errBody.error_description : "";
|
|
2300
|
+
throw new OAuthTokenEndpointError(resp.status, oauthError, errorDescription);
|
|
2301
|
+
}
|
|
2302
|
+
try {
|
|
2303
|
+
return await resp.json();
|
|
2304
|
+
} catch (e) {
|
|
2305
|
+
throw new Error(`token: decode: ${e instanceof Error ? e.message : String(e)}`);
|
|
2306
|
+
}
|
|
2307
|
+
} finally {
|
|
2308
|
+
ctl.dispose();
|
|
1587
2309
|
}
|
|
1588
2310
|
}
|
|
1589
2311
|
function newTokenSet(resp, clientID, serverURL) {
|
|
@@ -2066,10 +2788,31 @@ function parseHTTPErrorWithHeader(statusCode, body, header) {
|
|
|
2066
2788
|
} else if (typeof top.message === "string") {
|
|
2067
2789
|
message = top.message;
|
|
2068
2790
|
}
|
|
2791
|
+
const contractSource = errObj && typeof errObj === "object" ? errObj : top;
|
|
2069
2792
|
if (typeof top.errorCode === "string") errorCode = top.errorCode;
|
|
2793
|
+
else if (typeof contractSource.errorCode === "string") errorCode = contractSource.errorCode;
|
|
2070
2794
|
if (top.windowKind === "FIVE_HOUR" || top.windowKind === "WEEKLY") windowKind = top.windowKind;
|
|
2071
2795
|
if (typeof top.windowResetAt === "string") windowResetAt = top.windowResetAt;
|
|
2072
2796
|
if (typeof top.windowOverridable === "boolean") windowOverridable = top.windowOverridable;
|
|
2797
|
+
const disposition = contractSource.requestDisposition === "not_accepted" || contractSource.requestDisposition === "accepted" || contractSource.requestDisposition === "unknown" ? contractSource.requestDisposition : void 0;
|
|
2798
|
+
const domains = ["user_auth", "caller_credentials", "account_quota", "account_permission", "provider", "gateway", "transport", "stream_ticket", "protocol"];
|
|
2799
|
+
return new exports.HTTPError(statusCode, {
|
|
2800
|
+
type,
|
|
2801
|
+
message,
|
|
2802
|
+
retryAfter,
|
|
2803
|
+
body: bodyStr,
|
|
2804
|
+
errorCode,
|
|
2805
|
+
windowKind,
|
|
2806
|
+
windowResetAt,
|
|
2807
|
+
windowOverridable,
|
|
2808
|
+
errorContractVersion: contractSource.errorContractVersion === 1 ? 1 : void 0,
|
|
2809
|
+
faultDomain: typeof contractSource.faultDomain === "string" && domains.includes(contractSource.faultDomain) ? contractSource.faultDomain : void 0,
|
|
2810
|
+
requestDisposition: disposition,
|
|
2811
|
+
transportRequestId: typeof contractSource.transportRequestId === "string" ? contractSource.transportRequestId : null,
|
|
2812
|
+
consumeRequestId: typeof contractSource.consumeRequestId === "string" ? contractSource.consumeRequestId : null,
|
|
2813
|
+
providerRequestId: typeof contractSource.providerRequestId === "string" ? contractSource.providerRequestId : null,
|
|
2814
|
+
retryable: contractSource.retryable === true
|
|
2815
|
+
});
|
|
2073
2816
|
}
|
|
2074
2817
|
} catch {
|
|
2075
2818
|
}
|
|
@@ -2131,7 +2874,19 @@ function parseStreamError(data) {
|
|
|
2131
2874
|
if (code === "" && errObj.type) code = errObj.type;
|
|
2132
2875
|
}
|
|
2133
2876
|
}
|
|
2134
|
-
return new exports.StreamError({
|
|
2877
|
+
return new exports.StreamError({
|
|
2878
|
+
code,
|
|
2879
|
+
stage,
|
|
2880
|
+
message,
|
|
2881
|
+
rawError,
|
|
2882
|
+
retryable,
|
|
2883
|
+
errorContractVersion: payload.errorContractVersion,
|
|
2884
|
+
faultDomain: payload.faultDomain,
|
|
2885
|
+
requestDisposition: payload.requestDisposition,
|
|
2886
|
+
transportRequestId: payload.transportRequestId,
|
|
2887
|
+
consumeRequestId: payload.consumeRequestId,
|
|
2888
|
+
providerRequestId: payload.providerRequestId
|
|
2889
|
+
});
|
|
2135
2890
|
}
|
|
2136
2891
|
function isOrderSuccess(status) {
|
|
2137
2892
|
switch (status) {
|
|
@@ -2365,6 +3120,13 @@ var Client = class _Client {
|
|
|
2365
3120
|
tokens = null;
|
|
2366
3121
|
/** token 持久化 */
|
|
2367
3122
|
store;
|
|
3123
|
+
credentialMode;
|
|
3124
|
+
versionedCredentialStore;
|
|
3125
|
+
accessTokenProvider;
|
|
3126
|
+
beforeCredentialInstall;
|
|
3127
|
+
credentialAuthority;
|
|
3128
|
+
credentialRequestOwner;
|
|
3129
|
+
lifecycle = null;
|
|
2368
3130
|
/** fetch 实现 (默认 globalThis.fetch) */
|
|
2369
3131
|
fetchImpl;
|
|
2370
3132
|
/** 互斥锁 (TS 用 Promise chain 替代 sync.Mutex) */
|
|
@@ -2395,6 +3157,7 @@ var Client = class _Client {
|
|
|
2395
3157
|
/** V29 系数缓存 (TTL 8s, listCoefficients 内部用) */
|
|
2396
3158
|
coefCacheData = null;
|
|
2397
3159
|
coefCacheTimeMs = 0;
|
|
3160
|
+
credentialOwnerKey = null;
|
|
2398
3161
|
/** 串行化锁 (替代 Go sync.Mutex) */
|
|
2399
3162
|
coefMu = Promise.resolve();
|
|
2400
3163
|
constructor(cfg = {}) {
|
|
@@ -2405,8 +3168,92 @@ var Client = class _Client {
|
|
|
2405
3168
|
this.oauthMetadataProfile = cfg.oauthMetadataProfile ?? "desktop";
|
|
2406
3169
|
this.browserRefreshMode = cfg.browserRefreshMode ?? "direct";
|
|
2407
3170
|
this.refreshProxyURL = cfg.refreshProxyURL ?? null;
|
|
2408
|
-
this.
|
|
3171
|
+
this.credentialMode = cfg.credentialMode ?? "legacy";
|
|
3172
|
+
this.credentialAuthority = new URL(this.serverURL).origin;
|
|
3173
|
+
if (this.credentialMode === "versioned" && !cfg.versionedCredentialStore) {
|
|
3174
|
+
throw new Error("versionedCredentialStore is required for credentialMode=versioned");
|
|
3175
|
+
}
|
|
3176
|
+
if (cfg.credentialRequestOwner && this.credentialMode !== "versioned") {
|
|
3177
|
+
throw new Error("credentialRequestOwner requires credentialMode=versioned");
|
|
3178
|
+
}
|
|
3179
|
+
if (this.credentialMode === "legacy" && cfg.versionedCredentialStore) {
|
|
3180
|
+
throw new Error("credentialMode=versioned is required when versionedCredentialStore is provided");
|
|
3181
|
+
}
|
|
3182
|
+
if (this.credentialMode === "external" && cfg.versionedCredentialStore) {
|
|
3183
|
+
throw new Error("versionedCredentialStore is incompatible with credentialMode=external");
|
|
3184
|
+
}
|
|
3185
|
+
if (this.credentialMode !== "external" && cfg.accessTokenProvider) {
|
|
3186
|
+
throw new Error("accessTokenProvider requires credentialMode=external");
|
|
3187
|
+
}
|
|
3188
|
+
if (this.credentialMode === "external" && !cfg.accessTokenProvider) {
|
|
3189
|
+
throw new Error("accessTokenProvider is required for credentialMode=external");
|
|
3190
|
+
}
|
|
3191
|
+
if (this.credentialMode !== "versioned" && cfg.beforeCredentialInstall) {
|
|
3192
|
+
throw new Error("beforeCredentialInstall requires credentialMode=versioned");
|
|
3193
|
+
}
|
|
3194
|
+
if (this.credentialMode !== "legacy") {
|
|
3195
|
+
const authority = new URL(this.serverURL).origin;
|
|
3196
|
+
for (const [name, override] of [["apiBaseURL", this.apiBaseURL], ["complianceBaseURL", this.complianceBaseURL]]) {
|
|
3197
|
+
if (override && new URL(override).origin !== authority) {
|
|
3198
|
+
throw new Error(`${name} must use the credential authority ${authority}`);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
this.accessTokenProvider = cfg.accessTokenProvider;
|
|
3203
|
+
this.beforeCredentialInstall = cfg.beforeCredentialInstall;
|
|
3204
|
+
this.credentialRequestOwner = cfg.credentialRequestOwner ? {
|
|
3205
|
+
storeInstanceId: cfg.credentialRequestOwner.storeInstanceId,
|
|
3206
|
+
authSessionId: cfg.credentialRequestOwner.authSessionId,
|
|
3207
|
+
principal: cfg.credentialRequestOwner.principal ? {
|
|
3208
|
+
issuer: cfg.credentialRequestOwner.principal.issuer,
|
|
3209
|
+
subject: cfg.credentialRequestOwner.principal.subject,
|
|
3210
|
+
organizationId: cfg.credentialRequestOwner.principal.organizationId
|
|
3211
|
+
} : null
|
|
3212
|
+
} : null;
|
|
2409
3213
|
this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
3214
|
+
this.versionedCredentialStore = cfg.versionedCredentialStore ?? null;
|
|
3215
|
+
if (this.versionedCredentialStore) this.lifecycle = new CredentialLifecycle(this.versionedCredentialStore, {
|
|
3216
|
+
serverURL: this.serverURL,
|
|
3217
|
+
fetch: this.fetchImpl,
|
|
3218
|
+
metadata: (signal) => discoverWithProfile(this.serverURL, "desktop", signal, this.fetchImpl),
|
|
3219
|
+
profile: async (tokens, signal) => {
|
|
3220
|
+
const gatewayRoot = this.serverURL.replace(/\/api\/v4$/, "");
|
|
3221
|
+
const profileURL = `${gatewayRoot}/api/oauth/profile`;
|
|
3222
|
+
this.assertCredentialURL(profileURL);
|
|
3223
|
+
const response = await this.fetchImpl(profileURL, { headers: { Authorization: `Bearer ${tokens.access_token}` }, signal });
|
|
3224
|
+
if (!response.ok) {
|
|
3225
|
+
const bodyBytes = response.body ? await readLimited(response.body, maxErrorBodySize) : new Uint8Array();
|
|
3226
|
+
const failure = parseHTTPErrorWithHeader(response.status, bodyBytes, response.headers);
|
|
3227
|
+
const contract = readGatewayErrorContract(failure);
|
|
3228
|
+
const code = contract?.errorCode ?? null;
|
|
3229
|
+
if (code === "INVALID_SCOPE") throw new Error("invalid_scope");
|
|
3230
|
+
if (code === "AUTH_CONTRACT_UNSUPPORTED") throw new Error("auth_contract_unsupported");
|
|
3231
|
+
if (code === "ACCOUNT_NOT_FOUND") throw new Error("account_permission");
|
|
3232
|
+
throw new Error("identity_unavailable");
|
|
3233
|
+
}
|
|
3234
|
+
const body = await response.json();
|
|
3235
|
+
const subject = body.account?.uuid;
|
|
3236
|
+
if (typeof subject !== "string" || !subject) throw new Error("invalid_response");
|
|
3237
|
+
const account = body.account;
|
|
3238
|
+
const organization = body.organization;
|
|
3239
|
+
return {
|
|
3240
|
+
subject,
|
|
3241
|
+
organizationId: organization?.uuid || null,
|
|
3242
|
+
...typeof account.display_name === "string" ? { displayName: account.display_name } : {},
|
|
3243
|
+
...[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") } : {},
|
|
3244
|
+
...typeof account.email === "string" ? { email: account.email } : {},
|
|
3245
|
+
...typeof account.image_url === "string" ? { imageUrl: account.image_url } : {},
|
|
3246
|
+
...typeof account.created_at === "string" ? { accountCreatedAt: account.created_at } : {},
|
|
3247
|
+
...typeof account.requires_phone_binding === "boolean" ? { requiresPhoneBinding: account.requires_phone_binding } : {},
|
|
3248
|
+
...typeof organization?.has_extra_usage_enabled === "boolean" ? { hasExtraUsageEnabled: organization.has_extra_usage_enabled } : {},
|
|
3249
|
+
...typeof organization?.billing_type === "string" ? { billingType: organization.billing_type } : {},
|
|
3250
|
+
...typeof organization?.subscription_created_at === "string" ? { subscriptionCreatedAt: organization.subscription_created_at } : {},
|
|
3251
|
+
...typeof organization?.rate_limit_tier === "string" ? { rateLimitTier: organization.rate_limit_tier } : {},
|
|
3252
|
+
...typeof organization?.name === "string" ? { organizationName: organization.name } : {}
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
});
|
|
3256
|
+
this.store = cfg.store ?? defaultTokenStore();
|
|
2410
3257
|
this.retryPolicy = effectivePolicy(cfg.retryPolicy ?? null);
|
|
2411
3258
|
}
|
|
2412
3259
|
/**
|
|
@@ -2415,6 +3262,11 @@ var Client = class _Client {
|
|
|
2415
3262
|
*/
|
|
2416
3263
|
static async create(cfg = {}) {
|
|
2417
3264
|
const c = new _Client(cfg);
|
|
3265
|
+
if (c.credentialMode === "external") return c;
|
|
3266
|
+
if (c.credentialMode === "versioned") {
|
|
3267
|
+
await c.reconcileCredentials();
|
|
3268
|
+
return c;
|
|
3269
|
+
}
|
|
2418
3270
|
try {
|
|
2419
3271
|
const tokens = await c.store.load();
|
|
2420
3272
|
if (tokens) {
|
|
@@ -2428,6 +3280,63 @@ var Client = class _Client {
|
|
|
2428
3280
|
}
|
|
2429
3281
|
return c;
|
|
2430
3282
|
}
|
|
3283
|
+
/** Read the durable authority. Available only in explicit versioned mode. */
|
|
3284
|
+
async getCredentialSnapshot(signal) {
|
|
3285
|
+
if (!this.versionedCredentialStore) {
|
|
3286
|
+
throw new Error("getCredentialSnapshot requires credentialMode=versioned");
|
|
3287
|
+
}
|
|
3288
|
+
return this.versionedCredentialStore.readSnapshot(signal);
|
|
3289
|
+
}
|
|
3290
|
+
/** Reconcile memory from durable state; storage errors never clear confirmed memory. */
|
|
3291
|
+
async reconcileCredentials(signal) {
|
|
3292
|
+
const snapshot = await this.lifecycle.reconcile(signal);
|
|
3293
|
+
this.adoptCredentialOwner(snapshot);
|
|
3294
|
+
this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
|
|
3295
|
+
return snapshot;
|
|
3296
|
+
}
|
|
3297
|
+
ownerKey(snapshot) {
|
|
3298
|
+
return `${snapshot.storeInstanceId}\0${snapshot.authSessionId ?? ""}\0${snapshot.principal?.issuer ?? ""}\0${snapshot.principal?.subject ?? ""}\0${snapshot.principal?.organizationId ?? ""}`;
|
|
3299
|
+
}
|
|
3300
|
+
adoptCredentialOwner(snapshot) {
|
|
3301
|
+
const next = this.ownerKey(snapshot);
|
|
3302
|
+
if (this.credentialOwnerKey !== null && this.credentialOwnerKey !== next) {
|
|
3303
|
+
this.modelCache = [];
|
|
3304
|
+
this.modelCacheTimeMs = 0;
|
|
3305
|
+
this.coefCacheData = null;
|
|
3306
|
+
this.coefCacheTimeMs = 0;
|
|
3307
|
+
}
|
|
3308
|
+
this.credentialOwnerKey = next;
|
|
3309
|
+
}
|
|
3310
|
+
async ensureCredential(signal) {
|
|
3311
|
+
if (!this.lifecycle) return this.ensureToken(signal);
|
|
3312
|
+
const token = await this.lifecycle.ensure(signal, void 0, this.credentialRequestOwner ?? void 0);
|
|
3313
|
+
const snapshot = await this.lifecycle.read(signal);
|
|
3314
|
+
this.adoptCredentialOwner(snapshot);
|
|
3315
|
+
this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
|
|
3316
|
+
return token;
|
|
3317
|
+
}
|
|
3318
|
+
subscribeCredentialState(listener) {
|
|
3319
|
+
if (!this.lifecycle) throw new Error("subscribeCredentialState requires credentialMode=versioned");
|
|
3320
|
+
return this.lifecycle.subscribe(listener);
|
|
3321
|
+
}
|
|
3322
|
+
async retryCredentialIdentity(signal) {
|
|
3323
|
+
const snapshot = await this.getCredentialSnapshot(signal);
|
|
3324
|
+
if (!snapshot.authSessionId) throw new Error("not authorized");
|
|
3325
|
+
const ready = await this.lifecycle.bindIdentity(snapshot.authSessionId, signal);
|
|
3326
|
+
this.adoptCredentialOwner(ready);
|
|
3327
|
+
this.tokens = ready.credentialState === "ready" ? ready.tokenSet : null;
|
|
3328
|
+
return ready;
|
|
3329
|
+
}
|
|
3330
|
+
async logoutCredential(signal, expected) {
|
|
3331
|
+
if (!this.lifecycle) throw new Error("logoutCredential requires credentialMode=versioned");
|
|
3332
|
+
const result = await this.lifecycle.logout(signal, expected);
|
|
3333
|
+
if (result.status === "committed" || result.status === "already_signed_out") {
|
|
3334
|
+
const current = await this.lifecycle.read(signal);
|
|
3335
|
+
this.adoptCredentialOwner(current);
|
|
3336
|
+
this.tokens = null;
|
|
3337
|
+
}
|
|
3338
|
+
return result;
|
|
3339
|
+
}
|
|
2431
3340
|
// ===========================================================================
|
|
2432
3341
|
// 授权生命周期
|
|
2433
3342
|
// ===========================================================================
|
|
@@ -2478,6 +3387,44 @@ var Client = class _Client {
|
|
|
2478
3387
|
return this.loginInternal(appName, scopes, { handler, ...opts }, signal);
|
|
2479
3388
|
}
|
|
2480
3389
|
async loginInternal(appName, scopes, opts, signal) {
|
|
3390
|
+
if (this.lifecycle) {
|
|
3391
|
+
const attempt = await this.lifecycle.reserveLogin(signal);
|
|
3392
|
+
let installHookRejected = false;
|
|
3393
|
+
try {
|
|
3394
|
+
const registration = await register(attempt.metadata, appName, signal, this.fetchImpl);
|
|
3395
|
+
await this.lifecycle.assertLogin(attempt.attemptId, signal);
|
|
3396
|
+
const authorization = await authorize(attempt.metadata, registration.client_id, scopes, { ...opts, handler: opts?.handler ?? void 0, signal });
|
|
3397
|
+
await this.lifecycle.assertLogin(attempt.attemptId, signal);
|
|
3398
|
+
const response = await exchangeCode(attempt.metadata, registration.client_id, authorization.result.code, authorization.result.redirectURI, authorization.verifier, signal, this.fetchImpl);
|
|
3399
|
+
if (!Number.isFinite(response.expires_in) || response.expires_in <= 0) throw new Error("invalid_response");
|
|
3400
|
+
await this.lifecycle.assertLogin(attempt.attemptId, signal);
|
|
3401
|
+
try {
|
|
3402
|
+
await this.beforeCredentialInstall?.({
|
|
3403
|
+
accessToken: response.access_token,
|
|
3404
|
+
attemptId: attempt.attemptId,
|
|
3405
|
+
serverURL: this.serverURL,
|
|
3406
|
+
clientId: registration.client_id
|
|
3407
|
+
}, signal);
|
|
3408
|
+
} catch (error) {
|
|
3409
|
+
installHookRejected = true;
|
|
3410
|
+
throw error;
|
|
3411
|
+
}
|
|
3412
|
+
await this.lifecycle.assertLogin(attempt.attemptId, signal);
|
|
3413
|
+
const ready = await this.lifecycle.installLogin(attempt.attemptId, newTokenSet(response, registration.client_id, this.serverURL), signal);
|
|
3414
|
+
const current = await this.getCredentialSnapshot(signal);
|
|
3415
|
+
if (current.revision !== ready.revision || current.authSessionId !== ready.authSessionId) throw new Error("superseded");
|
|
3416
|
+
this.tokens = ready.tokenSet;
|
|
3417
|
+
opts?.handler?.({ type: EventComplete, attemptId: attempt.attemptId });
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
const current = await this.getCredentialSnapshot();
|
|
3420
|
+
if (current.loginAttempt?.attemptId === attempt.attemptId || current.lastLoginAttemptId === attempt.attemptId) {
|
|
3421
|
+
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" });
|
|
3422
|
+
}
|
|
3423
|
+
await this.lifecycle.cancelLogin(attempt.attemptId);
|
|
3424
|
+
throw error;
|
|
3425
|
+
}
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
2481
3428
|
const handler = opts?.handler ?? void 0;
|
|
2482
3429
|
const emit = (e) => {
|
|
2483
3430
|
if (handler) handler(e);
|
|
@@ -2586,6 +3533,11 @@ var Client = class _Client {
|
|
|
2586
3533
|
}
|
|
2587
3534
|
/** 吊销 token 并清除本地存储 */
|
|
2588
3535
|
async logout(signal) {
|
|
3536
|
+
if (this.lifecycle) {
|
|
3537
|
+
await this.logoutCredential(signal);
|
|
3538
|
+
await this.reconcileCredentials(signal);
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
2589
3541
|
const tokens = this.tokens;
|
|
2590
3542
|
let meta = this.meta;
|
|
2591
3543
|
this.tokens = null;
|
|
@@ -2623,6 +3575,13 @@ var Client = class _Client {
|
|
|
2623
3575
|
* 避免应用启动期 "login + 多个 API 调用" 并发场景下 4+ 条 "not authorized" 误报.
|
|
2624
3576
|
*/
|
|
2625
3577
|
async ensureToken(signal) {
|
|
3578
|
+
if (this.credentialMode === "external") {
|
|
3579
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
3580
|
+
const token = await this.accessTokenProvider(signal);
|
|
3581
|
+
if (typeof token !== "string" || token.length === 0) throw new Error("external access token unavailable");
|
|
3582
|
+
return token;
|
|
3583
|
+
}
|
|
3584
|
+
if (this.lifecycle) return this.lifecycle.ensure(signal, void 0, this.credentialRequestOwner ?? void 0);
|
|
2626
3585
|
let tokens = this.tokens;
|
|
2627
3586
|
const ready = this.tokenReady.promise;
|
|
2628
3587
|
const inFlight = this.loginInFlight;
|
|
@@ -2670,7 +3629,14 @@ var Client = class _Client {
|
|
|
2670
3629
|
);
|
|
2671
3630
|
}
|
|
2672
3631
|
/** 强制刷新 token (用于 401 重试) */
|
|
2673
|
-
async forceRefresh(signal) {
|
|
3632
|
+
async forceRefresh(signal, rejectedToken) {
|
|
3633
|
+
if (this.credentialMode === "external") throw new Error("external credentials cannot be refreshed by the SDK");
|
|
3634
|
+
if (this.lifecycle) {
|
|
3635
|
+
await this.lifecycle.forceRefresh(signal, rejectedToken, this.credentialRequestOwner ?? void 0);
|
|
3636
|
+
const snapshot = await this.lifecycle.read(signal);
|
|
3637
|
+
this.tokens = snapshot.credentialState === "ready" ? snapshot.tokenSet : null;
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
2674
3640
|
return this.withMu(
|
|
2675
3641
|
() => this.storeWithLock(async () => {
|
|
2676
3642
|
await this.syncFromDisk();
|
|
@@ -2865,6 +3831,7 @@ var Client = class _Client {
|
|
|
2865
3831
|
*/
|
|
2866
3832
|
async listModelsWithStatus(signal, opts) {
|
|
2867
3833
|
const includeLocked = opts?.includeLocked === true;
|
|
3834
|
+
const requestOwner = this.lifecycle ? this.ownerKey(await this.getCredentialSnapshot(signal)) : null;
|
|
2868
3835
|
const path = includeLocked ? "/managed-models?picker=1" : "/managed-models";
|
|
2869
3836
|
const { result, headers } = await this.doJSONFull(
|
|
2870
3837
|
"GET",
|
|
@@ -2873,6 +3840,11 @@ var Client = class _Client {
|
|
|
2873
3840
|
signal
|
|
2874
3841
|
);
|
|
2875
3842
|
const normalized = normalizeInputModalities(result.data);
|
|
3843
|
+
if (requestOwner !== null) {
|
|
3844
|
+
const current = await this.getCredentialSnapshot(signal);
|
|
3845
|
+
if (this.ownerKey(current) !== requestOwner) throw new Error("superseded");
|
|
3846
|
+
this.adoptCredentialOwner(current);
|
|
3847
|
+
}
|
|
2876
3848
|
if (!includeLocked) {
|
|
2877
3849
|
this.modelCache = normalized;
|
|
2878
3850
|
this.modelCacheTimeMs = Date.now();
|
|
@@ -3292,12 +4264,11 @@ var Client = class _Client {
|
|
|
3292
4264
|
throw classifyTransport("POST " + endpoint, url, e);
|
|
3293
4265
|
}
|
|
3294
4266
|
if (resp.status === 401 && !retried) {
|
|
4267
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
4268
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
4269
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
3295
4270
|
try {
|
|
3296
|
-
await
|
|
3297
|
-
} catch {
|
|
3298
|
-
}
|
|
3299
|
-
try {
|
|
3300
|
-
await this.forceRefresh(signal);
|
|
4271
|
+
await this.forceRefresh(signal, token);
|
|
3301
4272
|
} catch (refreshErr) {
|
|
3302
4273
|
throw new Error(
|
|
3303
4274
|
`stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
|
|
@@ -3363,12 +4334,11 @@ var Client = class _Client {
|
|
|
3363
4334
|
throw classifyTransport("POST " + endpoint, url, e);
|
|
3364
4335
|
}
|
|
3365
4336
|
if (resp.status === 401 && !retried) {
|
|
4337
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
4338
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
4339
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
3366
4340
|
try {
|
|
3367
|
-
await
|
|
3368
|
-
} catch {
|
|
3369
|
-
}
|
|
3370
|
-
try {
|
|
3371
|
-
await this.forceRefresh(signal);
|
|
4341
|
+
await this.forceRefresh(signal, token);
|
|
3372
4342
|
} catch (refreshErr) {
|
|
3373
4343
|
throw new Error(
|
|
3374
4344
|
`messages stream: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
|
|
@@ -3458,7 +4428,9 @@ var Client = class _Client {
|
|
|
3458
4428
|
if (!base.endsWith("/api/v4")) {
|
|
3459
4429
|
base += "/api/v4";
|
|
3460
4430
|
}
|
|
3461
|
-
|
|
4431
|
+
const url = base + path;
|
|
4432
|
+
this.assertCredentialURL(url);
|
|
4433
|
+
return url;
|
|
3462
4434
|
}
|
|
3463
4435
|
/**
|
|
3464
4436
|
* Compliance API URL 拼接。
|
|
@@ -3471,7 +4443,14 @@ var Client = class _Client {
|
|
|
3471
4443
|
*/
|
|
3472
4444
|
complianceURL(path) {
|
|
3473
4445
|
const base = this.complianceBaseURL ?? this.serverURL + "/admin-api";
|
|
3474
|
-
|
|
4446
|
+
const url = base + path;
|
|
4447
|
+
this.assertCredentialURL(url);
|
|
4448
|
+
return url;
|
|
4449
|
+
}
|
|
4450
|
+
assertCredentialURL(url) {
|
|
4451
|
+
if (this.credentialMode !== "legacy" && new URL(url).origin !== this.credentialAuthority) {
|
|
4452
|
+
throw new Error("credential request authority changed");
|
|
4453
|
+
}
|
|
3475
4454
|
}
|
|
3476
4455
|
/** GET/POST/... 通用 JSON 调用 (返回 result 已 typed) */
|
|
3477
4456
|
async doJSON(method, path, body, signal) {
|
|
@@ -3500,12 +4479,11 @@ var Client = class _Client {
|
|
|
3500
4479
|
ctl.signal
|
|
3501
4480
|
);
|
|
3502
4481
|
if (resp.status === 401 && !retried) {
|
|
4482
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
4483
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
4484
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
3503
4485
|
try {
|
|
3504
|
-
await
|
|
3505
|
-
} catch {
|
|
3506
|
-
}
|
|
3507
|
-
try {
|
|
3508
|
-
await this.forceRefresh(ctl.signal);
|
|
4486
|
+
await this.forceRefresh(ctl.signal, token);
|
|
3509
4487
|
} catch (refreshErr) {
|
|
3510
4488
|
throw new Error(
|
|
3511
4489
|
`unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
|
|
@@ -3570,12 +4548,11 @@ var Client = class _Client {
|
|
|
3570
4548
|
ctl.signal
|
|
3571
4549
|
);
|
|
3572
4550
|
if (resp.status === 401 && !retried) {
|
|
4551
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
4552
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
4553
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
3573
4554
|
try {
|
|
3574
|
-
await
|
|
3575
|
-
} catch {
|
|
3576
|
-
}
|
|
3577
|
-
try {
|
|
3578
|
-
await this.forceRefresh(ctl.signal);
|
|
4555
|
+
await this.forceRefresh(ctl.signal, token);
|
|
3579
4556
|
} catch (refreshErr) {
|
|
3580
4557
|
throw new Error(
|
|
3581
4558
|
`unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
|
|
@@ -3650,6 +4627,7 @@ var Client = class _Client {
|
|
|
3650
4627
|
* 6 处原始 fetch() 全部走此 helper
|
|
3651
4628
|
*/
|
|
3652
4629
|
async doRequest(req, signal) {
|
|
4630
|
+
if (new Headers(req.headers).has("Authorization")) this.assertCredentialURL(req.url);
|
|
3653
4631
|
try {
|
|
3654
4632
|
return await this.fetchImpl(req.url, {
|
|
3655
4633
|
method: req.method,
|
|
@@ -4647,12 +5625,11 @@ async function uploadSkillInternal(c, zipData, scope, intent, retried, signal) {
|
|
|
4647
5625
|
throw classifyTransport("POST /skill-store/upload", url, e);
|
|
4648
5626
|
}
|
|
4649
5627
|
if (resp.status === 401 && !retried) {
|
|
5628
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
5629
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
5630
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
4650
5631
|
try {
|
|
4651
|
-
await
|
|
4652
|
-
} catch {
|
|
4653
|
-
}
|
|
4654
|
-
try {
|
|
4655
|
-
await c.forceRefresh(ctl.signal);
|
|
5632
|
+
await c.forceRefresh(ctl.signal, token);
|
|
4656
5633
|
} catch (refreshErr) {
|
|
4657
5634
|
throw new Error(
|
|
4658
5635
|
`upload: unauthorized and refresh failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`
|
|
@@ -4823,9 +5800,7 @@ Client.prototype.updateNotificationPreference = async function(typeCode, pref, s
|
|
|
4823
5800
|
|
|
4824
5801
|
// src/notifications/ws.ts
|
|
4825
5802
|
Client.prototype.connect = async function(cfg, signal) {
|
|
4826
|
-
|
|
4827
|
-
await this.disconnect();
|
|
4828
|
-
}
|
|
5803
|
+
const oldDisconnect = this.ws ? this.disconnect() : Promise.resolve();
|
|
4829
5804
|
const noop = () => {
|
|
4830
5805
|
};
|
|
4831
5806
|
const filledCfg = {
|
|
@@ -4841,21 +5816,31 @@ Client.prototype.connect = async function(cfg, signal) {
|
|
|
4841
5816
|
const done = new Promise((r) => {
|
|
4842
5817
|
resolveDone = r;
|
|
4843
5818
|
});
|
|
4844
|
-
const
|
|
5819
|
+
const abort2 = new AbortController();
|
|
4845
5820
|
if (signal) {
|
|
4846
|
-
if (signal.aborted)
|
|
4847
|
-
else signal.addEventListener("abort", () =>
|
|
5821
|
+
if (signal.aborted) abort2.abort();
|
|
5822
|
+
else signal.addEventListener("abort", () => abort2.abort());
|
|
4848
5823
|
}
|
|
4849
5824
|
const ws = {
|
|
4850
5825
|
conn: null,
|
|
4851
5826
|
cfg: filledCfg,
|
|
4852
|
-
abort,
|
|
5827
|
+
abort: abort2,
|
|
4853
5828
|
done,
|
|
4854
5829
|
doneResolve: resolveDone,
|
|
4855
|
-
connected: false
|
|
5830
|
+
connected: false,
|
|
5831
|
+
owner: null
|
|
4856
5832
|
};
|
|
4857
|
-
await wsConnectOnce(this, ws);
|
|
4858
5833
|
this.ws = ws;
|
|
5834
|
+
try {
|
|
5835
|
+
await oldDisconnect;
|
|
5836
|
+
await assertCurrent(this, ws);
|
|
5837
|
+
await wsConnectOnce(this, ws);
|
|
5838
|
+
} catch (error) {
|
|
5839
|
+
ws.abort.abort();
|
|
5840
|
+
if (this.ws === ws) this.ws = null;
|
|
5841
|
+
ws.doneResolve();
|
|
5842
|
+
throw error;
|
|
5843
|
+
}
|
|
4859
5844
|
void wsLoop(this, ws);
|
|
4860
5845
|
};
|
|
4861
5846
|
Client.prototype.disconnect = async function() {
|
|
@@ -4893,15 +5878,47 @@ function getWebSocketCtor() {
|
|
|
4893
5878
|
}
|
|
4894
5879
|
return WSCtor;
|
|
4895
5880
|
}
|
|
5881
|
+
function sameOwner(a, b) {
|
|
5882
|
+
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;
|
|
5883
|
+
}
|
|
5884
|
+
async function readOwner(c, signal) {
|
|
5885
|
+
if (c.credentialMode !== "versioned") return null;
|
|
5886
|
+
const snapshot = await c.getCredentialSnapshot(signal);
|
|
5887
|
+
return {
|
|
5888
|
+
storeInstanceId: snapshot.storeInstanceId,
|
|
5889
|
+
authSessionId: snapshot.authSessionId,
|
|
5890
|
+
principal: snapshot.principal
|
|
5891
|
+
};
|
|
5892
|
+
}
|
|
5893
|
+
async function assertCurrent(c, ws) {
|
|
5894
|
+
if (ws.abort.signal.aborted || c.ws !== ws) throw new Error("websocket connection superseded");
|
|
5895
|
+
if (ws.owner !== null && !sameOwner(ws.owner, await readOwner(c, ws.abort.signal))) {
|
|
5896
|
+
ws.abort.abort();
|
|
5897
|
+
throw new Error("websocket credential owner changed");
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
4896
5900
|
async function wsConnectOnce(c, ws) {
|
|
5901
|
+
const observedOwner = await readOwner(c, ws.abort.signal);
|
|
5902
|
+
if (ws.owner === null) ws.owner = observedOwner;
|
|
5903
|
+
else if (!sameOwner(ws.owner, observedOwner)) throw new Error("websocket credential owner changed");
|
|
5904
|
+
await assertCurrent(c, ws);
|
|
4897
5905
|
const url = wsURL(c);
|
|
4898
5906
|
const WSCtor = getWebSocketCtor();
|
|
4899
|
-
const
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5907
|
+
const token = await c.ensureToken(ws.abort.signal);
|
|
5908
|
+
await assertCurrent(c, ws);
|
|
5909
|
+
const ticketURL = c.apiURL("/ws/stream-ticket");
|
|
5910
|
+
await assertCurrent(c, ws);
|
|
5911
|
+
const ticketHTTP = await c.doRequest({
|
|
5912
|
+
method: "POST",
|
|
5913
|
+
url: ticketURL,
|
|
5914
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
5915
|
+
}, ws.abort.signal);
|
|
5916
|
+
if (!ticketHTTP.ok) {
|
|
5917
|
+
const body = ticketHTTP.body ? await readLimited(ticketHTTP.body, maxErrorBodySize) : new Uint8Array();
|
|
5918
|
+
throw parseHTTPErrorWithHeader(ticketHTTP.status, body, ticketHTTP.headers);
|
|
5919
|
+
}
|
|
5920
|
+
const ticketResp = await ticketHTTP.json();
|
|
5921
|
+
await assertCurrent(c, ws);
|
|
4905
5922
|
const ticket = ticketResp.data.ticket;
|
|
4906
5923
|
const u = new URL(url);
|
|
4907
5924
|
u.searchParams.set("ticket", ticket);
|
|
@@ -4922,11 +5939,28 @@ async function wsConnectOnce(c, ws) {
|
|
|
4922
5939
|
reject(new Error("dial: handshake timeout"));
|
|
4923
5940
|
}
|
|
4924
5941
|
}, 3e4);
|
|
5942
|
+
const abortHandshake = () => {
|
|
5943
|
+
clearTimeout(handshakeTimer);
|
|
5944
|
+
try {
|
|
5945
|
+
conn.close();
|
|
5946
|
+
} catch {
|
|
5947
|
+
}
|
|
5948
|
+
reject(new Error("websocket connection aborted"));
|
|
5949
|
+
};
|
|
5950
|
+
ws.abort.signal.addEventListener("abort", abortHandshake, { once: true });
|
|
4925
5951
|
conn.addEventListener("open", () => {
|
|
5952
|
+
if (ws.abort.signal.aborted || c.ws !== ws) {
|
|
5953
|
+
try {
|
|
5954
|
+
conn.close();
|
|
5955
|
+
} catch {
|
|
5956
|
+
}
|
|
5957
|
+
return;
|
|
5958
|
+
}
|
|
4926
5959
|
opened = true;
|
|
4927
5960
|
});
|
|
4928
5961
|
conn.addEventListener("error", (e) => {
|
|
4929
5962
|
clearTimeout(handshakeTimer);
|
|
5963
|
+
ws.abort.signal.removeEventListener("abort", abortHandshake);
|
|
4930
5964
|
reject(new Error(`dial: ${e.message ?? "connection error"}`));
|
|
4931
5965
|
});
|
|
4932
5966
|
conn.addEventListener("message", (e) => {
|
|
@@ -4935,6 +5969,7 @@ async function wsConnectOnce(c, ws) {
|
|
|
4935
5969
|
const welcome = JSON.parse(msg);
|
|
4936
5970
|
if (welcome.type !== "welcome") {
|
|
4937
5971
|
clearTimeout(handshakeTimer);
|
|
5972
|
+
ws.abort.signal.removeEventListener("abort", abortHandshake);
|
|
4938
5973
|
try {
|
|
4939
5974
|
conn.close();
|
|
4940
5975
|
} catch {
|
|
@@ -4942,31 +5977,34 @@ async function wsConnectOnce(c, ws) {
|
|
|
4942
5977
|
reject(new Error(`unexpected first message: ${welcome.type}`));
|
|
4943
5978
|
return;
|
|
4944
5979
|
}
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
JSON.stringify({
|
|
4952
|
-
type: "subscribe",
|
|
4953
|
-
topics: ws.cfg.topics
|
|
4954
|
-
})
|
|
4955
|
-
);
|
|
4956
|
-
} catch (sendErr) {
|
|
4957
|
-
ws.conn = null;
|
|
4958
|
-
ws.connected = false;
|
|
5980
|
+
void assertCurrent(c, ws).then(() => {
|
|
5981
|
+
clearTimeout(handshakeTimer);
|
|
5982
|
+
ws.abort.signal.removeEventListener("abort", abortHandshake);
|
|
5983
|
+
ws.conn = conn;
|
|
5984
|
+
ws.connected = true;
|
|
5985
|
+
if (ws.cfg.topics.length > 0) {
|
|
4959
5986
|
try {
|
|
4960
|
-
conn.
|
|
4961
|
-
|
|
5987
|
+
conn.send(
|
|
5988
|
+
JSON.stringify({
|
|
5989
|
+
type: "subscribe",
|
|
5990
|
+
topics: ws.cfg.topics
|
|
5991
|
+
})
|
|
5992
|
+
);
|
|
5993
|
+
} catch (sendErr) {
|
|
5994
|
+
ws.conn = null;
|
|
5995
|
+
ws.connected = false;
|
|
5996
|
+
try {
|
|
5997
|
+
conn.close();
|
|
5998
|
+
} catch {
|
|
5999
|
+
}
|
|
6000
|
+
reject(new Error(`send subscribe: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`));
|
|
6001
|
+
return;
|
|
4962
6002
|
}
|
|
4963
|
-
reject(new Error(`send subscribe: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`));
|
|
4964
|
-
return;
|
|
4965
6003
|
}
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
6004
|
+
ws.cfg.onConnect();
|
|
6005
|
+
console.log(`[acosmi-sdk] websocket connected, connId=${welcome.connId ?? ""}`);
|
|
6006
|
+
resolve();
|
|
6007
|
+
}).catch(reject);
|
|
4970
6008
|
} catch (parseErr) {
|
|
4971
6009
|
clearTimeout(handshakeTimer);
|
|
4972
6010
|
try {
|
|
@@ -4981,7 +6019,7 @@ async function wsConnectOnce(c, ws) {
|
|
|
4981
6019
|
async function wsLoop(c, ws) {
|
|
4982
6020
|
try {
|
|
4983
6021
|
while (true) {
|
|
4984
|
-
await wsReadLoop(ws);
|
|
6022
|
+
await wsReadLoop(c, ws);
|
|
4985
6023
|
if (ws.abort.signal.aborted) return;
|
|
4986
6024
|
if (ws.conn) {
|
|
4987
6025
|
try {
|
|
@@ -5012,7 +6050,7 @@ async function wsLoop(c, ws) {
|
|
|
5012
6050
|
ws.doneResolve();
|
|
5013
6051
|
}
|
|
5014
6052
|
}
|
|
5015
|
-
async function wsReadLoop(ws) {
|
|
6053
|
+
async function wsReadLoop(c, ws) {
|
|
5016
6054
|
const conn = ws.conn;
|
|
5017
6055
|
if (!conn) return;
|
|
5018
6056
|
return new Promise((resolve) => {
|
|
@@ -5020,10 +6058,17 @@ async function wsReadLoop(ws) {
|
|
|
5020
6058
|
try {
|
|
5021
6059
|
const data = e.data;
|
|
5022
6060
|
const event = JSON.parse(data);
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
6061
|
+
void assertCurrent(c, ws).then(() => {
|
|
6062
|
+
try {
|
|
6063
|
+
ws.cfg.onEvent(event);
|
|
6064
|
+
} catch {
|
|
6065
|
+
}
|
|
6066
|
+
}).catch(() => {
|
|
6067
|
+
try {
|
|
6068
|
+
conn.close();
|
|
6069
|
+
} catch {
|
|
6070
|
+
}
|
|
6071
|
+
});
|
|
5027
6072
|
} catch {
|
|
5028
6073
|
}
|
|
5029
6074
|
};
|
|
@@ -5236,6 +6281,7 @@ function isTerminalRemoteEvent(ev) {
|
|
|
5236
6281
|
}
|
|
5237
6282
|
|
|
5238
6283
|
// src/agent-runs/client.ts
|
|
6284
|
+
init_errors();
|
|
5239
6285
|
var agentRunsByClient = /* @__PURE__ */ new WeakMap();
|
|
5240
6286
|
Object.defineProperty(Client.prototype, "agentRuns", {
|
|
5241
6287
|
configurable: true,
|
|
@@ -5615,11 +6661,10 @@ var AgentRunsClient = class {
|
|
|
5615
6661
|
}
|
|
5616
6662
|
const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
|
|
5617
6663
|
if (resp.status === 401 && opts.retryOn401 && !retried) {
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
await this.client.forceRefresh(signal);
|
|
6664
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
6665
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
6666
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
6667
|
+
await this.client.forceRefresh(signal, token);
|
|
5623
6668
|
return this.requestRawInner(method, path, body, signal, opts, true);
|
|
5624
6669
|
}
|
|
5625
6670
|
if (resp.status < 200 || resp.status >= 300) {
|
|
@@ -6246,6 +7291,7 @@ function isComplianceBusinessError(err) {
|
|
|
6246
7291
|
}
|
|
6247
7292
|
|
|
6248
7293
|
// src/compliance/client.ts
|
|
7294
|
+
init_errors();
|
|
6249
7295
|
var cache = /* @__PURE__ */ new WeakMap();
|
|
6250
7296
|
Object.defineProperty(Client.prototype, "compliance", {
|
|
6251
7297
|
configurable: true,
|
|
@@ -7062,11 +8108,10 @@ var ComplianceClient = class {
|
|
|
7062
8108
|
}
|
|
7063
8109
|
const resp = await this.client.doRequest({ method, url, headers, body: bodyStr }, signal);
|
|
7064
8110
|
if (resp.status === 401 && opts.retryOn401 && !retried) {
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
await this.client.forceRefresh(signal);
|
|
8111
|
+
const bodyBytes = resp.body ? await readLimited(resp.body, maxErrorBodySize) : new Uint8Array();
|
|
8112
|
+
const authError = parseHTTPErrorWithHeader(resp.status, bodyBytes, resp.headers);
|
|
8113
|
+
if (!isUserAccessTokenRejected(authError)) throw authError;
|
|
8114
|
+
await this.client.forceRefresh(signal, token);
|
|
7070
8115
|
return this.executeJsonInner(method, path, body, signal, opts, true);
|
|
7071
8116
|
}
|
|
7072
8117
|
if (resp.status < 200 || resp.status >= 300) {
|
|
@@ -7987,6 +9032,7 @@ exports.isRegion = isRegion;
|
|
|
7987
9032
|
exports.isSSECommentLine = isSSECommentLine;
|
|
7988
9033
|
exports.isSSLError = isSSLError;
|
|
7989
9034
|
exports.isTerminalRemoteEvent = isTerminalRemoteEvent;
|
|
9035
|
+
exports.isUserAccessTokenRejected = isUserAccessTokenRejected;
|
|
7990
9036
|
exports.isValidTokenSet = isValidTokenSet;
|
|
7991
9037
|
exports.isWindowLimitError = isWindowLimitError;
|
|
7992
9038
|
exports.isWindowLimitStreamError = isWindowLimitStreamError;
|
|
@@ -8004,6 +9050,7 @@ exports.parseNotificationEvent = parseNotificationEvent;
|
|
|
8004
9050
|
exports.parseRemoteControlEvent = parseRemoteControlEvent;
|
|
8005
9051
|
exports.parseSettlement = parseSettlement;
|
|
8006
9052
|
exports.parseSourcesEvent = parseSourcesEvent;
|
|
9053
|
+
exports.readGatewayErrorContract = readGatewayErrorContract;
|
|
8007
9054
|
exports.refreshToken = refreshToken;
|
|
8008
9055
|
exports.register = register;
|
|
8009
9056
|
exports.registerWebOAuthClient = registerWebOAuthClient;
|