From e755277aaffbdaf9d830534aba971b1637326773 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:11:02 +0000 Subject: [PATCH 1/2] fix: resolve model discovery to /v1/models regardless of baseUrl Co-Authored-By: thibault --- requesty.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requesty.js b/requesty.js index 5ab8906..cd5b420 100644 --- a/requesty.js +++ b/requesty.js @@ -13,6 +13,11 @@ function normalizeBaseUrl(baseUrl) { return baseUrl.replace(/\/+$/, ""); } +function modelsUrl(baseUrl) { + const base = normalizeBaseUrl(baseUrl); + return /\/v\d+$/.test(base) ? `${base}/models` : `${base}/v1/models`; +} + function readModelsJson() { if (!fs.existsSync(MODELS_JSON_PATH)) { throw new Error(`${MODELS_JSON_PATH} does not exist`); @@ -56,7 +61,7 @@ function getRequestyConfig() { } async function discoverModels(provider) { - const response = await fetch(`${provider.baseUrl}/models`, { + const response = await fetch(modelsUrl(provider.baseUrl), { headers: { Authorization: `Bearer ${provider.apiKey}` }, }); From 625ec738b50a5255762fc6ee0bf59c0c861481dc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:04:44 +0000 Subject: [PATCH 2/2] test: add unit tests for normalizeBaseUrl and modelsUrl Co-Authored-By: thibault --- package.json | 3 +++ requesty.js | 4 ++-- requesty.test.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 requesty.test.js diff --git a/package.json b/package.json index acc1cec..72b5201 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "The official Requesty extension for the Pi Coding Agent", "type": "module", "main": "requesty.js", + "scripts": { + "test": "node --test" + }, "keywords": ["pi-package", "pi", "pi-coding-agent", "pi-extensions", "requesty", "openai-compatible", "models"], "license": "MIT", "peerDependencies": { diff --git a/requesty.js b/requesty.js index cd5b420..c918f22 100644 --- a/requesty.js +++ b/requesty.js @@ -9,11 +9,11 @@ const DEFAULT_NAME = "Requesty"; const DEFAULT_CONTEXT_WINDOW = 128000; const DEFAULT_MAX_TOKENS = 4096; -function normalizeBaseUrl(baseUrl) { +export function normalizeBaseUrl(baseUrl) { return baseUrl.replace(/\/+$/, ""); } -function modelsUrl(baseUrl) { +export function modelsUrl(baseUrl) { const base = normalizeBaseUrl(baseUrl); return /\/v\d+$/.test(base) ? `${base}/models` : `${base}/v1/models`; } diff --git a/requesty.test.js b/requesty.test.js new file mode 100644 index 0000000..e5f41fc --- /dev/null +++ b/requesty.test.js @@ -0,0 +1,35 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { modelsUrl, normalizeBaseUrl } from "./requesty.js"; + +test("normalizeBaseUrl strips trailing slashes", () => { + const cases = [ + ["https://router.requesty.ai", "https://router.requesty.ai"], + ["https://router.requesty.ai/", "https://router.requesty.ai"], + ["https://router.requesty.ai///", "https://router.requesty.ai"], + ["https://router.requesty.ai/v1", "https://router.requesty.ai/v1"], + ["https://router.requesty.ai/v1/", "https://router.requesty.ai/v1"], + ]; + + for (const [input, expected] of cases) { + assert.equal(normalizeBaseUrl(input), expected); + } +}); + +test("modelsUrl always resolves to a single /v1/models endpoint", () => { + const cases = [ + // Anthropic Messages config: baseUrl has no version segment. + ["https://router.requesty.ai", "https://router.requesty.ai/v1/models"], + ["https://router.requesty.ai/", "https://router.requesty.ai/v1/models"], + // OpenAI Completions config: baseUrl already ends with a version segment. + ["https://router.requesty.ai/v1", "https://router.requesty.ai/v1/models"], + ["https://router.requesty.ai/v1/", "https://router.requesty.ai/v1/models"], + // Other version segments are respected rather than doubled up. + ["https://router.requesty.ai/v2", "https://router.requesty.ai/v2/models"], + ]; + + for (const [input, expected] of cases) { + assert.equal(modelsUrl(input), expected); + } +});