xshat-lite 1.0.0

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.
@@ -0,0 +1,66 @@
1
+ import { listProviders } from '../utils/config.mjs';
2
+
3
+ const DEFAULT_HEALTH_PENALTIES = {
4
+ deepseek: 8,
5
+ gemini: 1,
6
+ duckai: 3,
7
+ deepai: 4,
8
+ 'api-deepseek': 0,
9
+ 'api-openai': 0
10
+ };
11
+
12
+ export function getWebProviders() {
13
+ return listProviders().filter((provider) => provider.transport !== 'api');
14
+ }
15
+
16
+ export function getApiProviders() {
17
+ return listProviders().filter((provider) => provider.transport === 'api');
18
+ }
19
+
20
+ export function getProviderPool({
21
+ mode = 'web-first',
22
+ currentProviderId = null,
23
+ providerHealth = {}
24
+ } = {}) {
25
+ const webProviders = getWebProviders();
26
+ const apiProviders = getApiProviders();
27
+
28
+ const ordered = mode === 'api-first'
29
+ ? [...apiProviders, ...webProviders]
30
+ : mode === 'web-only'
31
+ ? [...webProviders]
32
+ : [...webProviders, ...apiProviders];
33
+ const scored = [...ordered].sort((left, right) => {
34
+ const leftPenalty = Number(providerHealth[left.id] ?? DEFAULT_HEALTH_PENALTIES[left.id] ?? 0);
35
+ const rightPenalty = Number(providerHealth[right.id] ?? DEFAULT_HEALTH_PENALTIES[right.id] ?? 0);
36
+
37
+ if (leftPenalty !== rightPenalty) {
38
+ return leftPenalty - rightPenalty;
39
+ }
40
+
41
+ return ordered.findIndex((item) => item.id === left.id) - ordered.findIndex((item) => item.id === right.id);
42
+ });
43
+
44
+ if (!currentProviderId) {
45
+ return scored;
46
+ }
47
+
48
+ const currentIndex = scored.findIndex((provider) => provider.id === currentProviderId);
49
+ if (currentIndex < 0) {
50
+ return scored;
51
+ }
52
+
53
+ return [
54
+ ...scored.slice(currentIndex + 1),
55
+ ...scored.slice(0, currentIndex + 1)
56
+ ];
57
+ }
58
+
59
+ export function getFallbackProvider({
60
+ currentProviderId,
61
+ mode = 'web-first',
62
+ providerHealth = {}
63
+ } = {}) {
64
+ const pool = getProviderPool({ mode, currentProviderId, providerHealth });
65
+ return pool.find((provider) => provider.id !== currentProviderId) || null;
66
+ }
@@ -0,0 +1,26 @@
1
+ import ApiChatManager from './api-chat.mjs';
2
+ import ChatManager from './chat.mjs';
3
+
4
+ export function isApiProvider(provider) {
5
+ return provider?.transport === 'api';
6
+ }
7
+
8
+ export function requiresBrowserForProvider(provider) {
9
+ return !isApiProvider(provider);
10
+ }
11
+
12
+ export function getProviderSessionUrl(provider, browserManager = null) {
13
+ if (isApiProvider(provider)) {
14
+ return `api://${provider?.id || 'unknown'}`;
15
+ }
16
+
17
+ return browserManager?.getCurrentUrl?.() || provider?.url || '';
18
+ }
19
+
20
+ export function createProviderChatManager(provider, browserManager) {
21
+ if (isApiProvider(provider)) {
22
+ return new ApiChatManager(provider);
23
+ }
24
+
25
+ return new ChatManager(browserManager.getPage(), provider);
26
+ }
@@ -0,0 +1,102 @@
1
+ function includesAny(text = '', patterns = []) {
2
+ const value = String(text || '').toLowerCase();
3
+ return patterns.some((pattern) => value.includes(String(pattern).toLowerCase()));
4
+ }
5
+
6
+ function compact(text = '') {
7
+ return String(text ?? '')
8
+ .replace(/\s+/g, ' ')
9
+ .trim();
10
+ }
11
+
12
+ export function getProviderStrategy(providerId = '') {
13
+ const id = String(providerId || '').trim().toLowerCase();
14
+
15
+ if (id === 'gemini') {
16
+ return {
17
+ response: {
18
+ interruptedRetryLimit: 4,
19
+ transientTextMarkers: [
20
+ '正在搜索网络',
21
+ '搜索网络',
22
+ '搜索网页',
23
+ '正在搜索网页',
24
+ 'searching the web',
25
+ 'searching for webpages'
26
+ ],
27
+ stopTextMarkers: [
28
+ '你已让系统停止这条回答',
29
+ '你已停止这条回答',
30
+ 'you stopped this response',
31
+ 'response was stopped'
32
+ ]
33
+ }
34
+ };
35
+ }
36
+
37
+ if (id === 'deepseek') {
38
+ return {
39
+ response: {
40
+ interruptedRetryLimit: 5,
41
+ transientTextMarkers: [
42
+ '思考中',
43
+ '正在思考',
44
+ '联网搜索中',
45
+ '正在搜索',
46
+ 'searching',
47
+ 'thinking'
48
+ ],
49
+ stopTextMarkers: [
50
+ '你已让系统停止这条回答',
51
+ '你已停止这条回答',
52
+ 'stopped generating',
53
+ 'response was stopped'
54
+ ]
55
+ }
56
+ };
57
+ }
58
+
59
+ if (id === 'duckai') {
60
+ return {
61
+ response: {
62
+ interruptedRetryLimit: 2,
63
+ transientTextMarkers: [
64
+ '正在生成回复',
65
+ 'generating a response'
66
+ ],
67
+ stopTextMarkers: [
68
+ 'response was stopped',
69
+ 'you stopped this response'
70
+ ]
71
+ }
72
+ };
73
+ }
74
+
75
+ return {
76
+ response: {
77
+ interruptedRetryLimit: 3,
78
+ transientTextMarkers: [],
79
+ stopTextMarkers: [
80
+ '你已让系统停止这条回答',
81
+ '你已停止这条回答',
82
+ 'you stopped this response',
83
+ 'response was stopped',
84
+ 'stopped generating'
85
+ ]
86
+ }
87
+ };
88
+ }
89
+
90
+ export function isProviderTransientText(providerId, text = '') {
91
+ const strategy = getProviderStrategy(providerId);
92
+ return includesAny(compact(text), strategy.response.transientTextMarkers);
93
+ }
94
+
95
+ export function isProviderInterruptedText(providerId, text = '') {
96
+ const strategy = getProviderStrategy(providerId);
97
+ return includesAny(compact(text), strategy.response.stopTextMarkers);
98
+ }
99
+
100
+ export function getProviderInterruptedRetryLimit(providerId) {
101
+ return getProviderStrategy(providerId).response.interruptedRetryLimit || 3;
102
+ }