From 1a420a1a710e94bedfedbe61946f86265a347790 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Thu, 5 Mar 2026 07:46:31 -0600
Subject: fix(app): websearch and codesearch tool rendering
---
packages/ui/src/components/basic-tool.tsx | 39 ++++++++++-
packages/ui/src/components/message-part.css | 40 +++++++++++
packages/ui/src/components/message-part.tsx | 102 ++++++++++++++++++++++++++++
3 files changed, 179 insertions(+), 2 deletions(-)
(limited to 'packages/ui/src/components')
diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx
index fff6e92f1..4ad91824d 100644
--- a/packages/ui/src/components/basic-tool.tsx
+++ b/packages/ui/src/components/basic-tool.tsx
@@ -203,6 +203,41 @@ export function BasicTool(props: BasicToolProps) {
)
}
-export function GenericTool(props: { tool: string; status?: string; hideDetails?: boolean }) {
- return
+function label(input: Record | undefined) {
+ const keys = ["description", "query", "url", "filePath", "path", "pattern", "name"]
+ return keys.map((key) => input?.[key]).find((value): value is string => typeof value === "string" && value.length > 0)
+}
+
+function args(input: Record | undefined) {
+ if (!input) return []
+ const skip = new Set(["description", "query", "url", "filePath", "path", "pattern", "name"])
+ return Object.entries(input)
+ .filter(([key]) => !skip.has(key))
+ .flatMap(([key, value]) => {
+ if (typeof value === "string") return [`${key}=${value}`]
+ if (typeof value === "number") return [`${key}=${value}`]
+ if (typeof value === "boolean") return [`${key}=${value}`]
+ return []
+ })
+ .slice(0, 3)
+}
+
+export function GenericTool(props: {
+ tool: string
+ status?: string
+ hideDetails?: boolean
+ input?: Record
+}) {
+ return (
+
+ )
}
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css
index 3eee45c75..8fc709013 100644
--- a/packages/ui/src/components/message-part.css
+++ b/packages/ui/src/components/message-part.css
@@ -577,6 +577,46 @@
justify-content: center;
}
+[data-component="exa-tool-output"] {
+ width: 100%;
+ padding-top: 8px;
+ display: flex;
+ flex-direction: column;
+}
+
+[data-slot="basic-tool-tool-subtitle"].exa-tool-query {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+[data-slot="exa-tool-links"] {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+[data-slot="exa-tool-link"] {
+ display: block;
+ max-width: 100%;
+ color: var(--text-interactive-base);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:hover {
+ color: var(--text-interactive-base);
+ }
+
+ &:visited {
+ color: var(--text-interactive-base);
+ }
+}
+
[data-component="todos"] {
padding: 10px 0 24px 0;
display: flex;
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 766060f1b..fbeb8bda2 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -243,6 +243,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
title: i18n.t("ui.tool.webfetch"),
subtitle: input.url,
}
+ case "websearch":
+ return {
+ icon: "window-cursor",
+ title: i18n.t("ui.tool.websearch"),
+ subtitle: input.query,
+ }
+ case "codesearch":
+ return {
+ icon: "code",
+ title: i18n.t("ui.tool.codesearch"),
+ subtitle: input.query,
+ }
case "task":
return {
icon: "task",
@@ -303,6 +315,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
}
}
+function urls(text: string | undefined) {
+ if (!text) return []
+ const seen = new Set()
+ return [...text.matchAll(/https?:\/\/[^\s<>"'`)\]]+/g)]
+ .map((item) => item[0].replace(/[),.;:!?]+$/g, ""))
+ .filter((item) => {
+ if (seen.has(item)) return false
+ seen.add(item)
+ return true
+ })
+}
+
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
@@ -598,6 +622,32 @@ function contextToolSummary(parts: ToolPart[]) {
return { read, search, list }
}
+function ExaOutput(props: { output?: string }) {
+ const links = createMemo(() => urls(props.output))
+
+ return (
+ 0}>
+
+
+ )
+}
+
export function registerPartComponent(type: string, component: PartComponent) {
PART_MAPPING[type] = component
}
@@ -1467,6 +1517,58 @@ ToolRegistry.register({
},
})
+ToolRegistry.register({
+ name: "websearch",
+ render(props) {
+ const i18n = useI18n()
+ const query = createMemo(() => {
+ const value = props.input.query
+ if (typeof value !== "string") return ""
+ return value
+ })
+
+ return (
+
+
+
+ )
+ },
+})
+
+ToolRegistry.register({
+ name: "codesearch",
+ render(props) {
+ const i18n = useI18n()
+ const query = createMemo(() => {
+ const value = props.input.query
+ if (typeof value !== "string") return ""
+ return value
+ })
+
+ return (
+
+
+
+ )
+ },
+})
+
ToolRegistry.register({
name: "task",
render(props) {
--
cgit v1.2.3