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
2 changes: 1 addition & 1 deletion apps/web/src/app/admin/custom-llms/CustomLlmsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function CustomLlmsContent() {
return;
}

const result = CustomLlmDefinitionSchema.safeParse(parsed);
const result = CustomLlmDefinitionSchema.strict().safeParse(parsed);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Legacy custom LLM definitions can no longer be re-saved

list still returns the raw JSON from custom_llm2, so existing rows that still contain the removed reasoning_summary key will open in the editor unchanged. With .strict() here, clicking Save now fails validation on those untouched records. Normalizing the definition before rendering/editing, or validating non-strictly and saving the parsed value, avoids breaking edits for existing entries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intentional

if (!result.success) {
const messages = result.error.issues
.map(issue => `${issue.path.join('.')}: ${issue.message}`)
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/app/api/openrouter/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import type { MicrodollarUsageContext, PromptInfo } from '@/lib/ai-gateway/proce
import { extractResponsesPromptInfo } from '@/lib/ai-gateway/processUsage.responses';
import { extractMessagesPromptInfo } from '@/lib/ai-gateway/processUsage.messages';
import {
enableReasoningSummaries,
fixResponsesRequest,
getMaxTokens,
hasMiddleOutTransform,
Expand Down Expand Up @@ -503,6 +504,8 @@ export async function POST(request: NextRequest): Promise<NextResponseType<unkno
fixResponsesRequest(requestBodyParsed.body);
}

enableReasoningSummaries(requestBodyParsed);

const toolsAvailable = getToolsAvailable(requestBodyParsed);
const toolsUsed = getToolsUsed(requestBodyParsed);

Expand Down
7 changes: 0 additions & 7 deletions apps/web/src/lib/ai-gateway/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,6 @@ export async function getProvider(
if (customLlm.add_cache_breakpoints) {
addCacheBreakpoints(context.request);
}
if (
customLlm.reasoning_summary &&
context.request.kind === 'responses' &&
context.request.body.reasoning
) {
context.request.body.reasoning.summary = customLlm.reasoning_summary;
}
if (customLlm.inject_reasoning_into_content) {
injectReasoningIntoContent(context.request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,17 @@ export function isReasoningExplicitlyDisabled(request: GatewayRequest) {
}
return (request.body.reasoning?.effort ?? request.body.reasoning_effort) === 'none';
}

export function enableReasoningSummaries(request: GatewayRequest) {
if (
request.kind === 'messages' &&
request.body.thinking &&
(request.body.thinking.type === 'enabled' || request.body.thinking.type === 'adaptive') &&
!request.body.thinking.display
) {
request.body.thinking.display = 'summarized';
}
if (request.kind === 'responses' && request.body.reasoning && !request.body.reasoning.summary) {
request.body.reasoning.summary = 'auto';
}
}
39 changes: 18 additions & 21 deletions packages/db/src/schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,27 +990,24 @@ export const CustomLlmPricingSchema = z.object({

export type CustomLlmPricing = z.infer<typeof CustomLlmPricingSchema>;

export const CustomLlmDefinitionSchema = z
.object({
internal_id: z.string(),
display_name: z.string(),
context_length: z.number(),
max_completion_tokens: z.number(),
base_url: z.string(),
api_key: z.string(),
organization_ids: z.array(z.string()),
supports_image_input: z.boolean().optional(),
add_cache_breakpoints: z.boolean().optional(),
inject_reasoning_into_content: z.boolean().optional(),
reasoning_summary: z.enum(['auto', 'concise', 'detailed']).optional(),
extra_headers: CustomLlmExtraHeadersSchema.optional(),
extra_body: CustomLlmExtraBodySchema.optional(),
remove_from_body: z.array(z.string()).optional(),
opencode_settings: OpenCodeSettingsSchema.optional(),
openclaw_settings: OpenClawModelSettingsSchema.optional(),
pricing: CustomLlmPricingSchema.optional(),
})
.strict();
export const CustomLlmDefinitionSchema = z.object({
internal_id: z.string(),
display_name: z.string(),
context_length: z.number(),
max_completion_tokens: z.number(),
base_url: z.string(),
api_key: z.string(),
organization_ids: z.array(z.string()),
supports_image_input: z.boolean().optional(),
add_cache_breakpoints: z.boolean().optional(),
inject_reasoning_into_content: z.boolean().optional(),
extra_headers: CustomLlmExtraHeadersSchema.optional(),
extra_body: CustomLlmExtraBodySchema.optional(),
remove_from_body: z.array(z.string()).optional(),
opencode_settings: OpenCodeSettingsSchema.optional(),
openclaw_settings: OpenClawModelSettingsSchema.optional(),
pricing: CustomLlmPricingSchema.optional(),
});

export type CustomLlmDefinition = z.infer<typeof CustomLlmDefinitionSchema>;

Expand Down
Loading