@opentiny/next-sdk 0.3.0-alpha.0 → 0.3.1

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/WebMcpClient.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  createStreamProxy,
23
23
  createSocketProxy
24
24
  } from '@opentiny/next'
25
+ import { setupBuiltinProxy } from './utils/builtinProxy'
25
26
  import type {
26
27
  Result,
27
28
  Request,
@@ -69,6 +70,7 @@ export interface ClientConnectOptions {
69
70
  sessionId?: string
70
71
  type?: 'channel' | 'sse' | 'stream' | 'socket'
71
72
  agent?: boolean
73
+ builtin?: boolean
72
74
  onError?: (error: Error) => void
73
75
  }
74
76
 
@@ -120,7 +122,7 @@ export class WebMcpClient {
120
122
  return { transport: this.transport, sessionId: this.transport.sessionId as string }
121
123
  }
122
124
 
123
- const { url, token, sessionId, type, agent, onError } = options as ClientConnectOptions
125
+ const { url, token, sessionId, type, agent, builtin, onError } = options as ClientConnectOptions
124
126
 
125
127
  if (agent === true) {
126
128
  const proxyOptions: ProxyOptions = { client: this.client, url, token, sessionId }
@@ -139,6 +141,10 @@ export class WebMcpClient {
139
141
  onError?.(error)
140
142
  }
141
143
 
144
+ if (builtin === true) {
145
+ setupBuiltinProxy(transport)
146
+ }
147
+
142
148
  response = { transport, sessionId }
143
149
  }
144
150
 
@@ -12,7 +12,8 @@ import { createDeepSeek, DeepSeekProvider } from '@ai-sdk/deepseek'
12
12
  import { ExtensionClientTransport } from '../transport/ExtensionClientTransport'
13
13
  import { MessageChannelTransport } from '@opentiny/next'
14
14
  import { WebMcpClient } from '../WebMcpClient'
15
- import { getAISDKTools } from './utils/getAISDKTools'
15
+ import { getAISDKTools } from './utils/getAISDKTools'
16
+ import { getBuiltinMcpTools } from './utils/getBuiltinMcpTools'
16
17
  import { generateReActToolsPrompt } from './utils/generateReActPrompt'
17
18
  import { parseReActAction } from './utils/parseReActAction'
18
19
 
@@ -82,6 +83,15 @@ export class AgentModelProvider {
82
83
  private async _createOneClient(serverConfig: McpServerConfig) {
83
84
  try {
84
85
  let transport: MCPClientConfig['transport']
86
+ // builtin 类型:使用 navigator.modelContextTesting 作为工具数据源
87
+ if ('type' in serverConfig && serverConfig.type === 'builtin') {
88
+ const builtinClient = serverConfig.client
89
+ // 包装成与 ai-sdk MCPClient 相同接口:提供 tools() 方法
90
+ return {
91
+ tools: () => getBuiltinMcpTools(builtinClient),
92
+ close: async () => {}
93
+ }
94
+ }
85
95
  // transport 一定是 streamableHttp/sse 或者就是: ai-sdk允许的 transport
86
96
  if ('type' in serverConfig && serverConfig.type.toLocaleLowerCase() === 'streamablehttp') {
87
97
  const configWithHeaders = serverConfig as { url: string; headers?: Record<string, string> }
package/agent/type.ts CHANGED
@@ -4,6 +4,17 @@ import type { MCPClientConfig } from '@ai-sdk/mcp'
4
4
  // 从 MCPClientConfig 中提取 transport 类型
5
5
  export type MCPTransport = MCPClientConfig['transport']
6
6
 
7
+ /**
8
+ * 浏览器内置 WebMCP 测试 API 接口 (如 navigator.modelContextTesting)
9
+ */
10
+ export interface BuiltinMcpClient {
11
+ listTools?: () => Promise<any[]>
12
+ getTools?: () => Promise<any[]>
13
+ executeTool: (name: string, input: string) => Promise<any>
14
+ registerTool?: (config: { name: string; execute: (input: any) => Promise<any> | any; [key: string]: any }) => void
15
+ unregisterTool?: (name: string) => void
16
+ }
17
+
7
18
  type ProviderFactory = 'openai' | 'deepseek' | ((options: any) => ProviderV2)
8
19
 
9
20
  type LlmFactoryConfig = {
@@ -39,6 +50,16 @@ export type McpServerConfig =
39
50
  | { type: 'sse'; url: string; useAISdkClient?: boolean; headers?: Record<string, string> }
40
51
  | { type: 'extension'; url: string; sessionId: string; useAISdkClient?: boolean; headers?: Record<string, string> }
41
52
  | { type: 'local'; transport: MCPTransport; useAISdkClient?: boolean }
53
+ | {
54
+ /**
55
+ * 浏览器内置 WebMCP 类型。
56
+ * 将 `navigator.modelContextTesting` 作为 MCP 工具数据源,
57
+ * 通过 `getBuiltinMcpTools` 适配为 ai-sdk 可调用的 ToolSet。
58
+ */
59
+ type: 'builtin'
60
+ /** 传入 `navigator.modelContextTesting` 对象 */
61
+ client: BuiltinMcpClient
62
+ }
42
63
 
43
64
  /** */
44
65
  export interface IAgentModelProviderOption {
@@ -46,4 +67,4 @@ export interface IAgentModelProviderOption {
46
67
  llmConfig: IAgentModelProviderLlmConfig
47
68
  /** Mcp Server的配置对象的集合,键为服务器名称,值为配置对象 */
48
69
  mcpServers?: Record<string, McpServerConfig>
49
- }
70
+ }
@@ -0,0 +1,86 @@
1
+ import { dynamicTool, jsonSchema, Tool, ToolSet } from 'ai'
2
+
3
+ /**
4
+ * 浏览器内置 WebMCP 测试 API 的工具描述格式(Chrome navigator.modelContextTesting)
5
+ */
6
+ type BuiltinToolDescriptor = {
7
+ name: string
8
+ description?: string
9
+ inputSchema?: {
10
+ type?: string
11
+ properties?: Record<string, unknown>
12
+ required?: string[]
13
+ [key: string]: unknown
14
+ }
15
+ }
16
+
17
+ type BuiltinModelContextTesting = {
18
+ listTools?: () => Promise<BuiltinToolDescriptor[]>
19
+ getTools?: () => Promise<BuiltinToolDescriptor[]>
20
+ executeTool?: (name: string, input: string) => Promise<unknown>
21
+ }
22
+
23
+ /**
24
+ * 将浏览器内置 WebMCP 的 `navigator.modelContext` 适配为 ai-sdk 的 ToolSet。
25
+ *
26
+ * 类似 getAISDKTools,但数据源是浏览器原生 API 而非 MCP client。
27
+ * 工具执行时通过 `executeTool(name, JSON.stringify(args))` 代理给浏览器。
28
+ *
29
+ * @param client - `navigator.modelContext` 对象
30
+ * @returns ai-sdk 格式的 ToolSet,可直接传入 streamText/generateText 的 tools 参数
31
+ */
32
+ export const getBuiltinMcpTools = async (client: object | undefined | null): Promise<ToolSet> => {
33
+ const tools: Record<string, Tool> = {}
34
+ if (!client) {
35
+ return tools
36
+ }
37
+
38
+ const testing = client as BuiltinModelContextTesting
39
+
40
+ // 优先使用 listTools,降级到 getTools
41
+ const listFn = testing.listTools ?? testing.getTools
42
+ if (!listFn) {
43
+ return tools
44
+ }
45
+
46
+ const rawList = await listFn.call(testing)
47
+ const list = Array.isArray(rawList) ? (rawList as BuiltinToolDescriptor[]) : []
48
+
49
+ for (const descriptor of list) {
50
+ const { name, description } = descriptor
51
+ const rawInputSchema = descriptor.inputSchema
52
+ let schemaObj: Record<string, any> = {}
53
+
54
+ if (typeof rawInputSchema === 'string') {
55
+ try {
56
+ schemaObj = JSON.parse(rawInputSchema)
57
+ } catch (e) {
58
+ console.error('Failed to parse inputSchema in getBuiltinMcpTools:', e)
59
+ }
60
+ } else if (typeof rawInputSchema === 'object' && rawInputSchema !== null) {
61
+ schemaObj = rawInputSchema as Record<string, any>
62
+ }
63
+
64
+ // 规范化 inputSchema:补全 properties/additionalProperties 字段
65
+ const normalizedSchema = {
66
+ type: 'object' as const,
67
+ properties: (schemaObj.properties ?? {}) as Record<string, unknown>,
68
+ ...(schemaObj.required ? { required: schemaObj.required } : {}),
69
+ additionalProperties: false,
70
+ ...schemaObj
71
+ }
72
+
73
+ tools[name] = dynamicTool({
74
+ description: description ?? '',
75
+ inputSchema: jsonSchema(normalizedSchema as Parameters<typeof jsonSchema>[0]),
76
+ async execute(args) {
77
+ if (!testing.executeTool) {
78
+ throw new Error(`navigator.modelContextTesting.executeTool is not available`)
79
+ }
80
+ return testing.executeTool(name, JSON.stringify(args ?? {}))
81
+ }
82
+ })
83
+ }
84
+
85
+ return tools
86
+ }