Skip to content

feat(ai): support OpenAI-compatible API endpoints and sync complete .env.example - #28

Merged
JOY (JOY) merged 1 commit into
mainfrom
dev
Sep 1, 2026
Merged

feat(ai): support OpenAI-compatible API endpoints and sync complete .env.example#28
JOY (JOY) merged 1 commit into
mainfrom
dev

Conversation

@JOY

@JOY JOY (JOY) commented Sep 1, 2026

Copy link
Copy Markdown

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):

  1. OpenAI-Compatible Custom Endpoint (OPENAI_BASE_URL): Enables custom OpenAI baseURL across all AI service layers (OpenaiService, CopilotController, AgentGraphService, AutopostService).
  2. Model Name Overrides (OPENAI_MODEL_NAME, OPENAI_IMAGE_MODEL): Allows flexible model configuration without hardcoding gpt-4.1 or chatgpt-image-latest.
  3. Comprehensive Environment Reference: Synchronized all 15 configuration sections in .env.example with clear documentation and sample placeholders, strictly matching variable order.

Technical Details & Scope

  • libraries/nestjs-libraries/src/openai/openai.service.ts: Dynamic OpenAI client instantiation with baseURL and custom model options.
  • apps/backend/src/api/routes/copilot.controller.ts: Configured CopilotKit runtime with custom OpenAI client and model name.
  • libraries/nestjs-libraries/src/agent/agent.graph.service.ts: Passed configuration.baseURL to LangChain ChatOpenAI and configurable DALL-E image model.
  • libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts: Passed configuration.baseURL to ChatOpenAI.
  • libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts: Configured LangChain ChatOpenAI and DallEAPIWrapper with custom baseURL and models.
  • .env.example: Full 15-section reference with descriptions.

Verification & Testing

  • Backend TypeScript compilation (pnpm --filter ./apps/backend run build): Passed 100%.
  • Branding Guard (pnpm dlx tsx scripts/branding-guard.ts): Passed 100%.
  • Extension build (pnpm run build:extension): Passed 100%.

QA

  1. Run pnpm run build from repo root to verify clean TypeScript compilation.
  2. Set OPENAI_BASE_URL="https://api.openai.com/v1" and OPENAI_MODEL_NAME="gpt-4o-mini" in .env.
  3. Trigger AI post generation or copilot chat and verify requests route to the specified baseURL.

Checklist:

  • My code follows the project's code style and architectural conventions.
  • Local build passes (pnpm run build).
  • Branding guard validation passes (pnpm dlx tsx scripts/branding-guard.ts).
  • Tests and typecheck have been verified without errors.
  • Documentation has been updated (if applicable).
  • No secrets or sensitive credentials are included in this PR.
  • I have filled in the QA / Verification section above with real steps to verify this change.

Note

Medium Risk
Touches all primary AI call paths; misconfigured OPENAI_BASE_URL or 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: optional OPENAI_BASE_URL, plus OPENAI_MODEL_NAME and OPENAI_IMAGE_MODEL instead of hardcoded gpt-4.1 / chatgpt-image-latest. CopilotKit now uses an explicit OpenAI client; LangChain ChatOpenAI picks up configuration.baseURL when set; OpenaiService builds the client per call via helpers rather than a single module-level instance.

.env.example is 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 as API_LIMIT="100" and DOS-oriented OAuth URLs.

Reviewed by Cursor Bugbot for commit e0b574a. Configure here.

…PENAI_MODEL_NAME, OPENAI_IMAGE_MODEL) and sync complete .env.example
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@JOY
JOY (JOY) merged commit f82132d into main Sep 1, 2026
19 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment on lines 31 to 37
const generate = (
await openai.images.generate({
prompt,
model: 'chatgpt-image-latest',
model: getImageModel(),
size: isVertical ? '1024x1536' : '1024x1024',
})
).data[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Use optional chaining and a fallback object when accessing data[0] to prevent potential runtime crashes if the API returns an empty data array.

Suggested change
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] || {};

Comment on lines 34 to 37
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',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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 } }
    : {}),
});

Comment on lines 47 to 50
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',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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 } }
    : {}),
});

Comment on lines 25 to 32
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 } }
: {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Comment on lines 38 to 45
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 } }
: {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Comment on lines 11 to 18
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 } }
: {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Comment on lines +50 to +53
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Comment on lines +101 to +104
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant