coding-tool-x 3.8.4 → 3.8.6

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 (434) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-Db9wAyaZ.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics-C4gMzv7g.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-Bd_nMFu3.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-CwzJHyNP.js +1 -0
  10. package/dist/web/assets/CollapseItem-CTCfSiue.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-vDghB1U9.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-C-XDvlHc.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-CYY8XRED.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-CbsJp76X.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-liMSTh7s.js +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-D6QSiYaL.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-RvET6iNG.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-P8eseZcf.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-CYmLGYju.js +1 -0
  29. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-Ch3bkKIq.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-BcTlgSpN.js +1 -0
  34. package/dist/web/assets/PluginManager-Dh2o0xkZ.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  36. package/dist/web/assets/PluginsDrawer-deKQ5u3I.js +1 -0
  37. package/dist/web/assets/PluginsPanel-BofsgQgj.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-DhSWJHTA.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-Cfb2FeeU.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-3eXamJhr.js +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  45. package/dist/web/assets/SessionCard-CW0eHURI.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-DQfyDGWU.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-Dw5NUgHC.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-u_vwUuPS.js +1 -0
  51. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  52. package/dist/web/assets/SkillManager-DHMprJLA.js +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-CkF3BpyC.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-DWE4pFGx.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-BgfG1Ked.js +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  59. package/dist/web/assets/Upload-CNwaGOws.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-Br5CeCzL.js +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  62. package/dist/web/assets/WorkspaceManager-B2O8-qIw.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-5oDKit3c.js +1 -0
  64. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-BMAUPq5K.js +2 -0
  71. package/dist/web/assets/index-Bi1MQHYX.css +1 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-jbmqZuJs.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +8 -4
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +104 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +165 -0
  140. package/src/platforms/channel-auth-service.js +107 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +98 -418
  269. package/src/{server/services → platforms}/notification-hooks.js +190 -1023
  270. package/src/platforms/oauth-credentials-service.js +635 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +133 -24
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +12 -2
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +876 -214
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { NATIVE_PATHS } = require('../../../config/paths');
7
+ const {
8
+ normalizeCost,
9
+ normalizeUsage,
10
+ usageFieldPaths,
11
+ createSelectiveJsonLineParser,
12
+ readJsonLines,
13
+ walkFiles,
14
+ createScannedFileCursor,
15
+ createIncrementalJsonlCursor
16
+ } = require('../native-log-utils');
17
+ const { calculateUsageCost } = require('../../../server/services/usage-log-utils');
18
+
19
+ const MAX_WHOLE_JSON_BYTES = 16 * 1024 * 1024;
20
+ const MIN_WHOLE_JSON_PARSE_INTERVAL_MS = 15 * 1000;
21
+
22
+ const GEMINI_SELECTED_FIELDS = [
23
+ '$set.sessionId', '$set.provider', '$set.model', '$set.lastUpdated',
24
+ 'id', 'messageId', 'uuid', 'model', 'timestamp', 'createdAt', 'provider', 'cost', 'channelId', 'channel',
25
+ 'tokens', 'usage',
26
+ 'message.id', 'message.messageId', 'message.uuid', 'message.model', 'message.timestamp',
27
+ 'message.createdAt', 'message.provider', 'message.cost', 'message.channelId', 'message.channel',
28
+ 'message.tokens', 'message.usage',
29
+ ...usageFieldPaths('tokens'),
30
+ ...usageFieldPaths('usage'),
31
+ ...usageFieldPaths('message.tokens'),
32
+ ...usageFieldPaths('message.usage')
33
+ ];
34
+
35
+ function parseSession(filePath, fsImpl) {
36
+ let content;
37
+ try { content = fsImpl.readFileSync(filePath, 'utf8').trim(); } catch (_) { return []; }
38
+ if (!content) return [];
39
+ let session;
40
+ try {
41
+ session = JSON.parse(content);
42
+ } catch (_) {
43
+ const records = readJsonLines(filePath, fsImpl);
44
+ const state = {};
45
+ const messages = [];
46
+ for (const record of records) {
47
+ if (record.$set && typeof record.$set === 'object') Object.assign(state, record.$set);
48
+ if (record.type || record.tokens || record.usage) messages.push(record);
49
+ }
50
+ session = { ...state, messages };
51
+ }
52
+ const messages = Array.isArray(session?.messages) ? session.messages : [];
53
+ return messages.flatMap(message => {
54
+ const usage = message.tokens || message.usage;
55
+ if (!usage || typeof usage !== 'object') return [];
56
+ const messageId = message.id || message.messageId || message.uuid || crypto.createHash('sha1')
57
+ .update(JSON.stringify(message))
58
+ .digest('hex');
59
+ return [{
60
+ id: `${path.basename(filePath)}:${messageId}`,
61
+ source: 'gemini',
62
+ sessionId: session.sessionId || path.basename(filePath),
63
+ timestamp: message.timestamp || message.createdAt || session.lastUpdated,
64
+ provider: message.provider || session.provider || '',
65
+ model: message.model || session.model || '',
66
+ tokens: normalizeUsage(usage),
67
+ cost: normalizeCost(message.cost ?? usage.cost)
68
+ || calculateUsageCost('gemini', message.model || session.model || '', normalizeUsage(usage)),
69
+ channelId: message.channelId,
70
+ channel: message.channel
71
+ }];
72
+ });
73
+ }
74
+
75
+ function parseGeminiJsonlLine(filePath, record, state) {
76
+ if (record?.$set && typeof record.$set === 'object') {
77
+ Object.assign(state.metadata, record.$set);
78
+ }
79
+ const message = record?.message && typeof record.message === 'object' ? record.message : record;
80
+ const usage = message?.tokens || message?.usage;
81
+ if (!usage || typeof usage !== 'object') return null;
82
+ const messageId = message.id || message.messageId || message.uuid
83
+ || record?.id || record?.messageId || record?.uuid || `line-${state.entryIndex}`;
84
+ const model = message.model || state.metadata.model || '';
85
+ const sessionId = state.metadata.sessionId || path.basename(filePath);
86
+ return {
87
+ id: `${path.basename(filePath)}:${messageId}`,
88
+ source: 'gemini',
89
+ sessionId,
90
+ timestamp: message.timestamp || message.createdAt || state.metadata.lastUpdated,
91
+ provider: message.provider || state.metadata.provider || '',
92
+ model,
93
+ tokens: normalizeUsage(usage),
94
+ cost: normalizeCost(message.cost ?? usage.cost)
95
+ || calculateUsageCost('gemini', model, normalizeUsage(usage)),
96
+ channelId: message.channelId,
97
+ channel: message.channel
98
+ };
99
+ }
100
+
101
+ function createDriver({ nativeRoot, pathContext, fsImpl = fs } = {}) {
102
+ const resolvedNativeRoot = pathContext?.customized
103
+ ? (pathContext.native?.tmp || nativeRoot || NATIVE_PATHS.gemini.tmp)
104
+ : (nativeRoot || NATIVE_PATHS.gemini.tmp);
105
+ return {
106
+ platform: 'gemini',
107
+ capability: 'nativeLogs',
108
+ createNativeLogCursor({ fs: cursorFs = fsImpl, skipInitialParse = false, onDiagnostic } = {}) {
109
+ const jsonlFiles = () => walkFiles(resolvedNativeRoot, name => /^session-.*\.jsonl$/.test(name), cursorFs);
110
+ const jsonFiles = () => walkFiles(resolvedNativeRoot, name => /^session-.*\.json$/.test(name), cursorFs);
111
+ const jsonlCursor = createIncrementalJsonlCursor({
112
+ scanFiles: jsonlFiles,
113
+ createFileState: () => ({ metadata: {} }),
114
+ parseLine: parseGeminiJsonlLine,
115
+ fsImpl: cursorFs,
116
+ skipInitialParse,
117
+ createLongLineParser: () => createSelectiveJsonLineParser(GEMINI_SELECTED_FIELDS),
118
+ onDiagnostic,
119
+ onError: (error, filePath) => {
120
+ console.warn('[Gemini Native Logs] Failed to read changed JSONL events:', filePath, error.message);
121
+ }
122
+ });
123
+ let lastWholeJsonParse = new Map();
124
+ let oversizeWarned = new Set();
125
+ let initializedWholeJsonFiles = new Set();
126
+ const jsonCursor = createScannedFileCursor({
127
+ scanFiles: jsonFiles,
128
+ parseFile: filePath => {
129
+ return parseSession(filePath, cursorFs);
130
+ },
131
+ shouldParseFile: (filePath, stat, { isInitialRead, skipInitialParse }) => {
132
+ if (stat.size > MAX_WHOLE_JSON_BYTES) {
133
+ if (!oversizeWarned.has(filePath)) {
134
+ oversizeWarned.add(filePath);
135
+ console.warn(`[Gemini Native Logs] Skipping oversized JSON session (${stat.size} bytes): ${filePath}`);
136
+ }
137
+ return false;
138
+ }
139
+ if (skipInitialParse) {
140
+ return true;
141
+ }
142
+ if (isInitialRead || !initializedWholeJsonFiles.has(filePath)) {
143
+ initializedWholeJsonFiles.add(filePath);
144
+ lastWholeJsonParse.set(filePath, Date.now());
145
+ return true;
146
+ }
147
+ const now = Date.now();
148
+ const allowed = now - (lastWholeJsonParse.get(filePath) || 0) >= MIN_WHOLE_JSON_PARSE_INTERVAL_MS;
149
+ if (allowed) lastWholeJsonParse.set(filePath, now);
150
+ return allowed;
151
+ },
152
+ normalizeEvent: event => event,
153
+ fsImpl: cursorFs,
154
+ skipInitialParse
155
+ });
156
+ return {
157
+ initialize() { jsonlCursor.initialize(); jsonCursor.initialize(); },
158
+ readNewEvents() { return [...jsonlCursor.readNewEvents(), ...jsonCursor.readNewEvents()]; },
159
+ read() { return this.readNewEvents(); },
160
+ reset() {
161
+ jsonlCursor.reset();
162
+ jsonCursor.reset();
163
+ lastWholeJsonParse = new Map();
164
+ oversizeWarned = new Set();
165
+ initializedWholeJsonFiles = new Set();
166
+ },
167
+ close() {
168
+ jsonlCursor.close();
169
+ jsonCursor.close();
170
+ lastWholeJsonParse.clear();
171
+ oversizeWarned.clear();
172
+ initializedWholeJsonFiles.clear();
173
+ }
174
+ };
175
+ },
176
+ createCursor(options) { return this.createNativeLogCursor(options); }
177
+ };
178
+ }
179
+
180
+ module.exports = { createDriver };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const mcpFormat = require('../../../shared/mcp-format');
4
+ const {
5
+ createProjectAdapter,
6
+ createJsonMcpHandlers
7
+ } = require('../../../shared/project-config');
8
+
9
+ function createAdapter({ manifest, fsImpl } = {}) {
10
+ const relativePath = manifest?.projectResources?.mcp?.path;
11
+ const mcpHandlers = createJsonMcpHandlers({
12
+ relativePath,
13
+ format: manifest?.projectResources?.mcp?.format || 'gemini-json',
14
+ toNative: mcpFormat.extractServerSpec,
15
+ fromNative: value => ({ ...value }),
16
+ fsImpl
17
+ });
18
+ return createProjectAdapter({ manifest, fsImpl, mcpHandlers });
19
+ }
20
+
21
+ module.exports = { createAdapter };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { createProjectsDriver } = require('../../../shared/driver-factories/projects');
4
+
5
+ function createDriver(context = {}) {
6
+ return createProjectsDriver({
7
+ ...context,
8
+ platform: 'gemini',
9
+ servicePath: './gemini/sessions-implementation',
10
+ localServicePath: '../../platforms/drivers/gemini/sessions-implementation',
11
+ availabilityCheck: () => {
12
+ const config = context.requireImpl
13
+ ? context.requireImpl('./gemini/config')
14
+ : require('./config');
15
+ config.configure?.({ pathContext: context.pathContext });
16
+ return config.isGeminiInstalled();
17
+ },
18
+ onSuccess: operation => {
19
+ if (['saveProjectOrder', 'createProject', 'deleteProject'].includes(operation)) {
20
+ context.sessionHistoryIndex?.invalidateSource('gemini');
21
+ }
22
+ }
23
+ });
24
+ }
25
+
26
+ module.exports = { createDriver };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const { createPromptDriver } = require('../../../shared/driver-factories/prompts');
4
+
5
+ function createDriver(context = {}) {
6
+ return createPromptDriver({ ...context, platform: 'gemini' });
7
+ }
8
+
9
+ module.exports = { createDriver };
@@ -2,28 +2,18 @@ const express = require('express');
2
2
  const httpProxy = require('http-proxy');
3
3
  const http = require('http');
4
4
  const chalk = require('chalk');
5
- const { broadcastLog, broadcastSchedulerState } = require('./websocket-server');
6
- const { allocateChannel, releaseChannel, getSchedulerState } = require('./services/channel-scheduler');
7
- const { recordSuccess, recordFailure } = require('./services/channel-health');
8
- const { loadConfig } = require('../config/loader');
9
- const DEFAULT_CONFIG = require('../config/default');
10
- const { resolveModelPricing, calculateTokenCost } = require('./utils/pricing');
11
- const { recordRequest: recordGeminiRequest } = require('./services/gemini-statistics-service');
12
- const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
13
- const { createDecodedStream } = require('./services/response-decoder');
14
- const { getEffectiveApiKey } = require('./services/gemini-channels');
15
- const { persistProxyRequestSnapshot } = require('./services/request-logger');
16
- const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
17
- const { redirectModel: redirectModelBase, resolveTargetUrl } = require('./services/base/proxy-utils');
5
+ const { broadcastSchedulerState } = require('../../../server/websocket-server');
6
+ const { allocateChannel, releaseChannel, getSchedulerState } = require('../../../server/services/channel-scheduler');
7
+ const { recordSuccess, recordFailure } = require('../../../server/services/channel-health');
8
+ const { loadConfig } = require('../../../config/loader');
9
+ const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('../../../server/services/proxy-runtime');
18
10
  const {
19
- parseSSEUsage,
20
- parseNonStreamingUsage,
21
- splitSSEEvents,
22
- parseSSEEventText,
23
- mergeUsageIntoTokenData,
24
- createTokenData
25
- } = require('./services/base/response-usage-parser');
26
- const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
11
+ getEffectiveApiKey,
12
+ getGeminiProxyExcludedChannelIds = () => []
13
+ } = require('./channels-implementation');
14
+ const { persistProxyRequestSnapshot } = require('../../../server/services/request-logger');
15
+ const { redirectModel: redirectModelBase, resolveTargetUrl } = require('../../../shared/proxy-utils');
16
+ const { attachServerShutdownHandling, expediteServerShutdown } = require('../../../server/services/server-shutdown');
27
17
 
28
18
  let proxyServer = null;
29
19
  let proxyApp = null;
@@ -36,24 +26,7 @@ const requestMetadata = new Map();
36
26
  // 格式: { channelId: { "originalModel": "redirectedModel", ... } }
37
27
  const printedGeminiRedirectCache = new Map();
38
28
 
39
- // Gemini 模型定价(每百万 tokens 的价格,单位:美元)
40
- // 作为 model-metadata 未覆盖时的兜底值
41
- const PRICING = {
42
- 'gemini-2.5-pro': { input: 1.25, output: 10, cacheRead: 0.125 },
43
- 'gemini-2.5-flash': { input: 0.3, output: 2.5, cacheRead: 0.03 },
44
- 'gemini-2.5-flash-lite': { input: 0.1, output: 0.4, cacheRead: 0.01 },
45
- 'gemini-2.0-flash-exp': { input: 0, output: 0 }, // 实验性免费
46
- 'gemini-2.0-flash-thinking-exp-1219': { input: 0, output: 0 }, // 实验性免费
47
- 'gemini-1.5-pro': { input: 1.25, output: 5 },
48
- 'gemini-1.5-flash': { input: 0.075, output: 0.3 },
49
- 'gemini-1.5-flash-8b': { input: 0.0375, output: 0.15 },
50
- 'gemini-1.0-pro': { input: 0.5, output: 1.5 },
51
- // 旧版本别名
52
- 'gemini-pro': { input: 0.5, output: 1.5 },
53
- 'gemini-pro-vision': { input: 0.5, output: 1.5 }
54
- };
55
29
 
56
- const GEMINI_BASE_PRICING = DEFAULT_CONFIG.pricing.gemini;
57
30
  const jsonBodyParser = express.json({
58
31
  limit: '100mb',
59
32
  verify: (req, res, buf) => {
@@ -192,43 +165,6 @@ function isHttpErrorStatus(statusCode) {
192
165
  return Number.isFinite(code) && (code < 200 || code >= 300);
193
166
  }
194
167
 
195
- /**
196
- * 计算请求成本
197
- */
198
- function calculateCost(model, tokens) {
199
- // 尝试精确匹配
200
- let fallbackPricing = PRICING[model];
201
-
202
- // 如果没有精确匹配,尝试模糊匹配
203
- if (!fallbackPricing) {
204
- const modelLower = String(model || '').toLowerCase();
205
- if (modelLower.includes('gemini-2.5-pro')) {
206
- fallbackPricing = PRICING['gemini-2.5-pro'];
207
- } else if (modelLower.includes('gemini-2.5-flash-lite')) {
208
- fallbackPricing = PRICING['gemini-2.5-flash-lite'];
209
- } else if (modelLower.includes('gemini-2.5-flash')) {
210
- fallbackPricing = PRICING['gemini-2.5-flash'];
211
- } else if (modelLower.includes('gemini-2.0-flash-thinking')) {
212
- fallbackPricing = PRICING['gemini-2.0-flash-thinking-exp-1219'];
213
- } else if (modelLower.includes('gemini-2.0-flash')) {
214
- fallbackPricing = PRICING['gemini-2.0-flash-exp'];
215
- } else if (modelLower.includes('gemini-1.5-pro')) {
216
- fallbackPricing = PRICING['gemini-1.5-pro'];
217
- } else if (modelLower.includes('gemini-1.5-flash-8b')) {
218
- fallbackPricing = PRICING['gemini-1.5-flash-8b'];
219
- } else if (modelLower.includes('gemini-1.5-flash')) {
220
- fallbackPricing = PRICING['gemini-1.5-flash'];
221
- } else if (modelLower.includes('gemini-1.0-pro')) {
222
- fallbackPricing = PRICING['gemini-1.0-pro'];
223
- } else if (modelLower.includes('gemini-pro')) {
224
- fallbackPricing = PRICING['gemini-pro'];
225
- }
226
- }
227
-
228
- const pricing = resolveModelPricing('gemini', model, fallbackPricing, GEMINI_BASE_PRICING);
229
- return calculateTokenCost(pricing, tokens, GEMINI_BASE_PRICING);
230
- }
231
-
232
168
  // 启动 Gemini 代理服务器
233
169
  async function startGeminiProxyServer(options = {}) {
234
170
  // options.preserveStartTime - 是否保留现有的启动时间(用于切换渠道时)
@@ -238,6 +174,13 @@ async function startGeminiProxyServer(options = {}) {
238
174
  console.log('Gemini proxy server already running on port', currentPort);
239
175
  return { success: true, port: currentPort };
240
176
  }
177
+ const excludedChannelIds = getGeminiProxyExcludedChannelIds();
178
+ if (excludedChannelIds.length > 0) {
179
+ const error = new Error('Gemini dynamic proxy supports API-key channels only; disable OAuth channels first');
180
+ error.code = 'gemini_oauth_proxy_unsupported';
181
+ error.statusCode = 409;
182
+ throw error;
183
+ }
241
184
 
242
185
  try {
243
186
  const config = loadConfig();
@@ -295,7 +238,11 @@ async function startGeminiProxyServer(options = {}) {
295
238
 
296
239
  proxyApp.use(async (req, res) => {
297
240
  try {
298
- const channel = await allocateChannel({ source: 'gemini', enableSessionBinding: false });
241
+ const channel = await allocateChannel({
242
+ source: 'gemini',
243
+ enableSessionBinding: false,
244
+ excludeChannelIds: getGeminiProxyExcludedChannelIds()
245
+ });
299
246
  req.selectedChannel = channel;
300
247
 
301
248
  const release = (() => {
@@ -316,14 +263,6 @@ async function startGeminiProxyServer(options = {}) {
316
263
  const effectiveKey = getEffectiveApiKey(channel);
317
264
  if (!effectiveKey) {
318
265
  release();
319
- publishFailureLog({
320
- source: 'gemini',
321
- channel: channel.name,
322
- message: 'API key not configured or expired. Please update your channel key.',
323
- statusCode: 401,
324
- stage: 'preflight',
325
- broadcastLog
326
- });
327
266
  return res.status(401).json({
328
267
  error: {
329
268
  message: 'API key not configured or expired. Please update your channel key.',
@@ -402,20 +341,6 @@ async function startGeminiProxyServer(options = {}) {
402
341
  release();
403
342
  if (err) {
404
343
  recordFailure(channel.id, 'gemini', err);
405
- const metadata = requestMetadata.get(req) || {
406
- channel: channel.name,
407
- channelId: channel.id,
408
- startTime: Date.now()
409
- };
410
- publishFailureLog({
411
- source: 'gemini',
412
- metadata,
413
- message: err.message,
414
- error: err,
415
- statusCode: 502,
416
- stage: 'proxy_web',
417
- broadcastLog
418
- });
419
344
  console.error('Gemini proxy error:', err);
420
345
  if (res && !res.headersSent) {
421
346
  res.status(502).json({
@@ -429,13 +354,6 @@ async function startGeminiProxyServer(options = {}) {
429
354
  });
430
355
  } catch (error) {
431
356
  console.error('Gemini channel allocation error:', error);
432
- publishFailureLog({
433
- source: 'gemini',
434
- message: error.message || 'No Gemini channel available',
435
- statusCode: 503,
436
- stage: 'allocate_channel',
437
- broadcastLog
438
- });
439
357
  if (!res.headersSent) {
440
358
  res.status(503).json({
441
359
  error: {
@@ -447,170 +365,35 @@ async function startGeminiProxyServer(options = {}) {
447
365
  }
448
366
  });
449
367
 
450
- // 监听代理响应 (OpenAI 兼容格式)
368
+ // 代理响应只负责维护传输健康状态;实时日志和 usage 来自 CLI 原生日志。
451
369
  proxy.on('proxyRes', (proxyRes, req, res) => {
452
370
  const metadata = requestMetadata.get(req);
453
- if (!metadata) {
454
- return;
455
- }
371
+ if (!metadata) return;
456
372
 
457
- // 检查响应是否已关闭
458
373
  if (res.writableEnded || res.destroyed) {
459
374
  requestMetadata.delete(req);
460
375
  return;
461
376
  }
462
377
 
463
- // 标记响应是否已关闭
464
- let isResponseClosed = false;
465
-
466
- // 监听响应关闭事件
467
- res.on('close', () => {
468
- isResponseClosed = true;
469
- requestMetadata.delete(req);
470
- });
471
-
472
- // 监听响应错误事件
378
+ res.on('close', () => requestMetadata.delete(req));
473
379
  res.on('error', (err) => {
474
- isResponseClosed = true;
475
- // 忽略客户端断开连接的常见错误
476
- if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
477
- console.error('Response error:', err);
478
- }
380
+ if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') console.error('Response error:', err);
381
+ recordFailure(metadata.channelId, 'gemini', err);
479
382
  requestMetadata.delete(req);
480
383
  });
481
384
 
482
- let buffer = '';
483
- let tokenData = createTokenData();
484
- let usageRecorded = false;
485
385
  const upstreamStatusCode = Number(proxyRes.statusCode) || 200;
486
- const isUpstreamError = isHttpErrorStatus(upstreamStatusCode);
487
- const parsedStream = createDecodedStream(proxyRes);
488
-
489
- function recordUsageIfReady() {
490
- if (usageRecorded) {
491
- return false;
492
- }
493
-
494
- if (!tokenData.model && metadata.modelFromUrl) {
495
- tokenData.model = metadata.modelFromUrl;
496
- }
497
-
498
- const result = publishUsageLog({
499
- source: 'gemini',
500
- metadata,
501
- model: tokenData.model,
502
- tokens: {
503
- input: tokenData.inputTokens,
504
- output: tokenData.outputTokens,
505
- cacheCreation: tokenData.cacheCreation,
506
- cacheRead: tokenData.cacheRead,
507
- cached: tokenData.cachedTokens,
508
- reasoning: tokenData.reasoningTokens,
509
- total: tokenData.totalTokens
510
- },
511
- calculateCost,
512
- broadcastLog,
513
- recordRequest: recordGeminiRequest,
514
- recordSuccess,
515
- allowBroadcast: true
516
- });
517
-
518
- if (!result) {
519
- return false;
520
- }
521
-
522
- usageRecorded = true;
523
- return true;
524
- }
525
-
526
- parsedStream.on('data', (chunk) => {
527
- if (isResponseClosed) {
528
- return;
529
- }
530
-
531
- buffer += chunk.toString('utf8');
532
-
533
- // 检查是否是 SSE 流
534
- if (!isUpstreamError && proxyRes.headers['content-type']?.includes('text/event-stream')) {
535
- const parsedEvents = splitSSEEvents(buffer);
536
- buffer = parsedEvents.remainder;
537
-
538
- parsedEvents.events.forEach((eventText) => {
539
- if (!eventText.trim()) return;
540
-
541
- try {
542
- const event = parseSSEEventText(eventText);
543
- if (!event) return;
544
-
545
- const parsed = JSON.parse(event.data);
546
- const usage = parseSSEUsage(parsed, event.eventType);
547
- mergeUsageIntoTokenData(tokenData, usage);
548
-
549
- if (usage.isDone) {
550
- recordUsageIfReady();
551
- }
552
- } catch (err) {
553
- // 忽略解析错误
554
- }
555
- });
556
- }
557
- });
558
-
559
- parsedStream.on('end', () => {
560
- if (isUpstreamError) {
561
- const message = extractGeminiUpstreamErrorMessage(buffer, upstreamStatusCode);
562
- const error = new Error(buffer.trim() || message);
563
- recordFailure(metadata.channelId, 'gemini', error);
564
- publishFailureLog({
565
- source: 'gemini',
566
- metadata,
567
- message,
568
- error,
569
- statusCode: upstreamStatusCode,
570
- stage: 'upstream_response',
571
- broadcastLog
572
- });
573
-
574
- if (!isResponseClosed) {
575
- requestMetadata.delete(req);
576
- }
577
- return;
578
- }
579
-
580
- // 如果不是流式响应,尝试从完整响应中解析
581
- if (!proxyRes.headers['content-type']?.includes('text/event-stream')) {
582
- try {
583
- const parsed = JSON.parse(buffer);
584
- const usage = parseNonStreamingUsage(parsed);
585
- mergeUsageIntoTokenData(tokenData, usage);
586
- } catch (err) {
587
- // 忽略解析错误
588
- }
589
- }
590
-
591
- recordUsageIfReady();
592
-
593
- if (!isResponseClosed) {
594
- requestMetadata.delete(req);
386
+ proxyRes.on('end', () => {
387
+ if (isHttpErrorStatus(upstreamStatusCode)) {
388
+ recordFailure(metadata.channelId, 'gemini', new Error(`Gemini upstream HTTP ${upstreamStatusCode}`));
389
+ } else {
390
+ recordSuccess(metadata.channelId, 'gemini');
595
391
  }
392
+ requestMetadata.delete(req);
596
393
  });
597
-
598
- parsedStream.on('error', (err) => {
599
- // 忽略代理响应错误(可能是网络问题)
600
- if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
601
- console.error('Proxy response error:', err);
602
- }
603
- isResponseClosed = true;
394
+ proxyRes.on('error', (err) => {
395
+ if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') console.error('Proxy response error:', err);
604
396
  recordFailure(metadata.channelId, 'gemini', err);
605
- publishFailureLog({
606
- source: 'gemini',
607
- metadata,
608
- message: err.message,
609
- error: err,
610
- statusCode: proxyRes.statusCode,
611
- stage: 'response_stream',
612
- broadcastLog
613
- });
614
397
  requestMetadata.delete(req);
615
398
  });
616
399
  });
@@ -623,19 +406,6 @@ async function startGeminiProxyServer(options = {}) {
623
406
  releaseChannel(req.selectedChannel.id, 'gemini');
624
407
  broadcastSchedulerState('gemini', getSchedulerState('gemini'));
625
408
  }
626
- publishFailureLog({
627
- source: 'gemini',
628
- metadata: (req && requestMetadata.get(req)) || {
629
- channel: req?.selectedChannel?.name,
630
- channelId: req?.selectedChannel?.id,
631
- model: req?.body?.model
632
- },
633
- message: err.message,
634
- error: err,
635
- statusCode: 502,
636
- stage: 'proxy',
637
- broadcastLog
638
- });
639
409
  if (res && !res.headersSent) {
640
410
  res.status(502).json({
641
411
  error: {
@@ -746,7 +516,6 @@ module.exports = {
746
516
  stopGeminiProxyServer,
747
517
  getGeminiProxyStatus,
748
518
  clearGeminiRedirectCache,
749
- calculateCost,
750
519
  _test: {
751
520
  buildVertexAiV1Path,
752
521
  extractGeminiUpstreamErrorMessage,
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { createProxyDriver } = require('../../../shared/proxy-driver');
4
+
5
+ function createDriver(context = {}) {
6
+ return createProxyDriver({
7
+ ...context,
8
+ platform: 'gemini',
9
+ servicePath: './gemini/proxy-implementation',
10
+ localServicePath: '../platforms/drivers/gemini/proxy-implementation',
11
+ exports: { status: 'getGeminiProxyStatus', start: 'startGeminiProxyServer', stop: 'stopGeminiProxyServer' },
12
+ cliMetadata: { defaultPort: 20090 }
13
+ });
14
+ }
15
+
16
+ module.exports = { createDriver };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const { createResourceSyncDriver } = require('../../../shared/driver-factories/resource-sync');
4
+
5
+ function createDriver(context = {}) {
6
+ return createResourceSyncDriver({ ...context, platform: 'gemini' });
7
+ }
8
+
9
+ module.exports = { createDriver };