braintrust 0.0.166 → 0.0.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/browser.ts
31
31
  var browser_exports = {};
32
32
  __export(browser_exports, {
33
+ Attachment: () => Attachment,
33
34
  BraintrustState: () => BraintrustState,
34
35
  BraintrustStream: () => BraintrustStream,
35
36
  Dataset: () => Dataset,
@@ -43,6 +44,7 @@ __export(browser_exports, {
43
44
  ReadonlyExperiment: () => ReadonlyExperiment,
44
45
  SpanImpl: () => SpanImpl,
45
46
  X_CACHED_HEADER: () => X_CACHED_HEADER,
47
+ _exportsForTestingOnly: () => _exportsForTestingOnly,
46
48
  _internalGetGlobalState: () => _internalGetGlobalState,
47
49
  _internalSetInitialState: () => _internalSetInitialState,
48
50
  braintrustStreamChunkSchema: () => braintrustStreamChunkSchema,
@@ -587,11 +589,11 @@ var BraintrustState = class _BraintrustState {
587
589
  state.loginReplaceApiConn(state.apiConn());
588
590
  return state;
589
591
  }
590
- setFetch(fetch) {
591
- this.loginParams.fetch = fetch;
592
- this.fetch = fetch;
593
- this._apiConn?.setFetch(fetch);
594
- this._appConn?.setFetch(fetch);
592
+ setFetch(fetch2) {
593
+ this.loginParams.fetch = fetch2;
594
+ this.fetch = fetch2;
595
+ this._apiConn?.setFetch(fetch2);
596
+ this._appConn?.setFetch(fetch2);
595
597
  }
596
598
  async login(loginParams) {
597
599
  if (this.apiUrl && !loginParams.forceLogin) {
@@ -680,15 +682,15 @@ var HTTPConnection = class _HTTPConnection {
680
682
  token;
681
683
  headers;
682
684
  fetch;
683
- constructor(base_url, fetch) {
685
+ constructor(base_url, fetch2) {
684
686
  this.base_url = base_url;
685
687
  this.token = null;
686
688
  this.headers = {};
687
689
  this._reset();
688
- this.fetch = fetch;
690
+ this.fetch = fetch2;
689
691
  }
690
- setFetch(fetch) {
691
- this.fetch = fetch;
692
+ setFetch(fetch2) {
693
+ this.fetch = fetch2;
692
694
  }
693
695
  async ping() {
694
696
  try {
@@ -724,12 +726,14 @@ var HTTPConnection = class _HTTPConnection {
724
726
  ([k, v]) => v !== void 0 ? typeof v === "string" ? [[k, v]] : v.map((x) => [k, x]) : []
725
727
  ) : []
726
728
  ).toString();
729
+ const this_fetch = this.fetch;
730
+ const this_headers = this.headers;
727
731
  return await checkResponse(
728
732
  // Using toString() here makes it work with isomorphic fetch
729
- await this.fetch(url.toString(), {
733
+ await this_fetch(url.toString(), {
730
734
  headers: {
731
735
  Accept: "application/json",
732
- ...this.headers,
736
+ ...this_headers,
733
737
  ...headers
734
738
  },
735
739
  keepalive: true,
@@ -739,13 +743,16 @@ var HTTPConnection = class _HTTPConnection {
739
743
  }
740
744
  async post(path, params, config) {
741
745
  const { headers, ...rest } = config || {};
746
+ const this_fetch = this.fetch;
747
+ const this_base_url = this.base_url;
748
+ const this_headers = this.headers;
742
749
  return await checkResponse(
743
- await this.fetch((0, import_core._urljoin)(this.base_url, path), {
750
+ await this_fetch((0, import_core._urljoin)(this_base_url, path), {
744
751
  method: "POST",
745
752
  headers: {
746
753
  Accept: "application/json",
747
754
  "Content-Type": "application/json",
748
- ...this.headers,
755
+ ...this_headers,
749
756
  ...headers
750
757
  },
751
758
  body: typeof params === "string" ? params : params ? JSON.stringify(params) : void 0,
@@ -778,6 +785,167 @@ var HTTPConnection = class _HTTPConnection {
778
785
  return await resp.json();
779
786
  }
780
787
  };
788
+ var Attachment = class {
789
+ /**
790
+ * The object that replaces this `Attachment` at upload time.
791
+ */
792
+ reference;
793
+ uploader;
794
+ data;
795
+ state;
796
+ // For debug logging only.
797
+ dataDebugString;
798
+ /**
799
+ * Construct an attachment.
800
+ *
801
+ * @param data A string representing the path of the file on disk, or a
802
+ * `Blob`/`ArrayBuffer` with the file's contents. The caller is responsible
803
+ * for ensuring the file/blob/buffer is not modified until upload is complete.
804
+ *
805
+ * @param filename The desired name of the file in Braintrust after uploading.
806
+ * This parameter is for visualization purposes only and has no effect on
807
+ * attachment storage.
808
+ *
809
+ * @param contentType The MIME type of the file.
810
+ *
811
+ * @param state (Optional) For internal use.
812
+ */
813
+ constructor({ data, filename, contentType, state }) {
814
+ this.reference = {
815
+ type: import_typespecs2.BRAINTRUST_ATTACHMENT,
816
+ filename,
817
+ content_type: contentType,
818
+ key: newId()
819
+ };
820
+ this.state = state;
821
+ this.dataDebugString = typeof data === "string" ? data : "<in-memory data>";
822
+ this.data = this.initData(data);
823
+ this.uploader = this.initUploader();
824
+ }
825
+ /**
826
+ * On first access, (1) reads the attachment from disk if needed, (2)
827
+ * authenticates with the data plane to request a signed URL, (3) uploads to
828
+ * object store, and (4) updates the attachment.
829
+ *
830
+ * @returns The attachment status.
831
+ */
832
+ async upload() {
833
+ return await this.uploader.get();
834
+ }
835
+ /**
836
+ * A human-readable description for logging and debugging.
837
+ *
838
+ * @returns The debug object. The return type is not stable and may change in
839
+ * a future release.
840
+ */
841
+ debugInfo() {
842
+ return {
843
+ inputData: this.dataDebugString,
844
+ reference: this.reference,
845
+ state: this.state
846
+ };
847
+ }
848
+ initUploader() {
849
+ const doUpload = async (conn, orgId) => {
850
+ const requestParams = {
851
+ key: this.reference.key,
852
+ filename: this.reference.filename,
853
+ content_type: this.reference.content_type,
854
+ org_id: orgId
855
+ };
856
+ const [metadataPromiseResult, dataPromiseResult] = await Promise.allSettled([
857
+ conn.post("/attachment", requestParams),
858
+ this.data.get()
859
+ ]);
860
+ if (metadataPromiseResult.status === "rejected") {
861
+ const errorStr = JSON.stringify(metadataPromiseResult.reason);
862
+ throw new Error(
863
+ `Failed to request signed URL from API server: ${errorStr}`
864
+ );
865
+ }
866
+ if (dataPromiseResult.status === "rejected") {
867
+ const errorStr = JSON.stringify(dataPromiseResult.reason);
868
+ throw new Error(`Failed to read file: ${errorStr}`);
869
+ }
870
+ const metadataResponse = metadataPromiseResult.value;
871
+ const data = dataPromiseResult.value;
872
+ let signedUrl;
873
+ let headers;
874
+ try {
875
+ ({ signedUrl, headers } = import_zod2.z.object({
876
+ signedUrl: import_zod2.z.string().url(),
877
+ headers: import_zod2.z.record(import_zod2.z.string())
878
+ }).parse(await metadataResponse.json()));
879
+ } catch (error) {
880
+ if (error instanceof import_zod2.ZodError) {
881
+ const errorStr = JSON.stringify(error.flatten());
882
+ throw new Error(`Invalid response from API server: ${errorStr}`);
883
+ }
884
+ throw error;
885
+ }
886
+ let objectStoreResponse;
887
+ try {
888
+ objectStoreResponse = await checkResponse(
889
+ await fetch(signedUrl, {
890
+ method: "PUT",
891
+ headers,
892
+ body: data
893
+ })
894
+ );
895
+ } catch (error) {
896
+ if (error instanceof FailedHTTPResponse) {
897
+ throw new Error(
898
+ `Failed to upload attachment to object store: ${error.status} ${error.text} ${error.data}`
899
+ );
900
+ }
901
+ throw error;
902
+ }
903
+ return { signedUrl, metadataResponse, objectStoreResponse };
904
+ };
905
+ const errorWrapper = async () => {
906
+ const status = { upload_status: "done" };
907
+ const state = this.state ?? _globalState;
908
+ await state.login({});
909
+ const conn = state.apiConn();
910
+ const orgId = state.orgId ?? "";
911
+ try {
912
+ await doUpload(conn, orgId);
913
+ } catch (error) {
914
+ status.upload_status = "error";
915
+ status.error_message = error instanceof Error ? error.message : JSON.stringify(error);
916
+ }
917
+ const requestParams = {
918
+ key: this.reference.key,
919
+ org_id: orgId,
920
+ status
921
+ };
922
+ const statusResponse = await conn.post(
923
+ "/attachment/status",
924
+ requestParams
925
+ );
926
+ if (!statusResponse.ok) {
927
+ const errorStr = JSON.stringify(statusResponse);
928
+ throw new Error(`Couldn't log attachment status: ${errorStr}`);
929
+ }
930
+ return status;
931
+ };
932
+ return new LazyValue(errorWrapper);
933
+ }
934
+ initData(data) {
935
+ if (typeof data === "string") {
936
+ const readFile = isomorph_default.readFile;
937
+ if (!readFile) {
938
+ throw new Error(
939
+ `This platform does not support reading the filesystem. Construct the Attachment
940
+ with a Blob/ArrayBuffer, or run the program on Node.js.`
941
+ );
942
+ }
943
+ return new LazyValue(async () => new Blob([await readFile(data)]));
944
+ } else {
945
+ return new LazyValue(async () => new Blob([data]));
946
+ }
947
+ }
948
+ };
781
949
  function logFeedbackImpl(state, parentObjectType, parentObjectId, {
782
950
  id,
783
951
  expected,
@@ -802,7 +970,7 @@ function logFeedbackImpl(state, parentObjectType, parentObjectId, {
802
970
  expected,
803
971
  tags
804
972
  });
805
- let { metadata, ...updateEvent } = validatedEvent;
973
+ let { metadata, ...updateEvent } = deepCopyEvent(validatedEvent);
806
974
  updateEvent = Object.fromEntries(
807
975
  Object.entries(updateEvent).filter(([_, v]) => !isEmpty(v))
808
976
  );
@@ -851,10 +1019,12 @@ function updateSpanImpl({
851
1019
  id,
852
1020
  event
853
1021
  }) {
854
- const updateEvent = validateAndSanitizeExperimentLogPartialArgs({
855
- id,
856
- ...event
857
- });
1022
+ const updateEvent = deepCopyEvent(
1023
+ validateAndSanitizeExperimentLogPartialArgs({
1024
+ id,
1025
+ ...event
1026
+ })
1027
+ );
858
1028
  const parentIds = async () => new import_core.SpanComponentsV3({
859
1029
  object_type: parentObjectType,
860
1030
  object_id: await parentObjectId.get()
@@ -1055,7 +1225,7 @@ var Logger = class {
1055
1225
  * @param event.id: (Optional) a unique identifier for the event. If you don't provide one, BrainTrust will generate one for you.
1056
1226
  * @param options Additional logging options
1057
1227
  * @param options.allowConcurrentWithSpans in rare cases where you need to log at the top level separately from spans on the logger elsewhere, set this to true.
1058
- * :returns: The `id` of the logged event.
1228
+ * @returns The `id` of the logged event.
1059
1229
  */
1060
1230
  log(event, options) {
1061
1231
  if (this.calledStartSpan && !options?.allowConcurrentWithSpans) {
@@ -1078,7 +1248,7 @@ var Logger = class {
1078
1248
  /**
1079
1249
  * Create a new toplevel span underneath the logger. The name defaults to "root".
1080
1250
  *
1081
- * See `Span.traced` for full details.
1251
+ * See {@link Span.traced} for full details.
1082
1252
  */
1083
1253
  traced(callback, args) {
1084
1254
  const { setCurrent, ...argsRest } = args ?? {};
@@ -1112,7 +1282,7 @@ var Logger = class {
1112
1282
  * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span",
1113
1283
  * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead.
1114
1284
  *
1115
- * See `traced` for full details.
1285
+ * See {@link traced} for full details.
1116
1286
  */
1117
1287
  startSpan(args) {
1118
1288
  this.calledStartSpan = true;
@@ -1152,7 +1322,7 @@ var Logger = class {
1152
1322
  * Update a span in the experiment using its id. It is important that you only update a span once the original span has been fully written and flushed,
1153
1323
  * since otherwise updates to the span may conflict with the original span.
1154
1324
  *
1155
- * @param event The event data to update the span with. Must include `id`. See `Experiment.log` for a full list of valid fields.
1325
+ * @param event The event data to update the span with. Must include `id`. See {@link Experiment.log} for a full list of valid fields.
1156
1326
  */
1157
1327
  updateSpan(event) {
1158
1328
  const { id, ...eventRest } = event;
@@ -1168,7 +1338,9 @@ var Logger = class {
1168
1338
  });
1169
1339
  }
1170
1340
  /**
1171
- * Return a serialized representation of the logger that can be used to start subspans in other places. See `Span.start_span` for more details.
1341
+ * Return a serialized representation of the logger that can be used to start subspans in other places.
1342
+ *
1343
+ * See {@link Span.startSpan} for more details.
1172
1344
  */
1173
1345
  async export() {
1174
1346
  return new import_core.SpanComponentsV3({
@@ -1309,7 +1481,7 @@ var BackgroundLogger = class _BackgroundLogger {
1309
1481
  const batchSize = args?.batchSize ?? this.defaultBatchSize;
1310
1482
  const wrappedItems = this.items;
1311
1483
  this.items = [];
1312
- const allItems = await this.unwrapLazyValues(wrappedItems);
1484
+ const [allItems, attachments] = await this.unwrapLazyValues(wrappedItems);
1313
1485
  if (allItems.length === 0) {
1314
1486
  return;
1315
1487
  }
@@ -1341,6 +1513,23 @@ var BackgroundLogger = class _BackgroundLogger {
1341
1513
  );
1342
1514
  }
1343
1515
  }
1516
+ const attachmentErrors = [];
1517
+ for (const attachment of attachments) {
1518
+ try {
1519
+ const result = await attachment.upload();
1520
+ if (result.upload_status === "error" && result.error_message) {
1521
+ attachmentErrors.push(new Error(result.error_message));
1522
+ }
1523
+ } catch (error) {
1524
+ attachmentErrors.push(error);
1525
+ }
1526
+ }
1527
+ if (attachmentErrors.length > 0) {
1528
+ throw new AggregateError(
1529
+ attachmentErrors,
1530
+ `Encountered the following errors while uploading attachments:`
1531
+ );
1532
+ }
1344
1533
  if (this.items.length > 0) {
1345
1534
  await this.flushOnce(args);
1346
1535
  }
@@ -1348,8 +1537,10 @@ var BackgroundLogger = class _BackgroundLogger {
1348
1537
  async unwrapLazyValues(wrappedItems) {
1349
1538
  for (let i = 0; i < this.numTries; ++i) {
1350
1539
  try {
1351
- const itemPromises = wrappedItems.map((x) => x.get());
1352
- return (0, import_core.mergeRowBatch)(await Promise.all(itemPromises));
1540
+ const items = await Promise.all(wrappedItems.map((x) => x.get()));
1541
+ const attachments = [];
1542
+ items.forEach((item) => extractAttachments(item, attachments));
1543
+ return [(0, import_core.mergeRowBatch)(items), attachments];
1353
1544
  } catch (e) {
1354
1545
  let errmsg = "Encountered error when constructing records to flush";
1355
1546
  const isRetrying = i + 1 < this.numTries;
@@ -1368,7 +1559,7 @@ var BackgroundLogger = class _BackgroundLogger {
1368
1559
  console.warn(
1369
1560
  `Failed to construct log records to flush after ${this.numTries} attempts. Dropping batch`
1370
1561
  );
1371
- return [];
1562
+ return [[], []];
1372
1563
  }
1373
1564
  async submitLogsRequest(items) {
1374
1565
  const conn = await this.apiConn.get();
@@ -1457,15 +1648,17 @@ Error: ${errorText}`;
1457
1648
  return;
1458
1649
  }
1459
1650
  try {
1460
- const allItems = await this.unwrapLazyValues(wrappedItems);
1651
+ const [allItems, allAttachments] = await this.unwrapLazyValues(wrappedItems);
1461
1652
  const dataStr = constructLogs3Data(
1462
1653
  allItems.map((x) => JSON.stringify(x))
1463
1654
  );
1655
+ const attachmentStr = JSON.stringify(
1656
+ allAttachments.map((a) => a.debugInfo())
1657
+ );
1658
+ const payload = `{"data": ${dataStr}, "attachments": ${attachmentStr}}
1659
+ `;
1464
1660
  for (const payloadDir of publishPayloadsDir) {
1465
- await _BackgroundLogger.writePayloadToDir({
1466
- payloadDir,
1467
- payload: dataStr
1468
- });
1661
+ await _BackgroundLogger.writePayloadToDir({ payloadDir, payload });
1469
1662
  }
1470
1663
  } catch (e) {
1471
1664
  console.error(e);
@@ -1546,7 +1739,7 @@ function init(projectOrOptions, optionalOptions) {
1546
1739
  apiKey,
1547
1740
  orgName,
1548
1741
  forceLogin,
1549
- fetch,
1742
+ fetch: fetch2,
1550
1743
  metadata,
1551
1744
  gitMetadataSettings,
1552
1745
  projectId,
@@ -1564,7 +1757,7 @@ function init(projectOrOptions, optionalOptions) {
1564
1757
  }
1565
1758
  const lazyMetadata2 = new LazyValue(
1566
1759
  async () => {
1567
- await state.login({ apiKey, appUrl, orgName, fetch, forceLogin });
1760
+ await state.login({ apiKey, appUrl, orgName, fetch: fetch2, forceLogin });
1568
1761
  const args = {
1569
1762
  project_name: project,
1570
1763
  project_id: projectId,
@@ -1735,7 +1928,7 @@ function initDataset(projectOrOptions, optionalOptions) {
1735
1928
  appUrl,
1736
1929
  apiKey,
1737
1930
  orgName,
1738
- fetch,
1931
+ fetch: fetch2,
1739
1932
  forceLogin,
1740
1933
  projectId,
1741
1934
  metadata,
@@ -1749,7 +1942,7 @@ function initDataset(projectOrOptions, optionalOptions) {
1749
1942
  orgName,
1750
1943
  apiKey,
1751
1944
  appUrl,
1752
- fetch,
1945
+ fetch: fetch2,
1753
1946
  forceLogin
1754
1947
  });
1755
1948
  const args = {
@@ -1831,7 +2024,7 @@ function initLogger(options = {}) {
1831
2024
  apiKey,
1832
2025
  orgName,
1833
2026
  forceLogin,
1834
- fetch,
2027
+ fetch: fetch2,
1835
2028
  state: stateArg
1836
2029
  } = options || {};
1837
2030
  const computeMetadataArgs = {
@@ -1846,7 +2039,7 @@ function initLogger(options = {}) {
1846
2039
  apiKey,
1847
2040
  appUrl,
1848
2041
  forceLogin,
1849
- fetch
2042
+ fetch: fetch2
1850
2043
  });
1851
2044
  return computeLoggerMetadata(state, computeMetadataArgs);
1852
2045
  }
@@ -1870,7 +2063,7 @@ async function loadPrompt({
1870
2063
  appUrl,
1871
2064
  apiKey,
1872
2065
  orgName,
1873
- fetch,
2066
+ fetch: fetch2,
1874
2067
  forceLogin,
1875
2068
  state: stateArg
1876
2069
  }) {
@@ -1885,7 +2078,7 @@ async function loadPrompt({
1885
2078
  orgName,
1886
2079
  apiKey,
1887
2080
  appUrl,
1888
- fetch,
2081
+ fetch: fetch2,
1889
2082
  forceLogin
1890
2083
  });
1891
2084
  const args = {
@@ -1936,7 +2129,7 @@ async function loginToState(options = {}) {
1936
2129
  appUrl = isomorph_default.getEnv("BRAINTRUST_APP_URL") || "https://www.braintrust.dev",
1937
2130
  apiKey = isomorph_default.getEnv("BRAINTRUST_API_KEY"),
1938
2131
  orgName = isomorph_default.getEnv("BRAINTRUST_ORG_NAME"),
1939
- fetch = globalThis.fetch
2132
+ fetch: fetch2 = globalThis.fetch
1940
2133
  } = options || {};
1941
2134
  const appPublicUrl = isomorph_default.getEnv("BRAINTRUST_APP_PUBLIC_URL") || appUrl;
1942
2135
  const state = new BraintrustState(options);
@@ -1946,7 +2139,7 @@ async function loginToState(options = {}) {
1946
2139
  let conn = null;
1947
2140
  if (apiKey !== void 0) {
1948
2141
  const resp = await checkResponse(
1949
- await fetch((0, import_core._urljoin)(state.appUrl, `/api/apikey/login`), {
2142
+ await fetch2((0, import_core._urljoin)(state.appUrl, `/api/apikey/login`), {
1950
2143
  method: "POST",
1951
2144
  headers: {
1952
2145
  "Content-Type": "application/json",
@@ -2114,8 +2307,8 @@ async function flush(options) {
2114
2307
  const state = options?.state ?? _globalState;
2115
2308
  return await state.bgLogger().flush();
2116
2309
  }
2117
- function setFetch(fetch) {
2118
- _globalState.setFetch(fetch);
2310
+ function setFetch(fetch2) {
2311
+ _globalState.setFetch(fetch2);
2119
2312
  }
2120
2313
  function startSpanAndIsLogger(args) {
2121
2314
  const state = args?.state ?? _globalState;
@@ -2243,6 +2436,47 @@ function validateAndSanitizeExperimentLogPartialArgs(event) {
2243
2436
  return { ...event };
2244
2437
  }
2245
2438
  }
2439
+ function deepCopyEvent(event) {
2440
+ const attachments = [];
2441
+ const IDENTIFIER = "_bt_internal_saved_attachment";
2442
+ const savedAttachmentSchema = import_zod2.z.strictObject({ [IDENTIFIER]: import_zod2.z.number() });
2443
+ const serialized = JSON.stringify(event, (_k, v) => {
2444
+ if (v instanceof SpanImpl || v instanceof NoopSpan) {
2445
+ return `<span>`;
2446
+ } else if (v instanceof Experiment) {
2447
+ return `<experiment>`;
2448
+ } else if (v instanceof Dataset) {
2449
+ return `<dataset>`;
2450
+ } else if (v instanceof Logger) {
2451
+ return `<logger>`;
2452
+ } else if (v instanceof Attachment) {
2453
+ const idx = attachments.push(v);
2454
+ return { [IDENTIFIER]: idx - 1 };
2455
+ }
2456
+ return v;
2457
+ });
2458
+ const x = JSON.parse(serialized, (_k, v) => {
2459
+ const parsedAttachment = savedAttachmentSchema.safeParse(v);
2460
+ if (parsedAttachment.success) {
2461
+ return attachments[parsedAttachment.data[IDENTIFIER]];
2462
+ }
2463
+ return v;
2464
+ });
2465
+ return x;
2466
+ }
2467
+ function extractAttachments(event, attachments) {
2468
+ for (const [key, value] of Object.entries(event)) {
2469
+ if (value instanceof Attachment) {
2470
+ attachments.push(value);
2471
+ event[key] = value.reference;
2472
+ continue;
2473
+ }
2474
+ if (!(value instanceof Object)) {
2475
+ continue;
2476
+ }
2477
+ extractAttachments(value, attachments);
2478
+ }
2479
+ }
2246
2480
  function validateAndSanitizeExperimentLogFullArgs(event, hasDataset) {
2247
2481
  if ("input" in event && !isEmpty(event.input) && "inputs" in event && !isEmpty(event.inputs) || !("input" in event) && !("inputs" in event)) {
2248
2482
  throw new Error(
@@ -2373,10 +2607,10 @@ var Experiment = class extends ObjectFetcher {
2373
2607
  * @param event.metrics: (Optional) a dictionary of metrics to log. The following keys are populated automatically: "start", "end".
2374
2608
  * @param event.id: (Optional) a unique identifier for the event. If you don't provide one, BrainTrust will generate one for you.
2375
2609
  * @param event.dataset_record_id: (Optional) the id of the dataset record that this event is associated with. This field is required if and only if the experiment is associated with a dataset.
2376
- * @param event.inputs: (Deprecated) the same as `input` (will be removed in a future version).
2610
+ * @deprecated @param event.inputs: (Deprecated) the same as `input` (will be removed in a future version).
2377
2611
  * @param options Additional logging options
2378
2612
  * @param options.allowConcurrentWithSpans in rare cases where you need to log at the top level separately from spans on the experiment elsewhere, set this to true.
2379
- * :returns: The `id` of the logged event.
2613
+ * @returns The `id` of the logged event.
2380
2614
  */
2381
2615
  log(event, options) {
2382
2616
  if (this.calledStartSpan && !options?.allowConcurrentWithSpans) {
@@ -2392,7 +2626,7 @@ var Experiment = class extends ObjectFetcher {
2392
2626
  /**
2393
2627
  * Create a new toplevel span underneath the experiment. The name defaults to "root".
2394
2628
  *
2395
- * See `Span.traced` for full details.
2629
+ * See {@link Span.traced} for full details.
2396
2630
  */
2397
2631
  traced(callback, args) {
2398
2632
  const { setCurrent, ...argsRest } = args ?? {};
@@ -2418,7 +2652,7 @@ var Experiment = class extends ObjectFetcher {
2418
2652
  * where you cannot use callbacks. However, spans started with `startSpan` will not be marked as the "current span",
2419
2653
  * so `currentSpan()` and `traced()` will be no-ops. If you want to mark a span as current, use `traced` instead.
2420
2654
  *
2421
- * See `traced` for full details.
2655
+ * See {@link traced} for full details.
2422
2656
  */
2423
2657
  startSpan(args) {
2424
2658
  this.calledStartSpan = true;
@@ -2530,7 +2764,7 @@ var Experiment = class extends ObjectFetcher {
2530
2764
  * Update a span in the experiment using its id. It is important that you only update a span once the original span has been fully written and flushed,
2531
2765
  * since otherwise updates to the span may conflict with the original span.
2532
2766
  *
2533
- * @param event The event data to update the span with. Must include `id`. See `Experiment.log` for a full list of valid fields.
2767
+ * @param event The event data to update the span with. Must include `id`. See {@link Experiment.log} for a full list of valid fields.
2534
2768
  */
2535
2769
  updateSpan(event) {
2536
2770
  const { id, ...eventRest } = event;
@@ -2546,7 +2780,9 @@ var Experiment = class extends ObjectFetcher {
2546
2780
  });
2547
2781
  }
2548
2782
  /**
2549
- * Return a serialized representation of the experiment that can be used to start subspans in other places. See `Span.start_span` for more details.
2783
+ * Return a serialized representation of the experiment that can be used to start subspans in other places.
2784
+ *
2785
+ * See {@link Span.startSpan} for more details.
2550
2786
  */
2551
2787
  async export() {
2552
2788
  return new import_core.SpanComponentsV3({
@@ -2561,7 +2797,7 @@ var Experiment = class extends ObjectFetcher {
2561
2797
  return await this.state.bgLogger().flush();
2562
2798
  }
2563
2799
  /**
2564
- * This function is deprecated. You can simply remove it from your code.
2800
+ * @deprecated This function is deprecated. You can simply remove it from your code.
2565
2801
  */
2566
2802
  async close() {
2567
2803
  console.warn(
@@ -2703,27 +2939,14 @@ var SpanImpl = class _SpanImpl {
2703
2939
  event,
2704
2940
  internalData
2705
2941
  });
2706
- let partialRecord = {
2942
+ const partialRecord = deepCopyEvent({
2707
2943
  id: this.id,
2708
2944
  span_id: this.spanId,
2709
2945
  root_span_id: this.rootSpanId,
2710
2946
  span_parents: this.spanParents,
2711
2947
  ...serializableInternalData,
2712
2948
  [import_core.IS_MERGE_FIELD]: this.isMerge
2713
- };
2714
- const serializedPartialRecord = JSON.stringify(partialRecord, (_k, v) => {
2715
- if (v instanceof _SpanImpl) {
2716
- return `<span>`;
2717
- } else if (v instanceof Experiment) {
2718
- return `<experiment>`;
2719
- } else if (v instanceof Dataset) {
2720
- return `<dataset>`;
2721
- } else if (v instanceof Logger) {
2722
- return `<logger>`;
2723
- }
2724
- return v;
2725
2949
  });
2726
- partialRecord = JSON.parse(serializedPartialRecord);
2727
2950
  if (partialRecord.metrics?.end) {
2728
2951
  this.loggedEndTime = partialRecord.metrics?.end;
2729
2952
  }
@@ -2958,15 +3181,17 @@ var Dataset = class extends ObjectFetcher {
2958
3181
  }) {
2959
3182
  this.validateEvent({ metadata, expected, output, tags });
2960
3183
  const rowId = id || (0, import_uuid.v4)();
2961
- const args = this.createArgs({
2962
- id: rowId,
2963
- input,
2964
- expected,
2965
- metadata,
2966
- tags,
2967
- output,
2968
- isMerge: false
2969
- });
3184
+ const args = this.createArgs(
3185
+ deepCopyEvent({
3186
+ id: rowId,
3187
+ input,
3188
+ expected,
3189
+ metadata,
3190
+ tags,
3191
+ output,
3192
+ isMerge: false
3193
+ })
3194
+ );
2970
3195
  this.state.bgLogger().log([args]);
2971
3196
  return rowId;
2972
3197
  }
@@ -2991,14 +3216,16 @@ var Dataset = class extends ObjectFetcher {
2991
3216
  id
2992
3217
  }) {
2993
3218
  this.validateEvent({ metadata, expected, tags });
2994
- const args = this.createArgs({
2995
- id,
2996
- input,
2997
- expected,
2998
- metadata,
2999
- tags,
3000
- isMerge: true
3001
- });
3219
+ const args = this.createArgs(
3220
+ deepCopyEvent({
3221
+ id,
3222
+ input,
3223
+ expected,
3224
+ metadata,
3225
+ tags,
3226
+ isMerge: true
3227
+ })
3228
+ );
3002
3229
  this.state.bgLogger().log([args]);
3003
3230
  return id;
3004
3231
  }
@@ -3053,7 +3280,7 @@ var Dataset = class extends ObjectFetcher {
3053
3280
  return await this.state.bgLogger().flush();
3054
3281
  }
3055
3282
  /**
3056
- * This function is deprecated. You can simply remove it from your code.
3283
+ * @deprecated This function is deprecated. You can simply remove it from your code.
3057
3284
  */
3058
3285
  async close() {
3059
3286
  console.warn(
@@ -3217,6 +3444,7 @@ var Prompt = class {
3217
3444
  return this.parsedPromptData;
3218
3445
  }
3219
3446
  };
3447
+ var _exportsForTestingOnly = { extractAttachments, deepCopyEvent };
3220
3448
 
3221
3449
  // src/browser-config.ts
3222
3450
  var browserConfigured = false;
@@ -3243,6 +3471,7 @@ function configureBrowser() {
3243
3471
  // src/exports-browser.ts
3244
3472
  var exports_browser_exports = {};
3245
3473
  __export(exports_browser_exports, {
3474
+ Attachment: () => Attachment,
3246
3475
  BraintrustState: () => BraintrustState,
3247
3476
  BraintrustStream: () => BraintrustStream,
3248
3477
  Dataset: () => Dataset,
@@ -3256,6 +3485,7 @@ __export(exports_browser_exports, {
3256
3485
  ReadonlyExperiment: () => ReadonlyExperiment,
3257
3486
  SpanImpl: () => SpanImpl,
3258
3487
  X_CACHED_HEADER: () => X_CACHED_HEADER,
3488
+ _exportsForTestingOnly: () => _exportsForTestingOnly,
3259
3489
  _internalGetGlobalState: () => _internalGetGlobalState,
3260
3490
  _internalSetInitialState: () => _internalSetInitialState,
3261
3491
  braintrustStreamChunkSchema: () => braintrustStreamChunkSchema,
@@ -3304,7 +3534,7 @@ async function invoke(args) {
3304
3534
  apiKey,
3305
3535
  appUrl,
3306
3536
  forceLogin,
3307
- fetch,
3537
+ fetch: fetch2,
3308
3538
  input,
3309
3539
  messages,
3310
3540
  parent: parentArg,
@@ -3320,7 +3550,7 @@ async function invoke(args) {
3320
3550
  apiKey,
3321
3551
  appUrl,
3322
3552
  forceLogin,
3323
- fetch
3553
+ fetch: fetch2
3324
3554
  });
3325
3555
  const parent = parentArg ? typeof parentArg === "string" ? parentArg : await parentArg.export() : await getSpanParentObject().export();
3326
3556
  const functionId = import_typespecs3.functionIdSchema.safeParse({
@@ -3748,6 +3978,7 @@ configureBrowser();
3748
3978
  var browser_default = exports_browser_exports;
3749
3979
  // Annotate the CommonJS export names for ESM import in node:
3750
3980
  0 && (module.exports = {
3981
+ Attachment,
3751
3982
  BraintrustState,
3752
3983
  BraintrustStream,
3753
3984
  Dataset,
@@ -3761,6 +3992,7 @@ var browser_default = exports_browser_exports;
3761
3992
  ReadonlyExperiment,
3762
3993
  SpanImpl,
3763
3994
  X_CACHED_HEADER,
3995
+ _exportsForTestingOnly,
3764
3996
  _internalGetGlobalState,
3765
3997
  _internalSetInitialState,
3766
3998
  braintrustStreamChunkSchema,