@kortyx/agent 0.34.0 → 0.35.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.
package/dist/index.js CHANGED
@@ -6938,6 +6938,7 @@ var emitTelemetryEvent = (args) => {
6938
6938
 
6939
6939
  // src/telemetry/topology.ts
6940
6940
  var import_node_crypto2 = require("crypto");
6941
+ var import_eval_attribution = require("@kortyx/core/eval-attribution");
6941
6942
  var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
6942
6943
  var stableValue = (value) => {
6943
6944
  if (Array.isArray(value)) return value.map(stableValue);
@@ -7090,6 +7091,8 @@ var projectWorkflowTopology = (args) => {
7090
7091
  };
7091
7092
  };
7092
7093
  var prepareWorkflowTelemetry = (args) => {
7094
+ if (args.sessionId)
7095
+ (0, import_eval_attribution.associateEvalExecution)({ sessionId: args.sessionId, runId: args.runId });
7093
7096
  const telemetry = isRecord2(args.config.telemetry) ? args.config.telemetry : void 0;
7094
7097
  if (!telemetry?.reporter || !telemetry.environment || !telemetry.service) {
7095
7098
  return args.config;
@@ -10059,9 +10062,12 @@ var EvalStepResultSchema = import_zod7.z.object({
10059
10062
  EvalVerdictSchema.extend({ id: import_zod7.z.string(), text: import_zod7.z.string() })
10060
10063
  )
10061
10064
  }).strict();
10065
+ var runtimeExecution = import_zod7.z.object({ sessionId: import_zod7.z.string().min(1), runId: import_zod7.z.string().min(1) }).strict();
10062
10066
  var EvalCaseResultSchema = import_zod7.z.object({
10063
10067
  caseId: id2,
10064
10068
  repetition: import_zod7.z.number().int().positive(),
10069
+ attemptId: import_zod7.z.uuid().optional(),
10070
+ runtimeExecutions: import_zod7.z.array(runtimeExecution).optional(),
10065
10071
  sessionId: import_zod7.z.string(),
10066
10072
  status,
10067
10073
  durationMs: import_zod7.z.number().nonnegative(),
@@ -10110,6 +10116,7 @@ var EvalRunResultSchema = import_zod7.z.object({
10110
10116
  var EvalManifestSchema = import_zod7.z.object({
10111
10117
  schemaVersion: import_zod7.z.literal(1),
10112
10118
  studioJudging: import_zod7.z.literal(true).optional(),
10119
+ attemptScheduling: import_zod7.z.literal(true).optional(),
10113
10120
  suites: import_zod7.z.array(EvalSuiteSchema),
10114
10121
  responders: import_zod7.z.array(
10115
10122
  import_zod7.z.object({
@@ -10123,6 +10130,13 @@ var EvalManifestSchema = import_zod7.z.object({
10123
10130
  judge: judge.optional()
10124
10131
  }).strict();
10125
10132
  var EvalProgressSchema = import_zod7.z.discriminatedUnion("type", [
10133
+ import_zod7.z.object({
10134
+ type: import_zod7.z.literal("case-runtime-associated"),
10135
+ caseId: id2,
10136
+ repetition: import_zod7.z.number().int().positive(),
10137
+ attemptId: import_zod7.z.uuid(),
10138
+ execution: runtimeExecution
10139
+ }).strict(),
10126
10140
  import_zod7.z.object({
10127
10141
  type: import_zod7.z.literal("run-started"),
10128
10142
  caseIds: import_zod7.z.array(id2).min(1),
@@ -10138,6 +10152,7 @@ var EvalProgressSchema = import_zod7.z.discriminatedUnion("type", [
10138
10152
  }).strict(),
10139
10153
  import_zod7.z.object({
10140
10154
  type: import_zod7.z.literal("case-started"),
10155
+ attemptId: import_zod7.z.uuid().optional(),
10141
10156
  caseId: id2,
10142
10157
  repetition: import_zod7.z.number().int().positive(),
10143
10158
  sessionId: import_zod7.z.string()
@@ -10157,11 +10172,17 @@ var EvalRemoteRunRequestSchema = import_zod7.z.object({
10157
10172
  judge: EvalJudgeIdentitySchema.optional(),
10158
10173
  caseIds: import_zod7.z.array(id2).min(1).max(100).optional(),
10159
10174
  repetitions: import_zod7.z.number().int().min(1).max(20).default(1),
10160
- concurrency: import_zod7.z.number().int().min(1).max(20).default(1)
10175
+ concurrency: import_zod7.z.number().int().min(1).max(20).default(1),
10176
+ attempt: import_zod7.z.object({
10177
+ caseId: id2,
10178
+ repetition: import_zod7.z.number().int().min(1).max(20),
10179
+ evaluationId: import_zod7.z.uuid()
10180
+ }).strict().optional()
10161
10181
  }).strict();
10162
10182
  var StudioEvalStartRequestSchema = EvalRemoteRunRequestSchema.omit({
10163
10183
  grading: true,
10164
- judge: true
10184
+ judge: true,
10185
+ attempt: true
10165
10186
  }).extend({
10166
10187
  targetId: import_zod7.z.string().min(1).max(128),
10167
10188
  judge: import_zod7.z.enum(["studio", "app"]).default("studio")
@@ -10198,6 +10219,7 @@ function parseEvalSuite(value) {
10198
10219
 
10199
10220
  // src/evals/create-evals.ts
10200
10221
  var import_node_crypto5 = require("crypto");
10222
+ var import_eval_attribution2 = require("@kortyx/core/eval-attribution");
10201
10223
  var import_zod8 = require("zod");
10202
10224
 
10203
10225
  // src/evals/evidence.ts
@@ -10683,6 +10705,7 @@ function createEvals(options) {
10683
10705
  return {
10684
10706
  schemaVersion: 1,
10685
10707
  studioJudging: true,
10708
+ attemptScheduling: true,
10686
10709
  suites: clone(suites),
10687
10710
  responders: Object.entries(responders).map(([name, value]) => ({
10688
10711
  name,
@@ -10727,6 +10750,8 @@ function createEvals(options) {
10727
10750
  );
10728
10751
  if (!Number.isSafeInteger(selected.length * repetitions))
10729
10752
  throw new EvalConfigurationError("Too many case repetitions.");
10753
+ if (args.attempt && (!selected.some((item) => item.id === args.attempt?.caseId) || !Number.isSafeInteger(args.attempt.repetition) || args.attempt.repetition < 1 || args.attempt.repetition > repetitions))
10754
+ throw new EvalConfigurationError("Invalid scheduled attempt.");
10730
10755
  const studioGrading = args.grading === "studio";
10731
10756
  if (!studioGrading && !options.judge && selected.some(
10732
10757
  (item) => item.steps.some((step) => step.expect.criteria?.length)
@@ -10763,7 +10788,10 @@ function createEvals(options) {
10763
10788
  const emit = async (event) => {
10764
10789
  if (!args.onProgress || !reporting) return;
10765
10790
  try {
10766
- await args.onProgress(clone(event));
10791
+ await (0, import_eval_attribution2.withEvalAttribution)(
10792
+ void 0,
10793
+ () => args.onProgress(clone(event))
10794
+ );
10767
10795
  } catch {
10768
10796
  reporting = false;
10769
10797
  result.errors.push(failure("reporting"));
@@ -10778,138 +10806,220 @@ function createEvals(options) {
10778
10806
  controller.abort();
10779
10807
  }, defaults.caseTimeoutMs);
10780
10808
  const signal = args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal;
10809
+ const attemptId = (0, import_node_crypto5.randomUUID)();
10810
+ const runtimeExecutions = [];
10781
10811
  const attemptResult = {
10782
10812
  caseId: item.id,
10783
10813
  repetition,
10814
+ attemptId,
10815
+ runtimeExecutions,
10784
10816
  sessionId: `eval-${(0, import_node_crypto5.randomUUID)()}`,
10785
10817
  status: "passed",
10786
10818
  durationMs: 0,
10787
10819
  steps: [],
10788
10820
  errors: []
10789
10821
  };
10790
- let phase = "params";
10791
- let stepIndex;
10792
- const reportPhase = async (next) => {
10793
- phase = next;
10794
- if (args.includeActivity)
10822
+ let associationProgress = Promise.resolve();
10823
+ let acceptingExecutions = true;
10824
+ const executeAttempt = async () => {
10825
+ let phase = "params";
10826
+ let stepIndex;
10827
+ const reportPhase = async (next) => {
10828
+ phase = next;
10829
+ if (args.includeActivity)
10830
+ await emit({
10831
+ type: "case-progress",
10832
+ caseId: item.id,
10833
+ repetition,
10834
+ phase,
10835
+ ...stepIndex !== void 0 ? { stepIndex } : {}
10836
+ });
10837
+ };
10838
+ let context;
10839
+ let continuation;
10840
+ let activeStep;
10841
+ let activeStepReported = false;
10842
+ const history = [];
10843
+ const call = async (readyContext, command, activeSignal) => {
10844
+ const run = (overrides) => executeEvalChat({
10845
+ agent: options.agent,
10846
+ command,
10847
+ continuation,
10848
+ history: clone(history),
10849
+ sessionId: attemptResult.sessionId,
10850
+ clientTurnId: (0, import_node_crypto5.randomUUID)(),
10851
+ signal: activeSignal,
10852
+ ...item.workflowId ? { workflowId: item.workflowId } : {},
10853
+ ...overrides
10854
+ });
10855
+ const execution = options.execute ? await options.execute({
10856
+ ...readyContext,
10857
+ signal: activeSignal,
10858
+ command,
10859
+ continuation,
10860
+ history: clone(history),
10861
+ run
10862
+ }) : await run();
10863
+ continuation = execution.continuation;
10864
+ const observation = EvalObservationSchema.parse(
10865
+ execution.observation
10866
+ );
10867
+ if (observation.type === "interrupt" && execution.continuation === void 0)
10868
+ throw new Error(
10869
+ "A waiting interrupt requires a private continuation."
10870
+ );
10871
+ return {
10872
+ observation,
10873
+ ...continuation !== void 0 ? { continuation } : {}
10874
+ };
10875
+ };
10876
+ try {
10795
10877
  await emit({
10796
- type: "case-progress",
10878
+ type: "case-started",
10879
+ attemptId,
10797
10880
  caseId: item.id,
10798
10881
  repetition,
10799
- phase,
10800
- ...stepIndex !== void 0 ? { stepIndex } : {}
10882
+ sessionId: attemptResult.sessionId
10801
10883
  });
10802
- };
10803
- let context;
10804
- let continuation;
10805
- let activeStep;
10806
- let activeStepReported = false;
10807
- const history = [];
10808
- const call = async (readyContext, command, activeSignal) => {
10809
- const run = (overrides) => executeEvalChat({
10810
- agent: options.agent,
10811
- command,
10812
- continuation,
10813
- history: clone(history),
10814
- sessionId: attemptResult.sessionId,
10815
- clientTurnId: (0, import_node_crypto5.randomUUID)(),
10816
- signal: activeSignal,
10817
- ...item.workflowId ? { workflowId: item.workflowId } : {},
10818
- ...overrides
10819
- });
10820
- const execution = options.execute ? await options.execute({
10821
- ...readyContext,
10822
- signal: activeSignal,
10823
- command,
10824
- continuation,
10825
- history: clone(history),
10826
- run
10827
- }) : await run();
10828
- continuation = execution.continuation;
10829
- const observation = EvalObservationSchema.parse(
10830
- execution.observation
10831
- );
10832
- if (observation.type === "interrupt" && execution.continuation === void 0)
10833
- throw new Error(
10834
- "A waiting interrupt requires a private continuation."
10835
- );
10836
- return {
10837
- observation,
10838
- ...continuation !== void 0 ? { continuation } : {}
10839
- };
10840
- };
10841
- try {
10842
- await emit({
10843
- type: "case-started",
10844
- caseId: item.id,
10845
- repetition,
10846
- sessionId: attemptResult.sessionId
10847
- });
10848
- signal.throwIfAborted();
10849
- await reportPhase("params");
10850
- const params = options.paramsSchema ? await options.paramsSchema.parseAsync(item.params) : item.params;
10851
- const base = {
10852
- suiteId: suite.id,
10853
- case: item,
10854
- repetition,
10855
- sessionId: attemptResult.sessionId,
10856
- params,
10857
- signal
10858
- };
10859
- await reportPhase("setup");
10860
- const prepared = options.setup ? await options.setup(base) : void 0;
10861
- context = { ...base, prepared };
10862
- signal.throwIfAborted();
10863
- for (const [index, step] of item.steps.entries()) {
10864
- stepIndex = index;
10865
- activeStep = void 0;
10866
- activeStepReported = false;
10867
10884
  signal.throwIfAborted();
10868
- let command;
10869
- if (step.message !== void 0)
10870
- command = { type: "message", message: step.message };
10871
- else {
10872
- await reportPhase("responder");
10873
- const interrupt = attemptResult.steps.at(-1)?.observation.interrupt;
10874
- let response = step.resume;
10875
- if (handlerRef(response)) {
10876
- const responder = responders[response.using];
10877
- if (typeof responder !== "function" && (responder.schemaId !== interrupt.schemaId || responder.schemaVersion && responder.schemaVersion !== interrupt.schemaVersion))
10878
- throw new Error("Incompatible responder.");
10879
- response = await (typeof responder === "function" ? responder : responder.respond)({
10880
- ...context,
10881
- interrupt: clone(interrupt),
10882
- ...response.params !== void 0 ? { args: response.params } : {}
10883
- });
10884
- }
10885
- command = {
10886
- type: "resume",
10887
- // z.json() is inferred as optional under the package's relaxed
10888
- // declaration build. Runtime validation still requires value.
10889
- response: EvalResumeResponseSchema.parse(
10890
- response
10891
- )
10892
- };
10893
- }
10894
- signal.throwIfAborted();
10895
- await reportPhase("execute");
10896
- const { observation } = await call(context, command, signal);
10897
- const input = command.type === "message" ? { message: command.message } : { resume: command.response };
10898
- const evaluated = {
10899
- index,
10900
- input,
10901
- expectation: clone(step.expect),
10902
- observation,
10903
- status: "passed",
10904
- criteria: []
10885
+ await reportPhase("params");
10886
+ const params = options.paramsSchema ? await options.paramsSchema.parseAsync(item.params) : item.params;
10887
+ const base = {
10888
+ suiteId: suite.id,
10889
+ case: item,
10890
+ repetition,
10891
+ sessionId: attemptResult.sessionId,
10892
+ params,
10893
+ signal
10905
10894
  };
10906
- attemptResult.steps.push(evaluated);
10907
- activeStep = evaluated;
10895
+ await reportPhase("setup");
10896
+ const prepared = options.setup ? await options.setup(base) : void 0;
10897
+ context = { ...base, prepared };
10908
10898
  signal.throwIfAborted();
10909
- if (observation.type === "error") throw new Error("Agent failed.");
10910
- if (observation.type === "cancelled") {
10911
- evaluated.status = "cancelled";
10912
- attemptResult.status = "cancelled";
10899
+ for (const [index, step] of item.steps.entries()) {
10900
+ stepIndex = index;
10901
+ activeStep = void 0;
10902
+ activeStepReported = false;
10903
+ signal.throwIfAborted();
10904
+ let command;
10905
+ if (step.message !== void 0)
10906
+ command = { type: "message", message: step.message };
10907
+ else {
10908
+ await reportPhase("responder");
10909
+ const interrupt = attemptResult.steps.at(-1)?.observation.interrupt;
10910
+ let response = step.resume;
10911
+ if (handlerRef(response)) {
10912
+ const responder = responders[response.using];
10913
+ if (typeof responder !== "function" && (responder.schemaId !== interrupt.schemaId || responder.schemaVersion && responder.schemaVersion !== interrupt.schemaVersion))
10914
+ throw new Error("Incompatible responder.");
10915
+ response = await (typeof responder === "function" ? responder : responder.respond)({
10916
+ ...context,
10917
+ interrupt: clone(interrupt),
10918
+ ...response.params !== void 0 ? { args: response.params } : {}
10919
+ });
10920
+ }
10921
+ command = {
10922
+ type: "resume",
10923
+ // z.json() is inferred as optional under the package's relaxed
10924
+ // declaration build. Runtime validation still requires value.
10925
+ response: EvalResumeResponseSchema.parse(
10926
+ response
10927
+ )
10928
+ };
10929
+ }
10930
+ signal.throwIfAborted();
10931
+ await reportPhase("execute");
10932
+ const { observation } = await call(context, command, signal);
10933
+ const input = command.type === "message" ? { message: command.message } : { resume: command.response };
10934
+ const evaluated = {
10935
+ index,
10936
+ input,
10937
+ expectation: clone(step.expect),
10938
+ observation,
10939
+ status: "passed",
10940
+ criteria: []
10941
+ };
10942
+ attemptResult.steps.push(evaluated);
10943
+ activeStep = evaluated;
10944
+ signal.throwIfAborted();
10945
+ if (observation.type === "error")
10946
+ throw new Error("Agent failed.");
10947
+ if (observation.type === "cancelled") {
10948
+ evaluated.status = "cancelled";
10949
+ attemptResult.status = "cancelled";
10950
+ await emit({
10951
+ type: "step-completed",
10952
+ caseId: item.id,
10953
+ repetition,
10954
+ step: evaluated
10955
+ });
10956
+ activeStepReported = true;
10957
+ break;
10958
+ }
10959
+ if (observation.type !== step.expect.type || step.expect.schemaId && step.expect.schemaId !== observation.interrupt?.schemaId || step.expect.schemaVersion && step.expect.schemaVersion !== observation.interrupt?.schemaVersion) {
10960
+ evaluated.status = "failed";
10961
+ evaluated.reason = "The observed interaction did not match the expected answer or interrupt contract.";
10962
+ } else {
10963
+ const outputReason = missingOutputReason(
10964
+ step.expect.outputs,
10965
+ observation
10966
+ );
10967
+ if (outputReason) {
10968
+ evaluated.status = "failed";
10969
+ evaluated.reason = outputReason;
10970
+ } else {
10971
+ const reference = step.expect.reference;
10972
+ if (reference !== void 0) {
10973
+ await reportPhase("reference");
10974
+ evaluated.reference = handlerRef(reference) ? import_zod8.z.json().parse(
10975
+ await references[reference.using]({
10976
+ ...context,
10977
+ observation: clone(observation),
10978
+ ...reference.params !== void 0 ? { args: reference.params } : {}
10979
+ })
10980
+ ) : clone(reference);
10981
+ }
10982
+ await reportPhase("grading");
10983
+ evaluated.evidence = createEvalJudgeEvidence(
10984
+ observation,
10985
+ suite.evidence,
10986
+ evidenceFilters
10987
+ );
10988
+ if (studioGrading && step.expect.criteria?.length) {
10989
+ evaluated.status = "ungraded";
10990
+ attemptResult.status = "ungraded";
10991
+ }
10992
+ for (const [criterionIndex, value] of (studioGrading ? [] : step.expect.criteria ?? []).entries()) {
10993
+ signal.throwIfAborted();
10994
+ const criterion = typeof value === "string" ? { id: String(criterionIndex), text: value } : value;
10995
+ evaluated.judgeCalls = (evaluated.judgeCalls ?? 0) + 1;
10996
+ const verdict = EvalVerdictSchema.parse(
10997
+ await (0, import_eval_attribution2.withEvalAttribution)(
10998
+ void 0,
10999
+ () => options.judge.grade({
11000
+ criterion,
11001
+ onUsage: (usage) => {
11002
+ if (!signal.aborted) {
11003
+ evaluated.judgeUsage ??= [];
11004
+ evaluated.judgeUsage.push(clone(usage));
11005
+ }
11006
+ },
11007
+ input: clone(input),
11008
+ ...getEvalGradeEvidence(
11009
+ evaluated,
11010
+ attemptResult.steps.slice(0, -1)
11011
+ ),
11012
+ signal,
11013
+ ...evaluated.reference !== void 0 ? { reference: clone(evaluated.reference) } : {}
11014
+ })
11015
+ )
11016
+ );
11017
+ signal.throwIfAborted();
11018
+ evaluated.criteria.push({ ...criterion, ...verdict });
11019
+ if (!verdict.passed) evaluated.status = "failed";
11020
+ }
11021
+ }
11022
+ }
10913
11023
  await emit({
10914
11024
  type: "step-completed",
10915
11025
  caseId: item.id,
@@ -10917,161 +11027,116 @@ function createEvals(options) {
10917
11027
  step: evaluated
10918
11028
  });
10919
11029
  activeStepReported = true;
10920
- break;
10921
- }
10922
- if (observation.type !== step.expect.type || step.expect.schemaId && step.expect.schemaId !== observation.interrupt?.schemaId || step.expect.schemaVersion && step.expect.schemaVersion !== observation.interrupt?.schemaVersion) {
10923
- evaluated.status = "failed";
10924
- evaluated.reason = "The observed interaction did not match the expected answer or interrupt contract.";
10925
- } else {
10926
- const outputReason = missingOutputReason(
10927
- step.expect.outputs,
10928
- observation
10929
- );
10930
- if (outputReason) {
10931
- evaluated.status = "failed";
10932
- evaluated.reason = outputReason;
10933
- } else {
10934
- const reference = step.expect.reference;
10935
- if (reference !== void 0) {
10936
- await reportPhase("reference");
10937
- evaluated.reference = handlerRef(reference) ? import_zod8.z.json().parse(
10938
- await references[reference.using]({
10939
- ...context,
10940
- observation: clone(observation),
10941
- ...reference.params !== void 0 ? { args: reference.params } : {}
10942
- })
10943
- ) : clone(reference);
10944
- }
10945
- await reportPhase("grading");
10946
- evaluated.evidence = createEvalJudgeEvidence(
10947
- observation,
10948
- suite.evidence,
10949
- evidenceFilters
10950
- );
10951
- if (studioGrading && step.expect.criteria?.length) {
10952
- evaluated.status = "ungraded";
10953
- attemptResult.status = "ungraded";
10954
- }
10955
- for (const [criterionIndex, value] of (studioGrading ? [] : step.expect.criteria ?? []).entries()) {
10956
- signal.throwIfAborted();
10957
- const criterion = typeof value === "string" ? { id: String(criterionIndex), text: value } : value;
10958
- evaluated.judgeCalls = (evaluated.judgeCalls ?? 0) + 1;
10959
- const verdict = EvalVerdictSchema.parse(
10960
- await options.judge.grade({
10961
- criterion,
10962
- onUsage: (usage) => {
10963
- if (!signal.aborted) {
10964
- evaluated.judgeUsage ??= [];
10965
- evaluated.judgeUsage.push(clone(usage));
10966
- }
10967
- },
10968
- input: clone(input),
10969
- ...getEvalGradeEvidence(
10970
- evaluated,
10971
- attemptResult.steps.slice(0, -1)
10972
- ),
10973
- signal,
10974
- ...evaluated.reference !== void 0 ? { reference: clone(evaluated.reference) } : {}
10975
- })
10976
- );
10977
- signal.throwIfAborted();
10978
- evaluated.criteria.push({ ...criterion, ...verdict });
10979
- if (!verdict.passed) evaluated.status = "failed";
10980
- }
11030
+ if (evaluated.status === "failed") {
11031
+ attemptResult.status = "failed";
11032
+ break;
10981
11033
  }
11034
+ history.push(
11035
+ {
11036
+ role: "user",
11037
+ content: command.type === "message" ? command.message : JSON.stringify(command.response)
11038
+ },
11039
+ { role: "assistant", content: observation.text }
11040
+ );
10982
11041
  }
10983
- await emit({
10984
- type: "step-completed",
10985
- caseId: item.id,
10986
- repetition,
10987
- step: evaluated
10988
- });
10989
- activeStepReported = true;
10990
- if (evaluated.status === "failed") {
10991
- attemptResult.status = "failed";
10992
- break;
11042
+ } catch {
11043
+ if (signal.aborted) {
11044
+ attemptResult.status = timedOut ? "error" : "cancelled";
11045
+ if (timedOut)
11046
+ attemptResult.errors.push({
11047
+ phase,
11048
+ code: "EVAL_TIMEOUT",
11049
+ message: "Case exceeded its configured time limit."
11050
+ });
11051
+ } else {
11052
+ attemptResult.status = "error";
11053
+ attemptResult.errors.push(failure(phase));
10993
11054
  }
10994
- history.push(
10995
- {
10996
- role: "user",
10997
- content: command.type === "message" ? command.message : JSON.stringify(command.response)
10998
- },
10999
- { role: "assistant", content: observation.text }
11000
- );
11001
- }
11002
- } catch {
11003
- if (signal.aborted) {
11004
- attemptResult.status = timedOut ? "error" : "cancelled";
11005
- if (timedOut)
11006
- attemptResult.errors.push({
11007
- phase,
11008
- code: "EVAL_TIMEOUT",
11009
- message: "Case exceeded its configured time limit."
11055
+ if (activeStep && !activeStepReported) {
11056
+ activeStep.status = attemptResult.status;
11057
+ activeStep.reason = attemptResult.errors.at(-1)?.message ?? "Case was cancelled.";
11058
+ await emit({
11059
+ type: "step-completed",
11060
+ caseId: item.id,
11061
+ repetition,
11062
+ step: activeStep
11010
11063
  });
11011
- } else {
11012
- attemptResult.status = "error";
11013
- attemptResult.errors.push(failure(phase));
11014
- }
11015
- if (activeStep && !activeStepReported) {
11016
- activeStep.status = attemptResult.status;
11017
- activeStep.reason = attemptResult.errors.at(-1)?.message ?? "Case was cancelled.";
11018
- await emit({
11019
- type: "step-completed",
11020
- caseId: item.id,
11021
- repetition,
11022
- step: activeStep
11023
- });
11024
- }
11025
- } finally {
11026
- clearTimeout(timer);
11027
- if (context) {
11028
- await reportPhase("cleanup");
11029
- const cleanup = new AbortController();
11030
- const cleanupTimer = setTimeout(
11031
- () => cleanup.abort(),
11032
- defaults.cleanupTimeoutMs
11033
- );
11034
- try {
11035
- if (continuation !== void 0) {
11036
- try {
11037
- const cancelled = await call(
11038
- context,
11039
- { type: "cancel" },
11040
- cleanup.signal
11041
- );
11042
- if (cancelled.observation.type === "error" || cancelled.continuation !== void 0)
11064
+ }
11065
+ } finally {
11066
+ clearTimeout(timer);
11067
+ if (context) {
11068
+ await reportPhase("cleanup");
11069
+ const cleanup = new AbortController();
11070
+ const cleanupTimer = setTimeout(
11071
+ () => cleanup.abort(),
11072
+ defaults.cleanupTimeoutMs
11073
+ );
11074
+ try {
11075
+ if (continuation !== void 0) {
11076
+ try {
11077
+ const cancelled = await call(
11078
+ context,
11079
+ { type: "cancel" },
11080
+ cleanup.signal
11081
+ );
11082
+ if (cancelled.observation.type === "error" || cancelled.continuation !== void 0)
11083
+ attemptResult.errors.push(
11084
+ failure("cleanup", "EVAL_INTERRUPT_CLEANUP_FAILED")
11085
+ );
11086
+ cleanup.signal.throwIfAborted();
11087
+ } catch {
11043
11088
  attemptResult.errors.push(
11044
11089
  failure("cleanup", "EVAL_INTERRUPT_CLEANUP_FAILED")
11045
11090
  );
11091
+ }
11092
+ }
11093
+ try {
11094
+ await options.teardown?.({
11095
+ ...context,
11096
+ signal: cleanup.signal,
11097
+ result: clone(attemptResult)
11098
+ });
11046
11099
  cleanup.signal.throwIfAborted();
11047
11100
  } catch {
11048
- attemptResult.errors.push(
11049
- failure("cleanup", "EVAL_INTERRUPT_CLEANUP_FAILED")
11050
- );
11101
+ attemptResult.errors.push(failure("cleanup"));
11051
11102
  }
11103
+ } finally {
11104
+ clearTimeout(cleanupTimer);
11052
11105
  }
11053
- try {
11054
- await options.teardown?.({
11055
- ...context,
11056
- signal: cleanup.signal,
11057
- result: clone(attemptResult)
11058
- });
11059
- cleanup.signal.throwIfAborted();
11060
- } catch {
11061
- attemptResult.errors.push(failure("cleanup"));
11062
- }
11063
- } finally {
11064
- clearTimeout(cleanupTimer);
11106
+ if (attemptResult.errors.some((error) => error.phase === "cleanup"))
11107
+ attemptResult.status = "error";
11065
11108
  }
11066
- if (attemptResult.errors.some((error) => error.phase === "cleanup"))
11067
- attemptResult.status = "error";
11109
+ attemptResult.durationMs = Date.now() - started;
11068
11110
  }
11069
- attemptResult.durationMs = Date.now() - started;
11070
- }
11071
- await emit({ type: "case-completed", result: attemptResult });
11072
- return attemptResult;
11111
+ acceptingExecutions = false;
11112
+ await associationProgress;
11113
+ await emit({ type: "case-completed", result: attemptResult });
11114
+ return attemptResult;
11115
+ };
11116
+ return (0, import_eval_attribution2.withEvalAttribution)(
11117
+ {
11118
+ attemptId,
11119
+ onExecution: (execution) => {
11120
+ if (!acceptingExecutions || runtimeExecutions.some(
11121
+ (entry) => entry.runId === execution.runId && entry.sessionId === execution.sessionId
11122
+ ))
11123
+ return;
11124
+ runtimeExecutions.push(execution);
11125
+ associationProgress = associationProgress.then(
11126
+ () => emit({
11127
+ type: "case-runtime-associated",
11128
+ caseId: item.id,
11129
+ repetition,
11130
+ attemptId,
11131
+ execution
11132
+ })
11133
+ );
11134
+ }
11135
+ },
11136
+ executeAttempt
11137
+ );
11073
11138
  };
11074
- const total = selected.length * repetitions;
11139
+ const total = args.attempt ? 1 : selected.length * repetitions;
11075
11140
  if (args.includeActivity)
11076
11141
  await emit({
11077
11142
  type: "run-started",
@@ -11085,8 +11150,11 @@ function createEvals(options) {
11085
11150
  Array.from({ length: Math.min(concurrency, total) }, async () => {
11086
11151
  while (cursor < total) {
11087
11152
  const index = cursor++;
11088
- const item = selected[Math.floor(index / repetitions)];
11089
- completed[index] = await attempt(item, index % repetitions + 1);
11153
+ const item = args.attempt ? selected.find((item2) => item2.id === args.attempt?.caseId) : selected[Math.floor(index / repetitions)];
11154
+ completed[index] = await attempt(
11155
+ item,
11156
+ args.attempt?.repetition ?? index % repetitions + 1
11157
+ );
11090
11158
  }
11091
11159
  })
11092
11160
  );
@@ -11396,6 +11464,7 @@ function createEvalRouteHandler({
11396
11464
  const digest = (value) => (0, import_node_crypto6.createHash)("sha256").update(value).digest();
11397
11465
  const expected = digest(`Bearer ${serviceKey}`);
11398
11466
  let active = 0;
11467
+ const evaluations = /* @__PURE__ */ new Map();
11399
11468
  return async (request) => {
11400
11469
  if (!(0, import_node_crypto6.timingSafeEqual)(
11401
11470
  expected,
@@ -11452,14 +11521,32 @@ function createEvalRouteHandler({
11452
11521
  { error: "App judge changed. Refresh before running." },
11453
11522
  { status: 409 }
11454
11523
  );
11455
- if (active >= maxActiveRuns)
11524
+ if (body.attempt && (!manifest.attemptScheduling || !suite.cases.some((item) => item.id === body.attempt?.caseId) || body.caseIds && !body.caseIds.includes(body.attempt.caseId) || body.attempt.repetition > body.repetitions))
11525
+ return Response.json(
11526
+ { error: "Unsupported or invalid scheduled attempt." },
11527
+ { status: 409 }
11528
+ );
11529
+ const group = body.attempt && evaluations.get(body.attempt.evaluationId);
11530
+ if (group && (group.concurrency !== body.concurrency || group.active >= group.concurrency))
11531
+ return Response.json(
11532
+ { error: "Evaluation concurrency exhausted or changed." },
11533
+ { status: 429 }
11534
+ );
11535
+ if (!group && active >= maxActiveRuns)
11456
11536
  return Response.json(
11457
11537
  { error: "Eval executor is busy." },
11458
11538
  { status: 429 }
11459
11539
  );
11460
11540
  if (request.signal.aborted)
11461
11541
  return Response.json({ error: "Request cancelled." }, { status: 409 });
11462
- active++;
11542
+ if (body.attempt) {
11543
+ const state = group ?? { active: 0, concurrency: body.concurrency };
11544
+ if (!group) {
11545
+ active++;
11546
+ evaluations.set(body.attempt.evaluationId, state);
11547
+ }
11548
+ state.active++;
11549
+ } else active++;
11463
11550
  const controller = new AbortController();
11464
11551
  const requestSignal = request.signal;
11465
11552
  const abortRequest = () => controller.abort();
@@ -11475,6 +11562,7 @@ function createEvalRouteHandler({
11475
11562
  };
11476
11563
  void evals.run({
11477
11564
  suiteId: body.suiteId,
11565
+ ...body.attempt ? { attempt: body.attempt } : {},
11478
11566
  ...body.grading ? { grading: body.grading } : {},
11479
11567
  ...body.judge ? { judgeIdentity: body.judge } : {},
11480
11568
  repetitions: body.repetitions,
@@ -11487,7 +11575,14 @@ function createEvalRouteHandler({
11487
11575
  () => send({ type: "error", message: "Eval runner failed." })
11488
11576
  ).finally(() => {
11489
11577
  requestSignal.removeEventListener("abort", abortRequest);
11490
- active--;
11578
+ if (body.attempt) {
11579
+ const state = evaluations.get(body.attempt.evaluationId);
11580
+ state.active--;
11581
+ if (!state.active) {
11582
+ evaluations.delete(body.attempt.evaluationId);
11583
+ active--;
11584
+ }
11585
+ } else active--;
11491
11586
  if (!controller.signal.aborted) output.close();
11492
11587
  });
11493
11588
  },