@plune-ai/cli 0.32.1 → 0.33.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.cjs CHANGED
@@ -457,24 +457,24 @@ function priceFrom(price, usage) {
457
457
  var defaultWarn = (message) => {
458
458
  process.stderr.write(message + "\n");
459
459
  };
460
- function resolveCost(usage, model, reportedCostUsd, overrides, deps = {}) {
460
+ function resolveCostEntry(usage, model, reportedCostUsd, overrides, deps = {}) {
461
461
  const table = deps.table ?? PRICE_TABLE;
462
462
  const warn = deps.warn ?? defaultWarn;
463
463
  const override = overrides?.[model];
464
464
  if (override !== void 0) {
465
- return priceFrom(override, usage);
465
+ return { cost_usd: priceFrom(override, usage), priced: true };
466
466
  }
467
467
  if (reportedCostUsd !== void 0) {
468
- return reportedCostUsd;
468
+ return { cost_usd: reportedCostUsd, priced: true };
469
469
  }
470
470
  const tablePrice = table[model];
471
471
  if (tablePrice !== void 0) {
472
- return priceFrom(tablePrice, usage);
472
+ return { cost_usd: priceFrom(tablePrice, usage), priced: true };
473
473
  }
474
474
  warn(
475
475
  `plune: no price for model "${model}" \u2014 reporting cost_usd=0. Set pricing["${model}"] in plune.yaml to track its cost.`
476
476
  );
477
- return 0;
477
+ return { cost_usd: 0, priced: false };
478
478
  }
479
479
 
480
480
  // src/providers/anthropic.ts
@@ -520,7 +520,7 @@ function makeAnthropicProvider(config, env, pricing) {
520
520
  }
521
521
  },
522
522
  estimateCost(usage, reportedCostUsd) {
523
- return { cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing) };
523
+ return resolveCostEntry(usage, config.model, reportedCostUsd, pricing);
524
524
  }
525
525
  };
526
526
  }
@@ -576,7 +576,7 @@ function makeOpenAiCompatibleProvider(opts) {
576
576
  }
577
577
  },
578
578
  estimateCost(usage, reportedCostUsd) {
579
- return { cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing) };
579
+ return resolveCostEntry(usage, config.model, reportedCostUsd, pricing);
580
580
  }
581
581
  };
582
582
  }
@@ -638,9 +638,7 @@ function makeEstimateProvider(config, pricing) {
638
638
  complete: () => Promise.reject(
639
639
  new Error("The --dry-run provider only estimates cost; it never calls a model.")
640
640
  ),
641
- estimateCost: (usage, reportedCostUsd) => ({
642
- cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing)
643
- })
641
+ estimateCost: (usage, reportedCostUsd) => resolveCostEntry(usage, config.model, reportedCostUsd, pricing)
644
642
  };
645
643
  }
646
644
 
@@ -1426,6 +1424,12 @@ function toRunError(err) {
1426
1424
  function estimateUsage(prompt5, maxTokens) {
1427
1425
  return { input_tokens: Math.ceil(prompt5.length / 4), output_tokens: maxTokens };
1428
1426
  }
1427
+ function pricedField(priced) {
1428
+ return priced === void 0 ? {} : { priced };
1429
+ }
1430
+ function costFields(c) {
1431
+ return { cost_usd: c.cost_usd, ...pricedField(c.priced) };
1432
+ }
1429
1433
  async function runRow(p) {
1430
1434
  const t0 = p.now();
1431
1435
  const prompt5 = resolvePrompt(p.template, p.row.vars);
@@ -1433,7 +1437,8 @@ async function runRow(p) {
1433
1437
  const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
1434
1438
  if (p.dryRun) {
1435
1439
  const est = estimateUsage(prompt5, maxTokens);
1436
- const usage2 = { ...est, cost_usd: p.provider.estimateCost(est).cost_usd };
1440
+ const cost = p.provider.estimateCost(est);
1441
+ const usage2 = { ...est, ...costFields(cost) };
1437
1442
  return { vars: p.row.vars, output: null, cached: false, usage: usage2, assertions: [] };
1438
1443
  }
1439
1444
  const key = cacheKey(
@@ -1471,7 +1476,9 @@ async function runRow(p) {
1471
1476
  usage = {
1472
1477
  input_tokens: hit.usage.input_tokens,
1473
1478
  output_tokens: hit.usage.output_tokens,
1474
- cost_usd: 0
1479
+ cost_usd: 0,
1480
+ // A hit costs nothing, but whether that 0 is a known price depends on the model.
1481
+ ...pricedField(p.provider.estimateCost(hit.usage).priced)
1475
1482
  };
1476
1483
  cacheSaved = {
1477
1484
  input_tokens: hit.usage.input_tokens,
@@ -1488,12 +1495,13 @@ async function runRow(p) {
1488
1495
  prompt_resolved: prompt5
1489
1496
  });
1490
1497
  output = res.output;
1498
+ const cost = p.provider.estimateCost(res.usage, res.cost_usd);
1491
1499
  usage = {
1492
1500
  input_tokens: res.usage.input_tokens,
1493
1501
  output_tokens: res.usage.output_tokens,
1494
1502
  // Prefer the provider-reported actual cost (res.cost_usd) when present; resolveCost still
1495
1503
  // lets a config pricing override win, else falls back to the table estimate (ADR-PRC01).
1496
- cost_usd: p.provider.estimateCost(res.usage, res.cost_usd).cost_usd
1504
+ ...costFields(cost)
1497
1505
  };
1498
1506
  if (!p.noCache && isCacheable(res) && answerParses(res.output, p.assertions)) {
1499
1507
  p.cache.set(key, res);
@@ -1510,8 +1518,11 @@ async function runRow(p) {
1510
1518
  }
1511
1519
  }
1512
1520
  let judgeCost = 0;
1521
+ let judgeUnpriced = false;
1513
1522
  const judge = buildJudge(p.provider, p.providerConfig, (u) => {
1514
- judgeCost += p.provider.estimateCost(u).cost_usd;
1523
+ const c = p.provider.estimateCost(u);
1524
+ judgeCost += c.cost_usd;
1525
+ if (c.priced === false) judgeUnpriced = true;
1515
1526
  });
1516
1527
  const records = [];
1517
1528
  let assertionError;
@@ -1536,7 +1547,12 @@ async function runRow(p) {
1536
1547
  break;
1537
1548
  }
1538
1549
  }
1539
- const finalUsage = { ...usage, cost_usd: usage.cost_usd + judgeCost };
1550
+ const finalUsage = {
1551
+ ...usage,
1552
+ cost_usd: usage.cost_usd + judgeCost,
1553
+ // A row is priced only when every call in it was; the field stays absent if the provider never said.
1554
+ ...judgeUnpriced ? { priced: false } : {}
1555
+ };
1540
1556
  return {
1541
1557
  vars: p.row.vars,
1542
1558
  output,
@@ -1758,7 +1774,9 @@ var assertionResultRecordSchema2 = import_zod3.z.object({
1758
1774
  var usageSchema = import_zod3.z.object({
1759
1775
  input_tokens: import_zod3.z.number(),
1760
1776
  output_tokens: import_zod3.z.number(),
1761
- cost_usd: import_zod3.z.number()
1777
+ cost_usd: import_zod3.z.number(),
1778
+ /** cli#151. Optional and additive — a RunResult written before it validates unchanged. */
1779
+ priced: import_zod3.z.boolean().optional()
1762
1780
  });
1763
1781
  var runErrorSchema = import_zod3.z.object({
1764
1782
  code: import_zod3.z.string(),
package/dist/index.d.cts CHANGED
@@ -44,6 +44,12 @@ interface Usage {
44
44
  input_tokens: number;
45
45
  output_tokens: number;
46
46
  cost_usd: number;
47
+ /**
48
+ * plune-ai/cli#151. `true` when the price of the model is known, a zero price included (a `:free`
49
+ * model, `pricing: 0`); `false` when nobody has a price and `cost_usd` is a placeholder 0. Optional
50
+ * and additive: a report without it reads as before, and `schemaVersion` stays 1.
51
+ */
52
+ priced?: boolean;
47
53
  }
48
54
  interface RunError {
49
55
  code: string;
@@ -263,6 +269,8 @@ interface CompletionResponse {
263
269
  }
264
270
  interface CostEstimate {
265
271
  cost_usd: number;
272
+ /** Whether `cost_usd` is a known price (zero included) rather than the 0 of an unpriced model. Optional and additive. */
273
+ priced?: boolean;
266
274
  }
267
275
  interface Provider<_TConfig extends ProviderConfig = ProviderConfig> {
268
276
  complete(req: CompletionRequest): Promise<CompletionResponse>;
@@ -510,6 +518,7 @@ declare const runResultSchema: z.ZodObject<{
510
518
  input_tokens: z.ZodNumber;
511
519
  output_tokens: z.ZodNumber;
512
520
  cost_usd: z.ZodNumber;
521
+ priced: z.ZodOptional<z.ZodBoolean>;
513
522
  }, z.core.$strip>>;
514
523
  latency_ms: z.ZodOptional<z.ZodNumber>;
515
524
  error: z.ZodOptional<z.ZodObject<{
package/dist/index.d.ts CHANGED
@@ -44,6 +44,12 @@ interface Usage {
44
44
  input_tokens: number;
45
45
  output_tokens: number;
46
46
  cost_usd: number;
47
+ /**
48
+ * plune-ai/cli#151. `true` when the price of the model is known, a zero price included (a `:free`
49
+ * model, `pricing: 0`); `false` when nobody has a price and `cost_usd` is a placeholder 0. Optional
50
+ * and additive: a report without it reads as before, and `schemaVersion` stays 1.
51
+ */
52
+ priced?: boolean;
47
53
  }
48
54
  interface RunError {
49
55
  code: string;
@@ -263,6 +269,8 @@ interface CompletionResponse {
263
269
  }
264
270
  interface CostEstimate {
265
271
  cost_usd: number;
272
+ /** Whether `cost_usd` is a known price (zero included) rather than the 0 of an unpriced model. Optional and additive. */
273
+ priced?: boolean;
266
274
  }
267
275
  interface Provider<_TConfig extends ProviderConfig = ProviderConfig> {
268
276
  complete(req: CompletionRequest): Promise<CompletionResponse>;
@@ -510,6 +518,7 @@ declare const runResultSchema: z.ZodObject<{
510
518
  input_tokens: z.ZodNumber;
511
519
  output_tokens: z.ZodNumber;
512
520
  cost_usd: z.ZodNumber;
521
+ priced: z.ZodOptional<z.ZodBoolean>;
513
522
  }, z.core.$strip>>;
514
523
  latency_ms: z.ZodOptional<z.ZodNumber>;
515
524
  error: z.ZodOptional<z.ZodObject<{
package/dist/index.js CHANGED
@@ -397,24 +397,24 @@ function priceFrom(price, usage) {
397
397
  var defaultWarn = (message) => {
398
398
  process.stderr.write(message + "\n");
399
399
  };
400
- function resolveCost(usage, model, reportedCostUsd, overrides, deps = {}) {
400
+ function resolveCostEntry(usage, model, reportedCostUsd, overrides, deps = {}) {
401
401
  const table = deps.table ?? PRICE_TABLE;
402
402
  const warn = deps.warn ?? defaultWarn;
403
403
  const override = overrides?.[model];
404
404
  if (override !== void 0) {
405
- return priceFrom(override, usage);
405
+ return { cost_usd: priceFrom(override, usage), priced: true };
406
406
  }
407
407
  if (reportedCostUsd !== void 0) {
408
- return reportedCostUsd;
408
+ return { cost_usd: reportedCostUsd, priced: true };
409
409
  }
410
410
  const tablePrice = table[model];
411
411
  if (tablePrice !== void 0) {
412
- return priceFrom(tablePrice, usage);
412
+ return { cost_usd: priceFrom(tablePrice, usage), priced: true };
413
413
  }
414
414
  warn(
415
415
  `plune: no price for model "${model}" \u2014 reporting cost_usd=0. Set pricing["${model}"] in plune.yaml to track its cost.`
416
416
  );
417
- return 0;
417
+ return { cost_usd: 0, priced: false };
418
418
  }
419
419
 
420
420
  // src/providers/anthropic.ts
@@ -460,7 +460,7 @@ function makeAnthropicProvider(config, env, pricing) {
460
460
  }
461
461
  },
462
462
  estimateCost(usage, reportedCostUsd) {
463
- return { cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing) };
463
+ return resolveCostEntry(usage, config.model, reportedCostUsd, pricing);
464
464
  }
465
465
  };
466
466
  }
@@ -516,7 +516,7 @@ function makeOpenAiCompatibleProvider(opts) {
516
516
  }
517
517
  },
518
518
  estimateCost(usage, reportedCostUsd) {
519
- return { cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing) };
519
+ return resolveCostEntry(usage, config.model, reportedCostUsd, pricing);
520
520
  }
521
521
  };
522
522
  }
@@ -578,9 +578,7 @@ function makeEstimateProvider(config, pricing) {
578
578
  complete: () => Promise.reject(
579
579
  new Error("The --dry-run provider only estimates cost; it never calls a model.")
580
580
  ),
581
- estimateCost: (usage, reportedCostUsd) => ({
582
- cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing)
583
- })
581
+ estimateCost: (usage, reportedCostUsd) => resolveCostEntry(usage, config.model, reportedCostUsd, pricing)
584
582
  };
585
583
  }
586
584
 
@@ -1366,6 +1364,12 @@ function toRunError(err) {
1366
1364
  function estimateUsage(prompt5, maxTokens) {
1367
1365
  return { input_tokens: Math.ceil(prompt5.length / 4), output_tokens: maxTokens };
1368
1366
  }
1367
+ function pricedField(priced) {
1368
+ return priced === void 0 ? {} : { priced };
1369
+ }
1370
+ function costFields(c) {
1371
+ return { cost_usd: c.cost_usd, ...pricedField(c.priced) };
1372
+ }
1369
1373
  async function runRow(p) {
1370
1374
  const t0 = p.now();
1371
1375
  const prompt5 = resolvePrompt(p.template, p.row.vars);
@@ -1373,7 +1377,8 @@ async function runRow(p) {
1373
1377
  const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
1374
1378
  if (p.dryRun) {
1375
1379
  const est = estimateUsage(prompt5, maxTokens);
1376
- const usage2 = { ...est, cost_usd: p.provider.estimateCost(est).cost_usd };
1380
+ const cost = p.provider.estimateCost(est);
1381
+ const usage2 = { ...est, ...costFields(cost) };
1377
1382
  return { vars: p.row.vars, output: null, cached: false, usage: usage2, assertions: [] };
1378
1383
  }
1379
1384
  const key = cacheKey(
@@ -1411,7 +1416,9 @@ async function runRow(p) {
1411
1416
  usage = {
1412
1417
  input_tokens: hit.usage.input_tokens,
1413
1418
  output_tokens: hit.usage.output_tokens,
1414
- cost_usd: 0
1419
+ cost_usd: 0,
1420
+ // A hit costs nothing, but whether that 0 is a known price depends on the model.
1421
+ ...pricedField(p.provider.estimateCost(hit.usage).priced)
1415
1422
  };
1416
1423
  cacheSaved = {
1417
1424
  input_tokens: hit.usage.input_tokens,
@@ -1428,12 +1435,13 @@ async function runRow(p) {
1428
1435
  prompt_resolved: prompt5
1429
1436
  });
1430
1437
  output = res.output;
1438
+ const cost = p.provider.estimateCost(res.usage, res.cost_usd);
1431
1439
  usage = {
1432
1440
  input_tokens: res.usage.input_tokens,
1433
1441
  output_tokens: res.usage.output_tokens,
1434
1442
  // Prefer the provider-reported actual cost (res.cost_usd) when present; resolveCost still
1435
1443
  // lets a config pricing override win, else falls back to the table estimate (ADR-PRC01).
1436
- cost_usd: p.provider.estimateCost(res.usage, res.cost_usd).cost_usd
1444
+ ...costFields(cost)
1437
1445
  };
1438
1446
  if (!p.noCache && isCacheable(res) && answerParses(res.output, p.assertions)) {
1439
1447
  p.cache.set(key, res);
@@ -1450,8 +1458,11 @@ async function runRow(p) {
1450
1458
  }
1451
1459
  }
1452
1460
  let judgeCost = 0;
1461
+ let judgeUnpriced = false;
1453
1462
  const judge = buildJudge(p.provider, p.providerConfig, (u) => {
1454
- judgeCost += p.provider.estimateCost(u).cost_usd;
1463
+ const c = p.provider.estimateCost(u);
1464
+ judgeCost += c.cost_usd;
1465
+ if (c.priced === false) judgeUnpriced = true;
1455
1466
  });
1456
1467
  const records = [];
1457
1468
  let assertionError;
@@ -1476,7 +1487,12 @@ async function runRow(p) {
1476
1487
  break;
1477
1488
  }
1478
1489
  }
1479
- const finalUsage = { ...usage, cost_usd: usage.cost_usd + judgeCost };
1490
+ const finalUsage = {
1491
+ ...usage,
1492
+ cost_usd: usage.cost_usd + judgeCost,
1493
+ // A row is priced only when every call in it was; the field stays absent if the provider never said.
1494
+ ...judgeUnpriced ? { priced: false } : {}
1495
+ };
1480
1496
  return {
1481
1497
  vars: p.row.vars,
1482
1498
  output,
@@ -1698,7 +1714,9 @@ var assertionResultRecordSchema2 = z3.object({
1698
1714
  var usageSchema = z3.object({
1699
1715
  input_tokens: z3.number(),
1700
1716
  output_tokens: z3.number(),
1701
- cost_usd: z3.number()
1717
+ cost_usd: z3.number(),
1718
+ /** cli#151. Optional and additive — a RunResult written before it validates unchanged. */
1719
+ priced: z3.boolean().optional()
1702
1720
  });
1703
1721
  var runErrorSchema = z3.object({
1704
1722
  code: z3.string(),
@@ -726,6 +726,7 @@ function boundKey(key) {
726
726
  if (/[\uD800-\uDBFF]$/.test(head)) head = head.slice(0, -1);
727
727
  return { ...key, value: `${head}#${digest}` };
728
728
  }
729
+ var keyId = (key) => `${key.kind ?? ""}\0${key.value}`;
729
730
  function whyUnoffered(out) {
730
731
  if (out.ok || out.status !== 429) return "other";
731
732
  if (/review queue quota reached/i.test(out.detail)) return "queue";
@@ -863,7 +864,7 @@ async function startRun(passed, declared) {
863
864
  stats.deferred += results.length;
864
865
  }
865
866
  async function resolveKeys(values) {
866
- const unknown = values.filter((k) => !resolved.has(k.value));
867
+ const unknown = values.filter((k) => !resolved.has(keyId(k)));
867
868
  if (unknown.length === 0 || offline) return;
868
869
  for (const part of chunk(unknown, RESOLVE_CHUNK)) {
869
870
  const out = await client.post(
@@ -875,7 +876,7 @@ async function startRun(passed, declared) {
875
876
  return;
876
877
  }
877
878
  for (const row of out.body.results) {
878
- if (typeof row?.key?.value === "string") resolved.set(row.key.value, row.testCaseId);
879
+ if (typeof row?.key?.value === "string") resolved.set(keyId(row.key), row.testCaseId);
879
880
  }
880
881
  }
881
882
  }
@@ -884,7 +885,7 @@ async function startRun(passed, declared) {
884
885
  const stated2 = expected?.slice(0, EXPECTED_MAX) ?? [];
885
886
  const configuration2 = stated2.length === 0 ? void 0 : {
886
887
  expected: stated2.flatMap((keys) => {
887
- const hit = keys.map((k) => resolved.get(k.value)).find((v) => typeof v === "string");
888
+ const hit = keys.map((k) => resolved.get(keyId(k))).find((v) => typeof v === "string");
888
889
  if (typeof hit === "string") return [{ testCaseId: hit }];
889
890
  return keys[0] === void 0 ? [] : [{ externalKey: keys[0] }];
890
891
  }),
@@ -933,7 +934,7 @@ async function startRun(passed, declared) {
933
934
  if (result.testCaseId !== void 0) return { kind: "found", testCaseId: result.testCaseId };
934
935
  let unanswered = false;
935
936
  for (const key of result.keys) {
936
- const hit = resolved.get(key.value);
937
+ const hit = resolved.get(keyId(key));
937
938
  if (typeof hit === "string") return { kind: "found", testCaseId: hit };
938
939
  if (hit === void 0) unanswered = true;
939
940
  }
@@ -945,14 +946,14 @@ async function startRun(passed, declared) {
945
946
  if (result.keys.length === 0) return;
946
947
  const title = boundTitle(result.title);
947
948
  if (title !== result.title) shortened.add(result.title);
948
- discoveries.set(result.keys[0].value, {
949
+ discoveries.set(keyId(result.keys[0]), {
949
950
  keys: result.keys,
950
951
  title,
951
952
  source: result.source,
952
953
  specRef: result.specRef,
953
954
  rawStatus: result.rawStatus
954
955
  });
955
- held.set(result.keys[0].value, result);
956
+ for (const key of result.keys) held.set(keyId(key), result);
956
957
  }
957
958
  async function offer() {
958
959
  if (discoveries.size === 0 || offline || runId === null) return;
@@ -976,9 +977,9 @@ async function startRun(passed, declared) {
976
977
  const submissions = [];
977
978
  const filesByKey = /* @__PURE__ */ new Map();
978
979
  for (const r of made) {
979
- const pending = typeof r.id === "string" && r.id !== "" ? held.get(r.key?.value) : void 0;
980
+ const pending = typeof r.id === "string" && r.id !== "" && typeof r.key?.value === "string" ? held.get(keyId(r.key)) : void 0;
980
981
  if (pending === void 0) continue;
981
- held.delete(r.key.value);
982
+ for (const key of pending.keys) held.delete(keyId(key));
982
983
  const { keys: _keys, files, ...rest } = pending;
983
984
  submissions.push({ ...rest, testCaseId: r.id });
984
985
  if (files !== void 0) filesByKey.set(pending.resultKey, files);
@@ -676,6 +676,7 @@ function boundKey(key) {
676
676
  if (/[\uD800-\uDBFF]$/.test(head)) head = head.slice(0, -1);
677
677
  return { ...key, value: `${head}#${digest}` };
678
678
  }
679
+ var keyId = (key) => `${key.kind ?? ""}\0${key.value}`;
679
680
  function whyUnoffered(out) {
680
681
  if (out.ok || out.status !== 429) return "other";
681
682
  if (/review queue quota reached/i.test(out.detail)) return "queue";
@@ -813,7 +814,7 @@ async function startRun(passed, declared) {
813
814
  stats.deferred += results.length;
814
815
  }
815
816
  async function resolveKeys(values) {
816
- const unknown = values.filter((k) => !resolved.has(k.value));
817
+ const unknown = values.filter((k) => !resolved.has(keyId(k)));
817
818
  if (unknown.length === 0 || offline) return;
818
819
  for (const part of chunk(unknown, RESOLVE_CHUNK)) {
819
820
  const out = await client.post(
@@ -825,7 +826,7 @@ async function startRun(passed, declared) {
825
826
  return;
826
827
  }
827
828
  for (const row of out.body.results) {
828
- if (typeof row?.key?.value === "string") resolved.set(row.key.value, row.testCaseId);
829
+ if (typeof row?.key?.value === "string") resolved.set(keyId(row.key), row.testCaseId);
829
830
  }
830
831
  }
831
832
  }
@@ -834,7 +835,7 @@ async function startRun(passed, declared) {
834
835
  const stated2 = expected?.slice(0, EXPECTED_MAX) ?? [];
835
836
  const configuration2 = stated2.length === 0 ? void 0 : {
836
837
  expected: stated2.flatMap((keys) => {
837
- const hit = keys.map((k) => resolved.get(k.value)).find((v) => typeof v === "string");
838
+ const hit = keys.map((k) => resolved.get(keyId(k))).find((v) => typeof v === "string");
838
839
  if (typeof hit === "string") return [{ testCaseId: hit }];
839
840
  return keys[0] === void 0 ? [] : [{ externalKey: keys[0] }];
840
841
  }),
@@ -883,7 +884,7 @@ async function startRun(passed, declared) {
883
884
  if (result.testCaseId !== void 0) return { kind: "found", testCaseId: result.testCaseId };
884
885
  let unanswered = false;
885
886
  for (const key of result.keys) {
886
- const hit = resolved.get(key.value);
887
+ const hit = resolved.get(keyId(key));
887
888
  if (typeof hit === "string") return { kind: "found", testCaseId: hit };
888
889
  if (hit === void 0) unanswered = true;
889
890
  }
@@ -895,14 +896,14 @@ async function startRun(passed, declared) {
895
896
  if (result.keys.length === 0) return;
896
897
  const title = boundTitle(result.title);
897
898
  if (title !== result.title) shortened.add(result.title);
898
- discoveries.set(result.keys[0].value, {
899
+ discoveries.set(keyId(result.keys[0]), {
899
900
  keys: result.keys,
900
901
  title,
901
902
  source: result.source,
902
903
  specRef: result.specRef,
903
904
  rawStatus: result.rawStatus
904
905
  });
905
- held.set(result.keys[0].value, result);
906
+ for (const key of result.keys) held.set(keyId(key), result);
906
907
  }
907
908
  async function offer() {
908
909
  if (discoveries.size === 0 || offline || runId === null) return;
@@ -926,9 +927,9 @@ async function startRun(passed, declared) {
926
927
  const submissions = [];
927
928
  const filesByKey = /* @__PURE__ */ new Map();
928
929
  for (const r of made) {
929
- const pending = typeof r.id === "string" && r.id !== "" ? held.get(r.key?.value) : void 0;
930
+ const pending = typeof r.id === "string" && r.id !== "" && typeof r.key?.value === "string" ? held.get(keyId(r.key)) : void 0;
930
931
  if (pending === void 0) continue;
931
- held.delete(r.key.value);
932
+ for (const key of pending.keys) held.delete(keyId(key));
932
933
  const { keys: _keys, files, ...rest } = pending;
933
934
  submissions.push({ ...rest, testCaseId: r.id });
934
935
  if (files !== void 0) filesByKey.set(pending.resultKey, files);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.32.1",
3
+ "version": "0.33.0",
4
4
  "description": "AI-powered assertion testing for LLM apps — write assertions, run them against any provider, get a pass/fail report (local, CI, or diff).",
5
5
  "keywords": [
6
6
  "ai",