From ceb8e2e603dd2edaca5ea7801170bc5f7b053d25 Mon Sep 17 00:00:00 2001 From: lforst <8118419+lforst@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:18:56 +0000 Subject: [PATCH 1/3] ref!: Remove Zod derived types from public SDK declarations --- AGENTS.md | 15 +++ js/src/eval-parameters.ts | 16 ++- js/src/exports.ts | 7 +- js/src/framework-types.ts | 2 +- js/src/framework.ts | 14 +-- js/src/framework2.ts | 24 ++-- js/src/functions/invoke.ts | 14 +-- js/src/functions/stream.ts | 87 ++++++++------ js/src/gitutil.ts | 2 +- js/src/graph-framework.ts | 2 +- js/src/isomorph.ts | 2 +- js/src/logger.ts | 111 ++++++++++++------ js/src/prompt-schemas.ts | 59 +++++++--- js/src/public-types.test.ts | 69 +++++++++++ js/src/sandbox.ts | 2 +- .../api-compatibility.test.ts | 54 +++++++++ js/util/git_fields.ts | 2 +- js/util/object.ts | 2 +- js/util/span_identifier_v3.ts | 35 +++++- js/util/span_identifier_v4.ts | 9 +- 20 files changed, 398 insertions(+), 130 deletions(-) create mode 100644 js/src/public-types.test.ts diff --git a/AGENTS.md b/AGENTS.md index 6338e8830..9b8a6bc9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,21 @@ mise install # Install toolchain and dependencies pnpm run build # Build all workspace packages (from repo root) ``` +## Public TypeScript APIs + +Do not derive publicly exposed TypeScript types from Zod schemas (for example, +with `z.infer`, `z.input`, `z.output`, or equivalent schema-derived aliases). +Define public API types explicitly with interfaces, type aliases, or generated +plain types. When exporting a runtime validator, give it a compact public type +such as `z.ZodType` and test that the validator and public type stay +in sync. + +Zod-derived public declarations can expand into large schema implementation +graphs. Those declarations are expensive for downstream TypeScript consumers to +parse, instantiate, and type-check, increasing compile time, declaration size, +and memory usage. They also expose validation-library implementation details as +part of the SDK's API surface. + ## Instrumentation Use the normal Orchestrion config plus plugin/channel path by default. Special-case source patches should be rare exceptions only when the target SDK cannot be instrumented through the standard transformer path, and the reason should be documented next to the patch. diff --git a/js/src/eval-parameters.ts b/js/src/eval-parameters.ts index 1154fcfb8..b2c40e45c 100644 --- a/js/src/eval-parameters.ts +++ b/js/src/eval-parameters.ts @@ -4,6 +4,7 @@ import { Prompt, RemoteEvalParameters } from "./logger"; import { promptDefinitionWithToolsSchema, promptDefinitionToPromptData, + type PromptDefinitionWithTools, } from "./prompt-schemas"; import { PromptData as promptDataSchema } from "./generated_types"; @@ -25,7 +26,20 @@ export const evalParametersSchema = z.record( ]), ); -export type EvalParameters = z.infer; +export type EvalParameters = Record< + string, + | { + type: "prompt"; + default?: PromptDefinitionWithTools; + description?: string; + } + | { + type: "model"; + default?: string; + description?: string; + } + | z.ZodTypeAny +>; // Type helper to infer the type of a parameter value type InferParameterValue = T extends { type: "prompt" } diff --git a/js/src/exports.ts b/js/src/exports.ts index 9ed496ceb..e8286a959 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -1,3 +1,7 @@ +import type { z } from "zod/v3"; +import { AttachmentReference as attachmentReferenceSchema } from "./generated_types"; +import type { AttachmentReferenceType } from "./generated_plain_types"; + export type { AnyDataset, AttachmentParams, @@ -322,7 +326,8 @@ export type { export { addAzureBlobHeaders, LazyValue } from "./util"; -export { AttachmentReference } from "./generated_types"; +export const AttachmentReference: z.ZodType = + attachmentReferenceSchema; export type { EvalParameters } from "./eval-parameters"; diff --git a/js/src/framework-types.ts b/js/src/framework-types.ts index b5183cc7d..d98fa2cf1 100644 --- a/js/src/framework-types.ts +++ b/js/src/framework-types.ts @@ -1,4 +1,4 @@ -import { type IfExistsType as IfExists } from "./generated_types"; +import { type IfExistsType as IfExists } from "./generated_plain_types"; export type GenericFunction = | ((input: Input) => Output) diff --git a/js/src/framework.ts b/js/src/framework.ts index 9a463533b..540b95dc6 100644 --- a/js/src/framework.ts +++ b/js/src/framework.ts @@ -7,13 +7,13 @@ import { SpanTypeAttribute, spanObjectTypeV3ToTypedString, } from "../util/index"; -import { - type GitMetadataSettingsType as GitMetadataSettings, - ObjectReference as ObjectReferenceSchema, - type ObjectReferenceType as ObjectReference, - type RepoInfoType as RepoInfo, - type SSEProgressEventDataType as SSEProgressEventData, -} from "./generated_types"; +import { ObjectReference as ObjectReferenceSchema } from "./generated_types"; +import type { + GitMetadataSettingsType as GitMetadataSettings, + ObjectReferenceType as ObjectReference, + RepoInfoType as RepoInfo, + SSEProgressEventDataType as SSEProgressEventData, +} from "./generated_plain_types"; import { queue } from "async"; import iso from "./isomorph"; diff --git a/js/src/framework2.ts b/js/src/framework2.ts index f9220b6e1..ab4ee52c5 100644 --- a/js/src/framework2.ts +++ b/js/src/framework2.ts @@ -3,17 +3,17 @@ import type { Trace } from "./trace"; import iso from "./isomorph"; import { slugify } from "../util/string_util"; import { z } from "zod/v3"; -import { - type FunctionTypeEnumType as FunctionType, - type IfExistsType as IfExists, - type SavedFunctionIdType as SavedFunctionId, - type PromptBlockDataType as PromptBlockData, - type PromptDataType as PromptData, - type ToolFunctionDefinitionType as ToolFunctionDefinition, - FunctionData as functionDataSchema, - Project as projectSchema, - type ExtendedSavedFunctionIdType as ExtendedSavedFunctionId, -} from "./generated_types"; +import { Project as projectSchema } from "./generated_types"; +import type { + FunctionTypeEnumType as FunctionType, + IfExistsType as IfExists, + SavedFunctionIdType as SavedFunctionId, + PromptBlockDataType as PromptBlockData, + PromptDataType as PromptData, + ToolFunctionDefinitionType as ToolFunctionDefinition, + ExtendedSavedFunctionIdType as ExtendedSavedFunctionId, + FunctionDataType, +} from "./generated_plain_types"; import { loadPrettyXact, TransactionId } from "../util/index"; import { _internalGetGlobalState, @@ -782,7 +782,7 @@ interface FunctionEvent { name: string; description: string; prompt_data?: PromptData; - function_data: z.infer; + function_data: FunctionDataType; function_type?: FunctionType; if_exists?: IfExists; tags?: string[]; diff --git a/js/src/functions/invoke.ts b/js/src/functions/invoke.ts index f7bbb35f5..82fd40de4 100644 --- a/js/src/functions/invoke.ts +++ b/js/src/functions/invoke.ts @@ -1,10 +1,10 @@ -import { - FunctionId as functionIdSchema, - type InvokeFunctionType as InvokeFunctionRequest, - type ChatCompletionMessageParamType as Message, - type StreamingModeType as StreamingMode, - type FunctionTypeEnumType as FunctionType, -} from "../generated_types"; +import { FunctionId as functionIdSchema } from "../generated_types"; +import type { + InvokeFunctionType as InvokeFunctionRequest, + ChatCompletionMessageParamType as Message, + StreamingModeType as StreamingMode, + FunctionTypeEnumType as FunctionType, +} from "../generated_plain_types"; import { _internalGetGlobalState, BraintrustState, diff --git a/js/src/functions/stream.ts b/js/src/functions/stream.ts index 300895075..26dd64acc 100644 --- a/js/src/functions/stream.ts +++ b/js/src/functions/stream.ts @@ -1,9 +1,13 @@ import { - type CallEventType as CallEventSchema, CallEvent as callEventSchema, SSEConsoleEventData as sseConsoleEventDataSchema, SSEProgressEventData as sseProgressEventDataSchema, } from "../generated_types"; +import type { + CallEventType as CallEventSchema, + SSEConsoleEventDataType, + SSEProgressEventDataType, +} from "../generated_plain_types"; import { createParser, EventSourceParser, @@ -12,46 +16,55 @@ import { } from "eventsource-parser"; import { z } from "zod/v3"; -export const braintrustStreamChunkSchema = z.union([ - z.object({ - type: z.literal("text_delta"), - data: z.string(), - }), - z.object({ - type: z.literal("reasoning_delta"), - data: z.string(), - }), - z.object({ - type: z.literal("json_delta"), - data: z.string(), - }), - z.object({ - type: z.literal("error"), - data: z.string(), - }), - z.object({ - type: z.literal("console"), - data: sseConsoleEventDataSchema, - }), - z.object({ - type: z.literal("progress"), - data: sseProgressEventDataSchema, - }), - z.object({ - type: z.literal("start"), - data: z.string(), - }), - z.object({ - type: z.literal("done"), - data: z.string(), - }), -]); - /** * A chunk of data from a Braintrust stream. Each chunk type matches * an SSE event type. */ -export type BraintrustStreamChunk = z.infer; +export type BraintrustStreamChunk = + | { type: "text_delta"; data: string } + | { type: "reasoning_delta"; data: string } + | { type: "json_delta"; data: string } + | { type: "error"; data: string } + | { type: "console"; data: SSEConsoleEventDataType } + | { type: "progress"; data: SSEProgressEventDataType } + | { type: "start"; data: string } + | { type: "done"; data: string }; + +export const braintrustStreamChunkSchema: z.ZodType = + z.union([ + z.object({ + type: z.literal("text_delta"), + data: z.string(), + }), + z.object({ + type: z.literal("reasoning_delta"), + data: z.string(), + }), + z.object({ + type: z.literal("json_delta"), + data: z.string(), + }), + z.object({ + type: z.literal("error"), + data: z.string(), + }), + z.object({ + type: z.literal("console"), + data: sseConsoleEventDataSchema, + }), + z.object({ + type: z.literal("progress"), + data: sseProgressEventDataSchema, + }), + z.object({ + type: z.literal("start"), + data: z.string(), + }), + z.object({ + type: z.literal("done"), + data: z.string(), + }), + ]); /** * A Braintrust stream. This is a wrapper around a ReadableStream of `BraintrustStreamChunk`, diff --git a/js/src/gitutil.ts b/js/src/gitutil.ts index 8e318ac98..2883131ba 100644 --- a/js/src/gitutil.ts +++ b/js/src/gitutil.ts @@ -1,7 +1,7 @@ import { type GitMetadataSettingsType as GitMetadataSettings, type RepoInfoType as RepoInfo, -} from "./generated_types"; +} from "./generated_plain_types"; import { debugLogger } from "./debug-logger"; import { runGitCommand } from "./git-command"; diff --git a/js/src/graph-framework.ts b/js/src/graph-framework.ts index f60ff3fba..f432ab541 100644 --- a/js/src/graph-framework.ts +++ b/js/src/graph-framework.ts @@ -5,7 +5,7 @@ import { type GraphNodeType as GraphNode, type GraphEdgeType as GraphEdge, type PromptBlockDataType as PromptBlockData, -} from "./generated_types"; +} from "./generated_plain_types"; export interface BuildContext { getFunctionId(functionObj: unknown): Promise; diff --git a/js/src/isomorph.ts b/js/src/isomorph.ts index d23ddcc7e..9a7b7e96c 100644 --- a/js/src/isomorph.ts +++ b/js/src/isomorph.ts @@ -1,7 +1,7 @@ import { type GitMetadataSettingsType as GitMetadataSettings, type RepoInfoType as RepoInfo, -} from "./generated_types"; +} from "./generated_plain_types"; import { newGlobalTracingChannel, type GlobalHookAsyncLocalStorage, diff --git a/js/src/logger.ts b/js/src/logger.ts index 9733c3030..8d29e1a17 100644 --- a/js/src/logger.ts +++ b/js/src/logger.ts @@ -66,37 +66,39 @@ import { getObjValueByPath, } from "./util"; import { - type AnyModelParamsType as AnyModelParam, AttachmentReference as attachmentReferenceSchema, - type AttachmentReferenceType as AttachmentReference, BraintrustAttachmentReference as BraintrustAttachmentReferenceSchema, - type BraintrustAttachmentReferenceType as BraintrustAttachmentReference, BraintrustModelParams as braintrustModelParamsSchema, ChatCompletionTool as chatCompletionToolSchema, - type ChatCompletionToolType as ChatCompletionTool, ExternalAttachmentReference as ExternalAttachmentReferenceSchema, - type ExternalAttachmentReferenceType as ExternalAttachmentReference, - type ModelParamsType as ModelParams, ResponseFormatJsonSchema as responseFormatJsonSchemaSchema, AttachmentStatus as attachmentStatusSchema, - type AttachmentStatusType as AttachmentStatus, GitMetadataSettings as gitMetadataSettingsSchema, - type GitMetadataSettingsType as GitMetadataSettings, - type ChatCompletionMessageParamType as Message, - type ChatCompletionOpenAIMessageParamType as OpenAIMessage, DatasetSnapshot as datasetSnapshotSchema, - type DatasetSnapshotType as DatasetSnapshot, PromptData as promptDataSchema, - type PromptDataType as PromptData, Prompt as promptSchema, - type PromptType as PromptRow, - type PromptSessionEventType as PromptSessionEvent, - type RepoInfoType as RepoInfo, - type ObjectReferenceType as ObjectReference, - type PromptBlockDataType as PromptBlockData, - type ResponseFormatJsonSchemaType as ResponseFormatJsonSchema, - type ObjectReferenceType, } from "./generated_types"; +import type { + AnyModelParamsType as AnyModelParam, + AttachmentReferenceType as AttachmentReference, + BraintrustAttachmentReferenceType as BraintrustAttachmentReference, + ChatCompletionToolType as ChatCompletionTool, + ExternalAttachmentReferenceType as ExternalAttachmentReference, + ModelParamsType as ModelParams, + AttachmentStatusType as AttachmentStatus, + GitMetadataSettingsType as GitMetadataSettings, + ChatCompletionMessageParamType as Message, + ChatCompletionOpenAIMessageParamType as OpenAIMessage, + DatasetSnapshotType as DatasetSnapshot, + PromptDataType as PromptData, + PromptType as PromptRow, + PromptSessionEventType as PromptSessionEvent, + RepoInfoType as RepoInfo, + ObjectReferenceType as ObjectReference, + ObjectReferenceType, + PromptBlockDataType as PromptBlockData, + ResponseFormatJsonSchemaType as ResponseFormatJsonSchema, +} from "./generated_plain_types"; const BRAINTRUST_ATTACHMENT = BraintrustAttachmentReferenceSchema.shape.type.value; @@ -120,16 +122,21 @@ const datasetRestorePreviewResultSchema = z.object({ rows_to_restore: z.number(), rows_to_delete: z.number(), }); -export type DatasetRestorePreviewResult = z.infer< - typeof datasetRestorePreviewResultSchema ->; +export type DatasetRestorePreviewResult = { + rows_to_restore: number; + rows_to_delete: number; +}; const datasetRestoreResultSchema = z.object({ xact_id: z.string().nullable(), rows_restored: z.number(), rows_deleted: z.number(), }); -export type DatasetRestoreResult = z.infer; +export type DatasetRestoreResult = { + xact_id: string | null; + rows_restored: number; + rows_deleted: number; +}; const parametersRowSchema = z.object({ id: z.string().uuid(), @@ -148,7 +155,21 @@ const parametersRowSchema = z.object({ .union([z.object({}).partial().passthrough(), z.null()]) .optional(), }); -type ParametersRow = z.infer; +type ParametersRow = { + id: string; + _xact_id: string; + project_id: string; + name: string; + slug: string; + description?: string | null; + function_type: "parameters"; + function_data: { + type: "parameters"; + data?: Record; + __schema: Record; + }; + metadata?: Record | null; +}; import { waitUntil } from "@vercel/functions"; import { @@ -714,7 +735,18 @@ const loginSchema = z.strictObject({ debugLogLevelDisabled: z.boolean().optional(), }); -export type SerializedBraintrustState = z.infer; +export type SerializedBraintrustState = { + appUrl: string; + appPublicUrl: string; + orgName: string; + apiUrl: string; + proxyUrl: string; + loginToken: string; + orgId?: string | null; + gitMetadataSettings?: GitMetadataSettings | null; + debugLogLevel?: "error" | "warn" | "info" | "debug"; + debugLogLevelDisabled?: boolean; +}; let stateNonce = 0; @@ -1863,7 +1895,10 @@ const attachmentMetadataSchema = z.object({ status: attachmentStatusSchema, }); -type AttachmentMetadata = z.infer; +type AttachmentMetadata = { + downloadUrl: string; + status: AttachmentStatus; +}; /** * A readonly alternative to `Attachment`, which can be used for fetching @@ -2784,14 +2819,22 @@ function castLogger( return logger as unknown as Logger; } -export const logs3OverflowUploadSchema = z.object({ - method: z.enum(["PUT", "POST"]), - signedUrl: z.string().url(), - headers: z.record(z.string()).optional(), - fields: z.record(z.string()).optional(), - key: z.string().min(1), -}); -export type Logs3OverflowUpload = z.infer; +export type Logs3OverflowUpload = { + method: "PUT" | "POST"; + signedUrl: string; + headers?: Record; + fields?: Record; + key: string; +}; + +export const logs3OverflowUploadSchema: z.ZodType = + z.object({ + method: z.enum(["PUT", "POST"]), + signedUrl: z.string().url(), + headers: z.record(z.string()).optional(), + fields: z.record(z.string()).optional(), + key: z.string().min(1), + }); export type Logs3OverflowInputRow = { object_ids: Record; diff --git a/js/src/prompt-schemas.ts b/js/src/prompt-schemas.ts index c4203f90e..30fc79672 100644 --- a/js/src/prompt-schemas.ts +++ b/js/src/prompt-schemas.ts @@ -1,15 +1,23 @@ import { z } from "zod/v3"; import { ToolFunctionDefinition as toolFunctionDefinitionSchema, - type ToolFunctionDefinitionType as ToolFunctionDefinition, ChatCompletionMessageParam as chatCompletionMessageParamSchema, ModelParams as modelParamsSchema, - type PromptBlockDataType as PromptBlockData, - type PromptDataType as PromptData, } from "./generated_types"; +import type { + ToolFunctionDefinitionType as ToolFunctionDefinition, + ChatCompletionMessageParamType, + ModelParamsType, + PromptBlockDataType as PromptBlockData, + PromptDataType as PromptData, +} from "./generated_plain_types"; // This roughly maps to promptBlockDataSchema, but is more ergonomic for the user. -export const promptContentsSchema = z.union([ +export type PromptContents = + | { prompt: string } + | { messages: ChatCompletionMessageParamType[] }; + +const promptContentsSchemaInternal = z.union([ z.object({ prompt: z.string(), }), @@ -17,10 +25,20 @@ export const promptContentsSchema = z.union([ messages: z.array(chatCompletionMessageParamSchema), }), ]); +export const promptContentsSchema: z.ZodType< + PromptContents, + z.ZodTypeDef, + unknown +> = promptContentsSchemaInternal; -export type PromptContents = z.infer; +export type PromptDefinition = PromptContents & { + model: string; + params?: ModelParamsType; + templateFormat?: "mustache" | "nunjucks" | "none"; + environments?: string[]; +}; -export const promptDefinitionSchema = promptContentsSchema.and( +const promptDefinitionSchemaInternal = promptContentsSchemaInternal.and( z.object({ model: z.string(), params: modelParamsSchema.optional(), @@ -28,18 +46,27 @@ export const promptDefinitionSchema = promptContentsSchema.and( environments: z.array(z.string()).optional(), }), ); +export const promptDefinitionSchema: z.ZodType< + PromptDefinition, + z.ZodTypeDef, + unknown +> = promptDefinitionSchemaInternal; -export type PromptDefinition = z.infer; - -export const promptDefinitionWithToolsSchema = promptDefinitionSchema.and( - z.object({ - tools: z.array(toolFunctionDefinitionSchema).optional(), - }), -); +export type PromptDefinitionWithTools = PromptDefinition & { + tools?: ToolFunctionDefinition[]; +}; -export type PromptDefinitionWithTools = z.infer< - typeof promptDefinitionWithToolsSchema ->; +const promptDefinitionWithToolsSchemaInternal = + promptDefinitionSchemaInternal.and( + z.object({ + tools: z.array(toolFunctionDefinitionSchema).optional(), + }), + ); +export const promptDefinitionWithToolsSchema: z.ZodType< + PromptDefinitionWithTools, + z.ZodTypeDef, + unknown +> = promptDefinitionWithToolsSchemaInternal; export function promptDefinitionToPromptData( promptDefinition: PromptDefinition, diff --git a/js/src/public-types.test.ts b/js/src/public-types.test.ts new file mode 100644 index 000000000..0583994df --- /dev/null +++ b/js/src/public-types.test.ts @@ -0,0 +1,69 @@ +import { expectTypeOf, test } from "vitest"; +import { z } from "zod/v3"; + +import { + AttachmentReference, + braintrustStreamChunkSchema, + logs3OverflowUploadSchema, + promptContentsSchema, + promptDefinitionSchema, + promptDefinitionWithToolsSchema, + type BraintrustStreamChunk, + type EvalParameters, + type Logs3OverflowUpload, + type PromptContents, + type PromptDefinition, + type PromptDefinitionWithTools, +} from "./exports"; +import type { InferParameters } from "./eval-parameters"; +import type { AttachmentReferenceType } from "./generated_plain_types"; +import type { Prompt } from "./logger"; +import { + spanComponentsV3Schema, + type SpanComponentsV3Data, +} from "../util/span_identifier_v3"; +import { + spanComponentsV4Schema, + type SpanComponentsV4Data, +} from "../util/span_identifier_v4"; + +test("exported validators preserve their public output types", () => { + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); + expectTypeOf< + z.infer + >().toEqualTypeOf(); +}); + +test("evaluation parameters retain custom schema inference", () => { + const parameters = { + subject: z.string(), + model: { type: "model" as const }, + prompt: { type: "prompt" as const }, + } satisfies EvalParameters; + + expectTypeOf>().toEqualTypeOf<{ + subject: string; + model: string; + prompt: Prompt; + }>(); +}); diff --git a/js/src/sandbox.ts b/js/src/sandbox.ts index 8517c801d..542c4a57f 100644 --- a/js/src/sandbox.ts +++ b/js/src/sandbox.ts @@ -1,6 +1,6 @@ import { z } from "zod/v3"; import { slugify } from "../util/string_util"; -import { type IfExistsType } from "./generated_types"; +import { type IfExistsType } from "./generated_plain_types"; import { type BraintrustState, _internalGetGlobalState } from "./logger"; /** diff --git a/js/tests/api-compatibility/api-compatibility.test.ts b/js/tests/api-compatibility/api-compatibility.test.ts index f11e3d847..d33438ef1 100644 --- a/js/tests/api-compatibility/api-compatibility.test.ts +++ b/js/tests/api-compatibility/api-compatibility.test.ts @@ -2948,6 +2948,60 @@ describe("API Compatibility", () => { expect(fs.existsSync(path.join(tempDir, "package"))).toBe(true); }); + test("keeps public declarations free of expanded Zod schema graphs", () => { + const publicDeclarationPaths = [ + "dist/index.d.ts", + "dist/browser.d.ts", + "util/dist/index.d.ts", + ]; + + for (const declarationTypesPath of publicDeclarationPaths) { + const declarationPath = path.join( + __dirname, + "..", + "..", + declarationTypesPath, + ); + const declaration = fs.readFileSync(declarationPath, "utf8"); + + expect(declaration).not.toMatch(/z\.infer { if (!publishedVersion) { console.log("Skipping test: No published version available"); diff --git a/js/util/git_fields.ts b/js/util/git_fields.ts index 8eb06d625..482a43526 100644 --- a/js/util/git_fields.ts +++ b/js/util/git_fields.ts @@ -1,4 +1,4 @@ -import { GitMetadataSettingsType as GitMetadataSettings } from "./generated_types"; +import type { GitMetadataSettingsType as GitMetadataSettings } from "./generated_plain_types"; export function mergeGitMetadataSettings( s1: GitMetadataSettings, diff --git a/js/util/object.ts b/js/util/object.ts index 7ea593005..e6b5193c3 100644 --- a/js/util/object.ts +++ b/js/util/object.ts @@ -1,7 +1,7 @@ import { AsyncScoringControlType as AsyncScoringControl, type ObjectReferenceType, -} from "./generated_types"; +} from "./generated_plain_types"; import { Source, ASYNC_SCORING_CONTROL_FIELD, diff --git a/js/util/span_identifier_v3.ts b/js/util/span_identifier_v3.ts index f057f798b..78a753d53 100644 --- a/js/util/span_identifier_v3.ts +++ b/js/util/span_identifier_v3.ts @@ -38,7 +38,8 @@ export enum SpanObjectTypeV3 { PLAYGROUND_LOGS = 3, } -export const spanObjectTypeV3EnumSchema = z.nativeEnum(SpanObjectTypeV3); +export const spanObjectTypeV3EnumSchema: z.ZodType = + z.nativeEnum(SpanObjectTypeV3); export function spanObjectTypeV3ToTypedString( objectType: SpanObjectTypeV3, @@ -81,7 +82,35 @@ const _INTERNAL_SPAN_COMPONENT_UUID_FIELDS_ID_TO_NAME: Record< [InternalSpanComponentUUIDFields.ROOT_SPAN_ID]: "root_span_id", }; -export const spanComponentsV3Schema = z +type SpanObjectMetadata = + | { + object_id?: string | null; + compute_object_metadata_args?: null; + } + | { + object_id?: null; + compute_object_metadata_args: Record; + }; + +type SpanRowIds = + | { + row_id: string; + span_id: string; + root_span_id: string; + } + | { + row_id?: null; + span_id?: null; + root_span_id?: null; + }; + +export type SpanComponentsV3Data = { + object_type: SpanObjectTypeV3; + propagated_event?: Record | null; +} & SpanObjectMetadata & + SpanRowIds; + +export const spanComponentsV3Schema: z.ZodType = z .object({ object_type: spanObjectTypeV3EnumSchema, // TODO(manu): We should have a more elaborate zod schema for @@ -118,8 +147,6 @@ export const spanComponentsV3Schema = z ]), ); -export type SpanComponentsV3Data = z.infer; - export class SpanComponentsV3 { constructor(public data: SpanComponentsV3Data) {} diff --git a/js/util/span_identifier_v4.ts b/js/util/span_identifier_v4.ts index e92e3eddf..771d72aec 100644 --- a/js/util/span_identifier_v4.ts +++ b/js/util/span_identifier_v4.ts @@ -5,6 +5,7 @@ import { SpanComponentsV3, SpanObjectTypeV3, spanObjectTypeV3EnumSchema, + type SpanComponentsV3Data, } from "./span_identifier_v3"; import { ParentExperimentIds, @@ -19,7 +20,7 @@ import { uint8ArrayToString, } from "./bytes"; import { z } from "zod/v3"; -import { InvokeFunctionType as InvokeFunctionRequest } from "./generated_types"; +import type { InvokeFunctionType as InvokeFunctionRequest } from "./generated_plain_types"; import { mergeDicts } from "./object_util"; const ENCODING_VERSION_NUMBER_V4 = 4; @@ -88,7 +89,9 @@ const FIELDS_ID_TO_NAME: Record = { [Fields.ROOT_SPAN_ID]: "root_span_id", }; -export const spanComponentsV4Schema = z +export type SpanComponentsV4Data = SpanComponentsV3Data; + +export const spanComponentsV4Schema: z.ZodType = z .object({ object_type: spanObjectTypeV3EnumSchema, propagated_event: z.record(z.unknown()).nullish(), @@ -122,8 +125,6 @@ export const spanComponentsV4Schema = z ]), ); -export type SpanComponentsV4Data = z.infer; - export class SpanComponentsV4 { constructor(public data: SpanComponentsV4Data) {} From 1bb454c096f080f38b9eafa81257ad5602cf98f1 Mon Sep 17 00:00:00 2001 From: lforst <8118419+lforst@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:35:54 +0000 Subject: [PATCH 2/3] Update PR #2404 --- .changeset/remove-zod-derived-types.md | 5 +++++ js/src/eval-parameters.ts | 19 ------------------- 2 files changed, 5 insertions(+), 19 deletions(-) create mode 100644 .changeset/remove-zod-derived-types.md diff --git a/.changeset/remove-zod-derived-types.md b/.changeset/remove-zod-derived-types.md new file mode 100644 index 000000000..c741bede7 --- /dev/null +++ b/.changeset/remove-zod-derived-types.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove Zod derived types from public SDK declarations diff --git a/js/src/eval-parameters.ts b/js/src/eval-parameters.ts index b2c40e45c..bc15dc105 100644 --- a/js/src/eval-parameters.ts +++ b/js/src/eval-parameters.ts @@ -2,30 +2,11 @@ import { z } from "zod/v3"; import Ajv from "ajv"; import { Prompt, RemoteEvalParameters } from "./logger"; import { - promptDefinitionWithToolsSchema, promptDefinitionToPromptData, type PromptDefinitionWithTools, } from "./prompt-schemas"; import { PromptData as promptDataSchema } from "./generated_types"; -// Schema for evaluation parameters -export const evalParametersSchema = z.record( - z.string(), - z.union([ - z.object({ - type: z.literal("prompt"), - default: promptDefinitionWithToolsSchema.optional(), - description: z.string().optional(), - }), - z.object({ - type: z.literal("model"), - default: z.string().optional(), - description: z.string().optional(), - }), - z.instanceof(z.ZodType), // For Zod schemas - ]), -); - export type EvalParameters = Record< string, | { From f72e06a9e7c216045c89d3f6e6fca5bcc526baf1 Mon Sep 17 00:00:00 2001 From: lforst <8118419+lforst@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:58:41 +0000 Subject: [PATCH 3/3] Update PR #2404 --- AGENTS.md | 8 ++++---- js/src/framework-types.ts | 2 +- js/src/functions/stream.ts | 6 +++--- js/src/gitutil.ts | 6 +++--- js/src/graph-framework.ts | 12 ++++++------ js/src/isomorph.ts | 6 +++--- js/src/logger.ts | 5 ++--- js/src/prompt-schemas.ts | 14 +++++++------- js/src/sandbox.ts | 2 +- .../api-compatibility/api-compatibility.test.ts | 12 ++++-------- js/util/object.ts | 4 ++-- 11 files changed, 36 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9b8a6bc9e..1b62e3529 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,12 +27,12 @@ pnpm run build # Build all workspace packages (from repo root) ## Public TypeScript APIs -Do not derive publicly exposed TypeScript types from Zod schemas (for example, +Do not derive SDK-owned public TypeScript types from Zod schemas (for example, with `z.infer`, `z.input`, `z.output`, or equivalent schema-derived aliases). Define public API types explicitly with interfaces, type aliases, or generated -plain types. When exporting a runtime validator, give it a compact public type -such as `z.ZodType` and test that the validator and public type stay -in sync. +plain types. Generic APIs may still infer types from caller-provided schemas. +When exporting a runtime validator, give it a compact public type such as +`z.ZodType` and test that the validator and public type stay in sync. Zod-derived public declarations can expand into large schema implementation graphs. Those declarations are expensive for downstream TypeScript consumers to diff --git a/js/src/framework-types.ts b/js/src/framework-types.ts index d98fa2cf1..0f9d45c26 100644 --- a/js/src/framework-types.ts +++ b/js/src/framework-types.ts @@ -1,4 +1,4 @@ -import { type IfExistsType as IfExists } from "./generated_plain_types"; +import type { IfExistsType as IfExists } from "./generated_plain_types"; export type GenericFunction = | ((input: Input) => Output) diff --git a/js/src/functions/stream.ts b/js/src/functions/stream.ts index 26dd64acc..392e6b337 100644 --- a/js/src/functions/stream.ts +++ b/js/src/functions/stream.ts @@ -4,7 +4,7 @@ import { SSEProgressEventData as sseProgressEventDataSchema, } from "../generated_types"; import type { - CallEventType as CallEventSchema, + CallEventType as CallEvent, SSEConsoleEventDataType, SSEProgressEventDataType, } from "../generated_plain_types"; @@ -176,7 +176,7 @@ export class BraintrustStream { return this.memoizedFinalValue; } - static parseRawEvent(event: CallEventSchema): BraintrustStreamChunk { + static parseRawEvent(event: CallEvent): BraintrustStreamChunk { switch (event.event) { case "text_delta": return { @@ -225,7 +225,7 @@ export class BraintrustStream { } } - static serializeRawEvent(event: BraintrustStreamChunk): CallEventSchema { + static serializeRawEvent(event: BraintrustStreamChunk): CallEvent { switch (event.type) { case "text_delta": return { diff --git a/js/src/gitutil.ts b/js/src/gitutil.ts index 2883131ba..9a687ebb3 100644 --- a/js/src/gitutil.ts +++ b/js/src/gitutil.ts @@ -1,6 +1,6 @@ -import { - type GitMetadataSettingsType as GitMetadataSettings, - type RepoInfoType as RepoInfo, +import type { + GitMetadataSettingsType as GitMetadataSettings, + RepoInfoType as RepoInfo, } from "./generated_plain_types"; import { debugLogger } from "./debug-logger"; import { runGitCommand } from "./git-command"; diff --git a/js/src/graph-framework.ts b/js/src/graph-framework.ts index f432ab541..5d6766da6 100644 --- a/js/src/graph-framework.ts +++ b/js/src/graph-framework.ts @@ -1,10 +1,10 @@ import { newId, Prompt } from "./logger"; -import { - type FunctionIdType as FunctionId, - type GraphDataType as GraphData, - type GraphNodeType as GraphNode, - type GraphEdgeType as GraphEdge, - type PromptBlockDataType as PromptBlockData, +import type { + FunctionIdType as FunctionId, + GraphDataType as GraphData, + GraphNodeType as GraphNode, + GraphEdgeType as GraphEdge, + PromptBlockDataType as PromptBlockData, } from "./generated_plain_types"; export interface BuildContext { diff --git a/js/src/isomorph.ts b/js/src/isomorph.ts index 9a7b7e96c..0b500bcec 100644 --- a/js/src/isomorph.ts +++ b/js/src/isomorph.ts @@ -1,6 +1,6 @@ -import { - type GitMetadataSettingsType as GitMetadataSettings, - type RepoInfoType as RepoInfo, +import type { + GitMetadataSettingsType as GitMetadataSettings, + RepoInfoType as RepoInfo, } from "./generated_plain_types"; import { newGlobalTracingChannel, diff --git a/js/src/logger.ts b/js/src/logger.ts index 8d29e1a17..fcf88d9a4 100644 --- a/js/src/logger.ts +++ b/js/src/logger.ts @@ -95,7 +95,6 @@ import type { PromptSessionEventType as PromptSessionEvent, RepoInfoType as RepoInfo, ObjectReferenceType as ObjectReference, - ObjectReferenceType, PromptBlockDataType as PromptBlockData, ResponseFormatJsonSchemaType as ResponseFormatJsonSchema, } from "./generated_plain_types"; @@ -8418,7 +8417,7 @@ export class Dataset< metadata?: Record; tags?: string[]; output?: unknown; - origin?: ObjectReferenceType; + origin?: ObjectReference; isMerge?: boolean; }): LazyValue { return new LazyValue(async () => { @@ -8477,7 +8476,7 @@ export class Dataset< readonly metadata?: Record; readonly id?: string; readonly output?: unknown; - readonly origin?: ObjectReferenceType; + readonly origin?: ObjectReference; }): string { this.validateEvent({ metadata, expected, output, tags }); diff --git a/js/src/prompt-schemas.ts b/js/src/prompt-schemas.ts index 30fc79672..46ad5f307 100644 --- a/js/src/prompt-schemas.ts +++ b/js/src/prompt-schemas.ts @@ -17,7 +17,7 @@ export type PromptContents = | { prompt: string } | { messages: ChatCompletionMessageParamType[] }; -const promptContentsSchemaInternal = z.union([ +const internalPromptContentsSchema = z.union([ z.object({ prompt: z.string(), }), @@ -29,7 +29,7 @@ export const promptContentsSchema: z.ZodType< PromptContents, z.ZodTypeDef, unknown -> = promptContentsSchemaInternal; +> = internalPromptContentsSchema; export type PromptDefinition = PromptContents & { model: string; @@ -38,7 +38,7 @@ export type PromptDefinition = PromptContents & { environments?: string[]; }; -const promptDefinitionSchemaInternal = promptContentsSchemaInternal.and( +const internalPromptDefinitionSchema = internalPromptContentsSchema.and( z.object({ model: z.string(), params: modelParamsSchema.optional(), @@ -50,14 +50,14 @@ export const promptDefinitionSchema: z.ZodType< PromptDefinition, z.ZodTypeDef, unknown -> = promptDefinitionSchemaInternal; +> = internalPromptDefinitionSchema; export type PromptDefinitionWithTools = PromptDefinition & { tools?: ToolFunctionDefinition[]; }; -const promptDefinitionWithToolsSchemaInternal = - promptDefinitionSchemaInternal.and( +const internalPromptDefinitionWithToolsSchema = + internalPromptDefinitionSchema.and( z.object({ tools: z.array(toolFunctionDefinitionSchema).optional(), }), @@ -66,7 +66,7 @@ export const promptDefinitionWithToolsSchema: z.ZodType< PromptDefinitionWithTools, z.ZodTypeDef, unknown -> = promptDefinitionWithToolsSchemaInternal; +> = internalPromptDefinitionWithToolsSchema; export function promptDefinitionToPromptData( promptDefinition: PromptDefinition, diff --git a/js/src/sandbox.ts b/js/src/sandbox.ts index 542c4a57f..cfafa8c98 100644 --- a/js/src/sandbox.ts +++ b/js/src/sandbox.ts @@ -1,6 +1,6 @@ import { z } from "zod/v3"; import { slugify } from "../util/string_util"; -import { type IfExistsType } from "./generated_plain_types"; +import type { IfExistsType } from "./generated_plain_types"; import { type BraintrustState, _internalGetGlobalState } from "./logger"; /** diff --git a/js/tests/api-compatibility/api-compatibility.test.ts b/js/tests/api-compatibility/api-compatibility.test.ts index d33438ef1..2fd785357 100644 --- a/js/tests/api-compatibility/api-compatibility.test.ts +++ b/js/tests/api-compatibility/api-compatibility.test.ts @@ -2949,6 +2949,7 @@ describe("API Compatibility", () => { }); test("keeps public declarations free of expanded Zod schema graphs", () => { + const declarationRoot = path.join(__dirname, "..", ".."); const publicDeclarationPaths = [ "dist/index.d.ts", "dist/browser.d.ts", @@ -2956,12 +2957,7 @@ describe("API Compatibility", () => { ]; for (const declarationTypesPath of publicDeclarationPaths) { - const declarationPath = path.join( - __dirname, - "..", - "..", - declarationTypesPath, - ); + const declarationPath = path.join(declarationRoot, declarationTypesPath); const declaration = fs.readFileSync(declarationPath, "utf8"); expect(declaration).not.toMatch(/z\.infer { } const mainDeclaration = fs.readFileSync( - path.join(__dirname, "..", "..", "dist/index.d.ts"), + path.join(declarationRoot, "dist/index.d.ts"), "utf8", ); for (const schemaName of [ @@ -2988,7 +2984,7 @@ describe("API Compatibility", () => { } const utilDeclaration = fs.readFileSync( - path.join(__dirname, "..", "..", "util/dist/index.d.ts"), + path.join(declarationRoot, "util/dist/index.d.ts"), "utf8", ); for (const schemaName of [ diff --git a/js/util/object.ts b/js/util/object.ts index e6b5193c3..941c6264a 100644 --- a/js/util/object.ts +++ b/js/util/object.ts @@ -1,6 +1,6 @@ -import { +import type { AsyncScoringControlType as AsyncScoringControl, - type ObjectReferenceType, + ObjectReferenceType, } from "./generated_plain_types"; import { Source,