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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -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">新建会话(本地Id,仅演示): {newSessionId}</Text>}
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
- // 预设列表(可选,providerPresets 不存在则返回空)
124
+ // 预设列表
125
125
  static async listPresets(): Promise<any[]> {
126
126
  try {
127
- // 相对 src/server/cli config 的路径
128
- const mod = await import('../config/providerPresets.ts');
129
- const presets = (mod as any).presets || (mod as any).providerPresets || [];
130
- return presets;
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
- // 连通性测试:按 apiFormat 选择探测路径
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
- const base = (p.baseUrl || '').replace(/\/+$/,'');
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
- // 2xx/3xx 都视为连通
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:选模型, 1:填key
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') { setAddStep(0); setInputKey(''); setAddError(''); setModelSelectIdx(0); }
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
- // 步骤0: 主模型选择
100
- if (addStep === 0) {
101
- if (key.downArrow) setModelSelectIdx(idx => Math.min(MODEL_OPTIONS.length - 1, idx + 1));
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) { setAddStep(1); }
114
+ else if (key.return) {
115
+ completeAdd(modelOptions[modelSelectIdx]);
116
+ }
104
117
  }
105
118
  });
106
119
 
107
- // 新增表单
108
- const addSubmit = async (keyInput: string) => {
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
- const exists = (await ProviderManager.listProviders()).some(p => p.id === presetId);
114
- if (exists) { setAddError('该模型已添加过,如要更换Key请在列表编辑。'); return; }
115
- setAddError('');
116
- // 支持预设覆盖
117
- const overrideBase = (global as any).__PM_BASEURL_OVERRIDE__ || FIXED_BASEURL;
118
- const overrideFmt = (global as any).__PM_APIFMT_OVERRIDE__ || FIXED_APIFMT;
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
- presetId, name, apiKey: keyInput.trim(),
121
- baseUrl: overrideBase, apiFormat: overrideFmt,
154
+ ...pendingProvider,
155
+ name,
122
156
  models: { main: selectedModel, haiku: '', sonnet: '', opus: '' }
123
157
  });
124
- delete (global as any).__PM_BASEURL_OVERRIDE__;
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
- {MODEL_OPTIONS.map((m, idx) => (
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={addSubmit}
195
+ onSubmit={fetchModelsStep}
172
196
  placeholder="请输入API Key"
173
197
  />
174
- <Text color="gray">输入后回车提交,q返回</Text>
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
- const p = presets.find((x: any) => x.id === it.value);
263
- if (!p) return;
264
- const model = (p.defaultModel || MODEL_OPTIONS[0]);
265
- const presetBase = p.baseUrl || FIXED_BASEURL;
266
- const presetFmt = p.apiFormat || FIXED_APIFMT;
267
- const idx = Math.max(0, MODEL_OPTIONS.findIndex(m => m === model));
268
- setModelSelectIdx(idx);
269
- (global as any).__PM_BASEURL_OVERRIDE__ = presetBase;
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>
@@ -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
- getConfig(
1348
- getGlobalClaudeFile(),
1349
- createDefaultGlobalConfig,
1350
- true /* throw on invalid */,
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
  }