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
5 changes: 5 additions & 0 deletions .changeset/remove-zod-derived-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"braintrust": major
---

ref!: Remove Zod derived types from public SDK declarations
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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. 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<PublicType>` 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.
Expand Down
35 changes: 15 additions & 20 deletions js/src/eval-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,25 @@ 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 = z.infer<typeof evalParametersSchema>;
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> = T extends { type: "prompt" }
Expand Down
7 changes: 6 additions & 1 deletion js/src/exports.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -322,7 +326,8 @@ export type {

export { addAzureBlobHeaders, LazyValue } from "./util";

export { AttachmentReference } from "./generated_types";
export const AttachmentReference: z.ZodType<AttachmentReferenceType> =
attachmentReferenceSchema;

export type { EvalParameters } from "./eval-parameters";

Expand Down
2 changes: 1 addition & 1 deletion js/src/framework-types.ts
Original file line number Diff line number Diff line change
@@ -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, Output> =
| ((input: Input) => Output)
Expand Down
14 changes: 7 additions & 7 deletions js/src/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
24 changes: 12 additions & 12 deletions js/src/framework2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -782,7 +782,7 @@ interface FunctionEvent {
name: string;
description: string;
prompt_data?: PromptData;
function_data: z.infer<typeof functionDataSchema>;
function_data: FunctionDataType;
function_type?: FunctionType;
if_exists?: IfExists;
tags?: string[];
Expand Down
14 changes: 7 additions & 7 deletions js/src/functions/invoke.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
91 changes: 52 additions & 39 deletions js/src/functions/stream.ts
Original file line number Diff line number Diff line change
@@ -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 CallEvent,
SSEConsoleEventDataType,
SSEProgressEventDataType,
} from "../generated_plain_types";
import {
createParser,
EventSourceParser,
Expand All @@ -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<typeof braintrustStreamChunkSchema>;
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<BraintrustStreamChunk> =
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`,
Expand Down Expand Up @@ -163,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 {
Expand Down Expand Up @@ -212,7 +225,7 @@ export class BraintrustStream {
}
}

static serializeRawEvent(event: BraintrustStreamChunk): CallEventSchema {
static serializeRawEvent(event: BraintrustStreamChunk): CallEvent {
switch (event.type) {
case "text_delta":
return {
Expand Down
8 changes: 4 additions & 4 deletions js/src/gitutil.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type GitMetadataSettingsType as GitMetadataSettings,
type RepoInfoType as RepoInfo,
} from "./generated_types";
import type {
GitMetadataSettingsType as GitMetadataSettings,
RepoInfoType as RepoInfo,
} from "./generated_plain_types";
import { debugLogger } from "./debug-logger";
import { runGitCommand } from "./git-command";

Expand Down
14 changes: 7 additions & 7 deletions js/src/graph-framework.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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,
} from "./generated_types";
import type {
FunctionIdType as FunctionId,
GraphDataType as GraphData,
GraphNodeType as GraphNode,
GraphEdgeType as GraphEdge,
PromptBlockDataType as PromptBlockData,
} from "./generated_plain_types";

export interface BuildContext {
getFunctionId(functionObj: unknown): Promise<FunctionId>;
Expand Down
8 changes: 4 additions & 4 deletions js/src/isomorph.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type GitMetadataSettingsType as GitMetadataSettings,
type RepoInfoType as RepoInfo,
} from "./generated_types";
import type {
GitMetadataSettingsType as GitMetadataSettings,
RepoInfoType as RepoInfo,
} from "./generated_plain_types";
import {
newGlobalTracingChannel,
type GlobalHookAsyncLocalStorage,
Expand Down
Loading
Loading