Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import type { ProviderReplayTranscript } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";

import { classifyClaudeNativeTool } from "../Adapters/ClaudeAdapterV2.ts";
import {
Expand All @@ -27,12 +26,10 @@ import {
THREAD_MERGE_BACK_SOURCE_MARKER,
} from "./fixtures/shared.ts";
import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts";
import { decodeProviderReplayNdjson } from "./ReplayTranscriptNdjson.ts";
import { readProviderReplayTranscript } from "./ReplayTranscriptNdjson.ts";

const readTranscript = Effect.fn("readClaudeReplayFixture")(function* (file: URL) {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(decodeURIComponent(file.pathname));
return yield* decodeProviderReplayNdjson(text);
return yield* readProviderReplayTranscript(file);
}, Effect.provide(NodeServices.layer));

function claudeFixture(name: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
import type { ProviderReplayTranscript } from "@t3tools/contracts";
import * as CodexReplay from "effect-codex-app-server/replay";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Schema from "effect/Schema";

import { ORCHESTRATOR_REPLAY_FIXTURES } from "./fixtures/index.ts";
Expand All @@ -19,7 +18,7 @@ import {
THREAD_MERGE_BACK_SIBLINGS_SOURCE_MARKER,
THREAD_MERGE_BACK_SOURCE_MARKER,
} from "./fixtures/shared.ts";
import { decodeProviderReplayNdjson } from "./ReplayTranscriptNdjson.ts";
import { readProviderReplayTranscript } from "./ReplayTranscriptNdjson.ts";

const PROVIDER_THREAD_RESUME_FIRST_FINAL = "provider thread resume fixture first turn complete";
const PROVIDER_THREAD_RESUME_SECOND_FINAL = "provider thread resume fixture second turn complete";
Expand Down Expand Up @@ -327,9 +326,7 @@ const decodeCodexTranscript = Schema.decodeUnknownEffect(
CodexReplay.CodexAppServerReplayTranscript,
);
const readTranscript = Effect.fn("readCodexReplayFixture")(function* (file: URL) {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(decodeURIComponent(file.pathname));
return yield* decodeProviderReplayNdjson(text);
return yield* readProviderReplayTranscript(file);
}, Effect.provide(NodeServices.layer));

function labels(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { assert, describe, it } from "@effect/vitest";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { OrchestrationV2Command, ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Schema from "effect/Schema";

import { IdAllocatorV2, layer as idAllocatorLayer } from "../IdAllocator.ts";
Expand All @@ -13,13 +12,11 @@ import {
materializeFixtureInput,
type OrchestratorFixtureInput,
} from "./fixtures/shared.ts";
import { decodeProviderReplayNdjson } from "./ReplayTranscriptNdjson.ts";
import { readProviderReplayTranscript } from "./ReplayTranscriptNdjson.ts";

const decodeCommand = Schema.decodeUnknownEffect(OrchestrationV2Command);
const readTranscript = Effect.fn("readOrchestratorReplayContractTranscript")(function* (file: URL) {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(decodeURIComponent(file.pathname));
return yield* decodeProviderReplayNdjson(text);
return yield* readProviderReplayTranscript(file);
}, Effect.provide(NodeServices.layer));

function assertUnique(values: ReadonlyArray<string>, label: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import type { OrchestrationV2DomainEvent, ProviderReplayTranscript } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";

import { ClaudeOrchestratorReplayHarness } from "../Adapters/ClaudeAdapterV2.testkit.ts";
import { CodexOrchestratorReplayHarness } from "../Adapters/CodexAdapterV2.testkit.ts";
Expand All @@ -25,15 +24,13 @@ import {
} from "./ProviderReplayHarness.ts";
import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts";
import {
decodeProviderReplayNdjson,
materializeReplayTranscriptRuntimeInstructions,
materializeReplayTranscriptWorkspace,
readProviderReplayTranscript,
} from "./ReplayTranscriptNdjson.ts";

const readTranscript = Effect.fn("readOrchestratorReplayTranscript")(function* (file: URL) {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(decodeURIComponent(file.pathname));
return yield* decodeProviderReplayNdjson(text);
return yield* readProviderReplayTranscript(file);
}, Effect.provide(NodeServices.layer));

function normalizeTestError(cause: unknown): Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ import {
import { runOrchestratorV2ProviderReplayScenario } from "./ProviderReplayHarness.ts";
import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts";
import {
decodeProviderReplayNdjson,
materializeReplayTranscriptRuntimeInstructions,
materializeReplayTranscriptWorkspace,
readProviderReplayTranscript,
} from "./ReplayTranscriptNdjson.ts";

const FIRST_FINAL = "provider thread resume fixture first turn complete";
Expand All @@ -54,9 +54,7 @@ const decodeCodexTranscript = Schema.decodeUnknownEffect(
CodexReplay.CodexAppServerReplayTranscript,
);
const readRawTranscript = Effect.fn("readRecoveryTranscript")(function* (file: URL) {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(decodeURIComponent(file.pathname));
return yield* decodeProviderReplayNdjson(text);
return yield* readProviderReplayTranscript(file);
});
const readCodexTranscript = Effect.fn("readCodexRecoveryTranscript")(function* (workspace: string) {
const transcript = yield* readRawTranscript(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { ProviderReplayNdjsonParseError } from "./ReplayTranscriptNdjson.ts";
import * as NodePath from "@effect/platform-node/NodePath";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";

import {
decodeProviderReplayNdjson,
materializeReplayTranscriptWorkspace,
readProviderReplayTranscript,
} from "./ReplayTranscriptNdjson.ts";

const encodeParseError = Schema.encodeUnknownEffect(ProviderReplayNdjsonParseError);
Expand Down Expand Up @@ -96,3 +101,75 @@ describe("decodeProviderReplayNdjson", () => {
}),
);
});

const FILE_URL_TRANSCRIPT = `{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"file-url-read"}
{"type":"runtime_exit","status":"success"}
`;

it.layer(NodeServices.layer)("readProviderReplayTranscript", (it) => {
it.effect("loads a transcript behind a file URL containing an encoded space", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3 replay fixture " });
const filePath = path.join(dir, "transcript.ndjson");
yield* fs.writeFileString(filePath, FILE_URL_TRANSCRIPT);

const fileUrl = yield* path.toFileUrl(filePath);
assert.isTrue(fileUrl.pathname.includes("%20"));

const transcript = yield* readProviderReplayTranscript(fileUrl);
assert.equal(transcript.scenario, "file-url-read");
}),
);

it.effect("passes drive-letter and UNC file URLs through the Windows path service", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const requestedPaths: Array<string> = [];
const recordingFs = FileSystem.FileSystem.of({
...fs,
readFileString: (filePath: string) => {
requestedPaths.push(filePath);
return Effect.succeed(FILE_URL_TRANSCRIPT);
},
});
const readAsWindows = (file: URL) =>
readProviderReplayTranscript(file).pipe(
Effect.provideService(FileSystem.FileSystem, recordingFs),
Effect.provide(NodePath.layerWin32),
);

const driveLetter = yield* readAsWindows(
new URL("file:///C:/Users/dev/t3%20worktree/transcript.ndjson"),
);
const unc = yield* readAsWindows(new URL("file://fileserver/shared/transcript.ndjson"));

assert.equal(driveLetter.scenario, "file-url-read");
assert.equal(unc.scenario, "file-url-read");
assert.deepEqual(requestedPaths, [
"C:\\Users\\dev\\t3 worktree\\transcript.ndjson",
"\\\\fileserver\\shared\\transcript.ndjson",
]);
}),
);

it.effect("rejects non-file URLs instead of decoding their pathname", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const recordingFs = FileSystem.FileSystem.of({
...fs,
readFileString: () => Effect.succeed(FILE_URL_TRANSCRIPT),
});
const error = yield* readProviderReplayTranscript(
new URL("https://example.com/transcript.ndjson"),
).pipe(
Effect.provideService(FileSystem.FileSystem, recordingFs),
Effect.provide(NodePath.layerPosix),
Effect.flip,
);

assert.equal(error._tag, "BadArgument");
}),
);
});
16 changes: 16 additions & 0 deletions apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
type ProviderReplayTranscriptHeader,
} from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";

import { buildRuntimeInstructions } from "../../provider/RuntimeInstructions.ts";
Expand Down Expand Up @@ -230,3 +232,17 @@ export function decodeProviderReplayNdjson(
});
});
}

/**
* Reads a provider replay transcript from a `file:` URL and decodes it.
* Conversion goes through the `Path` service so drive-letter and UNC fixture
* URLs resolve to native paths on Windows instead of `/C:/...` pathname strings.
*/
export const readProviderReplayTranscript = Effect.fn("readProviderReplayTranscript")(function* (
file: URL,
) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const text = yield* fs.readFileString(yield* path.fromFileUrl(file));
return yield* decodeProviderReplayNdjson(text);
});
Loading