@evomap/evolver 1.89.20 → 2.0.0-beta.0

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 (231) hide show
  1. package/README.md +27 -564
  2. package/bin/evolver-llm-proxy.js +3 -0
  3. package/bin/evolver-mcp.js +2 -0
  4. package/bin/evolver-proxy.js +3 -0
  5. package/bin/evolver.js +4 -0
  6. package/index.js +1 -3586
  7. package/package.json +32 -62
  8. package/CONTRIBUTING.md +0 -19
  9. package/LICENSE +0 -641
  10. package/README.ja-JP.md +0 -521
  11. package/README.ko-KR.md +0 -520
  12. package/README.zh-CN.md +0 -531
  13. package/SKILL.md +0 -365
  14. package/assets/cover.png +0 -0
  15. package/assets/gep/genes.seed.json +0 -496
  16. package/conformance/savings-core/constants.json +0 -30
  17. package/conformance/savings-core/golden-vectors.json +0 -333
  18. package/scripts/a2a_export.js +0 -63
  19. package/scripts/a2a_ingest.js +0 -79
  20. package/scripts/a2a_promote.js +0 -118
  21. package/scripts/analyze_by_skill.js +0 -121
  22. package/scripts/build_binaries.js +0 -479
  23. package/scripts/check-changelog.js +0 -166
  24. package/scripts/extract_log.js +0 -85
  25. package/scripts/generate_history.js +0 -75
  26. package/scripts/gep_append_event.js +0 -96
  27. package/scripts/gep_personality_report.js +0 -234
  28. package/scripts/human_report.js +0 -147
  29. package/scripts/recall-verify-report.js +0 -234
  30. package/scripts/recover_loop.js +0 -61
  31. package/scripts/refresh_stars_badge.js +0 -168
  32. package/scripts/seed-merchants.js +0 -91
  33. package/scripts/skill2recipes.js +0 -118
  34. package/scripts/suggest_version.js +0 -89
  35. package/scripts/validate-modules.js +0 -38
  36. package/scripts/validate-suite.js +0 -78
  37. package/skills/_meta/SKILL.md +0 -41
  38. package/skills/index.json +0 -14
  39. package/src/adapters/claudeCode.js +0 -194
  40. package/src/adapters/codex.js +0 -216
  41. package/src/adapters/cursor.js +0 -91
  42. package/src/adapters/hookAdapter.js +0 -469
  43. package/src/adapters/kiro.js +0 -195
  44. package/src/adapters/opencode.js +0 -326
  45. package/src/adapters/scripts/_lockPaths.js +0 -74
  46. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  47. package/src/adapters/scripts/_runtimePaths.js +0 -440
  48. package/src/adapters/scripts/evolver-session-end.js +0 -321
  49. package/src/adapters/scripts/evolver-session-start.js +0 -587
  50. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  51. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  52. package/src/atp/atpExecute.js +0 -283
  53. package/src/atp/atpTaskPickup.js +0 -233
  54. package/src/atp/autoBuyer.js +0 -382
  55. package/src/atp/autoDeliver.js +0 -215
  56. package/src/atp/cli.js +0 -354
  57. package/src/atp/cliAutobuyPrompt.js +0 -154
  58. package/src/atp/consumerAgent.js +0 -157
  59. package/src/atp/defaultHandler.js +0 -69
  60. package/src/atp/heartbeatSignalsHandler.js +0 -254
  61. package/src/atp/hubClient.js +0 -317
  62. package/src/atp/index.js +0 -38
  63. package/src/atp/merchantAgent.js +0 -118
  64. package/src/atp/protocol.js +0 -41
  65. package/src/atp/questionComposer.js +0 -133
  66. package/src/atp/serviceHelper.js +0 -92
  67. package/src/canary.js +0 -13
  68. package/src/config.js +0 -351
  69. package/src/evolve/guards.js +0 -1
  70. package/src/evolve/pipeline/collect.js +0 -1
  71. package/src/evolve/pipeline/dispatch.js +0 -1
  72. package/src/evolve/pipeline/enrich.js +0 -1
  73. package/src/evolve/pipeline/hub.js +0 -1
  74. package/src/evolve/pipeline/select.js +0 -1
  75. package/src/evolve/pipeline/signals.js +0 -1
  76. package/src/evolve/utils.js +0 -1
  77. package/src/evolve.js +0 -1
  78. package/src/experiment/agentRunner.js +0 -229
  79. package/src/experiment/cli.js +0 -159
  80. package/src/experiment/comparison.js +0 -233
  81. package/src/experiment/metrics.js +0 -75
  82. package/src/forceUpdate.js +0 -991
  83. package/src/gep/a2a.js +0 -173
  84. package/src/gep/a2aProtocol.js +0 -1
  85. package/src/gep/analyzer.js +0 -35
  86. package/src/gep/antiAbuseTelemetry.js +0 -1
  87. package/src/gep/assetCallLog.js +0 -197
  88. package/src/gep/assetStore.js +0 -723
  89. package/src/gep/assets.js +0 -36
  90. package/src/gep/autoDistillConv.js +0 -1
  91. package/src/gep/autoDistillLlm.js +0 -1
  92. package/src/gep/bridge.js +0 -138
  93. package/src/gep/candidateEval.js +0 -1
  94. package/src/gep/candidates.js +0 -1
  95. package/src/gep/claimNudge.js +0 -121
  96. package/src/gep/cliContracts.js +0 -1190
  97. package/src/gep/contentHash.js +0 -1
  98. package/src/gep/conversationDistiller.js +0 -1
  99. package/src/gep/conversationSniffer.js +0 -1
  100. package/src/gep/crypto.js +0 -1
  101. package/src/gep/curriculum.js +0 -1
  102. package/src/gep/deviceId.js +0 -1
  103. package/src/gep/directoryClient.js +0 -115
  104. package/src/gep/envFingerprint.js +0 -1
  105. package/src/gep/epigenetics.js +0 -1
  106. package/src/gep/execBridge.js +0 -1
  107. package/src/gep/executionTrace.js +0 -291
  108. package/src/gep/explore.js +0 -1
  109. package/src/gep/featureFlags.js +0 -121
  110. package/src/gep/gitOps.js +0 -265
  111. package/src/gep/hash.js +0 -1
  112. package/src/gep/hostErrorClassifier.js +0 -34
  113. package/src/gep/hubFetch.js +0 -1
  114. package/src/gep/hubReview.js +0 -1
  115. package/src/gep/hubSearch.js +0 -1
  116. package/src/gep/hubVerify.js +0 -1
  117. package/src/gep/idleScheduler.js +0 -400
  118. package/src/gep/issueReporter.js +0 -416
  119. package/src/gep/learningSignals.js +0 -1
  120. package/src/gep/llmReview.js +0 -92
  121. package/src/gep/localStateAwareness.js +0 -243
  122. package/src/gep/mailboxTransport.js +0 -119
  123. package/src/gep/memoryGraph.js +0 -1
  124. package/src/gep/memoryGraphAdapter.js +0 -1
  125. package/src/gep/mutation.js +0 -1
  126. package/src/gep/narrativeMemory.js +0 -1
  127. package/src/gep/oauthLogin.js +0 -181
  128. package/src/gep/openPRRegistry.js +0 -1
  129. package/src/gep/paths.js +0 -522
  130. package/src/gep/personality.js +0 -1
  131. package/src/gep/policyCheck.js +0 -1
  132. package/src/gep/portable.js +0 -103
  133. package/src/gep/privacyClient.js +0 -235
  134. package/src/gep/prompt.js +0 -1
  135. package/src/gep/questionGenerator.js +0 -518
  136. package/src/gep/recallInject.js +0 -1
  137. package/src/gep/recallVerifier.js +0 -1
  138. package/src/gep/reflection.js +0 -1
  139. package/src/gep/sanitize.js +0 -264
  140. package/src/gep/savingsCore.js +0 -1
  141. package/src/gep/schemas/capsule.js +0 -170
  142. package/src/gep/schemas/gene.js +0 -154
  143. package/src/gep/schemas/index.js +0 -8
  144. package/src/gep/schemas/protocol.js +0 -51
  145. package/src/gep/schemas/task.js +0 -74
  146. package/src/gep/selector.js +0 -1
  147. package/src/gep/selfPR.js +0 -469
  148. package/src/gep/signals.js +0 -776
  149. package/src/gep/skill2gep.js +0 -1056
  150. package/src/gep/skill2gepAudit.js +0 -303
  151. package/src/gep/skill2recipes.js +0 -511
  152. package/src/gep/skillDistiller.js +0 -1
  153. package/src/gep/skillPublisher.js +0 -358
  154. package/src/gep/solidify.js +0 -1
  155. package/src/gep/strategy.js +0 -1
  156. package/src/gep/taskReceiver.js +0 -575
  157. package/src/gep/tokenSavings.js +0 -1
  158. package/src/gep/trajectoryExport.js +0 -1
  159. package/src/gep/validationReport.js +0 -55
  160. package/src/gep/validator/index.js +0 -411
  161. package/src/gep/validator/reporter.js +0 -210
  162. package/src/gep/validator/sandboxExecutor.js +0 -480
  163. package/src/gep/validator/stakeBootstrap.js +0 -357
  164. package/src/gep/workspaceKeychain.js +0 -1
  165. package/src/ops/cleanup.js +0 -80
  166. package/src/ops/commentary.js +0 -60
  167. package/src/ops/health_check.js +0 -104
  168. package/src/ops/index.js +0 -11
  169. package/src/ops/innovation.js +0 -67
  170. package/src/ops/lifecycle.js +0 -798
  171. package/src/ops/self_repair.js +0 -76
  172. package/src/ops/skills_monitor.js +0 -147
  173. package/src/ops/trigger.js +0 -33
  174. package/src/proxy/clientSettings.js +0 -405
  175. package/src/proxy/envelope.js +0 -59
  176. package/src/proxy/extensions/dmHandler.js +0 -45
  177. package/src/proxy/extensions/sessionHandler.js +0 -141
  178. package/src/proxy/extensions/skillUpdater.js +0 -64
  179. package/src/proxy/extensions/traceControl.js +0 -1
  180. package/src/proxy/index.js +0 -1395
  181. package/src/proxy/inject.js +0 -1
  182. package/src/proxy/lifecycle/manager.js +0 -1568
  183. package/src/proxy/mailbox/state.js +0 -207
  184. package/src/proxy/mailbox/store.js +0 -590
  185. package/src/proxy/router/cache_passthrough.js +0 -26
  186. package/src/proxy/router/features.js +0 -84
  187. package/src/proxy/router/gemini_route.js +0 -154
  188. package/src/proxy/router/messages_route.js +0 -535
  189. package/src/proxy/router/model_router.js +0 -113
  190. package/src/proxy/router/models_route.js +0 -52
  191. package/src/proxy/router/ollama_route.js +0 -103
  192. package/src/proxy/router/responses_route.js +0 -170
  193. package/src/proxy/router/vertex_route.js +0 -110
  194. package/src/proxy/server/http.js +0 -363
  195. package/src/proxy/server/routes.js +0 -558
  196. package/src/proxy/server/settings.js +0 -115
  197. package/src/proxy/sync/engine.js +0 -179
  198. package/src/proxy/sync/inbound.js +0 -211
  199. package/src/proxy/sync/outbound.js +0 -320
  200. package/src/proxy/task/monitor.js +0 -131
  201. package/src/proxy/trace/extractor.js +0 -1
  202. package/src/proxy/trace/usage.js +0 -1
  203. package/src/webui/client/clientJs/assets.js +0 -111
  204. package/src/webui/client/clientJs/bootstrap.js +0 -92
  205. package/src/webui/client/clientJs/common.js +0 -77
  206. package/src/webui/client/clientJs/i18n.js +0 -366
  207. package/src/webui/client/clientJs/index.js +0 -35
  208. package/src/webui/client/clientJs/interactions.js +0 -351
  209. package/src/webui/client/clientJs/overview.js +0 -152
  210. package/src/webui/client/clientJs/personality.js +0 -285
  211. package/src/webui/client/clientJs/pipelines.js +0 -330
  212. package/src/webui/client/indexHtml.js +0 -221
  213. package/src/webui/client/static.js +0 -23
  214. package/src/webui/client/stylesCss.js +0 -639
  215. package/src/webui/client/vendor/README.md +0 -15
  216. package/src/webui/client/vendor/echarts.min.js +0 -45
  217. package/src/webui/index.js +0 -14
  218. package/src/webui/observer/assets.js +0 -146
  219. package/src/webui/observer/index.js +0 -37
  220. package/src/webui/observer/interactions.js +0 -127
  221. package/src/webui/observer/jsonl.js +0 -75
  222. package/src/webui/observer/paths.js +0 -46
  223. package/src/webui/observer/personality.js +0 -43
  224. package/src/webui/observer/pipelineEvents.js +0 -58
  225. package/src/webui/observer/redact.js +0 -63
  226. package/src/webui/observer/runs.js +0 -356
  227. package/src/webui/observer/safety.js +0 -57
  228. package/src/webui/observer/skills.js +0 -70
  229. package/src/webui/observer/status.js +0 -71
  230. package/src/webui/server/http.js +0 -138
  231. package/src/webui/server/routes.js +0 -41
@@ -1,363 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const http = require('http');
5
- const { writeSettings, readSettings, clearSettings, clearIfStale } = require('./settings');
6
- const {
7
- isValidReusableProxyToken,
8
- readReusableClientProxyToken,
9
- syncClaudeProxySettings,
10
- } = require('../clientSettings');
11
-
12
- const MAX_PORT_ATTEMPTS = 100;
13
- const DEFAULT_PORT = 19820;
14
-
15
- // GHSA-7xp7-m392-h92c: cap request body at 1 MiB. The proxy's HTTP surface is
16
- // bound to 127.0.0.1 but still reachable by any local process (other users on
17
- // a shared dev host, container neighbors sharing the host netns, malicious
18
- // postinstall scripts). Without a cap, /asset/submit and /mailbox/send write
19
- // the full body verbatim into messages.jsonl, so an attacker can fill the
20
- // disk and make the daemon OOM on every restart (readFileSync over a multi-
21
- // GB JSONL). Tune via EVOMAP_PROXY_MAX_BODY_BYTES if a legitimate workload
22
- // truly needs bigger bodies.
23
- const DEFAULT_MAX_BODY_BYTES = 1 * 1024 * 1024;
24
- function resolveMaxBodyBytes() {
25
- const raw = Number(process.env.EVOMAP_PROXY_MAX_BODY_BYTES);
26
- if (Number.isFinite(raw) && raw > 0) return Math.floor(raw);
27
- return DEFAULT_MAX_BODY_BYTES;
28
- }
29
-
30
- function parseBody(req, opts) {
31
- const maxBytes = (opts && Number.isFinite(opts.maxBytes) && opts.maxBytes > 0)
32
- ? opts.maxBytes
33
- : resolveMaxBodyBytes();
34
- return new Promise((resolve, reject) => {
35
- const declared = Number(req.headers['content-length']);
36
- if (Number.isFinite(declared) && declared > maxBytes) {
37
- const err = new Error('Request body too large');
38
- err.statusCode = 413;
39
- return reject(err);
40
- }
41
- const chunks = [];
42
- let received = 0;
43
- let settled = false;
44
- const fail = (err) => {
45
- if (settled) return;
46
- settled = true;
47
- try { req.destroy(); } catch { /* ignore */ }
48
- reject(err);
49
- };
50
- req.on('data', (c) => {
51
- if (settled) return;
52
- received += c.length;
53
- if (received > maxBytes) {
54
- const err = new Error('Request body too large');
55
- err.statusCode = 413;
56
- return fail(err);
57
- }
58
- chunks.push(c);
59
- });
60
- req.on('end', () => {
61
- if (settled) return;
62
- settled = true;
63
- const raw = Buffer.concat(chunks).toString();
64
- if (!raw) return resolve({});
65
- try { resolve(JSON.parse(raw)); }
66
- catch (e) { reject(new Error('Invalid JSON body')); }
67
- });
68
- req.on('error', fail);
69
- });
70
- }
71
-
72
- function sendJson(res, status, body, extraHeaders = {}) {
73
- const payload = JSON.stringify(body);
74
- const headers = {
75
- 'Content-Type': 'application/json',
76
- 'Content-Length': Buffer.byteLength(payload),
77
- };
78
- for (const [name, value] of Object.entries(extraHeaders || {})) {
79
- if (value === undefined || value === null) continue;
80
- const lower = String(name).toLowerCase();
81
- if (lower === 'content-length' || lower === 'transfer-encoding' || lower === 'connection') continue;
82
- const headerValue = Array.isArray(value) ? value.join(', ') : String(value);
83
- if (/[\r\n]/.test(headerValue)) continue;
84
- headers[name] = headerValue;
85
- }
86
- res.writeHead(status, headers);
87
- res.end(payload);
88
- }
89
-
90
- function tryListen(server, port) {
91
- return new Promise((resolve, reject) => {
92
- server.once('error', (err) => {
93
- if (err.code === 'EADDRINUSE') return resolve(false);
94
- reject(err);
95
- });
96
- server.listen(port, '127.0.0.1', () => resolve(true));
97
- });
98
- }
99
-
100
- class ProxyHttpServer {
101
- constructor(routes, { port, logger, clientSettings } = {}) {
102
- this.routes = routes;
103
- this.basePort = port || Number(process.env.EVOMAP_PROXY_PORT) || DEFAULT_PORT;
104
- this.actualPort = null;
105
- this.logger = logger || console;
106
- this.server = null;
107
- this.token = null;
108
- this.clientSettings = clientSettings || null;
109
- }
110
-
111
- async start() {
112
- // Capture the prior token before clearIfStale wipes it. Daemon restarts
113
- // routinely fall into the stale branch (the previous PID is gone), and
114
- // rotating `proxy.token` on every restart invalidates ANTHROPIC_AUTH_TOKEN
115
- // already exported into long-lived shells (the .bashrc auto-source only
116
- // runs once per terminal).
117
- const priorProxy = readSettings().proxy || {};
118
- const previous = isValidReusableProxyToken(priorProxy.token) ? priorProxy.token.trim() : null;
119
- // settings.json is operator-edited; previous_tokens may contain non-strings
120
- // (numbers, booleans, objects) that would later crash Buffer.from(cand, 'utf8')
121
- // in _handleRequest as ERR_INVALID_ARG_TYPE — an unhandled rejection that
122
- // takes the daemon down under default --unhandled-rejections=throw.
123
- const priorPreviousTokens = Array.isArray(priorProxy.previous_tokens)
124
- ? priorProxy.previous_tokens
125
- .map((t) => (typeof t === 'string' ? t.trim() : ''))
126
- .filter((t) => isValidReusableProxyToken(t))
127
- : [];
128
- clearIfStale();
129
- const clientSettingsOpts = this.clientSettings || {};
130
- const clientToken = this.clientSettings
131
- ? readReusableClientProxyToken({ ...clientSettingsOpts, port: this.basePort })
132
- : null;
133
- this.token = previous || clientToken || crypto.randomBytes(32).toString('hex');
134
- this._priorPreviousTokens = priorPreviousTokens;
135
- this.server = http.createServer((req, res) => this._handleRequest(req, res));
136
-
137
- let port = this.basePort;
138
- for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {
139
- const ok = await tryListen(this.server, port);
140
- if (ok) {
141
- this.actualPort = port;
142
- const url = `http://127.0.0.1:${port}`;
143
- const proxyBlock = {
144
- url,
145
- pid: process.pid,
146
- started_at: new Date().toISOString(),
147
- token: this.token,
148
- };
149
- if (this._priorPreviousTokens && this._priorPreviousTokens.length) {
150
- proxyBlock.previous_tokens = this._priorPreviousTokens;
151
- }
152
- writeSettings({ proxy: proxyBlock });
153
- if (this.clientSettings) {
154
- try {
155
- const syncResult = syncClaudeProxySettings({
156
- ...clientSettingsOpts,
157
- url,
158
- port: this.basePort,
159
- token: this.token,
160
- runtimeEnv: process.env,
161
- });
162
- if (syncResult.synced && syncResult.changed) {
163
- this.logger.log(`[proxy] Synced Claude client settings at ${syncResult.file}`);
164
- } else if (syncResult.reason === 'invalid_settings_json') {
165
- this.logger.warn?.(`[proxy] Skipped Claude client settings sync because ${syncResult.file} is not valid JSON`);
166
- }
167
- } catch (err) {
168
- this.logger.warn?.('[proxy] Claude client settings sync failed: ' + (err && err.message ? err.message : err));
169
- }
170
- }
171
- this.logger.log(`[proxy] HTTP server listening on ${url}`);
172
- return { port, url, token: this.token };
173
- }
174
- port++;
175
- }
176
- throw new Error(`Could not find free port after ${MAX_PORT_ATTEMPTS} attempts starting from ${this.basePort}`);
177
- }
178
-
179
- async stop() {
180
- if (this.server) {
181
- await new Promise((resolve) => this.server.close(resolve));
182
- this.server = null;
183
- }
184
- clearSettings();
185
- }
186
-
187
- async _handleRequest(req, res) {
188
- const authHeader = req.headers['authorization'] || '';
189
- const provided = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';
190
- const provBuf = Buffer.from(provided, 'utf8');
191
- // Primary token plus any grace tokens from settings.json::proxy.previous_tokens.
192
- // The grace list is the recovery path for the rare case where settings.json was
193
- // wiped externally (logout, manual rm) while long-lived CC sessions still hold
194
- // the pre-wipe token in their fork-time env. Operator writes the lost token into
195
- // previous_tokens; once those sessions close, the operator can clear the array.
196
- // Reading directly from settings.json (instead of an env shim) keeps the
197
- // single source of truth on disk — no python bridge in the daemon hook.
198
- // Defense in depth: even though start() filters non-strings before persisting,
199
- // settings.json can be hand-edited between requests, so re-validate every read.
200
- // A non-string slipping into Buffer.from below would throw ERR_INVALID_ARG_TYPE
201
- // and unhandled-reject through the auth path.
202
- const previous = readSettings().proxy?.previous_tokens;
203
- const extras = Array.isArray(previous)
204
- ? previous
205
- .map((t) => (typeof t === 'string' ? t.trim() : ''))
206
- .filter((t) => isValidReusableProxyToken(t))
207
- : [];
208
- const candidates = [this.token, ...extras]
209
- .filter((t) => isValidReusableProxyToken(t))
210
- .map((t) => t.trim());
211
- let valid = false;
212
- for (const cand of candidates) {
213
- const expBuf = Buffer.from(cand, 'utf8');
214
- if (provBuf.length === expBuf.length && crypto.timingSafeEqual(provBuf, expBuf)) {
215
- valid = true;
216
- break;
217
- }
218
- }
219
- if (!valid) {
220
- return sendJson(res, 401, { error: 'Unauthorized' });
221
- }
222
-
223
- const url = new URL(req.url, `http://127.0.0.1:${this.actualPort}`);
224
- const routeKey = `${req.method} ${url.pathname}`;
225
-
226
- const paramMatch = this._matchRoute(req.method, url.pathname);
227
-
228
- if (!paramMatch) {
229
- return sendJson(res, 404, { error: 'Not found', path: url.pathname });
230
- }
231
-
232
- const { handler, params } = paramMatch;
233
-
234
- try {
235
- const body = (req.method === 'POST' || req.method === 'PUT') ? await parseBody(req) : {};
236
- const query = Object.fromEntries(url.searchParams);
237
- const headers = req.headers;
238
- const result = await handler({ body, query, params, headers });
239
- if (result && result.stream) {
240
- await this._streamResponse(res, result);
241
- } else {
242
- sendJson(res, result.status || 200, result.body || result, result.headers);
243
- }
244
- } catch (err) {
245
- this.logger.error(`[proxy] ${routeKey} error:`, err.message);
246
- if (res.headersSent) {
247
- try { res.end(); } catch { /* ignore */ }
248
- } else {
249
- sendJson(res, err.statusCode || 500, {
250
- error: err.message || 'Internal error',
251
- });
252
- }
253
- }
254
- }
255
-
256
- // SSE / pass-through streaming path used by /v1/messages (slice 5).
257
- // `result.stream` may be any async iterable yielding Buffer or string
258
- // chunks (Web ReadableStream from fetch, or a Node Readable). Headers
259
- // default to text/event-stream so Anthropic-style SSE bytes piped
260
- // through reach the client unmodified. The caller owns producing
261
- // correctly-framed SSE; this method only relays bytes.
262
- async _streamResponse(res, result) {
263
- const headers = Object.assign({
264
- 'Content-Type': 'text/event-stream',
265
- 'Cache-Control': 'no-cache',
266
- 'Connection': 'keep-alive',
267
- }, result.headers || {});
268
- res.writeHead(result.status || 200, headers);
269
-
270
- // Browsers, network blips, and Ctrl-C all close the SSE socket mid-stream.
271
- // If we await `drain` without watching for `close`, the drain event never
272
- // fires on a destroyed socket and this coroutine hangs forever — which
273
- // also pins the upstream fetch body open (real Anthropic socket leak, not
274
- // just coroutine leak). For Web ReadableStream upstreams we read via an
275
- // explicit reader so cancellation can go through the same lock; for sync
276
- // generators / Node Readables we fall back to for-await.
277
- const stream = result.stream;
278
- const reader = stream && typeof stream.getReader === 'function' ? stream.getReader() : null;
279
-
280
- let clientGone = false;
281
- const onClose = () => {
282
- clientGone = true;
283
- if (reader) {
284
- reader.cancel().catch(() => { /* upstream already settled */ });
285
- } else if (stream && typeof stream.destroy === 'function') {
286
- try { stream.destroy(); } catch { /* ignore */ }
287
- }
288
- };
289
- res.once('close', onClose);
290
-
291
- const awaitBackpressure = () => new Promise((resolve) => {
292
- let settled = false;
293
- const onDrain = () => {
294
- if (settled) return;
295
- settled = true;
296
- res.off('close', onCloseInner);
297
- resolve();
298
- };
299
- const onCloseInner = () => {
300
- if (settled) return;
301
- settled = true;
302
- res.off('drain', onDrain);
303
- resolve();
304
- };
305
- res.once('drain', onDrain);
306
- res.once('close', onCloseInner);
307
- });
308
-
309
- try {
310
- if (reader) {
311
- for (;;) {
312
- const { value, done } = await reader.read();
313
- if (done || clientGone) break;
314
- if (!res.write(value)) {
315
- await awaitBackpressure();
316
- if (clientGone) break;
317
- }
318
- }
319
- } else {
320
- for await (const chunk of stream) {
321
- if (clientGone) break;
322
- if (!res.write(chunk)) {
323
- await awaitBackpressure();
324
- if (clientGone) break;
325
- }
326
- }
327
- }
328
- } finally {
329
- res.off('close', onClose);
330
- try { res.end(); } catch { /* socket may already be destroyed */ }
331
- }
332
- }
333
-
334
- _matchRoute(method, pathname) {
335
- for (const [pattern, handler] of Object.entries(this.routes)) {
336
- const [routeMethod, routePath] = pattern.split(' ');
337
- if (routeMethod !== method) continue;
338
-
339
- const params = matchPath(routePath, pathname);
340
- if (params !== null) return { handler, params };
341
- }
342
- return null;
343
- }
344
- }
345
-
346
- function matchPath(pattern, pathname) {
347
- const patternParts = pattern.split('/');
348
- const pathParts = pathname.split('/');
349
-
350
- if (patternParts.length !== pathParts.length) return null;
351
-
352
- const params = {};
353
- for (let i = 0; i < patternParts.length; i++) {
354
- if (patternParts[i].startsWith(':')) {
355
- params[patternParts[i].slice(1)] = pathParts[i];
356
- } else if (patternParts[i] !== pathParts[i]) {
357
- return null;
358
- }
359
- }
360
- return params;
361
- }
362
-
363
- module.exports = { ProxyHttpServer, parseBody, sendJson, DEFAULT_PORT, DEFAULT_MAX_BODY_BYTES, resolveMaxBodyBytes };