🧳 fix: Carry Custom Endpoint Names Through Chat URLs (#15424)

`createPayload` interpolated the endpoint name straight into the chat URL,
so a custom endpoint named `Company/API` produced
`/api/agents/chat/Company/API`. The slash reads as a path separator, the
request no longer matches the router's `/:endpoint`, and it falls through
to the catch-all handler, which answers `404 Endpoint not found`.

Encoding the name keeps it a single path segment. Express matches the
encoded segment and decodes it back, and the endpoint the server acts on
comes from the request body regardless, so nothing else has to change.

Closes #15270
This commit is contained in:
Abhishek Biswas 2026-09-02 06:37:29 +05:30 • committed by GitHub
parent 0f9667b391
commit 38183fdc13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View file

@ -0,0 +1,55 @@
import type { TSubmission } from '../src/types';
import { EModelEndpoint } from '../src/schemas';
import { EndpointURLs } from '../src/config';
import createPayload from '../src/createPayload';
const agentsChatRoot = EndpointURLs[EModelEndpoint.agents];
const makeSubmission = (endpoint: string): TSubmission =>
({
conversation: { conversationId: 'convo-1', endpoint },
userMessage: { text: 'hello' },
endpointOption: { endpoint },
}) as unknown as TSubmission;
describe('createPayload server URL', () => {
it('builds the chat URL from a plain custom endpoint name', () => {
const { server } = createPayload(makeSubmission('OpenRouter'));
expect(server).toBe(`${agentsChatRoot}/OpenRouter`);
});
it('percent-encodes a slash so the name stays a single path segment', () => {
/** A raw `/` would split into an extra path segment and miss the server's
* `/:endpoint` route, which answers with a generic 404 "Endpoint not found"
* (danny-avila/LibreChat#15270). */
const { server } = createPayload(makeSubmission('Company/API'));
expect(server).toBe(`${agentsChatRoot}/Company%2FAPI`);
expect(server.slice(agentsChatRoot.length + 1)).not.toContain('/');
});
it('round-trips the encoded name back to the configured value', () => {
const { server } = createPayload(makeSubmission('Company/API'));
expect(decodeURIComponent(server.slice(agentsChatRoot.length + 1))).toBe('Company/API');
});
it('encodes other characters that are unsafe in a path segment', () => {
const { server } = createPayload(makeSubmission('Team A?v=1#x'));
expect(server).toBe(`${agentsChatRoot}/Team%20A%3Fv%3D1%23x`);
});
it('leaves the endpoint name itself untouched in the payload body', () => {
const { payload } = createPayload(makeSubmission('Company/API'));
expect(payload.endpoint).toBe('Company/API');
});
it('does not touch the assistants URL, which carries no endpoint segment', () => {
const { server } = createPayload(makeSubmission(EModelEndpoint.assistants));
expect(server).toBe(EndpointURLs[EModelEndpoint.assistants]);
});
});

View file

@ -35,7 +35,9 @@ export default function createPayload(submission: t.TSubmission) {
};
const endpoint = _e as s.EModelEndpoint;
let server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`;
/** Custom endpoint names are user-defined and may contain `/`, which would
* otherwise split into extra path segments and miss the `/:endpoint` route. */
let server = `${EndpointURLs[s.EModelEndpoint.agents]}/${encodeURIComponent(endpoint)}`;
if (s.isAssistantsEndpoint(endpoint)) {
server =
EndpointURLs[(endpointType ?? endpoint) as 'assistants' | 'azureAssistants'] +