@xopcai/xopc 0.0.88 → 0.0.90

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 (275) 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-cPvvYLXo.js +222 -0
  6. package/dist/gateway/static/root/assets/apps-page-Bk1_P5FJ.js +1 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CZoeQwHz.js +1 -0
  8. package/dist/gateway/static/root/assets/{channels-status-swr-DIsl75Y3.js → channels-status-swr-BrtH2VzC.js} +1 -1
  9. package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +1 -0
  10. package/dist/gateway/static/root/assets/cron-api-CyqbgfHM.js +1 -0
  11. package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +2 -0
  12. package/dist/gateway/static/root/assets/cron-page-BpLdiQN8.js +1 -0
  13. package/dist/gateway/static/root/assets/dist-BpAiK86n.js +1 -0
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BVJohZoZ.js → extension-debug-page-D6Ak0STa.js} +1 -1
  15. package/dist/gateway/static/root/assets/{extension-page-BT2tmElC.js → extension-page-Q0P3d6DW.js} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-settings-page-BSS47c2j.js → extension-settings-page-CL55LwU_.js} +1 -1
  17. package/dist/gateway/static/root/assets/eye-DAfL1U7M.js +1 -0
  18. package/dist/gateway/static/root/assets/{fetch-BaFNUtkE.js → fetch-Dqa9iTWl.js} +1 -1
  19. package/dist/gateway/static/root/assets/{field-primitives-QwYEq6Hz.js → field-primitives-HUR6JElP.js} +1 -1
  20. package/dist/gateway/static/root/assets/{heartbeat-config-api-BVSidEDJ.js → heartbeat-config-api-DusckjUX.js} +1 -1
  21. package/dist/gateway/static/root/assets/{index-qNrVJp-y.js → index-BYcGfwcE.js} +97 -97
  22. package/dist/gateway/static/root/assets/index-V7MQ7834.css +1 -0
  23. package/dist/gateway/static/root/assets/{logs-page-DDonPVLn.js → logs-page-_HcZ2fgK.js} +1 -1
  24. package/dist/gateway/static/root/assets/sessions-page-iezSMjho.js +1 -0
  25. package/dist/gateway/static/root/assets/{settings-form-section-B8N3A3Zo.js → settings-form-section-a0qGVOlr.js} +1 -1
  26. package/dist/gateway/static/root/assets/settings-page-C9_nYQwM.js +3 -0
  27. package/dist/gateway/static/root/assets/{share-preview-page-Q7KqkO-u.js → share-preview-page-DExl7CJy.js} +1 -1
  28. package/dist/gateway/static/root/assets/skills-page-BlgGD93t.js +2 -0
  29. package/dist/gateway/static/root/assets/{theme-store-BbRc5ugR.js → theme-store-C0Ehmdo5.js} +1 -1
  30. package/dist/gateway/static/root/assets/url-fxyYANfA.js +3 -0
  31. package/dist/gateway/static/root/assets/{utils-CxDGduqK.js → utils-DRQryzdn.js} +1 -1
  32. package/dist/gateway/static/root/assets/voice-api-key-field-D0viACE2.js +1 -0
  33. package/dist/gateway/static/root/assets/workflow-page.utils-DnG8JBhV.js +1 -0
  34. package/dist/gateway/static/root/assets/workflows-page-BvMobnJP.js +27 -0
  35. package/dist/gateway/static/root/index.html +7 -6
  36. package/dist/package.js +1 -1
  37. package/dist/src/agent/agent-manager.d.ts +2 -0
  38. package/dist/src/agent/agent-manager.js +1 -0
  39. package/dist/src/agent/agent-manager.js.map +1 -1
  40. package/dist/src/agent/service.js +2 -1
  41. package/dist/src/agent/service.js.map +1 -1
  42. package/dist/src/agent/service.types.d.ts +3 -1
  43. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js +20 -18
  44. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js.map +1 -1
  45. package/dist/src/agent/tools/cronjob-tool.d.ts +6 -0
  46. package/dist/src/agent/tools/cronjob-tool.js +76 -10
  47. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  48. package/dist/src/agent/tools/edit.d.ts +5 -1
  49. package/dist/src/agent/tools/edit.js +7 -5
  50. package/dist/src/agent/tools/edit.js.map +1 -1
  51. package/dist/src/agent/tools/factory.d.ts +3 -0
  52. package/dist/src/agent/tools/factory.js +4 -25
  53. package/dist/src/agent/tools/factory.js.map +1 -1
  54. package/dist/src/agent/tools/workflow-tool.d.ts +6 -28
  55. package/dist/src/agent/tools/workflow-tool.js +60 -260
  56. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  57. package/dist/src/agent/tools/write.d.ts +5 -1
  58. package/dist/src/agent/tools/write.js +7 -5
  59. package/dist/src/agent/tools/write.js.map +1 -1
  60. package/dist/src/agent/workflow/agent-progress.js +2 -0
  61. package/dist/src/agent/workflow/agent-progress.js.map +1 -1
  62. package/dist/src/agent/workflow/builtins/client-proposal.d.ts +12 -0
  63. package/dist/src/agent/workflow/builtins/client-proposal.js +155 -0
  64. package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -0
  65. package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +12 -0
  66. package/dist/src/agent/workflow/builtins/competitor-scan.js +150 -0
  67. package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -0
  68. package/dist/src/agent/workflow/builtins/content-draft.d.ts +13 -0
  69. package/dist/src/agent/workflow/builtins/content-draft.js +146 -0
  70. package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -0
  71. package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +11 -0
  72. package/dist/src/agent/workflow/builtins/content-repurpose.js +137 -0
  73. package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -0
  74. package/dist/src/agent/workflow/builtins/decision-compare.d.ts +13 -0
  75. package/dist/src/agent/workflow/builtins/decision-compare.js +173 -0
  76. package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -0
  77. package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +11 -0
  78. package/dist/src/agent/workflow/builtins/inbox-triage.js +148 -0
  79. package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -0
  80. package/dist/src/agent/workflow/builtins/index.d.ts +10 -1
  81. package/dist/src/agent/workflow/builtins/index.js +46 -1
  82. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  83. package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +12 -0
  84. package/dist/src/agent/workflow/builtins/meeting-prep.js +144 -0
  85. package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -0
  86. package/dist/src/agent/workflow/builtins/offer-design.d.ts +12 -0
  87. package/dist/src/agent/workflow/builtins/offer-design.js +161 -0
  88. package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -0
  89. package/dist/src/agent/workflow/builtins/weekly-review.d.ts +12 -0
  90. package/dist/src/agent/workflow/builtins/weekly-review.js +131 -0
  91. package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -0
  92. package/dist/src/agent/workflow/step-labels.js +2 -2
  93. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  94. package/dist/src/agent/workflow/subagent-runner.js +3 -1
  95. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  96. package/dist/src/agent/workflow/types.d.ts +4 -0
  97. package/dist/src/agent/workflow/workflow-child-tools.d.ts +4 -0
  98. package/dist/src/agent/workflow/workflow-child-tools.js +21 -0
  99. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -0
  100. package/dist/src/auth/credentials.d.ts +14 -2
  101. package/dist/src/auth/credentials.js +38 -13
  102. package/dist/src/auth/credentials.js.map +1 -1
  103. package/dist/src/auth/oauth/types.d.ts +16 -0
  104. package/dist/src/chat-commands/agent-edit.d.ts +4 -0
  105. package/dist/src/chat-commands/agent-edit.js +136 -0
  106. package/dist/src/chat-commands/agent-edit.js.map +1 -0
  107. package/dist/src/chat-commands/index.d.ts +1 -0
  108. package/dist/src/chat-commands/index.js +3 -1
  109. package/dist/src/chat-commands/index.js.map +1 -1
  110. package/dist/src/cli/bin.js +2 -0
  111. package/dist/src/cli/bin.js.map +1 -1
  112. package/dist/src/cli/commands/auth.js +6 -0
  113. package/dist/src/cli/commands/auth.js.map +1 -1
  114. package/dist/src/cli/commands/cron.js +42 -3
  115. package/dist/src/cli/commands/cron.js.map +1 -1
  116. package/dist/src/cli/commands/doctor/checks/session-integrity.js +79 -56
  117. package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
  118. package/dist/src/cli/commands/onboard/model.js +6 -0
  119. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  120. package/dist/src/cli/commands/update.js +86 -79
  121. package/dist/src/cli/commands/update.js.map +1 -1
  122. package/dist/src/commands/agents.config.d.ts +3 -2
  123. package/dist/src/commands/agents.config.js +5 -2
  124. package/dist/src/commands/agents.config.js.map +1 -1
  125. package/dist/src/config/agent-typed-models.d.ts +2 -7
  126. package/dist/src/config/agent-typed-models.js +3 -14
  127. package/dist/src/config/agent-typed-models.js.map +1 -1
  128. package/dist/src/config/localized-text.d.ts +6 -0
  129. package/dist/src/config/localized-text.js +42 -0
  130. package/dist/src/config/localized-text.js.map +1 -0
  131. package/dist/src/config/models-json.d.ts +6 -6
  132. package/dist/src/config/schema.d.ts +6 -21
  133. package/dist/src/config/schema.js +4 -4
  134. package/dist/src/config/schema.js.map +1 -1
  135. package/dist/src/cron/executor.d.ts +4 -0
  136. package/dist/src/cron/executor.js +169 -5
  137. package/dist/src/cron/executor.js.map +1 -1
  138. package/dist/src/cron/job-content.js +2 -1
  139. package/dist/src/cron/job-content.js.map +1 -1
  140. package/dist/src/cron/types.d.ts +28 -1
  141. package/dist/src/cron/validation.d.ts +80 -0
  142. package/dist/src/cron/validation.js +30 -4
  143. package/dist/src/cron/validation.js.map +1 -1
  144. package/dist/src/cron/workflow-run-completion.d.ts +23 -0
  145. package/dist/src/cron/workflow-run-completion.js +72 -0
  146. package/dist/src/cron/workflow-run-completion.js.map +1 -0
  147. package/dist/src/extensions/update.d.ts +51 -0
  148. package/dist/src/extensions/update.js +260 -0
  149. package/dist/src/extensions/update.js.map +1 -0
  150. package/dist/src/gateway/agents-admin.d.ts +15 -8
  151. package/dist/src/gateway/agents-admin.js +77 -28
  152. package/dist/src/gateway/agents-admin.js.map +1 -1
  153. package/dist/src/gateway/gateway-workflow-host.types.d.ts +17 -0
  154. package/dist/src/gateway/gateway-workflow-host.types.js +1 -0
  155. package/dist/src/gateway/heartbeat/service.js +1 -1
  156. package/dist/src/gateway/hono/lib/config-payload.d.ts +5 -0
  157. package/dist/src/gateway/hono/lib/config-payload.js +2 -1
  158. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  159. package/dist/src/gateway/hono/middleware/auth.d.ts +2 -0
  160. package/dist/src/gateway/hono/middleware/auth.js +12 -7
  161. package/dist/src/gateway/hono/middleware/auth.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 +55 -12
  167. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  168. package/dist/src/gateway/hono/routes/config-patch/agents.js +1 -1
  169. package/dist/src/gateway/hono/routes/models.js +11 -5
  170. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  171. package/dist/src/gateway/hono/routes/update.js +55 -107
  172. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  173. package/dist/src/gateway/hono/routes/workflows.js +72 -191
  174. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  175. package/dist/src/gateway/server.js +2 -0
  176. package/dist/src/gateway/server.js.map +1 -1
  177. package/dist/src/gateway/service.d.ts +5 -0
  178. package/dist/src/gateway/service.js +24 -3
  179. package/dist/src/gateway/service.js.map +1 -1
  180. package/dist/src/heartbeat/index.js +1 -1
  181. package/dist/src/infra/brew.d.ts +4 -0
  182. package/dist/src/infra/brew.js +20 -0
  183. package/dist/src/infra/brew.js.map +1 -0
  184. package/dist/src/infra/package-json.d.ts +2 -0
  185. package/dist/src/infra/package-json.js +23 -0
  186. package/dist/src/infra/package-json.js.map +1 -0
  187. package/dist/src/infra/package-update-steps.d.ts +35 -0
  188. package/dist/src/infra/package-update-steps.js +304 -0
  189. package/dist/src/infra/package-update-steps.js.map +1 -0
  190. package/dist/src/infra/path-env.d.ts +11 -0
  191. package/dist/src/infra/path-env.js +90 -0
  192. package/dist/src/infra/path-env.js.map +1 -0
  193. package/dist/src/infra/path-prepend.d.ts +7 -0
  194. package/dist/src/infra/path-prepend.js +44 -0
  195. package/dist/src/infra/path-prepend.js.map +1 -0
  196. package/dist/src/infra/stable-node-path.d.ts +2 -0
  197. package/dist/src/infra/stable-node-path.js +28 -0
  198. package/dist/src/infra/stable-node-path.js.map +1 -0
  199. package/dist/src/infra/update-global.d.ts +30 -23
  200. package/dist/src/infra/update-global.js +113 -64
  201. package/dist/src/infra/update-global.js.map +1 -1
  202. package/dist/src/infra/update-log.d.ts +1 -0
  203. package/dist/src/infra/update-log.js +12 -0
  204. package/dist/src/infra/update-log.js.map +1 -0
  205. package/dist/src/infra/update-restart.d.ts +20 -0
  206. package/dist/src/infra/update-restart.js +165 -0
  207. package/dist/src/infra/update-restart.js.map +1 -0
  208. package/dist/src/infra/update-runner.d.ts +89 -1
  209. package/dist/src/infra/update-runner.js +604 -173
  210. package/dist/src/infra/update-runner.js.map +1 -1
  211. package/dist/src/infra/update-startup.d.ts +3 -0
  212. package/dist/src/infra/update-startup.js +8 -4
  213. package/dist/src/infra/update-startup.js.map +1 -1
  214. package/dist/src/providers/index.d.ts +8 -0
  215. package/dist/src/providers/index.js +51 -12
  216. package/dist/src/providers/index.js.map +1 -1
  217. package/dist/src/routing/resolve-route.d.ts +3 -1
  218. package/dist/src/routing/resolve-route.js.map +1 -1
  219. package/dist/src/session/store.d.ts +5 -3
  220. package/dist/src/session/store.js +66 -20
  221. package/dist/src/session/store.js.map +1 -1
  222. package/dist/src/share/site-share-config.d.ts +3 -2
  223. package/dist/src/share/site-share-config.js.map +1 -1
  224. package/dist/src/utils/logger/stats.d.ts +1 -1
  225. package/dist/src/workflows/domain/command.d.ts +2 -1
  226. package/dist/src/workflows/domain/definition-utils.d.ts +14 -0
  227. package/dist/src/workflows/domain/definition-utils.js +50 -0
  228. package/dist/src/workflows/domain/definition-utils.js.map +1 -0
  229. package/dist/src/workflows/domain/event.d.ts +3 -0
  230. package/dist/src/workflows/domain/index.d.ts +2 -0
  231. package/dist/src/workflows/domain/index.js +3 -1
  232. package/dist/src/workflows/domain/run.d.ts +60 -0
  233. package/dist/src/workflows/domain/run.js.map +1 -1
  234. package/dist/src/workflows/domain/validation.d.ts +19 -0
  235. package/dist/src/workflows/domain/validation.js +66 -0
  236. package/dist/src/workflows/domain/validation.js.map +1 -0
  237. package/dist/src/workflows/engine/projector.js +17 -0
  238. package/dist/src/workflows/engine/projector.js.map +1 -1
  239. package/dist/src/workflows/engine/workflow-engine.d.ts +2 -1
  240. package/dist/src/workflows/engine/workflow-engine.js +128 -0
  241. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  242. package/dist/src/workflows/index.d.ts +4 -0
  243. package/dist/src/workflows/index.js +9 -2
  244. package/dist/src/workflows/service/run-view-to-snapshot.d.ts +4 -0
  245. package/dist/src/workflows/service/run-view-to-snapshot.js +63 -0
  246. package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -0
  247. package/dist/src/workflows/service/workflow-run-service.d.ts +37 -0
  248. package/dist/src/workflows/service/workflow-run-service.js +282 -0
  249. package/dist/src/workflows/service/workflow-run-service.js.map +1 -0
  250. package/dist/src/workflows/service/workflow-run-service.types.d.ts +47 -0
  251. package/dist/src/workflows/service/workflow-run-service.types.js +1 -0
  252. package/dist/src/workflows/service/workflow-session-bridge.d.ts +29 -0
  253. package/dist/src/workflows/service/workflow-session-bridge.js +177 -0
  254. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -0
  255. package/dist/src/workflows/service/workflow-session-key.d.ts +3 -0
  256. package/dist/src/workflows/service/workflow-session-key.js +21 -0
  257. package/dist/src/workflows/service/workflow-session-key.js.map +1 -0
  258. package/dist/src/workflows/store/run-store.js +1 -0
  259. package/dist/src/workflows/store/run-store.js.map +1 -1
  260. package/package.json +1 -1
  261. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +0 -222
  262. package/dist/gateway/static/root/assets/apps-page-wKWf3l57.js +0 -1
  263. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +0 -1
  264. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +0 -1
  265. package/dist/gateway/static/root/assets/cron-api-N9hvuRrn.js +0 -1
  266. package/dist/gateway/static/root/assets/cron-dreaming-jobs-DueM3rBz.js +0 -2
  267. package/dist/gateway/static/root/assets/cron-page-tlNGNxhP.js +0 -1
  268. package/dist/gateway/static/root/assets/dist-CJwfHYvT.js +0 -1
  269. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +0 -1
  270. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +0 -1
  271. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +0 -3
  272. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +0 -2
  273. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +0 -7
  274. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +0 -1
  275. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +0 -27
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":[],"sources":["../../../../../src/gateway/hono/middleware/auth.ts"],"sourcesContent":["import { createMiddleware } from 'hono/factory';\nimport type { Context } from 'hono';\nimport { getConnInfo } from '@hono/node-server/conninfo';\n\nimport type { GatewayAuthConfig } from '../../../config/schema.js';\nimport type { ResolvedGatewayAuth } from '../../auth.js';\nimport { resolveClientIpFromRequest } from '../../client-ip.js';\nimport {\n authPolicyConfig,\n buckets,\n isAuthRateLimitGloballyDisabled,\n resolveAuthRateLimit,\n resolveAuthTracking,\n type ResolvedAuthRateLimitConfig,\n} from '../../rate-limit/index.js';\nimport { getClientIpFromHeaders } from '../../security/loopback.js';\nimport { safeEqualSecret } from '../../security/secret-equal.js';\nimport { authorizeTrustedProxy } from '../../trusted-proxy.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('Hono:Auth');\n\nexport interface AuthConfig {\n token?: string;\n /** Current gateway auth from config (for rate-limit settings); optional. */\n getGatewayAuth?: () => GatewayAuthConfig | undefined;\n getResolvedAuth?: () => ResolvedGatewayAuth;\n getTrustedProxyContext?: () => {\n trustedProxies?: string[];\n allowRealIpFallback?: boolean;\n };\n}\n\nfunction validateToken(providedToken: string | undefined, expectedToken: string): boolean {\n if (!providedToken) return false;\n return safeEqualSecret(providedToken, expectedToken);\n}\n\nfunction extractTokenFromHeader(authHeader: string | null): string | null {\n if (!authHeader) return null;\n const parts = authHeader.split(' ');\n if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') return parts[1];\n return authHeader;\n}\n\n/**\n * SECURITY: query-string tokens leak into server logs, Referer headers, and\n * browser history. We accept them only for SSE/WebSocket connections where\n * the `Authorization` header cannot be set by `EventSource`. For normal REST\n * requests prefer the `Authorization: Bearer <token>` header.\n */\nfunction extractTokenFromQuery(url: string): string | null {\n return new URL(url).searchParams.get('token');\n}\n\nconst QUERY_TOKEN_ALLOWED_PATHS = new Set(['/api/events', '/api/ws']);\n\nfunction isQueryTokenAllowedPath(path: string): boolean {\n return QUERY_TOKEN_ALLOWED_PATHS.has(path) || path.startsWith('/api/events');\n}\n\nfunction resolveRemoteAddress(c: Context): string | undefined {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n}\n\nfunction resolveMiddlewareClientIp(\n c: Context,\n trustedProxies?: string[],\n allowRealIpFallback?: boolean,\n): string {\n if (trustedProxies?.length) {\n return resolveClientIpFromRequest({\n remoteAddress: resolveRemoteAddress(c),\n getHeader: (name) => c.req.header(name),\n trustedProxies,\n allowRealIpFallback,\n });\n }\n return getClientIpFromHeaders({\n get: (name: string) => c.req.header(name) ?? undefined,\n });\n}\n\ntype RateLimitContext = {\n active: boolean;\n cfg: ResolvedAuthRateLimitConfig;\n /** `undefined` when the client is exempted (loopback, disabled, etc.). */\n trackingKey: string | undefined;\n};\n\nfunction buildRateLimitContext(\n getGatewayAuth: AuthConfig['getGatewayAuth'],\n clientIp: string,\n origin: string | undefined,\n): RateLimitContext {\n const cfg = resolveAuthRateLimit(getGatewayAuth?.()?.rateLimit);\n const active = cfg.enabled && !isAuthRateLimitGloballyDisabled();\n if (!active) return { active: false, cfg, trackingKey: undefined };\n const tracking = resolveAuthTracking({ clientIp, origin, cfg: authPolicyConfig(cfg) });\n return {\n active: true,\n cfg,\n trackingKey: tracking.exempt ? undefined : tracking.key,\n };\n}\n\nfunction checkBlocked(rl: RateLimitContext): { blocked: false } | { blocked: true; retryAfterSec: number } {\n if (!rl.active || rl.trackingKey === undefined) return { blocked: false };\n return buckets.authFailure(rl.cfg).check(rl.trackingKey);\n}\n\nfunction recordFailure(rl: RateLimitContext): void {\n if (!rl.active || rl.trackingKey === undefined) return;\n buckets.authFailure(rl.cfg).fail(rl.trackingKey);\n}\n\nfunction recordSuccess(rl: RateLimitContext): void {\n if (!rl.active || rl.trackingKey === undefined) return;\n buckets.authFailure(rl.cfg).succeed(rl.trackingKey);\n}\n\nfunction blockedResponse(c: Context, retryAfterSec: number) {\n c.header('Retry-After', String(retryAfterSec));\n return c.json(\n {\n error: 'Too Many Requests',\n code: 'auth_blocked',\n message: 'Too many authentication attempts',\n retryAfter: retryAfterSec,\n },\n 429,\n );\n}\n\nexport function auth(config?: AuthConfig) {\n const { token, getGatewayAuth, getResolvedAuth, getTrustedProxyContext } = config || {};\n\n return createMiddleware(async (c, next) => {\n const resolvedAuth = getResolvedAuth?.();\n const authMode = resolvedAuth?.mode ?? (token ? 'token' : 'none');\n\n if (authMode === 'trusted-proxy') {\n const proxyContext = getTrustedProxyContext?.();\n const trustedProxies = proxyContext?.trustedProxies;\n const trustedProxyConfig = resolvedAuth?.trustedProxy;\n\n const clientIp = resolveMiddlewareClientIp(c, trustedProxies, proxyContext?.allowRealIpFallback);\n const origin = c.req.header('origin');\n const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);\n\n // Server misconfiguration — not an attack signal. Don't count.\n if (!trustedProxyConfig) {\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'trusted_proxy_config_missing' },\n 'HTTP auth rejected: trusted-proxy config missing',\n );\n return c.json(\n { error: 'Unauthorized', code: 'auth_unconfigured', message: 'Trusted-proxy auth is not configured' },\n 401,\n );\n }\n\n const blocked = checkBlocked(rl);\n if (blocked.blocked) {\n log.warn(\n { clientIp, origin: origin ?? undefined, path: c.req.path, method: c.req.method, retryAfterSec: blocked.retryAfterSec, reason: 'auth_blocked' },\n 'Auth rate limit blocked',\n );\n return blockedResponse(c, blocked.retryAfterSec);\n }\n\n const result = authorizeTrustedProxy({\n remoteAddress: resolveRemoteAddress(c),\n getHeader: (name) => c.req.header(name),\n trustedProxies,\n trustedProxyConfig,\n });\n\n if (result.ok === false) {\n recordFailure(rl);\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: result.reason },\n `HTTP auth rejected: trusted-proxy validation failed (${result.reason})`,\n );\n return c.json(\n { error: 'Unauthorized', code: 'invalid_proxy_credentials', message: 'Trusted-proxy authentication failed' },\n 401,\n );\n }\n\n recordSuccess(rl);\n await next();\n return;\n }\n\n if (authMode === 'none' || !token) {\n return next();\n }\n\n const proxyContext = getTrustedProxyContext?.();\n const clientIp = resolveMiddlewareClientIp(c, proxyContext?.trustedProxies, proxyContext?.allowRealIpFallback);\n const origin = c.req.header('origin');\n const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);\n\n const authHeader = extractTokenFromHeader(c.req.header('authorization'));\n const requestPath = new URL(c.req.url).pathname;\n const queryToken = isQueryTokenAllowedPath(requestPath) ? extractTokenFromQuery(c.req.url) : null;\n\n if (!authHeader && queryToken === null && new URL(c.req.url).searchParams.has('token')) {\n log.warn(\n { path: requestPath, method: c.req.method, clientIp },\n 'Token in query string rejected: use Authorization header for this endpoint',\n );\n }\n\n const providedToken = authHeader || queryToken;\n\n if (providedToken && validateToken(providedToken, token)) {\n recordSuccess(rl);\n await next();\n return;\n }\n\n const blocked = checkBlocked(rl);\n if (blocked.blocked) {\n log.warn(\n { clientIp, origin: origin ?? undefined, path: requestPath, method: c.req.method, retryAfterSec: blocked.retryAfterSec, reason: 'auth_blocked' },\n 'Auth rate limit blocked',\n );\n return blockedResponse(c, blocked.retryAfterSec);\n }\n\n // Missing token is an unauthenticated request, not a brute-force signal —\n // page reloads / SDK cold starts often hit endpoints before the token is\n // attached. Counting this would lock users out of the token-entry path.\n if (!providedToken) {\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'missing_token' },\n 'HTTP auth rejected: no Bearer or ?token=',\n );\n return c.json(\n { error: 'Unauthorized', code: 'missing_token', message: 'Missing authentication token' },\n 401,\n );\n }\n\n recordFailure(rl);\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'invalid_token' },\n 'HTTP auth rejected: token mismatch',\n );\n return c.json(\n { error: 'Unauthorized', code: 'invalid_token', message: 'Invalid authentication token' },\n 401,\n );\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../../../../src/gateway/hono/middleware/auth.ts"],"sourcesContent":["import { createMiddleware } from 'hono/factory';\nimport type { Context } from 'hono';\nimport { getConnInfo } from '@hono/node-server/conninfo';\n\nimport type { GatewayAuthConfig } from '../../../config/schema.js';\nimport type { ResolvedGatewayAuth } from '../../auth.js';\nimport { resolveClientIpFromRequest } from '../../client-ip.js';\nimport {\n authPolicyConfig,\n buckets,\n isAuthRateLimitGloballyDisabled,\n resolveAuthRateLimit,\n resolveAuthTracking,\n type ResolvedAuthRateLimitConfig,\n} from '../../rate-limit/index.js';\nimport { getClientIpFromHeaders } from '../../security/loopback.js';\nimport { safeEqualSecret } from '../../security/secret-equal.js';\nimport { authorizeTrustedProxy } from '../../trusted-proxy.js';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst log = createLogger('Hono:Auth');\n\nexport interface AuthConfig {\n token?: string;\n /** Current gateway auth from config (for rate-limit settings); optional. */\n getGatewayAuth?: () => GatewayAuthConfig | undefined;\n getResolvedAuth?: () => ResolvedGatewayAuth;\n getTrustedProxyContext?: () => {\n trustedProxies?: string[];\n allowRealIpFallback?: boolean;\n };\n}\n\nfunction validateToken(providedToken: string | undefined, expectedToken: string): boolean {\n if (!providedToken) return false;\n return safeEqualSecret(providedToken, expectedToken);\n}\n\nfunction extractTokenFromHeader(authHeader: string | null): string | null {\n if (!authHeader) return null;\n const parts = authHeader.split(' ');\n if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') return parts[1];\n return authHeader;\n}\n\n/**\n * SECURITY: query-string tokens leak into server logs, Referer headers, and\n * browser history. We accept them only where the `Authorization` header cannot\n * be set — SSE/WebSocket (`EventSource`) and `<img>` subresource loads for agent\n * avatars. For normal REST requests prefer the `Authorization: Bearer <token>`\n * header.\n */\nfunction extractTokenFromQuery(url: string): string | null {\n return new URL(url).searchParams.get('token');\n}\n\nconst QUERY_TOKEN_ALLOWED_PATHS = new Set(['/api/events', '/api/ws']);\n\nconst AGENT_AVATAR_GET_PATH = /^\\/api\\/agents\\/[^/]+\\/avatar$/;\n\n/** Exported for gateway security tests. */\nexport function isQueryTokenAllowedPath(path: string, method: string): boolean {\n if (QUERY_TOKEN_ALLOWED_PATHS.has(path) || path.startsWith('/api/events')) {\n return true;\n }\n // `<img src>` cannot send Bearer tokens; gateway console loads custom avatars here.\n if (method === 'GET' && AGENT_AVATAR_GET_PATH.test(path)) {\n return true;\n }\n return false;\n}\n\nfunction resolveRemoteAddress(c: Context): string | undefined {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n}\n\nfunction resolveMiddlewareClientIp(\n c: Context,\n trustedProxies?: string[],\n allowRealIpFallback?: boolean,\n): string {\n if (trustedProxies?.length) {\n return resolveClientIpFromRequest({\n remoteAddress: resolveRemoteAddress(c),\n getHeader: (name) => c.req.header(name),\n trustedProxies,\n allowRealIpFallback,\n });\n }\n return getClientIpFromHeaders({\n get: (name: string) => c.req.header(name) ?? undefined,\n });\n}\n\ntype RateLimitContext = {\n active: boolean;\n cfg: ResolvedAuthRateLimitConfig;\n /** `undefined` when the client is exempted (loopback, disabled, etc.). */\n trackingKey: string | undefined;\n};\n\nfunction buildRateLimitContext(\n getGatewayAuth: AuthConfig['getGatewayAuth'],\n clientIp: string,\n origin: string | undefined,\n): RateLimitContext {\n const cfg = resolveAuthRateLimit(getGatewayAuth?.()?.rateLimit);\n const active = cfg.enabled && !isAuthRateLimitGloballyDisabled();\n if (!active) return { active: false, cfg, trackingKey: undefined };\n const tracking = resolveAuthTracking({ clientIp, origin, cfg: authPolicyConfig(cfg) });\n return {\n active: true,\n cfg,\n trackingKey: tracking.exempt ? undefined : tracking.key,\n };\n}\n\nfunction checkBlocked(rl: RateLimitContext): { blocked: false } | { blocked: true; retryAfterSec: number } {\n if (!rl.active || rl.trackingKey === undefined) return { blocked: false };\n return buckets.authFailure(rl.cfg).check(rl.trackingKey);\n}\n\nfunction recordFailure(rl: RateLimitContext): void {\n if (!rl.active || rl.trackingKey === undefined) return;\n buckets.authFailure(rl.cfg).fail(rl.trackingKey);\n}\n\nfunction recordSuccess(rl: RateLimitContext): void {\n if (!rl.active || rl.trackingKey === undefined) return;\n buckets.authFailure(rl.cfg).succeed(rl.trackingKey);\n}\n\nfunction blockedResponse(c: Context, retryAfterSec: number) {\n c.header('Retry-After', String(retryAfterSec));\n return c.json(\n {\n error: 'Too Many Requests',\n code: 'auth_blocked',\n message: 'Too many authentication attempts',\n retryAfter: retryAfterSec,\n },\n 429,\n );\n}\n\nexport function auth(config?: AuthConfig) {\n const { token, getGatewayAuth, getResolvedAuth, getTrustedProxyContext } = config || {};\n\n return createMiddleware(async (c, next) => {\n const resolvedAuth = getResolvedAuth?.();\n const authMode = resolvedAuth?.mode ?? (token ? 'token' : 'none');\n\n if (authMode === 'trusted-proxy') {\n const proxyContext = getTrustedProxyContext?.();\n const trustedProxies = proxyContext?.trustedProxies;\n const trustedProxyConfig = resolvedAuth?.trustedProxy;\n\n const clientIp = resolveMiddlewareClientIp(c, trustedProxies, proxyContext?.allowRealIpFallback);\n const origin = c.req.header('origin');\n const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);\n\n // Server misconfiguration — not an attack signal. Don't count.\n if (!trustedProxyConfig) {\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'trusted_proxy_config_missing' },\n 'HTTP auth rejected: trusted-proxy config missing',\n );\n return c.json(\n { error: 'Unauthorized', code: 'auth_unconfigured', message: 'Trusted-proxy auth is not configured' },\n 401,\n );\n }\n\n const blocked = checkBlocked(rl);\n if (blocked.blocked) {\n log.warn(\n { clientIp, origin: origin ?? undefined, path: c.req.path, method: c.req.method, retryAfterSec: blocked.retryAfterSec, reason: 'auth_blocked' },\n 'Auth rate limit blocked',\n );\n return blockedResponse(c, blocked.retryAfterSec);\n }\n\n const result = authorizeTrustedProxy({\n remoteAddress: resolveRemoteAddress(c),\n getHeader: (name) => c.req.header(name),\n trustedProxies,\n trustedProxyConfig,\n });\n\n if (result.ok === false) {\n recordFailure(rl);\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: result.reason },\n `HTTP auth rejected: trusted-proxy validation failed (${result.reason})`,\n );\n return c.json(\n { error: 'Unauthorized', code: 'invalid_proxy_credentials', message: 'Trusted-proxy authentication failed' },\n 401,\n );\n }\n\n recordSuccess(rl);\n await next();\n return;\n }\n\n if (authMode === 'none' || !token) {\n return next();\n }\n\n const proxyContext = getTrustedProxyContext?.();\n const clientIp = resolveMiddlewareClientIp(c, proxyContext?.trustedProxies, proxyContext?.allowRealIpFallback);\n const origin = c.req.header('origin');\n const rl = buildRateLimitContext(getGatewayAuth, clientIp, origin);\n\n const authHeader = extractTokenFromHeader(c.req.header('authorization'));\n const requestPath = new URL(c.req.url).pathname;\n const queryToken = isQueryTokenAllowedPath(requestPath, c.req.method)\n ? extractTokenFromQuery(c.req.url)\n : null;\n\n if (!authHeader && queryToken === null && new URL(c.req.url).searchParams.has('token')) {\n log.warn(\n { path: requestPath, method: c.req.method, clientIp },\n 'Token in query string rejected: use Authorization header for this endpoint',\n );\n }\n\n const providedToken = authHeader || queryToken;\n\n if (providedToken && validateToken(providedToken, token)) {\n recordSuccess(rl);\n await next();\n return;\n }\n\n const blocked = checkBlocked(rl);\n if (blocked.blocked) {\n log.warn(\n { clientIp, origin: origin ?? undefined, path: requestPath, method: c.req.method, retryAfterSec: blocked.retryAfterSec, reason: 'auth_blocked' },\n 'Auth rate limit blocked',\n );\n return blockedResponse(c, blocked.retryAfterSec);\n }\n\n // Missing token is an unauthenticated request, not a brute-force signal —\n // page reloads / SDK cold starts often hit endpoints before the token is\n // attached. Counting this would lock users out of the token-entry path.\n if (!providedToken) {\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'missing_token' },\n 'HTTP auth rejected: no Bearer or ?token=',\n );\n return c.json(\n { error: 'Unauthorized', code: 'missing_token', message: 'Missing authentication token' },\n 401,\n );\n }\n\n recordFailure(rl);\n log.warn(\n { path: c.req.path, method: c.req.method, clientIp, reason: 'invalid_token' },\n 'HTTP auth rejected: token mismatch',\n );\n return c.json(\n { error: 'Unauthorized', code: 'invalid_token', message: 'Invalid authentication token' },\n 401,\n );\n });\n}\n"],"mappings":"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"}
@@ -45,7 +45,7 @@ const SESSION_TTL_MS = 600 * 1e3;
45
45
  setInterval(() => {
46
46
  const now = Date.now();
47
47
  for (const [id, session] of oauthSessions.entries()) if (now > session.expiresAt) {
48
- if (session.abortController) session.abortController.abort();
48
+ cancelOAuthSession(session, "OAuth flow expired");
49
49
  oauthSessions.delete(id);
50
50
  log.debug({ sessionId: id }, "Cleaned up expired OAuth session");
51
51
  }
@@ -53,6 +53,14 @@ setInterval(() => {
53
53
  function generateSessionId() {
54
54
  return `oauth_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
55
55
  }
56
+ function cancelOAuthSession(session, message = "OAuth flow cancelled") {
57
+ if (session.abortController) session.abortController.abort();
58
+ if (session.manualCodeResolve) session.manualCodeResolve("");
59
+ session.manualCodeResolve = void 0;
60
+ session.manualCodeReject = void 0;
61
+ session.status = "cancelled";
62
+ session.message = message;
63
+ }
56
64
  function createOAuthAsyncHandler(service) {
57
65
  const oauth = new Hono();
58
66
  /**
@@ -142,14 +150,7 @@ function createOAuthAsyncHandler(service) {
142
150
  const sessionId = c.req.param("sessionId");
143
151
  const session = oauthSessions.get(sessionId);
144
152
  if (!session) return c.json({ error: "Session not found" }, 404);
145
- if (session.abortController) session.abortController.abort();
146
- if (session.manualCodeReject) {
147
- session.manualCodeReject(/* @__PURE__ */ new Error("OAuth cancelled by user"));
148
- session.manualCodeReject = void 0;
149
- session.manualCodeResolve = void 0;
150
- }
151
- session.status = "cancelled";
152
- session.message = "OAuth flow cancelled";
153
+ cancelOAuthSession(session);
153
154
  return c.json({
154
155
  ok: true,
155
156
  payload: { message: "OAuth flow cancelled" }
@@ -162,8 +163,7 @@ function createOAuthAsyncHandler(service) {
162
163
  oauth.delete("/:sessionId", (c) => {
163
164
  const sessionId = c.req.param("sessionId");
164
165
  if (oauthSessions.has(sessionId)) {
165
- const session = oauthSessions.get(sessionId);
166
- if (session.abortController) session.abortController.abort();
166
+ cancelOAuthSession(oauthSessions.get(sessionId));
167
167
  oauthSessions.delete(sessionId);
168
168
  }
169
169
  return c.json({ ok: true });
@@ -197,6 +197,14 @@ async function runOAuthFlow(session, oauthProvider, _service) {
197
197
  session.message = "Complete authorization in browser";
198
198
  }
199
199
  },
200
+ onDeviceCode: (info) => {
201
+ session.status = "waiting_auth";
202
+ session.authUrl = info.verificationUri;
203
+ session.deviceCode = info.userCode;
204
+ session.verificationUri = info.verificationUri;
205
+ session.instructions = `Enter code ${info.userCode}`;
206
+ session.message = `Open ${info.verificationUri} and enter code ${info.userCode}`;
207
+ },
200
208
  onPrompt: async (prompt) => {
201
209
  session.status = "waiting_code";
202
210
  session.deviceCode = prompt.deviceCode;
@@ -221,13 +229,30 @@ async function runOAuthFlow(session, oauthProvider, _service) {
221
229
  if (manualCodePromise) return manualCodePromise;
222
230
  return "";
223
231
  },
232
+ onSelect: async (prompt) => {
233
+ const browserOption = prompt.options.find((option) => option.id === "browser");
234
+ const firstOption = prompt.options[0];
235
+ const selectedOption = browserOption ?? firstOption;
236
+ if (!selectedOption) throw new Error("OAuth login did not provide any selectable auth method");
237
+ log.debug({
238
+ sessionId: session.id,
239
+ provider: session.provider,
240
+ selected: selectedOption.id
241
+ }, "Selected OAuth auth method");
242
+ return selectedOption.id;
243
+ },
224
244
  signal: abortController.signal
225
245
  };
226
246
  try {
227
247
  const credentials = await oauthProvider.login(callbacks);
228
248
  setOAuthCredentialsToCache(session.provider, credentials);
229
- const apiKey = oauthProvider.getApiKey(credentials);
230
- await new CredentialResolver().saveApiKey(session.provider, apiKey, { profileName: "default" });
249
+ await new CredentialResolver().saveOAuthToken(session.provider, {
250
+ access: oauthProvider.getApiKey(credentials),
251
+ refresh: credentials.refresh,
252
+ expiresAt: credentials.expires,
253
+ scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value) => typeof value === "string") : void 0,
254
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
255
+ });
231
256
  session.status = "completed";
232
257
  session.credentials = credentials;
233
258
  session.message = "OAuth login successful";
@@ -236,9 +261,9 @@ async function runOAuthFlow(session, oauthProvider, _service) {
236
261
  provider: session.provider
237
262
  }, "OAuth login completed");
238
263
  } catch (err) {
239
- if (abortController.signal.aborted) {
264
+ if (abortController.signal.aborted || session.status === "cancelled") {
240
265
  session.status = "cancelled";
241
- session.message = "OAuth flow cancelled by user";
266
+ session.message ??= "OAuth flow cancelled by user";
242
267
  } else {
243
268
  session.status = "failed";
244
269
  session.error = formatOAuthAsyncError(err);
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-async.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth-async.ts"],"sourcesContent":["/**\n * Async OAuth Handler\n * \n * Provides non-blocking OAuth flow with session-based state management.\n * This allows OAuth flows that require user interaction (browser login) \n * without blocking the HTTP request.\n */\n\nimport { Hono } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\n\nconst log = createLogger('OAuthAsync');\n\n/** User-facing message when undici/fetch fails (often DNS, firewall, or wrong machine for localhost callback). */\nfunction formatOAuthAsyncError(err: unknown): string {\n\tconst base = err instanceof Error ? err.message : 'OAuth login failed';\n\tconst cause =\n\t\terr instanceof Error && err.cause instanceof Error\n\t\t\t? err.cause.message\n\t\t\t: err instanceof Error && typeof err.cause === 'string'\n\t\t\t\t? err.cause\n\t\t\t\t: '';\n\tconst detail = cause ? ` (${cause})` : '';\n\tif (/^fetch failed$/i.test(base) || base.includes('fetch failed')) {\n\t\treturn (\n\t\t\t`Network request failed${detail}. If the browser opened on another device, the redirect goes to that device's localhost — ` +\n\t\t\t`copy the full URL from the browser address bar after sign-in (starts with http://127.0.0.1 or http://localhost) and paste it below. ` +\n\t\t\t`Otherwise check VPN/proxy/DNS/firewall access to Google OAuth.`\n\t\t);\n\t}\n\treturn base;\n}\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// OAuth session state\ninterface OAuthSession {\n id: string;\n provider: string;\n status: 'pending' | 'waiting_auth' | 'waiting_code' | 'completed' | 'failed' | 'cancelled';\n authUrl?: string;\n instructions?: string;\n deviceCode?: string;\n verificationUri?: string;\n message?: string;\n error?: string;\n credentials?: OAuthCredentials;\n createdAt: number;\n expiresAt: number;\n abortController?: AbortController;\n manualCodeResolve?: (code: string) => void;\n manualCodeReject?: (error: Error) => void;\n}\n\n// In-memory session store (could be moved to Redis for production)\nconst oauthSessions = new Map<string, OAuthSession>();\nconst SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\n// Clean up expired sessions periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [id, session] of oauthSessions.entries()) {\n if (now > session.expiresAt) {\n if (session.abortController) {\n session.abortController.abort();\n }\n oauthSessions.delete(id);\n log.debug({ sessionId: id }, 'Cleaned up expired OAuth session');\n }\n }\n}, 60 * 1000);\n\nfunction generateSessionId(): string {\n return `oauth_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nexport function createOAuthAsyncHandler(service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth-async/start\n * Start async OAuth flow - returns immediately with session ID\n */\n oauth.post('/start', async (c) => {\n const { provider } = await c.req.json().catch(() => ({}));\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n const sessionId = generateSessionId();\n const session: OAuthSession = {\n id: sessionId,\n provider,\n status: 'pending',\n createdAt: Date.now(),\n expiresAt: Date.now() + SESSION_TTL_MS,\n };\n\n oauthSessions.set(sessionId, session);\n\n // Start OAuth flow in background\n runOAuthFlow(session, oauthProvider, service).catch(err => {\n log.error({ sessionId, provider, error: err }, 'Background OAuth flow failed');\n session.status = 'failed';\n session.error = err instanceof Error ? err.message : 'OAuth flow failed';\n });\n\n return c.json({ \n ok: true, \n payload: { \n sessionId,\n provider,\n status: session.status,\n } \n });\n });\n\n /**\n * GET /api/auth/oauth-async/:sessionId/status\n * Check OAuth session status\n */\n oauth.get('/:sessionId/status', (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n return c.json({ \n ok: true, \n payload: { \n sessionId: session.id,\n provider: session.provider,\n status: session.status,\n authUrl: session.authUrl,\n instructions: session.instructions,\n deviceCode: session.deviceCode,\n verificationUri: session.verificationUri,\n message: session.message,\n error: session.error,\n expiresAt: session.expiresAt,\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/code\n * Submit manual authorization code\n */\n oauth.post('/:sessionId/code', async (c) => {\n const sessionId = c.req.param('sessionId');\n const { code } = await c.req.json().catch(() => ({}));\n \n if (!code) {\n return c.json({ error: 'Code is required' }, 400);\n }\n\n const session = oauthSessions.get(sessionId);\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n if (session.status !== 'waiting_code' || !session.manualCodeResolve) {\n return c.json({ error: 'Session is not waiting for code' }, 400);\n }\n\n // Resolve the manual code promise\n session.manualCodeResolve(code);\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n\n return c.json({ \n ok: true, \n payload: { \n message: 'Code submitted, processing...',\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/cancel\n * Cancel OAuth flow\n */\n oauth.post('/:sessionId/cancel', async (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n if (session.abortController) {\n session.abortController.abort();\n }\n\n if (session.manualCodeReject) {\n session.manualCodeReject(new Error('OAuth cancelled by user'));\n session.manualCodeReject = undefined;\n session.manualCodeResolve = undefined;\n }\n\n session.status = 'cancelled';\n session.message = 'OAuth flow cancelled';\n\n return c.json({ \n ok: true, \n payload: { \n message: 'OAuth flow cancelled',\n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth-async/:sessionId\n * Clean up OAuth session\n */\n oauth.delete('/:sessionId', (c) => {\n const sessionId = c.req.param('sessionId');\n \n if (oauthSessions.has(sessionId)) {\n const session = oauthSessions.get(sessionId)!;\n if (session.abortController) {\n session.abortController.abort();\n }\n oauthSessions.delete(sessionId);\n }\n\n return c.json({ ok: true });\n });\n\n return oauth;\n}\n\n/**\n * Run OAuth flow in background\n */\nasync function runOAuthFlow(\n session: OAuthSession,\n oauthProvider: OAuthProviderInterface,\n _service: GatewayService\n): Promise<void> {\n const abortController = new AbortController();\n session.abortController = abortController;\n\n let manualCodePromise: Promise<string> | null = null;\n let manualCodeResolve: ((code: string) => void) | undefined;\n let manualCodeReject: ((error: Error) => void) | undefined;\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n session.authUrl = auth.url;\n session.instructions = auth.instructions;\n \n if (oauthProvider.usesCallbackServer) {\n // For callback server providers, prepare for manual code input\n session.status = 'waiting_code';\n session.message = 'Complete authorization in browser, or paste the redirect URL below';\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n } else {\n session.status = 'waiting_auth';\n session.message = 'Complete authorization in browser';\n }\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n session.status = 'waiting_code';\n session.deviceCode = prompt.deviceCode;\n session.verificationUri = prompt.verificationUri;\n session.message = prompt.message;\n \n // For device code flow, wait for manual input\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n \n // Return empty for now, will be resolved by manual code submission\n return '';\n },\n onProgress: (message: string) => {\n log.debug({ sessionId: session.id, message }, 'OAuth progress');\n session.message = message;\n },\n onManualCodeInput: async () => {\n // Return the manual code promise for callback server providers\n if (manualCodePromise) {\n return manualCodePromise;\n }\n return '';\n },\n signal: abortController.signal,\n };\n\n try {\n const credentials = await oauthProvider.login(callbacks);\n \n // Save credentials to cache\n setOAuthCredentialsToCache(session.provider, credentials);\n\n // Get API key from OAuth credentials\n const apiKey = oauthProvider.getApiKey(credentials);\n\n // Save API key to credential system\n const resolver = new CredentialResolver();\n await resolver.saveApiKey(session.provider, apiKey, { profileName: 'default' });\n\n session.status = 'completed';\n session.credentials = credentials;\n session.message = 'OAuth login successful';\n \n log.info({ sessionId: session.id, provider: session.provider }, 'OAuth login completed');\n } catch (err) {\n if (abortController.signal.aborted) {\n session.status = 'cancelled';\n session.message = 'OAuth flow cancelled by user';\n } else {\n session.status = 'failed';\n session.error = formatOAuthAsyncError(err);\n log.error({ sessionId: session.id, provider: session.provider, error: err }, 'OAuth login failed');\n }\n } finally {\n session.abortController = undefined;\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n }\n}\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nexport function getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nexport function setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nexport function deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth-async.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth-async.ts"],"sourcesContent":["/**\n * Async OAuth Handler\n * \n * Provides non-blocking OAuth flow with session-based state management.\n * This allows OAuth flows that require user interaction (browser login) \n * without blocking the HTTP request.\n */\n\nimport { Hono } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\n\nconst log = createLogger('OAuthAsync');\n\n/** User-facing message when undici/fetch fails (often DNS, firewall, or wrong machine for localhost callback). */\nfunction formatOAuthAsyncError(err: unknown): string {\n\tconst base = err instanceof Error ? err.message : 'OAuth login failed';\n\tconst cause =\n\t\terr instanceof Error && err.cause instanceof Error\n\t\t\t? err.cause.message\n\t\t\t: err instanceof Error && typeof err.cause === 'string'\n\t\t\t\t? err.cause\n\t\t\t\t: '';\n\tconst detail = cause ? ` (${cause})` : '';\n\tif (/^fetch failed$/i.test(base) || base.includes('fetch failed')) {\n\t\treturn (\n\t\t\t`Network request failed${detail}. If the browser opened on another device, the redirect goes to that device's localhost — ` +\n\t\t\t`copy the full URL from the browser address bar after sign-in (starts with http://127.0.0.1 or http://localhost) and paste it below. ` +\n\t\t\t`Otherwise check VPN/proxy/DNS/firewall access to Google OAuth.`\n\t\t);\n\t}\n\treturn base;\n}\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// OAuth session state\ninterface OAuthSession {\n id: string;\n provider: string;\n status: 'pending' | 'waiting_auth' | 'waiting_code' | 'completed' | 'failed' | 'cancelled';\n authUrl?: string;\n instructions?: string;\n deviceCode?: string;\n verificationUri?: string;\n message?: string;\n error?: string;\n credentials?: OAuthCredentials;\n createdAt: number;\n expiresAt: number;\n abortController?: AbortController;\n manualCodeResolve?: (code: string) => void;\n manualCodeReject?: (error: Error) => void;\n}\n\n// In-memory session store (could be moved to Redis for production)\nconst oauthSessions = new Map<string, OAuthSession>();\nconst SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\n// Clean up expired sessions periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [id, session] of oauthSessions.entries()) {\n if (now > session.expiresAt) {\n cancelOAuthSession(session, 'OAuth flow expired');\n oauthSessions.delete(id);\n log.debug({ sessionId: id }, 'Cleaned up expired OAuth session');\n }\n }\n}, 60 * 1000);\n\nfunction generateSessionId(): string {\n return `oauth_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nfunction cancelOAuthSession(session: OAuthSession, message = 'OAuth flow cancelled'): void {\n if (session.abortController) {\n session.abortController.abort();\n }\n\n if (session.manualCodeResolve) {\n session.manualCodeResolve('');\n }\n\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n session.status = 'cancelled';\n session.message = message;\n}\n\nexport function createOAuthAsyncHandler(service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth-async/start\n * Start async OAuth flow - returns immediately with session ID\n */\n oauth.post('/start', async (c) => {\n const { provider } = await c.req.json().catch(() => ({}));\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n const sessionId = generateSessionId();\n const session: OAuthSession = {\n id: sessionId,\n provider,\n status: 'pending',\n createdAt: Date.now(),\n expiresAt: Date.now() + SESSION_TTL_MS,\n };\n\n oauthSessions.set(sessionId, session);\n\n // Start OAuth flow in background\n runOAuthFlow(session, oauthProvider, service).catch(err => {\n log.error({ sessionId, provider, error: err }, 'Background OAuth flow failed');\n session.status = 'failed';\n session.error = err instanceof Error ? err.message : 'OAuth flow failed';\n });\n\n return c.json({ \n ok: true, \n payload: { \n sessionId,\n provider,\n status: session.status,\n } \n });\n });\n\n /**\n * GET /api/auth/oauth-async/:sessionId/status\n * Check OAuth session status\n */\n oauth.get('/:sessionId/status', (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n return c.json({ \n ok: true, \n payload: { \n sessionId: session.id,\n provider: session.provider,\n status: session.status,\n authUrl: session.authUrl,\n instructions: session.instructions,\n deviceCode: session.deviceCode,\n verificationUri: session.verificationUri,\n message: session.message,\n error: session.error,\n expiresAt: session.expiresAt,\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/code\n * Submit manual authorization code\n */\n oauth.post('/:sessionId/code', async (c) => {\n const sessionId = c.req.param('sessionId');\n const { code } = await c.req.json().catch(() => ({}));\n \n if (!code) {\n return c.json({ error: 'Code is required' }, 400);\n }\n\n const session = oauthSessions.get(sessionId);\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n if (session.status !== 'waiting_code' || !session.manualCodeResolve) {\n return c.json({ error: 'Session is not waiting for code' }, 400);\n }\n\n // Resolve the manual code promise\n session.manualCodeResolve(code);\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n\n return c.json({ \n ok: true, \n payload: { \n message: 'Code submitted, processing...',\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/cancel\n * Cancel OAuth flow\n */\n oauth.post('/:sessionId/cancel', async (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n cancelOAuthSession(session);\n\n return c.json({ \n ok: true, \n payload: { \n message: 'OAuth flow cancelled',\n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth-async/:sessionId\n * Clean up OAuth session\n */\n oauth.delete('/:sessionId', (c) => {\n const sessionId = c.req.param('sessionId');\n \n if (oauthSessions.has(sessionId)) {\n const session = oauthSessions.get(sessionId)!;\n cancelOAuthSession(session);\n oauthSessions.delete(sessionId);\n }\n\n return c.json({ ok: true });\n });\n\n return oauth;\n}\n\n/**\n * Run OAuth flow in background\n */\nasync function runOAuthFlow(\n session: OAuthSession,\n oauthProvider: OAuthProviderInterface,\n _service: GatewayService\n): Promise<void> {\n const abortController = new AbortController();\n session.abortController = abortController;\n\n let manualCodePromise: Promise<string> | null = null;\n let manualCodeResolve: ((code: string) => void) | undefined;\n let manualCodeReject: ((error: Error) => void) | undefined;\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n session.authUrl = auth.url;\n session.instructions = auth.instructions;\n \n if (oauthProvider.usesCallbackServer) {\n // For callback server providers, prepare for manual code input\n session.status = 'waiting_code';\n session.message = 'Complete authorization in browser, or paste the redirect URL below';\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n } else {\n session.status = 'waiting_auth';\n session.message = 'Complete authorization in browser';\n }\n },\n onDeviceCode: (info) => {\n session.status = 'waiting_auth';\n session.authUrl = info.verificationUri;\n session.deviceCode = info.userCode;\n session.verificationUri = info.verificationUri;\n session.instructions = `Enter code ${info.userCode}`;\n session.message = `Open ${info.verificationUri} and enter code ${info.userCode}`;\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n session.status = 'waiting_code';\n session.deviceCode = prompt.deviceCode;\n session.verificationUri = prompt.verificationUri;\n session.message = prompt.message;\n \n // For device code flow, wait for manual input\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n \n // Return empty for now, will be resolved by manual code submission\n return '';\n },\n onProgress: (message: string) => {\n log.debug({ sessionId: session.id, message }, 'OAuth progress');\n session.message = message;\n },\n onManualCodeInput: async () => {\n // Return the manual code promise for callback server providers\n if (manualCodePromise) {\n return manualCodePromise;\n }\n return '';\n },\n onSelect: async (prompt) => {\n const browserOption = prompt.options.find((option) => option.id === 'browser');\n const firstOption = prompt.options[0];\n const selectedOption = browserOption ?? firstOption;\n if (!selectedOption) {\n throw new Error('OAuth login did not provide any selectable auth method');\n }\n log.debug(\n { sessionId: session.id, provider: session.provider, selected: selectedOption.id },\n 'Selected OAuth auth method',\n );\n return selectedOption.id;\n },\n signal: abortController.signal,\n };\n\n try {\n const credentials = await oauthProvider.login(callbacks);\n \n // Save credentials to cache and persist them as first-class OAuth credentials.\n setOAuthCredentialsToCache(session.provider, credentials);\n\n const resolver = new CredentialResolver();\n await resolver.saveOAuthToken(session.provider, {\n access: oauthProvider.getApiKey(credentials),\n refresh: credentials.refresh,\n expiresAt: credentials.expires,\n scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value): value is string => typeof value === 'string') : undefined,\n createdAt: new Date().toISOString(),\n });\n\n session.status = 'completed';\n session.credentials = credentials;\n session.message = 'OAuth login successful';\n \n log.info({ sessionId: session.id, provider: session.provider }, 'OAuth login completed');\n } catch (err) {\n if (abortController.signal.aborted || session.status === 'cancelled') {\n session.status = 'cancelled';\n session.message ??= 'OAuth flow cancelled by user';\n } else {\n session.status = 'failed';\n session.error = formatOAuthAsyncError(err);\n log.error({ sessionId: session.id, provider: session.provider, error: err }, 'OAuth login failed');\n }\n } finally {\n session.abortController = undefined;\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n }\n}\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nexport function getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nexport function setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nexport function deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { CredentialResolver, init_credentials } from "../../auth/credentials.js";
2
2
  import { anthropicOAuthProvider } from "../../auth/oauth/anthropic.js";
3
- import { init_providers, isProviderConfigured } from "../../providers/index.js";
3
+ import { getProviderAuthState, init_providers, isProviderConfigured } from "../../providers/index.js";
4
4
  import { minimaxOAuthProvider } from "../../auth/oauth/minimax.js";
5
5
  import { minimaxCnOAuthProvider } from "../../auth/oauth/minimax-cn.js";
6
6
  import { kimiCodingOAuthProvider } from "../../auth/oauth/kimi-coding.js";
@@ -61,6 +61,15 @@ function createOAuthHandler(_service) {
61
61
  instructions: auth.instructions
62
62
  };
63
63
  },
64
+ onDeviceCode: (info) => {
65
+ authResult = {
66
+ url: info.verificationUri,
67
+ instructions: `Enter code ${info.userCode}`,
68
+ message: `Open ${info.verificationUri} and enter code ${info.userCode}`,
69
+ deviceCode: info.userCode,
70
+ verificationUri: info.verificationUri
71
+ };
72
+ },
64
73
  onPrompt: async (prompt) => {
65
74
  authResult = {
66
75
  message: prompt.message,
@@ -75,11 +84,19 @@ function createOAuthHandler(_service) {
75
84
  onManualCodeInput: async () => {
76
85
  manualCodeRequested = true;
77
86
  return "";
87
+ },
88
+ onSelect: async (prompt) => {
89
+ return prompt.options.find((option) => option.id === "browser")?.id ?? prompt.options[0]?.id;
78
90
  }
79
91
  });
80
92
  setOAuthCredentialsToCache(provider, credentials);
81
- const apiKey = oauthProvider.getApiKey(credentials);
82
- await new CredentialResolver().saveApiKey(provider, apiKey, { profileName: "default" });
93
+ await new CredentialResolver().saveOAuthToken(provider, {
94
+ access: oauthProvider.getApiKey(credentials),
95
+ refresh: credentials.refresh,
96
+ expiresAt: credentials.expires,
97
+ scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value) => typeof value === "string") : void 0,
98
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
99
+ });
83
100
  return c.json({
84
101
  ok: true,
85
102
  payload: {
@@ -107,12 +124,15 @@ function createOAuthHandler(_service) {
107
124
  oauth.get("/:provider", async (c) => {
108
125
  const provider = c.req.param("provider");
109
126
  const credentials = getOAuthCredentialsFromCache(provider);
127
+ const authState = await getProviderAuthState(provider);
110
128
  const configured = await isProviderConfigured(provider);
111
129
  return c.json({
112
130
  ok: true,
113
131
  payload: {
114
132
  configured: configured || !!credentials,
115
- expires: credentials?.expires
133
+ authMode: authState.authMode,
134
+ authStatus: authState.authStatus,
135
+ expiresAt: authState.expiresAt ?? credentials?.expires
116
136
  }
117
137
  });
118
138
  });
@@ -121,8 +141,13 @@ function createOAuthHandler(_service) {
121
141
  * Revoke OAuth credentials
122
142
  */
123
143
  oauth.delete("/:provider", async (c) => {
124
- deleteOAuthCredentialsFromCache(c.req.param("provider"));
125
- return c.json({ ok: true });
144
+ const provider = c.req.param("provider");
145
+ deleteOAuthCredentialsFromCache(provider);
146
+ await new CredentialResolver().deleteProviderCredential(provider);
147
+ return c.json({
148
+ ok: true,
149
+ payload: { disconnected: provider }
150
+ });
126
151
  });
127
152
  /**
128
153
  * GET /api/auth/oauth
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth.ts"],"sourcesContent":["/**\n * OAuth HTTP Handler\n * \n * Provides HTTP endpoints for OAuth login flow.\n */\n\nimport { Hono } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\nimport { isProviderConfigured } from '../../providers/index.js';\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nfunction getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nfunction setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nfunction deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n\n/** No-op: OAuth tokens live on disk under auth paths; cache is populated during login. */\nexport function loadOAuthCredentialsToCache(_service: GatewayService): void {}\n\nexport function createOAuthHandler(_service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth/start\n * Start OAuth flow for a provider\n */\n oauth.post('/start', async (c) => {\n const { provider } = await c.req.json().catch(() => ({}));\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n try {\n let authResult: any = null;\n let manualCodeRequested = false;\n \n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n authResult = { url: auth.url, instructions: auth.instructions };\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n authResult = { \n message: prompt.message, \n deviceCode: prompt.deviceCode,\n verificationUri: prompt.verificationUri,\n };\n return prompt.deviceCode || '';\n },\n onProgress: (message: string) => {\n console.log('OAuth progress:', message);\n },\n onManualCodeInput: async () => {\n // For callback server providers, signal that manual code input is needed\n manualCodeRequested = true;\n // Return empty - frontend will handle manual input\n return '';\n },\n };\n\n const credentials = await oauthProvider.login(callbacks);\n setOAuthCredentialsToCache(provider, credentials);\n\n // Get API key from OAuth credentials\n const apiKey = oauthProvider.getApiKey(credentials);\n\n // Save API key to credential system\n const resolver = new CredentialResolver();\n await resolver.saveApiKey(provider, apiKey, { profileName: 'default' });\n\n return c.json({ \n ok: true, \n payload: { \n success: true,\n provider,\n message: 'OAuth login successful',\n expires: credentials.expires,\n authUrl: authResult?.url,\n deviceCode: authResult?.deviceCode,\n verificationUri: authResult?.verificationUri,\n instructions: authResult?.instructions,\n usesCallbackServer: oauthProvider.usesCallbackServer ?? false,\n manualCodeRequested,\n } \n });\n } catch (err) {\n console.error('OAuth login error:', err);\n return c.json({ \n error: err instanceof Error ? err.message : 'OAuth login failed' \n }, 500);\n }\n });\n\n /**\n * GET /api/auth/oauth/:provider\n * Check OAuth status for a provider\n */\n oauth.get('/:provider', async (c) => {\n const provider = c.req.param('provider');\n const credentials = getOAuthCredentialsFromCache(provider);\n const configured = await isProviderConfigured(provider);\n\n return c.json({ \n ok: true, \n payload: { \n configured: configured || !!credentials,\n expires: credentials?.expires \n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth/:provider\n * Revoke OAuth credentials\n */\n oauth.delete('/:provider', async (c) => {\n const provider = c.req.param('provider');\n \n deleteOAuthCredentialsFromCache(provider);\n\n return c.json({ ok: true });\n });\n\n /**\n * GET /api/auth/oauth\n * List available OAuth providers\n */\n oauth.get('/', (c) => {\n const result = Object.entries(OAUTH_PROVIDERS).map(([id, p]) => ({\n id,\n name: p.name,\n }));\n\n return c.json({ ok: true, payload: { providers: result } });\n });\n\n return oauth;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;kBAuB+D;gBACC;AAGhE,MAAM,kBAA0D;CAC9D,eAAe;CACf,WAAW;CACX,cAAc;CACd,aAAa;CACb,kBAAkB;CAClB,qBAAqBA;CACrB,sBAAsBC;CACtB,gBAAgB;CACjB;AAGD,MAAM,wCAAuD,IAAI,KAAK;AAEtE,SAAS,6BAA6B,UAAgD;AACpF,QAAO,sBAAsB,IAAI,SAAS;;AAG5C,SAAS,2BAA2B,UAAkB,OAA+B;AACnF,uBAAsB,IAAI,UAAU,MAAM;;AAG5C,SAAS,gCAAgC,UAAwB;AAC/D,uBAAsB,OAAO,SAAS;;;AAIxC,SAAgB,4BAA4B,UAAgC;AAE5E,SAAgB,mBAAmB,UAA0B;CAC3D,MAAM,QAAQ,IAAI,MAAM;;;;;AAMxB,OAAM,KAAK,UAAU,OAAO,MAAM;EAChC,MAAM,EAAE,aAAa,MAAM,EAAE,IAAI,MAAM,CAAC,aAAa,EAAE,EAAE;AAEzD,MAAI,CAAC,SACH,QAAO,EAAE,KAAK,EAAE,OAAO,wBAAwB,EAAE,IAAI;EAGvD,MAAM,gBAAgB,gBAAgB;AACtC,MAAI,CAAC,cACH,QAAO,EAAE,KAAK,EAAE,OAAO,2BAA2B,YAAY,EAAE,IAAI;AAGtE,MAAI;GACF,IAAI,aAAkB;GACtB,IAAI,sBAAsB;GAyB1B,MAAM,cAAc,MAAM,cAAc,MAAM;IAtB5C,SAAS,SAAiD;AACxD,kBAAa;MAAE,KAAK,KAAK;MAAK,cAAc,KAAK;MAAc;;IAEjE,UAAU,OAAO,WAA+E;AAC9F,kBAAa;MACX,SAAS,OAAO;MAChB,YAAY,OAAO;MACnB,iBAAiB,OAAO;MACzB;AACD,YAAO,OAAO,cAAc;;IAE9B,aAAa,YAAoB;AAC/B,aAAQ,IAAI,mBAAmB,QAAQ;;IAEzC,mBAAmB,YAAY;AAE7B,2BAAsB;AAEtB,YAAO;;IAI4C,CAAC;AACxD,8BAA2B,UAAU,YAAY;GAGjD,MAAM,SAAS,cAAc,UAAU,YAAY;AAInD,SAAM,IADe,oBACP,CAAC,WAAW,UAAU,QAAQ,EAAE,aAAa,WAAW,CAAC;AAEvE,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KACP,SAAS;KACT;KACA,SAAS;KACT,SAAS,YAAY;KACrB,SAAS,YAAY;KACrB,YAAY,YAAY;KACxB,iBAAiB,YAAY;KAC7B,cAAc,YAAY;KAC1B,oBAAoB,cAAc,sBAAsB;KACxD;KACD;IACF,CAAC;WACK,KAAK;AACZ,WAAQ,MAAM,sBAAsB,IAAI;AACxC,UAAO,EAAE,KAAK,EACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,sBAC7C,EAAE,IAAI;;GAET;;;;;AAMF,OAAM,IAAI,cAAc,OAAO,MAAM;EACnC,MAAM,WAAW,EAAE,IAAI,MAAM,WAAW;EACxC,MAAM,cAAc,6BAA6B,SAAS;EAC1D,MAAM,aAAa,MAAM,qBAAqB,SAAS;AAEvD,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP,YAAY,cAAc,CAAC,CAAC;IAC5B,SAAS,aAAa;IACvB;GACF,CAAC;GACF;;;;;AAMF,OAAM,OAAO,cAAc,OAAO,MAAM;AAGtC,kCAFiB,EAAE,IAAI,MAAM,WAEW,CAAC;AAEzC,SAAO,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC;GAC3B;;;;;AAMF,OAAM,IAAI,MAAM,MAAM;EACpB,MAAM,SAAS,OAAO,QAAQ,gBAAgB,CAAC,KAAK,CAAC,IAAI,QAAQ;GAC/D;GACA,MAAM,EAAE;GACT,EAAE;AAEH,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,SAAS,EAAE,WAAW,QAAQ;GAAE,CAAC;GAC3D;AAEF,QAAO"}
1
+ {"version":3,"file":"oauth.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth.ts"],"sourcesContent":["/**\n * OAuth HTTP Handler\n * \n * Provides HTTP endpoints for OAuth login flow.\n */\n\nimport { Hono } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\nimport { getProviderAuthState, isProviderConfigured } from '../../providers/index.js';\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nfunction getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nfunction setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nfunction deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n\n/** No-op: OAuth tokens live on disk under auth paths; cache is populated during login. */\nexport function loadOAuthCredentialsToCache(_service: GatewayService): void {}\n\nexport function createOAuthHandler(_service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth/start\n * Start OAuth flow for a provider\n */\n oauth.post('/start', async (c) => {\n const { provider } = await c.req.json().catch(() => ({}));\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n try {\n let authResult: any = null;\n let manualCodeRequested = false;\n \n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n authResult = { url: auth.url, instructions: auth.instructions };\n },\n onDeviceCode: (info) => {\n authResult = {\n url: info.verificationUri,\n instructions: `Enter code ${info.userCode}`,\n message: `Open ${info.verificationUri} and enter code ${info.userCode}`,\n deviceCode: info.userCode,\n verificationUri: info.verificationUri,\n };\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n authResult = { \n message: prompt.message, \n deviceCode: prompt.deviceCode,\n verificationUri: prompt.verificationUri,\n };\n return prompt.deviceCode || '';\n },\n onProgress: (message: string) => {\n console.log('OAuth progress:', message);\n },\n onManualCodeInput: async () => {\n // For callback server providers, signal that manual code input is needed\n manualCodeRequested = true;\n // Return empty - frontend will handle manual input\n return '';\n },\n onSelect: async (prompt) => {\n const browserOption = prompt.options.find((option) => option.id === 'browser');\n return browserOption?.id ?? prompt.options[0]?.id;\n },\n };\n\n const credentials = await oauthProvider.login(callbacks);\n setOAuthCredentialsToCache(provider, credentials);\n\n const resolver = new CredentialResolver();\n await resolver.saveOAuthToken(provider, {\n access: oauthProvider.getApiKey(credentials),\n refresh: credentials.refresh,\n expiresAt: credentials.expires,\n scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value): value is string => typeof value === 'string') : undefined,\n createdAt: new Date().toISOString(),\n });\n\n return c.json({ \n ok: true, \n payload: { \n success: true,\n provider,\n message: 'OAuth login successful',\n expires: credentials.expires,\n authUrl: authResult?.url,\n deviceCode: authResult?.deviceCode,\n verificationUri: authResult?.verificationUri,\n instructions: authResult?.instructions,\n usesCallbackServer: oauthProvider.usesCallbackServer ?? false,\n manualCodeRequested,\n } \n });\n } catch (err) {\n console.error('OAuth login error:', err);\n return c.json({ \n error: err instanceof Error ? err.message : 'OAuth login failed' \n }, 500);\n }\n });\n\n /**\n * GET /api/auth/oauth/:provider\n * Check OAuth status for a provider\n */\n oauth.get('/:provider', async (c) => {\n const provider = c.req.param('provider');\n const credentials = getOAuthCredentialsFromCache(provider);\n const authState = await getProviderAuthState(provider);\n const configured = await isProviderConfigured(provider);\n\n return c.json({ \n ok: true, \n payload: { \n configured: configured || !!credentials,\n authMode: authState.authMode,\n authStatus: authState.authStatus,\n expiresAt: authState.expiresAt ?? credentials?.expires,\n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth/:provider\n * Revoke OAuth credentials\n */\n oauth.delete('/:provider', async (c) => {\n const provider = c.req.param('provider');\n \n deleteOAuthCredentialsFromCache(provider);\n const resolver = new CredentialResolver();\n await resolver.deleteProviderCredential(provider);\n\n return c.json({ ok: true, payload: { disconnected: provider } });\n });\n\n /**\n * GET /api/auth/oauth\n * List available OAuth providers\n */\n oauth.get('/', (c) => {\n const result = Object.entries(OAUTH_PROVIDERS).map(([id, p]) => ({\n id,\n name: p.name,\n }));\n\n return c.json({ ok: true, payload: { providers: result } });\n });\n\n return oauth;\n}\n"],"mappings":"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"}
@@ -1,12 +1,14 @@
1
1
  import { init_agent_scope, normalizeAgentId } from "../../../agent/agent-scope.js";
2
+ import { init_localized_text, normalizeLocalizedText } from "../../../config/localized-text.js";
2
3
  import { init_schema, parseModelRef } from "../../../config/schema.js";
3
4
  import { init_providers, isProviderConfigured, resolveModel } from "../../../providers/index.js";
4
5
  import { getVoiceModelsConfig } from "../../../config/voice.js";
5
- import { agentModelFallbacksToArray, agentModelRefToString } from "../lib/agent-model.js";
6
6
  import { deleteAgentAvatarFile, finalizeCreateAgentDirs, listAgentProfileFiles, listGatewayAgents, prepareCreateAgent, prepareCreateAgentsBatch, prepareDeleteAgent, prepareUpdateAgent, readAgentAvatarFile, readAgentProfileFile, runAfterDeletePurge, writeAgentAvatarFromBase64, writeAgentProfileFile } from "../../agents-admin.js";
7
7
  import { resolveImageGenerationCapabilities, resolveImageUnderstandingCapabilities } from "../../image-capabilities.js";
8
+ import { agentModelFallbacksToArray, agentModelRefToString } from "../lib/agent-model.js";
8
9
  //#region src/gateway/hono/routes/agents.ts
9
10
  init_schema();
11
+ init_localized_text();
10
12
  init_providers();
11
13
  init_agent_scope();
12
14
  function parseProfileFiles(raw) {
@@ -22,14 +24,29 @@ function parseProfileFiles(raw) {
22
24
  function isParseError(value) {
23
25
  return typeof value === "object" && value !== null && "error" in value && typeof value.error === "string";
24
26
  }
27
+ function parseLocalizedText(raw, fieldName) {
28
+ if (raw === void 0) return;
29
+ if (typeof raw === "string") return raw;
30
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { error: `${fieldName} must be a string or locale map` };
31
+ const localized = {};
32
+ for (const [locale, text] of Object.entries(raw)) {
33
+ if (typeof text !== "string") return { error: `${fieldName}.${locale} must be a string` };
34
+ localized[locale] = text;
35
+ }
36
+ return normalizeLocalizedText(localized);
37
+ }
25
38
  function parseCreateAgentBody(raw) {
26
39
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { error: "each agent must be an object" };
27
40
  const body = raw;
28
- const name = typeof body.name === "string" ? body.name : "";
41
+ const parsedName = parseLocalizedText(body.name, "name");
42
+ if (isParseError(parsedName)) return parsedName;
43
+ const parsedDescription = parseLocalizedText(body.description, "description");
44
+ if (isParseError(parsedDescription)) return parsedDescription;
45
+ const name = parsedName ?? "";
29
46
  const workspace = typeof body.workspace === "string" ? body.workspace : "";
30
47
  const model = typeof body.model === "string" ? body.model : void 0;
31
48
  const agentDir = typeof body.agentDir === "string" ? body.agentDir : void 0;
32
- const description = typeof body.description === "string" ? body.description : void 0;
49
+ const description = parsedDescription;
33
50
  const id = typeof body.id === "string" ? body.id : void 0;
34
51
  const toolsDisable = Array.isArray(body.toolsDisable) ? body.toolsDisable.map((x) => String(x).trim()).filter(Boolean) : void 0;
35
52
  let profileFiles;
@@ -38,6 +55,7 @@ function parseCreateAgentBody(raw) {
38
55
  if (isParseError(parsed)) return parsed;
39
56
  profileFiles = parsed;
40
57
  }
58
+ const cloneFrom = typeof body.cloneFrom === "string" ? body.cloneFrom : void 0;
41
59
  return {
42
60
  name,
43
61
  workspace,
@@ -46,14 +64,15 @@ function parseCreateAgentBody(raw) {
46
64
  ...id !== void 0 ? { id } : {},
47
65
  ...description !== void 0 ? { description } : {},
48
66
  ...toolsDisable !== void 0 ? { toolsDisable } : {},
49
- ...profileFiles !== void 0 ? { profileFiles } : {}
67
+ ...profileFiles !== void 0 ? { profileFiles } : {},
68
+ ...cloneFrom !== void 0 ? { cloneFrom } : {}
50
69
  };
51
70
  }
52
71
  function registerAgentsRoutes(authenticated, deps) {
53
72
  const { service, strictRateLimitMiddleware } = deps;
54
73
  authenticated.get("/api/agents", async (c) => {
55
74
  const cfg = service.currentConfig;
56
- const payload = await listGatewayAgents(cfg);
75
+ const payload = await listGatewayAgents(cfg, { locale: c.req.query("locale") || c.req.header("Accept-Language")?.split(",")[0]?.trim() });
57
76
  return c.json({
58
77
  ok: true,
59
78
  payload
@@ -104,7 +123,7 @@ function registerAgentsRoutes(authenticated, deps) {
104
123
  }, finalized.status ?? 400);
105
124
  agentIds.push(item.agentId);
106
125
  }
107
- const agentsPayload = await listGatewayAgents(cfg);
126
+ const agentsPayload = await listGatewayAgents(cfg, { locale: c.req.query("locale") || c.req.header("Accept-Language")?.split(",")[0]?.trim() });
108
127
  return c.json({
109
128
  ok: true,
110
129
  payload: {
@@ -139,12 +158,16 @@ function registerAgentsRoutes(authenticated, deps) {
139
158
  ok: false,
140
159
  error: { message: save.error ?? "save failed" }
141
160
  }, 500);
142
- const finalized = await finalizeCreateAgentDirs(service.currentConfig, agentId, { ...parsed.profileFiles !== void 0 ? { profileFiles: parsed.profileFiles } : {} });
161
+ const finalized = await finalizeCreateAgentDirs(service.currentConfig, agentId, {
162
+ ...parsed.profileFiles !== void 0 ? { profileFiles: parsed.profileFiles } : {},
163
+ ...parsed.cloneFrom ? { cloneFrom: parsed.cloneFrom } : {}
164
+ });
143
165
  if (finalized.ok === false) return c.json({
144
166
  ok: false,
145
167
  error: { message: finalized.error }
146
168
  }, finalized.status ?? 400);
147
- const agentsPayload = await listGatewayAgents(service.currentConfig);
169
+ const locale = c.req.query("locale") || c.req.header("Accept-Language")?.split(",")[0]?.trim();
170
+ const agentsPayload = await listGatewayAgents(service.currentConfig, { locale });
148
171
  return c.json({
149
172
  ok: true,
150
173
  payload: {
@@ -166,9 +189,27 @@ function registerAgentsRoutes(authenticated, deps) {
166
189
  }
167
190
  const skillsPatch = body.skills === null ? null : Array.isArray(body.skills) ? body.skills.map((x) => String(x).trim()).filter(Boolean) : void 0;
168
191
  const toolsDisablePatch = body.toolsDisable === null ? null : Array.isArray(body.toolsDisable) ? body.toolsDisable.map((x) => String(x).trim()).filter(Boolean) : void 0;
169
- const descriptionPatch = Object.hasOwn(body, "description") ? body.description === null ? null : typeof body.description === "string" ? body.description : void 0 : void 0;
192
+ let namePatch;
193
+ if (Object.hasOwn(body, "name")) {
194
+ const parsedName = parseLocalizedText(body.name, "name");
195
+ if (isParseError(parsedName)) return c.json({
196
+ ok: false,
197
+ error: { message: parsedName.error }
198
+ }, 400);
199
+ namePatch = parsedName;
200
+ }
201
+ let descriptionPatch;
202
+ if (Object.hasOwn(body, "description")) if (body.description === null) descriptionPatch = null;
203
+ else {
204
+ const parsedDescription = parseLocalizedText(body.description, "description");
205
+ if (isParseError(parsedDescription)) return c.json({
206
+ ok: false,
207
+ error: { message: parsedDescription.error }
208
+ }, 400);
209
+ descriptionPatch = parsedDescription;
210
+ }
170
211
  const prep = prepareUpdateAgent(service.currentConfig, id, {
171
- name: typeof body.name === "string" ? body.name : void 0,
212
+ name: namePatch,
172
213
  ...descriptionPatch !== void 0 ? { description: descriptionPatch } : {},
173
214
  workspace: typeof body.workspace === "string" ? body.workspace : void 0,
174
215
  model: body.model === null ? null : typeof body.model === "string" ? body.model : void 0,
@@ -186,7 +227,8 @@ function registerAgentsRoutes(authenticated, deps) {
186
227
  ok: false,
187
228
  error: { message: save.error ?? "save failed" }
188
229
  }, 500);
189
- const agentsPayload = await listGatewayAgents(service.currentConfig);
230
+ const locale = c.req.query("locale") || c.req.header("Accept-Language")?.split(",")[0]?.trim();
231
+ const agentsPayload = await listGatewayAgents(service.currentConfig, { locale });
190
232
  return c.json({
191
233
  ok: true,
192
234
  payload: agentsPayload
@@ -207,7 +249,8 @@ function registerAgentsRoutes(authenticated, deps) {
207
249
  error: { message: save.error ?? "save failed" }
208
250
  }, 500);
209
251
  if (purge) await runAfterDeletePurge(service.currentConfig, agentId);
210
- const agentsPayload = await listGatewayAgents(service.currentConfig);
252
+ const locale = c.req.query("locale") || c.req.header("Accept-Language")?.split(",")[0]?.trim();
253
+ const agentsPayload = await listGatewayAgents(service.currentConfig, { locale });
211
254
  return c.json({
212
255
  ok: true,
213
256
  payload: {