@blade-hq/agent-client 2610.0.0-beta.11 → 2610.0.0-beta.12

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
@@ -2,11 +2,12 @@
2
2
 
3
3
  Blade Agent 的框架无关客户端。浏览器和 Node.js 都能用;用 Vue、Svelte 或自建 UI 的团队直接用这个包,React 团队一般用上层的 `@blade-hq/agent-react`。
4
4
 
5
- 它做三件事:
5
+ 它做四件事:
6
6
 
7
7
  1. **实时会话**(`AgentSession`):把 Socket.IO 协议、历史加载、流式合流、断线重连全部封装掉,你只面对"状态快照 + 动作 + 事件"。
8
8
  2. **登录**:`client.auth.login()` 弹窗授权,用户点一下"许可授权"就拿到访问令牌,不用手工复制粘贴。
9
9
  3. **REST**:类型化会话和模型目录(`client.sessions.*`、`client.models.list()`)。其他长尾接口对照 Swagger 用原生 `fetch` + `client.token` 调用。
10
+ 4. **部署端点**:从同源 `config.json` 读取其他 Blade 服务的公开地址,不根据主机名和固定端口猜测拓扑。
10
11
 
11
12
  ```bash
12
13
  npm install @blade-hq/agent-client
@@ -47,6 +48,27 @@ const client = new BladeClient({
47
48
  })
48
49
  ```
49
50
 
51
+ ## 部署端点
52
+
53
+ Blade 平台前端需要跳转其他服务时,读取当前 origin 的公开配置:
54
+
55
+ ```ts
56
+ import { loadPlatformEndpoints, resolveServiceUrl } from "@blade-hq/agent-client"
57
+
58
+ const endpoints = await loadPlatformEndpoints()
59
+ const hubUrl = resolveServiceUrl(endpoints, "hub", "/skills/42")
60
+ if (hubUrl) window.open(hubUrl)
61
+ ```
62
+
63
+ `loadPlatformEndpoints()` 请求 `config.json`,超过 3 秒、网络失败或配置非法时返回空配置;
64
+ 调用方应隐藏对应入口。文件只能放浏览器可访问的公开地址,不能写 Docker 服务名、令牌或
65
+ 其他内部配置。应用部署在子路径时,通过 `baseUrl` 显式传入部署根路径。
66
+ `resolveServiceUrl()` 的 `path` 只接受服务内相对路径;绝对 URL、反斜杠和越出服务
67
+ base path 的路径返回 `null`。
68
+
69
+ 公开类型为 `PlatformEndpoints`、`PlatformServiceName`、`LoadPlatformEndpointsOptions`;
70
+ 需要同步初始化时可直接使用 `EMPTY_PLATFORM_ENDPOINTS`。
71
+
50
72
  ## BladeClient
51
73
 
52
74
  ### 构造
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { BladeClient } from "./blade-client";
2
2
  export type { BladeClientOptions, UploadProgress } from "./blade-client";
3
3
  export { BladeApiError } from "./rest";
4
4
  export type { LoginOptions, LoginResult, TokenStorageMode } from "./auth-login";
5
+ export { EMPTY_PLATFORM_ENDPOINTS, loadPlatformEndpoints, resolveServiceUrl, } from "./platform-endpoints";
6
+ export type { LoadPlatformEndpointsOptions, PlatformEndpoints, PlatformServiceName, } from "./platform-endpoints";
5
7
  export { SDK_NAME, SDK_VERSION } from "./version";
6
8
  export { AgentSession } from "./session/agent-session";
7
9
  export type { AttachAppOptions, SendOptions } from "./session/agent-session";
package/dist/index.js CHANGED
@@ -4499,7 +4499,7 @@ function resolveAuthToken(options) {
4499
4499
 
4500
4500
  // src/version.ts
4501
4501
  var SDK_NAME = "agent-client";
4502
- var SDK_VERSION = true ? "2610.0.0-beta.11" : "1.1.1";
4502
+ var SDK_VERSION = true ? "2610.0.0-beta.12" : "1.1.1";
4503
4503
 
4504
4504
  // src/socket.ts
4505
4505
  function withSdkIdentity(auth) {
@@ -4892,6 +4892,87 @@ function parseXhrHeaders(rawHeaders) {
4892
4892
  return headers;
4893
4893
  }
4894
4894
 
4895
+ // src/platform-endpoints.ts
4896
+ var PLATFORM_SERVICE_NAMES = [
4897
+ "os",
4898
+ "hub",
4899
+ "agent",
4900
+ "oauth",
4901
+ "llmGateway",
4902
+ "gitea"
4903
+ ];
4904
+ var EMPTY_PLATFORM_ENDPOINTS = Object.freeze({
4905
+ services: Object.freeze({})
4906
+ });
4907
+ function defaultDocumentBaseUrl() {
4908
+ if (typeof document === "undefined") return void 0;
4909
+ if (document.querySelector("base[href]")) return document.baseURI;
4910
+ if (typeof location !== "undefined" && location.origin) return `${location.origin}/`;
4911
+ return document.baseURI;
4912
+ }
4913
+ function isRecord6(value) {
4914
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4915
+ }
4916
+ function normalizeServiceUrl(value) {
4917
+ if (typeof value !== "string" || !value.trim()) return null;
4918
+ if (value.includes("?") || value.includes("#")) return null;
4919
+ try {
4920
+ const url = new URL(value.trim());
4921
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
4922
+ if (url.username || url.password || url.search || url.hash) return null;
4923
+ return url.toString().replace(/\/$/, "");
4924
+ } catch {
4925
+ return null;
4926
+ }
4927
+ }
4928
+ function parsePlatformEndpoints(value) {
4929
+ if (!isRecord6(value) || !isRecord6(value.services)) return null;
4930
+ const services = {};
4931
+ for (const name of PLATFORM_SERVICE_NAMES) {
4932
+ if (!(name in value.services)) continue;
4933
+ const url = normalizeServiceUrl(value.services[name]);
4934
+ if (!url) return null;
4935
+ services[name] = url;
4936
+ }
4937
+ return { services };
4938
+ }
4939
+ async function loadPlatformEndpoints(options = {}) {
4940
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
4941
+ const baseUrl = options.baseUrl ?? defaultDocumentBaseUrl();
4942
+ if (!fetchImpl || !baseUrl) return EMPTY_PLATFORM_ENDPOINTS;
4943
+ const controller = new AbortController();
4944
+ const timeout = globalThis.setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
4945
+ try {
4946
+ const response = await fetchImpl(new URL("config.json", baseUrl), {
4947
+ cache: "no-store",
4948
+ credentials: "same-origin",
4949
+ headers: { Accept: "application/json" },
4950
+ signal: controller.signal
4951
+ });
4952
+ if (!response.ok) return EMPTY_PLATFORM_ENDPOINTS;
4953
+ return parsePlatformEndpoints(await response.json()) ?? EMPTY_PLATFORM_ENDPOINTS;
4954
+ } catch {
4955
+ return EMPTY_PLATFORM_ENDPOINTS;
4956
+ } finally {
4957
+ globalThis.clearTimeout(timeout);
4958
+ }
4959
+ }
4960
+ function resolveServiceUrl(endpoints, name, path = "") {
4961
+ const baseUrl = endpoints.services[name];
4962
+ if (!baseUrl) return null;
4963
+ if (!path) return baseUrl;
4964
+ const pathReference = path.trimStart();
4965
+ if (path.includes("\\") || pathReference.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(pathReference)) {
4966
+ return null;
4967
+ }
4968
+ const base = new URL(`${baseUrl}/`);
4969
+ const target = new URL(path.replace(/^\/+/, ""), base);
4970
+ if (target.origin !== base.origin || !target.pathname.startsWith(base.pathname)) {
4971
+ return null;
4972
+ }
4973
+ return target.toString();
4974
+ }
4975
+
4895
4976
  // src/commands/protocol.ts
4896
4977
  function isCommandEnvelope(value) {
4897
4978
  if (typeof value !== "object" || value === null) return false;
@@ -4996,6 +5077,7 @@ export {
4996
5077
  BladeClient,
4997
5078
  ClientProjectionBuilder,
4998
5079
  DEFAULT_REPLAY_SPEED,
5080
+ EMPTY_PLATFORM_ENDPOINTS,
4999
5081
  LayoutType,
5000
5082
  ModelsResource,
5001
5083
  SDK_NAME,
@@ -5021,8 +5103,10 @@ export {
5021
5103
  isHiddenInternalMessage,
5022
5104
  isInboundEnvelope,
5023
5105
  latestPostChatFollowup,
5106
+ loadPlatformEndpoints,
5024
5107
  normalizeMessageContent,
5025
5108
  reconcileOptimisticUserTurns,
5109
+ resolveServiceUrl,
5026
5110
  toReplaySnapshot,
5027
5111
  transformSlashCommand
5028
5112
  };