niceeval 0.1.1 → 0.2.0
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/README.md +8 -8
- package/README.zh.md +13 -13
- package/docs/README.md +119 -0
- package/docs/adapters/README.md +60 -0
- package/docs/adapters/authoring.md +179 -0
- package/docs/adapters/coding-agent-skills-plugins.md +324 -0
- package/docs/adapters/collection.md +128 -0
- package/docs/adapters/contract.md +263 -0
- package/docs/adapters/reference/agent-eval.md +215 -0
- package/docs/adapters/reference/agent-loop-apis.md +69 -0
- package/docs/adapters/reference/claude-code-otel-telemetry.md +100 -0
- package/docs/adapters/reference/eve-protocol.md +127 -0
- package/docs/adapters/reference/otel-genai.md +107 -0
- package/docs/adapters/reference/otel-instrumentation.md +65 -0
- package/docs/adapters/targets.md +99 -0
- package/docs/architecture.md +140 -0
- package/docs/assertions.md +388 -0
- package/docs/capabilities-by-construction.md +48 -0
- package/docs/cli.md +171 -0
- package/docs/concepts.md +106 -0
- package/docs/e2e-ci.md +295 -0
- package/docs/eval-authoring.md +319 -0
- package/docs/experiments.md +154 -0
- package/docs/getting-started.md +224 -0
- package/docs/multi-agent.md +145 -0
- package/docs/observability.md +333 -0
- package/docs/origin-integration.md +195 -0
- package/docs/references.md +35 -0
- package/docs/results-format.md +228 -0
- package/docs/runner.md +104 -0
- package/docs/sandbox.md +276 -0
- package/docs/scoring.md +119 -0
- package/docs/source-map.md +106 -0
- package/docs/tier-sync.md +177 -0
- package/docs/view.md +157 -0
- package/docs/vision.md +88 -0
- package/package.json +39 -5
- package/src/agents/ai-sdk-otel.test.ts +22 -0
- package/src/agents/ai-sdk-otel.ts +62 -0
- package/src/agents/ai-sdk.test.ts +462 -0
- package/src/agents/ai-sdk.ts +575 -0
- package/src/agents/bub.ts +30 -37
- package/src/agents/claude-code.ts +7 -4
- package/src/agents/codex.ts +15 -10
- package/src/agents/index.ts +48 -1
- package/src/agents/sdk-streams.test.ts +145 -0
- package/src/agents/sdk-streams.ts +457 -0
- package/src/agents/shared.ts +77 -39
- package/src/agents/streaming.test.ts +152 -0
- package/src/agents/streaming.ts +195 -0
- package/src/agents/types.ts +218 -0
- package/src/agents/ui-message-stream.test.ts +241 -0
- package/src/agents/ui-message-stream.ts +368 -0
- package/src/cli.ts +124 -77
- package/src/context/context.test.ts +203 -7
- package/src/context/context.ts +158 -49
- package/src/context/session.test.ts +96 -0
- package/src/context/session.ts +119 -9
- package/src/context/types.ts +205 -0
- package/src/define.ts +4 -14
- package/src/expect/index.ts +8 -71
- package/src/i18n/core.ts +28 -0
- package/src/i18n/en.ts +47 -5
- package/src/i18n/index.ts +5 -17
- package/src/i18n/zh-CN.ts +47 -5
- package/src/index.ts +12 -0
- package/src/loaders/index.ts +8 -39
- package/src/o11y/otlp/canonical.ts +7 -6
- package/src/o11y/otlp/mappers/codex.ts +10 -8
- package/src/o11y/otlp/mappers/index.ts +9 -21
- package/src/o11y/otlp/receiver.ts +25 -7
- package/src/o11y/otlp/sandbox-receiver.ts +57 -21
- package/src/o11y/otlp/turn-otel.test.ts +110 -0
- package/src/o11y/otlp/turn-otel.ts +125 -0
- package/src/o11y/parsers/bub.ts +13 -11
- package/src/o11y/parsers/claude-code.ts +27 -51
- package/src/o11y/parsers/codex.ts +29 -46
- package/src/o11y/parsers/index.ts +5 -14
- package/src/o11y/tool-names.test.ts +61 -0
- package/src/o11y/tool-names.ts +74 -0
- package/src/o11y/types.ts +135 -0
- package/src/runner/attempt.ts +456 -0
- package/src/runner/fingerprint.ts +59 -0
- package/src/runner/remote-sandbox.ts +53 -0
- package/src/runner/report.ts +53 -0
- package/src/runner/reporters/artifacts.ts +25 -4
- package/src/runner/reporters/console.ts +2 -8
- package/src/runner/reporters/live.ts +2 -8
- package/src/runner/reporters/shared.ts +15 -0
- package/src/runner/reporters/table.ts +10 -62
- package/src/runner/run.ts +114 -619
- package/src/runner/types.ts +237 -0
- package/src/sandbox/checkpoint.ts +15 -13
- package/src/sandbox/docker-stream.ts +87 -0
- package/src/sandbox/docker.ts +42 -120
- package/src/sandbox/e2b.ts +24 -75
- package/src/sandbox/local-files.ts +33 -0
- package/src/sandbox/paths.test.ts +85 -0
- package/src/sandbox/paths.ts +45 -0
- package/src/sandbox/resolve.ts +10 -34
- package/src/sandbox/shell.ts +17 -0
- package/src/sandbox/source-files.ts +42 -2
- package/src/sandbox/types.ts +158 -0
- package/src/sandbox/vercel.ts +55 -71
- package/src/scoring/collector.ts +3 -2
- package/src/scoring/judge.ts +72 -146
- package/src/scoring/match.ts +79 -0
- package/src/scoring/scoped.ts +66 -18
- package/src/scoring/types.ts +76 -0
- package/src/shared/aggregate.ts +48 -0
- package/src/shared/format.ts +20 -0
- package/src/shared/outcome.ts +48 -0
- package/src/shared/types.ts +37 -0
- package/src/types.ts +20 -911
- package/src/view/aggregate.ts +103 -0
- package/src/view/app/App.tsx +26 -5
- package/src/view/app/components/AttemptModal.tsx +12 -3
- package/src/view/app/components/CodeView.tsx +16 -19
- package/src/view/app/components/CopyControls.tsx +4 -4
- package/src/view/app/components/ExperimentTable.tsx +5 -5
- package/src/view/app/i18n.ts +31 -7
- package/src/view/app/lib/format.ts +17 -20
- package/src/view/app/lib/outcome.ts +4 -16
- package/src/view/app/lib/transcript-data.tsx +20 -4
- package/src/view/app/main.tsx +3 -5
- package/src/view/app/pages/RunsPage.tsx +2 -2
- package/src/view/app/pages/TracesPage.tsx +2 -2
- package/src/view/app/shared.ts +2 -1
- package/src/view/app/types.ts +9 -44
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +18 -18
- package/src/view/index.ts +24 -401
- package/src/view/loader.ts +234 -0
- package/src/view/server.ts +136 -0
- package/src/view/shared/types.ts +64 -0
- package/src/view/styles.css +60 -9
|
@@ -7,61 +7,37 @@
|
|
|
7
7
|
|
|
8
8
|
import type { StreamEvent, Usage, ToolName, JsonValue } from "../../types.ts";
|
|
9
9
|
import type { ParsedTranscript } from "./index.ts";
|
|
10
|
+
import { normalizeToolName as normalizeShared } from "../tool-names.ts";
|
|
10
11
|
|
|
11
12
|
// ───────────────────────── 工具名归一 ─────────────────────────
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
shell: "shell",
|
|
38
|
-
execute_command: "shell",
|
|
39
|
-
run_command: "shell",
|
|
40
|
-
|
|
41
|
-
// web
|
|
42
|
-
WebFetch: "web_fetch",
|
|
43
|
-
web_fetch: "web_fetch",
|
|
44
|
-
fetch_url: "web_fetch",
|
|
45
|
-
mcp__fetch__fetch: "web_fetch",
|
|
46
|
-
WebSearch: "web_search",
|
|
47
|
-
web_search: "web_search",
|
|
14
|
+
/**
|
|
15
|
+
* Claude Code 特有别名(键小写;PascalCase 原名靠共享层的 toLowerCase 兜住);通用别名走基表。
|
|
16
|
+
* 裸名 read/write/edit/webfetch/websearch/task 必须在这里:它们是 Claude Code 的核心工具
|
|
17
|
+
* (Read/Write/Edit/WebFetch/WebSearch/Task),基表刻意不收裸动词。
|
|
18
|
+
*/
|
|
19
|
+
export const CLAUDE_TOOL_ALIASES: Record<string, ToolName> = {
|
|
20
|
+
read: "file_read",
|
|
21
|
+
readfile: "file_read",
|
|
22
|
+
write: "file_write",
|
|
23
|
+
writefile: "file_write",
|
|
24
|
+
write_to_file: "file_write",
|
|
25
|
+
edit: "file_edit",
|
|
26
|
+
multiedit: "file_edit",
|
|
27
|
+
editfile: "file_edit",
|
|
28
|
+
strreplace: "file_edit",
|
|
29
|
+
notebookedit: "file_edit",
|
|
30
|
+
bashoutput: "shell",
|
|
31
|
+
webfetch: "web_fetch",
|
|
32
|
+
mcp__fetch__fetch: "web_fetch",
|
|
33
|
+
websearch: "web_search",
|
|
34
|
+
ls: "list_dir",
|
|
35
|
+
listdir: "list_dir",
|
|
36
|
+
task: "agent_task",
|
|
37
|
+
};
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
glob: "glob",
|
|
52
|
-
list_files: "glob",
|
|
53
|
-
Grep: "grep",
|
|
54
|
-
grep: "grep",
|
|
55
|
-
search_files: "grep",
|
|
56
|
-
LS: "list_dir",
|
|
57
|
-
list_dir: "list_dir",
|
|
58
|
-
ListDir: "list_dir",
|
|
59
|
-
|
|
60
|
-
// 子 agent
|
|
61
|
-
Task: "agent_task",
|
|
62
|
-
task: "agent_task",
|
|
63
|
-
};
|
|
64
|
-
return toolMap[name] || "unknown";
|
|
39
|
+
function normalizeToolName(name: string): ToolName {
|
|
40
|
+
return normalizeShared(name, CLAUDE_TOOL_ALIASES);
|
|
65
41
|
}
|
|
66
42
|
|
|
67
43
|
// ───────────────────────── 小工具 ─────────────────────────
|
|
@@ -6,53 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
import type { StreamEvent, Usage, ToolName, JsonValue } from "../../types.ts";
|
|
8
8
|
import type { ParsedTranscript } from "./index.ts";
|
|
9
|
+
import { GENERIC_VERB_ALIASES, normalizeToolName as normalizeShared } from "../tool-names.ts";
|
|
9
10
|
|
|
10
11
|
// ───────────────────────── 工具名归一 ─────────────────────────
|
|
11
12
|
|
|
12
|
-
/** Codex
|
|
13
|
+
/** Codex 特有别名 + 裸动词(codex transcript 的工具名由 CLI 控制,裸动词安全);通用别名走基表。 */
|
|
14
|
+
export const CODEX_TOOL_ALIASES: Record<string, ToolName> = {
|
|
15
|
+
...GENERIC_VERB_ALIASES,
|
|
16
|
+
file_change: "file_edit",
|
|
17
|
+
update_plan: "agent_task",
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
function normalizeToolName(name: string): ToolName {
|
|
14
|
-
|
|
15
|
-
// 文件
|
|
16
|
-
read_file: "file_read",
|
|
17
|
-
write_file: "file_write",
|
|
18
|
-
create_file: "file_write",
|
|
19
|
-
delete_file: "file_write",
|
|
20
|
-
edit_file: "file_edit",
|
|
21
|
-
patch_file: "file_edit",
|
|
22
|
-
apply_patch: "file_edit",
|
|
23
|
-
file_change: "file_edit",
|
|
24
|
-
update_plan: "agent_task",
|
|
25
|
-
|
|
26
|
-
// shell
|
|
27
|
-
shell: "shell",
|
|
28
|
-
bash: "shell",
|
|
29
|
-
exec: "shell",
|
|
30
|
-
execute: "shell",
|
|
31
|
-
run: "shell",
|
|
32
|
-
terminal: "shell",
|
|
33
|
-
command_execution: "shell",
|
|
34
|
-
local_shell: "shell",
|
|
35
|
-
|
|
36
|
-
// web
|
|
37
|
-
fetch: "web_fetch",
|
|
38
|
-
http_request: "web_fetch",
|
|
39
|
-
curl: "web_fetch",
|
|
40
|
-
web_fetch: "web_fetch",
|
|
41
|
-
web_search: "web_search",
|
|
42
|
-
search: "web_search",
|
|
43
|
-
|
|
44
|
-
// 检索 / 导航
|
|
45
|
-
glob: "glob",
|
|
46
|
-
find_files: "glob",
|
|
47
|
-
list_files: "glob",
|
|
48
|
-
grep: "grep",
|
|
49
|
-
search_files: "grep",
|
|
50
|
-
ripgrep: "grep",
|
|
51
|
-
ls: "list_dir",
|
|
52
|
-
list_directory: "list_dir",
|
|
53
|
-
dir: "list_dir",
|
|
54
|
-
};
|
|
55
|
-
return toolMap[name.toLowerCase()] || "unknown";
|
|
21
|
+
return normalizeShared(name, CODEX_TOOL_ALIASES);
|
|
56
22
|
}
|
|
57
23
|
|
|
58
24
|
// ───────────────────────── 小工具 ─────────────────────────
|
|
@@ -140,13 +106,17 @@ function pickUsage(data: unknown): unknown {
|
|
|
140
106
|
|
|
141
107
|
// ───────────────────────── compaction 标记 ─────────────────────────
|
|
142
108
|
|
|
143
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* 仅在出现可信压缩标记时才认(保守):type / item.type / subtype 含 compact 或 context_truncat。
|
|
111
|
+
* 不再匹配 "summariz"——任何名字带 summarize 的工具 / item(如 summarization_tool)都会被
|
|
112
|
+
* 误判成压缩边界并整条吞掉,污染计数且丢事件。命中即吞整条事件,匹配面必须窄。
|
|
113
|
+
*/
|
|
144
114
|
function isCompactionMarker(eventType: unknown, data: unknown): boolean {
|
|
145
115
|
const candidates = [eventType, get(get(data, "item"), "type"), get(data, "subtype")];
|
|
146
116
|
for (const c of candidates) {
|
|
147
117
|
if (typeof c !== "string") continue;
|
|
148
118
|
const s = c.toLowerCase();
|
|
149
|
-
if (s.includes("compact") || s.includes("
|
|
119
|
+
if (s.includes("compact") || s.includes("context_truncat")) return true;
|
|
150
120
|
}
|
|
151
121
|
return false;
|
|
152
122
|
}
|
|
@@ -447,7 +417,11 @@ export function parseCodexTranscript(raw: string | undefined): ParsedTranscript
|
|
|
447
417
|
return { events, usage, compactions, parseSuccess };
|
|
448
418
|
}
|
|
449
419
|
|
|
450
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* 解读一条工具结果输出:显式 success/error → status 字符串 → exit_code,层层找成败证据。
|
|
422
|
+
* 都没有才默认 completed —— 这是乐观兜底:codex 不带任何成败字段的结果会被记成成功,
|
|
423
|
+
* noFailedActions() 这类负断言可能因此假通过。所以证据检查要尽量全,别轻易走到兜底。
|
|
424
|
+
*/
|
|
451
425
|
function interpretOutput(
|
|
452
426
|
rawOut: unknown,
|
|
453
427
|
data: unknown,
|
|
@@ -457,6 +431,15 @@ function interpretOutput(
|
|
|
457
431
|
if (explicitSuccess === false) return { output: (rawOut ?? null) as JsonValue, status: "failed" };
|
|
458
432
|
if (get(data, "error")) return { output: (rawOut ?? null) as JsonValue, status: "failed" };
|
|
459
433
|
|
|
434
|
+
// 事件自带 status 字符串(item/data 两层都看)。
|
|
435
|
+
const statusStr = get(data, "status") ?? get(get(data, "item"), "status");
|
|
436
|
+
if (statusStr === "failed" || statusStr === "error") {
|
|
437
|
+
return { output: (rawOut ?? null) as JsonValue, status: "failed" };
|
|
438
|
+
}
|
|
439
|
+
if (statusStr === "rejected" || statusStr === "declined") {
|
|
440
|
+
return { output: (rawOut ?? null) as JsonValue, status: "rejected" };
|
|
441
|
+
}
|
|
442
|
+
|
|
460
443
|
// 字符串输出尝试解析 exit_code(codex shell 结果常见)。
|
|
461
444
|
let parsed: unknown = rawOut;
|
|
462
445
|
if (typeof rawOut === "string") {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// o11y 解析器入口:把各 agent 的原始 transcript 归一成 niceeval StreamEvent[]。
|
|
2
|
-
// 每个解析器都返回同一个 ParsedTranscript
|
|
2
|
+
// 每个解析器都返回同一个 ParsedTranscript 形状。
|
|
3
|
+
//
|
|
4
|
+
// 这里没有「按 agent 名分派」的入口:解析器由 adapter 直连(shared.parseCodex /
|
|
5
|
+
// parseClaudeCode / parseBub),core 不按名字分支。接新 agent = 写新解析器 + adapter
|
|
6
|
+
// 里直接调,不改本文件的任何分派逻辑。
|
|
3
7
|
|
|
4
8
|
import type { StreamEvent, Usage } from "../../types.ts";
|
|
5
9
|
import { parseCodexTranscript, parseCodex } from "./codex.ts";
|
|
@@ -22,16 +26,3 @@ export {
|
|
|
22
26
|
parseBubTranscript,
|
|
23
27
|
parseBub,
|
|
24
28
|
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 按 agent 名分派到对应解析器;认不出来的走通用(bub)兜底。
|
|
28
|
-
* agent 名做包含匹配,兼容 "vercel-ai-gateway/codex"、"claude-code-1m" 之类的变体。
|
|
29
|
-
*/
|
|
30
|
-
export function parseTranscript(raw: string | undefined, agent: string): ParsedTranscript {
|
|
31
|
-
const a = (agent || "").toLowerCase();
|
|
32
|
-
if (a.includes("codex")) return parseCodexTranscript(raw);
|
|
33
|
-
if (a.includes("claude")) return parseClaudeCodeTranscript(raw);
|
|
34
|
-
if (a.includes("bub")) return parseBubTranscript(raw);
|
|
35
|
-
// 默认:通用 JSONL 兜底解析器。
|
|
36
|
-
return parseBubTranscript(raw);
|
|
37
|
-
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// 工具名归一的回归锁:canonical ToolName 是跨 agent 断言(calledTool("file_read"))
|
|
2
|
+
// 的命中基础。这里逐 agent 锁死关键映射,防止基表 / per-agent 差异表重构时悄悄漂移
|
|
3
|
+
// (曾发生:Claude Code 的裸名 Read/Write/Edit 在一次表合并中归一成了 "unknown")。
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { normalizeToolName } from "./tool-names.ts";
|
|
7
|
+
import { CLAUDE_TOOL_ALIASES } from "./parsers/claude-code.ts";
|
|
8
|
+
import { CODEX_TOOL_ALIASES } from "./parsers/codex.ts";
|
|
9
|
+
import { BUB_TOOL_ALIASES } from "./parsers/bub.ts";
|
|
10
|
+
|
|
11
|
+
describe("normalizeToolName", () => {
|
|
12
|
+
it("claude-code:PascalCase 核心工具全部命中", () => {
|
|
13
|
+
const n = (name: string) => normalizeToolName(name, CLAUDE_TOOL_ALIASES);
|
|
14
|
+
expect(n("Read")).toBe("file_read");
|
|
15
|
+
expect(n("Write")).toBe("file_write");
|
|
16
|
+
expect(n("Edit")).toBe("file_edit");
|
|
17
|
+
expect(n("MultiEdit")).toBe("file_edit");
|
|
18
|
+
expect(n("NotebookEdit")).toBe("file_edit");
|
|
19
|
+
expect(n("Bash")).toBe("shell");
|
|
20
|
+
expect(n("BashOutput")).toBe("shell");
|
|
21
|
+
expect(n("WebFetch")).toBe("web_fetch");
|
|
22
|
+
expect(n("WebSearch")).toBe("web_search");
|
|
23
|
+
expect(n("Glob")).toBe("glob");
|
|
24
|
+
expect(n("Grep")).toBe("grep");
|
|
25
|
+
expect(n("LS")).toBe("list_dir");
|
|
26
|
+
expect(n("Task")).toBe("agent_task");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("codex:裸动词与专属名命中", () => {
|
|
30
|
+
const n = (name: string) => normalizeToolName(name, CODEX_TOOL_ALIASES);
|
|
31
|
+
expect(n("exec")).toBe("shell");
|
|
32
|
+
expect(n("local_shell")).toBe("shell");
|
|
33
|
+
expect(n("apply_patch")).toBe("file_edit");
|
|
34
|
+
expect(n("file_change")).toBe("file_edit");
|
|
35
|
+
expect(n("update_plan")).toBe("agent_task");
|
|
36
|
+
expect(n("web_search")).toBe("web_search");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("bub:fs.* 命名空间与裸名命中", () => {
|
|
40
|
+
const n = (name: string) => normalizeToolName(name, BUB_TOOL_ALIASES);
|
|
41
|
+
expect(n("fs.read")).toBe("file_read");
|
|
42
|
+
expect(n("fs_write")).toBe("file_write");
|
|
43
|
+
expect(n("edit")).toBe("file_edit");
|
|
44
|
+
expect(n("bash")).toBe("shell");
|
|
45
|
+
expect(n("update_todos")).toBe("agent_task");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("无 overrides(AI SDK 应用):域内可能撞名的裸动词不误归一", () => {
|
|
49
|
+
// 被测应用完全可能有叫 search / run / fetch / task 的域内工具;
|
|
50
|
+
// 它们必须落 "unknown",否则 notCalledTool("web_search") 这类断言语义被悄悄改写。
|
|
51
|
+
expect(normalizeToolName("search")).toBe("unknown");
|
|
52
|
+
expect(normalizeToolName("run")).toBe("unknown");
|
|
53
|
+
expect(normalizeToolName("fetch")).toBe("unknown");
|
|
54
|
+
expect(normalizeToolName("task")).toBe("unknown");
|
|
55
|
+
expect(normalizeToolName("get_weather")).toBe("unknown");
|
|
56
|
+
// 但明确的复合名照常命中。
|
|
57
|
+
expect(normalizeToolName("read_file")).toBe("file_read");
|
|
58
|
+
expect(normalizeToolName("execute_command")).toBe("shell");
|
|
59
|
+
expect(normalizeToolName("web_search")).toBe("web_search");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// 规范工具名归一的唯一出处。canonical ToolName 是跨 agent 断言(calledTool("file_read"))
|
|
2
|
+
// 的命中基础:基表收「跨 agent 一致」的通用别名,各 parser 只声明自己特有的别名叠加在
|
|
3
|
+
// 基表上——新增一个 canonical 名只改这里,不必同步 4 张漂移的表。
|
|
4
|
+
|
|
5
|
+
import type { ToolName } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 通用别名基表(键一律小写;normalizeToolName 匹配前先 toLowerCase)。
|
|
9
|
+
* 只收「不会撞上被测应用域内工具名」的保守别名(多为带下划线的复合名);
|
|
10
|
+
* `search`/`run`/`fetch` 这类裸动词在 GENERIC_VERB_ALIASES,由确知自己 transcript
|
|
11
|
+
* 词汇的 parser 显式 opt-in —— 否则 AI SDK 应用里一个叫 `search` 的商品搜索工具
|
|
12
|
+
* 会被误归成 web_search,负断言语义被悄悄改写。
|
|
13
|
+
*/
|
|
14
|
+
const BASE_TOOL_ALIASES: Record<string, ToolName> = {
|
|
15
|
+
// 文件
|
|
16
|
+
read_file: "file_read",
|
|
17
|
+
write_file: "file_write",
|
|
18
|
+
create_file: "file_write",
|
|
19
|
+
delete_file: "file_write",
|
|
20
|
+
edit_file: "file_edit",
|
|
21
|
+
patch_file: "file_edit",
|
|
22
|
+
apply_patch: "file_edit",
|
|
23
|
+
str_replace_editor: "file_edit",
|
|
24
|
+
|
|
25
|
+
// shell
|
|
26
|
+
shell: "shell",
|
|
27
|
+
bash: "shell",
|
|
28
|
+
command_execution: "shell",
|
|
29
|
+
local_shell: "shell",
|
|
30
|
+
execute_command: "shell",
|
|
31
|
+
run_command: "shell",
|
|
32
|
+
|
|
33
|
+
// web
|
|
34
|
+
web_fetch: "web_fetch",
|
|
35
|
+
fetch_url: "web_fetch",
|
|
36
|
+
http_request: "web_fetch",
|
|
37
|
+
web_search: "web_search",
|
|
38
|
+
|
|
39
|
+
// 检索 / 导航
|
|
40
|
+
glob: "glob",
|
|
41
|
+
find_files: "glob",
|
|
42
|
+
list_files: "glob",
|
|
43
|
+
grep: "grep",
|
|
44
|
+
search_files: "grep",
|
|
45
|
+
ripgrep: "grep",
|
|
46
|
+
list_dir: "list_dir",
|
|
47
|
+
list_directory: "list_dir",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 裸动词别名:coding-agent CLI 自己的 transcript 词汇(codex/bub/claude 的工具名
|
|
52
|
+
* 由 CLI 控制,不会和用户域名冲突)。对被测应用(如 AI SDK 的自定义工具)不适用。
|
|
53
|
+
*/
|
|
54
|
+
export const GENERIC_VERB_ALIASES: Record<string, ToolName> = {
|
|
55
|
+
exec: "shell",
|
|
56
|
+
execute: "shell",
|
|
57
|
+
run: "shell",
|
|
58
|
+
terminal: "shell",
|
|
59
|
+
fetch: "web_fetch",
|
|
60
|
+
curl: "web_fetch",
|
|
61
|
+
search: "web_search",
|
|
62
|
+
ls: "list_dir",
|
|
63
|
+
dir: "list_dir",
|
|
64
|
+
task: "agent_task",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 原始工具名 → 规范 ToolName。先查 agent 特有别名(overrides),再查通用基表,
|
|
69
|
+
* 都没有则 "unknown"。键匹配不区分大小写(Claude Code 的 PascalCase 名靠这里兜住)。
|
|
70
|
+
*/
|
|
71
|
+
export function normalizeToolName(name: string, overrides?: Record<string, ToolName>): ToolName {
|
|
72
|
+
const key = name.toLowerCase();
|
|
73
|
+
return overrides?.[key] ?? BASE_TOOL_ALIASES[key] ?? "unknown";
|
|
74
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// o11y 域类型:标准事件流(StreamEvent)、折叠事实(DerivedFacts)、
|
|
2
|
+
// OTLP trace(TraceSpan / SpanKind)与用量 / 摘要。
|
|
3
|
+
|
|
4
|
+
import type { JsonValue, SourceLoc } from "../shared/types.ts";
|
|
5
|
+
|
|
6
|
+
/** 一次运行的 token 用量(沙箱型从 transcript 抠,remote 由 send 返回)。 */
|
|
7
|
+
export interface Usage {
|
|
8
|
+
inputTokens: number;
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
cacheReadTokens?: number;
|
|
11
|
+
cacheWriteTokens?: number;
|
|
12
|
+
requests?: number;
|
|
13
|
+
/** 网关实测成本(若 agent 带回)——优先于价格表估算。 */
|
|
14
|
+
costUSD?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 跨 agent 归一化后的规范工具名。 */
|
|
18
|
+
export type ToolName =
|
|
19
|
+
| "file_read"
|
|
20
|
+
| "file_write"
|
|
21
|
+
| "file_edit"
|
|
22
|
+
| "shell"
|
|
23
|
+
| "web_fetch"
|
|
24
|
+
| "web_search"
|
|
25
|
+
| "glob"
|
|
26
|
+
| "grep"
|
|
27
|
+
| "list_dir"
|
|
28
|
+
| "agent_task"
|
|
29
|
+
| "unknown";
|
|
30
|
+
|
|
31
|
+
export interface InputRequest {
|
|
32
|
+
readonly id?: string;
|
|
33
|
+
readonly prompt?: string;
|
|
34
|
+
readonly display?: string;
|
|
35
|
+
readonly action?: string;
|
|
36
|
+
readonly input?: JsonValue;
|
|
37
|
+
readonly options?: readonly { id: string; label?: string }[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 标准事件流的词汇(对标 docs/agents-and-adapters.md)。adapter 唯一的硬活就是把
|
|
42
|
+
* 各 agent 五花八门的原始 transcript 映射成 StreamEvent[];映射完,整套断言免费。
|
|
43
|
+
*/
|
|
44
|
+
export type StreamEvent =
|
|
45
|
+
| { type: "message"; role: "assistant" | "user"; text: string; loc?: SourceLoc }
|
|
46
|
+
| { type: "action.called"; callId: string; name: string; input: JsonValue; tool?: ToolName }
|
|
47
|
+
| {
|
|
48
|
+
type: "action.result";
|
|
49
|
+
callId: string;
|
|
50
|
+
output?: JsonValue;
|
|
51
|
+
status: "completed" | "failed" | "rejected";
|
|
52
|
+
}
|
|
53
|
+
| { type: "subagent.called"; callId: string; name: string; remoteUrl?: string }
|
|
54
|
+
| { type: "subagent.completed"; callId: string; output?: JsonValue; status: "completed" | "failed" }
|
|
55
|
+
| { type: "input.requested"; request: InputRequest }
|
|
56
|
+
| { type: "thinking"; text: string }
|
|
57
|
+
| { type: "compaction"; reason?: string }
|
|
58
|
+
| { type: "error"; message: string };
|
|
59
|
+
|
|
60
|
+
/** core 从事件流折叠出的结构化事实(deriveRunFacts)。 */
|
|
61
|
+
export interface ToolCall {
|
|
62
|
+
callId: string;
|
|
63
|
+
name: ToolName;
|
|
64
|
+
originalName?: string;
|
|
65
|
+
input: JsonValue;
|
|
66
|
+
output?: JsonValue;
|
|
67
|
+
status: "completed" | "failed" | "rejected";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SubagentCall {
|
|
71
|
+
callId: string;
|
|
72
|
+
name: string;
|
|
73
|
+
remoteUrl?: string;
|
|
74
|
+
output?: JsonValue;
|
|
75
|
+
status: "completed" | "failed";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DerivedFacts {
|
|
79
|
+
readonly toolCalls: readonly ToolCall[];
|
|
80
|
+
readonly subagentCalls: readonly SubagentCall[];
|
|
81
|
+
readonly inputRequests: readonly InputRequest[];
|
|
82
|
+
readonly parked: boolean;
|
|
83
|
+
readonly messageCount: number;
|
|
84
|
+
readonly compactions: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* span 的【语义角色】,从 OTel GenAI 语义约定的 gen_ai.operation.name 归一而来
|
|
89
|
+
* (见 o11y/otlp/canonical.ts)。view 据此着色 / 分组 / 跨 agent 对比,**只认这个,
|
|
90
|
+
* 不读原生 span 名**。未识别的 span 落 "other",view 折叠。
|
|
91
|
+
*/
|
|
92
|
+
export type SpanKind = "turn" | "model" | "tool" | "agent" | "other";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 一条分布式追踪的 span(从 agent 经 OpenTelemetry 导出的 OTLP traces 归一而来)。
|
|
96
|
+
* 与 StreamEvent 不同:它带【时间】(起止 epoch 毫秒)与【父子】(parentSpanId),
|
|
97
|
+
* 所以 view 能画成瀑布图。事件流回答「做了什么」,trace 回答「各花了多久、谁套谁」。
|
|
98
|
+
*
|
|
99
|
+
* 两层归一:线格式层(OTLP/JSON|protobuf → 本结构,见 otlp/parse.ts,通用);
|
|
100
|
+
* 语义层(原生 span 名/属性 → canonical GenAI semconv,见 otlp/mappers/<agent>.ts,每 agent 一个薄 mapper)。
|
|
101
|
+
*/
|
|
102
|
+
export interface TraceSpan {
|
|
103
|
+
traceId: string;
|
|
104
|
+
spanId: string;
|
|
105
|
+
parentSpanId?: string;
|
|
106
|
+
name: string;
|
|
107
|
+
/** span 起点 / 终点(epoch 毫秒)。 */
|
|
108
|
+
startMs: number;
|
|
109
|
+
endMs: number;
|
|
110
|
+
status?: "ok" | "error" | "unset";
|
|
111
|
+
/**
|
|
112
|
+
* 归一后的语义角色(每-agent mapper 据 canonical GenAI semconv 定;view/select 只认它)。
|
|
113
|
+
* 未经 mapper 或未识别时为 undefined / "other"。
|
|
114
|
+
*/
|
|
115
|
+
kind?: SpanKind;
|
|
116
|
+
/** OTLP span 属性(gen_ai.* / tool 名 / token 等),按 key 摊平。raw 属性始终保留供下钻。 */
|
|
117
|
+
attributes?: Record<string, JsonValue>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 给人 / 给 EVAL.ts 看的 o11y 摘要(注入沙箱 __niceeval__/results.json)。 */
|
|
121
|
+
export interface O11ySummary {
|
|
122
|
+
totalTurns: number;
|
|
123
|
+
toolCalls: Record<string, number>;
|
|
124
|
+
totalToolCalls: number;
|
|
125
|
+
filesRead: string[];
|
|
126
|
+
filesModified: string[];
|
|
127
|
+
shellCommands: { command: string; exitCode?: number; success?: boolean }[];
|
|
128
|
+
webFetches: { url: string; status?: number; success?: boolean }[];
|
|
129
|
+
errors: string[];
|
|
130
|
+
thinkingBlocks: number;
|
|
131
|
+
compactions: number;
|
|
132
|
+
durationMs: number;
|
|
133
|
+
usage: Usage;
|
|
134
|
+
estimatedCostUSD?: number;
|
|
135
|
+
}
|