bardd's picture
Deploy Mike backend-only Space
06671aa verified
Raw History Blame Contribute Delete
2.17 kB
import type { ResearchBundleV1, ResearchSourceV1, WebCitationV1 } from "./types";
/**
* Web citation handling (§30, §31). Keeps web citations separate from document
* citations. The action model cites source IDs inline as [S<number>]. The
* server accepts only IDs from the validated bundle and builds final citation
* objects from server-owned source metadata. Model-generated URLs are ignored.
*/
const MARKER_RE = /\[S(\d+)\]/g;
/** Extract the ordered, de-duplicated source IDs actually referenced in text. */
export function extractReferencedSourceIds(text: string): string[] {
const out: string[] = [];
if (!text) return out;
const seen = new Set<string>();
let m: RegExpExecArray | null;
MARKER_RE.lastIndex = 0;
while ((m = MARKER_RE.exec(text)) !== null) {
const id = `S${m[1]}`;
if (!seen.has(id)) {
seen.add(id);
out.push(id);
}
}
return out;
}
/** Build web citation objects only for referenced, validated sources. */
export function buildWebCitations(
text: string,
bundle: ResearchBundleV1 | null,
): WebCitationV1[] {
if (!bundle) return [];
const referenced = new Set(extractReferencedSourceIds(text));
const byId = new Map<string, ResearchSourceV1>();
for (const s of bundle.sources) byId.set(s.sourceId, s);
const citations: WebCitationV1[] = [];
for (const id of referenced) {
const src = byId.get(id);
if (!src) continue; // unknown ID ignored
citations.push({
sourceId: src.sourceId,
title: src.title,
url: src.url,
publisher: src.publisher,
publishedAt: src.publishedAt,
sourceType: src.sourceType,
});
}
return citations;
}
/** Unknown/referenced-but-missing IDs (for observability), never thrown. */
export function findUnknownSourceIds(text: string, bundle: ResearchBundleV1 | null): string[] {
const referenced = extractReferencedSourceIds(text);
if (!bundle) return referenced;
const known = new Set(bundle.sources.map((s) => s.sourceId));
return referenced.filter((id) => !known.has(id));
}