@alexeiled/pi-fusion 0.3.1 → 0.5.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.
@@ -0,0 +1,335 @@
1
+ import type {
2
+ ModelAttempt,
3
+ PanelConfidence,
4
+ PanelDecision,
5
+ PanelOutput,
6
+ ProviderFailure,
7
+ RunObservation,
8
+ RunUsage,
9
+ } from "./types.js";
10
+ import { isFiniteNumber, isRecord } from "./utils.js";
11
+
12
+ export const PANEL_DECISION_OPEN = "<fusion-panel-decision>";
13
+ export const PANEL_DECISION_CLOSE = "</fusion-panel-decision>";
14
+
15
+ export function extractPanelDecision(
16
+ value: unknown,
17
+ ): PanelDecision | undefined {
18
+ if (typeof value === "string") return extractTaggedPanelDecision(value);
19
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
20
+ return extractTaggedPanelDecision(value.join("\n"));
21
+ }
22
+ return isRecord(value) ? panelDecisionFromRecord(value) : undefined;
23
+ }
24
+
25
+ function extractTaggedPanelDecision(value: string): PanelDecision | undefined {
26
+ const openIndex = value.lastIndexOf(PANEL_DECISION_OPEN);
27
+ if (openIndex < 0) return undefined;
28
+ const jsonStart = openIndex + PANEL_DECISION_OPEN.length;
29
+ const closeIndex = value.indexOf(PANEL_DECISION_CLOSE, jsonStart);
30
+ if (closeIndex < 0) return undefined;
31
+ const closeEnd = closeIndex + PANEL_DECISION_CLOSE.length;
32
+ if (value.slice(closeEnd).trim()) return undefined;
33
+
34
+ let parsed: unknown;
35
+ try {
36
+ parsed = JSON.parse(value.slice(jsonStart, closeIndex));
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ if (!isRecord(parsed)) return undefined;
41
+
42
+ const answerMarkdown = value.slice(0, openIndex).trim();
43
+ return panelDecisionFromRecord(parsed, answerMarkdown);
44
+ }
45
+
46
+ function panelDecisionFromRecord(
47
+ value: Record<string, unknown>,
48
+ fallbackAnswerMarkdown?: string,
49
+ ): PanelDecision | undefined {
50
+ const recommendation = firstNonBlankString(value.recommendation);
51
+ const answerMarkdown =
52
+ firstNonBlankString(value.answerMarkdown) ?? fallbackAnswerMarkdown;
53
+ const confidence = value.confidence;
54
+ if (
55
+ !recommendation ||
56
+ !answerMarkdown ||
57
+ !isPanelConfidence(confidence) ||
58
+ typeof value.needsMoreEvidence !== "boolean"
59
+ ) {
60
+ return undefined;
61
+ }
62
+ return {
63
+ recommendation,
64
+ confidence,
65
+ needsMoreEvidence: value.needsMoreEvidence,
66
+ answerMarkdown,
67
+ };
68
+ }
69
+
70
+ export function normalizeRecommendation(value: string): string {
71
+ return value
72
+ .toLocaleLowerCase()
73
+ .replace(/[^\p{L}\p{N}]+/gu, " ")
74
+ .trim();
75
+ }
76
+
77
+ export function hasStrongPanelAgreement(
78
+ outputs: readonly PanelOutput[],
79
+ completedPanelCount: number,
80
+ totalPanelCount: number,
81
+ ): boolean {
82
+ if (outputs.length < 2 || completedPanelCount >= totalPanelCount) {
83
+ return false;
84
+ }
85
+ const decisions = outputs.map((output) => output.decision);
86
+ if (decisions.some((decision) => !decision)) return false;
87
+ if (
88
+ decisions.some(
89
+ (decision) =>
90
+ decision?.confidence !== "high" || decision.needsMoreEvidence,
91
+ )
92
+ ) {
93
+ return false;
94
+ }
95
+ const recommendation = normalizeRecommendation(
96
+ decisions[0]?.recommendation ?? "",
97
+ );
98
+ return Boolean(
99
+ recommendation &&
100
+ decisions.every(
101
+ (decision) =>
102
+ decision &&
103
+ normalizeRecommendation(decision.recommendation) === recommendation,
104
+ ),
105
+ );
106
+ }
107
+
108
+ export function mergeRunObservations(
109
+ base: RunObservation | undefined,
110
+ latest: RunObservation | undefined,
111
+ ): RunObservation {
112
+ if (!base) return latest ? cloneObservation(latest) : {};
113
+ if (!latest) return cloneObservation(base);
114
+ const failures = summarizeProviderFailures(
115
+ latest.providerFailures ?? base.providerFailures ?? [],
116
+ ).map(({ count, ...failure }) =>
117
+ count && count > 1 ? { ...failure, count } : failure,
118
+ );
119
+ return {
120
+ ...((latest.model ?? base.model)
121
+ ? { model: latest.model ?? base.model }
122
+ : {}),
123
+ ...((latest.durationMs ?? base.durationMs) !== undefined
124
+ ? { durationMs: latest.durationMs ?? base.durationMs }
125
+ : {}),
126
+ ...mergeUsage(base.usage, latest.usage),
127
+ ...((latest.attempts ?? base.attempts)
128
+ ? {
129
+ attempts: [...(latest.attempts ?? base.attempts ?? [])].map(
130
+ (attempt) => ({ ...attempt }),
131
+ ),
132
+ }
133
+ : {}),
134
+ ...(failures.length > 0 ? { providerFailures: failures } : {}),
135
+ };
136
+ }
137
+
138
+ export function extractRunObservation(value: unknown): RunObservation {
139
+ if (!isRecord(value)) return {};
140
+
141
+ const model = firstString(value.model);
142
+ const durationMs = extractDuration(value);
143
+ const usage = extractUsage(value);
144
+ const attempts = extractAttempts(value.modelAttempts);
145
+ const attemptFailures = attempts.flatMap((attempt) =>
146
+ attempt.success || !attempt.error
147
+ ? []
148
+ : [providerFailureFromAttempt(attempt)],
149
+ );
150
+ const rawError = firstNonBlankString(value.error);
151
+ const providerFailures = summarizeProviderFailures(
152
+ rawError &&
153
+ attemptFailures.length === 0 &&
154
+ (value.success === false || value.state === "failed")
155
+ ? [
156
+ {
157
+ provider: model ? providerFromModel(model) : "unknown provider",
158
+ ...(model ? { model } : {}),
159
+ message: rawError,
160
+ },
161
+ ]
162
+ : attemptFailures,
163
+ ).map(({ count, ...failure }) =>
164
+ count && count > 1 ? { ...failure, count } : failure,
165
+ );
166
+
167
+ return {
168
+ ...(model ? { model } : {}),
169
+ ...(durationMs !== undefined ? { durationMs } : {}),
170
+ ...(usage ? { usage } : {}),
171
+ ...(attempts.length > 0 ? { attempts } : {}),
172
+ ...(providerFailures.length > 0 ? { providerFailures } : {}),
173
+ };
174
+ }
175
+
176
+ export function summarizeProviderFailures(
177
+ failures: readonly ProviderFailure[],
178
+ ): ProviderFailure[] {
179
+ const grouped = new Map<string, ProviderFailure & { count: number }>();
180
+ for (const failure of failures) {
181
+ const message = failure.message.trim();
182
+ if (!message) continue;
183
+ const key = `${failure.provider}\u0000${failure.model ?? ""}\u0000${message}`;
184
+ const existing = grouped.get(key);
185
+ if (existing) {
186
+ existing.count += failure.count ?? 1;
187
+ continue;
188
+ }
189
+ grouped.set(key, {
190
+ ...failure,
191
+ message,
192
+ count: failure.count ?? 1,
193
+ });
194
+ }
195
+
196
+ return [...grouped.values()]
197
+ .sort((left, right) =>
198
+ `${left.provider}\u0000${left.model ?? ""}\u0000${left.message}`.localeCompare(
199
+ `${right.provider}\u0000${right.model ?? ""}\u0000${right.message}`,
200
+ ),
201
+ )
202
+ .map(({ count, ...failure }) => ({ ...failure, count }));
203
+ }
204
+
205
+ function mergeUsage(
206
+ base: RunUsage | undefined,
207
+ latest: RunUsage | undefined,
208
+ ): { usage?: RunUsage } {
209
+ if (!base && !latest) return {};
210
+ const inputTokens = latest?.inputTokens ?? base?.inputTokens;
211
+ const outputTokens = latest?.outputTokens ?? base?.outputTokens;
212
+ const costUsd = latest?.costUsd ?? base?.costUsd;
213
+ const usage: RunUsage = {
214
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
215
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
216
+ ...(costUsd !== undefined ? { costUsd } : {}),
217
+ };
218
+ return { usage };
219
+ }
220
+
221
+ function cloneObservation(observation: RunObservation): RunObservation {
222
+ return {
223
+ ...observation,
224
+ ...(observation.usage ? { usage: { ...observation.usage } } : {}),
225
+ ...(observation.attempts
226
+ ? { attempts: observation.attempts.map((attempt) => ({ ...attempt })) }
227
+ : {}),
228
+ ...(observation.providerFailures
229
+ ? {
230
+ providerFailures: observation.providerFailures.map((failure) => ({
231
+ ...failure,
232
+ })),
233
+ }
234
+ : {}),
235
+ };
236
+ }
237
+
238
+ function extractDuration(value: Record<string, unknown>): number | undefined {
239
+ if (isFiniteNumber(value.durationMs) && value.durationMs >= 0) {
240
+ return value.durationMs;
241
+ }
242
+ if (
243
+ isFiniteNumber(value.startedAt) &&
244
+ isFiniteNumber(value.endedAt) &&
245
+ value.endedAt >= value.startedAt
246
+ ) {
247
+ return value.endedAt - value.startedAt;
248
+ }
249
+ return undefined;
250
+ }
251
+
252
+ function extractUsage(value: Record<string, unknown>): RunUsage | undefined {
253
+ const totalCost = isRecord(value.totalCost) ? value.totalCost : undefined;
254
+ const rawUsage = isRecord(value.usage) ? value.usage : undefined;
255
+ const inputTokens = firstFinite(
256
+ totalCost?.inputTokens,
257
+ rawUsage?.inputTokens,
258
+ rawUsage?.input,
259
+ );
260
+ const outputTokens = firstFinite(
261
+ totalCost?.outputTokens,
262
+ rawUsage?.outputTokens,
263
+ rawUsage?.output,
264
+ );
265
+ const costUsd = firstFinite(
266
+ totalCost?.costUsd,
267
+ rawUsage?.costUsd,
268
+ isRecord(rawUsage?.cost) ? rawUsage.cost.total : undefined,
269
+ );
270
+
271
+ if (
272
+ inputTokens === undefined &&
273
+ outputTokens === undefined &&
274
+ costUsd === undefined
275
+ ) {
276
+ return undefined;
277
+ }
278
+ return {
279
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
280
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
281
+ ...(costUsd !== undefined ? { costUsd } : {}),
282
+ };
283
+ }
284
+
285
+ function extractAttempts(value: unknown): ModelAttempt[] {
286
+ if (!Array.isArray(value)) return [];
287
+ const attempts: ModelAttempt[] = [];
288
+ for (const item of value) {
289
+ if (!isRecord(item)) continue;
290
+ const model = firstString(item.model);
291
+ if (!model || typeof item.success !== "boolean") continue;
292
+ const error = firstString(item.error);
293
+ attempts.push({
294
+ model,
295
+ success: item.success,
296
+ ...(error ? { error } : {}),
297
+ });
298
+ }
299
+ return attempts;
300
+ }
301
+
302
+ function providerFailureFromAttempt(attempt: ModelAttempt): ProviderFailure {
303
+ return {
304
+ provider: providerFromModel(attempt.model),
305
+ model: attempt.model,
306
+ message: attempt.error ?? "model attempt failed",
307
+ };
308
+ }
309
+
310
+ function providerFromModel(model: string): string {
311
+ return model.split("/", 1)[0] || "unknown provider";
312
+ }
313
+
314
+ function firstFinite(...values: readonly unknown[]): number | undefined {
315
+ return values.find(isFiniteNumber);
316
+ }
317
+
318
+ function isPanelConfidence(value: unknown): value is PanelConfidence {
319
+ return value === "low" || value === "medium" || value === "high";
320
+ }
321
+
322
+ function firstNonBlankString(
323
+ ...values: readonly unknown[]
324
+ ): string | undefined {
325
+ for (const value of values) {
326
+ if (typeof value !== "string") continue;
327
+ const trimmed = value.trim();
328
+ if (trimmed) return trimmed;
329
+ }
330
+ return undefined;
331
+ }
332
+
333
+ function firstString(...values: readonly unknown[]): string | undefined {
334
+ return values.find((value): value is string => typeof value === "string");
335
+ }