Skip to content

Commit 5b36033

Browse files
authored
fix: stale modelSlug (#99)
1 parent a66afba commit 5b36033

3 files changed

Lines changed: 67 additions & 9 deletions

File tree

internal/services/toolkit/client/client_v2.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ func (a *AIClientV2) GetOpenAIClient(llmConfig *models.LLMProviderConfig) *opena
3535
if Endpoint == "" {
3636
if APIKey != "" {
3737
// User provided their own API key, use the OpenAI-compatible endpoint
38-
Endpoint = a.cfg.InferenceBaseURL + "/openai"
38+
Endpoint = a.cfg.OpenAIBaseURL // standard openai base url
3939
} else {
40+
// suffix needed for cloudflare gateway
4041
Endpoint = a.cfg.InferenceBaseURL + "/openrouter"
4142
}
4243
}
@@ -63,11 +64,37 @@ func NewAIClientV2(
6364
logger *logger.Logger,
6465
) *AIClientV2 {
6566
database := db.Database("paperdebugger")
66-
oaiClient := openai.NewClient(
67-
option.WithBaseURL(cfg.InferenceBaseURL+"/openrouter"),
68-
option.WithAPIKey(cfg.InferenceAPIKey),
67+
68+
llmProvider := &models.LLMProviderConfig{
69+
APIKey: cfg.OpenAIAPIKey,
70+
}
71+
72+
var baseUrl string
73+
var apiKey string
74+
var modelSlug string
75+
76+
// User specified their own API key, use the OpenAI-compatible endpoint
77+
if llmProvider != nil && llmProvider.IsCustom() {
78+
baseUrl = cfg.OpenAIBaseURL
79+
apiKey = cfg.OpenAIAPIKey
80+
modelSlug = "gpt-5-nano"
81+
// Use the default inference endpoint
82+
} else {
83+
// suffix needed for cloudflare gateway
84+
baseUrl = cfg.InferenceBaseURL + "/openrouter"
85+
apiKey = cfg.InferenceAPIKey
86+
modelSlug = "openai/gpt-5-nano"
87+
}
88+
89+
CheckOpenAIWorksV2(
90+
openai.NewClient(
91+
option.WithBaseURL(baseUrl),
92+
option.WithAPIKey(apiKey),
93+
),
94+
baseUrl,
95+
modelSlug,
96+
logger,
6997
)
70-
CheckOpenAIWorksV2(oaiClient, logger)
7198

7299
toolRegistry := initializeToolkitV2(db, projectService, cfg, logger)
73100
toolCallHandler := handler.NewToolCallHandlerV2(toolRegistry)

internal/services/toolkit/client/utils_v2.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2)
8787
}
8888
}
8989

90-
func CheckOpenAIWorksV2(oaiClient openaiv3.Client, logger *logger.Logger) {
91-
logger.Info("[AI Client V2] checking if openai client works")
90+
func CheckOpenAIWorksV2(oaiClient openaiv3.Client, baseUrl string, model string, logger *logger.Logger) {
91+
logger.Info("[AI Client V2] checking if openai client works with " + baseUrl + " ..")
9292
chatCompletion, err := oaiClient.Chat.Completions.New(context.TODO(), openaiv3.ChatCompletionNewParams{
9393
Messages: []openaiv3.ChatCompletionMessageParamUnion{
9494
openaiv3.UserMessage("Say 'openai client works'"),
9595
},
96-
Model: "openai/gpt-5-nano",
96+
Model: model,
9797
})
9898
if err != nil {
9999
logger.Errorf("[AI Client V2] openai client does not work: %v", err)

webapp/_webapp/src/views/settings/setting-text-input.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Button, cn } from "@heroui/react";
33
import { useSettingStore } from "../../stores/setting-store";
44
import { Settings } from "../../pkg/gen/apiclient/user/v1/user_pb";
55
import { PlainMessage } from "../../query/types";
6+
import { useConversationStore } from "../../stores/conversation/conversation-store";
7+
import { listSupportedModels } from "../../query/api";
68

79
type SettingKey = keyof PlainMessage<Settings>;
810

@@ -27,6 +29,7 @@ export function createSettingsTextInput<K extends SettingKey>(settingKey: K) {
2729
password = false,
2830
}: SettingsTextInputProps) {
2931
const { settings, isUpdating, updateSettings } = useSettingStore();
32+
const { setCurrentConversation } = useConversationStore();
3033
const [value, setValue] = useState<string>("");
3134
const [originalValue, setOriginalValue] = useState<string>("");
3235
const [isEditing, setIsEditing] = useState<boolean>(false);
@@ -43,11 +46,39 @@ export function createSettingsTextInput<K extends SettingKey>(settingKey: K) {
4346

4447
const valueChanged = value !== originalValue;
4548

49+
// helper normalizes model by retrieving the model (assumed to be the last segment, if '/' present)
50+
const normalizeModelId = (modelSlug: string) =>
51+
modelSlug.toLowerCase().trim().split("/").filter(Boolean).pop()!;
52+
4653
const saveSettings = useCallback(async () => {
4754
await updateSettings({ [settingKey]: value.trim() } as Partial<PlainMessage<Settings>>);
4855
setOriginalValue(value.trim());
4956
setIsEditing(false);
50-
}, [value, updateSettings]); // settingKey is an outer scope value, not a dependency
57+
58+
// If openaiApiKey was updated, fetch new model list and update current model slug
59+
if (settingKey === "openaiApiKey") {
60+
const response = await listSupportedModels({});
61+
if (response.models?.length) {
62+
const { currentConversation: latest } = useConversationStore.getState();
63+
// try to find a model that matches the current slug
64+
// we don't do exact match but attempt exact suffix match (case insensitive); as per convention
65+
// we don't assume any provided prefix (e.g. openai/, quen/) but fair to assume model name is suffix
66+
const currentId = normalizeModelId(latest.modelSlug);
67+
const matchingModel = response.models.find(m =>
68+
normalizeModelId(m.name) === currentId
69+
);
70+
// fall back to the first model in the list
71+
const newSlug = matchingModel?.slug ?? response.models[0].slug;
72+
73+
if (newSlug !== latest.modelSlug) {
74+
setCurrentConversation({
75+
...latest,
76+
modelSlug: newSlug,
77+
});
78+
}
79+
}
80+
}
81+
}, [value, settingKey, updateSettings]); // settingKey is an outer scope value, not a dependency
5182

5283
const handleEdit = useCallback(() => {
5384
setIsEditing(true);

0 commit comments

Comments
 (0)