"""
Context builder — assembles system prompt, user context, and messages for LLM calls.
"""
from __future__ import annotations
from typing import Any, Optional
from openspace.utils.logging import Logger
logger = Logger.get_logger(__name__)
class ContextBuilder:
"""Builds the full context dict (system prompt + user context + messages)
that gets sent to the LLM for each query."""
def __init__(
self,
base_system_prompt: str = "",
skill_instructions: str = "",
) -> None:
self._base_system_prompt = base_system_prompt
self._skill_instructions = skill_instructions
async def build(
self,
query: str,
session_state: dict[str, Any],
) -> dict[str, Any]:
system_prompt = self._build_system_prompt(session_state)
user_context = self._build_user_context(session_state)
messages: list[dict[str, Any]] = []
history = session_state.get("messages", [])
if history:
messages.extend(history)
messages.append({"role": "user", "content": query})
return {
"system_prompt": system_prompt,
"user_context": user_context,
"messages": messages,
}
# ── System prompt ─────────────────────────────────────────────
def _build_system_prompt(self, session_state: dict[str, Any]) -> str:
parts: list[str] = []
if self._base_system_prompt:
parts.append(self._base_system_prompt)
if self._skill_instructions:
parts.append(self._skill_instructions)
prompt = "\n\n".join(parts) if parts else ""
prompt = self._inject_session_metadata(prompt, session_state)
authorized_tools = session_state.get("authorized_tools", [])
if authorized_tools:
prompt = self._inject_permission_context(prompt, authorized_tools)
diagnostics = self._build_diagnostics(session_state)
if diagnostics:
prompt = f"{prompt}\n\n{diagnostics}"
return prompt
# ── User context ──────────────────────────────────────────────
def _build_user_context(self, session_state: dict[str, Any]) -> dict[str, Any]:
ctx: dict[str, Any] = {}
if "project" in session_state:
ctx["project"] = session_state["project"]
if "cwd" in session_state:
ctx["cwd"] = session_state["cwd"]
tools = session_state.get("authorized_tools", [])
if tools:
ctx["authorized_tools"] = tools
env = session_state.get("env")
if env:
ctx["env"] = env
return ctx
# ── Injection helpers ─────────────────────────────────────────
def _inject_session_metadata(
self,
prompt: str,
session_state: dict[str, Any],
) -> str:
meta_parts: list[str] = []
session_id = session_state.get("session_id")
if session_id:
meta_parts.append(f"Session ID: {session_id}")
turn_count = session_state.get("turn_count")
if turn_count is not None:
meta_parts.append(f"Turn: {turn_count}")
model = session_state.get("model")
if model:
meta_parts.append(f"Model: {model}")
if not meta_parts:
return prompt
block = "\n".join(meta_parts)
return f"{prompt}\n\n\n{block}\n"
def _inject_permission_context(
self,
prompt: str,
authorized_tools: list[str],
) -> str:
tool_list = ", ".join(authorized_tools)
block = (
"\n"
f"Pre-authorized tools: {tool_list}\n"
"These tools may be used without additional user confirmation.\n"
""
)
return f"{prompt}\n\n{block}"
# ── Diagnostics ───────────────────────────────────────────────
def _build_diagnostics(
self,
session_state: dict[str, Any],
) -> Optional[str]:
diag = session_state.get("diagnostics")
if not diag:
return None
lines: list[str] = [""]
if isinstance(diag, dict):
for key, value in diag.items():
lines.append(f" {key}: {value}")
elif isinstance(diag, str):
lines.append(f" {diag}")
else:
lines.append(f" {diag!r}")
lines.append("")
return "\n".join(lines)