dsh-grok-provider 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.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +200 -0
  5. package/README.md +200 -0
  6. package/SECURITY.md +35 -0
  7. package/dist/client/client.js +215 -0
  8. package/dist/host/index.mjs +147 -0
  9. package/dist/internal/account-dashboard.mjs +67 -0
  10. package/dist/internal/auth-controller.mjs +184 -0
  11. package/dist/internal/auth-registry.mjs +64 -0
  12. package/dist/internal/auth-rpc.mjs +118 -0
  13. package/dist/internal/billing-summary.mjs +105 -0
  14. package/dist/internal/credential-source.mjs +168 -0
  15. package/dist/internal/grok-adapter.mjs +160 -0
  16. package/dist/internal/grok-command-handler.mjs +62 -0
  17. package/dist/internal/grok-command.mjs +17 -0
  18. package/dist/internal/grok-transport.mjs +279 -0
  19. package/dist/internal/model-catalog.mjs +142 -0
  20. package/dist/internal/official-auth-driver.mjs +40 -0
  21. package/dist/internal/official-cli-auth.mjs +231 -0
  22. package/dist/internal/official-cli-verifier.mjs +62 -0
  23. package/dist/internal/official-credential-loader.mjs +64 -0
  24. package/dist/internal/provider-runtime.mjs +43 -0
  25. package/dist/internal/responses-codec.mjs +357 -0
  26. package/dist/internal/responses-request.mjs +246 -0
  27. package/dist/internal/responses-sse.mjs +123 -0
  28. package/docs/01-product-requirements.md +128 -0
  29. package/docs/02-architecture-options.md +180 -0
  30. package/docs/03-security-threat-model.md +243 -0
  31. package/docs/04-harness-contract.md +325 -0
  32. package/docs/05-test-plan.md +224 -0
  33. package/docs/06-release-plan.md +182 -0
  34. package/docs/07-decision-gate.md +76 -0
  35. package/docs/08-upstream-cli-1.0.5-evidence.md +98 -0
  36. package/docs/09-implementation-status.md +37 -0
  37. package/docs/README.md +60 -0
  38. package/docs/adr/0001-auth-and-transport-route.md +86 -0
  39. package/docs/adr/0002-v0.1-scope.md +46 -0
  40. package/docs/adr/0003-dual-authentication.md +77 -0
  41. package/docs/adr/0004-dynamic-model-catalog.md +34 -0
  42. package/docs/adr/0005-official-cli-only-authentication.md +36 -0
  43. package/docs/adr/0006-account-dashboard.md +84 -0
  44. package/grok-provider.patch.yml +3 -0
  45. package/package.json +92 -0
  46. package/types/index.d.ts +9 -0
@@ -0,0 +1,279 @@
1
+ const BASE_URL = "https://cli-chat-proxy.grok.com"
2
+ const MAX_JSON_RESPONSE_BYTES = 256 * 1024
3
+ const MAX_REQUEST_BYTES = 16 * 1024 * 1024
4
+ const DEFAULT_MODEL_TIMEOUT_MS = 30 * 1000
5
+ const DEFAULT_BILLING_TIMEOUT_MS = 15 * 1000
6
+ const DEFAULT_RESPONSE_TIMEOUT_MS = 10 * 60 * 1000
7
+ const MAX_TIMER_DELAY_MS = 2_147_483_647
8
+
9
+ export class GrokTransportError extends Error {
10
+ constructor(status) {
11
+ super("The Grok Build transport request failed")
12
+ this.name = "GrokTransportError"
13
+ if (status !== undefined) this.status = status
14
+ }
15
+ }
16
+
17
+ export function createGrokTransport({
18
+ credentialSource,
19
+ fetch,
20
+ attributionHeaders,
21
+ clientIdentifier,
22
+ clientVersion,
23
+ modelTimeoutMs = DEFAULT_MODEL_TIMEOUT_MS,
24
+ billingTimeoutMs = DEFAULT_BILLING_TIMEOUT_MS,
25
+ responseTimeoutMs = DEFAULT_RESPONSE_TIMEOUT_MS,
26
+ }) {
27
+ if (
28
+ !credentialSource ||
29
+ typeof credentialSource.withAccessToken !== "function" ||
30
+ typeof fetch !== "function" ||
31
+ typeof attributionHeaders !== "function" ||
32
+ !isHeaderValue(clientIdentifier) ||
33
+ !isHeaderValue(clientVersion) ||
34
+ !isTimeout(modelTimeoutMs) ||
35
+ !isTimeout(billingTimeoutMs) ||
36
+ !isTimeout(responseTimeoutMs)
37
+ ) {
38
+ throw new TypeError("Invalid Grok transport dependencies")
39
+ }
40
+
41
+ return Object.freeze({
42
+ async getBilling({ signal } = {}) {
43
+ const deadline = createDeadline(signal, billingTimeoutMs)
44
+ try {
45
+ return await credentialSource.withAccessToken(async (accessToken, metadata) => {
46
+ if (!isPlainObject(metadata) || !isHeaderValue(metadata.userId)) throw new GrokTransportError()
47
+ let response
48
+ try {
49
+ const headers = buildHeaders({ accessToken, attributionHeaders, clientIdentifier, clientVersion })
50
+ headers.set("x-userid", metadata.userId)
51
+ response = await fetch(`${BASE_URL}/v1/billing?format=credits`, {
52
+ method: "GET",
53
+ redirect: "error",
54
+ headers,
55
+ signal: deadline.signal,
56
+ })
57
+ validateJsonResponse(response)
58
+ if (response.status !== 200) throw new GrokTransportError(response.status)
59
+ return await readBoundedText(response, MAX_JSON_RESPONSE_BYTES)
60
+ } catch (error) {
61
+ if (error instanceof GrokTransportError) throw error
62
+ if (signal?.aborted && error?.name === "AbortError") throw error
63
+ throw new GrokTransportError()
64
+ } finally {
65
+ response = undefined
66
+ metadata = undefined
67
+ accessToken = undefined
68
+ }
69
+ })
70
+ } finally {
71
+ deadline.dispose()
72
+ }
73
+ },
74
+
75
+ async listModels({ signal } = {}) {
76
+ const deadline = createDeadline(signal, modelTimeoutMs)
77
+ try {
78
+ return await credentialSource.withAccessToken(async (accessToken) => {
79
+ let response
80
+ try {
81
+ response = await fetch(`${BASE_URL}/v1/models`, {
82
+ method: "GET",
83
+ redirect: "error",
84
+ headers: buildHeaders({
85
+ accessToken,
86
+ attributionHeaders,
87
+ clientIdentifier,
88
+ clientVersion,
89
+ }),
90
+ signal: deadline.signal,
91
+ })
92
+ validateJsonResponse(response)
93
+ if (response.status !== 200) throw new GrokTransportError(response.status)
94
+ return await readBoundedText(response, MAX_JSON_RESPONSE_BYTES)
95
+ } catch (error) {
96
+ if (error instanceof GrokTransportError) throw error
97
+ if (signal?.aborted && error?.name === "AbortError") throw error
98
+ throw new GrokTransportError()
99
+ } finally {
100
+ response = undefined
101
+ accessToken = undefined
102
+ }
103
+ })
104
+ } finally {
105
+ deadline.dispose()
106
+ }
107
+ },
108
+
109
+ async *streamResponses(request, { signal } = {}) {
110
+ if (!isPlainObject(request) || request.stream !== true || request.store !== false) {
111
+ throw new TypeError("Invalid Grok Responses request")
112
+ }
113
+ let body
114
+ try {
115
+ body = JSON.stringify(request)
116
+ } catch {
117
+ throw new TypeError("Invalid Grok Responses request")
118
+ }
119
+ if (Buffer.byteLength(body, "utf8") > MAX_REQUEST_BYTES) {
120
+ throw new TypeError("Invalid Grok Responses request")
121
+ }
122
+
123
+ let response
124
+ let source
125
+ const deadline = createDeadline(signal, responseTimeoutMs)
126
+ try {
127
+ response = await credentialSource.withAccessToken(async (accessToken) => {
128
+ const headers = buildHeaders({
129
+ accessToken,
130
+ attributionHeaders,
131
+ clientIdentifier,
132
+ clientVersion,
133
+ })
134
+ headers.set("Content-Type", "application/json")
135
+ return fetch(`${BASE_URL}/v1/responses`, {
136
+ method: "POST",
137
+ redirect: "error",
138
+ headers,
139
+ body,
140
+ signal: deadline.signal,
141
+ })
142
+ })
143
+ validateEventStreamResponse(response)
144
+ if (response.status !== 200) throw new GrokTransportError(response.status)
145
+ source = response.body
146
+ for await (const chunk of source) {
147
+ if (!(chunk instanceof Uint8Array)) throw new GrokTransportError(response.status)
148
+ yield chunk
149
+ }
150
+ } catch (error) {
151
+ if (error instanceof GrokTransportError) throw error
152
+ if (signal?.aborted && error?.name === "AbortError") throw error
153
+ throw new GrokTransportError()
154
+ } finally {
155
+ deadline.dispose()
156
+ source = undefined
157
+ response = undefined
158
+ body = undefined
159
+ }
160
+ },
161
+ })
162
+ }
163
+
164
+ function createDeadline(callerSignal, timeoutMs) {
165
+ if (callerSignal !== undefined && (
166
+ callerSignal === null ||
167
+ typeof callerSignal !== "object" ||
168
+ typeof callerSignal.addEventListener !== "function" ||
169
+ typeof callerSignal.removeEventListener !== "function" ||
170
+ typeof callerSignal.aborted !== "boolean"
171
+ )) throw new TypeError("Invalid Grok transport abort signal")
172
+ const controller = new AbortController()
173
+ const abortFromCaller = () => controller.abort(callerSignal.reason)
174
+ if (callerSignal?.aborted) abortFromCaller()
175
+ else callerSignal?.addEventListener("abort", abortFromCaller, { once: true })
176
+ const timer = setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), timeoutMs)
177
+ return {
178
+ signal: controller.signal,
179
+ dispose() {
180
+ clearTimeout(timer)
181
+ callerSignal?.removeEventListener("abort", abortFromCaller)
182
+ },
183
+ }
184
+ }
185
+
186
+ function isTimeout(value) {
187
+ return Number.isSafeInteger(value) && value > 0 && value <= MAX_TIMER_DELAY_MS
188
+ }
189
+
190
+ function buildHeaders({ accessToken, attributionHeaders, clientIdentifier, clientVersion }) {
191
+ if (!isHeaderValue(accessToken)) throw new GrokTransportError()
192
+ const attribution = attributionHeaders()
193
+ if (!isPlainObject(attribution)) throw new GrokTransportError()
194
+
195
+ const headers = new Headers()
196
+ for (const [name, value] of Object.entries(attribution)) {
197
+ if (!isHeaderValue(name) || !isHeaderValue(value)) throw new GrokTransportError()
198
+ headers.set(name, value)
199
+ }
200
+ headers.set("Accept", "application/json")
201
+ headers.set("Authorization", `Bearer ${accessToken}`)
202
+ headers.set("X-XAI-Token-Auth", "xai-grok-cli")
203
+ headers.set("x-grok-client-version", clientVersion)
204
+ headers.set("x-grok-client-identifier", clientIdentifier)
205
+ return headers
206
+ }
207
+
208
+ function validateJsonResponse(response) {
209
+ validateResponseShape(response)
210
+ const contentType = response.headers.get("content-type")
211
+ if (typeof contentType !== "string" || !contentType.toLowerCase().startsWith("application/json")) {
212
+ throw new GrokTransportError(response.status)
213
+ }
214
+ }
215
+
216
+ function validateEventStreamResponse(response) {
217
+ validateResponseShape(response)
218
+ const contentType = response.headers.get("content-type")
219
+ if (typeof contentType !== "string" || !contentType.toLowerCase().startsWith("text/event-stream")) {
220
+ throw new GrokTransportError(response.status)
221
+ }
222
+ if (!response.body || typeof response.body[Symbol.asyncIterator] !== "function") {
223
+ throw new GrokTransportError(response.status)
224
+ }
225
+ }
226
+
227
+ function validateResponseShape(response) {
228
+ if (
229
+ response === null ||
230
+ typeof response !== "object" ||
231
+ !Number.isSafeInteger(response.status) ||
232
+ response.status < 100 ||
233
+ response.status > 599 ||
234
+ !response.headers ||
235
+ typeof response.headers.get !== "function"
236
+ ) {
237
+ throw new GrokTransportError()
238
+ }
239
+ }
240
+
241
+ async function readBoundedText(response, maxBytes) {
242
+ if (!response.body || typeof response.body.getReader !== "function") {
243
+ if (typeof response.text !== "function") throw new GrokTransportError(response.status)
244
+ const text = await response.text()
245
+ if (typeof text !== "string" || Buffer.byteLength(text, "utf8") > maxBytes) {
246
+ throw new GrokTransportError(response.status)
247
+ }
248
+ return text
249
+ }
250
+
251
+ const reader = response.body.getReader()
252
+ const decoder = new TextDecoder("utf-8", { fatal: true })
253
+ let byteLength = 0
254
+ let text = ""
255
+ try {
256
+ while (true) {
257
+ const { done, value } = await reader.read()
258
+ if (done) break
259
+ if (!(value instanceof Uint8Array)) throw new GrokTransportError(response.status)
260
+ byteLength += value.byteLength
261
+ if (byteLength > maxBytes) throw new GrokTransportError(response.status)
262
+ text += decoder.decode(value, { stream: true })
263
+ }
264
+ text += decoder.decode()
265
+ return text
266
+ } finally {
267
+ reader.releaseLock()
268
+ }
269
+ }
270
+
271
+ function isHeaderValue(value) {
272
+ return typeof value === "string" && value.length > 0 && value.length <= 16 * 1024 && !/[\r\n\0]/u.test(value)
273
+ }
274
+
275
+ function isPlainObject(value) {
276
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
277
+ const prototype = Object.getPrototypeOf(value)
278
+ return prototype === Object.prototype || prototype === null
279
+ }
@@ -0,0 +1,142 @@
1
+ const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
2
+ const MAX_CATALOG_BYTES = 256 * 1024
3
+ const MAX_MODELS = 256
4
+ const MAX_REASONING_EFFORTS = 16
5
+
6
+ export class InvalidModelCatalogError extends Error {
7
+ constructor() {
8
+ super("The Grok model catalog response is invalid or unsupported")
9
+ this.name = "InvalidModelCatalogError"
10
+ }
11
+ }
12
+
13
+ export function parseModelCatalogResponse(raw, { provider }) {
14
+ if (typeof raw !== "string" || Buffer.byteLength(raw, "utf8") > MAX_CATALOG_BYTES) {
15
+ throw new InvalidModelCatalogError()
16
+ }
17
+ if (typeof provider !== "string" || provider.length === 0) {
18
+ throw new TypeError("A provider id is required")
19
+ }
20
+
21
+ let value
22
+ try {
23
+ value = JSON.parse(raw)
24
+ } catch {
25
+ throw new InvalidModelCatalogError()
26
+ }
27
+
28
+ if (!isPlainObject(value) || value.object !== "list" || !Array.isArray(value.data)) {
29
+ throw new InvalidModelCatalogError()
30
+ }
31
+ if (value.data.length > MAX_MODELS) throw new InvalidModelCatalogError()
32
+
33
+ const seen = new Set()
34
+ return value.data.map((model) => {
35
+ const entry = parseModel(model, provider)
36
+ if (seen.has(entry.resolvedModelInfo.id)) throw new InvalidModelCatalogError()
37
+ seen.add(entry.resolvedModelInfo.id)
38
+ return entry
39
+ })
40
+ }
41
+
42
+ function parseModel(model, provider) {
43
+ if (
44
+ !isPlainObject(model) ||
45
+ typeof model.id !== "string" ||
46
+ !MODEL_ID_PATTERN.test(model.id) ||
47
+ typeof model.name !== "string" ||
48
+ model.name.length === 0 ||
49
+ model.name.length > 128 ||
50
+ (model.description !== undefined && (
51
+ typeof model.description !== "string" ||
52
+ model.description.length > 1024
53
+ )) ||
54
+ !Number.isSafeInteger(model.context_window) ||
55
+ model.context_window <= 0 ||
56
+ model.context_window > 10_000_000 ||
57
+ model.api_backend !== "responses"
58
+ ) {
59
+ throw new InvalidModelCatalogError()
60
+ }
61
+
62
+ const resolvedModelInfo = {
63
+ provider,
64
+ id: model.id,
65
+ name: model.name,
66
+ ...(model.description === undefined ? {} : { description: model.description }),
67
+ inputModalities: ["text"],
68
+ context: { contextWindow: model.context_window },
69
+ ...parseReasoning(model),
70
+ }
71
+
72
+ return {
73
+ backend: model.api_backend,
74
+ resolvedModelInfo,
75
+ }
76
+ }
77
+
78
+ function parseReasoning(model) {
79
+ if (model.supports_reasoning_effort !== true) return {}
80
+ if (!Array.isArray(model.reasoning_efforts)) throw new InvalidModelCatalogError()
81
+ if (
82
+ model.reasoning_efforts.length === 0 ||
83
+ model.reasoning_efforts.length > MAX_REASONING_EFFORTS
84
+ ) {
85
+ throw new InvalidModelCatalogError()
86
+ }
87
+
88
+ const seen = new Set()
89
+ let defaultEffort
90
+ const efforts = model.reasoning_efforts.map((effort) => {
91
+ if (
92
+ !isPlainObject(effort) ||
93
+ typeof effort.id !== "string" ||
94
+ !MODEL_ID_PATTERN.test(effort.id) ||
95
+ effort.value !== effort.id ||
96
+ typeof effort.label !== "string" ||
97
+ effort.label.length === 0 ||
98
+ effort.label.length > 128 ||
99
+ (effort.description !== undefined && (
100
+ typeof effort.description !== "string" ||
101
+ effort.description.length > 512
102
+ )) ||
103
+ typeof effort.default !== "boolean" ||
104
+ seen.has(effort.id)
105
+ ) {
106
+ throw new InvalidModelCatalogError()
107
+ }
108
+ seen.add(effort.id)
109
+ if (effort.default) {
110
+ if (defaultEffort !== undefined) throw new InvalidModelCatalogError()
111
+ defaultEffort = effort.id
112
+ }
113
+ return {
114
+ id: effort.id,
115
+ name: effort.label,
116
+ ...(effort.description === undefined ? {} : { description: effort.description }),
117
+ }
118
+ })
119
+
120
+ if (model.reasoning_effort !== undefined) {
121
+ if (typeof model.reasoning_effort !== "string" || !seen.has(model.reasoning_effort)) {
122
+ throw new InvalidModelCatalogError()
123
+ }
124
+ if (defaultEffort !== undefined && defaultEffort !== model.reasoning_effort) {
125
+ throw new InvalidModelCatalogError()
126
+ }
127
+ defaultEffort = model.reasoning_effort
128
+ }
129
+
130
+ return {
131
+ reasoning: {
132
+ efforts,
133
+ ...(defaultEffort === undefined ? {} : { defaultEffort }),
134
+ },
135
+ }
136
+ }
137
+
138
+ function isPlainObject(value) {
139
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
140
+ const prototype = Object.getPrototypeOf(value)
141
+ return prototype === Object.prototype || prototype === null
142
+ }
@@ -0,0 +1,40 @@
1
+ const OUTCOMES = new Set(["succeeded", "cancelled"])
2
+
3
+ export function createOfficialAuthDriver({ officialAuth, credentialSource }) {
4
+ if (
5
+ !officialAuth ||
6
+ typeof officialAuth.login !== "function" ||
7
+ typeof officialAuth.logout !== "function" ||
8
+ !credentialSource ||
9
+ typeof credentialSource.withAccessToken !== "function"
10
+ ) throw new TypeError("Invalid official Grok authentication driver dependencies")
11
+
12
+ return Object.freeze({
13
+ async begin({ signal } = {}) {
14
+ return Object.freeze({
15
+ completion: (async () => {
16
+ const outcome = await officialAuth.login({ signal })
17
+ validateOutcome(outcome)
18
+ if (outcome.kind !== "succeeded") return outcome
19
+ await credentialSource.withAccessToken(async () => undefined)
20
+ return outcome
21
+ })(),
22
+ })
23
+ },
24
+
25
+ async logout({ signal } = {}) {
26
+ const outcome = await officialAuth.logout({ signal })
27
+ validateOutcome(outcome)
28
+ return outcome
29
+ },
30
+ })
31
+ }
32
+
33
+ function validateOutcome(value) {
34
+ if (
35
+ value === null ||
36
+ typeof value !== "object" ||
37
+ !OUTCOMES.has(value.kind) ||
38
+ Object.keys(value).length !== 1
39
+ ) throw new TypeError("Invalid official Grok authentication outcome")
40
+ }
@@ -0,0 +1,231 @@
1
+ import path from "node:path"
2
+
3
+ const EXPECTED_VERSION = "grok 1.0.5 (5115b46bc909)"
4
+ const GRACE_MS = 5_000
5
+ const DEFAULT_VERSION_TIMEOUT_MS = 10 * 1000
6
+ const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000
7
+ const DEFAULT_LOGOUT_TIMEOUT_MS = 2 * 60 * 1000
8
+ const DEFAULT_REFRESH_TIMEOUT_MS = 30 * 1000
9
+ const MAX_TIMER_DELAY_MS = 2_147_483_647
10
+
11
+ export class OfficialCliAuthError extends Error {
12
+ constructor() {
13
+ super("The official Grok CLI login could not be completed")
14
+ this.name = "OfficialCliAuthError"
15
+ }
16
+ }
17
+
18
+ export function createOfficialCliAuth({
19
+ subprocess,
20
+ platform,
21
+ homeDir,
22
+ verifyExecutable,
23
+ versionTimeoutMs = DEFAULT_VERSION_TIMEOUT_MS,
24
+ loginTimeoutMs = DEFAULT_LOGIN_TIMEOUT_MS,
25
+ logoutTimeoutMs = DEFAULT_LOGOUT_TIMEOUT_MS,
26
+ refreshTimeoutMs = DEFAULT_REFRESH_TIMEOUT_MS,
27
+ }) {
28
+ if (
29
+ !subprocess ||
30
+ typeof subprocess.resolveExecutable !== "function" ||
31
+ typeof subprocess.spawn !== "function" ||
32
+ (platform !== "darwin" && platform !== "win32") ||
33
+ typeof homeDir !== "string" ||
34
+ homeDir.length === 0 ||
35
+ typeof verifyExecutable !== "function" ||
36
+ !isTimeout(versionTimeoutMs) ||
37
+ !isTimeout(loginTimeoutMs) ||
38
+ !isTimeout(logoutTimeoutMs) ||
39
+ !isTimeout(refreshTimeoutMs)
40
+ ) {
41
+ throw new TypeError("Invalid official Grok CLI auth dependencies")
42
+ }
43
+
44
+ const pathApi = platform === "win32" ? path.win32 : path.posix
45
+ const grokHome = pathApi.join(homeDir, ".grok")
46
+ const candidate = pathApi.join(grokHome, "bin", platform === "win32" ? "grok.exe" : "grok")
47
+ const cliEnvironment = buildCliEnvironment(platform, homeDir)
48
+
49
+ const runAction = async (argvTail, callerSignal, timeoutMs) => {
50
+ let preparationDeadline = createDeadline(callerSignal, versionTimeoutMs)
51
+ let actionDeadline
52
+ let resolved
53
+ try {
54
+ resolved = await subprocess.resolveExecutable(candidate, {}, preparationDeadline.signal)
55
+ if (typeof resolved !== "string" || !pathApi.isAbsolute(resolved)) throw new OfficialCliAuthError()
56
+ await verifyExecutable({ candidate, resolved, grokHome, platform })
57
+
58
+ const version = await runCollected(subprocess, {
59
+ argv: [resolved, "--version"],
60
+ cwd: grokHome,
61
+ maxBytes: 4 * 1024,
62
+ signal: preparationDeadline.signal,
63
+ env: cliEnvironment,
64
+ })
65
+ if (version.stdout.trim() !== EXPECTED_VERSION || version.stderr.length !== 0) {
66
+ throw new OfficialCliAuthError()
67
+ }
68
+
69
+ preparationDeadline.dispose()
70
+ preparationDeadline = undefined
71
+ actionDeadline = createDeadline(callerSignal, timeoutMs)
72
+ await runCollected(subprocess, {
73
+ argv: [resolved, ...argvTail],
74
+ cwd: grokHome,
75
+ maxBytes: 64 * 1024,
76
+ signal: actionDeadline.signal,
77
+ env: cliEnvironment,
78
+ })
79
+ return Object.freeze({ kind: "succeeded" })
80
+ } catch (error) {
81
+ if (callerSignal?.aborted) {
82
+ return Object.freeze({ kind: "cancelled" })
83
+ }
84
+ if (error instanceof OfficialCliAuthError || error instanceof TypeError) throw error
85
+ throw new OfficialCliAuthError()
86
+ } finally {
87
+ actionDeadline?.dispose()
88
+ preparationDeadline?.dispose()
89
+ resolved = undefined
90
+ }
91
+ }
92
+
93
+ return Object.freeze({
94
+ login({ signal } = {}) {
95
+ return runAction(["login", "--oauth"], signal, loginTimeoutMs)
96
+ },
97
+ logout({ signal } = {}) {
98
+ return runAction(["logout"], signal, logoutTimeoutMs)
99
+ },
100
+ refresh({ signal } = {}) {
101
+ return runAction(["models"], signal, refreshTimeoutMs)
102
+ },
103
+ })
104
+ }
105
+
106
+ function createDeadline(callerSignal, timeoutMs) {
107
+ const controller = new AbortController()
108
+ const abortFromCaller = () => controller.abort(callerSignal.reason)
109
+ if (callerSignal?.aborted) abortFromCaller()
110
+ else callerSignal?.addEventListener("abort", abortFromCaller, { once: true })
111
+ const timer = setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), timeoutMs)
112
+ return {
113
+ signal: controller.signal,
114
+ dispose() {
115
+ clearTimeout(timer)
116
+ callerSignal?.removeEventListener("abort", abortFromCaller)
117
+ },
118
+ }
119
+ }
120
+
121
+ function isTimeout(value) {
122
+ return Number.isSafeInteger(value) && value > 0 && value <= MAX_TIMER_DELAY_MS
123
+ }
124
+
125
+ async function runCollected(subprocess, { argv, cwd, maxBytes, signal, env }) {
126
+ let handle
127
+ let stdout
128
+ let stderr
129
+ try {
130
+ handle = subprocess.spawn({
131
+ argv,
132
+ cwd,
133
+ stdio: {
134
+ stdin: "ignore",
135
+ stdout: { maxBytes },
136
+ stderr: { maxBytes },
137
+ },
138
+ graceMs: GRACE_MS,
139
+ signal,
140
+ env,
141
+ })
142
+ if (!isProcessHandle(handle)) throw new OfficialCliAuthError()
143
+ const outcome = await handle.done
144
+ if (!isOutcome(outcome) || outcome.exitCode !== 0 || outcome.signal !== null) {
145
+ throw new OfficialCliAuthError()
146
+ }
147
+ stdout = readCollected(handle.collected.stdout)
148
+ stderr = readCollected(handle.collected.stderr)
149
+ return { stdout, stderr }
150
+ } finally {
151
+ if (handle && typeof handle.waitForExit === "function") {
152
+ const exited = await handle.waitForExit()
153
+ if (exited !== true && !signal?.aborted) throw new OfficialCliAuthError()
154
+ }
155
+ stderr = undefined
156
+ stdout = undefined
157
+ handle = undefined
158
+ }
159
+ }
160
+
161
+ function buildCliEnvironment(platform, homeDir) {
162
+ const environment = {}
163
+ const fixedTombstones = [
164
+ "BROWSER",
165
+ "GROK_AUTH_PROVIDER_COMMAND",
166
+ "GROK_CONFIG",
167
+ "GROK_CONFIG_PATH",
168
+ "GROK_HOME",
169
+ "NODE_EXTRA_CA_CERTS",
170
+ "NODE_OPTIONS",
171
+ "NODE_PATH",
172
+ "SSLKEYLOGFILE",
173
+ "XAI_API_KEY",
174
+ ]
175
+ for (const name of fixedTombstones) environment[name] = undefined
176
+ for (const name of Object.keys(process.env)) {
177
+ if (/^(?:GROK_|XAI_|DYLD_|LD_)/iu.test(name)) environment[name] = undefined
178
+ }
179
+
180
+ if (platform === "darwin") {
181
+ environment.HOME = homeDir
182
+ environment.PATH = "/usr/bin:/bin:/usr/sbin:/sbin"
183
+ } else {
184
+ const inheritedRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT
185
+ const systemRoot = typeof inheritedRoot === "string" && path.win32.isAbsolute(inheritedRoot) &&
186
+ inheritedRoot.length <= 260 && !/[\r\n\0]/u.test(inheritedRoot)
187
+ ? path.win32.normalize(inheritedRoot)
188
+ : "C:\\Windows"
189
+ environment.USERPROFILE = homeDir
190
+ environment.PATH = [
191
+ path.win32.join(systemRoot, "System32"),
192
+ systemRoot,
193
+ path.win32.join(systemRoot, "System32", "Wbem"),
194
+ ].join(";")
195
+ environment.PATHEXT = ".COM;.EXE;.BAT;.CMD"
196
+ environment.COMSPEC = path.win32.join(systemRoot, "System32", "cmd.exe")
197
+ }
198
+ return Object.freeze(environment)
199
+ }
200
+
201
+ function readCollected(reader) {
202
+ if (!reader || typeof reader.readFrom !== "function") throw new OfficialCliAuthError()
203
+ const output = reader.readFrom(0)
204
+ if (
205
+ !output ||
206
+ typeof output.text !== "string" ||
207
+ output.lossy !== false ||
208
+ output.spillPath !== undefined
209
+ ) throw new OfficialCliAuthError()
210
+ return output.text
211
+ }
212
+
213
+ function isProcessHandle(handle) {
214
+ return (
215
+ handle !== null &&
216
+ typeof handle === "object" &&
217
+ handle.done &&
218
+ typeof handle.done.then === "function" &&
219
+ handle.collected &&
220
+ typeof handle.waitForExit === "function"
221
+ )
222
+ }
223
+
224
+ function isOutcome(outcome) {
225
+ return (
226
+ outcome !== null &&
227
+ typeof outcome === "object" &&
228
+ (Number.isSafeInteger(outcome.exitCode) || outcome.exitCode === null) &&
229
+ (typeof outcome.signal === "string" || outcome.signal === null)
230
+ )
231
+ }