diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index 947fadad07c3..bcc90b7b1d5c 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -120,6 +120,10 @@ class TokenRefreshRequest extends Schema.Class("TokenRefres const clientId = "opencode-cli" const eagerRefreshThreshold = Duration.minutes(5) +const eagerRefreshThresholdMs = Duration.toMillis(eagerRefreshThreshold) + +const isTokenFresh = (tokenExpiry: number | null, now: number) => + tokenExpiry != null && tokenExpiry > now + eagerRefreshThresholdMs const mapAccountServiceError = (message = "Account service operation failed") => @@ -219,7 +223,7 @@ export namespace Account { const account = maybeAccount.value const now = yield* Clock.currentTimeMillis - if (account.token_expiry && account.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) { + if (isTokenFresh(account.token_expiry, now)) { return account.access_token } @@ -229,7 +233,7 @@ export namespace Account { const resolveToken = Effect.fnUntraced(function* (row: AccountRow) { const now = yield* Clock.currentTimeMillis - if (row.token_expiry && row.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) { + if (isTokenFresh(row.token_expiry, now)) { return row.access_token } diff --git a/packages/opencode/test/account/service.test.ts b/packages/opencode/test/account/service.test.ts index e4c43a1f64f8..85ab259f1da6 100644 --- a/packages/opencode/test/account/service.test.ts +++ b/packages/opencode/test/account/service.test.ts @@ -18,6 +18,9 @@ const truncate = Layer.effectDiscard( const it = testEffect(Layer.merge(AccountRepo.layer, truncate)) +const insideEagerRefreshWindow = Duration.toMillis(Duration.minutes(1)) +const outsideEagerRefreshWindow = Duration.toMillis(Duration.minutes(10)) + const live = (client: HttpClient.HttpClient) => Account.layer.pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client))) @@ -63,7 +66,7 @@ it.live("orgsByAccount groups orgs per account", () => url: "https://one.example.com", accessToken: AccessToken.make("at_1"), refreshToken: RefreshToken.make("rt_1"), - expiry: Date.now() + 10 * 60_000, + expiry: Date.now() + outsideEagerRefreshWindow, orgID: Option.none(), }), ) @@ -75,7 +78,7 @@ it.live("orgsByAccount groups orgs per account", () => url: "https://two.example.com", accessToken: AccessToken.make("at_2"), refreshToken: RefreshToken.make("rt_2"), - expiry: Date.now() + 10 * 60_000, + expiry: Date.now() + outsideEagerRefreshWindow, orgID: Option.none(), }), ) @@ -159,7 +162,7 @@ it.live("token refreshes before expiry when inside the eager refresh window", () url: "https://one.example.com", accessToken: AccessToken.make("at_old"), refreshToken: RefreshToken.make("rt_old"), - expiry: Date.now() + 60_000, + expiry: Date.now() + insideEagerRefreshWindow, orgID: Option.none(), }), ) @@ -267,7 +270,7 @@ it.live("config sends the selected org header", () => url: "https://one.example.com", accessToken: AccessToken.make("at_1"), refreshToken: RefreshToken.make("rt_1"), - expiry: Date.now() + 10 * 60_000, + expiry: Date.now() + outsideEagerRefreshWindow, orgID: Option.none(), }), )