relay-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
utils/paths.py ADDED
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def resolve_path(base: str | Path, path: str | Path) -> Path:
5
+ base_path = Path(base).resolve()
6
+ candidate = Path(path)
7
+ resolved = candidate.resolve() if candidate.is_absolute() else (base_path / candidate).resolve()
8
+ try:
9
+ resolved.relative_to(base_path)
10
+ except ValueError as exc:
11
+ raise ValueError(f"Path is outside the working directory: {path}") from exc
12
+ return resolved
13
+
14
+ def display_path_relative_to_cwd(path: str, cwd: Path | None) -> str:
15
+ try:
16
+ p = Path(path)
17
+ except Exception:
18
+ return path
19
+
20
+ if cwd:
21
+ try:
22
+ return str(p.relative_to(cwd))
23
+ except ValueError:
24
+ pass
25
+
26
+ return str(p)
27
+
28
+ def is_binary_file(path: str | Path) -> bool:
29
+ try:
30
+ with open(path, "rb") as f:
31
+ chunk = f.read(8192)
32
+ return b"\x00" in chunk
33
+ except (OSError, IOError):
34
+ return False
35
+
36
+ def ensure_parent_dir(path: str | Path) -> Path:
37
+ path = Path(path)
38
+
39
+ path.parent.mkdir(
40
+ parents=True,
41
+ exist_ok=True
42
+ )
43
+
44
+ return path
utils/text.py ADDED
@@ -0,0 +1,74 @@
1
+ import tiktoken
2
+
3
+ def get_tokenizer(model: str):
4
+ try:
5
+ return tiktoken.encoding_for_model(model)
6
+ except Exception:
7
+ return tiktoken.get_encoding("cl100k_base")
8
+
9
+ def count_tokens(text: str, model: str = "") -> int:
10
+ tokenizer = get_tokenizer(model)
11
+
12
+ if tokenizer:
13
+ return len(tokenizer.encode(text))
14
+
15
+ return estimate_tokens(text)
16
+
17
+ def estimate_tokens(text: str) -> int:
18
+ return max(1, len(text) // 4) # on avg 1 token is ~ 4 characters
19
+
20
+ def truncate_text(
21
+ text: str,
22
+ model: str,
23
+ max_tokens: int,
24
+ suffix: str = "\n...[truncated]",
25
+ preserve_lines: bool = True,
26
+ ):
27
+
28
+ current_tokens = count_tokens(text, model)
29
+
30
+ if current_tokens <= max_tokens:
31
+ return text
32
+
33
+ suffix_tokens = count_tokens(suffix, model)
34
+ target_tokens = max_tokens - suffix_tokens
35
+
36
+ if target_tokens <= 0:
37
+ return suffix.strip()
38
+
39
+ if preserve_lines:
40
+ return _truncate_by_lines(text, target_tokens, suffix, model)
41
+ else:
42
+ return _truncate_by_chars(text, target_tokens, suffix, model)
43
+
44
+ def _truncate_by_lines(text: str, target_tokens: int, suffix: str, model: str) -> str:
45
+ lines = text.split("\n")
46
+ result_lines: list[str] = []
47
+ current_tokens = 0
48
+
49
+ for line in lines:
50
+ line_tokens = count_tokens(line + "\n", model)
51
+ if current_tokens + line_tokens > target_tokens:
52
+ break
53
+ result_lines.append(line)
54
+ current_tokens += line_tokens
55
+
56
+ if not result_lines:
57
+ # Fall back to character truncation if no complete lines fit
58
+ return _truncate_by_chars(text, target_tokens, suffix, model)
59
+
60
+ return "\n".join(result_lines) + suffix
61
+
62
+
63
+ def _truncate_by_chars(text: str, target_tokens: int, suffix: str, model: str) -> str:
64
+ # Binary search for the right length
65
+ low, high = 0, len(text)
66
+
67
+ while low < high:
68
+ mid = (low + high + 1) // 2
69
+ if count_tokens(text[:mid], model) <= target_tokens:
70
+ low = mid
71
+ else:
72
+ high = mid - 1
73
+
74
+ return text[:low] + suffix