- 表单下拉框不再用 label 包裹,改为 div + aria-label,避免点击后 下拉面板瞬间关闭 - Provider 卡片新增「从上游获取模型」和「修改」入口,支持编辑 base URL、API Key 与 api_mode,凭据写入对应的环境变量 - 服务端抽出 provider-model-discovery 统一不同 api_mode 的模型发现, 旧的 custom_providers 与新的 providers 字典结构都能正确更新 - Agent Bridge 改为后台启动,HTTP 接口在 Python 运行时就绪前即可用; worker 连接被拒时按类型重试,不重放可能已产生副作用的请求 - 断线重连后恢复 profiles、models 与会话列表,健康检查改为自调度 的 setTimeout,离线时 2 秒后重试 - 批量删除确认文案改用 Popconfirm content,避免变成侧栏触发器文本
40 lines
2.4 KiB
TypeScript
40 lines
2.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { discoverProviderModels } from '../../packages/server/src/services/hermes/provider-model-discovery'
|
|
|
|
afterEach(() => vi.unstubAllGlobals())
|
|
|
|
describe('live provider model discovery', () => {
|
|
it('uses Anthropic authentication and collects every page before returning the catalog', async () => {
|
|
const fetch = vi.fn()
|
|
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'MiniMax-M3' }], has_more: true, last_id: 'MiniMax-M3' }) })
|
|
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'MiniMax-M2.7' }], has_more: false }) })
|
|
vi.stubGlobal('fetch', fetch)
|
|
await expect(discoverProviderModels('https://proxy.invalid/anthropic/v1/', 'test-key', true)).resolves.toEqual(['MiniMax-M2.7', 'MiniMax-M3'])
|
|
expect(fetch).toHaveBeenNthCalledWith(1, 'https://proxy.invalid/anthropic/v1/models', expect.objectContaining({
|
|
headers: { 'x-api-key': 'test-key', 'anthropic-version': '2023-06-01' },
|
|
}))
|
|
expect(fetch.mock.calls[1][0]).toContain('after_id=MiniMax-M3')
|
|
})
|
|
|
|
it('uses Bearer authentication for OpenAI-compatible endpoints and normalizes model IDs', async () => {
|
|
const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: [{ id: 'b' }, { id: '' }, { id: 'a' }, { id: 'b' }] }) })
|
|
vi.stubGlobal('fetch', fetch)
|
|
await expect(discoverProviderModels('https://proxy.invalid', 'test-key')).resolves.toEqual(['a', 'b'])
|
|
expect(fetch).toHaveBeenCalledWith('https://proxy.invalid/v1/models', expect.objectContaining({ headers: { Authorization: 'Bearer test-key' } }))
|
|
})
|
|
|
|
it('rejects a failed later page instead of returning a partial list', async () => {
|
|
vi.stubGlobal('fetch', vi.fn()
|
|
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'first' }], has_more: true, last_id: 'first' }) })
|
|
.mockResolvedValueOnce({ ok: false, status: 503 }))
|
|
await expect(discoverProviderModels('https://proxy.invalid/v1', 'test-key', true)).rejects.toThrow('HTTP 503')
|
|
})
|
|
|
|
it('rejects a repeated cursor instead of looping', async () => {
|
|
const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: [{ id: 'first' }], has_more: true, last_id: 'first' }) })
|
|
vi.stubGlobal('fetch', fetch)
|
|
await expect(discoverProviderModels('https://proxy.invalid/v1', 'test-key', true)).rejects.toThrow('invalid model pagination')
|
|
expect(fetch).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|