@opentiny/next-sdk 0.4.1 → 0.4.3
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/{SimulatorMask-BHVXyogh-iNUb4dyF.js → SimulatorMask-BHVXyogh-CKmEDsYG.js} +1 -1
- package/dist/core.js +1 -1
- package/dist/{index-CXK8CcKq.js → index-CHMa2zG4.js} +1985 -1434
- package/dist/index.d.ts +11 -0
- package/dist/index.js +69 -50
- package/dist/{initialize-builtin-WebMCP-rALfqS-o.js → initialize-builtin-WebMCP-JaoKwVlm.js} +355 -346
- package/dist/page-tools/a11y/build.d.ts +3 -4
- package/dist/page-tools/a11y/config.d.ts +99 -0
- package/dist/page-tools/a11y/constants.d.ts +22 -0
- package/dist/page-tools/a11y/search.d.ts +1 -1
- package/dist/page-tools/a11y/types.d.ts +13 -22
- package/dist/page-tools/a11y/utils.d.ts +46 -11
- package/dist/page-tools/a11y/vnode.d.ts +14 -11
- package/dist/page-tools/a11y-tree.d.ts +1 -0
- package/dist/page-tools/configs/console-cloud.d.ts +6 -0
- package/dist/page-tools/constants.d.ts +7 -17
- package/dist/page-tools/context.d.ts +14 -0
- package/dist/page-tools/handlers/hover.d.ts +9 -0
- package/dist/page-tools/handlers/searchTree.d.ts +2 -1
- package/dist/page-tools/page-agent-highlight/index.d.ts +12 -0
- package/dist/page-tools/page-agent-mask/SimulatorMask.d.ts +2 -0
- package/dist/page-tools/page-agent-tool-event.d.ts +26 -0
- package/dist/page-tools/schema.d.ts +4 -3
- package/dist/page-tools/tool-config.d.ts +50 -0
- package/dist/page-tools/utils/dom.d.ts +8 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +22 -187
- package/dist/vitest.config.d.ts +2 -0
- package/package.json +26 -16
- package/WebMcpClient.ts +0 -505
- package/WebMcpServer.ts +0 -447
- package/agent/AgentModelProvider.ts +0 -932
- package/agent/type.ts +0 -91
- package/agent/utils/generateReActPrompt.ts +0 -71
- package/agent/utils/getAISDKTools.ts +0 -34
- package/agent/utils/getBuiltinMcpTools.ts +0 -86
- package/agent/utils/parseReActAction.ts +0 -34
- package/core.ts +0 -26
- package/index.ts +0 -56
- package/page-tools/a11y/build.ts +0 -70
- package/page-tools/a11y/constants.ts +0 -110
- package/page-tools/a11y/search.ts +0 -129
- package/page-tools/a11y/types.ts +0 -108
- package/page-tools/a11y/utils.ts +0 -266
- package/page-tools/a11y/vnode.ts +0 -229
- package/page-tools/a11y-tree.ts +0 -13
- package/page-tools/bridge.ts +0 -702
- package/page-tools/constants.ts +0 -72
- package/page-tools/context.ts +0 -13
- package/page-tools/effects.ts +0 -345
- package/page-tools/handlers/browserState.ts +0 -12
- package/page-tools/handlers/click.ts +0 -26
- package/page-tools/handlers/executeJavascript.ts +0 -22
- package/page-tools/handlers/fill.ts +0 -60
- package/page-tools/handlers/scroll.ts +0 -66
- package/page-tools/handlers/searchTree.ts +0 -30
- package/page-tools/handlers/select.ts +0 -34
- package/page-tools/initialize-builtin-WebMCP.ts +0 -20
- package/page-tools/page-agent-highlight/index.ts +0 -198
- package/page-tools/page-agent-mask/SimulatorMask.module.css +0 -14
- package/page-tools/page-agent-mask/SimulatorMask.ts +0 -279
- package/page-tools/page-agent-mask/checkDarkMode.ts +0 -181
- package/page-tools/page-agent-mask/cursor-border.svg +0 -3
- package/page-tools/page-agent-mask/cursor-fill.svg +0 -5
- package/page-tools/page-agent-mask/cursor.module.css +0 -70
- package/page-tools/page-agent-prompt.md +0 -139
- package/page-tools/page-agent-tool.ts +0 -175
- package/page-tools/page-state-cache.ts +0 -78
- package/page-tools/schema.ts +0 -51
- package/page-tools/utils/dom.ts +0 -155
- package/page-tools/utils/scroll.ts +0 -58
- package/remoter/QrCode.ts +0 -54
- package/remoter/createRemoter.ts +0 -957
- package/remoter/svgs/chat.svg +0 -43
- package/remoter/svgs/icon-copy.svg +0 -25
- package/remoter/svgs/link.svg +0 -28
- package/remoter/svgs/logo.svg +0 -45
- package/remoter/svgs/qrcode.svg +0 -35
- package/remoter/svgs/scan.svg +0 -21
- package/remoter/tooltips.ts +0 -260
- package/runtime.html +0 -39
- package/runtime.ts +0 -13
- package/script/utils.ts +0 -26
- package/skills/index.ts +0 -287
- package/tsconfig.json +0 -16
- package/utils/builtinProxy.ts +0 -144
- package/utils/env.ts +0 -13
- package/utils/uuid.ts +0 -10
- package/vite.config.runtime.ts +0 -22
- package/vite.config.ts +0 -63
package/skills/index.ts
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web 端 Skill 公共能力模块(next-sdk)
|
|
3
|
-
* - 提供解析、概况、systemPrompt 拼接、按路径/名称查文档
|
|
4
|
-
* - 提供 createSkillTools:供 remoter 注入 get_skill_content 工具,大模型可按需加载技能文档
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { tool } from 'ai'
|
|
8
|
-
import { z } from 'zod'
|
|
9
|
-
|
|
10
|
-
/** 主 SKILL.md 路径格式:仅匹配一级子目录下的 SKILL.md,如 ./calculator/SKILL.md */
|
|
11
|
-
const MAIN_SKILL_PATH_REG = /^\.\/[^/]+\/SKILL\.md$/
|
|
12
|
-
|
|
13
|
-
/** 从 front matter 中提取 name 和 description 的正则(--- 与 --- 之间) */
|
|
14
|
-
const FRONT_MATTER_BLOCK_REG = /^---\s*\n([\s\S]+?)\s*\n---/
|
|
15
|
-
|
|
16
|
-
/** 单个技能的概况信息(从主 SKILL.md 的 front matter 提取) */
|
|
17
|
-
export interface SkillMeta {
|
|
18
|
-
/** 技能名称,与 skill 目录名一致 */
|
|
19
|
-
name: string
|
|
20
|
-
/** 技能描述,用于 systemPrompt */
|
|
21
|
-
description: string
|
|
22
|
-
/** 主 SKILL.md 相对路径,如 ./calculator/SKILL.md */
|
|
23
|
-
path: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 从主 SKILL.md 的 YAML front matter 中用正则提取 name、description
|
|
28
|
-
*/
|
|
29
|
-
export async function parseSkillFrontMatter(
|
|
30
|
-
content: string | (() => Promise<string>)
|
|
31
|
-
): Promise<{ name: string; description: string } | null> {
|
|
32
|
-
if (typeof content !== 'string' && typeof content !== 'function') return null
|
|
33
|
-
|
|
34
|
-
// 先提取 --- 之间的文本块
|
|
35
|
-
const realContent = typeof content === 'string' ? content : await content()
|
|
36
|
-
const blockMatch = realContent.match(FRONT_MATTER_BLOCK_REG)
|
|
37
|
-
|
|
38
|
-
if (!blockMatch?.[1]) return null
|
|
39
|
-
const block = blockMatch[1]
|
|
40
|
-
|
|
41
|
-
// 分别匹配 name 和 description 字段(支持任意顺序)
|
|
42
|
-
const nameMatch = block.match(/^name:\s*(.+)$/m)
|
|
43
|
-
const descMatch = block.match(/^description:\s*(.+)$/m)
|
|
44
|
-
|
|
45
|
-
const name = nameMatch?.[1]?.trim()
|
|
46
|
-
const description = descMatch?.[1]?.trim()
|
|
47
|
-
|
|
48
|
-
return name && description ? { name, description } : null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 将 Vite import.meta.glob 得到的多种 key 格式统一为「相对 skills 根目录」的路径(如 ./calculator/SKILL.md),
|
|
53
|
-
* 以便 getSkillMdContent / getMainSkillPathByName 等能正确按 path 查找。
|
|
54
|
-
* 兼容任意引入位置:./skills/xxx、../skills/xxx、src/skills/xxx 等,取最后一个 skills/ 后的部分并加上 ./
|
|
55
|
-
*/
|
|
56
|
-
async function normalizeSkillModuleKeys(
|
|
57
|
-
modules: Record<string, string | (() => Promise<string>)>
|
|
58
|
-
): Promise<Record<string, string>> {
|
|
59
|
-
const result: Record<string, string> = {}
|
|
60
|
-
for (const [key, content] of Object.entries(modules)) {
|
|
61
|
-
const normalizedKey = key.replace(/\\/g, '/')
|
|
62
|
-
const skillsIndex = normalizedKey.lastIndexOf('skills/')
|
|
63
|
-
const relativePath = skillsIndex >= 0 ? normalizedKey.slice(skillsIndex + 7) : normalizedKey
|
|
64
|
-
const standardPath = relativePath.startsWith('./') ? relativePath : `./${relativePath}`
|
|
65
|
-
const realContent = typeof content === 'string' ? content : await content()
|
|
66
|
-
result[standardPath] = realContent
|
|
67
|
-
}
|
|
68
|
-
return result
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 获取所有「主 SKILL.md」的路径(一级子目录下的 SKILL.md)
|
|
73
|
-
* - 对传入的 modules 先做 normalize,兼容任意 import.meta.glob 写法
|
|
74
|
-
*/
|
|
75
|
-
export async function getMainSkillPaths(modules: Record<string, string | (() => Promise<string>)>): Promise<string[]> {
|
|
76
|
-
const normalized = await normalizeSkillModuleKeys(modules)
|
|
77
|
-
return Object.keys(normalized).filter((path) => MAIN_SKILL_PATH_REG.test(path))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 获取所有技能的概况列表(name、description、path),用于 systemPrompt 或列表展示
|
|
82
|
-
* - 内部统一对 modules 做 normalize,避免调用方关心路径细节
|
|
83
|
-
*/
|
|
84
|
-
export async function getSkillOverviews(
|
|
85
|
-
modules: Record<string, string | (() => Promise<string>)>
|
|
86
|
-
): Promise<SkillMeta[]> {
|
|
87
|
-
const normalized = await normalizeSkillModuleKeys(modules)
|
|
88
|
-
const mainPaths = Object.keys(normalized).filter((path) => MAIN_SKILL_PATH_REG.test(path))
|
|
89
|
-
const list: SkillMeta[] = []
|
|
90
|
-
for (const path of mainPaths) {
|
|
91
|
-
const content = normalized[path]
|
|
92
|
-
if (!content) continue
|
|
93
|
-
const parsed = await parseSkillFrontMatter(content)
|
|
94
|
-
if (!parsed) continue
|
|
95
|
-
list.push({
|
|
96
|
-
name: parsed.name,
|
|
97
|
-
description: parsed.description,
|
|
98
|
-
path
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
return list
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 格式化为大模型 systemPrompt 可用的技能说明文本
|
|
106
|
-
* @param skills 不传则需由调用方传入从 getSkillOverviews 得到的结果
|
|
107
|
-
*/
|
|
108
|
-
export function formatSkillsForSystemPrompt(skills: SkillMeta[]): string {
|
|
109
|
-
if (skills.length === 0) return ''
|
|
110
|
-
const lines = skills.map((s) => `- **${s.name}**: ${s.description}`)
|
|
111
|
-
return `## 可用技能\n\n${lines.join('\n')}\n\n当需要用到某技能时,请使用 get_skill_content 工具获取该技能的完整文档内容。`
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 根据相对路径获取某个技能文档的原始内容(支持 .md、.json、.xml 等文本格式)
|
|
116
|
-
* - 自动对 modules 做 normalize,再按 path 查找
|
|
117
|
-
*/
|
|
118
|
-
export async function getSkillMdContent(
|
|
119
|
-
modules: Record<string, string | (() => Promise<string>)>,
|
|
120
|
-
path: string
|
|
121
|
-
): Promise<string | undefined> {
|
|
122
|
-
const normalized = await normalizeSkillModuleKeys(modules)
|
|
123
|
-
|
|
124
|
-
// 1. 尝试原有的严格匹配
|
|
125
|
-
const exactMatch = normalized[path]
|
|
126
|
-
if (exactMatch) return exactMatch
|
|
127
|
-
|
|
128
|
-
// 2. 降级匹配:如果严格匹配完整路径未找到
|
|
129
|
-
// 则尝试寻找后缀能够匹配上的真实文件路径。
|
|
130
|
-
// 去除开头的 '.' 或 './' 以精确匹配结尾部分的路径。
|
|
131
|
-
const suffix = path.replace(/^\.?\//, '/')
|
|
132
|
-
const matchingKey = Object.keys(normalized).find((key) => key.endsWith(suffix))
|
|
133
|
-
return matchingKey ? normalized[matchingKey] : undefined
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 根据技能 name 查找其主 SKILL.md 的路径
|
|
138
|
-
* 支持匹配目录名(如 ecommerce)或 SKILL.md 内 frontmatter 定义的 name
|
|
139
|
-
* - 依赖 getMainSkillPaths,内部已做 normalize
|
|
140
|
-
*/
|
|
141
|
-
export async function getMainSkillPathByName(
|
|
142
|
-
modules: Record<string, string | (() => Promise<string>)>,
|
|
143
|
-
name: string
|
|
144
|
-
): Promise<string | undefined> {
|
|
145
|
-
const normalizedModules = await normalizeSkillModuleKeys(modules)
|
|
146
|
-
const paths = await getMainSkillPaths(normalizedModules)
|
|
147
|
-
|
|
148
|
-
// 1. 先尝试按目录名精确匹配 (兼容老逻辑)
|
|
149
|
-
const dirMatch = paths.find((p) => p.startsWith(`./${name}/SKILL.md`))
|
|
150
|
-
if (dirMatch) return dirMatch
|
|
151
|
-
|
|
152
|
-
// 2. 如果按目录名找不到,则解析内容按 frontmatter 的 name 匹配
|
|
153
|
-
for (const p of paths) {
|
|
154
|
-
const content = normalizedModules[p]
|
|
155
|
-
if (content) {
|
|
156
|
-
const parsed = await parseSkillFrontMatter(content)
|
|
157
|
-
if (parsed && parsed.name === name) {
|
|
158
|
-
return p
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return undefined
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ============ 内置工具:供 remoter 注入,替代业界 skill 中「读取文档」的操作 ============
|
|
167
|
-
|
|
168
|
-
/** AI SDK Tool 类型,用于 extraTools 合并,不写死泛型避免与 ai 包版本强绑定 */
|
|
169
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
-
export type SkillToolsSet = Record<string, any>
|
|
171
|
-
|
|
172
|
-
// 提升为模块级常量:避免 tool() 推断 PARAMETERS 泛型时递归展开 Zod 链导致"类型实例化过深"
|
|
173
|
-
const SKILL_INPUT_SCHEMA = z.object({
|
|
174
|
-
skillName: z
|
|
175
|
-
.string()
|
|
176
|
-
.optional()
|
|
177
|
-
.describe(
|
|
178
|
-
'进入某个技能的主入口名称。优先匹配技能的目录名(如 ecommerce),或者技能的中文名称(如"客户价保单创建及审核")。'
|
|
179
|
-
),
|
|
180
|
-
path: z
|
|
181
|
-
.string()
|
|
182
|
-
.optional()
|
|
183
|
-
.describe('你想查阅的文档的路径。如 ./calculator/SKILL.md 或从其他文档里看到的相对路径 ./reference/inventory.md。'),
|
|
184
|
-
currentPath: z
|
|
185
|
-
.string()
|
|
186
|
-
.optional()
|
|
187
|
-
.describe(
|
|
188
|
-
'你当前正在阅读的文档路径(如果有)。比如你刚刚读取了 ./ecommerce/SKILL.md,请把这个路径原样传回来,这样系统才能根据你的相对路径准确找到下一份文件。'
|
|
189
|
-
)
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* 根据 skillMdModules 创建供 AI 调用的工具集
|
|
194
|
-
* - get_skill_content: 按技能名或路径获取完整文档内容,便于大模型自动识别并加载技能
|
|
195
|
-
* remoter 可将返回的 tools 合并进 extraTools 注入 agent
|
|
196
|
-
*/
|
|
197
|
-
export function createSkillTools(modules: Record<string, string | (() => Promise<string>)>): SkillToolsSet {
|
|
198
|
-
let isNormalizeSkillModuleKeys = false
|
|
199
|
-
let normalizeSkillModuleKeysResult: Record<string, string>
|
|
200
|
-
|
|
201
|
-
// @ts-ignore ai package 的 tool() 函数类型推断存在"类型实例化过深"的已知限制,无法正确推断包含复杂 Zod 链的 schema
|
|
202
|
-
const getSkillContent = tool({
|
|
203
|
-
description:
|
|
204
|
-
'根据技能名称或文档路径获取该技能的完整文档内容。如果你想根据相对路径查阅文件,请务必同时提供你当前所在的文件路径 currentPath。',
|
|
205
|
-
inputSchema: SKILL_INPUT_SCHEMA,
|
|
206
|
-
execute: async (args: {
|
|
207
|
-
skillName?: string
|
|
208
|
-
path?: string
|
|
209
|
-
currentPath?: string
|
|
210
|
-
}): Promise<Record<string, unknown>> => {
|
|
211
|
-
if (!isNormalizeSkillModuleKeys) {
|
|
212
|
-
normalizeSkillModuleKeysResult = await normalizeSkillModuleKeys(modules)
|
|
213
|
-
isNormalizeSkillModuleKeys = true
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const normalizedModules = normalizeSkillModuleKeysResult
|
|
217
|
-
const { skillName, path: pathArg, currentPath: currentPathArg } = args
|
|
218
|
-
let content: string | undefined
|
|
219
|
-
let resolvedPath = ''
|
|
220
|
-
|
|
221
|
-
if (pathArg) {
|
|
222
|
-
// 使用明确提供的当前阅读上下文作为基准路径(默认在根目录)
|
|
223
|
-
let basePathContext = '.'
|
|
224
|
-
if (currentPathArg) {
|
|
225
|
-
// 提取出当前文档所在的目录
|
|
226
|
-
// 比如 ./ecommerce/SKILL.md -> ./ecommerce
|
|
227
|
-
const lastSlashIndex = currentPathArg.lastIndexOf('/')
|
|
228
|
-
if (lastSlashIndex >= 0) {
|
|
229
|
-
basePathContext = currentPathArg.slice(0, lastSlashIndex)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// 尝试 1:按照大模型当前提供的上下文进行标准相对路径解析
|
|
234
|
-
const dummyBase = `http://localhost/${basePathContext}/`
|
|
235
|
-
const url = new URL(pathArg, dummyBase)
|
|
236
|
-
resolvedPath = '.' + url.pathname
|
|
237
|
-
content = await getSkillMdContent(normalizedModules, resolvedPath)
|
|
238
|
-
|
|
239
|
-
// 尝试 2:如果大模型忘了传正确的 currentPath,或者是强行传错,做个智能根目录回退
|
|
240
|
-
if (content === undefined && (pathArg.startsWith('./') || pathArg.startsWith('../')) && currentPathArg) {
|
|
241
|
-
const baseParts = currentPathArg.split('/')
|
|
242
|
-
if (baseParts.length >= 2) {
|
|
243
|
-
const skillRoot = baseParts[1]
|
|
244
|
-
const fallbackDummyBase = `http://localhost/${skillRoot}/`
|
|
245
|
-
const fallbackUrl = new URL(pathArg, fallbackDummyBase)
|
|
246
|
-
const fallbackPath = '.' + fallbackUrl.pathname
|
|
247
|
-
content = await getSkillMdContent(normalizedModules, fallbackPath)
|
|
248
|
-
if (content) {
|
|
249
|
-
resolvedPath = fallbackPath
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 尝试 3:后缀自动降级匹配修正
|
|
255
|
-
if (content && !normalizedModules[resolvedPath]) {
|
|
256
|
-
const suffix = resolvedPath.replace(/^\.?\//, '/')
|
|
257
|
-
const matchingKey = Object.keys(normalizedModules).find((key) => key.endsWith(suffix))
|
|
258
|
-
if (matchingKey) {
|
|
259
|
-
resolvedPath = matchingKey
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
} else if (skillName) {
|
|
263
|
-
const mainPath = await getMainSkillPathByName(normalizedModules, skillName)
|
|
264
|
-
if (mainPath) {
|
|
265
|
-
resolvedPath = mainPath
|
|
266
|
-
content = await getSkillMdContent(normalizedModules, mainPath)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (content === undefined) {
|
|
271
|
-
return {
|
|
272
|
-
error: '未找到对应技能文档',
|
|
273
|
-
skillName,
|
|
274
|
-
path: pathArg,
|
|
275
|
-
providedCurrentPath: currentPathArg,
|
|
276
|
-
attemptedPath: resolvedPath
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return { content, path: resolvedPath }
|
|
281
|
-
}
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
get_skill_content: getSkillContent
|
|
286
|
-
}
|
|
287
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"declaration": true,
|
|
4
|
-
"outDir": "dist",
|
|
5
|
-
"target": "ES2015",
|
|
6
|
-
"lib": ["ES2015", "DOM"],
|
|
7
|
-
"moduleResolution": "node",
|
|
8
|
-
"module": "esnext",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"types": ["vite/client"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["**/*.ts"],
|
|
15
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
16
|
-
}
|
package/utils/builtinProxy.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
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
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 建立原生的 JSON-RPC 拦截层:
|
|
31
|
-
* 将发向 remote 的 MCP 请求拦截,代理给所在浏览器的 document.modelContext 上执行。
|
|
32
|
-
*/
|
|
33
|
-
export const setupBuiltinProxy = (transport: Transport) => {
|
|
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
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
transport.onmessage = async (message: JsonRpcMessage) => {
|
|
43
|
-
if (!message || typeof message !== 'object') return
|
|
44
|
-
const id = message.id
|
|
45
|
-
const method = message.method
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
if (method === 'initialize') {
|
|
49
|
-
await transport.send({
|
|
50
|
-
jsonrpc: '2.0',
|
|
51
|
-
id,
|
|
52
|
-
result: {
|
|
53
|
-
protocolVersion: '2024-11-05',
|
|
54
|
-
capabilities: {
|
|
55
|
-
tools: { listChanged: true },
|
|
56
|
-
logging: {}
|
|
57
|
-
},
|
|
58
|
-
serverInfo: { name: 'browser-builtin-webmcp-proxy', version: '1.0.0' }
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
} else if (method === 'notifications/initialized') {
|
|
62
|
-
// Ignore
|
|
63
|
-
} else if (method === 'ping' || method === 'custom_ping') {
|
|
64
|
-
await transport.send({ jsonrpc: '2.0', id, result: {} })
|
|
65
|
-
} else if (method === 'tools/list') {
|
|
66
|
-
const nativeCtx = getNativeCtx()
|
|
67
|
-
if (nativeCtx && nativeCtx.getTools) {
|
|
68
|
-
const rawTools = await nativeCtx.getTools()
|
|
69
|
-
const tools = rawTools.map((t: any) => {
|
|
70
|
-
let schemaObj: any = {}
|
|
71
|
-
if (typeof t.inputSchema === 'string') {
|
|
72
|
-
try {
|
|
73
|
-
schemaObj = JSON.parse(t.inputSchema)
|
|
74
|
-
} catch (e) {
|
|
75
|
-
console.error('Failed to parse inputSchema:', e)
|
|
76
|
-
}
|
|
77
|
-
} else if (typeof t.inputSchema === 'object' && t.inputSchema !== null) {
|
|
78
|
-
schemaObj = t.inputSchema
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
name: t.name,
|
|
82
|
-
description: t.description || '',
|
|
83
|
-
inputSchema: {
|
|
84
|
-
type: 'object',
|
|
85
|
-
properties: schemaObj.properties || {},
|
|
86
|
-
required: schemaObj.required || []
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
await transport.send({ jsonrpc: '2.0', id, result: { tools } })
|
|
91
|
-
} else {
|
|
92
|
-
await transport.send({ jsonrpc: '2.0', id, result: { tools: [] } })
|
|
93
|
-
}
|
|
94
|
-
} else if (method === 'tools/call') {
|
|
95
|
-
const nativeCtx = getNativeCtx()
|
|
96
|
-
if (nativeCtx && nativeCtx.executeTool) {
|
|
97
|
-
if (!message.params || typeof message.params !== 'object' || !message.params.name) {
|
|
98
|
-
const error: Error & { code?: number } = new Error('Invalid params: "name" is required and params must be an object')
|
|
99
|
-
error.code = -32602 // Invalid params
|
|
100
|
-
throw error
|
|
101
|
-
}
|
|
102
|
-
const { name, arguments: args } = message.params
|
|
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 || {}))
|
|
107
|
-
const finalResult =
|
|
108
|
-
result && typeof result === 'object' && 'content' in result
|
|
109
|
-
? result
|
|
110
|
-
: { content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result) }] }
|
|
111
|
-
await transport.send({ jsonrpc: '2.0', id, result: finalResult })
|
|
112
|
-
} else {
|
|
113
|
-
const error: Error & { code?: number } = new Error('executeTool not implemented in Browser built-in WebMCP')
|
|
114
|
-
error.code = -32601 // Method not found
|
|
115
|
-
throw error
|
|
116
|
-
}
|
|
117
|
-
} else if (method === 'logging/setLevel') {
|
|
118
|
-
// 浏览器内置 WebMCP 不是标准 MCP Server,不支持日志级别设置。
|
|
119
|
-
// 直接 mock 返回成功,避免严格的 MCP 客户端因未实现该方法而报错。
|
|
120
|
-
await transport.send({ jsonrpc: '2.0', id, result: {} })
|
|
121
|
-
} else if (method === 'prompts/list') {
|
|
122
|
-
await transport.send({ jsonrpc: '2.0', id, result: { prompts: [] } })
|
|
123
|
-
} else if (method === 'resources/list') {
|
|
124
|
-
await transport.send({ jsonrpc: '2.0', id, result: { resources: [] } })
|
|
125
|
-
} else if (id !== undefined) {
|
|
126
|
-
// 对于其他不支持但带 id 的请求,返回规范要求的 -32601 Method not found
|
|
127
|
-
await transport.send({
|
|
128
|
-
jsonrpc: '2.0',
|
|
129
|
-
id,
|
|
130
|
-
error: { code: -32601, message: `Method not found: ${method}` }
|
|
131
|
-
})
|
|
132
|
-
}
|
|
133
|
-
} catch (err: unknown) {
|
|
134
|
-
if (id !== undefined) {
|
|
135
|
-
const errObj = err as { code?: number; message?: string }
|
|
136
|
-
await transport.send({
|
|
137
|
-
jsonrpc: '2.0',
|
|
138
|
-
id,
|
|
139
|
-
error: { code: errObj.code || -32000, message: errObj.message || String(err) }
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
package/utils/env.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 判断当前是否处于有效的浏览器环境
|
|
3
|
-
*/
|
|
4
|
-
export const isBrowser = (): boolean => {
|
|
5
|
-
return typeof window !== 'undefined' && typeof navigator !== 'undefined'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 判断当前是否处于有效且能访问 document 的浏览器环境
|
|
10
|
-
*/
|
|
11
|
-
export const isDomAvailable = (): boolean => {
|
|
12
|
-
return isBrowser() && typeof document !== 'undefined'
|
|
13
|
-
}
|
package/utils/uuid.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const randomUUID = () => {
|
|
2
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
3
|
-
return crypto.randomUUID()
|
|
4
|
-
}
|
|
5
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
6
|
-
const r = (Math.random() * 16) | 0
|
|
7
|
-
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
8
|
-
return v.toString(16)
|
|
9
|
-
})
|
|
10
|
-
}
|
package/vite.config.runtime.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
})
|
package/vite.config.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import dts from 'vite-plugin-dts'
|
|
3
|
-
|
|
4
|
-
// https://vitejs.dev/config/
|
|
5
|
-
export default defineConfig(() => {
|
|
6
|
-
// 需要排除的第三方依赖列表
|
|
7
|
-
const externalDependencies = [
|
|
8
|
-
'@modelcontextprotocol/sdk',
|
|
9
|
-
'@opentiny/next',
|
|
10
|
-
'@ai-sdk/openai',
|
|
11
|
-
'@ai-sdk/deepseek',
|
|
12
|
-
'@ai-sdk/provider',
|
|
13
|
-
'qrcode',
|
|
14
|
-
'zod',
|
|
15
|
-
'ajv',
|
|
16
|
-
'ai'
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
plugins: [
|
|
21
|
-
// 配置 dts 插件生成类型声明文件
|
|
22
|
-
dts({
|
|
23
|
-
// 指定 TypeScript 配置文件路径
|
|
24
|
-
tsconfigPath: './tsconfig.json',
|
|
25
|
-
outDir: 'dist',
|
|
26
|
-
// 包含所有 TypeScript 文件,确保所有被引用的文件都被处理
|
|
27
|
-
include: ['**/*.ts'],
|
|
28
|
-
exclude: ['node_modules/**', 'dist/**', '**/*.test.ts', '**/*.spec.ts'],
|
|
29
|
-
// 不合并类型文件,保持文件结构以便相对路径引用正常工作
|
|
30
|
-
rollupTypes: false,
|
|
31
|
-
// 插入类型入口文件引用
|
|
32
|
-
insertTypesEntry: true
|
|
33
|
-
})
|
|
34
|
-
],
|
|
35
|
-
build: {
|
|
36
|
-
emptyOutDir: false,
|
|
37
|
-
lib: {
|
|
38
|
-
entry: {
|
|
39
|
-
index: 'index.ts',
|
|
40
|
-
core: 'core.ts'
|
|
41
|
-
},
|
|
42
|
-
name: 'NEXT-SDK',
|
|
43
|
-
formats: ['es'],
|
|
44
|
-
fileName: (format, entryName) => `${entryName}.js`
|
|
45
|
-
},
|
|
46
|
-
rollupOptions: {
|
|
47
|
-
// 排除第三方依赖,保留本地文件
|
|
48
|
-
external: (id) => {
|
|
49
|
-
// 如果是相对路径导入(本地文件),不排除
|
|
50
|
-
if (id.startsWith('.') || id.startsWith('/')) {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
// 排除 node_modules 中的第三方依赖
|
|
54
|
-
if (id.includes('node_modules')) {
|
|
55
|
-
return true
|
|
56
|
-
}
|
|
57
|
-
// 排除指定的第三方依赖包
|
|
58
|
-
return externalDependencies.some((dep) => id === dep || id.startsWith(`${dep}/`))
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
})
|