requestCopilotDeviceGrant function
Requests a device code (goal: device_flow.go requestDeviceCode).
Throws CopilotDeviceFlowError with kind CopilotDeviceFlowErrorKind
.endpointDisabled when GitHub rejects the app's device flow (a 404 or
a JSON error body — the Phase-0 live finding), naming clientId and
the paste-a-token fallback; transport errors carry the HTTP detail.
Implementation
Future<CopilotDeviceGrant> requestCopilotDeviceGrant({
String clientId = copilotDeviceClientId,
String scope = copilotDeviceScope,
http.Client? client,
}) async {
final transport = client ?? sharedProviderHttpClient();
final http.Response response;
try {
response = await transport
.post(
Uri.parse(copilotDeviceGrantUrl),
headers: const {'accept': 'application/json'},
body: {'client_id': clientId, 'scope': scope},
)
.timeout(effectiveProviderConnectTimeout);
} on CopilotDeviceFlowError {
rethrow;
} on Object catch (error) {
throw CopilotDeviceFlowError(
CopilotDeviceFlowErrorKind.transport,
'Copilot device-code request failed: $error',
);
}
final body = _decodeJsonObject(response);
if (body == null) {
throw CopilotDeviceFlowError(
CopilotDeviceFlowErrorKind.transport,
'Copilot device-code request returned a non-JSON response '
'(HTTP ${response.statusCode}): ${response.body.trim()}',
);
}
final errorField = body['error'];
if (response.statusCode != 200 ||
(errorField is String && errorField.isNotEmpty)) {
throw _endpointDisabled(clientId, response.statusCode, response.body);
}
final deviceCode = body['device_code'];
final userCode = body['user_code'];
if (deviceCode is! String || userCode is! String) {
throw CopilotDeviceFlowError(
CopilotDeviceFlowErrorKind.transport,
'Copilot device-code response had an unexpected shape: '
'${response.body.trim()}',
);
}
return CopilotDeviceGrant(
deviceCode: deviceCode,
userCode: userCode,
verificationUri: switch (body['verification_uri']) {
final String uri when uri.isNotEmpty => uri,
_ => 'https://github.com/login/device',
},
expiresIn: switch (body['expires_in']) {
final int n => n,
_ => 900,
},
interval: switch (body['interval']) {
final int n => n,
_ => 5,
},
);
}