@@ -75,7 +75,7 @@ import { assertConnectionHasOneOfPermissions } from '../security/auth'
7575import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio'
7676import { getRootSubpath } from '../lib'
7777import { evalBlueprint } from './blueprints/cache'
78- import { StudioBlueprintManifest } from '@sofie-automation/blueprints-integration'
78+ import { StudioBlueprintManifest , TSR } from '@sofie-automation/blueprints-integration'
7979import { StatusMessageResolver } from '@sofie-automation/corelib'
8080import { interpollateTranslation } from '@sofie-automation/corelib/dist/TranslatableMessage'
8181import { Blueprint } from '@sofie-automation/corelib/dist/dataModel/Blueprint'
@@ -189,6 +189,82 @@ async function resolveDeviceStatusDetails(
189189 }
190190}
191191
192+ /**
193+ * Resolve a TSR ActionExecutionResult using the Studio blueprint's deviceActionMessages.
194+ * If the result has a structured `code` and `context`, and the blueprint defines a custom
195+ * message template for that code, the `response` field is replaced with the resolved message.
196+ *
197+ * @param deviceId - The peripheral device ID (used to look up the studio and blueprint)
198+ * @param result - The action execution result from TSR
199+ * @returns The result with `response` resolved if a custom message was found
200+ */
201+ export async function resolveActionResult (
202+ deviceId : PeripheralDeviceId ,
203+ result : TSR . ActionExecutionResult
204+ ) : Promise < TSR . ActionExecutionResult > {
205+ if ( result . result === TSR . ActionExecutionResultCode . Ok ) return result
206+ if ( ! result . code ) return result
207+
208+ try {
209+ const device = ( await PeripheralDevices . findOneAsync ( deviceId , {
210+ projection : { name : 1 , studioAndConfigId : 1 } ,
211+ } ) ) as Pick < PeripheralDevice , 'name' | 'studioAndConfigId' > | undefined
212+
213+ if ( ! device ?. studioAndConfigId ?. studioId ) return result
214+
215+ const studio = ( await Studios . findOneAsync ( device . studioAndConfigId . studioId , {
216+ projection : { blueprintId : 1 } ,
217+ } ) ) as Pick < DBStudio , 'blueprintId' > | undefined
218+
219+ if ( ! studio ?. blueprintId ) return result
220+
221+ const blueprint = ( await Blueprints . findOneAsync ( studio . blueprintId , {
222+ projection : { _id : 1 , name : 1 , code : 1 } ,
223+ } ) ) as Pick < Blueprint , '_id' | 'name' | 'code' > | undefined
224+
225+ if ( ! blueprint ) return result
226+
227+ const blueprintManifest = evalBlueprint ( blueprint ) as StudioBlueprintManifest
228+
229+ if ( ! blueprintManifest . deviceActionMessages ) return result
230+
231+ const resolver = new StatusMessageResolver ( blueprint . _id , blueprintManifest . deviceActionMessages , undefined )
232+
233+ // Use the existing TSR response as the fallback default message
234+ const defaultMessage = result . response ?. key ?? ''
235+
236+ const resolved = resolver . getDeviceStatusMessage (
237+ result . code ,
238+ {
239+ ...( result . context ?? { } ) ,
240+ deviceName : device . name ,
241+ deviceId : unprotectString ( deviceId ) ,
242+ } ,
243+ defaultMessage
244+ )
245+
246+ if ( resolved === null ) {
247+ // Message suppressed by blueprint
248+ return result
249+ }
250+
251+ // resolved.key is either the custom blueprint message or the defaultMessage
252+ if ( resolved . key === defaultMessage ) {
253+ // No custom message found - keep original response unchanged
254+ return result
255+ }
256+
257+ const interpolated = interpollateTranslation ( resolved . key , resolved . args )
258+ return {
259+ ...result ,
260+ response : { key : interpolated } ,
261+ }
262+ } catch ( e ) {
263+ logger . error ( `Error resolving device action messages: ${ e } ` )
264+ return result
265+ }
266+ }
267+
192268export namespace ServerPeripheralDeviceAPI {
193269 export async function initialize (
194270 context : MethodContext ,
0 commit comments