@danypops/jittor 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/extension/src/benchmark-tui.ts +4 -0
- package/extension/src/capabilities/codex-recovery.ts +127 -0
- package/extension/src/capabilities/http-headers.ts +5 -0
- package/extension/src/capabilities/local-run-telemetry.ts +104 -0
- package/extension/src/capabilities/provider-response-telemetry.ts +96 -0
- package/extension/src/footer.ts +10 -53
- package/extension/src/index.ts +92 -272
- package/extension/src/session-identity.ts +20 -0
- package/extension/src/tui.ts +20 -11
- package/package.json +1 -1
- package/src/adapters/sqlite-metric-store.ts +11 -2
- package/src/adapters/sqlite-session-identity-store.ts +45 -0
- package/src/cli-commands/benchmarks.ts +140 -0
- package/src/cli-commands/compaction.ts +17 -0
- package/src/cli-commands/context.ts +49 -0
- package/src/cli-commands/metrics.ts +296 -0
- package/src/cli-commands/op.ts +40 -0
- package/src/cli-commands/route-args.ts +15 -0
- package/src/cli-commands/router.ts +207 -0
- package/src/cli-commands/service-daemon.ts +72 -0
- package/src/cli-commands/session.ts +42 -0
- package/src/cli-commands/support.ts +33 -0
- package/src/cli.ts +42 -769
- package/src/constants.ts +7 -0
- package/src/daemon.ts +13 -3
- package/src/db.ts +15 -1
- package/src/operations/benchmark-operations.ts +12 -0
- package/src/operations/context-operations.ts +30 -0
- package/src/operations/metrics-operations.ts +77 -0
- package/src/operations/model-ranking-operations.ts +16 -0
- package/src/operations/router-operations.ts +19 -0
- package/src/operations/session-identity-operations.ts +15 -0
- package/src/operations/session-scope.ts +31 -0
- package/src/operations/types.ts +3 -0
- package/src/ports/metric-store.ts +2 -0
- package/src/ports/router-controller.ts +9 -9
- package/src/ports/session-identity-store.ts +5 -0
- package/src/providers/telemetry-sources.ts +2 -1
- package/src/router.ts +124 -67
- package/src/service.ts +60 -118
- package/src/session-identity-service.ts +55 -0
package/extension/src/index.ts
CHANGED
|
@@ -1,39 +1,33 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
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
3
|
FOOTER_COMPACTION_RENDER_INTERVAL_MS,
|
|
9
4
|
MAX_DYNAMIC_ROUTES,
|
|
10
|
-
MILLISECONDS_PER_MINUTE,
|
|
11
|
-
MILLISECONDS_PER_SECOND,
|
|
12
5
|
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
13
6
|
PAPYRUS_TASK_FOCUS_CHANNEL,
|
|
14
7
|
CONTEXT_EVENT_DEDUP_LIMIT,
|
|
15
8
|
} from "../../src/constants.ts";
|
|
16
|
-
import { CodexRecoveryPolicy, classifyCodexFailure, type CodexFailureKind, type CodexFailureMetadata } from "../../src/domain/codex-recovery.ts";
|
|
17
9
|
import { CompactionTelemetry, papyrusContextMetric, validatePapyrusContextInjection } from "../../src/domain/context-telemetry.ts";
|
|
18
10
|
import { applyTaskFocusEvent, validateTaskFocusEvent } from "../../src/domain/task-focus.ts";
|
|
19
11
|
import type { MetricObservation, StoredMetricObservation } from "../../src/domain/metric.ts";
|
|
20
|
-
import {
|
|
12
|
+
import { TASK_DOMAINS, TASK_TYPES, type ModelTaskDomain, type ModelTaskType } from "../../src/domain/model-observation.ts";
|
|
21
13
|
import type { ModelCandidate } from "../../src/domain/model-ranking.ts";
|
|
22
14
|
import { USAGE_PERIODS, type UsagePeriod } from "../../src/domain/usage.ts";
|
|
23
15
|
import type { PolicyDecision, Route } from "../../src/policy.ts";
|
|
24
16
|
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
17
|
import { showBenchmarkPanel } from "./benchmark-tui.ts";
|
|
29
18
|
import { installIntegratedFooter, type CompactionProgress, type IntegratedFooterState } from "./footer.ts";
|
|
30
19
|
import { callJittor } from "./service-client.ts";
|
|
31
20
|
import { persistentEnforcementControl, type CodexRecoveryControl, type EnforcementControl, type UsageBudgetControl } from "./settings.ts";
|
|
32
21
|
import { showSettingsPanel } from "./settings-tui.ts";
|
|
33
|
-
import { buildFooterBudget, formatFooterStatus, showJittorPanel } from "./tui.ts";
|
|
22
|
+
import { buildFooterBudget, formatFooterStatus, providerBudgetMetricQuery, showJittorPanel } from "./tui.ts";
|
|
23
|
+
import { cacheSessionSecret, forgetSessionSecret, sessionSecretField } from "./session-identity.ts";
|
|
34
24
|
import { showUsagePanel } from "./usage.ts";
|
|
25
|
+
import { CodexRecoveryCapability, SYSTEM_RECOVERY_RUNTIME, type CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
26
|
+
import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
|
|
27
|
+
import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
|
|
35
28
|
|
|
36
29
|
export { formatFooterStatus } from "./tui.ts";
|
|
30
|
+
export type { CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
37
31
|
|
|
38
32
|
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
39
33
|
const RECOVERY_GUIDANCE = "Run /jittor off to disable blocking, or restart the daemon with: systemctl --user restart jittor.service";
|
|
@@ -46,20 +40,6 @@ const daemonClient: JittorExtensionClient = {
|
|
|
46
40
|
call: (operation, input) => callJittor(operation as Parameters<typeof callJittor>[0], input as never),
|
|
47
41
|
};
|
|
48
42
|
|
|
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
43
|
function usageBudgetControl(enforcement: EnforcementControl): UsageBudgetControl {
|
|
64
44
|
const candidate = enforcement as EnforcementControl & Partial<UsageBudgetControl>;
|
|
65
45
|
return typeof candidate.getUsageTokenBudget === "function" && typeof candidate.setUsageTokenBudget === "function"
|
|
@@ -81,31 +61,17 @@ function recoveryControl(enforcement: EnforcementControl): CodexRecoveryControl
|
|
|
81
61
|
: { isCodexRecoveryEnabled: () => false, setCodexRecoveryEnabled() {} };
|
|
82
62
|
}
|
|
83
63
|
|
|
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
64
|
async function recordMetrics(client: JittorExtensionClient, metrics: MetricObservation[]): Promise<void> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
startedAt: number;
|
|
96
|
-
firstTokenAt: number | null;
|
|
97
|
-
providerResponses: number;
|
|
98
|
-
toolNames: string[];
|
|
99
|
-
toolCalls: number;
|
|
100
|
-
toolFailures: number;
|
|
65
|
+
if (metrics.length === 0) return;
|
|
66
|
+
// One atomic transaction rather than a per-metric RPC loop: a later observation in the same
|
|
67
|
+
// event failing validation, or the connection dropping mid-loop, must not leave this event's
|
|
68
|
+
// metrics partially persisted.
|
|
69
|
+
await client.call("metrics.record_batch", { observations: metrics });
|
|
101
70
|
}
|
|
102
71
|
|
|
103
|
-
async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState): Promise<void> {
|
|
104
|
-
const status = await client.call("router.status", {}) as RouterStatus;
|
|
105
|
-
const
|
|
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;
|
|
72
|
+
async function refreshFooter(client: JittorExtensionClient, state: IntegratedFooterState, sessionId: string): Promise<void> {
|
|
73
|
+
const status = await client.call("router.status", { session_id: sessionId }) as RouterStatus;
|
|
74
|
+
const query = providerBudgetMetricQuery(status);
|
|
109
75
|
const metrics = query ? await client.call("metrics.query", query) as StoredMetricObservation[] : [];
|
|
110
76
|
state.providerBudget = buildFooterBudget(status, metrics);
|
|
111
77
|
state.requestRender?.();
|
|
@@ -168,32 +134,41 @@ export function benchmarkCandidatesFromPi(models: PiRouteModel[], thinking: stri
|
|
|
168
134
|
}
|
|
169
135
|
|
|
170
136
|
export function routesFromPi(models: PiRouteModel[], current: PiRouteModel, thinking: string): Route[] {
|
|
171
|
-
const
|
|
172
|
-
.filter((model) => model.provider
|
|
173
|
-
.filter((model, index, rows) => rows.findIndex((candidate) => candidate.id === model.id) === index);
|
|
174
|
-
if (!
|
|
175
|
-
const routes: Route[] = [{ provider: current.provider, model: current.id, thinking }];
|
|
137
|
+
const catalog = models
|
|
138
|
+
.filter((model) => model.provider.length > 0 && model.id.length > 0)
|
|
139
|
+
.filter((model, index, rows) => rows.findIndex((candidate) => candidate.provider === model.provider && candidate.id === model.id) === index);
|
|
140
|
+
if (!catalog.some((model) => model.provider === current.provider && model.id === current.id)) catalog.push(current);
|
|
176
141
|
const currentLevel = THINKING_DESCENDING.indexOf(thinking as typeof THINKING_DESCENDING[number]);
|
|
177
142
|
const lowerLevels = THINKING_DESCENDING.slice(currentLevel >= 0 ? currentLevel + 1 : 0);
|
|
143
|
+
const routes: Route[] = [];
|
|
144
|
+
const add = (route: Route): void => {
|
|
145
|
+
if (routes.length >= MAX_DYNAMIC_ROUTES || routes.some((candidate) => candidate.provider === route.provider && candidate.model === route.model && candidate.thinking === route.thinking)) return;
|
|
146
|
+
routes.push(route);
|
|
147
|
+
};
|
|
148
|
+
add({ provider: current.provider, model: current.id, thinking });
|
|
178
149
|
for (const level of lowerLevels) {
|
|
179
|
-
if (supportsThinking(current, level))
|
|
150
|
+
if (supportsThinking(current, level)) add({ provider: current.provider, model: current.id, thinking: level });
|
|
180
151
|
}
|
|
181
|
-
const alternatives =
|
|
182
|
-
.filter((model) => model.id !== current.id)
|
|
183
|
-
.sort((left, right) =>
|
|
152
|
+
const alternatives = catalog
|
|
153
|
+
.filter((model) => model.provider !== current.provider || model.id !== current.id)
|
|
154
|
+
.sort((left, right) => {
|
|
155
|
+
const providerPriority = Number(left.provider !== current.provider) - Number(right.provider !== current.provider);
|
|
156
|
+
return providerPriority || modelCost(left) - modelCost(right) || left.provider.localeCompare(right.provider) || left.id.localeCompare(right.id);
|
|
157
|
+
});
|
|
184
158
|
for (const model of alternatives) {
|
|
185
159
|
const level = [thinking, ...lowerLevels].find((candidate) => supportsThinking(model, candidate)) ?? "off";
|
|
186
|
-
|
|
187
|
-
if (routes.length >= MAX_DYNAMIC_ROUTES) break;
|
|
160
|
+
add({ provider: model.provider, model: model.id, thinking: level });
|
|
188
161
|
}
|
|
189
162
|
return routes;
|
|
190
163
|
}
|
|
191
164
|
|
|
192
165
|
async function syncAvailableRoutes(pi: ExtensionAPI, client: JittorExtensionClient, ctx: ExtensionContext): Promise<void> {
|
|
193
|
-
|
|
166
|
+
const session_id = ctx.sessionManager.getSessionId();
|
|
167
|
+
const secret = sessionSecretField(session_id);
|
|
168
|
+
if (!ctx.model) { await client.call("router.available_routes", { routes: [], session_id, ...secret }); return; }
|
|
194
169
|
const models = ctx.modelRegistry.getAvailable() as PiRouteModel[];
|
|
195
170
|
const routes = routesFromPi(models, ctx.model as PiRouteModel, pi.getThinkingLevel());
|
|
196
|
-
await client.call("router.available_routes", { routes });
|
|
171
|
+
await client.call("router.available_routes", { routes, session_id, ...secret });
|
|
197
172
|
}
|
|
198
173
|
|
|
199
174
|
async function syncCurrentRoute(
|
|
@@ -204,7 +179,8 @@ async function syncCurrentRoute(
|
|
|
204
179
|
thinking = pi.getThinkingLevel(),
|
|
205
180
|
): Promise<void> {
|
|
206
181
|
if (!model) return;
|
|
207
|
-
|
|
182
|
+
const session_id = ctx.sessionManager.getSessionId();
|
|
183
|
+
await client.call("router.current_route", { provider: model.provider, model: model.id, thinking, session_id, ...sessionSecretField(session_id) });
|
|
208
184
|
}
|
|
209
185
|
|
|
210
186
|
function halt(ctx: ExtensionContext, reason: string): false {
|
|
@@ -225,7 +201,7 @@ async function applyDecision(
|
|
|
225
201
|
if (!decision.route || await applyRoute(pi, ctx, decision.route)) return true;
|
|
226
202
|
if (allowResync) {
|
|
227
203
|
await syncAvailableRoutes(pi, client, ctx);
|
|
228
|
-
return applyDecision(pi, client, ctx, await client.call("router.decide", {}) as PolicyDecision, false);
|
|
204
|
+
return applyDecision(pi, client, ctx, await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision, false);
|
|
229
205
|
}
|
|
230
206
|
return halt(ctx, `Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`);
|
|
231
207
|
}
|
|
@@ -264,9 +240,9 @@ export function registerJittorExtension(
|
|
|
264
240
|
const footerState: IntegratedFooterState = { providerBudget: null };
|
|
265
241
|
const usageBudgets = usageBudgetControl(enforcement);
|
|
266
242
|
let compactionTelemetry = new CompactionTelemetry();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
243
|
+
const localRunTelemetry = new LocalRunTelemetry();
|
|
244
|
+
const providerResponseTelemetry = new ProviderResponseTelemetry();
|
|
245
|
+
const codexRecoveryCapability = new CodexRecoveryCapability(pi, codexRecovery, recoveryRuntime);
|
|
270
246
|
const contextObservations = new Set<string>();
|
|
271
247
|
const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
|
|
272
248
|
try {
|
|
@@ -296,71 +272,9 @@ export function registerJittorExtension(
|
|
|
296
272
|
// Reject malformed or stale cross-extension events without retaining payloads or crashing the extension.
|
|
297
273
|
}
|
|
298
274
|
});
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
};
|
|
275
|
+
const cancelRecovery = (resetPolicy: boolean): void => codexRecoveryCapability.cancel(resetPolicy);
|
|
276
|
+
const recoveryStatusText = (): string => codexRecoveryCapability.statusText();
|
|
277
|
+
const scheduleCodexRecovery = (ctx: ExtensionContext): void => codexRecoveryCapability.scheduleIfIdle(ctx);
|
|
364
278
|
let compactionTimer: ReturnType<typeof setInterval> | undefined;
|
|
365
279
|
const finishCompactionUi = (): void => {
|
|
366
280
|
if (compactionTimer) clearInterval(compactionTimer);
|
|
@@ -406,11 +320,11 @@ export function registerJittorExtension(
|
|
|
406
320
|
await syncCurrentRoute(pi, client, ctx);
|
|
407
321
|
await syncAvailableRoutes(pi, client, ctx);
|
|
408
322
|
await client.call("telemetry.poll", {});
|
|
409
|
-
const readinessDecision = await client.call("router.decide", {}) as PolicyDecision;
|
|
323
|
+
const readinessDecision = await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision;
|
|
410
324
|
if (readinessDecision.action === "halt") throw new Error(readinessDecision.reason);
|
|
411
325
|
enforcement.setEnabled(true);
|
|
412
326
|
showFooter(ctx);
|
|
413
|
-
await refreshFooter(client, footerState);
|
|
327
|
+
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
414
328
|
ctx.ui.notify("Jittor enforcement enabled.", "info");
|
|
415
329
|
} catch (error) {
|
|
416
330
|
enforcement.setEnabled(false);
|
|
@@ -430,7 +344,7 @@ export function registerJittorExtension(
|
|
|
430
344
|
setFooter: async (enabled) => {
|
|
431
345
|
enforcement.setFooterEnabled(enabled);
|
|
432
346
|
showFooter(ctx);
|
|
433
|
-
if (enabled) await refreshFooter(client, footerState).catch(() => undefined);
|
|
347
|
+
if (enabled) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
434
348
|
},
|
|
435
349
|
setRecovery: (enabled) => {
|
|
436
350
|
if (!enabled) cancelRecovery(true);
|
|
@@ -466,12 +380,12 @@ export function registerJittorExtension(
|
|
|
466
380
|
return;
|
|
467
381
|
}
|
|
468
382
|
if (action === "outcome accepted" || action === "outcome rejected") {
|
|
469
|
-
|
|
383
|
+
const explicitOutcome = action.endsWith("accepted") ? "accepted" as const : "rejected" as const;
|
|
384
|
+
const outcomeMetric = localRunTelemetry.explicitOutcomeMetric(explicitOutcome);
|
|
385
|
+
if (!outcomeMetric) {
|
|
470
386
|
ctx.ui.notify("No completed local model run is available for an explicit outcome.", "warning");
|
|
471
387
|
return;
|
|
472
388
|
}
|
|
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
389
|
outcomeMetric.observedAt = Date.now();
|
|
476
390
|
await recordMetrics(client, [outcomeMetric]);
|
|
477
391
|
ctx.ui.notify(`Recorded explicit ${explicitOutcome} outcome for the latest local model run.`, "info");
|
|
@@ -508,7 +422,7 @@ export function registerJittorExtension(
|
|
|
508
422
|
if (action === "footer on" || action === "footer enable") {
|
|
509
423
|
enforcement.setFooterEnabled(true);
|
|
510
424
|
showFooter(ctx);
|
|
511
|
-
await refreshFooter(client, footerState).catch(() => undefined);
|
|
425
|
+
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
512
426
|
ctx.ui.notify("Jittor informational footer enabled; routing enforcement is unchanged.", "info");
|
|
513
427
|
return;
|
|
514
428
|
}
|
|
@@ -577,19 +491,25 @@ export function registerJittorExtension(
|
|
|
577
491
|
focusedTaskId = null;
|
|
578
492
|
finishCompactionUi();
|
|
579
493
|
compactionTelemetry = new CompactionTelemetry();
|
|
580
|
-
|
|
581
|
-
lastCompletedLocalRun = undefined;
|
|
494
|
+
localRunTelemetry.reset();
|
|
582
495
|
cancelRecovery(true);
|
|
583
|
-
|
|
584
|
-
lastGoogleVertexResponse = {};
|
|
585
|
-
lastAnthropicVertexResponse = {};
|
|
496
|
+
providerResponseTelemetry.resetTurn();
|
|
586
497
|
ctx.ui.setStatus("jittor", undefined);
|
|
587
498
|
showFooter(ctx);
|
|
499
|
+
// Registered before any router-mutating call could plausibly happen, closing most of the
|
|
500
|
+
// first-touch registration window; best-effort -- a registration failure leaves this session
|
|
501
|
+
// unarmored (opt-in armor), never blocked.
|
|
502
|
+
try {
|
|
503
|
+
const { secret } = await client.call("session.register", { session_id: currentSessionId });
|
|
504
|
+
cacheSessionSecret(currentSessionId, secret);
|
|
505
|
+
} catch {
|
|
506
|
+
// Unarmored for this session; every router.* call still works exactly as before.
|
|
507
|
+
}
|
|
588
508
|
try {
|
|
589
509
|
await syncCurrentRoute(pi, client, ctx);
|
|
590
510
|
await syncAvailableRoutes(pi, client, ctx);
|
|
591
511
|
await client.call("telemetry.poll", {});
|
|
592
|
-
await refreshFooter(client, footerState);
|
|
512
|
+
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
593
513
|
} catch {
|
|
594
514
|
footerState.providerBudget = null;
|
|
595
515
|
footerState.requestRender?.();
|
|
@@ -623,7 +543,7 @@ export function registerJittorExtension(
|
|
|
623
543
|
try {
|
|
624
544
|
await syncCurrentRoute(pi, client, ctx);
|
|
625
545
|
await syncAvailableRoutes(pi, client, ctx);
|
|
626
|
-
await refreshFooter(client, footerState);
|
|
546
|
+
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
627
547
|
} catch {
|
|
628
548
|
footerState.providerBudget = null;
|
|
629
549
|
footerState.requestRender?.();
|
|
@@ -634,7 +554,7 @@ export function registerJittorExtension(
|
|
|
634
554
|
if (event.source !== "extension") cancelRecovery(true);
|
|
635
555
|
if (event.source === "extension" || !enforcement.isEnabled()) return { action: "continue" as const };
|
|
636
556
|
try {
|
|
637
|
-
const next = await client.call("router.decide", {}) as PolicyDecision;
|
|
557
|
+
const next = await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision;
|
|
638
558
|
if (next.action === "halt") {
|
|
639
559
|
ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
640
560
|
return { action: "handled" as const };
|
|
@@ -648,7 +568,7 @@ export function registerJittorExtension(
|
|
|
648
568
|
|
|
649
569
|
pi.on("model_select", async (event, ctx) => {
|
|
650
570
|
await syncCurrentRoute(pi, client, ctx, event.model).then(() => syncAvailableRoutes(pi, client, ctx)).catch(() => undefined);
|
|
651
|
-
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
|
|
571
|
+
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
652
572
|
});
|
|
653
573
|
|
|
654
574
|
pi.on("thinking_level_select", async (event, ctx) => {
|
|
@@ -658,147 +578,45 @@ export function registerJittorExtension(
|
|
|
658
578
|
pi.on("turn_start", async (event, ctx) => {
|
|
659
579
|
currentSessionId = ctx.sessionManager.getSessionId();
|
|
660
580
|
compactionTelemetry.observeTurn();
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
};
|
|
581
|
+
codexRecoveryCapability.resetTurn();
|
|
582
|
+
providerResponseTelemetry.resetTurn();
|
|
583
|
+
localRunTelemetry.beginTurn(event.timestamp);
|
|
673
584
|
if (!enforcement.isEnabled()) return;
|
|
674
585
|
try {
|
|
675
586
|
await syncCurrentRoute(pi, client, ctx);
|
|
676
587
|
await syncAvailableRoutes(pi, client, ctx);
|
|
677
|
-
await applyDecision(pi, client, ctx, await client.call("router.decide", {}) as PolicyDecision);
|
|
678
|
-
await refreshFooter(client, footerState);
|
|
588
|
+
await applyDecision(pi, client, ctx, await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() }) as PolicyDecision);
|
|
589
|
+
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
679
590
|
} catch {
|
|
680
591
|
halt(ctx, "Jittor could not verify or apply a safe route");
|
|
681
592
|
}
|
|
682
593
|
});
|
|
683
594
|
|
|
684
595
|
pi.on("message_update", async (event) => {
|
|
685
|
-
|
|
686
|
-
if (["text_delta", "thinking_delta", "toolcall_delta"].includes(event.assistantMessageEvent.type)) activeLocalRun.firstTokenAt = Date.now();
|
|
596
|
+
localRunTelemetry.onMessageUpdate(event.assistantMessageEvent.type);
|
|
687
597
|
});
|
|
688
598
|
|
|
689
599
|
pi.on("tool_execution_end", async (event) => {
|
|
690
|
-
|
|
691
|
-
activeLocalRun.toolCalls += 1;
|
|
692
|
-
if (event.isError) activeLocalRun.toolFailures += 1;
|
|
693
|
-
if (activeLocalRun.toolNames.length < 100) activeLocalRun.toolNames.push(event.toolName);
|
|
600
|
+
localRunTelemetry.onToolExecutionEnd(event.toolName, event.isError);
|
|
694
601
|
});
|
|
695
602
|
|
|
696
603
|
pi.on("after_provider_response", async (event, ctx) => {
|
|
697
|
-
|
|
698
|
-
if (ctx.model?.provider === "openai-codex")
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (ctx.
|
|
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);
|
|
604
|
+
localRunTelemetry.onProviderResponse();
|
|
605
|
+
if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
|
|
606
|
+
const notifySchemaDrift = (message: string) => { if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error"); };
|
|
607
|
+
await providerResponseTelemetry.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift);
|
|
608
|
+
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
739
609
|
});
|
|
740
610
|
|
|
741
611
|
pi.on("turn_end", async (event) => {
|
|
742
|
-
const
|
|
743
|
-
|
|
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);
|
|
612
|
+
const metrics = localRunTelemetry.completeTurn(event.message, pi.getThinkingLevel());
|
|
613
|
+
await recordMetrics(client, metrics).catch(() => undefined);
|
|
776
614
|
});
|
|
777
615
|
|
|
778
|
-
pi.on("message_end", async (event,
|
|
779
|
-
if (event.message.role === "assistant"
|
|
780
|
-
if (event.message.
|
|
781
|
-
|
|
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 = {};
|
|
616
|
+
pi.on("message_end", async (event, ctx) => {
|
|
617
|
+
if (event.message.role === "assistant") {
|
|
618
|
+
if (event.message.provider === "openai-codex") codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
|
|
619
|
+
await providerResponseTelemetry.handleMessageEnd(client, event.message.provider, event.message.stopReason, event.message.errorMessage);
|
|
802
620
|
}
|
|
803
621
|
const metrics = assistantUsageMetrics(event.message, Date.now(), focusedTaskId, pi.getThinkingLevel());
|
|
804
622
|
if (metrics.length > 0) {
|
|
@@ -806,7 +624,7 @@ export function registerJittorExtension(
|
|
|
806
624
|
compactionTelemetry.observeProviderUsage({ input: amount("input-tokens"), output: amount("output-tokens"), cacheRead: amount("cache-read-tokens"), cacheWrite: amount("cache-write-tokens") });
|
|
807
625
|
await recordMetrics(client, metrics).catch(() => undefined);
|
|
808
626
|
}
|
|
809
|
-
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState).catch(() => undefined);
|
|
627
|
+
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
810
628
|
});
|
|
811
629
|
|
|
812
630
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -815,9 +633,11 @@ export function registerJittorExtension(
|
|
|
815
633
|
stopPapyrusContext?.();
|
|
816
634
|
stopPapyrusTaskFocus?.();
|
|
817
635
|
cancelRecovery(true);
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
636
|
+
localRunTelemetry.reset();
|
|
637
|
+
const session_id = ctx.sessionManager.getSessionId();
|
|
638
|
+
const secret = sessionSecretField(session_id);
|
|
639
|
+
if (secret.session_secret) await client.call("session.release", { session_id, ...secret }).catch(() => undefined);
|
|
640
|
+
forgetSessionSecret(session_id);
|
|
821
641
|
ctx.ui.setStatus("jittor", undefined);
|
|
822
642
|
ctx.ui.setFooter(undefined);
|
|
823
643
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side cache for this extension's own session_secret, registered with the daemon at
|
|
3
|
+
* session_start and released at session_shutdown (see index.ts). Keyed by sessionId rather
|
|
4
|
+
* than a single "current" variable, matching every router call site's own explicit sessionId.
|
|
5
|
+
*/
|
|
6
|
+
const secretsBySessionId = new Map<string, string>();
|
|
7
|
+
|
|
8
|
+
export function cacheSessionSecret(sessionId: string, secret: string): void {
|
|
9
|
+
secretsBySessionId.set(sessionId, secret);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function forgetSessionSecret(sessionId: string): void {
|
|
13
|
+
secretsBySessionId.delete(sessionId);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Spread into any router-mutating request body alongside session_id -- empty object when no secret is cached for this sessionId (unregistered, or registration hasn't completed yet), matching the daemon's opt-in-armor default. */
|
|
17
|
+
export function sessionSecretField(sessionId: string | undefined): { session_secret?: string } {
|
|
18
|
+
const secret = sessionId ? secretsBySessionId.get(sessionId) : undefined;
|
|
19
|
+
return secret ? { session_secret: secret } : {};
|
|
20
|
+
}
|