@@ -1642,6 +1642,60 @@ export const RUN_SSE_EMPTY_RESPONSE_ERROR =
16421642export const RUN_SSE_INCOMPLETE_RESPONSE_ERROR =
16431643 formatRunSseError ( "HTTP 200,SSE 响应中没有可展示的模型回复。" ) ;
16441644
1645+ const RUN_SSE_FIRST_EVENT_TIMEOUT_MS = 30_000 ;
1646+ export const RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR =
1647+ formatRunSseError ( "30 秒内未收到首个 SSE 事件。" ) ;
1648+
1649+ interface RunSseFirstEventDeadline {
1650+ signal ?: AbortSignal ;
1651+ clearDeadline : ( ) => void ;
1652+ cleanup : ( ) => void ;
1653+ timedOut : ( ) => boolean ;
1654+ }
1655+
1656+ function runSseFirstEventDeadline (
1657+ signal : AbortSignal | undefined ,
1658+ ) : RunSseFirstEventDeadline {
1659+ if ( signal ?. aborted ) {
1660+ return {
1661+ signal,
1662+ clearDeadline : ( ) => { } ,
1663+ cleanup : ( ) => { } ,
1664+ timedOut : ( ) => false ,
1665+ } ;
1666+ }
1667+
1668+ const controller = new AbortController ( ) ;
1669+ let didTimeout = false ;
1670+ let deadlineCleared = false ;
1671+ const clearDeadline = ( ) => {
1672+ if ( deadlineCleared ) return ;
1673+ deadlineCleared = true ;
1674+ clearTimeout ( timer ) ;
1675+ } ;
1676+ const onAbort = ( ) => {
1677+ if ( controller . signal . aborted ) return ;
1678+ controller . abort ( signal ?. reason ?? new DOMException ( "Aborted" , "AbortError" ) ) ;
1679+ } ;
1680+ const timer = setTimeout ( ( ) => {
1681+ if ( deadlineCleared || controller . signal . aborted ) return ;
1682+ didTimeout = true ;
1683+ deadlineCleared = true ;
1684+ controller . abort ( new Error ( RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR ) ) ;
1685+ } , RUN_SSE_FIRST_EVENT_TIMEOUT_MS ) ;
1686+ signal ?. addEventListener ( "abort" , onAbort , { once : true } ) ;
1687+
1688+ return {
1689+ signal : controller . signal ,
1690+ clearDeadline,
1691+ cleanup : ( ) => {
1692+ clearDeadline ( ) ;
1693+ signal ?. removeEventListener ( "abort" , onAbort ) ;
1694+ } ,
1695+ timedOut : ( ) => didTimeout ,
1696+ } ;
1697+ }
1698+
16451699/** Stream agent events for one user turn. */
16461700export async function * runSSE ( {
16471701 appName,
@@ -1698,6 +1752,7 @@ export async function* runSSE({
16981752 } ;
16991753 }
17001754 let res : Response ;
1755+ const firstEventDeadline = runSseFirstEventDeadline ( signal ) ;
17011756 try {
17021757 res = await apiFetch (
17031758 "/run_sse" ,
@@ -1717,16 +1772,19 @@ export async function* runSSE({
17171772 ? { veadkInvocation : invocationMetadata }
17181773 : undefined ,
17191774 } ) ,
1720- signal,
1775+ signal : firstEventDeadline . signal ,
17211776 } ,
17221777 ep ,
17231778 0 ,
17241779 ) ;
17251780 } catch ( error ) {
1781+ firstEventDeadline . cleanup ( ) ;
1782+ if ( firstEventDeadline . timedOut ( ) ) throw new Error ( RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR ) ;
17261783 if ( signal ?. aborted || ( error as Error ) ?. name === "AbortError" ) throw error ;
17271784 throw new Error ( formatRunSseError ( error ) ) ;
17281785 }
17291786 if ( ! res . ok ) {
1787+ firstEventDeadline . cleanup ( ) ;
17301788 const detail = await httpErrorMessage ( res , "运行会话失败" ) ;
17311789 throw new Error (
17321790 formatRunSseError ( `run_sse failed: ${ res . status } :${ detail } ` ) ,
@@ -1736,6 +1794,7 @@ export async function* runSSE({
17361794 try {
17371795 for await ( const evt of parseSSE ( res ) ) {
17381796 receivedEvent = true ;
1797+ firstEventDeadline . clearDeadline ( ) ;
17391798 const event = evt as AdkEvent ;
17401799 if ( typeof event . error === "string" ) event . error = formatRunSseError ( event . error ) ;
17411800 if ( typeof event . errorMessage === "string" ) {
@@ -1747,8 +1806,11 @@ export async function* runSSE({
17471806 yield event ;
17481807 }
17491808 } catch ( error ) {
1809+ if ( firstEventDeadline . timedOut ( ) ) throw new Error ( RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR ) ;
17501810 if ( signal ?. aborted || ( error as Error ) ?. name === "AbortError" ) throw error ;
17511811 throw new Error ( formatRunSseError ( error ) ) ;
1812+ } finally {
1813+ firstEventDeadline . cleanup ( ) ;
17521814 }
17531815 if ( ! receivedEvent ) throw new Error ( RUN_SSE_EMPTY_RESPONSE_ERROR ) ;
17541816}
@@ -3678,25 +3740,44 @@ export async function* runGeneratedAgentTestSSE({
36783740 signal ?: AbortSignal ;
36793741} ) : AsyncGenerator < AdkEvent , void , unknown > {
36803742 const parts : Record < string , unknown > [ ] = text . trim ( ) ? [ { text } ] : [ ] ;
3681- const res = await apiFetch (
3682- `/web/generated-agent-test-runs/${ runId } /run_sse` ,
3683- {
3684- method : "POST" ,
3685- headers : { "Content-Type" : "application/json" } ,
3686- body : JSON . stringify ( {
3687- user_id : userId ,
3688- session_id : sessionId ,
3689- new_message : { role : "user" , parts } ,
3690- streaming : true ,
3691- } ) ,
3692- signal,
3693- } ,
3694- { } ,
3695- 0 ,
3696- ) ;
3697- if ( ! res . ok ) throw new Error ( await httpErrorMessage ( res , "调试运行失败" ) ) ;
3698- for await ( const evt of parseSSE ( res ) ) {
3699- yield evt as AdkEvent ;
3743+ const firstEventDeadline = runSseFirstEventDeadline ( signal ) ;
3744+ let res : Response ;
3745+ try {
3746+ res = await apiFetch (
3747+ `/web/generated-agent-test-runs/${ runId } /run_sse` ,
3748+ {
3749+ method : "POST" ,
3750+ headers : { "Content-Type" : "application/json" } ,
3751+ body : JSON . stringify ( {
3752+ user_id : userId ,
3753+ session_id : sessionId ,
3754+ new_message : { role : "user" , parts } ,
3755+ streaming : true ,
3756+ } ) ,
3757+ signal : firstEventDeadline . signal ,
3758+ } ,
3759+ { } ,
3760+ 0 ,
3761+ ) ;
3762+ } catch ( error ) {
3763+ firstEventDeadline . cleanup ( ) ;
3764+ if ( firstEventDeadline . timedOut ( ) ) throw new Error ( RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR ) ;
3765+ throw error ;
3766+ }
3767+ if ( ! res . ok ) {
3768+ firstEventDeadline . cleanup ( ) ;
3769+ throw new Error ( await httpErrorMessage ( res , "调试运行失败" ) ) ;
3770+ }
3771+ try {
3772+ for await ( const evt of parseSSE ( res ) ) {
3773+ firstEventDeadline . clearDeadline ( ) ;
3774+ yield evt as AdkEvent ;
3775+ }
3776+ } catch ( error ) {
3777+ if ( firstEventDeadline . timedOut ( ) ) throw new Error ( RUN_SSE_FIRST_EVENT_TIMEOUT_ERROR ) ;
3778+ throw error ;
3779+ } finally {
3780+ firstEventDeadline . cleanup ( ) ;
37003781 }
37013782}
37023783
0 commit comments