ctxoptimizer 0.4.1__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.
- ctxoptimizer/__init__.py +5 -0
- ctxoptimizer/__main__.py +14 -0
- ctxoptimizer/admin_security.py +130 -0
- ctxoptimizer/analysis/__init__.py +1 -0
- ctxoptimizer/analysis/baseline.py +69 -0
- ctxoptimizer/analysis/break_even.py +49 -0
- ctxoptimizer/analysis/drift.py +151 -0
- ctxoptimizer/analysis/edits.py +148 -0
- ctxoptimizer/analysis/markers.py +34 -0
- ctxoptimizer/analysis/recommend.py +114 -0
- ctxoptimizer/analysis/segments.py +57 -0
- ctxoptimizer/analysis/session.py +62 -0
- ctxoptimizer/analysis/state.py +239 -0
- ctxoptimizer/analysis/tokens.py +62 -0
- ctxoptimizer/analysis/waste.py +322 -0
- ctxoptimizer/cli.py +873 -0
- ctxoptimizer/config.py +512 -0
- ctxoptimizer/dashboard/__init__.py +1 -0
- ctxoptimizer/dashboard/api.py +537 -0
- ctxoptimizer/dashboard/service.py +157 -0
- ctxoptimizer/dashboard/static/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/doctor.html +46 -0
- ctxoptimizer/dashboard/templates/live.html +91 -0
- ctxoptimizer/dashboard/templates/overview.html +172 -0
- ctxoptimizer/dashboard/templates/pricing.html +115 -0
- ctxoptimizer/dashboard/templates/request.html +111 -0
- ctxoptimizer/dashboard/templates/session.html +67 -0
- ctxoptimizer/dashboard/templates/settings.html +79 -0
- ctxoptimizer/dashboard/templates/table.html +56 -0
- ctxoptimizer/dashboard/templates/transforms.html +118 -0
- ctxoptimizer/dashboard/templates/waste.html +120 -0
- ctxoptimizer/dashboard/views.py +429 -0
- ctxoptimizer/data/cache_limits.json +23 -0
- ctxoptimizer/data/default_config.toml +185 -0
- ctxoptimizer/data/pricing.json +421 -0
- ctxoptimizer/doctor.py +129 -0
- ctxoptimizer/guard.py +139 -0
- ctxoptimizer/ids.py +73 -0
- ctxoptimizer/log.py +73 -0
- ctxoptimizer/mcp.py +124 -0
- ctxoptimizer/models.py +145 -0
- ctxoptimizer/pipeline.py +126 -0
- ctxoptimizer/profiles.py +152 -0
- ctxoptimizer/providers/__init__.py +37 -0
- ctxoptimizer/providers/anthropic.py +105 -0
- ctxoptimizer/providers/base.py +191 -0
- ctxoptimizer/providers/gemini.py +143 -0
- ctxoptimizer/providers/ollama.py +133 -0
- ctxoptimizer/providers/openai_chat.py +135 -0
- ctxoptimizer/providers/openai_responses.py +121 -0
- ctxoptimizer/proxy.py +665 -0
- ctxoptimizer/routing.py +259 -0
- ctxoptimizer/server.py +319 -0
- ctxoptimizer/service.py +150 -0
- ctxoptimizer/telemetry/__init__.py +1 -0
- ctxoptimizer/telemetry/blobs.py +57 -0
- ctxoptimizer/telemetry/expand.py +62 -0
- ctxoptimizer/telemetry/pricing.py +168 -0
- ctxoptimizer/telemetry/redact.py +53 -0
- ctxoptimizer/telemetry/sse.py +145 -0
- ctxoptimizer/telemetry/store.py +829 -0
- ctxoptimizer/telemetry/usage.py +435 -0
- ctxoptimizer/transforms/__init__.py +87 -0
- ctxoptimizer/transforms/ansi.py +21 -0
- ctxoptimizer/transforms/anthropic_cache.py +100 -0
- ctxoptimizer/transforms/backref.py +74 -0
- ctxoptimizer/transforms/base.py +93 -0
- ctxoptimizer/transforms/cache_serve.py +146 -0
- ctxoptimizer/transforms/dedupe_lines.py +69 -0
- ctxoptimizer/transforms/eligibility.py +24 -0
- ctxoptimizer/transforms/openai_hints.py +84 -0
- ctxoptimizer/transforms/progress.py +52 -0
- ctxoptimizer/transforms/stale_result_elision.py +109 -0
- ctxoptimizer/transforms/truncate.py +100 -0
- ctxoptimizer/transforms/volatile.py +177 -0
- ctxoptimizer/transforms/whitespace.py +25 -0
- ctxoptimizer-0.4.1.dist-info/METADATA +196 -0
- ctxoptimizer-0.4.1.dist-info/RECORD +83 -0
- ctxoptimizer-0.4.1.dist-info/WHEEL +4 -0
- ctxoptimizer-0.4.1.dist-info/entry_points.txt +3 -0
- ctxoptimizer-0.4.1.dist-info/licenses/LICENSE +21 -0
- ctxoptimizer-0.4.1.dist-info/licenses/NOTICE +13 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Duplicate tool-result back-references within a request (SPEC 12.6)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ctxoptimizer.analysis.tokens import estimate_tokens
|
|
10
|
+
from ctxoptimizer.config import Config
|
|
11
|
+
from ctxoptimizer.models import RequestContext, TransformReport
|
|
12
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
13
|
+
from ctxoptimizer.transforms.ansi import clean as ansi_clean
|
|
14
|
+
from ctxoptimizer.transforms.eligibility import is_never_touch
|
|
15
|
+
from ctxoptimizer.transforms.progress import clean as progress_clean
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DuplicateBackref:
|
|
19
|
+
id = "duplicate_backref"
|
|
20
|
+
|
|
21
|
+
def applies(
|
|
22
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
23
|
+
) -> bool:
|
|
24
|
+
return ctx.config.transforms.duplicate_backref.enabled
|
|
25
|
+
|
|
26
|
+
def apply(
|
|
27
|
+
self,
|
|
28
|
+
ctx: RequestContext,
|
|
29
|
+
payload: dict[str, Any],
|
|
30
|
+
adapter: ProviderAdapter,
|
|
31
|
+
config: Config,
|
|
32
|
+
) -> TransformReport:
|
|
33
|
+
started = time.perf_counter()
|
|
34
|
+
settings = config.transforms.duplicate_backref
|
|
35
|
+
seen: dict[bytes, str] = {}
|
|
36
|
+
items = 0
|
|
37
|
+
bytes_before = 0
|
|
38
|
+
bytes_after = 0
|
|
39
|
+
tokens_before = 0
|
|
40
|
+
tokens_after = 0
|
|
41
|
+
for ref in adapter.iter_tool_results(payload):
|
|
42
|
+
if is_never_touch(ref, config) and not settings.include_never_touch:
|
|
43
|
+
continue
|
|
44
|
+
text = ref.get_text()
|
|
45
|
+
if text is None or len(text.encode("utf-8")) < settings.min_bytes:
|
|
46
|
+
continue
|
|
47
|
+
sigma = progress_clean(ansi_clean(text))
|
|
48
|
+
digest = sha256(sigma.encode("utf-8")).digest()
|
|
49
|
+
if digest not in seen:
|
|
50
|
+
seen[digest] = ref.tool_use_id or "earlier"
|
|
51
|
+
continue
|
|
52
|
+
stub = f"[ctxoptimizer: output identical to earlier tool result {seen[digest]}]"
|
|
53
|
+
if len(stub.encode("utf-8")) >= len(text.encode("utf-8")):
|
|
54
|
+
continue
|
|
55
|
+
items += 1
|
|
56
|
+
bytes_before += len(text.encode("utf-8"))
|
|
57
|
+
bytes_after += len(stub.encode("utf-8"))
|
|
58
|
+
tokens_before += estimate_tokens(text)
|
|
59
|
+
tokens_after += estimate_tokens(stub)
|
|
60
|
+
ref.set_text(stub)
|
|
61
|
+
return TransformReport(
|
|
62
|
+
transform_id=self.id,
|
|
63
|
+
ran=True,
|
|
64
|
+
changed=items > 0,
|
|
65
|
+
items_touched=items,
|
|
66
|
+
bytes_before=bytes_before,
|
|
67
|
+
bytes_after=bytes_after,
|
|
68
|
+
est_tokens_before=tokens_before,
|
|
69
|
+
est_tokens_after=tokens_after,
|
|
70
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = ["DuplicateBackref"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Transform protocol and the pure-text transform driver (SPEC section 12.0.1)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any, Protocol
|
|
8
|
+
|
|
9
|
+
from ctxoptimizer.analysis.tokens import estimate_tokens
|
|
10
|
+
from ctxoptimizer.config import Config
|
|
11
|
+
from ctxoptimizer.models import RequestContext, ToolResultRef, TransformReport
|
|
12
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Transform(Protocol):
|
|
16
|
+
id: str
|
|
17
|
+
|
|
18
|
+
def applies(
|
|
19
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
20
|
+
) -> bool: ...
|
|
21
|
+
|
|
22
|
+
def apply(
|
|
23
|
+
self,
|
|
24
|
+
ctx: RequestContext,
|
|
25
|
+
payload: dict[str, Any],
|
|
26
|
+
adapter: ProviderAdapter,
|
|
27
|
+
config: Config,
|
|
28
|
+
) -> TransformReport: ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TextTransform:
|
|
32
|
+
"""Run a pure ``str -> str`` function over every eligible tool result."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
transform_id: str,
|
|
37
|
+
clean: Callable[[str], str],
|
|
38
|
+
*,
|
|
39
|
+
enabled: bool = True,
|
|
40
|
+
eligible: Callable[[ToolResultRef], bool] | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.id = transform_id
|
|
43
|
+
self._clean = clean
|
|
44
|
+
self._enabled = enabled
|
|
45
|
+
self._eligible = eligible or (lambda ref: True)
|
|
46
|
+
|
|
47
|
+
def applies(
|
|
48
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
49
|
+
) -> bool:
|
|
50
|
+
return self._enabled
|
|
51
|
+
|
|
52
|
+
def apply(
|
|
53
|
+
self,
|
|
54
|
+
ctx: RequestContext,
|
|
55
|
+
payload: dict[str, Any],
|
|
56
|
+
adapter: ProviderAdapter,
|
|
57
|
+
config: Config,
|
|
58
|
+
) -> TransformReport:
|
|
59
|
+
started = time.perf_counter()
|
|
60
|
+
items = 0
|
|
61
|
+
bytes_before = 0
|
|
62
|
+
bytes_after = 0
|
|
63
|
+
tokens_before = 0
|
|
64
|
+
tokens_after = 0
|
|
65
|
+
for ref in adapter.iter_tool_results(payload):
|
|
66
|
+
if not self._eligible(ref):
|
|
67
|
+
continue
|
|
68
|
+
text = ref.get_text()
|
|
69
|
+
if text is None:
|
|
70
|
+
continue
|
|
71
|
+
new_text = self._clean(text)
|
|
72
|
+
if new_text == text:
|
|
73
|
+
continue
|
|
74
|
+
items += 1
|
|
75
|
+
bytes_before += len(text.encode("utf-8"))
|
|
76
|
+
bytes_after += len(new_text.encode("utf-8"))
|
|
77
|
+
tokens_before += estimate_tokens(text)
|
|
78
|
+
tokens_after += estimate_tokens(new_text)
|
|
79
|
+
ref.set_text(new_text)
|
|
80
|
+
return TransformReport(
|
|
81
|
+
transform_id=self.id,
|
|
82
|
+
ran=True,
|
|
83
|
+
changed=items > 0,
|
|
84
|
+
items_touched=items,
|
|
85
|
+
bytes_before=bytes_before,
|
|
86
|
+
bytes_after=bytes_after,
|
|
87
|
+
est_tokens_before=tokens_before,
|
|
88
|
+
est_tokens_after=tokens_after,
|
|
89
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = ["TextTransform", "Transform"]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Byte-true cross-request substitution (SPEC section 12.12)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ctxoptimizer.analysis.tokens import estimate_tokens
|
|
12
|
+
from ctxoptimizer.config import Config
|
|
13
|
+
from ctxoptimizer.models import RequestContext, TransformReport
|
|
14
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
15
|
+
from ctxoptimizer.transforms.ansi import clean as ansi_clean
|
|
16
|
+
from ctxoptimizer.transforms.progress import clean as progress_clean
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class CacheEntry:
|
|
21
|
+
hash: bytes
|
|
22
|
+
size: int
|
|
23
|
+
first_turn: int
|
|
24
|
+
ref: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CacheServeState:
|
|
28
|
+
"""Bounded per-session content map (INV-17 says no silent loss)."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, max_sessions: int = 256, max_entries: int = 512) -> None:
|
|
31
|
+
self.max_sessions = max_sessions
|
|
32
|
+
self.max_entries = max_entries
|
|
33
|
+
self._sessions: OrderedDict[str, OrderedDict[bytes, CacheEntry]] = OrderedDict()
|
|
34
|
+
|
|
35
|
+
def session(self, session_id: str) -> OrderedDict[bytes, CacheEntry]:
|
|
36
|
+
entries = self._sessions.get(session_id)
|
|
37
|
+
if entries is None:
|
|
38
|
+
if len(self._sessions) >= self.max_sessions:
|
|
39
|
+
self._sessions.popitem(last=False)
|
|
40
|
+
entries = OrderedDict()
|
|
41
|
+
self._sessions[session_id] = entries
|
|
42
|
+
else:
|
|
43
|
+
self._sessions.move_to_end(session_id)
|
|
44
|
+
return entries
|
|
45
|
+
|
|
46
|
+
def put(self, entries: OrderedDict[bytes, CacheEntry], entry: CacheEntry) -> None:
|
|
47
|
+
entries[entry.hash] = entry
|
|
48
|
+
entries.move_to_end(entry.hash)
|
|
49
|
+
while len(entries) > self.max_entries:
|
|
50
|
+
entries.popitem(last=False)
|
|
51
|
+
|
|
52
|
+
def entries(self, session_id: str) -> dict[bytes, CacheEntry]:
|
|
53
|
+
return dict(self.session(session_id))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CacheServe:
|
|
57
|
+
id = "cache_serve"
|
|
58
|
+
|
|
59
|
+
def __init__(self, state: CacheServeState) -> None:
|
|
60
|
+
self.state = state
|
|
61
|
+
|
|
62
|
+
def applies(
|
|
63
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
64
|
+
) -> bool:
|
|
65
|
+
return (
|
|
66
|
+
ctx.config.transforms.cache_serve.enabled
|
|
67
|
+
and ctx.config.telemetry.capture_payloads != "off"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def apply(
|
|
71
|
+
self,
|
|
72
|
+
ctx: RequestContext,
|
|
73
|
+
payload: dict[str, Any],
|
|
74
|
+
adapter: ProviderAdapter,
|
|
75
|
+
config: Config,
|
|
76
|
+
) -> TransformReport:
|
|
77
|
+
started = time.perf_counter()
|
|
78
|
+
settings = config.transforms.cache_serve
|
|
79
|
+
turns = _assistant_turns(payload)
|
|
80
|
+
boundary = max(
|
|
81
|
+
0,
|
|
82
|
+
((turns - settings.keep_recent_turns) // settings.batch_turns) * settings.batch_turns,
|
|
83
|
+
)
|
|
84
|
+
entries = self.state.session(ctx.session_id)
|
|
85
|
+
seen: set[bytes] = set()
|
|
86
|
+
stubs = 0
|
|
87
|
+
tokens_before = 0
|
|
88
|
+
tokens_after = 0
|
|
89
|
+
for ref in adapter.iter_tool_results(payload):
|
|
90
|
+
text = ref.get_text()
|
|
91
|
+
if text is None or len(text.encode("utf-8")) < settings.min_bytes:
|
|
92
|
+
continue
|
|
93
|
+
sigma = progress_clean(ansi_clean(text))
|
|
94
|
+
digest = sha256(sigma.encode("utf-8")).digest()
|
|
95
|
+
if digest in seen:
|
|
96
|
+
continue
|
|
97
|
+
seen.add(digest)
|
|
98
|
+
entry = entries.get(digest)
|
|
99
|
+
if (
|
|
100
|
+
entry is not None
|
|
101
|
+
and entry.first_turn < boundary
|
|
102
|
+
and stubs < settings.max_stubs_per_request
|
|
103
|
+
):
|
|
104
|
+
stub = (
|
|
105
|
+
f"[ctxoptimizer: content identical to turn {entry.first_turn} "
|
|
106
|
+
f"({entry.size / 1024:.1f} KB, sha256 {entry.ref}...); unchanged. "
|
|
107
|
+
f"reread ref={entry.ref} | ctxoptimizer_expand({entry.ref}) retrieves it]"
|
|
108
|
+
)
|
|
109
|
+
if len(stub.encode("utf-8")) < len(text.encode("utf-8")):
|
|
110
|
+
tokens_before += estimate_tokens(text)
|
|
111
|
+
tokens_after += estimate_tokens(stub)
|
|
112
|
+
ref.set_text(stub)
|
|
113
|
+
stubs += 1
|
|
114
|
+
continue
|
|
115
|
+
if entry is None:
|
|
116
|
+
self.state.put(
|
|
117
|
+
entries,
|
|
118
|
+
CacheEntry(
|
|
119
|
+
hash=digest,
|
|
120
|
+
size=len(text.encode("utf-8")),
|
|
121
|
+
first_turn=turns,
|
|
122
|
+
ref=sha256(text.encode("utf-8")).hexdigest()[:8],
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
return TransformReport(
|
|
126
|
+
transform_id=self.id,
|
|
127
|
+
ran=True,
|
|
128
|
+
changed=stubs > 0,
|
|
129
|
+
items_touched=stubs,
|
|
130
|
+
est_tokens_before=tokens_before,
|
|
131
|
+
est_tokens_after=tokens_after,
|
|
132
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
133
|
+
notes={"boundary": boundary, "turns": turns, "stubs": stubs},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _assistant_turns(payload: dict[str, Any]) -> int:
|
|
138
|
+
items = payload.get("messages")
|
|
139
|
+
if not isinstance(items, list):
|
|
140
|
+
items = payload.get("input")
|
|
141
|
+
if not isinstance(items, list):
|
|
142
|
+
return 0
|
|
143
|
+
return sum(1 for item in items if isinstance(item, dict) and item.get("role") == "assistant")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
__all__ = ["CacheEntry", "CacheServe", "CacheServeState"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Exact and near-duplicate line collapsing (SPEC section 12.3)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
SIGNAL = re.compile(
|
|
8
|
+
r"error|fail(?:ed|ure)?|exception|fatal|warning|traceback|panic",
|
|
9
|
+
re.IGNORECASE,
|
|
10
|
+
)
|
|
11
|
+
_TS = re.compile(r"\b\d{4}-\d{2}-\d{2}T\S+|\b\d{2}:\d{2}:\d{2}\b")
|
|
12
|
+
_HEX = re.compile(r"\b[0-9a-f]{8,}\b", re.IGNORECASE)
|
|
13
|
+
_DIGITS = re.compile(r"\d+")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def clean(text: str, min_repeat: int = 3) -> str:
|
|
17
|
+
lines = text.split("\n")
|
|
18
|
+
exact: list[str] = []
|
|
19
|
+
index = 0
|
|
20
|
+
while index < len(lines):
|
|
21
|
+
end = index
|
|
22
|
+
while end + 1 < len(lines) and lines[end + 1].rstrip() == lines[index].rstrip():
|
|
23
|
+
end += 1
|
|
24
|
+
run = end - index + 1
|
|
25
|
+
if run >= min_repeat:
|
|
26
|
+
exact.append(lines[index])
|
|
27
|
+
exact.append(f"[ctxoptimizer: line repeated {run - 1} more times]")
|
|
28
|
+
else:
|
|
29
|
+
exact.extend(lines[index : end + 1])
|
|
30
|
+
index = end + 1
|
|
31
|
+
return "\n".join(_collapse_near(exact, min_repeat))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _collapse_near(lines: list[str], min_repeat: int) -> list[str]:
|
|
35
|
+
result: list[str] = []
|
|
36
|
+
index = 0
|
|
37
|
+
while index < len(lines):
|
|
38
|
+
line = lines[index]
|
|
39
|
+
if line.startswith("[ctxoptimizer:") or SIGNAL.search(line):
|
|
40
|
+
result.append(line)
|
|
41
|
+
index += 1
|
|
42
|
+
continue
|
|
43
|
+
key = _normalise(line)
|
|
44
|
+
end = index
|
|
45
|
+
while (
|
|
46
|
+
end + 1 < len(lines)
|
|
47
|
+
and not lines[end + 1].startswith("[ctxoptimizer:")
|
|
48
|
+
and not SIGNAL.search(lines[end + 1])
|
|
49
|
+
and _normalise(lines[end + 1]) == key
|
|
50
|
+
):
|
|
51
|
+
end += 1
|
|
52
|
+
run = end - index + 1
|
|
53
|
+
if run >= 2 * min_repeat:
|
|
54
|
+
result.append(lines[index])
|
|
55
|
+
result.append(f"[ctxoptimizer: {run - 2} similar lines omitted]")
|
|
56
|
+
result.append(lines[end])
|
|
57
|
+
else:
|
|
58
|
+
result.extend(lines[index : end + 1])
|
|
59
|
+
index = end + 1
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _normalise(line: str) -> str:
|
|
64
|
+
line = _TS.sub("<ts>", line)
|
|
65
|
+
line = _HEX.sub("<id>", line)
|
|
66
|
+
return _DIGITS.sub("#", line)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["clean"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Tool eligibility from the ``[tools]`` globs (SPEC section 12.0.4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fnmatch import fnmatchcase
|
|
6
|
+
|
|
7
|
+
from ctxoptimizer.config import Config
|
|
8
|
+
from ctxoptimizer.models import ToolResultRef
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def matches(name: str, patterns: list[str]) -> bool:
|
|
12
|
+
lowered = name.lower()
|
|
13
|
+
return any(fnmatchcase(lowered, pattern.lower()) for pattern in patterns)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_never_touch(ref: ToolResultRef, config: Config) -> bool:
|
|
17
|
+
return bool(ref.tool_name and matches(ref.tool_name, config.tools.never_touch))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_compactable(ref: ToolResultRef, config: Config) -> bool:
|
|
21
|
+
return bool(ref.tool_name and matches(ref.tool_name, config.tools.compactable))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["is_compactable", "is_never_touch", "matches"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""OpenAI prompt_cache_key hints (SPEC section 12.10)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
from ctxoptimizer.config import Config
|
|
11
|
+
from ctxoptimizer.models import Endpoint, Provider, RequestContext, TransformReport
|
|
12
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OpenAIHints:
|
|
16
|
+
id = "openai_hints"
|
|
17
|
+
|
|
18
|
+
def applies(
|
|
19
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
20
|
+
) -> bool:
|
|
21
|
+
settings = ctx.config.transforms.openai_hints
|
|
22
|
+
if not settings.enabled:
|
|
23
|
+
return False
|
|
24
|
+
if ctx.provider is not Provider.OPENAI:
|
|
25
|
+
return False
|
|
26
|
+
if ctx.endpoint not in {Endpoint.CHAT, Endpoint.RESPONSES}:
|
|
27
|
+
return False
|
|
28
|
+
entry = ctx.config.upstreams.get(ctx.upstream_name)
|
|
29
|
+
if entry is None:
|
|
30
|
+
return False
|
|
31
|
+
url = entry if isinstance(entry, str) else entry.url
|
|
32
|
+
if (urlsplit(url).hostname or "").lower() != "api.openai.com":
|
|
33
|
+
return False
|
|
34
|
+
if settings.send_prompt_cache_key and "prompt_cache_key" not in payload:
|
|
35
|
+
return True
|
|
36
|
+
if (
|
|
37
|
+
settings.inject_stream_usage
|
|
38
|
+
and ctx.endpoint is Endpoint.CHAT
|
|
39
|
+
and payload.get("stream") is True
|
|
40
|
+
):
|
|
41
|
+
options = payload.get("stream_options")
|
|
42
|
+
return not isinstance(options, dict) or options.get("include_usage") is not True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def apply(
|
|
46
|
+
self,
|
|
47
|
+
ctx: RequestContext,
|
|
48
|
+
payload: dict[str, Any],
|
|
49
|
+
adapter: ProviderAdapter,
|
|
50
|
+
config: Config,
|
|
51
|
+
) -> TransformReport:
|
|
52
|
+
started = time.perf_counter()
|
|
53
|
+
settings = config.transforms.openai_hints
|
|
54
|
+
changed = False
|
|
55
|
+
notes: dict[str, object] = {}
|
|
56
|
+
if settings.send_prompt_cache_key and "prompt_cache_key" not in payload:
|
|
57
|
+
key = "ctxp-" + sha256(ctx.session_id.encode("utf-8")).hexdigest()[:24]
|
|
58
|
+
payload["prompt_cache_key"] = key
|
|
59
|
+
notes["key"] = key
|
|
60
|
+
changed = True
|
|
61
|
+
if (
|
|
62
|
+
settings.inject_stream_usage
|
|
63
|
+
and ctx.endpoint is Endpoint.CHAT
|
|
64
|
+
and payload.get("stream") is True
|
|
65
|
+
):
|
|
66
|
+
options = payload.get("stream_options")
|
|
67
|
+
if not isinstance(options, dict):
|
|
68
|
+
options = {}
|
|
69
|
+
payload["stream_options"] = options
|
|
70
|
+
if options.get("include_usage") is not True:
|
|
71
|
+
options["include_usage"] = True
|
|
72
|
+
notes["inject_stream_usage"] = True
|
|
73
|
+
changed = True
|
|
74
|
+
return TransformReport(
|
|
75
|
+
transform_id=self.id,
|
|
76
|
+
ran=True,
|
|
77
|
+
changed=changed,
|
|
78
|
+
items_touched=len(notes),
|
|
79
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
80
|
+
notes=notes,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ["OpenAIHints"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Progress-bar and carriage-return collapsing (SPEC section 12.2)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
PROGRESS = re.compile(
|
|
8
|
+
r"(?:[#=>█▉▊▋▌▍▎▏.\- ]{8,}\s*\d{1,3}%"
|
|
9
|
+
r"|\b\d+(?:\.\d+)?\s?(?:kB|MB|GB|KiB|MiB)/s\b"
|
|
10
|
+
r"|^(?:Downloading|Fetching|Receiving objects|Resolving deltas|"
|
|
11
|
+
r"Compressing objects|Unpacking|Counting objects|Collecting|Installing)\b"
|
|
12
|
+
r".*?(?:\d{1,3}%|\d+\s?(?:kB|MB|GB|KiB|MiB)))",
|
|
13
|
+
re.IGNORECASE,
|
|
14
|
+
)
|
|
15
|
+
SIGNAL = re.compile(r"error|fail|fatal|warning", re.IGNORECASE)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def clean(text: str) -> str:
|
|
19
|
+
lines = [_last_carriage_segment(line) for line in text.split("\n")]
|
|
20
|
+
result: list[str] = []
|
|
21
|
+
index = 0
|
|
22
|
+
while index < len(lines):
|
|
23
|
+
line = lines[index]
|
|
24
|
+
if not _is_progress(line):
|
|
25
|
+
result.append(line)
|
|
26
|
+
index += 1
|
|
27
|
+
continue
|
|
28
|
+
end = index
|
|
29
|
+
while end + 1 < len(lines) and _is_progress(lines[end + 1]):
|
|
30
|
+
end += 1
|
|
31
|
+
run = end - index + 1
|
|
32
|
+
if run >= 3:
|
|
33
|
+
result.append(f"[ctxoptimizer: {run} progress updates collapsed]")
|
|
34
|
+
result.append(lines[end])
|
|
35
|
+
else:
|
|
36
|
+
result.extend(lines[index : end + 1])
|
|
37
|
+
index = end + 1
|
|
38
|
+
return "\n".join(result)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _last_carriage_segment(line: str) -> str:
|
|
42
|
+
if "\r" not in line:
|
|
43
|
+
return line
|
|
44
|
+
segments = [part for part in line.split("\r") if part]
|
|
45
|
+
return segments[-1] if segments else ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_progress(line: str) -> bool:
|
|
49
|
+
return bool(PROGRESS.search(line)) and not SIGNAL.search(line)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ["clean"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Opt-in stale tool-result elision (SPEC section 12.8, P6-4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ctxoptimizer.config import Config
|
|
9
|
+
from ctxoptimizer.models import RequestContext, TransformReport
|
|
10
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StaleResultElision:
|
|
14
|
+
id = "stale_result_elision"
|
|
15
|
+
|
|
16
|
+
def applies(
|
|
17
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
18
|
+
) -> bool:
|
|
19
|
+
return ctx.config.transforms.stale_result_elision.enabled
|
|
20
|
+
|
|
21
|
+
def apply(
|
|
22
|
+
self,
|
|
23
|
+
ctx: RequestContext,
|
|
24
|
+
payload: dict[str, Any],
|
|
25
|
+
adapter: ProviderAdapter,
|
|
26
|
+
config: Config,
|
|
27
|
+
) -> TransformReport:
|
|
28
|
+
started = time.perf_counter()
|
|
29
|
+
settings = config.transforms.stale_result_elision
|
|
30
|
+
turns = assistant_turns(payload)
|
|
31
|
+
boundary = max(
|
|
32
|
+
0,
|
|
33
|
+
((turns - settings.keep_recent_turns) // settings.batch_turns) * settings.batch_turns,
|
|
34
|
+
)
|
|
35
|
+
turn_by_index = _turn_by_index(payload)
|
|
36
|
+
touched = 0
|
|
37
|
+
before = 0
|
|
38
|
+
after = 0
|
|
39
|
+
for ref in adapter.iter_tool_results(payload):
|
|
40
|
+
text = ref.get_text()
|
|
41
|
+
if text is None or text.startswith("[ctxoptimizer:"):
|
|
42
|
+
continue
|
|
43
|
+
size = len(text.encode("utf-8"))
|
|
44
|
+
if size < settings.min_bytes:
|
|
45
|
+
continue
|
|
46
|
+
turn = turn_by_index.get(ref.message_index, turns)
|
|
47
|
+
if turn >= boundary:
|
|
48
|
+
continue
|
|
49
|
+
stub = (
|
|
50
|
+
f"[ctxoptimizer: tool result elided ({size / 1024:.1f} KB); "
|
|
51
|
+
"re-run the tool if needed]"
|
|
52
|
+
)
|
|
53
|
+
if len(stub.encode("utf-8")) >= size:
|
|
54
|
+
continue
|
|
55
|
+
before += size
|
|
56
|
+
after += len(stub.encode("utf-8"))
|
|
57
|
+
ref.set_text(stub)
|
|
58
|
+
touched += 1
|
|
59
|
+
return TransformReport(
|
|
60
|
+
transform_id=self.id,
|
|
61
|
+
ran=True,
|
|
62
|
+
changed=touched > 0,
|
|
63
|
+
items_touched=touched,
|
|
64
|
+
bytes_before=before,
|
|
65
|
+
bytes_after=after,
|
|
66
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
67
|
+
notes={"boundary": boundary, "turns": turns, "elided": touched},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def assistant_turns(payload: dict[str, Any]) -> int:
|
|
72
|
+
return sum(1 for item in _items(payload) if _is_assistant(item))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _turn_by_index(payload: dict[str, Any]) -> dict[int, int]:
|
|
76
|
+
turns = 0
|
|
77
|
+
result: dict[int, int] = {}
|
|
78
|
+
for index, item in enumerate(_items(payload)):
|
|
79
|
+
if _is_assistant(item):
|
|
80
|
+
turns += 1
|
|
81
|
+
result[index] = turns
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _items(payload: dict[str, Any]) -> list[Any]:
|
|
86
|
+
value = payload.get("messages")
|
|
87
|
+
if isinstance(value, list):
|
|
88
|
+
return value
|
|
89
|
+
value = payload.get("contents")
|
|
90
|
+
if isinstance(value, list):
|
|
91
|
+
return value
|
|
92
|
+
value = payload.get("input")
|
|
93
|
+
if isinstance(value, str):
|
|
94
|
+
return [{"type": "message", "role": "user", "content": value}]
|
|
95
|
+
return value if isinstance(value, list) else []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _is_assistant(item: Any) -> bool:
|
|
99
|
+
if not isinstance(item, dict):
|
|
100
|
+
return False
|
|
101
|
+
if item.get("type") == "function_call":
|
|
102
|
+
return True
|
|
103
|
+
role = item.get("role")
|
|
104
|
+
if role is None and item.get("type") == "message":
|
|
105
|
+
role = item.get("role")
|
|
106
|
+
return role in {"assistant", "model"}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
__all__ = ["StaleResultElision", "assistant_turns"]
|