From 507d823336a5340ea1c0bbba3b39acef9a1a35e0 Mon Sep 17 00:00:00 2001 From: midwestE <6331501+midweste@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:23:28 -0500 Subject: [PATCH] feat: auto-infer port from QDRANT_URL for reverse proxy support --- README.md | 2 +- src/constants.ts | 11 +++++++++++ src/services/qdrant.ts | 3 ++- tests/unit/constants.test.ts | 24 ++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28002ce..b2a06b6 100644 --- a/README.md +++ b/README.md @@ -712,7 +712,7 @@ Artifacts are chunked and embedded into Qdrant using the same hybrid dense + BM2 | Variable | Default | Description | |----------|---------|-------------| | `QDRANT_MODE` | `managed` | `managed` = Docker-managed local Qdrant (default). `external` = user-provided remote or cloud Qdrant (no Docker management). | -| `QDRANT_URL` | *(none)* | Full URL of a remote/cloud Qdrant instance (e.g. `https://xyz.aws.cloud.qdrant.io:6333`). When set, takes precedence over `QDRANT_HOST` + `QDRANT_PORT`. Required (or set `QDRANT_HOST`) when `QDRANT_MODE=external`. | +| `QDRANT_URL` | *(none)* | Full URL of a remote/cloud Qdrant instance (e.g. `https://xyz.aws.cloud.qdrant.io:6333`). When set, takes precedence over `QDRANT_HOST` + `QDRANT_PORT`. Port is auto-inferred from the URL: explicit port if present (e.g. `:8443`), otherwise `443` for `https://` or `6333` for `http://`. Required (or set `QDRANT_HOST`) when `QDRANT_MODE=external`. | | `QDRANT_PORT` | `16333` | Qdrant REST API port (managed mode, or external without `QDRANT_URL`) | | `QDRANT_GRPC_PORT` | `16334` | Qdrant gRPC port (managed mode only) | | `QDRANT_HOST` | `localhost` | Qdrant hostname (alternative to `QDRANT_URL` for non-HTTPS external instances) | diff --git a/src/constants.ts b/src/constants.ts index 43a44cc..409e36e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,6 +30,17 @@ export const QDRANT_MODE: "managed" | "external" = export const QDRANT_CONTAINER_NAME = "socraticode-qdrant"; export const QDRANT_IMAGE = "qdrant/qdrant:v1.17.0"; +/** + * Resolve the Qdrant REST port from a URL. + * Returns the explicit port if present (e.g. `:8443`), + * otherwise `443` for `https://` or `6333` for `http://`. + */ +export function resolveQdrantPort(url: string): number { + const parsed = new URL(url); + if (parsed.port) return parseInt(parsed.port, 10); + return url.startsWith("https:") ? 443 : 6333; +} + // ── Ollama configuration ──────────────────────────────────────────────── export const OLLAMA_PORT = parseInt(process.env.OLLAMA_PORT || "11435", 10); diff --git a/src/services/qdrant.ts b/src/services/qdrant.ts index 0934213..9057bd4 100644 --- a/src/services/qdrant.ts +++ b/src/services/qdrant.ts @@ -2,7 +2,7 @@ // Copyright (C) 2026 Giancarlo Erra - Altaire Limited import { createHash } from "node:crypto"; import { QdrantClient } from "@qdrant/js-client-rest"; -import { QDRANT_API_KEY, QDRANT_HOST, QDRANT_PORT, QDRANT_URL } from "../constants.js"; +import { QDRANT_API_KEY, QDRANT_HOST, QDRANT_PORT, QDRANT_URL, resolveQdrantPort } from "../constants.js"; import type { ArtifactIndexState, CodeGraph, FileChunk, SearchResult } from "../types.js"; import { getEmbeddingConfig } from "./embedding-config.js"; import { generateEmbeddings, generateQueryEmbedding, prepareDocumentText } from "./embeddings.js"; @@ -43,6 +43,7 @@ function getClient(): QdrantClient { QDRANT_URL ? { url: QDRANT_URL, + port: resolveQdrantPort(QDRANT_URL), ...(QDRANT_API_KEY ? { apiKey: QDRANT_API_KEY } : {}), checkCompatibility: false, } diff --git a/tests/unit/constants.test.ts b/tests/unit/constants.test.ts index 6938504..740a210 100644 --- a/tests/unit/constants.test.ts +++ b/tests/unit/constants.test.ts @@ -22,6 +22,7 @@ import { QDRANT_MODE, QDRANT_PORT, QDRANT_URL, + resolveQdrantPort, SEARCH_DEFAULT_LIMIT, SEARCH_MIN_SCORE, SPECIAL_FILES, @@ -371,3 +372,26 @@ describe("constants", () => { }); }); }); + +describe("resolveQdrantPort", () => { + it("returns explicit port from URL", () => { + expect(resolveQdrantPort("https://qdrant.example.com:6333")).toBe(6333); + expect(resolveQdrantPort("http://localhost:8080")).toBe(8080); + expect(resolveQdrantPort("https://my-qdrant.com:8443")).toBe(8443); + }); + + it("defaults to 443 for HTTPS URLs without explicit port", () => { + expect(resolveQdrantPort("https://qdrant.example.com")).toBe(443); + expect(resolveQdrantPort("https://my-tunnel.trycloudflare.com")).toBe(443); + }); + + it("defaults to 6333 for HTTP URLs without explicit port", () => { + expect(resolveQdrantPort("http://localhost")).toBe(6333); + expect(resolveQdrantPort("http://192.168.1.100")).toBe(6333); + }); + + it("handles URLs with paths and query strings", () => { + expect(resolveQdrantPort("https://qdrant.example.com:9999/some/path")).toBe(9999); + expect(resolveQdrantPort("https://qdrant.example.com/some/path")).toBe(443); + }); +});