@danypops/pi-jittor 0.2.0 → 0.2.1
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/benchmark-tui.ts +27 -12
- package/extension/src/capabilities/codex-recovery.ts +39 -23
- package/extension/src/capabilities/context-hub.ts +1 -5
- package/extension/src/capabilities/local-run-telemetry.ts +14 -10
- package/extension/src/capabilities/provider-response-telemetry.ts +20 -6
- package/extension/src/context-breakdown.ts +60 -29
- package/extension/src/context-report.ts +16 -5
- package/extension/src/context-view.ts +39 -7
- package/extension/src/footer.ts +56 -26
- package/extension/src/index.ts +228 -97
- package/extension/src/service-client.ts +1 -1
- package/extension/src/settings-tui.ts +35 -20
- package/extension/src/settings.ts +13 -10
- package/extension/src/tui.ts +148 -63
- package/extension/src/usage.ts +124 -42
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { USAGE_PERIODS, type UsagePeriod } from "@danypops/jittor";
|
|
1
2
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { USAGE_PERIODS, type UsagePeriod } from "@danypops/jittor";
|
|
4
4
|
import type { CodexRecoveryControl, EnforcementControl, UsageBudgetControl } from "./settings.ts";
|
|
5
5
|
|
|
6
6
|
export interface SettingsSnapshot {
|
|
@@ -24,12 +24,7 @@ export interface SettingsEffects {
|
|
|
24
24
|
setRecovery(enabled: boolean): void | Promise<void>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const SETTINGS_KEYS: SettingsKey[] = [
|
|
28
|
-
"enforcement",
|
|
29
|
-
"footer",
|
|
30
|
-
"recovery",
|
|
31
|
-
...USAGE_PERIODS.map(({ id }) => `budget-${id}` as const),
|
|
32
|
-
];
|
|
27
|
+
const SETTINGS_KEYS: SettingsKey[] = ["enforcement", "footer", "recovery", ...USAGE_PERIODS.map(({ id }) => `budget-${id}` as const)];
|
|
33
28
|
|
|
34
29
|
function state(enabled: boolean, theme: SettingsTheme): string {
|
|
35
30
|
return enabled ? theme.fg("success", "ON") : theme.fg("muted", "OFF");
|
|
@@ -63,18 +58,14 @@ export function settingsSnapshot(
|
|
|
63
58
|
|
|
64
59
|
export function renderSettingsView(snapshot: SettingsSnapshot, selected: number, width: number, theme: SettingsTheme): string[] {
|
|
65
60
|
const safeWidth = Math.max(20, width);
|
|
66
|
-
const lines = [
|
|
67
|
-
theme.bold("Jittor Settings"),
|
|
68
|
-
theme.fg("dim", "Token budgets are user values; provider quotas remain separate."),
|
|
69
|
-
"",
|
|
70
|
-
];
|
|
61
|
+
const lines = [theme.bold("Jittor Settings"), theme.fg("dim", "Token budgets are user values; provider quotas remain separate."), ""];
|
|
71
62
|
for (let index = 0; index < SETTINGS_KEYS.length; index += 1) {
|
|
72
63
|
const selectedRow = index === selected;
|
|
73
64
|
const prefix = selectedRow ? theme.fg("accent", "› ") : " ";
|
|
74
65
|
lines.push(`${prefix}${rowText(SETTINGS_KEYS[index]!, snapshot, theme)}`);
|
|
75
66
|
}
|
|
76
67
|
lines.push("", theme.fg("dim", "↑/↓ select · Enter edit · Esc close"));
|
|
77
|
-
return lines.map((line) => visibleWidth(line) <= safeWidth ? line : truncateToWidth(line, safeWidth, "…"));
|
|
68
|
+
return lines.map((line) => (visibleWidth(line) <= safeWidth ? line : truncateToWidth(line, safeWidth, "…")));
|
|
78
69
|
}
|
|
79
70
|
|
|
80
71
|
function plainTheme(): SettingsTheme {
|
|
@@ -113,7 +104,12 @@ export async function showSettingsPanel(
|
|
|
113
104
|
},
|
|
114
105
|
): Promise<void> {
|
|
115
106
|
if (ctx.mode !== "tui") {
|
|
116
|
-
ctx.ui.notify(
|
|
107
|
+
ctx.ui.notify(
|
|
108
|
+
renderSettingsView(settingsSnapshot(enforcement, recovery, budgets), -1, 100, plainTheme())
|
|
109
|
+
.slice(0, -2)
|
|
110
|
+
.join("\n"),
|
|
111
|
+
"info",
|
|
112
|
+
);
|
|
117
113
|
return;
|
|
118
114
|
}
|
|
119
115
|
for (;;) {
|
|
@@ -122,19 +118,32 @@ export async function showSettingsPanel(
|
|
|
122
118
|
let selected = 0;
|
|
123
119
|
return {
|
|
124
120
|
invalidate() {},
|
|
125
|
-
render(width: number): string[] {
|
|
121
|
+
render(width: number): string[] {
|
|
122
|
+
return renderSettingsView(snapshot, selected, width, theme);
|
|
123
|
+
},
|
|
126
124
|
handleInput(data: string): void {
|
|
127
125
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || data === "q") done({ kind: "close" });
|
|
128
|
-
else if (matchesKey(data, "up")) {
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
else if (matchesKey(data, "up")) {
|
|
127
|
+
selected = (selected - 1 + SETTINGS_KEYS.length) % SETTINGS_KEYS.length;
|
|
128
|
+
tui.requestRender();
|
|
129
|
+
} else if (matchesKey(data, "down")) {
|
|
130
|
+
selected = (selected + 1) % SETTINGS_KEYS.length;
|
|
131
|
+
tui.requestRender();
|
|
132
|
+
} else if (matchesKey(data, "return") || matchesKey(data, "enter") || matchesKey(data, "space"))
|
|
133
|
+
done({ kind: "activate", key: SETTINGS_KEYS[selected]! });
|
|
131
134
|
},
|
|
132
135
|
};
|
|
133
136
|
});
|
|
134
137
|
if (!action || action.kind === "close") return;
|
|
135
138
|
if (action.key === "enforcement") {
|
|
136
139
|
if (enforcement.isEnabled()) {
|
|
137
|
-
if (
|
|
140
|
+
if (
|
|
141
|
+
await ctx.ui.confirm(
|
|
142
|
+
"Disable routing enforcement?",
|
|
143
|
+
"Jittor will remain monitor-only and will no longer block unsafe provider requests.",
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
await effects.setEnforcement(false);
|
|
138
147
|
} else await effects.setEnforcement(true);
|
|
139
148
|
continue;
|
|
140
149
|
}
|
|
@@ -144,7 +153,13 @@ export async function showSettingsPanel(
|
|
|
144
153
|
}
|
|
145
154
|
if (action.key === "recovery") {
|
|
146
155
|
if (!recovery.isCodexRecoveryEnabled()) {
|
|
147
|
-
if (
|
|
156
|
+
if (
|
|
157
|
+
await ctx.ui.confirm(
|
|
158
|
+
"Enable Codex recovery?",
|
|
159
|
+
"Jittor may start bounded hidden retries only after transient Codex failures fully settle.",
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
await effects.setRecovery(true);
|
|
148
163
|
} else await effects.setRecovery(false);
|
|
149
164
|
continue;
|
|
150
165
|
}
|
|
@@ -35,14 +35,16 @@ function defaultSettings(): ExtensionSettings {
|
|
|
35
35
|
function parseUsageTokenBudgets(value: unknown): Partial<Record<UsagePeriod, number>> {
|
|
36
36
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
37
37
|
const record = value as Record<string, unknown>;
|
|
38
|
-
return Object.fromEntries(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
USAGE_PERIODS.flatMap(({ id }) => {
|
|
40
|
+
const tokens = record[id];
|
|
41
|
+
return typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0 ? [[id, tokens]] : [];
|
|
42
|
+
}),
|
|
43
|
+
) as Partial<Record<UsagePeriod, number>>;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function settingsPath(env: Record<string, string | undefined> = process.env): string {
|
|
45
|
-
const config = env
|
|
47
|
+
const config = env.XDG_CONFIG_HOME ?? join(env.HOME ?? ".", ".config");
|
|
46
48
|
return join(config, JITTOR_STATE_DIRECTORY, JITTOR_EXTENSION_SETTINGS_FILENAME);
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -52,10 +54,10 @@ function loadSettings(path: string): ExtensionSettings {
|
|
|
52
54
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return defaultSettings();
|
|
53
55
|
const record = value as Record<string, unknown>;
|
|
54
56
|
return {
|
|
55
|
-
enforcementEnabled: record
|
|
56
|
-
footerEnabled: record
|
|
57
|
-
codexRecoveryEnabled: record
|
|
58
|
-
usageTokenBudgets: parseUsageTokenBudgets(record
|
|
57
|
+
enforcementEnabled: record.enforcementEnabled !== false,
|
|
58
|
+
footerEnabled: record.footerEnabled !== false,
|
|
59
|
+
codexRecoveryEnabled: record.codexRecoveryEnabled === true,
|
|
60
|
+
usageTokenBudgets: parseUsageTokenBudgets(record.usageTokenBudgets),
|
|
59
61
|
};
|
|
60
62
|
} catch {
|
|
61
63
|
return defaultSettings();
|
|
@@ -93,7 +95,8 @@ export function persistentEnforcementControl(env: Record<string, string | undefi
|
|
|
93
95
|
return settings.usageTokenBudgets[period];
|
|
94
96
|
},
|
|
95
97
|
setUsageTokenBudget(period, tokens): void {
|
|
96
|
-
if (tokens !== undefined && (!Number.isFinite(tokens) || tokens <= 0))
|
|
98
|
+
if (tokens !== undefined && (!Number.isFinite(tokens) || tokens <= 0))
|
|
99
|
+
throw new Error("usage token budget must be a positive finite number");
|
|
97
100
|
if (tokens === undefined) delete settings.usageTokenBudgets[period];
|
|
98
101
|
else settings.usageTokenBudgets[period] = tokens;
|
|
99
102
|
persistSettings(path, settings);
|
package/extension/src/tui.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
1
|
import {
|
|
4
2
|
HUMAN_STATUS_MAX_SOURCES,
|
|
5
3
|
HUMAN_TEXT_FIELD_MAX_CHARACTERS,
|
|
@@ -9,6 +7,8 @@ import {
|
|
|
9
7
|
type RouterStatus,
|
|
10
8
|
type StoredMetricObservation,
|
|
11
9
|
} from "@danypops/jittor";
|
|
10
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
12
12
|
import type { ProviderBudget } from "./footer.ts";
|
|
13
13
|
import { sessionSecretField } from "./session-identity.ts";
|
|
14
14
|
|
|
@@ -18,22 +18,34 @@ export interface JittorPanelClient {
|
|
|
18
18
|
|
|
19
19
|
export function providerBudgetMetricQuery(status: RouterStatus): MetricQuery | null {
|
|
20
20
|
switch (status.currentRoute?.provider) {
|
|
21
|
-
case "openai-codex":
|
|
22
|
-
|
|
23
|
-
case "
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
case "openai-codex":
|
|
22
|
+
return { source: "codex-subscription", metric: "used-fraction", order: "desc", limit: 100 };
|
|
23
|
+
case "openrouter":
|
|
24
|
+
return { source: "openrouter", order: "desc", limit: 20 };
|
|
25
|
+
case "anthropic":
|
|
26
|
+
return { source: "anthropic", metric: "used-fraction", order: "desc", limit: 20 };
|
|
27
|
+
case "anthropic-vertex":
|
|
28
|
+
return { source: "anthropic-vertex", metric: "used-fraction", order: "desc", limit: 20 };
|
|
29
|
+
default:
|
|
30
|
+
return null;
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
type PanelAction = "pause" | "resume" | "refresh" | "override" | "clear-override" | "close";
|
|
30
35
|
|
|
31
|
-
function latest(
|
|
36
|
+
function latest(
|
|
37
|
+
rows: StoredMetricObservation[],
|
|
38
|
+
predicate: (row: StoredMetricObservation) => boolean,
|
|
39
|
+
): StoredMetricObservation | undefined {
|
|
32
40
|
return rows.filter(predicate).sort((left, right) => right.observedAt - left.observedAt || right.id - left.id)[0];
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
function sanitizedText(value: string): string {
|
|
36
|
-
return value
|
|
44
|
+
return value
|
|
45
|
+
.replace(/[\r\n\t]/g, " ")
|
|
46
|
+
.replace(/ +/g, " ")
|
|
47
|
+
.trim()
|
|
48
|
+
.slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
function routeText(route: Route): string {
|
|
@@ -45,26 +57,31 @@ function normalizedIdentity(value: unknown): string {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
function longestWindow(rows: StoredMetricObservation[]): StoredMetricObservation | undefined {
|
|
48
|
-
return [...rows].sort(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
return [...rows].sort(
|
|
61
|
+
(left, right) =>
|
|
62
|
+
Number(right.attributes.windowSeconds ?? 0) - Number(left.attributes.windowSeconds ?? 0) ||
|
|
63
|
+
right.observedAt - left.observedAt ||
|
|
64
|
+
right.id - left.id,
|
|
52
65
|
)[0];
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
function codexWindowForModel(rows: StoredMetricObservation[], model: string): StoredMetricObservation | undefined {
|
|
56
|
-
const codexRows = rows.filter(
|
|
69
|
+
const codexRows = rows.filter(
|
|
70
|
+
(row) => row.source === "codex-subscription" && row.metric === "used-fraction" && typeof row.value === "number",
|
|
71
|
+
);
|
|
57
72
|
const modelIdentity = normalizedIdentity(model);
|
|
58
73
|
const matchingAdditional = codexRows.filter((row) => {
|
|
59
|
-
const limitId = normalizedIdentity(row.attributes
|
|
60
|
-
const limitName = normalizedIdentity(row.attributes
|
|
74
|
+
const limitId = normalizedIdentity(row.attributes.limitId);
|
|
75
|
+
const limitName = normalizedIdentity(row.attributes.limitName);
|
|
61
76
|
return limitId !== "codex" && limitName.length > 0 && limitName === modelIdentity;
|
|
62
77
|
});
|
|
63
78
|
if (matchingAdditional.length > 0) return longestWindow(matchingAdditional);
|
|
64
|
-
return longestWindow(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
return longestWindow(
|
|
80
|
+
codexRows.filter((row) => {
|
|
81
|
+
const limitId = normalizedIdentity(row.attributes.limitId);
|
|
82
|
+
return limitId === "codex" || (limitId.length === 0 && row.scope.startsWith("codex:"));
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
function compactWindowName(seconds: number): string {
|
|
@@ -92,20 +109,27 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
92
109
|
if (status.currentRoute.provider === "openai-codex") {
|
|
93
110
|
const codex = codexWindowForModel(metrics, status.currentRoute.model);
|
|
94
111
|
if (!codex || typeof codex.value !== "number") return null;
|
|
95
|
-
const resetsAtSeconds = Number(codex.attributes
|
|
112
|
+
const resetsAtSeconds = Number(codex.attributes.resetsAt);
|
|
96
113
|
return {
|
|
97
114
|
kind: "bounded",
|
|
98
|
-
label: compactWindowName(Number(codex.attributes
|
|
115
|
+
label: compactWindowName(Number(codex.attributes.windowSeconds ?? 0)),
|
|
99
116
|
remainingFraction: 1 - codex.value,
|
|
100
117
|
observedAt: codex.observedAt,
|
|
101
118
|
...(Number.isFinite(resetsAtSeconds) && resetsAtSeconds > 0 ? { resetsAt: resetsAtSeconds * 1_000 } : {}),
|
|
102
119
|
};
|
|
103
120
|
}
|
|
104
121
|
if (status.currentRoute.provider === "anthropic") {
|
|
105
|
-
const anthropic =
|
|
106
|
-
|
|
122
|
+
const anthropic =
|
|
123
|
+
latest(
|
|
124
|
+
metrics,
|
|
125
|
+
(row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number",
|
|
126
|
+
) ??
|
|
127
|
+
latest(
|
|
128
|
+
metrics,
|
|
129
|
+
(row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number",
|
|
130
|
+
);
|
|
107
131
|
if (!anthropic || typeof anthropic.value !== "number") return null;
|
|
108
|
-
const resetsAt = Number(anthropic.attributes
|
|
132
|
+
const resetsAt = Number(anthropic.attributes.resetsAt);
|
|
109
133
|
return {
|
|
110
134
|
kind: "bounded",
|
|
111
135
|
label: anthropic.scope === "tokens" ? "tok" : "req",
|
|
@@ -121,10 +145,19 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
121
145
|
// pool even if the header shape is identical. If nothing was ever observed, this stays null
|
|
122
146
|
// (may still resolve later), not undefined (never possible) -- unlike google-vertex, this
|
|
123
147
|
// provider's transport has not been shown to structurally lack the signal.
|
|
124
|
-
const anthropicVertex =
|
|
125
|
-
|
|
148
|
+
const anthropicVertex =
|
|
149
|
+
latest(
|
|
150
|
+
metrics,
|
|
151
|
+
(row) =>
|
|
152
|
+
row.source === "anthropic-vertex" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number",
|
|
153
|
+
) ??
|
|
154
|
+
latest(
|
|
155
|
+
metrics,
|
|
156
|
+
(row) =>
|
|
157
|
+
row.source === "anthropic-vertex" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number",
|
|
158
|
+
);
|
|
126
159
|
if (!anthropicVertex || typeof anthropicVertex.value !== "number") return null;
|
|
127
|
-
const resetsAt = Number(anthropicVertex.attributes
|
|
160
|
+
const resetsAt = Number(anthropicVertex.attributes.resetsAt);
|
|
128
161
|
return {
|
|
129
162
|
kind: "bounded",
|
|
130
163
|
label: anthropicVertex.scope === "tokens" ? "vtok" : "vreq",
|
|
@@ -135,9 +168,12 @@ export function buildFooterBudget(status: RouterStatus, metrics: StoredMetricObs
|
|
|
135
168
|
}
|
|
136
169
|
if (status.currentRoute.provider === "openrouter") {
|
|
137
170
|
const openRouter = latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number");
|
|
138
|
-
const remaining = latest(
|
|
171
|
+
const remaining = latest(
|
|
172
|
+
metrics,
|
|
173
|
+
(row) => row.source === "openrouter" && row.metric === "remaining-fraction" && typeof row.value === "number",
|
|
174
|
+
);
|
|
139
175
|
if (remaining && typeof remaining.value === "number" && (!openRouter || remaining.observedAt >= openRouter.observedAt)) {
|
|
140
|
-
const reset = typeof remaining.attributes
|
|
176
|
+
const reset = typeof remaining.attributes.reset === "string" ? sanitizedText(remaining.attributes.reset) : undefined;
|
|
141
177
|
return {
|
|
142
178
|
kind: "bounded",
|
|
143
179
|
label: "OR",
|
|
@@ -160,27 +196,42 @@ export function formatFooterStatus(status: RouterStatus, metrics: StoredMetricOb
|
|
|
160
196
|
|
|
161
197
|
function nextAction(action: PolicyAction | undefined): string {
|
|
162
198
|
switch (action) {
|
|
163
|
-
case "continue":
|
|
164
|
-
|
|
165
|
-
case "
|
|
166
|
-
|
|
167
|
-
case "
|
|
168
|
-
|
|
169
|
-
|
|
199
|
+
case "continue":
|
|
200
|
+
return "throttle";
|
|
201
|
+
case "throttle":
|
|
202
|
+
return "lower thinking";
|
|
203
|
+
case "lower-thinking":
|
|
204
|
+
return "switch model";
|
|
205
|
+
case "switch-model":
|
|
206
|
+
return "switch provider";
|
|
207
|
+
case "switch-provider":
|
|
208
|
+
return "halt";
|
|
209
|
+
case "halt":
|
|
210
|
+
return "halted";
|
|
211
|
+
default:
|
|
212
|
+
return "waiting for decision";
|
|
170
213
|
}
|
|
171
214
|
}
|
|
172
215
|
|
|
173
216
|
function burnLine(rows: StoredMetricObservation[], current: StoredMetricObservation, now: number): string {
|
|
174
217
|
const previous = rows
|
|
175
|
-
.filter(
|
|
218
|
+
.filter(
|
|
219
|
+
(row) =>
|
|
220
|
+
row.source === current.source &&
|
|
221
|
+
row.scope === current.scope &&
|
|
222
|
+
row.metric === current.metric &&
|
|
223
|
+
row.id !== current.id &&
|
|
224
|
+
row.observedAt < current.observedAt,
|
|
225
|
+
)
|
|
176
226
|
.sort((left, right) => right.observedAt - left.observedAt)[0];
|
|
177
|
-
const resetsAt = Number(current.attributes
|
|
227
|
+
const resetsAt = Number(current.attributes.resetsAt ?? 0) * 1_000;
|
|
178
228
|
const remainingSeconds = (resetsAt - now) / 1_000;
|
|
179
229
|
const sustainable = typeof current.value === "number" && remainingSeconds > 0 ? (1 - current.value) / remainingSeconds : null;
|
|
180
|
-
const observed =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
230
|
+
const observed =
|
|
231
|
+
previous && typeof previous.value === "number" && typeof current.value === "number" && current.observedAt > previous.observedAt
|
|
232
|
+
? (current.value - previous.value) / ((current.observedAt - previous.observedAt) / 1_000)
|
|
233
|
+
: null;
|
|
234
|
+
const perHour = (rate: number | null) => (rate === null ? "n/a" : `${(rate * 3_600 * 100).toFixed(2)}%/h`);
|
|
184
235
|
return `Burn: observed ${perHour(observed)} · sustainable ${perHour(sustainable)}`;
|
|
185
236
|
}
|
|
186
237
|
|
|
@@ -188,21 +239,34 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
|
|
|
188
239
|
const lines = [status.ready ? "Ready" : "Not ready"];
|
|
189
240
|
const codex = status.currentRoute?.provider === "openai-codex" ? codexWindowForModel(metrics, status.currentRoute.model) : undefined;
|
|
190
241
|
if (codex && typeof codex.value === "number") {
|
|
191
|
-
const seconds = Number(codex.attributes
|
|
242
|
+
const seconds = Number(codex.attributes.windowSeconds ?? 0);
|
|
192
243
|
lines.push(`Codex ${windowName(seconds)}: ${((1 - codex.value) * 100).toFixed(1)}% left`);
|
|
193
244
|
lines.push(burnLine(metrics, codex, now));
|
|
194
245
|
}
|
|
195
|
-
const openRouter =
|
|
196
|
-
|
|
197
|
-
|
|
246
|
+
const openRouter =
|
|
247
|
+
status.currentRoute?.provider === "openrouter"
|
|
248
|
+
? latest(metrics, (row) => row.source === "openrouter" && row.metric === "usage" && typeof row.value === "number")
|
|
249
|
+
: undefined;
|
|
198
250
|
if (openRouter && typeof openRouter.value === "number") lines.push(`OpenRouter spend: $${openRouter.value.toFixed(3)}`);
|
|
199
|
-
const anthropic =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
251
|
+
const anthropic =
|
|
252
|
+
status.currentRoute?.provider === "anthropic"
|
|
253
|
+
? (latest(
|
|
254
|
+
metrics,
|
|
255
|
+
(row) => row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "tokens" && typeof row.value === "number",
|
|
256
|
+
) ??
|
|
257
|
+
latest(
|
|
258
|
+
metrics,
|
|
259
|
+
(row) =>
|
|
260
|
+
row.source === "anthropic" && row.metric === "used-fraction" && row.scope === "requests" && typeof row.value === "number",
|
|
261
|
+
))
|
|
262
|
+
: undefined;
|
|
263
|
+
if (anthropic && typeof anthropic.value === "number")
|
|
264
|
+
lines.push(`Anthropic ${anthropic.scope}: ${((1 - anthropic.value) * 100).toFixed(1)}% left`);
|
|
204
265
|
if (status.currentRoute) lines.push(`Route: ${routeText(status.currentRoute)}`);
|
|
205
|
-
if (status.lastDecision)
|
|
266
|
+
if (status.lastDecision)
|
|
267
|
+
lines.push(
|
|
268
|
+
`Pressure: ${Number.isFinite(status.lastDecision.pressure) ? status.lastDecision.pressure.toFixed(3) : "∞"} · ${status.lastDecision.action}`,
|
|
269
|
+
);
|
|
206
270
|
lines.push(`Next: ${nextAction(status.lastDecision?.action)}`);
|
|
207
271
|
lines.push("Telemetry:");
|
|
208
272
|
const providerSources = status.sources.filter((source) => source.provider === status.currentRoute?.provider);
|
|
@@ -210,21 +274,28 @@ export function buildStatusView(status: RouterStatus, metrics: StoredMetricObser
|
|
|
210
274
|
const freshness = !source.ok ? "failed" : source.observedAt !== undefined && now - source.observedAt > 120_000 ? "stale" : "fresh";
|
|
211
275
|
lines.push(` ${sanitizedText(source.id)}: ${freshness} · ${source.metrics} metrics`);
|
|
212
276
|
}
|
|
213
|
-
if (providerSources.length > HUMAN_STATUS_MAX_SOURCES)
|
|
277
|
+
if (providerSources.length > HUMAN_STATUS_MAX_SOURCES)
|
|
278
|
+
lines.push(` … ${providerSources.length - HUMAN_STATUS_MAX_SOURCES} more telemetry sources omitted`);
|
|
214
279
|
if (status.override) lines.push(`Override: ${routeText(status.override.route)}`);
|
|
215
280
|
if (status.paused) lines.push("Emergency halt is active");
|
|
216
281
|
return lines;
|
|
217
282
|
}
|
|
218
283
|
|
|
219
|
-
async function snapshot(
|
|
220
|
-
|
|
284
|
+
async function snapshot(
|
|
285
|
+
client: JittorPanelClient,
|
|
286
|
+
sessionId: string,
|
|
287
|
+
): Promise<{ status: RouterStatus; metrics: StoredMetricObservation[] }> {
|
|
288
|
+
const status = (await client.call("router.status", { session_id: sessionId })) as RouterStatus;
|
|
221
289
|
const query = providerBudgetMetricQuery(status);
|
|
222
|
-
const metrics = query ? await client.call("metrics.query", query) as StoredMetricObservation[] : [];
|
|
290
|
+
const metrics = query ? ((await client.call("metrics.query", query)) as StoredMetricObservation[]) : [];
|
|
223
291
|
return { status, metrics };
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
async function chooseOverride(ctx: ExtensionCommandContext, routes: Route[]): Promise<Route | undefined> {
|
|
227
|
-
if (routes.length === 0) {
|
|
295
|
+
if (routes.length === 0) {
|
|
296
|
+
ctx.ui.notify("Pi reports no authenticated routes for the current provider.", "warning");
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
228
299
|
const labels = routes.map(routeText);
|
|
229
300
|
const selected = await ctx.ui.select("Override route", labels);
|
|
230
301
|
const index = selected ? labels.indexOf(selected) : -1;
|
|
@@ -265,20 +336,34 @@ export async function showJittorPanel(ctx: ExtensionCommandContext, client: Jitt
|
|
|
265
336
|
},
|
|
266
337
|
}));
|
|
267
338
|
if (!action || action === "close") return;
|
|
268
|
-
if (action === "refresh") {
|
|
339
|
+
if (action === "refresh") {
|
|
340
|
+
await client.call("telemetry.poll", {});
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
269
343
|
if (action === "pause" || action === "resume") {
|
|
270
|
-
if (
|
|
344
|
+
if (
|
|
345
|
+
await ctx.ui.confirm(
|
|
346
|
+
action === "pause" ? "Emergency-halt provider requests?" : "Release emergency halt?",
|
|
347
|
+
"This changes provider-request enforcement. Use /jittor off to disable blocking entirely.",
|
|
348
|
+
)
|
|
349
|
+
) {
|
|
271
350
|
await client.call(action === "pause" ? "router.pause" : "router.resume", { session_id, ...sessionSecretField(session_id) });
|
|
272
351
|
}
|
|
273
352
|
continue;
|
|
274
353
|
}
|
|
275
354
|
if (action === "clear-override") {
|
|
276
|
-
if (await ctx.ui.confirm("Clear route override?", "Policy-controlled routing will resume."))
|
|
355
|
+
if (await ctx.ui.confirm("Clear route override?", "Policy-controlled routing will resume."))
|
|
356
|
+
await client.call("router.clear_override", { session_id, ...sessionSecretField(session_id) });
|
|
277
357
|
continue;
|
|
278
358
|
}
|
|
279
359
|
const route = await chooseOverride(ctx, current.status.availableRoutes);
|
|
280
|
-
if (route && await ctx.ui.confirm("Apply route override?", `${routeText(route)} for one hour`)) {
|
|
281
|
-
await client.call("router.override", {
|
|
360
|
+
if (route && (await ctx.ui.confirm("Apply route override?", `${routeText(route)} for one hour`))) {
|
|
361
|
+
await client.call("router.override", {
|
|
362
|
+
route,
|
|
363
|
+
expiresAt: Date.now() + 60 * 60 * 1_000,
|
|
364
|
+
session_id,
|
|
365
|
+
...sessionSecretField(session_id),
|
|
366
|
+
});
|
|
282
367
|
}
|
|
283
368
|
}
|
|
284
369
|
}
|