Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/config/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./appConfigOnDisk";
export * from "./modelParameters";
export * from "./providersConfig";
export * from "./configOperations";
export * from "./taskSettings";
43 changes: 43 additions & 0 deletions src/common/config/schemas/modelParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { CallSettings } from "ai";
import { z } from "zod";

const STANDARD_MODEL_PARAMETER_SHAPE = {
max_output_tokens: z.number().int().positive().optional(),
temperature: z.number().min(0).max(2).optional(),
top_p: z.number().min(0).max(1).optional(),
top_k: z.number().int().positive().optional(),
seed: z.number().int().optional(),
frequency_penalty: z.number().optional(),
presence_penalty: z.number().optional(),
} as const;

export const StandardModelParameterOverridesSchema = z.object(STANDARD_MODEL_PARAMETER_SHAPE);

// Single runtime mapping source of truth (snake_case config -> AI SDK CallSettings keys)
export const STANDARD_MODEL_PARAMETER_TO_CALL_SETTING = {
max_output_tokens: "maxOutputTokens",
temperature: "temperature",
top_p: "topP",
top_k: "topK",
seed: "seed",
frequency_penalty: "frequencyPenalty",
presence_penalty: "presencePenalty",
} as const satisfies Record<keyof typeof STANDARD_MODEL_PARAMETER_SHAPE, keyof CallSettings>;

export const ModelParameterOverridesSchema = StandardModelParameterOverridesSchema.passthrough();

export const ModelParametersByModelSchema = z.record(
z.string().min(1),
ModelParameterOverridesSchema
);

export type ModelParameterOverrides = z.infer<typeof ModelParameterOverridesSchema>;
export type StandardModelParameterOverrides = z.infer<typeof StandardModelParameterOverridesSchema>;

// Downstream type for call settings forwarding — derived from AI SDK CallSettings
export type ResolvedCallSettingsOverrides = Partial<
Pick<
CallSettings,
(typeof STANDARD_MODEL_PARAMETER_TO_CALL_SETTING)[keyof typeof STANDARD_MODEL_PARAMETER_TO_CALL_SETTING]
>
>;
78 changes: 78 additions & 0 deletions src/common/config/schemas/providersConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,82 @@ describe("ProvidersConfigSchema", () => {

expect(ProvidersConfigSchema.safeParse(invalid).success).toBe(false);
});

describe("modelParameters", () => {
it("accepts valid per-model and wildcard overrides", () => {
const valid = {
openai: {
modelParameters: {
"gpt-5": { max_output_tokens: 1024, temperature: 0.4 },
"*": { top_p: 0.9 },
},
},
};

expect(ProvidersConfigSchema.safeParse(valid).success).toBe(true);
});

it("rejects negative max_output_tokens", () => {
const invalid = {
openai: {
modelParameters: {
"gpt-5": { max_output_tokens: -1 },
},
},
};

expect(ProvidersConfigSchema.safeParse(invalid).success).toBe(false);
});

it("rejects temperature values above 2", () => {
const invalid = {
openai: {
modelParameters: {
"gpt-5": { temperature: 3 },
},
},
};

expect(ProvidersConfigSchema.safeParse(invalid).success).toBe(false);
});

it("rejects top_p values above 1", () => {
const invalid = {
openai: {
modelParameters: {
"gpt-5": { top_p: 1.5 },
},
},
};

expect(ProvidersConfigSchema.safeParse(invalid).success).toBe(false);
});

it("passes through unknown override keys", () => {
const valid = {
openai: {
modelParameters: {
"gpt-5": { transforms: ["middle-out"] },
},
},
};

const parsed = ProvidersConfigSchema.safeParse(valid);

expect(parsed.success).toBe(true);
if (parsed.success) {
expect(parsed.data.openai?.modelParameters?.["gpt-5"]).toEqual({
transforms: ["middle-out"],
});
}
});

it("allows provider configs without modelParameters", () => {
const valid = {
openai: { apiKey: "sk-openai-123" },
};

expect(ProvidersConfigSchema.safeParse(valid).success).toBe(true);
});
});
});
2 changes: 2 additions & 0 deletions src/common/config/schemas/providersConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";

import { ModelParametersByModelSchema } from "./modelParameters";
import { ProviderModelEntrySchema } from "./providerModelEntry";

export const CacheTtlSchema = z.enum(["5m", "1h"]);
Expand All @@ -15,6 +16,7 @@ export const BaseProviderConfigSchema = z
headers: z.record(z.string(), z.string()).optional(),
enabled: z.boolean().optional(),
models: z.array(ProviderModelEntrySchema).optional(),
modelParameters: ModelParametersByModelSchema.optional(),
})
.passthrough();

Expand Down
Loading
Loading