()
| 213 | } |
| 214 | |
| 215 | async startDeviceFlow(): Promise< |
| 216 | Result< |
| 217 | { |
| 218 | flowId: string; |
| 219 | userCode: string; |
| 220 | verifyUrl: string; |
| 221 | intervalSeconds: number; |
| 222 | }, |
| 223 | string |
| 224 | > |
| 225 | > { |
| 226 | const flowId = randomBase64Url(); |
| 227 | |
| 228 | const deviceAuthResult = await this.requestDeviceUserCode(); |
| 229 | if (!deviceAuthResult.success) { |
| 230 | return Err(deviceAuthResult.error); |
| 231 | } |
| 232 | |
| 233 | const { deviceAuthId, userCode, intervalSeconds, expiresAtMs } = deviceAuthResult.data; |
| 234 | const verifyUrl = CODEX_OAUTH_DEVICE_VERIFY_URL; |
| 235 | |
| 236 | const { promise: resultPromise, resolve: resolveResult } = |
| 237 | createDeferred<Result<void, string>>(); |
| 238 | |
| 239 | const abortController = new AbortController(); |
| 240 | |
| 241 | const timeoutMs = Math.min(DEFAULT_DEVICE_TIMEOUT_MS, Math.max(0, expiresAtMs - Date.now())); |
| 242 | const timeout = setTimeout(() => { |
| 243 | void this.finishDeviceFlow(flowId, Err("Device code expired")); |
| 244 | }, timeoutMs); |
| 245 | |
| 246 | this.deviceFlows.set(flowId, { |
| 247 | flowId, |
| 248 | deviceAuthId, |
| 249 | userCode, |
| 250 | verifyUrl, |
| 251 | intervalSeconds, |
| 252 | expiresAtMs, |
| 253 | abortController, |
| 254 | pollingStarted: false, |
| 255 | timeout, |
| 256 | cleanupTimeout: null, |
| 257 | resultPromise, |
| 258 | resolveResult, |
| 259 | settled: false, |
| 260 | }); |
| 261 | |
| 262 | log.debug(`[Codex OAuth] Device flow started (flowId=${flowId})`); |
| 263 | |
| 264 | return Ok({ flowId, userCode, verifyUrl, intervalSeconds }); |
| 265 | } |
| 266 | |
| 267 | async waitForDeviceFlow( |
| 268 | flowId: string, |
no test coverage detected