Skip to content

Latest commit

 

History

History
347 lines (282 loc) · 10.5 KB

File metadata and controls

347 lines (282 loc) · 10.5 KB

OpenCode GitHub Copilot Integration: Complete Implementation Guide

Executive Summary

OpenCode implements a sophisticated GitHub Copilot integration using OAuth device flow authentication combined with proprietary token exchange mechanisms. The system acts as a proxy layer, presenting an OpenAI-compatible interface while routing requests to GitHub's internal Copilot endpoints.

Architecture Overview

graph TB
    A[User Request] --> B[OpenCode Proxy Layer]
    B --> C[Authentication Module]
    C --> D[GitHub OAuth Device Flow]
    D --> E[Token Exchange]
    E --> F[Copilot API Token]
    F --> G[Provider Configuration]
    G --> H[Request to Copilot API]
    H --> I[Response Transformation]
    I --> J[OpenAI-Compatible Response]
Loading

Core Components

1. Authentication Module (AuthGithubCopilot)

Location: packages/opencode/src/auth/github-copilot.ts

Device Code Flow Initialization

export async function authorize() {
  const deviceResponse = await fetch(DEVICE_CODE_URL, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      "User-Agent": "GitHubCopilotChat/0.26.7",
    },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      scope: "read:user",
    }),
  });
  
  const deviceData: DeviceCodeResponse = await deviceResponse.json();
  return {
    device: deviceData.device_code,
    user: deviceData.user_code,
    verification: deviceData.verification_uri,
    interval: deviceData.interval || 5,
    expiry: deviceData.expires_in,
  };
}

Purpose:

  • Initiates GitHub's OAuth device flow.
  • Makes a POST request to GitHub's device code endpoint.
  • Uses a specific client ID for Copilot integration with the "read:user" scope.
  • Returns device code, user code, verification URL, polling interval, and expiry.

Token Polling Implementation

export async function poll(device_code: string) {
  const response = await fetch(ACCESS_TOKEN_URL, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      "User-Agent": "GitHubCopilotChat/0.26.7",
    },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      device_code,
      grant_type: "urn:ietf:params:oauth:grant-type:device_code",
    }),
  });

  if (!response.ok) return "failed";

  const data: AccessTokenResponse = await response.json();

  if (data.access_token) {
    await Auth.set("github-copilot", {
      type: "oauth",
      refresh: data.access_token,
      access: "",
      expires: 0,
    });
    return "complete";
  }

  if (data.error === "authorization_pending") return "pending";
  if (data.error) return "failed";

  return "pending";
}

Purpose:

  • Polls GitHub's OAuth endpoint for authentication status.
  • Handles three states: "complete", "pending", or "failed".
  • Stores successful OAuth tokens using the Auth.set method.
  • Implements standard OAuth device flow polling.

Copilot Token Management

export async function access() {
  const info = await Auth.get("github-copilot");
  if (!info || info.type !== "oauth") return;
  if (info.access && info.expires > Date.now()) return info.access;

  const response = await fetch(COPILOT_API_KEY_URL, {
    headers: {
      Accept: "application/json",
      Authorization: `Bearer ${info.refresh}`,
      "User-Agent": "GitHubCopilotChat/0.26.7",
      "Editor-Version": "vscode/1.99.3",
      "Editor-Plugin-Version": "copilot-chat/0.26.7",
    },
  });

  if (!response.ok) return;

  const tokenData: CopilotTokenResponse = await response.json();

  await Auth.set("github-copilot", {
    type: "oauth",
    refresh: info.refresh,
    access: tokenData.token,
    expires: tokenData.expires_at * 1000,
  });

  return tokenData.token;
}

Purpose:

  • Manages Copilot-specific API tokens.
  • Checks for existing valid tokens.
  • Exchanges the OAuth token for a Copilot API token.
  • Handles automatic token refresh.
  • Stores tokens with expiration timestamps.

2. Error Handling System

export const DeviceCodeError = NamedError.create("DeviceCodeError", z.object({}));
export const TokenExchangeError = NamedError.create(
  "TokenExchangeError",
  z.object({ message: z.string() })
);
export const AuthenticationError = NamedError.create(
  "AuthenticationError",
  z.object({ message: z.string() })
);
export const CopilotTokenError = NamedError.create(
  "CopilotTokenError",
  z.object({ message: z.string() })
);

Purpose:

  • Provides robust error handling for different authentication scenarios.
  • Custom error types help differentiate issues during device code flow, token exchange, and Copilot API token management.

3. Provider Integration

Location: packages/opencode/src/provider/provider.ts

Custom Loader Configuration

"github-copilot": async (provider) => {
  const copilot = await AuthCopilot();
  if (!copilot) return { autoload: false };
  let info = await Auth.get("github-copilot");
  if (!info || info.type !== "oauth") return { autoload: false };

  if (provider && provider.models) {
    for (const model of Object.values(provider.models)) {
      model.cost = {
        input: 0,
        output: 0,
      };
    }
  }

  return {
    autoload: true,
    options: {
      apiKey: "",
      async fetch(input: any, init: any) {
        const info = await Auth.get("github-copilot");
        if (!info || info.type !== "oauth") return;
        if (!info.access || info.expires < Date.now()) {
          const tokens = await copilot.access(info.refresh);
          if (!tokens)
            throw new Error("GitHub Copilot authentication expired");
          await Auth.set("github-copilot", {
            type: "oauth",
            ...tokens,
          });
          info.access = tokens.access;
        }
        const headers = {
          ...init.headers,
          ...copilot.HEADERS,
          Authorization: `Bearer ${info.access}`,
          "Openai-Intent": "conversation-edits",
        };
        delete headers["x-api-key"];
        return fetch(input, {
          ...init,
          headers,
        });
      },
    },
  };
}

Key Features:

  • Retrieves Copilot authentication data.
  • Sets cost parameters to zero for models.
  • Provides a custom fetch method that injects proper authorization headers into API calls.
  • Automatically refreshes tokens before making requests.

API Endpoint Architecture

Proxy Layer Design

The system implements a proxy architecture that:

  1. Receives OpenAI-compatible requests.
  2. Validates and parses incoming requests.
  3. Manages token lifecycle automatically.
  4. Transforms requests to fit the Copilot API requirements.
  5. Routes requests to GitHub's internal endpoints.
  6. Transforms responses back to OpenAI-compatible formats.

Request Flow

async function handleRequest(request) {
  // Parse as an OpenAI-compatible request.
  const parsedRequest = parseOpenAIRequest(request);

  // Ensure a valid token is available.
  const token = await getValidToken();

  // Transform and forward the request to the Copilot API endpoint.
  const response = await forwardToCopilot(parsedRequest, token);

  // Transform and return the response in an OpenAI-compatible format.
  return transformToOpenAIResponse(response);
}

Endpoint Compatibility

OpenAI Compatibility Layer:

  • Request Format: Accepts standard OpenAI chat completion requests.
  • Response Format: Returns OpenAI-compatible response structures.
  • Error Handling: Maps Copilot errors to OpenAI error formats.
  • Streaming: Supports streaming responses when available.

Backend Management

For systems like Sonnet 4 or other models, the backend manages integration by:

  1. Unified Interface: A single API endpoint for all models.
  2. Dynamic Routing: Routes requests based on the selected model.
  3. Token Management: Handles provider-specific authentication automatically.
  4. Proxy Configuration: Supports alternate configurations (e.g., LiteLLM) for providers like Anthropic and Claude Code.

Setting Up Claude Code with Proxy (Example)

# Set Anthropic endpoint to proxy
export ANTHROPIC_BASE_URL="http://localhost:8000/v1"
export ANTHROPIC_API_KEY="your-proxy-key"

# Run Claude Code
claude-code

Custom Endpoint Configuration (Example)

// Custom provider configuration for Claude Code via a proxy
const providerConfig = {
  "custom-claude": {
    baseURL: "https://your-proxy.com/v1",
    apiKey: process.env.CUSTOM_API_KEY,
    models: ["claude-3-sonnet", "claude-3-opus"],
    transformRequest: (req) => {
      // Custom request transformation
      return req;
    },
    transformResponse: (res) => {
      // Custom response transformation
      return res;
    }
  }
};

Security Considerations

Token Security

  • OAuth tokens are stored securely in encrypted format.
  • Short-lived Copilot tokens minimize the risk of exposure.
  • Automatic token refresh reduces the need for manual intervention.
  • The device flow avoids continual password exposure.

API Security

  • User-Agent spoofing ensures compatibility.
  • Rate limiting and retry mechanisms are in place.
  • Tokens are transmitted securely over HTTPS.
  • Proper error sanitization is enforced.

Troubleshooting

Common Issues

  1. Authentication Failures
    • Ensure device code has not expired.
    • Verify network connectivity and correct client ID.
  2. Token Refresh Issues
    • Monitor token expiration times.
    • Ensure Copilot API endpoint is available.
    • Verify the Bearer token format in authorization headers.
  3. Proxy Configuration
    • Validate endpoint URLs.
    • Confirm proxy authentication details.
    • Ensure correct request/response transformation.

Conclusion

OpenCode's GitHub Copilot integration demonstrates a sophisticated proxy architecture that seamlessly incorporates proprietary APIs into an OpenAI-compatible interface. The modular design allows easy extension to other providers while maintaining a consistent developer experience. The system's strength lies in its automatic token management, robust error handling, and transparent proxy operation that masks the complexity of multiple authentication flows behind a simple, unified API.


Credits:
Developed with insights from GitHub Copilot (@copilot) and inspired by the OpenCode repository architecture.