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,64 @@
1
+ export class AuthModeUnavailableError extends Error {
2
+ constructor() {
3
+ super("The official Grok authentication source is unavailable")
4
+ this.name = "AuthModeUnavailableError"
5
+ }
6
+ }
7
+
8
+ export function createAuthRegistry({ createTransport }) {
9
+ if (typeof createTransport !== "function") {
10
+ throw new TypeError("A Grok transport factory is required")
11
+ }
12
+
13
+ let entry
14
+ let generation = 0
15
+
16
+ return Object.freeze({
17
+ install(source) {
18
+ if (!source || typeof source !== "object") throw new TypeError("Invalid Grok credential source")
19
+ const token = Object.freeze({})
20
+ const transport = createTransport(source)
21
+ if (!transport || typeof transport !== "object") throw new TypeError("Invalid Grok transport")
22
+ entry = { source, transport, token }
23
+ generation += 1
24
+ let disposed = false
25
+ return () => {
26
+ if (disposed) return
27
+ disposed = true
28
+ if (entry?.token === token) {
29
+ entry = undefined
30
+ generation += 1
31
+ }
32
+ }
33
+ },
34
+
35
+ invalidate() {
36
+ generation += 1
37
+ },
38
+
39
+ getGeneration() {
40
+ if (entry === undefined) throw new AuthModeUnavailableError()
41
+ return Object.freeze({ id: generation, transport: entry.transport })
42
+ },
43
+
44
+ async status() {
45
+ for (let attempt = 0; attempt < 2; attempt += 1) {
46
+ const snapshot = entry
47
+ const snapshotGeneration = generation
48
+ let available = false
49
+ if (snapshot !== undefined && typeof snapshot.source.withAccessToken === "function") {
50
+ try {
51
+ await snapshot.source.withAccessToken(() => undefined)
52
+ available = true
53
+ } catch {
54
+ available = false
55
+ }
56
+ }
57
+ if (entry === snapshot && generation === snapshotGeneration) {
58
+ return Object.freeze({ generation: snapshotGeneration, available })
59
+ }
60
+ }
61
+ return Object.freeze({ generation, available: false })
62
+ },
63
+ })
64
+ }
@@ -0,0 +1,118 @@
1
+ import { AuthDriverUnavailableError, AuthLoginBusyError } from "./auth-controller.mjs"
2
+
3
+ export function createAuthRpcHandler({ controller, dashboard }) {
4
+ if (
5
+ !controller ||
6
+ typeof controller.status !== "function" ||
7
+ typeof controller.beginLogin !== "function" ||
8
+ typeof controller.cancel !== "function" ||
9
+ typeof controller.logout !== "function" ||
10
+ typeof dashboard !== "function"
11
+ ) throw new TypeError("Invalid Grok auth RPC controller")
12
+
13
+ return async function handleAuthRpc(endpoint, payload, signal) {
14
+ if (signal?.aborted) return cancelled()
15
+ try {
16
+ if (endpoint === "status") {
17
+ if (!hasExactKeys(payload, [])) return badRequest()
18
+ const status = serializable(await controller.status())
19
+ return ok({ kind: "status", status })
20
+ }
21
+ if (endpoint === "dashboard") {
22
+ if (!hasExactKeys(payload, [])) return badRequest()
23
+ return ok({ kind: "dashboard", dashboard: serializable(await dashboard({ signal })) })
24
+ }
25
+ if (endpoint === "login") {
26
+ if (!hasExactKeys(payload, [])) return badRequest()
27
+ const session = await controller.beginLogin()
28
+ const status = serializable(session.public)
29
+ return ok({ kind: "login-started", status, sessionId: status.sessionId })
30
+ }
31
+ if (endpoint === "cancel") {
32
+ const cancellation = parseCancelPayload(payload)
33
+ if (cancellation === undefined) return badRequest()
34
+ return ok({
35
+ kind: controller.cancel(cancellation.sessionId) ? "cancelled" : "not-running",
36
+ status: serializable(await controller.status()),
37
+ })
38
+ }
39
+ if (endpoint === "logout") {
40
+ if (!hasExactKeys(payload, [])) return badRequest()
41
+ const outcome = await controller.logout({ signal })
42
+ if (outcome.kind === "confirmation-required") {
43
+ return ok({
44
+ kind: "logout-confirmation-required",
45
+ confirmationId: outcome.confirmationId,
46
+ expiresAt: outcome.expiresAt,
47
+ })
48
+ }
49
+ return ok({ kind: `logout-${outcome.kind}`, status: serializable(await controller.status()) })
50
+ }
51
+ return badRequest()
52
+ } catch (error) {
53
+ if (error instanceof AuthLoginBusyError) {
54
+ return ok({ kind: "busy", status: serializable(await controller.status()) })
55
+ }
56
+ if (error instanceof AuthDriverUnavailableError) {
57
+ return ok({ kind: "unavailable", status: serializable(await controller.status()) })
58
+ }
59
+ return signal?.aborted ? cancelled() : internal()
60
+ }
61
+ }
62
+ }
63
+
64
+ function parseCancelPayload(payload) {
65
+ if (
66
+ !hasExactKeys(payload, ["sessionId"]) ||
67
+ typeof payload.sessionId !== "string" ||
68
+ payload.sessionId.length === 0 ||
69
+ payload.sessionId.length > 128
70
+ ) return undefined
71
+ return payload
72
+ }
73
+
74
+ function hasExactKeys(value, expected) {
75
+ if (!isPlainObject(value)) return false
76
+ const keys = Object.keys(value).sort()
77
+ const sorted = [...expected].sort()
78
+ return keys.length === sorted.length && keys.every((key, index) => key === sorted[index])
79
+ }
80
+
81
+ function serializable(value) {
82
+ const text = JSON.stringify(value)
83
+ if (typeof text !== "string" || Buffer.byteLength(text, "utf8") > 64 * 1024) {
84
+ throw new TypeError("Invalid Grok auth RPC response")
85
+ }
86
+ return JSON.parse(text)
87
+ }
88
+
89
+ function ok(value) {
90
+ return { ok: true, value }
91
+ }
92
+
93
+ function badRequest() {
94
+ return {
95
+ ok: false,
96
+ error: { code: "bad-request", message: "Invalid Grok auth RPC request", details: { issues: [] } },
97
+ }
98
+ }
99
+
100
+ function cancelled() {
101
+ return {
102
+ ok: false,
103
+ error: { code: "cancelled", message: "The Grok auth operation was cancelled", details: {} },
104
+ }
105
+ }
106
+
107
+ function internal() {
108
+ return {
109
+ ok: false,
110
+ error: { code: "internal", message: "The Grok auth operation failed", details: {} },
111
+ }
112
+ }
113
+
114
+ function isPlainObject(value) {
115
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
116
+ const prototype = Object.getPrototypeOf(value)
117
+ return prototype === Object.prototype || prototype === null
118
+ }
@@ -0,0 +1,105 @@
1
+ const MAX_BILLING_BYTES = 256 * 1024
2
+
3
+ export class InvalidBillingResponseError extends Error {
4
+ constructor() {
5
+ super("The Grok billing response is invalid or unsupported")
6
+ this.name = "InvalidBillingResponseError"
7
+ }
8
+ }
9
+
10
+ export function parseBillingSummary(raw) {
11
+ if (typeof raw !== "string" || Buffer.byteLength(raw, "utf8") > MAX_BILLING_BYTES) fail()
12
+ let value
13
+ try { value = JSON.parse(raw) } catch { fail() }
14
+ if (!isPlainObject(value) || (value.config !== null && !isPlainObject(value.config))) fail()
15
+ if (value.config === null) return Object.freeze({ state: "unavailable" })
16
+
17
+ const config = value.config
18
+ const period = parsePeriod(config)
19
+ const usedPercent = parseUsage(config, period)
20
+ return Object.freeze({
21
+ state: "ready",
22
+ ...(usedPercent === undefined ? {} : {
23
+ usedPercent,
24
+ remainingPercent: normalizePercent(100 - usedPercent),
25
+ }),
26
+ ...period,
27
+ })
28
+ }
29
+
30
+ function parseUsage(config, period) {
31
+ if (config.creditUsagePercent !== undefined) {
32
+ if (!isPercent(config.creditUsagePercent)) fail()
33
+ return config.creditUsagePercent
34
+ }
35
+ // GetGrokCreditsConfig is protobuf-backed. Its JSON projection omits the
36
+ // scalar zero value, while retaining the typed period that owns it.
37
+ if (
38
+ period.periodKind !== undefined
39
+ && period.periodStart !== undefined
40
+ && period.resetsAt !== undefined
41
+ ) return 0
42
+ if (config.monthlyLimit === undefined && config.used === undefined) return undefined
43
+ const limit = parseCent(config.monthlyLimit)
44
+ const used = parseCent(config.used)
45
+ if (limit <= 0 || used < 0 || used > limit) fail()
46
+ return normalizePercent((used / limit) * 100)
47
+ }
48
+
49
+ function parsePeriod(config) {
50
+ if (config.currentPeriod !== undefined) {
51
+ if (!isPlainObject(config.currentPeriod)) fail()
52
+ const current = config.currentPeriod
53
+ const periodKind = current.type === "USAGE_PERIOD_TYPE_WEEKLY" ? "weekly"
54
+ : current.type === "USAGE_PERIOD_TYPE_MONTHLY" ? "monthly" : undefined
55
+ if (current.type !== undefined && (
56
+ typeof current.type !== "string" || current.type.length === 0 || current.type.length > 128
57
+ )) fail()
58
+ const periodStart = parseOptionalDateTime(current.start)
59
+ const resetsAt = parseOptionalDateTime(current.end)
60
+ return {
61
+ ...(periodKind === undefined ? {} : { periodKind }),
62
+ ...(periodStart === undefined ? {} : { periodStart }),
63
+ ...(resetsAt === undefined ? {} : { resetsAt }),
64
+ }
65
+ }
66
+ const periodStart = parseOptionalDateTime(config.billingPeriodStart)
67
+ const resetsAt = parseOptionalDateTime(config.billingPeriodEnd)
68
+ if (periodStart === undefined && resetsAt === undefined) return {}
69
+ return {
70
+ periodKind: "monthly",
71
+ ...(periodStart === undefined ? {} : { periodStart }),
72
+ ...(resetsAt === undefined ? {} : { resetsAt }),
73
+ }
74
+ }
75
+
76
+ function parseCent(value) {
77
+ if (!isPlainObject(value) || !Number.isSafeInteger(value.val)) fail()
78
+ return value.val
79
+ }
80
+
81
+ function parseOptionalDateTime(value) {
82
+ if (value === undefined) return undefined
83
+ if (typeof value !== "string") fail()
84
+ const time = Date.parse(value)
85
+ if (!Number.isFinite(time)) fail()
86
+ return new Date(time).toISOString()
87
+ }
88
+
89
+ function normalizePercent(value) {
90
+ return Math.round(value * 100) / 100
91
+ }
92
+
93
+ function isPercent(value) {
94
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100
95
+ }
96
+
97
+ function fail() {
98
+ throw new InvalidBillingResponseError()
99
+ }
100
+
101
+ function isPlainObject(value) {
102
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
103
+ const prototype = Object.getPrototypeOf(value)
104
+ return prototype === Object.prototype || prototype === null
105
+ }
@@ -0,0 +1,168 @@
1
+ const CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828"
2
+ const EXPIRY_SKEW_MS = 5 * 60 * 1000
3
+
4
+ export const GROK_CLI_1_0_5_AUTH_CONTRACT = Object.freeze({
5
+ authMode: "oidc",
6
+ clientId: CLIENT_ID,
7
+ issuer: "https://auth.x.ai",
8
+ scope: `https://auth.x.ai::${CLIENT_ID}`,
9
+ })
10
+
11
+ export class UnsupportedCredentialError extends Error {
12
+ constructor() {
13
+ super("The Grok credential does not match the supported authentication contract")
14
+ this.name = "UnsupportedCredentialError"
15
+ }
16
+ }
17
+
18
+ export class CredentialFileTooLargeError extends Error {
19
+ constructor() {
20
+ super("The Grok credential file exceeds the 64 KiB limit")
21
+ this.name = "CredentialFileTooLargeError"
22
+ }
23
+ }
24
+
25
+ class CredentialRefreshRequiredError extends Error {}
26
+
27
+ export function createCredentialSource({ contract, load, now, refresh }) {
28
+ if (
29
+ !contract ||
30
+ typeof load !== "function" ||
31
+ typeof now !== "function" ||
32
+ (refresh !== undefined && typeof refresh !== "function")
33
+ ) {
34
+ throw new TypeError("Invalid credential source dependencies")
35
+ }
36
+ let activeRefresh
37
+
38
+ const refreshOnce = async () => {
39
+ if (activeRefresh === undefined) {
40
+ activeRefresh = Promise.resolve().then(refresh).finally(() => {
41
+ activeRefresh = undefined
42
+ })
43
+ }
44
+ return activeRefresh
45
+ }
46
+
47
+ return Object.freeze({
48
+ async withAccessToken(operation) {
49
+ if (typeof operation !== "function") {
50
+ throw new TypeError("Access-token operation must be a function")
51
+ }
52
+
53
+ let raw
54
+ let rawText
55
+ let parsed
56
+ let accessToken
57
+ let metadata
58
+ let refreshAttempted = false
59
+
60
+ try {
61
+ while (accessToken === undefined) {
62
+ try {
63
+ raw = await load()
64
+ if (utf8ByteLength(raw) > 64 * 1024) {
65
+ throw new CredentialFileTooLargeError()
66
+ }
67
+ rawText = decodeUtf8(raw)
68
+ parsed = JSON.parse(rawText)
69
+
70
+ if (!isPlainObject(parsed)) throw new UnsupportedCredentialError()
71
+
72
+ const entries = Object.entries(parsed)
73
+ if (entries.length !== 1 || entries[0][0] !== contract.scope) {
74
+ throw new UnsupportedCredentialError()
75
+ }
76
+
77
+ const record = entries[0][1]
78
+ if (
79
+ !isPlainObject(record) ||
80
+ record.auth_mode !== contract.authMode ||
81
+ record.oidc_issuer !== contract.issuer ||
82
+ record.oidc_client_id !== contract.clientId ||
83
+ typeof record.key !== "string" ||
84
+ record.key.length === 0 ||
85
+ !isDateTime(record.expires_at)
86
+ ) {
87
+ throw new UnsupportedCredentialError()
88
+ }
89
+ const currentTime = now()
90
+ if (!(currentTime instanceof Date) || !Number.isFinite(currentTime.getTime())) {
91
+ throw new TypeError("Invalid credential source clock")
92
+ }
93
+ if (!isFutureDateTime(record.expires_at, currentTime)) throw new CredentialRefreshRequiredError()
94
+
95
+ accessToken = record.key
96
+ metadata = Object.freeze({
97
+ ...(isHeaderValue(record.user_id) ? { userId: record.user_id } : {}),
98
+ })
99
+ } catch (error) {
100
+ if (
101
+ error instanceof CredentialRefreshRequiredError &&
102
+ refresh !== undefined &&
103
+ !refreshAttempted
104
+ ) {
105
+ refreshAttempted = true
106
+ try {
107
+ await refreshOnce()
108
+ } catch {
109
+ throw new UnsupportedCredentialError()
110
+ }
111
+ raw = undefined
112
+ rawText = undefined
113
+ parsed = undefined
114
+ continue
115
+ }
116
+ if (
117
+ error instanceof UnsupportedCredentialError ||
118
+ error instanceof CredentialFileTooLargeError ||
119
+ error instanceof TypeError
120
+ ) {
121
+ throw error
122
+ }
123
+ throw new UnsupportedCredentialError()
124
+ }
125
+ }
126
+
127
+ return await operation(accessToken, metadata)
128
+ } finally {
129
+ metadata = undefined
130
+ accessToken = undefined
131
+ parsed = undefined
132
+ rawText = undefined
133
+ raw = undefined
134
+ }
135
+ },
136
+ })
137
+ }
138
+
139
+ function isHeaderValue(value) {
140
+ return typeof value === "string" && value.length > 0 && value.length <= 16 * 1024 && !/[\r\n\0]/u.test(value)
141
+ }
142
+
143
+ function utf8ByteLength(value) {
144
+ if (typeof value === "string") return Buffer.byteLength(value, "utf8")
145
+ if (value instanceof Uint8Array) return value.byteLength
146
+ throw new TypeError("Credential loader must return a string or Uint8Array")
147
+ }
148
+
149
+ function decodeUtf8(value) {
150
+ if (typeof value === "string") return value
151
+ return new TextDecoder("utf-8", { fatal: true }).decode(value)
152
+ }
153
+
154
+ function isPlainObject(value) {
155
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
156
+ const prototype = Object.getPrototypeOf(value)
157
+ return prototype === Object.prototype || prototype === null
158
+ }
159
+
160
+ function isFutureDateTime(value, now) {
161
+ if (typeof value !== "string") return false
162
+ const expiresAt = Date.parse(value)
163
+ return Number.isFinite(expiresAt) && expiresAt - now.getTime() > EXPIRY_SKEW_MS
164
+ }
165
+
166
+ function isDateTime(value) {
167
+ return typeof value === "string" && Number.isFinite(Date.parse(value))
168
+ }
@@ -0,0 +1,160 @@
1
+ import { parseModelCatalogResponse } from "./model-catalog.mjs"
2
+ import { createResponsesEventDecoder } from "./responses-codec.mjs"
3
+ import { encodeResponsesRequest } from "./responses-request.mjs"
4
+ import { parseResponsesSse } from "./responses-sse.mjs"
5
+
6
+ export class GrokAdapterError extends Error {
7
+ constructor() {
8
+ super("The requested Grok model or adapter generation is unavailable")
9
+ this.name = "GrokAdapterError"
10
+ }
11
+ }
12
+
13
+ export function createGrokAdapter({ getGeneration, mapError = (error) => error }) {
14
+ if (typeof getGeneration !== "function") {
15
+ throw new TypeError("A Grok adapter generation source is required")
16
+ }
17
+ if (typeof mapError !== "function") throw new TypeError("Invalid Grok adapter error mapper")
18
+
19
+ const captureGeneration = () => {
20
+ const generation = getGeneration()
21
+ if (
22
+ !isPlainObject(generation) ||
23
+ !generation.transport ||
24
+ typeof generation.transport.listModels !== "function" ||
25
+ typeof generation.transport.streamResponses !== "function"
26
+ ) {
27
+ throw new GrokAdapterError()
28
+ }
29
+ return generation
30
+ }
31
+
32
+ const resolveWithGeneration = async (generation, provider, model, signal) => {
33
+ try {
34
+ requireProvider(provider)
35
+ if (typeof model !== "string" || model.length === 0) throw new GrokAdapterError()
36
+ const entries = await discover(generation, provider, signal)
37
+ const match = entries.find((entry) => entry.resolvedModelInfo.id === model)
38
+ if (match === undefined) throw new GrokAdapterError()
39
+ return match.resolvedModelInfo
40
+ } catch (error) {
41
+ throw mapError(error)
42
+ }
43
+ }
44
+
45
+ return Object.freeze({
46
+ providerInfo(provider) {
47
+ requireProvider(provider)
48
+ return Object.freeze({ id: "grok", name: "Grok Build" })
49
+ },
50
+
51
+ providerRetryPolicy(provider) {
52
+ requireProvider(provider)
53
+ return undefined
54
+ },
55
+
56
+ async listModels(provider) {
57
+ try {
58
+ requireProvider(provider)
59
+ const entries = await discover(captureGeneration(), provider)
60
+ return Object.freeze(entries.map((entry) => entry.resolvedModelInfo))
61
+ } catch (error) {
62
+ throw mapError(error)
63
+ }
64
+ },
65
+
66
+ async resolveModel(provider, model, signal) {
67
+ return resolveWithGeneration(captureGeneration(), provider, model, signal)
68
+ },
69
+
70
+ async prepareCall(provider, model, signal) {
71
+ const generation = captureGeneration()
72
+ const resolvedModel = await resolveWithGeneration(generation, provider, model, signal)
73
+ return Object.freeze({
74
+ model: resolvedModel,
75
+ stream(options) {
76
+ try {
77
+ validatePreparedOptions(options, resolvedModel)
78
+ return streamWithGeneration(generation, options, mapError)
79
+ } catch (error) {
80
+ throw mapError(error)
81
+ }
82
+ },
83
+ })
84
+ },
85
+
86
+ stream(options) {
87
+ try {
88
+ return streamWithGeneration(captureGeneration(), options, mapError)
89
+ } catch (error) {
90
+ throw mapError(error)
91
+ }
92
+ },
93
+ })
94
+ }
95
+
96
+ async function discover(generation, provider, signal) {
97
+ const raw = await generation.transport.listModels({ signal })
98
+ return parseModelCatalogResponse(raw, { provider }).map((entry) => Object.freeze({
99
+ backend: entry.backend,
100
+ resolvedModelInfo: freezeModel(entry.resolvedModelInfo),
101
+ }))
102
+ }
103
+
104
+ async function* streamWithGeneration(generation, options, mapError) {
105
+ try {
106
+ requireProvider(options?.provider)
107
+ const request = encodeResponsesRequest(options)
108
+ const decoder = createResponsesEventDecoder()
109
+ for await (const event of parseResponsesSse(
110
+ generation.transport.streamResponses(request, { signal: options.signal }),
111
+ )) {
112
+ for (const chunk of decoder.push(event)) yield chunk
113
+ }
114
+ decoder.finish()
115
+ } catch (error) {
116
+ throw mapError(error)
117
+ }
118
+ }
119
+
120
+ function validatePreparedOptions(options, resolvedModel) {
121
+ if (!isPlainObject(options) || options.provider !== "grok" || options.model !== resolvedModel.id) {
122
+ throw new GrokAdapterError()
123
+ }
124
+ if (options.reasoningEffort !== undefined) {
125
+ const efforts = resolvedModel.reasoning?.efforts
126
+ if (!Array.isArray(efforts) || !efforts.some((effort) => effort.id === options.reasoningEffort)) {
127
+ throw new GrokAdapterError()
128
+ }
129
+ }
130
+ }
131
+
132
+ function freezeModel(model) {
133
+ const frozen = {
134
+ ...model,
135
+ inputModalities: model.inputModalities === undefined
136
+ ? undefined
137
+ : Object.freeze([...model.inputModalities]),
138
+ context: model.context === undefined ? undefined : Object.freeze({ ...model.context }),
139
+ reasoning: model.reasoning === undefined
140
+ ? undefined
141
+ : Object.freeze({
142
+ ...model.reasoning,
143
+ efforts: Object.freeze(model.reasoning.efforts.map((effort) => Object.freeze({ ...effort }))),
144
+ }),
145
+ }
146
+ if (frozen.inputModalities === undefined) delete frozen.inputModalities
147
+ if (frozen.context === undefined) delete frozen.context
148
+ if (frozen.reasoning === undefined) delete frozen.reasoning
149
+ return Object.freeze(frozen)
150
+ }
151
+
152
+ function requireProvider(provider) {
153
+ if (provider !== "grok") throw new GrokAdapterError()
154
+ }
155
+
156
+ function isPlainObject(value) {
157
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
158
+ const prototype = Object.getPrototypeOf(value)
159
+ return prototype === Object.prototype || prototype === null
160
+ }
@@ -0,0 +1,62 @@
1
+ import { parseGrokCommandInput } from "./grok-command.mjs"
2
+
3
+ const USAGE = "Usage: /grok status|login|cancel|logout"
4
+
5
+ export function createGrokCommandHandler({ controller }) {
6
+ if (
7
+ !controller ||
8
+ typeof controller.status !== "function" ||
9
+ typeof controller.beginLogin !== "function" ||
10
+ typeof controller.cancel !== "function" ||
11
+ typeof controller.logout !== "function"
12
+ ) throw new TypeError("Invalid Grok command controller")
13
+
14
+ return async function handleGrokCommand({ rawInput, signal } = {}) {
15
+ const command = parseGrokCommandInput(rawInput)
16
+ if (command === undefined) return error(USAGE)
17
+
18
+ try {
19
+ if (command.verb === "status") {
20
+ const status = await controller.status()
21
+ return success(`Grok official CLI auth: ${renderStatus(status)}`)
22
+ }
23
+ if (command.verb === "login") {
24
+ const session = await controller.beginLogin()
25
+ const outcome = await session.wait({ signal })
26
+ return outcome.kind === "succeeded"
27
+ ? success("Grok official CLI login succeeded")
28
+ : error(`Grok official CLI login ${outcome.kind}`)
29
+ }
30
+ if (command.verb === "cancel") {
31
+ const sessionId = (await controller.status()).session?.sessionId
32
+ return typeof sessionId === "string" && controller.cancel(sessionId)
33
+ ? success("Grok login cancellation requested")
34
+ : error("No Grok login is running")
35
+ }
36
+ if (command.verb === "logout") {
37
+ const outcome = await controller.logout({ signal })
38
+ return outcome.kind === "succeeded"
39
+ ? success("Grok official CLI logout succeeded")
40
+ : outcome.kind === "confirmation-required"
41
+ ? success("Repeat /grok logout within 30 seconds to confirm")
42
+ : error(`Grok official CLI logout ${outcome.kind}`)
43
+ }
44
+ return error(USAGE)
45
+ } catch {
46
+ return error("The Grok authentication operation failed")
47
+ }
48
+ }
49
+ }
50
+
51
+ function renderStatus(status) {
52
+ if (status.session?.state === "running") return "running"
53
+ return status.driver && status.available ? "ready" : "unavailable"
54
+ }
55
+
56
+ function success(text) {
57
+ return Object.freeze({ kind: "success", text })
58
+ }
59
+
60
+ function error(text) {
61
+ return Object.freeze({ kind: "error", text })
62
+ }
@@ -0,0 +1,17 @@
1
+ const GRAMMAR = Object.freeze([
2
+ { pattern: /^\s+status$/u, build: () => ({ verb: "status" }) },
3
+ { pattern: /^\s+login$/u, build: () => ({ verb: "login" }) },
4
+ { pattern: /^\s+cancel$/u, build: () => ({ verb: "cancel" }) },
5
+ { pattern: /^\s+logout$/u, build: () => ({ verb: "logout" }) },
6
+ ])
7
+
8
+ export function parseGrokCommandInput(rawInput) {
9
+ if (typeof rawInput !== "string" || rawInput.length > 256 || /[\r\n\0]/u.test(rawInput)) {
10
+ return undefined
11
+ }
12
+ for (const rule of GRAMMAR) {
13
+ const match = rule.pattern.exec(rawInput)
14
+ if (match !== null) return Object.freeze(rule.build(match))
15
+ }
16
+ return undefined
17
+ }