@tarquinen/opencode-dcp 3.1.11 → 3.1.13
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 +14 -9
- package/dist/index.js +106 -49
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/context.d.ts +15 -0
- package/dist/lib/commands/context.d.ts.map +1 -1
- package/dist/lib/commands/help.d.ts +1 -0
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/manual.d.ts +1 -1
- package/dist/lib/commands/manual.d.ts.map +1 -1
- package/dist/lib/commands/stats.d.ts +10 -0
- package/dist/lib/commands/stats.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts +0 -2
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/index.d.ts +0 -1
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +0 -7
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +3 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts +1 -0
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/tui/commands.d.ts +3 -0
- package/dist/lib/tui/commands.d.ts.map +1 -0
- package/dist/lib/tui/data.d.ts +14 -0
- package/dist/lib/tui/data.d.ts.map +1 -0
- package/dist/lib/tui/dialogs.d.ts +30 -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/modals.d.ts +10 -0
- package/dist/lib/tui/modals.d.ts.map +1 -0
- package/dist/lib/tui/types.d.ts +15 -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/tui.d.ts +7 -0
- package/dist/tui.d.ts.map +1 -0
- package/lib/auth.ts +37 -0
- package/lib/commands/compression-targets.ts +137 -0
- package/lib/commands/context.ts +305 -0
- package/lib/commands/decompress.ts +275 -0
- package/lib/commands/help.ts +76 -0
- package/lib/commands/index.ts +11 -0
- package/lib/commands/manual.ts +127 -0
- package/lib/commands/recompress.ts +224 -0
- package/lib/commands/stats.ts +159 -0
- package/lib/commands/sweep.ts +268 -0
- package/lib/compress/index.ts +3 -0
- package/lib/compress/message-utils.ts +250 -0
- package/lib/compress/message.ts +145 -0
- package/lib/compress/pipeline.ts +108 -0
- package/lib/compress/protected-content.ts +208 -0
- package/lib/compress/range-utils.ts +308 -0
- package/lib/compress/range.ts +192 -0
- package/lib/compress/search.ts +267 -0
- package/lib/compress/state.ts +268 -0
- package/lib/compress/timing.ts +77 -0
- package/lib/compress/types.ts +108 -0
- package/lib/compress-permission.ts +25 -0
- package/lib/config.ts +1007 -0
- package/lib/hooks.ts +375 -0
- package/lib/host-permissions.ts +101 -0
- package/lib/logger.ts +226 -0
- package/lib/message-ids.ts +172 -0
- package/lib/messages/index.ts +8 -0
- package/lib/messages/inject/inject.ts +215 -0
- package/lib/messages/inject/subagent-results.ts +82 -0
- package/lib/messages/inject/utils.ts +374 -0
- package/lib/messages/priority.ts +102 -0
- package/lib/messages/prune.ts +233 -0
- package/lib/messages/query.ts +72 -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 +185 -0
- package/lib/prompts/compress-message.ts +43 -0
- package/lib/prompts/compress-range.ts +60 -0
- package/lib/prompts/context-limit-nudge.ts +18 -0
- package/lib/prompts/extensions/nudge.ts +43 -0
- package/lib/prompts/extensions/system.ts +32 -0
- package/lib/prompts/extensions/tool.ts +35 -0
- package/lib/prompts/index.ts +29 -0
- package/lib/prompts/iteration-nudge.ts +6 -0
- package/lib/prompts/store.ts +467 -0
- package/lib/prompts/system.ts +33 -0
- package/lib/prompts/turn-nudge.ts +10 -0
- package/lib/protected-patterns.ts +128 -0
- package/lib/state/index.ts +4 -0
- package/lib/state/persistence.ts +305 -0
- package/lib/state/state.ts +208 -0
- package/lib/state/tool-cache.ts +98 -0
- package/lib/state/types.ts +111 -0
- package/lib/state/utils.ts +345 -0
- package/lib/strategies/deduplication.ts +127 -0
- package/lib/strategies/index.ts +2 -0
- package/lib/strategies/purge-errors.ts +88 -0
- package/lib/subagents/subagent-results.ts +74 -0
- package/lib/token-utils.ts +164 -0
- package/lib/tui/commands.ts +31 -0
- package/lib/tui/data.ts +73 -0
- package/lib/tui/dialogs.tsx +235 -0
- package/lib/tui/format.ts +25 -0
- package/lib/tui/modals.tsx +92 -0
- package/lib/tui/types.ts +16 -0
- package/lib/tui/ui.tsx +219 -0
- package/lib/ui/notification.ts +347 -0
- package/lib/ui/utils.ts +304 -0
- package/lib/update.ts +185 -0
- package/package.json +17 -5
- package/tui.tsx +26 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { SessionState, WithParts } from "./state"
|
|
2
|
+
import { isIgnoredUserMessage } from "./messages/query"
|
|
3
|
+
|
|
4
|
+
const MESSAGE_REF_REGEX = /^m(\d{4})$/
|
|
5
|
+
const BLOCK_REF_REGEX = /^b([1-9]\d*)$/
|
|
6
|
+
const MESSAGE_ID_TAG_NAME = "dcp-message-id"
|
|
7
|
+
|
|
8
|
+
const MESSAGE_REF_WIDTH = 4
|
|
9
|
+
const MESSAGE_REF_MIN_INDEX = 1
|
|
10
|
+
export const MESSAGE_REF_MAX_INDEX = 9999
|
|
11
|
+
|
|
12
|
+
export type ParsedBoundaryId =
|
|
13
|
+
| {
|
|
14
|
+
kind: "message"
|
|
15
|
+
ref: string
|
|
16
|
+
index: number
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: "compressed-block"
|
|
20
|
+
ref: string
|
|
21
|
+
blockId: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatMessageRef(index: number): string {
|
|
25
|
+
if (
|
|
26
|
+
!Number.isInteger(index) ||
|
|
27
|
+
index < MESSAGE_REF_MIN_INDEX ||
|
|
28
|
+
index > MESSAGE_REF_MAX_INDEX
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Message ID index out of bounds: ${index}. Supported range is 0-${MESSAGE_REF_MAX_INDEX}.`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatBlockRef(blockId: number): string {
|
|
38
|
+
if (!Number.isInteger(blockId) || blockId < 1) {
|
|
39
|
+
throw new Error(`Invalid block ID: ${blockId}`)
|
|
40
|
+
}
|
|
41
|
+
return `b${blockId}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseMessageRef(ref: string): number | null {
|
|
45
|
+
const normalized = ref.trim().toLowerCase()
|
|
46
|
+
const match = normalized.match(MESSAGE_REF_REGEX)
|
|
47
|
+
if (!match) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
const index = Number.parseInt(match[1], 10)
|
|
51
|
+
if (!Number.isInteger(index)) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
if (index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
return index
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parseBlockRef(ref: string): number | null {
|
|
61
|
+
const normalized = ref.trim().toLowerCase()
|
|
62
|
+
const match = normalized.match(BLOCK_REF_REGEX)
|
|
63
|
+
if (!match) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
const id = Number.parseInt(match[1], 10)
|
|
67
|
+
return Number.isInteger(id) ? id : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseBoundaryId(id: string): ParsedBoundaryId | null {
|
|
71
|
+
const normalized = id.trim().toLowerCase()
|
|
72
|
+
const messageIndex = parseMessageRef(normalized)
|
|
73
|
+
if (messageIndex !== null) {
|
|
74
|
+
return {
|
|
75
|
+
kind: "message",
|
|
76
|
+
ref: formatMessageRef(messageIndex),
|
|
77
|
+
index: messageIndex,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const blockId = parseBlockRef(normalized)
|
|
82
|
+
if (blockId !== null) {
|
|
83
|
+
return {
|
|
84
|
+
kind: "compressed-block",
|
|
85
|
+
ref: formatBlockRef(blockId),
|
|
86
|
+
blockId,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function escapeXmlAttribute(value: string): string {
|
|
94
|
+
return value
|
|
95
|
+
.replace(/&/g, "&")
|
|
96
|
+
.replace(/"/g, """)
|
|
97
|
+
.replace(/</g, "<")
|
|
98
|
+
.replace(/>/g, ">")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function formatMessageIdTag(
|
|
102
|
+
ref: string,
|
|
103
|
+
attributes?: Record<string, string | undefined>,
|
|
104
|
+
): string {
|
|
105
|
+
const serializedAttributes = Object.entries(attributes || {})
|
|
106
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
107
|
+
.map(([name, value]) => {
|
|
108
|
+
if (name.trim().length === 0 || typeof value !== "string" || value.length === 0) {
|
|
109
|
+
return ""
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return ` ${name}="${escapeXmlAttribute(value)}"`
|
|
113
|
+
})
|
|
114
|
+
.join("")
|
|
115
|
+
|
|
116
|
+
return `\n<${MESSAGE_ID_TAG_NAME}${serializedAttributes}>${ref}</${MESSAGE_ID_TAG_NAME}>`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function assignMessageRefs(state: SessionState, messages: WithParts[]): number {
|
|
120
|
+
let assigned = 0
|
|
121
|
+
let skippedSubAgentPrompt = false
|
|
122
|
+
|
|
123
|
+
for (const message of messages) {
|
|
124
|
+
if (isIgnoredUserMessage(message)) {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (state.isSubAgent && !skippedSubAgentPrompt && message.info.role === "user") {
|
|
129
|
+
skippedSubAgentPrompt = true
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rawMessageId = message.info.id
|
|
134
|
+
if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const existingRef = state.messageIds.byRawId.get(rawMessageId)
|
|
139
|
+
if (existingRef) {
|
|
140
|
+
if (state.messageIds.byRef.get(existingRef) !== rawMessageId) {
|
|
141
|
+
state.messageIds.byRef.set(existingRef, rawMessageId)
|
|
142
|
+
}
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ref = allocateNextMessageRef(state)
|
|
147
|
+
state.messageIds.byRawId.set(rawMessageId, ref)
|
|
148
|
+
state.messageIds.byRef.set(ref, rawMessageId)
|
|
149
|
+
assigned++
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return assigned
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function allocateNextMessageRef(state: SessionState): string {
|
|
156
|
+
let candidate = Number.isInteger(state.messageIds.nextRef)
|
|
157
|
+
? Math.max(MESSAGE_REF_MIN_INDEX, state.messageIds.nextRef)
|
|
158
|
+
: MESSAGE_REF_MIN_INDEX
|
|
159
|
+
|
|
160
|
+
while (candidate <= MESSAGE_REF_MAX_INDEX) {
|
|
161
|
+
const ref = formatMessageRef(candidate)
|
|
162
|
+
if (!state.messageIds.byRef.has(ref)) {
|
|
163
|
+
state.messageIds.nextRef = candidate + 1
|
|
164
|
+
return ref
|
|
165
|
+
}
|
|
166
|
+
candidate++
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { prune } from "./prune"
|
|
2
|
+
export { syncCompressionBlocks } from "./sync"
|
|
3
|
+
export { injectCompressNudges } from "./inject/inject"
|
|
4
|
+
export { injectMessageIds } from "./inject/inject"
|
|
5
|
+
export { injectExtendedSubAgentResults } from "./inject/subagent-results"
|
|
6
|
+
export { stripStaleMetadata } from "./reasoning-strip"
|
|
7
|
+
export { buildPriorityMap } from "./priority"
|
|
8
|
+
export { buildToolIdList, stripHallucinations, stripHallucinationsFromString } from "./utils"
|
|
@@ -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 type { CompressionPriorityMap } from "../priority"
|
|
7
|
+
import { compressPermission } from "../../compress-permission"
|
|
8
|
+
import {
|
|
9
|
+
getLastUserMessage,
|
|
10
|
+
isIgnoredUserMessage,
|
|
11
|
+
isProtectedUserMessage,
|
|
12
|
+
messageHasCompress,
|
|
13
|
+
} from "../query"
|
|
14
|
+
import { saveSessionState } from "../../state/persistence"
|
|
15
|
+
import {
|
|
16
|
+
appendToTextPart,
|
|
17
|
+
appendToLastTextPart,
|
|
18
|
+
appendToAllToolParts,
|
|
19
|
+
createSyntheticTextPart,
|
|
20
|
+
hasContent,
|
|
21
|
+
} from "../utils"
|
|
22
|
+
import {
|
|
23
|
+
addAnchor,
|
|
24
|
+
applyAnchoredNudges,
|
|
25
|
+
countMessagesAfterIndex,
|
|
26
|
+
findLastNonIgnoredMessage,
|
|
27
|
+
getIterationNudgeThreshold,
|
|
28
|
+
getNudgeFrequency,
|
|
29
|
+
getModelInfo,
|
|
30
|
+
isContextOverLimits,
|
|
31
|
+
} from "./utils"
|
|
32
|
+
|
|
33
|
+
export const injectCompressNudges = (
|
|
34
|
+
state: SessionState,
|
|
35
|
+
config: PluginConfig,
|
|
36
|
+
logger: Logger,
|
|
37
|
+
messages: WithParts[],
|
|
38
|
+
prompts: RuntimePrompts,
|
|
39
|
+
compressionPriorities?: CompressionPriorityMap,
|
|
40
|
+
): void => {
|
|
41
|
+
if (compressPermission(state, config) === "deny") {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (state.manualMode) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lastMessage = findLastNonIgnoredMessage(messages)
|
|
50
|
+
const lastAssistantMessage = messages.findLast((message) => message.info.role === "assistant")
|
|
51
|
+
|
|
52
|
+
if (lastAssistantMessage && messageHasCompress(lastAssistantMessage)) {
|
|
53
|
+
state.nudges.contextLimitAnchors.clear()
|
|
54
|
+
state.nudges.turnNudgeAnchors.clear()
|
|
55
|
+
state.nudges.iterationNudgeAnchors.clear()
|
|
56
|
+
void saveSessionState(state, logger)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { providerId, modelId } = getModelInfo(messages)
|
|
61
|
+
let anchorsChanged = false
|
|
62
|
+
|
|
63
|
+
const { overMaxLimit, overMinLimit } = isContextOverLimits(
|
|
64
|
+
config,
|
|
65
|
+
state,
|
|
66
|
+
providerId,
|
|
67
|
+
modelId,
|
|
68
|
+
messages,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (!overMinLimit) {
|
|
72
|
+
const hadTurnAnchors = state.nudges.turnNudgeAnchors.size > 0
|
|
73
|
+
const hadIterationAnchors = state.nudges.iterationNudgeAnchors.size > 0
|
|
74
|
+
|
|
75
|
+
if (hadTurnAnchors || hadIterationAnchors) {
|
|
76
|
+
state.nudges.turnNudgeAnchors.clear()
|
|
77
|
+
state.nudges.iterationNudgeAnchors.clear()
|
|
78
|
+
anchorsChanged = true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (overMaxLimit) {
|
|
83
|
+
if (lastMessage) {
|
|
84
|
+
const interval = getNudgeFrequency(config)
|
|
85
|
+
const added = addAnchor(
|
|
86
|
+
state.nudges.contextLimitAnchors,
|
|
87
|
+
lastMessage.message.info.id,
|
|
88
|
+
lastMessage.index,
|
|
89
|
+
messages,
|
|
90
|
+
interval,
|
|
91
|
+
)
|
|
92
|
+
if (added) {
|
|
93
|
+
anchorsChanged = true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (overMinLimit) {
|
|
97
|
+
const isLastMessageUser = lastMessage?.message.info.role === "user"
|
|
98
|
+
|
|
99
|
+
if (isLastMessageUser && lastAssistantMessage) {
|
|
100
|
+
const previousSize = state.nudges.turnNudgeAnchors.size
|
|
101
|
+
state.nudges.turnNudgeAnchors.add(lastMessage.message.info.id)
|
|
102
|
+
state.nudges.turnNudgeAnchors.add(lastAssistantMessage.info.id)
|
|
103
|
+
if (state.nudges.turnNudgeAnchors.size !== previousSize) {
|
|
104
|
+
anchorsChanged = true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const lastUserMessage = getLastUserMessage(messages)
|
|
109
|
+
if (lastUserMessage && lastMessage) {
|
|
110
|
+
const lastUserMessageIndex = messages.findIndex(
|
|
111
|
+
(message) => message.info.id === lastUserMessage.info.id,
|
|
112
|
+
)
|
|
113
|
+
if (lastUserMessageIndex >= 0) {
|
|
114
|
+
const messagesSinceUser = countMessagesAfterIndex(messages, lastUserMessageIndex)
|
|
115
|
+
const iterationThreshold = getIterationNudgeThreshold(config)
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
lastMessage.index > lastUserMessageIndex &&
|
|
119
|
+
messagesSinceUser >= iterationThreshold
|
|
120
|
+
) {
|
|
121
|
+
const interval = getNudgeFrequency(config)
|
|
122
|
+
const added = addAnchor(
|
|
123
|
+
state.nudges.iterationNudgeAnchors,
|
|
124
|
+
lastMessage.message.info.id,
|
|
125
|
+
lastMessage.index,
|
|
126
|
+
messages,
|
|
127
|
+
interval,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if (added) {
|
|
131
|
+
anchorsChanged = true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities)
|
|
139
|
+
|
|
140
|
+
if (anchorsChanged) {
|
|
141
|
+
void saveSessionState(state, logger)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const injectMessageIds = (
|
|
146
|
+
state: SessionState,
|
|
147
|
+
config: PluginConfig,
|
|
148
|
+
messages: WithParts[],
|
|
149
|
+
compressionPriorities?: CompressionPriorityMap,
|
|
150
|
+
): void => {
|
|
151
|
+
if (compressPermission(state, config) === "deny") {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const message of messages) {
|
|
156
|
+
if (isIgnoredUserMessage(message)) {
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const messageRef = state.messageIds.byRawId.get(message.info.id)
|
|
161
|
+
if (!messageRef) {
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isBlockedMessage = isProtectedUserMessage(config, message)
|
|
166
|
+
const priority =
|
|
167
|
+
config.compress.mode === "message" && !isBlockedMessage
|
|
168
|
+
? compressionPriorities?.get(message.info.id)?.priority
|
|
169
|
+
: undefined
|
|
170
|
+
const tag = formatMessageIdTag(
|
|
171
|
+
isBlockedMessage ? "BLOCKED" : messageRef,
|
|
172
|
+
priority ? { priority } : undefined,
|
|
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,82 @@
|
|
|
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" || part.tool !== "task" || !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
|
+
)
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const subAgentSessionId = getSubAgentId(part)
|
|
55
|
+
if (!subAgentSessionId) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let subAgentMessages: WithParts[] = []
|
|
60
|
+
try {
|
|
61
|
+
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
64
|
+
subAgentSessionId,
|
|
65
|
+
callID: part.callID,
|
|
66
|
+
error: error instanceof Error ? error.message : String(error),
|
|
67
|
+
})
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const subAgentResultText = buildSubagentResultText(subAgentMessages)
|
|
72
|
+
if (!subAgentResultText) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
state.subAgentResultCache.set(part.callID, subAgentResultText)
|
|
77
|
+
part.state.output = stripHallucinationsFromString(
|
|
78
|
+
mergeSubagentResult(part.state.output, subAgentResultText),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|