bingocode 1.0.19 → 1.0.20
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/package.json
CHANGED
package/src/entrypoints/init.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs'
|
|
1
2
|
import { profileCheckpoint } from '../utils/startupProfiler.js'
|
|
2
3
|
import '../bootstrap/state.js'
|
|
3
4
|
import '../utils/config.js'
|
|
@@ -25,7 +26,7 @@ import { logForDebugging } from '../utils/debug.js'
|
|
|
25
26
|
import { detectCurrentRepository } from '../utils/detectRepository.js'
|
|
26
27
|
import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
|
|
27
28
|
import { initJetBrainsDetection } from '../utils/envDynamic.js'
|
|
28
|
-
import { isEnvTruthy } from '../utils/envUtils.js'
|
|
29
|
+
import { getClaudeConfigHomeDir, isEnvTruthy } from '../utils/envUtils.js'
|
|
29
30
|
import { ConfigParseError, errorMessage } from '../utils/errors.js'
|
|
30
31
|
// showInvalidConfigDialog is dynamically imported in the error path to avoid loading React at init
|
|
31
32
|
import {
|
|
@@ -55,6 +56,13 @@ import { setShellIfWindows } from '../utils/windowsPaths.js'
|
|
|
55
56
|
let telemetryInitialized = false
|
|
56
57
|
|
|
57
58
|
export const init = memoize(async (): Promise<void> => {
|
|
59
|
+
// Ensure config directory exists for first-time use
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(getClaudeConfigHomeDir(), { recursive: true });
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// Ignore error if it already exists
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
const initStartTime = Date.now()
|
|
59
67
|
logForDiagnosticsNoPII('info', 'init_started')
|
|
60
68
|
profileCheckpoint('init_function_start')
|
|
@@ -818,7 +818,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
818
818
|
<Box flexDirection="column" width={VIEW_W} height={MID_H}>
|
|
819
819
|
{creating && <Text color="yellow">新建中...</Text>}
|
|
820
820
|
{createErr && <Text color="red">新建失败: {createErr}</Text>}
|
|
821
|
-
{newSessionId && <Text color="green"
|
|
821
|
+
{newSessionId && <Text color="green">新建会话: {newSessionId}</Text>}
|
|
822
822
|
{!creating && !createErr && !newSessionId && <Text dimColor>已进入新建会话页,等待创建结果...</Text>}
|
|
823
823
|
</Box>
|
|
824
824
|
);
|
|
@@ -121,14 +121,13 @@ export class ProviderManager {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
//
|
|
124
|
+
// 预设列表
|
|
125
125
|
static async listPresets(): Promise<any[]> {
|
|
126
126
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} catch {
|
|
127
|
+
const { loadProviderPresets } = await import('../config/providerPresets.ts');
|
|
128
|
+
return await loadProviderPresets();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error('Failed to load presets:', e);
|
|
132
131
|
return [];
|
|
133
132
|
}
|
|
134
133
|
}
|
|
@@ -145,33 +144,83 @@ export class ProviderManager {
|
|
|
145
144
|
baseUrl: overrides?.baseUrl ?? p.baseUrl ?? '',
|
|
146
145
|
apiFormat: overrides?.apiFormat ?? p.apiFormat ?? 'openai_chat',
|
|
147
146
|
models: overrides?.models ?? { main: p.defaultModel || '', haiku: '', sonnet: '', opus: '' },
|
|
148
|
-
notes: overrides?.notes ?? p.notes ?? ''
|
|
147
|
+
notes: overrides?.notes ?? p.notes ?? '',
|
|
148
|
+
extra: { ...p, ...overrides?.extra }
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
//
|
|
152
|
+
// 获取模型列表
|
|
153
|
+
static async fetchModels(target: SavedProvider | any, apiKeyOverride?: string): Promise<string[]> {
|
|
154
|
+
const p = target;
|
|
155
|
+
const apiKey = apiKeyOverride || p.apiKey;
|
|
156
|
+
const baseUrl = (p.baseUrl || '').replace(/\/+$/, '');
|
|
157
|
+
const modelsUrl = p.modelsUrl || p.extra?.modelsUrl;
|
|
158
|
+
const modelsDataPath = p.modelsDataPath || p.extra?.modelsDataPath || 'data';
|
|
159
|
+
const authStyle = p.modelsAuthStyle || p.extra?.modelsAuthStyle || 'bearer';
|
|
160
|
+
|
|
161
|
+
if (!modelsUrl) return [];
|
|
162
|
+
|
|
163
|
+
const url = modelsUrl.startsWith('http') ? modelsUrl : (baseUrl + modelsUrl);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const headers: any = {};
|
|
167
|
+
if (apiKey) {
|
|
168
|
+
if (authStyle === 'bearer') {
|
|
169
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
170
|
+
} else if (authStyle === 'x-api-key') {
|
|
171
|
+
headers['x-api-key'] = apiKey;
|
|
172
|
+
headers['anthropic-version'] = '2023-06-01';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const res = await axios.get(url, { headers, timeout: 5000 });
|
|
177
|
+
const data = res.data;
|
|
178
|
+
const list = modelsDataPath.split('.').reduce((acc: any, part: string) => acc && acc[part], data);
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(list)) {
|
|
181
|
+
return list.map((m: any) => typeof m === 'string' ? m : (m.id || m.name)).filter(Boolean);
|
|
182
|
+
}
|
|
183
|
+
return [];
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error('Failed to fetch models:', e);
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 连通性测试:优先使用配置的 modelsUrl
|
|
153
191
|
static async testProvider(target: SavedProvider | string): Promise<{ ok: boolean; latencyMs?: number; message?: string }> {
|
|
154
192
|
const p = typeof target === 'string' ? await this.getProvider(target) : target;
|
|
155
193
|
if (!p) return { ok: false, message: 'Provider not found' };
|
|
156
|
-
|
|
194
|
+
|
|
195
|
+
const baseUrl = (p.baseUrl || '').replace(/\/+$/, '');
|
|
196
|
+
const modelsUrl = p.modelsUrl || p.extra?.modelsUrl || (p.apiFormat?.includes('openai') ? '/v1/models' : '/v1/models');
|
|
197
|
+
const authStyle = p.modelsAuthStyle || p.extra?.modelsAuthStyle || 'bearer';
|
|
198
|
+
const apiKey = p.apiKey;
|
|
199
|
+
|
|
200
|
+
const url = modelsUrl.startsWith('http') ? modelsUrl : (baseUrl + modelsUrl);
|
|
157
201
|
const start = Date.now();
|
|
158
|
-
|
|
159
|
-
let url = base;
|
|
160
|
-
if ((p.apiFormat || '').includes('openai')) url = base + '/v1/models';
|
|
161
|
-
else if ((p.apiFormat || '').includes('anthropic')) url = base + '/v1/models';
|
|
202
|
+
|
|
162
203
|
try {
|
|
204
|
+
const headers: any = {};
|
|
205
|
+
if (apiKey) {
|
|
206
|
+
if (authStyle === 'bearer') {
|
|
207
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
208
|
+
} else if (authStyle === 'x-api-key') {
|
|
209
|
+
headers['x-api-key'] = apiKey;
|
|
210
|
+
headers['anthropic-version'] = '2023-06-01';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
163
214
|
const res = await axios.get(url, {
|
|
164
215
|
timeout: 8000,
|
|
165
|
-
headers
|
|
166
|
-
...(p.apiKey ? { Authorization: `Bearer ${p.apiKey}` } : {}),
|
|
167
|
-
},
|
|
216
|
+
headers,
|
|
168
217
|
validateStatus: () => true
|
|
169
218
|
});
|
|
170
|
-
|
|
219
|
+
|
|
171
220
|
if (res.status >= 200 && res.status < 400) {
|
|
172
221
|
return { ok: true, latencyMs: Date.now() - start };
|
|
173
222
|
}
|
|
174
|
-
return { ok: false, message: `HTTP ${res.status}` };
|
|
223
|
+
return { ok: false, message: `HTTP ${res.status}: ${JSON.stringify(res.data)}` };
|
|
175
224
|
} catch (e: any) {
|
|
176
225
|
return { ok: false, message: e?.message || 'network error' };
|
|
177
226
|
}
|
|
@@ -30,8 +30,9 @@ const FIXED_APIFMT = 'openai_chat';
|
|
|
30
30
|
|
|
31
31
|
const ProvidersMenu: React.FC = () => {
|
|
32
32
|
const { exit } = useApp();
|
|
33
|
+
const [modelOptions, setModelOptions] = useState<string[]>([]);
|
|
33
34
|
const [modelSelectIdx, setModelSelectIdx] = useState(0);
|
|
34
|
-
const [addStep, setAddStep] = useState(0); // 0
|
|
35
|
+
const [addStep, setAddStep] = useState(0); // 0:输入key并加载模型, 1:选模型
|
|
35
36
|
const [inputKey, setInputKey] = useState('');
|
|
36
37
|
const [addError, setAddError] = useState('');
|
|
37
38
|
const [mode, setMode] = useState<UiMode>('list');
|
|
@@ -44,6 +45,9 @@ const ProvidersMenu: React.FC = () => {
|
|
|
44
45
|
const [msg, setMsg] = useState<string>('');
|
|
45
46
|
const [detail, setDetail] = useState<SavedProvider | null>(null);
|
|
46
47
|
const [presets, setPresets] = useState<any[]>([]);
|
|
48
|
+
// 临时存储当前正在配置的 Provider 草案
|
|
49
|
+
const [pendingProvider, setPendingProvider] = useState<CreateProviderInput | null>(null);
|
|
50
|
+
|
|
47
51
|
useEffect(() => { ProviderManager.listPresets().then(setPresets).catch(()=>setPresets([])); }, []);
|
|
48
52
|
|
|
49
53
|
const refresh = async () => {
|
|
@@ -54,7 +58,14 @@ const ProvidersMenu: React.FC = () => {
|
|
|
54
58
|
};
|
|
55
59
|
useEffect(() => { refresh(); }, []);
|
|
56
60
|
useEffect(() => {
|
|
57
|
-
if (mode !== 'add') {
|
|
61
|
+
if (mode !== 'add') {
|
|
62
|
+
setAddStep(0);
|
|
63
|
+
setInputKey('');
|
|
64
|
+
setAddError('');
|
|
65
|
+
setModelSelectIdx(0);
|
|
66
|
+
setModelOptions([]);
|
|
67
|
+
setPendingProvider(null);
|
|
68
|
+
}
|
|
58
69
|
}, [mode]);
|
|
59
70
|
|
|
60
71
|
// 主界面 LIST 模式
|
|
@@ -96,33 +107,55 @@ const ProvidersMenu: React.FC = () => {
|
|
|
96
107
|
setMode('list'); setAddError(''); setInputKey(''); setAddStep(0);
|
|
97
108
|
return;
|
|
98
109
|
}
|
|
99
|
-
// 步骤
|
|
100
|
-
if (addStep === 0) {
|
|
101
|
-
if (key.downArrow) setModelSelectIdx(idx => Math.min(
|
|
110
|
+
// 步骤1: 选模型
|
|
111
|
+
if (addStep === 1 && modelOptions.length > 0) {
|
|
112
|
+
if (key.downArrow) setModelSelectIdx(idx => Math.min(modelOptions.length - 1, idx + 1));
|
|
102
113
|
else if (key.upArrow) setModelSelectIdx(idx => Math.max(0, idx - 1));
|
|
103
|
-
else if (key.return) {
|
|
114
|
+
else if (key.return) {
|
|
115
|
+
completeAdd(modelOptions[modelSelectIdx]);
|
|
116
|
+
}
|
|
104
117
|
}
|
|
105
118
|
});
|
|
106
119
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
const selectedModel = MODEL_OPTIONS[modelSelectIdx];
|
|
110
|
-
const presetId = selectedModel.replace(/[^a-zA-Z0-9]/g, '') + '-preset';
|
|
111
|
-
const name = `${selectedModel} Provider`;
|
|
120
|
+
// 获取模型并进入下一步
|
|
121
|
+
const fetchModelsStep = async (keyInput: string) => {
|
|
112
122
|
if (!keyInput.trim()) { setAddError('API Key 不能为空'); return; }
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
setAddError('正在获取模型列表...');
|
|
124
|
+
try {
|
|
125
|
+
// 如果没有 pendingProvider,手动创建一个(比如 Custom 模式)
|
|
126
|
+
const draft = pendingProvider || {
|
|
127
|
+
presetId: 'custom-' + Date.now(),
|
|
128
|
+
name: 'Custom Provider',
|
|
129
|
+
baseUrl: FIXED_BASEURL,
|
|
130
|
+
apiFormat: FIXED_APIFMT,
|
|
131
|
+
apiKey: keyInput.trim(),
|
|
132
|
+
models: { main: '', haiku: '', sonnet: '', opus: '' }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const models = await ProviderManager.fetchModels(draft, keyInput.trim());
|
|
136
|
+
if (models.length === 0) {
|
|
137
|
+
setAddError('未获取到可用模型,请检查 API Key 或网络连通性。');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setModelOptions(models);
|
|
141
|
+
setPendingProvider({ ...draft, apiKey: keyInput.trim() });
|
|
142
|
+
setAddStep(1);
|
|
143
|
+
setAddError('');
|
|
144
|
+
} catch (e: any) {
|
|
145
|
+
setAddError('获取模型失败: ' + (e.message || '未知错误'));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const completeAdd = async (selectedModel: string) => {
|
|
150
|
+
if (!pendingProvider) return;
|
|
151
|
+
const name = pendingProvider.name || `${selectedModel} Provider`;
|
|
152
|
+
|
|
119
153
|
await ProviderManager.addProvider({
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
...pendingProvider,
|
|
155
|
+
name,
|
|
122
156
|
models: { main: selectedModel, haiku: '', sonnet: '', opus: '' }
|
|
123
157
|
});
|
|
124
|
-
|
|
125
|
-
delete (global as any).__PM_APIFMT_OVERRIDE__;
|
|
158
|
+
|
|
126
159
|
setMode('list');
|
|
127
160
|
setAddStep(0);
|
|
128
161
|
setInputKey('');
|
|
@@ -153,25 +186,25 @@ const ProvidersMenu: React.FC = () => {
|
|
|
153
186
|
{/* 新增 */}
|
|
154
187
|
{mode === 'add' && (
|
|
155
188
|
<Box flexDirection="column">
|
|
156
|
-
<Text>选择模型与输入API Key:</Text>
|
|
157
189
|
{addStep === 0
|
|
158
190
|
? <>
|
|
159
|
-
|
|
160
|
-
<Text key={m} color={idx === modelSelectIdx ? 'yellow' : undefined}>
|
|
161
|
-
{idx === modelSelectIdx ? '> ' : ' '}{m}
|
|
162
|
-
</Text>
|
|
163
|
-
))}
|
|
164
|
-
<Text color="gray">↑↓选择,回车下一步,q返回</Text>
|
|
165
|
-
</>
|
|
166
|
-
: <>
|
|
167
|
-
<Text>已选模型:{MODEL_OPTIONS[modelSelectIdx]}</Text>
|
|
191
|
+
<Text>请输入 API Key (将自动拉取可用模型):</Text>
|
|
168
192
|
<TextInput
|
|
169
193
|
value={inputKey}
|
|
170
194
|
onChange={setInputKey}
|
|
171
|
-
onSubmit={
|
|
195
|
+
onSubmit={fetchModelsStep}
|
|
172
196
|
placeholder="请输入API Key"
|
|
173
197
|
/>
|
|
174
|
-
<Text color="gray"
|
|
198
|
+
<Text color="gray">输入后回车继续,q返回</Text>
|
|
199
|
+
</>
|
|
200
|
+
: <>
|
|
201
|
+
<Text>获取成功!请选择主模型:</Text>
|
|
202
|
+
{modelOptions.map((m, idx) => (
|
|
203
|
+
<Text key={m} color={idx === modelSelectIdx ? 'yellow' : undefined}>
|
|
204
|
+
{idx === modelSelectIdx ? '> ' : ' '}{m}
|
|
205
|
+
</Text>
|
|
206
|
+
))}
|
|
207
|
+
<Text color="gray">↑↓选择,回车保存,q返回</Text>
|
|
175
208
|
</>
|
|
176
209
|
}
|
|
177
210
|
{addError && <Text color="red">{addError}</Text>}
|
|
@@ -259,17 +292,14 @@ const ProvidersMenu: React.FC = () => {
|
|
|
259
292
|
<SelectInput
|
|
260
293
|
items={presets.map((p: any) => ({ label: `${p.name || p.id} ${p.baseUrl || ''}`, value: p.id }))}
|
|
261
294
|
onSelect={async it => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
(global as any).__PM_APIFMT_OVERRIDE__ = presetFmt;
|
|
271
|
-
setMode('add');
|
|
272
|
-
setAddStep(1); // 直接跳到 Key 输入
|
|
295
|
+
try {
|
|
296
|
+
const draft = await ProviderManager.applyPreset(it.value);
|
|
297
|
+
setPendingProvider(draft);
|
|
298
|
+
setMode('add');
|
|
299
|
+
setAddStep(0); // 去输入 Key
|
|
300
|
+
} catch (e: any) {
|
|
301
|
+
setMsg('预设加载失败: ' + e.message);
|
|
302
|
+
}
|
|
273
303
|
}}
|
|
274
304
|
/>
|
|
275
305
|
<Text color="gray">回车选择预设,q 返回</Text>
|
package/src/utils/config.ts
CHANGED
|
@@ -1344,11 +1344,17 @@ export function enableConfigs(): void {
|
|
|
1344
1344
|
// to prevent us from adding config reading during module initialization
|
|
1345
1345
|
configReadingAllowed = true
|
|
1346
1346
|
// We only check the global config because currently all the configs share a file
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1347
|
+
try {
|
|
1348
|
+
getConfig(
|
|
1349
|
+
getGlobalClaudeFile(),
|
|
1350
|
+
createDefaultGlobalConfig,
|
|
1351
|
+
true /* throw on invalid */,
|
|
1352
|
+
)
|
|
1353
|
+
} catch (e) {
|
|
1354
|
+
logForDebugging(`Failed to load config during enableConfigs: ${e}`, { level: 'error' })
|
|
1355
|
+
// If it's a corrupted file, we allow the boostrap to continue with defaults
|
|
1356
|
+
// instead of hard-crashing the process.
|
|
1357
|
+
}
|
|
1352
1358
|
|
|
1353
1359
|
logForDiagnosticsNoPII('info', 'enable_configs_completed', {
|
|
1354
1360
|
duration_ms: Date.now() - startTime,
|
|
@@ -202,7 +202,7 @@ export function PreflightStep(t0) {
|
|
|
202
202
|
|
|
203
203
|
//@C:ID=F.PC._temp;K=F;V=1.0;P=Helper function for process exit;D=UI;M=Connectivity;S=Utility;In=void;Out=void
|
|
204
204
|
function _temp() {
|
|
205
|
-
console.log("F.PC._temp");
|
|
206
|
-
|
|
207
|
-
return process.exit(1);
|
|
205
|
+
console.log("F.PC._temp: Skipping force exit on connectivity failure.");
|
|
206
|
+
|
|
207
|
+
// return process.exit(1);
|
|
208
208
|
}
|