pi-ui-extend 1.0.39 → 1.0.41
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 +1 -1
- package/dist/app/commands/command-registry.js +2 -2
- package/dist/app/commands/command-session-actions.d.ts +0 -1
- package/dist/app/commands/command-session-actions.js +22 -13
- package/dist/app/icons.d.ts +14 -0
- package/dist/app/icons.js +33 -0
- package/dist/app/rendering/conversation-tool-renderer.js +2 -2
- package/dist/app/rendering/dcp-stats.d.ts +6 -1
- package/dist/app/rendering/dcp-stats.js +214 -46
- package/dist/app/rendering/editor-panels.js +8 -5
- package/dist/app/session/lazy-session-manager.js +12 -1
- package/dist/app/session/tabs-controller.d.ts +2 -5
- package/dist/app/session/tabs-controller.js +12 -21
- package/dist/app/subagents/subagents-model.d.ts +14 -1
- package/dist/app/subagents/subagents-model.js +34 -15
- package/dist/app/types.d.ts +2 -0
- package/dist/bundled-extensions/session-title/config.js +1 -1
- package/dist/markdown-format.js +27 -9
- package/dist/schemas/pi-tools-suite-schema.d.ts +29 -16
- package/dist/schemas/pi-tools-suite-schema.js +46 -31
- package/external/pi-tools-suite/README.md +392 -52
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +31 -21
- package/external/pi-tools-suite/docs/context-gateway-p00-adr.md +216 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-gate-review.md +122 -0
- package/external/pi-tools-suite/docs/context-gateway-p01n-measurement.md +133 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-ra-evidence.md +111 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rb-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rc-evidence.md +69 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rd-evidence.md +100 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-re-evidence.md +74 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rf-evidence.md +153 -0
- package/external/pi-tools-suite/docs/context-gateway-p01r-rg-evidence.md +235 -0
- package/external/pi-tools-suite/docs/evals.md +684 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +109 -0
- package/external/pi-tools-suite/package.json +10 -3
- package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/scripts/browser-qa-runner.mjs +82 -1
- package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/SKILL.md → agents/browser-qa.md} +261 -12
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +20 -0
- package/external/pi-tools-suite/src/async-subagents/agents/oracle.md +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +27 -243
- package/external/pi-tools-suite/src/async-subagents/commands.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +41 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +13 -93
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +494 -0
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +9 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +200 -143
- package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +54 -0
- package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +7 -6
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +52 -45
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +12 -4
- package/external/pi-tools-suite/src/async-subagents/index.ts +11 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +46 -18
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -2
- package/external/pi-tools-suite/src/async-subagents/types.ts +2 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +41 -142
- package/external/pi-tools-suite/src/config.ts +1 -22
- package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +111 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +160 -0
- package/external/pi-tools-suite/src/context-gateway/metadata-normalization.ts +88 -0
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +89 -0
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +429 -0
- package/external/pi-tools-suite/src/context-gateway/test-output-parser.ts +326 -0
- package/external/pi-tools-suite/src/context-gateway/types.ts +152 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +810 -106
- package/external/pi-tools-suite/src/dcp/commands.ts +64 -139
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +369 -35
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +510 -64
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +113 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
- package/external/pi-tools-suite/src/dcp/config.ts +36 -61
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +421 -0
- package/external/pi-tools-suite/src/dcp/debug-log.ts +7 -5
- package/external/pi-tools-suite/src/dcp/index.ts +617 -203
- package/external/pi-tools-suite/src/dcp/journal.ts +566 -0
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +244 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +10 -7
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +298 -78
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +173 -281
- package/external/pi-tools-suite/src/dcp/pruner-emergency.ts +2 -4
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +17 -5
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +11 -1
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +30 -82
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +22 -133
- package/external/pi-tools-suite/src/dcp/pruner.ts +18 -33
- package/external/pi-tools-suite/src/dcp/recovery.ts +129 -0
- package/external/pi-tools-suite/src/dcp/shadow-plan.ts +127 -0
- package/external/pi-tools-suite/src/dcp/state-transaction.ts +102 -0
- package/external/pi-tools-suite/src/dcp/state.ts +158 -580
- package/external/pi-tools-suite/src/dcp/ui.ts +1 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +55 -220
- package/external/pi-tools-suite/src/index.ts +9 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +76 -42
- package/external/pi-tools-suite/src/repo-discovery/index.ts +84 -18
- package/external/pi-tools-suite/src/repo-discovery/native-compact.ts +458 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
- package/external/pi-tools-suite/src/tool-descriptions.ts +43 -38
- package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
- package/package.json +6 -6
- package/schemas/pi-tools-suite.json +159 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +0 -78
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +0 -223
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +0 -195
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.mjs +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { accountContextGatewayParts } from "./accounting.js";
|
|
4
|
+
import { parseTestBuildOutput, planProspectiveTestOutputDelivery } from "./test-output-parser.js";
|
|
5
|
+
import type {
|
|
6
|
+
ContextGatewayClassTelemetry,
|
|
7
|
+
ContextGatewayNativePolicyReason,
|
|
8
|
+
ContextGatewayNativePolicyTelemetry,
|
|
9
|
+
ContextGatewayObservation,
|
|
10
|
+
ContextGatewayTelemetrySnapshot,
|
|
11
|
+
ContextGatewayTestOutputTelemetry,
|
|
12
|
+
ContextGatewayToolClass,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
|
|
15
|
+
type ToolCallLike = {
|
|
16
|
+
toolCallId?: unknown;
|
|
17
|
+
toolName?: unknown;
|
|
18
|
+
input?: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ToolResultLike = {
|
|
22
|
+
toolCallId?: unknown;
|
|
23
|
+
toolName?: unknown;
|
|
24
|
+
content?: unknown;
|
|
25
|
+
details?: unknown;
|
|
26
|
+
isError?: unknown;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const NATIVE_POLICY_REASONS = new Set<ContextGatewayNativePolicyReason>([
|
|
30
|
+
"invalid-wrapper-budget",
|
|
31
|
+
"unknown-flag",
|
|
32
|
+
"missing-flag-value",
|
|
33
|
+
"duplicate-flag",
|
|
34
|
+
"invalid-flag-value",
|
|
35
|
+
"conflicting-flags",
|
|
36
|
+
"compact-limit-exceeded",
|
|
37
|
+
"full-limit-exceeded",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
type CallBinding = {
|
|
41
|
+
toolClass: ContextGatewayToolClass;
|
|
42
|
+
inputFingerprint?: string;
|
|
43
|
+
shellCommandScope?: "simple" | "compound" | "unknown";
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface ContextGatewayRolePartBytes {
|
|
47
|
+
userTextBytes: number;
|
|
48
|
+
assistantProseBytes: number;
|
|
49
|
+
assistantThinkingBytes: number;
|
|
50
|
+
assistantToolCallArgumentBytes: number;
|
|
51
|
+
toolResultTextBytes: number;
|
|
52
|
+
controlOrUnknownTextBytes: number;
|
|
53
|
+
metadataDetailsBytes: number;
|
|
54
|
+
messageWrapperBytes: number;
|
|
55
|
+
unknownParts: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function emptyCounters(): ContextGatewayClassTelemetry {
|
|
59
|
+
return {
|
|
60
|
+
results: 0,
|
|
61
|
+
errors: 0,
|
|
62
|
+
contentBytes: 0,
|
|
63
|
+
textBytes: 0,
|
|
64
|
+
imageBytes: 0,
|
|
65
|
+
detailsBytes: 0,
|
|
66
|
+
upstreamTruncatedResults: 0,
|
|
67
|
+
overBudgetResults: 0,
|
|
68
|
+
potentialBytesOverBudget: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function stableValue(value: unknown): unknown {
|
|
73
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
74
|
+
if (value !== null && typeof value === "object") {
|
|
75
|
+
const result: Record<string, unknown> = {};
|
|
76
|
+
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
77
|
+
result[key] = stableValue((value as Record<string, unknown>)[key]);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
if (typeof value === "bigint") return value.toString();
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function inputFingerprint(toolName: string, input: unknown): string {
|
|
86
|
+
let encoded = "";
|
|
87
|
+
try {
|
|
88
|
+
encoded = JSON.stringify(stableValue(input)) ?? "";
|
|
89
|
+
} catch {
|
|
90
|
+
encoded = "[unserializable]";
|
|
91
|
+
}
|
|
92
|
+
return createHash("sha256").update(`${toolName}\u0000${encoded}`).digest("hex");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function shellCommandScope(input: unknown): "simple" | "compound" | "unknown" {
|
|
96
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return "unknown";
|
|
97
|
+
const command = (input as Record<string, unknown>).command;
|
|
98
|
+
if (typeof command !== "string" || command.trim().length === 0) return "unknown";
|
|
99
|
+
// Conservative only: false positives merely keep passthrough. This does not
|
|
100
|
+
// parse, rewrite, approve, or execute the shell command.
|
|
101
|
+
return /[\r\n;|`]|&&|\$\(/.test(command) ? "compound" : "simple";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function byteLengthJson(value: unknown): number {
|
|
105
|
+
if (value === undefined) return 0;
|
|
106
|
+
try {
|
|
107
|
+
const seen = new WeakSet<object>();
|
|
108
|
+
const json = JSON.stringify(value, (_key, item) => {
|
|
109
|
+
if (typeof item === "bigint") return item.toString();
|
|
110
|
+
if (item !== null && typeof item === "object") {
|
|
111
|
+
if (seen.has(item)) return "[Circular]";
|
|
112
|
+
seen.add(item);
|
|
113
|
+
}
|
|
114
|
+
return item;
|
|
115
|
+
});
|
|
116
|
+
return json === undefined ? 0 : Buffer.byteLength(json, "utf8");
|
|
117
|
+
} catch {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function contentSizes(content: unknown): { textBytes: number; imageBytes: number; contentBytes: number } {
|
|
123
|
+
let textBytes = 0;
|
|
124
|
+
let imageBytes = 0;
|
|
125
|
+
if (Array.isArray(content)) {
|
|
126
|
+
for (const raw of content) {
|
|
127
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
128
|
+
const block = raw as Record<string, unknown>;
|
|
129
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
130
|
+
textBytes += Buffer.byteLength(block.text, "utf8");
|
|
131
|
+
} else if (block.type === "image" && typeof block.data === "string") {
|
|
132
|
+
imageBytes += Math.floor((block.data.length * 3) / 4);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { textBytes, imageBytes, contentBytes: byteLengthJson(content) };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function contentText(content: unknown): string {
|
|
140
|
+
if (!Array.isArray(content)) return "";
|
|
141
|
+
let text = "";
|
|
142
|
+
for (const raw of content) {
|
|
143
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
144
|
+
const block = raw as Record<string, unknown>;
|
|
145
|
+
if (block.type === "text" && typeof block.text === "string") text += block.text;
|
|
146
|
+
}
|
|
147
|
+
return text;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function emptyTestOutputTelemetry(): ContextGatewayTestOutputTelemetry {
|
|
151
|
+
return {
|
|
152
|
+
parserVersion: 1,
|
|
153
|
+
results: 0,
|
|
154
|
+
scanLimitedResults: 0,
|
|
155
|
+
compactCandidates: 0,
|
|
156
|
+
passthroughRecommended: 0,
|
|
157
|
+
byClassification: { recognised: 0, partial: 0, unrecognised: 0 },
|
|
158
|
+
byFormat: { "bun-test": 0, tap: 0, typescript: 0, mixed: 0, unknown: 0 },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isTruncated(details: unknown): boolean {
|
|
163
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) return false;
|
|
164
|
+
const truncation = (details as Record<string, unknown>).truncation;
|
|
165
|
+
return Boolean(
|
|
166
|
+
truncation
|
|
167
|
+
&& typeof truncation === "object"
|
|
168
|
+
&& !Array.isArray(truncation)
|
|
169
|
+
&& (truncation as Record<string, unknown>).truncated === true,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function nativePolicyFromResult(event: ToolResultLike): {
|
|
174
|
+
refused: boolean;
|
|
175
|
+
outputMode: "compact" | "full";
|
|
176
|
+
reason?: ContextGatewayNativePolicyReason;
|
|
177
|
+
} | undefined {
|
|
178
|
+
const toolName = typeof event.toolName === "string" ? event.toolName.trim().toLowerCase() : "";
|
|
179
|
+
if (!toolName.startsWith("repo_")) return undefined;
|
|
180
|
+
if (!event.details || typeof event.details !== "object" || Array.isArray(event.details)) return undefined;
|
|
181
|
+
const candidate = (event.details as Record<string, unknown>).nativePolicy;
|
|
182
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return undefined;
|
|
183
|
+
const policy = candidate as Record<string, unknown>;
|
|
184
|
+
if (policy.version !== 1 || policy.profile !== "native-compact") return undefined;
|
|
185
|
+
if (policy.outputMode !== "compact" && policy.outputMode !== "full") return undefined;
|
|
186
|
+
if (typeof policy.refused !== "boolean") return undefined;
|
|
187
|
+
const rawReason = typeof policy.reason === "string" ? policy.reason : undefined;
|
|
188
|
+
const reason = rawReason
|
|
189
|
+
? (NATIVE_POLICY_REASONS.has(rawReason as ContextGatewayNativePolicyReason)
|
|
190
|
+
? rawReason as ContextGatewayNativePolicyReason
|
|
191
|
+
: "unknown")
|
|
192
|
+
: undefined;
|
|
193
|
+
return { refused: policy.refused, outputMode: policy.outputMode, ...(reason ? { reason } : {}) };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function classifyContextGatewayTool(toolName: string): ContextGatewayToolClass {
|
|
197
|
+
const normalized = toolName.trim().toLowerCase();
|
|
198
|
+
if (normalized === "read") return "code-read";
|
|
199
|
+
if (["bash", "shell", "powershell"].includes(normalized)) return "shell";
|
|
200
|
+
if (normalized === "repo_search") return "repo-search";
|
|
201
|
+
if (normalized === "repo_ast") return "repo-ast";
|
|
202
|
+
if (normalized === "repo_structure") return "repo-structure";
|
|
203
|
+
if (normalized === "ast_grep") return "ast-grep";
|
|
204
|
+
if (["write", "edit", "apply_patch", "ast_apply", "multiedit"].includes(normalized)) return "mutation";
|
|
205
|
+
if (["web_search", "web_fetch"].includes(normalized)) return "web";
|
|
206
|
+
if (normalized === "subagents" || normalized.startsWith("async_subagents_")) return "subagent";
|
|
207
|
+
return "other";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function observeToolResult(
|
|
211
|
+
event: ToolResultLike,
|
|
212
|
+
maxResultBytes: number,
|
|
213
|
+
options: { toolClass?: ContextGatewayToolClass; shellCommandScope?: "simple" | "compound" | "unknown" } = {},
|
|
214
|
+
): ContextGatewayObservation {
|
|
215
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
216
|
+
const sizes = contentSizes(event.content);
|
|
217
|
+
const detailsBytes = byteLengthJson(event.details);
|
|
218
|
+
const potentialBytesOverBudget = Math.max(0, sizes.contentBytes - maxResultBytes);
|
|
219
|
+
const toolClass = options.toolClass ?? classifyContextGatewayTool(toolName);
|
|
220
|
+
const completeness = isTruncated(event.details) ? "upstream-truncated" : "unknown";
|
|
221
|
+
const observation: ContextGatewayObservation = {
|
|
222
|
+
version: 1,
|
|
223
|
+
toolClass,
|
|
224
|
+
outcome: event.isError === true ? "error" : "success",
|
|
225
|
+
source: {
|
|
226
|
+
kind: "tool-result-boundary",
|
|
227
|
+
completeness,
|
|
228
|
+
contentBytes: sizes.contentBytes,
|
|
229
|
+
textBytes: sizes.textBytes,
|
|
230
|
+
imageBytes: sizes.imageBytes,
|
|
231
|
+
detailsBytes,
|
|
232
|
+
},
|
|
233
|
+
view: { kind: "text-content", textBytes: sizes.textBytes },
|
|
234
|
+
delivery: {
|
|
235
|
+
representation: "passthrough",
|
|
236
|
+
contentBytes: sizes.contentBytes,
|
|
237
|
+
textBytes: sizes.textBytes,
|
|
238
|
+
},
|
|
239
|
+
overBudget: sizes.contentBytes > maxResultBytes,
|
|
240
|
+
potentialBytesOverBudget,
|
|
241
|
+
};
|
|
242
|
+
if (toolClass === "shell") {
|
|
243
|
+
const parsed = parseTestBuildOutput({
|
|
244
|
+
text: contentText(event.content),
|
|
245
|
+
hostOutcome: observation.outcome,
|
|
246
|
+
upstreamTruncated: completeness === "upstream-truncated",
|
|
247
|
+
});
|
|
248
|
+
const commandScope = options.shellCommandScope ?? "unknown";
|
|
249
|
+
const prospective = planProspectiveTestOutputDelivery(parsed, maxResultBytes, { commandScope });
|
|
250
|
+
observation.testOutput = {
|
|
251
|
+
parserVersion: 1,
|
|
252
|
+
classification: parsed.classification,
|
|
253
|
+
format: parsed.format,
|
|
254
|
+
scanLimited: parsed.scanLimited,
|
|
255
|
+
terminalSummarySeen: parsed.terminalSummarySeen,
|
|
256
|
+
diagnosticCount: parsed.diagnostics.filter((entry) => entry.severity === "error").length,
|
|
257
|
+
warningCount: parsed.diagnostics.filter((entry) => entry.severity === "warning").length,
|
|
258
|
+
commandScope,
|
|
259
|
+
prospectiveDecision: prospective.decision,
|
|
260
|
+
prospectiveReason: prospective.reason,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return observation;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export class ContextGatewayTelemetry {
|
|
267
|
+
private totals = emptyCounters();
|
|
268
|
+
private byClass: Partial<Record<ContextGatewayToolClass, ContextGatewayClassTelemetry>> = {};
|
|
269
|
+
private bindings = new Map<string, CallBinding>();
|
|
270
|
+
private seenInputFingerprints = new Set<string>();
|
|
271
|
+
private unboundResults = 0;
|
|
272
|
+
private repeatCandidateCount = 0;
|
|
273
|
+
private retrievalCalls = 0;
|
|
274
|
+
private nativePolicy: ContextGatewayNativePolicyTelemetry = {
|
|
275
|
+
results: 0,
|
|
276
|
+
refusals: 0,
|
|
277
|
+
fullOverrides: 0,
|
|
278
|
+
byReason: {},
|
|
279
|
+
};
|
|
280
|
+
private testOutput = emptyTestOutputTelemetry();
|
|
281
|
+
private lastObservation: ContextGatewayObservation | undefined;
|
|
282
|
+
|
|
283
|
+
recordToolCall(event: ToolCallLike): void {
|
|
284
|
+
if (typeof event.toolCallId !== "string" || typeof event.toolName !== "string") return;
|
|
285
|
+
const normalizedToolName = event.toolName.trim().toLowerCase();
|
|
286
|
+
if (normalizedToolName === "artifact_read" || normalizedToolName === "artifact_search") {
|
|
287
|
+
this.retrievalCalls += 1;
|
|
288
|
+
}
|
|
289
|
+
const fingerprint = normalizedToolName === "read"
|
|
290
|
+
? inputFingerprint(event.toolName, event.input)
|
|
291
|
+
: undefined;
|
|
292
|
+
if (fingerprint) {
|
|
293
|
+
if (this.seenInputFingerprints.has(fingerprint)) this.repeatCandidateCount += 1;
|
|
294
|
+
else this.seenInputFingerprints.add(fingerprint);
|
|
295
|
+
}
|
|
296
|
+
this.bindings.set(event.toolCallId, {
|
|
297
|
+
toolClass: classifyContextGatewayTool(event.toolName),
|
|
298
|
+
inputFingerprint: fingerprint,
|
|
299
|
+
...(classifyContextGatewayTool(event.toolName) === "shell"
|
|
300
|
+
? { shellCommandScope: shellCommandScope(event.input) }
|
|
301
|
+
: {}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
recordToolResult(event: ToolResultLike, maxResultBytes: number): ContextGatewayObservation {
|
|
306
|
+
const binding = typeof event.toolCallId === "string" ? this.bindings.get(event.toolCallId) : undefined;
|
|
307
|
+
const observation = observeToolResult(event, maxResultBytes, {
|
|
308
|
+
...(binding ? { toolClass: binding.toolClass } : {}),
|
|
309
|
+
...(binding?.shellCommandScope ? { shellCommandScope: binding.shellCommandScope } : {}),
|
|
310
|
+
});
|
|
311
|
+
if (typeof event.toolCallId !== "string" || !binding) {
|
|
312
|
+
this.unboundResults += 1;
|
|
313
|
+
// Lifecycle/reload can route a late final result to a runner that did not
|
|
314
|
+
// observe the originating tool_call. Keep the diagnostic count, but do
|
|
315
|
+
// not attribute its bytes/class/parser policy to the current session or
|
|
316
|
+
// branch. `record()` remains the explicit API for standalone fixtures.
|
|
317
|
+
return observation;
|
|
318
|
+
}
|
|
319
|
+
this.bindings.delete(event.toolCallId);
|
|
320
|
+
this.aggregateObservation(event, observation);
|
|
321
|
+
return observation;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
pendingCallCount(): number {
|
|
325
|
+
return this.bindings.size;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
clearTransientBindings(): void {
|
|
329
|
+
this.bindings.clear();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private aggregateObservation(event: ToolResultLike, observation: ContextGatewayObservation): void {
|
|
333
|
+
this.lastObservation = observation;
|
|
334
|
+
const nativePolicy = nativePolicyFromResult(event);
|
|
335
|
+
if (nativePolicy) {
|
|
336
|
+
this.nativePolicy.results += 1;
|
|
337
|
+
if (nativePolicy.outputMode === "full") this.nativePolicy.fullOverrides += 1;
|
|
338
|
+
if (nativePolicy.refused) {
|
|
339
|
+
this.nativePolicy.refusals += 1;
|
|
340
|
+
const reason = nativePolicy.reason ?? "unknown";
|
|
341
|
+
this.nativePolicy.byReason[reason] = (this.nativePolicy.byReason[reason] ?? 0) + 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (observation.testOutput) {
|
|
345
|
+
const parsed = observation.testOutput;
|
|
346
|
+
this.testOutput.results += 1;
|
|
347
|
+
if (parsed.scanLimited) this.testOutput.scanLimitedResults += 1;
|
|
348
|
+
this.testOutput.byClassification[parsed.classification] += 1;
|
|
349
|
+
this.testOutput.byFormat[parsed.format] += 1;
|
|
350
|
+
if (parsed.prospectiveDecision === "compact-candidate") this.testOutput.compactCandidates += 1;
|
|
351
|
+
else this.testOutput.passthroughRecommended += 1;
|
|
352
|
+
}
|
|
353
|
+
this.add(this.totals, observation);
|
|
354
|
+
const classCounters = this.byClass[observation.toolClass] ?? emptyCounters();
|
|
355
|
+
this.byClass[observation.toolClass] = classCounters;
|
|
356
|
+
this.add(classCounters, observation);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Backward-compatible convenience for observation-only callers without a tool_call event. */
|
|
360
|
+
record(event: ToolResultLike, maxResultBytes: number): ContextGatewayObservation {
|
|
361
|
+
const observation = observeToolResult(event, maxResultBytes);
|
|
362
|
+
this.aggregateObservation(event, observation);
|
|
363
|
+
return observation;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
reset(): void {
|
|
367
|
+
this.totals = emptyCounters();
|
|
368
|
+
this.byClass = {};
|
|
369
|
+
this.bindings.clear();
|
|
370
|
+
this.seenInputFingerprints.clear();
|
|
371
|
+
this.unboundResults = 0;
|
|
372
|
+
this.repeatCandidateCount = 0;
|
|
373
|
+
this.retrievalCalls = 0;
|
|
374
|
+
this.nativePolicy = { results: 0, refusals: 0, fullOverrides: 0, byReason: {} };
|
|
375
|
+
this.testOutput = emptyTestOutputTelemetry();
|
|
376
|
+
this.lastObservation = undefined;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
snapshot(): ContextGatewayTelemetrySnapshot {
|
|
380
|
+
return {
|
|
381
|
+
version: 1,
|
|
382
|
+
...this.totals,
|
|
383
|
+
pendingCalls: this.bindings.size,
|
|
384
|
+
unboundResults: this.unboundResults,
|
|
385
|
+
repeatCandidateCount: this.repeatCandidateCount,
|
|
386
|
+
retrievalCalls: this.retrievalCalls,
|
|
387
|
+
nativePolicy: {
|
|
388
|
+
...this.nativePolicy,
|
|
389
|
+
byReason: { ...this.nativePolicy.byReason },
|
|
390
|
+
},
|
|
391
|
+
testOutput: {
|
|
392
|
+
...this.testOutput,
|
|
393
|
+
byClassification: { ...this.testOutput.byClassification },
|
|
394
|
+
byFormat: { ...this.testOutput.byFormat },
|
|
395
|
+
},
|
|
396
|
+
byClass: Object.fromEntries(
|
|
397
|
+
Object.entries(this.byClass).map(([key, value]) => [key, value ? { ...value } : value]),
|
|
398
|
+
) as ContextGatewayTelemetrySnapshot["byClass"],
|
|
399
|
+
...(this.lastObservation ? { lastObservation: structuredClone(this.lastObservation) } : {}),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private add(target: ContextGatewayClassTelemetry, observation: ContextGatewayObservation): void {
|
|
404
|
+
target.results += 1;
|
|
405
|
+
if (observation.outcome === "error") target.errors += 1;
|
|
406
|
+
target.contentBytes += observation.source.contentBytes;
|
|
407
|
+
target.textBytes += observation.source.textBytes;
|
|
408
|
+
target.imageBytes += observation.source.imageBytes;
|
|
409
|
+
target.detailsBytes += observation.source.detailsBytes;
|
|
410
|
+
if (observation.source.completeness === "upstream-truncated") target.upstreamTruncatedResults += 1;
|
|
411
|
+
if (observation.overBudget) target.overBudgetResults += 1;
|
|
412
|
+
target.potentialBytesOverBudget += observation.potentialBytesOverBudget;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function classifyRoleParts(messages: readonly unknown[]): ContextGatewayRolePartBytes {
|
|
417
|
+
const accounting = accountContextGatewayParts(messages);
|
|
418
|
+
return {
|
|
419
|
+
userTextBytes: accounting.categories.userText.bytes,
|
|
420
|
+
assistantProseBytes: accounting.categories.assistantProse.bytes,
|
|
421
|
+
assistantThinkingBytes: accounting.categories.assistantThinking.bytes,
|
|
422
|
+
assistantToolCallArgumentBytes: accounting.categories.assistantToolCallArguments.bytes,
|
|
423
|
+
toolResultTextBytes: accounting.categories.toolResultText.bytes,
|
|
424
|
+
controlOrUnknownTextBytes: accounting.categories.controlOrUnknownText.bytes,
|
|
425
|
+
metadataDetailsBytes: accounting.metadataDetailsBytes,
|
|
426
|
+
messageWrapperBytes: accounting.messageWrapperBytes,
|
|
427
|
+
unknownParts: accounting.unknownParts,
|
|
428
|
+
};
|
|
429
|
+
}
|