@opentiny/next-sdk 0.2.8 → 0.2.10

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.
@@ -2,6 +2,7 @@ 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'
5
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
5
6
  import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
6
7
  import type { IAgentModelProviderOption, McpServerConfig } from './type'
7
8
  import { ProviderV2 } from '@ai-sdk/provider'
@@ -81,15 +82,21 @@ export class AgentModelProvider {
81
82
  private async _createOneClient(serverConfig: McpServerConfig) {
82
83
  try {
83
84
  let transport: MCPClientConfig['transport']
84
- // transport 一定是 streamableHttp 或者就是: ai-sdk允许的 transport
85
+ // transport 一定是 streamableHttp/sse 或者就是: ai-sdk允许的 transport
85
86
  if ('type' in serverConfig && serverConfig.type.toLocaleLowerCase() === 'streamablehttp') {
86
- transport = new StreamableHTTPClientTransport(new URL((serverConfig as { url: string }).url))
87
+ const configWithHeaders = serverConfig as { url: string; headers?: Record<string, string> }
88
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : undefined
89
+ transport = new StreamableHTTPClientTransport(new URL(configWithHeaders.url), { requestInit })
90
+ } else if ('type' in serverConfig && serverConfig.type === 'sse') {
91
+ const configWithHeaders = serverConfig as { url: string; headers?: Record<string, string> }
92
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : undefined
93
+ transport = new SSEClientTransport(new URL(configWithHeaders.url), { requestInit })
87
94
  } else if ('type' in serverConfig && serverConfig.type === 'extension') {
88
95
  transport = new ExtensionClientTransport(serverConfig.sessionId)
89
96
  } else if ('transport' in serverConfig) {
90
97
  transport = serverConfig.transport
91
98
  } else {
92
- transport = serverConfig as MCPClientConfig['transport']
99
+ transport = serverConfig as unknown as MCPClientConfig['transport']
93
100
  }
94
101
 
95
102
  // 根据 useAISdkClient 配置决定使用哪种 client 创建方式
package/agent/type.ts CHANGED
@@ -35,9 +35,9 @@ export type IAgentModelProviderLlmConfig = LlmFactoryConfig | LlmInstanceConfig
35
35
 
36
36
  /** Mcp Server的配置对象 */
37
37
  export type McpServerConfig =
38
- | { type: 'streamableHttp'; url: string; useAISdkClient?: boolean }
39
- | { type: 'sse'; url: string; useAISdkClient?: boolean }
40
- | { type: 'extension'; url: string; sessionId: string; useAISdkClient?: boolean }
38
+ | { type: 'streamableHttp'; url: string; useAISdkClient?: boolean; headers?: Record<string, string> }
39
+ | { type: 'sse'; url: string; useAISdkClient?: boolean; headers?: Record<string, string> }
40
+ | { type: 'extension'; url: string; sessionId: string; useAISdkClient?: boolean; headers?: Record<string, string> }
41
41
  | { type: 'local'; transport: MCPTransport; useAISdkClient?: boolean }
42
42
 
43
43
  /** */
@@ -32,15 +32,18 @@ export type McpServerConfig = {
32
32
  type: 'streamableHttp';
33
33
  url: string;
34
34
  useAISdkClient?: boolean;
35
+ headers?: Record<string, string>;
35
36
  } | {
36
37
  type: 'sse';
37
38
  url: string;
38
39
  useAISdkClient?: boolean;
40
+ headers?: Record<string, string>;
39
41
  } | {
40
42
  type: 'extension';
41
43
  url: string;
42
44
  sessionId: string;
43
45
  useAISdkClient?: boolean;
46
+ headers?: Record<string, string>;
44
47
  } | {
45
48
  type: 'local';
46
49
  transport: MCPTransport;
@@ -27069,27 +27069,27 @@ class FloatingBlock {
27069
27069
  }
27070
27070
  /**
27071
27071
  * 合并菜单项配置。
27072
- * - sessionId:使用默认菜单 + 用户配置(可定制每一项的 show/text/icon 等)
27073
- * - sessionId:不渲染任何下拉菜单,仅保留点击浮标打开对话框的能力
27072
+ * - 用户明确传入 menuItems:直接使用用户配置,不受 sessionId 限制;未传 icon 时自动补充默认图标
27073
+ * - sessionId 且未传 menuItems:使用默认菜单
27074
+ * - 无 sessionId 且未传 menuItems:不渲染任何下拉菜单,仅保留点击浮标打开对话框的能力
27074
27075
  */
27075
27076
  mergeMenuItems(userMenuItems) {
27077
+ const defaultIcons = {
27078
+ "qr-code": qrCode,
27079
+ "ai-chat": chat,
27080
+ "remote-url": link,
27081
+ "remote-control": scan
27082
+ };
27083
+ if (userMenuItems) {
27084
+ return userMenuItems.map((item) => ({
27085
+ ...item,
27086
+ icon: item.icon ?? defaultIcons[item.action]
27087
+ }));
27088
+ }
27076
27089
  if (!this.options.sessionId) {
27077
27090
  return [];
27078
27091
  }
27079
- if (!userMenuItems) {
27080
- return getDefaultMenuItems(this.options);
27081
- }
27082
- return getDefaultMenuItems(this.options).map((defaultItem) => {
27083
- const userItem = userMenuItems.find((item) => item.action === defaultItem.action);
27084
- if (userItem) {
27085
- return {
27086
- ...defaultItem,
27087
- ...userItem,
27088
- show: userItem.show !== void 0 ? userItem.show : defaultItem.show
27089
- };
27090
- }
27091
- return defaultItem;
27092
- });
27092
+ return getDefaultMenuItems(this.options);
27093
27093
  }
27094
27094
  init() {
27095
27095
  this.createFloatingBlock();
@@ -27120,7 +27120,7 @@ class FloatingBlock {
27120
27120
  <div class="tiny-remoter-dropdown-item__content">
27121
27121
  <div title="${item.tip}">${item.text}</div>
27122
27122
  <div class="tiny-remoter-dropdown-item__desc-wrapper">
27123
- <div class="tiny-remoter-dropdown-item__desc ${item.active ? "tiny-remoter-dropdown-item__desc--active" : ""} ${item.know ? "tiny-remoter-dropdown-item__desc--know" : ""}">${item.desc}</div>
27123
+ <div class="tiny-remoter-dropdown-item__desc ${item.active ? "tiny-remoter-dropdown-item__desc--active" : ""} ${item.know ? "tiny-remoter-dropdown-item__desc--know" : ""}">${item.desc ?? ""}</div>
27124
27124
  <div>
27125
27125
  ${item.showCopyIcon ? `
27126
27126
  <div class="tiny-remoter-copy-icon" id="${item.action}" data-action="${item.action}">
@@ -27228,12 +27228,20 @@ class FloatingBlock {
27228
27228
  this.closeDropdown();
27229
27229
  }
27230
27230
  copyRemoteControl() {
27231
- if (!this.options.sessionId) return;
27232
- this.copyToClipboard(this.options.sessionId.slice(-6));
27231
+ const menuItem = this.menuItems.find((item) => item.action === "remote-control");
27232
+ const codeToCopy = menuItem?.desc || menuItem?.text || (this.options.sessionId ? this.options.sessionId.slice(-6) : "");
27233
+ if (codeToCopy) {
27234
+ this.copyToClipboard(codeToCopy);
27235
+ }
27233
27236
  }
27234
27237
  copyRemoteURL() {
27235
- if (!this.options.sessionId) return;
27236
- this.copyToClipboard(this.options.remoteUrl + this.sessionPrefix + this.options.sessionId);
27238
+ const menuItem = this.menuItems.find((item) => item.action === "remote-url");
27239
+ const sessionUrl = this.options.sessionId ? this.options.remoteUrl + this.sessionPrefix + this.options.sessionId : "";
27240
+ const customDesc = menuItem?.desc && menuItem.desc !== this.options.remoteUrl ? menuItem.desc : void 0;
27241
+ const urlToCopy = customDesc || sessionUrl || menuItem?.text || "";
27242
+ if (urlToCopy) {
27243
+ this.copyToClipboard(urlToCopy);
27244
+ }
27237
27245
  }
27238
27246
  // 实现复制到剪贴板功能
27239
27247
  async copyToClipboard(text2) {
@@ -49629,7 +49637,13 @@ class AgentModelProvider {
49629
49637
  try {
49630
49638
  let transport;
49631
49639
  if ("type" in serverConfig && serverConfig.type.toLocaleLowerCase() === "streamablehttp") {
49632
- transport = new StreamableHTTPClientTransport(new URL(serverConfig.url));
49640
+ const configWithHeaders = serverConfig;
49641
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : void 0;
49642
+ transport = new StreamableHTTPClientTransport(new URL(configWithHeaders.url), { requestInit });
49643
+ } else if ("type" in serverConfig && serverConfig.type === "sse") {
49644
+ const configWithHeaders = serverConfig;
49645
+ const requestInit = configWithHeaders.headers ? { headers: configWithHeaders.headers } : void 0;
49646
+ transport = new SSEClientTransport(new URL(configWithHeaders.url), { requestInit });
49633
49647
  } else if ("type" in serverConfig && serverConfig.type === "extension") {
49634
49648
  transport = new ExtensionClientTransport(serverConfig.sessionId);
49635
49649
  } else if ("transport" in serverConfig) {
@@ -50466,6 +50480,7 @@ const MSG_PAGE_LEAVE = "next-sdk:page-leave";
50466
50480
  const MSG_REMOTER_READY = "next-sdk:remoter-ready";
50467
50481
  const MSG_ROUTE_STATE_INITIAL = "next-sdk:route-state-initial";
50468
50482
  const activePages = /* @__PURE__ */ new Map();
50483
+ const normalizeRoute = (value) => value.replace(/\/+$/, "") || "/";
50469
50484
  const broadcastTargets = /* @__PURE__ */ new Set();
50470
50485
  function initBroadcastTargets() {
50471
50486
  if (typeof window !== "undefined") {
@@ -50512,6 +50527,88 @@ let _navigator = null;
50512
50527
  function setNavigator(fn) {
50513
50528
  _navigator = fn;
50514
50529
  }
50530
+ function waitForPageReady(path, timeoutMs = 1500) {
50531
+ if (typeof window === "undefined") {
50532
+ return Promise.resolve();
50533
+ }
50534
+ const target = normalizeRoute(path);
50535
+ return new Promise((resolve2) => {
50536
+ let done = false;
50537
+ const cleanup = () => {
50538
+ if (done) return;
50539
+ done = true;
50540
+ window.removeEventListener("message", handleMessage);
50541
+ resolve2();
50542
+ };
50543
+ const handleMessage = (event) => {
50544
+ if (event.source !== window || event.data?.type !== MSG_PAGE_READY) return;
50545
+ const route = normalizeRoute(String(event.data.route ?? ""));
50546
+ if (route === target) {
50547
+ cleanup();
50548
+ }
50549
+ };
50550
+ window.addEventListener("message", handleMessage);
50551
+ setTimeout(cleanup, timeoutMs);
50552
+ });
50553
+ }
50554
+ function registerNavigateTool(server, options) {
50555
+ const name16 = options?.name ?? "navigate_to_page";
50556
+ const title2 = options?.title ?? "页面跳转";
50557
+ const description2 = options?.description ?? '当需要的工具在当前页面不可用时,使用此工具跳转到特定页面。例如:要查询订单时跳转到 "/orders",要创建价保时跳转到 "/price-protection"。';
50558
+ const timeoutMs = options?.timeoutMs ?? 1500;
50559
+ return server.registerTool(
50560
+ name16,
50561
+ {
50562
+ title: title2,
50563
+ description: description2,
50564
+ inputSchema: {
50565
+ path: stringType().describe('目标页面的路由地址,例如 "/orders"、"/inventory"、"/price-protection" 等。')
50566
+ }
50567
+ },
50568
+ async ({ path }) => {
50569
+ if (typeof window === "undefined") {
50570
+ return {
50571
+ content: [{ type: "text", text: "当前环境不支持页面跳转(window 不存在)。" }]
50572
+ };
50573
+ }
50574
+ if (!_navigator) {
50575
+ return {
50576
+ content: [
50577
+ {
50578
+ type: "text",
50579
+ text: "页面跳转失败:尚未在应用入口调用 setNavigator 注册导航函数,无法执行路由跳转。"
50580
+ }
50581
+ ]
50582
+ };
50583
+ }
50584
+ try {
50585
+ const target = normalizeRoute(path);
50586
+ const current = normalizeRoute(window.location.pathname);
50587
+ const isAlreadyOnTarget = current === target || current.endsWith(target) && (current.length === target.length || current[current.lastIndexOf(target) - 1] === "/");
50588
+ if (isAlreadyOnTarget) {
50589
+ return {
50590
+ content: [{ type: "text", text: `当前已在页面:${path}。请继续你的下一步操作。` }]
50591
+ };
50592
+ }
50593
+ const readyPromise = waitForPageReady(path, timeoutMs);
50594
+ await _navigator(path);
50595
+ await readyPromise;
50596
+ return {
50597
+ content: [{ type: "text", text: `已成功跳转至页面:${path}。请继续你的下一步操作。` }]
50598
+ };
50599
+ } catch (err) {
50600
+ return {
50601
+ content: [
50602
+ {
50603
+ type: "text",
50604
+ text: `页面跳转失败:${err instanceof Error ? err.message : String(err)}。`
50605
+ }
50606
+ ]
50607
+ };
50608
+ }
50609
+ }
50610
+ );
50611
+ }
50515
50612
  function buildPageHandler(name16, route, timeout = 3e4, effectConfig) {
50516
50613
  return (input) => {
50517
50614
  const callId = randomUUID();
@@ -50601,10 +50698,10 @@ function withPageTools(server) {
50601
50698
  }
50602
50699
  function registerPageTool(options) {
50603
50700
  const { route: routeOption, handlers } = options;
50604
- const normalizeRoute = (value) => value.replace(/\/+$/, "") || "/";
50605
- const route = normalizeRoute(routeOption ?? window.location.pathname);
50701
+ const normalizeRoute2 = (value) => value.replace(/\/+$/, "") || "/";
50702
+ const route = normalizeRoute2(routeOption ?? window.location.pathname);
50606
50703
  const handleMessage = async (event) => {
50607
- if (event.source !== window || event.data?.type !== MSG_TOOL_CALL || normalizeRoute(String(event.data?.route ?? "")) !== route || !(event.data.toolName in handlers)) {
50704
+ if (event.source !== window || event.data?.type !== MSG_TOOL_CALL || normalizeRoute2(String(event.data?.route ?? "")) !== route || !(event.data.toolName in handlers)) {
50608
50705
  return;
50609
50706
  }
50610
50707
  const { callId, toolName, input } = event.data;
@@ -50826,6 +50923,7 @@ export {
50826
50923
  isSSEClientTransport,
50827
50924
  isStreamableHTTPClientTransport,
50828
50925
  parseSkillFrontMatter,
50926
+ registerNavigateTool,
50829
50927
  registerPageTool,
50830
50928
  setNavigator,
50831
50929
  withPageTools,