{"version":3,"sources":["../../src/internal/matchers.ts"],"sourcesContent":["/**\n * Shared configuration for built-in value matchers.\n *\n * @example\n * ```ts\n * const config: BaseMatcherConfig = {\n *   requireAll: true,\n *   allowExtras: false,\n * };\n * ```\n */\nexport interface BaseMatcherConfig {\n  /** Require every expected field or tool to match. */\n  requireAll?: boolean;\n  /** Allow actual output/tool calls to contain extra fields or calls. */\n  allowExtras?: boolean;\n  /** Emit matcher debug details to the console. */\n  debug?: boolean;\n}\n\n/**\n * Matching strategy used by structured-output and tool-call judges.\n *\n * @example\n * ```ts\n * const exact: MatchStrategy = \"strict\";\n * const custom: MatchStrategy = (expected, actual) =>\n *   String(actual).includes(String(expected));\n * ```\n */\nexport type MatchStrategy<T = unknown> =\n  | \"strict\"\n  | \"fuzzy\"\n  | ((expected: T, actual: T, context?: string) => boolean);\n\n/**\n * Options controlling fuzzy matcher behavior.\n *\n * @example\n * ```ts\n * const fuzzyOptions: FuzzyMatchOptions = {\n *   caseInsensitive: true,\n *   substring: true,\n *   numericTolerance: 0.01,\n * };\n * ```\n */\nexport interface FuzzyMatchOptions {\n  /** Compare strings without case sensitivity. */\n  caseInsensitive?: boolean;\n  /** Treat either string containing the other as a match. */\n  substring?: boolean;\n  /** Relative and absolute tolerance used for numeric comparisons. */\n  numericTolerance?: number;\n  /** Match arrays without requiring the same order. */\n  ignoreArrayOrder?: boolean;\n  /** Allow simple string/number/boolean coercions before failing. */\n  coerceTypes?: boolean;\n}\n\ntype Mismatch = {\n  key: string;\n  expected: unknown;\n  actual: unknown;\n};\n\n/** Debug logger interface accepted by matcher helpers. */\nexport interface Logger {\n  log: (message: string, ...args: unknown[]) => void;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Compares two values with strict deep equality semantics. */\nexport function strictEquals(\n  expected: unknown,\n  actual: unknown,\n  context?: string,\n): boolean {\n  void context;\n\n  if (expected === actual) {\n    return true;\n  }\n\n  if (\n    expected === null ||\n    expected === undefined ||\n    actual === null ||\n    actual === undefined\n  ) {\n    return false;\n  }\n\n  if (typeof expected !== typeof actual) {\n    return false;\n  }\n\n  if (Array.isArray(expected)) {\n    return (\n      Array.isArray(actual) &&\n      expected.length === actual.length &&\n      expected.every((item, index) =>\n        strictEquals(item, actual[index], context),\n      )\n    );\n  }\n\n  if (isRecord(expected) && isRecord(actual)) {\n    const expectedKeys = Object.keys(expected);\n    const actualKeys = Object.keys(actual);\n\n    return (\n      expectedKeys.length === actualKeys.length &&\n      expectedKeys.every(\n        (key) =>\n          key in actual && strictEquals(expected[key], actual[key], context),\n      )\n    );\n  }\n\n  return false;\n}\n\nfunction fuzzyMatchString(\n  expected: string,\n  actual: string,\n  options: FuzzyMatchOptions,\n): boolean {\n  const { caseInsensitive = true, substring = false } = options;\n  const expectedText = caseInsensitive ? expected.toLowerCase() : expected;\n  const actualText = caseInsensitive ? actual.toLowerCase() : actual;\n\n  return substring\n    ? actualText.includes(expectedText) || expectedText.includes(actualText)\n    : expectedText === actualText;\n}\n\nfunction fuzzyMatchNumber(\n  expected: number,\n  actual: number,\n  options: FuzzyMatchOptions,\n): boolean {\n  const { numericTolerance = 0.001 } = options;\n  const tolerance = Math.max(\n    Math.abs(expected) * numericTolerance,\n    numericTolerance,\n  );\n\n  return Math.abs(expected - actual) <= tolerance;\n}\n\nfunction fuzzyMatchArray(\n  expected: unknown[],\n  actual: unknown[],\n  options: FuzzyMatchOptions,\n  context?: string,\n): boolean {\n  const { ignoreArrayOrder = true } = options;\n\n  if (!ignoreArrayOrder) {\n    return (\n      expected.length === actual.length &&\n      expected.every((item, index) =>\n        fuzzyMatch(item, actual[index], options, context),\n      )\n    );\n  }\n\n  const actualUsed = actual.map(() => false);\n  return expected.every((expectedItem) => {\n    for (const [index, actualItem] of actual.entries()) {\n      if (actualUsed[index]) {\n        continue;\n      }\n\n      if (fuzzyMatch(expectedItem, actualItem, options, context)) {\n        actualUsed[index] = true;\n        return true;\n      }\n    }\n\n    return false;\n  });\n}\n\nfunction fuzzyMatchObject(\n  expected: Record<string, unknown>,\n  actual: Record<string, unknown>,\n  options: FuzzyMatchOptions,\n  context?: string,\n): boolean {\n  return Object.entries(expected).every(\n    ([key, value]) =>\n      key in actual && fuzzyMatch(value, actual[key], options, context),\n  );\n}\n\nfunction fuzzyMatchWithCoercion(expected: unknown, actual: unknown): boolean {\n  if (typeof expected === \"boolean\" && typeof actual === \"string\") {\n    return expected === (actual.toLowerCase() === \"true\" || actual === \"1\");\n  }\n\n  if (typeof expected === \"string\" && typeof actual === \"number\") {\n    return Number.parseFloat(expected) === actual;\n  }\n\n  if (typeof expected === \"number\" && typeof actual === \"string\") {\n    return expected === Number.parseFloat(actual);\n  }\n\n  return false;\n}\n\n/** Compares two values with the configured fuzzy matching rules. */\nexport function fuzzyMatch(\n  expected: unknown,\n  actual: unknown,\n  options: FuzzyMatchOptions = {},\n  context?: string,\n): boolean {\n  const { coerceTypes = false } = options;\n\n  if (expected instanceof RegExp) {\n    return typeof actual === \"string\" && expected.test(actual);\n  }\n\n  if (typeof expected === \"function\") {\n    return Boolean((expected as (value: unknown) => unknown)(actual));\n  }\n\n  if (\n    expected === null ||\n    expected === undefined ||\n    actual === null ||\n    actual === undefined\n  ) {\n    return expected === actual;\n  }\n\n  if (typeof expected === \"string\" && typeof actual === \"string\") {\n    return fuzzyMatchString(expected, actual, options);\n  }\n\n  if (typeof expected === \"number\" && typeof actual === \"number\") {\n    return fuzzyMatchNumber(expected, actual, options);\n  }\n\n  if (Array.isArray(expected) && Array.isArray(actual)) {\n    return fuzzyMatchArray(expected, actual, options, context);\n  }\n\n  if (isRecord(expected) && isRecord(actual)) {\n    return fuzzyMatchObject(expected, actual, options, context);\n  }\n\n  if (coerceTypes && fuzzyMatchWithCoercion(expected, actual)) {\n    return true;\n  }\n\n  return expected === actual;\n}\n\n/** Builds a reusable matcher function from a strict, fuzzy, or custom strategy. */\nexport function createMatcher<T = unknown>(\n  strategy: MatchStrategy<T>,\n  options?: FuzzyMatchOptions,\n): (expected: T, actual: T, context?: string) => boolean {\n  if (typeof strategy === \"function\") {\n    return (expected, actual, context) => strategy(expected, actual, context);\n  }\n\n  if (strategy === \"strict\") {\n    return (expected, actual, context) =>\n      strictEquals(expected, actual, context);\n  }\n\n  return (expected, actual, context) =>\n    fuzzyMatch(expected, actual, options ?? {}, context);\n}\n\n/** Formats a value for scorer rationale and debug output. */\nexport function formatValue(value: unknown): string {\n  if (value === undefined) {\n    return \"undefined\";\n  }\n\n  if (value === null) {\n    return \"null\";\n  }\n\n  if (value instanceof RegExp) {\n    return value.toString();\n  }\n\n  if (typeof value === \"string\") {\n    return `\"${value}\"`;\n  }\n\n  if (typeof value === \"object\") {\n    try {\n      return JSON.stringify(value);\n    } catch {\n      return String(value);\n    }\n  }\n\n  return String(value);\n}\n\n/** Returns a normalized partial-credit score for match-based scorers. */\nexport function calculatePartialScore(\n  matched: number,\n  total: number,\n  requireAll: boolean,\n): number {\n  if (requireAll && matched < total) {\n    return 0;\n  }\n\n  return total > 0 ? matched / total : 1;\n}\n\nconst defaultLogger: Logger = {\n  log: (message: string, ...args: unknown[]) => {\n    if (\n      process.env.NODE_ENV === \"development\" ||\n      process.env.NODE_ENV === \"test\" ||\n      process.env.VITEST_EVALS_DEBUG === \"true\"\n    ) {\n      console.log(message, ...args);\n    }\n  },\n};\n\n/** Emits scorer debug details through the provided logger. */\nexport function debugLog(\n  context: string,\n  data: {\n    expected: unknown;\n    actual: unknown;\n    matches?: string[];\n    mismatches?: Mismatch[];\n    extras?: string[];\n  },\n  logger: Logger = defaultLogger,\n): void {\n  logger.log(`${context} debug:`);\n  logger.log(\"Expected:\", data.expected);\n  logger.log(\"Actual:\", data.actual);\n  if (data.matches) {\n    logger.log(\"Matches:\", data.matches);\n  }\n  if (data.mismatches) {\n    logger.log(\"Mismatches:\", data.mismatches);\n  }\n  if (data.extras) {\n    logger.log(\"Extras:\", data.extras);\n  }\n}\n"],"mappings":"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","names":[]}