@danypops/jittor 0.5.0 → 0.6.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/README.md +51 -7
- package/docs/BENCHMARK_SOURCES.md +32 -0
- package/docs/OUTPUT_CHANNELS.md +31 -0
- package/docs/PROVIDER_RESEARCH.md +38 -0
- package/extension/src/benchmark-tui.ts +103 -0
- package/extension/src/footer.ts +43 -3
- package/extension/src/index.ts +196 -33
- package/extension/src/tui.ts +32 -7
- package/extension/src/usage.ts +147 -38
- package/package.json +4 -1
- package/src/adapters/metric-benchmark-store.ts +99 -0
- package/src/adapters/openrouter-benchmark-index-source.ts +93 -0
- package/src/adapters/openrouter-benchmark-source.ts +109 -0
- package/src/cli.ts +531 -9
- package/src/constants.ts +38 -1
- package/src/daemon.ts +23 -2
- package/src/domain/benchmark.ts +264 -0
- package/src/domain/context-telemetry.ts +31 -0
- package/src/domain/metric.ts +46 -5
- package/src/domain/model-observation.ts +181 -0
- package/src/domain/model-ranking-service.ts +40 -0
- package/src/domain/model-ranking.ts +213 -0
- package/src/domain/usage.ts +84 -7
- package/src/ports/benchmark-controller.ts +11 -0
- package/src/ports/benchmark-source.ts +7 -0
- package/src/ports/benchmark-store.ts +7 -0
- package/src/ports/router-controller.ts +1 -0
- package/src/providers/anthropic-contracts.ts +116 -0
- package/src/providers/google-vertex-contracts.ts +104 -0
- package/src/router.ts +12 -0
- package/src/service.ts +60 -3
package/extension/src/index.ts
CHANGED
|
@@ -15,11 +15,16 @@ import {
|
|
|
15
15
|
import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureKind, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
|
|
16
16
|
import { CompactionTelemetry, papyrusContextMetric, validatePapyrusContextInjection } from "../../src/domain/context-telemetry.ts";
|
|
17
17
|
import type { MetricObservation, StoredMetricObservation } from "../../src/domain/metric.ts";
|
|
18
|
+
import { classifyTaskFromTools, modelRunMetrics, TASK_CLASSES, type ModelRunObservation, type ModelTaskClass } from "../../src/domain/model-observation.ts";
|
|
19
|
+
import type { ModelCandidate } from "../../src/domain/model-ranking.ts";
|
|
18
20
|
import { USAGE_PERIODS, type UsagePeriod } from "../../src/domain/usage.ts";
|
|
19
21
|
import type { PolicyDecision, Route } from "../../src/policy.ts";
|
|
20
22
|
import type { RouterStatus } from "../../src/ports/router-controller.ts";
|
|
23
|
+
import { hasAnthropicRateLimitHeaders, parseAnthropicRateLimitHeaders } from "../../src/providers/anthropic-contracts.ts";
|
|
21
24
|
import { parseCodexRateLimitHeaders } from "../../src/providers/codex.ts";
|
|
22
|
-
import {
|
|
25
|
+
import { classifyGoogleVertexFailure, googleVertexFailureMetrics, type GoogleVertexFailureMetadata } from "../../src/providers/google-vertex-contracts.ts";
|
|
26
|
+
import { showBenchmarkPanel } from "./benchmark-tui.ts";
|
|
27
|
+
import { installIntegratedFooter, type CompactionProgress, type IntegratedFooterState } from "./footer.ts";
|
|
23
28
|
import { callJittor } from "./service-client.ts";
|
|
24
29
|
import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
25
30
|
import { showSettingsPanel } from "./settings-tui.ts";
|
|
@@ -83,6 +88,16 @@ async function recordMetrics(client: JittorExtensionClient, metrics: MetricObser
|
|
|
83
88
|
for (const metric of metrics) await client.call("metrics.record", metric);
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
interface ActiveLocalModelRun {
|
|
92
|
+
runId: string;
|
|
93
|
+
startedAt: number;
|
|
94
|
+
firstTokenAt: number | null;
|
|
95
|
+
providerResponses: number;
|
|
96
|
+
toolNames: string[];
|
|
97
|
+
toolCalls: number;
|
|
98
|
+
toolFailures: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState): Promise<void> {
|
|
87
102
|
const status = await client.call("router.status", {}) as RouterStatus;
|
|
88
103
|
const provider = status.currentRoute?.provider;
|
|
@@ -139,6 +154,17 @@ function modelCost(model: PiRouteModel): number {
|
|
|
139
154
|
return (model.cost?.input ?? 0) + (model.cost?.output ?? 0);
|
|
140
155
|
}
|
|
141
156
|
|
|
157
|
+
export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: string): ModelCandidate[] {
|
|
158
|
+
const candidates: ModelCandidate[] = [];
|
|
159
|
+
for (const model of models) {
|
|
160
|
+
if (!model.provider || !model.id || candidates.some((candidate) => candidate.provider === model.provider && candidate.model === model.id)) continue;
|
|
161
|
+
const level = supportsThinking(model, thinking) ? thinking : "off";
|
|
162
|
+
candidates.push({ provider: model.provider, model: model.id, thinking: level });
|
|
163
|
+
if (candidates.length >= MAX_DYNAMIC_ROUTES) break;
|
|
164
|
+
}
|
|
165
|
+
return candidates;
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
|
|
143
169
|
const sameProvider = models
|
|
144
170
|
.filter((model) => model.provider === current.provider)
|
|
@@ -231,6 +257,9 @@ export function registerJittorExtension(
|
|
|
231
257
|
const footerState: IntegratedFooterState = { providerBudget: null };
|
|
232
258
|
const usageBudgets = usageBudgetControl(enforcement);
|
|
233
259
|
let compactionTelemetry = new CompactionTelemetry();
|
|
260
|
+
let localRunSequence = 0;
|
|
261
|
+
let activeLocalRun: ActiveLocalModelRun | undefined;
|
|
262
|
+
let lastCompletedLocalRun: ModelRunObservation | undefined;
|
|
234
263
|
const contextObservations = new Set<string>();
|
|
235
264
|
const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
|
|
236
265
|
try {
|
|
@@ -255,6 +284,7 @@ export function registerJittorExtension(
|
|
|
255
284
|
let recoveryTimer: unknown;
|
|
256
285
|
let recoveryCooldown: { until: number; attempt: number; failureKind: CodexFailureKind } | undefined;
|
|
257
286
|
let lastCodexResponse: CodexFailureMetadata = {};
|
|
287
|
+
let lastGoogleVertexResponse: GoogleVertexFailureMetadata = {};
|
|
258
288
|
const cancelRecovery = (resetPolicy: boolean): void => {
|
|
259
289
|
if (recoveryTimer !== undefined) recoveryRuntime.clearTimeout(recoveryTimer);
|
|
260
290
|
recoveryTimer = undefined;
|
|
@@ -314,10 +344,21 @@ export function registerJittorExtension(
|
|
|
314
344
|
const beginCompactionUi = (ctx: ExtensionContext, signal: AbortSignal): void => {
|
|
315
345
|
finishCompactionUi();
|
|
316
346
|
const usage = ctx.getContextUsage();
|
|
317
|
-
|
|
347
|
+
const compaction: CompactionProgress = {
|
|
318
348
|
startedAt: Date.now(),
|
|
319
349
|
initialFraction: usage?.percent === null || usage?.percent === undefined ? 1 : usage.percent / 100,
|
|
350
|
+
estimatedMs: null,
|
|
351
|
+
confidence: "cold-start",
|
|
320
352
|
};
|
|
353
|
+
footerState.compaction = compaction;
|
|
354
|
+
// Non-blocking: compaction UI starts immediately as cold-start; if a learned estimate resolves
|
|
355
|
+
// before this compaction finishes (and this is still the active compaction, not a later one),
|
|
356
|
+
// upgrade the same progress object in place so the drain bar and status text switch to "learned".
|
|
357
|
+
void client.call("compaction.estimate", {}).then((estimate) => {
|
|
358
|
+
if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
|
|
359
|
+
footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
|
|
360
|
+
footerState.requestRender?.();
|
|
361
|
+
}).catch(() => undefined);
|
|
321
362
|
compactionTimer = setInterval(() => footerState.requestRender?.(), FOOTER_COMPACTION_RENDER_INTERVAL_MS);
|
|
322
363
|
signal.addEventListener("abort", finishCompactionUi, { once: true });
|
|
323
364
|
if (signal.aborted) finishCompactionUi();
|
|
@@ -353,9 +394,50 @@ export function registerJittorExtension(
|
|
|
353
394
|
};
|
|
354
395
|
|
|
355
396
|
pi.registerCommand("jittor", {
|
|
356
|
-
description: "
|
|
397
|
+
description: "Jittor settings, routing status, benchmarks, and Codex recovery controls",
|
|
357
398
|
handler: async (args, ctx) => {
|
|
358
399
|
const action = args.trim().toLowerCase();
|
|
400
|
+
if (action === "" || action === "settings") {
|
|
401
|
+
await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
|
|
402
|
+
setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
|
|
403
|
+
setFooter: async (enabled) => {
|
|
404
|
+
enforcement.setFooterEnabled(enabled);
|
|
405
|
+
showFooter(ctx);
|
|
406
|
+
if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
|
|
407
|
+
},
|
|
408
|
+
setRecovery: (enabled) => {
|
|
409
|
+
if (!enabled) cancelRecovery(true);
|
|
410
|
+
codexRecovery.setCodexRecoveryEnabled(enabled);
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (action === "benchmarks" || action.startsWith("benchmarks ")) {
|
|
416
|
+
if (!ctx.model) {
|
|
417
|
+
ctx.ui.notify("No active Pi model is available for benchmark recommendations.", "warning");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const requestedTask = action.split(/\s+/)[1] ?? "general";
|
|
421
|
+
if (!TASK_CLASSES.includes(requestedTask as ModelTaskClass)) {
|
|
422
|
+
ctx.ui.notify("Usage: /jittor benchmarks [coding|research|planning|general]", "warning");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const candidates = benchmarkCandidatesFromPi(ctx.modelRegistry.getAvailable() as PiRouteModel[], pi.getThinkingLevel());
|
|
426
|
+
await showBenchmarkPanel(ctx, client, candidates, `${ctx.model.provider}/${ctx.model.id}`, requestedTask as ModelTaskClass);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (action === "outcome accepted" || action === "outcome rejected") {
|
|
430
|
+
if (!lastCompletedLocalRun) {
|
|
431
|
+
ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
|
|
435
|
+
const outcomeMetric = modelRunMetrics({ ...lastCompletedLocalRun, explicitOutcome }).find((metric) => metric.metric === "outcome-accepted")!;
|
|
436
|
+
outcomeMetric.observedAt = Date.now();
|
|
437
|
+
await recordMetrics(client, [outcomeMetric]);
|
|
438
|
+
ctx.ui.notify(`Recorded explicit ${explicitOutcome} outcome for the latest local model run.`, "info");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
359
441
|
if (action === "recovery" || action === "recovery status") {
|
|
360
442
|
ctx.ui.notify(recoveryStatusText(), "info");
|
|
361
443
|
return;
|
|
@@ -403,23 +485,22 @@ export function registerJittorExtension(
|
|
|
403
485
|
].join("\n"), "info");
|
|
404
486
|
return;
|
|
405
487
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
enforcement.setFooterEnabled(enabled);
|
|
411
|
-
showFooter(ctx);
|
|
412
|
-
if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
|
|
413
|
-
},
|
|
414
|
-
setRecovery: (enabled) => {
|
|
415
|
-
if (!enabled) cancelRecovery(true);
|
|
416
|
-
codexRecovery.setCodexRecoveryEnabled(enabled);
|
|
417
|
-
},
|
|
418
|
-
});
|
|
488
|
+
// Reached only for the explicit "status" keyword or any other unrecognized text; bare "" is
|
|
489
|
+
// handled above by the settings branch, so this always has a non-empty, non-settings action.
|
|
490
|
+
if (!enforcement.isEnabled()) {
|
|
491
|
+
ctx.ui.notify("Jittor is monitor-only. Run /jittor on to re-enable blocking.", "info");
|
|
419
492
|
return;
|
|
420
493
|
}
|
|
421
|
-
|
|
422
|
-
|
|
494
|
+
await showJittorPanel(ctx, client);
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
pi.registerCommand("usage", {
|
|
499
|
+
description: "Cumulative token/cost usage graph with hourly/daily/weekly/monthly/quarterly views",
|
|
500
|
+
handler: async (args, ctx) => {
|
|
501
|
+
const action = args.trim().toLowerCase();
|
|
502
|
+
if (action === "budget" || action.startsWith("budget ")) {
|
|
503
|
+
const [, periodText, valueText] = action.split(/\s+/);
|
|
423
504
|
const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? periodText as UsagePeriod : undefined;
|
|
424
505
|
if (!period) {
|
|
425
506
|
const values = USAGE_PERIODS.map(({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`).join(" · ");
|
|
@@ -437,30 +518,29 @@ export function registerJittorExtension(
|
|
|
437
518
|
}
|
|
438
519
|
const tokens = Number(valueText.replaceAll(",", ""));
|
|
439
520
|
if (!Number.isFinite(tokens) || tokens <= 0) {
|
|
440
|
-
ctx.ui.notify("Usage: /
|
|
521
|
+
ctx.ui.notify("Usage: /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
|
|
441
522
|
return;
|
|
442
523
|
}
|
|
443
524
|
usageBudgets.setUsageTokenBudget(period, tokens);
|
|
444
525
|
ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
|
|
445
526
|
return;
|
|
446
527
|
}
|
|
447
|
-
if (action
|
|
448
|
-
|
|
528
|
+
if (action !== "" && action !== "cost" && action !== "tokens") {
|
|
529
|
+
ctx.ui.notify("Usage: /usage [cost] | /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
|
|
449
530
|
return;
|
|
450
531
|
}
|
|
451
|
-
|
|
452
|
-
ctx.ui.notify("Jittor is monitor-only. Run /jittor on to re-enable blocking.", "info");
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
await showJittorPanel(ctx, client);
|
|
532
|
+
await showUsagePanel(ctx, client, usageBudgets, Date.now(), action === "cost" ? "cost" : "tokens");
|
|
456
533
|
},
|
|
457
534
|
});
|
|
458
535
|
|
|
459
536
|
pi.on("session_start", async (_event, ctx) => {
|
|
460
537
|
finishCompactionUi();
|
|
461
538
|
compactionTelemetry = new CompactionTelemetry();
|
|
539
|
+
activeLocalRun = undefined;
|
|
540
|
+
lastCompletedLocalRun = undefined;
|
|
462
541
|
cancelRecovery(true);
|
|
463
542
|
lastCodexResponse = {};
|
|
543
|
+
lastGoogleVertexResponse = {};
|
|
464
544
|
ctx.ui.setStatus("jittor", undefined);
|
|
465
545
|
showFooter(ctx);
|
|
466
546
|
try {
|
|
@@ -533,9 +613,19 @@ export function registerJittorExtension(
|
|
|
533
613
|
await syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
|
|
534
614
|
});
|
|
535
615
|
|
|
536
|
-
pi.on("turn_start", async (
|
|
616
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
537
617
|
compactionTelemetry.observeTurn();
|
|
538
618
|
lastCodexResponse = {};
|
|
619
|
+
lastGoogleVertexResponse = {};
|
|
620
|
+
activeLocalRun = {
|
|
621
|
+
runId: `local-${event.timestamp}-${++localRunSequence}`,
|
|
622
|
+
startedAt: event.timestamp,
|
|
623
|
+
firstTokenAt: null,
|
|
624
|
+
providerResponses: 0,
|
|
625
|
+
toolNames: [],
|
|
626
|
+
toolCalls: 0,
|
|
627
|
+
toolFailures: 0,
|
|
628
|
+
};
|
|
539
629
|
if (!enforcement.isEnabled()) return;
|
|
540
630
|
try {
|
|
541
631
|
await syncCurrentRoute(pi, client, ctx);
|
|
@@ -547,20 +637,84 @@ export function registerJittorExtension(
|
|
|
547
637
|
}
|
|
548
638
|
});
|
|
549
639
|
|
|
640
|
+
pi.on("message_update", async (event) => {
|
|
641
|
+
if (!activeLocalRun || activeLocalRun.firstTokenAt !== null) return;
|
|
642
|
+
if (["text_delta", "thinking_delta", "toolcall_delta"].includes(event.assistantMessageEvent.type)) activeLocalRun.firstTokenAt = Date.now();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
pi.on("tool_execution_end", async (event) => {
|
|
646
|
+
if (!activeLocalRun) return;
|
|
647
|
+
activeLocalRun.toolCalls += 1;
|
|
648
|
+
if (event.isError) activeLocalRun.toolFailures += 1;
|
|
649
|
+
if (activeLocalRun.toolNames.length < 100) activeLocalRun.toolNames.push(event.toolName);
|
|
650
|
+
});
|
|
651
|
+
|
|
550
652
|
pi.on("after_provider_response", async (event, ctx) => {
|
|
653
|
+
if (activeLocalRun) activeLocalRun.providerResponses += 1;
|
|
551
654
|
if (ctx.model?.provider === "openai-codex") {
|
|
552
655
|
lastCodexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
|
|
553
656
|
}
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
657
|
+
if (ctx.model?.provider === "anthropic") {
|
|
658
|
+
const headers = new Headers(event.headers);
|
|
659
|
+
if (hasAnthropicRateLimitHeaders(headers)) {
|
|
660
|
+
try {
|
|
661
|
+
await recordMetrics(client, parseAnthropicRateLimitHeaders(headers, Date.now()).metrics);
|
|
662
|
+
} catch {
|
|
663
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Anthropic telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (ctx.model?.provider === "google-vertex") {
|
|
668
|
+
lastGoogleVertexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
|
|
669
|
+
}
|
|
670
|
+
if (Object.keys(event.headers).some((name) => name.toLowerCase().startsWith("x-codex-"))) {
|
|
671
|
+
try {
|
|
672
|
+
const updates = parseCodexRateLimitHeaders(new Headers(event.headers), Date.now());
|
|
673
|
+
await recordMetrics(client, updates.flatMap((update) => update.metrics));
|
|
674
|
+
} catch {
|
|
675
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Codex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
|
|
676
|
+
}
|
|
560
677
|
}
|
|
561
678
|
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
|
|
562
679
|
});
|
|
563
680
|
|
|
681
|
+
pi.on("turn_end", async (event) => {
|
|
682
|
+
const active = activeLocalRun;
|
|
683
|
+
activeLocalRun = undefined;
|
|
684
|
+
const message = event.message as unknown;
|
|
685
|
+
if (!active || typeof message !== "object" || message === null || Array.isArray(message)) return;
|
|
686
|
+
const value = message as Record<string, unknown>;
|
|
687
|
+
if (value["role"] !== "assistant" || typeof value["provider"] !== "string" || typeof value["model"] !== "string") return;
|
|
688
|
+
const usage = typeof value["usage"] === "object" && value["usage"] !== null ? value["usage"] as Record<string, unknown> : {};
|
|
689
|
+
const amount = (name: string): number => typeof usage[name] === "number" && Number.isFinite(usage[name]) ? usage[name] as number : 0;
|
|
690
|
+
const cost = typeof usage["cost"] === "object" && usage["cost"] !== null && typeof (usage["cost"] as Record<string, unknown>)["total"] === "number"
|
|
691
|
+
? (usage["cost"] as Record<string, number>)["total"] ?? 0 : 0;
|
|
692
|
+
const stopReason = ["stop", "length", "toolUse", "error", "aborted"].includes(String(value["stopReason"]))
|
|
693
|
+
? value["stopReason"] as ModelRunObservation["stopReason"] : "unknown";
|
|
694
|
+
const completedAt = Math.max(Date.now(), active.firstTokenAt ?? active.startedAt, active.startedAt);
|
|
695
|
+
lastCompletedLocalRun = {
|
|
696
|
+
runId: active.runId,
|
|
697
|
+
provider: value["provider"],
|
|
698
|
+
model: value["model"],
|
|
699
|
+
thinking: pi.getThinkingLevel(),
|
|
700
|
+
taskClass: classifyTaskFromTools(active.toolNames),
|
|
701
|
+
startedAt: active.startedAt,
|
|
702
|
+
firstTokenAt: active.firstTokenAt,
|
|
703
|
+
completedAt,
|
|
704
|
+
inputTokens: amount("input"),
|
|
705
|
+
outputTokens: amount("output"),
|
|
706
|
+
cacheReadTokens: amount("cacheRead"),
|
|
707
|
+
cacheWriteTokens: amount("cacheWrite"),
|
|
708
|
+
costUsd: Number.isFinite(cost) && cost >= 0 ? cost : 0,
|
|
709
|
+
providerResponses: Math.max(1, active.providerResponses),
|
|
710
|
+
toolCalls: active.toolCalls,
|
|
711
|
+
toolFailures: active.toolFailures,
|
|
712
|
+
stopReason,
|
|
713
|
+
explicitOutcome: "unknown",
|
|
714
|
+
};
|
|
715
|
+
await recordMetrics(client, modelRunMetrics(lastCompletedLocalRun)).catch(() => undefined);
|
|
716
|
+
});
|
|
717
|
+
|
|
564
718
|
pi.on("message_end", async (event, _ctx) => {
|
|
565
719
|
if (event.message.role === "assistant" && event.message.provider === "openai-codex") {
|
|
566
720
|
if (event.message.stopReason === "error") {
|
|
@@ -572,6 +726,13 @@ export function registerJittorExtension(
|
|
|
572
726
|
}
|
|
573
727
|
lastCodexResponse = {};
|
|
574
728
|
}
|
|
729
|
+
if (event.message.role === "assistant" && event.message.provider === "google-vertex") {
|
|
730
|
+
if (event.message.stopReason === "error") {
|
|
731
|
+
const failure = classifyGoogleVertexFailure(event.message.errorMessage, lastGoogleVertexResponse);
|
|
732
|
+
await recordMetrics(client, googleVertexFailureMetrics(failure, Date.now())).catch(() => undefined);
|
|
733
|
+
}
|
|
734
|
+
lastGoogleVertexResponse = {};
|
|
735
|
+
}
|
|
575
736
|
const metrics = assistantUsageMetrics(event.message, Date.now());
|
|
576
737
|
if (metrics.length > 0) {
|
|
577
738
|
const amount = (name: string): number => metrics.filter((metric) => metric.metric === name && typeof metric.value === "number").reduce((sum, metric) => sum + (metric.value ?? 0), 0);
|
|
@@ -587,6 +748,8 @@ export function registerJittorExtension(
|
|
|
587
748
|
stopPapyrusContext?.();
|
|
588
749
|
cancelRecovery(true);
|
|
589
750
|
lastCodexResponse = {};
|
|
751
|
+
activeLocalRun = undefined;
|
|
752
|
+
lastCompletedLocalRun = undefined;
|
|
590
753
|
ctx.ui.setStatus("jittor", undefined);
|
|
591
754
|
ctx.ui.setFooter(undefined);
|
|
592
755
|
});
|
package/extension/src/tui.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { HUMAN_STATUS_MAX_SOURCES, HUMAN_TEXT_FIELD_MAX_CHARACTERS } from "../../src/constants.ts";
|
|
3
4
|
import type { StoredMetricObservation } from "../../src/domain/metric.ts";
|
|
4
5
|
import type { PolicyAction, Route } from "../../src/policy.ts";
|
|
5
6
|
import type { RouterStatus } from "../../src/ports/router-controller.ts";
|
|
@@ -16,7 +17,11 @@ function latest(rows: StoredMetricObservation[], predicate: (row: StoredMetricOb
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function sanitizedText(value: string): string {
|
|
19
|
-
return value.replace(/[\r\n\t]/g, " ").replace(/ +/g, " ").trim();
|
|
20
|
+
return value.replace(/[\r\n\t]/g, " ").replace(/ +/g, " ").trim().slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function routeText(route: Route): string {
|
|
24
|
+
return `${sanitizedText(route.provider)}/${sanitizedText(route.model)} · ${sanitizedText(route.thinking)}`;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
function normalizedIdentity(value: unknown): string {
|
|
@@ -72,6 +77,19 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
72
77
|
...(Number.isFinite(resetsAtSeconds) && resetsAtSeconds > 0 ? { resetsAt: resetsAtSeconds * 1_000 } : {}),
|
|
73
78
|
};
|
|
74
79
|
}
|
|
80
|
+
if (status.currentRoute.provider === "anthropic") {
|
|
81
|
+
const anthropic = latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number")
|
|
82
|
+
?? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number");
|
|
83
|
+
if (!anthropic || typeof anthropic.value !== "number") return null;
|
|
84
|
+
const resetsAt = Number(anthropic.attributes["resetsAt"]);
|
|
85
|
+
return {
|
|
86
|
+
kind: "bounded",
|
|
87
|
+
label: anthropic.scope === "tokens" ? "tok" : "req",
|
|
88
|
+
remainingFraction: 1 - anthropic.value,
|
|
89
|
+
observedAt: anthropic.observedAt,
|
|
90
|
+
...(Number.isFinite(resetsAt) && resetsAt > 0 ? { resetsAt } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
75
93
|
if (status.currentRoute.provider === "openrouter") {
|
|
76
94
|
const openRouter = latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number");
|
|
77
95
|
const remaining = latest(metrics, (row) => row.source === "openrouter" && row.metric === "remaining-fraction" && typeof row.value === "number");
|
|
@@ -135,15 +153,22 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
|
|
|
135
153
|
? latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number")
|
|
136
154
|
: undefined;
|
|
137
155
|
if (openRouter && typeof openRouter.value === "number") lines.push(`OpenRouter spend: $${openRouter.value.toFixed(3)}`);
|
|
138
|
-
|
|
156
|
+
const anthropic = status.currentRoute?.provider === "anthropic"
|
|
157
|
+
? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number")
|
|
158
|
+
?? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number")
|
|
159
|
+
: undefined;
|
|
160
|
+
if (anthropic && typeof anthropic.value === "number") lines.push(`Anthropic ${anthropic.scope}: ${((1 - anthropic.value) * 100).toFixed(1)}% left`);
|
|
161
|
+
if (status.currentRoute) lines.push(`Route: ${routeText(status.currentRoute)}`);
|
|
139
162
|
if (status.lastDecision) lines.push(`Pressure: ${Number.isFinite(status.lastDecision.pressure) ? status.lastDecision.pressure.toFixed(3) : "∞"} · ${status.lastDecision.action}`);
|
|
140
163
|
lines.push(`Next: ${nextAction(status.lastDecision?.action)}`);
|
|
141
164
|
lines.push("Telemetry:");
|
|
142
|
-
|
|
165
|
+
const providerSources = status.sources.filter((source) => source.provider === status.currentRoute?.provider);
|
|
166
|
+
for (const source of providerSources.slice(0, HUMAN_STATUS_MAX_SOURCES)) {
|
|
143
167
|
const freshness = !source.ok ? "failed" : source.observedAt !== undefined && now - source.observedAt > 120_000 ? "stale" : "fresh";
|
|
144
|
-
lines.push(` ${source.id}: ${freshness} · ${source.metrics} metrics`);
|
|
168
|
+
lines.push(` ${sanitizedText(source.id)}: ${freshness} · ${source.metrics} metrics`);
|
|
145
169
|
}
|
|
146
|
-
if (
|
|
170
|
+
if (providerSources.length > HUMAN_STATUS_MAX_SOURCES) lines.push(` … ${providerSources.length - HUMAN_STATUS_MAX_SOURCES} more telemetry sources omitted`);
|
|
171
|
+
if (status.override) lines.push(`Override: ${routeText(status.override.route)}`);
|
|
147
172
|
if (status.paused) lines.push("Emergency halt is active");
|
|
148
173
|
return lines;
|
|
149
174
|
}
|
|
@@ -160,7 +185,7 @@ async function snapshot(client: JittorPanelClient): Promise<{ status: RouterStat
|
|
|
160
185
|
|
|
161
186
|
async function chooseOverride(ctx: ExtensionCommandContext, routes: Route[]): Promise<Route | undefined> {
|
|
162
187
|
if (routes.length === 0) { ctx.ui.notify("Pi reports no authenticated routes for the current provider.", "warning"); return undefined; }
|
|
163
|
-
const labels = routes.map(
|
|
188
|
+
const labels = routes.map(routeText);
|
|
164
189
|
const selected = await ctx.ui.select("Override route", labels);
|
|
165
190
|
const index = selected ? labels.indexOf(selected) : -1;
|
|
166
191
|
return index >= 0 ? routes[index] : undefined;
|
|
@@ -211,7 +236,7 @@ export async function showJittorPanel(ctx: ExtensionCommandContext, client: Jitt
|
|
|
211
236
|
continue;
|
|
212
237
|
}
|
|
213
238
|
const route = await chooseOverride(ctx, current.status.availableRoutes);
|
|
214
|
-
if (route && await ctx.ui.confirm("Apply route override?", `${route
|
|
239
|
+
if (route && await ctx.ui.confirm("Apply route override?", `${routeText(route)} for one hour`)) {
|
|
215
240
|
await client.call("router.override", { route, expiresAt: Date.now() + 60 * 60 * 1_000 });
|
|
216
241
|
}
|
|
217
242
|
}
|