@opentiny/tiny-robot-cli 0.5.2-alpha.3 → 1.0.0-beta.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.
Files changed (53) hide show
  1. package/README.md +49 -1
  2. package/bin/commands/add.js +563 -269
  3. package/bin/commands/create.js +6 -3
  4. package/bin/runtime-version.js +37 -0
  5. package/bin/utils.js +147 -45
  6. package/package.json +5 -3
  7. package/templates/basic/.env.example +8 -0
  8. package/templates/basic/README.md +53 -22
  9. package/templates/basic/index.html +2 -2
  10. package/templates/basic/package.json +12 -16
  11. package/templates/basic/public/favicon.svg +1 -0
  12. package/templates/basic/public/icons.svg +24 -0
  13. package/templates/basic/src/App.vue +111 -119
  14. package/templates/basic/src/main.ts +2 -2
  15. package/templates/basic/tsconfig.app.json +2 -5
  16. package/templates/basic/tsconfig.node.json +3 -6
  17. package/templates/basic/vite.config.ts +6 -1
  18. package/templates/chat/.env.example +9 -0
  19. package/templates/chat/README.md +46 -0
  20. package/templates/chat/index.html +12 -0
  21. package/templates/chat/package.json +28 -0
  22. package/templates/chat/src/TinyRobotChat.vue +88 -27
  23. package/templates/chat/src/components/ComposerTools.vue +99 -0
  24. package/templates/chat/src/components/WindowHeader.vue +192 -0
  25. package/templates/chat/src/composables/useWindow.ts +83 -0
  26. package/templates/chat/src/config/chat-runtime.ts +82 -0
  27. package/templates/chat/src/config/chat-ui.ts +289 -0
  28. package/templates/chat/src/index.css +109 -0
  29. package/templates/chat/src/main.ts +7 -0
  30. package/templates/chat/tsconfig.app.json +13 -0
  31. package/templates/chat/tsconfig.json +7 -0
  32. package/templates/chat/tsconfig.node.json +19 -0
  33. package/templates/chat/vite.config.ts +16 -0
  34. package/templates/basic/public/modelcontextprotocol.png +0 -0
  35. package/templates/basic/src/components/ChatList.vue +0 -82
  36. package/templates/basic/src/components/ChatSender.vue +0 -125
  37. package/templates/basic/src/components/ConversationHistory.vue +0 -136
  38. package/templates/basic/src/components/HistoryDrawerButton.vue +0 -43
  39. package/templates/basic/src/components/McpServerPickerButton.vue +0 -278
  40. package/templates/basic/src/components/ThemeToggleButton.vue +0 -44
  41. package/templates/basic/src/components/icons/IconDeepThink.vue +0 -29
  42. package/templates/basic/src/components/icons/IconModelAliyunBailian.vue +0 -51
  43. package/templates/basic/src/components/icons/IconModelDeepseek.vue +0 -29
  44. package/templates/basic/src/components/icons/IconMoon.vue +0 -29
  45. package/templates/basic/src/components/icons/IconPlugin.vue +0 -29
  46. package/templates/basic/src/components/icons/IconSun.vue +0 -35
  47. package/templates/basic/src/components/icons/IconWebSearch.vue +0 -36
  48. package/templates/basic/src/components/icons/index.ts +0 -7
  49. package/templates/basic/src/composables/useChat.ts +0 -129
  50. package/templates/basic/src/composables/useMcp.ts +0 -170
  51. package/templates/basic/src/composables/useModel.ts +0 -82
  52. package/templates/basic/src/mcpServers.ts +0 -40
  53. package/templates/basic/src/models.ts +0 -81
@@ -1,129 +0,0 @@
1
- import { sseStreamToGenerator, toolPlugin, useConversation } from '@opentiny/tiny-robot-kit'
2
- import { computed, ref } from 'vue'
3
- import type { McpServerKey } from '../mcpServers'
4
- import { useMcp } from './useMcp'
5
- import { useModel } from './useModel'
6
-
7
- function createChatStore() {
8
- const inputMessage = ref('')
9
- const modelStore = useModel()
10
- const mcpStore = useMcp()
11
- const { listTools, callTool } = mcpStore
12
- const { selectedModel, getSelectedModelParams } = modelStore
13
-
14
- const useConversationReturn = useConversation({
15
- useMessageOptions: {
16
- responseProvider: async (requestBody, abortSignal) => {
17
- const currentModel = selectedModel.value
18
- if (!currentModel) {
19
- throw new Error('No model selected.')
20
- }
21
- if (!currentModel.apiKey) {
22
- throw new Error(`Missing API key for provider "${currentModel.provider}".`)
23
- }
24
-
25
- const response = await fetch(currentModel.apiUrl, {
26
- method: 'POST',
27
- headers: {
28
- 'Content-Type': 'application/json',
29
- Authorization: `Bearer ${currentModel.apiKey}`,
30
- },
31
- body: JSON.stringify({
32
- ...requestBody,
33
- ...getSelectedModelParams(),
34
- stream: true,
35
- }),
36
- signal: abortSignal,
37
- })
38
-
39
- if (!response.ok) {
40
- const detail = await response.text().catch(() => '')
41
- throw new Error(`HTTP ${response.status}: ${response.statusText}${detail ? ` - ${detail}` : ''}`)
42
- }
43
-
44
- return sseStreamToGenerator(response, { signal: abortSignal })
45
- },
46
- initialMessages: [
47
- {
48
- role: 'system',
49
- content: 'You are a helpful assistant.',
50
- },
51
- ],
52
- plugins: [
53
- toolPlugin({
54
- getTools: async () => {
55
- const mcpTools = await listTools()
56
- return mcpTools.map((tool) => ({
57
- type: 'function',
58
- function: {
59
- name: tool.name,
60
- description: tool.description || '',
61
- parameters: (tool.inputSchema as Record<string, unknown> | undefined) || {
62
- type: 'object',
63
- properties: {},
64
- },
65
- },
66
- }))
67
- },
68
- callTool: async (toolCall) => {
69
- const toolName = toolCall.function?.name
70
- if (!toolName || !toolName.includes('__')) {
71
- throw new Error(`Unknown MCP tool name: ${toolName || '(empty)'}`)
72
- }
73
- const [serverKeyPart, ...nameParts] = toolName.split('__')
74
- const originalToolName = nameParts.join('__')
75
- const serverKey = serverKeyPart as McpServerKey
76
- if (!originalToolName) {
77
- throw new Error(`Unknown MCP tool name: ${toolName}`)
78
- }
79
- const args = JSON.parse(toolCall.function?.arguments || '{}') as Record<string, unknown>
80
- return await callTool(serverKey, originalToolName, args)
81
- },
82
- }),
83
- {
84
- onError({ currentTurn, error }) {
85
- console.error(error)
86
- currentTurn[currentTurn.length - 1].content = String(error)
87
- },
88
- },
89
- ],
90
- },
91
- autoSaveMessages: true,
92
- })
93
-
94
- const messages = computed(() => useConversationReturn.activeConversation.value?.engine.messages.value || [])
95
- const isProcessing = computed(
96
- () => useConversationReturn.activeConversation.value?.engine.isProcessing.value ?? false,
97
- )
98
-
99
- function sendMessage(content: string) {
100
- const value = content?.trim()
101
- if (!value || isProcessing.value) {
102
- return
103
- }
104
- if (!useConversationReturn.activeConversationId.value) {
105
- useConversationReturn.createConversation({ title: value.slice(0, 24) })
106
- }
107
- useConversationReturn.sendMessage(value)
108
- inputMessage.value = ''
109
- }
110
-
111
- return {
112
- ...useConversationReturn,
113
- ...modelStore,
114
- messages,
115
- inputMessage,
116
- isProcessing,
117
- sendMessage,
118
- }
119
- }
120
-
121
- type ChatStore = ReturnType<typeof createChatStore>
122
- let chatStore: ChatStore | null = null
123
-
124
- export function useChat() {
125
- if (!chatStore) {
126
- chatStore = createChatStore()
127
- }
128
- return chatStore
129
- }
@@ -1,170 +0,0 @@
1
- import { computed, ref } from 'vue'
2
- import { Client } from '@modelcontextprotocol/sdk/client'
3
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse'
4
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp'
5
- import type { CallToolRequest } from '@modelcontextprotocol/sdk/types'
6
- import { McpServers, type McpServerConfig, type McpServerKey } from '../mcpServers'
7
-
8
- type McpTool = Awaited<ReturnType<Client['listTools']>>['tools'][number]
9
- type McpToolCallResult = Awaited<ReturnType<Client['callTool']>>
10
-
11
- function createMcpClient(serverKey: McpServerKey, server: McpServerConfig) {
12
- const client = new Client({
13
- name: serverKey,
14
- version: '0.1.0',
15
- })
16
- const requestInit = {
17
- headers: server.headers,
18
- }
19
- const transport =
20
- server.type === 'streamableHttp'
21
- ? new StreamableHTTPClientTransport(new URL(server.baseUrl), { requestInit })
22
- : new SSEClientTransport(new URL(server.baseUrl), { requestInit })
23
- return { client, transport }
24
- }
25
-
26
- function getServerByKey(serverKey: McpServerKey): McpServerConfig {
27
- const server = McpServers[serverKey]
28
- if (!server) {
29
- throw new Error(`Unknown MCP server key: ${serverKey}`)
30
- }
31
- return server
32
- }
33
-
34
- async function listMcpToolsFromServer(serverKey: McpServerKey): Promise<McpTool[]> {
35
- const server = getServerByKey(serverKey)
36
- const { client, transport } = createMcpClient(serverKey, server)
37
- try {
38
- await client.connect(transport)
39
- const response = await client.listTools()
40
- return response.tools
41
- } finally {
42
- await client.close().catch(() => undefined)
43
- }
44
- }
45
-
46
- async function callMcpToolFromServer(
47
- serverKey: McpServerKey,
48
- toolName: string,
49
- args: CallToolRequest['params']['arguments'] = {},
50
- ): Promise<McpToolCallResult> {
51
- const server = getServerByKey(serverKey)
52
- const { client, transport } = createMcpClient(serverKey, server)
53
- try {
54
- await client.connect(transport)
55
- return await client.callTool({
56
- name: toolName,
57
- arguments: args,
58
- })
59
- } finally {
60
- await client.close().catch(() => undefined)
61
- }
62
- }
63
-
64
- function createMcpStore() {
65
- // Added servers are selectable from McpServers and start empty by default.
66
- const addedMcpServers = ref<McpServerKey[]>([])
67
- const inUseMcpServers = ref<McpServerKey[]>([])
68
-
69
- const availableMcpServers = computed(() => Object.keys(McpServers).map((serverKey) => serverKey as McpServerKey))
70
-
71
- function addMcpServer(serverKey: McpServerKey) {
72
- if (!McpServers[serverKey]) {
73
- throw new Error(`Unknown MCP server key: ${serverKey}`)
74
- }
75
- if (!addedMcpServers.value.includes(serverKey)) {
76
- addedMcpServers.value = [...addedMcpServers.value, serverKey]
77
- }
78
- // First add should enable the server immediately.
79
- if (!inUseMcpServers.value.includes(serverKey)) {
80
- inUseMcpServers.value = [...inUseMcpServers.value, serverKey]
81
- }
82
- }
83
-
84
- function toggleMcpServer(serverKey: McpServerKey) {
85
- if (!addedMcpServers.value.includes(serverKey)) {
86
- throw new Error(`MCP server "${serverKey}" is not added.`)
87
- }
88
- if (inUseMcpServers.value.includes(serverKey)) {
89
- inUseMcpServers.value = inUseMcpServers.value.filter((item) => item !== serverKey)
90
- return
91
- }
92
- inUseMcpServers.value = [...inUseMcpServers.value, serverKey]
93
- }
94
-
95
- function removeMcpServer(serverKey: McpServerKey) {
96
- if (!addedMcpServers.value.includes(serverKey)) {
97
- return
98
- }
99
- addedMcpServers.value = addedMcpServers.value.filter((item) => item !== serverKey)
100
- inUseMcpServers.value = inUseMcpServers.value.filter((item) => item !== serverKey)
101
- }
102
-
103
- function assertServerInUse(serverKey: McpServerKey) {
104
- if (!inUseMcpServers.value.includes(serverKey)) {
105
- throw new Error(`MCP server "${serverKey}" is not in use.`)
106
- }
107
- }
108
-
109
- async function listTools(serverKey?: McpServerKey): Promise<McpTool[]> {
110
- if (serverKey) {
111
- return listMcpToolsFromServer(serverKey)
112
- }
113
-
114
- const resultEntries = await Promise.allSettled(
115
- inUseMcpServers.value.map(async (key) => await listMcpToolsFromServer(key)),
116
- )
117
-
118
- const allTools: McpTool[] = []
119
- resultEntries.forEach((item, index) => {
120
- const serverKey = inUseMcpServers.value[index]
121
- if (!serverKey) {
122
- return
123
- }
124
- if (item.status === 'fulfilled') {
125
- item.value.forEach((tool, toolIndex) => {
126
- const originalName = typeof tool.name === 'string' ? tool.name : `tool_${toolIndex + 1}`
127
- allTools.push({
128
- ...tool,
129
- name: `${serverKey}__${originalName}`,
130
- })
131
- })
132
- return
133
- }
134
- console.error(`[MCP] Failed to list tools from "${serverKey}":`, item.reason)
135
- })
136
-
137
- return allTools
138
- }
139
-
140
- async function callTool(
141
- serverKey: McpServerKey,
142
- toolName: string,
143
- args: Record<string, unknown> = {},
144
- ): Promise<McpToolCallResult> {
145
- assertServerInUse(serverKey)
146
- return callMcpToolFromServer(serverKey, toolName, args)
147
- }
148
-
149
- return {
150
- McpServers,
151
- availableMcpServers,
152
- addedMcpServers,
153
- addMcpServer,
154
- inUseMcpServers,
155
- toggleMcpServer,
156
- removeMcpServer,
157
- listTools,
158
- callTool,
159
- }
160
- }
161
-
162
- type McpStore = ReturnType<typeof createMcpStore>
163
- let mcpStore: McpStore | null = null
164
-
165
- export function useMcp() {
166
- if (!mcpStore) {
167
- mcpStore = createMcpStore()
168
- }
169
- return mcpStore
170
- }
@@ -1,82 +0,0 @@
1
- import type { Component } from 'vue'
2
- import { computed, ref } from 'vue'
3
- import { ModelConfigs, type ModelConfigItem } from '../models'
4
-
5
- export interface ModelOption {
6
- id: string
7
- provider: string
8
- name: string
9
- model: string
10
- icon: Component
11
- apiUrl: string
12
- apiKey?: string
13
- capabilities?: ModelConfigItem['capabilities']
14
- }
15
-
16
- function createModelStore() {
17
- const modelOptions: ModelOption[] = Object.entries(ModelConfigs).flatMap(([provider, config]) =>
18
- config.models.map((modelItem) => ({
19
- id: `${provider}:${modelItem.model}`,
20
- provider,
21
- name: modelItem.name,
22
- model: modelItem.model,
23
- icon: config.icon,
24
- apiUrl: config.apiUrl,
25
- apiKey: config.apiKey?.trim(),
26
- capabilities: modelItem.capabilities,
27
- })),
28
- )
29
-
30
- const selectedModelId = ref(modelOptions[0]?.id || '')
31
- const selectedModel = computed<ModelOption | undefined>(() =>
32
- modelOptions.find((item) => item.id === selectedModelId.value),
33
- )
34
-
35
- const thinkingEnabled = ref(false)
36
- const searchEnabled = ref(false)
37
-
38
- const supportsThinking = computed(() => Boolean(selectedModel.value?.capabilities?.thinking))
39
- const supportsSearch = computed(() => Boolean(selectedModel.value?.capabilities?.search))
40
-
41
- const hasApiConfig = computed(() => {
42
- const current = selectedModel.value
43
- return Boolean(current?.apiUrl && current?.apiKey)
44
- })
45
-
46
- function getSelectedModelParams(): Record<string, unknown> {
47
- const current = selectedModel.value
48
- if (!current) {
49
- return {}
50
- }
51
-
52
- const { thinking, search } = current.capabilities ?? {}
53
-
54
- return {
55
- model: current.model,
56
- ...(thinkingEnabled.value && thinking ? thinking : {}),
57
- ...(searchEnabled.value && search ? search : {}),
58
- }
59
- }
60
-
61
- return {
62
- modelOptions,
63
- selectedModelId,
64
- selectedModel,
65
- thinkingEnabled,
66
- searchEnabled,
67
- supportsThinking,
68
- supportsSearch,
69
- hasApiConfig,
70
- getSelectedModelParams,
71
- }
72
- }
73
-
74
- type ModelStore = ReturnType<typeof createModelStore>
75
- let modelStore: ModelStore | null = null
76
-
77
- export function useModel() {
78
- if (!modelStore) {
79
- modelStore = createModelStore()
80
- }
81
- return modelStore
82
- }
@@ -1,40 +0,0 @@
1
- export type McpServerConfig = {
2
- type: 'sse' | 'streamableHttp'
3
- name: string
4
- description?: string
5
- baseUrl: string
6
- headers?: Record<string, string>
7
- logoUrl?: string
8
- }
9
-
10
- export const McpServers = {
11
- 'china-railway': {
12
- type: 'sse',
13
- description: '开源社区开发者封装,提供 12306购票信息查询等服务',
14
- name: '12306 车票查询',
15
- baseUrl: 'https://dashscope.aliyuncs.com/api/v1/mcps/china-railway/sse',
16
- logoUrl: 'https://img.alicdn.com/imgextra/i3/O1CN01yUKR7l1FrpKqvxNjt_!!6000000000541-2-tps-512-512.png',
17
- headers: {
18
- Authorization: `Bearer ${import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY}`,
19
- },
20
- },
21
- 'amap-maps': {
22
- type: 'sse',
23
- description:
24
- '高德地图MCP Server现已覆盖15大核心接口,提供全场景覆盖的地理信息服务,包括生成专属地图、导航到目的地、打车、地理编码、逆地理编码、IP定位、天气查询、骑行路径规划、步行路径规划、驾车路径规划、公交路径规划、距离测量、关键词搜索、周边搜索、详情搜索等。',
25
- name: '高德地图',
26
- baseUrl: 'https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/sse',
27
- logoUrl: 'https://img.alicdn.com/imgextra/i4/O1CN01iPPabT1EGRN6uatHP_!!6000000000324-0-tps-512-512.jpg',
28
- headers: {
29
- Authorization: `Bearer ${import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY}`,
30
- },
31
- },
32
- 'model-context-protocol-mcp': {
33
- type: 'streamableHttp',
34
- name: 'Model Context Protocol MCP',
35
- baseUrl: window.location.origin + '/modelcontextprotocol-mcp',
36
- logoUrl: window.location.origin + '/modelcontextprotocol.png',
37
- },
38
- } satisfies Record<string, McpServerConfig>
39
-
40
- export type McpServerKey = keyof typeof McpServers
@@ -1,81 +0,0 @@
1
- import { type Component } from 'vue'
2
- import { IconModelAliyunBailian, IconModelDeepseek } from './components/icons'
3
-
4
- export interface ModelCapabilities {
5
- thinking?: Record<string, unknown> | false
6
- search?: Record<string, unknown> | false
7
- }
8
-
9
- export interface ModelConfigItem {
10
- name: string
11
- model: string
12
- capabilities?: ModelCapabilities
13
- }
14
-
15
- export interface ProviderModelConfig {
16
- apiUrl: string
17
- icon: Component
18
- apiKey?: string
19
- models: ModelConfigItem[]
20
- }
21
-
22
- export const ModelConfigs: Record<string, ProviderModelConfig> = {
23
- aliyun: {
24
- apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
25
- icon: IconModelAliyunBailian,
26
- apiKey: import.meta.env.VITE_ALIYUN_DASHSCOPE_KEY,
27
- models: [
28
- {
29
- name: 'Qwen Flash',
30
- model: 'qwen-flash',
31
- capabilities: {
32
- thinking: {
33
- enable_thinking: true,
34
- },
35
- search: {
36
- enable_search: true,
37
- },
38
- },
39
- },
40
- {
41
- name: 'Qwen Plus',
42
- model: 'qwen-plus',
43
- capabilities: {
44
- thinking: {
45
- enable_thinking: true,
46
- },
47
- search: {
48
- enable_search: true,
49
- },
50
- },
51
- },
52
- {
53
- name: 'Qwen Max',
54
- model: 'qwen-max',
55
- capabilities: {
56
- thinking: {
57
- enable_thinking: true,
58
- },
59
- search: {
60
- enable_search: true,
61
- },
62
- },
63
- },
64
- ],
65
- },
66
- deepseek: {
67
- apiUrl: 'https://api.deepseek.com/chat/completions',
68
- icon: IconModelDeepseek,
69
- apiKey: import.meta.env.VITE_DEEPSEEK_API_KEY,
70
- models: [
71
- {
72
- name: 'DeepSeek V4 Flash',
73
- model: 'deepseek-v4-flash',
74
- },
75
- {
76
- name: 'DeepSeek V4 Pro',
77
- model: 'deepseek-v4-pro',
78
- },
79
- ],
80
- },
81
- }