import { z } from 'zod';

// src/matchers.ts
var MATCH_OPTIONS_DEFAULTS = {
  toolCallOrder: "ignore",
  maxExtraToolCalls: null,
  argumentMatching: "partial"
};
function matchArgumentPlaceholder(expectedValue, actualValue) {
  if (typeof expectedValue !== "string") return null;
  switch (expectedValue.trim().toLowerCase()) {
    case "any":
      return actualValue !== void 0;
    case "string":
      return typeof actualValue === "string";
    case "number":
      return typeof actualValue === "number";
    case "boolean":
      return typeof actualValue === "boolean";
    case "object":
      return actualValue !== null && typeof actualValue === "object" && !Array.isArray(actualValue);
    case "array":
      return Array.isArray(actualValue);
    case "null":
      return actualValue === null;
    default:
      return null;
  }
}
function stableStringify(value) {
  if (value === null || typeof value !== "object") {
    return JSON.stringify(value);
  }
  if (Array.isArray(value)) {
    return `[${value.map(stableStringify).join(",")}]`;
  }
  const keys = Object.keys(value).sort();
  return `{${keys.map(
    (k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`
  ).join(",")}}`;
}
function deepEqual(a, b) {
  return stableStringify(a) === stableStringify(b);
}
function partialArgumentsMatch(expectedArgs, actualArgs) {
  for (const [key, value] of Object.entries(expectedArgs)) {
    const placeholder = matchArgumentPlaceholder(value, actualArgs[key]);
    if (placeholder !== null) {
      if (!placeholder) return false;
      continue;
    }
    if (!deepEqual(actualArgs[key], value)) {
      return false;
    }
  }
  return true;
}
function exactArgumentsMatch(expectedArgs, actualArgs) {
  return deepEqual(expectedArgs, actualArgs);
}
function argsCompatible(expectedArgs, actualArgs, mode) {
  if (mode === "ignore") return true;
  if (mode === "partial") {
    if (Object.keys(expectedArgs).length === 0) return true;
    return partialArgumentsMatch(expectedArgs, actualArgs);
  }
  return exactArgumentsMatch(expectedArgs, actualArgs);
}
function callsCompatible(expected, actual, argumentMatching) {
  if (expected.toolName !== actual.toolName) return false;
  return argsCompatible(
    expected.arguments || {},
    actual.arguments || {},
    argumentMatching
  );
}
function pair(expected, actual, mode, argumentMatching) {
  const expectedToActual = /* @__PURE__ */ new Map();
  if (mode === "strict") {
    const min = Math.min(expected.length, actual.length);
    for (let i = 0; i < min; i++) {
      if (callsCompatible(expected[i], actual[i], argumentMatching)) {
        expectedToActual.set(i, i);
      }
    }
    return { expectedToActual };
  }
  if (mode === "superset") {
    let k = 0;
    const supersetCursorByExpected = new Array(expected.length);
    for (let i = 0; i < expected.length; i++) {
      supersetCursorByExpected[i] = k;
      while (k < actual.length) {
        if (callsCompatible(expected[i], actual[k], argumentMatching)) {
          expectedToActual.set(i, k);
          k++;
          break;
        }
        k++;
      }
      if (k >= actual.length) {
        for (let j = i + 1; j < expected.length; j++) {
          supersetCursorByExpected[j] = k;
        }
        break;
      }
    }
    return { expectedToActual, supersetCursorByExpected };
  }
  const consumedActual = /* @__PURE__ */ new Set();
  for (let i = 0; i < expected.length; i++) {
    for (let j = 0; j < actual.length; j++) {
      if (consumedActual.has(j)) continue;
      if (callsCompatible(expected[i], actual[j], argumentMatching)) {
        expectedToActual.set(i, j);
        consumedActual.add(j);
        break;
      }
    }
  }
  return { expectedToActual };
}
function assertValidMaxExtra(value) {
  if (value === void 0 || value === null) return;
  if (!Number.isInteger(value) || value < 0) {
    throw new Error(
      `Invalid maxExtraToolCalls: ${value}. Must be null (unlimited) or a non-negative integer.`
    );
  }
}
function evaluateToolCalls(expected, actual, options) {
  const normalizedExpected = Array.isArray(expected) ? expected : [];
  const normalizedActual = Array.isArray(actual) ? actual : [];
  let maxExtraToolCalls;
  if (options?.maxExtraToolCalls !== void 0) {
    maxExtraToolCalls = options.maxExtraToolCalls;
  } else if (options?.allowExtraToolCalls !== void 0) {
    maxExtraToolCalls = options.allowExtraToolCalls ? null : 0;
  } else {
    maxExtraToolCalls = MATCH_OPTIONS_DEFAULTS.maxExtraToolCalls;
  }
  assertValidMaxExtra(maxExtraToolCalls);
  const toolCallOrder = options?.toolCallOrder ?? MATCH_OPTIONS_DEFAULTS.toolCallOrder;
  const argumentMatching = options?.argumentMatching ?? MATCH_OPTIONS_DEFAULTS.argumentMatching;
  if (options?.isNegativeTest) {
    return {
      missing: [],
      extra: normalizedActual,
      outOfOrder: [],
      argumentMismatches: [],
      passed: normalizedActual.length === 0
    };
  }
  if (normalizedActual.length === 0) {
    return {
      missing: normalizedExpected,
      extra: [],
      outOfOrder: [],
      argumentMismatches: [],
      passed: false
    };
  }
  const { expectedToActual, supersetCursorByExpected } = pair(
    normalizedExpected,
    normalizedActual,
    toolCallOrder,
    argumentMatching
  );
  const matchedExpectedIndices = new Set(expectedToActual.keys());
  const matchedActualIndices = new Set(expectedToActual.values());
  const argumentMismatches = [];
  for (let ei = 0; ei < normalizedExpected.length; ei++) {
    if (matchedExpectedIndices.has(ei)) continue;
    const exp = normalizedExpected[ei];
    const expectedArgs = exp.arguments || {};
    let scanStart = 0;
    let scanEnd = normalizedActual.length;
    if (toolCallOrder === "strict") {
      scanStart = ei;
      scanEnd = Math.min(ei + 1, normalizedActual.length);
    } else if (toolCallOrder === "superset" && supersetCursorByExpected) {
      scanStart = Math.min(
        supersetCursorByExpected[ei] ?? 0,
        normalizedActual.length
      );
    }
    for (let ai = scanStart; ai < scanEnd; ai++) {
      if (matchedActualIndices.has(ai)) continue;
      const act = normalizedActual[ai];
      if (act.toolName !== exp.toolName) continue;
      const actualArgs = act.arguments || {};
      matchedExpectedIndices.add(ei);
      matchedActualIndices.add(ai);
      expectedToActual.set(ei, ai);
      if (argumentMatching !== "ignore") {
        argumentMismatches.push({
          toolName: exp.toolName,
          expectedArgs,
          actualArgs
        });
      }
      break;
    }
  }
  const outOfOrder = [];
  const missing = normalizedExpected.filter(
    (_, idx) => !matchedExpectedIndices.has(idx)
  );
  const extra = normalizedActual.filter(
    (_, idx) => !matchedActualIndices.has(idx)
  );
  const extraFails = maxExtraToolCalls !== null && extra.length > maxExtraToolCalls;
  const passed = missing.length === 0 && argumentMismatches.length === 0 && outOfOrder.length === 0 && !extraFails;
  return {
    missing,
    extra,
    outOfOrder,
    argumentMismatches,
    passed
  };
}

// src/predicates/argMatcher.ts
var PROBE = "__predicate_arg_probe__";
function argMatch(matcher, actualArgs) {
  const argumentMatching = matcher.argumentMatching ?? "partial";
  const result = evaluateToolCalls(
    [{ toolName: PROBE, arguments: matcher.args ?? {} }],
    [{ toolName: PROBE, arguments: actualArgs ?? {} }],
    { argumentMatching }
  );
  return result.passed;
}

// src/predicates/evaluate.ts
var MAX_VALUE_CHARS = 200;
var MAX_ERROR_MSG_CHARS = 200;
var MAX_ITEMS_SHOWN = 3;
var MAX_REASON_CHARS = 600;
var REDACTED = "\xABredacted\xBB";
var MAX_REGEX_INPUT_CHARS = 1e5;
var NESTED_QUANTIFIER = /[+*}]\??\)[+*{]/;
var SENSITIVE_KEY = /(authorization|bearer|password|passwd|secret|token|api[_-]?key|access[_-]?key|client[_-]?secret|cookie|credential|private[_-]?key)/i;
function redact(value, depth = 0) {
  if (depth > 6) return "\xABdepth-limit\xBB";
  if (Array.isArray(value)) {
    return value.slice(0, 20).map((v) => redact(v, depth + 1));
  }
  if (value && typeof value === "object") {
    const out = {};
    for (const [k, v] of Object.entries(value)) {
      out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1);
    }
    return out;
  }
  return value;
}
function truncate(s, max) {
  return s.length <= max ? s : `${s.slice(0, max)}\u2026(+${s.length - max} chars)`;
}
function brief(value) {
  let json;
  try {
    json = JSON.stringify(redact(value ?? {}));
  } catch {
    json = String(value);
  }
  return truncate(json ?? "null", MAX_VALUE_CHARS);
}
function callsTo(transcript, toolName) {
  return (transcript.toolCalls ?? []).filter((c) => c.toolName === toolName);
}
function resolveFinalMessage(transcript) {
  return typeof transcript.finalAssistantMessage === "string" ? transcript.finalAssistantMessage : "";
}
function resolveTotalTokens(transcript) {
  const usage = transcript.usage;
  if (!usage) return void 0;
  const { inputTokens, outputTokens, totalTokens } = usage;
  const sum = typeof inputTokens === "number" || typeof outputTokens === "number" ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0;
  const total = typeof totalTokens === "number" ? totalTokens : void 0;
  if (total === void 0 && sum === void 0) return void 0;
  return Math.max(total ?? 0, sum ?? 0);
}
function sanitizePredicate(predicate) {
  if (predicate.type === "toolCalledWith") {
    return {
      ...predicate,
      args: {
        ...predicate.args,
        args: redact(predicate.args.args ?? {})
      }
    };
  }
  return predicate;
}
function pass(predicate, reason) {
  return {
    predicate: sanitizePredicate(predicate),
    passed: true,
    reason: truncate(reason, MAX_REASON_CHARS)
  };
}
function fail(predicate, reason) {
  return {
    predicate: sanitizePredicate(predicate),
    passed: false,
    reason: truncate(reason, MAX_REASON_CHARS)
  };
}
function evaluatePredicate(transcript, predicate) {
  switch (predicate.type) {
    case "toolCalledWith": {
      const minCount = predicate.minCount ?? 1;
      if (!Number.isInteger(minCount) || minCount < 1) {
        return fail(
          predicate,
          `invalid minCount ${String(predicate.minCount)}; expected a positive integer \u2265 1`
        );
      }
      const calls = callsTo(transcript, predicate.toolName);
      const matching = calls.filter(
        (c) => argMatch(predicate.args, c.arguments ?? {})
      );
      const mode = predicate.args.argumentMatching ?? "partial";
      if (matching.length >= minCount) {
        return pass(
          predicate,
          `tool "${predicate.toolName}" called with matching args (${matching.length}/${minCount} required; ${mode} match)`
        );
      }
      if (calls.length === 0) {
        return fail(
          predicate,
          `expected tool "${predicate.toolName}" called \u2265${minCount}\xD7 with ${brief(predicate.args.args)} (${mode} match), but it was never called`
        );
      }
      const shown = calls.slice(0, MAX_ITEMS_SHOWN);
      const actualArgs = shown.map((c) => brief(c.arguments ?? {}));
      const more = calls.length > MAX_ITEMS_SHOWN ? `, +${calls.length - MAX_ITEMS_SHOWN} more` : "";
      return fail(
        predicate,
        `expected tool "${predicate.toolName}" called \u2265${minCount}\xD7 with ${brief(predicate.args.args)} (${mode} match); got ${calls.length} call(s) with args [${actualArgs.join(", ")}${more}], ${matching.length} matching`
      );
    }
    case "toolCalledAtLeastOnce": {
      const calls = callsTo(transcript, predicate.toolName);
      return calls.length > 0 ? pass(predicate, `tool "${predicate.toolName}" called ${calls.length}\xD7`) : fail(predicate, `tool "${predicate.toolName}" was never called`);
    }
    case "firstToolWas": {
      if (typeof predicate.toolName !== "string" || predicate.toolName.length === 0) {
        return fail(predicate, `firstToolWas requires a non-empty toolName`);
      }
      const first = (transcript.toolCalls ?? [])[0];
      if (!first) {
        return fail(
          predicate,
          `expected first tool "${predicate.toolName}" but no tools were called`
        );
      }
      return first.toolName === predicate.toolName ? pass(
        predicate,
        `first tool call was "${predicate.toolName}"`
      ) : fail(
        predicate,
        `expected first tool "${predicate.toolName}", got "${first.toolName}"`
      );
    }
    case "toolNeverCalled": {
      if (typeof predicate.toolName !== "string" || predicate.toolName.length === 0) {
        return fail(predicate, `toolNeverCalled requires a non-empty toolName`);
      }
      const calls = callsTo(transcript, predicate.toolName);
      return calls.length === 0 ? pass(predicate, `tool "${predicate.toolName}" was not called`) : fail(
        predicate,
        `forbidden tool "${predicate.toolName}" was called ${calls.length}\xD7`
      );
    }
    case "responseContains": {
      if (typeof predicate.needle !== "string" || predicate.needle.length === 0) {
        return fail(predicate, `responseContains requires a non-empty needle`);
      }
      const message = resolveFinalMessage(transcript);
      const caseSensitive = predicate.caseSensitive ?? false;
      const haystack = caseSensitive ? message : message.toLowerCase();
      const needle = caseSensitive ? predicate.needle : predicate.needle.toLowerCase();
      return haystack.includes(needle) ? pass(
        predicate,
        `final assistant message contains "${predicate.needle}"` + (caseSensitive ? " (case-sensitive)" : "")
      ) : fail(
        predicate,
        `final assistant message does not contain "${predicate.needle}"` + (caseSensitive ? " (case-sensitive)" : "")
      );
    }
    case "responseMatches": {
      const message = resolveFinalMessage(transcript);
      if (typeof predicate.pattern !== "string" || predicate.pattern.length === 0) {
        return fail(
          predicate,
          `responseMatches requires a non-empty string pattern`
        );
      }
      if (NESTED_QUANTIFIER.test(predicate.pattern)) {
        return fail(
          predicate,
          `regex pattern /${predicate.pattern}/ contains a nested quantifier; refusing to evaluate to avoid catastrophic backtracking`
        );
      }
      let regex;
      try {
        regex = new RegExp(predicate.pattern);
      } catch (error) {
        return fail(
          predicate,
          `invalid regex pattern /${predicate.pattern}/: ${error instanceof Error ? error.message : String(error)}`
        );
      }
      if (message.length > MAX_REGEX_INPUT_CHARS) {
        return fail(
          predicate,
          `final assistant message exceeds ${MAX_REGEX_INPUT_CHARS} chars; refusing to evaluate /${predicate.pattern}/ safely`
        );
      }
      return regex.test(message) ? pass(predicate, `final assistant message matches /${predicate.pattern}/`) : fail(
        predicate,
        `final assistant message does not match /${predicate.pattern}/`
      );
    }
    case "noToolErrors": {
      const errors = transcript.toolErrors ?? [];
      if (errors.length === 0) {
        return pass(predicate, "no tool errors");
      }
      const detail = errors.slice(0, MAX_ITEMS_SHOWN).map((e) => {
        const name = e.toolName ? `"${e.toolName}"` : "tool";
        const msg = e.message ? `: ${truncate(e.message, MAX_ERROR_MSG_CHARS)}` : "";
        return `${name} (${e.kind}${msg})`;
      }).join(", ");
      const moreErrors = errors.length > MAX_ITEMS_SHOWN ? ` (+${errors.length - MAX_ITEMS_SHOWN} more)` : "";
      return fail(
        predicate,
        `${errors.length} tool error(s): ${detail}${moreErrors}`
      );
    }
    case "finalAssistantMessageNonEmpty": {
      const message = resolveFinalMessage(transcript);
      return message.trim().length > 0 ? pass(predicate, "final assistant message is non-empty") : fail(predicate, "final assistant message is empty");
    }
    case "tokenBudgetUnder": {
      const total = resolveTotalTokens(transcript);
      if (total === void 0) {
        return fail(
          predicate,
          `token usage unavailable; cannot verify budget < ${predicate.tokens}`
        );
      }
      return total < predicate.tokens ? pass(predicate, `token usage ${total} < ${predicate.tokens}`) : fail(
        predicate,
        `token usage ${total} is not under budget ${predicate.tokens}`
      );
    }
    default: {
      const exhaustive = predicate;
      return {
        predicate: exhaustive,
        passed: false,
        reason: `unknown predicate type`
      };
    }
  }
}
function evaluatePredicates(transcript, predicates) {
  return (predicates ?? []).map((p) => {
    try {
      return evaluatePredicate(transcript, p);
    } catch (error) {
      return {
        predicate: p,
        passed: false,
        reason: `malformed predicate: ${error instanceof Error ? error.message : String(error)}`
      };
    }
  });
}
function allPredicatesPassed(results) {
  return results.every((r) => r.passed);
}

// src/eval-tool-execution.ts
function isRecord(value) {
  return !!value && typeof value === "object" && !Array.isArray(value);
}
function unwrapToolOutput(output) {
  if (!isRecord(output)) return output;
  if (!("type" in output) || !("value" in output)) return output;
  return output.value;
}
function classifyToolFailurePart(part) {
  if (!isRecord(part) || typeof part.type !== "string") return null;
  if (part.type !== "tool-result") return null;
  const toolName = typeof part.toolName === "string" ? part.toolName : void 0;
  const record = (kind) => toolName ? { kind, toolName } : { kind };
  if (typeof part.error === "string" && part.error.trim())
    return record("protocol-error");
  if (isRecord(part.error) && typeof part.error.message === "string" && part.error.message.trim()) {
    return record("protocol-error");
  }
  const output = part.output;
  if (isRecord(output) && output.type === "error-text")
    return record("protocol-error");
  if (isRecord(part.result) && part.result.isError === true)
    return record("content-error");
  const unwrapped = unwrapToolOutput(output);
  if (isRecord(unwrapped) && unwrapped.isError === true)
    return record("content-error");
  if (part.isError === true) return record("content-error");
  return null;
}
function messageToolErrorRecords(messages) {
  if (!Array.isArray(messages)) return [];
  const records = [];
  for (const msg of messages) {
    if (!msg || typeof msg.role !== "string") continue;
    if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
    for (const part of msg.content) {
      const classified = classifyToolFailurePart(part);
      if (classified) {
        records.push(
          classified.toolName ? { kind: classified.kind, toolName: classified.toolName } : { kind: classified.kind }
        );
      }
    }
  }
  return records;
}
function spanToolErrorRecords(spans) {
  if (!Array.isArray(spans)) return [];
  const records = [];
  for (const span of spans) {
    if (span.category !== "tool" || span.status !== "error") continue;
    const name = span.name;
    records.push(
      typeof name === "string" ? { kind: "protocol-error", toolName: name } : { kind: "protocol-error" }
    );
  }
  return records;
}
function extractToolErrors(trace) {
  if (trace == null || typeof trace === "string") return [];
  if (Array.isArray(trace)) return messageToolErrorRecords(trace);
  if (!isRecord(trace)) return [];
  const messages = trace.messages;
  const spans = trace.spans;
  return [...spanToolErrorRecords(spans), ...messageToolErrorRecords(messages)];
}

// src/predicates/transcript.ts
function isRecord2(value) {
  return !!value && typeof value === "object" && !Array.isArray(value);
}
function extractFinalAssistantMessage(messages) {
  if (!Array.isArray(messages)) return void 0;
  for (let i = messages.length - 1; i >= 0; i--) {
    const msg = messages[i];
    if (!isRecord2(msg) || msg.role !== "assistant") continue;
    const content = msg.content;
    if (typeof content === "string") {
      return content.trim() ? content : void 0;
    }
    if (Array.isArray(content)) {
      const text = content.map(
        (part) => isRecord2(part) && part.type === "text" ? String(part.text ?? "") : ""
      ).join("");
      return text.trim() ? text : void 0;
    }
    return void 0;
  }
  return void 0;
}
function messagesOf(trace) {
  if (trace == null || typeof trace === "string") return void 0;
  if (Array.isArray(trace)) return trace;
  if (isRecord2(trace)) return trace.messages;
  return void 0;
}
function buildIterationTranscript(input) {
  const finalAssistantMessage = input.finalAssistantMessage ?? extractFinalAssistantMessage(messagesOf(input.trace));
  return {
    toolCalls: input.toolCalls,
    toolErrors: extractToolErrors(input.trace),
    ...finalAssistantMessage !== void 0 ? { finalAssistantMessage } : {},
    ...input.usage ? { usage: input.usage } : {}
  };
}
var PREDICATE_PLACEHOLDER_STRINGS = [
  "any",
  "string",
  "number",
  "boolean",
  "object",
  "array",
  "null"
];
var argMatcherSchema = z.object({
  args: z.record(z.string(), z.unknown()),
  argumentMatching: z.enum(["exact", "partial", "ignore"]).optional()
});
var predicateSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("toolCalledWith"),
    toolName: z.string().min(1),
    args: argMatcherSchema,
    minCount: z.number().int().positive().optional()
  }),
  z.object({
    type: z.literal("toolCalledAtLeastOnce"),
    toolName: z.string().min(1)
  }),
  z.object({
    type: z.literal("toolNeverCalled"),
    toolName: z.string().min(1)
  }),
  z.object({
    type: z.literal("firstToolWas"),
    toolName: z.string().min(1)
  }),
  z.object({
    type: z.literal("responseContains"),
    needle: z.string().min(1),
    caseSensitive: z.boolean().optional()
  }),
  z.object({
    type: z.literal("responseMatches"),
    pattern: z.string().min(1).refine(
      (p) => {
        try {
          new RegExp(p);
          return true;
        } catch {
          return false;
        }
      },
      { message: "Invalid regular expression" }
    )
  }),
  z.object({
    type: z.literal("noToolErrors")
  }),
  z.object({
    type: z.literal("finalAssistantMessageNonEmpty")
  }),
  z.object({
    type: z.literal("tokenBudgetUnder"),
    tokens: z.number().int().positive()
  })
]);
var predicateArraySchema = z.array(predicateSchema);
var casePredicatesSchema = z.object({
  mode: z.enum(["inherit", "replace", "extend"]),
  list: z.array(predicateSchema)
});

export { PREDICATE_PLACEHOLDER_STRINGS, allPredicatesPassed, argMatch, argMatcherSchema, buildIterationTranscript, casePredicatesSchema, evaluatePredicate, evaluatePredicates, extractFinalAssistantMessage, extractToolErrors, predicateArraySchema, predicateSchema };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map