mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-22 13:17:10 +08:00
- 新增 cursor-agent provider,支持创建 agent、轮询 run 状态和 SSE 流式响应 - 接入 AITestProvider 与 AIListModels,支持 Cursor 官方 /v1/models 连通性测试和模型发现 - 在 AI 设置中新增 Cursor 供应商预设,固定 cursor-agent 协议并补齐默认端点配置 - 调整 provider readiness 与 insights 规则,允许 Cursor 未显式选模型时走官方默认模型 - 补充后端 provider/service 测试和前端 preset、表单、readiness 相关用例 Close #576
237 lines
6.7 KiB
TypeScript
237 lines
6.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { AIProviderType } from '../types';
|
|
import {
|
|
LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL,
|
|
QWEN_BAILIAN_ANTHROPIC_BASE_URL,
|
|
QWEN_BAILIAN_MODELS_BASE_URL,
|
|
QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
|
|
QWEN_CODING_PLAN_MODELS,
|
|
matchQwenPresetKey,
|
|
resolvePresetBaseURL,
|
|
resolvePresetModelSelection,
|
|
resolvePresetTransport,
|
|
resolveProviderPresetKey,
|
|
} from './aiProviderPresets';
|
|
|
|
type PresetMatcher = {
|
|
key: string;
|
|
backendType: AIProviderType;
|
|
defaultBaseUrl: string;
|
|
fixedApiFormat?: string;
|
|
};
|
|
|
|
const PRESETS: PresetMatcher[] = [
|
|
{ key: 'openai', backendType: 'openai', defaultBaseUrl: 'https://api.openai.com/v1' },
|
|
{ key: 'qwen-bailian', backendType: 'anthropic', defaultBaseUrl: QWEN_BAILIAN_ANTHROPIC_BASE_URL },
|
|
{
|
|
key: 'qwen-coding-plan',
|
|
backendType: 'custom',
|
|
defaultBaseUrl: QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
|
|
fixedApiFormat: 'claude-cli',
|
|
},
|
|
{ key: 'codebuddy', backendType: 'custom', defaultBaseUrl: '', fixedApiFormat: 'codebuddy-cli' },
|
|
{ key: 'cursor', backendType: 'custom', defaultBaseUrl: 'https://api.cursor.com/v1', fixedApiFormat: 'cursor-agent' },
|
|
{ key: 'custom', backendType: 'custom', defaultBaseUrl: '' },
|
|
];
|
|
|
|
describe('ai provider preset helpers', () => {
|
|
it('maps legacy Bailian compatible-mode URL back to the Bailian preset', () => {
|
|
expect(matchQwenPresetKey({
|
|
type: 'openai',
|
|
baseUrl: QWEN_BAILIAN_MODELS_BASE_URL,
|
|
})).toBe('qwen-bailian');
|
|
});
|
|
|
|
it('maps Coding Plan Claude CLI config back to the dedicated Coding Plan preset', () => {
|
|
expect(matchQwenPresetKey({
|
|
type: 'custom',
|
|
apiFormat: 'claude-cli',
|
|
baseUrl: QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
|
|
})).toBe('qwen-coding-plan');
|
|
});
|
|
|
|
it('maps legacy Coding Plan OpenAI config back to the dedicated Coding Plan preset', () => {
|
|
expect(matchQwenPresetKey({
|
|
type: 'openai',
|
|
baseUrl: LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL,
|
|
})).toBe('qwen-coding-plan');
|
|
});
|
|
|
|
it('does not treat a custom OpenAI endpoint as the built-in Coding Plan preset', () => {
|
|
expect(matchQwenPresetKey({
|
|
type: 'custom',
|
|
apiFormat: 'openai',
|
|
baseUrl: LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL,
|
|
})).toBeNull();
|
|
});
|
|
|
|
it('does not keep a baked-in model list for the Coding Plan preset', () => {
|
|
expect(QWEN_CODING_PLAN_MODELS).toEqual([
|
|
'qwen3.5-plus',
|
|
'kimi-k2.5',
|
|
'glm-5',
|
|
'MiniMax-M2.5',
|
|
'qwen3-max-2026-01-23',
|
|
'qwen3-coder-next',
|
|
'qwen3-coder-plus',
|
|
'glm-4.7',
|
|
]);
|
|
});
|
|
|
|
it('keeps built-in preset model empty when the preset intentionally requires an explicit selection', () => {
|
|
expect(resolvePresetModelSelection({
|
|
presetKey: 'qwen-coding-plan',
|
|
presetDefaultModel: '',
|
|
presetModels: QWEN_CODING_PLAN_MODELS,
|
|
valuesModel: '',
|
|
customModels: [],
|
|
})).toEqual({
|
|
model: '',
|
|
models: QWEN_CODING_PLAN_MODELS,
|
|
});
|
|
});
|
|
|
|
it('still falls back to the first configured model for custom-like presets', () => {
|
|
expect(resolvePresetModelSelection({
|
|
presetKey: 'custom',
|
|
presetDefaultModel: '',
|
|
presetModels: [],
|
|
valuesModel: '',
|
|
customModels: ['foo-model', 'bar-model'],
|
|
})).toEqual({
|
|
model: 'foo-model',
|
|
models: ['foo-model', 'bar-model'],
|
|
});
|
|
});
|
|
|
|
it('keeps Cursor model empty when only a suggested model list is configured', () => {
|
|
expect(resolvePresetModelSelection({
|
|
presetKey: 'cursor',
|
|
presetDefaultModel: '',
|
|
presetModels: [],
|
|
valuesModel: '',
|
|
customModels: ['composer-2', 'composer-latest'],
|
|
})).toEqual({
|
|
model: '',
|
|
models: ['composer-2', 'composer-latest'],
|
|
});
|
|
});
|
|
|
|
it('forces built-in presets back to their standard base URL when saving or testing', () => {
|
|
expect(resolvePresetBaseURL({
|
|
presetKey: 'qwen-bailian',
|
|
presetDefaultBaseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
valuesBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
})).toBe('https://dashscope.aliyuncs.com/apps/anthropic');
|
|
});
|
|
|
|
it('keeps the user-entered base URL for custom-like presets', () => {
|
|
expect(resolvePresetBaseURL({
|
|
presetKey: 'codebuddy',
|
|
presetDefaultBaseUrl: '',
|
|
valuesBaseUrl: 'https://example-proxy.internal/v1',
|
|
})).toBe('https://example-proxy.internal/v1');
|
|
});
|
|
|
|
it('keeps the user-entered base URL for the Cursor preset', () => {
|
|
expect(resolvePresetBaseURL({
|
|
presetKey: 'cursor',
|
|
presetDefaultBaseUrl: 'https://api.cursor.com/v1',
|
|
valuesBaseUrl: 'https://cursor-proxy.internal/v1',
|
|
})).toBe('https://cursor-proxy.internal/v1');
|
|
});
|
|
|
|
it('forces qwen coding plan to save as custom plus claude-cli', () => {
|
|
expect(resolvePresetTransport({
|
|
presetBackendType: 'custom',
|
|
presetFixedApiFormat: 'claude-cli',
|
|
valuesApiFormat: 'anthropic',
|
|
})).toEqual({
|
|
type: 'custom',
|
|
apiFormat: 'claude-cli',
|
|
});
|
|
});
|
|
|
|
it('keeps custom preset transport editable', () => {
|
|
expect(resolvePresetTransport({
|
|
presetBackendType: 'custom',
|
|
valuesApiFormat: 'gemini',
|
|
})).toEqual({
|
|
type: 'custom',
|
|
apiFormat: 'gemini',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('resolveProviderPresetKey', () => {
|
|
it('不会把自定义 OpenAI 端点误识别成千问 Coding Plan', () => {
|
|
const key = resolveProviderPresetKey(
|
|
{
|
|
type: 'custom',
|
|
apiFormat: 'openai',
|
|
baseUrl: LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL,
|
|
},
|
|
PRESETS,
|
|
'custom',
|
|
);
|
|
|
|
expect(key).toBe('custom');
|
|
});
|
|
|
|
it('仍然能识别当前内置的千问 Coding Plan 预设', () => {
|
|
const key = resolveProviderPresetKey(
|
|
{
|
|
type: 'custom',
|
|
apiFormat: 'claude-cli',
|
|
baseUrl: QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
|
|
},
|
|
PRESETS,
|
|
'custom',
|
|
);
|
|
|
|
expect(key).toBe('qwen-coding-plan');
|
|
});
|
|
|
|
it('仍然能识别当前内置的千问百炼预设', () => {
|
|
const key = resolveProviderPresetKey(
|
|
{
|
|
type: 'anthropic',
|
|
apiFormat: undefined,
|
|
baseUrl: QWEN_BAILIAN_ANTHROPIC_BASE_URL,
|
|
},
|
|
PRESETS,
|
|
'custom',
|
|
);
|
|
|
|
expect(key).toBe('qwen-bailian');
|
|
});
|
|
|
|
it('能识别没有 Base URL 的 CodeBuddy CLI 预设', () => {
|
|
const key = resolveProviderPresetKey(
|
|
{
|
|
type: 'custom',
|
|
apiFormat: 'codebuddy-cli',
|
|
baseUrl: '',
|
|
},
|
|
PRESETS,
|
|
'custom',
|
|
);
|
|
|
|
expect(key).toBe('codebuddy');
|
|
});
|
|
|
|
it('能识别 Cursor Agent 预设', () => {
|
|
const key = resolveProviderPresetKey(
|
|
{
|
|
type: 'custom',
|
|
apiFormat: 'cursor-agent',
|
|
baseUrl: 'https://api.cursor.com/v1',
|
|
},
|
|
PRESETS,
|
|
'custom',
|
|
);
|
|
|
|
expect(key).toBe('cursor');
|
|
});
|
|
});
|