@opentiny/next-sdk 0.2.7 → 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.
@@ -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";
@@ -50166,6 +50466,7 @@ const MSG_PAGE_LEAVE = "next-sdk:page-leave";
50166
50466
  const MSG_REMOTER_READY = "next-sdk:remoter-ready";
50167
50467
  const MSG_ROUTE_STATE_INITIAL = "next-sdk:route-state-initial";
50168
50468
  const activePages = /* @__PURE__ */ new Map();
50469
+ const normalizeRoute = (value) => value.replace(/\/+$/, "") || "/";
50169
50470
  const broadcastTargets = /* @__PURE__ */ new Set();
50170
50471
  function initBroadcastTargets() {
50171
50472
  if (typeof window !== "undefined") {
@@ -50212,7 +50513,89 @@ let _navigator = null;
50212
50513
  function setNavigator(fn) {
50213
50514
  _navigator = fn;
50214
50515
  }
50215
- function buildPageHandler(name16, route, timeout = 3e4) {
50516
+ function waitForPageReady(path, timeoutMs = 1500) {
50517
+ if (typeof window === "undefined") {
50518
+ return Promise.resolve();
50519
+ }
50520
+ const target = normalizeRoute(path);
50521
+ return new Promise((resolve2) => {
50522
+ let done = false;
50523
+ const cleanup = () => {
50524
+ if (done) return;
50525
+ done = true;
50526
+ window.removeEventListener("message", handleMessage);
50527
+ resolve2();
50528
+ };
50529
+ const handleMessage = (event) => {
50530
+ if (event.source !== window || event.data?.type !== MSG_PAGE_READY) return;
50531
+ const route = normalizeRoute(String(event.data.route ?? ""));
50532
+ if (route === target) {
50533
+ cleanup();
50534
+ }
50535
+ };
50536
+ window.addEventListener("message", handleMessage);
50537
+ setTimeout(cleanup, timeoutMs);
50538
+ });
50539
+ }
50540
+ function registerNavigateTool(server, options) {
50541
+ const name16 = options?.name ?? "navigate_to_page";
50542
+ const title2 = options?.title ?? "页面跳转";
50543
+ const description2 = options?.description ?? '当需要的工具在当前页面不可用时,使用此工具跳转到特定页面。例如:要查询订单时跳转到 "/orders",要创建价保时跳转到 "/price-protection"。';
50544
+ const timeoutMs = options?.timeoutMs ?? 1500;
50545
+ return server.registerTool(
50546
+ name16,
50547
+ {
50548
+ title: title2,
50549
+ description: description2,
50550
+ inputSchema: {
50551
+ path: stringType().describe('目标页面的路由地址,例如 "/orders"、"/inventory"、"/price-protection" 等。')
50552
+ }
50553
+ },
50554
+ async ({ path }) => {
50555
+ if (typeof window === "undefined") {
50556
+ return {
50557
+ content: [{ type: "text", text: "当前环境不支持页面跳转(window 不存在)。" }]
50558
+ };
50559
+ }
50560
+ if (!_navigator) {
50561
+ return {
50562
+ content: [
50563
+ {
50564
+ type: "text",
50565
+ text: "页面跳转失败:尚未在应用入口调用 setNavigator 注册导航函数,无法执行路由跳转。"
50566
+ }
50567
+ ]
50568
+ };
50569
+ }
50570
+ try {
50571
+ const target = normalizeRoute(path);
50572
+ const current = normalizeRoute(window.location.pathname);
50573
+ const isAlreadyOnTarget = current === target || current.endsWith(target) && (current.length === target.length || current[current.lastIndexOf(target) - 1] === "/");
50574
+ if (isAlreadyOnTarget) {
50575
+ return {
50576
+ content: [{ type: "text", text: `当前已在页面:${path}。请继续你的下一步操作。` }]
50577
+ };
50578
+ }
50579
+ const readyPromise = waitForPageReady(path, timeoutMs);
50580
+ await _navigator(path);
50581
+ await readyPromise;
50582
+ return {
50583
+ content: [{ type: "text", text: `已成功跳转至页面:${path}。请继续你的下一步操作。` }]
50584
+ };
50585
+ } catch (err) {
50586
+ return {
50587
+ content: [
50588
+ {
50589
+ type: "text",
50590
+ text: `页面跳转失败:${err instanceof Error ? err.message : String(err)}。`
50591
+ }
50592
+ ]
50593
+ };
50594
+ }
50595
+ }
50596
+ );
50597
+ }
50598
+ function buildPageHandler(name16, route, timeout = 3e4, effectConfig) {
50216
50599
  return (input) => {
50217
50600
  const callId = randomUUID();
50218
50601
  return new Promise((resolve2, reject) => {
@@ -50224,6 +50607,9 @@ function buildPageHandler(name16, route, timeout = 3e4) {
50224
50607
  if (readyHandler) {
50225
50608
  window.removeEventListener("message", readyHandler);
50226
50609
  }
50610
+ if (effectConfig) {
50611
+ hideToolInvokeEffect();
50612
+ }
50227
50613
  };
50228
50614
  timer = setTimeout(() => {
50229
50615
  cleanup();
@@ -50247,6 +50633,9 @@ function buildPageHandler(name16, route, timeout = 3e4) {
50247
50633
  };
50248
50634
  const run = async () => {
50249
50635
  try {
50636
+ if (effectConfig) {
50637
+ showToolInvokeEffect(effectConfig);
50638
+ }
50250
50639
  if (activePages.get(route)) {
50251
50640
  sendCallOnce();
50252
50641
  return;
@@ -50283,9 +50672,10 @@ function withPageTools(server) {
50283
50672
  if (typeof handlerOrRoute === "function") {
50284
50673
  return rawRegister(name16, config2, handlerOrRoute);
50285
50674
  }
50286
- const { route, timeout } = handlerOrRoute;
50675
+ const { route, timeout, invokeEffect } = handlerOrRoute;
50287
50676
  toolRouteMap.set(name16, route);
50288
- return rawRegister(name16, config2, buildPageHandler(name16, route, timeout));
50677
+ const effectConfig = resolveRuntimeEffectConfig(name16, config2?.title, invokeEffect);
50678
+ return rawRegister(name16, config2, buildPageHandler(name16, route, timeout, effectConfig));
50289
50679
  };
50290
50680
  }
50291
50681
  return Reflect.get(target, prop, receiver);
@@ -50294,10 +50684,10 @@ function withPageTools(server) {
50294
50684
  }
50295
50685
  function registerPageTool(options) {
50296
50686
  const { route: routeOption, handlers } = options;
50297
- const normalizeRoute = (value) => value.replace(/\/+$/, "") || "/";
50298
- const route = normalizeRoute(routeOption ?? window.location.pathname);
50687
+ const normalizeRoute2 = (value) => value.replace(/\/+$/, "") || "/";
50688
+ const route = normalizeRoute2(routeOption ?? window.location.pathname);
50299
50689
  const handleMessage = async (event) => {
50300
- if (event.source !== window || event.data?.type !== MSG_TOOL_CALL || normalizeRoute(String(event.data?.route ?? "")) !== route || !(event.data.toolName in handlers)) {
50690
+ if (event.source !== window || event.data?.type !== MSG_TOOL_CALL || normalizeRoute2(String(event.data?.route ?? "")) !== route || !(event.data.toolName in handlers)) {
50301
50691
  return;
50302
50692
  }
50303
50693
  const { callId, toolName, input } = event.data;
@@ -50383,33 +50773,95 @@ function getSkillMdPaths(modules) {
50383
50773
  }
50384
50774
  function getSkillMdContent(modules, path) {
50385
50775
  const normalized = normalizeSkillModuleKeys(modules);
50386
- return normalized[path];
50776
+ const exactMatch = normalized[path];
50777
+ if (exactMatch) return exactMatch;
50778
+ const suffix = path.replace(/^\.?\//, "/");
50779
+ const matchingKey = Object.keys(normalized).find((key) => key.endsWith(suffix));
50780
+ return matchingKey ? normalized[matchingKey] : void 0;
50387
50781
  }
50388
50782
  function getMainSkillPathByName(modules, name16) {
50389
- return getMainSkillPaths(modules).find((p) => p.startsWith(`./${name16}/SKILL.md`));
50783
+ const normalizedModules = normalizeSkillModuleKeys(modules);
50784
+ const paths = getMainSkillPaths(normalizedModules);
50785
+ const dirMatch = paths.find((p) => p.startsWith(`./${name16}/SKILL.md`));
50786
+ if (dirMatch) return dirMatch;
50787
+ for (const p of paths) {
50788
+ const content = normalizedModules[p];
50789
+ if (content) {
50790
+ const parsed = parseSkillFrontMatter(content);
50791
+ if (parsed && parsed.name === name16) {
50792
+ return p;
50793
+ }
50794
+ }
50795
+ }
50796
+ return void 0;
50390
50797
  }
50391
50798
  const SKILL_INPUT_SCHEMA = objectType({
50392
- skillName: stringType().optional().describe("技能名称,与目录名一致,如 calculator"),
50393
- path: stringType().optional().describe("文档相对路径,如 ./calculator/SKILL.md 或 ./product-guide/reference/xxx.json")
50799
+ skillName: stringType().optional().describe(
50800
+ '进入某个技能的主入口名称。优先匹配技能的目录名(如 ecommerce),或者技能的中文名称(如"客户价保单创建及审核")。'
50801
+ ),
50802
+ path: stringType().optional().describe("你想查阅的文档的路径。如 ./calculator/SKILL.md 或从其他文档里看到的相对路径 ./reference/inventory.md。"),
50803
+ currentPath: stringType().optional().describe(
50804
+ "你当前正在阅读的文档路径(如果有)。比如你刚刚读取了 ./ecommerce/SKILL.md,请把这个路径原样传回来,这样系统才能根据你的相对路径准确找到下一份文件。"
50805
+ )
50394
50806
  });
50395
50807
  function createSkillTools(modules) {
50396
50808
  const normalizedModules = normalizeSkillModuleKeys(modules);
50397
50809
  const getSkillContent = tool({
50398
- description: "根据技能名称或文档路径获取该技能的完整文档内容。传入 skillName(如 calculator)或 path(如 ./calculator/SKILL.md)。支持 .md、.json、.xml 等各类文本格式文件。",
50810
+ description: "根据技能名称或文档路径获取该技能的完整文档内容。如果你想根据相对路径查阅文件,请务必同时提供你当前所在的文件路径 currentPath。",
50399
50811
  inputSchema: SKILL_INPUT_SCHEMA,
50400
50812
  execute: (args) => {
50401
- const { skillName, path: pathArg } = args;
50813
+ const { skillName, path: pathArg, currentPath: currentPathArg } = args;
50402
50814
  let content;
50815
+ let resolvedPath = "";
50403
50816
  if (pathArg) {
50404
- content = getSkillMdContent(normalizedModules, pathArg);
50817
+ let basePathContext = ".";
50818
+ if (currentPathArg) {
50819
+ const lastSlashIndex = currentPathArg.lastIndexOf("/");
50820
+ if (lastSlashIndex >= 0) {
50821
+ basePathContext = currentPathArg.slice(0, lastSlashIndex);
50822
+ }
50823
+ }
50824
+ const dummyBase = `http://localhost/${basePathContext}/`;
50825
+ const url2 = new URL(pathArg, dummyBase);
50826
+ resolvedPath = "." + url2.pathname;
50827
+ content = getSkillMdContent(normalizedModules, resolvedPath);
50828
+ if (content === void 0 && (pathArg.startsWith("./") || pathArg.startsWith("../")) && currentPathArg) {
50829
+ const baseParts = currentPathArg.split("/");
50830
+ if (baseParts.length >= 2) {
50831
+ const skillRoot = baseParts[1];
50832
+ const fallbackDummyBase = `http://localhost/${skillRoot}/`;
50833
+ const fallbackUrl = new URL(pathArg, fallbackDummyBase);
50834
+ const fallbackPath = "." + fallbackUrl.pathname;
50835
+ content = getSkillMdContent(normalizedModules, fallbackPath);
50836
+ if (content) {
50837
+ resolvedPath = fallbackPath;
50838
+ }
50839
+ }
50840
+ }
50841
+ if (content && !normalizedModules[resolvedPath]) {
50842
+ const suffix = resolvedPath.replace(/^\.?\//, "/");
50843
+ const matchingKey = Object.keys(normalizedModules).find((key) => key.endsWith(suffix));
50844
+ if (matchingKey) {
50845
+ resolvedPath = matchingKey;
50846
+ }
50847
+ }
50405
50848
  } else if (skillName) {
50406
50849
  const mainPath = getMainSkillPathByName(normalizedModules, skillName);
50407
- content = mainPath ? getSkillMdContent(normalizedModules, mainPath) : void 0;
50850
+ if (mainPath) {
50851
+ resolvedPath = mainPath;
50852
+ content = getSkillMdContent(normalizedModules, mainPath);
50853
+ }
50408
50854
  }
50409
50855
  if (content === void 0) {
50410
- return { error: "未找到对应技能文档", skillName: skillName ?? pathArg };
50856
+ return {
50857
+ error: "未找到对应技能文档",
50858
+ skillName,
50859
+ path: pathArg,
50860
+ providedCurrentPath: currentPathArg,
50861
+ attemptedPath: resolvedPath
50862
+ };
50411
50863
  }
50412
- return { content, path: pathArg ?? getMainSkillPathByName(normalizedModules, skillName) };
50864
+ return { content, path: resolvedPath };
50413
50865
  }
50414
50866
  });
50415
50867
  return {
@@ -50457,6 +50909,7 @@ export {
50457
50909
  isSSEClientTransport,
50458
50910
  isStreamableHTTPClientTransport,
50459
50911
  parseSkillFrontMatter,
50912
+ registerNavigateTool,
50460
50913
  registerPageTool,
50461
50914
  setNavigator,
50462
50915
  withPageTools,