feat: render LSP tool output in chat

Adds expandable LSP tool rows with scan icon and stable file path display
Adds formatted/raw JSON output toggle and copy action
Improves compact LSP input formatting
This commit is contained in:
Bohdan Triapitsyn
2026-06-03 17:15:52 +03:00
parent 8e2c7549ca
commit a3908232ae
15 changed files with 263 additions and 14 deletions
+5
View File
@@ -1826,6 +1826,11 @@ export const dict = {
'chat.toolPart.awaitingResponse': 'Awaiting response...',
'chat.toolPart.noOutputProduced': 'No output produced',
'chat.toolPart.output': 'Output',
'chat.toolPart.showRawJson': 'Show raw JSON',
'chat.toolPart.showFormattedJson': 'Show formatted JSON',
'chat.toolPart.copyOutput': 'Copy output',
'chat.toolPart.copiedOutput': 'Copied output',
'chat.toolPart.copyOutputFailed': 'Failed to copy output',
'chat.toolPart.openSubtask': 'Open {type} subtask',
'chat.todo.total': 'Total',
'chat.todo.inProgress': 'In Progress',
+5
View File
@@ -1792,6 +1792,11 @@ export const dict: Record<I18nKey, string> = {
"chat.toolPart.awaitingResponse": "Esperando respuesta...",
"chat.toolPart.noOutputProduced": "No se produjo ninguna salida",
"chat.toolPart.output": "Salida",
"chat.toolPart.showRawJson": "Mostrar JSON sin formato",
"chat.toolPart.showFormattedJson": "Mostrar JSON formateado",
"chat.toolPart.copyOutput": "Copiar salida",
"chat.toolPart.copiedOutput": "Salida copiada",
"chat.toolPart.copyOutputFailed": "No se pudo copiar la salida",
"chat.toolPart.openSubtask": "Abrir subtarea {type}",
"chat.todo.total": "Total",
"chat.todo.inProgress": "En progreso",
+5
View File
@@ -1826,6 +1826,11 @@ export const dict: Record<I18nKey, string> = {
'chat.toolPart.awaitingResponse': '응답 대기 중…',
'chat.toolPart.noOutputProduced': '출력 없음',
'chat.toolPart.output': '출력',
'chat.toolPart.showRawJson': '원시 JSON 표시',
'chat.toolPart.showFormattedJson': '형식화된 JSON 표시',
'chat.toolPart.copyOutput': '출력 복사',
'chat.toolPart.copiedOutput': '출력 복사됨',
'chat.toolPart.copyOutputFailed': '출력을 복사하지 못했습니다',
'chat.toolPart.openSubtask': '{type} 하위 작업 열기',
'chat.todo.total': '전체',
'chat.todo.inProgress': '진행 중',
+5
View File
@@ -1199,6 +1199,11 @@ export const dict: Record<I18nKey, string> = {
'chat.toolPart.noOutputProduced': 'Brak wygenerowanego wyniku',
'chat.toolPart.openSubtask': 'Otwórz podzadanie typu {type}',
'chat.toolPart.output': 'Wyjście',
'chat.toolPart.showRawJson': 'Pokaż surowy JSON',
'chat.toolPart.showFormattedJson': 'Pokaż sformatowany JSON',
'chat.toolPart.copyOutput': 'Kopiuj wyjście',
'chat.toolPart.copiedOutput': 'Skopiowano wyjście',
'chat.toolPart.copyOutputFailed': 'Nie udało się skopiować wyjścia',
'commandPalette.description': 'Szukaj plików, sesji i poleceń.',
'commandPalette.empty.noResults': 'Nie znaleziono wyników.',
'commandPalette.empty.searchingFiles': 'Wyszukiwanie plików...',
@@ -1792,6 +1792,11 @@ export const dict: Record<I18nKey, string> = {
"chat.toolPart.awaitingResponse": "Aguardando resposta...",
"chat.toolPart.noOutputProduced": "Nenhuma saída produzida",
"chat.toolPart.output": "Saída",
"chat.toolPart.showRawJson": "Mostrar JSON bruto",
"chat.toolPart.showFormattedJson": "Mostrar JSON formatado",
"chat.toolPart.copyOutput": "Copiar saída",
"chat.toolPart.copiedOutput": "Saída copiada",
"chat.toolPart.copyOutputFailed": "Falha ao copiar saída",
"chat.toolPart.openSubtask": "Abrir subtarefa {type}",
"chat.todo.total": "Total",
"chat.todo.inProgress": "Em andamento",
+5
View File
@@ -1792,6 +1792,11 @@ export const dict: Record<I18nKey, string> = {
"chat.toolPart.awaitingResponse": "Очікування відповіді...",
"chat.toolPart.noOutputProduced": "Вивід відсутній",
"chat.toolPart.output": "Вивід",
"chat.toolPart.showRawJson": "Показати сирий JSON",
"chat.toolPart.showFormattedJson": "Показати форматований JSON",
"chat.toolPart.copyOutput": "Скопіювати вивід",
"chat.toolPart.copiedOutput": "Вивід скопійовано",
"chat.toolPart.copyOutputFailed": "Не вдалося скопіювати вивід",
"chat.toolPart.openSubtask": "Відкрити підзавдання {type}",
"chat.todo.total": "Усього",
"chat.todo.inProgress": "В роботі",
@@ -1792,6 +1792,11 @@ export const dict: Record<I18nKey, string> = {
'chat.toolPart.awaitingResponse': '等待响应...',
'chat.toolPart.noOutputProduced': '未产生输出',
'chat.toolPart.output': '输出',
'chat.toolPart.showRawJson': '显示原始 JSON',
'chat.toolPart.showFormattedJson': '显示格式化 JSON',
'chat.toolPart.copyOutput': '复制输出',
'chat.toolPart.copiedOutput': '已复制输出',
'chat.toolPart.copyOutputFailed': '复制输出失败',
'chat.toolPart.openSubtask': '打开{type}子任务',
'chat.todo.total': '总计',
'chat.todo.inProgress': '进行中',
@@ -1796,6 +1796,11 @@ export const dict: Record<I18nKey, string> = {
'chat.toolPart.awaitingResponse': '等待回應...',
'chat.toolPart.noOutputProduced': '未產生輸出',
'chat.toolPart.output': '輸出',
'chat.toolPart.showRawJson': '顯示原始 JSON',
'chat.toolPart.showFormattedJson': '顯示格式化 JSON',
'chat.toolPart.copyOutput': '複製輸出',
'chat.toolPart.copiedOutput': '已複製輸出',
'chat.toolPart.copyOutputFailed': '複製輸出失敗',
'chat.toolPart.openSubtask': '開啟{type}子任務',
'chat.todo.total': '總計',
'chat.todo.inProgress': '進行中',
+38 -5
View File
@@ -165,12 +165,25 @@ export const TOOL_METADATA: Record<string, ToolMetadata> = {
{ key: 'name', label: 'Skill Name', type: 'text' }
]
},
question: {
displayName: 'Question',
category: 'ai',
outputLanguage: 'text',
question: {
displayName: 'Question',
category: 'ai',
outputLanguage: 'text',
inputFields: [
{ key: 'questions', label: 'Questions', type: 'code', language: 'json' }
]
},
lsp: {
displayName: 'LSP',
category: 'code',
outputLanguage: 'json',
inputFields: [
{ key: 'questions', label: 'Questions', type: 'code', language: 'json' }
{ key: 'operation', label: 'Operation', type: 'text' },
{ key: 'filePath', label: 'File Path', type: 'file' },
{ key: 'line', label: 'Line', type: 'text' },
{ key: 'character', label: 'Character', type: 'text' },
{ key: 'query', label: 'Query', type: 'text' }
]
},
@@ -702,6 +715,26 @@ export function formatToolInput(input: Record<string, unknown>, toolName: string
if (cmd) return cmd;
}
if (toolName === 'lsp') {
const operation = getString('operation') || 'lsp';
const filePath = getString('filePath') || getString('file_path') || getString('path');
const line = getString('line');
const character = getString('character');
const query = getString('query');
const position = line && character ? ` (Line: ${line}; Character: ${character})` : '';
if (operation === 'workspaceSymbol') {
return query ? `Operation: ${operation} (Query: "${query}")` : `Operation: ${operation}`;
}
const summary = `Operation: ${operation}${position}`;
if (filePath) {
return `${summary}\n${filePath}`;
}
return summary;
}
if (toolName === 'task') {
const prompt = getString('prompt');
if (prompt) return prompt;