@tbox.cn/app-sdk-client 0.21.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tbox.cn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @tbox.cn/app-sdk-client
2
+
3
+ tbox AI Agent 平台 SDK client 轴:能力适配器(asr / tts / upload / feedback 均走同源 server 代理)、
4
+ chat 引擎、状态域(zustand)、桥协议 v2、WS 客户端(keepalive / 重连护栏 / 鉴权守卫)、卡片渲染与 dev 预览页。
5
+
6
+ 真实外部依赖仅 react + react-dom + zustand(+ core/contracts)——client 树零 Node 内建、零 server 重依赖
7
+ (2026-09 三包拆分;浏览器直连 o.tbox.cn 的能力已全部收口为同源代理)。
8
+
9
+ 公开入口:`.` / `./client` / `./client/card-preview` / `./platform/code-inspect`(vite 页面微调 babel plugin)。
@@ -0,0 +1,459 @@
1
+ import { TboxLogger } from '@tbox.cn/app-sdk-core';
2
+ export { InferModuleCards, LogContext, LogLevel, TboxLogger, createLogger, detailForLog, sanitizeForLog, truncateForLog } from '@tbox.cn/app-sdk-core';
3
+ import * as React from 'react';
4
+ import React__default, { ReactNode, CSSProperties, Component } from 'react';
5
+ import { TboxCardPayload, CardComponent, UiAction, ClientSlotId, ClientSlotValueMap } from '@tbox.cn/app-contracts';
6
+
7
+ /**
8
+ * 客户端日志壳:client run 上下文(req/conv 尾缀)+ 全局错误兜底 + transport 扩展点(上报预留)。
9
+ *
10
+ * - 级别探测:localStorage['tbox:logLevel'] → ?logLevel= → info(typeof 守卫,SSR/node 测试环境安全);
11
+ * - setClientRunContext:chat-store 在 RUN_STARTED/RUN_FINISHED 更新(runId → req= 尾缀);
12
+ * - installGlobalErrorReporting:window.onerror + unhandledrejection → logger(10s 窗口去重;
13
+ * 返回卸载函数;非浏览器环境 no-op);
14
+ * - ClientLogTransport:上报扩展点(默认 no-op——WS 上报协议为独立演进方向,此处只留接口)。
15
+ */
16
+
17
+ /** run 上下文更新(chat-store RUN_STARTED/RUN_FINISHED 消费;null = 清除) */
18
+ declare function setClientRunContext(ctx: {
19
+ conversationId?: string;
20
+ runId?: string;
21
+ } | null): void;
22
+ declare function createClientLogger(tag: string): TboxLogger;
23
+ /** 上报扩展点(P3 预留:默认 no-op,接入 WS 上报时替换实现) */
24
+ interface ClientLogTransport {
25
+ send(level: string, line: string): void;
26
+ }
27
+ declare function setClientLogTransport(t: ClientLogTransport): void;
28
+ /** 全局错误兜底:未捕获异常/rejection → logger.error + transport;返回卸载函数 */
29
+ declare function installGlobalErrorReporting(logger?: TboxLogger): () => void;
30
+
31
+ declare function CardRenderer({ card, registry, onAction, }: {
32
+ card: TboxCardPayload;
33
+ registry: Record<string, CardComponent>;
34
+ /** 动作按钮点击回调(应用层注入 sendUiAction 桥:onAction={(a) => sendUiAction(card.id, a)};
35
+ * 缺省仅展示不可点) */
36
+ onAction?: (action: UiAction) => void;
37
+ }): React.JSX.Element;
38
+
39
+ /**
40
+ * 多卡纵向铺开(决策出卡多实例展示):气泡卡片区自上而下顺序排布。
41
+ * 单卡直出(零 chrome);多卡纵向 flex 铺开,间距单一来源(宿主 .tbox-card 底部间距)。
42
+ * 基元模式 inline style(对齐 CardFrame 先例,不引 CSS 文件);
43
+ * 输入即已剥离标签的卡片数据,组件不渲染任何协议痕迹。
44
+ */
45
+
46
+ declare function CardCarousel({ cards, renderItem, className, }: {
47
+ cards: TboxCardPayload[];
48
+ /** 单卡渲染委托(模板传 CardRenderer 包装;key 由本组件 wrapper 持有) */
49
+ renderItem: (card: TboxCardPayload) => ReactNode;
50
+ className?: string;
51
+ }): JSX.Element;
52
+
53
+ /**
54
+ * 卡片渲染基元(011 J5:封顶 5 件——新增须走提案)。
55
+ * 模块卡片组件唯一可消费的 SDK 视觉基元:仅依赖 `--tbox-*` CSS 变量 + antd-mobile,
56
+ * 零业务样式。token 未定义时回退内置默认值(本地预览可用)。
57
+ */
58
+
59
+ /**
60
+ * 自渲染动作标记(0.3.2 D10):置位的卡片组件在 CardRenderer 内跳过默认动作按钮条
61
+ * (卡内自持写操作按钮,经 sendCardAction(moduleAction) 直连)。与未置位卡并存,
62
+ * 旧 SDK 无消费方 → 回落现状双渲染,不 break。
63
+ */
64
+ type CardComponentWithFlag = CardComponent & {
65
+ /** 置位后 CardRenderer 不渲染 payload.actions 按钮条(卡内自行处理动作) */
66
+ selfRendersActions?: boolean;
67
+ };
68
+ /**
69
+ * 标记卡片为自渲染动作(富卡范式:卡内写按钮 + 交互状态,禁用 SDK 默认按钮条)。
70
+ * 泛型不限 CardComponent:富卡常带精确 data 类型,与 CardComponent<unknown> 不协变;
71
+ * 标记仅运行时附加静态标志,消费侧注册处仍按 CardComponent 使用。
72
+ */
73
+ declare function markSelfRendering<T>(component: T): T & CardComponentWithFlag;
74
+ /** 卡片容器:标题 + 副标题 + 内容槽(模块卡片的统一外框) */
75
+ declare function CardFrame(props: {
76
+ title?: string;
77
+ subtitle?: string;
78
+ children: ReactNode;
79
+ style?: CSSProperties;
80
+ }): React__default.JSX.Element;
81
+ /** 键值文本行:label 左、value 右(模块卡片的通用信息行) */
82
+ declare function ValueText(props: {
83
+ label: string;
84
+ value: string;
85
+ valueStyle?: CSSProperties;
86
+ }): React__default.JSX.Element;
87
+ type StatusPillTone = 'success' | 'warning' | 'neutral';
88
+ /** 状态徽标:成功/警示/中性(模块卡片的状态展示) */
89
+ declare function StatusPill(props: {
90
+ tone: StatusPillTone;
91
+ text: string;
92
+ }): React__default.JSX.Element;
93
+
94
+ /** 错误边界:子树渲染抛错时显示 fallback,不让单点错误炸掉整页/整条消息。 */
95
+ declare class ErrorBoundary extends Component<{
96
+ fallback: ReactNode;
97
+ children: ReactNode;
98
+ }, {
99
+ hasError: boolean;
100
+ }> {
101
+ state: {
102
+ hasError: boolean;
103
+ };
104
+ static getDerivedStateFromError(): {
105
+ hasError: boolean;
106
+ };
107
+ componentDidCatch(error: unknown): void;
108
+ render(): ReactNode;
109
+ }
110
+
111
+ /**
112
+ * 支付宝 web-view 桥协议常量 / 类型。
113
+ *
114
+ * 必须与壳子 `miniapp-container-alipay/pages/ai-agent/bridge.ts` 对齐:
115
+ * H5 → 小程序:my.postMessage({ bridge, action, apiName, params, requestId })
116
+ * 小程序 → H5:webViewContext.postMessage(receipt) → H5 my.onMessage 收
117
+ *
118
+ * 命名空间契约:action = apiName 的解析命名空间——
119
+ * 'my' / 'my.call' = 支付宝平台 API(壳通用代理透传);
120
+ * 'tbox' = 壳注册能力(容器自带裸名 apiName / 应用自实现 app.* 前缀)。
121
+ * 点分 'tbox.*' 值保留给未来协议级操作(握手/版本协商),勿用作能力命名空间。
122
+ */
123
+ declare const BRIDGE_NAME = "open-mini-webview-bridge";
124
+ declare const BRIDGE_VERSION = "1.0.0";
125
+ declare const MY_ACTION = "my";
126
+ declare const MY_CALL_ACTION = "my.call";
127
+ /** tbox 能力命名空间:apiName 在壳内注册表解析(容器自带裸名 / 应用自实现 app.* 前缀) */
128
+ declare const TBOX_ACTION = "tbox";
129
+ /** JSAPI 调用模式:invoke = my.xxx;call = my.call(apiName, params, cb) */
130
+ type JsapiMode = 'invoke' | 'call';
131
+ /** H5 发给小程序的请求消息(bridge 标记必带——缺标记消息壳不执行) */
132
+ interface BridgeRequest {
133
+ bridge: typeof BRIDGE_NAME;
134
+ action: typeof MY_ACTION | typeof MY_CALL_ACTION | typeof TBOX_ACTION;
135
+ apiName: string;
136
+ params: Record<string, unknown>;
137
+ requestId: string;
138
+ }
139
+ /** 小程序回执(壳子 buildMiniProgramReceipt 产出) */
140
+ interface BridgeReceipt {
141
+ bridge: typeof BRIDGE_NAME;
142
+ version: string;
143
+ sendToWebView: '1';
144
+ action: string;
145
+ requestId: string;
146
+ success: boolean;
147
+ code: string;
148
+ data?: unknown;
149
+ errorMessage: string;
150
+ received?: unknown;
151
+ }
152
+ declare function isBridgeReceipt(msg: unknown): msg is BridgeReceipt;
153
+
154
+ /**
155
+ * 调一个平台 JSAPI,Promise 化。
156
+ * @param apiName 如 'getLocation' / 'chooseImage' / 'getAuthCode'
157
+ * @param params JSAPI 入参
158
+ * @param mode 'invoke'(默认,my.xxx)| 'call'(my.call)
159
+ */
160
+ declare function callJsapi<T = unknown>(apiName: string, params?: Record<string, unknown>, mode?: JsapiMode, timeoutMs?: number): Promise<T>;
161
+ /**
162
+ * 调一个 tbox 命名空间能力(壳注册表内实现,无 mode 维度)。
163
+ *
164
+ * 与 callJsapi 的辨析:平台 my.xxx API 用 callJsapi;壳注册能力(容器自带如
165
+ * 'openPlateKeyboard',应用自实现约定 app.* 前缀)用本函数。支付宝标准
166
+ * my JSAPI(包括 startRecord/stopRecord)应使用 callJsapi。
167
+ */
168
+ declare function callCapability<T = unknown>(apiName: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
169
+
170
+ /**
171
+ * 常用桥 JSAPI 便捷封装(供业务/适配层调用)。
172
+ * 仅在支付宝壳环境可用;浏览器环境 callJsapi / callCapability 均会抛 BRIDGE_UNAVAILABLE。
173
+ * 业务新能力(扫码/导航/分享等)经 callJsapi 透传平台 API(壳子可能要补 JSAPI 白名单);
174
+ * 壳实现/应用自实现能力(app.* 前缀)经 callCapability(bridge-client)走 tbox 命名空间。
175
+ */
176
+
177
+ /** 壳子 chooseImage/chooseVideo 把原生结果转成的 base64 dataURL 文件项 */
178
+ interface BridgeMediaFile {
179
+ data: string;
180
+ name?: string;
181
+ size?: number;
182
+ }
183
+ /** sourceType: ['album'|'camera'];不传由壳子默认(相册+相机)。超时用默认 5min(含授权与壳侧文件读取)。 */
184
+ declare function chooseImage(count?: number, sourceType?: ('album' | 'camera')[]): Promise<{
185
+ files: BridgeMediaFile[];
186
+ }>;
187
+ declare function chooseVideo(): Promise<{
188
+ files: BridgeMediaFile[];
189
+ }>;
190
+ /** 定位(壳子已把 getLocation 列入 invoke 白名单)。
191
+ * 支付宝小程序 getLocation 回传经纬度为字符串,统一转成 number 再返回。
192
+ */
193
+ declare function getLocation(): Promise<{
194
+ longitude: number;
195
+ latitude: number;
196
+ [k: string]: unknown;
197
+ }>;
198
+ /**
199
+ * 换登录 authCode:走壳子通用代理直调 my.getAuthCode,壳子无需特殊处理。
200
+ * scopes 默认 ['auth_base']:静默拿用户标识、不弹窗(拿头像昵称才用 auth_user)。
201
+ * 返回 { authCode },交给 server /api/auth/login 换 userId。
202
+ */
203
+ declare function getAuthCode(scopes?: string[]): Promise<{
204
+ authCode: string;
205
+ }>;
206
+ /**
207
+ * 收银台支付拉起(015 P4:my.tradePay,orderInfo = 厂商支付凭证不透明透传)。
208
+ * 双参数透传:tradeNO(交易号语义,joycity GetPaySign d.sn 审计确认)+ orderStr(签名串语义,
209
+ * 其他厂商兼容)——my.tradePay 按 tradeNO 优先消费。 resultCode:9000 成功 / 6001 取消 /
210
+ * 8000·4000 等失败——状态映射在 host-effect-runner。浏览器环境抛 BRIDGE_UNAVAILABLE。
211
+ */
212
+ declare function requestPayment(orderInfo: string): Promise<{
213
+ resultCode?: string;
214
+ }>;
215
+ /** Parse a complete支付宝 startapp link and invoke the platform navigateTo JSAPI through the bridge. */
216
+ declare function navigateToAlipayPage(url: string): Promise<unknown>;
217
+ /** 会员登录门禁回执:H5 只消费门控信号(loginStatus + message)。
218
+ * 回执即使携带 token 等凭证字段也不采信——外部凭证唯一合法入口 = 服务端换码器。 */
219
+ interface LoginGateReceipt {
220
+ /** success=gate 完成登录(含开卡);cancel=用户拒绝(仅浏览);fail=gate 内部失败 */
221
+ loginStatus: 'success' | 'fail' | 'cancel';
222
+ message?: string;
223
+ }
224
+ /**
225
+ * 会员登录门禁(tbox 命名空间能力,壳按名分发):挂起调起商场侧登录/开卡页
226
+ * (协议上登录与开卡一体——GUEST 在壳侧即未登录,会开 gate),用户完成/取消后回执。
227
+ * 未注册该能力的壳(含浏览器)reject BRIDGE_UNAVAILABLE,由调用方按卡型降级
228
+ * (openLink 深链兜底)。默认超时 5min(对齐媒体级长挂起:gate 页停留期间 web-view 冻结)。
229
+ */
230
+ declare function getLoginState(params?: Record<string, unknown>, timeoutMs?: number): Promise<LoginGateReceipt>;
231
+ interface PlateKeyboardOptions {
232
+ value?: string;
233
+ newEnergy?: boolean;
234
+ }
235
+ interface PlateKeyboardResult {
236
+ plateNo: string;
237
+ valid: boolean;
238
+ newEnergy: boolean;
239
+ }
240
+ /**
241
+ * 唤起壳内原生车牌键盘(tbox 命名空间能力,壳按名分发);用户确认后返回车牌号,
242
+ * 取消时 reject USER_CANCEL。浏览器环境会按普通桥能力返回 BRIDGE_UNAVAILABLE,
243
+ * 由业务决定是否降级为文本输入。
244
+ */
245
+ declare function openPlateKeyboard(options?: PlateKeyboardOptions): Promise<PlateKeyboardResult>;
246
+ declare function closePlateKeyboard(): Promise<{
247
+ closed: boolean;
248
+ }>;
249
+
250
+ /** 内联流过滤器(域注册;复合序 = 注册序) */
251
+ type InlineCardFilter = (cards: readonly TboxCardPayload[]) => TboxCardPayload[];
252
+ /**
253
+ * ClientCardRegistry:客户端卡片组件注册表(cardType → CardComponent,无状态)。
254
+ * 模块在 clientModule.register(ctx) 内 ctx.cards.registerMap(map) 注册组件。
255
+ * 与 server 端 CardRegistry 对称:server 注册数据契约(CardMeta),client 注册渲染组件。
256
+ *
257
+ * 三通道(单一模板统一化 W3-2):
258
+ * - 原始注册(registerMap):模块自有卡组件(RawCardRenderer 消费面);
259
+ * - 覆盖注册(registerOverride):聊天视图组合层覆盖(同 cardType 包装/替换——原始视图不受影响,
260
+ * 域模块包自持防嵌套语义,如 mall-home 包装 alipay-activity 的联单卡);
261
+ * - 内联过滤(registerInlineFilter):域声明哪些卡不进内联流(由域浮层自持;多域按注册序复合)。
262
+ * 应用组装端读取:`{...ctx.cards.getAll(), ...ctx.cards.getOverrides()}` 为聊天视图注册表;
263
+ * `ctx.cards.applyInlineFilters` 为 ChatPage filterInlineCards 注入值。
264
+ */
265
+ declare class ClientCardRegistry {
266
+ private cards;
267
+ private overrides;
268
+ private inlineFilters;
269
+ register(cardType: string, component: CardComponent): void;
270
+ /**
271
+ * 按 map 批量注册(模块 clientModule.cards 形态;key 即 cardType)。
272
+ * 幂等:重复注册同一 cardType 告警并覆盖(组装端多次 registerAll 安全)。
273
+ */
274
+ registerMap(map: Record<string, CardComponent>): void;
275
+ /**
276
+ * 聊天视图覆盖注册(组合层):同 cardType 覆盖原始注册在聊天流的渲染;
277
+ * 原始注册不受影响(RawCardRenderer/详情弹层仍取原始形态)。
278
+ * 幂等:重复覆盖告警并覆盖(组装端多次 registerAll 安全)。
279
+ */
280
+ registerOverride(cardType: string, component: CardComponent): void;
281
+ getOverrides(): Record<string, CardComponent>;
282
+ /** 内联流过滤注册(域浮层自持卡的剥离声明;复合序 = 注册序)。 */
283
+ registerInlineFilter(filter: InlineCardFilter): void;
284
+ /** 内联流过滤复合(无注册 → 恒等返回原数组引用)。 */
285
+ applyInlineFilters(cards: readonly TboxCardPayload[]): TboxCardPayload[];
286
+ resolve(cardType: string): CardComponent | undefined;
287
+ getAll(): Record<string, CardComponent>;
288
+ has(cardType: string): boolean;
289
+ get size(): number;
290
+ }
291
+ /**
292
+ * ClientSlotRegistry:客户端槽位注册表(slotId → 槽值,无状态)。
293
+ * 模块在 clientModule.register(ctx) 内注册(如 mall-home `ctx.slots.register('home', render)`);
294
+ * kit 壳按槽存在性派生应用形态(home 注册 = home-first 主屏;缺席 = chat 流)。
295
+ * 槽 id 词汇 = TIER0 contracts KNOWN_CLIENT_SLOT_IDS(封闭集);未知 id =
296
+ * 版本偏斜信号:warn + 照常注册(前向兼容——老壳消费新模块不炸,resolve 侧自然 miss 降级)。
297
+ */
298
+ declare class ClientSlotRegistry {
299
+ private slots;
300
+ register<K extends ClientSlotId>(slotId: K, value: ClientSlotValueMap[K]): void;
301
+ resolve<K extends ClientSlotId>(slotId: K): ClientSlotValueMap[K] | undefined;
302
+ has(slotId: ClientSlotId): boolean;
303
+ get size(): number;
304
+ }
305
+ /**
306
+ * ClientContext:模块 clientModule.register(ctx) 的上下文(组装端 createClientContext() 创建一次)。
307
+ * cards = 卡片组件注册表;slots = 壳渲染槽注册表(单一模板统一化 W1-②:应用形态派生位)。
308
+ */
309
+ interface ClientContext {
310
+ /** 卡片组件注册表(cardType → CardComponent) */
311
+ cards: ClientCardRegistry;
312
+ /** 壳渲染槽注册表(槽 id 契约 = KNOWN_CLIENT_SLOT_IDS) */
313
+ slots: ClientSlotRegistry;
314
+ }
315
+ /**
316
+ * 模块描述对象(客户端):模块包 client 入口 export 的 clientModule。
317
+ *
318
+ * - cards:卡片组件声明 map(key 约定 = cardType;数据 + 类型源,
319
+ * 供 `InferModuleCards` 从装配数组自动推导 app 端 CardType)。
320
+ * - register(ctx):注册期自注册(一期仅 ctx.cards.registerMap(cards))。
321
+ *
322
+ * 与服务端 `ServerModule` 逐行对称;组装端遍历 modules 调 register(ctx)。
323
+ * register 为函数签名声明:method shorthand 经 `satisfies` 检查(TS 方法 bivariance——
324
+ * 放行比契约更窄的参数,运行时无危害;缺字段的过宽参数会被拦截)。
325
+ * cards 为必填属性:无卡片模块写 `cards: {}`(keyof = never,不污染装配类型)。
326
+ */
327
+ interface ClientModule<C extends Record<string, CardComponent> = Record<string, CardComponent>> {
328
+ cards: C;
329
+ register: (ctx: ClientContext) => void | Promise<void>;
330
+ }
331
+ /**
332
+ * 创建客户端上下文(组装端调用一次,apps/client 模块加载期)。
333
+ */
334
+ declare function createClientContext(): ClientContext;
335
+
336
+ /**
337
+ * 运行环境识别 + API/WS 地址推导 + 启动参数读取(client 平台轴)。
338
+ *
339
+ * 本 SDK 同时跑在两种环境(都是 web):
340
+ * 1. 浏览器 H5(直接访问)
341
+ * 2. 支付宝小程序 web-view 壳(agent-chat-h5-template-alipay)里的 H5
342
+ * —— 原生能力走 postMessage 桥(见 ./bridge)。
343
+ *
344
+ * H1:SDK **零 Vite 依赖**——不读取 import.meta.env;VITE_API_URL 等环境值由组装端
345
+ * (模板 main.tsx)经 configureRuntime({ apiBaseUrl }) 注入。
346
+ */
347
+ type RuntimeContainer = 'browser' | 'alipay-miniapp';
348
+ /**
349
+ * 运行时配置注入(组装端唯一入口,模板 main.tsx 调用一次)。
350
+ * apiBaseUrl:HTTP API base。空串表示相对路径(同源,前后端同源部署默认形态)。
351
+ */
352
+ declare function configureRuntime(opts: {
353
+ apiBaseUrl?: string;
354
+ }): void;
355
+ /** HTTP API base。空串表示相对路径(同源)。 */
356
+ declare function getApiBaseUrl(): string;
357
+ /** 最终 HTTP 源(用于显示/拼接)。 */
358
+ declare function getApiOrigin(): string;
359
+ /** WebSocket 地址:从 API origin 推导 http→ws。 */
360
+ declare function getWsUrl(path?: string): string;
361
+ /** 读取 URL query(壳子会把 agent_id/user_id/channel/code/env 等拼到 index.html?... 上) */
362
+ declare function getRuntimeQuery(): Record<string, string>;
363
+ declare function getQueryParam(key: string): string | undefined;
364
+ /**
365
+ * 是否运行在支付宝小程序 web-view 壳里。
366
+ *
367
+ * 识别策略(按可靠性排序):
368
+ * 1. 壳子在 web-view URL 上加的确定性标记 `?container=alipay-miniapp`(最可靠,推荐壳子配合)
369
+ * 2. UA 含 AlipayClient / AliApp(AP/...)(支付宝客户端 web-view 兜底)
370
+ * 任一命中即判定为支付宝壳环境。
371
+ */
372
+ declare function isInAlipayShell(): boolean;
373
+ /** 支付宝 web-view 桥通信能力是否已经可用。 */
374
+ declare function hasAlipayBridge(): boolean;
375
+ declare function getContainer(): RuntimeContainer;
376
+
377
+ type AlipayNavigation = {
378
+ apiName: 'navigateTo';
379
+ params: {
380
+ url: string;
381
+ };
382
+ } | {
383
+ apiName: 'navigateToMiniProgram';
384
+ params: {
385
+ appId: string;
386
+ path: string;
387
+ query?: string;
388
+ };
389
+ };
390
+ /**
391
+ * Resolve an Alipay startapp deep link to the native mini-program navigation API.
392
+ * The host app id is required because same-app pages must use navigateTo.
393
+ * 基于 `parseAlipayScheme`(不经 `new URL`),规避安卓/UC 内核对 `alipays://` 非特殊 scheme 的解析差异。
394
+ */
395
+ declare function resolveAlipayNavigation(url: string, hostMiniAppId?: string): AlipayNavigation | undefined;
396
+
397
+ interface RequestOptions {
398
+ /** 追加/覆盖请求头(如鉴权 token) */
399
+ headers?: Record<string, string>;
400
+ /** 取消信号 */
401
+ signal?: AbortSignal;
402
+ /** fetch 缓存模式透传('no-store' = 绕过 HTTP 缓存读写,含存量条目) */
403
+ cache?: RequestCache;
404
+ }
405
+ interface HttpResult<T> {
406
+ status: number;
407
+ data: T;
408
+ }
409
+ interface ServiceClient {
410
+ get<T = any>(path: string, opts?: RequestOptions): Promise<HttpResult<T>>;
411
+ post<T = any>(path: string, body?: unknown, opts?: RequestOptions): Promise<HttpResult<T>>;
412
+ /** 二进制上传:Blob body 不 JSON 化、不注入 application/json(Content-Type 由调用方头承载) */
413
+ postBinary<T = any>(path: string, body: Blob, opts?: RequestOptions): Promise<HttpResult<T>>;
414
+ }
415
+ interface CreateServiceOptions {
416
+ /** 是否注入同源鉴权 token + 401 自动重登(默认 false;仅 defaultService 显式开启) */
417
+ auth?: boolean;
418
+ /** base 动态解析(缺省 false):请求时调 getApiBaseUrl()——defaultService 专用(H1:configureRuntime 注入后生效,任意时机重配均跟随) */
419
+ dynamicBase?: boolean;
420
+ }
421
+ declare function configureAuthToken(fn: () => string | null): void;
422
+ /** 401 处理器(auth-client 注册 refreshAuth;返回后调用方用新 token 重试一次) */
423
+ type AuthErrorHandler = () => Promise<unknown>;
424
+ declare function configureAuthErrorHandler(fn: AuthErrorHandler): void;
425
+ /**
426
+ * 创建绑定到指定域名的请求客户端。
427
+ *
428
+ * @param baseUrl 目标域名(如 `https://biz.example.com`)。空串=同源(相对路径)。
429
+ * @param defaults 该 service 所有请求共用的默认项(如固定鉴权头)。
430
+ * @param opts.auth 是否注入本地 token + 401 重登(仅同源/受信域名设为 true)。
431
+ */
432
+ declare function createService(baseUrl?: string, defaults?: RequestOptions, opts?: CreateServiceOptions): ServiceClient;
433
+ /** 默认同源客户端:打本 server 的 `/api/*`。
434
+ * dynamicBase(H1 时序安全):base 每次请求经 getApiBaseUrl() 动态解析——
435
+ * configureRuntime 在模块加载后执行且可重配,base 不得固化于模块加载时。 */
436
+ declare const defaultService: ServiceClient;
437
+ /** 向后兼容导出:等价于 defaultService.get / .post / .postBinary。 */
438
+ declare const httpGet: <T = any>(path: string, opts?: RequestOptions) => Promise<HttpResult<T>>;
439
+ declare const httpPost: <T = any>(path: string, body?: unknown, opts?: RequestOptions) => Promise<HttpResult<T>>;
440
+ declare const httpPostBinary: <T = any>(path: string, body: Blob, opts?: RequestOptions) => Promise<HttpResult<T>>;
441
+
442
+ declare global {
443
+ interface Window {
444
+ my?: AlipayWebViewMy;
445
+ AlipayJSBridge?: unknown;
446
+ }
447
+ }
448
+ /** 支付宝 web-view JSSDK 暴露的 my(仅用到的子集) */
449
+ interface AlipayWebViewMy {
450
+ postMessage: (data: Record<string, unknown>) => void;
451
+ onMessage?: (e: unknown) => void;
452
+ }
453
+ /**
454
+ * 确保 JSSDK 就绪,resolve 出 `my`(浏览器环境 resolve null)。
455
+ * 幂等:多次调用复用同一个 Promise。
456
+ */
457
+ declare function ensureAlipayJSSDK(): Promise<AlipayWebViewMy | null>;
458
+
459
+ export { type AlipayNavigation, type AlipayWebViewMy, BRIDGE_NAME, BRIDGE_VERSION, type BridgeMediaFile, type BridgeReceipt, type BridgeRequest, CardCarousel, type CardComponentWithFlag, CardFrame, CardRenderer, ClientCardRegistry, type ClientContext, type ClientLogTransport, type ClientModule, ClientSlotRegistry, type CreateServiceOptions, ErrorBoundary, type HttpResult, type JsapiMode, type LoginGateReceipt, MY_ACTION, MY_CALL_ACTION, type PlateKeyboardOptions, type PlateKeyboardResult, type RequestOptions, type RuntimeContainer, type ServiceClient, StatusPill, type StatusPillTone, TBOX_ACTION, ValueText, callCapability, callJsapi, chooseImage, chooseVideo, closePlateKeyboard, configureAuthErrorHandler, configureAuthToken, configureRuntime, createClientContext, createClientLogger, createService, defaultService, ensureAlipayJSSDK, getApiBaseUrl, getApiOrigin, getAuthCode, getContainer, getLocation, getLoginState, getQueryParam, getRuntimeQuery, getWsUrl, hasAlipayBridge, httpGet, httpPost, httpPostBinary, installGlobalErrorReporting, isBridgeReceipt, isInAlipayShell, markSelfRendering, navigateToAlipayPage, openPlateKeyboard, requestPayment, resolveAlipayNavigation, setClientLogTransport, setClientRunContext };
@@ -0,0 +1 @@
1
+ import{createLogger as Vt}from"@tbox.cn/app-sdk-core";import{createCoreLogger as be,resolveLevel as j,setLogContextProvider as Ce}from"@tbox.cn/app-sdk-core";var M=null;Ce(()=>M??void 0);function he(e){M=e?{requestId:e.runId,conversationId:e.conversationId}:null}function ve(){try{if(typeof localStorage<"u")return j(localStorage.getItem("tbox:logLevel")??void 0,"info")}catch{}try{if(typeof location<"u"){let e=new URLSearchParams(location.search).get("logLevel");if(e)return j(e,"info")}}catch{}return"info"}function u(e){return be({tag:e,getLevel:ve,getContext:()=>M??void 0})}var X;function we(e){X=e}function Re(e){if(typeof window>"u"||typeof window.addEventListener!="function")return()=>{};let t=e??u("GLOBAL"),r=new Map,n=1e4,l=a=>{let o=Date.now();for(let[g,s]of r)o-s>=n&&r.delete(g);r.has(a)||(r.set(a,o),t.error(a),X?.send("error",a))},i=a=>{l(`uncaught: ${a.message} @ ${a.filename}:${a.lineno}`)},d=a=>{let o=a.reason instanceof Error?a.reason.message:String(a.reason);l(`unhandledrejection: ${o}`)};return window.addEventListener("error",i),window.addEventListener("unhandledrejection",d),()=>{window.removeEventListener("error",i),window.removeEventListener("unhandledrejection",d)}}import{Component as Te}from"react";var Ae=u("ErrorBoundary"),h=class extends Te{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t){Ae.error("render error:",t)}render(){return this.state.hasError?this.props.fallback:this.props.children}};import{jsx as m,jsxs as Ie}from"react/jsx-runtime";var R={info:{bg:"rgba(22,119,255,0.08)",color:"#1677ff"},warning:{bg:"rgba(250,140,22,0.10)",color:"#d46b08"},error:{bg:"rgba(255,77,79,0.08)",color:"#ff4d4f"}};function Se({notices:e}){return e.length?m("div",{style:{display:"flex",flexDirection:"column",gap:4,marginBottom:8},children:e.map(t=>m("div",{style:{fontSize:12,lineHeight:"18px",padding:"4px 10px",borderRadius:8,background:R[t.level]?.bg??R.info.bg,color:R[t.level]?.color??R.info.color},children:t.message},`${t.code}:${t.message}`))}):null}function Pe({card:e,registry:t,onAction:r}){let n=e.status!=="error"?t[e.cardType]:void 0,l=e.status==="error"||!n,i=n?.selfRendersActions===!0,d=!e.isHistory&&!i&&e.actions?.length?e.actions:[],a=typeof e.expiresAt=="number"&&Date.now()>=e.expiresAt;return Ie("div",{className:`tbox-card${l?" tbox-card--fallback":""}${a?" tbox-card--stale":""}`,style:a?{opacity:.55,filter:"grayscale(1)"}:void 0,children:[m(Se,{notices:e.notices??[]}),e.status==="error"?`卡片渲染失败${e.error?`:${e.error}`:""}`:n?m(h,{fallback:"卡片渲染出错",children:m(n,{data:e.data,cardId:e.id,isHistory:!!e.isHistory,actionTokens:e.actionTokens})}):`未知卡片类型:${e.cardType}`,d.length>0&&m("div",{style:{display:"flex",gap:8,marginTop:8,flexWrap:"wrap"},children:d.map(o=>{let g=!o.kind||o.kind==="moduleAction",s=g?e.actionTokens?.[o.actionId]:void 0,p=o.enabled===!1,c=p||!r||g&&!s,f=()=>o.kind==="sendMessage"?{type:"sendMessage",value:o.value??o.label??o.actionId,visible:o.visible??!0}:o.kind==="openLink"?{type:"openLink",url:o.url??""}:{type:"moduleAction",actionId:o.actionId,contextToken:s??""},B=()=>o.style==="primary"?{fontSize:13,padding:"6px 14px",borderRadius:18,border:"1px solid var(--tbox-primary, #1677ff)",color:"#fff",background:"var(--tbox-primary, #1677ff)"}:o.style==="link"?{fontSize:13,padding:"6px 4px",borderRadius:4,border:"none",color:"var(--tbox-primary, #1677ff)",background:"transparent",textDecoration:"underline"}:{fontSize:13,padding:"6px 14px",borderRadius:18,border:"1px solid var(--tbox-primary, #1677ff)",color:"var(--tbox-primary, #1677ff)",background:"transparent"};return m("button",{type:"button",disabled:c,title:p?o.disabledReason:void 0,onClick:()=>!c&&r?.(f()),style:{...B(),cursor:c?"not-allowed":"pointer",opacity:c?.5:1},children:o.label??o.actionId},o.actionId)})})]})}import{Fragment as Ee,jsx as N}from"react/jsx-runtime";function ke({cards:e,renderItem:t,className:r}){if(e.length<=1)return N(Ee,{children:e.length===1?t(e[0]):null});let n=["tbox-card-stack",r].filter(Boolean).join(" ");return N("div",{className:n,style:{display:"flex",flexDirection:"column"},children:e.map(l=>N("div",{children:t(l)},l.id))})}import{jsx as v,jsxs as q}from"react/jsx-runtime";var x=(e,t)=>`var(--${e}, ${t})`;function Le(e){return e.selfRendersActions=!0,e}function Be(e){return q("div",{style:{borderRadius:12,background:x("tbox-card-bg","#ffffff"),padding:"12px 16px",boxShadow:`0 1px 4px ${x("tbox-shadow","rgba(0,0,0,0.06)")}`,...e.style},children:[(e.title||e.subtitle)&&q("div",{style:{marginBottom:8},children:[e.title&&v("div",{style:{fontSize:15,fontWeight:600,color:x("tbox-text-primary","#1a1a1a"),lineHeight:"20px"},children:e.title}),e.subtitle&&v("div",{style:{fontSize:12,color:x("tbox-text-secondary","#888888"),marginTop:2},children:e.subtitle})]}),e.children]})}function Oe(e){return q("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"4px 0",gap:12},children:[v("span",{style:{fontSize:13,color:x("tbox-text-secondary","#888888"),flexShrink:0},children:e.label}),v("span",{style:{fontSize:13,color:x("tbox-text-primary","#1a1a1a"),textAlign:"right",wordBreak:"break-all",...e.valueStyle},children:e.value})]})}var Me={success:{fg:"#0f8a5f",bg:"#e6f7f0"},warning:{fg:"#b26a00",bg:"#fff4e0"},neutral:{fg:"#666666",bg:"#f2f3f5"}};function Ne(e){let t=Me[e.tone];return v("span",{style:{display:"inline-block",fontSize:11,lineHeight:"18px",padding:"0 8px",borderRadius:9,color:t.fg,background:t.bg,whiteSpace:"nowrap"},children:e.text})}var Y=u("bridge"),_="https://appx/web-view.min.js",b=null;function Q(){return typeof window>"u"?null:window.my&&typeof window.my.postMessage=="function"?window.my:null}function $(){if(typeof window<"u"){let e=Q();if(e)return Promise.resolve(e)}return b||(b=new Promise(e=>{if(typeof window>"u"){e(null);return}let t=!1,r=null,n=null,l=()=>{r&&clearInterval(r),n&&clearTimeout(n),document.removeEventListener("AlipayJSBridgeReady",d)},i=o=>{t||(t=!0,l(),e(o))},d=()=>{let o=Q();o&&i(o)};document.addEventListener("AlipayJSBridgeReady",d);let a=document.querySelector(`script[src="${_}"]`);a||(a=document.createElement("script"),a.src=_,document.head.appendChild(a)),a.addEventListener("load",d,{once:!0}),a.addEventListener("error",()=>{Y.warn("加载支付宝 JSSDK 失败:",_),a?.remove(),i(null)},{once:!0}),r=setInterval(d,50),n=setTimeout(()=>{Y.warn("支付宝 JSSDK 已加载,但 my.postMessage 未就绪"),i(null)},5e3),d()}),b.then(e=>{e||(b=null)}),b)}import{detailForLog as K}from"@tbox.cn/app-sdk-core";var T="open-mini-webview-bridge",qe="1.0.0",D="my",W="my.call",F="tbox";function A(e){return!!e&&typeof e=="object"&&e.bridge===T&&typeof e.requestId=="string"}var C=u("bridge"),S=new Map,Z=0,ee=!1,re=300*1e3;function _e(){return Z+=1,`h5_${Date.now()}_${Z}`}function $e(e){if(A(e)){te(e);return}let t=e,r=e;if(r&&typeof r=="object"&&(r.detail!==void 0?t=r.detail:r.data!==void 0&&(t=r.data)),typeof t=="string")try{t=JSON.parse(t)}catch{return}A(t)&&te(t)}function te(e){let t=S.get(e.requestId);if(!t){C.debug(`收到无 pending 回执 req=${e.requestId} action=${e.action}`);return}clearTimeout(t.timer),S.delete(e.requestId);let r=Date.now()-t.startedAt;e.success?(C.info(`← ${t.apiName} ok ${r}ms req=${e.requestId}`),t.resolve(e.data)):(C.warn(`← ${t.apiName} fail code=${e.code||"BRIDGE_ERROR"} ${r}ms params=${K(t.params)} req=${e.requestId}`),t.reject({code:e.code||"BRIDGE_ERROR",errorMessage:e.errorMessage||"桥调用失败"}))}async function ne(){let e=await $();return e&&!ee&&(e.onMessage=$e,ee=!0),e}function oe(e,t,r,n,l){let i=_e();return new Promise((d,a)=>{let o=setTimeout(()=>{S.delete(i),C.warn(`← ${r} fail code=BRIDGE_TIMEOUT ${l}ms params=${K(n)} req=${i}`),a({code:"BRIDGE_TIMEOUT",errorMessage:`桥调用超时: ${r}`})},l);S.set(i,{resolve:d,reject:a,timer:o,apiName:r,params:n,startedAt:Date.now()}),C.info(`→ ${t} ${r} req=${i}`),C.debug(`params=${K(n)} req=${i}`),e.postMessage({bridge:T,action:t,apiName:r,params:n,requestId:i})})}async function y(e,t={},r="invoke",n=re){let l=await ne();if(!l)throw{code:"BRIDGE_UNAVAILABLE",errorMessage:"支付宝 WebView 桥未就绪"};return oe(l,r==="call"?W:"my",e,t,n)}async function w(e,t={},r=re){let n=await ne();if(!n)throw{code:"BRIDGE_UNAVAILABLE",errorMessage:"支付宝 WebView 桥未就绪"};return oe(n,F,e,t,r)}var H=/^[A-Za-z0-9._-]{1,80}$/u,ie=/[\u0000-\u001F\u007F]/u,ae=/^[A-Za-z][A-Za-z0-9+.-]*:/u;function De(e,t){if(!t||!H.test(t))return;let r;try{r=se(e)}catch{return}if(r.protocol!=="alipays"&&r.protocol!=="alipayrisk"||r.host!=="platformapi"||r.path!=="/startapp")return;let n=(r.appId||r.query.appId||"").trim(),l=(r.query.page||"").trim(),i=(r.query.query||"").trim()||void 0,d=l.replace(/^\/+/,"");if(!(!H.test(n)||!d||ie.test(d)||ae.test(d)||d.split("/").includes(".."))){if(n===t){let a=d.includes("?")?"&":"?";return{apiName:"navigateTo",params:{url:`/${d}${i?`${a}${i}`:""}`}}}return{apiName:"navigateToMiniProgram",params:{appId:n,path:d,...i?{query:i}:{}}}}}function V(e){try{return decodeURIComponent(e)}catch{return e}}function se(e){if(!e||typeof e!="string")throw new Error("Invalid URL");let t={protocol:"",host:"",path:"",query:{page:"",appId:""},appId:"",page:{path:"",query:{}}},r=e.match(/^([a-zA-Z]+):\/\//),n=e;r&&(t.protocol=r[1],n=n.slice(r[0].length));let l=n.indexOf("?"),i=l===-1?n:n.slice(0,l),d=l===-1?"":n.slice(l+1),a=i.indexOf("/");if(a>0?(t.host=i.slice(0,a),t.path=i.slice(a)):t.host=i,d){let o="",g={};for(let s of d.split("&")){if(s.startsWith("page=")){o=s.slice(5);continue}let p=s.indexOf("="),c=p>=0?s.slice(0,p):s;if(!c)continue;let f=p>=0?s.slice(p+1):"";g[c]=V(f)}if(t.query=g,o){t.query.page=V(o),t.query.appId&&(t.appId=t.query.appId);let s=o.indexOf("?"),p=s>=0?o.slice(0,s):o;t.page.path=V(p)}}return t}function le(e){if(typeof e!="string")return;let t;try{t=se(e)}catch{return}if(t.protocol!=="alipays"&&t.protocol!=="alipayrisk"||t.host!=="platformapi"||t.path!=="/startapp")return;let r=(t.appId||t.query.appId||"").trim(),n=(t.query.page||"").trim(),l=(t.query.query||"").trim()||void 0,i=n.replace(/^\/+/,"");if(!H.test(r)||!i||ie.test(i)||ae.test(i)||i.split("/").includes(".."))return;let d=i.includes("?")?"&":"?";return{appId:r,url:`/${i}${l?`${d}${l}`:""}`}}function We(e=9,t){return y("chooseImage",t?{count:e,sourceType:t}:{count:e},"invoke")}function Fe(){return y("chooseVideo",{},"invoke")}async function Ke(){let e=await y("getLocation",{type:0});return{...e,longitude:Number(e.longitude),latitude:Number(e.latitude)}}function Ve(e=["auth_base"]){return y("getAuthCode",{scopes:e})}function He(e){return y("tradePay",{tradeNO:e,orderStr:e})}function Ue(e){let t=le(e);return t?y("navigateTo",{url:t.url}):Promise.reject({code:"INVALID_PARAMS",errorMessage:"支付宝小程序跳转链接无效"})}function Ge(e,t=300*1e3){return w("getLoginState",e??{},t)}function Je(e={}){return w("openPlateKeyboard",{...e})}function ze(){return w("closePlateKeyboard")}import{KNOWN_CLIENT_SLOT_IDS as je}from"@tbox.cn/app-contracts";var P=u("ClientCardRegistry"),de=u("ClientSlotRegistry"),I=class{cards=new Map;overrides=new Map;inlineFilters=[];register(t,r){if(!t){P.warn("register without cardType skipped");return}this.cards.has(t)&&P.warn(`overwriting existing cardType: ${t}`),this.cards.set(t,r)}registerMap(t){for(let[r,n]of Object.entries(t))this.register(r,n)}registerOverride(t,r){if(!t){P.warn("registerOverride without cardType skipped");return}this.overrides.has(t)&&P.warn(`overwriting existing override: ${t}`),this.overrides.set(t,r)}getOverrides(){return Object.fromEntries(this.overrides)}registerInlineFilter(t){this.inlineFilters.push(t)}applyInlineFilters(t){return this.inlineFilters.length===0?[...t]:this.inlineFilters.reduce((r,n)=>n(r),[...t])}resolve(t){return this.cards.get(t)}getAll(){return Object.fromEntries(this.cards)}has(t){return this.cards.has(t)}get size(){return this.cards.size}},k=class{slots=new Map;register(t,r){je.includes(t)||de.warn(`unknown slot id: ${t}(KNOWN_CLIENT_SLOT_IDS 未含——版本偏斜?照常注册)`),this.slots.has(t)&&de.warn(`overwriting existing slot: ${t}`),this.slots.set(t,r)}resolve(t){return this.slots.get(t)}has(t){return this.slots.has(t)}get size(){return this.slots.size}};function Xe(){return{cards:new I,slots:new k}}var pe="";function Ye(e){pe=(e.apiBaseUrl??"").trim().replace(/\/$/,"")}function E(){return pe}function ce(){let e=E();return e||(typeof window<"u"?`${window.location.protocol}//${window.location.host}`:"")}function Qe(e="/ws"){return ce().replace(/^http/,"ws")+e}var U=null;function ue(){if(U)return U;let e={};return typeof window<"u"&&new URLSearchParams(window.location.search).forEach((r,n)=>{e[n]=r}),U=e,e}function ge(e){return ue()[e]}function fe(){let e=ge("container");if(e==="alipay-miniapp"||e==="alipay")return!0;if(typeof navigator<"u"){let t=navigator.userAgent;if(/AlipayClient/i.test(t)||/AliApp\(AP\//i.test(t))return!0}return!1}function Ze(){if(typeof window>"u")return!1;let e=window.my;return!!(e&&typeof e.postMessage=="function")}function et(){return fe()?"alipay-miniapp":"browser"}var tt=u("HTTP"),G=null;function rt(e){G=e}var J=null;function nt(e){J=e}function ot(e,t){if(e!==401)return!1;let r=t?.code;return r==="AUTH_UNAUTHORIZED"||r==="AUTH_TOKEN_EXPIRED"}function it(e){return e.startsWith("/api/auth/")}function ye(e="",t,r={}){let n=e.replace(/\/$/,""),l=r.auth??!1,i=r.dynamicBase??!1,d=s=>({...t?.headers,...s?.headers});function a(){return i?E():n}function o(s){let p=d(s);if(l){let c=G?G():null;c&&!p.Authorization&&(p.Authorization=`Bearer ${c}`)}return p}async function g(s,p,c,f,B=0){let me=s==="POST"&&c instanceof Blob?o(f):s==="POST"?{"Content-Type":"application/json",...o(f)}:o(f),O=await fetch(a()+p,{method:s,headers:me,body:s==="POST"&&c!==void 0?c instanceof Blob?c:JSON.stringify(c):void 0,signal:f?.signal,cache:f?.cache}),z=await O.json().catch(()=>null);if(l&&B===0&&!it(p)&&ot(O.status,z)&&J){try{await J()}catch(xe){tt.warn("401 refresh failed:",xe.message)}return g(s,p,c,f,1)}return{status:O.status,data:z}}return{get:(s,p)=>g("GET",s,void 0,p),post:(s,p,c)=>g("POST",s,p,c),postBinary:(s,p,c)=>g("POST",s,p,c)}}var L=ye("",void 0,{auth:!0,dynamicBase:!0}),at=L.get,st=L.post,lt=L.postBinary;import{sanitizeForLog as ir,detailForLog as ar,truncateForLog as sr}from"@tbox.cn/app-sdk-core";export{T as BRIDGE_NAME,qe as BRIDGE_VERSION,ke as CardCarousel,Be as CardFrame,Pe as CardRenderer,I as ClientCardRegistry,k as ClientSlotRegistry,h as ErrorBoundary,D as MY_ACTION,W as MY_CALL_ACTION,Ne as StatusPill,F as TBOX_ACTION,Oe as ValueText,w as callCapability,y as callJsapi,We as chooseImage,Fe as chooseVideo,ze as closePlateKeyboard,nt as configureAuthErrorHandler,rt as configureAuthToken,Ye as configureRuntime,Xe as createClientContext,u as createClientLogger,Vt as createLogger,ye as createService,L as defaultService,ar as detailForLog,$ as ensureAlipayJSSDK,E as getApiBaseUrl,ce as getApiOrigin,Ve as getAuthCode,et as getContainer,Ke as getLocation,Ge as getLoginState,ge as getQueryParam,ue as getRuntimeQuery,Qe as getWsUrl,Ze as hasAlipayBridge,at as httpGet,st as httpPost,lt as httpPostBinary,Re as installGlobalErrorReporting,A as isBridgeReceipt,fe as isInAlipayShell,Le as markSelfRendering,Ue as navigateToAlipayPage,Je as openPlateKeyboard,He as requestPayment,De as resolveAlipayNavigation,ir as sanitizeForLog,we as setClientLogTransport,he as setClientRunContext,sr as truncateForLog};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@tbox.cn/app-sdk-client",
3
+ "version": "0.21.0",
4
+ "type": "module",
5
+ "description": "tbox 应用平台 SDK client 轴:runtime/http/logger/error-boundary、桥接(支付宝 web-view)、client-context、卡片渲染基元。agent 前端运行时在 @tbox.cn/app-agent-sdk-client。",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/client/index.d.ts",
9
+ "import": "./dist/client/index.js"
10
+ },
11
+ "./client": {
12
+ "types": "./dist/client/index.d.ts",
13
+ "import": "./dist/client/index.js"
14
+ },
15
+ "./platform/code-inspect": {
16
+ "types": "./platform/code-inspect.d.cts",
17
+ "default": "./platform/code-inspect.cjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "platform",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@tbox.cn/app-contracts": "0.12.0",
34
+ "@tbox.cn/app-sdk-core": "0.20.0",
35
+ "react": "18.3.1",
36
+ "react-dom": "18.3.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "24.13.3",
40
+ "@types/react": "18.3.31",
41
+ "@types/react-dom": "18.3.7",
42
+ "tsup": "8.5.1",
43
+ "typescript": "5.9.3",
44
+ "vitest": "4.1.10"
45
+ },
46
+ "license": "MIT",
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest run"
51
+ }
52
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ============================================================================
3
+ * 平台配套 babel plugin - 给每个 JSXElement 注入 data-code-inspect-* 属性。
4
+ * 配合 tbox 平台 inspector runtime(/__vs_inspector__.js)使用,让 host 端微调浮层
5
+ * 能拿到源码位置,直接走「直改源码」快路径,而不是把意图发回 LLM 重新生成代码。
6
+ *
7
+ * 注入的属性(与 inspector runtime 期望严格对齐,名字别改):
8
+ * data-code-inspect-loc-filepath = 绝对路径
9
+ * data-code-inspect-loc-line = "{line}:{column}" (1-based, 来自 JSX opening tag)
10
+ * data-code-inspect-jsx-tag = JSX 标签名 (如 "div" / "Form.Item")
11
+ * data-code-inspect-component = 所在组件名 (爬 AST 找最近的函数/类组件)
12
+ *
13
+ * Vite 接入:`import { codeInspectPlugin } from '@tbox.cn/app-sdk/platform/code-inspect'`,
14
+ * 在 vite.config.ts 里 `react({ babel: { plugins: [...] } })`,仅 dev(command==='serve')启用。
15
+ * ============================================================================
16
+ */
17
+
18
+ const LOC_FILEPATH_ATTRIBUTE_NAME = 'data-code-inspect-loc-filepath';
19
+ const LOC_LINE_ATTRIBUTE_NAME = 'data-code-inspect-loc-line';
20
+ const TAG_ATTRIBUTE_NAME = 'data-code-inspect-jsx-tag';
21
+ const COMPONENT_ATTRIBUTE_NAME = 'data-code-inspect-component';
22
+
23
+ const PLUGIN_NAME = 'code-inspect-plugin';
24
+
25
+ function getJsxName(nameNode) {
26
+ if (nameNode.type === 'JSXIdentifier') return nameNode.name;
27
+ if (nameNode.type === 'JSXMemberExpression') {
28
+ return getJsxName(nameNode.object) + '.' + nameNode.property.name;
29
+ }
30
+ return '';
31
+ }
32
+
33
+ function getCurrentComponentName(path) {
34
+ let current = path;
35
+ while (current) {
36
+ if (current.isFunctionDeclaration() && current.node.id) {
37
+ return current.node.id.name;
38
+ }
39
+ if (
40
+ current.isVariableDeclarator() &&
41
+ current.node.id &&
42
+ current.node.init
43
+ ) {
44
+ const init = current.node.init;
45
+ if (
46
+ init.type === 'ArrowFunctionExpression' ||
47
+ init.type === 'FunctionExpression'
48
+ ) {
49
+ return current.node.id.name;
50
+ }
51
+ }
52
+ if (current.isClassDeclaration() && current.node.id) {
53
+ return current.node.id.name;
54
+ }
55
+ if (
56
+ (current.isFunctionExpression() ||
57
+ current.isArrowFunctionExpression() ||
58
+ current.isCallExpression()) &&
59
+ current.parent &&
60
+ current.parent.type === 'VariableDeclarator' &&
61
+ current.parent.id
62
+ ) {
63
+ return current.parent.id.name;
64
+ }
65
+ current = current.parentPath;
66
+ }
67
+ return 'Unknown';
68
+ }
69
+
70
+ function CodeInspectPlugin(babel) {
71
+ const t = babel.types;
72
+
73
+ return {
74
+ name: PLUGIN_NAME,
75
+ visitor: {
76
+ JSXElement(path) {
77
+ const node = path.node;
78
+ const nodeName = getJsxName(node.openingElement.name);
79
+ if (!nodeName) return;
80
+ if (!node.loc) return;
81
+
82
+ const filename = path.hub.file.opts.filename || 'unknown';
83
+ const { line, column } = node.loc.start;
84
+ const componentName = getCurrentComponentName(path);
85
+
86
+ node.openingElement.attributes.push(
87
+ t.jsxAttribute(
88
+ t.jsxIdentifier(LOC_FILEPATH_ATTRIBUTE_NAME),
89
+ t.stringLiteral(filename),
90
+ ),
91
+ t.jsxAttribute(
92
+ t.jsxIdentifier(LOC_LINE_ATTRIBUTE_NAME),
93
+ t.stringLiteral(`${line}:${column}`),
94
+ ),
95
+ t.jsxAttribute(
96
+ t.jsxIdentifier(TAG_ATTRIBUTE_NAME),
97
+ t.stringLiteral(nodeName),
98
+ ),
99
+ t.jsxAttribute(
100
+ t.jsxIdentifier(COMPONENT_ATTRIBUTE_NAME),
101
+ t.stringLiteral(componentName),
102
+ ),
103
+ );
104
+ },
105
+ },
106
+ };
107
+ }
108
+
109
+ module.exports = CodeInspectPlugin;
110
+ module.exports.CodeInspectPlugin = CodeInspectPlugin;
111
+ module.exports.PLUGIN_NAME = PLUGIN_NAME;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 平台 code-inspect babel plugin 类型声明(最小结构面,不引 @babel/core 类型依赖)。
3
+ * 运行时实现见同目录 code-inspect.cjs;vite.config.ts 接入示例见 .cjs 头注释。
4
+ */
5
+ declare function CodeInspectPlugin(babel: {
6
+ types: {
7
+ jsxAttribute: (name: unknown, value: unknown) => unknown;
8
+ jsxIdentifier: (name: string) => unknown;
9
+ stringLiteral: (value: string) => unknown;
10
+ };
11
+ }): {
12
+ name: string;
13
+ visitor: Record<string, (path: unknown) => void>;
14
+ };
15
+
16
+ /** default = 插件工厂函数本身(CJS module.exports),.CodeInspectPlugin/.PLUGIN_NAME 为挂载属性 */
17
+ declare const codeInspectPlugin: typeof CodeInspectPlugin & {
18
+ CodeInspectPlugin: typeof CodeInspectPlugin;
19
+ PLUGIN_NAME: string;
20
+ };
21
+
22
+ export default codeInspectPlugin;