Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
70b861b
fix(livekit): Respect LiveKit's PII and project redaction settings (#…
delner Nov 3, 2026
eb59da8
feat(audio): Add AudioSessionRecorder for audio schema v3 sessions (#…
delner Nov 3, 2026
9c0665f
feat(livekit): Trace LiveKit voice sessions with audio schema v3 (#2726)
delner Nov 3, 2026
f42d9dc
fix(openai): align Decisions spans with Python SDK (#2723)
ankrgyl Nov 3, 2026
245d1f9
fix(dev): honor request max_concurrency (#2730)
paultancre-bt Nov 5, 2026
47f1653
docs(functions): clarify where invoke metadata reaches code functions…
AbhiPrasad Nov 5, 2026
2212770
feat: Bump support for cloudflare agents (#2732)
lforst Nov 5, 2026
a775b68
feat: Bump support for cloudflare ai chat (#2737)
lforst Nov 5, 2026
7ec4866
feat: Bump support for google adk (#2734)
lforst Nov 5, 2026
9d6d75a
feat: Bump support for langgraph (#2736)
lforst Nov 5, 2026
a5cf049
feat: Bump support for ai sdk harness (#2738)
lforst Nov 5, 2026
0995ed8
feat: Bump support for cloudflare think (#2739)
lforst Nov 5, 2026
ed32ada
feat: Bump support for google genai (#2740)
lforst Nov 5, 2026
7b46abe
feat: Bump support for langsmith (#2741)
lforst Nov 5, 2026
519d9a4
feat: Bump support for mistral (#2735)
lforst Nov 5, 2026
355d0f5
deps: Bump support for anthropic SDK (#2733)
lforst Nov 5, 2026
0c00282
feat: Bump support for cursor SDK (#2742)
lforst Nov 5, 2026
753d396
chore: Prepare release v3.43.0 (0c002829738b) (#2744)
github-actions[bot] Nov 6, 2026
a6271d2
feat: Bump support for eve (#2749)
lforst Nov 9, 2026
43d1245
feat: Bump support for mastra (#2747)
lforst Nov 9, 2026
de6ec5f
feat: Bump support for temporal (#2748)
lforst Nov 9, 2026
43d5223
feat: Bump support for openai agents (#2746)
lforst Nov 9, 2026
561dee1
fix(ai-sdk): log AI SDK v7 inline file data as attachments (#2752)
AbhiPrasad Nov 9, 2026
3d8f95a
feat: Support eve 1.0.0 (#2764)
lforst Nov 9, 2026
223be87
feat(langchain): Add capture controls (#2763)
lforst Nov 9, 2026
631a644
feat: Bump support for openrouter sdk (#2759)
lforst Nov 9, 2026
f5a852c
feat(instrumentation): Track service tiers (#2768)
lforst Nov 10, 2026
8f1bac6
fix(deps): Upgrade minimatch from v10 to v11 (#2770)
AbhiPrasad Nov 10, 2026
7f59041
Merge main into v4
lforst Nov 11, 2026
7801fc3
chore: Prepare release v3.44.0 (8f1bac6c4c25) (#2772)
github-actions[bot] Nov 11, 2026
159cfe1
Merge latest main release into v4
lforst Nov 12, 2026
f4a5dd3
feat(google-genai): Trace decision calls (#2774)
lforst Nov 12, 2026
3e675de
Merge latest main into v4
lforst Nov 12, 2026
2d57ac5
feat: Bump support for cloudflare agents (#2760)
lforst Nov 12, 2026
8fe3609
feat: V4 migration guide (#2627)
lforst Nov 12, 2026
975135e
Merge latest main into v4
lforst Nov 12, 2026
0e6e866
Update PR #2773
lforst Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 0 additions & 5 deletions .changeset/add-fastmcp-tracing.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/bump-cloudflare-agents-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"braintrust": minor
---

feat: Bump support for cloudflare agents
5 changes: 0 additions & 5 deletions .changeset/fix-cursor-sdk-tracing.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-logged-byte-stream-utf8.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/google-genai-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"braintrust": minor
---

feat(google-genai): Trace decision calls
2 changes: 1 addition & 1 deletion .changeset/light-plants-return.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"braintrust": major
---

feat!: Add `BRAINTRUST_CAPTURE_ATTACHMENTS` env var option and `captureAttachments` logger option for all integrations
feat!: Add `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS` env var option and `autoCaptureAttachments` logger option for all integrations
5 changes: 0 additions & 5 deletions .changeset/mcp-handler-tracing.md

This file was deleted.

2 changes: 1 addition & 1 deletion .changeset/remove-flush-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"braintrust": major
---

ref!: Remove `flushAsync` option
ref!: Remove `asyncFlush` option
7 changes: 7 additions & 0 deletions dev-packages/seinfeld/src/cassette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export interface RecordedRequest {

/** A single recorded response, as it appeared on the wire. */
export interface RecordedResponse {
/** Original wire chunk sizes and inter-chunk delays for interactive streams. */
streamChunks?: Array<{
size: number;
delayMs: number;
/** Complete Connect request frames received before this response chunk. */
requestFrames?: number;
}>;
status: number;
statusText?: string;
headers: Record<string, string>;
Expand Down
9 changes: 9 additions & 0 deletions dev-packages/seinfeld/src/format/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ const recordedRequestSchema = z.object({
});

const recordedResponseSchema = z.object({
streamChunks: z
.array(
z.object({
size: z.number().int().positive(),
delayMs: z.number().nonnegative(),
requestFrames: z.number().int().nonnegative().optional(),
}),
)
.optional(),
status: z.number().int().min(100).max(599),
statusText: z.string().optional(),
headers: z.record(z.string()),
Expand Down
40 changes: 37 additions & 3 deletions dev-packages/seinfeld/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,23 @@ export async function recordResponseDraft(
/** Build a Fetch API `Response` from a recorded response body. */
export async function buildResponse(
recorded: RecordedResponse | RecordedResponseOrDraft,
ctx?: { store: CassetteStore; name: string },
ctx?: {
store: CassetteStore;
name: string;
waitForRequestFrames?: (count: number) => Promise<void>;
},
): Promise<Response> {
// Expand \n-joined set-cookie back into multiple header entries.
// Strip encoding headers: stored bytes are already decoded by the fetch
// layer (undici decompresses gzip/deflate before handing the body to us).
// Preserving content-encoding would cause callers to attempt a second
// decode of already-plain bytes, which throws a zlib error.
const headers = expandSetCookieHeader(stripEncodingHeaders(recorded.headers));
const init: ResponseInit = { status: recorded.status, headers };
if (recorded.statusText) init.statusText = recorded.statusText;
const init: ResponseInit = {
status: recorded.status,
headers,
...(recorded.statusText ? { statusText: recorded.statusText } : {}),
};
// 1xx/204/304 responses must not have a body, per Fetch spec.
if (isNullBodyStatus(recorded.status)) return new Response(null, init);

Expand Down Expand Up @@ -146,6 +153,33 @@ export async function buildResponse(
? recorded.body.bytes
: await decodeBody(recorded.body, ctx);
if (bytes.length === 0) return new Response(null, init);
if (recorded.streamChunks) {
// Interactive providers wait for client messages between chunks. Frame
// counts preserve that ordering even if replay's client is slower than
// recording's client; recorded delays alone cannot enforce it.
const chunks = recorded.streamChunks;
let index = 0;
let offset = 0;
const stream = new ReadableStream({
async pull(controller) {
if (index === chunks.length) {
controller.close();
return;
}
const { size, delayMs, requestFrames } = chunks[index++]!;
if (requestFrames !== undefined) {
if (!ctx?.waitForRequestFrames) {
throw new Error("Connect replay requires request frame tracking");
}
await ctx.waitForRequestFrames(requestFrames);
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
controller.enqueue(bytes.slice(offset, offset + size));
offset += size;
},
});
return new Response(stream, init);
}
// Copy into a fresh ArrayBuffer. (TS 5.7 typed Uint8Array as
// Uint8Array<ArrayBufferLike>, and `.buffer` may be `SharedArrayBuffer` —
// neither satisfies the DOM lib's strict ArrayBuffer-only BodyInit.)
Expand Down
67 changes: 67 additions & 0 deletions dev-packages/seinfeld/src/internal/connect-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { IncomingMessage } from "node:http";

/** Count complete Connect envelopes without retaining their payloads. */
export function trackConnectRequest(req: IncomingMessage) {
const header = Buffer.alloc(5);
let headerSize = 0;
let remaining = 0;
let frames = 0;
let closed = false;
let notify = () => {};

const onData = (data: Buffer) => {
let offset = 0;
while (offset < data.length) {
if (headerSize < header.length) {
const size = Math.min(header.length - headerSize, data.length - offset);
data.copy(header, headerSize, offset, offset + size);
headerSize += size;
offset += size;
if (headerSize < header.length) continue;
remaining = header.readUInt32BE(1);
}
const size = Math.min(remaining, data.length - offset);
remaining -= size;
offset += size;
if (remaining === 0) {
frames++;
headerSize = 0;
}
}
notify();
};
const onClose = () => {
closed = true;
notify();
};
req.on("data", onData);
req.on("end", onClose);
req.on("close", onClose);
req.on("error", onClose);

return {
get frames() {
return frames;
},
// Response stream pulls are sequential, so there is only one waiter.
async waitFor(count: number) {
while (frames < count) {
if (closed) {
throw new Error(
`Connect request ended after ${frames} frames; replay requires ${count}`,
);
}
await new Promise<void>((resolve) => {
notify = resolve;
});
}
},
dispose() {
req.off("data", onData);
req.off("end", onClose);
req.off("close", onClose);
req.off("error", onClose);
onClose();
},
};
}
Loading
Loading