mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 00:52:18 +03:00
Requests AgentUrlConfig from Cursor with each selected account token and selects the account's actual server-assigned Agent endpoint (agentUrl/agentnUrl) instead of a fixed global/us host, which fails for teams pinned to a different region. Caches validated endpoints by connection+token. Closes #10802. Validated in an isolated worktree boarded onto origin/release/v3.8.50 (0 conflicts, 4 files): - 18/18 focused tests pass (cursor-agent-host, cursor-apikey-provider). - provider-translate-path-golden.test.ts initially failed — traced to a pre-existing base-red (stale golden snapshot left by an earlier freebuff merge, #10531, unrelated to this PR) and confirmed it reproduces on the pure release tip without this PR's changes. Fixed directly on release/v3.8.50 (mechanical key-ordering regen, values unchanged) rather than folding it into this PR's scope; green after merging that fix in. - check-file-size, check-changelog-integrity: OK. - typecheck:core: clean. - check-complexity / check-cognitive-complexity: OK, both under baseline. - Author additionally validated live: a real Cursor request selected agentn.us.api5.cursor.sh and returned HTTP 200/PING. Co-authored-by: tuandinh0801 <tuandinh0801@users.noreply.github.com>
212 lines
8.7 KiB
TypeScript
212 lines
8.7 KiB
TypeScript
/**
|
|
* `cursor-api` is the API-key sibling of the `cursor` (IDE session) provider:
|
|
* same executor, format and model catalog, its own registry/catalog entry so
|
|
* API-key and IDE-session connections never share renewal or dashboard
|
|
* semantics. The executor swaps the crsr_ key for the exchanged session token
|
|
* before dialing.
|
|
*/
|
|
import { describe, it, afterEach } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { APIKEY_PROVIDERS, OAUTH_PROVIDERS } =
|
|
await import("../../src/shared/constants/providers.ts");
|
|
const { isManagedProviderConnectionId } = await import("../../src/lib/providers/catalog.ts");
|
|
const { cursorProvider, cursor_apiProvider } =
|
|
await import("../../open-sse/config/providers/registry/cursor/index.ts");
|
|
const { REGISTRY, generateAliasMap, getProviderCategory } =
|
|
await import("../../open-sse/config/providerRegistry.ts");
|
|
const { CursorExecutor, getExecutor, hasSpecializedExecutor } =
|
|
await import("../../open-sse/executors/index.ts");
|
|
const { __resetCursorApiKeyAuthForTest } =
|
|
await import("../../open-sse/services/cursorApiKeyAuth.ts");
|
|
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
|
|
|
const API_KEY = "crsr_provider_test_key";
|
|
|
|
function jwt(exp: number): string {
|
|
const b64 = (value: object) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
return `${b64({ alg: "HS256" })}.${b64({ exp })}.sig`;
|
|
}
|
|
|
|
describe("cursor-api provider wiring", () => {
|
|
it("is a distinct API-key registry entry sharing the cursor executor, format and models", () => {
|
|
assert.equal(cursor_apiProvider.id, "cursor-api");
|
|
assert.equal(cursor_apiProvider.authType, "apikey");
|
|
assert.equal(cursor_apiProvider.format, cursorProvider.format);
|
|
assert.equal(cursor_apiProvider.baseUrl, cursorProvider.baseUrl);
|
|
assert.equal(cursor_apiProvider.models, cursorProvider.models);
|
|
assert.equal(REGISTRY["cursor-api"], cursor_apiProvider);
|
|
assert.equal(generateAliasMap()["cursor-api"], "cua");
|
|
assert.equal(getProviderCategory("cursor-api"), "apikey");
|
|
});
|
|
|
|
it("leaves the IDE cursor provider OAuth-only", () => {
|
|
assert.equal(cursorProvider.authType, "oauth");
|
|
assert.equal(getProviderCategory("cursor"), "oauth");
|
|
assert.ok(OAUTH_PROVIDERS.cursor);
|
|
assert.ok(!APIKEY_PROVIDERS.cursor);
|
|
});
|
|
|
|
it("has its own API-key catalog card admitted by the managed-connection gate", () => {
|
|
assert.ok(APIKEY_PROVIDERS["cursor-api"]);
|
|
assert.equal(APIKEY_PROVIDERS["cursor-api"].alias, "cua");
|
|
assert.ok(!OAUTH_PROVIDERS["cursor-api"]);
|
|
assert.equal(isManagedProviderConnectionId("cursor-api"), true);
|
|
});
|
|
|
|
it("routes cursor-api and its alias to a CursorExecutor bound to the cursor-api id", () => {
|
|
for (const key of ["cursor-api", "cua"]) {
|
|
assert.equal(hasSpecializedExecutor(key), true, key);
|
|
const executor = getExecutor(key);
|
|
assert.ok(executor instanceof CursorExecutor, key);
|
|
assert.equal(executor.getProvider(), "cursor-api");
|
|
}
|
|
assert.equal(getExecutor("cursor").getProvider(), "cursor");
|
|
});
|
|
});
|
|
|
|
describe("CursorExecutor credential resolution", () => {
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
__resetCursorApiKeyAuthForTest();
|
|
});
|
|
|
|
it("sends the stripped IDE session token for OAuth connections", () => {
|
|
const executor = new CursorExecutor();
|
|
const headers = executor.buildHeaders({
|
|
accessToken: "user_01::ide.session.jwt",
|
|
providerSpecificData: {},
|
|
});
|
|
assert.equal(headers.authorization, "Bearer ide.session.jwt");
|
|
assert.equal(headers["x-cursor-client-type"], "cli");
|
|
});
|
|
|
|
it("exchanges a crsr_ key and sends the session JWT, never the raw key", async () => {
|
|
const calls: string[] = [];
|
|
const exp = Math.floor(Date.now() / 1000) + 3600;
|
|
globalThis.fetch = (async (input: string | URL | Request) => {
|
|
calls.push(String(input));
|
|
return new Response(JSON.stringify({ accessToken: jwt(exp), refreshToken: jwt(exp) }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}) as typeof fetch;
|
|
|
|
const executor = new CursorExecutor("cursor-api");
|
|
const resolved = await executor.resolveExecutionCredentials({
|
|
apiKey: API_KEY,
|
|
providerSpecificData: {},
|
|
});
|
|
assert.ok(!(resolved instanceof Response));
|
|
const headers = executor.buildHeaders(resolved);
|
|
assert.equal(headers.authorization, `Bearer ${jwt(exp)}`);
|
|
assert.ok(!headers.authorization.includes(API_KEY));
|
|
assert.equal(calls.length, 1);
|
|
assert.match(calls[0], /\/auth\/exchange_user_api_key$/);
|
|
});
|
|
|
|
it("returns a sanitized 401 response when Cursor rejects the key", async () => {
|
|
globalThis.fetch = (async () => new Response("bad key", { status: 401 })) as typeof fetch;
|
|
const executor = new CursorExecutor("cursor-api");
|
|
const resolved = await executor.resolveExecutionCredentials({ apiKey: API_KEY });
|
|
assert.ok(resolved instanceof Response);
|
|
assert.equal(resolved.status, 401);
|
|
const body = (await resolved.json()) as { error: { message: string; type: string } };
|
|
assert.equal(body.error.type, "authentication_error");
|
|
assert.ok(!body.error.message.includes(API_KEY));
|
|
assert.ok(!body.error.message.includes("at /"));
|
|
});
|
|
|
|
it("leaves OAuth credentials untouched without calling the exchange endpoint", async () => {
|
|
globalThis.fetch = (async () => {
|
|
throw new Error("exchange must not be called");
|
|
}) as typeof fetch;
|
|
const executor = new CursorExecutor();
|
|
const credentials = { accessToken: "user_01::ide.session.jwt" };
|
|
const resolved = await executor.resolveExecutionCredentials(credentials);
|
|
assert.equal(resolved, credentials);
|
|
});
|
|
|
|
it("exchanges a crsr_ key before Agent endpoint discovery", async () => {
|
|
const exp = Math.floor(Date.now() / 1000) + 3600;
|
|
const sessionToken = jwt(exp);
|
|
const calls: Array<{ url: string; authorization: string }> = [];
|
|
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
const url = String(input);
|
|
calls.push({
|
|
url,
|
|
authorization: new Headers(init?.headers).get("authorization") ?? "",
|
|
});
|
|
if (url.endsWith("/auth/exchange_user_api_key")) {
|
|
return new Response(
|
|
JSON.stringify({ accessToken: sessionToken, refreshToken: sessionToken }),
|
|
{ status: 200, headers: { "content-type": "application/json" } }
|
|
);
|
|
}
|
|
return new Response(Buffer.alloc(0), { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
const executor = new CursorExecutor("cursor-api");
|
|
const result = await executor.execute({
|
|
model: "auto",
|
|
body: { messages: [] },
|
|
stream: false,
|
|
credentials: { apiKey: API_KEY, connectionId: "cursor-api-test" },
|
|
});
|
|
|
|
assert.equal(result.response.status, 500);
|
|
assert.equal(calls.length, 2);
|
|
assert.match(calls[0].url, /\/auth\/exchange_user_api_key$/);
|
|
assert.match(calls[1].url, /ServerConfigService\/GetServerConfig$/);
|
|
assert.equal(calls[1].authorization, `Bearer ${sessionToken}`);
|
|
assert.ok(!calls[1].authorization.includes(API_KEY));
|
|
});
|
|
});
|
|
|
|
describe("cursor-api connection test", () => {
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
__resetCursorApiKeyAuthForTest();
|
|
});
|
|
|
|
it("validates by exchanging the key and reports the exchange as the method", async () => {
|
|
let exchangeCalls = 0;
|
|
globalThis.fetch = (async () => {
|
|
exchangeCalls += 1;
|
|
return new Response(JSON.stringify({ accessToken: jwt(1_900_000_000), refreshToken: "r" }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
}) as typeof fetch;
|
|
const result = await validateProviderApiKey({ provider: "cursor-api", apiKey: API_KEY });
|
|
assert.equal(result.valid, true);
|
|
assert.equal(result.unsupported, false);
|
|
assert.equal(exchangeCalls, 1);
|
|
});
|
|
|
|
it("reports a rejected key as invalid (401), not as unsupported", async () => {
|
|
globalThis.fetch = (async () => new Response("nope", { status: 401 })) as typeof fetch;
|
|
const result = await validateProviderApiKey({ provider: "cursor-api", apiKey: API_KEY });
|
|
assert.equal(result.valid, false);
|
|
assert.equal(result.unsupported, false);
|
|
assert.equal(result.statusCode, 401);
|
|
assert.ok(!String(result.error).includes(API_KEY));
|
|
});
|
|
|
|
it("rejects keys without the crsr_ prefix locally", async () => {
|
|
globalThis.fetch = (async () => {
|
|
throw new Error("must not be called");
|
|
}) as typeof fetch;
|
|
const result = await validateProviderApiKey({
|
|
provider: "cursor-api",
|
|
apiKey: "sk-not-cursor",
|
|
});
|
|
assert.equal(result.valid, false);
|
|
assert.equal(result.statusCode, 400);
|
|
});
|
|
});
|