niceeval 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +1 -0
- package/README.md +4 -2
- package/README.zh.md +4 -2
- package/dist/agents/types.d.ts +2 -2
- package/dist/o11y/types.d.ts +3 -3
- package/dist/report/assets/colors.d.ts +1 -1
- package/dist/report/assets/colors.js +1 -1
- package/dist/report/components/attempt-detail/AttemptSource.js +1 -2
- package/dist/report/components/attempt-detail/compute.d.ts +4 -4
- package/dist/report/components/attempt-detail/compute.js +6 -6
- package/dist/report/components/attempt-detail/faces.d.ts +1 -1
- package/dist/report/components/attempt-detail/faces.js +3 -3
- package/dist/report/components/entity-lists/AttemptList.d.ts +3 -3
- package/dist/report/components/entity-lists/AttemptList.js +3 -3
- package/dist/report/components/entity-lists/ExperimentList.js +2 -2
- package/dist/report/components/entity-lists/compute.js +1 -1
- package/dist/report/components/entity-lists/faces.js +1 -1
- package/dist/report/components/entity-lists/index.d.ts +1 -1
- package/dist/report/components/entity-lists/index.js +1 -1
- package/dist/report/components/metric-views/chart-math.d.ts +2 -2
- package/dist/report/components/metric-views/chart-math.js +2 -2
- package/dist/report/components/metric-views/compute.d.ts +2 -3
- package/dist/report/components/metric-views/compute.js +3 -4
- package/dist/report/components/shared.d.ts +1 -1
- package/dist/report/components/site-components/compute.d.ts +5 -5
- package/dist/report/components/site-components/compute.js +6 -6
- package/dist/report/components/site-components/index.d.ts +7 -7
- package/dist/report/components/site-components/index.js +7 -7
- package/dist/report/components/site-components/scope-warnings.d.ts +1 -1
- package/dist/report/components/site-components/scope-warnings.js +3 -3
- package/dist/report/components/site-components/snapshot-diagnostics.js +1 -1
- package/dist/report/components/summaries/compute.d.ts +1 -1
- package/dist/report/components/summaries/compute.js +2 -2
- package/dist/report/components/summaries/faces.js +1 -1
- package/dist/report/components/summaries/index.d.ts +1 -1
- package/dist/report/components/summaries/index.js +1 -1
- package/dist/report/index.js +1 -1
- package/dist/report/model/aggregate.d.ts +2 -2
- package/dist/report/model/aggregate.js +2 -2
- package/dist/report/model/format.d.ts +2 -2
- package/dist/report/model/format.js +2 -2
- package/dist/report/model/metrics.js +3 -3
- package/dist/report/model/types.d.ts +11 -12
- package/dist/results/locator.js +1 -1
- package/dist/results/select.d.ts +4 -4
- package/dist/results/select.js +5 -5
- package/dist/results/types.d.ts +8 -8
- package/dist/runner/types.d.ts +14 -14
- package/dist/sandbox/resolve.d.ts +1 -1
- package/dist/sandbox/types.d.ts +1 -1
- package/docs-site/zh/reference/builtin-agents.mdx +21 -0
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/report-components.mdx +156 -74
- package/docs-site/zh/tutorials/custom-reports.mdx +112 -22
- package/docs-site/zh/tutorials/sandbox-agent.mdx +3 -4
- package/docs-site/zh/tutorials/sandbox-providers.mdx +15 -15
- package/docs-site/zh/tutorials/theming.mdx +169 -0
- package/package.json +2 -1
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/bub-install-spec.ts +26 -8
- package/src/agents/bub.ts +40 -15
- package/src/agents/builtin.ts +11 -1
- package/src/agents/coding-cli-versions.ts +67 -0
- package/src/agents/hermes.ts +235 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/langgraph.test.ts +1 -1
- package/src/agents/manifest.ts +1 -1
- package/src/agents/openai-compat.test.ts +1 -1
- package/src/agents/openclaw.ts +103 -24
- package/src/agents/opencode.ts +183 -0
- package/src/agents/sdk-streams.test.ts +1 -1
- package/src/agents/shared.ts +13 -2
- package/src/agents/types.ts +2 -2
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/o11y/cost.ts +1 -1
- package/src/o11y/parsers/bub.test.ts +1 -1
- package/src/o11y/parsers/bub.ts +1 -1
- package/src/o11y/parsers/codex.test.ts +1 -1
- package/src/o11y/parsers/codex.ts +1 -1
- package/src/o11y/parsers/hermes.ts +198 -0
- package/src/o11y/parsers/openclaw.ts +25 -3
- package/src/o11y/parsers/opencode.ts +295 -0
- package/src/o11y/types.ts +3 -3
- package/src/report/assets/colors.ts +1 -1
- package/src/report/assets/styles.css +17 -15
- package/src/report/components/attempt-detail/AttemptAssertions.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptConversation.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptDiff.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptError.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptSource.tsx +2 -3
- package/src/report/components/attempt-detail/AttemptSummary.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptTimeline.tsx +1 -1
- package/src/report/components/attempt-detail/AttemptTrace.tsx +1 -1
- package/src/report/components/attempt-detail/UsageTable.tsx +1 -1
- package/src/report/components/attempt-detail/attempt-components.test.tsx +1 -1
- package/src/report/components/attempt-detail/compute.ts +6 -6
- package/src/report/components/attempt-detail/faces.ts +3 -3
- package/src/report/components/attempt-detail/index.tsx +1 -1
- package/src/report/components/entity-lists/AttemptList.tsx +3 -3
- package/src/report/components/entity-lists/ExperimentList.tsx +2 -2
- package/src/report/components/entity-lists/compute.ts +1 -1
- package/src/report/components/entity-lists/faces.ts +1 -1
- package/src/report/components/entity-lists/index.tsx +1 -1
- package/src/report/components/metric-views/DeltaTable.tsx +1 -2
- package/src/report/components/metric-views/StabilityMatrix.tsx +1 -1
- package/src/report/components/metric-views/chart-math.test.ts +1 -1
- package/src/report/components/metric-views/chart-math.ts +2 -2
- package/src/report/components/metric-views/compute.ts +3 -4
- package/src/report/components/shared.ts +1 -1
- package/src/report/components/site-components/CopyFixPrompt.tsx +1 -1
- package/src/report/components/site-components/HeroCard.tsx +1 -1
- package/src/report/components/site-components/PoweredBy.tsx +1 -1
- package/src/report/components/site-components/ScopeWarnings.tsx +1 -1
- package/src/report/components/site-components/SnapshotDiagnostics.tsx +1 -1
- package/src/report/components/site-components/TraceWaterfall.tsx +1 -1
- package/src/report/components/site-components/compute.ts +6 -6
- package/src/report/components/site-components/index.tsx +7 -7
- package/src/report/components/site-components/scope-warnings.ts +3 -3
- package/src/report/components/site-components/snapshot-diagnostics.ts +1 -1
- package/src/report/components/summaries/ScopeSummary.tsx +2 -2
- package/src/report/components/summaries/compute.ts +2 -2
- package/src/report/components/summaries/faces.ts +1 -1
- package/src/report/components/summaries/index.tsx +1 -1
- package/src/report/index.ts +1 -1
- package/src/report/model/aggregate.ts +2 -2
- package/src/report/model/format.ts +2 -2
- package/src/report/model/metrics.ts +3 -3
- package/src/report/model/types.ts +12 -13
- package/src/results/annotated-source.test.ts +1 -1
- package/src/results/attempt-evidence.test.ts +1 -1
- package/src/results/copy.ts +3 -3
- package/src/results/format.ts +3 -3
- package/src/results/host-equivalence.test.ts +6 -6
- package/src/results/index.ts +1 -1
- package/src/results/locator.test.ts +2 -2
- package/src/results/locator.ts +1 -1
- package/src/results/open.ts +2 -2
- package/src/results/publish.ts +1 -1
- package/src/results/results.test.ts +6 -323
- package/src/results/select.test.ts +438 -0
- package/src/results/select.ts +6 -6
- package/src/results/skipped-notice.ts +1 -1
- package/src/results/truncate.ts +2 -2
- package/src/results/types.ts +9 -9
- package/src/results/writer.ts +4 -4
- package/src/runner/attempt.test.ts +2 -2
- package/src/runner/attempt.ts +6 -6
- package/src/runner/eval-source.test.ts +1 -1
- package/src/runner/eval-source.ts +1 -1
- package/src/runner/reporters/artifacts.ts +1 -1
- package/src/runner/run.ts +2 -2
- package/src/runner/timing.ts +1 -1
- package/src/runner/types.ts +14 -14
- package/src/sandbox/docker-agent-image.ts +36 -0
- package/src/sandbox/e2b-agent-template.test.ts +6 -21
- package/src/sandbox/e2b-agent-template.ts +44 -11
- package/src/sandbox/index.ts +8 -0
- package/src/sandbox/official-baselines.test.ts +175 -0
- package/src/sandbox/resolve.ts +1 -1
- package/src/sandbox/types.ts +1 -1
- package/src/scoring/diff.ts +1 -1
- package/src/scoring/types.ts +1 -1
- package/src/shared/facts.ts +1 -1
- package/src/show/index.ts +1 -1
- package/src/show/render.ts +1 -1
- package/src/show/show.test.ts +3 -3
- package/src/view/app/App.tsx +3 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +1 -1
- package/src/view/data.test.ts +2 -2
- package/src/view/data.ts +1 -1
- package/src/view/site-base.test.ts +51 -0
- package/src/view/site.ts +16 -0
- package/src/view/styles.css +1 -2
- package/src/view/template.html +1 -0
- package/src/view/view-report.test.ts +1 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// OpenCode `run --format json` / `opencode export` 解析器。方言只住这里,不进 core
|
|
2
|
+
// (契约见 docs/feature/adapters/sdk/opencode/README.md)。
|
|
3
|
+
|
|
4
|
+
import type { StreamEvent, Usage, ToolName, JsonValue } from "../../types.ts";
|
|
5
|
+
import type { ParsedTranscript } from "./index.ts";
|
|
6
|
+
import { GENERIC_VERB_ALIASES, normalizeToolName as normalizeShared } from "../tool-names.ts";
|
|
7
|
+
|
|
8
|
+
export const OPENCODE_TOOL_ALIASES: Record<string, ToolName> = {
|
|
9
|
+
...GENERIC_VERB_ALIASES,
|
|
10
|
+
read: "file_read",
|
|
11
|
+
write: "file_write",
|
|
12
|
+
create: "file_write",
|
|
13
|
+
edit: "file_edit",
|
|
14
|
+
patch: "file_edit",
|
|
15
|
+
// OpenCode 写文件主路径是 apply_patch;Add File 在下方按 patch 内容升成 file_write。
|
|
16
|
+
apply_patch: "file_edit",
|
|
17
|
+
bash: "shell",
|
|
18
|
+
shell: "shell",
|
|
19
|
+
webfetch: "web_fetch",
|
|
20
|
+
websearch: "web_search",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** 从 apply_patch 的 patchText 抠 path,并把 Add File 升成 file_write。 */
|
|
24
|
+
function enrichApplyPatchInput(input: JsonValue): { input: JsonValue; tool: ToolName } {
|
|
25
|
+
const obj = input && typeof input === "object" && !Array.isArray(input)
|
|
26
|
+
? { ...(input as Record<string, unknown>) }
|
|
27
|
+
: ({ patchText: input } as Record<string, unknown>);
|
|
28
|
+
const patchText = typeof obj.patchText === "string" ? obj.patchText : typeof obj.patch === "string" ? obj.patch : "";
|
|
29
|
+
const add = patchText.match(/\*\*\*\s*Add File:\s*(.+)/);
|
|
30
|
+
const update = patchText.match(/\*\*\*\s*(?:Update|Delete) File:\s*(.+)/);
|
|
31
|
+
const path = (add?.[1] ?? update?.[1] ?? "").trim();
|
|
32
|
+
if (path) obj.path = path;
|
|
33
|
+
return {
|
|
34
|
+
input: obj as JsonValue,
|
|
35
|
+
tool: add ? "file_write" : "file_edit",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeToolName(name: string): ToolName {
|
|
40
|
+
return normalizeShared(name, OPENCODE_TOOL_ALIASES);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function get(obj: unknown, key: string): unknown {
|
|
44
|
+
return obj && typeof obj === "object" ? (obj as Record<string, unknown>)[key] : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function str(v: unknown): string | undefined {
|
|
48
|
+
return typeof v === "string" && v ? v : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function num(obj: unknown, ...keys: string[]): number {
|
|
52
|
+
for (const k of keys) {
|
|
53
|
+
const v = get(obj, k);
|
|
54
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function coerceArgs(value: unknown): JsonValue {
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(value) as JsonValue;
|
|
63
|
+
} catch {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return (value ?? {}) as JsonValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 从 stdout / export 的 JSONL(或夹杂日志的文本)里抽出 JSON 对象行。
|
|
72
|
+
*/
|
|
73
|
+
export function extractOpenCodeJsonl(raw: string | undefined): string | undefined {
|
|
74
|
+
if (!raw?.trim()) return undefined;
|
|
75
|
+
const lines = raw.split("\n").filter((line) => {
|
|
76
|
+
const t = line.trim();
|
|
77
|
+
return t.startsWith("{") && t.endsWith("}");
|
|
78
|
+
});
|
|
79
|
+
return lines.length ? lines.join("\n") : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 事件行上的 sessionID / sessionId。 */
|
|
83
|
+
export function sessionIdFromOpenCodeTranscript(raw: string | undefined): string | undefined {
|
|
84
|
+
const jsonl = extractOpenCodeJsonl(raw) ?? raw;
|
|
85
|
+
if (!jsonl) return undefined;
|
|
86
|
+
for (const line of jsonl.split("\n")) {
|
|
87
|
+
const t = line.trim();
|
|
88
|
+
if (!t) continue;
|
|
89
|
+
try {
|
|
90
|
+
const obj = JSON.parse(t) as Record<string, unknown>;
|
|
91
|
+
const id = str(obj.sessionID) ?? str(obj.sessionId) ?? str(get(obj, "session_id"));
|
|
92
|
+
if (id) return id;
|
|
93
|
+
} catch {
|
|
94
|
+
// skip
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* OpenCode JSON 事件流 → 标准事件 + usage。
|
|
102
|
+
* 认 `tool_use` / `text` / `step_finish` 主路径,并兼容少量 legacy 形状。
|
|
103
|
+
*/
|
|
104
|
+
export function parseOpenCodeTranscript(raw: string | undefined): ParsedTranscript {
|
|
105
|
+
const events: StreamEvent[] = [];
|
|
106
|
+
let inputTokens = 0;
|
|
107
|
+
let outputTokens = 0;
|
|
108
|
+
let cacheReadTokens = 0;
|
|
109
|
+
let cacheCreationTokens = 0;
|
|
110
|
+
let costUSD = 0;
|
|
111
|
+
let requests = 0;
|
|
112
|
+
let parseSuccess = true;
|
|
113
|
+
let synth = 0;
|
|
114
|
+
const nextSynthId = (): string => `oc_${++synth}`;
|
|
115
|
+
const pending: string[] = [];
|
|
116
|
+
|
|
117
|
+
const jsonl = extractOpenCodeJsonl(raw) ?? raw;
|
|
118
|
+
if (!jsonl?.trim()) {
|
|
119
|
+
return { events, usage: {}, compactions: 0, parseSuccess: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const addUsage = (usage: unknown): void => {
|
|
123
|
+
if (!usage || typeof usage !== "object") return;
|
|
124
|
+
const rawInput = num(usage, "input", "input_tokens", "inputTokens", "prompt_tokens");
|
|
125
|
+
const output = num(usage, "output", "output_tokens", "outputTokens", "completion_tokens");
|
|
126
|
+
let cacheRead = num(usage, "cacheRead", "cache_read", "cache_read_input_tokens", "cacheReadTokens");
|
|
127
|
+
if (cacheRead === 0) cacheRead = num(get(usage, "prompt_tokens_details"), "cached_tokens");
|
|
128
|
+
const cacheCreation = num(usage, "cacheWrite", "cache_write", "cache_creation_input_tokens");
|
|
129
|
+
const cost = num(usage, "cost", "costUSD", "total_cost");
|
|
130
|
+
if (rawInput === 0 && output === 0 && cacheRead === 0 && cacheCreation === 0 && cost === 0) return;
|
|
131
|
+
// Chat Completions 口径的 prompt_tokens 可能含缓存命中
|
|
132
|
+
const input = Math.max(0, rawInput - cacheRead);
|
|
133
|
+
inputTokens += input;
|
|
134
|
+
outputTokens += output;
|
|
135
|
+
cacheReadTokens += cacheRead;
|
|
136
|
+
cacheCreationTokens += cacheCreation;
|
|
137
|
+
costUSD += cost;
|
|
138
|
+
requests += 1;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
for (const line of jsonl.split("\n")) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (!trimmed) continue;
|
|
144
|
+
let data: Record<string, unknown>;
|
|
145
|
+
try {
|
|
146
|
+
data = JSON.parse(trimmed) as Record<string, unknown>;
|
|
147
|
+
} catch {
|
|
148
|
+
parseSuccess = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const eventType = str(data.type) ?? str(data.kind) ?? str(data.event);
|
|
154
|
+
const part = get(data, "part") as Record<string, unknown> | undefined;
|
|
155
|
+
const state = part ? (get(part, "state") as Record<string, unknown> | undefined) : undefined;
|
|
156
|
+
|
|
157
|
+
if (eventType === "tool_use" && part && str(get(part, "tool"))) {
|
|
158
|
+
const name = str(get(part, "tool"))!;
|
|
159
|
+
let input = coerceArgs(get(state, "input") ?? get(part, "input"));
|
|
160
|
+
let tool = normalizeToolName(name);
|
|
161
|
+
if (name.toLowerCase() === "apply_patch" || name.toLowerCase() === "patch") {
|
|
162
|
+
const enriched = enrichApplyPatchInput(input);
|
|
163
|
+
input = enriched.input;
|
|
164
|
+
tool = enriched.tool;
|
|
165
|
+
}
|
|
166
|
+
const callId =
|
|
167
|
+
str(get(part, "callID")) ??
|
|
168
|
+
str(get(part, "callId")) ??
|
|
169
|
+
str(get(part, "id")) ??
|
|
170
|
+
str(get(data, "id")) ??
|
|
171
|
+
nextSynthId();
|
|
172
|
+
if (!str(get(part, "callID")) && !str(get(part, "callId")) && !str(get(part, "id"))) {
|
|
173
|
+
pending.push(callId);
|
|
174
|
+
}
|
|
175
|
+
events.push({
|
|
176
|
+
type: "action.called",
|
|
177
|
+
callId,
|
|
178
|
+
name,
|
|
179
|
+
input,
|
|
180
|
+
tool,
|
|
181
|
+
});
|
|
182
|
+
const status = str(get(state, "status"));
|
|
183
|
+
if (status === "completed" || status === "error" || get(state, "output") !== undefined) {
|
|
184
|
+
const output = (get(state, "output") ?? get(state, "error") ?? null) as JsonValue;
|
|
185
|
+
const metadata = get(state, "metadata") as Record<string, unknown> | undefined;
|
|
186
|
+
const exitCode = typeof get(metadata, "exit") === "number" ? (get(metadata, "exit") as number) : undefined;
|
|
187
|
+
const isShell = ["bash", "shell", "exec"].includes(name.toLowerCase());
|
|
188
|
+
const success = isShell
|
|
189
|
+
? exitCode === 0 || exitCode === undefined
|
|
190
|
+
: status !== "error" && !get(state, "error");
|
|
191
|
+
events.push({
|
|
192
|
+
type: "action.result",
|
|
193
|
+
callId,
|
|
194
|
+
output,
|
|
195
|
+
status: success ? "completed" : "failed",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (eventType === "text") {
|
|
202
|
+
const text = str(get(part, "text")) ?? str(data.text) ?? str(data.content);
|
|
203
|
+
if (text?.trim()) events.push({ type: "message", role: "assistant", text });
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (eventType === "step_finish" || eventType === "step_start") {
|
|
208
|
+
// OpenCode 1.18 把用量放在 part.tokens({input,output,...}) + part.cost。
|
|
209
|
+
const tokens = get(part, "tokens") ?? get(data, "tokens");
|
|
210
|
+
if (tokens && typeof tokens === "object") {
|
|
211
|
+
addUsage({
|
|
212
|
+
...(tokens as Record<string, unknown>),
|
|
213
|
+
cost: get(part, "cost") ?? get(data, "cost"),
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
addUsage(get(data, "usage") ?? get(part, "usage") ?? get(state, "usage"));
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (eventType === "message" || eventType === "assistant" || eventType === "response") {
|
|
222
|
+
const text =
|
|
223
|
+
str(get(get(data, "message"), "content")) ?? str(data.content) ?? str(data.text);
|
|
224
|
+
if (text?.trim()) events.push({ type: "message", role: "assistant", text });
|
|
225
|
+
addUsage(get(data, "usage"));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (eventType === "error") {
|
|
230
|
+
const message =
|
|
231
|
+
str(get(get(data, "error"), "message")) ?? str(data.message) ?? str(data.content) ?? "opencode error";
|
|
232
|
+
events.push({ type: "error", message });
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// export 形态:顶层 messages[]
|
|
237
|
+
const messages = get(data, "messages");
|
|
238
|
+
if (Array.isArray(messages)) {
|
|
239
|
+
for (const msg of messages) {
|
|
240
|
+
const info = get(msg, "info") as Record<string, unknown> | undefined;
|
|
241
|
+
const role = str(get(info, "role")) ?? str(get(msg, "role"));
|
|
242
|
+
const parts = get(msg, "parts");
|
|
243
|
+
if (Array.isArray(parts)) {
|
|
244
|
+
for (const p of parts) {
|
|
245
|
+
const pType = str(get(p, "type"));
|
|
246
|
+
if (pType === "text") {
|
|
247
|
+
const text = str(get(p, "text"));
|
|
248
|
+
if (text?.trim() && role !== "user") {
|
|
249
|
+
events.push({ type: "message", role: "assistant", text });
|
|
250
|
+
}
|
|
251
|
+
} else if (pType === "tool" || pType === "tool_use") {
|
|
252
|
+
const name = str(get(p, "tool")) ?? str(get(p, "name")) ?? "unknown";
|
|
253
|
+
const callId = str(get(p, "callID")) ?? str(get(p, "id")) ?? nextSynthId();
|
|
254
|
+
events.push({
|
|
255
|
+
type: "action.called",
|
|
256
|
+
callId,
|
|
257
|
+
name,
|
|
258
|
+
input: coerceArgs(get(get(p, "state"), "input") ?? get(p, "input")),
|
|
259
|
+
tool: normalizeToolName(name),
|
|
260
|
+
});
|
|
261
|
+
const st = get(p, "state") as Record<string, unknown> | undefined;
|
|
262
|
+
if (st && (str(get(st, "status")) === "completed" || get(st, "output") !== undefined)) {
|
|
263
|
+
events.push({
|
|
264
|
+
type: "action.result",
|
|
265
|
+
callId,
|
|
266
|
+
output: (get(st, "output") ?? null) as JsonValue,
|
|
267
|
+
status: str(get(st, "status")) === "error" ? "failed" : "completed",
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
const text = str(get(msg, "content")) ?? str(get(msg, "text"));
|
|
274
|
+
if (text?.trim() && role !== "user") {
|
|
275
|
+
events.push({ type: "message", role: "assistant", text });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
addUsage(get(info, "usage") ?? get(msg, "usage"));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
parseSuccess = false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const usage: Usage = {};
|
|
287
|
+
if (inputTokens) usage.inputTokens = inputTokens;
|
|
288
|
+
if (outputTokens) usage.outputTokens = outputTokens;
|
|
289
|
+
if (cacheReadTokens) usage.cacheReadTokens = cacheReadTokens;
|
|
290
|
+
if (cacheCreationTokens) usage.cacheCreationTokens = cacheCreationTokens;
|
|
291
|
+
if (costUSD) usage.costUSD = costUSD;
|
|
292
|
+
if (requests) usage.requests = requests;
|
|
293
|
+
|
|
294
|
+
return { events, usage, compactions: 0, parseSuccess };
|
|
295
|
+
}
|
package/src/o11y/types.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { JsonValue, SourceLoc } from "../shared/types.ts";
|
|
|
8
8
|
* send 的 `Turn.usage` 直接返回)。每个字段只在协议真实提供该值时存在——原始协议没有 usage 时
|
|
9
9
|
* 省略,不编造数值;不存在「默认 0」或「默认 1」的字段。三个输入侧桶恒互斥:相加才是送进模型的
|
|
10
10
|
* 完整上下文量;把协议原生口径归一到互斥是 adapter 的落值义务——OpenAI 系报「含缓存的输入总量 +
|
|
11
|
-
* 缓存命中子集」,落 inputTokens 前先扣掉子集(docs/feature/
|
|
11
|
+
* 缓存命中子集」,落 inputTokens 前先扣掉子集(docs/feature/record/architecture.md#usage,
|
|
12
12
|
* 各协议明细见 docs/feature/adapters/sdk/<name>/cost.md)。
|
|
13
13
|
*/
|
|
14
14
|
export interface Usage {
|
|
@@ -64,7 +64,7 @@ export interface InputRequest {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* 落盘截断的结构化标记(见 docs/feature/
|
|
67
|
+
* 落盘截断的结构化标记(见 docs/feature/record/architecture.md「大值截断」):
|
|
68
68
|
* `path` 是被截断的位置(事件里是字段名,如 "output";span 里是 attribute key,如 "output.value"),
|
|
69
69
|
* `originalBytes` 是截断前的 UTF-8 字节数。view 显示「输出过大,已截断」靠它,不正则匹配 marker。
|
|
70
70
|
*/
|
|
@@ -188,7 +188,7 @@ export interface TraceSpan {
|
|
|
188
188
|
* 给人 / 给 EVAL.ts 看的 o11y 摘要(注入沙箱 __niceeval__/results.json)。从 `events.json` 派生的
|
|
189
189
|
* 行为计数缓存,不是权威——同一 niceeval 版本写读,删除后可从 `events.json` 重算。token 用量、
|
|
190
190
|
* 成本与耗时不在这里:权威唯一在 `result.json` 的 `Usage` / `estimatedCostUSD` 与 `durationMs`
|
|
191
|
-
* / `phases`(见 docs/feature/
|
|
191
|
+
* / `phases`(见 docs/feature/record/architecture.md「o11y.json」)。
|
|
192
192
|
*/
|
|
193
193
|
export interface O11ySummary {
|
|
194
194
|
totalTurns: number;
|
|
@@ -36,7 +36,7 @@ export function colorIndexForKey(key: string): number {
|
|
|
36
36
|
* 同一张图内 series 键集合的配色:每个键仍以稳定散列为起点(无冲突时与跨图配色一致),
|
|
37
37
|
* 图内撞色时按传入顺序(图例顺序)线性探测下一个空色格——跨图稳定让位给图内可辨;
|
|
38
38
|
* 键数超过色板后无空格可探,回落散列格(复用不可避免)。返回键 → 色板下标。
|
|
39
|
-
* 契约见 docs/feature/reports/
|
|
39
|
+
* 契约见 docs/feature/reports/components/README.md「系列色:分配单位是页」;
|
|
40
40
|
* 修法台账见 memory/scatter-series-color-collision.md。
|
|
41
41
|
*/
|
|
42
42
|
export function colorIndicesForKeys(keys: readonly string[]): Map<string, number> {
|
|
@@ -56,9 +56,10 @@
|
|
|
56
56
|
|
|
57
57
|
/* ---- AttemptList:实体列表的叶子层,一条卡片一个 Attempt ---- */
|
|
58
58
|
.nre-attempt-list .nre-attempts { list-style:none; margin:0; padding:0; }
|
|
59
|
-
.nre-attempt-list .nre-attempt { border:1px solid var(--line); border-
|
|
59
|
+
.nre-attempt-list .nre-attempt { border:1px solid var(--line); border-radius:8px; padding:10px 14px; margin-bottom:8px; background:var(--panel); }
|
|
60
|
+
/* 只有 failed/errored 留左边语义色条;其余靠 hairline,避免每行都像 callout */
|
|
60
61
|
.nre-attempt-list .nre-attempt-failed,
|
|
61
|
-
.nre-attempt-list .nre-attempt-errored { border-left
|
|
62
|
+
.nre-attempt-list .nre-attempt-errored { border-left: 3px solid var(--bad); }
|
|
62
63
|
.nre-attempt-list .nre-attempt-head { display:flex; flex-wrap:wrap; gap:.75em; align-items:baseline; }
|
|
63
64
|
.nre-attempt-list .nre-attempt-eval { font-weight:620; }
|
|
64
65
|
.nre-attempt-list .nre-attempt-experiment,
|
|
@@ -336,9 +337,8 @@ table.nre,
|
|
|
336
337
|
.nre-scope-summary .nre-scope-kpi:last-child { border-right: 0; }
|
|
337
338
|
.nre-scope-summary .nre-scope-kpi dt {
|
|
338
339
|
color: var(--soft);
|
|
339
|
-
font-size:
|
|
340
|
-
|
|
341
|
-
letter-spacing: 0.08em;
|
|
340
|
+
font-size: 12px;
|
|
341
|
+
font-weight: 550;
|
|
342
342
|
}
|
|
343
343
|
.nre-scope-summary .nre-scope-kpi dd {
|
|
344
344
|
margin: 6px 0 0;
|
|
@@ -949,13 +949,13 @@ table.nre,
|
|
|
949
949
|
letter-spacing: 0.04em;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
-
/* Error
|
|
952
|
+
/* Error 与断言:中性底 + 失败左边条;可展开明细。 */
|
|
953
953
|
.nre-attempt-error {
|
|
954
954
|
overflow: hidden;
|
|
955
|
-
border: 1px solid
|
|
955
|
+
border: 1px solid var(--line);
|
|
956
956
|
border-left: 3px solid var(--bad);
|
|
957
957
|
border-radius: 9px;
|
|
958
|
-
background:
|
|
958
|
+
background: var(--panel);
|
|
959
959
|
}
|
|
960
960
|
.nre-attempt-error .nre-attempt-error-fields {
|
|
961
961
|
display: grid;
|
|
@@ -980,8 +980,8 @@ table.nre,
|
|
|
980
980
|
overflow: auto;
|
|
981
981
|
margin: 0;
|
|
982
982
|
padding: 12px 15px;
|
|
983
|
-
border-top: 1px solid
|
|
984
|
-
background:
|
|
983
|
+
border-top: 1px solid var(--line);
|
|
984
|
+
background: var(--panel-2);
|
|
985
985
|
color: var(--muted);
|
|
986
986
|
font: 12px/1.55 ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
987
987
|
white-space: pre-wrap;
|
|
@@ -996,10 +996,14 @@ table.nre,
|
|
|
996
996
|
.nre-attempt-source .nre-source-line-detail .nre-assertion-row {
|
|
997
997
|
overflow: hidden;
|
|
998
998
|
border: 1px solid var(--line);
|
|
999
|
-
border-left: 3px solid var(--line-strong);
|
|
1000
999
|
border-radius: 8px;
|
|
1001
1000
|
background: var(--panel);
|
|
1002
1001
|
}
|
|
1002
|
+
/* 只有 gate failed(tone-bad)留左边色条;soft/passed/na 用 hairline */
|
|
1003
|
+
.nre-attempt-assertions .nre-assertion-row:has(.nre-tone-bad),
|
|
1004
|
+
.nre-attempt-source .nre-source-line-detail .nre-assertion-row.nre-tone-bad {
|
|
1005
|
+
border-left: 3px solid var(--bad);
|
|
1006
|
+
}
|
|
1003
1007
|
.nre-attempt-assertions .nre-assertion-row > summary {
|
|
1004
1008
|
display: flex;
|
|
1005
1009
|
flex-wrap: wrap;
|
|
@@ -1060,7 +1064,7 @@ table.nre,
|
|
|
1060
1064
|
}
|
|
1061
1065
|
|
|
1062
1066
|
/* Source:GitHub diff 式整块横向滚动;与产品站示例卡同一套视觉语言(整行 8% 浅染 + 2px 左缘 +
|
|
1063
|
-
行号位图标,规范见 docs/feature/reports/
|
|
1067
|
+
行号位图标,规范见 docs/feature/reports/components/attempt-detail/attempt-source.md)。普通行只是 div,
|
|
1064
1068
|
只有有证据的行才是可点击 details。 */
|
|
1065
1069
|
.nre-attempt-source {
|
|
1066
1070
|
overflow: hidden;
|
|
@@ -1358,12 +1362,10 @@ table.nre,
|
|
|
1358
1362
|
.nre-attempt-source .nre-attempt-source-unlocated > .nre-source-turn {
|
|
1359
1363
|
overflow: hidden;
|
|
1360
1364
|
border: 1px solid var(--line);
|
|
1361
|
-
border-left: 3px solid var(--blue, var(--nre-c0));
|
|
1362
1365
|
border-radius: 8px;
|
|
1363
1366
|
background: var(--panel-2);
|
|
1364
1367
|
}
|
|
1365
|
-
.nre-attempt-source .nre-attempt-source-unlocated > .nre-source-turn-failed { border-left
|
|
1366
|
-
.nre-attempt-source .nre-attempt-source-unlocated > .nre-source-turn-waiting { border-left-color: var(--warn); }
|
|
1368
|
+
.nre-attempt-source .nre-attempt-source-unlocated > .nre-source-turn-failed { border-left: 3px solid var(--bad); }
|
|
1367
1369
|
|
|
1368
1370
|
/* Other conversation(unlocated turns):TurnDetail 在这里复用 AttemptConversation 同一套回复
|
|
1369
1371
|
renderer(showMeta/showSent 都为真,没有可依附的 send 行),但 .nre-conv-* 的规则此前只覆盖了
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptAssertions:全量 assertion,非 passed 默认展开、passed 按 group 折叠计数。
|
|
2
|
-
// 没有 assertion 时零输出(docs/feature/reports/
|
|
2
|
+
// 没有 assertion 时零输出(docs/feature/reports/components/attempt-detail/README.md)。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement } from "react";
|
|
5
5
|
import type { AttemptAssertionsData } from "../../model/types.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptConversation:标准事件流按轮组织的完整分轮事件卡。没有 events 时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/README.md)。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement, ReactNode } from "react";
|
|
5
5
|
import type { AttemptConversationData, AttemptConversationReply, AttemptConversationRound } from "../../model/types.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptDiagnostics:lifecycle 分组的 diagnostics。没有 diagnostics 时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/README.md)。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement } from "react";
|
|
5
5
|
import type { AttemptDiagnosticsData } from "../../model/types.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptDiff:generated / modified / deleted 文件摘要与展开的行变化统计。没有变更时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/README.md)。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement } from "react";
|
|
5
5
|
import type { AttemptDiffData, AttemptDiffFileEntry } from "../../model/types.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AttemptError:结构化 error 明细。没有 error 时零输出(docs/feature/reports/
|
|
1
|
+
// AttemptError:结构化 error 明细。没有 error 时零输出(docs/feature/reports/components/attempt-detail/README.md)。
|
|
2
2
|
// 字段标签是低层技术标识,与终端 `niceeval show` 一样保持英文,不进 view 的 i18n 词典。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement } from "react";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptFixPrompt:单条 attempt 的复制修复 prompt。passed 或没有可操作失败时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/README.md)。与 CopyFixPrompt 同一套增强约定:
|
|
3
3
|
// prompt 在 resolve 阶段算好、烘进静态 HTML,复制按钮是 enhance.js 的增强行为。
|
|
4
4
|
|
|
5
5
|
import type { ReactElement } from "react";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// AttemptSource:GitHub diff 式带标注 eval 源码。send / assertion 行按状态着色,点击
|
|
2
2
|
// 原生 details 在调用点展开完整回复与 assertion 细节。没有 source 时零输出
|
|
3
|
-
// (docs/feature/reports/
|
|
3
|
+
// (docs/feature/reports/components/attempt-detail/attempt-source.md)。
|
|
4
4
|
|
|
5
5
|
import type { ReactElement, ReactNode } from "react";
|
|
6
6
|
import type { AttemptSourceData, AttemptSourceLineData, AttemptSourceTurn } from "../../model/types.ts";
|
|
@@ -192,8 +192,7 @@ function AssertionDetail({ assertion, locale }: { assertion: AssertionResult & {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/** `t.score(label, n)` 调用行的展开区:给分记录本体(label、挣分、分组路径),不着判定色——
|
|
195
|
-
* 给分是分数面事实,不是判定(docs/feature/reports/
|
|
196
|
-
* web 面视觉规范」给分行)。 */
|
|
195
|
+
* 给分是分数面事实,不是判定(docs/feature/reports/components/attempt-detail/attempt-source.md「给分行」)。 */
|
|
197
196
|
function ScoreEntryDetail({ entry }: { entry: ScoreEntry }): ReactElement {
|
|
198
197
|
return (
|
|
199
198
|
<div className="nre-score-entry-row">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AttemptSummary:恒非空的身份/verdict/时间/成本卡(docs/feature/reports/
|
|
1
|
+
// AttemptSummary:恒非空的身份/verdict/时间/成本卡(docs/feature/reports/components/attempt-detail/README.md)。
|
|
2
2
|
|
|
3
3
|
import type { ReactElement } from "react";
|
|
4
4
|
import type { AttemptSummaryData } from "../../model/types.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// AttemptTimeline:runner phases 主链 + 收尾段,children(hook/command/turn)默认收合、
|
|
2
2
|
// 失败节点带标记;turn 节点按 traceId 关联同一轮的 agent/model/tool span。没有 phase 时
|
|
3
|
-
// 零输出(docs/feature/reports/
|
|
3
|
+
// 零输出(docs/feature/reports/components/attempt-detail/README.md)。
|
|
4
4
|
|
|
5
5
|
import type { ReactElement } from "react";
|
|
6
6
|
import type { AttemptTimelineData } from "../../model/types.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AttemptTrace:不与 runner 节点混合的原始 OTel span 树。没有 trace 时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/README.md)。
|
|
3
3
|
|
|
4
4
|
import type { ReactElement } from "react";
|
|
5
5
|
import type { AttemptTraceData } from "../../model/types.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// UsageTable:判定、轮数、工具调用数、token 拆分与成本摊成的用量明细。没有 usage 时零输出
|
|
2
|
-
// (docs/feature/reports/
|
|
2
|
+
// (docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源)。web 面与 text 面
|
|
3
3
|
// 单行摘要(usageTableText)是同一份 UsageTableData 的两种呈现,不重复组装口径。
|
|
4
4
|
|
|
5
5
|
import type { ReactElement } from "react";
|
|
@@ -423,7 +423,7 @@ describe("Attempt 详情组件族:非空/空证据矩阵", () => {
|
|
|
423
423
|
});
|
|
424
424
|
|
|
425
425
|
it("UsageTable:桶恒互斥,inputTokens 落盘原样透传,不派生第二个输入字段", () => {
|
|
426
|
-
// inputTokens 本身就是未缓存输入(docs/feature/
|
|
426
|
+
// inputTokens 本身就是未缓存输入(docs/feature/record/architecture.md#usage),
|
|
427
427
|
// "uncached in" 只是 face 层在 cacheReadTokens 在场时给的标注,data 不多一个字段。
|
|
428
428
|
const both = usageTableData(evidenceOf({ result: resultOf({ usage: { inputTokens: 100, outputTokens: 1, cacheReadTokens: 40 } }) }))!;
|
|
429
429
|
expect(both.usage?.inputTokens).toBe(100);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Attempt 详情组件族的计算函数(docs/feature/reports/
|
|
1
|
+
// Attempt 详情组件族的计算函数(docs/feature/reports/components/attempt-detail/README.md)。每个
|
|
2
2
|
// `attempt*Data(evidence)` 都是纯同步派生:evidence 已经由 loadAttemptEvidence 一次性
|
|
3
3
|
// 装配好全部证据,这里只做适合展示与序列化的取舍,不读文件、不 fetch、不重复调用
|
|
4
4
|
// attempt.events() / trace() / diff()。
|
|
@@ -263,7 +263,7 @@ export function attemptSourceData(evidence: AttemptEvidence): AttemptSourceData
|
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
265
|
* 单条 attempt 版的批量修复 prompt(与 CopyFixPrompt 的多条版本同一份步骤文案)。三态
|
|
266
|
-
* (docs/feature/reports/
|
|
266
|
+
* (docs/feature/reports/components/attempt-detail/README.md「`AttemptFixPrompt`」):计分制丢分或中止 →
|
|
267
267
|
* 非 null(围绕丢分检查点组装);计分制挣满且未中止、或通过制 passed → null;skipped 恒 null。
|
|
268
268
|
*/
|
|
269
269
|
export function attemptFixPromptData(evidence: AttemptEvidence): AttemptFixPromptData | null {
|
|
@@ -303,7 +303,7 @@ export function attemptFixPromptData(evidence: AttemptEvidence): AttemptFixPromp
|
|
|
303
303
|
|
|
304
304
|
// ───────────────────────── AttemptTimeline ─────────────────────────
|
|
305
305
|
|
|
306
|
-
/** 收尾段的阶段名(见 docs/feature/
|
|
306
|
+
/** 收尾段的阶段名(见 docs/feature/record/architecture.md);两面渲染都把这些单列在主链之后,不计入主链总耗时。 */
|
|
307
307
|
export const TIMELINE_CLOSING_PHASES: ReadonlySet<string> = new Set([
|
|
308
308
|
"eval.teardown",
|
|
309
309
|
"agent.teardown",
|
|
@@ -340,7 +340,7 @@ function commandStartOffsetMs(phases: readonly PhaseTiming[] | undefined, timing
|
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
/**
|
|
343
|
-
* 失败命令按关联 timing 节点的 `startOffsetMs` 排序(docs/feature/
|
|
343
|
+
* 失败命令按关联 timing 节点的 `startOffsetMs` 排序(docs/feature/record/architecture.md
|
|
344
344
|
* 「commandsjson」);关联不到 timing 节点的排在最后,组内保持 `commands.json` 原始顺序作稳定
|
|
345
345
|
* tie-break,不按数组偶然顺序猜时间。
|
|
346
346
|
*/
|
|
@@ -355,7 +355,7 @@ function sortFailedCommands(
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
/**
|
|
358
|
-
* 标准事件流按 `loc` 分轮(docs/feature/reports/
|
|
358
|
+
* 标准事件流按 `loc` 分轮(docs/feature/reports/components/attempt-detail/README.md「Attempt 详情组件」):
|
|
359
359
|
* 带 loc 的 user 消息开一轮;无 loc 的 user 消息不开新轮——与当前轮 sent 同文本的回显直接
|
|
360
360
|
* 吃掉,其它(stop-hook 反馈、skill 注入等轮内注入)作为回复条目留在当前轮。流首出现无 loc
|
|
361
361
|
* 的 user 消息(没有当前轮可归入)时退化开一条 loc 缺省的兜底轮,不丢弃。未识别的事件类型
|
|
@@ -482,7 +482,7 @@ export function attemptDiagnosticsData(evidence: AttemptEvidence): AttemptDiagno
|
|
|
482
482
|
// ───────────────────────── UsageTable ─────────────────────────
|
|
483
483
|
|
|
484
484
|
/**
|
|
485
|
-
* 组装口径单源:docs/feature/reports/
|
|
485
|
+
* 组装口径单源:docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源。
|
|
486
486
|
* identity 字段(locator/experimentId/evalId/attempt/verdict)恒有;turns/toolCalls 是 events
|
|
487
487
|
* 派生(与 o11y.json 行为摘要同源,buildO11ySummary 与 o11y.json 落盘走同一份纯函数),没有
|
|
488
488
|
* events 就整对省略——不因为其中一个恰好是 0 就当作"缺失"处理,0 是观测到的事实。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Attempt 详情组件族的 text 面(docs/feature/reports/
|
|
1
|
+
// Attempt 详情组件族的 text 面(docs/feature/reports/components/attempt-detail/README.md「在 show 与
|
|
2
2
|
// view 怎样渲染」):与 web 面共享同一次 resolve 产出的 data 事实(verdict、计数、能力位、
|
|
3
3
|
// 引用),允许把大块内容折成摘要 + 专用证据命令,但不得改变判定、计数或引用。
|
|
4
4
|
// 零 react、零 IO、纯同步——text 宿主不需要 react-dom 的那一半。
|
|
@@ -304,7 +304,7 @@ export function attemptDiagnosticsText(data: AttemptDiagnosticsData | null, _ctx
|
|
|
304
304
|
// ───────────────────────── UsageTable ─────────────────────────
|
|
305
305
|
|
|
306
306
|
/**
|
|
307
|
-
* 单行装配形态,与 docs/feature/reports/
|
|
307
|
+
* 单行装配形态,与 docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源
|
|
308
308
|
* 的示例行文案是同一形态本身,不是它的近似摘要:
|
|
309
309
|
* `usage: 6 turns · 21 tool calls · 62.3k uncached in + 942.6k cache read / 6.7k out · 24 requests · $1.14`
|
|
310
310
|
* 每个片段独立地只在对应事实存在时出现,顺序保持不变;全部缺失时整行不出现(返回 ""),
|
|
@@ -332,7 +332,7 @@ export function usageTableText(data: UsageTableData | null, _ctx: TextContext):
|
|
|
332
332
|
data.toolCalls !== undefined ? `${data.toolCalls} tool call${data.toolCalls === 1 ? "" : "s"}` : undefined,
|
|
333
333
|
tokenSegment || undefined,
|
|
334
334
|
// requests 只在协议真实提供时显示——协议不提供就整段省略,绝不凑一个 1
|
|
335
|
-
// (docs/feature/reports/
|
|
335
|
+
// (docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源)。
|
|
336
336
|
usage?.requests !== undefined ? `${usage.requests} requests` : undefined,
|
|
337
337
|
data.estimatedCostUSD !== undefined ? formatUSD(data.estimatedCostUSD) : undefined,
|
|
338
338
|
].filter((s): s is string => s !== undefined);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Attempt 详情组件族的装配点(docs/feature/reports/
|
|
1
|
+
// Attempt 详情组件族的装配点(docs/feature/reports/components/attempt-detail/README.md)。11 个叶子
|
|
2
2
|
// 组件都是同一份 spec/data 判别联合(AttemptSectionProps<Data>):省略 input 时取当前
|
|
3
3
|
// attempt-input page 注入的 evidence;放在 scope-input page 且未显式传 input/data 时
|
|
4
4
|
// resolve 报完整用户反馈并指引移到 attempt-input page 或传入 evidence。
|
|
@@ -15,7 +15,7 @@ import { cx } from "../shared.ts";
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* 时效标注(`↩` + 紧凑时距):历史执行(携带,或跨快照拼入)才渲染,新执行不标——subdued
|
|
18
|
-
* 行内事实,不占框、不用警示色(docs/feature/reports/
|
|
18
|
+
* 行内事实,不占框、不用警示色(docs/feature/reports/components/entity-lists/README.md「时效标注」)。
|
|
19
19
|
* web 面 hover 显示完整执行时刻;ExperimentList / EvalList / AttemptList 三处共用。
|
|
20
20
|
*/
|
|
21
21
|
export function HistoricalMark({
|
|
@@ -35,7 +35,7 @@ export function HistoricalMark({
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Eval 父行的时效标注:全部 attempt 均为历史执行时,标最近一次执行(startedAt 最大)的时距;
|
|
38
|
-
* 新旧混合时父行不标,子行各自可见(docs/feature/reports/
|
|
38
|
+
* 新旧混合时父行不标,子行各自可见(docs/feature/reports/components/entity-lists/README.md「时效标注」)。
|
|
39
39
|
* EvalList 与 ExperimentList 的 Eval 父行共用。
|
|
40
40
|
*/
|
|
41
41
|
export function EvalHistoricalMark({
|
|
@@ -79,7 +79,7 @@ export function AttemptLocatorBadge({
|
|
|
79
79
|
/**
|
|
80
80
|
* failureSummary + moreFailures 的展示形态:摘要原样,+N 计数由 moreFailures 驱动。计分制
|
|
81
81
|
* passed attempt 的非 null 摘要只可能来自丢分得分点(规则 6)——"+N more lost points",不是
|
|
82
|
-
* "+N more failures":丢分不是失败,措辞分开(docs/feature/reports/
|
|
82
|
+
* "+N more failures":丢分不是失败,措辞分开(docs/feature/reports/components/entity-lists/README.md
|
|
83
83
|
* `AttemptListItem.moreFailures` 字段注释)。
|
|
84
84
|
*/
|
|
85
85
|
export function failureSummaryText(item: Pick<AttemptListItem, "failureSummary" | "moreFailures" | "verdict">, locale: ReportLocale): string | null {
|