MCPcopy
hub / github.com/coder/mux / askSideQuestion

Function askSideQuestion

src/node/services/sideQuestionService.ts:166–511  ·  view source on GitHub ↗
(
  opts: AskSideQuestionOptions
)

Source from the content-addressed store, hash-verified

164}
165
166export async function askSideQuestion(
167 opts: AskSideQuestionOptions
168): Promise<Result<AskSideQuestionSuccess, NameGenerationError>> {
169 const { workspaceId, question, candidates, aiService, historyService, emitChatEvent } = opts;
170
171 const trimmedQuestion = question.trim();
172 if (trimmedQuestion.length === 0) {
173 return Err({ type: "unknown", raw: "Side question is empty" });
174 }
175
176 if (candidates.length === 0) {
177 return Err({ type: "unknown", raw: "No model candidates available for side question" });
178 }
179
180 // ---------------------------------------------------------------------
181 // 0. Snapshot any in-flight MAIN-AGENT stream so the renderer can later
182 // split the interrupted message into pre-aside + post-aside halves
183 // around this /btw pair.
184 //
185 // Two structural guarantees make this safe to read synchronously:
186 // - `aiService.getStreamInfo` is a sync getter over an in-memory map
187 // (StreamManager.workspaceStreams), so no race with disk I/O.
188 // - The /btw pipeline bypasses StreamManager entirely (no
189 // `streamManager.startStream` call), so `getStreamInfo` can ONLY
190 // return a main-agent stream — never a concurrent /btw. The
191 // "side question must not interrupt itself" filter is therefore
192 // structural rather than a runtime check.
193 //
194 // MUST run before the first `await` below: any awaited work between
195 // this read and the user-message append widens the racy window where
196 // the main agent could finish streaming (StreamingMessageAggregator
197 // would then no longer have the interruption anchor we promise).
198 // ---------------------------------------------------------------------
199 const liveStreamSnapshot =
200 opts.liveStreamSnapshot ?? snapshotSideQuestionLiveStream(aiService.getStreamInfo(workspaceId));
201 const interruption = liveStreamSnapshot
202 ? {
203 interruptedMessageId: liveStreamSnapshot.messageId,
204 // Text length anchors the split across text parts; part index keeps
205 // non-text parts (reasoning/tool/file) that were already visible at
206 // the same text offset on the pre-aside side after reload.
207 interruptedTextLength: liveStreamSnapshot.parts.reduce(
208 (sum, p) => (p.type === "text" ? sum + p.text.length : sum),
209 0
210 ),
211 interruptedPartIndex: liveStreamSnapshot.parts.length,
212 interruptedHistorySequence: liveStreamSnapshot.historySequence,
213 }
214 : undefined;
215
216 // ---------------------------------------------------------------------
217 // 1. Persist + emit the user's /btw message FIRST so the question shows
218 // up in the chat immediately. Even if the model call fails, the user
219 // can see what they asked.
220 // ---------------------------------------------------------------------
221 const rawCommand = `${SIDE_QUESTION_COMMAND} ${trimmedQuestion}`;
222 const userMessage: MuxMessage = createMuxMessage(createUserMessageId(), "user", trimmedQuestion, {
223 timestamp: Date.now(),

Callers 2

askSideQuestionMethod · 0.90

Calls 15

ErrFunction · 0.90
createMuxMessageFunction · 0.90
createUserMessageIdFunction · 0.90
mapModelCreationErrorFunction · 0.90
createAssistantMessageIdFunction · 0.90
runLanguageModelCleanupFunction · 0.90
mapNameGenerationErrorFunction · 0.90
OkFunction · 0.90

Tested by

no test coverage detected