anpord 0.1.23 → 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.
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.23";
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`;
@@ -4543,11 +4626,17 @@ const runLocally = (request, options = {}) => effect.Effect.gen(function* () {
4543
4626
  }) ?? effect.Effect.void), effect.Effect.map((outcome) => ({
4544
4627
  durationMs: outcome.durationMs,
4545
4628
  name: subject.name,
4546
- status: outcome.outcome.status
4629
+ status: outcome.outcome.status,
4630
+ turns: outcome.outcome.commandCount,
4631
+ usage: effect.Option.getOrNull(outcome.result.usage)
4547
4632
  }))), { concurrency: 1 });
4548
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"]]))))));
4549
4634
  const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
4550
- 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);
4551
4640
  const localProblems = (cases) => cases.flatMap((one) => one.status === "passed" ? [] : [`${one.name} ${one.status}`]);
4552
4641
  //#endregion
4553
4642
  //#region src/cli/eval-outcome.ts
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.23";
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`;
@@ -4543,11 +4626,17 @@ const runLocally = (request, options = {}) => Effect.gen(function* () {
4543
4626
  }) ?? Effect.void), Effect.map((outcome) => ({
4544
4627
  durationMs: outcome.durationMs,
4545
4628
  name: subject.name,
4546
- status: outcome.outcome.status
4629
+ status: outcome.outcome.status,
4630
+ turns: outcome.outcome.commandCount,
4631
+ usage: Option.getOrNull(outcome.result.usage)
4547
4632
  }))), { concurrency: 1 });
4548
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"]]))))));
4549
4634
  const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
4550
- 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);
4551
4640
  const localProblems = (cases) => cases.flatMap((one) => one.status === "passed" ? [] : [`${one.name} ${one.status}`]);
4552
4641
  //#endregion
4553
4642
  //#region src/cli/eval-outcome.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anpord",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "TypeScript SDK and CLI for coding agent evals and prompt management.",
5
5
  "license": "MIT",
6
6
  "type": "module",