Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,14 @@ public struct AntigravityStatusProbe: Sendable {
let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: self.timeout)
let connectPort = try await Self.findWorkingPort(
ports: ports,
extensionPort: processInfo.extensionPort,
csrfToken: processInfo.csrfToken,
timeout: self.timeout)
let context = RequestContext(
httpsPort: connectPort,
httpPort: processInfo.extensionPort,
csrfToken: processInfo.csrfToken,
extensionCsrfToken: processInfo.extensionCsrfToken,
timeout: self.timeout)

do {
Expand All @@ -297,6 +299,7 @@ public struct AntigravityStatusProbe: Sendable {
let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: self.timeout)
let connectPort = try await Self.findWorkingPort(
ports: ports,
extensionPort: processInfo.extensionPort,
csrfToken: processInfo.csrfToken,
timeout: self.timeout)
let response = try await Self.makeRequest(
Expand All @@ -307,6 +310,7 @@ public struct AntigravityStatusProbe: Sendable {
httpsPort: connectPort,
httpPort: processInfo.extensionPort,
csrfToken: processInfo.csrfToken,
extensionCsrfToken: processInfo.extensionCsrfToken,
timeout: self.timeout))
return try Self.parsePlanInfoSummary(response)
}
Expand Down Expand Up @@ -405,6 +409,7 @@ public struct AntigravityStatusProbe: Sendable {
let pid: Int
let extensionPort: Int?
let csrfToken: String
let extensionCsrfToken: String?
let commandLine: String
}

Expand All @@ -428,7 +433,8 @@ public struct AntigravityStatusProbe: Sendable {
sawAntigravity = true
guard let token = Self.extractFlag("--csrf_token", from: match.command) else { continue }
let port = Self.extractPort("--extension_server_port", from: match.command)
return ProcessInfoResult(pid: match.pid, extensionPort: port, csrfToken: token, commandLine: match.command)
let extToken = Self.extractFlag("--extension_server_csrf_token", from: match.command)
return ProcessInfoResult(pid: match.pid, extensionPort: port, csrfToken: token, extensionCsrfToken: extToken, commandLine: match.command)
}

if sawAntigravity {
Expand Down Expand Up @@ -507,22 +513,66 @@ public struct AntigravityStatusProbe: Sendable {
return ports.sorted()
}

enum PortProbeResult {
case success
case httpError
case unreachable
}

private static func findWorkingPort(
ports: [Int],
extensionPort: Int?,
csrfToken: String,
timeout: TimeInterval) async throws -> Int
{
var httpErrorPort: Int?
for port in ports {
let ok = await Self.testPortConnectivity(port: port, csrfToken: csrfToken, timeout: timeout)
if ok { return port }
let result = await Self.testPortConnectivity(port: port, csrfToken: csrfToken, timeout: timeout)
switch result {
case .success:
return port
case .httpError:
if httpErrorPort == nil {
httpErrorPort = port
}
case .unreachable:
break
}
}
if let httpErrorPort {
self.log.debug("Port probe fell back to HTTP-reachable candidate", metadata: [
"port": "\(httpErrorPort)",
])
return httpErrorPort
}
if let fallback = self.fallbackProbePort(ports: ports, extensionPort: extensionPort) {
self.log.debug("Port probe fell back to best-effort candidate", metadata: [
"port": "\(fallback)",
])
return fallback
}
throw AntigravityStatusProbeError.portDetectionFailed("no working API port found")
}

static func fallbackProbePort(ports: [Int], extensionPort: Int?) -> Int? {
if let nonExtension = ports.first(where: { $0 != extensionPort }) {
return nonExtension
}
if let extensionPort {
return extensionPort
}
return ports.first
}

static func isReachableProbeError(_ error: Error) -> Bool {
guard case let AntigravityStatusProbeError.apiError(message) = error else { return false }
return message.hasPrefix("HTTP ")
}

private static func testPortConnectivity(
port: Int,
csrfToken: String,
timeout: TimeInterval) async -> Bool
timeout: TimeInterval) async -> PortProbeResult
{
do {
_ = try await self.makeRequest(
Expand All @@ -533,14 +583,22 @@ public struct AntigravityStatusProbe: Sendable {
httpsPort: port,
httpPort: nil,
csrfToken: csrfToken,
extensionCsrfToken: nil,
timeout: timeout))
return true
return .success
} catch {
if self.isReachableProbeError(error) {
self.log.debug("Port probe received HTTP response; treating port as reachable", metadata: [
"port": "\(port)",
"error": error.localizedDescription,
])
return .httpError
}
self.log.debug("Port probe failed", metadata: [
"port": "\(port)",
"error": error.localizedDescription,
])
return false
return .unreachable
}
}

Expand All @@ -555,6 +613,7 @@ public struct AntigravityStatusProbe: Sendable {
let httpsPort: Int
let httpPort: Int?
let csrfToken: String
let extensionCsrfToken: String?
let timeout: TimeInterval
}

Expand Down Expand Up @@ -598,12 +657,18 @@ public struct AntigravityStatusProbe: Sendable {
payload: payload,
context: context)
} catch {
guard let httpPort = context.httpPort, httpPort != context.httpsPort else { throw error }
guard let httpPort = context.httpPort else { throw error }
let fallbackContext = RequestContext(
httpsPort: httpPort,
httpPort: nil,
csrfToken: context.extensionCsrfToken ?? context.csrfToken,
extensionCsrfToken: nil,
timeout: context.timeout)
return try await Self.sendRequest(
scheme: "http",
port: httpPort,
payload: payload,
context: context)
context: fallbackContext)
}
}

Expand Down Expand Up @@ -693,23 +758,19 @@ private final class LocalhostSessionDelegate: NSObject {
extension LocalhostSessionDelegate: URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
let result = self.challengeResult(challenge)
completionHandler(result.disposition, result.credential)
didReceive challenge: URLAuthenticationChallenge
) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
self.challengeResult(challenge)
}
}

extension LocalhostSessionDelegate: URLSessionTaskDelegate {
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
let result = self.challengeResult(challenge)
completionHandler(result.disposition, result.credential)
didReceive challenge: URLAuthenticationChallenge
) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
self.challengeResult(challenge)
}

private func challengeResult(_ challenge: URLAuthenticationChallenge) -> (
Expand Down
30 changes: 30 additions & 0 deletions Tests/CodexBarTests/AntigravityStatusProbeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,34 @@ struct AntigravityStatusProbeTests {
#expect(usage.accountEmail(for: .antigravity) == "test@example.com")
#expect(usage.loginMethod(for: .antigravity) == "Pro")
}

@Test
func `http probe errors still count as reachable`() {
#expect(
AntigravityStatusProbe.isReachableProbeError(
AntigravityStatusProbeError.apiError("HTTP 403: Forbidden")))
#expect(
AntigravityStatusProbe.isReachableProbeError(
AntigravityStatusProbeError.apiError("HTTP 404: Not Found")))
#expect(
!AntigravityStatusProbe.isReachableProbeError(
AntigravityStatusProbeError.apiError("Invalid response")))
#expect(!AntigravityStatusProbe.isReachableProbeError(AntigravityStatusProbeError.notRunning))
}

@Test
func `fallback probe port prefers non extension candidate`() {
#expect(
AntigravityStatusProbe.fallbackProbePort(
ports: [51170, 61775],
extensionPort: 61775) == 51170)
#expect(
AntigravityStatusProbe.fallbackProbePort(
ports: [61775],
extensionPort: 61775) == 61775)
#expect(
AntigravityStatusProbe.fallbackProbePort(
ports: [51170, 61775],
extensionPort: nil) == 51170)
}
}