jev-layer 0.1.0 → 0.3.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/CHANGELOG.md +73 -34
- package/CONTRIBUTING.md +146 -146
- package/LICENSE +21 -21
- package/README.md +125 -126
- package/README.ru.md +140 -117
- package/README.zh-CN.md +139 -117
- package/RELEASE.md +53 -53
- package/SECURITY.md +64 -56
- package/bin/jev.mjs +215 -213
- package/config/codex.mcp.toml +8 -8
- package/config/generic-mcp.json +13 -13
- package/config/hermes.mcp.yaml +9 -9
- package/config/jev.example.json +23 -22
- package/config/omp.mcp.json +14 -14
- package/config/providers.env.example +13 -13
- package/docs/AGENT-IMPLEMENTATION.md +306 -0
- package/docs/COMPATIBILITY.md +24 -15
- package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +187 -0
- package/docs/PROVIDERS.md +109 -0
- package/docs/SCHEMA-VERSIONING.md +101 -94
- package/docs/architecture.svg +77 -0
- package/docs/jev-layer-banner.svg +38 -0
- package/examples/capabilities.json +39 -39
- package/examples/route-request.json +28 -28
- package/integrations/codex/.codex-plugin/plugin.json +19 -19
- package/integrations/codex/.mcp.json +11 -11
- package/integrations/codex/AGENTS.md +1 -1
- package/integrations/codex/run-mcp.mjs +9 -9
- package/integrations/codex/skills/jev-route/SKILL.md +17 -17
- package/integrations/hermes/__init__.py +156 -51
- package/integrations/hermes/plugin.yaml +11 -5
- package/integrations/hermes/schemas.py +120 -12
- package/integrations/omp/extension.js +106 -105
- package/integrations/template/README.md +10 -10
- package/integrations/template/adapter.mjs +87 -87
- package/package.json +60 -59
- package/scripts/benchmark.mjs +35 -35
- package/scripts/browser-benchmark.mjs +92 -92
- package/scripts/browser-e2e.mjs +117 -117
- package/scripts/capability-e2e.mjs +31 -31
- package/scripts/clean-install-smoke.mjs +176 -162
- package/scripts/codex-mcp-smoke.mjs +119 -119
- package/scripts/context-filter-e2e.mjs +45 -45
- package/scripts/fail-open-smoke.mjs +120 -120
- package/scripts/feature-flags-smoke.mjs +46 -46
- package/scripts/mcp-receipt-smoke.mjs +115 -98
- package/scripts/model-route-report.mjs +15 -0
- package/scripts/openrouter-choice.mjs +59 -59
- package/scripts/replay-eval.mjs +124 -124
- package/scripts/smoke.mjs +34 -34
- package/scripts/supervision-e2e.mjs +138 -109
- package/src/browser.mjs +583 -569
- package/src/cli.mjs +33 -33
- package/src/computer.mjs +196 -0
- package/src/config.mjs +86 -75
- package/src/context-filter.mjs +56 -56
- package/src/contract.mjs +74 -72
- package/src/discovery.mjs +65 -65
- package/src/hermes-adapter.mjs +134 -0
- package/src/images.mjs +98 -0
- package/src/mcp-server.mjs +313 -210
- package/src/model-route-metrics.mjs +89 -0
- package/src/model-routing.mjs +87 -0
- package/src/providers/demo.mjs +52 -52
- package/src/providers/index.mjs +12 -0
- package/src/providers/typesafe.mjs +206 -126
- package/src/receipts.mjs +228 -226
- package/src/registry.mjs +109 -109
- package/src/relevance-filter.mjs +99 -99
- package/src/route.mjs +228 -221
- package/src/shadow-compaction.mjs +170 -0
- package/src/supervision.mjs +268 -244
- package/test/browser.test.mjs +222 -199
- package/test/capability.test.mjs +54 -54
- package/test/computer.test.mjs +195 -0
- package/test/context-filter.test.mjs +65 -65
- package/test/hermes-adapter.test.mjs +63 -0
- package/test/image-fixture.mjs +2 -0
- package/test/images.test.mjs +85 -0
- package/test/model-route-metrics.test.mjs +61 -0
- package/test/model-routing.test.mjs +59 -0
- package/test/openrouter-provider.test.mjs +55 -55
- package/test/provider-transports.test.mjs +205 -0
- package/test/providers.test.mjs +125 -0
- package/test/receipts.test.mjs +71 -71
- package/test/route.test.mjs +86 -74
- package/test/shadow-compaction.test.mjs +67 -0
- package/test/supervision.test.mjs +117 -99
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { buildModelRouteReport } from "../src/model-route-metrics.mjs";
|
|
4
|
+
|
|
5
|
+
const path = process.argv[2] ?? process.env.JEV_REPLAY_CASES ?? ".jev/replay/cases.jsonl";
|
|
6
|
+
let text = "";
|
|
7
|
+
try {
|
|
8
|
+
text = await readFile(path, "utf8");
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error?.code !== "ENOENT") throw error;
|
|
11
|
+
}
|
|
12
|
+
const records = text.split(/\r?\n/).filter(Boolean).flatMap((line) => {
|
|
13
|
+
try { return [JSON.parse(line)]; } catch { return []; }
|
|
14
|
+
});
|
|
15
|
+
console.log(JSON.stringify(buildModelRouteReport(records), null, 2));
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { routeRequest } from "../src/route.mjs";
|
|
2
|
-
|
|
3
|
-
const endpoint = process.env.OPENROUTER_DECISIONS_ENDPOINT ?? "https://openrouter.ai/api/alpha/decisions";
|
|
4
|
-
const model = process.env.OPENROUTER_DECISIONS_MODEL ?? "typesafe/jev-1.
|
|
5
|
-
const decision = await routeRequest({
|
|
6
|
-
schema_version: 1,
|
|
7
|
-
harness: "jev-layer-openrouter-smoke",
|
|
8
|
-
intent: "Find where the OpenRouter Decisions provider is implemented without modifying files",
|
|
9
|
-
context: {
|
|
10
|
-
purpose: "one real bounded Choice call",
|
|
11
|
-
secret_note: "must be redacted before provider transmission",
|
|
12
|
-
},
|
|
13
|
-
actor_permissions: ["read", "browser", "terminal:read"],
|
|
14
|
-
capabilities: [
|
|
15
|
-
{
|
|
16
|
-
id: "repo_search",
|
|
17
|
-
kind: "mcp",
|
|
18
|
-
name: "Repository search",
|
|
19
|
-
description: "Search repository files and symbols without changing files",
|
|
20
|
-
permissions: ["read"],
|
|
21
|
-
risk: "low",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: "browser_inspect",
|
|
25
|
-
kind: "mcp",
|
|
26
|
-
name: "Browser inspection",
|
|
27
|
-
description: "Open a webpage and inspect visible browser state",
|
|
28
|
-
permissions: ["browser"],
|
|
29
|
-
risk: "medium",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "terminal_read",
|
|
33
|
-
kind: "cli",
|
|
34
|
-
name: "Read-only terminal",
|
|
35
|
-
description: "Run a read-only shell command and return its output",
|
|
36
|
-
permissions: ["terminal:read"],
|
|
37
|
-
risk: "low",
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
}, {
|
|
41
|
-
provider: "openrouter",
|
|
42
|
-
openrouter: { endpoint, model, timeoutMs: 15_000 },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const raw = decision.raw_jev ?? {};
|
|
46
|
-
const usage = raw.usage && typeof raw.usage === "object" ? raw.usage : {};
|
|
47
|
-
console.log(JSON.stringify({
|
|
48
|
-
request: { endpoint, model, method: "POST" },
|
|
49
|
-
response: { id: raw.id ?? null, model: raw.model ?? null, provider: raw.provider ?? null },
|
|
50
|
-
status: decision.status,
|
|
51
|
-
selected: decision.selected,
|
|
52
|
-
probabilities: decision.probabilities,
|
|
53
|
-
confidence: decision.confidence,
|
|
54
|
-
latency_ms: decision.receipt.latency_ms,
|
|
55
|
-
cost_usd: typeof usage.cost === "number" ? usage.cost : null,
|
|
56
|
-
usage,
|
|
57
|
-
fallback: decision.fallback,
|
|
58
|
-
execution: decision.execution,
|
|
59
|
-
}, null, 2));
|
|
1
|
+
import { routeRequest } from "../src/route.mjs";
|
|
2
|
+
|
|
3
|
+
const endpoint = process.env.OPENROUTER_DECISIONS_ENDPOINT ?? "https://openrouter.ai/api/alpha/decisions";
|
|
4
|
+
const model = process.env.OPENROUTER_DECISIONS_MODEL ?? "typesafe/jev-1.14";
|
|
5
|
+
const decision = await routeRequest({
|
|
6
|
+
schema_version: 1,
|
|
7
|
+
harness: "jev-layer-openrouter-smoke",
|
|
8
|
+
intent: "Find where the OpenRouter Decisions provider is implemented without modifying files",
|
|
9
|
+
context: {
|
|
10
|
+
purpose: "one real bounded Choice call",
|
|
11
|
+
secret_note: "must be redacted before provider transmission",
|
|
12
|
+
},
|
|
13
|
+
actor_permissions: ["read", "browser", "terminal:read"],
|
|
14
|
+
capabilities: [
|
|
15
|
+
{
|
|
16
|
+
id: "repo_search",
|
|
17
|
+
kind: "mcp",
|
|
18
|
+
name: "Repository search",
|
|
19
|
+
description: "Search repository files and symbols without changing files",
|
|
20
|
+
permissions: ["read"],
|
|
21
|
+
risk: "low",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "browser_inspect",
|
|
25
|
+
kind: "mcp",
|
|
26
|
+
name: "Browser inspection",
|
|
27
|
+
description: "Open a webpage and inspect visible browser state",
|
|
28
|
+
permissions: ["browser"],
|
|
29
|
+
risk: "medium",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "terminal_read",
|
|
33
|
+
kind: "cli",
|
|
34
|
+
name: "Read-only terminal",
|
|
35
|
+
description: "Run a read-only shell command and return its output",
|
|
36
|
+
permissions: ["terminal:read"],
|
|
37
|
+
risk: "low",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
}, {
|
|
41
|
+
provider: "openrouter",
|
|
42
|
+
openrouter: { endpoint, model, timeoutMs: 15_000 },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const raw = decision.raw_jev ?? {};
|
|
46
|
+
const usage = raw.usage && typeof raw.usage === "object" ? raw.usage : {};
|
|
47
|
+
console.log(JSON.stringify({
|
|
48
|
+
request: { endpoint, model, method: "POST" },
|
|
49
|
+
response: { id: raw.id ?? null, model: raw.model ?? null, provider: raw.provider ?? null },
|
|
50
|
+
status: decision.status,
|
|
51
|
+
selected: decision.selected,
|
|
52
|
+
probabilities: decision.probabilities,
|
|
53
|
+
confidence: decision.confidence,
|
|
54
|
+
latency_ms: decision.receipt.latency_ms,
|
|
55
|
+
cost_usd: typeof usage.cost === "number" ? usage.cost : null,
|
|
56
|
+
usage,
|
|
57
|
+
fallback: decision.fallback,
|
|
58
|
+
execution: decision.execution,
|
|
59
|
+
}, null, 2));
|
package/scripts/replay-eval.mjs
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { readRoutingCases, readSupervisionCases, replayCasePath } from "../src/receipts.mjs";
|
|
4
|
-
import { deterministicSupervisionPolicy } from "../src/supervision.mjs";
|
|
5
|
-
import { routeRequest } from "../src/route.mjs";
|
|
6
|
-
|
|
7
|
-
const args = parseArgs(process.argv.slice(2));
|
|
8
|
-
const inputPath = replayCasePath(args.input);
|
|
9
|
-
const provider = args.provider ?? process.env.JEV_REPLAY_PROVIDER ?? "demo";
|
|
10
|
-
const allCases = await readRoutingCases(inputPath);
|
|
11
|
-
const cases = args.limit ? allCases.slice(0, args.limit) : allCases;
|
|
12
|
-
const results = [];
|
|
13
|
-
|
|
14
|
-
for (const routingCase of cases) {
|
|
15
|
-
const decision = await routeRequest(routingCase.request, { provider });
|
|
16
|
-
if (decision.execution?.enabled) throw new Error(`replay attempted host execution for ${routingCase.case_id}`);
|
|
17
|
-
results.push({
|
|
18
|
-
source_case_id: routingCase.case_id,
|
|
19
|
-
source_correlation_id: routingCase.correlation_id,
|
|
20
|
-
replay_correlation_id: decision.correlation_id,
|
|
21
|
-
surface: routingCase.request.context?.browser ? "browser" : "generic",
|
|
22
|
-
harness: routingCase.harness,
|
|
23
|
-
selected: decision.selected,
|
|
24
|
-
confidence: decision.confidence,
|
|
25
|
-
status: decision.status,
|
|
26
|
-
fallback: decision.fallback?.type ?? null,
|
|
27
|
-
latency_ms: decision.receipt?.latency_ms ?? null,
|
|
28
|
-
cost_usd: decision.receipt?.cost_usd ?? decision.raw_jev?.usage?.cost ?? null,
|
|
29
|
-
execution: decision.execution,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const supervisionCases = args.limit
|
|
34
|
-
? (await readSupervisionCases(inputPath)).slice(0, args.limit)
|
|
35
|
-
: await readSupervisionCases(inputPath);
|
|
36
|
-
const supervisionResults = supervisionCases.map((record) => ({
|
|
37
|
-
source_case_id: record.case_id,
|
|
38
|
-
source_correlation_id: record.correlation_id,
|
|
39
|
-
harness: record.harness,
|
|
40
|
-
assessment: record.supervision.assessment,
|
|
41
|
-
recorded_action: record.supervision.action,
|
|
42
|
-
replayed_policy: deterministicSupervisionPolicy({
|
|
43
|
-
assessment: record.supervision.assessment,
|
|
44
|
-
evidence: record.request.context?.evidence ?? {},
|
|
45
|
-
}),
|
|
46
|
-
host_execution: false,
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const summary = summarize(results);
|
|
51
|
-
summary.supervision_case_count = supervisionResults.length;
|
|
52
|
-
summary.supervision_policy_match_rate = rate(
|
|
53
|
-
supervisionResults.filter((result) => result.replayed_policy.action === result.recorded_action).length,
|
|
54
|
-
supervisionResults.length,
|
|
55
|
-
);
|
|
56
|
-
const report = {
|
|
57
|
-
ok: true,
|
|
58
|
-
source: inputPath,
|
|
59
|
-
provider,
|
|
60
|
-
replayed_cases: results.length,
|
|
61
|
-
replayed_supervision_cases: supervisionResults.length,
|
|
62
|
-
host_execution_calls: 0,
|
|
63
|
-
summary,
|
|
64
|
-
cases: results,
|
|
65
|
-
supervision_cases: supervisionResults,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
if (args.output) {
|
|
69
|
-
await mkdir(dirname(args.output), { recursive: true });
|
|
70
|
-
await writeFile(args.output, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
71
|
-
}
|
|
72
|
-
console.log(JSON.stringify(report, null, 2));
|
|
73
|
-
function summarize(results) {
|
|
74
|
-
const total = results.length;
|
|
75
|
-
const fallbackCount = results.filter((result) => result.status === "fallback").length;
|
|
76
|
-
const selectedCount = results.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length;
|
|
77
|
-
const noDecisionCount = results.filter((result) => result.status === "no_decision").length;
|
|
78
|
-
const browserResults = results.filter((result) => result.surface === "browser");
|
|
79
|
-
const confidences = results.map((result) => result.confidence).filter((value) => typeof value === "number");
|
|
80
|
-
const latencies = results.map((result) => result.latency_ms).filter((value) => typeof value === "number");
|
|
81
|
-
const costs = results.map((result) => result.cost_usd).filter((value) => typeof value === "number");
|
|
82
|
-
return {
|
|
83
|
-
selected_rate: rate(selectedCount, total),
|
|
84
|
-
fallback_rate: rate(fallbackCount, total),
|
|
85
|
-
no_decision_rate: rate(noDecisionCount, total),
|
|
86
|
-
browser_case_count: browserResults.length,
|
|
87
|
-
browser_selected_rate: rate(browserResults.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length, browserResults.length),
|
|
88
|
-
average_confidence: average(confidences),
|
|
89
|
-
average_latency_ms: average(latencies),
|
|
90
|
-
p50_latency_ms: percentile(latencies, 0.5),
|
|
91
|
-
p95_latency_ms: percentile(latencies, 0.95),
|
|
92
|
-
total_cost_usd: Number(costs.reduce((sum, value) => sum + value, 0).toFixed(9)),
|
|
93
|
-
status_counts: Object.fromEntries([...new Set(results.map((result) => result.status))].map((status) => [status, results.filter((result) => result.status === status).length])),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function parseArgs(argv) {
|
|
98
|
-
const result = {};
|
|
99
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
100
|
-
const value = argv[index];
|
|
101
|
-
if (value === "--input") result.input = argv[++index];
|
|
102
|
-
else if (value === "--provider") result.provider = argv[++index];
|
|
103
|
-
else if (value === "--limit") result.limit = Number(argv[++index]);
|
|
104
|
-
else if (value === "--output") result.output = argv[++index];
|
|
105
|
-
}
|
|
106
|
-
if (result.limit !== undefined && (!Number.isInteger(result.limit) || result.limit < 0)) throw new Error("--limit must be a non-negative integer");
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function average(values) {
|
|
111
|
-
return values.length ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(3)) : null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function percentile(values, fraction) {
|
|
115
|
-
if (!values.length) return null;
|
|
116
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
117
|
-
const rank = Math.max(0, Math.ceil(sorted.length * fraction) - 1);
|
|
118
|
-
return Number(sorted[Math.min(sorted.length - 1, rank)].toFixed(3));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
function rate(part, total) {
|
|
123
|
-
return total ? Number((part / total).toFixed(4)) : 0;
|
|
124
|
-
}
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { readRoutingCases, readSupervisionCases, replayCasePath } from "../src/receipts.mjs";
|
|
4
|
+
import { deterministicSupervisionPolicy } from "../src/supervision.mjs";
|
|
5
|
+
import { routeRequest } from "../src/route.mjs";
|
|
6
|
+
|
|
7
|
+
const args = parseArgs(process.argv.slice(2));
|
|
8
|
+
const inputPath = replayCasePath(args.input);
|
|
9
|
+
const provider = args.provider ?? process.env.JEV_REPLAY_PROVIDER ?? "demo";
|
|
10
|
+
const allCases = await readRoutingCases(inputPath);
|
|
11
|
+
const cases = args.limit ? allCases.slice(0, args.limit) : allCases;
|
|
12
|
+
const results = [];
|
|
13
|
+
|
|
14
|
+
for (const routingCase of cases) {
|
|
15
|
+
const decision = await routeRequest(routingCase.request, { provider });
|
|
16
|
+
if (decision.execution?.enabled) throw new Error(`replay attempted host execution for ${routingCase.case_id}`);
|
|
17
|
+
results.push({
|
|
18
|
+
source_case_id: routingCase.case_id,
|
|
19
|
+
source_correlation_id: routingCase.correlation_id,
|
|
20
|
+
replay_correlation_id: decision.correlation_id,
|
|
21
|
+
surface: routingCase.request.context?.browser ? "browser" : "generic",
|
|
22
|
+
harness: routingCase.harness,
|
|
23
|
+
selected: decision.selected,
|
|
24
|
+
confidence: decision.confidence,
|
|
25
|
+
status: decision.status,
|
|
26
|
+
fallback: decision.fallback?.type ?? null,
|
|
27
|
+
latency_ms: decision.receipt?.latency_ms ?? null,
|
|
28
|
+
cost_usd: decision.receipt?.cost_usd ?? decision.raw_jev?.usage?.cost ?? null,
|
|
29
|
+
execution: decision.execution,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const supervisionCases = args.limit
|
|
34
|
+
? (await readSupervisionCases(inputPath)).slice(0, args.limit)
|
|
35
|
+
: await readSupervisionCases(inputPath);
|
|
36
|
+
const supervisionResults = supervisionCases.map((record) => ({
|
|
37
|
+
source_case_id: record.case_id,
|
|
38
|
+
source_correlation_id: record.correlation_id,
|
|
39
|
+
harness: record.harness,
|
|
40
|
+
assessment: record.supervision.assessment,
|
|
41
|
+
recorded_action: record.supervision.action,
|
|
42
|
+
replayed_policy: deterministicSupervisionPolicy({
|
|
43
|
+
assessment: record.supervision.assessment,
|
|
44
|
+
evidence: record.request.context?.evidence ?? {},
|
|
45
|
+
}),
|
|
46
|
+
host_execution: false,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const summary = summarize(results);
|
|
51
|
+
summary.supervision_case_count = supervisionResults.length;
|
|
52
|
+
summary.supervision_policy_match_rate = rate(
|
|
53
|
+
supervisionResults.filter((result) => result.replayed_policy.action === result.recorded_action).length,
|
|
54
|
+
supervisionResults.length,
|
|
55
|
+
);
|
|
56
|
+
const report = {
|
|
57
|
+
ok: true,
|
|
58
|
+
source: inputPath,
|
|
59
|
+
provider,
|
|
60
|
+
replayed_cases: results.length,
|
|
61
|
+
replayed_supervision_cases: supervisionResults.length,
|
|
62
|
+
host_execution_calls: 0,
|
|
63
|
+
summary,
|
|
64
|
+
cases: results,
|
|
65
|
+
supervision_cases: supervisionResults,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (args.output) {
|
|
69
|
+
await mkdir(dirname(args.output), { recursive: true });
|
|
70
|
+
await writeFile(args.output, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
71
|
+
}
|
|
72
|
+
console.log(JSON.stringify(report, null, 2));
|
|
73
|
+
function summarize(results) {
|
|
74
|
+
const total = results.length;
|
|
75
|
+
const fallbackCount = results.filter((result) => result.status === "fallback").length;
|
|
76
|
+
const selectedCount = results.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length;
|
|
77
|
+
const noDecisionCount = results.filter((result) => result.status === "no_decision").length;
|
|
78
|
+
const browserResults = results.filter((result) => result.surface === "browser");
|
|
79
|
+
const confidences = results.map((result) => result.confidence).filter((value) => typeof value === "number");
|
|
80
|
+
const latencies = results.map((result) => result.latency_ms).filter((value) => typeof value === "number");
|
|
81
|
+
const costs = results.map((result) => result.cost_usd).filter((value) => typeof value === "number");
|
|
82
|
+
return {
|
|
83
|
+
selected_rate: rate(selectedCount, total),
|
|
84
|
+
fallback_rate: rate(fallbackCount, total),
|
|
85
|
+
no_decision_rate: rate(noDecisionCount, total),
|
|
86
|
+
browser_case_count: browserResults.length,
|
|
87
|
+
browser_selected_rate: rate(browserResults.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length, browserResults.length),
|
|
88
|
+
average_confidence: average(confidences),
|
|
89
|
+
average_latency_ms: average(latencies),
|
|
90
|
+
p50_latency_ms: percentile(latencies, 0.5),
|
|
91
|
+
p95_latency_ms: percentile(latencies, 0.95),
|
|
92
|
+
total_cost_usd: Number(costs.reduce((sum, value) => sum + value, 0).toFixed(9)),
|
|
93
|
+
status_counts: Object.fromEntries([...new Set(results.map((result) => result.status))].map((status) => [status, results.filter((result) => result.status === status).length])),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseArgs(argv) {
|
|
98
|
+
const result = {};
|
|
99
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
100
|
+
const value = argv[index];
|
|
101
|
+
if (value === "--input") result.input = argv[++index];
|
|
102
|
+
else if (value === "--provider") result.provider = argv[++index];
|
|
103
|
+
else if (value === "--limit") result.limit = Number(argv[++index]);
|
|
104
|
+
else if (value === "--output") result.output = argv[++index];
|
|
105
|
+
}
|
|
106
|
+
if (result.limit !== undefined && (!Number.isInteger(result.limit) || result.limit < 0)) throw new Error("--limit must be a non-negative integer");
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function average(values) {
|
|
111
|
+
return values.length ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(3)) : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function percentile(values, fraction) {
|
|
115
|
+
if (!values.length) return null;
|
|
116
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
117
|
+
const rank = Math.max(0, Math.ceil(sorted.length * fraction) - 1);
|
|
118
|
+
return Number(sorted[Math.min(sorted.length - 1, rank)].toFixed(3));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function rate(part, total) {
|
|
123
|
+
return total ? Number((part / total).toFixed(4)) : 0;
|
|
124
|
+
}
|
package/scripts/smoke.mjs
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { routeRequest } from "../src/route.mjs";
|
|
4
|
-
|
|
5
|
-
const request = JSON.parse(await readFile(new URL("../examples/route-request.json", import.meta.url), "utf8"));
|
|
6
|
-
const selected = await routeRequest(request, { provider: "demo" });
|
|
7
|
-
assert.equal(selected.status, "selected");
|
|
8
|
-
assert.equal(selected.selected, "repo_search");
|
|
9
|
-
assert.equal(selected.execution.enabled, false);
|
|
10
|
-
assert.equal(selected.execution.status, "not_started");
|
|
11
|
-
assert.ok(selected.receipt.context_bytes > 0);
|
|
12
|
-
assert.equal(JSON.stringify(selected.raw_jev).includes("must-not-reach-provider"), false);
|
|
13
|
-
|
|
14
|
-
const unavailable = await routeRequest({
|
|
15
|
-
...request,
|
|
16
|
-
intent: "Modify a file",
|
|
17
|
-
capabilities: request.capabilities.map((capability) => ({ ...capability, availability: capability.id === "repo_search" ? { available: false, reason: "offline" } : undefined })),
|
|
18
|
-
}, { provider: "demo" });
|
|
19
|
-
assert.equal(unavailable.status, "no_decision");
|
|
20
|
-
|
|
21
|
-
const providerFailure = await routeRequest(request, {
|
|
22
|
-
provider: { name: "failing-test-provider", async decide() { throw new Error("synthetic provider failure"); } },
|
|
23
|
-
});
|
|
24
|
-
assert.equal(providerFailure.status, "fallback");
|
|
25
|
-
assert.equal(providerFailure.fallback.type, "provider_error");
|
|
26
|
-
assert.equal(providerFailure.execution.enabled, false);
|
|
27
|
-
|
|
28
|
-
console.log(JSON.stringify({
|
|
29
|
-
ok: true,
|
|
30
|
-
selected: selected.selected,
|
|
31
|
-
unavailable_status: unavailable.status,
|
|
32
|
-
provider_failure: providerFailure.fallback.type,
|
|
33
|
-
execution_started: selected.execution.enabled,
|
|
34
|
-
}));
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { routeRequest } from "../src/route.mjs";
|
|
4
|
+
|
|
5
|
+
const request = JSON.parse(await readFile(new URL("../examples/route-request.json", import.meta.url), "utf8"));
|
|
6
|
+
const selected = await routeRequest(request, { provider: "demo" });
|
|
7
|
+
assert.equal(selected.status, "selected");
|
|
8
|
+
assert.equal(selected.selected, "repo_search");
|
|
9
|
+
assert.equal(selected.execution.enabled, false);
|
|
10
|
+
assert.equal(selected.execution.status, "not_started");
|
|
11
|
+
assert.ok(selected.receipt.context_bytes > 0);
|
|
12
|
+
assert.equal(JSON.stringify(selected.raw_jev).includes("must-not-reach-provider"), false);
|
|
13
|
+
|
|
14
|
+
const unavailable = await routeRequest({
|
|
15
|
+
...request,
|
|
16
|
+
intent: "Modify a file",
|
|
17
|
+
capabilities: request.capabilities.map((capability) => ({ ...capability, availability: capability.id === "repo_search" ? { available: false, reason: "offline" } : undefined })),
|
|
18
|
+
}, { provider: "demo" });
|
|
19
|
+
assert.equal(unavailable.status, "no_decision");
|
|
20
|
+
|
|
21
|
+
const providerFailure = await routeRequest(request, {
|
|
22
|
+
provider: { name: "failing-test-provider", async decide() { throw new Error("synthetic provider failure"); } },
|
|
23
|
+
});
|
|
24
|
+
assert.equal(providerFailure.status, "fallback");
|
|
25
|
+
assert.equal(providerFailure.fallback.type, "provider_error");
|
|
26
|
+
assert.equal(providerFailure.execution.enabled, false);
|
|
27
|
+
|
|
28
|
+
console.log(JSON.stringify({
|
|
29
|
+
ok: true,
|
|
30
|
+
selected: selected.selected,
|
|
31
|
+
unavailable_status: unavailable.status,
|
|
32
|
+
provider_failure: providerFailure.fallback.type,
|
|
33
|
+
execution_started: selected.execution.enabled,
|
|
34
|
+
}));
|