Files
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

104 lines
4.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
按字节预算保存 Python 输出,超长结果保留可继续读取的文件。
"""
import hashlib
import os
import stat
from pathlib import Path
from typing import Any
# 标准输出内联预览最多保留的字节数。
STDOUT_BYTES = 50_000
# 标准错误内存缓冲最多保留的字节数。
STDERR_BYTES = 10_000
# 标准输出落盘内容的最大字节数,避免大输出无限占用磁盘。
SPILL_BYTES = 5_000_000
class OutputBuffer:
"""
持续排空进程管道,同时把宿主内存限制在固定字节预算内。
"""
def __init__(self, limit: int) -> None:
"""
容量独立于子进程总输出量,保留总字节数以报告缺失。
"""
self.limit, self.total = limit, 0
self.data = bytearray()
def append(self, data: bytes) -> None:
"""
持续消费超限分片,避免管道堵塞导致正常 cell 无法结束。
"""
self.total += len(data)
self.data.extend(data[:max(0, self.limit - len(self.data))])
def drain(self) -> tuple[str, int]:
"""
归还本次捕获和总量,跨 cell 的输出不得累加为当前结果。
"""
text, total = self.data.decode('utf-8', errors='replace'), self.total
self.data.clear()
self.total = 0
return text, total
def read_cell_stdout(payload: dict[str, Any], directory: Path) -> str:
"""
只续读宿主确定名称的当前 cell spill,拒绝跟随子进程返回的任意路径或链接。
"""
text = str(payload.get('stdout') or '')
if not payload.get('stdout_clipped') or not payload.get('stdout_spill_path'):
return text
path = directory / f"cell_{int(payload['execution_count']):06d}_stdout.txt"
descriptor = os.open(path, os.O_RDONLY | getattr(os, 'O_NOFOLLOW', 0))
with os.fdopen(descriptor, 'rb') as stream:
if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
raise ValueError('Python stdout spill must be a regular file')
return stream.read(SPILL_BYTES).decode('utf-8', errors='ignore')
def save_stdout(raw: bytes, directory: Path) -> Path:
"""
摘要文件仅复用内容一致的普通文件,既有链接或碰撞不能伪造当前回执。
"""
directory.mkdir(parents=True, mode=0o700, exist_ok=True)
os.chmod(directory, 0o700)
path = directory / f'stdout-{hashlib.sha256(raw).hexdigest()}.txt'
try:
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
except FileExistsError:
descriptor = os.open(path, os.O_RDONLY | getattr(os, 'O_NOFOLLOW', 0))
with os.fdopen(descriptor, 'rb') as stream:
if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode) or stream.read(len(raw) + 1) != raw:
raise ValueError('Python stdout spill does not match its content digest')
else:
with os.fdopen(descriptor, 'wb') as stream:
stream.write(raw)
return path
def project_stdout(text: str, directory: Path) -> tuple[str, dict[str, object]]:
"""
50KB 内联按40%头/60%尾截取,最多5MB正文以摘要名称去重保存。
"""
raw = text.encode('utf-8', errors='replace')
captured = min(len(raw), STDOUT_BYTES)
metadata: dict[str, object] = {
'stdout_truncated': len(raw) > captured, 'stdout_bytes_total': len(raw),
'stdout_bytes_captured': captured, 'stdout_bytes_omitted': len(raw) - captured,
}
if len(raw) <= STDOUT_BYTES:
return text, metadata
kept = raw[:SPILL_BYTES]
path = save_stdout(kept, directory)
metadata.update(stdout_spill_path=str(path), stdout_spill_capped=len(raw) > SPILL_BYTES,
warning='输出已截断;从 stdout_spill_path 分页读取,不要重复执行代码。')
head = STDOUT_BYTES * 2 // 5
preview = (raw[:head].decode('utf-8', errors='replace')
+ f'\n[... omitted {len(raw) - captured} bytes ...]\n'
+ raw[-(STDOUT_BYTES - head):].decode('utf-8', errors='replace'))
return preview, metadata