bingocode 1.0.18 → 1.0.19
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/bin/claude +1 -1
- package/package.json +1 -1
- package/src/cli/ProviderPanel.tsx +47 -9
- package/src/manager/CliMenuManager.tsx +83 -7
- package/src/server/__tests__/conversation-service.test.ts +7 -7
- package/src/server/__tests__/haha-oauth-service.test.ts +1 -1
- package/src/server/__tests__/providers-real.test.ts +15 -15
- package/src/server/api/computer-use.ts +2 -2
- package/src/server/api/providers.ts +6 -2
- package/src/server/cli/listProviders.ts +1 -1
- package/src/server/cli/providerManager.ts +1 -1
- package/src/server/config/providers.yaml +207 -207
- package/src/server/proxy/handler.ts +30 -47
- package/src/server/proxy/streaming/anthropicStreamLabeler.ts +56 -0
- package/src/server/services/conversationService.ts +5 -5
- package/src/server/services/hahaOAuthService.ts +1 -1
- package/src/server/services/providerManager.ts +1 -1
- package/src/server/services/providerService.ts +32 -14
- package/src/server/types/provider.ts +1 -0
- package/src/utils/computerUse/wrapper.tsx +2 -2
- package/src/utils/managedEnv.ts +23 -15
- package/src/utils/proxy.ts +13 -1
|
@@ -71,7 +71,7 @@ export class HahaOAuthService {
|
|
|
71
71
|
private getOAuthFilePath(): string {
|
|
72
72
|
const configDir =
|
|
73
73
|
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
|
|
74
|
-
return path.join(configDir, '
|
|
74
|
+
return path.join(configDir, 'bingo', 'oauth.json')
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
async loadTokens(): Promise<StoredOAuthTokens | null> {
|
|
@@ -6,7 +6,7 @@ import { loadPresets, applyPreset } from '../config/providerPresets.ts';
|
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
|
|
8
8
|
const home = process.env.CLAUDE_CONFIG_DIR || os.homedir();
|
|
9
|
-
const PROVIDERS_PATH = path.resolve(home, '.claude', '
|
|
9
|
+
const PROVIDERS_PATH = path.resolve(home, '.claude', 'bingo', 'providers.json');
|
|
10
10
|
|
|
11
11
|
export class ProviderManager {
|
|
12
12
|
static async load(): Promise<ProvidersIndex> {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider Service — preset-based provider configuration
|
|
3
3
|
*
|
|
4
|
-
* Storage: ~/.claude/
|
|
5
|
-
* Active provider env vars written to ~/.claude/
|
|
4
|
+
* Storage: ~/.claude/bingo/providers.json (lightweight index)
|
|
5
|
+
* Active provider env vars written to ~/.claude/bingo/settings.json
|
|
6
6
|
* (isolated from the original Claude Code's ~/.claude/settings.json)
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -58,7 +58,7 @@ export class ProviderService {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
private getCcHahaDir(): string {
|
|
61
|
-
return path.join(this.getConfigDir(), '
|
|
61
|
+
return path.join(this.getConfigDir(), 'bingo')
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
private getIndexPath(): string {
|
|
@@ -267,22 +267,22 @@ export class ProviderService {
|
|
|
267
267
|
|
|
268
268
|
/**
|
|
269
269
|
* Check whether any usable auth exists:
|
|
270
|
-
* 1. A
|
|
270
|
+
* 1. A bingo provider is active → has auth
|
|
271
271
|
* 2. Original ~/.claude/settings.json has ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY → has auth
|
|
272
272
|
* 3. process.env already has ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN → has auth
|
|
273
273
|
* 4. None of the above → needs setup
|
|
274
274
|
*/
|
|
275
275
|
async checkAuthStatus(): Promise<{
|
|
276
276
|
hasAuth: boolean
|
|
277
|
-
source: '
|
|
277
|
+
source: 'bingo-provider' | 'original-settings' | 'env' | 'none'
|
|
278
278
|
activeProvider?: string
|
|
279
279
|
}> {
|
|
280
|
-
// 1. Check
|
|
280
|
+
// 1. Check bingo active provider
|
|
281
281
|
const index = await this.readIndex()
|
|
282
282
|
if (index.activeId) {
|
|
283
283
|
const provider = index.providers.find(p => p.id === index.activeId)
|
|
284
284
|
if (provider?.apiKey) {
|
|
285
|
-
return { hasAuth: true, source: '
|
|
285
|
+
return { hasAuth: true, source: 'bingo-provider', activeProvider: provider.name }
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
@@ -364,6 +364,7 @@ export class ProviderService {
|
|
|
364
364
|
apiKey: string
|
|
365
365
|
apiFormat: ApiFormat
|
|
366
366
|
modelId: string
|
|
367
|
+
label?: string | null
|
|
367
368
|
} | null> {
|
|
368
369
|
const slots = await this.readSlots()
|
|
369
370
|
const entry = slots[slotName]
|
|
@@ -376,6 +377,7 @@ export class ProviderService {
|
|
|
376
377
|
apiKey: provider.apiKey,
|
|
377
378
|
apiFormat: provider.apiFormat ?? 'anthropic',
|
|
378
379
|
modelId: entry.modelId,
|
|
380
|
+
label: entry.label,
|
|
379
381
|
}
|
|
380
382
|
}
|
|
381
383
|
|
|
@@ -384,7 +386,12 @@ export class ProviderService {
|
|
|
384
386
|
const preset = PROVIDER_PRESETS.find(p => p.id === provider.presetId)
|
|
385
387
|
|
|
386
388
|
const base = provider.baseUrl.replace(/\/+$/, '')
|
|
387
|
-
if (!base) return []
|
|
389
|
+
if (!base && provider.presetId !== 'official') return []
|
|
390
|
+
|
|
391
|
+
// Special case for Official
|
|
392
|
+
if (provider.presetId === 'official') {
|
|
393
|
+
return ['claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307']
|
|
394
|
+
}
|
|
388
395
|
|
|
389
396
|
const modelsUrl = preset?.modelsUrl || '/v1/models'
|
|
390
397
|
const url = `${base}${modelsUrl}`
|
|
@@ -402,9 +409,10 @@ export class ProviderService {
|
|
|
402
409
|
}
|
|
403
410
|
|
|
404
411
|
try {
|
|
405
|
-
const
|
|
406
|
-
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10000), ...
|
|
412
|
+
const fetchOpts = getProxyFetchOptions()
|
|
413
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(10000), ...fetchOpts })
|
|
407
414
|
if (!res.ok) {
|
|
415
|
+
console.error(`[ProviderService] Failed to fetch models from ${url}: ${res.status}`)
|
|
408
416
|
return []
|
|
409
417
|
}
|
|
410
418
|
const data = await res.json() as any
|
|
@@ -412,7 +420,8 @@ export class ProviderService {
|
|
|
412
420
|
const list = data[dataPath] ?? data.data ?? data.models ?? []
|
|
413
421
|
if (!Array.isArray(list)) return []
|
|
414
422
|
return list.map((m: any) => (typeof m === 'string' ? m : m.id)).filter(Boolean)
|
|
415
|
-
} catch {
|
|
423
|
+
} catch (err) {
|
|
424
|
+
console.error(`[ProviderService] Error fetching models from ${url}:`, err)
|
|
416
425
|
return []
|
|
417
426
|
}
|
|
418
427
|
}
|
|
@@ -425,9 +434,17 @@ export class ProviderService {
|
|
|
425
434
|
): Promise<ProviderTestResult> {
|
|
426
435
|
const provider = await this.getProvider(id)
|
|
427
436
|
const baseUrl = overrides?.baseUrl || provider.baseUrl
|
|
428
|
-
const modelId = overrides?.modelId || provider.models.main
|
|
429
437
|
const apiFormat = overrides?.apiFormat ?? provider.apiFormat ?? 'anthropic'
|
|
430
438
|
|
|
439
|
+
// If no modelId provided, try to fetch from provider or use preset default
|
|
440
|
+
let modelId = overrides?.modelId || provider.models.main
|
|
441
|
+
if (!modelId || modelId === 'auto' || modelId.startsWith('claude-')) {
|
|
442
|
+
const fetched = await this.fetchProviderModels(id).catch(() => [])
|
|
443
|
+
if (fetched.length > 0) {
|
|
444
|
+
modelId = fetched[0] // Use first available model for testing
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
431
448
|
if (!baseUrl || !provider.apiKey) {
|
|
432
449
|
return { connectivity: { success: false, latencyMs: 0, error: 'Missing baseUrl or apiKey' } }
|
|
433
450
|
}
|
|
@@ -474,6 +491,7 @@ export class ProviderService {
|
|
|
474
491
|
const start = Date.now()
|
|
475
492
|
try {
|
|
476
493
|
const { url, headers, body } = buildDirectTestRequest(base, apiKey, modelId, format)
|
|
494
|
+
// 使用 getDirectFetchOptions 以绕开系统代理,测试直接连接
|
|
477
495
|
const directOpts = getDirectFetchOptions()
|
|
478
496
|
const response = await fetch(url, {
|
|
479
497
|
method: 'POST',
|
|
@@ -538,13 +556,13 @@ export class ProviderService {
|
|
|
538
556
|
}
|
|
539
557
|
|
|
540
558
|
// Call upstream with transformed request
|
|
541
|
-
const
|
|
559
|
+
const fetchOpts = getProxyFetchOptions()
|
|
542
560
|
const response = await fetch(upstreamUrl, {
|
|
543
561
|
method: 'POST',
|
|
544
562
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
545
563
|
body: JSON.stringify(transformedBody),
|
|
546
564
|
signal: AbortSignal.timeout(30000),
|
|
547
|
-
...
|
|
565
|
+
...fetchOpts,
|
|
548
566
|
})
|
|
549
567
|
|
|
550
568
|
if (!response.ok) {
|
|
@@ -82,6 +82,7 @@ export type SlotName = z.infer<typeof SlotNameSchema>
|
|
|
82
82
|
export const SlotEntrySchema = z.object({
|
|
83
83
|
providerId: z.string(),
|
|
84
84
|
modelId: z.string(),
|
|
85
|
+
label: z.string().nullable().optional(), // Display name for UI
|
|
85
86
|
}).nullable()
|
|
86
87
|
export type SlotEntry = z.infer<typeof SlotEntrySchema>
|
|
87
88
|
|
|
@@ -263,7 +263,7 @@ async function runDesktopPermissionDialog(
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
|
-
* Load pre-authorized apps from ~/.claude/
|
|
266
|
+
* Load pre-authorized apps from ~/.claude/bingo/computer-use-config.json.
|
|
267
267
|
* Called once when the binding is first created. Pre-authorized apps
|
|
268
268
|
* are injected into appState so `getAllowedApps()` returns them
|
|
269
269
|
* immediately — no runtime permission dialog needed.
|
|
@@ -272,7 +272,7 @@ async function loadPreAuthorizedApps(): Promise<void> {
|
|
|
272
272
|
try {
|
|
273
273
|
const configPath = join(
|
|
274
274
|
process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'),
|
|
275
|
-
'
|
|
275
|
+
'bingo',
|
|
276
276
|
'computer-use-config.json',
|
|
277
277
|
)
|
|
278
278
|
const raw = await readFile(configPath, 'utf8')
|
package/src/utils/managedEnv.ts
CHANGED
|
@@ -98,15 +98,23 @@ function filterSettingsEnv(
|
|
|
98
98
|
* contains ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, model defaults, etc.
|
|
99
99
|
* Returns an empty object if the file doesn't exist or is invalid.
|
|
100
100
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
function getBingoSettingsEnv(): Record<string, string> {
|
|
102
|
+
const configDir = getClaudeConfigHomeDir()
|
|
103
|
+
const paths = [
|
|
104
|
+
join(configDir, 'bingo', 'settings.json'),
|
|
105
|
+
join(configDir, 'cc-haha', 'settings.json'), // Fallback for migration
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
for (const settingsPath of paths) {
|
|
109
|
+
try {
|
|
110
|
+
const raw = readFileSync(settingsPath, 'utf-8')
|
|
111
|
+
const parsed = JSON.parse(raw) as { env?: Record<string, string> }
|
|
112
|
+
if (parsed.env) return parsed.env
|
|
113
|
+
} catch {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
109
116
|
}
|
|
117
|
+
return {}
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
/**
|
|
@@ -167,11 +175,11 @@ export function applySafeConfigEnvironmentVariables(): void {
|
|
|
167
175
|
)
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
//
|
|
171
|
-
// AFTER userSettings so
|
|
172
|
-
// the original Claude Code's settings. This prevents
|
|
178
|
+
// bingo provider isolation: apply env from ~/.claude/bingo/settings.json
|
|
179
|
+
// AFTER userSettings so Bingo-specific provider config takes priority over
|
|
180
|
+
// the original Claude Code's settings. This prevents Bingo from polluting
|
|
173
181
|
// ~/.claude/settings.json while still allowing it to override provider vars.
|
|
174
|
-
Object.assign(process.env, filterSettingsEnv(
|
|
182
|
+
Object.assign(process.env, filterSettingsEnv(getBingoSettingsEnv()))
|
|
175
183
|
|
|
176
184
|
// Compute remote-managed-settings eligibility now, with userSettings and
|
|
177
185
|
// flagSettings env applied. Eligibility reads CLAUDE_CODE_USE_BEDROCK,
|
|
@@ -214,9 +222,9 @@ export function applyConfigEnvironmentVariables(): void {
|
|
|
214
222
|
|
|
215
223
|
Object.assign(process.env, filterSettingsEnv(getSettings_DEPRECATED()?.env))
|
|
216
224
|
|
|
217
|
-
//
|
|
218
|
-
// apply
|
|
219
|
-
Object.assign(process.env, filterSettingsEnv(
|
|
225
|
+
// bingo provider isolation: same as in applySafeConfigEnvironmentVariables,
|
|
226
|
+
// apply Bingo-specific env last so it overrides the original settings.
|
|
227
|
+
Object.assign(process.env, filterSettingsEnv(getBingoSettingsEnv()))
|
|
220
228
|
|
|
221
229
|
// Clear caches so agents are rebuilt with the new env vars
|
|
222
230
|
clearCACertsCache()
|
package/src/utils/proxy.ts
CHANGED
|
@@ -334,14 +334,26 @@ export function getDirectFetchOptions(): {
|
|
|
334
334
|
return { ...base, proxy: undefined, ...getTLSFetchOptions() }
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
// Check if system proxy exists
|
|
338
|
+
const proxyUrl = getProxyUrl()
|
|
339
|
+
if (!proxyUrl) {
|
|
340
|
+
// No proxy configured, just return normal fetch options
|
|
341
|
+
return { ...base, ...getTLSFetchOptions() }
|
|
342
|
+
}
|
|
343
|
+
|
|
337
344
|
// In Node.js/undici, a fresh Agent with no proxy settings bypasses system defaults
|
|
338
345
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
339
346
|
const undiciMod = require('undici') as typeof undici
|
|
340
347
|
const tlsOpts = getTLSFetchOptions()
|
|
341
348
|
|
|
349
|
+
// Use the global dispatcher's options if possible, or fresh default options
|
|
350
|
+
const agentOptions = tlsOpts.dispatcher && 'options' in (tlsOpts.dispatcher as any)
|
|
351
|
+
? (tlsOpts.dispatcher as any).options
|
|
352
|
+
: {}
|
|
353
|
+
|
|
342
354
|
return {
|
|
343
355
|
...base,
|
|
344
|
-
dispatcher: new undiciMod.Agent(
|
|
356
|
+
dispatcher: new undiciMod.Agent(agentOptions),
|
|
345
357
|
}
|
|
346
358
|
}
|
|
347
359
|
|