@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.
package/extension/src/index.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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",
|
|
1046
|
-
|
|
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",
|
|
1053
|
-
|
|
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",
|
|
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
|
-
//
|
|
1063
|
-
//
|
|
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",
|
|
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
|
-
|
|
1105
|
-
|
|
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",
|
|
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
|
-
|
|
1083
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1113
1084
|
priorTurnToolNames = currentTurnToolNames;
|
|
1114
1085
|
currentTurnToolNames = [];
|
|
1115
1086
|
});
|
|
1116
1087
|
|
|
1117
|
-
pi.on("message_end",
|
|
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
|
-
|
|
1122
|
-
client,
|
|
1123
|
-
|
|
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
|
-
|
|
1114
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1147
1115
|
}
|
|
1148
|
-
|
|
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
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|