@monotykamary/pi-better-grok 0.3.4 → 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 +1 -1
- package/index.ts +54 -11
- package/package.json +1 -1
- package/src/xai-models.ts +117 -18
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. Until pi-core ships it, Grok 4.7 is layered onto the
|
|
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
|
|
package/index.ts
CHANGED
|
@@ -73,7 +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 {
|
|
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";
|
|
77
84
|
import { sep } from "node:path";
|
|
78
85
|
|
|
79
86
|
// pi-core's getSettingsListTheme pulls the host module graph into this
|
|
@@ -176,6 +183,17 @@ export function abbreviateHomePath(
|
|
|
176
183
|
}
|
|
177
184
|
|
|
178
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
|
+
|
|
179
197
|
const fetchUsageSnapshot = async (ctx: ExtensionContext): Promise<UsageSnapshot> => {
|
|
180
198
|
const credential = await resolveGrokCredential(ctx);
|
|
181
199
|
if (!credential) {
|
|
@@ -732,22 +750,47 @@ export default function betterGrok(pi: ExtensionAPI): void {
|
|
|
732
750
|
setStatusWidget(ctx, statusWidgetParts(fast, usage));
|
|
733
751
|
}
|
|
734
752
|
|
|
735
|
-
const ensureGrok47 = (ctx: ExtensionContext): void => {
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
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
|
+
}
|
|
739
761
|
try {
|
|
740
|
-
|
|
741
|
-
(name, config) => pi.registerProvider(name, config),
|
|
742
|
-
getAll.call(ctx.modelRegistry),
|
|
743
|
-
);
|
|
762
|
+
await ctx.modelRegistry?.refresh?.({ allowNetwork: false });
|
|
744
763
|
} catch {
|
|
745
|
-
//
|
|
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
|
+
}
|
|
746
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);
|
|
747
790
|
};
|
|
748
791
|
|
|
749
792
|
pi.on("session_start", (_event, ctx) => {
|
|
750
|
-
ensureGrok47(ctx);
|
|
793
|
+
void ensureGrok47(ctx);
|
|
751
794
|
invalidateContextUsage();
|
|
752
795
|
invalidateSessionName();
|
|
753
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/xai-models.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
1
3
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
2
4
|
|
|
3
5
|
export const GROK_47_ID = "grok-4.7";
|
|
@@ -14,10 +16,27 @@ const GROK_47_LONG_CONTEXT_TIER = {
|
|
|
14
16
|
cacheWrite: 0,
|
|
15
17
|
} as const;
|
|
16
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
|
+
|
|
17
37
|
/**
|
|
18
38
|
* Native xAI Grok 4.7, cloned from pi-core's grok-4.6 catalog plus the public
|
|
19
|
-
* 4.7 card
|
|
20
|
-
* reasoning efforts low/medium/high/xhigh. Used only when a sibling template exists.
|
|
39
|
+
* 4.7 card. Used as an in-memory fallback when models.json cannot be updated.
|
|
21
40
|
*/
|
|
22
41
|
export const GROK_47_FALLBACK: ProviderModelConfig = {
|
|
23
42
|
id: GROK_47_ID,
|
|
@@ -25,23 +44,25 @@ export const GROK_47_FALLBACK: ProviderModelConfig = {
|
|
|
25
44
|
api: "openai-responses",
|
|
26
45
|
baseUrl: "https://api.x.ai/v1",
|
|
27
46
|
reasoning: true,
|
|
28
|
-
thinkingLevelMap: {
|
|
29
|
-
off: null,
|
|
30
|
-
minimal: null,
|
|
31
|
-
low: "low",
|
|
32
|
-
medium: "medium",
|
|
33
|
-
high: "high",
|
|
34
|
-
xhigh: "xhigh",
|
|
35
|
-
max: null,
|
|
36
|
-
},
|
|
47
|
+
thinkingLevelMap: { ...GROK_47_THINKING_LEVEL_MAP },
|
|
37
48
|
input: ["text", "image"],
|
|
38
|
-
cost: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 }] },
|
|
45
66
|
contextWindow: 500_000,
|
|
46
67
|
maxTokens: 500_000,
|
|
47
68
|
compat: { supportsLongCacheRetention: false },
|
|
@@ -160,3 +181,81 @@ export function registerGrok47OnProviders(
|
|
|
160
181
|
}
|
|
161
182
|
return registered;
|
|
162
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
|
+
}
|