DEV Community

Hagicode
Hagicode

Posted on

How to Integrate GPT, Claude, and Other AI Models Using Copilot CLI

How to Integrate GPT, Claude, and Other AI Models Using Copilot CLI

In AI application development, how can you use a unified interface to integrate multiple models like GPT and Claude? This article shares our AI provider system design based on Orleans Grain architecture and practical GitHub Copilot CLI integration experience.

Background

In modern AI application development, integrating the latest GPT models is a core requirement for many developers. GitHub Copilot CLI is a powerful tool that not only supports OpenAI's GPT series models (such as GPT-4, GPT-5), but also other mainstream AI models like Claude. Through Copilot CLI, developers can call different AI models using a unified command-line interface without implementing complex integration logic for each model separately.

Actually, this is a long-standing issue. Having to write call logic for each model is just painful—too much heartache. After all, no one enjoys writing repetitive code, and rather than reinventing the wheel, it's better to find a unified interface to handle everything. Copilot CLI is exactly that kind of existence—you just call it, and let it handle the rest.

Core Values:

  • Unified CLI interface for accessing multiple AI models
  • Support for session management and context retention
  • Built-in tool calling capabilities (file operations, Git operations, etc.)
  • Support for streaming responses and real-time output

About HagiCode

The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an AI code assistant project. During development, we encountered the challenge of needing to support multiple AI models simultaneously—some users are accustomed to using GPT-4, some prefer Claude, and others want to try the latest GPT-5. If we implemented separate call logic for each model, the code would become difficult to maintain. Through Copilot CLI's unified interface, we successfully solved this multi-model support pain point.

To put it plainly, users just have diverse tastes—difficult to please everyone. Some like GPT, some prefer Claude, and others insist on using the latest GPT-5. We just want everyone to be able to use their favorite model—after all, being happy is what matters most.

System Architecture Design

We implemented an extensible AI provider system through Orleans Grain architecture with the following overall structure:

┌─────────────────┐
│  Frontend/Client │
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────┐
│  IGitHubCopilotGrain (Interface)│
│  - ExecuteCommandStreamAsync    │
│  - RunEditAsync                 │
│  - CancelAsync                  │
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  GitHubCopilotGrain (Implementation)│
│  - State Management             │
│  - Session Binding              │
│  - Response Mapping             │
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  CopilotAIProvider (Provider)   │
│  - Configuration Parsing        │
│  - Permission Management        │
│  - Streaming Processing         │
└────────┬────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│  HagiCode.Libs (Shared Runtime) │
│  - Copilot CLI Process Management│
│  - Message Protocol Parsing     │
│  - Session Retention            │
└─────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The advantage of this architecture is clear layering with single responsibilities. The interface layer defines the unified AI service contract, the implementation layer handles Orleans' distributed state management, the provider layer encapsulates Copilot CLI interaction details, and the underlying runtime is responsible for communicating with the CLI process.

To put it simply, clarify who does what—don't mix things up. After all, once code gets messy, it's hard to change later.

Core Component Analysis

1. GitHubCopilotGrain: Distributed AI Service Interface

As an Orleans Grain implementation, GitHubCopilotGrain provides distributed AI service capabilities:

public interface IGitHubCopilotGrain : IGrainWithStringKey
{
    /// <summary>
    /// Execute command and stream response
    /// </summary>
    Task<IAsyncEnumerable<GitHubCopilotResponse>> ExecuteCommandStreamAsync(
        string command,
        string? heroId = null,
        CancellationToken token = default,
        string? executionMessageId = null,
        string? systemMessage = null,
        Dictionary<string, string>? requestSettings = null);

    /// <summary>
    /// Execute edit operation
    /// </summary>
    Task<IAsyncEnumerable<GitHubCopilotResponse>> RunEditAsync(
        string editCommand,
        string? heroId = null,
        CancellationToken token = default);

    /// <summary>
    /// Cancel current execution
    /// </summary>
    Task CancelAsync(string heroId);
}
Enter fullscreen mode Exit fullscreen mode

Key Design Points:

  • Using IAsyncEnumerable to support streaming responses, avoiding long wait times
  • Session-level state isolation through heroId
  • Support for passing requestSettings to dynamically configure model parameters

2. CopilotAIProvider: Core Provider Implementation

CopilotAIProvider is the core of the entire solution, encapsulating all interaction logic with Copilot CLI:

public class CopilotAIProvider : IAIProvider, IVersionedAIProvider
{
    private readonly CopilotOptions _options;
    private readonly ICopilotProcessExecutor _executor;

    public async IAsyncEnumerable<AIStreamingChunk> SendMessageAsync(
        AIRequest request,
        string? embeddedCommandPrompt = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        // Build execution options
        var options = new CopilotOptions
        {
            Model = request.Model ?? _options.Model,
            SessionId = request.Options?.Settings?.GetValueOrDefault("copilotSessionId"),
            Timeout = _options.Timeout,
            PermissionMode = request.OperationType == AIOperationType.Edit
                ? CopilotPermissionMode.BypassPermissions
                : CopilotPermissionMode.Default
        };

        // Execute command and stream process response
        await foreach (var message in _executor.ExecuteAsync(
            options, request.Prompt, cancellationToken))
        {
            yield return BuildChunk(message);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Core Features:

  • Automatic retry mechanism: Handles transient network issues and CLI process exceptions
  • Reasoning content tracking: Captures the model's reasoning process (reasoning field)
  • Multiple message type handling: Supports assistant, tool.started, tool.completed and other messages
  • Permission mode switching: Edit operations automatically use bypassPermissions, regular queries use default

3. CopilotOptions: Flexible Configuration System

The configuration class supports rich option settings:

public class CopilotOptions
{
    /// <summary>
    /// Specify the model to use, such as "gpt-4", "gpt-5", "claude-opus-4.5"
    /// </summary>
    public string Model { get; set; } = "gpt-4";

    /// <summary>
    /// Copilot CLI executable path
    /// </summary>
    public string ExecutablePath { get; set; } = "copilot";

    /// <summary>
    /// Session timeout
    /// </summary>
    public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(1800);

    /// <summary>
    /// Authentication method
    /// </summary>
    public CopilotAuthSource AuthSource { get; set; } = CopilotAuthSource.LoggedInUser;

    /// <summary>
    /// Permission mode
    /// </summary>
    public CopilotPermissionMode PermissionMode { get; set; } = CopilotPermissionMode.Default;

    /// <summary>
    /// Session ID for maintaining context
    /// </summary>
    public string? SessionId { get; set; }

    /// <summary>
    /// Tool permission configuration
    /// </summary>
    public CopilotToolPermissions? Permissions { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Configuration is about being adequate—after all, who wants to write a bunch of configurations they'll never use? Covering most scenarios is enough.

Configuration Guide

1. Basic Configuration

Add Copilot provider configuration in appsettings.json:

{
  "AI": {
    "Providers": {
      "Providers": {
        "GitHubCopilot": {
          "Enabled": true,
          "ExecutablePath": "copilot",
          "Model": "gpt-5",
          "Timeout": 1800,
          "IdleTimeout": 300,
          "UseLoggedInUser": true,
          "NoAskUser": true,
          "PermissionMode": "default",
          "Permissions": {
            "AllowAllTools": false,
            "AllowAllPaths": false,
            "AllowedTools": ["Read", "Bash(git:*)", "Bash(cat:*)"],
            "DeniedTools": []
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Model Selection

The system supports the following models (specified via Copilot CLI's --model parameter):

Model Description Recommended Scenarios
gpt-4 / gpt-4-turbo OpenAI 4th generation models General tasks, cost-effective
gpt-5 OpenAI latest 5th generation model Complex reasoning, best performance
claude-sonnet-4.5 Anthropic Sonnet 4.5 Balance performance and cost
claude-opus-4.5 Anthropic Opus 4.5 High-precision tasks

In HagiCode's practice, we use GPT-4 as the default daily model, switch to GPT-5 for complex tasks (like large refactoring), and offer Claude models as an alternative for users who prefer Anthropic.

3. Register Services

Register related services in the DI container:

// Register Copilot AI provider
services.AddSingleton<IAIProvider, CopilotAIProvider>();

// Register Orleans Grain
services.AddSingleton<IGitHubCopilotGrain, GitHubCopilotGrain>();

// Register process executor
services.AddSingleton<ICopilotProcessExecutor, CopilotProcessExecutor>();
Enter fullscreen mode Exit fullscreen mode

Actually just these few lines—nothing special. Just register what needs to be registered so it can be found when needed.

Practice Examples

1. Basic Call

// Get Grain
var grain = grainFactory.GetGrain<IGitHubCopilotGrain>("session-123");

// Execute command
await foreach (var response in grain.ExecuteCommandStreamAsync(
    "Analyze the code structure of the current directory and generate documentation",
    heroId: null,
    token: cancellationToken))
{
    switch (response.Type)
    {
        case ExecutorResponseType.Text:
            Console.Write(response.Content);
            break;
        case ExecutorResponseType.ToolCall:
            Console.WriteLine($"[Tool Call] {response.ToolName}");
            break;
        case ExecutorResponseType.Completion:
            Console.WriteLine($"\n[Complete] Token Usage: {response.PromptTokens}+{response.CompletionTokens}");
            break;
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Context-Aware Session

var requestSettings = new Dictionary<string, string>
{
    { "model", "gpt-5" },
    { "temperature", "0.7" },
    { "maxTokens", "4096" },
    { "copilotSessionId", "existing-session-123" }  // Maintain session context
};

await foreach (var response in grain.ExecuteCommandStreamAsync(
    "Based on the previous analysis, generate corresponding unit tests",
    requestSettings: requestSettings,
    token: cancellationToken))
{
    // Handle response
}
Enter fullscreen mode Exit fullscreen mode

3. Edit Mode Call

await foreach (var response in grain.RunEditAsync(
    "Convert all PascalCase naming to camelCase",
    heroId: "hero-001",
    token: cancellationToken))
{
    if (response.Type == ExecutorResponseType.FileEdit)
    {
        Console.WriteLine($"[Edit] {response.FilePath}: {response.EditCount} changes");
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

Session Retention

Using the copilotSessionId parameter allows maintaining context across requests, which is very useful for scenarios requiring multi-turn dialogue. For example:

// Round 1: Establish context
var settings1 = new Dictionary<string, string> { { "copilotSessionId", "session-001" } };
await grain.ExecuteCommandStreamAsync("This is a C# project using .NET 8", requestSettings: settings1);

// Round 2: Ask based on context
var settings2 = new Dictionary<string, string> { { "copilotSessionId", "session-001" } };
await grain.ExecuteCommandStreamAsync("Recommend suitable project structure", requestSettings: settings2);
Enter fullscreen mode Exit fullscreen mode

After all, AI isn't omnipotent—without context, how does it know what you're talking about? Like chatting, you need back-and-forth to keep the conversation going.

Permission Control

Choose the appropriate permission mode based on operation type:

  • Query operations: Use default mode, allowing AI to only read files and execute safe Git commands
  • Edit operations: Use bypassPermissions mode, allowing AI to modify files
var permissionMode = operationType == AIOperationType.Edit
    ? CopilotPermissionMode.BypassPermissions
    : CopilotPermissionMode.Default;
Enter fullscreen mode Exit fullscreen mode

Tool Whitelist

Control AI executable operations through AllowedTools configuration:

{
  "Permissions": {
    "AllowAllTools": false,
    "AllowedTools": [
      "Read",
      "Bash(git:*)",
      "Bash(cat:*)",
      "Glob"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

In HagiCode, we strictly limit AI's operation permissions, only allowing file reading and Git command execution to ensure system security.

After all, you can't be too careful with security. Who knows if the AI might suddenly delete your entire project?

Timeout Handling

The default timeout is set to 30 minutes. For operations involving large numbers of files (like full code analysis), adjustments may be needed:

var options = new CopilotOptions
{
    Timeout = TimeSpan.FromMinutes(60)  // Extend to 60 minutes
};
Enter fullscreen mode Exit fullscreen mode

Common Questions

Q: How to switch between different AI models?

A: Specify through Model configuration or requestSettings:

var settings = new Dictionary<string, string> { { "model", "claude-opus-4.5" } };
Enter fullscreen mode Exit fullscreen mode

Actually just changing a parameter—nothing complex.

Q: How long can session context be maintained?

A: Depends on Copilot CLI implementation, usually cleaned up after session idle timeout (default 5 minutes). Can be adjusted through IdleTimeout configuration.

Q: How to handle CLI process crashes?

A: CopilotAIProvider has a built-in automatic retry mechanism that captures process exceptions and restarts the CLI. If consecutive failures exceed a threshold, it will throw an AIProviderException.

Program crashes are unavoidable. You can only do your best with fault tolerance—if it really goes down, just restart it.

Q: Are custom tools supported?

A: The tools supported by Copilot CLI are predefined, but you can control which tools are available through AllowedTools configuration. Custom tools require waiting for future Copilot CLI updates.

Summary

By integrating multiple AI models through Copilot CLI, we solved the multi-model support challenge in HagiCode development. The core advantages of this solution are:

  1. Unified Interface: One codebase supporting multiple models like GPT, Claude, etc.
  2. Session Management: Automatic context retention and session isolation
  3. Tool Integration: Built-in common tools like file operations, Git operations
  4. Streaming Response: Real-time AI output returns, improved user experience
  5. Security and Control: Fine-grained permission control and tool whitelisting

If your project also needs to support multiple AI models, or you're looking for a mature CLI tool integration solution, why not try Copilot CLI? This architecture has been fully validated in HagiCode and can handle complex production environment requirements.

After all, who wants to write separate call code for each model? Having a unified solution saves everyone trouble.

References

If this article helps you:

Top comments (0)