@erichll/pi-auto-review 0.16.0 → 0.17.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.
@@ -0,0 +1,207 @@
1
+ import {
2
+ completeSimple,
3
+ type ApiStreamSimpleFunction,
4
+ } from "@earendil-works/pi-ai/compat";
5
+ import type { ModelDecision, TranscriptResult } from "../policy.ts";
6
+ import type { PolicyAuditConfig } from "../policy-audit/index.ts";
7
+
8
+ export type ReasoningLevel =
9
+ | "off"
10
+ | "minimal"
11
+ | "low"
12
+ | "medium"
13
+ | "high"
14
+ | "xhigh"
15
+ | "max";
16
+
17
+ export type BoundedSurface = "external_directory" | "path";
18
+
19
+ export type Config = {
20
+ model: string;
21
+ reasoning: ReasoningLevel;
22
+ timeoutMs: number;
23
+ maxTokens: number;
24
+ retries: number;
25
+ maxUserTranscriptTokens: number;
26
+ maxToolTranscriptTokens: number;
27
+ maxRelevantResultTokens: number;
28
+ maxReviewerInputTokens: number;
29
+ breakGlassEnabled: boolean;
30
+ failureMode: "deny" | "defer";
31
+ grantTtlMs: number;
32
+ autoConfirmBoundedAllows: readonly BoundedSurface[];
33
+ policyAudit: Readonly<PolicyAuditConfig>;
34
+ };
35
+
36
+ export type CompletionMessage = {
37
+ stopReason?: string;
38
+ responseModel?: string;
39
+ errorMessage?: string;
40
+ content?: Array<{ type?: string; text?: string }>;
41
+ usage?: {
42
+ input?: number;
43
+ output?: number;
44
+ cacheRead?: number;
45
+ cacheWrite?: number;
46
+ reasoning?: number;
47
+ totalTokens?: number;
48
+ };
49
+ };
50
+
51
+ export type UsageAvailability =
52
+ | "reported"
53
+ | "estimated"
54
+ | "unavailable"
55
+ | "unknown_provenance";
56
+
57
+ export type ReviewUsage = {
58
+ input?: number;
59
+ output?: number;
60
+ cacheRead?: number;
61
+ cacheWrite?: number;
62
+ reasoning?: number;
63
+ totalTokens?: number;
64
+ observedInputTokens?: number;
65
+ };
66
+
67
+ export type ReviewAttemptStatus =
68
+ | "success"
69
+ | "format_error"
70
+ | "non_stop"
71
+ | "transport_failure"
72
+ | "timeout"
73
+ | "abort";
74
+
75
+ export type ReviewErrorClass =
76
+ | "none"
77
+ | "non_json"
78
+ | "schema"
79
+ | "empty_output"
80
+ | "output_limit"
81
+ | "provider_stop"
82
+ | "transient_connection"
83
+ | "transient_server"
84
+ | "rate_limit"
85
+ | "timeout"
86
+ | "abort"
87
+ | "authentication"
88
+ | "model_resolution"
89
+ | "request_configuration"
90
+ | "circuit_breaker"
91
+ | "critical_evidence_overflow"
92
+ | "required_profile_overflow"
93
+ | "reviewer_input_budget_exceeded"
94
+ | "unknown";
95
+
96
+ export type ReviewAttemptObservation = {
97
+ attempt: number;
98
+ model: string;
99
+ status: ReviewAttemptStatus;
100
+ errorClass: ReviewErrorClass;
101
+ stopReason: "stop" | "length" | "toolUse" | "error" | "aborted" | "deferred" | "unknown";
102
+ durationMs: number;
103
+ willRetry: boolean;
104
+ usageAvailability: UsageAvailability;
105
+ usage: ReviewUsage;
106
+ };
107
+
108
+ export type PreflightPart = { characters: number; estimatedTokens: number };
109
+
110
+ export type ReviewPreflight = {
111
+ estimator: "conservative:cjk-aware";
112
+ maxReviewerInputTokens: number;
113
+ framingReserveTokens: number;
114
+ fixedPrompt: PreflightPart;
115
+ canonicalRequest: PreflightPart;
116
+ override: PreflightPart;
117
+ user: PreflightPart;
118
+ tool: PreflightPart;
119
+ relevantResult: PreflightPart;
120
+ framing: PreflightPart;
121
+ total: PreflightPart;
122
+ };
123
+
124
+ export type ReviewExecutionSummary = {
125
+ attempts: ReviewAttemptObservation[];
126
+ errorCounts: Partial<Record<Exclude<ReviewErrorClass, "none">, number>>;
127
+ durationMs: number;
128
+ transcript: TranscriptResult;
129
+ preflight: ReviewPreflight;
130
+ };
131
+
132
+ export type ReviewerTelemetryEvent =
133
+ | ({
134
+ type: "review_attempt";
135
+ requestId: string;
136
+ surface: string;
137
+ } & ReviewAttemptObservation)
138
+ | {
139
+ type: "review_complete";
140
+ requestId: string;
141
+ surface: string;
142
+ model: string;
143
+ reasoning: ReasoningLevel;
144
+ outcome: "allow" | "deny" | "defer";
145
+ failureMode?: "deny" | "defer";
146
+ attempts: number;
147
+ errorCounts: ReviewExecutionSummary["errorCounts"];
148
+ durationMs: number;
149
+ usageAvailability: UsageAvailability;
150
+ usage: ReviewUsage;
151
+ transcript: {
152
+ userCharacters: number;
153
+ toolCharacters: number;
154
+ relevantResultCharacters: number;
155
+ truncated: boolean;
156
+ selectedCandidates: TranscriptResult["selectedCandidates"];
157
+ failureCode?: TranscriptResult["failureCode"];
158
+ userAuthorizationCeiling: TranscriptResult["userAuthorizationCeiling"];
159
+ userConstraint: TranscriptResult["userConstraint"];
160
+ compactionState: TranscriptResult["compactionState"];
161
+ budgetRemovals: TranscriptResult["budgetRemovals"];
162
+ };
163
+ preflight: ReviewPreflight;
164
+ };
165
+
166
+ export type ReviewerRuntime = {
167
+ model: Parameters<typeof completeSimple>[0];
168
+ auth: {
169
+ apiKey?: string;
170
+ // ProviderHeaders allows null values (e.g. "clear" directives) in 0.84.x.
171
+ headers?: Record<string, string | null>;
172
+ env?: Record<string, string>;
173
+ };
174
+ streamSimple?: ApiStreamSimpleFunction;
175
+ sessionId: string;
176
+ };
177
+
178
+ // Model/stream metadata is re-resolved for every review instead of being
179
+ // cached for the session: pi can refresh models.json or re-register a
180
+ // provider while a session is live, and the resolved model's baseUrl, api
181
+ // or streamSimple may have changed, so a per-session cache could keep
182
+ // calling a stale endpoint. Authentication is also deliberately excluded
183
+ // because pi resolves models.json auth and headers dynamically on every
184
+ // request (including OAuth refresh); it is reacquired per model call in
185
+ // complete().
186
+ export type ReviewerMeta = Omit<ReviewerRuntime, "auth" | "sessionId">;
187
+
188
+ export type ReviewResult = {
189
+ decision: ModelDecision;
190
+ attempts: number;
191
+ retryErrors: ReviewErrorClass[];
192
+ durationMs: number;
193
+ transcript: TranscriptResult;
194
+ summary: ReviewExecutionSummary;
195
+ unavailable?: boolean;
196
+ };
197
+
198
+ export class ReviewExecutionError extends Error {
199
+ constructor(
200
+ readonly errorClass: ReviewErrorClass,
201
+ readonly summary: ReviewExecutionSummary,
202
+ ) {
203
+ super(`automatic review failed (${errorClass})`);
204
+ this.name = "ReviewExecutionError";
205
+ }
206
+ }
207
+
@@ -454,7 +454,7 @@ export function buildUserReviewGroupLines(
454
454
  data: UserReviewGroupEntryData,
455
455
  ): UserReviewEntryData {
456
456
  if (data.members.length === 1) {
457
- const member = data.members[0];
457
+ const member = data.members[0]!;
458
458
  const lines = buildUserReviewLines(member);
459
459
  if (member.permissionResult === "deny" && member.outcome !== "deny") {
460
460
  lines.push("Local confirmation · denied");