Merge pull request #1299 from pikasTech/fix-1286-error-observability
fix: improve code agent failure diagnostics
This commit is contained in:
@@ -7,39 +7,58 @@ import { firstNonEmptyString } from "@/utils";
|
||||
const props = defineProps<{ message: ChatMessage }>();
|
||||
|
||||
const trace = computed(() => props.message.runnerTrace ?? null);
|
||||
const agentRun = computed(() => asAgentRun(trace.value?.agentRun));
|
||||
const error = computed(() => props.message.error ?? null);
|
||||
const traceRecord = computed(() => recordValue(trace.value));
|
||||
const agentRun = computed(() => asAgentRun(traceRecord.value?.agentRun ?? (props.message as Record<string, unknown>).agentRun));
|
||||
const error = computed(() => normalizeSummaryError(props.message.error ?? traceRecord.value?.error));
|
||||
const eventCount = computed(() => trace.value?.eventCount ?? trace.value?.events?.length ?? 0);
|
||||
|
||||
const errorTitle = computed(() => firstNonEmptyString(error.value?.code, error.value?.category, "失败原因"));
|
||||
const errorMessage = computed(() => firstNonEmptyString(error.value?.message, error.value?.userMessage, error.value?.reason));
|
||||
|
||||
const rows = computed(() => [
|
||||
{ key: "messageStatus", label: "消息状态", value: props.message.status },
|
||||
{ key: "traceStatus", label: "Trace 状态", value: firstNonEmptyString(trace.value?.status, trace.value?.traceStatus) },
|
||||
{ key: "traceId", label: "Trace", value: props.message.traceId ?? trace.value?.traceId },
|
||||
{ key: "conversationId", label: "Conversation", value: props.message.conversationId },
|
||||
{ key: "sessionId", label: "Session", value: props.message.sessionId ?? trace.value?.sessionId },
|
||||
{ key: "threadId", label: "Thread", value: props.message.threadId ?? trace.value?.threadId },
|
||||
{ key: "runnerKind", label: "Runner", value: trace.value?.runnerKind ?? agentRun.value?.adapter },
|
||||
{ key: "sessionMode", label: "Session Mode", value: trace.value?.sessionMode ?? agentRun.value?.backendProfile },
|
||||
{ key: "waitingFor", label: "Waiting For", value: trace.value?.waitingFor },
|
||||
{ key: "lastEvent", label: "Last Event", value: trace.value?.lastEventLabel },
|
||||
{ key: "eventCount", label: "Events", value: String(eventCount.value) },
|
||||
{ key: "eventsCompacted", label: "Compacted", value: trace.value?.eventsCompacted === true ? "true" : "false" },
|
||||
{ key: "fullTraceLoaded", label: "Full Trace", value: trace.value?.fullTraceLoaded === true ? "loaded" : "unverified" },
|
||||
{ key: "runId", label: "AgentRun", value: agentRun.value?.runId },
|
||||
{ key: "commandId", label: "Command", value: agentRun.value?.commandId },
|
||||
{ key: "attemptId", label: "Attempt", value: agentRun.value?.attemptId },
|
||||
{ key: "runnerId", label: "Runner ID", value: agentRun.value?.runnerId },
|
||||
{ key: "jobName", label: "Job", value: agentRun.value?.jobName },
|
||||
{ key: "namespace", label: "Namespace", value: agentRun.value?.namespace },
|
||||
{ key: "terminalStatus", label: "Terminal", value: agentRun.value?.terminalStatus },
|
||||
{ key: "errorCode", label: "Error Code", value: error.value?.code },
|
||||
{ key: "errorCategory", label: "Error Category", value: error.value?.category },
|
||||
{ key: "providerStatus", label: "Provider Status", value: error.value?.providerStatus == null ? null : String(error.value.providerStatus) }
|
||||
].map((row) => ({ ...row, value: row.value == null || row.value === "" ? "未观测" : String(row.value) })));
|
||||
summaryRow("messageStatus", "消息状态", props.message.status),
|
||||
summaryRow("traceStatus", "Trace 状态", firstNonEmptyString(trace.value?.status, trace.value?.traceStatus)),
|
||||
summaryRow("traceId", "Trace", props.message.traceId ?? trace.value?.traceId),
|
||||
summaryRow("conversationId", "Conversation", props.message.conversationId),
|
||||
summaryRow("sessionId", "Session", props.message.sessionId ?? trace.value?.sessionId),
|
||||
summaryRow("threadId", "Thread", props.message.threadId ?? trace.value?.threadId),
|
||||
summaryRow("runnerKind", "Runner", trace.value?.runnerKind ?? agentRun.value?.adapter),
|
||||
summaryRow("sessionMode", "Session Mode", trace.value?.sessionMode ?? agentRun.value?.backendProfile),
|
||||
summaryRow("waitingFor", "Waiting For", trace.value?.waitingFor),
|
||||
summaryRow("lastEvent", "Last Event", trace.value?.lastEventLabel),
|
||||
summaryRow("eventCount", "Events", eventCount.value > 0 ? eventCount.value : null),
|
||||
summaryRow("eventsCompacted", "Compacted", trace.value?.eventsCompacted === true ? "true" : null),
|
||||
summaryRow("fullTraceLoaded", "Full Trace", trace.value?.fullTraceLoaded === true ? "loaded" : null),
|
||||
summaryRow("runId", "AgentRun", agentRun.value?.runId),
|
||||
summaryRow("commandId", "Command", agentRun.value?.commandId),
|
||||
summaryRow("attemptId", "Attempt", agentRun.value?.attemptId),
|
||||
summaryRow("runnerId", "Runner ID", agentRun.value?.runnerId),
|
||||
summaryRow("jobName", "Job", agentRun.value?.jobName),
|
||||
summaryRow("namespace", "Namespace", agentRun.value?.namespace),
|
||||
summaryRow("terminalStatus", "Terminal", agentRun.value?.terminalStatus),
|
||||
summaryRow("errorCode", "Error Code", error.value?.code),
|
||||
summaryRow("errorCategory", "Error Category", error.value?.category),
|
||||
summaryRow("providerStatus", "Provider Status", error.value?.providerStatus)
|
||||
].filter((row): row is { key: string; label: string; value: string } => Boolean(row)));
|
||||
|
||||
function asAgentRun(value: unknown): AgentRunProvenance | null {
|
||||
return value && typeof value === "object" ? value as AgentRunProvenance : null;
|
||||
}
|
||||
|
||||
function recordValue(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function summaryRow(key: string, label: string, value: unknown): { key: string; label: string; value: string } | null {
|
||||
const text = value == null ? "" : String(value).trim();
|
||||
return text ? { key, label, value: text } : null;
|
||||
}
|
||||
|
||||
function normalizeSummaryError(value: unknown): Record<string, unknown> | null {
|
||||
if (!value) return null;
|
||||
if (typeof value === "string") return value.trim() ? { message: value.trim() } : null;
|
||||
return value && typeof value === "object" ? value as Record<string, unknown> : null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,7 +67,11 @@ function asAgentRun(value: unknown): AgentRunProvenance | null {
|
||||
<span>Code Agent 状态摘要</span>
|
||||
<StatusBadge :status="message.status" />
|
||||
</summary>
|
||||
<dl>
|
||||
<div v-if="error" class="code-agent-error-summary">
|
||||
<strong>{{ errorTitle }}</strong>
|
||||
<p v-if="errorMessage">{{ errorMessage }}</p>
|
||||
</div>
|
||||
<dl v-if="rows.length > 0">
|
||||
<div v-for="row in rows" :key="row.key" class="code-agent-status-row" :data-status-row="row.key">
|
||||
<dt>{{ row.label }}</dt>
|
||||
<dd>{{ row.value }}</dd>
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface TraceSnapshot {
|
||||
terminalEvidence?: unknown;
|
||||
finalResponse?: unknown;
|
||||
traceSummary?: unknown;
|
||||
error?: AgentChatResultResponse["error"];
|
||||
lastEventLabel?: string;
|
||||
waitingFor?: string;
|
||||
eventSource?: string;
|
||||
@@ -62,6 +63,7 @@ export function snapshotToRunnerTrace(snapshot: TraceSnapshot): NonNullable<Chat
|
||||
finalResponse: snapshot.finalResponse,
|
||||
traceSummary: snapshot.traceSummary,
|
||||
agentRun: snapshot.agentRun,
|
||||
error: snapshot.error,
|
||||
eventSource: snapshot.eventSource ?? "trace-api",
|
||||
runnerKind: snapshot.agentRun?.adapter,
|
||||
sessionMode: snapshot.agentRun?.backendProfile,
|
||||
@@ -108,30 +110,41 @@ function traceEventIdentity(event: TraceEvent): string | null {
|
||||
|
||||
export function mergeTraceResults(terminal: AgentChatResultResponse, trace: TraceSnapshot | null): AgentChatResultResponse {
|
||||
if (!trace) return terminal;
|
||||
const mergedTrace: TraceSnapshot = {
|
||||
...trace,
|
||||
sessionId: trace.sessionId ?? terminal.sessionId,
|
||||
threadId: trace.threadId ?? terminal.threadId,
|
||||
agentRun: trace.agentRun ?? terminal.agentRun,
|
||||
terminalEvidence: trace.terminalEvidence ?? terminal.terminalEvidence,
|
||||
finalResponse: trace.finalResponse ?? terminal.finalResponse,
|
||||
traceSummary: trace.traceSummary ?? terminal.traceSummary,
|
||||
error: trace.error ?? terminal.error
|
||||
};
|
||||
const events = Array.isArray(trace.events) ? trace.events : [];
|
||||
const assistantText = bestAssistantTextCandidate([
|
||||
finalResponseText(trace.finalResponse),
|
||||
finalResponseText(mergedTrace.finalResponse),
|
||||
terminalAssistantTextFromTraceEvents(events),
|
||||
firstNonEmptyResultText(terminal),
|
||||
assistantTextFromTraceEvents(events)
|
||||
]);
|
||||
return {
|
||||
...terminal,
|
||||
traceId: trace.traceId ?? terminal.traceId,
|
||||
sessionId: trace.sessionId ?? terminal.sessionId,
|
||||
threadId: trace.threadId ?? terminal.threadId,
|
||||
agentRun: trace.agentRun ?? terminal.agentRun,
|
||||
traceId: mergedTrace.traceId ?? terminal.traceId,
|
||||
sessionId: mergedTrace.sessionId ?? terminal.sessionId,
|
||||
threadId: mergedTrace.threadId ?? terminal.threadId,
|
||||
agentRun: mergedTrace.agentRun ?? terminal.agentRun,
|
||||
error: mergedTrace.error ?? terminal.error,
|
||||
assistantText: assistantText ?? terminal.assistantText,
|
||||
runnerTrace: snapshotToRunnerTrace(trace),
|
||||
runnerTrace: snapshotToRunnerTrace(mergedTrace),
|
||||
traceEvents: terminal.traceEvents ?? events,
|
||||
events,
|
||||
eventCount: trace.eventCount ?? events.length,
|
||||
traceStatus: trace.traceStatus ?? terminal.traceStatus,
|
||||
retention: trace.retention ?? terminal.retention,
|
||||
terminalEvidence: trace.terminalEvidence ?? terminal.terminalEvidence,
|
||||
finalResponse: trace.finalResponse ?? terminal.finalResponse,
|
||||
traceSummary: trace.traceSummary ?? terminal.traceSummary,
|
||||
lastEventLabel: trace.lastEventLabel ?? terminal.lastEventLabel ?? events.at(-1)?.label
|
||||
eventCount: mergedTrace.eventCount ?? events.length,
|
||||
traceStatus: mergedTrace.traceStatus ?? terminal.traceStatus,
|
||||
retention: mergedTrace.retention ?? terminal.retention,
|
||||
terminalEvidence: mergedTrace.terminalEvidence ?? terminal.terminalEvidence,
|
||||
finalResponse: mergedTrace.finalResponse ?? terminal.finalResponse,
|
||||
traceSummary: mergedTrace.traceSummary ?? terminal.traceSummary,
|
||||
lastEventLabel: mergedTrace.lastEventLabel ?? terminal.lastEventLabel ?? events.at(-1)?.label
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,6 +240,7 @@ function resultToTraceSnapshot(traceId: string, result: AgentChatResultResponse)
|
||||
terminalEvidence: result.terminalEvidence,
|
||||
finalResponse: result.finalResponse,
|
||||
traceSummary: result.traceSummary,
|
||||
error: result.error,
|
||||
lastEventLabel: result.lastEventLabel ?? lastEvent?.label ?? lastEvent?.type,
|
||||
eventSource: "trace-api",
|
||||
updatedAt: new Date().toISOString()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { api } from "@/api";
|
||||
import { mergeRunnerTrace, snapshotToRunnerTrace, subscribeToTrace, type TraceSnapshot } from "@/composables/useTraceSubscription";
|
||||
import type { AgentChatResponse, AgentChatResultResponse, ApiResult, ChatMessage, ConversationRecord, LiveSurface, ProviderProfile, WorkspaceRecord } from "@/types";
|
||||
import type { AgentChatResponse, AgentChatResultResponse, AgentRunProvenance, ApiResult, ChatMessage, ConversationRecord, LiveSurface, ProviderProfile, TraceEvent, WorkspaceRecord } from "@/types";
|
||||
import { DEFAULT_WORKBENCH_PROJECT_ID, firstNonEmptyString, nextProtocolId, rememberWorkbenchProjectId, resolveInitialWorkbenchProjectId, workspaceProjectId } from "@/utils";
|
||||
import { RECENT_DRAFTS_STORAGE_KEY, activeTraceIdFromWorkspace, defaultProviderProfileOptions, normalizeRecentDrafts, providerProfileOptionsFromPayload, recordRecentDraft, resolveComposerState, sortSessionTabs, workspaceWithClearedActiveTrace, type DraftEntry, type ProviderProfileOption } from "./workbench-session";
|
||||
|
||||
@@ -55,6 +55,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
rememberWorkbenchProjectId(workspaceProjectId(workspace.value, projectId.value));
|
||||
conversations.value = conversationsResult.ok ? conversationsResult.data?.conversations ?? [] : [];
|
||||
messages.value = messagesFromWorkspace(workspace.value);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
await refreshProviderOptions();
|
||||
const activeTraceId = activeTraceIdFromWorkspace(workspace.value);
|
||||
if (activeTraceId) void validateAndReattachTrace(activeTraceId);
|
||||
@@ -103,6 +104,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
if (response.ok) {
|
||||
workspace.value = response.data?.workspace ?? current;
|
||||
messages.value = messagesFromWorkspace(workspace.value);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
currentRequest.value = null;
|
||||
} else {
|
||||
workspace.value = previousWorkspace;
|
||||
@@ -288,14 +290,37 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const text = firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), typeof result.reply === "string" ? result.reply : result.reply?.content, result.text, result.summary) ?? "Code Agent 已完成,但没有返回可展示的 final response。";
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId || message.role !== "agent") return message;
|
||||
const runnerTrace = result.runnerTrace ? mergeRunnerTrace(message.runnerTrace, result.runnerTrace) : message.runnerTrace ?? null;
|
||||
return { ...message, status: result.status === "completed" ? "completed" : statusFromResult(result.status), text, runnerTrace, updatedAt: new Date().toISOString() };
|
||||
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
||||
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
||||
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
||||
return { ...message, status: result.status === "completed" ? "completed" : statusFromResult(result.status), text, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
||||
});
|
||||
chatPending.value = false;
|
||||
currentRequest.value = null;
|
||||
void clearActiveTrace(traceId, "trace-terminal");
|
||||
}
|
||||
|
||||
async function hydrateTerminalMessageDiagnostics(): Promise<void> {
|
||||
const targets = messages.value.filter(messageNeedsTerminalDiagnostics).slice(-6);
|
||||
await Promise.all(targets.map(async (message) => {
|
||||
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
|
||||
if (!traceId) return;
|
||||
const result = await api.agent.getAgentChatResult(`/v1/agent/chat/result/${encodeURIComponent(traceId)}?projectId=${encodeURIComponent(activeProjectId.value)}`, 8000, () => activityRef.value);
|
||||
if (!result.ok || !result.data) return;
|
||||
applyTerminalResultDiagnostics(traceId, result.data);
|
||||
}));
|
||||
}
|
||||
|
||||
function applyTerminalResultDiagnostics(traceId: string, result: AgentChatResultResponse): void {
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId || message.role !== "agent") return message;
|
||||
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
||||
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
||||
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
||||
return { ...message, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
||||
});
|
||||
}
|
||||
|
||||
function failTrace(traceId: string, message: string): void {
|
||||
markMessage(traceId, { status: "failed", text: message });
|
||||
chatPending.value = false;
|
||||
@@ -364,7 +389,120 @@ function messagesFromWorkspace(workspace: WorkspaceRecord | null): ChatMessage[]
|
||||
|
||||
function normalizeChatMessage(message: ChatMessage): ChatMessage {
|
||||
const text = firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), messageText((message as Record<string, unknown>).message), finalResponseText((message as Record<string, unknown>).finalResponse)) ?? "";
|
||||
return { ...message, text, id: message.id ?? nextProtocolId("msg"), title: message.title ?? (message.role === "user" ? "用户" : "Code Agent"), createdAt: message.createdAt ?? new Date().toISOString(), status: message.status ?? "source" };
|
||||
const runnerTrace = normalizeMessageRunnerTrace(message);
|
||||
const error = normalizeAgentError(message.error ?? runnerTrace?.error);
|
||||
const agentRun = agentRunFromMessage(message) ?? asAgentRun(runnerTrace?.agentRun);
|
||||
return { ...message, text, id: message.id ?? nextProtocolId("msg"), title: message.title ?? (message.role === "user" ? "用户" : "Code Agent"), createdAt: message.createdAt ?? new Date().toISOString(), status: message.status ?? "source", runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined };
|
||||
}
|
||||
|
||||
function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result: AgentChatResultResponse): NonNullable<ChatMessage["runnerTrace"]> {
|
||||
const resultTrace = recordValue(result.runnerTrace);
|
||||
const events = firstArray(result.events, result.traceEvents, resultTrace?.events, previous?.events);
|
||||
const agentRun = asAgentRun(result.agentRun ?? resultTrace?.agentRun ?? previous?.agentRun);
|
||||
return mergeRunnerTrace(previous, {
|
||||
...resultTrace,
|
||||
traceId: firstNonEmptyString(result.traceId, resultTrace?.traceId, previous?.traceId) ?? undefined,
|
||||
status: firstNonEmptyString(result.traceStatus, resultTrace?.status, result.status, previous?.status) ?? undefined,
|
||||
sessionId: firstNonEmptyString(result.sessionId, resultTrace?.sessionId, previous?.sessionId) ?? undefined,
|
||||
threadId: firstNonEmptyString(result.threadId, resultTrace?.threadId, previous?.threadId) ?? undefined,
|
||||
events,
|
||||
eventCount: firstFiniteNumber(result.eventCount, resultTrace?.eventCount, previous?.eventCount, events.length),
|
||||
eventsCompacted: firstBoolean(resultTrace?.eventsCompacted, previous?.eventsCompacted),
|
||||
traceStatus: firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus, previous?.traceStatus) ?? undefined,
|
||||
retention: result.retention ?? resultTrace?.retention ?? previous?.retention,
|
||||
terminalEvidence: result.terminalEvidence ?? resultTrace?.terminalEvidence ?? previous?.terminalEvidence,
|
||||
finalResponse: result.finalResponse ?? resultTrace?.finalResponse ?? previous?.finalResponse,
|
||||
traceSummary: result.traceSummary ?? resultTrace?.traceSummary ?? previous?.traceSummary,
|
||||
agentRun: agentRun ?? undefined,
|
||||
error: normalizeAgentError(result.error ?? resultTrace?.error ?? previous?.error) ?? undefined,
|
||||
runnerKind: firstNonEmptyString(agentRun?.adapter, resultTrace?.runnerKind, previous?.runnerKind) ?? undefined,
|
||||
sessionMode: firstNonEmptyString(agentRun?.backendProfile, resultTrace?.sessionMode, previous?.sessionMode) ?? undefined,
|
||||
lastEventLabel: firstNonEmptyString(result.lastEventLabel, resultTrace?.lastEventLabel, previous?.lastEventLabel, events.at(-1)?.label, events.at(-1)?.type) ?? undefined,
|
||||
updatedAt: new Date().toISOString()
|
||||
} as NonNullable<ChatMessage["runnerTrace"]>);
|
||||
}
|
||||
|
||||
function normalizeMessageRunnerTrace(message: ChatMessage): ChatMessage["runnerTrace"] {
|
||||
const trace = recordValue(message.runnerTrace);
|
||||
const agentRun = agentRunFromMessage(message);
|
||||
const error = normalizeAgentError(message.error ?? trace?.error);
|
||||
if (!trace && !agentRun && !error) return message.runnerTrace ?? null;
|
||||
return {
|
||||
...(trace ?? {}),
|
||||
traceId: message.traceId ?? trace?.traceId,
|
||||
sessionId: message.sessionId ?? trace?.sessionId,
|
||||
threadId: message.threadId ?? trace?.threadId,
|
||||
agentRun: agentRun ?? trace?.agentRun,
|
||||
error: error ?? trace?.error,
|
||||
runnerKind: agentRun?.adapter ?? trace?.runnerKind,
|
||||
sessionMode: agentRun?.backendProfile ?? trace?.sessionMode
|
||||
} as ChatMessage["runnerTrace"];
|
||||
}
|
||||
|
||||
function messageNeedsTerminalDiagnostics(message: ChatMessage): boolean {
|
||||
if (message.role !== "agent") return false;
|
||||
if (!isTerminalMessageStatus(message.status)) return false;
|
||||
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
|
||||
if (!traceId) return false;
|
||||
const agentRun = agentRunFromMessage(message);
|
||||
const error = normalizeAgentError(message.error ?? message.runnerTrace?.error);
|
||||
return !agentRun || (message.status !== "completed" && !error);
|
||||
}
|
||||
|
||||
function agentRunFromResult(result: AgentChatResultResponse, runnerTrace: ChatMessage["runnerTrace"]): AgentRunProvenance | null {
|
||||
return asAgentRun(result.agentRun ?? runnerTrace?.agentRun);
|
||||
}
|
||||
|
||||
function agentRunFromMessage(message: ChatMessage): AgentRunProvenance | null {
|
||||
return asAgentRun((message as Record<string, unknown>).agentRun ?? message.runnerTrace?.agentRun);
|
||||
}
|
||||
|
||||
function asAgentRun(value: unknown): AgentRunProvenance | null {
|
||||
return value && typeof value === "object" ? value as AgentRunProvenance : null;
|
||||
}
|
||||
|
||||
function normalizeAgentError(value: unknown): ChatMessage["error"] | null {
|
||||
if (!value) return null;
|
||||
if (typeof value === "string") return value.trim() ? { message: value.trim() } : null;
|
||||
if (value && typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
const message = firstNonEmptyString(record.message, record.userMessage, record.reason, record.detail, record.error);
|
||||
const code = firstNonEmptyString(record.code, record.failureKind, record.name);
|
||||
const category = firstNonEmptyString(record.category, record.layer, record.type);
|
||||
const providerStatus = typeof record.providerStatus === "number" ? record.providerStatus : Number.isFinite(Number(record.providerStatus)) ? Number(record.providerStatus) : undefined;
|
||||
return { ...record, code: code ?? undefined, message: message ?? undefined, category: category ?? undefined, providerStatus };
|
||||
}
|
||||
return { message: String(value) };
|
||||
}
|
||||
|
||||
function isTerminalMessageStatus(value: unknown): boolean {
|
||||
return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
|
||||
}
|
||||
|
||||
function recordValue(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function firstArray(...values: unknown[]): TraceEvent[] {
|
||||
for (const value of values) {
|
||||
if (Array.isArray(value)) return value as TraceEvent[];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function firstFiniteNumber(...values: unknown[]): number | undefined {
|
||||
for (const value of values) {
|
||||
const number = typeof value === "number" ? value : Number(value);
|
||||
if (Number.isFinite(number)) return number;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function firstBoolean(...values: unknown[]): boolean | undefined {
|
||||
for (const value of values) {
|
||||
if (typeof value === "boolean") return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function finalResponseText(value: unknown): string | null {
|
||||
|
||||
@@ -1230,6 +1230,28 @@
|
||||
border-top: 1px solid #d8e1eb;
|
||||
}
|
||||
|
||||
.code-agent-error-summary {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
border-top: 1px solid #d8e1eb;
|
||||
background: #fff7ed;
|
||||
padding: 9px 10px;
|
||||
color: #7c2d12;
|
||||
}
|
||||
|
||||
.code-agent-error-summary strong {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.code-agent-error-summary p {
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #7f1d1d;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.code-agent-status-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(110px, 0.8fr) minmax(0, 1.2fr);
|
||||
|
||||
Reference in New Issue
Block a user