Skip to content

Conversation

@vikasagarwal101
Copy link

@vikasagarwal101 vikasagarwal101 commented Jan 10, 2026

Summary

This PR adds full support for custom Anthropic-compatible API endpoints, enabling users to use Zhipu AI (GLM-4.7) and MiniMax models as AI providers in Automaker.

Features Added

Custom Endpoint Provider System

  • Zhipu AI support: GLM-4.7 and other GLM models via https://api.z.ai/api/anthropic
  • MiniMax support: MiniMax-2.1 and other MiniMax models via https://api.minimax.io/anthropic
  • Manual configuration: Support for any Anthropic-compatible endpoint

Per-Provider API Key Storage

  • Separate API keys stored for each provider (Zhipu, MiniMax, Manual)
  • Switching between providers automatically loads the correct credentials
  • Dynamic endpoint selection based on model name

Model Selection Integration

  • Custom models appear in ModelSelector and PhaseModelSelector
  • Models prefixed with custom- (e.g., custom-glm-4.7)
  • Automatic prefix stripping when sending to API

Technical Changes

Provider System

  • Added CustomProvider for Anthropic-compatible endpoints
  • Added custom to ModelProvider type
  • Added isCustomModel() utility function
  • Fixed API path to /v1/messages for Anthropic compatibility

UI Components

  • Added CustomSettingsTab for provider configuration
  • Added provider presets with auto-fill for Zhipu AI and MiniMax
  • Integrated custom models into model selector components
  • Updated feature dialogs to pass custom endpoint configuration

State Management

  • Added CustomEndpointConfigs for per-provider storage
  • Added customEndpointConfigs to app store with persistence
  • Added actions: setCustomEndpointForProvider, getCustomEndpointForProvider

Configuration Flow

  • Custom endpoint config flows from UI → Feature Data → Server → CustomProvider
  • getEndpointForModel() maps model names to correct provider endpoints

Related Issue

Closes #372

Testing

  • Tested Zhipu AI GLM-4.7 model execution
  • Tested MiniMax model execution
  • Verified correct API keys used per provider
  • Build passes (npm run build)
  • Format check passes (npm run format:check)

Summary by CodeRabbit

  • New Features

    • Support for custom Anthropic-compatible endpoints (Zhipu, MiniMax, manual) with per-provider configs, test connection, and per-request overrides.
    • UI: Custom provider toggle, globe icon, dynamic custom model lists, and a new Custom settings tab to configure/save/test endpoints and default model.
    • Feature metadata and agent runs now carry custom endpoint info for per-feature and automated flows.
  • Chores

    • Added a build dependency.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

Warning

Rate limit exceeded

@vikasagarwal101 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 42 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 03ec885 and cd53762.

📒 Files selected for processing (4)
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • package.json
📝 Walkthrough

Walkthrough

Adds a CustomProvider for Anthropic-compatible endpoints and wires support end-to-end: provider implementation, factory wiring, types, store persistence, UI settings/presets, model selector integration, feature payload plumbing, and streaming request handling.

Changes

Cohort / File(s) Summary
Backend: Custom provider implementation
apps/server/src/providers/custom-provider.ts, apps/server/src/providers/index.ts
New CustomProvider and CustomEndpointConfig; Anthropic-like message/stream types; executeQuery streams SSE from {baseUrl}/v1/messages, request/model normalization, validation, detectInstallation, feature support methods; exported from providers index.
Backend: Provider factory & execution wiring
apps/server/src/providers/provider-factory.ts
ProviderRegistration.factory now accepts ProviderOptions; ProviderFactory methods accept options (including customEndpoint); CustomProvider registered; disconnection handling updated and provider resolution adjusted.
Backend: AutoMode plumbing
apps/server/src/services/auto-mode-service.ts
ExecuteOptions extended with customEndpoint?; customEndpoint threaded through runAgent/agent execution, plan revisions, continuations, and task forks.
Frontend: Settings UI & presets
apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts, apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx, apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
New presets (Zhipu, MiniMax, manual), helpers to infer/resolve endpoints/models, CustomSettingsTab UI with save/clear/test flows, and new Custom tab in provider settings.
Frontend: Model selection & defaults
apps/ui/src/components/views/board-view/shared/model-constants.ts, apps/ui/src/components/views/board-view/shared/model-selector.tsx, apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
getCustomModels() generates custom-{id} model options; UI adds Custom provider button, Custom Models section, warnings when endpoint unconfigured, and settings popover group for custom models.
Frontend: Feature dialogs
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx, apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
FeatureData / dialog payloads gain optional customEndpoint; dialogs compute per-feature endpoint via presets, per-provider configs, global/manual fallback, and strip custom- prefix when sending.
Frontend: Store & shared types
apps/ui/src/store/app-store.ts, apps/ui/src/components/views/settings-view/shared/types.ts
AppState/AppActions gain customEndpoint and customEndpointConfigs; actions setCustomEndpoint, clearCustomEndpoint, setCustomEndpointForProvider, getCustomEndpointForProvider implemented; persistence/merge updated.
Type library: provider & model utilities
libs/types/src/provider.ts, libs/types/src/settings.ts, libs/types/src/provider-utils.ts, libs/types/src/index.ts
ExecuteOptions adds customEndpoint?; ModelProvider adds 'custom'; PROVIDER_PREFIXES.custom, isCustomModel() added and exported; provider resolution order adjusted.
Model resolution
libs/model-resolver/src/resolver.ts
resolveModelString early-returns custom- prefixed keys unchanged.
Feature typing
libs/types/src/feature.ts
Feature interface adds optional customEndpoint field.
Misc / deps
package.json
Added dependency lightningcss@^1.30.2.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Store
    participant Server
    participant Endpoint

    User->>UI: Configure custom endpoint (provider/baseUrl/apiKey/model)
    UI->>Store: setCustomEndpointForProvider(...)
    Store-->>UI: persisted

    User->>UI: Select custom model / run feature
    UI->>Server: POST executeFeature (ExecuteOptions with customEndpoint)
    Server->>Server: ProviderFactory.getProviderForModel('custom-...', { customEndpoint })
    Server->>Server: instantiate CustomProvider with config
    Server->>Endpoint: POST {baseUrl}/v1/messages (stream)
    Endpoint-->>Server: SSE events (content_block_delta / message_stop / error)
    Server->>UI: stream ProviderMessage chunks to client
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement, Ready-To-Merge

Poem

🐰
I hopped through code and found a door,
Custom endpoints promising more,
Zhipu, MiniMax, manual too,
Models wearing custom- like morning dew,
Hoppity — streams and routes brand new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding custom endpoint provider support for Zhipu AI and MiniMax models, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #372: adds custom provider with Zhipu/MiniMax presets [#372], implements Anthropic-compatible endpoint integration [#372], adds custom model utilities and type definitions [#372], and provides UI configuration with model selection [#372].
Out of Scope Changes check ✅ Passed All changes are directly scoped to custom endpoint support: backend provider implementation, UI dialogs and settings tabs, type definitions, and store state management. The lightningcss dependency addition appears to be incidental.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @vikasagarwal101, 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 significantly enhances the platform's flexibility by allowing users to connect to and utilize various Anthropic-compatible AI models beyond the default offerings. By introducing a robust custom endpoint provider system, it empowers users to leverage models like Zhipu AI's GLM series and MiniMax, managing their configurations and API keys independently for each, and integrating these custom models directly into the existing model selection workflows.

Highlights

  • Custom Endpoint Provider System: Introduced a new provider system that supports Anthropic-compatible API endpoints, specifically enabling integration with Zhipu AI (GLM-4.7) and MiniMax models, as well as any other manually configured compatible endpoint.
  • Per-Provider API Key Storage: Implemented separate API key storage for each custom provider (Zhipu, MiniMax, Manual), ensuring that switching between providers automatically loads the correct credentials and dynamically selects the appropriate endpoint based on the model name.
  • Model Selection Integration: Custom models are now seamlessly integrated into the ModelSelector and PhaseModelSelector UI components, appearing with a 'custom-' prefix (e.g., 'custom-glm-4.7'), which is automatically stripped before sending requests to the API.

🧠 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.

Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

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 introduces significant new functionality by adding support for custom Anthropic-compatible API endpoints, specifically for Zhipu AI and MiniMax models. The changes are comprehensive, spanning the server-side provider logic, UI components for configuration and model selection, and updates to the shared type definitions and state management. The implementation is robust, with good error handling in the new CustomProvider and a well-designed per-provider configuration system. My review focuses on improving code maintainability by reducing duplication, enhancing type safety, and fixing a potential bug in the model aggregation logic. I've also noted a minor configuration issue in package-lock.json that could affect other developers.

/**
* All available models (Claude + Cursor + Custom)
*/
export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS];
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The ALL_MODELS constant is not updated to include the new custom models from getCustomModels(), which contradicts the comment on line 81. This will cause custom models to be missing from any UI component that relies on ALL_MODELS. You should include the custom models in this array to fix this.

Suggested change
export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS];
export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS, ...getCustomModels()];

Comment on lines 129 to 139
if (Array.isArray(prompt)) {
messages.push({
role: 'user',
content: prompt,
});
} else {
messages.push({
role: 'user',
content: prompt,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This if/else block is redundant as both branches execute the exact same code. You can simplify this by removing the conditional and just having a single messages.push(...) call.

    messages.push({
      role: 'user',
      content: prompt,
    });

Comment on lines 581 to 590
customEndpoint: feature.customEndpoint as
| { baseUrl: string; apiKey: string; model: string }
| undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The type assertion here (as ...) suggests a potential type mismatch. The feature object is being accessed for a customEndpoint property that is not defined on its type (Feature from @automaker/types). While this may work at runtime because the property is added dynamically, it bypasses TypeScript's type safety. For better maintainability and type safety, consider adding the optional customEndpoint property to the Feature type definition in the @automaker/types package.

Comment on lines +282 to +388
customEndpoint: (() => {
if (!isCustomModel(selectedModel)) return undefined;

// Get the correct endpoint for this specific model
const modelEndpoint = getEndpointForModel(selectedModel);

// 1. Check strict per-provider config first (new way)
if (modelEndpoint && customEndpointConfigs) {
const providerConfig =
customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
if (providerConfig?.apiKey) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: providerConfig.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}

// 2. Fallback to old global customEndpoint if provider matches
if (modelEndpoint && customEndpoint?.apiKey) {
if (customEndpoint.provider === modelEndpoint.provider) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}

// 3. Fallback to global manual config
if (customEndpoint) {
return {
baseUrl: customEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: customEndpoint.model,
};
}

return undefined;
})(),
} as FeatureData;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This logic for determining the customEndpoint configuration is quite complex and is duplicated in EditFeatureDialog. To improve maintainability and reduce code duplication, consider extracting this logic into a reusable helper function. This function could be placed in a shared utility file, for example apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts.

Comment on lines 79 to 148
/**
* Get the provider for a model string
*
* @param model - Model string to check
* @returns The provider type, defaults to 'claude' for unknown models
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The JSDoc for the getModelProvider function is duplicated. Please remove the redundant block to improve code clarity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)

67-82: Missing customEndpoint in onUpdate type signature.

The updates object passed to onUpdate includes customEndpoint (lines 185-227), but the type signature doesn't include it. This causes a type mismatch.

Suggested fix
 onUpdate: (
   featureId: string,
   updates: {
     title: string;
     category: string;
     description: string;
     skipTests: boolean;
     model: ModelAlias;
     thinkingLevel: ThinkingLevel;
     imagePaths: DescriptionImagePath[];
     textFilePaths: DescriptionTextFilePath[];
     branchName: string;
     priority: number;
     planningMode: PlanningMode;
     requirePlanApproval: boolean;
+    customEndpoint?: {
+      baseUrl: string;
+      apiKey: string;
+      model: string;
+    };
   }
 ) => void;
apps/server/src/services/auto-mode-service.ts (1)

2414-2422: Custom provider will break in planning “revision / per-task / continuation” calls (customEndpoint not forwarded).

provider.executeQuery(...) is invoked directly in the plan revision loop and in the task/continuation streams without customEndpoint. Since ProviderFactory.getProviderForModel(finalModel) is called without ProviderOptions, CustomProvider won’t have constructor config either—so these calls will throw “Custom endpoint not configured”.

Proposed fix (always pass customEndpoint to all internal executeQuery calls)
-                        const revisionStream = provider.executeQuery({
+                        const revisionStream = provider.executeQuery({
                           prompt: revisionPrompt,
                           model: finalModel,
                           maxTurns: maxTurns || 100,
                           cwd: workDir,
                           allowedTools: allowedTools,
                           abortController,
                           mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
+                          customEndpoint: options?.customEndpoint,
                         });

...
-                    const taskStream = provider.executeQuery({
+                    const taskStream = provider.executeQuery({
                       prompt: taskPrompt,
                       model: finalModel,
                       maxTurns: Math.min(maxTurns || 100, 50), // Limit turns per task
                       cwd: workDir,
                       allowedTools: allowedTools,
                       abortController,
                       mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
+                      customEndpoint: options?.customEndpoint,
                     });

...
-                  const continuationStream = provider.executeQuery({
+                  const continuationStream = provider.executeQuery({
                     prompt: continuationPrompt,
                     model: finalModel,
                     maxTurns: maxTurns,
                     cwd: workDir,
                     allowedTools: allowedTools,
                     abortController,
                     mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
+                    customEndpoint: options?.customEndpoint,
                   });

Also applies to: 2552-2560, 2640-2648

apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

22-48: Update model-selector to check per-provider custom endpoint configs instead of relying on singular customEndpoint.

When switching to a custom provider, the component should determine which provider the selected model belongs to and check customEndpointConfigs[provider] for configuration (similar to add-feature-dialog and edit-feature-dialog). Currently at line 39 it uses customEndpoint?.model, and at line 142 the warning checks !customEndpoint — both should be wired to the active provider's config from customEndpointConfigs. This prevents incorrect warnings or default models when using the new per-provider configuration structure.

🤖 Fix all issues with AI agents
In @apps/server/src/providers/custom-provider.ts:
- Around line 102-285: The executeQuery implementation needs abort wiring,
robust URL joining, and tolerant SSE parsing: pass
options.abortController?.signal into the fetch call (use the provided
AbortController signal) and listen for abort to call reader.cancel() /
response.body?.cancel() and break the stream loop so upstream fetch is
terminated; normalize apiUrl by trimming trailing slashes from baseUrl before
appending '/v1/messages' (e.g., derive apiUrl from baseUrl.replace(/\/+$/, '') +
'/v1/messages') to avoid double-slashes; and make the SSE parser resilient by
splitting buffer on /\r?\n/, ignoring empty/comment lines (lines starting with
':'), accepting both 'data:' and 'data:' without a space (trim the payload), and
handling AbortError in the catch path so execution stops cleanly; locate these
changes in executeQuery around the apiUrl/fetch call, the reader loop, and the
SSE parsing logic.

In @apps/ui/src/components/views/board-view/shared/model-constants.ts:
- Around line 80-83: The comment above ALL_MODELS is misleading: it says "All
available models (Claude + Cursor + Custom)" but ALL_MODELS only spreads
CLAUDE_MODELS and CURSOR_MODELS while custom models are loaded dynamically via
getCustomModels(); update the comment to accurately describe that ALL_MODELS
contains only Claude and Cursor presets and that custom models are fetched
separately using getCustomModels(), or alternatively include a note/instruction
pointing to getCustomModels() so readers know custom models are not in this
constant.

In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 133-139: The code only checks global customEndpoint for
availability which hides per-provider custom models; update the useAppStore()
destructuring to include customEndpointConfigs (so the component has access to
per-provider settings) and change the availableCustomModels useMemo to return
getCustomModels() when either the global customEndpoint has baseUrl+apiKey OR
when per-provider configs in customEndpointConfigs indicate valid credentials
(match the same presence checks used in add-feature-dialog.tsx for providers
like zhipu/minimax); keep the dependency array updated to include
customEndpointConfigs so the memo recalculates when provider configs change.

In
@apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx:
- Around line 101-145: The handleTestConnection function builds testUrl but
calls fetch(baseUrl) and treats any non-network error as success; update it to
call fetch(testUrl) and properly inspect response: if response is null or
network-failed, setTestStatus('error') and setTestMessage accordingly; if
response.status is 401 or 403 treat as auth failure and setTestStatus('error')
with a clear auth message; if response.status is 404 or 405 attempt a fallback
dummy POST to testUrl (or mark as success only if that POST returns 200-299);
only call setTestStatus('success') when a tested request returns ok; keep the
existing try/catch and ensure response.ok and response.status drive
setTestStatus/setTestMessage logic.

In @apps/ui/src/store/app-store.ts:
- Around line 4-7: setCustomEndpointForProvider currently updates
customEndpointConfigs but not the singular customEndpoint, while ModelSelector
reads customEndpoint, causing stale "not configured" warnings; update
setCustomEndpointForProvider to also set or sync the singular customEndpoint
when the active provider matches the provider being updated (or change
ModelSelector to prefer customEndpointConfigs[activeProvider] over
customEndpoint), and ensure the code paths that clear or remove provider entries
also clear the singular customEndpoint to avoid mismatches; additionally,
document in the store README (or add a comment near the Zustand persist config)
that API keys are stored client-side and recommend encryption or a different
storage strategy if the threat model requires it.

In @libs/types/src/provider.ts:
- Around line 93-101: The inline customEndpoint type in libs/types (the
customEndpoint property) is missing the required provider field — add a
provider: 'zhipu' | 'minimax' | 'manual' to the customEndpoint type or replace
the inline definition by importing and using the shared CustomEndpointConfig
type so the customEndpoint shape (provider, baseUrl, apiKey, model) matches the
server's implementation and stays synchronized with CustomEndpointConfig.

In @package.json:
- Line 64: The package "lightningcss" is an unused production dependency; remove
the "lightningcss": "^1.30.2" entry from package.json's dependencies, update the
lockfile (run npm install or npm prune && npm install or yarn install as
appropriate), run the test/build to ensure nothing breaks, and commit the
package.json and updated lockfile; also verify it's not referenced anywhere else
(imports or build scripts) before merging.
🧹 Nitpick comments (3)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)

1-1: Consider importing ThinkingLevel from @automaker/types instead.

Per coding guidelines, prefer importing from shared packages (@automaker/*) over local paths. ThinkingLevel is exported from @automaker/types via libs/types/src/settings.ts.

Suggested change
-import type { ThinkingLevel } from '@/store/app-store';
+import type { ThinkingLevel } from '@automaker/types';
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (2)

280-323: Extract endpoint resolution logic to reduce duplication and improve maintainability.

This IIFE contains complex 3-tier fallback logic for resolving custom endpoints. The same pattern appears in edit-feature-dialog.tsx (lines 185-227). Consider extracting to a shared helper function.

Suggested helper extraction

Create a helper in a shared location (e.g., custom-provider-presets.ts):

export function resolveCustomEndpoint(
  selectedModel: string,
  customEndpointConfigs: CustomEndpointConfigs | undefined,
  customEndpoint: CustomEndpointConfig | undefined
): { baseUrl: string; apiKey: string; model: string } | undefined {
  if (!isCustomModel(selectedModel)) return undefined;

  const modelEndpoint = getEndpointForModel(selectedModel);
  const strippedModel = selectedModel.startsWith('custom-') 
    ? selectedModel.slice(7) 
    : selectedModel;

  // 1. Per-provider config
  if (modelEndpoint && customEndpointConfigs) {
    const providerConfig = customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
    if (providerConfig?.apiKey) {
      return {
        baseUrl: modelEndpoint.baseUrl,
        apiKey: providerConfig.apiKey,
        model: strippedModel,
      };
    }
  }

  // 2. Global config (matching provider)
  if (modelEndpoint && customEndpoint?.apiKey && customEndpoint.provider === modelEndpoint.provider) {
    return {
      baseUrl: modelEndpoint.baseUrl,
      apiKey: customEndpoint.apiKey,
      model: strippedModel,
    };
  }

  // 3. Global manual config
  if (customEndpoint) {
    return {
      baseUrl: customEndpoint.baseUrl,
      apiKey: customEndpoint.apiKey,
      model: customEndpoint.model,
    };
  }

  return undefined;
}

Then use in both dialogs:

-customEndpoint: (() => {
-  // ... 40 lines of logic
-})(),
+customEndpoint: resolveCustomEndpoint(selectedModel, customEndpointConfigs, customEndpoint),

296-296: Use stripProviderPrefix instead of hardcoded slice.

The magic number 7 (length of "custom-") is fragile. stripProviderPrefix from @automaker/types is already imported elsewhere in this codebase for this purpose and handles edge cases.

Suggested change
+import { stripProviderPrefix } from '@automaker/types';
 // ... in the resolution logic:
-model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
+model: stripProviderPrefix(selectedModel),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 299b838 and ee96e16.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • apps/server/src/providers/custom-provider.ts
  • apps/server/src/providers/index.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/ui/src/store/app-store.ts
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/index.ts
  • libs/types/src/provider-utils.ts
  • libs/types/src/provider.ts
  • libs/types/src/settings.ts
  • package.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/index.ts
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/server/src/providers/index.ts
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/settings.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/custom-provider.ts
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • libs/types/src/provider.ts
  • libs/types/src/provider-utils.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/providers/provider-factory.ts
  • apps/ui/src/store/app-store.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/index.ts
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/server/src/providers/index.ts
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/settings.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/custom-provider.ts
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • libs/types/src/provider.ts
  • libs/types/src/provider-utils.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/providers/provider-factory.ts
  • apps/ui/src/store/app-store.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/index.ts
  • apps/server/src/providers/custom-provider.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/providers/provider-factory.ts
🧠 Learnings (3)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
🧬 Code graph analysis (8)
apps/ui/src/components/views/settings-view/shared/types.ts (2)
apps/server/src/providers/custom-provider.ts (1)
  • CustomEndpointConfig (23-32)
apps/server/src/providers/index.ts (1)
  • CustomEndpointConfig (29-29)
apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx (2)
apps/ui/src/components/views/settings-view/providers/index.ts (1)
  • ProviderTabs (1-1)
apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx (1)
  • CustomSettingsTab (13-290)
libs/model-resolver/src/resolver.ts (2)
libs/types/src/index.ts (1)
  • PROVIDER_PREFIXES (150-150)
libs/types/src/provider-utils.ts (1)
  • PROVIDER_PREFIXES (14-18)
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (3)
libs/types/src/index.ts (1)
  • isCustomModel (153-153)
libs/types/src/provider-utils.ts (1)
  • isCustomModel (62-71)
apps/ui/tests/utils/git/worktree.ts (1)
  • FeatureData (24-31)
apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)
libs/types/src/provider-utils.ts (1)
  • isCustomModel (62-71)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (2)
libs/types/src/provider-utils.ts (1)
  • PROVIDER_PREFIXES (14-18)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • getCustomModels (62-78)
apps/server/src/providers/provider-factory.ts (2)
apps/server/src/providers/custom-provider.ts (2)
  • CustomEndpointConfig (23-32)
  • CustomProvider (85-379)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • CustomEndpointConfig (38-43)
apps/ui/src/store/app-store.ts (2)
apps/server/src/providers/custom-provider.ts (1)
  • CustomEndpointConfig (23-32)
apps/ui/src/components/views/settings-view/shared/types.ts (2)
  • CustomEndpointConfig (38-43)
  • CustomEndpointConfigs (46-62)
🔇 Additional comments (14)
libs/types/src/provider-utils.ts (4)

14-18: LGTM! Clean addition of custom provider prefix.

The custom provider prefix follows the established pattern and is properly documented.


56-71: LGTM! Custom model detection is correctly implemented.

The function appropriately checks for the custom- prefix. Unlike isCursorModel(), it doesn't check for bare model IDs, which is correct since custom models are user-defined and require an explicit prefix for routing.


85-93: LGTM! Provider detection correctly prioritizes custom models.

The custom model check is appropriately placed first in the chain, ensuring custom-prefixed models are routed to the custom provider before falling through to cursor or claude detection.


128-143: LGTM! Prefix addition logic is consistent.

The custom provider prefix handling mirrors the cursor provider pattern and correctly prevents double-prefixing.

libs/types/src/index.ts (1)

149-159: LGTM! Export correctly added.

The isCustomModel export is properly placed alongside other provider utilities.

libs/model-resolver/src/resolver.ts (1)

69-74: LGTM! Custom model resolution correctly integrated.

The custom model handling is properly positioned in the resolution chain and follows the same pass-through pattern as cursor models. The log statement aids debugging, and the comment clearly documents that prefix stripping occurs in CustomProvider.

apps/server/src/providers/index.ts (1)

28-29: LGTM! Provider exports properly added.

The CustomProvider and CustomEndpointConfig exports are well-documented and follow the established pattern for provider exports.

libs/types/src/settings.ts (1)

98-98: LGTM!

The ModelProvider type correctly extended to include the new 'custom' provider option, aligning with the PR objectives.

apps/ui/src/components/views/board-view/shared/model-constants.ts (1)

62-78: LGTM!

The getCustomModels() function correctly builds dynamic model options from presets, appropriately filtering out the 'manual' provider and applying the custom- prefix for provider routing.

apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx (1)

1-45: LGTM!

Clean implementation of the Custom tab integration:

  • Grid layout properly updated to 3 columns
  • Tab trigger and content correctly added
  • Props type extended appropriately
  • Both named and default exports provided for flexibility
apps/ui/src/components/views/settings-view/shared/types.ts (1)

46-62: LGTM!

Well-designed per-provider configuration structure. The distinction between preset providers (zhipu/minimax with optional model) and manual (requiring all fields) correctly reflects the different configuration requirements.

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)

645-684: LGTM!

Clean implementation of custom model rendering in the selector. The selection check correctly handles both prefixed and non-prefixed model IDs.

apps/server/src/providers/provider-factory.ts (1)

11-34: ProviderOptions plumbing looks good; verify “installation/status” callers won’t misinterpret CustomProvider as always uninstalled.

getAllProviders() / checkAllProviders() instantiate providers without options, so CustomProvider.detectInstallation() will report unconfigured. If the UI uses checkAllProviders() to decide whether to show/enable “Custom”, it may look broken even when runs provide ExecuteOptions.customEndpoint.

If you want “installed/authenticated” to reflect persisted custom settings, you’ll need a way to pass ProviderOptions.customEndpoint into the status path too (or special-case Custom provider).

Also applies to: 90-104, 137-154, 179-205

apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts (1)

20-44: Verify preset baseUrls + model IDs match provider docs (these will drift).

Since these are hard-coded external endpoints/model IDs, please confirm they’re correct and stable (and consider a small doc note on expected format: baseUrl should NOT include /v1/messages, since server appends it).

Also applies to: 84-115, 121-139

Comment on lines 102 to 292
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
const { prompt, model, systemPrompt } = options;

// Get custom endpoint config - prioritize options (runtime) over constructor config
const customEndpoint = options.customEndpoint || this.customConfig;

// Validate custom endpoint configuration
if (!customEndpoint) {
throw new Error(
'Custom endpoint not configured. Please configure the endpoint in Settings > Providers > Custom.'
);
}

const { baseUrl, apiKey } = customEndpoint;

if (!baseUrl || !apiKey) {
throw new Error(
'Custom endpoint configuration is incomplete. Please provide both Base URL and API Key.'
);
}

// Build the messages array
const messages: Array<{
role: 'user' | 'assistant';
content: string | Array<{ type: string; text?: string; source?: object }>;
}> = [];

if (Array.isArray(prompt)) {
messages.push({
role: 'user',
content: prompt,
});
} else {
messages.push({
role: 'user',
content: prompt,
});
}

// Strip the 'custom-' prefix from model name for the API request
const apiModel = model.startsWith('custom-') ? model.slice(7) : model;
logger.info(`Sending request to custom endpoint: model=${apiModel}, baseUrl=${baseUrl}`);

// Build the request body
const requestBody: AnthropicMessageRequest = {
model: apiModel,
messages,
max_tokens: 4096,
stream: true,
};

if (systemPrompt) {
// Handle systemPrompt which can be a string or SystemPromptPreset object
if (typeof systemPrompt === 'string') {
requestBody.system = systemPrompt;
}
// If it's a SystemPromptPreset object, we'd need to resolve it
// For now, skip custom provider support for preset-based system prompts
}

// Make the streaming request
try {
// Use /v1/messages path (standard Anthropic API format)
const apiUrl = `${baseUrl}/v1/messages`;
logger.info(`Making request to custom endpoint: ${apiUrl}`);

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify(requestBody),
});

if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
logger.error(
`Custom endpoint error: ${response.status} ${response.statusText} - ${errorText}`
);

// Classify the error
let errorType = 'unknown';
if (response.status === 401) errorType = 'authentication_failed';
else if (response.status === 429) errorType = 'rate_limit';
else if (response.status >= 500) errorType = 'server_error';
else if (response.status >= 400) errorType = 'invalid_request';

const error = new Error(`Custom endpoint error (${response.status}): ${errorText}`);
(error as any).type = errorType;
throw error;
}

// Check if the response is actually a stream
const contentType = response.headers.get('content-type') || '';
if (
!contentType.includes('text/event-stream') &&
!contentType.includes('application/octet-stream')
) {
// API returned a non-streaming response - likely an error in JSON format
const responseText = await response.text();
logger.error(`Custom endpoint returned non-streaming response: ${responseText}`);

// Try to parse as JSON error
try {
const jsonError = JSON.parse(responseText);
throw new Error(
`Custom endpoint returned non-streaming response: ${JSON.stringify(jsonError)}`
);
} catch {
throw new Error(`Custom endpoint returned non-streaming response: ${responseText}`);
}
}

// Stream the response
const reader = response.body?.getReader();
if (!reader) {
throw new Error('No response body received from custom endpoint');
}

const decoder = new TextDecoder();
let buffer = '';

while (true) {
const { done, value } = await reader.read();
if (done) break;

buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';

for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;

try {
const event: AnthropicStreamEvent = JSON.parse(data);

// Handle different event types
if (event.type === 'content_block_delta' && event.delta?.text) {
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [
{
type: 'text',
text: event.delta.text,
},
],
},
};
} else if (event.type === 'message_stop') {
yield {
type: 'result',
subtype: 'success',
result: 'Query completed successfully',
};
}
} catch (parseError) {
logger.warn(`Failed to parse SSE data: ${data}`, parseError);
}
}
}
}
} catch (error) {
const errorInfo = classifyError(error);
const userMessage = getUserFriendlyErrorMessage(error);

logger.error('CustomProvider executeQuery error:', {
type: errorInfo.type,
message: errorInfo.message,
stack: (error as Error).stack,
});

const enhancedError = new Error(userMessage);
(enhancedError as any).originalError = error;
(enhancedError as any).type = errorInfo.type;

throw enhancedError;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing abort wiring + fragile URL join/SSE parsing.

  1. fetch() should use options.abortController?.signal, and the stream loop should stop/cancel on abort—otherwise “Stop” won’t reliably stop the upstream request.
  2. const apiUrl = \${baseUrl}/v1/messages`` will misbehave with trailing slashes. Prefer normalizing/joining.
  3. SSE parsing should handle \r\n, data: (no space), and ignore empty/comment lines.
Proposed fix (abort + safer join + more tolerant SSE parsing)
-      const apiUrl = `${baseUrl}/v1/messages`;
+      const normalizedBaseUrl = baseUrl.replace(/\/+$/, '') + '/';
+      const apiUrl = new URL('v1/messages', normalizedBaseUrl).toString();

       const response = await fetch(apiUrl, {
         method: 'POST',
+        signal: options.abortController?.signal,
         headers: {
           'Content-Type': 'application/json',
           'x-api-key': apiKey,
           'anthropic-version': '2023-06-01',
         },
         body: JSON.stringify(requestBody),
       });

...
-      while (true) {
+      while (true) {
+        if (options.abortController?.signal.aborted) {
+          await reader.cancel().catch(() => {});
+          throw new Error('Aborted');
+        }
         const { done, value } = await reader.read();
         if (done) break;

         buffer += decoder.decode(value, { stream: true });
-        const lines = buffer.split('\n');
+        const lines = buffer.split(/\r?\n/);
         buffer = lines.pop() || '';

         for (const line of lines) {
-          if (line.startsWith('data: ')) {
-            const data = line.slice(6);
+          if (line.startsWith('data:')) {
+            const data = line.slice(5).trimStart();
             if (data === '[DONE]') continue;
+            if (!data) continue;
🤖 Prompt for AI Agents
In @apps/server/src/providers/custom-provider.ts around lines 102 - 285, The
executeQuery implementation needs abort wiring, robust URL joining, and tolerant
SSE parsing: pass options.abortController?.signal into the fetch call (use the
provided AbortController signal) and listen for abort to call reader.cancel() /
response.body?.cancel() and break the stream loop so upstream fetch is
terminated; normalize apiUrl by trimming trailing slashes from baseUrl before
appending '/v1/messages' (e.g., derive apiUrl from baseUrl.replace(/\/+$/, '') +
'/v1/messages') to avoid double-slashes; and make the SSE parser resilient by
splitting buffer on /\r?\n/, ignoring empty/comment lines (lines starting with
':'), accepting both 'data:' and 'data:' without a space (trim the payload), and
handling AbortError in the catch path so execution stops cleanly; locate these
changes in executeQuery around the apiUrl/fetch call, the reader loop, and the
SSE parsing logic.

Comment on lines 581 to 591
customEndpoint: feature.customEndpoint as
| { baseUrl: string; apiKey: string; model: string }
| undefined,
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l apps/server/src/services/auto-mode-service.ts

Repository: AutoMaker-Org/automaker

Length of output: 117


🏁 Script executed:

sed -n '575,590p' apps/server/src/services/auto-mode-service.ts

Repository: AutoMaker-Org/automaker

Length of output: 721


🏁 Script executed:

sed -n '1975,2000p' apps/server/src/services/auto-mode-service.ts

Repository: AutoMaker-Org/automaker

Length of output: 859


🏁 Script executed:

sed -n '2115,2140p' apps/server/src/services/auto-mode-service.ts

Repository: AutoMaker-Org/automaker

Length of output: 1230


🏁 Script executed:

# Find type definitions for Feature and ExecuteOptions
rg -A 10 "type Feature\s*=" apps/server/src/ --max-count=3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find ExecuteOptions type
rg -A 10 "type ExecuteOptions|interface ExecuteOptions" apps/server/src/ --max-count=3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Find Feature type definition
rg -B 5 -A 15 "type Feature\s*=|interface Feature" apps/server/src/ | head -50

Repository: AutoMaker-Org/automaker

Length of output: 3440


🏁 Script executed:

# Search for where feature.json is loaded
rg "feature\.json" apps/server/src/ -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 6705


🏁 Script executed:

# Search for customEndpoint validation
rg "customEndpoint" apps/server/src/services/auto-mode-service.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 744


🏁 Script executed:

# Find feature.json example or schema
find . -name "feature.json" -o -name "*feature*.json" 2>/dev/null | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check the full context around line 581 to understand feature source
sed -n '550,600p' apps/server/src/services/auto-mode-service.ts

Repository: AutoMaker-Org/automaker

Length of output: 1848


🏁 Script executed:

# Find Feature type definition in feature-loader
rg -A 20 "export type Feature|export interface Feature" apps/server/src/services/feature-loader.ts

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check what customEndpoint is in the Feature type
rg "customEndpoint" apps/server/src/services/feature-loader.ts -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Look at the full Feature type definition
sed -n '1,200p' apps/server/src/services/feature-loader.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3307


🏁 Script executed:

# Find the full Feature type
ast-grep --pattern 'export type Feature = $_'

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Search in shared packages for ExecuteOptions definition
rg -A 15 "type ExecuteOptions|interface ExecuteOptions" --max-count=5

Repository: AutoMaker-Org/automaker

Length of output: 2135


🏁 Script executed:

# Find the full Feature type in @automaker/types
find libs/types -name "*.ts" -exec grep -l "Feature" {} \;

Repository: AutoMaker-Org/automaker

Length of output: 251


🏁 Script executed:

# Look at the types file
rg "type Feature|interface Feature" libs/types/src/ -A 20

Repository: AutoMaker-Org/automaker

Length of output: 2510


🏁 Script executed:

# Get the full ExecuteOptions definition
sed -n '1,200p' libs/types/src/provider.ts | tail -100

Repository: AutoMaker-Org/automaker

Length of output: 2569


🏁 Script executed:

# Check if customEndpoint is in ExecuteOptions
rg "customEndpoint" libs/types/src/provider.ts -B 5 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 332


🏁 Script executed:

# Search for any validation of customEndpoint in the codebase
rg "customEndpoint" apps/server/src/ -B 3 -A 3 | grep -E "(validate|check|assert)" -C 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check the full Feature interface definition to confirm customEndpoint is missing
sed -n '1,100p' libs/types/src/feature.ts

Repository: AutoMaker-Org/automaker

Length of output: 1666


🏁 Script executed:

# Search for where customEndpoint might be set on Feature
rg "customEndpoint\s*=" apps/server/src/ -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 670


🏁 Script executed:

# Check if there's validation or schema enforcement for features
rg -i "schema|validate.*feature|zod|joi" apps/server/src/services/feature-loader.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 953


🏁 Script executed:

# Check if customEndpoint is documented in any feature.json examples or templates
find . -name "*.json" -type f | xargs grep -l "customEndpoint" 2>/dev/null | head -5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Search for API key handling in logs or debugging
rg "apiKey|api_key" apps/server/src/services/auto-mode-service.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 375


🏁 Script executed:

# Check how feature.json is written and if there's any filtering of customEndpoint
rg "JSON.stringify.*feature" apps/server/src/ -B 5 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 5492


🏁 Script executed:

# Look at custom-provider validation
sed -n '1,150p' apps/server/src/providers/custom-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 3975


🏁 Script executed:

# Search for any sanitization or filtering of feature data before persistence
rg "secureFs.writeFile.*feature" apps/server/src/ -B 10 -A 5

Repository: AutoMaker-Org/automaker

Length of output: 6264


Remove untyped customEndpoint cast and add explicit validation before runtime use.

feature.customEndpoint is accessed without a type definition on the Feature interface and forcibly cast with as, which bypasses validation. Since features are persisted to disk via JSON.stringify(feature) without filtering, any customEndpoint object—including API keys—can end up in feature.json files if manually added. Additionally, validation only occurs at provider runtime (inside CustomProvider.executeQuery), so incomplete or malformed data won't be caught until execution.

Required fixes:

  1. Add explicit customEndpoint?: CustomEndpointConfig type to the Feature interface (or document that it must never be persisted)
  2. Validate the shape of customEndpoint at feature load time, not provider runtime
  3. Exclude customEndpoint from feature.json serialization (filter before JSON.stringify), or explicitly retrieve it from secure settings instead

Also applies to: lines 1981-1994, 2121-2134

Comment on lines +4 to +9
import type {
CustomEndpointConfig,
CustomEndpointConfigs,
} from '@/components/views/settings-view/shared/types';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file exists and get its size
wc -l apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 102


🏁 Script executed:

# Read the import section and beginning of the file
head -50 apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 1096


🏁 Script executed:

# Search for state definition and customEndpoint property
sed -n '50,150p' apps/ui/src/store/app-store.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 3035


🏁 Script executed:

# Search for the state interface/type definition
rg -n "interface.*State|type.*State" apps/ui/src/store/app-store.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 181


🏁 Script executed:

# Read the AppState interface and surrounding code
sed -n '414,550p' apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 4935


🏁 Script executed:

# Search for setCustomEndpointForProvider method
rg -n "setCustomEndpointForProvider" apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 167


🏁 Script executed:

# Read the setCustomEndpointForProvider method implementation
sed -n '734,760p' apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 1293


🏁 Script executed:

# Read the actual implementation of setCustomEndpointForProvider around line 1403
sed -n '1399,1420p' apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 908


🏁 Script executed:

# Search for ModelSelector and how it uses customEndpoint
rg -n "ModelSelector\|customEndpoint" apps/ui/src/store/app-store.ts | head -30

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Search for ModelSelector component in the codebase
fd -type f -name "*ModelSelector*" apps/ui/src

Repository: AutoMaker-Org/automaker

Length of output: 237


🏁 Script executed:

# Search for ModelSelector component files
find apps/ui/src -name "*ModelSelector*"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Search for customEndpoint usage across the codebase
rg -l "customEndpoint" apps/ui/src --type ts --type tsx

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for customEndpoint usage across the codebase
rg "customEndpoint" apps/ui/src

Repository: AutoMaker-Org/automaker

Length of output: 5232


🏁 Script executed:

# Check the model-selector.tsx file to see the glm-4.7 default
cat apps/ui/src/components/views/board-view/shared/model-selector.tsx

Repository: AutoMaker-Org/automaker

Length of output: 10867


🏁 Script executed:

# Check the custom-settings-tab.tsx to see how it sets the endpoint
cat apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx

Repository: AutoMaker-Org/automaker

Length of output: 10450


🏁 Script executed:

# Check the edit-feature-dialog to see how it handles the mismatch
sed -n '1,100p' apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx | head -80

Repository: AutoMaker-Org/automaker

Length of output: 2445


State mismatch risk: setCustomEndpointForProvider() updates customEndpointConfigs but ModelSelector reads customEndpoint (singular), causing misconfigured warnings even when per-provider settings exist.

This store persists two separate endpoint properties:

  • customEndpoint?: CustomEndpointConfig (singular)
  • customEndpointConfigs?: CustomEndpointConfigs (per-provider)

However, setCustomEndpointForProvider() only updates customEndpointConfigs and never updates the singular customEndpoint. Since ModelSelector reads from customEndpoint, users can end up with configured per-provider endpoints but still see "not configured" warnings and fall back to 'glm-4.7'.

Additionally, API keys are persisted in client-side storage via Zustand's persist middleware—document this security tradeoff or consider encryption if your threat model requires it.

🤖 Prompt for AI Agents
In @apps/ui/src/store/app-store.ts around lines 4 - 7,
setCustomEndpointForProvider currently updates customEndpointConfigs but not the
singular customEndpoint, while ModelSelector reads customEndpoint, causing stale
"not configured" warnings; update setCustomEndpointForProvider to also set or
sync the singular customEndpoint when the active provider matches the provider
being updated (or change ModelSelector to prefer
customEndpointConfigs[activeProvider] over customEndpoint), and ensure the code
paths that clear or remove provider entries also clear the singular
customEndpoint to avoid mismatches; additionally, document in the store README
(or add a comment near the Zustand persist config) that API keys are stored
client-side and recommend encryption or a different storage strategy if the
threat model requires it.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @apps/server/src/providers/custom-provider.ts:
- Around line 156-163: The current custom provider request builder only assigns
string system prompts and skips SystemPromptPreset objects, so implement
resolving preset-based prompts: detect when systemPrompt is an object matching
SystemPromptPreset, call the existing resolver (or add a helper) to convert the
preset to a final string (e.g., resolveSystemPromptPreset or similar) and assign
that result to requestBody.system; ensure you import or reference the resolver
used elsewhere in the codebase and handle async resolution/errors consistently
with other provider flows in custom-provider.ts.
- Around line 118-124: Remove the debug console.log calls in CustomProvider that
print sensitive data: drop the console.log block that logs
baseUrl/apiKey/apiKeyPrefix/options.model and the later console.log that prints
the request body; instead use the existing logger (e.g., processLogger or
this.logger) at debug/info level and only log non-sensitive indicators (e.g.,
apiKeyPresent: !!apiKey and apiKeyLength or a fully masked token like
"<redacted>"), and never log apiKeyPrefix or raw request body—if you must record
request content, sanitize or redact sensitive fields before logging.

In @apps/ui/src/components/views/board-view/shared/model-selector.tsx:
- Around line 45-49: The code unconditionally falls back to the hard-coded
'glm-4.7' when switching to the custom provider; instead, pick a real available
custom model by checking customEndpoint?.model first, then the first entry
returned by getCustomModels(), and only as a last resort use a safe generic
fallback; update the branch handling in the provider switch (references:
provider, selectedProvider, customEndpoint, getCustomModels, defaultModel,
onModelSelect, PROVIDER_PREFIXES.custom) so onModelSelect is called with the
chosen model prefix + model name derived from customEndpoint?.model ||
getCustomModels()[0] || a minimal generic fallback.
- Around line 141-190: Update the warning text inside the Custom models block
(the JSX rendered when selectedProvider === 'custom') where the AlertTriangle
message currently reads "Custom endpoint not configured. Set it up in Settings →
Providers → Custom." to instead read "Custom endpoint not configured. Set it up
in Settings → AI Providers → Custom." so the navigation label matches the actual
UI; the change is in the Alert div text within the component
(model-selector.tsx) that renders this warning.

In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 138-149: availableCustomModels currently returns all custom models
if any provider is configured causing models from unconfigured providers to
appear; modify the React.useMemo block so after calling getCustomModels() you
filter the returned models to only include those whose provider/preset matches a
configured provider: check each model's provider identifier (e.g.,
model.provider or model.preset) and allow it only if the corresponding flag is
present in customEndpointConfigs (customEndpointConfigs.zhipu?.apiKey,
customEndpointConfigs.minimax?.apiKey, customEndpointConfigs.manual?.apiKey) or,
for legacy endpoints, if customEndpoint?.baseUrl && customEndpoint?.apiKey;
return that filtered array from availableCustomModels.
- Line 24: The import of getCustomModels should be relocated from the local
module to a shared @automaker package; update imports in
phase-model-selector.tsx (and other callers) to import getCustomModels from the
new shared package (e.g., @automaker/model-resolver) and remove the old
'@/components/views/board-view/shared/model-constants' import, ensuring the
shared package exports getCustomModels and preserving the same function
signature and usage in PhaseModelSelector (and any other components referencing
getCustomModels).
🧹 Nitpick comments (1)
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)

656-694: Custom models rendering looks good.

The implementation follows the same pattern as Cursor models with appropriate defensive selection checking. However, custom models lack the favorite functionality that Claude and Cursor models have (star button to toggle favorites).

⭐ Optional enhancement: Add favorites support for custom models

If custom models should support favorites (for consistency with other model types), you can add the favorite button similar to the Cursor model rendering:

 <div className="flex items-center gap-1 ml-2">
+  <Button
+    variant="ghost"
+    size="icon"
+    className={cn(
+      'h-6 w-6 hover:bg-transparent hover:text-yellow-500 focus:ring-0',
+      favoriteModels.includes(model.id)
+        ? 'text-yellow-500 opacity-100'
+        : 'opacity-0 group-hover:opacity-100 text-muted-foreground'
+    )}
+    onClick={(e) => {
+      e.stopPropagation();
+      toggleFavoriteModel(model.id);
+    }}
+  >
+    <Star className={cn('h-3.5 w-3.5', favoriteModels.includes(model.id) && 'fill-current')} />
+  </Button>
   {isSelected && <Check className="h-4 w-4 text-primary shrink-0" />}
 </div>

You would also need to update the favorites grouping logic (around line 243) to include custom models, similar to how Cursor models are processed.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee96e16 and 81ff1d1.

📒 Files selected for processing (8)
  • apps/server/src/providers/custom-provider.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • libs/types/src/feature.ts
  • libs/types/src/provider.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/feature.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • libs/types/src/provider.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/providers/custom-provider.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/feature.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • libs/types/src/provider.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/providers/custom-provider.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/custom-provider.ts
🧠 Learnings (2)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
🧬 Code graph analysis (1)
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (4)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • getCustomModels (62-78)
libs/types/src/provider-utils.ts (1)
  • stripProviderPrefix (105-114)
libs/types/src/index.ts (1)
  • stripProviderPrefix (155-155)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
🔇 Additional comments (11)
libs/types/src/feature.ts (1)

56-62: LGTM!

The customEndpoint property is well-structured and clearly documented. The type definition is consistent with ExecuteOptions.customEndpoint in the provider types, ensuring type safety across the feature-to-execution flow.

libs/types/src/provider.ts (1)

93-102: LGTM!

The customEndpoint property addition to ExecuteOptions is well-documented and type-safe. The structure matches the Feature.customEndpoint type, maintaining consistency across the codebase.

apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)

59-60: LGTM!

The imports correctly bring in the custom provider utilities needed for model detection and endpoint resolution.

apps/ui/src/components/views/board-view/shared/model-selector.tsx (1)

3-3: LGTM!

The imports and store integration correctly support custom provider functionality with appropriate icons and model retrieval utilities.

Also applies to: 9-9, 22-23

apps/server/src/providers/custom-provider.ts (4)

23-32: LGTM!

The CustomEndpointConfig interface is well-documented and provides a clear contract for custom endpoint configuration. The structure aligns with the types defined in the shared libs/types package.


166-223: Solid streaming implementation with proper error handling.

The executeQuery method correctly:

  • Validates endpoint configuration
  • Uses the standard Anthropic /v1/messages path
  • Handles both streaming and non-streaming error responses
  • Classifies errors appropriately (401 → auth, 429 → rate limit, etc.)
  • Provides user-friendly error messages

The SSE parsing and event handling follows the Anthropic streaming format correctly.

Also applies to: 224-292


297-313: LGTM!

The detectInstallation method appropriately checks for the presence of configuration rather than a binary installation, which makes sense for API-based providers.


326-362: LGTM!

The validateConfig method provides comprehensive validation with helpful error messages and appropriate warnings. URL validation using the URL constructor is robust.

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (3)

74-80: LGTM!

The extended store usage correctly destructures customEndpoint and customEndpointConfigs, following React patterns.


172-176: LGTM!

The custom model detection correctly checks both the stripped prefix version and the full ID, providing defensive coverage. The Globe icon is an appropriate visual indicator for custom endpoint models.


192-192: LGTM!

The dependency array correctly includes availableCustomModels since it's now used within the currentModel memo.

Comment on lines +118 to +124
console.log('[CustomProvider] Using config:', {
baseUrl,
apiKeyPresent: !!apiKey,
apiKeyLength: apiKey?.length || 0,
apiKeyPrefix: apiKey?.substring(0, 8) + '...',
model: options.model,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove debug console.log statements before production.

Lines 118-124 and 170-173 contain console.log statements that expose sensitive information:

  • Line 122: Logs the first 8 characters of the API key
  • Line 171-172: Logs a portion of the request body which may contain sensitive data

These debug statements should be removed or converted to proper logger calls with appropriate log levels.

🔒 Recommended fix
-    // Debug: Log what config we're using (mask the API key for security)
-    console.log('[CustomProvider] Using config:', {
-      baseUrl,
-      apiKeyPresent: !!apiKey,
-      apiKeyLength: apiKey?.length || 0,
-      apiKeyPrefix: apiKey?.substring(0, 8) + '...',
-      model: options.model,
-    });
+    logger.debug('Using custom endpoint config', {
+      baseUrl,
+      apiKeyPresent: !!apiKey,
+      model: options.model,
+    });

     if (!baseUrl || !apiKey) {
       throw new Error(
         'Custom endpoint configuration is incomplete. Please provide both Base URL and API Key.'
       );
     }
       // Use /v1/messages path (standard Anthropic API format)
       const apiUrl = `${baseUrl}/v1/messages`;
       logger.info(`Making request to custom endpoint: ${apiUrl}`);
-      console.log(
-        '[CustomProvider] Request body:',
-        JSON.stringify(requestBody, null, 2).substring(0, 500)
-      );
+      logger.debug('Sending request to custom endpoint', {
+        model: requestBody.model,
+        hasSystemPrompt: !!requestBody.system,
+        messageCount: requestBody.messages.length,
+      });

Also applies to: 170-173

🤖 Prompt for AI Agents
In @apps/server/src/providers/custom-provider.ts around lines 118 - 124, Remove
the debug console.log calls in CustomProvider that print sensitive data: drop
the console.log block that logs baseUrl/apiKey/apiKeyPrefix/options.model and
the later console.log that prints the request body; instead use the existing
logger (e.g., processLogger or this.logger) at debug/info level and only log
non-sensitive indicators (e.g., apiKeyPresent: !!apiKey and apiKeyLength or a
fully masked token like "<redacted>"), and never log apiKeyPrefix or raw request
body—if you must record request content, sanitize or redact sensitive fields
before logging.

Comment on lines +156 to +163
if (systemPrompt) {
// Handle systemPrompt which can be a string or SystemPromptPreset object
if (typeof systemPrompt === 'string') {
requestBody.system = systemPrompt;
}
// If it's a SystemPromptPreset object, we'd need to resolve it
// For now, skip custom provider support for preset-based system prompts
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incomplete system prompt support for preset-based prompts.

Lines 156-163 only handle string-based system prompts and explicitly skip SystemPromptPreset objects. This means features like CLAUDE.md auto-loading won't work with custom providers.

Consider implementing preset resolution or documenting this limitation in the provider's feature support.

Do you want me to generate code that resolves SystemPromptPreset objects for custom providers?

🤖 Prompt for AI Agents
In @apps/server/src/providers/custom-provider.ts around lines 156 - 163, The
current custom provider request builder only assigns string system prompts and
skips SystemPromptPreset objects, so implement resolving preset-based prompts:
detect when systemPrompt is an object matching SystemPromptPreset, call the
existing resolver (or add a helper) to convert the preset to a final string
(e.g., resolveSystemPromptPreset or similar) and assign that result to
requestBody.system; ensure you import or reference the resolver used elsewhere
in the codebase and handle async resolution/errors consistently with other
provider flows in custom-provider.ts.

Comment on lines +191 to +324
// Include customEndpoint when using a custom model
// Look up the correct endpoint based on the selected model
customEndpoint: (() => {
if (!isCustomModel(selectedModel)) return undefined;

// Get the correct endpoint for this specific model
const modelEndpoint = getEndpointForModel(selectedModel);

// 1. Check strict per-provider config first (new way)
if (modelEndpoint && customEndpointConfigs) {
const providerConfig =
customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
if (providerConfig?.apiKey) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: providerConfig.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}

// 2. Fallback to old global customEndpoint if provider matches
if (modelEndpoint && customEndpoint?.apiKey) {
if (customEndpoint.provider === modelEndpoint.provider) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}

// 3. Fallback to global manual config
if (customEndpoint) {
return {
baseUrl: customEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: customEndpoint.model,
};
}

return undefined;
})(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent model in final fallback and duplicate prefix stripping.

The customEndpoint resolution logic has two issues:

  1. Line 228: In the final fallback branch, customEndpoint.model is used instead of the user-selected selectedModel. This means if a user selects a different custom model, the fallback will ignore their choice and use the global config's model.

  2. Lines 207, 218, 228: The model prefix stripping logic (selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel) is duplicated across multiple branches.

🔧 Suggested fix

Extract the model normalization and use the selected model consistently:

       // Include customEndpoint when using a custom model
       // Look up the correct endpoint based on the selected model
       customEndpoint: (() => {
         if (!isCustomModel(selectedModel)) return undefined;
 
+        // Normalize model name once
+        const normalizedModel = selectedModel.startsWith('custom-')
+          ? selectedModel.slice(7)
+          : selectedModel;
+
         // Get the correct endpoint for this specific model
         const modelEndpoint = getEndpointForModel(selectedModel);
 
         // 1. Check strict per-provider config first (new way)
         if (modelEndpoint && customEndpointConfigs) {
           const providerConfig =
             customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
           if (providerConfig?.apiKey) {
             return {
               baseUrl: modelEndpoint.baseUrl,
               apiKey: providerConfig.apiKey,
-              model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
+              model: normalizedModel,
             };
           }
         }
 
         // 2. Fallback to old global customEndpoint if provider matches
         if (modelEndpoint && customEndpoint?.apiKey) {
           if (customEndpoint.provider === modelEndpoint.provider) {
             return {
               baseUrl: modelEndpoint.baseUrl,
               apiKey: customEndpoint.apiKey,
-              model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
+              model: normalizedModel,
             };
           }
         }
 
         // 3. Fallback to global manual config
         if (customEndpoint) {
           return {
             baseUrl: customEndpoint.baseUrl,
             apiKey: customEndpoint.apiKey,
-            model: customEndpoint.model,
+            model: normalizedModel,
           };
         }
 
         return undefined;
       })(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Include customEndpoint when using a custom model
// Look up the correct endpoint based on the selected model
customEndpoint: (() => {
if (!isCustomModel(selectedModel)) return undefined;
// Get the correct endpoint for this specific model
const modelEndpoint = getEndpointForModel(selectedModel);
// 1. Check strict per-provider config first (new way)
if (modelEndpoint && customEndpointConfigs) {
const providerConfig =
customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
if (providerConfig?.apiKey) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: providerConfig.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}
// 2. Fallback to old global customEndpoint if provider matches
if (modelEndpoint && customEndpoint?.apiKey) {
if (customEndpoint.provider === modelEndpoint.provider) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: selectedModel.startsWith('custom-') ? selectedModel.slice(7) : selectedModel,
};
}
}
// 3. Fallback to global manual config
if (customEndpoint) {
return {
baseUrl: customEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: customEndpoint.model,
};
}
return undefined;
})(),
// Include customEndpoint when using a custom model
// Look up the correct endpoint based on the selected model
customEndpoint: (() => {
if (!isCustomModel(selectedModel)) return undefined;
// Normalize model name once
const normalizedModel = selectedModel.startsWith('custom-')
? selectedModel.slice(7)
: selectedModel;
// Get the correct endpoint for this specific model
const modelEndpoint = getEndpointForModel(selectedModel);
// 1. Check strict per-provider config first (new way)
if (modelEndpoint && customEndpointConfigs) {
const providerConfig =
customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
if (providerConfig?.apiKey) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: providerConfig.apiKey,
model: normalizedModel,
};
}
}
// 2. Fallback to old global customEndpoint if provider matches
if (modelEndpoint && customEndpoint?.apiKey) {
if (customEndpoint.provider === modelEndpoint.provider) {
return {
baseUrl: modelEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: normalizedModel,
};
}
}
// 3. Fallback to global manual config
if (customEndpoint) {
return {
baseUrl: customEndpoint.baseUrl,
apiKey: customEndpoint.apiKey,
model: normalizedModel,
};
}
return undefined;
})(),

Comment on lines +45 to 74
} else if (provider === 'custom' && selectedProvider !== 'custom') {
// Switch to Custom endpoint's model (from configuration or default)
const defaultModel = customEndpoint?.model || 'glm-4.7';
onModelSelect(`${PROVIDER_PREFIXES.custom}${defaultModel}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hard-coded default model may not always be available.

Line 47 defaults to 'glm-4.7' when switching to the custom provider. If the user hasn't configured Zhipu AI or if they only use MiniMax, this default won't match their configuration.

Consider using the first available custom model from getCustomModels() or a more generic fallback.

♻️ Suggested improvement
     } else if (provider === 'custom' && selectedProvider !== 'custom') {
       // Switch to Custom endpoint's model (from configuration or default)
-      const defaultModel = customEndpoint?.model || 'glm-4.7';
+      const customModels = getCustomModels();
+      const defaultModel = customEndpoint?.model || customModels[0]?.id.replace(`${PROVIDER_PREFIXES.custom}`, '') || 'glm-4.7';
       onModelSelect(`${PROVIDER_PREFIXES.custom}${defaultModel}`);
     }
🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view/shared/model-selector.tsx around
lines 45 - 49, The code unconditionally falls back to the hard-coded 'glm-4.7'
when switching to the custom provider; instead, pick a real available custom
model by checking customEndpoint?.model first, then the first entry returned by
getCustomModels(), and only as a last resort use a safe generic fallback; update
the branch handling in the provider switch (references: provider,
selectedProvider, customEndpoint, getCustomModels, defaultModel, onModelSelect,
PROVIDER_PREFIXES.custom) so onModelSelect is called with the chosen model
prefix + model name derived from customEndpoint?.model || getCustomModels()[0]
|| a minimal generic fallback.

Comment on lines +141 to +229
{/* Custom Models */}
{selectedProvider === 'custom' && (
<div className="space-y-3">
{/* Warning when Custom endpoint is not configured */}
{!customEndpoint &&
!customEndpointConfigs?.zhipu?.apiKey &&
!customEndpointConfigs?.minimax?.apiKey &&
!customEndpointConfigs?.manual?.apiKey && (
<div className="flex items-start gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
<AlertTriangle className="w-4 h-4 text-amber-400 mt-0.5 shrink-0" />
<div className="text-sm text-amber-400">
Custom endpoint not configured. Set it up in Settings → Providers → Custom.
</div>
</div>
)}

<div className="flex items-center justify-between">
<Label className="flex items-center gap-2">
<Globe className="w-4 h-4 text-primary" />
Custom Model
</Label>
<span className="text-[11px] px-2 py-0.5 rounded-full border border-emerald-500/40 text-emerald-600 dark:text-emerald-400">
API
</span>
</div>
<div className="flex flex-col gap-2">
{getCustomModels().map((option) => {
const isSelected = selectedModel === option.id;
return (
<button
key={option.id}
type="button"
onClick={() => onModelSelect(option.id)}
title={option.description}
className={cn(
'w-full px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-between',
isSelected
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-accent border-border'
)}
data-testid={`${testIdPrefix}-${option.id}`}
>
<span>{option.label}</span>
<span className="text-xs opacity-70">{option.description}</span>
</button>
);
})}
</div>
</div>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for settings view structure and custom provider configuration
find apps/ui/src/components/views/settings-view -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1504


🏁 Script executed:

#!/bin/bash
# Look for Custom provider settings or Custom settings tab
rg -l "Custom" apps/ui/src/components/views/settings-view --type=tsx --type=ts

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Search for the settings navigation structure and Providers section
rg -n "Providers|Custom" apps/ui/src/components/views/settings-view -A 2 -B 2 --type=tsx --type=ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# List main files in settings-view directory
ls -la apps/ui/src/components/views/settings-view/ | grep -E "\.(tsx|ts)$"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for "Providers" navigation or tabs in settings view
rg "Providers" apps/ui/src/components/views/settings-view -n -A 3 -B 3

Repository: AutoMaker-Org/automaker

Length of output: 942


🏁 Script executed:

#!/bin/bash
# Search for Custom endpoint or Custom settings configuration
rg -i "custom.*endpoint|custom.*setting" apps/ui/src/components/views/settings-view -n -A 2 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 16557


🏁 Script executed:

#!/bin/bash
# Look for tab or navigation structure that might define settings sections
rg "tabs|navigation|sections" apps/ui/src/components/views/settings-view -n | head -30

Repository: AutoMaker-Org/automaker

Length of output: 1173


🏁 Script executed:

#!/bin/bash
# Verify the exact label used in the UI for the providers section
rg "AI Providers" apps/ui/src/components/views/settings-view -n

Repository: AutoMaker-Org/automaker

Length of output: 190


Update the navigation path label in the warning message.

The Custom settings tab does exist, but the warning message should reference "Settings → AI Providers → Custom" instead of "Settings → Providers → Custom" to match the actual UI label used in the navigation configuration.

🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view/shared/model-selector.tsx around
lines 141 - 190, Update the warning text inside the Custom models block (the JSX
rendered when selectedProvider === 'custom') where the AlertTriangle message
currently reads "Custom endpoint not configured. Set it up in Settings →
Providers → Custom." to instead read "Custom endpoint not configured. Set it up
in Settings → AI Providers → Custom." so the navigation label matches the actual
UI; the change is in the Alert div text within the component
(model-selector.tsx) that renders this warning.

CURSOR_MODELS,
THINKING_LEVELS,
THINKING_LEVEL_LABELS,
getCustomModels,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check where getCustomModels is imported from in the target file
rg -n "getCustomModels" apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx -B2 -A2

# Search for all imports of getCustomModels across the codebase
rg -n "import.*getCustomModels" --type=ts --type=tsx

# Find where getCustomModels is defined
fd -e ts -e tsx | xargs rg -l "export.*getCustomModels"

Repository: AutoMaker-Org/automaker

Length of output: 540


🏁 Script executed:

# Check all files importing getCustomModels
rg "getCustomModels" --type ts -l

# Get more context on the import source
cat apps/ui/src/components/views/board-view/shared/model-constants.ts | head -50

# Check if there's an @automaker package that should contain model utilities
fd -type d "@automaker" . 2>/dev/null || fd "automaker" . -type d | head -10

Repository: AutoMaker-Org/automaker

Length of output: 1891


🏁 Script executed:

# Find all files that use getCustomModels
rg "getCustomModels" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' -l

# Check where getCustomModels is defined
rg -B5 "export.*getCustomModels" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx'

# Check for @automaker packages structure
find . -path "*/node_modules" -prune -o -type f -name "package.json" -print | xargs grep -l "@automaker" | head -10

Repository: AutoMaker-Org/automaker

Length of output: 1185


🏁 Script executed:

# Check what @automaker packages export
find . -path "*/node_modules" -prune -o -path "*/libs/*" -name "package.json" -type f -print | xargs grep -l "@automaker" | head -5

# Check the libs structure for model-resolver
ls -la libs/model-resolver/ 2>/dev/null || echo "Not found"

# Check if model-resolver is meant to be used as @automaker/model-resolver
cat libs/model-resolver/package.json 2>/dev/null | head -20

# Check what model-resolver exports
cat libs/model-resolver/src/index.ts 2>/dev/null || cat libs/model-resolver/src/lib/index.ts 2>/dev/null || find libs/model-resolver -name "index.ts" -type f | xargs cat

Repository: AutoMaker-Org/automaker

Length of output: 1560


🏁 Script executed:

# Check the full model-constants.ts file to understand what it exports
cat apps/ui/src/components/views/board-view/shared/model-constants.ts

# Check if there are other relative imports from model-constants
rg "from.*model-constants" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' -A1 -B1

Repository: AutoMaker-Org/automaker

Length of output: 4919


Move getCustomModels import to a shared @automaker/* package.

The import from '@/components/views/board-view/shared/model-constants' violates the coding guideline requiring shared packages (@automaker/*) instead of relative paths. Since getCustomModels is used across multiple components (board-view and settings-view), it should be exported from a proper shared package like @automaker/model-resolver or similar.

🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
at line 24, The import of getCustomModels should be relocated from the local
module to a shared @automaker package; update imports in
phase-model-selector.tsx (and other callers) to import getCustomModels from the
new shared package (e.g., @automaker/model-resolver) and remove the old
'@/components/views/board-view/shared/model-constants' import, ensuring the
shared package exports getCustomModels and preserving the same function
signature and usage in PhaseModelSelector (and any other components referencing
getCustomModels).

Comment on lines +138 to +198
// Get available custom models (only if endpoint is configured)
const availableCustomModels = React.useMemo(() => {
// Check per-provider configs (new way) or global customEndpoint (legacy)
const hasProviderConfig =
customEndpointConfigs?.zhipu?.apiKey ||
customEndpointConfigs?.minimax?.apiKey ||
customEndpointConfigs?.manual?.apiKey;
if (!hasProviderConfig && (!customEndpoint?.baseUrl || !customEndpoint?.apiKey)) {
return [];
}
return getCustomModels();
}, [customEndpoint, customEndpointConfigs]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filter custom models by configured providers.

The current logic shows ALL custom models from ALL presets if ANY provider is configured. For example, if only Zhipu is configured (has an API key), users will still see MiniMax models in the selector, leading to execution failures when those models are selected.

🔧 Proposed fix to filter models by configured providers
 const availableCustomModels = React.useMemo(() => {
-  // Check per-provider configs (new way) or global customEndpoint (legacy)
-  const hasProviderConfig =
-    customEndpointConfigs?.zhipu?.apiKey ||
-    customEndpointConfigs?.minimax?.apiKey ||
-    customEndpointConfigs?.manual?.apiKey;
-  if (!hasProviderConfig && (!customEndpoint?.baseUrl || !customEndpoint?.apiKey)) {
+  // Get all custom models
+  const allCustomModels = getCustomModels();
+  
+  // Filter by configured providers
+  const configuredProviders = new Set<string>();
+  if (customEndpointConfigs?.zhipu?.apiKey) configuredProviders.add('Zhipu AI');
+  if (customEndpointConfigs?.minimax?.apiKey) configuredProviders.add('MiniMax');
+  if (customEndpointConfigs?.manual?.apiKey) configuredProviders.add('Manual Configuration');
+  
+  // Legacy support: if global customEndpoint is configured, include manual models
+  if (customEndpoint?.baseUrl && customEndpoint?.apiKey) {
+    configuredProviders.add('Manual Configuration');
+  }
+  
+  if (configuredProviders.size === 0) {
     return [];
   }
-  return getCustomModels();
+  
+  // Filter models to only show those from configured providers
+  return allCustomModels.filter(model => configuredProviders.has(model.description));
 }, [customEndpoint, customEndpointConfigs]);
🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
around lines 138 - 149, availableCustomModels currently returns all custom
models if any provider is configured causing models from unconfigured providers
to appear; modify the React.useMemo block so after calling getCustomModels() you
filter the returned models to only include those whose provider/preset matches a
configured provider: check each model's provider identifier (e.g.,
model.provider or model.preset) and allow it only if the corresponding flag is
present in customEndpointConfigs (customEndpointConfigs.zhipu?.apiKey,
customEndpointConfigs.minimax?.apiKey, customEndpointConfigs.manual?.apiKey) or,
for legacy endpoints, if customEndpoint?.baseUrl && customEndpoint?.apiKey;
return that filtered array from availableCustomModels.

- Added CustomEndpointConfigs type for storing separate API keys per provider (Zhipu, MiniMax, Manual)
- Updated app-store with setCustomEndpointForProvider and getCustomEndpointForProvider actions
- Updated CustomSettingsTab to save/load API keys separately per provider
- Updated add-feature-dialog and edit-feature-dialog to use per-provider API key lookup
- Added getEndpointForModel helper to map model names to correct provider endpoints
- Fixed API path from /messages to /v1/messages for Anthropic compatibility
- Added model prefix stripping (custom-glm-4.7 -> glm-4.7) before sending to API

Closes AutoMaker-Org#372
- Add customEndpoint to Feature type for type safety
- Add provider field to ExecuteOptions.customEndpoint type
- Add customEndpoint to onUpdate type signature in edit-feature-dialog
- Forward customEndpoint in plan revision/task/continuation streams
- Update model-selector.tsx to use customEndpointConfigs
- Update phase-model-selector.tsx to use customEndpointConfigs
- Remove redundant if/else in custom-provider.ts
- Fix misleading ALL_MODELS comment in model-constants.ts
@vikasagarwal101 vikasagarwal101 force-pushed the feature/custom-endpoint-per-provider-api-keys branch from 81ff1d1 to c683d94 Compare January 11, 2026 05:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/server/src/services/auto-mode-service.ts (2)

768-785: Critical: Pipeline steps don't propagate custom endpoint configuration.

Pipeline step execution calls runAgent without passing customEndpoint, even though the feature object (which contains customEndpoint) is available in this method's scope. This means pipeline steps will fail or use the wrong endpoint when custom providers (Zhipu, MiniMax, or manual) are configured.

🐛 Proposed fix
       await this.runAgent(
         workDir,
         featureId,
         prompt,
         abortController,
         projectPath,
         undefined, // no images for pipeline steps
         model,
         {
           projectPath,
           planningMode: 'skip', // Pipeline steps don't need planning
           requirePlanApproval: false,
           previousContent: previousContext,
           systemPrompt: contextFilesPrompt || undefined,
           autoLoadClaudeMd,
           thinkingLevel: feature.thinkingLevel,
+          customEndpoint: feature.customEndpoint,
         }
       );

1087-1103: Critical: Follow-up execution doesn't propagate custom endpoint configuration.

When a follow-up is executed on a feature that uses a custom endpoint (Zhipu, MiniMax, etc.), the runAgent call doesn't receive the customEndpoint configuration, even though the feature object is available at this point (loaded at Line 919). This causes follow-ups to fail or use the wrong provider.

🐛 Proposed fix
       await this.runAgent(
         workDir,
         featureId,
         fullPrompt,
         abortController,
         projectPath,
         allImagePaths.length > 0 ? allImagePaths : imagePaths,
         model,
         {
           projectPath,
           planningMode: 'skip', // Follow-ups don't require approval
           previousContent: previousContext || undefined,
           systemPrompt: contextFilesPrompt || undefined,
           autoLoadClaudeMd,
           thinkingLevel: feature?.thinkingLevel,
+          customEndpoint: feature?.customEndpoint,
         }
       );
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)

1-20: Critical: Unresolved git merge conflict markers.

This file contains unresolved merge conflicts at:

  • Lines 1-20 (imports)
  • Lines 70-102 (getCustomModels function and comments - three-way merge)

After resolving, ensure you keep:

  • ModelAlias type import (used by CLAUDE_MODELS typing)
  • ReasoningEffort type import (used by REASONING_EFFORT_LEVELS)
  • CODEX_MODEL_MAP and OPENCODE_MODELS imports
  • Provider icon imports
  • getCustomModels function and related imports
apps/server/src/providers/provider-factory.ts (1)

114-137: Pass options through fallback provider creation.

The fallback to Claude provider on line 132 doesn't pass through the options parameter, which could cause issues if the caller expects options to be honored.

♻️ Proposed fix
     if (!provider) {
       // Fallback to claude if provider not found
       const claudeReg = providerRegistry.get('claude');
       if (claudeReg) {
-        return claudeReg.factory();
+        return claudeReg.factory(options);
       }
       throw new Error(`No provider found for model: ${modelId}`);
     }
apps/ui/src/store/app-store.ts (1)

2226-2262: Critical: Resolve merge conflicts before merging.

The file contains unresolved Git merge conflict markers that will cause runtime syntax errors. All conflicts must be resolved before this PR can be merged.

Conflict locations:

  • Lines 2226-2262: Terminal actions and custom endpoint actions conflict
  • Lines 2637-2698: Terminal tab management conflict
  • Lines 2893-3056: Store configuration (merge, migrate, partialize) conflict

Please resolve these conflicts and ensure the code compiles and passes linting checks:

#!/bin/bash
# Verify the file compiles after conflict resolution
cd apps/ui
npm run typecheck
npm run lint
🤖 Fix all issues with AI agents
In @apps/server/src/providers/custom-provider.ts:
- Around line 166-183: The request headers always use 'x-api-key' which breaks
Zhipu AI; update the header construction around the fetch in the function that
builds apiUrl/requestBody so it checks customEndpoint.provider and sets
Authorization: Bearer <apiKey> when provider === 'zhipu' and retains x-api-key:
<apiKey> for 'minimax' and 'manual'; also replace the two console.log calls that
print request body/response with logger.debug(...) for consistency (the
console.log calls near the requestBody logging and the later response logging in
the same function).

In @apps/server/src/services/auto-mode-service.ts:
- Around line 588-590: The assignment is using an unsafe assertion on
feature.customEndpoint; remove the "as { baseUrl: string; apiKey: string; model:
string } | undefined" cast and either (A) rely on the correct Feature type from
@automaker/types by updating that type so customEndpoint is typed correctly, or
(B) add a small runtime guard before assigning (e.g., check
feature.customEndpoint is an object and has string baseUrl, apiKey, model) and
only pass it through when valid; update the code around the customEndpoint
property assignment in auto-mode-service (the place building the provider
config) to use the validated value or the properly typed property instead of the
cast.

In @apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx:
- Around line 69-73: The file contains unresolved git conflict markers that must
be removed and the variants merged: delete the markers (<<<<<<<, =======,
>>>>>>>) and reconcile the imports so the top import list includes
isCursorModel, isCustomModel, PROVIDER_PREFIXES and getEndpointForModel; merge
the FeatureData type by combining both conflicting definitions into a single
coherent type used by buildFeatureData; reconcile the useAppStore destructuring
so the hook call returns the complete set of properties expected elsewhere
(restore any keys removed by the conflict); and merge the buildFeatureData
return logic so it incorporates both existing behavior and the custom-endpoint
handling (e.g., usage of getEndpointForModel and provider constants) into one
correct implementation. Ensure no conflict markers remain and run tests/build to
verify.

In @apps/ui/src/components/views/board-view/shared/model-selector.tsx:
- Around line 4-9: This file has unresolved git conflict markers; remove all
<<<<<<<, =======, and >>>>>>> blocks and merge the variants so all needed
imports and options are present: combine icon imports to include Brain, Bot,
Terminal, AlertTriangle, Globe and also AnthropicIcon, CursorIcon, OpenAIIcon;
reconcile the model constants imports so both sides’ model identifiers are
imported; fix the three-way store destructuring by keeping all required store
variables (e.g., any activeModelId / setActiveModelId / provider-related keys
referenced in the file) and ensure no duplicate declarations remain; and update
the provider toggle button area to include all four provider options (Claude,
Cursor, Codex — keep the “Codex CLI” button — and Custom) so both Codex and
Custom buttons coexist. Ensure the file builds with no conflict markers and all
referenced symbols (imports, store variables, provider buttons) are used
consistently.

In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 30-40: The file has unresolved git merge markers; remove all
conflict markers (<<<<<<<, =======, >>>>>>>) and reconcile the three-way merges
so imports and usages are unified: keep REASONING_EFFORT_LEVELS and
REASONING_EFFORT_LABELS and also import getCustomModels; ensure the useAppStore
destructuring includes both customEndpoint and customEndpointConfigs; include
provider icons AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon and Globe in
the import list; and resolve the CommandGroup rendering conflict (the block
around CommandGroup) so only one correct JSX implementation remains. Ensure
references like REASONING_EFFORT_LEVELS, REASONING_EFFORT_LABELS,
getCustomModels, customEndpoint, customEndpointConfigs,
AnthropicIcon/CursorIcon/OpenAIIcon/OpenCodeIcon/Globe and CommandGroup are
present and used consistently.

In
@apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts:
- Line 26: Update the description string in the custom provider preset where
description: 'GLM Models by Zhipu AI a chinese frontier lab' is defined (in
custom-provider-presets.ts) to correct the capitalization and punctuation so it
reads e.g. "GLM Models by Zhipu AI, a Chinese frontier lab".
- Around line 100-115: getProviderForModel currently uses
bareModel.includes('glm') and includes('minimax') which can yield false
positives; change the matching to only accept proper model name prefixes after
stripping "custom-" (use startsWith checks or regex anchors like /^glm(-|$)/ and
/^minimax(-|$)/ against bareModel) and remove the generic includes checks so
only names beginning with the intended family (e.g., "glm-" or "glm" as the full
token) return 'zhipu' and similarly for 'minimax'.

In @apps/ui/src/components/views/settings-view/shared/types.ts:
- Around line 37-43: CustomEndpointConfig is duplicated across UI and server;
move the single interface into the shared types package and import it from
there: create a new file exporting the interface (e.g.,
libs/types/src/custom-endpoint.ts) and re-export it from
libs/types/src/index.ts, then replace local declarations by importing/exporting
the type in apps/ui/src/components/views/settings-view/shared/types.ts (export
type { CustomEndpointConfig } from '@automaker/types') and importing it in
apps/server/src/providers/custom-provider.ts (import type { CustomEndpointConfig
} from '@automaker/types'), ensuring both places remove their local definitions.
🧹 Nitpick comments (4)
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (1)

347-387: Extract custom endpoint resolution logic to a shared utility.

Once the merge conflicts are resolved, consider extracting this custom endpoint resolution logic into a shared helper function. The same logic appears duplicated in edit-feature-dialog.tsx. This would improve maintainability and reduce the risk of the two implementations diverging.

💡 Suggested helper extraction

Create a helper in a shared location (e.g., @/lib/custom-endpoint-utils.ts):

import { isCustomModel, getEndpointForModel } from '@automaker/types';
import type { CustomEndpointConfig } from '@/store/app-store';

export function resolveCustomEndpoint(
  selectedModel: string,
  customEndpoint?: CustomEndpointConfig,
  customEndpointConfigs?: Record<'zhipu' | 'minimax' | 'manual', CustomEndpointConfig | undefined>
): { baseUrl: string; apiKey: string; model: string } | undefined {
  if (!isCustomModel(selectedModel)) return undefined;

  const modelEndpoint = getEndpointForModel(selectedModel);
  const strippedModel = selectedModel.startsWith('custom-') 
    ? selectedModel.slice(7) 
    : selectedModel;

  // 1. Per-provider config
  if (modelEndpoint && customEndpointConfigs) {
    const providerConfig = customEndpointConfigs[modelEndpoint.provider as 'zhipu' | 'minimax' | 'manual'];
    if (providerConfig?.apiKey) {
      return { baseUrl: modelEndpoint.baseUrl, apiKey: providerConfig.apiKey, model: strippedModel };
    }
  }

  // 2. Global endpoint matching provider
  if (modelEndpoint && customEndpoint?.apiKey && customEndpoint.provider === modelEndpoint.provider) {
    return { baseUrl: modelEndpoint.baseUrl, apiKey: customEndpoint.apiKey, model: strippedModel };
  }

  // 3. Global manual fallback
  if (customEndpoint) {
    return { baseUrl: customEndpoint.baseUrl, apiKey: customEndpoint.apiKey, model: customEndpoint.model };
  }

  return undefined;
}
apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)

284-324: Duplicated custom endpoint resolution logic.

This logic is identical to the code in add-feature-dialog.tsx. As mentioned in the add-feature-dialog review, this should be extracted to a shared utility function to ensure consistency and reduce maintenance burden.

apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts (1)

71-79: Strengthen model ID validation.

The current regex allows model IDs like "..." or "---" which contain no alphanumeric characters. Consider requiring at least one alphanumeric character to prevent edge cases.

♻️ Proposed improvement
 export function isValidModelId(model: string, provider: 'zhipu' | 'minimax' | 'manual'): boolean {
   if (!model || model.trim().length === 0) {
     return false;
   }
 
-  // Basic validation - model IDs should be alphanumeric with hyphens, dots, etc.
-  const modelIdPattern = /^[a-zA-Z0-9._-]+$/;
+  // Basic validation - model IDs should contain at least one alphanumeric and may include hyphens, dots, underscores
+  const modelIdPattern = /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9._-]+$/;
   return modelIdPattern.test(model.trim());
 }
apps/server/src/providers/custom-provider.ts (1)

118-124: Use logger instead of console.log for consistency.

The code uses console.log for debug logging while the rest of the codebase uses the logger utility. Additionally, exposing the first 8 characters of an API key might be excessive for security purposes - consider reducing to 4 characters.

♻️ Proposed fix
-    // Debug: Log what config we're using (mask the API key for security)
-    console.log('[CustomProvider] Using config:', {
+    logger.debug('Using custom endpoint config:', {
       baseUrl,
       apiKeyPresent: !!apiKey,
       apiKeyLength: apiKey?.length || 0,
-      apiKeyPrefix: apiKey?.substring(0, 8) + '...',
+      apiKeyPrefix: apiKey?.substring(0, 4) + '...',
       model: options.model,
     });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81ff1d1 and c683d94.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • apps/server/src/providers/custom-provider.ts
  • apps/server/src/providers/index.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/ui/src/store/app-store.ts
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/feature.ts
  • libs/types/src/index.ts
  • libs/types/src/provider-utils.ts
  • libs/types/src/provider.ts
  • libs/types/src/settings.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
  • libs/types/src/index.ts
  • libs/types/src/provider-utils.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/settings.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • libs/types/src/feature.ts
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • libs/types/src/provider.ts
  • apps/server/src/providers/custom-provider.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/providers/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/settings.ts
  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • libs/types/src/feature.ts
  • apps/ui/src/components/views/settings-view/shared/types.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
  • libs/types/src/provider.ts
  • apps/server/src/providers/custom-provider.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/providers/index.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/custom-provider.ts
  • apps/server/src/providers/provider-factory.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/src/providers/index.ts
🧠 Learnings (4)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
  • apps/ui/src/components/views/board-view/shared/model-selector.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always import from shared packages (automaker/*), never from old relative paths

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/shared/types.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
🧬 Code graph analysis (11)
apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (5)
libs/types/src/index.ts (5)
  • isCursorModel (184-184)
  • isClaudeModel (185-185)
  • PROVIDER_PREFIXES (183-183)
  • supportsReasoningEffort (62-62)
  • isCustomModel (188-188)
libs/types/src/provider-utils.ts (4)
  • isCursorModel (28-43)
  • isClaudeModel (51-61)
  • PROVIDER_PREFIXES (15-20)
  • isCustomModel (132-141)
libs/types/src/model.ts (1)
  • supportsReasoningEffort (49-51)
apps/ui/src/lib/storage.ts (1)
  • storage (93-100)
apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts (1)
  • getEndpointForModel (121-139)
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (3)
apps/ui/src/components/views/board-view/shared/model-constants.ts (1)
  • getCustomModels (78-94)
libs/types/src/index.ts (1)
  • stripProviderPrefix (190-190)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/views/settings-view/shared/types.ts (2)
apps/server/src/providers/custom-provider.ts (1)
  • CustomEndpointConfig (23-32)
apps/server/src/providers/index.ts (1)
  • CustomEndpointConfig (32-32)
apps/ui/src/store/app-store.ts (2)
apps/ui/src/components/views/settings-view/shared/types.ts (2)
  • CustomEndpointConfig (38-43)
  • CustomEndpointConfigs (46-62)
libs/types/src/settings.ts (1)
  • DEFAULT_KEYBOARD_SHORTCUTS (751-772)
apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx (2)
libs/types/src/index.ts (1)
  • isCustomModel (188-188)
libs/types/src/provider-utils.ts (1)
  • isCustomModel (132-141)
libs/model-resolver/src/resolver.ts (2)
libs/types/src/index.ts (1)
  • PROVIDER_PREFIXES (183-183)
libs/types/src/provider-utils.ts (1)
  • PROVIDER_PREFIXES (15-20)
apps/ui/src/components/views/settings-view/providers/provider-tabs.tsx (1)
apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx (1)
  • CustomSettingsTab (13-290)
apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts (3)
apps/server/src/providers/custom-provider.ts (1)
  • CustomEndpointConfig (23-32)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • CustomEndpointConfig (38-43)
apps/server/src/providers/provider-factory.ts (1)
  • getProviderForModel (114-138)
apps/server/src/providers/custom-provider.ts (4)
apps/server/src/providers/index.ts (8)
  • CustomEndpointConfig (32-32)
  • CustomProvider (32-32)
  • BaseProvider (6-6)
  • ProviderConfig (14-14)
  • ExecuteOptions (15-15)
  • ProviderMessage (16-16)
  • InstallationStatus (17-17)
  • ModelDefinition (18-18)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • CustomEndpointConfig (38-43)
libs/types/src/index.ts (5)
  • ProviderConfig (8-8)
  • ExecuteOptions (10-10)
  • ProviderMessage (12-12)
  • InstallationStatus (13-13)
  • ModelDefinition (15-15)
libs/types/src/provider.ts (5)
  • ProviderConfig (24-28)
  • ExecuteOptions (96-167)
  • ProviderMessage (185-196)
  • InstallationStatus (201-218)
  • ModelDefinition (232-245)
apps/server/src/providers/provider-factory.ts (2)
apps/server/src/providers/custom-provider.ts (2)
  • CustomEndpointConfig (23-32)
  • CustomProvider (85-386)
libs/types/src/provider-utils.ts (1)
  • isCustomModel (132-141)
apps/ui/src/components/views/board-view/shared/model-selector.tsx (3)
apps/ui/src/components/views/board-view/shared/model-constants.ts (4)
  • CLAUDE_MODELS (31-53)
  • CURSOR_MODELS (59-67)
  • CODEX_MODELS (104-145)
  • ModelOption (22-29)
libs/types/src/model-display.ts (3)
  • CLAUDE_MODELS (45-67)
  • CODEX_MODELS (73-114)
  • ModelOption (17-28)
libs/types/src/provider-utils.ts (1)
  • PROVIDER_PREFIXES (15-20)
🪛 Biome (2.1.2)
apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx

[error] 71-72: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 74-75: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 75-75: expected ( but instead found custom

Remove custom

(parse)


[error] 75-75: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 75-75: expected ; but instead found )

Remove )

(parse)


[error] 73-73: Shouldn't redeclare 'isCursorModel'. Consider to delete it or rename it.

'isCursorModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 73-73: Shouldn't redeclare 'PROVIDER_PREFIXES'. Consider to delete it or rename it.

'PROVIDER_PREFIXES' is defined here:

(lint/suspicious/noRedeclare)


[error] 104-104: Expected an expression, or an assignment but instead found ':'.

Expected an expression, or an assignment here.

(parse)


[error] 110-110: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 156-157: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)


[error] 179-180: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 180-180: expected ( but instead found custom

Remove custom

(parse)


[error] 180-180: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 180-180: expected ; but instead found )

Remove )

(parse)


[error] 279-279: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 279-279: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 280-281: Expected a JSX attribute but instead found ',
======='.

Expected a JSX attribute here.

(parse)


[error] 284-284: Expected an identifier but instead found '('.

Expected an identifier here.

(parse)


[error] 285-285: expected ... but instead found if

Remove if

(parse)


[error] 285-285: Expected a JSX attribute but instead found '(!'.

Expected a JSX attribute here.

(parse)


[error] 285-285: Expected a JSX attribute but instead found '('.

Expected a JSX attribute here.

(parse)


[error] 285-288: Expected a JSX attribute but instead found ')) return undefined;

    // Get the correct endpoint for this specific model
    const'.

Expected a JSX attribute here.

(parse)


[error] 288-288: Expected a JSX attribute value but instead found 'getEndpointForModel'.

Expected a JSX attribute value here.

(parse)


[error] 288-288: Expected a JSX attribute but instead found '('.

Expected a JSX attribute here.

(parse)


[error] 288-291: Expected a JSX attribute but instead found ');

    // 1. Check strict per-provider config first (new way)
    if ('.

Expected a JSX attribute here.

(parse)


[error] 291-291: Expected a JSX attribute but instead found '&&'.

Expected a JSX attribute here.

(parse)


[error] 291-291: Expected a JSX attribute but instead found ')'.

Expected a JSX attribute here.

(parse)


[error] 292-292: expected ... but instead found const

Remove const

(parse)


[error] 293-293: Expected a JSX attribute value but instead found 'customEndpointConfigs'.

Expected a JSX attribute value here.

(parse)


[error] 293-293: Expected a JSX attribute but instead found '['.

Expected a JSX attribute here.

(parse)


[error] 293-293: Expected a JSX attribute but instead found '.'.

Expected a JSX attribute here.

(parse)


[error] 293-294: Expected a JSX attribute but instead found ''zhipu' | 'minimax' | 'manual'];
if ('.

Expected a JSX attribute here.

(parse)


[error] 294-294: Expected a JSX attribute but instead found '?.'.

Expected a JSX attribute here.

(parse)


[error] 294-294: Expected a JSX attribute but instead found ')'.

Expected a JSX attribute here.

(parse)


[error] 295-295: expected ... but instead found return

Remove return

(parse)


[error] 296-296: expected ... but instead found baseUrl

Remove baseUrl

(parse)


[error] 296-296: expected } but instead found :

Remove :

(parse)


[error] 296-296: Expected a JSX attribute but instead found '.'.

Expected a JSX attribute here.

(parse)


[error] 296-296: Expected a JSX attribute but instead found ','.

Expected a JSX attribute here.

(parse)


[error] 297-297: Expected a JSX attribute but instead found '.'.

Expected a JSX attribute here.

(parse)


[error] 297-297: Expected a JSX attribute but instead found ','.

Expected a JSX attribute here.

(parse)


[error] 298-298: Expected a JSX attribute but instead found '.'.

Expected a JSX attribute here.

(parse)


[error] 298-298: Expected a JSX attribute but instead found '('custom-') ?'.

Expected a JSX attribute here.

(parse)


[error] 298-298: Expected a JSX attribute but instead found '.'.

Expected a JSX attribute here.

(parse)


[error] 298-298: Expected a JSX attribute but instead found '(7) :'.

Expected a JSX attribute here.

(parse)


[error] 298-298: Expected a JSX attribute but instead found ','.

Expected a JSX attribute here.

(parse)


[error] 299-299: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 300-300: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 301-301: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 305-305: expected } but instead found if

Remove if

(parse)


[error] 306-306: expected } but instead found return

Remove return

(parse)


[error] 307-307: expected } but instead found :

Remove :

(parse)


[error] 310-310: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 311-311: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 312-312: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 316-316: expected } but instead found return

Remove return

(parse)


[error] 317-317: expected } but instead found :

Remove :

(parse)


[error] 320-320: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 321-321: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 324-324: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 324-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 325-325: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 303-303: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)


[error] 314-314: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx

[error] 35-38: Expected a statement but instead found '=======
getCustomModels,
} from '@/components/views/board-view/shared/model-constants''.

Expected a statement here.

(parse)


[error] 39-40: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 40-40: expected ( but instead found custom

Remove custom

(parse)


[error] 40-40: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 40-40: expected ; but instead found )

Remove )

(parse)


[error] 39-39: Shouldn't redeclare 'Check'. Consider to delete it or rename it.

'Check' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'ChevronsUpDown'. Consider to delete it or rename it.

'ChevronsUpDown' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'Star'. Consider to delete it or rename it.

'Star' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'ChevronRight'. Consider to delete it or rename it.

'ChevronRight' is defined here:

(lint/suspicious/noRedeclare)


[error] 92-93: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 95-96: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 96-96: expected ( but instead found custom

Remove custom

(parse)


[error] 96-96: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 96-96: expected ; but instead found )

Remove )

(parse)


[error] 96-97: Expected a statement but instead found '==='.

Expected a statement here.

(parse)


[error] 104-105: Expected a statement but instead found '>>>>>>> 81ff1d1 (fix: Address PR #406 review comments)'.

Expected a statement here.

(parse)


[error] 105-105: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 94-94: Shouldn't redeclare 'enabledCursorModels'. Consider to delete it or rename it.

'enabledCursorModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 94-94: Shouldn't redeclare 'favoriteModels'. Consider to delete it or rename it.

'favoriteModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 94-94: Shouldn't redeclare 'toggleFavoriteModel'. Consider to delete it or rename it.

'toggleFavoriteModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 99-99: Shouldn't redeclare 'enabledCursorModels'. Consider to delete it or rename it.

'enabledCursorModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 100-100: Shouldn't redeclare 'favoriteModels'. Consider to delete it or rename it.

'favoriteModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 101-101: Shouldn't redeclare 'toggleFavoriteModel'. Consider to delete it or rename it.

'toggleFavoriteModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 102-102: Shouldn't redeclare 'customEndpoint'. Consider to delete it or rename it.

'customEndpoint' is defined here:

(lint/suspicious/noRedeclare)


[error] 991-992: Expected a JSX Expression, a Element, or a text but instead found '<<<<<<'.

Expected a JSX Expression, a Element, or a text here.

(parse)


[error] 992-1043: Expected corresponding JSX closing tag for 'HEAD'.

Opening tag

closing tag

(parse)


[error] 1039-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 1040-1040: Unexpected token. Did you mean {'>'} or &gt;?

(parse)

apps/ui/src/store/app-store.ts

[error] 2238-2238: Expected an expression for the left hand side of the === operator.

This operator requires a left hand side value

(parse)


[error] 2238-2238: Expected an expression but instead found '==='.

Expected an expression here.

(parse)


[error] 2238-2238: Expected an expression but instead found '='.

Expected an expression here.

(parse)


[error] 2238-2238: Invalid assignment to ======

This expression cannot be assigned to

(parse)


[error] 2239-2239: expected , but instead found :

Remove :

(parse)


[error] 2239-2239: expected , but instead found (

Remove (

(parse)


[error] 2240-2240: expected , but instead found :

Remove :

(parse)


[error] 2240-2240: expected , but instead found (

Remove (

(parse)


[error] 2242-2242: expected , but instead found :

Remove :

(parse)


[error] 2242-2242: expected , but instead found (

Remove (

(parse)


[error] 2250-2251: expected , but instead found :

Remove :

(parse)


[error] 2251-2251: expected , but instead found (

Remove (

(parse)


[error] 2258-2258: expected , but instead found :

Remove :

(parse)


[error] 2258-2258: expected , but instead found (

Remove (

(parse)


[error] 2262-2262: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 2638-2640: Expected a statement but instead found '<<<<<<< HEAD
newActiveSessionId = newActiveTab?.layout ? findFirst(newActiveTab.layout) : null'.

Expected a statement here.

(parse)


[error] 2644-2644: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 2656-2657: Expected a statement but instead found ',

  updateTerminalPanelSizes: (tabId, panelKeys, sizes) =>'.

Expected a statement here.

(parse)


[error] 2701-2703: Expected a statement but instead found ',

  // Convert runtime layout to persisted format (preserves sessionIds for reconnection)
  saveTerminalLayout: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2758-2759: Expected a statement but instead found ',

  getPersistedTerminalLayout: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2762-2763: Expected a statement but instead found ',

  clearPersistedTerminalLayout: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2767-2769: Expected a statement but instead found ',

  // Spec Creation actions
  setSpecCreatingForProject: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2772-2773: Expected a statement but instead found ',

  isSpecCreatingForProject: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2776-2776: Expected a statement but instead found ',

  setDefaultPlanningMode: (mode) => set('.

Expected a statement here.

(parse)


[error] 2777-2777: Expected a statement but instead found '),
setDefaultRequirePlanApproval: (require) => set('.

Expected a statement here.

(parse)


[error] 2778-2778: Expected a statement but instead found '),
setDefaultAIProfileId: (profileId) => set('.

Expected a statement here.

(parse)


[error] 2780-2781: Expected a statement but instead found '),

  // Plan Approval actions
  setPendingPlanApproval: (approval) => set('.

Expected a statement here.

(parse)


[error] 2783-2784: Expected a statement but instead found '),

  // Claude Usage Tracking actions
  setClaudeRefreshInterval: (interval: number) => set('.

Expected a statement here.

(parse)


[error] 2785-2785: Expected a statement but instead found '),
setClaudeUsageLastUpdated: (timestamp: number) => set('.

Expected a statement here.

(parse)


[error] 2786-2788: Expected a statement but instead found '),
setClaudeUsage: (usage: ClaudeUsage | null) =>
set('.

Expected a statement here.

(parse)


[error] 2789-2789: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 2792-2795: Expected a statement but instead found '),

  // Pipeline actions
  setPipelineConfig: (projectPath, config) =>'.

Expected a statement here.

(parse)


[error] 2802-2803: Expected a statement but instead found ',

  getPipelineConfig: (projectPath) =>'.

Expected a statement here.

(parse)


[error] 2806-2807: Expected a statement but instead found ',

  addPipelineStep: (projectPath, step) =>'.

Expected a statement here.

(parse)


[error] 2831-2832: Expected a statement but instead found ',

  updatePipelineStep: (projectPath, stepId, updates) =>'.

Expected a statement here.

(parse)


[error] 2853-2854: Expected a statement but instead found ',

  deletePipelineStep: (projectPath, stepId) =>'.

Expected a statement here.

(parse)


[error] 2870-2871: Expected a statement but instead found ',

  reorderPipelineSteps: (projectPath, stepIds) =>'.

Expected a statement here.

(parse)


[error] 2891-2895: Expected a statement but instead found ',

  // Reset
  reset: () => set(initialState),'.

Expected a statement here.

(parse)


[error] 2895-2895: Expected a statement but instead found '),'.

Expected a statement here.

(parse)


[error] 2896-2896: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 2934-2935: Expected a statement but instead found ',
migrate: (persistedState: unknown, version: number) =>'.

Expected a statement here.

(parse)


[error] 2989-2991: Expected a statement but instead found ',
partialize: (state) =>
('.

Expected a statement here.

(parse)


[error] 2993-2993: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 3044-3044: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 3050-3051: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 3051-3051: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 3056-3056: Expected a statement but instead found ') as any,

ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)

apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx

[error] 72-73: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 73-73: expected ( but instead found custom

Remove custom

(parse)


[error] 73-73: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 73-73: expected ; but instead found )

Remove )

(parse)


[error] 71-71: Shouldn't redeclare 'isCursorModel'. Consider to delete it or rename it.

'isCursorModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 71-71: Shouldn't redeclare 'PROVIDER_PREFIXES'. Consider to delete it or rename it.

'PROVIDER_PREFIXES' is defined here:

(lint/suspicious/noRedeclare)


[error] 94-96: Expected a statement but instead found '=======
customEndpoint?:'.

Expected a statement here.

(parse)


[error] 100-101: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 101-101: expected ( but instead found custom

Remove custom

(parse)


[error] 101-101: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 101-101: expected ; but instead found )

Remove )

(parse)


[error] 180-181: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 190-191: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 191-191: expected ( but instead found custom

Remove custom

(parse)


[error] 191-191: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 191-191: expected ; but instead found )

Remove )

(parse)


[error] 184-184: Shouldn't redeclare 'defaultPlanningMode'. Consider to delete it or rename it.

'defaultPlanningMode' is defined here:

(lint/suspicious/noRedeclare)


[error] 185-185: Shouldn't redeclare 'defaultRequirePlanApproval'. Consider to delete it or rename it.

'defaultRequirePlanApproval' is defined here:

(lint/suspicious/noRedeclare)


[error] 186-186: Shouldn't redeclare 'defaultAIProfileId'. Consider to delete it or rename it.

'defaultAIProfileId' is defined here:

(lint/suspicious/noRedeclare)


[error] 341-341: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 341-341: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 342-342: Expected a JSX attribute but instead found ','.

Expected a JSX attribute here.

(parse)


[error] 343-343: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 347-347: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 348-348: expected } but instead found if

Remove if

(parse)


[error] 355-355: expected } but instead found const

Remove const

(parse)


[error] 358-358: expected } but instead found return

Remove return

(parse)


[error] 359-359: expected } but instead found :

Remove :

(parse)


[error] 362-362: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 363-363: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 364-364: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 368-368: expected } but instead found if

Remove if

(parse)


[error] 369-369: expected } but instead found return

Remove return

(parse)


[error] 370-370: expected } but instead found :

Remove :

(parse)


[error] 373-373: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 374-374: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 375-375: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 379-379: expected } but instead found return

Remove return

(parse)


[error] 380-380: expected } but instead found :

Remove :

(parse)


[error] 383-383: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 384-384: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 387-387: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 388-388: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 388-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 389-389: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 345-345: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)


[error] 350-350: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)


[error] 366-366: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)


[error] 377-377: Wrap comments inside children within braces.

Unsafe fix: Wrap the comments with braces

(lint/suspicious/noCommentText)

apps/ui/src/components/views/board-view/shared/model-constants.ts

[error] 1-1: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)


[error] 19-20: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 20-20: expected ( but instead found custom

Remove custom

(parse)


[error] 20-20: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 20-20: expected ; but instead found )

Remove )

(parse)


[error] 12-12: Shouldn't redeclare 'ThinkingLevel'. Consider to delete it or rename it.

'ThinkingLevel' is defined here:

(lint/suspicious/noRedeclare)


[error] 13-13: Shouldn't redeclare 'ModelProvider'. Consider to delete it or rename it.

'ModelProvider' is defined here:

(lint/suspicious/noRedeclare)


[error] 14-14: Shouldn't redeclare 'CURSOR_MODEL_MAP'. Consider to delete it or rename it.

'CURSOR_MODEL_MAP' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Brain'. Consider to delete it or rename it.

'Brain' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Zap'. Consider to delete it or rename it.

'Zap' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Scale'. Consider to delete it or rename it.

'Scale' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Cpu'. Consider to delete it or rename it.

'Cpu' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Rocket'. Consider to delete it or rename it.

'Rocket' is defined here:

(lint/suspicious/noRedeclare)


[error] 15-15: Shouldn't redeclare 'Sparkles'. Consider to delete it or rename it.

'Sparkles' is defined here:

(lint/suspicious/noRedeclare)

apps/ui/src/components/views/board-view/shared/model-selector.tsx

[error] 6-7: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 8-9: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 9-9: expected ( but instead found custom

Remove custom

(parse)


[error] 9-9: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 9-9: expected ; but instead found )

Remove )

(parse)


[error] 9-10: Illegal use of an import declaration not at the top level

move this declaration to the top level

(parse)


[error] 14-15: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)


[error] 16-17: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 18-19: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 19-19: expected ( but instead found custom

Remove custom

(parse)


[error] 19-19: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 19-19: expected ; but instead found )

Remove )

(parse)


[error] 8-8: Shouldn't redeclare 'Brain'. Consider to delete it or rename it.

'Brain' is defined here:

(lint/suspicious/noRedeclare)


[error] 8-8: Shouldn't redeclare 'AlertTriangle'. Consider to delete it or rename it.

'AlertTriangle' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'CLAUDE_MODELS'. Consider to delete it or rename it.

'CLAUDE_MODELS' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'CURSOR_MODELS'. Consider to delete it or rename it.

'CURSOR_MODELS' is defined here:

(lint/suspicious/noRedeclare)


[error] 35-36: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 37-38: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 40-41: Expected a statement but instead found '>>>>>>> 81ff1d1 (fix: Address PR #406 review comments)'.

Expected a statement here.

(parse)


[error] 41-41: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 42-43: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 43-43: expected ( but instead found custom

Remove custom

(parse)


[error] 43-43: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 43-43: expected ; but instead found )

Remove )

(parse)


[error] 37-37: Shouldn't redeclare 'enabledCursorModels'. Consider to delete it or rename it.

'enabledCursorModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 37-37: Shouldn't redeclare 'cursorDefaultModel'. Consider to delete it or rename it.

'cursorDefaultModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'enabledCursorModels'. Consider to delete it or rename it.

'enabledCursorModels' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'cursorDefaultModel'. Consider to delete it or rename it.

'cursorDefaultModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'customEndpoint'. Consider to delete it or rename it.

'customEndpoint' is defined here:

(lint/suspicious/noRedeclare)


[error] 42-42: Shouldn't redeclare 'cursorCliStatus'. Consider to delete it or rename it.

'cursorCliStatus' is defined here:

(lint/suspicious/noRedeclare)


[error] 112-113: Expected a JSX attribute but instead found '<<<<<<'.

Expected a JSX attribute here.

(parse)


[error] 113-113: expected > but instead found <

Remove <

(parse)


[error] 111-113: Expected corresponding JSX closing tag for 'button'.

Opening tag

closing tag

(parse)


[error] 134-134: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 136-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 137-137: Unexpected token. Did you mean {'>'} or &gt;?

(parse)

Comment on lines +166 to +183
try {
// Use /v1/messages path (standard Anthropic API format)
const apiUrl = `${baseUrl}/v1/messages`;
logger.info(`Making request to custom endpoint: ${apiUrl}`);
console.log(
'[CustomProvider] Request body:',
JSON.stringify(requestBody, null, 2).substring(0, 500)
);

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify(requestBody),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/server/src/providers/custom-provider.ts | head -200

Repository: AutoMaker-Org/automaker

Length of output: 7351


🏁 Script executed:

# Search for Zhipu AI and MiniMax references in the codebase
rg -i "zhipu|minimax" --type ts --type tsx -A 3 -B 3

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for provider configuration
find apps/server/src -type f -name "*.ts" | xargs grep -l "provider" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 941


🏁 Script executed:

# Check if there's provider configuration or constants
rg "Anthropic.*compatible|provider.*config" --type ts --type tsx -A 2 -B 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🌐 Web query:

Zhipu AI GLM-4 API authentication headers requirements

💡 Result:

Required headers for Zhipu AI GLM-4 (OpenAI-compatible) requests — concise:

  • Content-Type: application/json. [1][2]
  • Accept (recommended): application/json. [2]
  • Authorization: Bearer <YOUR_API_KEY> (exact format: "Authorization: Bearer YOUR_API_KEY"). [1][2][3]

Examples in requests/cURL include those three headers. [1][2]

Sources: [1] Z.AI developer docs (API header examples). [2] Zhipu/Zenlayer API reference (Authorization = Bearer). [3] Tasking.ai Zhipu model guide.


🌐 Web query:

MiniMax API Anthropic-compatible authentication headers

💡 Result:

Short answer: use the standard Anthropic headers when calling MiniMax's Anthropic-compatible endpoints.

Required headers

  • x-api-key: YOUR_MINIMAX_API_KEY. (MiniMax docs instruct setting ANTHROPIC_API_KEY / Anthropic SDK will send x-api-key.) [1][2]
  • content-type: application/json. [2]
  • anthropic-version: (Anthropic APIs require a version header, e.g. 2023-06-01). [3]

Notes / alternatives

  • Some client libraries/providers accept Authorization: Bearer for compatibility; x-api-key takes precedence if both are present. If you use a provider (e.g. vercel/Minimax provider) it may send Authorization: Bearer by default—check that provider’s docs. [4][5]

Example (curl)
curl https://api.minimax.io/anthropic/v1/messages
-H "x-api-key: YOUR_MINIMAX_API_KEY"
-H "anthropic-version: 2023-06-01"
-H "content-type: application/json"
-d '{ "model":"MiniMax-M2.1","max_tokens":512,"messages":[{"role":"user","content":"Hello"}] }'

Sources:
[1] MiniMax Anthropic-compatible docs — Quick Start (set ANTHROPIC_API_KEY / base URL).
[2] MiniMax docs (Anthropic-compatible API examples).
[3] Anthropic API overview / examples (x-api-key and anthropic-version requirements).
[4] Poe / Anthropic-compatible provider notes on x-api-key vs Authorization: Bearer.
[5] vercel-minimax provider docs (sends API key via Authorization header by default; baseURL for Anthropic-compatible endpoints).


🏁 Script executed:

# Check if there's any conditional logic based on provider type in custom-provider.ts
grep -n "provider.*zhipu\|provider.*minimax\|customEndpoint.provider" apps/server/src/providers/custom-provider.ts

Repository: AutoMaker-Org/automaker

Length of output: 113


🏁 Script executed:

# Check the full executeQuery method to see if there's provider-specific handling
sed -n '102,250p' apps/server/src/providers/custom-provider.ts | cat -n

Repository: AutoMaker-Org/automaker

Length of output: 6343


Add provider-specific header handling for Zhipu AI authentication.

The code currently uses x-api-key headers for all providers, but this is incompatible with Zhipu AI, which requires Authorization: Bearer <token> format. MiniMax correctly uses x-api-key (Anthropic-compatible). Implement conditional header logic based on the customEndpoint.provider value:

  • Zhipu AI: Authorization: Bearer <apiKey>
  • MiniMax & manual: x-api-key: <apiKey> (Anthropic standard)

Without this, Zhipu AI requests will fail with authentication errors despite being documented as supported.

Also replace the console.log statements on lines 118–124 and 170–173 with logger.debug() for consistency.

🤖 Prompt for AI Agents
In @apps/server/src/providers/custom-provider.ts around lines 166 - 183, The
request headers always use 'x-api-key' which breaks Zhipu AI; update the header
construction around the fetch in the function that builds apiUrl/requestBody so
it checks customEndpoint.provider and sets Authorization: Bearer <apiKey> when
provider === 'zhipu' and retains x-api-key: <apiKey> for 'minimax' and 'manual';
also replace the two console.log calls that print request body/response with
logger.debug(...) for consistency (the console.log calls near the requestBody
logging and the later response logging in the same function).

Comment on lines +69 to +73
<<<<<<< HEAD
=======
import { isCursorModel, isCustomModel, PROVIDER_PREFIXES } from '@automaker/types';
import { getEndpointForModel } from '../../settings-view/providers/custom-provider-presets';
>>>>>>> ee96e164 (feat: Add per-provider API key storage for custom endpoints)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved git merge conflict markers.

This file contains multiple unresolved merge conflicts that will cause the build to fail. The conflict markers (<<<<<<< HEAD, =======, >>>>>>>) must be resolved before merging.

Conflicts found at:

  • Lines 69-73 (imports)
  • Lines 93-101 (FeatureData type)
  • Lines 178-191 (useAppStore destructuring)
  • Lines 341-389 (buildFeatureData return)
🧰 Tools
🪛 Biome (2.1.2)

[error] 72-73: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 73-73: expected ( but instead found custom

Remove custom

(parse)


[error] 73-73: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 73-73: expected ; but instead found )

Remove )

(parse)


[error] 71-71: Shouldn't redeclare 'isCursorModel'. Consider to delete it or rename it.

'isCursorModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 71-71: Shouldn't redeclare 'PROVIDER_PREFIXES'. Consider to delete it or rename it.

'PROVIDER_PREFIXES' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
around lines 69 - 73, The file contains unresolved git conflict markers that
must be removed and the variants merged: delete the markers (<<<<<<<, =======,
>>>>>>>) and reconcile the imports so the top import list includes
isCursorModel, isCustomModel, PROVIDER_PREFIXES and getEndpointForModel; merge
the FeatureData type by combining both conflicting definitions into a single
coherent type used by buildFeatureData; reconcile the useAppStore destructuring
so the hook call returns the complete set of properties expected elsewhere
(restore any keys removed by the conflict); and merge the buildFeatureData
return logic so it incorporates both existing behavior and the custom-endpoint
handling (e.g., usage of getEndpointForModel and provider constants) into one
correct implementation. Ensure no conflict markers remain and run tests/build to
verify.

Comment on lines +64 to +75
<<<<<<< HEAD
import {
isCursorModel,
isClaudeModel,
PROVIDER_PREFIXES,
supportsReasoningEffort,
} from '@automaker/types';
import { useNavigate } from '@tanstack/react-router';
=======
import { isCursorModel, isCustomModel, PROVIDER_PREFIXES } from '@automaker/types';
import { getEndpointForModel } from '../../settings-view/providers/custom-provider-presets';
>>>>>>> ee96e164 (feat: Add per-provider API key storage for custom endpoints)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved git merge conflict markers.

This file contains multiple unresolved merge conflicts that will cause the build to fail.

Conflicts found at:

  • Lines 64-75 (imports)
  • Lines 98-110 (onUpdate signature)
  • Lines 157-180 (state declarations)
  • Lines 279-325 (handleUpdate return object)

After resolving, ensure you keep:

  • isClaudeModel and supportsReasoningEffort imports (needed for model validation)
  • isCustomModel and getEndpointForModel imports (needed for custom endpoint logic)
  • Both state declaration blocks (model selection state AND worktrees/customEndpoint state)
🧰 Tools
🪛 Biome (2.1.2)

[error] 71-72: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 74-75: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 75-75: expected ( but instead found custom

Remove custom

(parse)


[error] 75-75: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 75-75: expected ; but instead found )

Remove )

(parse)


[error] 73-73: Shouldn't redeclare 'isCursorModel'. Consider to delete it or rename it.

'isCursorModel' is defined here:

(lint/suspicious/noRedeclare)


[error] 73-73: Shouldn't redeclare 'PROVIDER_PREFIXES'. Consider to delete it or rename it.

'PROVIDER_PREFIXES' is defined here:

(lint/suspicious/noRedeclare)

Comment on lines +4 to +9
<<<<<<< HEAD
import { Brain, AlertTriangle } from 'lucide-react';
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
=======
import { Brain, Bot, Terminal, AlertTriangle, Globe } from 'lucide-react';
>>>>>>> ee96e164 (feat: Add per-provider API key storage for custom endpoints)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved git merge conflict markers.

This file contains multiple unresolved merge conflicts that will cause the build to fail.

Conflicts found at:

  • Lines 4-9 (icon imports)
  • Lines 15-19 (model constants imports)
  • Lines 32-43 (store destructuring - three-way merge)
  • Lines 113-137 (provider toggle button - Codex vs Custom)

Important decision needed: The conflict at lines 113-137 shows a choice between a "Codex CLI" button and a "Custom" button. You likely need both buttons. Ensure the resolution includes all four provider options: Claude, Cursor, Codex, and Custom.

🧰 Tools
🪛 Biome (2.1.2)

[error] 6-7: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 8-9: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 9-9: expected ( but instead found custom

Remove custom

(parse)


[error] 9-9: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 9-9: expected ; but instead found )

Remove )

(parse)


[error] 8-8: Shouldn't redeclare 'Brain'. Consider to delete it or rename it.

'Brain' is defined here:

(lint/suspicious/noRedeclare)


[error] 8-8: Shouldn't redeclare 'AlertTriangle'. Consider to delete it or rename it.

'AlertTriangle' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In @apps/ui/src/components/views/board-view/shared/model-selector.tsx around
lines 4 - 9, This file has unresolved git conflict markers; remove all <<<<<<<,
=======, and >>>>>>> blocks and merge the variants so all needed imports and
options are present: combine icon imports to include Brain, Bot, Terminal,
AlertTriangle, Globe and also AnthropicIcon, CursorIcon, OpenAIIcon; reconcile
the model constants imports so both sides’ model identifiers are imported; fix
the three-way store destructuring by keeping all required store variables (e.g.,
any activeModelId / setActiveModelId / provider-related keys referenced in the
file) and ensure no duplicate declarations remain; and update the provider
toggle button area to include all four provider options (Claude, Cursor, Codex —
keep the “Codex CLI” button — and Custom) so both Codex and Custom buttons
coexist. Ensure the file builds with no conflict markers and all referenced
symbols (imports, store variables, provider buttons) are used consistently.

Comment on lines +30 to +40
<<<<<<< HEAD
REASONING_EFFORT_LEVELS,
REASONING_EFFORT_LABELS,
} from '@/components/views/board-view/shared/model-constants';
import { Check, ChevronsUpDown, Star, ChevronRight } from 'lucide-react';
import { AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon } from '@/components/ui/provider-icon';
=======
getCustomModels,
} from '@/components/views/board-view/shared/model-constants';
import { Check, ChevronsUpDown, Star, Brain, Sparkles, ChevronRight, Globe } from 'lucide-react';
>>>>>>> ee96e164 (feat: Add per-provider API key storage for custom endpoints)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved git merge conflict markers.

This file contains multiple unresolved merge conflicts that will cause the build to fail.

Conflicts found at:

  • Lines 30-40 (imports)
  • Lines 89-105 (useAppStore destructuring - appears to be a three-way merge)
  • Lines 992-1040 (CommandGroup rendering)

Note: After resolving, ensure you keep:

  • REASONING_EFFORT_LEVELS and REASONING_EFFORT_LABELS imports (needed for Codex model rendering)
  • getCustomModels import
  • Both customEndpoint and customEndpointConfigs from useAppStore
  • Provider icons: AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon plus Globe
🧰 Tools
🪛 Biome (2.1.2)

[error] 35-38: Expected a statement but instead found '=======
getCustomModels,
} from '@/components/views/board-view/shared/model-constants''.

Expected a statement here.

(parse)


[error] 39-40: Expected a statement but instead found '>>>>>>> ee96e16 (feat: Add per-provider API key storage'.

Expected a statement here.

(parse)


[error] 40-40: expected ( but instead found custom

Remove custom

(parse)


[error] 40-40: expected ; but instead found endpoints

Remove endpoints

(parse)


[error] 40-40: expected ; but instead found )

Remove )

(parse)


[error] 39-39: Shouldn't redeclare 'Check'. Consider to delete it or rename it.

'Check' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'ChevronsUpDown'. Consider to delete it or rename it.

'ChevronsUpDown' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'Star'. Consider to delete it or rename it.

'Star' is defined here:

(lint/suspicious/noRedeclare)


[error] 39-39: Shouldn't redeclare 'ChevronRight'. Consider to delete it or rename it.

'ChevronRight' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
around lines 30 - 40, The file has unresolved git merge markers; remove all
conflict markers (<<<<<<<, =======, >>>>>>>) and reconcile the three-way merges
so imports and usages are unified: keep REASONING_EFFORT_LEVELS and
REASONING_EFFORT_LABELS and also import getCustomModels; ensure the useAppStore
destructuring includes both customEndpoint and customEndpointConfigs; include
provider icons AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon and Globe in
the import list; and resolve the CommandGroup rendering conflict (the block
around CommandGroup) so only one correct JSX implementation remains. Ensure
references like REASONING_EFFORT_LEVELS, REASONING_EFFORT_LABELS,
getCustomModels, customEndpoint, customEndpointConfigs,
AnthropicIcon/CursorIcon/OpenAIIcon/OpenCodeIcon/Globe and CommandGroup are
present and used consistently.

Comment on lines +37 to +43
// Single provider endpoint configuration
export interface CustomEndpointConfig {
provider: 'zhipu' | 'minimax' | 'manual';
baseUrl: string;
apiKey: string;
model: string;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor: Move CustomEndpointConfig to shared types package.

The CustomEndpointConfig interface is duplicated in apps/server/src/providers/custom-provider.ts (lines 22-31). This violates DRY and creates a risk of inconsistency when the type evolves. Since this type is used across both UI and server boundaries, it should be defined once in a shared package (e.g., libs/types/src) and imported by both layers.

♻️ Recommended consolidation approach
  1. Move CustomEndpointConfig to libs/types/src/custom-endpoint.ts
  2. Export it from libs/types/src/index.ts
  3. Update imports in both UI and server:
// In apps/ui/src/components/views/settings-view/shared/types.ts
export type { CustomEndpointConfig } from '@automaker/types';

// In apps/server/src/providers/custom-provider.ts
import type { CustomEndpointConfig } from '@automaker/types';

This aligns with the coding guideline: "Always import from shared packages (@automaker/*), never from old relative paths".

🤖 Prompt for AI Agents
In @apps/ui/src/components/views/settings-view/shared/types.ts around lines 37 -
43, CustomEndpointConfig is duplicated across UI and server; move the single
interface into the shared types package and import it from there: create a new
file exporting the interface (e.g., libs/types/src/custom-endpoint.ts) and
re-export it from libs/types/src/index.ts, then replace local declarations by
importing/exporting the type in
apps/ui/src/components/views/settings-view/shared/types.ts (export type {
CustomEndpointConfig } from '@automaker/types') and importing it in
apps/server/src/providers/custom-provider.ts (import type { CustomEndpointConfig
} from '@automaker/types'), ensuring both places remove their local definitions.

@vikasagarwal101 vikasagarwal101 deleted the feature/custom-endpoint-per-provider-api-keys branch January 11, 2026 12:03
@vikasagarwal101 vikasagarwal101 restored the feature/custom-endpoint-per-provider-api-keys branch January 11, 2026 12:15
vikasagarwal101 and others added 6 commits January 11, 2026 17:45
…ovider-presets.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…ttings-tab.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ovider-presets.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@webdevcody webdevcody changed the base branch from main to v0.11.0rc January 14, 2026 15:19
@webdevcody webdevcody deleted the branch AutoMaker-Org:v0.11.0rc January 15, 2026 01:31
@webdevcody webdevcody closed this Jan 15, 2026
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.

Add support for GLM-4.7 and Minimax-2.1 models

2 participants