work-agent 0.1.0
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/README.md +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- package/tsconfig.json +42 -0
package/lib/pi-config.ts
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PI Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages loading and saving of pi-coding-agent configuration.
|
|
5
|
+
* Configuration files are located at ~/.pi/agent/ by default
|
|
6
|
+
* Can be overridden via PI_CONFIG_PATH environment variable
|
|
7
|
+
* - models.json: Custom model providers and API keys
|
|
8
|
+
* - auth.json: Runtime auth settings (fallback)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir, tmpdir } from 'os';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the home directory for configuration
|
|
17
|
+
* Priority: PI_CONFIG_PATH env > /root (for deployed environments) > HOME env > homedir()
|
|
18
|
+
*/
|
|
19
|
+
function getConfigDir(): string {
|
|
20
|
+
// 1. Explicit override via environment variable
|
|
21
|
+
if (process.env.PI_CONFIG_PATH) {
|
|
22
|
+
return process.env.PI_CONFIG_PATH;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. Check if /root/.pi/agent exists (deployed environment)
|
|
26
|
+
const rootConfigDir = '/root/.pi/agent';
|
|
27
|
+
if (existsSync(rootConfigDir)) {
|
|
28
|
+
return rootConfigDir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 3. Use HOME environment variable
|
|
32
|
+
if (process.env.HOME) {
|
|
33
|
+
return join(process.env.HOME, '.pi', 'agent');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 4. Use os.homedir()
|
|
37
|
+
const home = homedir();
|
|
38
|
+
if (home && home !== '/') {
|
|
39
|
+
return join(home, '.pi', 'agent');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 5. Fallback to /root (for production deployments)
|
|
43
|
+
return '/root/.pi/agent';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// PI Agent config directory
|
|
47
|
+
export const PI_CONFIG_DIR = getConfigDir();
|
|
48
|
+
|
|
49
|
+
// Configuration file paths
|
|
50
|
+
const MODELS_FILE = join(PI_CONFIG_DIR, 'models.json');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Model provider configuration
|
|
54
|
+
*/
|
|
55
|
+
export interface ModelProvider {
|
|
56
|
+
baseUrl: string;
|
|
57
|
+
api: string;
|
|
58
|
+
apiKey: string;
|
|
59
|
+
models: Array<{ id: string }>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Models.json structure
|
|
64
|
+
*/
|
|
65
|
+
export interface ModelsConfig {
|
|
66
|
+
providers: Record<string, ModelProvider>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Loaded configuration with provider ID
|
|
71
|
+
*/
|
|
72
|
+
export interface ProviderConfig {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
api: string;
|
|
77
|
+
apiKey: string;
|
|
78
|
+
models: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Ensure config directory exists
|
|
83
|
+
*/
|
|
84
|
+
function ensureConfigDir(): void {
|
|
85
|
+
if (!existsSync(PI_CONFIG_DIR)) {
|
|
86
|
+
try {
|
|
87
|
+
mkdirSync(PI_CONFIG_DIR, { recursive: true });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to create config directory:', error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default provider configurations (without API keys)
|
|
96
|
+
*/
|
|
97
|
+
export const DEFAULT_PROVIDERS: Record<string, Omit<ModelProvider, 'apiKey'>> = {
|
|
98
|
+
zhipu: {
|
|
99
|
+
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
100
|
+
api: 'anthropic-messages',
|
|
101
|
+
models: [{ id: 'glm-4.7' }],
|
|
102
|
+
},
|
|
103
|
+
anthropic: {
|
|
104
|
+
baseUrl: 'https://api.anthropic.com',
|
|
105
|
+
api: 'anthropic-messages',
|
|
106
|
+
models: [{ id: 'claude-sonnet-4-20250514' }],
|
|
107
|
+
},
|
|
108
|
+
deepseek: {
|
|
109
|
+
baseUrl: 'https://api.deepseek.com',
|
|
110
|
+
api: 'openai-completions',
|
|
111
|
+
models: [{ id: 'deepseek-chat' }],
|
|
112
|
+
},
|
|
113
|
+
openai: {
|
|
114
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
115
|
+
api: 'openai-completions',
|
|
116
|
+
models: [{ id: 'gpt-4o' }],
|
|
117
|
+
},
|
|
118
|
+
ollama: {
|
|
119
|
+
baseUrl: 'http://192.168.44.151:11434/v1',
|
|
120
|
+
api: 'openai-completions',
|
|
121
|
+
models: [{ id: 'qwen3.5:27b' }],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get default configuration from environment variables
|
|
127
|
+
*
|
|
128
|
+
* Supported environment variables (AI_ prefix):
|
|
129
|
+
* - AI_PROVIDER: Provider ID (zhipu, anthropic, deepseek, openai, ollama, or custom)
|
|
130
|
+
* - AI_MODEL: Model ID (e.g., glm-4, claude-sonnet-4-20250514, qwen3.5:27b)
|
|
131
|
+
* - AI_BASE_URL: Custom API base URL
|
|
132
|
+
* - AI_API_KEY: Generic API key (lower priority than provider-specific keys)
|
|
133
|
+
*
|
|
134
|
+
* Legacy environment variables (still supported):
|
|
135
|
+
* - ZHIPU_API_KEY: Zhipu AI API key
|
|
136
|
+
* - ANTHROPIC_API_KEY: Anthropic API key
|
|
137
|
+
*/
|
|
138
|
+
function getDefaultConfigFromEnv(): ModelsConfig | null {
|
|
139
|
+
// Check for new AI_ prefix environment variables first
|
|
140
|
+
const aiProvider = process.env.AI_PROVIDER;
|
|
141
|
+
const aiModel = process.env.AI_MODEL;
|
|
142
|
+
const aiBaseUrl = process.env.AI_BASE_URL;
|
|
143
|
+
const aiApiKey = process.env.AI_API_KEY;
|
|
144
|
+
|
|
145
|
+
// If AI_PROVIDER is set, use the new configuration system
|
|
146
|
+
if (aiProvider) {
|
|
147
|
+
const providerId = aiProvider.toLowerCase();
|
|
148
|
+
const defaultProvider = DEFAULT_PROVIDERS[providerId];
|
|
149
|
+
|
|
150
|
+
// Determine API key: provider-specific key > AI_API_KEY
|
|
151
|
+
let apiKey = aiApiKey;
|
|
152
|
+
if (providerId === 'zhipu' && process.env.ZHIPU_API_KEY) {
|
|
153
|
+
apiKey = process.env.ZHIPU_API_KEY;
|
|
154
|
+
} else if (providerId === 'anthropic' && process.env.ANTHROPIC_API_KEY) {
|
|
155
|
+
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!apiKey) {
|
|
159
|
+
console.warn(`AI_PROVIDER is set to "${providerId}" but no API key found. Set AI_API_KEY or ${providerId.toUpperCase()}_API_KEY`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(`Using AI_PROVIDER=${providerId} from environment`);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
providers: {
|
|
167
|
+
[providerId]: {
|
|
168
|
+
baseUrl: aiBaseUrl || defaultProvider?.baseUrl || '',
|
|
169
|
+
api: defaultProvider?.api || 'anthropic-messages',
|
|
170
|
+
apiKey,
|
|
171
|
+
models: [{ id: aiModel || defaultProvider?.models?.[0]?.id || 'default' }],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Fallback to legacy environment variables
|
|
178
|
+
// Priority: ZHIPU_API_KEY > ANTHROPIC_API_KEY
|
|
179
|
+
const zhipuKey = process.env.ZHIPU_API_KEY;
|
|
180
|
+
if (zhipuKey) {
|
|
181
|
+
console.log('Using ZHIPU_API_KEY from environment');
|
|
182
|
+
return {
|
|
183
|
+
providers: {
|
|
184
|
+
zhipu: {
|
|
185
|
+
...DEFAULT_PROVIDERS.zhipu,
|
|
186
|
+
apiKey: zhipuKey,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
193
|
+
if (anthropicKey) {
|
|
194
|
+
console.log('Using ANTHROPIC_API_KEY from environment');
|
|
195
|
+
return {
|
|
196
|
+
providers: {
|
|
197
|
+
anthropic: {
|
|
198
|
+
...DEFAULT_PROVIDERS.anthropic,
|
|
199
|
+
apiKey: anthropicKey,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Load models.json configuration
|
|
210
|
+
*/
|
|
211
|
+
export function loadModelsConfig(): ModelsConfig | null {
|
|
212
|
+
try {
|
|
213
|
+
console.log('[pi-config] === Loading configuration ===');
|
|
214
|
+
console.log('[pi-config] HOME env:', process.env.HOME || 'not set');
|
|
215
|
+
console.log('[pi-config] USER env:', process.env.USER || 'not set');
|
|
216
|
+
console.log('[pi-config] PI_CONFIG_PATH env:', process.env.PI_CONFIG_PATH || 'not set');
|
|
217
|
+
console.log('[pi-config] Computed PI_CONFIG_DIR:', PI_CONFIG_DIR);
|
|
218
|
+
console.log('[pi-config] MODELS_FILE path:', MODELS_FILE);
|
|
219
|
+
|
|
220
|
+
if (!existsSync(MODELS_FILE)) {
|
|
221
|
+
console.log('[pi-config] models.json not found at', MODELS_FILE);
|
|
222
|
+
console.log('[pi-config] Checking environment variables...');
|
|
223
|
+
const envConfig = getDefaultConfigFromEnv();
|
|
224
|
+
if (envConfig) {
|
|
225
|
+
console.log('[pi-config] Using config from environment variables');
|
|
226
|
+
} else {
|
|
227
|
+
console.log('[pi-config] No configuration found!');
|
|
228
|
+
}
|
|
229
|
+
return envConfig;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log('[pi-config] models.json found, reading...');
|
|
233
|
+
const content = readFileSync(MODELS_FILE, 'utf-8');
|
|
234
|
+
console.log('[pi-config] models.json content length:', content.length);
|
|
235
|
+
|
|
236
|
+
const config = JSON.parse(content) as ModelsConfig;
|
|
237
|
+
console.log('[pi-config] Parsed providers:', Object.keys(config.providers || {}));
|
|
238
|
+
|
|
239
|
+
// Validate and log configuration
|
|
240
|
+
for (const [id, provider] of Object.entries(config.providers || {})) {
|
|
241
|
+
if (!provider.apiKey) {
|
|
242
|
+
console.warn(`[pi-config] Provider ${id} has no apiKey!`);
|
|
243
|
+
} else {
|
|
244
|
+
const key = provider.apiKey;
|
|
245
|
+
const maskedKey = key.length > 20
|
|
246
|
+
? `${key.substring(0, 10)}...${key.substring(key.length - 10)}`
|
|
247
|
+
: '***';
|
|
248
|
+
console.log(`[pi-config] Provider ${id}:`);
|
|
249
|
+
console.log(`[pi-config] baseUrl: ${provider.baseUrl}`);
|
|
250
|
+
console.log(`[pi-config] api: ${provider.api}`);
|
|
251
|
+
console.log(`[pi-config] apiKey: ${maskedKey} (length: ${key.length})`);
|
|
252
|
+
console.log(`[pi-config] models: ${provider.models?.map(m => m.id).join(', ')}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return config;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('[pi-config] Failed to load models.json:', error);
|
|
259
|
+
return getDefaultConfigFromEnv();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Save models.json configuration
|
|
265
|
+
*/
|
|
266
|
+
export function saveModelsConfig(config: ModelsConfig): boolean {
|
|
267
|
+
try {
|
|
268
|
+
ensureConfigDir();
|
|
269
|
+
|
|
270
|
+
// Validate config before saving
|
|
271
|
+
if (!config.providers || Object.keys(config.providers).length === 0) {
|
|
272
|
+
throw new Error('Invalid config: providers is required');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Validate each provider
|
|
276
|
+
for (const [id, provider] of Object.entries(config.providers)) {
|
|
277
|
+
if (!provider.baseUrl || !provider.apiKey) {
|
|
278
|
+
throw new Error(`Invalid provider ${id}: baseUrl and apiKey are required`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
writeFileSync(MODELS_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
283
|
+
console.log('Saved models.json');
|
|
284
|
+
return true;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error('Failed to save models.json:', error);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get all available providers from models.json
|
|
293
|
+
*/
|
|
294
|
+
export function getAvailableProviders(): ProviderConfig[] {
|
|
295
|
+
const config = loadModelsConfig();
|
|
296
|
+
if (!config || !config.providers) {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const providers: ProviderConfig[] = [];
|
|
301
|
+
|
|
302
|
+
for (const [id, provider] of Object.entries(config.providers)) {
|
|
303
|
+
providers.push({
|
|
304
|
+
id,
|
|
305
|
+
name: getProviderDisplayName(id),
|
|
306
|
+
baseUrl: provider.baseUrl,
|
|
307
|
+
api: provider.api,
|
|
308
|
+
apiKey: provider.apiKey,
|
|
309
|
+
models: provider.models?.map(m => m.id) || [],
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return providers;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get provider display name
|
|
318
|
+
*/
|
|
319
|
+
function getProviderDisplayName(id: string): string {
|
|
320
|
+
const names: Record<string, string> = {
|
|
321
|
+
'anthropic': 'Anthropic (Claude)',
|
|
322
|
+
'openai': 'OpenAI (GPT)',
|
|
323
|
+
'zhipu': '智谱 AI (GLM)',
|
|
324
|
+
'deepseek': 'DeepSeek',
|
|
325
|
+
'ollama': 'Ollama (Local)',
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return names[id] || id.charAt(0).toUpperCase() + id.slice(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get default provider (first one in the list)
|
|
333
|
+
*/
|
|
334
|
+
export function getDefaultProvider(): ProviderConfig | null {
|
|
335
|
+
const providers = getAvailableProviders();
|
|
336
|
+
return providers.length > 0 ? providers[0] : null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get provider by ID
|
|
341
|
+
*/
|
|
342
|
+
export function getProviderById(id: string): ProviderConfig | null {
|
|
343
|
+
const providers = getAvailableProviders();
|
|
344
|
+
return providers.find(p => p.id === id) || null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Update or add a provider
|
|
349
|
+
*/
|
|
350
|
+
export function upsertProvider(
|
|
351
|
+
id: string,
|
|
352
|
+
provider: Omit<ModelProvider, 'models'> & { models?: string[] }
|
|
353
|
+
): ModelsConfig {
|
|
354
|
+
const config = loadModelsConfig() || { providers: {} };
|
|
355
|
+
|
|
356
|
+
config.providers[id] = {
|
|
357
|
+
baseUrl: provider.baseUrl,
|
|
358
|
+
api: provider.api,
|
|
359
|
+
apiKey: provider.apiKey,
|
|
360
|
+
models: (provider.models || []).map(m => ({ id: m })),
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
saveModelsConfig(config);
|
|
364
|
+
return config;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Delete a provider
|
|
369
|
+
*/
|
|
370
|
+
export function deleteProvider(id: string): ModelsConfig | null {
|
|
371
|
+
const config = loadModelsConfig();
|
|
372
|
+
if (!config || !config.providers[id]) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
delete config.providers[id];
|
|
377
|
+
saveModelsConfig(config);
|
|
378
|
+
return config;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get configuration for AuthStorage
|
|
383
|
+
* Returns the first available provider's API key and base URL
|
|
384
|
+
*/
|
|
385
|
+
export function getAuthConfig(): { apiKey: string; baseUrl?: string } | null {
|
|
386
|
+
const provider = getDefaultProvider();
|
|
387
|
+
if (!provider) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
apiKey: provider.apiKey,
|
|
393
|
+
baseUrl: provider.baseUrl,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Validate provider configuration
|
|
399
|
+
*/
|
|
400
|
+
export function validateProvider(provider: Partial<ModelProvider>): { valid: boolean; errors: string[] } {
|
|
401
|
+
const errors: string[] = [];
|
|
402
|
+
|
|
403
|
+
if (!provider.baseUrl) {
|
|
404
|
+
errors.push('baseUrl is required');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!provider.apiKey) {
|
|
408
|
+
errors.push('apiKey is required');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!provider.api) {
|
|
412
|
+
errors.push('api is required');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Validate URL format
|
|
416
|
+
if (provider.baseUrl && !isValidUrl(provider.baseUrl)) {
|
|
417
|
+
errors.push('baseUrl must be a valid URL');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
valid: errors.length === 0,
|
|
422
|
+
errors,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Check if string is a valid URL
|
|
428
|
+
*/
|
|
429
|
+
function isValidUrl(url: string): boolean {
|
|
430
|
+
try {
|
|
431
|
+
new URL(url);
|
|
432
|
+
return true;
|
|
433
|
+
} catch {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|