11import path from 'path'
22
33import { callMainPrompt } from '@codebuff/agent-runtime/main-prompt'
4+ import { getCancelledAdditionalMessages } from '@codebuff/agent-runtime/util/messages'
45import { MAX_AGENT_STEPS_DEFAULT } from '@codebuff/common/constants/agents'
56import { getMCPClient, listMCPTools } from '@codebuff/common/mcp/client'
67import { toOptionalFile } from '@codebuff/common/old-constants'
@@ -100,17 +101,6 @@ export type RunOptions = {
100101 signal?: AbortSignal
101102}
102103
103- class AbortError extends Error {
104- name = 'AbortError'
105- }
106-
107- function checkAborted(signal?: AbortSignal): AbortError | null {
108- if (!signal?.aborted) {
109- return null
110- }
111- return new AbortError('Run cancelled by user')
112- }
113-
114104type RunReturnType = Awaited<ReturnType<typeof run>>
115105export async function run({
116106 apiKey,
@@ -180,18 +170,6 @@ export async function run({
180170 logger,
181171 })
182172 }
183- {
184- const aborted = checkAborted(signal)
185- if (aborted) {
186- return {
187- sessionState,
188- output: {
189- type: 'error',
190- message: aborted.message,
191- },
192- }
193- }
194- }
195173
196174 let resolve: (value: RunReturnType) => any = () => {}
197175 const promise = new Promise<RunReturnType>((res) => {
@@ -204,23 +182,50 @@ export async function run({
204182 }
205183 }
206184
185+ let pendingAgentResponse = ''
186+ /** Calculates the current session state if cancelled.
187+ *
188+ * This includes the user'e message and pending assistant message.
189+ */
190+ function getCancelledSessionState(): SessionState {
191+ const state = cloneDeep(sessionState)
192+ state.mainAgentState.messageHistory.push(
193+ ...getCancelledAdditionalMessages({
194+ prompt,
195+ params,
196+ pendingAgentResponse,
197+ }),
198+ )
199+ return state
200+ }
201+ function getCancelledRunState(): RunState {
202+ return {
203+ sessionState: getCancelledSessionState(),
204+ output: {
205+ type: 'error',
206+ message: 'Run cancelled by user',
207+ },
208+ }
209+ }
210+
207211 const buffers: Record<string | 0, string> = { 0: '' }
208212
209213 const onResponseChunk = async (
210214 action: ServerAction<'response-chunk'>,
211215 ): Promise<void> => {
212- const aborted = checkAborted(signal)
213- if (aborted) {
214- resolve({
215- sessionState,
216- output: {
217- type: 'error',
218- message: aborted.message,
219- },
220- })
216+ if (signal?.aborted) {
221217 return
222218 }
223219 const { chunk } = action
220+ addToPendingAssistantMessage: if (typeof chunk === 'string') {
221+ pendingAgentResponse += chunk
222+ } else if (
223+ chunk.type === 'reasoning_delta' &&
224+ chunk.ancestorRunIds.length === 0
225+ ) {
226+ pendingAgentResponse += chunk.text
227+ }
228+
224229 if (typeof chunk !== 'string') {
225230 if (chunk.type === 'reasoning_delta') {
226231 handleStreamChunk?.({
@@ -256,15 +261,7 @@ export async function run({
256261 const onSubagentResponseChunk = async (
257262 action: ServerAction<'subagent-response-chunk'>,
258263 ) => {
259- const aborted = checkAborted(signal)
260- if (aborted) {
261- resolve({
262- sessionState,
263- output: {
264- type: 'error',
265- message: aborted.message,
266- },
267- })
264+ if (signal?.aborted) {
268265 return
269266 }
270267 const { agentId, agentType, chunk } = action
@@ -406,19 +403,6 @@ export async function run({
406403 const promptId = Math.random().toString(36).substring(2, 15)
407404
408405 // Send input
409- {
410- const aborted = checkAborted(signal)
411- if (aborted) {
412- return {
413- sessionState,
414- output: {
415- type: 'error',
416- message: aborted.message,
417- },
418- }
419- }
420- }
421-
422406 const userInfo = await getUserInfoFromApiKey({
423407 ...agentRuntimeImpl,
424408 apiKey,
@@ -429,6 +413,13 @@ export async function run({
429413 }
430414 const userId = userInfo.id
431415
416+ signal?.addEventListener('abort', () => {
417+ resolve(getCancelledRunState())
418+ })
419+ if (signal?.aborted) {
420+ return getCancelledRunState()
421+ }
422+
432423 callMainPrompt({
433424 ...agentRuntimeImpl,
434425 promptId,
0 commit comments