Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/src/tools/message.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,12 @@ interface UseMessageReturn {
messages: Ref<ChatMessage[]>
/** 响应提供者(可动态更新) */
responseProvider: Ref<UseMessageOptions['responseProvider']>
/** 是否正在处理中 */
/** 是否正在处理请求(不包含暂停) */
isProcessing: ComputedRef<boolean>
/** 当前回合是否仍在运行或暂停等待确认 */
isCurrentTurn: ComputedRef<boolean>
/** 是否处于暂停等待确认状态 */
isPaused: ComputedRef<boolean>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/** 发送消息 */
sendMessage: (content: string) => Promise<void>
/** 发送消息(支持传入多个消息对象) */
Expand All @@ -141,14 +145,15 @@ interface UseMessageReturn {

```typescript
/** 请求状态 */
type RequestState = 'idle' | 'processing' | 'completed' | 'aborted' | 'error'
type RequestState = 'idle' | 'processing' | 'paused' | 'completed' | 'aborted' | 'error'

/** 处理状态 */
type RequestProcessingState = 'requesting' | 'completing' | string
```

- `idle`: 空闲状态,没有正在进行的请求
- `processing`: 正在处理中(包含 `requesting` 和 `completing` 两个子状态)
- `paused`: 当前回合暂停,等待工具确认或外部恢复
- `completed`: 请求已完成
- `aborted`: 请求被中止
- `error`: 请求发生错误
Expand Down Expand Up @@ -257,8 +262,9 @@ useMessage({
| `callTool` | `(toolCall, context) => MaybeStreamableResult<string \| Record<string, unknown>>` | 是 | - | 执行单个工具调用,返回结果字符串或可流式返回的对象,结果会合并到对应 tool 消息的 `content`。可通过 `context.toolSource` 判断工具来源。 |
| `beforeCallTools` | `(toolCalls, context) => Promise<void>` | 否 | - | 在真正执行工具前调用,可用于统一校验、鉴权、埋点。新字段为 `context.assistantMessage`;`context.currentMessage` 继续保留,但已弃用。 |
| `onToolCallStart` | `(toolCall, context) => void` | 否 | - | 单个工具开始执行时触发。此时对应的 tool 消息已经创建并追加到 `messages` 中;`context` 额外包含 `assistantMessage`、`primaryMessage`(兼容字段)和 `toolMessage`。 |
| `onToolCallEnd` | `(toolCall, context) => void` | 否 | - | 单个工具执行结束时触发。`context.status` 为 `'success' \| 'failed' \| 'cancelled'`,并额外包含 `assistantMessage`、`primaryMessage`(兼容字段)和 `toolMessage`,失败或取消时可能有 `context.error`。 |
| `onToolCallEnd` | `(toolCall, context) => void` | 否 | - | 单个工具执行结束时触发。`context.status` 为 `'success' \| 'failed' \| 'cancelled' \| 'denied'`,并额外包含 `assistantMessage`、`primaryMessage`(兼容字段)和 `toolMessage`,失败、取消或拒绝时可能有 `context.error`。 |
| `toolCallCancelledContent` | `string` | 否 | `'Tool call cancelled.'` | 请求被中止且需要补全缺失 tool 消息时,填入该默认内容。 |
| `toolCallDeniedContent` | `string` | 否 | `'Tool call denied.'` | 工具调用被用户拒绝或请求中止导致工具被标记为 denied 时,填入该提示。 |
| `toolCallFailedContent` | `string` | 否 | `'Tool call failed.'` | 工具执行抛错且当前 tool 消息内容仍为空时,写入该失败提示。 |
| `autoFillMissingToolMessages` | `boolean` | 否 | `false` | 在下一轮开始前,自动补齐上一次被取消但尚未写入的 tool 消息。 |

Expand All @@ -269,7 +275,7 @@ useMessage({
| `beforeCallTools` | `assistantMessage`、`currentMessage`(已弃用) | 在 `BasePluginContext` 基础上额外包含当前这条带 `tool_calls` 的 assistant 消息。推荐使用 `assistantMessage`;`currentMessage` 为兼容旧代码保留。 |
| `callTool` | `assistantMessage`、`currentMessage`(已弃用)、`toolMessage`、`toolSource` | 在 `BasePluginContext` 基础上额外包含当前这条带 `tool_calls` 的 assistant 消息、当前工具对应的 `toolMessage` 和工具来源。推荐使用 `assistantMessage`;`currentMessage` 为兼容旧代码保留。 |
| `onToolCallStart` | `assistantMessage`、`primaryMessage`(兼容字段)、`toolMessage`、`toolSource` | 在 `BasePluginContext` 基础上额外包含触发当前工具调用的 assistant 消息、当前 tool 消息和工具来源。推荐使用 `assistantMessage`;`primaryMessage` 为兼容旧代码保留。 |
| `onToolCallEnd` | `assistantMessage`、`primaryMessage`(兼容字段)、`toolMessage`、`toolSource`、`status`、`error?` | 在 `BasePluginContext` 基础上额外包含 assistant 消息、当前 tool 消息、工具来源和执行状态;当工具执行失败或被取消时,还可能包含 `error`。推荐使用 `assistantMessage`;`primaryMessage` 为兼容旧代码保留。 |
| `onToolCallEnd` | `assistantMessage`、`primaryMessage`(兼容字段)、`toolMessage`、`toolSource`、`status`、`error?` | 在 `BasePluginContext` 基础上额外包含 assistant 消息、当前 tool 消息、工具来源和执行状态;当工具执行失败、取消或拒绝时,还可能包含 `error`。推荐使用 `assistantMessage`;`primaryMessage` 为兼容旧代码保留。 |

`toolSource` 类型:

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/bubble/composables/useToolCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BubbleContentRendererProps, ChatMessageContent } from '../index.type'
import { getJsonrepair } from '../utils'
import { useBubbleStore } from './useBubbleStore'

const toolCallStatus = ['running', 'success', 'failed', 'cancelled'] as const
const toolCallStatus = ['running', 'success', 'failed', 'cancelled', 'awaiting-approval', 'denied'] as const
export type ToolCallStatus = (typeof toolCallStatus)[number]

export const useToolCall = (
Expand Down
23 changes: 20 additions & 3 deletions packages/components/src/bubble/renderers/Tool.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<script setup lang="ts">
import { IconArrowDown, IconCancelled, IconError, IconLoading, IconPlugin } from '@opentiny/tiny-robot-svgs'
import {
IconArrowDown,
IconCancelled,
IconClose,
IconError,
IconLoading,
IconPlugin,
IconWarning,
} from '@opentiny/tiny-robot-svgs'
import { type Component, computed, nextTick, ref, useCssModule, watch, watchEffect } from 'vue'
import { ToolCallStatus, useBubbleEventFn, useToolCall } from '../composables'
import { BubbleContentRendererProps, ChatMessageContent } from '../index.type'
Expand All @@ -20,10 +28,13 @@ const textAndIconMap = new Map<string, { text: string; icon: Component }>([
['success', { text: '已调用', icon: IconPlugin }],
['failed', { text: '调用失败', icon: IconError }],
['cancelled', { text: '已取消', icon: IconCancelled }],
['awaiting-approval', { text: '等待确认', icon: IconWarning }],
['denied', { text: '已拒绝', icon: IconClose }],
])

const textAndIcon = computed(() => {
return textAndIconMap.get(state.value?.status || '') || { text: '', icon: IconPlugin }
const status = state.value?.status || ''
return textAndIconMap.get(status) || { text: '', icon: IconPlugin }
})
Comment thread
xuanlid marked this conversation as resolved.

const prettyJSON = (json: unknown, space = 2) => {
Expand Down Expand Up @@ -193,6 +204,7 @@ const handleClick = () => {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
}

.header-icon {
Expand All @@ -209,9 +221,14 @@ const handleClick = () => {
}

&.icon-failed,
&.icon-cancelled {
&.icon-cancelled,
&.icon-denied {
color: var(--tr-color-error);
}

&.icon-awaiting-approval {
color: var(--tr-color-warning);
}
}

.expand-icon {
Expand Down
2 changes: 2 additions & 0 deletions packages/kit/src/message/adapters/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const createNativeMessageAdapter = (): MessageStateAdapter => {
processingState: state.processingState,
messages: [...state.messages],
isProcessing: state.requestState === 'processing',
isCurrentTurn: state.requestState === 'processing' || state.requestState === 'paused',
isPaused: state.requestState === 'paused',
} satisfies PublicMessageState
}

Expand Down
8 changes: 8 additions & 0 deletions packages/kit/src/message/adapters/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface VueMessageStateAdapter extends MessageStateAdapter {
processingState: Ref<RequestProcessingState | undefined>
messages: Ref<ChatMessage[]>
isProcessing: ComputedRef<boolean>
isCurrentTurn: ComputedRef<boolean>
isPaused: ComputedRef<boolean>
}

const toReactiveMessage = (message: ChatMessage) => reactive(message) as ChatMessage
Expand Down Expand Up @@ -46,6 +48,8 @@ export const createVueMessageAdapter = (): VueMessageStateAdapter => {
const processingState = ref<RequestProcessingState | undefined>(undefined)
const messages = ref<ChatMessage[]>([])
const isProcessing = computed(() => requestState.value === 'processing')
const isCurrentTurn = computed(() => requestState.value === 'processing' || requestState.value === 'paused')
const isPaused = computed(() => requestState.value === 'paused')

const initialize = (initialState: InternalMessageState) => {
if (initialized) {
Expand All @@ -72,6 +76,8 @@ export const createVueMessageAdapter = (): VueMessageStateAdapter => {
processingState: processingState.value,
messages: toPlainValue(messages.value),
isProcessing: isProcessing.value,
isCurrentTurn: isCurrentTurn.value,
isPaused: isPaused.value,
} satisfies PublicMessageState
}

Expand Down Expand Up @@ -127,6 +133,8 @@ export const createVueMessageAdapter = (): VueMessageStateAdapter => {
processingState,
messages,
isProcessing,
isCurrentTurn,
isPaused,
initialize,
getState,
createMessage,
Expand Down
Loading
Loading