@sema-agent/core
Version:
Stateless, task-oriented AI agent core
104 lines (103 loc) • 4.09 kB
JavaScript
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "./untrusted-text.js";
const MAX_PER_FILE = 10;
const MAX_TOTAL = 30;
const MAX_SUMMARY_CHARS = 4000;
function severitySymbol(severity) {
switch (severity) {
case 1: return "✖";
case 2: return "⚠";
case 3: return "ℹ";
case 4: return "★";
default: return "●";
}
}
function diagnosticKey(uri, d) {
const r = d.range ? `${d.range.start.line}:${d.range.start.character}-${d.range.end?.line ?? ""}:${d.range.end?.character ?? ""}` : "";
return `${uri}|${d.message}|${d.severity ?? ""}|${d.source ?? ""}|${d.code ?? ""}|${r}`;
}
export class LspDiagnosticsRegistry {
pending = new Map();
delivered = new Map();
publish(uri, diagnostics) {
if (diagnostics.length === 0)
this.pending.delete(uri);
else
this.pending.set(uri, diagnostics);
}
fileEdited(runIdent, uri) {
const forRun = this.delivered.get(runIdent);
if (forRun === undefined)
return;
const prefix = `${uri}|`;
for (const key of forRun) {
if (key.startsWith(prefix))
forRun.delete(key);
}
if (forRun.size === 0)
this.delivered.delete(runIdent);
}
releaseRun(runIdent) {
this.delivered.delete(runIdent);
}
isEmpty() {
return this.pending.size === 0;
}
drain(runIdent) {
const out = [];
const requeued = new Map();
let delivered = this.delivered.get(runIdent);
let total = 0;
for (const [uri, all] of this.pending) {
const fresh = all
.filter((d) => delivered?.has(diagnosticKey(uri, d)) !== true)
.sort((a, b) => (a.severity ?? 99) - (b.severity ?? 99));
if (fresh.length === 0)
continue;
const room = Math.max(0, Math.min(MAX_PER_FILE, MAX_TOTAL - total));
const take = fresh.slice(0, room);
if (fresh.length > take.length)
requeued.set(uri, fresh.slice(take.length));
if (take.length === 0)
continue;
if (delivered === undefined) {
delivered = new Set();
this.delivered.set(runIdent, delivered);
}
for (const d of take)
delivered.add(diagnosticKey(uri, d));
total += take.length;
out.push({ uri, diagnostics: take });
}
this.pending.clear();
for (const [uri, rest] of requeued)
this.pending.set(uri, rest);
return out;
}
}
function basenameOfUri(uri) {
const path = uri.replace(/^file:\/\//, "");
const idx = path.lastIndexOf("/");
return idx >= 0 ? path.slice(idx + 1) : path;
}
export function formatDiagnosticsSummary(files) {
const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
const lines = [];
for (const f of files) {
lines.push(`${safe(basenameOfUri(f.uri))}:`);
for (const d of f.diagnostics) {
const startLine = d.range?.start?.line;
const startChar = d.range?.start?.character;
const pos = typeof startLine === "number" && Number.isFinite(startLine) && typeof startChar === "number" && Number.isFinite(startChar)
? `[Line ${Math.trunc(startLine) + 1}:${Math.trunc(startChar) + 1}] `
: "";
const code = d.code !== undefined ? ` [${safe(String(d.code))}]` : "";
const source = d.source ? ` (${safe(String(d.source))})` : "";
lines.push(` ${severitySymbol(d.severity)} ${pos}${safe(String(d.message))}${code}${source}`);
}
}
const summary = lines.join("\n");
return summary.length > MAX_SUMMARY_CHARS ? `${summary.slice(0, MAX_SUMMARY_CHARS)}…[truncated]` : summary;
}
export function formatDiagnosticsBlock(files) {
return `<new-diagnostics>The following new diagnostic issues were detected:\n\n${formatDiagnosticsSummary(files)}</new-diagnostics>`;
}