awecompress 0.2.0__tar.gz
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.
- awecompress-0.2.0/LICENSE +207 -0
- awecompress-0.2.0/PKG-INFO +206 -0
- awecompress-0.2.0/README.md +183 -0
- awecompress-0.2.0/pyproject.toml +41 -0
- awecompress-0.2.0/setup.cfg +4 -0
- awecompress-0.2.0/src/awecompress/__init__.py +14 -0
- awecompress-0.2.0/src/awecompress/cli.py +127 -0
- awecompress-0.2.0/src/awecompress/compress.py +260 -0
- awecompress-0.2.0/src/awecompress/config.py +161 -0
- awecompress-0.2.0/src/awecompress/integrate.py +177 -0
- awecompress-0.2.0/src/awecompress/protocols.py +421 -0
- awecompress-0.2.0/src/awecompress/server.py +282 -0
- awecompress-0.2.0/src/awecompress/store.py +110 -0
- awecompress-0.2.0/src/awecompress/summarize.py +66 -0
- awecompress-0.2.0/src/awecompress.egg-info/PKG-INFO +206 -0
- awecompress-0.2.0/src/awecompress.egg-info/SOURCES.txt +24 -0
- awecompress-0.2.0/src/awecompress.egg-info/dependency_links.txt +1 -0
- awecompress-0.2.0/src/awecompress.egg-info/entry_points.txt +2 -0
- awecompress-0.2.0/src/awecompress.egg-info/requires.txt +6 -0
- awecompress-0.2.0/src/awecompress.egg-info/top_level.txt +1 -0
- awecompress-0.2.0/tests/test_cli.py +107 -0
- awecompress-0.2.0/tests/test_compress.py +311 -0
- awecompress-0.2.0/tests/test_config.py +82 -0
- awecompress-0.2.0/tests/test_server.py +328 -0
- awecompress-0.2.0/tests/test_store.py +54 -0
- awecompress-0.2.0/tests/test_summarize.py +110 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Mozilla Public License Version 2.0
|
|
2
|
+
|
|
3
|
+
1. Definitions
|
|
4
|
+
--------------
|
|
5
|
+
|
|
6
|
+
1.1. "Contributor"
|
|
7
|
+
means each individual or legal entity that creates, contributes to
|
|
8
|
+
the creation of, or owns Covered Software.
|
|
9
|
+
|
|
10
|
+
1.2. "Contributor Version"
|
|
11
|
+
means the combination of the Contributions of others (if any) used
|
|
12
|
+
by a Contributor and that particular Contributor's Contribution.
|
|
13
|
+
|
|
14
|
+
1.3. "Contribution"
|
|
15
|
+
means Covered Software of a particular Contributor.
|
|
16
|
+
|
|
17
|
+
1.4. "Covered Software"
|
|
18
|
+
means Source Code Form to which the initial Contributor has attached
|
|
19
|
+
the notice in Exhibit A, the Executable Form of such Source Code
|
|
20
|
+
Form, and Modifications of such Source Code Form, in each case
|
|
21
|
+
including portions thereof.
|
|
22
|
+
|
|
23
|
+
1.5. "Incompatible With Secondary Licenses"
|
|
24
|
+
means that the work at issue cannot be redistributed under the terms
|
|
25
|
+
of this License without violating, conflicting with, or otherwise
|
|
26
|
+
being incompatible with the terms of the Secondary License.
|
|
27
|
+
|
|
28
|
+
1.6. "Larger Work"
|
|
29
|
+
means a work that combines Covered Software with other material, in
|
|
30
|
+
a separate file or files, that is not Covered Software.
|
|
31
|
+
|
|
32
|
+
1.7. "License"
|
|
33
|
+
means this document.
|
|
34
|
+
|
|
35
|
+
1.8. "Licensable"
|
|
36
|
+
means having the right to grant, to the maximum extent possible,
|
|
37
|
+
whether at the time of the initial grant or subsequently, any and
|
|
38
|
+
all of the rights conveyed by this License.
|
|
39
|
+
|
|
40
|
+
1.9. "Modifications"
|
|
41
|
+
means any of the following:
|
|
42
|
+
|
|
43
|
+
(a) any addition to or deletion from the substance or structure of
|
|
44
|
+
the Covered Software; or
|
|
45
|
+
|
|
46
|
+
(b) any new Source Code Form to which the initial Contributor has
|
|
47
|
+
attached the notice in Exhibit A and which constitutes Covered
|
|
48
|
+
Software.
|
|
49
|
+
|
|
50
|
+
1.10. "Patent Claims" of a Contributor
|
|
51
|
+
means any patent claim, including without limitation, method,
|
|
52
|
+
process, and apparatus claims, in any patent Licensable by such
|
|
53
|
+
Contributor that would be infringed, but for the grant of the
|
|
54
|
+
License, by the making, using, selling, offering for sale, having
|
|
55
|
+
made, import, or transfer of either its Contributions or its
|
|
56
|
+
Contributor Version.
|
|
57
|
+
|
|
58
|
+
1.11. "Secondary License"
|
|
59
|
+
means either the GNU General Public License, Version 2.0, or any
|
|
60
|
+
later version of such license published by the Free Software
|
|
61
|
+
Foundation; or the GNU Lesser General Public License, Version 2.1,
|
|
62
|
+
or any later version of such license published by the Free Software
|
|
63
|
+
Foundation; or the GNU Affero General Public License, Version 3.0,
|
|
64
|
+
or any later version of such license published by the Free Software
|
|
65
|
+
Foundation.
|
|
66
|
+
|
|
67
|
+
1.12. "Source Code Form"
|
|
68
|
+
means the form of software preferred for making modifications.
|
|
69
|
+
|
|
70
|
+
1.13. "You" (or "Your")
|
|
71
|
+
means an individual or a legal entity exercising rights under this
|
|
72
|
+
License.
|
|
73
|
+
|
|
74
|
+
2. License Grants and Conditions
|
|
75
|
+
--------------------------------
|
|
76
|
+
|
|
77
|
+
2.1. Grants
|
|
78
|
+
|
|
79
|
+
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
80
|
+
non-exclusive license, subject to third-party intellectual property
|
|
81
|
+
claims, under intellectual property rights (other than patent or
|
|
82
|
+
trademark) Licensable by such Contributor to use, reproduce, make
|
|
83
|
+
available, modify, display, perform, distribute, and otherwise
|
|
84
|
+
exploit its Contributions, either on an unmodified basis, with
|
|
85
|
+
Modifications, or as part of a Larger Work; and under Patent Claims
|
|
86
|
+
of such Contributor to make, use, sell, offer for sale, have made,
|
|
87
|
+
import, and otherwise transfer either its Contributions or its
|
|
88
|
+
Contributor Version.
|
|
89
|
+
|
|
90
|
+
2.2. Effective Date
|
|
91
|
+
|
|
92
|
+
The licenses granted in Section 2.1 with respect to any Contribution
|
|
93
|
+
become effective for each Contribution on the date the Contributor
|
|
94
|
+
first distributes such Contribution.
|
|
95
|
+
|
|
96
|
+
2.3. Limitations on Grant Scope
|
|
97
|
+
|
|
98
|
+
Nothing in this License shall be construed as excluding or limiting
|
|
99
|
+
any implied license or other defense to infringement that may
|
|
100
|
+
otherwise be available to You under applicable patent law.
|
|
101
|
+
|
|
102
|
+
2.4. Subsequent Licenses
|
|
103
|
+
|
|
104
|
+
No Contributor makes additional grants as a result of Your choice to
|
|
105
|
+
distribute the Covered Software under a subsequent version of this
|
|
106
|
+
License or under the terms of a Secondary License (if permitted
|
|
107
|
+
under the terms of this License).
|
|
108
|
+
|
|
109
|
+
2.5. Representation
|
|
110
|
+
|
|
111
|
+
Each Contributor represents that the Contributor believes its
|
|
112
|
+
Contributions are its original creation(s) or it has sufficient
|
|
113
|
+
rights to grant the rights to its Contributions conveyed by this
|
|
114
|
+
License.
|
|
115
|
+
|
|
116
|
+
3. Obligations
|
|
117
|
+
--------------
|
|
118
|
+
|
|
119
|
+
3.1. Distribution of Source Code Form
|
|
120
|
+
|
|
121
|
+
All distribution of Covered Software in Source Code Form, including
|
|
122
|
+
any Modifications that You create or to which You contribute, must
|
|
123
|
+
be under the terms of this License. You must inform recipients that
|
|
124
|
+
the Source Code Form of the Covered Software is subject to the terms
|
|
125
|
+
of this License, and how they can obtain a copy of this License.
|
|
126
|
+
|
|
127
|
+
3.2. Distribution of Executable Form
|
|
128
|
+
|
|
129
|
+
If You distribute Covered Software in Executable Form then:
|
|
130
|
+
|
|
131
|
+
(a) such Covered Software must also be made available in Source Code
|
|
132
|
+
Form; and
|
|
133
|
+
|
|
134
|
+
(b) You may distribute such Executable Form under the terms of this
|
|
135
|
+
License; and
|
|
136
|
+
|
|
137
|
+
(c) You must include a copy of this License with every copy of the
|
|
138
|
+
Covered Software in Executable Form You distribute; and
|
|
139
|
+
|
|
140
|
+
(d) You may not prevent recipients from copying, modifying, or
|
|
141
|
+
further distributing the Covered Software in Source Code Form.
|
|
142
|
+
|
|
143
|
+
3.3. Distribution of a Larger Work
|
|
144
|
+
|
|
145
|
+
You may create and distribute a Larger Work under terms of Your
|
|
146
|
+
choice, provided that You also comply with the requirements of this
|
|
147
|
+
License for the Covered Software.
|
|
148
|
+
|
|
149
|
+
3.4. Notices
|
|
150
|
+
|
|
151
|
+
You may not remove or alter the substance of any license notices
|
|
152
|
+
(including copyright notices, patent notices, disclaimers of
|
|
153
|
+
warranty, or limitations of liability) contained within the Source
|
|
154
|
+
Code Form of the Covered Software.
|
|
155
|
+
|
|
156
|
+
3.5. Application of Additional Terms
|
|
157
|
+
|
|
158
|
+
You may not offer or impose any additional or different terms on any
|
|
159
|
+
portion of the Covered Software that would alter or restrict the
|
|
160
|
+
applicable version of this License or the recipients' rights
|
|
161
|
+
hereunder.
|
|
162
|
+
|
|
163
|
+
4. Inability to Comply Due to Statute or Regulation
|
|
164
|
+
---------------------------------------------------
|
|
165
|
+
|
|
166
|
+
If it is impossible for You to comply with any of the terms of this
|
|
167
|
+
License with respect to some or all of the Covered Software due to
|
|
168
|
+
statute, judicial order, or regulation then You must: (a) comply with
|
|
169
|
+
the terms of this License to the maximum extent possible; and (b)
|
|
170
|
+
describe the limitations and the code they affect.
|
|
171
|
+
|
|
172
|
+
5. Termination
|
|
173
|
+
--------------
|
|
174
|
+
|
|
175
|
+
5.1. The rights granted under this License will terminate automatically
|
|
176
|
+
if You fail to comply with any of its terms. However, if You become
|
|
177
|
+
compliant, then the rights granted under this License are
|
|
178
|
+
reinstated.
|
|
179
|
+
|
|
180
|
+
5.2. If You initiate litigation against any entity by asserting a patent
|
|
181
|
+
infringement claim (excluding declaratory judgment actions,
|
|
182
|
+
counter-claims, and cross-claims) alleging that a Contributor
|
|
183
|
+
Version directly or indirectly infringes any patent, then the rights
|
|
184
|
+
granted to You by any and all Contributors for the Covered Software
|
|
185
|
+
under Sections 2.1 and 2.2 shall terminate.
|
|
186
|
+
|
|
187
|
+
6. Disclaimer of Warranty
|
|
188
|
+
-------------------------
|
|
189
|
+
|
|
190
|
+
Covered Software is provided under this License on an "as is" basis,
|
|
191
|
+
without warranty of any kind, either express or implied.
|
|
192
|
+
|
|
193
|
+
7. Limitation of Liability
|
|
194
|
+
---------------------------
|
|
195
|
+
|
|
196
|
+
To the extent permitted by applicable law, in no event shall any
|
|
197
|
+
Contributor be liable to You for any indirect, special, incidental, or
|
|
198
|
+
consequential damages.
|
|
199
|
+
|
|
200
|
+
8. Miscellaneous
|
|
201
|
+
----------------
|
|
202
|
+
|
|
203
|
+
8.1. This License represents the complete agreement concerning subject
|
|
204
|
+
matter hereof.
|
|
205
|
+
|
|
206
|
+
8.2. If any provision of this License is held to be unenforceable, it
|
|
207
|
+
shall be reformed only to the minimum extent necessary.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: awecompress
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Context compression proxy: freeze old turns into one summary before they reach your provider.
|
|
5
|
+
Author: Peng
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Keywords: ai,llm,proxy,claude,claude-code,context,compression
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: aiohttp>=3.9
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
<div align="center">
|
|
25
|
+
<h1>awecompress: Context Compression Proxy</h1>
|
|
26
|
+
<p><strong>Freeze old turns into one summary before they reach your provider.</strong></p>
|
|
27
|
+
<p>Local context compression for coding agents — three wire protocols, standalone proxy or in-process inside awerouter. When a session's history crosses a token threshold, the oldest whole turns are replaced by a single frozen LLM summary — cached, so every later request reuses the same bytes and your provider's prompt cache stays warm.</p>
|
|
28
|
+
<p>
|
|
29
|
+
<strong>English</strong> ·
|
|
30
|
+
<a href="./README_cn.md">简体中文</a>
|
|
31
|
+
</p>
|
|
32
|
+
<p>
|
|
33
|
+
<img src="https://img.shields.io/badge/version-0.2.0-7C3AED?style=flat-square" alt="Version">
|
|
34
|
+
<img src="https://img.shields.io/badge/python-%E2%89%A53.9-0EA5E9?style=flat-square" alt="Python">
|
|
35
|
+
<img src="https://img.shields.io/badge/license-MPL--2.0-22C55E?style=flat-square" alt="License">
|
|
36
|
+
</p>
|
|
37
|
+
<p>
|
|
38
|
+
<img src="https://img.shields.io/badge/status-alpha-c96a3d?style=flat-square" alt="Status">
|
|
39
|
+
<img src="https://img.shields.io/badge/install-pip-22C55E?style=flat-square" alt="pip">
|
|
40
|
+
<img src="https://img.shields.io/badge/platform-terminal-334155?style=flat-square" alt="Platform">
|
|
41
|
+
<img src="https://img.shields.io/github/stars/wehuman01/awecompress?style=flat-square" alt="Stars">
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
> Compress long coding-agent context: old turns become one frozen summary, requests shrink, sessions run for days without a `/clear`. Standalone proxy, or one flag inside awerouter.
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
Claude Code resubmits the whole conversation every turn. Hours in, most of that is dead weight — old file reads, finished exploration, failed attempts.
|
|
50
|
+
|
|
51
|
+
awecompress sits between the agent and whatever speaks its protocol upstream — Anthropic Messages, OpenAI Chat Completions, or OpenAI Responses:
|
|
52
|
+
|
|
53
|
+
Responses requests that use the valid string form of `input` are forwarded transparently; compression requires the list form so turn boundaries remain explicit.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Claude Code → awecompress (:8808) → awerouter → providers
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For each request it estimates the context size. Above a threshold, it picks a cut point on a turn boundary (a message a human actually sent — so a tool call is never separated from its result), summarizes everything before it with one LLM call, replaces those messages with a single summary message, and freezes the result in a local SQLite store. Protected content — todo lists, plans, task/skill outcomes, files you name by pattern — renders into the summarizer uncapped and must survive the summary verbatim.
|
|
60
|
+
|
|
61
|
+
Three properties matter:
|
|
62
|
+
|
|
63
|
+
- **Frozen, not recomputed.** The summary is stored once. Every later request reuses the same bytes, so the provider prompt cache sees a stable prefix. Growing the covered span rewrites the summary once — a one-time cache miss.
|
|
64
|
+
- **Fail-open.** Any failure in the compression path forwards the original body untouched. A compression problem never breaks the session.
|
|
65
|
+
- **No auth, no routing.** Auth headers pass through; routing and failover stay in [awerouter](https://github.com/wehuman01/awerouter) (or whatever your upstream is). The summary calls themselves are ordinary requests through that upstream — awerouter's flash routing applies to them like anything else.
|
|
66
|
+
|
|
67
|
+
Set `X-Awecompress: off` on a request to bypass compression entirely.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install awecompress
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or from source:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/wehuman01/awecompress
|
|
79
|
+
cd awecompress && pip install -e .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
Stack with awerouter (the intended setup):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
awerouter serve run # your routing daemon, as usual
|
|
88
|
+
awecompress serve # the compression proxy, foreground
|
|
89
|
+
|
|
90
|
+
# point Claude Code at awecompress instead of awerouter
|
|
91
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8808
|
|
92
|
+
claude
|
|
93
|
+
|
|
94
|
+
# openai-chat / openai-responses clients work the same way
|
|
95
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8808/v1
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Or skip the proxy entirely** — with awerouter installed, flip the profile flag and the compression runs inside the router (see below).
|
|
99
|
+
|
|
100
|
+
Standalone against any Anthropic-protocol endpoint:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
awecompress serve --upstream https://api.anthropic.com
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Watch it work — one line per compressed request, and stats on demand:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
[awecompress] 3f9a2c1b: init — summarized messages 0..61 (est 41200 tok) into 1100
|
|
110
|
+
via claude-sonnet in 2.8s; body est 48900 -> 8800 tokens
|
|
111
|
+
[awecompress] 3f9a2c1b: applied frozen summary (messages 0..61) — est 48900 -> 8800 tokens
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
awecompress status
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## With awerouter (in-process, no proxy)
|
|
119
|
+
|
|
120
|
+
`awerouter` accepts an `awecompress` profile flag, exactly like `rtk`/`odcp`. The compression core runs inside the router's pipeline — ahead of odcp pruning and rtk compression — so clients keep pointing at the router port and the flag hot-reloads with `routing.json`:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
"cc-router-1": {
|
|
124
|
+
"protocol": "anthropic",
|
|
125
|
+
"destinations": { "flash": "stepfun,step-3.7-flash", "pro": "glm,glm-5.3" },
|
|
126
|
+
"odcp": true,
|
|
127
|
+
"awecompress": true
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
An object tunes it — `summaryModel` picks who serves the summary calls: `"flash"` (default, the flash destination — routed directly, never re-priced to pro by the long-context rule), `"pro"`, or any model a provider declares in `providers.json` (validated at serve start). The other keys mirror the standalone config:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
"awecompress": {
|
|
135
|
+
"summaryModel": "flash",
|
|
136
|
+
"thresholdTokens": 60000,
|
|
137
|
+
"keepRecentTurns": 4,
|
|
138
|
+
"protectedTools": ["task", "skill", "todowrite", "todoread", "updateplan"],
|
|
139
|
+
"protectedFilePatterns": ["**/*.schema.json"]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Requires the package on the router's side: `pip install awerouter[compress]` (the flag dies at serve start with that hint when it is missing). Savings land in the usage log next to rtk/odcp (`awecompress_saved`, shown by `awerouter usage`), `X-Awerouter-Token-Saver: off` disables all lossy layers at once, and the frozen store is shared with the standalone proxy (`awecompress status` / `clear` manage it either way).
|
|
144
|
+
|
|
145
|
+
## Config
|
|
146
|
+
|
|
147
|
+
`~/.config/awecompress/config.json` (or `$AWECOMPRESS_CONFIG_DIR`), written with defaults on first run:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"port": 8808,
|
|
152
|
+
"upstream": "http://127.0.0.1:20128",
|
|
153
|
+
"thresholdTokens": 60000,
|
|
154
|
+
"keepRecentTurns": 4,
|
|
155
|
+
"minSpanTokens": 8000,
|
|
156
|
+
"summaryModel": "",
|
|
157
|
+
"summaryMaxTokens": 2048
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Key | Default | Meaning |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `port` | `8808` | Listen port. |
|
|
164
|
+
| `upstream` | `http://127.0.0.1:20128` | Where requests go — awerouter by default. |
|
|
165
|
+
| `thresholdTokens` | `60000` | Estimated context above which compression triggers. |
|
|
166
|
+
| `keepRecentTurns` | `4` | Human turns always kept verbatim. |
|
|
167
|
+
| `minSpanTokens` | `8000` | Don't summarize spans smaller than this — not worth a call. |
|
|
168
|
+
| `summaryModel` | `""` | Model for summary calls. Empty = the request's own model, routed by your upstream (usually flash). |
|
|
169
|
+
| `summaryMaxTokens` | `2048` | Max output tokens for a summary. |
|
|
170
|
+
| `summaryTimeoutSeconds` | `60` | Give up on a summary call after this; the request forwards uncompressed. |
|
|
171
|
+
| `protectedTools` | see below | Tools whose calls/results render into the summarizer uncapped and must survive the summary verbatim. |
|
|
172
|
+
| `protectedFilePatterns` | `[]` | Glob patterns; a call whose `file_path`/`path` argument matches renders uncapped too. |
|
|
173
|
+
| `transcriptResultCap` | `4000` | Per-tool-result cap (chars) when flattening history for the summarizer. |
|
|
174
|
+
| `dbPath` | config dir | SQLite store for frozen summaries. |
|
|
175
|
+
|
|
176
|
+
## Commands
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
awecompress serve # run the proxy in the foreground
|
|
180
|
+
awecompress serve --port 8809 --upstream http://127.0.0.1:20128
|
|
181
|
+
awecompress status # running state + compression stats
|
|
182
|
+
awecompress config path # where the config lives
|
|
183
|
+
awecompress config show # print it
|
|
184
|
+
awecompress clear --yes # drop all frozen summaries
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Notes and limits
|
|
188
|
+
|
|
189
|
+
- **Three protocols** — Anthropic Messages, OpenAI Chat Completions, OpenAI Responses. Requests to other paths are relayed untouched.
|
|
190
|
+
- **Compression is lossy by design.** The summarizer prompt demands exhaustive technical detail and verbatim short user messages, but a summary is still a summary. `keepRecentTurns` keeps the working set verbatim; raise it if you want more raw history.
|
|
191
|
+
- **A session rewound to a checkpoint** (changed history under a stored summary) is detected by hash and recompressed from scratch.
|
|
192
|
+
- **`/v1/messages/count_tokens`** applies existing summaries but never triggers a new summary call.
|
|
193
|
+
- Inspired by [DCP](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning)'s Compress strategy (AGPL) and the closed-source Sleev — both harness-integrated. awecompress is an independent, proxy-native implementation; no DCP code is used.
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pip install -e ".[dev]"
|
|
199
|
+
pytest
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for architecture and the design contract.
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MPL-2.0. Compression behavior inspired by DCP's public Compress documentation; implementation written from scratch.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>awecompress: Context Compression Proxy</h1>
|
|
3
|
+
<p><strong>Freeze old turns into one summary before they reach your provider.</strong></p>
|
|
4
|
+
<p>Local context compression for coding agents — three wire protocols, standalone proxy or in-process inside awerouter. When a session's history crosses a token threshold, the oldest whole turns are replaced by a single frozen LLM summary — cached, so every later request reuses the same bytes and your provider's prompt cache stays warm.</p>
|
|
5
|
+
<p>
|
|
6
|
+
<strong>English</strong> ·
|
|
7
|
+
<a href="./README_cn.md">简体中文</a>
|
|
8
|
+
</p>
|
|
9
|
+
<p>
|
|
10
|
+
<img src="https://img.shields.io/badge/version-0.2.0-7C3AED?style=flat-square" alt="Version">
|
|
11
|
+
<img src="https://img.shields.io/badge/python-%E2%89%A53.9-0EA5E9?style=flat-square" alt="Python">
|
|
12
|
+
<img src="https://img.shields.io/badge/license-MPL--2.0-22C55E?style=flat-square" alt="License">
|
|
13
|
+
</p>
|
|
14
|
+
<p>
|
|
15
|
+
<img src="https://img.shields.io/badge/status-alpha-c96a3d?style=flat-square" alt="Status">
|
|
16
|
+
<img src="https://img.shields.io/badge/install-pip-22C55E?style=flat-square" alt="pip">
|
|
17
|
+
<img src="https://img.shields.io/badge/platform-terminal-334155?style=flat-square" alt="Platform">
|
|
18
|
+
<img src="https://img.shields.io/github/stars/wehuman01/awecompress?style=flat-square" alt="Stars">
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
> Compress long coding-agent context: old turns become one frozen summary, requests shrink, sessions run for days without a `/clear`. Standalone proxy, or one flag inside awerouter.
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
Claude Code resubmits the whole conversation every turn. Hours in, most of that is dead weight — old file reads, finished exploration, failed attempts.
|
|
27
|
+
|
|
28
|
+
awecompress sits between the agent and whatever speaks its protocol upstream — Anthropic Messages, OpenAI Chat Completions, or OpenAI Responses:
|
|
29
|
+
|
|
30
|
+
Responses requests that use the valid string form of `input` are forwarded transparently; compression requires the list form so turn boundaries remain explicit.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Claude Code → awecompress (:8808) → awerouter → providers
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For each request it estimates the context size. Above a threshold, it picks a cut point on a turn boundary (a message a human actually sent — so a tool call is never separated from its result), summarizes everything before it with one LLM call, replaces those messages with a single summary message, and freezes the result in a local SQLite store. Protected content — todo lists, plans, task/skill outcomes, files you name by pattern — renders into the summarizer uncapped and must survive the summary verbatim.
|
|
37
|
+
|
|
38
|
+
Three properties matter:
|
|
39
|
+
|
|
40
|
+
- **Frozen, not recomputed.** The summary is stored once. Every later request reuses the same bytes, so the provider prompt cache sees a stable prefix. Growing the covered span rewrites the summary once — a one-time cache miss.
|
|
41
|
+
- **Fail-open.** Any failure in the compression path forwards the original body untouched. A compression problem never breaks the session.
|
|
42
|
+
- **No auth, no routing.** Auth headers pass through; routing and failover stay in [awerouter](https://github.com/wehuman01/awerouter) (or whatever your upstream is). The summary calls themselves are ordinary requests through that upstream — awerouter's flash routing applies to them like anything else.
|
|
43
|
+
|
|
44
|
+
Set `X-Awecompress: off` on a request to bypass compression entirely.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install awecompress
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or from source:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/wehuman01/awecompress
|
|
56
|
+
cd awecompress && pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
Stack with awerouter (the intended setup):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
awerouter serve run # your routing daemon, as usual
|
|
65
|
+
awecompress serve # the compression proxy, foreground
|
|
66
|
+
|
|
67
|
+
# point Claude Code at awecompress instead of awerouter
|
|
68
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8808
|
|
69
|
+
claude
|
|
70
|
+
|
|
71
|
+
# openai-chat / openai-responses clients work the same way
|
|
72
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8808/v1
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Or skip the proxy entirely** — with awerouter installed, flip the profile flag and the compression runs inside the router (see below).
|
|
76
|
+
|
|
77
|
+
Standalone against any Anthropic-protocol endpoint:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
awecompress serve --upstream https://api.anthropic.com
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Watch it work — one line per compressed request, and stats on demand:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
[awecompress] 3f9a2c1b: init — summarized messages 0..61 (est 41200 tok) into 1100
|
|
87
|
+
via claude-sonnet in 2.8s; body est 48900 -> 8800 tokens
|
|
88
|
+
[awecompress] 3f9a2c1b: applied frozen summary (messages 0..61) — est 48900 -> 8800 tokens
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
awecompress status
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## With awerouter (in-process, no proxy)
|
|
96
|
+
|
|
97
|
+
`awerouter` accepts an `awecompress` profile flag, exactly like `rtk`/`odcp`. The compression core runs inside the router's pipeline — ahead of odcp pruning and rtk compression — so clients keep pointing at the router port and the flag hot-reloads with `routing.json`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
"cc-router-1": {
|
|
101
|
+
"protocol": "anthropic",
|
|
102
|
+
"destinations": { "flash": "stepfun,step-3.7-flash", "pro": "glm,glm-5.3" },
|
|
103
|
+
"odcp": true,
|
|
104
|
+
"awecompress": true
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An object tunes it — `summaryModel` picks who serves the summary calls: `"flash"` (default, the flash destination — routed directly, never re-priced to pro by the long-context rule), `"pro"`, or any model a provider declares in `providers.json` (validated at serve start). The other keys mirror the standalone config:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
"awecompress": {
|
|
112
|
+
"summaryModel": "flash",
|
|
113
|
+
"thresholdTokens": 60000,
|
|
114
|
+
"keepRecentTurns": 4,
|
|
115
|
+
"protectedTools": ["task", "skill", "todowrite", "todoread", "updateplan"],
|
|
116
|
+
"protectedFilePatterns": ["**/*.schema.json"]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Requires the package on the router's side: `pip install awerouter[compress]` (the flag dies at serve start with that hint when it is missing). Savings land in the usage log next to rtk/odcp (`awecompress_saved`, shown by `awerouter usage`), `X-Awerouter-Token-Saver: off` disables all lossy layers at once, and the frozen store is shared with the standalone proxy (`awecompress status` / `clear` manage it either way).
|
|
121
|
+
|
|
122
|
+
## Config
|
|
123
|
+
|
|
124
|
+
`~/.config/awecompress/config.json` (or `$AWECOMPRESS_CONFIG_DIR`), written with defaults on first run:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"port": 8808,
|
|
129
|
+
"upstream": "http://127.0.0.1:20128",
|
|
130
|
+
"thresholdTokens": 60000,
|
|
131
|
+
"keepRecentTurns": 4,
|
|
132
|
+
"minSpanTokens": 8000,
|
|
133
|
+
"summaryModel": "",
|
|
134
|
+
"summaryMaxTokens": 2048
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Key | Default | Meaning |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `port` | `8808` | Listen port. |
|
|
141
|
+
| `upstream` | `http://127.0.0.1:20128` | Where requests go — awerouter by default. |
|
|
142
|
+
| `thresholdTokens` | `60000` | Estimated context above which compression triggers. |
|
|
143
|
+
| `keepRecentTurns` | `4` | Human turns always kept verbatim. |
|
|
144
|
+
| `minSpanTokens` | `8000` | Don't summarize spans smaller than this — not worth a call. |
|
|
145
|
+
| `summaryModel` | `""` | Model for summary calls. Empty = the request's own model, routed by your upstream (usually flash). |
|
|
146
|
+
| `summaryMaxTokens` | `2048` | Max output tokens for a summary. |
|
|
147
|
+
| `summaryTimeoutSeconds` | `60` | Give up on a summary call after this; the request forwards uncompressed. |
|
|
148
|
+
| `protectedTools` | see below | Tools whose calls/results render into the summarizer uncapped and must survive the summary verbatim. |
|
|
149
|
+
| `protectedFilePatterns` | `[]` | Glob patterns; a call whose `file_path`/`path` argument matches renders uncapped too. |
|
|
150
|
+
| `transcriptResultCap` | `4000` | Per-tool-result cap (chars) when flattening history for the summarizer. |
|
|
151
|
+
| `dbPath` | config dir | SQLite store for frozen summaries. |
|
|
152
|
+
|
|
153
|
+
## Commands
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
awecompress serve # run the proxy in the foreground
|
|
157
|
+
awecompress serve --port 8809 --upstream http://127.0.0.1:20128
|
|
158
|
+
awecompress status # running state + compression stats
|
|
159
|
+
awecompress config path # where the config lives
|
|
160
|
+
awecompress config show # print it
|
|
161
|
+
awecompress clear --yes # drop all frozen summaries
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Notes and limits
|
|
165
|
+
|
|
166
|
+
- **Three protocols** — Anthropic Messages, OpenAI Chat Completions, OpenAI Responses. Requests to other paths are relayed untouched.
|
|
167
|
+
- **Compression is lossy by design.** The summarizer prompt demands exhaustive technical detail and verbatim short user messages, but a summary is still a summary. `keepRecentTurns` keeps the working set verbatim; raise it if you want more raw history.
|
|
168
|
+
- **A session rewound to a checkpoint** (changed history under a stored summary) is detected by hash and recompressed from scratch.
|
|
169
|
+
- **`/v1/messages/count_tokens`** applies existing summaries but never triggers a new summary call.
|
|
170
|
+
- Inspired by [DCP](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning)'s Compress strategy (AGPL) and the closed-source Sleev — both harness-integrated. awecompress is an independent, proxy-native implementation; no DCP code is used.
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
pip install -e ".[dev]"
|
|
176
|
+
pytest
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for architecture and the design contract.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MPL-2.0. Compression behavior inspired by DCP's public Compress documentation; implementation written from scratch.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "awecompress"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Context compression proxy: freeze old turns into one summary before they reach your provider."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MPL-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Peng" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "llm", "proxy", "claude", "claude-code", "context", "compression"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Topic :: Utilities"
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"click>=8.1",
|
|
26
|
+
"aiohttp>=3.9",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
awecompress = "awecompress.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
pythonpath = ["src"]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""awecompress: local context compression for coding agents.
|
|
2
|
+
|
|
3
|
+
Sits between the harness (Claude Code, OpenCode, ...) and its upstream, or
|
|
4
|
+
runs inside awerouter beside odcp/rtk. When a session's history crosses a
|
|
5
|
+
token threshold, the oldest whole turns are replaced by one frozen LLM
|
|
6
|
+
summary — cached, so every later request reuses the same bytes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("awecompress")
|
|
13
|
+
except PackageNotFoundError: # running from a source checkout
|
|
14
|
+
__version__ = "0.2.0"
|