@opentiny/next-sdk 0.2.2 → 0.2.4

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,8 +1,5 @@
1
1
  import { streamText, stepCountIs, generateText, StreamTextResult } from 'ai'
2
- import {
3
- experimental_MCPClientConfig as MCPClientConfig,
4
- experimental_createMCPClient as createMCPClient
5
- } from '@ai-sdk/mcp'
2
+ import { MCPClientConfig, createMCPClient } from '@ai-sdk/mcp'
6
3
  import type { ToolSet } from 'ai'
7
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
8
5
  import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
@@ -10,7 +7,7 @@ import type { IAgentModelProviderOption, McpServerConfig } from './type'
10
7
  import { ProviderV2 } from '@ai-sdk/provider'
11
8
  import { OpenAIProvider } from '@ai-sdk/openai'
12
9
  import { createOpenAI } from '@ai-sdk/openai'
13
- import { createDeepSeek } from '@ai-sdk/deepseek'
10
+ import { createDeepSeek, DeepSeekProvider } from '@ai-sdk/deepseek'
14
11
  import { ExtensionClientTransport } from '../transport/ExtensionClientTransport'
15
12
  import { MessageChannelTransport } from '@opentiny/next'
16
13
  import { WebMcpClient } from '../WebMcpClient'
@@ -30,7 +27,7 @@ type ChatMethodFn = typeof streamText | typeof generateText
30
27
  * @returns 暴露了 chat, chatStream方法
31
28
  */
32
29
  export class AgentModelProvider {
33
- llm: ProviderV2 | OpenAIProvider
30
+ llm: ProviderV2 | OpenAIProvider | DeepSeekProvider
34
31
  /** 当前mcpServers对象集合。键为服务器名称,值为 McpServerConfig 或任意的 MCPTransport
35
32
  * 参考: https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#initializing-an-mcp-client */
36
33
  mcpServers: Record<string, McpServerConfig> = {}
@@ -45,7 +42,7 @@ export class AgentModelProvider {
45
42
  /** Agent 内部报错时,抛出的错误事件 */
46
43
  onError: ((msg: string, err?: any) => void) | undefined
47
44
  /** 缓存 ai-sdk response 中的 多轮会话的上下文 */
48
- messages: any[] = []
45
+ responseMessages: any[] = []
49
46
  /** 是否使用 ReAct 模式(通过提示词而非 function calling 进行工具调用) */
50
47
  useReActMode: boolean = false
51
48
 
@@ -61,7 +58,7 @@ export class AgentModelProvider {
61
58
  this.llm = llmConfig.llm
62
59
  } else if (llmConfig.providerType) {
63
60
  const providerType = llmConfig.providerType
64
- let providerFn: (options: any) => ProviderV2 | OpenAIProvider
61
+ let providerFn: (options: any) => ProviderV2 | OpenAIProvider | DeepSeekProvider
65
62
 
66
63
  if (typeof providerType === 'string') {
67
64
  providerFn = AIProviderFactories[providerType]
@@ -360,7 +357,7 @@ export class AgentModelProvider {
360
357
  } else if (options.messages) {
361
358
  currentMessages = [...options.messages]
362
359
  } else {
363
- currentMessages = [...this.messages]
360
+ currentMessages = [...this.responseMessages]
364
361
  }
365
362
 
366
363
  // 确保 model 是字符串类型(ReAct 模式下 model 应该是模型名称字符串)
@@ -434,7 +431,7 @@ export class AgentModelProvider {
434
431
  }
435
432
 
436
433
  /**
437
- * 构建用于模型调用的消息列表(magentic-ui 风格)
434
+ * 构建用于模型调用的消息列表
438
435
  * 策略:保留所有文本消息,仅限制图片数量(类似 magentic-ui 的 maybe_remove_old_screenshots)
439
436
  *
440
437
  * @param systemMessage 系统提示词
@@ -500,17 +497,16 @@ export class AgentModelProvider {
500
497
  const allUserMessages = systemMessage ? messages.slice(1) : messages
501
498
 
502
499
  let stepCount = 0
503
- // 配置:最多保留的图片数量(默认3张,类似 magentic-ui)
500
+ // 配置:最多保留的图片数量
504
501
  const maxImages = (options as any).maxImages ?? 3
505
502
 
506
503
  while (stepCount < maxSteps) {
507
504
  stepCount++
508
505
 
509
- // 构建用于模型调用的消息列表(magentic-ui 风格:保留所有文本,限制图片)
506
+ // 构建用于模型调用的消息列表
510
507
  const messagesForModel = this._buildMessagesForModel(systemMessage, allUserMessages, maxImages)
511
508
 
512
509
  // 调用 LLM(ReAct 模式下不传递 tools,因为工具调用通过提示词实现)
513
- // 参考 magentic-ui:保留所有文本历史(上下文完整),仅限制图片数量(优化 token)
514
510
  const { tools: _, ...restOptions } = options
515
511
  const result = await generateText({
516
512
  // @ts-ignore ProviderV2 是所有llm的父类,在每一个具体的llm类都有一个选择model的函数用法
@@ -530,7 +526,7 @@ export class AgentModelProvider {
530
526
 
531
527
  if (!action) {
532
528
  // 没有工具调用,返回最终结果
533
- this.messages = fullMessageHistory
529
+ this.responseMessages = fullMessageHistory
534
530
  return {
535
531
  text: assistantMessage,
536
532
  response: { messages: fullMessageHistory }
@@ -554,7 +550,7 @@ export class AgentModelProvider {
554
550
  }
555
551
 
556
552
  // 达到最大步数,返回最后一条消息
557
- this.messages = fullMessageHistory
553
+ this.responseMessages = fullMessageHistory
558
554
  const lastMessage = fullMessageHistory[fullMessageHistory.length - 2]?.content || ''
559
555
  return {
560
556
  text: lastMessage,
@@ -589,14 +585,18 @@ export class AgentModelProvider {
589
585
 
590
586
  let stepCount = 0
591
587
  let accumulatedText = ''
592
- // 配置:最多保留的图片数量(默认3张,类似 magentic-ui)
588
+ // 配置:最多保留的图片数量
593
589
  const maxImages = (options as any).maxImages ?? 3
594
590
 
591
+ // 模拟开启流
592
+ controller.enqueue({ type: 'start' })
593
+ controller.enqueue({ type: 'start-step' })
594
+
595
595
  try {
596
596
  while (stepCount < maxSteps) {
597
597
  stepCount++
598
598
 
599
- // 构建用于模型调用的消息列表(magentic-ui 风格:保留所有文本,限制图片)
599
+ // 构建用于模型调用的消息列表
600
600
  const messagesForModel = self._buildMessagesForModel(systemMessage, allUserMessages, maxImages)
601
601
 
602
602
  // 移除 tools 选项,ReAct 模式下不传递 tools
@@ -611,7 +611,7 @@ export class AgentModelProvider {
611
611
  messages: messagesForModel
612
612
  })
613
613
 
614
- // 收集流式输出
614
+ // 1、直接问,收集流式输出
615
615
  let assistantText = ''
616
616
  for await (const part of result.fullStream) {
617
617
  if (part.type === 'text-delta') {
@@ -623,8 +623,10 @@ export class AgentModelProvider {
623
623
  })
624
624
  } else if (part.type === 'text-start') {
625
625
  controller.enqueue({ type: 'text-start' })
626
- } else if (part.type === 'text-end') {
626
+ } else if (part.type === 'text-end' || part.type === 'finish-step' || part.type === 'finish') {
627
627
  // 暂时不关闭,等待检查是否有工具调用
628
+ } else if (part.type === 'start' || part.type === 'start-step') {
629
+ // 后面每一轮的start 也要忽略
628
630
  } else {
629
631
  // 转发其他类型的事件
630
632
  controller.enqueue(part)
@@ -637,14 +639,16 @@ export class AgentModelProvider {
637
639
  allUserMessages.push(assistantMsg)
638
640
  fullMessageHistory.push(assistantMsg)
639
641
 
640
- // 解析工具调用
642
+ // 2、解析工具调用
641
643
  const action = parseReActAction(accumulatedText, tools)
642
644
 
643
645
  if (!action) {
644
646
  // 没有工具调用,结束流
645
647
  controller.enqueue({ type: 'text-end' })
648
+ controller.enqueue({ type: 'finish-step' })
649
+ controller.enqueue({ type: 'finish' })
646
650
  controller.close()
647
- self.messages = fullMessageHistory
651
+ self.responseMessages = fullMessageHistory
648
652
  // 触发 onFinish 回调
649
653
  streamCompleteResolver({ messages: fullMessageHistory })
650
654
  return
@@ -654,13 +658,15 @@ export class AgentModelProvider {
654
658
  if (action.toolName === 'computer' && action.arguments?.action === 'terminate') {
655
659
  // 视为对话结束
656
660
  controller.enqueue({ type: 'text-end' })
661
+ controller.enqueue({ type: 'finish-step' })
662
+ controller.enqueue({ type: 'finish' })
657
663
  controller.close()
658
- self.messages = fullMessageHistory
664
+ self.responseMessages = fullMessageHistory
659
665
  streamCompleteResolver({ messages: fullMessageHistory })
660
666
  return
661
667
  }
662
668
 
663
- // 发送工具调用开始事件(符合 tiny-robot 格式)
669
+ // 3、是工具调用,发送工具调用开始事件(符合 tiny-robot 格式)
664
670
  const toolCallId = `react-${Date.now()}`
665
671
  controller.enqueue({
666
672
  type: 'tool-input-start',
@@ -675,8 +681,19 @@ export class AgentModelProvider {
675
681
  id: toolCallId,
676
682
  delta: argsString
677
683
  })
684
+ controller.enqueue({
685
+ type: 'tool-input-end',
686
+ id: toolCallId
687
+ })
688
+
689
+ // 4、执行工具调用
690
+ controller.enqueue({
691
+ type: 'tool-call',
692
+ toolCallId: toolCallId,
693
+ toolName: action.toolName,
694
+ input: action.arguments
695
+ })
678
696
 
679
- // 执行工具调用
680
697
  const toolResult = await self._executeReActToolCall(action.toolName, action.arguments, tools)
681
698
 
682
699
  // 如果结果包含 screenshot,先提取出来,避免 JSON stringify 导致过大
@@ -708,50 +725,58 @@ export class AgentModelProvider {
708
725
  } else {
709
726
  observationText = JSON.stringify(resultData)
710
727
  }
711
- } else {
712
- observationText = `工具执行失败 - ${toolResult.error}`
713
- }
714
728
 
715
- // 统一使用 XML 格式的 Observation,如果有截图,添加验证提示
716
- let finalObservation = `<tool_response>\n${observationText}\n</tool_response>`
729
+ // 统一使用 XML 格式的 Observation,如果有截图,添加验证提示
730
+ let finalObservation = `<tool_response>\n${observationText}\n</tool_response>`
717
731
 
718
- if (screenshot) {
719
- finalObservation += `\n请检查截图以确认操作是否成功。如果成功,请继续下一步;如果失败,请重试。`
720
- }
721
-
722
- // 发送工具结果(符合 tiny-robot 格式,给 UI 展示用的,不包含 base64 防止卡顿)
723
- controller.enqueue({
724
- type: 'tool-result',
725
- toolCallId: toolCallId,
726
- result: finalObservation
727
- })
728
-
729
- // 添加工具结果到消息历史(ReAct 模式下,工具结果作为 user 消息添加)
730
- const observationMessage = screenshot
731
- ? {
732
- role: 'user',
733
- content: [
734
- { type: 'text', text: finalObservation },
735
- { type: 'image', image: screenshot }
736
- ]
737
- }
738
- : {
739
- role: 'user',
740
- content: finalObservation
741
- }
742
-
743
- // 添加到所有消息和完整历史
744
- allUserMessages.push(observationMessage)
745
- fullMessageHistory.push(observationMessage)
732
+ if (screenshot) {
733
+ finalObservation += `\n请检查截图以确认操作是否成功。如果成功,请继续下一步;如果失败,请重试。`
734
+ }
746
735
 
747
- // 重置累积文本,准备下一轮
748
- accumulatedText = ''
736
+ // 发送工具结果(符合 tiny-robot 格式,给 UI 展示用的,不包含 base64 防止卡顿)
737
+ controller.enqueue({
738
+ type: 'tool-result',
739
+ toolCallId: toolCallId,
740
+ result: finalObservation
741
+ })
742
+
743
+ // 添加工具结果到消息历史(ReAct 模式下,工具结果作为 user 消息添加)
744
+ const observationMessage = screenshot
745
+ ? {
746
+ role: 'user',
747
+ content: [
748
+ { type: 'text', text: finalObservation },
749
+ { type: 'image', image: screenshot }
750
+ ]
751
+ }
752
+ : {
753
+ role: 'user',
754
+ content: finalObservation
755
+ }
756
+
757
+ // 添加到所有消息和完整历史
758
+ allUserMessages.push(observationMessage)
759
+ fullMessageHistory.push(observationMessage)
760
+
761
+ // 重置累积文本,准备下一轮
762
+ accumulatedText = ''
763
+ } else {
764
+ observationText = `工具执行失败 - ${toolResult.error}`
765
+ controller.enqueue({
766
+ type: 'tool-error',
767
+ toolCallId: toolCallId,
768
+ input: action.arguments,
769
+ error: { message: observationText }
770
+ })
771
+ }
749
772
  }
750
773
 
751
774
  // 达到最大步数
752
775
  controller.enqueue({ type: 'text-end' })
776
+ controller.enqueue({ type: 'finish-step' })
777
+ controller.enqueue({ type: 'finish' })
753
778
  controller.close()
754
- self.messages = fullMessageHistory
779
+ self.responseMessages = fullMessageHistory
755
780
  // 触发 onFinish 回调
756
781
  streamCompleteResolver({ messages: fullMessageHistory })
757
782
  } catch (error: any) {
@@ -793,16 +818,34 @@ export class AgentModelProvider {
793
818
  tools: this._tempMergeTools(options.tools) as ToolSet
794
819
  }
795
820
 
821
+ // 保存最后一条 user 消息,用于后续缓存
822
+ let lastUserMessage: any = null
823
+
796
824
  if (options.message && !options.messages) {
797
- this.messages.push({ role: 'user', content: options.message })
798
- chatOptions.messages = [...this.messages]
825
+ // 使用 options.message 创建 user 消息
826
+ lastUserMessage = { role: 'user', content: options.message }
827
+ this.responseMessages.push(lastUserMessage)
828
+ chatOptions.messages = [...this.responseMessages]
829
+ } else if (options.messages && options.messages.length > 0) {
830
+ // 从 options.messages 中获取最后一条 user 消息
831
+ const lastMessage = options.messages[options.messages.length - 1]
832
+ if (lastMessage.role === 'user') {
833
+ lastUserMessage = lastMessage
834
+ }
799
835
  }
800
836
 
801
837
  const result = chatMethod(chatOptions)
802
838
 
803
- // 缓存 ai-sdk的多轮对话的消息
804
- ;(result as StreamTextResult<ToolSet, unknown>)?.response?.then((res: any) => {
805
- this.messages.push(...res.messages)
839
+ // 缓存 ai-sdk 的多轮对话的消息
840
+ ;(result as any)?.response?.then((res: any) => {
841
+ // 检查 res.messages 的第一条消息是否是 user 消息
842
+ // 如果不是,且有 lastUserMessage,则先添加 lastUserMessage
843
+ const firstMessage = res.messages?.[0]
844
+ if (lastUserMessage && firstMessage?.role !== 'user') {
845
+ this.responseMessages.push(lastUserMessage)
846
+ }
847
+ // 然后添加 AI 返回的消息
848
+ this.responseMessages.push(...res.messages)
806
849
  })
807
850
 
808
851
  return result
package/agent/type.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ProviderV2 } from '@ai-sdk/provider'
2
- import type { experimental_MCPClientConfig as MCPClientConfig } from '@ai-sdk/mcp'
2
+ import type { MCPClientConfig } from '@ai-sdk/mcp'
3
3
 
4
4
  // 从 MCPClientConfig 中提取 transport 类型
5
5
  export type MCPTransport = MCPClientConfig['transport']
@@ -1,4 +1,4 @@
1
- import { dynamicTool, jsonSchema, Tool, ToolCallOptions, ToolSet } from 'ai'
1
+ import { dynamicTool, jsonSchema, Tool, ToolExecutionOptions, ToolSet } from 'ai'
2
2
  import { WebMcpClient } from '../../WebMcpClient'
3
3
 
4
4
  /**
@@ -13,7 +13,7 @@ export const getAISDKTools = async (client: WebMcpClient): Promise<ToolSet> => {
13
13
  const listToolsResult = await client.listTools()
14
14
 
15
15
  for (const { name, description, inputSchema } of listToolsResult.tools) {
16
- const execute = async (args: any, options: ToolCallOptions): Promise<any> => {
16
+ const execute = async (args: any, options: ToolExecutionOptions): Promise<any> => {
17
17
  return client.callTool({ name, arguments: args }, { signal: options?.abortSignal })
18
18
  }
19
19
 
@@ -2,7 +2,7 @@ import { streamText, generateText } from 'ai';
2
2
  import { IAgentModelProviderOption, McpServerConfig } from './type';
3
3
  import { ProviderV2 } from '@ai-sdk/provider';
4
4
  import { OpenAIProvider, createOpenAI } from '@ai-sdk/openai';
5
- import { createDeepSeek } from '@ai-sdk/deepseek';
5
+ import { createDeepSeek, DeepSeekProvider } from '@ai-sdk/deepseek';
6
6
  import { WebMcpClient } from '../WebMcpClient';
7
7
 
8
8
  export declare const AIProviderFactories: {
@@ -14,7 +14,7 @@ export declare const AIProviderFactories: {
14
14
  * @returns 暴露了 chat, chatStream方法
15
15
  */
16
16
  export declare class AgentModelProvider {
17
- llm: ProviderV2 | OpenAIProvider;
17
+ llm: ProviderV2 | OpenAIProvider | DeepSeekProvider;
18
18
  /** 当前mcpServers对象集合。键为服务器名称,值为 McpServerConfig 或任意的 MCPTransport
19
19
  * 参考: https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#initializing-an-mcp-client */
20
20
  mcpServers: Record<string, McpServerConfig>;
@@ -29,7 +29,7 @@ export declare class AgentModelProvider {
29
29
  /** Agent 内部报错时,抛出的错误事件 */
30
30
  onError: ((msg: string, err?: any) => void) | undefined;
31
31
  /** 缓存 ai-sdk response 中的 多轮会话的上下文 */
32
- messages: any[];
32
+ responseMessages: any[];
33
33
  /** 是否使用 ReAct 模式(通过提示词而非 function calling 进行工具调用) */
34
34
  useReActMode: boolean;
35
35
  constructor({ llmConfig, mcpServers }: IAgentModelProviderOption);
@@ -50,7 +50,7 @@ export declare class AgentModelProvider {
50
50
  /** 全量更新所有的 mcpServers */
51
51
  updateMcpServers(mcpServers?: Record<string, McpServerConfig>): Promise<void>;
52
52
  /** 插入一个新的mcpServer,如果已经存在则返回false */
53
- insertMcpServer(serverName: string, mcpServer: McpServerConfig): Promise<false | WebMcpClient | import('@ai-sdk/mcp').experimental_MCPClient | null>;
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
56
  /** 创建临时允许调用的tools集合 */
@@ -74,7 +74,7 @@ export declare class AgentModelProvider {
74
74
  */
75
75
  private _removeImageFromMessage;
76
76
  /**
77
- * 构建用于模型调用的消息列表(magentic-ui 风格)
77
+ * 构建用于模型调用的消息列表
78
78
  * 策略:保留所有文本消息,仅限制图片数量(类似 magentic-ui 的 maybe_remove_old_screenshots)
79
79
  *
80
80
  * @param systemMessage 系统提示词
@@ -1,5 +1,5 @@
1
1
  import { ProviderV2 } from '@ai-sdk/provider';
2
- import { experimental_MCPClientConfig as MCPClientConfig } from '@ai-sdk/mcp';
2
+ import { MCPClientConfig } from '@ai-sdk/mcp';
3
3
 
4
4
  export type MCPTransport = MCPClientConfig['transport'];
5
5
  type ProviderFactory = 'openai' | 'deepseek' | ((options: any) => ProviderV2);
package/dist/index.d.ts CHANGED
@@ -26,3 +26,4 @@ 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 { getSkillOverviews, formatSkillsForSystemPrompt, getSkillMdPaths, getSkillMdContent, getMainSkillPaths, getMainSkillPathByName, parseSkillFrontMatter, createSkillTools, type SkillMeta, type SkillToolsSet } from './skills/index';