PageSourceSearch

https://hhdcz04-python2.hf.space/app/utils.js

js hhdcz04-python2.hf.space collected 2026-10-03 16:55:56 UTC 23,812 bytes, 661 lines download raw bytes

1// 工具函数
2import { t, getCurrentLanguage } from './i18n.js';
3import { apiClient } from './auth.js';
4
5/**
6 * 获取所有支持的提供商配置列表
7 * @param {string[]} supportedProviders - 已注册的提供商类型列表
8 * @returns {Object[]} 提供商配置对象数组
9 */
10/**
11 * 获取所有基础提供商配置(母版)
12 * @returns {Object[]} 基础提供商配置数组
13 */
14function getBaseProviderConfigs() {
15    return [
16        { 
17            id: 'forward-api', 
18            name: 'NewAPI', 
19            icon: 'fa-share-square'
20        },
21        { 
22            id: 'gemini-cli-oauth', 
23            name: t('dashboard.routing.nodeName.gemini'), 
24            icon: 'fa-robot',
25            defaultPath: 'configs/gemini/'
26        },
27        { 
28            id: 'gemini-antigravity', 
29            name: t('dashboard.routing.nodeName.antigravity'), 
30            icon: 'fa-rocket',
31            defaultPath: 'configs/antigravity/'
32        },
33        { 
34            id: 'claude-kiro-oauth', 
35            name: t('dashboard.routing.nodeName.kiro'), 
36            icon: 'fa-key',
37            defaultPath: 'configs/kiro/'
38        },
39        { 
40            id: 'openai-codex-oauth', 
41            name: t('dashboard.routing.nodeName.codex'), 
42            icon: 'fa-code',
43            defaultPath: 'configs/codex/'
44        },
45        {
46            id: 'grok-cli-oauth',
47            name: t('dashboard.routing.nodeName.grokCli'),
48            icon: 'fa-terminal',
49            defaultPath: 'configs/grok-cli/'
50        },
51        { 
52            id: 'openai-qwen-oauth', 
53            name: t('dashboard.routing.nodeName.qwen'), 
54            icon: 'fa-cloud',
55            defaultPath: 'configs/qwen/'
56        },
57        { 
58            id: 'openai-iflow', 
59            name: t('dashboard.routing.nodeName.iflow'), 
60            icon: 'fa-stream',
61            defaultPath: 'configs/iflow/'
62        },
63        { 
64            id: 'grok-web', 
65            name: t('dashboard.routing.nodeName.grok'), 
66            icon: 'fa-user-secret'
67        },
68        { 
69            id: 'openai-custom', 
70            name: t('dashboard.routing.nodeName.openai'), 
71            icon: 'fa-microchip'
72        },
73        { 
74            id: 'claude-custom', 
75            name: t('dashboard.routing.nodeName.claude'), 
76            icon: 'fa-brain'
77        },
78        { 
79            id: 'openaiResponses-custom', 
80            name: 'OpenAI Responses', 
81            icon: 'fa-reply-all'
82        },
83        { 
84            id: 'atlascloud', 
85            name: 'AtlasCloud', 
86            icon: 'fa-cloud'
87        },
88    ];
89}
90
91/**
92 * 获取所有支持的提供商配置列表
93 * @param {string[]} supportedProviders - 已注册的提供商类型列表
94 * @returns {Object[]} 提供商配置对象数组
95 */
96function getProviderConfigs(supportedProviders = []) {
97    const baseConfigs = getBaseProviderConfigs();
98
99    const result = [];
100    const usedIds = new Set();
101
102    // 1. 遍历基础配置,使其按照定义顺序排列,并且让带后缀的自定义提供商紧跟在它的基础母版后面
103    baseConfigs.forEach(baseConfig => {
104        const isSupported = supportedProviders.includes(baseConfig.id);
105        result.push({ ...baseConfig, visible: isSupported });
106        usedIds.add(baseConfig.id);
107
108        // 紧接着寻找匹配该基础 ID 的带后缀的自定义类型(例如 openai-custom-test)
109        supportedProviders.forEach(providerId => {
110            if (usedIds.has(providerId)) return;
111
112            if (providerId.startsWith(baseConfig.id + '-')) {
113                const suffix = providerId.substring(baseConfig.id.length + 1);
114                result.push({
115                    ...baseConfig,
116                    id: providerId,
117                    name: `${baseConfig.name} (${suffix})`,
118                    visible: true
119                });
120                usedIds.add(providerId);
121            }
122        });
123    });
124
125    // 2. 安全兜底:如果还有一些 supportedProviders 既不匹配任何基础 ID 也不匹配任何前缀,就追加到最后
126    supportedProviders.forEach(providerId => {
127        if (!usedIds.has(providerId)) {
128            result.push({
129                id: providerId,
130                name: providerId,
131                icon: 'fa-server',
132                visible: true
133            });
134            usedIds.add(providerId);
135        }
136    });
137
138    return result;
139}
140
141/**
142 * 格式化运行时间
143 * @param {number} seconds - 秒数
144 * @returns {string} 格式化的时间字符串
145 */
146function formatUptime(seconds) {
147    const days = Math.floor(seconds / 86400);
148    const hours = Math.floor((seconds % 86400) / 3600);
149    const minutes = Math.floor((seconds % 3600) / 60);
150    const secs = Math.floor(seconds % 60);
151    
152    if (getCurrentLanguage() === 'en-US') {
153        return `${days}d ${hours}h ${minutes}m ${secs}s`;
154    }
155    return `${days}天 ${hours}小时 ${minutes}分 ${secs}秒`;
156}
157
158/**
159 * HTML转义
160 * @param {string} text - 要转义的文本
161 * @returns {string} 转义后的文本
162 */
163function escapeHtml(text) {
164    const div = document.createElement('div');
165    div.textContent = text;
166    return div.innerHTML;
167}
168
169/**
170 * 显示提示消息
171 * @param {string} title - 提示标题 (可选,旧接口为 message)
172 * @param {string} message - 提示消息
173 * @param {string} type - 消息类型 (info, success, error)
174 */
175function showToast(title, message, type = 'info') {
176    // 兼容旧接口 (message, type)
177    if (arguments.length === 2 && (message === 'success' || message === 'error' || message === 'info' || message === 'warning')) {
178        type = message;
179        message = title;
180        title = t(`common.${type}`);
181    }
182
183    const toast = document.createElement('div');
184    toast.className = `toast ${type}`;
185    toast.innerHTML = `
186        <div style="font-weight: 600; margin-bottom: 4px;">${escapeHtml(title)}</div>
187        <div>${escapeHtml(message)}</div>
188    `;
189
190    // 获取toast容器
191    const toastContainer = document.getElementById('toastContainer') || document.querySelector('.toast-container');
192    if (toastContainer) {
193        toastContainer.appendChild(toast);
194
195        setTimeout(() => {
196            toast.remove();
197        }, 3000);
198    }
199}
200
201/**
202 * 获取字段显示文案
203 * @param {string} key - 字段键
204 * @returns {string} 显示文案
205 */
206function getFieldLabel(key) {
207    const labelMap = {
208        'customName': t('modal.provider.customName') + ' ' + t('config.optional'),
209        'checkModelName': t('modal.provider.checkModelName') + ' ' + t('config.optional'),
210        'checkHealth': t('modal.provider.healthCheckLabel'),
211        'concurrencyLimit': t('modal.provider.concurrencyLimit') + ' ' + t('config.optional'),
212        'queueLimit': t('modal.provider.queueLimit') + ' ' + t('config.optional'),
213        'OPENAI_API_KEY': 'OpenAI API Key',
214        'OPENAI_BASE_URL': 'OpenAI Base URL',
215        'CLAUDE_API_KEY': 'Claude API Key',
216        'CLAUDE_BASE_URL': 'Claude Base URL',
217        'PROJECT_ID': t('modal.provider.field.projectId'),
218        'GEMINI_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
219        'KIRO_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
220        'QWEN_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
221        'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
222        'IFLOW_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
223        'CODEX_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
224        'GROK_CLI_OAUTH_CREDS_FILE_PATH': t('modal.provider.field.oauthPath'),
225        'GROK_COOKIE_TOKEN': t('modal.provider.field.ssoToken'),
226        'GROK_CF_CLEARANCE': t('modal.provider.field.cfClearance'),
227        'GROK_CF_BM': t('modal.provider.field.cfBm'),
228
229        'GROK_USER_AGENT': t('modal.provider.field.userAgent'),
230        'GEMINI_BASE_URL': 'Gemini Base URL',
231        'KIRO_BASE_URL': t('modal.provider.field.baseUrl'),
232        'KIRO_REFRESH_URL': t('modal.provider.field.refreshUrl'),
233        'KIRO_REFRESH_IDC_URL': t('modal.provider.field.refreshIdcUrl'),
234        'QWEN_BASE_URL': 'Qwen Base URL',
235        'QWEN_OAUTH_BASE_URL': t('modal.provider.field.oauthBaseUrl'),
236        'ANTIGRAVITY_BASE_URL_DAILY': t('modal.provider.field.dailyBaseUrl'),
237        'ANTIGRAVITY_BASE_URL_AUTOPUSH': t('modal.provider.field.autopushBaseUrl'),
238        'IFLOW_BASE_URL': t('modal.provider.field.iflowBaseUrl'),
239        'CODEX_BASE_URL': t('modal.provider.field.codexBaseUrl'),
240        'GROK_BASE_URL': t('modal.provider.field.grokBaseUrl'),
241        'FORWARD_API_KEY': 'Forward API Key',
242        'FORWARD_BASE_URL': 'Forward Base URL',
243        'FORWARD_HEADER_NAME': t('modal.provider.field.headerName'),
244        'FORWARD_HEADER_VALUE_PREFIX': t('modal.provider.field.headerPrefix'),
245        'USE_SYSTEM_PROXY_FORWARD': t('modal.provider.field.useSystemProxy')
246    };
247    
248    return labelMap[key] || key;
249}
250
251/**
252 * 获取提供商类型的字段配置
253 * @param {string} providerType - 提供商类型
254 * @returns {Array} 字段配置数组
255 */
256function getProviderTypeFields(providerType) {
257    // 基础配置字段定义
258    const fieldConfigs = {
259        'openai-custom': [
260            {
261                id: 'OPENAI_API_KEY',
262                label: t('modal.provider.field.apiKey'),
263                type: 'password',
264                placeholder: 'sk-...'
265            },
266            {
267                id: 'OPENAI_BASE_URL',
268                label: 'OpenAI Base URL',
269                type: 'text',
270                placeholder: 'https://api.openai.com/v1'
271            }
272        ],
273        'atlascloud': [
274            {
275                id: 'OPENAI_API_KEY',
276                label: t('modal.provider.field.apiKey'),
277                type: 'password',
278                placeholder: 'sk-...'
279            },
280            {
281                id: 'OPENAI_BASE_URL',
282                label: 'OpenAI Base URL',
283                type: 'text',
284                placeholder: 'https://api.openai.com/v1'
285            }
286        ],
287        'openaiResponses-custom': [
288            {
289                id: 'OPENAI_API_KEY',
290                label: t('modal.provider.field.apiKey'),
291                type: 'password',
292                placeholder: 'sk-...'
293            },
294            {
295                id: 'OPENAI_BASE_URL',
296                label: 'OpenAI Base URL',
297                type: 'text',
298                placeholder: 'https://api.openai.com/v1'
299            }
300        ],
301        'claude-custom': [
302            {
303                id: 'CLAUDE_API_KEY',
304                label: 'Claude API Key',
305                type: 'password',
306                placeholder: 'sk-ant-...'
307            },
308            {
309                id: 'CLAUDE_BASE_URL',
310                label: 'Claude Base URL',
311                type: 'text',
312                placeholder: 'https://api.anthropic.com'
313            }
314        ],
315        'gemini-cli-oauth': [
316            {
317                id: 'PROJECT_ID',
318                label: t('modal.provider.field.projectId'),
319                type: 'text',
320                placeholder: t('modal.provider.field.projectId.placeholder')
321            },
322            {
323                id: 'GEMINI_OAUTH_CREDS_FILE_PATH',
324                label: t('modal.provider.field.oauthPath'),
325                type: 'text',
326                placeholder: t('modal.provider.field.oauthPath.gemini.placeholder')
327            },
328            {
329                id: 'GEMINI_BASE_URL',
330                label: `Gemini Base URL <span class="optional-tag">${t('config.optional')}</span>`,
331                type: 'text',
332                placeholder: 'https://cloudcode-pa.googleapis.com'
333            }
334        ],
335        'claude-kiro-oauth': [
336            {
337                id: 'KIRO_OAUTH_CREDS_FILE_PATH',
338                label: t('modal.provider.field.oauthPath'),
339                type: 'text',
340                placeholder: t('modal.provider.field.oauthPath.kiro.placeholder')
341            },
342            {
343                id: 'KIRO_BASE_URL',
344                label: `${t('modal.provider.field.baseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
345                type: 'text',
346                placeholder: 'https://q.{{region}}.amazonaws.com/generateAssistantResponse'
347            },
348            {
349                id: 'KIRO_REFRESH_URL',
350                label: `${t('modal.provider.field.refreshUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
351                type: 'text',
352                placeholder: 'https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken'
353            },
354            {
355                id: 'KIRO_REFRESH_IDC_URL',
356                label: `${t('modal.provider.field.refreshIdcUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
357                type: 'text',
358                placeholder: 'https://oidc.{{region}}.amazonaws.com/token'
359            }
360        ],
361        'openai-qwen-oauth': [
362            {
363                id: 'QWEN_OAUTH_CREDS_FILE_PATH',
364                label: t('modal.provider.field.oauthPath'),
365                type: 'text',
366                placeholder: t('modal.provider.field.oauthPath.qwen.placeholder')
367            },
368            {
369                id: 'QWEN_BASE_URL',
370                label: `Qwen Base URL <span class="optional-tag">${t('config.optional')}</span>`,
371                type: 'text',
372                placeholder: 'https://portal.qwen.ai/v1'
373            },
374            {
375                id: 'QWEN_OAUTH_BASE_URL',
376                label: `${t('modal.provider.field.oauthBaseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
377                type: 'text',
378                placeholder: 'https://chat.qwen.ai'
379            }
380        ],
381        'gemini-antigravity': [
382            {
383                id: 'PROJECT_ID',
384                label: `${t('modal.provider.field.projectId')} <span class="optional-tag">${t('config.optional')}</span>`,
385                type: 'text',
386                placeholder: t('modal.provider.field.projectId.optional.placeholder')
387            },
388            {
389                id: 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH',
390                label: t('modal.provider.field.oauthPath'),
391                type: 'text',
392                placeholder: t('modal.provider.field.oauthPath.antigravity.placeholder')
393            },
394            {
395                id: 'ANTIGRAVITY_BASE_URL_DAILY',
396                label: `${t('modal.provider.field.dailyBaseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
397                type: 'text',
398                placeholder: 'https://daily-cloudcode-pa.sandbox.googleapis.com'
399            },
400            {
401                id: 'ANTIGRAVITY_BASE_URL_AUTOPUSH',
402                label: `${t('modal.provider.field.autopushBaseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
403                type: 'text',
404                placeholder: 'https://autopush-cloudcode-pa.sandbox.googleapis.com'
405            }
406        ],
407        'openai-iflow': [
408            {
409                id: 'IFLOW_OAUTH_CREDS_FILE_PATH',
410                label: t('modal.provider.field.oauthPath'),
411                type: 'text',
412                placeholder: t('modal.provider.field.oauthPath.iflow.placeholder')
413            },
414            {
415                id: 'IFLOW_BASE_URL',
416                label: `iFlow Base URL <span class="optional-tag">${t('config.optional')}</span>`,
417                type: 'text',
418                placeholder: 'https://iflow.cn/api'
419            }
420        ],
421        'openai-codex-oauth': [
422            {
423                id: 'CODEX_OAUTH_CREDS_FILE_PATH',
424                label: t('modal.provider.field.oauthPath'),
425                type: 'text',
426                placeholder: t('modal.provider.field.oauthPath.codex.placeholder')
427            },
428            {
429                id: 'CODEX_EMAIL',
430                label: `${t('modal.provider.field.email')} <span class="optional-tag">${t('config.optional')}</span>`,
431                type: 'email',
432                placeholder: t('modal.provider.field.email.placeholder')
433            },
434            {
435                id: 'CODEX_BASE_URL',
436                label: `${t('modal.provider.field.codexBaseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
437                type: 'text',
438                placeholder: 'https://api.openai.com/v1/codex'
439            }
440        ],
441        'grok-cli-oauth': [
442            {
443                id: 'GROK_CLI_OAUTH_CREDS_FILE_PATH',
444                label: t('modal.provider.field.oauthPath'),
445                type: 'text',
446                placeholder: 'configs/grok-cli/..._xai-..._oauth_creds.json'
447            },
448            {
449                id: 'GROK_CLI_EMAIL',
450                label: `${t('modal.provider.field.email')} <span class="optional-tag">${t('config.optional')}</span>`,
451                type: 'email',
452                placeholder: t('modal.provider.field.email.placeholder')
453            },
454            {
455                id: 'GROK_CLI_BASE_URL',
456                label: `xAI Base URL <span class="optional-tag">${t('config.optional')}</span>`,
457                type: 'text',
458                placeholder: 'https://api.x.ai/v1'
459            }
460        ],
461        'grok-web': [
462            {
463                id: 'GROK_COOKIE_TOKEN',
464                label: t('modal.provider.field.ssoToken'),
465                type: 'password',
466                placeholder: 'sso cookie token'
467            },
468            {
469                id: 'GROK_BASE_URL',
470                label: `${t('modal.provider.field.grokBaseUrl')} <span class="optional-tag">${t('config.optional')}</span>`,
471                type: 'text',
472                placeholder: 'https://grok.com'
473            },
474            {
475                id: 'GROK_CF_CLEARANCE',
476                label: `${t('modal.provider.field.cfClearance')} <span class="optional-tag">${t('config.optional')}</span>`,
477                type: 'text',
478                placeholder: 'cf_clearance cookie value'
479            },
480            {
481                id: 'GROK_CF_BM',
482                label: `${t('modal.provider.field.cfBm')} <span class="optional-tag">${t('config.optional')}</span>`,
483                type: 'text',
484                placeholder: '__cf_bm cookie value'
485            },
486            {
487                id: 'GROK_USER_AGENT',
488                label: `${t('modal.provider.field.userAgent')} <span class="optional-tag">${t('config.optional')}</span>`,
489                type: 'text',
490                placeholder: 'Mozilla/5.0 ...'
491            },
492            {
493                id: 'GROK_STATSIG_ID',
494                label: `${t('modal.provider.field.statsigId')} <span class="optional-tag">${t('config.optional')}</span>`,
495                type: 'text',
496                placeholder: 'x-statsig-id header value'
497            }
498        ],
499        'forward-api': [
500            {
501                id: 'FORWARD_API_KEY',
502                label: t('modal.provider.field.apiKey'),
503                type: 'password',
504                placeholder: t('modal.provider.field.apiKey.placeholder')
505            },
506            {
507                id: 'FORWARD_BASE_URL',
508                label: t('modal.provider.field.baseUrl'),
509                type: 'text',
510                placeholder: 'https://api.example.com'
511            },
512            {
513                id: 'FORWARD_HEADER_NAME',
514                label: `${t('modal.provider.field.headerName')} <span class="optional-tag">${t('config.optional')}</span>`,
515                type: 'text',
516                placeholder: 'Authorization'
517            },
518            {
519                id: 'FORWARD_HEADER_VALUE_PREFIX',
520                label: `${t('modal.provider.field.headerPrefix')} <span class="optional-tag">${t('config.optional')}</span>`,
521                type: 'text',
522                placeholder: 'Bearer '
523            }
524        ]
525    };
526
527    // 1. 尝试精确匹配
528    if (fieldConfigs[providerType]) {
529        return fieldConfigs[providerType];
530    }
531
532    // 2. 尝试匹配前缀 (例如 openai-custom-test -> openai-custom)
533    for (const baseType in fieldConfigs) {
534        if (providerType.startsWith(baseType + '-')) {
535            return fieldConfigs[baseType];
536        }
537    }
538
539    return [];
540}
541
542/**
543 * 调试函数:获取当前提供商统计信息
544 * @param {Object} providerStats - 提供商统计对象
545 * @returns {Object} 扩展的统计信息
546 */
547function getProviderStats(providerStats) {
548    return {
549        ...providerStats,
550        // 添加计算得出的统计信息
551        successRate: providerStats.totalRequests > 0 ? 
552            ((providerStats.totalRequests - providerStats.totalErrors) / providerStats.totalRequests * 100).toFixed(2) + '%' : '0%',
553        avgUsagePerProvider: providerStats.activeProviders > 0 ? 
554            Math.round(providerStats.totalRequests / providerStats.activeProviders) : 0,
555        healthRatio: providerStats.totalAccounts > 0 ? 
556            (providerStats.healthyProviders / providerStats.totalAccounts * 100).toFixed(2) + '%' : '0%'
557    };
558}
559
560/**
561 * 通用 API 请求函数
562 * @param {string} url - API 端点 URL
563 * @param {Object} options - fetch 选项
564 * @returns {Promise<any>} 响应数据
565 */
566async function apiRequest(url, options = {}) {
567    // 如果 URL 以 /api 开头,去掉它(因为 apiClient.request 会自动添加)
568    const endpoint = url.startsWith('/api') ? url.slice(4) : url;
569    return apiClient.request(endpoint, options);
570}
571
572/**
573 * 复制文本到剪贴板(带兼容性回退)
574 * @param {string} text - 要复制的文本
575 * @returns {Promise<boolean>} 是否成功
576 */
577async function copyToClipboard(text) {
578    if (navigator.clipboard && navigator.clipboard.writeText) {
579        try {
580            await navigator.clipboard.writeText(text);
581            return true;
582        } catch (err) {
583            console.warn('navigator.clipboard failed, trying fallback:', err);
584        }
585    }
586
587    // Fallback: 使用 textarea 模拟复制
588    try {
589        const textArea = document.createElement('textarea');
590        textArea.value = text;
591        textArea.style.position = 'fixed';
592        textArea.style.left = '-9999px';
593        textArea.style.top = '0';
594        document.body.appendChild(textArea);
595        textArea.focus();
596        textArea.select();
597        const successful = document.execCommand('copy');
598        document.body.removeChild(textArea);
599        return successful;
600    } catch (err) {
601        console.error('Fallback copy failed:', err);
602        return false;
603    }
604}
605
606/**
607 * 只为元素绑定一次事件
608 * @param {HTMLElement|null} element - 需要绑定事件的元素
609 * @param {string} eventName - 事件名称
610 * @param {Function} handler - 事件处理函数
611 * @param {string} key - 绑定标识,避免同一元素重复绑定同一类事件
612 * @returns {boolean} 是否完成了本次绑定
613 */
614function bindOnce(element, eventName, handler, key = eventName) {
615    if (!element) {
616        return false;
617    }
618
619    if (!markOnce(element, key)) {
620        return false;
621    }
622
623    element.addEventListener(eventName, handler);
624    return true;
625}
626
627/**
628 * 为元素记录一次性初始化状态
629 * @param {HTMLElement|null} element - 需要标记的元素
630 * @param {string} key - 标识名称
631 * @returns {boolean} 是否是首次标记
632 */
633function markOnce(element, key) {
634    if (!element) {
635        return false;
636    }
637
638    const boundKey = `bound${key.charAt(0).toUpperCase()}${key.slice(1)}`;
639    if (element.dataset[boundKey]) {
640        return false;
641    }
642
643    element.dataset[boundKey] = 'true';
644    return true;
645}
646
647// 导出所有工具函数
648export {
649    formatUptime,
650    escapeHtml,
651    showToast,
652    getFieldLabel,
653    getProviderTypeFields,
654    getProviderConfigs,
655    getBaseProviderConfigs,
656    getProviderStats,
657    apiRequest,
658    copyToClipboard,
659    bindOnce,
660    markOnce
661};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.