braintrust 3.27.0 → 3.29.0

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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dev/dist/index.d.mts +879 -197
  3. package/dev/dist/index.d.ts +879 -197
  4. package/dev/dist/index.js +2960 -1865
  5. package/dev/dist/index.mjs +2217 -1122
  6. package/dist/apply-auto-instrumentation.js +345 -260
  7. package/dist/apply-auto-instrumentation.mjs +137 -52
  8. package/dist/auto-instrumentations/bundler/esbuild.cjs +124 -7
  9. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  10. package/dist/auto-instrumentations/bundler/next.cjs +124 -7
  11. package/dist/auto-instrumentations/bundler/next.mjs +3 -3
  12. package/dist/auto-instrumentations/bundler/rollup.cjs +124 -7
  13. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  14. package/dist/auto-instrumentations/bundler/vite.cjs +124 -7
  15. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  16. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +124 -7
  17. package/dist/auto-instrumentations/bundler/webpack.cjs +124 -7
  18. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  19. package/dist/auto-instrumentations/{chunk-ZNHTSSGI.mjs → chunk-AOIYCVEL.mjs} +34 -3
  20. package/dist/auto-instrumentations/{chunk-XEYKUBLY.mjs → chunk-DKTGDNA7.mjs} +91 -5
  21. package/dist/auto-instrumentations/{chunk-BW33ULMW.mjs → chunk-OMCZ3MV2.mjs} +1 -1
  22. package/dist/auto-instrumentations/hook.mjs +1186 -161
  23. package/dist/auto-instrumentations/index.cjs +34 -3
  24. package/dist/auto-instrumentations/index.mjs +1 -1
  25. package/dist/browser.d.mts +1388 -75
  26. package/dist/browser.d.ts +1388 -75
  27. package/dist/browser.js +3954 -1124
  28. package/dist/browser.mjs +3954 -1124
  29. package/dist/{chunk-MF7NU6BT.js → chunk-6Z5S7VOU.js} +175 -25
  30. package/dist/{chunk-CZM5JIQL.mjs → chunk-7FA6VP2S.mjs} +172 -22
  31. package/dist/{chunk-YKD22IMR.mjs → chunk-M6XPNJC4.mjs} +2102 -1113
  32. package/dist/{chunk-QRHGVBKU.js → chunk-XLLGRXGR.js} +3273 -2284
  33. package/dist/cli.js +6353 -1483
  34. package/dist/edge-light.d.mts +1 -1
  35. package/dist/edge-light.d.ts +1 -1
  36. package/dist/edge-light.js +3954 -1124
  37. package/dist/edge-light.mjs +3954 -1124
  38. package/dist/index.d.mts +1972 -659
  39. package/dist/index.d.ts +1972 -659
  40. package/dist/index.js +2453 -676
  41. package/dist/index.mjs +1973 -196
  42. package/dist/instrumentation/index.d.mts +738 -15
  43. package/dist/instrumentation/index.d.ts +738 -15
  44. package/dist/instrumentation/index.js +2791 -874
  45. package/dist/instrumentation/index.mjs +2791 -874
  46. package/dist/vitest-evals-reporter.js +16 -16
  47. package/dist/vitest-evals-reporter.mjs +2 -2
  48. package/dist/workerd.d.mts +1 -1
  49. package/dist/workerd.d.ts +1 -1
  50. package/dist/workerd.js +3954 -1124
  51. package/dist/workerd.mjs +3954 -1124
  52. package/package.json +2 -3
  53. package/util/dist/index.d.mts +1596 -109
  54. package/util/dist/index.d.ts +1596 -109
package/dist/index.mjs CHANGED
@@ -55,9 +55,11 @@ import {
55
55
  X_CACHED_HEADER,
56
56
  _exportsForTestingOnly,
57
57
  _internalGetGlobalState,
58
+ _internalResumeSpan,
58
59
  _internalSetInitialState,
59
60
  _internalStartSpanWithInitialMerge,
60
61
  addAzureBlobHeaders,
62
+ base64ToUint8Array,
61
63
  braintrustAISDKTelemetry,
62
64
  braintrustFlueInstrumentation,
63
65
  braintrustFlueObserver,
@@ -72,7 +74,6 @@ import {
72
74
  currentLogger,
73
75
  currentSpan,
74
76
  currentWorkflowAgentWrapperSpan,
75
- debugLogger,
76
77
  deepCopyEvent,
77
78
  deserializePlainStringAsJSON,
78
79
  devNullWritableStream,
@@ -105,7 +106,6 @@ import {
105
106
  loginToState,
106
107
  logs3OverflowUploadSchema,
107
108
  makeScorerPropagatedEvent,
108
- mergeDicts,
109
109
  newId,
110
110
  parseCachedHeader,
111
111
  parseTemplateFormat,
@@ -127,8 +127,10 @@ import {
127
127
  summarize,
128
128
  summarizeAndFlush,
129
129
  templateRegistry,
130
+ toNumericMetrics,
130
131
  traceable,
131
132
  traced,
133
+ uint8ArrayToBase64,
132
134
  updateSpan,
133
135
  uploadLogs3OverflowPayload,
134
136
  utf8ByteLength,
@@ -141,7 +143,7 @@ import {
141
143
  wrapMastraAgent,
142
144
  wrapTraced,
143
145
  zodToJsonSchema
144
- } from "./chunk-YKD22IMR.mjs";
146
+ } from "./chunk-M6XPNJC4.mjs";
145
147
  import {
146
148
  INSTRUMENTATION_NAMES,
147
149
  __export,
@@ -153,6 +155,7 @@ import {
153
155
  cloudflareThinkChannels,
154
156
  cohereChannels,
155
157
  cursorSDKChannels,
158
+ debugLogger,
156
159
  genkitChannels,
157
160
  gitHubCopilotChannels,
158
161
  googleADKChannels,
@@ -172,8 +175,9 @@ import {
172
175
  openRouterChannels,
173
176
  piCodingAgentChannels,
174
177
  strandsAgentSDKChannels,
178
+ voyageAIChannels,
175
179
  withSpanInstrumentationName
176
- } from "./chunk-CZM5JIQL.mjs";
180
+ } from "./chunk-7FA6VP2S.mjs";
177
181
 
178
182
  // src/exports.ts
179
183
  var exports_exports = {};
@@ -186,6 +190,8 @@ __export(exports_exports, {
186
190
  BRAINTRUST_PARENT_KEY: () => BRAINTRUST_PARENT_KEY,
187
191
  BaseAttachment: () => BaseAttachment,
188
192
  BaseExperiment: () => BaseExperiment,
193
+ BatchScorer: () => BatchScorer,
194
+ BatchTask: () => BatchTask,
189
195
  BraintrustLangChainCallbackHandler: () => BraintrustLangChainCallbackHandler,
190
196
  BraintrustMiddleware: () => BraintrustMiddleware,
191
197
  BraintrustObservabilityExporter: () => BraintrustObservabilityExporter,
@@ -199,6 +205,8 @@ __export(exports_exports, {
199
205
  DEFAULT_MAX_REQUEST_SIZE: () => DEFAULT_MAX_REQUEST_SIZE,
200
206
  Dataset: () => Dataset,
201
207
  DatasetPipeline: () => DatasetPipeline,
208
+ DurableEvalMemoryStore: () => DurableEvalMemoryStore,
209
+ DurableEvalRedisStore: () => DurableEvalRedisStore,
202
210
  ERR_PERMALINK: () => ERR_PERMALINK,
203
211
  Eval: () => Eval,
204
212
  EvalResultWithSummary: () => EvalResultWithSummary,
@@ -256,6 +264,7 @@ __export(exports_exports, {
256
264
  currentSpan: () => currentSpan,
257
265
  deepCopyEvent: () => deepCopyEvent,
258
266
  defaultErrorScoreHandler: () => defaultErrorScoreHandler,
267
+ defineDurableEval: () => defineDurableEval,
259
268
  deserializePlainStringAsJSON: () => deserializePlainStringAsJSON,
260
269
  devNullWritableStream: () => devNullWritableStream,
261
270
  evaluatorDefinitionSchema: () => evaluatorDefinitionSchema,
@@ -351,7 +360,8 @@ __export(exports_exports, {
351
360
  wrapPiCodingAgentSDK: () => wrapPiCodingAgentSDK,
352
361
  wrapStrandsAgentSDK: () => wrapStrandsAgentSDK,
353
362
  wrapTraced: () => wrapTraced,
354
- wrapVitest: () => wrapVitest
363
+ wrapVitest: () => wrapVitest,
364
+ wrapVoyageAI: () => wrapVoyageAI
355
365
  });
356
366
 
357
367
  // src/sandbox.ts
@@ -895,6 +905,13 @@ function wrapAISDK(aiSDK, options = {}) {
895
905
  switch (prop) {
896
906
  case "generateText":
897
907
  return wrapGenerateText(typedAISDK.generateText, options, typedAISDK);
908
+ case "generateImage":
909
+ case "experimental_generateImage":
910
+ return typeof original === "function" ? wrapGenerateImage(
911
+ original,
912
+ options,
913
+ typedAISDK
914
+ ) : original;
898
915
  case "streamText":
899
916
  return wrapStreamText(typedAISDK.streamText, options, typedAISDK);
900
917
  case "generateObject":
@@ -1131,6 +1148,32 @@ var wrapGenerateObject = (generateObject, options = {}, aiSDK) => {
1131
1148
  options
1132
1149
  );
1133
1150
  };
1151
+ var wrapGenerateImage = (generateImage, options = {}, aiSDK) => {
1152
+ return makeGenerateImageWrapper(generateImage, { aiSDK }, options);
1153
+ };
1154
+ var makeGenerateImageWrapper = (generateImage, contextOptions = {}, options = {}) => {
1155
+ const wrapper = async function(allParams) {
1156
+ const { span_info, ...params } = allParams;
1157
+ const tracedParams = { ...params };
1158
+ return aiSDKChannels.generateImage.tracePromise(
1159
+ () => generateImage(tracedParams),
1160
+ createAISDKChannelContext(tracedParams, {
1161
+ aiSDK: contextOptions.aiSDK,
1162
+ denyOutputPaths: options.denyOutputPaths,
1163
+ self: contextOptions.self,
1164
+ span_info: mergeSpanInfo(span_info, {
1165
+ name: "generateImage",
1166
+ spanType: contextOptions.spanType
1167
+ })
1168
+ })
1169
+ );
1170
+ };
1171
+ Object.defineProperty(wrapper, "name", {
1172
+ value: "generateImage",
1173
+ writable: false
1174
+ });
1175
+ return wrapper;
1176
+ };
1134
1177
  var makeEmbedWrapper = (channel, name, embed, contextOptions = {}, options = {}) => {
1135
1178
  const wrapper = async function(allParams) {
1136
1179
  const { span_info, ...params } = allParams;
@@ -1612,7 +1655,7 @@ function normalizeUsageMetrics(usage, provider, providerMetadata) {
1612
1655
  cacheCreationTokens
1613
1656
  );
1614
1657
  Object.assign(metrics, cacheTokens);
1615
- Object.assign(metrics, finalizeAnthropicTokens(metrics));
1658
+ return toNumericMetrics(finalizeAnthropicTokens(metrics));
1616
1659
  }
1617
1660
  }
1618
1661
  return metrics;
@@ -1881,7 +1924,7 @@ function braintrustEveHook(options) {
1881
1924
  }
1882
1925
  };
1883
1926
  }
1884
- function braintrustEveInstrumentation(options) {
1927
+ function createLegacyEveInstrumentation(options) {
1885
1928
  const state = options.defineState(EVE_TRACE_STATE_KEY, emptyEveTraceState);
1886
1929
  return {
1887
1930
  events: {
@@ -3269,8 +3312,10 @@ function capturedModelInput(modelInput) {
3269
3312
  const value = [];
3270
3313
  if (typeof instructions === "string") {
3271
3314
  value.push({ content: instructions, role: "system" });
3272
- } else if (instructions) {
3315
+ } else if (Array.isArray(instructions)) {
3273
3316
  value.push(...instructions.map(capturedEveModelMessage));
3317
+ } else if (instructions) {
3318
+ value.push(capturedEveModelMessage(instructions));
3274
3319
  }
3275
3320
  value.push(...messages.map(capturedEveModelMessage));
3276
3321
  try {
@@ -3514,6 +3559,464 @@ async function deterministicEveId(...parts) {
3514
3559
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3515
3560
  }
3516
3561
 
3562
+ // src/instrumentation/plugins/eve-provider.ts
3563
+ var MAX_EVE_PROVIDER_CACHE_ENTRIES = 1e4;
3564
+ function createEveInstrumentationProvider(options = {}) {
3565
+ const bridge = new EveProviderBridge(options.metadata);
3566
+ return {
3567
+ capture: "content",
3568
+ events: {
3569
+ "action.completed": (event, context) => bridge.handleActionTerminal(event, context),
3570
+ "action.failed": (event, context) => bridge.handleActionTerminal(event, context),
3571
+ "action.started": (event, context) => bridge.handleActionStarted(event, context),
3572
+ "model.call.completed": (event, context) => bridge.handleModelTerminal(event, context),
3573
+ "model.call.failed": (event, context) => bridge.handleModelTerminal(event, context),
3574
+ "model.call.started": (event, context) => bridge.handleModelStarted(event, context),
3575
+ "step.attempt.completed": (event) => bridge.handleStepAttemptTerminal(event),
3576
+ "step.attempt.failed": (event) => bridge.handleStepAttemptTerminal(event),
3577
+ "turn.cancelled": (event, context) => bridge.handleTurnTerminal(event, context),
3578
+ "turn.completed": (event, context) => bridge.handleTurnTerminal(event, context),
3579
+ "turn.failed": (event, context) => bridge.handleTurnTerminal(event, context),
3580
+ "turn.started": (event, context) => bridge.handleTurnStarted(event, context)
3581
+ },
3582
+ flush,
3583
+ setup: options.setup
3584
+ };
3585
+ }
3586
+ var EveProviderBridge = class {
3587
+ constructor(metadata) {
3588
+ this.metadata = metadata;
3589
+ }
3590
+ metadata;
3591
+ activeActions = /* @__PURE__ */ new Map();
3592
+ activeModels = /* @__PURE__ */ new Map();
3593
+ modelsByAttempt = /* @__PURE__ */ new Map();
3594
+ settledOperations = new LRUCache({
3595
+ max: MAX_EVE_PROVIDER_CACHE_ENTRIES
3596
+ });
3597
+ turns = new LRUCache({
3598
+ max: MAX_EVE_PROVIDER_CACHE_ENTRIES
3599
+ });
3600
+ async handleTurnStarted(event, context) {
3601
+ await this.contain("turn start", async () => {
3602
+ if (this.settledOperations.has(event.idempotencyKey)) {
3603
+ context.state.set({ skip: true });
3604
+ return;
3605
+ }
3606
+ const { rowId, spanId } = await generateEveIds(
3607
+ "turn",
3608
+ event.idempotencyKey
3609
+ );
3610
+ const key = turnKey2(event.sessionId, event.turnId);
3611
+ const stored = storedSpan(context);
3612
+ if (stored && "exported" in stored && stored.rootSpanId) {
3613
+ this.turns.set(key, { rootSpanId: stored.rootSpanId, spanId });
3614
+ return;
3615
+ }
3616
+ let rootSpanId = spanId;
3617
+ let parentSpanId;
3618
+ if (event.parentLineage) {
3619
+ const parentTurnKey = turnKey2(
3620
+ event.parentLineage.sessionId,
3621
+ event.parentLineage.turnId
3622
+ );
3623
+ const parentTurn = this.turns.get(parentTurnKey);
3624
+ rootSpanId = parentTurn?.rootSpanId ?? // Eve propagates the root session through every nested subagent. Use
3625
+ // it instead of the immediate parent session when no ancestor ran
3626
+ // in-process.
3627
+ (await generateEveIds(
3628
+ "turn",
3629
+ turnIdempotencyKey(
3630
+ event.rootSessionId,
3631
+ event.parentLineage.turnId
3632
+ )
3633
+ )).spanId;
3634
+ parentSpanId = (await generateEveIds(
3635
+ "subagent",
3636
+ actionIdempotencyKey(
3637
+ event.parentLineage.sessionId,
3638
+ event.parentLineage.turnId,
3639
+ event.parentLineage.callId
3640
+ )
3641
+ )).spanId;
3642
+ }
3643
+ const metadata = this.spanMetadata(event.sessionId);
3644
+ const span = await this.startSpan(
3645
+ context,
3646
+ {
3647
+ event: { id: rowId, metadata },
3648
+ name: "eve.turn",
3649
+ parentSpanIds: parentSpanId ? { rootSpanId, spanId: parentSpanId } : { parentSpanIds: [], rootSpanId },
3650
+ spanAttributes: { type: "task" /* TASK */ },
3651
+ spanId
3652
+ },
3653
+ rootSpanId
3654
+ );
3655
+ span?.log({ metadata });
3656
+ this.turns.set(key, {
3657
+ rootSpanId,
3658
+ spanId
3659
+ });
3660
+ });
3661
+ }
3662
+ async handleTurnTerminal(event, context) {
3663
+ await this.contain("turn terminal", async () => {
3664
+ if (this.settledOperations.has(event.idempotencyKey)) {
3665
+ context.state.set(void 0);
3666
+ return;
3667
+ }
3668
+ const error = event.type === "turn.failed" ? event.error : void 0;
3669
+ this.drainActionsForTurn(event.sessionId, event.turnId, error);
3670
+ const stored = storedSpan(context);
3671
+ if (stored && "exported" in stored) {
3672
+ updateSpan({
3673
+ exported: stored.exported,
3674
+ ...error !== void 0 ? { error } : {},
3675
+ metrics: { end: Date.now() / 1e3 }
3676
+ });
3677
+ }
3678
+ this.settledOperations.set(event.idempotencyKey, true);
3679
+ context.state.set(void 0);
3680
+ this.turns.delete(turnKey2(event.sessionId, event.turnId));
3681
+ });
3682
+ }
3683
+ async handleModelStarted(event, context) {
3684
+ await this.contain("model start", async () => {
3685
+ if (this.settledOperations.has(event.idempotencyKey)) {
3686
+ context.state.set({ skip: true });
3687
+ return;
3688
+ }
3689
+ const parent = await this.parentForScope(event.scope);
3690
+ const { rowId, spanId } = await generateEveIds(
3691
+ "step",
3692
+ event.idempotencyKey
3693
+ );
3694
+ const input = event.input ? capturedModelInput(event.input) : void 0;
3695
+ const metadata = {
3696
+ ...this.spanMetadata(event.scope.sessionId),
3697
+ model: event.model.modelId,
3698
+ provider: event.model.provider
3699
+ };
3700
+ const span = await this.startSpan(context, {
3701
+ event: {
3702
+ id: rowId,
3703
+ ...input !== void 0 ? { input } : {},
3704
+ metadata
3705
+ },
3706
+ name: "eve.step",
3707
+ parentSpanIds: parent,
3708
+ spanAttributes: { type: "llm" /* LLM */ },
3709
+ spanId
3710
+ });
3711
+ if (!span) return;
3712
+ span.log({ ...input !== void 0 ? { input } : {}, metadata });
3713
+ this.activeModels.set(event.idempotencyKey, {
3714
+ span,
3715
+ turnKey: turnKey2(event.scope.sessionId, event.scope.turnId)
3716
+ });
3717
+ const keys = this.modelsByAttempt.get(event.scope.attemptId) ?? /* @__PURE__ */ new Set();
3718
+ keys.add(event.idempotencyKey);
3719
+ this.modelsByAttempt.set(event.scope.attemptId, keys);
3720
+ });
3721
+ }
3722
+ async handleModelTerminal(event, context) {
3723
+ await this.contain("model terminal", async () => {
3724
+ if (this.settledOperations.has(event.idempotencyKey)) {
3725
+ context.state.set(void 0);
3726
+ return;
3727
+ }
3728
+ const active = this.activeModels.get(event.idempotencyKey);
3729
+ if (active) {
3730
+ const span = active.span;
3731
+ if (event.type === "model.call.failed") {
3732
+ if (event.error !== void 0) span.log({ error: event.error });
3733
+ } else {
3734
+ span.log({
3735
+ metrics: usageMetrics(event.usage),
3736
+ output: modelOutput(event)
3737
+ });
3738
+ }
3739
+ span.end();
3740
+ } else {
3741
+ const stored = storedSpan(context);
3742
+ if (stored && "exported" in stored) {
3743
+ updateSpan({
3744
+ exported: stored.exported,
3745
+ ...event.type === "model.call.failed" ? event.error !== void 0 ? { error: event.error } : {} : { output: modelOutput(event) },
3746
+ metrics: {
3747
+ ...event.type === "model.call.completed" ? usageMetrics(event.usage) : {},
3748
+ end: Date.now() / 1e3
3749
+ }
3750
+ });
3751
+ }
3752
+ }
3753
+ this.settledOperations.set(event.idempotencyKey, true);
3754
+ this.forgetModel(event.scope.attemptId, event.idempotencyKey);
3755
+ context.state.set(void 0);
3756
+ });
3757
+ }
3758
+ async handleActionStarted(event, context) {
3759
+ await this.contain("action start", async () => {
3760
+ if (event.kind === "load-skill" || this.settledOperations.has(event.idempotencyKey)) {
3761
+ context.state.set({ skip: true });
3762
+ return;
3763
+ }
3764
+ const parent = await this.parentForScope(event.scope);
3765
+ const { rowId, spanId } = await generateEveIds(
3766
+ event.kind === "subagent-call" ? "subagent" : "tool",
3767
+ event.idempotencyKey
3768
+ );
3769
+ const metadata = {
3770
+ ...this.spanMetadata(event.scope.sessionId),
3771
+ "eve.action_kind": event.kind
3772
+ };
3773
+ const span = await this.startSpan(context, {
3774
+ event: {
3775
+ id: rowId,
3776
+ ...event.input !== void 0 ? { input: event.input } : {},
3777
+ metadata
3778
+ },
3779
+ name: event.name,
3780
+ parentSpanIds: parent,
3781
+ spanAttributes: { type: "tool" /* TOOL */ },
3782
+ spanId
3783
+ });
3784
+ if (!span) return;
3785
+ span.log({
3786
+ ...event.input !== void 0 ? { input: event.input } : {},
3787
+ metadata
3788
+ });
3789
+ this.activeActions.set(event.idempotencyKey, {
3790
+ span,
3791
+ turnKey: turnKey2(event.scope.sessionId, event.scope.turnId)
3792
+ });
3793
+ });
3794
+ }
3795
+ async handleActionTerminal(event, context) {
3796
+ await this.contain("action terminal", async () => {
3797
+ if (this.settledOperations.has(event.idempotencyKey)) {
3798
+ context.state.set(void 0);
3799
+ return;
3800
+ }
3801
+ const stored = storedSpan(context);
3802
+ if (stored?.skip) {
3803
+ context.state.set(void 0);
3804
+ return;
3805
+ }
3806
+ const active = this.activeActions.get(event.idempotencyKey);
3807
+ const end = finiteTimestamp(event.acceptedAtMs) ?? Date.now();
3808
+ if (active) {
3809
+ const span = active.span;
3810
+ if (event.type === "action.failed") {
3811
+ span.log({
3812
+ error: event.error ?? new Error(event.errorCode ?? `Eve action ${event.outcome}`)
3813
+ });
3814
+ } else if (event.output.type === "error") {
3815
+ span.log({
3816
+ error: event.output.error ?? new Error("Eve action returned an error")
3817
+ });
3818
+ } else {
3819
+ span.log({ output: event.output.output });
3820
+ }
3821
+ span.end({ endTime: end / 1e3 });
3822
+ } else {
3823
+ const stored2 = storedSpan(context);
3824
+ if (stored2 && "exported" in stored2) {
3825
+ updateSpan({
3826
+ exported: stored2.exported,
3827
+ ...event.type === "action.failed" ? {
3828
+ error: event.error ?? new Error(event.errorCode ?? `Eve action ${event.outcome}`)
3829
+ } : event.output.type === "error" ? {
3830
+ error: event.output.error ?? new Error("Eve action returned an error")
3831
+ } : { output: event.output.output },
3832
+ metrics: { end: end / 1e3 }
3833
+ });
3834
+ }
3835
+ }
3836
+ this.settledOperations.set(event.idempotencyKey, true);
3837
+ this.activeActions.delete(event.idempotencyKey);
3838
+ context.state.set(void 0);
3839
+ });
3840
+ }
3841
+ handleStepAttemptTerminal(event) {
3842
+ void this.contain("step attempt terminal", () => {
3843
+ const keys = this.modelsByAttempt.get(event.scope.attemptId);
3844
+ if (!keys) return;
3845
+ for (const key of keys) {
3846
+ const active = this.activeModels.get(key);
3847
+ if (!active) continue;
3848
+ if (event.type === "step.attempt.failed" && event.error !== void 0) {
3849
+ active.span.log({ error: event.error });
3850
+ }
3851
+ active.span.end();
3852
+ this.activeModels.delete(key);
3853
+ }
3854
+ this.modelsByAttempt.delete(event.scope.attemptId);
3855
+ });
3856
+ }
3857
+ async parentForScope(scope) {
3858
+ const key = turnKey2(scope.sessionId, scope.turnId);
3859
+ const known = this.turns.get(key);
3860
+ if (known) {
3861
+ return { rootSpanId: known.rootSpanId, spanId: known.spanId };
3862
+ }
3863
+ const [{ spanId }, { spanId: rootSpanId }] = await Promise.all([
3864
+ generateEveIds("turn", turnIdempotencyKey(scope.sessionId, scope.turnId)),
3865
+ generateEveIds(
3866
+ "turn",
3867
+ turnIdempotencyKey(
3868
+ scope.rootSessionId ?? scope.sessionId,
3869
+ scope.turnId
3870
+ )
3871
+ )
3872
+ ]);
3873
+ return { rootSpanId, spanId };
3874
+ }
3875
+ async startSpan(context, args, rootSpanId) {
3876
+ const span = withCurrent(
3877
+ NOOP_SPAN,
3878
+ () => _internalStartSpanWithInitialMerge(
3879
+ withSpanInstrumentationName(args ?? {}, INSTRUMENTATION_NAMES.EVE)
3880
+ )
3881
+ );
3882
+ try {
3883
+ context.state.set({
3884
+ exported: await span.export(),
3885
+ ...rootSpanId ? { rootSpanId } : {}
3886
+ });
3887
+ } catch (error) {
3888
+ debugLogger.warn("Error exporting Eve provider span:", error);
3889
+ }
3890
+ return span;
3891
+ }
3892
+ drainActionsForTurn(sessionId, turnId, error) {
3893
+ const key = turnKey2(sessionId, turnId);
3894
+ for (const [idempotencyKey, active] of this.activeActions) {
3895
+ if (active.turnKey !== key) continue;
3896
+ if (error !== void 0) active.span.log({ error });
3897
+ active.span.end();
3898
+ this.activeActions.delete(idempotencyKey);
3899
+ }
3900
+ }
3901
+ forgetModel(attemptId, idempotencyKey) {
3902
+ this.activeModels.delete(idempotencyKey);
3903
+ const keys = this.modelsByAttempt.get(attemptId);
3904
+ keys?.delete(idempotencyKey);
3905
+ if (keys?.size === 0) this.modelsByAttempt.delete(attemptId);
3906
+ }
3907
+ spanMetadata(sessionId) {
3908
+ return {
3909
+ ...this.metadata ?? {},
3910
+ "eve.session_id": sessionId
3911
+ };
3912
+ }
3913
+ async contain(operation, fn) {
3914
+ try {
3915
+ await fn();
3916
+ } catch (error) {
3917
+ debugLogger.warn(`Error in Eve provider ${operation}:`, error);
3918
+ }
3919
+ }
3920
+ };
3921
+ function storedSpan(context) {
3922
+ const value = context.state.get();
3923
+ if (!isObject(value)) return void 0;
3924
+ if (value["skip"] === true) return { skip: true };
3925
+ return typeof value["exported"] === "string" ? {
3926
+ exported: value["exported"],
3927
+ ...typeof value["rootSpanId"] === "string" && value["rootSpanId"].length > 0 ? { rootSpanId: value["rootSpanId"] } : {}
3928
+ } : void 0;
3929
+ }
3930
+ function modelOutput(event) {
3931
+ const content = event.content ?? [];
3932
+ let text = "";
3933
+ const reasoning = [];
3934
+ const toolCalls2 = [];
3935
+ for (const part of content) {
3936
+ if (part.type === "text") {
3937
+ text += part.text;
3938
+ } else if (part.type === "reasoning" && part.text.trim().length > 0) {
3939
+ reasoning.push({ content: part.text });
3940
+ } else if (part.type === "tool-call") {
3941
+ toolCalls2.push({
3942
+ function: {
3943
+ arguments: safeJsonStringify(part.input),
3944
+ name: part.toolName
3945
+ },
3946
+ id: part.callId,
3947
+ type: "function"
3948
+ });
3949
+ }
3950
+ }
3951
+ return [
3952
+ {
3953
+ finish_reason: normalizeFinishReason2(event.finishReason),
3954
+ index: 0,
3955
+ message: {
3956
+ content: text || null,
3957
+ ...reasoning.length > 0 ? { reasoning } : {},
3958
+ role: "assistant",
3959
+ ...toolCalls2.length > 0 ? { tool_calls: toolCalls2 } : {}
3960
+ }
3961
+ }
3962
+ ];
3963
+ }
3964
+ function usageMetrics(usage) {
3965
+ const promptTokens = nonNegativeNumber(usage.inputTokens);
3966
+ const completionTokens = nonNegativeNumber(usage.outputTokens);
3967
+ const cachedTokens = nonNegativeNumber(
3968
+ usage.inputTokenDetails?.cacheReadTokens
3969
+ );
3970
+ const cacheCreationTokens = nonNegativeNumber(
3971
+ usage.inputTokenDetails?.cacheWriteTokens
3972
+ );
3973
+ return {
3974
+ ...promptTokens !== void 0 ? { prompt_tokens: promptTokens } : {},
3975
+ ...completionTokens !== void 0 ? { completion_tokens: completionTokens } : {},
3976
+ ...promptTokens !== void 0 && completionTokens !== void 0 ? { tokens: promptTokens + completionTokens } : {},
3977
+ ...cachedTokens !== void 0 ? { prompt_cached_tokens: cachedTokens } : {},
3978
+ ...cacheCreationTokens !== void 0 ? { prompt_cache_creation_tokens: cacheCreationTokens } : {}
3979
+ };
3980
+ }
3981
+ function nonNegativeNumber(value) {
3982
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
3983
+ }
3984
+ function finiteTimestamp(value) {
3985
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3986
+ }
3987
+ function normalizeFinishReason2(value) {
3988
+ if (value === "content-filter") return "content_filter";
3989
+ if (value === "tool-calls") return "tool_calls";
3990
+ return value;
3991
+ }
3992
+ function safeJsonStringify(value) {
3993
+ try {
3994
+ return JSON.stringify(value) ?? "null";
3995
+ } catch {
3996
+ return "null";
3997
+ }
3998
+ }
3999
+ function turnKey2(sessionId, turnId) {
4000
+ return `${sessionId}:${turnId}`;
4001
+ }
4002
+ function turnIdempotencyKey(sessionId, turnId) {
4003
+ return `turn:${sessionId}:${turnId}`;
4004
+ }
4005
+ function actionIdempotencyKey(sessionId, turnId, callId) {
4006
+ return `action:${sessionId}:${turnId}:${callId}`;
4007
+ }
4008
+
4009
+ // src/instrumentation/plugins/eve-instrumentation.ts
4010
+ var EVE_INSTRUMENTATION_PROVIDER = /* @__PURE__ */ Symbol.for("eve.instrumentation.provider");
4011
+ function braintrustEveInstrumentation(options) {
4012
+ const definition = "defineState" in options ? createLegacyEveInstrumentation(options) : createEveInstrumentationProvider(options);
4013
+ const declaration = {
4014
+ ...definition,
4015
+ [EVE_INSTRUMENTATION_PROVIDER]: true
4016
+ };
4017
+ return declaration;
4018
+ }
4019
+
3517
4020
  // src/typed-instrumentation-helpers.ts
3518
4021
  var TypedApplyProxy = Proxy;
3519
4022
 
@@ -3695,11 +4198,11 @@ function wrapClaudeAgentQuery(queryFn, defaultThis) {
3695
4198
  }
3696
4199
  };
3697
4200
  const invocationTarget = thisArg === proxy || thisArg === void 0 ? defaultThis ?? thisArg : thisArg;
3698
- return claudeAgentSDKChannels.query.traceSync(
3699
- () => Reflect.apply(target, invocationTarget, [wrappedParams]),
3700
- // The channel carries no extra context fields, but the generated
3701
- // StartOf<> type for Record<string, never> is overly strict here.
3702
- { arguments: [wrappedParams] }
4201
+ return claudeAgentSDKChannels.query.invoke(
4202
+ target,
4203
+ invocationTarget,
4204
+ [wrappedParams],
4205
+ {}
3703
4206
  );
3704
4207
  }
3705
4208
  });
@@ -4314,13 +4817,11 @@ function wrapAgentInstance(agent) {
4314
4817
  if (prop === "stream" && typeof value === "function") {
4315
4818
  return function(args, options) {
4316
4819
  const callArgs = [args, options];
4317
- return strandsAgentSDKChannels.agentStream.traceSync(
4318
- () => Reflect.apply(value, target, callArgs),
4319
- {
4320
- agent: proxy,
4321
- arguments: callArgs,
4322
- self: proxy
4323
- }
4820
+ return strandsAgentSDKChannels.agentStream.invoke(
4821
+ value,
4822
+ target,
4823
+ callArgs,
4824
+ { agent: proxy }
4324
4825
  );
4325
4826
  };
4326
4827
  }
@@ -4351,12 +4852,12 @@ function wrapMultiAgentInstance(orchestrator, kind) {
4351
4852
  return function(input, options) {
4352
4853
  const callArgs = [input, options];
4353
4854
  const channel = kind === "graph" ? strandsAgentSDKChannels.graphStream : strandsAgentSDKChannels.swarmStream;
4354
- return channel.traceSync(
4355
- () => Reflect.apply(value, target, callArgs),
4855
+ return channel.invoke(
4856
+ value,
4857
+ target,
4858
+ callArgs,
4356
4859
  {
4357
- arguments: callArgs,
4358
- orchestrator: proxy,
4359
- self: proxy
4860
+ orchestrator: proxy
4360
4861
  }
4361
4862
  );
4362
4863
  };
@@ -5725,6 +6226,84 @@ function wrapRerank3(rerank) {
5725
6226
  });
5726
6227
  }
5727
6228
 
6229
+ // src/wrappers/voyageai.ts
6230
+ function wrapVoyageAI(client) {
6231
+ if (!isSupportedVoyageAIClient(client)) {
6232
+ console.warn("Unsupported Voyage AI library. Not wrapping.");
6233
+ return client;
6234
+ }
6235
+ return voyageAIProxy(client);
6236
+ }
6237
+ var voyageAIProxyCache = /* @__PURE__ */ new WeakMap();
6238
+ function isObject2(value) {
6239
+ return typeof value === "object" && value !== null;
6240
+ }
6241
+ function hasFunction5(value, name) {
6242
+ return typeof Reflect.get(value, name) === "function";
6243
+ }
6244
+ function isSupportedVoyageAIClient(value) {
6245
+ if (!isObject2(value)) {
6246
+ return false;
6247
+ }
6248
+ return hasFunction5(value, "embed") || hasFunction5(value, "multimodalEmbed") || hasFunction5(value, "rerank") || hasFunction5(value, "contextualizedEmbed");
6249
+ }
6250
+ function voyageAIProxy(client) {
6251
+ const cached = voyageAIProxyCache.get(client);
6252
+ if (cached) {
6253
+ return cached;
6254
+ }
6255
+ const proxy = new Proxy(client, {
6256
+ get(target, prop, receiver) {
6257
+ switch (prop) {
6258
+ case "embed":
6259
+ if (typeof target.embed !== "function") {
6260
+ return target.embed;
6261
+ }
6262
+ return (request, options) => voyageAIChannels.embed.invoke(
6263
+ target.embed,
6264
+ target,
6265
+ [request, options],
6266
+ {}
6267
+ );
6268
+ case "multimodalEmbed":
6269
+ if (typeof target.multimodalEmbed !== "function") {
6270
+ return target.multimodalEmbed;
6271
+ }
6272
+ return (request, options) => voyageAIChannels.multimodalEmbed.invoke(
6273
+ target.multimodalEmbed,
6274
+ target,
6275
+ [request, options],
6276
+ {}
6277
+ );
6278
+ case "rerank":
6279
+ if (typeof target.rerank !== "function") {
6280
+ return target.rerank;
6281
+ }
6282
+ return (request, options) => voyageAIChannels.rerank.invoke(
6283
+ target.rerank,
6284
+ target,
6285
+ [request, options],
6286
+ {}
6287
+ );
6288
+ case "contextualizedEmbed":
6289
+ if (typeof target.contextualizedEmbed !== "function") {
6290
+ return target.contextualizedEmbed;
6291
+ }
6292
+ return (request, options) => voyageAIChannels.contextualizedEmbed.invoke(
6293
+ target.contextualizedEmbed,
6294
+ target,
6295
+ [request, options],
6296
+ {}
6297
+ );
6298
+ default:
6299
+ return Reflect.get(target, prop, receiver);
6300
+ }
6301
+ }
6302
+ });
6303
+ voyageAIProxyCache.set(client, proxy);
6304
+ return proxy;
6305
+ }
6306
+
5728
6307
  // src/wrappers/groq.ts
5729
6308
  function wrapGroq(groq) {
5730
6309
  if (isSupportedGroqClient(groq)) {
@@ -5736,14 +6315,14 @@ function wrapGroq(groq) {
5736
6315
  function isRecord4(value) {
5737
6316
  return typeof value === "object" && value !== null;
5738
6317
  }
5739
- function hasFunction5(value, methodName) {
6318
+ function hasFunction6(value, methodName) {
5740
6319
  return isRecord4(value) && methodName in value && typeof value[methodName] === "function";
5741
6320
  }
5742
6321
  function hasChat2(value) {
5743
- return isRecord4(value) && isRecord4(value.completions) && hasFunction5(value.completions, "create");
6322
+ return isRecord4(value) && isRecord4(value.completions) && hasFunction6(value.completions, "create");
5744
6323
  }
5745
6324
  function hasEmbeddings2(value) {
5746
- return hasFunction5(value, "create");
6325
+ return hasFunction6(value, "create");
5747
6326
  }
5748
6327
  function isSupportedGroqClient(value) {
5749
6328
  return isRecord4(value) && (value.chat !== void 0 && hasChat2(value.chat) || value.embeddings !== void 0 && hasEmbeddings2(value.embeddings));
@@ -6230,9 +6809,6 @@ var VitestContextManager = class {
6230
6809
  getCurrentContext() {
6231
6810
  return this.contextStorage.getStore();
6232
6811
  }
6233
- setContext(context) {
6234
- this.contextStorage.enterWith(context);
6235
- }
6236
6812
  runInContext(context, callback) {
6237
6813
  return this.contextStorage.run(context, callback);
6238
6814
  }
@@ -6285,7 +6861,7 @@ async function flushExperimentWithSync(context, config) {
6285
6861
  // src/wrappers/shared/scorers.ts
6286
6862
  async function runScorers(args) {
6287
6863
  const { scorers, output, expected, input, metadata, span } = args;
6288
- const scorerArgs = {
6864
+ const scorerArgs2 = {
6289
6865
  output,
6290
6866
  expected,
6291
6867
  input,
@@ -6294,7 +6870,7 @@ async function runScorers(args) {
6294
6870
  await Promise.all(
6295
6871
  scorers.map(async (scorer) => {
6296
6872
  try {
6297
- const result = await scorer(scorerArgs);
6873
+ const result = await scorer(scorerArgs2);
6298
6874
  const scores = normalizeScores(result);
6299
6875
  if (scores.length > 0) {
6300
6876
  const accScores = {};
@@ -6577,8 +7153,7 @@ function wrapDescribe(originalDescribe, config, afterAll) {
6577
7153
  if (config.onProgress) {
6578
7154
  config.onProgress({ type: "suite_start", suiteName });
6579
7155
  }
6580
- contextManager.setContext(lazyContext);
6581
- factory();
7156
+ contextManager.runInContext(lazyContext, factory);
6582
7157
  if (afterAll) {
6583
7158
  afterAll(async () => {
6584
7159
  await flushExperimentWithSync(context, config);
@@ -8653,6 +9228,26 @@ async function getExperimentParametersRef(parameters) {
8653
9228
  version: resolvedParameters.version
8654
9229
  };
8655
9230
  }
9231
+ async function _internalInitEvaluatorExperiment(projectName, evaluator, data, options = {}) {
9232
+ if (options.disabled) return null;
9233
+ const { baseExperiment } = callEvaluatorData(data);
9234
+ const parameters = await getExperimentParametersRef(evaluator.parameters);
9235
+ return initExperiment2(evaluator.state, {
9236
+ ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: projectName },
9237
+ experiment: options.experimentName ?? evaluator.experimentName,
9238
+ description: evaluator.description,
9239
+ metadata: evaluator.metadata,
9240
+ tags: evaluator.tags,
9241
+ isPublic: evaluator.isPublic,
9242
+ update: options.update ?? evaluator.update,
9243
+ baseExperiment: evaluator.baseExperimentName ?? baseExperiment,
9244
+ baseExperimentId: evaluator.baseExperimentId,
9245
+ gitMetadataSettings: evaluator.gitMetadataSettings,
9246
+ repoInfo: evaluator.repoInfo,
9247
+ dataset: Dataset.isDataset(data) ? data : void 0,
9248
+ parameters
9249
+ });
9250
+ }
8656
9251
  function callEvaluatorData(data) {
8657
9252
  const dataResult = typeof data === "function" ? data() : data;
8658
9253
  let baseExperiment = void 0;
@@ -8670,6 +9265,48 @@ function isAsyncIterable2(value) {
8670
9265
  function isIterable(value) {
8671
9266
  return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
8672
9267
  }
9268
+ async function _internalResolveEvaluatorData(evaluator, experiment) {
9269
+ if (typeof evaluator.data === "string") {
9270
+ throw new Error("Unimplemented: string data paths");
9271
+ }
9272
+ let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
9273
+ if ("_type" in dataResult) {
9274
+ if (dataResult._type !== "BaseExperiment") {
9275
+ throw new Error("Invalid _type");
9276
+ }
9277
+ if (!experiment) {
9278
+ throw new Error(
9279
+ "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
9280
+ );
9281
+ }
9282
+ let name = dataResult.name;
9283
+ if (isEmpty(name)) {
9284
+ const baseExperiment = await experiment.fetchBaseExperiment();
9285
+ if (!baseExperiment) {
9286
+ throw new Error("BaseExperiment() failed to fetch base experiment");
9287
+ }
9288
+ name = baseExperiment.name;
9289
+ }
9290
+ dataResult = initExperiment2(evaluator.state, {
9291
+ ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: evaluator.projectName },
9292
+ experiment: name,
9293
+ open: true
9294
+ }).asDataset();
9295
+ }
9296
+ const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
9297
+ if (isAsyncIterable2(resolvedDataResult)) {
9298
+ return resolvedDataResult;
9299
+ }
9300
+ if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {
9301
+ const iterable = resolvedDataResult;
9302
+ return (async function* () {
9303
+ for (const datum of iterable) yield datum;
9304
+ })();
9305
+ }
9306
+ throw new Error(
9307
+ "Evaluator data must be an array, iterable, or async iterable"
9308
+ );
9309
+ }
8673
9310
  globalThis._evals = {
8674
9311
  functions: [],
8675
9312
  prompts: [],
@@ -8716,25 +9353,13 @@ async function Eval(name, evaluator, reporterOrOpts) {
8716
9353
  }
8717
9354
  const resolvedReporter = options.reporter || defaultReporter;
8718
9355
  try {
8719
- const { data, baseExperiment: defaultBaseExperiment } = callEvaluatorData(
8720
- evaluator.data
9356
+ const { data } = callEvaluatorData(evaluator.data);
9357
+ const experiment = await _internalInitEvaluatorExperiment(
9358
+ name,
9359
+ evaluator,
9360
+ data,
9361
+ { disabled: Boolean(options.parent || options.noSendLogs) }
8721
9362
  );
8722
- const parameters = await getExperimentParametersRef(evaluator.parameters);
8723
- const experiment = options.parent || options.noSendLogs ? null : initExperiment2(evaluator.state, {
8724
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: name },
8725
- experiment: evaluator.experimentName,
8726
- description: evaluator.description,
8727
- metadata: evaluator.metadata,
8728
- tags: evaluator.tags,
8729
- isPublic: evaluator.isPublic,
8730
- update: evaluator.update,
8731
- baseExperiment: evaluator.baseExperimentName ?? defaultBaseExperiment,
8732
- baseExperimentId: evaluator.baseExperimentId,
8733
- gitMetadataSettings: evaluator.gitMetadataSettings,
8734
- repoInfo: evaluator.repoInfo,
8735
- dataset: Dataset.isDataset(data) ? data : void 0,
8736
- parameters
8737
- });
8738
9363
  if (experiment && typeof process !== "undefined" && globalThis.BRAINTRUST_CONTEXT_MANAGER !== void 0) {
8739
9364
  await experiment._waitForId();
8740
9365
  }
@@ -8832,30 +9457,85 @@ function scorerName(scorer, scorer_idx) {
8832
9457
  function classifierName(classifier, classifier_idx) {
8833
9458
  return classifier.name || `classifier_${classifier_idx}`;
8834
9459
  }
9460
+ async function _internalRunEvaluatorTask(task, datum, trialIndex, parameters, span, reportProgress = () => void 0) {
9461
+ const metadata = {
9462
+ ..."metadata" in datum ? datum.metadata : {}
9463
+ };
9464
+ const hooks = {
9465
+ meta(value) {
9466
+ Object.assign(metadata, value);
9467
+ },
9468
+ metadata,
9469
+ expected: "expected" in datum ? datum.expected : void 0,
9470
+ span,
9471
+ parameters,
9472
+ reportProgress,
9473
+ trialIndex,
9474
+ tags: [...datum.tags ?? []]
9475
+ };
9476
+ const output = await task(datum.input, hooks);
9477
+ span.log({ output });
9478
+ return {
9479
+ output,
9480
+ metadata: hooks.metadata,
9481
+ tags: hooks.tags ?? []
9482
+ };
9483
+ }
8835
9484
  function buildSpanMetadata(results) {
8836
- return results.length === 1 ? results[0].metadata : results.reduce(
8837
- (prev, s) => mergeDicts(prev, { [s.name]: s.metadata }),
8838
- {}
9485
+ return results.length === 1 ? results[0].metadata : Object.fromEntries(
9486
+ results.map((result) => [result.name, result.metadata])
8839
9487
  );
8840
9488
  }
8841
9489
  function buildSpanScores(results) {
8842
- const scoresRecord = results.reduce(
8843
- (prev, s) => mergeDicts(prev, { [s.name]: s.score }),
8844
- {}
9490
+ const scoresRecord = Object.fromEntries(
9491
+ results.map((result) => [result.name, result.score])
8845
9492
  );
8846
9493
  return { resultMetadata: buildSpanMetadata(results), scoresRecord };
8847
9494
  }
8848
- async function runInScorerSpan(rootSpan, spanName, spanType, propagatedEvent, eventInput, fn) {
8849
- try {
8850
- const value = await rootSpan.traced(fn, {
8851
- name: spanName,
8852
- spanAttributes: { type: spanType, purpose: "scorer" },
8853
- propagatedEvent,
8854
- event: { input: eventInput }
8855
- });
8856
- return { kind: "score", value };
8857
- } catch (e) {
8858
- return { kind: "error", value: e };
9495
+ function _internalPrepareEvaluatorScore(scoreValue, name) {
9496
+ if (scoreValue === null) return { results: null };
9497
+ if (Array.isArray(scoreValue)) {
9498
+ for (const score of scoreValue) {
9499
+ if (!(typeof score === "object" && !isEmpty(score))) {
9500
+ throw new Error(
9501
+ `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(score)}`
9502
+ );
9503
+ }
9504
+ }
9505
+ }
9506
+ let results;
9507
+ if (Array.isArray(scoreValue)) {
9508
+ results = scoreValue;
9509
+ } else if (typeof scoreValue === "object" && !isEmpty(scoreValue)) {
9510
+ results = [scoreValue];
9511
+ } else {
9512
+ results = [{ name, score: scoreValue }];
9513
+ }
9514
+ const { resultMetadata, scoresRecord } = buildSpanScores(results);
9515
+ const fields = (score) => {
9516
+ const { metadata: _metadata, name: _name, ...rest } = score;
9517
+ return rest;
9518
+ };
9519
+ return {
9520
+ results,
9521
+ output: results.length === 1 ? fields(results[0]) : Object.fromEntries(
9522
+ results.map((score) => [score.name ?? name, fields(score)])
9523
+ ),
9524
+ metadata: resultMetadata,
9525
+ scores: scoresRecord
9526
+ };
9527
+ }
9528
+ async function runInScorerSpan(rootSpan, spanName, spanType, propagatedEvent, eventInput, fn) {
9529
+ try {
9530
+ const value = await rootSpan.traced(fn, {
9531
+ name: spanName,
9532
+ spanAttributes: { type: spanType, purpose: "scorer" },
9533
+ propagatedEvent,
9534
+ event: { input: eventInput }
9535
+ });
9536
+ return { kind: "score", value };
9537
+ } catch (e) {
9538
+ return { kind: "error", value: e };
8859
9539
  }
8860
9540
  }
8861
9541
  function collectScoringResults(runResults, names, onResult) {
@@ -8889,6 +9569,27 @@ function toClassificationItem(c) {
8889
9569
  ...c.metadata !== void 0 ? { metadata: c.metadata } : {}
8890
9570
  };
8891
9571
  }
9572
+ function _internalPrepareEvaluatorClassification(value, name) {
9573
+ if (value === null) return { results: null };
9574
+ const results = (Array.isArray(value) ? value : [value]).map(
9575
+ (result) => validateClassificationResult(result, name)
9576
+ );
9577
+ const classifications = /* @__PURE__ */ Object.create(null);
9578
+ for (const result of results) {
9579
+ (classifications[result.name] ??= []).push(toClassificationItem(result));
9580
+ }
9581
+ return {
9582
+ results,
9583
+ output: results.length === 1 ? toClassificationItem(results[0]) : Object.fromEntries(
9584
+ results.map((result) => [
9585
+ result.name,
9586
+ toClassificationItem(result)
9587
+ ])
9588
+ ),
9589
+ metadata: buildSpanMetadata(results),
9590
+ classifications
9591
+ };
9592
+ }
8892
9593
  function logScoringFailures(kind, failures, metadata, rootSpan, state) {
8893
9594
  if (!failures.length) return [];
8894
9595
  const errorMap = Object.fromEntries(
@@ -8936,54 +9637,14 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8936
9637
  (evaluator.state ?? _internalGetGlobalState())?.spanCache?.start();
8937
9638
  }
8938
9639
  try {
8939
- if (typeof evaluator.data === "string") {
8940
- throw new Error("Unimplemented: string data paths");
8941
- }
8942
- let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
8943
9640
  parameters = await validateParameters(
8944
9641
  parameters ?? {},
8945
9642
  evaluator.parameters
8946
9643
  );
8947
- if ("_type" in dataResult) {
8948
- if (dataResult._type !== "BaseExperiment") {
8949
- throw new Error("Invalid _type");
8950
- }
8951
- if (!experiment) {
8952
- throw new Error(
8953
- "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
8954
- );
8955
- }
8956
- let name = dataResult.name;
8957
- if (isEmpty(name)) {
8958
- const baseExperiment = await experiment.fetchBaseExperiment();
8959
- if (!baseExperiment) {
8960
- throw new Error("BaseExperiment() failed to fetch base experiment");
8961
- }
8962
- name = baseExperiment.name;
8963
- }
8964
- dataResult = initExperiment2(evaluator.state, {
8965
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: evaluator.projectName },
8966
- experiment: name,
8967
- open: true
8968
- }).asDataset();
8969
- }
8970
- const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
8971
- const dataIterable = (() => {
8972
- if (isAsyncIterable2(resolvedDataResult)) {
8973
- return resolvedDataResult;
8974
- }
8975
- if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {
8976
- const iterable = resolvedDataResult;
8977
- return (async function* () {
8978
- for (const datum of iterable) {
8979
- yield datum;
8980
- }
8981
- })();
8982
- }
8983
- throw new Error(
8984
- "Evaluator data must be an array, iterable, or async iterable"
8985
- );
8986
- })();
9644
+ const dataIterable = await _internalResolveEvaluatorData(
9645
+ evaluator,
9646
+ experiment
9647
+ );
8987
9648
  progressReporter.start(evaluator.evalName, 0);
8988
9649
  const experimentIdPromise = experiment ? (async () => {
8989
9650
  try {
@@ -9052,57 +9713,45 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9052
9713
  ensureSpansFlushed,
9053
9714
  state
9054
9715
  }) : void 0;
9055
- let metadata = {
9056
- ..."metadata" in datum ? datum.metadata : {}
9057
- };
9716
+ let metadata = {};
9058
9717
  const expected = "expected" in datum ? datum.expected : void 0;
9059
9718
  let output = void 0;
9060
9719
  let error = void 0;
9061
- let tags = [...datum.tags ?? []];
9062
- const scores = {};
9063
- const classifications = {};
9720
+ let tags = [];
9721
+ const scores = /* @__PURE__ */ Object.create(null);
9722
+ const classifications = /* @__PURE__ */ Object.create(null);
9064
9723
  const scorerNames = (evaluator.scores ?? []).map(scorerName);
9065
9724
  const classifierNames = (evaluator.classifiers ?? []).map(
9066
9725
  classifierName
9067
9726
  );
9068
9727
  let unhandledScores = scorerNames;
9069
9728
  try {
9070
- const meta = (o) => metadata = { ...metadata, ...o };
9071
- await rootSpan.traced(
9072
- async (span) => {
9073
- const hooksForTask = {
9074
- meta,
9075
- metadata,
9076
- expected,
9077
- span,
9078
- parameters: parameters ?? {},
9079
- reportProgress: (event) => {
9080
- stream?.({
9081
- ...event,
9082
- id: rootSpan.id,
9083
- origin: baseEvent.event?.origin,
9084
- name: evaluator.evalName,
9085
- object_type: "task"
9086
- });
9087
- },
9088
- trialIndex,
9089
- tags
9090
- };
9091
- const outputResult = evaluator.task(datum.input, hooksForTask);
9092
- if (outputResult instanceof Promise) {
9093
- output = await outputResult;
9094
- } else {
9095
- output = outputResult;
9729
+ const taskResult = await rootSpan.traced(
9730
+ (span) => _internalRunEvaluatorTask(
9731
+ evaluator.task,
9732
+ datum,
9733
+ trialIndex,
9734
+ parameters ?? {},
9735
+ span,
9736
+ (event) => {
9737
+ stream?.({
9738
+ ...event,
9739
+ id: rootSpan.id,
9740
+ origin: baseEvent.event?.origin,
9741
+ name: evaluator.evalName,
9742
+ object_type: "task"
9743
+ });
9096
9744
  }
9097
- tags = hooksForTask.tags ?? [];
9098
- span.log({ output });
9099
- },
9745
+ ),
9100
9746
  {
9101
9747
  name: "task",
9102
9748
  spanAttributes: { type: "task" /* TASK */ },
9103
9749
  event: { input: datum.input }
9104
9750
  }
9105
9751
  );
9752
+ output = taskResult.output;
9753
+ metadata = taskResult.metadata;
9754
+ tags = taskResult.tags;
9106
9755
  if (tags.length) {
9107
9756
  rootSpan.log({ output, metadata, expected, tags });
9108
9757
  } else {
@@ -9112,20 +9761,18 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9112
9761
  await rootSpan.flush();
9113
9762
  }
9114
9763
  const scoringArgs = {
9764
+ id: datum.id,
9115
9765
  input: datum.input,
9116
9766
  expected: "expected" in datum ? datum.expected : void 0,
9117
9767
  metadata,
9118
9768
  output,
9769
+ tags,
9119
9770
  trace
9120
9771
  };
9121
9772
  const { trace: _trace, ...scoringArgsForLogging } = scoringArgs;
9122
9773
  const propagatedEvent = makeScorerPropagatedEvent(
9123
9774
  await rootSpan.export()
9124
9775
  );
9125
- const getOtherFields = (s) => {
9126
- const { metadata: _metadata, name: _name, ...rest } = s;
9127
- return rest;
9128
- };
9129
9776
  const [scoreResults, classificationResults] = await Promise.all([
9130
9777
  Promise.all(
9131
9778
  (evaluator.scores ?? []).map(
@@ -9139,35 +9786,17 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9139
9786
  const scoreValue = await Promise.resolve(
9140
9787
  score(scoringArgs)
9141
9788
  );
9142
- if (scoreValue === null) return null;
9143
- if (Array.isArray(scoreValue)) {
9144
- for (const s of scoreValue) {
9145
- if (!(typeof s === "object" && !isEmpty(s))) {
9146
- throw new Error(
9147
- `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(s)}`
9148
- );
9149
- }
9150
- }
9151
- }
9152
- const results = Array.isArray(scoreValue) ? scoreValue : typeof scoreValue === "object" && !isEmpty(scoreValue) ? [scoreValue] : [
9153
- {
9154
- name: scorerNames[score_idx],
9155
- score: scoreValue
9156
- }
9157
- ];
9158
- const { resultMetadata, scoresRecord } = buildSpanScores(results);
9159
- const resultOutput = results.length === 1 ? getOtherFields(results[0]) : results.reduce(
9160
- (prev, s) => mergeDicts(prev, {
9161
- [s.name]: getOtherFields(s)
9162
- }),
9163
- {}
9789
+ const prepared = _internalPrepareEvaluatorScore(
9790
+ scoreValue,
9791
+ scorerNames[score_idx]
9164
9792
  );
9793
+ if (prepared.results === null) return null;
9165
9794
  span.log({
9166
- output: resultOutput,
9167
- metadata: resultMetadata,
9168
- scores: scoresRecord
9795
+ output: prepared.output,
9796
+ metadata: prepared.metadata,
9797
+ scores: prepared.scores
9169
9798
  });
9170
- return results;
9799
+ return prepared.results;
9171
9800
  }
9172
9801
  )
9173
9802
  )
@@ -9184,24 +9813,16 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9184
9813
  const classifierValue = await Promise.resolve(
9185
9814
  classifier(scoringArgs)
9186
9815
  );
9187
- if (classifierValue === null) return null;
9188
- const rawResults = (Array.isArray(classifierValue) ? classifierValue : [classifierValue]).map(
9189
- (result) => validateClassificationResult(
9190
- result,
9191
- classifierNames[idx]
9192
- )
9193
- );
9194
- const resultOutput = rawResults.length === 1 ? toClassificationItem(rawResults[0]) : rawResults.reduce(
9195
- (prev, r) => mergeDicts(prev, {
9196
- [r.name]: toClassificationItem(r)
9197
- }),
9198
- {}
9816
+ const prepared = _internalPrepareEvaluatorClassification(
9817
+ classifierValue,
9818
+ classifierNames[idx]
9199
9819
  );
9820
+ if (prepared.results === null) return null;
9200
9821
  span.log({
9201
- output: resultOutput,
9202
- metadata: buildSpanMetadata(rawResults)
9822
+ output: prepared.output,
9823
+ metadata: prepared.metadata
9203
9824
  });
9204
- return rawResults;
9825
+ return prepared.results;
9205
9826
  }
9206
9827
  )
9207
9828
  )
@@ -9430,7 +10051,7 @@ function accumulateScores(accumulator, scores) {
9430
10051
  }
9431
10052
  }
9432
10053
  function ensureScoreAccumulator(results) {
9433
- const accumulator = {};
10054
+ const accumulator = /* @__PURE__ */ Object.create(null);
9434
10055
  for (const result of results) {
9435
10056
  accumulateScores(accumulator, result.scores);
9436
10057
  }
@@ -9573,6 +10194,1156 @@ var defaultReporter = {
9573
10194
  }
9574
10195
  };
9575
10196
 
10197
+ // src/durable-eval.ts
10198
+ var encoder = new TextEncoder();
10199
+ var decoder = new TextDecoder();
10200
+ var BATCH_TASK_KIND = "braintrust.durable.batch-task";
10201
+ var BATCH_SCORER_KIND = "braintrust.durable.batch-scorer";
10202
+ var DEFAULT_BATCH_SIZE = 1e3;
10203
+ var DurableEvalMemoryStore = class {
10204
+ values = /* @__PURE__ */ new Map();
10205
+ async read(key) {
10206
+ return this.values.get(key)?.slice();
10207
+ }
10208
+ async write(key, value) {
10209
+ this.values.set(key, value.slice());
10210
+ }
10211
+ async getOrSet(key, value) {
10212
+ const existing = this.values.get(key);
10213
+ if (existing) return { value: existing.slice(), created: false };
10214
+ this.values.set(key, value.slice());
10215
+ return { value: value.slice(), created: true };
10216
+ }
10217
+ };
10218
+ var DurableEvalRedisStore = class {
10219
+ client;
10220
+ keyPrefix;
10221
+ ttlMs;
10222
+ constructor(options) {
10223
+ this.client = options.client;
10224
+ this.keyPrefix = options.keyPrefix ?? "braintrust-eval:";
10225
+ this.ttlMs = options.ttlMs ?? 1e3 * 60 * 60 * 24 * 7;
10226
+ if (!Number.isInteger(this.ttlMs) || this.ttlMs < 1) {
10227
+ throw new Error("DurableEvalRedisStore ttlMs must be a positive integer");
10228
+ }
10229
+ }
10230
+ async read(key) {
10231
+ const value = await this.client.get(`${this.keyPrefix}${key}`);
10232
+ if (value == null) return void 0;
10233
+ if (typeof value !== "string") {
10234
+ throw new Error("DurableEvalRedisStore expected GET to return a string");
10235
+ }
10236
+ return base64ToUint8Array(value);
10237
+ }
10238
+ async write(key, value) {
10239
+ const client = this.client;
10240
+ const set = client.set;
10241
+ const redisKey = `${this.keyPrefix}${key}`;
10242
+ const encoded = uint8ArrayToBase64(value);
10243
+ if (typeof client.defineCommand === "function") {
10244
+ await set.call(client, redisKey, encoded, "PX", this.ttlMs);
10245
+ } else if (typeof client.sendCommand === "function") {
10246
+ await set.call(client, redisKey, encoded, { PX: this.ttlMs });
10247
+ } else if (typeof client.createScript === "function") {
10248
+ await set.call(client, redisKey, encoded, { px: this.ttlMs });
10249
+ } else {
10250
+ throw new Error(
10251
+ "DurableEvalRedisStore requires a node-redis, ioredis, or @upstash/redis client"
10252
+ );
10253
+ }
10254
+ }
10255
+ async getOrSet(key, value) {
10256
+ const redisKey = `${this.keyPrefix}${key}`;
10257
+ const encoded = uint8ArrayToBase64(value);
10258
+ const client = this.client;
10259
+ const set = client.set;
10260
+ let setOptions;
10261
+ if (typeof client.defineCommand === "function") {
10262
+ setOptions = ["PX", this.ttlMs, "NX", "GET"];
10263
+ } else if (typeof client.sendCommand === "function") {
10264
+ setOptions = [{ PX: this.ttlMs, NX: true, GET: true }];
10265
+ } else if (typeof client.createScript === "function") {
10266
+ setOptions = [{ px: this.ttlMs, nx: true, get: true }];
10267
+ } else {
10268
+ throw new Error(
10269
+ "DurableEvalRedisStore getOrSet requires a node-redis, ioredis, or @upstash/redis client"
10270
+ );
10271
+ }
10272
+ const existing = await set.call(client, redisKey, encoded, ...setOptions);
10273
+ if (existing === null) return { value: value.slice(), created: true };
10274
+ if (typeof existing !== "string") {
10275
+ throw new Error(
10276
+ "DurableEvalRedisStore expected atomic SET to return a string or null"
10277
+ );
10278
+ }
10279
+ return { value: base64ToUint8Array(existing), created: false };
10280
+ }
10281
+ };
10282
+ var BatchTask = class {
10283
+ constructor(processor) {
10284
+ this.processor = processor;
10285
+ }
10286
+ processor;
10287
+ kind = BATCH_TASK_KIND;
10288
+ };
10289
+ var BatchScorer = class {
10290
+ constructor(processor) {
10291
+ this.processor = processor;
10292
+ this.name = processor.name;
10293
+ }
10294
+ processor;
10295
+ kind = BATCH_SCORER_KIND;
10296
+ name;
10297
+ };
10298
+ function defineDurableEval(projectName, evaluator) {
10299
+ const definition = {
10300
+ projectName,
10301
+ evalName: evaluator.experimentName ?? projectName,
10302
+ evaluator
10303
+ };
10304
+ return {
10305
+ start: (options = {}) => startDurableEval(definition, options),
10306
+ status: (options) => getDurableEvalStatus(definition, options),
10307
+ poll: (options) => pollDurableEval(definition, options),
10308
+ processBatchResult: (result) => processDurableBatchResult(definition, result)
10309
+ };
10310
+ }
10311
+ async function startDurableEval(definition, options) {
10312
+ const store = definition.evaluator.store;
10313
+ const runId = newId();
10314
+ const key = runKey(definition.projectName, definition.evalName, runId);
10315
+ const { data } = callEvaluatorData(definition.evaluator.data);
10316
+ const parameters = await validateParameters(
10317
+ options.parameters ?? {},
10318
+ definition.evaluator.parameters
10319
+ );
10320
+ const experimentName = definition.evaluator.experimentName ?? `${definition.evalName}-${runId}`;
10321
+ const experiment = await _internalInitEvaluatorExperiment(
10322
+ definition.projectName,
10323
+ { ...definition.evaluator, data },
10324
+ data,
10325
+ {
10326
+ disabled: options.noSendLogs ?? false,
10327
+ experimentName,
10328
+ update: true
10329
+ }
10330
+ );
10331
+ if (!isBatchTask(definition.evaluator.task) && !(definition.evaluator.scores ?? []).some(isBatchScorer)) {
10332
+ const result = await runEvaluator(
10333
+ experiment,
10334
+ {
10335
+ ...definition.evaluator,
10336
+ projectName: definition.projectName,
10337
+ evalName: definition.evalName,
10338
+ data
10339
+ },
10340
+ {
10341
+ start: () => void 0,
10342
+ stop: () => void 0,
10343
+ increment: () => void 0
10344
+ },
10345
+ [],
10346
+ void 0,
10347
+ parameters,
10348
+ true,
10349
+ true
10350
+ );
10351
+ const state2 = {
10352
+ runId,
10353
+ experimentName,
10354
+ noSendLogs: options.noSendLogs ?? false,
10355
+ parameters: assertJsonValue(parameters, "eval parameters"),
10356
+ status: "completed",
10357
+ summary: result.summary,
10358
+ cases: [],
10359
+ batches: []
10360
+ };
10361
+ await experiment?.flush();
10362
+ await writeRunRecord(store, key, state2);
10363
+ return currentStatus(definition, state2);
10364
+ }
10365
+ const state = {
10366
+ runId,
10367
+ experimentName,
10368
+ noSendLogs: options.noSendLogs ?? false,
10369
+ parameters: assertJsonValue(parameters, "eval parameters"),
10370
+ status: "running",
10371
+ cases: await materializeCases(definition, data, experiment),
10372
+ batches: []
10373
+ };
10374
+ await writeCaseBaseRecords(store, key, state.cases);
10375
+ await writeRunRecord(store, key, state);
10376
+ return advanceDurableEval(definition, state, store, key, experiment);
10377
+ }
10378
+ async function getDurableEvalStatus(definition, options) {
10379
+ const store = definition.evaluator.store;
10380
+ const state = await readRunState(
10381
+ definition,
10382
+ store,
10383
+ runKey(definition.projectName, definition.evalName, options.runId)
10384
+ );
10385
+ if (!state) throw new Error(`Durable eval run ${options.runId} is missing`);
10386
+ return currentStatus(definition, state);
10387
+ }
10388
+ async function processDurableBatchResult(definition, result) {
10389
+ if (!result.batchId && !result.externalId) {
10390
+ throw new Error("Batch results require batchId or externalId");
10391
+ }
10392
+ const store = definition.evaluator.store;
10393
+ const key = runKey(definition.projectName, definition.evalName, result.runId);
10394
+ const state = await readRunState(definition, store, key);
10395
+ if (!state) throw new Error(`Durable eval run ${result.runId} is missing`);
10396
+ const byBatch = result.batchId ? state.batches.find((candidate) => candidate.id === result.batchId) : void 0;
10397
+ const byExternal = result.externalId ? state.batches.find(
10398
+ (candidate) => candidate.externalId === result.externalId
10399
+ ) : void 0;
10400
+ if (byBatch && byExternal && byBatch.id !== byExternal.id) {
10401
+ throw new Error("batchId and externalId identify different batches");
10402
+ }
10403
+ const batch = byBatch ?? byExternal;
10404
+ if (!batch) throw new Error("No submitted batch matches this result");
10405
+ if (batch.status !== "complete") {
10406
+ const records = await collectBatch(definition, state, batch);
10407
+ batch.status = "complete";
10408
+ await writeCaseRecords(store, key, records);
10409
+ await writeBatchRecords(store, key, [batch]);
10410
+ }
10411
+ return advanceDurableEval(
10412
+ definition,
10413
+ await readRunState(definition, store, key),
10414
+ store,
10415
+ key
10416
+ );
10417
+ }
10418
+ async function pollDurableEval(definition, options) {
10419
+ const store = definition.evaluator.store;
10420
+ const key = runKey(
10421
+ definition.projectName,
10422
+ definition.evalName,
10423
+ options.runId
10424
+ );
10425
+ const state = await readRunState(definition, store, key);
10426
+ if (!state) throw new Error(`Durable eval run ${options.runId} is missing`);
10427
+ const batches = state.batches.filter((batch) => {
10428
+ if (batch.status === "complete") return false;
10429
+ return processorForStage(definition, batch.kind, batch.scorerName).completion.mode === "poll";
10430
+ });
10431
+ const results = await Promise.all(
10432
+ batches.map(async (batch) => ({
10433
+ batch,
10434
+ result: await processorForStage(definition, batch.kind, batch.scorerName).completion.poll(batch.submissionData, {
10435
+ runId: state.runId,
10436
+ batchId: batch.id
10437
+ })
10438
+ }))
10439
+ );
10440
+ const changedCases = /* @__PURE__ */ new Map();
10441
+ const changedBatches = [];
10442
+ for (const { batch, result } of results) {
10443
+ if (result.status === "failed") throw asError(result.error);
10444
+ if (result.status !== "complete") continue;
10445
+ for (const record of await collectBatch(definition, state, batch)) {
10446
+ changedCases.set(record.id, record);
10447
+ }
10448
+ batch.status = "complete";
10449
+ changedBatches.push(batch);
10450
+ }
10451
+ if (changedBatches.length > 0) {
10452
+ await writeCaseRecords(store, key, [...changedCases.values()]);
10453
+ await writeBatchRecords(store, key, changedBatches);
10454
+ }
10455
+ const currentState = changedBatches.length > 0 ? await readRunState(definition, store, key) : state;
10456
+ return advanceDurableEval(definition, currentState, store, key);
10457
+ }
10458
+ async function openDurableExperiment(definition, state) {
10459
+ const data = [];
10460
+ return await _internalInitEvaluatorExperiment(
10461
+ definition.projectName,
10462
+ { ...definition.evaluator, data },
10463
+ data,
10464
+ {
10465
+ disabled: state.noSendLogs,
10466
+ experimentName: state.experimentName,
10467
+ update: true
10468
+ }
10469
+ );
10470
+ }
10471
+ async function advanceDurableEval(definition, state, store, key, existingExperiment) {
10472
+ if (state.status === "completed") return currentStatus(definition, state);
10473
+ const experiment = existingExperiment === void 0 ? await openDurableExperiment(definition, state) : existingExperiment;
10474
+ await runTaskStage(definition, state, store, key, experiment);
10475
+ await logCompletedTasks(definition, state, store, key, experiment);
10476
+ state = await readRunState(definition, store, key) ?? state;
10477
+ if (state.cases.some((record) => !record.taskComplete || !record.taskLogged)) {
10478
+ return currentStatus(definition, state);
10479
+ }
10480
+ await runScoreStages(definition, state, store, key, experiment);
10481
+ state = await readRunState(definition, store, key) ?? state;
10482
+ const scorerNames = resolveScorers(definition.evaluator.scores ?? []).map(
10483
+ ({ name }) => name
10484
+ );
10485
+ const classifierNames = (definition.evaluator.classifiers ?? []).map(
10486
+ classifierName
10487
+ );
10488
+ if (state.cases.some(
10489
+ (record) => scorerNames.some(
10490
+ (name) => !Object.hasOwn(record.scores, name) || !Object.hasOwn(record.loggedScores, name)
10491
+ ) || classifierNames.some(
10492
+ (name) => !Object.hasOwn(record.loggedClassifications, name)
10493
+ )
10494
+ )) {
10495
+ return currentStatus(definition, state);
10496
+ }
10497
+ if (!await claimAction(store, key, "finish")) {
10498
+ const latest = await readRunState(definition, store, key);
10499
+ return currentStatus(definition, latest ?? state);
10500
+ }
10501
+ state.summary = await finishExperiment(definition, state, experiment);
10502
+ state.status = "completed";
10503
+ await writeRunRecord(store, key, state);
10504
+ return currentStatus(definition, state);
10505
+ }
10506
+ function currentStatus(definition, state) {
10507
+ if (state.status === "completed") {
10508
+ if (!state.summary) {
10509
+ throw new Error(`Durable eval run ${state.runId} has no saved summary`);
10510
+ }
10511
+ return {
10512
+ status: "completed",
10513
+ runId: state.runId,
10514
+ pending: { poll: 0, webhook: 0 },
10515
+ summary: state.summary
10516
+ };
10517
+ }
10518
+ const pending = { poll: 0, webhook: 0 };
10519
+ for (const batch of state.batches) {
10520
+ if (batch.status === "complete") continue;
10521
+ pending[processorForStage(definition, batch.kind, batch.scorerName).completion.mode]++;
10522
+ }
10523
+ return { status: "waiting", runId: state.runId, pending };
10524
+ }
10525
+ async function startCaseRoot(definition, state, record, experiment) {
10526
+ if (!experiment) return NOOP_SPAN;
10527
+ const datum = record.datum;
10528
+ return _internalStartSpanWithInitialMerge({
10529
+ ...definition.evaluator.state ? { state: definition.evaluator.state } : {},
10530
+ parent: await experiment.export(),
10531
+ name: "eval",
10532
+ spanId: deterministicId(`${state.runId}:${record.id}:span`),
10533
+ spanAttributes: { type: "eval" /* EVAL */ },
10534
+ event: {
10535
+ id: deterministicId(`${state.runId}:${record.id}:row`),
10536
+ input: datum.input,
10537
+ expected: "expected" in datum ? datum.expected : void 0,
10538
+ tags: datum.tags,
10539
+ origin: datum.origin
10540
+ }
10541
+ });
10542
+ }
10543
+ async function logTaskResult(definition, state, record, experiment, task) {
10544
+ const datum = record.datum;
10545
+ const root = await startCaseRoot(definition, state, record, experiment);
10546
+ try {
10547
+ if (task) {
10548
+ const result = await root.traced(
10549
+ (span) => _internalRunEvaluatorTask(
10550
+ task,
10551
+ datum,
10552
+ record.trialIndex,
10553
+ state.parameters,
10554
+ span
10555
+ ),
10556
+ {
10557
+ name: "task",
10558
+ spanId: deterministicId(`${state.runId}:${record.id}:task`),
10559
+ spanAttributes: { type: "task" /* TASK */ },
10560
+ event: { input: datum.input }
10561
+ }
10562
+ );
10563
+ record.output = assertJsonValue(
10564
+ result.output,
10565
+ `task output for ${record.caseId}`
10566
+ );
10567
+ record.metadata = assertJsonValue(result.metadata, "task metadata");
10568
+ record.tags = result.tags;
10569
+ record.taskComplete = true;
10570
+ } else {
10571
+ await root.traced((span) => span.log({ output: record.output }), {
10572
+ name: "task",
10573
+ spanId: deterministicId(`${state.runId}:${record.id}:task`),
10574
+ spanAttributes: { type: "task" /* TASK */ },
10575
+ event: { input: datum.input }
10576
+ });
10577
+ }
10578
+ root.log({
10579
+ output: record.output,
10580
+ expected: "expected" in datum ? datum.expected : void 0,
10581
+ metadata: {
10582
+ ...record.metadata,
10583
+ durable_eval: {
10584
+ run_id: state.runId,
10585
+ case_id: record.caseId,
10586
+ trial_index: record.trialIndex
10587
+ }
10588
+ },
10589
+ tags: record.tags
10590
+ });
10591
+ record.rootSpan = await root.export();
10592
+ record.taskLogged = true;
10593
+ } catch (error) {
10594
+ logError(root, error);
10595
+ throw error;
10596
+ } finally {
10597
+ root.end();
10598
+ }
10599
+ }
10600
+ async function logCompletedTasks(definition, state, store, key, experiment) {
10601
+ const changed = [];
10602
+ for (const record of state.cases) {
10603
+ if (!record.taskComplete || record.taskLogged) continue;
10604
+ if (!await claimAction(store, key, "task-log", record.id)) continue;
10605
+ await logTaskResult(definition, state, record, experiment);
10606
+ changed.push(record);
10607
+ }
10608
+ if (changed.length > 0) {
10609
+ await experiment?.flush();
10610
+ await writeCaseRecords(store, key, changed);
10611
+ }
10612
+ }
10613
+ async function runTaskStage(definition, state, store, key, experiment) {
10614
+ if (isBatchTask(definition.evaluator.task)) {
10615
+ await ensureBatches(definition, state, store, key, "task");
10616
+ return;
10617
+ }
10618
+ const task = definition.evaluator.task;
10619
+ const changed = [];
10620
+ for (const record of state.cases) {
10621
+ if (record.taskComplete) continue;
10622
+ if (!await claimAction(store, key, "task", record.id)) continue;
10623
+ await logTaskResult(definition, state, record, experiment, task);
10624
+ changed.push(record);
10625
+ }
10626
+ if (changed.length > 0) {
10627
+ await experiment?.flush();
10628
+ await writeCaseRecords(store, key, changed);
10629
+ }
10630
+ }
10631
+ async function runScoreStages(definition, state, store, key, experiment) {
10632
+ const scorers = resolveScorers(definition.evaluator.scores ?? []);
10633
+ const changed = /* @__PURE__ */ new Map();
10634
+ const persistChangedCases = async () => {
10635
+ if (changed.size === 0) return;
10636
+ await experiment?.flush();
10637
+ await writeCaseRecords(store, key, [...changed.values()]);
10638
+ changed.clear();
10639
+ };
10640
+ for (const { name, scorer } of scorers) {
10641
+ if (isBatchScorer(scorer)) {
10642
+ for (const record of state.cases) {
10643
+ if (Object.hasOwn(record.scores, name) && !Object.hasOwn(record.loggedScores, name)) {
10644
+ if (!await claimAction(store, key, "score-log", record.id, name)) {
10645
+ continue;
10646
+ }
10647
+ await evaluateAndLogScore(
10648
+ definition,
10649
+ state,
10650
+ record,
10651
+ name,
10652
+ experiment
10653
+ );
10654
+ changed.set(record.id, record);
10655
+ }
10656
+ }
10657
+ await persistChangedCases();
10658
+ await ensureBatches(definition, state, store, key, "score", name);
10659
+ continue;
10660
+ }
10661
+ for (const record of state.cases) {
10662
+ if (Object.hasOwn(record.loggedScores, name)) continue;
10663
+ if (!await claimAction(store, key, "score", record.id, name)) continue;
10664
+ await evaluateAndLogScore(
10665
+ definition,
10666
+ state,
10667
+ record,
10668
+ name,
10669
+ experiment,
10670
+ scorer
10671
+ );
10672
+ changed.set(record.id, record);
10673
+ }
10674
+ }
10675
+ for (const [index, classifier] of (definition.evaluator.classifiers ?? []).entries()) {
10676
+ const name = classifierName(classifier, index);
10677
+ for (const record of state.cases) {
10678
+ if (Object.hasOwn(record.loggedClassifications, name)) continue;
10679
+ if (!await claimAction(store, key, "classification", record.id, name)) {
10680
+ continue;
10681
+ }
10682
+ await evaluateAndLogClassification(
10683
+ definition,
10684
+ state,
10685
+ record,
10686
+ name,
10687
+ classifier,
10688
+ experiment
10689
+ );
10690
+ changed.set(record.id, record);
10691
+ }
10692
+ }
10693
+ await persistChangedCases();
10694
+ }
10695
+ function scorerArgs(record) {
10696
+ const datum = record.datum;
10697
+ return {
10698
+ ...datum,
10699
+ metadata: record.metadata,
10700
+ output: record.output
10701
+ };
10702
+ }
10703
+ function resumeCaseRoot(definition, record, experiment) {
10704
+ if (!experiment) return NOOP_SPAN;
10705
+ if (!record.rootSpan) {
10706
+ throw new Error(`Durable eval case ${record.caseId} has no root span`);
10707
+ }
10708
+ return _internalResumeSpan({
10709
+ exported: record.rootSpan,
10710
+ state: definition.evaluator.state
10711
+ });
10712
+ }
10713
+ async function evaluateAndLogScore(definition, state, record, name, experiment, scorer) {
10714
+ const root = resumeCaseRoot(definition, record, experiment);
10715
+ try {
10716
+ const rootExport = await root.export();
10717
+ const prepared = await root.traced(
10718
+ async (span) => {
10719
+ const value = scorer ? await scorer(scorerArgs(record)) : record.scores[name];
10720
+ if (scorer) {
10721
+ record.scores[name] = assertJsonValue(value, `scorer ${name} output`);
10722
+ }
10723
+ const result = _internalPrepareEvaluatorScore(value, name);
10724
+ if (result.results !== null) {
10725
+ span.log({
10726
+ output: result.output,
10727
+ metadata: result.metadata,
10728
+ scores: result.scores
10729
+ });
10730
+ }
10731
+ return result;
10732
+ },
10733
+ {
10734
+ name,
10735
+ spanId: deterministicId(`${state.runId}:${record.id}:score:${name}`),
10736
+ spanAttributes: {
10737
+ type: "score" /* SCORE */,
10738
+ purpose: "scorer"
10739
+ },
10740
+ propagatedEvent: makeScorerPropagatedEvent(rootExport || void 0),
10741
+ event: { input: scorerArgs(record) }
10742
+ }
10743
+ );
10744
+ if (prepared.scores) root.log({ scores: prepared.scores });
10745
+ record.loggedScores[name] = true;
10746
+ } catch (error) {
10747
+ logError(root, error);
10748
+ throw error;
10749
+ } finally {
10750
+ root.end();
10751
+ }
10752
+ }
10753
+ async function evaluateAndLogClassification(definition, state, record, name, classifier, experiment) {
10754
+ const root = resumeCaseRoot(definition, record, experiment);
10755
+ try {
10756
+ const rootExport = await root.export();
10757
+ const prepared = await root.traced(
10758
+ async (span) => {
10759
+ const value = await classifier(scorerArgs(record));
10760
+ record.classifications[name] = assertJsonValue(
10761
+ value,
10762
+ `classifier ${name} output`
10763
+ );
10764
+ const result = _internalPrepareEvaluatorClassification(value, name);
10765
+ if (result.results !== null) {
10766
+ span.log({ output: result.output, metadata: result.metadata });
10767
+ }
10768
+ return result;
10769
+ },
10770
+ {
10771
+ name,
10772
+ spanId: deterministicId(
10773
+ `${state.runId}:${record.id}:classification:${name}`
10774
+ ),
10775
+ spanAttributes: {
10776
+ type: "classifier" /* CLASSIFIER */,
10777
+ purpose: "scorer"
10778
+ },
10779
+ propagatedEvent: makeScorerPropagatedEvent(rootExport || void 0),
10780
+ event: { input: scorerArgs(record) }
10781
+ }
10782
+ );
10783
+ if (prepared.classifications) {
10784
+ root.log({ classifications: prepared.classifications });
10785
+ }
10786
+ record.loggedClassifications[name] = true;
10787
+ } catch (error) {
10788
+ logError(root, error);
10789
+ throw error;
10790
+ } finally {
10791
+ root.end();
10792
+ }
10793
+ }
10794
+ async function ensureBatches(definition, state, store, key, kind, scorerName2) {
10795
+ const processor = processorForStage(definition, kind, scorerName2);
10796
+ const plans = plannedBatches(
10797
+ definition,
10798
+ state.runId,
10799
+ state.cases.map(({ id }) => id)
10800
+ );
10801
+ const casesById = new Map(state.cases.map((record) => [record.id, record]));
10802
+ for (const plan of plans) {
10803
+ if (plan.kind !== kind || plan.scorerName !== scorerName2) continue;
10804
+ if (state.batches.some(({ id }) => id === plan.id)) continue;
10805
+ const records = plan.itemIds.map((id) => casesById.get(id));
10806
+ const ready = records.every(
10807
+ (record) => kind === "task" ? !record.taskComplete : !Object.hasOwn(record.scores, scorerName2)
10808
+ );
10809
+ if (!ready) continue;
10810
+ const batchId = plan.id;
10811
+ const claim = await store.getOrSet(
10812
+ claimRecordKey(key, "batch", batchId),
10813
+ encoder.encode(batchId)
10814
+ );
10815
+ if (!claim.created) continue;
10816
+ const context = { runId: state.runId, batchId };
10817
+ const items = records.map(
10818
+ (record) => kind === "task" ? taskBatchItem(record, state.parameters) : scorerBatchItem(record)
10819
+ );
10820
+ const submissionData = assertJsonValue(
10821
+ await processor.submit(items, context),
10822
+ `submission data for batch ${batchId}`
10823
+ );
10824
+ const externalId = processor.completion.mode === "webhook" ? processor.completion.getExternalId(submissionData, context) : void 0;
10825
+ if (externalId !== void 0 && !externalId.trim()) {
10826
+ throw new Error(`Batch ${batchId} produced an empty externalId`);
10827
+ }
10828
+ const batch = {
10829
+ id: batchId,
10830
+ kind,
10831
+ scorerName: scorerName2,
10832
+ itemIds: records.map((record) => record.id),
10833
+ submissionData,
10834
+ externalId,
10835
+ status: "submitted"
10836
+ };
10837
+ state.batches.push(batch);
10838
+ await writeBatchRecords(store, key, [batch]);
10839
+ }
10840
+ }
10841
+ async function collectBatch(definition, state, batch) {
10842
+ const processor = processorForStage(definition, batch.kind, batch.scorerName);
10843
+ const context = { runId: state.runId, batchId: batch.id };
10844
+ const results = await processor.collect(batch.submissionData, context);
10845
+ if (!Array.isArray(results)) {
10846
+ throw new Error(`collect for batch ${batch.id} must return an array`);
10847
+ }
10848
+ const expectedIds = new Set(batch.itemIds);
10849
+ const seen = /* @__PURE__ */ new Set();
10850
+ const records = [];
10851
+ for (const result of results) {
10852
+ const id = resultItemId(result);
10853
+ if (!expectedIds.has(id)) {
10854
+ throw new Error(`Batch ${batch.id} returned unknown item ${id}`);
10855
+ }
10856
+ if (seen.has(id)) {
10857
+ throw new Error(`Batch ${batch.id} returned item ${id} more than once`);
10858
+ }
10859
+ seen.add(id);
10860
+ const record = state.cases.find((candidate) => candidate.id === id);
10861
+ records.push(record);
10862
+ if (batch.kind === "task") {
10863
+ record.output = assertJsonValue(
10864
+ result.output,
10865
+ `task output for item ${id}`
10866
+ );
10867
+ if ("metadata" in result && result.metadata !== void 0) {
10868
+ record.metadata = assertJsonValue(
10869
+ {
10870
+ ...record.metadata,
10871
+ ...result.metadata
10872
+ },
10873
+ `metadata for ${id}`
10874
+ );
10875
+ }
10876
+ if ("tags" in result && result.tags !== void 0)
10877
+ record.tags = result.tags;
10878
+ record.taskComplete = true;
10879
+ } else {
10880
+ record.scores[batch.scorerName] = assertJsonValue(
10881
+ result.score,
10882
+ `score output for item ${id}`
10883
+ );
10884
+ }
10885
+ }
10886
+ const missing = batch.itemIds.filter((id) => !seen.has(id));
10887
+ if (missing.length > 0) {
10888
+ throw new Error(
10889
+ `Batch ${batch.id} did not return results for: ${missing.join(", ")}`
10890
+ );
10891
+ }
10892
+ return records;
10893
+ }
10894
+ function processorForStage(definition, kind, scorerName2) {
10895
+ if (kind === "task") {
10896
+ if (!isBatchTask(definition.evaluator.task)) {
10897
+ throw new Error("Definition no longer contains the batch task");
10898
+ }
10899
+ return definition.evaluator.task.processor;
10900
+ }
10901
+ const scorer = resolveScorers(definition.evaluator.scores ?? []).find(
10902
+ ({ name }) => name === scorerName2
10903
+ )?.scorer;
10904
+ if (!isBatchScorer(scorer)) {
10905
+ throw new Error(`Definition no longer contains scorer ${scorerName2}`);
10906
+ }
10907
+ return scorer.processor;
10908
+ }
10909
+ async function materializeCases(definition, data, experiment) {
10910
+ const evaluator = definition.evaluator;
10911
+ const iterable = await _internalResolveEvaluatorData(
10912
+ {
10913
+ data,
10914
+ projectName: definition.projectName,
10915
+ projectId: evaluator.projectId,
10916
+ state: evaluator.state
10917
+ },
10918
+ experiment
10919
+ );
10920
+ const records = [];
10921
+ const seen = /* @__PURE__ */ new Set();
10922
+ for await (const datum of iterable) {
10923
+ const caseId = datum.id ?? datum.upsert_id ?? (evaluator.caseId ? await evaluator.caseId(datum) : void 0);
10924
+ if (!caseId) {
10925
+ throw new Error(
10926
+ "Every durable eval case requires id, upsert_id, or caseId"
10927
+ );
10928
+ }
10929
+ if (seen.has(caseId))
10930
+ throw new Error(`Duplicate durable eval case id: ${caseId}`);
10931
+ seen.add(caseId);
10932
+ const trialCount = datum.trialCount ?? evaluator.trialCount ?? 1;
10933
+ if (!Number.isInteger(trialCount) || trialCount < 1) {
10934
+ throw new Error(`Invalid trialCount for durable eval case ${caseId}`);
10935
+ }
10936
+ for (let trialIndex = 0; trialIndex < trialCount; trialIndex++) {
10937
+ records.push({
10938
+ id: `${caseId}:trial:${trialIndex}`,
10939
+ caseId,
10940
+ trialIndex,
10941
+ datum: assertJsonValue(datum, `case ${caseId}`),
10942
+ metadata: assertJsonValue(
10943
+ "metadata" in datum ? datum.metadata : {},
10944
+ `metadata for ${caseId}`
10945
+ ),
10946
+ tags: datum.tags,
10947
+ taskComplete: false,
10948
+ taskLogged: false,
10949
+ scores: /* @__PURE__ */ Object.create(null),
10950
+ loggedScores: /* @__PURE__ */ Object.create(null),
10951
+ classifications: /* @__PURE__ */ Object.create(null),
10952
+ loggedClassifications: /* @__PURE__ */ Object.create(null)
10953
+ });
10954
+ }
10955
+ }
10956
+ return records;
10957
+ }
10958
+ function taskBatchItem(record, parameters) {
10959
+ const datum = record.datum;
10960
+ return {
10961
+ id: record.id,
10962
+ input: datum.input,
10963
+ expected: "expected" in datum ? datum.expected : void 0,
10964
+ metadata: record.metadata,
10965
+ tags: record.tags,
10966
+ parameters,
10967
+ trialIndex: record.trialIndex
10968
+ };
10969
+ }
10970
+ function scorerBatchItem(record) {
10971
+ const datum = record.datum;
10972
+ return {
10973
+ id: record.id,
10974
+ input: datum.input,
10975
+ output: record.output,
10976
+ expected: "expected" in datum ? datum.expected : void 0,
10977
+ metadata: record.metadata,
10978
+ tags: record.tags,
10979
+ trialIndex: record.trialIndex
10980
+ };
10981
+ }
10982
+ async function finishExperiment(definition, state, experiment) {
10983
+ const scorerNames = resolveScorers(definition.evaluator.scores ?? []).map(
10984
+ ({ name }) => name
10985
+ );
10986
+ const results = state.cases.map((record) => {
10987
+ const datum = record.datum;
10988
+ const scores = Object.fromEntries(
10989
+ scorerNames.flatMap(
10990
+ (name) => Object.entries(
10991
+ _internalPrepareEvaluatorScore(
10992
+ record.scores[name],
10993
+ name
10994
+ ).scores ?? {}
10995
+ )
10996
+ )
10997
+ );
10998
+ const classifications = Object.fromEntries(
10999
+ Object.entries(record.classifications).flatMap(
11000
+ ([name, value]) => Object.entries(
11001
+ _internalPrepareEvaluatorClassification(value, name).classifications ?? {}
11002
+ )
11003
+ )
11004
+ );
11005
+ return {
11006
+ ...datum,
11007
+ output: record.output,
11008
+ metadata: record.metadata,
11009
+ tags: record.tags,
11010
+ scores,
11011
+ error: void 0,
11012
+ ...Object.keys(classifications).length > 0 ? { classifications } : {}
11013
+ };
11014
+ });
11015
+ if (!experiment) {
11016
+ return buildLocalSummary(
11017
+ {
11018
+ ...definition.evaluator,
11019
+ projectName: definition.projectName,
11020
+ evalName: state.experimentName
11021
+ },
11022
+ results
11023
+ );
11024
+ }
11025
+ await experiment.flush();
11026
+ let comparisonExperimentId = definition.evaluator.baseExperimentId;
11027
+ if (!comparisonExperimentId) {
11028
+ try {
11029
+ comparisonExperimentId = await experiment._getBaseExperimentId();
11030
+ } catch {
11031
+ comparisonExperimentId = void 0;
11032
+ }
11033
+ }
11034
+ return await experiment.summarize({
11035
+ summarizeScores: definition.evaluator.summarizeScores,
11036
+ ...comparisonExperimentId ? { comparisonExperimentId } : {}
11037
+ });
11038
+ }
11039
+ function resolveScorers(scorers) {
11040
+ return scorers.map((scorer, index) => ({
11041
+ name: isBatchScorer(scorer) ? scorer.name : scorer.name || `scorer_${index}`,
11042
+ scorer
11043
+ }));
11044
+ }
11045
+ function runKey(projectName, evalName, runId) {
11046
+ return `durable-eval/v1/runs/${contentVersion(encoder.encode(`${projectName}\0${evalName}\0${runId}`))}`;
11047
+ }
11048
+ function encodedKeyPart(value) {
11049
+ return uint8ArrayToBase64(encoder.encode(value)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
11050
+ }
11051
+ function caseRecordKey(key, caseId, kind, ...names) {
11052
+ const suffix = names.map(encodedKeyPart).join("/");
11053
+ return `${key}/cases/${encodedKeyPart(caseId)}/${kind}${suffix ? `/${suffix}` : ""}`;
11054
+ }
11055
+ function batchRecordKey(key, batchId) {
11056
+ return `${key}/batches/${encodedKeyPart(batchId)}`;
11057
+ }
11058
+ function claimRecordKey(key, kind, ...parts) {
11059
+ const identity = stableStringify([kind, parts]);
11060
+ return `${key}/claims/${contentVersion(encoder.encode(identity))}`;
11061
+ }
11062
+ async function claimAction(store, key, kind, ...parts) {
11063
+ return (await store.getOrSet(
11064
+ claimRecordKey(key, kind, ...parts),
11065
+ encoder.encode("claimed")
11066
+ )).created;
11067
+ }
11068
+ function plannedBatches(definition, runId, caseIds) {
11069
+ const stages = [];
11070
+ if (isBatchTask(definition.evaluator.task)) stages.push({ kind: "task" });
11071
+ for (const { name, scorer } of resolveScorers(
11072
+ definition.evaluator.scores ?? []
11073
+ )) {
11074
+ if (isBatchScorer(scorer)) {
11075
+ stages.push({ kind: "score", scorerName: name });
11076
+ }
11077
+ }
11078
+ const plans = [];
11079
+ for (const { kind, scorerName: scorerName2 } of stages) {
11080
+ const batchSize = processorForStage(definition, kind, scorerName2).batchSize ?? DEFAULT_BATCH_SIZE;
11081
+ if (!Number.isInteger(batchSize) || batchSize < 1) {
11082
+ throw new Error(
11083
+ `Invalid batchSize for ${scorerName2 ?? "task"}: ${batchSize}`
11084
+ );
11085
+ }
11086
+ for (let offset = 0; offset < caseIds.length; offset += batchSize) {
11087
+ const itemIds = caseIds.slice(offset, offset + batchSize);
11088
+ plans.push({
11089
+ id: deterministicId(
11090
+ stableStringify([runId, kind, scorerName2, itemIds])
11091
+ ),
11092
+ kind,
11093
+ scorerName: scorerName2,
11094
+ itemIds
11095
+ });
11096
+ }
11097
+ }
11098
+ return plans;
11099
+ }
11100
+ async function readCaseRecord(definition, store, key, id) {
11101
+ const scorers = resolveScorers(definition.evaluator.scores ?? []);
11102
+ const classifiers = (definition.evaluator.classifiers ?? []).map(
11103
+ classifierName
11104
+ );
11105
+ const [
11106
+ base,
11107
+ task,
11108
+ taskLog,
11109
+ scoreValues,
11110
+ scoreLogValues,
11111
+ classificationValues,
11112
+ classificationLogValues
11113
+ ] = await Promise.all([
11114
+ readJson(store, caseRecordKey(key, id, "base")),
11115
+ readJson(store, caseRecordKey(key, id, "task")),
11116
+ readJson(store, caseRecordKey(key, id, "task-log")),
11117
+ Promise.all(
11118
+ scorers.map(async ({ name }) => ({
11119
+ name,
11120
+ value: await readJson(
11121
+ store,
11122
+ caseRecordKey(key, id, "score", name)
11123
+ )
11124
+ }))
11125
+ ),
11126
+ Promise.all(
11127
+ scorers.map(async ({ name }) => ({
11128
+ name,
11129
+ value: await readJson(
11130
+ store,
11131
+ caseRecordKey(key, id, "score-log", name)
11132
+ )
11133
+ }))
11134
+ ),
11135
+ Promise.all(
11136
+ classifiers.map(async (name) => ({
11137
+ name,
11138
+ value: await readJson(
11139
+ store,
11140
+ caseRecordKey(key, id, "classification", name)
11141
+ )
11142
+ }))
11143
+ ),
11144
+ Promise.all(
11145
+ classifiers.map(async (name) => ({
11146
+ name,
11147
+ value: await readJson(
11148
+ store,
11149
+ caseRecordKey(key, id, "classification-log", name)
11150
+ )
11151
+ }))
11152
+ )
11153
+ ]);
11154
+ if (!base) throw new Error(`Durable eval case ${id} is missing`);
11155
+ const scores = /* @__PURE__ */ Object.create(null);
11156
+ for (const { name, value } of scoreValues) {
11157
+ if (value !== void 0) scores[name] = value;
11158
+ }
11159
+ const loggedScores = /* @__PURE__ */ Object.create(null);
11160
+ for (const { name, value } of scoreLogValues) {
11161
+ if (value) loggedScores[name] = true;
11162
+ }
11163
+ const classifications = /* @__PURE__ */ Object.create(null);
11164
+ for (const { name, value } of classificationValues) {
11165
+ if (value !== void 0) classifications[name] = value;
11166
+ }
11167
+ const loggedClassifications = /* @__PURE__ */ Object.create(null);
11168
+ for (const { name, value } of classificationLogValues) {
11169
+ if (value) loggedClassifications[name] = true;
11170
+ }
11171
+ return {
11172
+ ...base,
11173
+ metadata: task?.metadata ?? base.metadata,
11174
+ tags: task ? task.tags : base.tags,
11175
+ taskComplete: task !== void 0,
11176
+ taskLogged: taskLog !== void 0,
11177
+ output: task?.output,
11178
+ rootSpan: taskLog?.rootSpan,
11179
+ scores,
11180
+ loggedScores,
11181
+ classifications,
11182
+ loggedClassifications
11183
+ };
11184
+ }
11185
+ async function readRunState(definition, store, key) {
11186
+ const record = await readJson(store, key);
11187
+ if (!record) return void 0;
11188
+ const plans = plannedBatches(definition, record.runId, record.caseIds);
11189
+ const [cases, batchRecords] = await Promise.all([
11190
+ Promise.all(
11191
+ record.caseIds.map((id) => readCaseRecord(definition, store, key, id))
11192
+ ),
11193
+ Promise.all(
11194
+ plans.map(async ({ id }) => {
11195
+ return readJson(store, batchRecordKey(key, id));
11196
+ })
11197
+ )
11198
+ ]);
11199
+ const batches = batchRecords.filter(
11200
+ (value) => value !== void 0
11201
+ );
11202
+ const { caseIds: _caseIds, ...state } = record;
11203
+ return { ...state, cases, batches };
11204
+ }
11205
+ async function writeRunRecord(store, key, state) {
11206
+ const { cases, batches: _batches, ...record } = state;
11207
+ await writeJson(store, key, {
11208
+ ...record,
11209
+ caseIds: cases.map(({ id }) => id)
11210
+ });
11211
+ }
11212
+ async function writeCaseBaseRecords(store, key, records) {
11213
+ await Promise.all(
11214
+ records.map(
11215
+ ({ id, caseId, trialIndex, datum, metadata, tags }) => writeJson(store, caseRecordKey(key, id, "base"), {
11216
+ id,
11217
+ caseId,
11218
+ trialIndex,
11219
+ datum,
11220
+ metadata,
11221
+ tags
11222
+ })
11223
+ )
11224
+ );
11225
+ }
11226
+ async function writeCaseRecords(store, key, records) {
11227
+ const writes = [];
11228
+ for (const record of records) {
11229
+ if (record.taskComplete) {
11230
+ writes.push(
11231
+ writeJson(store, caseRecordKey(key, record.id, "task"), {
11232
+ output: record.output,
11233
+ metadata: record.metadata,
11234
+ tags: record.tags,
11235
+ taskComplete: true
11236
+ })
11237
+ );
11238
+ }
11239
+ if (record.taskLogged) {
11240
+ writes.push(
11241
+ writeJson(store, caseRecordKey(key, record.id, "task-log"), {
11242
+ rootSpan: record.rootSpan,
11243
+ taskLogged: true
11244
+ })
11245
+ );
11246
+ }
11247
+ for (const [name, value] of Object.entries(record.scores)) {
11248
+ writes.push(
11249
+ writeJson(store, caseRecordKey(key, record.id, "score", name), value)
11250
+ );
11251
+ }
11252
+ for (const name of Object.keys(record.loggedScores)) {
11253
+ writes.push(
11254
+ writeJson(
11255
+ store,
11256
+ caseRecordKey(key, record.id, "score-log", name),
11257
+ true
11258
+ )
11259
+ );
11260
+ }
11261
+ for (const [name, value] of Object.entries(record.classifications)) {
11262
+ writes.push(
11263
+ writeJson(
11264
+ store,
11265
+ caseRecordKey(key, record.id, "classification", name),
11266
+ value
11267
+ )
11268
+ );
11269
+ }
11270
+ for (const name of Object.keys(record.loggedClassifications)) {
11271
+ writes.push(
11272
+ writeJson(
11273
+ store,
11274
+ caseRecordKey(key, record.id, "classification-log", name),
11275
+ true
11276
+ )
11277
+ );
11278
+ }
11279
+ }
11280
+ await Promise.all(writes);
11281
+ }
11282
+ async function writeBatchRecords(store, key, records) {
11283
+ await Promise.all(
11284
+ records.map(
11285
+ (record) => writeJson(store, batchRecordKey(key, record.id), record)
11286
+ )
11287
+ );
11288
+ }
11289
+ function deterministicId(value) {
11290
+ const hex = contentVersion(encoder.encode(value)).padEnd(32, "0").slice(0, 32);
11291
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
11292
+ }
11293
+ function contentVersion(value) {
11294
+ if (isomorph_default.hash) return isomorph_default.hash(decoder.decode(value));
11295
+ let hash = 2166136261;
11296
+ for (const byte of value) {
11297
+ hash ^= byte;
11298
+ hash = Math.imul(hash, 16777619);
11299
+ }
11300
+ return (hash >>> 0).toString(16).padStart(8, "0");
11301
+ }
11302
+ async function readJson(store, key) {
11303
+ const value = await store.read(key);
11304
+ return value ? JSON.parse(decoder.decode(value)) : void 0;
11305
+ }
11306
+ async function writeJson(store, key, value) {
11307
+ await store.write(key, encoder.encode(stableStringify(value)));
11308
+ }
11309
+ function stableStringify(value) {
11310
+ return JSON.stringify(value, (_key, nested) => {
11311
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
11312
+ return Object.fromEntries(
11313
+ Object.entries(nested).sort(
11314
+ ([left], [right]) => left.localeCompare(right)
11315
+ )
11316
+ );
11317
+ }
11318
+ return nested;
11319
+ });
11320
+ }
11321
+ function assertJsonValue(value, label) {
11322
+ try {
11323
+ const serialized = JSON.stringify(value);
11324
+ if (serialized === void 0)
11325
+ throw new Error("value serializes to undefined");
11326
+ return JSON.parse(serialized);
11327
+ } catch (error) {
11328
+ throw new Error(`${label} must be JSON serializable`, { cause: error });
11329
+ }
11330
+ }
11331
+ function resultItemId(value) {
11332
+ if (typeof value !== "object" || value === null || !("id" in value) || typeof value.id !== "string") {
11333
+ throw new Error("Batch results must contain a string id");
11334
+ }
11335
+ return value.id;
11336
+ }
11337
+ function isBatchTask(value) {
11338
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === BATCH_TASK_KIND;
11339
+ }
11340
+ function isBatchScorer(value) {
11341
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === BATCH_SCORER_KIND;
11342
+ }
11343
+ function asError(error) {
11344
+ return error instanceof Error ? error : new Error(String(error));
11345
+ }
11346
+
9576
11347
  // src/agent-assertions.ts
9577
11348
  function agentAssertionScorer(callback, options = {}) {
9578
11349
  return async (args) => {
@@ -10505,6 +12276,8 @@ export {
10505
12276
  BRAINTRUST_PARENT_KEY,
10506
12277
  BaseAttachment,
10507
12278
  BaseExperiment,
12279
+ BatchScorer,
12280
+ BatchTask,
10508
12281
  BraintrustLangChainCallbackHandler,
10509
12282
  BraintrustMiddleware,
10510
12283
  BraintrustObservabilityExporter,
@@ -10518,6 +12291,8 @@ export {
10518
12291
  DEFAULT_MAX_REQUEST_SIZE,
10519
12292
  Dataset,
10520
12293
  DatasetPipeline,
12294
+ DurableEvalMemoryStore,
12295
+ DurableEvalRedisStore,
10521
12296
  ERR_PERMALINK,
10522
12297
  Eval,
10523
12298
  EvalResultWithSummary,
@@ -10576,6 +12351,7 @@ export {
10576
12351
  deepCopyEvent,
10577
12352
  exports_exports as default,
10578
12353
  defaultErrorScoreHandler,
12354
+ defineDurableEval,
10579
12355
  deserializePlainStringAsJSON,
10580
12356
  devNullWritableStream,
10581
12357
  evaluatorDefinitionSchema,
@@ -10671,5 +12447,6 @@ export {
10671
12447
  wrapPiCodingAgentSDK,
10672
12448
  wrapStrandsAgentSDK,
10673
12449
  wrapTraced,
10674
- wrapVitest
12450
+ wrapVitest,
12451
+ wrapVoyageAI
10675
12452
  };