@royalcat/opencode-dcp-rc 0.0.0-stage → 4.0.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/LICENSE +619 -0
- package/README.md +348 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7611 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/commands/compression-targets.d.ts +13 -0
- package/dist/lib/commands/compression-targets.d.ts.map +1 -0
- package/dist/lib/commands/context.d.ts +67 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/decompress.d.ts +12 -0
- package/dist/lib/commands/decompress.d.ts.map +1 -0
- package/dist/lib/commands/help.d.ts +18 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/index.d.ts +8 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/manual.d.ts +23 -0
- package/dist/lib/commands/manual.d.ts.map +1 -0
- package/dist/lib/commands/recompress.d.ts +12 -0
- package/dist/lib/commands/recompress.d.ts.map +1 -0
- package/dist/lib/commands/stats.d.ts +25 -0
- package/dist/lib/commands/stats.d.ts.map +1 -0
- package/dist/lib/commands/sweep.d.ts +23 -0
- package/dist/lib/commands/sweep.d.ts.map +1 -0
- package/dist/lib/compress/index.d.ts +3 -0
- package/dist/lib/compress/index.d.ts.map +1 -0
- package/dist/lib/compress/pipeline.d.ts +29 -0
- package/dist/lib/compress/pipeline.d.ts.map +1 -0
- package/dist/lib/compress/protected-content.d.ts +6 -0
- package/dist/lib/compress/protected-content.d.ts.map +1 -0
- package/dist/lib/compress/rc.d.ts +15 -0
- package/dist/lib/compress/rc.d.ts.map +1 -0
- package/dist/lib/compress/search.d.ts +12 -0
- package/dist/lib/compress/search.d.ts.map +1 -0
- package/dist/lib/compress/state.d.ts +10 -0
- package/dist/lib/compress/state.d.ts.map +1 -0
- package/dist/lib/compress/summary.d.ts +58 -0
- package/dist/lib/compress/summary.d.ts.map +1 -0
- package/dist/lib/compress/timing.d.ts +18 -0
- package/dist/lib/compress/timing.d.ts.map +1 -0
- package/dist/lib/compress/types.d.ts +55 -0
- package/dist/lib/compress/types.d.ts.map +1 -0
- package/dist/lib/compress-permission.d.ts +4 -0
- package/dist/lib/compress-permission.d.ts.map +1 -0
- package/dist/lib/config.d.ts +83 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/hooks.d.ts +22 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +31 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/message-ids.d.ts +20 -0
- package/dist/lib/message-ids.d.ts.map +1 -0
- package/dist/lib/messages/index.d.ts +8 -0
- package/dist/lib/messages/index.d.ts.map +1 -0
- package/dist/lib/messages/inject/inject.d.ts +7 -0
- package/dist/lib/messages/inject/inject.d.ts.map +1 -0
- package/dist/lib/messages/inject/subagent-results.d.ts +4 -0
- package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -0
- package/dist/lib/messages/inject/utils.d.ts +23 -0
- package/dist/lib/messages/inject/utils.d.ts.map +1 -0
- package/dist/lib/messages/prune.d.ts +4 -0
- package/dist/lib/messages/prune.d.ts.map +1 -0
- package/dist/lib/messages/query.d.ts +7 -0
- package/dist/lib/messages/query.d.ts.map +1 -0
- package/dist/lib/messages/reasoning-strip.d.ts +8 -0
- package/dist/lib/messages/reasoning-strip.d.ts.map +1 -0
- package/dist/lib/messages/shape.d.ts +5 -0
- package/dist/lib/messages/shape.d.ts.map +1 -0
- package/dist/lib/messages/sync.d.ts +4 -0
- package/dist/lib/messages/sync.d.ts.map +1 -0
- package/dist/lib/messages/utils.d.ts +27 -0
- package/dist/lib/messages/utils.d.ts.map +1 -0
- package/dist/lib/prompts/compress-rc.d.ts +4 -0
- package/dist/lib/prompts/compress-rc.d.ts.map +1 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts +2 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/nudge.d.ts +4 -0
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/system.d.ts +4 -0
- package/dist/lib/prompts/extensions/system.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/tool.d.ts +3 -0
- package/dist/lib/prompts/extensions/tool.d.ts.map +1 -0
- package/dist/lib/prompts/index.d.ts +4 -0
- package/dist/lib/prompts/index.d.ts.map +1 -0
- package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/store.d.ts +26 -0
- package/dist/lib/prompts/store.d.ts.map +1 -0
- package/dist/lib/prompts/system.d.ts +4 -0
- package/dist/lib/prompts/system.d.ts.map +1 -0
- package/dist/lib/prompts/turn-nudge.d.ts +2 -0
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
- package/dist/lib/protected-patterns.d.ts +6 -0
- package/dist/lib/protected-patterns.d.ts.map +1 -0
- package/dist/lib/state/index.d.ts +5 -0
- package/dist/lib/state/index.d.ts.map +1 -0
- package/dist/lib/state/persistence.d.ts +45 -0
- package/dist/lib/state/persistence.d.ts.map +1 -0
- package/dist/lib/state/state.d.ts +9 -0
- package/dist/lib/state/state.d.ts.map +1 -0
- package/dist/lib/state/tool-cache.d.ts +13 -0
- package/dist/lib/state/tool-cache.d.ts.map +1 -0
- package/dist/lib/state/types.d.ts +102 -0
- package/dist/lib/state/types.d.ts.map +1 -0
- package/dist/lib/state/utils.d.ts +22 -0
- package/dist/lib/state/utils.d.ts.map +1 -0
- package/dist/lib/strategies/deduplication.d.ts +10 -0
- package/dist/lib/strategies/deduplication.d.ts.map +1 -0
- package/dist/lib/strategies/index.d.ts +3 -0
- package/dist/lib/strategies/index.d.ts.map +1 -0
- package/dist/lib/strategies/purge-errors.d.ts +13 -0
- package/dist/lib/strategies/purge-errors.d.ts.map +1 -0
- package/dist/lib/subagents/subagent-results.d.ts +5 -0
- package/dist/lib/subagents/subagent-results.d.ts.map +1 -0
- package/dist/lib/token-utils.d.ts +19 -0
- package/dist/lib/token-utils.d.ts.map +1 -0
- package/dist/lib/tui/dialogs.d.ts +29 -0
- package/dist/lib/tui/dialogs.d.ts.map +1 -0
- package/dist/lib/tui/format.d.ts +4 -0
- package/dist/lib/tui/format.d.ts.map +1 -0
- package/dist/lib/tui/types.d.ts +18 -0
- package/dist/lib/tui/types.d.ts.map +1 -0
- package/dist/lib/tui/ui.d.ts +48 -0
- package/dist/lib/tui/ui.d.ts.map +1 -0
- package/dist/lib/ui/notification.d.ts +17 -0
- package/dist/lib/ui/notification.d.ts.map +1 -0
- package/dist/lib/ui/utils.d.ts +10 -0
- package/dist/lib/ui/utils.d.ts.map +1 -0
- package/dist/lib/v2/index.d.ts +5 -0
- package/dist/lib/v2/index.d.ts.map +1 -0
- package/dist/lib/v2/messages.d.ts +13 -0
- package/dist/lib/v2/messages.d.ts.map +1 -0
- package/dist/lib/v2/rpc.d.ts +55 -0
- package/dist/lib/v2/rpc.d.ts.map +1 -0
- package/dist/lib/v2/theme.d.ts +55 -0
- package/dist/lib/v2/theme.d.ts.map +1 -0
- package/dist/lib/v2/tui.d.ts +4 -0
- package/dist/lib/v2/tui.d.ts.map +1 -0
- package/dist/tui.d.ts +7 -0
- package/dist/tui.d.ts.map +1 -0
- package/lib/commands/compression-targets.ts +54 -0
- package/lib/commands/context.ts +305 -0
- package/lib/commands/decompress.ts +273 -0
- package/lib/commands/help.ts +76 -0
- package/lib/commands/index.ts +11 -0
- package/lib/commands/manual.ts +126 -0
- package/lib/commands/recompress.ts +222 -0
- package/lib/commands/stats.ts +159 -0
- package/lib/commands/sweep.ts +266 -0
- package/lib/compress/index.ts +2 -0
- package/lib/compress/pipeline.ts +116 -0
- package/lib/compress/protected-content.ts +163 -0
- package/lib/compress/rc.ts +458 -0
- package/lib/compress/search.ts +277 -0
- package/lib/compress/state.ts +272 -0
- package/lib/compress/summary.ts +293 -0
- package/lib/compress/timing.ts +77 -0
- package/lib/compress/types.ts +57 -0
- package/lib/compress-permission.ts +9 -0
- package/lib/config.ts +995 -0
- package/lib/hooks.ts +205 -0
- package/lib/logger.ts +226 -0
- package/lib/message-ids.ts +181 -0
- package/lib/messages/index.ts +7 -0
- package/lib/messages/inject/inject.ts +215 -0
- package/lib/messages/inject/subagent-results.ts +84 -0
- package/lib/messages/inject/utils.ts +304 -0
- package/lib/messages/prune.ts +238 -0
- package/lib/messages/query.ts +71 -0
- package/lib/messages/reasoning-strip.ts +40 -0
- package/lib/messages/shape.ts +50 -0
- package/lib/messages/sync.ts +124 -0
- package/lib/messages/utils.ts +190 -0
- package/lib/prompts/compress-rc.ts +25 -0
- package/lib/prompts/context-limit-nudge.ts +18 -0
- package/lib/prompts/extensions/nudge.ts +34 -0
- package/lib/prompts/extensions/system.ts +32 -0
- package/lib/prompts/extensions/tool.ts +21 -0
- package/lib/prompts/index.ts +29 -0
- package/lib/prompts/iteration-nudge.ts +6 -0
- package/lib/prompts/store.ts +452 -0
- package/lib/prompts/system.ts +41 -0
- package/lib/prompts/turn-nudge.ts +10 -0
- package/lib/protected-patterns.ts +159 -0
- package/lib/state/index.ts +4 -0
- package/lib/state/persistence.ts +305 -0
- package/lib/state/state.ts +210 -0
- package/lib/state/tool-cache.ts +99 -0
- package/lib/state/types.ts +114 -0
- package/lib/state/utils.ts +345 -0
- package/lib/strategies/deduplication.ts +125 -0
- package/lib/strategies/index.ts +2 -0
- package/lib/strategies/purge-errors.ts +86 -0
- package/lib/subagents/subagent-results.ts +75 -0
- package/lib/token-utils.ts +164 -0
- package/lib/tui/dialogs.tsx +237 -0
- package/lib/tui/format.ts +25 -0
- package/lib/tui/types.ts +25 -0
- package/lib/tui/ui.tsx +245 -0
- package/lib/ui/notification.ts +347 -0
- package/lib/ui/utils.ts +304 -0
- package/lib/v2/index.ts +401 -0
- package/lib/v2/messages.ts +252 -0
- package/lib/v2/rpc.ts +48 -0
- package/lib/v2/theme.ts +67 -0
- package/lib/v2/tui.tsx +115 -0
- package/package.json +91 -5
- package/server.js +1 -0
- package/tui.tsx +7 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "../../state"
|
|
2
|
+
import type { Logger } from "../../logger"
|
|
3
|
+
import type { PluginConfig } from "../../config"
|
|
4
|
+
import type { RuntimePrompts } from "../../prompts/store"
|
|
5
|
+
import { formatMessageIdTag } from "../../message-ids"
|
|
6
|
+
import { compressPermission } from "../../compress-permission"
|
|
7
|
+
import {
|
|
8
|
+
getLastUserMessage,
|
|
9
|
+
isIgnoredUserMessage,
|
|
10
|
+
isProtectedUserMessage,
|
|
11
|
+
messageHasCompress,
|
|
12
|
+
} from "../query"
|
|
13
|
+
import { saveSessionState } from "../../state/persistence"
|
|
14
|
+
import {
|
|
15
|
+
appendToTextPart,
|
|
16
|
+
appendToLastTextPart,
|
|
17
|
+
appendToAllToolParts,
|
|
18
|
+
createSyntheticTextPart,
|
|
19
|
+
hasContent,
|
|
20
|
+
} from "../utils"
|
|
21
|
+
import {
|
|
22
|
+
addAnchor,
|
|
23
|
+
applyAnchoredNudges,
|
|
24
|
+
countMessagesAfterIndex,
|
|
25
|
+
findLastNonIgnoredMessage,
|
|
26
|
+
getIterationNudgeThreshold,
|
|
27
|
+
getNudgeFrequency,
|
|
28
|
+
getModelInfo,
|
|
29
|
+
isContextOverLimits,
|
|
30
|
+
} from "./utils"
|
|
31
|
+
|
|
32
|
+
export const injectCompressNudges = (
|
|
33
|
+
state: SessionState,
|
|
34
|
+
config: PluginConfig,
|
|
35
|
+
logger: Logger,
|
|
36
|
+
messages: WithParts[],
|
|
37
|
+
prompts: RuntimePrompts,
|
|
38
|
+
createAnchors = true,
|
|
39
|
+
): void => {
|
|
40
|
+
if (compressPermission(state, config) === "deny") {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (state.manualMode) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!createAnchors) {
|
|
49
|
+
applyAnchoredNudges(state, config, messages, prompts)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lastMessage = findLastNonIgnoredMessage(messages)
|
|
54
|
+
const lastAssistantMessage = messages.findLast((message) => message.info.role === "assistant")
|
|
55
|
+
|
|
56
|
+
if (lastAssistantMessage && messageHasCompress(lastAssistantMessage)) {
|
|
57
|
+
state.nudges.contextLimitAnchors.clear()
|
|
58
|
+
state.nudges.turnNudgeAnchors.clear()
|
|
59
|
+
state.nudges.iterationNudgeAnchors.clear()
|
|
60
|
+
void saveSessionState(state, logger)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { providerId, modelId } = getModelInfo(messages)
|
|
65
|
+
let anchorsChanged = false
|
|
66
|
+
|
|
67
|
+
const { overMaxLimit, overMinLimit } = isContextOverLimits(
|
|
68
|
+
config,
|
|
69
|
+
state,
|
|
70
|
+
providerId,
|
|
71
|
+
modelId,
|
|
72
|
+
messages,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (!overMinLimit) {
|
|
76
|
+
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0
|
|
77
|
+
const hadIterationAnchors = state.nudges.iterationNudgeAnchors.size > 0
|
|
78
|
+
|
|
79
|
+
if (hadTurnAnchors || hadIterationAnchors) {
|
|
80
|
+
state.nudges.turnNudgeAnchors.clear()
|
|
81
|
+
state.nudges.iterationNudgeAnchors.clear()
|
|
82
|
+
anchorsChanged = true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (overMaxLimit) {
|
|
87
|
+
if (lastMessage) {
|
|
88
|
+
const interval = getNudgeFrequency(config)
|
|
89
|
+
const added = addAnchor(
|
|
90
|
+
state.nudges.contextLimitAnchors,
|
|
91
|
+
lastMessage.message.info.id,
|
|
92
|
+
lastMessage.index,
|
|
93
|
+
messages,
|
|
94
|
+
interval,
|
|
95
|
+
)
|
|
96
|
+
if (added) {
|
|
97
|
+
anchorsChanged = true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else if (overMinLimit) {
|
|
101
|
+
const isLastMessageUser = lastMessage?.message.info.role === "user"
|
|
102
|
+
|
|
103
|
+
if (isLastMessageUser && lastAssistantMessage) {
|
|
104
|
+
const previousSize = state.nudges.turnNudgeAnchors.size
|
|
105
|
+
state.nudges.turnNudgeAnchors.add(lastMessage.message.info.id)
|
|
106
|
+
state.nudges.turnNudgeAnchors.add(lastAssistantMessage.info.id)
|
|
107
|
+
if (state.nudges.turnNudgeAnchors.size !== previousSize) {
|
|
108
|
+
anchorsChanged = true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const lastUserMessage = getLastUserMessage(messages)
|
|
113
|
+
if (lastUserMessage && lastMessage) {
|
|
114
|
+
const lastUserMessageIndex = messages.findIndex(
|
|
115
|
+
(message) => message.info.id === lastUserMessage.info.id,
|
|
116
|
+
)
|
|
117
|
+
if (lastUserMessageIndex >= 0) {
|
|
118
|
+
const messagesSinceUser = countMessagesAfterIndex(messages, lastUserMessageIndex)
|
|
119
|
+
const iterationThreshold = getIterationNudgeThreshold(config)
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
lastMessage.index > lastUserMessageIndex &&
|
|
123
|
+
messagesSinceUser >= iterationThreshold
|
|
124
|
+
) {
|
|
125
|
+
const interval = getNudgeFrequency(config)
|
|
126
|
+
const added = addAnchor(
|
|
127
|
+
state.nudges.iterationNudgeAnchors,
|
|
128
|
+
lastMessage.message.info.id,
|
|
129
|
+
lastMessage.index,
|
|
130
|
+
messages,
|
|
131
|
+
interval,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if (added) {
|
|
135
|
+
anchorsChanged = true
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
applyAnchoredNudges(state, config, messages, prompts)
|
|
143
|
+
|
|
144
|
+
if (anchorsChanged) {
|
|
145
|
+
void saveSessionState(state, logger)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const injectMessageIds = (
|
|
150
|
+
state: SessionState,
|
|
151
|
+
config: PluginConfig,
|
|
152
|
+
messages: WithParts[],
|
|
153
|
+
): void => {
|
|
154
|
+
if (compressPermission(state, config) === "deny") {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const message of messages) {
|
|
159
|
+
if (isIgnoredUserMessage(message)) {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const messageRef = state.messageIds.byRawId.get(message.info.id)
|
|
164
|
+
if (!messageRef) {
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const isBlockedMessage = isProtectedUserMessage(config, message)
|
|
169
|
+
const tag = formatMessageIdTag(
|
|
170
|
+
isBlockedMessage ? "BLOCKED" : messageRef,
|
|
171
|
+
undefined,
|
|
172
|
+
state.idFormat,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (message.info.role === "user") {
|
|
176
|
+
let injected = false
|
|
177
|
+
for (const part of message.parts) {
|
|
178
|
+
if (part.type === "text") {
|
|
179
|
+
injected = appendToTextPart(part, tag) || injected
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (injected) {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
message.parts.push(createSyntheticTextPart(message, tag))
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (message.info.role !== "assistant") {
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!hasContent(message)) {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (appendToAllToolParts(message, tag)) {
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (appendToLastTextPart(message, tag)) {
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const syntheticPart = createSyntheticTextPart(message, tag)
|
|
208
|
+
const firstToolIndex = message.parts.findIndex((p) => p.type === "tool")
|
|
209
|
+
if (firstToolIndex === -1) {
|
|
210
|
+
message.parts.push(syntheticPart)
|
|
211
|
+
} else {
|
|
212
|
+
message.parts.splice(firstToolIndex, 0, syntheticPart)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Logger } from "../../logger"
|
|
2
|
+
import type { SessionState, WithParts } from "../../state"
|
|
3
|
+
import { filterMessages } from "../shape"
|
|
4
|
+
import {
|
|
5
|
+
buildSubagentResultText,
|
|
6
|
+
getSubAgentId,
|
|
7
|
+
mergeSubagentResult,
|
|
8
|
+
} from "../../subagents/subagent-results"
|
|
9
|
+
import { stripHallucinationsFromString } from "../utils"
|
|
10
|
+
|
|
11
|
+
async function fetchSubAgentMessages(client: any, sessionId: string): Promise<WithParts[]> {
|
|
12
|
+
const response = await client.session.messages({
|
|
13
|
+
path: { id: sessionId },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
return filterMessages(response?.data || response)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const injectExtendedSubAgentResults = async (
|
|
20
|
+
client: any,
|
|
21
|
+
state: SessionState,
|
|
22
|
+
logger: Logger,
|
|
23
|
+
messages: WithParts[],
|
|
24
|
+
allowSubAgents: boolean,
|
|
25
|
+
): Promise<void> => {
|
|
26
|
+
if (!allowSubAgents) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const message of messages) {
|
|
31
|
+
const parts = Array.isArray(message.parts) ? message.parts : []
|
|
32
|
+
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
if (part.type !== "tool" || !["task", "subagent"].includes(part.tool) || !part.callID) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
if (state.prune.tools.has(part.callID)) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cachedResult = state.subAgentResultCache.get(part.callID)
|
|
45
|
+
if (cachedResult !== undefined) {
|
|
46
|
+
if (cachedResult) {
|
|
47
|
+
part.state.output = stripHallucinationsFromString(
|
|
48
|
+
mergeSubagentResult(part.state.output, cachedResult),
|
|
49
|
+
state.idFormat,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const subAgentSessionId = getSubAgentId(part)
|
|
56
|
+
if (!subAgentSessionId) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let subAgentMessages: WithParts[] = []
|
|
61
|
+
try {
|
|
62
|
+
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
65
|
+
subAgentSessionId,
|
|
66
|
+
callID: part.callID,
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
})
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const subAgentResultText = buildSubagentResultText(subAgentMessages)
|
|
73
|
+
if (!subAgentResultText) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
state.subAgentResultCache.set(part.callID, subAgentResultText)
|
|
78
|
+
part.state.output = stripHallucinationsFromString(
|
|
79
|
+
mergeSubagentResult(part.state.output, subAgentResultText),
|
|
80
|
+
state.idFormat,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "../../state"
|
|
2
|
+
import type { PluginConfig } from "../../config"
|
|
3
|
+
import {
|
|
4
|
+
appendGuidanceToDcpTag,
|
|
5
|
+
buildCompressedBlockGuidance,
|
|
6
|
+
} from "../../prompts/extensions/nudge"
|
|
7
|
+
import type { RuntimePrompts } from "../../prompts/store"
|
|
8
|
+
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
|
9
|
+
import {
|
|
10
|
+
appendToTextPart,
|
|
11
|
+
appendToLastTextPart,
|
|
12
|
+
createSyntheticTextPart,
|
|
13
|
+
hasContent,
|
|
14
|
+
} from "../utils"
|
|
15
|
+
import { getLastUserMessage, isIgnoredUserMessage } from "../query"
|
|
16
|
+
import { getCurrentTokenUsage } from "../../token-utils"
|
|
17
|
+
import { getActiveSummaryTokenUsage } from "../../state/utils"
|
|
18
|
+
|
|
19
|
+
export interface LastUserModelContext {
|
|
20
|
+
providerId: string | undefined
|
|
21
|
+
modelId: string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LastNonIgnoredMessage {
|
|
25
|
+
message: WithParts
|
|
26
|
+
index: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getNudgeFrequency(config: PluginConfig): number {
|
|
30
|
+
return Math.max(1, Math.floor(config.compress.nudgeFrequency || 1))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getIterationNudgeThreshold(config: PluginConfig): number {
|
|
34
|
+
return Math.max(1, Math.floor(config.compress.iterationNudgeThreshold || 1))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findLastNonIgnoredMessage(messages: WithParts[]): LastNonIgnoredMessage | null {
|
|
38
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
39
|
+
const message = messages[i]
|
|
40
|
+
if (isIgnoredUserMessage(message)) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
return { message, index: i }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function countMessagesAfterIndex(messages: WithParts[], index: number): number {
|
|
50
|
+
let count = 0
|
|
51
|
+
|
|
52
|
+
for (let i = index + 1; i < messages.length; i++) {
|
|
53
|
+
const message = messages[i]
|
|
54
|
+
if (isIgnoredUserMessage(message)) {
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
count++
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return count
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getModelInfo(messages: WithParts[]): LastUserModelContext {
|
|
64
|
+
const lastUserMessage = getLastUserMessage(messages)
|
|
65
|
+
if (!lastUserMessage) {
|
|
66
|
+
return {
|
|
67
|
+
providerId: undefined,
|
|
68
|
+
modelId: undefined,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const userInfo = lastUserMessage.info as UserMessage
|
|
73
|
+
return {
|
|
74
|
+
providerId: userInfo.model.providerID,
|
|
75
|
+
modelId: userInfo.model.modelID,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveContextTokenLimit(
|
|
80
|
+
config: PluginConfig,
|
|
81
|
+
state: SessionState,
|
|
82
|
+
providerId: string | undefined,
|
|
83
|
+
modelId: string | undefined,
|
|
84
|
+
threshold: "max" | "min",
|
|
85
|
+
): number | undefined {
|
|
86
|
+
const parseLimitValue = (limit: number | `${number}%` | undefined): number | undefined => {
|
|
87
|
+
if (limit === undefined) {
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (typeof limit === "number") {
|
|
92
|
+
return limit
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!limit.endsWith("%") || state.modelContextLimit === undefined) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const parsedPercent = parseFloat(limit.slice(0, -1))
|
|
100
|
+
if (isNaN(parsedPercent)) {
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const roundedPercent = Math.round(parsedPercent)
|
|
105
|
+
const clampedPercent = Math.max(0, Math.min(100, roundedPercent))
|
|
106
|
+
return Math.round((clampedPercent / 100) * state.modelContextLimit)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const modelLimits =
|
|
110
|
+
threshold === "max" ? config.compress.modelMaxLimits : config.compress.modelMinLimits
|
|
111
|
+
if (modelLimits && providerId !== undefined && modelId !== undefined) {
|
|
112
|
+
const providerModelId = `${providerId}/${modelId}`
|
|
113
|
+
const modelLimit = modelLimits[providerModelId]
|
|
114
|
+
if (modelLimit !== undefined) {
|
|
115
|
+
return parseLimitValue(modelLimit)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const globalLimit =
|
|
120
|
+
threshold === "max" ? config.compress.maxContextLimit : config.compress.minContextLimit
|
|
121
|
+
return parseLimitValue(globalLimit)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function isContextOverLimits(
|
|
125
|
+
config: PluginConfig,
|
|
126
|
+
state: SessionState,
|
|
127
|
+
providerId: string | undefined,
|
|
128
|
+
modelId: string | undefined,
|
|
129
|
+
messages: WithParts[],
|
|
130
|
+
) {
|
|
131
|
+
const summaryTokenExtension = config.compress.summaryBuffer
|
|
132
|
+
? getActiveSummaryTokenUsage(state)
|
|
133
|
+
: 0
|
|
134
|
+
const resolvedMaxContextLimit = resolveContextTokenLimit(
|
|
135
|
+
config,
|
|
136
|
+
state,
|
|
137
|
+
providerId,
|
|
138
|
+
modelId,
|
|
139
|
+
"max",
|
|
140
|
+
)
|
|
141
|
+
const maxContextLimit =
|
|
142
|
+
resolvedMaxContextLimit === undefined
|
|
143
|
+
? undefined
|
|
144
|
+
: resolvedMaxContextLimit + summaryTokenExtension
|
|
145
|
+
const minContextLimit = resolveContextTokenLimit(config, state, providerId, modelId, "min")
|
|
146
|
+
const currentTokens = getCurrentTokenUsage(state, messages)
|
|
147
|
+
|
|
148
|
+
const overMaxLimit = maxContextLimit === undefined ? false : currentTokens > maxContextLimit
|
|
149
|
+
const overMinLimit = minContextLimit === undefined ? true : currentTokens >= minContextLimit
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
overMaxLimit,
|
|
153
|
+
overMinLimit,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function addAnchor(
|
|
158
|
+
anchorMessageIds: Set<string>,
|
|
159
|
+
anchorMessageId: string,
|
|
160
|
+
anchorMessageIndex: number,
|
|
161
|
+
messages: WithParts[],
|
|
162
|
+
interval: number,
|
|
163
|
+
): boolean {
|
|
164
|
+
if (anchorMessageIndex < 0) {
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let latestAnchorMessageIndex = -1
|
|
169
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
170
|
+
if (anchorMessageIds.has(messages[i].info.id)) {
|
|
171
|
+
latestAnchorMessageIndex = i
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const shouldAdd =
|
|
177
|
+
latestAnchorMessageIndex < 0 || anchorMessageIndex - latestAnchorMessageIndex >= interval
|
|
178
|
+
if (!shouldAdd) {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const previousSize = anchorMessageIds.size
|
|
183
|
+
anchorMessageIds.add(anchorMessageId)
|
|
184
|
+
return anchorMessageIds.size !== previousSize
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function injectAnchoredNudge(message: WithParts, nudgeText: string): void {
|
|
188
|
+
if (!nudgeText.trim()) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (message.info.role === "user") {
|
|
193
|
+
if (appendToLastTextPart(message, nudgeText)) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
message.parts.push(createSyntheticTextPart(message, nudgeText))
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (message.info.role !== "assistant") {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!hasContent(message)) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const part of message.parts) {
|
|
210
|
+
if (part.type === "text") {
|
|
211
|
+
if (appendToTextPart(part, nudgeText)) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const syntheticPart = createSyntheticTextPart(message, nudgeText)
|
|
218
|
+
const firstToolIndex = message.parts.findIndex((p) => p.type === "tool")
|
|
219
|
+
if (firstToolIndex === -1) {
|
|
220
|
+
message.parts.push(syntheticPart)
|
|
221
|
+
} else {
|
|
222
|
+
message.parts.splice(firstToolIndex, 0, syntheticPart)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function collectAnchoredMessages(
|
|
227
|
+
anchorMessageIds: Set<string>,
|
|
228
|
+
messages: WithParts[],
|
|
229
|
+
): Array<{ message: WithParts; index: number }> {
|
|
230
|
+
const anchoredMessages: Array<{ message: WithParts; index: number }> = []
|
|
231
|
+
|
|
232
|
+
for (const anchorMessageId of anchorMessageIds) {
|
|
233
|
+
const index = messages.findIndex((message) => message.info.id === anchorMessageId)
|
|
234
|
+
if (index === -1) {
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
anchoredMessages.push({
|
|
239
|
+
message: messages[index],
|
|
240
|
+
index,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return anchoredMessages
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function collectTurnNudgeAnchors(
|
|
248
|
+
state: SessionState,
|
|
249
|
+
config: PluginConfig,
|
|
250
|
+
messages: WithParts[],
|
|
251
|
+
): Set<string> {
|
|
252
|
+
const turnNudgeAnchors = new Set<string>()
|
|
253
|
+
const targetRole = config.compress.nudgeForce === "strong" ? "user" : "assistant"
|
|
254
|
+
|
|
255
|
+
for (const message of messages) {
|
|
256
|
+
if (!state.nudges.turnNudgeAnchors.has(message.info.id)) continue
|
|
257
|
+
|
|
258
|
+
if (message.info.role === targetRole) {
|
|
259
|
+
turnNudgeAnchors.add(message.info.id)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return turnNudgeAnchors
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function applyGuidedAnchoredNudge(
|
|
267
|
+
anchorMessageIds: Set<string>,
|
|
268
|
+
messages: WithParts[],
|
|
269
|
+
baseNudgeText: string,
|
|
270
|
+
compressedBlockGuidance: string,
|
|
271
|
+
): void {
|
|
272
|
+
const nudgeText = appendGuidanceToDcpTag(baseNudgeText, compressedBlockGuidance)
|
|
273
|
+
if (!nudgeText.trim()) {
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const { message } of collectAnchoredMessages(anchorMessageIds, messages)) {
|
|
278
|
+
injectAnchoredNudge(message, nudgeText)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function applyAnchoredNudges(
|
|
283
|
+
state: SessionState,
|
|
284
|
+
config: PluginConfig,
|
|
285
|
+
messages: WithParts[],
|
|
286
|
+
prompts: RuntimePrompts,
|
|
287
|
+
): void {
|
|
288
|
+
const turnNudgeAnchors = collectTurnNudgeAnchors(state, config, messages)
|
|
289
|
+
const compressedBlockGuidance = buildCompressedBlockGuidance(state)
|
|
290
|
+
|
|
291
|
+
applyGuidedAnchoredNudge(
|
|
292
|
+
state.nudges.contextLimitAnchors,
|
|
293
|
+
messages,
|
|
294
|
+
prompts.contextLimitNudge,
|
|
295
|
+
compressedBlockGuidance,
|
|
296
|
+
)
|
|
297
|
+
applyGuidedAnchoredNudge(turnNudgeAnchors, messages, prompts.turnNudge, compressedBlockGuidance)
|
|
298
|
+
applyGuidedAnchoredNudge(
|
|
299
|
+
state.nudges.iterationNudgeAnchors,
|
|
300
|
+
messages,
|
|
301
|
+
prompts.iterationNudge,
|
|
302
|
+
compressedBlockGuidance,
|
|
303
|
+
)
|
|
304
|
+
}
|