@blade-hq/agent-client 1.2.1-alpha.4 → 1.2.1-beta.10

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/README.md CHANGED
@@ -182,7 +182,7 @@ const unsubscribe = chat.subscribe(() => rerender(chat.getState()))
182
182
  ### 动作
183
183
 
184
184
  ```ts
185
- await chat.send("你好") // 发消息(字符串或多模态内容)
185
+ const accepted = await chat.send("你好") // 服务端接受后为 true;拒绝时为 false
186
186
  await chat.send("换个方案", { mode: "planning" }) // 指定模式/模型等选项
187
187
  chat.append("补充:预算不超过 5 万") // 智能体运行中追加说明
188
188
  await chat.stop() // 停止当前回复
@@ -389,6 +389,6 @@ function Message({ message }: { message: ChatMessage }) {
389
389
  - **会话状态机**:`SessionHub`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
390
390
  - **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
391
391
  - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
392
- - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`
392
+ - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`、`BackgroundTaskStopResult`
393
393
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
394
394
  - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`ClientProjectionBuilder`、`RawEvent`
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export type { ModeId, PrimarySkillParallelMode, PrimarySkillSnapshot, SessionDet
31
31
  export { LayoutType } from "./schemas/solution";
32
32
  export type { BizRole, Solution, SolutionAppField, SolutionAppState, SolutionAppUiConfig, } from "./schemas/solution";
33
33
  export { Task, TaskStatus } from "./schemas/task";
34
- export type { BackgroundTask } from "./schemas/background";
34
+ export type { BackgroundTask, BackgroundTaskStopResult } from "./schemas/background";
35
35
  export { ClientProjectionBuilder } from "./shared/projection";
36
36
  export type { RawEvent } from "./shared/projection";
37
37
  export { createSocket } from "./socket";
package/dist/index.js CHANGED
@@ -45,8 +45,8 @@ async function loginWithPopup(client, options = {}) {
45
45
  authorizeUrl.searchParams.set("client_origin", clientOrigin);
46
46
  authorizeUrl.searchParams.set("state", state);
47
47
  const popup = window.open(
48
- authorizeUrl.toString(),
49
- "blade-sdk-login",
48
+ "about:blank",
49
+ `blade-sdk-login-${state}`,
50
50
  `popup=yes,width=${width},height=${height},left=${left},top=${top}`
51
51
  );
52
52
  if (!popup) {
@@ -69,6 +69,7 @@ async function loginWithPopup(client, options = {}) {
69
69
  };
70
70
  const onMessage = (event) => {
71
71
  if (event.origin !== expectedOrigin) return;
72
+ if (event.source !== popup) return;
72
73
  if (!isAuthCallbackMessage(event.data)) return;
73
74
  if (event.data.state !== state) return;
74
75
  const message = event.data;
@@ -107,6 +108,7 @@ async function loginWithPopup(client, options = {}) {
107
108
  settle(() => reject(new Error("\u767B\u5F55\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5\u3002")));
108
109
  }, timeoutMs);
109
110
  window.addEventListener("message", onMessage);
111
+ popup.location.replace(authorizeUrl.toString());
110
112
  });
111
113
  }
112
114
 
@@ -319,13 +321,22 @@ var HeadlessResource = class {
319
321
  socket.on("chat:end", onChatEnd);
320
322
  socket.on("system:error", onSystemError);
321
323
  socket.emit("session:subscribe", { session_id, event_format: "raw" });
322
- socket.emit("chat:send", {
323
- session_id,
324
- message: prompt,
325
- headless: true,
326
- output_schema: options.schema ?? null,
327
- model: options.model ?? null
328
- });
324
+ socket.emit(
325
+ "chat:send",
326
+ {
327
+ session_id,
328
+ message: prompt,
329
+ headless: true,
330
+ output_schema: options.schema ?? null,
331
+ model: options.model ?? null
332
+ },
333
+ (response) => {
334
+ if (response.status === "accepted") return;
335
+ rejectAfterChatEnd(
336
+ new HeadlessError(response.message ?? "headless run was not accepted", response)
337
+ );
338
+ }
339
+ );
329
340
  });
330
341
  }
331
342
  };
@@ -905,6 +916,7 @@ var ClientProjectionBuilder = class {
905
916
  pendingChildPauses = /* @__PURE__ */ new Map();
906
917
  pendingMemoryRefs = /* @__PURE__ */ new Map();
907
918
  activeCompactions = /* @__PURE__ */ new Map();
919
+ failedCompactions = /* @__PURE__ */ new Map();
908
920
  seq = 0;
909
921
  syntheticCounter = 0;
910
922
  displayNameResolver;
@@ -968,6 +980,12 @@ var ClientProjectionBuilder = class {
968
980
  return null;
969
981
  }
970
982
  switch (event.type) {
983
+ case "llm:stream:reset":
984
+ state.blocks = [];
985
+ state.toolCalls = [];
986
+ state.usage = null;
987
+ state.responseDone = false;
988
+ return this.syncPatch(state);
971
989
  case "llm:thinking:delta":
972
990
  return this.onDelta(state, "thinking", "", String(payload.content ?? ""));
973
991
  case "llm:content:delta":
@@ -1004,6 +1022,7 @@ var ClientProjectionBuilder = class {
1004
1022
  this.pendingChildPauses.clear();
1005
1023
  this.pendingMemoryRefs.clear();
1006
1024
  this.activeCompactions.clear();
1025
+ this.failedCompactions.clear();
1007
1026
  this.seq = 0;
1008
1027
  this.syntheticCounter = 0;
1009
1028
  }
@@ -1476,7 +1495,7 @@ var ClientProjectionBuilder = class {
1476
1495
  onCompaction(eventType, loopId, payload) {
1477
1496
  const compactionId = String(payload.compaction_id ?? "");
1478
1497
  if (!compactionId) return null;
1479
- const current = {
1498
+ let current = {
1480
1499
  ...this.activeCompactions.get(compactionId) ?? {},
1481
1500
  ...payload,
1482
1501
  loop_id: loopId
@@ -1486,6 +1505,7 @@ var ClientProjectionBuilder = class {
1486
1505
  case "compaction:start":
1487
1506
  status = "streaming";
1488
1507
  this.activeCompactions.set(compactionId, current);
1508
+ this.failedCompactions.delete(compactionId);
1489
1509
  break;
1490
1510
  case "compaction:end":
1491
1511
  status = "completed";
@@ -1498,11 +1518,17 @@ var ClientProjectionBuilder = class {
1498
1518
  case "compaction:error":
1499
1519
  status = "failed";
1500
1520
  this.activeCompactions.delete(compactionId);
1521
+ this.failedCompactions.set(compactionId, { ...current });
1501
1522
  break;
1502
1523
  default:
1503
1524
  status = "streaming";
1504
1525
  this.activeCompactions.set(compactionId, current);
1505
1526
  }
1527
+ const failed = this.failedCompactions.get(compactionId);
1528
+ if (failed && status !== "failed") {
1529
+ current = { ...failed, ...current };
1530
+ status = "failed";
1531
+ }
1506
1532
  const turn = buildCompactionTurn(this.nextSeq(), compactionId, loopId, status, current);
1507
1533
  return [{ kind: "upsert", turn }];
1508
1534
  }
@@ -3037,6 +3063,11 @@ function extractTerminalChatEndStatus(events) {
3037
3063
  }
3038
3064
  return null;
3039
3065
  }
3066
+ function hasChatRunEvent(events) {
3067
+ return events.some(
3068
+ (event) => event.type !== "session:post_chat_followup" && !event.type.startsWith("compaction:")
3069
+ );
3070
+ }
3040
3071
  function isUiMetaLike(value) {
3041
3072
  return isRecord5(value) && ("resourceHTML" in value || "resourceUri" in value || "resourceURI" in value);
3042
3073
  }
@@ -3059,6 +3090,7 @@ var AgentSession = class _AgentSession {
3059
3090
  disposed = false;
3060
3091
  getAppContext = null;
3061
3092
  sendPending = false;
3093
+ manualCompactionPending = false;
3062
3094
  deliveredCommandKeys = /* @__PURE__ */ new Set();
3063
3095
  deliveredToolPreviews = /* @__PURE__ */ new Map();
3064
3096
  oomNotifiedKeys = /* @__PURE__ */ new Set();
@@ -3153,22 +3185,26 @@ var AgentSession = class _AgentSession {
3153
3185
  /** 发送一条消息。字符串或多模态内容均可。 */
3154
3186
  async send(content, options = {}) {
3155
3187
  this.runtime.ensureConnected();
3156
- if (this.state.isStreaming || this.sendPending) {
3157
- this.update((s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002"));
3158
- return;
3188
+ if (this.state.isStreaming || this.state.activeCompaction || this.sendPending || this.manualCompactionPending) {
3189
+ this.update(
3190
+ (s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
3191
+ );
3192
+ return false;
3159
3193
  }
3160
3194
  this.sendPending = true;
3161
3195
  try {
3162
- await this.sendUnlocked(content, options);
3196
+ return await this.sendUnlocked(content, options);
3163
3197
  } finally {
3164
3198
  this.sendPending = false;
3165
3199
  }
3166
3200
  }
3167
3201
  async sendUnlocked(content, options) {
3168
3202
  await this.ensureIdleBeforeSend();
3169
- if (this.state.isStreaming) {
3170
- this.update((s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002"));
3171
- return;
3203
+ if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
3204
+ this.update(
3205
+ (s) => addErrorMessage(s, "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u53D1\u9001\u3002")
3206
+ );
3207
+ return false;
3172
3208
  }
3173
3209
  let appContext;
3174
3210
  if (this.getAppContext) {
@@ -3189,19 +3225,28 @@ var AgentSession = class _AgentSession {
3189
3225
  } catch (error) {
3190
3226
  const message = error instanceof Error && error.message ? `\u8BFB\u53D6\u5E94\u7528\u4E0A\u4E0B\u6587\u5931\u8D25\uFF1A${error.message}` : "\u8BFB\u53D6\u5E94\u7528\u4E0A\u4E0B\u6587\u5931\u8D25\uFF0C\u6D88\u606F\u672A\u53D1\u9001";
3191
3227
  this.update((s) => addErrorMessage(s, message));
3192
- return;
3228
+ return false;
3193
3229
  }
3194
3230
  }
3195
3231
  const askUserAnswer = options.askUserAnswer;
3232
+ let optimisticTurnId = null;
3233
+ let optimisticAskAnswer;
3234
+ let previousAskAnswer;
3196
3235
  if (!(askUserAnswer && typeof askUserAnswer.tool_call_id === "string")) {
3197
- this.update((s) => addUserMessage(s, content));
3236
+ this.update((state) => {
3237
+ const next = addUserMessage(state, content);
3238
+ optimisticTurnId = next.turns.at(-1)?.turn_id ?? null;
3239
+ return next;
3240
+ });
3198
3241
  }
3199
3242
  this.update((s) => ({ ...s, isStreaming: true }));
3200
3243
  if (askUserAnswer && typeof askUserAnswer.tool_call_id === "string") {
3201
3244
  const { tool_call_id, ...rest } = askUserAnswer;
3245
+ previousAskAnswer = this.state.askAnswers[tool_call_id];
3246
+ optimisticAskAnswer = rest;
3202
3247
  this.update((s) => ({
3203
3248
  ...s,
3204
- askAnswers: { ...s.askAnswers, [tool_call_id]: rest }
3249
+ askAnswers: { ...s.askAnswers, [tool_call_id]: optimisticAskAnswer }
3205
3250
  }));
3206
3251
  }
3207
3252
  if (!options.replayDecision) {
@@ -3222,15 +3267,54 @@ var AgentSession = class _AgentSession {
3222
3267
  try {
3223
3268
  await this.ensureJoined();
3224
3269
  } catch (error) {
3225
- this.update(
3226
- (s) => addErrorMessage(
3227
- { ...s, isStreaming: false, status: "failed" },
3228
- error instanceof Error && error.message ? error.message : "\u4F1A\u8BDD\u8BA2\u9605\u5931\u8D25\uFF0C\u6D88\u606F\u672A\u53D1\u9001"
3229
- )
3270
+ this.rollbackOptimisticSend(
3271
+ optimisticTurnId,
3272
+ askUserAnswer,
3273
+ optimisticAskAnswer,
3274
+ previousAskAnswer,
3275
+ error instanceof Error && error.message ? error.message : "\u4F1A\u8BDD\u8BA2\u9605\u5931\u8D25\uFF0C\u6D88\u606F\u672A\u53D1\u9001"
3230
3276
  );
3231
- return;
3277
+ return false;
3278
+ }
3279
+ try {
3280
+ const response = await this.runtime.emitWithAck("chat:send", payload, 3e4);
3281
+ if (response?.status !== "accepted") {
3282
+ throw new Error(
3283
+ typeof response?.message === "string" ? response.message : "\u6D88\u606F\u672A\u88AB\u670D\u52A1\u7AEF\u63A5\u53D7"
3284
+ );
3285
+ }
3286
+ return true;
3287
+ } catch (error) {
3288
+ this.rollbackOptimisticSend(
3289
+ optimisticTurnId,
3290
+ askUserAnswer,
3291
+ optimisticAskAnswer,
3292
+ previousAskAnswer,
3293
+ error instanceof Error && error.message ? error.message : "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
3294
+ );
3295
+ return false;
3232
3296
  }
3233
- this.runtime.emitEvent("chat:send", payload);
3297
+ }
3298
+ rollbackOptimisticSend(optimisticTurnId, askUserAnswer, optimisticAskAnswer, previousAskAnswer, message) {
3299
+ this.pendingReplayMessage = null;
3300
+ this.pendingReplayMode = void 0;
3301
+ this.update((current) => {
3302
+ let next = optimisticTurnId ? withTurns(
3303
+ current,
3304
+ current.turns.filter((turn) => turn.turn_id !== optimisticTurnId)
3305
+ ) : current;
3306
+ const toolCallId = askUserAnswer && typeof askUserAnswer.tool_call_id === "string" ? askUserAnswer.tool_call_id : null;
3307
+ if (toolCallId && next.askAnswers[toolCallId] === optimisticAskAnswer) {
3308
+ const askAnswers = { ...next.askAnswers };
3309
+ if (previousAskAnswer === void 0) {
3310
+ delete askAnswers[toolCallId];
3311
+ } else {
3312
+ askAnswers[toolCallId] = previousAskAnswer;
3313
+ }
3314
+ next = { ...next, askAnswers };
3315
+ }
3316
+ return addErrorMessage({ ...next, isStreaming: false }, message);
3317
+ });
3234
3318
  }
3235
3319
  /** 智能体运行期间追加补充说明(不打断当前回复)。 */
3236
3320
  append(text) {
@@ -3244,7 +3328,28 @@ var AgentSession = class _AgentSession {
3244
3328
  }
3245
3329
  /** 手动触发上下文压缩。 */
3246
3330
  compact() {
3247
- return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {});
3331
+ if (this.state.isStreaming || this.state.activeCompaction || this.manualCompactionPending) {
3332
+ return Promise.resolve({
3333
+ status: "error",
3334
+ message: "\u5F53\u524D\u56DE\u590D\u5C1A\u672A\u7ED3\u675F\u6216\u5BF9\u8BDD\u6B63\u5728\u6574\u7406\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002"
3335
+ });
3336
+ }
3337
+ this.manualCompactionPending = true;
3338
+ this.update((state) => ({
3339
+ ...state,
3340
+ activeCompaction: {
3341
+ turn_id: "pending-manual-compaction",
3342
+ compaction_id: "pending-manual-compaction",
3343
+ status: "streaming",
3344
+ trigger: "manual"
3345
+ }
3346
+ }));
3347
+ return this.runtime.emitWithAck("chat:compact", { session_id: this.sessionId }, 3e4).then((response) => response ?? {}).finally(() => {
3348
+ this.manualCompactionPending = false;
3349
+ if (this.state.activeCompaction?.compaction_id === "pending-manual-compaction") {
3350
+ this.update((state) => ({ ...state, activeCompaction: null }));
3351
+ }
3352
+ });
3248
3353
  }
3249
3354
  /**
3250
3355
  * 把一段业务数据作为附件放进聊天输入框(比如地图上选中的点位)。
@@ -3334,10 +3439,7 @@ var AgentSession = class _AgentSession {
3334
3439
  _handleTurnEvents(events) {
3335
3440
  if (!events.length) return;
3336
3441
  const terminalChatEndStatus = extractTerminalChatEndStatus(events);
3337
- const followupOnly = events.every(
3338
- (event) => event.type === "session:post_chat_followup"
3339
- );
3340
- if (!terminalChatEndStatus && !followupOnly) {
3442
+ if (!terminalChatEndStatus && hasChatRunEvent(events)) {
3341
3443
  this.markRunningIfIdle();
3342
3444
  }
3343
3445
  if (this.state.turns.length) {
@@ -3411,11 +3513,30 @@ var AgentSession = class _AgentSession {
3411
3513
  this.pendingReplayMessage = null;
3412
3514
  this.pendingReplayMode = void 0;
3413
3515
  const respond = (decision) => {
3414
- this.runtime.emitEvent("chat:send", {
3415
- session_id: this.sessionId,
3416
- message,
3417
- mode,
3418
- replay_decision: decision
3516
+ void this.runtime.emitWithAck(
3517
+ "chat:send",
3518
+ {
3519
+ session_id: this.sessionId,
3520
+ message,
3521
+ mode,
3522
+ replay_decision: decision
3523
+ },
3524
+ 3e4
3525
+ ).then((response) => {
3526
+ if (response?.status === "accepted") return;
3527
+ this.update(
3528
+ (state) => addErrorMessage(
3529
+ { ...state, isStreaming: false },
3530
+ typeof response?.message === "string" ? response.message : "\u6D88\u606F\u672A\u88AB\u670D\u52A1\u7AEF\u63A5\u53D7"
3531
+ )
3532
+ );
3533
+ }).catch((error) => {
3534
+ this.update(
3535
+ (state) => addErrorMessage(
3536
+ { ...state, isStreaming: false },
3537
+ error instanceof Error && error.message ? error.message : "\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
3538
+ )
3539
+ );
3419
3540
  });
3420
3541
  };
3421
3542
  const handled = this.emitter.emitFirst("replayMismatch", {
@@ -3697,6 +3818,18 @@ var AgentSession = class _AgentSession {
3697
3818
  };
3698
3819
 
3699
3820
  // src/session/hub.ts
3821
+ var BACKGROUND_STATUSES = [
3822
+ "starting",
3823
+ "running",
3824
+ "cancelling",
3825
+ "succeeded",
3826
+ "failed",
3827
+ "cancelled",
3828
+ "lost"
3829
+ ];
3830
+ function normalizeBackgroundStatus(value) {
3831
+ return BACKGROUND_STATUSES.includes(value) ? value : "running";
3832
+ }
3700
3833
  var SessionHub = class {
3701
3834
  client;
3702
3835
  sessions = /* @__PURE__ */ new Map();
@@ -3951,7 +4084,11 @@ var SessionHub = class {
3951
4084
  id: taskId,
3952
4085
  command: typeof task.command === "string" ? task.command : "",
3953
4086
  description: typeof task.description === "string" ? task.description : void 0,
3954
- status: task.status === "running" || task.status === "failed" ? task.status : "done",
4087
+ // Pass the server's status through unchanged. Collapsing it here
4088
+ // made subscribers see a successful-looking "done" for tasks the
4089
+ // REST API correctly reported as cancelled or lost.
4090
+ status: normalizeBackgroundStatus(task.status),
4091
+ failureReason: typeof task.failure_reason === "string" ? task.failure_reason : void 0,
3955
4092
  exitCode: typeof task.exit_code === "number" || task.exit_code === null ? task.exit_code : void 0,
3956
4093
  elapsedSeconds: typeof task.elapsed_seconds === "number" ? task.elapsed_seconds : 0,
3957
4094
  output: Array.isArray(task.output_preview) && task.output_preview.length > 0 ? task.output_preview.join("\n") : void 0
@@ -3994,7 +4131,7 @@ function resolveAuthToken(options) {
3994
4131
 
3995
4132
  // src/version.ts
3996
4133
  var SDK_NAME = "agent-client";
3997
- var SDK_VERSION = true ? "1.2.1-alpha.4" : "1.1.1";
4134
+ var SDK_VERSION = true ? "1.2.1-beta.10" : "1.1.1";
3998
4135
 
3999
4136
  // src/socket.ts
4000
4137
  function withSdkIdentity(auth) {
@@ -4342,12 +4479,31 @@ function resolveTokenOption(tokenOption) {
4342
4479
  const token = typeof tokenOption === "function" ? tokenOption() : tokenOption;
4343
4480
  return token ? token : null;
4344
4481
  }
4482
+ var SANDBOX_ONLY_HOST = "host.docker.internal";
4345
4483
  function normalizeBaseUrl(baseUrl) {
4346
4484
  const normalized = baseUrl.trim().replace(/\/+$/, "");
4347
- if (normalized || typeof window === "undefined") {
4485
+ if (typeof window === "undefined") {
4348
4486
  return normalized;
4349
4487
  }
4350
- return window.location.origin;
4488
+ if (!normalized) {
4489
+ return window.location.origin;
4490
+ }
4491
+ assertBrowserReachable(normalized);
4492
+ return normalized;
4493
+ }
4494
+ function assertBrowserReachable(baseUrl) {
4495
+ let hostname;
4496
+ try {
4497
+ hostname = new URL(baseUrl, window.location.href).hostname;
4498
+ } catch {
4499
+ return;
4500
+ }
4501
+ if (hostname !== SANDBOX_ONLY_HOST) {
4502
+ return;
4503
+ }
4504
+ throw new Error(
4505
+ `Blade Agent \u5730\u5740 ${baseUrl} \u53EA\u6709\u6C99\u76D2\u5185\u90E8\u89E3\u6790\u5F97\u5230\uFF0C\u6D4F\u89C8\u5668\u6253\u4E0D\u5F00\u3002\u8BF7\u628A\u5B83\u6539\u6210\u7528\u6237\u6D4F\u89C8\u5668\u80FD\u8BBF\u95EE\u7684\u5730\u5740\uFF08\u901A\u5E38\u662F\u8BBF\u95EE\u8FD9\u4E2A\u5E94\u7528\u65F6\u7528\u7684\u4E3B\u673A\u540D + Blade Server \u7AEF\u53E3\uFF09\u3002`
4506
+ );
4351
4507
  }
4352
4508
  function isFormData(value) {
4353
4509
  return typeof FormData !== "undefined" && value instanceof FormData;