From 3e91c1539d6dca5af688aec0a4d654ff9312fb99 Mon Sep 17 00:00:00 2001
From: Wu-kung <1434246346@qq.com>
Date: Wed, 15 Oct 2025 12:24:36 +0800
Subject: [PATCH 1/2] fix: enforce model selection(#313)
---
src/ui/OnBoarding.tsx | 81 -------------------------------------------
src/ui/store.ts | 19 ++++++++--
2 files changed, 17 insertions(+), 83 deletions(-)
delete mode 100644 src/ui/OnBoarding.tsx
diff --git a/src/ui/OnBoarding.tsx b/src/ui/OnBoarding.tsx
deleted file mode 100644
index 8d344fd3..00000000
--- a/src/ui/OnBoarding.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { Box, Text, useInput } from 'ink';
-import React, { useEffect, useState } from 'react';
-import { ModelSelect } from '../slash-commands/builtin/model';
-import { useAppStore } from './store';
-
-export function OnBoarding() {
- const { providers } = useAppStore();
- const [showModelSelect, setShowModelSelect] = useState(false);
- const [completed, setCompleted] = useState(false);
-
- useInput((input, key) => {
- if (key.return && !showModelSelect && !completed) {
- setShowModelSelect(true);
- }
- if (key.escape) {
- process.exit(0);
- }
- });
-
- if (completed) {
- return (
-
- ✓ Model configured successfully
-
- );
- }
-
- if (showModelSelect) {
- return (
- {
- setShowModelSelect(false);
- }}
- onSelect={() => {
- setCompleted(true);
- }}
- />
- );
- }
-
- return (
-
-
- ⚠ Model Configuration Required
-
-
-
- You have not configured a model yet. Please set an API key in your
- environment for one of the providers we support:
-
-
-
- {Object.entries(providers).map(([id, provider]) => {
- if (!provider.env.length) {
- return null;
- }
- return (
-
- • {provider.name}
- -
- {provider.env[0]}
-
- );
- })}
-
-
-
- Press{' '}
-
- Enter
- {' '}
- to select a model after setting your API key. Or press{' '}
-
- Esc
- {' '}
- to exit.
-
-
-
- );
-}
diff --git a/src/ui/store.ts b/src/ui/store.ts
index 95fa6a0a..42ae937d 100644
--- a/src/ui/store.ts
+++ b/src/ui/store.ts
@@ -346,8 +346,15 @@ export const useAppStore = create()(
},
send: async (message) => {
- const { bridge, cwd, sessionId, planMode, status, pastedTextMap } =
- get();
+ const {
+ bridge,
+ cwd,
+ sessionId,
+ planMode,
+ status,
+ pastedTextMap,
+ model,
+ } = get();
bridge.request('utils.telemetry', {
cwd,
@@ -355,6 +362,14 @@ export const useAppStore = create()(
payload: { message, sessionId },
});
+ if (!isSlashCommand(message) && !model) {
+ set({
+ status: 'failed',
+ error: 'Please select a model first (use /model command)',
+ });
+ return;
+ }
+
// Check if processing, queue the message
if (isExecuting(status)) {
get().addToQueue(message);
From 02d368e86f0df8d39c85e48ee92074b8615e8dd4 Mon Sep 17 00:00:00 2001
From: Wu-kung <1434246346@qq.com>
Date: Wed, 15 Oct 2025 18:49:22 +0800
Subject: [PATCH 2/2] feat: add GitHub Copilot login check before model
selection
---
src/model.ts | 5 ---
src/nodeBridge.ts | 22 ++++++++++++
src/slash-commands/builtin/model.tsx | 54 +++++++++++++++++++++++++---
3 files changed, 72 insertions(+), 9 deletions(-)
diff --git a/src/model.ts b/src/model.ts
index b1f2f6c8..c55d8ab2 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -810,11 +810,6 @@ export const providers: ProvidersMap = {
const githubDataPath = path.join(globalConfigDir, 'githubCopilot.json');
const githubProvider = new GithubProvider({ authFile: githubDataPath });
const token = await githubProvider.access();
- if (!token) {
- throw new Error(
- 'Failed to get GitHub Copilot token, use /login to login first',
- );
- }
return createOpenAI({
baseURL: 'https://api.individual.githubcopilot.com',
headers: {
diff --git a/src/nodeBridge.ts b/src/nodeBridge.ts
index 0f4c3c7b..1ae138a2 100644
--- a/src/nodeBridge.ts
+++ b/src/nodeBridge.ts
@@ -1,3 +1,4 @@
+import path from 'path';
import { compact } from './compact';
import { type ApprovalMode, type Config, ConfigManager } from './config';
import { CANCELED_MESSAGE_TEXT } from './constants';
@@ -932,6 +933,27 @@ class NodeHandlerRegistry {
};
},
);
+
+ this.messageBus.registerHandler(
+ 'utils.checkGithubCopilotLogin',
+ async (data: { cwd: string }) => {
+ const { cwd } = data;
+ const context = await this.getContext(cwd);
+ const { GithubProvider } = await import('./providers/githubCopilot');
+ const githubDataPath = path.join(
+ context.paths.globalConfigDir,
+ 'githubCopilot.json',
+ );
+ const githubProvider = new GithubProvider({ authFile: githubDataPath });
+ const token = await githubProvider.access();
+ return {
+ success: true,
+ data: {
+ loggedIn: !!token,
+ },
+ };
+ },
+ );
}
}
diff --git a/src/slash-commands/builtin/model.tsx b/src/slash-commands/builtin/model.tsx
index c67ebc69..98b05b28 100644
--- a/src/slash-commands/builtin/model.tsx
+++ b/src/slash-commands/builtin/model.tsx
@@ -29,6 +29,7 @@ export const ModelSelect: React.FC = ({
modelId: string;
} | null>(null);
const [groupedModels, setGroupedModels] = useState([]);
+ const [error, setError] = useState(null);
useEffect(() => {
bridge.request('models.list', { cwd }).then((result) => {
@@ -40,6 +41,54 @@ export const ModelSelect: React.FC = ({
});
}, [cwd]);
+ const handleSelect = async (item: { value: string }) => {
+ if (item.value.startsWith('github-copilot/')) {
+ const loginCheckResult = await bridge.request(
+ 'utils.checkGithubCopilotLogin',
+ { cwd },
+ );
+ if (!loginCheckResult.data.loggedIn) {
+ setError(
+ 'GitHub Copilot is not logged in. Please use /login command first.',
+ );
+ return;
+ }
+ }
+ setError(null);
+ setModel(item.value);
+ onSelect(item.value);
+ };
+
+ useInput((_input, key) => {
+ if (key.return && error) {
+ setError(null);
+ }
+ });
+
+ if (error) {
+ return (
+
+
+
+ Login Required
+
+
+
+ {error}
+
+
+ Press Enter to continue...
+
+
+ );
+ }
+
return (
= ({
itemsPerPage={15}
enableSearch={true}
onCancel={() => onExit(currentModel)}
- onSelect={(item) => {
- setModel(item.value);
- onSelect(item.value);
- }}
+ onSelect={handleSelect}
/>