@opengoat/core 2026.2.9

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 (288) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/apps/runtime/create-opengoat-runtime.d.ts +14 -0
  4. package/dist/apps/runtime/create-opengoat-runtime.js +23 -0
  5. package/dist/apps/runtime/create-opengoat-runtime.js.map +1 -0
  6. package/dist/core/acp/application/acp-agent.d.ts +60 -0
  7. package/dist/core/acp/application/acp-agent.js +424 -0
  8. package/dist/core/acp/application/acp-agent.js.map +1 -0
  9. package/dist/core/acp/application/session-store.d.ts +13 -0
  10. package/dist/core/acp/application/session-store.js +40 -0
  11. package/dist/core/acp/application/session-store.js.map +1 -0
  12. package/dist/core/acp/domain/meta.d.ts +7 -0
  13. package/dist/core/acp/domain/meta.js +31 -0
  14. package/dist/core/acp/domain/meta.js.map +1 -0
  15. package/dist/core/acp/domain/session.d.ts +23 -0
  16. package/dist/core/acp/domain/session.js +2 -0
  17. package/dist/core/acp/domain/session.js.map +1 -0
  18. package/dist/core/acp/index.d.ts +5 -0
  19. package/dist/core/acp/index.js +4 -0
  20. package/dist/core/acp/index.js.map +1 -0
  21. package/dist/core/agents/application/agent-manifest.service.d.ts +20 -0
  22. package/dist/core/agents/application/agent-manifest.service.js +89 -0
  23. package/dist/core/agents/application/agent-manifest.service.js.map +1 -0
  24. package/dist/core/agents/application/agent.service.d.ts +25 -0
  25. package/dist/core/agents/application/agent.service.js +191 -0
  26. package/dist/core/agents/application/agent.service.js.map +1 -0
  27. package/dist/core/agents/application/workspace-context.service.d.ts +28 -0
  28. package/dist/core/agents/application/workspace-context.service.js +157 -0
  29. package/dist/core/agents/application/workspace-context.service.js.map +1 -0
  30. package/dist/core/agents/domain/agent-manifest.d.ts +37 -0
  31. package/dist/core/agents/domain/agent-manifest.js +228 -0
  32. package/dist/core/agents/domain/agent-manifest.js.map +1 -0
  33. package/dist/core/agents/domain/workspace-context.d.ts +13 -0
  34. package/dist/core/agents/domain/workspace-context.js +14 -0
  35. package/dist/core/agents/domain/workspace-context.js.map +1 -0
  36. package/dist/core/agents/index.d.ts +6 -0
  37. package/dist/core/agents/index.js +6 -0
  38. package/dist/core/agents/index.js.map +1 -0
  39. package/dist/core/bootstrap/application/bootstrap.service.d.ts +24 -0
  40. package/dist/core/bootstrap/application/bootstrap.service.js +108 -0
  41. package/dist/core/bootstrap/application/bootstrap.service.js.map +1 -0
  42. package/dist/core/bootstrap/index.d.ts +1 -0
  43. package/dist/core/bootstrap/index.js +2 -0
  44. package/dist/core/bootstrap/index.js.map +1 -0
  45. package/dist/core/domain/agent-id.d.ts +3 -0
  46. package/dist/core/domain/agent-id.js +12 -0
  47. package/dist/core/domain/agent-id.js.map +1 -0
  48. package/dist/core/domain/agent.d.ts +39 -0
  49. package/dist/core/domain/agent.js +2 -0
  50. package/dist/core/domain/agent.js.map +1 -0
  51. package/dist/core/domain/opengoat-paths.d.ts +29 -0
  52. package/dist/core/domain/opengoat-paths.js +2 -0
  53. package/dist/core/domain/opengoat-paths.js.map +1 -0
  54. package/dist/core/gateway/domain/protocol.d.ts +113 -0
  55. package/dist/core/gateway/domain/protocol.js +394 -0
  56. package/dist/core/gateway/domain/protocol.js.map +1 -0
  57. package/dist/core/gateway/index.d.ts +2 -0
  58. package/dist/core/gateway/index.js +2 -0
  59. package/dist/core/gateway/index.js.map +1 -0
  60. package/dist/core/llm/application/vercel-ai-text-runtime.d.ts +26 -0
  61. package/dist/core/llm/application/vercel-ai-text-runtime.js +223 -0
  62. package/dist/core/llm/application/vercel-ai-text-runtime.js.map +1 -0
  63. package/dist/core/llm/domain/text-runtime.d.ts +22 -0
  64. package/dist/core/llm/domain/text-runtime.js +2 -0
  65. package/dist/core/llm/domain/text-runtime.js.map +1 -0
  66. package/dist/core/llm/index.d.ts +2 -0
  67. package/dist/core/llm/index.js +2 -0
  68. package/dist/core/llm/index.js.map +1 -0
  69. package/dist/core/logging/application/structured-logger.d.ts +29 -0
  70. package/dist/core/logging/application/structured-logger.js +86 -0
  71. package/dist/core/logging/application/structured-logger.js.map +1 -0
  72. package/dist/core/logging/domain/logger.d.ts +16 -0
  73. package/dist/core/logging/domain/logger.js +18 -0
  74. package/dist/core/logging/domain/logger.js.map +1 -0
  75. package/dist/core/logging/index.d.ts +3 -0
  76. package/dist/core/logging/index.js +3 -0
  77. package/dist/core/logging/index.js.map +1 -0
  78. package/dist/core/opengoat/application/opengoat.service.d.ts +84 -0
  79. package/dist/core/opengoat/application/opengoat.service.js +308 -0
  80. package/dist/core/opengoat/application/opengoat.service.js.map +1 -0
  81. package/dist/core/opengoat/index.d.ts +1 -0
  82. package/dist/core/opengoat/index.js +2 -0
  83. package/dist/core/opengoat/index.js.map +1 -0
  84. package/dist/core/orchestration/application/orchestration-planner.service.d.ts +28 -0
  85. package/dist/core/orchestration/application/orchestration-planner.service.js +279 -0
  86. package/dist/core/orchestration/application/orchestration-planner.service.js.map +1 -0
  87. package/dist/core/orchestration/application/orchestration.service.d.ts +52 -0
  88. package/dist/core/orchestration/application/orchestration.service.js +1044 -0
  89. package/dist/core/orchestration/application/orchestration.service.js.map +1 -0
  90. package/dist/core/orchestration/application/routing.service.d.ts +11 -0
  91. package/dist/core/orchestration/application/routing.service.js +108 -0
  92. package/dist/core/orchestration/application/routing.service.js.map +1 -0
  93. package/dist/core/orchestration/domain/loop.d.ts +119 -0
  94. package/dist/core/orchestration/domain/loop.js +2 -0
  95. package/dist/core/orchestration/domain/loop.js.map +1 -0
  96. package/dist/core/orchestration/domain/routing.d.ts +58 -0
  97. package/dist/core/orchestration/domain/routing.js +2 -0
  98. package/dist/core/orchestration/domain/routing.js.map +1 -0
  99. package/dist/core/orchestration/domain/run-events.d.ts +21 -0
  100. package/dist/core/orchestration/domain/run-events.js +2 -0
  101. package/dist/core/orchestration/domain/run-events.js.map +1 -0
  102. package/dist/core/orchestration/index.d.ts +6 -0
  103. package/dist/core/orchestration/index.js +4 -0
  104. package/dist/core/orchestration/index.js.map +1 -0
  105. package/dist/core/plugins/application/plugin.service.d.ts +32 -0
  106. package/dist/core/plugins/application/plugin.service.js +236 -0
  107. package/dist/core/plugins/application/plugin.service.js.map +1 -0
  108. package/dist/core/plugins/domain/openclaw-compat.d.ts +60 -0
  109. package/dist/core/plugins/domain/openclaw-compat.js +9 -0
  110. package/dist/core/plugins/domain/openclaw-compat.js.map +1 -0
  111. package/dist/core/plugins/index.d.ts +3 -0
  112. package/dist/core/plugins/index.js +3 -0
  113. package/dist/core/plugins/index.js.map +1 -0
  114. package/dist/core/ports/command-runner.port.d.ts +14 -0
  115. package/dist/core/ports/command-runner.port.js +2 -0
  116. package/dist/core/ports/command-runner.port.js.map +1 -0
  117. package/dist/core/ports/file-system.port.d.ts +9 -0
  118. package/dist/core/ports/file-system.port.js +2 -0
  119. package/dist/core/ports/file-system.port.js.map +1 -0
  120. package/dist/core/ports/path.port.d.ts +3 -0
  121. package/dist/core/ports/path.port.js +2 -0
  122. package/dist/core/ports/path.port.js.map +1 -0
  123. package/dist/core/ports/paths-provider.port.d.ts +4 -0
  124. package/dist/core/ports/paths-provider.port.js +2 -0
  125. package/dist/core/ports/paths-provider.port.js.map +1 -0
  126. package/dist/core/providers/application/provider.service.d.ts +80 -0
  127. package/dist/core/providers/application/provider.service.js +391 -0
  128. package/dist/core/providers/application/provider.service.js.map +1 -0
  129. package/dist/core/providers/base-provider.d.ts +20 -0
  130. package/dist/core/providers/base-provider.js +44 -0
  131. package/dist/core/providers/base-provider.js.map +1 -0
  132. package/dist/core/providers/cli-provider.d.ts +26 -0
  133. package/dist/core/providers/cli-provider.js +151 -0
  134. package/dist/core/providers/cli-provider.js.map +1 -0
  135. package/dist/core/providers/command-executor.d.ts +12 -0
  136. package/dist/core/providers/command-executor.js +76 -0
  137. package/dist/core/providers/command-executor.js.map +1 -0
  138. package/dist/core/providers/errors.d.ts +30 -0
  139. package/dist/core/providers/errors.js +52 -0
  140. package/dist/core/providers/errors.js.map +1 -0
  141. package/dist/core/providers/index.d.ts +18 -0
  142. package/dist/core/providers/index.js +27 -0
  143. package/dist/core/providers/index.js.map +1 -0
  144. package/dist/core/providers/loader.d.ts +2 -0
  145. package/dist/core/providers/loader.js +99 -0
  146. package/dist/core/providers/loader.js.map +1 -0
  147. package/dist/core/providers/onboarding.d.ts +13 -0
  148. package/dist/core/providers/onboarding.js +149 -0
  149. package/dist/core/providers/onboarding.js.map +1 -0
  150. package/dist/core/providers/provider-module.d.ts +19 -0
  151. package/dist/core/providers/provider-module.js +2 -0
  152. package/dist/core/providers/provider-module.js.map +1 -0
  153. package/dist/core/providers/provider-session.d.ts +3 -0
  154. package/dist/core/providers/provider-session.js +44 -0
  155. package/dist/core/providers/provider-session.js.map +1 -0
  156. package/dist/core/providers/providers/claude/index.d.ts +4 -0
  157. package/dist/core/providers/providers/claude/index.js +19 -0
  158. package/dist/core/providers/providers/claude/index.js.map +1 -0
  159. package/dist/core/providers/providers/claude/provider.d.ts +8 -0
  160. package/dist/core/providers/providers/claude/provider.js +106 -0
  161. package/dist/core/providers/providers/claude/provider.js.map +1 -0
  162. package/dist/core/providers/providers/codex/index.d.ts +4 -0
  163. package/dist/core/providers/providers/codex/index.js +19 -0
  164. package/dist/core/providers/providers/codex/index.js.map +1 -0
  165. package/dist/core/providers/providers/codex/provider.d.ts +7 -0
  166. package/dist/core/providers/providers/codex/provider.js +31 -0
  167. package/dist/core/providers/providers/codex/provider.js.map +1 -0
  168. package/dist/core/providers/providers/cursor/index.d.ts +4 -0
  169. package/dist/core/providers/providers/cursor/index.js +19 -0
  170. package/dist/core/providers/providers/cursor/index.js.map +1 -0
  171. package/dist/core/providers/providers/cursor/provider.d.ts +11 -0
  172. package/dist/core/providers/providers/cursor/provider.js +90 -0
  173. package/dist/core/providers/providers/cursor/provider.js.map +1 -0
  174. package/dist/core/providers/providers/extended-http/catalog.d.ts +40 -0
  175. package/dist/core/providers/providers/extended-http/catalog.js +728 -0
  176. package/dist/core/providers/providers/extended-http/catalog.js.map +1 -0
  177. package/dist/core/providers/providers/extended-http/index.d.ts +5 -0
  178. package/dist/core/providers/providers/extended-http/index.js +13 -0
  179. package/dist/core/providers/providers/extended-http/index.js.map +1 -0
  180. package/dist/core/providers/providers/extended-http/provider.d.ts +31 -0
  181. package/dist/core/providers/providers/extended-http/provider.js +580 -0
  182. package/dist/core/providers/providers/extended-http/provider.js.map +1 -0
  183. package/dist/core/providers/providers/gemini/index.d.ts +4 -0
  184. package/dist/core/providers/providers/gemini/index.js +37 -0
  185. package/dist/core/providers/providers/gemini/index.js.map +1 -0
  186. package/dist/core/providers/providers/gemini/provider.d.ts +7 -0
  187. package/dist/core/providers/providers/gemini/provider.js +59 -0
  188. package/dist/core/providers/providers/gemini/provider.js.map +1 -0
  189. package/dist/core/providers/providers/grok/index.d.ts +4 -0
  190. package/dist/core/providers/providers/grok/index.js +41 -0
  191. package/dist/core/providers/providers/grok/index.js.map +1 -0
  192. package/dist/core/providers/providers/grok/provider.d.ts +13 -0
  193. package/dist/core/providers/providers/grok/provider.js +95 -0
  194. package/dist/core/providers/providers/grok/provider.js.map +1 -0
  195. package/dist/core/providers/providers/openai/index.d.ts +4 -0
  196. package/dist/core/providers/providers/openai/index.js +45 -0
  197. package/dist/core/providers/providers/openai/index.js.map +1 -0
  198. package/dist/core/providers/providers/openai/provider.d.ts +14 -0
  199. package/dist/core/providers/providers/openai/provider.js +203 -0
  200. package/dist/core/providers/providers/openai/provider.js.map +1 -0
  201. package/dist/core/providers/providers/openclaw/index.d.ts +4 -0
  202. package/dist/core/providers/providers/openclaw/index.js +31 -0
  203. package/dist/core/providers/providers/openclaw/index.js.map +1 -0
  204. package/dist/core/providers/providers/openclaw/provider.d.ts +11 -0
  205. package/dist/core/providers/providers/openclaw/provider.js +108 -0
  206. package/dist/core/providers/providers/openclaw/provider.js.map +1 -0
  207. package/dist/core/providers/providers/opencode/index.d.ts +4 -0
  208. package/dist/core/providers/providers/opencode/index.js +23 -0
  209. package/dist/core/providers/providers/opencode/index.js.map +1 -0
  210. package/dist/core/providers/providers/opencode/provider.d.ts +11 -0
  211. package/dist/core/providers/providers/opencode/provider.js +215 -0
  212. package/dist/core/providers/providers/opencode/provider.js.map +1 -0
  213. package/dist/core/providers/providers/openrouter/index.d.ts +4 -0
  214. package/dist/core/providers/providers/openrouter/index.js +37 -0
  215. package/dist/core/providers/providers/openrouter/index.js.map +1 -0
  216. package/dist/core/providers/providers/openrouter/provider.d.ts +13 -0
  217. package/dist/core/providers/providers/openrouter/provider.js +80 -0
  218. package/dist/core/providers/providers/openrouter/provider.js.map +1 -0
  219. package/dist/core/providers/registry.d.ts +13 -0
  220. package/dist/core/providers/registry.js +36 -0
  221. package/dist/core/providers/registry.js.map +1 -0
  222. package/dist/core/providers/types.d.ts +101 -0
  223. package/dist/core/providers/types.js +2 -0
  224. package/dist/core/providers/types.js.map +1 -0
  225. package/dist/core/scenarios/application/scenario-runner.service.d.ts +23 -0
  226. package/dist/core/scenarios/application/scenario-runner.service.js +172 -0
  227. package/dist/core/scenarios/application/scenario-runner.service.js.map +1 -0
  228. package/dist/core/scenarios/domain/scenario.d.ts +43 -0
  229. package/dist/core/scenarios/domain/scenario.js +2 -0
  230. package/dist/core/scenarios/domain/scenario.js.map +1 -0
  231. package/dist/core/scenarios/index.d.ts +2 -0
  232. package/dist/core/scenarios/index.js +2 -0
  233. package/dist/core/scenarios/index.js.map +1 -0
  234. package/dist/core/sessions/application/session.service.d.ts +74 -0
  235. package/dist/core/sessions/application/session.service.js +895 -0
  236. package/dist/core/sessions/application/session.service.js.map +1 -0
  237. package/dist/core/sessions/domain/session.d.ts +80 -0
  238. package/dist/core/sessions/domain/session.js +24 -0
  239. package/dist/core/sessions/domain/session.js.map +1 -0
  240. package/dist/core/sessions/domain/transcript.d.ts +28 -0
  241. package/dist/core/sessions/domain/transcript.js +10 -0
  242. package/dist/core/sessions/domain/transcript.js.map +1 -0
  243. package/dist/core/sessions/errors.d.ts +12 -0
  244. package/dist/core/sessions/errors.js +22 -0
  245. package/dist/core/sessions/errors.js.map +1 -0
  246. package/dist/core/sessions/index.d.ts +6 -0
  247. package/dist/core/sessions/index.js +4 -0
  248. package/dist/core/sessions/index.js.map +1 -0
  249. package/dist/core/skills/application/skill.service.d.ts +24 -0
  250. package/dist/core/skills/application/skill.service.js +375 -0
  251. package/dist/core/skills/application/skill.service.js.map +1 -0
  252. package/dist/core/skills/domain/skill.d.ts +62 -0
  253. package/dist/core/skills/domain/skill.js +46 -0
  254. package/dist/core/skills/domain/skill.js.map +1 -0
  255. package/dist/core/skills/index.d.ts +3 -0
  256. package/dist/core/skills/index.js +3 -0
  257. package/dist/core/skills/index.js.map +1 -0
  258. package/dist/core/templates/default-templates.d.ts +19 -0
  259. package/dist/core/templates/default-templates.js +259 -0
  260. package/dist/core/templates/default-templates.js.map +1 -0
  261. package/dist/index.d.ts +27 -0
  262. package/dist/index.js +28 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/platform/node/acp-server.d.ts +12 -0
  265. package/dist/platform/node/acp-server.js +18 -0
  266. package/dist/platform/node/acp-server.js.map +1 -0
  267. package/dist/platform/node/dotenv.d.ts +6 -0
  268. package/dist/platform/node/dotenv.js +67 -0
  269. package/dist/platform/node/dotenv.js.map +1 -0
  270. package/dist/platform/node/node-command-runner.d.ts +4 -0
  271. package/dist/platform/node/node-command-runner.js +29 -0
  272. package/dist/platform/node/node-command-runner.js.map +1 -0
  273. package/dist/platform/node/node-file-system.d.ts +10 -0
  274. package/dist/platform/node/node-file-system.js +47 -0
  275. package/dist/platform/node/node-file-system.js.map +1 -0
  276. package/dist/platform/node/node-logger.d.ts +9 -0
  277. package/dist/platform/node/node-logger.js +124 -0
  278. package/dist/platform/node/node-logger.js.map +1 -0
  279. package/dist/platform/node/node-path.port.d.ts +9 -0
  280. package/dist/platform/node/node-path.port.js +41 -0
  281. package/dist/platform/node/node-path.port.js.map +1 -0
  282. package/dist/platform/node/opengoat-gateway-client.d.ts +19 -0
  283. package/dist/platform/node/opengoat-gateway-client.js +194 -0
  284. package/dist/platform/node/opengoat-gateway-client.js.map +1 -0
  285. package/dist/platform/node/opengoat-gateway-server.d.ts +53 -0
  286. package/dist/platform/node/opengoat-gateway-server.js +906 -0
  287. package/dist/platform/node/opengoat-gateway-server.js.map +1 -0
  288. package/package.json +37 -0
@@ -0,0 +1,906 @@
1
+ import { createServer } from "node:http";
2
+ import { randomUUID, timingSafeEqual, randomBytes } from "node:crypto";
3
+ import { WebSocketServer } from "ws";
4
+ import { DEFAULT_AGENT_ID } from "../../core/domain/agent-id.js";
5
+ import { OPENGOAT_GATEWAY_DEFAULTS, OPENGOAT_GATEWAY_ERROR_CODES, OPENGOAT_GATEWAY_EVENTS, OPENGOAT_GATEWAY_METHODS, OPENGOAT_GATEWAY_PROTOCOL_VERSION, OPENGOAT_GATEWAY_SCOPES, isOpenGoatGatewayMethod, isReadMethod, parseAgentRunParams, parseConnectParams, parseGatewayRequestFrame, parseSessionHistoryParams, parseSessionListParams } from "../../core/gateway/index.js";
6
+ const DEFAULT_GATEWAY_LOGGER = {
7
+ info: () => {
8
+ // no-op
9
+ },
10
+ warn: () => {
11
+ // no-op
12
+ },
13
+ error: () => {
14
+ // no-op
15
+ },
16
+ debug: () => {
17
+ // no-op
18
+ }
19
+ };
20
+ export async function startOpenGoatGatewayServer(service, options = {}) {
21
+ const logger = options.logger ?? DEFAULT_GATEWAY_LOGGER;
22
+ const config = buildRuntimeConfig(options);
23
+ const startedAtMs = Date.now();
24
+ const clients = new Map();
25
+ const idempotency = new Map();
26
+ let eventSeq = 0;
27
+ const httpServer = createServer((request, response) => {
28
+ handleHttpRequest(request, response, {
29
+ startedAtMs,
30
+ clients,
31
+ path: config.path,
32
+ protocolVersion: OPENGOAT_GATEWAY_PROTOCOL_VERSION,
33
+ authRequired: config.requireAuth,
34
+ hasAuthToken: Boolean(config.authToken)
35
+ });
36
+ });
37
+ const websocketServer = new WebSocketServer({
38
+ noServer: true,
39
+ maxPayload: config.maxPayloadBytes
40
+ });
41
+ httpServer.on("upgrade", (request, socket, head) => {
42
+ const requestPath = resolveRequestPath(request);
43
+ if (requestPath !== config.path) {
44
+ socket.write("HTTP/1.1 404 Not Found\\r\\n\\r\\n");
45
+ socket.destroy();
46
+ return;
47
+ }
48
+ websocketServer.handleUpgrade(request, socket, head, (ws) => {
49
+ websocketServer.emit("connection", ws, request);
50
+ });
51
+ });
52
+ websocketServer.on("connection", (socket, request) => {
53
+ const connId = randomUUID();
54
+ const remoteAddress = request.socket.remoteAddress;
55
+ const requestHost = headerValue(request.headers.host);
56
+ const requestOrigin = headerValue(request.headers.origin);
57
+ const connectNonce = randomUUID();
58
+ const client = {
59
+ connId,
60
+ socket,
61
+ remoteAddress,
62
+ requestHost,
63
+ requestOrigin,
64
+ connectedAtMs: Date.now(),
65
+ connectNonce,
66
+ requestTimes: [],
67
+ maxBufferedBytes: config.maxBufferedBytes,
68
+ scopes: [OPENGOAT_GATEWAY_SCOPES.admin]
69
+ };
70
+ logger.info("Gateway websocket connected.", {
71
+ connId,
72
+ remoteAddress,
73
+ requestHost,
74
+ requestOrigin
75
+ });
76
+ sendEvent(client, {
77
+ event: "connect.challenge",
78
+ payload: { nonce: connectNonce, ts: Date.now() }
79
+ });
80
+ const handshakeTimer = setTimeout(() => {
81
+ if (!client.connect) {
82
+ logger.warn("Gateway websocket handshake timeout.", {
83
+ connId,
84
+ remoteAddress
85
+ });
86
+ closeSocket(client.socket, 1008, "handshake timeout");
87
+ }
88
+ }, config.handshakeTimeoutMs);
89
+ socket.on("message", async (buffer) => {
90
+ const text = toUtf8String(buffer);
91
+ if (!text) {
92
+ closeSocket(socket, 1008, "invalid payload");
93
+ return;
94
+ }
95
+ let parsedFrame;
96
+ try {
97
+ parsedFrame = JSON.parse(text);
98
+ }
99
+ catch {
100
+ sendError(client, {
101
+ id: "invalid",
102
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
103
+ message: "invalid json frame"
104
+ });
105
+ if (!client.connect) {
106
+ closeSocket(socket, 1008, "invalid json frame");
107
+ }
108
+ return;
109
+ }
110
+ const frame = parseGatewayRequestFrame(parsedFrame);
111
+ if (!frame.ok) {
112
+ sendError(client, {
113
+ id: "invalid",
114
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
115
+ message: frame.error
116
+ });
117
+ if (!client.connect) {
118
+ closeSocket(socket, 1008, frame.error);
119
+ }
120
+ return;
121
+ }
122
+ if (!client.connect) {
123
+ if (frame.value.method !== "connect") {
124
+ sendError(client, {
125
+ id: frame.value.id,
126
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
127
+ message: "first request must be connect"
128
+ });
129
+ closeSocket(socket, 1008, "first request must be connect");
130
+ return;
131
+ }
132
+ const connect = parseConnectParams(frame.value.params);
133
+ if (!connect.ok) {
134
+ sendError(client, {
135
+ id: frame.value.id,
136
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
137
+ message: connect.error
138
+ });
139
+ closeSocket(socket, 1008, connect.error);
140
+ return;
141
+ }
142
+ const authResult = authorizeConnect({
143
+ connect: connect.value,
144
+ authToken: config.authToken,
145
+ requireAuth: config.requireAuth,
146
+ challengeNonce: connectNonce,
147
+ remoteAddress,
148
+ allowedOrigins: config.allowedOrigins,
149
+ requestHost,
150
+ requestOrigin
151
+ });
152
+ if (!authResult.ok) {
153
+ sendError(client, {
154
+ id: frame.value.id,
155
+ code: authResult.code,
156
+ message: authResult.message
157
+ });
158
+ closeSocket(socket, 1008, authResult.message);
159
+ return;
160
+ }
161
+ const scoped = resolveScopes(connect.value.scopes);
162
+ client.connect = connect.value;
163
+ client.scopes = scoped;
164
+ clients.set(connId, client);
165
+ clearTimeout(handshakeTimer);
166
+ sendResponse(client, {
167
+ id: frame.value.id,
168
+ ok: true,
169
+ payload: {
170
+ type: "hello-ok",
171
+ protocol: OPENGOAT_GATEWAY_PROTOCOL_VERSION,
172
+ server: {
173
+ version: process.env.npm_package_version ?? "dev",
174
+ connId,
175
+ startedAtMs
176
+ },
177
+ features: {
178
+ methods: OPENGOAT_GATEWAY_METHODS,
179
+ events: OPENGOAT_GATEWAY_EVENTS,
180
+ optionalGateway: true
181
+ },
182
+ policy: {
183
+ maxPayload: config.maxPayloadBytes,
184
+ maxBufferedBytes: config.maxBufferedBytes,
185
+ handshakeTimeoutMs: config.handshakeTimeoutMs,
186
+ tickIntervalMs: config.tickIntervalMs,
187
+ rateLimitPerMinute: config.rateLimitPerMinute,
188
+ idempotencyTtlMs: config.idempotencyTtlMs
189
+ },
190
+ snapshot: {
191
+ connectedClients: clients.size,
192
+ uptimeMs: Date.now() - startedAtMs
193
+ }
194
+ }
195
+ });
196
+ logger.info("Gateway websocket authenticated.", {
197
+ connId,
198
+ clientId: connect.value.client.id,
199
+ clientMode: connect.value.client.mode,
200
+ scopes: scoped
201
+ });
202
+ return;
203
+ }
204
+ if (!checkRateLimit(client, config.rateLimitPerMinute)) {
205
+ const retryAfterMs = resolveRateLimitRetryMs(client);
206
+ sendError(client, {
207
+ id: frame.value.id,
208
+ code: OPENGOAT_GATEWAY_ERROR_CODES.rateLimited,
209
+ message: "rate limit exceeded",
210
+ retryable: true,
211
+ retryAfterMs
212
+ });
213
+ return;
214
+ }
215
+ if (!isOpenGoatGatewayMethod(frame.value.method)) {
216
+ sendError(client, {
217
+ id: frame.value.id,
218
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
219
+ message: `unknown method: ${frame.value.method}`
220
+ });
221
+ return;
222
+ }
223
+ const method = frame.value.method;
224
+ if (!isAuthorizedForMethod(method, client.scopes)) {
225
+ sendError(client, {
226
+ id: frame.value.id,
227
+ code: OPENGOAT_GATEWAY_ERROR_CODES.forbidden,
228
+ message: "missing scope"
229
+ });
230
+ return;
231
+ }
232
+ try {
233
+ const response = await handleGatewayMethod({
234
+ service,
235
+ method,
236
+ frameId: frame.value.id,
237
+ params: frame.value.params,
238
+ client,
239
+ idempotency,
240
+ idempotencyTtlMs: config.idempotencyTtlMs,
241
+ idempotencyMaxEntries: config.idempotencyMaxEntries,
242
+ logger,
243
+ sendAgentEvent: (payload) => {
244
+ sendEvent(client, {
245
+ event: "agent.stream",
246
+ payload
247
+ });
248
+ }
249
+ });
250
+ sendResponse(client, response);
251
+ }
252
+ catch (error) {
253
+ logger.error("Gateway method failed.", {
254
+ connId,
255
+ method,
256
+ error: String(error)
257
+ });
258
+ sendError(client, {
259
+ id: frame.value.id,
260
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unavailable,
261
+ message: toErrorMessage(error)
262
+ });
263
+ }
264
+ });
265
+ socket.on("close", () => {
266
+ clearTimeout(handshakeTimer);
267
+ clients.delete(connId);
268
+ logger.info("Gateway websocket closed.", {
269
+ connId,
270
+ remoteAddress
271
+ });
272
+ });
273
+ socket.on("error", (error) => {
274
+ logger.warn("Gateway websocket error.", {
275
+ connId,
276
+ error: String(error)
277
+ });
278
+ });
279
+ });
280
+ const tickTimer = setInterval(() => {
281
+ for (const client of clients.values()) {
282
+ sendEvent(client, {
283
+ event: "tick",
284
+ payload: {
285
+ ts: Date.now(),
286
+ uptimeMs: Date.now() - startedAtMs
287
+ },
288
+ seq: ++eventSeq
289
+ });
290
+ }
291
+ }, config.tickIntervalMs);
292
+ const maintenanceTimer = setInterval(() => {
293
+ cleanupIdempotencyEntries(idempotency, {
294
+ ttlMs: config.idempotencyTtlMs,
295
+ maxEntries: config.idempotencyMaxEntries
296
+ });
297
+ }, 60_000);
298
+ const listenPort = options.port ?? 18789;
299
+ await listen(httpServer, listenPort, config.bindHost);
300
+ const boundPort = resolveServerPort(httpServer);
301
+ const protocol = "ws";
302
+ const url = `${protocol}://${formatHostForUrl(config.bindHost)}:${boundPort}${config.path}`;
303
+ logger.info("OpenGoat gateway started.", {
304
+ url,
305
+ bindHost: config.bindHost,
306
+ port: boundPort,
307
+ authRequired: config.requireAuth,
308
+ hasAuthToken: Boolean(config.authToken)
309
+ });
310
+ let closePromise;
311
+ let resolveClosed;
312
+ const closed = new Promise((resolve) => {
313
+ resolveClosed = resolve;
314
+ });
315
+ const close = async () => {
316
+ if (closePromise) {
317
+ await closePromise;
318
+ return;
319
+ }
320
+ closePromise = (async () => {
321
+ clearInterval(tickTimer);
322
+ clearInterval(maintenanceTimer);
323
+ for (const client of clients.values()) {
324
+ closeSocket(client.socket, 1001, "server shutdown");
325
+ }
326
+ clients.clear();
327
+ await Promise.all([closeWebSocketServer(websocketServer), closeHttpServer(httpServer)]);
328
+ resolveClosed?.();
329
+ })();
330
+ await closePromise;
331
+ };
332
+ return {
333
+ url,
334
+ port: boundPort,
335
+ bindHost: config.bindHost,
336
+ path: config.path,
337
+ authToken: config.authToken,
338
+ closed,
339
+ close
340
+ };
341
+ }
342
+ async function handleGatewayMethod(params) {
343
+ const { service, method, frameId, params: methodParams, client, idempotency, idempotencyTtlMs, idempotencyMaxEntries, logger, sendAgentEvent } = params;
344
+ if (method === "health") {
345
+ return {
346
+ id: frameId,
347
+ ok: true,
348
+ payload: {
349
+ status: "ok",
350
+ protocol: OPENGOAT_GATEWAY_PROTOCOL_VERSION,
351
+ homeDir: service.getHomeDir(),
352
+ connectedAtMs: client.connectedAtMs,
353
+ serverTime: new Date().toISOString()
354
+ }
355
+ };
356
+ }
357
+ if (method === "agent.list") {
358
+ return {
359
+ id: frameId,
360
+ ok: true,
361
+ payload: {
362
+ agents: await service.listAgents()
363
+ }
364
+ };
365
+ }
366
+ if (method === "session.list") {
367
+ const parsed = parseSessionListParams(methodParams);
368
+ if (!parsed.ok) {
369
+ return {
370
+ id: frameId,
371
+ ok: false,
372
+ error: toGatewayError({
373
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
374
+ message: parsed.error
375
+ })
376
+ };
377
+ }
378
+ const sessions = await service.listSessions(parsed.value.agentId ?? DEFAULT_AGENT_ID, {
379
+ activeMinutes: parsed.value.activeMinutes
380
+ });
381
+ return {
382
+ id: frameId,
383
+ ok: true,
384
+ payload: {
385
+ sessions
386
+ }
387
+ };
388
+ }
389
+ if (method === "session.history") {
390
+ const parsed = parseSessionHistoryParams(methodParams);
391
+ if (!parsed.ok) {
392
+ return {
393
+ id: frameId,
394
+ ok: false,
395
+ error: toGatewayError({
396
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
397
+ message: parsed.error
398
+ })
399
+ };
400
+ }
401
+ const history = await service.getSessionHistory(parsed.value.agentId ?? DEFAULT_AGENT_ID, {
402
+ sessionRef: parsed.value.sessionRef,
403
+ limit: parsed.value.limit,
404
+ includeCompaction: parsed.value.includeCompaction
405
+ });
406
+ return {
407
+ id: frameId,
408
+ ok: true,
409
+ payload: {
410
+ history
411
+ }
412
+ };
413
+ }
414
+ if (method === "agent.run") {
415
+ const parsed = parseAgentRunParams(methodParams);
416
+ if (!parsed.ok) {
417
+ return {
418
+ id: frameId,
419
+ ok: false,
420
+ error: toGatewayError({
421
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
422
+ message: parsed.error
423
+ })
424
+ };
425
+ }
426
+ const dedupeKey = buildIdempotencyKey({
427
+ client: client.connect?.client,
428
+ method,
429
+ idempotencyKey: parsed.value.idempotencyKey
430
+ });
431
+ const existing = idempotency.get(dedupeKey);
432
+ if (existing && Date.now() - existing.ts <= idempotencyTtlMs) {
433
+ logger.debug("Gateway idempotent response replayed.", {
434
+ method,
435
+ idempotencyKey: parsed.value.idempotencyKey
436
+ });
437
+ if (existing.response) {
438
+ return withResponseId(frameId, existing.response);
439
+ }
440
+ if (existing.pending) {
441
+ const pendingResponse = await existing.pending;
442
+ return withResponseId(frameId, pendingResponse);
443
+ }
444
+ }
445
+ const runId = frameId;
446
+ const pending = (async () => {
447
+ const result = await service.runAgent(parsed.value.agentId ?? DEFAULT_AGENT_ID, {
448
+ message: parsed.value.message,
449
+ sessionRef: parsed.value.sessionRef,
450
+ forceNewSession: parsed.value.forceNewSession,
451
+ disableSession: parsed.value.disableSession,
452
+ cwd: parsed.value.cwd,
453
+ model: parsed.value.model,
454
+ onStdout: (chunk) => {
455
+ if (chunk) {
456
+ sendAgentEvent({ runId, stream: "stdout", chunk });
457
+ }
458
+ },
459
+ onStderr: (chunk) => {
460
+ if (chunk) {
461
+ sendAgentEvent({ runId, stream: "stderr", chunk });
462
+ }
463
+ }
464
+ });
465
+ return {
466
+ id: frameId,
467
+ ok: true,
468
+ payload: {
469
+ runId,
470
+ result
471
+ }
472
+ };
473
+ })();
474
+ idempotency.set(dedupeKey, {
475
+ ts: Date.now(),
476
+ pending
477
+ });
478
+ try {
479
+ const response = await pending;
480
+ idempotency.set(dedupeKey, {
481
+ ts: Date.now(),
482
+ response
483
+ });
484
+ cleanupIdempotencyEntries(idempotency, {
485
+ ttlMs: idempotencyTtlMs,
486
+ maxEntries: idempotencyMaxEntries
487
+ });
488
+ return response;
489
+ }
490
+ catch (error) {
491
+ idempotency.delete(dedupeKey);
492
+ throw error;
493
+ }
494
+ }
495
+ return {
496
+ id: frameId,
497
+ ok: false,
498
+ error: toGatewayError({
499
+ code: OPENGOAT_GATEWAY_ERROR_CODES.invalidRequest,
500
+ message: `unknown method: ${method}`
501
+ })
502
+ };
503
+ }
504
+ function buildRuntimeConfig(options) {
505
+ const bindHost = (options.bindHost ?? "127.0.0.1").trim();
506
+ const path = normalizePath(options.path ?? OPENGOAT_GATEWAY_DEFAULTS.path);
507
+ const requireAuth = options.requireAuth ?? true;
508
+ const authToken = options.authToken?.trim() || (requireAuth ? createGatewayToken() : undefined);
509
+ if (requireAuth && !authToken) {
510
+ throw new Error("Gateway auth token is required when authentication is enabled.");
511
+ }
512
+ return {
513
+ bindHost,
514
+ path,
515
+ authToken,
516
+ requireAuth,
517
+ allowedOrigins: normalizeOrigins(options.allowedOrigins),
518
+ handshakeTimeoutMs: Math.max(1000, options.handshakeTimeoutMs ?? OPENGOAT_GATEWAY_DEFAULTS.handshakeTimeoutMs),
519
+ maxPayloadBytes: Math.max(1024, options.maxPayloadBytes ?? OPENGOAT_GATEWAY_DEFAULTS.maxPayloadBytes),
520
+ maxBufferedBytes: Math.max(1024, options.maxBufferedBytes ?? OPENGOAT_GATEWAY_DEFAULTS.maxBufferedBytes),
521
+ tickIntervalMs: Math.max(1000, options.tickIntervalMs ?? OPENGOAT_GATEWAY_DEFAULTS.tickIntervalMs),
522
+ rateLimitPerMinute: Math.max(1, options.rateLimitPerMinute ?? OPENGOAT_GATEWAY_DEFAULTS.rateLimitPerMinute),
523
+ idempotencyTtlMs: Math.max(1000, options.idempotencyTtlMs ?? OPENGOAT_GATEWAY_DEFAULTS.idempotencyTtlMs),
524
+ idempotencyMaxEntries: Math.max(1, options.idempotencyMaxEntries ?? OPENGOAT_GATEWAY_DEFAULTS.idempotencyMaxEntries)
525
+ };
526
+ }
527
+ function authorizeConnect(params) {
528
+ const { connect, requireAuth, authToken, challengeNonce, remoteAddress, allowedOrigins, requestHost, requestOrigin } = params;
529
+ if (connect.maxProtocol < OPENGOAT_GATEWAY_PROTOCOL_VERSION || connect.minProtocol > OPENGOAT_GATEWAY_PROTOCOL_VERSION) {
530
+ return {
531
+ ok: false,
532
+ code: OPENGOAT_GATEWAY_ERROR_CODES.protocolMismatch,
533
+ message: "protocol mismatch"
534
+ };
535
+ }
536
+ if (requireAuth) {
537
+ if (!authToken) {
538
+ return {
539
+ ok: false,
540
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
541
+ message: "gateway auth is not configured"
542
+ };
543
+ }
544
+ const providedToken = connect.auth?.token;
545
+ if (!providedToken) {
546
+ return {
547
+ ok: false,
548
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
549
+ message: "missing auth token"
550
+ };
551
+ }
552
+ if (!safeEqual(providedToken, authToken)) {
553
+ return {
554
+ ok: false,
555
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
556
+ message: "invalid auth token"
557
+ };
558
+ }
559
+ }
560
+ const remoteIsLoopback = isLoopbackAddress(remoteAddress);
561
+ if (!remoteIsLoopback) {
562
+ if (!connect.nonce) {
563
+ return {
564
+ ok: false,
565
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
566
+ message: "nonce is required for non-loopback clients"
567
+ };
568
+ }
569
+ if (connect.nonce !== challengeNonce) {
570
+ return {
571
+ ok: false,
572
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
573
+ message: "nonce mismatch"
574
+ };
575
+ }
576
+ }
577
+ else if (connect.nonce && connect.nonce !== challengeNonce) {
578
+ return {
579
+ ok: false,
580
+ code: OPENGOAT_GATEWAY_ERROR_CODES.unauthorized,
581
+ message: "nonce mismatch"
582
+ };
583
+ }
584
+ if (!isOriginAllowed({
585
+ origin: requestOrigin,
586
+ host: requestHost,
587
+ allowedOrigins
588
+ })) {
589
+ return {
590
+ ok: false,
591
+ code: OPENGOAT_GATEWAY_ERROR_CODES.forbidden,
592
+ message: "origin not allowed"
593
+ };
594
+ }
595
+ return { ok: true };
596
+ }
597
+ function isAuthorizedForMethod(method, scopes) {
598
+ if (scopes.includes(OPENGOAT_GATEWAY_SCOPES.admin)) {
599
+ return true;
600
+ }
601
+ if (isReadMethod(method)) {
602
+ return scopes.includes(OPENGOAT_GATEWAY_SCOPES.read) || scopes.includes(OPENGOAT_GATEWAY_SCOPES.write);
603
+ }
604
+ return scopes.includes(OPENGOAT_GATEWAY_SCOPES.write);
605
+ }
606
+ function resolveScopes(scopes) {
607
+ if (!scopes || scopes.length === 0) {
608
+ return [OPENGOAT_GATEWAY_SCOPES.admin];
609
+ }
610
+ return [...new Set(scopes)];
611
+ }
612
+ function buildIdempotencyKey(params) {
613
+ const clientId = params.client?.id ?? "unknown";
614
+ const instanceId = params.client?.instanceId ?? "unknown";
615
+ return `${clientId}:${instanceId}:${params.method}:${params.idempotencyKey}`;
616
+ }
617
+ function cleanupIdempotencyEntries(entries, options) {
618
+ const now = Date.now();
619
+ for (const [key, entry] of entries) {
620
+ if (now - entry.ts > options.ttlMs) {
621
+ entries.delete(key);
622
+ }
623
+ }
624
+ if (entries.size <= options.maxEntries) {
625
+ return;
626
+ }
627
+ const sorted = [...entries.entries()].sort((left, right) => left[1].ts - right[1].ts);
628
+ const excess = sorted.length - options.maxEntries;
629
+ for (let index = 0; index < excess; index += 1) {
630
+ const key = sorted[index]?.[0];
631
+ if (key) {
632
+ entries.delete(key);
633
+ }
634
+ }
635
+ }
636
+ function sendResponse(client, response) {
637
+ safeSend(client, {
638
+ type: "res",
639
+ id: response.id,
640
+ ok: response.ok,
641
+ payload: response.payload,
642
+ error: response.error
643
+ });
644
+ }
645
+ function withResponseId(id, response) {
646
+ return {
647
+ id,
648
+ ok: response.ok,
649
+ payload: response.payload,
650
+ error: response.error
651
+ };
652
+ }
653
+ function sendError(client, params) {
654
+ const frame = {
655
+ type: "res",
656
+ id: params.id,
657
+ ok: false,
658
+ error: toGatewayError(params)
659
+ };
660
+ sendResponse(client, frame);
661
+ }
662
+ function sendEvent(client, event) {
663
+ safeSend(client, {
664
+ type: "event",
665
+ event: event.event,
666
+ payload: event.payload,
667
+ seq: event.seq
668
+ });
669
+ }
670
+ function safeSend(client, payload) {
671
+ if (client.socket.readyState !== client.socket.OPEN) {
672
+ return;
673
+ }
674
+ if (client.socket.bufferedAmount > client.maxBufferedBytes) {
675
+ closeSocket(client.socket, 1008, "slow consumer");
676
+ return;
677
+ }
678
+ try {
679
+ client.socket.send(JSON.stringify(payload));
680
+ }
681
+ catch {
682
+ closeSocket(client.socket, 1011, "send failed");
683
+ }
684
+ }
685
+ function closeSocket(socket, code, reason) {
686
+ try {
687
+ socket.close(code, reason);
688
+ }
689
+ catch {
690
+ // ignore close failure
691
+ }
692
+ }
693
+ function handleHttpRequest(request, response, options) {
694
+ const requestPath = resolveRequestPath(request);
695
+ if (requestPath === "/health") {
696
+ response.statusCode = 200;
697
+ response.setHeader("content-type", "application/json; charset=utf-8");
698
+ response.end(JSON.stringify({
699
+ status: "ok",
700
+ protocol: options.protocolVersion,
701
+ connectedClients: options.clients.size,
702
+ uptimeMs: Date.now() - options.startedAtMs,
703
+ authRequired: options.authRequired,
704
+ hasAuthToken: options.hasAuthToken
705
+ }));
706
+ return;
707
+ }
708
+ if (requestPath === options.path) {
709
+ response.statusCode = 426;
710
+ response.setHeader("content-type", "application/json; charset=utf-8");
711
+ response.end(JSON.stringify({ error: "Upgrade Required" }));
712
+ return;
713
+ }
714
+ response.statusCode = 404;
715
+ response.setHeader("content-type", "application/json; charset=utf-8");
716
+ response.end(JSON.stringify({ error: "Not Found" }));
717
+ }
718
+ function resolveRequestPath(request) {
719
+ const rawPath = request.url ?? "/";
720
+ const host = request.headers.host ?? "127.0.0.1";
721
+ try {
722
+ const url = new URL(rawPath, `http://${host}`);
723
+ return url.pathname;
724
+ }
725
+ catch {
726
+ return "/";
727
+ }
728
+ }
729
+ function headerValue(value) {
730
+ if (Array.isArray(value)) {
731
+ return value[0];
732
+ }
733
+ return value;
734
+ }
735
+ function toUtf8String(buffer) {
736
+ if (typeof buffer === "string") {
737
+ return buffer;
738
+ }
739
+ if (buffer instanceof ArrayBuffer) {
740
+ return Buffer.from(buffer).toString("utf-8");
741
+ }
742
+ if (Array.isArray(buffer)) {
743
+ return Buffer.concat(buffer.map((chunk) => Buffer.from(chunk))).toString("utf-8");
744
+ }
745
+ if (Buffer.isBuffer(buffer)) {
746
+ return buffer.toString("utf-8");
747
+ }
748
+ return null;
749
+ }
750
+ function checkRateLimit(client, limitPerMinute) {
751
+ const now = Date.now();
752
+ const windowStart = now - 60_000;
753
+ while (client.requestTimes.length > 0) {
754
+ const first = client.requestTimes[0];
755
+ if (first === undefined || first >= windowStart) {
756
+ break;
757
+ }
758
+ client.requestTimes.shift();
759
+ }
760
+ if (client.requestTimes.length >= limitPerMinute) {
761
+ return false;
762
+ }
763
+ client.requestTimes.push(now);
764
+ return true;
765
+ }
766
+ function resolveRateLimitRetryMs(client) {
767
+ const now = Date.now();
768
+ const first = client.requestTimes[0];
769
+ if (!first) {
770
+ return 1000;
771
+ }
772
+ const retryAfter = first + 60_000 - now;
773
+ return retryAfter > 0 ? retryAfter : 1000;
774
+ }
775
+ function toGatewayError(params) {
776
+ return {
777
+ code: params.code,
778
+ message: params.message,
779
+ details: params.details,
780
+ retryable: params.retryable,
781
+ retryAfterMs: params.retryAfterMs
782
+ };
783
+ }
784
+ function toErrorMessage(error) {
785
+ if (error instanceof Error && error.message) {
786
+ return error.message;
787
+ }
788
+ return "request failed";
789
+ }
790
+ function safeEqual(left, right) {
791
+ const leftBytes = Buffer.from(left);
792
+ const rightBytes = Buffer.from(right);
793
+ if (leftBytes.length !== rightBytes.length) {
794
+ return false;
795
+ }
796
+ return timingSafeEqual(leftBytes, rightBytes);
797
+ }
798
+ function isLoopbackAddress(ip) {
799
+ if (!ip) {
800
+ return false;
801
+ }
802
+ const value = ip.trim().toLowerCase();
803
+ return (value === "127.0.0.1" ||
804
+ value.startsWith("127.") ||
805
+ value === "::1" ||
806
+ value.startsWith("::ffff:127.") ||
807
+ value === "localhost");
808
+ }
809
+ function isOriginAllowed(params) {
810
+ const origin = params.origin?.trim();
811
+ if (!origin) {
812
+ return true;
813
+ }
814
+ let parsedOrigin;
815
+ try {
816
+ parsedOrigin = new URL(origin);
817
+ }
818
+ catch {
819
+ return false;
820
+ }
821
+ const normalizedOrigin = parsedOrigin.origin.toLowerCase();
822
+ if (params.allowedOrigins.includes(normalizedOrigin)) {
823
+ return true;
824
+ }
825
+ const host = (params.host ?? "").trim().toLowerCase();
826
+ if (host && parsedOrigin.host.toLowerCase() === host) {
827
+ return true;
828
+ }
829
+ return isLoopbackHost(parsedOrigin.hostname) && isLoopbackHost(resolveHostName(host));
830
+ }
831
+ function isLoopbackHost(hostname) {
832
+ const value = hostname.trim().toLowerCase();
833
+ return value === "localhost" || value === "::1" || value === "127.0.0.1" || value.startsWith("127.");
834
+ }
835
+ function resolveHostName(hostHeader) {
836
+ const trimmed = hostHeader.trim().toLowerCase();
837
+ if (!trimmed) {
838
+ return "";
839
+ }
840
+ if (trimmed.startsWith("[")) {
841
+ const closeBracket = trimmed.indexOf("]");
842
+ if (closeBracket > 1) {
843
+ return trimmed.slice(1, closeBracket);
844
+ }
845
+ }
846
+ const [host] = trimmed.split(":");
847
+ return host ?? "";
848
+ }
849
+ function normalizePath(path) {
850
+ const trimmed = path.trim();
851
+ if (!trimmed || trimmed === "/") {
852
+ return OPENGOAT_GATEWAY_DEFAULTS.path;
853
+ }
854
+ if (!trimmed.startsWith("/")) {
855
+ return `/${trimmed}`;
856
+ }
857
+ return trimmed;
858
+ }
859
+ function normalizeOrigins(origins) {
860
+ if (!origins) {
861
+ return [];
862
+ }
863
+ return [...new Set(origins.map((origin) => origin.trim().toLowerCase()).filter(Boolean))];
864
+ }
865
+ function createGatewayToken() {
866
+ return randomBytes(32).toString("base64url");
867
+ }
868
+ function formatHostForUrl(host) {
869
+ if (host.includes(":")) {
870
+ return `[${host}]`;
871
+ }
872
+ return host;
873
+ }
874
+ async function listen(server, port, host) {
875
+ await new Promise((resolve, reject) => {
876
+ const onError = (error) => {
877
+ server.off("listening", onListening);
878
+ reject(error);
879
+ };
880
+ const onListening = () => {
881
+ server.off("error", onError);
882
+ resolve();
883
+ };
884
+ server.once("error", onError);
885
+ server.once("listening", onListening);
886
+ server.listen(port, host);
887
+ });
888
+ }
889
+ function resolveServerPort(server) {
890
+ const address = server.address();
891
+ if (!address || typeof address === "string") {
892
+ throw new Error("Unable to resolve gateway port.");
893
+ }
894
+ return address.port;
895
+ }
896
+ async function closeHttpServer(server) {
897
+ await new Promise((resolve) => {
898
+ server.close(() => resolve());
899
+ });
900
+ }
901
+ async function closeWebSocketServer(server) {
902
+ await new Promise((resolve) => {
903
+ server.close(() => resolve());
904
+ });
905
+ }
906
+ //# sourceMappingURL=opengoat-gateway-server.js.map