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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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"