From f6b095547cd9389c10ad690018940d99e054d674 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Mon, 22 Jun 2026 10:00:55 +0000 Subject: [PATCH 1/8] Generated with Hive: Add resolveRequestUrl helper and persist requestUrl in session config and endpoints --- .../src/__tests__/resolveRequestUrl.test.ts | 82 +++++++++++++++++++ mcp/src/aieo/src/provider.ts | 36 ++++++++ mcp/src/benchmark/sessions.ts | 4 + mcp/src/repo/agent.ts | 2 + mcp/src/repo/session.ts | 1 + 5 files changed, 125 insertions(+) create mode 100644 mcp/src/aieo/src/__tests__/resolveRequestUrl.test.ts diff --git a/mcp/src/aieo/src/__tests__/resolveRequestUrl.test.ts b/mcp/src/aieo/src/__tests__/resolveRequestUrl.test.ts new file mode 100644 index 000000000..14c01af46 --- /dev/null +++ b/mcp/src/aieo/src/__tests__/resolveRequestUrl.test.ts @@ -0,0 +1,82 @@ +import { test, expect } from "@playwright/test"; +import { resolveRequestUrl } from "../provider.js"; + +// --------------------------------------------------------------------------- +// resolveRequestUrl — unit tests +// +// Verifies the fully-qualified HTTP endpoint returned per provider, +// both with an explicit baseUrl (gateway) and without (direct / no env). +// --------------------------------------------------------------------------- + +test.describe("resolveRequestUrl — with explicit baseUrl (gateway)", () => { + test("anthropic → /messages", () => { + expect(resolveRequestUrl("anthropic", "http://gw:3000")).toBe( + "http://gw:3000/anthropic/v1/messages", + ); + }); + + test("openai → /chat/completions", () => { + expect(resolveRequestUrl("openai", "http://gw:3000")).toBe( + "http://gw:3000/openai/v1/chat/completions", + ); + }); + + test("openrouter → /chat/completions (rides openai path)", () => { + expect(resolveRequestUrl("openrouter", "http://gw:3000")).toBe( + "http://gw:3000/openai/v1/chat/completions", + ); + }); + + test("google → /openai/v1/chat/completions (compat path)", () => { + expect(resolveRequestUrl("google", "http://gw:3000")).toBe( + "http://gw:3000/openai/v1/chat/completions", + ); + }); + + test("google strips provider suffix to recover gateway root", () => { + // gatewayUrlFor("google", "http://gw:3000") → "http://gw:3000/genai/v1beta" + // resolveRequestUrl must strip /genai/v1beta and re-target /openai/v1/chat/completions + const result = resolveRequestUrl("google", "http://gw:3000"); + expect(result).toBe("http://gw:3000/openai/v1/chat/completions"); + // Confirm the genai path is NOT present in the final URL + expect(result).not.toContain("/genai/"); + }); +}); + +test.describe("resolveRequestUrl — without baseUrl and no LLM_GATEWAY_URL env", () => { + // These tests assume LLM_GATEWAY_URL is not set in the test environment. + // If it is set, getGatewayBaseURL() will return a value and the test + // would produce a URL instead of undefined — skip gracefully in that case. + + test("anthropic → undefined (no gateway configured)", () => { + if (process.env.LLM_GATEWAY_URL) { + test.skip(true, "LLM_GATEWAY_URL is set; skipping no-gateway test"); + return; + } + expect(resolveRequestUrl("anthropic", undefined)).toBeUndefined(); + }); + + test("openai → undefined (no gateway configured)", () => { + if (process.env.LLM_GATEWAY_URL) { + test.skip(true, "LLM_GATEWAY_URL is set; skipping no-gateway test"); + return; + } + expect(resolveRequestUrl("openai", undefined)).toBeUndefined(); + }); + + test("openrouter → undefined (no gateway configured)", () => { + if (process.env.LLM_GATEWAY_URL) { + test.skip(true, "LLM_GATEWAY_URL is set; skipping no-gateway test"); + return; + } + expect(resolveRequestUrl("openrouter", undefined)).toBeUndefined(); + }); + + test("google → undefined (no gateway configured)", () => { + if (process.env.LLM_GATEWAY_URL) { + test.skip(true, "LLM_GATEWAY_URL is set; skipping no-gateway test"); + return; + } + expect(resolveRequestUrl("google", undefined)).toBeUndefined(); + }); +}); diff --git a/mcp/src/aieo/src/provider.ts b/mcp/src/aieo/src/provider.ts index 3504d11c7..2f144f1eb 100644 --- a/mcp/src/aieo/src/provider.ts +++ b/mcp/src/aieo/src/provider.ts @@ -88,6 +88,42 @@ export function gatewayUrlFor(provider: Provider, baseUrl: string): string { return `${trimmed}${providerPath}`; } +/** + * Returns the fully-qualified HTTP endpoint the SDK will hit for `provider`, + * given an optional caller-supplied `baseUrl`. Returns `undefined` when no + * gateway is configured and the SDK would call the provider directly. + * + * Endpoint path per SDK: + * anthropic → /messages + * openai / openrouter → /chat/completions + * google via gateway → /openai/v1/chat/completions (compat path) + * google direct → undefined (no fixed base URL) + */ +export function resolveRequestUrl( + provider: Provider, + baseUrl?: string, +): string | undefined { + const gatewayBase = baseUrl + ? gatewayUrlFor(provider, baseUrl) + : getGatewayBaseURL(provider); + if (!gatewayBase) return undefined; + + if (provider === "anthropic") { + return `${gatewayBase}/messages`; + } + if (provider === "google") { + // Mirror getModel(): strip any provider suffix to recover the gateway + // root, then target the OpenAI-compat endpoint. + const root = gatewayBase.replace( + /\/(anthropic|openai|genai)\/v\d+[a-z]*\/?$/i, + "", + ); + return `${root}/openai/v1/chat/completions`; + } + // openai, openrouter + return `${gatewayBase}/chat/completions`; +} + /** * Like {@link gatewayUrlFor} but takes a model name (shortcut like * `"sonnet"`, namespaced like `"anthropic/claude-sonnet-4-6"`, or a full diff --git a/mcp/src/benchmark/sessions.ts b/mcp/src/benchmark/sessions.ts index 1f00e9b70..e0527c1ae 100644 --- a/mcp/src/benchmark/sessions.ts +++ b/mcp/src/benchmark/sessions.ts @@ -7,6 +7,7 @@ import { loadSearchProvenance, loadAnnotations, appendAnnotation, + loadSessionConfig, type Annotation, type AnnotationMarker, } from "../repo/session.js"; @@ -293,6 +294,7 @@ export async function get_session(req: Request, res: Response) { const step_meta = loadStepMeta(id); const search_provenance = loadSearchProvenance(id); const annotations = loadAnnotations(id); + const sessionCfg = loadSessionConfig(id); if (db) { try { @@ -327,6 +329,7 @@ export async function get_session(req: Request, res: Response) { step_meta, search_provenance, annotations, + request_url: sessionCfg?.requestUrl ?? null, trace, }); return; @@ -362,6 +365,7 @@ export async function get_session(req: Request, res: Response) { step_meta, search_provenance, annotations, + request_url: sessionCfg?.requestUrl ?? null, trace, }); } diff --git a/mcp/src/repo/agent.ts b/mcp/src/repo/agent.ts index e8d504a16..b6df5a047 100644 --- a/mcp/src/repo/agent.ts +++ b/mcp/src/repo/agent.ts @@ -13,6 +13,7 @@ import { ModelName, getModelDetails, getProviderOptions, + resolveRequestUrl, normalizeUsage, } from "../aieo/src/index.js"; import { get_tools, ToolsConfig, SkillsConfig, GgnnConfig, MessagesRef, ProvenanceCollector } from "./tools.js"; @@ -513,6 +514,7 @@ Apply the guidance from each skill throughout your response.`; ), providerConfig: getProviderOptions(provider as any, undefined, modelId), baseUrl: opts.baseUrl, + requestUrl: resolveRequestUrl(provider as any, opts.baseUrl), // Redact secrets before persisting mcpServers: opts.mcpServers?.map(({ token, headers, ...rest }) => rest), subAgents: opts.subAgents?.map(({ apiToken, ...rest }) => rest), diff --git a/mcp/src/repo/session.ts b/mcp/src/repo/session.ts index 283306a9d..4a581681f 100644 --- a/mcp/src/repo/session.ts +++ b/mcp/src/repo/session.ts @@ -46,6 +46,7 @@ export interface SessionInitConfig { tools?: Record; // name → description for every resolved tool providerConfig?: { [key: string]: any }; // resolved getProviderOptions output baseUrl?: string; + requestUrl?: string; mcpServers?: { [key: string]: any }[]; // secrets (token/headers) redacted subAgents?: { [key: string]: any }[]; // secrets (apiToken) redacted ggnn?: { [key: string]: any }; From 4f815fb8a6e6966ed60d5a26a438944c0f29b315 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Fri, 26 Jun 2026 01:41:27 +0000 Subject: [PATCH 2/8] Generated with Hive: Retry cargo fetch in e2e workflow to fix Rust dependency CI failure --- .github/workflows/e2e-standalone.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-standalone.yml b/.github/workflows/e2e-standalone.yml index f15a6c045..dacf7f46e 100644 --- a/.github/workflows/e2e-standalone.yml +++ b/.github/workflows/e2e-standalone.yml @@ -72,11 +72,22 @@ jobs: done' echo "Neo4j is healthy!" + - name: Fetch Rust dependencies (with retry) + run: | + for i in 1 2 3; do + cargo fetch && break + echo "cargo fetch attempt $i failed, retrying in 15s..." + sleep 15 + done + + - name: Build Rust Standalone Server + run: cargo build --bin standalone --features neo4j + - name: Start Rust Standalone Server run: | export TEST_REF_ID=1 export USE_LSP=0 - cargo run --bin standalone --features neo4j > standalone.log 2>&1 & + ./target/debug/standalone > standalone.log 2>&1 & echo $! > standalone.pid - name: Wait for Rust server From a0dd49ecaa8dd3cbf4e5f59015b9e0aa54f27b16 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 17:25:27 +0000 Subject: [PATCH 3/8] Generated with Hive: Fix LSP CI failures by installing TypeScript dependencies for Next.js test fixture --- .github/workflows/rust-test-lsp.yml | 6 ++++++ mcp/src/benchmark/sessions.ts | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index 6131f3720..729a86fcb 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -141,6 +141,12 @@ jobs: which solargraph || echo "solargraph not found" which ruby-lsp || echo "ruby-lsp not found" + # -------------------- Install TypeScript deps for test fixtures -------------------- + - name: Install npm deps for nextjs test fixture + run: | + cd ast/src/testing/nextjs + npm install + # -------------------- Rust full test with LSP -------------------- - name: Run rust library tests with LSP run: USE_LSP=true cargo test -p ast --lib -- --test-threads=1 diff --git a/mcp/src/benchmark/sessions.ts b/mcp/src/benchmark/sessions.ts index c9ec5c485..f69e93158 100644 --- a/mcp/src/benchmark/sessions.ts +++ b/mcp/src/benchmark/sessions.ts @@ -7,7 +7,6 @@ import { loadSearchProvenance, loadAnnotations, appendAnnotation, - loadSessionConfig, type Annotation, type AnnotationMarker, } from "../repo/session.js"; @@ -314,7 +313,6 @@ export async function get_session(req: Request, res: Response) { const step_meta = loadStepMeta(id); const search_provenance = loadSearchProvenance(id); const annotations = loadAnnotations(id); - const sessionCfg = loadSessionConfig(id); if (db) { try { @@ -349,7 +347,6 @@ export async function get_session(req: Request, res: Response) { step_meta, search_provenance, annotations, - request_url: sessionCfg?.requestUrl ?? null, trace, }); return; @@ -385,7 +382,6 @@ export async function get_session(req: Request, res: Response) { step_meta, search_provenance, annotations, - request_url: sessionCfg?.requestUrl ?? null, trace, }); } From 2fabe836c1998179e0a5657b6955ef903ba13037 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 17:26:41 +0000 Subject: [PATCH 4/8] Generated with Hive: Install TypeScript in nextjs test fixture to fix LSP unit tests --- .github/workflows/rust-test-lsp.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index 729a86fcb..720ab42a6 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -55,6 +55,11 @@ jobs: tsc --version typescript-language-server --version + - name: Install TypeScript in nextjs test fixture + run: | + cd ast/src/testing/nextjs + npm install --ignore-scripts + # -------------------- Java / Kotlin -------------------- - name: Install OpenJDK 17 and jq run: | From 4d3159adaa269d1c0911ef137e9b58b625c0858e Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 17:41:39 +0000 Subject: [PATCH 5/8] Generated with Hive: Install npm dependencies for all TypeScript LSP test fixtures in CI --- .github/workflows/rust-test-lsp.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index 720ab42a6..e91d95a90 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -147,10 +147,11 @@ jobs: which ruby-lsp || echo "ruby-lsp not found" # -------------------- Install TypeScript deps for test fixtures -------------------- - - name: Install npm deps for nextjs test fixture + - name: Install npm deps for TypeScript test fixtures run: | - cd ast/src/testing/nextjs - npm install + cd ast/src/testing/nextjs && npm install + cd ../../testing/react && npm install + cd ../../testing/typescript && npm install # -------------------- Rust full test with LSP -------------------- - name: Run rust library tests with LSP From 6069fc6dd634d5ee0a1f74693b1a91b15b805822 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 17:47:05 +0000 Subject: [PATCH 6/8] Generated with Hive: Fix npm dependency installation for TypeScript test fixtures in CI workflow --- .github/workflows/rust-test-lsp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index e91d95a90..3ee909e18 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -149,9 +149,9 @@ jobs: # -------------------- Install TypeScript deps for test fixtures -------------------- - name: Install npm deps for TypeScript test fixtures run: | - cd ast/src/testing/nextjs && npm install - cd ../../testing/react && npm install - cd ../../testing/typescript && npm install + npm install --prefix ast/src/testing/nextjs + npm install --prefix ast/src/testing/react --legacy-peer-deps + npm install --prefix ast/src/testing/typescript # -------------------- Rust full test with LSP -------------------- - name: Run rust library tests with LSP From 88205b07e59222146d2a0ba2ec864eb8ed58cf15 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 18:05:09 +0000 Subject: [PATCH 7/8] Generated with Hive: Fix CI TypeScript installation path for LSP tests --- .github/workflows/rust-test-lsp.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index 3ee909e18..796661acd 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -47,8 +47,9 @@ jobs: - name: Install TypeScript and TypeScript LSP run: | npm install -g typescript typescript-language-server - export PATH="$(npm bin -g):$PATH" - echo "$(npm bin -g)" >> $GITHUB_PATH + NPM_GLOBAL_BIN="$(npm config get prefix)/bin" + export PATH="$NPM_GLOBAL_BIN:$PATH" + echo "$NPM_GLOBAL_BIN" >> $GITHUB_PATH - name: Verify TypeScript Installation run: | From 8a0064c341f15ac3ccb64e0b4d5d4e42165c1582 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Wed, 8 Jul 2026 18:18:47 +0000 Subject: [PATCH 8/8] Generated with Hive: Fix LSP CI by setting tsserver path and passing env var to TypeScript language server --- .github/workflows/rust-test-lsp.yml | 8 ++------ lsp/src/client.rs | 8 +++++--- lsp/src/language.rs | 11 ++++++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/rust-test-lsp.yml b/.github/workflows/rust-test-lsp.yml index 796661acd..fa5e40bb8 100644 --- a/.github/workflows/rust-test-lsp.yml +++ b/.github/workflows/rust-test-lsp.yml @@ -48,19 +48,15 @@ jobs: run: | npm install -g typescript typescript-language-server NPM_GLOBAL_BIN="$(npm config get prefix)/bin" - export PATH="$NPM_GLOBAL_BIN:$PATH" + NPM_GLOBAL_ROOT="$(npm root -g)" echo "$NPM_GLOBAL_BIN" >> $GITHUB_PATH + echo "TSSERVER_PATH=$NPM_GLOBAL_ROOT/typescript/bin/tsserver" >> $GITHUB_ENV - name: Verify TypeScript Installation run: | tsc --version typescript-language-server --version - - name: Install TypeScript in nextjs test fixture - run: | - cd ast/src/testing/nextjs - npm install --ignore-scripts - # -------------------- Java / Kotlin -------------------- - name: Install OpenJDK 17 and jq run: | diff --git a/lsp/src/client.rs b/lsp/src/client.rs index 9433f2db9..16cc68879 100644 --- a/lsp/src/client.rs +++ b/lsp/src/client.rs @@ -105,13 +105,15 @@ impl LspClient { } pub async fn init(&mut self) -> Result { debug!("LSP init... {:?}", self.root); + let root_uri = Url::from_file_path(&self.root).map_err(|_| { + Error::validation(format!("Invalid root path for LSP: {:?}", self.root)) + })?; let ret = self .server .initialize(InitializeParams { + root_uri: Some(root_uri.clone()), workspace_folders: Some(vec![WorkspaceFolder { - uri: Url::from_file_path(&self.root).map_err(|_| { - Error::validation(format!("Invalid root path for LSP: {:?}", self.root)) - })?, + uri: root_uri, name: "root".into(), }]), capabilities: ClientCapabilities { diff --git a/lsp/src/language.rs b/lsp/src/language.rs index c5d33be57..826d1baa3 100644 --- a/lsp/src/language.rs +++ b/lsp/src/language.rs @@ -226,7 +226,16 @@ impl Language { match self { Self::Rust => Vec::new(), Self::Go => Vec::new(), - Self::Typescript => vec!["--stdio".to_string()], + Self::Typescript => { + let mut args = vec!["--stdio".to_string()]; + if let Ok(tsserver_path) = std::env::var("TSSERVER_PATH") { + if !tsserver_path.is_empty() { + args.push("--tsserver-path".to_string()); + args.push(tsserver_path); + } + } + args + } Self::Python => Vec::new(), Self::Ruby => Vec::new(), Self::Kotlin => Vec::new(),