Ctrl K
ComunidadGuías…

scripts/live-test-capture-provider.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Generate and validate a disposable OpenAI-compatible capture provider.
 *
 * The generated Worker stores assertion definitions and pass/fail results in a
 * disposable D1 database. It never stores request bodies, credentials, or raw
 * prompts. Operators provide CAPTURE_API_KEY and CAPTURE_ADMIN_KEY as Wrangler
 * secrets after generation; neither secret is accepted on this CLI's argv.
 */
import { mkdirSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

export const CAPTURE_PROVIDER_SCHEMA = 'chickpea-live-capture-provider/v1';
export const CAPTURE_MAX_BODY_BYTES = 262_144;
export const CAPTURE_MAX_ASSERTIONS = 64;

export function validateCaptureConfiguration(input) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
    throw new Error('Capture configuration must be an object.');
  }
  const runMarker = exactToken(input.runMarker, 'run marker', 128);
  const responseText = boundedText(input.responseText, 'response text', 2_000);
  if (!Array.isArray(input.assertions) || input.assertions.length < 1 || input.assertions.length > CAPTURE_MAX_ASSERTIONS) {
    throw new Error(`Capture configuration needs 1-${CAPTURE_MAX_ASSERTIONS} assertions.`);
  }
  const seen = new Set();
  const assertions = input.assertions.map((item) => {
    if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('Capture assertion must be an object.');
    const id = exactToken(item.id, 'assertion ID', 100);
    if (seen.has(id)) throw new Error(`Duplicate assertion ID: ${id}`);
    seen.add(id);
    const model = exactToken(item.model, 'model ID', 200);
    if (!model.startsWith(`${runMarker}:`)) {
      throw new Error(`Assertion ${id} model must carry the exact run marker.`);
    }
    const needle = boundedText(item.needle, 'assertion needle', 8_000);
    if (item.expect !== 'present' && item.expect !== 'absent') {
      throw new Error(`Assertion ${id} expect must be present or absent.`);
    }
    return { id, model, needle, expect: item.expect };
  });
  return { schema: CAPTURE_PROVIDER_SCHEMA, runMarker, responseText, assertions };
}

export function evaluateCaptureRequest(bodyText, configuration, requestedModel) {
  const config = validateCaptureConfiguration(configuration);
  if (typeof bodyText !== 'string' || Buffer.byteLength(bodyText, 'utf8') > CAPTURE_MAX_BODY_BYTES) {
    throw new Error(`Capture request body exceeds ${CAPTURE_MAX_BODY_BYTES} bytes.`);
  }
  const model = exactToken(requestedModel, 'requested model', 200);
  return config.assertions
    .filter((assertion) => assertion.model === model)
    .map((assertion) => {
      const found = bodyText.includes(assertion.needle);
      return {
        id: assertion.id,
        model,
        expect: assertion.expect,
        passed: assertion.expect === 'present' ? found : !found,
      };
    });
}

export function openAiCaptureStream(responseText, model = 'capture-model') {
  const text = boundedText(responseText, 'response text', 2_000);
  const modelId = exactToken(model, 'model ID', 200);
  const base = { id: 'chatcmpl-chickpea-capture', object: 'chat.completion.chunk', created: 0, model: modelId };
  const chunks = [
    { ...base, choices: [{ index: 0, delta: { role: 'assistant', content: text }, finish_reason: null }] },
    { ...base, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] },
    { ...base, choices: [], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } },
  ];
  return `${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join('')}data: [DONE]\n\n`;
}

export function renderCaptureProviderWorker() {
  return `// Generated by scripts/live-test-capture-provider.mjs. Disposable acceptance resource only.
const SCHEMA = ${JSON.stringify(CAPTURE_PROVIDER_SCHEMA)};
const MAX_BODY_BYTES = ${CAPTURE_MAX_BODY_BYTES};
const MAX_ASSERTIONS = ${CAPTURE_MAX_ASSERTIONS};

export default {
  async fetch(request, env) {
    await initialize(env.CAPTURE_DB);
    const url = new URL(request.url);
    if (url.pathname === '/__capture/configure' && request.method === 'POST') {
      if (!authorized(request, env.CAPTURE_ADMIN_KEY)) return json({ ok: false }, 401);
      const input = await boundedJson(request);
      const config = validateConfiguration(input);
      await env.CAPTURE_DB.batch([
        env.CAPTURE_DB.prepare('DELETE FROM capture_assertions'),
        env.CAPTURE_DB.prepare('DELETE FROM capture_results'),
        env.CAPTURE_DB.prepare('DELETE FROM capture_meta'),
        env.CAPTURE_DB.prepare('INSERT INTO capture_meta (key, value) VALUES (?, ?)').bind('run_marker', config.runMarker),
        env.CAPTURE_DB.prepare('INSERT INTO capture_meta (key, value) VALUES (?, ?)').bind('response_text', config.responseText),
        ...config.assertions.map((item) => env.CAPTURE_DB.prepare(
          'INSERT INTO capture_assertions (id, model, needle, expectation) VALUES (?, ?, ?, ?)'
        ).bind(item.id, item.model, item.needle, item.expect)),
      ]);
      return json({ ok: true, schema: SCHEMA, assertionCount: config.assertions.length });
    }
    if (url.pathname === '/__capture/results' && request.method === 'GET') {
      if (!authorized(request, env.CAPTURE_ADMIN_KEY)) return json({ ok: false }, 401);
      const meta = await env.CAPTURE_DB.prepare('SELECT key, value FROM capture_meta').all();
      const results = await env.CAPTURE_DB.prepare(
        'SELECT assertion_id AS id, model, expectation AS expect, passed FROM capture_results ORDER BY request_id, assertion_id'
      ).all();
      const map = Object.fromEntries((meta.results || []).map((row) => [row.key, row.value]));
      return json({
        ok: true,
        schema: SCHEMA,
        runMarker: map.run_marker || null,
        results: (results.results || []).map((row) => ({ ...row, passed: row.passed === 1 })),
      });
    }
    if (url.pathname === '/v1/chat/completions' && request.method === 'POST') {
      if (!authorized(request, env.CAPTURE_API_KEY)) return json({ error: { type: 'invalid_request_error' } }, 401);
      const bodyText = await boundedText(request);
      let body;
      try { body = JSON.parse(bodyText); } catch { return json({ error: { type: 'invalid_request_error' } }, 400); }
      const model = exactToken(body && body.model, 'requested model', 200);
      const rows = await env.CAPTURE_DB.prepare(
        'SELECT id, needle, expectation FROM capture_assertions WHERE model = ? ORDER BY id'
      ).bind(model).all();
      if (!rows.results || rows.results.length === 0) return json({ error: { type: 'unconfigured_model' } }, 409);
      const requestId = crypto.randomUUID();
      await env.CAPTURE_DB.batch(rows.results.map((row) => {
        const found = bodyText.includes(row.needle);
        const passed = row.expectation === 'present' ? found : !found;
        return env.CAPTURE_DB.prepare(
          'INSERT INTO capture_results (request_id, assertion_id, model, expectation, passed, created_at) VALUES (?, ?, ?, ?, ?, ?)'
        ).bind(requestId, row.id, model, row.expectation, passed ? 1 : 0, Date.now());
      }));
      const response = await env.CAPTURE_DB.prepare('SELECT value FROM capture_meta WHERE key = ?').bind('response_text').first();
      return new Response(openAiStream(response && response.value || 'Capture accepted.', model), {
        headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-store' },
      });
    }
    return json({ ok: false }, 404);
  },
};

async function initialize(db) {
  await db.exec(\`
    CREATE TABLE IF NOT EXISTS capture_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
    CREATE TABLE IF NOT EXISTS capture_assertions (
      id TEXT PRIMARY KEY, model TEXT NOT NULL, needle TEXT NOT NULL, expectation TEXT NOT NULL
    );
    CREATE TABLE IF NOT EXISTS capture_results (
      request_id TEXT NOT NULL, assertion_id TEXT NOT NULL, model TEXT NOT NULL,
      expectation TEXT NOT NULL, passed INTEGER NOT NULL, created_at INTEGER NOT NULL,
      PRIMARY KEY (request_id, assertion_id)
    );
  \`);
}

function authorized(request, expected) {
  return typeof expected === 'string' && expected.length >= 24 && request.headers.get('authorization') === 'Bearer ' + expected;
}

async function boundedJson(request) {
  const text = await boundedText(request);
  try { return JSON.parse(text); } catch { throw new Error('Invalid JSON.'); }
}

async function boundedText(request) {
  const declared = Number(request.headers.get('content-length') || 0);
  if (declared > MAX_BODY_BYTES) throw new Error('Request too large.');
  const text = await request.text();
  if (new TextEncoder().encode(text).byteLength > MAX_BODY_BYTES) throw new Error('Request too large.');
  return text;
}

function validateConfiguration(input) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Invalid configuration.');
  const runMarker = exactToken(input.runMarker, 'run marker', 128);
  const responseText = boundedValue(input.responseText, 'response text', 2000);
  if (!Array.isArray(input.assertions) || input.assertions.length < 1 || input.assertions.length > MAX_ASSERTIONS) {
    throw new Error('Invalid assertions.');
  }
  const seen = new Set();
  const assertions = input.assertions.map((item) => {
    const id = exactToken(item && item.id, 'assertion ID', 100);
    if (seen.has(id)) throw new Error('Duplicate assertion.');
    seen.add(id);
    const model = exactToken(item.model, 'model ID', 200);
    if (!model.startsWith(runMarker + ':')) throw new Error('Model is outside this run.');
    const needle = boundedValue(item.needle, 'needle', 8000);
    if (item.expect !== 'present' && item.expect !== 'absent') throw new Error('Invalid expectation.');
    return { id, model, needle, expect: item.expect };
  });
  return { runMarker, responseText, assertions };
}

function exactToken(value, label, maximum) {
  if (typeof value !== 'string' || value.length < 1 || value.length > maximum || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)) {
    throw new Error('Invalid ' + label + '.');
  }
  return value;
}

function boundedValue(value, label, maximum) {
  if (typeof value !== 'string' || value.length < 1 || value.length > maximum || /[\\u0000]/.test(value)) {
    throw new Error('Invalid ' + label + '.');
  }
  return value;
}

function openAiStream(text, model) {
  const base = { id: 'chatcmpl-chickpea-capture', object: 'chat.completion.chunk', created: 0, model };
  const chunks = [
    { ...base, choices: [{ index: 0, delta: { role: 'assistant', content: text }, finish_reason: null }] },
    { ...base, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] },
    { ...base, choices: [], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } },
  ];
  return chunks.map((chunk) => 'data: ' + JSON.stringify(chunk) + '\\n\\n').join('') + 'data: [DONE]\\n\\n';
}

function json(value, status = 200) {
  return Response.json(value, { status, headers: { 'cache-control': 'no-store' } });
}
`;
}

export function renderCaptureWranglerConfig({ workerName, databaseName, databaseId }) {
  const name = exactToken(workerName, 'Worker name', 63);
  const dbName = exactToken(databaseName, 'D1 name', 63);
  const dbId = exactToken(databaseId, 'D1 ID', 64);
  return `${JSON.stringify({
    name,
    main: './capture-worker.mjs',
    compatibility_date: '2026-08-12',
    workers_dev: true,
    d1_databases: [{ binding: 'CAPTURE_DB', database_name: dbName, database_id: dbId }],
  }, null, 2)}\n`;
}

export function writeCaptureProviderBundle(directoryInput, options) {
  const directory = resolve(directoryInput);
  mkdirSync(directory, { recursive: true, mode: 0o700 });
  writeFileSync(resolve(directory, 'capture-worker.mjs'), renderCaptureProviderWorker(), { encoding: 'utf8', mode: 0o600 });
  writeFileSync(resolve(directory, 'wrangler.json'), renderCaptureWranglerConfig(options), { encoding: 'utf8', mode: 0o600 });
  return { directory, workerSource: resolve(directory, 'capture-worker.mjs'), wranglerConfig: resolve(directory, 'wrangler.json') };
}

function exactToken(value, label, maximum) {
  if (typeof value !== 'string' || value.length < 1 || value.length > maximum || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)) {
    throw new Error(`Invalid ${label}.`);
  }
  return value;
}

function boundedText(value, label, maximum) {
  if (typeof value !== 'string' || value.length < 1 || value.length > maximum || value.includes('\0')) {
    throw new Error(`Invalid ${label}.`);
  }
  return value;
}

function arg(args, flag) {
  const index = args.indexOf(flag);
  return index >= 0 ? args[index + 1] : undefined;
}

async function cli() {
  const args = process.argv.slice(2);
  if (args[0] !== 'prepare') {
    throw new Error('Usage: live-test-capture-provider.mjs prepare --directory <path> --worker-name <name> --database-name <name> --database-id <id>');
  }
  const directory = arg(args, '--directory');
  const workerName = arg(args, '--worker-name');
  const databaseName = arg(args, '--database-name');
  const databaseId = arg(args, '--database-id');
  if (!directory || !workerName || !databaseName || !databaseId) throw new Error('All prepare arguments are required.');
  console.log(JSON.stringify(writeCaptureProviderBundle(directory, { workerName, databaseName, databaseId })));
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  cli().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
}