-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpayload.js
More file actions
225 lines (194 loc) · 6.45 KB
/
Copy pathpayload.js
File metadata and controls
225 lines (194 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import { toChatText } from './utils.js'
const chatByteBudget = 120_000
const chatMaxSummaryChars = 3_600
const chatMaxConversationMessages = 14
const systemPromptMessage = [
'You are an expert software development assistant focused on CSS dialects and JSX syntax across React and native DOM APIs.',
'Prioritize practical, safe, and minimal changes that fit the current project architecture.',
'When proposing concrete editor edits, prefer tool calls so the user can explicitly review and apply changes.',
'Do not assume framework migrations unless the user asks.',
].join(' ')
const toUtf8ByteLength = value => {
const text = typeof value === 'string' ? value : ''
return new TextEncoder().encode(text).length
}
const summarizeConversationSlice = messages => {
if (!Array.isArray(messages) || messages.length === 0) {
return ''
}
const lines = []
for (const message of messages) {
const role = message.role === 'assistant' ? 'Assistant' : 'User'
const content = toChatText(message.content)
if (!content) {
continue
}
const clipped = content.length > 280 ? `${content.slice(0, 280)}...` : content
lines.push(`- ${role}: ${clipped}`)
}
const summary = lines.join('\n').trim()
if (!summary) {
return ''
}
if (summary.length <= chatMaxSummaryChars) {
return summary
}
return `${summary.slice(0, chatMaxSummaryChars)}...`
}
const mergeConversationSummary = ({ existingSummary, droppedMessages }) => {
const droppedSummary = summarizeConversationSlice(droppedMessages)
if (!droppedSummary) {
return existingSummary
}
const merged = [existingSummary, droppedSummary].filter(Boolean).join('\n')
if (merged.length <= chatMaxSummaryChars) {
return merged
}
return `${merged.slice(0, chatMaxSummaryChars)}...`
}
const toModeDisplayText = value => {
const mode = toChatText(value)
return mode || 'unknown'
}
const toModeKey = value => toChatText(value).toLowerCase()
const collectModePolicyContext = ({ renderMode, styleMode }) => {
const renderModeText = toModeDisplayText(renderMode)
const styleModeText = toModeDisplayText(styleMode)
const renderModeKey = toModeKey(renderMode)
const styleModeKey = toModeKey(styleMode)
const policyLines = [
'Mode-aware policy:',
`- Render mode: ${renderModeText}`,
`- Style mode: ${styleModeText}`,
'- Preserve the selected style dialect and avoid cross-dialect rewrites unless the user explicitly asks for conversion.',
]
if (renderModeKey === 'dom') {
policyLines.push(
'- In DOM mode, avoid React hook/state guidance unless the user explicitly asks for React migration.',
)
policyLines.push(
'- In DOM mode, JSX is compiled for @knighted/jsx DOM runtime and should not be treated as a React application by default.',
)
policyLines.push(
'- Prefer native DOM APIs, event listeners, and direct browser-compatible patterns.',
)
policyLines.push(
'- Do not suggest React imports, hooks, or React-only runtime APIs unless the user explicitly requests React mode or migration.',
)
}
if (renderModeKey === 'react') {
policyLines.push('- In React mode, prefer component-based React guidance.')
}
if (styleModeKey === 'css') {
policyLines.push(
'- Keep style advice compatible with plain CSS unless user asks for a preprocessor.',
)
}
if (styleModeKey === 'module') {
policyLines.push(
'- In CSS modules mode, keep class names module-scoped and preserve CSS module semantics.',
)
policyLines.push(
'- Avoid converting CSS modules files to global CSS unless the user explicitly asks.',
)
}
if (styleModeKey === 'less') {
policyLines.push(
'- In Less mode, prefer Less-compatible syntax and avoid Sass-specific directives/features.',
)
}
if (styleModeKey === 'sass') {
policyLines.push(
'- In Sass mode, prefer Sass/SCSS-compatible syntax and avoid Less-specific directives/features.',
)
}
return policyLines.join('\n')
}
const collectSystemRolePrompt = ({ renderMode, styleMode }) => {
return [systemPromptMessage, collectModePolicyContext({ renderMode, styleMode })].join(
'\n\n',
)
}
const collectConversation = messages => {
return messages
.filter(message => message.role === 'user' || message.role === 'assistant')
.map(message => ({
role: message.role,
content: toChatText(message.content),
}))
.filter(message => Boolean(message.content))
}
export const buildOutboundMessages = ({
messages,
repositoryContext,
editorContext,
renderMode,
styleMode,
existingSummary,
}) => {
const normalizedRepositoryContext = toChatText(repositoryContext)
const systemMessages = [
{
role: 'system',
content: collectSystemRolePrompt({ renderMode, styleMode }),
},
...(normalizedRepositoryContext
? [{ role: 'system', content: normalizedRepositoryContext }]
: []),
...(editorContext ? [{ role: 'system', content: editorContext }] : []),
]
const conversation = collectConversation(messages)
let retainedConversation = conversation.slice(-chatMaxConversationMessages)
let droppedConversation = conversation.slice(
0,
Math.max(0, conversation.length - retainedConversation.length),
)
let nextSummary = existingSummary
if (droppedConversation.length > 0) {
nextSummary = mergeConversationSummary({
existingSummary: nextSummary,
droppedMessages: droppedConversation,
})
}
let payloadMessages = [
...systemMessages,
...(nextSummary
? [
{
role: 'system',
content: `Conversation summary of earlier turns:\n${nextSummary}`,
},
]
: []),
...retainedConversation,
]
while (
toUtf8ByteLength(JSON.stringify({ messages: payloadMessages })) > chatByteBudget &&
retainedConversation.length > 2
) {
droppedConversation = [...droppedConversation, retainedConversation.shift()]
if (droppedConversation.length > 0) {
nextSummary = mergeConversationSummary({
existingSummary: nextSummary,
droppedMessages: droppedConversation,
})
droppedConversation = []
}
payloadMessages = [
...systemMessages,
...(nextSummary
? [
{
role: 'system',
content: `Conversation summary of earlier turns:\n${nextSummary}`,
},
]
: []),
...retainedConversation,
]
}
return {
outboundMessages: payloadMessages,
nextSummary,
}
}