@p4r4d0xb0x/opencode-dcp 3.0.5

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.
Files changed (203) hide show
  1. package/LICENSE +619 -0
  2. package/README.md +229 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +88 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/auth.d.ts +4 -0
  8. package/dist/lib/auth.d.ts.map +1 -0
  9. package/dist/lib/auth.js +32 -0
  10. package/dist/lib/auth.js.map +1 -0
  11. package/dist/lib/commands/context.d.ts +52 -0
  12. package/dist/lib/commands/context.d.ts.map +1 -0
  13. package/dist/lib/commands/context.js +241 -0
  14. package/dist/lib/commands/context.js.map +1 -0
  15. package/dist/lib/commands/decompress.d.ts +12 -0
  16. package/dist/lib/commands/decompress.d.ts.map +1 -0
  17. package/dist/lib/commands/decompress.js +159 -0
  18. package/dist/lib/commands/decompress.js.map +1 -0
  19. package/dist/lib/commands/help.d.ts +17 -0
  20. package/dist/lib/commands/help.d.ts.map +1 -0
  21. package/dist/lib/commands/help.js +52 -0
  22. package/dist/lib/commands/help.js.map +1 -0
  23. package/dist/lib/commands/manual.d.ts +22 -0
  24. package/dist/lib/commands/manual.d.ts.map +1 -0
  25. package/dist/lib/commands/manual.js +48 -0
  26. package/dist/lib/commands/manual.js.map +1 -0
  27. package/dist/lib/commands/recompress.d.ts +12 -0
  28. package/dist/lib/commands/recompress.d.ts.map +1 -0
  29. package/dist/lib/commands/recompress.js +137 -0
  30. package/dist/lib/commands/recompress.js.map +1 -0
  31. package/dist/lib/commands/stats.d.ts +15 -0
  32. package/dist/lib/commands/stats.d.ts.map +1 -0
  33. package/dist/lib/commands/stats.js +62 -0
  34. package/dist/lib/commands/stats.js.map +1 -0
  35. package/dist/lib/commands/sweep.d.ts +23 -0
  36. package/dist/lib/commands/sweep.d.ts.map +1 -0
  37. package/dist/lib/commands/sweep.js +194 -0
  38. package/dist/lib/commands/sweep.js.map +1 -0
  39. package/dist/lib/config.d.ts +72 -0
  40. package/dist/lib/config.d.ts.map +1 -0
  41. package/dist/lib/config.js +766 -0
  42. package/dist/lib/config.js.map +1 -0
  43. package/dist/lib/hooks.d.ts +33 -0
  44. package/dist/lib/hooks.d.ts.map +1 -0
  45. package/dist/lib/hooks.js +195 -0
  46. package/dist/lib/hooks.js.map +1 -0
  47. package/dist/lib/host-permissions.d.ts +11 -0
  48. package/dist/lib/host-permissions.d.ts.map +1 -0
  49. package/dist/lib/host-permissions.js +58 -0
  50. package/dist/lib/host-permissions.js.map +1 -0
  51. package/dist/lib/logger.d.ts +31 -0
  52. package/dist/lib/logger.d.ts.map +1 -0
  53. package/dist/lib/logger.js +204 -0
  54. package/dist/lib/logger.js.map +1 -0
  55. package/dist/lib/message-ids.d.ts +19 -0
  56. package/dist/lib/message-ids.d.ts.map +1 -0
  57. package/dist/lib/message-ids.js +112 -0
  58. package/dist/lib/message-ids.js.map +1 -0
  59. package/dist/lib/messages/index.d.ts +7 -0
  60. package/dist/lib/messages/index.d.ts.map +1 -0
  61. package/dist/lib/messages/index.js +7 -0
  62. package/dist/lib/messages/index.js.map +1 -0
  63. package/dist/lib/messages/inject/inject.d.ts +7 -0
  64. package/dist/lib/messages/inject/inject.d.ts.map +1 -0
  65. package/dist/lib/messages/inject/inject.js +131 -0
  66. package/dist/lib/messages/inject/inject.js.map +1 -0
  67. package/dist/lib/messages/inject/subagent-results.d.ts +4 -0
  68. package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -0
  69. package/dist/lib/messages/inject/subagent-results.js +58 -0
  70. package/dist/lib/messages/inject/subagent-results.js.map +1 -0
  71. package/dist/lib/messages/inject/utils.d.ts +25 -0
  72. package/dist/lib/messages/inject/utils.d.ts.map +1 -0
  73. package/dist/lib/messages/inject/utils.js +198 -0
  74. package/dist/lib/messages/inject/utils.js.map +1 -0
  75. package/dist/lib/messages/prune.d.ts +5 -0
  76. package/dist/lib/messages/prune.d.ts.map +1 -0
  77. package/dist/lib/messages/prune.js +177 -0
  78. package/dist/lib/messages/prune.js.map +1 -0
  79. package/dist/lib/messages/reasoning-strip.d.ts +8 -0
  80. package/dist/lib/messages/reasoning-strip.d.ts.map +1 -0
  81. package/dist/lib/messages/reasoning-strip.js +33 -0
  82. package/dist/lib/messages/reasoning-strip.js.map +1 -0
  83. package/dist/lib/messages/sync.d.ts +4 -0
  84. package/dist/lib/messages/sync.d.ts.map +1 -0
  85. package/dist/lib/messages/sync.js +93 -0
  86. package/dist/lib/messages/sync.js.map +1 -0
  87. package/dist/lib/messages/utils.d.ts +21 -0
  88. package/dist/lib/messages/utils.d.ts.map +1 -0
  89. package/dist/lib/messages/utils.js +118 -0
  90. package/dist/lib/messages/utils.js.map +1 -0
  91. package/dist/lib/prompts/compress.d.ts +2 -0
  92. package/dist/lib/prompts/compress.d.ts.map +1 -0
  93. package/dist/lib/prompts/compress.js +85 -0
  94. package/dist/lib/prompts/compress.js.map +1 -0
  95. package/dist/lib/prompts/context-limit-nudge.d.ts +2 -0
  96. package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -0
  97. package/dist/lib/prompts/context-limit-nudge.js +25 -0
  98. package/dist/lib/prompts/context-limit-nudge.js.map +1 -0
  99. package/dist/lib/prompts/index.d.ts +3 -0
  100. package/dist/lib/prompts/index.d.ts.map +1 -0
  101. package/dist/lib/prompts/index.js +19 -0
  102. package/dist/lib/prompts/index.js.map +1 -0
  103. package/dist/lib/prompts/internal-overlays.d.ts +5 -0
  104. package/dist/lib/prompts/internal-overlays.d.ts.map +1 -0
  105. package/dist/lib/prompts/internal-overlays.js +43 -0
  106. package/dist/lib/prompts/internal-overlays.js.map +1 -0
  107. package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
  108. package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
  109. package/dist/lib/prompts/iteration-nudge.js +9 -0
  110. package/dist/lib/prompts/iteration-nudge.js.map +1 -0
  111. package/dist/lib/prompts/store.d.ts +24 -0
  112. package/dist/lib/prompts/store.d.ts.map +1 -0
  113. package/dist/lib/prompts/store.js +346 -0
  114. package/dist/lib/prompts/store.js.map +1 -0
  115. package/dist/lib/prompts/system.d.ts +2 -0
  116. package/dist/lib/prompts/system.d.ts.map +1 -0
  117. package/dist/lib/prompts/system.js +30 -0
  118. package/dist/lib/prompts/system.js.map +1 -0
  119. package/dist/lib/prompts/turn-nudge.d.ts +2 -0
  120. package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
  121. package/dist/lib/prompts/turn-nudge.js +12 -0
  122. package/dist/lib/prompts/turn-nudge.js.map +1 -0
  123. package/dist/lib/protected-patterns.d.ts +5 -0
  124. package/dist/lib/protected-patterns.d.ts.map +1 -0
  125. package/dist/lib/protected-patterns.js +107 -0
  126. package/dist/lib/protected-patterns.js.map +1 -0
  127. package/dist/lib/shared-utils.d.ts +8 -0
  128. package/dist/lib/shared-utils.d.ts.map +1 -0
  129. package/dist/lib/shared-utils.js +30 -0
  130. package/dist/lib/shared-utils.js.map +1 -0
  131. package/dist/lib/state/index.d.ts +4 -0
  132. package/dist/lib/state/index.d.ts.map +1 -0
  133. package/dist/lib/state/index.js +4 -0
  134. package/dist/lib/state/index.js.map +1 -0
  135. package/dist/lib/state/persistence.d.ts +41 -0
  136. package/dist/lib/state/persistence.d.ts.map +1 -0
  137. package/dist/lib/state/persistence.js +174 -0
  138. package/dist/lib/state/persistence.js.map +1 -0
  139. package/dist/lib/state/state.d.ts +7 -0
  140. package/dist/lib/state/state.d.ts.map +1 -0
  141. package/dist/lib/state/state.js +134 -0
  142. package/dist/lib/state/state.js.map +1 -0
  143. package/dist/lib/state/tool-cache.d.ts +13 -0
  144. package/dist/lib/state/tool-cache.d.ts.map +1 -0
  145. package/dist/lib/state/tool-cache.js +69 -0
  146. package/dist/lib/state/tool-cache.js.map +1 -0
  147. package/dist/lib/state/types.d.ts +90 -0
  148. package/dist/lib/state/types.d.ts.map +1 -0
  149. package/dist/lib/state/types.js +2 -0
  150. package/dist/lib/state/types.js.map +1 -0
  151. package/dist/lib/state/utils.d.ts +18 -0
  152. package/dist/lib/state/utils.d.ts.map +1 -0
  153. package/dist/lib/state/utils.js +196 -0
  154. package/dist/lib/state/utils.js.map +1 -0
  155. package/dist/lib/strategies/deduplication.d.ts +10 -0
  156. package/dist/lib/strategies/deduplication.d.ts.map +1 -0
  157. package/dist/lib/strategies/deduplication.js +100 -0
  158. package/dist/lib/strategies/deduplication.js.map +1 -0
  159. package/dist/lib/strategies/index.d.ts +4 -0
  160. package/dist/lib/strategies/index.d.ts.map +1 -0
  161. package/dist/lib/strategies/index.js +4 -0
  162. package/dist/lib/strategies/index.js.map +1 -0
  163. package/dist/lib/strategies/purge-errors.d.ts +13 -0
  164. package/dist/lib/strategies/purge-errors.d.ts.map +1 -0
  165. package/dist/lib/strategies/purge-errors.js +62 -0
  166. package/dist/lib/strategies/purge-errors.js.map +1 -0
  167. package/dist/lib/strategies/supersede-writes.d.ts +13 -0
  168. package/dist/lib/strategies/supersede-writes.d.ts.map +1 -0
  169. package/dist/lib/strategies/supersede-writes.js +94 -0
  170. package/dist/lib/strategies/supersede-writes.js.map +1 -0
  171. package/dist/lib/strategies/utils.d.ts +21 -0
  172. package/dist/lib/strategies/utils.d.ts.map +1 -0
  173. package/dist/lib/strategies/utils.js +128 -0
  174. package/dist/lib/strategies/utils.js.map +1 -0
  175. package/dist/lib/subagents/subagent-results.d.ts +5 -0
  176. package/dist/lib/subagents/subagent-results.d.ts.map +1 -0
  177. package/dist/lib/subagents/subagent-results.js +52 -0
  178. package/dist/lib/subagents/subagent-results.js.map +1 -0
  179. package/dist/lib/tools/compress.d.ts +4 -0
  180. package/dist/lib/tools/compress.d.ts.map +1 -0
  181. package/dist/lib/tools/compress.js +110 -0
  182. package/dist/lib/tools/compress.js.map +1 -0
  183. package/dist/lib/tools/index.d.ts +3 -0
  184. package/dist/lib/tools/index.d.ts.map +1 -0
  185. package/dist/lib/tools/index.js +2 -0
  186. package/dist/lib/tools/index.js.map +1 -0
  187. package/dist/lib/tools/types.d.ts +13 -0
  188. package/dist/lib/tools/types.d.ts.map +1 -0
  189. package/dist/lib/tools/types.js +2 -0
  190. package/dist/lib/tools/types.js.map +1 -0
  191. package/dist/lib/tools/utils.d.ts +80 -0
  192. package/dist/lib/tools/utils.d.ts.map +1 -0
  193. package/dist/lib/tools/utils.js +675 -0
  194. package/dist/lib/tools/utils.js.map +1 -0
  195. package/dist/lib/ui/notification.d.ts +10 -0
  196. package/dist/lib/ui/notification.d.ts.map +1 -0
  197. package/dist/lib/ui/notification.js +182 -0
  198. package/dist/lib/ui/notification.js.map +1 -0
  199. package/dist/lib/ui/utils.d.ts +10 -0
  200. package/dist/lib/ui/utils.d.ts.map +1 -0
  201. package/dist/lib/ui/utils.js +255 -0
  202. package/dist/lib/ui/utils.js.map +1 -0
  203. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # Dynamic Context Pruning Plugin
2
+
3
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/dansmolsky)
4
+ [![npm version](https://img.shields.io/npm/v/@tarquinen/opencode-dcp.svg)](https://www.npmjs.com/package/@tarquinen/opencode-dcp)
5
+
6
+ Automatically reduces token usage in OpenCode by managing conversation context.
7
+
8
+ ![DCP in action](assets/images/dcp-demo9.png)
9
+
10
+ ## Installation
11
+
12
+ Add to your OpenCode config:
13
+
14
+ ```jsonc
15
+ // opencode.jsonc
16
+ {
17
+ "plugin": ["@tarquinen/opencode-dcp@latest"],
18
+ }
19
+ ```
20
+
21
+ Using `@latest` ensures you always get the newest version automatically when OpenCode starts.
22
+
23
+ Restart OpenCode. The plugin will automatically start optimizing your sessions.
24
+
25
+ ## How It Works
26
+
27
+ DCP reduces context size through a compress tool and automatic cleanup. Your session history is never modified — DCP replaces pruned content with placeholders before sending requests to your LLM.
28
+
29
+ ### Compress
30
+
31
+ Compress is a tool exposed to your model that selects a conversation range and replaces it with a technical summary. You can think of this as a much smarter version of Opencode's compaction process. Instead of triggering statically when your session reaches its maximum context and on the entire coding session, Compress allows the model to pick when to activate based on task completion, and to only compress a subset of messages containing the completed task. This allows the summaries replacing the session content to be much more focused and precise than Opencode's native compaction.
32
+
33
+ When a new compression overlaps an earlier one, the earlier summary is nested inside the new one — so information is preserved through layers of compression rather than diluted away. Additionally, protected tool outputs (such as subagents and skills) and protected file patterns are always kept in compression summaries, ensuring that the most important information is never lost. You can also enable `protectUserMessages` to preserve your messages verbatim during compression, though note that large prompts (e.g. copy-pasting log files in the prompt) will then never be compressed away.
34
+
35
+ ### Deduplication
36
+
37
+ Identifies repeated tool calls (same tool, same arguments) and keeps only the most recent output. Recalculated when the compress tool runs, so prompt cache is only impacted alongside compression.
38
+
39
+ ### Purge Errors
40
+
41
+ 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.
42
+
43
+ ## Configuration
44
+
45
+ DCP uses its own config file, searched in order:
46
+
47
+ 1. Global: `~/.config/opencode/dcp.jsonc` (or `dcp.json`), created automatically on first run
48
+ 2. Custom config directory: `$OPENCODE_CONFIG_DIR/dcp.jsonc` (or `dcp.json`), if `OPENCODE_CONFIG_DIR` is set
49
+ 3. Project: `.opencode/dcp.jsonc` (or `dcp.json`) in your project's `.opencode` directory
50
+
51
+ Each level overrides the previous, so project settings take priority over global. Restart OpenCode after making config changes.
52
+
53
+ > [!IMPORTANT]
54
+ > Defaults are applied automatically. Expand this if you want to review or override settings.
55
+
56
+ <details>
57
+ <summary><strong>Default Configuration</strong> (click to expand)</summary>
58
+
59
+ ```jsonc
60
+ {
61
+ "$schema": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/master/dcp.schema.json",
62
+ // Enable or disable the plugin
63
+ "enabled": true,
64
+ // Enable debug logging to ~/.config/opencode/logs/dcp/
65
+ "debug": false,
66
+ // Notification display: "off", "minimal", or "detailed"
67
+ "pruneNotification": "detailed",
68
+ // Notification type: "chat" (in-conversation) or "toast" (system toast)
69
+ "pruneNotificationType": "chat",
70
+ // Slash commands configuration
71
+ "commands": {
72
+ "enabled": true,
73
+ // Additional tools to protect from pruning via commands (e.g., /dcp sweep)
74
+ "protectedTools": [],
75
+ },
76
+ // Manual mode: disables autonomous context management,
77
+ // tools only run when explicitly triggered via /dcp commands
78
+ "manualMode": {
79
+ "enabled": false,
80
+ // When true, automatic cleanup (deduplication, purgeErrors)
81
+ // still runs even in manual mode
82
+ "automaticStrategies": true,
83
+ },
84
+ // Protect from pruning for <turns> message turns past tool invocation
85
+ "turnProtection": {
86
+ "enabled": false,
87
+ "turns": 4,
88
+ },
89
+ // Experimental settings
90
+ "experimental": {
91
+ // Allow DCP processing in subagent sessions
92
+ "allowSubAgents": false,
93
+ // Enable user-editable prompt overrides under dcp-prompts directories
94
+ // When false (default), prompt override files/directories are ignored
95
+ "customPrompts": false,
96
+ },
97
+ // Protect file operations from pruning via glob patterns
98
+ // Patterns match tool parameters.filePath (e.g. read/write/edit)
99
+ "protectedFilePatterns": [],
100
+ // Unified context compression tool and behavior settings
101
+ "compress": {
102
+ // Permission mode: "allow" (no prompt), "ask" (prompt), "deny" (tool not registered)
103
+ "permission": "allow",
104
+ // Show compression content in a chat notification
105
+ "showCompression": false,
106
+ // Soft upper threshold: above this, DCP keeps injecting strong
107
+ // compression nudges (based on nudgeFrequency), so compression is
108
+ // much more likely. Accepts: number or "X%" of model context window.
109
+ "maxContextLimit": 150000,
110
+ // Soft lower threshold for reminder nudges: below this, turn/iteration
111
+ // reminders are off (compression less likely). At/above this, reminders
112
+ // are on. Accepts: number or "X%" of model context window.
113
+ "minContextLimit": 50000,
114
+ // Optional per-model override for maxContextLimit by providerID/modelID.
115
+ // If present, this wins over the global maxContextLimit.
116
+ // Accepts: number or "X%".
117
+ // Example:
118
+ // "modelMaxLimits": {
119
+ // "openai/gpt-5.3-codex": 120000,
120
+ // "anthropic/claude-sonnet-4.6": "80%"
121
+ // },
122
+ // Optional per-model override for minContextLimit.
123
+ // If present, this wins over the global minContextLimit.
124
+ // "modelMinLimits": {
125
+ // "openai/gpt-5.3-codex": 50000,
126
+ // "anthropic/claude-sonnet-4.6": "25%"
127
+ // },
128
+ // How often the context-limit nudge fires (1 = every fetch, 5 = every 5th)
129
+ "nudgeFrequency": 5,
130
+ // Start adding compression reminders after this many
131
+ // messages have happened since the last user message
132
+ "iterationNudgeThreshold": 15,
133
+ // Controls how likely compression is after user messages
134
+ // ("strong" = more likely, "soft" = less likely)
135
+ "nudgeForce": "soft",
136
+ // Flat tool schema: improves tool call reliability but uglier in the TUI
137
+ "flatSchema": false,
138
+ // Tool names whose completed outputs are appended to the compression
139
+ "protectedTools": [],
140
+ // Preserve your messages during compression.
141
+ // Warning: large copy-pasted prompts will never be compressed away
142
+ "protectUserMessages": false,
143
+ },
144
+ // Automatic pruning strategies
145
+ "strategies": {
146
+ // Remove duplicate tool calls (same tool with same arguments)
147
+ "deduplication": {
148
+ "enabled": true,
149
+ // Additional tools to protect from pruning
150
+ "protectedTools": [],
151
+ },
152
+ // Prune write tool inputs when the file has been subsequently read
153
+ "supersedeWrites": {
154
+ "enabled": true,
155
+ },
156
+ // Prune tool inputs for errored tools after X turns
157
+ "purgeErrors": {
158
+ "enabled": true,
159
+ // Number of turns before errored tool inputs are pruned
160
+ "turns": 4,
161
+ // Additional tools to protect from pruning
162
+ "protectedTools": [],
163
+ },
164
+ },
165
+ }
166
+ ```
167
+
168
+ </details>
169
+
170
+ ### Commands
171
+
172
+ DCP provides a `/dcp` slash command:
173
+
174
+ - `/dcp` — Shows available DCP commands
175
+ - `/dcp context` — Shows a breakdown of your current session's token usage by category (system, user, assistant, tools, etc.) and how much has been saved through pruning.
176
+ - `/dcp stats` — Shows cumulative pruning statistics across all sessions.
177
+ - `/dcp sweep` — Prunes all tools since the last user message. Accepts an optional count: `/dcp sweep 10` prunes the last 10 tools. Respects `commands.protectedTools`.
178
+ - `/dcp manual [on|off]` — Toggle manual mode or set explicit state. When on, the AI will not autonomously use context management tools.
179
+ - `/dcp compress [focus]` — Trigger a single compress tool execution. Optional focus text directs what range to compress.
180
+ - `/dcp decompress <n>` — Restore a specific active compression by ID (for example `/dcp decompress 2`). Running without an argument shows available compression IDs, token sizes, and topics.
181
+ - `/dcp recompress <n>` — Re-apply a user-decompressed compression by ID (for example `/dcp recompress 2`). Running without an argument shows recompressible IDs, token sizes, and topics.
182
+
183
+ ### Prompt Overrides
184
+
185
+ DCP exposes five editable prompts:
186
+
187
+ - `system`
188
+ - `compress`
189
+ - `context-limit-nudge`
190
+ - `turn-nudge`
191
+ - `iteration-nudge`
192
+
193
+ This feature is disabled by default. Set `experimental.customPrompts` to `true` in your DCP config to activate it.
194
+
195
+ When enabled, managed defaults are written to `~/.config/opencode/dcp-prompts/defaults/` as plain-text prompt files. A single `README.md` in that directory explains each prompt and how to create overrides.
196
+
197
+ To customize behavior, add a file with the same name under an overrides directory and edit it as plain text.
198
+
199
+ To reset an override, delete the matching file from your overrides directory.
200
+
201
+ > [!NOTE]
202
+ > `compress` prompt changes apply after plugin restart because tool descriptions are registered at startup.
203
+
204
+ ### Protected Tools
205
+
206
+ By default, these tools are always protected from pruning:
207
+ `task`, `skill`, `todowrite`, `todoread`, `compress`, `batch`, `plan_enter`, `plan_exit`
208
+
209
+ The `protectedTools` arrays in `commands` and `strategies` add to this default list.
210
+
211
+ For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are appended to the compressed summary. It defaults to an empty array `[]` but always inherently protects `task`, `skill`, `todowrite`, and `todoread`.
212
+
213
+ ## Impact on Prompt Caching
214
+
215
+ LLM providers cache prompts based on exact prefix matching. When DCP prunes content, it changes messages, which invalidates cached prefixes from that point forward.
216
+
217
+ **Trade-off:** You lose some cache reads but gain token savings from reduced context size and fewer hallucinations from stale context. In most cases, especially in long sessions, the savings outweigh the cache miss cost.
218
+
219
+ > [!NOTE]
220
+ > In testing, cache hit rates were approximately 85% with DCP vs 90% without.
221
+
222
+ **No impact for:**
223
+
224
+ - **Request-based billing** — Providers like GitHub Copilot that charge per request, not tokens.
225
+ - **Uniform token pricing** — Providers like Cerebras that bill cached and uncached tokens at the same rate.
226
+
227
+ ## License
228
+
229
+ AGPL-3.0-or-later
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const plugin: Plugin;
3
+ export default plugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAmBjD,QAAA,MAAM,MAAM,EAAE,MAwHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ import { getConfig } from "./lib/config";
2
+ import { compressDisabledByOpencode, hasExplicitToolPermission, } from "./lib/host-permissions";
3
+ import { Logger } from "./lib/logger";
4
+ import { createSessionState } from "./lib/state";
5
+ import { createCompressTool } from "./lib/tools";
6
+ import { PromptStore } from "./lib/prompts/store";
7
+ import { createChatMessageTransformHandler, createCommandExecuteHandler, createSystemPromptHandler, createTextCompleteHandler, } from "./lib/hooks";
8
+ import { configureClientAuth, isSecureMode } from "./lib/auth";
9
+ const plugin = (async (ctx) => {
10
+ const config = getConfig(ctx);
11
+ if (!config.enabled) {
12
+ return {};
13
+ }
14
+ const logger = new Logger(config.debug);
15
+ const state = createSessionState();
16
+ const prompts = new PromptStore(logger, ctx.directory, config.experimental.customPrompts);
17
+ const hostPermissions = {
18
+ global: undefined,
19
+ agents: {},
20
+ };
21
+ if (isSecureMode()) {
22
+ configureClientAuth(ctx.client);
23
+ // logger.info("Secure mode detected, configured client authentication")
24
+ }
25
+ logger.info("DCP initialized", {
26
+ strategies: config.strategies,
27
+ });
28
+ return {
29
+ "experimental.chat.system.transform": createSystemPromptHandler(state, logger, config, prompts),
30
+ "experimental.chat.messages.transform": createChatMessageTransformHandler(ctx.client, state, logger, config, prompts, hostPermissions),
31
+ "chat.message": async (input, _output) => {
32
+ state.variant = input.variant;
33
+ logger.debug("Cached variant from chat.message hook", { variant: input.variant });
34
+ },
35
+ "experimental.text.complete": createTextCompleteHandler(),
36
+ "command.execute.before": createCommandExecuteHandler(ctx.client, state, logger, config, ctx.directory, hostPermissions),
37
+ tool: {
38
+ ...(config.compress.permission !== "deny" && {
39
+ compress: createCompressTool({
40
+ client: ctx.client,
41
+ state,
42
+ logger,
43
+ config,
44
+ workingDirectory: ctx.directory,
45
+ prompts,
46
+ }),
47
+ }),
48
+ },
49
+ config: async (opencodeConfig) => {
50
+ if (config.commands.enabled) {
51
+ opencodeConfig.command ??= {};
52
+ opencodeConfig.command["dcp"] = {
53
+ template: "",
54
+ description: "Show available DCP commands",
55
+ };
56
+ }
57
+ if (config.compress.permission !== "deny" &&
58
+ compressDisabledByOpencode(opencodeConfig.permission)) {
59
+ config.compress.permission = "deny";
60
+ }
61
+ const toolsToAdd = [];
62
+ if (config.compress.permission !== "deny" && !config.experimental.allowSubAgents) {
63
+ toolsToAdd.push("compress");
64
+ }
65
+ if (toolsToAdd.length > 0) {
66
+ const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [];
67
+ opencodeConfig.experimental = {
68
+ ...opencodeConfig.experimental,
69
+ primary_tools: [...existingPrimaryTools, ...toolsToAdd],
70
+ };
71
+ }
72
+ if (!hasExplicitToolPermission(opencodeConfig.permission, "compress")) {
73
+ const permission = opencodeConfig.permission ?? {};
74
+ opencodeConfig.permission = {
75
+ ...permission,
76
+ compress: config.compress.permission,
77
+ };
78
+ }
79
+ hostPermissions.global = opencodeConfig.permission;
80
+ hostPermissions.agents = Object.fromEntries(Object.entries(opencodeConfig.agent ?? {}).map(([name, agent]) => [
81
+ name,
82
+ agent?.permission,
83
+ ]));
84
+ },
85
+ };
86
+ });
87
+ export default plugin;
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function isSecureMode(): boolean;
2
+ export declare function getAuthorizationHeader(): string | undefined;
3
+ export declare function configureClientAuth(client: any): any;
4
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../lib/auth.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAQ3D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAsBpD"}
@@ -0,0 +1,32 @@
1
+ export function isSecureMode() {
2
+ return !!process.env.OPENCODE_SERVER_PASSWORD;
3
+ }
4
+ export function getAuthorizationHeader() {
5
+ const password = process.env.OPENCODE_SERVER_PASSWORD;
6
+ if (!password)
7
+ return undefined;
8
+ const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
9
+ // Use Buffer for Node.js base64 encoding (btoa may not be available in all Node versions)
10
+ const credentials = Buffer.from(`${username}:${password}`).toString("base64");
11
+ return `Basic ${credentials}`;
12
+ }
13
+ export function configureClientAuth(client) {
14
+ const authHeader = getAuthorizationHeader();
15
+ if (!authHeader) {
16
+ return client;
17
+ }
18
+ // The SDK client has an internal client with request interceptors
19
+ // Access the underlying client to add the interceptor
20
+ const innerClient = client._client || client.client;
21
+ if (innerClient?.interceptors?.request) {
22
+ innerClient.interceptors.request.use((request) => {
23
+ // Only add auth header if not already present
24
+ if (!request.headers.has("Authorization")) {
25
+ request.headers.set("Authorization", authHeader);
26
+ }
27
+ return request;
28
+ });
29
+ }
30
+ return client;
31
+ }
32
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../lib/auth.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY;IACxB,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,sBAAsB;IAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAA;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAE/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,UAAU,CAAA;IACnE,0FAA0F;IAC1F,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,IAAI,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC7E,OAAO,SAAS,WAAW,EAAE,CAAA;AACjC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAW;IAC3C,MAAM,UAAU,GAAG,sBAAsB,EAAE,CAAA;IAE3C,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,OAAO,MAAM,CAAA;IACjB,CAAC;IAED,kEAAkE;IAClE,sDAAsD;IACtD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAA;IAEnD,IAAI,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;QACrC,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAgB,EAAE,EAAE;YACtD,8CAA8C;YAC9C,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;gBACxC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAA;YACpD,CAAC;YACD,OAAO,OAAO,CAAA;QAClB,CAAC,CAAC,CAAA;IACN,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * DCP Context Command
3
+ * Shows a visual breakdown of token usage in the current session.
4
+ *
5
+ * TOKEN CALCULATION STRATEGY
6
+ * ==========================
7
+ * We minimize tokenizer estimation by leveraging API-reported values wherever possible.
8
+ *
9
+ * WHAT WE GET FROM THE API (exact):
10
+ * - tokens.input : Input tokens for each assistant response
11
+ * - tokens.output : Output tokens generated (includes text + tool calls)
12
+ * - tokens.reasoning: Reasoning tokens used
13
+ * - tokens.cache : Cache read/write tokens
14
+ *
15
+ * HOW WE CALCULATE EACH CATEGORY:
16
+ *
17
+ * SYSTEM = firstAssistant.input + cache.read + cache.write - tokenizer(firstUserMessage)
18
+ * The first response's total input (input + cache.read + cache.write)
19
+ * contains system + first user message. On the first request of a
20
+ * session, the system prompt appears in cache.write (cache creation),
21
+ * not cache.read.
22
+ *
23
+ * TOOLS = tokenizer(toolInputs + toolOutputs) - prunedTokens
24
+ * We must tokenize tools anyway for pruning decisions.
25
+ *
26
+ * USER = tokenizer(all user messages)
27
+ * User messages are typically small, so estimation is acceptable.
28
+ *
29
+ * ASSISTANT = total - system - user - tools
30
+ * Calculated as residual. This absorbs:
31
+ * - Assistant text output tokens
32
+ * - Reasoning tokens (if persisted by the model)
33
+ * - Any estimation errors
34
+ *
35
+ * TOTAL = input + output + reasoning + cache.read + cache.write
36
+ * Matches opencode's UI display.
37
+ *
38
+ * WHY ASSISTANT IS THE RESIDUAL:
39
+ * If reasoning tokens persist in context (model-dependent), they semantically
40
+ * belong with "Assistant" since reasoning IS assistant-generated content.
41
+ */
42
+ import type { Logger } from "../logger";
43
+ import type { SessionState, WithParts } from "../state";
44
+ export interface ContextCommandContext {
45
+ client: any;
46
+ state: SessionState;
47
+ logger: Logger;
48
+ sessionId: string;
49
+ messages: WithParts[];
50
+ }
51
+ export declare function handleContextCommand(ctx: ContextCommandContext): Promise<void>;
52
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../lib/commands/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAQvD,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,GAAG,CAAA;IACX,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,SAAS,EAAE,CAAA;CACxB;AAiPD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF"}