From 3ae3f494c75d53bf1d545cb48593ce36276b214d Mon Sep 17 00:00:00 2001 From: 4ndrelim Date: Sun, 25 Jan 2026 03:18:40 +0800 Subject: [PATCH 1/6] fix: BYOK --- internal/services/toolkit/client/client_v2.go | 21 +++++++++++++------ internal/services/toolkit/client/utils_v2.go | 6 +++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/services/toolkit/client/client_v2.go b/internal/services/toolkit/client/client_v2.go index 8279b6bc..9f2bb0b9 100644 --- a/internal/services/toolkit/client/client_v2.go +++ b/internal/services/toolkit/client/client_v2.go @@ -35,7 +35,7 @@ func (a *AIClientV2) GetOpenAIClient(llmConfig *models.LLMProviderConfig) *opena if Endpoint == "" { if APIKey != "" { // User provided their own API key, use the OpenAI-compatible endpoint - Endpoint = a.cfg.InferenceBaseURL + "/openai" + Endpoint = a.cfg.OpenAIBaseURL // standard openai base url } else { Endpoint = a.cfg.InferenceBaseURL + "/openrouter" } @@ -63,11 +63,20 @@ func NewAIClientV2( logger *logger.Logger, ) *AIClientV2 { database := db.Database("paperdebugger") - oaiClient := openai.NewClient( - option.WithBaseURL(cfg.InferenceBaseURL+"/openrouter"), - option.WithAPIKey(cfg.InferenceAPIKey), - ) - CheckOpenAIWorksV2(oaiClient, logger) + + if cfg.InferenceBaseURL != "" && cfg.InferenceAPIKey != "" { + oaiClient := openai.NewClient( + option.WithBaseURL(cfg.InferenceBaseURL+"/openrouter"), + option.WithAPIKey(cfg.InferenceAPIKey), + ) + CheckOpenAIWorksV2(oaiClient, cfg.InferenceBaseURL+"/openrouter", "openai/gpt-5-nano", logger) + } else { + oaiClient := openai.NewClient( + option.WithBaseURL(cfg.OpenAIBaseURL), + option.WithAPIKey(cfg.OpenAIAPIKey), + ) + CheckOpenAIWorksV2(oaiClient, cfg.OpenAIBaseURL, "gpt-5-nano", logger) + } toolRegistry := initializeToolkitV2(db, projectService, cfg, logger) toolCallHandler := handler.NewToolCallHandlerV2(toolRegistry) diff --git a/internal/services/toolkit/client/utils_v2.go b/internal/services/toolkit/client/utils_v2.go index e502cb21..61b38bd4 100644 --- a/internal/services/toolkit/client/utils_v2.go +++ b/internal/services/toolkit/client/utils_v2.go @@ -87,13 +87,13 @@ func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2) } } -func CheckOpenAIWorksV2(oaiClient openaiv3.Client, logger *logger.Logger) { - logger.Info("[AI Client V2] checking if openai client works") +func CheckOpenAIWorksV2(oaiClient openaiv3.Client, baseUrl string, model string, logger *logger.Logger) { + logger.Info("[AI Client V2] checking if openai client works with " + baseUrl + "..") chatCompletion, err := oaiClient.Chat.Completions.New(context.TODO(), openaiv3.ChatCompletionNewParams{ Messages: []openaiv3.ChatCompletionMessageParamUnion{ openaiv3.UserMessage("Say 'openai client works'"), }, - Model: "openai/gpt-5-nano", + Model: model, }) if err != nil { logger.Errorf("[AI Client V2] openai client does not work: %v", err) From 9fc435f7f44cd52560f54bf8b5db058bb628a422 Mon Sep 17 00:00:00 2001 From: 4ndrelim Date: Sun, 25 Jan 2026 03:18:49 +0800 Subject: [PATCH 2/6] fix: stale mdoelslug --- .../src/views/settings/setting-text-input.tsx | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/webapp/_webapp/src/views/settings/setting-text-input.tsx b/webapp/_webapp/src/views/settings/setting-text-input.tsx index 893df676..a67e9e27 100644 --- a/webapp/_webapp/src/views/settings/setting-text-input.tsx +++ b/webapp/_webapp/src/views/settings/setting-text-input.tsx @@ -3,6 +3,8 @@ import { Button, cn } from "@heroui/react"; import { useSettingStore } from "../../stores/setting-store"; import { Settings } from "../../pkg/gen/apiclient/user/v1/user_pb"; import { PlainMessage } from "../../query/types"; +import { useConversationStore } from "../../stores/conversation/conversation-store"; +import { listSupportedModels } from "../../query/api"; type SettingKey = keyof PlainMessage; @@ -27,6 +29,7 @@ export function createSettingsTextInput(settingKey: K) { password = false, }: SettingsTextInputProps) { const { settings, isUpdating, updateSettings } = useSettingStore(); + const { currentConversation, setCurrentConversation } = useConversationStore(); const [value, setValue] = useState(""); const [originalValue, setOriginalValue] = useState(""); const [isEditing, setIsEditing] = useState(false); @@ -47,7 +50,29 @@ export function createSettingsTextInput(settingKey: K) { await updateSettings({ [settingKey]: value.trim() } as Partial>); setOriginalValue(value.trim()); setIsEditing(false); - }, [value, updateSettings]); // settingKey is an outer scope value, not a dependency + + // If openaiApiKey was updated, fetch new model list and update current model slug + if (settingKey === "openaiApiKey") { + const response = await listSupportedModels({}); + if (response.models?.length) { + const { currentConversation: latest } = useConversationStore.getState(); + // try to find a model that matches the current slug + const currentSlugLower = latest.modelSlug.toLowerCase(); + const matchingModel = response.models.find(m => + currentSlugLower.includes(m.name.toLowerCase()) + ); + // fall back to the first model in the list + const newSlug = matchingModel?.slug ?? response.models[0].slug; + + if (newSlug !== latest.modelSlug) { + setCurrentConversation({ + ...latest, + modelSlug: newSlug, + }); + } + } + } + }, [value, settingKey, updateSettings]); // settingKey is an outer scope value, not a dependency const handleEdit = useCallback(() => { setIsEditing(true); From 5176de9af7c0ebc54979582de0aa5f44eeabedb8 Mon Sep 17 00:00:00 2001 From: Wang Jiayi <95198512+wjiayis@users.noreply.github.com> Date: Sun, 25 Jan 2026 22:24:47 +0800 Subject: [PATCH 3/6] fix: stale modelSlug (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. fix: remove definition of `currentConversation` as it is not used (to fix the following error when running `npm run build:prd:chrome` ```bash > webapp@0.0.0 build:prd:chrome > bash -c 'source ./scripts/build-prd-chrome.sh' 🧩 Start building Chrome Extension ... 📦 Version: 2.16.0 📦 Monorepo Revision: 9fc435 📦 Beta Build: false 📦 API Endpoint: https://app.paperdebugger.com ❌ Failed to build Chrome Extension, please check logs/build.log > webapp@0.0.0 build > tsc -b && npm run _build:default && npm run _build:background && npm run _build:intermediate && npm run _build:settings && npm run _build:popup src/views/settings/setting-text-input.tsx(32,13): error TS6133: 'currentConversation' is declared but its value is never read. ``` 2. fix: change matching from substring to exact, to prevent `gpt-4.1-mini` from matching to `gpt-4.0` (edit: typo, should be `gpt-4.1`), making the method more robust to the order of supported models defined. 3. [edit] refactor: use IsCustom and not redefine logic in NewAIClientV2 --- internal/services/toolkit/client/client_v2.go | 34 +++++++++++++------ .../src/views/settings/setting-text-input.tsx | 4 +-- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/internal/services/toolkit/client/client_v2.go b/internal/services/toolkit/client/client_v2.go index 9f2bb0b9..3c672c66 100644 --- a/internal/services/toolkit/client/client_v2.go +++ b/internal/services/toolkit/client/client_v2.go @@ -63,21 +63,33 @@ func NewAIClientV2( logger *logger.Logger, ) *AIClientV2 { database := db.Database("paperdebugger") + + llmProvider := &models.LLMProviderConfig{ + APIKey: cfg.OpenAIAPIKey, + } + + var baseUrl string + var apiKey string + var modelSlug string - if cfg.InferenceBaseURL != "" && cfg.InferenceAPIKey != "" { - oaiClient := openai.NewClient( - option.WithBaseURL(cfg.InferenceBaseURL+"/openrouter"), - option.WithAPIKey(cfg.InferenceAPIKey), - ) - CheckOpenAIWorksV2(oaiClient, cfg.InferenceBaseURL+"/openrouter", "openai/gpt-5-nano", logger) + // User specified their own API key, use the OpenAI-compatible endpoint + if llmProvider != nil && llmProvider.IsCustom() { + baseUrl = cfg.OpenAIBaseURL + apiKey = cfg.OpenAIAPIKey + modelSlug = "gpt-5-nano" + // Use the default inference endpoint } else { - oaiClient := openai.NewClient( - option.WithBaseURL(cfg.OpenAIBaseURL), - option.WithAPIKey(cfg.OpenAIAPIKey), - ) - CheckOpenAIWorksV2(oaiClient, cfg.OpenAIBaseURL, "gpt-5-nano", logger) + baseUrl = cfg.InferenceBaseURL + "/openrouter" + apiKey = cfg.InferenceAPIKey + modelSlug = "openai/gpt-5-nano" } + oaiClient := openai.NewClient( + option.WithBaseURL(baseUrl), + option.WithAPIKey(apiKey), + ) + CheckOpenAIWorksV2(oaiClient, baseUrl, modelSlug, logger) + toolRegistry := initializeToolkitV2(db, projectService, cfg, logger) toolCallHandler := handler.NewToolCallHandlerV2(toolRegistry) diff --git a/webapp/_webapp/src/views/settings/setting-text-input.tsx b/webapp/_webapp/src/views/settings/setting-text-input.tsx index a67e9e27..23eab3ad 100644 --- a/webapp/_webapp/src/views/settings/setting-text-input.tsx +++ b/webapp/_webapp/src/views/settings/setting-text-input.tsx @@ -29,7 +29,7 @@ export function createSettingsTextInput(settingKey: K) { password = false, }: SettingsTextInputProps) { const { settings, isUpdating, updateSettings } = useSettingStore(); - const { currentConversation, setCurrentConversation } = useConversationStore(); + const { setCurrentConversation } = useConversationStore(); const [value, setValue] = useState(""); const [originalValue, setOriginalValue] = useState(""); const [isEditing, setIsEditing] = useState(false); @@ -59,7 +59,7 @@ export function createSettingsTextInput(settingKey: K) { // try to find a model that matches the current slug const currentSlugLower = latest.modelSlug.toLowerCase(); const matchingModel = response.models.find(m => - currentSlugLower.includes(m.name.toLowerCase()) + currentSlugLower === m.name.toLowerCase() ); // fall back to the first model in the list const newSlug = matchingModel?.slug ?? response.models[0].slug; From 9f037fb8459a96a202940502b4a4dd1792cffbea Mon Sep 17 00:00:00 2001 From: 4ndrelim Date: Sun, 25 Jan 2026 23:57:45 +0800 Subject: [PATCH 4/6] nits --- internal/services/toolkit/client/client_v2.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/services/toolkit/client/client_v2.go b/internal/services/toolkit/client/client_v2.go index 3c672c66..87a1e26a 100644 --- a/internal/services/toolkit/client/client_v2.go +++ b/internal/services/toolkit/client/client_v2.go @@ -37,6 +37,7 @@ func (a *AIClientV2) GetOpenAIClient(llmConfig *models.LLMProviderConfig) *opena // User provided their own API key, use the OpenAI-compatible endpoint Endpoint = a.cfg.OpenAIBaseURL // standard openai base url } else { + // suffix needed for cloudflare gateway Endpoint = a.cfg.InferenceBaseURL + "/openrouter" } } @@ -63,7 +64,7 @@ func NewAIClientV2( logger *logger.Logger, ) *AIClientV2 { database := db.Database("paperdebugger") - + llmProvider := &models.LLMProviderConfig{ APIKey: cfg.OpenAIAPIKey, } @@ -77,18 +78,23 @@ func NewAIClientV2( baseUrl = cfg.OpenAIBaseURL apiKey = cfg.OpenAIAPIKey modelSlug = "gpt-5-nano" - // Use the default inference endpoint + // Use the default inference endpoint } else { + // suffix needed for cloudflare gateway baseUrl = cfg.InferenceBaseURL + "/openrouter" apiKey = cfg.InferenceAPIKey modelSlug = "openai/gpt-5-nano" } - oaiClient := openai.NewClient( - option.WithBaseURL(baseUrl), - option.WithAPIKey(apiKey), + CheckOpenAIWorksV2( + openai.NewClient( + option.WithBaseURL(baseUrl), + option.WithAPIKey(apiKey), + ), + baseUrl, + modelSlug, + logger, ) - CheckOpenAIWorksV2(oaiClient, baseUrl, modelSlug, logger) toolRegistry := initializeToolkitV2(db, projectService, cfg, logger) toolCallHandler := handler.NewToolCallHandlerV2(toolRegistry) From 81f2457e939314742dd062fb3fb62b31e1afcc22 Mon Sep 17 00:00:00 2001 From: 4ndrelim Date: Mon, 26 Jan 2026 00:27:17 +0800 Subject: [PATCH 5/6] nits --- internal/services/toolkit/client/utils_v2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/toolkit/client/utils_v2.go b/internal/services/toolkit/client/utils_v2.go index 61b38bd4..c3ab40dd 100644 --- a/internal/services/toolkit/client/utils_v2.go +++ b/internal/services/toolkit/client/utils_v2.go @@ -88,7 +88,7 @@ func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2) } func CheckOpenAIWorksV2(oaiClient openaiv3.Client, baseUrl string, model string, logger *logger.Logger) { - logger.Info("[AI Client V2] checking if openai client works with " + baseUrl + "..") + logger.Info("[AI Client V2] checking if openai client works with " + baseUrl + " ..") chatCompletion, err := oaiClient.Chat.Completions.New(context.TODO(), openaiv3.ChatCompletionNewParams{ Messages: []openaiv3.ChatCompletionMessageParamUnion{ openaiv3.UserMessage("Say 'openai client works'"), From 7deb2c4b4654122b63d4f3d087b38abed9eed91c Mon Sep 17 00:00:00 2001 From: 4ndrelim Date: Mon, 26 Jan 2026 00:27:35 +0800 Subject: [PATCH 6/6] improve matching modelSlug logic --- .../_webapp/src/views/settings/setting-text-input.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webapp/_webapp/src/views/settings/setting-text-input.tsx b/webapp/_webapp/src/views/settings/setting-text-input.tsx index 23eab3ad..e2fe2c00 100644 --- a/webapp/_webapp/src/views/settings/setting-text-input.tsx +++ b/webapp/_webapp/src/views/settings/setting-text-input.tsx @@ -46,6 +46,10 @@ export function createSettingsTextInput(settingKey: K) { const valueChanged = value !== originalValue; + // helper normalizes model by retrieving the model (assumed to be the last segment, if '/' present) + const normalizeModelId = (modelSlug: string) => + modelSlug.toLowerCase().trim().split("/").filter(Boolean).pop()!; + const saveSettings = useCallback(async () => { await updateSettings({ [settingKey]: value.trim() } as Partial>); setOriginalValue(value.trim()); @@ -57,9 +61,11 @@ export function createSettingsTextInput(settingKey: K) { if (response.models?.length) { const { currentConversation: latest } = useConversationStore.getState(); // try to find a model that matches the current slug - const currentSlugLower = latest.modelSlug.toLowerCase(); + // we don't do exact match but attempt exact suffix match (case insensitive); as per convention + // we don't assume any provided prefix (e.g. openai/, quen/) but fair to assume model name is suffix + const currentId = normalizeModelId(latest.modelSlug); const matchingModel = response.models.find(m => - currentSlugLower === m.name.toLowerCase() + normalizeModelId(m.name) === currentId ); // fall back to the first model in the list const newSlug = matchingModel?.slug ?? response.models[0].slug;