feat: save compatible provider models to customModels DB for /v1/models listing#122
Conversation
Summary of ChangesHello @nyatoru, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the management of compatible provider models by integrating them with a Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces functionality to persist compatible provider models in a custom database, ensuring they appear in the /v1/models listing, and integrates their saving and deletion across various workflows. While the feature is useful, the implementation has several security vulnerabilities. New API endpoints for managing provider models and aliases lack authentication and authorization checks, allowing unauthorized modification of system configuration. Furthermore, a URL parameter injection vulnerability exists in the model deletion logic due to improper encoding of user-controlled variables. These security issues must be addressed before merging.
| // Save to customModels DB so it shows up in /v1/models | ||
| await fetch("/api/provider-models", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| provider: providerStorageAlias, | ||
| modelId, | ||
| modelName: model.name || modelId, | ||
| source: "imported", | ||
| }), | ||
| }); | ||
| // Also create alias for routing | ||
| await onSetAlias(modelId, resolvedAlias, providerStorageAlias); |
There was a problem hiding this comment.
Similar to the manual add function, if the fetch call to save the imported model to the customModels database fails, the onSetAlias call still proceeds. This can result in an alias being created for a model that wasn't successfully recorded in the database, leading to data inconsistencies.
Ensure that the onSetAlias call is only executed if the model is successfully saved to the customModels database. This will prevent partial updates and maintain data integrity.
// Save to customModels DB so it shows up in /v1/models
const res = await fetch("/api/provider-models", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: providerStorageAlias,
modelId,
modelName: model.name || modelId,
source: "imported",
}),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error?.message || "Failed to save imported model to custom DB");
}
// Also create alias for routing
await onSetAlias(modelId, resolvedAlias, providerStorageAlias);
| try { | ||
| // Remove from customModels DB | ||
| await fetch( | ||
| `/api/provider-models?provider=${providerStorageAlias}&model=${encodeURIComponent(modelId)}`, |
There was a problem hiding this comment.
The providerStorageAlias variable is concatenated directly into the URL query string without proper encoding. Since providerStorageAlias is derived from the providerId URL parameter (which is user-controlled), an attacker could craft a malicious URL containing characters like & or = to inject additional query parameters into the API request, potentially manipulating the deletion logic (e.g., by injecting a different model parameter).
`/api/provider-models?provider=${encodeURIComponent(providerStorageAlias)}&model=${encodeURIComponent(modelId)}`,
| // Also delete the alias | ||
| await onDeleteAlias(alias); | ||
| } catch (error) { | ||
| console.log("Error deleting model:", error); |
There was a problem hiding this comment.
The handleDeleteModel function only logs errors to the console. For a user-facing action like deleting a model, it would be beneficial to provide visual feedback to the user if the deletion fails. This could be an alert, a toast notification, or an error message displayed on the UI.
Consider adding a user-friendly error notification to improve the user experience.
console.error("Error deleting model:", error);
alert("Failed to delete model. Please try again.");
|
Hello @nyatoru! I'm here to help. Do you have any specific questions about the pull request or my previous summary and review comments? Feel free to ask, or if you'd like me to perform another action, you can use commands like |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces significant enhancements to model management and API security. Key changes include updating the ComboFormModal to match provider nodes by ID or prefix, and refactoring model addition and deletion logic in CompatibleModelsSection to first interact with a customModels database before managing aliases. A new handleDeleteModel function was added to ensure models are removed from both the database and alias system. Authentication has been added to several API routes (/api/models/alias and /api/provider-models) by introducing a verifyAuth function that checks for API keys or JWT cookies. The /api/v1/models endpoint now incorporates providerNodes to better handle custom model prefixes and activation checks for compatible providers. Model display in ModelSelectModal and model availability checks in chat.ts were updated to correctly resolve custom prefixes using getModelInfo. The getModelInfo service itself was refactored to prioritize checking custom provider nodes for both alias and non-alias model strings.
Review comments highlight several areas for improvement: the verifyAuth function is duplicated across multiple API routes and should be extracted into a shared utility; the variable providerPart in ComboFormModal could be renamed for better clarity; error handling for customModelRes.json() could be more explicit; and alert calls for user feedback should be replaced with a non-blocking notification system. Additionally, user-facing notifications are suggested for failed imported model saves and model deletions. Finally, the review questions the suitability of providerId as a fallback for nodePrefix if it's a UUID, and asks for assurance that prefixToCheck in getModelInfo is always a concise identifier.
src/app/api/models/alias/route.ts
Outdated
| async function verifyAuth(request) { | ||
| // Check API key (for external clients) | ||
| const apiKey = extractApiKey(request); | ||
| if (apiKey && (await isValidApiKey(apiKey))) { | ||
| return true; | ||
| } | ||
|
|
||
| // Check JWT cookie (for dashboard session) | ||
| if (process.env.JWT_SECRET) { | ||
| try { | ||
| const cookieStore = await cookies(); | ||
| const token = cookieStore.get("auth_token")?.value; | ||
| if (token) { | ||
| const secret = new TextEncoder().encode(process.env.JWT_SECRET); | ||
| await jwtVerify(token, secret); | ||
| return true; | ||
| } | ||
| } catch { | ||
| // Invalid/expired token or cookies not available | ||
| } | ||
| } | ||
|
|
||
| return false; |
There was a problem hiding this comment.
The verifyAuth function is duplicated across src/app/api/models/alias/route.ts and src/app/api/provider-models/route.ts. This leads to code duplication and makes maintenance harder. It would be better to extract this authentication logic into a shared utility function or middleware that can be imported and reused across different API routes. This improves maintainability and ensures consistent authentication logic.
/**
* Verify authentication - check API key or JWT cookie
*/
// This function is duplicated. Consider moving it to a shared utility file.
// For example: import { verifyAuth } from "@/lib/authUtils";
async function verifyAuth(request) {
// Check API key (for external clients)
const apiKey = extractApiKey(request);
if (apiKey && (await isValidApiKey(apiKey))) {
return true;
}
// Check JWT cookie (for dashboard session)
if (process.env.JWT_SECRET) {
try {
const cookieStore = await cookies();
const token = cookieStore.get("auth_token")?.value;
if (token) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
await jwtVerify(token, secret);
return true;
}
} catch {
// Invalid/expired token or cookies not available
}
}
return false;
}
src/app/api/provider-models/route.ts
Outdated
| async function verifyAuth(request) { | ||
| // Check API key (for external clients) | ||
| const apiKey = extractApiKey(request); | ||
| if (apiKey && (await isValidApiKey(apiKey))) { | ||
| return true; | ||
| } | ||
|
|
||
| // Check JWT cookie (for dashboard session) | ||
| if (process.env.JWT_SECRET) { | ||
| try { | ||
| const cookieStore = await cookies(); | ||
| const token = cookieStore.get("auth_token")?.value; | ||
| if (token) { | ||
| const secret = new TextEncoder().encode(process.env.JWT_SECRET); | ||
| await jwtVerify(token, secret); | ||
| return true; | ||
| } | ||
| } catch { | ||
| // Invalid/expired token or cookies not available | ||
| } | ||
| } | ||
|
|
||
| return false; |
There was a problem hiding this comment.
The verifyAuth function is duplicated across src/app/api/models/alias/route.ts and src/app/api/provider-models/route.ts. This leads to code duplication and makes maintenance harder. It would be better to extract this authentication logic into a shared utility function or middleware that can be imported and reused across different API routes. This improves maintainability and ensures consistent authentication logic.
/**
* Verify authentication - check API key or JWT cookie
*/
// This function is duplicated. Consider moving it to a shared utility file.
// For example: import { verifyAuth } from "@/lib/authUtils";
async function verifyAuth(request) {
// Check API key (for external clients)
const apiKey = extractApiKey(request);
if (apiKey && (await isValidApiKey(apiKey))) {
return true;
}
// Check JWT cookie (for dashboard session)
if (process.env.JWT_SECRET) {
try {
const cookieStore = await cookies();
const token = cookieStore.get("auth_token")?.value;
if (token) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
await jwtVerify(token, secret);
return true;
}
} catch {
// Invalid/expired token or cookies not available
}
}
return false;
}| console.log("Error adding model:", error); | ||
| alert(error instanceof Error ? error.message : "Failed to add model. Please try again."); |
There was a problem hiding this comment.
The alert function is generally not recommended for user feedback in modern web applications, as it's blocking and can lead to a poor user experience. Consider using a more integrated notification system (like the notify.error used elsewhere in the codebase) to provide feedback to the user without interrupting their workflow.
console.log("Error adding model:", error);
// Use a non-blocking notification system instead of alert
// notify.error(error instanceof Error ? error.message : "Failed to add model. Please try again.");
alert(error instanceof Error ? error.message : "Failed to add model. Please try again.");
| if (!customModelRes.ok) { | ||
| console.error("Failed to save imported model to customModels DB"); |
There was a problem hiding this comment.
Similar to the previous comment, console.error is used here for an error that might be user-facing. While it's good for debugging, if this failure is due to user input or a known API issue, it might be better to provide user feedback through a notification system rather than just logging to the console. This ensures the user is aware of why the import failed.
if (!customModelRes.ok) {
console.error("Failed to save imported model to customModels DB");
// Consider adding user-facing notification here, e.g., notify.error("Failed to save imported model.");
return false;
}
| const handleDeleteModel = async (modelId: string, alias: string) => { | ||
| try { | ||
| // Remove from customModels DB | ||
| await fetch( | ||
| `/api/provider-models?provider=${providerStorageAlias}&model=${encodeURIComponent(modelId)}`, | ||
| { method: "DELETE" } | ||
| ); | ||
| // Also delete the alias | ||
| await onDeleteAlias(alias); | ||
| } catch (error) { | ||
| console.log("Error deleting model:", error); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The handleDeleteModel function is well-structured. However, it only logs the error to the console. For a user-initiated action like deleting a model, it's important to provide feedback to the user if the deletion fails. Consider adding a user-facing notification (e.g., using notify.error) to inform the user about the failure.
const handleDeleteModel = async (modelId: string, alias: string) => {
try {
// Remove from customModels DB
await fetch(
`/api/provider-models?provider=${providerStorageAlias}&model=${encodeURIComponent(modelId)}`,
{ method: "DELETE" }
);
// Also delete the alias
await onDeleteAlias(alias);
// notify.success("Model deleted successfully"); // Example user feedback
} catch (error) {
console.log("Error deleting model:", error);
// notify.error("Failed to delete model. Please try again."); // Example user feedback
}
};
…x custom provider model resolution
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
The pull request introduces significant improvements by enhancing model handling for custom providers, centralizing authentication logic, and refining the model import/delete processes. The changes improve flexibility and security across the application. However, there are a couple of minor issues that need to be addressed.
| } | ||
| throw new Error(errorData.error?.message || "Failed to save custom model"); | ||
| } | ||
| } |
- Fix URL parameter injection: apply encodeURIComponent on providerStorageAlias and providerId in all API calls - Replace blocking alert() with non-blocking notify.error/notify.success toast notifications - Add success feedback for model add and delete operations - Improve error handling: use console.error consistently and add user-facing notifications for import failures - Check DELETE response status before proceeding with alias removal
…DB for /v1/models listing Includes security hardening and UX improvements: - Authentication on /api/provider-models via isAuthenticated - URL parameter injection prevention (encodeURIComponent) - Replaced alert() with notify.error/notify.success toasts - Transactional save: DB first, then alias creation - Consistent error handling across all model operations
Add Custom Provider to support models endpoint