@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
package/README.md CHANGED
@@ -67,7 +67,88 @@ function BashTool({ toolCall }: ToolRendererProps) {
67
67
 
68
68
  ## 认证
69
69
 
70
- SDK 支持 cookie 会话和 Bearer Token 两种模式。
70
+ SDK 支持网页授权、cookie 会话和显式 Bearer Token 三种模式。
71
+
72
+ ### 浏览器零配置接入
73
+
74
+ 浏览器中可以不填写后端地址和 PAT。SDK 默认使用当前页面的协议与 hostname,并连接 `8020` 端口。例如页面地址是 `https://demo.example.com:5173`,默认后端地址为 `https://demo.example.com:8020`。
75
+
76
+ React 应用可以直接初始化客户端并渲染完整连接面板:
77
+
78
+ ```tsx
79
+ import {
80
+ BladeClientProvider,
81
+ BladeConnectionPanel,
82
+ bootstrapBladeClient,
83
+ } from "@blade-hq/agent-kit/react"
84
+
85
+ const client = bootstrapBladeClient()
86
+
87
+ root.render(
88
+ <BladeClientProvider client={client}>
89
+ <BladeConnectionPanel />
90
+ <App />
91
+ </BladeClientProvider>,
92
+ )
93
+ ```
94
+
95
+ 如果业务页面只需要紧凑入口,可以使用:
96
+
97
+ ```tsx
98
+ import { BladeConnectionButton } from "@blade-hq/agent-kit/connection"
99
+
100
+ <BladeConnectionButton />
101
+ ```
102
+
103
+ 用户点击后会打开 Blade Agent 登录和授权窗口。授权完成后,一次性授权码会被兑换成 PAT;PAT 不会出现在 URL 或跨窗口消息中。
104
+
105
+ 非 React 应用可以使用同一套底层 API 自建界面:
106
+
107
+ ```ts
108
+ import { BladeClient } from "@blade-hq/agent-kit/client"
109
+
110
+ const client = new BladeClient()
111
+ await client.auth.restore()
112
+
113
+ loginButton.addEventListener("click", async () => {
114
+ const user = await client.auth.connectWithPopup()
115
+ console.log(`已连接:${user.display_name ?? user.username}`)
116
+ })
117
+ ```
118
+
119
+ `connectWithPopup()` 必须直接在点击等用户手势中调用,否则浏览器可能拦截授权窗口。`client.auth.getConnectionSnapshot()` 和 `subscribe()` 可用于 Vue、Svelte 等框架的状态绑定。
120
+
121
+ ### 手动配置后端地址
122
+
123
+ 完整地址配置优先于自动推导:
124
+
125
+ ```ts
126
+ const client = new BladeClient({ baseUrl: "https://blade.example.com" })
127
+ ```
128
+
129
+ 如果只需覆盖默认端口,可以使用:
130
+
131
+ ```ts
132
+ const client = new BladeClient({ port: 9000 })
133
+ ```
134
+
135
+ `baseUrl` 和 `port` 不能同时传入。切换运行中的客户端可调用 `client.setBaseUrl(nextUrl)`;SDK 会断开旧 Socket,并按新后端隔离恢复登录状态。
136
+
137
+ Node.js、SSR 和其他没有 `window.location` 的环境无法自动推导地址,必须显式传入 `baseUrl`。popup 登录也只在浏览器中可用,服务端程序应使用显式 PAT。
138
+
139
+ ### 登录状态存储
140
+
141
+ 网页授权默认把认证信息保存到业务页面的 `localStorage`,并按后端 Base URL 隔离。断开连接只会清理当前页面针对当前后端保存的认证信息,不会撤销账户级 PAT。
142
+
143
+ 如果宿主页面安全要求更高,可以仅保存在内存中:
144
+
145
+ ```ts
146
+ const client = new BladeClient({ authStorage: false })
147
+ ```
148
+
149
+ 也可以传入实现 `get`、`set`、`remove` 的自定义 `BladeAuthStorage`。宿主页面若发生 XSS,页面脚本可能读取 localStorage 中的 PAT;接入方应根据自身安全边界选择存储方式。
150
+
151
+ ### Cookie 会话
71
152
 
72
153
  首方 Web 应用通常使用 cookie 会话:
73
154
 
@@ -77,7 +158,9 @@ const client = new BladeClient({ baseUrl: "https://blade.example.com" })
77
158
 
78
159
  这种模式下,REST 请求始终使用 `credentials: "include"`,不会发送 `Authorization`;Socket.IO 连接不发送 token,由浏览器自动携带 cookie。
79
160
 
80
- 第三方接入使用 Bearer Token。Token 必须在构造 `BladeClient` 时注入,不能在单次方法调用时临时传入:
161
+ ### 显式 PAT
162
+
163
+ 自动授权不适用时,可以在构造 `BladeClient` 时注入 PAT。Token 不能在单次方法调用时临时传入:
81
164
 
82
165
  ```ts
83
166
  const client = new BladeClient({
@@ -90,6 +173,16 @@ const client = new BladeClient({
90
173
 
91
174
  如果 `token` 是空字符串,SDK 按 cookie 会话模式处理,不会发送空的 `Authorization` 或 Socket.IO token。`buildAuthedUrl()` 只会给同一后端域名追加 `?token=...`,用于文件预览等无法自定义请求头的场景;外部绝对 URL 不会被追加 Blade token。
92
175
 
176
+ 显式传入的 Token 始终由调用方管理。SDK 不会持久化、覆盖或删除它,也不会对它执行 popup 授权流程。
177
+
178
+ ### 常见连接问题
179
+
180
+ - **服务未启动或地址错误**:确认 Blade Agent 已监听目标 hostname 和端口,再在连接面板中修改地址并重新检测。
181
+ - **HTTPS 页面连接 HTTP 后端**:浏览器会阻止混合内容。请为 Blade Agent 提供 HTTPS,或通过同一 HTTPS 入口反向代理。
182
+ - **授权窗口没有打开**:允许当前业务页面弹出窗口,然后重新点击“连接 Blade Agent”。
183
+ - **登录已失效**:SDK 会清理失效的受管 Token,重新点击连接即可。
184
+ - **页面刷新后需要再次连接**:浏览器可能禁用了 localStorage,或接入方配置了 `authStorage: false`。
185
+
93
186
  ## 类型生成
94
187
 
95
188
  SDK 的类型来源以后端为准,生成产物提交到仓库,确保本地安装后可以直接 typecheck。
@@ -0,0 +1,251 @@
1
+ import {
2
+ getBootstrappedClient
3
+ } from "./chunk-YJSN44Q4.js";
4
+ import {
5
+ cn
6
+ } from "./chunk-7LEKQI47.js";
7
+
8
+ // src/react/hooks/use-blade-connection.ts
9
+ import { useCallback, useEffect, useSyncExternalStore } from "react";
10
+
11
+ // src/react/provider.tsx
12
+ import { useContext } from "react";
13
+
14
+ // src/react/context.ts
15
+ import { createContext } from "react";
16
+ var BladeContext = createContext(null);
17
+
18
+ // src/react/provider.tsx
19
+ import { jsx } from "react/jsx-runtime";
20
+ function BladeClientProvider({ client, children }) {
21
+ const instance = client ?? getBootstrappedClient();
22
+ return /* @__PURE__ */ jsx(BladeContext.Provider, { value: instance, children });
23
+ }
24
+ function useBladeClient() {
25
+ return useContext(BladeContext) ?? getBootstrappedClient();
26
+ }
27
+
28
+ // src/react/hooks/use-blade-connection.ts
29
+ function useBladeConnection() {
30
+ const client = useBladeClient();
31
+ const snapshot = useSyncExternalStore(
32
+ useCallback((listener) => client.auth.subscribe(listener), [client]),
33
+ useCallback(() => client.auth.getConnectionSnapshot(), [client]),
34
+ useCallback(() => client.auth.getConnectionSnapshot(), [client])
35
+ );
36
+ useEffect(() => {
37
+ void client.auth.restore();
38
+ }, [client]);
39
+ return {
40
+ ...snapshot,
41
+ connect: useCallback(
42
+ (options) => client.auth.connectWithPopup(options),
43
+ [client]
44
+ ),
45
+ disconnect: useCallback(() => client.auth.disconnect(), [client]),
46
+ retry: useCallback(() => client.auth.retry(), [client]),
47
+ setBaseUrl: useCallback((baseUrl) => client.setBaseUrl(baseUrl), [client])
48
+ };
49
+ }
50
+
51
+ // src/react/components/connection/BladeConnectionButton.tsx
52
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
53
+ function BladeConnectionButton({
54
+ className,
55
+ onConnected,
56
+ onError
57
+ }) {
58
+ const connection = useBladeConnection();
59
+ const busy = connection.status === "checking_service" || connection.status === "waiting_for_authorization" || connection.status === "connecting";
60
+ const busyLabel = connection.status === "checking_service" ? "\u6B63\u5728\u68C0\u67E5\u670D\u52A1\u2026" : connection.status === "waiting_for_authorization" ? "\u8BF7\u5728\u65B0\u7A97\u53E3\u5B8C\u6210\u6388\u6743\u2026" : "\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5\u2026";
61
+ const label = connection.status === "connected" ? `\u5DF2\u8FDE\u63A5${connection.user?.display_name || connection.user?.username ? ` \xB7 ${connection.user.display_name || connection.user.username}` : ""}` : connection.status === "expired" && !connection.managedBySdk ? "\u63A5\u5165 Token \u5DF2\u5931\u6548" : busy ? busyLabel : connection.status === "expired" ? "\u91CD\u65B0\u8FDE\u63A5 Blade Agent" : "\u8FDE\u63A5 Blade Agent";
62
+ const handleClick = async () => {
63
+ try {
64
+ const user = await connection.connect();
65
+ onConnected?.(user);
66
+ } catch (error) {
67
+ onError?.(error);
68
+ }
69
+ };
70
+ return /* @__PURE__ */ jsxs(
71
+ "button",
72
+ {
73
+ type: "button",
74
+ className: cn(
75
+ "inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border px-4 py-2 text-sm font-semibold transition-colors",
76
+ connection.status === "connected" ? "border-emerald-200 bg-emerald-50 text-emerald-800" : "border-transparent bg-blue-600 text-white hover:bg-blue-700 disabled:cursor-wait disabled:opacity-70",
77
+ className
78
+ ),
79
+ disabled: busy || connection.status === "connected" || connection.status === "expired" && !connection.managedBySdk,
80
+ onClick: () => void handleClick(),
81
+ children: [
82
+ /* @__PURE__ */ jsx2(
83
+ "span",
84
+ {
85
+ "aria-hidden": "true",
86
+ className: cn(
87
+ "size-2 rounded-full",
88
+ connection.status === "connected" ? "bg-emerald-500" : "bg-current opacity-70",
89
+ busy && "animate-pulse"
90
+ )
91
+ }
92
+ ),
93
+ label
94
+ ]
95
+ }
96
+ );
97
+ }
98
+
99
+ // src/react/components/connection/BladeConnectionPanel.tsx
100
+ import { useEffect as useEffect2, useState } from "react";
101
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
102
+ var STATUS_COPY = {
103
+ checking_service: ["\u6B63\u5728\u68C0\u67E5\u670D\u52A1", "\u6B63\u5728\u786E\u8BA4 Blade Agent \u662F\u5426\u53EF\u7528\u3002"],
104
+ service_unavailable: ["\u65E0\u6CD5\u8FDE\u63A5\u5230 Blade Agent", "\u8BF7\u68C0\u67E5\u670D\u52A1\u5730\u5740\u6216\u7F51\u7EDC\u72B6\u6001\u540E\u91CD\u8BD5\u3002"],
105
+ signed_out: ["\u5C1A\u672A\u8FDE\u63A5", "\u8FDE\u63A5\u540E\u5373\u53EF\u5728\u5F53\u524D\u9875\u9762\u4F7F\u7528 Blade Agent\u3002"],
106
+ waiting_for_authorization: ["\u7B49\u5F85\u6388\u6743", "\u8BF7\u5728\u65B0\u7A97\u53E3\u4E2D\u786E\u8BA4\u8D26\u53F7\u5E76\u5141\u8BB8\u8FDE\u63A5\u3002"],
107
+ connecting: ["\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5", "\u6388\u6743\u5DF2\u5B8C\u6210\uFF0C\u6B63\u5728\u9A8C\u8BC1\u8D26\u53F7\u3002"],
108
+ connected: ["\u5DF2\u8FDE\u63A5", "\u5F53\u524D\u9875\u9762\u5DF2\u7ECF\u53EF\u4EE5\u4F7F\u7528 Blade Agent\u3002"],
109
+ expired: ["\u767B\u5F55\u5DF2\u5931\u6548", "\u8BF7\u91CD\u65B0\u8FDE\u63A5 Blade Agent\u3002"],
110
+ configuration_error: ["\u670D\u52A1\u5730\u5740\u9700\u8981\u8C03\u6574", "\u8BF7\u4FEE\u6539\u5730\u5740\u540E\u91CD\u65B0\u68C0\u6D4B\u3002"]
111
+ };
112
+ function BladeConnectionPanel({
113
+ className,
114
+ onConnected,
115
+ onError
116
+ }) {
117
+ const connection = useBladeConnection();
118
+ const [draftUrl, setDraftUrl] = useState(connection.baseUrl);
119
+ const [inputError, setInputError] = useState(null);
120
+ const [title, description] = STATUS_COPY[connection.status];
121
+ const busy = connection.status === "checking_service" || connection.status === "waiting_for_authorization" || connection.status === "connecting";
122
+ useEffect2(() => setDraftUrl(connection.baseUrl), [connection.baseUrl]);
123
+ const connect = async () => {
124
+ try {
125
+ const user = await connection.connect();
126
+ onConnected?.(user);
127
+ } catch (error) {
128
+ onError?.(error);
129
+ }
130
+ };
131
+ const applyAddress = () => {
132
+ try {
133
+ connection.setBaseUrl(draftUrl);
134
+ setInputError(null);
135
+ } catch (error) {
136
+ setInputError(error instanceof Error ? error.message : "\u8BF7\u8F93\u5165\u6709\u6548\u7684 HTTP(S) \u5730\u5740\u3002");
137
+ }
138
+ };
139
+ return /* @__PURE__ */ jsxs2(
140
+ "section",
141
+ {
142
+ className: cn(
143
+ "w-full max-w-xl rounded-2xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm",
144
+ className
145
+ ),
146
+ children: [
147
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-start gap-3", children: [
148
+ /* @__PURE__ */ jsx3(
149
+ "span",
150
+ {
151
+ "aria-hidden": "true",
152
+ className: cn(
153
+ "mt-1 size-3 shrink-0 rounded-full",
154
+ connection.status === "connected" ? "bg-emerald-500" : connection.status === "service_unavailable" || connection.status === "configuration_error" || connection.status === "expired" ? "bg-amber-500" : "bg-blue-500",
155
+ busy && "animate-pulse"
156
+ )
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
160
+ /* @__PURE__ */ jsx3("h2", { className: "m-0 text-base font-semibold", children: title }),
161
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm leading-6 text-slate-600", children: description }),
162
+ connection.user && /* @__PURE__ */ jsx3("p", { className: "mt-2 text-sm font-medium text-slate-800", children: connection.user.display_name || connection.user.username })
163
+ ] })
164
+ ] }),
165
+ /* @__PURE__ */ jsxs2("div", { className: "mt-5", children: [
166
+ /* @__PURE__ */ jsx3("label", { className: "mb-1.5 block text-sm font-medium", htmlFor: "blade-agent-base-url", children: "Blade Agent \u670D\u52A1\u5730\u5740" }),
167
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
168
+ /* @__PURE__ */ jsx3(
169
+ "input",
170
+ {
171
+ id: "blade-agent-base-url",
172
+ className: "min-h-10 min-w-0 flex-1 rounded-lg border border-slate-300 bg-white px-3 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100",
173
+ inputMode: "url",
174
+ spellCheck: false,
175
+ value: draftUrl,
176
+ onChange: (event) => setDraftUrl(event.target.value)
177
+ }
178
+ ),
179
+ /* @__PURE__ */ jsx3(
180
+ "button",
181
+ {
182
+ type: "button",
183
+ className: "min-h-10 rounded-lg border border-slate-300 px-3 text-sm font-medium hover:bg-slate-50",
184
+ onClick: applyAddress,
185
+ children: "\u68C0\u6D4B"
186
+ }
187
+ )
188
+ ] }),
189
+ inputError && /* @__PURE__ */ jsx3("p", { className: "mt-1.5 text-sm text-red-600", children: inputError })
190
+ ] }),
191
+ connection.error && /* @__PURE__ */ jsx3("div", { className: "mt-4 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900", children: connection.error.message }),
192
+ /* @__PURE__ */ jsxs2("div", { className: "mt-5 flex flex-wrap gap-2", children: [
193
+ connection.status === "connected" && connection.managedBySdk ? /* @__PURE__ */ jsx3(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ className: "min-h-10 rounded-lg border border-slate-300 px-4 text-sm font-semibold hover:bg-slate-50",
198
+ onClick: () => void connection.disconnect(),
199
+ children: "\u65AD\u5F00\u8FDE\u63A5"
200
+ }
201
+ ) : connection.status !== "connected" && connection.managedBySdk ? /* @__PURE__ */ jsx3(
202
+ "button",
203
+ {
204
+ type: "button",
205
+ className: "min-h-10 rounded-lg bg-blue-600 px-4 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-wait disabled:opacity-70",
206
+ disabled: busy,
207
+ onClick: () => void connect(),
208
+ children: connection.status === "waiting_for_authorization" ? "\u7B49\u5F85\u6388\u6743\u2026" : connection.status === "connecting" ? "\u6B63\u5728\u8FDE\u63A5\u2026" : "\u8FDE\u63A5 Blade Agent"
209
+ }
210
+ ) : null,
211
+ (connection.status === "service_unavailable" || connection.status === "expired") && /* @__PURE__ */ jsx3(
212
+ "button",
213
+ {
214
+ type: "button",
215
+ className: "min-h-10 rounded-lg border border-slate-300 px-4 text-sm font-semibold hover:bg-slate-50",
216
+ onClick: () => void connection.retry(),
217
+ children: "\u91CD\u65B0\u68C0\u6D4B"
218
+ }
219
+ )
220
+ ] }),
221
+ /* @__PURE__ */ jsxs2("details", { className: "mt-5 border-t border-slate-200 pt-4 text-sm text-slate-600", children: [
222
+ /* @__PURE__ */ jsx3("summary", { className: "cursor-pointer font-medium text-slate-700", children: "\u6280\u672F\u8BE6\u60C5" }),
223
+ /* @__PURE__ */ jsxs2("dl", { className: "mt-3 grid grid-cols-[auto_1fr] gap-x-3 gap-y-2", children: [
224
+ /* @__PURE__ */ jsx3("dt", { children: "\u670D\u52A1\u5730\u5740" }),
225
+ /* @__PURE__ */ jsx3("dd", { className: "min-w-0 break-all text-slate-900", children: connection.baseUrl }),
226
+ /* @__PURE__ */ jsx3("dt", { children: "\u72B6\u6001" }),
227
+ /* @__PURE__ */ jsx3("dd", { className: "text-slate-900", children: connection.status }),
228
+ /* @__PURE__ */ jsx3("dt", { children: "\u767B\u5F55\u4FDD\u5B58" }),
229
+ /* @__PURE__ */ jsx3("dd", { className: "text-slate-900", children: connection.persistent ? "\u5237\u65B0\u540E\u4FDD\u7559" : "\u4EC5\u5F53\u524D\u9875\u9762" }),
230
+ /* @__PURE__ */ jsx3("dt", { children: "\u51ED\u636E\u7BA1\u7406" }),
231
+ /* @__PURE__ */ jsx3("dd", { className: "text-slate-900", children: connection.managedBySdk ? "\u7531 SDK \u7BA1\u7406" : "\u7531\u63A5\u5165\u65B9\u7BA1\u7406" }),
232
+ connection.error && /* @__PURE__ */ jsxs2(Fragment, { children: [
233
+ /* @__PURE__ */ jsx3("dt", { children: "\u9519\u8BEF\u7C7B\u578B" }),
234
+ /* @__PURE__ */ jsx3("dd", { className: "text-slate-900", children: connection.error.code })
235
+ ] })
236
+ ] })
237
+ ] })
238
+ ]
239
+ }
240
+ );
241
+ }
242
+
243
+ export {
244
+ BladeContext,
245
+ BladeClientProvider,
246
+ useBladeClient,
247
+ useBladeConnection,
248
+ BladeConnectionButton,
249
+ BladeConnectionPanel
250
+ };
251
+ //# sourceMappingURL=chunk-6KO3AOJ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/hooks/use-blade-connection.ts","../src/react/provider.tsx","../src/react/context.ts","../src/react/components/connection/BladeConnectionButton.tsx","../src/react/components/connection/BladeConnectionPanel.tsx"],"sourcesContent":["import { useCallback, useEffect, useSyncExternalStore } from \"react\"\nimport type { BladePopupOptions } from \"../../client\"\nimport { useBladeClient } from \"../provider\"\n\nexport function useBladeConnection() {\n const client = useBladeClient()\n const snapshot = useSyncExternalStore(\n useCallback((listener) => client.auth.subscribe(listener), [client]),\n useCallback(() => client.auth.getConnectionSnapshot(), [client]),\n useCallback(() => client.auth.getConnectionSnapshot(), [client]),\n )\n\n useEffect(() => {\n void client.auth.restore()\n }, [client])\n\n return {\n ...snapshot,\n connect: useCallback(\n (options?: BladePopupOptions) => client.auth.connectWithPopup(options),\n [client],\n ),\n disconnect: useCallback(() => client.auth.disconnect(), [client]),\n retry: useCallback(() => client.auth.retry(), [client]),\n setBaseUrl: useCallback((baseUrl: string) => client.setBaseUrl(baseUrl), [client]),\n }\n}\n\nexport type UseBladeConnectionResult = ReturnType<typeof useBladeConnection>\n","import type { ReactNode } from \"react\"\nimport { useContext } from \"react\"\nimport type { BladeClient } from \"../client\"\nimport { getBootstrappedClient } from \"./bootstrap\"\nimport { BladeContext } from \"./context\"\n\nexport interface BladeClientProviderProps {\n client?: BladeClient\n children: ReactNode\n}\n\nexport function BladeClientProvider({ client, children }: BladeClientProviderProps) {\n const instance = client ?? getBootstrappedClient()\n return <BladeContext.Provider value={instance}>{children}</BladeContext.Provider>\n}\n\nexport function useBladeClient(): BladeClient {\n return useContext(BladeContext) ?? getBootstrappedClient()\n}\n","import { createContext } from \"react\"\nimport type { BladeClient } from \"../client\"\n\nexport const BladeContext = createContext<BladeClient | null>(null)\n","import type { BladeAuthenticatedUser, BladeConnectionError } from \"../../../client\"\nimport { cn } from \"../../lib/utils\"\nimport { useBladeConnection } from \"../../hooks/use-blade-connection\"\n\nexport interface BladeConnectionButtonProps {\n className?: string\n onConnected?: (user: BladeAuthenticatedUser) => void\n onError?: (error: BladeConnectionError) => void\n}\n\nexport function BladeConnectionButton({\n className,\n onConnected,\n onError,\n}: BladeConnectionButtonProps) {\n const connection = useBladeConnection()\n const busy =\n connection.status === \"checking_service\" ||\n connection.status === \"waiting_for_authorization\" ||\n connection.status === \"connecting\"\n const busyLabel =\n connection.status === \"checking_service\"\n ? \"正在检查服务…\"\n : connection.status === \"waiting_for_authorization\"\n ? \"请在新窗口完成授权…\"\n : \"正在建立连接…\"\n const label =\n connection.status === \"connected\"\n ? `已连接${connection.user?.display_name || connection.user?.username ? ` · ${connection.user.display_name || connection.user.username}` : \"\"}`\n : connection.status === \"expired\" && !connection.managedBySdk\n ? \"接入 Token 已失效\"\n : busy\n ? busyLabel\n : connection.status === \"expired\"\n ? \"重新连接 Blade Agent\"\n : \"连接 Blade Agent\"\n\n const handleClick = async () => {\n try {\n const user = await connection.connect()\n onConnected?.(user)\n } catch (error) {\n onError?.(error as BladeConnectionError)\n }\n }\n\n return (\n <button\n type=\"button\"\n className={cn(\n \"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border px-4 py-2 text-sm font-semibold transition-colors\",\n connection.status === \"connected\"\n ? \"border-emerald-200 bg-emerald-50 text-emerald-800\"\n : \"border-transparent bg-blue-600 text-white hover:bg-blue-700 disabled:cursor-wait disabled:opacity-70\",\n className,\n )}\n disabled={\n busy ||\n connection.status === \"connected\" ||\n (connection.status === \"expired\" && !connection.managedBySdk)\n }\n onClick={() => void handleClick()}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n \"size-2 rounded-full\",\n connection.status === \"connected\" ? \"bg-emerald-500\" : \"bg-current opacity-70\",\n busy && \"animate-pulse\",\n )}\n />\n {label}\n </button>\n )\n}\n","import { useEffect, useState } from \"react\"\nimport type { BladeAuthenticatedUser, BladeConnectionError } from \"../../../client\"\nimport { useBladeConnection } from \"../../hooks/use-blade-connection\"\nimport { cn } from \"../../lib/utils\"\n\nexport interface BladeConnectionPanelProps {\n className?: string\n onConnected?: (user: BladeAuthenticatedUser) => void\n onError?: (error: BladeConnectionError) => void\n}\n\nconst STATUS_COPY = {\n checking_service: [\"正在检查服务\", \"正在确认 Blade Agent 是否可用。\"],\n service_unavailable: [\"无法连接到 Blade Agent\", \"请检查服务地址或网络状态后重试。\"],\n signed_out: [\"尚未连接\", \"连接后即可在当前页面使用 Blade Agent。\"],\n waiting_for_authorization: [\"等待授权\", \"请在新窗口中确认账号并允许连接。\"],\n connecting: [\"正在建立连接\", \"授权已完成,正在验证账号。\"],\n connected: [\"已连接\", \"当前页面已经可以使用 Blade Agent。\"],\n expired: [\"登录已失效\", \"请重新连接 Blade Agent。\"],\n configuration_error: [\"服务地址需要调整\", \"请修改地址后重新检测。\"],\n} as const\n\nexport function BladeConnectionPanel({\n className,\n onConnected,\n onError,\n}: BladeConnectionPanelProps) {\n const connection = useBladeConnection()\n const [draftUrl, setDraftUrl] = useState(connection.baseUrl)\n const [inputError, setInputError] = useState<string | null>(null)\n const [title, description] = STATUS_COPY[connection.status]\n const busy =\n connection.status === \"checking_service\" ||\n connection.status === \"waiting_for_authorization\" ||\n connection.status === \"connecting\"\n\n useEffect(() => setDraftUrl(connection.baseUrl), [connection.baseUrl])\n\n const connect = async () => {\n try {\n const user = await connection.connect()\n onConnected?.(user)\n } catch (error) {\n onError?.(error as BladeConnectionError)\n }\n }\n\n const applyAddress = () => {\n try {\n connection.setBaseUrl(draftUrl)\n setInputError(null)\n } catch (error) {\n setInputError(error instanceof Error ? error.message : \"请输入有效的 HTTP(S) 地址。\")\n }\n }\n\n return (\n <section\n className={cn(\n \"w-full max-w-xl rounded-2xl border border-slate-200 bg-white p-5 text-slate-900 shadow-sm\",\n className,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <span\n aria-hidden=\"true\"\n className={cn(\n \"mt-1 size-3 shrink-0 rounded-full\",\n connection.status === \"connected\"\n ? \"bg-emerald-500\"\n : connection.status === \"service_unavailable\" ||\n connection.status === \"configuration_error\" ||\n connection.status === \"expired\"\n ? \"bg-amber-500\"\n : \"bg-blue-500\",\n busy && \"animate-pulse\",\n )}\n />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"m-0 text-base font-semibold\">{title}</h2>\n <p className=\"mt-1 text-sm leading-6 text-slate-600\">{description}</p>\n {connection.user && (\n <p className=\"mt-2 text-sm font-medium text-slate-800\">\n {connection.user.display_name || connection.user.username}\n </p>\n )}\n </div>\n </div>\n\n <div className=\"mt-5\">\n <label className=\"mb-1.5 block text-sm font-medium\" htmlFor=\"blade-agent-base-url\">\n Blade Agent 服务地址\n </label>\n <div className=\"flex gap-2\">\n <input\n id=\"blade-agent-base-url\"\n className=\"min-h-10 min-w-0 flex-1 rounded-lg border border-slate-300 bg-white px-3 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100\"\n inputMode=\"url\"\n spellCheck={false}\n value={draftUrl}\n onChange={(event) => setDraftUrl(event.target.value)}\n />\n <button\n type=\"button\"\n className=\"min-h-10 rounded-lg border border-slate-300 px-3 text-sm font-medium hover:bg-slate-50\"\n onClick={applyAddress}\n >\n 检测\n </button>\n </div>\n {inputError && <p className=\"mt-1.5 text-sm text-red-600\">{inputError}</p>}\n </div>\n\n {connection.error && (\n <div className=\"mt-4 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900\">\n {connection.error.message}\n </div>\n )}\n\n <div className=\"mt-5 flex flex-wrap gap-2\">\n {connection.status === \"connected\" && connection.managedBySdk ? (\n <button\n type=\"button\"\n className=\"min-h-10 rounded-lg border border-slate-300 px-4 text-sm font-semibold hover:bg-slate-50\"\n onClick={() => void connection.disconnect()}\n >\n 断开连接\n </button>\n ) : connection.status !== \"connected\" && connection.managedBySdk ? (\n <button\n type=\"button\"\n className=\"min-h-10 rounded-lg bg-blue-600 px-4 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-wait disabled:opacity-70\"\n disabled={busy}\n onClick={() => void connect()}\n >\n {connection.status === \"waiting_for_authorization\"\n ? \"等待授权…\"\n : connection.status === \"connecting\"\n ? \"正在连接…\"\n : \"连接 Blade Agent\"}\n </button>\n ) : null}\n {(connection.status === \"service_unavailable\" || connection.status === \"expired\") && (\n <button\n type=\"button\"\n className=\"min-h-10 rounded-lg border border-slate-300 px-4 text-sm font-semibold hover:bg-slate-50\"\n onClick={() => void connection.retry()}\n >\n 重新检测\n </button>\n )}\n </div>\n\n <details className=\"mt-5 border-t border-slate-200 pt-4 text-sm text-slate-600\">\n <summary className=\"cursor-pointer font-medium text-slate-700\">技术详情</summary>\n <dl className=\"mt-3 grid grid-cols-[auto_1fr] gap-x-3 gap-y-2\">\n <dt>服务地址</dt>\n <dd className=\"min-w-0 break-all text-slate-900\">{connection.baseUrl}</dd>\n <dt>状态</dt>\n <dd className=\"text-slate-900\">{connection.status}</dd>\n <dt>登录保存</dt>\n <dd className=\"text-slate-900\">{connection.persistent ? \"刷新后保留\" : \"仅当前页面\"}</dd>\n <dt>凭据管理</dt>\n <dd className=\"text-slate-900\">\n {connection.managedBySdk ? \"由 SDK 管理\" : \"由接入方管理\"}\n </dd>\n {connection.error && (\n <>\n <dt>错误类型</dt>\n <dd className=\"text-slate-900\">{connection.error.code}</dd>\n </>\n )}\n </dl>\n </details>\n </section>\n )\n}\n"],"mappings":"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","names":["jsx","useEffect","jsx","jsxs","useEffect"]}