prova-core / brain /devtools /attachments.py
chintakapp's picture
core 2.0
210ef30
Raw History Blame Contribute Delete
5.06 kB
"""Attached files for the developer assistant. Master prompt 6.
Limits from the spec: text or code only, up to 1 MB each, at most 20 files. On top of that, a
total budget for what is injected into one prompt — twenty 1 MB files would blow through the
free tier's per-minute token allowance in a single turn, so past the budget each file is cut and
the model is told it was.
"""
import re
from dataclasses import dataclass
MAX_FILE_BYTES = 1_000_000
MAX_FILES = 20
#: Characters of attached text injected into one turn. Roughly 50k tokens.
PROMPT_BUDGET_CHARS = 200_000
#: Extensions whose content is worth sending. Anything else is refused at the door rather than
#: sent as mojibake.
TEXT_EXTENSIONS = frozenset(
".php .js .jsx .ts .tsx .mjs .cjs .py .rb .go .rs .java .kt .kts .swift .c .h .cpp .hpp .cs "
".html .htm .css .scss .sass .less .vue .svelte .json .yaml .yml .toml .ini .env.example .xml "
".sql .md .txt .sh .bash .ps1 .bat .dockerfile .gradle .properties .twig .blade.php .lock "
".graphql .prisma .csv .conf .htaccess .nginx".split()
)
_LANGUAGE = {
".php": "php",
".js": "javascript",
".jsx": "jsx",
".ts": "typescript",
".tsx": "tsx",
".py": "python",
".json": "json",
".yaml": "yaml",
".yml": "yaml",
".sql": "sql",
".css": "css",
".scss": "scss",
".html": "html",
".vue": "vue",
".sh": "bash",
".java": "java",
".kt": "kotlin",
".go": "go",
".rs": "rust",
".rb": "ruby",
".md": "markdown",
}
_FENCE = re.compile(r"```[\w+-]*\n(.*?)```", re.DOTALL)
class AttachmentError(ValueError):
pass
@dataclass(frozen=True)
class Attachment:
name: str
content: str
def is_text_file(name: str) -> bool:
lower = name.lower()
base = lower.rsplit("/", 1)[-1]
if base in {
"dockerfile",
"makefile",
".htaccess",
"composer.json",
"package.json",
".gitignore",
}:
return True
return any(lower.endswith(ext) for ext in TEXT_EXTENSIONS)
def validate(files: list[Attachment]) -> list[Attachment]:
if len(files) > MAX_FILES:
raise AttachmentError(f"at most {MAX_FILES} files per message")
for item in files:
if not is_text_file(item.name):
raise AttachmentError(f"{item.name}: only text and code files can be attached")
if len(item.content.encode("utf-8")) > MAX_FILE_BYTES:
raise AttachmentError(f"{item.name}: larger than 1 MB")
if "\x00" in item.content:
raise AttachmentError(f"{item.name}: looks like a binary file")
return files
def language_for(name: str) -> str:
lower = name.lower()
if lower.endswith(".blade.php"):
return "blade"
for ext, language in _LANGUAGE.items():
if lower.endswith(ext):
return language
return ""
def render_for_prompt(files: list[Attachment], tree: list[str] | None = None) -> str:
"""The attachments as one block of the user's message, inside the total budget."""
if not files and not tree:
return ""
parts: list[str] = []
if tree:
shown = tree[:400]
more = f"\n… and {len(tree) - len(shown)} more" if len(tree) > len(shown) else ""
parts.append("PROJECT TREE\n" + "\n".join(shown) + more)
remaining = PROMPT_BUDGET_CHARS
per_file = PROMPT_BUDGET_CHARS // max(len(files), 1)
for item in files:
budget = min(per_file, remaining)
body = item.content
note = ""
if len(body) > budget:
body = body[:budget]
note = f"\n[… truncated: {len(item.content) - budget} more characters not shown]"
remaining -= len(body)
fence = language_for(item.name)
parts.append(f"FILE: {item.name}\n```{fence}\n{body}\n```{note}")
return "ATTACHED FOR THIS MESSAGE\n\n" + "\n\n".join(parts)
def pasted_code(message: str) -> str:
"""Code he pasted into the message itself: the fenced blocks, or failing that, all of it."""
blocks = _FENCE.findall(message)
return "\n\n".join(block.strip() for block in blocks) if blocks else message.strip()
def resolve(target: str | None, attachments: dict[str, str], message: str) -> tuple[str, str]:
"""Which code a dev tool should look at: (label, content).
A target naming an attachment wins — exactly, then by file name. Otherwise it is the code
pasted into the message, and failing that the only attachment if there is exactly one.
"""
if target:
wanted = target.strip()
if wanted in attachments:
return wanted, attachments[wanted]
by_name = {name.rsplit("/", 1)[-1]: name for name in attachments}
if wanted in by_name:
return by_name[wanted], attachments[by_name[wanted]]
code = pasted_code(message)
if _FENCE.search(message) or not attachments:
return "pasted code", code
if len(attachments) == 1:
name, content = next(iter(attachments.items()))
return name, content
return "pasted code", code