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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); ref!: Remove Zod derived types from public SDK declarations by lforst · Pull Request #2404 · braintrustdata/braintrust-sdk-javascript · GitHub
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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