From a1443b3e4620afc75426eb653d98ca0fa93f0f57 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Fri, 14 Aug 2026 03:54:32 +1000 Subject: [PATCH] fix: preserve v2 proxy routing --- src/plugin-v2.ts | 32 +++++++++++++++++++++++++++----- tests/unit/plugin-v2.test.ts | 31 +++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/plugin-v2.ts b/src/plugin-v2.ts index 0ca898c..68bc479 100644 --- a/src/plugin-v2.ts +++ b/src/plugin-v2.ts @@ -47,6 +47,17 @@ type V2ToolDefinition = { execute: (args: any, ctx: any) => Promise; options: { codemode: true }; }; +type V2SessionHook = { + (name: "context", callback: (event: { + model?: { providerID?: string }; + system: Array<{ type: "text"; text: string }>; + tools: Record }>; + }) => Promise): Promise; + (name: "http.request", callback: (event: { + model: { providerID: string }; + request: Request; + }) => Promise): Promise; +}; type V2Context = { catalog: { transform: (callback: (draft: { @@ -74,14 +85,18 @@ type V2Context = { }) => void) => Promise; }; session: { - hook: (name: "context", callback: (event: { - model?: { providerID?: string }; - system: Array<{ type: "text"; text: string }>; - tools: Record }>; - }) => Promise) => Promise; + hook: V2SessionHook; }; }; +function routeRequestToProxy(request: Request, baseURL: string): Request { + const target = new URL(request.url); + const proxy = new URL(baseURL); + target.protocol = proxy.protocol; + target.host = proxy.host; + return new Request(target, request); +} + /** Convert a V1-style tool entry (with zod args) into a V2 tool definition. */ function v2ToolFromV1( name: string, @@ -195,6 +210,13 @@ export function createV2Setup() { const proxyBaseURL = await ensureCursorProxyServer(workspaceDirectory, router ?? undefined); log.debug("Proxy server started", { baseURL: proxyBaseURL }); + // Config providers load after package plugins in V2 and can overwrite the + // catalog URL. Route at the HTTP boundary as the final source of truth. + registrations.push(await ctx.session.hook("http.request", async (event) => { + if (event.model.providerID !== CURSOR_PROVIDER_ID) return; + event.request = routeRequestToProxy(event.request, proxyBaseURL); + })); + // Register the cursor-acp provider + auth via catalog/integration transforms. registrations.push(await ctx.catalog.transform((catalog) => { catalog.provider.update(CURSOR_PROVIDER_ID, (p) => { diff --git a/tests/unit/plugin-v2.test.ts b/tests/unit/plugin-v2.test.ts index e004fd9..62541d2 100644 --- a/tests/unit/plugin-v2.test.ts +++ b/tests/unit/plugin-v2.test.ts @@ -32,6 +32,7 @@ function registration(onDispose: () => void = () => {}): Registration { function createContext() { const disposed: string[] = []; const toolAddCalls: any[][] = []; + let httpRequestHook: ((event: any) => Promise) | undefined; let sessionContextHook: ((event: any) => Promise) | undefined; let activeConnectionCalls = 0; let credential: any = { type: "key", key: "cursor-key" }; @@ -77,9 +78,10 @@ function createContext() { }, session: { hook: async (name: string, callback: (event: any) => Promise) => { - expect(name).toBe("context"); - sessionContextHook = callback; - return registration(() => disposed.push("session")); + if (name === "context") sessionContextHook = callback; + else if (name === "http.request") httpRequestHook = callback; + else throw new Error(`Unexpected session hook: ${name}`); + return registration(() => disposed.push(`session:${name}`)); }, }, }; @@ -91,6 +93,7 @@ function createContext() { toolAddCalls, activeConnectionCalls: () => activeConnectionCalls, setCredential: (value: any) => { credential = value; }, + httpRequestHook: () => httpRequestHook, sessionContextHook: () => sessionContextHook, }; } @@ -116,6 +119,24 @@ describe("opencode V2 adapter", () => { expect(provider.api).toBeUndefined(); }); + test("routes Cursor HTTP requests to the live proxy after config overlays", async () => { + const fixture = createContext(); + await createV2Setup()(fixture.context); + const event = { + model: { providerID: "cursor-acp" }, + request: new Request("http://127.0.0.1:9/v1/chat/completions", { + method: "POST", + body: "probe", + }), + }; + + await fixture.httpRequestHook()!(event); + + expect(event.request.url).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/v1\/chat\/completions$/); + expect(event.request.url).not.toContain(":9/"); + expect(await event.request.text()).toBe("probe"); + }); + test("registers complete tools with one V2 add argument", async () => { const { context, toolAddCalls } = createContext(); @@ -186,6 +207,8 @@ describe("opencode V2 adapter", () => { expect(cleanup).toBeTypeOf("function"); await cleanup!(); - expect(fixture.disposed.sort()).toEqual(["catalog", "integration", "session", "tool"]); + expect(fixture.disposed.sort()).toEqual([ + "catalog", "integration", "session:context", "session:http.request", "tool", + ]); }); });