@lobehub/chat 1.138.3 → 1.138.5
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +656 -0
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +585 -0
- package/packages/model-runtime/src/core/contextBuilders/google.ts +201 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +191 -179
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +305 -47
- package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +93 -84
- package/packages/model-runtime/src/providers/anthropic/generateObject.ts +3 -3
- package/packages/model-runtime/src/providers/google/generateObject.test.ts +588 -83
- package/packages/model-runtime/src/providers/google/generateObject.ts +104 -6
- package/packages/model-runtime/src/providers/google/index.test.ts +0 -395
- package/packages/model-runtime/src/providers/google/index.ts +28 -194
- package/packages/model-runtime/src/providers/openai/index.test.ts +18 -17
- package/packages/model-runtime/src/types/structureOutput.ts +3 -4
- package/packages/types/src/aiChat.ts +0 -1
- package/src/app/(backend)/trpc/edge/[trpc]/route.ts +0 -2
- package/src/server/routers/edge/index.ts +2 -1
- package/src/server/routers/lambda/aiChat.ts +1 -2
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/upload.ts +16 -0
- package/src/services/__tests__/upload.test.ts +266 -18
- package/src/services/upload.ts +2 -2
|
@@ -419,6 +419,318 @@ describe('AiInfraRepos', () => {
|
|
|
419
419
|
// For custom provider, when user enables search with no builtin settings, default to 'params'
|
|
420
420
|
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
|
421
421
|
});
|
|
422
|
+
|
|
423
|
+
// 测试场景:用户模型 abilitie 为空(Empty),而基础模型有搜索能力和设置
|
|
424
|
+
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
425
|
+
const mockProviders = [
|
|
426
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
const userModel: EnabledAiModel = {
|
|
430
|
+
id: 'gpt-4',
|
|
431
|
+
providerId: 'openai',
|
|
432
|
+
enabled: true,
|
|
433
|
+
type: 'chat',
|
|
434
|
+
abilities: {}, // Empty object, no search
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const builtinModel = {
|
|
438
|
+
id: 'gpt-4',
|
|
439
|
+
enabled: true,
|
|
440
|
+
type: 'chat' as const,
|
|
441
|
+
abilities: { search: false }, // 使用 builtin abilities
|
|
442
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
446
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
447
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
448
|
+
|
|
449
|
+
const result = await repo.getEnabledModels();
|
|
450
|
+
|
|
451
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
452
|
+
expect(merged).toBeDefined();
|
|
453
|
+
// 使用 builtin abilities
|
|
454
|
+
expect(merged?.abilities?.search).toEqual(false);
|
|
455
|
+
// 删去 builtin settings
|
|
456
|
+
expect(merged?.settings).toBeUndefined();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
460
|
+
const mockProviders = [
|
|
461
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
const userModel: EnabledAiModel = {
|
|
465
|
+
id: 'gpt-4',
|
|
466
|
+
providerId: 'openai',
|
|
467
|
+
enabled: true,
|
|
468
|
+
type: 'chat',
|
|
469
|
+
abilities: {}, // Empty object, no search
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const builtinModel = {
|
|
473
|
+
id: 'gpt-4',
|
|
474
|
+
enabled: true,
|
|
475
|
+
type: 'chat' as const,
|
|
476
|
+
abilities: { search: true }, // 使用 builtin abilities
|
|
477
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
481
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
482
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
483
|
+
|
|
484
|
+
const result = await repo.getEnabledModels();
|
|
485
|
+
|
|
486
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
487
|
+
expect(merged).toBeDefined();
|
|
488
|
+
// 使用 builtin abilities
|
|
489
|
+
expect(merged?.abilities?.search).toEqual(true);
|
|
490
|
+
// 保留 builtin settings
|
|
491
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型有搜索能力和设置
|
|
495
|
+
it('should retain builtin settings when user model has no abilities.search (undefined) and builtin has settings', async () => {
|
|
496
|
+
const mockProviders = [
|
|
497
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
const userModel: EnabledAiModel = {
|
|
501
|
+
id: 'gpt-4',
|
|
502
|
+
providerId: 'openai',
|
|
503
|
+
enabled: true,
|
|
504
|
+
type: 'chat',
|
|
505
|
+
abilities: { vision: true }, // 启用 vision 能力, no search
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const builtinModel = {
|
|
509
|
+
id: 'gpt-4',
|
|
510
|
+
enabled: true,
|
|
511
|
+
type: 'chat' as const,
|
|
512
|
+
abilities: { search: false }, // builtin abilities 不生效
|
|
513
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
517
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
518
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
519
|
+
|
|
520
|
+
const result = await repo.getEnabledModels();
|
|
521
|
+
|
|
522
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
523
|
+
expect(merged).toBeDefined();
|
|
524
|
+
// abilities.search 仍 undefined(兼容老版本)
|
|
525
|
+
expect(merged?.abilities?.search).toBeUndefined();
|
|
526
|
+
// 保留 builtin settings
|
|
527
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should retain builtin settings when user model has no abilities.search (undefined) and builtin has settings', async () => {
|
|
531
|
+
const mockProviders = [
|
|
532
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
const userModel: EnabledAiModel = {
|
|
536
|
+
id: 'gpt-4',
|
|
537
|
+
providerId: 'openai',
|
|
538
|
+
enabled: true,
|
|
539
|
+
type: 'chat',
|
|
540
|
+
abilities: { vision: true }, // 启用 vision 能力, no search
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const builtinModel = {
|
|
544
|
+
id: 'gpt-4',
|
|
545
|
+
enabled: true,
|
|
546
|
+
type: 'chat' as const,
|
|
547
|
+
abilities: { search: true }, // builtin abilities 不生效
|
|
548
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
552
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
553
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
554
|
+
|
|
555
|
+
const result = await repo.getEnabledModels();
|
|
556
|
+
|
|
557
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
558
|
+
expect(merged).toBeDefined();
|
|
559
|
+
// abilities.search 仍 undefined(兼容老版本)
|
|
560
|
+
expect(merged?.abilities?.search).toBeUndefined();
|
|
561
|
+
// 保留 builtin settings
|
|
562
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型也无搜索能力和设置
|
|
566
|
+
it('should retain no settings when user model has no abilities.search (undefined) and builtin has no settings', async () => {
|
|
567
|
+
const mockProviders = [
|
|
568
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
const userModel: EnabledAiModel = {
|
|
572
|
+
id: 'gpt-4',
|
|
573
|
+
providerId: 'openai',
|
|
574
|
+
enabled: true,
|
|
575
|
+
type: 'chat',
|
|
576
|
+
abilities: {}, // 无 search
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const builtinModel = {
|
|
580
|
+
id: 'gpt-4',
|
|
581
|
+
enabled: true,
|
|
582
|
+
type: 'chat' as const,
|
|
583
|
+
abilities: {},
|
|
584
|
+
// builtin 无 settings
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
588
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
589
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
590
|
+
|
|
591
|
+
const result = await repo.getEnabledModels();
|
|
592
|
+
|
|
593
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
594
|
+
expect(merged).toBeDefined();
|
|
595
|
+
expect(merged?.abilities?.search).toBeUndefined();
|
|
596
|
+
// 无 settings
|
|
597
|
+
expect(merged?.settings).toBeUndefined();
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// 测试:用户模型有 abilities.search: true
|
|
601
|
+
it('should inject defaults when user has search: true, no existing settings (builtin none)', async () => {
|
|
602
|
+
const mockProviders = [
|
|
603
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
const userModel: EnabledAiModel = {
|
|
607
|
+
id: 'gpt-4',
|
|
608
|
+
providerId: 'openai',
|
|
609
|
+
enabled: true,
|
|
610
|
+
type: 'chat',
|
|
611
|
+
abilities: { search: true }, // 用户启用 search
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const builtinModel = {
|
|
615
|
+
id: 'gpt-4',
|
|
616
|
+
enabled: true,
|
|
617
|
+
type: 'chat' as const,
|
|
618
|
+
abilities: {},
|
|
619
|
+
// 无 settings
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
623
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
624
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
625
|
+
|
|
626
|
+
const result = await repo.getEnabledModels();
|
|
627
|
+
|
|
628
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
629
|
+
expect(merged).toBeDefined();
|
|
630
|
+
expect(merged?.abilities).toEqual({ search: true });
|
|
631
|
+
// 注入 defaults (openai: params)
|
|
632
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should retain existing settings when user has search: true and builtin has settings', async () => {
|
|
636
|
+
const mockProviders = [
|
|
637
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
638
|
+
];
|
|
639
|
+
|
|
640
|
+
const userModel: EnabledAiModel = {
|
|
641
|
+
id: 'gpt-4',
|
|
642
|
+
providerId: 'openai',
|
|
643
|
+
enabled: true,
|
|
644
|
+
type: 'chat',
|
|
645
|
+
abilities: { search: true },
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const builtinModel = {
|
|
649
|
+
id: 'gpt-4',
|
|
650
|
+
enabled: true,
|
|
651
|
+
type: 'chat' as const,
|
|
652
|
+
settings: { searchImpl: 'tool' }, // builtin 有 settings
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
656
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
657
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
658
|
+
|
|
659
|
+
const result = await repo.getEnabledModels();
|
|
660
|
+
|
|
661
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
662
|
+
expect(merged).toBeDefined();
|
|
663
|
+
expect(merged?.abilities).toEqual({ search: true });
|
|
664
|
+
// 使用 builtin settings
|
|
665
|
+
expect(merged?.settings).toEqual({ searchImpl: 'tool' });
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// 测试:用户模型有 abilities.search: false
|
|
669
|
+
it('should remove settings when user has search: false and builtin has settings', async () => {
|
|
670
|
+
const mockProviders = [
|
|
671
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
const userModel: EnabledAiModel = {
|
|
675
|
+
id: 'gpt-4',
|
|
676
|
+
providerId: 'openai',
|
|
677
|
+
enabled: true,
|
|
678
|
+
type: 'chat',
|
|
679
|
+
abilities: { search: false }, // 用户禁用 search
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const builtinModel = {
|
|
683
|
+
id: 'gpt-4',
|
|
684
|
+
enabled: true,
|
|
685
|
+
type: 'chat' as const,
|
|
686
|
+
settings: { searchImpl: 'tool', extendParams: [] }, // builtin 有 settings
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
690
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
691
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
692
|
+
|
|
693
|
+
const result = await repo.getEnabledModels();
|
|
694
|
+
|
|
695
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
696
|
+
expect(merged).toBeDefined();
|
|
697
|
+
expect(merged?.abilities).toEqual({ search: false });
|
|
698
|
+
// 移除 search 相关,保留其他
|
|
699
|
+
expect(merged?.settings).toEqual({ extendParams: [] });
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should keep no settings when user has search: false and no existing settings', async () => {
|
|
703
|
+
const mockProviders = [
|
|
704
|
+
{ enabled: true, id: 'openai', name: 'OpenAI', source: 'builtin' as const },
|
|
705
|
+
];
|
|
706
|
+
|
|
707
|
+
const userModel: EnabledAiModel = {
|
|
708
|
+
id: 'gpt-4',
|
|
709
|
+
providerId: 'openai',
|
|
710
|
+
enabled: true,
|
|
711
|
+
type: 'chat',
|
|
712
|
+
abilities: { search: false },
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const builtinModel = {
|
|
716
|
+
id: 'gpt-4',
|
|
717
|
+
enabled: true,
|
|
718
|
+
type: 'chat' as const,
|
|
719
|
+
// 无 settings
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
|
|
723
|
+
vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue([userModel]);
|
|
724
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([builtinModel]);
|
|
725
|
+
|
|
726
|
+
const result = await repo.getEnabledModels();
|
|
727
|
+
|
|
728
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
729
|
+
expect(merged).toBeDefined();
|
|
730
|
+
expect(merged?.abilities).toEqual({ search: false });
|
|
731
|
+
// 无 settings
|
|
732
|
+
expect(merged?.settings).toBeUndefined();
|
|
733
|
+
});
|
|
422
734
|
});
|
|
423
735
|
|
|
424
736
|
describe('getAiProviderModelList', () => {
|
|
@@ -614,6 +926,350 @@ describe('AiInfraRepos', () => {
|
|
|
614
926
|
// For custom provider, when user enables search with no builtin settings, default to 'params'
|
|
615
927
|
expect(merged.settings).toEqual({ searchImpl: 'params' });
|
|
616
928
|
});
|
|
929
|
+
|
|
930
|
+
// 测试场景:用户模型 abilitie 为空(Empty),而基础模型有搜索能力和设置
|
|
931
|
+
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
932
|
+
const providerId = 'openai';
|
|
933
|
+
|
|
934
|
+
const userModels: AiProviderModelListItem[] = [
|
|
935
|
+
{
|
|
936
|
+
id: 'gpt-4',
|
|
937
|
+
type: 'chat',
|
|
938
|
+
enabled: true,
|
|
939
|
+
abilities: {}, // Empty object, no search
|
|
940
|
+
},
|
|
941
|
+
];
|
|
942
|
+
|
|
943
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
944
|
+
{
|
|
945
|
+
id: 'gpt-4',
|
|
946
|
+
type: 'chat',
|
|
947
|
+
enabled: true,
|
|
948
|
+
abilities: { search: false }, // 使用 builtin abilities
|
|
949
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
950
|
+
},
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
954
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
955
|
+
|
|
956
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
957
|
+
|
|
958
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
959
|
+
expect(merged).toBeDefined();
|
|
960
|
+
// 使用 builtin abilities
|
|
961
|
+
expect(merged?.abilities?.search).toEqual(false);
|
|
962
|
+
// 保留 builtin settings
|
|
963
|
+
expect(merged?.settings).toBeUndefined();
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('should retain builtin abilities and settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
967
|
+
const providerId = 'openai';
|
|
968
|
+
|
|
969
|
+
const userModels: AiProviderModelListItem[] = [
|
|
970
|
+
{
|
|
971
|
+
id: 'gpt-4',
|
|
972
|
+
type: 'chat',
|
|
973
|
+
enabled: true,
|
|
974
|
+
abilities: {}, // Empty object, no search
|
|
975
|
+
},
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
979
|
+
{
|
|
980
|
+
id: 'gpt-4',
|
|
981
|
+
type: 'chat',
|
|
982
|
+
enabled: true,
|
|
983
|
+
abilities: { search: true }, // 使用 builtin abilities
|
|
984
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
985
|
+
},
|
|
986
|
+
];
|
|
987
|
+
|
|
988
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
989
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
990
|
+
|
|
991
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
992
|
+
|
|
993
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
994
|
+
expect(merged).toBeDefined();
|
|
995
|
+
// 使用 builtin abilities
|
|
996
|
+
expect(merged?.abilities?.search).toEqual(true);
|
|
997
|
+
// 保留 builtin settings
|
|
998
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// 测试场景:用户模型未启用搜索(abilities.search 为 undefined),而基础模型有搜索能力和设置
|
|
1002
|
+
it('should retain builtin settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
1003
|
+
const providerId = 'openai';
|
|
1004
|
+
|
|
1005
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1006
|
+
{
|
|
1007
|
+
id: 'gpt-4',
|
|
1008
|
+
type: 'chat',
|
|
1009
|
+
enabled: true,
|
|
1010
|
+
abilities: { vision: true }, // 启用 vision 能力, no search
|
|
1011
|
+
},
|
|
1012
|
+
];
|
|
1013
|
+
|
|
1014
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1015
|
+
{
|
|
1016
|
+
id: 'gpt-4',
|
|
1017
|
+
type: 'chat',
|
|
1018
|
+
enabled: true,
|
|
1019
|
+
abilities: { search: false }, // builtin abilities 会被 merge
|
|
1020
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
1021
|
+
},
|
|
1022
|
+
];
|
|
1023
|
+
|
|
1024
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1025
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1026
|
+
|
|
1027
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1028
|
+
|
|
1029
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1030
|
+
expect(merged).toBeDefined();
|
|
1031
|
+
// abilities.search 会被 merge 为 false,此处和 getEnabledAiModel 不同
|
|
1032
|
+
expect(merged?.abilities?.search).toEqual(false);
|
|
1033
|
+
// 删去 builtin settings
|
|
1034
|
+
expect(merged?.settings).toBeUndefined();
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it('should retain builtin settings when user model has no abilities (empty) and builtin has settings', async () => {
|
|
1038
|
+
const providerId = 'openai';
|
|
1039
|
+
|
|
1040
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1041
|
+
{
|
|
1042
|
+
id: 'gpt-4',
|
|
1043
|
+
type: 'chat',
|
|
1044
|
+
enabled: true,
|
|
1045
|
+
abilities: { vision: true }, // 启用 vision 能力, no search
|
|
1046
|
+
},
|
|
1047
|
+
];
|
|
1048
|
+
|
|
1049
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1050
|
+
{
|
|
1051
|
+
id: 'gpt-4',
|
|
1052
|
+
type: 'chat',
|
|
1053
|
+
enabled: true,
|
|
1054
|
+
abilities: { search: true }, // builtin abilities 会被 merge
|
|
1055
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin has settings
|
|
1056
|
+
},
|
|
1057
|
+
];
|
|
1058
|
+
|
|
1059
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1060
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1061
|
+
|
|
1062
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1063
|
+
|
|
1064
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1065
|
+
expect(merged).toBeDefined();
|
|
1066
|
+
// abilities.search 会被 merge 为 true,此处和 getEnabledAiModel 不同
|
|
1067
|
+
expect(merged?.abilities?.search).toEqual(true);
|
|
1068
|
+
// 保留 builtin settings
|
|
1069
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// 测试:用户模型无 abilities.search(undefined),保留 builtin settings(mergeArrayById 优先用户,但用户无则 builtin)
|
|
1073
|
+
it('should retain builtin settings when user model has no abilities.search (undefined) and builtin has settings', async () => {
|
|
1074
|
+
const providerId = 'openai';
|
|
1075
|
+
|
|
1076
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1077
|
+
{
|
|
1078
|
+
id: 'gpt-4',
|
|
1079
|
+
type: 'chat',
|
|
1080
|
+
enabled: true,
|
|
1081
|
+
abilities: {}, // 无 search
|
|
1082
|
+
},
|
|
1083
|
+
];
|
|
1084
|
+
|
|
1085
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1086
|
+
{
|
|
1087
|
+
id: 'gpt-4',
|
|
1088
|
+
type: 'chat',
|
|
1089
|
+
enabled: true,
|
|
1090
|
+
abilities: {},
|
|
1091
|
+
settings: { searchImpl: 'params', searchProvider: 'google' }, // builtin 有
|
|
1092
|
+
},
|
|
1093
|
+
];
|
|
1094
|
+
|
|
1095
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1096
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1097
|
+
|
|
1098
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1099
|
+
|
|
1100
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1101
|
+
expect(merged).toBeDefined();
|
|
1102
|
+
expect(merged?.abilities?.search).toBeUndefined();
|
|
1103
|
+
// 保留 builtin settings
|
|
1104
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params', searchProvider: 'google' });
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('should retain no settings when user model has no abilities.search (undefined) and builtin has no settings', async () => {
|
|
1108
|
+
const providerId = 'openai';
|
|
1109
|
+
|
|
1110
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1111
|
+
{
|
|
1112
|
+
id: 'gpt-4',
|
|
1113
|
+
type: 'chat',
|
|
1114
|
+
enabled: true,
|
|
1115
|
+
abilities: {}, // 无 search
|
|
1116
|
+
},
|
|
1117
|
+
];
|
|
1118
|
+
|
|
1119
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1120
|
+
{
|
|
1121
|
+
id: 'gpt-4',
|
|
1122
|
+
type: 'chat',
|
|
1123
|
+
enabled: true,
|
|
1124
|
+
// 无 settings
|
|
1125
|
+
},
|
|
1126
|
+
];
|
|
1127
|
+
|
|
1128
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1129
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1130
|
+
|
|
1131
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1132
|
+
|
|
1133
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1134
|
+
expect(merged).toBeDefined();
|
|
1135
|
+
expect(merged?.abilities?.search).toBeUndefined();
|
|
1136
|
+
// 无 settings
|
|
1137
|
+
expect(merged?.settings).toBeUndefined();
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// 测试:用户模型有 abilities.search: true
|
|
1141
|
+
it('should inject defaults when user has search: true, no existing settings (builtin none)', async () => {
|
|
1142
|
+
const providerId = 'openai';
|
|
1143
|
+
|
|
1144
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1145
|
+
{
|
|
1146
|
+
id: 'gpt-4',
|
|
1147
|
+
type: 'chat',
|
|
1148
|
+
enabled: true,
|
|
1149
|
+
abilities: { search: true }, // 用户启用
|
|
1150
|
+
},
|
|
1151
|
+
];
|
|
1152
|
+
|
|
1153
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1154
|
+
{
|
|
1155
|
+
id: 'gpt-4',
|
|
1156
|
+
type: 'chat',
|
|
1157
|
+
enabled: true,
|
|
1158
|
+
// 无 settings
|
|
1159
|
+
},
|
|
1160
|
+
];
|
|
1161
|
+
|
|
1162
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1163
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1164
|
+
|
|
1165
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1166
|
+
|
|
1167
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1168
|
+
expect(merged).toBeDefined();
|
|
1169
|
+
expect(merged?.abilities).toEqual({ search: true });
|
|
1170
|
+
// 注入 defaults
|
|
1171
|
+
expect(merged?.settings).toEqual({ searchImpl: 'params' });
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it('should retain existing settings when user has search: true and builtin has settings', async () => {
|
|
1175
|
+
const providerId = 'openai';
|
|
1176
|
+
|
|
1177
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1178
|
+
{
|
|
1179
|
+
id: 'gpt-4',
|
|
1180
|
+
type: 'chat',
|
|
1181
|
+
enabled: true,
|
|
1182
|
+
abilities: { search: true },
|
|
1183
|
+
},
|
|
1184
|
+
];
|
|
1185
|
+
|
|
1186
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1187
|
+
{
|
|
1188
|
+
id: 'gpt-4',
|
|
1189
|
+
type: 'chat',
|
|
1190
|
+
enabled: true,
|
|
1191
|
+
settings: { searchImpl: 'tool' },
|
|
1192
|
+
},
|
|
1193
|
+
];
|
|
1194
|
+
|
|
1195
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1196
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1197
|
+
|
|
1198
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1199
|
+
|
|
1200
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1201
|
+
expect(merged).toBeDefined();
|
|
1202
|
+
expect(merged?.abilities).toEqual({ search: true });
|
|
1203
|
+
// 使用 builtin settings
|
|
1204
|
+
expect(merged?.settings).toEqual({ searchImpl: 'tool' });
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// 测试:用户模型有 abilities.search: false
|
|
1208
|
+
it('should remove settings when user has search: false and builtin has settings', async () => {
|
|
1209
|
+
const providerId = 'openai';
|
|
1210
|
+
|
|
1211
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1212
|
+
{
|
|
1213
|
+
id: 'gpt-4',
|
|
1214
|
+
type: 'chat',
|
|
1215
|
+
enabled: true,
|
|
1216
|
+
abilities: { search: false }, // 用户禁用
|
|
1217
|
+
},
|
|
1218
|
+
];
|
|
1219
|
+
|
|
1220
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1221
|
+
{
|
|
1222
|
+
id: 'gpt-4',
|
|
1223
|
+
type: 'chat',
|
|
1224
|
+
enabled: true,
|
|
1225
|
+
settings: { searchImpl: 'tool', extendParams: [] },
|
|
1226
|
+
},
|
|
1227
|
+
];
|
|
1228
|
+
|
|
1229
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1230
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1231
|
+
|
|
1232
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1233
|
+
|
|
1234
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1235
|
+
expect(merged).toBeDefined();
|
|
1236
|
+
expect(merged?.abilities).toEqual({ search: false });
|
|
1237
|
+
// 移除 search 相关,保留其他
|
|
1238
|
+
expect(merged?.settings).toEqual({ extendParams: [] });
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it('should keep no settings when user has search: false and no existing settings', async () => {
|
|
1242
|
+
const providerId = 'openai';
|
|
1243
|
+
|
|
1244
|
+
const userModels: AiProviderModelListItem[] = [
|
|
1245
|
+
{
|
|
1246
|
+
id: 'gpt-4',
|
|
1247
|
+
type: 'chat',
|
|
1248
|
+
enabled: true,
|
|
1249
|
+
abilities: { search: false },
|
|
1250
|
+
},
|
|
1251
|
+
];
|
|
1252
|
+
|
|
1253
|
+
const builtinModels: AiProviderModelListItem[] = [
|
|
1254
|
+
{
|
|
1255
|
+
id: 'gpt-4',
|
|
1256
|
+
type: 'chat',
|
|
1257
|
+
enabled: true,
|
|
1258
|
+
// 无 settings
|
|
1259
|
+
},
|
|
1260
|
+
];
|
|
1261
|
+
|
|
1262
|
+
vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(userModels);
|
|
1263
|
+
vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(builtinModels);
|
|
1264
|
+
|
|
1265
|
+
const result = await repo.getAiProviderModelList(providerId);
|
|
1266
|
+
|
|
1267
|
+
const merged = result.find((m) => m.id === 'gpt-4');
|
|
1268
|
+
expect(merged).toBeDefined();
|
|
1269
|
+
expect(merged?.abilities).toEqual({ search: false });
|
|
1270
|
+
// 无 settings
|
|
1271
|
+
expect(merged?.settings).toBeUndefined();
|
|
1272
|
+
});
|
|
617
1273
|
});
|
|
618
1274
|
|
|
619
1275
|
describe('getAiProviderRuntimeState', () => {
|