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,62 @@
1
+ import { lstat, realpath, stat } from "node:fs/promises"
2
+ import path from "node:path"
3
+
4
+ export class OfficialCliVerificationError extends Error {
5
+ constructor() {
6
+ super("The official Grok CLI executable path is invalid or unsupported")
7
+ this.name = "OfficialCliVerificationError"
8
+ }
9
+ }
10
+
11
+ export async function verifyOfficialCliExecutable({ candidate, resolved, grokHome, platform }) {
12
+ if (
13
+ typeof candidate !== "string" ||
14
+ typeof resolved !== "string" ||
15
+ typeof grokHome !== "string" ||
16
+ (platform !== "darwin" && platform !== "win32")
17
+ ) throw new TypeError("Invalid official Grok CLI verification request")
18
+
19
+ const pathApi = platform === "win32" ? path.win32 : path.posix
20
+ if (!pathApi.isAbsolute(candidate) || !pathApi.isAbsolute(resolved) || !pathApi.isAbsolute(grokHome)) {
21
+ throw new OfficialCliVerificationError()
22
+ }
23
+
24
+ try {
25
+ const [candidateInfo, canonicalCandidate, canonicalResolved, canonicalHome] = await Promise.all([
26
+ lstat(candidate),
27
+ realpath(candidate),
28
+ realpath(resolved),
29
+ realpath(grokHome),
30
+ ])
31
+ const resolvedInfo = await stat(canonicalCandidate)
32
+ if (!resolvedInfo.isFile()) throw new OfficialCliVerificationError()
33
+
34
+ if (platform === "darwin") {
35
+ if (!candidateInfo.isFile() && !candidateInfo.isSymbolicLink()) throw new OfficialCliVerificationError()
36
+ if ((resolvedInfo.mode & 0o111) === 0 || (resolvedInfo.mode & 0o022) !== 0) {
37
+ throw new OfficialCliVerificationError()
38
+ }
39
+ if (typeof process.getuid === "function") {
40
+ const userId = process.getuid()
41
+ if (candidateInfo.uid !== userId || resolvedInfo.uid !== userId) {
42
+ throw new OfficialCliVerificationError()
43
+ }
44
+ }
45
+ } else {
46
+ if (!candidateInfo.isFile() || candidateInfo.isSymbolicLink()) throw new OfficialCliVerificationError()
47
+ if (pathApi.extname(candidate).toLowerCase() !== ".exe") throw new OfficialCliVerificationError()
48
+ }
49
+
50
+ const normalize = (value) => platform === "win32" ? value.toLowerCase() : value
51
+ if (normalize(pathApi.normalize(canonicalCandidate)) !== normalize(pathApi.normalize(canonicalResolved))) {
52
+ throw new OfficialCliVerificationError()
53
+ }
54
+ const relative = pathApi.relative(canonicalHome, canonicalCandidate)
55
+ if (relative === "" || relative === ".." || relative.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(relative)) {
56
+ throw new OfficialCliVerificationError()
57
+ }
58
+ } catch (error) {
59
+ if (error instanceof OfficialCliVerificationError || error instanceof TypeError) throw error
60
+ throw new OfficialCliVerificationError()
61
+ }
62
+ }
@@ -0,0 +1,64 @@
1
+ import { constants } from "node:fs"
2
+ import { lstat, open } from "node:fs/promises"
3
+ import path from "node:path"
4
+
5
+ const MAX_CREDENTIAL_BYTES = 64 * 1024
6
+
7
+ export class OfficialCredentialFileError extends Error {
8
+ constructor() {
9
+ super("The official Grok credential file is missing or insecure")
10
+ this.name = "OfficialCredentialFileError"
11
+ }
12
+ }
13
+
14
+ export function isSameOpenedFile(before, opened) {
15
+ return before.dev === opened.dev && before.ino === opened.ino
16
+ }
17
+
18
+ export function createOfficialCredentialLoader({ authPath, platform }) {
19
+ if (
20
+ typeof authPath !== "string" ||
21
+ (platform !== "darwin" && platform !== "win32") ||
22
+ !(platform === "win32" ? path.win32 : path.posix).isAbsolute(authPath)
23
+ ) {
24
+ throw new TypeError("Invalid official Grok credential path")
25
+ }
26
+
27
+ return async function loadOfficialCredential() {
28
+ let handle
29
+ try {
30
+ const before = await lstat(authPath)
31
+ if (!before.isFile() || before.isSymbolicLink()) throw new OfficialCredentialFileError()
32
+ const flags = platform === "darwin"
33
+ ? constants.O_RDONLY | constants.O_NOFOLLOW
34
+ : constants.O_RDONLY
35
+ handle = await open(authPath, flags)
36
+ const opened = await handle.stat()
37
+ if (
38
+ !isSameOpenedFile(before, opened) ||
39
+ !opened.isFile() ||
40
+ opened.size < 1 ||
41
+ opened.size > MAX_CREDENTIAL_BYTES
42
+ ) {
43
+ throw new OfficialCredentialFileError()
44
+ }
45
+ if (platform === "darwin") {
46
+ if ((opened.mode & 0o777) !== 0o600) throw new OfficialCredentialFileError()
47
+ if (typeof process.getuid === "function" && opened.uid !== process.getuid()) {
48
+ throw new OfficialCredentialFileError()
49
+ }
50
+ }
51
+ const bytes = await handle.readFile()
52
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength !== opened.size) {
53
+ throw new OfficialCredentialFileError()
54
+ }
55
+ return bytes
56
+ } catch (error) {
57
+ if (error instanceof OfficialCredentialFileError || error instanceof TypeError) throw error
58
+ throw new OfficialCredentialFileError()
59
+ } finally {
60
+ if (handle !== undefined) await handle.close()
61
+ handle = undefined
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,43 @@
1
+ import { createAuthRegistry } from "./auth-registry.mjs"
2
+
3
+ export function installProviderRuntime({
4
+ llm,
5
+ officialSource,
6
+ createTransport,
7
+ createAdapter,
8
+ }) {
9
+ if (
10
+ !llm ||
11
+ typeof llm.registerAdapter !== "function" ||
12
+ typeof llm.registerConfigurableProviders !== "function" ||
13
+ !officialSource ||
14
+ typeof createTransport !== "function" ||
15
+ typeof createAdapter !== "function"
16
+ ) {
17
+ throw new TypeError("Invalid Grok provider runtime dependencies")
18
+ }
19
+
20
+ const auth = createAuthRegistry({ createTransport })
21
+ const removeOfficial = auth.install(officialSource)
22
+ const adapter = createAdapter({ getGeneration: () => auth.getGeneration() })
23
+ const removeAdapter = llm.registerAdapter(["grok"], adapter)
24
+ const removeDirectory = llm.registerConfigurableProviders([{
25
+ provider: "grok",
26
+ displayName: "Grok Build",
27
+ settingsNs: "llm-grok",
28
+ settingsPath: [],
29
+ }])
30
+ let disposed = false
31
+
32
+ return Object.freeze({
33
+ adapter,
34
+ auth,
35
+ dispose() {
36
+ if (disposed) return
37
+ disposed = true
38
+ removeDirectory()
39
+ removeAdapter()
40
+ removeOfficial()
41
+ },
42
+ })
43
+ }
@@ -0,0 +1,357 @@
1
+ const MAX_EVENTS = 100_000
2
+ const MAX_BLOCK_TEXT_BYTES = 8 * 1024 * 1024
3
+ const MAX_TOOL_ARGUMENT_BYTES = 2 * 1024 * 1024
4
+ const MAX_ENCRYPTED_REASONING_BYTES = 8 * 1024 * 1024
5
+
6
+ export class InvalidResponsesStreamError extends Error {
7
+ constructor() {
8
+ super("The Grok Responses stream is invalid or incomplete")
9
+ this.name = "InvalidResponsesStreamError"
10
+ }
11
+ }
12
+
13
+ export function decodeResponsesEvents(events) {
14
+ if (!Array.isArray(events) || events.length === 0 || events.length > MAX_EVENTS) {
15
+ throw new InvalidResponsesStreamError()
16
+ }
17
+
18
+ const chunks = []
19
+ const decoder = createResponsesEventDecoder()
20
+ for (const event of events) chunks.push(...decoder.push(event))
21
+ decoder.finish()
22
+ return chunks
23
+ }
24
+
25
+ export function createResponsesEventDecoder() {
26
+ const blocks = new Map()
27
+ const replayBlocks = []
28
+ let lastSequence = -1
29
+ let created = false
30
+ let inProgress = false
31
+ let completed = false
32
+ let sawToolCall = false
33
+ let sawOutput = false
34
+ let eventCount = 0
35
+
36
+ return Object.freeze({
37
+ push(event) {
38
+ eventCount += 1
39
+ if (eventCount > MAX_EVENTS || !isPlainObject(event) || typeof event.type !== "string") fail()
40
+ if (!Number.isSafeInteger(event.sequence_number) || event.sequence_number <= lastSequence) fail()
41
+ lastSequence = event.sequence_number
42
+ if (completed) fail()
43
+ const chunks = []
44
+
45
+ switch (event.type) {
46
+ case "response.created":
47
+ if (created || !isResponseStatus(event.response, "in_progress")) fail()
48
+ created = true
49
+ break
50
+
51
+ case "response.in_progress":
52
+ if (!created || inProgress || !isResponseStatus(event.response, "in_progress")) fail()
53
+ inProgress = true
54
+ break
55
+
56
+ case "response.output_item.added":
57
+ requireRunning(created, inProgress)
58
+ sawOutput = true
59
+ if (addOutputItem(event, blocks, replayBlocks, chunks) === "tool-call") sawToolCall = true
60
+ break
61
+
62
+ case "response.reasoning_summary_part.added":
63
+ requireBlock(event, blocks, "reasoning")
64
+ if (!isPlainObject(event.part) || event.part.type !== "summary_text" || event.part.text !== "") fail()
65
+ break
66
+
67
+ case "response.reasoning_summary_text.delta": {
68
+ const block = requireBlock(event, blocks, "reasoning")
69
+ appendDelta(block, event.delta)
70
+ chunks.push({ type: "reasoning-delta", index: event.output_index, text: event.delta })
71
+ break
72
+ }
73
+
74
+ case "response.reasoning_summary_text.done": {
75
+ const block = requireBlock(event, blocks, "reasoning")
76
+ if (event.text !== block.text) fail()
77
+ break
78
+ }
79
+
80
+ case "response.reasoning_summary_part.done": {
81
+ const block = requireBlock(event, blocks, "reasoning")
82
+ if (!isPlainObject(event.part) || event.part.type !== "summary_text" || event.part.text !== block.text) fail()
83
+ break
84
+ }
85
+
86
+ case "response.content_part.added": {
87
+ requireBlock(event, blocks, "text")
88
+ if (!isPlainObject(event.part) || event.part.type !== "output_text" || event.part.text !== "") fail()
89
+ break
90
+ }
91
+
92
+ case "response.output_text.delta": {
93
+ const block = requireBlock(event, blocks, "text")
94
+ appendDelta(block, event.delta)
95
+ chunks.push({ type: "text-delta", index: event.output_index, text: event.delta })
96
+ break
97
+ }
98
+
99
+ case "response.output_text.done": {
100
+ const block = requireBlock(event, blocks, "text")
101
+ if (event.text !== block.text) fail()
102
+ break
103
+ }
104
+
105
+ case "response.function_call_arguments.delta": {
106
+ const block = requireBlock(event, blocks, "tool-call")
107
+ appendToolArguments(block, event.delta)
108
+ chunks.push({
109
+ type: "tool-call-delta",
110
+ index: event.output_index,
111
+ id: block.callId,
112
+ ...(block.nameEmitted ? {} : { name: block.name }),
113
+ argumentsDelta: event.delta,
114
+ })
115
+ block.nameEmitted = true
116
+ break
117
+ }
118
+
119
+ case "response.function_call_arguments.done": {
120
+ const block = requireBlock(event, blocks, "tool-call")
121
+ if (event.name !== block.name || event.arguments !== block.arguments) fail()
122
+ break
123
+ }
124
+
125
+ case "response.content_part.done": {
126
+ const block = requireBlock(event, blocks, "text")
127
+ if (!isPlainObject(event.part) || event.part.type !== "output_text" || event.part.text !== block.text) fail()
128
+ break
129
+ }
130
+
131
+ case "response.output_item.done":
132
+ closeOutputItem(event, blocks, replayBlocks, chunks)
133
+ break
134
+
135
+ case "response.completed":
136
+ requireRunning(created, inProgress)
137
+ if (!sawOutput || blocks.size !== 0 || !isResponseStatus(event.response, "completed")) fail()
138
+ chunks.push({ type: "usage", usage: parseUsage(event.response.usage) })
139
+ chunks.push({
140
+ type: "finish",
141
+ reason: { kind: sawToolCall ? "tool-calls" : "stop" },
142
+ ...(replayBlocks.some((block) => block !== null)
143
+ ? {
144
+ replayState: {
145
+ response: { version: 1 },
146
+ blocks: replayBlocks,
147
+ },
148
+ }
149
+ : {}),
150
+ })
151
+ completed = true
152
+ break
153
+
154
+ case "response.incomplete":
155
+ requireRunning(created, inProgress)
156
+ if (
157
+ !sawOutput ||
158
+ !isResponseStatus(event.response, "incomplete") ||
159
+ !isPlainObject(event.response.incomplete_details) ||
160
+ event.response.incomplete_details.reason !== "max_output_tokens"
161
+ ) fail()
162
+ for (const [index, block] of blocks) {
163
+ if (block.type === "tool-call") fail()
164
+ chunks.push({
165
+ type: "block-end",
166
+ index,
167
+ block: { type: block.type, text: block.text },
168
+ })
169
+ }
170
+ blocks.clear()
171
+ chunks.push({ type: "usage", usage: parseUsage(event.response.usage) })
172
+ chunks.push({ type: "finish", reason: { kind: "max-tokens" } })
173
+ completed = true
174
+ break
175
+
176
+ default:
177
+ fail()
178
+ }
179
+ return chunks
180
+ },
181
+
182
+ finish() {
183
+ if (!completed || blocks.size !== 0) fail()
184
+ },
185
+ })
186
+ }
187
+
188
+ function addOutputItem(event, blocks, replayBlocks, chunks) {
189
+ const index = parseIndex(event.output_index)
190
+ if (blocks.has(index) || !isPlainObject(event.item) || !isBoundedString(event.item.id, 256)) fail()
191
+
192
+ let type
193
+ let block
194
+ if (event.item.type === "reasoning") {
195
+ type = "reasoning"
196
+ block = { id: event.item.id, type, text: "" }
197
+ } else if (event.item.type === "message" && event.item.role === "assistant") {
198
+ type = "text"
199
+ block = { id: event.item.id, type, text: "" }
200
+ } else if (
201
+ event.item.type === "function_call" &&
202
+ event.item.status === "in_progress" &&
203
+ event.item.arguments === "" &&
204
+ isBoundedString(event.item.call_id, 256) &&
205
+ isBoundedString(event.item.name, 128)
206
+ ) {
207
+ type = "tool-call"
208
+ block = {
209
+ id: event.item.id,
210
+ type,
211
+ callId: event.item.call_id,
212
+ name: event.item.name,
213
+ nameEmitted: false,
214
+ arguments: "",
215
+ }
216
+ }
217
+ else fail()
218
+
219
+ block.replayPosition = replayBlocks.length
220
+ replayBlocks.push(null)
221
+ blocks.set(index, block)
222
+ chunks.push({ type: "block-start", index, blockType: type })
223
+ return type
224
+ }
225
+
226
+ function closeOutputItem(event, blocks, replayBlocks, chunks) {
227
+ const block = requireBlock(event, blocks)
228
+ if (
229
+ !isPlainObject(event.item) ||
230
+ event.item.id !== block.id ||
231
+ event.item.status !== "completed"
232
+ ) fail()
233
+ if (block.type === "reasoning") {
234
+ if (
235
+ event.item.type !== "reasoning" ||
236
+ !Array.isArray(event.item.summary) ||
237
+ event.item.summary.length !== 1 ||
238
+ !isPlainObject(event.item.summary[0]) ||
239
+ event.item.summary[0].type !== "summary_text" ||
240
+ event.item.summary[0].text !== block.text
241
+ ) fail()
242
+ if (event.item.encrypted_content !== undefined) {
243
+ if (!isBoundedUtf8String(event.item.encrypted_content, MAX_ENCRYPTED_REASONING_BYTES)) fail()
244
+ replayBlocks[block.replayPosition] = {
245
+ type: "reasoning",
246
+ id: block.id,
247
+ encryptedContent: event.item.encrypted_content,
248
+ }
249
+ }
250
+ }
251
+ if (block.type === "text" && (event.item.type !== "message" || event.item.role !== "assistant")) fail()
252
+ if (block.type === "tool-call" && (
253
+ event.item.type !== "function_call" ||
254
+ event.item.call_id !== block.callId ||
255
+ event.item.name !== block.name ||
256
+ event.item.arguments !== block.arguments ||
257
+ !isJsonObject(block.arguments)
258
+ )) fail()
259
+
260
+ chunks.push({
261
+ type: "block-end",
262
+ index: event.output_index,
263
+ block: block.type === "tool-call"
264
+ ? { type: "tool-call", id: block.callId, name: block.name, arguments: block.arguments }
265
+ : { type: block.type, text: block.text },
266
+ })
267
+ blocks.delete(event.output_index)
268
+ }
269
+
270
+ function appendToolArguments(block, delta) {
271
+ if (typeof delta !== "string" || delta.length === 0) fail()
272
+ block.arguments += delta
273
+ if (Buffer.byteLength(block.arguments, "utf8") > MAX_TOOL_ARGUMENT_BYTES) fail()
274
+ }
275
+
276
+ function requireBlock(event, blocks, expectedType) {
277
+ const index = parseIndex(event.output_index)
278
+ const block = blocks.get(index)
279
+ if (!block || (expectedType !== undefined && block.type !== expectedType)) fail()
280
+ if (event.item_id !== undefined && event.item_id !== block.id) fail()
281
+ return block
282
+ }
283
+
284
+ function appendDelta(block, delta) {
285
+ if (typeof delta !== "string" || delta.length === 0) fail()
286
+ block.text += delta
287
+ if (Buffer.byteLength(block.text, "utf8") > MAX_BLOCK_TEXT_BYTES) fail()
288
+ }
289
+
290
+ function parseUsage(usage) {
291
+ if (!isPlainObject(usage)) fail()
292
+ const inputTokens = parseCount(usage.input_tokens)
293
+ const outputTokens = parseCount(usage.output_tokens)
294
+ const cachedTokens = usage.input_tokens_details === undefined
295
+ ? 0
296
+ : parseCountFrom(usage.input_tokens_details, "cached_tokens")
297
+ const reasoningTokens = usage.output_tokens_details === undefined
298
+ ? 0
299
+ : parseCountFrom(usage.output_tokens_details, "reasoning_tokens")
300
+ if (cachedTokens > inputTokens) fail()
301
+
302
+ return {
303
+ inputTokens: inputTokens - cachedTokens,
304
+ outputTokens,
305
+ ...(cachedTokens === 0 ? {} : { cacheReadTokens: cachedTokens }),
306
+ ...(reasoningTokens === 0 ? {} : { reasoningTokens }),
307
+ }
308
+ }
309
+
310
+ function parseCountFrom(value, field) {
311
+ if (!isPlainObject(value)) fail()
312
+ return parseCount(value[field] ?? 0)
313
+ }
314
+
315
+ function parseCount(value) {
316
+ if (!Number.isSafeInteger(value) || value < 0) fail()
317
+ return value
318
+ }
319
+
320
+ function parseIndex(value) {
321
+ if (!Number.isSafeInteger(value) || value < 0) fail()
322
+ return value
323
+ }
324
+
325
+ function isResponseStatus(value, status) {
326
+ return isPlainObject(value) && value.status === status
327
+ }
328
+
329
+ function isBoundedString(value, maxLength) {
330
+ return typeof value === "string" && value.length > 0 && value.length <= maxLength
331
+ }
332
+
333
+ function isBoundedUtf8String(value, maxBytes) {
334
+ return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= maxBytes
335
+ }
336
+
337
+ function isJsonObject(value) {
338
+ try {
339
+ return isPlainObject(JSON.parse(value))
340
+ } catch {
341
+ return false
342
+ }
343
+ }
344
+
345
+ function requireRunning(created, inProgress) {
346
+ if (!created || !inProgress) fail()
347
+ }
348
+
349
+ function isPlainObject(value) {
350
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false
351
+ const prototype = Object.getPrototypeOf(value)
352
+ return prototype === Object.prototype || prototype === null
353
+ }
354
+
355
+ function fail() {
356
+ throw new InvalidResponsesStreamError()
357
+ }