@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.
@@ -1,4 +1,4 @@
1
- import { streamText, stepCountIs, generateText, StreamTextResult } from 'ai'
1
+ import { streamText, stepCountIs, generateText } from 'ai'
2
2
  import { MCPClientConfig, createMCPClient } from '@ai-sdk/mcp'
3
3
  import type { ToolSet } from 'ai'
4
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
@@ -281,18 +281,27 @@ export class AgentModelProvider {
281
281
  this.onUpdatedTools?.()
282
282
  }
283
283
 
284
- /** 创建临时允许调用的tools集合 */
285
- private _tempMergeTools(extraTool = {}) {
286
- // 将对象的值转换为数组后再 reduce
287
- const toolsResult = Object.values(this.mcpTools).reduce((acc, curr) => ({ ...acc, ...curr }), {})
284
+ /** 创建临时允许调用的 tools 集合,合并 mcpTools 与 extraTool */
285
+ private _tempMergeTools(extraTool: ToolSet = {} as ToolSet, deleteIgnored = true): ToolSet {
286
+ const toolsResult: ToolSet = Object.values(this.mcpTools).reduce(
287
+ (acc, curr) => ({ ...acc, ...curr } as ToolSet),
288
+ {} as ToolSet
289
+ )
288
290
  Object.assign(toolsResult, extraTool)
289
291
 
290
- this.ignoreToolnames.forEach((name) => {
291
- delete toolsResult[name]
292
- })
292
+ if (deleteIgnored) {
293
+ this.ignoreToolnames.forEach((name) => {
294
+ delete toolsResult[name]
295
+ })
296
+ }
293
297
  return toolsResult
294
298
  }
295
299
 
300
+ /** 获取当前激活的 tools 名称列表(过滤 ignoreToolnames) */
301
+ private _getActiveToolNames(tools: ToolSet): string[] {
302
+ return Object.keys(tools).filter((name) => !this.ignoreToolnames.includes(name))
303
+ }
304
+
296
305
  /** 生成 ReAct 模式的系统提示词(包含工具描述) */
297
306
  private _generateReActSystemPrompt(tools: ToolSet, modelName: string, baseSystemPrompt?: string): string {
298
307
  // 统一使用 XML 格式的 ReAct 提示词(所有 ReAct 模式都使用相同格式)
@@ -342,7 +351,7 @@ export class AgentModelProvider {
342
351
  await this.initClientsAndTools()
343
352
 
344
353
  // 合并所有可用工具
345
- const allTools = this._tempMergeTools(options.tools) as ToolSet
354
+ const allTools = this._tempMergeTools(options.tools)
346
355
  const toolNames = Object.keys(allTools)
347
356
 
348
357
  // 如果没有工具,回退到普通模式
@@ -810,12 +819,15 @@ export class AgentModelProvider {
810
819
 
811
820
  await this.initClientsAndTools()
812
821
 
822
+ const allTools = this._tempMergeTools(options.tools, false)
823
+
813
824
  const chatOptions = {
814
825
  // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
815
826
  model: this.llm(model),
816
827
  stopWhen: stepCountIs(maxSteps),
817
828
  ...options,
818
- tools: this._tempMergeTools(options.tools) as ToolSet
829
+ tools: allTools,
830
+ activeTools: this._getActiveToolNames(allTools),
819
831
  }
820
832
 
821
833
  // 保存最后一条 user 消息,用于后续缓存
@@ -53,8 +53,10 @@ export declare class AgentModelProvider {
53
53
  insertMcpServer(serverName: string, mcpServer: McpServerConfig): Promise<false | WebMcpClient | import('@ai-sdk/mcp').MCPClient | null>;
54
54
  /** 通过服务器名称删除mcpServer: mcpServers mcpClients mcpTools ignoreToolnames */
55
55
  removeMcpServer(serverName: string): Promise<void>;
56
- /** 创建临时允许调用的tools集合 */
56
+ /** 创建临时允许调用的 tools 集合,合并 mcpTools 与 extraTool */
57
57
  private _tempMergeTools;
58
+ /** 获取当前激活的 tools 名称列表(过滤 ignoreToolnames) */
59
+ private _getActiveToolNames;
58
60
  /** 生成 ReAct 模式的系统提示词(包含工具描述) */
59
61
  private _generateReActSystemPrompt;
60
62
  /** 执行 ReAct 模式下的工具调用 */
package/dist/index.d.ts CHANGED
@@ -26,5 +26,5 @@ export { AgentModelProvider } from './agent/AgentModelProvider';
26
26
  export { getAISDKTools } from './agent/utils/getAISDKTools';
27
27
  export { QrCode, type QrCodeOption } from './remoter/QrCode';
28
28
  export type * from './agent/type';
29
- export * from './page-tool-bridge';
29
+ export * from './page-tools/bridge';
30
30
  export { getSkillOverviews, formatSkillsForSystemPrompt, getSkillMdPaths, getSkillMdContent, getMainSkillPaths, getMainSkillPathByName, parseSkillFrontMatter, createSkillTools, type SkillMeta, type SkillToolsSet } from './skills/index';
@@ -27758,6 +27758,19 @@ class FloatingBlock {
27758
27758
  this.dropdownMenu.parentNode.removeChild(this.dropdownMenu);
27759
27759
  }
27760
27760
  }
27761
+ // 隐藏组件
27762
+ hide() {
27763
+ if (this.floatingBlock) {
27764
+ this.floatingBlock.style.display = "none";
27765
+ }
27766
+ this.closeDropdown();
27767
+ }
27768
+ // 显示组件
27769
+ show() {
27770
+ if (this.floatingBlock) {
27771
+ this.floatingBlock.style.display = "flex";
27772
+ }
27773
+ }
27761
27774
  }
27762
27775
  const createRemoter = (options = {}) => {
27763
27776
  return new FloatingBlock(options);
@@ -49772,15 +49785,24 @@ class AgentModelProvider {
49772
49785
  }
49773
49786
  this.onUpdatedTools?.();
49774
49787
  }
49775
- /** 创建临时允许调用的tools集合 */
49776
- _tempMergeTools(extraTool = {}) {
49777
- const toolsResult = Object.values(this.mcpTools).reduce((acc, curr) => ({ ...acc, ...curr }), {});
49788
+ /** 创建临时允许调用的 tools 集合,合并 mcpTools 与 extraTool */
49789
+ _tempMergeTools(extraTool = {}, deleteIgnored = true) {
49790
+ const toolsResult = Object.values(this.mcpTools).reduce(
49791
+ (acc, curr) => ({ ...acc, ...curr }),
49792
+ {}
49793
+ );
49778
49794
  Object.assign(toolsResult, extraTool);
49779
- this.ignoreToolnames.forEach((name16) => {
49780
- delete toolsResult[name16];
49781
- });
49795
+ if (deleteIgnored) {
49796
+ this.ignoreToolnames.forEach((name16) => {
49797
+ delete toolsResult[name16];
49798
+ });
49799
+ }
49782
49800
  return toolsResult;
49783
49801
  }
49802
+ /** 获取当前激活的 tools 名称列表(过滤 ignoreToolnames) */
49803
+ _getActiveToolNames(tools) {
49804
+ return Object.keys(tools).filter((name16) => !this.ignoreToolnames.includes(name16));
49805
+ }
49784
49806
  /** 生成 ReAct 模式的系统提示词(包含工具描述) */
49785
49807
  _generateReActSystemPrompt(tools, modelName, baseSystemPrompt) {
49786
49808
  const toolsPrompt = generateReActToolsPrompt(tools);
@@ -50124,12 +50146,14 @@ ${observationText}
50124
50146
  throw new Error("LLM is not initialized");
50125
50147
  }
50126
50148
  await this.initClientsAndTools();
50149
+ const allTools = this._tempMergeTools(options.tools, false);
50127
50150
  const chatOptions = {
50128
50151
  // @ts-ignore ProviderV2 是所有llm的父类, 在每一个具体的llm 类都有一个选择model的函数用法
50129
50152
  model: this.llm(model),
50130
50153
  stopWhen: stepCountIs(maxSteps),
50131
50154
  ...options,
50132
- tools: this._tempMergeTools(options.tools)
50155
+ tools: allTools,
50156
+ activeTools: this._getActiveToolNames(allTools)
50133
50157
  };
50134
50158
  let lastUserMessage = null;
50135
50159
  if (options.message && !options.messages) {
@@ -50159,6 +50183,282 @@ ${observationText}
50159
50183
  return this._chat(streamText, options);
50160
50184
  }
50161
50185
  }
50186
+ let overlayElement = null;
50187
+ let labelElement = null;
50188
+ let styleElement = null;
50189
+ let activeCount = 0;
50190
+ const BODY_GLOW_CLASS = "next-sdk-tool-body-glow";
50191
+ function ensureDomReady() {
50192
+ return typeof window !== "undefined" && typeof document !== "undefined";
50193
+ }
50194
+ function ensureStyleElement() {
50195
+ if (!ensureDomReady()) return;
50196
+ if (styleElement) return;
50197
+ const style = document.createElement("style");
50198
+ style.textContent = `
50199
+ .${BODY_GLOW_CLASS} {
50200
+ position: relative;
50201
+ }
50202
+
50203
+ .next-sdk-tool-overlay {
50204
+ position: fixed;
50205
+ inset: 0;
50206
+ z-index: 999999;
50207
+ pointer-events: none;
50208
+ display: flex;
50209
+ align-items: flex-end;
50210
+ justify-content: flex-start;
50211
+ padding: 0 0 18px 18px;
50212
+ background: transparent;
50213
+ animation: next-sdk-overlay-fade-in 260ms ease-out;
50214
+ }
50215
+
50216
+ .next-sdk-tool-overlay--exit {
50217
+ animation: next-sdk-overlay-fade-out 220ms ease-in forwards;
50218
+ }
50219
+
50220
+ .next-sdk-tool-overlay__glow-ring {
50221
+ display: none;
50222
+ }
50223
+
50224
+ .next-sdk-tool-overlay__panel {
50225
+ position: relative;
50226
+ min-width: min(320px, 78vw);
50227
+ max-width: min(420px, 82vw);
50228
+ padding: 10px 14px;
50229
+ border-radius: 999px;
50230
+ background:
50231
+ linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(17, 24, 39, 0.9)),
50232
+ radial-gradient(circle at top left, rgba(96, 165, 250, 0.25), transparent 55%),
50233
+ radial-gradient(circle at bottom right, rgba(45, 212, 191, 0.22), transparent 60%);
50234
+ box-shadow:
50235
+ 0 12px 28px rgba(15, 23, 42, 0.78),
50236
+ 0 0 0 1px rgba(148, 163, 184, 0.26);
50237
+ display: flex;
50238
+ align-items: center;
50239
+ gap: 10px;
50240
+ pointer-events: none;
50241
+ transform-origin: center;
50242
+ animation: next-sdk-panel-pop-in 260ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
50243
+ color: #e5e7eb;
50244
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
50245
+ }
50246
+
50247
+ .next-sdk-tool-overlay__indicator {
50248
+ width: 26px;
50249
+ height: 26px;
50250
+ border-radius: 999px;
50251
+ background: radial-gradient(circle at 30% 10%, #f9fafb, #93c5fd);
50252
+ box-shadow:
50253
+ 0 0 0 1px rgba(191, 219, 254, 0.6),
50254
+ 0 8px 18px rgba(37, 99, 235, 0.8),
50255
+ 0 0 28px rgba(56, 189, 248, 0.9);
50256
+ position: relative;
50257
+ flex-shrink: 0;
50258
+ display: flex;
50259
+ align-items: center;
50260
+ justify-content: center;
50261
+ overflow: hidden;
50262
+ }
50263
+
50264
+ .next-sdk-tool-overlay__indicator-orbit {
50265
+ position: absolute;
50266
+ inset: 2px;
50267
+ border-radius: inherit;
50268
+ border: 1px solid rgba(248, 250, 252, 0.6);
50269
+ box-sizing: border-box;
50270
+ opacity: 0.9;
50271
+ }
50272
+
50273
+ .next-sdk-tool-overlay__indicator-orbit::before {
50274
+ content: '';
50275
+ position: absolute;
50276
+ width: 6px;
50277
+ height: 6px;
50278
+ border-radius: 999px;
50279
+ background: #f9fafb;
50280
+ box-shadow:
50281
+ 0 0 12px rgba(248, 250, 252, 0.9),
50282
+ 0 0 24px rgba(250, 249, 246, 0.9);
50283
+ top: 0;
50284
+ left: 50%;
50285
+ transform: translate(-50%, -50%);
50286
+ transform-origin: 50% 18px;
50287
+ animation: next-sdk-indicator-orbit 1.4s linear infinite;
50288
+ }
50289
+
50290
+ .next-sdk-tool-overlay__indicator-core {
50291
+ width: 14px;
50292
+ height: 14px;
50293
+ border-radius: inherit;
50294
+ background: radial-gradient(circle at 30% 20%, #f9fafb, #bfdbfe);
50295
+ box-shadow:
50296
+ 0 0 12px rgba(248, 250, 252, 0.9),
50297
+ 0 0 32px rgba(191, 219, 254, 0.8);
50298
+ opacity: 0.96;
50299
+ }
50300
+
50301
+ .next-sdk-tool-overlay__content {
50302
+ display: flex;
50303
+ flex-direction: column;
50304
+ gap: 1px;
50305
+ min-width: 0;
50306
+ }
50307
+
50308
+ .next-sdk-tool-overlay__title {
50309
+ font-size: 11px;
50310
+ letter-spacing: 0.08em;
50311
+ text-transform: uppercase;
50312
+ color: rgba(156, 163, 175, 0.96);
50313
+ display: flex;
50314
+ align-items: center;
50315
+ gap: 6px;
50316
+ }
50317
+
50318
+ .next-sdk-tool-overlay__title-dot {
50319
+ width: 6px;
50320
+ height: 6px;
50321
+ border-radius: 999px;
50322
+ background: #22c55e;
50323
+ box-shadow:
50324
+ 0 0 8px rgba(34, 197, 94, 0.9),
50325
+ 0 0 14px rgba(22, 163, 74, 0.9);
50326
+ }
50327
+
50328
+ .next-sdk-tool-overlay__label {
50329
+ font-size: 12px;
50330
+ font-weight: 500;
50331
+ color: #e5e7eb;
50332
+ white-space: nowrap;
50333
+ text-overflow: ellipsis;
50334
+ overflow: hidden;
50335
+ }
50336
+
50337
+ @keyframes next-sdk-overlay-fade-in {
50338
+ from { opacity: 0; }
50339
+ to { opacity: 1; }
50340
+ }
50341
+
50342
+ @keyframes next-sdk-overlay-fade-out {
50343
+ from { opacity: 1; }
50344
+ to { opacity: 0; }
50345
+ }
50346
+
50347
+ @keyframes next-sdk-indicator-orbit {
50348
+ from {
50349
+ transform: translate(-50%, -50%) rotate(0deg) translateY(0);
50350
+ }
50351
+ to {
50352
+ transform: translate(-50%, -50%) rotate(360deg) translateY(0);
50353
+ }
50354
+ }
50355
+
50356
+ @keyframes next-sdk-panel-pop-in {
50357
+ 0% {
50358
+ opacity: 0;
50359
+ transform: scale(0.92) translateY(10px);
50360
+ }
50361
+ 100% {
50362
+ opacity: 1;
50363
+ transform: scale(1) translateY(0);
50364
+ }
50365
+ }
50366
+ `;
50367
+ document.head.appendChild(style);
50368
+ styleElement = style;
50369
+ }
50370
+ function ensureOverlayElement() {
50371
+ if (!ensureDomReady()) return;
50372
+ if (overlayElement) return;
50373
+ ensureStyleElement();
50374
+ const overlay = document.createElement("div");
50375
+ overlay.className = "next-sdk-tool-overlay";
50376
+ const glowRing = document.createElement("div");
50377
+ glowRing.className = "next-sdk-tool-overlay__glow-ring";
50378
+ const panel = document.createElement("div");
50379
+ panel.className = "next-sdk-tool-overlay__panel";
50380
+ const indicator = document.createElement("div");
50381
+ indicator.className = "next-sdk-tool-overlay__indicator";
50382
+ const indicatorOrbit = document.createElement("div");
50383
+ indicatorOrbit.className = "next-sdk-tool-overlay__indicator-orbit";
50384
+ const indicatorCore = document.createElement("div");
50385
+ indicatorCore.className = "next-sdk-tool-overlay__indicator-core";
50386
+ const content = document.createElement("div");
50387
+ content.className = "next-sdk-tool-overlay__content";
50388
+ const titleRow = document.createElement("div");
50389
+ titleRow.className = "next-sdk-tool-overlay__title";
50390
+ titleRow.textContent = "AI 正在调用页面工具";
50391
+ const titleDot = document.createElement("span");
50392
+ titleDot.className = "next-sdk-tool-overlay__title-dot";
50393
+ const label = document.createElement("div");
50394
+ label.className = "next-sdk-tool-overlay__label";
50395
+ titleRow.prepend(titleDot);
50396
+ content.appendChild(titleRow);
50397
+ content.appendChild(label);
50398
+ indicator.appendChild(indicatorOrbit);
50399
+ indicator.appendChild(indicatorCore);
50400
+ panel.appendChild(indicator);
50401
+ panel.appendChild(content);
50402
+ overlay.appendChild(glowRing);
50403
+ overlay.appendChild(panel);
50404
+ document.body.appendChild(overlay);
50405
+ overlayElement = overlay;
50406
+ labelElement = label;
50407
+ }
50408
+ function updateOverlay(config2) {
50409
+ if (!ensureDomReady()) return;
50410
+ ensureOverlayElement();
50411
+ if (!overlayElement || !labelElement) return;
50412
+ overlayElement.classList.remove("next-sdk-tool-overlay--exit");
50413
+ labelElement.textContent = config2.label;
50414
+ }
50415
+ function removeOverlayWithAnimation() {
50416
+ if (!overlayElement) return;
50417
+ overlayElement.classList.add("next-sdk-tool-overlay--exit");
50418
+ const localOverlay = overlayElement;
50419
+ let handled = false;
50420
+ let timerId;
50421
+ const handle = () => {
50422
+ if (handled) return;
50423
+ handled = true;
50424
+ if (timerId !== void 0) {
50425
+ clearTimeout(timerId);
50426
+ timerId = void 0;
50427
+ }
50428
+ if (localOverlay.parentNode) {
50429
+ localOverlay.parentNode.removeChild(localOverlay);
50430
+ }
50431
+ if (overlayElement === localOverlay) {
50432
+ overlayElement = null;
50433
+ labelElement = null;
50434
+ }
50435
+ localOverlay.removeEventListener("animationend", handle);
50436
+ };
50437
+ localOverlay.addEventListener("animationend", handle);
50438
+ timerId = setTimeout(handle, 500);
50439
+ }
50440
+ function showToolInvokeEffect(config2) {
50441
+ if (!ensureDomReady()) return;
50442
+ activeCount += 1;
50443
+ updateOverlay(config2);
50444
+ }
50445
+ function hideToolInvokeEffect() {
50446
+ if (!ensureDomReady() || activeCount <= 0) return;
50447
+ activeCount -= 1;
50448
+ if (activeCount === 0) {
50449
+ removeOverlayWithAnimation();
50450
+ }
50451
+ }
50452
+ function resolveRuntimeEffectConfig(toolName, toolTitle, value) {
50453
+ if (!value) return void 0;
50454
+ const baseLabel = toolTitle || toolName;
50455
+ if (typeof value === "boolean") {
50456
+ return value ? { label: baseLabel } : void 0;
50457
+ }
50458
+ return {
50459
+ label: value.label || baseLabel
50460
+ };
50461
+ }
50162
50462
  const MSG_TOOL_CALL = "next-sdk:tool-call";
50163
50463
  const MSG_TOOL_RESPONSE = "next-sdk:tool-response";
50164
50464
  const MSG_PAGE_READY = "next-sdk:page-ready";
@@ -50212,7 +50512,7 @@ let _navigator = null;
50212
50512
  function setNavigator(fn) {
50213
50513
  _navigator = fn;
50214
50514
  }
50215
- function buildPageHandler(name16, route, timeout = 3e4) {
50515
+ function buildPageHandler(name16, route, timeout = 3e4, effectConfig) {
50216
50516
  return (input) => {
50217
50517
  const callId = randomUUID();
50218
50518
  return new Promise((resolve2, reject) => {
@@ -50224,6 +50524,9 @@ function buildPageHandler(name16, route, timeout = 3e4) {
50224
50524
  if (readyHandler) {
50225
50525
  window.removeEventListener("message", readyHandler);
50226
50526
  }
50527
+ if (effectConfig) {
50528
+ hideToolInvokeEffect();
50529
+ }
50227
50530
  };
50228
50531
  timer = setTimeout(() => {
50229
50532
  cleanup();
@@ -50247,6 +50550,9 @@ function buildPageHandler(name16, route, timeout = 3e4) {
50247
50550
  };
50248
50551
  const run = async () => {
50249
50552
  try {
50553
+ if (effectConfig) {
50554
+ showToolInvokeEffect(effectConfig);
50555
+ }
50250
50556
  if (activePages.get(route)) {
50251
50557
  sendCallOnce();
50252
50558
  return;
@@ -50283,9 +50589,10 @@ function withPageTools(server) {
50283
50589
  if (typeof handlerOrRoute === "function") {
50284
50590
  return rawRegister(name16, config2, handlerOrRoute);
50285
50591
  }
50286
- const { route, timeout } = handlerOrRoute;
50592
+ const { route, timeout, invokeEffect } = handlerOrRoute;
50287
50593
  toolRouteMap.set(name16, route);
50288
- return rawRegister(name16, config2, buildPageHandler(name16, route, timeout));
50594
+ const effectConfig = resolveRuntimeEffectConfig(name16, config2?.title, invokeEffect);
50595
+ return rawRegister(name16, config2, buildPageHandler(name16, route, timeout, effectConfig));
50289
50596
  };
50290
50597
  }
50291
50598
  return Reflect.get(target, prop, receiver);
@@ -50383,33 +50690,95 @@ function getSkillMdPaths(modules) {
50383
50690
  }
50384
50691
  function getSkillMdContent(modules, path) {
50385
50692
  const normalized = normalizeSkillModuleKeys(modules);
50386
- return normalized[path];
50693
+ const exactMatch = normalized[path];
50694
+ if (exactMatch) return exactMatch;
50695
+ const suffix = path.replace(/^\.?\//, "/");
50696
+ const matchingKey = Object.keys(normalized).find((key) => key.endsWith(suffix));
50697
+ return matchingKey ? normalized[matchingKey] : void 0;
50387
50698
  }
50388
50699
  function getMainSkillPathByName(modules, name16) {
50389
- return getMainSkillPaths(modules).find((p) => p.startsWith(`./${name16}/SKILL.md`));
50700
+ const normalizedModules = normalizeSkillModuleKeys(modules);
50701
+ const paths = getMainSkillPaths(normalizedModules);
50702
+ const dirMatch = paths.find((p) => p.startsWith(`./${name16}/SKILL.md`));
50703
+ if (dirMatch) return dirMatch;
50704
+ for (const p of paths) {
50705
+ const content = normalizedModules[p];
50706
+ if (content) {
50707
+ const parsed = parseSkillFrontMatter(content);
50708
+ if (parsed && parsed.name === name16) {
50709
+ return p;
50710
+ }
50711
+ }
50712
+ }
50713
+ return void 0;
50390
50714
  }
50391
50715
  const SKILL_INPUT_SCHEMA = objectType({
50392
- skillName: stringType().optional().describe("技能名称,与目录名一致,如 calculator"),
50393
- path: stringType().optional().describe("文档相对路径,如 ./calculator/SKILL.md 或 ./product-guide/reference/xxx.json")
50716
+ skillName: stringType().optional().describe(
50717
+ '进入某个技能的主入口名称。优先匹配技能的目录名(如 ecommerce),或者技能的中文名称(如"客户价保单创建及审核")。'
50718
+ ),
50719
+ path: stringType().optional().describe("你想查阅的文档的路径。如 ./calculator/SKILL.md 或从其他文档里看到的相对路径 ./reference/inventory.md。"),
50720
+ currentPath: stringType().optional().describe(
50721
+ "你当前正在阅读的文档路径(如果有)。比如你刚刚读取了 ./ecommerce/SKILL.md,请把这个路径原样传回来,这样系统才能根据你的相对路径准确找到下一份文件。"
50722
+ )
50394
50723
  });
50395
50724
  function createSkillTools(modules) {
50396
50725
  const normalizedModules = normalizeSkillModuleKeys(modules);
50397
50726
  const getSkillContent = tool({
50398
- description: "根据技能名称或文档路径获取该技能的完整文档内容。传入 skillName(如 calculator)或 path(如 ./calculator/SKILL.md)。支持 .md、.json、.xml 等各类文本格式文件。",
50727
+ description: "根据技能名称或文档路径获取该技能的完整文档内容。如果你想根据相对路径查阅文件,请务必同时提供你当前所在的文件路径 currentPath。",
50399
50728
  inputSchema: SKILL_INPUT_SCHEMA,
50400
50729
  execute: (args) => {
50401
- const { skillName, path: pathArg } = args;
50730
+ const { skillName, path: pathArg, currentPath: currentPathArg } = args;
50402
50731
  let content;
50732
+ let resolvedPath = "";
50403
50733
  if (pathArg) {
50404
- content = getSkillMdContent(normalizedModules, pathArg);
50734
+ let basePathContext = ".";
50735
+ if (currentPathArg) {
50736
+ const lastSlashIndex = currentPathArg.lastIndexOf("/");
50737
+ if (lastSlashIndex >= 0) {
50738
+ basePathContext = currentPathArg.slice(0, lastSlashIndex);
50739
+ }
50740
+ }
50741
+ const dummyBase = `http://localhost/${basePathContext}/`;
50742
+ const url2 = new URL(pathArg, dummyBase);
50743
+ resolvedPath = "." + url2.pathname;
50744
+ content = getSkillMdContent(normalizedModules, resolvedPath);
50745
+ if (content === void 0 && (pathArg.startsWith("./") || pathArg.startsWith("../")) && currentPathArg) {
50746
+ const baseParts = currentPathArg.split("/");
50747
+ if (baseParts.length >= 2) {
50748
+ const skillRoot = baseParts[1];
50749
+ const fallbackDummyBase = `http://localhost/${skillRoot}/`;
50750
+ const fallbackUrl = new URL(pathArg, fallbackDummyBase);
50751
+ const fallbackPath = "." + fallbackUrl.pathname;
50752
+ content = getSkillMdContent(normalizedModules, fallbackPath);
50753
+ if (content) {
50754
+ resolvedPath = fallbackPath;
50755
+ }
50756
+ }
50757
+ }
50758
+ if (content && !normalizedModules[resolvedPath]) {
50759
+ const suffix = resolvedPath.replace(/^\.?\//, "/");
50760
+ const matchingKey = Object.keys(normalizedModules).find((key) => key.endsWith(suffix));
50761
+ if (matchingKey) {
50762
+ resolvedPath = matchingKey;
50763
+ }
50764
+ }
50405
50765
  } else if (skillName) {
50406
50766
  const mainPath = getMainSkillPathByName(normalizedModules, skillName);
50407
- content = mainPath ? getSkillMdContent(normalizedModules, mainPath) : void 0;
50767
+ if (mainPath) {
50768
+ resolvedPath = mainPath;
50769
+ content = getSkillMdContent(normalizedModules, mainPath);
50770
+ }
50408
50771
  }
50409
50772
  if (content === void 0) {
50410
- return { error: "未找到对应技能文档", skillName: skillName ?? pathArg };
50773
+ return {
50774
+ error: "未找到对应技能文档",
50775
+ skillName,
50776
+ path: pathArg,
50777
+ providedCurrentPath: currentPathArg,
50778
+ attemptedPath: resolvedPath
50779
+ };
50411
50780
  }
50412
- return { content, path: pathArg ?? getMainSkillPathByName(normalizedModules, skillName) };
50781
+ return { content, path: resolvedPath };
50413
50782
  }
50414
50783
  });
50415
50784
  return {