@citeark/agent 0.3.20

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 (347) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +128 -0
  3. package/data/dataset-source-registry.v1.json +300 -0
  4. package/dist/arkgraph/boot.js +6 -0
  5. package/dist/arkgraph/index.html +1 -0
  6. package/dist/arkgraph/viewer.css +1 -0
  7. package/dist/arkgraph/viewer.en.css +1 -0
  8. package/dist/arkgraph/viewer.en.js +49 -0
  9. package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
  10. package/dist/arkgraph/viewer.js +49 -0
  11. package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
  12. package/docker/claude-code/Dockerfile +97 -0
  13. package/docker/claude-code/codex-pro-relay.mjs +466 -0
  14. package/docker/claude-code/runtime-contract-check.mjs +79 -0
  15. package/docs/arkgraph-reading.md +79 -0
  16. package/docs/configuration.md +100 -0
  17. package/docs/integration.md +92 -0
  18. package/docs/maturity-plan.md +27 -0
  19. package/docs/npm-release.md +44 -0
  20. package/docs/paper-reading.md +40 -0
  21. package/docs/research-plan-granularity.md +27 -0
  22. package/docs/terminal.md +49 -0
  23. package/examples/toy-evaluation/compile-task.json +27 -0
  24. package/examples/toy-evaluation/paper.md +5 -0
  25. package/examples/toy-evaluation/repository/README.md +9 -0
  26. package/examples/toy-evaluation/repository/checkpoint.json +4 -0
  27. package/examples/toy-evaluation/repository/evaluate.py +17 -0
  28. package/examples/toy-evaluation/task.json +81 -0
  29. package/package.json +59 -0
  30. package/prompts/compile-research.md +58 -0
  31. package/prompts/execute-contract.md +72 -0
  32. package/prompts/execute-workspace-simple.md +51 -0
  33. package/prompts/execute-workspace.md +34 -0
  34. package/prompts/prepare-reproduction.md +82 -0
  35. package/prompts/repair-research.md +45 -0
  36. package/protocol/CAP.md +129 -0
  37. package/protocol/LICENSE +12 -0
  38. package/protocol/MAPPINGS.md +72 -0
  39. package/protocol/README.md +38 -0
  40. package/protocol/conformance-v2.0-alpha.1.json +36 -0
  41. package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
  42. package/protocol/examples/arkgraph/fixtures.mjs +49 -0
  43. package/protocol/examples/arkgraph/paper-free.json +291 -0
  44. package/protocol/examples/arkgraph/partial-failure.json +344 -0
  45. package/protocol/examples/arkgraph/training-evaluation.json +443 -0
  46. package/protocol/profiles/agent-trace.md +16 -0
  47. package/protocol/profiles/computational-run.md +16 -0
  48. package/protocol/profiles/core.md +15 -0
  49. package/protocol/profiles/public-bundle.md +18 -0
  50. package/protocol/profiles/reproduction.md +29 -0
  51. package/protocol/profiles/research-compilation.md +44 -0
  52. package/protocol/profiles/research-plan.md +39 -0
  53. package/protocol/profiles/restricted-evidence.md +15 -0
  54. package/runtime/bootstrap-autodl-runtime.sh +314 -0
  55. package/runtime/create-runtime-venv.sh +41 -0
  56. package/runtime/install-local-cpu-runtime.sh +23 -0
  57. package/runtime/install-scientific-runtime.sh +153 -0
  58. package/runtime/mineru/parse.py +62 -0
  59. package/runtime/mineru/requirements.txt +4 -0
  60. package/runtime/requirements-baseline.txt +38 -0
  61. package/schemas/cap/v2/activity.schema.json +47 -0
  62. package/schemas/cap/v2/agent.schema.json +32 -0
  63. package/schemas/cap/v2/assertion.schema.json +110 -0
  64. package/schemas/cap/v2/descriptor.schema.json +243 -0
  65. package/schemas/cap/v2/entity.schema.json +64 -0
  66. package/schemas/cap/v2/manifest.schema.json +67 -0
  67. package/schemas/cap/v2/relation.schema.json +82 -0
  68. package/schemas/compute-catalog.schema.json +63 -0
  69. package/schemas/compute-decision.schema.json +27 -0
  70. package/schemas/execution-contract.schema.json +1024 -0
  71. package/schemas/research-card.schema.json +30 -0
  72. package/schemas/research-inventory-draft.schema.json +366 -0
  73. package/schemas/research.schema.json +1044 -0
  74. package/schemas/result.schema.json +173 -0
  75. package/schemas/verification-policy.schema.json +47 -0
  76. package/schemas/verified-conclusion.schema.json +58 -0
  77. package/schemas/workspace-summary.schema.json +24 -0
  78. package/scripts/build-arkgraph-view.mjs +12 -0
  79. package/scripts/check-execution-feasibility.mjs +24 -0
  80. package/scripts/check-syntax.mjs +15 -0
  81. package/scripts/deterministic-asset-preparation.py +438 -0
  82. package/scripts/package-cap.mjs +23 -0
  83. package/scripts/package-local-agent.mjs +23 -0
  84. package/scripts/preview-arkgraph.mjs +25 -0
  85. package/scripts/replay-research-compiler-candidate.mjs +134 -0
  86. package/scripts/review-compiler-sources.mjs +44 -0
  87. package/scripts/run-asset-preparation.sh +17 -0
  88. package/scripts/run-research-plan.mjs +98 -0
  89. package/scripts/validate-asset-preparation.py +290 -0
  90. package/scripts/verify-local-runtime.mjs +57 -0
  91. package/scripts/verify-npm-package.mjs +57 -0
  92. package/src/adapters/paper2agent.mjs +107 -0
  93. package/src/assets/cache.mjs +159 -0
  94. package/src/assets/compute.mjs +98 -0
  95. package/src/assets/executor.mjs +145 -0
  96. package/src/assets/lifecycle.mjs +213 -0
  97. package/src/assets/manifest.mjs +242 -0
  98. package/src/assets/opportunistic-preparation.mjs +81 -0
  99. package/src/assets/plan.mjs +411 -0
  100. package/src/assets/prompts.mjs +29 -0
  101. package/src/assets/public-asset-probe.mjs +525 -0
  102. package/src/assets/qualification.mjs +119 -0
  103. package/src/assets/readiness.mjs +130 -0
  104. package/src/assets/reproduction-admission.mjs +355 -0
  105. package/src/assets/requirements.mjs +152 -0
  106. package/src/assets/source-grounding.mjs +341 -0
  107. package/src/assets/source-policy.mjs +118 -0
  108. package/src/autodl/client.mjs +260 -0
  109. package/src/autodl/ssh.mjs +380 -0
  110. package/src/autodl/tools.mjs +129 -0
  111. package/src/cap/redaction.mjs +38 -0
  112. package/src/cap/v2/archive.mjs +152 -0
  113. package/src/cap/v2/attestation.mjs +204 -0
  114. package/src/cap/v2/canonical-json.mjs +114 -0
  115. package/src/cap/v2/compilation-artifact.mjs +240 -0
  116. package/src/cap/v2/core.mjs +282 -0
  117. package/src/cap/v2/measurement-assessment-records.mjs +23 -0
  118. package/src/cap/v2/pipeline-artifact.mjs +922 -0
  119. package/src/cap/v2/read.mjs +41 -0
  120. package/src/cap/v2/reassessment-artifact.mjs +383 -0
  121. package/src/cap/v2/research-artifact.mjs +231 -0
  122. package/src/cap/v2/research-map-records.mjs +46 -0
  123. package/src/cap/v2/research-object-records.mjs +163 -0
  124. package/src/cap/v2/research-records.mjs +187 -0
  125. package/src/cap/v2/verify.mjs +642 -0
  126. package/src/cli.mjs +1146 -0
  127. package/src/compute/autodl-pro-compiler.mjs +347 -0
  128. package/src/compute/autodl-pro-executor.mjs +459 -0
  129. package/src/compute/autodl-pro-job.mjs +843 -0
  130. package/src/compute/autodl-pro-network.mjs +295 -0
  131. package/src/compute/autodl-pro-remote.mjs +810 -0
  132. package/src/compute/autodl-pro-staging.mjs +117 -0
  133. package/src/compute/campaign.mjs +110 -0
  134. package/src/compute/catalog.mjs +123 -0
  135. package/src/compute/checkpoint-protocol.mjs +154 -0
  136. package/src/compute/codex-account-lock.mjs +111 -0
  137. package/src/compute/codex-account-session.mjs +107 -0
  138. package/src/compute/compiler-profile.mjs +38 -0
  139. package/src/compute/compiler-router.mjs +23 -0
  140. package/src/compute/coordinator-recovery.mjs +210 -0
  141. package/src/compute/executor-router.mjs +29 -0
  142. package/src/compute/gcp-batch-compiler.mjs +685 -0
  143. package/src/compute/gcp-batch-executor.mjs +1215 -0
  144. package/src/compute/gcp-batch-failure.mjs +92 -0
  145. package/src/compute/gcp-batch-job.mjs +527 -0
  146. package/src/compute/gcp-batch-lifecycle.mjs +81 -0
  147. package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
  148. package/src/compute/local-codex-compiler.mjs +52 -0
  149. package/src/compute/measurement-hardware.mjs +128 -0
  150. package/src/compute/remote-attempt.mjs +226 -0
  151. package/src/compute/requirements.mjs +124 -0
  152. package/src/compute/research-phases.mjs +48 -0
  153. package/src/compute/scheduler.mjs +452 -0
  154. package/src/compute/shared-workloads.mjs +26 -0
  155. package/src/compute/stage-archive.mjs +79 -0
  156. package/src/contracts/campaign-contract.mjs +52 -0
  157. package/src/contracts/execution-contract.mjs +819 -0
  158. package/src/contracts/execution-mode.mjs +19 -0
  159. package/src/contracts/execution-timeouts.mjs +45 -0
  160. package/src/contracts/execution-workload.mjs +68 -0
  161. package/src/contracts/preflight-schema.mjs +25 -0
  162. package/src/contracts/public-contract.mjs +63 -0
  163. package/src/contracts/subject-tags.mjs +31 -0
  164. package/src/dashboard/data.mjs +898 -0
  165. package/src/dashboard/server.mjs +79 -0
  166. package/src/dashboard/static/dashboard.css +366 -0
  167. package/src/dashboard/static/dashboard.js +560 -0
  168. package/src/dashboard/static/index.html +85 -0
  169. package/src/deployment/community-policy.mjs +9 -0
  170. package/src/deployment/environment.mjs +112 -0
  171. package/src/deployment/guided.mjs +98 -0
  172. package/src/deployment/handoff.mjs +102 -0
  173. package/src/deployment/local-contract.mjs +31 -0
  174. package/src/deployment/local.mjs +100 -0
  175. package/src/deployment/prepare.mjs +46 -0
  176. package/src/deployment/recipe.mjs +108 -0
  177. package/src/deployment/supplement.mjs +51 -0
  178. package/src/deployment/terminal.mjs +43 -0
  179. package/src/diagnosis/renderer.mjs +75 -0
  180. package/src/diagnosis/target-failure.mjs +46 -0
  181. package/src/evidence/parser-registry.mjs +54 -0
  182. package/src/evidence/parsers/fasttext-classification.mjs +82 -0
  183. package/src/evidence/parsers/json-scalar.mjs +96 -0
  184. package/src/evidence/parsers/simcse-senteval.mjs +104 -0
  185. package/src/evidence/parsers/starspace-classification.mjs +78 -0
  186. package/src/evidence/registry.mjs +147 -0
  187. package/src/execution/runner-audit.mjs +473 -0
  188. package/src/gcp/auth.mjs +106 -0
  189. package/src/gcp/batch-client.mjs +120 -0
  190. package/src/gcp/resource-discovery.mjs +177 -0
  191. package/src/gcp/rest.mjs +82 -0
  192. package/src/gcp/secret-manager.mjs +34 -0
  193. package/src/gcp/signed-url.mjs +133 -0
  194. package/src/gcp/storage.mjs +220 -0
  195. package/src/graph/command.mjs +41 -0
  196. package/src/graph/execution.mjs +97 -0
  197. package/src/graph/model.mjs +37 -0
  198. package/src/graph/presentation.mjs +110 -0
  199. package/src/graph/query.mjs +159 -0
  200. package/src/graph/research-relations.mjs +69 -0
  201. package/src/graph/source-page.mjs +12 -0
  202. package/src/graph/source-preview.mjs +34 -0
  203. package/src/graph/validate.mjs +76 -0
  204. package/src/job.mjs +496 -0
  205. package/src/network/autodl-routing-proxy.mjs +462 -0
  206. package/src/network/egress-proxy.mjs +158 -0
  207. package/src/observability/event-contract.mjs +230 -0
  208. package/src/observability/pipeline-monitor.mjs +166 -0
  209. package/src/pipeline/orchestrator.mjs +1281 -0
  210. package/src/pipeline/recovery-error.mjs +11 -0
  211. package/src/pipeline/replay.mjs +304 -0
  212. package/src/pipeline/shared-execution.mjs +115 -0
  213. package/src/pipeline/stage-checkpoint.mjs +86 -0
  214. package/src/pipeline/stage-recovery.mjs +101 -0
  215. package/src/pipeline/targets.mjs +110 -0
  216. package/src/process.mjs +143 -0
  217. package/src/protocol.mjs +312 -0
  218. package/src/provider/codex-account.mjs +44 -0
  219. package/src/provider/codex-completion.mjs +49 -0
  220. package/src/provider/completion.mjs +292 -0
  221. package/src/provider/model-client.mjs +44 -0
  222. package/src/provider/model-route.mjs +29 -0
  223. package/src/provider/openrouter-readiness.mjs +189 -0
  224. package/src/provider/reader-bridge.mjs +35 -0
  225. package/src/provider/relay.mjs +263 -0
  226. package/src/provider/runtime-auth.mjs +40 -0
  227. package/src/public/cap.d.mts +90 -0
  228. package/src/public/cap.mjs +12 -0
  229. package/src/public/contracts.d.mts +2 -0
  230. package/src/public/host.mjs +171 -0
  231. package/src/public/operations.d.mts +11 -0
  232. package/src/public/presentation.d.mts +4 -0
  233. package/src/records/views.mjs +26 -0
  234. package/src/remote/command.mjs +178 -0
  235. package/src/remote/ssh.mjs +59 -0
  236. package/src/repository-origin.mjs +81 -0
  237. package/src/reproduction/evidence-feedback.mjs +96 -0
  238. package/src/reproduction/incomplete-initialization.mjs +25 -0
  239. package/src/reproduction/lifecycle.mjs +253 -0
  240. package/src/reproduction/plan.mjs +132 -0
  241. package/src/reproduction/prompts.mjs +70 -0
  242. package/src/reproduction/runner.mjs +188 -0
  243. package/src/reproduction/summary.mjs +130 -0
  244. package/src/reproduction/workspace-mode.mjs +7 -0
  245. package/src/research/automatic-admission.mjs +156 -0
  246. package/src/research/compiler-coverage.mjs +85 -0
  247. package/src/research/compiler-failure.mjs +24 -0
  248. package/src/research/compiler-normalization-guards.mjs +112 -0
  249. package/src/research/compiler-repair.mjs +3 -0
  250. package/src/research/compiler.mjs +853 -0
  251. package/src/research/continuation-selection.mjs +26 -0
  252. package/src/research/execution-graph-context.mjs +43 -0
  253. package/src/research/experiment-importance.mjs +15 -0
  254. package/src/research/inventory-handoff.mjs +104 -0
  255. package/src/research/inventory-revisions.mjs +32 -0
  256. package/src/research/mineru-local.mjs +73 -0
  257. package/src/research/paper-command.mjs +19 -0
  258. package/src/research/paper-markdown.mjs +180 -0
  259. package/src/research/paper-source-map.mjs +69 -0
  260. package/src/research/planning-policy.mjs +88 -0
  261. package/src/research/reference-materials.mjs +11 -0
  262. package/src/research/reproduction-scope.mjs +30 -0
  263. package/src/research/research-map.mjs +94 -0
  264. package/src/research/research-objects.mjs +88 -0
  265. package/src/research/source-discovery.mjs +646 -0
  266. package/src/research/source-observations.mjs +75 -0
  267. package/src/research/source-review-cli-mcp.mjs +26 -0
  268. package/src/research/source-review-input.mjs +209 -0
  269. package/src/research/source-review-local-codex.mjs +36 -0
  270. package/src/research/source-review-model.mjs +70 -0
  271. package/src/research/source-review.mjs +173 -0
  272. package/src/research/structure.mjs +3163 -0
  273. package/src/research-card/renderer.mjs +277 -0
  274. package/src/research-card/verified-conclusion.mjs +143 -0
  275. package/src/results/output-registry.mjs +183 -0
  276. package/src/runtime/claude-code.mjs +52 -0
  277. package/src/runtime/codex-capacity-retry.mjs +87 -0
  278. package/src/runtime/codex.mjs +64 -0
  279. package/src/runtime/config.mjs +157 -0
  280. package/src/runtime/final-output.mjs +40 -0
  281. package/src/runtime/index.mjs +21 -0
  282. package/src/runtime/local-codex.mjs +74 -0
  283. package/src/runtime/opencode.mjs +95 -0
  284. package/src/runtime/prompt.mjs +13 -0
  285. package/src/sandbox/docker.mjs +363 -0
  286. package/src/settings/command.mjs +297 -0
  287. package/src/settings/store.mjs +119 -0
  288. package/src/telemetry/pricing.mjs +68 -0
  289. package/src/telemetry/usage.mjs +265 -0
  290. package/src/terminal/events.mjs +97 -0
  291. package/src/terminal/input.mjs +40 -0
  292. package/src/terminal/plain.mjs +40 -0
  293. package/src/terminal/remote-stream.mjs +22 -0
  294. package/src/terminal/screen.mjs +214 -0
  295. package/src/terminal/transcript.mjs +69 -0
  296. package/src/util.mjs +107 -0
  297. package/src/verification/ai-assessor.mjs +534 -0
  298. package/src/verification/claim-evaluator.mjs +242 -0
  299. package/src/verification/evidence-context.mjs +165 -0
  300. package/src/verification/evidence-reader.mjs +95 -0
  301. package/src/verification/integrity.mjs +570 -0
  302. package/src/verification/tolerance.mjs +32 -0
  303. package/src/workloads/cpu-research-preparation.mjs +56 -0
  304. package/src/workloads/definition.mjs +74 -0
  305. package/src/workloads/phase-aware-reproduction.mjs +46 -0
  306. package/src/workloads/reproduction.mjs +85 -0
  307. package/src/workspace/command.mjs +242 -0
  308. package/src/workspace/control.mjs +49 -0
  309. package/src/workspace/entry.mjs +28 -0
  310. package/src/workspace/input.mjs +93 -0
  311. package/src/workspace/interactive.mjs +94 -0
  312. package/src/workspace/jobs.mjs +418 -0
  313. package/src/workspace/session.mjs +97 -0
  314. package/src/workspace/worker.mjs +137 -0
  315. package/ui/arkgraph/ambient-motion.mjs +10 -0
  316. package/ui/arkgraph/app.jsx +153 -0
  317. package/ui/arkgraph/boot.js +6 -0
  318. package/ui/arkgraph/camera-motion.mjs +20 -0
  319. package/ui/arkgraph/context-reveal.mjs +39 -0
  320. package/ui/arkgraph/details.css +3 -0
  321. package/ui/arkgraph/entry.jsx +28 -0
  322. package/ui/arkgraph/experiment-curves.mjs +17 -0
  323. package/ui/arkgraph/experiment-selection.mjs +15 -0
  324. package/ui/arkgraph/experiment-style.css +26 -0
  325. package/ui/arkgraph/experiment-ui.jsx +32 -0
  326. package/ui/arkgraph/frame.html +1 -0
  327. package/ui/arkgraph/graph-gestures.mjs +62 -0
  328. package/ui/arkgraph/label-layout.mjs +57 -0
  329. package/ui/arkgraph/locales/en.json +229 -0
  330. package/ui/arkgraph/locales/source-types.json +15 -0
  331. package/ui/arkgraph/localization-build.mjs +27 -0
  332. package/ui/arkgraph/material-build.mjs +23 -0
  333. package/ui/arkgraph/material-colors.mjs +39 -0
  334. package/ui/arkgraph/material-style.css +15 -0
  335. package/ui/arkgraph/open-graph.jsx +326 -0
  336. package/ui/arkgraph/outline.jsx +49 -0
  337. package/ui/arkgraph/package-lock.json +888 -0
  338. package/ui/arkgraph/package.json +17 -0
  339. package/ui/arkgraph/reading-layout.mjs +130 -0
  340. package/ui/arkgraph/reading-presentation.mjs +73 -0
  341. package/ui/arkgraph/record-detail.css +51 -0
  342. package/ui/arkgraph/record-details.jsx +29 -0
  343. package/ui/arkgraph/research-types.mjs +31 -0
  344. package/ui/arkgraph/selection-mark.jsx +6 -0
  345. package/ui/arkgraph/soft-spine.mjs +26 -0
  346. package/ui/arkgraph/steering-style.css +187 -0
  347. package/ui/arkgraph/style.css +272 -0
@@ -0,0 +1,462 @@
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+ import net from "node:net";
4
+ import tls from "node:tls";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ const GOOGLE_HOSTS = [
8
+ "google.com",
9
+ ".google.com",
10
+ "googleapis.com",
11
+ ".googleapis.com",
12
+ "googlesource.com",
13
+ ".googlesource.com",
14
+ "gstatic.com",
15
+ ".gstatic.com",
16
+ "googleusercontent.com",
17
+ ".googleusercontent.com",
18
+ "research.google",
19
+ ];
20
+
21
+ const ALWAYS_LIGUSE_HOSTS = [
22
+ ...GOOGLE_HOSTS,
23
+ "openai.com",
24
+ ".openai.com",
25
+ "chatgpt.com",
26
+ ".chatgpt.com",
27
+ "oaistatic.com",
28
+ ".oaistatic.com",
29
+ "oaiusercontent.com",
30
+ ".oaiusercontent.com",
31
+ "anthropic.com",
32
+ ".anthropic.com",
33
+ "openrouter.ai",
34
+ ".openrouter.ai",
35
+ ];
36
+
37
+ const TURBO_HOSTS = [
38
+ "github.com",
39
+ ".github.com",
40
+ "githubusercontent.com",
41
+ ".githubusercontent.com",
42
+ "githubassets.com",
43
+ ".githubassets.com",
44
+ "huggingface.co",
45
+ ".huggingface.co",
46
+ "hf.co",
47
+ ".hf.co",
48
+ "xethub.hf.co",
49
+ ".xethub.hf.co",
50
+ "pypi.org",
51
+ ".pypi.org",
52
+ "pythonhosted.org",
53
+ ".pythonhosted.org",
54
+ "pytorch.org",
55
+ ".pytorch.org",
56
+ ];
57
+
58
+ export function routeAutoDlHost(hostname, { alwaysProxyHosts = [] } = {}) {
59
+ const normalized = normalizeHost(hostname);
60
+ if (matchesHost(normalized, [...ALWAYS_LIGUSE_HOSTS, ...alwaysProxyHosts])) {
61
+ return "liguse-sg";
62
+ }
63
+ if (matchesHost(normalized, TURBO_HOSTS)) return "autodl-turbo";
64
+ return "direct";
65
+ }
66
+
67
+ export async function startAutoDlRoutingProxy({
68
+ listenHost = "127.0.0.1",
69
+ listenPort = 0,
70
+ turboProxyUrl,
71
+ liguseProxyUrl,
72
+ alwaysProxyHosts = [],
73
+ connectTimeoutMs = 12_000,
74
+ directConnector = connectDirect,
75
+ logger = () => {},
76
+ } = {}) {
77
+ const upstreams = {
78
+ "autodl-turbo": parseProxyUrl(turboProxyUrl, "AutoDL 学术加速代理"),
79
+ "liguse-sg": parseProxyUrl(liguseProxyUrl, "LiguseSG 受限公网代理"),
80
+ };
81
+ const routing = { alwaysProxyHosts, connectTimeoutMs, directConnector };
82
+ const sockets = new Set();
83
+ const server = http.createServer((request, response) => {
84
+ if (request.url === "/__citeark_health") {
85
+ response.writeHead(204).end();
86
+ return;
87
+ }
88
+ void forwardHttp(request, response, upstreams, routing, logger);
89
+ });
90
+ server.on("connect", (request, clientSocket, head) => {
91
+ void forwardConnect(request, clientSocket, head, upstreams, routing, sockets, logger);
92
+ });
93
+ server.on("connection", (socket) => trackSocket(socket, sockets));
94
+ await new Promise((resolve, reject) => {
95
+ server.once("error", reject);
96
+ server.listen(listenPort, listenHost, resolve);
97
+ });
98
+ const address = server.address();
99
+ const port = typeof address === "object" && address ? address.port : null;
100
+ return {
101
+ host: listenHost,
102
+ port,
103
+ proxyUrl: `http://${listenHost}:${port}`,
104
+ close: async () => {
105
+ for (const socket of sockets) socket.destroy();
106
+ await new Promise((resolve) => server.close(resolve));
107
+ },
108
+ };
109
+ }
110
+
111
+ async function forwardHttp(request, response, upstreams, routing, logger) {
112
+ let target;
113
+ try {
114
+ target = new URL(request.url);
115
+ } catch {
116
+ response.writeHead(400).end("Absolute proxy URL required\n");
117
+ return;
118
+ }
119
+ if (!["http:", "https:"].includes(target.protocol)) {
120
+ response.writeHead(403).end("Proxy protocol is not allowed\n");
121
+ return;
122
+ }
123
+ const route = routeAutoDlHost(target.hostname, routing);
124
+ logger({ type: "http", route, host: target.hostname });
125
+ const headers = sanitizedHeaders(request.headers, target.host);
126
+ let body;
127
+ try {
128
+ body = await readRequestBody(request);
129
+ } catch {
130
+ response.writeHead(413).end("Proxy request body is too large\n");
131
+ return;
132
+ }
133
+ const routes = route === "liguse-sg" ? [route] : [route, "liguse-sg"];
134
+ for (let index = 0; index < routes.length; index += 1) {
135
+ const candidate = routes[index];
136
+ try {
137
+ const incoming = await httpAttempt({
138
+ request,
139
+ target,
140
+ headers,
141
+ body,
142
+ route: candidate,
143
+ upstreams,
144
+ timeoutMs: routing.connectTimeoutMs,
145
+ });
146
+ const canRetry = index + 1 < routes.length
147
+ && isIdempotent(request.method)
148
+ && retryableStatus(incoming.statusCode);
149
+ if (canRetry) {
150
+ incoming.destroy();
151
+ logger({
152
+ type: "http-fallback",
153
+ from: candidate,
154
+ route: "liguse-sg",
155
+ host: target.hostname,
156
+ status: incoming.statusCode,
157
+ });
158
+ continue;
159
+ }
160
+ response.writeHead(incoming.statusCode ?? 502, incoming.headers);
161
+ incoming.pipe(response);
162
+ return;
163
+ } catch {
164
+ if (index + 1 < routes.length) {
165
+ logger({
166
+ type: "http-fallback",
167
+ from: candidate,
168
+ route: "liguse-sg",
169
+ host: target.hostname,
170
+ });
171
+ continue;
172
+ }
173
+ }
174
+ }
175
+ if (!response.headersSent) response.writeHead(502);
176
+ response.end("CiteArk routing proxy upstream error\n");
177
+ }
178
+
179
+ function httpAttempt({ request, target, headers, body, route, upstreams, timeoutMs }) {
180
+ return new Promise((resolve, reject) => {
181
+ const upstream = route === "direct" ? null : upstreams[route];
182
+ const transport = upstream?.protocol === "https:"
183
+ ? https
184
+ : upstream
185
+ ? http
186
+ : target.protocol === "https:"
187
+ ? https
188
+ : http;
189
+ const options = upstream
190
+ ? {
191
+ hostname: upstream.hostname,
192
+ port: upstream.port,
193
+ method: request.method,
194
+ path: target.href,
195
+ headers: withProxyAuthorization(headers, upstream),
196
+ }
197
+ : {
198
+ hostname: target.hostname,
199
+ port: target.port || (target.protocol === "https:" ? 443 : 80),
200
+ method: request.method,
201
+ path: `${target.pathname}${target.search}`,
202
+ headers,
203
+ };
204
+ const outgoing = transport.request(options, resolve);
205
+ outgoing.setTimeout(timeoutMs, () => {
206
+ outgoing.destroy(new Error("HTTP upstream timed out"));
207
+ });
208
+ outgoing.once("error", reject);
209
+ outgoing.end(body);
210
+ });
211
+ }
212
+
213
+ async function readRequestBody(request, maximumBytes = 16 * 1024 * 1024) {
214
+ const chunks = [];
215
+ let bytes = 0;
216
+ for await (const chunk of request) {
217
+ bytes += chunk.length;
218
+ if (bytes > maximumBytes) throw new Error("Request body is too large");
219
+ chunks.push(chunk);
220
+ }
221
+ return Buffer.concat(chunks);
222
+ }
223
+
224
+ function isIdempotent(method) {
225
+ return new Set(["GET", "HEAD", "OPTIONS"]).has(String(method ?? "GET").toUpperCase());
226
+ }
227
+
228
+ function retryableStatus(status) {
229
+ return status === 408 || status === 425 || status === 429 || status >= 500;
230
+ }
231
+
232
+ async function forwardConnect(
233
+ request,
234
+ clientSocket,
235
+ head,
236
+ upstreams,
237
+ routing,
238
+ sockets,
239
+ logger,
240
+ ) {
241
+ let authority;
242
+ try {
243
+ authority = parseAuthority(request.url);
244
+ } catch {
245
+ clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
246
+ return;
247
+ }
248
+ if (![80, 443].includes(authority.port)) {
249
+ clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
250
+ return;
251
+ }
252
+ const route = routeAutoDlHost(authority.hostname, routing);
253
+ logger({ type: "connect", route, host: authority.hostname });
254
+ try {
255
+ let upstream;
256
+ try {
257
+ upstream = route === "direct"
258
+ ? await routing.directConnector(authority, sockets, routing.connectTimeoutMs)
259
+ : await connectThroughProxy(
260
+ authority,
261
+ upstreams[route],
262
+ sockets,
263
+ routing.connectTimeoutMs,
264
+ );
265
+ } catch (error) {
266
+ if (route === "liguse-sg") throw error;
267
+ logger({
268
+ type: "connect-fallback",
269
+ from: route,
270
+ route: "liguse-sg",
271
+ host: authority.hostname,
272
+ });
273
+ upstream = await connectThroughProxy(
274
+ authority,
275
+ upstreams["liguse-sg"],
276
+ sockets,
277
+ routing.connectTimeoutMs,
278
+ );
279
+ }
280
+ if (clientSocket.destroyed) {
281
+ upstream.destroy();
282
+ return;
283
+ }
284
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
285
+ if (head.length) upstream.write(head);
286
+ upstream.pipe(clientSocket);
287
+ clientSocket.pipe(upstream);
288
+ clientSocket.once("error", () => upstream.destroy());
289
+ upstream.once("error", () => clientSocket.destroy());
290
+ } catch {
291
+ if (!clientSocket.destroyed) {
292
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
293
+ }
294
+ }
295
+ }
296
+
297
+ function connectDirect(authority, sockets, timeoutMs) {
298
+ return new Promise((resolve, reject) => {
299
+ const socket = net.connect({ host: authority.hostname, port: authority.port });
300
+ trackSocket(socket, sockets);
301
+ const timer = setTimeout(() => {
302
+ socket.destroy();
303
+ reject(new Error("Direct connection timed out"));
304
+ }, timeoutMs);
305
+ timer.unref();
306
+ socket.once("connect", () => {
307
+ clearTimeout(timer);
308
+ resolve(socket);
309
+ });
310
+ socket.once("error", (error) => {
311
+ clearTimeout(timer);
312
+ reject(error);
313
+ });
314
+ });
315
+ }
316
+
317
+ function connectThroughProxy(authority, proxy, sockets, timeoutMs) {
318
+ return new Promise((resolve, reject) => {
319
+ const socket = proxy.protocol === "https:"
320
+ ? tls.connect({
321
+ host: proxy.hostname,
322
+ port: proxy.port,
323
+ servername: proxy.hostname,
324
+ })
325
+ : net.connect({ host: proxy.hostname, port: proxy.port });
326
+ trackSocket(socket, sockets);
327
+ let response = Buffer.alloc(0);
328
+ const timer = setTimeout(() => {
329
+ socket.destroy();
330
+ reject(new Error("Proxy connection timed out"));
331
+ }, timeoutMs);
332
+ timer.unref();
333
+ const connectedEvent = proxy.protocol === "https:" ? "secureConnect" : "connect";
334
+ socket.once(connectedEvent, () => {
335
+ const headers = [
336
+ `CONNECT ${authority.hostname}:${authority.port} HTTP/1.1`,
337
+ `Host: ${authority.hostname}:${authority.port}`,
338
+ "Proxy-Connection: keep-alive",
339
+ ];
340
+ const authorization = proxyAuthorization(proxy);
341
+ if (authorization) headers.push(`Proxy-Authorization: ${authorization}`);
342
+ socket.write(`${headers.join("\r\n")}\r\n\r\n`);
343
+ });
344
+ const onData = (chunk) => {
345
+ response = Buffer.concat([response, chunk]);
346
+ if (response.length > 64 * 1024) {
347
+ clearTimeout(timer);
348
+ socket.destroy();
349
+ reject(new Error("Proxy response header is too large"));
350
+ return;
351
+ }
352
+ const boundary = response.indexOf("\r\n\r\n");
353
+ if (boundary < 0) return;
354
+ socket.off("data", onData);
355
+ const statusLine = response.subarray(0, boundary).toString("latin1").split("\r\n")[0];
356
+ if (!/^HTTP\/1\.[01] 2\d\d\b/.test(statusLine)) {
357
+ clearTimeout(timer);
358
+ socket.destroy();
359
+ reject(new Error("Upstream proxy rejected CONNECT"));
360
+ return;
361
+ }
362
+ const remainder = response.subarray(boundary + 4);
363
+ if (remainder.length) socket.unshift(remainder);
364
+ clearTimeout(timer);
365
+ resolve(socket);
366
+ };
367
+ socket.on("data", onData);
368
+ socket.once("error", (error) => {
369
+ clearTimeout(timer);
370
+ reject(error);
371
+ });
372
+ });
373
+ }
374
+
375
+ function parseProxyUrl(value, label) {
376
+ let parsed;
377
+ try {
378
+ parsed = new URL(String(value ?? ""));
379
+ } catch {
380
+ throw new TypeError(`${label} URL 无效`);
381
+ }
382
+ if (!["http:", "https:"].includes(parsed.protocol) || !parsed.hostname) {
383
+ throw new TypeError(`${label} 必须是 http/https URL`);
384
+ }
385
+ return {
386
+ protocol: parsed.protocol,
387
+ hostname: parsed.hostname,
388
+ port: Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80)),
389
+ username: decodeURIComponent(parsed.username),
390
+ password: decodeURIComponent(parsed.password),
391
+ };
392
+ }
393
+
394
+ function parseAuthority(value) {
395
+ const parsed = new URL(`http://${String(value ?? "")}`);
396
+ if (!parsed.hostname) throw new TypeError("CONNECT authority is invalid");
397
+ return {
398
+ hostname: normalizeHost(parsed.hostname),
399
+ port: Number(parsed.port || 443),
400
+ };
401
+ }
402
+
403
+ function sanitizedHeaders(input, host) {
404
+ const headers = { ...input, host };
405
+ delete headers["proxy-authorization"];
406
+ delete headers["proxy-connection"];
407
+ return headers;
408
+ }
409
+
410
+ function withProxyAuthorization(headers, proxy) {
411
+ const output = { ...headers };
412
+ const authorization = proxyAuthorization(proxy);
413
+ if (authorization) output["proxy-authorization"] = authorization;
414
+ return output;
415
+ }
416
+
417
+ function proxyAuthorization(proxy) {
418
+ if (!proxy.username) return null;
419
+ return `Basic ${Buffer.from(`${proxy.username}:${proxy.password}`).toString("base64")}`;
420
+ }
421
+
422
+ function matchesHost(hostname, patterns) {
423
+ return patterns.some((pattern) => pattern.startsWith(".")
424
+ ? hostname.endsWith(pattern)
425
+ : hostname === pattern);
426
+ }
427
+
428
+ function normalizeHost(value) {
429
+ return String(value ?? "").toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
430
+ }
431
+
432
+ function trackSocket(socket, sockets) {
433
+ sockets.add(socket);
434
+ socket.once("close", () => sockets.delete(socket));
435
+ }
436
+
437
+ async function main() {
438
+ const proxy = await startAutoDlRoutingProxy({
439
+ listenHost: process.env.CITEARK_ROUTING_PROXY_HOST ?? "127.0.0.1",
440
+ listenPort: Number(process.env.CITEARK_ROUTING_PROXY_PORT ?? 18080),
441
+ turboProxyUrl: process.env.CITEARK_AUTODL_TURBO_PROXY,
442
+ liguseProxyUrl: process.env.CITEARK_LIGUSE_SG_PROXY,
443
+ alwaysProxyHosts: commaList(process.env.CITEARK_ALWAYS_PROXY_HOSTS),
444
+ logger: (event) => process.stderr.write(`${JSON.stringify(event)}\n`),
445
+ });
446
+ process.stderr.write(`${JSON.stringify({
447
+ type: "citeark.routing_proxy.ready",
448
+ host: proxy.host,
449
+ port: proxy.port,
450
+ })}\n`);
451
+ }
452
+
453
+ function commaList(value) {
454
+ return String(value ?? "")
455
+ .split(",")
456
+ .map((entry) => normalizeHost(entry.trim()))
457
+ .filter(Boolean);
458
+ }
459
+
460
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
461
+ await main();
462
+ }
@@ -0,0 +1,158 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import http from "node:http";
3
+ import https from "node:https";
4
+ import net from "node:net";
5
+
6
+ const DEFAULT_ALLOWED_HOSTS = [
7
+ "arxiv.org",
8
+ "export.arxiv.org",
9
+ "github.com",
10
+ ".githubusercontent.com",
11
+ ".githubassets.com",
12
+ "huggingface.co",
13
+ ".huggingface.co",
14
+ ".hf.co",
15
+ ".xethub.hf.co",
16
+ "pypi.org",
17
+ ".pythonhosted.org",
18
+ "download.pytorch.org",
19
+ ".pytorch.org",
20
+ ];
21
+
22
+ export function previewEgressProxy() {
23
+ return {
24
+ mode: "host-allowlist-proxy",
25
+ containerProxyUrl: null,
26
+ allowedHosts: [...DEFAULT_ALLOWED_HOSTS],
27
+ close: async () => {},
28
+ };
29
+ }
30
+
31
+ export async function startEgressProxy({ allowedHosts = DEFAULT_ALLOWED_HOSTS } = {}) {
32
+ // 代理监听 0.0.0.0 以便容器经 host.docker.internal 访问;
33
+ // 使用每次执行随机生成的令牌做 Proxy-Authorization 鉴权,
34
+ // 避免同局域网的其他机器借用这条出口(与 provider relay 的令牌机制对齐)。
35
+ const token = randomBytes(24).toString("base64url");
36
+ const expectedAuth = Buffer.from(
37
+ `Basic ${Buffer.from(`citeark-agent:${token}`).toString("base64")}`,
38
+ );
39
+ const sockets = new Set();
40
+ const server = http.createServer((request, response) => {
41
+ if (!authorized(request.headers["proxy-authorization"], expectedAuth)) {
42
+ response
43
+ .writeHead(407, { "proxy-authenticate": 'Basic realm="citeark-egress"' })
44
+ .end("CiteArk egress proxy authentication required\n");
45
+ return;
46
+ }
47
+ void forwardHttpRequest(request, response, allowedHosts);
48
+ });
49
+ server.on("connect", (request, clientSocket, head) => {
50
+ if (!authorized(request.headers["proxy-authorization"], expectedAuth)) {
51
+ clientSocket.end(
52
+ 'HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="citeark-egress"\r\n\r\n',
53
+ );
54
+ return;
55
+ }
56
+ forwardConnect(request, clientSocket, head, allowedHosts, sockets);
57
+ });
58
+ server.on("connection", (socket) => {
59
+ sockets.add(socket);
60
+ socket.once("close", () => sockets.delete(socket));
61
+ });
62
+ await new Promise((resolve, reject) => {
63
+ server.once("error", reject);
64
+ server.listen(0, "0.0.0.0", resolve);
65
+ });
66
+ const address = server.address();
67
+ const port = typeof address === "object" && address ? address.port : null;
68
+ return {
69
+ mode: "host-allowlist-proxy",
70
+ hostProxyUrl: `http://citeark-agent:${token}@127.0.0.1:${port}`,
71
+ containerProxyUrl: `http://citeark-agent:${token}@host.docker.internal:${port}`,
72
+ allowedHosts: [...allowedHosts],
73
+ close: async () => {
74
+ for (const socket of sockets) socket.destroy();
75
+ await new Promise((resolve) => server.close(resolve));
76
+ },
77
+ };
78
+ }
79
+
80
+ function authorized(header, expectedAuth) {
81
+ if (!header) return false;
82
+ const actual = Buffer.from(header);
83
+ return actual.length === expectedAuth.length && timingSafeEqual(actual, expectedAuth);
84
+ }
85
+
86
+ async function forwardHttpRequest(request, response, allowedHosts) {
87
+ let target;
88
+ try {
89
+ target = new URL(request.url);
90
+ } catch {
91
+ response.writeHead(400).end("Absolute proxy URL required\n");
92
+ return;
93
+ }
94
+ if (!allowed(target.hostname, allowedHosts)) {
95
+ response.writeHead(403).end("Host is not allowed by CiteArk egress policy\n");
96
+ return;
97
+ }
98
+ const transport = target.protocol === "https:" ? https : http;
99
+ const headers = { ...request.headers, host: target.host };
100
+ delete headers["proxy-connection"];
101
+ delete headers["proxy-authorization"];
102
+ const upstream = transport.request(target, {
103
+ method: request.method,
104
+ headers,
105
+ }, (upstreamResponse) => {
106
+ response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers);
107
+ upstreamResponse.pipe(response);
108
+ });
109
+ upstream.on("error", () => {
110
+ if (!response.headersSent) response.writeHead(502);
111
+ response.end("CiteArk egress upstream error\n");
112
+ });
113
+ request.pipe(upstream);
114
+ }
115
+
116
+ function forwardConnect(request, clientSocket, head, allowedHosts, sockets) {
117
+ const authority = request.url ?? "";
118
+ const separator = authority.lastIndexOf(":");
119
+ const hostname = separator > 0 ? authority.slice(0, separator) : authority;
120
+ const port = separator > 0 ? Number(authority.slice(separator + 1)) : 443;
121
+ if (!allowed(hostname, allowedHosts) || !new Set([80, 443]).has(port)) {
122
+ clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
123
+ return;
124
+ }
125
+ const upstream = net.connect({ host: hostname, port });
126
+ sockets.add(upstream);
127
+ // A desktop task can detach its terminal while a long-running download is
128
+ // still in flight. Treat either half of the CONNECT tunnel disappearing as a
129
+ // normal cancellation; otherwise a late pipe write can raise an unhandled
130
+ // EPIPE and kill the whole pipeline coordinator.
131
+ clientSocket.once("error", () => upstream.destroy());
132
+ upstream.once("close", () => sockets.delete(upstream));
133
+ upstream.once("connect", () => {
134
+ if (clientSocket.destroyed) {
135
+ upstream.destroy();
136
+ return;
137
+ }
138
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
139
+ if (head.length) upstream.write(head);
140
+ upstream.pipe(clientSocket);
141
+ clientSocket.pipe(upstream);
142
+ });
143
+ upstream.once("error", () => {
144
+ if (!clientSocket.destroyed) {
145
+ clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
146
+ }
147
+ });
148
+ }
149
+
150
+ function allowed(hostname, allowedHosts) {
151
+ const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
152
+ return allowedHosts.some((entry) => {
153
+ const expected = entry.toLowerCase();
154
+ return expected.startsWith(".")
155
+ ? normalized.endsWith(expected)
156
+ : normalized === expected;
157
+ });
158
+ }