Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/agentMentionSearchDiagnostics.ts
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

283 lines
8.0 KiB
TypeScript

import type {
AgentMentionFilterId,
AgentMentionGroup
} from "./AgentMentionSearchController";
import { AGENT_CONTEXT_MENTION_PROVIDER_IDS } from "./agentContextMentionProvider";
export type AgentMentionProviderDiagnosticStatus =
| "success"
| "timeout"
| "error"
| "missing";
export interface AgentMentionProviderQueryDiagnostic {
durationMs: number;
errorKind?: string;
providerId: string;
resultCount: number;
status: AgentMentionProviderDiagnosticStatus;
}
export interface AgentMentionSearchDiagnosticLog {
debounceMs: number;
durationMs: number;
errorKind?: string;
event: "agent_gui.mention_search";
filter: AgentMentionFilterId;
groupCount: number;
itemCount: number;
mode: "browse" | "results";
providerResults: AgentMentionProviderQueryDiagnostic[];
providerTimeoutMs: number;
queryLength: number;
requestId: number;
status: "ready" | "error";
workspaceId: string;
}
const AGENT_MENTION_SEARCH_LOG_PREFIX = "[agent-gui] mention-search";
const {
agentGeneratedFile: AGENT_GENERATED_FILE_PROVIDER_ID,
agentSession: AGENT_SESSION_PROVIDER_ID,
file: FILE_PROVIDER_ID,
workspaceApp: WORKSPACE_APP_PROVIDER_ID,
workspaceIssue: WORKSPACE_ISSUE_PROVIDER_ID
} = AGENT_CONTEXT_MENTION_PROVIDER_IDS;
const AGENT_MENTION_PROVIDER_LOG_ORDER: readonly string[] = [
FILE_PROVIDER_ID,
AGENT_GENERATED_FILE_PROVIDER_ID,
WORKSPACE_APP_PROVIDER_ID,
WORKSPACE_ISSUE_PROVIDER_ID,
AGENT_SESSION_PROVIDER_ID
];
export function emitAgentMentionSearchDiagnostic(input: {
debounceMs: number;
diagnosticInfoLogger: (payload: AgentMentionSearchDiagnosticLog) => void;
diagnosticNow: () => number;
diagnosticSlowThresholdMs: number;
error?: unknown;
filter: AgentMentionFilterId;
groups: readonly AgentMentionGroup[];
mode: "browse" | "results";
providerDiagnostics: readonly AgentMentionProviderQueryDiagnostic[];
providerTimeoutMs: number;
query: string;
requestId: number;
startedAt: number;
status: "ready" | "error";
workspaceId: string;
}): void {
const durationMs = elapsedAgentMentionSearchDiagnosticMs(
input.diagnosticNow(),
input.startedAt
);
const providerResults = orderAgentMentionProviderDiagnostics(
input.providerDiagnostics
);
const hasProviderIssue = providerResults.some(
(result) => result.status === "timeout" || result.status === "error"
);
if (
input.status === "ready" &&
!hasProviderIssue &&
durationMs < input.diagnosticSlowThresholdMs
) {
return;
}
const payload: AgentMentionSearchDiagnosticLog = {
debounceMs: input.debounceMs,
durationMs,
...(input.status === "error"
? { errorKind: agentMentionSearchErrorKind(input.error) }
: {}),
event: "agent_gui.mention_search",
filter: input.filter,
groupCount: input.groups.length,
itemCount: input.groups.reduce((sum, group) => sum + group.items.length, 0),
mode: input.mode,
providerResults,
providerTimeoutMs: input.providerTimeoutMs,
queryLength: [...input.query].length,
requestId: input.requestId,
status: input.status,
workspaceId: input.workspaceId
};
try {
input.diagnosticInfoLogger(payload);
} catch {
// Diagnostic logging must never affect mention search state.
}
}
export async function queryAgentMentionProviderWithDiagnostics<T>(input: {
abortSignal?: AbortSignal;
diagnosticNow: () => number;
diagnostics: AgentMentionProviderQueryDiagnostic[];
fallback: T;
providerId: string;
providerTimeoutMs: number;
query: ((abortSignal: AbortSignal) => Promise<T>) | null;
resultCount: (result: T) => number;
throwOnTimeout?: boolean;
}): Promise<T> {
if (!input.query) {
input.diagnostics.push({
durationMs: 0,
providerId: input.providerId,
resultCount: 0,
status: "missing"
});
return input.fallback;
}
const startedAt = input.diagnosticNow();
try {
const { result, timedOut } = await runAgentMentionProviderQuery({
abortSignal: input.abortSignal,
fallback: input.fallback,
providerTimeoutMs: input.providerTimeoutMs,
query: input.query
});
input.diagnostics.push({
durationMs: elapsedAgentMentionSearchDiagnosticMs(
input.diagnosticNow(),
startedAt
),
providerId: input.providerId,
resultCount: input.resultCount(result),
status: timedOut ? "timeout" : "success"
});
if (timedOut && input.throwOnTimeout) {
const error = new Error("Mention provider query timed out.");
error.name = "TimeoutError";
throw error;
}
return result;
} catch (error) {
if (
error instanceof Error &&
error.name === "TimeoutError" &&
input.diagnostics.at(-1)?.providerId === input.providerId &&
input.diagnostics.at(-1)?.status === "timeout"
) {
throw error;
}
input.diagnostics.push({
durationMs: elapsedAgentMentionSearchDiagnosticMs(
input.diagnosticNow(),
startedAt
),
errorKind: agentMentionSearchErrorKind(error),
providerId: input.providerId,
resultCount: 0,
status: "error"
});
throw error;
}
}
function elapsedAgentMentionSearchDiagnosticMs(
now: number,
startedAt: number
): number {
const durationMs = now - startedAt;
if (!Number.isFinite(durationMs)) {
return 0;
}
return Math.max(0, Math.round(durationMs));
}
function agentMentionSearchErrorKind(error: unknown): string {
if (error instanceof Error && error.name.trim()) {
return error.name.trim();
}
if (error === null) {
return "null";
}
return typeof error;
}
export function logAgentMentionSearchInfo(
payload: AgentMentionSearchDiagnosticLog
): void {
console.info(AGENT_MENTION_SEARCH_LOG_PREFIX, JSON.stringify(payload));
}
function orderAgentMentionProviderDiagnostics(
diagnostics: readonly AgentMentionProviderQueryDiagnostic[]
): AgentMentionProviderQueryDiagnostic[] {
return [...diagnostics].sort((left, right) => {
const leftIndex = providerDiagnosticOrder(left.providerId);
const rightIndex = providerDiagnosticOrder(right.providerId);
return (
leftIndex - rightIndex || left.providerId.localeCompare(right.providerId)
);
});
}
function providerDiagnosticOrder(providerId: string): number {
const index = AGENT_MENTION_PROVIDER_LOG_ORDER.indexOf(providerId);
return index === -1 ? AGENT_MENTION_PROVIDER_LOG_ORDER.length : index;
}
async function runAgentMentionProviderQuery<T>(input: {
abortSignal?: AbortSignal;
fallback: T;
providerTimeoutMs: number;
query: (abortSignal: AbortSignal) => Promise<T>;
}): Promise<{ result: T; timedOut: boolean }> {
const abortController = new AbortController();
const abortFromParent = () => abortController.abort();
if (input.abortSignal?.aborted) {
abortController.abort();
} else {
input.abortSignal?.addEventListener("abort", abortFromParent, {
once: true
});
}
let timedOut = false;
const queryPromise = Promise.resolve().then(() =>
input.query(abortController.signal)
);
const queryResultPromise = queryPromise.then(
(result) => ({ result, timedOut: false }),
(error) => {
if (timedOut && abortController.signal.aborted) {
return { result: input.fallback, timedOut: true };
}
throw error;
}
);
if (
input.providerTimeoutMs <= 0 ||
!Number.isFinite(input.providerTimeoutMs)
) {
try {
return await queryResultPromise;
} finally {
input.abortSignal?.removeEventListener("abort", abortFromParent);
}
}
let timeout: ReturnType<typeof setTimeout> | null = null;
const timeoutPromise = new Promise<{ result: T; timedOut: boolean }>(
(resolve) => {
timeout = setTimeout(() => {
timedOut = true;
abortController.abort();
resolve({ result: input.fallback, timedOut: true });
}, input.providerTimeoutMs);
}
);
try {
return await Promise.race([queryResultPromise, timeoutPromise]);
} finally {
input.abortSignal?.removeEventListener("abort", abortFromParent);
if (timeout !== null) {
clearTimeout(timeout);
}
}
}