mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(ai): correct apiKeySet detection and prevent stale key reuse
- Fix apiKeySet reporting false positives for local no-key configs by resolving the actual key via resolveApiKey and excluding placeholders - Prevent editing a config from reusing an incompatible API key when provider or connection mode has changed - Pass summary strategy to single-session generation for consistency Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a9b8599417
commit
88ab0ae172
11 files changed
+232
-18
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { toLlmConfigDisplay } from './ai-config-display'
|
||||
|
||||
describe('toLlmConfigDisplay', () => {
|
||||
it('marks apiKeySet when provider fallback auth resolves a key', () => {
|
||||
const result = toLlmConfigDisplay(
|
||||
{
|
||||
id: 'cfg-1',
|
||||
name: 'OpenAI compatible',
|
||||
provider: 'openai-compatible',
|
||||
apiKey: '',
|
||||
},
|
||||
(provider, authProfile) => (provider === 'openai-compatible' && !authProfile ? 'sk-provider-fallback' : '')
|
||||
)
|
||||
|
||||
assert.equal(result.apiKey, '')
|
||||
assert.equal(result.apiKeySet, true)
|
||||
})
|
||||
|
||||
it('does not mark the local no-key placeholder as a real API key', () => {
|
||||
const result = toLlmConfigDisplay(
|
||||
{
|
||||
id: 'cfg-2',
|
||||
name: 'LAN Ollama',
|
||||
provider: 'openai-compatible',
|
||||
apiKey: '',
|
||||
baseUrl: 'http://127.0.0.1:11434',
|
||||
authProfile: 'lan-ollama',
|
||||
},
|
||||
() => 'sk-no-key-required'
|
||||
)
|
||||
|
||||
assert.equal(result.apiKey, '')
|
||||
assert.equal(result.apiKeySet, false)
|
||||
})
|
||||
|
||||
it('does not mark legacy plaintext apiKey as reusable', () => {
|
||||
const result = toLlmConfigDisplay(
|
||||
{
|
||||
id: 'cfg-3',
|
||||
name: 'Legacy OpenAI',
|
||||
provider: 'openai',
|
||||
apiKey: 'sk-legacy-plaintext',
|
||||
},
|
||||
() => ''
|
||||
)
|
||||
|
||||
assert.equal(result.apiKey, '')
|
||||
assert.equal(result.apiKeySet, false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
type LlmConfigRecord = Record<string, unknown>
|
||||
|
||||
type ResolveApiKey = (provider: string, authProfile?: string) => string | undefined
|
||||
const NO_KEY_PLACEHOLDER = 'sk-no-key-required'
|
||||
|
||||
function hasRealApiKey(apiKey: string): boolean {
|
||||
return !!apiKey && apiKey !== NO_KEY_PLACEHOLDER
|
||||
}
|
||||
|
||||
export function toLlmConfigDisplay(config: LlmConfigRecord, resolveApiKey: ResolveApiKey): LlmConfigRecord {
|
||||
const { apiKey: _rawApiKey, ...rest } = config
|
||||
const provider = typeof config.provider === 'string' ? config.provider : ''
|
||||
const authProfile = typeof config.authProfile === 'string' ? config.authProfile : undefined
|
||||
const resolvedApiKey = provider ? resolveApiKey(provider, authProfile) : ''
|
||||
|
||||
return {
|
||||
...rest,
|
||||
apiKey: '',
|
||||
apiKeySet: hasRealApiKey(resolvedApiKey || ''),
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ import {
|
||||
deleteCustomModel,
|
||||
} from '../../ai/custom-store'
|
||||
import { validateApiKey, fetchRemoteModels } from '../../ai/remote-api'
|
||||
import { resolveApiKey } from '@openchatlab/config'
|
||||
import { toLlmConfigDisplay } from './ai-config-display'
|
||||
|
||||
function getAiDir(dbManager: DatabaseManager): string {
|
||||
const pathProvider = (dbManager as any)['pathProvider']
|
||||
@@ -177,10 +179,7 @@ export function registerAiRoutes(
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
const configs = (data.configs || []).map((c: Record<string, unknown>) => {
|
||||
const { apiKey: _k, ...rest } = c
|
||||
return { ...rest, apiKey: '', apiKeySet: !!c.authProfile }
|
||||
})
|
||||
const configs = (data.configs || []).map((c: Record<string, unknown>) => toLlmConfigDisplay(c, resolveApiKey))
|
||||
return {
|
||||
configs,
|
||||
defaultAssistant: data.defaultAssistant ?? null,
|
||||
|
||||
Reference in new issue
Block a user