mike-backend / backend /src /lib /chatTools.ts
bardd's picture
Deploy Mike backend-only Space
06671aa verified
Raw History Blame Contribute Delete
397 kB
import path from "path";
import crypto from "node:crypto";
import type { RunTree } from "langsmith";
import {
downloadFile,
generatedDocKey,
storageKey,
uploadFile,
} from "./storage";
import { convertedPdfKey } from "./convert";
import { createServerSupabase } from "./supabase";
import {
applyTrackedEdits,
applyTrackedOperations,
buildDocxEditSurface,
extractDocxBodyText,
findInDocxEditSurface,
renderDocxEditSurfaceForModel,
extractTrackedChangeIds,
type EditOperation,
type EditInput,
type AppliedChange,
type CanonicalAnchor,
} from "./docxTrackedChanges";
import { buildDownloadUrl } from "./downloadTokens";
import { attachActiveVersionPaths, loadActiveVersion } from "./documentVersions";
import { DOCX_AUTHOR_DEFAULT } from "./brand";
import {
streamChatWithTools,
resolveModel,
extractWebAnnotationsFromEvents,
type LlmMessage,
type OpenAIToolSchema,
type NormalizedToolCall,
type NormalizedToolResult,
} from "./llm";
import { getDefaultModelForFeature } from "./inference/config";
import {
createTraceChild,
endTrace,
traceHeaders,
buildDocumentSurfaceTracePayload,
sha256,
} from "./langsmith";
import { ControlledUserFacingError } from "./assistantErrors";
import { decodeXmlEntities } from "./docxTrackedChanges";
import type { AssistantTurnMode } from "./documentMode";
import { buildChatSystemPrompt } from "../prompts/chat/builders/buildChatSystemPrompt";
import { appendModeBanner } from "../prompts/chat/fragments/mode-banners";
import { buildSaudiLawWebHintsBanner } from "../prompts/chat/fragments/saudi-law-web-hints";
import type { SaudiLawRoutingResult } from "./saudiLawRouting";
import {
buildSaudiLawSourcePacket,
renderSaudiLawSourcePacketHintsBanner,
summarizeSaudiLawSourcePacket,
type SaudiLawSourcePacket,
} from "./saudiLawSourceControl";
import {
buildSaudiLawEvidencePacket,
hasGrounding,
renderSaudiLawEvidencePacketBanner,
summarizeSaudiLawEvidencePacket,
type SaudiLawEvidencePacket,
} from "./saudiLawResearch";
import { mergeGrounding } from "./llm/grounding";
import type { SearchGrounding } from "./llm/types";
import { buildWorkflowUserPrefix } from "../prompts/chat/workflow-user-prefix";
import { DOCX_EDIT_EMPTY_TURN_RETRY } from "../prompts/chat/retry/empty-turn-docx-edit";
import { WORKFLOW_REVIEW_EMPTY_TURN_RETRY } from "../prompts/chat/retry/empty-turn-workflow-review";
import { WEB_SEARCH_EMPTY_TURN_RETRY } from "../prompts/chat/retry/empty-turn-web-search";
const STANDARD_FONT_DATA_URL = (() => {
try {
const pkgPath = require.resolve("pdfjs-dist/package.json");
return path.join(path.dirname(pkgPath), "standard_fonts") + path.sep;
} catch {
return undefined;
}
})();
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
const FREE_PROXY_CONCURRENCY = Number(process.env.MIKE_FREE_PROXY_CONCURRENCY) || 4;
const freeProxySlotTimeoutMs = () => Number(process.env.MIKE_FREE_PROXY_SLOT_TIMEOUT_MS) || 240_000;
const freeProxyAcquireTimeoutMs = () => Number(process.env.MIKE_FREE_PROXY_SLOT_ACQUIRE_TIMEOUT_MS) || 30_000;
interface FreeProxySlotWaiter {
resolve: (release: () => void) => void;
reject: (err: Error) => void;
}
let freeProxyPermits = FREE_PROXY_CONCURRENCY;
const freeProxyWaiters: FreeProxySlotWaiter[] = [];
function acquireFreeProxySlot(): Promise<() => void> {
return new Promise((resolve, reject) => {
const waiter: FreeProxySlotWaiter = { resolve, reject };
const timer = setTimeout(() => {
const idx = freeProxyWaiters.indexOf(waiter);
if (idx >= 0) freeProxyWaiters.splice(idx, 1);
reject(new Error("Timed out waiting for a free proxy slot"));
}, freeProxyAcquireTimeoutMs());
const release = () => {
clearTimeout(timer);
freeProxyPermits++;
const next = freeProxyWaiters.shift();
if (next) {
freeProxyPermits--;
next.resolve(release);
}
};
if (freeProxyPermits > 0) {
freeProxyPermits--;
resolve(release);
} else {
freeProxyWaiters.push(waiter);
}
});
}
export async function withFreeProxySlot<T>(
fn: (signal: AbortSignal) => Promise<T>,
externalSignal?: AbortSignal,
): Promise<T> {
const ac = new AbortController();
const slotTimeout = setTimeout(() => {
console.error(
"[withFreeProxySlot] slot timeout; aborting request and releasing slot",
);
ac.abort();
}, freeProxySlotTimeoutMs());
// Merge the pool's own abort (slot timeout) with any external abort (e.g.
// client disconnect from the route) so all sources can cancel the in-flight
// request and free the slot.
const merged = new AbortController();
const link = (sig: AbortSignal) => {
if (sig.aborted) merged.abort(sig.reason);
else sig.addEventListener("abort", () => merged.abort(sig.reason), { once: true });
};
link(ac.signal);
if (externalSignal) link(externalSignal);
let release: (() => void) | undefined;
try {
release = await acquireFreeProxySlot();
return await fn(merged.signal);
} catch (e) {
if (ac.signal.aborted) {
throw new Error("proxy_timeout: free proxy slot timed out");
}
throw e;
} finally {
clearTimeout(slotTimeout);
release?.();
}
}
async function delayBetweenSaudiEvalProxyCalls(label: string): Promise<void> {
const raw =
process.env.SAUDI_EVAL_PROXY_DELAY_MS ??
process.env.PROXY_DELAY_MS ??
"0";
const delayMs = Number(raw);
if (!Number.isFinite(delayMs) || delayMs <= 0) return;
console.log(
`[runLLMStream] waiting ${delayMs}ms before proxy request (${label})`,
);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
export type DocStore = Map<
string,
{ storage_path: string; file_type: string; filename: string }
>;
export type WorkflowStore = Map<string, { title: string; prompt_md: string; edit_mode_prompt_md?: string }>;
export type DocIndex = Record<
string,
{
document_id: string;
filename: string;
file_type?: string;
version_id?: string | null;
version_number?: number | null;
}
>;
export type TabularCellStore = {
columns: { index: number; name: string }[];
documents: { id: string; filename: string }[];
/** key: `${colIndex}:${docId}` */
cells: Map<string, { summary: string; flag?: string; reasoning?: string } | null>;
};
export type ToolCall = {
id: string;
function: { name: string; arguments: string };
};
// Canonical anchor map cache: surface_id -> block_id -> CanonicalAnchor
const surfaceAnchorCache = new Map<string, Map<string, CanonicalAnchor>>();
type AnchorManifest = {
docLabel: string;
documentId?: string;
docRevisionId: string;
surfaceId: string;
readSerial: number;
anchors: Map<string, CanonicalAnchor>;
};
type AnchorManifestState = {
byDocLabel: Map<string, AnchorManifest>;
nextReadSerial: number;
};
const SECTION_HEADING_RE = /^(?:Section|Clause|Article)\s+\d/i;
const INTERNAL_BLOCK_ID_RE = /\b(?:BMK_[PT]_[A-Za-z0-9_-]+|P_\d{4,})\b/g;
function sanitizeWorkflowDisplayText(value: string | undefined | null): string {
return (value ?? "")
.replace(INTERNAL_BLOCK_ID_RE, "")
.replace(/\[\s*\]/g, "")
.replace(/\(\s*\)/g, "")
.replace(/\s+([,.;:])/g, "$1")
.replace(/\s{2,}/g, " ")
.trim();
}
function isMeaningfulSectionLabel(value: string | undefined | null): value is string {
const sanitized = sanitizeWorkflowDisplayText(value);
return Boolean(sanitized && !/^(?:section|clause|article)$/i.test(sanitized));
}
function findSectionHeading(
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>,
blockId: string,
): string | null {
const blockIdx = surface.blocks.findIndex((b) => b.id === blockId);
if (blockIdx < 0) return null;
// Include the selected block because MISSING findings are intentionally
// anchored on the nearest eligible section heading.
for (let i = blockIdx; i >= 0; i--) {
if (surface.anchors[i]?.anchor_eligible === false) continue;
const text = surface.blocks[i].text.trim();
if (SECTION_HEADING_RE.test(text)) {
const heading = sanitizeWorkflowDisplayText(decodeXmlEntities(text));
if (heading) return heading;
}
}
return null;
}
function normalizeWorkflowAction(opType: string | undefined): WorkflowAction {
switch (opType) {
case "REWRITE_PARAGRAPH":
case "REWRITE":
return "REWRITE";
case "INSERT":
case "INSERT_PARAGRAPH":
return "INSERT";
case "DELETE_PARAGRAPH":
case "DELETE":
return "DELETE";
case "COMMENT":
default:
return "COMMENT";
}
}
function parseCommentFields(comment: string): {
issue: string;
zamilImpact: string;
recommendedAction: string;
} {
let issue = comment;
let zamilImpact = "";
let recommendedAction = "";
const zamilMatch = comment.match(/Zamil Impact:\s*(.*?)(?:\s*\|\s*Recommended Action:|$)/i);
if (zamilMatch) zamilImpact = zamilMatch[1].trim();
const recMatch = comment.match(/Recommended Action:\s*(.*?)$/i);
if (recMatch) recommendedAction = recMatch[1].trim();
const issueMatch = comment.match(/(?:\[[\w]+\]\s*)?Issue:\s*(.*?)(?:\s*\|\s*Zamil Impact:|$)/i);
if (issueMatch) {
issue = issueMatch[1].trim();
} else {
const stripped = comment.replace(/^\[[\w]+\]\s*/, "");
const pipeIdx = stripped.indexOf("| Zamil Impact:");
issue = pipeIdx >= 0 ? stripped.slice(0, pipeIdx).trim() : stripped.trim();
}
return { issue, zamilImpact, recommendedAction };
}
function normalizeModelBlockId(blockId: string): string {
return blockId
.replace(/^_(BMK_[PT]_[a-z0-9]+)$/, "$1")
.replace(/^_(BMK_P_page\d+)$/, "$1");
}
function isValidDocxModelBlockId(blockId: string): boolean {
return /^P_\d+$/.test(blockId) || /^BMK_[PT]_[a-z0-9]+$/.test(blockId);
}
function getDocumentOrder(
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>,
blockId?: string | null,
bookmarkName?: string | null,
): number | undefined {
const normalizedBlockId = blockId ? normalizeModelBlockId(blockId) : null;
const normalizedBookmarkName = bookmarkName ? normalizeModelBlockId(bookmarkName) : null;
if (!normalizedBlockId && !normalizedBookmarkName) return undefined;
const idx = surface.anchors.findIndex((a: any) =>
(!!normalizedBlockId && a.block_id === normalizedBlockId) ||
(!!normalizedBookmarkName && a.bookmark_name === normalizedBookmarkName)
);
return idx >= 0 ? idx : undefined;
}
function createAnchorManifestState(): AnchorManifestState {
return { byDocLabel: new Map(), nextReadSerial: 0 };
}
type TurnReadRecord = {
docLabel: string;
filename: string;
content: string;
charCount: number;
readSerial: number;
surfaceId?: string;
};
type TurnDocumentState = {
readsByDocLabel: Map<string, TurnReadRecord>;
duplicateReadCount: number;
};
function createTurnDocumentState(): TurnDocumentState {
return {
readsByDocLabel: new Map(),
duplicateReadCount: 0,
};
}
const MAX_TOOL_BATCHES_PER_TURN = 6;
const MAX_TOOL_BATCHES_WORKFLOW_REVIEW = 12;
const MAX_FULL_DOC_READS_PER_TURN = 5;
const MAX_DUPLICATE_READS_PER_TURN = 2;
const WORKFLOW_WRITE_TOOL_NAMES = new Set([
"plan_comments_on_document",
"correct_comment_anchors",
"fix_comment_findings",
]);
function assertToolBudget(params: {
toolBatchCount: number;
turnDocumentState: TurnDocumentState;
workflowReview?: boolean;
}): void {
const maxBatches = params.workflowReview
? MAX_TOOL_BATCHES_WORKFLOW_REVIEW
: MAX_TOOL_BATCHES_PER_TURN;
if (params.toolBatchCount > maxBatches) {
throw new ControlledUserFacingError({
code: "tool_loop_detected",
message:
"The assistant attempted too many tool rounds. I stopped the turn to prevent a runaway loop.",
safeDetails: {
tool_batch_count: params.toolBatchCount,
max_tool_batches_per_turn: maxBatches,
},
});
}
if (params.turnDocumentState.readsByDocLabel.size > MAX_FULL_DOC_READS_PER_TURN) {
throw new ControlledUserFacingError({
code: "too_many_document_reads",
message:
"The assistant attempted to read too many full documents in one turn. Please narrow the request or use targeted search.",
safeDetails: {
full_document_reads: params.turnDocumentState.readsByDocLabel.size,
max_full_doc_reads_per_turn: MAX_FULL_DOC_READS_PER_TURN,
},
});
}
if (params.turnDocumentState.duplicateReadCount > MAX_DUPLICATE_READS_PER_TURN) {
throw new ControlledUserFacingError({
code: "duplicate_document_read_loop",
message:
"The assistant repeatedly tried to read the same document. I stopped the loop to protect the session.",
safeDetails: {
duplicate_read_count: params.turnDocumentState.duplicateReadCount,
max_duplicate_reads_per_turn: MAX_DUPLICATE_READS_PER_TURN,
},
});
}
}
function isAlreadyReadToolResult(content: string): boolean {
try {
const parsed = JSON.parse(content) as { already_read?: unknown };
return parsed.already_read === true;
} catch {
return false;
}
}
function clampInteger(value: number | undefined, params: { min: number; max: number; fallback: number }): number {
if (typeof value !== "number" || !Number.isFinite(value)) return params.fallback;
return Math.max(params.min, Math.min(params.max, Math.floor(value)));
}
function rememberAnchorManifest(
state: AnchorManifestState | undefined,
docLabel: string,
documentId: string | undefined,
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>,
): AnchorManifest | null {
const anchorMap = new Map<string, CanonicalAnchor>();
for (const a of surface.anchors) anchorMap.set(a.block_id, a);
surfaceAnchorCache.set(surface.surfaceId, anchorMap);
if (!state) return null;
const manifest: AnchorManifest = {
docLabel,
documentId,
docRevisionId: surface.surfaceId,
surfaceId: surface.surfaceId,
readSerial: ++state.nextReadSerial,
anchors: anchorMap,
};
state.byDocLabel.set(docLabel, manifest);
return manifest;
}
export type ChatMessage = {
role: string;
content: string | null;
files?: { filename: string; document_id?: string }[];
workflow?: { id: string; title: string };
};
// ---------------------------------------------------------------------------
// Prompt constants (migrated to backend/src/prompts/ — re-export shims)
// ---------------------------------------------------------------------------
import { DIRECT_DOCX_EDIT_TOOLS } from "../prompts/tools/direct-docx-edit";
import { WORKFLOW_TOOLS } from "../prompts/tools/workflow";
import { ASSISTANT_CORE_TOOLS as TOOLS } from "../prompts/tools/assistant-core";
export { SYSTEM_PROMPT } from "../prompts/chat/system-main";
export { DIRECT_DOCX_EDIT_SYSTEM_PROMPT } from "../prompts/chat/system-direct-docx-edit";
export { PROJECT_EXTRA_TOOLS } from "../prompts/tools/project";
export { TABULAR_TOOLS } from "../prompts/tools/tabular";
export { WORKFLOW_TOOLS } from "../prompts/tools/workflow";
export { DIRECT_DOCX_EDIT_TOOLS } from "../prompts/tools/direct-docx-edit";
export { ASSISTANT_CORE_TOOLS as TOOLS } from "../prompts/tools/assistant-core";
// ---------------------------------------------------------------------------
// Server-side capability matrix
// ---------------------------------------------------------------------------
//
// Tool availability is enforced HERE, in server code, not in prompts. Each
// assistant turn mode exposes only a fixed, audited set of tools. This is the
// single source of truth used both to filter the tool schema sent to the model
// AND to reject any forbidden tool call the model attempts anyway.
// Read-only tools never mutate document bytes or storage. Safe in every mode.
const READ_ONLY_TOOLS = [
"read_document",
"find_in_document",
"list_documents",
"fetch_documents",
"list_workflows",
"read_workflow",
"read_table_cells",
"inspect_document",
"search_document",
] as const;
// Comment tools annotate documents (Word comments / planned findings). They do
// not perform tracked-byte mutations on their own in comments mode.
const COMMENT_TOOLS = [
"comment_on_document",
"plan_comments_on_document",
"correct_comment_anchors",
"fix_comment_findings",
] as const;
// Tracked-change byte mutation tool for direct document edits.
const DIRECT_EDIT_TOOLS = ["edit_document"] as const;
// Document creation / replication tools.
const GENERATION_TOOLS = ["generate_docx", "replicate_document"] as const;
const MODE_TOOL_CAPABILITIES: Record<AssistantTurnMode, ReadonlySet<string>> = {
answer: new Set<string>([...READ_ONLY_TOOLS]),
web_search: new Set<string>([...READ_ONLY_TOOLS]),
comments: new Set<string>([...READ_ONLY_TOOLS, ...COMMENT_TOOLS]),
workflow_review: new Set<string>([
...READ_ONLY_TOOLS,
...COMMENT_TOOLS,
...GENERATION_TOOLS,
]),
document_generation: new Set<string>([
...READ_ONLY_TOOLS,
...GENERATION_TOOLS,
]),
document_direct_edit: new Set<string>([
...READ_ONLY_TOOLS,
...DIRECT_EDIT_TOOLS,
]),
document_edit_with_web_search: new Set<string>([
...READ_ONLY_TOOLS,
...DIRECT_EDIT_TOOLS,
]),
// project_chat is the auto fallback for project routes: full comment +
// generation surface, but never the direct tracked-edit tool.
project_chat: new Set<string>([
...READ_ONLY_TOOLS,
...COMMENT_TOOLS,
...GENERATION_TOOLS,
]),
};
/**
* Single source of truth for "may this tool run in this turn mode?". Used to
* filter the exposed tool schema and to reject forbidden tool calls the model
* attempts despite the filtered schema.
*/
export function isToolAllowedForMode(
toolName: string,
turnMode: AssistantTurnMode,
): boolean {
return MODE_TOOL_CAPABILITIES[turnMode]?.has(toolName) ?? false;
}
/** Human-readable rejection returned to the model for a forbidden tool call. */
export function forbiddenToolMessage(
toolName: string,
turnMode: AssistantTurnMode,
): string {
return `Tool '${toolName}' is not permitted in the current mode (${turnMode}).`;
}
type ParsedCitation = {
ref: number;
doc_id: string;
page: number | string;
quote: string;
};
function normalizeCitation(raw: unknown): ParsedCitation | null {
if (!raw || typeof raw !== "object") return null;
const c = raw as Record<string, unknown>;
if (typeof c.ref !== "number" || typeof c.doc_id !== "string") return null;
if (typeof c.quote !== "string" || !c.quote) return null;
let page: number | string;
if (typeof c.page === "number") {
page = c.page;
} else if (typeof c.page === "string" && /^\d+\s*-\s*\d+$/.test(c.page)) {
page = c.page;
} else {
const n = parseInt(String(c.page ?? ""), 10);
if (!Number.isFinite(n)) return null;
page = n;
}
return { ref: c.ref, doc_id: c.doc_id, page, quote: c.quote };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
export function resolveDoc(rawId: string, docIndex: DocIndex) {
return docIndex[rawId];
}
/**
* Resolve whatever identifier the model passed (`doc-N` slug, filename, or
* document UUID) back to a chat-local doc label. Generated docs surface in
* tool results with both `doc_id` (slug) and `document_id` (UUID), so the
* model often picks the wrong one — without this fallback `read_document`
* silently returns "not found" and the model gives up and re-generates.
*/
export function resolveDocLabel(
rawId: string,
docStore: DocStore,
docIndex?: DocIndex,
): string | null {
if (docStore.has(rawId)) return rawId;
for (const [label, info] of docStore.entries()) {
if (info.filename === rawId) return label;
}
if (docIndex) {
for (const [label, info] of Object.entries(docIndex)) {
if (info.document_id === rawId) return label;
}
}
return null;
}
/**
* Append a tool-activity summary to the most recent assistant message so
* the model can see what it just did (read / create / edit / workflow
* applied) in the prior turn — otherwise it only sees its own prose and
* forgets which docs it touched, which leads to e.g. re-generating a doc
* that already exists.
*
* Doc references use the *current-turn* `doc_id` slug (looked up by
* matching the event's stored `document_id` against this turn's freshly
* built `docIndex`), since slugs are reassigned every turn and the old
* slug from the prior turn would be meaningless. Falls back to filename
* only if the doc is no longer in the index (deleted, scope changed).
*/
export async function enrichWithPriorEvents(
messages: ChatMessage[],
chatId: string | null | undefined,
db: ReturnType<typeof createServerSupabase>,
docIndex: DocIndex,
): Promise<ChatMessage[]> {
if (!chatId) return messages;
const { data: rows } = await db
.from("chat_messages")
.select("content, created_at")
.eq("chat_id", chatId)
.eq("role", "assistant")
.order("created_at", { ascending: false })
.limit(1);
const lastRow = rows?.[0] as { content?: unknown } | undefined;
const content = lastRow?.content;
if (!Array.isArray(content)) return messages;
const slugByDocumentId = new Map<string, string>();
for (const [slug, info] of Object.entries(docIndex)) {
if (info.document_id) slugByDocumentId.set(info.document_id, slug);
}
const refFor = (documentId: unknown, filename: unknown) => {
const slug =
typeof documentId === "string"
? slugByDocumentId.get(documentId)
: undefined;
return slug ? `${slug} ("${filename}")` : `"${filename}"`;
};
const lines: string[] = [];
for (const ev of content as Record<string, unknown>[]) {
if (ev?.type === "doc_created") {
lines.push(
`- generate_docx → ${refFor(ev.document_id, ev.filename)}`,
);
} else if (ev?.type === "doc_edited") {
lines.push(
`- plan_comments_on_document (edit) → ${refFor(ev.document_id, ev.filename)}`,
);
} else if (ev?.type === "doc_read") {
lines.push(
`- read_document → ${refFor(ev.document_id, ev.filename)}`,
);
} else if (ev?.type === "doc_replicated") {
// The model needs to know what each copy resolved to so it
// can call plan_comments_on_document / read_document on them. Emit one
// line per copy, all attributed back to the same source.
const srcLabel =
typeof ev.filename === "string" ? `"${ev.filename}"` : "";
const copies = Array.isArray(ev.copies)
? (ev.copies as {
new_filename?: unknown;
document_id?: unknown;
}[])
: [];
for (const c of copies) {
const ref = refFor(c.document_id, c.new_filename);
lines.push(
srcLabel
? `- replicate_document → ${ref} (copy of ${srcLabel})`
: `- replicate_document → ${ref}`,
);
}
} else if (ev?.type === "workflow_applied") {
lines.push(`- applied workflow: "${ev.title}"`);
}
}
if (lines.length === 0) return messages;
const summary = `\n\n[Tool activity in your previous turn]\n${lines.join("\n")}`;
// Find the index of the last assistant message and attach the
// summary there only.
let lastAssistantIdx = -1;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === "assistant") {
lastAssistantIdx = i;
break;
}
}
if (lastAssistantIdx < 0) return messages;
const enriched = messages.slice();
const target = enriched[lastAssistantIdx];
enriched[lastAssistantIdx] = {
...target,
content: (target.content ?? "") + summary,
};
return enriched;
}
export function buildMessages(
messages: ChatMessage[],
docAvailability: { doc_id: string; filename: string; folder_path?: string }[],
systemPromptExtra?: string,
docIndex?: DocIndex,
turnMode: AssistantTurnMode = "answer",
) {
const formatted: unknown[] = [];
const isDirectDocxEdit =
turnMode === "document_direct_edit" ||
turnMode === "document_edit_with_web_search";
const systemContent = buildChatSystemPrompt({
turnMode,
docAvailability,
systemPromptExtra,
});
formatted.push({ role: "system", content: systemContent });
// Map document_id (UUID) → current-turn doc_id slug, so when we
// inline a user attachment we hand the model the same handle it
// would use to call read_document / fetch_documents.
const slugByDocumentId = new Map<string, string>();
if (docIndex) {
for (const [slug, info] of Object.entries(docIndex)) {
if (info.document_id) slugByDocumentId.set(info.document_id, slug);
}
}
for (const msg of messages) {
let content = msg.content ?? "";
if (msg.role === "user" && msg.workflow) {
content = `${buildWorkflowUserPrefix(msg.workflow.title, msg.workflow.id)}\n\n${content}`;
}
if (msg.role === "user" && msg.files?.length) {
const lines = msg.files.map((f) => {
const slug = f.document_id
? slugByDocumentId.get(f.document_id)
: undefined;
return slug
? `- ${slug}: ${f.filename}`
: `- ${f.filename}`;
});
if (isDirectDocxEdit) {
content = `[The user has attached the following document(s). In direct DOCX edit mode, use inspect_document/search_document if content or anchors are needed, then use edit_document to apply the requested mutation:\n${lines.join("\n")}]\n\n${content}`;
} else {
content = `[The user has attached the following document(s). You MUST use the read_document tool to read their contents before you can answer or edit:\n${lines.join("\n")}]\n\n${content}`;
}
}
formatted.push({ role: msg.role, content });
}
return formatted;
}
// ===========================================================================
// PDF Read-Only virtual surface and quote resolution types & helpers
// ===========================================================================
export type PdfVirtualPage = {
page: number;
block_id: string;
text: string;
text_hash: string;
};
export type PdfVirtualSurface = {
surfaceId: string;
pages: PdfVirtualPage[];
text: string;
};
export function sha256Text(input: string): string {
return crypto.createHash("sha256").update(input, "utf8").digest("hex");
}
export function normalizePdfPageText(input: string): string {
return input
.replace(/\u00A0/g, " ")
.replace(/[ \t\r\f\v]+/g, " ")
.replace(/\n[ \t]+/g, "\n")
.replace(/[ \t]+\n/g, "\n")
.replace(/\n{3,}/g, "\n\n")
.trim();
}
export function pdfPageBlockId(page: number): string {
return `BMK_P_page${page}`;
}
export function pageFromPdfBlockId(blockId: string | null | undefined): number | null {
if (!blockId) return null;
const normalizedBlockId = normalizeModelBlockId(blockId);
const match = normalizedBlockId.match(/^BMK_P_page(\d+)$/);
if (!match) return null;
const page = Number.parseInt(match[1], 10);
if (!Number.isFinite(page) || page < 1) return null;
return page;
}
export async function buildPdfVirtualSurface(
buf: ArrayBuffer,
opts?: { documentId?: string; versionId?: string | null },
): Promise<PdfVirtualSurface> {
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs" as string) as any;
const loadingTask = pdfjs.getDocument({
data: new Uint8Array(buf),
useWorkerFetch: false,
isEvalSupported: false,
disableFontFace: true,
standardFontDataUrl: STANDARD_FONT_DATA_URL,
});
const pdf = await loadingTask.promise;
const pages: PdfVirtualPage[] = [];
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
const page = await pdf.getPage(pageNumber);
const textContent = await page.getTextContent();
const rawText = textContent.items
.map((item: any) => {
if (
item &&
typeof item === "object" &&
"str" in item &&
typeof item.str === "string"
) {
return item.str;
}
return "";
})
.filter((part: string) => part.length > 0)
.join(" ");
const text = normalizePdfPageText(rawText);
const block_id = pdfPageBlockId(pageNumber);
pages.push({
page: pageNumber,
block_id,
text,
text_hash: sha256Text(text),
});
}
const allExtractedText = pages.map((page) => page.text).join("\n\n");
const textHash = sha256Text(allExtractedText);
const surfaceId = sha256Text(
[
"pdf",
opts?.documentId ?? "unknown-document",
opts?.versionId ?? "unknown-version",
String(pdf.numPages),
textHash,
].join(":"),
);
const text = pages
.map((page) => {
return [
`[[${page.block_id}]]`,
`[Page ${page.page}]`,
page.text || "[No extractable text on this page]",
].join("\n");
})
.join("\n\n");
return {
surfaceId,
pages,
text,
};
}
export function rememberPdfAnchorManifest(
anchorManifestState: AnchorManifestState | undefined,
docLabel: string,
documentId: string | undefined,
surface: PdfVirtualSurface,
): void {
if (!anchorManifestState) return;
const anchors = surface.pages.map((page, index) => {
const previousPage = index > 0 ? surface.pages[index - 1] : null;
const nextPage = index < surface.pages.length - 1 ? surface.pages[index + 1] : null;
return {
block_id: page.block_id,
model_block_id: page.block_id,
doc_revision_id: surface.surfaceId,
native_para_id: null,
stable_id: `pdf-page-${page.page}`,
xml_path: `pdf/page/${page.page}`,
text_hash: page.text_hash,
prev_text_hash: previousPage ? previousPage.text_hash : null,
next_text_hash: nextPage ? nextPage.text_hash : null,
visible_text_length: page.text.length,
commentable: page.text.trim().length > 0,
text_preview: page.text.slice(0, 240),
kind: "paragraph",
container: "body",
bookmark_name: page.block_id,
};
});
const anchorMap = new Map<string, CanonicalAnchor>();
for (const a of anchors) {
anchorMap.set(a.block_id, a as unknown as CanonicalAnchor);
}
const manifest: AnchorManifest = {
docLabel,
documentId,
docRevisionId: surface.surfaceId,
surfaceId: surface.surfaceId,
readSerial: ++anchorManifestState.nextReadSerial,
anchors: anchorMap,
};
anchorManifestState.byDocLabel.set(docLabel, manifest);
}
export type PdfTargetResolution = {
text: string;
confidence: number;
strategy: string;
};
export type PdfTargetResolutionInput = {
pageText: string;
targetText: string;
beforeAnchor?: string;
afterAnchor?: string;
};
export function normalizeForPdfQuoteMatch(input: string): string {
return input
.replace(/\u00A0/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function escapeRegExp(input: string): string {
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function findWhitespaceNormalizedOriginalSlice(
originalText: string,
targetText: string,
caseInsensitive: boolean,
): string | null {
const normalizedTarget = normalizeForPdfQuoteMatch(targetText);
if (!normalizedTarget) return null;
const escapedParts = normalizedTarget
.split(" ")
.filter(Boolean)
.map(escapeRegExp);
if (escapedParts.length === 0) return null;
const pattern = escapedParts.join("\\s+");
const flags = caseInsensitive ? "i" : "";
const regex = new RegExp(pattern, flags);
const match = originalText.match(regex);
return match?.[0] ?? null;
}
export function extractSentenceWindowContaining(
pageText: string,
targetText: string,
): string | null {
const normalizedTarget = normalizeForPdfQuoteMatch(targetText);
if (!normalizedTarget) return null;
const sentences = pageText
.split(/(?<=[.!?;:])\s+/)
.map((part) => part.trim())
.filter(Boolean);
const lowerTarget = normalizedTarget.toLowerCase();
const matchingSentences = sentences.filter((sentence) => {
const normalizedSentence = normalizeForPdfQuoteMatch(sentence).toLowerCase();
return normalizedSentence.includes(lowerTarget) || lowerTarget.includes(normalizedSentence);
});
if (matchingSentences.length === 1) {
return matchingSentences[0];
}
if (matchingSentences.length > 1) {
return matchingSentences
.sort((a, b) => Math.abs(a.length - normalizedTarget.length) - Math.abs(b.length - normalizedTarget.length))[0];
}
const targetWords = lowerTarget.split(" ").filter((word) => word.length >= 4);
if (targetWords.length === 0) return null;
const scored = sentences
.map((sentence) => {
const lowerSentence = normalizeForPdfQuoteMatch(sentence).toLowerCase();
const score = targetWords.reduce((count, word) => {
return lowerSentence.includes(word) ? count + 1 : count;
}, 0);
return { sentence, score };
})
.filter((item) => item.score > 0)
.sort((a, b) => b.score - a.score);
if (scored.length === 0) return null;
const best = scored[0];
if (best.score >= Math.max(2, Math.ceil(targetWords.length * 0.5))) {
return best.sentence;
}
return null;
}
export function resolvePdfTargetText(input: PdfTargetResolutionInput): PdfTargetResolution {
const pageText = input.pageText ?? "";
const targetText = input.targetText ?? "";
const beforeAnchor = input.beforeAnchor ?? "";
const afterAnchor = input.afterAnchor ?? "";
if (!pageText.trim() && targetText.trim()) {
return {
text: targetText.trim(),
confidence: 0.1,
strategy: "target_text_on_empty_page",
};
}
if (!targetText.trim()) {
return {
text: pageText.slice(0, 240),
confidence: 0.15,
strategy: "missing_target_text_page_preview",
};
}
if (pageText.includes(targetText)) {
return {
text: targetText,
confidence: 1,
strategy: "exact",
};
}
const whitespaceMatch = findWhitespaceNormalizedOriginalSlice(pageText, targetText, false);
if (whitespaceMatch) {
return {
text: whitespaceMatch,
confidence: 0.92,
strategy: "whitespace_normalized",
};
}
const caseInsensitiveMatch = findWhitespaceNormalizedOriginalSlice(pageText, targetText, true);
if (caseInsensitiveMatch) {
return {
text: caseInsensitiveMatch,
confidence: 0.85,
strategy: "case_insensitive_whitespace_normalized",
};
}
const normalizedBefore = normalizeForPdfQuoteMatch(beforeAnchor);
const normalizedAfter = normalizeForPdfQuoteMatch(afterAnchor);
if (normalizedBefore || normalizedAfter) {
const normalizedPageText = normalizeForPdfQuoteMatch(pageText);
const normalizedTarget = normalizeForPdfQuoteMatch(targetText);
const beforeIndex = normalizedBefore ? normalizedPageText.indexOf(normalizedBefore) : -1;
const afterIndex = normalizedAfter ? normalizedPageText.indexOf(normalizedAfter) : -1;
const targetIndex = normalizedTarget ? normalizedPageText.indexOf(normalizedTarget) : -1;
if (targetIndex >= 0) {
const beforeOk = beforeIndex < 0 || beforeIndex <= targetIndex;
const afterOk = afterIndex < 0 || afterIndex >= targetIndex;
if (beforeOk && afterOk) {
const anchorAwareMatch = findWhitespaceNormalizedOriginalSlice(pageText, targetText, true);
if (anchorAwareMatch) {
return {
text: anchorAwareMatch,
confidence: 0.88,
strategy: "anchor_aware",
};
}
}
}
}
const sentenceWindow = extractSentenceWindowContaining(pageText, targetText);
if (sentenceWindow) {
return {
text: sentenceWindow,
confidence: 0.6,
strategy: "sentence_window",
};
}
return {
text: targetText.trim() || pageText.slice(0, 240),
confidence: 0.25,
strategy: "fallback_original_target_text",
};
}
export async function extractPdfText(buf: ArrayBuffer): Promise<string> {
try {
const surface = await buildPdfVirtualSurface(buf);
return surface.text;
} catch {
return "";
}
}
export async function generateDocx(
title: string,
sections: unknown[],
userId: string,
db: ReturnType<typeof createServerSupabase>,
options?: { landscape?: boolean; projectId?: string | null },
) {
try {
const {
Document, Paragraph, HeadingLevel, Packer,
Table, TableRow, TableCell, WidthType, BorderStyle,
TextRun, AlignmentType, PageOrientation, PageBreak,
} = await import("docx");
const FONT = "Times New Roman";
const SIZE = 22; // 11pt in half-points
type DocChild = InstanceType<typeof Paragraph> | InstanceType<typeof Table>;
const children: DocChild[] = [];
children.push(
new Paragraph({
heading: HeadingLevel.TITLE,
spacing: { after: 200 },
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: title.toUpperCase(), color: "000000", font: FONT, size: SIZE, bold: true })],
}),
);
const cellBorder = {
top: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" },
bottom: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" },
left: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" },
right: { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" },
};
const headingLevels = [
HeadingLevel.HEADING_1,
HeadingLevel.HEADING_2,
HeadingLevel.HEADING_3,
HeadingLevel.HEADING_4,
];
const counters = [0, 0, 0, 0];
for (const section of sections as {
heading?: string;
content?: string;
level?: number;
pageBreak?: boolean;
table?: { headers: string[]; rows: string[][] };
}[]) {
if (section.pageBreak) {
children.push(
new Paragraph({ children: [new PageBreak()] }),
);
}
if (section.heading) {
const idx = Math.min((section.level ?? 1) - 1, 3);
counters[idx]++;
for (let i = idx + 1; i < 4; i++) counters[i] = 0;
const prefix = counters.slice(0, idx + 1).join(".");
const headingText = `${prefix}. ${idx === 0 ? section.heading.toUpperCase() : section.heading}`;
children.push(
new Paragraph({
heading: headingLevels[idx],
spacing: { after: 160 },
children: [new TextRun({ text: headingText, color: "000000", font: FONT, size: SIZE, bold: true })],
}),
);
}
if (section.table) {
const { headers, rows } = section.table;
const colCount = headers.length;
const tableRows: InstanceType<typeof TableRow>[] = [];
// Header row
tableRows.push(
new TableRow({
tableHeader: true,
children: headers.map(
(h) =>
new TableCell({
borders: cellBorder,
shading: { fill: "F2F2F2" },
children: [
new Paragraph({
children: [new TextRun({ text: h, bold: true, font: FONT, size: SIZE })],
alignment: AlignmentType.LEFT,
}),
],
}),
),
}),
);
// Data rows — normalize each row to exactly colCount cells.
// LLMs occasionally emit malformed rows (extra fragments from
// stray delimiters, or short rows); padding/truncating here
// keeps the rendered table aligned to the headers.
for (const rawRow of rows) {
const row = Array.isArray(rawRow) ? rawRow : [];
const normalized: string[] = [];
for (let i = 0; i < colCount; i++) {
normalized.push(
typeof row[i] === "string" ? row[i] : "",
);
}
if (row.length !== colCount) {
console.warn(
`[generate_docx] row length ${row.length} != headers ${colCount}; normalized`,
);
}
tableRows.push(
new TableRow({
children: normalized.map(
(cell) =>
new TableCell({
borders: cellBorder,
children: [
new Paragraph({
children: [new TextRun({ text: cell, font: FONT, size: SIZE })],
}),
],
}),
),
}),
);
}
children.push(
new Table({
width: { size: 100, type: WidthType.PERCENTAGE },
rows: tableRows,
}),
);
children.push(new Paragraph({ text: "" }));
}
if (section.content) {
for (const line of section.content.split("\n")) {
const trimmed = line.trim();
if (!trimmed) continue;
const bulletMatch = trimmed.match(/^[-•*]\s+(.+)/);
if (bulletMatch) {
children.push(
new Paragraph({
bullet: { level: 0 },
spacing: { after: 120 },
children: [new TextRun({ text: bulletMatch[1], font: FONT, size: SIZE })],
}),
);
} else {
children.push(
new Paragraph({
spacing: { after: 120 },
children: [new TextRun({ text: trimmed, font: FONT, size: SIZE })],
}),
);
}
}
}
}
const pageSetup = options?.landscape
? { page: { size: { orientation: PageOrientation.LANDSCAPE } } }
: {};
const doc = new Document({ sections: [{ properties: pageSetup, children }] });
const buf = await Packer.toBuffer(doc);
const docId = crypto.randomUUID().replace(/-/g, "");
const safeTitle =
title
.replace(/[^a-zA-Z0-9 -]/g, "")
.trim()
.slice(0, 64) || "document";
const filename = `${safeTitle}.docx`;
const key = generatedDocKey(userId, docId, filename);
await uploadFile(
key,
buf.buffer as ArrayBuffer,
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
const downloadUrl = buildDownloadUrl(key, filename);
// Persist to DB so generated docs are first-class documents:
// openable in the DocPanel and editable via plan_comments_on_document. In
// project chats we attach to the project so it appears in the
// sidebar; in the general chat we leave project_id null and it
// stays a standalone document.
const { data: docRow, error: docErr } = await db
.from("documents")
.insert({
project_id: options?.projectId ?? null,
user_id: userId,
filename,
file_type: "docx",
size_bytes: buf.byteLength,
status: "ready",
})
.select("id")
.single();
if (docErr || !docRow) {
return {
error: `Failed to record generated document: ${docErr?.message ?? "unknown"}`,
};
}
const documentId = docRow.id as string;
const { data: versionRow, error: verErr } = await db
.from("document_versions")
.insert({
document_id: documentId,
storage_path: key,
source: "generated",
version_number: 1,
display_name: filename,
})
.select("id")
.single();
if (verErr || !versionRow) {
return {
error: `Failed to record generated document version: ${verErr?.message ?? "unknown"}`,
};
}
const versionId = versionRow.id as string;
await db
.from("documents")
.update({ current_version_id: versionId })
.eq("id", documentId);
return {
filename,
download_url: downloadUrl,
document_id: documentId,
version_id: versionId,
version_number: 1,
storage_path: key,
message: `Document '${filename}' has been generated successfully.`,
};
} catch (e) {
return { error: String(e) };
}
}
// ---------------------------------------------------------------------------
// Document version helpers (DOCX tracked-change editing)
// ---------------------------------------------------------------------------
/**
* Resolve the current .docx bytes for a document, preferring the active
* tracked-changes version if one exists, else the original upload.
*/
export async function loadCurrentVersionBytes(
documentId: string,
db: ReturnType<typeof createServerSupabase>,
): Promise<{
bytes: Buffer;
storage_path: string;
version_id: string;
version_number: number | null;
} | null> {
const active = await loadActiveVersion(documentId, db);
if (!active) return null;
const raw = await downloadFile(active.storage_path);
if (!raw) return null;
return {
bytes: Buffer.from(raw),
storage_path: active.storage_path,
version_id: active.id,
version_number: active.version_number,
};
}
/**
* Ensure the document has a document_versions row for the current upload.
* Called before writing the first 'assistant_edit' row so the history is
* complete. Idempotent.
*/
export async function runEditDocument(params: {
documentId: string;
userId: string;
edits?: EditInput[];
operations?: EditOperation[];
expectedSurfaceId?: string;
db: ReturnType<typeof createServerSupabase>;
/**
* If provided, append these edits to the existing turn-scoped version
* (overwrites the file at storagePath and reuses the document_versions
* row) instead of creating a new version. Used to collapse multiple
* plan_comments_on_document tool calls within a single assistant turn into one
* version.
*/
reuseVersion?: {
versionId: string;
versionNumber: number;
storagePath: string;
};
baseBytesOverride?: Buffer;
extraApplyReport?: any[];
extraApplySummary?: {
total: number;
applied: number;
failed: number;
failures: {
id: string;
target_text: string;
message: string;
paragraph_id: string | null;
}[];
};
}): Promise<
| {
ok: true;
version_id: string;
version_number: number;
storage_path: string;
download_url: string;
annotations: EditAnnotation[];
errors: { index: number; reason: string }[];
edit_verification?: EditVerification;
apply_report?: any[];
apply_summary?: {
total: number;
applied: number;
failed: number;
failures: {
id: string;
target_text: string;
message: string;
paragraph_id: string | null;
}[];
};
}
| { ok: false; error: string }
> {
const {
documentId,
userId,
edits,
operations,
expectedSurfaceId,
db,
reuseVersion,
baseBytesOverride,
extraApplyReport,
extraApplySummary,
} = params;
const { data: doc } = await db
.from("documents")
.select("id, filename")
.eq("id", documentId)
.single();
if (!doc) return { ok: false, error: "Document not found." };
const current = await loadCurrentVersionBytes(documentId, db);
if (!current) return { ok: false, error: "Could not load document bytes." };
const hasOperations = Array.isArray(operations) && operations.length > 0;
const hasLegacyEdits = Array.isArray(edits) && edits.length > 0;
if (!hasOperations && !hasLegacyEdits) {
return { ok: false, error: "No edit payload provided. Pass operations or edits." };
}
const baseBytes = baseBytesOverride ?? current.bytes;
const { bytes: editedBytes, changes, errors } = hasOperations
? await applyTrackedOperations(baseBytes, operations!, {
author: DOCX_AUTHOR_DEFAULT,
expectedSurfaceId,
})
: await applyTrackedEdits(baseBytes, edits!, { author: DOCX_AUTHOR_DEFAULT });
if (changes.length === 0) {
return {
ok: false,
error:
errors[0]?.reason ??
"No edits could be applied. Re-read the document and retry with a precise anchor target.",
};
}
const ab = editedBytes.buffer.slice(
editedBytes.byteOffset,
editedBytes.byteOffset + editedBytes.byteLength,
) as ArrayBuffer;
let versionRowId: string;
let newPath: string;
let nextVersionNumber: number;
if (reuseVersion) {
// Overwrite the existing turn version's file in place. The version
// row, version_number, and current_version_id all already point here.
newPath = reuseVersion.storagePath;
versionRowId = reuseVersion.versionId;
nextVersionNumber = reuseVersion.versionNumber;
await uploadFile(
newPath,
ab,
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
} else {
const versionId = crypto.randomUUID().replace(/-/g, "");
newPath = `documents/${userId}/${documentId}/edits/${versionId}.docx`;
await uploadFile(
newPath,
ab,
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
// Per-document sequential number for the new assistant_edit
// version. The counter spans upload + user_upload + assistant_edit
// so the original upload is V1 and the first assistant edit is V2.
const { data: maxRow } = await db
.from("document_versions")
.select("version_number")
.eq("document_id", documentId)
.in("source", ["upload", "user_upload", "assistant_edit"])
.order("version_number", { ascending: false, nullsFirst: false })
.limit(1)
.maybeSingle();
nextVersionNumber = ((maxRow?.version_number as number | null) ?? 1) + 1;
// Inherit the display name from the most recent prior version so
// user-applied renames carry forward through further edits. Falls
// back to the parent document's filename when no prior version has
// a display name (e.g. the first assistant edit of a pre-existing
// doc). We intentionally do NOT append "[Edited Vn]" — the version
// number is surfaced separately as a tag in the UI.
const { data: prevRow } = await db
.from("document_versions")
.select("display_name, created_at")
.eq("document_id", documentId)
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
const inheritedDisplayName =
(prevRow?.display_name as string | null) ??
(doc.filename as string | null) ??
null;
const { data: versionRow, error: verErr } = await db
.from("document_versions")
.insert({
document_id: documentId,
storage_path: newPath,
source: "assistant_edit",
version_number: nextVersionNumber,
display_name: inheritedDisplayName,
})
.select("id")
.single();
if (verErr || !versionRow) {
return { ok: false, error: "Failed to record document version." };
}
versionRowId = versionRow.id as string;
}
// Insert one row per change
const editRows = changes.map((c) => ({
document_id: documentId,
version_id: versionRowId,
change_id: c.id,
del_w_id: c.delId ?? null,
ins_w_id: c.insId ?? null,
deleted_text: c.deletedText,
inserted_text: c.insertedText,
context_before: c.contextBefore ?? "",
context_after: c.contextAfter ?? "",
operation: c.operation ?? null,
target_block_id: c.targetBlockId ?? null,
target_start: c.targetStart ?? null,
target_end: c.targetEnd ?? null,
base_surface_id: c.baseSurfaceId ?? expectedSurfaceId ?? null,
target_text_hash: c.targetTextHash ?? null,
status: "pending" as const,
}));
const { data: insertedEdits, error: editsErr } = await db
.from("document_edits")
.insert(editRows)
.select("id, change_id, del_w_id, ins_w_id, deleted_text, inserted_text, context_before, context_after, operation, target_block_id, target_start, target_end, base_surface_id, target_text_hash");
if (editsErr || !insertedEdits) {
return { ok: false, error: "Failed to record edits." };
}
await db
.from("documents")
.update({ current_version_id: versionRowId })
.eq("id", documentId);
const annotations: EditAnnotation[] = insertedEdits.map((r: {
id: string;
change_id: string;
deleted_text: string;
inserted_text: string;
context_before: string | null;
context_after: string | null;
operation: string | null;
target_block_id: string | null;
target_start: number | null;
target_end: number | null;
base_surface_id: string | null;
target_text_hash: string | null;
}) => {
const src = changes.find((c) => c.id === r.change_id);
return {
kind: "edit",
edit_id: r.id,
document_id: documentId,
version_id: versionRowId,
version_number: nextVersionNumber,
change_id: r.change_id,
del_w_id: src?.delId,
ins_w_id: src?.insId,
deleted_text: r.deleted_text ?? "",
inserted_text: r.inserted_text ?? "",
context_before: r.context_before ?? "",
context_after: r.context_after ?? "",
operation: r.operation ?? src?.operation ?? null,
target_block_id: r.target_block_id ?? src?.targetBlockId ?? null,
target_start: r.target_start ?? src?.targetStart ?? null,
target_end: r.target_end ?? src?.targetEnd ?? null,
base_surface_id: r.base_surface_id ?? src?.baseSurfaceId ?? expectedSurfaceId ?? null,
target_text_hash: r.target_text_hash ?? src?.targetTextHash ?? null,
reason: src?.reason,
status: "pending",
};
});
// --- Net-new tracked-change verification ---
let editVerification: EditVerification | undefined;
try {
editVerification = await verifyDocxEdits(baseBytes, editedBytes, changes);
} catch (err) {
console.warn("[runEditDocument] edit verification failed:", err);
}
// Persistent, non-expiring permalink. The backend streams fresh bytes
// on each request, so this URL stays valid as long as the file exists.
const permalink = buildDownloadUrl(newPath, doc.filename as string);
return {
ok: true,
version_id: versionRowId,
version_number: nextVersionNumber,
storage_path: newPath,
download_url: permalink,
annotations,
errors,
edit_verification: editVerification,
apply_report: extraApplyReport,
apply_summary: extraApplySummary,
};
}
// ---------------------------------------------------------------------------
// Net-new tracked-change verification
// ---------------------------------------------------------------------------
interface EditVerification {
claimed: number;
net_new_inserted: number;
matched: { text: string }[];
unmatched: { text: string; reason: string }[];
}
/**
* Compares the original and edited document bytes to find net-new w:ins/w:del
* tags (ignoring pre-existing ones), then cross-references with the list of
* changes the model claimed to insert. Returns a verification summary.
*/
async function verifyDocxEdits(
originalBytes: Buffer,
editedBytes: Buffer,
claimedChanges: AppliedChange[],
): Promise<EditVerification> {
// Extract all tracked change IDs from both documents
const originalIds = await extractTrackedChangeIds(originalBytes);
const editedIds = await extractTrackedChangeIds(editedBytes);
// Build set of pre-existing IDs
const preExisting = new Set(originalIds.map((i) => i.w_id));
// Net-new = IDs in edited that weren't in original
const netNew = editedIds.filter((i) => !preExisting.has(i.w_id));
const netNewIdSet = new Set(netNew.map((i) => i.w_id));
const matched: { text: string }[] = [];
const unmatched: { text: string; reason: string }[] = [];
for (const change of claimedChanges) {
// A change is "matched" if any of its IDs (insId, delId) appear in net-new
const changeIds = [change.insId, change.delId].filter(
(id): id is string => typeof id === "string" && id.length > 0,
);
const isPresent = changeIds.some((id) => netNewIdSet.has(id));
const label =
change.insertedText?.slice(0, 60) ||
change.deletedText?.slice(0, 60) ||
change.id;
if (isPresent) {
matched.push({ text: label });
} else if (changeIds.length === 0) {
// No IDs to check — likely a formatting-only change
matched.push({ text: label });
} else {
unmatched.push({
text: label,
reason: "Tracked change IDs not found in net-new changes of the output document",
});
}
}
return {
claimed: claimedChanges.length,
net_new_inserted: netNew.length,
matched,
unmatched,
};
}
// ---------------------------------------------------------------------------
// Post-write verification for comment-only batches
// ---------------------------------------------------------------------------
export interface CommentBatchVerification {
ok: boolean;
original_block_count: number;
commented_block_count: number;
body_text_unchanged: boolean;
comments_in_xml: number;
expected_applied: number;
missing_from_xml: number;
paragraph_mismatches: string[];
}
export async function verifyCommentBatchPostWrite(params: {
originalBytes: Buffer;
commentedBytes: Buffer;
expectedAppliedCount: number;
appliedReports: any[];
}): Promise<CommentBatchVerification> {
const { originalBytes, commentedBytes, expectedAppliedCount, appliedReports } = params;
try {
const originalSurface = await buildDocxEditSurface(originalBytes);
const commentedSurface = await buildDocxEditSurface(commentedBytes);
const bodyTextUnchanged =
originalSurface.blocks.map((b) => b.text).join("\n") ===
commentedSurface.blocks.map((b) => b.text).join("\n");
// Count comments in word/comments.xml
let commentsInXml = 0;
try {
const JSZip = (await import("jszip")).default;
const zip = await JSZip.loadAsync(commentedBytes);
const commentsXml = zip.file("word/comments.xml");
if (commentsXml) {
const xml = await commentsXml.async("string");
const parser = new (await import("fast-xml-parser")).XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_",
});
const parsed = parser.parse(xml);
const comments = parsed?.["w:comments"]?.["w:comment"];
if (Array.isArray(comments)) {
commentsInXml = comments.length;
} else if (comments) {
commentsInXml = 1;
}
}
} catch {
// If we can't parse comments.xml, fall through
}
const appliedIds = new Set(
appliedReports
.filter((r) => r.status === "applied")
.map((r) => r.model_block_id ?? r.paragraph_id)
.filter(Boolean),
);
const paragraphMismatches: string[] = [];
for (const blockId of appliedIds) {
const origAnchor = originalSurface.anchors.find((a) => a.block_id === blockId);
const newAnchor = commentedSurface.anchors.find((a) => a.block_id === blockId);
if (origAnchor && newAnchor) {
if (origAnchor.text_hash !== newAnchor.text_hash) {
paragraphMismatches.push(`${blockId}: text_hash changed`);
}
}
}
const missingFromXml = Math.max(0, expectedAppliedCount - commentsInXml);
return {
ok: bodyTextUnchanged && missingFromXml === 0 && paragraphMismatches.length === 0,
original_block_count: originalSurface.blocks.length,
commented_block_count: commentedSurface.blocks.length,
body_text_unchanged: bodyTextUnchanged,
comments_in_xml: commentsInXml,
expected_applied: expectedAppliedCount,
missing_from_xml: missingFromXml,
paragraph_mismatches: paragraphMismatches,
};
} catch (err) {
console.warn("[verifyCommentBatchPostWrite] verification failed:", err);
return {
ok: false,
original_block_count: -1,
commented_block_count: -1,
body_text_unchanged: false,
comments_in_xml: -1,
expected_applied: expectedAppliedCount,
missing_from_xml: -1,
paragraph_mismatches: [String(err)],
};
}
}
// ---------------------------------------------------------------------------
// Pydantic-style validation for comment_on_document arguments
// ---------------------------------------------------------------------------
interface CommentArgValidation {
valid: boolean;
errors: string[];
validated: {
doc_id: string;
block_id: string;
target_text: string;
anchor_mode: "paragraph" | "exact";
before_anchor?: string;
after_anchor?: string;
comment: string;
comment_key?: string;
};
}
function sanitizeCommentArgs(args: Record<string, unknown>): Record<string, unknown> {
const sanitized = { ...args };
const rawDocId = String(sanitized.doc_id ?? "").trim();
const docMatch = rawDocId.match(/^(doc-\d+)/);
if (docMatch && rawDocId !== docMatch[1]) {
sanitized.doc_id = docMatch[1];
console.warn(
`[comment_on_document] sanitized malformed doc_id "${rawDocId}" -> "${docMatch[1]}"`
);
}
const rawBlockId = String(sanitized.block_id ?? "").trim();
const blockMatch = rawBlockId.match(/^((?:P_\d+|_?BMK_[PT]_[a-z0-9]+))/);
if (blockMatch) {
const cleanedBlockId = normalizeModelBlockId(blockMatch[1]);
if (rawBlockId !== cleanedBlockId) {
sanitized.block_id = cleanedBlockId;
console.warn(
`[comment_on_document] sanitized malformed block_id "${rawBlockId}" -> "${cleanedBlockId}"`
);
}
}
return sanitized;
}
function validateCommentArgs(
args: Record<string, unknown>,
docStore: DocStore,
): CommentArgValidation {
const errors: string[] = [];
const validated: Partial<CommentArgValidation["validated"]> = {};
// 1. doc_id validation
const rawDocId = args.doc_id;
if (rawDocId === undefined || rawDocId === null || rawDocId === "") {
errors.push(`doc_id: Field is required.`);
} else {
const docIdStr = String(rawDocId).trim();
const availableLabels = Array.from(docStore.keys());
if (!docStore.has(docIdStr)) {
errors.push(
`doc_id: Value "${docIdStr}" is invalid. ` +
`Must be one of the available document labels: ${JSON.stringify(availableLabels)}.`
);
} else {
validated.doc_id = docIdStr;
}
}
// 2. block_id validation
const rawBlockId = args.block_id;
if (rawBlockId === undefined || rawBlockId === null || rawBlockId === "") {
errors.push(`block_id: Field is required.`);
} else {
const blockIdStr = normalizeModelBlockId(String(rawBlockId).trim());
if (!isValidDocxModelBlockId(blockIdStr)) {
errors.push(
`block_id: Value "${String(rawBlockId).trim()}" is invalid. ` +
`Must match P_NNNN or BMK_P_/BMK_T_ bookmark format (e.g., "P_0001", "BMK_P_a3x7k").`
);
} else {
validated.block_id = blockIdStr;
}
}
// 3. anchor_mode validation
const rawAnchorMode = args.anchor_mode;
const anchorMode = rawAnchorMode === undefined || rawAnchorMode === null || rawAnchorMode === ""
? "paragraph"
: String(rawAnchorMode).trim().toLowerCase();
if (anchorMode !== "paragraph" && anchorMode !== "exact") {
errors.push(`anchor_mode: Value "${anchorMode}" is invalid. Must be "paragraph" or "exact".`);
} else {
validated.anchor_mode = anchorMode;
}
// 4. target_text validation
const rawTargetText = args.target_text;
if (rawTargetText === undefined || rawTargetText === null || rawTargetText === "") {
if (anchorMode === "exact") {
errors.push(`target_text: Field is required when anchor_mode is "exact".`);
} else {
validated.target_text = "";
}
} else {
const targetTextStr = String(rawTargetText).trim();
if (targetTextStr.length === 0 && anchorMode === "exact") {
errors.push(`target_text: Cannot be an empty string.`);
} else {
validated.target_text = targetTextStr;
}
}
// 5. comment validation
const rawComment = args.comment;
if (rawComment === undefined || rawComment === null || rawComment === "") {
errors.push(`comment: Field is required.`);
} else {
const commentStr = String(rawComment).trim();
if (commentStr.length === 0) {
errors.push(`comment: Cannot be an empty string.`);
} else {
validated.comment = commentStr;
}
}
// 6. optional anchor fields
validated.before_anchor = args.before_anchor ? String(args.before_anchor).trim() : "";
validated.after_anchor = args.after_anchor ? String(args.after_anchor).trim() : "";
// 7. optional comment_key
const rawCommentKey = args.comment_key;
if (rawCommentKey !== undefined && rawCommentKey !== null && rawCommentKey !== "") {
validated.comment_key = String(rawCommentKey).trim();
}
return {
valid: errors.length === 0,
errors,
validated: validated as CommentArgValidation["validated"],
};
}
export async function runCommentDocument(params: {
userId: string;
documentId: string;
args: { block_id: string; target_text: string; anchor_mode: "paragraph" | "exact"; before_anchor: string; after_anchor: string; comment: string; comment_key?: string };
db: ReturnType<typeof createServerSupabase>;
docIndex: DocIndex;
trace?: RunTree | null;
}): Promise<{
ok: boolean;
version_id?: string;
version_number?: number;
download_url?: string;
apply_report?: any[];
error?: string;
}> {
const { userId, documentId, args, db, docIndex, trace } = params;
const docInfo = Object.values(docIndex).find(d => d.document_id === documentId);
if (!docInfo) return { ok: false, error: "Document not found." };
if (docInfo.file_type !== "docx") return { ok: false, error: "comment_on_document only supports .docx files." };
const current = await loadCurrentVersionBytes(documentId, db);
if (!current) return { ok: false, error: "Could not load document bytes." };
// Build canonical anchor map for the current document bytes.
const currentSurface = await buildDocxEditSurface(current.bytes, {
versionId: current.version_id,
});
const anchorMap = new Map(currentSurface.anchors.map((a) => [a.block_id, a]));
const anchor = anchorMap.get(args.block_id);
if (!anchor) {
return {
ok: false,
error: `Unknown or stale block_id ${args.block_id} for the current document surface. Call read_document again and retry with a block_id from the latest surface.`,
};
}
try {
const pythonUrl = process.env.PYTHON_SERVICE_URL || "http://localhost:8000";
const response = await fetch(`${pythonUrl}/api/apply-comments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-user-id": userId,
...traceHeaders(trace),
},
body: JSON.stringify({
document_bytes_b64: Buffer.from(current.bytes).toString("base64"),
document_name: docInfo.filename,
findings: [
{
id: `chat_comment_${Date.now()}`,
model_block_id: args.block_id,
comment_key: args.comment_key,
risk_level: "info",
clause_type: "Comment",
issue: "Assistant Feedback",
comment: args.comment,
review_reasoning: { clause_section: "N/A", violated_standard: "N/A", zamil_exposure: "Manual chat comment not from playbook review" },
target_text: args.target_text,
anchor_mode: args.anchor_mode,
start_paragraph_id: args.block_id,
end_paragraph_id: args.block_id,
start_anchor: args.target_text ? args.target_text.slice(0, 220) : "",
end_anchor: args.target_text ? args.target_text.slice(-220) : "",
before_anchor: args.before_anchor || "",
after_anchor: args.after_anchor || "",
confidence: 1.0,
stable_id: anchor.stable_id,
native_para_id: anchor.native_para_id,
text_hash: anchor.text_hash,
xml_path: anchor.xml_path,
bookmark_name: anchor.bookmark_name ?? null,
},
],
}),
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
return { ok: false, error: errData.detail || `Python service error: ${response.statusText}` };
}
const data = await response.json();
const commentedBytesB64 =
typeof data.commented_bytes_b64 === "string"
? data.commented_bytes_b64
: typeof data.document_bytes_b64 === "string"
? data.document_bytes_b64
: "";
if (!commentedBytesB64) {
return {
ok: false,
error: "Python service did not return commented document bytes.",
apply_report: Array.isArray(data.apply_report) ? data.apply_report : undefined,
};
}
const commentedBytes = Buffer.from(commentedBytesB64, "base64");
// Save as a new version
const versionId = crypto.randomUUID().replace(/-/g, "");
const newPath = `documents/${userId}/${documentId}/comments/${versionId}.docx`;
const exact = commentedBytes.buffer.slice(
commentedBytes.byteOffset,
commentedBytes.byteOffset + commentedBytes.byteLength,
) as ArrayBuffer;
await uploadFile(newPath, exact, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
const { data: maxRow } = await db
.from("document_versions")
.select("version_number")
.eq("document_id", documentId)
.order("version_number", { ascending: false })
.limit(1)
.maybeSingle();
const nextVersionNumber = ((maxRow?.version_number as number | null) ?? 1) + 1;
const { data: versionRow, error: verErr } = await db
.from("document_versions")
.insert({
document_id: documentId,
storage_path: newPath,
source: "assistant_edit", // Treat as an edit for UI purposes
version_number: nextVersionNumber,
display_name: docInfo.filename,
})
.select("id")
.single();
if (verErr || !versionRow) {
return { ok: false, error: "Failed to record document version." };
}
await db
.from("documents")
.update({ current_version_id: versionRow.id })
.eq("id", documentId);
return {
ok: true,
version_id: versionRow.id,
version_number: nextVersionNumber,
download_url: buildDownloadUrl(newPath, docInfo.filename),
apply_report: data.apply_report,
};
} catch (err) {
console.error("[runCommentDocument] error:", err);
return { ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
// ---------------------------------------------------------------------------
// Tool dispatch
// ---------------------------------------------------------------------------
async function readDocumentContent(
docLabel: string,
docStore: DocStore,
write: (s: string) => void,
docIndex?: DocIndex,
db?: ReturnType<typeof createServerSupabase>,
opts?: { emitEvents?: boolean },
anchorManifestState?: AnchorManifestState,
trace?: RunTree | null,
): Promise<string> {
const emitEvents = opts?.emitEvents ?? true;
console.log(`[read_document] called with docLabel="${docLabel}"`);
const docInfo = docStore.get(docLabel);
if (!docInfo) {
console.log(
`[read_document] MISS — docLabel "${docLabel}" not in docStore. Known labels:`,
Array.from(docStore.keys()),
);
return "Document not found.";
}
console.log(
`[read_document] docInfo: filename="${docInfo.filename}", file_type="${docInfo.file_type}", storage_path="${docInfo.storage_path}"`,
);
const documentId = docIndex?.[docLabel]?.document_id;
const emitDocRead = () => {
if (!emitEvents) return;
write(
`data: ${JSON.stringify({
type: "doc_read",
filename: docInfo.filename,
document_id: documentId,
})}\n\n`,
);
};
if (emitEvents)
write(
`data: ${JSON.stringify({
type: "doc_read_start",
filename: docInfo.filename,
document_id: documentId,
})}\n\n`,
);
try {
// Prefer the current tracked-changes version (if any) so read_document
// reflects accepted/pending edits rather than the original upload.
let raw: ArrayBuffer | null = null;
let sourcePath = docInfo.storage_path;
if (documentId && db) {
const current = await loadCurrentVersionBytes(documentId, db);
if (current) {
raw = current.bytes.buffer.slice(
current.bytes.byteOffset,
current.bytes.byteOffset + current.bytes.byteLength,
) as ArrayBuffer;
sourcePath = current.storage_path;
console.log(
`[read_document] using current version path="${sourcePath}" (bytes=${raw.byteLength})`,
);
} else {
console.log(
`[read_document] loadCurrentVersionBytes returned null for documentId="${documentId}", falling back to original storage_path`,
);
}
}
if (!raw) {
raw = await downloadFile(docInfo.storage_path);
if (raw) {
console.log(
`[read_document] fallback download from storage_path="${docInfo.storage_path}" (bytes=${raw.byteLength})`,
);
}
}
if (!raw) {
console.log(
`[read_document] FAILED to download any bytes for docLabel="${docLabel}" (tried path="${sourcePath}")`,
);
emitDocRead();
return "Document could not be read.";
}
// Log the first 8 bytes so we can identify real file format regardless
// of the declared file_type. Valid .docx starts with "PK\x03\x04"
// (zip). Legacy .doc starts with "\xD0\xCF\x11\xE0" (OLE/CFB).
// %PDF-1 is a PDF even if mislabeled. Truncated uploads show as all-zero.
{
const head = Buffer.from(raw).subarray(0, 8);
const hex = head.toString("hex");
const ascii = head
.toString("binary")
.replace(/[^\x20-\x7e]/g, ".");
console.log(
`[read_document] magic bytes hex=${hex} ascii="${ascii}" for filename="${docInfo.filename}"`,
);
}
let text: string;
if (docInfo.file_type === "pdf") {
const surface = await buildPdfVirtualSurface(raw, {
documentId,
versionId: docIndex?.[docLabel]?.version_id ?? null,
});
rememberPdfAnchorManifest(
anchorManifestState,
docLabel,
documentId,
surface,
);
text = surface.text;
console.log(
`[read_document] pdf virtual surface constructed length=${text.length} pages=${surface.pages.length} for filename="${docInfo.filename}"`,
);
} else if (docInfo.file_type === "docx") {
// For DOCX we expose an anchor-aware edit surface so the model can
// target edits deterministically via block IDs.
const surface = await buildDocxEditSurface(Buffer.from(raw), {
versionId: docIndex?.[docLabel]?.version_id ?? null,
});
// Cache canonical anchors so comment_on_document can validate
// block_id against the exact surface shown to the model.
rememberAnchorManifest(
anchorManifestState,
docLabel,
documentId,
surface,
);
text = renderDocxEditSurfaceForModel(surface);
console.log(
`[read_document] docx rendered edit surface length=${text.length} blocks=${surface.blocks.length} ` +
`navigation_regions=${surface.navigationRegions.length} ` +
`navigation_blocks=${surface.anchors.filter((anchor) => !anchor.anchor_eligible).length} ` +
`for filename="${docInfo.filename}"`,
);
if (!text) {
console.log(
`[read_document] docx surface renderer returned empty, falling back to extractDocxBodyText for filename="${docInfo.filename}"`,
);
text = await extractDocxBodyText(Buffer.from(raw));
console.log(
`[read_document] docx extractDocxBodyText fallback length=${text.length} for filename="${docInfo.filename}"`,
);
}
// Log exact model-visible surface to LangSmith for debugging anchor issues
if (trace) {
const surfaceTrace = buildDocumentSurfaceTracePayload({
docLabel,
documentId,
filename: docInfo.filename,
surfaceId: surface.surfaceId,
text,
});
const surfTrace = createTraceChild(trace, {
name: "read_document.surface",
run_type: "chain",
inputs: surfaceTrace,
tags: ["document_surface_for_model"],
});
void endTrace(surfTrace, {
outputs: {
char_count: text.length,
block_count: surface.anchors.length,
navigation_region_count: surface.navigationRegions.length,
navigation_block_count: surface.anchors.filter(
(anchor) => !anchor.anchor_eligible,
).length,
surface_id: surface.surfaceId,
},
});
}
} else {
console.log(
`[read_document] unknown file_type="${docInfo.file_type}" for filename="${docInfo.filename}", trying mammoth`,
);
const mammoth = await import("mammoth");
const result = await mammoth.extractRawText({
buffer: Buffer.from(raw),
});
text = result.value;
console.log(
`[read_document] mammoth length=${text.length} for filename="${docInfo.filename}"`,
);
}
console.log(
`[read_document] DONE filename="${docInfo.filename}" finalTextLength=${text.length}`,
);
emitDocRead();
return text;
} catch (err) {
console.log(
`[read_document] THREW for docLabel="${docLabel}" filename="${docInfo.filename}":`,
err,
);
if (emitEvents)
write(`data: ${JSON.stringify({ type: "doc_read", filename: docInfo.filename })}\n\n`);
return "Document could not be read.";
}
}
async function readDocumentContentOncePerTurn(params: {
docLabel: string;
docStore: DocStore;
write: (s: string) => void;
docIndex?: DocIndex;
db?: ReturnType<typeof createServerSupabase>;
opts?: { emitEvents?: boolean };
anchorManifestState?: AnchorManifestState;
trace?: RunTree | null;
turnDocumentState: TurnDocumentState;
}): Promise<string> {
const {
docLabel,
docStore,
write,
docIndex,
db,
opts,
anchorManifestState,
trace,
turnDocumentState,
} = params;
const existing = turnDocumentState.readsByDocLabel.get(docLabel);
if (existing) {
turnDocumentState.duplicateReadCount += 1;
return JSON.stringify({
ok: true,
already_read: true,
doc_id: docLabel,
filename: existing.filename,
char_count: existing.charCount,
read_serial: existing.readSerial,
surface_id: existing.surfaceId ?? null,
message:
"This document has already been read in this assistant turn. Use the prior read_document/fetch_documents result already in context. For exact phrase lookup, call find_in_document instead of reading the full document again.",
});
}
const content = await readDocumentContent(
docLabel,
docStore,
write,
docIndex,
db,
opts,
anchorManifestState,
trace,
);
const docInfo = docStore.get(docLabel);
if (docInfo) {
const manifest = anchorManifestState?.byDocLabel.get(docLabel);
turnDocumentState.readsByDocLabel.set(docLabel, {
docLabel,
filename: docInfo.filename,
content,
charCount: content.length,
readSerial: manifest?.readSerial ?? turnDocumentState.readsByDocLabel.size + 1,
surfaceId: manifest?.surfaceId,
});
}
return content;
}
/**
* Build a whitespace-collapsed, lowercased copy of `text`, plus a map from
* each character index in the normalized form back to the corresponding
* index in the original text. Used by `findInDocumentContent` so matches
* are tolerant of case + whitespace variance but can still return the
* exact original excerpt.
*/
function normalizeWithMap(text: string): { norm: string; origIdx: number[] } {
const norm: string[] = [];
const origIdx: number[] = [];
let prevSpace = false;
for (let i = 0; i < text.length; i++) {
const ch = text[i];
if (/\s/.test(ch)) {
if (!prevSpace) {
norm.push(" ");
origIdx.push(i);
prevSpace = true;
}
} else {
norm.push(ch.toLowerCase());
origIdx.push(i);
prevSpace = false;
}
}
return { norm: norm.join(""), origIdx };
}
function normalizeQuery(q: string): string {
return q.trim().replace(/\s+/g, " ").toLowerCase();
}
/**
* Ctrl+F helper. Returns a JSON-serializable result with up to `maxResults`
* hits, each containing the original-text excerpt plus surrounding context.
*/
async function findInDocumentContent(params: {
docLabel: string;
query: string;
maxResults?: number;
contextChars?: number;
anchorEligibleOnly?: boolean;
docStore: DocStore;
write: (s: string) => void;
docIndex?: DocIndex;
db?: ReturnType<typeof createServerSupabase>;
anchorManifestState?: AnchorManifestState;
}): Promise<string> {
const {
docLabel,
query,
maxResults: requestedMaxResults,
contextChars: requestedContextChars,
anchorEligibleOnly = true,
docStore,
write,
docIndex,
db,
anchorManifestState,
} = params;
const maxResults = clampInteger(requestedMaxResults, {
min: 1,
max: 20,
fallback: 10,
});
const contextChars = clampInteger(requestedContextChars, {
min: 20,
max: 300,
fallback: 120,
});
if (!query || !query.trim()) {
return JSON.stringify({ ok: false, error: "Empty query." });
}
const docInfo = docStore.get(docLabel);
if (!docInfo) {
return JSON.stringify({
ok: false,
error: `Document '${docLabel}' not found.`,
});
}
// Announce the search to the UI, then reuse readDocumentContent for its
// fallbacks — but suppress its own doc_read events so the user only sees
// the doc_find block (not a competing doc_read block for the same op).
write(
`data: ${JSON.stringify({
type: "doc_find_start",
filename: docInfo.filename,
query,
})}\n\n`,
);
const documentId = docIndex?.[docLabel]?.document_id;
let raw: ArrayBuffer | null = null;
if (documentId && db) {
const current = await loadCurrentVersionBytes(documentId, db);
if (current) {
raw = current.bytes.buffer.slice(
current.bytes.byteOffset,
current.bytes.byteOffset + current.bytes.byteLength,
) as ArrayBuffer;
}
}
if (!raw) {
raw = await downloadFile(docInfo.storage_path);
}
if (!raw) {
write(
`data: ${JSON.stringify({
type: "doc_find",
filename: docInfo.filename,
query,
total_matches: 0,
})}\n\n`,
);
return JSON.stringify({
ok: false,
filename: docInfo.filename,
error: "Document could not be read.",
});
}
if (docInfo.file_type === "docx") {
const surface = await buildDocxEditSurface(Buffer.from(raw), {
versionId: docIndex?.[docLabel]?.version_id ?? null,
});
rememberAnchorManifest(
anchorManifestState,
docLabel,
documentId,
surface,
);
const search = findInDocxEditSurface({
surface,
query,
maxResults,
contextChars,
anchorEligibleOnly,
});
write(
`data: ${JSON.stringify({
type: "doc_find",
filename: docInfo.filename,
query,
total_matches: search.totalMatches,
excluded_navigation_matches: search.excludedNavigationMatches,
})}\n\n`,
);
return JSON.stringify({
ok: true,
filename: docInfo.filename,
query,
surface_id: surface.surfaceId,
total_matches: search.totalMatches,
excluded_navigation_matches: search.excludedNavigationMatches,
returned: search.hits.length,
truncated: search.totalMatches > search.hits.length,
hits: search.hits,
});
}
const text = await readDocumentContent(
docLabel,
docStore,
write,
docIndex,
db,
{ emitEvents: false },
anchorManifestState,
);
if (!text || text === "Document could not be read.") {
write(
`data: ${JSON.stringify({
type: "doc_find",
filename: docInfo.filename,
query,
total_matches: 0,
})}\n\n`,
);
return JSON.stringify({
ok: false,
filename: docInfo.filename,
error: "Document could not be read.",
});
}
const { norm, origIdx } = normalizeWithMap(text);
const needle = normalizeQuery(query);
if (!needle) {
return JSON.stringify({ ok: false, error: "Empty query after normalization." });
}
type Hit = {
index: number;
excerpt: string;
context: string;
};
const hits: Hit[] = [];
let from = 0;
while (from <= norm.length - needle.length && hits.length < maxResults) {
const pos = norm.indexOf(needle, from);
if (pos < 0) break;
const endNormPos = pos + needle.length;
const origStart = origIdx[pos] ?? 0;
const origEnd =
endNormPos - 1 < origIdx.length
? origIdx[endNormPos - 1] + 1
: text.length;
const ctxStart = Math.max(0, origStart - contextChars);
const ctxEnd = Math.min(text.length, origEnd + contextChars);
hits.push({
index: hits.length,
excerpt: text.slice(origStart, origEnd),
context:
(ctxStart > 0 ? "…" : "") +
text.slice(ctxStart, ctxEnd).replace(/\s+/g, " ").trim() +
(ctxEnd < text.length ? "…" : ""),
});
from = pos + Math.max(1, needle.length);
}
// Count total occurrences beyond the cap so the model knows whether to narrow the query.
let totalMatches = hits.length;
if (hits.length >= maxResults) {
let probe = from;
while (probe <= norm.length - needle.length) {
const pos = norm.indexOf(needle, probe);
if (pos < 0) break;
totalMatches++;
probe = pos + Math.max(1, needle.length);
}
}
write(
`data: ${JSON.stringify({
type: "doc_find",
filename: docInfo.filename,
query,
total_matches: totalMatches,
})}\n\n`,
);
return JSON.stringify({
ok: true,
filename: docInfo.filename,
query,
total_matches: totalMatches,
returned: hits.length,
truncated: totalMatches > hits.length,
hits,
});
}
export type DocEditedResult = {
filename: string;
document_id: string;
version_id: string;
version_number: number | null;
download_url: string;
annotations: EditAnnotation[];
apply_report?: any[];
apply_summary?: {
total: number;
applied: number;
failed: number;
failures: {
id: string;
target_text: string;
message: string;
paragraph_id: string | null;
}[];
};
edit_verification?: unknown;
};
export type TurnEditState = Map<
string,
{ versionId: string; versionNumber: number; storagePath: string }
>;
type CommentAttemptState = Map<
string,
{
attempts: number;
applied: boolean;
staleFailures: number;
latestFailureCode?: string;
latestMessage?: string;
latestFailureReadSerial?: number;
toolCallIds: string[];
}
>;
// ---------------------------------------------------------------------------
// Best-of-both-worlds comment planning pipeline types
// ---------------------------------------------------------------------------
export type CommentPlanIntent = {
block_id: string;
comment: string;
status?: string;
reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string };
target_text?: string;
anchor_mode?: "paragraph" | "exact";
before_anchor?: string;
after_anchor?: string;
comment_key?: string;
type?: "COMMENT" | "REWRITE_PARAGRAPH" | "INSERT" | "DELETE_PARAGRAPH";
new_text?: string;
insert_text?: string;
position?: string;
rewrite_mode?: string;
old_span?: string;
new_span?: string;
};
type AnchorCorrectionCandidate = {
block_id: string;
text: string;
score: number;
match_reason: string;
};
type CommentPlanSource =
| "plan_comments_on_document"
| "correct_comment_anchors"
| "fix_comment_findings";
type RepairKind =
| "anchor"
| "evidence"
| "operation_schema"
| "base_schema"
| "red_zone"
| "unknown";
type TerminalFallback = "COMMENT";
type PlannedCommentItem = CommentPlanIntent & {
anchor?: CanonicalAnchor;
failureCode?: string;
relatedFailureCodes?: string[];
message?: string;
repairKind?: RepairKind;
repairable?: boolean;
repairAttempts?: number;
terminalFallback?: TerminalFallback;
rawIntent?: unknown;
downgradeToComment?: boolean;
fallbackApplied?: boolean;
anchorCandidates?: AnchorCorrectionCandidate[];
anchorRejectionReason?: string;
excludedNavigationRegion?: {
start_block_id: string;
end_block_id: string;
reason: string;
};
anchorRedirectedFrom?: string;
anchorRedirectedTo?: string;
anchorRedirectReason?: string;
};
type FindingCorrection = {
comment_key: string;
status: "corrected" | "unresolved" | "downgrade_to_comment";
reason?: string;
new_block_id?: string;
corrected_comment?: string;
corrected_status?: "FAIL" | "MISSING" | "RISK" | "REVIEW" | "ESCALATE";
corrected_reasoning?: {
clause_section: string;
violated_standard: string;
zamil_exposure: string;
};
corrected_target_text?: string;
corrected_type?: "COMMENT" | "REWRITE_PARAGRAPH" | "INSERT" | "DELETE_PARAGRAPH";
corrected_new_text?: string;
corrected_insert_text?: string;
corrected_position?: "before" | "after";
corrected_rewrite_mode?: "paragraph" | "span";
corrected_old_span?: string;
corrected_new_span?: string;
};
type PlanValidationResult =
| { ok: true; item: PlannedCommentItem }
| {
ok: false;
item: PlannedCommentItem;
failureCode: string;
relatedFailureCodes?: string[];
message: string;
repairable: boolean;
repairKind: RepairKind;
terminalFallback?: TerminalFallback;
};
const MAX_FINDING_REPAIR_ATTEMPTS = 1;
type PendingCommentPlan = {
docLabel: string;
documentId: string;
docInfo: NonNullable<ReturnType<DocStore["get"]>>;
indexed: DocIndex[string];
current: NonNullable<Awaited<ReturnType<typeof loadCurrentVersionBytes>>>;
currentSurface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
manifest: AnchorManifest | undefined;
valid: PlannedCommentItem[];
needsCorrection: PlannedCommentItem[];
failedPermanently: PlannedCommentItem[];
ignoredPassReports: Record<string, any>[];
};
export type PendingCommentPlanState = Map<string, PendingCommentPlan>;
// Generic writer input so both legacy batch and planned batch can share runCommentDocumentBatch
type WriterCommentItem = {
id: string;
args: {
doc_id: string;
block_id: string;
comment: string;
target_text: string;
anchor_mode: "paragraph" | "exact";
before_anchor?: string;
after_anchor?: string;
comment_key: string;
type?: string;
new_text?: string;
insert_text?: string;
position?: string;
rewrite_mode?: string;
old_span?: string;
new_span?: string;
};
anchor: CanonicalAnchor;
reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string };
anchor_text?: string;
section_heading?: string | null;
finding_status?: WorkflowFindingStatus;
document_order?: number;
source?: CommentPlanSource;
};
type CommentVerificationRow = {
tool_call_id: string;
ok: boolean;
doc_id?: string;
document_id?: string;
block_id?: string;
comment_key?: string;
status?: string;
failure_code?: string;
message?: string;
model_block_id?: string;
writer_paragraph_id?: string;
comment?: string;
reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string };
anchor_text?: string;
section_heading?: string | null;
op_type?: string;
bookmark_name?: string | null;
finding_status?: WorkflowFindingStatus;
document_order?: number;
apply_status?: WorkflowApplyStatus;
source?: CommentPlanSource;
amended_wording?: string;
repair_kind?: RepairKind;
repair_attempts?: number;
fallback_applied?: boolean;
downgrade_to_comment?: boolean;
original_type?: string;
final_type?: string;
};
function stringOrUndefined(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value : undefined;
}
function stringOrNull(value: unknown): string | null {
return typeof value === "string" && value.trim() ? value : null;
}
function numberOrUndefined(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value)
? value
: undefined;
}
function workflowReasoningOrUndefined(
value: unknown,
): CommentVerificationRow["reasoning"] | undefined {
if (!value || typeof value !== "object") return undefined;
const raw = value as Record<string, unknown>;
if (!("clause_section" in raw)) return undefined;
return {
clause_section: String(raw.clause_section ?? "").trim(),
violated_standard: String(raw.violated_standard ?? "").trim(),
zamil_exposure: String(raw.zamil_exposure ?? "").trim(),
};
}
function workflowFindingStatusOrUndefined(
value: unknown,
): WorkflowFindingStatus | undefined {
return value === "FAIL" ||
value === "MISSING" ||
value === "RISK" ||
value === "REVIEW" ||
value === "ESCALATE"
? value
: undefined;
}
function workflowSourceOrUndefined(
value: unknown,
): CommentVerificationRow["source"] | undefined {
return value === "plan_comments_on_document" ||
value === "correct_comment_anchors" ||
value === "fix_comment_findings"
? value
: undefined;
}
function applyStatusFromVerificationRow(
row: CommentVerificationRow,
): WorkflowApplyStatus {
return row.apply_status ??
(row.status === "ignored_pass"
? "ignored_pass"
: row.status === "applied"
? "applied"
: row.failure_code
? "writer_failed"
: "not_applied");
}
function mergeVerificationMetadata(
existing: CommentVerificationRow,
incoming: CommentVerificationRow,
): CommentVerificationRow {
return {
...incoming,
status: incoming.status ?? existing.status,
failure_code: incoming.failure_code ?? existing.failure_code,
message: incoming.message ?? existing.message,
comment: incoming.comment ?? existing.comment,
reasoning: incoming.reasoning ?? existing.reasoning,
anchor_text: incoming.anchor_text ?? existing.anchor_text,
section_heading: incoming.section_heading ?? existing.section_heading,
op_type: incoming.op_type ?? existing.op_type,
bookmark_name: incoming.bookmark_name ?? existing.bookmark_name,
finding_status: incoming.finding_status ?? existing.finding_status,
document_order: incoming.document_order ?? existing.document_order,
apply_status: incoming.apply_status ?? existing.apply_status,
source: incoming.source ?? existing.source,
amended_wording: incoming.amended_wording ?? existing.amended_wording,
};
}
function dedupeCommentVerificationRows(
rows: CommentVerificationRow[],
): CommentVerificationRow[] {
return Array.from(
rows.reduce((acc, row) => {
const key =
row.comment_key ||
`${row.model_block_id ?? row.block_id ?? "unknown"}:${row.tool_call_id}`;
const existing = acc.get(key);
if (!existing) {
acc.set(key, row);
return acc;
}
const existingStatus = applyStatusFromVerificationRow(existing);
const incomingStatus = applyStatusFromVerificationRow(row);
if (shouldReplace(existingStatus, incomingStatus)) {
acc.set(key, mergeVerificationMetadata(existing, row));
} else if (existingStatus === incomingStatus) {
acc.set(key, mergeVerificationMetadata(row, existing));
}
return acc;
}, new Map<string, CommentVerificationRow>())
.values(),
);
}
type ValidatedCommentArgs = CommentArgValidation["validated"] & {
comment_key: string;
};
type PreparedCommentCall = {
tc: ToolCall;
docLabel: string;
docInfo: NonNullable<ReturnType<DocStore["get"]>>;
indexed: DocIndex[string];
args: ValidatedCommentArgs;
op_type?: string;
};
export type DocCreatedResult = {
filename: string;
download_url: string;
document_id?: string;
version_id?: string;
version_number?: number | null;
};
export type DocReplicatedResult = {
/** Filename of the source document being copied. */
filename: string;
/** How many copies were produced in this single tool call. */
count: number;
/** One entry per new copy. */
copies: {
new_filename: string;
document_id: string;
version_id: string;
}[];
};
type ToolResultRow = {
tool_call_id: string;
content?: unknown;
};
type ToolFailureSummary = {
tool_call_id: string;
tool_name: string;
error: string;
doc_id?: string;
document_id?: string;
};
function parseToolResultContent(content: unknown): unknown {
if (typeof content !== "string") return content;
try {
return JSON.parse(content);
} catch {
return content;
}
}
function summarizeToolFailure(
row: ToolResultRow,
call: ToolCall | undefined,
): ToolFailureSummary | null {
const parsed = parseToolResultContent(row.content);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return null;
}
const obj = parsed as Record<string, unknown>;
const error =
typeof obj.error === "string" && obj.error.trim()
? obj.error.trim()
: obj.ok === false
? "Tool returned ok=false without an error message."
: null;
if (!error) return null;
return {
tool_call_id: row.tool_call_id,
tool_name: call?.function.name ?? "unknown",
error,
doc_id: typeof obj.doc_id === "string" ? obj.doc_id : undefined,
document_id:
typeof obj.document_id === "string" ? obj.document_id : undefined,
};
}
function summarizeMissingToolResult(call: ToolCall): ToolFailureSummary {
return {
tool_call_id: call.id,
tool_name: call.function.name,
error: `Tool '${call.function.name}' did not produce a tool_result.`,
};
}
const STALE_ANCHOR_FAILURE_CODES = new Set([
"anchor_stale",
"anchor_stale_requires_document_refresh",
]);
export function makeCommentKey(args: CommentArgValidation["validated"]): string {
if (args.comment_key) {
return `${args.doc_id}:${args.comment_key}`;
}
return `${args.doc_id}:${args.block_id}:${args.comment
.slice(0, 120)
.replace(/\s+/g, " ")
.trim()}`;
}
const ACTIONABLE_STATUSES = new Set(["FAIL", "MISSING", "RISK", "REVIEW", "ESCALATE"]);
export function isPassItem(intent: CommentPlanIntent): boolean {
const status = intent.status?.trim().toUpperCase();
if (status === "PASS") return true;
const commentPrefix = intent.comment?.trim().toUpperCase();
if (commentPrefix?.startsWith("[PASS]")) return true;
return false;
}
export function isActionableStatus(intent: CommentPlanIntent): boolean {
const status = intent.status?.trim().toUpperCase();
if (!status) return false;
return ACTIONABLE_STATUSES.has(status);
}
export function buildIgnoredPassReport(intent: CommentPlanIntent, index: number): Record<string, unknown> {
return {
id: `ignored-pass-${index}`,
status: "ignored_pass",
failure_code: null,
ignored_reason: "pass_status",
doc_id: intent.block_id ? undefined : null,
block_id: intent.block_id ?? null,
comment_key: intent.comment_key ?? null,
comment: intent.comment ?? null,
reasoning: intent.reasoning ?? null,
};
}
function makeCommentFailureReport(params: {
id: string;
commentKey?: string;
blockId?: string;
status?: string;
failureCode: string;
message: string;
anchor?: CanonicalAnchor | null;
opType?: string;
}): Record<string, unknown> {
const { id, commentKey, blockId, status = "not_applied", failureCode, message, anchor, opType } = params;
return {
id,
status,
message,
paragraph_id: blockId ?? null,
match_count: 0,
failure_code: failureCode,
model_block_id: blockId ?? null,
writer_paragraph_id: null,
native_para_id: anchor?.native_para_id ?? null,
stable_id: anchor?.stable_id ?? null,
xml_path: anchor?.xml_path ?? null,
comment_key: commentKey ?? null,
op_type: opType ?? null,
bookmark_name: anchor?.bookmark_name ?? null,
};
}
function verificationRowFromReport(params: {
toolCallId: string;
docId?: string;
documentId?: string;
blockId?: string;
commentKey?: string;
report: Record<string, any>;
comment?: string;
reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string };
anchorText?: string;
sectionHeading?: string | null;
opType?: string;
bookmarkName?: string | null;
}): CommentVerificationRow {
const { toolCallId, docId, documentId, blockId, commentKey, report, comment, reasoning, anchorText, sectionHeading, opType, bookmarkName } = params;
const status = typeof report.status === "string" ? report.status : "not_applied";
return {
tool_call_id: toolCallId,
ok: status === "applied",
doc_id: docId,
document_id: documentId,
block_id: blockId,
comment_key:
typeof report.comment_key === "string" && report.comment_key
? report.comment_key
: commentKey,
status,
failure_code:
typeof report.failure_code === "string" ? report.failure_code : undefined,
message: typeof report.message === "string" ? report.message : undefined,
model_block_id:
typeof report.model_block_id === "string" ? report.model_block_id : undefined,
writer_paragraph_id:
typeof report.writer_paragraph_id === "string"
? report.writer_paragraph_id
: undefined,
comment,
reasoning,
anchor_text: anchorText,
section_heading: sectionHeading,
op_type: opType ?? (typeof report.op_type === "string" ? report.op_type : undefined),
bookmark_name: bookmarkName ?? (typeof report.bookmark_name === "string" ? report.bookmark_name : null) ?? null,
};
}
export function buildApplySummary(applyReport: any[]): DocEditedResult["apply_summary"] {
const uniqueReports = Array.from(
applyReport
.reduce((acc: Map<string, any>, report: any) => {
const key =
report?.comment_key ||
`${report?.model_block_id ?? report?.paragraph_id ?? "unknown"}:${report?.id ?? "unknown"}`;
const existing = acc.get(key);
if (!existing || existing.status !== "applied") {
acc.set(key, report);
}
return acc;
}, new Map<string, any>())
.values(),
);
const actionableReports = uniqueReports.filter((r: any) => r.status !== "ignored_pass");
const applied = actionableReports.filter((r: any) => r.status === "applied").length;
const failed = actionableReports.length - applied;
return {
total: actionableReports.length,
applied,
failed,
failures: actionableReports
.filter((r: any) => r.status !== "applied")
.map((r: any) => ({
id: String(r.id ?? ""),
target_text: String(r.message ?? "").slice(0, 80),
message: String(r.message || "Comment was not inserted."),
paragraph_id: r.model_block_id ?? r.paragraph_id ?? null,
})),
};
}
function recordCommentAttempt(params: {
state?: CommentAttemptState;
commentKey?: string;
toolCallId: string;
report: Record<string, any>;
readSerial?: number;
}): void {
const { state, commentKey, toolCallId, report, readSerial } = params;
if (!state || !commentKey) return;
const existing = state.get(commentKey) ?? {
attempts: 0,
applied: false,
staleFailures: 0,
toolCallIds: [],
};
existing.attempts += 1;
existing.toolCallIds.push(toolCallId);
if (report.status === "applied") {
existing.applied = true;
existing.latestFailureCode = undefined;
existing.latestMessage = String(report.message ?? "");
} else {
const failureCode = String(report.failure_code ?? "not_applied");
existing.latestFailureCode = failureCode;
existing.latestMessage = String(report.message ?? "");
if (STALE_ANCHOR_FAILURE_CODES.has(failureCode)) {
existing.staleFailures += 1;
existing.latestFailureReadSerial = readSerial;
}
}
state.set(commentKey, existing);
}
function validatePreparedCommentAnchor(params: {
prepared: PreparedCommentCall;
manifest: AnchorManifest | undefined;
currentSurface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
commentAttemptState?: CommentAttemptState;
}): { ok: true; anchor: CanonicalAnchor; readSerial: number } | { ok: false; report: Record<string, unknown>; readSerial?: number } {
const { prepared, manifest, currentSurface, commentAttemptState } = params;
const commentKey = prepared.args.comment_key;
const opType = prepared.op_type;
const previousAttempt = commentAttemptState?.get(commentKey);
if (
previousAttempt &&
!previousAttempt.applied &&
STALE_ANCHOR_FAILURE_CODES.has(previousAttempt.latestFailureCode ?? "") &&
previousAttempt.staleFailures >= 2
) {
return {
ok: false,
readSerial: manifest?.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "DUPLICATE_COMMENT_RETRY_BLOCKED",
message:
`This same comment has already failed twice with anchor_stale at ${prepared.args.block_id}. ` +
"It will not be retried again in this turn.",
anchor: manifest?.anchors.get(prepared.args.block_id),
opType,
}),
};
}
if (
previousAttempt &&
!previousAttempt.applied &&
STALE_ANCHOR_FAILURE_CODES.has(previousAttempt.latestFailureCode ?? "") &&
previousAttempt.staleFailures >= 1 &&
(!manifest ||
(previousAttempt.latestFailureReadSerial !== undefined &&
manifest.readSerial <= previousAttempt.latestFailureReadSerial))
) {
return {
ok: false,
readSerial: manifest?.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_stale_requires_document_refresh",
message:
`This same comment already failed with anchor_stale at ${prepared.args.block_id}. ` +
"Call read_document and choose a fresh block_id before retrying.",
anchor: manifest?.anchors.get(prepared.args.block_id),
opType,
}),
};
}
if (!manifest) {
return {
ok: false,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_revision_stale",
message:
"No current anchor manifest exists for this document in the turn. " +
"Call read_document before inserting comments.",
opType,
}),
};
}
if (manifest.docRevisionId !== currentSurface.surfaceId) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_revision_stale",
message:
"The document revision has changed since the model read it. " +
"Call read_document again and retry with a block_id from the latest surface.",
anchor: manifest.anchors.get(prepared.args.block_id),
opType,
}),
};
}
const manifestAnchor = manifest.anchors.get(prepared.args.block_id);
if (!manifestAnchor) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_not_found",
message:
`Block ${prepared.args.block_id} is not in the latest document surface shown to the model. ` +
"Call read_document and choose a valid block_id.",
opType,
}),
};
}
if (!manifestAnchor.commentable) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_uncommentable",
message:
`Block ${prepared.args.block_id} has no visible non-deleted text and cannot be safely commented.`,
anchor: manifestAnchor,
opType,
}),
};
}
const currentAnchor = currentSurface.anchors.find((a) => a.block_id === prepared.args.block_id);
if (!currentAnchor) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_stale_requires_document_refresh",
message:
`Block ${prepared.args.block_id} no longer exists in the current document. ` +
"Call read_document and retry with a fresh block_id.",
anchor: manifestAnchor,
opType,
}),
};
}
if (!manifestAnchor.anchor_eligible || !currentAnchor.anchor_eligible) {
const region = navigationRegionForBlock(
currentSurface,
prepared.args.block_id,
);
const eligibleCandidates = buildEligibleAnchorCandidates({
intent: {
block_id: prepared.args.block_id,
comment: prepared.args.comment,
target_text: prepared.args.target_text,
},
surface: currentSurface,
rejectedAnchor: currentAnchor,
});
return {
ok: false,
readSerial: manifest.readSerial,
report: {
...makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "ANCHOR_NOT_ELIGIBLE",
message:
`Block ${prepared.args.block_id} is in a navigation region and cannot be used for comments or edits.`,
anchor: currentAnchor,
opType,
}),
anchor_rejection_reason:
currentAnchor.anchor_rejection_reason ??
manifestAnchor.anchor_rejection_reason ??
"navigation",
excluded_navigation_region: region,
eligible_candidates: eligibleCandidates,
},
};
}
const identityMismatch =
(manifestAnchor.native_para_id || currentAnchor.native_para_id) &&
manifestAnchor.native_para_id !== currentAnchor.native_para_id
? "native_para_id"
: manifestAnchor.stable_id !== currentAnchor.stable_id
? "stable_id"
: manifestAnchor.xml_path !== currentAnchor.xml_path
? "xml_path"
: "";
if (identityMismatch) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_identity_mismatch",
message:
`Block ${prepared.args.block_id} has a ${identityMismatch} mismatch between the read surface and current document. ` +
"Call read_document and retry with a fresh block_id.",
anchor: currentAnchor,
opType,
}),
};
}
const textMismatch =
manifestAnchor.text_hash !== currentAnchor.text_hash ||
manifestAnchor.prev_text_hash !== currentAnchor.prev_text_hash ||
manifestAnchor.next_text_hash !== currentAnchor.next_text_hash;
if (textMismatch) {
return {
ok: false,
readSerial: manifest.readSerial,
report: {
...makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_stale_requires_document_refresh",
message:
`Block ${prepared.args.block_id} text or neighbor hash changed since the model read it. ` +
"Call read_document and retry with a fresh block_id.",
anchor: currentAnchor,
opType,
}),
expected_text_hash: manifestAnchor.text_hash,
actual_text_hash: currentAnchor.text_hash,
},
};
}
if (!currentAnchor.commentable) {
return {
ok: false,
readSerial: manifest.readSerial,
report: makeCommentFailureReport({
id: prepared.tc.id,
commentKey,
blockId: prepared.args.block_id,
failureCode: "anchor_uncommentable",
message:
`Block ${prepared.args.block_id} has no visible non-deleted text in the current document.`,
anchor: currentAnchor,
opType,
}),
};
}
return { ok: true, anchor: currentAnchor, readSerial: manifest.readSerial };
}
// ---------------------------------------------------------------------------
// Plan / correct comment pipeline (best-of-both-worlds)
// ---------------------------------------------------------------------------
function normalizeAnchorCandidateText(value: string): string {
return decodeXmlEntities(value)
.toLowerCase()
.replace(/\u00a0/g, " ")
.replace(/[^\p{L}\p{N}]+/gu, " ")
.replace(/\s+/g, " ")
.trim();
}
function buildEligibleAnchorCandidates(params: {
intent: CommentPlanIntent;
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
rejectedAnchor: CanonicalAnchor;
limit?: number;
}): AnchorCorrectionCandidate[] {
const { intent, surface, rejectedAnchor, limit = 3 } = params;
const rejectedIndex = surface.anchors.findIndex(
(anchor) => anchor.block_id === rejectedAnchor.block_id,
);
const targetText = normalizeAnchorCandidateText(intent.target_text ?? "");
const navigationLabel = normalizeAnchorCandidateText(
rejectedAnchor.navigation_label ?? rejectedAnchor.text_preview,
);
const clauseSection = normalizeAnchorCandidateText(
intent.reasoning?.clause_section ?? "",
);
const scored = surface.anchors
.map((anchor, index) => {
if (!anchor.anchor_eligible || !anchor.commentable) return null;
const fullText = surface.blocks[index]?.text ?? anchor.text_preview;
const text = normalizeAnchorCandidateText(fullText);
let score = 0;
const reasons: string[] = [];
if (targetText.length >= 8 && text.includes(targetText)) {
score += 120;
reasons.push("supporting quote match");
}
if (navigationLabel.length >= 3) {
if (text === navigationLabel) {
score += 100;
reasons.push("exact normalized title match");
} else if (
text.startsWith(navigationLabel) ||
navigationLabel.startsWith(text)
) {
score += 70;
reasons.push("title prefix match");
} else if (
text.includes(navigationLabel) ||
navigationLabel.includes(text)
) {
score += 45;
reasons.push("partial title match");
}
}
if (
clauseSection.length >= 5 &&
(text.includes(clauseSection) || clauseSection.includes(text))
) {
score += 35;
reasons.push("reasoning section match");
}
if (rejectedIndex >= 0) {
const distance = Math.abs(index - rejectedIndex);
score += Math.max(0, 12 - Math.min(12, distance));
if (index > rejectedIndex && distance <= 12) {
reasons.push("near rejected navigation entry");
}
}
return {
block_id: anchor.block_id,
text: anchor.text_preview.slice(0, 200),
score,
match_reason: reasons.join(", ") || "nearest eligible body block",
index,
};
})
.filter(
(
candidate,
): candidate is AnchorCorrectionCandidate & { index: number } =>
candidate !== null,
)
.sort((a, b) => b.score - a.score || a.index - b.index);
const matched = scored.filter((candidate) => candidate.score >= 20);
const candidates = matched.length > 0 ? matched : scored;
return candidates.slice(0, limit).map(({ index: _index, ...candidate }) => candidate);
}
function navigationRegionForBlock(
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>,
blockId: string,
): {
start_block_id: string;
end_block_id: string;
reason: string;
} | undefined {
const blockIndex = surface.anchors.findIndex(
(anchor) => anchor.block_id === blockId,
);
if (blockIndex < 0) return undefined;
const region = surface.navigationRegions.find(
(candidate) =>
blockIndex >= candidate.startIndex && blockIndex <= candidate.endIndex,
);
if (!region) return undefined;
return {
start_block_id: region.startBlockId,
end_block_id: region.endBlockId,
reason: region.reason,
};
}
type AnchorRedirect = {
from: string;
to: string;
reason: string;
};
function tryHighConfidenceNavigationRedirect(params: {
intent: CommentPlanIntent;
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
}): AnchorRedirect | null {
const { intent, surface } = params;
const rejectedIndex = surface.anchors.findIndex(
(anchor) => anchor.block_id === intent.block_id,
);
if (rejectedIndex < 0) return null;
const rejectedAnchor = surface.anchors[rejectedIndex];
if (rejectedAnchor.anchor_eligible) return null;
const title = normalizeAnchorCandidateText(
rejectedAnchor.navigation_label ?? rejectedAnchor.text_preview,
);
const targetText = normalizeAnchorCandidateText(intent.target_text ?? "");
if (title.length < 3 || targetText.length < 12) return null;
const matches = surface.anchors
.map((anchor, index) => {
if (!anchor.anchor_eligible || !anchor.commentable) return null;
const blockText = normalizeAnchorCandidateText(
surface.blocks[index]?.text ?? anchor.text_preview,
);
const titleMatches =
blockText === title ||
blockText.startsWith(`${title} `);
const quoteMatches = blockText.includes(targetText);
return titleMatches && quoteMatches ? anchor : null;
})
.filter((anchor): anchor is CanonicalAnchor => anchor !== null);
if (matches.length !== 1) return null;
return {
from: rejectedAnchor.block_id,
to: matches[0].block_id,
reason: "unique eligible title and supporting-quote match",
};
}
function isLikelyHeadingBlock(params: {
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
blockIndex: number;
normalizedBlockText: string;
}): boolean {
const { surface, blockIndex, normalizedBlockText } = params;
const anchor = surface.anchors[blockIndex];
const style = `${anchor?.style_id ?? ""} ${anchor?.style_name ?? ""}`.toLowerCase();
const rawText = surface.blocks[blockIndex]?.text.trim() ?? "";
const headingShape =
/^(section|clause|article|schedule|exhibit|annex|appendix)\s+\d+/i.test(
rawText,
) ||
/^\d+(?:\.\d+)*\s+\S+/.test(rawText);
const styledHeading =
style.includes("heading") ||
style.includes("title") ||
style.includes("subtitle");
if (!headingShape && !styledHeading) return false;
const nextSubstantive = surface.anchors
.slice(blockIndex + 1)
.findIndex((candidate) => candidate.anchor_eligible && candidate.commentable);
if (nextSubstantive < 0) return false;
const actualNextIndex = blockIndex + 1 + nextSubstantive;
const nextText = normalizeAnchorCandidateText(
surface.blocks[actualNextIndex]?.text ?? "",
);
return normalizedBlockText.length <= 180 && nextText.length >= 80;
}
export function validateFindingTargetEvidence(params: {
intent: CommentPlanIntent;
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
anchor: CanonicalAnchor;
}):
| { ok: true }
| { ok: false; failureCode: string; message: string } {
const { intent, surface, anchor } = params;
if (intent.status?.trim().toUpperCase() === "MISSING") return { ok: true };
const targetText = normalizeAnchorCandidateText(intent.target_text ?? "");
if (targetText.length < 12) {
return {
ok: false,
failureCode: intent.target_text?.trim()
? "target_text_too_short"
: "schema_missing_target_text",
message:
"Non-MISSING findings require target_text containing at least 12 meaningful characters quoted from the selected block.",
};
}
const blockIndex = surface.anchors.findIndex(
(candidate) => candidate.block_id === anchor.block_id,
);
const blockText = normalizeAnchorCandidateText(
blockIndex >= 0 ? surface.blocks[blockIndex]?.text ?? "" : "",
);
if (!blockText.includes(targetText)) {
return {
ok: false,
failureCode: "target_text_not_in_block",
message:
`target_text does not occur in block ${anchor.block_id}. ` +
"Provide corrected_target_text from that block or choose a different eligible block_id.",
};
}
if (
blockText === targetText &&
isLikelyHeadingBlock({
surface,
blockIndex,
normalizedBlockText: blockText,
})
) {
return {
ok: false,
failureCode: "target_text_heading_only",
message:
`target_text for block ${anchor.block_id} contains only a section heading. ` +
"Anchor the finding on the substantive clause paragraph, or use MISSING when the clause is absent.",
};
}
return { ok: true };
}
export function validateCommentPlanIntent(params: {
intent: CommentPlanIntent;
manifest: AnchorManifest | undefined;
currentSurface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
}):
| { ok: true; anchor: CanonicalAnchor; readSerial: number }
| {
ok: false;
failureCode: string;
message: string;
readSerial?: number;
candidates?: AnchorCorrectionCandidate[];
rejectionReason?: string;
excludedNavigationRegion?: {
start_block_id: string;
end_block_id: string;
reason: string;
};
} {
const { intent, manifest, currentSurface } = params;
const blockId = intent.block_id;
if (!manifest) {
return {
ok: false,
failureCode: "anchor_revision_stale",
message: "No current anchor manifest exists for this document in the turn. Call read_document before inserting comments.",
};
}
if (manifest.docRevisionId !== currentSurface.surfaceId) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_revision_stale",
message: "The document revision has changed since the model read it. Call read_document again and retry with a block_id from the latest surface.",
};
}
const manifestAnchor = manifest.anchors.get(blockId);
if (!manifestAnchor) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_not_found",
message: `Block ${blockId} is not in the latest document surface shown to the model. Call read_document and choose a valid block_id.`,
};
}
if (!manifestAnchor.commentable) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_uncommentable",
message: `Block ${blockId} has no visible non-deleted text and cannot be safely commented.`,
};
}
const currentAnchor = currentSurface.anchors.find((a) => a.block_id === blockId);
if (!currentAnchor) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_stale_requires_document_refresh",
message: `Block ${blockId} no longer exists in the current document. Call read_document and retry with a fresh block_id.`,
};
}
if (!manifestAnchor.anchor_eligible || !currentAnchor.anchor_eligible) {
const candidates = buildEligibleAnchorCandidates({
intent,
surface: currentSurface,
rejectedAnchor: currentAnchor,
});
const candidateIds = candidates.map((candidate) => candidate.block_id).join(", ");
const excludedNavigationRegion = navigationRegionForBlock(
currentSurface,
blockId,
);
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "ANCHOR_NOT_ELIGIBLE",
message:
`Block ${blockId} is in a navigation region` +
`${currentAnchor.anchor_rejection_reason ? ` (${currentAnchor.anchor_rejection_reason})` : ""} ` +
"and cannot be used for comments or edits. " +
(candidateIds
? `Choose an eligible body block, such as: ${candidateIds}.`
: "Use find_in_document to choose an eligible body block."),
candidates,
rejectionReason:
currentAnchor.anchor_rejection_reason ??
manifestAnchor.anchor_rejection_reason ??
"navigation",
excludedNavigationRegion,
};
}
const identityMismatch =
(manifestAnchor.native_para_id || currentAnchor.native_para_id) &&
manifestAnchor.native_para_id !== currentAnchor.native_para_id
? "native_para_id"
: manifestAnchor.stable_id !== currentAnchor.stable_id
? "stable_id"
: manifestAnchor.xml_path !== currentAnchor.xml_path
? "xml_path"
: "";
if (identityMismatch) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_identity_mismatch",
message: `Block ${blockId} has a ${identityMismatch} mismatch between the read surface and current document. Call read_document and retry with a fresh block_id.`,
};
}
const textMismatch =
manifestAnchor.text_hash !== currentAnchor.text_hash ||
manifestAnchor.prev_text_hash !== currentAnchor.prev_text_hash ||
manifestAnchor.next_text_hash !== currentAnchor.next_text_hash;
if (textMismatch) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_stale_requires_document_refresh",
message: `Block ${blockId} text or neighbor hash changed since the model read it. Call read_document and retry with a fresh block_id.`,
};
}
if (!currentAnchor.commentable) {
return {
ok: false,
readSerial: manifest.readSerial,
failureCode: "anchor_uncommentable",
message: `Block ${blockId} has no visible non-deleted text in the current document.`,
};
}
return { ok: true, anchor: currentAnchor, readSerial: manifest.readSerial };
}
export function buildCandidateContext(params: {
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
targetBlockId: string;
windowSize?: number;
}): Array<{ block_id: string; text: string }> {
const { surface, targetBlockId, windowSize = 5 } = params;
const idx = surface.anchors.findIndex((a) => a.block_id === targetBlockId);
if (idx < 0) return [];
const start = Math.max(0, idx - windowSize);
const end = Math.min(surface.anchors.length, idx + windowSize + 1);
return surface.anchors
.slice(start, end)
.filter((anchor) => anchor.anchor_eligible && anchor.commentable)
.map((anchor) => ({
block_id: anchor.block_id,
text: anchor.text_preview.slice(0, 200),
}));
}
function normalizeFindingType(value: unknown): CommentPlanIntent["type"] {
const raw = typeof value === "string" ? value.trim() : "";
if (
raw === "COMMENT" ||
raw === "REWRITE_PARAGRAPH" ||
raw === "INSERT" ||
raw === "DELETE_PARAGRAPH"
) {
return raw;
}
return undefined;
}
function isTrackedChangeOperation(intent: CommentPlanIntent | PlannedCommentItem): boolean {
return intent.type === "REWRITE_PARAGRAPH" || intent.type === "INSERT" || intent.type === "DELETE_PARAGRAPH";
}
function downgradeEditIntentToComment(intent: PlannedCommentItem, reason: string): PlannedCommentItem {
const existingComment = intent.comment?.trim() || "[REVIEW] Issue preserved as a comment.";
const systemNote =
`[System note: The model attempted an edit operation, but the document was reviewed in comments-only mode. ` +
`Preserved as a comment instead. Reason: ${reason}]`;
const next: PlannedCommentItem = {
...intent,
type: "COMMENT",
comment: existingComment.includes("[System note:")
? existingComment
: `${existingComment}\n\n${systemNote}`,
downgradeToComment: true,
fallbackApplied: true,
};
delete (next as any).new_text;
delete (next as any).insert_text;
delete (next as any).position;
delete (next as any).rewrite_mode;
delete (next as any).old_span;
delete (next as any).new_span;
return next;
}
function isRepairableAnchorFailure(failureCode: string): boolean {
return failureCode === "ANCHOR_NOT_ELIGIBLE" ||
failureCode === "anchor_not_found" ||
failureCode === "anchor_invalid_format" ||
failureCode === "anchor_uncommentable" ||
failureCode === "anchor_revision_stale" ||
failureCode === "anchor_identity_mismatch" ||
failureCode === "anchor_stale_requires_document_refresh";
}
function hasValidReasoning(
reasoning: CommentPlanIntent["reasoning"],
): reasoning is NonNullable<CommentPlanIntent["reasoning"]> {
return !!reasoning &&
typeof reasoning === "object" &&
!!reasoning.clause_section?.trim() &&
!!reasoning.violated_standard?.trim() &&
!!reasoning.zamil_exposure?.trim();
}
function terminalFailure(
intent: CommentPlanIntent,
failureCode: string,
message: string,
repairKind: RepairKind = "base_schema",
): PlanValidationResult {
return {
ok: false,
item: {
...intent,
failureCode,
message,
repairKind,
repairable: false,
},
failureCode,
message,
repairable: false,
repairKind,
};
}
function repairableFailure(params: {
intent: CommentPlanIntent;
failureCode: string;
relatedFailureCodes?: string[];
message: string;
repairKind: RepairKind;
terminalFallback?: TerminalFallback;
anchor?: CanonicalAnchor;
anchorCandidates?: AnchorCorrectionCandidate[];
anchorRejectionReason?: string;
excludedNavigationRegion?: {
start_block_id: string;
end_block_id: string;
reason: string;
};
anchorRedirect?: AnchorRedirect;
}): PlanValidationResult {
const item: PlannedCommentItem = {
...params.intent,
anchor: params.anchor,
failureCode: params.failureCode,
relatedFailureCodes: params.relatedFailureCodes,
message: params.message,
repairKind: params.repairKind,
repairable: true,
repairAttempts: 0,
terminalFallback: params.terminalFallback,
anchorCandidates: params.anchorCandidates,
anchorRejectionReason: params.anchorRejectionReason,
excludedNavigationRegion: params.excludedNavigationRegion,
anchorRedirectedFrom: params.anchorRedirect?.from,
anchorRedirectedTo: params.anchorRedirect?.to,
anchorRedirectReason: params.anchorRedirect?.reason,
};
return {
ok: false,
item,
failureCode: params.failureCode,
relatedFailureCodes: params.relatedFailureCodes,
message: params.message,
repairable: true,
repairKind: params.repairKind,
terminalFallback: params.terminalFallback,
};
}
function validateBaseFindingFields(intent: CommentPlanIntent): PlanValidationResult | null {
const missingFields: string[] = [];
if (!intent.comment_key?.trim()) missingFields.push("comment_key");
if (!intent.block_id?.trim()) missingFields.push("block_id");
if (!intent.comment?.trim()) missingFields.push("comment");
if (!intent.status || !isActionableStatus(intent)) missingFields.push("status");
if (!hasValidReasoning(intent.reasoning)) missingFields.push("reasoning");
if (missingFields.length === 0) return null;
if (!intent.comment_key?.trim()) {
return terminalFailure(
intent,
"schema_missing_comment_key",
`Missing required field(s): ${missingFields.join(", ")}. Cannot repair without comment_key because corrections are matched by comment_key.`,
"base_schema",
);
}
const hasEnoughIdentityForRepair = !!intent.comment_key?.trim() &&
(!!intent.block_id?.trim() || !!intent.comment?.trim());
if (!hasEnoughIdentityForRepair) {
return terminalFailure(
intent,
"schema_insufficient_identity",
`Missing required field(s): ${missingFields.join(", ")}. The item does not have enough identity to repair safely.`,
"base_schema",
);
}
const primaryFailure = missingFields.includes("block_id")
? "schema_missing_block_id"
: missingFields.includes("comment")
? "schema_missing_comment"
: missingFields.includes("status")
? "schema_missing_or_invalid_status"
: "schema_missing_or_invalid_reasoning";
const repairKind: RepairKind = missingFields.includes("block_id")
? "anchor"
: "base_schema";
return repairableFailure({
intent,
failureCode: primaryFailure,
relatedFailureCodes: missingFields.map((field) => `schema_missing_${field}`),
message:
`Missing or invalid required field(s): ${missingFields.join(", ")}. ` +
"Provide corrected_comment, corrected_status, corrected_reasoning, and/or new_block_id as applicable.",
repairKind,
});
}
function validateOperationFields(intent: CommentPlanIntent): PlanValidationResult | null {
const opType = intent.type ?? "COMMENT";
if (
opType !== "COMMENT" &&
opType !== "REWRITE_PARAGRAPH" &&
opType !== "INSERT" &&
opType !== "DELETE_PARAGRAPH"
) {
return repairableFailure({
intent,
failureCode: "schema_invalid_operation_type",
message:
`Unsupported operation type '${opType}'. ` +
"Provide corrected_type as COMMENT, REWRITE_PARAGRAPH, INSERT, or DELETE_PARAGRAPH.",
repairKind: "operation_schema",
terminalFallback: "COMMENT",
});
}
if (opType === "REWRITE_PARAGRAPH") {
if (intent.rewrite_mode === "span") {
if (!intent.old_span?.trim() || !intent.new_span?.trim()) {
return repairableFailure({
intent,
failureCode: "schema_missing_rewrite_span",
relatedFailureCodes: [
!intent.old_span?.trim() ? "schema_missing_old_span" : "",
!intent.new_span?.trim() ? "schema_missing_new_span" : "",
].filter(Boolean),
message: "REWRITE_PARAGRAPH with rewrite_mode=span requires old_span and new_span.",
repairKind: "operation_schema",
terminalFallback: "COMMENT",
});
}
} else if (!intent.new_text?.trim()) {
return repairableFailure({
intent,
failureCode: "schema_missing_new_text",
message: "REWRITE_PARAGRAPH requires new_text.",
repairKind: "operation_schema",
terminalFallback: "COMMENT",
});
}
}
if (opType === "INSERT" && !intent.insert_text?.trim()) {
return repairableFailure({
intent,
failureCode: "schema_missing_insert_text",
message: "INSERT requires insert_text.",
repairKind: "operation_schema",
terminalFallback: "COMMENT",
});
}
return null;
}
function isRedZoneEditOperation(params: {
intent: CommentPlanIntent;
anchor?: CanonicalAnchor;
}): { blocked: true; failureCode: string; message: string } | { blocked: false } {
const opType = params.intent.type ?? "COMMENT";
if (opType === "COMMENT") return { blocked: false };
const anchor = params.anchor;
if (!anchor) return { blocked: false };
const redZoneStyles = ["TOC", "TOC1", "TOC2", "TOC3", "TOC4", "TOCHeading", "TOCEntry"];
const redZoneKeywords = ["table of contents", "cover page", "signature page", "execution page"];
const styleId = `${String((anchor as any).style_id ?? "")} ${String((anchor as any).style_name ?? "")}`.toLowerCase();
const textPreview = String((anchor as any).text_preview ?? "").toLowerCase();
if (redZoneStyles.some((style) => styleId.includes(style.toLowerCase()))) {
return {
blocked: true,
failureCode: "red_zone_toc",
message: "Tracked edit operations on Table of Contents blocks are not allowed.",
};
}
if (redZoneKeywords.some((keyword) => textPreview.includes(keyword))) {
return {
blocked: true,
failureCode: "red_zone_signature_cover",
message: "Tracked edit operations on cover/signature/execution page sections are not allowed.",
};
}
return { blocked: false };
}
export function validatePlannedFinding(params: {
intent: CommentPlanIntent;
manifest: AnchorManifest | undefined;
currentSurface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
allowCommentFallback: boolean;
}): PlanValidationResult {
const { intent, manifest, currentSurface, allowCommentFallback } = params;
const baseFailure = validateBaseFindingFields(intent);
if (baseFailure) return baseFailure;
const operationFailure = validateOperationFields(intent);
let anchorFailure:
| {
failureCode: string;
message: string;
candidates?: AnchorCorrectionCandidate[];
rejectionReason?: string;
excludedNavigationRegion?: {
start_block_id: string;
end_block_id: string;
reason: string;
};
}
| null = null;
let anchor: CanonicalAnchor | undefined;
let anchorRedirect: AnchorRedirect | null = null;
if (!intent.block_id?.trim()) {
anchorFailure = {
failureCode: "schema_missing_block_id",
message: "Missing block_id. Provide new_block_id from the latest document surface.",
};
} else {
intent.block_id = normalizeModelBlockId(intent.block_id);
if (!isValidDocxModelBlockId(intent.block_id)) {
anchorFailure = {
failureCode: "anchor_invalid_format",
message: `Block id '${intent.block_id}' is not a valid P_NNNN or BMK_P_/BMK_T_ bookmark format.`,
};
} else {
anchorRedirect = tryHighConfidenceNavigationRedirect({
intent,
surface: currentSurface,
});
if (anchorRedirect) {
intent.block_id = anchorRedirect.to;
}
const anchorValidation = validateCommentPlanIntent({
intent,
manifest,
currentSurface,
});
if (anchorValidation.ok) {
anchor = anchorValidation.anchor;
} else {
anchorFailure = {
failureCode: anchorValidation.failureCode,
message: anchorValidation.message,
candidates: anchorValidation.candidates,
rejectionReason: anchorValidation.rejectionReason,
excludedNavigationRegion:
anchorValidation.excludedNavigationRegion,
};
}
}
}
if (operationFailure && !operationFailure.ok && anchorFailure) {
const relatedFailureCodes = [
operationFailure.failureCode,
...(operationFailure.relatedFailureCodes ?? []),
anchorFailure.failureCode,
];
return repairableFailure({
intent,
failureCode: operationFailure.failureCode,
relatedFailureCodes,
message: `${operationFailure.message} Also fix anchor issue: ${anchorFailure.message}`,
repairKind: "operation_schema",
terminalFallback: operationFailure.terminalFallback,
anchor,
anchorCandidates: anchorFailure.candidates,
anchorRejectionReason: anchorFailure.rejectionReason,
excludedNavigationRegion: anchorFailure.excludedNavigationRegion,
anchorRedirect: anchorRedirect ?? undefined,
});
}
if (operationFailure && !operationFailure.ok) {
return repairableFailure({
intent,
failureCode: operationFailure.failureCode,
relatedFailureCodes: operationFailure.relatedFailureCodes,
message: operationFailure.message,
repairKind: operationFailure.repairKind,
terminalFallback: operationFailure.terminalFallback,
anchor,
anchorRedirect: anchorRedirect ?? undefined,
});
}
if (anchorFailure) {
return repairableFailure({
intent,
failureCode: anchorFailure.failureCode,
message: anchorFailure.message,
repairKind: "anchor",
terminalFallback: undefined,
anchor,
anchorCandidates: anchorFailure.candidates,
anchorRejectionReason: anchorFailure.rejectionReason,
excludedNavigationRegion: anchorFailure.excludedNavigationRegion,
anchorRedirect: anchorRedirect ?? undefined,
});
}
const evidenceValidation = validateFindingTargetEvidence({
intent,
surface: currentSurface,
anchor: anchor!,
});
if (!evidenceValidation.ok) {
return repairableFailure({
intent,
failureCode: evidenceValidation.failureCode,
message: evidenceValidation.message,
repairKind: "evidence",
anchor,
anchorRedirect: anchorRedirect ?? undefined,
});
}
const redZone = isRedZoneEditOperation({ intent, anchor });
if (redZone.blocked) {
return repairableFailure({
intent,
failureCode: redZone.failureCode,
message: redZone.message,
repairKind: "red_zone",
terminalFallback: allowCommentFallback ? "COMMENT" : undefined,
anchor,
anchorRedirect: anchorRedirect ?? undefined,
});
}
return {
ok: true,
item: {
...intent,
anchor,
repairable: false,
anchorRedirectedFrom: anchorRedirect?.from,
anchorRedirectedTo: anchorRedirect?.to,
anchorRedirectReason: anchorRedirect?.reason,
},
};
}
function downgradeMalformedEditToComment(
intent: PlannedCommentItem,
reason: string,
): PlannedCommentItem {
const existingComment = intent.comment?.trim() || "[REVIEW] Issue preserved as a comment.";
const systemNote =
`[System note: The model attempted an edit operation but the edit payload could not be safely applied. ` +
`Preserved as a comment instead. Original validation error: ${reason}]`;
const next: PlannedCommentItem = {
...intent,
type: "COMMENT",
comment: existingComment.includes("[System note:")
? existingComment
: `${existingComment}\n\n${systemNote}`,
new_text: undefined,
insert_text: undefined,
position: undefined,
rewrite_mode: undefined,
old_span: undefined,
new_span: undefined,
failureCode: undefined,
relatedFailureCodes: undefined,
message: undefined,
repairKind: intent.repairKind,
repairable: false,
terminalFallback: undefined,
downgradeToComment: true,
fallbackApplied: true,
};
delete (next as any).new_text;
delete (next as any).insert_text;
delete (next as any).position;
delete (next as any).rewrite_mode;
delete (next as any).old_span;
delete (next as any).new_span;
return next;
}
function applyFindingCorrection(
original: PlannedCommentItem,
correction: FindingCorrection,
): PlannedCommentItem {
const next: PlannedCommentItem = {
...original,
repairAttempts: (original.repairAttempts ?? 0) + 1,
};
if (correction.new_block_id) next.block_id = correction.new_block_id;
if (correction.corrected_comment) next.comment = correction.corrected_comment;
if (correction.corrected_status) next.status = correction.corrected_status;
if (correction.corrected_reasoning) next.reasoning = correction.corrected_reasoning;
if (correction.corrected_target_text) {
next.target_text = correction.corrected_target_text;
}
if (correction.status === "downgrade_to_comment") {
return downgradeMalformedEditToComment(
next,
correction.reason || next.message || "Model requested safe downgrade to COMMENT.",
);
}
if (correction.corrected_type) next.type = correction.corrected_type;
if (correction.corrected_new_text) next.new_text = correction.corrected_new_text;
if (correction.corrected_insert_text) next.insert_text = correction.corrected_insert_text;
if (correction.corrected_position) next.position = correction.corrected_position;
if (correction.corrected_rewrite_mode) next.rewrite_mode = correction.corrected_rewrite_mode;
if (correction.corrected_old_span) next.old_span = correction.corrected_old_span;
if (correction.corrected_new_span) next.new_span = correction.corrected_new_span;
return next;
}
function buildFallbackCandidateContext(params: {
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
targetBlockId: string;
}): Array<{ block_id: string; text: string }> {
const direct = buildCandidateContext({
surface: params.surface,
targetBlockId: params.targetBlockId,
windowSize: 5,
});
if (direct.length > 0) return direct;
return params.surface.anchors
.filter((anchor) => anchor.anchor_eligible && anchor.commentable)
.slice(0, 20)
.map((anchor) => ({
block_id: anchor.block_id,
text: anchor.text_preview.slice(0, 200),
}));
}
function buildFindingCorrectionRequest(params: {
item: PlannedCommentItem;
currentSurface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
}): Record<string, unknown> {
const { item, currentSurface } = params;
const failureCodes = new Set([
item.failureCode,
...(item.relatedFailureCodes ?? []),
].filter(Boolean) as string[]);
const allowedCorrections: string[] = [];
if (
failureCodes.has("schema_missing_block_id") ||
failureCodes.has("ANCHOR_NOT_ELIGIBLE") ||
failureCodes.has("anchor_invalid_format") ||
failureCodes.has("anchor_not_found") ||
failureCodes.has("anchor_uncommentable") ||
failureCodes.has("anchor_revision_stale") ||
failureCodes.has("anchor_identity_mismatch") ||
failureCodes.has("anchor_stale_requires_document_refresh")
) {
allowedCorrections.push("new_block_id");
}
if (
failureCodes.has("schema_missing_comment") ||
failureCodes.has("schema_missing_comment_field")
) {
allowedCorrections.push("corrected_comment");
}
if (
failureCodes.has("schema_missing_or_invalid_status") ||
failureCodes.has("schema_missing_status")
) {
allowedCorrections.push("corrected_status");
}
if (
failureCodes.has("schema_missing_or_invalid_reasoning") ||
failureCodes.has("schema_missing_reasoning")
) {
allowedCorrections.push("corrected_reasoning");
}
if (
failureCodes.has("schema_missing_target_text") ||
failureCodes.has("target_text_too_short") ||
failureCodes.has("target_text_not_in_block") ||
failureCodes.has("target_text_heading_only")
) {
allowedCorrections.push("corrected_target_text");
allowedCorrections.push("new_block_id + corrected_target_text");
}
if (failureCodes.has("schema_invalid_operation_type")) {
allowedCorrections.push("corrected_type");
}
if (failureCodes.has("schema_missing_new_text")) {
allowedCorrections.push("corrected_new_text");
allowedCorrections.push("corrected_rewrite_mode + corrected_old_span + corrected_new_span");
allowedCorrections.push("downgrade_to_comment");
}
if (
failureCodes.has("schema_missing_rewrite_span") ||
failureCodes.has("schema_missing_old_span") ||
failureCodes.has("schema_missing_new_span")
) {
allowedCorrections.push("corrected_old_span + corrected_new_span");
allowedCorrections.push("corrected_new_text");
allowedCorrections.push("downgrade_to_comment");
}
if (failureCodes.has("schema_missing_insert_text")) {
allowedCorrections.push("corrected_insert_text");
allowedCorrections.push("downgrade_to_comment");
}
if (item.repairKind === "red_zone") {
allowedCorrections.push("new_block_id");
allowedCorrections.push("downgrade_to_comment");
}
if (allowedCorrections.length === 0 && item.terminalFallback === "COMMENT") {
allowedCorrections.push("downgrade_to_comment");
}
const requiredFix =
item.repairKind === "operation_schema"
? "Provide the missing corrected operation payload, or set status='downgrade_to_comment' if you cannot draft the edit safely."
: item.repairKind === "anchor"
? "Provide new_block_id from the latest document surface, or set status='unresolved' if no safe anchor exists."
: item.repairKind === "evidence"
? "Provide corrected_target_text quoted from the selected substantive block, and change new_block_id too if the finding points to a different clause."
: item.repairKind === "base_schema"
? "Provide corrected_comment, corrected_status, and/or corrected_reasoning for the missing finding fields."
: item.repairKind === "red_zone"
? "Provide a safe non-red-zone new_block_id, or set status='downgrade_to_comment' to preserve the issue as a comment."
: "Repair the finding using the allowed correction fields.";
return {
comment_key: item.comment_key,
failure_code: item.failureCode,
related_failure_codes: item.relatedFailureCodes ?? [],
repair_kind: item.repairKind ?? "unknown",
repair_attempts: item.repairAttempts ?? 0,
message: item.message,
original_type: item.type ?? "COMMENT",
original_block_id: item.block_id,
bad_block_id: item.block_id,
original_comment: item.comment,
original_status: item.status,
original_reasoning: item.reasoning,
target_text: item.target_text,
anchor_rejection_reason: item.anchorRejectionReason,
excluded_navigation_region: item.excludedNavigationRegion,
required_fix: requiredFix,
allowed_corrections: Array.from(new Set(allowedCorrections)),
eligible_candidates:
item.anchorCandidates && item.anchorCandidates.length > 0
? item.anchorCandidates
: buildFallbackCandidateContext({
surface: currentSurface,
targetBlockId: item.block_id,
}),
};
}
function buildFindingDiagnostics(params: {
item: PlannedCommentItem;
finalItem?: PlannedCommentItem;
applyStatus: string;
}): Record<string, unknown> {
const { item, finalItem, applyStatus } = params;
return {
comment_key: item.comment_key,
original_type: item.type ?? "COMMENT",
final_type: finalItem?.type ?? item.type ?? "COMMENT",
repair_kind: item.repairKind ?? "unknown",
repair_attempts: item.repairAttempts ?? 0,
failure_code: item.failureCode ?? null,
fallback_applied: !!(finalItem?.fallbackApplied ?? item.fallbackApplied),
downgrade_to_comment: !!(finalItem?.downgradeToComment ?? item.downgradeToComment),
anchor_redirected_from:
finalItem?.anchorRedirectedFrom ?? item.anchorRedirectedFrom ?? null,
anchor_redirected_to:
finalItem?.anchorRedirectedTo ?? item.anchorRedirectedTo ?? null,
anchor_redirect_reason:
finalItem?.anchorRedirectReason ?? item.anchorRedirectReason ?? null,
apply_status: applyStatus,
};
}
async function applyCommentWriterBatch(params: {
userId: string;
documentId: string;
docLabel: string;
docInfo: NonNullable<ReturnType<DocStore["get"]>>;
current: NonNullable<Awaited<ReturnType<typeof loadCurrentVersionBytes>>>;
comments: WriterCommentItem[];
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
trace?: RunTree | null;
docIndex?: DocIndex;
docStore?: DocStore;
anchorManifestState?: AnchorManifestState;
docsEdited: DocEditedResult[];
prevalidationFailures?: any[];
toolCallId: string;
verificationRows: CommentVerificationRow[];
trackChanges?: boolean;
}): Promise<{
ok: boolean;
version_id?: string;
version_number?: number;
download_url?: string;
apply_report?: any[];
apply_summary?: DocEditedResult["apply_summary"];
error?: string;
}> {
const { userId, documentId, docLabel, docInfo, current, comments, db, write, trace, docIndex, docStore, anchorManifestState, docsEdited, prevalidationFailures = [], toolCallId, verificationRows, trackChanges } = params;
if (comments.length === 0) {
return { ok: false, error: "No validated comments to apply." };
}
write(`data: ${JSON.stringify({
type: "doc_edited_start",
filename: docInfo.filename,
})}
`);
const batchResult = await runCommentDocumentBatch({
userId,
documentId,
docLabel,
docInfo,
current,
comments,
db,
trackChanges,
trace,
});
const writerReport = batchResult.apply_report ?? [];
const reportById = new Map<string, any>();
for (const report of writerReport) {
if (typeof report?.id === "string") reportById.set(report.id, report);
}
const combinedApplyReport = [...prevalidationFailures, ...writerReport];
// Build lookup from comment_key to original intent data for enriching verification rows
const commentMetaByKey = new Map<string, { comment?: string; reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string }; anchor_text?: string; section_heading?: string | null; op_type?: string; bookmark_name?: string | null; finding_status?: WorkflowFindingStatus; document_order?: number; source?: CommentPlanSource; amended_wording?: string }>();
for (const c of comments) {
commentMetaByKey.set(c.args.comment_key, {
comment: c.args.comment,
reasoning: c.reasoning,
anchor_text: c.anchor_text,
section_heading: c.section_heading,
op_type: c.args.type,
bookmark_name: c.anchor?.bookmark_name ?? null,
finding_status: c.finding_status,
document_order: c.document_order,
source: c.source,
amended_wording: c.args.new_span || c.args.new_text || c.args.insert_text || undefined,
});
}
// Push verification rows for every item in this batch so insertion_verification traces correctly
for (const report of combinedApplyReport) {
const meta = commentMetaByKey.get(report.comment_key ?? "") ?? {};
const fallbackOpType = stringOrUndefined(report.op_type) ?? "COMMENT";
verificationRows.push({
tool_call_id: toolCallId,
ok: report.status === "applied",
doc_id: docLabel,
document_id: documentId,
block_id: report.model_block_id ?? report.paragraph_id ?? null,
comment_key: report.comment_key ?? null,
status: report.status ?? "not_applied",
failure_code: report.failure_code ?? undefined,
message: report.message ?? undefined,
model_block_id: report.model_block_id ?? undefined,
writer_paragraph_id: report.writer_paragraph_id ?? undefined,
comment: meta.comment ?? stringOrUndefined(report.comment),
reasoning: meta.reasoning ?? workflowReasoningOrUndefined(report.reasoning),
anchor_text: meta.anchor_text ?? stringOrUndefined(report.anchor_text),
section_heading: meta.section_heading ?? stringOrNull(report.section_heading),
op_type: meta.op_type ?? fallbackOpType,
bookmark_name: meta.bookmark_name ?? stringOrNull(report.bookmark_name),
finding_status:
meta.finding_status ??
workflowFindingStatusOrUndefined(report.finding_status),
document_order:
meta.document_order ?? numberOrUndefined(report.document_order),
apply_status:
report.apply_status ??
(report.status === "applied"
? "applied"
: "writer_failed"),
source: meta.source ?? workflowSourceOrUndefined(report.source),
amended_wording: meta.amended_wording,
});
}
if (batchResult.ok && batchResult.download_url && batchResult.version_id) {
if (docIndex?.[docLabel]) {
docIndex[docLabel] = {
...docIndex[docLabel],
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
};
}
if (docStore && batchResult.storage_path) {
const currentDocStore = docStore.get(docLabel);
if (currentDocStore) {
docStore.set(docLabel, {
...currentDocStore,
storage_path: batchResult.storage_path,
});
}
}
const appliedCount = combinedApplyReport.filter((r: any) => r.status === "applied").length;
let commentVerification: CommentBatchVerification | undefined;
if (batchResult.commented_bytes) {
commentVerification = await verifyCommentBatchPostWrite({
originalBytes: current.bytes,
commentedBytes: batchResult.commented_bytes,
expectedAppliedCount: appliedCount,
appliedReports: combinedApplyReport,
});
}
if (batchResult.commented_bytes && commentVerification?.body_text_unchanged) {
const newSurface = await buildDocxEditSurface(batchResult.commented_bytes, {
versionId: batchResult.version_id,
});
rememberAnchorManifest(anchorManifestState, docLabel, documentId, newSurface);
const oldSurfaceId = (await buildDocxEditSurface(current.bytes, { versionId: current.version_id })).surfaceId;
console.log(
`[applyCommentWriterBatch] manifest refreshed for ${docLabel} after comment-only write. ` +
`old_revision=${oldSurfaceId} new_revision=${newSurface.surfaceId} ` +
`entries=${newSurface.anchors.length} applied=${appliedCount}`
);
} else {
anchorManifestState?.byDocLabel.delete(docLabel);
}
const payload: DocEditedResult = {
filename: docInfo.filename,
document_id: documentId,
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
download_url: batchResult.download_url,
annotations: [],
apply_report: combinedApplyReport,
apply_summary: buildApplySummary(combinedApplyReport),
};
docsEdited.push(payload);
write(`data: ${JSON.stringify({
type: "doc_edited",
...payload,
})}
`);
return {
ok: true,
version_id: batchResult.version_id,
version_number: batchResult.version_number,
download_url: batchResult.download_url,
apply_report: combinedApplyReport,
apply_summary: buildApplySummary(combinedApplyReport),
};
} else {
const payload = {
type: "doc_edited",
filename: docInfo.filename,
document_id: documentId,
version_id: "",
version_number: null,
download_url: "",
annotations: [],
apply_report: combinedApplyReport,
apply_summary: buildApplySummary(combinedApplyReport),
error: batchResult.error ?? "No comments were inserted.",
};
write(`data: ${JSON.stringify(payload)}
`);
return {
ok: false,
error: batchResult.error ?? "No comments were inserted.",
apply_report: combinedApplyReport,
apply_summary: buildApplySummary(combinedApplyReport),
};
}
}
function buildFailedPdfValidationRow(input: {
tc: ToolCall;
docLabel: string;
documentId: string;
intent: any;
error: string;
message: string;
}): any {
return {
tool_call_id: input.tc.id,
ok: false,
doc_id: input.docLabel,
document_id: input.documentId,
block_id: input.intent?.block_id ?? null,
comment_key: input.intent?.comment_key ?? null,
status: "failed",
comment: input.intent?.comment ?? input.message,
reasoning: input.intent?.reasoning ?? null,
anchor_text: input.intent?.target_text ?? "",
section_heading: input.intent?.block_id ?? "PDF",
op_type: input.intent?.type ?? "COMMENT",
bookmark_name: input.intent?.block_id ?? null,
finding_status: input.intent?.status ?? "REVIEW",
document_order: Number.MAX_SAFE_INTEGER,
apply_status: "failed",
error: input.error,
message: input.message,
source: "plan_comments_on_document",
};
}
function parseStructuredCommentParts(comment: string | null | undefined): {
issue: string;
zamil_impact: string;
recommended_action: string;
} {
const raw = comment ?? "";
const issueMatch = raw.match(/Issue:\s*(.*?)(?:\s*\|\s*Zamil Impact:|\s*\|\s*Recommended Action:|$)/i);
const impactMatch = raw.match(/Zamil Impact:\s*(.*?)(?:\s*\|\s*Recommended Action:|$)/i);
const actionMatch = raw.match(/Recommended Action:\s*(.*)$/i);
return {
issue: issueMatch?.[1]?.trim() || raw.trim(),
zamil_impact: impactMatch?.[1]?.trim() || "",
recommended_action: actionMatch?.[1]?.trim() || "",
};
}
function parseCommentPlanIntents(commentsRaw: unknown): CommentPlanIntent[] {
const rawList = Array.isArray(commentsRaw) ? commentsRaw : [];
const intents: CommentPlanIntent[] = [];
for (const raw of rawList) {
if (!raw || typeof raw !== "object") continue;
const r = raw as Record<string, unknown>;
const blockId = normalizeModelBlockId(String(r.block_id ?? "").trim());
const comment = sanitizeWorkflowDisplayText(String(r.comment ?? ""));
const commentKey = String(r.comment_key ?? "").trim();
intents.push({
block_id: blockId,
comment,
status: r.status ? String(r.status).trim() : undefined,
reasoning: r.reasoning && typeof r.reasoning === "object" && "clause_section" in r.reasoning
? {
clause_section: sanitizeWorkflowDisplayText(String((r.reasoning as Record<string, unknown>).clause_section ?? "")),
violated_standard: sanitizeWorkflowDisplayText(String((r.reasoning as Record<string, unknown>).violated_standard ?? "")),
zamil_exposure: sanitizeWorkflowDisplayText(String((r.reasoning as Record<string, unknown>).zamil_exposure ?? "")),
}
: undefined,
target_text: r.target_text ? sanitizeWorkflowDisplayText(String(r.target_text)) : undefined,
anchor_mode: r.anchor_mode === "exact" ? "exact" : "paragraph",
before_anchor: r.before_anchor ? sanitizeWorkflowDisplayText(String(r.before_anchor)) : undefined,
after_anchor: r.after_anchor ? sanitizeWorkflowDisplayText(String(r.after_anchor)) : undefined,
comment_key: commentKey,
type: normalizeFindingType(r.type),
new_text: r.new_text ? sanitizeWorkflowDisplayText(String(r.new_text)) : undefined,
insert_text: r.insert_text ? sanitizeWorkflowDisplayText(String(r.insert_text)) : undefined,
position: r.position ? String(r.position).trim() : undefined,
rewrite_mode: r.rewrite_mode ? String(r.rewrite_mode).trim() : undefined,
old_span: r.old_span ? sanitizeWorkflowDisplayText(String(r.old_span)) : undefined,
new_span: r.new_span ? sanitizeWorkflowDisplayText(String(r.new_span)) : undefined,
});
}
return intents;
}
async function handlePlanCommentsOnPdf(params: {
args: any;
tc: ToolCall;
docLabel: string;
docInfo: any;
indexed: any;
db: any;
verificationRows: any[];
}): Promise<{ role: "tool"; tool_call_id: string; content: string }> {
const {
args,
tc,
docLabel,
indexed,
db,
verificationRows,
} = params;
const documentId = indexed.document_id;
const current = await loadCurrentVersionBytes(documentId, db);
if (!current) {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_BYTES_UNAVAILABLE",
message: "Could not load current PDF document bytes.",
}),
};
}
const currentBytes = (current.bytes instanceof ArrayBuffer
? current.bytes
: current.bytes.buffer.slice(
current.bytes.byteOffset,
current.bytes.byteOffset + current.bytes.byteLength,
)) as ArrayBuffer;
const surface = await buildPdfVirtualSurface(currentBytes, {
documentId,
versionId: current.version_id ?? indexed.version_id ?? null,
});
const comments = parseCommentPlanIntents(args.comments);
const rows: any[] = [];
for (const intent of comments) {
const pageNumber = pageFromPdfBlockId(intent.block_id);
const page = pageNumber ? surface.pages[pageNumber - 1] : null;
if (!pageNumber || !page) {
rows.push(buildFailedPdfValidationRow({
tc,
docLabel,
documentId,
intent,
error: "PDF_PAGE_NOT_FOUND",
message: `Invalid PDF page anchor "${intent.block_id}". Expected a block_id like BMK_P_page1, BMK_P_page2, etc.`,
}));
continue;
}
if (intent.type && intent.type !== "COMMENT") {
rows.push(buildFailedPdfValidationRow({
tc,
docLabel,
documentId,
intent,
error: "PDF_READ_ONLY",
message: "PDF documents are read-only. Only COMMENT findings are supported for PDFs.",
}));
continue;
}
if (!intent.status || !intent.reasoning || !intent.comment_key || !intent.comment) {
rows.push(buildFailedPdfValidationRow({
tc,
docLabel,
documentId,
intent,
error: "VALIDATION_FAILED",
message: "PDF finding is missing one or more required fields: status, reasoning, comment_key, comment.",
}));
continue;
}
const resolvedQuote = resolvePdfTargetText({
pageText: page.text,
targetText: intent.target_text ?? "",
beforeAnchor: intent.before_anchor ?? "",
afterAnchor: intent.after_anchor ?? "",
});
const parts = parseStructuredCommentParts(intent.comment);
rows.push({
tool_call_id: tc.id,
ok: true,
row_id: `${docLabel}:${intent.comment_key}`,
doc_id: docLabel,
document_id: documentId,
version_id: current.version_id ?? indexed.version_id ?? null,
block_id: intent.block_id,
comment_key: intent.comment_key,
status: "applied",
comment: intent.comment,
reasoning: intent.reasoning,
anchor_text: resolvedQuote.text || intent.target_text || page.text.slice(0, 240),
original_wording: resolvedQuote.text || intent.target_text || page.text.slice(0, 240),
section_heading: `Page ${pageNumber}`,
op_type: "COMMENT",
action: "COMMENT",
bookmark_name: intent.block_id,
finding_status: intent.status,
document_order: pageNumber * 1000,
apply_status: "applied",
issue: parts.issue,
zamil_impact: parts.zamil_impact,
recommended_action: parts.recommended_action,
quote_resolution: {
confidence: resolvedQuote.confidence,
strategy: resolvedQuote.strategy,
},
source: "plan_comments_on_document",
read_only_pdf: true,
});
}
verificationRows.push(...rows);
const applied = rows.filter((row) => row.ok).length;
const failed = rows.filter((row) => !row.ok).length;
const extractedTextLength = surface.pages.reduce((sum, page) => sum + page.text.trim().length, 0);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
document_id: documentId,
read_only_pdf: true,
no_extractable_text: extractedTextLength === 0,
total: comments.length,
applied,
failed,
message: extractedTextLength === 0
? "The PDF appears to have no extractable text. No PDF file was modified."
: "PDF review findings were recorded as read-only workflow rows. No PDF file was modified.",
}),
};
}
export async function handlePlanCommentsOnDocument(params: {
tc: ToolCall;
args: Record<string, unknown>;
docStore: DocStore;
docIndex: DocIndex;
userId: string;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
trace?: RunTree | null;
anchorManifestState?: AnchorManifestState;
pendingCommentPlanState: PendingCommentPlanState;
docsEdited: DocEditedResult[];
verificationRows?: CommentVerificationRow[];
trackChanges?: boolean;
}): Promise<{ role: "tool"; tool_call_id: string; content: string }> {
const { tc, args, docStore, docIndex, userId, db, write, trace, anchorManifestState, pendingCommentPlanState, docsEdited, verificationRows = [], trackChanges } = params;
const rawDocId = String(args.doc_id ?? "").trim();
const docLabel = resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const docInfo = docStore.get(docLabel);
const indexed = docIndex?.[docLabel];
if (!docInfo || !indexed) {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_NOT_FOUND",
message: `Document '${rawDocId}' is not available.`,
}),
};
}
if (docInfo.file_type === "pdf") {
return handlePlanCommentsOnPdf({
args,
tc,
docLabel,
docInfo,
indexed,
db,
verificationRows,
});
}
if (docInfo.file_type !== "docx") {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "UNSUPPORTED_DOCUMENT_TYPE",
message: "comment_on_document only supports .docx files.",
}),
};
}
const current = await loadCurrentVersionBytes(indexed.document_id, db);
if (!current) {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_BYTES_UNAVAILABLE",
message: "Could not load current document bytes.",
}),
};
}
const currentSurface = await buildDocxEditSurface(current.bytes, {
versionId: current.version_id,
});
const manifest = anchorManifestState?.byDocLabel.get(docLabel);
const commentsRaw = Array.isArray(args.comments) ? args.comments : [];
const actionableIntents: PlannedCommentItem[] = [];
const ignoredPassIntents: CommentPlanIntent[] = [];
const failedPermanently: PlannedCommentItem[] = [];
// 1) Parse raw input list and partition into actionable, pass, and malformed
for (const [index, raw] of commentsRaw.entries()) {
if (!raw || typeof raw !== "object") {
failedPermanently.push({
block_id: "UNKNOWN",
comment: "Comment intent is not an object.",
comment_key: `malformed_${index}`,
failureCode: "VALIDATION_FAILED",
message: "Comment intent is not an object.",
rawIntent: raw,
});
continue;
}
const r = raw as Record<string, unknown>;
const intent: CommentPlanIntent = {
block_id: normalizeModelBlockId(String(r.block_id ?? "").trim()),
comment: sanitizeWorkflowDisplayText(String(r.comment ?? "")),
status: r.status ? String(r.status).trim() : undefined,
reasoning: r.reasoning && typeof r.reasoning === "object" && "clause_section" in r.reasoning
? {
clause_section: sanitizeWorkflowDisplayText(String((r.reasoning as any).clause_section ?? "")),
violated_standard: sanitizeWorkflowDisplayText(String((r.reasoning as any).violated_standard ?? "")),
zamil_exposure: sanitizeWorkflowDisplayText(String((r.reasoning as any).zamil_exposure ?? "")),
}
: undefined,
target_text: r.target_text ? sanitizeWorkflowDisplayText(String(r.target_text)) : undefined,
anchor_mode: r.anchor_mode === "exact" ? "exact" : "paragraph",
before_anchor: r.before_anchor ? sanitizeWorkflowDisplayText(String(r.before_anchor)) : undefined,
after_anchor: r.after_anchor ? sanitizeWorkflowDisplayText(String(r.after_anchor)) : undefined,
comment_key: String(r.comment_key ?? "").trim(),
type: normalizeFindingType(r.type),
new_text: r.new_text ? sanitizeWorkflowDisplayText(String(r.new_text)) : undefined,
insert_text: r.insert_text ? sanitizeWorkflowDisplayText(String(r.insert_text)) : undefined,
position: r.position ? String(r.position).trim() : undefined,
rewrite_mode: r.rewrite_mode ? String(r.rewrite_mode).trim() : undefined,
old_span: r.old_span ? sanitizeWorkflowDisplayText(String(r.old_span)) : undefined,
new_span: r.new_span ? sanitizeWorkflowDisplayText(String(r.new_span)) : undefined,
};
if (isPassItem(intent)) {
ignoredPassIntents.push(intent);
} else {
let item: PlannedCommentItem = {
...intent,
rawIntent: r,
};
if (trackChanges !== true && isTrackedChangeOperation(item)) {
item = downgradeEditIntentToComment(item, "Document reviewed in comments-only mode");
}
actionableIntents.push(item);
}
}
const ignoredPassReports = ignoredPassIntents.map((intent, i) => buildIgnoredPassReport(intent, i));
const validationTrace = createTraceChild(trace, {
name: "comment_plan_validation",
run_type: "chain",
inputs: {
doc_id: docLabel,
document_id: indexed.document_id,
comments_attempted: commentsRaw.length,
comments_actionable: actionableIntents.length,
comments_ignored_pass: ignoredPassIntents.length,
manifest_surface_id: manifest?.surfaceId ?? null,
current_surface_id: currentSurface.surfaceId,
navigation_region_count: currentSurface.navigationRegions.length,
navigation_block_count: currentSurface.anchors.filter(
(anchor) => !anchor.anchor_eligible,
).length,
track_changes: trackChanges ?? false,
},
tags: ["verification", "comments", "plan"],
});
// Log ignored PASS diagnostics to LangSmith
if (ignoredPassReports.length > 0 && validationTrace) {
validationTrace.addEvent({
name: "ignored_pass_items",
kwargs: { count: ignoredPassReports.length, items: ignoredPassReports },
});
}
const valid: PlannedCommentItem[] = [];
const needsCorrection: PlannedCommentItem[] = [];
// 2) Run validatePlannedFinding on all actionable items with allowCommentFallback=true
for (const intent of actionableIntents) {
const result = validatePlannedFinding({
intent,
manifest,
currentSurface,
allowCommentFallback: true,
});
if (result.ok) {
valid.push(result.item);
} else {
if (result.repairable) {
needsCorrection.push(result.item);
} else {
failedPermanently.push(result.item);
}
}
}
await endTrace(validationTrace, {
outputs: {
commentsAttempted: commentsRaw.length,
commentsActionable: actionableIntents.length,
commentsIgnoredPass: ignoredPassIntents.length,
commentsValid: valid.length,
commentsNeedCorrection: needsCorrection.length,
commentsFailedPermanently: failedPermanently.length,
ignoredPassReports,
validItems: valid.map((v) => ({
comment_key: v.comment_key,
status: v.status,
block_id: v.block_id,
comment: v.comment,
reasoning: v.reasoning,
anchor_text: v.anchor?.text_preview ?? null,
anchor_text_hash: v.anchor?.text_hash ?? null,
section_heading: findSectionHeading(currentSurface, v.block_id),
native_para_id: v.anchor?.native_para_id ?? null,
stable_id: v.anchor?.stable_id ?? null,
xml_path: v.anchor?.xml_path ?? null,
container: v.anchor?.container ?? null,
commentable: v.anchor?.commentable ?? null,
bookmark_name: v.anchor?.bookmark_name ?? null,
op_type: v.type ?? "COMMENT",
validation_status: "valid",
anchor_redirected_from: v.anchorRedirectedFrom ?? null,
anchor_redirected_to: v.anchorRedirectedTo ?? null,
anchor_redirect_reason: v.anchorRedirectReason ?? null,
})),
needCorrectionItems: needsCorrection.map((c) => ({
comment_key: c.comment_key,
status: c.status,
block_id: c.block_id,
comment: c.comment,
reasoning: c.reasoning,
anchor_text: c.anchor?.text_preview ?? null,
section_heading: findSectionHeading(currentSurface, c.block_id),
bookmark_name: c.anchor?.bookmark_name ?? null,
op_type: c.type ?? "COMMENT",
failure_code: c.failureCode,
message: c.message,
validation_status: "failed",
})),
failedPermanentlyItems: failedPermanently.map((f) => ({
comment_key: f.comment_key,
status: f.status,
block_id: f.block_id,
comment: f.comment,
reasoning: f.reasoning,
anchor_text: f.anchor?.text_preview ?? null,
section_heading: findSectionHeading(currentSurface, f.block_id),
bookmark_name: f.anchor?.bookmark_name ?? null,
op_type: f.type ?? "COMMENT",
failure_code: f.failureCode,
message: f.message,
validation_status: "failed",
})),
},
error: needsCorrection.length > 0 || failedPermanently.length > 0
? `${needsCorrection.length + failedPermanently.length} comment anchor(s) failed prevalidation`
: null,
});
// Build correction requests using the unified helper
const correctionRequests = needsCorrection.map((item) =>
buildFindingCorrectionRequest({ item, currentSurface })
);
// Store the pending plan so correct_comment_anchors can retrieve it
pendingCommentPlanState.set(docLabel, {
docLabel,
documentId: indexed.document_id,
docInfo,
indexed,
current,
currentSurface,
manifest,
valid,
needsCorrection,
failedPermanently,
ignoredPassReports,
});
if (needsCorrection.length > 0) {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
total: commentsRaw.length,
valid: valid.length,
ignored_pass: ignoredPassIntents.length,
needs_correction: needsCorrection.length,
failed_permanently: failedPermanently.length,
ready_to_apply: false,
valid_comments: valid.map((v) => ({
comment_key: v.comment_key,
block_id: v.block_id,
status: "pending_valid",
anchor_redirected_from: v.anchorRedirectedFrom ?? null,
anchor_redirected_to: v.anchorRedirectedTo ?? null,
anchor_redirect_reason: v.anchorRedirectReason ?? null,
})),
correction_requests: correctionRequests,
failed_permanently_items: failedPermanently.map((f) => ({
comment_key: f.comment_key,
block_id: f.block_id,
failure_code: f.failureCode,
message: f.message,
})),
instructions: `Call correct_comment_anchors for doc_id '${docLabel}' with corrected block_ids for the ${needsCorrection.length} item(s) that need correction. Valid comments are preserved and will be merged after correction.`,
}),
};
}
if (valid.length > 0) {
const writerItems: WriterCommentItem[] = valid.map((v, i) => ({
id: `plan-comment-${i}`,
args: {
doc_id: docLabel,
block_id: v.block_id,
comment: v.comment,
target_text: v.target_text ?? "",
anchor_mode: v.anchor_mode ?? "paragraph",
before_anchor: v.before_anchor ?? "",
after_anchor: v.after_anchor ?? "",
comment_key: v.comment_key ?? makeCommentKey({
doc_id: docLabel,
block_id: v.block_id,
target_text: v.target_text ?? "",
anchor_mode: v.anchor_mode ?? "paragraph",
comment: v.comment,
}),
type: (v as any).type,
new_text: (v as any).new_text,
insert_text: (v as any).insert_text,
position: (v as any).position,
rewrite_mode: (v as any).rewrite_mode,
old_span: (v as any).old_span,
new_span: (v as any).new_span,
},
anchor: v.anchor!,
reasoning: v.reasoning,
anchor_text: v.anchor?.text_preview ?? undefined,
section_heading: findSectionHeading(currentSurface, v.block_id),
finding_status: v.status as WorkflowFindingStatus,
document_order: getDocumentOrder(currentSurface, v.block_id, v.anchor?.bookmark_name ?? null),
source: "plan_comments_on_document",
}));
const prevalidationFailures = [
...failedPermanently.map((item) => ({
status: "not_applied",
apply_status: "failed_validation" as const,
comment_key: item.comment_key,
model_block_id: item.block_id,
message: item.message,
failure_code: item.failureCode ?? "VALIDATION_FAILED",
op_type: item.type ?? "COMMENT",
comment: item.comment,
reasoning: item.reasoning,
finding_status: item.status,
section_heading: findSectionHeading(currentSurface, item.block_id),
document_order: getDocumentOrder(currentSurface, item.block_id, item.anchor?.bookmark_name ?? null),
bookmark_name: item.anchor?.bookmark_name ?? null,
source: "plan_comments_on_document" as const,
...buildFindingDiagnostics({ item, applyStatus: "failed_validation" }),
})),
...ignoredPassReports.map((report) => ({
...report,
source: "plan_comments_on_document" as const,
})),
];
const applyResult = await applyCommentWriterBatch({
userId,
documentId: indexed.document_id,
docLabel,
docInfo,
current,
comments: writerItems,
db,
write,
trace,
docIndex,
docStore,
anchorManifestState,
docsEdited,
prevalidationFailures,
toolCallId: tc.id,
verificationRows,
trackChanges,
});
pendingCommentPlanState.delete(docLabel);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: applyResult.ok,
doc_id: docLabel,
total: commentsRaw.length,
valid: valid.length,
ignored_pass: ignoredPassIntents.length,
needs_correction: 0,
failed_permanently: failedPermanently.length,
ready_to_apply: true,
applied: applyResult.ok,
version_id: applyResult.version_id,
version_number: applyResult.version_number,
download_url: applyResult.download_url,
apply_summary: applyResult.apply_summary,
anchor_redirects: valid
.filter((item) => item.anchorRedirectedFrom)
.map((item) => ({
comment_key: item.comment_key,
from: item.anchorRedirectedFrom,
to: item.anchorRedirectedTo,
reason: item.anchorRedirectReason,
})),
}),
};
}
if (failedPermanently.length > 0) {
for (const item of failedPermanently) {
verificationRows.push({
tool_call_id: tc.id,
ok: false,
doc_id: docLabel,
document_id: indexed.document_id,
block_id: item.block_id,
comment_key: item.comment_key,
status: "not_applied",
failure_code: item.failureCode ?? "VALIDATION_FAILED",
message: item.message ?? "Validation failed.",
comment: item.comment,
reasoning: item.reasoning,
anchor_text: item.anchor?.text_preview ?? undefined,
section_heading: findSectionHeading(currentSurface, item.block_id),
op_type: item.type ?? "COMMENT",
bookmark_name: item.anchor?.bookmark_name ?? null,
finding_status: item.status as WorkflowFindingStatus | undefined,
document_order: getDocumentOrder(currentSurface, item.block_id, item.anchor?.bookmark_name ?? null),
apply_status: "failed_validation",
source: "plan_comments_on_document",
});
}
for (const report of ignoredPassReports) {
verificationRows.push({
tool_call_id: tc.id,
ok: true,
doc_id: docLabel,
document_id: indexed.document_id,
block_id: String(report.block_id ?? ""),
comment_key: String(report.comment_key ?? ""),
status: "ignored_pass",
failure_code: undefined,
message: String(report.ignored_reason ?? "pass_status"),
comment: String(report.comment ?? ""),
reasoning: (report.reasoning && typeof report.reasoning === "object" && "clause_section" in report.reasoning) ? { clause_section: String((report.reasoning as Record<string, unknown>).clause_section ?? "").trim(), violated_standard: String((report.reasoning as Record<string, unknown>).violated_standard ?? "").trim(), zamil_exposure: String((report.reasoning as Record<string, unknown>).zamil_exposure ?? "").trim() } : undefined,
op_type: (typeof report.op_type === "string" ? report.op_type : undefined),
bookmark_name: (typeof report.bookmark_name === "string" ? report.bookmark_name : null) ?? null,
finding_status: undefined,
apply_status: "ignored_pass",
source: "plan_comments_on_document",
});
}
pendingCommentPlanState.delete(docLabel);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
doc_id: docLabel,
total: commentsRaw.length,
valid: 0,
ignored_pass: ignoredPassIntents.length,
needs_correction: 0,
failed_permanently: failedPermanently.length,
ready_to_apply: true,
applied: false,
}),
};
}
// Only ignored PASS or empty input remains
for (const report of ignoredPassReports) {
verificationRows.push({
tool_call_id: tc.id,
ok: true,
doc_id: docLabel,
document_id: indexed.document_id,
block_id: String(report.block_id ?? ""),
comment_key: String(report.comment_key ?? ""),
status: "ignored_pass",
failure_code: undefined,
message: String(report.ignored_reason ?? "pass_status"),
comment: String(report.comment ?? ""),
reasoning: (report.reasoning && typeof report.reasoning === "object" && "clause_section" in report.reasoning) ? { clause_section: String((report.reasoning as Record<string, unknown>).clause_section ?? "").trim(), violated_standard: String((report.reasoning as Record<string, unknown>).violated_standard ?? "").trim(), zamil_exposure: String((report.reasoning as Record<string, unknown>).zamil_exposure ?? "").trim() } : undefined,
op_type: (typeof report.op_type === "string" ? report.op_type : undefined),
bookmark_name: (typeof report.bookmark_name === "string" ? report.bookmark_name : null) ?? null,
finding_status: undefined,
apply_status: "ignored_pass",
source: "plan_comments_on_document",
});
}
pendingCommentPlanState.delete(docLabel);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
total: commentsRaw.length,
valid: 0,
ignored_pass: ignoredPassIntents.length,
needs_correction: 0,
failed_permanently: 0,
ready_to_apply: true,
applied: false,
no_action: true,
}),
};
}
export async function handleCorrectCommentAnchors(params: {
tc: ToolCall;
args: Record<string, unknown>;
docStore: DocStore;
docIndex: DocIndex;
userId: string;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
trace?: RunTree | null;
anchorManifestState?: AnchorManifestState;
pendingCommentPlanState: PendingCommentPlanState;
docsEdited: DocEditedResult[];
verificationRows?: CommentVerificationRow[];
trackChanges?: boolean;
}): Promise<{ role: "tool"; tool_call_id: string; content: string }> {
const { tc, args, docStore, docIndex, userId, db, write, trace, anchorManifestState, pendingCommentPlanState, docsEdited, verificationRows = [], trackChanges } = params;
const rawDocId = String(args.doc_id ?? "").trim();
const docLabel = resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const plan = pendingCommentPlanState.get(docLabel);
if (!plan) {
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "NO_PENDING_PLAN",
message: `No pending comment plan found for '${docLabel}'. Call plan_comments_on_document first.`,
}),
};
}
const sourceToolName: CommentPlanSource = tc.function.name === "fix_comment_findings"
? "fix_comment_findings"
: "correct_comment_anchors";
const correctionsRaw = Array.isArray(args.corrections) ? args.corrections : [];
const corrections = correctionsRaw
.filter((r): r is Record<string, unknown> => !!r && typeof r === "object")
.map((r) => ({
comment_key: String(r.comment_key ?? "").trim(),
new_block_id: r.new_block_id ? String(r.new_block_id).trim() : undefined,
status: String(r.status ?? "").trim() as FindingCorrection["status"],
reason: r.reason ? String(r.reason).trim() : undefined,
corrected_comment: r.corrected_comment ? String(r.corrected_comment).trim() : undefined,
corrected_status: r.corrected_status ? String(r.corrected_status).trim() as FindingCorrection["corrected_status"] : undefined,
corrected_reasoning: r.corrected_reasoning && typeof r.corrected_reasoning === "object" && "clause_section" in r.corrected_reasoning
? {
clause_section: String((r.corrected_reasoning as any).clause_section ?? "").trim(),
violated_standard: String((r.corrected_reasoning as any).violated_standard ?? "").trim(),
zamil_exposure: String((r.corrected_reasoning as any).zamil_exposure ?? "").trim(),
}
: undefined,
corrected_target_text: r.corrected_target_text
? String(r.corrected_target_text).trim()
: undefined,
corrected_type: normalizeFindingType(r.corrected_type) as FindingCorrection["corrected_type"],
corrected_new_text: r.corrected_new_text ? String(r.corrected_new_text).trim() : undefined,
corrected_insert_text: r.corrected_insert_text ? String(r.corrected_insert_text).trim() : undefined,
corrected_position: r.corrected_position ? String(r.corrected_position).trim() as FindingCorrection["corrected_position"] : undefined,
corrected_rewrite_mode: r.corrected_rewrite_mode ? String(r.corrected_rewrite_mode).trim() as FindingCorrection["corrected_rewrite_mode"] : undefined,
corrected_old_span: r.corrected_old_span ? String(r.corrected_old_span).trim() : undefined,
corrected_new_span: r.corrected_new_span ? String(r.corrected_new_span).trim() : undefined,
}));
const stillNeedsCorrection: PlannedCommentItem[] = [];
const newlyValid: PlannedCommentItem[] = [];
const unresolved: PlannedCommentItem[] = [];
const revalidationTrace = createTraceChild(trace, {
name: "comment_anchor_correction",
run_type: "chain",
inputs: {
doc_id: docLabel,
document_id: plan.documentId,
corrections_attempted: corrections.length,
source_tool: sourceToolName,
},
tags: ["verification", "comments", "correction"],
});
for (const correction of corrections) {
const originalItem = plan.needsCorrection.find((n) => n.comment_key === correction.comment_key);
if (!originalItem) continue;
// Ensure at most 1 repair attempt is allowed (MAX_FINDING_REPAIR_ATTEMPTS = 1)
const currentAttempts = originalItem.repairAttempts ?? 0;
if (currentAttempts >= MAX_FINDING_REPAIR_ATTEMPTS) {
// Apply safe downgrade to COMMENT if defined, else fail permanently
if (originalItem.terminalFallback === "COMMENT") {
const downgraded = downgradeMalformedEditToComment(
originalItem,
"Max repair attempts exceeded. Falling back to COMMENT."
);
const result = validatePlannedFinding({
intent: downgraded,
manifest: plan.manifest,
currentSurface: plan.currentSurface,
allowCommentFallback: false,
});
if (result.ok) {
newlyValid.push(result.item);
} else {
unresolved.push({
...result.item,
failureCode: result.failureCode ?? "max_repairs_exceeded_downgrade_failed",
message: `Max repair attempts exceeded. Fallback to COMMENT failed: ${result.message}`,
});
}
} else {
unresolved.push({
...originalItem,
failureCode: "max_repairs_exceeded",
message: "Maximum repair attempts exceeded. Finding could not be repaired.",
});
}
continue;
}
if (correction.status === "unresolved") {
if (originalItem.terminalFallback === "COMMENT") {
const downgraded = downgradeMalformedEditToComment(
originalItem,
correction.reason || "Model marked unresolved. Applying safe fallback COMMENT."
);
const result = validatePlannedFinding({
intent: downgraded,
manifest: plan.manifest,
currentSurface: plan.currentSurface,
allowCommentFallback: false,
});
if (result.ok) {
newlyValid.push(result.item);
} else {
unresolved.push({
...result.item,
failureCode: result.failureCode ?? "unresolved_downgrade_failed",
message: `Model marked unresolved. Fallback to COMMENT failed: ${result.message}`,
});
}
} else {
unresolved.push({
...originalItem,
failureCode: "unresolved",
message: correction.reason || "Could not confidently identify the correct block or repair finding.",
});
}
continue;
}
// Apply LLM corrected edits to the original item
let correctedItem = applyFindingCorrection(originalItem, correction);
if (trackChanges !== true && isTrackedChangeOperation(correctedItem)) {
correctedItem = downgradeEditIntentToComment(correctedItem, "Document reviewed in comments-only mode");
}
// Run validatePlannedFinding on the corrected item
const result = validatePlannedFinding({
intent: correctedItem,
manifest: plan.manifest,
currentSurface: plan.currentSurface,
allowCommentFallback: false, // Do not trigger automatic fallback
});
if (result.ok) {
newlyValid.push(result.item);
} else {
if (result.item.terminalFallback === "COMMENT") {
const downgraded = downgradeMalformedEditToComment(
result.item,
`Repair attempt failed: ${result.message}. Applying safe fallback COMMENT.`
);
const secondResult = validatePlannedFinding({
intent: downgraded,
manifest: plan.manifest,
currentSurface: plan.currentSurface,
allowCommentFallback: false,
});
if (secondResult.ok) {
newlyValid.push(secondResult.item);
} else {
unresolved.push({
...secondResult.item,
failureCode: secondResult.failureCode ?? "repair_failed_downgrade_failed",
message: `Repair failed: ${result.message}. Downgrade fallback also failed: ${secondResult.message}`,
});
}
} else {
stillNeedsCorrection.push(result.item);
}
}
}
const addressedKeys = new Set(corrections.map((c) => c.comment_key));
for (const unaddressed of plan.needsCorrection) {
if (!addressedKeys.has(unaddressed.comment_key!)) {
if (unaddressed.terminalFallback === "COMMENT") {
const downgraded = downgradeMalformedEditToComment(
unaddressed,
"No correction was provided. Falling back to COMMENT."
);
const result = validatePlannedFinding({
intent: downgraded,
manifest: plan.manifest,
currentSurface: plan.currentSurface,
allowCommentFallback: false,
});
if (result.ok) {
newlyValid.push(result.item);
} else {
unresolved.push({
...result.item,
failureCode: result.failureCode ?? "unaddressed_downgrade_failed",
message: `Unaddressed fallback to COMMENT failed: ${result.message}`,
});
}
} else {
unresolved.push({
...unaddressed,
failureCode: "unresolved",
message: "No correction was provided for this item.",
});
}
}
}
// Merge: original valid + newly corrected valid
const allValid = [...plan.valid, ...newlyValid];
const allFailed = [...plan.failedPermanently, ...stillNeedsCorrection, ...unresolved];
await endTrace(revalidationTrace, {
outputs: {
originallyValid: plan.valid.length,
newlyValid: newlyValid.length,
stillNeedsCorrection: stillNeedsCorrection.length,
unresolved: unresolved.length,
failedPermanently: plan.failedPermanently.length,
newlyValidItems: newlyValid.map((v) => ({
comment_key: v.comment_key,
block_id: v.block_id,
op_type: v.type ?? "COMMENT",
bookmark_name: v.anchor?.bookmark_name ?? null,
})),
stillNeedsCorrectionItems: stillNeedsCorrection.map((c) => ({
comment_key: c.comment_key,
block_id: c.block_id,
op_type: c.type ?? "COMMENT",
failure_code: c.failureCode,
message: c.message,
})),
unresolvedItems: unresolved.map((u) => ({
comment_key: u.comment_key,
block_id: u.block_id,
op_type: u.type ?? "COMMENT",
failure_code: u.failureCode,
message: u.message,
})),
},
error: allFailed.length > 0 ? `${allFailed.length} comment(s) still failed after correction` : null,
});
// Update the pending plan with merged state
pendingCommentPlanState.set(docLabel, {
...plan,
valid: allValid,
needsCorrection: stillNeedsCorrection,
failedPermanently: allFailed,
});
if (stillNeedsCorrection.length > 0) {
const newCorrectionRequests = stillNeedsCorrection.map((item) =>
buildFindingCorrectionRequest({ item, currentSurface: plan.currentSurface })
);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
total: plan.valid.length + plan.needsCorrection.length + plan.failedPermanently.length,
valid: allValid.length,
ignored_pass: plan.ignoredPassReports?.length ?? 0,
needs_correction: stillNeedsCorrection.length,
failed_permanently: allFailed.length - stillNeedsCorrection.length,
ready_to_apply: false,
valid_comments: allValid.map((v) => ({
comment_key: v.comment_key,
block_id: v.block_id,
status: "pending_valid",
})),
correction_requests: newCorrectionRequests,
failed_permanently_items: allFailed.filter(f => !stillNeedsCorrection.some(s => s.comment_key === f.comment_key)).map((f) => ({
comment_key: f.comment_key,
block_id: f.block_id,
failure_code: f.failureCode,
message: f.message,
})),
instructions: `Call ${sourceToolName} for doc_id '${docLabel}' with corrected block_ids/payloads for the ${stillNeedsCorrection.length} item(s) that need correction.`,
}),
};
}
if (allValid.length === 0) {
for (const item of allFailed) {
verificationRows.push({
tool_call_id: tc.id,
ok: false,
doc_id: docLabel,
document_id: plan.documentId,
block_id: item.block_id,
comment_key: item.comment_key,
status: "not_applied",
failure_code: item.failureCode ?? "VALIDATION_FAILED",
message: item.message ?? "Validation failed after correction.",
comment: item.comment,
reasoning: item.reasoning,
anchor_text: item.anchor?.text_preview ?? undefined,
section_heading: findSectionHeading(plan.currentSurface, item.block_id),
op_type: item.type ?? "COMMENT",
bookmark_name: item.anchor?.bookmark_name ?? null,
finding_status: item.status as WorkflowFindingStatus | undefined,
document_order: getDocumentOrder(plan.currentSurface, item.block_id, item.anchor?.bookmark_name ?? null),
apply_status: "failed_validation",
source: sourceToolName,
...buildFindingDiagnostics({ item, applyStatus: "failed_validation" }),
});
}
for (const report of plan.ignoredPassReports ?? []) {
verificationRows.push({
tool_call_id: tc.id,
ok: true,
doc_id: docLabel,
document_id: plan.documentId,
block_id: String(report.block_id ?? ""),
comment_key: String(report.comment_key ?? ""),
status: "ignored_pass",
failure_code: undefined,
message: String(report.ignored_reason ?? "pass_status"),
comment: String(report.comment ?? ""),
reasoning: (report.reasoning && typeof report.reasoning === "object" && "clause_section" in report.reasoning) ? { clause_section: String((report.reasoning as Record<string, unknown>).clause_section ?? "").trim(), violated_standard: String((report.reasoning as Record<string, unknown>).violated_standard ?? "").trim(), zamil_exposure: String((report.reasoning as Record<string, unknown>).zamil_exposure ?? "").trim() } : undefined,
op_type: (typeof report.op_type === "string" ? report.op_type : undefined),
bookmark_name: (typeof report.bookmark_name === "string" ? report.bookmark_name : null) ?? null,
finding_status: undefined,
apply_status: "ignored_pass",
source: sourceToolName,
});
}
pendingCommentPlanState.delete(docLabel);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
doc_id: docLabel,
total: plan.valid.length + plan.needsCorrection.length + plan.failedPermanently.length,
valid: 0,
applied: 0,
failed: allFailed.length,
ready_to_apply: true,
message: `No comments could be applied after correction. Re-read the document and create a new plan if needed.`,
}),
};
}
// Apply merged batch
const writerItems: WriterCommentItem[] = allValid.map((v, i) => ({
id: `plan-comment-${i}`,
args: {
doc_id: docLabel,
block_id: v.block_id,
comment: v.comment,
target_text: v.target_text ?? "",
anchor_mode: v.anchor_mode ?? "paragraph",
before_anchor: v.before_anchor ?? "",
after_anchor: v.after_anchor ?? "",
comment_key: v.comment_key ?? makeCommentKey({
doc_id: docLabel,
block_id: v.block_id,
target_text: v.target_text ?? "",
anchor_mode: v.anchor_mode ?? "paragraph",
comment: v.comment,
}),
type: v.type,
new_text: v.new_text,
insert_text: v.insert_text,
position: v.position,
rewrite_mode: v.rewrite_mode,
old_span: v.old_span,
new_span: v.new_span,
},
anchor: v.anchor!,
reasoning: v.reasoning,
anchor_text: v.anchor?.text_preview ?? undefined,
section_heading: findSectionHeading(plan.currentSurface, v.block_id),
finding_status: v.status as WorkflowFindingStatus,
document_order: getDocumentOrder(plan.currentSurface, v.block_id, v.anchor?.bookmark_name ?? null),
source: sourceToolName,
}));
const prevalidationFailures = [
...allFailed.map((f) => ({
status: "not_applied",
apply_status: "failed_validation" as const,
comment_key: f.comment_key,
model_block_id: f.block_id,
message: f.message,
failure_code: f.failureCode ?? "VALIDATION_FAILED",
op_type: f.type ?? "COMMENT",
comment: f.comment,
reasoning: f.reasoning,
finding_status: f.status,
section_heading: findSectionHeading(plan.currentSurface, f.block_id),
document_order: getDocumentOrder(plan.currentSurface, f.block_id, f.anchor?.bookmark_name ?? null),
bookmark_name: f.anchor?.bookmark_name ?? null,
source: sourceToolName,
...buildFindingDiagnostics({ item: f, applyStatus: "failed_validation" }),
})),
...(plan.ignoredPassReports ?? []).map((report) => ({
...report,
source: sourceToolName,
})),
];
const applyResult = await applyCommentWriterBatch({
userId,
documentId: plan.documentId,
docLabel,
docInfo: plan.docInfo,
current: plan.current,
comments: writerItems,
db,
write,
trace,
docIndex,
docStore,
anchorManifestState,
docsEdited,
prevalidationFailures,
toolCallId: tc.id,
verificationRows,
trackChanges,
});
pendingCommentPlanState.delete(docLabel);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: applyResult.ok,
doc_id: docLabel,
total: plan.valid.length + plan.needsCorrection.length + plan.failedPermanently.length,
valid: allValid.length,
applied: applyResult.ok ? (applyResult.apply_summary?.applied ?? 0) : 0,
failed: allFailed.length,
ready_to_apply: true,
version_id: applyResult.version_id,
version_number: applyResult.version_number,
download_url: applyResult.download_url,
apply_summary: applyResult.apply_summary,
anchor_redirects: allValid
.filter((item) => item.anchorRedirectedFrom)
.map((item) => ({
comment_key: item.comment_key,
from: item.anchorRedirectedFrom,
to: item.anchorRedirectedTo,
reason: item.anchorRedirectReason,
})),
unresolved: unresolved.map((u) => ({
comment_key: u.comment_key,
block_id: u.block_id,
failure_code: u.failureCode,
message: u.message,
})),
}),
};
}
export type WriterAnchorEligibilityInput = {
id: string;
args: {
block_id: string;
comment_key: string;
type?: string;
};
anchor: CanonicalAnchor;
};
export function buildWriterAnchorEligibilityFailures(
comments: WriterAnchorEligibilityInput[],
): Record<string, unknown>[] {
return comments
.filter((item) => !item.anchor.anchor_eligible)
.map((item) => ({
...makeCommentFailureReport({
id: item.id,
commentKey: item.args.comment_key,
blockId: item.args.block_id,
failureCode: "ANCHOR_NOT_ELIGIBLE",
message:
`Block ${item.args.block_id} is not eligible for comments or edits.`,
anchor: item.anchor,
opType: item.args.type,
}),
anchor_rejection_reason:
item.anchor.anchor_rejection_reason ?? "navigation",
}));
}
async function runCommentDocumentBatch(params: {
userId: string;
documentId: string;
docLabel: string;
docInfo: NonNullable<ReturnType<DocStore["get"]>>;
current: NonNullable<Awaited<ReturnType<typeof loadCurrentVersionBytes>>>;
comments: Array<WriterCommentItem>;
db: ReturnType<typeof createServerSupabase>;
trace?: RunTree | null;
trackChanges?: boolean;
}): Promise<{
ok: boolean;
version_id?: string;
version_number?: number;
storage_path?: string;
download_url?: string;
apply_report?: any[];
commented_bytes?: Buffer;
error?: string;
}> {
const { userId, documentId, docInfo, current, comments, db, trace, trackChanges } = params;
if (comments.length === 0) return { ok: false, error: "No validated comments to apply." };
const eligibilityFailures = buildWriterAnchorEligibilityFailures(comments);
if (eligibilityFailures.length > 0) {
return {
ok: false,
error:
"Writer boundary rejected one or more navigation-region anchors.",
apply_report: eligibilityFailures,
};
}
try {
const pythonUrl = process.env.PYTHON_SERVICE_URL || "http://localhost:8000";
const response = await fetch(`${pythonUrl}/api/apply-comments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-user-id": userId,
...traceHeaders(trace),
},
body: JSON.stringify({
document_bytes_b64: Buffer.from(current.bytes).toString("base64"),
document_name: docInfo.filename,
track_changes: trackChanges === true,
findings: comments.map((item) => ({
id: item.id,
model_block_id: item.args.block_id,
comment_key: item.args.comment_key,
risk_level: "info",
clause_type: "Comment",
issue: "Assistant Feedback",
comment: item.args.comment,
review_reasoning: { clause_section: "N/A", violated_standard: "N/A", zamil_exposure: "Manual chat comment not from playbook review" },
target_text: item.args.target_text,
anchor_mode: item.args.anchor_mode,
start_paragraph_id: item.args.block_id,
end_paragraph_id: item.args.block_id,
start_anchor: item.args.target_text ? item.args.target_text.slice(0, 220) : "",
end_anchor: item.args.target_text ? item.args.target_text.slice(-220) : "",
before_anchor: item.args.before_anchor || "",
after_anchor: item.args.after_anchor || "",
confidence: 1.0,
stable_id: item.anchor.stable_id,
native_para_id: item.anchor.native_para_id,
text_hash: item.anchor.text_hash,
xml_path: item.anchor.xml_path,
op_type: item.args.type || "COMMENT",
new_text: item.args.new_text ?? null,
insert_text: item.args.insert_text ?? null,
position: item.args.position ?? null,
rewrite_mode: item.args.rewrite_mode ?? null,
old_span: item.args.old_span ?? null,
new_span: item.args.new_span ?? null,
bookmark_name: item.anchor?.bookmark_name ?? null,
})),
}),
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
return { ok: false, error: errData.detail || `Python service error: ${response.statusText}` };
}
const data = await response.json();
const applyReport = Array.isArray(data.apply_report) ? data.apply_report : [];
const appliedCount = applyReport.filter((r: any) => r.status === "applied").length;
const commentedBytesB64 =
typeof data.commented_bytes_b64 === "string"
? data.commented_bytes_b64
: typeof data.document_bytes_b64 === "string"
? data.document_bytes_b64
: "";
if (appliedCount === 0) {
return {
ok: false,
error: "No comments were inserted.",
apply_report: applyReport,
};
}
if (!commentedBytesB64) {
return {
ok: false,
error: "Python service did not return commented document bytes.",
apply_report: applyReport,
};
}
const commentedBytes = Buffer.from(commentedBytesB64, "base64");
const versionId = crypto.randomUUID().replace(/-/g, "");
const newPath = `documents/${userId}/${documentId}/comments/${versionId}.docx`;
const exact = commentedBytes.buffer.slice(
commentedBytes.byteOffset,
commentedBytes.byteOffset + commentedBytes.byteLength,
) as ArrayBuffer;
await uploadFile(
newPath,
exact,
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
const { data: maxRow } = await db
.from("document_versions")
.select("version_number")
.eq("document_id", documentId)
.order("version_number", { ascending: false })
.limit(1)
.maybeSingle();
const nextVersionNumber = ((maxRow?.version_number as number | null) ?? 1) + 1;
const { data: versionRow, error: verErr } = await db
.from("document_versions")
.insert({
document_id: documentId,
storage_path: newPath,
source: "assistant_edit",
version_number: nextVersionNumber,
display_name: docInfo.filename,
})
.select("id")
.single();
if (verErr || !versionRow) {
return { ok: false, error: "Failed to record document version.", apply_report: applyReport };
}
await db
.from("documents")
.update({ current_version_id: versionRow.id })
.eq("id", documentId);
return {
ok: true,
version_id: versionRow.id,
version_number: nextVersionNumber,
storage_path: newPath,
download_url: buildDownloadUrl(newPath, docInfo.filename),
apply_report: applyReport,
commented_bytes: commentedBytes,
};
} catch (err) {
console.error("[runCommentDocumentBatch] error:", err);
return { ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
type DirectInsertTextOperation = {
op: "insert_text";
location?:
| { type: "document_end" }
| { type: "before_block"; block_id?: string }
| { type: "after_block"; block_id?: string }
| { type: "at_offset"; block_id?: string; offset?: number };
text?: string;
reason?: string;
};
type DirectReplaceTextOperation = {
op: "replace_text";
block_id?: string;
find?: string;
replacement?: string;
occurrence?: number;
reason?: string;
};
type DirectRewriteBlockOperation = {
op: "rewrite_block";
block_id?: string;
new_text?: string;
reason?: string;
};
type DirectDeleteTextOperation = {
op: "delete_text";
block_id?: string;
start_offset?: number;
end_offset?: number;
find?: string;
occurrence?: number;
reason?: string;
};
type DirectAddCommentOperation = {
op: "add_comment";
block_id?: string;
comment?: string;
target_text?: string;
anchor_mode?: "paragraph" | "exact";
reason?: string;
};
type DirectDocumentOperation =
| DirectInsertTextOperation
| DirectReplaceTextOperation
| DirectRewriteBlockOperation
| DirectDeleteTextOperation
| DirectAddCommentOperation;
type DirectNormalizedComment = {
operationIndex: number;
block_id: string;
comment: string;
target_text: string;
anchor_mode: "paragraph" | "exact";
reason?: string;
anchor: CanonicalAnchor;
};
type DirectOperationNormalizationResult = {
trackedOperations: EditOperation[];
comments: DirectNormalizedComment[];
errors: { index: number; reason: string }[];
};
function isObjectRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function asString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}
function asTrimmedString(value: unknown): string | undefined {
const s = asString(value);
if (!s) return undefined;
const trimmed = s.trim();
return trimmed ? trimmed : undefined;
}
function asInteger(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value)
? Math.floor(value)
: undefined;
}
function findNthDirectOccurrence(text: string, needle: string, occurrence: number): number | null {
if (!needle) return null;
let from = 0;
let seen = 0;
while (from <= text.length - needle.length) {
const pos = text.indexOf(needle, from);
if (pos < 0) return null;
if (seen === occurrence) return pos;
seen += 1;
from = pos + Math.max(1, needle.length);
}
return null;
}
function isDirectEditRedZone(anchor: CanonicalAnchor | undefined): { blocked: true; reason: string } | { blocked: false } {
if (!anchor) return { blocked: false };
if (!anchor.anchor_eligible) {
return {
blocked: true,
reason:
`Block '${anchor.block_id}' is in a navigation region and is not eligible for edits.`,
};
}
const styleId = `${String((anchor as any).style_id ?? "")} ${String((anchor as any).style_name ?? "")}`.toLowerCase();
const preview = String((anchor as any).text_preview ?? "").toLowerCase();
const redZoneStyleFragments = [
"toc",
"toc1",
"toc2",
"toc3",
"toc4",
"tocheading",
"tocentry",
];
const redZoneTextFragments = [
"table of contents",
"signature page",
"execution page",
"cover page",
];
if (redZoneStyleFragments.some((fragment) => styleId.includes(fragment))) {
return {
blocked: true,
reason: "Direct tracked edits to table-of-contents blocks are not allowed.",
};
}
if (redZoneTextFragments.some((fragment) => preview.includes(fragment))) {
return {
blocked: true,
reason: "Direct tracked edits to cover, TOC, signature, or execution page blocks are not allowed.",
};
}
return { blocked: false };
}
function normalizeDirectSemanticOperations(
rawOperations: unknown,
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>,
): DirectOperationNormalizationResult {
const trackedOperations: EditOperation[] = [];
const comments: DirectNormalizedComment[] = [];
const errors: { index: number; reason: string }[] = [];
if (!Array.isArray(rawOperations)) {
return {
trackedOperations,
comments,
errors: [{ index: 0, reason: "operations must be an array." }],
};
}
if (rawOperations.length === 0) {
return {
trackedOperations,
comments,
errors: [{ index: 0, reason: "operations must contain at least one operation." }],
};
}
const blockById = new Map(surface.blocks.map((block) => [block.id, block]));
const anchorByBlockId = new Map(surface.anchors.map((anchor) => [anchor.block_id, anchor]));
for (let i = 0; i < rawOperations.length; i++) {
const raw = rawOperations[i];
if (!isObjectRecord(raw)) {
errors.push({ index: i, reason: "Operation must be an object." });
continue;
}
const op = asString(raw.op);
if (op === "insert_text") {
const text = asTrimmedString(raw.text);
const location = raw.location;
if (!text) {
errors.push({ index: i, reason: "insert_text requires non-empty text." });
continue;
}
if (!isObjectRecord(location)) {
errors.push({ index: i, reason: "insert_text requires a location object." });
continue;
}
const locationType = asString(location.type);
const reason = asString(raw.reason);
if (locationType === "document_end") {
trackedOperations.push({
op: "insert_at_document_end",
text,
reason,
});
continue;
}
const blockId = asTrimmedString(location.block_id);
if (!blockId) {
errors.push({
index: i,
reason: `insert_text location '${locationType ?? "unknown"}' requires block_id.`,
});
continue;
}
const block = blockById.get(blockId);
const anchor = anchorByBlockId.get(blockId);
if (!block) {
errors.push({
index: i,
reason: `Unknown block_id '${blockId}'. Call inspect_document or search_document and retry with a current block_id.`,
});
continue;
}
const redZone = isDirectEditRedZone(anchor);
if (redZone.blocked) {
errors.push({ index: i, reason: redZone.reason });
continue;
}
if (locationType === "before_block") {
trackedOperations.push({
op: "insert_before_block",
block_id: blockId,
text,
reason,
});
continue;
}
if (locationType === "after_block") {
trackedOperations.push({
op: "insert_after_block",
block_id: blockId,
text,
reason,
});
continue;
}
if (locationType === "at_offset") {
const offset = asInteger(location.offset);
if (offset == null || offset < 0 || offset > block.length) {
errors.push({
index: i,
reason: `insert_text at_offset requires offset between 0 and ${block.length}.`,
});
continue;
}
trackedOperations.push({
op: "insert_at_offset",
block_id: blockId,
offset,
text,
reason,
});
continue;
}
errors.push({
index: i,
reason: `Unsupported insert_text location type '${locationType ?? "missing"}'.`,
});
continue;
}
if (op === "replace_text") {
const blockId = asTrimmedString(raw.block_id);
const find = asString(raw.find);
const replacement = asString(raw.replacement);
const occurrence = asInteger(raw.occurrence);
const reason = asString(raw.reason);
if (!blockId || !find || replacement == null) {
errors.push({
index: i,
reason: "replace_text requires block_id, find, and replacement.",
});
continue;
}
const block = blockById.get(blockId);
const anchor = anchorByBlockId.get(blockId);
if (!block) {
errors.push({
index: i,
reason: `Unknown block_id '${blockId}'. Call inspect_document or search_document and retry with a current block_id.`,
});
continue;
}
const redZone = isDirectEditRedZone(anchor);
if (redZone.blocked) {
errors.push({ index: i, reason: redZone.reason });
continue;
}
trackedOperations.push({
op: "replace_text_in_block",
block_id: blockId,
find,
occurrence: occurrence ?? 0,
text: replacement,
reason,
});
continue;
}
if (op === "rewrite_block") {
const blockId = asTrimmedString(raw.block_id);
const newText = asString(raw.new_text);
const reason = asString(raw.reason);
if (!blockId || newText == null) {
errors.push({
index: i,
reason: "rewrite_block requires block_id and new_text.",
});
continue;
}
const block = blockById.get(blockId);
const anchor = anchorByBlockId.get(blockId);
if (!block) {
errors.push({
index: i,
reason: `Unknown block_id '${blockId}'. Call inspect_document or search_document and retry with a current block_id.`,
});
continue;
}
const redZone = isDirectEditRedZone(anchor);
if (redZone.blocked) {
errors.push({ index: i, reason: redZone.reason });
continue;
}
trackedOperations.push({
op: "replace_range",
block_id: blockId,
start_offset: 0,
end_offset: block.length,
text: newText,
reason,
});
continue;
}
if (op === "delete_text") {
const blockId = asTrimmedString(raw.block_id);
const reason = asString(raw.reason);
if (!blockId) {
errors.push({
index: i,
reason: "delete_text requires block_id.",
});
continue;
}
const block = blockById.get(blockId);
const anchor = anchorByBlockId.get(blockId);
if (!block) {
errors.push({
index: i,
reason: `Unknown block_id '${blockId}'. Call inspect_document or search_document and retry with a current block_id.`,
});
continue;
}
const redZone = isDirectEditRedZone(anchor);
if (redZone.blocked) {
errors.push({ index: i, reason: redZone.reason });
continue;
}
const find = asString(raw.find);
if (find) {
const occurrence = Math.max(0, asInteger(raw.occurrence) ?? 0);
const start = findNthDirectOccurrence(block.text, find, occurrence);
if (start == null) {
errors.push({
index: i,
reason: `Could not find occurrence ${occurrence} of '${find.slice(0, 80)}' in block ${blockId}.`,
});
continue;
}
trackedOperations.push({
op: "delete_range",
block_id: blockId,
start_offset: start,
end_offset: start + find.length,
reason,
});
continue;
}
const startOffset = asInteger(raw.start_offset);
const endOffset = asInteger(raw.end_offset);
if (
startOffset == null ||
endOffset == null ||
startOffset < 0 ||
endOffset < startOffset ||
endOffset > block.length
) {
errors.push({
index: i,
reason: `delete_text requires either find or a valid start_offset/end_offset range between 0 and ${block.length}.`,
});
continue;
}
trackedOperations.push({
op: "delete_range",
block_id: blockId,
start_offset: startOffset,
end_offset: endOffset,
reason,
});
continue;
}
if (op === "add_comment") {
const blockId = asTrimmedString(raw.block_id);
const comment = asTrimmedString(raw.comment);
const targetText = asString(raw.target_text) ?? "";
const anchorModeRaw = asString(raw.anchor_mode);
const anchorMode: "paragraph" | "exact" =
anchorModeRaw === "exact" ? "exact" : "paragraph";
if (!blockId || !comment) {
errors.push({
index: i,
reason: "add_comment requires block_id and comment.",
});
continue;
}
const block = blockById.get(blockId);
const anchor = anchorByBlockId.get(blockId);
if (!block || !anchor) {
errors.push({
index: i,
reason: `Unknown block_id '${blockId}'. Call inspect_document or search_document and retry with a current block_id.`,
});
continue;
}
if (!anchor.commentable) {
errors.push({
index: i,
reason: `Block '${blockId}' is not safely commentable.`,
});
continue;
}
if (!anchor.anchor_eligible) {
errors.push({
index: i,
reason: `Block '${blockId}' is in a navigation region and is not eligible for comments.`,
});
continue;
}
if (anchorMode === "exact" && !targetText.trim()) {
errors.push({
index: i,
reason: "add_comment with anchor_mode='exact' requires target_text.",
});
continue;
}
comments.push({
operationIndex: i,
block_id: blockId,
comment,
target_text: targetText,
anchor_mode: anchorMode,
reason: asString(raw.reason),
anchor,
});
continue;
}
errors.push({
index: i,
reason: `Unsupported operation '${op ?? "missing"}'. Valid operations are insert_text, replace_text, rewrite_block, delete_text, add_comment.`,
});
}
return { trackedOperations, comments, errors };
}
function renderDirectInspectSurface(params: {
surface: Awaited<ReturnType<typeof buildDocxEditSurface>>;
scope: string;
query?: string;
}): string {
const { surface, scope, query } = params;
const lines: string[] = [];
const pushBlock = (index: number) => {
const block = surface.blocks[index];
const anchor = surface.anchors[index];
if (!block) return;
const container = anchor?.container === "table_cell" ? " container=table_cell" : "";
const eligibility =
anchor?.anchor_eligible === false
? ` anchor_eligible=false reason=${anchor.anchor_rejection_reason ?? "navigation"}`
: " anchor_eligible=true";
lines.push(
`[[${block.id} type=${block.kind}${container} chars=${block.length} index=${index}${eligibility}]]`,
);
lines.push(block.text);
lines.push("");
};
lines.push(`[DOCX_EDIT_SURFACE surface_id=${surface.surfaceId}]`);
lines.push(`[[DOC_END after=${surface.documentEnd.afterBlockId ?? "NONE"}]]`);
lines.push("");
if (scope === "full") {
for (let i = 0; i < surface.blocks.length; i++) pushBlock(i);
return lines.join("\n");
}
if (scope === "tail") {
const start = Math.max(0, surface.blocks.length - 24);
for (let i = start; i < surface.blocks.length; i++) pushBlock(i);
return lines.join("\n");
}
if (scope === "headings") {
const headingRe = /^\s*((section|clause|article|schedule|exhibit|annex|appendix)\s+\d+|\d+(\.\d+)*\s+|[A-Z][A-Z0-9 ,/&()\-]{6,})/i;
for (let i = 0; i < surface.blocks.length; i++) {
if (surface.anchors[i]?.anchor_eligible === false) continue;
const text = surface.blocks[i].text.trim();
if (text && text.length <= 180 && headingRe.test(text)) {
pushBlock(i);
}
}
if (lines.length <= 3) {
lines.push("No likely headings detected. Use scope='summary' or search_document.");
}
return lines.join("\n");
}
if (scope === "around_search_hit") {
const q = query?.trim() ?? "";
if (!q) {
lines.push("scope='around_search_hit' requires query.");
return lines.join("\n");
}
const search = findInDocxEditSurface({
surface,
query: q,
maxResults: 8,
contextChars: 120,
});
if (search.hits.length === 0) {
lines.push(`No hits for query: ${q}`);
return lines.join("\n");
}
const indices = new Set<number>();
for (const hit of search.hits) {
const idx = surface.blocks.findIndex((b) => b.id === hit.block_id);
if (idx >= 0) {
indices.add(Math.max(0, idx - 1));
indices.add(idx);
indices.add(Math.min(surface.blocks.length - 1, idx + 1));
}
}
for (const idx of Array.from(indices).sort((a, b) => a - b)) {
pushBlock(idx);
}
lines.push(`[SEARCH_SUMMARY query=${JSON.stringify(q)} total_matches=${search.totalMatches} returned_hits=${search.hits.length}]`);
return lines.join("\n");
}
const headCount = Math.min(12, surface.blocks.length);
for (let i = 0; i < headCount; i++) pushBlock(i);
if (surface.blocks.length > headCount) {
lines.push(`... ${surface.blocks.length - headCount} additional block(s) omitted. Use scope='full', scope='tail', scope='headings', or search_document to inspect more.`);
}
return lines.join("\n");
}
function buildDirectWriterCommentItems(params: {
docLabel: string;
toolCallId: string;
comments: DirectNormalizedComment[];
}): WriterCommentItem[] {
return params.comments.map((comment, index) => ({
id: `${params.toolCallId}:direct-comment:${index}`,
args: {
doc_id: params.docLabel,
block_id: comment.block_id,
comment: comment.comment,
target_text: comment.target_text,
anchor_mode: comment.anchor_mode,
before_anchor: "",
after_anchor: "",
comment_key: `direct-comment-${index + 1}`,
type: "COMMENT",
},
anchor: comment.anchor,
anchor_text: comment.target_text || comment.anchor.text_preview,
section_heading: null,
source: "plan_comments_on_document",
}));
}
async function applyDirectCommentsToDocxBytes(params: {
userId: string;
documentName: string;
bytes: Buffer;
comments: WriterCommentItem[];
trace?: RunTree | null;
}): Promise<{
ok: true;
bytes: Buffer;
apply_report: any[];
} | {
ok: false;
error: string;
apply_report?: any[];
}> {
const { userId, documentName, bytes, comments, trace } = params;
if (comments.length === 0) {
return { ok: true, bytes, apply_report: [] };
}
try {
const pythonUrl = process.env.PYTHON_SERVICE_URL || "http://localhost:8000";
const response = await fetch(`${pythonUrl}/api/apply-comments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-user-id": userId,
...traceHeaders(trace),
},
body: JSON.stringify({
document_bytes_b64: Buffer.from(bytes).toString("base64"),
document_name: documentName,
track_changes: false,
findings: comments.map((item) => ({
id: item.id,
model_block_id: item.args.block_id,
comment_key: item.args.comment_key,
risk_level: "info",
clause_type: "DirectEditComment",
issue: "Direct document edit comment",
comment: item.args.comment,
review_reasoning: {
clause_section: "N/A",
violated_standard: "N/A",
zamil_exposure: "Direct user-requested comment",
},
target_text: item.args.target_text,
anchor_mode: item.args.anchor_mode,
start_paragraph_id: item.args.block_id,
end_paragraph_id: item.args.block_id,
start_anchor: item.args.target_text ? item.args.target_text.slice(0, 220) : "",
end_anchor: item.args.target_text ? item.args.target_text.slice(-220) : "",
before_anchor: item.args.before_anchor || "",
after_anchor: item.args.after_anchor || "",
confidence: 1.0,
stable_id: item.anchor.stable_id,
native_para_id: item.anchor.native_para_id,
text_hash: item.anchor.text_hash,
xml_path: item.anchor.xml_path,
op_type: "COMMENT",
new_text: null,
insert_text: null,
position: null,
rewrite_mode: null,
old_span: null,
new_span: null,
bookmark_name: item.anchor?.bookmark_name ?? null,
})),
}),
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
return {
ok: false,
error: errData.detail || `Python service error: ${response.statusText}`,
};
}
const data = await response.json();
const applyReport = Array.isArray(data.apply_report) ? data.apply_report : [];
const appliedCount = applyReport.filter((r: any) => r.status === "applied").length;
const commentedBytesB64 =
typeof data.commented_bytes_b64 === "string"
? data.commented_bytes_b64
: typeof data.document_bytes_b64 === "string"
? data.document_bytes_b64
: "";
if (appliedCount === 0) {
return {
ok: false,
error: "No direct comments were inserted.",
apply_report: applyReport,
};
}
if (!commentedBytesB64) {
return {
ok: false,
error: "Python service did not return commented document bytes.",
apply_report: applyReport,
};
}
return {
ok: true,
bytes: Buffer.from(commentedBytesB64, "base64"),
apply_report: applyReport,
};
} catch (err) {
console.error("[applyDirectCommentsToDocxBytes] error:", err);
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
async function handleInspectDocumentTool(params: {
tc: ToolCall;
args: Record<string, unknown>;
docStore: DocStore;
docIndex: DocIndex;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
anchorManifestState?: AnchorManifestState;
trace?: RunTree | null;
}): Promise<{
toolResult: { role: "tool"; tool_call_id: string; content: string };
docRead?: { filename: string; document_id?: string };
}> {
const { tc, args, docStore, docIndex, db, write, anchorManifestState, trace } = params;
const rawDocId = asString(args.doc_id) ?? "";
const docLabel = resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const scope = asString(args.scope) ?? "summary";
const query = asString(args.query);
const docInfo = docStore.get(docLabel);
const indexed = docIndex[docLabel];
if (!docInfo || !indexed) {
return {
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_NOT_FOUND",
message: `Document '${rawDocId}' was not found.`,
}),
},
};
}
if (docInfo.file_type !== "docx") {
return {
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "UNSUPPORTED_FILE_TYPE",
message: "inspect_document only supports DOCX files in direct edit mode.",
}),
},
};
}
write(
`data: ${JSON.stringify({
type: "doc_read_start",
filename: docInfo.filename,
document_id: indexed.document_id,
})}\n\n`,
);
const current = await loadCurrentVersionBytes(indexed.document_id, db);
if (!current) {
write(
`data: ${JSON.stringify({
type: "doc_read",
filename: docInfo.filename,
document_id: indexed.document_id,
})}\n\n`,
);
return {
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_BYTES_UNAVAILABLE",
message: "Could not load current DOCX bytes.",
}),
},
};
}
const surface = await buildDocxEditSurface(current.bytes, {
versionId: current.version_id,
});
rememberAnchorManifest(
anchorManifestState,
docLabel,
indexed.document_id,
surface,
);
if (trace) {
const renderedForTrace = renderDocxEditSurfaceForModel(surface);
const surfaceTrace = buildDocumentSurfaceTracePayload({
docLabel,
documentId: indexed.document_id,
filename: docInfo.filename,
surfaceId: surface.surfaceId,
text: renderedForTrace,
});
const surfTrace = createTraceChild(trace, {
name: "inspect_document.surface",
run_type: "chain",
inputs: surfaceTrace,
tags: ["document_surface_for_model", "direct_docx_edit"],
});
void endTrace(surfTrace, {
outputs: {
char_count: renderedForTrace.length,
block_count: surface.anchors.length,
surface_id: surface.surfaceId,
scope,
},
});
}
const text = renderDirectInspectSurface({
surface,
scope,
query,
});
write(
`data: ${JSON.stringify({
type: "doc_read",
filename: docInfo.filename,
document_id: indexed.document_id,
})}\n\n`,
);
return {
docRead: {
filename: docInfo.filename,
document_id: indexed.document_id,
},
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
document_id: indexed.document_id,
filename: docInfo.filename,
version_id: current.version_id,
version_number: current.version_number,
surface_id: surface.surfaceId,
scope,
block_count: surface.blocks.length,
document_end: surface.documentEnd,
content: text,
}),
},
};
}
async function handleSearchDocumentTool(params: {
tc: ToolCall;
args: Record<string, unknown>;
docStore: DocStore;
docIndex: DocIndex;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
anchorManifestState?: AnchorManifestState;
}): Promise<{
toolResult: { role: "tool"; tool_call_id: string; content: string };
docFound?: { filename: string; query: string; total_matches: number };
}> {
const { tc, args, docStore, docIndex, db, write, anchorManifestState } = params;
const rawDocId = asString(args.doc_id) ?? "";
const docLabel = resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const query = asString(args.query) ?? "";
const maxResults = asInteger(args.max_results);
const docInfo = docStore.get(docLabel);
const indexed = docIndex[docLabel];
if (!docInfo || !indexed) {
return {
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "DOCUMENT_NOT_FOUND",
message: `Document '${rawDocId}' was not found.`,
}),
},
};
}
if (docInfo.file_type !== "docx") {
return {
toolResult: {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
error: "UNSUPPORTED_FILE_TYPE",
message: "search_document only supports DOCX files in direct edit mode.",
}),
},
};
}
const content = await findInDocumentContent({
docLabel,
query,
maxResults,
contextChars: 140,
docStore,
write,
docIndex,
db,
anchorManifestState,
});
let totalMatches = 0;
try {
const parsed = JSON.parse(content) as { total_matches?: number };
totalMatches = parsed.total_matches ?? 0;
} catch {
totalMatches = 0;
}
return {
docFound: {
filename: docInfo.filename,
query,
total_matches: totalMatches,
},
toolResult: {
role: "tool",
tool_call_id: tc.id,
content,
},
};
}
async function handleEditDocumentTool(params: {
tc: ToolCall;
args: Record<string, unknown>;
docStore: DocStore;
docIndex: DocIndex;
userId: string;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
trace?: RunTree | null;
anchorManifestState?: AnchorManifestState;
docsEdited: DocEditedResult[];
}): Promise<{ role: "tool"; tool_call_id: string; content: string }> {
const {
tc,
args,
docStore,
docIndex,
userId,
db,
write,
trace,
anchorManifestState,
docsEdited,
} = params;
const rawDocId = asString(args.doc_id) ?? "";
const docLabel = resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const userVisibleSummary =
asTrimmedString(args.user_visible_summary) ??
"Applied the requested document edit.";
const docInfo = docStore.get(docLabel);
const indexed = docIndex[docLabel];
const fail = (payload: Record<string, unknown>) => ({
role: "tool" as const,
tool_call_id: tc.id,
content: JSON.stringify({
ok: false,
doc_id: docLabel,
...payload,
}),
});
if (!docInfo || !indexed) {
return fail({
error: "DOCUMENT_NOT_FOUND",
message: `Document '${rawDocId}' was not found.`,
});
}
if (docInfo.file_type !== "docx") {
return fail({
error: "UNSUPPORTED_FILE_TYPE",
message: "edit_document only supports DOCX files.",
});
}
const current = await loadCurrentVersionBytes(indexed.document_id, db);
if (!current) {
return fail({
error: "DOCUMENT_BYTES_UNAVAILABLE",
message: "Could not load current DOCX bytes.",
});
}
const surface = await buildDocxEditSurface(current.bytes, {
versionId: current.version_id,
});
rememberAnchorManifest(
anchorManifestState,
docLabel,
indexed.document_id,
surface,
);
const normalized = normalizeDirectSemanticOperations(args.operations, surface);
if (normalized.errors.length > 0) {
return fail({
error: "VALIDATION_FAILED",
needs_correction: true,
errors: normalized.errors,
hint: "Call inspect_document or search_document and retry with valid locations, block IDs, offsets, and text.",
});
}
if (
normalized.trackedOperations.length === 0 &&
normalized.comments.length === 0
) {
return fail({
error: "NO_OPERATIONS",
needs_correction: true,
message: "No valid operations were supplied.",
});
}
const writerComments = buildDirectWriterCommentItems({
docLabel,
toolCallId: tc.id,
comments: normalized.comments,
});
write(
`data: ${JSON.stringify({
type: "doc_edited_start",
filename: docInfo.filename,
})}\n\n`,
);
if (
normalized.comments.length > 0 &&
normalized.trackedOperations.length === 0
) {
const batchResult = await runCommentDocumentBatch({
userId,
documentId: indexed.document_id,
docLabel,
docInfo,
current,
comments: writerComments,
db,
trace,
trackChanges: false,
});
if (batchResult.ok && batchResult.download_url && batchResult.version_id) {
if (docIndex[docLabel]) {
docIndex[docLabel] = {
...docIndex[docLabel],
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
};
}
const currentDocStore = docStore.get(docLabel);
if (currentDocStore && batchResult.storage_path) {
docStore.set(docLabel, {
...currentDocStore,
storage_path: batchResult.storage_path,
});
}
const applyReport = batchResult.apply_report ?? [];
const payload: DocEditedResult = {
filename: docInfo.filename,
document_id: indexed.document_id,
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
download_url: batchResult.download_url,
annotations: [],
apply_report: applyReport,
apply_summary: buildApplySummary(applyReport),
};
docsEdited.push(payload);
write(
`data: ${JSON.stringify({
type: "doc_edited",
...payload,
})}\n\n`,
);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
document_id: indexed.document_id,
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
applied_operations: normalized.comments.length,
summary: userVisibleSummary,
apply_report: applyReport,
}),
};
}
const applyReport = batchResult.apply_report ?? [];
write(
`data: ${JSON.stringify({
type: "doc_edited",
filename: docInfo.filename,
document_id: indexed.document_id,
version_id: "",
version_number: null,
download_url: "",
annotations: [],
apply_report: applyReport,
apply_summary: buildApplySummary(applyReport),
error: batchResult.error ?? "No comments were inserted.",
})}\n\n`,
);
return fail({
error: "COMMENT_WRITE_FAILED",
message: batchResult.error ?? "No comments were inserted.",
apply_report: applyReport,
});
}
let baseBytesOverride: Buffer | undefined;
let extraApplyReport: any[] | undefined;
let extraApplySummary:
| {
total: number;
applied: number;
failed: number;
failures: {
id: string;
target_text: string;
message: string;
paragraph_id: string | null;
}[];
}
| undefined;
if (writerComments.length > 0) {
const commented = await applyDirectCommentsToDocxBytes({
userId,
documentName: docInfo.filename,
bytes: current.bytes,
comments: writerComments,
trace,
});
if (!commented.ok) {
const applyReport = commented.apply_report ?? [];
write(
`data: ${JSON.stringify({
type: "doc_edited",
filename: docInfo.filename,
document_id: indexed.document_id,
version_id: "",
version_number: null,
download_url: "",
annotations: [],
apply_report: applyReport,
apply_summary: buildApplySummary(applyReport),
error: commented.error,
})}\n\n`,
);
return fail({
error: "COMMENT_WRITE_FAILED",
message: commented.error,
apply_report: applyReport,
});
}
baseBytesOverride = commented.bytes;
extraApplyReport = commented.apply_report;
extraApplySummary = buildApplySummary(commented.apply_report);
}
const result = await runEditDocument({
documentId: indexed.document_id,
userId,
operations: normalized.trackedOperations,
expectedSurfaceId: surface.surfaceId,
db,
baseBytesOverride,
extraApplyReport,
extraApplySummary,
});
if (!result.ok) {
write(
`data: ${JSON.stringify({
type: "doc_edited",
filename: docInfo.filename,
document_id: indexed.document_id,
version_id: "",
version_number: null,
download_url: "",
annotations: [],
error: result.error,
})}\n\n`,
);
return fail({
error: "EDIT_WRITE_FAILED",
message: result.error,
});
}
if (docIndex[docLabel]) {
docIndex[docLabel] = {
...docIndex[docLabel],
version_id: result.version_id,
version_number: result.version_number ?? null,
};
}
const currentDocStore = docStore.get(docLabel);
if (currentDocStore && result.storage_path) {
docStore.set(docLabel, {
...currentDocStore,
storage_path: result.storage_path,
});
}
const payload: DocEditedResult = {
filename: docInfo.filename,
document_id: indexed.document_id,
version_id: result.version_id,
version_number: result.version_number ?? null,
download_url: result.download_url,
annotations: result.annotations,
apply_report: result.apply_report,
apply_summary: result.apply_summary,
edit_verification: result.edit_verification,
};
docsEdited.push(payload);
write(
`data: ${JSON.stringify({
type: "doc_edited",
...payload,
})}\n\n`,
);
return {
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
doc_id: docLabel,
document_id: indexed.document_id,
version_id: result.version_id,
version_number: result.version_number ?? null,
applied_operations:
normalized.trackedOperations.length + normalized.comments.length,
tracked_operations: normalized.trackedOperations.length,
comment_operations: normalized.comments.length,
summary: userVisibleSummary,
errors: result.errors,
apply_report: result.apply_report,
edit_verification: result.edit_verification,
}),
};
}
async function processCommentToolCallsBatch(params: {
commentCalls: ToolCall[];
docStore: DocStore;
docIndex: DocIndex;
userId: string;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
trace?: RunTree | null;
commentAttemptState?: CommentAttemptState;
anchorManifestState?: AnchorManifestState;
toolResults: unknown[];
docsEdited: DocEditedResult[];
commentVerificationRows: CommentVerificationRow[];
}): Promise<void> {
const {
commentCalls,
docStore,
docIndex,
userId,
db,
write,
trace,
commentAttemptState,
anchorManifestState,
toolResults,
docsEdited,
commentVerificationRows,
} = params;
const preparedByDoc = new Map<string, PreparedCommentCall[]>();
const resultByToolCallId = new Map<string, string>();
const addReportResult = (
tc: ToolCall,
report: Record<string, any>,
opts: {
docId?: string;
documentId?: string;
blockId?: string;
commentKey?: string;
readSerial?: number;
error?: string;
} = {},
) => {
const status = typeof report.status === "string" ? report.status : "not_applied";
const failureCode =
typeof report.failure_code === "string" ? report.failure_code : undefined;
const content = {
ok: status === "applied",
error: opts.error ?? (status === "applied" ? undefined : failureCode ?? "COMMENT_NOT_APPLIED"),
message: report.message,
doc_id: opts.docId,
document_id: opts.documentId,
block_id: opts.blockId,
comment_key: opts.commentKey,
apply_report: [report],
};
resultByToolCallId.set(tc.id, JSON.stringify(content));
commentVerificationRows.push(
verificationRowFromReport({
toolCallId: tc.id,
docId: opts.docId,
documentId: opts.documentId,
blockId: opts.blockId,
commentKey: opts.commentKey,
report,
}),
);
recordCommentAttempt({
state: commentAttemptState,
commentKey: opts.commentKey,
toolCallId: tc.id,
report,
readSerial: opts.readSerial,
});
};
for (const tc of commentCalls) {
let args: Record<string, unknown> = {};
try {
args = JSON.parse(tc.function.arguments || "{}");
} catch {
/* validation below will report the missing required fields */
}
const sanitizedArgs = sanitizeCommentArgs(args);
const validation = validateCommentArgs(sanitizedArgs, docStore);
if (!validation.valid) {
const report = makeCommentFailureReport({
id: tc.id,
status: "validation_failed",
failureCode: "VALIDATION_FAILED",
message: validation.errors.join("; "),
opType: typeof sanitizedArgs.type === "string" ? sanitizedArgs.type : undefined,
});
addReportResult(tc, report, {
error: "VALIDATION_FAILED",
});
continue;
}
const docIdToUse = validation.validated.doc_id;
const docLabel = resolveDocLabel(docIdToUse, docStore, docIndex) ?? docIdToUse;
const docInfo = docStore.get(docLabel);
const indexed = docIndex[docLabel];
const commentKey = makeCommentKey(validation.validated);
const argsWithKey: ValidatedCommentArgs = {
...validation.validated,
doc_id: docIdToUse,
comment_key: commentKey,
};
if (!docInfo || !indexed) {
const message = `Document '${docIdToUse}' is registered in chat but could not be indexed.`;
const report = makeCommentFailureReport({
id: tc.id,
commentKey,
blockId: validation.validated.block_id,
status: "document_not_found",
failureCode: "DOCUMENT_NOT_FOUND",
message,
opType: typeof sanitizedArgs.type === "string" ? sanitizedArgs.type : undefined,
});
addReportResult(tc, report, {
docId: docIdToUse,
blockId: validation.validated.block_id,
commentKey,
error: "DOCUMENT_NOT_FOUND",
});
continue;
}
if (docInfo.file_type !== "docx") {
const report = makeCommentFailureReport({
id: tc.id,
commentKey,
blockId: validation.validated.block_id,
failureCode: "UNSUPPORTED_DOCUMENT_TYPE",
message: "comment_on_document only supports .docx files.",
opType: typeof sanitizedArgs.type === "string" ? sanitizedArgs.type : undefined,
});
addReportResult(tc, report, {
docId: docIdToUse,
documentId: indexed.document_id,
blockId: validation.validated.block_id,
commentKey,
error: "UNSUPPORTED_DOCUMENT_TYPE",
});
continue;
}
const bucket = preparedByDoc.get(docLabel) ?? [];
bucket.push({
tc,
docLabel,
docInfo,
indexed,
args: argsWithKey,
op_type: typeof sanitizedArgs.type === "string" ? sanitizedArgs.type : undefined,
});
preparedByDoc.set(docLabel, bucket);
}
for (const [docLabel, preparedCalls] of preparedByDoc) {
const first = preparedCalls[0];
const current = await loadCurrentVersionBytes(first.indexed.document_id, db);
if (!current) {
for (const prepared of preparedCalls) {
const report = makeCommentFailureReport({
id: prepared.tc.id,
commentKey: prepared.args.comment_key,
blockId: prepared.args.block_id,
failureCode: "DOCUMENT_BYTES_UNAVAILABLE",
message: "Could not load current document bytes.",
opType: prepared.op_type,
});
addReportResult(prepared.tc, report, {
docId: prepared.args.doc_id,
documentId: prepared.indexed.document_id,
blockId: prepared.args.block_id,
commentKey: prepared.args.comment_key,
error: "DOCUMENT_BYTES_UNAVAILABLE",
});
}
continue;
}
const currentSurface = await buildDocxEditSurface(current.bytes, {
versionId: current.version_id,
});
const manifest = anchorManifestState?.byDocLabel.get(docLabel);
const validationTrace = createTraceChild(trace, {
name: "comment_batch_validation",
run_type: "chain",
inputs: {
doc_id: docLabel,
document_id: first.indexed.document_id,
comments_attempted: preparedCalls.length,
manifest_surface_id: manifest?.surfaceId ?? null,
current_surface_id: currentSurface.surfaceId,
},
tags: ["verification", "comments"],
});
const validForWriter: Array<PreparedCommentCall & { anchor: CanonicalAnchor; readSerial: number }> = [];
const prevalidationFailures: Record<string, any>[] = [];
for (const prepared of preparedCalls) {
const validated = validatePreparedCommentAnchor({
prepared,
manifest,
currentSurface,
commentAttemptState,
});
if (validated.ok) {
validForWriter.push({
...prepared,
anchor: validated.anchor,
readSerial: validated.readSerial,
});
continue;
}
const report = validated.report as Record<string, any>;
prevalidationFailures.push(report);
addReportResult(prepared.tc, report, {
docId: prepared.args.doc_id,
documentId: prepared.indexed.document_id,
blockId: prepared.args.block_id,
commentKey: prepared.args.comment_key,
readSerial: validated.readSerial,
error: String(report.failure_code ?? "COMMENT_PREVALIDATION_FAILED"),
});
}
await endTrace(validationTrace, {
outputs: {
commentsAttempted: preparedCalls.length,
commentsValid: validForWriter.length,
commentsRejected: prevalidationFailures.length,
rejected: prevalidationFailures,
},
error:
prevalidationFailures.length > 0
? `${prevalidationFailures.length} comment anchor(s) failed prevalidation`
: null,
});
if (validForWriter.length === 0) {
continue;
}
write(`data: ${JSON.stringify({
type: "doc_edited_start",
filename: first.docInfo.filename,
})}\n\n`);
const batchResult = await runCommentDocumentBatch({
userId,
documentId: first.indexed.document_id,
docLabel,
docInfo: first.docInfo,
current,
comments: validForWriter.map((v) => ({
id: v.tc.id,
args: v.args,
anchor: v.anchor,
})),
db,
trace,
});
const writerReport = batchResult.apply_report ?? [];
const reportById = new Map<string, any>();
for (const report of writerReport) {
if (typeof report?.id === "string") reportById.set(report.id, report);
}
for (const prepared of validForWriter) {
const report = reportById.get(prepared.tc.id) ?? makeCommentFailureReport({
id: prepared.tc.id,
commentKey: prepared.args.comment_key,
blockId: prepared.args.block_id,
failureCode: batchResult.error ? "backend_error" : "comment_result_missing",
message: batchResult.error ?? "The comment writer did not return a result for this comment.",
anchor: prepared.anchor,
opType: prepared.op_type,
});
addReportResult(prepared.tc, report, {
docId: prepared.args.doc_id,
documentId: prepared.indexed.document_id,
blockId: prepared.args.block_id,
commentKey: prepared.args.comment_key,
readSerial: prepared.readSerial,
error: report.status === "applied" ? undefined : String(report.failure_code ?? batchResult.error ?? "COMMENT_NOT_APPLIED"),
});
}
if (batchResult.ok && batchResult.download_url && batchResult.version_id) {
const combinedApplyReport = [...prevalidationFailures, ...writerReport];
if (docIndex[docLabel]) {
docIndex[docLabel] = {
...docIndex[docLabel],
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
};
}
const currentDocStore = docStore.get(docLabel);
if (currentDocStore && batchResult.storage_path) {
docStore.set(docLabel, {
...currentDocStore,
storage_path: batchResult.storage_path,
});
}
// Phase 9: Post-write verification
const appliedCount = combinedApplyReport.filter((r: any) => r.status === "applied").length;
let commentVerification: CommentBatchVerification | undefined;
if (batchResult.commented_bytes) {
commentVerification = await verifyCommentBatchPostWrite({
originalBytes: current.bytes,
commentedBytes: batchResult.commented_bytes,
expectedAppliedCount: appliedCount,
appliedReports: combinedApplyReport,
});
}
// Phase 10: Refresh manifest after comment-only write
// For comment-only writes, body text should not change, so we can safely rebuild the manifest.
if (batchResult.commented_bytes && commentVerification?.body_text_unchanged) {
const newSurface = await buildDocxEditSurface(batchResult.commented_bytes, {
versionId: batchResult.version_id,
});
rememberAnchorManifest(anchorManifestState, docLabel, first.indexed.document_id, newSurface);
console.log(
`[processCommentToolCallsBatch] manifest refreshed for ${docLabel} after comment-only write. ` +
`old_revision=${currentSurface.surfaceId} new_revision=${newSurface.surfaceId} ` +
`entries=${newSurface.anchors.length} applied=${appliedCount}`
);
} else {
// If body text changed or we couldn't verify, delete the manifest to force a re-read
anchorManifestState?.byDocLabel.delete(docLabel);
}
const payload: DocEditedResult = {
filename: first.docInfo.filename,
document_id: first.indexed.document_id,
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
download_url: batchResult.download_url,
annotations: [],
apply_report: combinedApplyReport,
apply_summary: buildApplySummary(combinedApplyReport),
};
docsEdited.push(payload);
write(`data: ${JSON.stringify({
type: "doc_edited",
...payload,
})}\n\n`);
for (const prepared of validForWriter) {
const report = reportById.get(prepared.tc.id);
if (!report) continue;
const existing = resultByToolCallId.get(prepared.tc.id);
const parsed = existing ? JSON.parse(existing) as Record<string, unknown> : {};
resultByToolCallId.set(prepared.tc.id, JSON.stringify({
...parsed,
ok: report.status === "applied",
version_id: batchResult.version_id,
version_number: batchResult.version_number ?? null,
download_url: batchResult.download_url,
}));
}
} else {
write(`data: ${JSON.stringify({
type: "doc_edited",
filename: first.docInfo.filename,
document_id: first.indexed.document_id,
version_id: "",
version_number: null,
download_url: "",
annotations: [],
apply_report: [...prevalidationFailures, ...writerReport],
apply_summary: buildApplySummary([...prevalidationFailures, ...writerReport]),
error: batchResult.error ?? "No comments were inserted.",
})}\n\n`);
}
}
for (const tc of commentCalls) {
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content:
resultByToolCallId.get(tc.id) ??
JSON.stringify({
ok: false,
error: "COMMENT_TOOL_INTERNAL_ERROR",
message: "comment_on_document did not produce a result.",
}),
});
}
}
export async function runToolCalls(
toolCalls: ToolCall[],
docStore: DocStore,
userId: string,
db: ReturnType<typeof createServerSupabase>,
write: (s: string) => void,
workflowStore?: WorkflowStore,
tabularStore?: TabularCellStore,
docIndex?: DocIndex,
turnEditState?: TurnEditState,
projectId?: string | null,
trace?: RunTree | null,
commentAttemptState?: CommentAttemptState,
anchorManifestState?: AnchorManifestState,
pendingCommentPlanState?: PendingCommentPlanState,
trackChanges?: boolean,
turnDocumentState?: TurnDocumentState,
): Promise<{
toolResults: unknown[];
docsRead: { filename: string; document_id?: string }[];
docsFound: { filename: string; query: string; total_matches: number }[];
docsCreated: DocCreatedResult[];
docsReplicated: DocReplicatedResult[];
workflowsApplied: { workflow_id: string; title: string }[];
docsEdited: DocEditedResult[];
workflowFindings: WorkflowFinding[];
workflowRows: WorkflowFindingRow[];
}> {
const toolResults: unknown[] = [];
const docsRead: { filename: string; document_id?: string }[] = [];
const docsFound: {
filename: string;
query: string;
total_matches: number;
}[] = [];
const docsCreated: DocCreatedResult[] = [];
const docsReplicated: DocReplicatedResult[] = [];
const workflowsApplied: { workflow_id: string; title: string }[] = [];
const docsEdited: DocEditedResult[] = [];
const commentVerificationRows: CommentVerificationRow[] = [];
const processedCommentToolCallIds = new Set<string>();
const effectiveTurnDocumentState = turnDocumentState ?? createTurnDocumentState();
for (const tc of toolCalls) {
let args: Record<string, unknown> = {};
try {
args = JSON.parse(tc.function.arguments || "{}");
} catch {
/* ignore */
}
if (tc.function.name === "inspect_document" && docIndex) {
const result = await handleInspectDocumentTool({
tc,
args,
docStore,
docIndex,
db,
write,
anchorManifestState,
trace,
});
if (result.docRead) {
docsRead.push(result.docRead);
}
toolResults.push(result.toolResult);
continue;
}
if (tc.function.name === "search_document" && docIndex) {
const result = await handleSearchDocumentTool({
tc,
args,
docStore,
docIndex,
db,
write,
anchorManifestState,
});
if (result.docFound) {
docsFound.push(result.docFound);
}
toolResults.push(result.toolResult);
continue;
}
if (tc.function.name === "edit_document" && docIndex) {
const result = await handleEditDocumentTool({
tc,
args,
docStore,
docIndex,
userId,
db,
write,
trace,
anchorManifestState,
docsEdited,
});
toolResults.push(result);
continue;
}
if (tc.function.name === "read_document") {
const rawDocId = args.doc_id as string;
const docId =
resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const content = await readDocumentContentOncePerTurn({
docLabel: docId,
docStore,
write,
docIndex,
db,
anchorManifestState,
trace,
turnDocumentState: effectiveTurnDocumentState,
});
const filename = docStore.get(docId)?.filename;
const documentId = docIndex?.[docId]?.document_id;
if (filename && !isAlreadyReadToolResult(content)) {
docsRead.push({ filename, document_id: documentId });
}
toolResults.push({ role: "tool", tool_call_id: tc.id, content });
} else if (tc.function.name === "find_in_document") {
const rawDocId = args.doc_id as string;
const docId =
resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const query = (args.query as string) ?? "";
const maxResults = typeof args.max_results === "number" ? args.max_results : undefined;
const contextChars = typeof args.context_chars === "number" ? args.context_chars : undefined;
const anchorEligibleOnly =
typeof args.anchor_eligible_only === "boolean"
? args.anchor_eligible_only
: undefined;
const content = await findInDocumentContent({
docLabel: docId,
query,
maxResults,
contextChars,
anchorEligibleOnly,
docStore,
write,
docIndex,
db,
anchorManifestState,
});
const filename = docStore.get(docId)?.filename;
if (filename) {
let totalMatches = 0;
try {
const parsed = JSON.parse(content) as {
total_matches?: number;
};
totalMatches = parsed.total_matches ?? 0;
} catch {
/* ignore — still record the find attempt */
}
docsFound.push({
filename,
query,
total_matches: totalMatches,
});
}
toolResults.push({ role: "tool", tool_call_id: tc.id, content });
} else if (tc.function.name === "comment_on_document" && docIndex) {
if (!processedCommentToolCallIds.has(tc.id)) {
const commentCalls = toolCalls.filter(
(call) => call.function.name === "comment_on_document",
);
for (const call of commentCalls) {
processedCommentToolCallIds.add(call.id);
}
await processCommentToolCallsBatch({
commentCalls,
docStore,
docIndex,
userId,
db,
write,
trace,
commentAttemptState,
anchorManifestState,
toolResults,
docsEdited,
commentVerificationRows,
});
}
} else if (tc.function.name === "plan_comments_on_document" && docIndex) {
const planResult = await handlePlanCommentsOnDocument({
tc,
args,
docStore,
docIndex,
userId,
db,
write,
trace,
anchorManifestState,
pendingCommentPlanState: pendingCommentPlanState ?? new Map(),
docsEdited,
verificationRows: commentVerificationRows,
trackChanges,
});
toolResults.push(planResult);
} else if ((tc.function.name === "correct_comment_anchors" || tc.function.name === "fix_comment_findings") && docIndex) {
const correctResult = await handleCorrectCommentAnchors({
tc,
args,
docStore,
docIndex,
userId,
db,
write,
trace,
anchorManifestState,
pendingCommentPlanState: pendingCommentPlanState ?? new Map(),
docsEdited,
verificationRows: commentVerificationRows,
trackChanges,
});
toolResults.push(correctResult);
} else if (tc.function.name === "list_documents") {
const list = Array.from(docStore.entries()).map(
([doc_id, info]) => ({
doc_id,
filename: info.filename,
file_type: info.file_type,
}),
);
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify(list),
});
} else if (tc.function.name === "fetch_documents") {
const rawDocIds = (args.doc_ids as string[]) ?? [];
const docIds = rawDocIds.map(
(id) => resolveDocLabel(id, docStore, docIndex) ?? id,
);
const parts: string[] = [];
for (const docId of docIds) {
const content = await readDocumentContentOncePerTurn({
docLabel: docId,
docStore,
write,
docIndex,
db,
anchorManifestState,
trace,
turnDocumentState: effectiveTurnDocumentState,
});
const filename = docStore.get(docId)?.filename ?? docId;
parts.push(`--- ${filename} (${docId}) ---\n${content}`);
if (docStore.get(docId) && !isAlreadyReadToolResult(content)) {
const documentId = docIndex?.[docId]?.document_id;
docsRead.push({ filename, document_id: documentId });
}
}
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: parts.join("\n\n"),
});
} else if (tc.function.name === "list_workflows") {
const list = workflowStore
? Array.from(workflowStore.entries()).map(([id, w]) => ({ id, title: w.title }))
: [];
toolResults.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(list) });
} else if (tc.function.name === "read_workflow") {
const wfId = args.workflow_id as string;
const wf = workflowStore?.get(wfId);
if (wf) {
write(`data: ${JSON.stringify({ type: "workflow_applied", workflow_id: wfId, title: wf.title })}\n\n`);
workflowsApplied.push({ workflow_id: wfId, title: wf.title });
// Log workflow identity to LangSmith
if (trace) {
const wfIdTrace = createTraceChild(trace, {
name: "workflow_prompt_identity",
run_type: "chain",
inputs: {
workflow_id: wfId,
workflow_name: wf.title,
workflow_type: "assistant/playbook",
prompt_sha256: sha256(wf.prompt_md),
prompt_preview: wf.prompt_md.slice(0, 400),
},
tags: ["workflow", "prompt_identity"],
});
void endTrace(wfIdTrace, {
outputs: { workflow_id: wfId, workflow_name: wf.title },
});
}
}
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: wf ? ((trackChanges && wf.edit_mode_prompt_md) ? wf.edit_mode_prompt_md : wf.prompt_md) : `Workflow '${wfId}' not found.`,
});
} else if (tc.function.name === "read_table_cells" && tabularStore) {
const colIndices = args.col_indices as number[] | undefined;
const rowIndices = args.row_indices as number[] | undefined;
const filteredCols = colIndices?.length
? tabularStore.columns.filter((_, i) => colIndices.includes(i))
: tabularStore.columns;
const filteredDocs = rowIndices?.length
? tabularStore.documents.filter((_, i) => rowIndices.includes(i))
: tabularStore.documents;
const label = `${filteredCols.length} ${filteredCols.length === 1 ? "column" : "columns"} × ${filteredDocs.length} ${filteredDocs.length === 1 ? "row" : "rows"}`;
write(`data: ${JSON.stringify({ type: "doc_read_start", filename: label })}\n\n`);
const lines: string[] = [];
for (const col of filteredCols) {
const colPos = tabularStore.columns.findIndex((c) => c.index === col.index);
for (const doc of filteredDocs) {
const rowPos = tabularStore.documents.findIndex((d) => d.id === doc.id);
const cell = tabularStore.cells.get(`${col.index}:${doc.id}`);
lines.push(`[COL:${colPos} "${col.name}" | ROW:${rowPos} "${doc.filename}"]`);
if (cell?.summary) {
lines.push(`Summary: ${cell.summary}`);
if (cell.flag) lines.push(`Flag: ${cell.flag}`);
if (cell.reasoning) lines.push(`Reasoning: ${cell.reasoning}`);
} else {
lines.push(`(not yet generated)`);
}
lines.push("");
}
}
write(`data: ${JSON.stringify({ type: "doc_read", filename: label })}\n\n`);
docsRead.push({ filename: label });
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: lines.join("\n") || "No cells found.",
});
} else if (tc.function.name === "replicate_document" && docIndex) {
const rawDocId = args.doc_id as string;
const requestedFilename =
typeof args.new_filename === "string" &&
args.new_filename.trim()
? args.new_filename.trim()
: null;
const requestedCount =
typeof args.count === "number" && Number.isFinite(args.count)
? Math.max(1, Math.min(20, Math.floor(args.count)))
: 1;
const sourceLabel =
resolveDocLabel(rawDocId, docStore, docIndex) ?? rawDocId;
const sourceInfo = docStore.get(sourceLabel);
const sourceIndexed = docIndex[sourceLabel];
const sourceFilename = sourceInfo?.filename ?? rawDocId;
write(
`data: ${JSON.stringify({
type: "doc_replicate_start",
filename: sourceFilename,
count: requestedCount,
})}\n\n`,
);
const fail = (error: string) => {
write(
`data: ${JSON.stringify({
type: "doc_replicated",
filename: sourceFilename,
count: requestedCount,
copies: [],
error,
})}\n\n`,
);
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({ ok: false, error }),
});
};
if (!sourceInfo || !sourceIndexed) {
fail(`Document '${rawDocId}' not found in this project.`);
} else if (!projectId) {
fail("replicate_document is only available in project chats.");
} else {
try {
// Pull the active version once — every copy gets the
// same starting bytes (with any accepted tracked
// changes rolled in), no point re-fetching per copy.
const active = await loadActiveVersion(
sourceIndexed.document_id,
db,
);
const sourcePath =
active?.storage_path ?? sourceInfo.storage_path;
const sourcePdfPath = active?.pdf_storage_path ?? null;
const raw = await downloadFile(sourcePath);
const pdfBytes = sourcePdfPath
? await downloadFile(sourcePdfPath)
: null;
if (!raw) {
fail(
"Could not read the source document's bytes from storage.",
);
} else {
// Build N filenames. With count=1 keep the
// pre-existing "(copy)" suffix; with count>1 use
// numbered "(1)", "(2)" suffixes.
const srcExt =
sourceInfo.filename.match(/\.[^./\\]+$/)?.[0] ?? "";
const baseStem = (() => {
if (requestedFilename) {
return requestedFilename.replace(
/\.[^./\\]+$/,
"",
);
}
return sourceInfo.filename.replace(
/\.[^./\\]+$/,
"",
);
})();
const filenames: string[] = [];
for (let n = 1; n <= requestedCount; n++) {
const suffix =
requestedCount === 1
? requestedFilename
? ""
: " (copy)"
: ` (${n})`;
filenames.push(`${baseStem}${suffix}${srcExt}`);
}
// Bulk insert N documents in one round-trip.
const docRows = filenames.map((fn) => ({
project_id: projectId,
user_id: userId,
filename: fn,
file_type: sourceInfo.file_type,
size_bytes: raw.byteLength,
status: "ready",
}));
const { data: insertedDocs, error: docErr } = await db
.from("documents")
.insert(docRows)
.select("id, filename");
if (docErr || !insertedDocs || insertedDocs.length === 0) {
fail(
`Failed to record replicated documents: ${docErr?.message ?? "unknown"}`,
);
} else {
// Preserve the request order so each row pairs
// with the right filename. Supabase returns
// inserted rows in the same order as the
// payload.
const newDocs = insertedDocs as {
id: string;
filename: string;
}[];
const contentType =
sourceInfo.file_type === "pdf"
? "application/pdf"
: "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
// Parallel uploads: the doc bytes (and PDF
// rendition if any) for every new copy.
const uploadJobs: Promise<unknown>[] = [];
const newKeys: string[] = [];
const newPdfKeys: (string | null)[] = [];
for (const d of newDocs) {
const key = storageKey(
userId,
d.id,
d.filename,
);
newKeys.push(key);
uploadJobs.push(
uploadFile(key, raw, contentType),
);
if (pdfBytes) {
const pdfKey = convertedPdfKey(
userId,
d.id,
);
newPdfKeys.push(pdfKey);
uploadJobs.push(
uploadFile(
pdfKey,
pdfBytes,
"application/pdf",
),
);
} else {
newPdfKeys.push(null);
}
}
await Promise.all(uploadJobs);
// Bulk insert N versions in one round-trip.
const versionRows = newDocs.map((d, idx) => ({
document_id: d.id,
storage_path: newKeys[idx],
pdf_storage_path: newPdfKeys[idx],
source: "upload",
version_number: 1,
display_name: d.filename,
}));
const { data: insertedVersions, error: verErr } =
await db
.from("document_versions")
.insert(versionRows)
.select("id, document_id");
if (
verErr ||
!insertedVersions ||
insertedVersions.length !== newDocs.length
) {
fail(
`Failed to record replicated document versions: ${verErr?.message ?? "unknown"}`,
);
} else {
const versionByDocId = new Map<string, string>();
for (const v of insertedVersions as {
id: string;
document_id: string;
}[]) {
versionByDocId.set(v.document_id, v.id);
}
// current_version_id has to be a per-row
// value, so a single UPDATE statement
// can't cover all N. Fan out in parallel
// instead of sequential awaits.
await Promise.all(
newDocs.map((d) =>
db
.from("documents")
.update({
current_version_id:
versionByDocId.get(d.id),
})
.eq("id", d.id),
),
);
// Register every copy under a fresh doc-N
// slug so the model can edit/read any of
// them in the same turn.
const existingLabels = new Set(
Object.keys(docIndex),
);
let nextLabelIdx = 0;
const copies: {
new_filename: string;
document_id: string;
version_id: string;
}[] = [];
const toolPayloadCopies: {
doc_id: string;
document_id: string;
version_id: string;
filename: string;
download_url: string;
}[] = [];
for (let idx = 0; idx < newDocs.length; idx++) {
const d = newDocs[idx];
const newKey = newKeys[idx];
const versionId = versionByDocId.get(d.id);
if (!versionId) continue;
while (
existingLabels.has(
`doc-${nextLabelIdx}`,
)
)
nextLabelIdx++;
const slug = `doc-${nextLabelIdx}`;
existingLabels.add(slug);
docIndex[slug] = {
document_id: d.id,
filename: d.filename,
};
docStore.set(slug, {
storage_path: newKey,
file_type: sourceInfo.file_type,
filename: d.filename,
});
copies.push({
new_filename: d.filename,
document_id: d.id,
version_id: versionId,
});
toolPayloadCopies.push({
doc_id: slug,
document_id: d.id,
version_id: versionId,
filename: d.filename,
download_url: buildDownloadUrl(
newKey,
d.filename,
),
});
}
write(
`data: ${JSON.stringify({
type: "doc_replicated",
filename: sourceFilename,
count: copies.length,
copies,
})}\n\n`,
);
docsReplicated.push({
filename: sourceFilename,
count: copies.length,
copies,
});
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify({
ok: true,
count: copies.length,
copies: toolPayloadCopies,
}),
});
}
}
}
} catch (e) {
fail(`replicate_document failed: ${String(e)}`);
}
}
} else if (tc.function.name === "generate_docx") {
const title = args.title as string;
const landscape = !!(args.landscape);
console.log(`[generate_docx] title="${title}" landscape=${landscape} args.landscape=${args.landscape}`);
const previewFilename = `${(title.replace(/[^a-zA-Z0-9 _-]/g, "").trim().slice(0, 64) || "document")}.docx`;
write(`data: ${JSON.stringify({ type: "doc_created_start", filename: previewFilename })}\n\n`);
const result = await generateDocx(
title,
args.sections as unknown[],
userId,
db,
{ landscape, projectId: projectId ?? null },
);
let newDocLabel: string | null = null;
if ("filename" in result && "download_url" in result) {
const dlFilename = result.filename as string;
const dlUrl = result.download_url as string;
const documentId = (result as { document_id?: string }).document_id;
const versionId = (result as { version_id?: string }).version_id;
const versionNumber = (result as { version_number?: number }).version_number ?? null;
const storagePath = (result as { storage_path?: string }).storage_path;
// Register the generated doc in the chat context so
// edit_document (and read_document / find_in_document)
// can act on it within the same assistant turn. New label
// is the next free `doc-N` index. Subsequent turns pick
// it up via the normal attachment/project doc query.
if (documentId && storagePath && docIndex) {
const existingLabels = new Set(Object.keys(docIndex));
let i = 0;
while (existingLabels.has(`doc-${i}`)) i++;
newDocLabel = `doc-${i}`;
docIndex[newDocLabel] = {
document_id: documentId,
filename: dlFilename,
};
docStore.set(newDocLabel, {
storage_path: storagePath,
file_type: "docx",
filename: dlFilename,
});
}
write(
`data: ${JSON.stringify({
type: "doc_created",
filename: dlFilename,
download_url: dlUrl,
document_id: documentId,
version_id: versionId,
version_number: versionNumber,
})}\n\n`,
);
docsCreated.push({
filename: dlFilename,
download_url: dlUrl,
document_id: documentId,
version_id: versionId,
version_number: versionNumber,
});
} else {
write(`data: ${JSON.stringify({ type: "doc_created", filename: previewFilename, download_url: "" })}\n\n`);
}
// Surface the chat-local doc label in the tool result so the
// model can pass it as `doc_id` to edit_document / read_document
// / find_in_document in the same turn. Without this the model
// only sees the DB UUID, which isn't valid as a doc_id anchor.
const toolResultPayload = newDocLabel
? { ...(result as Record<string, unknown>), doc_id: newDocLabel }
: result;
toolResults.push({
role: "tool",
tool_call_id: tc.id,
content: JSON.stringify(toolResultPayload),
});
}
}
let dedupedReports: CommentVerificationRow[] = [];
if (commentVerificationRows.length > 0) {
const applied = commentVerificationRows.filter(
(row) => row.status === "applied",
).length;
const ignoredPassCount = commentVerificationRows.filter(
(row) => row.status === "ignored_pass",
).length;
const failed = commentVerificationRows.length - applied - ignoredPassCount;
dedupedReports = dedupeCommentVerificationRows(commentVerificationRows);
const dedupedApplied = dedupedReports.filter(
(row) => row.status === "applied",
).length;
const dedupedIgnoredPass = dedupedReports.filter(
(row) => row.status === "ignored_pass",
).length;
const dedupedFailed = dedupedReports.length - dedupedApplied - dedupedIgnoredPass;
const actionableAttempted = commentVerificationRows.length - ignoredPassCount;
const verificationTrace = createTraceChild(trace, {
name: "insertion_verification",
run_type: "chain",
inputs: {
comments_attempted: commentVerificationRows.length,
comments_actionable_attempted: actionableAttempted,
comments_ignored_pass: ignoredPassCount,
documents_edited: docsEdited.map((doc) => ({
filename: doc.filename,
document_id: doc.document_id,
version_id: doc.version_id,
})),
},
tags: ["verification", "comments"],
});
await endTrace(verificationTrace, {
outputs: {
commentsAttempted: commentVerificationRows.length,
commentsActionableAttempted: actionableAttempted,
commentsApplied: applied,
commentsFailed: failed,
commentsIgnoredPass: ignoredPassCount,
uniqueCommentsAttempted: dedupedReports.length,
uniqueCommentsApplied: dedupedApplied,
uniqueCommentsFailed: dedupedFailed,
uniqueCommentsIgnoredPass: dedupedIgnoredPass,
reports: commentVerificationRows,
deduplicatedReports: dedupedReports,
},
error: failed > 0 ? `${failed} comment insertion attempt(s) failed` : null,
});
}
const workflowSourceRows = dedupedReports.length > 0
? dedupedReports
: commentVerificationRows;
const workflowFindings: WorkflowFinding[] = workflowSourceRows
.filter((row) => row.ok && row.status !== "ignored_pass")
.map((row) => {
const edit = docsEdited
.flatMap((d) => d.annotations ?? [])
.find((a) => a.target_block_id === row.block_id || a.change_id === row.comment_key);
return {
comment_key: row.comment_key,
block_id: row.block_id,
op_type: row.op_type ?? "COMMENT",
status: row.status,
section_heading: row.section_heading,
bookmark_name: row.bookmark_name,
comment: row.comment,
target_text: edit?.deleted_text || row.anchor_text || undefined,
new_text: edit?.inserted_text || undefined,
reasoning: row.reasoning || null,
applied: row.ok,
};
});
const workflowRows: WorkflowFindingRow[] = workflowSourceRows.map((row) => {
const action = normalizeWorkflowAction(row.op_type ?? "COMMENT");
const { issue, zamilImpact, recommendedAction } = parseCommentFields(row.comment ?? "");
const edit = docsEdited
.flatMap((d) => d.annotations ?? [])
.find((a) => a.target_block_id === row.block_id || a.change_id === row.comment_key);
const isComment = action === "COMMENT";
const isInsert = action === "INSERT";
const isDelete = action === "DELETE";
const applyStatus = applyStatusFromVerificationRow(row);
return {
row_id: `${row.doc_id ?? ""}:${row.comment_key ?? row.block_id ?? crypto.randomUUID()}`,
doc_id: row.doc_id ?? "",
document_id: row.document_id,
comment_key: row.comment_key ?? "",
block_id: row.block_id ?? row.model_block_id ?? "",
bookmark_name: row.bookmark_name ?? null,
section_heading: row.section_heading ?? null,
document_order: row.document_order,
finding_status: row.finding_status ?? "REVIEW",
apply_status: applyStatus,
action,
issue,
zamil_impact: zamilImpact || undefined,
recommended_action: recommendedAction || undefined,
original_wording: isInsert
? undefined
: edit?.deleted_text || row.anchor_text || undefined,
amended_wording: isComment || isDelete
? undefined
: row.amended_wording ?? edit?.inserted_text ?? undefined,
reasoning: row.reasoning ?? null,
failure_code: row.failure_code,
failure_message: row.message,
source: row.source ?? "plan_comments_on_document",
};
});
return {
toolResults,
docsRead,
docsFound,
docsCreated,
docsReplicated,
workflowsApplied,
docsEdited,
workflowFindings,
workflowRows,
};
}
// ---------------------------------------------------------------------------
// Citation parsing
// ---------------------------------------------------------------------------
const CITATIONS_BLOCK_RE = /<CITATIONS>\s*([\s\S]*?)\s*<\/CITATIONS>/;
const CITATIONS_OPEN_TAG = "<CITATIONS>";
function parseCitations(text: string): ParsedCitation[] {
const match = text.match(CITATIONS_BLOCK_RE);
if (!match) return [];
try {
const raw = JSON.parse(match[1]);
if (!Array.isArray(raw)) return [];
return raw
.map(normalizeCitation)
.filter((c): c is ParsedCitation => c !== null);
} catch {
return [];
}
}
// ---------------------------------------------------------------------------
// LLM streaming loop
// ---------------------------------------------------------------------------
export type EditAnnotation = {
kind: "edit";
edit_id: string;
document_id: string;
version_id: string;
version_number?: number | null;
change_id: string;
del_w_id?: string;
ins_w_id?: string;
deleted_text: string;
inserted_text: string;
context_before: string;
context_after: string;
operation?: string | null;
target_block_id?: string | null;
target_start?: number | null;
target_end?: number | null;
base_surface_id?: string | null;
target_text_hash?: string | null;
reason?: string;
status: "pending" | "accepted" | "rejected";
};
export type WorkflowFindingStatus =
| "FAIL"
| "MISSING"
| "RISK"
| "REVIEW"
| "ESCALATE";
export type WorkflowApplyStatus =
| "applied"
| "not_applied"
| "ignored_pass"
| "needs_correction"
| "failed_validation"
| "writer_failed";
export type WorkflowAction =
| "COMMENT"
| "REWRITE"
| "INSERT"
| "DELETE";
export type WorkflowFindingRow = {
row_id: string;
workflow_id?: string;
workflow_title?: string;
doc_id: string;
document_id?: string;
version_id?: string;
download_url?: string;
comment_key: string;
block_id: string;
bookmark_name?: string | null;
section_heading?: string | null;
document_order?: number;
finding_status: WorkflowFindingStatus;
apply_status: WorkflowApplyStatus;
action: WorkflowAction;
issue: string;
zamil_impact?: string;
recommended_action?: string;
original_wording?: string;
amended_wording?: string;
reasoning?: {
clause_section: string;
violated_standard: string;
zamil_exposure: string;
} | null;
failure_code?: string;
failure_message?: string;
repair_kind?: RepairKind;
repair_attempts?: number;
fallback_applied?: boolean;
downgrade_to_comment?: boolean;
original_type?: string;
final_type?: string;
source: CommentPlanSource;
};
export type WorkflowFinding = {
comment_key?: string;
block_id?: string;
op_type?: string;
status?: string;
section_heading?: string | null;
bookmark_name?: string | null;
comment?: string;
target_text?: string;
new_text?: string;
reasoning?: { clause_section: string; violated_standard: string; zamil_exposure: string } | null;
applied: boolean;
};
const APPLY_STATUS_PRIORITY: Record<WorkflowApplyStatus, number> = {
applied: 6,
writer_failed: 5,
failed_validation: 4,
needs_correction: 3,
not_applied: 2,
ignored_pass: 1,
};
function shouldReplace(
current: WorkflowApplyStatus,
incoming: WorkflowApplyStatus,
): boolean {
return APPLY_STATUS_PRIORITY[incoming] > APPLY_STATUS_PRIORITY[current];
}
class WorkflowAccumulator {
private rows = new Map<string, WorkflowFindingRow>();
addRows(rows: WorkflowFindingRow[]): void {
for (const row of rows) {
const existing = this.rows.get(row.row_id);
if (!existing || shouldReplace(existing.apply_status, row.apply_status)) {
this.rows.set(row.row_id, {
...row,
finding_status: row.finding_status ?? existing?.finding_status ?? "REVIEW",
issue: row.issue || existing?.issue || "",
reasoning: row.reasoning ?? existing?.reasoning ?? null,
section_heading: row.section_heading ?? existing?.section_heading ?? null,
document_order: row.document_order ?? existing?.document_order,
original_wording: row.original_wording ?? existing?.original_wording,
amended_wording: row.amended_wording ?? existing?.amended_wording,
});
}
}
}
getRows(): WorkflowFindingRow[] {
return Array.from(this.rows.values());
}
size(): number {
return this.rows.size;
}
}
type WorkflowBatchDecision =
| { terminal: true; reason: "applied" | "no_action" | "failed" }
| { terminal: false; reason: "needs_anchor_correction" };
function parseToolJson(content: string): Record<string, any> {
try {
const parsed = JSON.parse(content);
return parsed && typeof parsed === "object" ? parsed : {};
} catch {
return {};
}
}
export function isWorkflowTerminalBatch(input: {
calls: NormalizedToolCall[];
toolResults: NormalizedToolResult[];
workflowRows: WorkflowFindingRow[];
}): WorkflowBatchDecision {
const writeTools = new Set([
"plan_comments_on_document",
"correct_comment_anchors",
"fix_comment_findings",
]);
const hasWriteTool = input.calls.some((c) => writeTools.has(c.name));
if (!hasWriteTool) {
return { terminal: false, reason: "needs_anchor_correction" };
}
let resultSaysApplied = false;
let resultSaysFailed = false;
let resultSaysNoAction = false;
const writeCalls = input.calls.filter((c) => writeTools.has(c.name));
for (const call of writeCalls) {
const result = input.toolResults.find((r) => r.tool_use_id === call.id);
const parsed = result ? parseToolJson(result.content) : {};
const needsCorrection = Number(parsed.needs_correction ?? 0);
const readyToApply = parsed.ready_to_apply === true;
if (needsCorrection > 0 && !readyToApply) {
return { terminal: false, reason: "needs_anchor_correction" };
}
const appliedCount = Number(parsed.apply_summary?.applied ?? 0);
const failedCount = Number(
parsed.failed_permanently ?? parsed.failed ?? parsed.apply_summary?.failed ?? 0,
);
const validCount = Number(parsed.valid ?? 0);
const ignoredPassCount = Number(parsed.ignored_pass ?? 0);
if (parsed.applied === true || appliedCount > 0) {
resultSaysApplied = true;
}
if (failedCount > 0 || typeof parsed.error === "string") {
resultSaysFailed = true;
}
if (
readyToApply &&
validCount === 0 &&
failedCount === 0 &&
ignoredPassCount > 0
) {
resultSaysNoAction = true;
}
}
const applied = input.workflowRows.some((r) => r.apply_status === "applied");
const failed = input.workflowRows.some(
(r) => r.apply_status !== "applied" && r.apply_status !== "ignored_pass",
);
if (applied || resultSaysApplied) return { terminal: true, reason: "applied" };
if (failed || resultSaysFailed) return { terminal: true, reason: "failed" };
if (resultSaysNoAction) return { terminal: true, reason: "no_action" };
return { terminal: true, reason: "no_action" };
}
const OP_TYPE_LABELS: Record<string, string> = {
COMMENT: "Comment",
REWRITE_PARAGRAPH: "Rewrite",
INSERT_PARAGRAPH: "Insert",
DELETE_PARAGRAPH: "Delete",
};
const STATUS_LABELS: Record<string, string> = {
FAIL: "FAIL",
RISK: "RISK",
REVIEW: "REVIEW",
ESCALATE: "ESCALATE",
MISSING: "MISSING",
};
const SEVERITY_ORDER: Record<string, number> = {
FAIL: 1,
MISSING: 2,
RISK: 3,
ESCALATE: 4,
REVIEW: 5,
};
const ACTION_ORDER: Record<string, number> = {
REWRITE: 1,
INSERT: 2,
DELETE: 3,
COMMENT: 4,
};
export type WorkflowSummaryMetrics = {
total: number;
actionable: number;
applied: number;
failed: number;
ignoredPass: number;
rewrites: number;
inserts: number;
deletes: number;
comments: number;
bySeverity: Record<string, number>;
};
export type WorkflowSummaryRender = {
markdown: string;
reasoningMarkdown: string;
metrics: WorkflowSummaryMetrics;
};
export function buildWorkflowSummaryFromRows(rows: WorkflowFindingRow[]): WorkflowSummaryRender {
const applied = rows.filter((r) => r.apply_status === "applied");
const failed = rows.filter((r) => r.apply_status !== "applied" && r.apply_status !== "ignored_pass");
const ignoredPass = rows.filter((r) => r.apply_status === "ignored_pass");
const actionable = rows.filter((r) => r.apply_status !== "ignored_pass");
const metrics: WorkflowSummaryMetrics = {
total: rows.length,
actionable: actionable.length,
applied: applied.length,
failed: failed.length,
ignoredPass: ignoredPass.length,
rewrites: applied.filter((r) => r.action === "REWRITE").length,
inserts: applied.filter((r) => r.action === "INSERT").length,
deletes: applied.filter((r) => r.action === "DELETE").length,
comments: applied.filter((r) => r.action === "COMMENT").length,
bySeverity: {},
};
for (const r of applied) {
const key = r.finding_status;
metrics.bySeverity[key] = (metrics.bySeverity[key] ?? 0) + 1;
}
if (rows.length === 0 || (applied.length === 0 && failed.length === 0)) {
return {
markdown: "I completed the playbook review. No actionable changes were applied.",
reasoningMarkdown: "",
metrics,
};
}
const sortKey = (r: WorkflowFindingRow) => [
r.document_order ?? Infinity,
SEVERITY_ORDER[r.finding_status] ?? 99,
ACTION_ORDER[r.action] ?? 99,
r.comment_key,
];
const sortedApplied = [...applied].sort((a, b) => {
const ka = sortKey(a);
const kb = sortKey(b);
for (let i = 0; i < ka.length; i++) {
if (ka[i] < kb[i]) return -1;
if (ka[i] > kb[i]) return 1;
}
return 0;
});
const esc = (s: string | undefined | null) =>
sanitizeWorkflowDisplayText(s)
.replace(/\|/g, "\\|")
.replace(/\n/g, " ")
.slice(0, 120) || "—";
const formatWorkflowSection = (row: any): string => {
const pageMatch = typeof row?.block_id === "string"
? row.block_id.match(/^BMK_P_page(\d+)$/)
: null;
if (pageMatch) return `Page ${pageMatch[1]}`;
const sectionCandidates = [
row.section_heading,
row.reasoning?.clause_section,
];
for (const candidate of sectionCandidates) {
if (isMeaningfulSectionLabel(candidate)) {
return decodeXmlEntities(esc(candidate));
}
}
return "Relevant section";
};
const isPdfWorkflowRow = (row: any): boolean => {
return typeof row?.block_id === "string" && /^BMK_P_page\d+$/.test(row.block_id);
};
const hasPdfRows = rows.some(isPdfWorkflowRow);
const hasOnlyPdfRows = rows.length > 0 && rows.every(isPdfWorkflowRow);
const appliedCount = applied.length;
const actionParts = [
metrics.rewrites > 0 ? `${metrics.rewrites} rewrite${metrics.rewrites === 1 ? "" : "s"}` : "",
metrics.inserts > 0 ? `${metrics.inserts} insertion${metrics.inserts === 1 ? "" : "s"}` : "",
metrics.deletes > 0 ? `${metrics.deletes} deletion${metrics.deletes === 1 ? "" : "s"}` : "",
metrics.comments > 0 ? `${metrics.comments} comment${metrics.comments === 1 ? "" : "s"}` : "",
].filter(Boolean);
const severityParts = Object.entries(metrics.bySeverity)
.map(([severity, count]) => `${count} ${severity}`)
.join(", ");
const existingIntro = applied.length > 0
? failed.length > 0
? `I completed the playbook review and applied ${applied.length} of ${actionable.length} actionable findings. ${failed.length} finding${failed.length === 1 ? " needs" : "s need"} manual review.`
: `I completed the playbook review and applied ${applied.length} finding${applied.length === 1 ? "" : "s"}${actionParts.length ? `: ${actionParts.join(", ")}` : ""}${severityParts ? `. Severity: ${severityParts}.` : "."}`
: `I completed the playbook review. No findings were applied. ${failed.length} finding${failed.length === 1 ? " needs" : "s need"} manual review.`;
const intro = hasOnlyPdfRows
? `I completed the read-only PDF review and reported ${appliedCount} actionable finding${appliedCount === 1 ? "" : "s"}. No changes were made to the PDF.`
: hasPdfRows
? `I completed the document review. PDF findings were reported read-only, and editable document comments were handled through the existing document workflow.`
: existingIntro;
const header = "| # | Section | Status | Action | Target Text | Amended Text | Issue / Reason | Zamil Impact | Recommended Action |";
const separator = "|---|---------|--------|--------|-------------|--------------|----------------|--------------|--------------------|";
const appliedRows = sortedApplied.map((r, index) => {
const section = formatWorkflowSection(r);
return `| ${index + 1} | ${section} | ${r.finding_status} | ${capitalize(r.action)} | ${r.action === "INSERT" ? "—" : esc(r.original_wording)} | ${r.action === "COMMENT" || r.action === "DELETE" ? "—" : esc(r.amended_wording)} | ${esc(r.issue)} | ${esc(r.zamil_impact)} | ${esc(r.recommended_action)} |`;
});
let markdown = intro;
if (appliedRows.length > 0) {
markdown += `\n\n${header}\n${separator}\n${appliedRows.join("\n")}`;
}
if (failed.length > 0) {
const sortedFailed = [...failed].sort((a, b) => {
const ka = sortKey(a);
const kb = sortKey(b);
for (let i = 0; i < ka.length; i++) {
if (ka[i] < kb[i]) return -1;
if (ka[i] > kb[i]) return 1;
}
return 0;
});
markdown += "\n\n### Not applied\n\n";
const failedHeader = "| # | Section | Status | Action | Reason | Next Step |";
const failedSeparator = "|---|---------|--------|--------|--------|-----------|";
const failedRows = sortedFailed.map((r, index) => {
const section = formatWorkflowSection(r);
const nextStep = r.failure_code === "anchor_not_found"
? "Re-read the document and rerun the finding, or apply manually."
: "Review the failure reason and retry or apply manually.";
return `| ${index + 1} | ${section} | ${r.finding_status} | ${capitalize(r.action)} | ${esc(r.failure_message ?? r.failure_code)} | ${nextStep} |`;
});
markdown += `${failedHeader}\n${failedSeparator}\n${failedRows.join("\n")}`;
}
const reasoningLines: string[] = [];
sortedApplied.forEach((r, index) => {
const section = formatWorkflowSection(r);
reasoningLines.push(`### ${index + 1}. ${section}`);
if (r.reasoning) {
reasoningLines.push(`- **Clause/Section:** ${esc(r.reasoning.clause_section)}`);
reasoningLines.push(`- **Violated Standard:** ${esc(r.reasoning.violated_standard)}`);
reasoningLines.push(`- **Zamil Exposure:** ${esc(r.reasoning.zamil_exposure)}`);
}
if (r.issue) reasoningLines.push(`- **Issue:** ${esc(r.issue)}`);
if (r.zamil_impact) reasoningLines.push(`- **Zamil Impact:** ${esc(r.zamil_impact)}`);
if (r.recommended_action) reasoningLines.push(`- **Recommended Action:** ${esc(r.recommended_action)}`);
reasoningLines.push("");
});
return {
markdown,
reasoningMarkdown: reasoningLines.join("\n"),
metrics,
};
}
function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}
export function buildWorkflowSummary(findings: WorkflowFinding[]): string {
const applied = findings.filter((f) => f.applied);
const rewriteCount = applied.filter((f) => f.op_type === "REWRITE_PARAGRAPH").length;
const insertCount = applied.filter((f) => f.op_type === "INSERT_PARAGRAPH").length;
const deleteCount = applied.filter((f) => f.op_type === "DELETE_PARAGRAPH").length;
const commentCount = applied.filter((f) => !f.op_type || f.op_type === "COMMENT").length;
const flaggedCount = applied.filter((f) => f.status === "FAIL").length;
const riskCount = applied.filter((f) => f.status === "RISK").length;
const reviewCount = applied.filter((f) => f.status === "REVIEW").length;
const parts: string[] = [];
if (rewriteCount > 0) parts.push(`redlined ${rewriteCount} issue${rewriteCount > 1 ? "s" : ""}`);
if (insertCount > 0) parts.push(`inserted ${insertCount} clause${insertCount > 1 ? "s" : ""}`);
if (deleteCount > 0) parts.push(`deleted ${deleteCount} provision${deleteCount > 1 ? "s" : ""}`);
if (commentCount > 0) parts.push(`flagged ${commentCount} concern${commentCount > 1 ? "s" : ""}`);
const statusParts: string[] = [];
if (flaggedCount > 0) statusParts.push(`${flaggedCount} flagged`);
if (riskCount > 0) statusParts.push(`${riskCount} at risk`);
if (reviewCount > 0) statusParts.push(`${reviewCount} for review`);
let intro = "Here's my review";
if (parts.length > 0) {
intro += ` — I ${parts.join(", ")}`;
}
intro += " in the document.";
if (statusParts.length > 0) {
intro += ` (${statusParts.join(", ")})`;
}
if (applied.length === 0) {
return `${intro}\n\nNo changes were applied to the document.`;
}
const header = "| Section | Status | Change | Target Text | Amended Text | Reason |";
const separator = "|---------|--------|--------|-------------|--------------|--------|";
const rows = applied.map((f) => {
const sectionCandidate = isMeaningfulSectionLabel(f.section_heading)
? f.section_heading
: isMeaningfulSectionLabel(f.reasoning?.clause_section)
? f.reasoning?.clause_section
: "Relevant section";
const section = decodeXmlEntities(
sanitizeWorkflowDisplayText(sectionCandidate).replace(/\|/g, "\\|"),
);
const statusLabel = STATUS_LABELS[f.status ?? ""] || f.status || "—";
const changeLabel = OP_TYPE_LABELS[f.op_type ?? ""] || f.op_type || "Comment";
const isInsert = f.op_type === "INSERT" || f.op_type === "INSERT_PARAGRAPH";
const isComment = !f.op_type || f.op_type === "COMMENT";
const isDelete = f.op_type === "DELETE_PARAGRAPH";
const target = isInsert ? "—" : (sanitizeWorkflowDisplayText(f.target_text) || "—").replace(/\|/g, "\\|").replace(/\n/g, " ").slice(0, 120);
const amended = isComment || isDelete ? "—" : (sanitizeWorkflowDisplayText(f.new_text) || "—").replace(/\|/g, "\\|").replace(/\n/g, " ").slice(0, 120);
const reason = (sanitizeWorkflowDisplayText(f.comment) || "—").replace(/\|/g, "\\|").replace(/\n/g, " ").slice(0, 200);
return `| ${section} | ${statusLabel} | ${changeLabel} | ${target} | ${amended} | ${reason} |`;
});
return `${intro}\n\n${header}\n${separator}\n${rows.join("\n")}`;
}
type AssistantEvent =
| { type: "reasoning"; text: string }
| { type: "doc_read"; filename: string; document_id?: string }
| {
type: "doc_find";
filename: string;
query: string;
total_matches: number;
}
| {
type: "doc_created";
filename: string;
download_url: string;
document_id?: string;
version_id?: string;
version_number?: number | null;
}
| { type: "doc_download"; filename: string; download_url: string }
| {
type: "doc_replicated";
/** Source document being copied. */
filename: string;
count: number;
copies: {
new_filename: string;
document_id: string;
version_id: string;
}[];
}
| { type: "workflow_applied"; workflow_id: string; title: string }
| {
type: "doc_edited";
filename: string;
document_id: string;
version_id: string;
/** Per-document monotonic Vn; null if backend couldn't determine it. */
version_number: number | null;
download_url: string;
annotations: EditAnnotation[];
apply_report?: any[];
apply_summary?: DocEditedResult["apply_summary"];
edit_verification?: unknown;
}
| { type: "content"; text: string }
| {
type: "search_grounding";
grounding: import("./llm/types").SearchGrounding;
}
| {
type: "workflow_summary";
summary: string;
reasoning: string;
metrics?: WorkflowSummaryRender["metrics"];
rows?: WorkflowFindingRow[];
};
function isClarifyingDocumentEditResponse(text: string): boolean {
const normalized = text.trim();
if (!normalized) return false;
return (
/\?/.test(normalized) &&
/\b(which|what|where|who|how|please confirm|clarify|document|docx|file|section|clause|paragraph|location|text|wording|should I)\b/i.test(
normalized,
)
);
}
export async function runLLMStream(params: {
apiMessages: unknown[];
docStore: DocStore;
docIndex: DocIndex;
userId: string;
db: ReturnType<typeof createServerSupabase>;
write: (s: string) => void;
extraTools?: unknown[];
workflowStore?: WorkflowStore;
tabularStore?: TabularCellStore;
buildCitations?: (fullText: string) => unknown[];
model?: string;
inferenceProvider?: string | null;
apiKeys?: import("./llm").UserApiKeys;
trace?: RunTree | null;
/**
* If set, generate_docx will attach created docs to this project so
* they appear in the project sidebar. Leave null for general chats —
* generated docs still get persisted, but as standalone documents.
*/
projectId?: string | null;
trackChanges?: boolean;
isExecutingWorkflow?: boolean;
turnMode?: AssistantTurnMode;
enableGoogleSearch?: boolean;
saudiLawRouting?: SaudiLawRoutingResult;
saudiLawSourcePacket?: SaudiLawSourcePacket;
signal?: AbortSignal;
}): Promise<{
fullText: string;
events: AssistantEvent[];
saudiLawEvidence?: SaudiLawEvidencePacket | null;
}> {
const { apiMessages, docStore, docIndex, userId, db, write, extraTools, workflowStore, tabularStore, buildCitations, model, inferenceProvider, apiKeys, projectId, trace, trackChanges, isExecutingWorkflow, turnMode, enableGoogleSearch, saudiLawRouting, saudiLawSourcePacket, signal } = params;
const anchorManifestState = createAnchorManifestState();
const isSaudiLawTurn = saudiLawRouting?.isSaudiLawQuestion === true;
const baseTurnMode: AssistantTurnMode =
turnMode ??
(isExecutingWorkflow
? "workflow_review"
: trackChanges
? "document_direct_edit"
: projectId
? "project_chat"
: "answer");
const effectiveTurnMode: AssistantTurnMode =
isSaudiLawTurn && baseTurnMode === "answer"
? "web_search"
: isSaudiLawTurn && baseTurnMode === "document_direct_edit"
? "document_edit_with_web_search"
: baseTurnMode;
const isDocumentEditTurn =
effectiveTurnMode === "document_direct_edit" ||
effectiveTurnMode === "document_edit_with_web_search";
const isWebSearchTurn =
effectiveTurnMode === "web_search" ||
effectiveTurnMode === "document_edit_with_web_search";
const effectiveEnableGoogleSearch =
isSaudiLawTurn || enableGoogleSearch === true || isWebSearchTurn;
let activeTools: OpenAIToolSchema[];
if (isDocumentEditTurn) {
activeTools = DIRECT_DOCX_EDIT_TOOLS;
} else {
activeTools = extraTools?.length
? ([...TOOLS, ...WORKFLOW_TOOLS, ...extraTools] as OpenAIToolSchema[])
: ([...TOOLS, ...WORKFLOW_TOOLS] as OpenAIToolSchema[]);
}
// Server-side capability enforcement: expose only the tools the resolved
// turn mode is allowed to use. The model never sees a forbidden tool.
activeTools = activeTools.filter((tool) =>
isToolAllowedForMode(tool.function.name, effectiveTurnMode),
);
// Extract system prompt; pass remaining turns to the adapter as
// plain user/assistant messages.
const rawMsgs = apiMessages as { role: string; content: string | null }[];
let systemPrompt =
rawMsgs[0]?.role === "system" ? (rawMsgs[0].content ?? "") : "";
const lastUserContent =
rawMsgs
.slice()
.reverse()
.find((m) => m.role === "user")?.content ?? "";
// Append mode-specific banner. Direct DOCX edit mode already uses
// DIRECT_DOCX_EDIT_SYSTEM_PROMPT from buildMessages(), so never append
// the legacy plan_comments_on_document edit banner there.
systemPrompt = appendModeBanner(systemPrompt, {
trackChanges,
effectiveTurnMode,
saudiLawRouting,
});
let activeSaudiLawSourcePacket: SaudiLawSourcePacket | null = null;
if (isSaudiLawTurn) {
systemPrompt += `\n\n${buildSaudiLawWebHintsBanner(saudiLawRouting)}`;
activeSaudiLawSourcePacket =
saudiLawSourcePacket ??
buildSaudiLawSourcePacket({
routing: saudiLawRouting,
userText: lastUserContent,
});
const sourcePacketBanner = renderSaudiLawSourcePacketHintsBanner(
activeSaudiLawSourcePacket,
);
if (sourcePacketBanner) {
systemPrompt += `\n\n${sourcePacketBanner}`;
}
}
const chatMessages: LlmMessage[] = rawMsgs
.filter((m) => m.role !== "system")
.map((m) => ({
role: m.role === "assistant" ? "assistant" : "user",
content: m.content ?? "",
}));
const chatFeatureId = projectId ? "project-chat" : "chat";
const activeInferenceFeatureId = isSaudiLawTurn
? "saudi-law-answer"
: chatFeatureId;
const defaultChatModel = isSaudiLawTurn
? (process.env.SAUDI_EVAL_ANSWER_MODEL?.trim() ||
getDefaultModelForFeature("saudi-law-answer"))
: getDefaultModelForFeature(chatFeatureId);
const selectedModel = isSaudiLawTurn
? resolveModel(undefined, defaultChatModel)
: resolveModel(model, defaultChatModel);
const assistantTrace = createTraceChild(trace, {
name: "assistant.turn",
run_type: "chain",
inputs: {
message_count: chatMessages.length,
api_messages: apiMessages,
doc_count: Object.keys(docIndex).length,
workflow_count: workflowStore?.size ?? 0,
tabular_columns: tabularStore?.columns.length ?? 0,
model: selectedModel,
track_changes: trackChanges ?? false,
turn_mode: effectiveTurnMode,
enable_google_search: effectiveEnableGoogleSearch,
inference_feature_id: activeInferenceFeatureId,
saudi_law_source_packet:
summarizeSaudiLawSourcePacket(activeSaudiLawSourcePacket),
},
tags: ["assistant", "llm"],
metadata: {
project_id: projectId ?? null,
},
});
let activeSaudiLawEvidence: SaudiLawEvidencePacket | null = null;
if (isSaudiLawTurn && activeSaudiLawSourcePacket) {
activeSaudiLawEvidence = await buildSaudiLawEvidencePacket({
routing: saudiLawRouting,
sourcePacket: activeSaudiLawSourcePacket,
userText: lastUserContent,
apiKeys,
trace: assistantTrace,
signal,
});
systemPrompt += `\n\n${renderSaudiLawEvidencePacketBanner(activeSaudiLawEvidence)}`;
assistantTrace?.addEvent({
name: "saudi_law_evidence_packet",
kwargs:
summarizeSaudiLawEvidencePacket(activeSaudiLawEvidence) ??
undefined,
});
}
// CRITICAL: Do not inline full document contents into the system prompt.
// Document content must enter the model context only through read_document
// or fetch_documents, and each full document may be returned at most once
// per assistant turn. This prevents duplicate document bodies from
// ballooning provider payloads and exhausting proxy credentials.
console.log(
"[runLLMStream] system prompt:\n" +
"─".repeat(80) +
"\n" +
systemPrompt.slice(0, 1000) +
"...\n[TRUNCATED IN LOGS]\n" +
"─".repeat(80),
);
const isSaudiLawContext = /Saudi|KSA|CTL|Civil Transactions Law|Royal Decree|Article \d+|regulation|Ministry|ZATCA|MHRSD|Qiwa|Companies Law|Labour Law|Arbitration Law|Enforcement Law|Bankruptcy Law/i.test(lastUserContent);
const events: AssistantEvent[] = [];
let emittedResearchGrounding: SearchGrounding | null = null;
if (activeSaudiLawEvidence) {
const researchGrounding = activeSaudiLawEvidence.research.grounding;
if (researchGrounding && hasGrounding(researchGrounding)) {
emittedResearchGrounding = researchGrounding;
events.push({
type: "search_grounding",
grounding: researchGrounding,
});
write(
`data: ${JSON.stringify({
type: "search_grounding",
grounding: researchGrounding,
})}\n\n`,
);
}
}
const emitMergedGrounding = (grounding: SearchGrounding) => {
const merged = mergeGrounding(emittedResearchGrounding, grounding);
if (!merged) return;
emittedResearchGrounding = merged;
const existingIdx = events.findIndex(
(event) => event.type === "search_grounding",
);
const groundingEvent = {
type: "search_grounding" as const,
grounding: merged,
};
if (existingIdx >= 0) {
events[existingIdx] = groundingEvent;
} else {
events.push(groundingEvent);
}
write(
`data: ${JSON.stringify({
type: "search_grounding",
grounding: merged,
})}\n\n`,
);
};
// One assistant turn produces at most one document_versions row per
// edited doc. `runToolCalls` fires once per tool-call batch; the model
// may emit multiple batches in a single turn, so this map persists
// across batches to let subsequent plan_comments_on_document edit calls overwrite the
// turn's existing version instead of creating a new one.
const turnEditState: TurnEditState = new Map();
let fullText = "";
let iterText = "";
let iterVisibleText = "";
let iterReasoning = "";
let visibleTailBuffer = "";
let citationsOpenSeen = false;
const streamVisibleContent = (delta: string) => {
if (!delta) return;
if (citationsOpenSeen) return;
const combined = visibleTailBuffer + delta;
const markerIdx = combined.indexOf(CITATIONS_OPEN_TAG);
if (markerIdx >= 0) {
const visible = combined.slice(0, markerIdx);
if (visible) {
iterVisibleText += visible;
write(
`data: ${JSON.stringify({ type: "content_delta", text: visible })}\n\n`,
);
assistantTrace?.addEvent({
name: "content_delta",
kwargs: { text: visible },
});
}
visibleTailBuffer = "";
citationsOpenSeen = true;
return;
}
const keep = Math.min(CITATIONS_OPEN_TAG.length - 1, combined.length);
const visible = combined.slice(0, combined.length - keep);
visibleTailBuffer = combined.slice(combined.length - keep);
if (visible) {
iterVisibleText += visible;
write(
`data: ${JSON.stringify({ type: "content_delta", text: visible })}\n\n`,
);
assistantTrace?.addEvent({
name: "content_delta",
kwargs: { text: visible },
});
}
};
// Keep the streaming connection active during long model turns.
const keepAliveInterval = setInterval(() => {
try {
write(": keep-alive\n\n");
} catch (e) {
// connection likely closed
clearInterval(keepAliveInterval);
}
}, 5000);
const flushVisibleTail = () => {
if (citationsOpenSeen || !visibleTailBuffer) {
visibleTailBuffer = "";
return;
}
iterVisibleText += visibleTailBuffer;
write(
`data: ${JSON.stringify({ type: "content_delta", text: visibleTailBuffer })}\n\n`,
);
assistantTrace?.addEvent({
name: "content_delta",
kwargs: { text: visibleTailBuffer },
});
visibleTailBuffer = "";
};
const flushText = () => {
if (!iterText) return;
fullText += iterText;
flushVisibleTail();
if (iterVisibleText) {
events.push({ type: "content", text: iterVisibleText });
}
iterText = "";
iterVisibleText = "";
visibleTailBuffer = "";
citationsOpenSeen = false;
};
const flushReasoning = () => {
if (!iterReasoning) return;
events.push({ type: "reasoning", text: iterReasoning });
write(`data: ${JSON.stringify({ type: "reasoning_block_end" })}\n\n`);
iterReasoning = "";
};
const commentAttemptState: CommentAttemptState = new Map();
const pendingCommentPlanState: PendingCommentPlanState = new Map();
const turnDocumentState = createTurnDocumentState();
const allWorkflowFindings: WorkflowFinding[] = [];
const workflowAccumulator = new WorkflowAccumulator();
const isWorkflowTurn = effectiveTurnMode === "workflow_review";
let toolBatchCount = 0;
const emptyTurnRetryInstruction = isDocumentEditTurn
? DOCX_EDIT_EMPTY_TURN_RETRY
: isWorkflowTurn
? WORKFLOW_REVIEW_EMPTY_TURN_RETRY
: isWebSearchTurn
? WEB_SEARCH_EMPTY_TURN_RETRY
: undefined;
try {
if (activeSaudiLawEvidence?.research.invoked) {
await delayBetweenSaudiEvalProxyCalls(
"Saudi-law final answer after research node",
);
}
const streamResult = await streamChatWithTools({
model: selectedModel,
explicitRequestProvider: inferenceProvider,
featureId: activeInferenceFeatureId,
systemPrompt,
messages: chatMessages,
tools: activeTools as OpenAIToolSchema[],
maxIterations: 10,
apiKeys,
enableThinking: true,
trace: assistantTrace,
workflowMode: isWorkflowTurn,
enableGoogleSearch: effectiveEnableGoogleSearch,
emptyTurnRetryInstruction,
signal,
...(isSaudiLawContext ? { temperature: 0 } : {}),
callbacks: {
onContentDelta: (delta) => {
iterText += delta;
streamVisibleContent(delta);
},
onReasoningDelta: (delta) => {
iterReasoning += delta;
write(
`data: ${JSON.stringify({ type: "reasoning_delta", text: delta })}\n\n`,
);
},
onReasoningBlockEnd: () => {
if (!iterReasoning) return;
events.push({ type: "reasoning", text: iterReasoning });
write(
`data: ${JSON.stringify({ type: "reasoning_block_end" })}\n\n`,
);
iterReasoning = "";
},
// Fires after Claude's turn ends with stop_reason=tool_use, before
// the tool actually runs. Flushes any buffered assistant text so
// it's emitted in chronological order, then signals the client so
// it can open a fresh PreResponseWrapper (shows "Working…") while
// the tool executes — avoids the dead gap between message_stop
// and the first tool-specific event.
onToolCallStart: (call) => {
flushReasoning();
flushText();
write(
`data: ${JSON.stringify({
type: "tool_call_start",
name: call.name,
})}\n\n`,
);
},
onGrounding: (grounding) => {
flushReasoning();
flushText();
emitMergedGrounding(grounding);
},
},
runTools: async (calls) => {
// Emit any text the model produced before this tool turn so the
// UI sees it before the tool results stream in.
flushReasoning();
flushText();
try {
toolBatchCount += 1;
assertToolBudget({
toolBatchCount,
turnDocumentState,
workflowReview: isWorkflowTurn,
});
// After the document is loaded, workflow review must move to
// plan_comments — block prep/search tools even if the proxy
// hallucinates them.
const workflowWritePhase =
isWorkflowTurn &&
turnDocumentState.readsByDocLabel.size > 0;
const allowedCalls = calls.filter((c) => {
if (
workflowWritePhase &&
!WORKFLOW_WRITE_TOOL_NAMES.has(c.name)
) {
return false;
}
return isToolAllowedForMode(c.name, effectiveTurnMode);
});
const forbiddenCalls = calls.filter(
(c) => !allowedCalls.includes(c),
);
const toolCalls: ToolCall[] = allowedCalls.map((c) => ({
id: c.id,
function: {
name: c.name,
arguments: JSON.stringify(c.input),
},
}));
const {
toolResults,
docsRead,
docsFound,
docsCreated,
docsReplicated,
workflowsApplied,
docsEdited,
workflowFindings,
workflowRows,
} = await runToolCalls(
toolCalls,
docStore,
userId,
db,
write,
workflowStore,
tabularStore,
docIndex,
turnEditState,
projectId,
assistantTrace,
commentAttemptState,
anchorManifestState,
pendingCommentPlanState,
trackChanges,
turnDocumentState,
);
assertToolBudget({
toolBatchCount,
turnDocumentState,
workflowReview: isWorkflowTurn,
});
allWorkflowFindings.push(...workflowFindings);
workflowAccumulator.addRows(workflowRows);
for (const r of docsRead) {
events.push({
type: "doc_read",
filename: r.filename,
document_id: r.document_id,
});
}
for (const f of docsFound) {
events.push({
type: "doc_find",
filename: f.filename,
query: f.query,
total_matches: f.total_matches,
});
}
for (const dl of docsCreated) {
events.push({
type: "doc_created",
filename: dl.filename,
download_url: dl.download_url,
document_id: dl.document_id,
version_id: dl.version_id,
version_number: dl.version_number ?? null,
});
}
for (const r of docsReplicated) {
events.push({
type: "doc_replicated",
filename: r.filename,
count: r.count,
copies: r.copies,
});
}
for (const wf of workflowsApplied) {
events.push({
type: "workflow_applied",
workflow_id: wf.workflow_id,
title: wf.title,
});
}
for (const e of docsEdited) {
events.push({
type: "doc_edited",
filename: e.filename,
document_id: e.document_id,
version_id: e.version_id,
version_number: e.version_number,
download_url: e.download_url,
annotations: e.annotations,
apply_report: e.apply_report,
apply_summary: e.apply_summary,
edit_verification: e.edit_verification,
});
}
// Index alignment would break if any tool branch skips its
// push (unhandled tool name, disabled store, guard failure).
// Each tool_result already carries its tool_call_id, so key off
// that directly — and fall back to an error result for any
// tool_use that didn't produce one, so Claude's next request
// has a tool_result for every tool_use it sent.
const resultByCallId = new Map<string, string>();
const callById = new Map(toolCalls.map((call) => [call.id, call]));
const toolFailures: ToolFailureSummary[] = [];
for (const r of toolResults) {
const row = r as ToolResultRow;
resultByCallId.set(row.tool_call_id, String(row.content ?? ""));
const failure = summarizeToolFailure(
row,
callById.get(row.tool_call_id),
);
if (failure) toolFailures.push(failure);
}
for (const call of toolCalls) {
if (!resultByCallId.has(call.id)) {
toolFailures.push(summarizeMissingToolResult(call));
}
}
for (const call of forbiddenCalls) {
resultByCallId.set(
call.id,
JSON.stringify({
error: forbiddenToolMessage(
call.name,
effectiveTurnMode,
),
}),
);
}
const normalizedResults: NormalizedToolResult[] = calls.map((c) => ({
tool_use_id: c.id,
content:
resultByCallId.get(c.id) ??
JSON.stringify({
error: `Tool '${c.name}' is not available.`,
}),
}));
if (isWorkflowTurn) {
const decision = isWorkflowTerminalBatch({
calls,
toolResults: normalizedResults,
workflowRows: workflowAccumulator.getRows(),
});
if (decision.terminal) {
return {
results: normalizedResults,
control: {
endTurn: true,
suppressFurtherModelCalls: true,
reason:
decision.reason === "applied"
? "workflow_terminal"
: decision.reason === "no_action"
? "workflow_no_action"
: "workflow_failed",
},
};
}
}
return normalizedResults;
} catch (err) {
throw err;
}
},
});
flushReasoning();
flushText();
if (effectiveTurnMode === "web_search") {
const hasAnswerContent = events.some(
(event) =>
event.type === "content" && event.text.trim().length > 0,
);
if (!hasAnswerContent) {
throw new ControlledUserFacingError({
code: "assistant_stream_error",
message:
"Web search completed but no answer was produced. Please retry.",
safeDetails: {
turn_mode: effectiveTurnMode,
model: selectedModel,
event_types: events.map((event) => event.type),
has_answer_content: hasAnswerContent,
},
});
}
}
if (isDocumentEditTurn) {
const hasVisibleText =
fullText.trim().length > 0 ||
events.some(
(event) =>
event.type === "content" && event.text.trim().length > 0,
);
const visibleText = [
fullText,
...events
.filter((event) => event.type === "content")
.map((event) => (event as { text: string }).text),
].join("\n");
const hasAppliedDocEdit = events.some(
(event) => event.type === "doc_edited",
);
const hasClarification = isClarifyingDocumentEditResponse(visibleText);
if (!hasAppliedDocEdit && !hasClarification) {
throw new ControlledUserFacingError({
code: "assistant_stream_error",
message:
hasVisibleText
? "The model responded without applying a document edit or asking a clarification question. No document changes were saved. Please retry, or switch to a non-proxy model if this repeats."
: "The model stopped without applying a document edit. No document changes were saved. Please retry, or switch to a non-proxy model if this repeats.",
safeDetails: {
turn_mode: effectiveTurnMode,
model: selectedModel,
event_types: events.map((event) => event.type),
has_visible_text: hasVisibleText,
has_clarification: hasClarification,
},
});
}
}
// Parse and emit citations from <CITATIONS> block plus web grounding
const docCitations = buildCitations
? buildCitations(fullText)
: parseCitations(fullText).map((c) => {
const docInfo = resolveDoc(c.doc_id, docIndex);
return {
ref: c.ref,
doc_id: c.doc_id,
document_id: docInfo?.document_id,
version_id: docInfo?.version_id ?? null,
version_number: docInfo?.version_number ?? null,
filename: docInfo?.filename ?? c.doc_id,
page: c.page,
quote: c.quote,
};
});
const webCitations = extractWebAnnotationsFromEvents(events);
const citations = [...docCitations, ...webCitations];
write(`data: ${JSON.stringify({ type: "citations", citations })}\n\n`);
if (isWorkflowTurn) {
const render = buildWorkflowSummaryFromRows(workflowAccumulator.getRows());
if (render.reasoningMarkdown.trim()) {
events.push({
type: "reasoning",
text: render.reasoningMarkdown,
});
write(
`data: ${JSON.stringify({
type: "reasoning_block_end",
})}\n\n`,
);
}
events.push({
type: "workflow_summary",
summary: render.markdown,
reasoning: render.reasoningMarkdown,
metrics: render.metrics,
rows: workflowAccumulator.getRows(),
});
write(`data: ${JSON.stringify({
type: "workflow_summary_replace",
summary: render.markdown,
reasoning: render.reasoningMarkdown,
metrics: render.metrics,
rows: workflowAccumulator.getRows(),
})}\n\n`);
}
write("data: [DONE]\n\n");
await endTrace(assistantTrace, {
outputs: {
fullTextLength: fullText.length,
event_count: events.length,
citation_count: citations.length,
...(isWorkflowTurn ? {
workflow_terminal_after_tool: streamResult.workflowTerminatedAfterTools ?? false,
workflow_llm_iterations: streamResult.iterations ?? 0,
workflow_prevented_final_call: streamResult.workflowTerminatedAfterTools ?? false,
suppressed_workflow_text_chars: streamResult.suppressedWorkflowTextLength ?? 0,
workflow_findings_total: workflowAccumulator.size(),
workflow_findings_applied: workflowAccumulator.getRows().filter(
(r) => r.apply_status === "applied",
).length,
workflow_findings_failed: workflowAccumulator.getRows().filter(
(r) => r.apply_status !== "applied" && r.apply_status !== "ignored_pass",
).length,
workflow_findings_ignored_pass: workflowAccumulator.getRows().filter(
(r) => r.apply_status === "ignored_pass",
).length,
} : {}),
},
});
clearInterval(keepAliveInterval);
return { fullText, events, saudiLawEvidence: activeSaudiLawEvidence };
} catch (err) {
clearInterval(keepAliveInterval);
await endTrace(assistantTrace, {
outputs: {
fullTextLength: fullText.length,
event_count: events.length,
},
error: err instanceof Error ? err.message : String(err),
});
throw err;
}
}
// ---------------------------------------------------------------------------
// Annotation extraction (for DB save)
// ---------------------------------------------------------------------------
export function extractAnnotations(
fullText: string,
docIndex: DocIndex,
events?: { type: string } & Record<string, unknown>[] | unknown[],
): unknown[] {
const out: unknown[] = parseCitations(fullText).map((c) => {
const docInfo = resolveDoc(c.doc_id, docIndex);
return {
type: "citation_data",
ref: c.ref,
doc_id: c.doc_id,
document_id: docInfo?.document_id,
version_id: docInfo?.version_id ?? null,
version_number: docInfo?.version_number ?? null,
filename: docInfo?.filename ?? c.doc_id,
page: c.page,
quote: c.quote,
};
});
if (Array.isArray(events)) {
for (const ev of events as { type?: string; annotations?: EditAnnotation[] }[]) {
if (ev?.type === "doc_edited" && Array.isArray(ev.annotations)) {
for (const a of ev.annotations) out.push({ ...a, type: "edit_data" });
}
}
out.push(...extractWebAnnotationsFromEvents(events));
}
return out;
}
// ---------------------------------------------------------------------------
// Document context builder (from message file attachments)
// ---------------------------------------------------------------------------
export async function buildDocContext(
messages: ChatMessage[],
userId: string,
db: ReturnType<typeof createServerSupabase>,
chatId?: string | null,
): Promise<{ docIndex: DocIndex; docStore: DocStore }> {
const docIndex: DocIndex = {};
const docStore: DocStore = new Map();
const documentIds = new Set<string>();
for (const m of messages) {
for (const f of m.files ?? []) {
if (f.document_id) documentIds.add(f.document_id);
}
}
// Also pull in document_ids from prior assistant events in this chat —
// generated docs (generate_docx) and tracked-change edits (plan_comments_on_document)
// aren't attached to user messages as files, so they only live in the
// assistant's `doc_created` / `doc_edited` events. Without this sweep
// the model loses access to generated docs after the turn that created
// them, and can't call plan_comments_on_document / read_document on them.
if (chatId) {
const { data: rows } = await db
.from("chat_messages")
.select("content")
.eq("chat_id", chatId)
.eq("role", "assistant");
for (const row of rows ?? []) {
const content = (row as { content?: unknown }).content;
if (!Array.isArray(content)) continue;
for (const ev of content as Record<string, unknown>[]) {
if (
(ev?.type === "doc_created" ||
ev?.type === "doc_edited") &&
typeof ev.document_id === "string"
) {
documentIds.add(ev.document_id);
}
}
}
}
const ids = [...documentIds];
if (ids.length > 0) {
const { data: docs } = await db
.from("documents")
.select("id, filename, file_type, current_version_id, status")
.in("id", ids)
.eq("user_id", userId)
.eq("status", "ready");
const docList = (docs ?? []) as unknown as {
id: string;
filename: string;
file_type: string;
current_version_id?: string | null;
active_version_number?: number | null;
storage_path?: string | null;
}[];
await attachActiveVersionPaths(db, docList);
for (let i = 0; i < docList.length; i++) {
const doc = docList[i];
if (!doc.storage_path) continue;
const docLabel = `doc-${i}`;
docIndex[docLabel] = {
document_id: doc.id,
filename: doc.filename,
file_type: doc.file_type,
version_id: doc.current_version_id ?? null,
version_number: doc.active_version_number ?? null,
};
docStore.set(docLabel, {
storage_path: doc.storage_path,
file_type: doc.file_type,
filename: doc.filename,
});
}
}
console.log(
"[buildDocContext] available docs:",
Object.entries(docIndex).map(([label, info]) => ({
label,
filename: info.filename,
document_id: info.document_id,
})),
);
return { docIndex, docStore };
}
export async function buildProjectDocContext(
projectId: string,
_userId: string,
db: ReturnType<typeof createServerSupabase>,
): Promise<{ docIndex: DocIndex; docStore: DocStore; folderPaths: Map<string, string> }> {
const docIndex: DocIndex = {};
const docStore: DocStore = new Map();
const [{ data: docs }, { data: folders }] = await Promise.all([
db.from("documents")
.select("id, filename, file_type, current_version_id, status, folder_id")
.eq("project_id", projectId)
.eq("status", "ready")
.order("created_at", { ascending: true }),
db.from("project_subfolders")
.select("id, name, parent_folder_id")
.eq("project_id", projectId),
]);
const docList = (docs ?? []) as unknown as {
id: string;
filename: string;
file_type: string;
current_version_id?: string | null;
active_version_number?: number | null;
folder_id?: string | null;
storage_path?: string | null;
}[];
await attachActiveVersionPaths(db, docList);
// Build folder id → full path map
const folderMap = new Map<string, { name: string; parent_folder_id: string | null }>();
for (const f of folders ?? []) folderMap.set(f.id, { name: f.name, parent_folder_id: f.parent_folder_id });
function resolvePath(folderId: string | null): string {
if (!folderId) return "";
const parts: string[] = [];
let cur: string | null = folderId;
while (cur) {
const f = folderMap.get(cur);
if (!f) break;
parts.unshift(f.name);
cur = f.parent_folder_id;
}
return parts.join(" / ");
}
const folderPaths = new Map<string, string>(); // doc label → folder path
for (let i = 0; i < docList.length; i++) {
const doc = docList[i];
if (!doc.storage_path) continue;
const docLabel = `doc-${i}`;
docIndex[docLabel] = {
document_id: doc.id,
filename: doc.filename,
file_type: doc.file_type,
version_id: doc.current_version_id ?? null,
version_number: doc.active_version_number ?? null,
};
docStore.set(docLabel, {
storage_path: doc.storage_path,
file_type: doc.file_type,
filename: doc.filename,
});
const path = resolvePath(doc.folder_id ?? null);
if (path) folderPaths.set(docLabel, path);
}
console.log(
"[buildProjectDocContext] available docs:",
Object.entries(docIndex).map(([label, info]) => ({
label,
filename: info.filename,
document_id: info.document_id,
folder: folderPaths.get(label) ?? null,
})),
);
return { docIndex, docStore, folderPaths };
}
export async function buildWorkflowStore(
userId: string,
userEmail: string | null | undefined,
db: ReturnType<typeof createServerSupabase>,
): Promise<WorkflowStore> {
const { BUILTIN_WORKFLOWS } = await import("./builtinWorkflows");
const store: WorkflowStore = new Map();
const normalizedUserEmail = (userEmail ?? "").trim().toLowerCase();
// Seed built-ins first
for (const wf of BUILTIN_WORKFLOWS) {
store.set(wf.id, { title: wf.title, prompt_md: wf.prompt_md, edit_mode_prompt_md: (wf as any).edit_mode_prompt_md });
}
// Then overlay user-owned assistant workflows.
const { data: workflows } = await db
.from("workflows")
.select("id, title, prompt_md")
.eq("user_id", userId)
.eq("type", "assistant");
for (const wf of workflows ?? []) {
if (wf.prompt_md) {
store.set(wf.id, { title: wf.title, prompt_md: wf.prompt_md });
}
}
// Shared assistant workflows must also be readable by workflow tools.
if (normalizedUserEmail) {
const { data: shares } = await db
.from("workflow_shares")
.select("workflow_id")
.eq("shared_with_email", normalizedUserEmail);
const sharedIds = [...new Set((shares ?? []).map((share) => share.workflow_id))];
if (sharedIds.length > 0) {
const { data: sharedWorkflows } = await db
.from("workflows")
.select("id, title, prompt_md")
.in("id", sharedIds)
.eq("type", "assistant");
for (const wf of sharedWorkflows ?? []) {
if (wf.prompt_md) {
store.set(wf.id, { title: wf.title, prompt_md: wf.prompt_md });
}
}
}
}
return store;
}