@@ -108,6 +108,16 @@ const isReasoningEffort = (value: unknown): value is 'minimal' | 'low' | 'medium
108108const isVerbosity = ( value : unknown ) : value is 'low' | 'medium' | 'high' =>
109109 value === 'low' || value === 'medium' || value === 'high'
110110
111+ const createAbortError = ( ) : Error => {
112+ if ( typeof DOMException !== 'undefined' ) {
113+ return new DOMException ( 'Aborted' , 'AbortError' )
114+ }
115+
116+ const error = new Error ( 'Aborted' )
117+ error . name = 'AbortError'
118+ return error
119+ }
120+
111121export class DeepChatAgentPresenter implements IAgentImplementation {
112122 private readonly llmProviderPresenter : ILlmProviderPresenter
113123 private readonly configPresenter : IConfigPresenter
@@ -1012,6 +1022,23 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
10121022 return undefined
10131023 }
10141024
1025+ private getAbortSignalForSession ( sessionId : string ) : AbortSignal | undefined {
1026+ return (
1027+ this . activeGenerations . get ( sessionId ) ?. abortController . signal ??
1028+ this . abortControllers . get ( sessionId ) ?. signal
1029+ )
1030+ }
1031+
1032+ private throwIfAbortRequested ( signal ?: AbortSignal ) : void {
1033+ if ( signal ?. aborted ) {
1034+ throw createAbortError ( )
1035+ }
1036+ }
1037+
1038+ private isAbortError ( error : unknown ) : boolean {
1039+ return error instanceof Error && ( error . name === 'AbortError' || error . name === 'CanceledError' )
1040+ }
1041+
10151042 private dispatchResolvedToolHook ( params : {
10161043 sessionId : string
10171044 messageId : string
@@ -1433,7 +1460,8 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
14331460 toolName : tool . toolName ,
14341461 toolArgs : tool . toolArgs ,
14351462 content : tool . content ,
1436- isError : tool . isError
1463+ isError : tool . isError ,
1464+ abortSignal : abortController . signal
14371465 } )
14381466 } ,
14391467 io : {
@@ -2883,7 +2911,8 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
28832911 toolName,
28842912 toolArgs : toolCall . params || '{}' ,
28852913 content : rawData . content ,
2886- isError : rawData . isError === true
2914+ isError : rawData . isError === true ,
2915+ abortSignal : this . getAbortSignalForSession ( sessionId )
28872916 } )
28882917 const responseText = this . toolContentToText ( normalizedContent )
28892918 const prepared = await this . toolOutputGuard . prepareToolOutput ( {
@@ -2981,11 +3010,13 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
29813010 toolArgs : string
29823011 content : MCPToolResponse [ 'content' ]
29833012 isError : boolean
3013+ abortSignal ?: AbortSignal
29843014 } ) : Promise < MCPToolResponse [ 'content' ] > {
29853015 if ( params . isError ) {
29863016 return params . content
29873017 }
29883018
3019+ const abortSignal = params . abortSignal ?? this . getAbortSignalForSession ( params . sessionId )
29893020 const screenshotPayload = this . extractScreenshotToolPayload (
29903021 params . toolName ,
29913022 params . toolArgs ,
@@ -2995,12 +3026,15 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
29953026 return params . content
29963027 }
29973028
2998- const visionModel = await this . resolveScreenshotVisionModel ( params . sessionId )
2999- if ( ! visionModel ) {
3000- return 'Screenshot captured, but automatic English analysis is unavailable because neither the current session model nor the agent vision model can analyze images.'
3001- }
3002-
30033029 try {
3030+ this . throwIfAbortRequested ( abortSignal )
3031+ const visionModel = await this . resolveScreenshotVisionModel ( params . sessionId , abortSignal )
3032+ this . throwIfAbortRequested ( abortSignal )
3033+
3034+ if ( ! visionModel ) {
3035+ return 'Screenshot captured, but automatic English analysis is unavailable because neither the current session model nor the agent vision model can analyze images.'
3036+ }
3037+
30043038 const messages : ChatMessage [ ] = [
30053039 {
30063040 role : 'user' ,
@@ -3029,14 +3063,20 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
30293063 messages ,
30303064 visionModel . modelId ,
30313065 modelConfig ?. temperature ?? 0.2 ,
3032- Math . min ( modelConfig ?. maxTokens ?? 900 , 900 )
3066+ Math . min ( modelConfig ?. maxTokens ?? 900 , 900 ) ,
3067+ abortSignal ? { signal : abortSignal } : undefined
30333068 )
3069+ this . throwIfAbortRequested ( abortSignal )
30343070 const normalized = response . trim ( )
30353071 if ( ! normalized ) {
30363072 return 'Screenshot captured, but automatic English analysis returned no usable description.'
30373073 }
30383074 return normalized
30393075 } catch ( error ) {
3076+ if ( this . isAbortError ( error ) ) {
3077+ return 'Screenshot captured, but automatic English analysis was canceled.'
3078+ }
3079+
30403080 const message = error instanceof Error ? error . message : String ( error )
30413081 console . warn ( '[DeepChatAgent] Failed to normalize screenshot tool output:' , {
30423082 sessionId : params . sessionId ,
@@ -3110,8 +3150,10 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
31103150 }
31113151
31123152 private async resolveScreenshotVisionModel (
3113- sessionId : string
3153+ sessionId : string ,
3154+ abortSignal ?: AbortSignal
31143155 ) : Promise < { providerId : string ; modelId : string } | null > {
3156+ this . throwIfAbortRequested ( abortSignal )
31153157 const state = this . runtimeState . get ( sessionId )
31163158 const dbSession = this . sessionStore . get ( sessionId )
31173159 const agentId = this . getSessionAgentId ( sessionId ) ?? 'deepchat'
@@ -3120,8 +3162,10 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
31203162 modelId : state ?. modelId ?? dbSession ?. model_id ,
31213163 agentId,
31223164 configPresenter : this . configPresenter ,
3165+ signal : abortSignal ,
31233166 logLabel : `screenshot:${ sessionId } `
31243167 } )
3168+ this . throwIfAbortRequested ( abortSignal )
31253169
31263170 if ( ! resolved ) {
31273171 return null
@@ -3130,6 +3174,7 @@ export class DeepChatAgentPresenter implements IAgentImplementation {
31303174 if ( resolved . source === 'agent-vision-model' ) {
31313175 const agentSupportsVision =
31323176 ( await this . configPresenter . agentSupportsCapability ?.( agentId , 'vision' ) ) === true
3177+ this . throwIfAbortRequested ( abortSignal )
31333178 if ( ! agentSupportsVision ) {
31343179 return null
31353180 }
0 commit comments