Commit 6dc6a790 by luoqi

feat(ai-script): 接入通义千问 qwen3.7-max(DashScope 兼容端点)做话术生成 provider

- 装 @ai-sdk/openai-compatible,AiProviderService 加 qwen provider(DashScope compatible-mode 端点)。
- resolve 支持 qwen 前缀(qwen3.7-max / 裸键 qwen → 默认);config 加 qwenApiKey/qwenBaseUrl/qwenDefaultModel
  (QWEN_API_KEY 缺省回落 DASHSCOPE_API_KEY;默认模型 qwen3.7-max);priceTable 加 qwen3.7-max 估算价。
- 前端模型下拉加'通义千问 Max'(qwen3.7-max)。
- 注:该账号 key 仅 qwen3.7-max 可用(qwen-max/qwen3-max 等均 Model.AccessDenied,实测确认)。
验证(本地 standard 流):start→逐字partial→done,source=agent,¥0.031/4823tok,落库 succeeded。
- ️ 密钥走 .env(gitignore),服务器部署需单独在 .env 加 QWEN_API_KEY。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent a0afbd7e
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
"dependencies": { "dependencies": {
"@ai-sdk/deepseek": "^2.0.35", "@ai-sdk/deepseek": "^2.0.35",
"@ai-sdk/google": "^3.0.80", "@ai-sdk/google": "^3.0.80",
"@ai-sdk/openai-compatible": "^2.0.48",
"@bull-board/api": "^7.1.5", "@bull-board/api": "^7.1.5",
"@bull-board/express": "^7.1.5", "@bull-board/express": "^7.1.5",
"@bull-board/nestjs": "^7.1.5", "@bull-board/nestjs": "^7.1.5",
......
...@@ -29,6 +29,10 @@ export interface AppConfig { ...@@ -29,6 +29,10 @@ export interface AppConfig {
qwenOmniModel: string; qwenOmniModel: string;
/// Gemini Live 实时模型名(实时教练可选 provider) /// Gemini Live 实时模型名(实时教练可选 provider)
geminiLiveModel: string; geminiLiveModel: string;
/// Qwen(通义千问,DashScope OpenAI-兼容端点)— 话术生成可选 provider
qwenApiKey: string;
qwenBaseUrl: string;
qwenDefaultModel: string;
/// LLM 调用上限(秒),防卡死 /// LLM 调用上限(秒),防卡死
requestTimeoutSec: number; requestTimeoutSec: number;
/// 价格表(¥/M tokens)— 从 AI_PRICE_TABLE_JSON env 读;调价时改 env 重启即可 /// 价格表(¥/M tokens)— 从 AI_PRICE_TABLE_JSON env 读;调价时改 env 重启即可
...@@ -66,6 +70,9 @@ export function loadConfig(): AppConfig { ...@@ -66,6 +70,9 @@ export function loadConfig(): AppConfig {
dashscopeApiKey: process.env.DASHSCOPE_API_KEY ?? '', dashscopeApiKey: process.env.DASHSCOPE_API_KEY ?? '',
qwenOmniModel: process.env.QWEN_OMNI_MODEL ?? 'qwen3-omni-flash-realtime', qwenOmniModel: process.env.QWEN_OMNI_MODEL ?? 'qwen3-omni-flash-realtime',
geminiLiveModel: process.env.GEMINI_LIVE_MODEL ?? 'gemini-3.1-flash-live-preview', geminiLiveModel: process.env.GEMINI_LIVE_MODEL ?? 'gemini-3.1-flash-live-preview',
qwenApiKey: process.env.QWEN_API_KEY ?? process.env.DASHSCOPE_API_KEY ?? '',
qwenBaseUrl: process.env.QWEN_BASE_URL ?? 'https://dashscope.aliyuncs.com/compatible-mode/v1',
qwenDefaultModel: process.env.QWEN_DEFAULT_MODEL ?? 'qwen3.7-max',
requestTimeoutSec: Number(process.env.AI_REQUEST_TIMEOUT_SEC ?? 60), requestTimeoutSec: Number(process.env.AI_REQUEST_TIMEOUT_SEC ?? 60),
priceTable: parsePriceTable(process.env.AI_PRICE_TABLE_JSON), priceTable: parsePriceTable(process.env.AI_PRICE_TABLE_JSON),
}, },
...@@ -103,6 +110,8 @@ function parsePriceTable(raw: string | undefined): Record<string, { inHit: numbe ...@@ -103,6 +110,8 @@ function parsePriceTable(raw: string | undefined): Record<string, { inHit: numbe
// Gemini Flash 估算价(×7.2 汇率)— 实际调价改 AI_PRICE_TABLE_JSON env // Gemini Flash 估算价(×7.2 汇率)— 实际调价改 AI_PRICE_TABLE_JSON env
'gemini-3.5-flash': { inHit: 0.54, inMiss: 2.16, out: 18 }, 'gemini-3.5-flash': { inHit: 0.54, inMiss: 2.16, out: 18 },
'gemini-2.5-flash': { inHit: 0.54, inMiss: 2.16, out: 18 }, 'gemini-2.5-flash': { inHit: 0.54, inMiss: 2.16, out: 18 },
// Qwen Max(通义千问,DashScope)估算价(¥/M)— 实际调价改 AI_PRICE_TABLE_JSON env
'qwen3.7-max': { inHit: 0.6, inMiss: 2.4, out: 9.6 },
}; };
if (!raw) return DEFAULT; if (!raw) return DEFAULT;
try { try {
......
...@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; ...@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { createDeepSeek, type DeepSeekProvider } from '@ai-sdk/deepseek'; import { createDeepSeek, type DeepSeekProvider } from '@ai-sdk/deepseek';
import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from '@ai-sdk/google'; import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from '@ai-sdk/google';
import { createOpenAICompatible, type OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai'; import type { LanguageModel } from 'ai';
import type { AppConfig } from '../../../config/configuration'; import type { AppConfig } from '../../../config/configuration';
...@@ -23,13 +24,16 @@ export class AiProviderService { ...@@ -23,13 +24,16 @@ export class AiProviderService {
private readonly logger = new Logger(AiProviderService.name); private readonly logger = new Logger(AiProviderService.name);
private readonly deepseek: DeepSeekProvider; private readonly deepseek: DeepSeekProvider;
private readonly google: GoogleGenerativeAIProvider; private readonly google: GoogleGenerativeAIProvider;
private readonly qwen: OpenAICompatibleProvider;
private readonly defaultModel: string; private readonly defaultModel: string;
private readonly geminiDefaultModel: string; private readonly geminiDefaultModel: string;
private readonly qwenDefaultModel: string;
constructor(private readonly config: ConfigService<AppConfig, true>) { constructor(private readonly config: ConfigService<AppConfig, true>) {
const ai = this.config.get('ai', { infer: true }); const ai = this.config.get('ai', { infer: true });
this.defaultModel = ai.defaultModel; this.defaultModel = ai.defaultModel;
this.geminiDefaultModel = ai.geminiDefaultModel; this.geminiDefaultModel = ai.geminiDefaultModel;
this.qwenDefaultModel = ai.qwenDefaultModel;
if (!ai.deepseekApiKey) { if (!ai.deepseekApiKey) {
this.logger.warn( this.logger.warn(
...@@ -46,6 +50,16 @@ export class AiProviderService { ...@@ -46,6 +50,16 @@ export class AiProviderService {
// baseURL 留空 → SDK 默认 endpoint;设了走代理 / 镜像 // baseURL 留空 → SDK 默认 endpoint;设了走代理 / 镜像
...(ai.geminiBaseUrl ? { baseURL: ai.geminiBaseUrl } : {}), ...(ai.geminiBaseUrl ? { baseURL: ai.geminiBaseUrl } : {}),
}); });
if (!ai.qwenApiKey) {
this.logger.warn('QWEN_API_KEY(或 DASHSCOPE_API_KEY)未设置 — Qwen 调用会失败。到阿里云百炼 dashscope 申请');
}
// 通义千问走 DashScope OpenAI-兼容端点(compatible-mode);structured output 用 JSON 模式
this.qwen = createOpenAICompatible({
name: 'qwen',
apiKey: ai.qwenApiKey,
baseURL: ai.qwenBaseUrl,
});
} }
/** /**
...@@ -65,7 +79,12 @@ export class AiProviderService { ...@@ -65,7 +79,12 @@ export class AiProviderService {
const canonical = modelId === 'gemini' ? this.geminiDefaultModel : modelId; const canonical = modelId === 'gemini' ? this.geminiDefaultModel : modelId;
return { model: this.google(canonical), provider: 'gemini', modelId: canonical }; return { model: this.google(canonical), provider: 'gemini', modelId: canonical };
} }
throw new Error(`Unsupported model id: ${modelId}(支持 deepseek-* / gemini-* 前缀或裸键,后续扩 qwen-*)`); // qwen 前缀(含 qwen3.7-max / qwen-max / 裸键 qwen)→ DashScope 兼容端点
if (modelId.startsWith('qwen')) {
const canonical = modelId === 'qwen' ? this.qwenDefaultModel : modelId;
return { model: this.qwen(canonical), provider: 'qwen', modelId: canonical };
}
throw new Error(`Unsupported model id: ${modelId}(支持 deepseek-* / gemini-* / qwen* 前缀或裸键)`);
} }
getDefaultModelId(): string { getDefaultModelId(): string {
......
...@@ -67,7 +67,7 @@ import { CallWidget } from './call-widget'; ...@@ -67,7 +67,7 @@ import { CallWidget } from './call-widget';
import { submitExecution, adaptAbandonReasons } from './execution-api'; import { submitExecution, adaptAbandonReasons } from './execution-api';
/// 话术生成模型(具体型号,直传后端 AiProviderService.resolve) /// 话术生成模型(具体型号,直传后端 AiProviderService.resolve)
export type ScriptModel = 'deepseek-v4-pro' | 'deepseek-v4-flash' | 'gemini-3.5-flash'; export type ScriptModel = 'deepseek-v4-pro' | 'deepseek-v4-flash' | 'gemini-3.5-flash' | 'qwen3.7-max';
/// 投入档(话术生成档位):稳健=4段模板填空 / 标准=去模板自由编排 / 深度=多步多段+对抗校验。 /// 投入档(话术生成档位):稳健=4段模板填空 / 标准=去模板自由编排 / 深度=多步多段+对抗校验。
export type ScriptTier = 'stable' | 'standard' | 'deep'; export type ScriptTier = 'stable' | 'standard' | 'deep';
...@@ -1666,6 +1666,7 @@ const SCRIPT_MODELS: { key: ScriptModel; label: string }[] = [ ...@@ -1666,6 +1666,7 @@ const SCRIPT_MODELS: { key: ScriptModel; label: string }[] = [
{ key: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro' }, { key: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro' },
{ key: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' }, { key: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' },
{ key: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash' }, { key: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash' },
{ key: 'qwen3.7-max', label: '通义千问 Max' },
]; ];
// 投入档选项(跟模型并列,直传后端 tier) // 投入档选项(跟模型并列,直传后端 tier)
...@@ -1704,7 +1705,7 @@ function RegenBtn({ ...@@ -1704,7 +1705,7 @@ function RegenBtn({
'inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10.5px] border-r transition-colors', 'inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10.5px] border-r transition-colors',
streaming streaming
? 'text-slate-300 border-slate-100 cursor-not-allowed' ? 'text-slate-300 border-slate-100 cursor-not-allowed'
: 'text-slate-500 border-slate-200 hover:text-indigo-700 hover:bg-indigo-50', : 'text-slate-500 border-slate-200 hover:text-teal-700 hover:bg-teal-50',
); );
return ( return (
<div className="inline-flex flex-none items-stretch rounded border border-slate-200 overflow-hidden"> <div className="inline-flex flex-none items-stretch rounded border border-slate-200 overflow-hidden">
...@@ -1762,7 +1763,7 @@ function RegenBtn({ ...@@ -1762,7 +1763,7 @@ function RegenBtn({
'inline-flex items-center gap-1 px-2 py-0.5 text-[10.5px] font-medium transition-colors', 'inline-flex items-center gap-1 px-2 py-0.5 text-[10.5px] font-medium transition-colors',
streaming streaming
? 'text-rose-600 bg-rose-50 hover:bg-rose-100' ? 'text-rose-600 bg-rose-50 hover:bg-rose-100'
: 'text-indigo-600 hover:text-indigo-700 hover:bg-indigo-50', : 'text-teal-600 hover:text-teal-700 hover:bg-indigo-50',
)} )}
> >
{streaming ? ( {streaming ? (
......
...@@ -42,6 +42,9 @@ importers: ...@@ -42,6 +42,9 @@ importers:
'@ai-sdk/google': '@ai-sdk/google':
specifier: ^3.0.80 specifier: ^3.0.80
version: 3.0.80(zod@4.4.3) version: 3.0.80(zod@4.4.3)
'@ai-sdk/openai-compatible':
specifier: ^2.0.48
version: 2.0.48(zod@4.4.3)
'@bull-board/api': '@bull-board/api':
specifier: ^7.1.5 specifier: ^7.1.5
version: 7.1.5(@bull-board/ui@7.1.5) version: 7.1.5(@bull-board/ui@7.1.5)
...@@ -348,6 +351,12 @@ packages: ...@@ -348,6 +351,12 @@ packages:
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
'@ai-sdk/openai-compatible@2.0.48':
resolution: {integrity: sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/provider-utils@4.0.27': '@ai-sdk/provider-utils@4.0.27':
resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==} resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==}
engines: {node: '>=18'} engines: {node: '>=18'}
...@@ -5901,6 +5910,12 @@ snapshots: ...@@ -5901,6 +5910,12 @@ snapshots:
'@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3)
zod: 4.4.3 zod: 4.4.3
'@ai-sdk/openai-compatible@2.0.48(zod@4.4.3)':
dependencies:
'@ai-sdk/provider': 3.0.10
'@ai-sdk/provider-utils': 4.0.27(zod@4.4.3)
zod: 4.4.3
'@ai-sdk/provider-utils@4.0.27(zod@4.4.3)': '@ai-sdk/provider-utils@4.0.27(zod@4.4.3)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.10 '@ai-sdk/provider': 3.0.10
...@@ -9114,8 +9129,8 @@ snapshots: ...@@ -9114,8 +9129,8 @@ snapshots:
'@next/eslint-plugin-next': 16.2.4 '@next/eslint-plugin-next': 16.2.4
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
...@@ -9137,7 +9152,7 @@ snapshots: ...@@ -9137,7 +9152,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)): eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
'@nolyfill/is-core-module': 1.0.39 '@nolyfill/is-core-module': 1.0.39
debug: 4.4.3 debug: 4.4.3
...@@ -9148,22 +9163,22 @@ snapshots: ...@@ -9148,22 +9163,22 @@ snapshots:
tinyglobby: 0.2.16 tinyglobby: 0.2.16
unrs-resolver: 1.11.1 unrs-resolver: 1.11.1
optionalDependencies: optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
optionalDependencies: optionalDependencies:
'@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
'@rtsao/scc': 1.1.0 '@rtsao/scc': 1.1.0
array-includes: 3.1.9 array-includes: 3.1.9
...@@ -9174,7 +9189,7 @@ snapshots: ...@@ -9174,7 +9189,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
hasown: 2.0.3 hasown: 2.0.3
is-core-module: 2.16.2 is-core-module: 2.16.2
is-glob: 4.0.3 is-glob: 4.0.3
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment