@danypops/jittor 0.10.0 → 0.12.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 (43) hide show
  1. package/README.md +28 -77
  2. package/package.json +11 -14
  3. package/src/adapters/sqlite-metric-store.ts +11 -2
  4. package/src/adapters/sqlite-session-identity-store.ts +45 -0
  5. package/src/cli-commands/benchmarks.ts +140 -0
  6. package/src/cli-commands/compaction.ts +17 -0
  7. package/src/cli-commands/context.ts +49 -0
  8. package/src/cli-commands/metrics.ts +296 -0
  9. package/src/cli-commands/op.ts +40 -0
  10. package/src/cli-commands/route-args.ts +15 -0
  11. package/src/cli-commands/router.ts +207 -0
  12. package/src/cli-commands/service-daemon.ts +72 -0
  13. package/src/cli-commands/session.ts +42 -0
  14. package/src/cli-commands/support.ts +33 -0
  15. package/src/cli.ts +42 -769
  16. package/src/constants.ts +7 -0
  17. package/src/daemon.ts +13 -3
  18. package/src/db.ts +15 -1
  19. package/src/index.ts +137 -0
  20. package/src/operations/benchmark-operations.ts +12 -0
  21. package/src/operations/context-operations.ts +30 -0
  22. package/src/operations/metrics-operations.ts +77 -0
  23. package/src/operations/model-ranking-operations.ts +16 -0
  24. package/src/operations/router-operations.ts +19 -0
  25. package/src/operations/session-identity-operations.ts +15 -0
  26. package/src/operations/session-scope.ts +31 -0
  27. package/src/operations/types.ts +3 -0
  28. package/src/ports/metric-store.ts +2 -0
  29. package/src/ports/router-controller.ts +9 -9
  30. package/src/ports/session-identity-store.ts +5 -0
  31. package/src/providers/telemetry-sources.ts +2 -1
  32. package/src/router.ts +124 -67
  33. package/src/service.ts +60 -118
  34. package/src/session-identity-service.ts +55 -0
  35. package/docs/USAGE_PRIOR_ART.md +0 -64
  36. package/extension/src/benchmark-tui.ts +0 -105
  37. package/extension/src/footer.ts +0 -366
  38. package/extension/src/index.ts +0 -828
  39. package/extension/src/service-client.ts +0 -26
  40. package/extension/src/settings-tui.ts +0 -153
  41. package/extension/src/settings.ts +0 -103
  42. package/extension/src/tui.ts +0 -270
  43. package/extension/src/usage.ts +0 -320
@@ -1,828 +0,0 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import {
3
- CODEX_RECOVERY_ATTEMPT_WINDOW_MS,
4
- CODEX_RECOVERY_BASE_DELAY_MS,
5
- CODEX_RECOVERY_JITTER_RATIO,
6
- CODEX_RECOVERY_MAX_ATTEMPTS,
7
- CODEX_RECOVERY_MAX_DELAY_MS,
8
- FOOTER_COMPACTION_RENDER_INTERVAL_MS,
9
- MAX_DYNAMIC_ROUTES,
10
- MILLISECONDS_PER_MINUTE,
11
- MILLISECONDS_PER_SECOND,
12
- PAPYRUS_CONTEXT_INJECTION_CHANNEL,
13
- PAPYRUS_TASK_FOCUS_CHANNEL,
14
- CONTEXT_EVENT_DEDUP_LIMIT,
15
- } from "../../src/constants.ts";
16
- import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureKind, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
17
- import { CompactionTelemetry, papyrusContextMetric, validatePapyrusContextInjection } from "../../src/domain/context-telemetry.ts";
18
- import { applyTaskFocusEvent, validateTaskFocusEvent } from "../../src/domain/task-focus.ts";
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";
22
- import { USAGE_PERIODS, type UsagePeriod } from "../../src/domain/usage.ts";
23
- import type { PolicyDecision, Route } from "../../src/policy.ts";
24
- import type { RouterStatus } from "../../src/ports/router-controller.ts";
25
- import { hasAnthropicRateLimitHeaders, parseAnthropicRateLimitHeaders } from "../../src/providers/anthropic-contracts.ts";
26
- import { parseCodexRateLimitHeaders } from "../../src/providers/codex.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";
30
- import { callJittor } from "./service-client.ts";
31
- import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
32
- import { showSettingsPanel } from "./settings-tui.ts";
33
- import { buildFooterBudget, formatFooterStatus, showJittorPanel } from "./tui.ts";
34
- import { showUsagePanel } from "./usage.ts";
35
-
36
- export { formatFooterStatus } from "./tui.ts";
37
-
38
- const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
39
- const RECOVERY_GUIDANCE = "Run /jittor off to disable blocking, or restart the daemon with: systemctl --user restart jittor.service";
40
-
41
- export interface JittorExtensionClient {
42
- call(operation: string, input: unknown): Promise<any>;
43
- }
44
-
45
- const daemonClient: JittorExtensionClient = {
46
- call: (operation, input) => callJittor(operation as Parameters<typeof callJittor>[0], input as never),
47
- };
48
-
49
- export interface CodexRecoveryRuntime {
50
- now(): number;
51
- random(): number;
52
- setTimeout(callback: () => void | Promise<void>, delayMs: number): unknown;
53
- clearTimeout(handle: unknown): void;
54
- }
55
-
56
- const SYSTEM_RECOVERY_RUNTIME: CodexRecoveryRuntime = {
57
- now: Date.now,
58
- random: Math.random,
59
- setTimeout(callback, delayMs) { return setTimeout(() => { void callback(); }, delayMs); },
60
- clearTimeout(handle) { clearTimeout(handle as ReturnType<typeof setTimeout>); },
61
- };
62
-
63
- function usageBudgetControl(enforcement: EnforcementControl): UsageBudgetControl {
64
- const candidate = enforcement as EnforcementControl & Partial<UsageBudgetControl>;
65
- return typeof candidate.getUsageTokenBudget === "function" && typeof candidate.setUsageTokenBudget === "function"
66
- ? {
67
- getUsageTokenBudget: (period) => candidate.getUsageTokenBudget!(period),
68
- setUsageTokenBudget: (period, tokens) => candidate.setUsageTokenBudget!(period, tokens),
69
- }
70
- : { getUsageTokenBudget: () => undefined, setUsageTokenBudget() {} };
71
- }
72
-
73
- function recoveryControl(enforcement: EnforcementControl): CodexRecoveryControl {
74
- const candidate = enforcement as EnforcementControl & Partial<CodexRecoveryControl>;
75
- const set = (candidate as Partial<CodexRecoveryControl>).setCodexRecoveryEnabled;
76
- return typeof candidate.isCodexRecoveryEnabled === "function" && typeof set === "function"
77
- ? {
78
- isCodexRecoveryEnabled: () => candidate.isCodexRecoveryEnabled!(),
79
- setCodexRecoveryEnabled: (enabled) => set.call(candidate, enabled),
80
- }
81
- : { isCodexRecoveryEnabled: () => false, setCodexRecoveryEnabled() {} };
82
- }
83
-
84
- function header(headers: Record<string, string>, name: string): string | undefined {
85
- const expected = name.toLowerCase();
86
- return Object.entries(headers).find(([key]) => key.toLowerCase() === expected)?.[1];
87
- }
88
-
89
- async function recordMetrics(client: JittorExtensionClient, metrics: MetricObservation[]): Promise<void> {
90
- for (const metric of metrics) await client.call("metrics.record", metric);
91
- }
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
-
103
- async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState): Promise<void> {
104
- const status = await client.call("router.status", {}) as RouterStatus;
105
- const provider = status.currentRoute?.provider;
106
- const query = provider === "openai-codex"
107
- ? { source: "codex-subscription", metric: "used-fraction", limit: 100, order: "desc" }
108
- : provider === "openrouter" ? { source: "openrouter", limit: 20, order: "desc" } : null;
109
- const metrics = query ? await client.call("metrics.query", query) as StoredMetricObservation[] : [];
110
- state.providerBudget = buildFooterBudget(status, metrics);
111
- state.requestRender?.();
112
- }
113
-
114
- function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
115
- if (milliseconds <= 0) return Promise.resolve();
116
- return new Promise((resolve, reject) => {
117
- const timer = setTimeout(resolve, milliseconds);
118
- signal?.addEventListener("abort", () => {
119
- clearTimeout(timer);
120
- reject(new Error("Jittor throttle cancelled"));
121
- }, { once: true });
122
- });
123
- }
124
-
125
- function routeModelAvailable(ctx: ExtensionContext, route: Route): boolean {
126
- return ctx.modelRegistry.getAvailable().some((model) => model.provider === route.provider && model.id === route.model);
127
- }
128
-
129
- async function applyRoute(pi: ExtensionAPI, ctx: ExtensionContext, route: Route): Promise<boolean> {
130
- if (!routeModelAvailable(ctx, route)) return false;
131
- const model = ctx.modelRegistry.find(route.provider, route.model);
132
- if (!model) return false;
133
- if (!ctx.model || ctx.model.provider !== route.provider || ctx.model.id !== route.model) {
134
- if (!await pi.setModel(model)) return false;
135
- }
136
- if (THINKING_LEVELS.has(route.thinking)) pi.setThinkingLevel(route.thinking as Parameters<ExtensionAPI["setThinkingLevel"]>[0]);
137
- return true;
138
- }
139
-
140
- interface PiRouteModel {
141
- provider: string;
142
- id: string;
143
- reasoning?: boolean;
144
- thinkingLevelMap?: Partial<Record<string, unknown>>;
145
- cost?: { input?: number; output?: number };
146
- }
147
-
148
- const THINKING_DESCENDING = ["max", "xhigh", "high", "medium", "low", "minimal", "off"] as const;
149
-
150
- function supportsThinking(model: PiRouteModel, level: string): boolean {
151
- if (!model.reasoning) return level === "off";
152
- return model.thinkingLevelMap?.[level] !== null;
153
- }
154
-
155
- function modelCost(model: PiRouteModel): number {
156
- return (model.cost?.input ?? 0) + (model.cost?.output ?? 0);
157
- }
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
-
170
- export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
171
- const sameProvider = models
172
- .filter((model) => model.provider === current.provider)
173
- .filter((model, index, rows) => rows.findIndex((candidate) => candidate.id === model.id) === index);
174
- if (!sameProvider.some((model) => model.id === current.id)) sameProvider.push(current);
175
- const routes: Route[] = [{ provider: current.provider, model: current.id, thinking }];
176
- const currentLevel = THINKING_DESCENDING.indexOf(thinking as typeof THINKING_DESCENDING[number]);
177
- const lowerLevels = THINKING_DESCENDING.slice(currentLevel >= 0 ? currentLevel + 1 : 0);
178
- for (const level of lowerLevels) {
179
- if (supportsThinking(current, level)) routes.push({ provider: current.provider, model: current.id, thinking: level });
180
- }
181
- const alternatives = sameProvider
182
- .filter((model) => model.id !== current.id)
183
- .sort((left, right) => modelCost(left) - modelCost(right) || left.id.localeCompare(right.id));
184
- for (const model of alternatives) {
185
- const level = [thinking, ...lowerLevels].find((candidate) => supportsThinking(model, candidate)) ?? "off";
186
- routes.push({ provider: model.provider, model: model.id, thinking: level });
187
- if (routes.length >= MAX_DYNAMIC_ROUTES) break;
188
- }
189
- return routes;
190
- }
191
-
192
- async function syncAvailableRoutes(pi: ExtensionAPI, client: JittorExtensionClient, ctx: ExtensionContext): Promise<void> {
193
- if (!ctx.model) { await client.call("router.available_routes", { routes: [] }); return; }
194
- const models = ctx.modelRegistry.getAvailable() as PiRouteModel[];
195
- const routes = routesFromPi(models, ctx.model as PiRouteModel, pi.getThinkingLevel());
196
- await client.call("router.available_routes", { routes });
197
- }
198
-
199
- async function syncCurrentRoute(
200
- pi: ExtensionAPI,
201
- client: JittorExtensionClient,
202
- ctx: ExtensionContext,
203
- model = ctx.model,
204
- thinking = pi.getThinkingLevel(),
205
- ): Promise<void> {
206
- if (!model) return;
207
- await client.call("router.current_route", { provider: model.provider, model: model.id, thinking });
208
- }
209
-
210
- function halt(ctx: ExtensionContext, reason: string): false {
211
- ctx.ui.notify(`${reason}. ${RECOVERY_GUIDANCE}.`, "warning");
212
- ctx.abort();
213
- return false;
214
- }
215
-
216
- async function applyDecision(
217
- pi: ExtensionAPI,
218
- client: JittorExtensionClient,
219
- ctx: ExtensionContext,
220
- decision: PolicyDecision,
221
- allowResync = true,
222
- ): Promise<boolean> {
223
- if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
224
- if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
225
- if (!decision.route || await applyRoute(pi, ctx, decision.route)) return true;
226
- if (allowResync) {
227
- await syncAvailableRoutes(pi, client, ctx);
228
- return applyDecision(pi, client, ctx, await client.call("router.decide", {}) as PolicyDecision, false);
229
- }
230
- return halt(ctx, `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`);
231
- }
232
-
233
- /**
234
- * taskId, when a Papyrus task is focused, tags the metric for cost-per-task correlation. thinking
235
- * comes from pi.getThinkingLevel() at message_end time, not from the message itself -- AssistantMessage
236
- * has no thinking field of its own, and the level can't have changed mid-message.
237
- */
238
- function assistantUsageMetrics(message: unknown, observedAt: number, taskId: string | null = null, thinking: string | null = null): MetricObservation[] {
239
- if (typeof message !== "object" || message === null || Array.isArray(message)) return [];
240
- const value = message as Record<string, unknown>;
241
- if (value["role"] !== "assistant" || typeof value["usage"] !== "object" || value["usage"] === null) return [];
242
- const usage = value["usage"] as Record<string, unknown>;
243
- const provider = typeof value["provider"] === "string" ? value["provider"] : "unknown";
244
- const model = typeof value["model"] === "string" ? value["model"] : "unknown";
245
- const scope = `${provider}:${model}`;
246
- const attributes = { provider, model, ...(taskId === null ? {} : { taskId }), ...(thinking === null || thinking.length === 0 ? {} : { thinking }) };
247
- const metrics: MetricObservation[] = [];
248
- for (const [field, metric] of [["input", "input-tokens"], ["output", "output-tokens"], ["cacheRead", "cache-read-tokens"], ["cacheWrite", "cache-write-tokens"]] as const) {
249
- const amount = usage[field];
250
- if (typeof amount === "number" && Number.isFinite(amount)) metrics.push({ source: "pi", scope, metric, value: amount, unit: "tokens", observedAt, attributes });
251
- }
252
- const cost = typeof usage["cost"] === "object" && usage["cost"] !== null ? (usage["cost"] as Record<string, unknown>)["total"] : undefined;
253
- if (typeof cost === "number" && Number.isFinite(cost)) metrics.push({ source: "pi", scope, metric: "cost", value: cost, unit: "usd", observedAt, attributes });
254
- return metrics;
255
- }
256
-
257
- export function registerJittorExtension(
258
- pi: ExtensionAPI,
259
- client: JittorExtensionClient = daemonClient,
260
- enforcement: EnforcementControl = persistentEnforcementControl(),
261
- codexRecovery: CodexRecoveryControl = recoveryControl(enforcement),
262
- recoveryRuntime: CodexRecoveryRuntime = SYSTEM_RECOVERY_RUNTIME,
263
- ): void {
264
- const footerState: IntegratedFooterState = { providerBudget: null };
265
- const usageBudgets = usageBudgetControl(enforcement);
266
- let compactionTelemetry = new CompactionTelemetry();
267
- let localRunSequence = 0;
268
- let activeLocalRun: ActiveLocalModelRun | undefined;
269
- let lastCompletedLocalRun: ModelRunObservation | undefined;
270
- const contextObservations = new Set<string>();
271
- const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
272
- try {
273
- const observation = validatePapyrusContextInjection(payload);
274
- const observationKey = `${observation.producerId}:${observation.sequence}`;
275
- if (contextObservations.has(observationKey)) return;
276
- contextObservations.add(observationKey);
277
- if (contextObservations.size > CONTEXT_EVENT_DEDUP_LIMIT) contextObservations.delete(contextObservations.values().next().value!);
278
- compactionTelemetry.observeInjection(observation.injected.characters, observation.estimatedTokens);
279
- void recordMetrics(client, [papyrusContextMetric(observation)]).catch(() => undefined);
280
- } catch {
281
- // Reject malformed or stale cross-extension observations without retaining payloads.
282
- }
283
- });
284
- // Real-time cost-per-task correlation: Jittor observes Papyrus's task-focus broadcasts (Papyrus
285
- // never depends on Jittor) and tags newly recorded token/cost metrics with the currently focused
286
- // task id. Scoped to this Pi session: a focus change in a different concurrent session must not
287
- // affect this one's attribution.
288
- let currentSessionId: string | undefined;
289
- let focusedTaskId: string | null = null;
290
- const stopPapyrusTaskFocus = pi.events?.on?.(PAPYRUS_TASK_FOCUS_CHANNEL, (payload) => {
291
- try {
292
- const event = validateTaskFocusEvent(payload);
293
- if (event.sessionId !== undefined && event.sessionId !== currentSessionId) return;
294
- focusedTaskId = applyTaskFocusEvent(event);
295
- } catch {
296
- // Reject malformed or stale cross-extension events without retaining payloads or crashing the extension.
297
- }
298
- });
299
- const recoveryPolicy = new CodexRecoveryPolicy({
300
- baseDelayMs: CODEX_RECOVERY_BASE_DELAY_MS,
301
- maxDelayMs: CODEX_RECOVERY_MAX_DELAY_MS,
302
- maxAttempts: CODEX_RECOVERY_MAX_ATTEMPTS,
303
- attemptWindowMs: CODEX_RECOVERY_ATTEMPT_WINDOW_MS,
304
- jitterRatio: CODEX_RECOVERY_JITTER_RATIO,
305
- }, recoveryRuntime.random);
306
- let recoveryTimer: unknown;
307
- let recoveryCooldown: { until: number; attempt: number; failureKind: CodexFailureKind } | undefined;
308
- let lastCodexResponse: CodexFailureMetadata = {};
309
- let lastGoogleVertexResponse: GoogleVertexFailureMetadata = {};
310
- // The third-party "anthropic-vertex" provider (Anthropic Claude via Google Vertex) is tracked
311
- // separately from "google-vertex" (Pi's own, unrelated native Vertex provider): different code
312
- // path, different account/quota pool, and its metrics must stay distinguishable -- see
313
- // google-vertex-contracts.ts and anthropic-contracts.ts.
314
- let lastAnthropicVertexResponse: GoogleVertexFailureMetadata = {};
315
- const cancelRecovery = (resetPolicy: boolean): void => {
316
- if (recoveryTimer !== undefined) recoveryRuntime.clearTimeout(recoveryTimer);
317
- recoveryTimer = undefined;
318
- recoveryCooldown = undefined;
319
- if (resetPolicy) recoveryPolicy.cancel();
320
- };
321
- const recoveryStatusText = (): string => {
322
- const now = recoveryRuntime.now();
323
- const state = recoveryPolicy.state(now);
324
- const enabled = codexRecovery.isCodexRecoveryEnabled();
325
- const attempt = recoveryCooldown?.attempt ?? (state.pending ? state.attempts + 1 : state.attempts);
326
- const phase = recoveryCooldown
327
- ? `cooldown ${Math.ceil(Math.max(0, recoveryCooldown.until - now) / MILLISECONDS_PER_SECOND)}s`
328
- : state.pending ? "pending"
329
- : state.attempts >= CODEX_RECOVERY_MAX_ATTEMPTS ? "exhausted"
330
- : state.attempts > 0 ? "waiting" : "idle";
331
- const failureKind = recoveryCooldown?.failureKind ?? state.lastFailureKind;
332
- return [
333
- `Codex recovery: ${enabled ? "on" : "off"}`,
334
- phase,
335
- `attempt ${attempt}/${CODEX_RECOVERY_MAX_ATTEMPTS}`,
336
- `window ${CODEX_RECOVERY_ATTEMPT_WINDOW_MS / MILLISECONDS_PER_MINUTE}m`,
337
- ...(failureKind ? [failureKind] : []),
338
- ].join(" · ");
339
- };
340
- const scheduleCodexRecovery = (ctx: ExtensionContext): void => {
341
- if (!codexRecovery.isCodexRecoveryEnabled() || recoveryTimer !== undefined || !ctx.isIdle() || ctx.hasPendingMessages()) return;
342
- const plan = recoveryPolicy.plan(recoveryRuntime.now());
343
- if (plan.action === "exhausted") {
344
- recoveryPolicy.abandonFailure();
345
- if (ctx.hasUI) ctx.ui.notify(`Jittor Codex recovery stopped: ${plan.reason}.`, "warning");
346
- return;
347
- }
348
- if (plan.action !== "schedule") return;
349
- recoveryCooldown = { until: recoveryRuntime.now() + plan.delayMs, attempt: plan.attempt, failureKind: plan.failureKind };
350
- recoveryTimer = recoveryRuntime.setTimeout(async () => {
351
- recoveryTimer = undefined;
352
- recoveryCooldown = undefined;
353
- if (!ctx.isIdle() || ctx.hasPendingMessages()) return;
354
- const attempt = recoveryPolicy.recordAttempt(recoveryRuntime.now());
355
- if (!attempt) return;
356
- pi.sendMessage({
357
- customType: "jittor-codex-recovery",
358
- content: `Retry the previous Codex request after a transient ${attempt.failureKind} failure. Automatic recovery attempt ${attempt.attempt} of ${CODEX_RECOVERY_MAX_ATTEMPTS}.`,
359
- display: false,
360
- details: { attempt: attempt.attempt, failureKind: attempt.failureKind },
361
- }, { triggerTurn: true, deliverAs: "followUp" });
362
- }, plan.delayMs);
363
- };
364
- let compactionTimer: ReturnType<typeof setInterval> | undefined;
365
- const finishCompactionUi = (): void => {
366
- if (compactionTimer) clearInterval(compactionTimer);
367
- compactionTimer = undefined;
368
- footerState.compaction = undefined;
369
- footerState.requestRender?.();
370
- };
371
- const beginCompactionUi = (ctx: ExtensionContext, signal: AbortSignal): void => {
372
- finishCompactionUi();
373
- const usage = ctx.getContextUsage();
374
- const compaction: CompactionProgress = {
375
- startedAt: Date.now(),
376
- initialFraction: usage?.percent === null || usage?.percent === undefined ? 1 : usage.percent / 100,
377
- estimatedMs: null,
378
- confidence: "cold-start",
379
- };
380
- footerState.compaction = compaction;
381
- // Non-blocking: compaction UI starts immediately as cold-start; if a learned estimate resolves
382
- // before this compaction finishes (and this is still the active compaction, not a later one),
383
- // upgrade the same progress object in place so the drain bar and status text switch to "learned".
384
- void client.call("compaction.estimate", {}).then((estimate) => {
385
- if (footerState.compaction !== compaction || estimate.confidence !== "learned" || estimate.ms === null) return;
386
- footerState.compaction = { ...compaction, estimatedMs: estimate.ms, confidence: "learned" };
387
- footerState.requestRender?.();
388
- }).catch(() => undefined);
389
- compactionTimer = setInterval(() => footerState.requestRender?.(), FOOTER_COMPACTION_RENDER_INTERVAL_MS);
390
- signal.addEventListener("abort", finishCompactionUi, { once: true });
391
- if (signal.aborted) finishCompactionUi();
392
- else footerState.requestRender?.();
393
- };
394
- const showFooter = (ctx: ExtensionContext): void => {
395
- if (enforcement.isFooterEnabled()) installIntegratedFooter(ctx, footerState, () => pi.getThinkingLevel());
396
- else ctx.ui.setFooter(undefined);
397
- };
398
- const disable = (ctx: ExtensionContext): void => {
399
- enforcement.setEnabled(false);
400
- ctx.ui.setStatus("jittor", undefined);
401
- showFooter(ctx);
402
- ctx.ui.notify("Jittor enforcement is off (monitor-only); the informational footer remains independent and provider requests will not be blocked.", "warning");
403
- };
404
- const enable = async (ctx: ExtensionContext): Promise<void> => {
405
- try {
406
- await syncCurrentRoute(pi, client, ctx);
407
- await syncAvailableRoutes(pi, client, ctx);
408
- await client.call("telemetry.poll", {});
409
- const readinessDecision = await client.call("router.decide", {}) as PolicyDecision;
410
- if (readinessDecision.action === "halt") throw new Error(readinessDecision.reason);
411
- enforcement.setEnabled(true);
412
- showFooter(ctx);
413
- await refreshFooter(client, footerState);
414
- ctx.ui.notify("Jittor enforcement enabled.", "info");
415
- } catch (error) {
416
- enforcement.setEnabled(false);
417
- showFooter(ctx);
418
- const reason = error instanceof Error ? error.message : "readiness failed";
419
- ctx.ui.notify(`Jittor remains monitor-only: ${reason}. ${RECOVERY_GUIDANCE}.`, "error");
420
- }
421
- };
422
-
423
- pi.registerCommand("jittor", {
424
- description: "Jittor settings, routing status, benchmarks, and Codex recovery controls",
425
- handler: async (args, ctx) => {
426
- const action = args.trim().toLowerCase();
427
- if (action === "" || action === "settings") {
428
- await showSettingsPanel(ctx, enforcement, codexRecovery, usageBudgets, {
429
- setEnforcement: async (enabled) => enabled ? enable(ctx) : disable(ctx),
430
- setFooter: async (enabled) => {
431
- enforcement.setFooterEnabled(enabled);
432
- showFooter(ctx);
433
- if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
434
- },
435
- setRecovery: (enabled) => {
436
- if (!enabled) cancelRecovery(true);
437
- codexRecovery.setCodexRecoveryEnabled(enabled);
438
- },
439
- });
440
- return;
441
- }
442
- if (action === "benchmarks" || action.startsWith("benchmarks ")) {
443
- if (!ctx.model) {
444
- ctx.ui.notify("No active Pi model is available for benchmark recommendations.", "warning");
445
- return;
446
- }
447
- // Domain (subject matter, e.g. coding) and type (activity, e.g. research/planning) are
448
- // independent axes -- each positional word is classified against whichever axis it
449
- // belongs to, in either order, so "/jittor benchmarks coding research" and
450
- // "/jittor benchmarks research coding" both work; an unmatched word is a usage error.
451
- const requested = action.split(/\s+/).slice(1);
452
- let requestedDomain: ModelTaskDomain | undefined;
453
- let requestedType: ModelTaskType | undefined;
454
- let malformed = requested.length > 2;
455
- for (const word of requested) {
456
- if (TASK_DOMAINS.includes(word as ModelTaskDomain) && requestedDomain === undefined) requestedDomain = word as ModelTaskDomain;
457
- else if (TASK_TYPES.includes(word as ModelTaskType) && requestedType === undefined) requestedType = word as ModelTaskType;
458
- else malformed = true;
459
- }
460
- if (malformed) {
461
- ctx.ui.notify("Usage: /jittor benchmarks [coding|general] [research|planning|general]", "warning");
462
- return;
463
- }
464
- const candidates = benchmarkCandidatesFromPi(ctx.modelRegistry.getAvailable() as PiRouteModel[], pi.getThinkingLevel());
465
- await showBenchmarkPanel(ctx, client, candidates, `${ctx.model.provider}/${ctx.model.id}`, requestedDomain ?? "general", requestedType ?? "general");
466
- return;
467
- }
468
- if (action === "outcome accepted" || action === "outcome rejected") {
469
- if (!lastCompletedLocalRun) {
470
- ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
471
- return;
472
- }
473
- const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
474
- const outcomeMetric = modelRunMetrics({ ...lastCompletedLocalRun, explicitOutcome }).find((metric) => metric.metric === "outcome-accepted")!;
475
- outcomeMetric.observedAt = Date.now();
476
- await recordMetrics(client, [outcomeMetric]);
477
- ctx.ui.notify(`Recorded explicit ${explicitOutcome} outcome for the latest local model run.`, "info");
478
- return;
479
- }
480
- if (action === "recovery" || action === "recovery status") {
481
- ctx.ui.notify(recoveryStatusText(), "info");
482
- return;
483
- }
484
- if (action === "recovery on" || action === "recovery enable") {
485
- codexRecovery.setCodexRecoveryEnabled(true);
486
- ctx.ui.notify("Jittor Codex recovery enabled; bounded retries begin only after transient failures fully settle.", "info");
487
- return;
488
- }
489
- if (action === "recovery off" || action === "recovery disable") {
490
- cancelRecovery(true);
491
- codexRecovery.setCodexRecoveryEnabled(false);
492
- ctx.ui.notify("Jittor Codex recovery disabled and pending recovery cleared.", "info");
493
- return;
494
- }
495
- if (action === "recovery cancel") {
496
- cancelRecovery(true);
497
- ctx.ui.notify(`Jittor Codex recovery cooldown and attempt window cleared; recovery remains ${codexRecovery.isCodexRecoveryEnabled() ? "on" : "off"}.`, "info");
498
- return;
499
- }
500
- if (action === "off" || action === "disable") { disable(ctx); return; }
501
- if (action === "on" || action === "enable") { await enable(ctx); return; }
502
- if (action === "footer off" || action === "footer disable") {
503
- enforcement.setFooterEnabled(false);
504
- ctx.ui.setFooter(undefined);
505
- ctx.ui.notify("Jittor footer disabled; routing enforcement is unchanged.", "info");
506
- return;
507
- }
508
- if (action === "footer on" || action === "footer enable") {
509
- enforcement.setFooterEnabled(true);
510
- showFooter(ctx);
511
- await refreshFooter(client, footerState).catch(() => undefined);
512
- ctx.ui.notify("Jittor informational footer enabled; routing enforcement is unchanged.", "info");
513
- return;
514
- }
515
- if (action === "context") {
516
- const summary = await client.call("context.assess", {}) as import("../../src/domain/context-telemetry.ts").ContextAssessment;
517
- const average = summary.injection.averageCharacters === null ? "unknown" : Math.round(summary.injection.averageCharacters).toLocaleString();
518
- const p95 = summary.injection.p95Characters === null ? "unknown" : Math.round(summary.injection.p95Characters).toLocaleString();
519
- ctx.ui.notify([
520
- `Papyrus injection: ${summary.injection.runs} runs · avg ${average} chars · p95 ${p95} chars · unchanged ${summary.injection.unchangedRate === null ? "unknown" : `${(summary.injection.unchangedRate * 100).toFixed(1)}%`}`,
521
- `Mix: rules ${summary.injection.ruleCharacters.toLocaleString()} chars · tasks ${summary.injection.taskCharacters.toLocaleString()} chars · estimated ${summary.injection.estimatedTokens.toLocaleString()} tokens`,
522
- `Compactions: ${summary.compaction.completed} completed · ${summary.compaction.aborted} aborted · ${summary.compaction.perRun === null ? "unknown" : summary.compaction.perRun.toFixed(3)} per agent run · ${summary.compaction.perTurn === null ? "unknown" : summary.compaction.perTurn.toFixed(3)} per turn`,
523
- `Completeness: ${summary.completeness}`,
524
- ].join("\n"), "info");
525
- return;
526
- }
527
- // Reached only for the explicit "status" keyword or any other unrecognized text; bare "" is
528
- // handled above by the settings branch, so this always has a non-empty, non-settings action.
529
- if (!enforcement.isEnabled()) {
530
- ctx.ui.notify("Jittor is monitor-only. Run /jittor on to re-enable blocking.", "info");
531
- return;
532
- }
533
- await showJittorPanel(ctx, client);
534
- },
535
- });
536
-
537
- pi.registerCommand("usage", {
538
- description: "Cumulative token/cost usage graph with hourly/daily/weekly/monthly/quarterly views",
539
- handler: async (args, ctx) => {
540
- const action = args.trim().toLowerCase();
541
- if (action === "budget" || action.startsWith("budget ")) {
542
- const [, periodText, valueText] = action.split(/\s+/);
543
- const period = USAGE_PERIODS.some((candidate) => candidate.id === periodText) ? periodText as UsagePeriod : undefined;
544
- if (!period) {
545
- const values = USAGE_PERIODS.map(({ id, label }) => `${label}: ${usageBudgets.getUsageTokenBudget(id)?.toLocaleString() ?? "not configured"}`).join(" · ");
546
- ctx.ui.notify(`Token budgets · ${values}`, "info");
547
- return;
548
- }
549
- if (valueText === undefined) {
550
- ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget: ${usageBudgets.getUsageTokenBudget(period)?.toLocaleString() ?? "not configured"}`, "info");
551
- return;
552
- }
553
- if (valueText === "off" || valueText === "clear") {
554
- usageBudgets.setUsageTokenBudget(period, undefined);
555
- ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget cleared.`, "info");
556
- return;
557
- }
558
- const tokens = Number(valueText.replaceAll(",", ""));
559
- if (!Number.isFinite(tokens) || tokens <= 0) {
560
- ctx.ui.notify("Usage: /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
561
- return;
562
- }
563
- usageBudgets.setUsageTokenBudget(period, tokens);
564
- ctx.ui.notify(`${USAGE_PERIODS.find((candidate) => candidate.id === period)!.label} token budget set to ${tokens.toLocaleString()} tokens.`, "info");
565
- return;
566
- }
567
- if (action !== "" && action !== "cost" && action !== "tokens") {
568
- ctx.ui.notify("Usage: /usage [cost] | /usage budget <hourly|daily|weekly|monthly|quarterly> <positive-tokens|off>", "warning");
569
- return;
570
- }
571
- await showUsagePanel(ctx, client, usageBudgets, Date.now(), action === "cost" ? "cost" : "tokens");
572
- },
573
- });
574
-
575
- pi.on("session_start", async (_event, ctx) => {
576
- currentSessionId = ctx.sessionManager.getSessionId();
577
- focusedTaskId = null;
578
- finishCompactionUi();
579
- compactionTelemetry = new CompactionTelemetry();
580
- activeLocalRun = undefined;
581
- lastCompletedLocalRun = undefined;
582
- cancelRecovery(true);
583
- lastCodexResponse = {};
584
- lastGoogleVertexResponse = {};
585
- lastAnthropicVertexResponse = {};
586
- ctx.ui.setStatus("jittor", undefined);
587
- showFooter(ctx);
588
- try {
589
- await syncCurrentRoute(pi, client, ctx);
590
- await syncAvailableRoutes(pi, client, ctx);
591
- await client.call("telemetry.poll", {});
592
- await refreshFooter(client, footerState);
593
- } catch {
594
- footerState.providerBudget = null;
595
- footerState.requestRender?.();
596
- }
597
- });
598
-
599
- pi.on("session_before_compact", async (event, ctx) => {
600
- beginCompactionUi(ctx, event.signal);
601
- const usage = ctx.getContextUsage();
602
- const metric = compactionTelemetry.begin({
603
- reason: event.reason,
604
- willRetry: event.willRetry,
605
- ...(usage?.percent === null || usage?.percent === undefined ? {} : { contextPercent: usage.percent }),
606
- ...(usage?.tokens === null || usage?.tokens === undefined ? {} : { contextTokens: usage.tokens }),
607
- });
608
- await recordMetrics(client, [metric]).catch(() => undefined);
609
- });
610
-
611
- pi.on("session_compact", async (event) => {
612
- finishCompactionUi();
613
- await recordMetrics(client, [compactionTelemetry.complete({ reason: event.reason, willRetry: event.willRetry })]).catch(() => undefined);
614
- });
615
-
616
- pi.on("agent_settled", async (_event, ctx) => {
617
- if (footerState.compaction) {
618
- finishCompactionUi();
619
- if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
620
- }
621
- scheduleCodexRecovery(ctx);
622
- if (!enforcement.isFooterEnabled()) return;
623
- try {
624
- await syncCurrentRoute(pi, client, ctx);
625
- await syncAvailableRoutes(pi, client, ctx);
626
- await refreshFooter(client, footerState);
627
- } catch {
628
- footerState.providerBudget = null;
629
- footerState.requestRender?.();
630
- }
631
- });
632
-
633
- pi.on("input", async (event, ctx) => {
634
- if (event.source !== "extension") cancelRecovery(true);
635
- if (event.source === "extension" || !enforcement.isEnabled()) return { action: "continue" as const };
636
- try {
637
- const next = await client.call("router.decide", {}) as PolicyDecision;
638
- if (next.action === "halt") {
639
- ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
640
- return { action: "handled" as const };
641
- }
642
- return { action: "continue" as const };
643
- } catch {
644
- ctx.ui.notify(`Jittor could not verify budget telemetry, so fail-closed enforcement blocked input. ${RECOVERY_GUIDANCE}.`, "error");
645
- return { action: "handled" as const };
646
- }
647
- });
648
-
649
- pi.on("model_select", async (event, ctx) => {
650
- await syncCurrentRoute(pi, client, ctx, event.model).then(() => syncAvailableRoutes(pi, client, ctx)).catch(() => undefined);
651
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
652
- });
653
-
654
- pi.on("thinking_level_select", async (event, ctx) => {
655
- await syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
656
- });
657
-
658
- pi.on("turn_start", async (event, ctx) => {
659
- currentSessionId = ctx.sessionManager.getSessionId();
660
- compactionTelemetry.observeTurn();
661
- lastCodexResponse = {};
662
- lastGoogleVertexResponse = {};
663
- lastAnthropicVertexResponse = {};
664
- activeLocalRun = {
665
- runId: `local-${event.timestamp}-${++localRunSequence}`,
666
- startedAt: event.timestamp,
667
- firstTokenAt: null,
668
- providerResponses: 0,
669
- toolNames: [],
670
- toolCalls: 0,
671
- toolFailures: 0,
672
- };
673
- if (!enforcement.isEnabled()) return;
674
- try {
675
- await syncCurrentRoute(pi, client, ctx);
676
- await syncAvailableRoutes(pi, client, ctx);
677
- await applyDecision(pi, client, ctx, await client.call("router.decide", {}) as PolicyDecision);
678
- await refreshFooter(client, footerState);
679
- } catch {
680
- halt(ctx, "Jittor could not verify or apply a safe route");
681
- }
682
- });
683
-
684
- pi.on("message_update", async (event) => {
685
- if (!activeLocalRun || activeLocalRun.firstTokenAt !== null) return;
686
- if (["text_delta", "thinking_delta", "toolcall_delta"].includes(event.assistantMessageEvent.type)) activeLocalRun.firstTokenAt = Date.now();
687
- });
688
-
689
- pi.on("tool_execution_end", async (event) => {
690
- if (!activeLocalRun) return;
691
- activeLocalRun.toolCalls += 1;
692
- if (event.isError) activeLocalRun.toolFailures += 1;
693
- if (activeLocalRun.toolNames.length < 100) activeLocalRun.toolNames.push(event.toolName);
694
- });
695
-
696
- pi.on("after_provider_response", async (event, ctx) => {
697
- if (activeLocalRun) activeLocalRun.providerResponses += 1;
698
- if (ctx.model?.provider === "openai-codex") {
699
- lastCodexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
700
- }
701
- if (ctx.model?.provider === "anthropic") {
702
- const headers = new Headers(event.headers);
703
- if (hasAnthropicRateLimitHeaders(headers)) {
704
- try {
705
- await recordMetrics(client, parseAnthropicRateLimitHeaders(headers, Date.now()).metrics);
706
- } catch {
707
- if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Anthropic telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
708
- }
709
- }
710
- }
711
- if (ctx.model?.provider === "anthropic-vertex") {
712
- // Best-effort only: unverified whether this passthrough ever forwards Anthropic's own
713
- // rate-limit headers. If it doesn't, hasAnthropicRateLimitHeaders is false and nothing is
714
- // recorded -- the same honest default as every other unconfirmed signal in this file.
715
- const headers = new Headers(event.headers);
716
- if (hasAnthropicRateLimitHeaders(headers)) {
717
- try {
718
- await recordMetrics(client, parseAnthropicRateLimitHeaders(headers, Date.now(), "anthropic-vertex").metrics);
719
- } catch {
720
- if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Anthropic-on-Vertex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
721
- }
722
- }
723
- // Well-evidenced regardless of headers: GCP's own quota system fronts this transport, so the
724
- // same failure classification as google-vertex applies -- see google-vertex-contracts.ts.
725
- lastAnthropicVertexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
726
- }
727
- if (ctx.model?.provider === "google-vertex") {
728
- lastGoogleVertexResponse = { status: event.status, ...(header(event.headers, "retry-after") ? { retryAfter: header(event.headers, "retry-after") } : {}) };
729
- }
730
- if (Object.keys(event.headers).some((name) => name.toLowerCase().startsWith("x-codex-"))) {
731
- try {
732
- const updates = parseCodexRateLimitHeaders(new Headers(event.headers), Date.now());
733
- await recordMetrics(client, updates.flatMap((update) => update.metrics));
734
- } catch {
735
- if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected Codex telemetry schema drift. ${RECOVERY_GUIDANCE}.`, "error");
736
- }
737
- }
738
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
739
- });
740
-
741
- pi.on("turn_end", async (event) => {
742
- const active = activeLocalRun;
743
- activeLocalRun = undefined;
744
- const message = event.message as unknown;
745
- if (!active || typeof message !== "object" || message === null || Array.isArray(message)) return;
746
- const value = message as Record<string, unknown>;
747
- if (value["role"] !== "assistant" || typeof value["provider"] !== "string" || typeof value["model"] !== "string") return;
748
- const usage = typeof value["usage"] === "object" && value["usage"] !== null ? value["usage"] as Record<string, unknown> : {};
749
- const amount = (name: string): number => typeof usage[name] === "number" && Number.isFinite(usage[name]) ? usage[name] as number : 0;
750
- const cost = typeof usage["cost"] === "object" && usage["cost"] !== null && typeof (usage["cost"] as Record<string, unknown>)["total"] === "number"
751
- ? (usage["cost"] as Record<string, number>)["total"] ?? 0 : 0;
752
- const stopReason = ["stop", "length", "toolUse", "error", "aborted"].includes(String(value["stopReason"]))
753
- ? value["stopReason"] as ModelRunObservation["stopReason"] : "unknown";
754
- const completedAt = Math.max(Date.now(), active.firstTokenAt ?? active.startedAt, active.startedAt);
755
- lastCompletedLocalRun = {
756
- runId: active.runId,
757
- provider: value["provider"],
758
- model: value["model"],
759
- thinking: pi.getThinkingLevel(),
760
- ...classifyTaskFromTools(active.toolNames),
761
- startedAt: active.startedAt,
762
- firstTokenAt: active.firstTokenAt,
763
- completedAt,
764
- inputTokens: amount("input"),
765
- outputTokens: amount("output"),
766
- cacheReadTokens: amount("cacheRead"),
767
- cacheWriteTokens: amount("cacheWrite"),
768
- costUsd: Number.isFinite(cost) && cost >= 0 ? cost : 0,
769
- providerResponses: Math.max(1, active.providerResponses),
770
- toolCalls: active.toolCalls,
771
- toolFailures: active.toolFailures,
772
- stopReason,
773
- explicitOutcome: "unknown",
774
- };
775
- await recordMetrics(client, modelRunMetrics(lastCompletedLocalRun)).catch(() => undefined);
776
- });
777
-
778
- pi.on("message_end", async (event, _ctx) => {
779
- if (event.message.role === "assistant" && event.message.provider === "openai-codex") {
780
- if (event.message.stopReason === "error") {
781
- const failure = classifyCodexFailure(event.message.errorMessage, lastCodexResponse);
782
- if (codexRecovery.isCodexRecoveryEnabled() && failure.transient) recoveryPolicy.observeFailure(failure, recoveryRuntime.now());
783
- else cancelRecovery(true);
784
- } else if (event.message.stopReason !== "aborted") {
785
- cancelRecovery(true);
786
- }
787
- lastCodexResponse = {};
788
- }
789
- if (event.message.role === "assistant" && event.message.provider === "google-vertex") {
790
- if (event.message.stopReason === "error") {
791
- const failure = classifyGoogleVertexFailure(event.message.errorMessage, lastGoogleVertexResponse);
792
- await recordMetrics(client, googleVertexFailureMetrics(failure, Date.now())).catch(() => undefined);
793
- }
794
- lastGoogleVertexResponse = {};
795
- }
796
- if (event.message.role === "assistant" && event.message.provider === "anthropic-vertex") {
797
- if (event.message.stopReason === "error") {
798
- const failure = classifyGoogleVertexFailure(event.message.errorMessage, lastAnthropicVertexResponse);
799
- await recordMetrics(client, googleVertexFailureMetrics(failure, Date.now(), "anthropic-vertex")).catch(() => undefined);
800
- }
801
- lastAnthropicVertexResponse = {};
802
- }
803
- const metrics = assistantUsageMetrics(event.message, Date.now(), focusedTaskId, pi.getThinkingLevel());
804
- if (metrics.length > 0) {
805
- const amount = (name: string): number => metrics.filter((metric) => metric.metric === name && typeof metric.value === "number").reduce((sum, metric) => sum + (metric.value ?? 0), 0);
806
- compactionTelemetry.observeProviderUsage({ input: amount("input-tokens"), output: amount("output-tokens"), cacheRead: amount("cache-read-tokens"), cacheWrite: amount("cache-write-tokens") });
807
- await recordMetrics(client, metrics).catch(() => undefined);
808
- }
809
- if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
810
- });
811
-
812
- pi.on("session_shutdown", async (_event, ctx) => {
813
- finishCompactionUi();
814
- if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
815
- stopPapyrusContext?.();
816
- stopPapyrusTaskFocus?.();
817
- cancelRecovery(true);
818
- lastCodexResponse = {};
819
- activeLocalRun = undefined;
820
- lastCompletedLocalRun = undefined;
821
- ctx.ui.setStatus("jittor", undefined);
822
- ctx.ui.setFooter(undefined);
823
- });
824
- }
825
-
826
- export default function jittorExtension(pi: ExtensionAPI): void {
827
- registerJittorExtension(pi);
828
- }