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.
Files changed (74) hide show
  1. package/bin/hermes-web-ui.mjs +15 -1
  2. package/dist/client/assets/{Add-CKf6ViXR.js → Add-Cc7cgBoB.js} +1 -1
  3. package/dist/client/assets/{Button-CrrCCorI.js → Button-EoeZgIFH.js} +1 -1
  4. package/dist/client/assets/{ChannelsView-D4I7hhZO.js → ChannelsView-Bfbq3w7n.js} +1 -1
  5. package/dist/client/assets/{ChatView-DxyBUK57.js → ChatView-CDdyTo72.js} +1 -1
  6. package/dist/client/assets/{Close-C9xwy-pW.js → Close-CKHcXisf.js} +1 -1
  7. package/dist/client/assets/{FormItem-BgJdrTW0.js → FormItem-CvZvjrtr.js} +1 -1
  8. package/dist/client/assets/{GatewaysView-Cib2JydO.js → GatewaysView-Dp4-TFPE.js} +1 -1
  9. package/dist/client/assets/{Input-ChENEW-Z.js → Input-Bk7XdoG-.js} +1 -1
  10. package/dist/client/assets/{InputNumber-Xd6HWSdp.js → InputNumber-Dn0EHi-K.js} +1 -1
  11. package/dist/client/assets/{JobsView-SnToCbDd.js → JobsView-D4JN73Zz.js} +2 -2
  12. package/dist/client/assets/{LoginView-BZdmMnsf.js → LoginView--hy5CI5O.js} +1 -1
  13. package/dist/client/assets/{LogsView-DblvOJIg.js → LogsView-Dz2ZeYad.js} +1 -1
  14. package/dist/client/assets/{MarkdownRenderer-DJLVk7ei.js → MarkdownRenderer-BbedVxvo.js} +1 -1
  15. package/dist/client/assets/{MemoryView-exXvRwCc.js → MemoryView-D2EHM35l.js} +1 -1
  16. package/dist/client/assets/{Modal-B2zvXTrk.js → Modal-C5-Iw50K.js} +1 -1
  17. package/dist/client/assets/ModelsView-CtrRf4vK.js +1 -0
  18. package/dist/client/assets/{ModelsView-jbgZP3YF.css → ModelsView-VM-oMq5M.css} +1 -1
  19. package/dist/client/assets/{Popconfirm-BoZc0kKk.js → Popconfirm-C-M2anVL.js} +1 -1
  20. package/dist/client/assets/{Popover-Cu52vG3D.js → Popover-mIRPCy7U.js} +1 -1
  21. package/dist/client/assets/{ProfilesView-D0FY7Jwe.js → ProfilesView-Dy9PivgB.js} +1 -1
  22. package/dist/client/assets/{Select-BHc7u-Yf.js → Select-uZBC8HC2.js} +2 -2
  23. package/dist/client/assets/{SettingRow-i-UXlco7.js → SettingRow-D9R65bDj.js} +1 -1
  24. package/dist/client/assets/{SettingsView-Dhr2wzAB.css → SettingsView-C3sd8K0e.css} +1 -1
  25. package/dist/client/assets/SettingsView-DWEEXqSY.js +352 -0
  26. package/dist/client/assets/{SkillsView-B5QBaAzi.js → SkillsView-CdZSRy9_.js} +1 -1
  27. package/dist/client/assets/{Spin-DsNCRPk9.js → Spin-ChbFBUOD.js} +1 -1
  28. package/dist/client/assets/{Suffix-3xK0KZGt.js → Suffix-DgzfIwzx.js} +1 -1
  29. package/dist/client/assets/{Switch-Bf63XXgA.js → Switch--HhY1uSh.js} +1 -1
  30. package/dist/client/assets/{Tag-Dmbj68Ki.js → Tag-B2zrHMmZ.js} +1 -1
  31. package/dist/client/assets/{TerminalView-DrJHZ0qI.js → TerminalView-BwfnH803.js} +1 -1
  32. package/dist/client/assets/{Tooltip-CRbZNhG0.js → Tooltip-9tdvSKGi.js} +1 -1
  33. package/dist/client/assets/{UsageView-DQ43JasX.js → UsageView-zL3a7F86.js} +1 -1
  34. package/dist/client/assets/{Warning-kBbRMAif.js → Warning-CXXqHzLa.js} +1 -1
  35. package/dist/client/assets/{_plugin-vue_export-helper-CnosYBkx.js → _plugin-vue_export-helper-Cnn0Z73x.js} +1 -1
  36. package/dist/client/assets/app-BMobzABI.js +1 -0
  37. package/dist/client/assets/app-Bqu9Uz-1.js +1 -0
  38. package/dist/client/assets/{browser-Djp4tkp3.js → browser-CQRjhbaB.js} +1 -1
  39. package/dist/client/assets/chat-BIdq6ZXF.js +6 -0
  40. package/dist/client/assets/composables-ClIU-Ad1.js +1 -0
  41. package/dist/client/assets/{fade-in.cssr-CIVyTG6A.js → fade-in.cssr-lwO9nLky.js} +1 -1
  42. package/dist/client/assets/index-Tg6M43Om.js +284 -0
  43. package/dist/client/assets/{jobs-CcVaCGMJ.js → jobs-Z2HS0j2d.js} +1 -1
  44. package/dist/client/assets/{light-CSp9-LhE.js → light-CjCy-Dkn.js} +1 -1
  45. package/dist/client/assets/{light-BJ96fCLC.js → light-DZ0Ns16h.js} +1 -1
  46. package/dist/client/assets/{light-BF6E9z0k.js → light-DgIst23O.js} +1 -1
  47. package/dist/client/assets/{light-KCEDTUGE.js → light-Dx6qj2pM.js} +1 -1
  48. package/dist/client/assets/{light-BPqyaxve.js → light-DzpNsLai.js} +1 -1
  49. package/dist/client/assets/{light-D9G2GshF.js → light-oE8MEiWL.js} +1 -1
  50. package/dist/client/assets/models-DLQiHB7r.js +1 -0
  51. package/dist/client/assets/{pinia-iHE5_ZXa.js → pinia-Dp_b1vdW.js} +1 -1
  52. package/dist/client/assets/{profiles-CJCR84uQ.js → profiles-CNTHYFZE.js} +1 -1
  53. package/dist/client/assets/{router-C-NNJUuf.js → router-Dj-Nmg7q.js} +2 -2
  54. package/dist/client/assets/{sessions-C4bnNvzS.js → sessions-C0kvgvBm.js} +1 -1
  55. package/dist/client/assets/{skills-B4slZfeZ.js → skills-G7EoEvdS.js} +1 -1
  56. package/dist/client/assets/{use-message-BIpqgDet.js → use-message-BgToAqhv.js} +1 -1
  57. package/dist/client/assets/{useTheme-B78N9tyz.js → useTheme-BUShiwRu.js} +1 -1
  58. package/dist/client/index.html +27 -27
  59. package/dist/server/routes/hermes/filesystem.js +231 -199
  60. package/dist/server/routes/hermes/logs.js +50 -11
  61. package/dist/server/routes/hermes/proxy-handler.js +16 -6
  62. package/dist/server/services/hermes/gateway-manager.d.ts +2 -0
  63. package/dist/server/services/hermes/gateway-manager.js +15 -0
  64. package/dist/server/services/hermes/hermes-profile.d.ts +6 -0
  65. package/dist/server/services/hermes/hermes-profile.js +12 -0
  66. package/dist/server/shared/providers.js +1 -13
  67. package/package.json +1 -1
  68. package/dist/client/assets/ModelsView-DGs47Cj4.js +0 -1
  69. package/dist/client/assets/SettingsView-BW6ctYG5.js +0 -352
  70. package/dist/client/assets/app-BT9yU6N6.js +0 -1
  71. package/dist/client/assets/app-CjNVVG5x.js +0 -1
  72. package/dist/client/assets/chat-DlC9S9DK.js +0 -6
  73. package/dist/client/assets/composables-DCA4Yga5.js +0 -1
  74. package/dist/client/assets/index-D12ukDT7.js +0 -284
@@ -1 +1 @@
1
- import{o as e}from"./router-C-NNJUuf.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
+ 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-C-NNJUuf.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
+ 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-C-NNJUuf.js";import{$ as t,Y as n}from"./browser-Djp4tkp3.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
+ 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-C-NNJUuf.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};
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};
@@ -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-D12ukDT7.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/router-C-NNJUuf.js">
11
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-CnosYBkx.js">
12
- <link rel="modulepreload" crossorigin href="/assets/browser-Djp4tkp3.js">
13
- <link rel="modulepreload" crossorigin href="/assets/fade-in.cssr-CIVyTG6A.js">
14
- <link rel="modulepreload" crossorigin href="/assets/Suffix-3xK0KZGt.js">
15
- <link rel="modulepreload" crossorigin href="/assets/Close-C9xwy-pW.js">
16
- <link rel="modulepreload" crossorigin href="/assets/Popover-Cu52vG3D.js">
17
- <link rel="modulepreload" crossorigin href="/assets/Button-CrrCCorI.js">
18
- <link rel="modulepreload" crossorigin href="/assets/Tag-Dmbj68Ki.js">
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-BHc7u-Yf.js">
21
- <link rel="modulepreload" crossorigin href="/assets/Warning-kBbRMAif.js">
22
- <link rel="modulepreload" crossorigin href="/assets/Modal-B2zvXTrk.js">
23
- <link rel="modulepreload" crossorigin href="/assets/Input-ChENEW-Z.js">
24
- <link rel="modulepreload" crossorigin href="/assets/light-KCEDTUGE.js">
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-iHE5_ZXa.js">
27
- <link rel="modulepreload" crossorigin href="/assets/profiles-CJCR84uQ.js">
28
- <link rel="modulepreload" crossorigin href="/assets/sessions-C4bnNvzS.js">
29
- <link rel="modulepreload" crossorigin href="/assets/app-BT9yU6N6.js">
30
- <link rel="modulepreload" crossorigin href="/assets/chat-DlC9S9DK.js">
31
- <link rel="modulepreload" crossorigin href="/assets/light-BJ96fCLC.js">
32
- <link rel="modulepreload" crossorigin href="/assets/use-message-BIpqgDet.js">
33
- <link rel="modulepreload" crossorigin href="/assets/light-BPqyaxve.js">
34
- <link rel="modulepreload" crossorigin href="/assets/light-D9G2GshF.js">
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-CSp9-LhE.js">
37
- <link rel="modulepreload" crossorigin href="/assets/light-BF6E9z0k.js">
38
- <link rel="modulepreload" crossorigin href="/assets/useTheme-B78N9tyz.js">
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: 'OPENROUTER_BASE_URL' },
50
- zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
51
- 'kimi-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
52
- 'kimi-coding-cn': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
53
- moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: 'MOONSHOT_BASE_URL' },
54
- minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
55
- 'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
56
- deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
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: 'XAI_BASE_URL' },
60
- xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
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: 'KILOCODE_BASE_URL' },
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: 'OPENCODE_ZEN_BASE_URL' },
65
- 'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
66
- huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
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: 'HERMES_CODEX_BASE_URL' },
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
- const authPath = () => (0, hermes_profile_1.getActiveAuthPath)();
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.error(`[available-models] ${baseUrl} returned ${res.status}`);
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.error(`[available-models] ${baseUrl} returned unexpected format`);
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 — fetch models from all credential pool endpoints
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 liveEndpoints = [];
470
- for (const ep of endpoints) {
471
- const catalogModels = PROVIDER_MODEL_CATALOG[ep.key];
472
- if (catalogModels && catalogModels.length > 0) {
473
- groups.push({ provider: ep.key, label: ep.label, base_url: ep.base_url, models: catalogModels });
474
- }
475
- else {
476
- liveEndpoints.push(ep);
477
- }
478
- }
479
- if (liveEndpoints.length > 0) {
480
- const results = await Promise.allSettled(liveEndpoints.map(async (ep) => {
481
- const models = await fetchProviderModels(ep.base_url, ep.token);
482
- return { ...ep, models };
483
- }));
484
- for (const result of results) {
485
- if (result.status === 'fulfilled' && result.value.models.length > 0) {
486
- const { key, label, base_url, models } = result.value;
487
- groups.push({ provider: key, label, base_url, models: Array.from(new Set(models)) });
488
- }
489
- else if (result.status === 'rejected') {
490
- console.error(`[available-models] Failed: ${result.reason?.message || result.reason}`);
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
- // Deduplicate models within each group and merge groups with the same provider key
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
- else {
510
- seenProviders.set(g.provider, dedupedGroups.length);
511
- dedupedGroups.push(g);
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
- // Merge custom_providers from config.yaml (ensures manually-input model names appear)
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
- for (const cp of customProviders) {
519
- if (!cp.base_url || !cp.model)
520
- continue;
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
- // Check if we already have a group for this base_url
523
- const existing = dedupedGroups.find(g => g.base_url.replace(/\/+$/, '') === baseUrl);
524
- if (existing) {
525
- if (!existing.models.includes(cp.model)) {
526
- existing.models.push(cp.model);
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
- else {
530
- dedupedGroups.push({
531
- provider: `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`,
532
- label: cp.name,
533
- base_url: baseUrl,
534
- models: [cp.model],
535
- });
536
- }
537
- }
538
- // Ensure config's current default model appears in the model list
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 (dedupedGroups.length === 0) {
527
+ if (groups.length === 0) {
561
528
  const fallback = buildModelGroups(config);
562
- ctx.body = fallback;
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
- ctx.body = { default: currentDefault, default_provider: currentDefaultProvider, groups: dedupedGroups };
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
- // DELETE /api/config/providers/:poolKey
681
- exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) => {
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 auth = await loadAuthJson();
685
- if (!auth?.credential_pool) {
686
- ctx.status = 404;
687
- ctx.body = { error: 'No credential pool found' };
688
- return;
689
- }
690
- const keys = Object.keys(auth.credential_pool);
691
- if (keys.length <= 1) {
692
- ctx.status = 400;
693
- ctx.body = { error: 'Cannot delete the last provider' };
694
- return;
695
- }
696
- // Case-insensitive key lookup: normalize poolKey to match credential_pool
697
- let resolvedKey = poolKey;
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: `Provider "${poolKey}" not found` };
664
+ ctx.body = { error: `Custom provider "${poolKey}" not found` };
704
665
  return;
705
666
  }
706
- resolvedKey = match;
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
- // Check if this is the current active provider
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 currentProvider = config.model?.provider;
711
- const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey;
712
- // Save base_url before deleting
713
- const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url;
714
- // 1. Delete from auth.json
715
- delete auth.credential_pool[resolvedKey];
716
- await saveAuthJson(auth);
717
- // 2. Remove matching entry from config.yaml custom_providers
718
- if (deletedBaseUrl && Array.isArray(config.custom_providers)) {
719
- config.custom_providers = config.custom_providers.filter((entry) => entry.base_url !== deletedBaseUrl);
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
- // 3. If was the current provider, switch to first remaining
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
- const remainingKeys = Object.keys(auth.credential_pool);
725
- if (remainingKeys.length > 0) {
726
- const fallback = remainingKeys[0];
727
- const fallbackEntry = auth.credential_pool[fallback]?.[0];
728
- const catalogModels = PROVIDER_MODEL_CATALOG[fallback] || [];
729
- const fallbackModel = catalogModels[0] || fallbackEntry?.label || fallback;
730
- const config2 = await readConfigYaml();
731
- if (typeof config2.model !== 'object' || config2.model === null) {
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
- config2.model.default = fallbackModel;
735
- config2.model.provider = fallback;
736
- await writeConfigYaml(config2);
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 };