mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-05-13 07:46:47 +00:00
🪆 fix: Allow Nested addParams in Config Schema (#12526)
* fix: allow nested addParams in config schema * Respect no-op task constraint Constraint: Task 2 explicitly forbids code changes Directive: Keep this worker branch code-identical to the assigned base for this task Confidence: high Scope-risk: narrow Tested: git status --short (clean) * fix: align addParams web_search validation with runtime * test: cover addParams edge cases * chore: ignore .codex directory
This commit is contained in:
parent
6ecd1b510f
commit
ed02fe40e0
3 changed files with 199 additions and 3 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -137,6 +137,7 @@ helm/**/.values.yaml
|
||||||
|
|
||||||
# AI Assistants
|
# AI Assistants
|
||||||
/.claude/
|
/.claude/
|
||||||
|
/.codex/
|
||||||
/.cursor/
|
/.cursor/
|
||||||
/.copilot/
|
/.copilot/
|
||||||
/.aider/
|
/.aider/
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import {
|
import {
|
||||||
endpointSchema,
|
|
||||||
paramDefinitionSchema,
|
paramDefinitionSchema,
|
||||||
agentsEndpointSchema,
|
agentsEndpointSchema,
|
||||||
azureEndpointSchema,
|
azureEndpointSchema,
|
||||||
|
endpointSchema,
|
||||||
|
configSchema,
|
||||||
} from '../src/config';
|
} from '../src/config';
|
||||||
import { tModelSpecPresetSchema, EModelEndpoint } from '../src/schemas';
|
import { tModelSpecPresetSchema, EModelEndpoint } from '../src/schemas';
|
||||||
|
|
||||||
|
|
@ -222,6 +223,109 @@ describe('endpointSchema deprecated fields', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('endpointSchema addParams validation', () => {
|
||||||
|
const validEndpoint = {
|
||||||
|
name: 'CustomEndpoint',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
baseURL: 'https://api.example.com',
|
||||||
|
models: { default: ['model-1'] },
|
||||||
|
};
|
||||||
|
const nestedAddParams = {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
quantizations: ['int4'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('accepts nested addParams objects and arrays', () => {
|
||||||
|
const result = endpointSchema.safeParse({
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: nestedAddParams,
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.addParams).toEqual(nestedAddParams);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps configSchema validation intact with nested custom addParams', () => {
|
||||||
|
const result = configSchema.safeParse({
|
||||||
|
version: '1.0.0',
|
||||||
|
endpoints: {
|
||||||
|
custom: [
|
||||||
|
{
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: nestedAddParams,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts boolean web_search in addParams', () => {
|
||||||
|
const result = endpointSchema.safeParse({
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
web_search: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts scalar addParams values', () => {
|
||||||
|
const result = endpointSchema.safeParse({
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: {
|
||||||
|
model: 'custom-model',
|
||||||
|
retries: 2,
|
||||||
|
metadata: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects non-boolean web_search objects in addParams', () => {
|
||||||
|
const result = endpointSchema.safeParse({
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
web_search: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects configSchema entries with non-boolean web_search objects in custom addParams', () => {
|
||||||
|
const result = configSchema.safeParse({
|
||||||
|
version: '1.0.0',
|
||||||
|
endpoints: {
|
||||||
|
custom: [
|
||||||
|
{
|
||||||
|
...validEndpoint,
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
web_search: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('agentsEndpointSchema', () => {
|
describe('agentsEndpointSchema', () => {
|
||||||
it('does not accept baseURL', () => {
|
it('does not accept baseURL', () => {
|
||||||
const result = agentsEndpointSchema.safeParse({
|
const result = agentsEndpointSchema.safeParse({
|
||||||
|
|
@ -251,4 +355,69 @@ describe('azureEndpointSchema', () => {
|
||||||
expect(result.data).not.toHaveProperty('plugins');
|
expect(result.data).not.toHaveProperty('plugins');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts nested addParams in azure groups', () => {
|
||||||
|
const result = azureEndpointSchema.safeParse({
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
group: 'test-group',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
models: { 'gpt-4': true },
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.groups[0].addParams).toEqual({
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts boolean web_search in azure addParams', () => {
|
||||||
|
const result = azureEndpointSchema.safeParse({
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
group: 'test-group',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
models: { 'gpt-4': true },
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
web_search: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects non-boolean web_search objects in azure addParams', () => {
|
||||||
|
const result = azureEndpointSchema.safeParse({
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
group: 'test-group',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
models: { 'gpt-4': true },
|
||||||
|
addParams: {
|
||||||
|
provider: {
|
||||||
|
only: ['z-ai'],
|
||||||
|
},
|
||||||
|
web_search: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -115,13 +115,39 @@ export const modelConfigSchema = z
|
||||||
|
|
||||||
export type TAzureModelConfig = z.infer<typeof modelConfigSchema>;
|
export type TAzureModelConfig = z.infer<typeof modelConfigSchema>;
|
||||||
|
|
||||||
|
const paramValueSchema: z.ZodType<unknown> = z.lazy(() =>
|
||||||
|
z.union([
|
||||||
|
z.string(),
|
||||||
|
z.number(),
|
||||||
|
z.boolean(),
|
||||||
|
z.null(),
|
||||||
|
z.array(paramValueSchema),
|
||||||
|
z.record(z.string(), paramValueSchema),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Validates addParams while keeping web_search aligned with current runtime boolean handling. */
|
||||||
|
const addParamsSchema: z.ZodType<Record<string, unknown>> = z
|
||||||
|
.record(z.string(), paramValueSchema)
|
||||||
|
.superRefine((params, ctx) => {
|
||||||
|
if (params.web_search === undefined || typeof params.web_search === 'boolean') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ['web_search'],
|
||||||
|
message: '`web_search` must be a boolean in addParams',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export const azureBaseSchema = z.object({
|
export const azureBaseSchema = z.object({
|
||||||
apiKey: z.string(),
|
apiKey: z.string(),
|
||||||
serverless: z.boolean().optional(),
|
serverless: z.boolean().optional(),
|
||||||
instanceName: z.string().optional(),
|
instanceName: z.string().optional(),
|
||||||
deploymentName: z.string().optional(),
|
deploymentName: z.string().optional(),
|
||||||
assistants: z.boolean().optional(),
|
assistants: z.boolean().optional(),
|
||||||
addParams: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
|
addParams: addParamsSchema.optional(),
|
||||||
dropParams: z.array(z.string()).optional(),
|
dropParams: z.array(z.string()).optional(),
|
||||||
version: z.string().optional(),
|
version: z.string().optional(),
|
||||||
baseURL: z.string().optional(),
|
baseURL: z.string().optional(),
|
||||||
|
|
@ -361,7 +387,7 @@ export const endpointSchema = baseEndpointSchema.merge(
|
||||||
iconURL: z.string().optional(),
|
iconURL: z.string().optional(),
|
||||||
modelDisplayLabel: z.string().optional(),
|
modelDisplayLabel: z.string().optional(),
|
||||||
headers: z.record(z.string()).optional(),
|
headers: z.record(z.string()).optional(),
|
||||||
addParams: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
|
addParams: addParamsSchema.optional(),
|
||||||
dropParams: z.array(z.string()).optional(),
|
dropParams: z.array(z.string()).optional(),
|
||||||
customParams: z
|
customParams: z
|
||||||
.object({
|
.object({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue