diff --git a/src-tauri/src/deeplink/provider.rs b/src-tauri/src/deeplink/provider.rs
index 32fa77b58..2ae90a013 100644
--- a/src-tauri/src/deeplink/provider.rs
+++ b/src-tauri/src/deeplink/provider.rs
@@ -428,8 +428,11 @@ fn build_additive_app_settings(request: &DeepLinkImportRequest) -> serde_json::V
/// Emitting camelCase here — as the OpenClaw path does — would poison the
/// YAML with unknown root fields the Hermes runtime ignores.
///
-/// `api_mode` is deliberately omitted so Hermes auto-detects the protocol
-/// from the endpoint; callers can still override via the UI later.
+/// `api_mode` is always written explicitly. Deeplinks have no field to carry
+/// it, so we default to `chat_completions` (the most widely compatible
+/// protocol) and let the user adjust via the UI after import. We never rely
+/// on Hermes' built-in URL heuristics, which only recognize a handful of
+/// official endpoints.
fn build_hermes_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
let endpoint = get_primary_endpoint(request);
@@ -447,6 +450,8 @@ fn build_hermes_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
config.insert("api_key".to_string(), json!(api_key));
}
+ config.insert("api_mode".to_string(), json!("chat_completions"));
+
if let Some(model) = &request.model {
config.insert(
"models".to_string(),
@@ -784,11 +789,12 @@ mod tests {
}
#[test]
- fn build_hermes_settings_omits_api_mode_for_auto_detect() {
+ fn build_hermes_settings_writes_default_api_mode() {
let settings = build_hermes_settings(&hermes_request());
- assert!(
- settings.as_object().unwrap().get("api_mode").is_none(),
- "api_mode must be omitted so Hermes auto-detects"
+ assert_eq!(
+ settings.as_object().unwrap().get("api_mode").unwrap(),
+ "chat_completions",
+ "api_mode must be written explicitly so Hermes never falls back to URL auto-detection"
);
}
@@ -810,6 +816,7 @@ mod tests {
assert!(obj.get("base_url").is_none());
assert!(obj.get("api_key").is_none());
assert!(obj.get("models").is_none());
+ assert_eq!(obj.get("api_mode").unwrap(), "chat_completions");
}
#[test]
diff --git a/src/components/providers/forms/HermesFormFields.tsx b/src/components/providers/forms/HermesFormFields.tsx
index e74b5365b..598a2e0d7 100644
--- a/src/components/providers/forms/HermesFormFields.tsx
+++ b/src/components/providers/forms/HermesFormFields.tsx
@@ -40,7 +40,7 @@ import {
} from "@/lib/api/model-fetch";
import {
hermesApiModes,
- type HermesApiModeChoice,
+ type HermesApiMode,
type HermesModel,
} from "@/config/hermesProviderPresets";
import type { ProviderCategory } from "@/types";
@@ -55,8 +55,8 @@ interface HermesFormFieldsProps {
websiteUrl: string;
isPartner?: boolean;
partnerPromotionKey?: string;
- apiMode: HermesApiModeChoice;
- onApiModeChange: (mode: HermesApiModeChoice) => void;
+ apiMode: HermesApiMode;
+ onApiModeChange: (mode: HermesApiMode) => void;
models: HermesModel[];
onModelsChange: (models: HermesModel[]) => void;
}
@@ -181,7 +181,7 @@ export function HermesFormFields({
{t("hermes.form.apiModeHint", {
- defaultValue:
- "供应商 API 协议。Auto 表示让 Hermes 按端点自动判断。",
+ defaultValue: "供应商 API 协议。请根据端点选择正确的协议。",
})}
diff --git a/src/components/providers/forms/hooks/useHermesFormState.ts b/src/components/providers/forms/hooks/useHermesFormState.ts
index d788e367b..44c5450eb 100644
--- a/src/components/providers/forms/hooks/useHermesFormState.ts
+++ b/src/components/providers/forms/hooks/useHermesFormState.ts
@@ -1,10 +1,11 @@
import { useState, useCallback, useMemo } from "react";
import type { AppId } from "@/lib/api";
import { useProvidersQuery } from "@/lib/query/queries";
-import type {
- HermesApiModeChoice,
- HermesModel,
- HermesProviderSettingsConfig,
+import {
+ HERMES_DEFAULT_API_MODE,
+ type HermesApiMode,
+ type HermesModel,
+ type HermesProviderSettingsConfig,
} from "@/config/hermesProviderPresets";
interface UseHermesFormStateParams {
@@ -34,12 +35,12 @@ export interface HermesFormState {
setHermesProviderKey: (key: string) => void;
hermesBaseUrl: string;
hermesApiKey: string;
- hermesApiMode: HermesApiModeChoice;
+ hermesApiMode: HermesApiMode;
hermesModels: HermesModel[];
existingHermesKeys: string[];
handleHermesBaseUrlChange: (baseUrl: string) => void;
handleHermesApiKeyChange: (apiKey: string) => void;
- handleHermesApiModeChange: (mode: HermesApiModeChoice) => void;
+ handleHermesApiModeChange: (mode: HermesApiMode) => void;
handleHermesModelsChange: (models: HermesModel[]) => void;
resetHermesState: (config?: Partial) => void;
}
@@ -92,17 +93,15 @@ export function useHermesFormState({
return parseHermesField(initialData, "api_key", "");
});
- const [hermesApiMode, setHermesApiMode] = useState(
- () => {
- if (appId !== "hermes") return "auto";
- const stored = parseHermesField(
- initialData,
- "api_mode",
- "",
- );
- return stored || "auto";
- },
- );
+ const [hermesApiMode, setHermesApiMode] = useState(() => {
+ if (appId !== "hermes") return HERMES_DEFAULT_API_MODE;
+ const stored = parseHermesField(
+ initialData,
+ "api_mode",
+ "",
+ );
+ return stored || HERMES_DEFAULT_API_MODE;
+ });
const [hermesModels, setHermesModels] = useState(() => {
if (appId !== "hermes") return [];
@@ -143,14 +142,10 @@ export function useHermesFormState({
);
const handleHermesApiModeChange = useCallback(
- (mode: HermesApiModeChoice) => {
+ (mode: HermesApiMode) => {
setHermesApiMode(mode);
updateHermesConfig((config) => {
- if (mode === "auto") {
- delete config.api_mode;
- } else {
- config.api_mode = mode;
- }
+ config.api_mode = mode;
});
},
[updateHermesConfig],
@@ -175,7 +170,7 @@ export function useHermesFormState({
setHermesProviderKey("");
setHermesBaseUrl(config?.base_url || "");
setHermesApiKey(config?.api_key || "");
- setHermesApiMode(config?.api_mode ?? "auto");
+ setHermesApiMode(config?.api_mode ?? HERMES_DEFAULT_API_MODE);
setHermesModels(config?.models ?? []);
},
[],
diff --git a/src/config/hermesProviderPresets.ts b/src/config/hermesProviderPresets.ts
index bcc41e523..de5dbabbd 100644
--- a/src/config/hermesProviderPresets.ts
+++ b/src/config/hermesProviderPresets.ts
@@ -67,30 +67,20 @@ export interface HermesSuggestedDefaults {
};
}
-/** Hermes custom_provider protocol mode (optional; auto-detected when omitted). */
+/** Hermes custom_provider protocol mode. Always written explicitly. */
export type HermesApiMode =
| "chat_completions"
| "anthropic_messages"
| "codex_responses";
-/**
- * Form-facing value used by the API Mode dropdown.
- *
- * `auto` is the UI-only sentinel for "omit `api_mode` and let Hermes detect the
- * protocol from the endpoint". When serialized to `settings_config`, `auto`
- * becomes `undefined` so the YAML doesn't include `api_mode` at all.
- */
-export type HermesApiModeChoice = "auto" | HermesApiMode;
+/** Default mode used when a provider has no stored value yet. */
+export const HERMES_DEFAULT_API_MODE: HermesApiMode = "chat_completions";
-/**
- * Dropdown options for the API Mode selector. `labelKey` is looked up in i18n;
- * `value` of "auto" means "don't write `api_mode` to the config".
- */
+/** Dropdown options for the API Mode selector. `labelKey` is looked up in i18n. */
export const hermesApiModes: Array<{
- value: HermesApiModeChoice;
+ value: HermesApiMode;
labelKey: string;
}> = [
- { value: "auto", labelKey: "hermes.form.apiModeAuto" },
{ value: "chat_completions", labelKey: "hermes.form.apiModeChatCompletions" },
{
value: "anthropic_messages",
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index f3616a1d3..144b7eb16 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1651,11 +1651,10 @@
"providerKeyInvalid": "Provider key can only contain lowercase letters, numbers, and hyphens",
"providerKeyDuplicate": "This provider key already exists",
"apiMode": "API Mode",
- "apiModeHint": "Provider API protocol. Auto lets Hermes detect it from the endpoint.",
- "apiModeAuto": "Auto-detect",
+ "apiModeHint": "Provider API protocol. Choose the format that matches your endpoint.",
"apiModeChatCompletions": "OpenAI Chat Completions",
"apiModeAnthropicMessages": "Anthropic Messages",
- "apiModeCodexResponses": "Codex Responses (Copilot / OpenCode)",
+ "apiModeCodexResponses": "OpenAI Responses",
"models": "Models",
"addModel": "Add model",
"noModels": "No models configured. Switching to this provider won't change the default model.",
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index 1389b7ce6..3dc46b8b6 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -1651,11 +1651,10 @@
"providerKeyInvalid": "プロバイダーキーは小文字、数字、ハイフンのみ使用できます",
"providerKeyDuplicate": "このプロバイダーキーは既に存在します",
"apiMode": "API モード",
- "apiModeHint": "プロバイダーの API プロトコル。Auto はエンドポイントから自動判定します。",
- "apiModeAuto": "自動検出",
+ "apiModeHint": "プロバイダーの API プロトコル。エンドポイントに合わせて正しい形式を選択してください。",
"apiModeChatCompletions": "OpenAI Chat Completions",
"apiModeAnthropicMessages": "Anthropic Messages",
- "apiModeCodexResponses": "Codex Responses(Copilot / OpenCode)",
+ "apiModeCodexResponses": "OpenAI Responses",
"models": "モデル一覧",
"addModel": "モデルを追加",
"noModels": "モデル未設定。このプロバイダーに切り替えてもデフォルトモデルは更新されません。",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index 74ec9e379..0df36ad9e 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -1651,11 +1651,10 @@
"providerKeyInvalid": "供应商标识只能包含小写字母、数字和连字符",
"providerKeyDuplicate": "该供应商标识已存在",
"apiMode": "API 模式",
- "apiModeHint": "供应商 API 协议。Auto 表示让 Hermes 按端点自动判断。",
- "apiModeAuto": "自动检测",
+ "apiModeHint": "供应商 API 协议。请根据端点选择正确的协议。",
"apiModeChatCompletions": "OpenAI Chat Completions",
"apiModeAnthropicMessages": "Anthropic Messages",
- "apiModeCodexResponses": "Codex Responses(Copilot / OpenCode)",
+ "apiModeCodexResponses": "OpenAI Responses",
"models": "模型列表",
"addModel": "添加模型",
"noModels": "暂无模型配置。切换到此供应商时将不会更新默认模型。",