hermes-web-ui 0.3.1 → 0.3.3
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/dist/client/assets/{Add-cXqy3eJz.js → Add-CIZmky1Q.js} +1 -1
- package/dist/client/assets/{Button-BeWnBoDR.js → Button-Cpfqx5bj.js} +1 -1
- package/dist/client/assets/{ChannelsView-Do43S3tP.css → ChannelsView-CSo2o-P4.css} +1 -1
- package/dist/client/assets/ChannelsView-CqgEMY6f.js +1 -0
- package/dist/client/assets/{ChatView-CqA3Wo54.js → ChatView-o_KHRYai.js} +1 -1
- package/dist/client/assets/{Close-ySsuLJTC.js → Close-Dx71Ggz9.js} +1 -1
- package/dist/client/assets/{FormItem-EcbFnIOF.js → FormItem-D0pABOgK.js} +1 -1
- package/dist/client/assets/{Input-DP5LDvGa.js → Input-DmGT4Mu3.js} +1 -1
- package/dist/client/assets/{InputNumber-CU7XKbbt.js → InputNumber-CQVOZP6a.js} +1 -1
- package/dist/client/assets/JobsView-BwxWBXo3.js +2 -0
- package/dist/client/assets/{JobsView-CvuV9mZY.css → JobsView-CVx2Yv-y.css} +1 -1
- package/dist/client/assets/{LoginView-CDJXoOEZ.js → LoginView-C4Id2mSY.js} +1 -1
- package/dist/client/assets/{LogsView-DS_E9xkh.js → LogsView-H_gfkSSN.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-BntkPd0f.js → MarkdownRenderer-DJK1b6T2.js} +1 -1
- package/dist/client/assets/{MemoryView-CCCXW1tv.js → MemoryView-DO4Y2le5.js} +1 -1
- package/dist/client/assets/{Modal-D_vF9k8c.js → Modal-fhZL63EZ.js} +1 -1
- package/dist/client/assets/ModelsView-9CSt8tCa.js +1 -0
- package/dist/client/assets/{ModelsView-BhNEZwC0.css → ModelsView-D1p_2trx.css} +1 -1
- package/dist/client/assets/{Popconfirm-IKYJxM7n.js → Popconfirm-CLvbZW3I.js} +1 -1
- package/dist/client/assets/{Popover-BLBLYanQ.js → Popover-Dwi5NArH.js} +1 -1
- package/dist/client/assets/{ProfilesView-qt3NXito.js → ProfilesView-R_u1mtsl.js} +1 -1
- package/dist/client/assets/{Scrollbar-DZGif2x4.js → Scrollbar-zYdleApO.js} +1 -1
- package/dist/client/assets/{Select-3KS3TLPL.js → Select-0jFWg4Lf.js} +1 -1
- package/dist/client/assets/{SettingRow-D26AUezg.js → SettingRow-LTP_cMLl.js} +1 -1
- package/dist/client/assets/{SettingsView-30x1265A.js → SettingsView-5o2GfsQf.js} +2 -2
- package/dist/client/assets/{SettingsView-C78xbLXK.css → SettingsView-BIEQOPzq.css} +1 -1
- package/dist/client/assets/{SkillsView-qq8Oz7fo.js → SkillsView-Cez6jHIn.js} +1 -1
- package/dist/client/assets/{Spin-C9izy3tZ.js → Spin-CfKqXT5a.js} +1 -1
- package/dist/client/assets/{Suffix-CIX5CF-j.js → Suffix-jKQO068E.js} +1 -1
- package/dist/client/assets/{Switch-D9x35tOv.js → Switch-tk7Pkw6L.js} +1 -1
- package/dist/client/assets/{TerminalView-zaRNn2aX.js → TerminalView-C-oFmyFh.js} +1 -1
- package/dist/client/assets/{Tooltip-A5--U35v.js → Tooltip-Dc4tc92D.js} +1 -1
- package/dist/client/assets/{UsageView-DQ3t8CHE.js → UsageView-SSxSWAFO.js} +1 -1
- package/dist/client/assets/{Warning-BZfjWCrB.js → Warning-BPlnDrmx.js} +1 -1
- package/dist/client/assets/{_plugin-vue_export-helper-B4hqCVU_.js → _plugin-vue_export-helper-CcX4e_Is.js} +1 -1
- package/dist/client/assets/{app-DG_zFxqi.js → app-CcLe99Xa.js} +1 -1
- package/dist/client/assets/app-ML9hRyDO.js +1 -0
- package/dist/client/assets/{browser-BanYmQkE.js → browser-BfJIjfY3.js} +1 -1
- package/dist/client/assets/{chat-C6pq-bYQ.js → chat-CuVG21Hv.js} +2 -2
- package/dist/client/assets/composables-BTIEu8ZB.js +1 -0
- package/dist/client/assets/{fade-in-scale-up.cssr-B100njI2.js → fade-in-scale-up.cssr-e30I2JjN.js} +1 -1
- package/dist/client/assets/index-XufFb2mL.js +284 -0
- package/dist/client/assets/{jobs-DK_GVjqW.js → jobs-LBrAH2Mz.js} +1 -1
- package/dist/client/assets/{light-B8HxMQKs.js → light-BF--Dz10.js} +1 -1
- package/dist/client/assets/{light-BT2gqEar.js → light-BJ4xZyd0.js} +1 -1
- package/dist/client/assets/{light-CDN7aXQb.js → light-BLbGAFtS.js} +1 -1
- package/dist/client/assets/{light-Dav3NshO.js → light-CCZMDqco.js} +1 -1
- package/dist/client/assets/{light-BfPLur6t.js → light-CDuRPTUd.js} +1 -1
- package/dist/client/assets/{light-CeMZM90j.js → light-DTBcb4qq.js} +1 -1
- package/dist/client/assets/{pinia-DEKB7D30.js → pinia-D2g49IcO.js} +1 -1
- package/dist/client/assets/{profiles-BSAOLUpt.js → profiles-DbCu_blp.js} +1 -1
- package/dist/client/assets/{router-Zpu1Tghg.js → router-0X4x3p8e.js} +2 -2
- package/dist/client/assets/{sessions-1rFjfO8M.js → sessions-SzjWXR9A.js} +1 -1
- package/dist/client/assets/{skills-Bu5JdqX-.js → skills-CsjvGVsY.js} +1 -1
- package/dist/client/assets/use-compitable-DY4l-k3U.js +1 -0
- package/dist/client/assets/{use-message-CFWjMwGX.js → use-message-4kASja7X.js} +1 -1
- package/dist/client/assets/{useTheme-CHkzsMnk.js → useTheme-Dh7NTo_V.js} +1 -1
- package/dist/client/index.html +27 -27
- package/dist/server/index.js +2 -2
- package/dist/server/routes/hermes/codex-auth.d.ts +2 -0
- package/dist/server/routes/hermes/codex-auth.js +302 -0
- package/dist/server/routes/hermes/config.js +15 -3
- package/dist/server/routes/hermes/filesystem.js +92 -18
- package/dist/server/routes/hermes/index.js +2 -0
- package/dist/server/routes/hermes/logs.js +1 -1
- package/dist/server/routes/hermes/profiles.js +1 -1
- package/dist/server/routes/hermes/sessions.js +1 -1
- package/dist/server/routes/hermes/weixin.js +2 -2
- package/dist/server/routes/webhook.js +1 -1
- package/dist/server/services/hermes/hermes-cli.d.ts +125 -0
- package/dist/server/services/hermes/hermes-cli.js +488 -0
- package/dist/server/services/hermes/hermes-profile.d.ts +22 -0
- package/dist/server/services/hermes/hermes-profile.js +60 -0
- package/dist/server/services/hermes/hermes.d.ts +40 -0
- package/dist/server/services/hermes/hermes.js +118 -0
- package/dist/server/shared/providers.js +64 -20
- package/package.json +4 -1
- package/dist/client/assets/ChannelsView-tUAkDoSF.js +0 -1
- package/dist/client/assets/JobsView-g3zcs8_m.js +0 -2
- package/dist/client/assets/ModelsView-C8rPRq6p.js +0 -1
- package/dist/client/assets/app-DVQEsGHs.js +0 -1
- package/dist/client/assets/composables-xsQwqT-T.js +0 -1
- package/dist/client/assets/index-DtDTIaVj.js +0 -284
- package/dist/client/assets/use-compitable-Dl0AqAGv.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{o as e}from"./router-
|
|
1
|
+
import{o as e}from"./router-0X4x3p8e.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-0X4x3p8e.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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{C as e}from"./router-0X4x3p8e.js";function t(t,n){return e(()=>{for(let e of n)if(t[e]!==void 0)return t[e];return t[n[n.length-1]]})}export{t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{F as e}from"./router-
|
|
1
|
+
import{F as e}from"./router-0X4x3p8e.js";import{$ as t,Y as n}from"./browser-BfJIjfY3.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-0X4x3p8e.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/Scrollbar-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/use-compitable-
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/Popover-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/Close-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/Button-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/Suffix-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-XufFb2mL.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/router-0X4x3p8e.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-CcX4e_Is.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/browser-BfJIjfY3.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/Scrollbar-zYdleApO.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/use-compitable-DY4l-k3U.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/Popover-Dwi5NArH.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/Close-Dx71Ggz9.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/Button-Cpfqx5bj.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/Suffix-jKQO068E.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/fade-in-scale-up.cssr-e30I2JjN.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/light-BF--Dz10.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/assets/create-5zWq3BEB.js">
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/Select-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/Warning-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/Modal-
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/pinia-
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/profiles-
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/Select-0jFWg4Lf.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/Warning-BPlnDrmx.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/Modal-fhZL63EZ.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/pinia-D2g49IcO.js">
|
|
26
|
+
<link rel="modulepreload" crossorigin href="/assets/profiles-DbCu_blp.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/omit-1BRB6K75.js">
|
|
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/light-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/use-message-
|
|
34
|
-
<link rel="modulepreload" crossorigin href="/assets/light-
|
|
28
|
+
<link rel="modulepreload" crossorigin href="/assets/sessions-SzjWXR9A.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/app-CcLe99Xa.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/chat-CuVG21Hv.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="/assets/light-CDuRPTUd.js">
|
|
32
|
+
<link rel="modulepreload" crossorigin href="/assets/light-DTBcb4qq.js">
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/use-message-4kASja7X.js">
|
|
34
|
+
<link rel="modulepreload" crossorigin href="/assets/light-CCZMDqco.js">
|
|
35
35
|
<link rel="modulepreload" crossorigin href="/assets/_common-DgdkN_d5.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-BLbGAFtS.js">
|
|
37
|
+
<link rel="modulepreload" crossorigin href="/assets/light-BJ4xZyd0.js">
|
|
38
|
+
<link rel="modulepreload" crossorigin href="/assets/useTheme-Dh7NTo_V.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="/assets/logo-Cd-t_oGE.js">
|
|
40
40
|
<link rel="stylesheet" crossorigin href="/assets/index-JeutuTe0.css">
|
|
41
41
|
</head>
|
package/dist/server/index.js
CHANGED
|
@@ -49,7 +49,7 @@ const config_1 = require("./config");
|
|
|
49
49
|
const hermes_1 = require("./routes/hermes");
|
|
50
50
|
const upload_1 = require("./routes/upload");
|
|
51
51
|
const webhook_1 = require("./routes/webhook");
|
|
52
|
-
const hermesCli = __importStar(require("./services/hermes-cli"));
|
|
52
|
+
const hermesCli = __importStar(require("./services/hermes/hermes-cli"));
|
|
53
53
|
const auth_1 = require("./services/auth");
|
|
54
54
|
function getLocalVersion() {
|
|
55
55
|
// production: dist/server → ../../package.json
|
|
@@ -241,7 +241,7 @@ function bindShutdown() {
|
|
|
241
241
|
async function ensureApiServerConfig() {
|
|
242
242
|
const { readFileSync, writeFileSync, existsSync, copyFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
243
243
|
const yaml = (await Promise.resolve().then(() => __importStar(require('js-yaml')))).default;
|
|
244
|
-
const { getActiveConfigPath } = await Promise.resolve().then(() => __importStar(require('./services/hermes-profile')));
|
|
244
|
+
const { getActiveConfigPath } = await Promise.resolve().then(() => __importStar(require('./services/hermes/hermes-profile')));
|
|
245
245
|
const configPath = getActiveConfigPath();
|
|
246
246
|
const defaults = {
|
|
247
247
|
enabled: true,
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.codexAuthRoutes = void 0;
|
|
7
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const os_1 = require("os");
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const hermes_profile_1 = require("../../services/hermes/hermes-profile");
|
|
13
|
+
// --- OAuth Constants ---
|
|
14
|
+
const CODEX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
15
|
+
const CODEX_DEVICE_AUTH_URL = 'https://auth.openai.com/api/accounts/deviceauth/usercode';
|
|
16
|
+
const CODEX_DEVICE_TOKEN_URL = 'https://auth.openai.com/api/accounts/deviceauth/token';
|
|
17
|
+
const CODEX_OAUTH_TOKEN_URL = 'https://auth.openai.com/oauth/token';
|
|
18
|
+
const CODEX_DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
|
|
19
|
+
const CODEX_REDIRECT_URI = 'https://auth.openai.com/deviceauth/callback';
|
|
20
|
+
const CODEX_VERIFICATION_URL = 'https://auth.openai.com/codex/device';
|
|
21
|
+
const CODEX_HOME = (0, path_1.join)((0, os_1.homedir)(), '.codex');
|
|
22
|
+
const POLL_MAX_DURATION = 15 * 60 * 1000; // 15 minutes
|
|
23
|
+
const POLL_DEFAULT_INTERVAL = 5000; // 5 seconds
|
|
24
|
+
const sessions = new Map();
|
|
25
|
+
function cleanupExpiredSessions() {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
sessions.forEach((session, id) => {
|
|
28
|
+
if (now - session.createdAt > POLL_MAX_DURATION + 60000) {
|
|
29
|
+
sessions.delete(id);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function loadAuthJson(authPath) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = (0, fs_1.readFileSync)(authPath, 'utf-8');
|
|
36
|
+
return JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { version: 1 };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function saveAuthJson(authPath, data) {
|
|
43
|
+
data.updated_at = new Date().toISOString();
|
|
44
|
+
const dir = authPath.substring(0, authPath.lastIndexOf('/'));
|
|
45
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
46
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
47
|
+
(0, fs_1.writeFileSync)(authPath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
48
|
+
}
|
|
49
|
+
function saveCodexCliTokens(accessToken, refreshToken) {
|
|
50
|
+
const codexHome = process.env.CODEX_HOME || CODEX_HOME;
|
|
51
|
+
const codexAuthPath = (0, path_1.join)(codexHome, 'auth.json');
|
|
52
|
+
const dir = codexAuthPath.substring(0, codexAuthPath.lastIndexOf('/'));
|
|
53
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
54
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
55
|
+
const data = {
|
|
56
|
+
tokens: { access_token: accessToken, refresh_token: refreshToken },
|
|
57
|
+
last_refresh: new Date().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
(0, fs_1.writeFileSync)(codexAuthPath, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
60
|
+
}
|
|
61
|
+
function decodeJwtExp(token) {
|
|
62
|
+
try {
|
|
63
|
+
const parts = token.split('.');
|
|
64
|
+
if (parts.length !== 3)
|
|
65
|
+
return null;
|
|
66
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
67
|
+
const claims = JSON.parse(payload);
|
|
68
|
+
return typeof claims.exp === 'number' ? claims.exp : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// --- Background login worker ---
|
|
75
|
+
async function codexLoginWorker(session, authPath) {
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
const interval = POLL_DEFAULT_INTERVAL;
|
|
78
|
+
while (Date.now() - startTime < POLL_MAX_DURATION) {
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
80
|
+
if (session.status !== 'pending')
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
// Step 3: Poll for authorization
|
|
84
|
+
const pollRes = await fetch(CODEX_DEVICE_TOKEN_URL, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
device_auth_id: session.deviceAuthId,
|
|
89
|
+
user_code: session.userCode,
|
|
90
|
+
}),
|
|
91
|
+
signal: AbortSignal.timeout(10000),
|
|
92
|
+
});
|
|
93
|
+
if (pollRes.status === 200) {
|
|
94
|
+
const pollData = await pollRes.json();
|
|
95
|
+
// Step 4: Exchange authorization code for tokens
|
|
96
|
+
const tokenRes = await fetch(CODEX_OAUTH_TOKEN_URL, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
99
|
+
body: new URLSearchParams({
|
|
100
|
+
grant_type: 'authorization_code',
|
|
101
|
+
code: pollData.authorization_code,
|
|
102
|
+
redirect_uri: CODEX_REDIRECT_URI,
|
|
103
|
+
client_id: CODEX_CLIENT_ID,
|
|
104
|
+
code_verifier: pollData.code_verifier,
|
|
105
|
+
}).toString(),
|
|
106
|
+
signal: AbortSignal.timeout(15000),
|
|
107
|
+
});
|
|
108
|
+
if (!tokenRes.ok) {
|
|
109
|
+
const errText = await tokenRes.text();
|
|
110
|
+
console.error('[Codex Auth] Token exchange failed:', tokenRes.status, errText);
|
|
111
|
+
session.status = 'error';
|
|
112
|
+
session.error = `Token exchange failed: ${tokenRes.status}`;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const tokenData = await tokenRes.json();
|
|
116
|
+
const refreshToken = tokenData.refresh_token || '';
|
|
117
|
+
session.accessToken = tokenData.access_token;
|
|
118
|
+
session.refreshToken = refreshToken;
|
|
119
|
+
session.status = 'approved';
|
|
120
|
+
// Save to auth.json
|
|
121
|
+
const auth = loadAuthJson(authPath);
|
|
122
|
+
if (!auth.providers)
|
|
123
|
+
auth.providers = {};
|
|
124
|
+
auth.providers['openai-codex'] = {
|
|
125
|
+
tokens: {
|
|
126
|
+
access_token: tokenData.access_token,
|
|
127
|
+
refresh_token: refreshToken,
|
|
128
|
+
},
|
|
129
|
+
last_refresh: new Date().toISOString(),
|
|
130
|
+
auth_mode: 'chatgpt',
|
|
131
|
+
};
|
|
132
|
+
// Add to credential pool
|
|
133
|
+
if (!auth.credential_pool)
|
|
134
|
+
auth.credential_pool = {};
|
|
135
|
+
auth.credential_pool['openai-codex'] = [{
|
|
136
|
+
id: `openai-codex-${Date.now()}`,
|
|
137
|
+
label: 'OpenAI Codex',
|
|
138
|
+
base_url: CODEX_DEFAULT_BASE_URL,
|
|
139
|
+
access_token: tokenData.access_token,
|
|
140
|
+
last_status: null,
|
|
141
|
+
}];
|
|
142
|
+
saveAuthJson(authPath, auth);
|
|
143
|
+
// Save to ~/.codex/auth.json for CLI sync
|
|
144
|
+
saveCodexCliTokens(tokenData.access_token, refreshToken);
|
|
145
|
+
console.log('[Codex Auth] Login successful');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (pollRes.status === 403 || pollRes.status === 404) {
|
|
149
|
+
// Not yet authorized, keep polling
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Other error status
|
|
153
|
+
console.error('[Codex Auth] Poll failed:', pollRes.status);
|
|
154
|
+
session.status = 'error';
|
|
155
|
+
session.error = `Poll failed: ${pollRes.status}`;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
console.error('[Codex Auth] Poll error:', err.message);
|
|
163
|
+
session.status = 'error';
|
|
164
|
+
session.error = err.message;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Timeout
|
|
169
|
+
session.status = 'expired';
|
|
170
|
+
}
|
|
171
|
+
// --- Routes ---
|
|
172
|
+
exports.codexAuthRoutes = new router_1.default();
|
|
173
|
+
exports.codexAuthRoutes.post('/api/hermes/auth/codex/start', async (ctx) => {
|
|
174
|
+
try {
|
|
175
|
+
cleanupExpiredSessions();
|
|
176
|
+
// Step 1: Request device code
|
|
177
|
+
const res = await fetch(CODEX_DEVICE_AUTH_URL, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
'Accept': 'application/json',
|
|
182
|
+
'User-Agent': 'node-fetch',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({ client_id: CODEX_CLIENT_ID }),
|
|
185
|
+
signal: AbortSignal.timeout(10000),
|
|
186
|
+
});
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
let errorBody = null;
|
|
189
|
+
try {
|
|
190
|
+
errorBody = await res.json();
|
|
191
|
+
}
|
|
192
|
+
catch { /* ignore */ }
|
|
193
|
+
console.error(`[codex-auth] Device code request failed: ${res.status}`, errorBody);
|
|
194
|
+
let errorMessage = `Device code request failed: ${res.status}`;
|
|
195
|
+
if (errorBody?.error?.code === 'unsupported_country_region_territory') {
|
|
196
|
+
errorMessage = 'OpenAI does not support your region. You may need to use a proxy or VPN to access Codex.';
|
|
197
|
+
}
|
|
198
|
+
ctx.status = 502;
|
|
199
|
+
ctx.body = { error: errorMessage, code: errorBody?.error?.code };
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const data = await res.json();
|
|
203
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
204
|
+
const session = {
|
|
205
|
+
id: sessionId,
|
|
206
|
+
userCode: data.user_code,
|
|
207
|
+
deviceAuthId: data.device_auth_id,
|
|
208
|
+
status: 'pending',
|
|
209
|
+
createdAt: Date.now(),
|
|
210
|
+
};
|
|
211
|
+
sessions.set(sessionId, session);
|
|
212
|
+
// Start background worker
|
|
213
|
+
const authPath = (0, hermes_profile_1.getActiveAuthPath)();
|
|
214
|
+
codexLoginWorker(session, authPath).catch(err => {
|
|
215
|
+
console.error('[Codex Auth] Worker error:', err);
|
|
216
|
+
session.status = 'error';
|
|
217
|
+
session.error = err.message;
|
|
218
|
+
});
|
|
219
|
+
ctx.body = {
|
|
220
|
+
session_id: sessionId,
|
|
221
|
+
user_code: data.user_code,
|
|
222
|
+
verification_url: CODEX_VERIFICATION_URL,
|
|
223
|
+
expires_in: 900, // 15 minutes
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
ctx.status = 500;
|
|
228
|
+
ctx.body = { error: err.message };
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
exports.codexAuthRoutes.get('/api/hermes/auth/codex/poll/:sessionId', async (ctx) => {
|
|
232
|
+
const session = sessions.get(ctx.params.sessionId);
|
|
233
|
+
if (!session) {
|
|
234
|
+
ctx.status = 404;
|
|
235
|
+
ctx.body = { error: 'Session not found' };
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
ctx.body = {
|
|
239
|
+
status: session.status,
|
|
240
|
+
error: session.error || null,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
exports.codexAuthRoutes.get('/api/hermes/auth/codex/status', async (ctx) => {
|
|
244
|
+
try {
|
|
245
|
+
const authPath = (0, hermes_profile_1.getActiveAuthPath)();
|
|
246
|
+
const auth = loadAuthJson(authPath);
|
|
247
|
+
const tokens = auth.providers?.['openai-codex']?.tokens;
|
|
248
|
+
if (!tokens?.access_token || !auth.providers) {
|
|
249
|
+
ctx.body = { authenticated: false };
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const codexProvider = auth.providers['openai-codex'];
|
|
253
|
+
// Check if token is expired
|
|
254
|
+
const exp = decodeJwtExp(tokens.access_token);
|
|
255
|
+
if (exp && exp <= Date.now() / 1000 + 120) {
|
|
256
|
+
// Try refresh
|
|
257
|
+
if (tokens.refresh_token) {
|
|
258
|
+
try {
|
|
259
|
+
const refreshRes = await fetch(CODEX_OAUTH_TOKEN_URL, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
262
|
+
body: new URLSearchParams({
|
|
263
|
+
grant_type: 'refresh_token',
|
|
264
|
+
refresh_token: tokens.refresh_token,
|
|
265
|
+
client_id: CODEX_CLIENT_ID,
|
|
266
|
+
}).toString(),
|
|
267
|
+
signal: AbortSignal.timeout(15000),
|
|
268
|
+
});
|
|
269
|
+
if (refreshRes.ok) {
|
|
270
|
+
const newTokens = await refreshRes.json();
|
|
271
|
+
codexProvider.tokens.access_token = newTokens.access_token;
|
|
272
|
+
if (newTokens.refresh_token) {
|
|
273
|
+
codexProvider.tokens.refresh_token = newTokens.refresh_token;
|
|
274
|
+
}
|
|
275
|
+
codexProvider.last_refresh = new Date().toISOString();
|
|
276
|
+
saveAuthJson(authPath, auth);
|
|
277
|
+
saveCodexCliTokens(newTokens.access_token, newTokens.refresh_token || tokens.refresh_token);
|
|
278
|
+
// Update credential pool too
|
|
279
|
+
if (auth.credential_pool?.['openai-codex']?.[0]) {
|
|
280
|
+
auth.credential_pool['openai-codex'][0].access_token = newTokens.access_token;
|
|
281
|
+
saveAuthJson(authPath, auth);
|
|
282
|
+
}
|
|
283
|
+
ctx.body = { authenticated: true, last_refresh: codexProvider.last_refresh };
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Refresh failed
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
ctx.body = { authenticated: false };
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
ctx.body = {
|
|
295
|
+
authenticated: true,
|
|
296
|
+
last_refresh: codexProvider.last_refresh,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
ctx.body = { authenticated: false };
|
|
301
|
+
}
|
|
302
|
+
});
|
|
@@ -8,8 +8,8 @@ const router_1 = __importDefault(require("@koa/router"));
|
|
|
8
8
|
const promises_1 = require("fs/promises");
|
|
9
9
|
const promises_2 = require("fs/promises");
|
|
10
10
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
-
const hermes_cli_1 = require("../../services/hermes-cli");
|
|
12
|
-
const hermes_profile_1 = require("../../services/hermes-profile");
|
|
11
|
+
const hermes_cli_1 = require("../../services/hermes/hermes-cli");
|
|
12
|
+
const hermes_profile_1 = require("../../services/hermes/hermes-profile");
|
|
13
13
|
// Platform sections that require gateway restart after config change
|
|
14
14
|
const PLATFORM_SECTIONS = new Set([
|
|
15
15
|
'telegram', 'discord', 'slack', 'whatsapp', 'matrix',
|
|
@@ -82,6 +82,18 @@ function getNested(obj, path) {
|
|
|
82
82
|
}
|
|
83
83
|
return cur;
|
|
84
84
|
}
|
|
85
|
+
function deepMerge(target, source) {
|
|
86
|
+
for (const key of Object.keys(source)) {
|
|
87
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
|
|
88
|
+
target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
|
|
89
|
+
target[key] = deepMerge(target[key], source[key]);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
target[key] = source[key];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return target;
|
|
96
|
+
}
|
|
85
97
|
async function readEnvPlatforms() {
|
|
86
98
|
try {
|
|
87
99
|
const raw = await (0, promises_1.readFile)(envPath(), 'utf-8');
|
|
@@ -218,7 +230,7 @@ exports.configRoutes.put('/api/hermes/config', async (ctx) => {
|
|
|
218
230
|
}
|
|
219
231
|
try {
|
|
220
232
|
const config = await readConfig();
|
|
221
|
-
config[section] =
|
|
233
|
+
config[section] = deepMerge(config[section] || {}, values);
|
|
222
234
|
await writeConfig(config);
|
|
223
235
|
// Restart gateway for platform/channel config changes
|
|
224
236
|
if (PLATFORM_SECTIONS.has(section)) {
|
|
@@ -41,14 +41,16 @@ const router_1 = __importDefault(require("@koa/router"));
|
|
|
41
41
|
const promises_1 = require("fs/promises");
|
|
42
42
|
const path_1 = require("path");
|
|
43
43
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
44
|
-
const hermes_profile_1 = require("../../services/hermes-profile");
|
|
45
|
-
const hermesCli = __importStar(require("../../services/hermes-cli"));
|
|
44
|
+
const hermes_profile_1 = require("../../services/hermes/hermes-profile");
|
|
45
|
+
const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
46
46
|
// --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
|
|
47
47
|
// Maps provider key → { api_key_envs: all env var aliases for API key, base_url_env: env var for base URL }
|
|
48
48
|
const PROVIDER_ENV_MAP = {
|
|
49
49
|
openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: 'OPENROUTER_BASE_URL' },
|
|
50
50
|
zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
|
|
51
|
-
'kimi-
|
|
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' },
|
|
52
54
|
minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
|
|
53
55
|
'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
|
|
54
56
|
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
|
|
@@ -57,11 +59,13 @@ const PROVIDER_ENV_MAP = {
|
|
|
57
59
|
xai: { api_key_env: 'XAI_API_KEY', base_url_env: 'XAI_BASE_URL' },
|
|
58
60
|
xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
|
|
59
61
|
gemini: { api_key_env: 'GEMINI_API_KEY', base_url_env: '' },
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
opencode: { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_ZEN_BASE_URL' },
|
|
62
|
+
kilocode: { api_key_env: 'KILO_API_KEY', base_url_env: 'KILOCODE_BASE_URL' },
|
|
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' },
|
|
63
65
|
'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
|
|
64
66
|
huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
|
|
67
|
+
arcee: { api_key_env: 'ARCEE_API_KEY', base_url_env: '' },
|
|
68
|
+
'openai-codex': { api_key_env: '', base_url_env: 'HERMES_CODEX_BASE_URL' },
|
|
65
69
|
};
|
|
66
70
|
async function saveEnvValue(key, value) {
|
|
67
71
|
const envPath = (0, hermes_profile_1.getActiveEnvPath)();
|
|
@@ -390,14 +394,12 @@ exports.fsRoutes.post('/api/hermes/memory', async (ctx) => {
|
|
|
390
394
|
// Build model list from user's actual config.yaml using js-yaml
|
|
391
395
|
function buildModelGroups(config) {
|
|
392
396
|
let defaultModel = '';
|
|
393
|
-
let defaultProvider = '';
|
|
394
397
|
const groups = [];
|
|
395
398
|
const allModelIds = new Set();
|
|
396
399
|
// 1. Extract current model
|
|
397
400
|
const modelSection = config.model;
|
|
398
401
|
if (typeof modelSection === 'object' && modelSection !== null) {
|
|
399
402
|
defaultModel = String(modelSection.default || '').trim();
|
|
400
|
-
defaultProvider = String(modelSection.provider || '').trim();
|
|
401
403
|
}
|
|
402
404
|
else if (typeof modelSection === 'string') {
|
|
403
405
|
defaultModel = modelSection.trim();
|
|
@@ -480,20 +482,85 @@ exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
|
|
|
480
482
|
for (const result of results) {
|
|
481
483
|
if (result.status === 'fulfilled' && result.value.models.length > 0) {
|
|
482
484
|
const { key, label, base_url, models } = result.value;
|
|
483
|
-
groups.push({ provider: key, label, base_url, models });
|
|
485
|
+
groups.push({ provider: key, label, base_url, models: Array.from(new Set(models)) });
|
|
484
486
|
}
|
|
485
487
|
else if (result.status === 'rejected') {
|
|
486
488
|
console.error(`[available-models] Failed: ${result.reason?.message || result.reason}`);
|
|
487
489
|
}
|
|
488
490
|
}
|
|
489
491
|
}
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
+
// Deduplicate models within each group and merge groups with the same provider key
|
|
493
|
+
const dedupedGroups = [];
|
|
494
|
+
const seenProviders = new Map();
|
|
495
|
+
for (const g of groups) {
|
|
496
|
+
g.models = Array.from(new Set(g.models));
|
|
497
|
+
const existingIdx = seenProviders.get(g.provider);
|
|
498
|
+
if (existingIdx !== undefined) {
|
|
499
|
+
// Merge models into existing group
|
|
500
|
+
const existing = dedupedGroups[existingIdx];
|
|
501
|
+
const existingSet = new Set(existing.models);
|
|
502
|
+
for (const m of g.models) {
|
|
503
|
+
if (!existingSet.has(m))
|
|
504
|
+
existing.models.push(m);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
seenProviders.set(g.provider, dedupedGroups.length);
|
|
509
|
+
dedupedGroups.push(g);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Merge custom_providers from config.yaml (ensures manually-input model names appear)
|
|
513
|
+
const customProviders = Array.isArray(config.custom_providers)
|
|
514
|
+
? config.custom_providers
|
|
515
|
+
: [];
|
|
516
|
+
for (const cp of customProviders) {
|
|
517
|
+
if (!cp.base_url || !cp.model)
|
|
518
|
+
continue;
|
|
519
|
+
const baseUrl = cp.base_url.replace(/\/+$/, '');
|
|
520
|
+
// Check if we already have a group for this base_url
|
|
521
|
+
const existing = dedupedGroups.find(g => g.base_url.replace(/\/+$/, '') === baseUrl);
|
|
522
|
+
if (existing) {
|
|
523
|
+
if (!existing.models.includes(cp.model)) {
|
|
524
|
+
existing.models.push(cp.model);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
dedupedGroups.push({
|
|
529
|
+
provider: `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`,
|
|
530
|
+
label: cp.name,
|
|
531
|
+
base_url: baseUrl,
|
|
532
|
+
models: [cp.model],
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// Ensure config's current default model appears in the model list
|
|
537
|
+
if (currentDefault) {
|
|
538
|
+
const currentProvider = typeof config.model === 'object' ? String(config.model.provider || '').trim() : '';
|
|
539
|
+
if (currentProvider) {
|
|
540
|
+
const targetGroup = dedupedGroups.find(g => g.provider === currentProvider);
|
|
541
|
+
if (targetGroup && !targetGroup.models.includes(currentDefault)) {
|
|
542
|
+
targetGroup.models.unshift(currentDefault);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// No provider specified — add to the first group that matches via base_url
|
|
547
|
+
// or just prepend to all groups
|
|
548
|
+
let found = false;
|
|
549
|
+
for (const g of dedupedGroups) {
|
|
550
|
+
if (!found && !g.models.includes(currentDefault)) {
|
|
551
|
+
g.models.unshift(currentDefault);
|
|
552
|
+
found = true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Fallback: if still no providers, fall back to config.yaml parsing
|
|
558
|
+
if (dedupedGroups.length === 0) {
|
|
492
559
|
const fallback = buildModelGroups(config);
|
|
493
560
|
ctx.body = fallback;
|
|
494
561
|
return;
|
|
495
562
|
}
|
|
496
|
-
ctx.body = { default: currentDefault, groups };
|
|
563
|
+
ctx.body = { default: currentDefault, groups: dedupedGroups };
|
|
497
564
|
}
|
|
498
565
|
catch (err) {
|
|
499
566
|
ctx.status = 500;
|
|
@@ -624,19 +691,26 @@ exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) =>
|
|
|
624
691
|
ctx.body = { error: 'Cannot delete the last provider' };
|
|
625
692
|
return;
|
|
626
693
|
}
|
|
694
|
+
// Case-insensitive key lookup: normalize poolKey to match credential_pool
|
|
695
|
+
let resolvedKey = poolKey;
|
|
627
696
|
if (!(poolKey in auth.credential_pool)) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
697
|
+
const normalized = poolKey.toLowerCase();
|
|
698
|
+
const match = Object.keys(auth.credential_pool).find(k => k.toLowerCase() === normalized);
|
|
699
|
+
if (!match) {
|
|
700
|
+
ctx.status = 404;
|
|
701
|
+
ctx.body = { error: `Provider "${poolKey}" not found` };
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
resolvedKey = match;
|
|
631
705
|
}
|
|
632
706
|
// Check if this is the current active provider
|
|
633
707
|
const config = await readConfigYaml();
|
|
634
708
|
const currentProvider = config.model?.provider;
|
|
635
|
-
const isCurrent = currentProvider === poolKey;
|
|
709
|
+
const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey;
|
|
636
710
|
// Save base_url before deleting
|
|
637
|
-
const deletedBaseUrl = auth.credential_pool[
|
|
711
|
+
const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url;
|
|
638
712
|
// 1. Delete from auth.json
|
|
639
|
-
delete auth.credential_pool[
|
|
713
|
+
delete auth.credential_pool[resolvedKey];
|
|
640
714
|
await saveAuthJson(auth);
|
|
641
715
|
// 2. Remove matching entry from config.yaml custom_providers
|
|
642
716
|
if (deletedBaseUrl && Array.isArray(config.custom_providers)) {
|
|
@@ -11,6 +11,7 @@ const config_1 = require("./config");
|
|
|
11
11
|
const filesystem_1 = require("./filesystem");
|
|
12
12
|
const logs_1 = require("./logs");
|
|
13
13
|
const weixin_1 = require("./weixin");
|
|
14
|
+
const codex_auth_1 = require("./codex-auth");
|
|
14
15
|
const proxy_1 = require("./proxy");
|
|
15
16
|
Object.defineProperty(exports, "proxyMiddleware", { enumerable: true, get: function () { return proxy_1.proxyMiddleware; } });
|
|
16
17
|
const terminal_1 = require("./terminal");
|
|
@@ -22,4 +23,5 @@ exports.hermesRoutes.use(config_1.configRoutes.routes());
|
|
|
22
23
|
exports.hermesRoutes.use(filesystem_1.fsRoutes.routes());
|
|
23
24
|
exports.hermesRoutes.use(logs_1.logRoutes.routes());
|
|
24
25
|
exports.hermesRoutes.use(weixin_1.weixinRoutes.routes());
|
|
26
|
+
exports.hermesRoutes.use(codex_auth_1.codexAuthRoutes.routes());
|
|
25
27
|
exports.hermesRoutes.use(proxy_1.proxyRoutes.routes());
|