feat(http-recorder): sync recorder v0.3 (#35619)
This commit is contained in:
parent
05e8d5be73
commit
3dd29094f4
47 changed files with 2697 additions and 2207 deletions
|
|
@ -12,7 +12,6 @@ const openAI = OpenAI.configure({
|
|||
})
|
||||
const openAIChat = openAI.chat("gpt-4o-mini")
|
||||
const openAIResponses = openAI.responses("gpt-5.5")
|
||||
const openAIResponsesWebSocket = openAI.responsesWebSocket("gpt-4.1-mini")
|
||||
const anthropic = Anthropic.configure({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY ?? "fixture",
|
||||
})
|
||||
|
|
@ -89,14 +88,6 @@ describeRecordedGoldenScenarios([
|
|||
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "OpenAI Responses WebSocket gpt-4.1-mini",
|
||||
prefix: "openai-responses-websocket",
|
||||
model: openAIResponsesWebSocket,
|
||||
transport: "websocket",
|
||||
requires: ["OPENAI_API_KEY"],
|
||||
scenarios: ["tool-loop"],
|
||||
},
|
||||
{
|
||||
name: "Anthropic Haiku 4.5",
|
||||
prefix: "anthropic-messages",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ type TargetInput = {
|
|||
readonly transport?: Transport
|
||||
readonly prefix?: string
|
||||
readonly tags?: ReadonlyArray<string>
|
||||
readonly metadata?: Record<string, unknown>
|
||||
readonly metadata?: HttpRecorder.CassetteMetadata
|
||||
readonly options?: HttpRecorder.RecorderOptions
|
||||
readonly scenarios: ReadonlyArray<ScenarioInput>
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ const defaultPrefix = (target: TargetInput) => {
|
|||
|
||||
const metadata = (target: TargetInput) => ({
|
||||
provider: target.model.provider,
|
||||
protocol: target.protocol,
|
||||
...(target.protocol ? { protocol: target.protocol } : {}),
|
||||
route: target.model.route.id,
|
||||
transport: target.transport ?? "http",
|
||||
model: target.model.id,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { HttpRecorder } from "@opencode-ai/http-recorder"
|
||||
import { test, type TestOptions } from "bun:test"
|
||||
import { Effect, type Layer } from "effect"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
|
@ -11,7 +12,7 @@ export type RecordedGroupOptions = {
|
|||
readonly protocol?: string
|
||||
readonly requires?: ReadonlyArray<string>
|
||||
readonly tags?: ReadonlyArray<string>
|
||||
readonly metadata?: Record<string, unknown>
|
||||
readonly metadata?: HttpRecorder.CassetteMetadata
|
||||
}
|
||||
|
||||
export type RecordedCaseOptions = {
|
||||
|
|
@ -21,7 +22,7 @@ export type RecordedCaseOptions = {
|
|||
readonly protocol?: string
|
||||
readonly requires?: ReadonlyArray<string>
|
||||
readonly tags?: ReadonlyArray<string>
|
||||
readonly metadata?: Record<string, unknown>
|
||||
readonly metadata?: HttpRecorder.CassetteMetadata
|
||||
}
|
||||
|
||||
export const recordedEffectGroup = <
|
||||
|
|
@ -36,7 +37,7 @@ export const recordedEffectGroup = <
|
|||
readonly layer: (input: {
|
||||
readonly cassette: string
|
||||
readonly tags: ReadonlyArray<string>
|
||||
readonly metadata: Record<string, unknown>
|
||||
readonly metadata: HttpRecorder.CassetteMetadata
|
||||
readonly recording: boolean
|
||||
readonly options: Options
|
||||
readonly caseOptions: CaseOptions
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { HttpRecorder } from "@opencode-ai/http-recorder"
|
||||
import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
|
||||
import { Layer } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import * as path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { LLMClient, RequestExecutor } from "../src/route"
|
||||
import { LLMClient, RequestExecutor, WebSocketExecutor } from "../src/route"
|
||||
import type { Service as LLMClientService } from "../src/route/client"
|
||||
import type { Service as RequestExecutorService } from "../src/route/executor"
|
||||
import type { Service as WebSocketExecutorService } from "../src/route/transport/websocket"
|
||||
|
|
@ -14,7 +11,6 @@ import {
|
|||
type RecordedCaseOptions as RunnerCaseOptions,
|
||||
type RecordedGroupOptions,
|
||||
} from "./recorded-runner"
|
||||
import { webSocketCassetteLayer } from "./recorded-websocket"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const FIXTURES_DIR = path.resolve(__dirname, "fixtures", "recordings")
|
||||
|
|
@ -64,31 +60,27 @@ export const recordedTests = (options: RecordedTestsOptions) =>
|
|||
recordedEffectGroup<RecordedEnv, never, RecordedTestsOptions, RecordedCaseOptions>({
|
||||
duplicateLabel: "recorded cassette",
|
||||
options,
|
||||
cassetteExists: (cassette) => HttpRecorderInternal.hasCassetteSync(cassette, { directory: FIXTURES_DIR }),
|
||||
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,
|
||||
}
|
||||
const mode = recording ? "record" : "replay"
|
||||
const cassetteService = HttpRecorderInternal.Cassette.fileSystem({ directory: FIXTURES_DIR }).pipe(
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
)
|
||||
if (recording) {
|
||||
if (process.env.CI !== undefined) throw new Error("Unset CI before recording HTTP cassettes")
|
||||
HttpRecorder.removeCassetteSync(cassette, { directory: FIXTURES_DIR })
|
||||
}
|
||||
const requestExecutor = RequestExecutor.layer.pipe(
|
||||
Layer.provide(
|
||||
HttpRecorderInternal.recordingLayer(cassette, {
|
||||
mode,
|
||||
HttpRecorder.layerFetch(cassette, {
|
||||
...recorderOptions,
|
||||
directory: FIXTURES_DIR,
|
||||
metadata: recorderMetadata,
|
||||
redactor: HttpRecorderInternal.Redactor.make(recorderOptions?.redact),
|
||||
match: recorderOptions?.match,
|
||||
}).pipe(Layer.provide(FetchHttpClient.layer)),
|
||||
}),
|
||||
),
|
||||
)
|
||||
const deps = Layer.mergeAll(
|
||||
requestExecutor,
|
||||
webSocketCassetteLayer(cassette, { metadata: recorderMetadata, mode }),
|
||||
)
|
||||
return Layer.mergeAll(deps, LLMClient.layer.pipe(Layer.provide(deps))).pipe(Layer.provide(cassetteService))
|
||||
const deps = Layer.mergeAll(requestExecutor, WebSocketExecutor.layer)
|
||||
return Layer.mergeAll(deps, LLMClient.layer.pipe(Layer.provide(deps)))
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { WebSocketExecutor } from "../src/route"
|
||||
import type { Service as WebSocketExecutorService } from "../src/route/transport/websocket"
|
||||
|
||||
const liveWebSocket = WebSocketExecutor.open
|
||||
|
||||
export const webSocketCassetteLayer = (
|
||||
cassette: string,
|
||||
input: { readonly metadata?: Record<string, unknown>; readonly mode: HttpRecorderInternal.RecordReplayMode },
|
||||
): Layer.Layer<WebSocketExecutorService, never, HttpRecorderInternal.Cassette.Service> =>
|
||||
Layer.effect(
|
||||
WebSocketExecutor.Service,
|
||||
Effect.gen(function* () {
|
||||
const cassetteService = yield* HttpRecorderInternal.Cassette.Service
|
||||
const executor = yield* HttpRecorderInternal.makeWebSocketExecutor({
|
||||
name: cassette,
|
||||
mode: input.mode,
|
||||
metadata: input.metadata,
|
||||
cassette: cassetteService,
|
||||
live: { open: liveWebSocket },
|
||||
compareClientMessagesAsJson: true,
|
||||
})
|
||||
return WebSocketExecutor.Service.of(executor)
|
||||
}),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue