Repository navigation
Expand file tree
/
Copy pathlog.ts
More file actions
116 lines (109 loc) · 3.67 KB
/
Copy pathlog.ts
File metadata and controls
116 lines (109 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { randomUUID } from "node:crypto"
import { appendFile, mkdir, rename, writeFile } from "node:fs/promises"
import { homedir } from "node:os"
import { join } from "node:path"
export const defaultDirectory = () =>
process.env.REQUEST_LOG_DIR ||
join(
process.env.XDG_CONFIG_HOME || join(homedir(), ".config"),
"opencode",
"logs",
"request-logger",
)
export interface Scope {
sessionID: string
kind?: string
agent?: string
model?: unknown
}
export function decode(text: string): unknown {
try {
return JSON.parse(text)
} catch {
return text
}
}
export class Capture {
constructor(readonly directory = defaultDirectory()) {}
async write(scope: Scope, id: string, suffix: string, value: unknown) {
const directory = join(this.directory, encodeURIComponent(scope.sessionID))
const file = join(directory, `${id}.${suffix}`)
try {
await mkdir(directory, { recursive: true, mode: 0o700 })
if (value instanceof Uint8Array) {
await appendFile(file, value, { mode: 0o600 })
} else {
await writeFile(
`${file}.tmp`,
JSON.stringify({
timestamp: new Date().toISOString(),
...scope,
requestID: id,
...(value as object),
}),
{ mode: 0o600 },
)
await rename(`${file}.tmp`, file)
}
} catch (error) {
console.error("request-logger: cannot write capture", file, error)
}
}
async request(scope: Scope, request: Request) {
const id = randomUUID()
await this.write(scope, id, "request.json", {
type: "http.request",
url: request.url,
method: request.method,
body: decode(await request.clone().text()),
})
return id
}
async response(scope: Scope, id: string, response: Response) {
await this.write(scope, id, "response.json", {
type: "http.response",
status: response.status,
contentType: response.headers.get("content-type"),
})
const end = (status: string) =>
this.write(scope, id, "end.json", { type: "http.end", status })
if (!response.body) {
await end("completed")
return response
}
const reader = response.body.getReader()
const capture = this
const body = new ReadableStream<Uint8Array>({
async pull(controller) {
try {
const chunk = await reader.read()
if (chunk.done) {
await end("completed")
controller.close()
reader.releaseLock()
} else {
await capture.write(scope, id, "response.body", chunk.value)
controller.enqueue(chunk.value)
}
} catch (error) {
await end("error")
controller.error(error)
reader.releaseLock()
}
},
async cancel(reason) {
await end("cancelled")
try {
await reader.cancel(reason)
} finally {
reader.releaseLock()
}
},
})
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
})
}
}