cortico 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
package/src/deploy.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 部署配置按顺序深合并:BotDefinition.defaults()、包内 World 覆盖、共享端点表、部署 config.json。
|
|
3
|
+
* 部署文件中的 providers 字段不参与合并。Core、Persona 与 World 的默认值由各自所有者提供。
|
|
4
|
+
* 运行时共享合并后的配置对象,控制台和调参工具原位更新。
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
8
|
+
import type { CoreConfig, LLMProviderEntry } from './core/types.ts';
|
|
9
|
+
import { deepMerge, type LoadedConfig } from './core/config.ts';
|
|
10
|
+
import { secretReader } from './core/secrets.ts';
|
|
11
|
+
import { readTextFile } from './core/util.ts';
|
|
12
|
+
import { deploymentRoot, repoRoot as codeRepoRoot } from './paths.ts';
|
|
13
|
+
|
|
14
|
+
export type { LoadedConfig } from './core/config.ts';
|
|
15
|
+
|
|
16
|
+
/** 部署根下每个含 deployment.json 的目录就是一份可启动的部署。 */
|
|
17
|
+
export function listBots(root = deploymentRoot()): string[] {
|
|
18
|
+
if (!existsSync(root)) return [];
|
|
19
|
+
return readdirSync(root)
|
|
20
|
+
.filter((name) => {
|
|
21
|
+
const dir = resolve(root, name);
|
|
22
|
+
return statSync(dir).isDirectory() && existsSync(resolve(dir, 'deployment.json'));
|
|
23
|
+
})
|
|
24
|
+
.sort();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 部署根下一份部署都没有时自建的那份,以及它引用的 bot 代码包。 */
|
|
28
|
+
export const DEFAULT_DEPLOYMENT = { name: 'mybot', bot: 'cormini' } as const;
|
|
29
|
+
|
|
30
|
+
/** 部署目录中的开场引导标记。 */
|
|
31
|
+
export const ONBOARDING_FLAG_FILE = '.onboarding';
|
|
32
|
+
|
|
33
|
+
/** 部署根下另有用途的目录名,不能拿来当部署名。 */
|
|
34
|
+
const RESERVED_DEPLOYMENT_NAMES: readonly string[] = ['providers', 'runtimes', 'models'];
|
|
35
|
+
|
|
36
|
+
/** 部署目录名不合用的原因;合用时为 null。这个名字就是目录名,也出现在命令行上。 */
|
|
37
|
+
export function deploymentNameProblem(name: string, taken: readonly string[]): string | null {
|
|
38
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
|
|
39
|
+
return '只能用字母、数字、- . _,且以字母或数字开头。';
|
|
40
|
+
}
|
|
41
|
+
if (RESERVED_DEPLOYMENT_NAMES.includes(name)) return `${name} 是部署根下的固定目录。`;
|
|
42
|
+
if (taken.includes(name)) return `${name} 已经有了。`;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 建一份空白部署:`deployment.json` 指向代码包,加上开场引导的标记;给了展示名就写一份
|
|
48
|
+
* 只有 `displayName` 的 `config.json`。端点与其余设置在控制台里配。
|
|
49
|
+
*/
|
|
50
|
+
export function createDeployment(
|
|
51
|
+
options: { name: string; bot: string; displayName?: string; root?: string },
|
|
52
|
+
): string {
|
|
53
|
+
const root = options.root ?? deploymentRoot();
|
|
54
|
+
const problem = deploymentNameProblem(options.name, listBots(root));
|
|
55
|
+
if (problem) throw new Error(problem);
|
|
56
|
+
const dir = resolve(root, options.name);
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
writeJson(resolve(dir, 'deployment.json'), { bot: options.bot });
|
|
59
|
+
if (options.displayName) writeJson(resolve(dir, 'config.json'), { displayName: options.displayName });
|
|
60
|
+
writeFileSync(resolve(dir, ONBOARDING_FLAG_FILE), '', 'utf8');
|
|
61
|
+
return options.name;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeJson(file: string, value: unknown): void {
|
|
65
|
+
writeFileSync(file, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 返回一个可启动的部署名;部署根下一份都没有时先建 DEFAULT_DEPLOYMENT。 */
|
|
69
|
+
export function ensureDeployment(root = deploymentRoot()): string {
|
|
70
|
+
const existing = listBots(root);
|
|
71
|
+
if (existing.length > 0) return existing[0];
|
|
72
|
+
return createDeployment({ ...DEFAULT_DEPLOYMENT, root });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DeploymentSource<C extends CoreConfig> {
|
|
76
|
+
/** 每次返回独立的合并默认值。 */
|
|
77
|
+
defaults(): C;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 读取 <部署根>/providers/<端点名>/config.json,供同一部署根下的部署共享。
|
|
82
|
+
* 同名条目覆盖 Core 默认端点;目录内的密钥与其他状态由 provider 管理。
|
|
83
|
+
*/
|
|
84
|
+
function globalProviders(providersDir: string): Record<string, LLMProviderEntry> {
|
|
85
|
+
if (!existsSync(providersDir)) return {};
|
|
86
|
+
const table: Record<string, LLMProviderEntry> = {};
|
|
87
|
+
for (const name of readdirSync(providersDir)) {
|
|
88
|
+
const file = resolve(providersDir, name, 'config.json');
|
|
89
|
+
if (!existsSync(file)) continue;
|
|
90
|
+
try {
|
|
91
|
+
table[name] = JSON.parse(readTextFile(file)) as LLMProviderEntry;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw new Error(`${file} 解析失败:${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return table;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 代码包在 worlds/<id>/config.json 提供 World 配置覆盖;部署 config.json 可继续覆盖。
|
|
101
|
+
* 路径、设备与端口等本机配置由部署提供。
|
|
102
|
+
*/
|
|
103
|
+
function packageWorldOverrides(pkgDir: string): Record<string, unknown> {
|
|
104
|
+
const ioDir = resolve(pkgDir, 'worlds');
|
|
105
|
+
if (!existsSync(ioDir)) return {};
|
|
106
|
+
const worlds: Record<string, unknown> = {};
|
|
107
|
+
for (const id of readdirSync(ioDir)) {
|
|
108
|
+
const file = resolve(ioDir, id, 'config.json');
|
|
109
|
+
if (!existsSync(file)) continue;
|
|
110
|
+
try {
|
|
111
|
+
worlds[id] = JSON.parse(readTextFile(file));
|
|
112
|
+
} catch (err) {
|
|
113
|
+
throw new Error(`${file} 解析失败:${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(worlds).length ? { worlds } : {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 加载部署并解析目录:
|
|
121
|
+
* botDir 是部署目录,config.json、.env 及相对 memory/data 路径以它为基准。
|
|
122
|
+
* repoRoot 是代码检出目录,用于扩展与代码信息。
|
|
123
|
+
* pkgDir 是代码包目录,供模板和包内资产使用;缺省为 botDir。
|
|
124
|
+
* providersDir 是共享端点目录;缺省为 <botDir>/providers。
|
|
125
|
+
*/
|
|
126
|
+
export function loadDeployment<C extends CoreConfig>(
|
|
127
|
+
source: DeploymentSource<C>,
|
|
128
|
+
botDir: string,
|
|
129
|
+
repoRoot: string = codeRepoRoot(),
|
|
130
|
+
pkgDir: string = botDir,
|
|
131
|
+
providersDir: string = resolve(botDir, 'providers'),
|
|
132
|
+
): LoadedConfig<C> {
|
|
133
|
+
const dir = resolve(botDir);
|
|
134
|
+
const root = resolve(repoRoot);
|
|
135
|
+
const providers = resolve(providersDir);
|
|
136
|
+
const cfgPath = resolve(dir, 'config.json');
|
|
137
|
+
const raw: Partial<C> & Record<string, unknown> = existsSync(cfgPath)
|
|
138
|
+
? (JSON.parse(readTextFile(cfgPath)) as Partial<C> & Record<string, unknown>)
|
|
139
|
+
: {};
|
|
140
|
+
// 部署文件不能覆盖共享端点表。
|
|
141
|
+
delete raw.providers;
|
|
142
|
+
// source.defaults() 已包含各所有者默认值,后续层依次覆盖。
|
|
143
|
+
const config = deepMerge(
|
|
144
|
+
deepMerge(
|
|
145
|
+
deepMerge(source.defaults(), packageWorldOverrides(resolve(pkgDir)) as Partial<C>),
|
|
146
|
+
{ providers: globalProviders(providers) } as Partial<C>,
|
|
147
|
+
),
|
|
148
|
+
raw,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const abs = (p: string): string => (isAbsolute(p) ? p : resolve(dir, p));
|
|
152
|
+
return {
|
|
153
|
+
config,
|
|
154
|
+
secret: secretReader(resolve(dir, '.env')),
|
|
155
|
+
rootDir: dir,
|
|
156
|
+
packageDir: resolve(pkgDir),
|
|
157
|
+
providersDir: providers,
|
|
158
|
+
repoRoot: root,
|
|
159
|
+
memoryDir: abs(config.paths.memory),
|
|
160
|
+
dataDir: abs(config.paths.data),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!-- Owner: src/extensions/manifest.ts, src/extensions/runtime.ts, src/extensions/dry-mount.ts, src/extensions.ts -->
|
|
2
|
+
|
|
3
|
+
# src/extensions
|
|
4
|
+
|
|
5
|
+
扩展是装在 `extensions/` 下的 npm 包,给框架补一个 World、一个 provider 或一个 bot。
|
|
6
|
+
三类使用相同的 manifest 和加载流程,按 `kind` 校验默认导出结构、id 命名空间与控制台页前缀。
|
|
7
|
+
`pnpm check:extension` 使用 `dry-mount.ts` 在假部署中验证构造与声明接口:
|
|
8
|
+
World 调用 `create()` / `tools()` / `console()`,provider 调用 `create()`,bot 调用 `build()`;
|
|
9
|
+
不调用 `start()`。
|
|
10
|
+
|
|
11
|
+
## manifest
|
|
12
|
+
|
|
13
|
+
包的 `package.json` 里一块 `cortico`:
|
|
14
|
+
|
|
15
|
+
```jsonc
|
|
16
|
+
{
|
|
17
|
+
"name": "cortico-world-vtuber",
|
|
18
|
+
"type": "module", // 必填;CommonJS 有加载重复模块实例的风险
|
|
19
|
+
"keywords": ["cortico-world"], // npm 搜索按类关键字:cortico-world / cortico-provider / cortico-bot
|
|
20
|
+
"cortico": {
|
|
21
|
+
"kind": "world", // world | provider | bot
|
|
22
|
+
"api": 4, // 扩展契约版本,与 EXTENSION_API_VERSION 相等才加载
|
|
23
|
+
"consoleClient": "dist/console.js", // 可选:预构建的面板 bundle,包内相对路径
|
|
24
|
+
"consoleStyle": "dist/console.css" // 可选:随 bundle 注入的样式
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`parseExtensionManifest(pkg)` 只做解析与校验,不碰文件系统;装载器与
|
|
30
|
+
`pnpm check:extension <dir>` 共用它。`api` 与框架不等时不加载,扩展页说明哪一边旧。
|
|
31
|
+
`WorldDefinition`、`ProviderModule`、`BotDefinition`(连同 `BotParts`、`Persona`、`LoadedConfig`)
|
|
32
|
+
或 `ConsolePanelContext` 任一不兼容变更就把 `EXTENSION_API_VERSION` 加一。同一次发布里的多处变更合计加一。
|
|
33
|
+
|
|
34
|
+
## 装载
|
|
35
|
+
|
|
36
|
+
`loadExtensions(repoRoot)` 读 `extensions/package.json` 的 dependencies,逐个从
|
|
37
|
+
`extensions/node_modules/<包>/` import 入口(`exports` → `module` → `main` → `index.js`),
|
|
38
|
+
按 `kind` 校验默认导出:
|
|
39
|
+
|
|
40
|
+
| kind | 默认导出 | 命名空间 | 控制台页 |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `world` | `WorldDefinition`(`id` / `label` / `defaults()` / `create()`) | 与内建 World 共用 | `world:<id>` |
|
|
43
|
+
| `provider` | `ProviderModule`(`id` / `title` / `reasoningTiers` / `serviceTiers` / `create()`) | 与内建 provider 共用 | `llm:<id>` |
|
|
44
|
+
| `bot` | `BotDefinition`(`id` / `defaults()` / `build()`) | 不得与仓内 `bots/` 目录同名 | `persona:<id>` |
|
|
45
|
+
|
|
46
|
+
与内建 id 冲突的扩展不能加载;两个扩展 id 相同时只加载第一个。单个扩展失败不影响其他扩展,
|
|
47
|
+
原因写入 `ExtensionRecord.reason`。
|
|
48
|
+
|
|
49
|
+
bot 包不在这条循环里 import:`deployment.json` 的 `bot` 字段指向哪个包,启动器就先经
|
|
50
|
+
`locateBotPackage()`(优先仓内 `bots/<名>/`,其次为 `extensions/` 下同名且 `kind: "bot"` 的包)与
|
|
51
|
+
`importBotDefinition()` 导入该包,再作为 `activeBot` 交给 `loadExtensions()` 标记加载状态:
|
|
52
|
+
同名记录记 loaded,其余 bot 包记 `idle`。`ExtensionSet.bot` 说明这份部署的 bot 来自扩展包,
|
|
53
|
+
`createBot` 据此把已安装扩展包内的提示词模板设为只读,pnpm 安装的文件可能硬链接到 store。结果 `ExtensionSet` 里 `worlds` 由启动器接在仓内目录(`src/worlds/index.ts`)之后,整张表经
|
|
54
|
+
`withWorlds()` 交给 bot 定义;它仅补充缺失的 World 配置段,已有段保持原值,新增段的 `enabled` 取决于 bot 是否声明该 World。`providers` 交给 `registerProviderModules()`,`consoleAssets` 交给控制台。
|
|
55
|
+
|
|
56
|
+
新装或卸掉的包要重启进程:ESM 模块缓存不支持运行中换代码。
|
|
57
|
+
|
|
58
|
+
## 扩展怎么 import 框架
|
|
59
|
+
|
|
60
|
+
扩展写 `import { nowIso } from 'cortico/core/util.ts'`:`cortico/<路径>` 就是 `src/<路径>`。
|
|
61
|
+
`runtime.ts` 用 `module.registerHooks` 把这个前缀映到框架源码,再交给链上下一个解析器,
|
|
62
|
+
所以扩展与框架拿到同一份模块实例。fork 出去的子进程用 `childExecArgv()` 带上同一个钩子。
|
|
63
|
+
|
|
64
|
+
浏览器侧(面板 bundle)对 `cortico/*` 只允许 `import type`;bundle 里要用的运行时值由包自带。
|
|
65
|
+
|
|
66
|
+
## 控制台接口
|
|
67
|
+
|
|
68
|
+
`ExtensionManager` 提供扩展页接口:`list()` 比较启动时的加载结果与当前安装状态
|
|
69
|
+
(`loaded` / `failed` / `pending-restart` / `removed`),`search()` 按关键字查 npm registry,
|
|
70
|
+
`install()` / `uninstall()` 经 `corepack pnpm add|remove --ignore-workspace` 改 `extensions/`。
|
|
71
|
+
装卸串行。面板 bundle 的 URL 由服务端分配:`/assets/extensions/<包>/<版本>/<文件>`,只发
|
|
72
|
+
manifest 里声明的那两个文件。
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在临时部署中检查扩展构造与声明接口,不调用 start()。
|
|
3
|
+
* World 使用默认配置、无密钥,检查 create、tools、envPromptVars、console 与工具名冲突;
|
|
4
|
+
* provider 使用测试端点 create,bot 使用测试部署 build。
|
|
5
|
+
* World 构造失败为错误;provider 仅在绑定端点时构造,测试条目不完整导致的失败记为警告。
|
|
6
|
+
* 临时文件位于 scratchDir,调用方负责创建与清理。
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import type { CoreConfig, World } from '../core/types.ts';
|
|
11
|
+
import { MODULE_LAMP_MAX } from '../core/types.ts';
|
|
12
|
+
import type { LoadedConfig } from '../core/config.ts';
|
|
13
|
+
import { CORE_DEFAULTS } from '../core/config.ts';
|
|
14
|
+
import { RESERVED_FRAME_NAMES } from '../core/loop.ts';
|
|
15
|
+
import { nullLogger } from '../core/util.ts';
|
|
16
|
+
import type { Language } from '../core/language.ts';
|
|
17
|
+
import type { BotDefinition } from '../bot.ts';
|
|
18
|
+
import type { WorldContext, WorldDefinition, WorldSection } from '../world.ts';
|
|
19
|
+
import type { ProviderModule } from '../providers/base.ts';
|
|
20
|
+
|
|
21
|
+
export interface DryMountReport {
|
|
22
|
+
ok: string[];
|
|
23
|
+
warnings: string[];
|
|
24
|
+
failures: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DryMountOptions {
|
|
28
|
+
/** 假部署与假端点目录的根。 */
|
|
29
|
+
scratchDir: string;
|
|
30
|
+
/** 扩展包目录;bot 的 `packageDir` 与 World 的 `packageDir` 都指它。缺省用假部署目录。 */
|
|
31
|
+
packageDir?: string;
|
|
32
|
+
repoRoot?: string;
|
|
33
|
+
language?: Language;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isPlainObject = (v: unknown): v is Record<string, unknown> =>
|
|
37
|
+
typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
38
|
+
|
|
39
|
+
const message = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
|
40
|
+
|
|
41
|
+
/** 面板 id 的形状:控制台一页内的局部 id。 */
|
|
42
|
+
const PANEL_ID = /^[a-z0-9-]+$/;
|
|
43
|
+
/** 工具名带前缀:`<短名>_` 起头。 */
|
|
44
|
+
const TOOL_PREFIX = /^[a-z0-9]+_/;
|
|
45
|
+
|
|
46
|
+
function deploymentDirs(opts: DryMountOptions): { botDir: string; dataDir: string; memoryDir: string } {
|
|
47
|
+
const botDir = join(opts.scratchDir, 'deployment');
|
|
48
|
+
const dataDir = join(botDir, 'data');
|
|
49
|
+
const memoryDir = join(botDir, 'memory');
|
|
50
|
+
mkdirSync(dataDir, { recursive: true });
|
|
51
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
52
|
+
return { botDir, dataDir, memoryDir };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 装配层给 `create()` 的上下文的假件:默认配置、没有密钥、写回都是空操作。 */
|
|
56
|
+
export function fakeWorldContext<S extends WorldSection>(def: WorldDefinition<S>, opts: DryMountOptions): WorldContext<S> {
|
|
57
|
+
const cfg = def.defaults();
|
|
58
|
+
const { botDir, dataDir } = deploymentDirs(opts);
|
|
59
|
+
return {
|
|
60
|
+
id: def.id,
|
|
61
|
+
cfg,
|
|
62
|
+
timezone: CORE_DEFAULTS.timezone,
|
|
63
|
+
botName: CORE_DEFAULTS.displayName,
|
|
64
|
+
botDir,
|
|
65
|
+
packageDir: opts.packageDir ?? botDir,
|
|
66
|
+
dataDir,
|
|
67
|
+
repoRoot: opts.repoRoot ?? botDir,
|
|
68
|
+
secret: () => '',
|
|
69
|
+
storeSecret: () => {},
|
|
70
|
+
persist: (patch) => { Object.assign(cfg as object, patch); },
|
|
71
|
+
restart: async () => {},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 收集工具名称及所属定义,用于冲突检查;无法构造的定义列入 skipped,
|
|
77
|
+
* 调用方应说明冲突检查未覆盖这些定义。
|
|
78
|
+
*/
|
|
79
|
+
export function collectToolNames(
|
|
80
|
+
defs: readonly WorldDefinition<WorldSection>[],
|
|
81
|
+
opts: DryMountOptions,
|
|
82
|
+
): { taken: Map<string, string>; skipped: string[] } {
|
|
83
|
+
const taken = new Map<string, string>();
|
|
84
|
+
const skipped: string[] = [];
|
|
85
|
+
for (const def of defs) {
|
|
86
|
+
try {
|
|
87
|
+
for (const tool of def.create(fakeWorldContext(def, opts)).tools()) taken.set(tool.name, def.label);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
skipped.push(`${def.label}: ${message(error)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { taken, skipped };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface WorldDryMountOptions extends DryMountOptions {
|
|
96
|
+
/** manifest 声明了浏览器端产物;声明了面板却没有产物只是警告。 */
|
|
97
|
+
hasConsoleClient?: boolean;
|
|
98
|
+
/** 已被别的 World 占用的工具名 → 占用者。 */
|
|
99
|
+
takenToolNames?: ReadonlyMap<string, string>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function dryMountWorld(def: WorldDefinition<WorldSection>, opts: WorldDryMountOptions): Promise<DryMountReport> {
|
|
103
|
+
const report: DryMountReport = { ok: [], warnings: [], failures: [] };
|
|
104
|
+
const { ok, warnings, failures } = report;
|
|
105
|
+
|
|
106
|
+
let defaults: unknown;
|
|
107
|
+
try {
|
|
108
|
+
defaults = def.defaults();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
failures.push(`defaults() 抛错: ${message(error)}`);
|
|
111
|
+
return report;
|
|
112
|
+
}
|
|
113
|
+
if (!isPlainObject(defaults)) {
|
|
114
|
+
failures.push('defaults() 没有返回对象:它是 worlds.<id> 段的默认值。');
|
|
115
|
+
return report;
|
|
116
|
+
}
|
|
117
|
+
if (defaults.enabled !== false) {
|
|
118
|
+
warnings.push('defaults().enabled 不是 false:启用与否由 bot 的 declares 与部署决定,定义里恒为 false。');
|
|
119
|
+
}
|
|
120
|
+
ok.push('defaults() 返回配置段。');
|
|
121
|
+
|
|
122
|
+
let world: World;
|
|
123
|
+
try {
|
|
124
|
+
world = def.create(fakeWorldContext(def, opts));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
failures.push(`create() 在默认配置、无密钥下抛错: ${message(error)}。装配层会构造所有可用定义,默认配置下也必须能完成构造。`);
|
|
127
|
+
return report;
|
|
128
|
+
}
|
|
129
|
+
if (world.id !== def.id) {
|
|
130
|
+
failures.push(`实例的 id「${world.id}」与定义的 id「${def.id}」不同:装配层按定义找槽位,Core 按实例归属工具与事件,两边会对不上。`);
|
|
131
|
+
} else {
|
|
132
|
+
ok.push(`create() 返回 World 实例,id = ${world.id}。`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let tools: ReturnType<World['tools']>;
|
|
136
|
+
try {
|
|
137
|
+
tools = world.tools();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
failures.push(`tools() 抛错: ${message(error)}`);
|
|
140
|
+
return report;
|
|
141
|
+
}
|
|
142
|
+
if (!Array.isArray(tools)) {
|
|
143
|
+
failures.push('tools() 没有返回数组。');
|
|
144
|
+
return report;
|
|
145
|
+
}
|
|
146
|
+
const seen = new Set<string>();
|
|
147
|
+
for (const tool of tools) {
|
|
148
|
+
const name = tool?.name;
|
|
149
|
+
if (typeof name !== 'string' || name === '') {
|
|
150
|
+
failures.push('有一个工具没有 name。');
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (seen.has(name)) failures.push(`工具名重复: ${name}。`);
|
|
154
|
+
seen.add(name);
|
|
155
|
+
if (RESERVED_FRAME_NAMES.has(name)) failures.push(`工具名「${name}」是 Core 的保留帧名,装配层拒绝挂载。`);
|
|
156
|
+
const owner = opts.takenToolNames?.get(name);
|
|
157
|
+
if (owner) failures.push(`工具名「${name}」与 ${owner} 撞名,装配层拒绝挂载。`);
|
|
158
|
+
if (!isPlainObject(tool.parameters)) failures.push(`工具「${name}」的 parameters 不是对象:它是发给模型的 JSON Schema。`);
|
|
159
|
+
if (typeof tool.handler !== 'function') failures.push(`工具「${name}」没有 handler。`);
|
|
160
|
+
if (!Array.isArray(tool.tags)) failures.push(`工具「${name}」没有 tags 数组:空数组表示明确不分类,缺席不行。`);
|
|
161
|
+
else if (tool.tags.length === 0) warnings.push(`工具「${name}」的 tags 为空,按标签选择工具时不会匹配;挂载时记录警告。`);
|
|
162
|
+
if (!TOOL_PREFIX.test(name)) warnings.push(`工具名「${name}」没有前缀:惯例是 <短名>_<动词短语>,工具名在整个 bot 里唯一。`);
|
|
163
|
+
}
|
|
164
|
+
if (!failures.length) ok.push(tools.length ? `tools(): ${tools.map((t) => t.name).join(', ')}` : 'tools(): 没有工具。');
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const vars = await world.envPromptVars();
|
|
168
|
+
if (vars === null) ok.push('envPromptVars() 返回 null:这个 World 的段不进前缀。');
|
|
169
|
+
else if (isPlainObject(vars)) ok.push(`envPromptVars(): ${Object.keys(vars).length} 个占位符值。`);
|
|
170
|
+
else failures.push('envPromptVars() 要返回对象或 null。');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
failures.push(`envPromptVars() 抛错: ${message(error)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof world.console === 'function') {
|
|
176
|
+
try {
|
|
177
|
+
const decl = world.console(opts.language ?? 'zh');
|
|
178
|
+
const panels = decl.panels ?? [];
|
|
179
|
+
const panelIds = new Set<string>();
|
|
180
|
+
for (const panel of panels) {
|
|
181
|
+
if (!PANEL_ID.test(panel.id)) failures.push(`面板 id「${panel.id}」不合形状:控制台一页内的局部 id,只用 [a-z0-9-]。`);
|
|
182
|
+
if (panelIds.has(panel.id)) failures.push(`面板 id 重复: ${panel.id}。`);
|
|
183
|
+
panelIds.add(panel.id);
|
|
184
|
+
}
|
|
185
|
+
if (panels.length && !opts.hasConsoleClient) {
|
|
186
|
+
warnings.push(`console() 声明了 ${panels.length} 个面板,未声明 cortico.consoleClient;使用自定义面板时需提供浏览器产物。`);
|
|
187
|
+
}
|
|
188
|
+
if ((decl.lamps?.length ?? 0) > MODULE_LAMP_MAX) {
|
|
189
|
+
warnings.push(`console().lamps 有 ${decl.lamps?.length} 颗,控制台最多画 ${MODULE_LAMP_MAX} 颗。`);
|
|
190
|
+
}
|
|
191
|
+
for (const group of decl.config ?? []) {
|
|
192
|
+
if (group.owner !== `world:${def.id}`) warnings.push(`配置组「${group.id}」的 owner 是「${group.owner}」,World 的配置组 owner 应为 world:${def.id}。`);
|
|
193
|
+
}
|
|
194
|
+
const keys = new Set<string>();
|
|
195
|
+
for (const doc of decl.promptDocs ?? []) {
|
|
196
|
+
if (keys.has(doc.key)) failures.push(`promptDocs key 重复: ${doc.key}。`);
|
|
197
|
+
keys.add(doc.key);
|
|
198
|
+
if (doc.role === 'envPrompt' && !existsSync(doc.path)) {
|
|
199
|
+
warnings.push(`环境提示词模板 ${doc.path} 不在:这个 World 的段会是空的。`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
ok.push(`console(): ${panels.length} 个面板,${decl.config?.length ?? 0} 个配置组,${decl.promptDocs?.length ?? 0} 份提示词文档。`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
failures.push(`console() 抛错: ${message(error)}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
ok.push('没有 console():控制台按通用信息显示。');
|
|
208
|
+
}
|
|
209
|
+
return report;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface ProviderDryMountOptions extends DryMountOptions {
|
|
213
|
+
hasConsoleClient?: boolean;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function dryMountProvider(mod: ProviderModule, opts: ProviderDryMountOptions): DryMountReport {
|
|
217
|
+
const report: DryMountReport = { ok: [], warnings: [], failures: [] };
|
|
218
|
+
const { ok, warnings, failures } = report;
|
|
219
|
+
const language = opts.language ?? 'zh';
|
|
220
|
+
|
|
221
|
+
for (const [field, tiers] of [['reasoningTiers', mod.reasoningTiers], ['serviceTiers', mod.serviceTiers]] as const) {
|
|
222
|
+
for (const tier of tiers) {
|
|
223
|
+
if (typeof tier?.id !== 'string' || typeof tier.label !== 'string') {
|
|
224
|
+
failures.push(`${field} 里有一项缺 id 或 label。`);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!failures.length) ok.push(`档位表: ${mod.reasoningTiers.length} 档推理(空表 = 开放),${mod.serviceTiers.length} 档服务。`);
|
|
230
|
+
|
|
231
|
+
const raw = { kind: mod.id, baseUrl: mod.defaultBaseUrl ?? mod.baseUrlSuggestions?.[0] ?? 'http://127.0.0.1:0' };
|
|
232
|
+
let entry = raw;
|
|
233
|
+
try {
|
|
234
|
+
entry = mod.normalize?.(structuredClone(raw)) ?? raw;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
failures.push(`normalize() 抛错: ${message(error)}`);
|
|
237
|
+
return report;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const stateDir = join(opts.scratchDir, 'providers', 'check');
|
|
241
|
+
mkdirSync(stateDir, { recursive: true });
|
|
242
|
+
const resources = new Map<string, unknown>();
|
|
243
|
+
const host = {
|
|
244
|
+
stateDir,
|
|
245
|
+
repoRoot: opts.repoRoot ?? opts.scratchDir,
|
|
246
|
+
resource: <T>(key: string, create: () => T): T => {
|
|
247
|
+
if (!resources.has(key)) resources.set(key, create());
|
|
248
|
+
return resources.get(key) as T;
|
|
249
|
+
},
|
|
250
|
+
currentEntry: () => entry,
|
|
251
|
+
secret: () => '',
|
|
252
|
+
readBlob: () => null,
|
|
253
|
+
keepThinking: () => false,
|
|
254
|
+
log: nullLogger(),
|
|
255
|
+
};
|
|
256
|
+
try {
|
|
257
|
+
const instance = mod.create('check', entry, host);
|
|
258
|
+
if (typeof instance?.client?.respond !== 'function') {
|
|
259
|
+
failures.push('create() 返回的实例没有 client.respond():Core 只经它调模型。');
|
|
260
|
+
} else {
|
|
261
|
+
ok.push('create() 返回实例,client.respond() 在。');
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
warnings.push(`create() 在假端点条目(只有 kind 与 baseUrl)下抛错: ${message(error)}。真实端点条目可能不同,这一项不算失败。`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (mod.config) {
|
|
268
|
+
try {
|
|
269
|
+
const groups = mod.config('check', entry, language);
|
|
270
|
+
if (!Array.isArray(groups)) failures.push('config() 没有返回数组。');
|
|
271
|
+
else {
|
|
272
|
+
for (const group of groups) {
|
|
273
|
+
if (group.owner !== `provider:${mod.id}`) warnings.push(`配置组「${group.id}」的 owner 是「${group.owner}」,provider 的配置组 owner 应为 provider:${mod.id}。`);
|
|
274
|
+
}
|
|
275
|
+
ok.push(`config(): ${groups.length} 个配置组。`);
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
failures.push(`config() 抛错: ${message(error)}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (mod.localize) {
|
|
283
|
+
try {
|
|
284
|
+
mod.localize(language);
|
|
285
|
+
ok.push("localize() 调用成功。");
|
|
286
|
+
} catch (error) {
|
|
287
|
+
failures.push(`localize() 抛错: ${message(error)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (mod.console) {
|
|
292
|
+
try {
|
|
293
|
+
const contribution = mod.console({
|
|
294
|
+
language,
|
|
295
|
+
entries: () => [],
|
|
296
|
+
instance: () => { throw new Error("构造检查不提供实际端点实例"); },
|
|
297
|
+
save: () => {},
|
|
298
|
+
});
|
|
299
|
+
const panels = contribution.panels ?? [];
|
|
300
|
+
for (const panel of panels) {
|
|
301
|
+
if (!PANEL_ID.test(panel.id)) failures.push(`面板 id「${panel.id}」不合形状:只用 [a-z0-9-]。`);
|
|
302
|
+
}
|
|
303
|
+
if (panels.length && !opts.hasConsoleClient) {
|
|
304
|
+
warnings.push(`console() 声明了 ${panels.length} 个面板,未声明 cortico.consoleClient;使用自定义面板时需提供浏览器产物。`);
|
|
305
|
+
}
|
|
306
|
+
ok.push(`console(): ${panels.length} 个面板。`);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
failures.push(`console() 抛错: ${message(error)}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return report;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface BotDryMountOptions extends DryMountOptions {
|
|
315
|
+
packageDir: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function dryMountBot(def: BotDefinition<CoreConfig>, opts: BotDryMountOptions): DryMountReport {
|
|
319
|
+
const report: DryMountReport = { ok: [], warnings: [], failures: [] };
|
|
320
|
+
const { ok, warnings, failures } = report;
|
|
321
|
+
|
|
322
|
+
let config: unknown;
|
|
323
|
+
try {
|
|
324
|
+
config = def.defaults();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
failures.push(`defaults() 抛错: ${message(error)}`);
|
|
327
|
+
return report;
|
|
328
|
+
}
|
|
329
|
+
if (!isPlainObject(config)) {
|
|
330
|
+
failures.push("defaults() 必须返回配置对象。");
|
|
331
|
+
return report;
|
|
332
|
+
}
|
|
333
|
+
(config as { worlds?: unknown }).worlds ??= {};
|
|
334
|
+
ok.push('defaults() 返回对象。');
|
|
335
|
+
|
|
336
|
+
for (const decl of def.declares ?? []) {
|
|
337
|
+
const shaped = typeof decl === 'string' ? decl !== '' : isPlainObject(decl) && typeof decl.id === 'string' && typeof decl.label === 'string';
|
|
338
|
+
if (!shaped) failures.push(`declares 里有一项不合形状: ${JSON.stringify(decl)}(World id 字符串,或 { id, label, reason? })。`);
|
|
339
|
+
}
|
|
340
|
+
if (def.declares?.length) ok.push(`declares: ${def.declares.map((d) => (typeof d === 'string' ? d : d.id)).join(', ')}`);
|
|
341
|
+
|
|
342
|
+
const { botDir, dataDir, memoryDir } = deploymentDirs(opts);
|
|
343
|
+
const loaded: LoadedConfig<CoreConfig> = {
|
|
344
|
+
config: config as unknown as CoreConfig,
|
|
345
|
+
secret: () => '',
|
|
346
|
+
rootDir: botDir,
|
|
347
|
+
memoryDir,
|
|
348
|
+
dataDir,
|
|
349
|
+
packageDir: opts.packageDir,
|
|
350
|
+
providersDir: join(botDir, 'providers'),
|
|
351
|
+
repoRoot: opts.repoRoot ?? botDir,
|
|
352
|
+
};
|
|
353
|
+
let parts: ReturnType<typeof def.build>;
|
|
354
|
+
try {
|
|
355
|
+
parts = def.build(loaded, []);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
failures.push(`build() 在假部署(默认配置、空 Memory、无密钥)下抛错: ${message(error)}。build() 必须能在该配置下完成构造。`);
|
|
358
|
+
return report;
|
|
359
|
+
}
|
|
360
|
+
const persona = parts?.persona as unknown;
|
|
361
|
+
if (!isPlainObject(persona)) {
|
|
362
|
+
failures.push('build() 没有返回 persona。');
|
|
363
|
+
return report;
|
|
364
|
+
}
|
|
365
|
+
for (const method of ['systemSegments', 'attach', 'declareSessions'] as const) {
|
|
366
|
+
if (typeof persona[method] !== 'function') failures.push(`persona 缺 ${method}():Persona 契约的必填项。`);
|
|
367
|
+
}
|
|
368
|
+
if (typeof persona.memoryDir !== 'string') failures.push('persona.memoryDir 不是字符串:Memory 目录的绝对路径。');
|
|
369
|
+
const blobs = persona.blobs as Record<string, unknown> | undefined;
|
|
370
|
+
if (!isPlainObject(blobs) || ['put', 'get', 'list'].some((m) => typeof blobs[m] !== 'function')) {
|
|
371
|
+
failures.push('persona.blobs 不是 BlobStore(put / get / list):mem: 句柄没有后端。');
|
|
372
|
+
}
|
|
373
|
+
if (!failures.length) ok.push('build() 返回 Persona,契约必填项都在。');
|
|
374
|
+
if (typeof def.memoryName !== 'string' || def.memoryName === '') {
|
|
375
|
+
warnings.push('BotDefinition 没有 memoryName:Memory 页标题回落到 persona.memory 的类名,再缺省是「Memory」。');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const prebuilt = parts.worlds ?? [];
|
|
379
|
+
const ids = new Set<string>();
|
|
380
|
+
for (const world of prebuilt) {
|
|
381
|
+
if (ids.has(world.id)) failures.push(`预建 World id 重复: ${world.id}。`);
|
|
382
|
+
ids.add(world.id);
|
|
383
|
+
}
|
|
384
|
+
if (prebuilt.length) ok.push(`预建 World: ${[...ids].join(', ')}(预建实例重启时复用原对象)。`);
|
|
385
|
+
|
|
386
|
+
for (const group of parts.console?.configGroups ?? []) {
|
|
387
|
+
if (group.owner !== 'persona') warnings.push(`配置组「${group.id}」的 owner 是「${group.owner}」,Persona 的配置组 owner 应为 persona。`);
|
|
388
|
+
}
|
|
389
|
+
return report;
|
|
390
|
+
}
|