@@ -74,13 +74,15 @@ import type { SDKResultSuccess } from '../entrypoints/sdk/coreTypes.js'
7474import type { PermissionMode } from '../utils/permissions/PermissionMode.js'
7575
7676/**
77- * StdoutMessage with session_id added. The transport layer adds session_id
78- * to messages at runtime, but the Zod schemas don't include it. This type
79- * makes it explicit that we're adding session_id to each message variant.
77+ * StdoutMessage with optional session_id. The transport layer accepts
78+ * StdoutMessage but we add session_id at runtime. Using optional because
79+ * the type system can't verify that adding session_id to a union type
80+ * is always valid, even though it is at runtime.
81+ *
82+ * We need to use 'as StdoutMessage' when passing to transport because
83+ * TypeScript can't verify that objects with session_id are valid StdoutMessage.
8084 */
81- type StdoutMessageWithSession = StdoutMessage extends infer T
82- ? T & { session_id : string }
83- : never
85+ type TransportMessage = StdoutMessage & { session_id ?: string }
8486
8587const ANTHROPIC_VERSION = '2023-06-01'
8688
@@ -619,17 +621,17 @@ export async function initEnvLessBridgeCore(
619621 const msgs = flushGate . end ( )
620622 if ( msgs . length === 0 ) return
621623 for ( const msg of msgs ) recentPostedUUIDs . add ( msg . uuid )
622- const events : StdoutMessageWithSession [ ] = toSDKMessages ( msgs ) . map ( m => ( {
624+ const events : TransportMessage [ ] = toSDKMessages ( msgs ) . map ( m => ( {
623625 ...m ,
624626 session_id : sessionId ,
625- } ) )
627+ } ) ) as TransportMessage [ ]
626628 if ( msgs . some ( m => m . type === 'user' ) ) {
627629 transport . reportState ( 'running' )
628630 }
629631 logForDebugging (
630632 `[remote-bridge] Drained ${ msgs . length } queued message(s) after flush` ,
631633 )
632- void transport . writeBatch ( events )
634+ void transport . writeBatch ( events as StdoutMessage [ ] )
633635 }
634636
635637 async function flushHistory ( msgs : Message [ ] ) : Promise < void > {
@@ -647,10 +649,10 @@ export async function initEnvLessBridgeCore(
647649 `[remote-bridge] Capped initial flush: ${ eligible . length } -> ${ capped . length } (cap=${ initialHistoryCap } )` ,
648650 )
649651 }
650- const events : StdoutMessageWithSession [ ] = toSDKMessages ( capped ) . map ( m => ( {
652+ const events : TransportMessage [ ] = toSDKMessages ( capped ) . map ( m => ( {
651653 ...m ,
652654 session_id : sessionId ,
653- } ) )
655+ } ) ) as TransportMessage [ ]
654656 if ( events . length === 0 ) return
655657 // Mid-turn init: if Remote Control is enabled while a query is running,
656658 // the last eligible message is a user prompt or tool_result (both 'user'
@@ -663,7 +665,7 @@ export async function initEnvLessBridgeCore(
663665 transport . reportState ( 'running' )
664666 }
665667 logForDebugging ( `[remote-bridge] Flushing ${ events . length } history events` )
666- await transport . writeBatch ( events )
668+ await transport . writeBatch ( events as StdoutMessage [ ] )
667669 }
668670
669671 // ── 9. Teardown ───────────────────────────────────────────────────────────
@@ -686,11 +688,11 @@ export async function initEnvLessBridgeCore(
686688 // explicit sleep. close() sets closed=true which interrupts drain at the
687689 // next while-check, so close-before-archive drops the result.
688690 transport . reportState ( 'idle' )
689- const resultMsg : StdoutMessageWithSession = {
691+ const resultMsg = {
690692 ...makeResultMessage ( sessionId ) ,
691693 session_id : sessionId ,
692- }
693- void transport . write ( resultMsg )
694+ } as unknown as TransportMessage
695+ void transport . write ( resultMsg as StdoutMessage )
694696 let token = getAccessToken ( )
695697 let status = await archiveSession (
696698 sessionId ,
@@ -809,10 +811,10 @@ export async function initEnvLessBridgeCore(
809811 }
810812
811813 for ( const msg of filtered ) recentPostedUUIDs . add ( msg . uuid )
812- const events : StdoutMessageWithSession [ ] = toSDKMessages ( filtered ) . map ( m => ( {
814+ const events : TransportMessage [ ] = toSDKMessages ( filtered ) . map ( m => ( {
813815 ...m ,
814816 session_id : sessionId ,
815- } ) )
817+ } ) ) as TransportMessage [ ]
816818 // v2 does not derive worker_status from events server-side (unlike v1
817819 // session-ingress session_status_updater.go). Push it from here so the
818820 // CCR web session list shows Running instead of stuck on Idle. A user
@@ -822,7 +824,7 @@ export async function initEnvLessBridgeCore(
822824 transport . reportState ( 'running' )
823825 }
824826 logForDebugging ( `[remote-bridge] Sending ${ filtered . length } message(s)` )
825- void transport . writeBatch ( events )
827+ void transport . writeBatch ( events as StdoutMessage [ ] )
826828 } ,
827829 writeSdkMessages ( messages : SDKMessage [ ] ) {
828830 const filtered = messages . filter (
@@ -842,11 +844,11 @@ export async function initEnvLessBridgeCore(
842844 )
843845 return
844846 }
845- const event : StdoutMessageWithSession = { ...request , session_id : sessionId }
847+ const event : TransportMessage = { ...request , session_id : sessionId } as TransportMessage
846848 if ( ( request as { request ?: { subtype ?: string } } ) . request ?. subtype === 'can_use_tool' ) {
847849 transport . reportState ( 'requires_action' )
848850 }
849- void transport . write ( event )
851+ void transport . write ( event as StdoutMessage )
850852 logForDebugging (
851853 `[remote-bridge] Sent control_request request_id=${ request . request_id } ` ,
852854 )
@@ -858,9 +860,9 @@ export async function initEnvLessBridgeCore(
858860 )
859861 return
860862 }
861- const event : StdoutMessageWithSession = { ...response , session_id : sessionId }
863+ const event : TransportMessage = { ...response , session_id : sessionId } as TransportMessage
862864 transport . reportState ( 'running' )
863- void transport . write ( event )
865+ void transport . write ( event as StdoutMessage )
864866 logForDebugging ( '[remote-bridge] Sent control_response' )
865867 } ,
866868 sendControlCancelRequest ( requestId : string ) {
@@ -870,16 +872,16 @@ export async function initEnvLessBridgeCore(
870872 )
871873 return
872874 }
873- const event : StdoutMessageWithSession = {
875+ const event : TransportMessage = {
874876 type : 'control_cancel_request' as const ,
875877 request_id : requestId ,
876878 session_id : sessionId ,
877- }
879+ } as TransportMessage
878880 // Hook/classifier/channel/recheck resolved the permission locally —
879881 // interactiveHandler calls only cancelRequest (no sendResponse) on
880882 // those paths, so without this the server stays on requires_action.
881883 transport . reportState ( 'running' )
882- void transport . write ( event )
884+ void transport . write ( event as StdoutMessage )
883885 logForDebugging (
884886 `[remote-bridge] Sent control_cancel_request request_id=${ requestId } ` ,
885887 )
@@ -890,11 +892,11 @@ export async function initEnvLessBridgeCore(
890892 return
891893 }
892894 transport . reportState ( 'idle' )
893- const resultMsg : StdoutMessageWithSession = {
895+ const resultMsg = {
894896 ...makeResultMessage ( sessionId ) ,
895897 session_id : sessionId ,
896- }
897- void transport . write ( resultMsg )
898+ } as unknown as TransportMessage
899+ void transport . write ( resultMsg as StdoutMessage )
898900 logForDebugging ( `[remote-bridge] Sent result` )
899901 } ,
900902 async teardown ( ) {
0 commit comments