← Files PromptfooARCHIVED FILE
skills/promptfoo-provider-setup/scripts/response-contract.mjs
2.73 KB · Oct 3, 2026 · 06:37 UTC
const TYPE_CHECKS = {
string: "typeof value === 'string'",
object: "value !== null && typeof value === 'object' && !Array.isArray(value)",
array: 'Array.isArray(value)',
number: "typeof value === 'number' && Number.isFinite(value)",
integer: 'Number.isInteger(value)',
boolean: "typeof value === 'boolean'",
null: 'value === null',
};
// Check declared JSON types, not the full schema (required keys, enums, formats, etc.).
function typeCheck(schema, resolveRef, seen = new Set()) {
if (schema === false) {
return 'false';
}
if (!schema || typeof schema !== 'object') {
return 'true';
}
const resolved = resolveRef(schema);
if (seen.has(resolved)) {
return 'true';
}
const active = new Set(seen).add(resolved);
const checks = [];
const types = Array.isArray(resolved.type) ? resolved.type : [resolved.type];
if (types.length > 0 && types.every((type) => Object.hasOwn(TYPE_CHECKS, type))) {
checks.push(types.map((type) => `(${TYPE_CHECKS[type]})`).join(' || '));
}
for (const key of ['allOf', 'oneOf', 'anyOf']) {
if (Array.isArray(resolved[key]) && resolved[key].length > 0) {
// For unions, accepting any declared type does not assert full oneOf validity.
checks.push(
resolved[key]
.map((branch) => `(${typeCheck(branch, resolveRef, active)})`)
.join(key === 'allOf' ? ' && ' : ' || '),
);
}
}
const check = checks.length ? checks.map((part) => `(${part})`).join(' && ') : 'true';
return resolved.nullable === true ? `value === null || (${check})` : check;
}
export function responseTransform({ field, array, schema, resolveRef }) {
const base = array ? 'json?.[0]' : 'json';
const selector = field
? /^[A-Za-z_$][0-9A-Za-z_$]*$/.test(field)
? `${base}?.${field}`
: `${base}?.[${JSON.stringify(field)}]`
: 'json';
const missingField = field ? `!Object.hasOwn(${base} ?? {}, ${JSON.stringify(field)}) || ` : '';
const expected = JSON.stringify(
`Expected response at ${selector} to match its declared JSON type`,
);
// Parse root scalars from text: the HTTP transform API coerces falsy data to null.
return `(json, text) => {
const value = ${selector === 'json' ? 'JSON.parse(text)' : selector};
if (${missingField}value === undefined || !(${typeCheck(schema, resolveRef)})) {
throw new Error(${expected});
}
return typeof value === 'string' ? value : JSON.stringify(value);
}`;
}
export function smokeAssertions(isJson, expectedText) {
if (expectedText !== undefined) {
return [{ type: 'contains', value: expectedText }];
}
return [
{
type: 'javascript',
value: isJson
? 'output !== null && output !== undefined'
: "typeof output === 'string' && output.length > 0",
},
];
}
SHA-256: 999d97a338e41e74784dbb0a7a57884ee0899219f2877c6c9b25c5e3dc222267