hermes-web-ui 0.3.2 → 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-CYFVC6GK.js → Add-CIZmky1Q.js} +1 -1
- package/dist/client/assets/{Button-Bls2FkVh.js → Button-Cpfqx5bj.js} +1 -1
- package/dist/client/assets/{ChannelsView-hQeWN-ve.js → ChannelsView-CqgEMY6f.js} +1 -1
- package/dist/client/assets/{ChatView-C67INFi3.js → ChatView-o_KHRYai.js} +1 -1
- package/dist/client/assets/{Close-CkAmZwHA.js → Close-Dx71Ggz9.js} +1 -1
- package/dist/client/assets/{FormItem-CO8nEllS.js → FormItem-D0pABOgK.js} +1 -1
- package/dist/client/assets/{Input-CmTdqaW1.js → Input-DmGT4Mu3.js} +1 -1
- package/dist/client/assets/{InputNumber-BVqiFQ5W.js → InputNumber-CQVOZP6a.js} +1 -1
- package/dist/client/assets/{JobsView-B0LZ2Ep0.js → JobsView-BwxWBXo3.js} +2 -2
- package/dist/client/assets/{LoginView-BjqBXdDl.js → LoginView-C4Id2mSY.js} +1 -1
- package/dist/client/assets/{LogsView-CXiJ_mte.js → LogsView-H_gfkSSN.js} +1 -1
- package/dist/client/assets/{MarkdownRenderer-CtI0yse1.js → MarkdownRenderer-DJK1b6T2.js} +1 -1
- package/dist/client/assets/{MemoryView-CjPn1pVR.js → MemoryView-DO4Y2le5.js} +1 -1
- package/dist/client/assets/{Modal-apmj2qGK.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-B9XSMaYA.js → Popconfirm-CLvbZW3I.js} +1 -1
- package/dist/client/assets/{Popover-CjCb_fJl.js → Popover-Dwi5NArH.js} +1 -1
- package/dist/client/assets/{ProfilesView-CmMfNg5X.js → ProfilesView-R_u1mtsl.js} +1 -1
- package/dist/client/assets/{Scrollbar-v6Ok23iG.js → Scrollbar-zYdleApO.js} +1 -1
- package/dist/client/assets/{Select-CDF2CEro.js → Select-0jFWg4Lf.js} +1 -1
- package/dist/client/assets/{SettingRow-CSjwu__h.js → SettingRow-LTP_cMLl.js} +1 -1
- package/dist/client/assets/{SettingsView-BYedQhU5.js → SettingsView-5o2GfsQf.js} +1 -1
- package/dist/client/assets/{SkillsView-B_MA4How.js → SkillsView-Cez6jHIn.js} +1 -1
- package/dist/client/assets/{Spin-UqA8uGuf.js → Spin-CfKqXT5a.js} +1 -1
- package/dist/client/assets/{Suffix-iRME2DUp.js → Suffix-jKQO068E.js} +1 -1
- package/dist/client/assets/{Switch-B4za8e-x.js → Switch-tk7Pkw6L.js} +1 -1
- package/dist/client/assets/{TerminalView-C0xlWgsR.js → TerminalView-C-oFmyFh.js} +1 -1
- package/dist/client/assets/{Tooltip-C9kFaGpi.js → Tooltip-Dc4tc92D.js} +1 -1
- package/dist/client/assets/{UsageView-Df6TKjYG.js → UsageView-SSxSWAFO.js} +1 -1
- package/dist/client/assets/{Warning-C--exCXL.js → Warning-BPlnDrmx.js} +1 -1
- package/dist/client/assets/{_plugin-vue_export-helper-BPCSidQI.js → _plugin-vue_export-helper-CcX4e_Is.js} +1 -1
- package/dist/client/assets/{app-BEBfTlZP.js → app-CcLe99Xa.js} +1 -1
- package/dist/client/assets/app-ML9hRyDO.js +1 -0
- package/dist/client/assets/{browser-OtgUKGth.js → browser-BfJIjfY3.js} +1 -1
- package/dist/client/assets/{chat-B8_g1FhX.js → chat-CuVG21Hv.js} +2 -2
- package/dist/client/assets/composables-BTIEu8ZB.js +1 -0
- package/dist/client/assets/{fade-in-scale-up.cssr-BbYEGcju.js → fade-in-scale-up.cssr-e30I2JjN.js} +1 -1
- package/dist/client/assets/index-XufFb2mL.js +284 -0
- package/dist/client/assets/{jobs-BRLhuQpK.js → jobs-LBrAH2Mz.js} +1 -1
- package/dist/client/assets/{light-Bmu6b0jg.js → light-BF--Dz10.js} +1 -1
- package/dist/client/assets/{light-B3QvGN97.js → light-BJ4xZyd0.js} +1 -1
- package/dist/client/assets/{light-DDEUT7ek.js → light-BLbGAFtS.js} +1 -1
- package/dist/client/assets/{light-B83Z4vA9.js → light-CCZMDqco.js} +1 -1
- package/dist/client/assets/{light-BtLlDltU.js → light-CDuRPTUd.js} +1 -1
- package/dist/client/assets/{light-8G-BZ0JM.js → light-DTBcb4qq.js} +1 -1
- package/dist/client/assets/{pinia-F-uLzgMS.js → pinia-D2g49IcO.js} +1 -1
- package/dist/client/assets/{profiles-DIJT1P0i.js → profiles-DbCu_blp.js} +1 -1
- package/dist/client/assets/{router-Drg4uUER.js → router-0X4x3p8e.js} +2 -2
- package/dist/client/assets/{sessions-BwtYroiN.js → sessions-SzjWXR9A.js} +1 -1
- package/dist/client/assets/{skills-BLhNwqgu.js → skills-CsjvGVsY.js} +1 -1
- package/dist/client/assets/use-compitable-DY4l-k3U.js +1 -0
- package/dist/client/assets/{use-message-xhQoRXQV.js → use-message-4kASja7X.js} +1 -1
- package/dist/client/assets/{useTheme-Dd5YNF4O.js → useTheme-Dh7NTo_V.js} +1 -1
- package/dist/client/index.html +27 -27
- 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/filesystem.js +67 -13
- package/dist/server/routes/hermes/index.js +2 -0
- package/dist/server/shared/providers.js +62 -18
- package/package.json +1 -1
- package/dist/client/assets/ModelsView-DXvahVC_.js +0 -1
- package/dist/client/assets/app-DoAgSphx.js +0 -1
- package/dist/client/assets/composables-VZZHak9F.js +0 -1
- package/dist/client/assets/index-DY7SeRWD.js +0 -284
- package/dist/client/assets/use-compitable-BgSV79Sl.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>
|
|
@@ -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
|
+
});
|
|
@@ -48,7 +48,9 @@ const hermesCli = __importStar(require("../../services/hermes/hermes-cli"));
|
|
|
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();
|
|
@@ -507,7 +509,52 @@ exports.fsRoutes.get('/api/hermes/available-models', async (ctx) => {
|
|
|
507
509
|
dedupedGroups.push(g);
|
|
508
510
|
}
|
|
509
511
|
}
|
|
510
|
-
//
|
|
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
|
|
511
558
|
if (dedupedGroups.length === 0) {
|
|
512
559
|
const fallback = buildModelGroups(config);
|
|
513
560
|
ctx.body = fallback;
|
|
@@ -644,19 +691,26 @@ exports.fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) =>
|
|
|
644
691
|
ctx.body = { error: 'Cannot delete the last provider' };
|
|
645
692
|
return;
|
|
646
693
|
}
|
|
694
|
+
// Case-insensitive key lookup: normalize poolKey to match credential_pool
|
|
695
|
+
let resolvedKey = poolKey;
|
|
647
696
|
if (!(poolKey in auth.credential_pool)) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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;
|
|
651
705
|
}
|
|
652
706
|
// Check if this is the current active provider
|
|
653
707
|
const config = await readConfigYaml();
|
|
654
708
|
const currentProvider = config.model?.provider;
|
|
655
|
-
const isCurrent = currentProvider === poolKey;
|
|
709
|
+
const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey;
|
|
656
710
|
// Save base_url before deleting
|
|
657
|
-
const deletedBaseUrl = auth.credential_pool[
|
|
711
|
+
const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url;
|
|
658
712
|
// 1. Delete from auth.json
|
|
659
|
-
delete auth.credential_pool[
|
|
713
|
+
delete auth.credential_pool[resolvedKey];
|
|
660
714
|
await saveAuthJson(auth);
|
|
661
715
|
// 2. Remove matching entry from config.yaml custom_providers
|
|
662
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());
|