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,100 @@
|
|
|
1
|
+
"""Error-aware truncation with signal windows (SPEC section 12.5)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
|
|
8
|
+
from ctxoptimizer.config import SmartTruncateConfig
|
|
9
|
+
|
|
10
|
+
SIGNAL = re.compile(
|
|
11
|
+
r"error|fail(?:ed|ure)?|exception|traceback|panic|assert(?:ion)?|fatal|"
|
|
12
|
+
r"segfault|cannot|can't|undefined|not found|denied|timeout|"
|
|
13
|
+
r"exit (?:code|status)|npm ERR!|^\s*E\s{3}\S|^FAILED|^ERROR|"
|
|
14
|
+
r"error\[E\d+\]|^\s*File \".*\", line \d+|\S+\.\w+:\d+(?::\d+)?:",
|
|
15
|
+
re.IGNORECASE | re.MULTILINE,
|
|
16
|
+
)
|
|
17
|
+
SUMMARY = re.compile(
|
|
18
|
+
r"\d+ (?:passed|failed|errors?|warnings?)|Tests?:|Test Suites:|"
|
|
19
|
+
r"Ran \d+ tests|BUILD (?:SUCCESS|FAILURE)|Finished|Compiling .* in",
|
|
20
|
+
re.IGNORECASE,
|
|
21
|
+
)
|
|
22
|
+
BASE64 = re.compile(r"[A-Za-z0-9+/=]{1000,}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def clean(
|
|
26
|
+
text: str,
|
|
27
|
+
cfg: SmartTruncateConfig,
|
|
28
|
+
*,
|
|
29
|
+
is_error: bool = False,
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Keep head, tail, signal windows and summaries; marker every gap."""
|
|
32
|
+
if "\x00" in text:
|
|
33
|
+
return text
|
|
34
|
+
if len(text.encode("utf-8")) <= cfg.trigger_bytes:
|
|
35
|
+
return text
|
|
36
|
+
ref = sha256(text.encode("utf-8")).hexdigest()[:8]
|
|
37
|
+
lines = [_prepare_line(line, cfg) for line in text.split("\n")]
|
|
38
|
+
head = min(cfg.head_lines, len(lines))
|
|
39
|
+
tail_start = max(0, len(lines) - cfg.tail_lines)
|
|
40
|
+
keep: set[int] = set(range(head)) | set(range(tail_start, len(lines)))
|
|
41
|
+
|
|
42
|
+
signal_limit = cfg.max_signal_lines * (2 if is_error else 1)
|
|
43
|
+
signal_seen = 0
|
|
44
|
+
for index, line in enumerate(lines):
|
|
45
|
+
if not (SIGNAL.search(line) or SUMMARY.search(line)):
|
|
46
|
+
continue
|
|
47
|
+
if signal_seen >= signal_limit:
|
|
48
|
+
break
|
|
49
|
+
signal_seen += 1
|
|
50
|
+
for neighbour in range(
|
|
51
|
+
max(0, index - cfg.context_lines),
|
|
52
|
+
min(len(lines), index + cfg.context_lines + 1),
|
|
53
|
+
):
|
|
54
|
+
keep.add(neighbour)
|
|
55
|
+
|
|
56
|
+
output = _render(lines, keep, ref)
|
|
57
|
+
if len(output.encode("utf-8")) > cfg.target_bytes * 1.5:
|
|
58
|
+
keep = {index for index in keep if index < head or index >= tail_start}
|
|
59
|
+
output = _render(lines, keep, ref)
|
|
60
|
+
if len(output.encode("utf-8")) > cfg.target_bytes * 1.5:
|
|
61
|
+
keep = {
|
|
62
|
+
index
|
|
63
|
+
for index in keep
|
|
64
|
+
if index < max(1, head // 2)
|
|
65
|
+
or index >= min(len(lines), len(lines) - max(1, cfg.tail_lines // 2))
|
|
66
|
+
}
|
|
67
|
+
output = _render(lines, keep, ref)
|
|
68
|
+
if len(output.encode("utf-8")) > cfg.target_bytes * 1.5:
|
|
69
|
+
output = output.encode("utf-8")[: int(cfg.target_bytes * 1.5)].decode("utf-8", "ignore")
|
|
70
|
+
return output
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _prepare_line(line: str, cfg: SmartTruncateConfig) -> str:
|
|
74
|
+
if len(line) > cfg.max_line_chars:
|
|
75
|
+
line = line[: cfg.max_line_chars] + f" [ctxoptimizer: line truncated, {len(line)} chars]"
|
|
76
|
+
return BASE64.sub(
|
|
77
|
+
lambda match: f"[ctxoptimizer: base64 blob, {len(match.group(0))} chars]", line
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _render(lines: list[str], keep: set[int], ref: str) -> str:
|
|
82
|
+
result: list[str] = []
|
|
83
|
+
index = 0
|
|
84
|
+
total = len(lines)
|
|
85
|
+
while index < total:
|
|
86
|
+
if index in keep:
|
|
87
|
+
result.append(lines[index])
|
|
88
|
+
index += 1
|
|
89
|
+
continue
|
|
90
|
+
end = index
|
|
91
|
+
while end < total and end not in keep:
|
|
92
|
+
end += 1
|
|
93
|
+
omitted = end - index
|
|
94
|
+
size_kb = sum(len(lines[i].encode("utf-8")) for i in range(index, end)) / 1024
|
|
95
|
+
result.append(f"[ctxoptimizer: {omitted} lines ({size_kb:.1f} KB) omitted; ref={ref}]")
|
|
96
|
+
index = end
|
|
97
|
+
return "\n".join(result)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
__all__ = ["clean"]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Experimental volatile-token normalization (SPEC section 12.11).
|
|
2
|
+
|
|
3
|
+
Opt-in only; it deliberately mutates system text (INV-9). Time values are
|
|
4
|
+
reduced to day granularity, UUIDs are replaced, and epoch seconds are removed
|
|
5
|
+
only inside ``<env>`` blocks. The function is deterministic and idempotent.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ctxoptimizer.analysis.tokens import estimate_tokens
|
|
15
|
+
from ctxoptimizer.config import Config
|
|
16
|
+
from ctxoptimizer.models import Provider, RequestContext, TransformReport
|
|
17
|
+
from ctxoptimizer.providers.base import ProviderAdapter
|
|
18
|
+
|
|
19
|
+
_ISO_DAY = re.compile(
|
|
20
|
+
r"\b(\d{4}-\d{2}-\d{2})[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?"
|
|
21
|
+
r"(?:Z|[+-]\d{2}:?\d{2})?\b"
|
|
22
|
+
)
|
|
23
|
+
_UUID4 = re.compile(
|
|
24
|
+
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-"
|
|
25
|
+
r"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b"
|
|
26
|
+
)
|
|
27
|
+
_TIME_ONLY = re.compile(r"(?i)\b(Current time|Time)\s*:\s*\d{1,2}:\d{2}(?::\d{2})?")
|
|
28
|
+
_ENV_EPOCH = re.compile(r"(?<!\d)\d{10}(?!\d)")
|
|
29
|
+
_ENV_BLOCK = re.compile(r"(?is)<env>.*?</env>")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def normalize_system(text: str) -> str:
|
|
33
|
+
text = _ISO_DAY.sub(r"\1", text)
|
|
34
|
+
text = _UUID4.sub("[uuid]", text)
|
|
35
|
+
text = _TIME_ONLY.sub(r"\1: [time omitted]", text)
|
|
36
|
+
return text
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def normalize_user_env(text: str) -> str:
|
|
40
|
+
def repl(match: re.Match[str]) -> str:
|
|
41
|
+
block = normalize_system(match.group(0))
|
|
42
|
+
return _ENV_EPOCH.sub("[epoch]", block)
|
|
43
|
+
|
|
44
|
+
return _ENV_BLOCK.sub(repl, text)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class VolatileNormalizer:
|
|
48
|
+
id = "volatile_normalizer"
|
|
49
|
+
|
|
50
|
+
def applies(
|
|
51
|
+
self, ctx: RequestContext, payload: dict[str, Any], adapter: ProviderAdapter
|
|
52
|
+
) -> bool:
|
|
53
|
+
return ctx.config.transforms.volatile_normalizer.enabled
|
|
54
|
+
|
|
55
|
+
def apply(
|
|
56
|
+
self,
|
|
57
|
+
ctx: RequestContext,
|
|
58
|
+
payload: dict[str, Any],
|
|
59
|
+
adapter: ProviderAdapter,
|
|
60
|
+
config: Config,
|
|
61
|
+
) -> TransformReport:
|
|
62
|
+
started = time.perf_counter()
|
|
63
|
+
items = 0
|
|
64
|
+
bytes_before = 0
|
|
65
|
+
bytes_after = 0
|
|
66
|
+
tokens_before = 0
|
|
67
|
+
tokens_after = 0
|
|
68
|
+
for target, key, user_text in _text_targets(payload, ctx.provider):
|
|
69
|
+
original = target.get(key)
|
|
70
|
+
if not isinstance(original, str):
|
|
71
|
+
continue
|
|
72
|
+
updated = normalize_user_env(original) if user_text else normalize_system(original)
|
|
73
|
+
if updated == original:
|
|
74
|
+
continue
|
|
75
|
+
target[key] = updated
|
|
76
|
+
items += 1
|
|
77
|
+
bytes_before += len(original.encode("utf-8"))
|
|
78
|
+
bytes_after += len(updated.encode("utf-8"))
|
|
79
|
+
tokens_before += estimate_tokens(original)
|
|
80
|
+
tokens_after += estimate_tokens(updated)
|
|
81
|
+
return TransformReport(
|
|
82
|
+
transform_id=self.id,
|
|
83
|
+
ran=True,
|
|
84
|
+
changed=items > 0,
|
|
85
|
+
items_touched=items,
|
|
86
|
+
bytes_before=bytes_before,
|
|
87
|
+
bytes_after=bytes_after,
|
|
88
|
+
est_tokens_before=tokens_before,
|
|
89
|
+
est_tokens_after=tokens_after,
|
|
90
|
+
duration_ms=(time.perf_counter() - started) * 1000,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _text_targets(
|
|
95
|
+
payload: dict[str, Any], provider: Provider
|
|
96
|
+
) -> list[tuple[dict[str, Any], str, bool]]:
|
|
97
|
+
targets: list[tuple[dict[str, Any], str, bool]] = []
|
|
98
|
+
if provider is Provider.ANTHROPIC:
|
|
99
|
+
system = payload.get("system")
|
|
100
|
+
if isinstance(system, str):
|
|
101
|
+
targets.append((payload, "system", False))
|
|
102
|
+
elif isinstance(system, list):
|
|
103
|
+
targets.extend(
|
|
104
|
+
(block, "text", False)
|
|
105
|
+
for block in system
|
|
106
|
+
if isinstance(block, dict) and isinstance(block.get("text"), str)
|
|
107
|
+
)
|
|
108
|
+
elif provider is Provider.GEMINI:
|
|
109
|
+
system = payload.get("systemInstruction")
|
|
110
|
+
if isinstance(system, dict):
|
|
111
|
+
targets.extend(
|
|
112
|
+
(part, "text", False)
|
|
113
|
+
for part in system.get("parts") or []
|
|
114
|
+
if isinstance(part, dict) and isinstance(part.get("text"), str)
|
|
115
|
+
)
|
|
116
|
+
elif provider is Provider.OLLAMA:
|
|
117
|
+
if isinstance(payload.get("system"), str):
|
|
118
|
+
targets.append((payload, "system", False))
|
|
119
|
+
|
|
120
|
+
if provider is Provider.OPENAI and isinstance(payload.get("instructions"), str):
|
|
121
|
+
targets.append((payload, "instructions", False))
|
|
122
|
+
|
|
123
|
+
messages = payload.get("messages")
|
|
124
|
+
if isinstance(messages, list):
|
|
125
|
+
for message in messages:
|
|
126
|
+
if not isinstance(message, dict):
|
|
127
|
+
continue
|
|
128
|
+
role = message.get("role")
|
|
129
|
+
content = message.get("content")
|
|
130
|
+
if role in {"system", "developer"}:
|
|
131
|
+
_add_content_targets(targets, message, "content", user_text=False)
|
|
132
|
+
elif role == "user":
|
|
133
|
+
_add_content_targets(targets, message, "content", user_text=True)
|
|
134
|
+
|
|
135
|
+
input_items = payload.get("input")
|
|
136
|
+
if isinstance(input_items, list):
|
|
137
|
+
for item in input_items:
|
|
138
|
+
if not isinstance(item, dict):
|
|
139
|
+
continue
|
|
140
|
+
if item.get("role") in {"system", "developer"}:
|
|
141
|
+
_add_content_targets(targets, item, "content", user_text=False)
|
|
142
|
+
elif item.get("role") == "user":
|
|
143
|
+
_add_content_targets(targets, item, "content", user_text=True)
|
|
144
|
+
|
|
145
|
+
contents = payload.get("contents")
|
|
146
|
+
if isinstance(contents, list):
|
|
147
|
+
for content in contents:
|
|
148
|
+
if not isinstance(content, dict):
|
|
149
|
+
continue
|
|
150
|
+
if content.get("role") == "user":
|
|
151
|
+
targets.extend(
|
|
152
|
+
(part, "text", True)
|
|
153
|
+
for part in content.get("parts") or []
|
|
154
|
+
if isinstance(part, dict) and isinstance(part.get("text"), str)
|
|
155
|
+
)
|
|
156
|
+
return targets
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _add_content_targets(
|
|
160
|
+
targets: list[tuple[dict[str, Any], str, bool]],
|
|
161
|
+
message: dict[str, Any],
|
|
162
|
+
key: str,
|
|
163
|
+
*,
|
|
164
|
+
user_text: bool,
|
|
165
|
+
) -> None:
|
|
166
|
+
content = message.get(key)
|
|
167
|
+
if isinstance(content, str):
|
|
168
|
+
targets.append((message, key, user_text))
|
|
169
|
+
elif isinstance(content, list):
|
|
170
|
+
targets.extend(
|
|
171
|
+
(block, "text", user_text)
|
|
172
|
+
for block in content
|
|
173
|
+
if isinstance(block, dict) and isinstance(block.get("text"), str)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
__all__ = ["VolatileNormalizer", "normalize_system", "normalize_user_env"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Trailing whitespace and blank-run trimming (SPEC section 12.4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def clean(text: str, max_blank_run: int = 2) -> str:
|
|
7
|
+
lines = [line.rstrip() for line in text.split("\n")]
|
|
8
|
+
result: list[str] = []
|
|
9
|
+
blanks = 0
|
|
10
|
+
for line in lines:
|
|
11
|
+
if line == "":
|
|
12
|
+
blanks += 1
|
|
13
|
+
if blanks <= max_blank_run:
|
|
14
|
+
result.append(line)
|
|
15
|
+
else:
|
|
16
|
+
blanks = 0
|
|
17
|
+
result.append(line)
|
|
18
|
+
while result and result[-1] == "":
|
|
19
|
+
result.pop()
|
|
20
|
+
if not result:
|
|
21
|
+
return ""
|
|
22
|
+
return "\n".join(result) + "\n"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["clean"]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctxoptimizer
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Local, deterministic cost optimizer for AI coding agents
|
|
5
|
+
Author: ahmed
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
License-File: NOTICE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Dist: httpx[http2]>=0.27
|
|
11
|
+
Requires-Dist: jinja2>=3.1
|
|
12
|
+
Requires-Dist: orjson>=3.10
|
|
13
|
+
Requires-Dist: platformdirs>=4.2
|
|
14
|
+
Requires-Dist: pydantic>=2.7
|
|
15
|
+
Requires-Dist: starlette>=0.37
|
|
16
|
+
Requires-Dist: typer>=0.12
|
|
17
|
+
Requires-Dist: uvicorn>=0.30
|
|
18
|
+
Requires-Dist: coverage>=7 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: hypothesis>=6.100 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: mypy>=1.10 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pip-audit ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-benchmark>=4 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov>=5 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: respx>=0.21 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.5 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: google-re2>=1.1 ; extra == 're2'
|
|
29
|
+
Requires-Dist: tiktoken>=0.7 ; extra == 'tokens'
|
|
30
|
+
Requires-Python: >=3.11
|
|
31
|
+
Project-URL: Homepage, https://github.com/ahmed-dev25/ctxoptimizer
|
|
32
|
+
Project-URL: Repository, https://github.com/ahmed-dev25/ctxoptimizer
|
|
33
|
+
Project-URL: Issues, https://github.com/ahmed-dev25/ctxoptimizer/issues
|
|
34
|
+
Project-URL: Changelog, https://github.com/ahmed-dev25/ctxoptimizer/blob/main/CHANGELOG.md
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Provides-Extra: re2
|
|
37
|
+
Provides-Extra: tokens
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# ctxoptimizer
|
|
41
|
+
|
|
42
|
+
A local, deterministic cost optimizer for AI coding agents. It sits between your
|
|
43
|
+
agent and the LLM provider, measures real token usage and spend, explains why
|
|
44
|
+
prompt caching missed, and applies cache-safe payload reductions.
|
|
45
|
+
|
|
46
|
+
- **Local only** — binds to loopback; no telemetry leaves the machine.
|
|
47
|
+
- **Deterministic** — no LLM calls inside the proxy; every decision is a hash,
|
|
48
|
+
regex, counter or threshold.
|
|
49
|
+
- **Honest numbers** — provider-reported usage is ground truth; everything else
|
|
50
|
+
is labelled `est.` or `sim.`.
|
|
51
|
+
- **Fail-open** — invalid transforms retry with the original bytes; the proxy
|
|
52
|
+
never blocks traffic.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
uv tool install ctxoptimizer
|
|
58
|
+
# or: pipx install ctxoptimizer
|
|
59
|
+
ctxoptimizer run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If PyPI does not have the release yet, install from GitHub:
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
uv tool install "git+https://github.com/ahmed-dev25/ctxoptimizer.git"
|
|
66
|
+
# or: pipx install "git+https://github.com/ahmed-dev25/ctxoptimizer.git"
|
|
67
|
+
ctxoptimizer run
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
From a checkout:
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
uv sync --extra dev
|
|
74
|
+
uv run ctxoptimizer run
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The startup banner prints the dashboard URL with a per-run admin token:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
dashboard: http://127.0.0.1:8787/_ctxoptimizer/?token=...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Open that URL once; the token is stored as an `HttpOnly; SameSite=Strict`
|
|
84
|
+
cookie. The dashboard is also protected by a Host allowlist and CSRF checks.
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
```powershell
|
|
89
|
+
ctxoptimizer env --shell powershell # bash | zsh | fish | powershell
|
|
90
|
+
ctxoptimizer status # health + counters
|
|
91
|
+
ctxoptimizer stats # recorded usage and cost
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Point a client at the printed base URL. See
|
|
95
|
+
[`docs/client-setup.md`](docs/client-setup.md) for Claude Code, OpenCode,
|
|
96
|
+
DeepSeek, OpenRouter, Gemini, Ollama, Codex and the other supported clients.
|
|
97
|
+
|
|
98
|
+
Default mode is `shadow`: everything is measured and simulated, while the
|
|
99
|
+
client's bytes are forwarded unchanged. Switch to `active` from the dashboard
|
|
100
|
+
(Settings), with `ctxoptimizer run --mode active`, or with
|
|
101
|
+
`x-ctxoptimizer-mode: active` per request.
|
|
102
|
+
|
|
103
|
+
## What it does
|
|
104
|
+
|
|
105
|
+
- Measures provider usage and cost for Anthropic, OpenAI Chat/Responses,
|
|
106
|
+
native Gemini, native Ollama and named OpenAI-compatible upstreams
|
|
107
|
+
(DeepSeek, OpenRouter, Gemini/Ollama compatibility, and custom gateways).
|
|
108
|
+
- Detects drift (tools/system/history/TTL/config/model) and waste W1–W8:
|
|
109
|
+
redundant re-reads, identical outputs, unbatched fan-out, runaway sessions,
|
|
110
|
+
recurring/param drift, edit churn and unused tool schemas.
|
|
111
|
+
- Applies deterministic transforms: ANSI/progress cleanup, line dedupe,
|
|
112
|
+
whitespace trim, signal-aware truncation, duplicate back-references,
|
|
113
|
+
byte-true `cache_serve`, opt-in stale-result elision, Anthropic
|
|
114
|
+
`cache_control` hints and OpenAI `prompt_cache_key`.
|
|
115
|
+
- Auto-quarantines a transform per session when fallback/drift guard counters
|
|
116
|
+
trip; the dashboard shows the history and lets you re-enable it.
|
|
117
|
+
- Provides `ctxoptimizer_expand` over MCP so an agent can retrieve any omitted
|
|
118
|
+
original from the local blob store.
|
|
119
|
+
- Serves a local dashboard: Overview, Live (SSE), Sessions, Requests + diff
|
|
120
|
+
viewer, Waste Opportunities with status controls, Transforms with hot
|
|
121
|
+
toggles/stats, Pricing editor, Settings and Doctor.
|
|
122
|
+
|
|
123
|
+
## Privacy
|
|
124
|
+
|
|
125
|
+
Payload capture defaults to `changed_blocks`; set it to `off` to store nothing.
|
|
126
|
+
Captured text stays in the per-user data directory, is redacted, and is bounded
|
|
127
|
+
by retention. `ctxoptimizer purge --all` wipes telemetry, blobs and findings. See
|
|
128
|
+
[`docs/privacy.md`](docs/privacy.md).
|
|
129
|
+
|
|
130
|
+
## Service
|
|
131
|
+
|
|
132
|
+
```powershell
|
|
133
|
+
ctxoptimizer service install # launchd / systemd --user / Windows scheduled task
|
|
134
|
+
ctxoptimizer service install --dry-run
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Contributing
|
|
138
|
+
|
|
139
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup and gates. Report
|
|
140
|
+
vulnerabilities privately via [`SECURITY.md`](SECURITY.md); do not open a public
|
|
141
|
+
issue for a security problem.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
ctxoptimizer is free and open-source software licensed under the **MIT License**.
|
|
146
|
+
See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).
|
|
147
|
+
|
|
148
|
+
## Updating
|
|
149
|
+
|
|
150
|
+
Use the package manager that installed ctxoptimizer:
|
|
151
|
+
|
|
152
|
+
```powershell
|
|
153
|
+
uv tool upgrade ctxoptimizer
|
|
154
|
+
# or
|
|
155
|
+
pipx upgrade ctxoptimizer
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
There is no built-in self-update command. PyPI releases are published with
|
|
159
|
+
Trusted Publishing and PEP 740 attestations. Source installs follow the GitHub
|
|
160
|
+
repository; downloaded wheels must be replaced manually. See
|
|
161
|
+
[`docs/updating.md`](docs/updating.md).
|
|
162
|
+
|
|
163
|
+
## Documentation
|
|
164
|
+
|
|
165
|
+
- [`SPEC.md`](SPEC.md) — authoritative specification
|
|
166
|
+
- [`docs/client-setup.md`](docs/client-setup.md) — per-client recipes
|
|
167
|
+
- [`docs/how-to-use.md`](docs/how-to-use.md) — complete install/run/update guide
|
|
168
|
+
- [`docs/provider-notes.md`](docs/provider-notes.md) — dated provider behavior
|
|
169
|
+
- [`docs/architecture.md`](docs/architecture.md) — component overview
|
|
170
|
+
- [`docs/privacy.md`](docs/privacy.md) — data handling and purge
|
|
171
|
+
- [`docs/faq.md`](docs/faq.md) — cache, savings, troubleshooting
|
|
172
|
+
- [`docs/test-report.md`](docs/test-report.md) — gates and test coverage summary
|
|
173
|
+
- [`docs/updating.md`](docs/updating.md) — package-manager upgrades and release artifacts
|
|
174
|
+
- [`docs/pypi-release.md`](docs/pypi-release.md) — PyPI Trusted Publishing setup
|
|
175
|
+
- [`docs/release-checklist.md`](docs/release-checklist.md) — release checklist
|
|
176
|
+
- [`docs/progress.md`](docs/progress.md) — build status and handoff notes
|
|
177
|
+
- [`CHANGELOG.md`](CHANGELOG.md)
|
|
178
|
+
- [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
179
|
+
- [`SECURITY.md`](SECURITY.md)
|
|
180
|
+
- [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md)
|
|
181
|
+
- [`docs/decisions/`](docs/decisions/) — architecture decision records
|
|
182
|
+
- [`AGENTS.md`](AGENTS.md) — coding and git workflow rules
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```powershell
|
|
187
|
+
uv run ruff check .
|
|
188
|
+
uv run ruff format --check .
|
|
189
|
+
uv run mypy --strict src
|
|
190
|
+
uv run pytest -q
|
|
191
|
+
uv build --wheel
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Supported Python: 3.11+. The package is built with `uv_build`; the test suite
|
|
195
|
+
uses a real fake upstream and recorded/documented fixtures. Live provider tests
|
|
196
|
+
are opt-in (`pytest -m live`) and require keys.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
ctxoptimizer/__init__.py,sha256=m9kqXRWCFOIGILYBMgfg3kOyEm5Q95aLeO8qXUljhXw,139
|
|
2
|
+
ctxoptimizer/__main__.py,sha256=oILSSwAoyrQXYyX9Py1HaUrpGdSYCJvXInXtq-0nfz8,241
|
|
3
|
+
ctxoptimizer/admin_security.py,sha256=fFU2BkiERsFlDWaKfBjzZu-28x6Ak0lyxgVfAsIE_pY,4239
|
|
4
|
+
ctxoptimizer/analysis/__init__.py,sha256=3xDJhZkmEpEZKaEomHnKXYL2FpYPjFxVF41AM3sulh8,85
|
|
5
|
+
ctxoptimizer/analysis/baseline.py,sha256=6a6zMAL9aUg95zEdLzmCXZCbrZThxwZHhJ0fVsQN1Fk,2222
|
|
6
|
+
ctxoptimizer/analysis/break_even.py,sha256=u5iJZVzN8-v-jBKhIP_ja236OzJ6AfIfTJ0Thq1iTCc,1539
|
|
7
|
+
ctxoptimizer/analysis/drift.py,sha256=ZQx9lKDKjnvKv_fzHh-22ntvU4VVzgQE-7jWRdym_AE,4684
|
|
8
|
+
ctxoptimizer/analysis/edits.py,sha256=HHuSnImbDGigFOPg_1TgT_dr8WgB2H8YZNFme0f7w0U,4901
|
|
9
|
+
ctxoptimizer/analysis/markers.py,sha256=lC8DqakrjHd69rXkWVWf9cbKXNGwcEIAU948SFsxXxw,958
|
|
10
|
+
ctxoptimizer/analysis/recommend.py,sha256=OX4Ljc93HtcULJOt4Plf3MpSt7i2_xcLYsPsejrp_QY,4291
|
|
11
|
+
ctxoptimizer/analysis/segments.py,sha256=GBTkhin12rhTMyJKByG9a3Q9uChZjm1g6WbH5yV0PJQ,1549
|
|
12
|
+
ctxoptimizer/analysis/session.py,sha256=-xZakAi_rulEEj7p2GEl7zm0iWO-BMKNR_jW-LQDRIw,2030
|
|
13
|
+
ctxoptimizer/analysis/state.py,sha256=B_on-eqk91xW7kZcoFE-OSd-j3ckSbM9b9qrf216Smo,8016
|
|
14
|
+
ctxoptimizer/analysis/tokens.py,sha256=fdY25I0L6XWxykM1EYQOb6KYiPHcolSPpqBRXbZrE5w,1814
|
|
15
|
+
ctxoptimizer/analysis/waste.py,sha256=1-EXfZEi-I2NY_27u3Ybyzmism5mIzsuGqejHdCrAEs,9418
|
|
16
|
+
ctxoptimizer/cli.py,sha256=7ETbw-7hApVl1WghB_5AGGmlsyq7cvhJBWViIAFQ1Wo,31687
|
|
17
|
+
ctxoptimizer/config.py,sha256=-J3sa47l-zevsHYbG2IVia5tvPnJYJJTtot95DJjd-s,17010
|
|
18
|
+
ctxoptimizer/dashboard/__init__.py,sha256=AOiOaOTyRBFBw6L2Z0IfECWH4hw9hy3cN8vxECp5u08,65
|
|
19
|
+
ctxoptimizer/dashboard/api.py,sha256=gId20pChmxndQQI2ds_l8uDPwHnrmt-wEbTDae7uXpY,19226
|
|
20
|
+
ctxoptimizer/dashboard/service.py,sha256=ipfvoELR-hTj8paPjn-UDAEEff6l271gQeJa6XX6B-E,5115
|
|
21
|
+
ctxoptimizer/dashboard/static/.gitkeep,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
22
|
+
ctxoptimizer/dashboard/templates/.gitkeep,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
23
|
+
ctxoptimizer/dashboard/templates/doctor.html,sha256=AvqIYvfBqpvlKb0oS2Or7lcRhwKqic4p7Zwo8QiST9M,2056
|
|
24
|
+
ctxoptimizer/dashboard/templates/live.html,sha256=qT35RBiSXKn2WsmehU43I8e7Zq4zy6q_PZQLvOLZeTE,4812
|
|
25
|
+
ctxoptimizer/dashboard/templates/overview.html,sha256=ZamPqCMdB1HO4KGvLq9WbVprzaMZWI557ex7O5gB3C4,8189
|
|
26
|
+
ctxoptimizer/dashboard/templates/pricing.html,sha256=VzpZKhCHOs6riaI_7d3thCAEW8n-NNXxknn-wCyvWdg,5248
|
|
27
|
+
ctxoptimizer/dashboard/templates/request.html,sha256=pUFSHZ8d8OoDHICIA8RQCRcRe9GAsxjeMVsegYxBuYQ,4717
|
|
28
|
+
ctxoptimizer/dashboard/templates/session.html,sha256=vI-K1033_-vAcLrVawNXRLR2d6wRuhH16sopQ2xv0N4,3244
|
|
29
|
+
ctxoptimizer/dashboard/templates/settings.html,sha256=iL_URUcEMveoXC72rsYnJe3llHpO7Cbrem0WoCT_yzs,3409
|
|
30
|
+
ctxoptimizer/dashboard/templates/table.html,sha256=0_wtFttPRzmYSwGu--c_5CbonZXtEGDFqYBffLcMyU0,2083
|
|
31
|
+
ctxoptimizer/dashboard/templates/transforms.html,sha256=adU--n1LIb1lrKnjDBwiJvKIb9ILtsTSRMEA_cPlIJo,5479
|
|
32
|
+
ctxoptimizer/dashboard/templates/waste.html,sha256=dGAeCFhwJCa4uN7m4MtY47E_NgrGvNaW8bky27m21tg,5363
|
|
33
|
+
ctxoptimizer/dashboard/views.py,sha256=bW-7OoSOMfiX9pmzN47kprbfx1uQFx8RYNHS_mjWq2M,14090
|
|
34
|
+
ctxoptimizer/data/cache_limits.json,sha256=EgJWBd07ZZMoUwg9zPbw19Z0BGJN68Pk3y9eDBVAym8,1068
|
|
35
|
+
ctxoptimizer/data/default_config.toml,sha256=-FEkyoIOIO1tLDLVMdTqewJPAkICvcjZf-5-S3CqoBc,3515
|
|
36
|
+
ctxoptimizer/data/pricing.json,sha256=8_ivliNzDKClYzkhi2I_AlAco7K46J6-emuRv7-RsRE,11735
|
|
37
|
+
ctxoptimizer/doctor.py,sha256=ZXQhcGm6H-h1Pd6nce-RWmFIMyRtpTUhCmHC1YxnlkM,4041
|
|
38
|
+
ctxoptimizer/guard.py,sha256=AVO_PEzoMc0jKyzXkiIhIrwdhfiAweJwB-c6kTdhMnQ,5913
|
|
39
|
+
ctxoptimizer/ids.py,sha256=YZa2SZD1EkRF4DmB2JsvZHTi8kmtuMGRVBsSUvEoGlM,2175
|
|
40
|
+
ctxoptimizer/log.py,sha256=9weVpT_ct8P_0NX2O5hfI3t4tHPO0cIFYeOgW3wHwgI,2259
|
|
41
|
+
ctxoptimizer/mcp.py,sha256=_A9iPy9drdbV-g6viU00oFRt2FFzmyqbuDxY6CnCzgw,4307
|
|
42
|
+
ctxoptimizer/models.py,sha256=ByJe_uSNJYyKZZ_T5iIYkMedxTOHWjFZNcr53LvQbgA,3582
|
|
43
|
+
ctxoptimizer/pipeline.py,sha256=WS4oeZu_T_zzuSRPvAApKOva77oao7Y1QQOonvi41w4,4471
|
|
44
|
+
ctxoptimizer/profiles.py,sha256=Agw0NGcYNUca6OP4ps0KJHA51tXnaHL1GfgQVI8-TF0,4602
|
|
45
|
+
ctxoptimizer/providers/__init__.py,sha256=SOrlG2pwaAERvooqPntgHgzj84pwcSkkB6JoxhDOFgA,1216
|
|
46
|
+
ctxoptimizer/providers/anthropic.py,sha256=n6myDvBSSqIrVTR2FH6YWrp2s5xa7LmkeL5bRlwMtoU,4115
|
|
47
|
+
ctxoptimizer/providers/base.py,sha256=Q5848ZWZXH3TwUK9LNJ2A1p2So3eClOlxiwYyacvj8U,5300
|
|
48
|
+
ctxoptimizer/providers/gemini.py,sha256=54d40_ELl6ECK1RYC9D6uOZp2YsxmShRuC-T1OsHo0o,5493
|
|
49
|
+
ctxoptimizer/providers/ollama.py,sha256=qHzU3YhjiV8MI0qX0K0-OqzW28xGwh2UDb2SVsvmqi8,5348
|
|
50
|
+
ctxoptimizer/providers/openai_chat.py,sha256=TeI7Ort82kPlrafYf2pzMTHKXqm4RtJIRJh8W-48uqo,5371
|
|
51
|
+
ctxoptimizer/providers/openai_responses.py,sha256=qTqRxERFnYILmSTGWl3MNOUPAcmJs7v8_IUfeBOskbA,4837
|
|
52
|
+
ctxoptimizer/proxy.py,sha256=r6VSerD2pzbMbs2T6gEsZMZ19mkZITAQ0BfUgwHaA20,24494
|
|
53
|
+
ctxoptimizer/routing.py,sha256=HY6M2udNJ8RgzgeZ-W5AcphIAxP2B99eOBsaF4MKcC8,8600
|
|
54
|
+
ctxoptimizer/server.py,sha256=90hMJu0XusjWClHMiib0JWUub2dqW4iGWrWbgKtlWCc,12624
|
|
55
|
+
ctxoptimizer/service.py,sha256=BV3MJRWDYm0SUyhTfkX5-KdP6C55SPrihWkvfbX_Nis,4438
|
|
56
|
+
ctxoptimizer/telemetry/__init__.py,sha256=xezH3zIzrIur0ueJpAvv5LvYYEueig0Nl4BUdYTfhKQ,82
|
|
57
|
+
ctxoptimizer/telemetry/blobs.py,sha256=421ybXUsYPSJ-x4OWMhdhSICFA1vvyrpTtz_Lii9IOQ,1609
|
|
58
|
+
ctxoptimizer/telemetry/expand.py,sha256=N3DgU2wWa138kCBkfQ3BKGMJBkNfVzzQFi2eKrgSwCc,1768
|
|
59
|
+
ctxoptimizer/telemetry/pricing.py,sha256=n9r2GrvvG8rmNihkoFoHQ3GPgWTwsSoEffQ8jeETA_s,5352
|
|
60
|
+
ctxoptimizer/telemetry/redact.py,sha256=dN6tB3uKC47qJD5281bbP17sQtdg8Ni0_uhgvva1NbY,1646
|
|
61
|
+
ctxoptimizer/telemetry/sse.py,sha256=jIXkoIMPq18n-Q_GWEGsAlL36PB7lI7uyCbvljd72vo,4569
|
|
62
|
+
ctxoptimizer/telemetry/store.py,sha256=9-f_kof9rfHCAoeQGX5whs5weKnTggefkJ_iFttR2LI,32669
|
|
63
|
+
ctxoptimizer/telemetry/usage.py,sha256=Bv0qSXbwxXQ-0GPDikhX-yQO3cLuWWyF-jydde56dpU,14874
|
|
64
|
+
ctxoptimizer/transforms/__init__.py,sha256=6JT4EJ10DCdEXE3dV_VG0mBM54YkLOKN3LLZEweZUG8,3148
|
|
65
|
+
ctxoptimizer/transforms/ansi.py,sha256=fhcR97gp4Z218MKWcvyaV_SWAodpxKuXCvgUQqTeWBo,531
|
|
66
|
+
ctxoptimizer/transforms/anthropic_cache.py,sha256=xUn0AIy6FbPOj6DpLx9EosUhz91K92vXsKbxodhgntk,3694
|
|
67
|
+
ctxoptimizer/transforms/backref.py,sha256=mQSwfYqtZuwrTobbQR3DjjXMLEd70n7USWLMTxsDZDk,2648
|
|
68
|
+
ctxoptimizer/transforms/base.py,sha256=mbeaLkHwBSLy7y-txOHssR3PtW_nH3gMAwL2fubKD1M,2736
|
|
69
|
+
ctxoptimizer/transforms/cache_serve.py,sha256=w1LXICyHVVo0f77M6YGijemYodSQSyQ_1NUhujkO898,5167
|
|
70
|
+
ctxoptimizer/transforms/dedupe_lines.py,sha256=Kz3fF1rP8hjlQDAYCdvmSAFU9m8rRt7peAlRU1_hFDI,2076
|
|
71
|
+
ctxoptimizer/transforms/eligibility.py,sha256=bPVMIeAHVBoJLzyf-zLNjTBCqTOwTfND-tmd7saHILY,748
|
|
72
|
+
ctxoptimizer/transforms/openai_hints.py,sha256=WnI09LMAJnVkBnrtcmm49H_at3Xmqfr8mjWKdSAEE6g,2909
|
|
73
|
+
ctxoptimizer/transforms/progress.py,sha256=kKyL5xphZcR9HMZ59WI3LoPJSNm1iT_8jnNP1xx0PKU,1569
|
|
74
|
+
ctxoptimizer/transforms/stale_result_elision.py,sha256=wRm3A8ZULt6X6ChilEG7QeHm_i932JeG78xCW1t__Jg,3432
|
|
75
|
+
ctxoptimizer/transforms/truncate.py,sha256=MzUQtXFr4g_u27mbXovK0m94W0e8GL2a0bKgXd3T0wg,3488
|
|
76
|
+
ctxoptimizer/transforms/volatile.py,sha256=dPYLHt17vl52hHeeRmqdou4HBIKrp3n89_kX2cqJTFo,6246
|
|
77
|
+
ctxoptimizer/transforms/whitespace.py,sha256=G3K_ZhGAOAGnP-zIYkA0jTVhfgaT0m4WoHWS5i0i05M,630
|
|
78
|
+
ctxoptimizer-0.4.1.dist-info/licenses/LICENSE,sha256=mT3OIEepsAfZheCsFU_-faNVDCJadYSSvjDz5hPDheI,1062
|
|
79
|
+
ctxoptimizer-0.4.1.dist-info/licenses/NOTICE,sha256=wy8RRAkVzCIyl1DvAAtpy2n65AsqfIvwTOC9_1dIA9o,560
|
|
80
|
+
ctxoptimizer-0.4.1.dist-info/WHEEL,sha256=si2p8vW03RnhyBnhWl4tj-ObJa78SpE-c9HsEtblGR0,80
|
|
81
|
+
ctxoptimizer-0.4.1.dist-info/entry_points.txt,sha256=lV3sTBBTZ6tNUf4FJYXBLIg9QpqxmFblBBQAhBd29-I,55
|
|
82
|
+
ctxoptimizer-0.4.1.dist-info/METADATA,sha256=5SH7DENaYe7CaXzUIO42WblJU9H5SrLuVEfUbxFrars,7478
|
|
83
|
+
ctxoptimizer-0.4.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ctxoptimizer
|
|
2
|
+
Copyright (c) 2026 Ahmed
|
|
3
|
+
|
|
4
|
+
ctxoptimizer is free and open-source software licensed under the MIT License.
|
|
5
|
+
See LICENSE.
|
|
6
|
+
|
|
7
|
+
Provider names (Anthropic, OpenAI, Google Gemini, Ollama, DeepSeek, OpenRouter,
|
|
8
|
+
OpenCode, Claude Code, Codex, Cursor, Aider, Cline and others) are trademarks of
|
|
9
|
+
their respective owners and are used only for descriptive compatibility. This
|
|
10
|
+
project is not affiliated with or endorsed by any provider.
|
|
11
|
+
|
|
12
|
+
Third-party components are distributed under their own licenses. See
|
|
13
|
+
THIRD_PARTY_LICENSES.md and the installed package metadata.
|