( agentIdOrSessionId: string, )
| 148 | } |
| 149 | |
| 150 | export function createDumpPromptsFetch( |
| 151 | agentIdOrSessionId: string, |
| 152 | ): ClientOptions['fetch'] { |
| 153 | const filePath = getDumpPromptsPath(agentIdOrSessionId) |
| 154 | |
| 155 | return async (input: RequestInfo | URL, init?: RequestInit) => { |
| 156 | const state = dumpState.get(agentIdOrSessionId) ?? { |
| 157 | initialized: false, |
| 158 | messageCountSeen: 0, |
| 159 | lastInitDataHash: '', |
| 160 | lastInitFingerprint: '', |
| 161 | } |
| 162 | dumpState.set(agentIdOrSessionId, state) |
| 163 | |
| 164 | let timestamp: string | undefined |
| 165 | |
| 166 | if (init?.method === 'POST' && init.body) { |
| 167 | timestamp = new Date().toISOString() |
| 168 | // Parsing + stringifying the request (system prompt + tool schemas = MBs) |
| 169 | // takes hundreds of ms. Defer so it doesn't block the actual API call — |
| 170 | // this is debug tooling for /issue, not on the critical path. |
| 171 | setImmediate(dumpRequest, init.body as string, timestamp, state, filePath) |
| 172 | } |
| 173 | |
| 174 | // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins |
| 175 | const response = await globalThis.fetch(input, init) |
| 176 | |
| 177 | // Save response async |
| 178 | if (timestamp && response.ok && isInternalBuild()) { |
| 179 | const cloned = response.clone() |
| 180 | void (async () => { |
| 181 | try { |
| 182 | const isStreaming = cloned.headers |
| 183 | .get('content-type') |
| 184 | ?.includes('text/event-stream') |
| 185 | |
| 186 | let data: unknown |
| 187 | if (isStreaming && cloned.body) { |
| 188 | // Parse SSE stream into chunks |
| 189 | const reader = cloned.body.getReader() |
| 190 | const decoder = new TextDecoder() |
| 191 | let buffer = '' |
| 192 | try { |
| 193 | while (true) { |
| 194 | const { done, value } = await reader.read() |
| 195 | if (done) break |
| 196 | buffer += decoder.decode(value, { stream: true }) |
| 197 | } |
| 198 | } finally { |
| 199 | reader.releaseLock() |
| 200 | } |
| 201 | const chunks: unknown[] = [] |
| 202 | for (const event of buffer.split('\n\n')) { |
| 203 | for (const line of event.split('\n')) { |
| 204 | if (line.startsWith('data: ') && line !== 'data: [DONE]') { |
| 205 | try { |
| 206 | chunks.push(jsonParse(line.slice(6))) |
| 207 | } catch { |
no test coverage detected