@opentiny/next-sdk 0.2.8 → 0.2.9
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/dist/index.es.dev.js +87 -3
- package/dist/index.es.js +9962 -9898
- package/dist/index.js +878 -814
- package/dist/index.umd.dev.js +87 -3
- package/dist/index.umd.js +40 -40
- package/dist/page-tools/bridge.d.ts +23 -0
- package/package.json +1 -1
- package/page-tools/bridge.ts +134 -0
|
@@ -64,6 +64,29 @@ type RegisterToolConfig<InputArgs extends ZodRawShape, OutputArgs extends ZodRaw
|
|
|
64
64
|
export type PageAwareServer = Omit<WebMcpServer, 'registerTool'> & {
|
|
65
65
|
registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(name: string, config: RegisterToolConfig<InputArgs, OutputArgs>, handlerOrRoute: ((...args: any[]) => any) | RouteConfig): RegisteredTool;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* 注册一个通用的页面跳转工具(navigate_to_page),供大模型在需要时主动跳转到指定路由。
|
|
69
|
+
*
|
|
70
|
+
* 要求:
|
|
71
|
+
* - 业务侧在应用入口通过 setNavigator 注册导航函数(如 router.push 或 navigateByUrl)
|
|
72
|
+
* - 前端页面在目标路由下调用 registerPageTool,确保 page-ready 能正确广播
|
|
73
|
+
*
|
|
74
|
+
* 工具行为:
|
|
75
|
+
* - 输入 path(如 "/orders"、"/price-protection"),调用 setNavigator 注册的函数执行跳转
|
|
76
|
+
* - 等待目标页面完成挂载并广播 page-ready(或在超时时间到达时兜底返回)
|
|
77
|
+
* - 返回简单的文本说明,提示跳转结果
|
|
78
|
+
*/
|
|
79
|
+
export type NavigateToolOptions = {
|
|
80
|
+
/** 工具名称,默认 'navigate_to_page' */
|
|
81
|
+
name?: string;
|
|
82
|
+
/** 工具标题,默认 '页面跳转' */
|
|
83
|
+
title?: string;
|
|
84
|
+
/** 工具描述 */
|
|
85
|
+
description?: string;
|
|
86
|
+
/** 等待 page-ready 的超时时间(ms),默认 1500 */
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
};
|
|
89
|
+
export declare function registerNavigateTool(server: WebMcpServer, options?: NavigateToolOptions): RegisteredTool;
|
|
67
90
|
/**
|
|
68
91
|
* 包装 WebMcpServer,使 registerTool 第三个参数支持 RouteConfig。
|
|
69
92
|
*
|
package/package.json
CHANGED
package/page-tools/bridge.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { ZodRawShape } from 'zod'
|
|
16
|
+
import { z } from 'zod'
|
|
16
17
|
import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
17
18
|
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'
|
|
18
19
|
import type { WebMcpServer } from '../WebMcpServer'
|
|
@@ -34,6 +35,9 @@ export const MSG_ROUTE_STATE_INITIAL = 'next-sdk:route-state-initial'
|
|
|
34
35
|
// 已激活页面注册表:路由路径 → 是否已挂载
|
|
35
36
|
const activePages = new Map<string, boolean>()
|
|
36
37
|
|
|
38
|
+
// 路由路径规范化:去除尾部斜杠,空路径兜底为 '/'
|
|
39
|
+
const normalizeRoute = (value: string) => value.replace(/\/+$/, '') || '/'
|
|
40
|
+
|
|
37
41
|
type BroadcastTarget = { win: Window; origin: string }
|
|
38
42
|
|
|
39
43
|
// 跨窗口广播目标:同窗口默认 [window],iframe 场景下会加入 remoter 的 contentWindow
|
|
@@ -113,6 +117,42 @@ export function setNavigator(fn: (route: string) => void | Promise<void>) {
|
|
|
113
117
|
_navigator = fn
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
/**
|
|
121
|
+
* 等待指定路由页面完成挂载并广播 page-ready。
|
|
122
|
+
* - 仅在浏览器环境下生效(window 存在时)
|
|
123
|
+
* - 使用与 registerPageTool 相同的路由规范化规则
|
|
124
|
+
* - 内置兜底超时,防止 Promise 永远不 resolve
|
|
125
|
+
*/
|
|
126
|
+
function waitForPageReady(path: string, timeoutMs = 1500): Promise<void> {
|
|
127
|
+
if (typeof window === 'undefined') {
|
|
128
|
+
return Promise.resolve()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const target = normalizeRoute(path)
|
|
132
|
+
|
|
133
|
+
return new Promise<void>((resolve) => {
|
|
134
|
+
let done = false
|
|
135
|
+
|
|
136
|
+
const cleanup = () => {
|
|
137
|
+
if (done) return
|
|
138
|
+
done = true
|
|
139
|
+
window.removeEventListener('message', handleMessage)
|
|
140
|
+
resolve()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handleMessage = (event: MessageEvent) => {
|
|
144
|
+
if (event.source !== window || event.data?.type !== MSG_PAGE_READY) return
|
|
145
|
+
const route = normalizeRoute(String(event.data.route ?? ''))
|
|
146
|
+
if (route === target) {
|
|
147
|
+
cleanup()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
window.addEventListener('message', handleMessage)
|
|
152
|
+
setTimeout(cleanup, timeoutMs)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
116
156
|
/**
|
|
117
157
|
* registerTool 第三个参数的路由配置对象类型。
|
|
118
158
|
* 当传入此类型时,工具调用会自动跳转到 route 对应的页面并通过消息通信执行。
|
|
@@ -163,6 +203,100 @@ export type PageAwareServer = Omit<WebMcpServer, 'registerTool'> & {
|
|
|
163
203
|
): RegisteredTool
|
|
164
204
|
}
|
|
165
205
|
|
|
206
|
+
/**
|
|
207
|
+
* 注册一个通用的页面跳转工具(navigate_to_page),供大模型在需要时主动跳转到指定路由。
|
|
208
|
+
*
|
|
209
|
+
* 要求:
|
|
210
|
+
* - 业务侧在应用入口通过 setNavigator 注册导航函数(如 router.push 或 navigateByUrl)
|
|
211
|
+
* - 前端页面在目标路由下调用 registerPageTool,确保 page-ready 能正确广播
|
|
212
|
+
*
|
|
213
|
+
* 工具行为:
|
|
214
|
+
* - 输入 path(如 "/orders"、"/price-protection"),调用 setNavigator 注册的函数执行跳转
|
|
215
|
+
* - 等待目标页面完成挂载并广播 page-ready(或在超时时间到达时兜底返回)
|
|
216
|
+
* - 返回简单的文本说明,提示跳转结果
|
|
217
|
+
*/
|
|
218
|
+
export type NavigateToolOptions = {
|
|
219
|
+
/** 工具名称,默认 'navigate_to_page' */
|
|
220
|
+
name?: string
|
|
221
|
+
/** 工具标题,默认 '页面跳转' */
|
|
222
|
+
title?: string
|
|
223
|
+
/** 工具描述 */
|
|
224
|
+
description?: string
|
|
225
|
+
/** 等待 page-ready 的超时时间(ms),默认 1500 */
|
|
226
|
+
timeoutMs?: number
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function registerNavigateTool(server: WebMcpServer, options?: NavigateToolOptions): RegisteredTool {
|
|
230
|
+
const name = options?.name ?? 'navigate_to_page'
|
|
231
|
+
const title = options?.title ?? '页面跳转'
|
|
232
|
+
const description =
|
|
233
|
+
options?.description ??
|
|
234
|
+
'当需要的工具在当前页面不可用时,使用此工具跳转到特定页面。例如:要查询订单时跳转到 "/orders",要创建价保时跳转到 "/price-protection"。'
|
|
235
|
+
const timeoutMs = options?.timeoutMs ?? 1500
|
|
236
|
+
|
|
237
|
+
return server.registerTool(
|
|
238
|
+
name,
|
|
239
|
+
{
|
|
240
|
+
title,
|
|
241
|
+
description,
|
|
242
|
+
inputSchema: {
|
|
243
|
+
path: z.string().describe('目标页面的路由地址,例如 "/orders"、"/inventory"、"/price-protection" 等。')
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
async ({ path }: { path: string }) => {
|
|
247
|
+
if (typeof window === 'undefined') {
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: 'text', text: '当前环境不支持页面跳转(window 不存在)。' }]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!_navigator) {
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: '页面跳转失败:尚未在应用入口调用 setNavigator 注册导航函数,无法执行路由跳转。'
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const target = normalizeRoute(path)
|
|
266
|
+
// 若当前已在目标路由上,直接返回成功,避免不必要的跳转。
|
|
267
|
+
// 兼容子路径部署(如 base: '/ai-vue/'):pathname 可能为 /ai-vue/orders,而 path 为 /orders,需判断 pathname 是否“以目标路由结尾”
|
|
268
|
+
const current = normalizeRoute(window.location.pathname)
|
|
269
|
+
const isAlreadyOnTarget =
|
|
270
|
+
current === target ||
|
|
271
|
+
(current.endsWith(target) && (current.length === target.length || current[current.lastIndexOf(target) - 1] === '/'))
|
|
272
|
+
if (isAlreadyOnTarget) {
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: 'text', text: `当前已在页面:${path}。请继续你的下一步操作。` }]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 先注册 page-ready 监听再触发导航,避免极快导航下事件先于监听器触发而漏收(与 buildPageHandler 中的顺序一致)
|
|
279
|
+
const readyPromise = waitForPageReady(path, timeoutMs)
|
|
280
|
+
await _navigator(path)
|
|
281
|
+
await readyPromise
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: 'text', text: `已成功跳转至页面:${path}。请继续你的下一步操作。` }]
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
return {
|
|
288
|
+
content: [
|
|
289
|
+
{
|
|
290
|
+
type: 'text',
|
|
291
|
+
text: `页面跳转失败:${err instanceof Error ? err.message : String(err)}。`
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
166
300
|
/**
|
|
167
301
|
* 内部:根据 name/route/timeout 生成转发给页面的 handler 函数。
|
|
168
302
|
* 调用流程:
|