feat(http-recorder): prepare public beta release (#31018)

This commit is contained in:
Kit Langton
2026-06-06 03:01:26 +00:00
committed by GitHub
parent ba57718b05
commit 54f4974546
36 changed files with 2277 additions and 713 deletions
+62 -3
View File
@@ -1,7 +1,9 @@
import { Option } from "effect"
import { decodeJson } from "./matching"
import { redactHeaders, redactUrl } from "./redaction"
import type { RequestSnapshot, ResponseSnapshot } from "./schema"
import { decodeJson } from "./matching.js"
import { REDACTED, redactHeaders, redactUrl } from "./redaction.js"
import type { RedactOptions, RequestSnapshot, ResponseSnapshot } from "./types.js"
export type { RedactOptions } from "./types.js"
export const DEFAULT_REQUEST_HEADERS: ReadonlyArray<string> = ["content-type", "accept", "openai-beta"]
export const DEFAULT_RESPONSE_HEADERS: ReadonlyArray<string> = ["content-type"]
@@ -67,6 +69,63 @@ export interface DefaultRedactorOverrides {
readonly body?: (parsed: unknown) => unknown
}
const DEFAULT_REDACT_JSON_FIELDS = [
"access_token",
"api_key",
"apikey",
"client_secret",
"password",
"refresh_token",
"secret",
"token",
]
const normalizeField = (field: string) => field.replace(/[^a-z0-9]/gi, "").toLowerCase()
const redactJsonFields = (value: unknown, fields: ReadonlySet<string>): unknown => {
if (Array.isArray(value)) return value.map((item) => redactJsonFields(item, fields))
if (!value || typeof value !== "object") return value
return Object.fromEntries(
Object.entries(value).map(([key, child]) => [
key,
fields.has(normalizeField(key)) ? REDACTED : redactJsonFields(child, fields),
]),
)
}
const redactBody = (value: string, fields: ReadonlySet<string>, transform: ((body: string) => string) | undefined) => {
const redacted = Option.match(decodeJson(value), {
onNone: () => value,
onSome: (parsed) => JSON.stringify(redactJsonFields(parsed, fields)),
})
return transform?.(redacted) ?? redacted
}
export const make = (options: RedactOptions = {}): Redactor => {
const fields = new Set([...DEFAULT_REDACT_JSON_FIELDS, ...(options.jsonFields ?? [])].map(normalizeField))
return compose(
requestHeaders({
allow: [...DEFAULT_REQUEST_HEADERS, ...(options.allowRequestHeaders ?? []), ...(options.headers ?? [])],
redact: options.headers,
}),
responseHeaders({
allow: [...DEFAULT_RESPONSE_HEADERS, ...(options.allowResponseHeaders ?? []), ...(options.headers ?? [])],
redact: options.headers,
}),
url({ query: options.queryParameters, transform: options.url }),
{
request: (snapshot) => ({
...snapshot,
body: redactBody(snapshot.body, fields, options.body),
}),
response: (snapshot) => ({
...snapshot,
body: redactBody(snapshot.body, fields, options.body),
}),
},
)
}
export const defaults = (overrides: DefaultRedactorOverrides = {}): Redactor =>
compose(
requestHeaders(overrides.requestHeaders),