@monotykamary/pi-better-grok 0.3.3 → 0.3.5
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 +2 -2
- package/index.ts +59 -0
- package/package.json +1 -1
- package/src/config.ts +3 -0
- package/src/xai-models.ts +261 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-better-grok
|
|
2
2
|
|
|
3
|
-
Better Grok/xAI for [pi](https://pi.dev) — mirrors the [pi-better-openai](https://github.com/monotykamary/pi-better-openai) UX for SuperGrok subscribers: fast mode, subscription usage in the footer, footer polish, and a settings picker.
|
|
3
|
+
Better Grok/xAI for [pi](https://pi.dev) — mirrors the [pi-better-openai](https://github.com/monotykamary/pi-better-openai) UX for SuperGrok subscribers: fast mode, subscription usage in the footer, footer polish, and a settings picker. Until pi-core ships it, Grok 4.7 is layered onto the builtin `xai` provider via `~/.pi/agent/models.json` so session restore and scoped-model matching can see it at startup.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -51,7 +51,7 @@ JSON config at `~/.pi/agent/extensions/pi-better-grok.json` (global) or `<projec
|
|
|
51
51
|
```json
|
|
52
52
|
{
|
|
53
53
|
"persistState": true,
|
|
54
|
-
"supportedModels": ["xai/grok-4.6", "xai/grok-4.5"],
|
|
54
|
+
"supportedModels": ["xai/grok-4.7", "xai/grok-4.6", "xai/grok-4.5"],
|
|
55
55
|
"fast": { "effort": "low" },
|
|
56
56
|
"usage": {
|
|
57
57
|
"enabled": true,
|
package/index.ts
CHANGED
|
@@ -73,6 +73,14 @@ import {
|
|
|
73
73
|
} from "./src/grok-auth.ts";
|
|
74
74
|
import { currentModelKey, FastController, modelList, supportsFast } from "./src/fast-controller.ts";
|
|
75
75
|
import { isGrokSubscriptionModel, UsageController } from "./src/usage-controller.ts";
|
|
76
|
+
import { piAgentDir } from "./src/paths.ts";
|
|
77
|
+
import {
|
|
78
|
+
GROK_47_ID,
|
|
79
|
+
ensureGrok47InModelsJsonFile,
|
|
80
|
+
lastSessionModel,
|
|
81
|
+
registerGrok47OnProviders,
|
|
82
|
+
shouldRestoreGrok47,
|
|
83
|
+
} from "./src/xai-models.ts";
|
|
76
84
|
import { sep } from "node:path";
|
|
77
85
|
|
|
78
86
|
// pi-core's getSettingsListTheme pulls the host module graph into this
|
|
@@ -175,6 +183,17 @@ export function abbreviateHomePath(
|
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
export default function betterGrok(pi: ExtensionAPI): void {
|
|
186
|
+
// models.json is the only catalog layer that exists when pi restores the
|
|
187
|
+
// last model, before session_start. registerProvider is too late and replaces
|
|
188
|
+
// the xAI list, which drops grok-4.7 back out of scoped-model matching.
|
|
189
|
+
if (!process.env.VITEST) {
|
|
190
|
+
try {
|
|
191
|
+
ensureGrok47InModelsJsonFile(piAgentDir());
|
|
192
|
+
} catch {
|
|
193
|
+
// Missing home dir / unreadable models.json must not block the extension.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
const fetchUsageSnapshot = async (ctx: ExtensionContext): Promise<UsageSnapshot> => {
|
|
179
198
|
const credential = await resolveGrokCredential(ctx);
|
|
180
199
|
if (!credential) {
|
|
@@ -731,7 +750,47 @@ export default function betterGrok(pi: ExtensionAPI): void {
|
|
|
731
750
|
setStatusWidget(ctx, statusWidgetParts(fast, usage));
|
|
732
751
|
}
|
|
733
752
|
|
|
753
|
+
const ensureGrok47 = async (ctx: ExtensionContext): Promise<void> => {
|
|
754
|
+
if (!process.env.VITEST) {
|
|
755
|
+
try {
|
|
756
|
+
ensureGrok47InModelsJsonFile(piAgentDir());
|
|
757
|
+
} catch {
|
|
758
|
+
// Keep going; in-memory fallback below may still register the model.
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
await ctx.modelRegistry?.refresh?.({ allowNetwork: false });
|
|
763
|
+
} catch {
|
|
764
|
+
// Offline refresh failures should not block session startup.
|
|
765
|
+
}
|
|
766
|
+
const registry = ctx.modelRegistry;
|
|
767
|
+
if (typeof registry?.find === "function" && !registry.find("xai", GROK_47_ID)) {
|
|
768
|
+
const getAll = registry.getAll;
|
|
769
|
+
if (typeof pi.registerProvider === "function" && typeof getAll === "function") {
|
|
770
|
+
try {
|
|
771
|
+
registerGrok47OnProviders(
|
|
772
|
+
(name, config) => pi.registerProvider(name, config),
|
|
773
|
+
getAll.call(registry),
|
|
774
|
+
);
|
|
775
|
+
} catch {
|
|
776
|
+
// Catalog registration must not break session startup.
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const branch =
|
|
781
|
+
typeof ctx.sessionManager.getBranch === "function"
|
|
782
|
+
? ctx.sessionManager.getBranch()
|
|
783
|
+
: typeof ctx.sessionManager.getEntries === "function"
|
|
784
|
+
? ctx.sessionManager.getEntries()
|
|
785
|
+
: [];
|
|
786
|
+
const saved = lastSessionModel(branch);
|
|
787
|
+
if (!shouldRestoreGrok47(ctx.model, saved) || !saved) return;
|
|
788
|
+
const restored = registry?.find?.(saved.provider, saved.modelId);
|
|
789
|
+
if (restored) await pi.setModel(restored);
|
|
790
|
+
};
|
|
791
|
+
|
|
734
792
|
pi.on("session_start", (_event, ctx) => {
|
|
793
|
+
void ensureGrok47(ctx);
|
|
735
794
|
invalidateContextUsage();
|
|
736
795
|
invalidateSessionName();
|
|
737
796
|
multiproviderRefreshCtx = ctx;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-better-grok",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Improve Grok/xAI in pi with fast mode, subscription usage stats, banked reset redemption, multiprovider pools, footer polish, and settings — mirroring pi-better-openai.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"footer",
|
package/src/config.ts
CHANGED
|
@@ -8,10 +8,13 @@ export const FOOTER_MODES = ["replace", "status", "off"] as const;
|
|
|
8
8
|
export const FAST_EFFORTS = ["low", "medium", "high"] as const;
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_SUPPORTED_MODELS = [
|
|
11
|
+
"xai/grok-4.7",
|
|
11
12
|
"xai/grok-4.6",
|
|
12
13
|
"xai/grok-4.5",
|
|
14
|
+
"xai-oauth/grok-4.7",
|
|
13
15
|
"xai-oauth/grok-4.6",
|
|
14
16
|
"xai-oauth/grok-4.5",
|
|
17
|
+
"grok-build/grok-4.7",
|
|
15
18
|
"grok-build/grok-4.6",
|
|
16
19
|
"grok-build/grok-4.5",
|
|
17
20
|
] as const;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
export const GROK_47_ID = "grok-4.7";
|
|
6
|
+
export const GROK_47_NAME = "Grok 4.7";
|
|
7
|
+
|
|
8
|
+
/** Providers that share Grok model ids and should pick up 4.7 until pi-core ships it. */
|
|
9
|
+
export const GROK_CUSTOM_MODEL_PROVIDERS = ["xai", "xai-oauth", "xai-auth", "grok-build"] as const;
|
|
10
|
+
|
|
11
|
+
const GROK_47_LONG_CONTEXT_TIER = {
|
|
12
|
+
inputTokensAbove: 200_000,
|
|
13
|
+
input: 4,
|
|
14
|
+
output: 12,
|
|
15
|
+
cacheRead: 1,
|
|
16
|
+
cacheWrite: 0,
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
const GROK_47_COST = {
|
|
20
|
+
input: 2,
|
|
21
|
+
output: 6,
|
|
22
|
+
cacheRead: 0.5,
|
|
23
|
+
cacheWrite: 0,
|
|
24
|
+
tiers: [{ ...GROK_47_LONG_CONTEXT_TIER }],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const GROK_47_THINKING_LEVEL_MAP = {
|
|
28
|
+
off: null,
|
|
29
|
+
minimal: null,
|
|
30
|
+
low: "low",
|
|
31
|
+
medium: "medium",
|
|
32
|
+
high: "high",
|
|
33
|
+
xhigh: "xhigh",
|
|
34
|
+
max: null,
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Native xAI Grok 4.7, cloned from pi-core's grok-4.6 catalog plus the public
|
|
39
|
+
* 4.7 card. Used as an in-memory fallback when models.json cannot be updated.
|
|
40
|
+
*/
|
|
41
|
+
export const GROK_47_FALLBACK: ProviderModelConfig = {
|
|
42
|
+
id: GROK_47_ID,
|
|
43
|
+
name: GROK_47_NAME,
|
|
44
|
+
api: "openai-responses",
|
|
45
|
+
baseUrl: "https://api.x.ai/v1",
|
|
46
|
+
reasoning: true,
|
|
47
|
+
thinkingLevelMap: { ...GROK_47_THINKING_LEVEL_MAP },
|
|
48
|
+
input: ["text", "image"],
|
|
49
|
+
cost: { ...GROK_47_COST, tiers: [{ ...GROK_47_LONG_CONTEXT_TIER }] },
|
|
50
|
+
contextWindow: 500_000,
|
|
51
|
+
maxTokens: 500_000,
|
|
52
|
+
compat: { supportsLongCacheRetention: false },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* models.json custom model layered onto builtin `xai`. api/baseUrl are omitted so
|
|
57
|
+
* pi inherits them from grok-4.6 instead of replacing the provider catalog.
|
|
58
|
+
*/
|
|
59
|
+
export const GROK_47_MODELS_JSON_DEFINITION = {
|
|
60
|
+
id: GROK_47_ID,
|
|
61
|
+
name: GROK_47_NAME,
|
|
62
|
+
reasoning: true,
|
|
63
|
+
thinkingLevelMap: { ...GROK_47_THINKING_LEVEL_MAP },
|
|
64
|
+
input: ["text", "image"] as Array<"text" | "image">,
|
|
65
|
+
cost: { ...GROK_47_COST, tiers: [{ ...GROK_47_LONG_CONTEXT_TIER }] },
|
|
66
|
+
contextWindow: 500_000,
|
|
67
|
+
maxTokens: 500_000,
|
|
68
|
+
compat: { supportsLongCacheRetention: false },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Structural catalog slice; avoids a hard pi-ai import from this package. */
|
|
72
|
+
export type CatalogModel = {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
api?: ProviderModelConfig["api"];
|
|
76
|
+
provider: string;
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
reasoning: boolean;
|
|
79
|
+
thinkingLevelMap?: ProviderModelConfig["thinkingLevelMap"];
|
|
80
|
+
input: ProviderModelConfig["input"];
|
|
81
|
+
cost: ProviderModelConfig["cost"];
|
|
82
|
+
promptCache?: ProviderModelConfig["promptCache"];
|
|
83
|
+
contextWindow: number;
|
|
84
|
+
maxTokens: number;
|
|
85
|
+
headers?: Record<string, string>;
|
|
86
|
+
compat?: ProviderModelConfig["compat"];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export function catalogModelToProviderConfig(model: CatalogModel): ProviderModelConfig {
|
|
90
|
+
return {
|
|
91
|
+
id: model.id,
|
|
92
|
+
name: model.name,
|
|
93
|
+
api: model.api,
|
|
94
|
+
baseUrl: model.baseUrl,
|
|
95
|
+
reasoning: model.reasoning,
|
|
96
|
+
thinkingLevelMap: model.thinkingLevelMap,
|
|
97
|
+
input: model.input,
|
|
98
|
+
cost: model.cost,
|
|
99
|
+
promptCache: model.promptCache,
|
|
100
|
+
contextWindow: model.contextWindow,
|
|
101
|
+
maxTokens: model.maxTokens,
|
|
102
|
+
headers: model.headers,
|
|
103
|
+
compat: model.compat,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function grok47FromTemplate(template: CatalogModel | undefined): ProviderModelConfig {
|
|
108
|
+
if (!template) {
|
|
109
|
+
return {
|
|
110
|
+
...GROK_47_FALLBACK,
|
|
111
|
+
cost: { ...GROK_47_FALLBACK.cost, tiers: [{ ...GROK_47_LONG_CONTEXT_TIER }] },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const thinkingLevelMap = {
|
|
115
|
+
...template.thinkingLevelMap,
|
|
116
|
+
low: "low",
|
|
117
|
+
medium: "medium",
|
|
118
|
+
high: "high",
|
|
119
|
+
xhigh: "xhigh",
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
...catalogModelToProviderConfig(template),
|
|
123
|
+
id: GROK_47_ID,
|
|
124
|
+
name: GROK_47_NAME,
|
|
125
|
+
reasoning: true,
|
|
126
|
+
input: ["text", "image"],
|
|
127
|
+
contextWindow: Math.max(template.contextWindow, 500_000),
|
|
128
|
+
maxTokens: Math.max(template.maxTokens, 500_000),
|
|
129
|
+
thinkingLevelMap,
|
|
130
|
+
cost: {
|
|
131
|
+
...template.cost,
|
|
132
|
+
input: 2,
|
|
133
|
+
output: 6,
|
|
134
|
+
cacheRead: template.cost.cacheRead || 0.5,
|
|
135
|
+
cacheWrite: template.cost.cacheWrite ?? 0,
|
|
136
|
+
tiers: template.cost.tiers ?? [{ ...GROK_47_LONG_CONTEXT_TIER }],
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Extension registerProvider models replace that provider's catalog.
|
|
143
|
+
* Return the existing models plus grok-4.7, or undefined when 4.7 is already present
|
|
144
|
+
* or the provider has nothing to layer onto.
|
|
145
|
+
*/
|
|
146
|
+
export function upsertGrok47(existing: readonly CatalogModel[]): ProviderModelConfig[] | undefined {
|
|
147
|
+
if (existing.length === 0) return undefined;
|
|
148
|
+
if (existing.some((model) => model.id === GROK_47_ID)) return undefined;
|
|
149
|
+
const template =
|
|
150
|
+
existing.find((model) => model.id === "grok-4.6") ??
|
|
151
|
+
existing.find((model) => model.id === "grok-4.5") ??
|
|
152
|
+
existing[0];
|
|
153
|
+
return [...existing.map(catalogModelToProviderConfig), grok47FromTemplate(template)];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type Grok47Registration = { provider: string; models: ProviderModelConfig[] };
|
|
157
|
+
|
|
158
|
+
export function grok47Registrations(all: readonly CatalogModel[]): Grok47Registration[] {
|
|
159
|
+
const byProvider = new Map<string, CatalogModel[]>();
|
|
160
|
+
for (const model of all) {
|
|
161
|
+
const list = byProvider.get(model.provider) ?? [];
|
|
162
|
+
list.push(model);
|
|
163
|
+
byProvider.set(model.provider, list);
|
|
164
|
+
}
|
|
165
|
+
const registrations: Grok47Registration[] = [];
|
|
166
|
+
for (const provider of GROK_CUSTOM_MODEL_PROVIDERS) {
|
|
167
|
+
const models = upsertGrok47(byProvider.get(provider) ?? []);
|
|
168
|
+
if (models) registrations.push({ provider, models });
|
|
169
|
+
}
|
|
170
|
+
return registrations;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function registerGrok47OnProviders(
|
|
174
|
+
registerProvider: (name: string, config: { models: ProviderModelConfig[] }) => void,
|
|
175
|
+
all: readonly CatalogModel[],
|
|
176
|
+
): string[] {
|
|
177
|
+
const registered: string[] = [];
|
|
178
|
+
for (const entry of grok47Registrations(all)) {
|
|
179
|
+
registerProvider(entry.provider, { models: entry.models });
|
|
180
|
+
registered.push(entry.provider);
|
|
181
|
+
}
|
|
182
|
+
return registered;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
186
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
187
|
+
? (value as Record<string, unknown>)
|
|
188
|
+
: undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function upsertGrok47InModelsJson(raw: unknown): {
|
|
192
|
+
next: Record<string, unknown>;
|
|
193
|
+
changed: boolean;
|
|
194
|
+
} {
|
|
195
|
+
const root = { ...asObject(raw) };
|
|
196
|
+
const providers = { ...asObject(root.providers) };
|
|
197
|
+
const xai = { ...asObject(providers.xai) };
|
|
198
|
+
const models = Array.isArray(xai.models) ? [...xai.models] : [];
|
|
199
|
+
const alreadyPresent = models.some((entry) => asObject(entry)?.id === GROK_47_ID);
|
|
200
|
+
if (alreadyPresent) {
|
|
201
|
+
return { next: asObject(raw) ?? root, changed: false };
|
|
202
|
+
}
|
|
203
|
+
models.push({
|
|
204
|
+
...GROK_47_MODELS_JSON_DEFINITION,
|
|
205
|
+
thinkingLevelMap: { ...GROK_47_THINKING_LEVEL_MAP },
|
|
206
|
+
input: [...GROK_47_MODELS_JSON_DEFINITION.input],
|
|
207
|
+
cost: { ...GROK_47_COST, tiers: [{ ...GROK_47_LONG_CONTEXT_TIER }] },
|
|
208
|
+
compat: { ...GROK_47_MODELS_JSON_DEFINITION.compat },
|
|
209
|
+
});
|
|
210
|
+
xai.models = models;
|
|
211
|
+
providers.xai = xai;
|
|
212
|
+
root.providers = providers;
|
|
213
|
+
return { next: root, changed: true };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function grok47ModelsJsonPath(agentDir: string): string {
|
|
217
|
+
return join(agentDir, "models.json");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function ensureGrok47InModelsJsonFile(agentDir: string): boolean {
|
|
221
|
+
const path = grok47ModelsJsonPath(agentDir);
|
|
222
|
+
let parsed: unknown = {};
|
|
223
|
+
if (existsSync(path)) {
|
|
224
|
+
try {
|
|
225
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
226
|
+
} catch {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const { next, changed } = upsertGrok47InModelsJson(parsed);
|
|
231
|
+
if (!changed) return false;
|
|
232
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
233
|
+
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export type SessionModelRef = { provider: string; modelId: string };
|
|
238
|
+
|
|
239
|
+
export function lastSessionModel(
|
|
240
|
+
entries: ReadonlyArray<{ type: string; provider?: string; modelId?: string }>,
|
|
241
|
+
): SessionModelRef | undefined {
|
|
242
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
243
|
+
const entry = entries[index];
|
|
244
|
+
if (
|
|
245
|
+
entry?.type === "model_change" &&
|
|
246
|
+
typeof entry.provider === "string" &&
|
|
247
|
+
typeof entry.modelId === "string"
|
|
248
|
+
) {
|
|
249
|
+
return { provider: entry.provider, modelId: entry.modelId };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function shouldRestoreGrok47(
|
|
256
|
+
current: { provider?: string; id?: string } | undefined,
|
|
257
|
+
saved: SessionModelRef | undefined,
|
|
258
|
+
): boolean {
|
|
259
|
+
if (!saved || saved.modelId !== GROK_47_ID) return false;
|
|
260
|
+
return current?.id !== saved.modelId || current?.provider !== saved.provider;
|
|
261
|
+
}
|