zimakri / tree / src/offer.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// A LAX offer for the agent and a local check of the registry's rules (LAX registry 0.10.0, validateOutsideOffer).
import { FIVE_LAWS } from './laws.js';

export function buildOffer(cfg) {
  return {
    lax_version: '1.0',
    name: cfg.name,
    description: cfg.description,
    category: cfg.category,
    endpoint: cfg.endpoint,
    agent_did: cfg.did,
    capabilities: cfg.capabilities.map((c) => ({
      skill_id: c.skill_id,
      description: c.description,
      pricing: { model: c.model ?? 'per_call', amount: c.amount, currency: 'USDC', rail: 'x402' },
    })),
    constitutional_constraints: [...FIVE_LAWS],
    payment_rails: ['x402', 'usdc-base'],
  };
}

const HTML_RE = /[<>]|javascript:|data:text\/html/i;
const CTRL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/;
const DID_RE = /^did:[a-z0-9]{2,20}:[A-Za-z0-9._:%-]{1,160}$/;
const AMOUNT_RE = /^\d{1,7}(\.\d{1,6})?$/;
const MODELS = new Set(['per_call', 'per-call', 'per_minute', 'flat', 'free']);
const SCAM = /\b(seed|recovery|secret|backup)\s*(phrase|words?)\b|\bmnemonic\b|\bprivate\s*keys?\b|\bair\s*drops?\b|\bgive\s*aways?\b|\bguaranteed\s+(returns?|profits?)\b/i;

/** Returns a list of plain problems; empty means the registry rules pass. */
export function checkOffer(o) {
  const errs = [];
  const str = (v, f, min, max, multi = false) => {
    if (typeof v !== 'string') return errs.push(`${f} must be a string`), '';
    const s = v.trim();
    if (s.length < min) errs.push(`${f} must be at least ${min} characters`);
    if (s.length > max) errs.push(`${f} must be at most ${max} characters`);
    if (HTML_RE.test(s)) errs.push(`${f} must not contain HTML or scripts`);
    if (CTRL_RE.test(s) || (!multi && /[\r\n\t]/.test(s))) errs.push(`${f} contains control characters`);
    return s;
  };
  const name = str(o.name, 'name', 3, 80);
  if (/\b(kognai|achiri|godman)\b/i.test(name)) errs.push('name must not claim Kognai, Achiri or Godman');
  const desc = str(o.description, 'description', 20, 600, true);
  if (o.category != null) str(o.category, 'category', 2, 40);
  if (o.agent_did != null && !DID_RE.test(o.agent_did)) errs.push('agent_did must look like did:method:identifier');
  try {
    const u = new URL(o.endpoint);
    if (u.protocol !== 'https:') errs.push('endpoint must be an https URL');
    const h = u.hostname;
    if (!h.includes('.') || /^[\d.]+$/.test(h) || h === 'kognai.ai' || h.endsWith('.kognai.ai')) errs.push('endpoint must be a public domain of your own');
  } catch { errs.push('endpoint must be a valid URL'); }
  if (!Array.isArray(o.capabilities) || o.capabilities.length < 1 || o.capabilities.length > 10) errs.push('capabilities must hold 1 to 10 items');
  else o.capabilities.forEach((c, i) => {
    const id = str(c.skill_id, `capabilities[${i}].skill_id`, 2, 64);
    if (!/^[a-z0-9][a-z0-9_.-]*$/.test(id.toLowerCase())) errs.push(`capabilities[${i}].skill_id may use letters, digits, dot, underscore and hyphen`);
    str(c.description, `capabilities[${i}].description`, 3, 200);
    const p = c.pricing ?? {};
    if (!MODELS.has(p.model)) errs.push(`capabilities[${i}].pricing.model must be per_call, per_minute, flat or free`);
    if (p.currency !== 'USDC') errs.push(`capabilities[${i}].pricing.currency must be USDC`);
    if (p.rail !== 'x402') errs.push(`capabilities[${i}].pricing.rail must be x402`);
    if (typeof p.amount !== 'string' || !AMOUNT_RE.test(p.amount)) errs.push(`capabilities[${i}].pricing.amount must be a decimal string such as "0.05"`);
    else if (p.model !== 'free' && Number(p.amount) <= 0) errs.push(`capabilities[${i}].pricing.amount must be above 0 unless the model is free`);
  });
  const laws = new Set((o.constitutional_constraints ?? []).map((l) => String(l).toLowerCase()));
  const missing = FIVE_LAWS.filter((l) => !laws.has(l.toLowerCase()));
  if (missing.length) errs.push(`constitutional_constraints must accept the Five Laws; missing: ${missing.join(', ')}`);
  if (SCAM.test(`${name}\n${desc}`)) errs.push('the text trips the registry scam screen');
  if (/0x[0-9a-fA-F]{40}/.test(`${name}\n${desc}`)) errs.push('the text must not contain a wallet or token address');
  return errs;
}

agentgate-source: code mirror on Cloudflare Pages. All repos