From 919a060efc799c4732ee1519130ab3bf448515bd Mon Sep 17 00:00:00 2001 From: Mr_NoboDy Date: Fri, 5 Jun 2026 00:50:52 +0700 Subject: [PATCH 1/2] chore: log incoming tool sources --- src/sse/handlers/chat.js | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/sse/handlers/chat.js b/src/sse/handlers/chat.js index 9c4b159855..451e5ed3c1 100644 --- a/src/sse/handlers/chat.js +++ b/src/sse/handlers/chat.js @@ -20,6 +20,52 @@ import * as log from "../utils/logger.js"; import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js"; import { getProjectIdForConnection } from "open-sse/services/projectId.js"; +function getToolName(tool) { + return tool?.name || tool?.function?.name || tool?.type || "unknown"; +} + +function getToolSource(name) { + if (name.startsWith("mcp__")) { + const parts = name.split("__"); + return parts[1] ? `mcp:${parts[1]}` : "mcp"; + } + if (name.startsWith("web_search") || name.startsWith("web_fetch")) return "hosted:web"; + if (name.startsWith("computer_") || name.startsWith("str_replace_")) return "hosted:computer"; + return "client"; +} + +function summarizeToolSources(tools) { + if (!Array.isArray(tools) || tools.length === 0) return null; + const names = tools.map(getToolName); + const sourceCounts = new Map(); + for (const name of names) { + const source = getToolSource(name); + sourceCounts.set(source, (sourceCounts.get(source) || 0) + 1); + } + const sources = Array.from(sourceCounts.entries()) + .map(([source, count]) => `${source}=${count}`) + .join(", "); + const visibleNames = names.slice(0, 80).join(", "); + const suffix = names.length > 80 ? `, ... +${names.length - 80} more` : ""; + return `${tools.length} tools | sources: ${sources} | names: ${visibleNames}${suffix}`; +} + +function isDeterministicPayloadError(status, errorText) { + if (status !== HTTP_STATUS.BAD_REQUEST) return false; + const text = typeof errorText === "string" ? errorText.toLowerCase() : ""; + return text.includes("content_length_exceeds_threshold") || + text.includes("input is too long") || + text.includes("context length") || + text.includes("maximum context") || + text.includes("too many tokens"); +} + +function isNonAccountRecoverableError(provider, status, errorText) { + if (isDeterministicPayloadError(status, errorText)) return true; + const text = typeof errorText === "string" ? errorText.toLowerCase() : ""; + return provider === "nvidia" && status === HTTP_STATUS.BAD_GATEWAY && text.includes("fetch connect timeout"); +} + /** * Handle chat completion request * Supports: OpenAI, Claude, Gemini, OpenAI Responses API formats @@ -54,6 +100,8 @@ export async function handleChat(request, clientRawRequest = null) { const toolCount = body.tools?.length || 0; const effort = body.reasoning_effort || body.reasoning?.effort || null; log.request("POST", `${url.pathname} | ${modelStr} | ${msgCount} msgs${toolCount ? ` | ${toolCount} tools` : ""}${effort ? ` | effort=${effort}` : ""}`); + const toolSummary = summarizeToolSources(body.tools); + if (toolSummary) log.debug("TOOLS", toolSummary); // Log API key (masked) const authHeader = request.headers.get("Authorization"); From 9055b92df8e68fe70c7ba588b188758468684933 Mon Sep 17 00:00:00 2001 From: Mr_NoboDy Date: Fri, 5 Jun 2026 03:12:18 +0700 Subject: [PATCH 2/2] feat: add tool source log toggle --- src/app/(dashboard)/dashboard/profile/page.js | 54 +++++++++++++++---- src/lib/db/repos/settingsRepo.js | 1 + src/sse/handlers/chat.js | 7 ++- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js index 2851bc0c0b..762e04a7d2 100644 --- a/src/app/(dashboard)/dashboard/profile/page.js +++ b/src/app/(dashboard)/dashboard/profile/page.js @@ -462,6 +462,21 @@ export default function ProfilePage() { } }; + const updateLogToolSources = async (enabled) => { + try { + const res = await fetch("/api/settings", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ logToolSources: enabled }), + }); + if (res.ok) { + setSettings(prev => ({ ...prev, logToolSources: enabled })); + } + } catch (err) { + console.error("Failed to update logToolSources:", err); + } + }; + const reloadSettings = async () => { try { const res = await fetch("/api/settings"); @@ -553,6 +568,7 @@ export default function ProfilePage() { }; const observabilityEnabled = settings.enableObservability === true; + const logToolSourcesEnabled = settings.logToolSources === true; const handleShutdown = async () => { setIsShuttingDown(true); @@ -1089,18 +1105,34 @@ export default function ProfilePage() {

Observability

-
-
-

Enable Observability

-

- Record request details for inspection in the logs view -

+
+
+
+

Enable Observability

+

+ Record request details for inspection in the logs view +

+
+ +
+ +
+
+

Log Tool Sources

+

+ Add a diagnostic [TOOLS] line with tool names and MCP/source counts for each request +

+
+
-
diff --git a/src/lib/db/repos/settingsRepo.js b/src/lib/db/repos/settingsRepo.js index 7201a76099..a20b1b44ba 100644 --- a/src/lib/db/repos/settingsRepo.js +++ b/src/lib/db/repos/settingsRepo.js @@ -24,6 +24,7 @@ const DEFAULT_SETTINGS = { oidcScopes: "openid profile email", oidcLoginLabel: "Sign in with OIDC", enableObservability: true, + logToolSources: false, observabilityMaxRecords: 1000, observabilityBatchSize: 20, observabilityFlushIntervalMs: 5000, diff --git a/src/sse/handlers/chat.js b/src/sse/handlers/chat.js index 451e5ed3c1..bad72454a6 100644 --- a/src/sse/handlers/chat.js +++ b/src/sse/handlers/chat.js @@ -100,8 +100,6 @@ export async function handleChat(request, clientRawRequest = null) { const toolCount = body.tools?.length || 0; const effort = body.reasoning_effort || body.reasoning?.effort || null; log.request("POST", `${url.pathname} | ${modelStr} | ${msgCount} msgs${toolCount ? ` | ${toolCount} tools` : ""}${effort ? ` | effort=${effort}` : ""}`); - const toolSummary = summarizeToolSources(body.tools); - if (toolSummary) log.debug("TOOLS", toolSummary); // Log API key (masked) const authHeader = request.headers.get("Authorization"); @@ -115,6 +113,11 @@ export async function handleChat(request, clientRawRequest = null) { // Enforce API key if enabled in settings const settings = await getSettings(); + if (settings.logToolSources === true) { + const toolSummary = summarizeToolSources(body.tools); + if (toolSummary) log.debug("TOOLS", toolSummary); + } + if (settings.requireApiKey) { if (!apiKey) { log.warn("AUTH", "Missing API key (requireApiKey=true)");