mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
feat(copilot): add copilot manager module
- copilot-auth.ts: GitHub OAuth handling via copilot-api - copilot-daemon.ts: daemon lifecycle (start/stop/status) - copilot-models.ts: model catalog with Anthropic/OpenAI models - copilot-executor.ts: profile execution and env generation - types.ts: CopilotStatus, CopilotModel interfaces
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copilot Auth Handler
|
||||
*
|
||||
* Handles GitHub OAuth authentication for copilot-api.
|
||||
*/
|
||||
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import { CopilotAuthStatus, CopilotDebugInfo } from './types';
|
||||
|
||||
/**
|
||||
* Check if copilot-api is installed (available via npx).
|
||||
*/
|
||||
export function isCopilotApiInstalled(): boolean {
|
||||
try {
|
||||
const result = spawnSync('npx', ['copilot-api', '--version'], {
|
||||
stdio: 'pipe',
|
||||
timeout: 10000,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
return result.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get copilot-api debug info.
|
||||
* Returns authentication status and version info.
|
||||
*/
|
||||
export async function getCopilotDebugInfo(): Promise<CopilotDebugInfo | null> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const proc = spawn('npx', ['copilot-api', 'debug', '--json'], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
shell: process.platform === 'win32',
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let _stderr = '';
|
||||
|
||||
proc.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (data) => {
|
||||
_stderr += data.toString();
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
if (code === 0 && stdout) {
|
||||
try {
|
||||
const info = JSON.parse(stdout.trim()) as CopilotDebugInfo;
|
||||
resolve(info);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', () => {
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
// Timeout after 15 seconds
|
||||
setTimeout(() => {
|
||||
proc.kill();
|
||||
resolve(null);
|
||||
}, 15000);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check copilot authentication status.
|
||||
*/
|
||||
export async function checkAuthStatus(): Promise<CopilotAuthStatus> {
|
||||
const debugInfo = await getCopilotDebugInfo();
|
||||
|
||||
if (!debugInfo) {
|
||||
return {
|
||||
authenticated: false,
|
||||
error: 'Could not check auth status. Is copilot-api installed?',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
authenticated: debugInfo.authenticated ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start GitHub OAuth authentication flow.
|
||||
* Opens browser for user to authenticate with GitHub.
|
||||
*
|
||||
* @returns Promise that resolves when auth flow completes
|
||||
*/
|
||||
export function startAuthFlow(): Promise<{ success: boolean; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
console.log('[i] Starting GitHub authentication for Copilot...');
|
||||
console.log('[i] A browser window will open for GitHub OAuth.');
|
||||
console.log('');
|
||||
|
||||
const proc = spawn('npx', ['copilot-api', 'auth'], {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ success: true });
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Authentication failed with exit code ${code}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to start auth: ${err.message}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Copilot Daemon Manager
|
||||
*
|
||||
* Manages the copilot-api daemon lifecycle (start/stop/status).
|
||||
* Only used when auto_start is enabled in config.
|
||||
*/
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as http from 'http';
|
||||
import { CopilotDaemonStatus } from './types';
|
||||
import { CopilotConfig } from '../config/unified-config-types';
|
||||
|
||||
const PID_FILE = path.join(os.homedir(), '.ccs', 'copilot.pid');
|
||||
|
||||
/**
|
||||
* Check if copilot-api daemon is running on the specified port.
|
||||
*/
|
||||
export async function isDaemonRunning(port: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: 'localhost',
|
||||
port,
|
||||
path: '/usage',
|
||||
method: 'GET',
|
||||
timeout: 3000,
|
||||
},
|
||||
(res) => {
|
||||
resolve(res.statusCode === 200);
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get daemon status.
|
||||
*/
|
||||
export async function getDaemonStatus(port: number): Promise<CopilotDaemonStatus> {
|
||||
const running = await isDaemonRunning(port);
|
||||
const pid = getPidFromFile();
|
||||
|
||||
return {
|
||||
running,
|
||||
port,
|
||||
pid: running ? (pid ?? undefined) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read PID from file.
|
||||
*/
|
||||
function getPidFromFile(): number | null {
|
||||
try {
|
||||
if (fs.existsSync(PID_FILE)) {
|
||||
const content = fs.readFileSync(PID_FILE, 'utf8').trim();
|
||||
const pid = parseInt(content, 10);
|
||||
return isNaN(pid) ? null : pid;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write PID to file.
|
||||
*/
|
||||
function writePidToFile(pid: number): void {
|
||||
try {
|
||||
const dir = path.dirname(PID_FILE);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
fs.writeFileSync(PID_FILE, pid.toString(), { mode: 0o600 });
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove PID file.
|
||||
*/
|
||||
function removePidFile(): void {
|
||||
try {
|
||||
if (fs.existsSync(PID_FILE)) {
|
||||
fs.unlinkSync(PID_FILE);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the copilot-api daemon.
|
||||
*
|
||||
* @param config Copilot configuration
|
||||
* @returns Promise that resolves when daemon is ready
|
||||
*/
|
||||
export async function startDaemon(
|
||||
config: CopilotConfig
|
||||
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
// Check if already running
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
return { success: true, pid: getPidFromFile() ?? undefined };
|
||||
}
|
||||
|
||||
const args = ['copilot-api', 'start', '--port', config.port.toString()];
|
||||
|
||||
// Add account type
|
||||
if (config.account_type !== 'individual') {
|
||||
args.push('--account-type', config.account_type);
|
||||
}
|
||||
|
||||
// Add rate limiting
|
||||
if (config.rate_limit !== null && config.rate_limit > 0) {
|
||||
args.push('--rate-limit', config.rate_limit.toString());
|
||||
if (config.wait_on_limit) {
|
||||
args.push('--wait');
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let proc: ChildProcess;
|
||||
|
||||
try {
|
||||
proc = spawn('npx', args, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: true,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
// Unref so parent can exit
|
||||
proc.unref();
|
||||
|
||||
if (proc.pid) {
|
||||
writePidToFile(proc.pid);
|
||||
}
|
||||
|
||||
// Wait for daemon to be ready (poll for up to 30 seconds)
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
const checkInterval = setInterval(async () => {
|
||||
attempts++;
|
||||
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({ success: true, pid: proc.pid });
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Daemon did not start within 30 seconds',
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
proc.on('error', (err) => {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to start daemon: ${err.message}`,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to spawn daemon: ${(err as Error).message}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the copilot-api daemon.
|
||||
*/
|
||||
export async function stopDaemon(): Promise<{ success: boolean; error?: string }> {
|
||||
const pid = getPidFromFile();
|
||||
|
||||
if (!pid) {
|
||||
// No PID file, try to find by port
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
try {
|
||||
// Send SIGTERM to the process
|
||||
process.kill(pid, 'SIGTERM');
|
||||
|
||||
// Wait for process to exit (up to 5 seconds)
|
||||
let attempts = 0;
|
||||
while (attempts < 10) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
try {
|
||||
// Check if process still exists (kill(pid, 0) throws if not)
|
||||
process.kill(pid, 0);
|
||||
attempts++;
|
||||
} catch {
|
||||
// Process no longer exists
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
if (error.code === 'ESRCH') {
|
||||
// Process doesn't exist
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to stop daemon: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copilot Executor
|
||||
*
|
||||
* Main execution flow for running Claude Code with copilot-api proxy.
|
||||
* Similar to CLIProxy executor but for GitHub Copilot.
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { CopilotConfig } from '../config/unified-config-types';
|
||||
import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth';
|
||||
import { isDaemonRunning, startDaemon } from './copilot-daemon';
|
||||
import { CopilotStatus } from './types';
|
||||
|
||||
/**
|
||||
* Get full copilot status (auth + daemon).
|
||||
*/
|
||||
export async function getCopilotStatus(config: CopilotConfig): Promise<CopilotStatus> {
|
||||
const [auth, daemonRunning] = await Promise.all([
|
||||
checkAuthStatus(),
|
||||
isDaemonRunning(config.port),
|
||||
]);
|
||||
|
||||
return {
|
||||
auth,
|
||||
daemon: {
|
||||
running: daemonRunning,
|
||||
port: config.port,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate environment variables for Claude Code to use copilot-api.
|
||||
*/
|
||||
export function generateCopilotEnv(config: CopilotConfig): Record<string, string> {
|
||||
return {
|
||||
ANTHROPIC_BASE_URL: `http://localhost:${config.port}`,
|
||||
ANTHROPIC_AUTH_TOKEN: 'dummy', // copilot-api handles auth internally
|
||||
ANTHROPIC_MODEL: config.model,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: config.model,
|
||||
ANTHROPIC_SMALL_FAST_MODEL: config.model,
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: config.model,
|
||||
// Disable non-essential traffic to avoid rate limiting
|
||||
DISABLE_NON_ESSENTIAL_MODEL_CALLS: '1',
|
||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute Claude Code with copilot-api proxy.
|
||||
*
|
||||
* @param config Copilot configuration
|
||||
* @param claudeArgs Arguments to pass to Claude CLI
|
||||
* @returns Exit code
|
||||
*/
|
||||
export async function executeCopilotProfile(
|
||||
config: CopilotConfig,
|
||||
claudeArgs: string[]
|
||||
): Promise<number> {
|
||||
// Check if copilot-api is installed
|
||||
if (!isCopilotApiInstalled()) {
|
||||
console.error('[X] copilot-api is not installed.');
|
||||
console.error('');
|
||||
console.error('Install with: npm install -g copilot-api');
|
||||
console.error('Or run via npx: npx copilot-api auth');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check authentication
|
||||
const authStatus = await checkAuthStatus();
|
||||
if (!authStatus.authenticated) {
|
||||
console.error('[X] Not authenticated with GitHub.');
|
||||
console.error('');
|
||||
console.error('Run: npx copilot-api auth');
|
||||
console.error('Or: ccs copilot auth');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if daemon is running or needs to be started
|
||||
let daemonRunning = await isDaemonRunning(config.port);
|
||||
|
||||
if (!daemonRunning) {
|
||||
if (config.auto_start) {
|
||||
console.log('[i] Starting copilot-api daemon...');
|
||||
const result = await startDaemon(config);
|
||||
if (!result.success) {
|
||||
console.error(`[X] Failed to start daemon: ${result.error}`);
|
||||
return 1;
|
||||
}
|
||||
console.log(`[OK] Daemon started on port ${config.port}`);
|
||||
daemonRunning = true;
|
||||
} else {
|
||||
console.error('[X] copilot-api daemon is not running.');
|
||||
console.error('');
|
||||
console.error('Start the daemon manually:');
|
||||
console.error(` npx copilot-api start --port ${config.port}`);
|
||||
console.error('');
|
||||
console.error('Or enable auto_start in config:');
|
||||
console.error(' ccs config (then enable auto_start in Copilot section)');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate environment for Claude
|
||||
const copilotEnv = generateCopilotEnv(config);
|
||||
|
||||
// Merge with current environment
|
||||
const env = {
|
||||
...process.env,
|
||||
...copilotEnv,
|
||||
};
|
||||
|
||||
console.log(`[i] Using GitHub Copilot proxy (model: ${config.model})`);
|
||||
console.log('');
|
||||
|
||||
// Spawn Claude CLI
|
||||
return new Promise((resolve) => {
|
||||
const proc = spawn('claude', claudeArgs, {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
resolve(code ?? 0);
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
console.error(`[X] Failed to start Claude: ${err.message}`);
|
||||
resolve(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copilot Model Catalog
|
||||
*
|
||||
* Manages available models from copilot-api.
|
||||
*/
|
||||
|
||||
import * as http from 'http';
|
||||
import { CopilotModel } from './types';
|
||||
|
||||
/**
|
||||
* Default models available through copilot-api.
|
||||
* Used as fallback when API is not reachable.
|
||||
*/
|
||||
export const DEFAULT_COPILOT_MODELS: CopilotModel[] = [
|
||||
{
|
||||
id: 'claude-opus-4-5-20250514',
|
||||
name: 'Claude Opus 4.5',
|
||||
provider: 'anthropic',
|
||||
isDefault: true,
|
||||
},
|
||||
{ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', provider: 'anthropic' },
|
||||
{ id: 'gpt-4.1', name: 'GPT-4.1', provider: 'openai' },
|
||||
{ id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini', provider: 'openai' },
|
||||
{ id: 'o3', name: 'O3', provider: 'openai' },
|
||||
{ id: 'o4-mini', name: 'O4 Mini', provider: 'openai' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetch available models from running copilot-api daemon.
|
||||
*
|
||||
* @param port The port copilot-api is running on
|
||||
* @returns List of available models
|
||||
*/
|
||||
export async function fetchModelsFromDaemon(port: number): Promise<CopilotModel[]> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: 'localhost',
|
||||
port,
|
||||
path: '/v1/models',
|
||||
method: 'GET',
|
||||
timeout: 5000,
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const response = JSON.parse(data) as { data?: Array<{ id: string }> };
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
const models: CopilotModel[] = response.data.map((m) => ({
|
||||
id: m.id,
|
||||
name: formatModelName(m.id),
|
||||
provider: m.id.includes('claude') ? 'anthropic' : 'openai',
|
||||
isDefault: m.id === 'claude-opus-4-5-20250514',
|
||||
}));
|
||||
resolve(models.length > 0 ? models : DEFAULT_COPILOT_MODELS);
|
||||
} else {
|
||||
resolve(DEFAULT_COPILOT_MODELS);
|
||||
}
|
||||
} catch {
|
||||
resolve(DEFAULT_COPILOT_MODELS);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => {
|
||||
resolve(DEFAULT_COPILOT_MODELS);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(DEFAULT_COPILOT_MODELS);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available models (from daemon or defaults).
|
||||
*/
|
||||
export async function getAvailableModels(port: number): Promise<CopilotModel[]> {
|
||||
return fetchModelsFromDaemon(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default model.
|
||||
*/
|
||||
export function getDefaultModel(): string {
|
||||
return 'claude-opus-4-5-20250514';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format model ID to human-readable name.
|
||||
*/
|
||||
function formatModelName(modelId: string): string {
|
||||
// Convert model IDs to readable names
|
||||
const nameMap: Record<string, string> = {
|
||||
'claude-opus-4-5-20250514': 'Claude Opus 4.5',
|
||||
'claude-sonnet-4-20250514': 'Claude Sonnet 4',
|
||||
'gpt-4.1': 'GPT-4.1',
|
||||
'gpt-4.1-mini': 'GPT-4.1 Mini',
|
||||
o3: 'O3',
|
||||
'o4-mini': 'O4 Mini',
|
||||
};
|
||||
|
||||
return nameMap[modelId] || modelId;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copilot Module Index
|
||||
*
|
||||
* Central exports for GitHub Copilot integration via copilot-api.
|
||||
*/
|
||||
|
||||
// Types
|
||||
export * from './types';
|
||||
|
||||
// Auth
|
||||
export {
|
||||
isCopilotApiInstalled,
|
||||
checkAuthStatus,
|
||||
startAuthFlow,
|
||||
getCopilotDebugInfo,
|
||||
} from './copilot-auth';
|
||||
|
||||
// Daemon
|
||||
export { isDaemonRunning, getDaemonStatus, startDaemon, stopDaemon } from './copilot-daemon';
|
||||
|
||||
// Models
|
||||
export {
|
||||
DEFAULT_COPILOT_MODELS,
|
||||
fetchModelsFromDaemon,
|
||||
getAvailableModels,
|
||||
getDefaultModel,
|
||||
} from './copilot-models';
|
||||
|
||||
// Executor
|
||||
export { getCopilotStatus, generateCopilotEnv, executeCopilotProfile } from './copilot-executor';
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copilot API Types
|
||||
*
|
||||
* Type definitions for GitHub Copilot proxy integration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copilot authentication status.
|
||||
*/
|
||||
export interface CopilotAuthStatus {
|
||||
authenticated: boolean;
|
||||
/** GitHub username if authenticated */
|
||||
username?: string;
|
||||
/** Error message if auth check failed */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copilot daemon status.
|
||||
*/
|
||||
export interface CopilotDaemonStatus {
|
||||
running: boolean;
|
||||
port: number;
|
||||
/** Process ID if running */
|
||||
pid?: number;
|
||||
/** Version if available */
|
||||
version?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined copilot status.
|
||||
*/
|
||||
export interface CopilotStatus {
|
||||
auth: CopilotAuthStatus;
|
||||
daemon: CopilotDaemonStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copilot model information.
|
||||
*/
|
||||
export interface CopilotModel {
|
||||
id: string;
|
||||
name: string;
|
||||
/** Provider: openai or anthropic */
|
||||
provider: 'openai' | 'anthropic';
|
||||
/** Whether this is the default model */
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copilot debug info from `copilot-api debug --json`.
|
||||
*/
|
||||
export interface CopilotDebugInfo {
|
||||
version?: string;
|
||||
runtime?: string;
|
||||
authenticated?: boolean;
|
||||
tokenPath?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user