gentle-pi 2.6.0 → 2.6.1
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 +4 -4
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +8 -6
- package/extensions/gentle-agents.ts +0 -4
- package/extensions/runtime-metrics.ts +10 -19
- package/lib/native-review-cli.ts +5 -0
- package/lib/runtime-metrics-children.ts +3 -5
- package/lib/runtime-metrics-native.ts +17 -9
- package/lib/runtime-metrics.ts +95 -42
- package/package.json +1 -1
- package/runtime/native-review-cli.mjs +5 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/types-baseline.json +1 -5
- package/scripts/verify-package-files.mjs +3 -3
- package/tests/fixtures/runtime-metrics-native-batches.json +1 -1
- package/tests/gentle-agents.test.ts +0 -6
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/native-review-capability-contract.test.ts +12 -1
- package/tests/package-manifest.test.ts +6 -6
- package/tests/runtime-metrics-children.test.ts +4 -3
- package/tests/runtime-metrics-extension.test.ts +40 -31
- package/tests/runtime-metrics-model.test.ts +76 -0
- package/tests/runtime-metrics-native.test.ts +73 -7
- package/tests/runtime-metrics.test.ts +22 -19
- package/lib/runtime-metrics-pi-identity.ts +0 -113
- package/tests/runtime-metrics-pi-identity.test.ts +0 -113
|
@@ -2,14 +2,12 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import runtimeMetrics from "../extensions/runtime-metrics.ts";
|
|
4
4
|
import { parseAgentClass, type RuntimeMetricBucket } from "../lib/runtime-metrics.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { encodeNativeRuntimeEvent } from "../lib/runtime-metrics-native.ts";
|
|
6
6
|
import { CHILD_METRICS_EVENT, childEvent } from "../lib/runtime-metrics-children.ts";
|
|
7
7
|
import { normalizeRpcEvent, TASK_EVENT } from "../lib/agents-protocol.ts";
|
|
8
8
|
|
|
9
9
|
const tick = () => new Promise<void>(resolve => setImmediate(resolve));
|
|
10
|
-
|
|
11
|
-
function harness(env: NodeJS.ProcessEnv = {}, lookup?: CatalogLookup,
|
|
12
|
-
classify?: (input: unknown) => PiCatalogName, mode: "tui" | "print" = "tui", shutdownWaitMs = 1500) {
|
|
10
|
+
function harness(env: NodeJS.ProcessEnv = {}, mode: "tui" | "print" = "tui", shutdownWaitMs = 1500, model: unknown = { provider: "openai", id: "gpt-4o" }) {
|
|
13
11
|
const handlers = new Map<string, Function>();
|
|
14
12
|
const listeners = new Map<string, Function>();
|
|
15
13
|
let session = "first";
|
|
@@ -17,13 +15,13 @@ function harness(env: NodeJS.ProcessEnv = {}, lookup?: CatalogLookup,
|
|
|
17
15
|
let signal!: AbortSignal;
|
|
18
16
|
const sent: RuntimeMetricBucket[][] = [];
|
|
19
17
|
const launches: unknown[] = [];
|
|
20
|
-
const ctx: any = { cwd: "/fixture", mode, model
|
|
18
|
+
const ctx: any = { cwd: "/fixture", mode, model,
|
|
21
19
|
sessionManager: { getSessionId: () => session, getEntries: () => assert.fail("no reconstruction") } };
|
|
22
20
|
const pi: any = { on: (name: string, handler: Function) => handlers.set(name, handler),
|
|
23
21
|
getThinkingLevel: () => "high", registerCommand() {},
|
|
24
22
|
appendEntry: () => assert.fail("no metrics persistence"),
|
|
25
23
|
events: { on: (name: string, handler: Function) => { listeners.set(name, handler); return () => listeners.delete(name); } } };
|
|
26
|
-
runtimeMetrics(pi, env, {
|
|
24
|
+
runtimeMetrics(pi, env, { now: () => 0, shutdownWaitMs, send: (rows, _cwd, deps) => {
|
|
27
25
|
sent.push(structuredClone(rows)); launches.push(structuredClone(deps?.launches)); signal = deps!.signal!;
|
|
28
26
|
return new Promise(resolve => { finish = () => resolve("discarded"); });
|
|
29
27
|
} });
|
|
@@ -38,30 +36,41 @@ const final = (input = 7) => ({ role: "assistant", provider: "openai", model: "g
|
|
|
38
36
|
providerThinkingLevel: "low", stopReason: "stop", usage: { input, output: 3, cacheRead: 0 },
|
|
39
37
|
content: [{ text: "private response" }], errorMessage: "private error", path: "/private" });
|
|
40
38
|
|
|
41
|
-
test("
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (calls === 1) throw new Error("transient catalog failure");
|
|
48
|
-
return catalog;
|
|
49
|
-
});
|
|
50
|
-
const h = harness({}, catalogLookup.lookup, catalogLookup.classify);
|
|
51
|
-
h.emit("session_start");
|
|
52
|
-
await tick();
|
|
53
|
-
assert.equal(calls, 1);
|
|
54
|
-
assert.equal(h.emit("message_end", { message: final() }), undefined);
|
|
55
|
-
assert.equal(calls, 2);
|
|
39
|
+
test("a generic open-weight selection reaches the wire by name; no catalog probe or gate", async () => {
|
|
40
|
+
const h = harness({}, "tui", 1500, { provider: "nan", id: "deepseek-v4-flash" });
|
|
41
|
+
await h.emit("session_start");
|
|
42
|
+
h.emit("turn_start"); h.emit("before_provider_request");
|
|
43
|
+
h.emit("message_end", { message: { role: "assistant", provider: "nan", model: "deepseek-v4-flash",
|
|
44
|
+
providerThinkingLevel: "low", stopReason: "stop", usage: { input: 3, output: 2 } } });
|
|
56
45
|
await tick();
|
|
57
46
|
assert.equal(h.sent.length, 1);
|
|
58
|
-
assert.equal(h.sent[0][0].
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
assert.equal(h.sent[0][0].selectedProvider, "nan");
|
|
48
|
+
assert.equal(h.sent[0][0].selectedModelId, "deepseek-v4-flash");
|
|
49
|
+
const encoded = encodeNativeRuntimeEvent(h.sent[0]);
|
|
50
|
+
assert.ok(encoded);
|
|
51
|
+
const row = JSON.parse(encoded).rows[0];
|
|
52
|
+
assert.deepEqual(row.model, { provider: "nan", id: "deepseek-v4-flash" });
|
|
53
|
+
assert.equal(row.model_evidence, "selected");
|
|
54
|
+
await h.finish(); h.emit("session_shutdown");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("an ambiguous turn with no captured selection still reports the observed model, as selected evidence", async () => {
|
|
58
|
+
const h = harness({}, "tui", 1500, { provider: "nan", id: "deepseek-v4-flash" });
|
|
59
|
+
await h.emit("session_start");
|
|
60
|
+
// No turn_start/before_provider_request: the turn is ambiguous, so no
|
|
61
|
+
// selection was captured for this response.
|
|
62
|
+
h.emit("message_end", { message: { role: "assistant", provider: "nan", model: "glm5.3",
|
|
63
|
+
providerThinkingLevel: "low", stopReason: "stop", usage: { input: 3, output: 2 } } });
|
|
61
64
|
await tick();
|
|
62
|
-
assert.equal(h.sent
|
|
63
|
-
|
|
64
|
-
h.
|
|
65
|
+
assert.equal(h.sent.length, 1);
|
|
66
|
+
assert.equal(h.sent[0][0].selectedModelId, "unknown");
|
|
67
|
+
assert.equal(h.sent[0][0].observedModelId, "glm5.3");
|
|
68
|
+
const encoded = encodeNativeRuntimeEvent(h.sent[0]);
|
|
69
|
+
assert.ok(encoded);
|
|
70
|
+
const row = JSON.parse(encoded).rows[0];
|
|
71
|
+
assert.deepEqual(row.model, { provider: "nan", id: "glm5.3" });
|
|
72
|
+
assert.equal(row.model_evidence, "selected");
|
|
73
|
+
await h.finish(); h.emit("session_shutdown");
|
|
65
74
|
});
|
|
66
75
|
|
|
67
76
|
test("final callback returns before blocked transport; busy and duplicate messages drop", async () => {
|
|
@@ -97,7 +106,7 @@ test("replacement before launch cancels stale work; shutdown does not await a bl
|
|
|
97
106
|
});
|
|
98
107
|
|
|
99
108
|
test("print shutdown joins an accepted orchestrator delivery before disposing", async () => {
|
|
100
|
-
const h = harness({},
|
|
109
|
+
const h = harness({}, "print", 50); await h.emit("session_start");
|
|
101
110
|
h.emit("message_end", { message: final() }); await tick();
|
|
102
111
|
let stopped = false;
|
|
103
112
|
const shutdown = h.emit("session_shutdown").then(() => { stopped = true; });
|
|
@@ -109,7 +118,7 @@ test("print shutdown joins an accepted orchestrator delivery before disposing",
|
|
|
109
118
|
});
|
|
110
119
|
|
|
111
120
|
test("print shutdown joins an accepted child launch and response delivery", async () => {
|
|
112
|
-
const h = harness({},
|
|
121
|
+
const h = harness({}, "print", 50); await h.emit("session_start");
|
|
113
122
|
const observation = normalizeRpcEvent({ type: "message_end", message: final() }, { observeResponses: true })
|
|
114
123
|
.find(event => event.type === TASK_EVENT.RESPONSE_OBSERVATION);
|
|
115
124
|
assert.ok(observation?.type === TASK_EVENT.RESPONSE_OBSERVATION);
|
|
@@ -127,7 +136,7 @@ test("print shutdown joins an accepted child launch and response delivery", asyn
|
|
|
127
136
|
});
|
|
128
137
|
|
|
129
138
|
test("print shutdown aborts a delivery after the bounded join deadline", async () => {
|
|
130
|
-
const h = harness({},
|
|
139
|
+
const h = harness({}, "print", 5); await h.emit("session_start");
|
|
131
140
|
h.emit("message_end", { message: final() }); await tick();
|
|
132
141
|
await h.emit("session_shutdown");
|
|
133
142
|
assert.equal(h.signal().aborted, true);
|
|
@@ -135,7 +144,7 @@ test("print shutdown aborts a delivery after the bounded join deadline", async (
|
|
|
135
144
|
});
|
|
136
145
|
|
|
137
146
|
test("interactive shutdown never joins a blocked delivery", async () => {
|
|
138
|
-
const h = harness({},
|
|
147
|
+
const h = harness({}, "tui", 50); await h.emit("session_start");
|
|
139
148
|
h.emit("message_end", { message: final() }); await tick();
|
|
140
149
|
assert.equal(h.emit("session_shutdown"), undefined);
|
|
141
150
|
assert.equal(h.signal().aborted, true);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { normalizeRuntimeModel } from "../lib/runtime-metrics.ts";
|
|
4
|
+
|
|
5
|
+
// Data-driven family-pattern normalizer (gentle-pi#968 / gentle-ai#4536). Rules:
|
|
6
|
+
// - non-string or empty provider or id -> {provider: "unknown", id: "unknown"};
|
|
7
|
+
// - id is trimmed, the LAST "/"-separated segment is kept, then lowercased; it is
|
|
8
|
+
// public only when it matches the schema id pattern within its maxLength,
|
|
9
|
+
// otherwise the id becomes "custom";
|
|
10
|
+
// - provider is trimmed and lowercased; it is kept when it matches the schema
|
|
11
|
+
// provider pattern, otherwise it becomes "custom";
|
|
12
|
+
// - when the id is not public the provider becomes "custom" too, except for the
|
|
13
|
+
// "opencode" provider, which yields "opencode"/"custom";
|
|
14
|
+
// - the literal "unknown"/"unknown" and "custom"/"custom" pairs pass through.
|
|
15
|
+
const cases: Array<[string, unknown, unknown, { provider: string; id: string }]> = [
|
|
16
|
+
["open-weight family on an arbitrary provider stays public", "nan", "deepseek-v4-flash", { provider: "nan", id: "deepseek-v4-flash" }],
|
|
17
|
+
["a short version suffix without a separator is still public", "nan", "glm5.3", { provider: "nan", id: "glm5.3" }],
|
|
18
|
+
["multiple suffix groups without a leading separator are still public", "nan", "glm5.3-flash", { provider: "nan", id: "glm5.3-flash" }],
|
|
19
|
+
["only the last '/'-separated id segment is kept", "nano-gpt", "TEE/glm-5.3", { provider: "nano-gpt", id: "glm-5.3" }],
|
|
20
|
+
["a nested provider path in the id is dropped, keeping the outer provider", "openrouter", "deepseek/deepseek-v4-flash", { provider: "openrouter", id: "deepseek-v4-flash" }],
|
|
21
|
+
["provider and id are lowercased", "OpenAI", "GPT-5.6-Sol", { provider: "openai", id: "gpt-5.6-sol" }],
|
|
22
|
+
["a long dotted/dashed anthropic id stays public", "anthropic", "claude-sonnet-5-20260101", { provider: "anthropic", id: "claude-sonnet-5-20260101" }],
|
|
23
|
+
["an id with no recognized family prefix collapses provider and id to custom", "acme", "acme-internal-finetune", { provider: "custom", id: "custom" }],
|
|
24
|
+
["a public id with a non-conforming provider is emitted as custom/<id>", "Acme Corp", "gpt-5.6", { provider: "custom", id: "gpt-5.6" }],
|
|
25
|
+
["an id longer than the schema maxLength collapses to custom/custom", "anthropic", `claude-${"x".repeat(64)}`, { provider: "custom", id: "custom" }],
|
|
26
|
+
["opencode keeps its provider identity even for a private id", "opencode", "private-thing", { provider: "opencode", id: "custom" }],
|
|
27
|
+
["empty provider and id fail closed to unknown/unknown", "", "", { provider: "unknown", id: "unknown" }],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const [name, provider, id, expected] of cases) {
|
|
31
|
+
test(`normalizeRuntimeModel: ${name}`, () => {
|
|
32
|
+
assert.deepEqual(normalizeRuntimeModel(provider, id), expected);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("non-string provider or id fails closed to unknown/unknown", () => {
|
|
37
|
+
for (const [provider, id] of [[undefined, "gpt-5.6"], ["openai", undefined], [null, null], [42, "gpt-5.6"], ["openai", { id: "gpt-5.6" }]] as const) {
|
|
38
|
+
assert.deepEqual(normalizeRuntimeModel(provider, id), { provider: "unknown", id: "unknown" });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("whitespace-only provider or id fails closed to unknown/unknown", () => {
|
|
43
|
+
assert.deepEqual(normalizeRuntimeModel(" ", "gpt-5.6"), { provider: "unknown", id: "unknown" });
|
|
44
|
+
assert.deepEqual(normalizeRuntimeModel("openai", " "), { provider: "unknown", id: "unknown" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("literal unknown/unknown passes through without pattern matching", () => {
|
|
48
|
+
assert.deepEqual(normalizeRuntimeModel("unknown", "unknown"), { provider: "unknown", id: "unknown" });
|
|
49
|
+
assert.deepEqual(normalizeRuntimeModel("Unknown", "UNKNOWN"), { provider: "unknown", id: "unknown" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("literal custom/custom passes through", () => {
|
|
53
|
+
assert.deepEqual(normalizeRuntimeModel("custom", "custom"), { provider: "custom", id: "custom" });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("surrounding whitespace is trimmed before matching", () => {
|
|
57
|
+
assert.deepEqual(normalizeRuntimeModel(" nan ", " deepseek-v4-flash "), { provider: "nan", id: "deepseek-v4-flash" });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// The mirrored pattern is unambiguous (every suffix group starts with a
|
|
61
|
+
// mandatory separator), so a long alphanumeric run followed by an invalid
|
|
62
|
+
// character cannot trigger catastrophic backtracking in V8. The bound below
|
|
63
|
+
// is generous on purpose: it catches an exponential regression, not jitter.
|
|
64
|
+
test("a pathological 64-character id is rejected in linear time", () => {
|
|
65
|
+
const ids = [`gpt${"a".repeat(60)}!`, `glm${"5".repeat(60)}/`, `deepseek${"x".repeat(55)}-`];
|
|
66
|
+
const started = performance.now();
|
|
67
|
+
for (let round = 0; round < 50; round++) for (const id of ids) {
|
|
68
|
+
assert.deepEqual(normalizeRuntimeModel("nan", id), { provider: "custom", id: "custom" });
|
|
69
|
+
}
|
|
70
|
+
assert.ok(performance.now() - started < 500, "normalizer must not backtrack catastrophically");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("attached run then separated groups stays public", () => {
|
|
74
|
+
assert.deepEqual(normalizeRuntimeModel("nan", "glm5.3-flash:thinking"), { provider: "nan", id: "glm5.3-flash:thinking" });
|
|
75
|
+
assert.deepEqual(normalizeRuntimeModel("nan", "gpt-1-2-3-4-5-6-7-8-9"), { provider: "custom", id: "custom" });
|
|
76
|
+
});
|
|
@@ -75,18 +75,84 @@ test("encoder filters custom identities and never promotes SDK model to response
|
|
|
75
75
|
row.agentClass = "private-agent" as any; row.effort = "private-effort" as any;
|
|
76
76
|
row.error = "authentication";
|
|
77
77
|
const encoded = JSON.parse(native.encodeNativeRuntimeEvent([row])!);
|
|
78
|
-
|
|
78
|
+
// "private-model" never normalizes to a public id, so it never claims
|
|
79
|
+
// response evidence; the row falls through to the still-public selection
|
|
80
|
+
// (source()'s default selectedModelId "gpt-5.4") instead of leaking it or
|
|
81
|
+
// collapsing a perfectly good selected identity to custom/custom.
|
|
82
|
+
assert.deepEqual(encoded.rows[0].model, { provider: "openai", id: "gpt-5.4" });
|
|
83
|
+
assert.equal(encoded.rows[0].model_evidence, "selected");
|
|
79
84
|
assert.equal(encoded.rows[0].agent_class, "unknown");
|
|
80
85
|
assert.equal(encoded.rows[0].selected_effort, "unavailable");
|
|
81
86
|
assert.equal(encoded.rows[0].error_category, "auth");
|
|
82
87
|
assert.ok(!JSON.stringify(encoded).includes("private"));
|
|
83
|
-
row.responseModelId = "unknown";
|
|
84
|
-
const selected = JSON.parse(native.encodeNativeRuntimeEvent([row])!);
|
|
85
|
-
assert.equal(selected.rows[0].model_evidence, "selected");
|
|
86
|
-
assert.deepEqual(selected.rows[0].model, { provider: "openai", id: "gpt-5.4" });
|
|
87
88
|
row.selectedModelId = "unknown";
|
|
88
|
-
const
|
|
89
|
-
|
|
89
|
+
const observedFallback = JSON.parse(native.encodeNativeRuntimeEvent([row])!);
|
|
90
|
+
// With no public selected model either, it falls through once more, but
|
|
91
|
+
// source()'s default observedModelId ("private-alias") is not public either.
|
|
92
|
+
assert.equal(observedFallback.rows[0].model_evidence, "unknown");
|
|
93
|
+
assert.deepEqual(observedFallback.rows[0].model, { provider: "unknown", id: "unknown" });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a public observed model is a fallback evidence tier, behind response and selected", () => {
|
|
97
|
+
const row = source();
|
|
98
|
+
row.responseModelId = "unknown"; row.selectedModelId = "unknown"; row.observedModelId = "glm5.3-flash"; row.provider = "nan";
|
|
99
|
+
const encoded = JSON.parse(native.encodeNativeRuntimeEvent([row])!);
|
|
100
|
+
assert.deepEqual(encoded.rows[0].model, { provider: "nan", id: "glm5.3-flash" });
|
|
101
|
+
assert.equal(encoded.rows[0].model_evidence, "selected", "the transport has no separate observed evidence category");
|
|
102
|
+
// A public response model still wins over a public observed model.
|
|
103
|
+
const withResponse = source();
|
|
104
|
+
withResponse.selectedModelId = "unknown"; withResponse.observedModelId = "glm5.3-flash"; withResponse.provider = "nan";
|
|
105
|
+
const encodedResponse = JSON.parse(native.encodeNativeRuntimeEvent([withResponse])!);
|
|
106
|
+
assert.equal(encodedResponse.rows[0].model_evidence, "response");
|
|
107
|
+
// A public selected model still wins over a public observed model.
|
|
108
|
+
const withSelected = source();
|
|
109
|
+
withSelected.responseModelId = "unknown"; withSelected.observedModelId = "glm5.3-flash"; withSelected.provider = "nan";
|
|
110
|
+
const encodedSelected = JSON.parse(native.encodeNativeRuntimeEvent([withSelected])!);
|
|
111
|
+
assert.equal(encodedSelected.rows[0].model_evidence, "selected");
|
|
112
|
+
assert.deepEqual(encodedSelected.rows[0].model, { provider: withSelected.selectedProvider, id: withSelected.selectedModelId });
|
|
113
|
+
// A non-public observed model still falls through to unknown/unknown.
|
|
114
|
+
const noEvidence = source();
|
|
115
|
+
noEvidence.responseModelId = "unknown"; noEvidence.selectedModelId = "unknown"; noEvidence.observedModelId = "private-alias";
|
|
116
|
+
const encodedUnknown = JSON.parse(native.encodeNativeRuntimeEvent([noEvidence])!);
|
|
117
|
+
assert.deepEqual(encodedUnknown.rows[0].model, { provider: "unknown", id: "unknown" });
|
|
118
|
+
assert.equal(encodedUnknown.rows[0].model_evidence, "unknown");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("every encoded model satisfies the schema's anyOf, by pattern or by sentinel", () => {
|
|
122
|
+
const idRule = schema.$defs.model.anyOf[0].properties.id as { pattern: string };
|
|
123
|
+
const providerRule = schema.$defs.model.anyOf[0].properties.provider as { pattern: string };
|
|
124
|
+
const idPattern = new RegExp(idRule.pattern);
|
|
125
|
+
const providerPattern = new RegExp(providerRule.pattern);
|
|
126
|
+
const sentinels = schema.$defs.model.anyOf.slice(1) as Array<{ properties: { provider: { const: string }; id: { const: string } } }>;
|
|
127
|
+
function matchesSchema(model: { provider: string; id: string }): boolean {
|
|
128
|
+
if (providerPattern.test(model.provider) && idPattern.test(model.id)) return true;
|
|
129
|
+
return sentinels.some(({ properties }) => properties.provider.const === model.provider && properties.id.const === model.id);
|
|
130
|
+
}
|
|
131
|
+
// Response/selected/observed rows only ever win evidence with an id that is
|
|
132
|
+
// already public, so their encoded model is either the public-pattern
|
|
133
|
+
// branch or the terminal unknown/unknown sentinel. The custom/custom and
|
|
134
|
+
// opencode/custom sentinels are reachable only through a launch row, which
|
|
135
|
+
// (already upstream-classified, always "selected" evidence) is paired
|
|
136
|
+
// through the same normalizer unconditionally.
|
|
137
|
+
const rows: RuntimeMetricBucket[] = [
|
|
138
|
+
source(),
|
|
139
|
+
{ ...source(), provider: "nan", selectedProvider: "nan", selectedModelId: "deepseek-v4-flash", responseModelId: "unknown" },
|
|
140
|
+
{ ...source(), responseModelId: "unknown", selectedModelId: "unknown", observedModelId: "unknown" },
|
|
141
|
+
];
|
|
142
|
+
const launches = [
|
|
143
|
+
{ evidence: "launch_configuration" as const, agentClass: parseAgentClass("worker")!, selectedProvider: "opencode", selectedModelId: "custom", selectedEffort: "high" as const, launches: 1 },
|
|
144
|
+
{ evidence: "launch_configuration" as const, agentClass: parseAgentClass("worker")!, selectedProvider: "custom", selectedModelId: "custom", selectedEffort: "high" as const, launches: 1 },
|
|
145
|
+
{ evidence: "launch_configuration" as const, agentClass: parseAgentClass("worker")!, selectedProvider: "unknown", selectedModelId: "unknown", selectedEffort: "high" as const, launches: 1 },
|
|
146
|
+
];
|
|
147
|
+
const payload = native.encodeNativeRuntimeEvent(rows, launches);
|
|
148
|
+
assert.ok(payload);
|
|
149
|
+
const decoded = JSON.parse(payload).rows as Array<{ model: { provider: string; id: string } }>;
|
|
150
|
+
assert.equal(decoded.length, rows.length + launches.length);
|
|
151
|
+
for (const row of decoded) assert.ok(matchesSchema(row.model), `model ${JSON.stringify(row.model)} must satisfy $defs.model.anyOf`);
|
|
152
|
+
assert.deepEqual(decoded.map(row => row.model), [
|
|
153
|
+
{ provider: "openai", id: "gpt-5.4" }, { provider: "nan", id: "deepseek-v4-flash" }, { provider: "unknown", id: "unknown" },
|
|
154
|
+
{ provider: "opencode", id: "custom" }, { provider: "custom", id: "custom" }, { provider: "unknown", id: "unknown" },
|
|
155
|
+
]);
|
|
90
156
|
});
|
|
91
157
|
|
|
92
158
|
function fixture() {
|
|
@@ -16,31 +16,32 @@ function response(responseId = "local-response"): FinalResponse {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
test("selected provider is independent from response provider without relabeling",
|
|
20
|
-
const { lookupPiCatalogName } = await import("../lib/runtime-metrics-pi-identity.ts");
|
|
21
|
-
const { OPENAI_CODEX_MODELS } = await import("@earendil-works/pi-ai/providers/openai-codex.models");
|
|
22
|
-
const model = Object.values(OPENAI_CODEX_MODELS)[0];
|
|
23
|
-
await lookupPiCatalogName({ provider: model.provider, modelId: model.id });
|
|
19
|
+
test("selected provider is independent from response provider without relabeling", () => {
|
|
24
20
|
const metrics = new RuntimeMetrics();
|
|
25
|
-
assert.equal(recordRaw(metrics, { ...response(), selectedProvider:
|
|
26
|
-
selectedModelId:
|
|
21
|
+
assert.equal(recordRaw(metrics, { ...response(), selectedProvider: "openai-codex",
|
|
22
|
+
selectedModelId: "gpt-5.6-terra", provider: "anthropic" }), "recorded");
|
|
27
23
|
const [row] = metrics.snapshot();
|
|
28
|
-
assert.equal(row.selectedModelId,
|
|
29
|
-
assert.equal(row.selectedProvider,
|
|
24
|
+
assert.equal(row.selectedModelId, "gpt-5.6-terra");
|
|
25
|
+
assert.equal(row.selectedProvider, "openai-codex");
|
|
30
26
|
assert.equal(row.provider, "anthropic");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
// The provider dimension stays independent of a specific id's privacy outcome
|
|
28
|
+
// at record time; the pair is only coupled to a schema-conformant custom/custom
|
|
29
|
+
// model object later, at native encode time (see runtime-metrics-native.test.ts).
|
|
30
|
+
assert.equal(recordRaw(metrics, { ...response("private"), selectedProvider: "openai-codex",
|
|
31
|
+
selectedModelId: "private-internal-finetune" }), "recorded");
|
|
32
|
+
assert.equal(metrics.snapshot()[1].selectedProvider, "openai-codex");
|
|
34
33
|
assert.equal(metrics.snapshot()[1].selectedModelId, "custom");
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
test("
|
|
38
|
-
const metrics = new RuntimeMetrics(
|
|
39
|
-
assert.equal(metrics
|
|
40
|
-
responseModelId: "
|
|
36
|
+
test("open-weight models on arbitrary providers are reported by name, not squashed to a closed enum", () => {
|
|
37
|
+
const metrics = new RuntimeMetrics();
|
|
38
|
+
assert.equal(recordRaw(metrics, { ...response(), provider: "nan", selectedProvider: "nan",
|
|
39
|
+
selectedModelId: "deepseek-v4-flash", responseModelId: "glm5.3-flash" }), "recorded");
|
|
41
40
|
const [row] = metrics.snapshot();
|
|
42
|
-
assert.equal(row.
|
|
43
|
-
assert.equal(row.
|
|
41
|
+
assert.equal(row.provider, "nan");
|
|
42
|
+
assert.equal(row.selectedProvider, "nan");
|
|
43
|
+
assert.equal(row.selectedModelId, "deepseek-v4-flash");
|
|
44
|
+
assert.equal(row.responseModelId, "glm5.3-flash");
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
// Deliberately bypass static types to exercise the runtime boundary.
|
|
@@ -96,7 +97,9 @@ test("effort selections and absence states remain distinct", () => {
|
|
|
96
97
|
test("only closed dimensions survive; no prompt-based executor inference", () => {
|
|
97
98
|
const metrics = new RuntimeMetrics();
|
|
98
99
|
assert.equal(recordRaw(metrics, {
|
|
99
|
-
|
|
100
|
+
// Free text with spaces/case never matches the schema provider pattern,
|
|
101
|
+
// unlike a genuine open-weight provider slug (see the dedicated test above).
|
|
102
|
+
...response(), provider: "Private Provider Free Text", modelFamily: "private-model-id",
|
|
100
103
|
executor: "SDD apply executor", systemPrompt: "reviewer", errorMessage: "private failure",
|
|
101
104
|
}), "recorded");
|
|
102
105
|
const [row] = metrics.snapshot();
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { findPackageJSON } from "node:module";
|
|
2
|
-
|
|
3
|
-
// Catalog-name privacy classification ONLY. No identity issuance, route evidence,
|
|
4
|
-
// registry reads, SDK hooks, auth resolution, network, or injected catalogs.
|
|
5
|
-
export interface PiCatalogName {
|
|
6
|
-
readonly classification: "catalog_public" | "custom" | "unknown";
|
|
7
|
-
readonly modelId: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const UNKNOWN: PiCatalogName = Object.freeze({ classification: "unknown", modelId: "unknown" });
|
|
11
|
-
const CUSTOM: PiCatalogName = Object.freeze({ classification: "custom", modelId: "custom" });
|
|
12
|
-
// Initial provider coverage matches the accumulator. Other providers are custom;
|
|
13
|
-
// additions require deliberate review. These are providers, not model-ID lists.
|
|
14
|
-
const CATALOGS = ["anthropic", "openai", "openai-codex", "google", "google-vertex", "amazon-bedrock", "openrouter"] as const;
|
|
15
|
-
const MAX_MODELS = 4096;
|
|
16
|
-
const MAX_LOAD_ATTEMPTS = 3;
|
|
17
|
-
|
|
18
|
-
function object(value: unknown): value is Record<string, unknown> {
|
|
19
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function text(value: unknown, max: number): value is string {
|
|
23
|
-
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function key(provider: string, modelId: string): string {
|
|
27
|
-
return JSON.stringify([provider, modelId]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function loadCatalog(): Promise<ReadonlyMap<string, PiCatalogName>> {
|
|
31
|
-
// Node >=22.19 supports findPackageJSON. Verify ESM lookup from this module
|
|
32
|
-
// selects Pi's own pi-ai package; fail rather than silently use another copy.
|
|
33
|
-
const pi = import.meta.resolve("@earendil-works/pi-coding-agent");
|
|
34
|
-
const ownPackage = findPackageJSON("@earendil-works/pi-ai", import.meta.url);
|
|
35
|
-
if (!ownPackage || ownPackage !== findPackageJSON("@earendil-works/pi-ai", pi)) {
|
|
36
|
-
throw new Error("Pi catalog dependency mismatch");
|
|
37
|
-
}
|
|
38
|
-
const entries = new Map<string, PiCatalogName>();
|
|
39
|
-
let count = 0;
|
|
40
|
-
for (const provider of CATALOGS) {
|
|
41
|
-
// pi-ai 0.85.1 providers/* has an import-only export condition: use ESM,
|
|
42
|
-
// never require.resolve. Generated modules load packaged JSON, not registry
|
|
43
|
-
// overrides. Specifiers and export names come only from the fixed list.
|
|
44
|
-
const module = await import(`@earendil-works/pi-ai/providers/${provider}.models`);
|
|
45
|
-
const models: unknown = module[`${provider.replaceAll("-", "_").toUpperCase()}_MODELS`];
|
|
46
|
-
if (!object(models)) throw new Error("Invalid packaged model catalog");
|
|
47
|
-
for (const model of Object.values(models)) {
|
|
48
|
-
if (++count > MAX_MODELS) throw new RangeError("Packaged model catalog exceeds capacity");
|
|
49
|
-
if (!object(model) || model.provider !== provider || !text(model.id, 128)) {
|
|
50
|
-
throw new Error("Invalid packaged model name");
|
|
51
|
-
}
|
|
52
|
-
entries.set(key(provider, model.id), Object.freeze({ classification: "catalog_public", modelId: model.id }));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return entries;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function createPiCatalogNameLookup(
|
|
59
|
-
load: () => Promise<ReadonlyMap<string, PiCatalogName>> = loadCatalog,
|
|
60
|
-
maxAttempts = MAX_LOAD_ATTEMPTS,
|
|
61
|
-
): { lookup(input: unknown): Promise<PiCatalogName>; classify(input: unknown): PiCatalogName } {
|
|
62
|
-
let catalogPromise: Promise<ReadonlyMap<string, PiCatalogName>> | undefined;
|
|
63
|
-
let loadedCatalog: ReadonlyMap<string, PiCatalogName> | undefined;
|
|
64
|
-
let attempts = 0;
|
|
65
|
-
const classify = (input: unknown): PiCatalogName => {
|
|
66
|
-
if (!object(input) || !text(input.provider, 32) || !text(input.modelId, 128)) return UNKNOWN;
|
|
67
|
-
if (!CATALOGS.includes(input.provider as typeof CATALOGS[number])) return CUSTOM;
|
|
68
|
-
return loadedCatalog ? loadedCatalog.get(key(input.provider, input.modelId)) ?? CUSTOM : UNKNOWN;
|
|
69
|
-
};
|
|
70
|
-
const lookup = async (input: unknown): Promise<PiCatalogName> => {
|
|
71
|
-
if (!object(input) || !text(input.provider, 32) || !text(input.modelId, 128)) return UNKNOWN;
|
|
72
|
-
if (!CATALOGS.includes(input.provider as typeof CATALOGS[number])) return CUSTOM;
|
|
73
|
-
if (!loadedCatalog) {
|
|
74
|
-
if (!catalogPromise) {
|
|
75
|
-
if (attempts >= maxAttempts) throw new Error("Pi catalog load attempts exhausted");
|
|
76
|
-
attempts++;
|
|
77
|
-
catalogPromise = load().then(catalog => {
|
|
78
|
-
loadedCatalog = catalog;
|
|
79
|
-
return catalog;
|
|
80
|
-
}, error => {
|
|
81
|
-
catalogPromise = undefined;
|
|
82
|
-
throw error;
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
await catalogPromise;
|
|
86
|
-
}
|
|
87
|
-
return classify(input);
|
|
88
|
-
};
|
|
89
|
-
return { lookup, classify };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const catalogLookup = createPiCatalogNameLookup();
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Returns only a privacy-safe catalog name, NOT the model actually dispatched.
|
|
96
|
-
* Matching a public ID remains a public-name fact even on a custom endpoint;
|
|
97
|
-
* origin/API/endpoint strings are ignored, never treated as provenance evidence.
|
|
98
|
-
* Missing/malformed names are unknown; non-catalog names/aliases are custom.
|
|
99
|
-
* No modelVersion is invented. This labels a caller-observed selection only;
|
|
100
|
-
* actual dispatch/response identity requires separate future host evidence.
|
|
101
|
-
* One cached snapshot retains <=4096 names of <=128 code units; no input is kept.
|
|
102
|
-
* Missing/incompatible dependencies and catalog overflow reject, never skip.
|
|
103
|
-
*/
|
|
104
|
-
export async function lookupPiCatalogName(input: unknown): Promise<PiCatalogName> {
|
|
105
|
-
return catalogLookup.lookup(input);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Pure lookup after async catalog loading; unknown until initialization succeeds.
|
|
109
|
-
* Always recheck membership, never trust a caller's classification/public label.
|
|
110
|
-
*/
|
|
111
|
-
export function classifyPiCatalogName(input: unknown): PiCatalogName {
|
|
112
|
-
return catalogLookup.classify(input);
|
|
113
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { findPackageJSON } from "node:module";
|
|
4
|
-
import { OPENAI_CODEX_MODELS } from "@earendil-works/pi-ai/providers/openai-codex.models";
|
|
5
|
-
import { classifyPiCatalogName, createPiCatalogNameLookup, lookupPiCatalogName, type PiCatalogName } from "../lib/runtime-metrics-pi-identity.ts";
|
|
6
|
-
import { RuntimeMetrics, type FinalResponse } from "../lib/runtime-metrics.ts";
|
|
7
|
-
|
|
8
|
-
const model = Object.values(OPENAI_CODEX_MODELS)[0];
|
|
9
|
-
const name = { provider: model.provider, modelId: model.id };
|
|
10
|
-
|
|
11
|
-
test("a failed catalog load is retried lazily and later classifications recover", async () => {
|
|
12
|
-
let attempts = 0;
|
|
13
|
-
const publicName = Object.freeze({ classification: "catalog_public", modelId: name.modelId }) satisfies PiCatalogName;
|
|
14
|
-
const catalog = new Map([[JSON.stringify([name.provider, name.modelId]), publicName]]);
|
|
15
|
-
const lookup = createPiCatalogNameLookup(async () => {
|
|
16
|
-
attempts++;
|
|
17
|
-
if (attempts === 1) throw new Error("transient catalog failure");
|
|
18
|
-
return catalog;
|
|
19
|
-
});
|
|
20
|
-
await assert.rejects(lookup.lookup(name), /transient catalog failure/);
|
|
21
|
-
assert.deepEqual(lookup.classify(name), { classification: "unknown", modelId: "unknown" });
|
|
22
|
-
assert.strictEqual(await lookup.lookup(name), publicName);
|
|
23
|
-
assert.strictEqual(lookup.classify(name), publicName);
|
|
24
|
-
assert.equal(attempts, 2);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("catalog loading attempts are capped per lookup instance", async () => {
|
|
28
|
-
let attempts = 0;
|
|
29
|
-
const lookup = createPiCatalogNameLookup(async () => {
|
|
30
|
-
attempts++;
|
|
31
|
-
throw new Error(`failure ${attempts}`);
|
|
32
|
-
});
|
|
33
|
-
for (let attempt = 0; attempt < 5; attempt++) await assert.rejects(lookup.lookup(name));
|
|
34
|
-
assert.equal(attempts, 3);
|
|
35
|
-
assert.deepEqual(lookup.classify(name), { classification: "unknown", modelId: "unknown" });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("uninitialized synchronous classification fails closed", () => {
|
|
39
|
-
assert.deepEqual(classifyPiCatalogName(name), { classification: "unknown", modelId: "unknown" });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("missing catalog name remains unknown; no route evidence is invented", async () => {
|
|
43
|
-
for (const input of [undefined, null, {}, { provider: model.provider }, { modelId: model.id }]) {
|
|
44
|
-
assert.deepEqual(await lookupPiCatalogName(input), { classification: "unknown", modelId: "unknown" });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("custom aliases never export supplied strings", async () => {
|
|
49
|
-
for (const input of [{ ...name, modelId: "private-alias" }, { ...name, provider: "private-provider" }]) {
|
|
50
|
-
const result = await lookupPiCatalogName(input);
|
|
51
|
-
assert.deepEqual(result, { classification: "custom", modelId: "custom" });
|
|
52
|
-
assert.ok(Object.isFrozen(result));
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("caller-created public identities cannot bypass the catalog boundary", async () => {
|
|
57
|
-
for (const input of [{ modelId: "private-model", classification: "catalog_public" },
|
|
58
|
-
{ ...name, modelId: "private-model", origin: "builtin" }]) {
|
|
59
|
-
assert.notEqual((await lookupPiCatalogName(input)).classification, "catalog_public");
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("malformed or oversized name metadata fails closed", async () => {
|
|
64
|
-
for (const patch of [{ modelId: "" }, { modelId: "x".repeat(129) }, { provider: 12 },
|
|
65
|
-
{ modelId: null }, { provider: "x".repeat(33) }]) {
|
|
66
|
-
assert.deepEqual(await lookupPiCatalogName({ ...name, ...patch }), { classification: "unknown", modelId: "unknown" });
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("actual installed ESM catalog supplies immutable public names, not route claims", async () => {
|
|
71
|
-
const pi = import.meta.resolve("@earendil-works/pi-coding-agent");
|
|
72
|
-
assert.equal(findPackageJSON("@earendil-works/pi-ai", import.meta.url), findPackageJSON("@earendil-works/pi-ai", pi));
|
|
73
|
-
assert.ok(model);
|
|
74
|
-
const result = await lookupPiCatalogName(name);
|
|
75
|
-
assert.deepEqual(result, { classification: "catalog_public", modelId: model.id });
|
|
76
|
-
assert.ok(Object.isFrozen(result));
|
|
77
|
-
assert.strictEqual(await lookupPiCatalogName(name), result);
|
|
78
|
-
// Origin/endpoint assertions do not change privacy classification of a name.
|
|
79
|
-
// Neither a real catalog entry nor matching route strings establish dispatch.
|
|
80
|
-
for (const patch of [{ origin: "builtin" }, { origin: "unknown" }, { origin: "custom" },
|
|
81
|
-
{ baseUrl: "https://private.invalid" }, { api: "custom-api" }]) {
|
|
82
|
-
assert.strictEqual(await lookupPiCatalogName({ ...name, ...patch }), result);
|
|
83
|
-
}
|
|
84
|
-
assert.deepEqual(Object.keys(result), ["classification", "modelId"]);
|
|
85
|
-
assert.ok(!("modelVersion" in result));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("catalog-public selections are counted without claiming actual route identity", async () => {
|
|
89
|
-
const catalogName = await lookupPiCatalogName(name);
|
|
90
|
-
const metrics = new RuntimeMetrics();
|
|
91
|
-
const missing = { state: "unavailable" } as const;
|
|
92
|
-
for (const [index, identity] of [catalogName, { ...name, origin: "builtin", api: model.api, baseUrl: model.baseUrl }].entries()) {
|
|
93
|
-
const record = {
|
|
94
|
-
kind: "final_assistant_response", responseId: String(index), identity, selectedModelId: model.id,
|
|
95
|
-
executor: "worker", provider: model.provider, modelFamily: "gpt", effort: "high", error: "none",
|
|
96
|
-
tokens: { input: missing, output: missing, cacheRead: missing, cacheWrite: missing },
|
|
97
|
-
responseHeadersMs: missing, fullResponseMs: missing,
|
|
98
|
-
};
|
|
99
|
-
assert.equal(metrics.record(record as FinalResponse), "recorded");
|
|
100
|
-
}
|
|
101
|
-
const [bucket] = metrics.snapshot();
|
|
102
|
-
assert.equal(bucket.selectedModelId, model.id);
|
|
103
|
-
assert.ok(!("modelId" in bucket));
|
|
104
|
-
assert.ok(!("route" in bucket));
|
|
105
|
-
assert.equal(bucket.responses, 2);
|
|
106
|
-
assert.equal(bucket.hostAgent, "pi");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("selection classification rejects fabricated public labels and private IDs", async () => {
|
|
110
|
-
await lookupPiCatalogName(name);
|
|
111
|
-
assert.equal(classifyPiCatalogName({ ...name, modelId: "private-id", classification: "catalog_public" }).modelId, "custom");
|
|
112
|
-
assert.equal(classifyPiCatalogName({ ...name, provider: "anthropic" }).modelId, "custom");
|
|
113
|
-
});
|