@blade-hq/agent-kit 1.0.34 → 1.1.0

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.
Files changed (46) hide show
  1. package/README.md +95 -2
  2. package/dist/chunk-6KO3AOJ7.js +251 -0
  3. package/dist/chunk-6KO3AOJ7.js.map +1 -0
  4. package/dist/{chunk-FK4CV6R2.js → chunk-C63X3RCM.js} +629 -32
  5. package/dist/chunk-C63X3RCM.js.map +1 -0
  6. package/dist/{chunk-BDGMBHYV.js → chunk-DOBX7XP3.js} +5 -5
  7. package/dist/{chunk-EGGBOVVF.js → chunk-IP7EZVT5.js} +2 -2
  8. package/dist/{chunk-OICNN4K4.js → chunk-J6H4TMTH.js} +2 -2
  9. package/dist/{chunk-F4Y7SUBC.js → chunk-SEHOWQVO.js} +2 -2
  10. package/dist/{chunk-IDVNG3YJ.js → chunk-YJSN44Q4.js} +16 -3
  11. package/dist/{chunk-IDVNG3YJ.js.map → chunk-YJSN44Q4.js.map} +1 -1
  12. package/dist/{chunk-E7FQV4IX.js → chunk-ZPPYDQPU.js} +3 -3
  13. package/dist/client/auth.d.ts +55 -0
  14. package/dist/client/blade-client.d.ts +19 -3
  15. package/dist/client/connection.d.ts +41 -0
  16. package/dist/client/index.d.ts +3 -0
  17. package/dist/client/index.js +11 -3
  18. package/dist/client/resources/auth.d.ts +1 -26
  19. package/dist/client/socket.d.ts +1 -0
  20. package/dist/client/types/rest.d.ts +151 -0
  21. package/dist/client/types/socket-events.d.ts +4 -0
  22. package/dist/react/api/published-apps.js +3 -3
  23. package/dist/react/api/sessions.js +2 -2
  24. package/dist/react/bootstrap.d.ts +1 -1
  25. package/dist/react/components/chat/index.js +5 -5
  26. package/dist/react/components/connection/BladeConnectionButton.d.ts +7 -0
  27. package/dist/react/components/connection/BladeConnectionPanel.d.ts +7 -0
  28. package/dist/react/components/connection/index.d.ts +2 -0
  29. package/dist/react/components/connection/index.js +15 -0
  30. package/dist/react/components/connection/index.js.map +1 -0
  31. package/dist/react/components/plan/index.js +4 -4
  32. package/dist/react/components/session/index.js +3 -3
  33. package/dist/react/components/workspace/index.js +3 -3
  34. package/dist/react/hooks/use-blade-connection.d.ts +14 -0
  35. package/dist/react/index.d.ts +5 -1
  36. package/dist/react/index.js +20 -22
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/react/sockets/event-bridge.d.ts +2 -0
  39. package/dist/style.css +1 -1
  40. package/package.json +5 -1
  41. package/dist/chunk-FK4CV6R2.js.map +0 -1
  42. /package/dist/{chunk-BDGMBHYV.js.map → chunk-DOBX7XP3.js.map} +0 -0
  43. /package/dist/{chunk-EGGBOVVF.js.map → chunk-IP7EZVT5.js.map} +0 -0
  44. /package/dist/{chunk-OICNN4K4.js.map → chunk-J6H4TMTH.js.map} +0 -0
  45. /package/dist/{chunk-F4Y7SUBC.js.map → chunk-SEHOWQVO.js.map} +0 -0
  46. /package/dist/{chunk-E7FQV4IX.js.map → chunk-ZPPYDQPU.js.map} +0 -0
@@ -95,12 +95,99 @@ var AppDevTemplatesResource = class {
95
95
  }
96
96
  };
97
97
 
98
- // src/client/resources/auth.ts
98
+ // src/client/connection.ts
99
+ var BladeConnectionError = class extends Error {
100
+ code;
101
+ constructor(code, message, options) {
102
+ super(message, options);
103
+ this.name = "BladeConnectionError";
104
+ this.code = code;
105
+ }
106
+ };
107
+ function createMemoryAuthStorage() {
108
+ const values = /* @__PURE__ */ new Map();
109
+ return {
110
+ get: (key) => values.get(key) ?? null,
111
+ set: (key, value) => {
112
+ values.set(key, value);
113
+ },
114
+ remove: (key) => {
115
+ values.delete(key);
116
+ }
117
+ };
118
+ }
119
+ function createLocalAuthStorage() {
120
+ return {
121
+ get(key) {
122
+ const raw = window.localStorage.getItem(key);
123
+ if (!raw) return null;
124
+ try {
125
+ const value = JSON.parse(raw);
126
+ if (!value.accessToken || !value.user?.id || !value.user.username) return null;
127
+ return value;
128
+ } catch {
129
+ window.localStorage.removeItem(key);
130
+ return null;
131
+ }
132
+ },
133
+ set(key, value) {
134
+ window.localStorage.setItem(key, JSON.stringify(value));
135
+ },
136
+ remove(key) {
137
+ window.localStorage.removeItem(key);
138
+ }
139
+ };
140
+ }
141
+
142
+ // src/client/auth.ts
143
+ function buildSocketAuth(options) {
144
+ const token = resolveAuthToken(options);
145
+ return token ? { token } : void 0;
146
+ }
147
+ function resolveAuthToken(options) {
148
+ const token = typeof options.token === "function" ? options.token() : options.token;
149
+ return token ? token : null;
150
+ }
151
+ var DEFAULT_POPUP_TIMEOUT_MS = 5 * 60 * 1e3;
152
+ var DEFAULT_WINDOW_FEATURES = "popup=yes,width=560,height=720,resizable=yes,scrollbars=yes";
153
+ var HEALTH_TIMEOUT_MS = 5e3;
99
154
  var AuthResource = class {
100
155
  constructor(client) {
101
156
  this.client = client;
157
+ const configured = client.options.authStorage;
158
+ if (configured === false) {
159
+ this.storage = this.memoryStorage;
160
+ this.persistent = false;
161
+ } else if (configured) {
162
+ this.storage = configured;
163
+ this.persistent = true;
164
+ } else if (typeof window === "undefined") {
165
+ this.storage = this.memoryStorage;
166
+ this.persistent = false;
167
+ } else {
168
+ this.storage = createLocalAuthStorage();
169
+ this.persistent = true;
170
+ }
171
+ this.snapshot = {
172
+ status: "signed_out",
173
+ baseUrl: client.baseUrl,
174
+ user: null,
175
+ error: null,
176
+ persistent: this.persistent,
177
+ managedBySdk: client.options.token === void 0
178
+ };
102
179
  }
103
180
  client;
181
+ listeners = /* @__PURE__ */ new Set();
182
+ memoryStorage = createMemoryAuthStorage();
183
+ storage;
184
+ persistent;
185
+ restorePromise = null;
186
+ baseUrlGeneration = 0;
187
+ pendingConnection = null;
188
+ activePopup = null;
189
+ cancelPending = null;
190
+ snapshot;
104
191
  getProviders() {
105
192
  return this.client.json("GET", "/api/auth/providers");
106
193
  }
@@ -110,7 +197,449 @@ var AuthResource = class {
110
197
  logout() {
111
198
  return this.client.json("POST", "/api/auth/logout");
112
199
  }
200
+ getConnectionSnapshot() {
201
+ return this.snapshot;
202
+ }
203
+ subscribe(listener) {
204
+ this.listeners.add(listener);
205
+ return () => this.listeners.delete(listener);
206
+ }
207
+ async probe() {
208
+ this.assertNoMixedContent();
209
+ const controller = new AbortController();
210
+ const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
211
+ let response;
212
+ try {
213
+ response = await (this.client.options.fetchImpl ?? fetch)(this.url("/api/health"), {
214
+ method: "GET",
215
+ credentials: "omit",
216
+ signal: controller.signal
217
+ });
218
+ } catch (error) {
219
+ throw new BladeConnectionError(
220
+ "service_unreachable",
221
+ "\u65E0\u6CD5\u8FDE\u63A5\u5230 Blade Agent\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u5730\u5740\u548C\u7F51\u7EDC\u72B6\u6001\u3002",
222
+ { cause: error }
223
+ );
224
+ } finally {
225
+ clearTimeout(timeout);
226
+ }
227
+ if (!response.ok) {
228
+ throw new BladeConnectionError(
229
+ "service_unreachable",
230
+ `Blade Agent \u670D\u52A1\u8FD4\u56DE\u4E86 ${response.status}\u3002`
231
+ );
232
+ }
233
+ let data;
234
+ try {
235
+ data = await response.json();
236
+ } catch (error) {
237
+ throw new BladeConnectionError("not_blade_agent", "\u8BE5\u5730\u5740\u4E0D\u662F Blade Agent \u670D\u52A1\u3002", {
238
+ cause: error
239
+ });
240
+ }
241
+ if (data.service !== "blade-agent") {
242
+ throw new BladeConnectionError("not_blade_agent", "\u8BE5\u5730\u5740\u4E0D\u662F Blade Agent \u670D\u52A1\u3002");
243
+ }
244
+ if (data.sdk_auth_version !== "1") {
245
+ throw new BladeConnectionError(
246
+ "incompatible_server",
247
+ "Blade Agent \u7248\u672C\u8FC7\u4F4E\uFF0C\u8BF7\u5347\u7EA7\u670D\u52A1\u540E\u91CD\u8BD5\u3002"
248
+ );
249
+ }
250
+ return data;
251
+ }
252
+ restore() {
253
+ if (this.restorePromise) return this.restorePromise;
254
+ if (this.pendingConnection) {
255
+ return this.pendingConnection.then(
256
+ () => this.snapshot,
257
+ () => this.snapshot
258
+ );
259
+ }
260
+ const promise = this.runRestore().finally(() => {
261
+ if (this.restorePromise === promise) this.restorePromise = null;
262
+ });
263
+ this.restorePromise = promise;
264
+ return promise;
265
+ }
266
+ async runRestore() {
267
+ const generation = this.baseUrlGeneration;
268
+ this.update({ status: "checking_service", user: null, error: null });
269
+ try {
270
+ await this.probe();
271
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
272
+ if (this.client.options.token !== void 0) {
273
+ try {
274
+ const user = await this.getMe();
275
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
276
+ this.update({ status: "connected", user, error: null });
277
+ return this.snapshot;
278
+ } catch (error) {
279
+ if (error instanceof BladeApiError && error.status === 401) {
280
+ this.update({
281
+ status: "expired",
282
+ user: null,
283
+ error: new BladeConnectionError(
284
+ "invalid_credentials",
285
+ "\u63A5\u5165\u65B9\u914D\u7F6E\u7684 Token \u5DF2\u5931\u6548\uFF0C\u8BF7\u66F4\u65B0 Token \u540E\u91CD\u65B0\u68C0\u6D4B\u3002"
286
+ )
287
+ });
288
+ return this.snapshot;
289
+ }
290
+ throw error;
291
+ }
292
+ }
293
+ const stored = await this.storageGet();
294
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
295
+ if (!stored) {
296
+ this.client._setManagedToken(null);
297
+ this.update({ status: "signed_out", user: null, error: null });
298
+ return this.snapshot;
299
+ }
300
+ this.client._setManagedToken(stored.accessToken);
301
+ try {
302
+ const user = await this.getMe();
303
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
304
+ this.client._reconnectSocketWithCurrentAuth();
305
+ this.update({ status: "connected", user, error: null });
306
+ return this.snapshot;
307
+ } catch (error) {
308
+ if (error instanceof BladeApiError && error.status === 401) {
309
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
310
+ await this.clearManagedAuth();
311
+ if (generation !== this.baseUrlGeneration) return this.snapshot;
312
+ this.update({ status: "expired", user: null, error: null });
313
+ return this.snapshot;
314
+ }
315
+ throw error;
316
+ }
317
+ } catch (error) {
318
+ const connectionError = this.toConnectionError(error, "service_unreachable");
319
+ this.update({
320
+ status: connectionError.code === "invalid_url" || connectionError.code === "mixed_content" ? "configuration_error" : "service_unavailable",
321
+ user: null,
322
+ error: connectionError
323
+ });
324
+ return this.snapshot;
325
+ }
326
+ }
327
+ retry() {
328
+ return this.restore();
329
+ }
330
+ connectWithPopup(options = {}) {
331
+ if (this.pendingConnection) return this.pendingConnection;
332
+ const promise = this.runPopupFlow(options).finally(() => {
333
+ if (this.pendingConnection === promise) this.pendingConnection = null;
334
+ this.cancelPending = null;
335
+ this.activePopup = null;
336
+ });
337
+ this.pendingConnection = promise;
338
+ return promise;
339
+ }
340
+ async disconnect() {
341
+ if (this.client.options.token !== void 0) {
342
+ throw new BladeConnectionError(
343
+ "token_exchange_failed",
344
+ "\u5F53\u524D Token \u7531\u63A5\u5165\u65B9\u7BA1\u7406\uFF0C\u8BF7\u7531\u63A5\u5165\u65B9\u5B8C\u6210\u65AD\u5F00\u64CD\u4F5C\u3002"
345
+ );
346
+ }
347
+ const generation = this.baseUrlGeneration;
348
+ await this.clearManagedAuth();
349
+ if (generation !== this.baseUrlGeneration) return;
350
+ this.client._disconnectSocket();
351
+ this.update({ status: "signed_out", user: null, error: null });
352
+ }
353
+ async _handleUnauthorized() {
354
+ if (this.client.options.token !== void 0) return;
355
+ const generation = this.baseUrlGeneration;
356
+ await this.clearManagedAuth();
357
+ if (generation !== this.baseUrlGeneration) return;
358
+ this.client._disconnectSocket();
359
+ this.update({ status: "expired", user: null, error: null });
360
+ }
361
+ async _handleBaseUrlChange() {
362
+ this.baseUrlGeneration += 1;
363
+ const previousRestore = this.restorePromise;
364
+ const previousConnection = this.pendingConnection;
365
+ this.cancelPending?.(
366
+ new BladeConnectionError("invalid_url", "\u670D\u52A1\u5730\u5740\u5DF2\u6539\u53D8\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u53D6\u6D88\u3002")
367
+ );
368
+ this.activePopup?.close();
369
+ this.client._setManagedToken(null);
370
+ this.update({
371
+ status: "checking_service",
372
+ baseUrl: this.client.baseUrl,
373
+ user: null,
374
+ error: null
375
+ });
376
+ if (previousConnection) {
377
+ try {
378
+ await previousConnection;
379
+ } catch {
380
+ }
381
+ }
382
+ if (previousRestore) await previousRestore;
383
+ await this.restore();
384
+ }
385
+ async runPopupFlow(options) {
386
+ if (this.client.options.token !== void 0) {
387
+ throw new BladeConnectionError(
388
+ "token_exchange_failed",
389
+ "\u5DF2\u7ECF\u914D\u7F6E\u663E\u5F0F Token\uFF0C\u65E0\u9700\u518D\u6B21\u6388\u6743\u3002"
390
+ );
391
+ }
392
+ const generation = this.baseUrlGeneration;
393
+ if (typeof window === "undefined" || !window.location) {
394
+ throw new BladeConnectionError("invalid_url", "\u5F39\u7A97\u6388\u6743\u53EA\u80FD\u5728\u6D4F\u89C8\u5668\u9875\u9762\u4E2D\u4F7F\u7528\u3002");
395
+ }
396
+ try {
397
+ const popup = window.open(
398
+ "",
399
+ "blade-agent-sdk-authorization",
400
+ options.windowFeatures ?? DEFAULT_WINDOW_FEATURES
401
+ );
402
+ if (!popup) {
403
+ throw new BladeConnectionError(
404
+ "popup_blocked",
405
+ "\u6D4F\u89C8\u5668\u963B\u6B62\u4E86\u6388\u6743\u7A97\u53E3\uFF0C\u8BF7\u5141\u8BB8\u5F39\u7A97\u540E\u91CD\u8BD5\u3002"
406
+ );
407
+ }
408
+ this.activePopup = popup;
409
+ if (this.restorePromise) {
410
+ await this.restorePromise;
411
+ this.assertGeneration(generation);
412
+ if (this.snapshot.status === "connected" && this.snapshot.user) {
413
+ return this.snapshot.user;
414
+ }
415
+ }
416
+ this.update({ status: "checking_service", user: null, error: null });
417
+ await this.probe();
418
+ if (generation !== this.baseUrlGeneration) {
419
+ throw new BladeConnectionError("invalid_url", "\u670D\u52A1\u5730\u5740\u5DF2\u6539\u53D8\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u53D6\u6D88\u3002");
420
+ }
421
+ const state = createAuthorizationState();
422
+ const clientOrigin = normalizeBrowserOrigin(window.location.origin);
423
+ const authorizeUrl = new URL(this.url("/api/auth/sdk/authorize"));
424
+ authorizeUrl.searchParams.set("client_origin", clientOrigin);
425
+ authorizeUrl.searchParams.set("state", state);
426
+ popup.location.href = authorizeUrl.toString();
427
+ this.update({ status: "waiting_for_authorization", user: null, error: null });
428
+ const message = await this.waitForPopup(
429
+ popup,
430
+ state,
431
+ options.timeoutMs ?? DEFAULT_POPUP_TIMEOUT_MS
432
+ );
433
+ this.assertGeneration(generation);
434
+ if (message.status === "denied") {
435
+ throw new BladeConnectionError("authorization_denied", "\u4F60\u5DF2\u53D6\u6D88\u8FDE\u63A5\u3002");
436
+ }
437
+ if (message.status === "error") {
438
+ if (message.error === "authentication_expired") {
439
+ throw new BladeConnectionError(
440
+ "authorization_expired",
441
+ "Blade Agent \u767B\u5F55\u72B6\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u8FDE\u63A5\u3002"
442
+ );
443
+ }
444
+ throw new BladeConnectionError(
445
+ "token_exchange_failed",
446
+ "Blade Agent \u6682\u65F6\u65E0\u6CD5\u521B\u5EFA\u8FDE\u63A5\u51ED\u636E\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"
447
+ );
448
+ }
449
+ if (!message.code) {
450
+ throw new BladeConnectionError(
451
+ "token_exchange_failed",
452
+ "\u6388\u6743\u7ED3\u679C\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u8FDE\u63A5\u3002"
453
+ );
454
+ }
455
+ this.update({ status: "connecting", user: null, error: null });
456
+ const issued = await this.client.jsonFromInit("/api/auth/sdk/token", {
457
+ method: "POST",
458
+ credentials: "omit",
459
+ body: JSON.stringify({
460
+ code: message.code,
461
+ state,
462
+ client_origin: clientOrigin
463
+ })
464
+ });
465
+ this.assertGeneration(generation);
466
+ if (!issued.access_token || !issued.user?.id) {
467
+ throw new BladeConnectionError(
468
+ "token_exchange_failed",
469
+ "Blade Agent \u672A\u8FD4\u56DE\u6709\u6548\u7684\u8FDE\u63A5\u51ED\u636E\u3002"
470
+ );
471
+ }
472
+ this.client._setManagedToken(issued.access_token);
473
+ const verified = await this.getMe();
474
+ this.assertGeneration(generation);
475
+ if (verified.id !== issued.user.id) {
476
+ throw new BladeConnectionError("origin_mismatch", "\u6388\u6743\u8D26\u53F7\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u8FDE\u63A5\u3002");
477
+ }
478
+ const user = {
479
+ id: verified.id,
480
+ username: verified.username,
481
+ display_name: verified.display_name,
482
+ avatar_url: verified.avatar_url
483
+ };
484
+ await this.storageSet({
485
+ accessToken: issued.access_token,
486
+ user,
487
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
488
+ });
489
+ this.assertGeneration(generation);
490
+ this.update({ status: "connected", user, error: null });
491
+ this.client._reconnectSocketWithCurrentAuth();
492
+ return user;
493
+ } catch (error) {
494
+ if (generation === this.baseUrlGeneration) await this.clearManagedAuth();
495
+ const connectionError = this.toConnectionError(error, "token_exchange_failed");
496
+ if (generation === this.baseUrlGeneration) {
497
+ this.update({
498
+ status: connectionError.code === "authorization_denied" || connectionError.code === "popup_closed" ? "signed_out" : connectionError.code === "mixed_content" || connectionError.code === "invalid_url" ? "configuration_error" : "service_unavailable",
499
+ user: null,
500
+ error: connectionError
501
+ });
502
+ }
503
+ throw connectionError;
504
+ } finally {
505
+ this.activePopup?.close();
506
+ }
507
+ }
508
+ waitForPopup(popup, state, timeoutMs) {
509
+ const expectedOrigin = new URL(this.client.baseUrl).origin;
510
+ return new Promise((resolve, reject) => {
511
+ let settled = false;
512
+ const finish = (error, message) => {
513
+ if (settled) return;
514
+ settled = true;
515
+ window.removeEventListener("message", onMessage);
516
+ clearInterval(closedTimer);
517
+ clearTimeout(expiryTimer);
518
+ this.cancelPending = null;
519
+ if (error) reject(error);
520
+ else if (message) resolve(message);
521
+ };
522
+ const onMessage = (event) => {
523
+ const data = event.data;
524
+ if (event.source !== popup || data?.type !== "blade-agent:sdk-auth") return;
525
+ if (event.origin !== expectedOrigin || data.version !== 1 || data.state !== state) {
526
+ finish(
527
+ new BladeConnectionError(
528
+ "origin_mismatch",
529
+ "\u6388\u6743\u7ED3\u679C\u6765\u6E90\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u5173\u95ED\u7A97\u53E3\u540E\u91CD\u65B0\u8FDE\u63A5\u3002"
530
+ )
531
+ );
532
+ return;
533
+ }
534
+ if (data.status !== "success" && data.status !== "denied" && data.status !== "error") {
535
+ return;
536
+ }
537
+ finish(void 0, data);
538
+ };
539
+ window.addEventListener("message", onMessage);
540
+ const closedTimer = window.setInterval(() => {
541
+ if (popup.closed) {
542
+ finish(new BladeConnectionError("popup_closed", "\u6388\u6743\u7A97\u53E3\u5DF2\u5173\u95ED\u3002"));
543
+ }
544
+ }, 250);
545
+ const expiryTimer = window.setTimeout(() => {
546
+ finish(
547
+ new BladeConnectionError(
548
+ "authorization_expired",
549
+ "\u6388\u6743\u7B49\u5F85\u65F6\u95F4\u5DF2\u7ED3\u675F\uFF0C\u8BF7\u91CD\u65B0\u8FDE\u63A5\u3002"
550
+ )
551
+ );
552
+ }, timeoutMs);
553
+ this.cancelPending = (error) => finish(error);
554
+ });
555
+ }
556
+ assertNoMixedContent() {
557
+ if (typeof window !== "undefined" && window.location.protocol === "https:" && new URL(this.client.baseUrl).protocol === "http:") {
558
+ throw new BladeConnectionError(
559
+ "mixed_content",
560
+ "\u5F53\u524D\u9875\u9762\u4F7F\u7528 HTTPS\uFF0CBlade Agent \u4E5F\u9700\u8981\u4F7F\u7528 HTTPS \u5730\u5740\u6216\u901A\u8FC7\u53CD\u5411\u4EE3\u7406\u8BBF\u95EE\u3002"
561
+ );
562
+ }
563
+ }
564
+ assertGeneration(generation) {
565
+ if (generation !== this.baseUrlGeneration) {
566
+ throw new BladeConnectionError("invalid_url", "\u670D\u52A1\u5730\u5740\u5DF2\u6539\u53D8\uFF0C\u672C\u6B21\u6388\u6743\u5DF2\u53D6\u6D88\u3002");
567
+ }
568
+ }
569
+ url(path) {
570
+ const base = this.client.baseUrl.endsWith("/") ? this.client.baseUrl : `${this.client.baseUrl}/`;
571
+ return new URL(path.replace(/^\//, ""), base).toString();
572
+ }
573
+ storageKey() {
574
+ return `blade-agent:auth:v1:${encodeURIComponent(this.client.baseUrl)}`;
575
+ }
576
+ async storageGet() {
577
+ try {
578
+ return await this.storage.get(this.storageKey());
579
+ } catch {
580
+ this.useMemoryStorage();
581
+ return null;
582
+ }
583
+ }
584
+ async storageSet(value) {
585
+ const generation = this.baseUrlGeneration;
586
+ const key = this.storageKey();
587
+ try {
588
+ await this.storage.set(key, value);
589
+ } catch {
590
+ this.assertGeneration(generation);
591
+ this.useMemoryStorage();
592
+ await this.storage.set(key, value);
593
+ }
594
+ }
595
+ async clearManagedAuth() {
596
+ const generation = this.baseUrlGeneration;
597
+ const key = this.storageKey();
598
+ this.client._setManagedToken(null);
599
+ try {
600
+ await this.storage.remove(key);
601
+ } catch {
602
+ if (generation !== this.baseUrlGeneration) return;
603
+ this.useMemoryStorage();
604
+ await this.storage.remove(key);
605
+ }
606
+ }
607
+ useMemoryStorage() {
608
+ this.storage = this.memoryStorage;
609
+ this.persistent = false;
610
+ this.snapshot = { ...this.snapshot, persistent: false };
611
+ }
612
+ update(next) {
613
+ this.snapshot = {
614
+ ...this.snapshot,
615
+ ...next,
616
+ baseUrl: next.baseUrl ?? this.client.baseUrl,
617
+ persistent: this.persistent,
618
+ managedBySdk: this.client.options.token === void 0
619
+ };
620
+ for (const listener of this.listeners) listener();
621
+ }
622
+ toConnectionError(error, fallback) {
623
+ if (error instanceof BladeConnectionError) return error;
624
+ return new BladeConnectionError(
625
+ fallback,
626
+ fallback === "service_unreachable" ? "\u65E0\u6CD5\u8FDE\u63A5\u5230 Blade Agent\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u5730\u5740\u540E\u91CD\u8BD5\u3002" : "\u8FDE\u63A5\u6CA1\u6709\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u6388\u6743\u3002",
627
+ { cause: error }
628
+ );
629
+ }
113
630
  };
631
+ function normalizeBrowserOrigin(value) {
632
+ const url = new URL(value);
633
+ if (!/^https?:$/.test(url.protocol) || url.origin === "null") {
634
+ throw new BladeConnectionError("invalid_url", "\u5F53\u524D\u9875\u9762\u5FC5\u987B\u901A\u8FC7 HTTP \u6216 HTTPS \u6253\u5F00\u3002");
635
+ }
636
+ return url.origin;
637
+ }
638
+ function createAuthorizationState() {
639
+ const bytes = new Uint8Array(24);
640
+ crypto.getRandomValues(bytes);
641
+ return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
642
+ }
114
643
 
115
644
  // src/client/resources/env-buckets.ts
116
645
  var EnvBucketsResource = class {
@@ -2590,25 +3119,13 @@ var UserPreferencesResource = class {
2590
3119
 
2591
3120
  // src/client/socket.ts
2592
3121
  import { io } from "socket.io-client";
2593
-
2594
- // src/client/auth.ts
2595
- function buildSocketAuth(options) {
2596
- const token = resolveAuthToken(options);
2597
- return token ? { token } : void 0;
2598
- }
2599
- function resolveAuthToken(options) {
2600
- const token = typeof options.token === "function" ? options.token() : options.token;
2601
- return token ? token : null;
2602
- }
2603
-
2604
- // src/client/socket.ts
2605
3122
  function createSocket(options) {
2606
3123
  const auth = buildSocketAuth(options);
2607
3124
  const token = resolveAuthToken(options);
2608
3125
  return io(options.baseUrl, {
2609
3126
  path: options.path ?? "/socket.io",
2610
- withCredentials: true,
2611
- query: token ? { token } : void 0,
3127
+ withCredentials: options.withCredentials ?? true,
3128
+ query: typeof options.token === "function" || !token ? void 0 : { token },
2612
3129
  auth: typeof options.token === "function" ? (cb) => cb(buildSocketAuth(options) ?? {}) : auth,
2613
3130
  autoConnect: false
2614
3131
  });
@@ -2619,6 +3136,8 @@ var REFRESH_PATH = "/api/auth/refresh";
2619
3136
  var BladeClient = class {
2620
3137
  refreshPromise = null;
2621
3138
  socketInstance = null;
3139
+ managedToken = null;
3140
+ baseUrlChangeListeners = /* @__PURE__ */ new Set();
2622
3141
  storeRestTokenResolver = null;
2623
3142
  storeSocketTokenResolver = null;
2624
3143
  options;
@@ -2638,10 +3157,10 @@ var BladeClient = class {
2638
3157
  skills;
2639
3158
  solutions;
2640
3159
  userPreferences;
2641
- constructor(options) {
3160
+ constructor(options = {}) {
2642
3161
  this.options = {
2643
3162
  ...options,
2644
- baseUrl: normalizeBaseUrl(options.baseUrl)
3163
+ baseUrl: resolveBladeBaseUrl(options)
2645
3164
  };
2646
3165
  this.apiKeys = new ApiKeysResource(this);
2647
3166
  this.appDevTemplates = new AppDevTemplatesResource(this);
@@ -2667,22 +3186,47 @@ var BladeClient = class {
2667
3186
  this.storeSocketTokenResolver = fn;
2668
3187
  }
2669
3188
  setBaseUrl(baseUrl) {
2670
- const nextBaseUrl = normalizeBaseUrl(baseUrl);
3189
+ const nextBaseUrl = resolveBladeBaseUrl({ baseUrl });
2671
3190
  if (nextBaseUrl === this.options.baseUrl) {
2672
3191
  return;
2673
3192
  }
2674
3193
  this.options.baseUrl = nextBaseUrl;
3194
+ this.managedToken = null;
2675
3195
  if (this.socketInstance) {
2676
3196
  this.socketInstance.removeAllListeners();
2677
3197
  this.socketInstance.disconnect();
2678
3198
  this.socketInstance = null;
2679
3199
  }
3200
+ for (const listener of this.baseUrlChangeListeners) listener();
3201
+ void this.auth._handleBaseUrlChange();
3202
+ }
3203
+ onBaseUrlChange(listener) {
3204
+ this.baseUrlChangeListeners.add(listener);
3205
+ return () => this.baseUrlChangeListeners.delete(listener);
3206
+ }
3207
+ get baseUrl() {
3208
+ return this.options.baseUrl ?? "";
3209
+ }
3210
+ _setManagedToken(token) {
3211
+ this.managedToken = token;
3212
+ }
3213
+ _reconnectSocketWithCurrentAuth() {
3214
+ if (!this.socketInstance) return;
3215
+ this.socketInstance.disconnect();
3216
+ this.socketInstance.io.opts.withCredentials = !this.shouldOmitManagedCredentials(
3217
+ new URL(this.options.baseUrl)
3218
+ );
3219
+ this.socketInstance.connect();
3220
+ }
3221
+ _disconnectSocket() {
3222
+ this.socketInstance?.disconnect();
2680
3223
  }
2681
3224
  socket() {
2682
3225
  if (!this.socketInstance) {
2683
3226
  this.socketInstance = createSocket({
2684
3227
  baseUrl: this.options.baseUrl,
2685
- token: () => this.resolveSocketToken()
3228
+ token: () => this.resolveSocketToken(),
3229
+ withCredentials: !this.shouldOmitManagedCredentials(new URL(this.options.baseUrl))
2686
3230
  });
2687
3231
  }
2688
3232
  return this.socketInstance;
@@ -2743,10 +3287,11 @@ var BladeClient = class {
2743
3287
  }
2744
3288
  async fetch(method, path, init = {}, isRetry = false) {
2745
3289
  const url = this.buildUrl(path);
3290
+ const managedTokenAtRequest = this.managedToken;
2746
3291
  const response = await (this.options.fetchImpl ?? fetch)(url.toString(), {
2747
3292
  method,
2748
3293
  body: init.body,
2749
- credentials: init.credentials ?? "include",
3294
+ credentials: init.credentials ?? this.defaultCredentials(url),
2750
3295
  headers: this.buildHeaders(url, init.headers),
2751
3296
  signal: init.signal
2752
3297
  });
@@ -2756,6 +3301,9 @@ var BladeClient = class {
2756
3301
  return this.fetch(method, path, init, true);
2757
3302
  }
2758
3303
  }
3304
+ if (response.status === 401 && managedTokenAtRequest && this.managedToken === managedTokenAtRequest && this.isSameBackendUrl(url)) {
3305
+ void this.auth._handleUnauthorized();
3306
+ }
2759
3307
  if (init.expectOk !== false && !response.ok) {
2760
3308
  const detail = await extractErrorDetail(response);
2761
3309
  throw new BladeApiError(response, detail);
@@ -2803,7 +3351,13 @@ var BladeClient = class {
2803
3351
  }
2804
3352
  shouldRefreshFor401(url) {
2805
3353
  const refreshUrl = this.buildUrl(REFRESH_PATH);
2806
- return this.isSameBackendUrl(url) && url.pathname !== refreshUrl.pathname && !this.hasExplicitBearerToken();
3354
+ return this.isSameBackendUrl(url) && url.pathname !== refreshUrl.pathname && !this.hasExplicitBearerToken() && !this.managedToken;
3355
+ }
3356
+ defaultCredentials(url) {
3357
+ return this.shouldOmitManagedCredentials(url) ? "omit" : "include";
3358
+ }
3359
+ shouldOmitManagedCredentials(url) {
3360
+ return !!this.managedToken && typeof window !== "undefined" && !!window.location?.origin && url.origin !== window.location.origin;
2807
3361
  }
2808
3362
  hasExplicitBearerToken() {
2809
3363
  return this.options.token !== void 0;
@@ -2838,7 +3392,7 @@ var BladeClient = class {
2838
3392
  if (!this.isSameBackendUrl(url)) {
2839
3393
  return null;
2840
3394
  }
2841
- return this.resolveRestToken();
3395
+ return this.resolveConfiguredToken(this.storeRestTokenResolver);
2842
3396
  }
2843
3397
  resolveRestToken() {
2844
3398
  return this.resolveToken(this.storeRestTokenResolver);
@@ -2847,16 +3401,20 @@ var BladeClient = class {
2847
3401
  return this.resolveToken(this.storeSocketTokenResolver);
2848
3402
  }
2849
3403
  resolveToken(resolver) {
2850
- const token = this.options.token === void 0 ? resolver?.() : typeof this.options.token === "function" ? this.options.token() : this.options.token;
2851
- return token ? token : null;
3404
+ return this.resolveConfiguredToken(resolver) || this.managedToken || null;
3405
+ }
3406
+ resolveConfiguredToken(resolver) {
3407
+ const configuredToken = this.options.token === void 0 ? resolver?.() : typeof this.options.token === "function" ? this.options.token() : this.options.token;
3408
+ return configuredToken || null;
2852
3409
  }
2853
3410
  formDataWithUploadProgress(method, path, form, options, isRetry = false) {
2854
3411
  const url = this.buildUrl(path);
3412
+ const managedTokenAtRequest = this.managedToken;
2855
3413
  const headers = this.buildHeaders(url);
2856
3414
  return new Promise((resolve, reject) => {
2857
3415
  const xhr = new XMLHttpRequest();
2858
3416
  xhr.open(method, url.toString(), true);
2859
- xhr.withCredentials = true;
3417
+ xhr.withCredentials = this.defaultCredentials(url) === "include";
2860
3418
  headers.forEach((value, key) => {
2861
3419
  xhr.setRequestHeader(key, value);
2862
3420
  });
@@ -2878,6 +3436,9 @@ var BladeClient = class {
2878
3436
  return;
2879
3437
  }
2880
3438
  }
3439
+ if (xhr.status === 401 && managedTokenAtRequest && this.managedToken === managedTokenAtRequest && this.isSameBackendUrl(url)) {
3440
+ void this.auth._handleUnauthorized();
3441
+ }
2881
3442
  const response = new Response(xhr.responseText, {
2882
3443
  status: xhr.status,
2883
3444
  statusText: xhr.statusText,
@@ -2895,12 +3456,44 @@ var BladeClient = class {
2895
3456
  });
2896
3457
  }
2897
3458
  };
2898
- function normalizeBaseUrl(baseUrl) {
2899
- const normalized = baseUrl.trim().replace(/\/+$/, "");
2900
- if (normalized || typeof window === "undefined") {
2901
- return normalized;
3459
+ function resolveBladeBaseUrl(options = {}) {
3460
+ const explicit = options.baseUrl?.trim() ?? "";
3461
+ if (explicit && options.port !== void 0) {
3462
+ throw new TypeError("baseUrl and port cannot be configured at the same time");
3463
+ }
3464
+ if (options.port !== void 0 && (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535)) {
3465
+ throw new TypeError("port must be an integer between 1 and 65535");
3466
+ }
3467
+ let candidate = explicit;
3468
+ if (!candidate) {
3469
+ if (typeof window === "undefined" || !window.location?.hostname) {
3470
+ throw new TypeError(
3471
+ "BladeClient requires baseUrl outside a browser, for example new BladeClient({ baseUrl: 'http://127.0.0.1:8020' })"
3472
+ );
3473
+ }
3474
+ const protocol = window.location.protocol;
3475
+ if (protocol !== "http:" && protocol !== "https:") {
3476
+ throw new TypeError("BladeClient automatic baseUrl requires an HTTP(S) page");
3477
+ }
3478
+ const url = new URL(window.location.origin);
3479
+ url.port = String(options.port ?? 8020);
3480
+ url.pathname = "/";
3481
+ url.search = "";
3482
+ url.hash = "";
3483
+ candidate = url.toString();
3484
+ }
3485
+ let parsed;
3486
+ try {
3487
+ parsed = new URL(candidate);
3488
+ } catch (error) {
3489
+ throw new TypeError("baseUrl must be a valid HTTP(S) URL", { cause: error });
3490
+ }
3491
+ if (!/^https?:$/.test(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash) {
3492
+ throw new TypeError(
3493
+ "baseUrl must be an HTTP(S) URL without embedded credentials, query, or fragment"
3494
+ );
2902
3495
  }
2903
- return window.location.origin;
3496
+ return parsed.toString().replace(/\/+$/, "");
2904
3497
  }
2905
3498
  function isFormData(value) {
2906
3499
  return typeof FormData !== "undefined" && value instanceof FormData;
@@ -2925,6 +3518,9 @@ export {
2925
3518
  BladeApiError,
2926
3519
  ApiKeysResource,
2927
3520
  AppDevTemplatesResource,
3521
+ BladeConnectionError,
3522
+ createMemoryAuthStorage,
3523
+ createLocalAuthStorage,
2928
3524
  AuthResource,
2929
3525
  GisResource,
2930
3526
  HeadlessError,
@@ -2945,6 +3541,7 @@ export {
2945
3541
  SolutionsResource,
2946
3542
  UserPreferencesResource,
2947
3543
  createSocket,
2948
- BladeClient
3544
+ BladeClient,
3545
+ resolveBladeBaseUrl
2949
3546
  };
2950
- //# sourceMappingURL=chunk-FK4CV6R2.js.map
3547
+ //# sourceMappingURL=chunk-C63X3RCM.js.map