55 type ProviderApprovalDecision ,
66 type ProviderRuntimeEvent ,
77 type ProviderSession ,
8+ type ProviderUserInputAnswers ,
89 ProviderDriverKind ,
910 ProviderInstanceId ,
1011 RuntimeRequestId ,
@@ -56,6 +57,11 @@ import {
5657 makeGrokAcpRuntime ,
5758 resolveGrokAcpBaseModelId ,
5859} from "../acp/GrokAcpSupport.ts" ;
60+ import {
61+ extractXAiAskUserQuestions ,
62+ makeXAiAskUserQuestionResponse ,
63+ XAiAskUserQuestionRequest ,
64+ } from "../acp/XAiAcpExtension.ts" ;
5965import { type GrokAdapterShape } from "../Services/GrokAdapter.ts" ;
6066import { type EventNdjsonLogger , makeEventNdjsonLogger } from "./EventNdjsonLogger.ts" ;
6167
@@ -73,13 +79,17 @@ export interface GrokAdapterLiveOptions {
7379 readonly environment ?: NodeJS . ProcessEnv ;
7480 readonly nativeEventLogPath ?: string ;
7581 readonly nativeEventLogger ?: EventNdjsonLogger ;
76- readonly instanceId ?: typeof ProviderInstanceId . Type ;
82+ readonly instanceId ?: ProviderInstanceId ;
7783}
7884
7985interface PendingApproval {
8086 readonly decision : Deferred . Deferred < ProviderApprovalDecision > ;
8187}
8288
89+ interface PendingUserInput {
90+ readonly answers : Deferred . Deferred < ProviderUserInputAnswers > ;
91+ }
92+
8393interface GrokSessionContext {
8494 readonly threadId : ThreadId ;
8595 readonly acpSessionId : string ;
@@ -88,6 +98,7 @@ interface GrokSessionContext {
8898 readonly acp : AcpSessionRuntimeShape ;
8999 notificationFiber : Fiber . Fiber < void , never > | undefined ;
90100 readonly pendingApprovals : Map < ApprovalRequestId , PendingApproval > ;
101+ readonly pendingUserInputs : Map < ApprovalRequestId , PendingUserInput > ;
91102 turns : Array < { id : TurnId ; items : Array < unknown > } > ;
92103 lastPlanFingerprint : string | undefined ;
93104 activeTurnId : TurnId | undefined ;
@@ -105,6 +116,16 @@ function settlePendingApprovalsAsCancelled(
105116 ) ;
106117}
107118
119+ function settlePendingUserInputsAsEmptyAnswers (
120+ pendingUserInputs : ReadonlyMap < ApprovalRequestId , PendingUserInput > ,
121+ ) : Effect . Effect < void > {
122+ return Effect . forEach (
123+ Array . from ( pendingUserInputs . values ( ) ) ,
124+ ( pending ) => Deferred . succeed ( pending . answers , { } ) . pipe ( Effect . ignore ) ,
125+ { discard : true } ,
126+ ) ;
127+ }
128+
108129function isRecord ( value : unknown ) : value is Record < string , unknown > {
109130 return typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
110131}
@@ -287,6 +308,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
287308 if ( ctx . stopped ) return ;
288309 ctx . stopped = true ;
289310 yield * settlePendingApprovalsAsCancelled ( ctx . pendingApprovals ) ;
311+ yield * settlePendingUserInputsAsEmptyAnswers ( ctx . pendingUserInputs ) ;
290312 if ( ctx . notificationFiber ) {
291313 yield * Fiber . interrupt ( ctx . notificationFiber ) ;
292314 }
@@ -329,6 +351,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
329351 }
330352
331353 const pendingApprovals = new Map < ApprovalRequestId , PendingApproval > ( ) ;
354+ const pendingUserInputs = new Map < ApprovalRequestId , PendingUserInput > ( ) ;
332355 const sessionScope = yield * Scope . make ( "sequential" ) ;
333356 let sessionScopeTransferred = false ;
334357 yield * Effect . addFinalizer ( ( ) =>
@@ -363,6 +386,53 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
363386 ) ,
364387 ) ;
365388 const started = yield * Effect . gen ( function * ( ) {
389+ yield * Effect . forEach (
390+ [ "x.ai/ask_user_question" , "_x.ai/ask_user_question" ] as const ,
391+ ( method ) =>
392+ acp . handleExtRequest ( method , XAiAskUserQuestionRequest , ( params ) =>
393+ mapAcpCallbackFailure (
394+ Effect . gen ( function * ( ) {
395+ yield * logNative ( input . threadId , method , params ) ;
396+ const requestId = ApprovalRequestId . make ( yield * randomUUIDv4 ) ;
397+ const runtimeRequestId = RuntimeRequestId . make ( requestId ) ;
398+ const answers = yield * Deferred . make < ProviderUserInputAnswers > ( ) ;
399+ pendingUserInputs . set ( requestId , { answers } ) ;
400+ yield * offerRuntimeEvent ( {
401+ type : "user-input.requested" ,
402+ ...( yield * makeEventStamp ( ) ) ,
403+ provider : PROVIDER ,
404+ threadId : input . threadId ,
405+ turnId : sessions . get ( input . threadId ) ?. activeTurnId ,
406+ requestId : runtimeRequestId ,
407+ payload : { questions : extractXAiAskUserQuestions ( params ) } ,
408+ raw : {
409+ source : "acp.grok.extension" ,
410+ method,
411+ payload : params ,
412+ } ,
413+ } ) ;
414+ const resolved = yield * Deferred . await ( answers ) ;
415+ pendingUserInputs . delete ( requestId ) ;
416+ yield * offerRuntimeEvent ( {
417+ type : "user-input.resolved" ,
418+ ...( yield * makeEventStamp ( ) ) ,
419+ provider : PROVIDER ,
420+ threadId : input . threadId ,
421+ turnId : sessions . get ( input . threadId ) ?. activeTurnId ,
422+ requestId : runtimeRequestId ,
423+ payload : { answers : resolved } ,
424+ raw : {
425+ source : "acp.grok.extension" ,
426+ method,
427+ payload : params ,
428+ } ,
429+ } ) ;
430+ return makeXAiAskUserQuestionResponse ( resolved ) ;
431+ } ) ,
432+ ) ,
433+ ) ,
434+ { discard : true } ,
435+ ) ;
366436 yield * acp . handleRequestPermission ( ( params ) =>
367437 mapAcpCallbackFailure (
368438 Effect . gen ( function * ( ) {
@@ -470,6 +540,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
470540 acp,
471541 notificationFiber : undefined ,
472542 pendingApprovals,
543+ pendingUserInputs,
473544 turns : [ ] ,
474545 lastPlanFingerprint : undefined ,
475546 activeTurnId : undefined ,
@@ -733,6 +804,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
733804 Effect . gen ( function * ( ) {
734805 const ctx = yield * requireSession ( threadId ) ;
735806 yield * settlePendingApprovalsAsCancelled ( ctx . pendingApprovals ) ;
807+ yield * settlePendingUserInputsAsEmptyAnswers ( ctx . pendingUserInputs ) ;
736808 yield * Effect . ignore (
737809 ctx . acp . cancel . pipe (
738810 Effect . mapError ( ( error ) =>
@@ -760,14 +832,22 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
760832 yield * Deferred . succeed ( pending . decision , decision ) ;
761833 } ) ;
762834
763- const respondToUserInput : GrokAdapterShape [ "respondToUserInput" ] = ( threadId , requestId ) =>
835+ const respondToUserInput : GrokAdapterShape [ "respondToUserInput" ] = (
836+ threadId ,
837+ requestId ,
838+ answers ,
839+ ) =>
764840 Effect . gen ( function * ( ) {
765- yield * requireSession ( threadId ) ;
766- return yield * new ProviderAdapterRequestError ( {
767- provider : PROVIDER ,
768- method : "user-input/respond" ,
769- detail : `Grok has no pending user-input request: ${ requestId } ` ,
770- } ) ;
841+ const ctx = yield * requireSession ( threadId ) ;
842+ const pending = ctx . pendingUserInputs . get ( requestId ) ;
843+ if ( ! pending ) {
844+ return yield * new ProviderAdapterRequestError ( {
845+ provider : PROVIDER ,
846+ method : "_x.ai/ask_user_question" ,
847+ detail : `Unknown pending user-input request: ${ requestId } ` ,
848+ } ) ;
849+ }
850+ yield * Deferred . succeed ( pending . answers , answers ) ;
771851 } ) ;
772852
773853 const readThread : GrokAdapterShape [ "readThread" ] = ( threadId ) =>
0 commit comments