diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte
index cd7ad69769..6c51176aad 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageThinkingBlock.svelte
@@ -5,19 +5,22 @@
import { buttonVariants } from '$lib/components/ui/button/index.js';
import { Card } from '$lib/components/ui/card';
import { config } from '$lib/stores/settings.svelte';
+ import type { Snippet } from 'svelte';
interface Props {
class?: string;
hasRegularContent?: boolean;
isStreaming?: boolean;
reasoningContent: string | null;
+ children?: Snippet;
}
let {
class: className = '',
hasRegularContent = false,
isStreaming = false,
- reasoningContent
+ reasoningContent,
+ children
}: Props = $props();
const currentConfig = config();
@@ -72,9 +75,11 @@
-
+ {#if children}
+ {@render children()}
+ {:else}
{reasoningContent ?? ''}
-
+ {/if}
diff --git a/tools/server/webui/src/lib/services/chat.ts b/tools/server/webui/src/lib/services/chat.ts
index 3c616fc1dd..763c539dd0 100644
--- a/tools/server/webui/src/lib/services/chat.ts
+++ b/tools/server/webui/src/lib/services/chat.ts
@@ -119,6 +119,7 @@ export class ChatService {
messages: normalizedMessages.map((msg: ApiChatMessageData) => ({
role: msg.role,
content: msg.content,
+ ...(msg.reasoning_content ? { reasoning_content: msg.reasoning_content } : {}),
...((msg as ApiChatCompletionRequestMessage).tool_call_id
? { tool_call_id: (msg as ApiChatCompletionRequestMessage).tool_call_id }
: {}),
@@ -602,6 +603,9 @@ export class ChatService {
return {
role: message.role as ChatRole,
content: message.content,
+ ...(message.role === 'assistant' && message.thinking
+ ? { reasoning_content: message.thinking }
+ : {}),
// tool_call_id is only relevant for tool role messages
...(message.toolCallId ? { tool_call_id: message.toolCallId } : {}),
...(toolCalls ? { tool_calls: toolCalls } : {})
@@ -693,6 +697,9 @@ export class ChatService {
return {
role: message.role as ChatRole,
content: contentParts,
+ ...(message.role === 'assistant' && message.thinking
+ ? { reasoning_content: message.thinking }
+ : {}),
...(message.toolCallId ? { tool_call_id: message.toolCallId } : {}),
...(toolCalls ? { tool_calls: toolCalls } : {})
};
diff --git a/tools/server/webui/src/lib/types/api.d.ts b/tools/server/webui/src/lib/types/api.d.ts
index f507a7de50..9f5b1d19d0 100644
--- a/tools/server/webui/src/lib/types/api.d.ts
+++ b/tools/server/webui/src/lib/types/api.d.ts
@@ -35,6 +35,13 @@ export interface ApiChatMessageData {
role: ChatRole;
content: string | ApiChatMessageContentPart[];
timestamp?: number;
+ /**
+ * Optional reasoning/thinking content to be sent back to the server.
+ *
+ * llama-server accepts this non-OpenAI field and uses it to preserve the model's
+ * internal "thinking" blocks across tool-call resumptions (notably for gpt-oss).
+ */
+ reasoning_content?: string;
tool_call_id?: string;
tool_calls?: ApiChatCompletionToolCallDelta[];
}
@@ -183,6 +190,7 @@ export interface ApiLlamaCppServerProps {
export interface ApiChatCompletionRequestMessage {
role: ChatRole;
content: string | ApiChatMessageContentPart[];
+ reasoning_content?: string;
tool_call_id?: string;
tool_calls?: ApiChatCompletionToolCallDelta[];
}
diff --git a/tools/server/webui/tests/client/chatMessage.delete-merged-assistant.test.ts b/tools/server/webui/tests/client/chatMessage.delete-merged-assistant.test.ts
index 19dee42f1d..1b5c2b3606 100644
--- a/tools/server/webui/tests/client/chatMessage.delete-merged-assistant.test.ts
+++ b/tools/server/webui/tests/client/chatMessage.delete-merged-assistant.test.ts
@@ -75,9 +75,8 @@ describe('ChatMessage delete for merged assistant messages', () => {
conversationsStore.activeMessages = allMessages;
// Avoid touching IndexedDB by stubbing the store call used by getDeletionInfo.
- const originalGetConversationMessages = conversationsStore.getConversationMessages.bind(
- conversationsStore
- );
+ const originalGetConversationMessages =
+ conversationsStore.getConversationMessages.bind(conversationsStore);
conversationsStore.getConversationMessages = async () => allMessages;
const onDelete = vi.fn();
@@ -111,4 +110,3 @@ describe('ChatMessage delete for merged assistant messages', () => {
}
});
});
-
diff --git a/tools/server/webui/tests/client/components/TestChatMessageWrapper.svelte b/tools/server/webui/tests/client/components/TestChatMessageWrapper.svelte
index 544a881a54..9ae5505b02 100644
--- a/tools/server/webui/tests/client/components/TestChatMessageWrapper.svelte
+++ b/tools/server/webui/tests/client/components/TestChatMessageWrapper.svelte
@@ -14,4 +14,3 @@
-
diff --git a/tools/server/webui/tests/e2e/tool-output-no-echo.spec.ts b/tools/server/webui/tests/e2e/tool-output-no-echo.spec.ts
index e59f6d3e09..8f03257930 100644
--- a/tools/server/webui/tests/e2e/tool-output-no-echo.spec.ts
+++ b/tools/server/webui/tests/e2e/tool-output-no-echo.spec.ts
@@ -137,4 +137,6 @@ test('tool output does not echo tool arguments back to the model', async ({ page
);
expect(assistantWithToolCall).toBeTruthy();
expect(JSON.stringify(assistantWithToolCall?.tool_calls ?? null)).toContain('LARGE_CODE_BEGIN');
+ // Preserve the model's reasoning across tool-call resumptions (required for gpt-oss).
+ expect(String(assistantWithToolCall?.reasoning_content ?? '')).toContain('reasoning-step-1');
});