@adversity/coding-tool-x 3.1.1 → 3.1.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/web/assets/Analytics-BIqc8Rin.css +1 -0
  3. package/dist/web/assets/Analytics-D2V09DHH.js +39 -0
  4. package/dist/web/assets/{ConfigTemplates-ZrK_s7ma.js → ConfigTemplates-Bf_11LhH.js} +1 -1
  5. package/dist/web/assets/Home-BRnW4FTS.js +1 -0
  6. package/dist/web/assets/Home-CyCIx4BA.css +1 -0
  7. package/dist/web/assets/{PluginManager-BD7QUZbU.js → PluginManager-B9J32GhW.js} +1 -1
  8. package/dist/web/assets/{ProjectList-DRb1DuHV.js → ProjectList-5a19MWJk.js} +1 -1
  9. package/dist/web/assets/SessionList-CXUr6S7w.css +1 -0
  10. package/dist/web/assets/SessionList-Cxg5bAdT.js +1 -0
  11. package/dist/web/assets/{SkillManager-C1xG5B4Q.js → SkillManager-CVBr0CLi.js} +1 -1
  12. package/dist/web/assets/{Terminal-DksBo_lM.js → Terminal-D2Xe_Q0H.js} +1 -1
  13. package/dist/web/assets/{WorkspaceManager-Burx7XOo.js → WorkspaceManager-C7dwV94C.js} +1 -1
  14. package/dist/web/assets/icons-BxcwoY5F.js +1 -0
  15. package/dist/web/assets/index-BS9RA6SN.js +2 -0
  16. package/dist/web/assets/index-DUNAVDGb.css +1 -0
  17. package/dist/web/assets/naive-ui-BIXcURHZ.js +1 -0
  18. package/dist/web/assets/{vendors-CO3Upi1d.js → vendors-i5CBGnlm.js} +1 -1
  19. package/dist/web/assets/{vue-vendor-DqyWIXEb.js → vue-vendor-PKd8utv_.js} +1 -1
  20. package/dist/web/index.html +6 -6
  21. package/package.json +1 -1
  22. package/src/config/default.js +7 -27
  23. package/src/config/loader.js +6 -3
  24. package/src/config/model-metadata.js +167 -0
  25. package/src/config/model-metadata.json +125 -0
  26. package/src/config/model-pricing.js +23 -93
  27. package/src/server/api/channels.js +16 -39
  28. package/src/server/api/codex-channels.js +15 -43
  29. package/src/server/api/commands.js +0 -77
  30. package/src/server/api/config.js +4 -1
  31. package/src/server/api/gemini-channels.js +16 -40
  32. package/src/server/api/opencode-channels.js +108 -56
  33. package/src/server/api/opencode-proxy.js +42 -33
  34. package/src/server/api/opencode-sessions.js +4 -69
  35. package/src/server/api/sessions.js +11 -68
  36. package/src/server/api/settings.js +138 -0
  37. package/src/server/api/skills.js +0 -44
  38. package/src/server/api/statistics.js +115 -1
  39. package/src/server/codex-proxy-server.js +32 -59
  40. package/src/server/gemini-proxy-server.js +21 -18
  41. package/src/server/index.js +13 -7
  42. package/src/server/opencode-proxy-server.js +1232 -197
  43. package/src/server/proxy-server.js +8 -8
  44. package/src/server/services/codex-sessions.js +105 -6
  45. package/src/server/services/commands-service.js +0 -29
  46. package/src/server/services/config-templates-service.js +38 -28
  47. package/src/server/services/env-checker.js +97 -9
  48. package/src/server/services/env-manager.js +29 -1
  49. package/src/server/services/opencode-channels.js +3 -1
  50. package/src/server/services/opencode-sessions.js +486 -218
  51. package/src/server/services/opencode-settings-manager.js +172 -36
  52. package/src/server/services/plugins-service.js +37 -28
  53. package/src/server/services/pty-manager.js +22 -18
  54. package/src/server/services/response-decoder.js +21 -0
  55. package/src/server/services/skill-service.js +1 -49
  56. package/src/server/services/speed-test.js +40 -3
  57. package/src/server/services/statistics-service.js +238 -1
  58. package/src/server/utils/pricing.js +51 -60
  59. package/src/server/websocket-server.js +24 -5
  60. package/dist/web/assets/Home-B8YfhZ3c.js +0 -1
  61. package/dist/web/assets/Home-Di2qsylF.css +0 -1
  62. package/dist/web/assets/SessionList-BGJWyneI.css +0 -1
  63. package/dist/web/assets/SessionList-lZ0LKzfT.js +0 -1
  64. package/dist/web/assets/icons-kcfLIMBB.js +0 -1
  65. package/dist/web/assets/index-Ufv5rCa5.css +0 -1
  66. package/dist/web/assets/index-lAkrRC3h.js +0 -2
  67. package/dist/web/assets/naive-ui-CSrLusZZ.js +0 -1
  68. package/src/server/api/convert.js +0 -260
  69. package/src/server/services/session-converter.js +0 -577
@@ -1,4 +1,4 @@
1
- import{g as t,c as e,a as n}from"./markdown-C9MYpaSi.js";import{S as r}from"./vue-vendor-DqyWIXEb.js";var o={exports:{}};
1
+ import{g as t,c as e,a as n}from"./markdown-C9MYpaSi.js";import{S as r}from"./vue-vendor-PKd8utv_.js";var o={exports:{}};
2
2
  /**!
3
3
  * Sortable 1.14.0
4
4
  * @author RubaXa <trash@rubaxa.org>
@@ -42,4 +42,4 @@ let lc;const cc=e=>lc=e,ac=Symbol();function uc(e){return e&&"object"==typeof e&
42
42
  * vue-router v4.6.4
43
43
  * (c) 2025 Eduardo San Martin Morote
44
44
  * @license MIT
45
- */(e,t);n=Pa(o.reverse(),"beforeRouteLeave",e,t);for(const s of o)s.leaveGuards.forEach(o=>{n.push(Na(o,e,t))});const c=y.bind(null,e,t);return n.push(c),M(n).then(()=>{n=[];for(const o of s.list())n.push(Na(o,e,t));return n.push(c),M(n)}).then(()=>{n=Pa(r,"beforeRouteUpdate",e,t);for(const o of r)o.updateGuards.forEach(o=>{n.push(Na(o,e,t))});return n.push(c),M(n)}).then(()=>{n=[];for(const o of l)if(o.beforeEnter)if(Ic(o.beforeEnter))for(const r of o.beforeEnter)n.push(Na(r,e,t));else n.push(Na(o.beforeEnter,e,t));return n.push(c),M(n)}).then(()=>(e.matched.forEach(e=>e.enterCallbacks={}),n=Pa(l,"beforeRouteEnter",e,t,b),n.push(c),M(n))).then(()=>{n=[];for(const o of i.list())n.push(Na(o,e,t));return n.push(c),M(n)}).catch(e=>Ea(e,_a.NAVIGATION_CANCELLED)?e:Promise.reject(e))}function S(e,t,n){l.list().forEach(o=>b(()=>o(e,t,n)))}function C(e,t,n,o,s){const i=g(e,t);if(i)return i;const l=t===la,a=kc?history.state:{};n&&(o||l?r.replace(e.fullPath,Rc({scroll:l&&a&&a.scroll},s)):r.push(e.fullPath,s)),c.value=e,N(e,t,n,l),R()}let w;function x(){w||(w=r.listen((e,t,n)=>{if(!L.listening)return;const o=d(e),s=v(o,L.currentRoute.value);if(s)return void _(Rc(s,{replace:!0,force:!0}),o).catch(Pc);a=o;const i=c.value;var l,u;kc&&(l=ga(i.fullPath,n.delta),u=da(),ma.set(l,u)),E(o,i).catch(e=>Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_CANCELLED)?e:Ea(e,_a.NAVIGATION_GUARD_REDIRECT)?(_(Rc(h(e.to),{force:!0}),o).then(e=>{Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_DUPLICATED)&&!n.delta&&n.type===ca.pop&&r.go(-1,!1)}).catch(Pc),Promise.reject()):(n.delta&&r.go(-n.delta,!1),O(e,o,i))).then(e=>{(e=e||C(o,i,!1))&&(n.delta&&!Ea(e,_a.NAVIGATION_CANCELLED)?r.go(-n.delta,!1):n.type===ca.pop&&Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_DUPLICATED)&&r.go(-1,!1)),S(o,i,e)}).catch(Pc)}))}let A,T=Ra(),k=Ra();function O(e,t,n){R(e);const o=k.list();return o.length&&o.forEach(o=>o(e,t,n)),Promise.reject(e)}function R(e){return A||(A=!e,x(),T.list().forEach(([t,n])=>e?n(e):t()),T.reset()),e}function N(t,n,o,r){const{scrollBehavior:s}=e;if(!kc||!s)return Promise.resolve();const i=!o&&function(e){const t=ma.get(e);return ma.delete(e),t}(ga(t.fullPath,0))||(r||!o)&&history.state&&history.state.scroll||null;return un().then(()=>s(t,n,i)).then(e=>e&&ha(e)).catch(e=>O(e,t,n))}const P=e=>r.go(e);let I;const D=new Set,L={currentRoute:c,listening:!0,addRoute:function(e,n){let o,r;return va(e)?(o=t.getRecordMatcher(e),r=n):r=e,t.addRoute(r,o)},removeRoute:function(e){const n=t.getRecordMatcher(e);n&&t.removeRoute(n)},clearRoutes:t.clearRoutes,hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map(e=>e.record)},resolve:d,options:e,push:m,replace:function(e){return m(Rc(h(e),{replace:!0}))},go:P,back:()=>P(-1),forward:()=>P(1),beforeEach:s.add,beforeResolve:i.add,afterEach:l.add,onError:k.add,isReady:function(){return A&&c.value!==la?Promise.resolve():new Promise((e,t)=>{T.add([e,t])})},install(e){e.component("RouterLink",ru),e.component("RouterView",cu),e.config.globalProperties.$router=L,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>Lt(c)}),kc&&!I&&c.value===la&&(I=!0,m(r.location).catch(e=>{}));const t={};for(const o in la)Object.defineProperty(t,o,{get:()=>c.value[o],enumerable:!0});e.provide(Ta,L),e.provide(ka,_t(t)),e.provide(Oa,c);const n=e.unmount;D.add(e),e.unmount=function(){D.delete(e),D.size<1&&(a=la,w&&w(),w=null,c.value=la,I=!1,A=!1),n()}}};function M(e){return e.reduce((e,t)=>e.then(()=>b(t)),Promise.resolve())}return L}function uu(){return Tn(Ta)}function fu(e){return Tn(ka)}const pu=e(ic);export{er as $,Pt as A,Nn as B,_s as C,Ni as D,wl as E,ms as F,qi as G,Ms as H,vs as I,Tt as J,Ho as K,K as L,tc as M,Lt as N,xt as O,At as P,Ut as Q,B as R,pu as S,Kn as T,au as U,Ma as V,js as W,Ds as X,Cn as Y,Kl as Z,Z as _,Is as a,ks as a0,Ac as a1,tr as a2,Xo as a3,Tc as a4,fu as a5,uu as a6,zl as a7,Yo as a8,ko as a9,dc as aa,Vs as b,Ts as c,ao as d,yt as e,fi as f,Ys as g,jo as h,$o as i,Vo as j,vt as k,Tn as l,Ro as m,No as n,Ss as o,Fs as p,An as q,Nt as r,wn as s,pi as t,Ht as u,un as v,In as w,nr as x,Hs as y,Os as z};
45
+ */(e,t);n=Pa(o.reverse(),"beforeRouteLeave",e,t);for(const s of o)s.leaveGuards.forEach(o=>{n.push(Na(o,e,t))});const c=y.bind(null,e,t);return n.push(c),M(n).then(()=>{n=[];for(const o of s.list())n.push(Na(o,e,t));return n.push(c),M(n)}).then(()=>{n=Pa(r,"beforeRouteUpdate",e,t);for(const o of r)o.updateGuards.forEach(o=>{n.push(Na(o,e,t))});return n.push(c),M(n)}).then(()=>{n=[];for(const o of l)if(o.beforeEnter)if(Ic(o.beforeEnter))for(const r of o.beforeEnter)n.push(Na(r,e,t));else n.push(Na(o.beforeEnter,e,t));return n.push(c),M(n)}).then(()=>(e.matched.forEach(e=>e.enterCallbacks={}),n=Pa(l,"beforeRouteEnter",e,t,b),n.push(c),M(n))).then(()=>{n=[];for(const o of i.list())n.push(Na(o,e,t));return n.push(c),M(n)}).catch(e=>Ea(e,_a.NAVIGATION_CANCELLED)?e:Promise.reject(e))}function S(e,t,n){l.list().forEach(o=>b(()=>o(e,t,n)))}function C(e,t,n,o,s){const i=g(e,t);if(i)return i;const l=t===la,a=kc?history.state:{};n&&(o||l?r.replace(e.fullPath,Rc({scroll:l&&a&&a.scroll},s)):r.push(e.fullPath,s)),c.value=e,N(e,t,n,l),R()}let w;function x(){w||(w=r.listen((e,t,n)=>{if(!L.listening)return;const o=d(e),s=v(o,L.currentRoute.value);if(s)return void _(Rc(s,{replace:!0,force:!0}),o).catch(Pc);a=o;const i=c.value;var l,u;kc&&(l=ga(i.fullPath,n.delta),u=da(),ma.set(l,u)),E(o,i).catch(e=>Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_CANCELLED)?e:Ea(e,_a.NAVIGATION_GUARD_REDIRECT)?(_(Rc(h(e.to),{force:!0}),o).then(e=>{Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_DUPLICATED)&&!n.delta&&n.type===ca.pop&&r.go(-1,!1)}).catch(Pc),Promise.reject()):(n.delta&&r.go(-n.delta,!1),O(e,o,i))).then(e=>{(e=e||C(o,i,!1))&&(n.delta&&!Ea(e,_a.NAVIGATION_CANCELLED)?r.go(-n.delta,!1):n.type===ca.pop&&Ea(e,_a.NAVIGATION_ABORTED|_a.NAVIGATION_DUPLICATED)&&r.go(-1,!1)),S(o,i,e)}).catch(Pc)}))}let A,T=Ra(),k=Ra();function O(e,t,n){R(e);const o=k.list();return o.length&&o.forEach(o=>o(e,t,n)),Promise.reject(e)}function R(e){return A||(A=!e,x(),T.list().forEach(([t,n])=>e?n(e):t()),T.reset()),e}function N(t,n,o,r){const{scrollBehavior:s}=e;if(!kc||!s)return Promise.resolve();const i=!o&&function(e){const t=ma.get(e);return ma.delete(e),t}(ga(t.fullPath,0))||(r||!o)&&history.state&&history.state.scroll||null;return un().then(()=>s(t,n,i)).then(e=>e&&ha(e)).catch(e=>O(e,t,n))}const P=e=>r.go(e);let I;const D=new Set,L={currentRoute:c,listening:!0,addRoute:function(e,n){let o,r;return va(e)?(o=t.getRecordMatcher(e),r=n):r=e,t.addRoute(r,o)},removeRoute:function(e){const n=t.getRecordMatcher(e);n&&t.removeRoute(n)},clearRoutes:t.clearRoutes,hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map(e=>e.record)},resolve:d,options:e,push:m,replace:function(e){return m(Rc(h(e),{replace:!0}))},go:P,back:()=>P(-1),forward:()=>P(1),beforeEach:s.add,beforeResolve:i.add,afterEach:l.add,onError:k.add,isReady:function(){return A&&c.value!==la?Promise.resolve():new Promise((e,t)=>{T.add([e,t])})},install(e){e.component("RouterLink",ru),e.component("RouterView",cu),e.config.globalProperties.$router=L,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>Lt(c)}),kc&&!I&&c.value===la&&(I=!0,m(r.location).catch(e=>{}));const t={};for(const o in la)Object.defineProperty(t,o,{get:()=>c.value[o],enumerable:!0});e.provide(Ta,L),e.provide(ka,_t(t)),e.provide(Oa,c);const n=e.unmount;D.add(e),e.unmount=function(){D.delete(e),D.size<1&&(a=la,w&&w(),w=null,c.value=la,I=!1,A=!1),n()}}};function M(e){return e.reduce((e,t)=>e.then(()=>b(t)),Promise.resolve())}return L}function uu(){return Tn(Ta)}function fu(e){return Tn(ka)}const pu=e(ic);export{er as $,Pt as A,Nn as B,_s as C,Ni as D,wl as E,ms as F,qi as G,Ms as H,vs as I,Tt as J,Ho as K,K as L,tc as M,Lt as N,xt as O,At as P,Ut as Q,B as R,pu as S,Kn as T,au as U,Ma as V,js as W,Ds as X,Cn as Y,Kl as Z,Z as _,Is as a,ks as a0,Ac as a1,tr as a2,Xo as a3,Tc as a4,fu as a5,uu as a6,zl as a7,Yo as a8,ko as a9,dc as aa,Rt as ab,Vs as b,Ts as c,ao as d,yt as e,fi as f,Ys as g,jo as h,$o as i,Vo as j,vt as k,Tn as l,Ro as m,No as n,Ss as o,Fs as p,An as q,Nt as r,wn as s,pi as t,Ht as u,un as v,In as w,nr as x,Hs as y,Os as z};
@@ -5,14 +5,14 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>CC-TOOL - ClaudeCode增强工作助手</title>
8
- <script type="module" crossorigin src="/assets/index-lAkrRC3h.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BS9RA6SN.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-C9MYpaSi.js">
10
- <link rel="modulepreload" crossorigin href="/assets/vue-vendor-DqyWIXEb.js">
11
- <link rel="modulepreload" crossorigin href="/assets/vendors-CO3Upi1d.js">
12
- <link rel="modulepreload" crossorigin href="/assets/naive-ui-CSrLusZZ.js">
13
- <link rel="modulepreload" crossorigin href="/assets/icons-kcfLIMBB.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vue-vendor-PKd8utv_.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/vendors-i5CBGnlm.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/naive-ui-BIXcURHZ.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/icons-BxcwoY5F.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-Ufv5rCa5.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-DUNAVDGb.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversity/coding-tool-x",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  // 默认配置
2
2
  const path = require('path');
3
3
  const os = require('os');
4
+ const modelMetadataConfig = require('./model-metadata.json');
4
5
 
5
6
  const DEFAULT_CONFIG = {
6
7
  projectsDir: path.join(os.homedir(), '.claude', 'projects'),
@@ -17,36 +18,15 @@ const DEFAULT_CONFIG = {
17
18
  },
18
19
  maxLogs: 100,
19
20
  statsInterval: 30,
20
- defaultModels: {
21
- claude: [
22
- 'claude-opus-4-6',
23
- 'claude-sonnet-4-6',
24
- 'claude-opus-4-5-20251101',
25
- 'claude-sonnet-4-5-20250929',
26
- 'claude-haiku-4-5-20251001'
27
- ],
28
- codex: [
29
- 'gpt-5.2-codex',
30
- 'gpt-5.1-codex-max',
31
- 'gpt-5.1-codex',
32
- 'gpt-5.1-codex-mini',
33
- 'gpt-5-codex',
34
- 'gpt-5.2',
35
- 'gpt-5.1',
36
- 'gpt-5'
37
- ],
38
- gemini: [
39
- 'gemini-3-pro-preview',
40
- 'gemini-3-flash-preview',
41
- 'gemini-2.5-pro',
42
- 'gemini-2.5-flash',
43
- 'gemini-2.5-flash-lite'
44
- ]
45
- },
46
21
  modelDiscovery: {
47
- // 是否优先使用 /v1/models 获取可用模型;默认关闭,直接走默认模型探测
48
22
  useV1ModelsEndpoint: false
49
23
  },
24
+ defaultModels: modelMetadataConfig.defaultModels || { claude: [], codex: [], gemini: [] },
25
+ defaultSpeedTestModels: modelMetadataConfig.defaultSpeedTestModels || {
26
+ claude: 'claude-haiku-4-5',
27
+ codex: 'gpt-5.2',
28
+ gemini: 'gemini-2.5-pro'
29
+ },
50
30
  pricing: {
51
31
  claude: {
52
32
  mode: 'auto',
@@ -49,9 +49,9 @@ function mergeDefaultModels(defaultModels, overrides = {}) {
49
49
  return merged;
50
50
  }
51
51
 
52
- function mergeModelDiscovery(defaultModelDiscovery, overrides = {}) {
52
+ function mergeDefaultSpeedTestModels(defaultModels, overrides = {}) {
53
53
  return {
54
- ...defaultModelDiscovery,
54
+ ...defaultModels,
55
55
  ...(overrides || {})
56
56
  };
57
57
  }
@@ -99,7 +99,10 @@ function loadConfig() {
99
99
  config.ports = { ...DEFAULT_CONFIG.ports, ...userConfig.ports };
100
100
  config.pricing = mergePricing(DEFAULT_CONFIG.pricing, userConfig.pricing);
101
101
  config.defaultModels = mergeDefaultModels(DEFAULT_CONFIG.defaultModels, userConfig.defaultModels);
102
- config.modelDiscovery = mergeModelDiscovery(DEFAULT_CONFIG.modelDiscovery, userConfig.modelDiscovery);
102
+ config.defaultSpeedTestModels = mergeDefaultSpeedTestModels(
103
+ DEFAULT_CONFIG.defaultSpeedTestModels,
104
+ userConfig.defaultSpeedTestModels
105
+ );
103
106
 
104
107
  // 确保有 currentProject,使用 defaultProject 作为 currentProject
105
108
  if (!config.currentProject && config.defaultProject) {
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Model metadata runtime helpers.
3
+ * Data source: ./model-metadata.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const metadataConfig = require('./model-metadata.json');
9
+ const METADATA_FILE_PATH = path.join(__dirname, 'model-metadata.json');
10
+
11
+ const MODEL_METADATA = metadataConfig.models || {};
12
+ const MODEL_ALIASES = metadataConfig.aliases || {};
13
+ const DEFAULT_MODELS = metadataConfig.defaultModels || { claude: [], codex: [], gemini: [] };
14
+ const DEFAULT_SPEED_TEST_MODELS = metadataConfig.defaultSpeedTestModels || {
15
+ claude: 'claude-haiku-4-5',
16
+ codex: 'gpt-5.2',
17
+ gemini: 'gemini-2.5-pro'
18
+ };
19
+
20
+ function normalizeNonEmptyString(value) {
21
+ if (typeof value !== 'string') return null;
22
+ const trimmed = value.trim();
23
+ return trimmed || null;
24
+ }
25
+
26
+ function loadMetadataConfigFromFile() {
27
+ try {
28
+ const raw = fs.readFileSync(METADATA_FILE_PATH, 'utf8');
29
+ const parsed = JSON.parse(raw);
30
+ if (parsed && typeof parsed === 'object') {
31
+ return parsed;
32
+ }
33
+ } catch (error) {
34
+ console.warn(`[model-metadata] Failed to read metadata file, fallback to in-memory config: ${error.message}`);
35
+ }
36
+ return metadataConfig;
37
+ }
38
+
39
+ /**
40
+ * Resolve model metadata (limit + pricing) for a given model ID.
41
+ * Supports: exact match -> alias match -> prefix match -> generic Claude fallback
42
+ *
43
+ * @param {string} modelId
44
+ * @returns {{ limit: {context, output}, pricing: {input, output, cacheCreation?, cacheRead?} } | null}
45
+ */
46
+ function resolveModelMetadata(modelId) {
47
+ if (!modelId) return null;
48
+ const id = String(modelId).toLowerCase().trim();
49
+
50
+ // Exact match
51
+ for (const [key, meta] of Object.entries(MODEL_METADATA)) {
52
+ if (id === key.toLowerCase()) return meta;
53
+ }
54
+
55
+ // Alias match -> canonical
56
+ for (const [alias, canonical] of Object.entries(MODEL_ALIASES)) {
57
+ if (id === alias.toLowerCase()) {
58
+ const meta = MODEL_METADATA[canonical];
59
+ if (meta) return meta;
60
+ }
61
+ }
62
+
63
+ // Prefix match
64
+ for (const [key, meta] of Object.entries(MODEL_METADATA)) {
65
+ if (id.startsWith(key.toLowerCase())) return meta;
66
+ }
67
+
68
+ // Generic fallback for unknown Claude models
69
+ if (id.startsWith('claude-')) {
70
+ return {
71
+ limit: { context: 200000, output: 32000 },
72
+ pricing: { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }
73
+ };
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ function resolveModelLimit(modelId) {
80
+ const meta = resolveModelMetadata(modelId);
81
+ return meta ? meta.limit : null;
82
+ }
83
+
84
+ function resolveModelPricing(modelId) {
85
+ const meta = resolveModelMetadata(modelId);
86
+ return meta ? meta.pricing : null;
87
+ }
88
+
89
+ function getAllModelIds() {
90
+ return Object.keys(MODEL_METADATA);
91
+ }
92
+
93
+ function getDefaultModels() {
94
+ return {
95
+ claude: [...(DEFAULT_MODELS.claude || [])],
96
+ codex: [...(DEFAULT_MODELS.codex || [])],
97
+ gemini: [...(DEFAULT_MODELS.gemini || [])]
98
+ };
99
+ }
100
+
101
+ function getDefaultModelsByToolType(toolType) {
102
+ const key = String(toolType || '').trim().toLowerCase();
103
+ if (key === 'openai_compatible') return [...(DEFAULT_MODELS.codex || [])];
104
+ if (key === 'claude' || key === 'codex' || key === 'gemini') {
105
+ return [...(DEFAULT_MODELS[key] || [])];
106
+ }
107
+ return [];
108
+ }
109
+
110
+ function getDefaultSpeedTestModels() {
111
+ const fileConfig = loadMetadataConfigFromFile();
112
+ const raw = fileConfig.defaultSpeedTestModels || DEFAULT_SPEED_TEST_MODELS;
113
+ return {
114
+ claude: normalizeNonEmptyString(raw.claude) || DEFAULT_SPEED_TEST_MODELS.claude,
115
+ codex: normalizeNonEmptyString(raw.codex) || DEFAULT_SPEED_TEST_MODELS.codex,
116
+ gemini: normalizeNonEmptyString(raw.gemini) || DEFAULT_SPEED_TEST_MODELS.gemini
117
+ };
118
+ }
119
+
120
+ function getDefaultSpeedTestModelByToolType(toolType) {
121
+ const key = String(toolType || '').trim().toLowerCase();
122
+ const defaults = getDefaultSpeedTestModels();
123
+ if (key === 'openai_compatible') return defaults.codex;
124
+ if (key === 'claude' || key === 'codex' || key === 'gemini') {
125
+ return defaults[key];
126
+ }
127
+ return defaults.codex;
128
+ }
129
+
130
+ function saveDefaultSpeedTestModels(nextDefaults) {
131
+ const current = loadMetadataConfigFromFile();
132
+ const merged = {
133
+ ...getDefaultSpeedTestModels(),
134
+ ...(nextDefaults || {})
135
+ };
136
+
137
+ const normalized = {
138
+ claude: normalizeNonEmptyString(merged.claude) || DEFAULT_SPEED_TEST_MODELS.claude,
139
+ codex: normalizeNonEmptyString(merged.codex) || DEFAULT_SPEED_TEST_MODELS.codex,
140
+ gemini: normalizeNonEmptyString(merged.gemini) || DEFAULT_SPEED_TEST_MODELS.gemini
141
+ };
142
+
143
+ const nextConfig = {
144
+ ...current,
145
+ defaultSpeedTestModels: normalized
146
+ };
147
+ fs.writeFileSync(METADATA_FILE_PATH, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
148
+ metadataConfig.defaultSpeedTestModels = normalized;
149
+ return normalized;
150
+ }
151
+
152
+ module.exports = {
153
+ MODEL_METADATA,
154
+ MODEL_ALIASES,
155
+ DEFAULT_MODELS,
156
+ DEFAULT_SPEED_TEST_MODELS,
157
+ resolveModelMetadata,
158
+ resolveModelLimit,
159
+ resolveModelPricing,
160
+ getAllModelIds,
161
+ getDefaultModels,
162
+ getDefaultModelsByToolType,
163
+ getDefaultSpeedTestModels,
164
+ getDefaultSpeedTestModelByToolType,
165
+ saveDefaultSpeedTestModels,
166
+ METADATA_LAST_UPDATED: metadataConfig.lastUpdated || '2026-02-27'
167
+ };
@@ -0,0 +1,125 @@
1
+ {
2
+ "lastUpdated": "2026-02-27",
3
+ "defaultModels": {
4
+ "claude": [
5
+ "claude-opus-4-6",
6
+ "claude-sonnet-4-6",
7
+ "claude-opus-4-5-20251101",
8
+ "claude-sonnet-4-5-20250929",
9
+ "claude-haiku-4-5-20251001"
10
+ ],
11
+ "codex": [
12
+ "gpt-5.3-codex",
13
+ "gpt-5.2-codex",
14
+ "gpt-5.1-codex-max",
15
+ "gpt-5.1-codex",
16
+ "gpt-5.1-codex-mini",
17
+ "gpt-5-codex",
18
+ "gpt-5.2",
19
+ "gpt-5.1",
20
+ "gpt-5"
21
+ ],
22
+ "gemini": [
23
+ "gemini-3.1-pro",
24
+ "gemini-3-pro-preview",
25
+ "gemini-3-flash-preview",
26
+ "gemini-2.5-pro",
27
+ "gemini-2.5-flash",
28
+ "gemini-2.5-flash-lite"
29
+ ]
30
+ },
31
+ "defaultSpeedTestModels": {
32
+ "claude": "claude-haiku-4-5",
33
+ "codex": "gpt-5.2",
34
+ "gemini": "gemini-2.5-pro"
35
+ },
36
+ "aliases": {
37
+ "claude-opus-4-6": "claude-opus-4-6",
38
+ "claude-sonnet-4-6": "claude-sonnet-4-6",
39
+ "claude-opus-4-5": "claude-opus-4-5-20251101",
40
+ "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
41
+ "claude-haiku-4-5": "claude-haiku-4-5-20251001"
42
+ },
43
+ "models": {
44
+ "claude-opus-4-6": {
45
+ "limit": { "context": 200000, "output": 32000 },
46
+ "pricing": { "input": 15, "output": 75, "cacheCreation": 18.75, "cacheRead": 1.5 }
47
+ },
48
+ "claude-sonnet-4-6": {
49
+ "limit": { "context": 200000, "output": 64000 },
50
+ "pricing": { "input": 3, "output": 15, "cacheCreation": 3.75, "cacheRead": 0.3 }
51
+ },
52
+ "claude-opus-4-5-20251101": {
53
+ "limit": { "context": 200000, "output": 32000 },
54
+ "pricing": { "input": 5, "output": 25, "cacheCreation": 6.25, "cacheRead": 0.5 }
55
+ },
56
+ "claude-sonnet-4-5-20250929": {
57
+ "limit": { "context": 200000, "output": 64000 },
58
+ "pricing": { "input": 3, "output": 15, "cacheCreation": 3.75, "cacheRead": 0.3 }
59
+ },
60
+ "claude-haiku-4-5-20251001": {
61
+ "limit": { "context": 200000, "output": 8096 },
62
+ "pricing": { "input": 1, "output": 5, "cacheCreation": 1.25, "cacheRead": 0.1 }
63
+ },
64
+ "gpt-5": {
65
+ "limit": { "context": 1000000, "output": 32768 },
66
+ "pricing": { "input": 2, "output": 8 }
67
+ },
68
+ "gpt-5.1": {
69
+ "limit": { "context": 1000000, "output": 32768 },
70
+ "pricing": { "input": 2, "output": 8 }
71
+ },
72
+ "gpt-5.2": {
73
+ "limit": { "context": 1000000, "output": 32768 },
74
+ "pricing": { "input": 2, "output": 8 }
75
+ },
76
+ "gpt-5-codex": {
77
+ "limit": { "context": 1000000, "output": 32768 },
78
+ "pricing": { "input": 2, "output": 8 }
79
+ },
80
+ "gpt-5.1-codex": {
81
+ "limit": { "context": 1000000, "output": 32768 },
82
+ "pricing": { "input": 2, "output": 8 }
83
+ },
84
+ "gpt-5.1-codex-mini": {
85
+ "limit": { "context": 1000000, "output": 32768 },
86
+ "pricing": { "input": 1.5, "output": 6 }
87
+ },
88
+ "gpt-5.1-codex-max": {
89
+ "limit": { "context": 1000000, "output": 32768 },
90
+ "pricing": { "input": 3, "output": 12 }
91
+ },
92
+ "gpt-5.2-codex": {
93
+ "limit": { "context": 1000000, "output": 32768 },
94
+ "pricing": { "input": 2, "output": 8 }
95
+ },
96
+ "gpt-5.3-codex": {
97
+ "limit": { "context": 1000000, "output": 32768 },
98
+ "pricing": { "input": 2, "output": 8 }
99
+ },
100
+ "gemini-3.1-pro": {
101
+ "limit": { "context": 2097152, "output": 65536 },
102
+ "pricing": { "input": 2.5, "output": 15 }
103
+ },
104
+ "gemini-3-pro-preview": {
105
+ "limit": { "context": 2097152, "output": 65536 },
106
+ "pricing": { "input": 2.5, "output": 15 }
107
+ },
108
+ "gemini-3-flash-preview": {
109
+ "limit": { "context": 1048576, "output": 65536 },
110
+ "pricing": { "input": 0.3, "output": 1.2 }
111
+ },
112
+ "gemini-2.5-pro": {
113
+ "limit": { "context": 1048576, "output": 65536 },
114
+ "pricing": { "input": 1.25, "output": 10 }
115
+ },
116
+ "gemini-2.5-flash": {
117
+ "limit": { "context": 1048576, "output": 65536 },
118
+ "pricing": { "input": 0.15, "output": 0.6 }
119
+ },
120
+ "gemini-2.5-flash-lite": {
121
+ "limit": { "context": 1048576, "output": 65536 },
122
+ "pricing": { "input": 0.1, "output": 0.4 }
123
+ }
124
+ }
125
+ }
@@ -1,105 +1,35 @@
1
1
  /**
2
- * Centralized Claude Model Pricing
2
+ * Claude Model Pricing - Backward Compatibility Re-export
3
3
  *
4
- * Official Anthropic pricing as of 2026-02-02
5
- * Source: https://claude.com/pricing
6
- *
7
- * All prices in USD per million tokens
4
+ * This file re-exports Claude-specific pricing and aliases from the centralized
5
+ * model-metadata.js config. Use model-metadata.js directly for new code.
8
6
  */
9
7
 
10
- const CLAUDE_MODEL_PRICING = {
11
- // Claude 4.5 (current generation)
12
- 'claude-opus-4-5-20250929': {
13
- input: 5,
14
- output: 25,
15
- cacheCreation: 6.25,
16
- cacheRead: 0.50
17
- },
18
- 'claude-sonnet-4-5-20250929': {
19
- input: 3,
20
- output: 15,
21
- cacheCreation: 3.75,
22
- cacheRead: 0.30
23
- },
24
- 'claude-haiku-4-5-20250929': {
25
- input: 1,
26
- output: 5,
27
- cacheCreation: 1.25,
28
- cacheRead: 0.10
29
- },
30
-
31
- // Claude 4 (previous generation)
32
- 'claude-opus-4-20250514': {
33
- input: 5,
34
- output: 25,
35
- cacheCreation: 6.25,
36
- cacheRead: 0.50
37
- },
38
- 'claude-sonnet-4-20250514': {
39
- input: 3,
40
- output: 15,
41
- cacheCreation: 3.75,
42
- cacheRead: 0.30
43
- },
8
+ const { MODEL_METADATA, MODEL_ALIASES, METADATA_LAST_UPDATED } = require('./model-metadata');
44
9
 
45
- // Claude 3.5
46
- 'claude-haiku-3-5-20241022': {
47
- input: 1,
48
- output: 5,
49
- cacheCreation: 1.25,
50
- cacheRead: 0.10
51
- },
52
- 'claude-3-5-haiku-20241022': {
53
- input: 1,
54
- output: 5,
55
- cacheCreation: 1.25,
56
- cacheRead: 0.10
57
- },
58
- 'claude-sonnet-3-5-20241022': {
59
- input: 3,
60
- output: 15,
61
- cacheCreation: 3.75,
62
- cacheRead: 0.30
63
- },
64
- 'claude-sonnet-3-5-20240620': {
65
- input: 3,
66
- output: 15,
67
- cacheCreation: 3.75,
68
- cacheRead: 0.30
69
- },
70
-
71
- // Claude 3 (legacy)
72
- 'claude-opus-3-20240229': {
73
- input: 15,
74
- output: 75,
75
- cacheCreation: 18.75,
76
- cacheRead: 1.50
77
- },
78
- 'claude-3-opus-20240229': {
79
- input: 15,
80
- output: 75,
81
- cacheCreation: 18.75,
82
- cacheRead: 1.50
10
+ // Build CLAUDE_MODEL_PRICING from centralized metadata (Claude models only)
11
+ const CLAUDE_MODEL_PRICING = {};
12
+ for (const [id, meta] of Object.entries(MODEL_METADATA)) {
13
+ if (id.startsWith('claude-') && meta.pricing) {
14
+ CLAUDE_MODEL_PRICING[id] = {
15
+ input: meta.pricing.input,
16
+ output: meta.pricing.output,
17
+ cacheCreation: meta.pricing.cacheCreation,
18
+ cacheRead: meta.pricing.cacheRead
19
+ };
83
20
  }
84
- };
21
+ }
85
22
 
86
- /**
87
- * Model name aliases for normalization
88
- * Maps short names to full model identifiers
89
- */
90
- const CLAUDE_MODEL_ALIASES = {
91
- 'claude-opus-4-5': 'claude-opus-4-5-20250929',
92
- 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
93
- 'claude-haiku-4-5': 'claude-haiku-4-5-20250929',
94
- 'claude-opus-4': 'claude-opus-4-20250514',
95
- 'claude-sonnet-4': 'claude-sonnet-4-20250514',
96
- 'claude-haiku-3-5': 'claude-haiku-3-5-20241022',
97
- 'claude-sonnet-3-5': 'claude-sonnet-3-5-20241022',
98
- 'claude-opus-3': 'claude-opus-3-20240229'
99
- };
23
+ // Build CLAUDE_MODEL_ALIASES from centralized aliases (Claude models only)
24
+ const CLAUDE_MODEL_ALIASES = {};
25
+ for (const [alias, canonical] of Object.entries(MODEL_ALIASES)) {
26
+ if (alias.startsWith('claude-')) {
27
+ CLAUDE_MODEL_ALIASES[alias] = canonical;
28
+ }
29
+ }
100
30
 
101
31
  module.exports = {
102
32
  CLAUDE_MODEL_PRICING,
103
33
  CLAUDE_MODEL_ALIASES,
104
- PRICING_LAST_UPDATED: '2026-02-02'
34
+ PRICING_LAST_UPDATED: METADATA_LAST_UPDATED
105
35
  };
@@ -18,14 +18,15 @@ const {
18
18
  sanitizeBatchConcurrency,
19
19
  runWithConcurrencyLimit
20
20
  } = require('../services/speed-test');
21
- const {
22
- probeModelAvailability,
23
- fetchModelsFromProvider
24
- } = require('../services/model-detector');
21
+ const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
25
22
  const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
26
23
  const { clearRedirectCache } = require('../proxy-server');
27
24
  const CLAUDE_GATEWAY_SOURCE_TYPE = 'claude';
28
25
 
26
+ function getDefaultClaudeModel() {
27
+ return getDefaultSpeedTestModelByToolType('claude');
28
+ }
29
+
29
30
  // GET /api/channels - Get all channels with health status
30
31
  router.get('/', (req, res) => {
31
32
  try {
@@ -271,41 +272,17 @@ router.get('/:id/models', async (req, res) => {
271
272
  }
272
273
 
273
274
  const gatewaySourceType = CLAUDE_GATEWAY_SOURCE_TYPE;
274
- const listResult = await fetchModelsFromProvider(channel, 'openai_compatible');
275
- const listedModels = Array.isArray(listResult.models) ? listResult.models : [];
276
- let result;
277
-
278
- if (listedModels.length > 0) {
279
- result = {
280
- models: listedModels,
281
- supported: true,
282
- cached: !!listResult.cached,
283
- fallbackUsed: false,
284
- lastChecked: listResult.lastChecked || new Date().toISOString(),
285
- error: null,
286
- errorHint: null
287
- };
288
- } else {
289
- const usingConfiguredProbe = !!listResult.disabledByConfig;
290
- const probe = await probeModelAvailability(channel, gatewaySourceType, {
291
- stopOnFirstAvailable: false
292
- });
293
- const probedModels = Array.isArray(probe.availableModels) ? probe.availableModels : [];
294
-
295
- result = {
296
- models: probedModels,
297
- supported: probedModels.length > 0,
298
- cached: !!probe.cached || !!listResult.cached,
299
- fallbackUsed: probedModels.length > 0,
300
- lastChecked: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
301
- error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
302
- errorHint: probedModels.length > 0
303
- ? (usingConfiguredProbe ? '已按设置跳过 /v1/models,使用默认模型探测结果' : '模型列表接口不可用,已使用模型探测结果')
304
- : (listResult.errorHint || (usingConfiguredProbe
305
- ? '已按设置跳过 /v1/models,且默认模型探测无可用结果'
306
- : '模型列表接口不可用且模型探测无可用结果'))
307
- };
308
- }
275
+ const models = [getDefaultClaudeModel()];
276
+ const now = new Date().toISOString();
277
+ const result = {
278
+ models,
279
+ supported: models.length > 0,
280
+ cached: false,
281
+ fallbackUsed: false,
282
+ lastChecked: now,
283
+ error: models.length > 0 ? null : '未配置默认模型列表',
284
+ errorHint: models.length > 0 ? null : '请在设置中配置 Claude 默认模型'
285
+ };
309
286
 
310
287
  res.json({
311
288
  channelId: id,