@rise-so/pi-grok-build 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Viktor Taranenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # pi-grok-build
2
+
3
+ xAI Grok Build for [pi](https://github.com/earendil-works/pi): OAuth login
4
+ (Grok subscription) plus Grok chat models over the OpenAI Responses API.
5
+ **OAuth only — API keys are deliberately unsupported in this project.**
6
+
7
+ One dependency-free core with two adapters:
8
+
9
+ | Entry point | File | Use it for |
10
+ |---|---|---|
11
+ | `@rise-so/pi-grok-build/extension` | `src/extension.ts` | pi coding-agent extension: `/login` offers "xAI (Grok subscription)", `pi --list-models` shows the models |
12
+ | `@rise-so/pi-grok-build` | `src/provider.ts` | a pi-ai `Provider<"openai-responses">` for programs composing their own `Models` registry (pi-ai / pi-agent core) |
13
+ | `@rise-so/pi-grok-build/core` | `src/xai-grok-build.ts` | the raw OAuth flow (`loginXai`, `refreshXai`) and model metadata, no pi-ai runtime imports |
14
+
15
+ ## Models
16
+
17
+ | Model | Context | Pricing (per Mtok in/out/cache-read) | Notes |
18
+ |---|---|---|---|
19
+ | `grok-build-0.1` | 256k | $1.00 / $2.00 / $0.20 | flagship; aliases `grok-code-fast-1` upstream; pricing live-verified |
20
+ | `grok-4.3` | 1M | $1.25 / $2.50 / $0.20 | reasoning model; pricing live-verified; live pricing doubles past 200k tokens (pi records the base tier) |
21
+ | `grok-composer-2.5-fast` | 200k | $3.00 / $15.00 / $0.50 | responds on `/responses` but is **absent from the live `/models` catalog**; context/output metadata from third-party registries |
22
+
23
+ ### Media models (not wired up)
24
+
25
+ The subscription credential also sees `grok-imagine-image`,
26
+ `grok-imagine-image-quality`, `grok-imagine-video`, and
27
+ `grok-imagine-video-1.5` (per live `GET /v1/models`; run `pnpm smoke:models`).
28
+ They use xAI's `/images/*` and `/videos/*` endpoints — a different protocol
29
+ from `/responses` — so they are deliberately not registered. If wanted later,
30
+ they need a pi-ai images-api adapter, not entries in the chat model list.
31
+
32
+ ## Use as a pi extension
33
+
34
+ For development, load the extension directly from a checkout:
35
+
36
+ ```sh
37
+ pi -e /path/to/pi-grok-build
38
+ ```
39
+
40
+ For a permanent install, copy the project into pi's extensions directory —
41
+ a copy gives pi a stable snapshot that won't shift underneath it as the
42
+ checkout changes:
43
+
44
+ ```sh
45
+ cp -R /path/to/pi-grok-build ~/.pi/agent/extensions/pi-grok-build
46
+ ```
47
+
48
+ Then pick a model:
49
+
50
+ ```sh
51
+ pi --model xai-grok-build/grok-build-0.1
52
+ pi --model xai-grok-build/grok-4.3 -p "one-shot prompt"
53
+ ```
54
+
55
+ No `npm install` is needed for pi imports — pi's loader aliases
56
+ `@earendil-works/pi-*` to its own bundled copies. The extension registers the
57
+ provider id `xai-grok-build`; log in with `/login` (OAuth is the only
58
+ credential — the provider intentionally has no API-key path).
59
+
60
+ ### Install as a pi package
61
+
62
+ This repo is a pi package (`package.json` declares the extension under the
63
+ `pi` key), so `pi install` can manage it — pi records the source in
64
+ `~/.pi/agent/settings.json` and loads the extension on every run:
65
+
66
+ ```sh
67
+ pi install /path/to/pi-grok-build # from a local checkout
68
+ pi install git:github.com/rise-so/pi-grok-build # from a git remote (@tag to pin)
69
+
70
+ pi list # confirm it's installed
71
+ pi remove /path/to/pi-grok-build # uninstall
72
+ ```
73
+
74
+ Add `-l` to `pi install` to record it in the project's `.pi/settings.json`
75
+ instead of your user settings.
76
+
77
+ ## Use as a library provider
78
+
79
+ ```ts
80
+ import { createModels } from "@earendil-works/pi-ai";
81
+ import { xaiGrokBuildResponsesProvider } from "@rise-so/pi-grok-build";
82
+
83
+ const models = createModels({ credentials: myCredentialStore }); // holds the OAuth credential
84
+ models.setProvider(
85
+ xaiGrokBuildResponsesProvider({
86
+ // referrer: "my-app", // authorize-URL referrer param
87
+ }),
88
+ );
89
+
90
+ const model = models.getModel("xai-grok-build", "grok-build-0.1");
91
+ if (!model) throw new Error("model not registered");
92
+ const reply = await models.completeSimple(model, {
93
+ messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
94
+ });
95
+ ```
96
+
97
+ The host owns credential storage: pass a `CredentialStore` holding the OAuth
98
+ credential (obtained via the provider's `auth.oauth.login`), and pi-ai handles
99
+ refresh through the provider's own `refresh`. `live/support.ts` has a working
100
+ file-backed example. Model constants (`GROK_MODELS`, `GROK_BUILD_MODEL`, …)
101
+ are exported from the package root.
102
+
103
+ `@earendil-works/pi-ai` is a peer dependency (pinned to `>=0.80.0 <0.81.0`).
104
+ The package exports built output (`dist/`, `.js` + `.d.ts`), so it installs
105
+ cleanly from npm, a tarball, or a git dependency — Node refuses to type-strip
106
+ raw `.ts` under `node_modules`, so the build step is what makes copy-based
107
+ installs work. `pnpm install` builds `dist/` automatically (`prepare` script),
108
+ as does pnpm when consuming this repo as a git dependency.
109
+
110
+ ## Provider id: `xai-grok-build`, never `xai`
111
+
112
+ Credentials live in pi's shared `~/.pi/agent/auth.json`, keyed by provider id.
113
+ Registering OAuth under `"xai"` would capture pi's built-in xAI provider:
114
+ every built-in Grok model would be billed to the subscription token,
115
+ `registerProvider` would wipe pi's built-in xAI models, and `/login` would
116
+ offer an API-key row that silently overwrites the OAuth credential. A distinct
117
+ id makes all of that structurally impossible.
118
+
119
+ ## Load-bearing constraint (extension path)
120
+
121
+ `src/extension.ts` — and everything it imports at runtime — must never import
122
+ a `@earendil-works/pi-ai` subpath (`api/*`, `providers/*`) and must never
123
+ import `src/provider.ts`. pi's extension loader aliases the bare package
124
+ prefix to its compat bundle, so subpath specifiers resolve to nonexistent
125
+ paths: silent at typecheck, fatal at extension load. `extension.test.ts`
126
+ guards this with an import grep; keep it green.
127
+
128
+ ## Caveats
129
+
130
+ - The OAuth flow borrows the Grok CLI's public client id and fixed loopback
131
+ port 56121 (xAI has no public client registration). xAI can break it at any
132
+ time; a concurrent Grok CLI login collides on the port.
133
+ - OAuth is the only auth path, by policy. There is no `$XAI_API_KEY` handling,
134
+ no API-key login row, and no reading of other programs' credential files.
135
+ - Context windows and pricing for `grok-build-0.1` / `grok-4.3` are confirmed
136
+ by xAI's live `GET /v1/models` (see `pnpm smoke:models`). `maxTokens` output
137
+ caps are registry metadata only. `grok-composer-2.5-fast` is unlisted in the
138
+ live catalog (it serves anyway); its context/output figures come from
139
+ third-party registries and its pricing is maintainer-supplied.
140
+
141
+ ## Development
142
+
143
+ ```sh
144
+ pnpm install # also builds dist/ via the prepare script
145
+ pnpm check # tsc --noEmit + unit tests (offline, always safe)
146
+ pnpm build # emit dist/ (.js + .d.ts) for library consumers
147
+ pnpm test:live # live acceptance against api.x.ai (skips without a credential)
148
+ pnpm smoke:models # print the live model catalog for your credential
149
+ pnpm smoke:extension # end-to-end through a real `pi` binary
150
+ ```
151
+
152
+ The pi extension path (`pi -e`, `pi install`, the extensions directory) needs
153
+ no build: pi's loader executes `src/extension.ts` directly, per the `pi` key
154
+ in `package.json`. Only library consumers go through `dist/`.
155
+
156
+ Unit tests (`src/*.test.ts`) run offline with fetch stubbed; live tests
157
+ (`live/*.test.ts`) hit api.x.ai with the stored pi OAuth credential and are
158
+ never part of `pnpm check`. Acceptance criteria and the full test layering are
159
+ documented in `ACCEPTANCE.md`.
@@ -0,0 +1,2 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export default function xaiGrokBuild(pi: ExtensionAPI): void;
@@ -0,0 +1,63 @@
1
+ // pi extension adapter: registers the Grok Build provider with pi's coding
2
+ // agent so /login offers "xAI (Grok subscription)" and the model lists.
3
+ //
4
+ // LOAD-BEARING CONSTRAINT: this module must never import anything under
5
+ // `@earendil-works/pi-ai/(api|providers)/` — pi's extension loader aliases the
6
+ // bare `@earendil-works/pi-ai` prefix to its own compat bundle, which mangles
7
+ // subpath specifiers into paths that do not exist. That failure typechecks
8
+ // clean and only surfaces at extension load. It must not import provider.ts
9
+ // either (which needs a pi-ai subpath at runtime). extension.test.ts guards
10
+ // this with an import grep.
11
+ import { GROK_MODELS, XAI_BASE_URL, XAI_PROVIDER_ID, loginXai, refreshXai, requireXaiOAuthCredential, } from "./xai-grok-build.js";
12
+ // Identify as pi in the authorize URL.
13
+ const EXTENSION_OPTS = { referrer: "pi" };
14
+ // `usesCallbackServer` lives on OAuthProviderInterface and survives pi's
15
+ // registration spread, but ProviderConfig's inline `oauth` type omits it.
16
+ // Typing the const as Omit<…, "id"> gets it checked and dodges excess-property
17
+ // checking at the use site.
18
+ const oauth = {
19
+ name: "xAI (Grok subscription)",
20
+ usesCallbackServer: true,
21
+ async login(callbacks) {
22
+ return loginXai(toHost(callbacks), EXTENSION_OPTS);
23
+ },
24
+ async refreshToken(credentials) {
25
+ return refreshXai(requireXaiOAuthCredential(credentials));
26
+ },
27
+ getApiKey(credentials) {
28
+ // Sync and string-only, so the provider-level baseUrl below carries what
29
+ // the library adapter's toAuth() returns alongside the key.
30
+ return requireXaiOAuthCredential(credentials).access;
31
+ },
32
+ };
33
+ // Register synchronously inside the factory: pi drains pending registrations
34
+ // before any session exists, which is what makes `pi --list-models` and
35
+ // /login both see the provider.
36
+ export default function xaiGrokBuild(pi) {
37
+ pi.registerProvider(XAI_PROVIDER_ID, {
38
+ name: "xAI Grok Build",
39
+ baseUrl: XAI_BASE_URL,
40
+ // OAuth only, on purpose: no `apiKey` entry means pi never offers an
41
+ // API-key row for this provider and env keys are ignored. The Grok
42
+ // subscription token is the sole credential in this project.
43
+ api: "openai-responses",
44
+ models: GROK_MODELS.map((model) => ({
45
+ id: model.id,
46
+ name: model.name,
47
+ reasoning: model.reasoning,
48
+ input: [...model.input],
49
+ cost: { ...model.cost },
50
+ contextWindow: model.contextWindow,
51
+ maxTokens: model.maxTokens,
52
+ compat: { ...model.compat },
53
+ })),
54
+ oauth,
55
+ });
56
+ }
57
+ function toHost(cb) {
58
+ return {
59
+ authUrl: (url, instructions) => cb.onAuth({ url, instructions }),
60
+ progress: (message) => cb.onProgress?.(message),
61
+ ...(cb.signal !== undefined ? { signal: cb.signal } : {}),
62
+ };
63
+ }
@@ -0,0 +1,4 @@
1
+ import { type Provider } from "@earendil-works/pi-ai";
2
+ import { type XaiOptions } from "./xai-grok-build.js";
3
+ export { GROK_43_MODEL, GROK_BUILD_MODEL, GROK_COMPOSER_MODEL, GROK_MODELS, XAI_BASE_URL, XAI_MODEL_ID, XAI_PROVIDER_ID, type XaiOptions, } from "./xai-grok-build.js";
4
+ export declare function xaiGrokBuildResponsesProvider(options?: XaiOptions): Provider<"openai-responses">;
@@ -0,0 +1,44 @@
1
+ // pi-ai library adapter for the Grok Build core: import this from server-side
2
+ // code that composes a pi-ai / pi-agent Models registry. Keep it free of app
3
+ // imports and side effects, and never import it from client code. The
4
+ // extension adapter (extension.ts) must NOT import this module — the pi-ai
5
+ // subpath import below is fatal under pi's extension loader.
6
+ //
7
+ // Auth is OAuth-only by design: API keys are not supported anywhere in this
8
+ // project. The Grok subscription token is the sole credential.
9
+ import { createProvider, } from "@earendil-works/pi-ai";
10
+ import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
11
+ import { GROK_MODELS, XAI_BASE_URL, XAI_PROVIDER_ID, loginXai, refreshXai, requireXaiOAuthCredential, } from "./xai-grok-build.js";
12
+ export { GROK_43_MODEL, GROK_BUILD_MODEL, GROK_COMPOSER_MODEL, GROK_MODELS, XAI_BASE_URL, XAI_MODEL_ID, XAI_PROVIDER_ID, } from "./xai-grok-build.js";
13
+ export function xaiGrokBuildResponsesProvider(options = {}) {
14
+ const opts = { ...options };
15
+ return createProvider({
16
+ id: XAI_PROVIDER_ID,
17
+ name: "xAI Grok Build",
18
+ baseUrl: XAI_BASE_URL,
19
+ auth: { oauth: xaiOAuthAuth(opts) },
20
+ models: [...GROK_MODELS],
21
+ api: openAIResponsesApi(),
22
+ });
23
+ }
24
+ function xaiOAuthAuth(opts) {
25
+ return {
26
+ name: "xAI (Grok subscription)",
27
+ async login(callbacks) {
28
+ return { type: "oauth", ...(await loginXai(toHost(callbacks), opts)) };
29
+ },
30
+ async refresh(credential) {
31
+ return { type: "oauth", ...(await refreshXai(requireXaiOAuthCredential(credential))) };
32
+ },
33
+ async toAuth(credential) {
34
+ return { apiKey: requireXaiOAuthCredential(credential).access, baseUrl: XAI_BASE_URL };
35
+ },
36
+ };
37
+ }
38
+ function toHost(cb) {
39
+ return {
40
+ authUrl: (url, instructions) => cb.notify({ type: "auth_url", url, instructions }),
41
+ progress: (message) => cb.notify({ type: "progress", message }),
42
+ ...(cb.signal !== undefined ? { signal: cb.signal } : {}),
43
+ };
44
+ }
@@ -0,0 +1,161 @@
1
+ export declare const XAI_PROVIDER_ID = "xai-grok-build";
2
+ export declare const XAI_MODEL_ID = "grok-build-0.1";
3
+ export declare const XAI_BASE_URL = "https://api.x.ai/v1";
4
+ export declare const GROK_BUILD_MODEL: {
5
+ id: string;
6
+ name: string;
7
+ api: "openai-responses";
8
+ provider: string;
9
+ baseUrl: string;
10
+ reasoning: false;
11
+ input: ("text" | "image")[];
12
+ cost: {
13
+ input: number;
14
+ output: number;
15
+ cacheRead: number;
16
+ cacheWrite: number;
17
+ };
18
+ contextWindow: number;
19
+ maxTokens: number;
20
+ compat: {
21
+ readonly sendSessionIdHeader: false;
22
+ readonly supportsLongCacheRetention: false;
23
+ };
24
+ };
25
+ export declare const GROK_43_MODEL: {
26
+ id: string;
27
+ name: string;
28
+ api: "openai-responses";
29
+ provider: string;
30
+ baseUrl: string;
31
+ reasoning: true;
32
+ input: ("text" | "image")[];
33
+ cost: {
34
+ input: number;
35
+ output: number;
36
+ cacheRead: number;
37
+ cacheWrite: number;
38
+ };
39
+ contextWindow: number;
40
+ maxTokens: number;
41
+ compat: {
42
+ readonly sendSessionIdHeader: false;
43
+ readonly supportsLongCacheRetention: false;
44
+ };
45
+ };
46
+ export declare const GROK_COMPOSER_MODEL: {
47
+ id: string;
48
+ name: string;
49
+ api: "openai-responses";
50
+ provider: string;
51
+ baseUrl: string;
52
+ reasoning: false;
53
+ input: "text"[];
54
+ cost: {
55
+ input: number;
56
+ output: number;
57
+ cacheRead: number;
58
+ cacheWrite: number;
59
+ };
60
+ contextWindow: number;
61
+ maxTokens: number;
62
+ compat: {
63
+ readonly sendSessionIdHeader: false;
64
+ readonly supportsLongCacheRetention: false;
65
+ };
66
+ };
67
+ export declare const GROK_MODELS: readonly [{
68
+ id: string;
69
+ name: string;
70
+ api: "openai-responses";
71
+ provider: string;
72
+ baseUrl: string;
73
+ reasoning: false;
74
+ input: ("text" | "image")[];
75
+ cost: {
76
+ input: number;
77
+ output: number;
78
+ cacheRead: number;
79
+ cacheWrite: number;
80
+ };
81
+ contextWindow: number;
82
+ maxTokens: number;
83
+ compat: {
84
+ readonly sendSessionIdHeader: false;
85
+ readonly supportsLongCacheRetention: false;
86
+ };
87
+ }, {
88
+ id: string;
89
+ name: string;
90
+ api: "openai-responses";
91
+ provider: string;
92
+ baseUrl: string;
93
+ reasoning: true;
94
+ input: ("text" | "image")[];
95
+ cost: {
96
+ input: number;
97
+ output: number;
98
+ cacheRead: number;
99
+ cacheWrite: number;
100
+ };
101
+ contextWindow: number;
102
+ maxTokens: number;
103
+ compat: {
104
+ readonly sendSessionIdHeader: false;
105
+ readonly supportsLongCacheRetention: false;
106
+ };
107
+ }, {
108
+ id: string;
109
+ name: string;
110
+ api: "openai-responses";
111
+ provider: string;
112
+ baseUrl: string;
113
+ reasoning: false;
114
+ input: "text"[];
115
+ cost: {
116
+ input: number;
117
+ output: number;
118
+ cacheRead: number;
119
+ cacheWrite: number;
120
+ };
121
+ contextWindow: number;
122
+ maxTokens: number;
123
+ compat: {
124
+ readonly sendSessionIdHeader: false;
125
+ readonly supportsLongCacheRetention: false;
126
+ };
127
+ }];
128
+ export type XaiOptions = {
129
+ /**
130
+ * Authorization-URL `referrer` param identifying the host application.
131
+ * Defaults to "pi-grok-build"; the pi extension adapter passes "pi".
132
+ */
133
+ referrer?: string;
134
+ };
135
+ /**
136
+ * Everything the login flow needs from its host. Deliberately smaller than
137
+ * either pi surface: xAI's flow never prompts, only notifies.
138
+ */
139
+ export type XaiLoginHost = {
140
+ authUrl(url: string, instructions: string): void;
141
+ progress(message: string): void;
142
+ signal?: AbortSignal;
143
+ };
144
+ /**
145
+ * `type`, not `interface` — needs an implicit index signature to be assignable
146
+ * to pi's OAuthCredential / OAuthCredentials (`[key: string]: unknown`).
147
+ * Exactly these four fields, nothing more: pi coding-agent's refresh path does
148
+ * a FULL REPLACE of the stored credential, so anything refresh omits is gone
149
+ * forever. Identity fields (idToken/email/subject) are deliberately not
150
+ * persisted — nothing reads them and the refresh grant may omit id_token.
151
+ */
152
+ export type XaiOAuthCredential = {
153
+ access: string;
154
+ refresh: string;
155
+ expires: number;
156
+ tokenEndpoint: string;
157
+ };
158
+ /** pi hands back `Record<string, unknown>`. Narrow at the boundary; fail fast. */
159
+ export declare function requireXaiOAuthCredential(value: Record<string, unknown>): XaiOAuthCredential;
160
+ export declare function loginXai(host: XaiLoginHost, opts?: XaiOptions): Promise<XaiOAuthCredential>;
161
+ export declare function refreshXai(credential: XaiOAuthCredential): Promise<XaiOAuthCredential>;