* feat: Add shimmer text animation for processing state indicators * feat: Redesign CollapsibleContentBlock component with improved UX * feat: Add conditional setting display support with dependsOn field * feat: Add showAgenticTurnStats setting for per-turn statistics * feat: Update ChatMessageAgenticContent with improved UI and new features * feat: Enhance file read tool UI/UX * feat: Refine styling of collapsible content and code preview blocks * feat: add terminal variant to CollapsibleContentBlock * feat: add built-in tools UI registry * feat: extract ChatMessageReasoningBlock and ChatMessageToolCallBlock * refactor: simplify ChatMessageAgenticContent to use extracted blocks * fix: correct markdown content block margin spacing * fix: reorganize SettingsChatFields layout and reset button positioning * fix: use direct map access in agentic store session methods * refactor: remove reasoning preview/throttle system from CollapsibleContentBlock * feat: add auto-scroll to reasoning block and remove showThoughtInProgress * feat: add ChatMessageToolCallDateTime component and support for new tool types * feat: improve auto-scroll reliability in reasoning block with RAF coalescing and MutationObserver * feat: show MCP server favicon for tools without a built-in icon * feat: add search-results parsing utilities and tests * feat: add ChatMessageToolCallSearchResults component * feat: integrate search results rendering into ChatMessageAgenticContent * feat: display tool call input alongside output in ChatMessageToolCallBlock * style: use muted foreground color in reasoning block content * chore: Format * feat: Refine reasoning block layout and make pending thoughts display configurable * feat: Stream tool call code blocks with auto-scroll and handle partial JSON * feat: add streaming permission gate infrastructure * feat: wire permission gate into the agentic loop * fix: bail out on abort and skip already-approved tool calls * fix: clear partial tool calls on abort and savePartialResponse * test: cover partial tool call cleanup end-to-end * refactor: Remove streaming permission gate logic * fix: Correct autoscroll and streaming gates for tool calls and reasoning blocks * refactor: Chat Message Assistant componentization * fix: Show health metadata for disabled MCP servers and promote connections on enable * fix: Inherit global enabled state for missing MCP per-chat overrides * refactor: Cleanup * refactor: Split ChatMessageToolCallBlock into dedicated components * feat: Add live streaming and auto-scroll for tool execution output * feat: Add line numbers and change markers to file edit diffs * chore: Formatting * feat: Add type definitions and utilities for recommended MCP servers * feat: Add recommended MCP servers configuration and storage key * feat: Add McpServerCardCompact component for recommended servers * feat: Add recommended servers section to Add New Server dialog * feat: Update McpServerForm to support authorization requirements * feat: Add select-none classes for text selection prevention * feat: Add recommended MCP server icon assets * refactor: Store dismissed MCP recommendations as a boolean flag * feat: Render tool results as JSON or Markdown based on detected content type * feat: UI improvement * feat: Render search block early and update heading to show execution state * fix: Prevent non-web-search tools from triggering the search UI block * refactor: Cleanup * refactor: Extract hardcoded icon size classes into shared constants * refactor: Extract hardcoded tool result separator into a shared constant * refactor: Tool Calls UI/logic * refactor: Cleanup * refactor: Cleanup * refactor: Cleanup
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { MessageRole } from '$lib/enums';
|
|
import { deriveAgenticSections } from '$lib/utils/agentic';
|
|
import type { DatabaseMessage } from '$lib/types/database';
|
|
|
|
function makeAssistant(overrides: Partial<DatabaseMessage> = {}): DatabaseMessage {
|
|
return {
|
|
id: overrides.id ?? 'ast-1',
|
|
convId: 'conv-1',
|
|
type: 'text',
|
|
timestamp: Date.now(),
|
|
role: MessageRole.ASSISTANT,
|
|
content: overrides.content ?? '',
|
|
parent: null,
|
|
children: [],
|
|
...overrides
|
|
} as DatabaseMessage;
|
|
}
|
|
|
|
// Mirrors the filter inside ChatService.convertDbMessageToApiChatMessageData:
|
|
// a partial tool call captured mid-stream must not survive into the next request
|
|
// payload. The fix in chatStore.savePartialResponseIfNeeded clears toolCalls to ''
|
|
// on Stop/Send immediately, mirroring what the agentic flow already does in
|
|
// onAssistantTurnComplete(...undefined).
|
|
function buildApiToolCalls(message: DatabaseMessage): unknown[] | undefined {
|
|
if (!message.toolCalls) return undefined;
|
|
try {
|
|
const parsed = JSON.parse(message.toolCalls);
|
|
return Array.isArray(parsed) && parsed.length > 0 ? parsed : undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
describe('partial tool call cleanup', () => {
|
|
// Reproduces the broken payload from the user's screenshot: model was
|
|
// streaming a tool call whose arguments JSON was cut mid-string. The outer
|
|
// envelope still parses, but the arguments themselves are invalid JSON and
|
|
// the server rejects the request.
|
|
it('marks a partial tool call payload as unsafe to re-send', () => {
|
|
const message = makeAssistant({
|
|
content: 'partial reasoning',
|
|
toolCalls: JSON.stringify([
|
|
{
|
|
id: 'call_1',
|
|
type: 'function',
|
|
function: {
|
|
name: 'exec_shell_command',
|
|
arguments: '{"command":`grep -n \\"read_to\\" ` /Users'
|
|
}
|
|
}
|
|
])
|
|
});
|
|
|
|
const apiToolCalls = buildApiToolCalls(message);
|
|
|
|
// The bug: even though arguments are invalid, the outer array parses and
|
|
// the request gets sent. Function arguments must be parseable JSON on their
|
|
// own for the server to execute the tool.
|
|
expect(apiToolCalls).toBeDefined();
|
|
const args = (apiToolCalls![0] as { function: { arguments: string } }).function.arguments;
|
|
expect(() => JSON.parse(args)).toThrow();
|
|
});
|
|
|
|
// After Stop, savePartialResponseIfNeeded clears toolCalls and the agentic
|
|
// flow does the same in its silent-return detection. The next request reads
|
|
// toolCalls = '' and the conversion drops the field entirely so the server
|
|
// never sees the half-streamed call.
|
|
it('drops tool_calls from the API request after toolCalls is cleared', () => {
|
|
const clearedMessage = makeAssistant({
|
|
content: 'partial reasoning',
|
|
toolCalls: ''
|
|
});
|
|
|
|
const apiToolCalls = buildApiToolCalls(clearedMessage);
|
|
expect(apiToolCalls).toBeUndefined();
|
|
});
|
|
|
|
// The cleanup path keeps the partial reasoning content visible in the UI;
|
|
// only the tool_calls field is reset. deriveAgenticSections should still
|
|
// surface the reasoning as interrupted (no content / no tool calls behind
|
|
// it) without resurrecting the dead tool call block.
|
|
it('keeps reasoning content visible after cleanup, without a tool call block', () => {
|
|
const cleared = makeAssistant({
|
|
content: '',
|
|
reasoningContent: 'thinking about read_to',
|
|
toolCalls: ''
|
|
});
|
|
|
|
const sections = deriveAgenticSections(cleared);
|
|
expect(sections).toHaveLength(1);
|
|
expect(sections[0].type).toBe('reasoning');
|
|
expect(sections.some((s) => s.type.includes('tool_call'))).toBe(false);
|
|
});
|
|
});
|