@danypops/pi-jittor 0.7.0 → 0.9.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.
@@ -26,7 +26,6 @@ import {
26
26
  papyrusContextMetric,
27
27
  type Route,
28
28
  type RouterStatus,
29
- type StoredMetricObservation,
30
29
  TASK_DOMAINS,
31
30
  TASK_TYPES,
32
31
  type TextTokenCounter,
@@ -61,7 +60,7 @@ import {
61
60
  import { LocalRunTelemetry } from "./observability/model-run.ts";
62
61
  import { captureProviderContextSnapshot } from "./observability/provider-context-snapshot.ts";
63
62
  import { ProviderResponseTelemetry } from "./observability/provider-response.ts";
64
- import { buildFooterBudget, providerBudgetMetricQuery } from "./observability/status.ts";
63
+ import { buildFooterBudget, fetchProviderBudgetMetrics } from "./observability/status.ts";
65
64
  import { showUsagePanel } from "./observability/usage.ts";
66
65
  import { candidateIdentityOf, decideAutoMode, newAutoModeSessionState, recordAutoModeDismissal } from "./optimization/auto-mode.ts";
67
66
  import { showAutoModeSuggestion } from "./optimization/auto-mode-dialog.ts";
@@ -158,27 +157,11 @@ async function recordMetrics(client: JittorExtensionClient, metrics: MetricObser
158
157
 
159
158
  async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState, sessionId: string): Promise<void> {
160
159
  const status = (await client.call("router.status", { session_id: sessionId })) as RouterStatus;
161
- const query = providerBudgetMetricQuery(status);
162
- const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
160
+ const metrics = await fetchProviderBudgetMetrics(client, status);
163
161
  state.providerBudget = buildFooterBudget(status, metrics);
164
162
  state.requestRender?.();
165
163
  }
166
164
 
167
- function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
168
- if (milliseconds <= 0) return Promise.resolve();
169
- return new Promise((resolve, reject) => {
170
- const timer = setTimeout(resolve, milliseconds);
171
- signal?.addEventListener(
172
- "abort",
173
- () => {
174
- clearTimeout(timer);
175
- reject(new Error("Jittor throttle cancelled"));
176
- },
177
- { once: true },
178
- );
179
- });
180
- }
181
-
182
165
  function routeModelAvailable(ctx: ExtensionContext, route: Route): boolean {
183
166
  return ctx.modelRegistry.getAvailable().some((model) => model.provider === route.provider && model.id === route.model);
184
167
  }
@@ -318,38 +301,6 @@ async function syncCurrentRoute(
318
301
  });
319
302
  }
320
303
 
321
- function halt(ctx: ExtensionContext, reason: string): false {
322
- ctx.ui.notify(`${reason}. ${RECOVERY_GUIDANCE}.`, "warning");
323
- ctx.abort();
324
- return false;
325
- }
326
-
327
- async function applyDecision(
328
- pi: ExtensionAPI,
329
- client: JittorExtensionClient,
330
- ctx: ExtensionContext,
331
- decision: PolicyDecision,
332
- allowResync = true,
333
- ): Promise<boolean> {
334
- if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
335
- if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
336
- if (!decision.route || (await applyRoute(pi, ctx, decision.route))) return true;
337
- if (allowResync) {
338
- await syncAvailableRoutes(pi, client, ctx);
339
- return applyDecision(
340
- pi,
341
- client,
342
- ctx,
343
- (await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
344
- false,
345
- );
346
- }
347
- return halt(
348
- ctx,
349
- `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`,
350
- );
351
- }
352
-
353
304
  let assistantUsageRunSequence = 0;
354
305
 
355
306
  /**
@@ -503,54 +454,61 @@ export function registerJittorExtension(
503
454
  const contextFingerprinter = new HmacContextFingerprinter(contextFingerprintKey);
504
455
  let contextCaptureSequence = 0;
505
456
  pi.on("before_provider_request", (event, ctx) => {
506
- try {
507
- const sessionId = ctx.sessionManager.getSessionId();
508
- let history:
509
- | {
510
- roots: SessionTreeNodeLike[];
511
- activeEntryIds: Set<string>;
512
- branchEntryIds: Set<string>;
513
- }
514
- | undefined;
457
+ // Pi awaits this hook immediately before opening the provider request. Snapshotting a large
458
+ // payload here used to add its full CPU cost to TTFT even though persistence was detached.
459
+ // Defer the entire observational path to a later macrotask; the provider payload is immutable
460
+ // after serialization in Pi's request path, and a missed snapshot is preferable to delaying it.
461
+ const timer = setTimeout(() => {
515
462
  try {
516
- history = {
517
- roots: ctx.sessionManager.getTree() as SessionTreeNodeLike[],
518
- activeEntryIds: new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id)),
519
- branchEntryIds: new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id)),
463
+ const sessionId = ctx.sessionManager.getSessionId();
464
+ let history:
465
+ | {
466
+ roots: SessionTreeNodeLike[];
467
+ activeEntryIds: Set<string>;
468
+ branchEntryIds: Set<string>;
469
+ }
470
+ | undefined;
471
+ try {
472
+ history = {
473
+ roots: ctx.sessionManager.getTree() as SessionTreeNodeLike[],
474
+ activeEntryIds: new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id)),
475
+ branchEntryIds: new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id)),
476
+ };
477
+ } catch {
478
+ // Older/custom SessionManager implementations may not expose tree projections.
479
+ }
480
+ const captureInput = {
481
+ payload: event.payload,
482
+ captureId: `${++contextCaptureSequence}`,
483
+ sessionId,
484
+ provider: ctx.model?.provider ?? "unknown",
485
+ model: ctx.model?.id ?? "unknown",
486
+ capturedAt: Date.now(),
487
+ fingerprinter: contextFingerprinter,
520
488
  };
489
+ let snapshot: ContextSnapshot;
490
+ try {
491
+ snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
492
+ } catch {
493
+ // A custom SessionManager tree shape must not suppress the real request-payload snapshot.
494
+ snapshot = captureProviderContextSnapshot(captureInput);
495
+ }
496
+ // Observation must never alter or abort the provider request. Both local writes are detached;
497
+ // they receive only bounded token sizes and keyed fingerprints.
498
+ const requestTokens = snapshot.segments
499
+ .filter((segment) => segment.requestPosition !== null)
500
+ .reduce((sum, segment) => sum + segment.tokens, 0);
501
+ const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
502
+ provider: snapshot.provider,
503
+ model: snapshot.model,
504
+ });
505
+ void client.call("context.snapshot", snapshot).catch(() => undefined);
506
+ if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
521
507
  } catch {
522
- // Older/custom SessionManager implementations may not expose tree projections.
523
- }
524
- const captureInput = {
525
- payload: event.payload,
526
- captureId: `${++contextCaptureSequence}`,
527
- sessionId,
528
- provider: ctx.model?.provider ?? "unknown",
529
- model: ctx.model?.id ?? "unknown",
530
- capturedAt: Date.now(),
531
- fingerprinter: contextFingerprinter,
532
- };
533
- let snapshot: ContextSnapshot;
534
- try {
535
- snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
536
- } catch {
537
- // A custom SessionManager tree shape must not suppress the real request-payload snapshot.
538
- snapshot = captureProviderContextSnapshot(captureInput);
508
+ // Snapshot collection is strictly failure-isolated from provider delivery.
539
509
  }
540
- // Observation must never alter or abort the provider request. Both local writes are detached;
541
- // they receive only bounded token sizes and keyed fingerprints.
542
- const requestTokens = snapshot.segments
543
- .filter((segment) => segment.requestPosition !== null)
544
- .reduce((sum, segment) => sum + segment.tokens, 0);
545
- const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
546
- provider: snapshot.provider,
547
- model: snapshot.model,
548
- });
549
- void client.call("context.snapshot", snapshot).catch(() => undefined);
550
- if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
551
- } catch {
552
- // Snapshot collection is strictly failure-isolated from provider delivery.
553
- }
510
+ }, 0);
511
+ timer.unref?.();
554
512
  });
555
513
  const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
556
514
  // Cached from the most recent before_agent_start observation: Pi's own base system prompt is
@@ -1002,25 +960,58 @@ export function registerJittorExtension(
1002
960
  ]).catch(() => undefined);
1003
961
  });
1004
962
 
1005
- pi.on("agent_settled", async (_event, ctx) => {
963
+ let ambientRouting: Promise<void> | null = null;
964
+ const scheduleAmbientRouting = (ctx: ExtensionContext): void => {
965
+ if (ambientRouting || !ctx.isIdle()) return;
966
+ ambientRouting = (async () => {
967
+ try {
968
+ await syncCurrentRoute(pi, client, ctx);
969
+ await syncAvailableRoutes(pi, client, ctx);
970
+ if (!ctx.isIdle()) return;
971
+ let budgetActed = false;
972
+ if (enforcement.isEnabled()) {
973
+ const decision = (await client.call("router.decide", {
974
+ session_id: ctx.sessionManager.getSessionId(),
975
+ })) as PolicyDecision;
976
+ if (!ctx.isIdle()) return;
977
+ budgetActed = decision.action !== "continue";
978
+ if (decision.action === "halt") {
979
+ ctx.ui.notify(`Jittor recommends pausing future requests: ${decision.reason}.`, "warning");
980
+ } else if (decision.route && !(await applyRoute(pi, ctx, decision.route)) && ctx.isIdle()) {
981
+ // Catalogs can change while an ambient decision is in flight. Refresh and try one
982
+ // newly-computed route; failure simply leaves the current model in place.
983
+ await syncAvailableRoutes(pi, client, ctx);
984
+ const refreshed = (await client.call("router.decide", {
985
+ session_id: ctx.sessionManager.getSessionId(),
986
+ })) as PolicyDecision;
987
+ if (ctx.isIdle() && refreshed.route) await applyRoute(pi, ctx, refreshed.route);
988
+ }
989
+ }
990
+ if (!budgetActed && ctx.isIdle()) await runAutoModeTurn(ctx);
991
+ } catch {
992
+ // Routing is opportunistic: daemon latency or failure must never enter Pi's request path.
993
+ } finally {
994
+ ambientRouting = null;
995
+ }
996
+ })();
997
+ };
998
+
999
+ pi.on("agent_settled", (_event, ctx) => {
1006
1000
  if (footerState.compaction) {
1007
1001
  finishCompactionUi();
1008
1002
  if (compactionTelemetry.hasOpenCompaction())
1009
- await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
1003
+ void recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
1010
1004
  }
1011
1005
  scheduleCodexRecovery(ctx);
1006
+ scheduleAmbientRouting(ctx);
1012
1007
  if (!enforcement.isFooterEnabled()) return;
1013
- try {
1014
- await syncCurrentRoute(pi, client, ctx);
1015
- await syncAvailableRoutes(pi, client, ctx);
1016
- await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
1017
- } catch {
1008
+ void refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => {
1018
1009
  footerState.providerBudget = null;
1019
1010
  footerState.requestRender?.();
1020
- }
1011
+ });
1021
1012
  });
1022
1013
 
1023
- pi.on("input", async (event, ctx) => {
1014
+ pi.on("input", (event) => {
1024
1015
  if (event.source !== "extension") {
1025
1016
  cancelRecovery(true);
1026
1017
  // Auto mode's own effort classifier reads this at the next turn_start -- captured
@@ -1028,57 +1019,34 @@ export function registerJittorExtension(
1028
1019
  // separate, independently-toggled feature from budget enforcement.
1029
1020
  pendingUserText = event.text;
1030
1021
  }
1031
- if (event.source === "extension" || !enforcement.isEnabled()) return { action: "continue" as const };
1032
- try {
1033
- const next = (await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision;
1034
- if (next.action === "halt") {
1035
- ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
1036
- return { action: "handled" as const };
1037
- }
1038
- return { action: "continue" as const };
1039
- } catch {
1040
- ctx.ui.notify(`Jittor could not verify budget telemetry, so fail-closed enforcement blocked input. ${RECOVERY_GUIDANCE}.`, "error");
1041
- return { action: "handled" as const };
1042
- }
1022
+ // Routing is advisory and ambient. The submitted message always enters Pi immediately;
1023
+ // daemon decisions computed while idle can optimize a later turn.
1024
+ return { action: "continue" as const };
1043
1025
  });
1044
1026
 
1045
- pi.on("model_select", async (event, ctx) => {
1046
- await syncCurrentRoute(pi, client, ctx, event.model)
1027
+ pi.on("model_select", (event, ctx) => {
1028
+ void syncCurrentRoute(pi, client, ctx, event.model)
1047
1029
  .then(() => syncAvailableRoutes(pi, client, ctx))
1030
+ .then(() =>
1031
+ enforcement.isFooterEnabled()
1032
+ ? refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined)
1033
+ : undefined,
1034
+ )
1048
1035
  .catch(() => undefined);
1049
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
1050
1036
  });
1051
1037
 
1052
- pi.on("thinking_level_select", async (event, ctx) => {
1053
- await syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
1038
+ pi.on("thinking_level_select", (event, ctx) => {
1039
+ void syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
1054
1040
  });
1055
1041
 
1056
- pi.on("turn_start", async (event, ctx) => {
1042
+ pi.on("turn_start", (event, ctx) => {
1057
1043
  currentSessionId = ctx.sessionManager.getSessionId();
1058
1044
  compactionTelemetry.observeTurn();
1059
1045
  codexRecoveryCapability.resetTurn();
1060
1046
  providerResponseTelemetry.resetTurn();
1061
1047
  localRunTelemetry.beginTurn(event.timestamp);
1062
- // Budget/enforcement routing is safety-critical and always wins on conflict: Auto mode is
1063
- // only evaluated when budget pressure required no action this turn (action === "continue"),
1064
- // so the two decision sources never fight over the model mid-turn.
1065
- let budgetActedThisTurn = false;
1066
- if (enforcement.isEnabled()) {
1067
- try {
1068
- await syncCurrentRoute(pi, client, ctx);
1069
- await syncAvailableRoutes(pi, client, ctx);
1070
- const budgetDecision = (await client.call("router.decide", {
1071
- session_id: ctx.sessionManager.getSessionId(),
1072
- })) as PolicyDecision;
1073
- budgetActedThisTurn = budgetDecision.action !== "continue";
1074
- await applyDecision(pi, client, ctx, budgetDecision);
1075
- await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
1076
- } catch {
1077
- halt(ctx, "Jittor could not verify or apply a safe route");
1078
- return;
1079
- }
1080
- }
1081
- if (!budgetActedThisTurn) await runAutoModeTurn(ctx).catch(() => undefined);
1048
+ // Deliberately no daemon or routing work here: Pi awaits turn_start before provider delivery.
1049
+ // The previous agent_settled event computes and applies recommendations for later turns.
1082
1050
  });
1083
1051
 
1084
1052
  pi.on("message_update", async (event) => {
@@ -1095,35 +1063,35 @@ export function registerJittorExtension(
1095
1063
  if (currentTurnToolNames.length < EFFORT_CLASSIFICATION_MAX_TOOL_NAMES) currentTurnToolNames.push(event.toolName);
1096
1064
  });
1097
1065
 
1098
- pi.on("after_provider_response", async (event, ctx) => {
1066
+ pi.on("after_provider_response", (event, ctx) => {
1099
1067
  localRunTelemetry.onProviderResponse();
1100
1068
  if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
1101
1069
  const notifySchemaDrift = (message: string) => {
1102
1070
  if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error");
1103
1071
  };
1104
- await providerResponseTelemetry.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift);
1105
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
1072
+ // Pi fires this before consuming the provider stream. Never put telemetry or footer RPCs
1073
+ // between the HTTP response and the first streamed token.
1074
+ void providerResponseTelemetry
1075
+ .handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift)
1076
+ .catch(() => undefined);
1106
1077
  });
1107
1078
 
1108
- pi.on("turn_end", async (event, ctx) => {
1079
+ pi.on("turn_end", (event, ctx) => {
1109
1080
  const tokens = ctx.getContextUsage()?.tokens;
1110
1081
  if (typeof tokens === "number" && Number.isFinite(tokens)) contextGrowth.observe(++contextGrowthTurn, tokens);
1111
1082
  const metrics = localRunTelemetry.completeTurn(event.message, pi.getThinkingLevel());
1112
- await recordMetrics(client, metrics).catch(() => undefined);
1083
+ void recordMetrics(client, metrics).catch(() => undefined);
1113
1084
  priorTurnToolNames = currentTurnToolNames;
1114
1085
  currentTurnToolNames = [];
1115
1086
  });
1116
1087
 
1117
- pi.on("message_end", async (event, ctx) => {
1088
+ pi.on("message_end", (event, ctx) => {
1118
1089
  if (event.message.role === "assistant") {
1119
1090
  if (event.message.provider === "openai-codex")
1120
1091
  codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
1121
- await providerResponseTelemetry.handleMessageEnd(
1122
- client,
1123
- event.message.provider,
1124
- event.message.stopReason,
1125
- event.message.errorMessage,
1126
- );
1092
+ void providerResponseTelemetry
1093
+ .handleMessageEnd(client, event.message.provider, event.message.stopReason, event.message.errorMessage)
1094
+ .catch(() => undefined);
1127
1095
  }
1128
1096
  const metrics = assistantUsageMetrics(
1129
1097
  event.message,
@@ -1143,9 +1111,10 @@ export function registerJittorExtension(
1143
1111
  cacheRead: amount("cache-read-tokens"),
1144
1112
  cacheWrite: amount("cache-write-tokens"),
1145
1113
  });
1146
- await recordMetrics(client, metrics).catch(() => undefined);
1114
+ void recordMetrics(client, metrics).catch(() => undefined);
1147
1115
  }
1148
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
1116
+ // Footer refresh is consolidated in agent_settled; finalized-message accounting must not
1117
+ // hold back streamed lifecycle delivery or the next queued prompt.
1149
1118
  });
1150
1119
 
1151
1120
  pi.on("session_shutdown", async (_event, ctx) => {
@@ -48,7 +48,7 @@ interface FooterContext {
48
48
  }
49
49
 
50
50
  /** A bounded quota is explicitly remaining; unbounded values never receive a fabricated bar. */
51
- export type ProviderBudget =
51
+ export type ProviderBudget = { availableResets?: { count: number; observedAt: number } } & (
52
52
  | {
53
53
  kind: "bounded";
54
54
  label: string;
@@ -67,7 +67,21 @@ export type ProviderBudget =
67
67
  kind: "unavailable";
68
68
  label: string;
69
69
  valueText: string;
70
- };
70
+ }
71
+ );
72
+
73
+ export function resetAvailabilityText(budget: ProviderBudget | null | undefined, now: number): string | undefined {
74
+ const resets = budget?.availableResets;
75
+ if (
76
+ !resets ||
77
+ !Number.isSafeInteger(resets.count) ||
78
+ resets.count < 1 ||
79
+ resets.count > 10000 ||
80
+ now - resets.observedAt > TELEMETRY_STALE_AFTER_MS
81
+ )
82
+ return undefined;
83
+ return `${resets.count} reset${resets.count === 1 ? "" : "s"} available`;
84
+ }
71
85
 
72
86
  /** Compact, always-visible state of Jittor's effort-based Auto mode -- the newer, effort-driven router, distinct from the existing budget-pressure route already reflected by the model/budget segments. */
73
87
  export interface RouterFooterInfo {
@@ -227,6 +241,18 @@ function budgetSegment(
227
241
  width: number,
228
242
  compact: boolean,
229
243
  now: number,
244
+ ): string | undefined {
245
+ const quota = quotaSegment(budget, theme, width, compact, now);
246
+ const resets = resetAvailabilityText(budget, now);
247
+ return resets ? `${quota ?? "Codex"} · ${resets}` : quota;
248
+ }
249
+
250
+ function quotaSegment(
251
+ budget: ProviderBudget | null | undefined,
252
+ theme: FooterTheme,
253
+ width: number,
254
+ compact: boolean,
255
+ now: number,
230
256
  ): string | undefined {
231
257
  if (budget === undefined) return undefined;
232
258
  const w = barWidth(width);
@@ -13,7 +13,7 @@ import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tu
13
13
  import { BorderedSelectPanel, type TextMeasure } from "malevich-tui-components";
14
14
  import { sessionSecretField } from "../session-identity.ts";
15
15
  import { showConfirmDialog, showRouteOverrideMenu } from "../tui-prompts.ts";
16
- import type { ProviderBudget } from "./footer.ts";
16
+ import { type ProviderBudget, resetAvailabilityText } from "./footer.ts";
17
17
 
18
18
  export interface JittorPanelClient {
19
19
  call(operation: string, input: unknown): Promise<any>;
@@ -136,6 +136,28 @@ export function buildFooterBudget(
136
136
  metrics: StoredMetricObservation[],
137
137
  now = Date.now(),
138
138
  ): ProviderBudget | null | undefined {
139
+ const budget = buildQuotaBudget(status, metrics, now);
140
+ if (status.currentRoute?.provider !== "openai-codex" || codexTelemetryState(status, now) !== "available") return budget;
141
+ const resets = latest(
142
+ metrics,
143
+ (row) => row.source === "codex-subscription" && row.scope === "codex:resets" && row.metric === "available-resets",
144
+ );
145
+ if (
146
+ !resets ||
147
+ typeof resets.value !== "number" ||
148
+ !Number.isSafeInteger(resets.value) ||
149
+ resets.value <= 0 ||
150
+ resets.value > 10000 ||
151
+ now - resets.observedAt > TELEMETRY_STALE_AFTER_MS
152
+ )
153
+ return budget;
154
+ return {
155
+ ...(budget ?? { kind: "unavailable", label: "Codex", valueText: "usage unavailable" }),
156
+ availableResets: { count: resets.value, observedAt: resets.observedAt },
157
+ };
158
+ }
159
+
160
+ function buildQuotaBudget(status: RouterStatus, metrics: StoredMetricObservation[], now = Date.now()): ProviderBudget | null | undefined {
139
161
  if (!status.currentRoute) return null;
140
162
  if (status.currentRoute.provider === "openai-codex") {
141
163
  const codex = codexWindowForModel(metrics, status.currentRoute.model);
@@ -238,8 +260,12 @@ export function formatFooterStatus(status: RouterStatus, metrics: StoredMetricOb
238
260
  const budget = buildFooterBudget(status, metrics, now);
239
261
  if (!budget) return "";
240
262
  if (budget.kind === "unbounded") return budget.valueText;
241
- if (budget.kind === "unavailable") return `${budget.label} ${budget.valueText}`;
242
- return `${budget.label} ${(budget.remainingFraction * 100).toFixed(1)}% left`;
263
+ const resets = resetAvailabilityText(budget, now);
264
+ const quota =
265
+ budget.kind === "unavailable"
266
+ ? `${budget.label} ${budget.valueText}`
267
+ : `${budget.label} ${(budget.remainingFraction * 100).toFixed(1)}% left`;
268
+ return resets ? `${quota} · ${resets}` : quota;
243
269
  }
244
270
 
245
271
  function nextAction(action: PolicyAction | undefined): string {
@@ -287,6 +313,8 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
287
313
  const lines = [status.ready ? "Ready" : "Not ready"];
288
314
  const codex = status.currentRoute?.provider === "openai-codex" ? codexWindowForModel(metrics, status.currentRoute.model) : undefined;
289
315
  const budget = buildFooterBudget(status, metrics, now);
316
+ const resets = resetAvailabilityText(budget, now);
317
+ if (resets) lines.push(`Codex: ${resets}`);
290
318
  if (codex && typeof codex.value === "number" && budget?.kind === "bounded") {
291
319
  const seconds = Number(codex.attributes.windowSeconds ?? 0);
292
320
  lines.push(`Codex ${windowName(seconds)}: ${((1 - codex.value) * 100).toFixed(1)}% left`);
@@ -343,9 +371,24 @@ export interface StatusPanelSnapshot {
343
371
  /** Shared by the standalone status panel below and the unified /jittor shell, so both fetch identically. */
344
372
  export async function fetchStatusSnapshot(client: JittorPanelClient, sessionId: string): Promise<StatusPanelSnapshot> {
345
373
  const status = (await client.call("router.status", { session_id: sessionId })) as RouterStatus;
374
+ const metrics = await fetchProviderBudgetMetrics(client, status);
375
+ return { status, metrics };
376
+ }
377
+
378
+ export async function fetchProviderBudgetMetrics(client: JittorPanelClient, status: RouterStatus): Promise<StoredMetricObservation[]> {
346
379
  const query = providerBudgetMetricQuery(status);
347
380
  const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
348
- return { status, metrics };
381
+ if (status.currentRoute?.provider === "openai-codex") {
382
+ const resets = (await client.call("metrics.query", {
383
+ source: "codex-subscription",
384
+ scope: "codex:resets",
385
+ metric: "available-resets",
386
+ order: "desc",
387
+ limit: 1,
388
+ })) as StoredMetricObservation[];
389
+ return [...metrics, ...resets];
390
+ }
391
+ return metrics;
349
392
  }
350
393
 
351
394
  async function chooseOverride(ctx: ExtensionCommandContext, routes: Route[]): Promise<Route | undefined> {
@@ -15,6 +15,8 @@ let retrying: RetryingClient<JittorClient> = createRetryingClient(() => connecto
15
15
  * is classified: only reads may be transparently invoked twice after a connection-shaped error.
16
16
  */
17
17
  const OPERATION_RETRY_MODE = {
18
+ "subscription.resets.list": "retry",
19
+ "subscription.resets.redeem": "once",
18
20
  "metrics.record": "once",
19
21
  "metrics.record_batch": "once",
20
22
  "metrics.query": "retry",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-jittor",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Pi extension for Jittor token and context observability with optimization controls backed by the @danypops/jittor daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,7 +16,7 @@
16
16
  "@danypops/vehicle-client": "^0.10.1",
17
17
  "@danypops/vehicle-core": "^0.17.1",
18
18
  "@danypops/vehicle-server": "^0.25.1",
19
- "@danypops/jittor": "^0.20.0",
19
+ "@danypops/jittor": "^0.22.0",
20
20
  "malevich-tui-components": "^0.28.0"
21
21
  },
22
22
  "peerDependencies": {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@danypops/pi-extension-harness": "^0.6.2",
29
- "@danypops/pi-process-harness": "^0.1.2",
29
+ "@danypops/pi-process-harness": "^0.3.2",
30
30
  "@danypops/pi-tui-harness": "^0.0.2",
31
31
  "@earendil-works/pi-ai": "*",
32
32
  "bun-types": "latest"