UNPKG

@extenshi/mcp

Version:

Extenshi MCP server — bring the extension catalog (search, security, market research) and pre-publish scanning into your AI tools

69 lines • 3.36 kB
/** * Agent-readable outcome for `update_privacy_policy_with_ai`. * * The backend always returns a proposal: an AI draft when the model ran and * its draft passed the category guard, otherwise a deterministic section * merge. This puts the difference first in the tool result (`aiStep` + * `notice`) so an agent cannot present a merge as an AI draft. * * Skew-safe in both directions: a backend that returns `ai` (2026-09+) is * described from it; an older backend that only returns `usedLlm` / * `aiDegraded` / `keptHeadings` is described from those, and its * `kind: 'ai_updated'` on a non-AI proposal is corrected to `edited`. */ const AI_STATUSES_NOT_RUN = new Set(['not_configured']); const AI_STATUSES_DISCARDED = new Set(['request_failed', 'incomplete_reply', 'rejected']); function asRecord(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; } function strings(value) { return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : []; } export function describePrivacyAiUpdate(result) { const r = asRecord(result); const ai = asRecord(r.ai); const status = typeof ai.status === 'string' ? ai.status : undefined; const usedLlm = ai.applied === true || (status === undefined && r.usedLlm === true); const kept = strings(r.keptHeadings); const conflicts = strings(r.conflictHeadings); const missing = strings(r.missingCategories); let aiStep; if (usedLlm) aiStep = 'ran'; else if (status === 'not_needed') aiStep = 'not_needed'; else if (status && AI_STATUSES_NOT_RUN.has(status)) aiStep = 'did_not_run'; else if (status && AI_STATUSES_DISCARDED.has(status)) aiStep = 'discarded'; // Older backend: no `ai` field. It skipped the model silently when no key // was configured, so a customized policy without usedLlm did not get an AI pass. else if (r.aiDegraded === true || kept.length > 0) aiStep = 'did_not_run'; else aiStep = 'not_needed'; const reason = typeof ai.reason === 'string' && ai.reason ? ai.reason : undefined; let reviewLine = ''; if (conflicts.length) reviewLine += ` Sections that need a manual edit: ${conflicts.join(', ')}.`; if (missing.length) reviewLine += ` Required data categories not mentioned: ${missing.join(', ')}.`; let notice; switch (aiStep) { case 'ran': notice = `${reason ?? 'The proposal is an AI draft.'} Nothing is live until it is published.${reviewLine}`; break; case 'not_needed': notice = `${reason ?? 'No AI step was needed; the proposal is the regenerated policy.'}${reviewLine}`; break; default: notice = `${reason ?? 'The AI step did not run. The proposal is the deterministic section merge, not an AI draft.'} ` + "proposedMarkdown keeps the author's sections and adds only sections new since the last generated " + `baseline; it is not AI-written.${reviewLine}`; } // Never let a non-AI proposal carry the AI version label into publish. const kind = !usedLlm && r.kind === 'ai_updated' ? 'edited' : r.kind; return { aiStep, notice, ...r, ...(kind === undefined ? {} : { kind }) }; } //# sourceMappingURL=privacy-ai-outcome.js.map