opencode-acp 1.3.1 → 1.4.0

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.
package/README.md CHANGED
@@ -22,19 +22,78 @@ The model decides <em>when</em> and <em>what</em> to compress — not a hard lim
22
22
 
23
23
  ## Why ACP
24
24
 
25
- ACP is a hardened fork of [DCP](https://github.com/Tarquinen/opencode-dynamic-context-pruning) with **35 bug fixes** applied. It turns context management from a passive, crash-prone mechanism into something stable enough for production use.
26
-
27
- | | DCP (original) | ACP (this fork) |
25
+ ACP is **model-driven context management** for OpenCode. Instead of passively
26
+ truncating at a hard limit, it exposes tools that let the model decide **when**
27
+ and **what** to compress producing high-fidelity summaries of completed
28
+ segments while freeing context space. The model controls what to keep, which is
29
+ strictly better than blind truncation.
30
+
31
+ ### What makes ACP different
32
+
33
+ - **Full block lifecycle, model-driven.** The model can `compress` a range into a
34
+ summary, `decompress` to restore any block on demand, and `mark_block` /
35
+ `unmark_block` to flag blocks for deferred deletion. The model owns its own
36
+ context lifecycle — not just "create a block and hope GC handles it".
37
+ - **Cache-aware by design.** Summaries merge into existing user turns and batch
38
+ cleanup does a *single* cache break, so prefix-cache hit ratios stay near **90%**
39
+ even when sessions run at 70%+ context utilization (see [Proven at scale](#proven-at-scale)).
40
+ - **Pressure-aware GC.** Instead of blind age-based truncation that silently
41
+ drops important info (task IDs, file paths, decisions), ACP consolidates marked
42
+ blocks first and demotes blind truncation to a last-resort fallback at 100%.
43
+ - **Two compression modes.** *Range* mode (contiguous spans → block summaries)
44
+ and *message* mode (surgical per-message summaries for scattered content).
45
+ - **Protected content.** Tool outputs, file patterns, and user messages you mark
46
+ protected are injected into summaries, so nothing critical is ever lost.
47
+ - **Automatic strategies.** Deduplication (same tool + args → keep last) and
48
+ purge-errors (drop errored inputs after N turns), recalculated on compress —
49
+ not on every turn.
50
+ - **Production-grade configuration.** 3-layer merge (global → config-dir →
51
+ project), per-model context-limit overrides, and user-editable prompts.
52
+
53
+ ### A hardened fork of DCP
54
+
55
+ ACP started as a fork of [DCP](https://github.com/Tarquinen/opencode-dynamic-context-pruning)
56
+ and now diverges so far that the original is a small subset. Beyond the features
57
+ above, it ships **37 bug fixes** that make the core production-stable — state
58
+ persistence across restarts, real token reporting (was returning 0), GC
59
+ deactivation, reversed-boundary auto-recovery, 268× logger/tokenizer speedup,
60
+ dialog-role confusion fixes, and skipping OpenCode's internal title/summary
61
+ agents so session titles keep generating. Core deltas vs the original:
62
+
63
+ | | DCP (original) | ACP |
28
64
  |---|---|---|
29
- | **Max stable session** | ~200 messages | 10,000+ messages |
30
- | **Per-turn overhead** | 20 -- 50 seconds | ~90ms |
31
- | **State persistence** | Lost on restart | Survives restart |
32
- | **GC effectiveness** | Never deactivates old blocks | Age-based auto-cleanup |
33
- | **Compress reliability** | Fails on edge cases, model gives up | Auto-recovers reversed boundaries |
65
+ | **Max stable session** | ~200 messages | 10,000+ |
66
+ | **Per-turn overhead** | 20 50 s | ~90 ms |
67
+ | **Model-driven decompress + block cleanup** | No | Yes |
68
+ | **State survives restart** | No | Yes |
69
+
70
+ ---
71
+
72
+ ## Proven at scale
73
+
74
+ ACP is battle-tested on real, long-running engineering sessions. Aggregate stats
75
+ from a single developer workstation (1,445 sessions, 69,097 model turns):
76
+
77
+ | Metric | Value |
78
+ |--------|-------|
79
+ | Total tokens processed (incl. prompt-cache reads) | **6.17 billion** |
80
+ | Billable tokens (input + output + reasoning) | 828 million |
81
+ | Prompt-cache hit ratio (average) | ~87% |
82
+ | Compression blocks created (all-time) | 4,894 |
83
+
84
+ Two representative heavy sessions (anonymized) — the headline number is **total
85
+ tokens pushed through the model**, not peak context:
34
86
 
35
- > **Active** means the model proactively decides *when* and *what* to compress -- as opposed to passive approaches that only react when context hits a hard limit. The model uses the `compress` tool to produce high-fidelity summaries of completed conversation segments, preserving important details while freeing context space. This is superior to passive truncation because the model controls what information to keep.
87
+ | Session | Span | Turns | **Total tokens** | Cache hit | Context p50 | Context p95 | Peak |
88
+ |---------|------|-------|------------------|-----------|-------------|-------------|------|
89
+ | Session 1 | 6 days | 2,694 | **582 M** | 86.2% | 1.2 K (<1%) | 251 K (25%) | 488 K (49%) |
90
+ | Session 2 | 2 days | 1,536 | **463 M** | 89.0% | 1.8 K (<1%) | 335 K (34%) | 769 K (77%) |
36
91
 
37
- Key fixes include: state persistence across restarts, token usage reporting (was returning 0), summary message ID resolution, GC age-based deactivation, 268x logger/tokenizer speedup, auto-swap for reversed compress boundaries, and aging warning suppression at low context usage.
92
+ The picture this paints: the *median* turn is tiny (short tool exchanges), but
93
+ the heavy turns regularly reach **250K–335K context (25–34% of the 1M window)**
94
+ and occasionally spike to **49–77%**. Even at those spikes the prefix-cache hit
95
+ ratio stays near **90%** — the payoff of ACP's cache-aware compression, which
96
+ prunes from the tail (preserving the shared prefix) instead of truncating blindly.
38
97
 
39
98
  ---
40
99
 
@@ -79,6 +138,19 @@ Identifies repeated tool calls (same tool, same arguments) and keeps only the mo
79
138
 
80
139
  Prunes inputs from errored tool calls after a configurable number of turns (default: 4). Error messages are preserved; only the potentially large input content is removed. Recalculated on compress tool use.
81
140
 
141
+ ### Deferred Block Cleanup (mark_block)
142
+
143
+ Besides `compress` and `decompress`, ACP exposes `mark_block` / `unmark_block` tools. These give the model a **zero-cache-cost** way to flag compressed blocks it no longer needs in detail, deferring all consolidation into a single operation when context pressure rises.
144
+
145
+ - **`mark_block`** flags a block for later cleanup. The block stays fully active and keeps serving prompt-cache hits — nothing changes immediately.
146
+ - When context usage crosses configurable thresholds, ACP consolidates all marked blocks into one summary in a **single cache break** (instead of losing them one at a time):
147
+ - **Low (default 60%)**: a nudge reminds the model that marked blocks can be merged.
148
+ - **High (default 75%)**: all marked blocks are auto merge-compressed into one.
149
+ - **Force (default 90%)**: all old-gen blocks are merged regardless of marks — a last resort before age-based GC truncation.
150
+ - **`unmark_block`** removes the flag if the model changes its mind.
151
+
152
+ This is purely additive — existing GC behavior is retained as the ultimate fallback at 100%, and merged blocks still respond to `decompress`. Thresholds are configurable under `gc.batchCleanup`.
153
+
82
154
  ---
83
155
 
84
156
  ## Commands
@@ -131,7 +203,7 @@ Each level overrides the previous, so project settings take priority over global
131
203
 
132
204
  ```jsonc
133
205
  {
134
- "$schema": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/master/dcp.schema.json",
206
+ "$schema": "https://raw.githubusercontent.com/ranxianglei/opencode-acp/master/dcp.schema.json",
135
207
  // Enable or disable the plugin
136
208
  "enabled": true,
137
209
  // Automatically update npm-installed ACP when a newer npm latest is available.
@@ -239,6 +311,28 @@ Each level overrides the previous, so project settings take priority over global
239
311
  "protectedTools": [],
240
312
  },
241
313
  },
314
+ // Garbage collection and batch cleanup
315
+ "gc": {
316
+ "algorithm": "truncate",
317
+ // young → old generation promotion after this many survivals
318
+ "promotionThreshold": 5,
319
+ // deactivate a block after this many survivals
320
+ "maxBlockAge": 15,
321
+ // truncate old-gen summaries exceeding this length (chars)
322
+ "maxOldGenSummaryLength": 3000,
323
+ // run major GC when context usage exceeds this
324
+ "majorGcThresholdPercent": "100%",
325
+ // Three-tier batch merge-cleanup for blocks flagged via mark_block.
326
+ // Accepts a number or "X%" of the model context window.
327
+ "batchCleanup": {
328
+ // At/above this usage, remind the model about marked blocks
329
+ "lowThreshold": "60%",
330
+ // At/above this usage, auto merge-compress all marked blocks into one
331
+ "highThreshold": "75%",
332
+ // At/above this usage, force-merge all old-gen blocks (before GC)
333
+ "forceThreshold": "90%",
334
+ },
335
+ },
242
336
  }
243
337
  ```
244
338
 
@@ -266,11 +360,11 @@ To reset an override, delete the matching file from your overrides directory.
266
360
  ### Protected Tools
267
361
 
268
362
  By default, these tools are always protected from pruning:
269
- `task`, `skill`, `todowrite`, `todoread`, `compress`, `batch`, `plan_enter`, `plan_exit`, `write`, `edit`
363
+ `task`, `skill`, `todowrite`, `todoread`, `compress`, `decompress`, `mark_block`, `unmark_block`, `batch`, `plan_enter`, `plan_exit`, `write`, `edit`
270
364
 
271
365
  The `protectedTools` arrays in `commands` and `strategies` add to this default list.
272
366
 
273
- For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are appended to the compressed summary. By default it includes `task`, `skill`, `todowrite`, and `todoread`.
367
+ For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are appended to the compressed summary. By default it includes `task`, `skill`, `todowrite`, `todoread`, and `decompress`.
274
368
 
275
369
  ---
276
370
 
@@ -317,7 +411,7 @@ ACP auto-migrates config from `dcp.jsonc` to `acp.jsonc` and prompts from `dcp-p
317
411
  ---
318
412
 
319
413
  <details>
320
- <summary><strong>Bug Fixes (35 total)</strong> -- applied on top of DCP v3.1.11</summary>
414
+ <summary><strong>Bug Fixes (37 total)</strong> -- applied on top of DCP v3.1.11</summary>
321
415
 
322
416
  | # | Severity | Summary |
323
417
  |---|----------|---------|
@@ -345,6 +439,8 @@ ACP auto-migrates config from `dcp.jsonc` to `acp.jsonc` and prompts from `dcp-p
345
439
  | 22 | HIGH | compress throws hard error on reversed block boundaries -- model gives up |
346
440
  | 23--34 | MEDIUM | Various fixes for dedup, purge errors, schema validation, hook timing, etc. |
347
441
  | 35 | HIGH | Aging warnings shown at low context usage (<50%) -- triggers unnecessary compress, wastes tokens |
442
+ | 36 | HIGH | Compression summary emitted as a standalone user message before the user's real turn -- model reads its own prior assistant output as user input, causing dialog role confusion / self-Q&A loops |
443
+ | 37 | HIGH | Message-transform pipeline runs on OpenCode's hidden title/summary/compaction agent requests -- corrupts the request and shared session state, breaking session title generation |
348
444
 
349
445
  For the complete list with root cause analysis, see the [bug tracker](https://github.com/ranxianglei/opencode-acp/issues).
350
446
 
package/README.zh-CN.md CHANGED
@@ -22,19 +22,50 @@
22
22
 
23
23
  ## 为什么选择 ACP
24
24
 
25
- ACP 是 [DCP](https://github.com/Tarquinen/opencode-dynamic-context-pruning) 的强化分支,已应用 **35 项错误修复**。它将上下文管理从一种被动、易崩溃的机制,转变为足以稳定运行于生产环境的方案。
25
+ ACP 是 OpenCode 的**模型驱动上下文管理**。它不是被动地在硬性上限处截断,而是暴露工具让模型自行决定**何时**压缩以及**压缩什么** —— 对已完成的片段生成高保真摘要,在释放上下文空间的同时保留重要细节。模型掌握保留哪些信息,这严格优于盲目截断。
26
26
 
27
- | | DCP(原版) | ACP(本分支) |
27
+ ### ACP 有何不同
28
+
29
+ - **完整的块生命周期,模型自主。** 模型可以 `compress` 把一段范围压成摘要、`decompress` 按需恢复任意块、`mark_block` / `unmark_block` 标记延迟删除。模型拥有自己的上下文生命周期 —— 而不是"建个块然后指望 GC 去处理"。
30
+ - **缓存感知设计。** 摘要合并进已有的用户轮次,批量清理只产生*单次*缓存打断,所以即便会话跑到 70%+ 上下文占用,前缀缓存命中率仍接近 **90%**(见 [实战验证](#实战验证))。
31
+ - **压力感知 GC。** 不是盲目按年龄截断(会静默丢失 task ID、文件路径、决策等重要信息),而是优先整合被标记的块,把盲目截断降级为 100% 时的最后兜底。
32
+ - **两种压缩模式。** *Range* 模式(连续片段 → 块摘要)和 *message* 模式(针对分散内容的精准单消息摘要)。
33
+ - **受保护内容。** 你标记保护的工具输出、文件模式、用户消息会被注入摘要,关键信息永不丢失。
34
+ - **自动策略。** 去重(相同工具+参数 → 只留最后一次)和清除错误(N 轮后丢弃出错的输入),在 compress 时重算 —— 不是每轮。
35
+ - **生产级配置。** 三层合并(全局 → 配置目录 → 项目)+ 每模型上下文上限覆盖 + 用户可编辑 prompt。
36
+
37
+ ### DCP 的强化分支
38
+
39
+ ACP 起初是 [DCP](https://github.com/Tarquinen/opencode-dynamic-context-pruning) 的分支,如今分歧之大已让原版只相当于一个小子集。除上述特性外,还包含 **37 项 bug 修复**让核心达到生产稳定 —— 跨重启状态持久化、真实 token 上报(此前返回 0)、GC 停用、反转边界自动恢复、268 倍日志/tokenizer 加速、对话角色混乱修复,以及跳过 OpenCode 内置 title/summary agent 恢复标题生成。相对原版的核心差异:
40
+
41
+ | | DCP(原版) | ACP |
28
42
  |---|---|---|
29
- | **最大稳定会话** | ~200 条消息 | 10,000+ 条消息 |
30
- | **每轮开销** | 20 -- 50 秒 | ~90ms |
31
- | **状态持久化** | 重启后丢失 | 重启后保留 |
32
- | **GC 有效性** | 从不停用旧块 | 基于年龄的自动清理 |
33
- | **Compress 可靠性** | 边界情况失败,模型放弃 | 自动恢复反转的边界 |
43
+ | **最大稳定会话** | ~200 条消息 | 10,000+ |
44
+ | **每轮开销** | 20 50 秒 | ~90 ms |
45
+ | **模型自主解压 + 块清理** | | |
46
+ | **状态跨重启保留** | | |
47
+
48
+ ---
49
+
50
+ ## 实战验证
51
+
52
+ ACP 在真实的长周期工程会话上经受过检验。单台开发机的累计统计(1445 个会话,69,097 个模型回合):
34
53
 
35
- > **Active(主动)** 意味着模型主动决定*何时*压缩以及*压缩什么* 与被动方式不同,被动方式仅在上下文触及硬性限制时才做出反应。模型使用 `compress` 工具对已完成的对话片段生成高保真摘要,在保留重要细节的同时释放上下文空间。这优于被动截断,因为模型可以自行控制保留哪些信息。
54
+ | 指标 | 数值 |
55
+ |------|------|
56
+ | 总处理 token(含 prompt-cache 读取) | **61.7 亿** |
57
+ | 计费 token(input + output + reasoning) | 8.28 亿 |
58
+ | prompt-cache 命中率(平均) | ~87% |
59
+ | 累计创建压缩 block | 4,894 个 |
36
60
 
37
- 主要修复包括:跨重启状态持久化、token 用量报告(此前返回 0)、摘要消息 ID 解析、基于年龄的 GC 停用、268 倍的日志/tokenizer 加速、压缩边界反转的自动交换,以及低上下文使用率时的老化警告抑制。
61
+ 两个有代表性的重负载会话(已匿名化)—— 头条数字是**流经模型的累计 token**,而不是峰值上下文:
62
+
63
+ | 会话 | 跨度 | 回合数 | **累计 token** | 缓存命中 | 上下文 p50 | 上下文 p95 | 峰值 |
64
+ |------|------|--------|----------------|----------|------------|------------|------|
65
+ | 会话一 | 6 天 | 2,694 | **5.82 亿** | 86.2% | 1.2 K(<1%) | 25.1 万(25%) | 48.8 万(49%) |
66
+ | 会话二 | 2 天 | 1,536 | **4.63 亿** | 89.0% | 1.8 K(<1%) | 33.5 万(34%) | 76.9 万(77%) |
67
+
68
+ 这组数据说明:*中位*回合很小(短小的工具交互),但重量级回合经常到 **25–33 万上下文(1M 窗口的 25–34%)**,偶尔飙到 **49–77%**。即便在这些尖峰处,前缀缓存命中率仍接近 **90%** —— 这正是 ACP 缓存感知压缩的价值:从尾部裁剪(保留共享前缀),而非盲目截断。
38
69
 
39
70
  ---
40
71
 
@@ -79,6 +110,19 @@ ACP 支持两种压缩模式:
79
110
 
80
111
  在可配置的轮次后(默认:4 轮)剪除出错工具调用的输入。错误消息被保留;仅移除可能很大的输入内容。在 `compress` 工具使用时重新计算。
81
112
 
113
+ ### 延迟块清理(mark_block)
114
+
115
+ 除 `compress` 和 `decompress` 外,ACP 还提供 `mark_block` / `unmark_block` 工具。它们让模型能以**零缓存成本**标记不再需要详细内容的压缩块,将所有整合推迟到上下文压力升高时一次性完成。
116
+
117
+ - **`mark_block`** 将一个块标记为稍后清理。该块仍完全活跃并继续服务提示缓存命中 —— 立即没有任何变化。
118
+ - 当上下文使用率越过可配置阈值时,ACP 会在**单次缓存打断**中将所有已标记块整合为一个摘要(而非逐个丢失):
119
+ - **低阈值(默认 60%)**:提醒模型已标记的块可以合并。
120
+ - **高阈值(默认 75%)**:所有已标记块被自动合并压缩为一个。
121
+ - **强制阈值(默认 90%)**:无论是否标记,所有老年代块都被合并 —— 这是基于年龄的 GC 截断前的最后手段。
122
+ - **`unmark_block`** 在模型改变主意时移除标记。
123
+
124
+ 此机制纯粹是附加的 —— 既有的 GC 行为保留为 100% 时的最终兜底,合并后的块仍可被 `decompress` 恢复。阈值可在 `gc.batchCleanup` 下配置。
125
+
82
126
  ---
83
127
 
84
128
  ## 命令
@@ -131,7 +175,7 @@ ACP 使用自己的配置文件,按以下顺序搜索:
131
175
 
132
176
  ```jsonc
133
177
  {
134
- "$schema": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/master/dcp.schema.json",
178
+ "$schema": "https://raw.githubusercontent.com/ranxianglei/opencode-acp/master/dcp.schema.json",
135
179
  // Enable or disable the plugin
136
180
  "enabled": true,
137
181
  // Automatically update npm-installed ACP when a newer npm latest is available.
@@ -239,6 +283,28 @@ ACP 使用自己的配置文件,按以下顺序搜索:
239
283
  "protectedTools": [],
240
284
  },
241
285
  },
286
+ // 垃圾回收与批量清理
287
+ "gc": {
288
+ "algorithm": "truncate",
289
+ // 存活此次数后从新生代晋升为老年代
290
+ "promotionThreshold": 5,
291
+ // 存活此次数后停用该块
292
+ "maxBlockAge": 15,
293
+ // 截断超过此长度(字符)的老年代摘要
294
+ "maxOldGenSummaryLength": 3000,
295
+ // 上下文使用率超过此值时执行主 GC
296
+ "majorGcThresholdPercent": "100%",
297
+ // 通过 mark_block 标记的块的三级批量合并清理阈值。
298
+ // 接受数字或 "X%"(模型上下文窗口的百分比)。
299
+ "batchCleanup": {
300
+ // 达到此使用率时,提醒模型已标记的块
301
+ "lowThreshold": "60%",
302
+ // 达到此使用率时,自动将所有已标记块合并压缩为一个
303
+ "highThreshold": "75%",
304
+ // 达到此使用率时,强制合并所有老年代块(GC 之前)
305
+ "forceThreshold": "90%",
306
+ },
307
+ },
242
308
  }
243
309
  ```
244
310
 
@@ -266,11 +332,11 @@ ACP 暴露六个可编辑的 prompt:
266
332
  ### 受保护工具
267
333
 
268
334
  默认情况下,以下工具始终受保护不被剪枝:
269
- `task`、`skill`、`todowrite`、`todoread`、`compress`、`batch`、`plan_enter`、`plan_exit`、`write`、`edit`
335
+ `task`、`skill`、`todowrite`、`todoread`、`compress`、`decompress`、`mark_block`、`unmark_block`、`batch`、`plan_enter`、`plan_exit`、`write`、`edit`
270
336
 
271
337
  `commands` 和 `strategies` 中的 `protectedTools` 数组会添加到此默认列表。
272
338
 
273
- 对于 `compress` 工具,`compress.protectedTools` 确保特定工具的输出会被附加到压缩摘要中。默认包含 `task`、`skill`、`todowrite` 和 `todoread`。
339
+ 对于 `compress` 工具,`compress.protectedTools` 确保特定工具的输出会被附加到压缩摘要中。默认包含 `task`、`skill`、`todowrite`、`todoread` 和 `decompress`。
274
340
 
275
341
  ---
276
342
 
@@ -317,7 +383,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
317
383
  ---
318
384
 
319
385
  <details>
320
- <summary><strong>错误修复(共 35 项)</strong> — 基于 DCP v3.1.11</summary>
386
+ <summary><strong>错误修复(共 37 项)</strong> — 基于 DCP v3.1.11</summary>
321
387
 
322
388
  | # | 严重程度 | 摘要 |
323
389
  |---|----------|------|
@@ -345,6 +411,8 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
345
411
  | 22 | 高 | compress 在块边界反转时抛出硬错误 — 模型放弃 |
346
412
  | 23--34 | 中 | 去重、错误清除、schema 验证、hook 时序等方面的多项修复 |
347
413
  | 35 | 高 | 在低上下文使用率(<50%)时显示老化警告 — 触发不必要的 compress,浪费 token |
414
+ | 36 | 高 | 压缩摘要作为独立的 user 消息插入在用户真实发言之前 — 模型把自己先前的 assistant 输出误读为用户输入,导致对话角色混乱 / 自问自答循环 |
415
+ | 37 | 高 | 消息转换管线对 OpenCode 隐藏的 title/summary/compaction agent 请求也运行 — 污染请求并破坏共享会话状态,导致会话标题生成失效 |
348
416
 
349
417
  完整列表及根因分析,请参见 [Bug Tracker](https://github.com/ranxianglei/opencode-acp/issues)。
350
418
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAyBjD,QAAA,MAAM,MAAM,EAAE,MAkHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA2BjD,QAAA,MAAM,MAAM,EAAE,MAoHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}