diff --git a/packages/kilo-i18n/src/ar.ts b/packages/kilo-i18n/src/ar.ts index 4a8cd1c79ff..87a99ef423b 100644 --- a/packages/kilo-i18n/src/ar.ts +++ b/packages/kilo-i18n/src/ar.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "موصى به", "dialog.provider.kilo.note": "الوصول إلى أكثر من 500 نموذج ذكاء اصطناعي", + + // Reasoning block label + "ui.reasoning.label": "الاستدلال", } diff --git a/packages/kilo-i18n/src/br.ts b/packages/kilo-i18n/src/br.ts index 4734a98d0f4..fe1c553ba2c 100644 --- a/packages/kilo-i18n/src/br.ts +++ b/packages/kilo-i18n/src/br.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Recomendados", "dialog.provider.kilo.note": "Acesso a mais de 500 modelos de IA", + + // Reasoning block label + "ui.reasoning.label": "Raciocínio", } diff --git a/packages/kilo-i18n/src/bs.ts b/packages/kilo-i18n/src/bs.ts index 28025010251..96dd911fd11 100644 --- a/packages/kilo-i18n/src/bs.ts +++ b/packages/kilo-i18n/src/bs.ts @@ -16,4 +16,7 @@ export const dict = { "desktop.menu.reloadWebview": "Ponovno učitavanje webview-a", "desktop.updater.installFailed.message": "Neuspjelo instaliranje ažuriranja", "desktop.cli.installed.message": "CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'kilo'.", + + // Reasoning block label + "ui.reasoning.label": "Rezonovanje", } diff --git a/packages/kilo-i18n/src/da.ts b/packages/kilo-i18n/src/da.ts index 16f0c8f6828..e07c99b0268 100644 --- a/packages/kilo-i18n/src/da.ts +++ b/packages/kilo-i18n/src/da.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Anbefalede", "dialog.provider.kilo.note": "Adgang til 500+ AI-modeller", + + // Reasoning block label + "ui.reasoning.label": "Ræsonnement", } diff --git a/packages/kilo-i18n/src/de.ts b/packages/kilo-i18n/src/de.ts index b39b7eb4776..40478b3c35a 100644 --- a/packages/kilo-i18n/src/de.ts +++ b/packages/kilo-i18n/src/de.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Empfohlen", "dialog.provider.kilo.note": "Zugriff auf 500+ KI-Modelle", + + // Reasoning block label + "ui.reasoning.label": "Denken", } diff --git a/packages/kilo-i18n/src/en.ts b/packages/kilo-i18n/src/en.ts index 070c3d41cea..4c03f296e18 100644 --- a/packages/kilo-i18n/src/en.ts +++ b/packages/kilo-i18n/src/en.ts @@ -14,6 +14,9 @@ export const dict = { "dialog.provider.group.recommended": "Recommended", "dialog.provider.kilo.note": "Access 500+ AI models", + // Reasoning block label + "ui.reasoning.label": "Reasoning", + // Add any upstream overrides below this line // Example: "some.upstream.key": "Kilo-specific override value", } diff --git a/packages/kilo-i18n/src/es.ts b/packages/kilo-i18n/src/es.ts index 25c5dba9f36..b8bd8c27f75 100644 --- a/packages/kilo-i18n/src/es.ts +++ b/packages/kilo-i18n/src/es.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Recomendados", "dialog.provider.kilo.note": "Acceso a más de 500 modelos de IA", + + // Reasoning block label + "ui.reasoning.label": "Razonamiento", } diff --git a/packages/kilo-i18n/src/fr.ts b/packages/kilo-i18n/src/fr.ts index d593e4a2be3..70797589a53 100644 --- a/packages/kilo-i18n/src/fr.ts +++ b/packages/kilo-i18n/src/fr.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Recommandés", "dialog.provider.kilo.note": "Accès à plus de 500 modèles d'IA", + + // Reasoning block label + "ui.reasoning.label": "Raisonnement", } diff --git a/packages/kilo-i18n/src/ja.ts b/packages/kilo-i18n/src/ja.ts index 08eda7545aa..cbb255ee25e 100644 --- a/packages/kilo-i18n/src/ja.ts +++ b/packages/kilo-i18n/src/ja.ts @@ -10,4 +10,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "おすすめ", "dialog.provider.kilo.note": "500以上のAIモデルにアクセス", + + // Reasoning block label + "ui.reasoning.label": "推論", } diff --git a/packages/kilo-i18n/src/ko.ts b/packages/kilo-i18n/src/ko.ts index cbbd28aa1f3..5aba34cea40 100644 --- a/packages/kilo-i18n/src/ko.ts +++ b/packages/kilo-i18n/src/ko.ts @@ -10,4 +10,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "추천", "dialog.provider.kilo.note": "500개 이상의 AI 모델 이용 가능", + + // Reasoning block label + "ui.reasoning.label": "추론", } diff --git a/packages/kilo-i18n/src/no.ts b/packages/kilo-i18n/src/no.ts index bd17fff9b59..0c141192528 100644 --- a/packages/kilo-i18n/src/no.ts +++ b/packages/kilo-i18n/src/no.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Anbefalt", "dialog.provider.kilo.note": "Tilgang til 500+ AI-modeller", + + // Reasoning block label + "ui.reasoning.label": "Resonnement", } diff --git a/packages/kilo-i18n/src/pl.ts b/packages/kilo-i18n/src/pl.ts index 63aea01e6e3..363e04aa235 100644 --- a/packages/kilo-i18n/src/pl.ts +++ b/packages/kilo-i18n/src/pl.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Polecane", "dialog.provider.kilo.note": "Dostęp do ponad 500 modeli AI", + + // Reasoning block label + "ui.reasoning.label": "Rozumowanie", } diff --git a/packages/kilo-i18n/src/ru.ts b/packages/kilo-i18n/src/ru.ts index 7a0276ad710..0509880741b 100644 --- a/packages/kilo-i18n/src/ru.ts +++ b/packages/kilo-i18n/src/ru.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "Рекомендуемые", "dialog.provider.kilo.note": "Доступ к 500+ моделям ИИ", + + // Reasoning block label + "ui.reasoning.label": "Рассуждение", } diff --git a/packages/kilo-i18n/src/th.ts b/packages/kilo-i18n/src/th.ts index d6f88c10b10..688f92fe4b6 100644 --- a/packages/kilo-i18n/src/th.ts +++ b/packages/kilo-i18n/src/th.ts @@ -11,4 +11,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "แนะนำ", "dialog.provider.kilo.note": "เข้าถึงโมเดล AI มากกว่า 500 รายการ", + + // Reasoning block label + "ui.reasoning.label": "การให้เหตุผล", } diff --git a/packages/kilo-i18n/src/zh.ts b/packages/kilo-i18n/src/zh.ts index 93d0f4f6c0d..a864cdbcd48 100644 --- a/packages/kilo-i18n/src/zh.ts +++ b/packages/kilo-i18n/src/zh.ts @@ -9,4 +9,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "推荐", "dialog.provider.kilo.note": "访问 500+ AI 模型", + + // Reasoning block label + "ui.reasoning.label": "推理", } diff --git a/packages/kilo-i18n/src/zht.ts b/packages/kilo-i18n/src/zht.ts index a5b9a019f62..f8ea5697a64 100644 --- a/packages/kilo-i18n/src/zht.ts +++ b/packages/kilo-i18n/src/zht.ts @@ -9,4 +9,7 @@ export const dict = { // Provider dialog translations "dialog.provider.group.recommended": "推薦", "dialog.provider.kilo.note": "存取 500+ AI 模型", + + // Reasoning block label + "ui.reasoning.label": "推理", } diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css index 063a52330f8..f7b84895501 100644 --- a/packages/kilo-ui/src/components/message-part.css +++ b/packages/kilo-ui/src/components/message-part.css @@ -117,3 +117,64 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] { [data-component="question-answers"] { padding: 8px; } + +/* Reasoning block: visually distinct from normal agent output */ +[data-component="reasoning-part"] { + width: 100%; + border-left: 2px solid var(--border-weak-base); + border-radius: var(--radius-sm); + overflow: hidden; + + [data-component="collapsible"].tool-collapsible { + gap: 0; + } + + [data-slot="collapsible-trigger"] { + height: 28px; + padding: 0 8px; + gap: 6px; + background: var(--surface-inset-base); + border-radius: 0; + cursor: pointer; + } + + [data-slot="reasoning-header"] { + display: flex; + flex: 1 1 auto; + align-items: center; + gap: 6px; + color: var(--text-weaker); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + min-width: 0; + + [data-component="icon"] { + flex-shrink: 0; + color: var(--icon-weak-base); + } + } + + [data-slot="reasoning-label"] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + [data-slot="reasoning-content"] { + padding: 8px 10px; + background: var(--surface-inset-base); + + [data-component="markdown"] { + margin-top: 0; + font-style: italic; + font-size: var(--font-size-small); + color: var(--text-weak); + opacity: 0.9; + + strong, + b { + color: var(--text-weak); + } + } + } +} diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 5b83670bb08..157e015f9e8 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -1 +1,41 @@ export * from "@opencode-ai/ui/message-part" + +import { Show, createSignal } from "solid-js" +import { PART_MAPPING } from "@opencode-ai/ui/message-part" +import { Collapsible } from "@opencode-ai/ui/collapsible" +import { Icon } from "@opencode-ai/ui/icon" +import { Markdown } from "@opencode-ai/ui/markdown" +import { useI18n } from "@opencode-ai/ui/context/i18n" +import type { ReasoningPart } from "@kilocode/sdk/v2" +import type { MessagePartProps } from "@opencode-ai/ui/message-part" + +// Override: collapsible reasoning block with brain icon + label +// Replaces the upstream flat markdown render with a clearly distinguished, collapsed-by-default block +PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProps) { + const part = props.part as unknown as ReasoningPart + const i18n = useI18n() + const [open, setOpen] = createSignal(false) + // Filter out redacted reasoning chunks from OpenRouter (encrypted reasoning data appears as [REDACTED]) + const text = () => (part.text ?? "").replace("[REDACTED]", "").trim() + + return ( + + + + + + + {i18n.t("ui.reasoning.label" as never)} + + + + + + + + + + + + ) +} diff --git a/packages/kilo-ui/src/stories/message-part.stories.tsx b/packages/kilo-ui/src/stories/message-part.stories.tsx index 812c562bc0f..d2b546c3b59 100644 --- a/packages/kilo-ui/src/stories/message-part.stories.tsx +++ b/packages/kilo-ui/src/stories/message-part.stories.tsx @@ -1,6 +1,8 @@ /** @jsxImportSource solid-js */ import type { Meta, StoryObj } from "storybook-solidjs-vite" import { Message, AssistantMessageDisplay, UserMessageDisplay } from "@opencode-ai/ui/message-part" +// Side-effect import: registers kilo-ui's PART_MAPPING override for reasoning blocks +import "../components/message-part" import { DataProvider } from "@opencode-ai/ui/context/data" import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" import { CodeComponentProvider } from "@opencode-ai/ui/context/code" @@ -8,7 +10,7 @@ import { DialogProvider } from "@opencode-ai/ui/context/dialog" import { MarkedProvider } from "@opencode-ai/ui/context/marked" import { Diff } from "@opencode-ai/ui/diff" import { Code } from "@opencode-ai/ui/code" -import type { UserMessage, AssistantMessage, TextPart, ToolPart } from "@kilocode/sdk/v2" +import type { UserMessage, AssistantMessage, TextPart, ToolPart, ReasoningPart } from "@kilocode/sdk/v2" const SESSION_ID = "session-story-001" const USER_MSG_ID = "user-msg-001" @@ -104,6 +106,15 @@ const errorToolPart: ToolPart = { }, } +const reasoningPart: ReasoningPart = { + id: "part-reasoning-001", + sessionID: SESSION_ID, + messageID: ASST_MSG_ID, + type: "reasoning", + text: "Let me think about this carefully. The user wants code improvements.\n\n1. First, I should check for error boundaries — they prevent cascading failures\n2. The dependencies could be updated to newer minor versions\n3. Unit tests would improve confidence in refactoring later\n\nI'll structure my response to address each point clearly.", + time: { start: now - 9000, end: now - 8500 }, +} + const mockData = { session: [], session_status: {}, @@ -186,6 +197,33 @@ export const FullConversationTurn: Story = { ), } +export const WithReasoningCollapsed: Story = { + name: "WithReasoning (collapsed)", + render: () => ( + + + + ), +} + +export const WithReasoningExpanded: Story = { + name: "WithReasoning (expanded)", + render: () => { + // Use a wrapper to render with the collapsible open by default + const expandedReasoningPart = { ...reasoningPart, id: "part-reasoning-expanded" } + return ( + + + + ) + }, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + // Click the collapsible trigger to expand it + const trigger = canvasElement.querySelector("[data-slot='reasoning-header']")?.closest("button") + if (trigger) trigger.click() + }, +} + export const MessageSwitch: Story = { render: () => ( diff --git a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png new file mode 100644 index 00000000000..fdf49245b20 --- /dev/null +++ b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-collapsed-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf93ca79d15deab7dc52473f68305b521c62d10e968a9258e09609ad9c04fde6 +size 15837 diff --git a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png new file mode 100644 index 00000000000..3da58463454 --- /dev/null +++ b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14bf991adcbc6fcbf53f4cff2275dcec255943b5545c624231aeb73529ca2d6e +size 32478