import { ContextTextReader } from "./context-text-reader"; import { AGENT_PROMPT_MODULE_TITLES } from "@opengeni/contracts"; import type { ModelContextInstructionLayer, SessionModelContextResponse } from "@opengeni/sdk"; import { modelDisplayName } from "@opengeni/sdk/model-display"; import { ArrowLeftIcon, ChevronRightIcon, RefreshCwIcon } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { Button } from "../../../ui/button"; import { Input } from "../../../ui/input"; import { ScrollArea } from "../../../ui/scroll-area"; import { useAppContext } from "../../app-context"; import { userErrorText } from "../../../ui/api-error"; import type { SessionEvent } from "../../types"; const PAGE_SIZE = 30; const json = (value: unknown): string => JSON.stringify(value, null, 2) ?? ""; function record(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; } function readable(value: unknown): string { if (typeof value === "string") return value; if (Array.isArray(value)) return value.map(readable).join("\n\n"); const data = record(value); if (typeof data.text === "string") return data.text; if (["input_image", "image_url"].includes(String(data.type))) return "Image attachment"; if (data.encrypted_content) return ( "Encrypted content. Token count unavailable." + (data.summary ? "\n" + readable(data.summary) : "") ); return readableField(data) ?? json(value); } function readableField(data: Record): string | undefined { const value = data.content ?? data.output ?? data.arguments; return value == null ? undefined : readable(value); } function labelFor(value: unknown): string { const data = record(value); if (data.role) return ( ( { user: "You", assistant: "Assistant", developer: "Developer", system: "System", tool: "Tool result", } as Record )[String(data.role)] ?? String(data.role) ); if (data.type === "function_call") return "Tool call · " + String(data.name ?? ""); if (data.type === "function_call_output") return "Tool result"; if (data.type === "reasoning") return "Reasoning"; if (data.type === "compaction") return "Compacted history"; return typeof value === "string" ? "Input" : String(data.type ?? "Message"); } function tokenLabel(value: number | null | undefined) { return value == null ? "Unknown tokens" : "~" + value.toLocaleString() + " tokens"; } /** One titled part of the sent instructions: a layer, or a prompt module inside one. */ export type InstructionSection = { key: string; title: string; text: string; tokens: number | null; /** 1 = a prompt module inside the operational contract. */ depth: 0 | 1; }; /** * The captured instruction layers as titled sections. A layer that reports * its prompt modules (sessions with agent settings) is followed by one * section per module, sliced from the layer by the reported lengths; when * the lengths don't add up, the layer stays whole. */ export function instructionSections( layers: readonly ModelContextInstructionLayer[], ): InstructionSection[] { const sections: InstructionSection[] = []; for (const layer of layers) { sections.push({ key: layer.id, title: layer.title, text: layer.content, tokens: layer.estimatedTokens, depth: 0, }); const modules = layer.modules ?? []; const separator = "\n\n"; const total = modules.reduce((sum, module) => sum + module.chars, 0) + Math.max(0, modules.length - 1) * separator.length; if (modules.length === 0 || total !== layer.content.length) continue; let offset = 0; for (const module of modules) { const text = layer.content.slice(offset, offset + module.chars); offset += module.chars + separator.length; sections.push({ key: `${layer.id}:${module.id}`, title: AGENT_PROMPT_MODULE_TITLES[module.id] ?? module.id, text, tokens: Math.max(1, Math.round(module.chars / 4)), depth: 1, }); } } return sections; } export function ModelContextInspectorPane(props: { workspaceId: string; sessionId: string; events: SessionEvent[]; isRunning?: boolean | undefined; }) { const { client } = useAppContext(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [response, setResponse] = useState(null); const [refresh, setRefresh] = useState(0); const [pending, setPending] = useState(null); const currentCapture = useRef(null); const loadNext = useRef(false); useEffect(() => { setResponse(null); setPending(null); currentCapture.current = null; }, [client, props.workspaceId, props.sessionId]); // Fetch while mounted, including when a capture commits after a stream event. // One request at a time; obsolete responses cannot leak across sessions. useEffect(() => { let cancelled = false; let timer: ReturnType; setLoading(true); setError(null); let followUp = false; const fetchSnapshot = async () => { try { const next = await client.getSessionModelContext(props.workspaceId, props.sessionId); if (!cancelled) { const key = next.snapshot ? `${next.attemptId}:${next.snapshot.requestIndex}:${next.snapshot.capturedAt}` : null; if (currentCapture.current === null || loadNext.current) { currentCapture.current = key; loadNext.current = false; setResponse(next); setPending(null); } else if (key !== currentCapture.current) { setPending(next); } setError(null); } } catch (caught) { if (!cancelled) setError(userErrorText(caught)); } finally { if (!cancelled) { setLoading(false); if (props.isRunning || !followUp) { followUp = true; timer = setTimeout(fetchSnapshot, 5000); } } } }; void fetchSnapshot(); return () => { cancelled = true; clearTimeout(timer); }; }, [client, props.workspaceId, props.sessionId, props.isRunning, refresh]); const snapshot = response?.snapshot; const wire = snapshot?.providerRequest; const payload = useMemo(() => (wire?.body ? record(JSON.parse(wire.body)) : null), [wire?.body]); const [section, setSection] = useState("conversation"); const [query, setQuery] = useState(""); const [largest, setLargest] = useState(false); const [page, setPage] = useState(0); const [selected, setSelected] = useState(null); const [rawItem, setRawItem] = useState(false); const [instructionSection, setInstructionSection] = useState(null); const sections = useMemo(() => instructionSections(snapshot?.layers ?? []), [snapshot?.layers]); const openSection = sections.find((candidate) => candidate.key === instructionSection) ?? null; useEffect(() => { setSection("conversation"); setQuery(""); setPage(0); setSelected(null); }, [props.workspaceId, props.sessionId]); const conversationKey = payload && "messages" in payload ? "messages" : "input"; const partFor = (key: string) => wire?.parts.find((part) => part.key === key); const rows = useMemo(() => { const field = payload?.[section === "tools" ? "tools" : conversationKey]; const values = Array.isArray(field) ? field : field == null ? [] : [field]; const counts = wire?.parts.find( (part) => part.key === (section === "tools" ? "tools" : conversationKey), )?.itemEstimatedTokens; return values.map((value, index) => { const data = record(value); const definition = data.function ? record(data.function) : data; const label = section === "tools" ? String(definition.name ?? data.type ?? "Tool") : labelFor(value); const text = section === "tools" ? json(value) : readable(value); const preview = section === "tools" ? String(definition.description ?? data.type ?? "Tool definition") : text; return { index, value, label, text, preview: preview.slice(0, 180).replace(/\s+/g, " "), search: (label + " " + text).toLowerCase(), tokens: counts?.[index], }; }); }, [payload, section, conversationKey, wire?.parts]); const filtered = useMemo(() => { const result = rows.filter((row) => row.search.includes(query.toLowerCase())); return result.sort((a, b) => largest ? (b.tokens ?? -1) - (a.tokens ?? -1) || a.index - b.index : section === "tools" ? a.index - b.index : b.index - a.index, ); }, [rows, query, largest, section]); const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const currentPage = Math.min(page, pages - 1); const selectedRow = selected == null ? null : rows[selected]; const listSection = section === "conversation" || section === "tools"; const navigate = (next: string) => { setSection(next); setSelected(null); setQuery(""); setPage(0); setRawItem(false); setInstructionSection(null); }; return (

Captured model request

{typeof payload?.model === "string" ? modelDisplayName(payload.model) + " · " : ""} {snapshot ? new Date(snapshot.capturedAt).toLocaleTimeString() : "No capture yet"}

{pending ? ( ) : null} {payload ? ( <>
{( [ ["conversation", "Conversation", conversationKey], ["instructions", "Instructions", "instructions"], ["tools", "Tools", "tools"], ] as const ).map(([id, label, key]) => ( ))}
{listSection && !selectedRow ? ( <> { setQuery(event.target.value); setPage(0); }} />
{filtered.length.toLocaleString()} {section === "tools" ? "tools" : "items"} {query ? " matching" : " in this request"}
) : null} ) : null}
{error ? (

Could not refresh context. {error}

) : null} {loading && !snapshot ?

Loading context…

: null} {!loading && !snapshot ? (

No model request captured yet.

) : null} {snapshot && !payload ? ( <>

{wire?.unavailableReason ?? "This older capture contains only instructions. The full request was not recorded."}

) : null} {payload && listSection ? ( selectedRow ? (

#{selectedRow.index + 1} · {selectedRow.label}

{tokenLabel(selectedRow.tokens)}
) : (
{filtered.length === 0 ? (

{query ? "No matching items. Try a different search." : "No items in this request."}

) : null} {filtered .slice(currentPage * PAGE_SIZE, (currentPage + 1) * PAGE_SIZE) .map((row) => ( ))}
) ) : null} {payload && section === "instructions" && sections.length > 0 && instructionSection === null ? (

The instructions sent with this request, in order. Open a section to read it.

{[ { key: "__all", title: "Full text", text: readable(payload.instructions), tokens: partFor("instructions")?.estimatedTokens ?? null, depth: 0 as const, }, ...sections, ].map((row) => ( ))}
) : null} {payload && section === "instructions" && (sections.length === 0 || instructionSection !== null) ? (
{sections.length > 0 ? (

{openSection?.title ?? "Full text"}

) : null}
) : null} {payload && section === "raw" ? ( ) : null}
{payload ? ( ) : null}
); }