@monotykamary/pi-better-grok 0.1.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/LICENSE +21 -0
- package/README.md +65 -0
- package/THIRD_PARTY_NOTICES.md +19 -0
- package/index.ts +666 -0
- package/package.json +66 -0
- package/src/config.ts +314 -0
- package/src/fast-controller.ts +82 -0
- package/src/format.ts +66 -0
- package/src/grok-auth.ts +242 -0
- package/src/identity.ts +7 -0
- package/src/paths.ts +17 -0
- package/src/usage-controller.ts +210 -0
- package/src/usage.ts +351 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monotykamary/pi-better-grok",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Improve Grok/xAI in pi with fast mode, subscription usage stats, footer polish, and settings — mirroring pi-better-openai.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"footer",
|
|
7
|
+
"grok",
|
|
8
|
+
"pi",
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"pi-package",
|
|
11
|
+
"supergrok",
|
|
12
|
+
"usage",
|
|
13
|
+
"xai"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/monotykamary/pi-better-grok#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/monotykamary/pi-better-grok/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "monotykamary",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/monotykamary/pi-better-grok.git"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"index.ts",
|
|
27
|
+
"src/",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"THIRD_PARTY_NOTICES.md"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
38
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
39
|
+
"@types/node": "~22.19.0",
|
|
40
|
+
"oxfmt": "^0.47.0",
|
|
41
|
+
"oxlint": "^1.62.0",
|
|
42
|
+
"typescript": "^6.0.3",
|
|
43
|
+
"vitest": "^4.1.5"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
47
|
+
"@earendil-works/pi-tui": "*"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22.19.0"
|
|
51
|
+
},
|
|
52
|
+
"pi": {
|
|
53
|
+
"extensions": [
|
|
54
|
+
"./index.ts"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"check": "pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm test",
|
|
59
|
+
"format": "oxfmt .",
|
|
60
|
+
"format:check": "oxfmt --check .",
|
|
61
|
+
"lint": "oxlint --deny-warnings index.ts src/*.ts tests/*.ts",
|
|
62
|
+
"lint:fix": "oxlint --fix index.ts src/*.ts tests/*.ts",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"typecheck": "tsc --noEmit"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { CONFIG_BASENAME, logPrefix } from "./identity.ts";
|
|
5
|
+
import { piAgentDir } from "./paths.ts";
|
|
6
|
+
|
|
7
|
+
export const FOOTER_MODES = ["replace", "status", "off"] as const;
|
|
8
|
+
export const FAST_EFFORTS = ["low", "medium", "high"] as const;
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_SUPPORTED_MODELS = [
|
|
11
|
+
"xai/grok-4.6",
|
|
12
|
+
"xai/grok-4.5",
|
|
13
|
+
"xai-oauth/grok-4.6",
|
|
14
|
+
"xai-oauth/grok-4.5",
|
|
15
|
+
"grok-build/grok-4.6",
|
|
16
|
+
"grok-build/grok-4.5",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type FooterMode = (typeof FOOTER_MODES)[number];
|
|
20
|
+
export type FastEffort = (typeof FAST_EFFORTS)[number];
|
|
21
|
+
|
|
22
|
+
export type FastConfig = { effort?: FastEffort };
|
|
23
|
+
export type UsageConfig = {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
refreshIntervalMs?: number;
|
|
26
|
+
showOnlyOnSubscriptionModels?: boolean;
|
|
27
|
+
showResetTimes?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type FooterConfig = { mode?: FooterMode };
|
|
30
|
+
|
|
31
|
+
export interface ConfigFile {
|
|
32
|
+
persistState?: boolean;
|
|
33
|
+
active?: boolean;
|
|
34
|
+
desiredActive?: boolean;
|
|
35
|
+
supportedModels?: string[];
|
|
36
|
+
fast?: FastConfig;
|
|
37
|
+
usage?: UsageConfig;
|
|
38
|
+
footer?: FooterConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SupportedModel {
|
|
42
|
+
provider: string;
|
|
43
|
+
id: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ResolvedConfig {
|
|
47
|
+
configPath: string;
|
|
48
|
+
projectConfigPath: string;
|
|
49
|
+
globalConfigPath: string;
|
|
50
|
+
projectConfigExists: boolean;
|
|
51
|
+
globalConfigExists: boolean;
|
|
52
|
+
persistState: boolean;
|
|
53
|
+
active: boolean;
|
|
54
|
+
desiredActive: boolean;
|
|
55
|
+
supportedModels: SupportedModel[];
|
|
56
|
+
fast: Required<FastConfig>;
|
|
57
|
+
usage: Required<UsageConfig>;
|
|
58
|
+
footer: Required<FooterConfig>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_FAST_CONFIG: Required<FastConfig> = { effort: "low" };
|
|
62
|
+
export const DEFAULT_USAGE_CONFIG: Required<UsageConfig> = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
refreshIntervalMs: 60_000,
|
|
65
|
+
showOnlyOnSubscriptionModels: true,
|
|
66
|
+
showResetTimes: true,
|
|
67
|
+
};
|
|
68
|
+
export const DEFAULT_FOOTER_CONFIG: Required<FooterConfig> = { mode: "status" };
|
|
69
|
+
export const DEFAULT_CONFIG: ConfigFile = {
|
|
70
|
+
persistState: true,
|
|
71
|
+
active: false,
|
|
72
|
+
desiredActive: false,
|
|
73
|
+
supportedModels: [...DEFAULT_SUPPORTED_MODELS],
|
|
74
|
+
fast: DEFAULT_FAST_CONFIG,
|
|
75
|
+
usage: DEFAULT_USAGE_CONFIG,
|
|
76
|
+
footer: DEFAULT_FOOTER_CONFIG,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type SettingsOptionSection = "footer" | "usage" | "fast";
|
|
80
|
+
|
|
81
|
+
export type SettingsOptionDescriptor = {
|
|
82
|
+
id: string;
|
|
83
|
+
section: SettingsOptionSection;
|
|
84
|
+
key: string;
|
|
85
|
+
label: string;
|
|
86
|
+
description: string;
|
|
87
|
+
values?: readonly string[];
|
|
88
|
+
parse(rawValue: string): boolean | number | string;
|
|
89
|
+
current(config: ResolvedConfig): string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function asBoolean(value: unknown, fallback: boolean): boolean {
|
|
93
|
+
return typeof value === "boolean" ? value : fallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function asNumberInRange(value: unknown, fallback: number, min: number, max: number): number {
|
|
97
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
98
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function asEnum<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
|
|
102
|
+
return typeof value === "string" && (allowed as readonly string[]).includes(value)
|
|
103
|
+
? (value as T)
|
|
104
|
+
: fallback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseModelKey(key: string): SupportedModel | undefined {
|
|
108
|
+
const separator = key.indexOf("/");
|
|
109
|
+
if (separator <= 0 || separator === key.length - 1) return undefined;
|
|
110
|
+
const provider = key.slice(0, separator).trim();
|
|
111
|
+
const id = key.slice(separator + 1).trim();
|
|
112
|
+
if (!provider || !id) return undefined;
|
|
113
|
+
return { provider, id };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function parseModels(value: unknown): SupportedModel[] {
|
|
117
|
+
const entries = Array.isArray(value) ? value : [...DEFAULT_SUPPORTED_MODELS];
|
|
118
|
+
const models: SupportedModel[] = [];
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (typeof entry !== "string") continue;
|
|
121
|
+
const model = parseModelKey(entry);
|
|
122
|
+
if (model) models.push(model);
|
|
123
|
+
}
|
|
124
|
+
return models;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function normalizeModelKeys(models: SupportedModel[]): string[] {
|
|
128
|
+
return models.map((model) => `${model.provider}/${model.id}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mergedSection<T extends object>(
|
|
132
|
+
fallback: T,
|
|
133
|
+
globalValue: unknown,
|
|
134
|
+
projectValue: unknown,
|
|
135
|
+
): Required<T> {
|
|
136
|
+
const global = globalValue && typeof globalValue === "object" ? globalValue : {};
|
|
137
|
+
const project = projectValue && typeof projectValue === "object" ? projectValue : {};
|
|
138
|
+
return { ...fallback, ...global, ...project } as Required<T>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const FOOTER_SETTING_DESCRIPTORS: SettingsOptionDescriptor[] = [
|
|
142
|
+
{
|
|
143
|
+
id: "footer.mode",
|
|
144
|
+
section: "footer",
|
|
145
|
+
key: "mode",
|
|
146
|
+
label: "Footer mode",
|
|
147
|
+
description: "replace: custom footer. status: status line widget. off: hide.",
|
|
148
|
+
values: FOOTER_MODES,
|
|
149
|
+
parse: (rawValue) => asEnum(rawValue, FOOTER_MODES, DEFAULT_FOOTER_CONFIG.mode),
|
|
150
|
+
current: (config) => config.footer.mode,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const USAGE_SETTING_DESCRIPTORS: SettingsOptionDescriptor[] = [
|
|
155
|
+
{
|
|
156
|
+
id: "usage.enabled",
|
|
157
|
+
section: "usage",
|
|
158
|
+
key: "enabled",
|
|
159
|
+
label: "Usage display",
|
|
160
|
+
description: "Show Grok subscription usage in the footer.",
|
|
161
|
+
values: ["true", "false"],
|
|
162
|
+
parse: (rawValue) => rawValue === "true",
|
|
163
|
+
current: (config) => String(config.usage.enabled),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "usage.refreshIntervalMs",
|
|
167
|
+
section: "usage",
|
|
168
|
+
key: "refreshIntervalMs",
|
|
169
|
+
label: "Usage refresh interval",
|
|
170
|
+
description: "How often to poll Grok billing (milliseconds).",
|
|
171
|
+
values: ["30000", "60000", "120000", "300000"],
|
|
172
|
+
parse: (rawValue) =>
|
|
173
|
+
asNumberInRange(Number(rawValue), DEFAULT_USAGE_CONFIG.refreshIntervalMs, 5_000, 3_600_000),
|
|
174
|
+
current: (config) => String(config.usage.refreshIntervalMs),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "usage.showOnlyOnSubscriptionModels",
|
|
178
|
+
section: "usage",
|
|
179
|
+
key: "showOnlyOnSubscriptionModels",
|
|
180
|
+
label: "Only on subscription models",
|
|
181
|
+
description: "Hide usage unless the current model uses xAI OAuth.",
|
|
182
|
+
values: ["true", "false"],
|
|
183
|
+
parse: (rawValue) => rawValue === "true",
|
|
184
|
+
current: (config) => String(config.usage.showOnlyOnSubscriptionModels),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "usage.showResetTimes",
|
|
188
|
+
section: "usage",
|
|
189
|
+
key: "showResetTimes",
|
|
190
|
+
label: "Show reset times",
|
|
191
|
+
description: "Append the billing period reset countdown to the usage line.",
|
|
192
|
+
values: ["true", "false"],
|
|
193
|
+
parse: (rawValue) => rawValue === "true",
|
|
194
|
+
current: (config) => String(config.usage.showResetTimes),
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
export const FAST_SETTING_DESCRIPTORS: SettingsOptionDescriptor[] = [
|
|
199
|
+
{
|
|
200
|
+
id: "fast.effort",
|
|
201
|
+
section: "fast",
|
|
202
|
+
key: "effort",
|
|
203
|
+
label: "Fast effort",
|
|
204
|
+
description: "reasoning_effort injected while fast mode is enabled.",
|
|
205
|
+
values: FAST_EFFORTS,
|
|
206
|
+
parse: (rawValue) => asEnum(rawValue, FAST_EFFORTS, DEFAULT_FAST_CONFIG.effort),
|
|
207
|
+
current: (config) => config.fast.effort,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
export const SETTINGS_OPTION_DESCRIPTORS: SettingsOptionDescriptor[] = [
|
|
212
|
+
...FOOTER_SETTING_DESCRIPTORS,
|
|
213
|
+
...USAGE_SETTING_DESCRIPTORS,
|
|
214
|
+
...FAST_SETTING_DESCRIPTORS,
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
export const SETTINGS_OPTION_BY_ID = new Map(
|
|
218
|
+
SETTINGS_OPTION_DESCRIPTORS.map((descriptor) => [descriptor.id, descriptor]),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
export type SettingPatchContext = {
|
|
222
|
+
persistState?: boolean;
|
|
223
|
+
active?: boolean;
|
|
224
|
+
desiredActive?: boolean;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export function applySettingToRawConfig(
|
|
228
|
+
current: Record<string, unknown>,
|
|
229
|
+
id: string,
|
|
230
|
+
rawValue: string,
|
|
231
|
+
context: SettingPatchContext = {},
|
|
232
|
+
): Record<string, unknown> {
|
|
233
|
+
const next: Record<string, unknown> = { ...current };
|
|
234
|
+
if (id === "fast.enabled") {
|
|
235
|
+
const bool = rawValue === "true";
|
|
236
|
+
if (context.persistState) {
|
|
237
|
+
next.active = context.active ?? bool;
|
|
238
|
+
next.desiredActive = context.desiredActive ?? bool;
|
|
239
|
+
}
|
|
240
|
+
return next;
|
|
241
|
+
}
|
|
242
|
+
const descriptor = SETTINGS_OPTION_BY_ID.get(id);
|
|
243
|
+
if (!descriptor) return next;
|
|
244
|
+
const parsedValue = descriptor.parse(rawValue);
|
|
245
|
+
const currentSection = next[descriptor.section];
|
|
246
|
+
const section =
|
|
247
|
+
currentSection && typeof currentSection === "object" && !Array.isArray(currentSection)
|
|
248
|
+
? { ...(currentSection as Record<string, unknown>) }
|
|
249
|
+
: {};
|
|
250
|
+
section[descriptor.key] = parsedValue;
|
|
251
|
+
next[descriptor.section] = section;
|
|
252
|
+
return next;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function configPaths(cwd: string, home = homedir(), env = process.env) {
|
|
256
|
+
return {
|
|
257
|
+
project: join(cwd, ".pi", "extensions", CONFIG_BASENAME),
|
|
258
|
+
global: join(piAgentDir(env, home), "extensions", CONFIG_BASENAME),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function readRawConfig(path: string): Record<string, unknown> {
|
|
263
|
+
if (!existsSync(path)) return {};
|
|
264
|
+
try {
|
|
265
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
266
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
267
|
+
? (parsed as Record<string, unknown>)
|
|
268
|
+
: {};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
271
|
+
console.warn(`${logPrefix()} Failed to read ${path}: ${message}`);
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function writeConfig(path: string, config: ConfigFile | Record<string, unknown>): void {
|
|
277
|
+
try {
|
|
278
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
279
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
console.warn(`${logPrefix()} Failed to write ${path}: ${message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function firstDefined(
|
|
287
|
+
globalRaw: Record<string, unknown>,
|
|
288
|
+
projectRaw: Record<string, unknown>,
|
|
289
|
+
key: string,
|
|
290
|
+
): unknown {
|
|
291
|
+
if (globalRaw[key] !== undefined) return globalRaw[key];
|
|
292
|
+
return projectRaw[key];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function resolveConfig(cwd: string, home = homedir(), env = process.env): ResolvedConfig {
|
|
296
|
+
const paths = configPaths(cwd, home, env);
|
|
297
|
+
const projectRaw = readRawConfig(paths.project);
|
|
298
|
+
const globalRaw = readRawConfig(paths.global);
|
|
299
|
+
const projectHasContent = Object.keys(projectRaw).length > 0;
|
|
300
|
+
return {
|
|
301
|
+
configPath: projectHasContent ? paths.project : paths.global,
|
|
302
|
+
projectConfigPath: paths.project,
|
|
303
|
+
globalConfigPath: paths.global,
|
|
304
|
+
projectConfigExists: existsSync(paths.project),
|
|
305
|
+
globalConfigExists: existsSync(paths.global),
|
|
306
|
+
persistState: asBoolean(firstDefined(globalRaw, projectRaw, "persistState"), true),
|
|
307
|
+
active: asBoolean(firstDefined(globalRaw, projectRaw, "active"), false),
|
|
308
|
+
desiredActive: asBoolean(firstDefined(globalRaw, projectRaw, "desiredActive"), false),
|
|
309
|
+
supportedModels: parseModels(firstDefined(globalRaw, projectRaw, "supportedModels")),
|
|
310
|
+
fast: mergedSection(DEFAULT_FAST_CONFIG, globalRaw.fast, projectRaw.fast),
|
|
311
|
+
usage: mergedSection(DEFAULT_USAGE_CONFIG, globalRaw.usage, projectRaw.usage),
|
|
312
|
+
footer: mergedSection(DEFAULT_FOOTER_CONFIG, globalRaw.footer, projectRaw.footer),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { ResolvedConfig, SupportedModel } from "./config.ts";
|
|
3
|
+
|
|
4
|
+
export function currentModelKey(ctx: Pick<ExtensionContext, "model">): string | undefined {
|
|
5
|
+
const model = ctx.model;
|
|
6
|
+
if (!model?.provider || !model.id) return undefined;
|
|
7
|
+
return `${model.provider}/${model.id}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function modelList(models: SupportedModel[]): string {
|
|
11
|
+
return models.map((model) => `${model.provider}/${model.id}`).join(", ");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function supportsFast(
|
|
15
|
+
ctx: Pick<ExtensionContext, "model">,
|
|
16
|
+
supportedModels: SupportedModel[],
|
|
17
|
+
): boolean {
|
|
18
|
+
const key = currentModelKey(ctx);
|
|
19
|
+
return (
|
|
20
|
+
key !== undefined && supportedModels.some((model) => `${model.provider}/${model.id}` === key)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FastController {
|
|
25
|
+
active = false;
|
|
26
|
+
desiredActive = false;
|
|
27
|
+
|
|
28
|
+
setDesired(ctx: Pick<ExtensionContext, "model">, config: ResolvedConfig, next: boolean): void {
|
|
29
|
+
this.desiredActive = next;
|
|
30
|
+
this.applyDesiredState(ctx, config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
applyDesiredState(ctx: Pick<ExtensionContext, "model">, config: ResolvedConfig): void {
|
|
34
|
+
this.active = this.desiredActive && supportsFast(ctx, config.supportedModels);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
initializeForSession(
|
|
38
|
+
ctx: Pick<ExtensionContext, "model">,
|
|
39
|
+
config: ResolvedConfig,
|
|
40
|
+
flagEnabled: boolean,
|
|
41
|
+
): void {
|
|
42
|
+
this.desiredActive = flagEnabled || (config.persistState ? config.desiredActive : false);
|
|
43
|
+
this.applyDesiredState(ctx, config);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
injectProviderPayload(
|
|
47
|
+
event: { payload?: unknown },
|
|
48
|
+
ctx: Pick<ExtensionContext, "model">,
|
|
49
|
+
config: ResolvedConfig,
|
|
50
|
+
): Record<string, unknown> | undefined {
|
|
51
|
+
if (!this.active || !supportsFast(ctx, config.supportedModels)) return undefined;
|
|
52
|
+
if (!event?.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return { ...(event.payload as Record<string, unknown>), reasoning_effort: config.fast.effort };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
statusSegment(ctx: Pick<ExtensionContext, "model">, config: ResolvedConfig): string | undefined {
|
|
59
|
+
return this.active && supportsFast(ctx, config.supportedModels) ? "fast" : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stateText(config: ResolvedConfig): string {
|
|
63
|
+
return `Fast mode enabled (reasoning_effort=${config.fast.effort})`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
unsupportedRequestMessage(ctx: Pick<ExtensionContext, "model">, config: ResolvedConfig): string {
|
|
67
|
+
return `Fast mode unavailable for ${currentModelKey(ctx) ?? "current model"}. Supported: ${modelList(config.supportedModels)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
inactiveForModelMessage(ctx: Pick<ExtensionContext, "model">): string {
|
|
71
|
+
return `Fast mode inactive for ${currentModelKey(ctx) ?? "current model"}.`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
debugLines(ctx: Pick<ExtensionContext, "model">, config: ResolvedConfig): string[] {
|
|
75
|
+
return [
|
|
76
|
+
`Fast desired: ${this.desiredActive}`,
|
|
77
|
+
`Fast active: ${this.active}`,
|
|
78
|
+
`Fast eligible: ${supportsFast(ctx, config.supportedModels)}`,
|
|
79
|
+
`Fast effort: ${config.fast.effort}`,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
truncateToWidth as truncateTerminalText,
|
|
3
|
+
visibleWidth as terminalVisibleWidth,
|
|
4
|
+
} from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
6
|
+
const ANSI_ESCAPE_PATTERN = String.raw`\u001B\[[0-?]*[ -/]*[@-~]`;
|
|
7
|
+
const ANSI_ESCAPE_REGEXP = new RegExp(ANSI_ESCAPE_PATTERN, "g");
|
|
8
|
+
const DIAGNOSTIC_MAX_LENGTH = 500;
|
|
9
|
+
|
|
10
|
+
function replaceControlCharacters(value: string): string {
|
|
11
|
+
let result = "";
|
|
12
|
+
for (const char of value) {
|
|
13
|
+
const code = char.charCodeAt(0);
|
|
14
|
+
result += code <= 31 || (code >= 127 && code <= 159) ? " " : char;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function stripAnsi(value: string): string {
|
|
20
|
+
return value.replace(ANSI_ESCAPE_REGEXP, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function visibleWidth(value: string): number {
|
|
24
|
+
return terminalVisibleWidth(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function truncateToWidth(value: string, width: number, ellipsis = "..."): string {
|
|
28
|
+
return truncateTerminalText(value, width, ellipsis);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatTokens(count: number): string {
|
|
32
|
+
if (count < 1000) return count.toString();
|
|
33
|
+
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
34
|
+
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
35
|
+
if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
|
|
36
|
+
return `${Math.round(count / 1000000)}M`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function sanitizeStatusText(text: string): string {
|
|
40
|
+
return text.replace(/[ \r\n\t]+/g, " ").trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function maskIdentifier(value: string | undefined): string | undefined {
|
|
44
|
+
const trimmed = value?.trim();
|
|
45
|
+
if (!trimmed) return undefined;
|
|
46
|
+
if (trimmed.length <= 8) return "found";
|
|
47
|
+
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function sanitizeDiagnosticError(
|
|
51
|
+
message: string,
|
|
52
|
+
maxLength = DIAGNOSTIC_MAX_LENGTH,
|
|
53
|
+
): string {
|
|
54
|
+
const redactedSecrets = stripAnsi(message)
|
|
55
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
|
|
56
|
+
.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "sk-[REDACTED]")
|
|
57
|
+
.replace(
|
|
58
|
+
/(["']?(?:access|access_token|token|api[_-]?key|authorization|accountId|account_id|userId|user_id|refresh)["']?\s*[:=]\s*["']?)([^"',\s}\]]+)/gi,
|
|
59
|
+
"$1[REDACTED]",
|
|
60
|
+
)
|
|
61
|
+
.replace(/\[REDACTED\](?:\])+/g, "[REDACTED]");
|
|
62
|
+
const redacted = replaceControlCharacters(redactedSecrets).replace(/ +/g, " ").trim();
|
|
63
|
+
const sanitized = redacted || "Unknown error.";
|
|
64
|
+
if (sanitized.length <= maxLength) return sanitized;
|
|
65
|
+
return `${sanitized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
66
|
+
}
|