-
Notifications
You must be signed in to change notification settings - Fork 489
feat: Add Custom Endpoint provider support for Zhipu AI (GLM) and MiniMax models #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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 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. 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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. Comment |
Summary of ChangesHello @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
🧠 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 AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS]; | |
| export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS, ...getCustomModels()]; |
| if (Array.isArray(prompt)) { | ||
| messages.push({ | ||
| role: 'user', | ||
| content: prompt, | ||
| }); | ||
| } else { | ||
| messages.push({ | ||
| role: 'user', | ||
| content: prompt, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| customEndpoint: feature.customEndpoint as | ||
| | { baseUrl: string; apiKey: string; model: string } | ||
| | undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| /** | ||
| * Get the provider for a model string | ||
| * | ||
| * @param model - Model string to check | ||
| * @returns The provider type, defaults to 'claude' for unknown models | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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: MissingcustomEndpointinonUpdatetype signature.The
updatesobject passed toonUpdateincludescustomEndpoint(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 withoutcustomEndpoint. SinceProviderFactory.getProviderForModel(finalModel)is called withoutProviderOptions,CustomProviderwon’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 singularcustomEndpoint.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 usescustomEndpoint?.model, and at line 142 the warning checks!customEndpoint— both should be wired to the active provider's config fromcustomEndpointConfigs. 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 importingThinkingLevelfrom@automaker/typesinstead.Per coding guidelines, prefer importing from shared packages (
@automaker/*) over local paths.ThinkingLevelis exported from@automaker/typesvialibs/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: UsestripProviderPrefixinstead of hardcoded slice.The magic number
7(length of "custom-") is fragile.stripProviderPrefixfrom@automaker/typesis 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (20)
apps/server/src/providers/custom-provider.tsapps/server/src/providers/index.tsapps/server/src/providers/provider-factory.tsapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tsapps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsxapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxapps/ui/src/components/views/settings-view/shared/types.tsapps/ui/src/store/app-store.tslibs/model-resolver/src/resolver.tslibs/types/src/index.tslibs/types/src/provider-utils.tslibs/types/src/provider.tslibs/types/src/settings.tspackage.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.tsapps/ui/src/components/views/settings-view/shared/types.tsapps/server/src/providers/index.tsapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxlibs/model-resolver/src/resolver.tslibs/types/src/settings.tsapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/server/src/providers/custom-provider.tsapps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsxlibs/types/src/provider.tslibs/types/src/provider-utils.tsapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/server/src/providers/provider-factory.tsapps/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.tsapps/ui/src/components/views/settings-view/shared/types.tsapps/server/src/providers/index.tsapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxlibs/model-resolver/src/resolver.tslibs/types/src/settings.tsapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/server/src/providers/custom-provider.tsapps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsxlibs/types/src/provider.tslibs/types/src/provider-utils.tsapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/server/src/providers/provider-factory.tsapps/ui/src/store/app-store.ts
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor all server operations to emit events that stream to frontend via WebSocket
Files:
apps/server/src/providers/index.tsapps/server/src/providers/custom-provider.tsapps/server/src/services/auto-mode-service.tsapps/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.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsxapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/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.tsapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/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. UnlikeisCursorModel(), 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
isCustomModelexport 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
CustomProviderandCustomEndpointConfigexports are well-documented and follow the established pattern for provider exports.libs/types/src/settings.ts (1)
98-98: LGTM!The
ModelProvidertype 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 thecustom-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, soCustomProvider.detectInstallation()will report unconfigured. If the UI usescheckAllProviders()to decide whether to show/enable “Custom”, it may look broken even when runs provideExecuteOptions.customEndpoint.If you want “installed/authenticated” to reflect persisted custom settings, you’ll need a way to pass
ProviderOptions.customEndpointinto 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
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing abort wiring + fragile URL join/SSE parsing.
fetch()should useoptions.abortController?.signal, and the stream loop should stop/cancel on abort—otherwise “Stop” won’t reliably stop the upstream request.const apiUrl = \${baseUrl}/v1/messages`` will misbehave with trailing slashes. Prefer normalizing/joining.- 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.
| customEndpoint: feature.customEndpoint as | ||
| | { baseUrl: string; apiKey: string; model: string } | ||
| | undefined, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
wc -l apps/server/src/services/auto-mode-service.tsRepository: AutoMaker-Org/automaker
Length of output: 117
🏁 Script executed:
sed -n '575,590p' apps/server/src/services/auto-mode-service.tsRepository: AutoMaker-Org/automaker
Length of output: 721
🏁 Script executed:
sed -n '1975,2000p' apps/server/src/services/auto-mode-service.tsRepository: AutoMaker-Org/automaker
Length of output: 859
🏁 Script executed:
sed -n '2115,2140p' apps/server/src/services/auto-mode-service.tsRepository: 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=3Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Find ExecuteOptions type
rg -A 10 "type ExecuteOptions|interface ExecuteOptions" apps/server/src/ --max-count=3Repository: 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 -50Repository: 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 3Repository: 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 2Repository: 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 -20Repository: 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.tsRepository: 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.tsRepository: 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 3Repository: 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 -100Repository: 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=5Repository: 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 20Repository: AutoMaker-Org/automaker
Length of output: 2510
🏁 Script executed:
# Get the full ExecuteOptions definition
sed -n '1,200p' libs/types/src/provider.ts | tail -100Repository: 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 5Repository: 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 3Repository: 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.tsRepository: 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 3Repository: 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 2Repository: 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 -5Repository: 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 2Repository: 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 5Repository: AutoMaker-Org/automaker
Length of output: 5492
🏁 Script executed:
# Look at custom-provider validation
sed -n '1,150p' apps/server/src/providers/custom-provider.tsRepository: 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 5Repository: 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:
- Add explicit
customEndpoint?: CustomEndpointConfigtype to theFeatureinterface (or document that it must never be persisted) - Validate the shape of
customEndpointat feature load time, not provider runtime - Exclude
customEndpointfromfeature.jsonserialization (filter beforeJSON.stringify), or explicitly retrieve it from secure settings instead
Also applies to: lines 1981-1994, 2121-2134
apps/ui/src/components/views/board-view/shared/model-constants.ts
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsx
Show resolved
Hide resolved
| import type { | ||
| CustomEndpointConfig, | ||
| CustomEndpointConfigs, | ||
| } from '@/components/views/settings-view/shared/types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file exists and get its size
wc -l apps/ui/src/store/app-store.tsRepository: 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.tsRepository: 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 -100Repository: 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 -20Repository: 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.tsRepository: AutoMaker-Org/automaker
Length of output: 4935
🏁 Script executed:
# Search for setCustomEndpointForProvider method
rg -n "setCustomEndpointForProvider" apps/ui/src/store/app-store.tsRepository: AutoMaker-Org/automaker
Length of output: 167
🏁 Script executed:
# Read the setCustomEndpointForProvider method implementation
sed -n '734,760p' apps/ui/src/store/app-store.tsRepository: 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.tsRepository: 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 -30Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Search for ModelSelector component in the codebase
fd -type f -name "*ModelSelector*" apps/ui/srcRepository: 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 tsxRepository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
# Search for customEndpoint usage across the codebase
rg "customEndpoint" apps/ui/srcRepository: 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.tsxRepository: 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.tsxRepository: 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 -80Repository: 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.
There was a problem hiding this 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
📒 Files selected for processing (8)
apps/server/src/providers/custom-provider.tsapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxlibs/types/src/feature.tslibs/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.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxlibs/types/src/provider.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/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.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxlibs/types/src/provider.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/server/src/providers/custom-provider.ts
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor 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.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/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.tsxapps/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
customEndpointproperty is well-structured and clearly documented. The type definition is consistent withExecuteOptions.customEndpointin the provider types, ensuring type safety across the feature-to-execution flow.libs/types/src/provider.ts (1)
93-102: LGTM!The
customEndpointproperty addition toExecuteOptionsis well-documented and type-safe. The structure matches theFeature.customEndpointtype, 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
CustomEndpointConfiginterface 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/messagespath- 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
detectInstallationmethod appropriately checks for the presence of configuration rather than a binary installation, which makes sense for API-based providers.
326-362: LGTM!The
validateConfigmethod provides comprehensive validation with helpful error messages and appropriate warnings. URL validation using theURLconstructor 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
customEndpointandcustomEndpointConfigs, 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
availableCustomModelssince it's now used within thecurrentModelmemo.
| console.log('[CustomProvider] Using config:', { | ||
| baseUrl, | ||
| apiKeyPresent: !!apiKey, | ||
| apiKeyLength: apiKey?.length || 0, | ||
| apiKeyPrefix: apiKey?.substring(0, 8) + '...', | ||
| model: options.model, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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; | ||
| })(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent model in final fallback and duplicate prefix stripping.
The customEndpoint resolution logic has two issues:
-
Line 228: In the final fallback branch,
customEndpoint.modelis used instead of the user-selectedselectedModel. This means if a user selects a different custom model, the fallback will ignore their choice and use the global config's model. -
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.
| // 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; | |
| })(), |
| } 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}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {/* 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> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Repository: 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=tsRepository: 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 -100Repository: 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 3Repository: 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 2Repository: 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 -30Repository: 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 -nRepository: 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -10Repository: 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 -10Repository: 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 catRepository: 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 -B1Repository: 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).
| // 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
81ff1d1 to
c683d94
Compare
There was a problem hiding this 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
runAgentwithout passingcustomEndpoint, even though thefeatureobject (which containscustomEndpoint) 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
runAgentcall doesn't receive thecustomEndpointconfiguration, even though thefeatureobject 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:
ModelAliastype import (used by CLAUDE_MODELS typing)ReasoningEfforttype import (used by REASONING_EFFORT_LEVELS)CODEX_MODEL_MAPandOPENCODE_MODELSimports- Provider icon imports
getCustomModelsfunction and related importsapps/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
optionsparameter, 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.logfor debug logging while the rest of the codebase uses theloggerutility. 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (21)
apps/server/src/providers/custom-provider.tsapps/server/src/providers/index.tsapps/server/src/providers/provider-factory.tsapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/board-view/shared/model-constants.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tsapps/ui/src/components/views/settings-view/providers/custom-settings-tab.tsxapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxapps/ui/src/components/views/settings-view/shared/types.tsapps/ui/src/store/app-store.tslibs/model-resolver/src/resolver.tslibs/types/src/feature.tslibs/types/src/index.tslibs/types/src/provider-utils.tslibs/types/src/provider.tslibs/types/src/settings.tspackage.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.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxlibs/types/src/feature.tsapps/ui/src/components/views/settings-view/shared/types.tsapps/ui/src/store/app-store.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxlibs/model-resolver/src/resolver.tsapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tslibs/types/src/provider.tsapps/server/src/providers/custom-provider.tsapps/ui/src/components/views/board-view/shared/model-constants.tsapps/server/src/providers/provider-factory.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/server/src/services/auto-mode-service.tsapps/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.tsapps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxlibs/types/src/feature.tsapps/ui/src/components/views/settings-view/shared/types.tsapps/ui/src/store/app-store.tsapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxlibs/model-resolver/src/resolver.tsapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxapps/ui/src/components/views/settings-view/providers/custom-provider-presets.tslibs/types/src/provider.tsapps/server/src/providers/custom-provider.tsapps/ui/src/components/views/board-view/shared/model-constants.tsapps/server/src/providers/provider-factory.tsapps/ui/src/components/views/board-view/shared/model-selector.tsxapps/server/src/services/auto-mode-service.tsapps/server/src/providers/index.ts
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor all server operations to emit events that stream to frontend via WebSocket
Files:
apps/server/src/providers/custom-provider.tsapps/server/src/providers/provider-factory.tsapps/server/src/services/auto-mode-service.tsapps/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.tsxapps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsxapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxapps/ui/src/components/views/settings-view/providers/provider-tabs.tsxapps/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.tsxapps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsxlibs/model-resolver/src/resolver.tsapps/ui/src/components/views/board-view/shared/model-constants.tsapps/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.tsxapps/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 }?
(parse)
[error] 300-300: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 301-301: Unexpected token. Did you mean {'}'} or }?
(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 }?
(parse)
[error] 311-311: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 312-312: Unexpected token. Did you mean {'}'} or }?
(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 }?
(parse)
[error] 321-321: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 324-324: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 324-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 325-325: Unexpected token. Did you mean {'>'} or >?
(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 >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 1040-1040: Unexpected token. Did you mean {'>'} or >?
(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 }?
(parse)
[error] 347-347: Unexpected token. Did you mean {'>'} or >?
(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 }?
(parse)
[error] 363-363: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 364-364: Unexpected token. Did you mean {'}'} or }?
(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 }?
(parse)
[error] 374-374: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 375-375: Unexpected token. Did you mean {'}'} or }?
(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 }?
(parse)
[error] 384-384: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 387-387: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 388-388: Unexpected token. Did you mean {'}'} or }?
(parse)
[error] 388-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 389-389: Unexpected token. Did you mean {'>'} or >?
(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 >?
(parse)
[error] 136-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 137-137: Unexpected token. Did you mean {'>'} or >?
(parse)
| 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), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/server/src/providers/custom-provider.ts | head -200Repository: 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 3Repository: 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 -20Repository: 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 2Repository: 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.tsRepository: 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 -nRepository: 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).
| <<<<<<< 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <<<<<<< 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
isClaudeModelandsupportsReasoningEffortimports (needed for model validation)isCustomModelandgetEndpointForModelimports (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)
| <<<<<<< 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <<<<<<< 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_LEVELSandREASONING_EFFORT_LABELSimports (needed for Codex model rendering)getCustomModelsimport- Both
customEndpointandcustomEndpointConfigsfrom useAppStore - Provider icons:
AnthropicIcon,CursorIcon,OpenAIIcon,OpenCodeIconplusGlobe
🧰 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.
apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
Outdated
Show resolved
Hide resolved
apps/ui/src/components/views/settings-view/providers/custom-provider-presets.ts
Show resolved
Hide resolved
| // Single provider endpoint configuration | ||
| export interface CustomEndpointConfig { | ||
| provider: 'zhipu' | 'minimax' | 'manual'; | ||
| baseUrl: string; | ||
| apiKey: string; | ||
| model: string; | ||
| } |
There was a problem hiding this comment.
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
- Move
CustomEndpointConfigtolibs/types/src/custom-endpoint.ts - Export it from
libs/types/src/index.ts - 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.
…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>
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
https://api.z.ai/api/anthropichttps://api.minimax.io/anthropicPer-Provider API Key Storage
Model Selection Integration
custom-(e.g.,custom-glm-4.7)Technical Changes
Provider System
CustomProviderfor Anthropic-compatible endpointscustomtoModelProvidertypeisCustomModel()utility function/v1/messagesfor Anthropic compatibilityUI Components
CustomSettingsTabfor provider configurationState Management
CustomEndpointConfigsfor per-provider storagecustomEndpointConfigsto app store with persistencesetCustomEndpointForProvider,getCustomEndpointForProviderConfiguration Flow
getEndpointForModel()maps model names to correct provider endpointsRelated Issue
Closes #372
Testing
npm run build)npm run format:check)Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.