feat: add GitHub Copilot model provider with OAuth device flow
Add a new 'github' model provider backed by the Copilot API (api.githubcopilot.com), with OAuth device flow for authentication. - New src/auth/github.ts: device flow login, token storage at ~/.config/flynn/auth.json with 0600 permissions - New src/models/github.ts: OpenAI-compatible client with streaming, tool calling, and Copilot-specific headers - Add 'github' to provider enum in config schema - Register provider in daemon factory and TUI client factory - Refactor TUI to use provider-agnostic client factory (was hardcoded to AnthropicClient for all tiers) - Add /login command to TUI for interactive OAuth authorization - Add Copilot model cost tracking entries
This commit is contained in:
+27
-41
@@ -43,7 +43,7 @@ export function registerTuiCommand(program: Command): void {
|
||||
|
||||
// Dynamic imports to keep CLI startup fast
|
||||
const { SessionStore, SessionManager } = await import('../session/index.js');
|
||||
const { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, ModelRouter } = await import('../models/index.js');
|
||||
const { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, GitHubModelsClient, GeminiClient, BedrockClient, ModelRouter } = await import('../models/index.js');
|
||||
const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js');
|
||||
const { NativeAgent } = await import('../backends/index.js');
|
||||
const { ToolRegistry, ToolExecutor, allBuiltinTools } = await import('../tools/index.js');
|
||||
@@ -56,49 +56,35 @@ export function registerTuiCommand(program: Command): void {
|
||||
const sessionManager = new SessionManager(sessionStore);
|
||||
const models = config.models;
|
||||
|
||||
// Build model router
|
||||
const defaultClient = new AnthropicClient({
|
||||
model: models.default.model,
|
||||
apiKey: models.default.api_key,
|
||||
authToken: models.default.auth_token,
|
||||
});
|
||||
|
||||
let fastClient;
|
||||
let complexClient;
|
||||
let localClient;
|
||||
|
||||
if (models.fast) {
|
||||
fastClient = new AnthropicClient({
|
||||
model: models.fast.model,
|
||||
apiKey: models.fast.api_key,
|
||||
authToken: models.fast.auth_token,
|
||||
});
|
||||
}
|
||||
|
||||
if (models.complex) {
|
||||
complexClient = new AnthropicClient({
|
||||
model: models.complex.model,
|
||||
apiKey: models.complex.api_key,
|
||||
authToken: models.complex.auth_token,
|
||||
});
|
||||
}
|
||||
|
||||
if (models.local) {
|
||||
if (models.local.provider === 'ollama') {
|
||||
localClient = new OllamaClient({
|
||||
model: models.local.model,
|
||||
host: models.local.endpoint,
|
||||
numGpu: models.local.num_gpu,
|
||||
});
|
||||
} else if (models.local.provider === 'llamacpp') {
|
||||
localClient = new LlamaCppClient({
|
||||
endpoint: models.local.endpoint ?? 'http://localhost:8080',
|
||||
model: models.local.model,
|
||||
authToken: models.local.auth_token,
|
||||
});
|
||||
// Provider-agnostic client factory for TUI
|
||||
function createClient(cfg: typeof models.default) {
|
||||
switch (cfg.provider) {
|
||||
case 'anthropic':
|
||||
return new AnthropicClient({ model: cfg.model, apiKey: cfg.api_key, authToken: cfg.auth_token });
|
||||
case 'openai':
|
||||
return new OpenAIClient({ model: cfg.model, apiKey: cfg.api_key });
|
||||
case 'gemini':
|
||||
return new GeminiClient({ model: cfg.model, apiKey: cfg.api_key });
|
||||
case 'ollama':
|
||||
return new OllamaClient({ model: cfg.model, host: cfg.endpoint, numGpu: cfg.num_gpu });
|
||||
case 'llamacpp':
|
||||
return new LlamaCppClient({ endpoint: cfg.endpoint ?? 'http://localhost:8080', model: cfg.model, authToken: cfg.auth_token });
|
||||
case 'openrouter':
|
||||
return new OpenAIClient({ model: cfg.model, apiKey: cfg.api_key ?? process.env.OPENROUTER_API_KEY, baseURL: cfg.endpoint ?? 'https://openrouter.ai/api/v1' });
|
||||
case 'bedrock':
|
||||
return new BedrockClient({ model: cfg.model, region: cfg.endpoint, accessKeyId: cfg.api_key, secretAccessKey: cfg.auth_token });
|
||||
case 'github':
|
||||
return new GitHubModelsClient({ model: cfg.model, apiKey: cfg.api_key, endpoint: cfg.endpoint });
|
||||
default:
|
||||
throw new Error(`Unknown provider: ${cfg.provider}`);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultClient = createClient(models.default);
|
||||
const fastClient = models.fast ? createClient(models.fast) : undefined;
|
||||
const complexClient = models.complex ? createClient(models.complex) : undefined;
|
||||
const localClient = models.local ? createClient(models.local) : undefined;
|
||||
|
||||
const fallbackChain = [];
|
||||
for (const providerName of models.fallback_chain) {
|
||||
if (providerName === 'openai') {
|
||||
|
||||
Reference in New Issue
Block a user