@opentiny/next-sdk 0.3.3 → 0.4.1
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/agent/AgentModelProvider.ts +78 -81
- package/agent/type.ts +6 -9
- package/agent/utils/getAISDKTools.ts +0 -1
- package/agent/utils/getBuiltinMcpTools.ts +7 -7
- package/core.ts +1 -3
- package/dist/SimulatorMask-BHVXyogh-iNUb4dyF.js +361 -0
- package/dist/agent/type.d.ts +4 -12
- package/dist/agent/utils/getBuiltinMcpTools.d.ts +3 -3
- package/dist/core.d.ts +1 -1
- package/dist/core.js +18 -18
- package/dist/index-CXK8CcKq.js +6076 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +57 -2783
- package/dist/initialize-builtin-WebMCP-rALfqS-o.js +6389 -0
- package/dist/page-tools/a11y/build.d.ts +11 -0
- package/dist/page-tools/a11y/constants.d.ts +9 -0
- package/dist/page-tools/a11y/search.d.ts +17 -0
- package/dist/page-tools/a11y/types.d.ts +104 -0
- package/dist/page-tools/a11y/utils.d.ts +34 -0
- package/dist/page-tools/a11y/vnode.d.ts +37 -0
- package/dist/page-tools/a11y-tree.d.ts +12 -0
- package/dist/page-tools/bridge.d.ts +0 -6
- package/dist/page-tools/constants.d.ts +22 -0
- package/dist/page-tools/context.d.ts +28 -0
- package/dist/page-tools/handlers/browserState.d.ts +8 -0
- package/dist/page-tools/handlers/click.d.ts +8 -0
- package/dist/page-tools/handlers/executeJavascript.d.ts +8 -0
- package/dist/page-tools/handlers/fill.d.ts +8 -0
- package/dist/page-tools/handlers/scroll.d.ts +8 -0
- package/dist/page-tools/handlers/searchTree.d.ts +8 -0
- package/dist/page-tools/handlers/select.d.ts +8 -0
- package/dist/page-tools/initialize-builtin-WebMCP.d.ts +1 -0
- package/dist/page-tools/page-agent-highlight/index.d.ts +9 -0
- package/dist/page-tools/page-agent-mask/SimulatorMask.d.ts +14 -0
- package/dist/page-tools/page-agent-mask/checkDarkMode.d.ts +5 -0
- package/dist/page-tools/page-agent-tool.d.ts +4 -0
- package/dist/page-tools/page-state-cache.d.ts +36 -0
- package/dist/page-tools/schema.d.ts +43 -0
- package/dist/page-tools/utils/dom.d.ts +6 -0
- package/dist/page-tools/utils/scroll.d.ts +15 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.js +744 -0
- package/dist/utils/builtinProxy.d.ts +1 -1
- package/index.ts +2 -19
- package/package.json +14 -18
- package/page-tools/a11y/build.ts +70 -0
- package/page-tools/a11y/constants.ts +110 -0
- package/page-tools/a11y/search.ts +129 -0
- package/page-tools/a11y/types.ts +108 -0
- package/page-tools/a11y/utils.ts +266 -0
- package/page-tools/a11y/vnode.ts +229 -0
- package/page-tools/a11y-tree.ts +13 -0
- package/page-tools/bridge.ts +49 -15
- package/page-tools/constants.ts +72 -0
- package/page-tools/context.ts +13 -0
- package/page-tools/handlers/browserState.ts +12 -0
- package/page-tools/handlers/click.ts +26 -0
- package/page-tools/handlers/executeJavascript.ts +22 -0
- package/page-tools/handlers/fill.ts +60 -0
- package/page-tools/handlers/scroll.ts +66 -0
- package/page-tools/handlers/searchTree.ts +30 -0
- package/page-tools/handlers/select.ts +34 -0
- package/page-tools/initialize-builtin-WebMCP.ts +20 -0
- package/page-tools/page-agent-highlight/index.ts +198 -0
- package/page-tools/page-agent-mask/SimulatorMask.module.css +14 -0
- package/page-tools/page-agent-mask/SimulatorMask.ts +279 -0
- package/page-tools/page-agent-mask/checkDarkMode.ts +181 -0
- package/page-tools/page-agent-mask/cursor-border.svg +3 -0
- package/page-tools/page-agent-mask/cursor-fill.svg +5 -0
- package/page-tools/page-agent-mask/cursor.module.css +70 -0
- package/page-tools/page-agent-prompt.md +139 -0
- package/page-tools/page-agent-tool.ts +175 -0
- package/page-tools/page-state-cache.ts +78 -0
- package/page-tools/schema.ts +51 -0
- package/page-tools/utils/dom.ts +155 -0
- package/page-tools/utils/scroll.ts +58 -0
- package/runtime.ts +13 -0
- package/utils/builtinProxy.ts +45 -13
- package/vite.config.runtime.ts +22 -0
- package/vite.config.ts +52 -8
- package/McpSdk.ts +0 -14
- package/WebAgent.ts +0 -5
- package/WebMcp.ts +0 -20
- package/Zod.ts +0 -1
- package/dist/AgentModelProvider-CwqOT28f.js +0 -4198
- package/dist/McpSdk.d.ts +0 -14
- package/dist/WebAgent.d.ts +0 -5
- package/dist/WebMcp.d.ts +0 -20
- package/dist/Zod.d.ts +0 -1
- package/dist/index.es.dev.js +0 -53427
- package/dist/index.es.js +0 -42147
- package/dist/index.umd.dev.js +0 -53820
- package/dist/index.umd.js +0 -814
- package/dist/mcpsdk@1.25.3.dev.js +0 -22621
- package/dist/mcpsdk@1.25.3.es.dev.js +0 -22619
- package/dist/mcpsdk@1.25.3.es.js +0 -16802
- package/dist/mcpsdk@1.25.3.js +0 -43
- package/dist/transport/ExtensionClientTransport.d.ts +0 -24
- package/dist/transport/ExtensionContentServerTransport.d.ts +0 -39
- package/dist/transport/ExtensionPageServerTransport.d.ts +0 -36
- package/dist/transport/messages.d.ts +0 -9
- package/dist/vite.config.mcpSdk.d.ts +0 -2
- package/dist/vite.config.webAgent.d.ts +0 -2
- package/dist/vite.config.webMcp.d.ts +0 -2
- package/dist/vite.config.webMcpFull.d.ts +0 -2
- package/dist/vite.config.zod.d.ts +0 -2
- package/dist/webagent.dev.js +0 -47597
- package/dist/webagent.es.dev.js +0 -47204
- package/dist/webagent.es.js +0 -37494
- package/dist/webagent.js +0 -634
- package/dist/webmcp-full.dev.js +0 -24430
- package/dist/webmcp-full.es.dev.js +0 -24426
- package/dist/webmcp-full.es.js +0 -18333
- package/dist/webmcp-full.js +0 -43
- package/dist/webmcp.dev.js +0 -1595
- package/dist/webmcp.es.dev.js +0 -1589
- package/dist/webmcp.es.js +0 -1406
- package/dist/webmcp.js +0 -1
- package/dist/zod@3.25.76.dev.js +0 -4037
- package/dist/zod@3.25.76.es.dev.js +0 -4033
- package/dist/zod@3.25.76.es.js +0 -2945
- package/dist/zod@3.25.76.js +0 -1
- package/transport/ExtensionClientTransport.ts +0 -100
- package/transport/ExtensionContentServerTransport.ts +0 -162
- package/transport/ExtensionPageServerTransport.ts +0 -149
- package/transport/messages.ts +0 -63
- package/vite-build-tsc.ts +0 -63
- package/vite-env.d.ts +0 -5
- package/vite.config.mcpSdk.ts +0 -28
- package/vite.config.webAgent.ts +0 -19
- package/vite.config.webMcp.ts +0 -40
- package/vite.config.webMcpFull.ts +0 -19
- package/vite.config.zod.ts +0 -23
- /package/dist/{vite-build-tsc.d.ts → vite.config.runtime.d.ts} +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { initializeBuiltinWebMCP } from './initialize-builtin-WebMCP'
|
|
2
|
+
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
3
|
+
import pageAgentPrompt from './page-agent-prompt.md?raw'
|
|
4
|
+
import { PageController } from '@page-agent/page-controller'
|
|
5
|
+
import { buildA11yTree, type RefMap } from './a11y-tree'
|
|
6
|
+
import { PageStateCache } from './page-state-cache'
|
|
7
|
+
import { SimulatorMask } from './page-agent-mask/SimulatorMask'
|
|
8
|
+
import { highlight, unhighlight, globalRemoveListener } from './page-agent-highlight'
|
|
9
|
+
|
|
10
|
+
import { DEFAULT_ERROR_SELECTORS, DEFAULT_DIALOG_SELECTORS, type PageAgentToolOptions } from './constants'
|
|
11
|
+
import { inputSchema, type PageAgentToolInput } from './schema'
|
|
12
|
+
import type { ActionContext } from './context'
|
|
13
|
+
import { detectPageDialog, detectValidationErrors } from './utils/dom'
|
|
14
|
+
|
|
15
|
+
import { handleBrowserState } from './handlers/browserState'
|
|
16
|
+
import { handleClick } from './handlers/click'
|
|
17
|
+
import { handleFill } from './handlers/fill'
|
|
18
|
+
import { handleSelect } from './handlers/select'
|
|
19
|
+
import { handleScroll } from './handlers/scroll'
|
|
20
|
+
import { handleExecuteJavascript } from './handlers/executeJavascript'
|
|
21
|
+
import { handleSearchTree } from './handlers/searchTree'
|
|
22
|
+
|
|
23
|
+
/** 在浏览器页面中注册 page-agent-tool, 用于页面的内容获取和操作,页面的动效 */
|
|
24
|
+
export function registerPageAgentTool(options: PageAgentToolOptions = {}) {
|
|
25
|
+
initializeBuiltinWebMCP()
|
|
26
|
+
|
|
27
|
+
// 默认启用元素高亮
|
|
28
|
+
if (typeof options.enableHighlight === 'undefined') {
|
|
29
|
+
options.enableHighlight = true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.__webmcpcli_interactiveWhitelist = window.__webmcpcli_interactiveWhitelist || [] // 白名单元素列表,存在则识别为交互元素
|
|
33
|
+
window.__webmcpcli_interactiveBlacklist = window.__webmcpcli_interactiveBlacklist || [] // 黑名单,反之
|
|
34
|
+
window.__webmcpcli_exposedAttributes = window.__webmcpcli_exposedAttributes || options?.exposedAttributes || [] // 额外暴露的自定义属性白名单
|
|
35
|
+
window.__webmcpcli_beforeGetBrowserState = window.__webmcpcli_beforeGetBrowserState || null // 指定网站覆盖该函数,用于设置当前网站的黑白名单
|
|
36
|
+
// 校验错误选择器:默认覆盖 ARIA 标准 + 主流框架,网站可通过 window.__webmcpcli_errorSelectors 覆盖
|
|
37
|
+
window.__webmcpcli_errorSelectors = window.__webmcpcli_errorSelectors || DEFAULT_ERROR_SELECTORS
|
|
38
|
+
// 模态弹窗选择器:同上
|
|
39
|
+
window.__webmcpcli_dialogSelectors = window.__webmcpcli_dialogSelectors || DEFAULT_DIALOG_SELECTORS
|
|
40
|
+
|
|
41
|
+
// 保留 PageController ,先关闭内置mask, 再手工绑定当前项目的mask类
|
|
42
|
+
const pageController = new PageController({ enableMask: false })
|
|
43
|
+
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
pageController.maskReady = (async () => {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
pageController.mask = new SimulatorMask()
|
|
48
|
+
})()
|
|
49
|
+
|
|
50
|
+
// ─── 状态 Diff 缓存
|
|
51
|
+
const stateCache = new PageStateCache()
|
|
52
|
+
|
|
53
|
+
// 当前 ref 索引 → HTMLElement 映射(每次 buildA11yTree 后更新)
|
|
54
|
+
let currentRefMap: RefMap = new Map()
|
|
55
|
+
|
|
56
|
+
// ─── 辅助:构建错误响应 ───────────────────────────────────────────────────
|
|
57
|
+
async function errContent(msg: string) {
|
|
58
|
+
await pageController.hideMask()
|
|
59
|
+
return { content: [{ type: 'text' as const, text: msg }] }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// AI 使用过期 ref 时,自动重建 A11y 树并返回全量状态,
|
|
63
|
+
// 避免 AI 额外往返调用 browserState,减少操作轮次
|
|
64
|
+
async function refreshOnStaleRef(action: string, index: number) {
|
|
65
|
+
const refreshResult = await buildBrowserStateResponse('full')
|
|
66
|
+
const warning = `⚠️ ${action}失败: ref 索引 ${index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。\n`
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: 'text' as const, text: warning + refreshResult.content[0].text }]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── 核心:构建 browserState 响应(全量 or 增量 Diff)────────────────────
|
|
73
|
+
async function buildBrowserStateResponse(
|
|
74
|
+
responseMode: 'full' | 'diff' | 'both' = 'diff'
|
|
75
|
+
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
|
|
76
|
+
const url = window.location.href
|
|
77
|
+
const title = document.title
|
|
78
|
+
|
|
79
|
+
// 获取用户自定义黑名单与白名单及额外暴露的属性
|
|
80
|
+
const blacklist = (window.__webmcpcli_interactiveBlacklist ?? []) as Element[]
|
|
81
|
+
const whitelist = (window.__webmcpcli_interactiveWhitelist ?? []) as Element[]
|
|
82
|
+
const exposedAttributes = (window.__webmcpcli_exposedAttributes ?? []) as string[]
|
|
83
|
+
|
|
84
|
+
// 生成语义化 ARIA YAML 树 + 刷新 refMap
|
|
85
|
+
const { yaml, refMap } = buildA11yTree(document.body, blacklist, whitelist, {
|
|
86
|
+
exposedAttributes,
|
|
87
|
+
errorSelectors: (window.__webmcpcli_errorSelectors ?? DEFAULT_ERROR_SELECTORS).join(', ')
|
|
88
|
+
})
|
|
89
|
+
currentRefMap = refMap
|
|
90
|
+
|
|
91
|
+
// 高亮交互元素,且增加全局移除高亮的监听
|
|
92
|
+
if (options?.enableHighlight) {
|
|
93
|
+
highlight(refMap)
|
|
94
|
+
globalRemoveListener()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 计算 Diff
|
|
98
|
+
const diff = stateCache.update(url, yaml)
|
|
99
|
+
|
|
100
|
+
await pageController.hideMask()
|
|
101
|
+
|
|
102
|
+
// 检测页面弹窗/遮罩层(确认框、提示框等),让 AI 优先处理
|
|
103
|
+
const dialogAlert = detectPageDialog()
|
|
104
|
+
// 检测表单校验错误,提醒 AI 优先修复
|
|
105
|
+
const validationErrors = detectValidationErrors()
|
|
106
|
+
|
|
107
|
+
// 根据 responseMode 组装 content
|
|
108
|
+
let displayContent = ''
|
|
109
|
+
if (responseMode === 'full') {
|
|
110
|
+
displayContent = yaml
|
|
111
|
+
} else if (responseMode === 'diff') {
|
|
112
|
+
displayContent = diff.isFullRefresh ? yaml : diff.diffText
|
|
113
|
+
} else if (responseMode === 'both') {
|
|
114
|
+
displayContent = `【全量页面树】:\n${yaml}\n\n【增量差异】:\n${diff.isFullRefresh ? '(首次/刷新,无增量差异)' : diff.diffText}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 拼装 JSON 格式状态,与 webmcp-cli 的 state 提取逻辑对齐
|
|
118
|
+
const stateObj = {
|
|
119
|
+
url,
|
|
120
|
+
title,
|
|
121
|
+
content: displayContent
|
|
122
|
+
}
|
|
123
|
+
const text = `浏览器状态: ${JSON.stringify(stateObj)}${dialogAlert}${validationErrors}`
|
|
124
|
+
return { content: [{ type: 'text', text }] }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 组装上下文传递给 handlers
|
|
128
|
+
const actionContext: ActionContext = {
|
|
129
|
+
pageController,
|
|
130
|
+
stateCache,
|
|
131
|
+
getRefMap: () => currentRefMap,
|
|
132
|
+
setRefMap: (map: RefMap) => {
|
|
133
|
+
currentRefMap = map
|
|
134
|
+
},
|
|
135
|
+
buildBrowserStateResponse,
|
|
136
|
+
refreshOnStaleRef,
|
|
137
|
+
errContent
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── 工具注册(名称与 inputSchema 与原版完全一致)────────────────────────
|
|
141
|
+
;(document as any).modelContext.registerTool({
|
|
142
|
+
name: 'page-agent-tool',
|
|
143
|
+
description: pageAgentPrompt,
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
inputSchema: zodToJsonSchema(inputSchema) as any,
|
|
146
|
+
async execute(args: PageAgentToolInput) {
|
|
147
|
+
await pageController.showMask()
|
|
148
|
+
try {
|
|
149
|
+
switch (args.action) {
|
|
150
|
+
case 'browserState':
|
|
151
|
+
return await handleBrowserState(args, actionContext)
|
|
152
|
+
case 'click':
|
|
153
|
+
return await handleClick(args, actionContext)
|
|
154
|
+
case 'fill':
|
|
155
|
+
return await handleFill(args, actionContext)
|
|
156
|
+
case 'select':
|
|
157
|
+
return await handleSelect(args, actionContext)
|
|
158
|
+
case 'scroll':
|
|
159
|
+
return await handleScroll(args, actionContext)
|
|
160
|
+
case 'executeJavascript':
|
|
161
|
+
return await handleExecuteJavascript(args, actionContext)
|
|
162
|
+
case 'searchTree':
|
|
163
|
+
return await handleSearchTree(args, actionContext)
|
|
164
|
+
default:
|
|
165
|
+
await pageController.hideMask()
|
|
166
|
+
// @ts-ignore
|
|
167
|
+
return { content: [{ type: 'text', text: `未知操作: ${args.action}` }] }
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
await pageController.hideMask()
|
|
171
|
+
return { content: [{ type: 'text', text: `异常: ${String(error)}` }] }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* page-state-cache.ts
|
|
3
|
+
*
|
|
4
|
+
* 页面状态 Diff 缓存模块
|
|
5
|
+
* - 缓存上一次 buildA11yTree 生成的 YAML 文本
|
|
6
|
+
* - 每次操作后计算 diff,只返回 +/- 变化行,大幅节省 Token
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { diffLines } from 'diff'
|
|
10
|
+
|
|
11
|
+
export interface PageSnapshot {
|
|
12
|
+
url: string
|
|
13
|
+
yaml: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DiffResult {
|
|
17
|
+
/** 是否为全量刷新(首次加载或 URL 变化) */
|
|
18
|
+
isFullRefresh: boolean
|
|
19
|
+
/** 上一次的 URL(用于拼接 URL 变化提示) */
|
|
20
|
+
prevUrl: string
|
|
21
|
+
/** 新增行数 */
|
|
22
|
+
addedLines: number
|
|
23
|
+
/** 删除行数 */
|
|
24
|
+
removedLines: number
|
|
25
|
+
/** 只含 +/- 行的 diff 文本(不含相同行,节省 Token) */
|
|
26
|
+
diffText: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PageStateCache {
|
|
30
|
+
private prev: PageSnapshot | null = null
|
|
31
|
+
|
|
32
|
+
/** URL 变化或首次调用时,需要全量输出 */
|
|
33
|
+
isFullRefresh(currentUrl: string): boolean {
|
|
34
|
+
return !this.prev || this.prev.url !== currentUrl
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 更新缓存并返回 diff 结果
|
|
39
|
+
* @param url 当前页面 URL
|
|
40
|
+
* @param yaml 当前 YAML 树文本
|
|
41
|
+
*/
|
|
42
|
+
update(url: string, yaml: string): DiffResult {
|
|
43
|
+
const isFullRefresh = this.isFullRefresh(url)
|
|
44
|
+
const prevUrl = this.prev?.url ?? ''
|
|
45
|
+
|
|
46
|
+
let addedLines = 0
|
|
47
|
+
let removedLines = 0
|
|
48
|
+
let diffText = ''
|
|
49
|
+
|
|
50
|
+
if (!isFullRefresh && this.prev) {
|
|
51
|
+
const changes = diffLines(this.prev.yaml, yaml)
|
|
52
|
+
const buf: string[] = []
|
|
53
|
+
changes.forEach(part => {
|
|
54
|
+
// 跳过相同行,只保留变化行,大幅节省传给 AI 的 Token 数量
|
|
55
|
+
if (!part.added && !part.removed) return
|
|
56
|
+
const lines = part.value.split('\n').filter(Boolean)
|
|
57
|
+
if (part.added) {
|
|
58
|
+
addedLines += lines.length
|
|
59
|
+
lines.forEach(l => buf.push(`+ ${l}`))
|
|
60
|
+
} else {
|
|
61
|
+
removedLines += lines.length
|
|
62
|
+
lines.forEach(l => buf.push(`- ${l}`))
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
diffText = buf.join('\n')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 更新缓存
|
|
69
|
+
this.prev = { url, yaml }
|
|
70
|
+
|
|
71
|
+
return { isFullRefresh, prevUrl, addedLines, removedLines, diffText }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 强制清空缓存(页面刷新 / 手动重置场景) */
|
|
75
|
+
invalidate(): void {
|
|
76
|
+
this.prev = null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const inputSchema = z.object({
|
|
4
|
+
action: z.enum(['browserState', 'click', 'fill', 'select', 'scroll', 'executeJavascript', 'searchTree'] as const)
|
|
5
|
+
.describe(`执行的动作名称, 每一次执行 'click', 'fill', 'select'动作之前,**必须**要先调用 'browserState' 去获取页面的最新状态。
|
|
6
|
+
browserState: '查询整个页面的浏览器状态;返回页面的标题、URL、YAML格式的语义化页面树',
|
|
7
|
+
click: '根据元素索引点击;',
|
|
8
|
+
fill: '根据元素索引填写文本;';
|
|
9
|
+
select: '根据元素索引选择下拉框选项;';
|
|
10
|
+
scroll: '滚动页面的动作,可以指定水平滚动还是上下滚动; 不带元素索引时:滚动整个文档。带元素索引时:滚动该索引处的容器(或其最近的可滚动祖先元素)'
|
|
11
|
+
executeJavascript: '执行javascript代码'
|
|
12
|
+
searchTree: '按关键词搜索无障碍树,返回带行号的匹配节点及上下文,无需获取全量树。适合快速定位特定元素(如所有按钮、特定名称的链接等),显著减少上下文消耗。必须提供 query 参数。'
|
|
13
|
+
`),
|
|
14
|
+
index: z
|
|
15
|
+
.number()
|
|
16
|
+
.min(0)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('执行动作 of the element index, 动作为 click,fill,select时,必须提供元素索引'),
|
|
19
|
+
text: z.string().optional().describe('执行动作的文本内容, 动作为 fill,select 时,必须提供文本内容'),
|
|
20
|
+
down: z.boolean().optional().describe('执行上下滚动时,必须提供down参数'),
|
|
21
|
+
right: z.boolean().optional().describe('执行水平滚动方向, 必须提供right参数'),
|
|
22
|
+
numPages: z
|
|
23
|
+
.number()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('执行动作的滚动页数, 动作为 scroll时,可以提供滚动页数,建议每次滚动0.1页,该值不要大于5.'),
|
|
26
|
+
pixels: z.number().int().min(0).optional().describe('执行动作的滚动像素数,动作为 scroll时,可以提供滚动像素数'),
|
|
27
|
+
script: z.string().optional().describe('执行的javascript代码,动作为 executeJavascript时,必须提供script参数'),
|
|
28
|
+
query: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(
|
|
32
|
+
'搜索关键词,动作为 searchTree 时必须提供。支持按 role(如 button、link)、节点名称、状态(如 checked)或 ref 索引(如 #3)搜索'
|
|
33
|
+
),
|
|
34
|
+
contextLines: z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.min(0)
|
|
38
|
+
.max(10)
|
|
39
|
+
.default(2)
|
|
40
|
+
.describe('searchTree 时每个命中行前后保留的上下文行数,默认 2'),
|
|
41
|
+
maxMatches: z.number().int().min(1).max(50).default(20).describe('searchTree 时最大返回分组数,默认 20'),
|
|
42
|
+
responseMode: z
|
|
43
|
+
.enum(['full', 'diff', 'both'] as const)
|
|
44
|
+
.optional()
|
|
45
|
+
.default('diff')
|
|
46
|
+
.describe(
|
|
47
|
+
'返回浏览器状态的模式。full: 仅返回当前全量 A11y 树;diff: 仅返回与上一次状态的增量差异;both: 同时返回全量 A11y 树与增量差异。默认值为 diff。'
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export type PageAgentToolInput = z.infer<typeof inputSchema>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { DEFAULT_DIALOG_SELECTORS, DEFAULT_ERROR_SELECTORS } from '../constants'
|
|
2
|
+
|
|
3
|
+
// ─── 辅助:Shadow DOM 事件穿透 ───────────────────────────────────────────
|
|
4
|
+
// 外部包 inputTextElement/selectOptionElement 派发的合成事件 composed:false,
|
|
5
|
+
// 无法穿透 shadow boundary,导致事件委托框架(如 React)收不到 shadow 内元素的
|
|
6
|
+
// input/change 事件。这里对 shadow DOM 内元素补发 composed:true 事件。
|
|
7
|
+
export function isInShadowDom(el: Element): boolean {
|
|
8
|
+
return el.getRootNode() instanceof ShadowRoot
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function dispatchComposedEvents(el: Element, ...types: string[]) {
|
|
12
|
+
for (const type of types) {
|
|
13
|
+
el.dispatchEvent(new Event(type, { bubbles: true, composed: true }))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ─── 辅助:穿透 Shadow DOM 的查询 ─────────────────────────────────────────
|
|
18
|
+
export function deepQuerySelectorAll(selector: string, root: Document | Element | ShadowRoot = document): Element[] {
|
|
19
|
+
const elements = Array.from(root.querySelectorAll(selector))
|
|
20
|
+
|
|
21
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null)
|
|
22
|
+
let currentNode: Node | null = walker.currentNode
|
|
23
|
+
while (currentNode) {
|
|
24
|
+
if ((currentNode as Element).shadowRoot) {
|
|
25
|
+
elements.push(...deepQuerySelectorAll(selector, (currentNode as Element).shadowRoot!))
|
|
26
|
+
}
|
|
27
|
+
currentNode = walker.nextNode()
|
|
28
|
+
}
|
|
29
|
+
return elements
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── 辅助:等待 DOM 变更稳定 ────────────────────────────────────────────
|
|
33
|
+
// click/fill/select 操作后,框架(Angular/React/Vue)可能异步插入校验错误、
|
|
34
|
+
// 条件渲染选项等动态内容。用 MutationObserver 监听 DOM 变更,等待变更平息后
|
|
35
|
+
// 再构建 A11y 树,确保动态插入的内容(如 ng-star-inserted 校验提示)被捕获。
|
|
36
|
+
export function waitForDomSettled(timeout = 600, settleTime = 150): Promise<void> {
|
|
37
|
+
return new Promise<void>((resolve) => {
|
|
38
|
+
let settled = false
|
|
39
|
+
let settleTimer: ReturnType<typeof setTimeout> | undefined
|
|
40
|
+
const finish = () => {
|
|
41
|
+
if (settled) return
|
|
42
|
+
settled = true
|
|
43
|
+
observer.disconnect()
|
|
44
|
+
if (settleTimer) clearTimeout(settleTimer)
|
|
45
|
+
resolve()
|
|
46
|
+
}
|
|
47
|
+
// 每次有 DOM 变更时重置 settle 计时器;变更平息 settleTime 后视为稳定
|
|
48
|
+
const observer = new MutationObserver(() => {
|
|
49
|
+
if (settleTimer) clearTimeout(settleTimer)
|
|
50
|
+
settleTimer = setTimeout(finish, settleTime)
|
|
51
|
+
})
|
|
52
|
+
// 若无任何变更,settleTime 后直接结束
|
|
53
|
+
settleTimer = setTimeout(finish, settleTime)
|
|
54
|
+
// 超时保护:timeout 后强制结束,避免无限等待
|
|
55
|
+
setTimeout(finish, timeout)
|
|
56
|
+
observer.observe(document.body, {
|
|
57
|
+
childList: true,
|
|
58
|
+
subtree: true,
|
|
59
|
+
attributes: true,
|
|
60
|
+
attributeFilter: ['class', 'style', 'hidden'],
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── 辅助:检测页面级模态弹窗/遮罩层 ────────────────────────────────────
|
|
66
|
+
// 仅检测真正阻塞交互的模态弹窗(确认框、提交对话框等),
|
|
67
|
+
// 不检测顶部通知横幅、站内消息等非模态提示,避免误报导致 AI 循环
|
|
68
|
+
export function detectPageDialog(): string {
|
|
69
|
+
const seen = new Set<Element>()
|
|
70
|
+
const dialogs: string[] = []
|
|
71
|
+
// 仅匹配明确的模态弹窗选择器,避免宽泛子串匹配误报
|
|
72
|
+
const selectors = (window.__webmcpcli_dialogSelectors ?? DEFAULT_DIALOG_SELECTORS)
|
|
73
|
+
|
|
74
|
+
for (const selector of selectors) {
|
|
75
|
+
try {
|
|
76
|
+
for (const el of deepQuerySelectorAll(selector)) {
|
|
77
|
+
if (seen.has(el)) continue
|
|
78
|
+
// 跳过 SDK 自身遮罩
|
|
79
|
+
if (el.id?.includes('page-agent-runtime')) continue
|
|
80
|
+
if (el.closest('#page-agent-runtime_simulator-mask')) continue
|
|
81
|
+
|
|
82
|
+
const rect = el.getBoundingClientRect()
|
|
83
|
+
if (rect.width < 50 || rect.height < 50) continue
|
|
84
|
+
const style = window.getComputedStyle(el as HTMLElement)
|
|
85
|
+
if (style.display === 'none' || style.visibility === 'hidden') continue
|
|
86
|
+
|
|
87
|
+
// 模态性验证:必须是 fixed/absolute 且 z-index 较高
|
|
88
|
+
const isFixed = style.position === 'fixed' || style.position === 'absolute'
|
|
89
|
+
const zIndex = parseInt(style.zIndex || '0', 10)
|
|
90
|
+
if (!isFixed || zIndex < 100) continue
|
|
91
|
+
|
|
92
|
+
// 必须覆盖视口中心区域(真正的模态弹窗会遮挡页面中心)
|
|
93
|
+
const vw = window.innerWidth
|
|
94
|
+
const vh = window.innerHeight
|
|
95
|
+
const coversCenter = rect.left < vw * 0.6 && rect.right > vw * 0.4 &&
|
|
96
|
+
rect.top < vh * 0.6 && rect.bottom > vh * 0.4
|
|
97
|
+
if (!coversCenter) continue
|
|
98
|
+
|
|
99
|
+
seen.add(el)
|
|
100
|
+
const text = (el.textContent || '').trim().replace(/\s+/g, ' ')
|
|
101
|
+
if (text.length > 5) {
|
|
102
|
+
// 提取弹窗内的可交互按钮,帮助 AI 快速决策
|
|
103
|
+
const btns = el.querySelectorAll('button, [role="button"], a')
|
|
104
|
+
const btnTexts = Array.from(btns)
|
|
105
|
+
.map(b => (b.textContent || '').trim())
|
|
106
|
+
.filter(t => t.length > 0 && t.length < 20)
|
|
107
|
+
.slice(0, 5)
|
|
108
|
+
const btnInfo = btnTexts.length ? ` [可操作按钮: ${btnTexts.join(' / ')}]` : ''
|
|
109
|
+
dialogs.push(`${text.substring(0, 300)}${btnInfo}`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// 忽略选择器异常
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (dialogs.length === 0) return ''
|
|
118
|
+
return `\n[页面弹窗检测] 检测到 ${dialogs.length} 个模态弹窗,请优先处理:\n${dialogs.map((d, i) => `${i + 1}. ${d}`).join('\n')}\n`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── 辅助:检测页面可见的表单校验错误 ──────────────────────────────────
|
|
122
|
+
// 操作后自动扫描页面中的校验错误提示,提取文本并提醒 AI,
|
|
123
|
+
// 避免 AI 卡住时不知道是因为有校验错误未处理
|
|
124
|
+
export function detectValidationErrors(): string {
|
|
125
|
+
const seen = new Set<Element>()
|
|
126
|
+
const errors: string[] = []
|
|
127
|
+
// 校验错误选择器:ARIA 标准 + 主流 UI 框架(可配置)
|
|
128
|
+
const selectors = (window.__webmcpcli_errorSelectors ?? DEFAULT_ERROR_SELECTORS)
|
|
129
|
+
|
|
130
|
+
for (const selector of selectors) {
|
|
131
|
+
try {
|
|
132
|
+
for (const el of deepQuerySelectorAll(selector)) {
|
|
133
|
+
if (seen.has(el)) continue
|
|
134
|
+
// 跳过嵌套在已收集的错误元素内的子元素,避免重复
|
|
135
|
+
if (Array.from(seen).some(s => s.contains(el))) continue
|
|
136
|
+
|
|
137
|
+
const rect = el.getBoundingClientRect()
|
|
138
|
+
if (rect.width < 1 || rect.height < 1) continue
|
|
139
|
+
const style = window.getComputedStyle(el as HTMLElement)
|
|
140
|
+
if (style.display === 'none' || style.visibility === 'hidden') continue
|
|
141
|
+
|
|
142
|
+
const text = (el.textContent || '').trim().replace(/\s+/g, ' ')
|
|
143
|
+
if (text.length > 2 && text.length < 200) {
|
|
144
|
+
seen.add(el)
|
|
145
|
+
errors.push(text)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// 忽略选择器异常
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (errors.length === 0) return ''
|
|
154
|
+
return `\n[校验提示] 检测到 ${errors.length} 个表单校验错误,请先修复后再继续:\n${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}\n`
|
|
155
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// ─── 辅助:获取滚动目标的当前位置信息(仿 page-agent getPageInfo)────────
|
|
2
|
+
// 同时支持 window(文档滚动)和任意 Element(容器滚动)
|
|
3
|
+
export function getScrollInfo(target: Window | Element = window) {
|
|
4
|
+
if (target === window) {
|
|
5
|
+
const pageHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)
|
|
6
|
+
const pageWidth = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth)
|
|
7
|
+
const scrollY = window.scrollY
|
|
8
|
+
const scrollX = window.scrollX
|
|
9
|
+
const pixelsBelow = Math.max(0, pageHeight - (window.innerHeight + scrollY))
|
|
10
|
+
const pixelsRight = Math.max(0, pageWidth - (window.innerWidth + scrollX))
|
|
11
|
+
return {
|
|
12
|
+
scrollY, scrollX,
|
|
13
|
+
pixelsAbove: scrollY, pixelsBelow,
|
|
14
|
+
pixelsLeft: scrollX, pixelsRight,
|
|
15
|
+
pagesAbove: window.innerHeight > 0 ? scrollY / window.innerHeight : 0,
|
|
16
|
+
pagesBelow: window.innerHeight > 0 ? pixelsBelow / window.innerHeight : 0,
|
|
17
|
+
atTop: scrollY <= 1,
|
|
18
|
+
atBottom: pixelsBelow <= 1,
|
|
19
|
+
atLeft: scrollX <= 1,
|
|
20
|
+
atRight: pixelsRight <= 1,
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
const el = target as Element
|
|
24
|
+
const scrollTop = el.scrollTop
|
|
25
|
+
const scrollLeft = el.scrollLeft
|
|
26
|
+
const pixelsBelow = el.scrollHeight - el.clientHeight - scrollTop
|
|
27
|
+
const pixelsRight = el.scrollWidth - el.clientWidth - scrollLeft
|
|
28
|
+
return {
|
|
29
|
+
scrollY: scrollTop, scrollX: scrollLeft,
|
|
30
|
+
pixelsAbove: scrollTop, pixelsBelow: Math.max(0, pixelsBelow),
|
|
31
|
+
pixelsLeft: scrollLeft, pixelsRight: Math.max(0, pixelsRight),
|
|
32
|
+
pagesAbove: el.clientHeight > 0 ? scrollTop / el.clientHeight : 0,
|
|
33
|
+
pagesBelow: el.clientHeight > 0 ? Math.max(0, pixelsBelow) / el.clientHeight : 0,
|
|
34
|
+
atTop: scrollTop <= 1,
|
|
35
|
+
atBottom: pixelsBelow <= 1,
|
|
36
|
+
atLeft: scrollLeft <= 1,
|
|
37
|
+
atRight: pixelsRight <= 1,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 等待 smooth scroll 结束:优先监听 scrollend,超时兜底(边界无位移时 scrollend 可能不触发)
|
|
43
|
+
export function waitForScrollEnd(target: Window | Element, timeout = 2000): Promise<void> {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
let settled = false
|
|
46
|
+
const el = target === window ? document : (target as Element)
|
|
47
|
+
const finish = () => {
|
|
48
|
+
if (settled) return
|
|
49
|
+
settled = true
|
|
50
|
+
el.removeEventListener('scrollend', onScrollEnd)
|
|
51
|
+
clearTimeout(timer)
|
|
52
|
+
resolve()
|
|
53
|
+
}
|
|
54
|
+
const onScrollEnd = () => finish()
|
|
55
|
+
const timer = setTimeout(finish, timeout)
|
|
56
|
+
el.addEventListener('scrollend', onScrollEnd, { once: true })
|
|
57
|
+
})
|
|
58
|
+
}
|
package/runtime.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { initializeBuiltinWebMCP } from './page-tools/initialize-builtin-WebMCP'
|
|
2
|
+
import { registerPageAgentTool } from './page-tools/page-agent-tool'
|
|
3
|
+
|
|
4
|
+
if (typeof window !== 'undefined') {
|
|
5
|
+
;(window as any).initializeBuiltinWebMCP = initializeBuiltinWebMCP
|
|
6
|
+
;(window as any).registerPageAgentTool = registerPageAgentTool
|
|
7
|
+
|
|
8
|
+
// 作为 IIFE 注入到页面 MAIN world 时自动注册 page-agent-tool
|
|
9
|
+
// 避免 content script 需要额外触发(会被 CSP 内联 script 限制拦截)
|
|
10
|
+
registerPageAgentTool()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { initializeBuiltinWebMCP, registerPageAgentTool }
|
package/utils/builtinProxy.ts
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
2
2
|
|
|
3
|
+
/** 浏览器内置 WebMCP 原生上下文接口 */
|
|
4
|
+
interface NativeModelContext {
|
|
5
|
+
getTools(): Promise<NativeTool[]> | NativeTool[]
|
|
6
|
+
executeTool(tool: NativeTool, argsStr: string): Promise<unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** 原生工具描述 */
|
|
10
|
+
interface NativeTool {
|
|
11
|
+
name: string
|
|
12
|
+
description?: string
|
|
13
|
+
title?: string
|
|
14
|
+
inputSchema?: unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** JSON-RPC 消息基础结构 */
|
|
18
|
+
interface JsonRpcMessage {
|
|
19
|
+
jsonrpc: string
|
|
20
|
+
id?: string | number
|
|
21
|
+
method?: string
|
|
22
|
+
params?: {
|
|
23
|
+
name?: string
|
|
24
|
+
arguments?: Record<string, unknown>
|
|
25
|
+
[key: string]: unknown
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
3
29
|
/**
|
|
4
30
|
* 建立原生的 JSON-RPC 拦截层:
|
|
5
|
-
* 将发向 remote 的 MCP 请求拦截,代理给所在浏览器的
|
|
31
|
+
* 将发向 remote 的 MCP 请求拦截,代理给所在浏览器的 document.modelContext 上执行。
|
|
6
32
|
*/
|
|
7
33
|
export const setupBuiltinProxy = (transport: Transport) => {
|
|
8
|
-
const getNativeCtx = () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
34
|
+
const getNativeCtx = (): NativeModelContext | null => {
|
|
35
|
+
let ctx: NativeModelContext | null = null
|
|
36
|
+
if (typeof document !== 'undefined') ctx = ctx || ((document as unknown as Record<string, unknown>).modelContext as NativeModelContext | null)
|
|
37
|
+
if (typeof navigator !== 'undefined') ctx = ctx || ((navigator as unknown as Record<string, unknown>).modelContext as NativeModelContext | null)
|
|
38
|
+
ctx = ctx || ((globalThis as unknown as Record<string, unknown>).modelContext as NativeModelContext | null)
|
|
39
|
+
return ctx || null
|
|
12
40
|
}
|
|
13
41
|
|
|
14
|
-
transport.onmessage = async (message:
|
|
42
|
+
transport.onmessage = async (message: JsonRpcMessage) => {
|
|
15
43
|
if (!message || typeof message !== 'object') return
|
|
16
44
|
const id = message.id
|
|
17
45
|
const method = message.method
|
|
@@ -36,8 +64,8 @@ export const setupBuiltinProxy = (transport: Transport) => {
|
|
|
36
64
|
await transport.send({ jsonrpc: '2.0', id, result: {} })
|
|
37
65
|
} else if (method === 'tools/list') {
|
|
38
66
|
const nativeCtx = getNativeCtx()
|
|
39
|
-
if (nativeCtx && nativeCtx.
|
|
40
|
-
const rawTools = await nativeCtx.
|
|
67
|
+
if (nativeCtx && nativeCtx.getTools) {
|
|
68
|
+
const rawTools = await nativeCtx.getTools()
|
|
41
69
|
const tools = rawTools.map((t: any) => {
|
|
42
70
|
let schemaObj: any = {}
|
|
43
71
|
if (typeof t.inputSchema === 'string') {
|
|
@@ -67,19 +95,22 @@ export const setupBuiltinProxy = (transport: Transport) => {
|
|
|
67
95
|
const nativeCtx = getNativeCtx()
|
|
68
96
|
if (nativeCtx && nativeCtx.executeTool) {
|
|
69
97
|
if (!message.params || typeof message.params !== 'object' || !message.params.name) {
|
|
70
|
-
const error:
|
|
98
|
+
const error: Error & { code?: number } = new Error('Invalid params: "name" is required and params must be an object')
|
|
71
99
|
error.code = -32602 // Invalid params
|
|
72
100
|
throw error
|
|
73
101
|
}
|
|
74
102
|
const { name, arguments: args } = message.params
|
|
75
|
-
const
|
|
103
|
+
const tools = await (nativeCtx.getTools ? nativeCtx.getTools() : [])
|
|
104
|
+
const toolObj = tools.find((t: any) => t.name === name)
|
|
105
|
+
if (!toolObj) throw new Error(`Tool ${name} not found`)
|
|
106
|
+
const result = await nativeCtx.executeTool(toolObj, JSON.stringify(args || {}))
|
|
76
107
|
const finalResult =
|
|
77
108
|
result && typeof result === 'object' && 'content' in result
|
|
78
109
|
? result
|
|
79
110
|
: { content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result) }] }
|
|
80
111
|
await transport.send({ jsonrpc: '2.0', id, result: finalResult })
|
|
81
112
|
} else {
|
|
82
|
-
const error:
|
|
113
|
+
const error: Error & { code?: number } = new Error('executeTool not implemented in Browser built-in WebMCP')
|
|
83
114
|
error.code = -32601 // Method not found
|
|
84
115
|
throw error
|
|
85
116
|
}
|
|
@@ -99,12 +130,13 @@ export const setupBuiltinProxy = (transport: Transport) => {
|
|
|
99
130
|
error: { code: -32601, message: `Method not found: ${method}` }
|
|
100
131
|
})
|
|
101
132
|
}
|
|
102
|
-
} catch (err:
|
|
133
|
+
} catch (err: unknown) {
|
|
103
134
|
if (id !== undefined) {
|
|
135
|
+
const errObj = err as { code?: number; message?: string }
|
|
104
136
|
await transport.send({
|
|
105
137
|
jsonrpc: '2.0',
|
|
106
138
|
id,
|
|
107
|
-
error: { code:
|
|
139
|
+
error: { code: errObj.code || -32000, message: errObj.message || String(err) }
|
|
108
140
|
})
|
|
109
141
|
}
|
|
110
142
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
|
|
3
|
+
export default defineConfig(() => {
|
|
4
|
+
return {
|
|
5
|
+
define: {
|
|
6
|
+
'process.env.NODE_ENV': JSON.stringify('production')
|
|
7
|
+
},
|
|
8
|
+
build: {
|
|
9
|
+
emptyOutDir: false,
|
|
10
|
+
lib: {
|
|
11
|
+
entry: 'runtime.ts',
|
|
12
|
+
name: 'NextSDK',
|
|
13
|
+
formats: ['iife'] as any,
|
|
14
|
+
fileName: () => 'runtime.js'
|
|
15
|
+
},
|
|
16
|
+
rollupOptions: {
|
|
17
|
+
// 全量打包第三方依赖,便于浏览器中直接注入或运行
|
|
18
|
+
external: []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|