feat(backend): replace custom aiClient with @bytelyst/llm platform package
- Import PerplexityProvider, OpenAIProvider, GeminiProvider from @bytelyst/llm - Use createFallbackChain() instead of manual axios fallback loop - Remove axios and @types/axios — no longer needed - Preserve AIClient class interface (generateAnalysis, getProviderHealth) — no changes required in apiServer.ts or AIAnalysisRule.ts - Fallback order still driven by config.AI.FALLBACK_LIST Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bcb6bf4d71
commit
c3651f5696
@ -53,11 +53,11 @@
|
||||
"@azure/cosmos": "^4.3.0",
|
||||
"@bytelyst/auth": "link:../../../learning_ai/learning_ai_common_plat/packages/auth",
|
||||
"@bytelyst/cosmos": "link:../../../learning_ai/learning_ai_common_plat/packages/cosmos",
|
||||
"@bytelyst/llm": "link:../../../learning_ai/learning_ai_common_plat/packages/llm",
|
||||
"@alpacahq/alpaca-trade-api": "^3.1.3",
|
||||
"@supabase/supabase-js": "^2.90.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"axios": "^1.13.2",
|
||||
"ccxt": "^4.5.31",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
@ -68,7 +68,6 @@
|
||||
"winston": "^3.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.14.4",
|
||||
"@types/node": "^25.0.3",
|
||||
"c8": "^10.1.3",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
createFallbackChain,
|
||||
GeminiProvider,
|
||||
OpenAIProvider,
|
||||
PerplexityProvider,
|
||||
type LLMProvider,
|
||||
} from '@bytelyst/llm';
|
||||
import { config } from '../config/index.js';
|
||||
import logger from '../utils/logger.js';
|
||||
|
||||
@ -14,224 +20,106 @@ export interface AIProviderHealth {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ProviderEntry {
|
||||
name: AIProvider;
|
||||
make: () => LLMProvider;
|
||||
defaultModel: string;
|
||||
}
|
||||
|
||||
function resolveModel(provider: AIProvider): string {
|
||||
switch (provider) {
|
||||
case 'perplexity':
|
||||
return config.AI.MODEL.includes('sonar') ? config.AI.MODEL : 'sonar';
|
||||
case 'openai':
|
||||
return config.AI.MODEL.includes('gpt') ? config.AI.MODEL : 'gpt-4o-mini';
|
||||
case 'gemini':
|
||||
return config.AI.MODEL.includes('gemini') ? config.AI.MODEL : 'gemini-1.5-flash';
|
||||
}
|
||||
}
|
||||
|
||||
const PROVIDER_REGISTRY: ProviderEntry[] = [
|
||||
{
|
||||
name: 'perplexity',
|
||||
make: () => new PerplexityProvider({ apiKey: config.AI.PERPLEXITY_API_KEY, model: resolveModel('perplexity') }),
|
||||
defaultModel: 'sonar',
|
||||
},
|
||||
{
|
||||
name: 'openai',
|
||||
make: () => new OpenAIProvider({ apiKey: config.AI.OPENAI_API_KEY, model: resolveModel('openai') }),
|
||||
defaultModel: 'gpt-4o-mini',
|
||||
},
|
||||
{
|
||||
name: 'gemini',
|
||||
make: () => new GeminiProvider({ apiKey: config.AI.GEMINI_API_KEY, model: resolveModel('gemini') }),
|
||||
defaultModel: 'gemini-1.5-flash',
|
||||
},
|
||||
];
|
||||
|
||||
export class AIClient {
|
||||
public async generateAnalysis(prompt: string): Promise<string | null> {
|
||||
const fallbackList = config.AI.FALLBACK_LIST;
|
||||
|
||||
for (const provider of fallbackList) {
|
||||
try {
|
||||
logger.info(`[AI] Attempting analysis with provider: ${provider}...`);
|
||||
let result: string | null = null;
|
||||
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
result = await this.callOpenAI(prompt);
|
||||
break;
|
||||
case 'perplexity':
|
||||
result = await this.callPerplexity(prompt);
|
||||
break;
|
||||
case 'gemini':
|
||||
result = await this.callGemini(prompt);
|
||||
break;
|
||||
default:
|
||||
logger.warn(`[AI] Unsupported provider in fallback list: ${provider}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
logger.info(`[AI] Successfully generated analysis using ${provider}.`);
|
||||
return result;
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(`[AI] Provider ${provider} failed: ${error.message}`);
|
||||
// Continue to next provider in fallback list
|
||||
}
|
||||
}
|
||||
|
||||
logger.error('[AI] All providers in fallback list failed or were not configured.');
|
||||
return null;
|
||||
public async generateAnalysis(prompt: string): Promise<string | null> {
|
||||
const ordered = this.buildOrderedProviders();
|
||||
const chain = createFallbackChain(ordered.map(e => e.make()));
|
||||
|
||||
if (!chain.isConfigured()) {
|
||||
logger.warn('[AI] No providers configured — skipping analysis');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await chain.chatCompletion({
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are an expert crypto trading assistant. Strictly output JSON.' },
|
||||
{ role: 'user', content: prompt },
|
||||
],
|
||||
temperature: 0.2,
|
||||
});
|
||||
logger.info('[AI] Analysis generated successfully');
|
||||
return result.content;
|
||||
} catch (error: any) {
|
||||
logger.error(`[AI] All providers failed: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getProviderHealth(probe: boolean = false): Promise<AIProviderHealth[]> {
|
||||
const fallbackList = config.AI.FALLBACK_LIST;
|
||||
const providers: AIProvider[] = ['openai', 'perplexity', 'gemini'];
|
||||
|
||||
const results: AIProviderHealth[] = [];
|
||||
for (const provider of providers) {
|
||||
const configured = this.isProviderConfigured(provider);
|
||||
const model = this.resolveModel(provider);
|
||||
const fallbackIndex = fallbackList.indexOf(provider);
|
||||
|
||||
for (const entry of PROVIDER_REGISTRY) {
|
||||
const provider = entry.make();
|
||||
const configured = provider.isConfigured();
|
||||
const fallbackIndex = fallbackList.indexOf(entry.name);
|
||||
const inFallbackList = fallbackIndex >= 0;
|
||||
const model = resolveModel(entry.name);
|
||||
|
||||
if (!configured) {
|
||||
results.push({
|
||||
provider,
|
||||
configured: false,
|
||||
model,
|
||||
inFallbackList,
|
||||
fallbackIndex: inFallbackList ? fallbackIndex : null,
|
||||
status: 'missing_key',
|
||||
message: 'API key missing'
|
||||
});
|
||||
results.push({ provider: entry.name, configured: false, model, inFallbackList, fallbackIndex: inFallbackList ? fallbackIndex : null, status: 'missing_key', message: 'API key missing' });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!probe) {
|
||||
results.push({
|
||||
provider,
|
||||
configured: true,
|
||||
model,
|
||||
inFallbackList,
|
||||
fallbackIndex: inFallbackList ? fallbackIndex : null,
|
||||
status: 'configured',
|
||||
message: 'Configured (probe skipped)'
|
||||
});
|
||||
results.push({ provider: entry.name, configured: true, model, inFallbackList, fallbackIndex: inFallbackList ? fallbackIndex : null, status: 'configured', message: 'Configured (probe skipped)' });
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.probeProvider(provider);
|
||||
results.push({
|
||||
provider,
|
||||
configured: true,
|
||||
model,
|
||||
inFallbackList,
|
||||
fallbackIndex: inFallbackList ? fallbackIndex : null,
|
||||
status: 'ok',
|
||||
message: 'Provider probe succeeded'
|
||||
await provider.chatCompletion({
|
||||
messages: [{ role: 'user', content: 'Return JSON: {"action":"HOLD","confidence":0,"reasoning":"probe"}' }],
|
||||
temperature: 0,
|
||||
});
|
||||
results.push({ provider: entry.name, configured: true, model, inFallbackList, fallbackIndex: inFallbackList ? fallbackIndex : null, status: 'ok', message: 'Provider probe succeeded' });
|
||||
} catch (error: any) {
|
||||
results.push({
|
||||
provider,
|
||||
configured: true,
|
||||
model,
|
||||
inFallbackList,
|
||||
fallbackIndex: inFallbackList ? fallbackIndex : null,
|
||||
status: 'error',
|
||||
message: error?.message || 'Provider probe failed'
|
||||
});
|
||||
results.push({ provider: entry.name, configured: true, model, inFallbackList, fallbackIndex: inFallbackList ? fallbackIndex : null, status: 'error', message: error?.message || 'Provider probe failed' });
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private isProviderConfigured(provider: AIProvider): boolean {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
return !!config.AI.OPENAI_API_KEY;
|
||||
case 'perplexity':
|
||||
return !!config.AI.PERPLEXITY_API_KEY;
|
||||
case 'gemini':
|
||||
return !!config.AI.GEMINI_API_KEY;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
private buildOrderedProviders(): ProviderEntry[] {
|
||||
const fallbackList = config.AI.FALLBACK_LIST;
|
||||
return fallbackList
|
||||
.map(name => PROVIDER_REGISTRY.find(e => e.name === name))
|
||||
.filter((e): e is ProviderEntry => e !== undefined);
|
||||
}
|
||||
|
||||
private resolveModel(provider: AIProvider): string {
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
return config.AI.MODEL.includes('gpt') ? config.AI.MODEL : 'gpt-4o-mini';
|
||||
case 'perplexity':
|
||||
return config.AI.MODEL.includes('sonar') ? config.AI.MODEL : 'sonar';
|
||||
case 'gemini':
|
||||
return config.AI.MODEL.includes('gemini') ? config.AI.MODEL : 'gemini-1.5-flash';
|
||||
default:
|
||||
return config.AI.MODEL;
|
||||
}
|
||||
}
|
||||
|
||||
private async probeProvider(provider: AIProvider): Promise<void> {
|
||||
const probePrompt = 'Return JSON: {"action":"HOLD","confidence":0,"reasoning":"probe"}';
|
||||
let response: string | null = null;
|
||||
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
response = await this.callOpenAI(probePrompt);
|
||||
break;
|
||||
case 'perplexity':
|
||||
response = await this.callPerplexity(probePrompt);
|
||||
break;
|
||||
case 'gemini':
|
||||
response = await this.callGemini(probePrompt);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`);
|
||||
}
|
||||
|
||||
if (!response || !String(response).trim()) {
|
||||
throw new Error('Empty response from provider');
|
||||
}
|
||||
}
|
||||
|
||||
private async callOpenAI(prompt: string): Promise<string | null> {
|
||||
const apiKey = config.AI.OPENAI_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
|
||||
const model = config.AI.MODEL.includes('gpt') ? config.AI.MODEL : 'gpt-4o-mini';
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.openai.com/v1/chat/completions',
|
||||
{
|
||||
model: model,
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are an expert crypto trading assistant. strictly output JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0.2
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000
|
||||
}
|
||||
);
|
||||
return response.data.choices[0].message.content;
|
||||
}
|
||||
|
||||
private async callPerplexity(prompt: string): Promise<string | null> {
|
||||
const apiKey = config.AI.PERPLEXITY_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
|
||||
const model = config.AI.MODEL.includes('sonar') ? config.AI.MODEL : 'sonar';
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.perplexity.ai/chat/completions',
|
||||
{
|
||||
model: model,
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are an expert crypto trading assistant. Strictly output JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0.2
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000
|
||||
}
|
||||
);
|
||||
return response.data.choices[0].message.content;
|
||||
}
|
||||
|
||||
private async callGemini(prompt: string): Promise<string | null> {
|
||||
const apiKey = config.AI.GEMINI_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
|
||||
const model = config.AI.MODEL.includes('gemini') ? config.AI.MODEL : 'gemini-1.5-flash';
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
||||
|
||||
const response = await axios.post(url, {
|
||||
contents: [{
|
||||
parts: [{ text: `You are an expert crypto trading assistant. Strictly output JSON.\n\n${prompt}` }]
|
||||
}]
|
||||
}, { timeout: 10000 });
|
||||
|
||||
return response.data.candidates[0].content.parts[0].text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
pnpm-lock.yaml
generated
67
pnpm-lock.yaml
generated
@ -39,6 +39,9 @@ importers:
|
||||
'@bytelyst/cosmos':
|
||||
specifier: link:../../../learning_ai/learning_ai_common_plat/packages/cosmos
|
||||
version: link:../../../learning_ai/learning_ai_common_plat/packages/cosmos
|
||||
'@bytelyst/llm':
|
||||
specifier: link:../../../learning_ai/learning_ai_common_plat/packages/llm
|
||||
version: link:../../../learning_ai/learning_ai_common_plat/packages/llm
|
||||
'@supabase/supabase-js':
|
||||
specifier: ^2.90.1
|
||||
version: 2.101.1
|
||||
@ -48,9 +51,6 @@ importers:
|
||||
'@types/express':
|
||||
specifier: ^5.0.6
|
||||
version: 5.0.6
|
||||
axios:
|
||||
specifier: ^1.13.2
|
||||
version: 1.14.0
|
||||
ccxt:
|
||||
specifier: ^4.5.31
|
||||
version: 4.5.46
|
||||
@ -76,9 +76,6 @@ importers:
|
||||
specifier: ^3.19.0
|
||||
version: 3.19.0
|
||||
devDependencies:
|
||||
'@types/axios':
|
||||
specifier: ^0.14.4
|
||||
version: 0.14.4
|
||||
'@types/node':
|
||||
specifier: ^25.0.3
|
||||
version: 25.5.2
|
||||
@ -2168,10 +2165,6 @@ packages:
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
'@types/axios@0.14.4':
|
||||
resolution: {integrity: sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g==}
|
||||
deprecated: This is a stub types definition. axios provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@ -2684,9 +2677,6 @@ packages:
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
autoprefixer@10.4.27:
|
||||
resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@ -2701,9 +2691,6 @@ packages:
|
||||
axios@0.21.4:
|
||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||
|
||||
axios@1.14.0:
|
||||
resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==}
|
||||
|
||||
babel-jest@29.7.0:
|
||||
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@ -2988,10 +2975,6 @@ packages:
|
||||
resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@12.1.0:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -3207,10 +3190,6 @@ packages:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -3850,10 +3829,6 @@ packages:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -5105,10 +5080,6 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -8596,12 +8567,6 @@ snapshots:
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/axios@0.14.4':
|
||||
dependencies:
|
||||
axios: 1.14.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.2
|
||||
@ -9171,8 +9136,6 @@ snapshots:
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autoprefixer@10.4.27(postcss@8.5.8):
|
||||
dependencies:
|
||||
browserslist: 4.28.2
|
||||
@ -9192,14 +9155,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
axios@1.14.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
babel-jest@29.7.0(@babel/core@7.29.0):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@ -9565,10 +9520,6 @@ snapshots:
|
||||
color-convert: 3.1.3
|
||||
color-string: 2.1.4
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
@ -9777,8 +9728,6 @@ snapshots:
|
||||
has-property-descriptors: 1.0.2
|
||||
object-keys: 1.1.1
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
@ -10667,14 +10616,6 @@ snapshots:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
@ -12135,8 +12076,6 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qrcode-terminal@0.11.0: {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user