@opentiny/next-sdk 0.2.7 → 0.2.8

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/skills/index.ts CHANGED
@@ -115,15 +115,44 @@ export function getSkillMdPaths(modules: Record<string, string>): string[] {
115
115
  */
116
116
  export function getSkillMdContent(modules: Record<string, string>, path: string): string | undefined {
117
117
  const normalized = normalizeSkillModuleKeys(modules)
118
- return normalized[path]
118
+
119
+ // 1. 尝试原有的严格匹配
120
+ const exactMatch = normalized[path]
121
+ if (exactMatch) return exactMatch
122
+
123
+ // 2. 降级匹配:如果严格匹配完整路径未找到
124
+ // 则尝试寻找后缀能够匹配上的真实文件路径。
125
+ // 去除开头的 '.' 或 './' 以精确匹配结尾部分的路径。
126
+ const suffix = path.replace(/^\.?\//, '/')
127
+ const matchingKey = Object.keys(normalized).find((key) => key.endsWith(suffix))
128
+ return matchingKey ? normalized[matchingKey] : undefined
119
129
  }
120
130
 
121
131
  /**
122
- * 根据技能 name 查找其主 SKILL.md 的路径(name 与目录名一致)
132
+ * 根据技能 name 查找其主 SKILL.md 的路径
133
+ * 支持匹配目录名(如 ecommerce)或 SKILL.md 内 frontmatter 定义的 name
123
134
  * - 依赖 getMainSkillPaths,内部已做 normalize
124
135
  */
125
136
  export function getMainSkillPathByName(modules: Record<string, string>, name: string): string | undefined {
126
- return getMainSkillPaths(modules).find((p) => p.startsWith(`./${name}/SKILL.md`))
137
+ const normalizedModules = normalizeSkillModuleKeys(modules)
138
+ const paths = getMainSkillPaths(normalizedModules)
139
+
140
+ // 1. 先尝试按目录名精确匹配 (兼容老逻辑)
141
+ const dirMatch = paths.find((p) => p.startsWith(`./${name}/SKILL.md`))
142
+ if (dirMatch) return dirMatch
143
+
144
+ // 2. 如果按目录名找不到,则解析内容按 frontmatter 的 name 匹配
145
+ for (const p of paths) {
146
+ const content = normalizedModules[p]
147
+ if (content) {
148
+ const parsed = parseSkillFrontMatter(content)
149
+ if (parsed && parsed.name === name) {
150
+ return p
151
+ }
152
+ }
153
+ }
154
+
155
+ return undefined
127
156
  }
128
157
 
129
158
  // ============ 内置工具:供 remoter 注入,替代业界 skill 中「读取文档」的操作 ============
@@ -134,8 +163,22 @@ export type SkillToolsSet = Record<string, any>
134
163
 
135
164
  // 提升为模块级常量:避免 tool() 推断 PARAMETERS 泛型时递归展开 Zod 链导致"类型实例化过深"
136
165
  const SKILL_INPUT_SCHEMA = z.object({
137
- skillName: z.string().optional().describe('技能名称,与目录名一致,如 calculator'),
138
- path: z.string().optional().describe('文档相对路径,如 ./calculator/SKILL.md 或 ./product-guide/reference/xxx.json')
166
+ skillName: z
167
+ .string()
168
+ .optional()
169
+ .describe(
170
+ '进入某个技能的主入口名称。优先匹配技能的目录名(如 ecommerce),或者技能的中文名称(如"客户价保单创建及审核")。'
171
+ ),
172
+ path: z
173
+ .string()
174
+ .optional()
175
+ .describe('你想查阅的文档的路径。如 ./calculator/SKILL.md 或从其他文档里看到的相对路径 ./reference/inventory.md。'),
176
+ currentPath: z
177
+ .string()
178
+ .optional()
179
+ .describe(
180
+ '你当前正在阅读的文档路径(如果有)。比如你刚刚读取了 ./ecommerce/SKILL.md,请把这个路径原样传回来,这样系统才能根据你的相对路径准确找到下一份文件。'
181
+ )
139
182
  })
140
183
 
141
184
  /**
@@ -145,23 +188,77 @@ const SKILL_INPUT_SCHEMA = z.object({
145
188
  */
146
189
  export function createSkillTools(modules: Record<string, string>): SkillToolsSet {
147
190
  const normalizedModules = normalizeSkillModuleKeys(modules)
191
+
192
+ // @ts-ignore ai package 的 tool() 函数类型推断存在"类型实例化过深"的已知限制,无法正确推断包含复杂 Zod 链的 schema
148
193
  const getSkillContent = tool({
149
194
  description:
150
- '根据技能名称或文档路径获取该技能的完整文档内容。传入 skillName(如 calculator)或 path(如 ./calculator/SKILL.md)。支持 .md、.json、.xml 等各类文本格式文件。',
195
+ '根据技能名称或文档路径获取该技能的完整文档内容。如果你想根据相对路径查阅文件,请务必同时提供你当前所在的文件路径 currentPath。',
151
196
  inputSchema: SKILL_INPUT_SCHEMA,
152
- execute: (args: { skillName?: string; path?: string }): Record<string, unknown> => {
153
- const { skillName, path: pathArg } = args
197
+ execute: (args: { skillName?: string; path?: string; currentPath?: string }): Record<string, unknown> => {
198
+ const { skillName, path: pathArg, currentPath: currentPathArg } = args
154
199
  let content: string | undefined
200
+ let resolvedPath = ''
201
+
155
202
  if (pathArg) {
156
- content = getSkillMdContent(normalizedModules, pathArg)
203
+ // 使用明确提供的当前阅读上下文作为基准路径(默认在根目录)
204
+ let basePathContext = '.'
205
+ if (currentPathArg) {
206
+ // 提取出当前文档所在的目录
207
+ // 比如 ./ecommerce/SKILL.md -> ./ecommerce
208
+ const lastSlashIndex = currentPathArg.lastIndexOf('/')
209
+ if (lastSlashIndex >= 0) {
210
+ basePathContext = currentPathArg.slice(0, lastSlashIndex)
211
+ }
212
+ }
213
+
214
+ // 尝试 1:按照大模型当前提供的上下文进行标准相对路径解析
215
+ const dummyBase = `http://localhost/${basePathContext}/`
216
+ const url = new URL(pathArg, dummyBase)
217
+ resolvedPath = '.' + url.pathname
218
+ content = getSkillMdContent(normalizedModules, resolvedPath)
219
+
220
+ // 尝试 2:如果大模型忘了传正确的 currentPath,或者是强行传错,做个智能根目录回退
221
+ if (content === undefined && (pathArg.startsWith('./') || pathArg.startsWith('../')) && currentPathArg) {
222
+ const baseParts = currentPathArg.split('/')
223
+ if (baseParts.length >= 2) {
224
+ const skillRoot = baseParts[1]
225
+ const fallbackDummyBase = `http://localhost/${skillRoot}/`
226
+ const fallbackUrl = new URL(pathArg, fallbackDummyBase)
227
+ const fallbackPath = '.' + fallbackUrl.pathname
228
+ content = getSkillMdContent(normalizedModules, fallbackPath)
229
+ if (content) {
230
+ resolvedPath = fallbackPath
231
+ }
232
+ }
233
+ }
234
+
235
+ // 尝试 3:后缀自动降级匹配修正
236
+ if (content && !normalizedModules[resolvedPath]) {
237
+ const suffix = resolvedPath.replace(/^\.?\//, '/')
238
+ const matchingKey = Object.keys(normalizedModules).find((key) => key.endsWith(suffix))
239
+ if (matchingKey) {
240
+ resolvedPath = matchingKey
241
+ }
242
+ }
157
243
  } else if (skillName) {
158
244
  const mainPath = getMainSkillPathByName(normalizedModules, skillName)
159
- content = mainPath ? getSkillMdContent(normalizedModules, mainPath) : undefined
245
+ if (mainPath) {
246
+ resolvedPath = mainPath
247
+ content = getSkillMdContent(normalizedModules, mainPath)
248
+ }
160
249
  }
250
+
161
251
  if (content === undefined) {
162
- return { error: '未找到对应技能文档', skillName: skillName ?? pathArg }
252
+ return {
253
+ error: '未找到对应技能文档',
254
+ skillName,
255
+ path: pathArg,
256
+ providedCurrentPath: currentPathArg,
257
+ attemptedPath: resolvedPath
258
+ }
163
259
  }
164
- return { content, path: pathArg ?? getMainSkillPathByName(normalizedModules, skillName!) }
260
+
261
+ return { content, path: resolvedPath }
165
262
  }
166
263
  })
167
264