Fix provider management profile scoping
This commit is contained in:
@@ -2,7 +2,7 @@ import { readFile } from 'fs/promises'
|
|||||||
import { existsSync, readFileSync } from 'fs'
|
import { existsSync, readFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getActiveEnvPath, getActiveAuthPath, getActiveProfileName, getProfileDir, listProfileNamesFromDisk } from '../../services/hermes/hermes-profile'
|
import { getActiveEnvPath, getActiveAuthPath, getActiveProfileName, getProfileDir, listProfileNamesFromDisk } from '../../services/hermes/hermes-profile'
|
||||||
import { readConfigYaml, readConfigYamlForProfile, updateConfigYaml, fetchProviderModels, buildModelGroups, PROVIDER_ENV_MAP } from '../../services/config-helpers'
|
import { readConfigYaml, readConfigYamlForProfile, updateConfigYaml, updateConfigYamlForProfile, fetchProviderModels, buildModelGroups, PROVIDER_ENV_MAP } from '../../services/config-helpers'
|
||||||
import { buildProviderModelMap, PROVIDER_PRESETS } from '../../shared/providers'
|
import { buildProviderModelMap, PROVIDER_PRESETS } from '../../shared/providers'
|
||||||
import { getCopilotModelsDetailed, resolveCopilotOAuthToken, type CopilotModelMeta } from '../../services/hermes/copilot-models'
|
import { getCopilotModelsDetailed, resolveCopilotOAuthToken, type CopilotModelMeta } from '../../services/hermes/copilot-models'
|
||||||
import { readAppConfig, writeAppConfig, type ModelVisibilityRule } from '../../services/app-config'
|
import { readAppConfig, writeAppConfig, type ModelVisibilityRule } from '../../services/app-config'
|
||||||
@@ -200,6 +200,10 @@ function requestedProfileName(ctx: any): string {
|
|||||||
return typeof queryProfile === 'string' && queryProfile.trim() ? queryProfile.trim() : ''
|
return typeof queryProfile === 'string' && queryProfile.trim() ? queryProfile.trim() : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestScopedProfileName(ctx: any): string {
|
||||||
|
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
|
||||||
|
}
|
||||||
|
|
||||||
function visibleProfileNamesForUser(ctx: any): string[] {
|
function visibleProfileNamesForUser(ctx: any): string[] {
|
||||||
const diskProfiles = listProfileNamesFromDisk()
|
const diskProfiles = listProfileNamesFromDisk()
|
||||||
const user = ctx.state?.user
|
const user = ctx.state?.user
|
||||||
@@ -859,7 +863,7 @@ export async function setModelAlias(ctx: any) {
|
|||||||
|
|
||||||
export async function getConfigModels(ctx: any) {
|
export async function getConfigModels(ctx: any) {
|
||||||
try {
|
try {
|
||||||
const config = await readConfigYaml()
|
const config = await readConfigYamlForProfile(requestScopedProfileName(ctx))
|
||||||
ctx.body = buildModelGroups(config)
|
ctx.body = buildModelGroups(config)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.status = 500
|
ctx.status = 500
|
||||||
@@ -875,7 +879,8 @@ export async function setConfigModel(ctx: any) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await updateConfigYaml((config) => {
|
const profile = requestScopedProfileName(ctx)
|
||||||
|
await updateConfigYamlForProfile(profile, (config) => {
|
||||||
config.model = {}
|
config.model = {}
|
||||||
config.model.default = defaultModel
|
config.model.default = defaultModel
|
||||||
if (reqProvider) { config.model.provider = reqProvider }
|
if (reqProvider) { config.model.provider = reqProvider }
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import { existsSync, readFileSync } from 'fs'
|
import { existsSync, readFileSync } from 'fs'
|
||||||
import { writeFile } from 'fs/promises'
|
import { writeFile } from 'fs/promises'
|
||||||
import { getActiveAuthPath } from '../../services/hermes/hermes-profile'
|
import { join } from 'path'
|
||||||
import * as hermesCli from '../../services/hermes/hermes-cli'
|
import { getActiveProfileName, getProfileDir } from '../../services/hermes/hermes-profile'
|
||||||
import { updateConfigYaml, saveEnvValue, PROVIDER_ENV_MAP } from '../../services/config-helpers'
|
import { updateConfigYamlForProfile, saveEnvValueForProfile, PROVIDER_ENV_MAP } from '../../services/config-helpers'
|
||||||
import { PROVIDER_PRESETS } from '../../shared/providers'
|
import { PROVIDER_PRESETS } from '../../shared/providers'
|
||||||
import { logger } from '../../services/logger'
|
import { logger } from '../../services/logger'
|
||||||
|
|
||||||
const OPTIONAL_API_KEY_PROVIDERS = new Set(['cliproxyapi', 'xai-oauth'])
|
const OPTIONAL_API_KEY_PROVIDERS = new Set(['cliproxyapi', 'xai-oauth'])
|
||||||
const DIRECT_CONFIG_PROVIDERS = new Set(['xai-oauth'])
|
const DIRECT_CONFIG_PROVIDERS = new Set(['xai-oauth'])
|
||||||
|
|
||||||
async function clearStoredAuthProvider(poolKey: string) {
|
function requestedProfile(ctx: any): string {
|
||||||
|
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
function authPathForProfile(profile: string): string {
|
||||||
|
return join(getProfileDir(profile), 'auth.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearStoredAuthProvider(profile: string, poolKey: string) {
|
||||||
try {
|
try {
|
||||||
const authPath = getActiveAuthPath()
|
const authPath = authPathForProfile(profile)
|
||||||
if (!existsSync(authPath)) return
|
if (!existsSync(authPath)) return
|
||||||
|
|
||||||
const auth = JSON.parse(readFileSync(authPath, 'utf-8'))
|
const auth = JSON.parse(readFileSync(authPath, 'utf-8'))
|
||||||
@@ -49,9 +57,10 @@ export async function create(ctx: any) {
|
|||||||
ctx.status = 400; ctx.body = { error: 'Missing API key' }; return
|
ctx.status = 400; ctx.body = { error: 'Missing API key' }; return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const profile = requestedProfile(ctx)
|
||||||
const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
||||||
const isBuiltin = poolKey in PROVIDER_ENV_MAP
|
const isBuiltin = poolKey in PROVIDER_ENV_MAP
|
||||||
await updateConfigYaml(async (config) => {
|
await updateConfigYamlForProfile(profile, async (config) => {
|
||||||
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
|
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
|
||||||
if (!isBuiltin) {
|
if (!isBuiltin) {
|
||||||
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
|
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
|
||||||
@@ -79,12 +88,12 @@ export async function create(ctx: any) {
|
|||||||
config.model.provider = poolKey
|
config.model.provider = poolKey
|
||||||
} else {
|
} else {
|
||||||
if (PROVIDER_ENV_MAP[poolKey].api_key_env) {
|
if (PROVIDER_ENV_MAP[poolKey].api_key_env) {
|
||||||
await saveEnvValue(PROVIDER_ENV_MAP[poolKey].api_key_env, api_key)
|
await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].api_key_env, api_key)
|
||||||
if (PROVIDER_ENV_MAP[poolKey].base_url_env) { await saveEnvValue(PROVIDER_ENV_MAP[poolKey].base_url_env, base_url) }
|
if (PROVIDER_ENV_MAP[poolKey].base_url_env) { await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].base_url_env, base_url) }
|
||||||
config.model.default = model
|
config.model.default = model
|
||||||
config.model.provider = poolKey
|
config.model.provider = poolKey
|
||||||
} else if (DIRECT_CONFIG_PROVIDERS.has(poolKey)) {
|
} else if (DIRECT_CONFIG_PROVIDERS.has(poolKey)) {
|
||||||
if (PROVIDER_ENV_MAP[poolKey].base_url_env) { await saveEnvValue(PROVIDER_ENV_MAP[poolKey].base_url_env, base_url) }
|
if (PROVIDER_ENV_MAP[poolKey].base_url_env) { await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].base_url_env, base_url) }
|
||||||
config.model.default = model
|
config.model.default = model
|
||||||
config.model.provider = poolKey
|
config.model.provider = poolKey
|
||||||
} else {
|
} else {
|
||||||
@@ -131,9 +140,10 @@ export async function update(ctx: any) {
|
|||||||
name?: string; base_url?: string; api_key?: string; model?: string
|
name?: string; base_url?: string; api_key?: string; model?: string
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const profile = requestedProfile(ctx)
|
||||||
const isCustom = poolKey.startsWith('custom:')
|
const isCustom = poolKey.startsWith('custom:')
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
const found = await updateConfigYaml((config) => {
|
const found = await updateConfigYamlForProfile(profile, (config) => {
|
||||||
if (!Array.isArray(config.custom_providers)) return { data: config, result: false, write: false }
|
if (!Array.isArray(config.custom_providers)) return { data: config, result: false, write: false }
|
||||||
const entry = (config.custom_providers as any[]).find((e: any) => {
|
const entry = (config.custom_providers as any[]).find((e: any) => {
|
||||||
return `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
|
return `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
|
||||||
@@ -153,7 +163,7 @@ export async function update(ctx: any) {
|
|||||||
if (!envMapping?.api_key_env) {
|
if (!envMapping?.api_key_env) {
|
||||||
ctx.status = 400; ctx.body = { error: `Cannot update credentials for "${poolKey}"` }; return
|
ctx.status = 400; ctx.body = { error: `Cannot update credentials for "${poolKey}"` }; return
|
||||||
}
|
}
|
||||||
if (api_key !== undefined) { await saveEnvValue(envMapping.api_key_env, api_key) }
|
if (api_key !== undefined) { await saveEnvValueForProfile(profile, envMapping.api_key_env, api_key) }
|
||||||
}
|
}
|
||||||
// TODO: Test if provider works without gateway restart
|
// TODO: Test if provider works without gateway restart
|
||||||
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
||||||
@@ -166,8 +176,9 @@ export async function update(ctx: any) {
|
|||||||
export async function remove(ctx: any) {
|
export async function remove(ctx: any) {
|
||||||
const poolKey = decodeURIComponent(ctx.params.poolKey)
|
const poolKey = decodeURIComponent(ctx.params.poolKey)
|
||||||
try {
|
try {
|
||||||
|
const profile = requestedProfile(ctx)
|
||||||
const isCustom = poolKey.startsWith('custom:')
|
const isCustom = poolKey.startsWith('custom:')
|
||||||
const removed = await updateConfigYaml(async (config) => {
|
const removed = await updateConfigYamlForProfile(profile, async (config) => {
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
const idx = Array.isArray(config.custom_providers)
|
const idx = Array.isArray(config.custom_providers)
|
||||||
? (config.custom_providers as any[]).findIndex((e: any) => {
|
? (config.custom_providers as any[]).findIndex((e: any) => {
|
||||||
@@ -179,8 +190,8 @@ export async function remove(ctx: any) {
|
|||||||
} else {
|
} else {
|
||||||
const envMapping = PROVIDER_ENV_MAP[poolKey]
|
const envMapping = PROVIDER_ENV_MAP[poolKey]
|
||||||
if (envMapping?.api_key_env) {
|
if (envMapping?.api_key_env) {
|
||||||
await saveEnvValue(envMapping.api_key_env, '')
|
await saveEnvValueForProfile(profile, envMapping.api_key_env, '')
|
||||||
if (envMapping.base_url_env) { await saveEnvValue(envMapping.base_url_env, '') }
|
if (envMapping.base_url_env) { await saveEnvValueForProfile(profile, envMapping.base_url_env, '') }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.model?.provider === poolKey) {
|
if (config.model?.provider === poolKey) {
|
||||||
@@ -208,7 +219,7 @@ export async function remove(ctx: any) {
|
|||||||
ctx.status = 404; ctx.body = { error: `Provider "${poolKey}" not found` }; return
|
ctx.status = 404; ctx.body = { error: `Provider "${poolKey}" not found` }; return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await clearStoredAuthProvider(poolKey)
|
await clearStoredAuthProvider(profile, poolKey)
|
||||||
// TODO: Test if provider works without gateway restart
|
// TODO: Test if provider works without gateway restart
|
||||||
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
||||||
ctx.body = { success: true }
|
ctx.body = { success: true }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { readFile, chmod } from 'fs/promises'
|
|||||||
import { readdir, stat } from 'fs/promises'
|
import { readdir, stat } from 'fs/promises'
|
||||||
import { existsSync, readFileSync } from 'fs'
|
import { existsSync, readFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getActiveProfileDir, getActiveConfigPath, getActiveEnvPath, getActiveAuthPath, getProfileDir } from './hermes/hermes-profile'
|
import { getActiveProfileDir, getActiveConfigPath, getActiveEnvPath, getProfileDir } from './hermes/hermes-profile'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
import { safeFileStore } from './safe-file-store'
|
import { safeFileStore } from './safe-file-store'
|
||||||
|
|
||||||
@@ -71,13 +71,15 @@ export interface ModelGroup {
|
|||||||
// --- Config YAML helpers ---
|
// --- Config YAML helpers ---
|
||||||
|
|
||||||
const configPath = () => getActiveConfigPath()
|
const configPath = () => getActiveConfigPath()
|
||||||
|
const configPathForProfile = (profile: string) => join(getProfileDir(profile), 'config.yaml')
|
||||||
|
const envPathForProfile = (profile: string) => join(getProfileDir(profile), '.env')
|
||||||
|
|
||||||
export async function readConfigYaml(): Promise<Record<string, any>> {
|
export async function readConfigYaml(): Promise<Record<string, any>> {
|
||||||
return safeFileStore.readYaml(configPath())
|
return safeFileStore.readYaml(configPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readConfigYamlForProfile(profile: string): Promise<Record<string, any>> {
|
export async function readConfigYamlForProfile(profile: string): Promise<Record<string, any>> {
|
||||||
return safeFileStore.readYaml(join(getProfileDir(profile), 'config.yaml'))
|
return safeFileStore.readYaml(configPathForProfile(profile))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeConfigYaml(config: Record<string, any>): Promise<void> {
|
export async function writeConfigYaml(config: Record<string, any>): Promise<void> {
|
||||||
@@ -90,6 +92,13 @@ export async function updateConfigYaml<T = void>(
|
|||||||
return safeFileStore.updateYaml(configPath(), updater, { backup: true })
|
return safeFileStore.updateYaml(configPath(), updater, { backup: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateConfigYamlForProfile<T = void>(
|
||||||
|
profile: string,
|
||||||
|
updater: (config: Record<string, any>) => Record<string, any> | { data: Record<string, any>; result: T; write?: boolean } | Promise<Record<string, any> | { data: Record<string, any>; result: T; write?: boolean }>,
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
return safeFileStore.updateYaml(configPathForProfile(profile), updater, { backup: true })
|
||||||
|
}
|
||||||
|
|
||||||
export function stripLegacyApiServerGatewayConfig(config: Record<string, any>): { config: Record<string, any>; changed: boolean } {
|
export function stripLegacyApiServerGatewayConfig(config: Record<string, any>): { config: Record<string, any>; changed: boolean } {
|
||||||
if (!config.platforms || typeof config.platforms !== 'object' || Array.isArray(config.platforms)) {
|
if (!config.platforms || typeof config.platforms !== 'object' || Array.isArray(config.platforms)) {
|
||||||
return { config, changed: false }
|
return { config, changed: false }
|
||||||
@@ -112,9 +121,8 @@ function assertValidEnvKey(key: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveEnvValue(key: string, value: string): Promise<void> {
|
async function saveEnvValueAtPath(envPath: string, key: string, value: string): Promise<void> {
|
||||||
assertValidEnvKey(key)
|
assertValidEnvKey(key)
|
||||||
const envPath = getActiveEnvPath()
|
|
||||||
await safeFileStore.updateText(envPath, (raw) => {
|
await safeFileStore.updateText(envPath, (raw) => {
|
||||||
const remove = !value
|
const remove = !value
|
||||||
const lines = raw.split('\n')
|
const lines = raw.split('\n')
|
||||||
@@ -143,6 +151,14 @@ export async function saveEnvValue(key: string, value: string): Promise<void> {
|
|||||||
try { await chmod(envPath, 0o600) } catch { /* ignore */ }
|
try { await chmod(envPath, 0o600) } catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveEnvValue(key: string, value: string): Promise<void> {
|
||||||
|
await saveEnvValueAtPath(getActiveEnvPath(), key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveEnvValueForProfile(profile: string, key: string, value: string): Promise<void> {
|
||||||
|
await saveEnvValueAtPath(envPathForProfile(profile), key, value)
|
||||||
|
}
|
||||||
|
|
||||||
// --- File helpers ---
|
// --- File helpers ---
|
||||||
|
|
||||||
export async function safeReadFile(filePath: string): Promise<string | null> {
|
export async function safeReadFile(filePath: string): Promise<string | null> {
|
||||||
|
|||||||
@@ -15,17 +15,18 @@ async function loadProvidersController() {
|
|||||||
return import('../../packages/server/src/controllers/hermes/providers')
|
return import('../../packages/server/src/controllers/hermes/providers')
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCtx(poolKey: string) {
|
function makeCtx(poolKey: string, overrides: Record<string, any> = {}) {
|
||||||
return {
|
return {
|
||||||
params: { poolKey: encodeURIComponent(poolKey) },
|
params: { poolKey: encodeURIComponent(poolKey) },
|
||||||
request: { body: {} },
|
request: { body: {} },
|
||||||
status: 200,
|
status: 200,
|
||||||
body: undefined as unknown,
|
body: undefined as unknown,
|
||||||
|
...overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAuth() {
|
function readAuth(profileDir = hermesHome) {
|
||||||
return JSON.parse(readFileSync(join(hermesHome, 'auth.json'), 'utf-8'))
|
return JSON.parse(readFileSync(join(profileDir, 'auth.json'), 'utf-8'))
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('providers controller delete', () => {
|
describe('providers controller delete', () => {
|
||||||
@@ -153,4 +154,86 @@ describe('providers controller delete', () => {
|
|||||||
expect(ctx.body).toEqual({ success: true })
|
expect(ctx.body).toEqual({ success: true })
|
||||||
expect(existsSync(join(hermesHome, 'auth.json'))).toBe(false)
|
expect(existsSync(join(hermesHome, 'auth.json'))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('deletes provider state from the request-scoped profile only', async () => {
|
||||||
|
const researchDir = join(hermesHome, 'profiles', 'research')
|
||||||
|
mkdirSync(researchDir, { recursive: true })
|
||||||
|
writeFileSync(join(hermesHome, 'config.yaml'), [
|
||||||
|
'model:',
|
||||||
|
' provider: deepseek',
|
||||||
|
' default: keep-default-model',
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
writeFileSync(join(hermesHome, '.env'), [
|
||||||
|
['DEEPSEEK_API_KEY', 'keep-default-key'].join('='),
|
||||||
|
['OPENROUTER_API_KEY', 'keep-default-openrouter'].join('='),
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
writeFileSync(join(hermesHome, 'auth.json'), JSON.stringify({
|
||||||
|
providers: {
|
||||||
|
deepseek: { access_token: 'keep-default-token' },
|
||||||
|
},
|
||||||
|
credential_pool: {
|
||||||
|
deepseek: [{ label: 'keep-default' }],
|
||||||
|
},
|
||||||
|
}, null, 2))
|
||||||
|
writeFileSync(join(researchDir, 'config.yaml'), [
|
||||||
|
'model:',
|
||||||
|
' provider: deepseek',
|
||||||
|
' default: research-model',
|
||||||
|
'custom_providers:',
|
||||||
|
' - name: keep-provider',
|
||||||
|
' base_url: https://keep.invalid/v1',
|
||||||
|
' api_key: placeholder',
|
||||||
|
' model: keep-model',
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
writeFileSync(join(researchDir, '.env'), [
|
||||||
|
['DEEPSEEK_API_KEY', 'remove-research-key'].join('='),
|
||||||
|
['OPENROUTER_API_KEY', 'keep-research-openrouter'].join('='),
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
writeFileSync(join(researchDir, 'auth.json'), JSON.stringify({
|
||||||
|
providers: {
|
||||||
|
deepseek: { access_token: 'remove-research-token' },
|
||||||
|
openrouter: { access_token: 'keep-research-token' },
|
||||||
|
},
|
||||||
|
credential_pool: {
|
||||||
|
deepseek: [{ label: 'remove-research' }],
|
||||||
|
openrouter: [{ label: 'keep-research' }],
|
||||||
|
},
|
||||||
|
}, null, 2))
|
||||||
|
|
||||||
|
const { remove } = await loadProvidersController()
|
||||||
|
const ctx = makeCtx('deepseek', { state: { profile: { name: 'research' } } })
|
||||||
|
|
||||||
|
await remove(ctx)
|
||||||
|
|
||||||
|
expect(ctx.body).toEqual({ success: true })
|
||||||
|
|
||||||
|
const defaultEnvAfter = readFileSync(join(hermesHome, '.env'), 'utf-8')
|
||||||
|
expect(defaultEnvAfter).toContain(['DEEPSEEK_API_KEY', 'keep-default-key'].join('='))
|
||||||
|
expect(defaultEnvAfter).toContain(['OPENROUTER_API_KEY', 'keep-default-openrouter'].join('='))
|
||||||
|
expect(readFileSync(join(hermesHome, 'config.yaml'), 'utf-8')).toContain('keep-default-model')
|
||||||
|
expect(readAuth()).toEqual({
|
||||||
|
providers: {
|
||||||
|
deepseek: { access_token: 'keep-default-token' },
|
||||||
|
},
|
||||||
|
credential_pool: {
|
||||||
|
deepseek: [{ label: 'keep-default' }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const researchEnvAfter = readFileSync(join(researchDir, '.env'), 'utf-8')
|
||||||
|
expect(researchEnvAfter).not.toContain('DEEPSEEK_API_KEY')
|
||||||
|
expect(researchEnvAfter).toContain(['OPENROUTER_API_KEY', 'keep-research-openrouter'].join('='))
|
||||||
|
const researchConfigAfter = readFileSync(join(researchDir, 'config.yaml'), 'utf-8')
|
||||||
|
expect(researchConfigAfter).toContain('keep-provider')
|
||||||
|
expect(researchConfigAfter).toContain('keep-model')
|
||||||
|
const researchAuthAfter = readAuth(researchDir)
|
||||||
|
expect(researchAuthAfter.providers).not.toHaveProperty('deepseek')
|
||||||
|
expect(researchAuthAfter.credential_pool).not.toHaveProperty('deepseek')
|
||||||
|
expect(researchAuthAfter.providers.openrouter).toEqual({ access_token: 'keep-research-token' })
|
||||||
|
expect(researchAuthAfter.credential_pool.openrouter).toEqual([{ label: 'keep-research' }])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user