summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-05 07:46:31 -0600
committerAdam <[email protected]>2026-03-05 08:00:42 -0600
commit1a420a1a710e94bedfedbe61946f86265a347790 (patch)
treea622d1a7d81a0b40925695309b8b70847cf86bce
parent4c185c70f22f93a3a467a4cc4a28934e4384e393 (diff)
downloadopencode-1a420a1a710e94bedfedbe61946f86265a347790.tar.gz
opencode-1a420a1a710e94bedfedbe61946f86265a347790.zip
fix(app): websearch and codesearch tool rendering
-rw-r--r--packages/ui/src/components/basic-tool.tsx39
-rw-r--r--packages/ui/src/components/message-part.css40
-rw-r--r--packages/ui/src/components/message-part.tsx102
-rw-r--r--packages/ui/src/i18n/ar.ts2
-rw-r--r--packages/ui/src/i18n/br.ts2
-rw-r--r--packages/ui/src/i18n/bs.ts2
-rw-r--r--packages/ui/src/i18n/da.ts2
-rw-r--r--packages/ui/src/i18n/de.ts2
-rw-r--r--packages/ui/src/i18n/en.ts2
-rw-r--r--packages/ui/src/i18n/es.ts2
-rw-r--r--packages/ui/src/i18n/fr.ts2
-rw-r--r--packages/ui/src/i18n/ja.ts2
-rw-r--r--packages/ui/src/i18n/ko.ts2
-rw-r--r--packages/ui/src/i18n/no.ts2
-rw-r--r--packages/ui/src/i18n/pl.ts2
-rw-r--r--packages/ui/src/i18n/ru.ts2
-rw-r--r--packages/ui/src/i18n/th.ts2
-rw-r--r--packages/ui/src/i18n/tr.ts2
-rw-r--r--packages/ui/src/i18n/zh.ts2
-rw-r--r--packages/ui/src/i18n/zht.ts2
20 files changed, 213 insertions, 2 deletions
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 <BasicTool icon="mcp" status={props.status} trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
+function label(input: Record<string, unknown> | 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<string, unknown> | 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<string, unknown>
+}) {
+ return (
+ <BasicTool
+ icon="mcp"
+ status={props.status}
+ trigger={{
+ title: `Called \`${props.tool}\``,
+ subtitle: label(props.input),
+ args: args(props.input),
+ }}
+ hideDetails={props.hideDetails}
+ />
+ )
}
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<string>()
+ 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 (
+ <Show when={links().length > 0}>
+ <div data-component="exa-tool-output">
+ <div data-slot="exa-tool-links">
+ <For each={links()}>
+ {(url) => (
+ <a
+ data-slot="exa-tool-link"
+ href={url}
+ target="_blank"
+ rel="noopener noreferrer"
+ onClick={(event) => event.stopPropagation()}
+ >
+ {url}
+ </a>
+ )}
+ </For>
+ </div>
+ </div>
+ </Show>
+ )
+}
+
export function registerPartComponent(type: string, component: PartComponent) {
PART_MAPPING[type] = component
}
@@ -1468,6 +1518,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 (
+ <BasicTool
+ {...props}
+ icon="window-cursor"
+ trigger={{
+ title: i18n.t("ui.tool.websearch"),
+ subtitle: query(),
+ subtitleClass: "exa-tool-query",
+ }}
+ >
+ <ExaOutput output={props.output} />
+ </BasicTool>
+ )
+ },
+})
+
+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 (
+ <BasicTool
+ {...props}
+ icon="code"
+ trigger={{
+ title: i18n.t("ui.tool.codesearch"),
+ subtitle: query(),
+ subtitleClass: "exa-tool-query",
+ }}
+ >
+ <ExaOutput output={props.output} />
+ </BasicTool>
+ )
+ },
+})
+
+ToolRegistry.register({
name: "task",
render(props) {
const data = useData()
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts
index 3579eff5a..f0a56f772 100644
--- a/packages/ui/src/i18n/ar.ts
+++ b/packages/ui/src/i18n/ar.ts
@@ -94,6 +94,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "جلب الويب",
+ "ui.tool.websearch": "بحث الويب",
+ "ui.tool.codesearch": "بحث الكود",
"ui.tool.shell": "Shell",
"ui.tool.patch": "تصحيح",
"ui.tool.todos": "المهام",
diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts
index 76028878f..d06050605 100644
--- a/packages/ui/src/i18n/br.ts
+++ b/packages/ui/src/i18n/br.ts
@@ -94,6 +94,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Buscar Web",
+ "ui.tool.websearch": "Pesquisa na Web",
+ "ui.tool.codesearch": "Pesquisa de Código",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Tarefas",
diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts
index 9bc229336..754c6bcef 100644
--- a/packages/ui/src/i18n/bs.ts
+++ b/packages/ui/src/i18n/bs.ts
@@ -98,6 +98,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Web preuzimanje",
+ "ui.tool.websearch": "Pretraga weba",
+ "ui.tool.codesearch": "Pretraga koda",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Lista zadataka",
diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts
index 1bb475856..0126a60c8 100644
--- a/packages/ui/src/i18n/da.ts
+++ b/packages/ui/src/i18n/da.ts
@@ -93,6 +93,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webhentning",
+ "ui.tool.websearch": "Websøgning",
+ "ui.tool.codesearch": "Kodesøgning",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Opgaver",
diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts
index 951833c30..24d99ef79 100644
--- a/packages/ui/src/i18n/de.ts
+++ b/packages/ui/src/i18n/de.ts
@@ -99,6 +99,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webabruf",
+ "ui.tool.websearch": "Websuche",
+ "ui.tool.codesearch": "Codesuche",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Aufgaben",
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts
index 9c9ae6e27..1d92ea507 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -95,6 +95,8 @@ export const dict: Record<string, string> = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "Web Search",
+ "ui.tool.codesearch": "Code Search",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "To-dos",
diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts
index 6fb6eea51..9ee95d824 100644
--- a/packages/ui/src/i18n/es.ts
+++ b/packages/ui/src/i18n/es.ts
@@ -94,6 +94,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "Búsqueda web",
+ "ui.tool.codesearch": "Búsqueda de código",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Parche",
"ui.tool.todos": "Tareas",
diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts
index 3a77a3f5c..431abe568 100644
--- a/packages/ui/src/i18n/fr.ts
+++ b/packages/ui/src/i18n/fr.ts
@@ -94,6 +94,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "Recherche Web",
+ "ui.tool.codesearch": "Recherche de code",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Tâches",
diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts
index 9dfb03f76..c6cb2ac40 100644
--- a/packages/ui/src/i18n/ja.ts
+++ b/packages/ui/src/i18n/ja.ts
@@ -93,6 +93,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "Web検索",
+ "ui.tool.codesearch": "コード検索",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Todo",
diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts
index 84d261ac8..cd306e879 100644
--- a/packages/ui/src/i18n/ko.ts
+++ b/packages/ui/src/i18n/ko.ts
@@ -94,6 +94,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "웹 가져오기",
+ "ui.tool.websearch": "웹 검색",
+ "ui.tool.codesearch": "코드 검색",
"ui.tool.shell": "셸",
"ui.tool.patch": "패치",
"ui.tool.todos": "할 일",
diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts
index dd1822bee..ddfe09461 100644
--- a/packages/ui/src/i18n/no.ts
+++ b/packages/ui/src/i18n/no.ts
@@ -97,6 +97,8 @@ export const dict: Record<Keys, string> = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webhenting",
+ "ui.tool.websearch": "Nettsøk",
+ "ui.tool.codesearch": "Kodesøk",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Gjøremål",
diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts
index fcfedb2ef..73fa96afa 100644
--- a/packages/ui/src/i18n/pl.ts
+++ b/packages/ui/src/i18n/pl.ts
@@ -93,6 +93,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Pobieranie sieciowe",
+ "ui.tool.websearch": "Wyszukiwanie w sieci",
+ "ui.tool.codesearch": "Wyszukiwanie kodu",
"ui.tool.shell": "Terminal",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Zadania",
diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts
index 713ff47d1..085be2843 100644
--- a/packages/ui/src/i18n/ru.ts
+++ b/packages/ui/src/i18n/ru.ts
@@ -93,6 +93,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "Веб-поиск",
+ "ui.tool.codesearch": "Поиск кода",
"ui.tool.shell": "Оболочка",
"ui.tool.patch": "Патч",
"ui.tool.todos": "Задачи",
diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts
index 44761a279..705f68d1b 100644
--- a/packages/ui/src/i18n/th.ts
+++ b/packages/ui/src/i18n/th.ts
@@ -95,6 +95,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "ดึงจากเว็บ",
+ "ui.tool.websearch": "ค้นหาเว็บ",
+ "ui.tool.codesearch": "ค้นหาโค้ด",
"ui.tool.shell": "เชลล์",
"ui.tool.patch": "แพตช์",
"ui.tool.todos": "รายการงาน",
diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts
index 5ec108d4a..fa3bddb21 100644
--- a/packages/ui/src/i18n/tr.ts
+++ b/packages/ui/src/i18n/tr.ts
@@ -90,6 +90,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Web getir",
+ "ui.tool.websearch": "Web Araması",
+ "ui.tool.codesearch": "Kod Araması",
"ui.tool.shell": "Kabuk",
"ui.tool.patch": "Yama",
"ui.tool.todos": "Görevler",
diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts
index 39226605b..571574d92 100644
--- a/packages/ui/src/i18n/zh.ts
+++ b/packages/ui/src/i18n/zh.ts
@@ -98,6 +98,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "网络搜索",
+ "ui.tool.codesearch": "代码搜索",
"ui.tool.shell": "Shell",
"ui.tool.patch": "补丁",
"ui.tool.todos": "待办",
diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts
index 068e222d6..edbc96b12 100644
--- a/packages/ui/src/i18n/zht.ts
+++ b/packages/ui/src/i18n/zht.ts
@@ -98,6 +98,8 @@ export const dict = {
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Webfetch",
+ "ui.tool.websearch": "網頁搜尋",
+ "ui.tool.codesearch": "程式碼搜尋",
"ui.tool.shell": "Shell",
"ui.tool.patch": "修補",
"ui.tool.todos": "待辦",