@danypops/jittor 0.5.1 → 0.7.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 +62 -7
- package/docs/BENCHMARK_SOURCES.md +32 -0
- package/docs/OUTPUT_CHANNELS.md +31 -0
- package/docs/PROVIDER_RESEARCH.md +57 -0
- package/extension/src/benchmark-tui.ts +105 -0
- package/extension/src/footer.ts +68 -12
- package/extension/src/index.ts +263 -36
- package/extension/src/tui.ts +61 -9
- package/extension/src/usage.ts +165 -47
- package/package.json +5 -1
- package/src/adapters/metric-benchmark-store.ts +99 -0
- package/src/adapters/openrouter-benchmark-index-source.ts +94 -0
- package/src/adapters/openrouter-benchmark-source.ts +109 -0
- package/src/adapters/sqlite-metric-store.ts +43 -2
- package/src/cli.ts +660 -9
- package/src/client.ts +12 -44
- package/src/constants.ts +69 -5
- package/src/daemon.ts +65 -43
- package/src/db.ts +13 -30
- 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 +203 -0
- package/src/domain/model-ranking-service.ts +41 -0
- package/src/domain/model-ranking.ts +232 -0
- package/src/domain/task-cost.ts +70 -0
- package/src/domain/task-focus.ts +65 -0
- package/src/domain/usage.ts +134 -33
- package/src/log.ts +28 -0
- 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/metric-store.ts +30 -0
- package/src/ports/router-controller.ts +1 -0
- package/src/providers/anthropic-contracts.ts +127 -0
- package/src/providers/google-adc-auth.ts +63 -0
- package/src/providers/google-vertex-budget-contracts.ts +181 -0
- package/src/providers/google-vertex-budget.ts +127 -0
- package/src/providers/google-vertex-contracts.ts +116 -0
- package/src/providers/telemetry-sources.ts +35 -0
- package/src/router.ts +12 -0
- package/src/service.ts +132 -17
- package/src/state.ts +31 -57
- package/src/version.ts +2 -14
package/extension/src/index.ts
CHANGED
|
@@ -10,16 +10,23 @@ import {
|
|
|
10
10
|
MILLISECONDS_PER_MINUTE,
|
|
11
11
|
MILLISECONDS_PER_SECOND,
|
|
12
12
|
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
13
|
+
PAPYRUS_TASK_FOCUS_CHANNEL,
|
|
13
14
|
CONTEXT_EVENT_DEDUP_LIMIT,
|
|
14
15
|
} from "../../src/constants.ts";
|
|
15
16
|
import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureKind, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
|
|
16
17
|
import { CompactionTelemetry, papyrusContextMetric, validatePapyrusContextInjection } from "../../src/domain/context-telemetry.ts";
|
|
18
|
+
import { applyTaskFocusEvent, validateTaskFocusEvent } from "../../src/domain/task-focus.ts";
|
|
17
19
|
import type { MetricObservation, StoredMetricObservation } from "../../src/domain/metric.ts";
|
|
20
|
+
import { classifyTaskFromTools, modelRunMetrics, TASK_DOMAINS, TASK_TYPES, type ModelRunObservation, type ModelTaskDomain, type ModelTaskType } from "../../src/domain/model-observation.ts";
|
|
21
|
+
import type { ModelCandidate } from "../../src/domain/model-ranking.ts";
|
|
18
22
|
import { USAGE_PERIODS, type UsagePeriod } from "../../src/domain/usage.ts";
|
|
19
23
|
import type { PolicyDecision, Route } from "../../src/policy.ts";
|
|
20
24
|
import type { RouterStatus } from "../../src/ports/router-controller.ts";
|
|
25
|
+
import { hasAnthropicRateLimitHeaders, parseAnthropicRateLimitHeaders } from "../../src/providers/anthropic-contracts.ts";
|
|
21
26
|
import { parseCodexRateLimitHeaders } from "../../src/providers/codex.ts";
|
|
22
|
-
import {
|
|
27
|
+
import { classifyGoogleVertexFailure, googleVertexFailureMetrics, type GoogleVertexFailureMetadata } from "../../src/providers/google-vertex-contracts.ts";
|
|
28
|
+
import { showBenchmarkPanel } from "./benchmark-tui.ts";
|
|
29
|
+
import { installIntegratedFooter, type CompactionProgress, type IntegratedFooterState } from "./footer.ts";
|
|
23
30
|
import { callJittor } from "./service-client.ts";
|
|
24
31
|
import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
25
32
|
import { showSettingsPanel } from "./settings-tui.ts";
|
|
@@ -83,6 +90,16 @@ async function recordMetrics(client: JittorExtensionClient, metrics: MetricObser
|
|
|
83
90
|
for (const metric of metrics) await client.call("metrics.record", metric);
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
interface ActiveLocalModelRun {
|
|
94
|
+
runId: string;
|
|
95
|
+
startedAt: number;
|
|
96
|
+
firstTokenAt: number | null;
|
|
97
|
+
providerResponses: number;
|
|
98
|
+
toolNames: string[];
|
|
99
|
+
toolCalls: number;
|
|
100
|
+
toolFailures: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
86
103
|
async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState): Promise<void> {
|
|
87
104
|
const status = await client.call("router.status", {}) as RouterStatus;
|
|
88
105
|
const provider = status.currentRoute?.provider;
|
|
@@ -139,6 +156,17 @@ function modelCost(model: PiRouteModel): number {
|
|
|
139
156
|
return (model.cost?.input ?? 0) + (model.cost?.output ?? 0);
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: string): ModelCandidate[] {
|
|
160
|
+
const candidates: ModelCandidate[] = [];
|
|
161
|
+
for (const model of models) {
|
|
162
|
+
if (!model.provider || !model.id || candidates.some((candidate) => candidate.provider === model.provider && candidate.model === model.id)) continue;
|
|
163
|
+
const level = supportsThinking(model, thinking) ? thinking : "off";
|
|
164
|
+
candidates.push({ provider: model.provider, model: model.id, thinking: level });
|
|
165
|
+
if (candidates.length >= MAX_DYNAMIC_ROUTES) break;
|
|
166
|
+
}
|
|
167
|
+
return candidates;
|
|
168
|
+
}
|
|
169
|
+
|
|
142
170
|
export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
|
|
143
171
|
const sameProvider = models
|
|
144
172
|
.filter((model) => model.provider === current.provider)
|
|
@@ -202,7 +230,8 @@ async function applyDecision(
|
|
|
202
230
|
return halt(ctx, `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`);
|
|
203
231
|
}
|
|
204
232
|
|
|
205
|
-
|
|
233
|
+
/** taskId, when a Papyrus task is currently focused in this session, tags the metric for real-time cost-per-task correlation without any new instrumentation surface. */
|
|
234
|
+
function assistantUsageMetrics(message: unknown, observedAt: number, taskId: string | null = null): MetricObservation[] {
|
|
206
235
|
if (typeof message !== "object" || message === null || Array.isArray(message)) return [];
|
|
207
236
|
const value = message as Record<string, unknown>;
|
|
208
237
|
if (value["role"] !== "assistant" || typeof value["usage"] !== "object" || value["usage"] === null) return [];
|
|
@@ -210,7 +239,7 @@ function assistantUsageMetrics(message: unknown, observedAt: number): MetricObse
|
|
|
210
239
|
const provider = typeof value["provider"] === "string" ? value["provider"] : "unknown";
|
|
211
240
|
const model = typeof value["model"] === "string" ? value["model"] : "unknown";
|
|
212
241
|
const scope = `${provider}:${model}`;
|
|
213
|
-
const attributes = { provider, model };
|
|
242
|
+
const attributes = { provider, model, ...(taskId === null ? {} : { taskId }) };
|
|
214
243
|
const metrics: MetricObservation[] = [];
|
|
215
244
|
for (const [field, metric] of [["input", "input-tokens"], ["output", "output-tokens"], ["cacheRead", "cache-read-tokens"], ["cacheWrite", "cache-write-tokens"]] as const) {
|
|
216
245
|
const amount = usage[field];
|
|
@@ -231,6 +260,9 @@ export function registerJittorExtension(
|
|
|
231
260
|
const footerState: IntegratedFooterState = { providerBudget: null };
|
|
232
261
|
const usageBudgets = usageBudgetControl(enforcement);
|
|
233
262
|
let compactionTelemetry = new CompactionTelemetry();
|
|
263
|
+
let localRunSequence = 0;
|
|
264
|
+
let activeLocalRun: ActiveLocalModelRun | undefined;
|
|
265
|
+
let lastCompletedLocalRun: ModelRunObservation | undefined;
|
|
234
266
|
const contextObservations = new Set<string>();
|
|
235
267
|
const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
|
|
236
268
|
try {
|
|
@@ -245,6 +277,21 @@ export function registerJittorExtension(
|
|
|
245
277
|
// Reject malformed or stale cross-extension observations without retaining payloads.
|
|
246
278
|
}
|
|
247
279
|
});
|
|
280
|
+
// Real-time cost-per-task correlation: Jittor observes Papyrus's task-focus broadcasts (Papyrus
|
|
281
|
+
// never depends on Jittor) and tags newly recorded token/cost metrics with the currently focused
|
|
282
|
+
// task id. Scoped to this Pi session: a focus change in a different concurrent session must not
|
|
283
|
+
// affect this one's attribution.
|
|
284
|
+
let currentSessionId: string | undefined;
|
|
285
|
+
let focusedTaskId: string | null = null;
|
|
286
|
+
const stopPapyrusTaskFocus = pi.events?.on?.(PAPYRUS_TASK_FOCUS_CHANNEL, (payload) => {
|
|
287
|
+
try {
|
|
288
|
+
const event = validateTaskFocusEvent(payload);
|
|
289
|
+
if (event.sessionId !== undefined && event.sessionId !== currentSessionId) return;
|
|
290
|
+
focusedTaskId = applyTaskFocusEvent(event);
|
|
291
|
+
} catch {
|
|
292
|
+
// Reject malformed or stale cross-extension events without retaining payloads or crashing the extension.
|
|
293
|
+
}
|
|
294
|
+
});
|
|
248
295
|
const recoveryPolicy = new CodexRecoveryPolicy({
|
|
249
296
|
baseDelayMs: CODEX_RECOVERY_BASE_DELAY_MS,
|
|
250
297
|
maxDelayMs: CODEX_RECOVERY_MAX_DELAY_MS,
|
|
@@ -255,6 +302,12 @@ export function registerJittorExtension(
|
|
|
255
302
|
let recoveryTimer: unknown;
|
|
256
303
|
let recoveryCooldown: { until: number; attempt: number; failureKind: CodexFailureKind } | undefined;
|
|
257
304
|
let lastCodexResponse: CodexFailureMetadata = {};
|
|
305
|
+
let lastGoogleVertexResponse: GoogleVertexFailureMetadata = {};
|
|
306
|
+
// The third-party "anthropic-vertex" provider (Anthropic Claude via Google Vertex) is tracked
|
|
307
|
+
// separately from "google-vertex" (Pi's own, unrelated native Vertex provider): different code
|
|
308
|
+
// path, different account/quota pool, and its metrics must stay distinguishable -- see
|
|
309
|
+
// google-vertex-contracts.ts and anthropic-contracts.ts.
|
|
310
|
+
let lastAnthropicVertexResponse: GoogleVertexFailureMetadata = {};
|
|
258
311
|
const cancelRecovery = (resetPolicy: boolean): void => {
|
|
259
312
|
if (recoveryTimer !== undefined) recoveryRuntime.clearTimeout(recoveryTimer);
|
|
260
313
|
recoveryTimer = undefined;
|
|
@@ -314,10 +367,21 @@ export function registerJittorExtension(
|
|
|
314
367
|
const beginCompactionUi = (ctx: ExtensionContext, signal: AbortSignal): void => {
|
|
315
368
|
finishCompactionUi();
|
|
316
369
|
const usage = ctx.getContextUsage();
|
|
317
|
-
|
|
370
|
+
const compaction: CompactionProgress = {
|
|
318
371
|
startedAt: Date.now(),
|
|
319
372
|
initialFraction: usage?.percent === null || usage?.percent === undefined ? 1 : usage.percent / 100,
|
|
373
|
+
estimatedMs: null,
|
|
374
|
+
confidence: "cold-start",
|
|
320
375
|
};
|
|
376
|
+
footerState.compaction = compaction;
|
|
377
|
+
// Non-blocking: compaction UI starts immediately as cold-start; if a learned estimate resolves
|
|
378
|
+
// before this compaction finishes (and this is still the active compaction, not a later one),
|
|
379
|
+
// upgrade the same progress object in place so the drain bar and status text switch to "learned".
|
|
380
|
+
void client.call("compaction.estimate", {}).then((estimate) => {
|
|
381
|
+
if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
|
|
382
|
+
footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
|
|
383
|
+
footerState.requestRender?.();
|
|
384
|
+
}).catch(() => undefined);
|
|
321
385
|
compactionTimer = setInterval(() => footerState.requestRender?.(), FOOTER_COMPACTION_RENDER_INTERVAL_MS);
|
|
322
386
|
signal.addEventListener("abort", finishCompactionUi, { once: true });
|
|
323
387
|
if (signal.aborted) finishCompactionUi();
|
|
@@ -353,9 +417,62 @@ export function registerJittorExtension(
|
|
|
353
417
|
};
|
|
354
418
|
|
|
355
419
|
pi.registerCommand("jittor", {
|
|
356
|
-
description: "
|
|
420
|
+
description: "Jittor settings, routing status, benchmarks, and Codex recovery controls",
|
|
357
421
|
handler: async (args, ctx) => {
|
|
358
422
|
const action = args.trim().toLowerCase();
|
|
423
|
+
if (action === "" || action === "settings") {
|
|
424
|
+
await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
|
|
425
|
+
setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
|
|
426
|
+
setFooter: async (enabled) => {
|
|
427
|
+
enforcement.setFooterEnabled(enabled);
|
|
428
|
+
showFooter(ctx);
|
|
429
|
+
if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
|
|
430
|
+
},
|
|
431
|
+
setRecovery: (enabled) => {
|
|
432
|
+
if (!enabled) cancelRecovery(true);
|
|
433
|
+
codexRecovery.setCodexRecoveryEnabled(enabled);
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (action === "benchmarks" || action.startsWith("benchmarks ")) {
|
|
439
|
+
if (!ctx.model) {
|
|
440
|
+
ctx.ui.notify("No active Pi model is available for benchmark recommendations.", "warning");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// Domain (subject matter, e.g. coding) and type (activity, e.g. research/planning) are
|
|
444
|
+
// independent axes -- each positional word is classified against whichever axis it
|
|
445
|
+
// belongs to, in either order, so "/jittor benchmarks coding research" and
|
|
446
|
+
// "/jittor benchmarks research coding" both work; an unmatched word is a usage error.
|
|
447
|
+
const requested = action.split(/\s+/).slice(1);
|
|
448
|
+
let requestedDomain: ModelTaskDomain | undefined;
|
|
449
|
+
let requestedType: ModelTaskType | undefined;
|
|
450
|
+
let malformed = requested.length > 2;
|
|
451
|
+
for (const word of requested) {
|
|
452
|
+
if (TASK_DOMAINS.includes(word as ModelTaskDomain) && requestedDomain === undefined) requestedDomain = word as ModelTaskDomain;
|
|
453
|
+
else if (TASK_TYPES.includes(word as ModelTaskType) && requestedType === undefined) requestedType = word as ModelTaskType;
|
|
454
|
+
else malformed = true;
|
|
455
|
+
}
|
|
456
|
+
if (malformed) {
|
|
457
|
+
ctx.ui.notify("Usage: /jittor benchmarks [coding|general] [research|planning|general]", "warning");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const candidates = benchmarkCandidatesFromPi(ctx.modelRegistry.getAvailable() as PiRouteModel[], pi.getThinkingLevel());
|
|
461
|
+
await showBenchmarkPanel(ctx, client, candidates, `${ctx.model.provider}/${ctx.model.id}`, requestedDomain ?? "general", requestedType ?? "general");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (action === "outcome accepted" || action === "outcome rejected") {
|
|
465
|
+
if (!lastCompletedLocalRun) {
|
|
466
|
+
ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
|
|
470
|
+
const outcomeMetric = modelRunMetrics({ ...lastCompletedLocalRun, explicitOutcome }).find((metric) => metric.metric === "outcome-accepted")!;
|
|
471
|
+
outcomeMetric.observedAt = Date.now();
|
|
472
|
+
await recordMetrics(client, [outcomeMetric]);
|
|
473
|
+
ctx.ui.notify(`Recorded explicit ${explicitOutcome} outcome for the latest local model run.`, "info");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
359
476
|
if (action === "recovery" || action === "recovery status") {
|
|
360
477
|
ctx.ui.notify(recoveryStatusText(), "info");
|
|
361
478
|
return;
|
|
@@ -403,23 +520,22 @@ export function registerJittorExtension(
|
|
|
403
520
|
].join("\n"), "info");
|
|
404
521
|
return;
|
|
405
522
|
}
|
|
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
|
-
});
|
|
523
|
+
// Reached only for the explicit "status" keyword or any other unrecognized text; bare "" is
|
|
524
|
+
// handled above by the settings branch, so this always has a non-empty, non-settings action.
|
|
525
|
+
if (!enforcement.isEnabled()) {
|
|
526
|
+
ctx.ui.notify("Jittor is monitor-only. Run /jittor on to re-enable blocking.", "info");
|
|
419
527
|
return;
|
|
420
528
|
}
|
|
421
|
-
|
|
422
|
-
|
|
529
|
+
await showJittorPanel(ctx, client);
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
pi.registerCommand("usage", {
|
|
534
|
+
description: "Cumulative token/cost usage graph with hourly/daily/weekly/monthly/quarterly views",
|
|
535
|
+
handler: async (args, ctx) => {
|
|
536
|
+
const action = args.trim().toLowerCase();
|
|
537
|
+
if (action === "budget" || action.startsWith("budget ")) {
|
|
538
|
+
const [, periodText, valueText] = action.split(/\s+/);
|
|
423
539
|
const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? periodText as UsagePeriod : undefined;
|
|
424
540
|
if (!period) {
|
|
425
541
|
const values = USAGE_PERIODS.map(({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`).join(" · ");
|
|
@@ -437,30 +553,32 @@ export function registerJittorExtension(
|
|
|
437
553
|
}
|
|
438
554
|
const tokens = Number(valueText.replaceAll(",", ""));
|
|
439
555
|
if (!Number.isFinite(tokens) || tokens <= 0) {
|
|
440
|
-
ctx.ui.notify("Usage: /
|
|
556
|
+
ctx.ui.notify("Usage: /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
|
|
441
557
|
return;
|
|
442
558
|
}
|
|
443
559
|
usageBudgets.setUsageTokenBudget(period, tokens);
|
|
444
560
|
ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
|
|
445
561
|
return;
|
|
446
562
|
}
|
|
447
|
-
if (action
|
|
448
|
-
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
if (!enforcement.isEnabled()) {
|
|
452
|
-
ctx.ui.notify("Jittor is monitor-only. Run /jittor on to re-enable blocking.", "info");
|
|
563
|
+
if (action !== "" && action !== "cost" && action !== "tokens") {
|
|
564
|
+
ctx.ui.notify("Usage: /usage [cost] | /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
|
|
453
565
|
return;
|
|
454
566
|
}
|
|
455
|
-
await
|
|
567
|
+
await showUsagePanel(ctx, client, usageBudgets, Date.now(), action === "cost" ? "cost" : "tokens");
|
|
456
568
|
},
|
|
457
569
|
});
|
|
458
570
|
|
|
459
571
|
pi.on("session_start", async (_event, ctx) => {
|
|
572
|
+
currentSessionId = ctx.sessionManager.getSessionId();
|
|
573
|
+
focusedTaskId = null;
|
|
460
574
|
finishCompactionUi();
|
|
461
575
|
compactionTelemetry = new CompactionTelemetry();
|
|
576
|
+
activeLocalRun = undefined;
|
|
577
|
+
lastCompletedLocalRun = undefined;
|
|
462
578
|
cancelRecovery(true);
|
|
463
579
|
lastCodexResponse = {};
|
|
580
|
+
lastGoogleVertexResponse = {};
|
|
581
|
+
lastAnthropicVertexResponse = {};
|
|
464
582
|
ctx.ui.setStatus("jittor", undefined);
|
|
465
583
|
showFooter(ctx);
|
|
466
584
|
try {
|
|
@@ -533,9 +651,21 @@ export function registerJittorExtension(
|
|
|
533
651
|
await syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
|
|
534
652
|
});
|
|
535
653
|
|
|
536
|
-
pi.on("turn_start", async (
|
|
654
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
655
|
+
currentSessionId = ctx.sessionManager.getSessionId();
|
|
537
656
|
compactionTelemetry.observeTurn();
|
|
538
657
|
lastCodexResponse = {};
|
|
658
|
+
lastGoogleVertexResponse = {};
|
|
659
|
+
lastAnthropicVertexResponse = {};
|
|
660
|
+
activeLocalRun = {
|
|
661
|
+
runId: `local-${event.timestamp}-${++localRunSequence}`,
|
|
662
|
+
startedAt: event.timestamp,
|
|
663
|
+
firstTokenAt: null,
|
|
664
|
+
providerResponses: 0,
|
|
665
|
+
toolNames: [],
|
|
666
|
+
toolCalls: 0,
|
|
667
|
+
toolFailures: 0,
|
|
668
|
+
};
|
|
539
669
|
if (!enforcement.isEnabled()) return;
|
|
540
670
|
try {
|
|
541
671
|
await syncCurrentRoute(pi, client, ctx);
|
|
@@ -547,20 +677,100 @@ export function registerJittorExtension(
|
|
|
547
677
|
}
|
|
548
678
|
});
|
|
549
679
|
|
|
680
|
+
pi.on("message_update", async (event) => {
|
|
681
|
+
if (!activeLocalRun || activeLocalRun.firstTokenAt !== null) return;
|
|
682
|
+
if (["text_delta", "thinking_delta", "toolcall_delta"].includes(event.assistantMessageEvent.type)) activeLocalRun.firstTokenAt = Date.now();
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
pi.on("tool_execution_end", async (event) => {
|
|
686
|
+
if (!activeLocalRun) return;
|
|
687
|
+
activeLocalRun.toolCalls += 1;
|
|
688
|
+
if (event.isError) activeLocalRun.toolFailures += 1;
|
|
689
|
+
if (activeLocalRun.toolNames.length < 100) activeLocalRun.toolNames.push(event.toolName);
|
|
690
|
+
});
|
|
691
|
+
|
|
550
692
|
pi.on("after_provider_response", async (event, ctx) => {
|
|
693
|
+
if (activeLocalRun) activeLocalRun.providerResponses += 1;
|
|
551
694
|
if (ctx.model?.provider === "openai-codex") {
|
|
552
695
|
lastCodexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
|
|
553
696
|
}
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
697
|
+
if (ctx.model?.provider === "anthropic") {
|
|
698
|
+
const headers = new Headers(event.headers);
|
|
699
|
+
if (hasAnthropicRateLimitHeaders(headers)) {
|
|
700
|
+
try {
|
|
701
|
+
await recordMetrics(client, parseAnthropicRateLimitHeaders(headers, Date.now()).metrics);
|
|
702
|
+
} catch {
|
|
703
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Anthropic telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (ctx.model?.provider === "anthropic-vertex") {
|
|
708
|
+
// Best-effort only: unverified whether this passthrough ever forwards Anthropic's own
|
|
709
|
+
// rate-limit headers. If it doesn't, hasAnthropicRateLimitHeaders is false and nothing is
|
|
710
|
+
// recorded -- the same honest default as every other unconfirmed signal in this file.
|
|
711
|
+
const headers = new Headers(event.headers);
|
|
712
|
+
if (hasAnthropicRateLimitHeaders(headers)) {
|
|
713
|
+
try {
|
|
714
|
+
await recordMetrics(client, parseAnthropicRateLimitHeaders(headers, Date.now(), "anthropic-vertex").metrics);
|
|
715
|
+
} catch {
|
|
716
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Anthropic-on-Vertex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Well-evidenced regardless of headers: GCP's own quota system fronts this transport, so the
|
|
720
|
+
// same failure classification as google-vertex applies -- see google-vertex-contracts.ts.
|
|
721
|
+
lastAnthropicVertexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
|
|
722
|
+
}
|
|
723
|
+
if (ctx.model?.provider === "google-vertex") {
|
|
724
|
+
lastGoogleVertexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
|
|
725
|
+
}
|
|
726
|
+
if (Object.keys(event.headers).some((name) => name.toLowerCase().startsWith("x-codex-"))) {
|
|
727
|
+
try {
|
|
728
|
+
const updates = parseCodexRateLimitHeaders(new Headers(event.headers), Date.now());
|
|
729
|
+
await recordMetrics(client, updates.flatMap((update) => update.metrics));
|
|
730
|
+
} catch {
|
|
731
|
+
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Codex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
|
|
732
|
+
}
|
|
560
733
|
}
|
|
561
734
|
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
|
|
562
735
|
});
|
|
563
736
|
|
|
737
|
+
pi.on("turn_end", async (event) => {
|
|
738
|
+
const active = activeLocalRun;
|
|
739
|
+
activeLocalRun = undefined;
|
|
740
|
+
const message = event.message as unknown;
|
|
741
|
+
if (!active || typeof message !== "object" || message === null || Array.isArray(message)) return;
|
|
742
|
+
const value = message as Record<string, unknown>;
|
|
743
|
+
if (value["role"] !== "assistant" || typeof value["provider"] !== "string" || typeof value["model"] !== "string") return;
|
|
744
|
+
const usage = typeof value["usage"] === "object" && value["usage"] !== null ? value["usage"] as Record<string, unknown> : {};
|
|
745
|
+
const amount = (name: string): number => typeof usage[name] === "number" && Number.isFinite(usage[name]) ? usage[name] as number : 0;
|
|
746
|
+
const cost = typeof usage["cost"] === "object" && usage["cost"] !== null && typeof (usage["cost"] as Record<string, unknown>)["total"] === "number"
|
|
747
|
+
? (usage["cost"] as Record<string, number>)["total"] ?? 0 : 0;
|
|
748
|
+
const stopReason = ["stop", "length", "toolUse", "error", "aborted"].includes(String(value["stopReason"]))
|
|
749
|
+
? value["stopReason"] as ModelRunObservation["stopReason"] : "unknown";
|
|
750
|
+
const completedAt = Math.max(Date.now(), active.firstTokenAt ?? active.startedAt, active.startedAt);
|
|
751
|
+
lastCompletedLocalRun = {
|
|
752
|
+
runId: active.runId,
|
|
753
|
+
provider: value["provider"],
|
|
754
|
+
model: value["model"],
|
|
755
|
+
thinking: pi.getThinkingLevel(),
|
|
756
|
+
...classifyTaskFromTools(active.toolNames),
|
|
757
|
+
startedAt: active.startedAt,
|
|
758
|
+
firstTokenAt: active.firstTokenAt,
|
|
759
|
+
completedAt,
|
|
760
|
+
inputTokens: amount("input"),
|
|
761
|
+
outputTokens: amount("output"),
|
|
762
|
+
cacheReadTokens: amount("cacheRead"),
|
|
763
|
+
cacheWriteTokens: amount("cacheWrite"),
|
|
764
|
+
costUsd: Number.isFinite(cost) && cost >= 0 ? cost : 0,
|
|
765
|
+
providerResponses: Math.max(1, active.providerResponses),
|
|
766
|
+
toolCalls: active.toolCalls,
|
|
767
|
+
toolFailures: active.toolFailures,
|
|
768
|
+
stopReason,
|
|
769
|
+
explicitOutcome: "unknown",
|
|
770
|
+
};
|
|
771
|
+
await recordMetrics(client, modelRunMetrics(lastCompletedLocalRun)).catch(() => undefined);
|
|
772
|
+
});
|
|
773
|
+
|
|
564
774
|
pi.on("message_end", async (event, _ctx) => {
|
|
565
775
|
if (event.message.role === "assistant" && event.message.provider === "openai-codex") {
|
|
566
776
|
if (event.message.stopReason === "error") {
|
|
@@ -572,7 +782,21 @@ export function registerJittorExtension(
|
|
|
572
782
|
}
|
|
573
783
|
lastCodexResponse = {};
|
|
574
784
|
}
|
|
575
|
-
|
|
785
|
+
if (event.message.role === "assistant" && event.message.provider === "google-vertex") {
|
|
786
|
+
if (event.message.stopReason === "error") {
|
|
787
|
+
const failure = classifyGoogleVertexFailure(event.message.errorMessage, lastGoogleVertexResponse);
|
|
788
|
+
await recordMetrics(client, googleVertexFailureMetrics(failure, Date.now())).catch(() => undefined);
|
|
789
|
+
}
|
|
790
|
+
lastGoogleVertexResponse = {};
|
|
791
|
+
}
|
|
792
|
+
if (event.message.role === "assistant" && event.message.provider === "anthropic-vertex") {
|
|
793
|
+
if (event.message.stopReason === "error") {
|
|
794
|
+
const failure = classifyGoogleVertexFailure(event.message.errorMessage, lastAnthropicVertexResponse);
|
|
795
|
+
await recordMetrics(client, googleVertexFailureMetrics(failure, Date.now(), "anthropic-vertex")).catch(() => undefined);
|
|
796
|
+
}
|
|
797
|
+
lastAnthropicVertexResponse = {};
|
|
798
|
+
}
|
|
799
|
+
const metrics = assistantUsageMetrics(event.message, Date.now(), focusedTaskId);
|
|
576
800
|
if (metrics.length > 0) {
|
|
577
801
|
const amount = (name: string): number => metrics.filter((metric) => metric.metric === name && typeof metric.value === "number").reduce((sum, metric) => sum + (metric.value ?? 0), 0);
|
|
578
802
|
compactionTelemetry.observeProviderUsage({ input: amount("input-tokens"), output: amount("output-tokens"), cacheRead: amount("cache-read-tokens"), cacheWrite: amount("cache-write-tokens") });
|
|
@@ -585,8 +809,11 @@ export function registerJittorExtension(
|
|
|
585
809
|
finishCompactionUi();
|
|
586
810
|
if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
|
|
587
811
|
stopPapyrusContext?.();
|
|
812
|
+
stopPapyrusTaskFocus?.();
|
|
588
813
|
cancelRecovery(true);
|
|
589
814
|
lastCodexResponse = {};
|
|
815
|
+
activeLocalRun = undefined;
|
|
816
|
+
lastCompletedLocalRun = undefined;
|
|
590
817
|
ctx.ui.setStatus("jittor", undefined);
|
|
591
818
|
ctx.ui.setFooter(undefined);
|
|
592
819
|
});
|
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 {
|
|
@@ -58,7 +63,15 @@ function windowName(seconds: number): string {
|
|
|
58
63
|
return `${Math.round(seconds / 60)}m`;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
/**
|
|
67
|
+
* `null` means "not known yet, but this provider can report a budget once data arrives" -- router
|
|
68
|
+
* not ready, or a supported provider whose telemetry hasn't been observed yet; the footer shows a
|
|
69
|
+
* placeholder that may resolve. `undefined` means "no budget signal is possible for this provider
|
|
70
|
+
* at all" (e.g. google-vertex, which has no documented rate-limit or quota header/endpoint Jittor
|
|
71
|
+
* could ever read -- see google-vertex-contracts.ts); the footer omits the segment entirely rather
|
|
72
|
+
* than showing a `?` that can never resolve.
|
|
73
|
+
*/
|
|
74
|
+
export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObservation[]): ProviderBudget | null | undefined {
|
|
62
75
|
if (!status.ready || !status.currentRoute) return null;
|
|
63
76
|
if (status.currentRoute.provider === "openai-codex") {
|
|
64
77
|
const codex = codexWindowForModel(metrics, status.currentRoute.model);
|
|
@@ -72,6 +85,38 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
72
85
|
...(Number.isFinite(resetsAtSeconds) && resetsAtSeconds > 0 ? { resetsAt: resetsAtSeconds * 1_000 } : {}),
|
|
73
86
|
};
|
|
74
87
|
}
|
|
88
|
+
if (status.currentRoute.provider === "anthropic") {
|
|
89
|
+
const anthropic = latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number")
|
|
90
|
+
?? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number");
|
|
91
|
+
if (!anthropic || typeof anthropic.value !== "number") return null;
|
|
92
|
+
const resetsAt = Number(anthropic.attributes["resetsAt"]);
|
|
93
|
+
return {
|
|
94
|
+
kind: "bounded",
|
|
95
|
+
label: anthropic.scope === "tokens" ? "tok" : "req",
|
|
96
|
+
remainingFraction: 1 - anthropic.value,
|
|
97
|
+
observedAt: anthropic.observedAt,
|
|
98
|
+
...(Number.isFinite(resetsAt) && resetsAt > 0 ? { resetsAt } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (status.currentRoute.provider === "anthropic-vertex") {
|
|
102
|
+
// Best-effort only (see index.ts): these metrics only exist if Anthropic-style rate-limit
|
|
103
|
+
// headers were actually observed on this passthrough, which is unverified. Labeled distinctly
|
|
104
|
+
// ("vtok"/"vreq") from direct Anthropic's "tok"/"req" since they are a different account/quota
|
|
105
|
+
// pool even if the header shape is identical. If nothing was ever observed, this stays null
|
|
106
|
+
// (may still resolve later), not undefined (never possible) -- unlike google-vertex, this
|
|
107
|
+
// provider's transport has not been shown to structurally lack the signal.
|
|
108
|
+
const anthropicVertex = latest(metrics, (row) => row.source === "anthropic-vertex" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number")
|
|
109
|
+
?? latest(metrics, (row) => row.source === "anthropic-vertex" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number");
|
|
110
|
+
if (!anthropicVertex || typeof anthropicVertex.value !== "number") return null;
|
|
111
|
+
const resetsAt = Number(anthropicVertex.attributes["resetsAt"]);
|
|
112
|
+
return {
|
|
113
|
+
kind: "bounded",
|
|
114
|
+
label: anthropicVertex.scope === "tokens" ? "vtok" : "vreq",
|
|
115
|
+
remainingFraction: 1 - anthropicVertex.value,
|
|
116
|
+
observedAt: anthropicVertex.observedAt,
|
|
117
|
+
...(Number.isFinite(resetsAt) && resetsAt > 0 ? { resetsAt } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
75
120
|
if (status.currentRoute.provider === "openrouter") {
|
|
76
121
|
const openRouter = latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number");
|
|
77
122
|
const remaining = latest(metrics, (row) => row.source === "openrouter" && row.metric === "remaining-fraction" && typeof row.value === "number");
|
|
@@ -88,7 +133,7 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
88
133
|
if (!openRouter || typeof openRouter.value !== "number") return null;
|
|
89
134
|
return { kind: "unbounded", label: "spend", valueText: `$${openRouter.value.toFixed(3)}`, observedAt: openRouter.observedAt };
|
|
90
135
|
}
|
|
91
|
-
return
|
|
136
|
+
return undefined;
|
|
92
137
|
}
|
|
93
138
|
|
|
94
139
|
export function formatFooterStatus(status: RouterStatus, metrics: StoredMetricObservation[]): string {
|
|
@@ -135,15 +180,22 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
|
|
|
135
180
|
? latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number")
|
|
136
181
|
: undefined;
|
|
137
182
|
if (openRouter && typeof openRouter.value === "number") lines.push(`OpenRouter spend: $${openRouter.value.toFixed(3)}`);
|
|
138
|
-
|
|
183
|
+
const anthropic = status.currentRoute?.provider === "anthropic"
|
|
184
|
+
? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number")
|
|
185
|
+
?? latest(metrics, (row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number")
|
|
186
|
+
: undefined;
|
|
187
|
+
if (anthropic && typeof anthropic.value === "number") lines.push(`Anthropic ${anthropic.scope}: ${((1 - anthropic.value) * 100).toFixed(1)}% left`);
|
|
188
|
+
if (status.currentRoute) lines.push(`Route: ${routeText(status.currentRoute)}`);
|
|
139
189
|
if (status.lastDecision) lines.push(`Pressure: ${Number.isFinite(status.lastDecision.pressure) ? status.lastDecision.pressure.toFixed(3) : "∞"} · ${status.lastDecision.action}`);
|
|
140
190
|
lines.push(`Next: ${nextAction(status.lastDecision?.action)}`);
|
|
141
191
|
lines.push("Telemetry:");
|
|
142
|
-
|
|
192
|
+
const providerSources = status.sources.filter((source) => source.provider === status.currentRoute?.provider);
|
|
193
|
+
for (const source of providerSources.slice(0, HUMAN_STATUS_MAX_SOURCES)) {
|
|
143
194
|
const freshness = !source.ok ? "failed" : source.observedAt !== undefined && now - source.observedAt > 120_000 ? "stale" : "fresh";
|
|
144
|
-
lines.push(` ${source.id}: ${freshness} · ${source.metrics} metrics`);
|
|
195
|
+
lines.push(` ${sanitizedText(source.id)}: ${freshness} · ${source.metrics} metrics`);
|
|
145
196
|
}
|
|
146
|
-
if (
|
|
197
|
+
if (providerSources.length > HUMAN_STATUS_MAX_SOURCES) lines.push(` … ${providerSources.length - HUMAN_STATUS_MAX_SOURCES} more telemetry sources omitted`);
|
|
198
|
+
if (status.override) lines.push(`Override: ${routeText(status.override.route)}`);
|
|
147
199
|
if (status.paused) lines.push("Emergency halt is active");
|
|
148
200
|
return lines;
|
|
149
201
|
}
|
|
@@ -160,7 +212,7 @@ async function snapshot(client: JittorPanelClient): Promise<{ status: RouterStat
|
|
|
160
212
|
|
|
161
213
|
async function chooseOverride(ctx: ExtensionCommandContext, routes: Route[]): Promise<Route | undefined> {
|
|
162
214
|
if (routes.length === 0) { ctx.ui.notify("Pi reports no authenticated routes for the current provider.", "warning"); return undefined; }
|
|
163
|
-
const labels = routes.map(
|
|
215
|
+
const labels = routes.map(routeText);
|
|
164
216
|
const selected = await ctx.ui.select("Override route", labels);
|
|
165
217
|
const index = selected ? labels.indexOf(selected) : -1;
|
|
166
218
|
return index >= 0 ? routes[index] : undefined;
|
|
@@ -211,7 +263,7 @@ export async function showJittorPanel(ctx: ExtensionCommandContext, client: Jitt
|
|
|
211
263
|
continue;
|
|
212
264
|
}
|
|
213
265
|
const route = await chooseOverride(ctx, current.status.availableRoutes);
|
|
214
|
-
if (route && await ctx.ui.confirm("Apply route override?", `${route
|
|
266
|
+
if (route && await ctx.ui.confirm("Apply route override?", `${routeText(route)} for one hour`)) {
|
|
215
267
|
await client.call("router.override", { route, expiresAt: Date.now() + 60 * 60 * 1_000 });
|
|
216
268
|
}
|
|
217
269
|
}
|