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.
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +51 -0
- package/LICENSE +21 -0
- package/README.en.md +200 -0
- package/README.md +200 -0
- package/SECURITY.md +35 -0
- package/dist/client/client.js +215 -0
- package/dist/host/index.mjs +147 -0
- package/dist/internal/account-dashboard.mjs +67 -0
- package/dist/internal/auth-controller.mjs +184 -0
- package/dist/internal/auth-registry.mjs +64 -0
- package/dist/internal/auth-rpc.mjs +118 -0
- package/dist/internal/billing-summary.mjs +105 -0
- package/dist/internal/credential-source.mjs +168 -0
- package/dist/internal/grok-adapter.mjs +160 -0
- package/dist/internal/grok-command-handler.mjs +62 -0
- package/dist/internal/grok-command.mjs +17 -0
- package/dist/internal/grok-transport.mjs +279 -0
- package/dist/internal/model-catalog.mjs +142 -0
- package/dist/internal/official-auth-driver.mjs +40 -0
- package/dist/internal/official-cli-auth.mjs +231 -0
- package/dist/internal/official-cli-verifier.mjs +62 -0
- package/dist/internal/official-credential-loader.mjs +64 -0
- package/dist/internal/provider-runtime.mjs +43 -0
- package/dist/internal/responses-codec.mjs +357 -0
- package/dist/internal/responses-request.mjs +246 -0
- package/dist/internal/responses-sse.mjs +123 -0
- package/docs/01-product-requirements.md +128 -0
- package/docs/02-architecture-options.md +180 -0
- package/docs/03-security-threat-model.md +243 -0
- package/docs/04-harness-contract.md +325 -0
- package/docs/05-test-plan.md +224 -0
- package/docs/06-release-plan.md +182 -0
- package/docs/07-decision-gate.md +76 -0
- package/docs/08-upstream-cli-1.0.5-evidence.md +98 -0
- package/docs/09-implementation-status.md +37 -0
- package/docs/README.md +60 -0
- package/docs/adr/0001-auth-and-transport-route.md +86 -0
- package/docs/adr/0002-v0.1-scope.md +46 -0
- package/docs/adr/0003-dual-authentication.md +77 -0
- package/docs/adr/0004-dynamic-model-catalog.md +34 -0
- package/docs/adr/0005-official-cli-only-authentication.md +36 -0
- package/docs/adr/0006-account-dashboard.md +84 -0
- package/grok-provider.patch.yml +3 -0
- package/package.json +92 -0
- 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
|
+
}
|