개발할 때 반복적인 코드 작성에 시간을 날리고 있지 않은가. 요즘 많은 팀이 AI 기반 코드 어시스턴트를 도입하는데, 막상 백엔드 서버에 통합하려면 API 문서가 복잡하고 PHP 예제가 거의 없다. 이번에는 GitHub Copilot API를 PHP에서 직접 연동하는 방법을 완벽하게 정리했다. 인증 토큰부터 실시간 스트리밍 응답까지, 실무에서 바로 쓸 수 있는 코드를 소개한다.

 

1단계. GitHub Copilot API 인증 방식 이해하기

GitHub Copilot API는 GitHub의 OAuth 토큰을 기반으로 동작한다. 단순한 API 키가 아니라 GitHub 계정 인증이 필요하다는 뜻이다. 크게 두 가지 방식이 있다.

첫 번째는 개인 액세스 토큰(Personal Access Token)이다. 이것은 GitHub 설정에서 직접 생성하는 토큰으로, 개발 중이나 테스트 환경에서 쓰면 된다. 두 번째는 GitHub App 인증인데, 프로덕션 환경이나 여러 사용자를 관리할 때 권장된다. 이 글에서는 개인 액세스 토큰으로 빠르게 시작하는 방법을 먼저 보자.

Copilot API는 아직 베타 단계라서 API 접근 권한이 제한되어 있다. GitHub에서 waitlist에 등록했는지 확인하고, 승인된 계정으로 토큰을 발급받아야 한다.

 

GitHub Personal Access Token 발급받기

GitHub 홈페이지에 로그인한 후 Settings → Developer settings → Personal access tokens → Tokens(classic)으로 이동한다. New personal access token(classic) 버튼을 클릭하고 다음 정보를 입력한다.

필수 항목:
Token name: 아무 이름이나 지으면 된다. 예) copilot-api-dev
Expiration: 90days 이상 선택(너무 짧으면 자주 갱신해야 함)
Scopes: repo, read:user, user:email 선택

생성된 토큰을 복사해서 안전하게 보관한다. 화면을 나간 후에는 다시 볼 수 없으므로 주의한다.

 

2단계. PHP cURL로 Copilot API 기본 요청 보내기

GitHub Copilot API의 엔드포인트는 https://api.github.com/user/copilot_completions이다. 이 API는 POST 요청으로 코드 스니펫을 전송하면, AI가 예상되는 다음 코드를 반환해준다.

 

✗ 잘못된 코드 - 헤더와 인증 누락
<?php
$code_snippet = "function calculateSum(arr) {";
$ch = curl_init('https://api.github.com/user/copilot_completions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['prompt' => $code_snippet]));
$response = curl_exec($ch);
echo $response;
curl_close($ch);
?>

이 코드는 API 키 인증이 빠져있고, 필수 헤더도 없다. GitHub API는 User-Agent와 Authorization 헤더가 없으면 요청을 거부한다.

 

✓ 올바른 코드 - 완전한 인증과 헤더
<?php
$token = 'ghp_xxxxxxxxxxxx'; // GitHub Personal Access Token
$code_snippet = "function calculateSum(arr) {";

$ch = curl_init('https://api.github.com/user/copilot_completions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: token ' . $token,
    'User-Agent: PHP-Copilot-Client',
    'Accept: application/json',
    'Content-Type: application/json'
]);

$payload = json_encode([
    'prompt' => $code_snippet,
    'max_tokens' => 100
]);

curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

if (($response = curl_exec($ch)) === false) {
    echo 'cURL Error: ' . curl_error($ch);
} else {
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($http_code === 200) {
        $data = json_decode($response, true);
        echo 'Completion: ' . $data['choices'][0]['text'] ?? 'No completion';
    } else {
        echo 'API Error (HTTP ' . $http_code . '): ' . $response;
    }
}

curl_close($ch);
?>

Authorization 헤더에 token을 붙이고, User-Agent를 명시한다. max_tokens로 응답 길이를 제한하면 불필요한 트래픽을 줄일 수 있다. HTTP 상태 코드도 검사해서 401(인증 실패)이나 429(레이트 제한)를 구분한다.

 

3단계. 실전 예제 - 코드 자동완성 기능 구현

실제 프로젝트에서는 토큰을 환경 변수에 저장하고, 에러 처리와 로깅을 추가해야 한다. 다음은 클래스 기반의 실용적인 구현이다.

<?php
class GitHubCopilotClient {
    private $token;
    private $api_url = 'https://api.github.com/user/copilot_completions';
    private $timeout = 30;

    public function __construct($token) {
        $this->token = $token;
    }

    public function getCompletion($code_snippet, $language = 'javascript', $max_tokens = 100) {
        $payload = json_encode([
            'prompt' => $code_snippet,
            'language' => $language,
            'max_tokens' => $max_tokens
        ]);

        $ch = curl_init($this->api_url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_HTTPHEADER => [
                'Authorization: token ' . $this->token,
                'User-Agent: PHP-Copilot-Client/1.0',
                'Accept: application/json',
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS => $payload
        ]);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($response === false) {
            throw new Exception('cURL Error: ' . curl_error($ch));
        }

        curl_close($ch);

        if ($http_code !== 200) {
            $error_data = json_decode($response, true);
            throw new Exception('API Error (' . $http_code . '): ' . ($error_data['message'] ?? 'Unknown error'));
        }

        $data = json_decode($response, true);
        return $data['choices'][0]['text'] ?? null;
    }
}

// 사용 예제
$token = getenv('GITHUB_COPILOT_TOKEN');
$client = new GitHubCopilotClient($token);

try {
    $code = 'const users = [...];
const activeUsers = users.filter(u => ';
    $completion = $client->getCompletion($code, 'javascript', 150);
    echo 'Suggested code: ' . $completion;
} catch (Exception $e) {
    error_log('Copilot API Error: ' . $e->getMessage());
    echo 'Failed to get completion';
}
?>

이 클래스는 재사용 가능하고, 에러 처리가 명확하다. 각 메서드에서 예외를 던져서 호출한 쪽에서 처리하게 하는 패턴이다.

 

4단계. 실시간 스트리밍 응답 처리

GitHub Copilot API는 stream 파라미터를 true로 설정하면 응답을 실시간 스트리밍으로 받을 수 있다. 긴 코드 자동완성의 경우 서버에서 응답을 기다리는 시간이 길어지는데, 스트리밍을 쓰면 클라이언트가 완료 전부터 텍스트를 받아볼 수 있다.

 

✗ 잘못된 코드 - 스트리밍 헤더 누락
<?php
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: token ' . $token,
    'Accept: application/json' // 스트리밍을 명시하지 않음
]);
$response = curl_exec($ch);
// 응답이 완전히 도착할 때까지 블로킹됨
?>

이렇게 하면 API 응답이 모두 완료될 때까지 기다려야 한다. 스트리밍의 이점이 사라진다.

 

✓ 올바른 코드 - SSE 스트리밍 처리
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

$token = getenv('GITHUB_COPILOT_TOKEN');
$code_snippet = $_GET['prompt'] ?? '';

if (empty($code_snippet)) {
    echo 'data: {"error": "No prompt provided"}

';
    flush();
    exit;
}

$payload = json_encode([
    'prompt' => $code_snippet,
    'stream' => true,
    'max_tokens' => 200
]);

$ch = curl_init('https://api.github.com/user/copilot_completions');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Authorization: token ' . $token,
        'User-Agent: PHP-Copilot-Streaming',
        'Accept: text/event-stream',
        'Content-Type: application/json'
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_BUFFERSIZE => 1,
    CURLOPT_TIMEOUT => 60,
    CURLOPT_WRITEFUNCTION => function($curl, $data) {
        $lines = explode("\n", trim($data));
        foreach ($lines as $line) {
            if (strpos($line, 'data: ') === 0) {
                echo $line . "\n\n";
                flush();
            }
        }
        return strlen($data);
    }
]);

curl_exec($ch);
curl_close($ch);
?>

이 코드는 Server-Sent Events(SSE) 형식으로 응답을 클라이언트에게 보낸다. CURLOPT_WRITEFUNCTION 콜백을 써서 응답 데이터를 줄 단위로 처리하고, flush()로 바로 브라우저에 보낸다. Accept 헤더를 text/event-stream으로 설정하는 것이 핵심이다.

 

클라이언트 측 JavaScript로 스트리밍 수신
<?php
echo <<<'HTML'
<script>
const prompt = encodeURIComponent('function sum(a, b) { return ');
const eventSource = new EventSource('copilot-stream.php?prompt=' + prompt);

eventSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    if (data.error) {
        console.error('Error:', data.error);
        eventSource.close();
    } else {
        document.getElementById('output').textContent += data.choices?.[0]?.delta?.text || '';
    }
};

eventSource.onerror = function() {
    console.error('Stream error');
    eventSource.close();
};
</script>
<div id="output"></div>
HTML;
?>

EventSource API를 사용해서 서버에서 보낸 SSE 이벤트를 실시간으로 받는다. 각 데이터 청크가 도착할 때마다 UI가 갱신된다.

 

5단계. 주의사항과 흔한 실수

 

실수 1: 레이트 제한 무시

✗ 잘못된 것: 루프에서 계속 API를 호출하면 429(Too Many Requests) 에러가 난다.

✓ 올바른 것: 응답 헤더의 X-RateLimit-* 값을 확인하고, 필요하면 Redis 등으로 요청 횟수를 제한한다.

<?php
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code === 429) {
    $retry_after = curl_getinfo($ch, CURLINFO_HEADER_OUT);
    error_log('Rate limited. Retry after specified seconds.');
    sleep(60); // 기본 1분 대기
}
?>

 

실수 2: 토큰을 코드에 하드코딩

✗ 잘못된 것: 토큰을 소스 코드에 그대로 넣으면 Git에 커밋될 때 공개된다.

✓ 올바른 것: .env 파일이나 환경 변수로 관리한다.

<?php
// .env 파일
GITHUB_COPILOT_TOKEN=ghp_xxxxxxxxxxxx

// 코드
$token = getenv('GITHUB_COPILOT_TOKEN') ?: $_ENV['GITHUB_COPILOT_TOKEN'];
if (!$token) {
    throw new Exception('GitHub Copilot token not configured');
}
?>

 

실수 3: 스트리밍 응답의 JSON 파싱 오류

✗ 잘못된 것: 스트리밍 데이터를 한 번에 json_decode()하려고 하면 불완전한 JSON이 파싱되어 실패한다.

✓ 올바른 것: 각 라인을 개별적으로 파싱한다.

<?php
CURLOPT_WRITEFUNCTION => function($curl, $data) {
    // 한 번에 여러 라인이 올 수 있음
    $lines = explode("\n", $data);
    foreach ($lines as $line) {
        if (strpos($line, 'data: ') === 0) {
            $json_str = substr($line, 6);
            $json_data = json_decode($json_str, true);
            // $json_data 개별 처리
        }
    }
    return strlen($data);
}
?>

 

마무리

GitHub Copilot API는 코드 자동완성의 미래를 보여준다. 처음엔 복잡해 보이지만, 올바른 인증과 헤더 설정만 되면 PHP에서 충분히 다룰 수 있다. 이 글의 클래스 기반 구현을 참고해서 프로젝트에 넣으면, 개발자들이 반복 작업을 줄일 수 있을 것이다. 특히 스트리밍을 쓰면 사용자 경험이 크게 개선되니, 꼭 시도해보자.