diff --git a/main.js b/main.js index f874773..e2895b1 100644 --- a/main.js +++ b/main.js @@ -11737,7 +11737,15 @@ var ChatView = class extends import_obsidian5.ItemView { } return baseMessages; } - async processToolCalls(toolCalls, messages, tools, fullResponse, assistantMessageId) { + async processToolCalls(toolCalls, messages, tools, fullResponse, assistantMessageId, depth = 0) { + if (depth >= MAX_TOOL_CALL_DEPTH) { + this.updateMessageById(assistantMessageId, { + content: fullResponse || "(No response)", + isStreaming: false, + isThinking: false + }); + return; + } const readToolCalls = toolCalls.filter((tc) => !isWriteTool(tc.function?.name ?? "")); const writeToolCalls = toolCalls.filter((tc) => isWriteTool(tc.function?.name ?? "")); const readResults = (await Promise.all( @@ -11806,21 +11814,34 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`, const followUpStartTime = Date.now(); const response = await this.getActiveOllamaClient().chat(finalMessages, tools); const followUpDurationMs = Date.now() - followUpStartTime; - const finalResponse = response.content || fullResponse; - this.updateMessageById(assistantMessageId, { - content: finalResponse, - isStreaming: false, - isThinking: false - }); + const followUpContent = response.content ?? ""; + const followUpToolCalls = response.tool_calls ?? []; this.telemetryManager?.recordLlmCall({ model: this.getActiveModel(), promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4), - completionTokens: Math.round(finalResponse.length / 4), + completionTokens: Math.round(followUpContent.length / 4), totalTokens: Math.round( - (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + finalResponse.length) / 4 + (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + followUpContent.length) / 4 ), durationMs: followUpDurationMs }); + if (followUpToolCalls.length > 0) { + this.showActivityIndicator("Using tools\u2026"); + await this.processToolCalls( + followUpToolCalls, + finalMessages, + tools, + followUpContent, + assistantMessageId, + depth + 1 + ); + } else { + this.updateMessageById(assistantMessageId, { + content: followUpContent || fullResponse, + isStreaming: false, + isThinking: false + }); + } } else { this.updateMessageById(assistantMessageId, { content: fullResponse || "No tool results to report.", @@ -11861,29 +11882,43 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`, const followUpStartTime = Date.now(); const response = await this.getActiveOllamaClient().chat(finalMessages, tools); const followUpDurationMs = Date.now() - followUpStartTime; - const finalResponse = response.content || "Actions applied successfully."; - this.updateMessageById(assistantMessageId, { - content: finalResponse, - isStreaming: false, - isThinking: false - }); + const followUpContent = response.content ?? ""; + const followUpToolCalls = response.tool_calls ?? []; this.telemetryManager?.recordLlmCall({ model: this.getActiveModel(), promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4), - completionTokens: Math.round(finalResponse.length / 4), + completionTokens: Math.round(followUpContent.length / 4), totalTokens: Math.round( - (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + finalResponse.length) / 4 + (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + followUpContent.length) / 4 ), durationMs: followUpDurationMs }); + this.clearPendingActions(); + if (followUpToolCalls.length > 0) { + this.showActivityIndicator("Using tools\u2026"); + await this.processToolCalls( + followUpToolCalls, + finalMessages, + tools, + followUpContent, + assistantMessageId, + 1 + ); + } else { + this.updateMessageById(assistantMessageId, { + content: followUpContent || "Actions applied successfully.", + isStreaming: false, + isThinking: false + }); + } } else { this.updateMessageById(assistantMessageId, { content: "Actions applied successfully.", isStreaming: false, isThinking: false }); + this.clearPendingActions(); } - this.clearPendingActions(); this.render(); } cancelPendingActions() { @@ -12460,6 +12495,7 @@ ${actualMessage}` : actualMessage; } }; var MAX_TOOL_CALLS = 5; +var MAX_TOOL_CALL_DEPTH = 5; // src/constants.ts var DEFAULT_SETTINGS = { diff --git a/src/chat-view.ts b/src/chat-view.ts index 5555120..a8fb000 100755 --- a/src/chat-view.ts +++ b/src/chat-view.ts @@ -970,8 +970,18 @@ export class ChatView extends ItemView { messages: OllamaMessage[], tools: OllamaTool[], fullResponse: string, - assistantMessageId: string + assistantMessageId: string, + depth = 0 ): Promise { + if (depth >= MAX_TOOL_CALL_DEPTH) { + this.updateMessageById(assistantMessageId, { + content: fullResponse || '(No response)', + isStreaming: false, + isThinking: false, + }); + return; + } + const readToolCalls = toolCalls.filter((tc) => !isWriteTool(tc.function?.name ?? '')); const writeToolCalls = toolCalls.filter((tc) => isWriteTool(tc.function?.name ?? '')); @@ -1055,23 +1065,38 @@ export class ChatView extends ItemView { const followUpStartTime = Date.now(); const response = await this.getActiveOllamaClient().chat(finalMessages, tools); const followUpDurationMs = Date.now() - followUpStartTime; - const finalResponse = response.content || fullResponse; - this.updateMessageById(assistantMessageId, { - content: finalResponse, - isStreaming: false, - isThinking: false, - }); + const followUpContent = response.content ?? ''; + const followUpToolCalls = response.tool_calls ?? []; // Record follow-up LLM call telemetry this.telemetryManager?.recordLlmCall({ model: this.getActiveModel(), promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4), - completionTokens: Math.round(finalResponse.length / 4), + completionTokens: Math.round(followUpContent.length / 4), totalTokens: Math.round( - (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + finalResponse.length) / 4 + (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + followUpContent.length) / 4 ), durationMs: followUpDurationMs, }); + + if (followUpToolCalls.length > 0) { + // LLM wants another round of tool use — recurse rather than discarding these calls + this.showActivityIndicator('Using tools…'); + await this.processToolCalls( + followUpToolCalls, + finalMessages, + tools, + followUpContent, + assistantMessageId, + depth + 1 + ); + } else { + this.updateMessageById(assistantMessageId, { + content: followUpContent || fullResponse, + isStreaming: false, + isThinking: false, + }); + } } else { // No tool results to follow up on — all tools failed or produced no output this.updateMessageById(assistantMessageId, { @@ -1123,32 +1148,49 @@ export class ChatView extends ItemView { const followUpStartTime = Date.now(); const response = await this.getActiveOllamaClient().chat(finalMessages, tools); const followUpDurationMs = Date.now() - followUpStartTime; - const finalResponse = response.content || 'Actions applied successfully.'; - this.updateMessageById(assistantMessageId, { - content: finalResponse, - isStreaming: false, - isThinking: false, - }); + const followUpContent = response.content ?? ''; + const followUpToolCalls = response.tool_calls ?? []; // Record follow-up LLM call telemetry this.telemetryManager?.recordLlmCall({ model: this.getActiveModel(), promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4), - completionTokens: Math.round(finalResponse.length / 4), + completionTokens: Math.round(followUpContent.length / 4), totalTokens: Math.round( - (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + finalResponse.length) / 4 + (finalMessages.reduce((sum, m) => sum + m.content.length, 0) + followUpContent.length) / 4 ), durationMs: followUpDurationMs, }); + + this.clearPendingActions(); + + if (followUpToolCalls.length > 0) { + // LLM wants more tool calls after applying — hand off to processToolCalls + this.showActivityIndicator('Using tools…'); + await this.processToolCalls( + followUpToolCalls, + finalMessages, + tools, + followUpContent, + assistantMessageId, + 1 + ); + } else { + this.updateMessageById(assistantMessageId, { + content: followUpContent || 'Actions applied successfully.', + isStreaming: false, + isThinking: false, + }); + } } else { this.updateMessageById(assistantMessageId, { content: 'Actions applied successfully.', isStreaming: false, isThinking: false, }); + this.clearPendingActions(); } - this.clearPendingActions(); this.render(); } @@ -1919,3 +1961,4 @@ export class ChatView extends ItemView { } const MAX_TOOL_CALLS = 5; +const MAX_TOOL_CALL_DEPTH = 5; diff --git a/styles.css b/styles.css index 8f1cbd4..c529f2a 100644 --- a/styles.css +++ b/styles.css @@ -21,7 +21,8 @@ .ollama-new-chat-container { display: flex; - justify-content: flex-end; + flex-wrap: wrap; + justify-content: flex-start; align-items: center; gap: var(--size-4-1); padding: var(--size-4-1) var(--size-4-2);