{"version":3,"file":"scorers.mjs","names":[],"sources":["../../../../../../../ai/src/eval/scorers.ts"],"sourcesContent":["import type {\n  EvalScore,\n  EvalScorer,\n  EvalScorerContext,\n} from \"../contracts/agent/eval.type\";\n\n/**\n * Predicate signature for {@link predicate}. Receives the same context\n * a full scorer does and returns a boolean (sync or async). A `true`\n * verdict scores `1`, `false` scores `0`.\n */\nexport type EvalPredicate<TOutput = unknown> = (\n  context: EvalScorerContext<TOutput>,\n) => boolean | Promise<boolean>;\n\n/**\n * Normalize a value for case-insensitive, whitespace-trimmed string\n * comparison. Non-string values are JSON-serialized first so a\n * structured `expected` can still be matched against structured\n * `output`.\n */\nfunction normalizeForComparison(value: unknown): string {\n  const text = typeof value === \"string\" ? value : JSON.stringify(value);\n  return text.trim().toLowerCase();\n}\n\n/**\n * Exact-match scorer. Compares the agent's output against the case's\n * `expected` reference. Prefers `result.data` (parsed structured\n * output) when present, falling back to `result.text`. Comparison is\n * trimmed and case-insensitive; structured values are compared by\n * canonical JSON.\n *\n * Scores `1` / `passed: true` on a match, `0` / `passed: false`\n * otherwise. A case with no `expected` always scores `0` — exact\n * matching is meaningless without a reference.\n *\n * @example\n * const report = await agent.eval({\n *   cases: [{ name: \"q\", input: \"2+2?\", expected: \"4\" }],\n *   scorers: [exact()],\n * });\n */\nexport function exact<TOutput = unknown>(): EvalScorer<TOutput> {\n  return (context: EvalScorerContext<TOutput>): EvalScore => {\n    if (context.case.expected === undefined) {\n      return {\n        score: 0,\n        passed: false,\n        reason: \"no expected value supplied for exact match\",\n      };\n    }\n\n    const actual = context.output ?? context.text;\n\n    if (actual === undefined) {\n      return { score: 0, passed: false, reason: \"agent produced no output\" };\n    }\n\n    const matches =\n      normalizeForComparison(actual) === normalizeForComparison(context.case.expected);\n\n    return {\n      score: matches ? 1 : 0,\n      passed: matches,\n      reason: matches ? \"exact match\" : \"output did not match expected\",\n    };\n  };\n}\n\n/**\n * Substring / contains scorer. Passes when the normalized `expected`\n * string appears anywhere in the agent's normalized output. Useful\n * when the agent's phrasing varies but a key fact must be present.\n *\n * @example\n * scorers: [contains()] // expected \"Cairo\" passes \"The capital is Cairo.\"\n */\nexport function contains<TOutput = unknown>(): EvalScorer<TOutput> {\n  return (context: EvalScorerContext<TOutput>): EvalScore => {\n    if (context.case.expected === undefined) {\n      return {\n        score: 0,\n        passed: false,\n        reason: \"no expected value supplied for contains match\",\n      };\n    }\n\n    const actual = context.output ?? context.text;\n\n    if (actual === undefined) {\n      return { score: 0, passed: false, reason: \"agent produced no output\" };\n    }\n\n    const found = normalizeForComparison(actual).includes(\n      normalizeForComparison(context.case.expected),\n    );\n\n    return {\n      score: found ? 1 : 0,\n      passed: found,\n      reason: found ? \"expected substring found\" : \"expected substring not found\",\n    };\n  };\n}\n\n/**\n * Predicate scorer. Wraps a boolean-returning callback into a scorer —\n * `true` scores `1` / `passed`, `false` scores `0` / fails. The\n * escape hatch for arbitrary assertions (\"output is valid JSON\", \"no\n * tool errored\", \"duration under budget\") that don't fit exact or\n * judge scoring.\n *\n * @example\n * scorers: [predicate((ctx) => ctx.result.report.children.every(c => c.status === \"completed\"))]\n */\nexport function predicate<TOutput = unknown>(\n  fn: EvalPredicate<TOutput>,\n): EvalScorer<TOutput> {\n  return async (context: EvalScorerContext<TOutput>): Promise<EvalScore> => {\n    const result = await fn(context);\n\n    return {\n      score: result ? 1 : 0,\n      passed: result,\n      reason: result ? \"predicate passed\" : \"predicate failed\",\n    };\n  };\n}\n"],"mappings":";;;;;;;AAqBA,SAAS,uBAAuB,OAAwB;CAEtD,QADa,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK,EAC1D,CAAC,KAAK,CAAC,CAAC,YAAY;AACjC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,QAAgD;CAC9D,QAAQ,YAAmD;EACzD,IAAI,QAAQ,KAAK,aAAa,QAC5B,OAAO;GACL,OAAO;GACP,QAAQ;GACR,QAAQ;EACV;EAGF,MAAM,SAAS,QAAQ,UAAU,QAAQ;EAEzC,IAAI,WAAW,QACb,OAAO;GAAE,OAAO;GAAG,QAAQ;GAAO,QAAQ;EAA2B;EAGvE,MAAM,UACJ,uBAAuB,MAAM,MAAM,uBAAuB,QAAQ,KAAK,QAAQ;EAEjF,OAAO;GACL,OAAO,UAAU,IAAI;GACrB,QAAQ;GACR,QAAQ,UAAU,gBAAgB;EACpC;CACF;AACF;;;;;;;;;AAUA,SAAgB,WAAmD;CACjE,QAAQ,YAAmD;EACzD,IAAI,QAAQ,KAAK,aAAa,QAC5B,OAAO;GACL,OAAO;GACP,QAAQ;GACR,QAAQ;EACV;EAGF,MAAM,SAAS,QAAQ,UAAU,QAAQ;EAEzC,IAAI,WAAW,QACb,OAAO;GAAE,OAAO;GAAG,QAAQ;GAAO,QAAQ;EAA2B;EAGvE,MAAM,QAAQ,uBAAuB,MAAM,CAAC,CAAC,SAC3C,uBAAuB,QAAQ,KAAK,QAAQ,CAC9C;EAEA,OAAO;GACL,OAAO,QAAQ,IAAI;GACnB,QAAQ;GACR,QAAQ,QAAQ,6BAA6B;EAC/C;CACF;AACF;;;;;;;;;;;AAYA,SAAgB,UACd,IACqB;CACrB,OAAO,OAAO,YAA4D;EACxE,MAAM,SAAS,MAAM,GAAG,OAAO;EAE/B,OAAO;GACL,OAAO,SAAS,IAAI;GACpB,QAAQ;GACR,QAAQ,SAAS,qBAAqB;EACxC;CACF;AACF"}