Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions src/app/(dashboard)/dashboard/profile/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -553,6 +568,7 @@ export default function ProfilePage() {
};

const observabilityEnabled = settings.enableObservability === true;
const logToolSourcesEnabled = settings.logToolSources === true;

const handleShutdown = async () => {
setIsShuttingDown(true);
Expand Down Expand Up @@ -1089,18 +1105,34 @@ export default function ProfilePage() {
</div>
<h3 className="text-base sm:text-lg font-semibold">Observability</h3>
</div>
<div className="flex items-start sm:items-center justify-between gap-4">
<div className="flex-1 min-w-0">
<p className="font-medium text-sm sm:text-base">Enable Observability</p>
<p className="text-xs sm:text-sm text-text-muted">
Record request details for inspection in the logs view
</p>
<div className="flex flex-col gap-4">
<div className="flex items-start sm:items-center justify-between gap-4">
<div className="flex-1 min-w-0">
<p className="font-medium text-sm sm:text-base">Enable Observability</p>
<p className="text-xs sm:text-sm text-text-muted">
Record request details for inspection in the logs view
</p>
</div>
<Toggle
checked={observabilityEnabled}
onChange={updateObservabilityEnabled}
disabled={loading}
/>
</div>

<div className="flex items-start sm:items-center justify-between gap-4 pt-4 border-t border-border/50">
<div className="flex-1 min-w-0">
<p className="font-medium text-sm sm:text-base">Log Tool Sources</p>
<p className="text-xs sm:text-sm text-text-muted">
Add a diagnostic [TOOLS] line with tool names and MCP/source counts for each request
</p>
</div>
<Toggle
checked={logToolSourcesEnabled}
onChange={updateLogToolSources}
disabled={loading}
/>
</div>
<Toggle
checked={observabilityEnabled}
onChange={updateObservabilityEnabled}
disabled={loading}
/>
</div>
</Card>

Expand Down
1 change: 1 addition & 0 deletions src/lib/db/repos/settingsRepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
51 changes: 51 additions & 0 deletions src/sse/handlers/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,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)");
Expand Down