anpord 0.1.22 → 0.1.24

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.
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.cjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.cjs";
2
2
  //#region src/mock-api/context.d.ts
3
3
  declare const apiContext: {
4
4
  url: (name: string) => Promise<string>;
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.mjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.mjs";
2
2
  //#region src/mock-api/context.d.ts
3
3
  declare const apiContext: {
4
4
  url: (name: string) => Promise<string>;
@@ -58,6 +58,14 @@ declare const ApiCall_base: Schema.Class<ApiCall, {
58
58
  }>>>;
59
59
  }>, never, {
60
60
  readonly api: string;
61
+ } & {
62
+ readonly status: number;
63
+ } & {
64
+ readonly startedAt: number;
65
+ } & {
66
+ readonly path: string;
67
+ } & {
68
+ readonly index: number;
61
69
  } & {
62
70
  readonly output: {
63
71
  readonly text: string;
@@ -72,14 +80,6 @@ declare const ApiCall_base: Schema.Class<ApiCall, {
72
80
  readonly format: "text" | "json";
73
81
  readonly state: "captured" | "disabled" | "unavailable";
74
82
  };
75
- } & {
76
- readonly status: number;
77
- } & {
78
- readonly startedAt: number;
79
- } & {
80
- readonly path: string;
81
- } & {
82
- readonly index: number;
83
83
  } & {
84
84
  readonly error: string | null;
85
85
  } & {
@@ -58,6 +58,14 @@ declare const ApiCall_base: Schema.Class<ApiCall, {
58
58
  }>>>;
59
59
  }>, never, {
60
60
  readonly api: string;
61
+ } & {
62
+ readonly status: number;
63
+ } & {
64
+ readonly startedAt: number;
65
+ } & {
66
+ readonly path: string;
67
+ } & {
68
+ readonly index: number;
61
69
  } & {
62
70
  readonly output: {
63
71
  readonly text: string;
@@ -72,14 +80,6 @@ declare const ApiCall_base: Schema.Class<ApiCall, {
72
80
  readonly format: "text" | "json";
73
81
  readonly state: "captured" | "disabled" | "unavailable";
74
82
  };
75
- } & {
76
- readonly status: number;
77
- } & {
78
- readonly startedAt: number;
79
- } & {
80
- readonly path: string;
81
- } & {
82
- readonly index: number;
83
83
  } & {
84
84
  readonly error: string | null;
85
85
  } & {
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.cjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.cjs";
2
2
  import { t as ApiDefinition } from "./define-NZvmQuIv.cjs";
3
3
  //#region src/mock-api/runtime.d.ts
4
4
  declare const withApi: <A>({ api, run }: {
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.mjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.mjs";
2
2
  import { t as ApiDefinition } from "./define-NZvmQuIv.mjs";
3
3
  //#region src/mock-api/runtime.d.ts
4
4
  declare const withApi: <A>({ api, run }: {
package/dist/api.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.cjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.cjs";
2
2
  import { a as endpoint, i as api, n as ApiHandlerContext, r as EndpointDefinition, t as ApiDefinition } from "./define-NZvmQuIv.cjs";
3
3
  import { withApi } from "./api-runtime.cjs";
4
4
  export { type ApiCall, type ApiDefinition, type ApiHandlerContext, type EndpointDefinition, api, endpoint, withApi };
package/dist/api.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ApiCall } from "./api-mocks-B8a93t2t.mjs";
1
+ import { t as ApiCall } from "./api-mocks-ofqP55P_.mjs";
2
2
  import { a as endpoint, i as api, n as ApiHandlerContext, r as EndpointDefinition, t as ApiDefinition } from "./define-NZvmQuIv.mjs";
3
3
  import { withApi } from "./api-runtime.mjs";
4
4
  export { type ApiCall, type ApiDefinition, type ApiHandlerContext, type EndpointDefinition, api, endpoint, withApi };
package/dist/bin.cjs CHANGED
@@ -19,7 +19,7 @@ let yaml = require("yaml");
19
19
  let node_crypto = require("node:crypto");
20
20
  let node_child_process = require("node:child_process");
21
21
  //#region package.json
22
- var version$1 = "0.1.22";
22
+ var version$1 = "0.1.24";
23
23
  //#endregion
24
24
  //#region ../template/src/extract.ts
25
25
  /** Reads escapes too, so text the renderer leaves literal is never reported as
@@ -220,6 +220,86 @@ var NoEvalFiles = class extends effect.Data.TaggedError("NoEvalFiles") {
220
220
  }
221
221
  };
222
222
  //#endregion
223
+ //#region ../schema/src/domain/usage-health.ts
224
+ const CACHE_FLOOR = 5e4;
225
+ const PER_TURN_CEILING = 12e3;
226
+ const perTurnInput = ({ turns, usage }) => turns <= 0 ? usage.inputTokens : Math.round(usage.inputTokens / turns);
227
+ const usageConcerns = (reading) => {
228
+ const { usage } = reading;
229
+ const served = usage.inputTokens + usage.cacheReadTokens;
230
+ const concerns = [];
231
+ if (perTurnInput(reading) >= PER_TURN_CEILING) concerns.push("context-grew-fast");
232
+ if (served >= CACHE_FLOOR && usage.cacheReadTokens === 0) concerns.push("nothing-cached");
233
+ return concerns;
234
+ };
235
+ const CONCERN_REASONS = {
236
+ "context-grew-fast": "Each turn carried a large context, which is what happens when an agent reads a dependency or a long file into the conversation and then re-sends it every turn after.",
237
+ "nothing-cached": "Nothing was served from cache, so every turn paid full price for the turns before it."
238
+ };
239
+ //#endregion
240
+ //#region src/cli/eval-usage.ts
241
+ const THOUSAND = 1e3;
242
+ const MILLION = 1e6;
243
+ const CENT = .01;
244
+ const trialsOf = (run) => run.cells.flatMap((cell) => cell.trials);
245
+ const runUsage = (run) => {
246
+ const concerns = /* @__PURE__ */ new Set();
247
+ let inputTokens = 0;
248
+ let outputTokens = 0;
249
+ let totalTokens = 0;
250
+ for (const trial of trialsOf(run)) {
251
+ if (!trial.usage) continue;
252
+ inputTokens += trial.usage.inputTokens;
253
+ outputTokens += trial.usage.outputTokens;
254
+ totalTokens += trial.usage.totalTokens;
255
+ for (const concern of usageConcerns({
256
+ turns: trial.commands,
257
+ usage: trial.usage
258
+ })) concerns.add(concern);
259
+ }
260
+ return {
261
+ concerns: [...concerns],
262
+ inputTokens,
263
+ outputTokens,
264
+ totalTokens,
265
+ usd: run.costs?.estimatedEquivalentUsd ?? null
266
+ };
267
+ };
268
+ const formatTokens = (value) => {
269
+ if (value >= MILLION) return `${(value / MILLION).toFixed(1)}M`;
270
+ return value >= THOUSAND ? `${Math.round(value / THOUSAND)}k` : String(value);
271
+ };
272
+ const formatUsd = (value) => value < CENT ? "<$0.01" : `$${value.toFixed(2)}`;
273
+ const usageLines = (usage) => {
274
+ if (usage.totalTokens === 0) return [];
275
+ const spend = usage.usd === null ? "" : `, ${formatUsd(usage.usd)} est.`;
276
+ return [`${formatTokens(usage.totalTokens)} tokens (${formatTokens(usage.inputTokens)} in, ${formatTokens(usage.outputTokens)} out)${spend}`, ...usage.concerns.map((concern) => CONCERN_REASONS[concern])];
277
+ };
278
+ const localUsage = (cases) => {
279
+ const concerns = /* @__PURE__ */ new Set();
280
+ let inputTokens = 0;
281
+ let outputTokens = 0;
282
+ let totalTokens = 0;
283
+ for (const one of cases) {
284
+ if (!one.usage) continue;
285
+ inputTokens += one.usage.inputTokens;
286
+ outputTokens += one.usage.outputTokens;
287
+ totalTokens += one.usage.inputTokens + one.usage.outputTokens;
288
+ for (const concern of usageConcerns({
289
+ turns: one.turns,
290
+ usage: one.usage
291
+ })) concerns.add(concern);
292
+ }
293
+ return {
294
+ concerns: [...concerns],
295
+ inputTokens,
296
+ outputTokens,
297
+ totalTokens,
298
+ usd: null
299
+ };
300
+ };
301
+ const localUsageLines = (cases) => usageLines(localUsage(cases)).map((line) => ` ${line}`);
302
+ //#endregion
223
303
  //#region src/cli/eval-grid.ts
224
304
  const DIM = "\x1B[2m";
225
305
  const BOLD = "\x1B[1m";
@@ -289,7 +369,10 @@ const formatGrid = (run, trials, elapsedMs) => {
289
369
  }
290
370
  lines.push("");
291
371
  }
292
- lines.push(paint(DIM, ` ${formatElapsed(elapsedMs)} elapsed`));
372
+ const usage = runUsage(run);
373
+ const [spend, ...concerns] = usageLines(usage);
374
+ lines.push(paint(DIM, spend === void 0 ? ` ${formatElapsed(elapsedMs)} elapsed` : ` ${formatElapsed(elapsedMs)} elapsed · ${spend}`));
375
+ for (const concern of concerns) lines.push(paint(YELLOW, ` ${concern}`));
293
376
  return lines;
294
377
  };
295
378
  const up = (rows) => `[${rows}A`;
@@ -4485,7 +4568,7 @@ const SandboxProviderLive = effect.Layer.effect(SandboxProvider, effect.Effect.g
4485
4568
  //#endregion
4486
4569
  //#region ../eval/src/local-layer.ts
4487
4570
  const LocalAdaptersLive = effect.Layer.effect(SandboxAdapters, effect.Effect.map(effect.Effect.cached(makeLocalAdapter), (local) => SandboxAdapters.of({ resolve: (provider) => provider === "local" ? local : effect.Effect.dieMessage(`a local run cannot open a ${provider} sandbox`) })));
4488
- const evalLocalWith = (credentials) => LocalTrialsLive.pipe(effect.Layer.provide(AgentTrialJudgedLive.pipe(effect.Layer.provide(AgentTrialLive.pipe(effect.Layer.provide(ScorerChecksLive.pipe(effect.Layer.provide(ScorerGroundTruthLive))), effect.Layer.provide(SuspenderSleeping), effect.Layer.provide(SimulatedUserLive.pipe(effect.Layer.provide(_effect_platform.FetchHttpClient.layer))))), effect.Layer.provide(JudgeModelLive.pipe(effect.Layer.provide(_effect_platform.FetchHttpClient.layer))), effect.Layer.provide(effect.Layer.mergeAll(HarnessesLive, HarnessVersionsLive)))), effect.Layer.provide(credentials), effect.Layer.provide(SandboxProviderLive.pipe(effect.Layer.provide(LocalAdaptersLive))));
4571
+ const evalLocalWith = (credentials) => LocalTrialsLive.pipe(effect.Layer.provide(AgentTrialJudgedLive.pipe(effect.Layer.provide(AgentTrialLive.pipe(effect.Layer.provide(ScorerChecksLive.pipe(effect.Layer.provide(ScorerGroundTruthLive))), effect.Layer.provide(SuspenderSleeping), effect.Layer.provide(SimulatedUserLive.pipe(effect.Layer.provide(_effect_platform.FetchHttpClient.layer))))), effect.Layer.provide(JudgeModelLive.pipe(effect.Layer.provide(_effect_platform.FetchHttpClient.layer))), effect.Layer.provide(effect.Layer.mergeAll(HarnessesLive, HarnessVersionsLive)))), effect.Layer.provide(credentials), effect.Layer.provide(SandboxProviderLive.pipe(effect.Layer.provide(LocalAdaptersLive))), effect.Layer.provideMerge(HarnessVersionsLive));
4489
4572
  const EvalLocalLive = evalLocalWith(CredentialResolverFromEnv);
4490
4573
  //#endregion
4491
4574
  //#region src/cli/local-env.ts
@@ -4514,19 +4597,21 @@ const localEnv = (directory) => Object.assign({}, ...FILES.map((file) => read(di
4514
4597
  //#region src/cli/eval-local.ts
4515
4598
  const runLocally = (request, options = {}) => effect.Effect.gen(function* () {
4516
4599
  const trials = yield* LocalTrials;
4600
+ const versions = yield* HarnessVersions;
4517
4601
  const forwarded = localEnv(process.cwd());
4518
4602
  const task = request.tasks[0];
4519
4603
  if (task === void 0) return [];
4604
+ const harnessVersion = yield* versions.version(task.harness);
4520
4605
  return yield* effect.Effect.forEach(request.cases, (subject) => trials.run({
4521
4606
  caseName: subject.name,
4522
4607
  forwarded,
4523
4608
  harness: task.harness,
4524
- harnessVersion: "local",
4609
+ harnessVersion,
4525
4610
  model: task.model,
4526
4611
  prepare: subject.prepare ?? null,
4527
4612
  profile: profileOfRequest(task.profile),
4528
4613
  prompt: request.prompt,
4529
- source: { kind: "empty" },
4614
+ source: subject.source ?? { kind: "empty" },
4530
4615
  user: subject.user ?? null,
4531
4616
  validator: subject.validator ?? null,
4532
4617
  verifyCommand: subject.verify
@@ -4541,11 +4626,17 @@ const runLocally = (request, options = {}) => effect.Effect.gen(function* () {
4541
4626
  }) ?? effect.Effect.void), effect.Effect.map((outcome) => ({
4542
4627
  durationMs: outcome.durationMs,
4543
4628
  name: subject.name,
4544
- status: outcome.outcome.status
4629
+ status: outcome.outcome.status,
4630
+ turns: outcome.outcome.commandCount,
4631
+ usage: effect.Option.getOrNull(outcome.result.usage)
4545
4632
  }))), { concurrency: 1 });
4546
4633
  }).pipe(effect.Effect.provide(options.credentials === void 0 ? EvalLocalLive : evalLocalWith(credentialResolverFrom(options.credentials))), effect.Effect.scoped, effect.Effect.withConfigProvider(effect.ConfigProvider.fromEnv().pipe(effect.ConfigProvider.orElse(() => effect.ConfigProvider.fromMap(/* @__PURE__ */ new Map([["ANPORD_LOCAL_SANDBOX", "true"]]))))));
4547
4634
  const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
4548
- const reportLocal = (file, cases) => effect.Effect.forEach([`${file}: ${cases.length} case(s) on this machine`, ...cases.map((one) => ` ${one.status} ${one.name} ${seconds(one.durationMs)}`)], note);
4635
+ const reportLocal = (file, cases) => effect.Effect.forEach([
4636
+ `${file}: ${cases.length} case(s) on this machine`,
4637
+ ...cases.map((one) => ` ${one.status} ${one.name} ${seconds(one.durationMs)}`),
4638
+ ...localUsageLines(cases)
4639
+ ], note);
4549
4640
  const localProblems = (cases) => cases.flatMap((one) => one.status === "passed" ? [] : [`${one.name} ${one.status}`]);
4550
4641
  //#endregion
4551
4642
  //#region src/cli/eval-outcome.ts
@@ -4822,20 +4913,19 @@ const recordedLocally = (file) => effect.Effect.gen(function* () {
4822
4913
  trigger
4823
4914
  } });
4824
4915
  yield* reportStarted(file, started.id);
4916
+ yield* effect.Effect.addFinalizer(() => api.evals.finishRun({ payload: { id: started.id } }).pipe(effect.Effect.ignore));
4825
4917
  const leased = yield* api.evals.credentials({ payload: {
4826
4918
  harness: payload.tasks[0]?.harness ?? "codex",
4827
4919
  id: started.id
4828
4920
  } }).pipe(effect.Effect.map((lease) => lease.values), effect.Effect.catchAll(() => effect.Effect.succeed(void 0)));
4829
- const cases = yield* runLocally(payload, {
4921
+ return yield* runLocally(payload, {
4830
4922
  credentials: leased,
4831
4923
  onTrial: (trial) => api.evals.reportTrial({ payload: {
4832
4924
  id: started.id,
4833
4925
  trial
4834
4926
  } }).pipe(effect.Effect.ignore)
4835
4927
  });
4836
- yield* api.evals.finishRun({ payload: { id: started.id } }).pipe(effect.Effect.ignore);
4837
- return cases;
4838
- }).pipe(effect.Effect.provide(require_config.ClientLayer));
4928
+ }).pipe(effect.Effect.scoped, effect.Effect.provide(require_config.ClientLayer));
4839
4929
  const runOneEvalLocally = (file) => require_config.apiKeyConfig.pipe(effect.Effect.matchEffect({
4840
4930
  onFailure: () => require_compiler.compileEvalEffect(file).pipe(effect.Effect.flatMap((payload) => runLocally(payload))),
4841
4931
  onSuccess: () => recordedLocally(file)
package/dist/bin.mjs CHANGED
@@ -19,7 +19,7 @@ import { parseDocument } from "yaml";
19
19
  import { createHash } from "node:crypto";
20
20
  import { spawn } from "node:child_process";
21
21
  //#region package.json
22
- var version$1 = "0.1.22";
22
+ var version$1 = "0.1.24";
23
23
  //#endregion
24
24
  //#region ../template/src/extract.ts
25
25
  /** Reads escapes too, so text the renderer leaves literal is never reported as
@@ -220,6 +220,86 @@ var NoEvalFiles = class extends Data.TaggedError("NoEvalFiles") {
220
220
  }
221
221
  };
222
222
  //#endregion
223
+ //#region ../schema/src/domain/usage-health.ts
224
+ const CACHE_FLOOR = 5e4;
225
+ const PER_TURN_CEILING = 12e3;
226
+ const perTurnInput = ({ turns, usage }) => turns <= 0 ? usage.inputTokens : Math.round(usage.inputTokens / turns);
227
+ const usageConcerns = (reading) => {
228
+ const { usage } = reading;
229
+ const served = usage.inputTokens + usage.cacheReadTokens;
230
+ const concerns = [];
231
+ if (perTurnInput(reading) >= PER_TURN_CEILING) concerns.push("context-grew-fast");
232
+ if (served >= CACHE_FLOOR && usage.cacheReadTokens === 0) concerns.push("nothing-cached");
233
+ return concerns;
234
+ };
235
+ const CONCERN_REASONS = {
236
+ "context-grew-fast": "Each turn carried a large context, which is what happens when an agent reads a dependency or a long file into the conversation and then re-sends it every turn after.",
237
+ "nothing-cached": "Nothing was served from cache, so every turn paid full price for the turns before it."
238
+ };
239
+ //#endregion
240
+ //#region src/cli/eval-usage.ts
241
+ const THOUSAND = 1e3;
242
+ const MILLION = 1e6;
243
+ const CENT = .01;
244
+ const trialsOf = (run) => run.cells.flatMap((cell) => cell.trials);
245
+ const runUsage = (run) => {
246
+ const concerns = /* @__PURE__ */ new Set();
247
+ let inputTokens = 0;
248
+ let outputTokens = 0;
249
+ let totalTokens = 0;
250
+ for (const trial of trialsOf(run)) {
251
+ if (!trial.usage) continue;
252
+ inputTokens += trial.usage.inputTokens;
253
+ outputTokens += trial.usage.outputTokens;
254
+ totalTokens += trial.usage.totalTokens;
255
+ for (const concern of usageConcerns({
256
+ turns: trial.commands,
257
+ usage: trial.usage
258
+ })) concerns.add(concern);
259
+ }
260
+ return {
261
+ concerns: [...concerns],
262
+ inputTokens,
263
+ outputTokens,
264
+ totalTokens,
265
+ usd: run.costs?.estimatedEquivalentUsd ?? null
266
+ };
267
+ };
268
+ const formatTokens = (value) => {
269
+ if (value >= MILLION) return `${(value / MILLION).toFixed(1)}M`;
270
+ return value >= THOUSAND ? `${Math.round(value / THOUSAND)}k` : String(value);
271
+ };
272
+ const formatUsd = (value) => value < CENT ? "<$0.01" : `$${value.toFixed(2)}`;
273
+ const usageLines = (usage) => {
274
+ if (usage.totalTokens === 0) return [];
275
+ const spend = usage.usd === null ? "" : `, ${formatUsd(usage.usd)} est.`;
276
+ return [`${formatTokens(usage.totalTokens)} tokens (${formatTokens(usage.inputTokens)} in, ${formatTokens(usage.outputTokens)} out)${spend}`, ...usage.concerns.map((concern) => CONCERN_REASONS[concern])];
277
+ };
278
+ const localUsage = (cases) => {
279
+ const concerns = /* @__PURE__ */ new Set();
280
+ let inputTokens = 0;
281
+ let outputTokens = 0;
282
+ let totalTokens = 0;
283
+ for (const one of cases) {
284
+ if (!one.usage) continue;
285
+ inputTokens += one.usage.inputTokens;
286
+ outputTokens += one.usage.outputTokens;
287
+ totalTokens += one.usage.inputTokens + one.usage.outputTokens;
288
+ for (const concern of usageConcerns({
289
+ turns: one.turns,
290
+ usage: one.usage
291
+ })) concerns.add(concern);
292
+ }
293
+ return {
294
+ concerns: [...concerns],
295
+ inputTokens,
296
+ outputTokens,
297
+ totalTokens,
298
+ usd: null
299
+ };
300
+ };
301
+ const localUsageLines = (cases) => usageLines(localUsage(cases)).map((line) => ` ${line}`);
302
+ //#endregion
223
303
  //#region src/cli/eval-grid.ts
224
304
  const DIM = "\x1B[2m";
225
305
  const BOLD = "\x1B[1m";
@@ -289,7 +369,10 @@ const formatGrid = (run, trials, elapsedMs) => {
289
369
  }
290
370
  lines.push("");
291
371
  }
292
- lines.push(paint(DIM, ` ${formatElapsed(elapsedMs)} elapsed`));
372
+ const usage = runUsage(run);
373
+ const [spend, ...concerns] = usageLines(usage);
374
+ lines.push(paint(DIM, spend === void 0 ? ` ${formatElapsed(elapsedMs)} elapsed` : ` ${formatElapsed(elapsedMs)} elapsed · ${spend}`));
375
+ for (const concern of concerns) lines.push(paint(YELLOW, ` ${concern}`));
293
376
  return lines;
294
377
  };
295
378
  const up = (rows) => `[${rows}A`;
@@ -4485,7 +4568,7 @@ const SandboxProviderLive = Layer.effect(SandboxProvider, Effect.gen(function* (
4485
4568
  //#endregion
4486
4569
  //#region ../eval/src/local-layer.ts
4487
4570
  const LocalAdaptersLive = Layer.effect(SandboxAdapters, Effect.map(Effect.cached(makeLocalAdapter), (local) => SandboxAdapters.of({ resolve: (provider) => provider === "local" ? local : Effect.dieMessage(`a local run cannot open a ${provider} sandbox`) })));
4488
- const evalLocalWith = (credentials) => LocalTrialsLive.pipe(Layer.provide(AgentTrialJudgedLive.pipe(Layer.provide(AgentTrialLive.pipe(Layer.provide(ScorerChecksLive.pipe(Layer.provide(ScorerGroundTruthLive))), Layer.provide(SuspenderSleeping), Layer.provide(SimulatedUserLive.pipe(Layer.provide(FetchHttpClient.layer))))), Layer.provide(JudgeModelLive.pipe(Layer.provide(FetchHttpClient.layer))), Layer.provide(Layer.mergeAll(HarnessesLive, HarnessVersionsLive)))), Layer.provide(credentials), Layer.provide(SandboxProviderLive.pipe(Layer.provide(LocalAdaptersLive))));
4571
+ const evalLocalWith = (credentials) => LocalTrialsLive.pipe(Layer.provide(AgentTrialJudgedLive.pipe(Layer.provide(AgentTrialLive.pipe(Layer.provide(ScorerChecksLive.pipe(Layer.provide(ScorerGroundTruthLive))), Layer.provide(SuspenderSleeping), Layer.provide(SimulatedUserLive.pipe(Layer.provide(FetchHttpClient.layer))))), Layer.provide(JudgeModelLive.pipe(Layer.provide(FetchHttpClient.layer))), Layer.provide(Layer.mergeAll(HarnessesLive, HarnessVersionsLive)))), Layer.provide(credentials), Layer.provide(SandboxProviderLive.pipe(Layer.provide(LocalAdaptersLive))), Layer.provideMerge(HarnessVersionsLive));
4489
4572
  const EvalLocalLive = evalLocalWith(CredentialResolverFromEnv);
4490
4573
  //#endregion
4491
4574
  //#region src/cli/local-env.ts
@@ -4514,19 +4597,21 @@ const localEnv = (directory) => Object.assign({}, ...FILES.map((file) => read(di
4514
4597
  //#region src/cli/eval-local.ts
4515
4598
  const runLocally = (request, options = {}) => Effect.gen(function* () {
4516
4599
  const trials = yield* LocalTrials;
4600
+ const versions = yield* HarnessVersions;
4517
4601
  const forwarded = localEnv(process.cwd());
4518
4602
  const task = request.tasks[0];
4519
4603
  if (task === void 0) return [];
4604
+ const harnessVersion = yield* versions.version(task.harness);
4520
4605
  return yield* Effect.forEach(request.cases, (subject) => trials.run({
4521
4606
  caseName: subject.name,
4522
4607
  forwarded,
4523
4608
  harness: task.harness,
4524
- harnessVersion: "local",
4609
+ harnessVersion,
4525
4610
  model: task.model,
4526
4611
  prepare: subject.prepare ?? null,
4527
4612
  profile: profileOfRequest(task.profile),
4528
4613
  prompt: request.prompt,
4529
- source: { kind: "empty" },
4614
+ source: subject.source ?? { kind: "empty" },
4530
4615
  user: subject.user ?? null,
4531
4616
  validator: subject.validator ?? null,
4532
4617
  verifyCommand: subject.verify
@@ -4541,11 +4626,17 @@ const runLocally = (request, options = {}) => Effect.gen(function* () {
4541
4626
  }) ?? Effect.void), Effect.map((outcome) => ({
4542
4627
  durationMs: outcome.durationMs,
4543
4628
  name: subject.name,
4544
- status: outcome.outcome.status
4629
+ status: outcome.outcome.status,
4630
+ turns: outcome.outcome.commandCount,
4631
+ usage: Option.getOrNull(outcome.result.usage)
4545
4632
  }))), { concurrency: 1 });
4546
4633
  }).pipe(Effect.provide(options.credentials === void 0 ? EvalLocalLive : evalLocalWith(credentialResolverFrom(options.credentials))), Effect.scoped, Effect.withConfigProvider(ConfigProvider.fromEnv().pipe(ConfigProvider.orElse(() => ConfigProvider.fromMap(/* @__PURE__ */ new Map([["ANPORD_LOCAL_SANDBOX", "true"]]))))));
4547
4634
  const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
4548
- const reportLocal = (file, cases) => Effect.forEach([`${file}: ${cases.length} case(s) on this machine`, ...cases.map((one) => ` ${one.status} ${one.name} ${seconds(one.durationMs)}`)], note);
4635
+ const reportLocal = (file, cases) => Effect.forEach([
4636
+ `${file}: ${cases.length} case(s) on this machine`,
4637
+ ...cases.map((one) => ` ${one.status} ${one.name} ${seconds(one.durationMs)}`),
4638
+ ...localUsageLines(cases)
4639
+ ], note);
4549
4640
  const localProblems = (cases) => cases.flatMap((one) => one.status === "passed" ? [] : [`${one.name} ${one.status}`]);
4550
4641
  //#endregion
4551
4642
  //#region src/cli/eval-outcome.ts
@@ -4822,20 +4913,19 @@ const recordedLocally = (file) => Effect.gen(function* () {
4822
4913
  trigger
4823
4914
  } });
4824
4915
  yield* reportStarted(file, started.id);
4916
+ yield* Effect.addFinalizer(() => api.evals.finishRun({ payload: { id: started.id } }).pipe(Effect.ignore));
4825
4917
  const leased = yield* api.evals.credentials({ payload: {
4826
4918
  harness: payload.tasks[0]?.harness ?? "codex",
4827
4919
  id: started.id
4828
4920
  } }).pipe(Effect.map((lease) => lease.values), Effect.catchAll(() => Effect.succeed(void 0)));
4829
- const cases = yield* runLocally(payload, {
4921
+ return yield* runLocally(payload, {
4830
4922
  credentials: leased,
4831
4923
  onTrial: (trial) => api.evals.reportTrial({ payload: {
4832
4924
  id: started.id,
4833
4925
  trial
4834
4926
  } }).pipe(Effect.ignore)
4835
4927
  });
4836
- yield* api.evals.finishRun({ payload: { id: started.id } }).pipe(Effect.ignore);
4837
- return cases;
4838
- }).pipe(Effect.provide(ClientLayer));
4928
+ }).pipe(Effect.scoped, Effect.provide(ClientLayer));
4839
4929
  const runOneEvalLocally = (file) => apiKeyConfig.pipe(Effect.matchEffect({
4840
4930
  onFailure: () => compileEvalEffect(file).pipe(Effect.flatMap((payload) => runLocally(payload))),
4841
4931
  onSuccess: () => recordedLocally(file)