-
Notifications
You must be signed in to change notification settings - Fork 773
feat: Add GitHub Copilot as an AI provider with device flow authentication #2881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,12 +2,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Tooltip } from "@/app/element/tooltip"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { atoms, getSettingsKeyAtom } from "@/app/store/global"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { atoms, getApi, getSettingsKeyAtom } from "@/app/store/global"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RpcApi } from "@/app/store/wshclientapi"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TabRpcClient } from "@/app/store/wshrpcutil"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cn, fireAndForget, makeIconClass } from "@/util/util"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useAtomValue } from "jotai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { memo, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { memo, useCallback, useEffect, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getFilteredAIModeConfigs, getModeDisplayName } from "./ai-utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { WaveAIModel } from "./waveai-model"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -147,6 +147,91 @@ export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdow | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const telemetryEnabled = useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isOpen, setIsOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dropdownRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const copilotLoginAbortRef = useRef<AbortController | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [copilotLoggedIn, setCopilotLoggedIn] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [copilotLoginState, setCopilotLoginState] = useState< | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { status: "idle" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { status: "waiting"; userCode: string; verificationUri: string } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { status: "polling" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { status: "complete" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | { status: "error"; message: string } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| >({ status: "idle" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check Copilot login status on mount and when dropdown opens | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fireAndForget(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const status = await RpcApi.CopilotDeviceLoginStatusCommand(TabRpcClient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoggedIn(status.loggedin); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ignore errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [isOpen]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+161
to
+170
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing cleanup on unmount — leaked RPC poll and timers. If the component unmounts (e.g., tab switch) while a Copilot login poll is in progress, the 🛡️ Proposed fix — add cleanup effect // Check Copilot login status on mount and when dropdown opens
useEffect(() => {
fireAndForget(async () => {
try {
const status = await RpcApi.CopilotDeviceLoginStatusCommand(TabRpcClient);
setCopilotLoggedIn(status.loggedin);
} catch {
// ignore errors
}
});
+ return () => {
+ if (copilotLoginAbortRef.current) {
+ copilotLoginAbortRef.current.abort();
+ copilotLoginAbortRef.current = null;
+ }
+ };
}, [isOpen]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCopilotLogin = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const abortController = new AbortController(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| copilotLoginAbortRef.current = abortController; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fireAndForget(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ status: "polling" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const startData = await RpcApi.CopilotDeviceLoginStartCommand(TabRpcClient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (abortController.signal.aborted) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "waiting", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userCode: startData.usercode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verificationUri: startData.verificationuri, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copy code to clipboard and open browser | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await navigator.clipboard.writeText(startData.usercode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getApi().openExternal(startData.verificationuri); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Now poll for completion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pollResult = await RpcApi.CopilotDeviceLoginPollCommand(TabRpcClient, {}, { timeout: 15 * 60 * 1000 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (abortController.signal.aborted) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pollResult.status === "complete") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ status: "complete" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoggedIn(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Auto-switch to the copilot mode if one was created | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pollResult.modename) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait briefly for config watcher to pick up the new mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model.setAIMode(pollResult.modename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, 500); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+196
to
+199
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race condition: 500ms setTimeout to auto-switch mode is fragile. The delay assumes the config watcher will have processed the new Copilot modes within 500ms, but this depends on file I/O, watcher debounce, and event propagation timing. If the mode hasn't been registered yet, Consider subscribing to config updates and switching when the mode actually becomes available, rather than relying on a fixed delay. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => setCopilotLoginState({ status: "idle" }), 3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "error", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: pollResult.error || "Login failed", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => setCopilotLoginState({ status: "idle" }), 5000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (abortController.signal.aborted) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ status: "error", message: e.message || "Login failed" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => setCopilotLoginState({ status: "idle" }), 5000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+172
to
+215
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Search for CopilotDeviceLoginPollCommand definition
rg -n "CopilotDeviceLoginPollCommand" --type ts --type=go -B 2 -A 5Repository: wavetermdev/waveterm Length of output: 4072 🏁 Script executed: # Search for RpcOpts definition and how it's used
rg -n "RpcOpts" -A 10 --type ts | head -80Repository: wavetermdev/waveterm Length of output: 6446 🏁 Script executed: # Search for RpcOpts type/interface definition
rg -n "interface RpcOpts|type RpcOpts" --type ts -A 10Repository: wavetermdev/waveterm Length of output: 650 🏁 Script executed: # Get the full implementation of CopilotDeviceLoginPollCommand in Go
sed -n '1549,1600p' pkg/wshrpc/wshserver/wshserver.goRepository: wavetermdev/waveterm Length of output: 1698 🏁 Script executed: # Search for how timeout is handled on the server side in the RPC processing
rg -n "timeout" pkg/wshrpc/wshutil/ --type go -B 2 -A 2 | head -100Repository: wavetermdev/waveterm Length of output: 123 🏁 Script executed: # Find the correct directory structure
fd . pkg/wshrpc --type d | head -20Repository: wavetermdev/waveterm Length of output: 129 🏁 Script executed: # Search for timeout handling in the wshrpc package
rg -n "timeout" pkg/wshrpc/ --type go -B 2 -A 2 | head -150Repository: wavetermdev/waveterm Length of output: 3058 🏁 Script executed: # Search for RPC message dispatch and handler invocation
rg -n "timeout" pkg/wshrpc/wshserver/ --type go -B 3 -A 3 | head -150Repository: wavetermdev/waveterm Length of output: 46 🏁 Script executed: # Search for how RPC commands are dispatched/handled
rg -n "handleRequest|dispatch|invokeCommand" pkg/wshrpc/ --type go -B 2 -A 5 | head -150Repository: wavetermdev/waveterm Length of output: 46 🏁 Script executed: # Search for message handling and command routing
rg -n "command.*RpcMessage|func.*command" pkg/wshrpc/wshserver/ --type go -B 1 -A 3 | head -150Repository: wavetermdev/waveterm Length of output: 46 🏁 Script executed: # List files in wshserver
fd . pkg/wshrpc/wshserver/ --type f
# Also search for where commands are actually invoked
rg -n "case.*:" pkg/wshrpc/wshserver/ --type go -B 1 -A 3 | head -100Repository: wavetermdev/waveterm Length of output: 3537 🏁 Script executed: # Look for how RPC commands are dispatched from client messages to server handlers
rg -n "sendRpcRequest|wshRpcCall" pkg/wshrpc/wshclient/ --type go -B 2 -A 10 | head -150Repository: wavetermdev/waveterm Length of output: 11983 🏁 Script executed: # Search for WshRpc implementation and how it processes opts/timeout
rg -n "SendCommand|SendComplexRequest" --type go -B 2 -A 10 | head -200Repository: wavetermdev/waveterm Length of output: 6716 🏁 Script executed: # Search for RPC message dispatch on server side
rg -n "invokeHandler|processRpc|dispatchCommand|handleMessage" pkg/wshrpc/ --type go -B 2 -A 10 | head -200Repository: wavetermdev/waveterm Length of output: 46 🏁 Script executed: # Look at wshserver.go structure - search for message handling
rg -n "func.*message|processMsg|handleMsg" pkg/wshrpc/wshserver/wshserver.go --type go -B 1 -A 5Repository: wavetermdev/waveterm Length of output: 46 🏁 Script executed: # Look at wshserver.go line counts and search for dispatch/invoke patterns more broadly
wc -l pkg/wshrpc/wshserver/wshserver.go
# Search for where commands are actually invoked with the message
rg -n "reflect|invokeMethod" pkg/wshrpc/wshserver/ --type go -B 2 -A 5 | head -100Repository: wavetermdev/waveterm Length of output: 102 AbortController doesn't cancel the backend RPC poll.
The
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCopilotLoginCancel = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (copilotLoginAbortRef.current) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| copilotLoginAbortRef.current.abort(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| copilotLoginAbortRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoginState({ status: "idle" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCopilotLogout = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fireAndForget(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await RpcApi.CopilotDeviceLogoutCommand(TabRpcClient); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCopilotLoggedIn(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to logout from Copilot:", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { waveProviderConfigs, otherProviderConfigs } = getFilteredAIModeConfigs( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aiModeConfigs, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -312,6 +397,72 @@ export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdow | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className={makeIconClass("plus", false)}></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">New Chat</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="border-t border-gray-600 my-1" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "idle" && !copilotLoggedIn && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleCopilotLogin} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="w-full flex items-center gap-2 px-3 pt-1 pb-1 text-gray-300 hover:bg-zinc-700 cursor-pointer transition-colors text-left" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa-brands fa-github text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">Login to GitHub Copilot</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "idle" && copilotLoggedIn && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full flex items-center justify-between px-3 pt-1 pb-1 text-left"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2 text-green-400"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa-brands fa-github text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">Copilot Connected</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleCopilotLogout} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="text-[11px] text-gray-500 hover:text-red-400 cursor-pointer transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Logout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "polling" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full flex items-center gap-2 px-3 pt-1 pb-1 text-gray-400 text-left"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa fa-spinner fa-spin text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">Starting login...</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "waiting" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full flex flex-col gap-1 px-3 pt-1 pb-1 text-left"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2 text-green-300"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa fa-key text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm font-bold">{copilotLoginState.userCode}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="text-[11px] text-gray-400"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Code copied! Paste it on GitHub. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between text-gray-400"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa fa-spinner fa-spin text-[10px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-[11px]">Waiting for authorization...</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleCopilotLoginCancel} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="text-[11px] text-red-400 hover:text-red-300 cursor-pointer transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Cancel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "complete" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full flex items-center gap-2 px-3 pt-1 pb-1 text-green-400 text-left"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa fa-check text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">Logged in to GitHub Copilot!</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {copilotLoginState.status === "error" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full flex items-center gap-2 px-3 pt-1 pb-1 text-red-400 text-left"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <i className="fa fa-triangle-exclamation text-[12px]"></i> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm">{copilotLoginState.message}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="border-t border-gray-600 my-1" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleConfigureClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="w-full flex items-center gap-2 px-3 pt-1 pb-2 text-gray-300 hover:bg-zinc-700 cursor-pointer transition-colors text-left" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ai:apitypefield reference is missinggithub-copilot.Line 528 lists valid API types as
openai-chat,openai-responses, orgoogle-gemini, butgithub-copilotwas added to the supported API types list at line 50 and to the schema. Update the field reference to include it for consistency.🤖 Prompt for AI Agents