@xopcai/xopc 0.0.87 → 0.0.89

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 (282) hide show
  1. package/README.md +8 -1
  2. package/README.zh-CN.md +8 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/telegram/xopc.extension.json +1 -1
  5. package/dist/gateway/static/root/assets/agents-B6PJB07W.js +222 -0
  6. package/dist/gateway/static/root/assets/apps-page-BOr0B1wv.js +1 -0
  7. package/dist/gateway/static/root/assets/channels-settings-BelUKggl.js +1 -0
  8. package/dist/gateway/static/root/assets/{channels-status-swr-BSHqqCF1.js → channels-status-swr-DaHGkRF1.js} +1 -1
  9. package/dist/gateway/static/root/assets/cron-api-CjOg-BIj.js +1 -0
  10. package/dist/gateway/static/root/assets/cron-page-DhoZmZXb.js +1 -0
  11. package/dist/gateway/static/root/assets/{dist-Cmjp2APP.js → dist-6LecgDx5.js} +1 -1
  12. package/dist/gateway/static/root/assets/{extension-debug-page-CFa9z_1N.js → extension-debug-page-CtuKJ9tE.js} +1 -1
  13. package/dist/gateway/static/root/assets/{extension-page-BI8eaTPq.js → extension-page-ykzjOkR5.js} +1 -1
  14. package/dist/gateway/static/root/assets/extension-settings-page-Ce2qrdpO.js +1 -0
  15. package/dist/gateway/static/root/assets/{fetch-DRqwef_Q.js → fetch-C9FFJjuH.js} +1 -1
  16. package/dist/gateway/static/root/assets/{field-primitives-BiNHBo2Y.js → field-primitives-BFcrNeTU.js} +1 -1
  17. package/dist/gateway/static/root/assets/{heartbeat-config-api-ZRb8qhuz.js → heartbeat-config-api-CEg4Vr9R.js} +1 -1
  18. package/dist/gateway/static/root/assets/{index-Cu7bKuUi.js → index-CZfy9oxs.js} +85 -85
  19. package/dist/gateway/static/root/assets/index-CiN1cQiQ.css +1 -0
  20. package/dist/gateway/static/root/assets/logs-page-BwWLfqvd.js +1 -0
  21. package/dist/gateway/static/root/assets/sessions-page-DV5WN8uk.js +1 -0
  22. package/dist/gateway/static/root/assets/{settings-form-section-DiqqVs6m.js → settings-form-section-BqdzA28u.js} +1 -1
  23. package/dist/gateway/static/root/assets/settings-page-CfOBRbPX.js +3 -0
  24. package/dist/gateway/static/root/assets/{share-preview-page-n1Gprylk.js → share-preview-page-Di5Bzh4g.js} +1 -1
  25. package/dist/gateway/static/root/assets/skills-page-D0H5Kaxg.js +2 -0
  26. package/dist/gateway/static/root/assets/{theme-store-CZOh1nT3.js → theme-store-CNqbmTNV.js} +1 -1
  27. package/dist/gateway/static/root/assets/url-aYn-Rj1C.js +7 -0
  28. package/dist/gateway/static/root/assets/{utils-CkWBfxs4.js → utils-BWm2tG2w.js} +1 -1
  29. package/dist/gateway/static/root/assets/voice-api-key-field-X2UfnHeq.js +1 -0
  30. package/dist/gateway/static/root/assets/workflows-page-BOPpO3NG.js +27 -0
  31. package/dist/gateway/static/root/index.html +5 -5
  32. package/dist/package.js +1 -1
  33. package/dist/src/agent/agent-manager.d.ts +2 -0
  34. package/dist/src/agent/agent-manager.js +1 -0
  35. package/dist/src/agent/agent-manager.js.map +1 -1
  36. package/dist/src/agent/child-agent-factory.d.ts +15 -0
  37. package/dist/src/agent/child-agent-factory.js +35 -2
  38. package/dist/src/agent/child-agent-factory.js.map +1 -1
  39. package/dist/src/agent/client-error-format.d.ts +20 -0
  40. package/dist/src/agent/client-error-format.js +97 -0
  41. package/dist/src/agent/client-error-format.js.map +1 -0
  42. package/dist/src/agent/embedded/run-turn.js +23 -4
  43. package/dist/src/agent/embedded/run-turn.js.map +1 -1
  44. package/dist/src/agent/goals/goal-locale.d.ts +1 -1
  45. package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
  46. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  47. package/dist/src/agent/orchestration/llm-turn-retry.d.ts +2 -0
  48. package/dist/src/agent/orchestration/llm-turn-retry.js +9 -1
  49. package/dist/src/agent/orchestration/llm-turn-retry.js.map +1 -1
  50. package/dist/src/agent/service/process-direct-streaming.js +19 -3
  51. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  52. package/dist/src/agent/service/webchat-tts.d.ts +1 -2
  53. package/dist/src/agent/service/webchat-tts.js +1 -1
  54. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  55. package/dist/src/agent/service.js +2 -1
  56. package/dist/src/agent/service.js.map +1 -1
  57. package/dist/src/agent/service.types.d.ts +3 -1
  58. package/dist/src/agent/tools/cronjob-tool.js +2 -1
  59. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  60. package/dist/src/agent/tools/factory.d.ts +3 -0
  61. package/dist/src/agent/tools/factory.js +2 -23
  62. package/dist/src/agent/tools/factory.js.map +1 -1
  63. package/dist/src/agent/tools/workflow-tool.d.ts +6 -28
  64. package/dist/src/agent/tools/workflow-tool.js +61 -213
  65. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  66. package/dist/src/agent/workflow/agent-progress.d.ts +5 -0
  67. package/dist/src/agent/workflow/agent-progress.js +65 -0
  68. package/dist/src/agent/workflow/agent-progress.js.map +1 -0
  69. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
  70. package/dist/src/agent/workflow/builtins/audit-repo.js +14 -0
  71. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  72. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  73. package/dist/src/agent/workflow/builtins/debug-incident.js +14 -0
  74. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  75. package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +12 -0
  76. package/dist/src/agent/workflow/builtins/implementation-plan.js +175 -0
  77. package/dist/src/agent/workflow/builtins/implementation-plan.js.map +1 -0
  78. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  79. package/dist/src/agent/workflow/builtins/index.js +11 -1
  80. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  81. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +1 -1
  82. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +14 -0
  83. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  84. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  85. package/dist/src/agent/workflow/builtins/pr-review.js +14 -0
  86. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  87. package/dist/src/agent/workflow/builtins/release-check.d.ts +11 -0
  88. package/dist/src/agent/workflow/builtins/release-check.js +165 -0
  89. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -0
  90. package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
  91. package/dist/src/agent/workflow/builtins/research.js +14 -0
  92. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  93. package/dist/src/agent/workflow/index.d.ts +2 -1
  94. package/dist/src/agent/workflow/index.js +3 -2
  95. package/dist/src/agent/workflow/meta-locale.d.ts +12 -0
  96. package/dist/src/agent/workflow/meta-locale.js +62 -0
  97. package/dist/src/agent/workflow/meta-locale.js.map +1 -0
  98. package/dist/src/agent/workflow/parser.js +3 -0
  99. package/dist/src/agent/workflow/parser.js.map +1 -1
  100. package/dist/src/agent/workflow/runtime.d.ts +2 -2
  101. package/dist/src/agent/workflow/runtime.js +21 -14
  102. package/dist/src/agent/workflow/runtime.js.map +1 -1
  103. package/dist/src/agent/workflow/snapshot.js +2 -12
  104. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  105. package/dist/src/agent/workflow/step-labels.d.ts +8 -0
  106. package/dist/src/agent/workflow/step-labels.js +48 -0
  107. package/dist/src/agent/workflow/step-labels.js.map +1 -0
  108. package/dist/src/agent/workflow/subagent-runner.js +46 -1
  109. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  110. package/dist/src/agent/workflow/types.d.ts +74 -1
  111. package/dist/src/agent/workflow/workflow-child-tools.d.ts +4 -0
  112. package/dist/src/agent/workflow/workflow-child-tools.js +21 -0
  113. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -0
  114. package/dist/src/auth/credentials.d.ts +19 -2
  115. package/dist/src/auth/credentials.js +47 -13
  116. package/dist/src/auth/credentials.js.map +1 -1
  117. package/dist/src/auth/oauth/types.d.ts +16 -0
  118. package/dist/src/cli/commands/auth.js +6 -0
  119. package/dist/src/cli/commands/auth.js.map +1 -1
  120. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  121. package/dist/src/cli/commands/onboard/model.js +6 -0
  122. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  123. package/dist/src/config/agent-typed-models.d.ts +18 -0
  124. package/dist/src/config/agent-typed-models.js +53 -0
  125. package/dist/src/config/agent-typed-models.js.map +1 -0
  126. package/dist/src/config/index.js +2 -2
  127. package/dist/src/config/schema.d.ts +52 -0
  128. package/dist/src/config/schema.js +39 -3
  129. package/dist/src/config/schema.js.map +1 -1
  130. package/dist/src/config/voice.d.ts +3 -28
  131. package/dist/src/config/voice.js +27 -261
  132. package/dist/src/config/voice.js.map +1 -1
  133. package/dist/src/cron/executor.d.ts +2 -0
  134. package/dist/src/cron/executor.js +59 -5
  135. package/dist/src/cron/executor.js.map +1 -1
  136. package/dist/src/cron/job-content.js +2 -1
  137. package/dist/src/cron/job-content.js.map +1 -1
  138. package/dist/src/cron/types.d.ts +21 -1
  139. package/dist/src/cron/validation.d.ts +76 -0
  140. package/dist/src/cron/validation.js +26 -1
  141. package/dist/src/cron/validation.js.map +1 -1
  142. package/dist/src/gateway/agents-admin.d.ts +9 -0
  143. package/dist/src/gateway/agents-admin.js +16 -0
  144. package/dist/src/gateway/agents-admin.js.map +1 -1
  145. package/dist/src/gateway/config-tools-web.js +3 -2
  146. package/dist/src/gateway/config-tools-web.js.map +1 -1
  147. package/dist/src/gateway/gateway-workflow-host.types.d.ts +17 -0
  148. package/dist/src/gateway/gateway-workflow-host.types.js +1 -0
  149. package/dist/src/gateway/hono/lib/agent-model.d.ts +7 -0
  150. package/dist/src/gateway/hono/lib/agent-model.js +36 -1
  151. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  152. package/dist/src/gateway/hono/lib/config-payload.js +28 -5
  153. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  154. package/dist/src/gateway/hono/lib/mask-secret-length.d.ts +6 -0
  155. package/dist/src/gateway/hono/lib/mask-secret-length.js +16 -0
  156. package/dist/src/gateway/hono/lib/mask-secret-length.js.map +1 -0
  157. package/dist/src/gateway/hono/lib/safe-providers-config.d.ts +1 -1
  158. package/dist/src/gateway/hono/lib/safe-providers-config.js +2 -1
  159. package/dist/src/gateway/hono/lib/safe-providers-config.js.map +1 -1
  160. package/dist/src/gateway/hono/lib/safe-voice-config.js +2 -1
  161. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  162. package/dist/src/gateway/hono/oauth-async.js +40 -15
  163. package/dist/src/gateway/hono/oauth-async.js.map +1 -1
  164. package/dist/src/gateway/hono/oauth.js +31 -6
  165. package/dist/src/gateway/hono/oauth.js.map +1 -1
  166. package/dist/src/gateway/hono/routes/agents.js +1 -1
  167. package/dist/src/gateway/hono/routes/config-patch/agents.js +8 -2
  168. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  169. package/dist/src/gateway/hono/routes/config-patch/gateway.js +3 -2
  170. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  171. package/dist/src/gateway/hono/routes/config-patch/misc.js +7 -2
  172. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  173. package/dist/src/gateway/hono/routes/config.js +59 -0
  174. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  175. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  176. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  177. package/dist/src/gateway/hono/routes/models.js +84 -15
  178. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  179. package/dist/src/gateway/hono/routes/voice.js +75 -0
  180. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  181. package/dist/src/gateway/hono/routes/workflows.d.ts +3 -0
  182. package/dist/src/gateway/hono/routes/workflows.js +226 -0
  183. package/dist/src/gateway/hono/routes/workflows.js.map +1 -0
  184. package/dist/src/gateway/service/run-gateway-agent.js +2 -20
  185. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  186. package/dist/src/gateway/service.d.ts +8 -0
  187. package/dist/src/gateway/service.js +28 -2
  188. package/dist/src/gateway/service.js.map +1 -1
  189. package/dist/src/mcp/channel-bridge.js +1 -1
  190. package/dist/src/providers/index.d.ts +8 -0
  191. package/dist/src/providers/index.js +51 -12
  192. package/dist/src/providers/index.js.map +1 -1
  193. package/dist/src/share/site-share-config.d.ts +3 -2
  194. package/dist/src/share/site-share-config.js.map +1 -1
  195. package/dist/src/tui/tui-agent-events.js +2 -1
  196. package/dist/src/tui/tui-agent-events.js.map +1 -1
  197. package/dist/src/voice/metadata/builtin.d.ts +2 -0
  198. package/dist/src/voice/metadata/builtin.js +420 -0
  199. package/dist/src/voice/metadata/builtin.js.map +1 -0
  200. package/dist/src/voice/metadata/index.d.ts +4 -0
  201. package/dist/src/voice/metadata/index.js +3 -0
  202. package/dist/src/voice/metadata/registry.d.ts +5 -0
  203. package/dist/src/voice/metadata/registry.js +34 -0
  204. package/dist/src/voice/metadata/registry.js.map +1 -0
  205. package/dist/src/voice/metadata/types.d.ts +41 -0
  206. package/dist/src/voice/metadata/types.js +1 -0
  207. package/dist/src/voice/stt/list-providers.d.ts +3 -3
  208. package/dist/src/voice/stt/list-providers.js +41 -6
  209. package/dist/src/voice/stt/list-providers.js.map +1 -1
  210. package/dist/src/voice/tts/list-providers.d.ts +3 -3
  211. package/dist/src/voice/tts/list-providers.js +41 -6
  212. package/dist/src/voice/tts/list-providers.js.map +1 -1
  213. package/dist/src/workflows/domain/command.d.ts +19 -0
  214. package/dist/src/workflows/domain/command.js +1 -0
  215. package/dist/src/workflows/domain/definition-utils.d.ts +14 -0
  216. package/dist/src/workflows/domain/definition-utils.js +50 -0
  217. package/dist/src/workflows/domain/definition-utils.js.map +1 -0
  218. package/dist/src/workflows/domain/definition.d.ts +62 -0
  219. package/dist/src/workflows/domain/definition.js +1 -0
  220. package/dist/src/workflows/domain/event.d.ts +67 -0
  221. package/dist/src/workflows/domain/event.js +1 -0
  222. package/dist/src/workflows/domain/index.d.ts +7 -0
  223. package/dist/src/workflows/domain/index.js +4 -0
  224. package/dist/src/workflows/domain/result.d.ts +65 -0
  225. package/dist/src/workflows/domain/result.js +1 -0
  226. package/dist/src/workflows/domain/run.d.ts +177 -0
  227. package/dist/src/workflows/domain/run.js +14 -0
  228. package/dist/src/workflows/domain/run.js.map +1 -0
  229. package/dist/src/workflows/domain/validation.d.ts +19 -0
  230. package/dist/src/workflows/domain/validation.js +66 -0
  231. package/dist/src/workflows/domain/validation.js.map +1 -0
  232. package/dist/src/workflows/engine/index.d.ts +2 -0
  233. package/dist/src/workflows/engine/index.js +3 -0
  234. package/dist/src/workflows/engine/projector.d.ts +3 -0
  235. package/dist/src/workflows/engine/projector.js +205 -0
  236. package/dist/src/workflows/engine/projector.js.map +1 -0
  237. package/dist/src/workflows/engine/workflow-engine.d.ts +32 -0
  238. package/dist/src/workflows/engine/workflow-engine.js +189 -0
  239. package/dist/src/workflows/engine/workflow-engine.js.map +1 -0
  240. package/dist/src/workflows/index.d.ts +10 -0
  241. package/dist/src/workflows/index.js +18 -0
  242. package/dist/src/workflows/runtime/index.d.ts +1 -0
  243. package/dist/src/workflows/runtime/index.js +4 -0
  244. package/dist/src/workflows/runtime/script-runtime.d.ts +3 -0
  245. package/dist/src/workflows/runtime/script-runtime.js +3 -0
  246. package/dist/src/workflows/service/run-view-to-snapshot.d.ts +4 -0
  247. package/dist/src/workflows/service/run-view-to-snapshot.js +61 -0
  248. package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -0
  249. package/dist/src/workflows/service/workflow-run-service.d.ts +36 -0
  250. package/dist/src/workflows/service/workflow-run-service.js +279 -0
  251. package/dist/src/workflows/service/workflow-run-service.js.map +1 -0
  252. package/dist/src/workflows/service/workflow-run-service.types.d.ts +47 -0
  253. package/dist/src/workflows/service/workflow-run-service.types.js +1 -0
  254. package/dist/src/workflows/service/workflow-session-bridge.d.ts +29 -0
  255. package/dist/src/workflows/service/workflow-session-bridge.js +177 -0
  256. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -0
  257. package/dist/src/workflows/service/workflow-session-key.d.ts +3 -0
  258. package/dist/src/workflows/service/workflow-session-key.js +21 -0
  259. package/dist/src/workflows/service/workflow-session-key.js.map +1 -0
  260. package/dist/src/workflows/store/event-store.d.ts +17 -0
  261. package/dist/src/workflows/store/event-store.js +83 -0
  262. package/dist/src/workflows/store/event-store.js.map +1 -0
  263. package/dist/src/workflows/store/paths.d.ts +7 -0
  264. package/dist/src/workflows/store/paths.js +26 -0
  265. package/dist/src/workflows/store/paths.js.map +1 -0
  266. package/dist/src/workflows/store/run-store.d.ts +13 -0
  267. package/dist/src/workflows/store/run-store.js +69 -0
  268. package/dist/src/workflows/store/run-store.js.map +1 -0
  269. package/package.json +5 -5
  270. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +0 -222
  271. package/dist/gateway/static/root/assets/apps-page-Dg8R-Szf.js +0 -1
  272. package/dist/gateway/static/root/assets/channels-settings-yohw9YSu.js +0 -1
  273. package/dist/gateway/static/root/assets/cron-api-0h_QT8U3.js +0 -1
  274. package/dist/gateway/static/root/assets/cron-page-BkfKFfFk.js +0 -1
  275. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +0 -1
  276. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +0 -1
  277. package/dist/gateway/static/root/assets/logs-page-BFZ8GgCv.js +0 -1
  278. package/dist/gateway/static/root/assets/sessions-page-CD7AfB-2.js +0 -1
  279. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +0 -3
  280. package/dist/gateway/static/root/assets/skills-page-CcN_gj--.js +0 -2
  281. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +0 -3
  282. package/dist/gateway/static/root/assets/voice-api-key-field-O6awz9hi.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"misc.js","names":[],"sources":["../../../../../../src/gateway/hono/routes/config-patch/misc.ts"],"sourcesContent":["/**\n * `PATCH /api/config` — tail sections that are each <50 lines:\n * update / cron / goals / session / gateway.{skillsMarketplaceProvider,\n * skillsStoreBaseUrl} / providers / providersConfig / stt / tts / tools /\n * tunnel / bindings / mcp.\n *\n * Most of these delegate to a `mergeXxxConfigPatch(config, body) → { ok,\n * message? }` helper that lives next to the schema, so this file is mostly\n * \"validate object shape, call helper, surface 400 on failure\". The async\n * `providers` branch saves into the credential resolver rather than config.\n *\n * Final validation (resolveGatewayAuth + assertGatewayRuntimeConfig) runs\n * after every gateway-touching patch lands, so it sees the merged shape.\n */\nimport type { Config } from '../../../../config/schema.js';\nimport { BindingsConfigSchema, McpConfigSchema } from '../../../../config/schema.js';\nimport { CredentialResolver } from '../../../../auth/credentials.js';\nimport { applyToolsWebPatch } from '../../../config-tools-web.js';\nimport { mergeTunnelConfigPatch } from '../../../../tunnel/tunnel-config.js';\nimport { canonicalizeConfiguredMcpServer } from '../../../../config/mcp-config-normalize.js';\nimport {\n mergeCronConfigPatch,\n mergeGatewaySkillsMarketplacePatch,\n mergeGoalsConfigPatch,\n mergeSessionConfigPatch,\n mergeUpdateConfigPatch,\n} from '../../../../config/web-patch.js';\nimport { mergeSttConfigPatch, mergeTtsConfigPatch } from '../../lib/safe-voice-config.js';\nimport { assertGatewayRuntimeConfig } from '../../../runtime-config.js';\nimport { resolveGatewayAuth, assertGatewayAuthConfigured } from '../../../auth.js';\nimport { type PatchResult, PATCH_OK, patchError } from './result.js';\n\nexport async function applyMiscPatch(config: Config, body: any): Promise<PatchResult> {\n if (body.update !== undefined && typeof body.update === 'object' && body.update !== null) {\n const updateResult = mergeUpdateConfigPatch(config, body.update as Record<string, unknown>);\n if (updateResult.ok === false) {\n return patchError(updateResult.message);\n }\n }\n\n if (body.cron !== undefined) {\n if (typeof body.cron !== 'object' || body.cron === null || Array.isArray(body.cron)) {\n return patchError('cron must be an object');\n }\n const cronResult = mergeCronConfigPatch(config, body.cron as Record<string, unknown>);\n if (cronResult.ok === false) {\n return patchError(cronResult.message);\n }\n }\n\n if (body.goals !== undefined) {\n if (typeof body.goals !== 'object' || body.goals === null || Array.isArray(body.goals)) {\n return patchError('goals must be an object');\n }\n const goalsResult = mergeGoalsConfigPatch(config, body.goals as Record<string, unknown>);\n if (goalsResult.ok === false) {\n return patchError(goalsResult.message);\n }\n }\n\n if (body.session !== undefined) {\n if (typeof body.session !== 'object' || body.session === null || Array.isArray(body.session)) {\n return patchError('session must be an object');\n }\n const sessionResult = mergeSessionConfigPatch(config, body.session as Record<string, unknown>);\n if (sessionResult.ok === false) {\n return patchError(sessionResult.message);\n }\n }\n\n if (\n body.gateway !== undefined &&\n typeof body.gateway === 'object' &&\n body.gateway !== null &&\n !Array.isArray(body.gateway)\n ) {\n const gwPatch = body.gateway as Record<string, unknown>;\n if (gwPatch.skillsMarketplaceProvider !== undefined || gwPatch.skillsStoreBaseUrl !== undefined) {\n const skillsResult = mergeGatewaySkillsMarketplacePatch(config, {\n ...(gwPatch.skillsMarketplaceProvider !== undefined\n ? { skillsMarketplaceProvider: gwPatch.skillsMarketplaceProvider }\n : {}),\n ...(gwPatch.skillsStoreBaseUrl !== undefined\n ? { skillsStoreBaseUrl: gwPatch.skillsStoreBaseUrl }\n : {}),\n });\n if (skillsResult.ok === false) {\n return patchError(skillsResult.message);\n }\n }\n }\n\n // LLM provider credentials — saved into the credential system (not config).\n if (body.providers) {\n const resolver = new CredentialResolver();\n for (const [key, apiKey] of Object.entries(body.providers)) {\n if (\n apiKey !== undefined &&\n typeof apiKey === 'string' &&\n apiKey.trim() &&\n apiKey !== '***' &&\n apiKey !== '••••••••••••'\n ) {\n await resolver.saveApiKey(key, apiKey, { profileName: 'default' });\n }\n }\n }\n\n // Structured per-vendor provider config (cfg.providers.<id>) for capability\n // providers (image / audio / video). Distinct from `body.providers` above\n // which targets the LLM-side credential resolver.\n if (body.providersConfig && typeof body.providersConfig === 'object' && !Array.isArray(body.providersConfig)) {\n const cfgProviders = (config as { providers?: Record<string, Record<string, unknown>> }).providers ?? {};\n for (const [vendorId, raw] of Object.entries(body.providersConfig as Record<string, unknown>)) {\n if (!vendorId || typeof vendorId !== 'string') continue;\n if (raw === null) {\n delete cfgProviders[vendorId];\n continue;\n }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;\n const next = (cfgProviders[vendorId] ?? {}) as Record<string, unknown>;\n const patch = raw as Record<string, unknown>;\n for (const field of ['apiKey', 'baseUrl', 'region', 'imageBaseUrl'] as const) {\n if (patch[field] === null || patch[field] === '') {\n delete next[field];\n } else if (typeof patch[field] === 'string') {\n next[field] = (patch[field] as string).trim();\n }\n }\n if (patch.azure === null) {\n delete next.azure;\n } else if (patch.azure && typeof patch.azure === 'object' && !Array.isArray(patch.azure)) {\n next.azure = { ...(next.azure as Record<string, unknown> ?? {}), ...(patch.azure as Record<string, unknown>) };\n }\n if (patch.request === null) {\n delete next.request;\n } else if (patch.request && typeof patch.request === 'object' && !Array.isArray(patch.request)) {\n next.request = { ...(next.request as Record<string, unknown> ?? {}), ...(patch.request as Record<string, unknown>) };\n }\n cfgProviders[vendorId] = next;\n }\n (config as { providers?: Record<string, Record<string, unknown>> }).providers = cfgProviders;\n }\n\n // PATCH `stt` writes to tools.media.audio; PATCH `tts` writes to messages.tts.\n if (body.stt !== undefined) {\n config.tools = config.tools ?? {};\n config.tools.media = config.tools.media ?? {};\n (config.tools.media as Record<string, unknown>).audio = mergeSttConfigPatch(\n config.tools.media.audio,\n body.stt,\n );\n }\n if (body.tts !== undefined) {\n config.messages = config.messages ?? {};\n (config.messages as Record<string, unknown>).tts = mergeTtsConfigPatch(\n config.messages.tts,\n body.tts,\n );\n }\n\n const toolsPatchErr = applyToolsWebPatch(config, body as Record<string, unknown>);\n if (toolsPatchErr) {\n return patchError(toolsPatchErr);\n }\n\n if (body.tunnel !== undefined) {\n if (!body.tunnel || typeof body.tunnel !== 'object' || Array.isArray(body.tunnel)) {\n return patchError('tunnel must be an object');\n }\n const tunnelResult = mergeTunnelConfigPatch(config, body.tunnel as Record<string, unknown>);\n if (tunnelResult.ok === false) {\n return patchError(tunnelResult.message);\n }\n }\n\n if (body.bindings !== undefined) {\n if (!Array.isArray(body.bindings)) {\n return patchError('bindings must be an array');\n }\n const parsed = BindingsConfigSchema.safeParse(body.bindings);\n if (!parsed.success) {\n return patchError(parsed.error.issues.map((i) => i.message).join('; '));\n }\n config.bindings = parsed.data;\n }\n\n if (body.mcp !== undefined) {\n if (body.mcp === null) {\n delete config.mcp;\n } else if (typeof body.mcp !== 'object' || Array.isArray(body.mcp)) {\n return patchError('mcp must be an object');\n } else {\n const parsed = McpConfigSchema.safeParse(body.mcp);\n if (!parsed.success) {\n return patchError(parsed.error.issues.map((i) => i.message).join('; '));\n }\n if (parsed.data === undefined) {\n delete config.mcp;\n } else {\n const next = { ...parsed.data };\n if (next.servers) {\n next.servers = Object.fromEntries(\n Object.entries(next.servers).map(([name, server]) => [\n name,\n canonicalizeConfiguredMcpServer(server as Record<string, unknown>),\n ]),\n );\n }\n config.mcp = next;\n }\n }\n }\n\n return PATCH_OK;\n}\n\n/**\n * Re-validate gateway runtime config when `body.gateway` touched anything that\n * could break the bind/auth contract. Runs *after* all per-section patches\n * land so it sees the fully merged shape.\n */\nexport function validateGatewayAfterPatch(config: Config, body: any): PatchResult {\n if (body.gateway === undefined) return PATCH_OK;\n try {\n const auth = resolveGatewayAuth({ authConfig: config.gateway?.auth });\n assertGatewayAuthConfigured(auth);\n assertGatewayRuntimeConfig({\n cfg: config,\n auth,\n port: config.gateway?.port ?? 18790,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return patchError(message);\n }\n return PATCH_OK;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"misc.js","names":[],"sources":["../../../../../../src/gateway/hono/routes/config-patch/misc.ts"],"sourcesContent":["/**\n * `PATCH /api/config` — tail sections that are each <50 lines:\n * update / cron / goals / session / gateway.{skillsMarketplaceProvider,\n * skillsStoreBaseUrl} / providers / providersConfig / stt / tts / tools /\n * tunnel / bindings / mcp.\n *\n * Most of these delegate to a `mergeXxxConfigPatch(config, body) → { ok,\n * message? }` helper that lives next to the schema, so this file is mostly\n * \"validate object shape, call helper, surface 400 on failure\". The async\n * `providers` branch saves into the credential resolver rather than config.\n *\n * Final validation (resolveGatewayAuth + assertGatewayRuntimeConfig) runs\n * after every gateway-touching patch lands, so it sees the merged shape.\n */\nimport type { Config } from '../../../../config/schema.js';\nimport { BindingsConfigSchema, McpConfigSchema } from '../../../../config/schema.js';\nimport { CredentialResolver } from '../../../../auth/credentials.js';\nimport { isMaskedSecretPatchValue } from '../../lib/mask-secret-length.js';\nimport { applyToolsWebPatch } from '../../../config-tools-web.js';\nimport { mergeTunnelConfigPatch } from '../../../../tunnel/tunnel-config.js';\nimport { canonicalizeConfiguredMcpServer } from '../../../../config/mcp-config-normalize.js';\nimport {\n mergeCronConfigPatch,\n mergeGatewaySkillsMarketplacePatch,\n mergeGoalsConfigPatch,\n mergeSessionConfigPatch,\n mergeUpdateConfigPatch,\n} from '../../../../config/web-patch.js';\nimport { mergeSttConfigPatch, mergeTtsConfigPatch } from '../../lib/safe-voice-config.js';\nimport { assertGatewayRuntimeConfig } from '../../../runtime-config.js';\nimport { resolveGatewayAuth, assertGatewayAuthConfigured } from '../../../auth.js';\nimport { type PatchResult, PATCH_OK, patchError } from './result.js';\n\nexport async function applyMiscPatch(config: Config, body: any): Promise<PatchResult> {\n if (body.update !== undefined && typeof body.update === 'object' && body.update !== null) {\n const updateResult = mergeUpdateConfigPatch(config, body.update as Record<string, unknown>);\n if (updateResult.ok === false) {\n return patchError(updateResult.message);\n }\n }\n\n if (body.cron !== undefined) {\n if (typeof body.cron !== 'object' || body.cron === null || Array.isArray(body.cron)) {\n return patchError('cron must be an object');\n }\n const cronResult = mergeCronConfigPatch(config, body.cron as Record<string, unknown>);\n if (cronResult.ok === false) {\n return patchError(cronResult.message);\n }\n }\n\n if (body.goals !== undefined) {\n if (typeof body.goals !== 'object' || body.goals === null || Array.isArray(body.goals)) {\n return patchError('goals must be an object');\n }\n const goalsResult = mergeGoalsConfigPatch(config, body.goals as Record<string, unknown>);\n if (goalsResult.ok === false) {\n return patchError(goalsResult.message);\n }\n }\n\n if (body.session !== undefined) {\n if (typeof body.session !== 'object' || body.session === null || Array.isArray(body.session)) {\n return patchError('session must be an object');\n }\n const sessionResult = mergeSessionConfigPatch(config, body.session as Record<string, unknown>);\n if (sessionResult.ok === false) {\n return patchError(sessionResult.message);\n }\n }\n\n if (\n body.gateway !== undefined &&\n typeof body.gateway === 'object' &&\n body.gateway !== null &&\n !Array.isArray(body.gateway)\n ) {\n const gwPatch = body.gateway as Record<string, unknown>;\n if (gwPatch.skillsMarketplaceProvider !== undefined || gwPatch.skillsStoreBaseUrl !== undefined) {\n const skillsResult = mergeGatewaySkillsMarketplacePatch(config, {\n ...(gwPatch.skillsMarketplaceProvider !== undefined\n ? { skillsMarketplaceProvider: gwPatch.skillsMarketplaceProvider }\n : {}),\n ...(gwPatch.skillsStoreBaseUrl !== undefined\n ? { skillsStoreBaseUrl: gwPatch.skillsStoreBaseUrl }\n : {}),\n });\n if (skillsResult.ok === false) {\n return patchError(skillsResult.message);\n }\n }\n }\n\n // LLM provider credentials — saved into the credential system (not config).\n if (body.providers) {\n const resolver = new CredentialResolver();\n for (const [key, apiKey] of Object.entries(body.providers)) {\n if (\n apiKey !== undefined &&\n typeof apiKey === 'string' &&\n apiKey.trim() &&\n !isMaskedSecretPatchValue(apiKey)\n ) {\n await resolver.saveApiKey(key, apiKey, { profileName: 'default' });\n }\n }\n }\n\n // Structured per-vendor provider config (cfg.providers.<id>) for capability\n // providers (image / audio / video). Distinct from `body.providers` above\n // which targets the LLM-side credential resolver.\n if (body.providersConfig && typeof body.providersConfig === 'object' && !Array.isArray(body.providersConfig)) {\n const cfgProviders = (config as { providers?: Record<string, Record<string, unknown>> }).providers ?? {};\n for (const [vendorId, raw] of Object.entries(body.providersConfig as Record<string, unknown>)) {\n if (!vendorId || typeof vendorId !== 'string') continue;\n if (raw === null) {\n delete cfgProviders[vendorId];\n continue;\n }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;\n const next = (cfgProviders[vendorId] ?? {}) as Record<string, unknown>;\n const patch = raw as Record<string, unknown>;\n for (const field of ['apiKey', 'baseUrl', 'region', 'imageBaseUrl'] as const) {\n if (patch[field] === null || patch[field] === '') {\n delete next[field];\n } else if (typeof patch[field] === 'string') {\n const trimmed = (patch[field] as string).trim();\n if (field === 'apiKey' && isMaskedSecretPatchValue(trimmed)) {\n continue;\n }\n next[field] = trimmed;\n }\n }\n if (patch.azure === null) {\n delete next.azure;\n } else if (patch.azure && typeof patch.azure === 'object' && !Array.isArray(patch.azure)) {\n next.azure = { ...(next.azure as Record<string, unknown> ?? {}), ...(patch.azure as Record<string, unknown>) };\n }\n if (patch.request === null) {\n delete next.request;\n } else if (patch.request && typeof patch.request === 'object' && !Array.isArray(patch.request)) {\n next.request = { ...(next.request as Record<string, unknown> ?? {}), ...(patch.request as Record<string, unknown>) };\n }\n cfgProviders[vendorId] = next;\n }\n (config as { providers?: Record<string, Record<string, unknown>> }).providers = cfgProviders;\n }\n\n // PATCH `stt` writes to tools.media.audio; PATCH `tts` writes to messages.tts.\n if (body.stt !== undefined) {\n config.tools = config.tools ?? {};\n config.tools.media = config.tools.media ?? {};\n (config.tools.media as Record<string, unknown>).audio = mergeSttConfigPatch(\n config.tools.media.audio,\n body.stt,\n );\n }\n if (body.tts !== undefined) {\n config.messages = config.messages ?? {};\n (config.messages as Record<string, unknown>).tts = mergeTtsConfigPatch(\n config.messages.tts,\n body.tts,\n );\n }\n\n const toolsPatchErr = applyToolsWebPatch(config, body as Record<string, unknown>);\n if (toolsPatchErr) {\n return patchError(toolsPatchErr);\n }\n\n if (body.tunnel !== undefined) {\n if (!body.tunnel || typeof body.tunnel !== 'object' || Array.isArray(body.tunnel)) {\n return patchError('tunnel must be an object');\n }\n const tunnelResult = mergeTunnelConfigPatch(config, body.tunnel as Record<string, unknown>);\n if (tunnelResult.ok === false) {\n return patchError(tunnelResult.message);\n }\n }\n\n if (body.bindings !== undefined) {\n if (!Array.isArray(body.bindings)) {\n return patchError('bindings must be an array');\n }\n const parsed = BindingsConfigSchema.safeParse(body.bindings);\n if (!parsed.success) {\n return patchError(parsed.error.issues.map((i) => i.message).join('; '));\n }\n config.bindings = parsed.data;\n }\n\n if (body.mcp !== undefined) {\n if (body.mcp === null) {\n delete config.mcp;\n } else if (typeof body.mcp !== 'object' || Array.isArray(body.mcp)) {\n return patchError('mcp must be an object');\n } else {\n const parsed = McpConfigSchema.safeParse(body.mcp);\n if (!parsed.success) {\n return patchError(parsed.error.issues.map((i) => i.message).join('; '));\n }\n if (parsed.data === undefined) {\n delete config.mcp;\n } else {\n const next = { ...parsed.data };\n if (next.servers) {\n next.servers = Object.fromEntries(\n Object.entries(next.servers).map(([name, server]) => [\n name,\n canonicalizeConfiguredMcpServer(server as Record<string, unknown>),\n ]),\n );\n }\n config.mcp = next;\n }\n }\n }\n\n return PATCH_OK;\n}\n\n/**\n * Re-validate gateway runtime config when `body.gateway` touched anything that\n * could break the bind/auth contract. Runs *after* all per-section patches\n * land so it sees the fully merged shape.\n */\nexport function validateGatewayAfterPatch(config: Config, body: any): PatchResult {\n if (body.gateway === undefined) return PATCH_OK;\n try {\n const auth = resolveGatewayAuth({ authConfig: config.gateway?.auth });\n assertGatewayAuthConfigured(auth);\n assertGatewayRuntimeConfig({\n cfg: config,\n auth,\n port: config.gateway?.port ?? 18790,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return patchError(message);\n }\n return PATCH_OK;\n}\n"],"mappings":"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"}
@@ -68,6 +68,65 @@ function registerConfigRoutes(authenticated, deps) {
68
68
  payload: { config: safeConfig }
69
69
  });
70
70
  });
71
+ /** POST /api/gateway/reveal-auth-secret — plaintext gateway.auth token/password from config only. */
72
+ authenticated.post("/api/gateway/reveal-auth-secret", strictRateLimitMiddleware, async (c) => {
73
+ let field;
74
+ try {
75
+ const body = await c.req.json();
76
+ field = body && typeof body === "object" ? body.field : void 0;
77
+ } catch {
78
+ field = void 0;
79
+ }
80
+ if (field !== "token" && field !== "password") return c.json({
81
+ ok: false,
82
+ error: { message: "field must be token or password" }
83
+ }, 400);
84
+ const config = service.currentConfig;
85
+ const secret = field === "token" ? config.gateway?.auth?.token?.trim() || null : config.gateway?.auth?.password?.trim() || null;
86
+ return c.json({
87
+ ok: true,
88
+ payload: {
89
+ field,
90
+ secret,
91
+ source: secret ? "config" : "none"
92
+ }
93
+ });
94
+ });
95
+ /** POST /api/agents/browser/reveal-cloud-api-key — plaintext browser cloud apiKey from config only. */
96
+ authenticated.post("/api/agents/browser/reveal-cloud-api-key", strictRateLimitMiddleware, async (c) => {
97
+ const apiKey = service.currentConfig.agents?.defaults?.browser?.cloud?.apiKey?.trim() || null;
98
+ return c.json({
99
+ ok: true,
100
+ payload: {
101
+ apiKey,
102
+ source: apiKey ? "config" : "none"
103
+ }
104
+ });
105
+ });
106
+ /** POST /api/tools/web/reveal-search-api-key — plaintext search provider apiKey by index. */
107
+ authenticated.post("/api/tools/web/reveal-search-api-key", strictRateLimitMiddleware, async (c) => {
108
+ let index = -1;
109
+ try {
110
+ const body = await c.req.json();
111
+ if (body && typeof body === "object" && typeof body.index === "number") index = Math.floor(body.index);
112
+ } catch {
113
+ index = -1;
114
+ }
115
+ const providers = service.currentConfig.tools?.web?.search?.providers ?? [];
116
+ if (index < 0 || index >= providers.length) return c.json({
117
+ ok: false,
118
+ error: { message: "Invalid provider index" }
119
+ }, 400);
120
+ const apiKey = providers[index]?.apiKey?.trim() || null;
121
+ return c.json({
122
+ ok: true,
123
+ payload: {
124
+ index,
125
+ apiKey,
126
+ source: apiKey ? "config" : "none"
127
+ }
128
+ });
129
+ });
71
130
  }
72
131
  //#endregion
73
132
  export { registerConfigRoutes };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../../../../../src/gateway/hono/routes/config.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { Config } from '../../../config/schema.js';\nimport { buildSafeWebConfigPayload } from '../lib/config-payload.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport {\n applyAgentsPatch,\n applyChannelsPatch,\n applyGatewayPatch,\n applyMiscPatch,\n validateGatewayAfterPatch,\n} from './config-patch/index.js';\n\nexport function registerConfigRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n authenticated.post('/api/config/reload', strictRateLimitMiddleware, async (c) => {\n const result = await service.reloadConfig();\n return c.json({ ok: true, payload: result });\n });\n\n authenticated.post('/api/heartbeat/trigger', strictRateLimitMiddleware, async (c) => {\n let reason = 'manual';\n try {\n const body = await c.req.json();\n if (body && typeof body === 'object' && typeof (body as { reason?: unknown }).reason === 'string') {\n const r = (body as { reason: string }).reason.trim();\n if (r) reason = r.slice(0, 120);\n }\n } catch {\n /* empty or invalid body */\n }\n service.requestHeartbeatNow({ reason });\n return c.json({ ok: true, payload: { scheduled: true } });\n });\n\n authenticated.get('/api/config', async (c) => {\n const safeConfig = await buildSafeWebConfigPayload(service);\n return c.json({ ok: true, payload: { config: safeConfig } });\n });\n\n // PATCH /api/config — section patchers run sequentially against the live\n // config object (mutate-in-place is intentional: the route handler reads\n // `service.currentConfig` and rewrites it through `saveConfig`). Each\n // patcher only touches the keys it owns and returns `{ ok: false }` with a\n // 400 body when validation fails; we surface the first failure verbatim.\n authenticated.patch('/api/config', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json();\n const config: Config = service.currentConfig as Config;\n\n applyAgentsPatch(config, body);\n applyChannelsPatch(config, body);\n\n const gatewayResult = applyGatewayPatch(config, body);\n if (gatewayResult.ok === false) {\n return c.json({ ok: false, error: gatewayResult.error }, gatewayResult.status as 400 | 500);\n }\n\n const miscResult = await applyMiscPatch(config, body);\n if (miscResult.ok === false) {\n return c.json({ ok: false, error: miscResult.error }, miscResult.status as 400 | 500);\n }\n\n const finalGwCheck = validateGatewayAfterPatch(config, body);\n if (finalGwCheck.ok === false) {\n return c.json({ ok: false, error: finalGwCheck.error }, finalGwCheck.status as 400 | 500);\n }\n\n const result = await service.saveConfig(config);\n if (!result.saved) {\n return c.json({ ok: false, error: result.error }, 500);\n }\n\n if (body.gateway?.heartbeat !== undefined && typeof body.gateway.heartbeat === 'object') {\n service.reloadHeartbeatFromCurrentConfig();\n }\n\n const safeConfig = await buildSafeWebConfigPayload(service);\n return c.json({ ok: true, payload: { config: safeConfig } });\n });\n}\n"],"mappings":";;;;;;;AAaA,SAAgB,qBAAqB,eAAqB,MAAoC;CAC5F,MAAM,EAAE,SAAS,8BAA8B;AAE/C,eAAc,KAAK,sBAAsB,2BAA2B,OAAO,MAAM;EAC/E,MAAM,SAAS,MAAM,QAAQ,cAAc;AAC3C,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS;GAAQ,CAAC;GAC5C;AAEF,eAAc,KAAK,0BAA0B,2BAA2B,OAAO,MAAM;EACnF,IAAI,SAAS;AACb,MAAI;GACF,MAAM,OAAO,MAAM,EAAE,IAAI,MAAM;AAC/B,OAAI,QAAQ,OAAO,SAAS,YAAY,OAAQ,KAA8B,WAAW,UAAU;IACjG,MAAM,IAAK,KAA4B,OAAO,MAAM;AACpD,QAAI,EAAG,UAAS,EAAE,MAAM,GAAG,IAAI;;UAE3B;AAGR,UAAQ,oBAAoB,EAAE,QAAQ,CAAC;AACvC,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,WAAW,MAAM;GAAE,CAAC;GACzD;AAEF,eAAc,IAAI,eAAe,OAAO,MAAM;EAC5C,MAAM,aAAa,MAAM,0BAA0B,QAAQ;AAC3D,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,QAAQ,YAAY;GAAE,CAAC;GAC5D;AAOF,eAAc,MAAM,eAAe,2BAA2B,OAAO,MAAM;EACzE,MAAM,OAAO,MAAM,EAAE,IAAI,MAAM;EAC/B,MAAM,SAAiB,QAAQ;AAE/B,mBAAiB,QAAQ,KAAK;AAC9B,qBAAmB,QAAQ,KAAK;EAEhC,MAAM,gBAAgB,kBAAkB,QAAQ,KAAK;AACrD,MAAI,cAAc,OAAO,MACvB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,cAAc;GAAO,EAAE,cAAc,OAAoB;EAG7F,MAAM,aAAa,MAAM,eAAe,QAAQ,KAAK;AACrD,MAAI,WAAW,OAAO,MACpB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,WAAW;GAAO,EAAE,WAAW,OAAoB;EAGvF,MAAM,eAAe,0BAA0B,QAAQ,KAAK;AAC5D,MAAI,aAAa,OAAO,MACtB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,aAAa;GAAO,EAAE,aAAa,OAAoB;EAG3F,MAAM,SAAS,MAAM,QAAQ,WAAW,OAAO;AAC/C,MAAI,CAAC,OAAO,MACV,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,OAAO;GAAO,EAAE,IAAI;AAGxD,MAAI,KAAK,SAAS,cAAc,KAAA,KAAa,OAAO,KAAK,QAAQ,cAAc,SAC7E,SAAQ,kCAAkC;EAG5C,MAAM,aAAa,MAAM,0BAA0B,QAAQ;AAC3D,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,QAAQ,YAAY;GAAE,CAAC;GAC5D"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../../../../../src/gateway/hono/routes/config.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { Config } from '../../../config/schema.js';\nimport { buildSafeWebConfigPayload } from '../lib/config-payload.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport {\n applyAgentsPatch,\n applyChannelsPatch,\n applyGatewayPatch,\n applyMiscPatch,\n validateGatewayAfterPatch,\n} from './config-patch/index.js';\n\nexport function registerConfigRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n authenticated.post('/api/config/reload', strictRateLimitMiddleware, async (c) => {\n const result = await service.reloadConfig();\n return c.json({ ok: true, payload: result });\n });\n\n authenticated.post('/api/heartbeat/trigger', strictRateLimitMiddleware, async (c) => {\n let reason = 'manual';\n try {\n const body = await c.req.json();\n if (body && typeof body === 'object' && typeof (body as { reason?: unknown }).reason === 'string') {\n const r = (body as { reason: string }).reason.trim();\n if (r) reason = r.slice(0, 120);\n }\n } catch {\n /* empty or invalid body */\n }\n service.requestHeartbeatNow({ reason });\n return c.json({ ok: true, payload: { scheduled: true } });\n });\n\n authenticated.get('/api/config', async (c) => {\n const safeConfig = await buildSafeWebConfigPayload(service);\n return c.json({ ok: true, payload: { config: safeConfig } });\n });\n\n // PATCH /api/config — section patchers run sequentially against the live\n // config object (mutate-in-place is intentional: the route handler reads\n // `service.currentConfig` and rewrites it through `saveConfig`). Each\n // patcher only touches the keys it owns and returns `{ ok: false }` with a\n // 400 body when validation fails; we surface the first failure verbatim.\n authenticated.patch('/api/config', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json();\n const config: Config = service.currentConfig as Config;\n\n applyAgentsPatch(config, body);\n applyChannelsPatch(config, body);\n\n const gatewayResult = applyGatewayPatch(config, body);\n if (gatewayResult.ok === false) {\n return c.json({ ok: false, error: gatewayResult.error }, gatewayResult.status as 400 | 500);\n }\n\n const miscResult = await applyMiscPatch(config, body);\n if (miscResult.ok === false) {\n return c.json({ ok: false, error: miscResult.error }, miscResult.status as 400 | 500);\n }\n\n const finalGwCheck = validateGatewayAfterPatch(config, body);\n if (finalGwCheck.ok === false) {\n return c.json({ ok: false, error: finalGwCheck.error }, finalGwCheck.status as 400 | 500);\n }\n\n const result = await service.saveConfig(config);\n if (!result.saved) {\n return c.json({ ok: false, error: result.error }, 500);\n }\n\n if (body.gateway?.heartbeat !== undefined && typeof body.gateway.heartbeat === 'object') {\n service.reloadHeartbeatFromCurrentConfig();\n }\n\n const safeConfig = await buildSafeWebConfigPayload(service);\n return c.json({ ok: true, payload: { config: safeConfig } });\n });\n\n /** POST /api/gateway/reveal-auth-secret — plaintext gateway.auth token/password from config only. */\n authenticated.post('/api/gateway/reveal-auth-secret', strictRateLimitMiddleware, async (c) => {\n let field: unknown;\n try {\n const body = await c.req.json();\n field = body && typeof body === 'object' ? (body as { field?: unknown }).field : undefined;\n } catch {\n field = undefined;\n }\n if (field !== 'token' && field !== 'password') {\n return c.json({ ok: false, error: { message: 'field must be token or password' } }, 400);\n }\n const config = service.currentConfig as Config;\n const secret =\n field === 'token'\n ? config.gateway?.auth?.token?.trim() || null\n : config.gateway?.auth?.password?.trim() || null;\n return c.json({\n ok: true,\n payload: { field, secret, source: secret ? ('config' as const) : ('none' as const) },\n });\n });\n\n /** POST /api/agents/browser/reveal-cloud-api-key — plaintext browser cloud apiKey from config only. */\n authenticated.post('/api/agents/browser/reveal-cloud-api-key', strictRateLimitMiddleware, async (c) => {\n const config = service.currentConfig as Config;\n const apiKey = config.agents?.defaults?.browser?.cloud?.apiKey?.trim() || null;\n return c.json({\n ok: true,\n payload: { apiKey, source: apiKey ? ('config' as const) : ('none' as const) },\n });\n });\n\n /** POST /api/tools/web/reveal-search-api-key — plaintext search provider apiKey by index. */\n authenticated.post('/api/tools/web/reveal-search-api-key', strictRateLimitMiddleware, async (c) => {\n let index = -1;\n try {\n const body = await c.req.json();\n if (body && typeof body === 'object' && typeof (body as { index?: unknown }).index === 'number') {\n index = Math.floor((body as { index: number }).index);\n }\n } catch {\n index = -1;\n }\n const providers = service.currentConfig.tools?.web?.search?.providers ?? [];\n if (index < 0 || index >= providers.length) {\n return c.json({ ok: false, error: { message: 'Invalid provider index' } }, 400);\n }\n const apiKey = providers[index]?.apiKey?.trim() || null;\n return c.json({\n ok: true,\n payload: { index, apiKey, source: apiKey ? ('config' as const) : ('none' as const) },\n });\n });\n}\n"],"mappings":"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"}
@@ -126,6 +126,14 @@ const AUTHENTICATED_LAZY_ROUTE_BUNDLES = [
126
126
  return { register: registerGoalsRoutes };
127
127
  }
128
128
  },
129
+ {
130
+ id: "workflows",
131
+ match: (path) => startsWithAny(path, ["/api/workflows"]),
132
+ load: async () => {
133
+ const { registerWorkflowRoutes } = await import("./workflows.js");
134
+ return { register: registerWorkflowRoutes };
135
+ }
136
+ },
129
137
  {
130
138
  id: "logs",
131
139
  match: (path) => startsWithAny(path, ["/api/logs"]),
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-bundles.js","names":[],"sources":["../../../../../src/gateway/hono/routes/lazy-bundles.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { GatewayService } from '../../service.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nexport type AuthenticatedLazyRouteBundle = {\n id: string;\n match: (path: string) => boolean;\n load: () => Promise<{ register: (authenticated: Hono, deps: AuthenticatedRouteDeps) => void }>;\n};\n\nexport type AppLazyRouteBundle = {\n id: string;\n prefixes: readonly string[];\n match: (path: string) => boolean;\n load: () => Promise<{\n registerOnApp: (app: Hono, service: GatewayService) => void;\n }>;\n};\n\nfunction startsWithAny(path: string, prefixes: readonly string[]): boolean {\n return prefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));\n}\n\nexport const AUTHENTICATED_LAZY_ROUTE_BUNDLES: readonly AuthenticatedLazyRouteBundle[] = [\n {\n id: 'workspace',\n match: (path) => startsWithAny(path, ['/api/workspace']),\n load: async () => {\n const { registerWorkspaceRoutes } = await import('./workspace.js');\n return { register: registerWorkspaceRoutes };\n },\n },\n {\n id: 'host-fs',\n match: (path) => startsWithAny(path, ['/api/host/fs']),\n load: async () => {\n const { registerHostFsRoutes } = await import('./host-fs.js');\n return { register: registerHostFsRoutes };\n },\n },\n {\n id: 'channels',\n match: (path) => startsWithAny(path, ['/api/channels']),\n load: async () => {\n const { registerChannelRoutes } = await import('./channels.js');\n return { register: registerChannelRoutes };\n },\n },\n {\n id: 'browser-install',\n match: (path) =>\n path === '/api/browser/playwright/install/stream' ||\n path === '/api/browser/cloakbrowser/install/stream',\n load: async () => {\n const { registerBrowserInstallRoutes } = await import('./browser-install.js');\n return { register: registerBrowserInstallRoutes };\n },\n },\n {\n id: 'browser',\n // `browser-install` above already matched the SSE install streams; this\n // catches the remaining /api/browser/* handlers (extension, cdp,\n // cloakbrowser doctor/launch/install, playwright doctor/install, cloud).\n match: (path) => startsWithAny(path, ['/api/browser']),\n load: async () => {\n const { registerBrowserRoutes } = await import('./browser.js');\n return { register: registerBrowserRoutes };\n },\n },\n {\n id: 'config',\n match: (path) =>\n startsWithAny(path, ['/api/config', '/api/heartbeat/trigger']),\n load: async () => {\n const { registerConfigRoutes } = await import('./config.js');\n return { register: registerConfigRoutes };\n },\n },\n {\n id: 'doctor',\n match: (path) => startsWithAny(path, ['/api/doctor']),\n load: async () => {\n const { registerDoctorRoutes } = await import('./doctor.js');\n return { register: registerDoctorRoutes };\n },\n },\n {\n id: 'dreaming',\n match: (path) => startsWithAny(path, ['/api/dreaming']),\n load: async () => {\n const { registerDreamingRoutes } = await import('./dreaming.js');\n return { register: registerDreamingRoutes };\n },\n },\n {\n id: 'agents',\n match: (path) => startsWithAny(path, ['/api/agents', '/api/voice/models']),\n load: async () => {\n const { registerAgentsRoutes } = await import('./agents.js');\n return { register: registerAgentsRoutes };\n },\n },\n {\n id: 'auth-registry-extensions',\n match: (path) =>\n startsWithAny(path, [\n '/api/auth',\n '/api/registry',\n '/api/extensions',\n '/api/context',\n '/api/marketplace',\n ]),\n load: async () => {\n const { registerAuthRegistryExtensionsRoutes } = await import('./auth-registry-extensions.js');\n return { register: registerAuthRegistryExtensionsRoutes };\n },\n },\n {\n id: 'models',\n match: (path) =>\n startsWithAny(path, ['/api/models', '/api/models-json', '/api/providers', '/api/image']),\n load: async () => {\n const { registerModelsRoutes } = await import('./models.js');\n return { register: registerModelsRoutes };\n },\n },\n {\n id: 'commands-skills',\n match: (path) => startsWithAny(path, ['/api/commands', '/api/skills']),\n load: async () => {\n const { registerCommandsSkillsRoutes } = await import('./commands-skills.js');\n return { register: registerCommandsSkillsRoutes };\n },\n },\n {\n id: 'cron',\n match: (path) => startsWithAny(path, ['/api/cron']),\n load: async () => {\n const { registerCronRoutes } = await import('./cron.js');\n return { register: registerCronRoutes };\n },\n },\n {\n id: 'goals',\n match: (path) => startsWithAny(path, ['/api/goals']),\n load: async () => {\n const { registerGoalsRoutes } = await import('./goals.js');\n return { register: registerGoalsRoutes };\n },\n },\n {\n id: 'logs',\n match: (path) => startsWithAny(path, ['/api/logs']),\n load: async () => {\n const { registerLogsRoutes } = await import('./logs.js');\n return { register: registerLogsRoutes };\n },\n },\n {\n id: 'shares',\n match: (path) => startsWithAny(path, ['/api/shares']),\n load: async () => {\n const { registerShareRoutes } = await import('./shares.js');\n return { register: registerShareRoutes };\n },\n },\n {\n id: 'site-shares',\n match: (path) => startsWithAny(path, ['/api/site-shares']),\n load: async () => {\n const { registerSiteShareRoutes } = await import('./site-shares.js');\n return { register: registerSiteShareRoutes };\n },\n },\n {\n id: 'tunnel',\n match: (path) => startsWithAny(path, ['/api/tunnel']),\n load: async () => {\n const { registerTunnelRoutes } = await import('./tunnel.js');\n return { register: registerTunnelRoutes };\n },\n },\n {\n id: 'exposure',\n match: (path) => startsWithAny(path, ['/api/exposure']),\n load: async () => {\n const { registerExposureRoutes } = await import('./exposure.js');\n return { register: registerExposureRoutes };\n },\n },\n {\n id: 'extension-gateway',\n match: (path) => startsWithAny(path, ['/api/gateway']),\n load: async () => {\n const { registerExtensionGatewayRoutes } = await import('./extension-gateway.js');\n return { register: registerExtensionGatewayRoutes };\n },\n },\n {\n id: 'update',\n match: (path) => startsWithAny(path, ['/api/update']),\n load: async () => {\n const { registerUpdateRoutes } = await import('./update.js');\n return { register: registerUpdateRoutes };\n },\n },\n {\n id: 'voice',\n match: (path) => startsWithAny(path, ['/api/voice']) && path !== '/api/voice/models',\n load: async () => {\n const { registerVoiceRoutes } = await import('./voice.js');\n return { register: registerVoiceRoutes };\n },\n },\n {\n id: 'mcp',\n match: (path) => startsWithAny(path, ['/api/mcp']),\n load: async () => {\n const { registerMcpRoutes } = await import('./mcp.js');\n return { register: registerMcpRoutes };\n },\n },\n];\n\nexport const APP_LAZY_ROUTE_BUNDLES: readonly AppLazyRouteBundle[] = [\n {\n id: 'shares-public',\n prefixes: ['/s'],\n match: (path) => startsWithAny(path, ['/s']),\n load: async () => {\n const { registerSharePublicRoutes } = await import('./shares.js');\n return { registerOnApp: registerSharePublicRoutes };\n },\n },\n {\n id: 'tunnel-public',\n prefixes: [\n '/api/tunnel/pair/ping',\n '/api/tunnel/pair/validate-url',\n '/api/tunnel/exchange-token',\n ],\n match: (path) =>\n path === '/api/tunnel/exchange-token' ||\n path === '/api/tunnel/pair/ping' ||\n path === '/api/tunnel/pair/validate-url',\n load: async () => {\n const { registerTunnelPublicRoutes } = await import('./tunnel.js');\n return { registerOnApp: registerTunnelPublicRoutes };\n },\n },\n];\n\nexport function findAuthenticatedLazyRouteBundle(path: string): AuthenticatedLazyRouteBundle | undefined {\n return AUTHENTICATED_LAZY_ROUTE_BUNDLES.find((bundle) => bundle.match(path));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lazy-bundles.js","names":[],"sources":["../../../../../src/gateway/hono/routes/lazy-bundles.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { GatewayService } from '../../service.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nexport type AuthenticatedLazyRouteBundle = {\n id: string;\n match: (path: string) => boolean;\n load: () => Promise<{ register: (authenticated: Hono, deps: AuthenticatedRouteDeps) => void }>;\n};\n\nexport type AppLazyRouteBundle = {\n id: string;\n prefixes: readonly string[];\n match: (path: string) => boolean;\n load: () => Promise<{\n registerOnApp: (app: Hono, service: GatewayService) => void;\n }>;\n};\n\nfunction startsWithAny(path: string, prefixes: readonly string[]): boolean {\n return prefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));\n}\n\nexport const AUTHENTICATED_LAZY_ROUTE_BUNDLES: readonly AuthenticatedLazyRouteBundle[] = [\n {\n id: 'workspace',\n match: (path) => startsWithAny(path, ['/api/workspace']),\n load: async () => {\n const { registerWorkspaceRoutes } = await import('./workspace.js');\n return { register: registerWorkspaceRoutes };\n },\n },\n {\n id: 'host-fs',\n match: (path) => startsWithAny(path, ['/api/host/fs']),\n load: async () => {\n const { registerHostFsRoutes } = await import('./host-fs.js');\n return { register: registerHostFsRoutes };\n },\n },\n {\n id: 'channels',\n match: (path) => startsWithAny(path, ['/api/channels']),\n load: async () => {\n const { registerChannelRoutes } = await import('./channels.js');\n return { register: registerChannelRoutes };\n },\n },\n {\n id: 'browser-install',\n match: (path) =>\n path === '/api/browser/playwright/install/stream' ||\n path === '/api/browser/cloakbrowser/install/stream',\n load: async () => {\n const { registerBrowserInstallRoutes } = await import('./browser-install.js');\n return { register: registerBrowserInstallRoutes };\n },\n },\n {\n id: 'browser',\n // `browser-install` above already matched the SSE install streams; this\n // catches the remaining /api/browser/* handlers (extension, cdp,\n // cloakbrowser doctor/launch/install, playwright doctor/install, cloud).\n match: (path) => startsWithAny(path, ['/api/browser']),\n load: async () => {\n const { registerBrowserRoutes } = await import('./browser.js');\n return { register: registerBrowserRoutes };\n },\n },\n {\n id: 'config',\n match: (path) =>\n startsWithAny(path, ['/api/config', '/api/heartbeat/trigger']),\n load: async () => {\n const { registerConfigRoutes } = await import('./config.js');\n return { register: registerConfigRoutes };\n },\n },\n {\n id: 'doctor',\n match: (path) => startsWithAny(path, ['/api/doctor']),\n load: async () => {\n const { registerDoctorRoutes } = await import('./doctor.js');\n return { register: registerDoctorRoutes };\n },\n },\n {\n id: 'dreaming',\n match: (path) => startsWithAny(path, ['/api/dreaming']),\n load: async () => {\n const { registerDreamingRoutes } = await import('./dreaming.js');\n return { register: registerDreamingRoutes };\n },\n },\n {\n id: 'agents',\n match: (path) => startsWithAny(path, ['/api/agents', '/api/voice/models']),\n load: async () => {\n const { registerAgentsRoutes } = await import('./agents.js');\n return { register: registerAgentsRoutes };\n },\n },\n {\n id: 'auth-registry-extensions',\n match: (path) =>\n startsWithAny(path, [\n '/api/auth',\n '/api/registry',\n '/api/extensions',\n '/api/context',\n '/api/marketplace',\n ]),\n load: async () => {\n const { registerAuthRegistryExtensionsRoutes } = await import('./auth-registry-extensions.js');\n return { register: registerAuthRegistryExtensionsRoutes };\n },\n },\n {\n id: 'models',\n match: (path) =>\n startsWithAny(path, ['/api/models', '/api/models-json', '/api/providers', '/api/image']),\n load: async () => {\n const { registerModelsRoutes } = await import('./models.js');\n return { register: registerModelsRoutes };\n },\n },\n {\n id: 'commands-skills',\n match: (path) => startsWithAny(path, ['/api/commands', '/api/skills']),\n load: async () => {\n const { registerCommandsSkillsRoutes } = await import('./commands-skills.js');\n return { register: registerCommandsSkillsRoutes };\n },\n },\n {\n id: 'cron',\n match: (path) => startsWithAny(path, ['/api/cron']),\n load: async () => {\n const { registerCronRoutes } = await import('./cron.js');\n return { register: registerCronRoutes };\n },\n },\n {\n id: 'goals',\n match: (path) => startsWithAny(path, ['/api/goals']),\n load: async () => {\n const { registerGoalsRoutes } = await import('./goals.js');\n return { register: registerGoalsRoutes };\n },\n },\n {\n id: 'workflows',\n match: (path) => startsWithAny(path, ['/api/workflows']),\n load: async () => {\n const { registerWorkflowRoutes } = await import('./workflows.js');\n return { register: registerWorkflowRoutes };\n },\n },\n {\n id: 'logs',\n match: (path) => startsWithAny(path, ['/api/logs']),\n load: async () => {\n const { registerLogsRoutes } = await import('./logs.js');\n return { register: registerLogsRoutes };\n },\n },\n {\n id: 'shares',\n match: (path) => startsWithAny(path, ['/api/shares']),\n load: async () => {\n const { registerShareRoutes } = await import('./shares.js');\n return { register: registerShareRoutes };\n },\n },\n {\n id: 'site-shares',\n match: (path) => startsWithAny(path, ['/api/site-shares']),\n load: async () => {\n const { registerSiteShareRoutes } = await import('./site-shares.js');\n return { register: registerSiteShareRoutes };\n },\n },\n {\n id: 'tunnel',\n match: (path) => startsWithAny(path, ['/api/tunnel']),\n load: async () => {\n const { registerTunnelRoutes } = await import('./tunnel.js');\n return { register: registerTunnelRoutes };\n },\n },\n {\n id: 'exposure',\n match: (path) => startsWithAny(path, ['/api/exposure']),\n load: async () => {\n const { registerExposureRoutes } = await import('./exposure.js');\n return { register: registerExposureRoutes };\n },\n },\n {\n id: 'extension-gateway',\n match: (path) => startsWithAny(path, ['/api/gateway']),\n load: async () => {\n const { registerExtensionGatewayRoutes } = await import('./extension-gateway.js');\n return { register: registerExtensionGatewayRoutes };\n },\n },\n {\n id: 'update',\n match: (path) => startsWithAny(path, ['/api/update']),\n load: async () => {\n const { registerUpdateRoutes } = await import('./update.js');\n return { register: registerUpdateRoutes };\n },\n },\n {\n id: 'voice',\n match: (path) => startsWithAny(path, ['/api/voice']) && path !== '/api/voice/models',\n load: async () => {\n const { registerVoiceRoutes } = await import('./voice.js');\n return { register: registerVoiceRoutes };\n },\n },\n {\n id: 'mcp',\n match: (path) => startsWithAny(path, ['/api/mcp']),\n load: async () => {\n const { registerMcpRoutes } = await import('./mcp.js');\n return { register: registerMcpRoutes };\n },\n },\n];\n\nexport const APP_LAZY_ROUTE_BUNDLES: readonly AppLazyRouteBundle[] = [\n {\n id: 'shares-public',\n prefixes: ['/s'],\n match: (path) => startsWithAny(path, ['/s']),\n load: async () => {\n const { registerSharePublicRoutes } = await import('./shares.js');\n return { registerOnApp: registerSharePublicRoutes };\n },\n },\n {\n id: 'tunnel-public',\n prefixes: [\n '/api/tunnel/pair/ping',\n '/api/tunnel/pair/validate-url',\n '/api/tunnel/exchange-token',\n ],\n match: (path) =>\n path === '/api/tunnel/exchange-token' ||\n path === '/api/tunnel/pair/ping' ||\n path === '/api/tunnel/pair/validate-url',\n load: async () => {\n const { registerTunnelPublicRoutes } = await import('./tunnel.js');\n return { registerOnApp: registerTunnelPublicRoutes };\n },\n },\n];\n\nexport function findAuthenticatedLazyRouteBundle(path: string): AuthenticatedLazyRouteBundle | undefined {\n return AUTHENTICATED_LAZY_ROUTE_BUNDLES.find((bundle) => bundle.match(path));\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { init_models_json, loadModelsJson, saveModelsJson, validateModelsConfig
4
4
  import { getModelRegistry } from "../../../providers/model-registry.js";
5
5
  import { CredentialResolver, init_credentials } from "../../../auth/credentials.js";
6
6
  import { getProviderRegistry, init_plugin_registry } from "../../../providers/plugin-registry.js";
7
- import { PROVIDER_META, getAllModels, getAllProviders, getAvailableModels, getProviderActiveKeySource, init_providers, isProviderConfigured } from "../../../providers/index.js";
7
+ import { PROVIDER_META, getAllModels, getAllProviders, getAvailableModels, getProviderAuthState, init_providers, isProviderConfigured } from "../../../providers/index.js";
8
8
  import { getImageGenerationProvider } from "../../../agent/image/generation/provider-registry.js";
9
9
  import { listImageGenerationProvidersSummary } from "../../../agent/image/generation/runtime.js";
10
10
  import { respondStartupUnavailable } from "../lib/startup-unavailable.js";
@@ -14,6 +14,11 @@ init_resolve_config_value();
14
14
  init_providers();
15
15
  init_credentials();
16
16
  init_plugin_registry();
17
+ function readModelsJsonProviderApiKey(providerId) {
18
+ const { config } = loadModelsJson(resolveModelsJsonPath());
19
+ const key = (config.providers?.[providerId.trim()])?.apiKey;
20
+ return typeof key === "string" && key.trim() ? key.trim() : void 0;
21
+ }
17
22
  /** Plaintext key only when persisted under `cfg.providers.<id>.apiKey` (not env / credential store). */
18
23
  function readProviderApiKeyFromConfigFileOnly(cfg, providerId) {
19
24
  const id = providerId.trim().toLowerCase();
@@ -22,6 +27,18 @@ function readProviderApiKeyFromConfigFileOnly(cfg, providerId) {
22
27
  const k = bucket.apiKey;
23
28
  return typeof k === "string" && k.trim() ? k.trim() : void 0;
24
29
  }
30
+ /** Extension id from manifest `providers[]` (e.g. provider `demo` → extension `demo-provider`). */
31
+ function resolveExtensionIdForProvider(service, providerId) {
32
+ const loader = service.getExtensionLoader();
33
+ if (!loader) return void 0;
34
+ return loader.buildManifestRegistry().findByProvider(providerId)?.id;
35
+ }
36
+ /** Effective LLM REST base URL for a provider (models.json overrides included). */
37
+ function resolveProviderApiBaseUrl(providerId) {
38
+ const model = getModelRegistry().getAll().find((m) => m.provider === providerId);
39
+ if (!model?.baseUrl || model.baseUrl === "extension://provider-plugin") return void 0;
40
+ return model.baseUrl;
41
+ }
25
42
  function mapPluginModel(providerId, model, available) {
26
43
  return {
27
44
  id: `${providerId}/${model.id}`,
@@ -250,31 +267,84 @@ function registerModelsRoutes(authenticated, deps) {
250
267
  const pluginRegistry = getProviderRegistry();
251
268
  const meta = await Promise.all(providers.map(async (provider) => {
252
269
  const plugin = pluginRegistry.get(provider);
270
+ const extensionId = plugin ? resolveExtensionIdForProvider(service, provider) : void 0;
271
+ const authState = await getProviderAuthState(provider);
272
+ const configured = authState.authMode !== "none" || await isProviderConfigured(provider);
253
273
  return {
254
274
  id: provider,
255
275
  name: plugin?.name ?? PROVIDER_META[provider]?.name ?? provider,
256
276
  category: plugin ? "extension" : PROVIDER_META[provider]?.category || "specialty",
257
277
  supportsOAuth: plugin ? false : PROVIDER_META[provider]?.supportsOAuth ?? false,
258
- supportsApiKey: plugin ? true : PROVIDER_META[provider]?.supportsApiKey ?? true,
259
- configured: await isProviderConfigured(provider),
260
- activeKeySource: await getProviderActiveKeySource(provider)
278
+ supportsApiKey: plugin ? false : PROVIDER_META[provider]?.supportsApiKey ?? true,
279
+ configured,
280
+ activeKeySource: authState.authMode,
281
+ authMode: authState.authMode,
282
+ authStatus: authState.authStatus,
283
+ ...authState.expiresAt ? { expiresAt: authState.expiresAt } : {},
284
+ baseUrl: resolveProviderApiBaseUrl(provider),
285
+ ...extensionId ? { extensionId } : {}
261
286
  };
262
287
  }));
263
288
  const knownProviderIds = new Set(providers);
264
- for (const plugin of pluginRegistry.listAll()) if (!knownProviderIds.has(plugin.id)) meta.push({
265
- id: plugin.id,
266
- name: plugin.name,
267
- category: "extension",
268
- supportsOAuth: false,
269
- supportsApiKey: true,
270
- configured: true,
271
- activeKeySource: "extension"
272
- });
289
+ for (const plugin of pluginRegistry.listAll()) if (!knownProviderIds.has(plugin.id)) {
290
+ const extensionId = resolveExtensionIdForProvider(service, plugin.id);
291
+ meta.push({
292
+ id: plugin.id,
293
+ name: plugin.name,
294
+ category: "extension",
295
+ supportsOAuth: false,
296
+ supportsApiKey: false,
297
+ configured: true,
298
+ activeKeySource: "extension",
299
+ authMode: "extension",
300
+ authStatus: "connected",
301
+ baseUrl: resolveProviderApiBaseUrl(plugin.id),
302
+ ...extensionId ? { extensionId } : {}
303
+ });
304
+ }
273
305
  return c.json({
274
306
  ok: true,
275
307
  payload: { providers: meta }
276
308
  });
277
309
  });
310
+ /**
311
+ * POST /api/providers/:providerId/reveal-api-key — plaintext key when stored in the
312
+ * gateway credential store or models.json (not env vars or OAuth tokens).
313
+ */
314
+ authenticated.post("/api/providers/:providerId/reveal-api-key", strictRateLimitMiddleware, async (c) => {
315
+ const rawId = c.req.param("providerId")?.trim();
316
+ if (!rawId) return c.json({
317
+ ok: false,
318
+ error: { message: "Missing providerId" }
319
+ }, 400);
320
+ const providerId = rawId.toLowerCase();
321
+ const stored = await new CredentialResolver().revealGatewayStoredApiKey(providerId);
322
+ if (stored) return c.json({
323
+ ok: true,
324
+ payload: {
325
+ id: providerId,
326
+ apiKey: stored,
327
+ source: "credential"
328
+ }
329
+ });
330
+ const fromModelsJson = readModelsJsonProviderApiKey(providerId);
331
+ if (fromModelsJson) return c.json({
332
+ ok: true,
333
+ payload: {
334
+ id: providerId,
335
+ apiKey: fromModelsJson,
336
+ source: "models_json"
337
+ }
338
+ });
339
+ return c.json({
340
+ ok: true,
341
+ payload: {
342
+ id: providerId,
343
+ apiKey: null,
344
+ source: "none"
345
+ }
346
+ });
347
+ });
278
348
  authenticated.delete("/api/providers/:providerId/key", strictRateLimitMiddleware, async (c) => {
279
349
  const providerId = c.req.param("providerId");
280
350
  if (!providerId) return c.json({
@@ -282,10 +352,9 @@ function registerModelsRoutes(authenticated, deps) {
282
352
  error: { message: "Missing providerId" }
283
353
  }, 400);
284
354
  const normalizedProvider = providerId.toLowerCase();
285
- const profileId = `${normalizedProvider}:default`;
286
355
  const resolver = new CredentialResolver();
287
356
  try {
288
- await resolver.deleteProfile(profileId);
357
+ await resolver.deleteProviderCredential(normalizedProvider);
289
358
  return c.json({
290
359
  ok: true,
291
360
  payload: { deleted: normalizedProvider }
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","names":["getModelsJsonPath"],"sources":["../../../../../src/gateway/hono/routes/models.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport {\n getModelsJsonPath,\n loadModelsJson,\n saveModelsJson,\n validateModelsConfig,\n} from '../../../config/models-json.js';\nimport type { Config } from '../../../config/schema.js';\nimport { testApiKeyResolution } from '../../../config/resolve-config-value.js';\nimport {\n getImageGenerationProvider,\n listImageGenerationProvidersSummary,\n} from '../../../agent/image/generation/runtime.js';\nimport {\n getAllModels,\n getAvailableModels,\n getModelRegistry,\n getAllProviders,\n getProviderActiveKeySource,\n isProviderConfigured,\n PROVIDER_META,\n} from '../../../providers/index.js';\nimport { CredentialResolver } from '../../../auth/credentials.js';\nimport { getProviderRegistry } from '../../../providers/plugin-registry.js';\nimport type { ProviderModelDefinition } from '../../../extensions/types/providers.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport { respondStartupUnavailable } from '../lib/startup-unavailable.js';\n\n/** Plaintext key only when persisted under `cfg.providers.<id>.apiKey` (not env / credential store). */\nfunction readProviderApiKeyFromConfigFileOnly(cfg: Config, providerId: string): string | undefined {\n const id = providerId.trim().toLowerCase();\n const bucket = cfg.providers?.[id];\n if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) return undefined;\n const k = (bucket as { apiKey?: unknown }).apiKey;\n return typeof k === 'string' && k.trim() ? k.trim() : undefined;\n}\n\nfunction mapPluginModel(providerId: string, model: ProviderModelDefinition, available: boolean) {\n return {\n id: `${providerId}/${model.id}`,\n name: model.name,\n provider: providerId,\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxOutputTokens ?? 4096,\n reasoning: false,\n vision: model.supportsImages ?? false,\n cost: { input: model.pricing?.input ?? 0, output: model.pricing?.output ?? 0 },\n available,\n source: 'extension' as const,\n };\n}\n\nexport function registerModelsRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n // GET /api/models-json - Get models.json configuration\n authenticated.get('/api/models-json', async (c) => {\n const path = getModelsJsonPath();\n const { config, error } = loadModelsJson(path);\n const registry = getModelRegistry();\n \n return c.json({\n ok: true,\n payload: {\n config,\n path,\n exists: error === undefined,\n loadError: error || registry.getError(),\n },\n });\n });\n\n // POST /api/models-json/validate - Validate models.json configuration\n authenticated.post('/api/models-json/validate', async (c) => {\n const body = await c.req.json();\n const { config } = body;\n \n const result = validateModelsConfig(config);\n \n return c.json({\n ok: true,\n payload: result,\n });\n });\n\n // PATCH /api/models-json - Save models.json configuration\n authenticated.patch('/api/models-json', async (c) => {\n const body = await c.req.json();\n const { config } = body;\n \n const path = getModelsJsonPath();\n const result = saveModelsJson(path, config);\n \n if (!result.success) {\n return c.json({ ok: false, error: result.error }, 400);\n }\n \n // Refresh registry\n const registry = getModelRegistry();\n registry.refresh();\n \n // Emit event\n service.emit('models-json.updated', { \n modelCount: registry.getAll().length,\n });\n \n return c.json({ \n ok: true, \n payload: { \n saved: true,\n modelCount: registry.getAll().length,\n },\n });\n });\n\n // POST /api/models-json/reload - Hot reload models.json\n authenticated.post('/api/models-json/reload', async (c) => {\n const registry = getModelRegistry();\n registry.refresh();\n \n const error = registry.getError();\n const models = registry.getAll();\n \n service.emit('models-json.reloaded', { \n modelCount: models.length,\n error: error || undefined,\n });\n \n return c.json({\n ok: true,\n payload: {\n modelCount: models.length,\n error,\n },\n });\n });\n\n // POST /api/models-json/test-api-key - Test API key resolution\n authenticated.post('/api/models-json/test-api-key', async (c) => {\n const body = await c.req.json();\n const { value } = body;\n \n const result = testApiKeyResolution(value);\n \n return c.json({\n ok: true,\n payload: result,\n });\n });\n\n // GET /api/models - Get available models (only configured providers)\n authenticated.get('/api/models', async (c) => {\n if (!service.isGatewayReady()) {\n return respondStartupUnavailable(c, 'models.list');\n }\n const pluginRegistry = getProviderRegistry();\n const models = (await getAvailableModels()).map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name,\n provider: m.provider,\n contextWindow: m.contextWindow ?? 128000,\n maxTokens: m.maxTokens ?? 4096,\n reasoning: m.reasoning ?? false,\n vision: m.input?.includes('image') ?? false,\n cost: {\n input: m.cost?.input ?? 0,\n output: m.cost?.output ?? 0,\n },\n ...(pluginRegistry.has(m.provider) ? { source: 'extension' as const } : {}),\n }));\n\n const existingIds = new Set(models.map(m => m.id));\n for (const plugin of pluginRegistry.listAll()) {\n for (const model of plugin.models) {\n const compositeId = `${plugin.id}/${model.id}`;\n if (!existingIds.has(compositeId)) {\n models.push(mapPluginModel(plugin.id, model, true));\n existingIds.add(compositeId);\n }\n }\n }\n\n // Sort by provider then name\n models.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);\n return a.name.localeCompare(b.name);\n });\n\n return c.json({ ok: true, payload: { models } });\n });\n\n // GET /api/image/providers — registered image generation providers and models (not in LLM model registry)\n authenticated.get('/api/image/providers', (c) => {\n const cfg = deps.service.currentConfig;\n const summaries = listImageGenerationProvidersSummary(cfg);\n const providers = summaries.map((p) => {\n const provider = getImageGenerationProvider(p.id, cfg);\n let configured = false;\n try {\n configured = provider?.isConfigured?.({ cfg }) === true;\n } catch {\n configured = false;\n }\n return { ...p, configured };\n });\n return c.json({ ok: true, payload: { providers } });\n });\n\n // POST /api/image/providers/:id/test — lightweight credential probe; does NOT\n // hit the vendor (no quota burn). Returns `{ ok, configured, reason }`.\n authenticated.post('/api/image/providers/:id/test', (c) => {\n const id = c.req.param('id');\n const cfg = deps.service.currentConfig;\n const provider = getImageGenerationProvider(id, cfg);\n if (!provider) {\n return c.json(\n { ok: false, error: { message: `Image generation provider not found: ${id}` } },\n 404,\n );\n }\n let configured = false;\n let reason: string | undefined;\n try {\n configured = provider.isConfigured?.({ cfg }) === true;\n if (!configured) reason = 'Missing API key (set via config or environment).';\n } catch (err) {\n reason = err instanceof Error ? err.message : String(err);\n }\n return c.json({\n ok: true,\n payload: {\n id: provider.id,\n configured,\n ...(reason ? { reason } : {}),\n defaultModel: provider.defaultModel ?? null,\n },\n });\n });\n\n /**\n * POST /api/image/providers/:id/reveal-api-key — return `cfg.providers.<id>.apiKey` plaintext for the\n * gateway console (same auth as PATCH /api/config). Does not resolve env vars or credential files.\n */\n authenticated.post(\n '/api/image/providers/:id/reveal-api-key',\n strictRateLimitMiddleware,\n async (c) => {\n const rawId = c.req.param('id');\n const cfg = deps.service.currentConfig;\n const provider = getImageGenerationProvider(rawId, cfg);\n if (!provider) {\n return c.json(\n { ok: false, error: { message: `Image generation provider not found: ${rawId}` } },\n 404,\n );\n }\n const apiKey = readProviderApiKeyFromConfigFileOnly(cfg, provider.id);\n return c.json({\n ok: true,\n payload: {\n id: provider.id,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n },\n );\n\n // GET /api/providers - Get ALL available providers and models\n authenticated.get('/api/providers', async (c) => {\n const pluginRegistry = getProviderRegistry();\n const allModels = getAllModels();\n const availableModels = await getAvailableModels();\n const configured = new Set(availableModels.map(m => `${m.provider}/${m.id}`));\n\n const models = allModels.map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name,\n provider: m.provider,\n contextWindow: m.contextWindow ?? 128000,\n maxTokens: m.maxTokens ?? 4096,\n reasoning: m.reasoning ?? false,\n vision: m.input?.includes('image') ?? false,\n cost: {\n input: m.cost?.input ?? 0,\n output: m.cost?.output ?? 0,\n },\n available: configured.has(`${m.provider}/${m.id}`),\n ...(pluginRegistry.has(m.provider) ? { source: 'extension' as const } : {}),\n }));\n\n const existingIds = new Set(models.map(m => m.id));\n for (const plugin of pluginRegistry.listAll()) {\n for (const model of plugin.models) {\n const compositeId = `${plugin.id}/${model.id}`;\n if (!existingIds.has(compositeId)) {\n models.push(mapPluginModel(plugin.id, model, configured.has(compositeId)));\n existingIds.add(compositeId);\n }\n }\n }\n\n // Sort by provider then name\n models.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);\n return a.name.localeCompare(b.name);\n });\n\n return c.json({ ok: true, payload: { models } });\n });\n\n // GET /api/providers/meta - Get provider metadata (categories, display names)\n authenticated.get('/api/providers/meta', async (c) => {\n const providers = getAllProviders();\n const pluginRegistry = getProviderRegistry();\n\n const meta = await Promise.all(\n providers.map(async (provider) => {\n const plugin = pluginRegistry.get(provider);\n return {\n id: provider,\n name: plugin?.name ?? PROVIDER_META[provider]?.name ?? provider,\n category: plugin ? ('extension' as const) : PROVIDER_META[provider]?.category || 'specialty',\n supportsOAuth: plugin ? false : (PROVIDER_META[provider]?.supportsOAuth ?? false),\n supportsApiKey: plugin ? true : (PROVIDER_META[provider]?.supportsApiKey ?? true),\n configured: await isProviderConfigured(provider),\n activeKeySource: await getProviderActiveKeySource(provider),\n };\n }),\n );\n\n const knownProviderIds = new Set(providers);\n for (const plugin of pluginRegistry.listAll()) {\n if (!knownProviderIds.has(plugin.id)) {\n meta.push({\n id: plugin.id,\n name: plugin.name,\n category: 'extension',\n supportsOAuth: false,\n supportsApiKey: true,\n configured: true,\n activeKeySource: 'extension',\n });\n }\n }\n\n return c.json({ ok: true, payload: { providers: meta } });\n });\n\n // DELETE /api/providers/:providerId/key - Remove a provider's stored API key\n authenticated.delete('/api/providers/:providerId/key', strictRateLimitMiddleware, async (c) => {\n const providerId = c.req.param('providerId');\n if (!providerId) {\n return c.json({ ok: false, error: { message: 'Missing providerId' } }, 400);\n }\n\n const normalizedProvider = providerId.toLowerCase();\n const profileId = `${normalizedProvider}:default`;\n const resolver = new CredentialResolver();\n\n try {\n await resolver.deleteProfile(profileId);\n return c.json({ ok: true, payload: { deleted: normalizedProvider } });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return c.json({ ok: false, error: { message: `Failed to delete key: ${errorMessage}` } }, 500);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;kBAOwC;2BAEuC;gBAa1C;kBAC6B;sBACU;;AAM5E,SAAS,qCAAqC,KAAa,YAAwC;CACjG,MAAM,KAAK,WAAW,MAAM,CAAC,aAAa;CAC1C,MAAM,SAAS,IAAI,YAAY;AAC/B,KAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CAAE,QAAO,KAAA;CAC3E,MAAM,IAAK,OAAgC;AAC3C,QAAO,OAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,MAAM,GAAG,KAAA;;AAGxD,SAAS,eAAe,YAAoB,OAAgC,WAAoB;AAC9F,QAAO;EACL,IAAI,GAAG,WAAW,GAAG,MAAM;EAC3B,MAAM,MAAM;EACZ,UAAU;EACV,eAAe,MAAM,iBAAiB;EACtC,WAAW,MAAM,mBAAmB;EACpC,WAAW;EACX,QAAQ,MAAM,kBAAkB;EAChC,MAAM;GAAE,OAAO,MAAM,SAAS,SAAS;GAAG,QAAQ,MAAM,SAAS,UAAU;GAAG;EAC9E;EACA,QAAQ;EACT;;AAGH,SAAgB,qBAAqB,eAAqB,MAAoC;CAC5F,MAAM,EAAE,SAAS,8BAA8B;AAG/C,eAAc,IAAI,oBAAoB,OAAO,MAAM;EACjD,MAAM,OAAOA,uBAAmB;EAChC,MAAM,EAAE,QAAQ,UAAU,eAAe,KAAK;EAC9C,MAAM,WAAW,kBAAkB;AAEnC,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP;IACA;IACA,QAAQ,UAAU,KAAA;IAClB,WAAW,SAAS,SAAS,UAAU;IACxC;GACF,CAAC;GACF;AAGF,eAAc,KAAK,6BAA6B,OAAO,MAAM;EAE3D,MAAM,EAAE,WAAW,MADA,EAAE,IAAI,MAAM;EAG/B,MAAM,SAAS,qBAAqB,OAAO;AAE3C,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;GACV,CAAC;GACF;AAGF,eAAc,MAAM,oBAAoB,OAAO,MAAM;EAEnD,MAAM,EAAE,WAAW,MADA,EAAE,IAAI,MAAM;EAI/B,MAAM,SAAS,eADFA,uBACqB,EAAE,OAAO;AAE3C,MAAI,CAAC,OAAO,QACV,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,OAAO;GAAO,EAAE,IAAI;EAIxD,MAAM,WAAW,kBAAkB;AACnC,WAAS,SAAS;AAGlB,UAAQ,KAAK,uBAAuB,EAClC,YAAY,SAAS,QAAQ,CAAC,QAC/B,CAAC;AAEF,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,OAAO;IACP,YAAY,SAAS,QAAQ,CAAC;IAC/B;GACF,CAAC;GACF;AAGF,eAAc,KAAK,2BAA2B,OAAO,MAAM;EACzD,MAAM,WAAW,kBAAkB;AACnC,WAAS,SAAS;EAElB,MAAM,QAAQ,SAAS,UAAU;EACjC,MAAM,SAAS,SAAS,QAAQ;AAEhC,UAAQ,KAAK,wBAAwB;GACnC,YAAY,OAAO;GACnB,OAAO,SAAS,KAAA;GACjB,CAAC;AAEF,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,YAAY,OAAO;IACnB;IACD;GACF,CAAC;GACF;AAGF,eAAc,KAAK,iCAAiC,OAAO,MAAM;EAE/D,MAAM,EAAE,UAAU,MADC,EAAE,IAAI,MAAM;EAG/B,MAAM,SAAS,qBAAqB,MAAM;AAE1C,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;GACV,CAAC;GACF;AAGF,eAAc,IAAI,eAAe,OAAO,MAAM;AAC5C,MAAI,CAAC,QAAQ,gBAAgB,CAC3B,QAAO,0BAA0B,GAAG,cAAc;EAEpD,MAAM,iBAAiB,qBAAqB;EAC5C,MAAM,UAAU,MAAM,oBAAoB,EAAE,KAAI,OAAM;GACpD,IAAI,GAAG,EAAE,SAAS,GAAG,EAAE;GACvB,MAAM,EAAE;GACR,UAAU,EAAE;GACZ,eAAe,EAAE,iBAAiB;GAClC,WAAW,EAAE,aAAa;GAC1B,WAAW,EAAE,aAAa;GAC1B,QAAQ,EAAE,OAAO,SAAS,QAAQ,IAAI;GACtC,MAAM;IACJ,OAAO,EAAE,MAAM,SAAS;IACxB,QAAQ,EAAE,MAAM,UAAU;IAC3B;GACD,GAAI,eAAe,IAAI,EAAE,SAAS,GAAG,EAAE,QAAQ,aAAsB,GAAG,EAAE;GAC3E,EAAE;EAEH,MAAM,cAAc,IAAI,IAAI,OAAO,KAAI,MAAK,EAAE,GAAG,CAAC;AAClD,OAAK,MAAM,UAAU,eAAe,SAAS,CAC3C,MAAK,MAAM,SAAS,OAAO,QAAQ;GACjC,MAAM,cAAc,GAAG,OAAO,GAAG,GAAG,MAAM;AAC1C,OAAI,CAAC,YAAY,IAAI,YAAY,EAAE;AACjC,WAAO,KAAK,eAAe,OAAO,IAAI,OAAO,KAAK,CAAC;AACnD,gBAAY,IAAI,YAAY;;;AAMlC,SAAO,MAAM,GAAG,MAAM;AACpB,OAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,SAAS,cAAc,EAAE,SAAS;AAC1E,UAAO,EAAE,KAAK,cAAc,EAAE,KAAK;IACnC;AAEF,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,QAAQ;GAAE,CAAC;GAChD;AAGF,eAAc,IAAI,yBAAyB,MAAM;EAC/C,MAAM,MAAM,KAAK,QAAQ;EAEzB,MAAM,YADY,oCAAoC,IAC3B,CAAC,KAAK,MAAM;GACrC,MAAM,WAAW,2BAA2B,EAAE,IAAI,IAAI;GACtD,IAAI,aAAa;AACjB,OAAI;AACF,iBAAa,UAAU,eAAe,EAAE,KAAK,CAAC,KAAK;WAC7C;AACN,iBAAa;;AAEf,UAAO;IAAE,GAAG;IAAG;IAAY;IAC3B;AACF,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,WAAW;GAAE,CAAC;GACnD;AAIF,eAAc,KAAK,kCAAkC,MAAM;EACzD,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK;EAC5B,MAAM,MAAM,KAAK,QAAQ;EACzB,MAAM,WAAW,2BAA2B,IAAI,IAAI;AACpD,MAAI,CAAC,SACH,QAAO,EAAE,KACP;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,wCAAwC,MAAM;GAAE,EAC/E,IACD;EAEH,IAAI,aAAa;EACjB,IAAI;AACJ,MAAI;AACF,gBAAa,SAAS,eAAe,EAAE,KAAK,CAAC,KAAK;AAClD,OAAI,CAAC,WAAY,UAAS;WACnB,KAAK;AACZ,YAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAE3D,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,IAAI,SAAS;IACb;IACA,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;IAC5B,cAAc,SAAS,gBAAgB;IACxC;GACF,CAAC;GACF;;;;;AAMF,eAAc,KACZ,2CACA,2BACA,OAAO,MAAM;EACX,MAAM,QAAQ,EAAE,IAAI,MAAM,KAAK;EAC/B,MAAM,MAAM,KAAK,QAAQ;EACzB,MAAM,WAAW,2BAA2B,OAAO,IAAI;AACvD,MAAI,CAAC,SACH,QAAO,EAAE,KACP;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,wCAAwC,SAAS;GAAE,EAClF,IACD;EAEH,MAAM,SAAS,qCAAqC,KAAK,SAAS,GAAG;AACrE,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,IAAI,SAAS;IACb,QAAQ,UAAU;IAClB,QAAQ,SAAU,WAAsB;IACzC;GACF,CAAC;GAEL;AAGD,eAAc,IAAI,kBAAkB,OAAO,MAAM;EAC/C,MAAM,iBAAiB,qBAAqB;EAC5C,MAAM,YAAY,cAAc;EAChC,MAAM,kBAAkB,MAAM,oBAAoB;EAClD,MAAM,aAAa,IAAI,IAAI,gBAAgB,KAAI,MAAK,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,CAAC;EAE7E,MAAM,SAAS,UAAU,KAAI,OAAM;GACjC,IAAI,GAAG,EAAE,SAAS,GAAG,EAAE;GACvB,MAAM,EAAE;GACR,UAAU,EAAE;GACZ,eAAe,EAAE,iBAAiB;GAClC,WAAW,EAAE,aAAa;GAC1B,WAAW,EAAE,aAAa;GAC1B,QAAQ,EAAE,OAAO,SAAS,QAAQ,IAAI;GACtC,MAAM;IACJ,OAAO,EAAE,MAAM,SAAS;IACxB,QAAQ,EAAE,MAAM,UAAU;IAC3B;GACD,WAAW,WAAW,IAAI,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK;GAClD,GAAI,eAAe,IAAI,EAAE,SAAS,GAAG,EAAE,QAAQ,aAAsB,GAAG,EAAE;GAC3E,EAAE;EAEH,MAAM,cAAc,IAAI,IAAI,OAAO,KAAI,MAAK,EAAE,GAAG,CAAC;AAClD,OAAK,MAAM,UAAU,eAAe,SAAS,CAC3C,MAAK,MAAM,SAAS,OAAO,QAAQ;GACjC,MAAM,cAAc,GAAG,OAAO,GAAG,GAAG,MAAM;AAC1C,OAAI,CAAC,YAAY,IAAI,YAAY,EAAE;AACjC,WAAO,KAAK,eAAe,OAAO,IAAI,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC;AAC1E,gBAAY,IAAI,YAAY;;;AAMlC,SAAO,MAAM,GAAG,MAAM;AACpB,OAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,SAAS,cAAc,EAAE,SAAS;AAC1E,UAAO,EAAE,KAAK,cAAc,EAAE,KAAK;IACnC;AAEF,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,QAAQ;GAAE,CAAC;GAChD;AAGF,eAAc,IAAI,uBAAuB,OAAO,MAAM;EACpD,MAAM,YAAY,iBAAiB;EACnC,MAAM,iBAAiB,qBAAqB;EAE5C,MAAM,OAAO,MAAM,QAAQ,IACzB,UAAU,IAAI,OAAO,aAAa;GAChC,MAAM,SAAS,eAAe,IAAI,SAAS;AAC3C,UAAO;IACL,IAAI;IACJ,MAAM,QAAQ,QAAQ,cAAc,WAAW,QAAQ;IACvD,UAAU,SAAU,cAAwB,cAAc,WAAW,YAAY;IACjF,eAAe,SAAS,QAAS,cAAc,WAAW,iBAAiB;IAC3E,gBAAgB,SAAS,OAAQ,cAAc,WAAW,kBAAkB;IAC5E,YAAY,MAAM,qBAAqB,SAAS;IAChD,iBAAiB,MAAM,2BAA2B,SAAS;IAC5D;IACD,CACH;EAED,MAAM,mBAAmB,IAAI,IAAI,UAAU;AAC3C,OAAK,MAAM,UAAU,eAAe,SAAS,CAC3C,KAAI,CAAC,iBAAiB,IAAI,OAAO,GAAG,CAClC,MAAK,KAAK;GACR,IAAI,OAAO;GACX,MAAM,OAAO;GACb,UAAU;GACV,eAAe;GACf,gBAAgB;GAChB,YAAY;GACZ,iBAAiB;GAClB,CAAC;AAIN,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,WAAW,MAAM;GAAE,CAAC;GACzD;AAGF,eAAc,OAAO,kCAAkC,2BAA2B,OAAO,MAAM;EAC7F,MAAM,aAAa,EAAE,IAAI,MAAM,aAAa;AAC5C,MAAI,CAAC,WACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,sBAAsB;GAAE,EAAE,IAAI;EAG7E,MAAM,qBAAqB,WAAW,aAAa;EACnD,MAAM,YAAY,GAAG,mBAAmB;EACxC,MAAM,WAAW,IAAI,oBAAoB;AAEzC,MAAI;AACF,SAAM,SAAS,cAAc,UAAU;AACvC,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM,SAAS,EAAE,SAAS,oBAAoB;IAAE,CAAC;WAC9D,OAAO;GACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,yBAAyB,gBAAgB;IAAE,EAAE,IAAI;;GAEhG"}
1
+ {"version":3,"file":"models.js","names":["getModelsJsonPath"],"sources":["../../../../../src/gateway/hono/routes/models.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport {\n getModelsJsonPath,\n loadModelsJson,\n saveModelsJson,\n validateModelsConfig,\n} from '../../../config/models-json.js';\nimport type { Config } from '../../../config/schema.js';\nimport { testApiKeyResolution } from '../../../config/resolve-config-value.js';\nimport {\n getImageGenerationProvider,\n listImageGenerationProvidersSummary,\n} from '../../../agent/image/generation/runtime.js';\nimport {\n EXTENSION_PROVIDER_BASE_URL,\n getAllModels,\n getAvailableModels,\n getModelRegistry,\n getAllProviders,\n getProviderAuthState,\n isProviderConfigured,\n PROVIDER_META,\n} from '../../../providers/index.js';\nimport { CredentialResolver } from '../../../auth/credentials.js';\nimport { getProviderRegistry } from '../../../providers/plugin-registry.js';\nimport type { ProviderModelDefinition } from '../../../extensions/types/providers.js';\nimport type { GatewayService } from '../../service.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport { respondStartupUnavailable } from '../lib/startup-unavailable.js';\n\nfunction readModelsJsonProviderApiKey(providerId: string): string | undefined {\n const { config } = loadModelsJson(getModelsJsonPath());\n const entry = config.providers?.[providerId.trim()];\n const key = entry?.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\n/** Plaintext key only when persisted under `cfg.providers.<id>.apiKey` (not env / credential store). */\nfunction readProviderApiKeyFromConfigFileOnly(cfg: Config, providerId: string): string | undefined {\n const id = providerId.trim().toLowerCase();\n const bucket = cfg.providers?.[id];\n if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) return undefined;\n const k = (bucket as { apiKey?: unknown }).apiKey;\n return typeof k === 'string' && k.trim() ? k.trim() : undefined;\n}\n\n/** Extension id from manifest `providers[]` (e.g. provider `demo` → extension `demo-provider`). */\nfunction resolveExtensionIdForProvider(service: GatewayService, providerId: string): string | undefined {\n const loader = service.getExtensionLoader();\n if (!loader) return undefined;\n return loader.buildManifestRegistry().findByProvider(providerId)?.id;\n}\n\n/** Effective LLM REST base URL for a provider (models.json overrides included). */\nfunction resolveProviderApiBaseUrl(providerId: string): string | undefined {\n const model = getModelRegistry().getAll().find((m) => m.provider === providerId);\n if (!model?.baseUrl || model.baseUrl === EXTENSION_PROVIDER_BASE_URL) return undefined;\n return model.baseUrl;\n}\n\nfunction mapPluginModel(providerId: string, model: ProviderModelDefinition, available: boolean) {\n return {\n id: `${providerId}/${model.id}`,\n name: model.name,\n provider: providerId,\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxOutputTokens ?? 4096,\n reasoning: false,\n vision: model.supportsImages ?? false,\n cost: { input: model.pricing?.input ?? 0, output: model.pricing?.output ?? 0 },\n available,\n source: 'extension' as const,\n };\n}\n\nexport function registerModelsRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n // GET /api/models-json - Get models.json configuration\n authenticated.get('/api/models-json', async (c) => {\n const path = getModelsJsonPath();\n const { config, error } = loadModelsJson(path);\n const registry = getModelRegistry();\n \n return c.json({\n ok: true,\n payload: {\n config,\n path,\n exists: error === undefined,\n loadError: error || registry.getError(),\n },\n });\n });\n\n // POST /api/models-json/validate - Validate models.json configuration\n authenticated.post('/api/models-json/validate', async (c) => {\n const body = await c.req.json();\n const { config } = body;\n \n const result = validateModelsConfig(config);\n \n return c.json({\n ok: true,\n payload: result,\n });\n });\n\n // PATCH /api/models-json - Save models.json configuration\n authenticated.patch('/api/models-json', async (c) => {\n const body = await c.req.json();\n const { config } = body;\n \n const path = getModelsJsonPath();\n const result = saveModelsJson(path, config);\n \n if (!result.success) {\n return c.json({ ok: false, error: result.error }, 400);\n }\n \n // Refresh registry\n const registry = getModelRegistry();\n registry.refresh();\n \n // Emit event\n service.emit('models-json.updated', { \n modelCount: registry.getAll().length,\n });\n \n return c.json({ \n ok: true, \n payload: { \n saved: true,\n modelCount: registry.getAll().length,\n },\n });\n });\n\n // POST /api/models-json/reload - Hot reload models.json\n authenticated.post('/api/models-json/reload', async (c) => {\n const registry = getModelRegistry();\n registry.refresh();\n \n const error = registry.getError();\n const models = registry.getAll();\n \n service.emit('models-json.reloaded', { \n modelCount: models.length,\n error: error || undefined,\n });\n \n return c.json({\n ok: true,\n payload: {\n modelCount: models.length,\n error,\n },\n });\n });\n\n // POST /api/models-json/test-api-key - Test API key resolution\n authenticated.post('/api/models-json/test-api-key', async (c) => {\n const body = await c.req.json();\n const { value } = body;\n \n const result = testApiKeyResolution(value);\n \n return c.json({\n ok: true,\n payload: result,\n });\n });\n\n // GET /api/models - Get available models (only configured providers)\n authenticated.get('/api/models', async (c) => {\n if (!service.isGatewayReady()) {\n return respondStartupUnavailable(c, 'models.list');\n }\n const pluginRegistry = getProviderRegistry();\n const models = (await getAvailableModels()).map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name,\n provider: m.provider,\n contextWindow: m.contextWindow ?? 128000,\n maxTokens: m.maxTokens ?? 4096,\n reasoning: m.reasoning ?? false,\n vision: m.input?.includes('image') ?? false,\n cost: {\n input: m.cost?.input ?? 0,\n output: m.cost?.output ?? 0,\n },\n ...(pluginRegistry.has(m.provider) ? { source: 'extension' as const } : {}),\n }));\n\n const existingIds = new Set(models.map(m => m.id));\n for (const plugin of pluginRegistry.listAll()) {\n for (const model of plugin.models) {\n const compositeId = `${plugin.id}/${model.id}`;\n if (!existingIds.has(compositeId)) {\n models.push(mapPluginModel(plugin.id, model, true));\n existingIds.add(compositeId);\n }\n }\n }\n\n // Sort by provider then name\n models.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);\n return a.name.localeCompare(b.name);\n });\n\n return c.json({ ok: true, payload: { models } });\n });\n\n // GET /api/image/providers — registered image generation providers and models (not in LLM model registry)\n authenticated.get('/api/image/providers', (c) => {\n const cfg = deps.service.currentConfig;\n const summaries = listImageGenerationProvidersSummary(cfg);\n const providers = summaries.map((p) => {\n const provider = getImageGenerationProvider(p.id, cfg);\n let configured = false;\n try {\n configured = provider?.isConfigured?.({ cfg }) === true;\n } catch {\n configured = false;\n }\n return { ...p, configured };\n });\n return c.json({ ok: true, payload: { providers } });\n });\n\n // POST /api/image/providers/:id/test — lightweight credential probe; does NOT\n // hit the vendor (no quota burn). Returns `{ ok, configured, reason }`.\n authenticated.post('/api/image/providers/:id/test', (c) => {\n const id = c.req.param('id');\n const cfg = deps.service.currentConfig;\n const provider = getImageGenerationProvider(id, cfg);\n if (!provider) {\n return c.json(\n { ok: false, error: { message: `Image generation provider not found: ${id}` } },\n 404,\n );\n }\n let configured = false;\n let reason: string | undefined;\n try {\n configured = provider.isConfigured?.({ cfg }) === true;\n if (!configured) reason = 'Missing API key (set via config or environment).';\n } catch (err) {\n reason = err instanceof Error ? err.message : String(err);\n }\n return c.json({\n ok: true,\n payload: {\n id: provider.id,\n configured,\n ...(reason ? { reason } : {}),\n defaultModel: provider.defaultModel ?? null,\n },\n });\n });\n\n /**\n * POST /api/image/providers/:id/reveal-api-key — return `cfg.providers.<id>.apiKey` plaintext for the\n * gateway console (same auth as PATCH /api/config). Does not resolve env vars or credential files.\n */\n authenticated.post(\n '/api/image/providers/:id/reveal-api-key',\n strictRateLimitMiddleware,\n async (c) => {\n const rawId = c.req.param('id');\n const cfg = deps.service.currentConfig;\n const provider = getImageGenerationProvider(rawId, cfg);\n if (!provider) {\n return c.json(\n { ok: false, error: { message: `Image generation provider not found: ${rawId}` } },\n 404,\n );\n }\n const apiKey = readProviderApiKeyFromConfigFileOnly(cfg, provider.id);\n return c.json({\n ok: true,\n payload: {\n id: provider.id,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n },\n );\n\n // GET /api/providers - Get ALL available providers and models\n authenticated.get('/api/providers', async (c) => {\n const pluginRegistry = getProviderRegistry();\n const allModels = getAllModels();\n const availableModels = await getAvailableModels();\n const configured = new Set(availableModels.map(m => `${m.provider}/${m.id}`));\n\n const models = allModels.map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name,\n provider: m.provider,\n contextWindow: m.contextWindow ?? 128000,\n maxTokens: m.maxTokens ?? 4096,\n reasoning: m.reasoning ?? false,\n vision: m.input?.includes('image') ?? false,\n cost: {\n input: m.cost?.input ?? 0,\n output: m.cost?.output ?? 0,\n },\n available: configured.has(`${m.provider}/${m.id}`),\n ...(pluginRegistry.has(m.provider) ? { source: 'extension' as const } : {}),\n }));\n\n const existingIds = new Set(models.map(m => m.id));\n for (const plugin of pluginRegistry.listAll()) {\n for (const model of plugin.models) {\n const compositeId = `${plugin.id}/${model.id}`;\n if (!existingIds.has(compositeId)) {\n models.push(mapPluginModel(plugin.id, model, configured.has(compositeId)));\n existingIds.add(compositeId);\n }\n }\n }\n\n // Sort by provider then name\n models.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);\n return a.name.localeCompare(b.name);\n });\n\n return c.json({ ok: true, payload: { models } });\n });\n\n // GET /api/providers/meta - Get provider metadata (categories, display names)\n authenticated.get('/api/providers/meta', async (c) => {\n const providers = getAllProviders();\n const pluginRegistry = getProviderRegistry();\n\n const meta = await Promise.all(\n providers.map(async (provider) => {\n const plugin = pluginRegistry.get(provider);\n const extensionId = plugin\n ? resolveExtensionIdForProvider(service, provider)\n : undefined;\n const authState = await getProviderAuthState(provider);\n const configured = authState.authMode !== 'none' || (await isProviderConfigured(provider));\n return {\n id: provider,\n name: plugin?.name ?? PROVIDER_META[provider]?.name ?? provider,\n category: plugin ? ('extension' as const) : PROVIDER_META[provider]?.category || 'specialty',\n supportsOAuth: plugin ? false : (PROVIDER_META[provider]?.supportsOAuth ?? false),\n supportsApiKey: plugin ? false : (PROVIDER_META[provider]?.supportsApiKey ?? true),\n configured,\n activeKeySource: authState.authMode,\n authMode: authState.authMode,\n authStatus: authState.authStatus,\n ...(authState.expiresAt ? { expiresAt: authState.expiresAt } : {}),\n baseUrl: resolveProviderApiBaseUrl(provider),\n ...(extensionId ? { extensionId } : {}),\n };\n }),\n );\n\n const knownProviderIds = new Set(providers);\n for (const plugin of pluginRegistry.listAll()) {\n if (!knownProviderIds.has(plugin.id)) {\n const extensionId = resolveExtensionIdForProvider(service, plugin.id);\n meta.push({\n id: plugin.id,\n name: plugin.name,\n category: 'extension',\n supportsOAuth: false,\n supportsApiKey: false,\n configured: true,\n activeKeySource: 'extension',\n authMode: 'extension',\n authStatus: 'connected',\n baseUrl: resolveProviderApiBaseUrl(plugin.id),\n ...(extensionId ? { extensionId } : {}),\n });\n }\n }\n\n return c.json({ ok: true, payload: { providers: meta } });\n });\n\n /**\n * POST /api/providers/:providerId/reveal-api-key — plaintext key when stored in the\n * gateway credential store or models.json (not env vars or OAuth tokens).\n */\n authenticated.post(\n '/api/providers/:providerId/reveal-api-key',\n strictRateLimitMiddleware,\n async (c) => {\n const rawId = c.req.param('providerId')?.trim();\n if (!rawId) {\n return c.json({ ok: false, error: { message: 'Missing providerId' } }, 400);\n }\n const providerId = rawId.toLowerCase();\n const resolver = new CredentialResolver();\n const stored = await resolver.revealGatewayStoredApiKey(providerId);\n if (stored) {\n return c.json({\n ok: true,\n payload: { id: providerId, apiKey: stored, source: 'credential' as const },\n });\n }\n const fromModelsJson = readModelsJsonProviderApiKey(providerId);\n if (fromModelsJson) {\n return c.json({\n ok: true,\n payload: { id: providerId, apiKey: fromModelsJson, source: 'models_json' as const },\n });\n }\n return c.json({\n ok: true,\n payload: { id: providerId, apiKey: null, source: 'none' as const },\n });\n },\n );\n\n // DELETE /api/providers/:providerId/key - Remove a provider's stored API key\n authenticated.delete('/api/providers/:providerId/key', strictRateLimitMiddleware, async (c) => {\n const providerId = c.req.param('providerId');\n if (!providerId) {\n return c.json({ ok: false, error: { message: 'Missing providerId' } }, 400);\n }\n\n const normalizedProvider = providerId.toLowerCase();\n const resolver = new CredentialResolver();\n\n try {\n await resolver.deleteProviderCredential(normalizedProvider);\n return c.json({ ok: true, payload: { deleted: normalizedProvider } });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return c.json({ ok: false, error: { message: `Failed to delete key: ${errorMessage}` } }, 500);\n }\n });\n}\n"],"mappings":"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"}
@@ -2,12 +2,16 @@ import { createLogger } from "../../../utils/logger/index.js";
2
2
  import { init_logger } from "../../../utils/logger.js";
3
3
  import { getDefaultModelSync, init_providers, resolveModel } from "../../../providers/index.js";
4
4
  import { resolveTtsProviderConfigSlice } from "../../../voice/tts/config-slice.js";
5
+ import { isTTSAvailable } from "../../../voice/tts/factory.js";
6
+ import { speak } from "../../../voice/tts/speak-core.js";
7
+ import { mergeTtsConfigFromAppConfig } from "../../../voice/tts/merge-config.js";
5
8
  import { resolveSttProviderConfigSlice } from "../../../voice/stt/config-slice.js";
6
9
  import { transcribe } from "../../../voice/stt/transcribe-core.js";
7
10
  import { isSTTAvailable } from "../../../voice/stt/availability.js";
8
11
  import "../../../voice/stt/index.js";
9
12
  import { mergeSttConfigFromAppConfig } from "../../../channels/attachments/voice-stt-webchat.js";
10
13
  import { listSttProvidersForApi } from "../../../voice/stt/list-providers.js";
14
+ import "../../../voice/tts/index.js";
11
15
  import { listTtsProvidersForApi } from "../../../voice/tts/list-providers.js";
12
16
  import { complete } from "@earendil-works/pi-ai";
13
17
  //#region src/gateway/hono/routes/voice.ts
@@ -145,6 +149,77 @@ function registerVoiceRoutes(authenticated, deps) {
145
149
  });
146
150
  });
147
151
  /**
152
+ * POST /api/voice/tts-test
153
+ *
154
+ * Body: { text: string, provider?: string, model?: string, voice?: string }
155
+ * Response: { ok: true, payload: { audio: string, format: string, provider: string } }
156
+ */
157
+ authenticated.post("/api/voice/tts-test", strictRateLimitMiddleware, async (c) => {
158
+ let body = {};
159
+ try {
160
+ body = await c.req.json();
161
+ } catch {
162
+ return c.json({
163
+ ok: false,
164
+ error: { message: "Invalid JSON body" }
165
+ }, 400);
166
+ }
167
+ const text = typeof body.text === "string" ? body.text.trim() : "";
168
+ if (!text) return c.json({
169
+ ok: false,
170
+ error: { message: "text is required" }
171
+ }, 400);
172
+ if (text.length > 1e3) return c.json({
173
+ ok: false,
174
+ error: { message: "text exceeds 1000 characters" }
175
+ }, 400);
176
+ const config = service.currentConfig;
177
+ const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);
178
+ const provider = typeof body.provider === "string" && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;
179
+ const ttsConfig = {
180
+ ...baseTtsConfig,
181
+ enabled: true,
182
+ provider,
183
+ fallback: {
184
+ enabled: false,
185
+ order: [provider]
186
+ }
187
+ };
188
+ if (!isTTSAvailable(ttsConfig)) return c.json({
189
+ ok: false,
190
+ error: { message: `TTS provider "${provider}" is not configured.` }
191
+ }, 503);
192
+ try {
193
+ const result = await speak(text, ttsConfig, {
194
+ appConfig: config,
195
+ parseDirectives: false,
196
+ tts: {
197
+ ...typeof body.model === "string" && body.model.trim() ? { model: body.model.trim() } : {},
198
+ ...typeof body.voice === "string" && body.voice.trim() ? { voice: body.voice.trim() } : {}
199
+ }
200
+ });
201
+ return c.json({
202
+ ok: true,
203
+ payload: {
204
+ audio: result.audio.toString("base64"),
205
+ format: result.format,
206
+ provider: result.provider,
207
+ ...result.duration !== void 0 ? { duration: result.duration } : {}
208
+ }
209
+ });
210
+ } catch (error) {
211
+ const msg = error instanceof Error ? error.message : String(error);
212
+ log.error({
213
+ errorMessage: msg,
214
+ provider
215
+ }, "Voice TTS test failed");
216
+ return c.json({
217
+ ok: false,
218
+ error: { message: `TTS test failed: ${msg}` }
219
+ }, 502);
220
+ }
221
+ });
222
+ /**
148
223
  * POST /api/voice/transcribe
149
224
  *
150
225
  * Body: { audio: string (base64), mimeType: string, language?: string }