import * as NodeServices from "@effect/platform-node/NodeServices";import { CommandId, DEFAULT_PROVIDER_INTERACTION_MODE, MessageId, ProjectId, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, ProviderInstanceId, ThreadId, type ClientOrchestrationCommand, type UploadChatAttachment,} from "@t3tools/contracts";import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path"; import * as ServerConfig from "../config.ts";import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";import { canonicalizeClientCommandTimestamps, normalizeDispatchCommand } from "./Normalizer.ts"; const testLayer = Layer.mergeAll( ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-normalizer-test-" }), WorkspacePaths.layer,).pipe(Layer.provideMerge(NodeServices.layer)); const clientCreatedAt = "2031-01-01T00:00:00.000Z";const serverReceivedAt = "2026-07-18T00:00:00.000Z";const now = "2026-01-01T00:00:00.000Z"; const turnStartCommand = ( attachments: ReadonlyArray<UploadChatAttachment>,): ClientOrchestrationCommand => ({ type: "thread.turn.start", commandId: CommandId.make("cmd-turn-start-1"), threadId: ThreadId.make("thread-1"), message: { messageId: MessageId.make("user-message-1"), role: "user", text: "hello normalizer", attachments, }, runtimeMode: "full-access", interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, createdAt: now,}); const asDataUrl = (mimeType: string, bytes: Buffer) => `data:${mimeType};base64,${bytes.toString("base64")}`; describe("canonicalizeClientCommandTimestamps", () => { it("replaces a client command timestamp with the server receipt timestamp", () => { const command: ClientOrchestrationCommand = { type: "project.create", commandId: CommandId.make("command-1"), projectId: ProjectId.make("project-1"), title: "Clock-safe project", workspaceRoot: "/tmp/clock-safe-project", createdAt: clientCreatedAt, }; expect(canonicalizeClientCommandTimestamps(command, serverReceivedAt)).toEqual({ ...command, createdAt: serverReceivedAt, }); }); it("replaces both timestamps when the first turn bootstraps a thread", () => { const command: ClientOrchestrationCommand = { type: "thread.turn.start", commandId: CommandId.make("command-2"), threadId: ThreadId.make("thread-1"), message: { messageId: MessageId.make("message-1"), role: "user", text: "Start a thread", attachments: [], }, runtimeMode: "full-access", interactionMode: "default", bootstrap: { createThread: { projectId: ProjectId.make("project-1"), title: "Clock-safe thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, createdAt: clientCreatedAt, }, }, createdAt: clientCreatedAt, }; const result = canonicalizeClientCommandTimestamps(command, serverReceivedAt); expect(result.type).toBe("thread.turn.start"); if (result.type !== "thread.turn.start") { throw new Error("Expected a thread.turn.start command"); } expect(result.createdAt).toBe(serverReceivedAt); expect(result.bootstrap?.createThread?.createdAt).toBe(serverReceivedAt); });}); describe("Normalizer", () => { it.effect("persists file attachments and writes their bytes to the attachments dir", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const { attachmentsDir } = yield* ServerConfig.ServerConfig; const bytes = Buffer.from("hello world"); const normalized = yield* normalizeDispatchCommand( turnStartCommand([ { type: "file", name: "notes.txt", mimeType: "text/plain", sizeBytes: bytes.byteLength, dataUrl: asDataUrl("text/plain", bytes), }, ]), ); expect(normalized.type).toBe("thread.turn.start"); if (normalized.type !== "thread.turn.start") { return; } const attachment = normalized.message.attachments[0]; expect(attachment).toMatchObject({ type: "file", name: "notes.txt", mimeType: "text/plain", sizeBytes: bytes.byteLength, }); expect(attachment?.id).toMatch( /^thread-1-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, ); if (!attachment) { return; } const attachmentPath = path.join(attachmentsDir, `${attachment.id}.txt`); expect(yield* fileSystem.readFileString(attachmentPath)).toBe("hello world"); }).pipe(Effect.provide(testLayer)), ); it.effect("rejects image attachments with a non-image data URL mime type", () => Effect.gen(function* () { const error = yield* normalizeDispatchCommand( turnStartCommand([ { type: "image", name: "fake.png", mimeType: "image/png", sizeBytes: 5, dataUrl: asDataUrl("text/plain", Buffer.from("hello")), }, ]), ).pipe(Effect.flip); expect(error._tag).toBe("OrchestrationDispatchCommandError"); expect(error.message).toBe("Invalid image attachment payload for 'fake.png'."); }).pipe(Effect.provide(testLayer)), ); it.effect("rejects file attachments over the maximum attachment size", () => Effect.gen(function* () { const bytes = Buffer.alloc(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES + 1, "a"); const error = yield* normalizeDispatchCommand( turnStartCommand([ { type: "file", name: "big.txt", mimeType: "text/plain", sizeBytes: PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, dataUrl: asDataUrl("text/plain", bytes), }, ]), ).pipe(Effect.flip); expect(error._tag).toBe("OrchestrationDispatchCommandError"); expect(error.message).toBe("Attachment 'big.txt' is empty or too large."); }).pipe(Effect.provide(testLayer)), );});
import * as NodeServices from "@effect/platform-node/NodeServices";
import {
CommandId,
DEFAULT_PROVIDER_INTERACTION_MODE,
MessageId,
ProjectId,
PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
ProviderInstanceId,
ThreadId,
type ClientOrchestrationCommand,
type UploadChatAttachment,
} from "@t3tools/contracts";
import { describe, expect, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as ServerConfig from "../config.ts";
import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
import { canonicalizeClientCommandTimestamps, normalizeDispatchCommand } from "./Normalizer.ts";
const testLayer = Layer.mergeAll(
ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-normalizer-test-" }),
WorkspacePaths.layer,
).pipe(Layer.provideMerge(NodeServices.layer));
const clientCreatedAt = "2031-01-01T00:00:00.000Z";
const serverReceivedAt = "2026-07-18T00:00:00.000Z";
const now = "2026-01-01T00:00:00.000Z";
const turnStartCommand = (
attachments: ReadonlyArray<UploadChatAttachment>,
): ClientOrchestrationCommand => ({
type: "thread.turn.start",
commandId: CommandId.make("cmd-turn-start-1"),
threadId: ThreadId.make("thread-1"),
message: {
messageId: MessageId.make("user-message-1"),
role: "user",
text: "hello normalizer",
attachments,
},
runtimeMode: "full-access",
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
createdAt: now,
});
const asDataUrl = (mimeType: string, bytes: Buffer) =>
`data:${mimeType};base64,${bytes.toString("base64")}`;
describe("canonicalizeClientCommandTimestamps", () => {
it("replaces a client command timestamp with the server receipt timestamp", () => {
const command: ClientOrchestrationCommand = {
type: "project.create",
commandId: CommandId.make("command-1"),
projectId: ProjectId.make("project-1"),
title: "Clock-safe project",
workspaceRoot: "/tmp/clock-safe-project",
createdAt: clientCreatedAt,
};
expect(canonicalizeClientCommandTimestamps(command, serverReceivedAt)).toEqual({
...command,
createdAt: serverReceivedAt,
});
});
it("replaces both timestamps when the first turn bootstraps a thread", () => {
const command: ClientOrchestrationCommand = {
type: "thread.turn.start",
commandId: CommandId.make("command-2"),
threadId: ThreadId.make("thread-1"),
message: {
messageId: MessageId.make("message-1"),
role: "user",
text: "Start a thread",
attachments: [],
},
runtimeMode: "full-access",
interactionMode: "default",
bootstrap: {
createThread: {
projectId: ProjectId.make("project-1"),
title: "Clock-safe thread",
modelSelection: {
instanceId: ProviderInstanceId.make("codex"),
model: "gpt-5.4",
},
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: null,
createdAt: clientCreatedAt,
},
},
createdAt: clientCreatedAt,
};
const result = canonicalizeClientCommandTimestamps(command, serverReceivedAt);
expect(result.type).toBe("thread.turn.start");
if (result.type !== "thread.turn.start") {
throw new Error("Expected a thread.turn.start command");
}
expect(result.createdAt).toBe(serverReceivedAt);
expect(result.bootstrap?.createThread?.createdAt).toBe(serverReceivedAt);
});
});
describe("Normalizer", () => {
it.effect("persists file attachments and writes their bytes to the attachments dir", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const { attachmentsDir } = yield* ServerConfig.ServerConfig;
const bytes = Buffer.from("hello world");
const normalized = yield* normalizeDispatchCommand(
turnStartCommand([
{
type: "file",
name: "notes.txt",
mimeType: "text/plain",
sizeBytes: bytes.byteLength,
dataUrl: asDataUrl("text/plain", bytes),
},
]),
);
expect(normalized.type).toBe("thread.turn.start");
if (normalized.type !== "thread.turn.start") {
return;
}
const attachment = normalized.message.attachments[0];
expect(attachment).toMatchObject({
type: "file",
name: "notes.txt",
mimeType: "text/plain",
sizeBytes: bytes.byteLength,
});
expect(attachment?.id).toMatch(
/^thread-1-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
);
if (!attachment) {
return;
}
const attachmentPath = path.join(attachmentsDir, `${attachment.id}.txt`);
expect(yield* fileSystem.readFileString(attachmentPath)).toBe("hello world");
}).pipe(Effect.provide(testLayer)),
);
it.effect("rejects image attachments with a non-image data URL mime type", () =>
Effect.gen(function* () {
const error = yield* normalizeDispatchCommand(
turnStartCommand([
{
type: "image",
name: "fake.png",
mimeType: "image/png",
sizeBytes: 5,
dataUrl: asDataUrl("text/plain", Buffer.from("hello")),
},
]),
).pipe(Effect.flip);
expect(error._tag).toBe("OrchestrationDispatchCommandError");
expect(error.message).toBe("Invalid image attachment payload for 'fake.png'.");
}).pipe(Effect.provide(testLayer)),
);
it.effect("rejects file attachments over the maximum attachment size", () =>
Effect.gen(function* () {
const bytes = Buffer.alloc(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES + 1, "a");
const error = yield* normalizeDispatchCommand(
turnStartCommand([
{
type: "file",
name: "big.txt",
mimeType: "text/plain",
sizeBytes: PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
dataUrl: asDataUrl("text/plain", bytes),
},
]),
).pipe(Effect.flip);
expect(error._tag).toBe("OrchestrationDispatchCommandError");
expect(error.message).toBe("Attachment 'big.txt' is empty or too large.");
}).pipe(Effect.provide(testLayer)),
);
});