Skip to content

Feat:Migration of tiny robot version to 0.4.0#1804

Open
lichunn wants to merge 3 commits into
opentiny:developfrom
lichunn:feat/robot-330
Open

Feat:Migration of tiny robot version to 0.4.0#1804
lichunn wants to merge 3 commits into
opentiny:developfrom
lichunn:feat/robot-330

Conversation

@lichunn
Copy link
Copy Markdown
Collaborator

@lichunn lichunn commented May 14, 2026

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Added voice input with speech-to-text recognition.
    • Added image upload capability in chat.
    • Aborted conversation messages now display an indicator.
  • Improvements

    • Enhanced chat message rendering and streaming.
    • Refined tool-call execution handling.
    • Improved active request management.
  • Updates

    • Robot plugin dependencies updated to v0.4.0.

Review Change Stack

@github-actions github-actions Bot added the enhancement New feature or request label May 14, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Walkthrough

This PR refactors the robot plugin's AI client architecture from AIClient to OpenAICompatibleProvider, rebuilds conversation state management with async streaming and plugin-based completion handling, updates stream delta processing to support content merging, and enhances UI components for speech input and renderer configuration.

Changes

Provider Migration & Conversation Refactor

Layer / File(s) Summary
Status Constants and Dependencies Foundation
packages/plugins/robot/src/constants/status.ts, packages/plugins/robot/src/constants/index.ts, packages/plugins/robot/package.json
New STATUS enum (PENDING, STREAMING, FINISHED, ERROR, ABORTED), GeneratingStatus subset, and MessageState interface are introduced; constants are re-exported from index; dependency versions bumped to 0.4.0.
AI Provider Module Refactoring
packages/plugins/robot/src/services/aiClient.ts
AIClient instantiation removed; module now exports OpenAICompatibleProvider instance with retained getClientConfig and updateClientConfig helpers.
Conversation Adapter Architecture Rewrite
packages/plugins/robot/src/composables/core/useConversation.ts
useConversationAdapter refactored with new createResponseProvider async generator wrapping provider.chatStream with queuing and abort handling; updateMessageMetadata helper processes completion chunks; adapterPlugin handles finish/error callbacks; persistence uses localStorageStrategyFactory; methods assembled via apis object and spread in return.
Stream Delta and Tool-Call Processing
packages/plugins/robot/src/composables/core/useMessageStream.ts, packages/plugins/robot/src/composables/features/useToolCalls.ts
useMessageStream.ts adds "already merged" flags to handleDeltaReasoning and handleDeltaContent to prevent duplicate content updates; useToolCalls.ts switches to provider.chatStream, marks tools as handled, inserts placeholder assistant message, and continues streaming.
Chat Mode and Agent Mode Behaviors
packages/plugins/robot/src/composables/modes/useChatMode.ts, packages/plugins/robot/src/composables/modes/useAgentMode.ts
useChatMode.ts introduces updateToolCallState and updateToolCallRenderContent helpers for coordinated state/UI updates and removes explicit save calls; useAgentMode.ts uses provider and sources STATUS/MessageState from local constants.
Main Chat Composable Integration
packages/plugins/robot/src/composables/useChat.ts
Passes provider to composables; adds hasActiveRequest, abortRequest, interruptActiveRequest for request lifecycle; refines tool-call completion condition; removes placeholder loading message from sendUserMessage; exposes mappedStatus computed property mapping CHAT_STATUS to STATUS.
UI Components: Chat, Header, Markdown
packages/plugins/robot/src/Main.vue, packages/plugins/robot/src/components/chat/RobotChat.vue, packages/plugins/robot/src/components/header-extension/History.vue, packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue
Main.vue binds mappedStatus instead of chatStatus; RobotChat.vue refactors renderer selection to content-renderer-matches, adds speech-input handlers, conditional upload button, and aborted message footer; History.vue updates to ConversationInfo type; MarkdownRenderer.vue accepts message object prop with nested content field.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through the provider's door,
From AIClient to streaming core,
State merges gently, messages align,
Speech buttons whisper when you're online,
And conversations persist with care—
The refactor's magic fills the air! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title accurately describes the main change: upgrading tiny-robot dependencies from 0.3.1 to 0.4.0 and refactoring related components to work with the new version.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/plugins/robot/src/composables/useChat.ts (3)

95-122: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against lastMessage being undefined.

messages.at(-1) returns ChatMessage | undefined, but Line 98 (lastMessage.content) and Line 120 (lastMessage.content ?? '') both dereference it directly. If messages is ever empty when finish handling runs (e.g. an aborted request before any assistant message lands), this throws a TypeError. Add optional chaining or an early return.

🛡️ Proposed fix
 const lastMessage = messages.at(-1)
+ if (!lastMessage) return
 
 delete abortControllerMap.main
 await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/composables/useChat.ts` around lines 95 - 122, The
code assumes lastMessage = messages.at(-1) is always defined but it can be
undefined; update the endRequest/finalization logic to guard against that by
early-returning when lastMessage is falsy or using optional chaining when
accessing lastMessage.content and lastMessage.tool_calls; specifically adjust
the usages in this block (references: lastMessage, messages.at, onRequestEnd,
handleToolCall, onMessageProcessed, messageState, chatStatus) so you call
onRequestEnd with a safe string (or return early), skip tool handling and state
transitions when lastMessage is undefined, and avoid direct dereferences like
lastMessage.content or lastMessage.tool_calls without checks.

124-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same messages.at(-1) undefined risk on Line 127.

messages.at(-1).content can throw when the array is empty (e.g. error fired before any message was appended). Mirror the optional-chaining fix from handleFinishRequest.

🛡️ Proposed fix
-  await onRequestEnd('error', messages.at(-1).content, messages, { error })
+  await onRequestEnd('error', messages.at(-1)?.content ?? '', messages, { error })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/composables/useChat.ts` around lines 124 - 129, In
handleRequestError, avoid directly calling messages.at(-1).content which can
throw when messages is empty; mirror the fix from handleFinishRequest by using
optional chaining and a safe fallback (e.g., messages.at(-1)?.content ?? '')
when calling onRequestEnd so the function won’t throw if there’s no last
message; update the call site in handleRequestError accordingly.

1-317: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run pnpm build:plugin to validate the package build.

The robot plugin package has no local test script; run the repository-level pnpm build:plugin command to ensure the changes compile correctly and published package behavior is unaffected. This is required by coding guidelines when build output or published behavior may change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/composables/useChat.ts` around lines 1 - 317, Run
the repository-level build command `pnpm build:plugin` to reproduce the
compilation errors for the robot plugin and fix whatever TypeScript/build
problems surface in the package (packages/plugins/robot). Focus on correcting
imports/exports and types referenced in useChat.ts—check symbols like
initChatClient, beforeRequest, createStreamDataHandler, createToolCallHandler,
useConversationAdapter, createConversationWithMode and sendUserMessage for
missing/incorrect types or broken imports; iterate until `pnpm build:plugin`
completes successfully, then commit the fixes.
packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue (1)

30-43: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix incorrect prop structure and unsafe default in MarkdownRenderer.

The message prop is misnamed and incorrectly typed. This renderer should receive a content prop (a markdown string to render), not a message object, consistent with other renderers like ImgRenderer and LoadingRenderer. The current implementation accesses props.message.content (line 69), which:

  1. Uses the wrong prop name and type (Options is markdown-it's config type, not a message type)
  2. Has an unsafe default: with default: () => ({}), props.message.content is undefined, and markdownIt.render(undefined) throws at runtime

Change the message prop to content (string), make it required or default to an empty string, and access it directly:

🛡️ Proposed fix
 const props = defineProps({
-  message: {
-    type: Object as () => Options,
-    default: () => ({})
+  content: {
+    type: String,
+    required: true
   },
   theme: {
     type: String as () => 'light' | 'dark',
     default: 'light'
   },
   options: {
     type: Object as () => Options,
     default: () => ({})
   }
 })

 const renderContent = computed(() => {
-  return DOMPurify.sanitize(markdownIt.render(props.message.content))
+  return DOMPurify.sanitize(markdownIt.render(props.content))
 })

Also applies to: line 69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue` around
lines 30 - 43, The prop definition in MarkdownRenderer (defineProps) is wrong:
replace the current message prop (typed as Options with default {}) with a
content prop of type String that is either required or has a safe default (e.g.,
empty string), keep theme and options as-is, and update all usages that read
props.message.content (e.g., where markdownIt.render is called) to use
props.content directly so markdownIt.render never receives undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/plugins/robot/src/components/chat/RobotChat.vue`:
- Around line 153-185: The issue is that the markdown and image matchers in
contentRendererMatches inspect message.content instead of the per-item content
parameter, causing wrong matches; update the Markdown matcher (currently using
MarkdownRenderer) to use the signature find: (message: any, content: any) =>
!message.loading && content && content.type !== 'img' && content.type !==
'image' && content.type !== 'tool' (or similar check that content exists and is
not an image/tool), and update the Img matcher (currently using ImgRenderer) to
use find: (message: any, content: any) => content?.type === 'img' ||
content?.type === 'image' so both follow the same per-item content pattern as
Tools, Reasoning, and custom renderers.

In `@packages/plugins/robot/src/composables/modes/useChatMode.ts`:
- Around line 41-47: The updateToolCallRenderContent function lacks the same
guard as updateToolCallState and may match or create entries with an undefined
tool.id; add an early return at the top of updateToolCallRenderContent when
tool.id is falsy (same behavior as in updateToolCallState) so you never call
renderContent.find(... toolCallId === tool.id) or push a new entry with
toolCallId: undefined; reference the function name updateToolCallRenderContent
and the symbol tool.id/toolCallId when making the change.

In `@packages/plugins/robot/src/composables/useChat.ts`:
- Around line 198-214: abortRequest currently calls the async onRequestEnd
fire-and-forget and coerces message content with "as string", causing race
conditions, possible NPEs, and unhandled rejections; make abortRequest async,
synchronously capture a safe snapshot of the last message content and the
messages array (or await onRequestEnd before allowing conversation switch),
replace the unsafe "as string" cast with a guarded nullable value (e.g., check
existence before passing), and wrap the onRequestEnd invocation in try/catch (or
await it and propagate errors) so interruptActiveRequest can await abortRequest
and avoid appending the "aborted" block to the wrong conversation; reference
abortRequest, interruptActiveRequest, onRequestEnd, and messageManager.messages
when making these changes.

---

Outside diff comments:
In `@packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue`:
- Around line 30-43: The prop definition in MarkdownRenderer (defineProps) is
wrong: replace the current message prop (typed as Options with default {}) with
a content prop of type String that is either required or has a safe default
(e.g., empty string), keep theme and options as-is, and update all usages that
read props.message.content (e.g., where markdownIt.render is called) to use
props.content directly so markdownIt.render never receives undefined.

In `@packages/plugins/robot/src/composables/useChat.ts`:
- Around line 95-122: The code assumes lastMessage = messages.at(-1) is always
defined but it can be undefined; update the endRequest/finalization logic to
guard against that by early-returning when lastMessage is falsy or using
optional chaining when accessing lastMessage.content and lastMessage.tool_calls;
specifically adjust the usages in this block (references: lastMessage,
messages.at, onRequestEnd, handleToolCall, onMessageProcessed, messageState,
chatStatus) so you call onRequestEnd with a safe string (or return early), skip
tool handling and state transitions when lastMessage is undefined, and avoid
direct dereferences like lastMessage.content or lastMessage.tool_calls without
checks.
- Around line 124-129: In handleRequestError, avoid directly calling
messages.at(-1).content which can throw when messages is empty; mirror the fix
from handleFinishRequest by using optional chaining and a safe fallback (e.g.,
messages.at(-1)?.content ?? '') when calling onRequestEnd so the function won’t
throw if there’s no last message; update the call site in handleRequestError
accordingly.
- Around line 1-317: Run the repository-level build command `pnpm build:plugin`
to reproduce the compilation errors for the robot plugin and fix whatever
TypeScript/build problems surface in the package (packages/plugins/robot). Focus
on correcting imports/exports and types referenced in useChat.ts—check symbols
like initChatClient, beforeRequest, createStreamDataHandler,
createToolCallHandler, useConversationAdapter, createConversationWithMode and
sendUserMessage for missing/incorrect types or broken imports; iterate until
`pnpm build:plugin` completes successfully, then commit the fixes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 714a8ea4-34b4-405d-8683-fbea959acc47

📥 Commits

Reviewing files that changed from the base of the PR and between 59e8548 and e3a7d22.

📒 Files selected for processing (14)
  • packages/plugins/robot/package.json
  • packages/plugins/robot/src/Main.vue
  • packages/plugins/robot/src/components/chat/RobotChat.vue
  • packages/plugins/robot/src/components/header-extension/History.vue
  • packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue
  • packages/plugins/robot/src/composables/core/useConversation.ts
  • packages/plugins/robot/src/composables/core/useMessageStream.ts
  • packages/plugins/robot/src/composables/features/useToolCalls.ts
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
  • packages/plugins/robot/src/composables/modes/useChatMode.ts
  • packages/plugins/robot/src/composables/useChat.ts
  • packages/plugins/robot/src/constants/index.ts
  • packages/plugins/robot/src/constants/status.ts
  • packages/plugins/robot/src/services/aiClient.ts

Comment on lines +153 to +185
const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
{
priority: BubbleRendererMatchPriority.LOADING,
find: (message) => Boolean(message.loading),
renderer: LoadingRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
renderer: BubbleRenderers.Tools
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) =>
content?.type !== 'tool' && typeof message.reasoning_content === 'string' && message.reasoning_content,
renderer: BubbleRenderers.Reasoning
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any) => !message.loading && message.content,
renderer: MarkdownRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
renderer: ImgRenderer
},
...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({
priority: BubbleRendererMatchPriority.NORMAL,
find: (_message: any, content: any) => content?.type === type,
renderer
}))
])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

@opentiny/tiny-robot BubbleContentRendererMatch find signature content argument

💡 Result:

In @opentiny/tiny-robot, BubbleContentRendererMatch.find takes (message, content, contentIndex) and the match function’s signature is: find: (message: BubbleMessage, content: ChatMessageContentItem, contentIndex: number) => boolean [1] So the “content argument” (the second parameter) is the resolved/unified content item, and when content is an array the system uses contentIndex to select the specific item; the docs state: “content … 若为数组则取 contentIndex 对应项,若为字符串则转为 { type: 'text', text: string }” [1].

Citations:


🏁 Script executed:

fd -e vue -e ts -e js | grep -i robot | head -20

Repository: opentiny/tiny-engine

Length of output: 1077


🏁 Script executed:

find . -name "RobotChat.vue" -o -name "RobotChat.ts" | head -5

Repository: opentiny/tiny-engine

Length of output: 123


🏁 Script executed:

wc -l packages/plugins/robot/src/components/chat/RobotChat.vue

Repository: opentiny/tiny-engine

Length of output: 125


🏁 Script executed:

cat -n packages/plugins/robot/src/components/chat/RobotChat.vue | sed -n '140,200p'

Repository: opentiny/tiny-engine

Length of output: 2380


🏁 Script executed:

cat -n packages/plugins/robot/src/components/chat/RobotChat.vue | sed -n '280,330p'

Repository: opentiny/tiny-engine

Length of output: 1658


🏁 Script executed:

grep -n "renderContent" packages/plugins/robot/src/components/chat/RobotChat.vue | head -20

Repository: opentiny/tiny-engine

Length of output: 252


🏁 Script executed:

cat -n packages/plugins/robot/src/types/chat.types.ts

Repository: opentiny/tiny-engine

Length of output: 2222


Renderer matchers don't consistently use the per-item content parameter, causing some messages to render incorrectly.

The contentRendererMatches have two interrelated problems:

  1. Markdown matcher ignores per-item content type. At line 172, find: (message: any) => !message.loading && message.content checks the raw message.content property, while all other matchers (Tools at line 161, Reasoning at line 166, custom renderers at line 182) check the per-item content parameter. Since the library invokes find() per resolved content item, the markdown matcher should also check the per-item content type to avoid matching when an image or tool content item is passed.

  2. Image matcher reads from the wrong message property. At line 177, find: (message: any) => message?.content?.[0]?.type === 'img' inspects message.content[0], but file messages created in handleSendMessage (lines 300–309) have content: '' with the actual image stored under renderContent: [{ type: 'img', content: file.url }]. When the library invokes find() with the resolved content item from renderContent, the matcher ignores that per-item content parameter and instead checks message.content[0].type, which is always undefined. These file messages fall through to the markdown matcher and render as empty strings.

Both matchers should use the second parameter (content) like the Tools/Reasoning/custom matchers do:

Proposed fix
   {
     priority: BubbleRendererMatchPriority.NORMAL,
-    find: (message: any) => !message.loading && message.content,
+    find: (message: any, content: any) =>
+      !message.loading && (typeof content === 'string' || content?.type === 'text' || content?.type === 'markdown'),
     renderer: MarkdownRenderer
   },
   {
     priority: BubbleRendererMatchPriority.NORMAL,
-    find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
+    find: (_message: any, content: any) => content?.type === 'img' || content?.type === 'image',
     renderer: ImgRenderer
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
{
priority: BubbleRendererMatchPriority.LOADING,
find: (message) => Boolean(message.loading),
renderer: LoadingRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
renderer: BubbleRenderers.Tools
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) =>
content?.type !== 'tool' && typeof message.reasoning_content === 'string' && message.reasoning_content,
renderer: BubbleRenderers.Reasoning
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any) => !message.loading && message.content,
renderer: MarkdownRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
renderer: ImgRenderer
},
...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({
priority: BubbleRendererMatchPriority.NORMAL,
find: (_message: any, content: any) => content?.type === type,
renderer
}))
])
const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
{
priority: BubbleRendererMatchPriority.LOADING,
find: (message) => Boolean(message.loading),
renderer: LoadingRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
renderer: BubbleRenderers.Tools
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) =>
content?.type !== 'tool' && typeof message.reasoning_content === 'string' && message.reasoning_content,
renderer: BubbleRenderers.Reasoning
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (message: any, content: any) =>
!message.loading && (typeof content === 'string' || content?.type === 'text' || content?.type === 'markdown'),
renderer: MarkdownRenderer
},
{
priority: BubbleRendererMatchPriority.NORMAL,
find: (_message: any, content: any) => content?.type === 'img' || content?.type === 'image',
renderer: ImgRenderer
},
...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({
priority: BubbleRendererMatchPriority.NORMAL,
find: (_message: any, content: any) => content?.type === type,
renderer
}))
])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/components/chat/RobotChat.vue` around lines 153 -
185, The issue is that the markdown and image matchers in contentRendererMatches
inspect message.content instead of the per-item content parameter, causing wrong
matches; update the Markdown matcher (currently using MarkdownRenderer) to use
the signature find: (message: any, content: any) => !message.loading && content
&& content.type !== 'img' && content.type !== 'image' && content.type !== 'tool'
(or similar check that content exists and is not an image/tool), and update the
Img matcher (currently using ImgRenderer) to use find: (message: any, content:
any) => content?.type === 'img' || content?.type === 'image' so both follow the
same per-item content pattern as Tools, Reasoning, and custom renderers.

Comment on lines +41 to +47
const updateToolCallRenderContent = (
tool: Record<string, unknown>,
currentMessage: any,
{ status, result }: { status?: string; result?: object | string } = {}
) => {
const renderContent = currentMessage.renderContent || (currentMessage.renderContent = [])
const currentToolCallContent = renderContent.find((item: any) => item.type === 'tool' && item.toolCallId === tool.id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard tool.id consistently with updateToolCallState.

updateToolCallState early-returns when tool.id is falsy (line 24), but updateToolCallRenderContent does not. If a streamed tool delta arrives without an id, the find on Line 47 will match the first existing entry whose toolCallId is also undefined and silently overwrite it, or push a new entry with toolCallId: undefined, causing multiple distinct tool calls to collide on the same render block. Apply the same guard for consistency.

🛡️ Proposed guard
 const updateToolCallRenderContent = (
   tool: Record<string, unknown>,
   currentMessage: any,
   { status, result }: { status?: string; result?: object | string } = {}
 ) => {
+  if (!tool.id) {
+    return
+  }
+
   const renderContent = currentMessage.renderContent || (currentMessage.renderContent = [])
   const currentToolCallContent = renderContent.find((item: any) => item.type === 'tool' && item.toolCallId === tool.id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const updateToolCallRenderContent = (
tool: Record<string, unknown>,
currentMessage: any,
{ status, result }: { status?: string; result?: object | string } = {}
) => {
const renderContent = currentMessage.renderContent || (currentMessage.renderContent = [])
const currentToolCallContent = renderContent.find((item: any) => item.type === 'tool' && item.toolCallId === tool.id)
const updateToolCallRenderContent = (
tool: Record<string, unknown>,
currentMessage: any,
{ status, result }: { status?: string; result?: object | string } = {}
) => {
if (!tool.id) {
return
}
const renderContent = currentMessage.renderContent || (currentMessage.renderContent = [])
const currentToolCallContent = renderContent.find((item: any) => item.type === 'tool' && item.toolCallId === tool.id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/plugins/robot/src/composables/modes/useChatMode.ts` around lines 41
- 47, The updateToolCallRenderContent function lacks the same guard as
updateToolCallState and may match or create entries with an undefined tool.id;
add an early return at the top of updateToolCallRenderContent when tool.id is
falsy (same behavior as in updateToolCallState) so you never call
renderContent.find(... toolCallId === tool.id) or push a new entry with
toolCallId: undefined; reference the function name updateToolCallRenderContent
and the symbol tool.id/toolCallId when making the change.

Comment thread packages/plugins/robot/src/composables/useChat.ts
@lichunn lichunn changed the title Feat:Migration of tiny robot version to v0.4.0 Feat:Migration of tiny robot version to 0.4.0 May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant