mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 03:56:55 +03:00
124 lines
4.9 KiB
TypeScript
124 lines
4.9 KiB
TypeScript
import { HttpRecorder } from "@opencode/http-recorder"
|
|
import { NodeWS } from "@effect/platform-node"
|
|
import { Effect, Layer } from "effect"
|
|
import { Socket } from "effect/socket"
|
|
import * as path from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
import { LLMClient, RequestExecutor } from "../src/route.js"
|
|
import { ImageClient } from "../src/image-client.js"
|
|
import { VideoClient } from "../src/video-client.js"
|
|
import { SpeechClient } from "../src/speech-client.js"
|
|
import { TranscriptionClient } from "../src/transcription-client.js"
|
|
import { DecisionClient } from "../src/decision-client.js"
|
|
import type { Service as DecisionClientService } from "../src/decision-client.js"
|
|
import type { Service as ImageClientService } from "../src/image-client.js"
|
|
import type { Service as VideoClientService } from "../src/video-client.js"
|
|
import type { Service as SpeechClientService } from "../src/speech-client.js"
|
|
import type { Service as TranscriptionClientService } from "../src/transcription-client.js"
|
|
import type { Service as LLMClientService } from "../src/route/client.js"
|
|
import type { Service as RequestExecutorService } from "../src/route/executor.js"
|
|
import {
|
|
recordedEffectGroup,
|
|
type RecordedCaseOptions as RunnerCaseOptions,
|
|
type RecordedGroupOptions,
|
|
} from "./recorded-runner.js"
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const FIXTURES_DIR = path.resolve(__dirname, "fixtures", "recordings")
|
|
|
|
type RecordedEnv =
|
|
| RequestExecutorService
|
|
| LLMClientService
|
|
| ImageClientService
|
|
| VideoClientService
|
|
| SpeechClientService
|
|
| TranscriptionClientService
|
|
| DecisionClientService
|
|
| Socket.WebSocketConstructor
|
|
|
|
type RecordedTestsOptions = RecordedGroupOptions & {
|
|
readonly options?: HttpRecorder.RecorderOptions
|
|
}
|
|
|
|
type RecordedCaseOptions = RunnerCaseOptions & {
|
|
readonly options?: HttpRecorder.RecorderOptions
|
|
}
|
|
|
|
const mergeOptions = (
|
|
base: HttpRecorder.RecorderOptions | undefined,
|
|
override: HttpRecorder.RecorderOptions | undefined,
|
|
) => {
|
|
if (!base) return override
|
|
if (!override) return base
|
|
return {
|
|
...base,
|
|
...override,
|
|
metadata: base.metadata || override.metadata ? { ...base.metadata, ...override.metadata } : undefined,
|
|
redact:
|
|
base.redact || override.redact
|
|
? {
|
|
...base.redact,
|
|
...override.redact,
|
|
headers: [...(base.redact?.headers ?? []), ...(override.redact?.headers ?? [])],
|
|
allowRequestHeaders: [
|
|
...(base.redact?.allowRequestHeaders ?? []),
|
|
...(override.redact?.allowRequestHeaders ?? []),
|
|
],
|
|
allowResponseHeaders: [
|
|
...(base.redact?.allowResponseHeaders ?? []),
|
|
...(override.redact?.allowResponseHeaders ?? []),
|
|
],
|
|
queryParameters: [...(base.redact?.queryParameters ?? []), ...(override.redact?.queryParameters ?? [])],
|
|
jsonFields: [...(base.redact?.jsonFields ?? []), ...(override.redact?.jsonFields ?? [])],
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
export const recordedTests = (options: RecordedTestsOptions) =>
|
|
recordedEffectGroup<RecordedEnv, never, RecordedTestsOptions, RecordedCaseOptions>({
|
|
duplicateLabel: "recorded cassette",
|
|
options,
|
|
cassetteExists: (cassette) => HttpRecorder.hasCassetteSync(cassette, { directory: FIXTURES_DIR }),
|
|
layer: ({ cassette, metadata, options, caseOptions, recording }) => {
|
|
const recorderOptions = mergeOptions(options.options, caseOptions.options)
|
|
const recorderMetadata = {
|
|
...recorderOptions?.metadata,
|
|
...metadata,
|
|
}
|
|
if (recording && process.env.CI !== undefined) throw new Error("Unset CI before recording cassettes")
|
|
const requestExecutor = RequestExecutor.layer.pipe(
|
|
Layer.provide(
|
|
HttpRecorder.layerFetch(cassette, {
|
|
...recorderOptions,
|
|
directory: FIXTURES_DIR,
|
|
metadata: recorderMetadata,
|
|
}),
|
|
),
|
|
)
|
|
const webSocket = HttpRecorder.layerWebSocketConstructor(cassette, {
|
|
...recorderOptions,
|
|
directory: FIXTURES_DIR,
|
|
metadata: recorderMetadata,
|
|
}).pipe(Layer.provide(NodeWS.layerWebSocketConstructor))
|
|
const layer = Layer.mergeAll(
|
|
requestExecutor,
|
|
LLMClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
ImageClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
VideoClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
SpeechClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
TranscriptionClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
DecisionClient.layer.pipe(Layer.provide(requestExecutor)),
|
|
webSocket,
|
|
)
|
|
if (!recording) return layer
|
|
// Remove the cassette when the test runs, not when it is registered, so tests filtered out by `-t` keep theirs.
|
|
return Layer.unwrap(
|
|
Effect.sync(() => {
|
|
HttpRecorder.removeCassetteSync(cassette, { directory: FIXTURES_DIR })
|
|
return layer
|
|
}),
|
|
)
|
|
},
|
|
})
|