ideaco 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- package/tailwind.config.mjs +11 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Provider Registry - Outsourcing vendor marketplace
|
|
3
|
+
* Different job categories map to different model providers
|
|
4
|
+
* Providers need API Key configuration before enabling recruitment
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Job category enum
|
|
8
|
+
export const JobCategory = {
|
|
9
|
+
GENERAL: 'general', // General positions (text processing, analysis, coding, etc.)
|
|
10
|
+
DRAWING: 'drawing', // Drawing/illustration positions
|
|
11
|
+
MUSIC: 'music', // Music positions
|
|
12
|
+
VIDEO: 'video', // Video positions
|
|
13
|
+
CLI: 'cli', // CLI coding assistant positions (local CLI tools)
|
|
14
|
+
BROWSER: 'browser', // Browser DOM-based web chat (no API key needed)
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Job category label mapping (i18n keys for frontend)
|
|
18
|
+
export const JobCategoryLabel = {
|
|
19
|
+
[JobCategory.GENERAL]: 'general',
|
|
20
|
+
[JobCategory.DRAWING]: 'drawing',
|
|
21
|
+
[JobCategory.MUSIC]: 'music',
|
|
22
|
+
[JobCategory.VIDEO]: 'video',
|
|
23
|
+
[JobCategory.CLI]: 'cli',
|
|
24
|
+
[JobCategory.BROWSER]: 'browser',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Model Providers (outsourcing vendor marketplace)
|
|
28
|
+
export const ModelProviders = {
|
|
29
|
+
// === General Position Providers ===
|
|
30
|
+
OPENAI_GPT4: {
|
|
31
|
+
id: 'openai-gpt4',
|
|
32
|
+
name: 'OpenAI GPT-4',
|
|
33
|
+
provider: 'OpenAI',
|
|
34
|
+
model: 'gpt-4-turbo',
|
|
35
|
+
category: JobCategory.GENERAL,
|
|
36
|
+
capabilities: ['text-generation', 'coding', 'data-analysis', 'reasoning', 'translation'],
|
|
37
|
+
costPerToken: 0.03,
|
|
38
|
+
priceLabel: '$0.03/1K tokens',
|
|
39
|
+
priceLevel: 3, // 1=cheap 2=moderate 3=expensive
|
|
40
|
+
rating: 95, // Overall score 0-100
|
|
41
|
+
description: 'Most powerful general model, ideal for complex reasoning and coding tasks',
|
|
42
|
+
apiKey: '',
|
|
43
|
+
enabled: false,
|
|
44
|
+
},
|
|
45
|
+
OPENAI_GPT35: {
|
|
46
|
+
id: 'openai-gpt35',
|
|
47
|
+
name: 'OpenAI GPT-3.5',
|
|
48
|
+
provider: 'OpenAI',
|
|
49
|
+
model: 'gpt-3.5-turbo',
|
|
50
|
+
category: JobCategory.GENERAL,
|
|
51
|
+
capabilities: ['text-generation', 'simple-coding', 'translation', 'summarization'],
|
|
52
|
+
costPerToken: 0.002,
|
|
53
|
+
priceLabel: '$0.002/1K tokens',
|
|
54
|
+
priceLevel: 1,
|
|
55
|
+
rating: 72,
|
|
56
|
+
description: 'Cost-effective general model for simple tasks',
|
|
57
|
+
apiKey: '',
|
|
58
|
+
enabled: false,
|
|
59
|
+
},
|
|
60
|
+
ANTHROPIC_CLAUDE: {
|
|
61
|
+
id: 'anthropic-claude',
|
|
62
|
+
name: 'Anthropic Claude 3.5',
|
|
63
|
+
provider: 'Anthropic',
|
|
64
|
+
model: 'claude-3.5-sonnet',
|
|
65
|
+
category: JobCategory.GENERAL,
|
|
66
|
+
capabilities: ['text-generation', 'coding', 'data-analysis', 'long-context', 'reasoning'],
|
|
67
|
+
costPerToken: 0.015,
|
|
68
|
+
priceLabel: '$0.015/1K tokens',
|
|
69
|
+
priceLevel: 2,
|
|
70
|
+
rating: 93,
|
|
71
|
+
description: 'Excels at long-context understanding and precise analysis',
|
|
72
|
+
apiKey: '',
|
|
73
|
+
enabled: false,
|
|
74
|
+
},
|
|
75
|
+
DEEPSEEK: {
|
|
76
|
+
id: 'deepseek-v3',
|
|
77
|
+
name: 'DeepSeek V3',
|
|
78
|
+
provider: 'DeepSeek',
|
|
79
|
+
model: 'deepseek-chat',
|
|
80
|
+
category: JobCategory.GENERAL,
|
|
81
|
+
capabilities: ['text-generation', 'coding', 'math-reasoning', 'data-analysis'],
|
|
82
|
+
costPerToken: 0.001,
|
|
83
|
+
priceLabel: '$0.001/1K tokens',
|
|
84
|
+
priceLevel: 1,
|
|
85
|
+
rating: 88,
|
|
86
|
+
description: 'High cost-performance model with outstanding coding capability',
|
|
87
|
+
apiKey: '',
|
|
88
|
+
enabled: false,
|
|
89
|
+
},
|
|
90
|
+
QWEN: {
|
|
91
|
+
id: 'qwen-max',
|
|
92
|
+
name: 'Qwen Max',
|
|
93
|
+
provider: 'Alibaba Cloud',
|
|
94
|
+
model: 'qwen-max',
|
|
95
|
+
category: JobCategory.GENERAL,
|
|
96
|
+
capabilities: ['text-generation', 'coding', 'data-analysis', 'reasoning', 'translation'],
|
|
97
|
+
costPerToken: 0.004,
|
|
98
|
+
priceLabel: '$0.004/1K tokens',
|
|
99
|
+
priceLevel: 1,
|
|
100
|
+
rating: 86,
|
|
101
|
+
description: 'Alibaba Cloud flagship model with excellent multilingual support',
|
|
102
|
+
apiKey: '',
|
|
103
|
+
enabled: false,
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// === Drawing Position Providers ===
|
|
107
|
+
DALLE3: {
|
|
108
|
+
id: 'openai-dalle3',
|
|
109
|
+
name: 'DALL·E 3',
|
|
110
|
+
provider: 'OpenAI',
|
|
111
|
+
model: 'dall-e-3',
|
|
112
|
+
category: JobCategory.DRAWING,
|
|
113
|
+
capabilities: ['text-to-image', 'illustration', 'ui-design', 'concept-art'],
|
|
114
|
+
costPerImage: 0.04,
|
|
115
|
+
priceLabel: '$0.04/image',
|
|
116
|
+
priceLevel: 2,
|
|
117
|
+
rating: 90,
|
|
118
|
+
description: 'High-quality text-to-image model',
|
|
119
|
+
apiKey: '',
|
|
120
|
+
enabled: false,
|
|
121
|
+
},
|
|
122
|
+
MIDJOURNEY: {
|
|
123
|
+
id: 'midjourney-v6',
|
|
124
|
+
name: 'Midjourney V6',
|
|
125
|
+
provider: 'Midjourney',
|
|
126
|
+
model: 'midjourney-v6',
|
|
127
|
+
category: JobCategory.DRAWING,
|
|
128
|
+
capabilities: ['art-creation', 'concept-art', 'stylized-illustration', 'photography-style'],
|
|
129
|
+
costPerImage: 0.05,
|
|
130
|
+
priceLabel: '$0.05/image',
|
|
131
|
+
priceLevel: 3,
|
|
132
|
+
rating: 96,
|
|
133
|
+
description: 'Top choice for art creation with powerful stylization',
|
|
134
|
+
apiKey: '',
|
|
135
|
+
enabled: false,
|
|
136
|
+
},
|
|
137
|
+
STABLE_DIFFUSION: {
|
|
138
|
+
id: 'stability-sdxl',
|
|
139
|
+
name: 'Stable Diffusion XL',
|
|
140
|
+
provider: 'Stability AI',
|
|
141
|
+
model: 'sdxl-1.0',
|
|
142
|
+
category: JobCategory.DRAWING,
|
|
143
|
+
capabilities: ['text-to-image', 'image-to-image', 'style-transfer', 'fine-control'],
|
|
144
|
+
costPerImage: 0.02,
|
|
145
|
+
priceLabel: '$0.02/image',
|
|
146
|
+
priceLevel: 1,
|
|
147
|
+
rating: 82,
|
|
148
|
+
description: 'Open-source controllable image generation model',
|
|
149
|
+
apiKey: '',
|
|
150
|
+
enabled: false,
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// === Music Position Providers ===
|
|
154
|
+
SUNO: {
|
|
155
|
+
id: 'suno-v3',
|
|
156
|
+
name: 'Suno V3.5',
|
|
157
|
+
provider: 'Suno',
|
|
158
|
+
model: 'suno-v3.5',
|
|
159
|
+
category: JobCategory.MUSIC,
|
|
160
|
+
capabilities: ['songwriting', 'scoring', 'lyrics', 'multi-genre'],
|
|
161
|
+
costPerTrack: 0.1,
|
|
162
|
+
priceLabel: '$0.10/track',
|
|
163
|
+
priceLevel: 2,
|
|
164
|
+
rating: 91,
|
|
165
|
+
description: 'All-in-one AI music creation',
|
|
166
|
+
apiKey: '',
|
|
167
|
+
enabled: false,
|
|
168
|
+
},
|
|
169
|
+
UDIO: {
|
|
170
|
+
id: 'udio-v1',
|
|
171
|
+
name: 'Udio',
|
|
172
|
+
provider: 'Udio',
|
|
173
|
+
model: 'udio-v1.5',
|
|
174
|
+
category: JobCategory.MUSIC,
|
|
175
|
+
capabilities: ['music-generation', 'vocal-synthesis', 'arrangement', 'mixing'],
|
|
176
|
+
costPerTrack: 0.08,
|
|
177
|
+
priceLabel: '$0.08/track',
|
|
178
|
+
priceLevel: 1,
|
|
179
|
+
rating: 85,
|
|
180
|
+
description: 'High-fidelity music generation',
|
|
181
|
+
apiKey: '',
|
|
182
|
+
enabled: false,
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// === Video Position Providers ===
|
|
186
|
+
RUNWAY: {
|
|
187
|
+
id: 'runway-gen3',
|
|
188
|
+
name: 'Runway Gen-3',
|
|
189
|
+
provider: 'Runway',
|
|
190
|
+
model: 'gen-3-alpha',
|
|
191
|
+
category: JobCategory.VIDEO,
|
|
192
|
+
capabilities: ['text-to-video', 'image-to-video', 'video-editing', 'vfx'],
|
|
193
|
+
costPerSecond: 0.5,
|
|
194
|
+
priceLabel: '$0.50/sec',
|
|
195
|
+
priceLevel: 3,
|
|
196
|
+
rating: 92,
|
|
197
|
+
description: 'Professional-grade AI video generation',
|
|
198
|
+
apiKey: '',
|
|
199
|
+
enabled: false,
|
|
200
|
+
},
|
|
201
|
+
PIKA: {
|
|
202
|
+
id: 'pika-v2',
|
|
203
|
+
name: 'Pika 2.0',
|
|
204
|
+
provider: 'Pika',
|
|
205
|
+
model: 'pika-2.0',
|
|
206
|
+
category: JobCategory.VIDEO,
|
|
207
|
+
capabilities: ['short-video', 'animation', 'video-effects'],
|
|
208
|
+
costPerSecond: 0.3,
|
|
209
|
+
priceLabel: '$0.30/sec',
|
|
210
|
+
priceLevel: 2,
|
|
211
|
+
rating: 84,
|
|
212
|
+
description: 'Lightweight video generation',
|
|
213
|
+
apiKey: '',
|
|
214
|
+
enabled: false,
|
|
215
|
+
},
|
|
216
|
+
KLING: {
|
|
217
|
+
id: 'kling-v1',
|
|
218
|
+
name: 'Kling AI',
|
|
219
|
+
provider: 'Kuaishou',
|
|
220
|
+
model: 'kling-v1',
|
|
221
|
+
category: JobCategory.VIDEO,
|
|
222
|
+
capabilities: ['text-to-video', 'image-to-video', 'video-continuation'],
|
|
223
|
+
costPerSecond: 0.2,
|
|
224
|
+
priceLabel: '$0.20/sec',
|
|
225
|
+
priceLevel: 1,
|
|
226
|
+
rating: 80,
|
|
227
|
+
description: 'Cost-effective video generation model',
|
|
228
|
+
apiKey: '',
|
|
229
|
+
enabled: false,
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// === CLI Coding Assistant Providers ===
|
|
233
|
+
// CLI providers are auto-populated from CLI Backend Registry at runtime.
|
|
234
|
+
// They appear in the Brain Providers board as a separate "CLI" category.
|
|
235
|
+
// When enabled, HR can recruit agents that use local CLI tools as execution engines.
|
|
236
|
+
|
|
237
|
+
// === Browser (DOM-based) Providers ===
|
|
238
|
+
// These use Electron's hidden BrowserWindow to interact with web chat UIs via DOM scripting.
|
|
239
|
+
// The user logs in once via browser, then IdeaCo controls the page automatically.
|
|
240
|
+
CHATGPT_WEB: {
|
|
241
|
+
id: 'web-chatgpt-4o',
|
|
242
|
+
name: 'ChatGPT (Browser)',
|
|
243
|
+
provider: 'OpenAI Web',
|
|
244
|
+
model: 'chatgpt-4o-latest',
|
|
245
|
+
webModel: 'auto', // Model identifier used in ChatGPT web API
|
|
246
|
+
category: JobCategory.BROWSER,
|
|
247
|
+
capabilities: ['text-generation', 'coding', 'data-analysis', 'reasoning', 'translation'],
|
|
248
|
+
costPerToken: 0,
|
|
249
|
+
priceLabel: 'Free (browser)',
|
|
250
|
+
priceLevel: 1,
|
|
251
|
+
rating: 95,
|
|
252
|
+
description: 'ChatGPT via browser DOM automation — no API key needed, uses your existing ChatGPT subscription',
|
|
253
|
+
apiKey: '', // Not used — session managed by Electron's Chromium
|
|
254
|
+
cookie: '', // Legacy field, kept for backward compatibility
|
|
255
|
+
enabled: false,
|
|
256
|
+
isWeb: true,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Provider Registry - Manages all model providers
|
|
262
|
+
*/
|
|
263
|
+
export class ProviderRegistry {
|
|
264
|
+
constructor() {
|
|
265
|
+
this.providers = new Map();
|
|
266
|
+
// Register all built-in providers
|
|
267
|
+
Object.values(ModelProviders).forEach(p => this.register({ ...p }));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Sync CLI backends from CLIBackendRegistry into providers.
|
|
272
|
+
* Called at startup and after CLI detection to keep the two systems in sync.
|
|
273
|
+
* CLI backends become providers under the 'cli' category.
|
|
274
|
+
* @param {import('./agent/cli-agent/backends/registry.js').CLIBackendRegistry} cliRegistry
|
|
275
|
+
*/
|
|
276
|
+
syncCLIBackends(cliRegistry) {
|
|
277
|
+
if (!cliRegistry) return;
|
|
278
|
+
const backends = cliRegistry.listAll();
|
|
279
|
+
for (const b of backends) {
|
|
280
|
+
const providerId = `cli-${b.id}`;
|
|
281
|
+
const existing = this.providers.get(providerId);
|
|
282
|
+
// Preserve existing enabled/apiKey state if already registered
|
|
283
|
+
const wasEnabled = existing?.enabled || false;
|
|
284
|
+
this.providers.set(providerId, {
|
|
285
|
+
id: providerId,
|
|
286
|
+
name: b.name,
|
|
287
|
+
provider: 'Local CLI',
|
|
288
|
+
model: b.execCommand,
|
|
289
|
+
category: JobCategory.CLI,
|
|
290
|
+
capabilities: ['coding', 'file-operations', 'shell-execution', 'code-review', 'refactoring'],
|
|
291
|
+
costPerToken: 0,
|
|
292
|
+
priceLabel: 'Free (local)',
|
|
293
|
+
priceLevel: 1,
|
|
294
|
+
rating: b.rating || 80, // Use CLI's own rating (defined in BUILTIN_BACKENDS)
|
|
295
|
+
description: b.description || `${b.name} - local CLI coding assistant`,
|
|
296
|
+
// CLI providers don't need API keys; they use local CLI authentication
|
|
297
|
+
// Detected/configured CLIs are auto-enabled, otherwise keep manual toggle state
|
|
298
|
+
apiKey: (b.state === 'detected' || b.state === 'configured') ? 'cli-local' : '',
|
|
299
|
+
enabled: (b.state === 'detected' || b.state === 'configured') ? true : wasEnabled || false,
|
|
300
|
+
// Extra CLI metadata
|
|
301
|
+
isCLI: true,
|
|
302
|
+
cliBackendId: b.id,
|
|
303
|
+
cliState: b.state,
|
|
304
|
+
cliVersion: b.version,
|
|
305
|
+
cliIcon: b.icon,
|
|
306
|
+
// TODO: hide CodeBuddy from external users in the future
|
|
307
|
+
// hidden: b.id === 'codebuddy',
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Register a new model provider */
|
|
313
|
+
register(providerConfig) {
|
|
314
|
+
this.providers.set(providerConfig.id, providerConfig);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Get provider by ID */
|
|
318
|
+
getById(id) {
|
|
319
|
+
return this.providers.get(id);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Configure a provider's API Key
|
|
324
|
+
* @param {string} id - Provider ID
|
|
325
|
+
* @param {string} apiKey - API Key
|
|
326
|
+
* @returns {object} Updated provider config
|
|
327
|
+
*/
|
|
328
|
+
configure(id, apiKey) {
|
|
329
|
+
const provider = this.providers.get(id);
|
|
330
|
+
if (!provider) throw new Error(`Provider not found: ${id}`);
|
|
331
|
+
// CLI providers use toggle instead of API key
|
|
332
|
+
if (provider.isCLI) {
|
|
333
|
+
provider.enabled = !!apiKey;
|
|
334
|
+
return provider;
|
|
335
|
+
}
|
|
336
|
+
// Web/browser providers use DOM-based session (cookie field kept for backward compat)
|
|
337
|
+
if (provider.isWeb) {
|
|
338
|
+
provider.cookie = apiKey; // apiKey field carries the cookie string
|
|
339
|
+
provider.enabled = !!apiKey;
|
|
340
|
+
return provider;
|
|
341
|
+
}
|
|
342
|
+
provider.apiKey = apiKey;
|
|
343
|
+
provider.enabled = !!apiKey;
|
|
344
|
+
return provider;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Manually enable/disable a provider
|
|
349
|
+
*/
|
|
350
|
+
setEnabled(id, enabled) {
|
|
351
|
+
const provider = this.providers.get(id);
|
|
352
|
+
if (!provider) throw new Error(`Provider not found: ${id}`);
|
|
353
|
+
// CLI providers can be toggled without API key check
|
|
354
|
+
if (provider.isCLI) {
|
|
355
|
+
provider.enabled = enabled;
|
|
356
|
+
return provider;
|
|
357
|
+
}
|
|
358
|
+
// Web providers use DOM-based browser session (cookie field kept for backward compat)
|
|
359
|
+
if (provider.isWeb) {
|
|
360
|
+
if (enabled && !provider.cookie) {
|
|
361
|
+
throw new Error(`Provider ${provider.name} has no browser session configured, cannot enable`);
|
|
362
|
+
}
|
|
363
|
+
provider.enabled = enabled;
|
|
364
|
+
return provider;
|
|
365
|
+
}
|
|
366
|
+
if (enabled && !provider.apiKey) {
|
|
367
|
+
throw new Error(`Provider ${provider.name} has no API Key configured, cannot enable`);
|
|
368
|
+
}
|
|
369
|
+
provider.enabled = enabled;
|
|
370
|
+
return provider;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Get available (enabled) providers by job category.
|
|
374
|
+
* Browser providers are also returned when querying 'general' (they share capabilities). */
|
|
375
|
+
getByCategory(category) {
|
|
376
|
+
return [...this.providers.values()].filter(
|
|
377
|
+
p => (p.category === category || (category === 'general' && p.category === 'browser')) && p.enabled
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Get all providers by job category (including disabled) */
|
|
382
|
+
getAllByCategory(category) {
|
|
383
|
+
return [...this.providers.values()].filter(
|
|
384
|
+
p => p.category === category || (category === 'general' && p.category === 'browser')
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Check if a category has any enabled provider */
|
|
389
|
+
hasCategoryEnabled(category) {
|
|
390
|
+
return this.getByCategory(category).length > 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Recommend best provider for a job category (from enabled ones)
|
|
395
|
+
* Strategy: composite score = rating * 0.6 + (100 - priceLevel*20) * 0.4
|
|
396
|
+
* i.e. high-rating + low-price gets priority
|
|
397
|
+
*/
|
|
398
|
+
recommend(category, requirements = []) {
|
|
399
|
+
let candidates = this.getByCategory(category);
|
|
400
|
+
if (candidates.length === 0) return null;
|
|
401
|
+
|
|
402
|
+
// If specific capability requirements exist, prefer matches
|
|
403
|
+
if (requirements.length > 0) {
|
|
404
|
+
const matched = candidates.filter(p =>
|
|
405
|
+
requirements.every(req => p.capabilities.includes(req))
|
|
406
|
+
);
|
|
407
|
+
if (matched.length > 0) candidates = matched;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Sort by cost-performance: high rating + low price
|
|
411
|
+
candidates.sort((a, b) => {
|
|
412
|
+
const scoreA = (a.rating || 80) * 0.6 + (100 - (a.priceLevel || 2) * 20) * 0.4;
|
|
413
|
+
const scoreB = (b.rating || 80) * 0.6 + (100 - (b.priceLevel || 2) * 20) * 0.4;
|
|
414
|
+
return scoreB - scoreA;
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return candidates[0];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** List all providers */
|
|
421
|
+
listAll() {
|
|
422
|
+
return [...this.providers.values()];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** List all enabled providers */
|
|
426
|
+
listEnabled() {
|
|
427
|
+
return [...this.providers.values()].filter(p => p.enabled);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Get provider statistics (grouped by category, with enabled count) */
|
|
431
|
+
getStats() {
|
|
432
|
+
const stats = {};
|
|
433
|
+
for (const cat of Object.values(JobCategory)) {
|
|
434
|
+
// For stats display, use exact category match (don't merge browser into general)
|
|
435
|
+
const all = [...this.providers.values()].filter(p => p.category === cat);
|
|
436
|
+
// Skip empty CLI category if no CLI backends registered
|
|
437
|
+
if (cat === JobCategory.CLI && all.length === 0) continue;
|
|
438
|
+
const enabled = all.filter(p => p.enabled);
|
|
439
|
+
stats[cat] = {
|
|
440
|
+
label: JobCategoryLabel[cat],
|
|
441
|
+
total: all.length,
|
|
442
|
+
enabled: enabled.length,
|
|
443
|
+
providers: all.map(p => ({
|
|
444
|
+
id: p.id,
|
|
445
|
+
name: p.name,
|
|
446
|
+
provider: p.provider,
|
|
447
|
+
enabled: p.enabled,
|
|
448
|
+
hasKey: !!p.apiKey,
|
|
449
|
+
rating: p.rating || 0,
|
|
450
|
+
priceLabel: p.priceLabel || '',
|
|
451
|
+
priceLevel: p.priceLevel || 2,
|
|
452
|
+
description: p.description || '',
|
|
453
|
+
capabilities: p.capabilities || [],
|
|
454
|
+
// CLI-specific fields
|
|
455
|
+
isCLI: p.isCLI || false,
|
|
456
|
+
cliBackendId: p.cliBackendId || null,
|
|
457
|
+
cliState: p.cliState || null,
|
|
458
|
+
cliVersion: p.cliVersion || null,
|
|
459
|
+
cliIcon: p.cliIcon || null,
|
|
460
|
+
// Web/browser-specific fields
|
|
461
|
+
isWeb: p.isWeb || false,
|
|
462
|
+
hasCookie: !!(p.cookie),
|
|
463
|
+
})),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return stats;
|
|
467
|
+
}
|
|
468
|
+
}
|