hermes-web-ui 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hermes-web-ui.mjs +15 -1
- package/dist/client/assets/{Add-CKf6ViXR.js → Add-Cc7cgBoB.js} +1 -1
- package/dist/client/assets/{Button-CrrCCorI.js → Button-EoeZgIFH.js} +1 -1
- package/dist/client/assets/{ChannelsView-D4I7hhZO.js → ChannelsView-Bfbq3w7n.js} +1 -1
- package/dist/client/assets/{ChatView-DxyBUK57.js → ChatView-CDdyTo72.js} +1 -1
- package/dist/client/assets/{Close-C9xwy-pW.js → Close-CKHcXisf.js} +1 -1
- package/dist/client/assets/{FormItem-BgJdrTW0.js → FormItem-CvZvjrtr.js} +1 -1
- package/dist/client/assets/{GatewaysView-Cib2JydO.js → GatewaysView-Dp4-TFPE.js} +1 -1
- package/dist/client/assets/{Input-ChENEW-Z.js → Input-Bk7XdoG-.js} +1 -1
- package/dist/client/assets/{InputNumber-Xd6HWSdp.js → InputNumber-Dn0EHi-K.js} +1 -1
- package/dist/client/assets/{JobsView-SnToCbDd.js → JobsView-D4JN73Zz.js} +2 -2
- package/dist/client/assets/{LoginView-BZdmMnsf.js → LoginView--hy5CI5O.js} +1 -1
- package/dist/client/assets/{LogsView-DblvOJIg.js → LogsView-Dz2ZeYad.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-DJLVk7ei.js → MarkdownRenderer-BbedVxvo.js} +1 -1
- package/dist/client/assets/{MemoryView-exXvRwCc.js → MemoryView-D2EHM35l.js} +1 -1
- package/dist/client/assets/{Modal-B2zvXTrk.js → Modal-C5-Iw50K.js} +1 -1
- package/dist/client/assets/ModelsView-CtrRf4vK.js +1 -0
- package/dist/client/assets/{ModelsView-jbgZP3YF.css → ModelsView-VM-oMq5M.css} +1 -1
- package/dist/client/assets/{Popconfirm-BoZc0kKk.js → Popconfirm-C-M2anVL.js} +1 -1
- package/dist/client/assets/{Popover-Cu52vG3D.js → Popover-mIRPCy7U.js} +1 -1
- package/dist/client/assets/{ProfilesView-D0FY7Jwe.js → ProfilesView-Dy9PivgB.js} +1 -1
- package/dist/client/assets/{Select-BHc7u-Yf.js → Select-uZBC8HC2.js} +2 -2
- package/dist/client/assets/{SettingRow-i-UXlco7.js → SettingRow-D9R65bDj.js} +1 -1
- package/dist/client/assets/{SettingsView-Dhr2wzAB.css → SettingsView-C3sd8K0e.css} +1 -1
- package/dist/client/assets/SettingsView-DWEEXqSY.js +352 -0
- package/dist/client/assets/{SkillsView-B5QBaAzi.js → SkillsView-CdZSRy9_.js} +1 -1
- package/dist/client/assets/{Spin-DsNCRPk9.js → Spin-ChbFBUOD.js} +1 -1
- package/dist/client/assets/{Suffix-3xK0KZGt.js → Suffix-DgzfIwzx.js} +1 -1
- package/dist/client/assets/{Switch-Bf63XXgA.js → Switch--HhY1uSh.js} +1 -1
- package/dist/client/assets/{Tag-Dmbj68Ki.js → Tag-B2zrHMmZ.js} +1 -1
- package/dist/client/assets/{TerminalView-DrJHZ0qI.js → TerminalView-BwfnH803.js} +1 -1
- package/dist/client/assets/{Tooltip-CRbZNhG0.js → Tooltip-9tdvSKGi.js} +1 -1
- package/dist/client/assets/{UsageView-DQ43JasX.js → UsageView-zL3a7F86.js} +1 -1
- package/dist/client/assets/{Warning-kBbRMAif.js → Warning-CXXqHzLa.js} +1 -1
- package/dist/client/assets/{_plugin-vue_export-helper-CnosYBkx.js → _plugin-vue_export-helper-Cnn0Z73x.js} +1 -1
- package/dist/client/assets/app-BMobzABI.js +1 -0
- package/dist/client/assets/app-Bqu9Uz-1.js +1 -0
- package/dist/client/assets/{browser-Djp4tkp3.js → browser-CQRjhbaB.js} +1 -1
- package/dist/client/assets/chat-BIdq6ZXF.js +6 -0
- package/dist/client/assets/composables-ClIU-Ad1.js +1 -0
- package/dist/client/assets/{fade-in.cssr-CIVyTG6A.js → fade-in.cssr-lwO9nLky.js} +1 -1
- package/dist/client/assets/index-Tg6M43Om.js +284 -0
- package/dist/client/assets/{jobs-CcVaCGMJ.js → jobs-Z2HS0j2d.js} +1 -1
- package/dist/client/assets/{light-CSp9-LhE.js → light-CjCy-Dkn.js} +1 -1
- package/dist/client/assets/{light-BJ96fCLC.js → light-DZ0Ns16h.js} +1 -1
- package/dist/client/assets/{light-BF6E9z0k.js → light-DgIst23O.js} +1 -1
- package/dist/client/assets/{light-KCEDTUGE.js → light-Dx6qj2pM.js} +1 -1
- package/dist/client/assets/{light-BPqyaxve.js → light-DzpNsLai.js} +1 -1
- package/dist/client/assets/{light-D9G2GshF.js → light-oE8MEiWL.js} +1 -1
- package/dist/client/assets/models-DLQiHB7r.js +1 -0
- package/dist/client/assets/{pinia-iHE5_ZXa.js → pinia-Dp_b1vdW.js} +1 -1
- package/dist/client/assets/{profiles-CJCR84uQ.js → profiles-CNTHYFZE.js} +1 -1
- package/dist/client/assets/{router-C-NNJUuf.js → router-Dj-Nmg7q.js} +2 -2
- package/dist/client/assets/{sessions-C4bnNvzS.js → sessions-C0kvgvBm.js} +1 -1
- package/dist/client/assets/{skills-B4slZfeZ.js → skills-G7EoEvdS.js} +1 -1
- package/dist/client/assets/{use-message-BIpqgDet.js → use-message-BgToAqhv.js} +1 -1
- package/dist/client/assets/{useTheme-B78N9tyz.js → useTheme-BUShiwRu.js} +1 -1
- package/dist/client/index.html +27 -27
- package/dist/server/routes/hermes/filesystem.js +231 -199
- package/dist/server/routes/hermes/logs.js +50 -11
- package/dist/server/routes/hermes/proxy-handler.js +16 -6
- package/dist/server/services/hermes/gateway-manager.d.ts +2 -0
- package/dist/server/services/hermes/gateway-manager.js +15 -0
- package/dist/server/services/hermes/hermes-profile.d.ts +6 -0
- package/dist/server/services/hermes/hermes-profile.js +12 -0
- package/dist/server/shared/providers.js +1 -13
- package/package.json +1 -1
- package/dist/client/assets/ModelsView-DGs47Cj4.js +0 -1
- package/dist/client/assets/SettingsView-BW6ctYG5.js +0 -352
- package/dist/client/assets/app-BT9yU6N6.js +0 -1
- package/dist/client/assets/app-CjNVVG5x.js +0 -1
- package/dist/client/assets/chat-DlC9S9DK.js +0 -6
- package/dist/client/assets/composables-DCA4Yga5.js +0 -1
- package/dist/client/assets/index-D12ukDT7.js +0 -284
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e}from"./router-
|
|
1
|
+
import{o as e}from"./router-Dj-Nmg7q.js";async function t(t,n){let r=new URLSearchParams;t&&r.set(`source`,t),n&&r.set(`limit`,String(n));let i=r.toString();return(await e(`/api/hermes/sessions${i?`?${i}`:``}`)).sessions}async function n(t){try{return(await e(`/api/hermes/sessions/${t}`)).session}catch{return null}}async function r(t){try{return await e(`/api/hermes/sessions/${t}`,{method:`DELETE`}),!0}catch{return!1}}async function i(t,n){try{return await e(`/api/hermes/sessions/${t}/rename`,{method:`POST`,body:JSON.stringify({title:n})}),!0}catch{return!1}}export{i,n,t as r,r as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e}from"./router-
|
|
1
|
+
import{o as e}from"./router-Dj-Nmg7q.js";async function t(){return(await e(`/api/hermes/skills`)).categories}async function n(t){return(await e(`/api/hermes/skills/${t}`)).content}async function r(t,n){return(await e(`/api/hermes/skills/${t}/${n}/files`)).files}async function i(){return e(`/api/hermes/memory`)}async function a(t,n){await e(`/api/hermes/memory`,{method:`POST`,body:JSON.stringify({section:t,content:n})})}async function o(t,n){await e(`/api/hermes/skills/toggle`,{method:`PUT`,body:JSON.stringify({name:t,enabled:n})})}export{a,t as i,n,o,r,i as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{F as e}from"./router-
|
|
1
|
+
import{F as e}from"./router-Dj-Nmg7q.js";import{$ as t,Y as n}from"./browser-CQRjhbaB.js";var r=t(`n-message-api`),i=t(`n-message-provider`);function a(){let t=e(r,null);return t===null&&n(`use-message`,"No outer <n-message-provider /> founded. See prerequisite in https://www.naiveui.com/en-US/os-theme/components/message for more details. If you want to use `useMessage` outside setup, please check https://www.naiveui.com/zh-CN/os-theme/components/message#Q-&-A."),t}export{r as n,i as r,a as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{X as e,ct as t}from"./router-
|
|
1
|
+
import{X as e,ct as t}from"./router-Dj-Nmg7q.js";var n=`hermes_theme`,r=t(localStorage.getItem(n)||`system`),i=t(!1);function a(e){i.value=e,document.documentElement.classList.toggle(`dark`,e)}function o(e){return e===`system`?window.matchMedia(`(prefers-color-scheme: dark)`).matches:e===`dark`}a(o(r.value)),window.matchMedia(`(prefers-color-scheme: dark)`).addEventListener(`change`,()=>{r.value===`system`&&a(o(`system`))}),e(r,e=>{localStorage.setItem(n,e),a(o(e))});function s(){function e(e){r.value=e}function t(){r.value=i.value?`light`:`dark`}return{mode:r,isDark:i,setMode:e,toggleTheme:t}}export{s as t};
|
package/dist/client/index.html
CHANGED
|
@@ -6,36 +6,36 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>Hermes</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/router-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/browser-
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/fade-in.cssr-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/Suffix-
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/Close-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/Popover-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/Button-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/Tag-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-Tg6M43Om.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/router-Dj-Nmg7q.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-Cnn0Z73x.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/browser-CQRjhbaB.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/fade-in.cssr-lwO9nLky.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/Suffix-DgzfIwzx.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/Close-CKHcXisf.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/Popover-mIRPCy7U.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/Button-EoeZgIFH.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/Tag-B2zrHMmZ.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/assets/create-5zWq3BEB.js">
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/Select-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/Warning-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/Modal-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/Input-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/Select-uZBC8HC2.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/Warning-CXXqHzLa.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/Modal-C5-Iw50K.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/Input-Bk7XdoG-.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/light-Dx6qj2pM.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/omit-1BRB6K75.js">
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/pinia-
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/profiles-
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/sessions-
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/app-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/chat-
|
|
31
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
32
|
-
<link rel="modulepreload" crossorigin href="/assets/use-message-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
34
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
26
|
+
<link rel="modulepreload" crossorigin href="/assets/pinia-Dp_b1vdW.js">
|
|
27
|
+
<link rel="modulepreload" crossorigin href="/assets/profiles-CNTHYFZE.js">
|
|
28
|
+
<link rel="modulepreload" crossorigin href="/assets/sessions-C0kvgvBm.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/app-BMobzABI.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/chat-BIdq6ZXF.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DZ0Ns16h.js">
|
|
32
|
+
<link rel="modulepreload" crossorigin href="/assets/use-message-BgToAqhv.js">
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DzpNsLai.js">
|
|
34
|
+
<link rel="modulepreload" crossorigin href="/assets/light-oE8MEiWL.js">
|
|
35
35
|
<link rel="modulepreload" crossorigin href="/assets/_common-Yp55QE79.js">
|
|
36
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
37
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
38
|
-
<link rel="modulepreload" crossorigin href="/assets/useTheme-
|
|
36
|
+
<link rel="modulepreload" crossorigin href="/assets/light-CjCy-Dkn.js">
|
|
37
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DgIst23O.js">
|
|
38
|
+
<link rel="modulepreload" crossorigin href="/assets/useTheme-BUShiwRu.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="/assets/logo-Cd-t_oGE.js">
|
|
40
40
|
<link rel="stylesheet" crossorigin href="/assets/index-BEcRccNA.css">
|
|
41
41
|
</head>
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.fsRoutes = void 0;
|
|
40
40
|
const router_1 = __importDefault(require("@koa/router"));
|
|
41
41
|
const promises_1 = require("fs/promises");
|
|
42
|
+
const fs_1 = require("fs");
|
|
42
43
|
const path_1 = require("path");
|
|
43
44
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
44
45
|
const hermes_profile_1 = require("../../services/hermes/hermes-profile");
|
|
@@ -46,26 +47,25 @@ const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
|
46
47
|
// --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
|
|
47
48
|
// Maps provider key → { api_key_envs: all env var aliases for API key, base_url_env: env var for base URL }
|
|
48
49
|
const PROVIDER_ENV_MAP = {
|
|
49
|
-
openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: '
|
|
50
|
-
zai: { api_key_env: '
|
|
51
|
-
'kimi-coding': { api_key_env: '
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
minimax: { api_key_env: '
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
alibaba: { api_key_env: 'DASHSCOPE_API_KEY', base_url_env: 'DASHSCOPE_BASE_URL' },
|
|
50
|
+
openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: '' },
|
|
51
|
+
zai: { api_key_env: 'GLM_API_KEY', base_url_env: '' },
|
|
52
|
+
'kimi-coding-cn': { api_key_env: 'KIMI_CN_API_KEY', base_url_env: '' },
|
|
53
|
+
moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: '' },
|
|
54
|
+
minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: '' },
|
|
55
|
+
'minimax-cn': { api_key_env: 'MINIMAX_CN_API_KEY', base_url_env: '' },
|
|
56
|
+
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: '' },
|
|
57
|
+
alibaba: { api_key_env: 'DASHSCOPE_API_KEY', base_url_env: '' },
|
|
58
58
|
anthropic: { api_key_env: 'ANTHROPIC_API_KEY', base_url_env: '' },
|
|
59
|
-
xai: { api_key_env: 'XAI_API_KEY', base_url_env: '
|
|
60
|
-
xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: '
|
|
59
|
+
xai: { api_key_env: 'XAI_API_KEY', base_url_env: '' },
|
|
60
|
+
xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: '' },
|
|
61
61
|
gemini: { api_key_env: 'GEMINI_API_KEY', base_url_env: '' },
|
|
62
|
-
kilocode: { api_key_env: 'KILO_API_KEY', base_url_env: '
|
|
62
|
+
kilocode: { api_key_env: 'KILO_API_KEY', base_url_env: '' },
|
|
63
63
|
'ai-gateway': { api_key_env: 'AI_GATEWAY_API_KEY', base_url_env: '' },
|
|
64
|
-
'opencode-zen': { api_key_env: 'OPENCODE_API_KEY', base_url_env: '
|
|
65
|
-
'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: '
|
|
66
|
-
huggingface: { api_key_env: 'HF_TOKEN', base_url_env: '
|
|
64
|
+
'opencode-zen': { api_key_env: 'OPENCODE_API_KEY', base_url_env: '' },
|
|
65
|
+
'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: '' },
|
|
66
|
+
huggingface: { api_key_env: 'HF_TOKEN', base_url_env: '' },
|
|
67
67
|
arcee: { api_key_env: 'ARCEE_API_KEY', base_url_env: '' },
|
|
68
|
-
'openai-codex': { api_key_env: '', base_url_env: '
|
|
68
|
+
'openai-codex': { api_key_env: '', base_url_env: '' },
|
|
69
69
|
};
|
|
70
70
|
async function saveEnvValue(key, value) {
|
|
71
71
|
const envPath = (0, hermes_profile_1.getActiveEnvPath)();
|
|
@@ -105,19 +105,7 @@ async function saveEnvValue(key, value) {
|
|
|
105
105
|
let output = result.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\n+$/, '') + '\n';
|
|
106
106
|
await (0, promises_1.writeFile)(envPath, output, 'utf-8');
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
async function loadAuthJson() {
|
|
110
|
-
try {
|
|
111
|
-
const raw = await (0, promises_1.readFile)(authPath(), 'utf-8');
|
|
112
|
-
return JSON.parse(raw);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async function saveAuthJson(auth) {
|
|
119
|
-
await (0, promises_1.writeFile)(authPath(), JSON.stringify(auth, null, 2) + '\n', 'utf-8');
|
|
120
|
-
}
|
|
108
|
+
// --- Auth / Credential Pool ---
|
|
121
109
|
async function fetchProviderModels(baseUrl, apiKey) {
|
|
122
110
|
try {
|
|
123
111
|
const url = baseUrl.replace(/\/+$/, '') + '/models';
|
|
@@ -126,12 +114,12 @@ async function fetchProviderModels(baseUrl, apiKey) {
|
|
|
126
114
|
signal: AbortSignal.timeout(8000),
|
|
127
115
|
});
|
|
128
116
|
if (!res.ok) {
|
|
129
|
-
console.
|
|
117
|
+
console.warn(`[available-models] ${baseUrl} returned ${res.status}`);
|
|
130
118
|
return [];
|
|
131
119
|
}
|
|
132
120
|
const data = await res.json();
|
|
133
121
|
if (!Array.isArray(data.data)) {
|
|
134
|
-
console.
|
|
122
|
+
console.warn(`[available-models] ${baseUrl} returned unexpected format`);
|
|
135
123
|
return [];
|
|
136
124
|
}
|
|
137
125
|
return data.data.map(m => m.id).sort();
|
|
@@ -422,17 +410,11 @@ function buildModelGroups(config) {
|
|
|
422
410
|
groups.push({ provider: 'Custom', models: customModels });
|
|
423
411
|
}
|
|
424
412
|
}
|
|
425
|
-
// 3. Add current default model (if not already in custom_providers)
|
|
426
|
-
if (defaultModel && !allModelIds.has(defaultModel)) {
|
|
427
|
-
groups.unshift({ provider: 'Current', models: [{ id: defaultModel, label: defaultModel }] });
|
|
428
|
-
}
|
|
429
413
|
return { default: defaultModel, groups };
|
|
430
414
|
}
|
|
431
|
-
// GET /api/available-models —
|
|
415
|
+
// GET /api/available-models — resolve models from .env authorized providers + credential pool + custom providers
|
|
432
416
|
exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
|
|
433
417
|
try {
|
|
434
|
-
const auth = await loadAuthJson();
|
|
435
|
-
const pool = auth?.credential_pool || {};
|
|
436
418
|
const config = await readConfigYaml();
|
|
437
419
|
const modelSection = config.model;
|
|
438
420
|
let currentDefault = '';
|
|
@@ -444,125 +426,122 @@ exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
|
|
|
444
426
|
else if (typeof modelSection === 'string') {
|
|
445
427
|
currentDefault = modelSection.trim();
|
|
446
428
|
}
|
|
447
|
-
// Collect unique endpoints from credential pool
|
|
448
|
-
const endpoints = [];
|
|
449
|
-
const seenUrls = new Set();
|
|
450
|
-
for (const [providerKey, entries] of Object.entries(pool)) {
|
|
451
|
-
if (!Array.isArray(entries) || entries.length === 0)
|
|
452
|
-
continue;
|
|
453
|
-
const entry = entries.find(e => e.last_status !== 'exhausted') || entries[0];
|
|
454
|
-
if (!entry?.base_url || !entry?.access_token)
|
|
455
|
-
continue;
|
|
456
|
-
const baseUrl = entry.base_url.replace(/\/+$/, '');
|
|
457
|
-
if (seenUrls.has(baseUrl))
|
|
458
|
-
continue;
|
|
459
|
-
seenUrls.add(baseUrl);
|
|
460
|
-
endpoints.push({
|
|
461
|
-
key: providerKey,
|
|
462
|
-
label: providerKey.replace(/^custom:/, '') || entry.label || baseUrl,
|
|
463
|
-
base_url: baseUrl,
|
|
464
|
-
token: entry.access_token,
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
// Resolve models: hardcoded catalog first, live probe as fallback
|
|
468
429
|
const groups = [];
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
430
|
+
const seenProviders = new Set();
|
|
431
|
+
// 1. Read .env to discover authorized providers via PROVIDER_ENV_MAP
|
|
432
|
+
let envContent = '';
|
|
433
|
+
try {
|
|
434
|
+
envContent = await (0, promises_1.readFile)((0, hermes_profile_1.getActiveEnvPath)(), 'utf-8');
|
|
435
|
+
}
|
|
436
|
+
catch { }
|
|
437
|
+
const envHasValue = (key) => {
|
|
438
|
+
if (!key)
|
|
439
|
+
return false;
|
|
440
|
+
const match = envContent.match(new RegExp(`^${key}\\s*=\\s*(.+)`, 'm'));
|
|
441
|
+
return !!match && match[1].trim() !== '' && !match[1].trim().startsWith('#');
|
|
442
|
+
};
|
|
443
|
+
const envGetValue = (key) => {
|
|
444
|
+
if (!key)
|
|
445
|
+
return '';
|
|
446
|
+
const match = envContent.match(new RegExp(`^${key}\\s*=\\s*(.+)`, 'm'));
|
|
447
|
+
return match?.[1]?.trim() || '';
|
|
448
|
+
};
|
|
449
|
+
const addGroup = (provider, label, base_url, models, api_key) => {
|
|
450
|
+
if (seenProviders.has(provider))
|
|
451
|
+
return;
|
|
452
|
+
seenProviders.add(provider);
|
|
453
|
+
groups.push({ provider, label, base_url, models: [...models], api_key });
|
|
454
|
+
};
|
|
455
|
+
// Import PROVIDER_PRESETS for label + base_url lookup
|
|
456
|
+
const { PROVIDER_PRESETS } = await Promise.resolve().then(() => __importStar(require('../../shared/providers')));
|
|
457
|
+
// 1. Authorized providers from .env + OAuth-based providers (no api_key_env)
|
|
458
|
+
// Check OAuth auth (e.g. openai-codex) via auth.json
|
|
459
|
+
const isOAuthAuthorized = (providerKey) => {
|
|
460
|
+
try {
|
|
461
|
+
const authPath = (0, hermes_profile_1.getActiveAuthPath)();
|
|
462
|
+
if (!(0, fs_1.existsSync)(authPath))
|
|
463
|
+
return false;
|
|
464
|
+
const auth = JSON.parse((0, fs_1.readFileSync)(authPath, 'utf-8'));
|
|
465
|
+
return !!auth.providers?.[providerKey]?.tokens?.access_token;
|
|
492
466
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const dedupedGroups = [];
|
|
496
|
-
const seenProviders = new Map();
|
|
497
|
-
for (const g of groups) {
|
|
498
|
-
g.models = Array.from(new Set(g.models));
|
|
499
|
-
const existingIdx = seenProviders.get(g.provider);
|
|
500
|
-
if (existingIdx !== undefined) {
|
|
501
|
-
// Merge models into existing group
|
|
502
|
-
const existing = dedupedGroups[existingIdx];
|
|
503
|
-
const existingSet = new Set(existing.models);
|
|
504
|
-
for (const m of g.models) {
|
|
505
|
-
if (!existingSet.has(m))
|
|
506
|
-
existing.models.push(m);
|
|
507
|
-
}
|
|
467
|
+
catch {
|
|
468
|
+
return false;
|
|
508
469
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
470
|
+
};
|
|
471
|
+
for (const [providerKey, envMapping] of Object.entries(PROVIDER_ENV_MAP)) {
|
|
472
|
+
// Skip providers that require API key but don't have one configured
|
|
473
|
+
if (envMapping.api_key_env && !envHasValue(envMapping.api_key_env))
|
|
474
|
+
continue;
|
|
475
|
+
// Skip OAuth providers that haven't been authenticated
|
|
476
|
+
if (!envMapping.api_key_env && !isOAuthAuthorized(providerKey))
|
|
477
|
+
continue;
|
|
478
|
+
const preset = PROVIDER_PRESETS.find(p => p.value === providerKey);
|
|
479
|
+
const label = preset?.label || providerKey.replace(/^custom:/, '');
|
|
480
|
+
const baseUrl = preset?.base_url || '';
|
|
481
|
+
const catalogModels = PROVIDER_MODEL_CATALOG[providerKey];
|
|
482
|
+
if (catalogModels && catalogModels.length > 0) {
|
|
483
|
+
const apiKey = envMapping.api_key_env ? envGetValue(envMapping.api_key_env) : '';
|
|
484
|
+
addGroup(providerKey, label, baseUrl, catalogModels, apiKey);
|
|
512
485
|
}
|
|
513
486
|
}
|
|
514
|
-
//
|
|
487
|
+
// 2. Custom providers from config.yaml — dynamically fetch models
|
|
515
488
|
const customProviders = Array.isArray(config.custom_providers)
|
|
516
489
|
? config.custom_providers
|
|
517
490
|
: [];
|
|
518
|
-
|
|
519
|
-
if (!cp.base_url
|
|
520
|
-
|
|
491
|
+
const customFetches = await Promise.allSettled(customProviders.map(async (cp) => {
|
|
492
|
+
if (!cp.base_url)
|
|
493
|
+
return null;
|
|
494
|
+
const providerKey = `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`;
|
|
521
495
|
const baseUrl = cp.base_url.replace(/\/+$/, '');
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
496
|
+
let models = [cp.model]; // always include the statically configured model
|
|
497
|
+
if (cp.api_key) {
|
|
498
|
+
try {
|
|
499
|
+
const fetched = await fetchProviderModels(baseUrl, cp.api_key);
|
|
500
|
+
if (fetched.length > 0)
|
|
501
|
+
models = fetched;
|
|
527
502
|
}
|
|
503
|
+
catch { }
|
|
528
504
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (currentDefault) {
|
|
540
|
-
const currentProvider = typeof config.model === 'object' ? String(config.model.provider || '').trim() : '';
|
|
541
|
-
if (currentProvider) {
|
|
542
|
-
const targetGroup = dedupedGroups.find(g => g.provider === currentProvider);
|
|
543
|
-
if (targetGroup && !targetGroup.models.includes(currentDefault)) {
|
|
544
|
-
targetGroup.models.unshift(currentDefault);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
// No provider specified — add to the first group that matches via base_url
|
|
549
|
-
// or just prepend to all groups
|
|
550
|
-
let found = false;
|
|
551
|
-
for (const g of dedupedGroups) {
|
|
552
|
-
if (!found && !g.models.includes(currentDefault)) {
|
|
553
|
-
g.models.unshift(currentDefault);
|
|
554
|
-
found = true;
|
|
505
|
+
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '' };
|
|
506
|
+
}));
|
|
507
|
+
for (const result of customFetches) {
|
|
508
|
+
if (result.status === 'fulfilled' && result.value) {
|
|
509
|
+
const { providerKey, label, base_url, models, api_key: cpApiKey } = result.value;
|
|
510
|
+
const existing = groups.find(g => g.base_url.replace(/\/+$/, '') === base_url);
|
|
511
|
+
if (existing) {
|
|
512
|
+
for (const m of models) {
|
|
513
|
+
if (!existing.models.includes(m))
|
|
514
|
+
existing.models.push(m);
|
|
555
515
|
}
|
|
556
516
|
}
|
|
517
|
+
else {
|
|
518
|
+
addGroup(providerKey, label, base_url, models, cpApiKey);
|
|
519
|
+
}
|
|
557
520
|
}
|
|
558
521
|
}
|
|
522
|
+
// Deduplicate models within each group
|
|
523
|
+
for (const g of groups) {
|
|
524
|
+
g.models = Array.from(new Set(g.models));
|
|
525
|
+
}
|
|
559
526
|
// Fallback: if still no providers, fall back to config.yaml parsing
|
|
560
|
-
if (
|
|
527
|
+
if (groups.length === 0) {
|
|
561
528
|
const fallback = buildModelGroups(config);
|
|
562
|
-
|
|
529
|
+
const allProviders = PROVIDER_PRESETS.map(p => ({
|
|
530
|
+
provider: p.value,
|
|
531
|
+
label: p.label,
|
|
532
|
+
base_url: p.base_url,
|
|
533
|
+
models: p.models,
|
|
534
|
+
}));
|
|
535
|
+
ctx.body = { ...fallback, allProviders };
|
|
563
536
|
return;
|
|
564
537
|
}
|
|
565
|
-
|
|
538
|
+
const allProviders = PROVIDER_PRESETS.map(p => ({
|
|
539
|
+
provider: p.value,
|
|
540
|
+
label: p.label,
|
|
541
|
+
base_url: p.base_url,
|
|
542
|
+
models: p.models,
|
|
543
|
+
}));
|
|
544
|
+
ctx.body = { default: currentDefault, default_provider: currentDefaultProvider, groups, allProviders };
|
|
566
545
|
}
|
|
567
546
|
catch (err) {
|
|
568
547
|
ctx.status = 500;
|
|
@@ -632,21 +611,6 @@ exports.fsRoutes.post('/api/hermes/config/providers', async (ctx) => {
|
|
|
632
611
|
config.custom_providers.push({ name, base_url, api_key, model });
|
|
633
612
|
await writeConfigYaml(config);
|
|
634
613
|
}
|
|
635
|
-
// Write to auth.json credential_pool (all providers)
|
|
636
|
-
const auth = await loadAuthJson() || { credential_pool: {} };
|
|
637
|
-
if (!auth.credential_pool)
|
|
638
|
-
auth.credential_pool = {};
|
|
639
|
-
if (!auth.credential_pool[poolKey]) {
|
|
640
|
-
auth.credential_pool[poolKey] = [];
|
|
641
|
-
}
|
|
642
|
-
auth.credential_pool[poolKey].push({
|
|
643
|
-
id: `${poolKey}-${Date.now()}`,
|
|
644
|
-
label: name,
|
|
645
|
-
base_url,
|
|
646
|
-
access_token: api_key,
|
|
647
|
-
last_status: null,
|
|
648
|
-
});
|
|
649
|
-
await saveAuthJson(auth);
|
|
650
614
|
// Write API key to .env (built-in providers only)
|
|
651
615
|
const envMapping = PROVIDER_ENV_MAP[poolKey] || PROVIDER_ENV_MAP[providerKey || ''];
|
|
652
616
|
if (envMapping) {
|
|
@@ -677,63 +641,131 @@ exports.fsRoutes.post('/api/hermes/config/providers', async (ctx) => {
|
|
|
677
641
|
ctx.body = { error: err.message };
|
|
678
642
|
}
|
|
679
643
|
});
|
|
680
|
-
//
|
|
681
|
-
exports.fsRoutes.
|
|
644
|
+
// PUT /api/config/providers/:poolKey — update existing provider
|
|
645
|
+
exports.fsRoutes.put('/api/hermes/config/providers/:poolKey', async (ctx) => {
|
|
682
646
|
const poolKey = decodeURIComponent(ctx.params.poolKey);
|
|
647
|
+
const { name, base_url, api_key, model } = ctx.request.body;
|
|
683
648
|
try {
|
|
684
|
-
const
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (!(poolKey in auth.credential_pool)) {
|
|
699
|
-
const normalized = poolKey.toLowerCase();
|
|
700
|
-
const match = Object.keys(auth.credential_pool).find(k => k.toLowerCase() === normalized);
|
|
701
|
-
if (!match) {
|
|
649
|
+
const isCustom = poolKey.startsWith('custom:');
|
|
650
|
+
if (isCustom) {
|
|
651
|
+
// Update custom provider in config.yaml
|
|
652
|
+
const config = await readConfigYaml();
|
|
653
|
+
if (!Array.isArray(config.custom_providers)) {
|
|
654
|
+
ctx.status = 404;
|
|
655
|
+
ctx.body = { error: `Custom provider "${poolKey}" not found` };
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const entry = config.custom_providers.find((e) => {
|
|
659
|
+
const key = `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}`;
|
|
660
|
+
return key === poolKey;
|
|
661
|
+
});
|
|
662
|
+
if (!entry) {
|
|
702
663
|
ctx.status = 404;
|
|
703
|
-
ctx.body = { error: `
|
|
664
|
+
ctx.body = { error: `Custom provider "${poolKey}" not found` };
|
|
704
665
|
return;
|
|
705
666
|
}
|
|
706
|
-
|
|
667
|
+
if (name !== undefined)
|
|
668
|
+
entry.name = name;
|
|
669
|
+
if (base_url !== undefined)
|
|
670
|
+
entry.base_url = base_url;
|
|
671
|
+
if (api_key !== undefined)
|
|
672
|
+
entry.api_key = api_key;
|
|
673
|
+
if (model !== undefined)
|
|
674
|
+
entry.model = model;
|
|
675
|
+
await writeConfigYaml(config);
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// Built-in provider: update API key in .env
|
|
679
|
+
const envMapping = PROVIDER_ENV_MAP[poolKey];
|
|
680
|
+
if (!envMapping?.api_key_env) {
|
|
681
|
+
// OAuth provider — cannot update key
|
|
682
|
+
ctx.status = 400;
|
|
683
|
+
ctx.body = { error: `Cannot update credentials for "${poolKey}"` };
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (api_key !== undefined) {
|
|
687
|
+
await saveEnvValue(envMapping.api_key_env, api_key);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Restart gateway to pick up changes
|
|
691
|
+
try {
|
|
692
|
+
await hermesCli.restartGateway();
|
|
693
|
+
}
|
|
694
|
+
catch (e) {
|
|
695
|
+
console.error('[Provider] Gateway restart failed:', e.message);
|
|
707
696
|
}
|
|
708
|
-
|
|
697
|
+
ctx.body = { success: true };
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
ctx.status = 500;
|
|
701
|
+
ctx.body = { error: err.message };
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
// DELETE /api/config/providers/:poolKey
|
|
705
|
+
exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) => {
|
|
706
|
+
const poolKey = decodeURIComponent(ctx.params.poolKey);
|
|
707
|
+
try {
|
|
709
708
|
const config = await readConfigYaml();
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
709
|
+
const isCustom = poolKey.startsWith('custom:');
|
|
710
|
+
if (isCustom) {
|
|
711
|
+
// Delete from config.yaml custom_providers
|
|
712
|
+
const idx = Array.isArray(config.custom_providers)
|
|
713
|
+
? config.custom_providers.findIndex((e) => {
|
|
714
|
+
const key = `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}`;
|
|
715
|
+
return key === poolKey;
|
|
716
|
+
})
|
|
717
|
+
: -1;
|
|
718
|
+
if (idx === -1) {
|
|
719
|
+
ctx.status = 404;
|
|
720
|
+
ctx.body = { error: `Custom provider "${poolKey}" not found` };
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
config.custom_providers.splice(idx, 1);
|
|
720
724
|
await writeConfigYaml(config);
|
|
721
725
|
}
|
|
722
|
-
|
|
726
|
+
else {
|
|
727
|
+
// Built-in provider: remove API key from .env
|
|
728
|
+
const envMapping = PROVIDER_ENV_MAP[poolKey];
|
|
729
|
+
if (envMapping?.api_key_env) {
|
|
730
|
+
await saveEnvValue(envMapping.api_key_env, '');
|
|
731
|
+
}
|
|
732
|
+
else if (!envMapping?.api_key_env) {
|
|
733
|
+
// OAuth provider (e.g. openai-codex): clear tokens from auth.json
|
|
734
|
+
try {
|
|
735
|
+
const authPath = (0, hermes_profile_1.getActiveAuthPath)();
|
|
736
|
+
if ((0, fs_1.existsSync)(authPath)) {
|
|
737
|
+
const auth = JSON.parse((0, fs_1.readFileSync)(authPath, 'utf-8'));
|
|
738
|
+
if (auth.providers?.[poolKey]) {
|
|
739
|
+
delete auth.providers[poolKey];
|
|
740
|
+
}
|
|
741
|
+
if (auth.credential_pool?.[poolKey]) {
|
|
742
|
+
delete auth.credential_pool[poolKey];
|
|
743
|
+
}
|
|
744
|
+
const { writeFile: wfs } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
745
|
+
await wfs(authPath, JSON.stringify(auth, null, 2) + '\n', 'utf-8');
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
console.error(`[Provider] Failed to clear OAuth tokens for ${poolKey}:`, err.message);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// If was the current provider, switch to first remaining
|
|
754
|
+
const currentProvider = config.model?.provider;
|
|
755
|
+
const isCurrent = currentProvider === poolKey;
|
|
723
756
|
if (isCurrent) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
config2.model = {};
|
|
757
|
+
// Find fallback from .env authorized providers or remaining custom_providers
|
|
758
|
+
const freshConfig = await readConfigYaml();
|
|
759
|
+
const remaining = Array.isArray(freshConfig.custom_providers) ? freshConfig.custom_providers : [];
|
|
760
|
+
const fallbackCp = remaining[0];
|
|
761
|
+
if (fallbackCp) {
|
|
762
|
+
const fallbackKey = `custom:${fallbackCp.name.trim().toLowerCase().replace(/ /g, '-')}`;
|
|
763
|
+
if (typeof freshConfig.model !== 'object' || freshConfig.model === null) {
|
|
764
|
+
freshConfig.model = {};
|
|
733
765
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
await writeConfigYaml(
|
|
766
|
+
freshConfig.model.default = fallbackCp.model;
|
|
767
|
+
freshConfig.model.provider = fallbackKey;
|
|
768
|
+
await writeConfigYaml(freshConfig);
|
|
737
769
|
}
|
|
738
770
|
}
|
|
739
771
|
ctx.body = { success: true };
|