Conversation
…PENAI_MODEL_NAME, OPENAI_IMAGE_MODEL) and sync complete .env.example
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f97046d7-94fc-4e5d-b287-42de387e88ab) |
There was a problem hiding this comment.
Code Review
This pull request expands the configuration options in .env.example and refactors the OpenAI integrations across the backend and libraries to support custom OpenAI-compatible gateways, custom models, and dynamic configurations. The reviewer feedback highlights several key areas for improvement: using optional chaining to safely access image generation responses, ensuring DallEAPIWrapper instances are configured with the custom base URL, avoiding module-level instantiation of ChatOpenAI to prevent environment variable loading issues, and updating early return checks in the copilot controller to support API-keyless local endpoints when a custom base URL is provided.
| const generate = ( | ||
| await openai.images.generate({ | ||
| prompt, | ||
| model: 'chatgpt-image-latest', | ||
| model: getImageModel(), | ||
| size: isVertical ? '1024x1536' : '1024x1024', | ||
| }) | ||
| ).data[0]; |
There was a problem hiding this comment.
Use optional chaining and a fallback object when accessing data[0] to prevent potential runtime crashes if the API returns an empty data array.
| const generate = ( | |
| await openai.images.generate({ | |
| prompt, | |
| model: 'chatgpt-image-latest', | |
| model: getImageModel(), | |
| size: isVertical ? '1024x1536' : '1024x1024', | |
| }) | |
| ).data[0]; | |
| const generate = ( | |
| await openai.images.generate({ | |
| prompt, | |
| model: getImageModel(), | |
| size: isVertical ? '1024x1536' : '1024x1024', | |
| }) | |
| ).data?.[0] || {}; |
| const dalle = new DallEAPIWrapper({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| model: 'chatgpt-image-latest', | ||
| model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest', | ||
| }); |
There was a problem hiding this comment.
Ensure that DallEAPIWrapper also uses the custom OPENAI_BASE_URL configuration if provided, so that image generation requests are routed through the same custom gateway as text generation.
const dalle = new DallEAPIWrapper({
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest',
...(process.env.OPENAI_BASE_URL
? { configuration: { baseURL: process.env.OPENAI_BASE_URL } }
: {}),
});| const dalle = new DallEAPIWrapper({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| model: 'chatgpt-image-latest', | ||
| model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest', | ||
| }); |
There was a problem hiding this comment.
Ensure that DallEAPIWrapper also uses the custom OPENAI_BASE_URL configuration if provided, so that image generation requests are routed through the same custom gateway as text generation.
const dalle = new DallEAPIWrapper({
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest',
...(process.env.OPENAI_BASE_URL
? { configuration: { baseURL: process.env.OPENAI_BASE_URL } }
: {}),
});| const model = new ChatOpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| model: 'gpt-4.1', | ||
| model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', | ||
| temperature: 0.7, | ||
| ...(process.env.OPENAI_BASE_URL | ||
| ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } | ||
| : {}), | ||
| }); |
There was a problem hiding this comment.
Instantiating ChatOpenAI at the module level means it is evaluated when the file is imported. If environment variables (like OPENAI_API_KEY or OPENAI_BASE_URL) are loaded asynchronously or after this module is imported (which is common in NestJS bootstrap), this client will be initialized with fallback values and won't pick up the actual environment variables. Consider refactoring this to be lazily initialized or injected via NestJS dependency injection, similar to how OpenaiService was refactored.
| const model = new ChatOpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| model: 'gpt-4.1', | ||
| model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', | ||
| temperature: 0.7, | ||
| ...(process.env.OPENAI_BASE_URL | ||
| ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } | ||
| : {}), | ||
| }); |
There was a problem hiding this comment.
Instantiating ChatOpenAI at the module level means it is evaluated when the file is imported. If environment variables (like OPENAI_API_KEY or OPENAI_BASE_URL) are loaded asynchronously or after this module is imported (which is common in NestJS bootstrap), this client will be initialized with fallback values and won't pick up the actual environment variables. Consider refactoring this to be lazily initialized or injected via NestJS dependency injection, similar to how OpenaiService was refactored.
| const model = new ChatOpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| model: 'gpt-4o-2024-08-06', | ||
| model: process.env.OPENAI_MODEL_NAME || 'gpt-4o-2024-08-06', | ||
| temperature: 0, | ||
| ...(process.env.OPENAI_BASE_URL | ||
| ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } | ||
| : {}), | ||
| }); |
There was a problem hiding this comment.
Instantiating ChatOpenAI at the module level means it is evaluated when the file is imported. If environment variables (like OPENAI_API_KEY or OPENAI_BASE_URL) are loaded asynchronously or after this module is imported (which is common in NestJS bootstrap), this client will be initialized with fallback values and won't pick up the actual environment variables. Consider refactoring this to be lazily initialized or injected via NestJS dependency injection, similar to how OpenaiService was refactored.
| const openai = new OpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}), | ||
| }); |
There was a problem hiding this comment.
The early return check on lines 42-48 strictly requires OPENAI_API_KEY to be set. However, some OpenAI-compatible endpoints (like local Ollama or vLLM instances) do not require an API key. To fully support custom endpoints, consider updating the early return check to also allow when OPENAI_BASE_URL is set, and adjust the apiKey fallback accordingly.
| const openai = new OpenAI({ | ||
| apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', | ||
| ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}), | ||
| }); |
There was a problem hiding this comment.
The early return check on lines 74-80 strictly requires OPENAI_API_KEY to be set. However, some OpenAI-compatible endpoints (like local Ollama or vLLM instances) do not require an API key. To fully support custom endpoints, consider updating the early return check to also allow when OPENAI_BASE_URL is set, and adjust the apiKey fallback accordingly.
What kind of change does this PR introduce?
Feature & Configuration Standard
Why was this change needed?
Adds support for OpenAI-compatible API endpoints (e.g. AI Gateways, LiteLLM, Ollama, DeepSeek, vLLM, Azure OpenAI) and standardizes the full environment configuration reference (
.env.example&.env):OPENAI_BASE_URL): Enables custom OpenAI baseURL across all AI service layers (OpenaiService,CopilotController,AgentGraphService,AutopostService).OPENAI_MODEL_NAME,OPENAI_IMAGE_MODEL): Allows flexible model configuration without hardcodinggpt-4.1orchatgpt-image-latest..env.examplewith clear documentation and sample placeholders, strictly matching variable order.Technical Details & Scope
libraries/nestjs-libraries/src/openai/openai.service.ts: DynamicOpenAIclient instantiation withbaseURLand custom model options.apps/backend/src/api/routes/copilot.controller.ts: Configured CopilotKit runtime with customOpenAIclient and model name.libraries/nestjs-libraries/src/agent/agent.graph.service.ts: Passedconfiguration.baseURLto LangChainChatOpenAIand configurable DALL-E image model.libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts: Passedconfiguration.baseURLtoChatOpenAI.libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts: Configured LangChainChatOpenAIandDallEAPIWrapperwith custom baseURL and models..env.example: Full 15-section reference with descriptions.Verification & Testing
pnpm --filter ./apps/backend run build): Passed 100%.pnpm dlx tsx scripts/branding-guard.ts): Passed 100%.pnpm run build:extension): Passed 100%.QA
pnpm run buildfrom repo root to verify clean TypeScript compilation.OPENAI_BASE_URL="https://api.openai.com/v1"andOPENAI_MODEL_NAME="gpt-4o-mini"in.env.Checklist:
pnpm run build).pnpm dlx tsx scripts/branding-guard.ts).Note
Medium Risk
Touches all primary AI call paths; misconfigured
OPENAI_BASE_URLor model names could break generation/copilot at runtime, though behavior is unchanged when env vars are unset.Overview
Adds configurable OpenAI-compatible AI routing across copilot, agents, autopost, and
OpenaiService: optionalOPENAI_BASE_URL, plusOPENAI_MODEL_NAMEandOPENAI_IMAGE_MODELinstead of hardcodedgpt-4.1/chatgpt-image-latest. CopilotKit now uses an explicitOpenAIclient; LangChainChatOpenAIpicks upconfiguration.baseURLwhen set;OpenaiServicebuilds the client per call via helpers rather than a single module-level instance..env.exampleis rewritten into a 15-section Crove/Postiz configuration reference with documented placeholders (core DB/Redis, Temporal, storage, email, brand, DOS ID OAuth, ecosystem bootstrap, AI keys, social providers, Stripe, telemetry, etc.) and sample defaults such asAPI_LIMIT="100"and DOS-oriented OAuth URLs.Reviewed by Cursor Bugbot for commit e0b574a. Configure here.