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