@@ -7,27 +7,65 @@ import { logger } from "../../utils/logger.js";
77import { t } from "../../i18n/index.js" ;
88import { editBotText } from "../utils/telegram-text.js" ;
99
10+ const SERVER_READY_TIMEOUT_MS = 10_000 ;
11+ const SERVER_READY_POLL_INTERVAL_MS = 500 ;
12+ const HEALTH_CHECK_TIMEOUT_MS = 3_000 ;
13+ const HEALTH_CHECK_TIMED_OUT = Symbol ( "health-check-timed-out" ) ;
14+
15+ type HealthCheckResult = Awaited < ReturnType < typeof opencodeClient . global . health > > ;
16+
17+ async function healthWithTimeout (
18+ timeoutMs : number = HEALTH_CHECK_TIMEOUT_MS ,
19+ ) : Promise < HealthCheckResult | typeof HEALTH_CHECK_TIMED_OUT > {
20+ const controller = new AbortController ( ) ;
21+ let timeout : ReturnType < typeof setTimeout > | undefined ;
22+
23+ try {
24+ return await Promise . race ( [
25+ opencodeClient . global . health ( { signal : controller . signal } ) ,
26+ new Promise < typeof HEALTH_CHECK_TIMED_OUT > ( ( resolve ) => {
27+ timeout = setTimeout ( ( ) => {
28+ controller . abort ( ) ;
29+ resolve ( HEALTH_CHECK_TIMED_OUT ) ;
30+ } , timeoutMs ) ;
31+ } ) ,
32+ ] ) ;
33+ } finally {
34+ if ( timeout ) {
35+ clearTimeout ( timeout ) ;
36+ }
37+ }
38+ }
39+
40+ async function getHealthIfAvailable ( ) : Promise < HealthCheckResult | null > {
41+ try {
42+ const result = await healthWithTimeout ( ) ;
43+ if ( result === HEALTH_CHECK_TIMED_OUT ) {
44+ logger . warn ( `[Bot] OpenCode health check timed out after ${ HEALTH_CHECK_TIMEOUT_MS } ms` ) ;
45+ return null ;
46+ }
47+
48+ return result ;
49+ } catch {
50+ return null ;
51+ }
52+ }
53+
1054/**
1155 * Wait for OpenCode server to become ready by polling health endpoint
1256 * @param maxWaitMs Maximum time to wait in milliseconds
1357 * @returns true if server became ready, false if timeout
1458 */
1559async function waitForServerReady ( maxWaitMs : number = 10000 ) : Promise < boolean > {
1660 const startTime = Date . now ( ) ;
17- const pollInterval = 500 ;
1861
1962 while ( Date . now ( ) - startTime < maxWaitMs ) {
20- try {
21- const { data, error } = await opencodeClient . global . health ( ) ;
22-
23- if ( ! error && data ?. healthy ) {
24- return true ;
25- }
26- } catch {
27- // Server not ready yet
63+ const health = await getHealthIfAvailable ( ) ;
64+ if ( health ?. data ?. healthy ) {
65+ return true ;
2866 }
2967
30- await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) ) ;
68+ await new Promise ( ( resolve ) => setTimeout ( resolve , SERVER_READY_POLL_INTERVAL_MS ) ) ;
3169 }
3270
3371 return false ;
@@ -47,9 +85,10 @@ export async function opencodeStartCommand(ctx: CommandContext<Context>) {
4785
4886 // Check if server is already accessible.
4987 try {
50- const { data, error } = await opencodeClient . global . health ( ) ;
88+ const health = await getHealthIfAvailable ( ) ;
89+ const data = health ?. data ;
5190
52- if ( ! error && data ?. healthy ) {
91+ if ( data ?. healthy ) {
5392 await ctx . reply (
5493 t ( "opencode_start.already_running" , { version : data . version || t ( "common.unknown" ) } ) ,
5594 ) ;
@@ -82,7 +121,7 @@ export async function opencodeStartCommand(ctx: CommandContext<Context>) {
82121 childProcess . unref ( ) ;
83122
84123 logger . info ( "[Bot] Waiting for OpenCode server to become ready..." ) ;
85- const ready = await waitForServerReady ( 10000 ) ;
124+ const ready = await waitForServerReady ( SERVER_READY_TIMEOUT_MS ) ;
86125
87126 if ( ! ready ) {
88127 await editBotText ( {
@@ -96,7 +135,7 @@ export async function opencodeStartCommand(ctx: CommandContext<Context>) {
96135 return ;
97136 }
98137
99- const { data : health } = await opencodeClient . global . health ( ) ;
138+ const health = ( await getHealthIfAvailable ( ) ) ?. data ;
100139 await editBotText ( {
101140 api : ctx . api ,
102141 chatId : ctx . chat . id ,
0 commit comments