Skip to content

Commit 4950cdb

Browse files
lforstRaphaelFakhrirealarkAbhiPrasadclaude
authored
Merge latest main into v4 (#2680)
Co-authored-by: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Co-authored-by: Andrew Kent <31153155+realark@users.noreply.github.com> Co-authored-by: Abhijeet Prasad <abhijeet@braintrustdata.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Co-authored-by: theanecdote <sungeun.jp@icloud.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: David Elner <david.elner@braintrustdata.com>
2 parents eca72fa + f01391c commit 4950cdb

839 files changed

Lines changed: 214698 additions & 43752 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/create-changeset/SKILL.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ description: Create or update Changesets for this repository with a Conventional
66
# Create a Changeset
77

88
Inspect the diff to identify every publishable package affected and choose the appropriate semantic version bump for each package.
9-
Use `pnpm changeset` when creating the changeset unless the existing task requires editing a specific changeset file.
9+
Create the changeset by writing a new Markdown file under `.changeset/` directly, with a short kebab-case filename such as `.changeset/fix-queued-login-race.md`.
10+
11+
```markdown
12+
---
13+
"braintrust": patch
14+
"@braintrust/otel": minor
15+
---
16+
17+
fix: Prevent conflicting queued logins from replacing active credentials
18+
```
19+
20+
List each affected package by its `package.json` name with `major`, `minor`, or `patch`.
1021

1122
## Summary
1223

‎.agents/skills/instrumentation/SKILL.md‎

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
---
22
name: instrumentation
3-
description: Add or update Braintrust SDK instrumentation. Use when working on instrumentation of any kind - like wrappers, auto-instrumentation configs, tracing channels, provider plugins, vendored SDK typings, or instrumentation-specific tests.
3+
description: Add or update Braintrust SDK instrumentation. Use when working on instrumentation of any kind - like wrappers, auto-instrumentation configs, invocation hooks, provider plugins, vendored SDK typings, or instrumentation-specific tests.
44
---
55

66
# Instrumentation Rules
77

88
Read first based on the task:
99

10-
- `js/src/instrumentation/README.md` for plugin and tracing-channel architecture
11-
- Closest file in `js/src/instrumentation/core/` when changing shared channel semantics
10+
- `js/src/instrumentation/README.md` for wrapping and tracing architecture
11+
- Closest file in `js/src/instrumentation/core/` when changing shared invocation semantics
1212
- Closest file in `js/src/instrumentation/plugins/` when changing provider-specific extraction or span mapping
1313
- Closest file in `js/src/wrappers/` when manual wrappers and auto-instrumentation need to stay aligned
1414
- Closest test in `js/tests/auto-instrumentations/` when changing hook, loader, bundler, or transform behavior
1515
- Closest e2e scenario in `e2e/scenarios/*instrumentation*` or `e2e/scenarios/*node-hook*` when the user-visible trace contract changes
1616

1717
Map the change before editing:
1818

19-
- `js/src/instrumentation/core/` - tracing-channel helpers, stream patching, shared types
20-
- `js/src/instrumentation/plugins/` - provider-specific channel subscriptions and event-to-span conversion
19+
- `js/src/instrumentation/core/` - invocation definitions, independent tracing helpers, stream patching, shared types
20+
- `js/src/instrumentation/plugins/` - explicit interceptor registration and provider-specific tracing functions
2121
- `js/src/wrappers/` - manual instrumentation entrypoints that should mirror the same logical contracts
2222
- `js/src/auto-instrumentations/` - loader and bundler instrumentation config
2323
- `js/tests/auto-instrumentations/` - functional coverage for transformed code
@@ -27,10 +27,22 @@ Map the change before editing:
2727
- Inputs are untrusted: treat args, results, events, headers, and metadata as hostile. Prototype pollution is a concrete risk here. Avoid unsafe property access patterns, prototype-sensitive operations, and unnecessary mutation of third-party objects.
2828
- Support both auto-instrumentation and manual instrumentation. Auto-instrumentation does not cover every environment, loader, or framework.
2929
- For orchestrion auto-instrumentation, prefer targeting public API functions. Instrumenting internal helpers is more likely to break across library versions.
30-
- Auto and manual paths should share logic through the same typed channel. For new and migrated instrumentation, prefer `invoke` in manual wrappers and `intercept` in provider plugins so the target can be scoped with `AsyncLocalStorage.run()` and its arguments, receiver, or output can be patched. Keep tracing-style hooks only as the compatibility path for instrumentation that has not migrated yet. Manual wrappers should not directly emit observability data.
30+
- Braintrust's vendored Orchestrion implementation is purely internal to the SDK.
31+
Downstream packages must not depend on its APIs, config schema, generated helpers, or compatibility with upstream Orchestrion.
32+
Do not expose these internals through SDK entrypoints or public configuration options.
33+
Update the transformer and its SDK callers together; remove unused upstream features and compatibility scaffolding.
34+
Preserve instrumented application behavior, coordination between SDK copies, and upstream license notices.
35+
- Keep wrapping and tracing separate, including internal APIs.
36+
Define typed invocation hooks with `defineInterceptor`; `intercept` and `invoke` only compose and execute wrappers.
37+
Definitions describe call arguments, return values, and opaque additional data, without span provenance or tracing methods.
38+
The wrapping runtime must work without SDK initialization and have no dependency on spans, logging, or tracing lifecycle events.
39+
Put span creation, context propagation, and finalization in separate tracing functions.
40+
Plugins explicitly register those functions through `intercept`; shared tracing helpers accept callables and tracing configuration, never hooks or registration responsibilities.
41+
Do not add combined helpers such as `traceInvocation` or `interceptAndTrace`, or retain a tracing-event compatibility path.
42+
Manual wrappers and generated wrappers use the same hook through `invoke` and do not directly emit observability data.
3143
- Reuse shared repo utilities before introducing local helpers. Check `js/util/index.ts`, neighboring instrumentation files, and existing plugins/wrappers for utilities like `isObject`, merge helpers, and sanitizers before adding ad hoc replacements.
3244
- If a public instrumentation surface changes, check whether the export surface also needs updates in `js/src/instrumentation/index.ts` or `js/src/exports.ts`.
33-
- Preserve async context propagation. Changes around tracing channels, stream patching, or loader hooks must keep the current span context across awaits and stream consumption.
45+
- Preserve async context propagation. Changes around invocation hooks, stream patching, or loader hooks must keep the current span context across awaits and stream consumption.
3446
- Maintain isomorphic behavior. Node and browser/bundled paths must use compatible channel implementations and avoid channel-registry mismatches.
3547
- Setup, teardown, and patching must be idempotent. Enabling twice, disabling twice, or applying a patch twice should remain safe.
3648
- Promise/stream behavior must be preserved. Patches need to keep subclass/helper semantics intact.
@@ -41,10 +53,18 @@ Map the change before editing:
4153
- Log only the useful surface. Prefer narrow, stable payloads over dumping full request/response objects; exclude redundant or overly large data when possible.
4254
- Automatic capture of inline multimodal bytes as Braintrust attachments must be opt-in.
4355
Guard attachment creation and binary observation with `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS`, and enable them only when its value is `1` or `true`.
56+
Audio/video recordings may contain personal data and are separately controlled with their own explicit flags (`BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS`, `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS`): enabling the shared flag for other media never records audio/video without these being enabled.
4457
Preserve useful text, metadata, metrics, and remote references when attachment capture is disabled, and omit inline bytes from logged payloads.
58+
- Segmentable streaming media must use the shared `configureInstrumentation({ streamingAttachmentBufferBytes })` setting instead of provider-specific chunk-size or time-based controls.
59+
It defaults to 4 MiB per logical media stream and counts binary attachment payload bytes after decoding and format conversion, including any required padding or silence.
60+
Flush near the threshold at valid media boundaries and flush remaining valid content when the recording ends.
61+
Preserve ordering, format metadata, and timestamps needed to interpret the attachments.
62+
Keep whole-file responses intact, even if they exceed the threshold; never arbitrarily split encoded images, audio, or video.
63+
This is a flush threshold, not a total memory limit or attachment-capture opt-in.
64+
Preserve application stream behavior and existing capture consent; keep any capture safety ceiling internal and distinct from the threshold.
4565
- Do not modify package READMEs during instrumentation work unless the user specifically requests it or the change corrects outdated information.
4666
- We want to limit our instrumentation to operations that are relevant for AI generations and operations (LLMs, embeddings, media generation, ...). Things like creating entities on platforms (CRUD for Workflows of Agent entities) is irrelevant to us.
47-
- When building instrumentation, we should always have a vendored type/interface for what we are wrapping. The type or interface should not be larger than what is relevant to the instrumentation. The type or interface should be used for typing tracing channels and also should be used to assert the type on whatever is passed into wrappers as soon as the wrapper has verified that the passed in value is plausibly what should be wrapped.
67+
- When building instrumentation, we should always have a vendored type/interface for what we are wrapping. The type or interface should not be larger than what is relevant to the instrumentation. The type or interface should be used for typing invocation hooks and also should be used to assert the type on whatever is passed into wrappers as soon as the wrapper has verified that the passed in value is plausibly what should be wrapped.
4868

4969
## Process
5070

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"braintrust": minor
3+
---
4+
5+
feat(livekit): Record caller and agent audio with explicit per-direction flags
6+
7+
Set `BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS` and `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS` to record each side of a call.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"braintrust": patch
3+
---
4+
5+
fix(livekit): Finish uploading recordings before `session.close()` resolves, so they are not lost when the job process exits

‎AGENTS.md‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,22 @@ Keep public exports minimal. Generally, export only the requested runtime APIs a
4646

4747
## Instrumentation
4848

49+
Our vendored Orchestrion implementation is purely internal to the Braintrust SDK.
50+
Downstream packages must not depend on its APIs, config schema, generated helpers, or compatibility with upstream Orchestrion.
51+
Do not expose these internals through SDK entrypoints or public configuration options.
52+
Change the transformer and its SDK callers together, and remove unused upstream features instead of retaining compatibility scaffolding.
53+
Preserve instrumented application behavior, coordination between SDK copies, and upstream license notices.
54+
4955
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.
5056

57+
API wrapping and span instrumentation are separate concepts.
58+
Use `defineInterceptor` to define invocation hooks and `intercept`/`invoke` exclusively as generic wrapping machinery.
59+
Wrapping definitions and runtime code must not depend on spans, tracing lifecycle events, provenance, or SDK initialization.
60+
Keep span creation, context propagation, and finalization in separate tracing functions.
61+
Provider plugins explicitly register those functions through interceptors; tracing helpers accept callables and tracing configuration, never hooks or registration responsibilities.
62+
Do not introduce combined APIs such as `traceInvocation` or `interceptAndTrace`, including internal convenience APIs.
63+
Manual wrappers and generated wrappers only invoke hooks.
64+
5165
Instrumentation patches generally do not need to be removed during teardown. Prefer leaving behavior-preserving patches installed when they are idempotent; do not add unpatching machinery by default.
5266

5367
Span names should generally remain stable across calls and versions. Do not include dynamic values such as model names in span names; record those values in metadata instead.
@@ -61,11 +75,17 @@ Generally, do not add tests for build or linting code or their configuration. Va
6175
**From `js/` directory:**
6276

6377
```bash
64-
pnpm test # Core vitest suite (excludes wrappers)
65-
pnpm test -- -t "test name" # Filter by test name
66-
pnpm run test:checks # Hermetic tests (core + vitest wrapper)
78+
pnpm test # Core vitest suite (excludes wrappers)
79+
pnpm exec vitest run src/foo.test.ts # Run a single test file
80+
pnpm exec vitest run src/foo.test.ts -t "x" # Filter by test name within a file
81+
pnpm run test:checks # Hermetic tests (core + vitest wrapper)
82+
pnpm run check:typings # Typecheck source and test files
6783
```
6884

85+
Do not pass filters through `pnpm test -- ...`; the `--` makes Vitest ignore them and run the full suite.
86+
87+
Vitest does not typecheck. Before opening or updating a pull request, run `pnpm run check:typings` in each package you touched (or from the repo root) so type errors in source or test files are caught before CI.
88+
6989
**E2E tests (`e2e/`):**
7090

7191
Each scenario runs the SDK in a subprocess against a mock Braintrust server and snapshots the results. No API keys required for replay; recording needs provider keys.

‎PUBLISHING.md‎

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ All package publishing happens in GitHub Actions. Do not publish from your local
44

55
## Short Version
66

7-
- If your PR changes a publishable package, run `pnpm changeset` and commit the generated `.changeset/*.md` file.
7+
- If your PR changes a publishable package, add a `.changeset/*.md` file to it.
88
- Merge the PR into `main`.
99
- For a stable release:
1010
- Run **Prepare Release** on `main`.
@@ -21,19 +21,7 @@ All package publishing happens in GitHub Actions. Do not publish from your local
2121

2222
If your PR changes a publishable package, CI expects a changeset unless you explicitly skip it.
2323

24-
Create one with:
25-
26-
```bash
27-
pnpm changeset
28-
```
29-
30-
That creates a `.changeset/*.md` file. Commit it with your PR.
31-
32-
Use:
33-
34-
- `patch` for fixes and non-breaking maintenance changes
35-
- `minor` for new backwards-compatible features
36-
- `major` for breaking changes
24+
To add one, have an agent follow [`.agents/skills/create-changeset/SKILL.md`](.agents/skills/create-changeset/SKILL.md), then commit the generated `.changeset/*.md` file with your PR.
3725

3826
If no release is intended, bypass the check with either:
3927

‎dev-packages/seinfeld/src/recorder.ts‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ interface CassetteContext {
6464
ignoredRequests: IgnoredRequestMatcher[] | undefined;
6565
streamingRequests: StreamingRequestMatcher[] | undefined;
6666
threshold: number | false;
67+
inlineRequestBodies: boolean;
6768
onMiss: ((req: RecordedRequest) => void) | undefined;
6869

6970
// Per-run mutable state (reset on each start)
@@ -106,11 +107,17 @@ async function handleCassetteRequest(
106107

107108
switch (ctx.mode) {
108109
case "replay": {
109-
const recorded = await recordRequest(request, ctx.threshold);
110+
const recorded = await recordRequest(
111+
request,
112+
ctx.inlineRequestBodies ? false : ctx.threshold,
113+
);
110114
return handleReplay(ctx, recorded);
111115
}
112116
case "record": {
113-
const draft = await recordRequestDraft(request, ctx.threshold);
117+
const draft = await recordRequestDraft(
118+
request,
119+
ctx.inlineRequestBodies ? false : ctx.threshold,
120+
);
114121
return handleRecord(ctx, getRealResponse, draft);
115122
}
116123
case "passthrough":
@@ -449,6 +456,8 @@ export interface CassetteServerOptions {
449456
store: CassetteStore;
450457
/** Filter spec (matching-only normalization). */
451458
filters?: FilterSpec;
459+
/** Keep large request bodies inline so matching filters can normalize their bytes. */
460+
inlineRequestBodies?: boolean;
452461
/** Redaction spec for what gets persisted to disk. Defaults to paranoid. */
453462
redact?: RedactionSpec;
454463
/**
@@ -496,6 +505,7 @@ function createContext(options: CassetteServerOptions): CassetteContext {
496505
ignoredRequests: options.ignoredRequests,
497506
streamingRequests: options.streamingRequests,
498507
threshold: DEFAULT_EXTERNAL_BLOB_THRESHOLD,
508+
inlineRequestBodies: options.inlineRequestBodies ?? false,
499509
onMiss: options.onMiss,
500510
candidates: [],
501511
callCounts: new Map(),
@@ -797,15 +807,25 @@ async function writeFetchResponse(
797807

798808
const reader = response.body.getReader();
799809
try {
800-
for (;;) {
810+
while (!res.destroyed) {
801811
const { done, value } = await reader.read();
802812
if (done) break;
803813
if (!res.write(Buffer.from(value))) {
804-
await new Promise<void>((resolve) => res.once("drain", resolve));
814+
// A client that disconnects mid-stream never drains.
815+
await new Promise<void>((resolve) => {
816+
const resume = () => {
817+
res.off("drain", resume);
818+
res.off("close", resume);
819+
resolve();
820+
};
821+
res.on("drain", resume);
822+
res.on("close", resume);
823+
});
805824
}
806825
}
807826
} finally {
808827
res.end();
809-
reader.releaseLock();
828+
if (res.destroyed) await reader.cancel();
829+
else reader.releaseLock();
810830
}
811831
}

‎e2e/README.md‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,19 @@ pnpm --filter=@braintrust/js-e2e-tests run test:e2e
183183

184184
If a scenario records but later replay fails because of volatile fields in the request body (e.g. AI-SDK's generated message ids), add or update `<scenario-dir>/cassette-filter.mjs` for that scenario, then re-record.
185185

186-
After any successful record run, stale cassette variants are cleaned only inside scenarios that emitted cassette run-context records during that run. With explicit scenario names, cleanup is limited to those scenarios; with a full record run, cleanup applies to every observed cassette-backed scenario. Per-cassette blob sidecar cleanup still happens in the cassette file store when each cassette is saved.
186+
After a successful record run, unused cassette variants and their blob directories are cleaned only for scenarios whose test files and tests all passed. Cleanup uses the cassette keys actually selected by the harness, including overrides and opt-outs. Scenarios with skipped tests, missing test files, or missing usage records are left untouched. Per-cassette blob sidecar cleanup still happens in the cassette file store when each cassette is saved.
187+
188+
### Checking unused cassettes without re-recording
189+
190+
```bash
191+
pnpm run test:e2e:cassettes
192+
pnpm run test:e2e:cassettes -- cloudflare-think-instrumentation
193+
pnpm run test:e2e:cassettes -- --clean-cassettes cloudflare-think-instrumentation
194+
```
195+
196+
These commands run a fresh replay and compare the cassette files on disk with actual usage. The default check leaves files untouched and exits nonzero when it finds unused variants. `--clean-cassettes` removes unused JSON files and their `.cassette.blobs` directories after the tests pass.
197+
198+
Only whole scenarios can be selected; test filters, file paths, sharding, and snapshot updates are rejected. A scenario with skipped tests, missing test files, or missing usage records is reported as not checked and makes the command exit nonzero. Unselected scenarios and directories with no test files are not checked. Use `BRAINTRUST_E2E_RUN_CONTEXT_DIR` to retain the run records and test report for the trace summary.
187199

188200
### In-scope scenarios
189201

0 commit comments

Comments
 (0)