فهرست منبع

feat(webgui): add OAuth instructions support

Add optional instructions field to OAuth flow that displays provider-specific guidance during authentication. Instructions are shown in a blue info box when waiting for OAuth completion, and are properly cleaned up when auth is cancelled or completed.
paviko 3 ماه پیش
والد
کامیت
32b94f7a8d

+ 0 - 18
.opencode/bun.lock

@@ -1,18 +0,0 @@
-{
-  "lockfileVersion": 1,
-  "configVersion": 0,
-  "workspaces": {
-    "": {
-      "dependencies": {
-        "@opencode-ai/plugin": "1.0.152",
-      },
-    },
-  },
-  "packages": {
-    "@opencode-ai/plugin": ["@opencode-ai/[email protected]", "", { "dependencies": { "@opencode-ai/sdk": "1.0.152", "zod": "4.1.8" } }, "sha512-P8ov9iUOaonO1yWKnArYrIOcyzncFgIP0oDn3KTWbIM0viVA0lUf64GIwLuKcDd6W9m9ZgFcB06RzEKDnZHNjA=="],
-
-    "@opencode-ai/sdk": ["@opencode-ai/[email protected]", "", {}, "sha512-0m0Z8QFg98DMEt24PWOJZBrdUhKCHBYo5AHsCIrMkrH4jfl9V8zsN2upp1mWw+waREDEnFtQwdLA56oIS8gngQ=="],
-
-    "zod": ["[email protected]", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
-  }
-}

+ 1 - 1
hosts/jetbrains-plugin/build.gradle.kts

@@ -5,7 +5,7 @@ plugins {
 }
 
 group = "paviko.opencode"
-version = "25.12.29"
+version = "26.1.2"
 
 repositories {
     mavenCentral()

+ 5 - 0
hosts/jetbrains-plugin/changelog.html

@@ -1,5 +1,10 @@
 <h2>OpenCode UX+ (unofficial) JetBrains Plugin Changelog</h2>
 
+<h3>26.1.2</h3>
+<ul>
+  <li>OAuth Instructions: Support for provider-specific instructions during OAuth flow</li>
+</ul>
+
 <h3>25.12.29</h3>
 <ul>
   <li>Updated OpenCode to v1.0.207</li>

+ 4 - 0
hosts/vscode-plugin/CHANGELOG.md

@@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed placement of Model/Agent selector
 - Updated OpenCode to v1.0.133
 
+### [26.1.2] - 2026-01-02
+
+- OAuth Instructions: Support for provider-specific instructions during OAuth flow
+
 ## [25.11.30] - 2025-11-30
 
 - Updated OpenCode to v1.0.121

+ 1 - 1
hosts/vscode-plugin/package.json

@@ -2,7 +2,7 @@
   "name": "opencode-ux-plus",
   "displayName": "OpenCode UX+ (unofficial)",
   "description": "Unofficial OpenCode VSCode extension",
-  "version": "25.12.29",
+  "version": "26.1.2",
   "publisher": "paviko",
   "author": {
     "name": "paviko"

+ 7 - 2
packages/opencode/src/server/server.ts

@@ -104,6 +104,11 @@ function webGuiContentType(relativePath: string) {
   return "application/octet-stream"
 }
 
+function webGuiCacheControl(relativePath: string) {
+  if (relativePath.endsWith(".html") || relativePath.endsWith(".js")) return "no-store"
+  return "public, max-age=3600"
+}
+
 function serveWebGuiFromFs(relativePath: string) {
   const fullPath = path.join(webGuiRoot, relativePath)
   if (!fs.existsSync(fullPath)) return
@@ -111,7 +116,7 @@ function serveWebGuiFromFs(relativePath: string) {
   return new Response(file, {
     headers: {
       "Content-Type": webGuiContentType(relativePath),
-      "Cache-Control": "public, max-age=3600",
+      "Cache-Control": webGuiCacheControl(relativePath),
     },
   })
 }
@@ -124,7 +129,7 @@ function serveWebGuiFromEmbed(relativePath: string) {
   return new Response(body, {
     headers: {
       "Content-Type": webGuiContentType(relativePath),
-      "Cache-Control": "public, max-age=3600",
+      "Cache-Control": webGuiCacheControl(relativePath),
     },
   })
 }

+ 3 - 2
packages/opencode/src/webgui/server/webgui.ts

@@ -179,6 +179,7 @@ export const WebGuiRoute = new Hono()
                   id: z.string(),
                   url: z.string().optional(),
                   method: z.enum(["auto", "code"]),
+                  instructions: z.string().optional(),
                 }),
               ),
             },
@@ -243,7 +244,7 @@ export const WebGuiRoute = new Hono()
               pendingAuths.set(id, { status: "failed", result: err })
             })
 
-          return c.json({ id, url: authorize.url, method: "auto" as const })
+          return c.json({ id, url: authorize.url, method: "auto" as const, instructions: authorize.instructions })
         }
 
         if ((authorize as any).method === "code") {
@@ -251,7 +252,7 @@ export const WebGuiRoute = new Hono()
             status: "pending",
             callback: authorize.callback,
           })
-          return c.json({ id, url: authorize.url, method: "code" as const })
+          return c.json({ id, url: authorize.url, method: "code" as const, instructions: authorize.instructions })
         }
 
         throw new Error("Unsupported oauth method: " + authorize.method)

+ 24 - 2
packages/opencode/webgui/src/components/settings/ApiKeysTab/OAuthSection.tsx

@@ -11,7 +11,8 @@ interface OAuthSectionProps {
   methods: AuthMethod[]
   oauthMethodIndex: number
   authStatus: string
-  manualCodeState: { providerId: string; id: string } | null
+  authInstructions?: string
+  manualCodeState: { providerId: string; id: string; instructions?: string } | null
   manualCodeInput: string
   onOAuthLogin: (providerId: string, methodIndex: number) => void
   onCancel: (providerId: string) => void
@@ -26,6 +27,7 @@ export function OAuthSection({
   methods,
   oauthMethodIndex,
   authStatus,
+  authInstructions,
   manualCodeState,
   manualCodeInput,
   onOAuthLogin,
@@ -37,6 +39,9 @@ export function OAuthSection({
   const isWaiting = authStatus && (authStatus.startsWith("Waiting") || authStatus === "Initializing...")
   const showManualCodeInput = manualCodeState?.providerId === providerId
 
+  // Get instructions from either props or manualCodeState
+  const instructions = authInstructions || manualCodeState?.instructions
+
   return (
     <div className="space-y-2">
       <div className="flex items-center gap-2">
@@ -47,7 +52,7 @@ export function OAuthSection({
         >
           {methods[oauthMethodIndex].label || `Login with ${providerName}`}
         </button>
-        {authStatus && (
+        {authStatus && !instructions && (
           <div className="flex items-center gap-2">
             <span className="text-xs text-blue-600 dark:text-blue-400 animate-pulse">{authStatus}</span>
             {isWaiting && !showManualCodeInput && (
@@ -62,6 +67,23 @@ export function OAuthSection({
         )}
       </div>
 
+      {instructions && isWaiting && (
+        <div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md space-y-2">
+          <div className="text-xs text-blue-800 dark:text-blue-200 whitespace-pre-wrap font-mono">
+            {instructions}
+          </div>
+          <div className="flex items-center gap-2">
+            <span className="text-xs text-blue-600 dark:text-blue-400 animate-pulse">{authStatus}</span>
+            <button
+              onClick={() => onCancel(providerId)}
+              className="px-2 py-1 text-xs border border-blue-200 dark:border-blue-700 rounded hover:bg-blue-100 dark:hover:bg-blue-800 text-blue-600 dark:text-blue-400"
+            >
+              Cancel
+            </button>
+          </div>
+        </div>
+      )}
+
       {showManualCodeInput && (
         <ManualCodeInput
           value={manualCodeInput}

+ 4 - 1
packages/opencode/webgui/src/components/settings/ApiKeysTab/ProviderCard.tsx

@@ -15,7 +15,8 @@ interface ProviderCardProps {
   isLoading: boolean
   methods: AuthMethod[]
   authStatus: string
-  manualCodeState: { providerId: string; id: string } | null
+  authInstructions?: string
+  manualCodeState: { providerId: string; id: string; instructions?: string } | null
   manualCodeInput: string
   apiKey: string
   showApiKey: boolean
@@ -38,6 +39,7 @@ export function ProviderCard({
   isLoading,
   methods,
   authStatus,
+  authInstructions,
   manualCodeState,
   manualCodeInput,
   apiKey,
@@ -113,6 +115,7 @@ export function ProviderCard({
                 methods={methods}
                 oauthMethodIndex={oauthMethodIndex}
                 authStatus={authStatus}
+                authInstructions={authInstructions}
                 manualCodeState={manualCodeState}
                 manualCodeInput={manualCodeInput}
                 onOAuthLogin={onOAuthLogin}

+ 14 - 2
packages/opencode/webgui/src/components/settings/ApiKeysTab/hooks/useOAuthFlow.ts

@@ -5,6 +5,7 @@ import { ideBridge } from "../../../../lib/ideBridge"
 interface ManualCodeState {
   providerId: string
   id: string
+  instructions?: string
 }
 
 interface UseOAuthFlowProps {
@@ -24,13 +25,18 @@ export function useOAuthFlow({
 }: UseOAuthFlowProps) {
   const [authStatus, setAuthStatus] = useState<Record<string, string>>({})
   const [manualCodeState, setManualCodeState] = useState<ManualCodeState | null>(null)
+  const [authInstructions, setAuthInstructions] = useState<Record<string, string>>({})
   const [manualCodeInput, setManualCodeInput] = useState("")
   const pollIntervals = useRef<Record<string, ReturnType<typeof setInterval>>>({})
 
   const handleOAuthLogin = async (providerId: string, methodIndex: number) => {
     try {
       setAuthStatus((prev) => ({ ...prev, [providerId]: "Initializing..." }))
-      const { id, url, method } = await sdk.auth.start(providerId, methodIndex, {})
+      const { id, url, method, instructions } = await sdk.auth.start(providerId, methodIndex, {})
+
+      if (instructions) {
+        setAuthInstructions((prev) => ({ ...prev, [providerId]: instructions }))
+      }
 
       if (url) {
         if (ideBridge.isInstalled()) {
@@ -42,7 +48,7 @@ export function useOAuthFlow({
 
       if (method === "code") {
         setAuthStatus((prev) => ({ ...prev, [providerId]: "Waiting for code..." }))
-        setManualCodeState({ providerId, id })
+        setManualCodeState({ providerId, id, instructions })
         setManualCodeInput("")
         return
       }
@@ -107,6 +113,11 @@ export function useOAuthFlow({
       delete pollIntervals.current[providerId]
     }
     setAuthStatus((prev) => ({ ...prev, [providerId]: "" }))
+    setAuthInstructions((prev) => {
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      const { [providerId]: _removed, ...rest } = prev
+      return rest
+    })
     if (manualCodeState?.providerId === providerId) {
       setManualCodeState(null)
       setManualCodeInput("")
@@ -149,6 +160,7 @@ export function useOAuthFlow({
 
   return {
     authStatus,
+    authInstructions,
     manualCodeState,
     manualCodeInput,
     setManualCodeInput,

+ 1 - 0
packages/opencode/webgui/src/components/settings/ApiKeysTab/index.tsx

@@ -110,6 +110,7 @@ export function ApiKeysTab({
                 isLoading={isLoading}
                 methods={providerMethods}
                 authStatus={oAuthFlow.authStatus[provider.id] || ""}
+                authInstructions={oAuthFlow.authInstructions[provider.id]}
                 manualCodeState={oAuthFlow.manualCodeState}
                 manualCodeInput={oAuthFlow.manualCodeInput}
                 apiKey={apiKeys[provider.id] || ""}

+ 1 - 1
packages/opencode/webgui/src/lib/api/sdkClient.ts

@@ -136,7 +136,7 @@ export const sdk = {
         body: JSON.stringify({ provider, methodIndex, inputs }),
       })
       if (!res.ok) throw new Error(await res.text())
-      return res.json() as Promise<{ id: string; url?: string; method: "auto" | "code" }>
+      return res.json() as Promise<{ id: string; url?: string; method: "auto" | "code"; instructions?: string }>
     },
     submit: async (id: string, code: string) => {
       const res = await fetch("/app/api/auth/login/submit", {