From a6e4b48bc4c78054506055eafc43bde2e024cbd0 Mon Sep 17 00:00:00 2001 From: Z User Date: Sun, 14 Jun 2026 21:45:49 +0000 Subject: [PATCH] fix: make API key optional for custom providers & sync nativeTheme for IME color - #529: Custom provider wizard no longer requires an API key. Users can press Enter to skip the key step for local providers. The env array and auth.set call are now conditional on key presence. - #518: Sync Electron's nativeTheme.themeSource with the app's theme mode so that IME composition text color matches the active theme. On Windows, Chromium's internal dark mode flag (from nativeTheme) influences how Microsoft IME renders composition text. Without this sync, a system-dark + app-light mismatch caused white IME text on a white background, making pre-input characters invisible. --- packages/desktop/src/main/ipc.ts | 4 +++- packages/desktop/src/main/windows.ts | 4 ++++ packages/desktop/src/preload/index.ts | 1 + packages/desktop/src/preload/types.ts | 1 + packages/desktop/src/renderer/index.tsx | 3 ++- .../cli/cmd/tui/component/dialog-provider.tsx | 21 ++++++++++--------- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/desktop/src/main/ipc.ts b/packages/desktop/src/main/ipc.ts index 8dbca8ee..45ed1b4e 100644 --- a/packages/desktop/src/main/ipc.ts +++ b/packages/desktop/src/main/ipc.ts @@ -11,7 +11,7 @@ import type { WslConfig, } from "../preload/types" import { getStore } from "./store" -import { setTitlebar } from "./windows" +import { setTitlebar, setNativeThemeSource } from "./windows" const pickerFilters = (ext?: string[]) => { if (!ext || ext.length === 0) return undefined @@ -38,6 +38,7 @@ type Deps = { checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }> installUpdate: () => Promise | void setBackgroundColor: (color: string) => void + setNativeTheme: (mode: "light" | "dark") => void } export function registerIpcHandlers(deps: Deps) { @@ -69,6 +70,7 @@ export function registerIpcHandlers(deps: Deps) { ipcMain.handle("check-update", () => deps.checkUpdate()) ipcMain.handle("install-update", () => deps.installUpdate()) ipcMain.handle("set-background-color", (_event: IpcMainInvokeEvent, color: string) => deps.setBackgroundColor(color)) + ipcMain.handle("set-native-theme", (_event: IpcMainInvokeEvent, mode: "light" | "dark") => setNativeThemeSource(mode)) ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => { const store = getStore(name) const value = store.get(key) diff --git a/packages/desktop/src/main/windows.ts b/packages/desktop/src/main/windows.ts index 337e1ca0..ae1b45f1 100644 --- a/packages/desktop/src/main/windows.ts +++ b/packages/desktop/src/main/windows.ts @@ -52,6 +52,10 @@ function overlay(theme: Partial = {}) { } } +export function setNativeThemeSource(mode: "light" | "dark") { + nativeTheme.themeSource = mode +} + export function setTitlebar(win: BrowserWindow, theme: Partial = {}) { if (process.platform !== "win32") return win.setTitleBarOverlay(overlay(theme)) diff --git a/packages/desktop/src/preload/index.ts b/packages/desktop/src/preload/index.ts index 6261419c..4b33f0ed 100644 --- a/packages/desktop/src/preload/index.ts +++ b/packages/desktop/src/preload/index.ts @@ -66,6 +66,7 @@ const api: ElectronAPI = { checkUpdate: () => ipcRenderer.invoke("check-update"), installUpdate: () => ipcRenderer.invoke("install-update"), setBackgroundColor: (color: string) => ipcRenderer.invoke("set-background-color", color), + setNativeTheme: (mode: "light" | "dark") => ipcRenderer.invoke("set-native-theme", mode), } contextBridge.exposeInMainWorld("api", api) diff --git a/packages/desktop/src/preload/types.ts b/packages/desktop/src/preload/types.ts index 6e22954d..74beeee9 100644 --- a/packages/desktop/src/preload/types.ts +++ b/packages/desktop/src/preload/types.ts @@ -76,4 +76,5 @@ export type ElectronAPI = { checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }> installUpdate: () => Promise setBackgroundColor: (color: string) => Promise + setNativeTheme: (mode: "light" | "dark") => Promise } diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index f7df9d4d..83e9fb6a 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -310,11 +310,12 @@ render(() => { createEffect(() => { theme.themeId() - theme.mode() + const mode = theme.mode() const bg = getComputedStyle(document.documentElement).getPropertyValue("--background-base").trim() if (bg) { void window.api.setBackgroundColor(bg) } + void window.api.setNativeTheme(mode) }) return null diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index c9c94a45..a2f73152 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -188,10 +188,9 @@ export async function runCustomProviderWizard(opts: { const baseURL = baseURLRaw.trim() if (!baseURL) return - const apiKeyRaw = await step(4, 6, "API key", "sk-...") + const apiKeyRaw = await step(4, 6, "API key (optional, Enter to skip)", "sk-...") if (apiKeyRaw === null) return const apiKey = apiKeyRaw.trim() - if (!apiKey) return const modelIDRaw = await step(5, 6, "First model id", "e.g. claude-sonnet-4-6") if (modelIDRaw === null) return @@ -208,7 +207,7 @@ export async function runCustomProviderWizard(opts: { [providerID]: { name, npm: "@ai-sdk/openai-compatible", - env: [envKey], + ...(apiKey ? { env: [envKey] } : {}), options: { baseURL, setCacheKey: true, @@ -228,13 +227,15 @@ export async function runCustomProviderWizard(opts: { return } - const authRes = await sdk.client.auth.set({ - providerID, - auth: { type: "api", key: apiKey }, - }) - if (authRes.error) { - toast.show({ variant: "error", message: JSON.stringify(authRes.error) }) - return + if (apiKey) { + const authRes = await sdk.client.auth.set({ + providerID, + auth: { type: "api", key: apiKey }, + }) + if (authRes.error) { + toast.show({ variant: "error", message: JSON.stringify(authRes.error) }) + return + } } await sdk.client.instance.dispose()