@zhin.js/scaffold-wizard 0.1.6 → 0.1.7
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/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +2 -1
- package/lib/adapter.js.map +1 -1
- package/lib/ai.d.ts +6 -0
- package/lib/ai.d.ts.map +1 -1
- package/lib/ai.js +64 -34
- package/lib/ai.js.map +1 -1
- package/lib/apply.js +2 -2
- package/lib/apply.js.map +1 -1
- package/lib/index.d.ts +6 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/optional-peers.d.ts.map +1 -1
- package/lib/optional-peers.js +6 -5
- package/lib/optional-peers.js.map +1 -1
- package/lib/project-config-plan.d.ts +53 -0
- package/lib/project-config-plan.d.ts.map +1 -0
- package/lib/project-config-plan.js +337 -0
- package/lib/project-config-plan.js.map +1 -0
- package/lib/project-deps.d.ts +26 -3
- package/lib/project-deps.d.ts.map +1 -1
- package/lib/project-deps.js +211 -26
- package/lib/project-deps.js.map +1 -1
- package/lib/upgrade-l4.d.ts.map +1 -1
- package/lib/upgrade-l4.js +2 -2
- package/lib/upgrade-l4.js.map +1 -1
- package/lib/zhin-stack-deps.d.ts +66 -0
- package/lib/zhin-stack-deps.d.ts.map +1 -0
- package/lib/zhin-stack-deps.js +202 -0
- package/lib/zhin-stack-deps.js.map +1 -0
- package/package.json +7 -5
- package/src/adapter.ts +2 -1
- package/src/ai.ts +76 -32
- package/src/apply.ts +2 -2
- package/src/index.ts +37 -0
- package/src/optional-peers.ts +6 -6
- package/src/project-config-plan.ts +400 -0
- package/src/project-deps.ts +228 -27
- package/src/stack-versions.generated.json +48 -0
- package/src/upgrade-l4.ts +2 -1
- package/src/zhin-stack-deps.ts +252 -0
package/src/project-deps.ts
CHANGED
|
@@ -4,22 +4,62 @@ import path from 'node:path';
|
|
|
4
4
|
import type { AISetupConfig } from './ai.js';
|
|
5
5
|
import type { InitOptions } from './types.js';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* User project dependency policy: scaffolded dependencies intentionally float to latest.
|
|
9
|
+
*/
|
|
10
|
+
export const AI_STACK_VERSIONS = {
|
|
11
|
+
'@zhin.js/agent': 'latest',
|
|
12
|
+
zod: 'latest',
|
|
13
|
+
ai: 'latest',
|
|
14
|
+
'@modelcontextprotocol/sdk': 'latest',
|
|
15
|
+
'@ai-sdk/openai': 'latest',
|
|
16
|
+
'@ai-sdk/anthropic': 'latest',
|
|
17
|
+
'@ai-sdk/google': 'latest',
|
|
18
|
+
'@ai-sdk/deepseek': 'latest',
|
|
19
|
+
'@ai-sdk/openai-compatible': 'latest',
|
|
20
|
+
} as const;
|
|
9
21
|
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
22
|
+
/** @deprecated 使用 AI_STACK_VERSIONS['@modelcontextprotocol/sdk'] */
|
|
23
|
+
export const MCP_SDK_VERSION = AI_STACK_VERSIONS['@modelcontextprotocol/sdk'];
|
|
24
|
+
|
|
25
|
+
const WIZARD_ALIAS_TO_SDK: Record<string, string> = {
|
|
26
|
+
openai: 'openai',
|
|
27
|
+
anthropic: 'anthropic',
|
|
28
|
+
deepseek: 'deepseek',
|
|
29
|
+
google: 'google',
|
|
30
|
+
gemini: 'google',
|
|
31
|
+
ollama: 'ollama',
|
|
32
|
+
moonshot: 'openai-compatible',
|
|
33
|
+
zhipu: 'openai-compatible',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const SDK_TO_NPM_PACKAGE: Record<string, keyof typeof AI_STACK_VERSIONS> = {
|
|
37
|
+
openai: '@ai-sdk/openai',
|
|
38
|
+
anthropic: '@ai-sdk/anthropic',
|
|
39
|
+
google: '@ai-sdk/google',
|
|
40
|
+
deepseek: '@ai-sdk/deepseek',
|
|
41
|
+
ollama: '@ai-sdk/openai-compatible',
|
|
42
|
+
'openai-compatible': '@ai-sdk/openai-compatible',
|
|
43
|
+
};
|
|
13
44
|
|
|
14
45
|
const PROVIDER_SDK_PACKAGES: Record<string, { pkg: string; version: string }> = {
|
|
15
|
-
openai: { pkg: '@ai-sdk/openai', version: '
|
|
16
|
-
anthropic: { pkg: '@ai-sdk/anthropic', version: '
|
|
17
|
-
google: { pkg: '@ai-sdk/google', version: '
|
|
18
|
-
gemini: { pkg: '@ai-sdk/google', version: '
|
|
19
|
-
deepseek: { pkg: '@ai-sdk/deepseek', version: '
|
|
20
|
-
ollama: { pkg: '@ai-sdk/openai-compatible', version: '
|
|
21
|
-
moonshot: { pkg: '@ai-sdk/openai-compatible', version: '
|
|
22
|
-
zhipu: { pkg: '@ai-sdk/openai-compatible', version: '
|
|
46
|
+
openai: { pkg: '@ai-sdk/openai', version: AI_STACK_VERSIONS['@ai-sdk/openai'] },
|
|
47
|
+
anthropic: { pkg: '@ai-sdk/anthropic', version: AI_STACK_VERSIONS['@ai-sdk/anthropic'] },
|
|
48
|
+
google: { pkg: '@ai-sdk/google', version: AI_STACK_VERSIONS['@ai-sdk/google'] },
|
|
49
|
+
gemini: { pkg: '@ai-sdk/google', version: AI_STACK_VERSIONS['@ai-sdk/google'] },
|
|
50
|
+
deepseek: { pkg: '@ai-sdk/deepseek', version: AI_STACK_VERSIONS['@ai-sdk/deepseek'] },
|
|
51
|
+
ollama: { pkg: '@ai-sdk/openai-compatible', version: AI_STACK_VERSIONS['@ai-sdk/openai-compatible'] },
|
|
52
|
+
moonshot: { pkg: '@ai-sdk/openai-compatible', version: AI_STACK_VERSIONS['@ai-sdk/openai-compatible'] },
|
|
53
|
+
zhipu: { pkg: '@ai-sdk/openai-compatible', version: AI_STACK_VERSIONS['@ai-sdk/openai-compatible'] },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** @ai-sdk/* 主版本 ≥ threshold 时需 ai@7 */
|
|
57
|
+
const AI7_REQUIRED_SDK_MAJOR: Record<string, number> = {
|
|
58
|
+
'@ai-sdk/openai-compatible': 3,
|
|
59
|
+
'@ai-sdk/openai': 4,
|
|
60
|
+
'@ai-sdk/anthropic': 4,
|
|
61
|
+
'@ai-sdk/google': 4,
|
|
62
|
+
'@ai-sdk/deepseek': 3,
|
|
23
63
|
};
|
|
24
64
|
|
|
25
65
|
function providerSdkPackage(provider?: string): { pkg: string; version: string } | undefined {
|
|
@@ -27,6 +67,147 @@ function providerSdkPackage(provider?: string): { pkg: string; version: string }
|
|
|
27
67
|
return PROVIDER_SDK_PACKAGES[provider];
|
|
28
68
|
}
|
|
29
69
|
|
|
70
|
+
function inferSdkId(providerAlias: string, entry?: { sdk?: string }): string | undefined {
|
|
71
|
+
const explicit = entry?.sdk?.trim();
|
|
72
|
+
if (explicit) return explicit;
|
|
73
|
+
if (WIZARD_ALIAS_TO_SDK[providerAlias]) return WIZARD_ALIAS_TO_SDK[providerAlias];
|
|
74
|
+
if (SDK_TO_NPM_PACKAGE[providerAlias]) return providerAlias;
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function packagesForSdkIds(sdkIds: Iterable<string>): Record<string, string> {
|
|
79
|
+
const deps: Record<string, string> = {};
|
|
80
|
+
for (const sdkId of sdkIds) {
|
|
81
|
+
const npmName = SDK_TO_NPM_PACKAGE[sdkId];
|
|
82
|
+
if (!npmName) continue;
|
|
83
|
+
deps[npmName] = AI_STACK_VERSIONS[npmName];
|
|
84
|
+
}
|
|
85
|
+
return deps;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function collectSdkPackagesFromSetup(ai: AISetupConfig): Record<string, string> {
|
|
89
|
+
const sdkIds = new Set<string>();
|
|
90
|
+
const agentProvider = ai.agentProvider ?? ai.defaultProvider;
|
|
91
|
+
if (agentProvider) {
|
|
92
|
+
const id = inferSdkId(agentProvider, ai.providers?.[agentProvider] as { sdk?: string });
|
|
93
|
+
if (id) sdkIds.add(id);
|
|
94
|
+
}
|
|
95
|
+
for (const [alias, entry] of Object.entries(ai.providers ?? {})) {
|
|
96
|
+
const id = inferSdkId(alias, entry as { sdk?: string });
|
|
97
|
+
if (id) sdkIds.add(id);
|
|
98
|
+
}
|
|
99
|
+
return packagesForSdkIds(sdkIds);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function collectSdkPackagesFromConfig(config: { ai?: unknown }): Record<string, string> {
|
|
103
|
+
const ai = config.ai;
|
|
104
|
+
if (!ai || typeof ai !== 'object' || Array.isArray(ai)) return {};
|
|
105
|
+
const rec = ai as Record<string, unknown>;
|
|
106
|
+
const providers = rec.providers as Record<string, { sdk?: string }> | undefined;
|
|
107
|
+
const sdkIds = new Set<string>();
|
|
108
|
+
|
|
109
|
+
if (providers) {
|
|
110
|
+
for (const [alias, entry] of Object.entries(providers)) {
|
|
111
|
+
const id = inferSdkId(alias, entry);
|
|
112
|
+
if (id) sdkIds.add(id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const agents = rec.agents as Record<string, { provider?: string }> | undefined;
|
|
117
|
+
if (agents && providers) {
|
|
118
|
+
for (const agent of Object.values(agents)) {
|
|
119
|
+
if (!agent?.provider) continue;
|
|
120
|
+
const id = inferSdkId(agent.provider, providers[agent.provider]);
|
|
121
|
+
if (id) sdkIds.add(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (sdkIds.size === 0) {
|
|
126
|
+
const alias = resolveDefaultProviderFromConfig(config);
|
|
127
|
+
if (alias) {
|
|
128
|
+
const id = inferSdkId(alias, providers?.[alias]);
|
|
129
|
+
if (id) sdkIds.add(id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return packagesForSdkIds(sdkIds);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseMajor(versionSpec: string | undefined): number | undefined {
|
|
137
|
+
if (!versionSpec?.trim()) return undefined;
|
|
138
|
+
const match = versionSpec.trim().replace(/^[\^~>=<]+/, '').match(/^(\d+)/);
|
|
139
|
+
return match ? Number(match[1]) : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getDeclaredPackageVersion(pkg: PackageJsonLike, name: string): string | undefined {
|
|
143
|
+
return pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function readInstalledMajor(cwd: string, packageName: string): number | undefined {
|
|
147
|
+
const pkgJson = path.join(cwd, 'package.json');
|
|
148
|
+
if (!fs.existsSync(pkgJson)) return undefined;
|
|
149
|
+
try {
|
|
150
|
+
const req = createRequire(pkgJson);
|
|
151
|
+
const installed = req(`${packageName}/package.json`) as { version?: string };
|
|
152
|
+
return parseMajor(installed.version);
|
|
153
|
+
} catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function resolvePackageMajor(cwd: string, pkg: PackageJsonLike, packageName: string): number | undefined {
|
|
159
|
+
return readInstalledMajor(cwd, packageName) ?? parseMajor(getDeclaredPackageVersion(pkg, packageName));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface AiStackIncompatibility {
|
|
163
|
+
package: string;
|
|
164
|
+
reason: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function findOutdatedAiStackInPackageJson(
|
|
168
|
+
pkg: PackageJsonLike,
|
|
169
|
+
required: Record<string, string>,
|
|
170
|
+
): string[] {
|
|
171
|
+
const outdated: string[] = [];
|
|
172
|
+
for (const [name, minSpec] of Object.entries(required)) {
|
|
173
|
+
const declared = getDeclaredPackageVersion(pkg, name);
|
|
174
|
+
if (!declared) continue;
|
|
175
|
+
const declaredMajor = parseMajor(declared);
|
|
176
|
+
const minMajor = parseMajor(minSpec);
|
|
177
|
+
if (declaredMajor != null && minMajor != null && declaredMajor < minMajor) {
|
|
178
|
+
outdated.push(name);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return outdated;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function findInstalledAiStackIncompatibilities(
|
|
185
|
+
cwd: string,
|
|
186
|
+
pkg: PackageJsonLike,
|
|
187
|
+
): AiStackIncompatibility[] {
|
|
188
|
+
const aiMajor = resolvePackageMajor(cwd, pkg, 'ai');
|
|
189
|
+
if (aiMajor == null || aiMajor >= 7) return [];
|
|
190
|
+
|
|
191
|
+
const issues: AiStackIncompatibility[] = [];
|
|
192
|
+
if (aiMajor < 7) {
|
|
193
|
+
issues.push({
|
|
194
|
+
package: 'ai',
|
|
195
|
+
reason: 'ai@6 与 @ai-sdk/openai-compatible@3(v4 model spec)不兼容,请升级 ai 至 ^7.0.0',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const [name, threshold] of Object.entries(AI7_REQUIRED_SDK_MAJOR)) {
|
|
200
|
+
const major = resolvePackageMajor(cwd, pkg, name);
|
|
201
|
+
if (major == null || major < threshold) continue;
|
|
202
|
+
issues.push({
|
|
203
|
+
package: name,
|
|
204
|
+
reason: `${name}@${major} 需要 ai@7(当前 ai@${aiMajor})`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return issues;
|
|
209
|
+
}
|
|
210
|
+
|
|
30
211
|
/** 启用 AI 时将写入 package.json 的依赖名(用于向导提示与 CLI 输出) */
|
|
31
212
|
export function listAIDependencyNames(provider?: string, options?: { includeMcp?: boolean }): string[] {
|
|
32
213
|
const names = ['@zhin.js/agent', 'zod', 'ai'];
|
|
@@ -99,10 +280,14 @@ export function getRequiredAIDependenciesForConfig(config: { ai?: unknown }): Re
|
|
|
99
280
|
const ai = config.ai as Record<string, unknown>;
|
|
100
281
|
const setup: AISetupConfig = {
|
|
101
282
|
enabled: true,
|
|
102
|
-
|
|
283
|
+
agentProvider: resolveDefaultProviderFromConfig(config),
|
|
284
|
+
providers: ai.providers as AISetupConfig['providers'],
|
|
103
285
|
memoryMcp: aiConfigNeedsMcp(ai),
|
|
104
286
|
};
|
|
105
|
-
const deps =
|
|
287
|
+
const deps = {
|
|
288
|
+
...getAIDependencies(setup),
|
|
289
|
+
...collectSdkPackagesFromConfig(config),
|
|
290
|
+
};
|
|
106
291
|
if (!aiConfigNeedsMcp(ai)) {
|
|
107
292
|
delete deps['@modelcontextprotocol/sdk'];
|
|
108
293
|
}
|
|
@@ -140,9 +325,25 @@ export interface AIDependencyDiagnosis {
|
|
|
140
325
|
provider?: string;
|
|
141
326
|
required: Record<string, string>;
|
|
142
327
|
missingFromPackageJson: string[];
|
|
328
|
+
outdatedInPackageJson: string[];
|
|
329
|
+
incompatibleInstalled: AiStackIncompatibility[];
|
|
143
330
|
notInstalled: string[];
|
|
144
331
|
}
|
|
145
332
|
|
|
333
|
+
export function packagesNeedingAiStackFix(diagnosis: AIDependencyDiagnosis): string[] {
|
|
334
|
+
const names = new Set<string>([
|
|
335
|
+
...diagnosis.missingFromPackageJson,
|
|
336
|
+
...diagnosis.outdatedInPackageJson,
|
|
337
|
+
]);
|
|
338
|
+
for (const issue of diagnosis.incompatibleInstalled) {
|
|
339
|
+
names.add(issue.package);
|
|
340
|
+
}
|
|
341
|
+
if (diagnosis.incompatibleInstalled.some((issue) => issue.package !== 'ai')) {
|
|
342
|
+
names.add('ai');
|
|
343
|
+
}
|
|
344
|
+
return [...names];
|
|
345
|
+
}
|
|
346
|
+
|
|
146
347
|
export function diagnoseAIDependencies(
|
|
147
348
|
cwd: string,
|
|
148
349
|
config: { ai?: unknown },
|
|
@@ -153,6 +354,8 @@ export function diagnoseAIDependencies(
|
|
|
153
354
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
154
355
|
const packageJson = pkg ?? (fs.existsSync(packageJsonPath) ? fs.readJsonSync(packageJsonPath) as PackageJsonLike : {});
|
|
155
356
|
const missingFromPackageJson = findMissingPackageDependencies(packageJson, required);
|
|
357
|
+
const outdatedInPackageJson = findOutdatedAiStackInPackageJson(packageJson, required);
|
|
358
|
+
const incompatibleInstalled = findInstalledAiStackIncompatibilities(cwd, packageJson);
|
|
156
359
|
const declared = Object.keys(required).filter((name) => !missingFromPackageJson.includes(name));
|
|
157
360
|
const notInstalled = findUnresolvedPackageInstalls(cwd, declared);
|
|
158
361
|
return {
|
|
@@ -160,13 +363,15 @@ export function diagnoseAIDependencies(
|
|
|
160
363
|
provider: resolveDefaultProviderFromConfig(config),
|
|
161
364
|
required,
|
|
162
365
|
missingFromPackageJson,
|
|
366
|
+
outdatedInPackageJson,
|
|
367
|
+
incompatibleInstalled,
|
|
163
368
|
notInstalled,
|
|
164
369
|
};
|
|
165
370
|
}
|
|
166
371
|
|
|
167
|
-
export function formatAIDependencyFixCommand(
|
|
168
|
-
if (
|
|
169
|
-
const specs =
|
|
372
|
+
export function formatAIDependencyFixCommand(packages: string[], required: Record<string, string>): string {
|
|
373
|
+
if (packages.length === 0) return 'pnpm install';
|
|
374
|
+
const specs = packages.map((name) => `${name}@${required[name] ?? 'latest'}`);
|
|
170
375
|
return `pnpm add ${specs.join(' ')}`;
|
|
171
376
|
}
|
|
172
377
|
|
|
@@ -177,17 +382,13 @@ export function getAIDependencies(ai?: AISetupConfig): Record<string, string> {
|
|
|
177
382
|
if (!ai?.enabled) return {};
|
|
178
383
|
|
|
179
384
|
const deps: Record<string, string> = {
|
|
180
|
-
'@zhin.js/agent':
|
|
181
|
-
zod:
|
|
182
|
-
ai:
|
|
183
|
-
'@modelcontextprotocol/sdk':
|
|
385
|
+
'@zhin.js/agent': AI_STACK_VERSIONS['@zhin.js/agent'],
|
|
386
|
+
zod: AI_STACK_VERSIONS.zod,
|
|
387
|
+
ai: AI_STACK_VERSIONS.ai,
|
|
388
|
+
'@modelcontextprotocol/sdk': AI_STACK_VERSIONS['@modelcontextprotocol/sdk'],
|
|
389
|
+
...collectSdkPackagesFromSetup(ai),
|
|
184
390
|
};
|
|
185
391
|
|
|
186
|
-
const sdk = providerSdkPackage(ai.defaultProvider);
|
|
187
|
-
if (sdk) {
|
|
188
|
-
deps[sdk.pkg] = sdk.version;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
392
|
return deps;
|
|
192
393
|
}
|
|
193
394
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Auto-generated by scripts/sync-scaffold-deps.mjs — do not edit; run pnpm sync:scaffold-deps",
|
|
3
|
+
"zhinStack": {
|
|
4
|
+
"zhin.js": "^4.1.0",
|
|
5
|
+
"@zhin.js/cli": "^1.0.91",
|
|
6
|
+
"@zhin.js/agent": "^1.0.1",
|
|
7
|
+
"@zhin.js/database": "^1.0.74",
|
|
8
|
+
"@zhin.js/host-router": "^2.0.1",
|
|
9
|
+
"@zhin.js/host-api": "^2.0.3",
|
|
10
|
+
"@zhin.js/client": "^2.0.3",
|
|
11
|
+
"@zhin.js/contract": "^1.0.1",
|
|
12
|
+
"@zhin.js/satori": "^1.0.15",
|
|
13
|
+
"@zhin.js/speech": "^1.0.1",
|
|
14
|
+
"@zhin.js/html-renderer": "^1.0.1",
|
|
15
|
+
"@zhin.js/mcp": "^4.0.0",
|
|
16
|
+
"@zhin.js/adapter-sandbox": "^5.0.3",
|
|
17
|
+
"@zhin.js/adapter-dingtalk": "^4.0.0",
|
|
18
|
+
"@zhin.js/adapter-discord": "^5.0.0",
|
|
19
|
+
"@zhin.js/adapter-email": "^3.0.0",
|
|
20
|
+
"@zhin.js/adapter-github": "^3.0.0",
|
|
21
|
+
"@zhin.js/adapter-icqq": "^6.0.0",
|
|
22
|
+
"@zhin.js/adapter-kook": "^5.0.0",
|
|
23
|
+
"@zhin.js/adapter-lark": "^4.0.0",
|
|
24
|
+
"@zhin.js/adapter-line": "^2.0.0",
|
|
25
|
+
"@zhin.js/adapter-milky": "^3.0.0",
|
|
26
|
+
"@zhin.js/adapter-napcat": "^3.0.0",
|
|
27
|
+
"@zhin.js/adapter-onebot11": "^4.0.0",
|
|
28
|
+
"@zhin.js/adapter-onebot12": "^3.0.0",
|
|
29
|
+
"@zhin.js/adapter-qq": "^5.0.0",
|
|
30
|
+
"@zhin.js/adapter-satori": "^3.0.0",
|
|
31
|
+
"@zhin.js/adapter-slack": "^4.0.0",
|
|
32
|
+
"@zhin.js/adapter-telegram": "^5.0.0",
|
|
33
|
+
"@zhin.js/adapter-wechat-mp": "^3.0.0",
|
|
34
|
+
"@zhin.js/adapter-wecom": "^2.0.0",
|
|
35
|
+
"@zhin.js/adapter-weixin-ilink": "^3.0.0"
|
|
36
|
+
},
|
|
37
|
+
"aiStack": {
|
|
38
|
+
"@zhin.js/agent": "^1.0.1",
|
|
39
|
+
"ai": "^7.0.0",
|
|
40
|
+
"zod": "^4.0.0",
|
|
41
|
+
"@ai-sdk/openai": "^4.0.0",
|
|
42
|
+
"@ai-sdk/anthropic": "^4.0.0",
|
|
43
|
+
"@ai-sdk/google": "^4.0.0",
|
|
44
|
+
"@ai-sdk/deepseek": "^3.0.0",
|
|
45
|
+
"@ai-sdk/openai-compatible": "^3.0.0",
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/upgrade-l4.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isAiEnabledInConfig,
|
|
9
9
|
findMissingPackageDependencies,
|
|
10
10
|
findUnresolvedPackageInstalls,
|
|
11
|
+
packagesNeedingAiStackFix,
|
|
11
12
|
} from './project-deps.js';
|
|
12
13
|
import { diagnoseOptionalPeers, formatOptionalPeerFixCommand, getOptionalPeerDependencies } from './optional-peers.js';
|
|
13
14
|
|
|
@@ -52,7 +53,7 @@ export function diagnoseUpgradeToL4(
|
|
|
52
53
|
const optionalPeers = diagnoseOptionalPeers(cwd, config, packageJson);
|
|
53
54
|
|
|
54
55
|
const missingAiDeps = ai
|
|
55
|
-
? [...new Set([...ai
|
|
56
|
+
? [...new Set([...packagesNeedingAiStackFix(ai), ...ai.notInstalled])]
|
|
56
57
|
: [...Object.keys(getRequiredAIDependenciesForConfig({ ai: { enabled: true } }))];
|
|
57
58
|
|
|
58
59
|
if (!aiEnabled) {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DATABASE_PACKAGES } from './types.js';
|
|
5
|
+
import type { PackageJsonLike } from './project-deps.js';
|
|
6
|
+
import {
|
|
7
|
+
findMissingPackageDependencies,
|
|
8
|
+
findUnresolvedPackageInstalls,
|
|
9
|
+
isAiEnabledInConfig,
|
|
10
|
+
} from './project-deps.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* User project dependency policy: scaffolded dependencies intentionally float to latest.
|
|
14
|
+
*/
|
|
15
|
+
export const ZHIN_STACK_VERSIONS = {
|
|
16
|
+
'zhin.js': 'latest',
|
|
17
|
+
'@zhin.js/cli': 'latest',
|
|
18
|
+
'@zhin.js/agent': 'latest',
|
|
19
|
+
'@zhin.js/database': 'latest',
|
|
20
|
+
'@zhin.js/host-router': 'latest',
|
|
21
|
+
'@zhin.js/host-api': 'latest',
|
|
22
|
+
'@zhin.js/client': 'latest',
|
|
23
|
+
'@zhin.js/contract': 'latest',
|
|
24
|
+
'@zhin.js/satori': 'latest',
|
|
25
|
+
'@zhin.js/speech': 'latest',
|
|
26
|
+
'@zhin.js/html-renderer': 'latest',
|
|
27
|
+
'@zhin.js/mcp': 'latest',
|
|
28
|
+
'@zhin.js/adapter-sandbox': 'latest',
|
|
29
|
+
'@zhin.js/adapter-dingtalk': 'latest',
|
|
30
|
+
'@zhin.js/adapter-discord': 'latest',
|
|
31
|
+
'@zhin.js/adapter-email': 'latest',
|
|
32
|
+
'@zhin.js/adapter-github': 'latest',
|
|
33
|
+
'@zhin.js/adapter-icqq': 'latest',
|
|
34
|
+
'@zhin.js/adapter-kook': 'latest',
|
|
35
|
+
'@zhin.js/adapter-lark': 'latest',
|
|
36
|
+
'@zhin.js/adapter-line': 'latest',
|
|
37
|
+
'@zhin.js/adapter-milky': 'latest',
|
|
38
|
+
'@zhin.js/adapter-napcat': 'latest',
|
|
39
|
+
'@zhin.js/adapter-onebot11': 'latest',
|
|
40
|
+
'@zhin.js/adapter-onebot12': 'latest',
|
|
41
|
+
'@zhin.js/adapter-qq': 'latest',
|
|
42
|
+
'@zhin.js/adapter-satori': 'latest',
|
|
43
|
+
'@zhin.js/adapter-slack': 'latest',
|
|
44
|
+
'@zhin.js/adapter-telegram': 'latest',
|
|
45
|
+
'@zhin.js/adapter-wechat-mp': 'latest',
|
|
46
|
+
'@zhin.js/adapter-wecom': 'latest',
|
|
47
|
+
'@zhin.js/adapter-weixin-ilink': 'latest',
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export type ZhinStackPackage = keyof typeof ZHIN_STACK_VERSIONS;
|
|
51
|
+
|
|
52
|
+
const HOST_CONSOLE_PLUGINS = new Set([
|
|
53
|
+
'@zhin.js/host-router',
|
|
54
|
+
'@zhin.js/host-api',
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
function parseMajor(versionSpec: string | undefined): number | undefined {
|
|
58
|
+
if (!versionSpec?.trim()) return undefined;
|
|
59
|
+
if (versionSpec === 'workspace:*') return undefined;
|
|
60
|
+
const match = versionSpec.trim().replace(/^[\^~>=<]+/, '').match(/^(\d+)/);
|
|
61
|
+
return match ? Number(match[1]) : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getDeclaredPackageVersion(pkg: PackageJsonLike, name: string): string | undefined {
|
|
65
|
+
return pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readInstalledMajor(cwd: string, packageName: string): number | undefined {
|
|
69
|
+
const pkgJson = path.join(cwd, 'package.json');
|
|
70
|
+
if (!fs.existsSync(pkgJson)) return undefined;
|
|
71
|
+
try {
|
|
72
|
+
const req = createRequire(pkgJson);
|
|
73
|
+
const installed = req(`${packageName}/package.json`) as { version?: string };
|
|
74
|
+
return parseMajor(installed.version);
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolvePackageMajor(cwd: string, pkg: PackageJsonLike, packageName: string): number | undefined {
|
|
81
|
+
return readInstalledMajor(cwd, packageName) ?? parseMajor(getDeclaredPackageVersion(pkg, packageName));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveStackVersion(packageName: string): string {
|
|
85
|
+
return ZHIN_STACK_VERSIONS[packageName as ZhinStackPackage] ?? 'latest';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function collectZhinPluginsFromConfig(config: Record<string, unknown>): string[] {
|
|
89
|
+
const plugins = Array.isArray(config.plugins)
|
|
90
|
+
? config.plugins.filter((p): p is string => typeof p === 'string')
|
|
91
|
+
: [];
|
|
92
|
+
return [...new Set(plugins.filter((p) => p.startsWith('@zhin.js/')))];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** create-zhin / minimal-bot 默认 Host 端口(避免与常见 8086 占用冲突) */
|
|
96
|
+
export const DEFAULT_CREATE_BOT_HTTP_PORT = 8068;
|
|
97
|
+
|
|
98
|
+
/** create-zhin 生成项目的 .npmrc(避免全局 strict-peer-dependencies 阻断 AI 栈安装) */
|
|
99
|
+
export const CREATE_BOT_NPMRC = 'strict-peer-dependencies=false\n';
|
|
100
|
+
|
|
101
|
+
/** create-zhin 生成项目的 pnpm 配置 */
|
|
102
|
+
export function getCreateBotPnpmConfig(_aiEnabled?: boolean): Record<string, unknown> {
|
|
103
|
+
return {
|
|
104
|
+
onlyBuiltDependencies: ['esbuild'],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** create-zhin-app 默认 bot 骨架依赖(不含适配器 / AI / 数据库驱动) */
|
|
109
|
+
export function getCreateBotBaseDependencies(): Record<string, string> {
|
|
110
|
+
return {
|
|
111
|
+
'zhin.js': ZHIN_STACK_VERSIONS['zhin.js'],
|
|
112
|
+
'@zhin.js/cli': ZHIN_STACK_VERSIONS['@zhin.js/cli'],
|
|
113
|
+
'@zhin.js/host-router': ZHIN_STACK_VERSIONS['@zhin.js/host-router'],
|
|
114
|
+
'@zhin.js/client': ZHIN_STACK_VERSIONS['@zhin.js/client'],
|
|
115
|
+
'@zhin.js/host-api': ZHIN_STACK_VERSIONS['@zhin.js/host-api'],
|
|
116
|
+
'@zhin.js/contract': ZHIN_STACK_VERSIONS['@zhin.js/contract'],
|
|
117
|
+
'@zhin.js/satori': ZHIN_STACK_VERSIONS['@zhin.js/satori'],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 根据 zhin.config 推导应声明的 @zhin.js/* / zhin.js 依赖 */
|
|
122
|
+
export function getRequiredZhinDependenciesForConfig(config: Record<string, unknown>): Record<string, string> {
|
|
123
|
+
const deps: Record<string, string> = {
|
|
124
|
+
'zhin.js': ZHIN_STACK_VERSIONS['zhin.js'],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
for (const plugin of collectZhinPluginsFromConfig(config)) {
|
|
128
|
+
deps[plugin] = resolveStackVersion(plugin);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const database = config.database;
|
|
132
|
+
if (database && typeof database === 'object' && !Array.isArray(database)) {
|
|
133
|
+
deps['@zhin.js/database'] = ZHIN_STACK_VERSIONS['@zhin.js/database'];
|
|
134
|
+
const dialect = String((database as Record<string, unknown>).dialect ?? '');
|
|
135
|
+
const driver = DATABASE_PACKAGES[dialect as keyof typeof DATABASE_PACKAGES];
|
|
136
|
+
if (driver) {
|
|
137
|
+
deps[driver] = 'latest';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const usesHost = collectZhinPluginsFromConfig(config).some((p) => HOST_CONSOLE_PLUGINS.has(p));
|
|
142
|
+
if (usesHost) {
|
|
143
|
+
for (const pkg of ['@zhin.js/host-router', '@zhin.js/host-api'] as const) {
|
|
144
|
+
deps[pkg] = ZHIN_STACK_VERSIONS[pkg];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return deps;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ZhinStackIncompatibility {
|
|
152
|
+
package: string;
|
|
153
|
+
reason: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function findOutdatedZhinStackInPackageJson(
|
|
157
|
+
pkg: PackageJsonLike,
|
|
158
|
+
required: Record<string, string>,
|
|
159
|
+
): string[] {
|
|
160
|
+
const outdated: string[] = [];
|
|
161
|
+
for (const [name, minSpec] of Object.entries(required)) {
|
|
162
|
+
const declared = getDeclaredPackageVersion(pkg, name);
|
|
163
|
+
if (!declared || declared === 'workspace:*') continue;
|
|
164
|
+
const declaredMajor = parseMajor(declared);
|
|
165
|
+
const minMajor = parseMajor(minSpec);
|
|
166
|
+
if (declaredMajor != null && minMajor != null && declaredMajor < minMajor) {
|
|
167
|
+
outdated.push(name);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return outdated;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function findInstalledZhinStackIncompatibilities(
|
|
174
|
+
cwd: string,
|
|
175
|
+
pkg: PackageJsonLike,
|
|
176
|
+
config: Record<string, unknown>,
|
|
177
|
+
): ZhinStackIncompatibility[] {
|
|
178
|
+
const issues: ZhinStackIncompatibility[] = [];
|
|
179
|
+
const zhinMajor = resolvePackageMajor(cwd, pkg, 'zhin.js');
|
|
180
|
+
const agentDeclared = getDeclaredPackageVersion(pkg, '@zhin.js/agent');
|
|
181
|
+
const aiEnabled = isAiEnabledInConfig(config);
|
|
182
|
+
|
|
183
|
+
if (zhinMajor != null && zhinMajor < 4 && (aiEnabled || agentDeclared)) {
|
|
184
|
+
issues.push({
|
|
185
|
+
package: 'zhin.js',
|
|
186
|
+
reason: 'zhin.js 4.x 才支持 @zhin.js/agent 与 AI 安装分层(ADR 0019),请升级 zhin.js',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (aiEnabled && !agentDeclared) {
|
|
191
|
+
// missing 由 missingFromPackageJson 处理;此处只报版本组合问题
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const agentMajor = resolvePackageMajor(cwd, pkg, '@zhin.js/agent');
|
|
195
|
+
if (agentMajor != null && agentMajor < 1 && aiEnabled) {
|
|
196
|
+
issues.push({
|
|
197
|
+
package: '@zhin.js/agent',
|
|
198
|
+
reason: '@zhin.js/agent 1.x 与 zhin.js 4.x / AI SDK 7 配套,请升级 @zhin.js/agent',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return issues;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ZhinStackDiagnosis {
|
|
206
|
+
required: Record<string, string>;
|
|
207
|
+
missingFromPackageJson: string[];
|
|
208
|
+
outdatedInPackageJson: string[];
|
|
209
|
+
incompatibleInstalled: ZhinStackIncompatibility[];
|
|
210
|
+
notInstalled: string[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function packagesNeedingZhinStackFix(diagnosis: ZhinStackDiagnosis): string[] {
|
|
214
|
+
const names = new Set<string>([
|
|
215
|
+
...diagnosis.missingFromPackageJson,
|
|
216
|
+
...diagnosis.outdatedInPackageJson,
|
|
217
|
+
]);
|
|
218
|
+
for (const issue of diagnosis.incompatibleInstalled) {
|
|
219
|
+
names.add(issue.package);
|
|
220
|
+
}
|
|
221
|
+
return [...names];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function diagnoseZhinStackDependencies(
|
|
225
|
+
cwd: string,
|
|
226
|
+
config: Record<string, unknown>,
|
|
227
|
+
pkg?: PackageJsonLike,
|
|
228
|
+
): ZhinStackDiagnosis {
|
|
229
|
+
const required = getRequiredZhinDependenciesForConfig(config);
|
|
230
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
231
|
+
const packageJson = pkg ?? (fs.existsSync(packageJsonPath)
|
|
232
|
+
? fs.readJsonSync(packageJsonPath) as PackageJsonLike
|
|
233
|
+
: {});
|
|
234
|
+
const missingFromPackageJson = findMissingPackageDependencies(packageJson, required);
|
|
235
|
+
const outdatedInPackageJson = findOutdatedZhinStackInPackageJson(packageJson, required);
|
|
236
|
+
const incompatibleInstalled = findInstalledZhinStackIncompatibilities(cwd, packageJson, config);
|
|
237
|
+
const declared = Object.keys(required).filter((name) => !missingFromPackageJson.includes(name));
|
|
238
|
+
const notInstalled = findUnresolvedPackageInstalls(cwd, declared);
|
|
239
|
+
return {
|
|
240
|
+
required,
|
|
241
|
+
missingFromPackageJson,
|
|
242
|
+
outdatedInPackageJson,
|
|
243
|
+
incompatibleInstalled,
|
|
244
|
+
notInstalled,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function formatZhinStackFixCommand(packages: string[], required: Record<string, string>): string {
|
|
249
|
+
if (packages.length === 0) return 'pnpm install';
|
|
250
|
+
const specs = packages.map((name) => `${name}@${required[name] ?? 'latest'}`);
|
|
251
|
+
return `pnpm add ${specs.join(' ')}`;
|
|
252
|
+
}
|