@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.
Files changed (44) hide show
  1. package/README.md +62 -7
  2. package/docs/BENCHMARK_SOURCES.md +32 -0
  3. package/docs/OUTPUT_CHANNELS.md +31 -0
  4. package/docs/PROVIDER_RESEARCH.md +57 -0
  5. package/extension/src/benchmark-tui.ts +105 -0
  6. package/extension/src/footer.ts +68 -12
  7. package/extension/src/index.ts +263 -36
  8. package/extension/src/tui.ts +61 -9
  9. package/extension/src/usage.ts +165 -47
  10. package/package.json +5 -1
  11. package/src/adapters/metric-benchmark-store.ts +99 -0
  12. package/src/adapters/openrouter-benchmark-index-source.ts +94 -0
  13. package/src/adapters/openrouter-benchmark-source.ts +109 -0
  14. package/src/adapters/sqlite-metric-store.ts +43 -2
  15. package/src/cli.ts +660 -9
  16. package/src/client.ts +12 -44
  17. package/src/constants.ts +69 -5
  18. package/src/daemon.ts +65 -43
  19. package/src/db.ts +13 -30
  20. package/src/domain/benchmark.ts +264 -0
  21. package/src/domain/context-telemetry.ts +31 -0
  22. package/src/domain/metric.ts +46 -5
  23. package/src/domain/model-observation.ts +203 -0
  24. package/src/domain/model-ranking-service.ts +41 -0
  25. package/src/domain/model-ranking.ts +232 -0
  26. package/src/domain/task-cost.ts +70 -0
  27. package/src/domain/task-focus.ts +65 -0
  28. package/src/domain/usage.ts +134 -33
  29. package/src/log.ts +28 -0
  30. package/src/ports/benchmark-controller.ts +11 -0
  31. package/src/ports/benchmark-source.ts +7 -0
  32. package/src/ports/benchmark-store.ts +7 -0
  33. package/src/ports/metric-store.ts +30 -0
  34. package/src/ports/router-controller.ts +1 -0
  35. package/src/providers/anthropic-contracts.ts +127 -0
  36. package/src/providers/google-adc-auth.ts +63 -0
  37. package/src/providers/google-vertex-budget-contracts.ts +181 -0
  38. package/src/providers/google-vertex-budget.ts +127 -0
  39. package/src/providers/google-vertex-contracts.ts +116 -0
  40. package/src/providers/telemetry-sources.ts +35 -0
  41. package/src/router.ts +12 -0
  42. package/src/service.ts +132 -17
  43. package/src/state.ts +31 -57
  44. package/src/version.ts +2 -14
@@ -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 { installIntegratedFooter, type IntegratedFooterState } from "./footer.ts";
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
- function assistantUsageMetrics(message: unknown, observedAt: number): MetricObservation[] {
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
- footerState.compaction = {
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: "Inspect or control Jittor routing, budgets, and usage",
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
- if (action === "settings") {
407
- await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
408
- setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
409
- setFooter: async (enabled) => {
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
- if (action === "usage budget" || action.startsWith("usage budget ")) {
422
- const [, , periodText, valueText] = action.split(/\s+/);
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: /jittor usage budget <hourly|daily|weekly|monthly> <positive-tokens|off>", "warning");
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 === "usage") {
448
- await showUsagePanel(ctx, client, usageBudgets);
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 showJittorPanel(ctx, client);
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 (_event, ctx) => {
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 (!Object.keys(event.headers).some((name) => name.toLowerCase().startsWith("x-codex-"))) return;
555
- try {
556
- const updates = parseCodexRateLimitHeaders(new Headers(event.headers), Date.now());
557
- await recordMetrics(client, updates.flatMap((update) => update.metrics));
558
- } catch {
559
- if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Codex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
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
- const metrics = assistantUsageMetrics(event.message, Date.now());
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
  });
@@ -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
- export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObservation[]): ProviderBudget | null {
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 null;
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
- if (status.currentRoute) lines.push(`Route: ${status.currentRoute.provider}/${status.currentRoute.model} · ${status.currentRoute.thinking}`);
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
- for (const source of status.sources.filter((source) => source.provider === status.currentRoute?.provider)) {
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 (status.override) lines.push(`Override: ${status.override.route.provider}/${status.override.route.model} · ${status.override.route.thinking}`);
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((route) => `${route.provider}/${route.model} · ${route.thinking}`);
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.provider}/${route.model} · ${route.thinking} for one hour`)) {
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
  }