@router-agent/infra 0.0.0-stage → 0.3.32
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/dist/chat.d.ts +74 -0
- package/dist/chat.js +236 -0
- package/dist/chat.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +11 -0
- package/dist/logger.js.map +1 -0
- package/dist/plugin-install.d.ts +45 -0
- package/dist/plugin-install.js +240 -0
- package/dist/plugin-install.js.map +1 -0
- package/dist/plugin-loader.d.ts +75 -0
- package/dist/plugin-loader.js +181 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/process.d.ts +20 -0
- package/dist/process.js +46 -0
- package/dist/process.js.map +1 -0
- package/dist/vision.d.ts +45 -0
- package/dist/vision.js +164 -0
- package/dist/vision.js.map +1 -0
- package/package.json +31 -4
- package/README.md +0 -3
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按 npm 包名安装运行时 / 通道插件。
|
|
3
|
+
*
|
|
4
|
+
* 插件加载器以 `<RA_HOME>/config.yaml` 为锚点解析包名,所以插件必须装在
|
|
5
|
+
* `<RA_HOME>/node_modules` 下;本模块负责把配置里声明的 npm 包下载到这里,
|
|
6
|
+
* 并校验装完以后加载器确实能解析到它。
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { mkdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { errorMessage, noopLogger } from './logger.js';
|
|
12
|
+
import { defaultPluginLoader } from './plugin-loader.js';
|
|
13
|
+
const PACKAGE_NAME_PATTERN = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
|
14
|
+
const LOCK_STALE_MS = 10 * 60_000;
|
|
15
|
+
const MAX_LOG_LINES = 200;
|
|
16
|
+
const NPM_TIMEOUT_MS = 180_000;
|
|
17
|
+
/**
|
|
18
|
+
* 从插件入口里取出 npm 包名。
|
|
19
|
+
*
|
|
20
|
+
* 本地路径(`./x` / `/x` / `file:`)、URL 和带空格的写法都返回 undefined;
|
|
21
|
+
* 调用方据此区分「需要安装」与「重启即可生效」。
|
|
22
|
+
*/
|
|
23
|
+
export function npmPackageNameOf(value) {
|
|
24
|
+
const text = value.trim();
|
|
25
|
+
if (!text || text.startsWith('.') || text.startsWith('/') || text.startsWith('file:'))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (text.includes('\\') || /\s/.test(text))
|
|
28
|
+
return undefined;
|
|
29
|
+
return PACKAGE_NAME_PATTERN.test(text) ? text : undefined;
|
|
30
|
+
}
|
|
31
|
+
/** 配置里的版本号:只做最基本的注入防护,具体合法版本交给 npm 校验。 */
|
|
32
|
+
function validVersion(version) {
|
|
33
|
+
const text = version?.trim() || 'latest';
|
|
34
|
+
if (text.startsWith('-') || /\s/.test(text))
|
|
35
|
+
return 'latest';
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
/** npm 全局安装的同一套命令名:Windows 上是 npm.cmd。 */
|
|
39
|
+
export function npmBin() {
|
|
40
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
41
|
+
}
|
|
42
|
+
function appendLog(log, line, onLine) {
|
|
43
|
+
const text = line.trimEnd();
|
|
44
|
+
if (!text)
|
|
45
|
+
return;
|
|
46
|
+
log.push(text);
|
|
47
|
+
if (log.length > MAX_LOG_LINES)
|
|
48
|
+
log.splice(0, log.length - MAX_LOG_LINES);
|
|
49
|
+
onLine?.(text);
|
|
50
|
+
}
|
|
51
|
+
function defaultRunNpm(args, opts) {
|
|
52
|
+
return new Promise(resolvePromise => {
|
|
53
|
+
let settled = false;
|
|
54
|
+
let timedOut = false;
|
|
55
|
+
const child = spawn(npmBin(), args, {
|
|
56
|
+
cwd: opts.cwd,
|
|
57
|
+
env: { ...process.env, ...opts.env },
|
|
58
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
59
|
+
detached: process.platform !== 'win32',
|
|
60
|
+
});
|
|
61
|
+
const finish = (code) => {
|
|
62
|
+
if (settled)
|
|
63
|
+
return;
|
|
64
|
+
settled = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
resolvePromise(code);
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
timedOut = true;
|
|
70
|
+
try {
|
|
71
|
+
if (process.platform === 'win32') {
|
|
72
|
+
child.kill('SIGKILL');
|
|
73
|
+
}
|
|
74
|
+
else if (child.pid) {
|
|
75
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// 进程可能已经退出,忽略即可。
|
|
80
|
+
}
|
|
81
|
+
finish(124);
|
|
82
|
+
}, opts.timeoutMs);
|
|
83
|
+
const pipe = (stream) => {
|
|
84
|
+
let buffer = '';
|
|
85
|
+
stream.setEncoding('utf8');
|
|
86
|
+
stream.on('data', (chunk) => {
|
|
87
|
+
buffer += chunk;
|
|
88
|
+
let index;
|
|
89
|
+
while ((index = buffer.indexOf('\n')) >= 0) {
|
|
90
|
+
opts.onLine(buffer.slice(0, index));
|
|
91
|
+
buffer = buffer.slice(index + 1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
stream.on('end', () => {
|
|
95
|
+
if (buffer)
|
|
96
|
+
opts.onLine(buffer);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
pipe(child.stdout);
|
|
100
|
+
pipe(child.stderr);
|
|
101
|
+
child.on('error', error => {
|
|
102
|
+
opts.onLine(`${error.message}${timedOut ? '(安装超时)' : ''}`);
|
|
103
|
+
finish(127);
|
|
104
|
+
});
|
|
105
|
+
child.on('close', code => finish(code ?? (timedOut ? 124 : 1)));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** 安装锁目录按包名区分;同名包不允许并发写入同一个 node_modules。 */
|
|
109
|
+
function lockDirOf(home, packageName) {
|
|
110
|
+
return join(home, '.plugin-install', `${packageName.replace(/[^a-z0-9-_.]/gi, '_')}.lock`);
|
|
111
|
+
}
|
|
112
|
+
function lockIsStale(path) {
|
|
113
|
+
try {
|
|
114
|
+
const info = JSON.parse(readFileSync(join(path, 'lock.json'), 'utf8'));
|
|
115
|
+
const startedAt = info.startedAt ? Date.parse(info.startedAt) : statSync(path).mtimeMs;
|
|
116
|
+
return !Number.isFinite(startedAt) || Date.now() - startedAt > LOCK_STALE_MS;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// 锁目录存在但没有 lock.json:按目录的 mtime 判断是否陈旧。
|
|
120
|
+
try {
|
|
121
|
+
return Date.now() - statSync(path).mtimeMs > LOCK_STALE_MS;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function acquireLock(home, packageName) {
|
|
129
|
+
mkdirSync(join(home, '.plugin-install'), { recursive: true });
|
|
130
|
+
const path = lockDirOf(home, packageName);
|
|
131
|
+
try {
|
|
132
|
+
mkdirSync(path);
|
|
133
|
+
return { path };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
if (lockIsStale(path)) {
|
|
137
|
+
try {
|
|
138
|
+
rmSync(path, { recursive: true, force: true });
|
|
139
|
+
mkdirSync(path);
|
|
140
|
+
return { path };
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// 抢锁失败,继续走「已有安装任务」分支。
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { path, error: `已有 ${packageName} 安装任务在进行` };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function releaseLock(path) {
|
|
150
|
+
rmSync(path, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
/** 读已安装包的版本;没装或 package.json 损坏时返回 null。 */
|
|
153
|
+
export function installedPluginVersion(home, packageName) {
|
|
154
|
+
try {
|
|
155
|
+
const pkg = JSON.parse(readFileSync(join(home, 'node_modules', packageName, 'package.json'), 'utf8'));
|
|
156
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// 目录不存在 / 只装了一半 / JSON 损坏都按未安装处理。
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** 最近几行 npm 输出用于日志 detail:不写完整 stderr,避免日志过大。 */
|
|
164
|
+
function failureDetail(log, code) {
|
|
165
|
+
const tail = log.filter(line => line.trim()).slice(-3).join(' | ');
|
|
166
|
+
const text = code === 124 ? 'npm install 超时' : `npm install 退出码 ${code}`;
|
|
167
|
+
return tail ? `${text}:${tail.slice(0, 300)}` : text;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 把 npm 包安装到 `<RA_HOME>/node_modules`。
|
|
171
|
+
*
|
|
172
|
+
* 安装完成后会用插件加载器同一套解析逻辑确认包能被找到;解析不到的包
|
|
173
|
+
* 即使用 npm 装上了,服务重启时也加载不了,所以这里按失败返回。
|
|
174
|
+
*/
|
|
175
|
+
export async function installPluginPackage(opts) {
|
|
176
|
+
const log = [];
|
|
177
|
+
const logger = opts.logger ?? noopLogger;
|
|
178
|
+
const packageName = npmPackageNameOf(opts.packageName);
|
|
179
|
+
if (!packageName) {
|
|
180
|
+
return { ok: false, log, error: `不是合法的 npm 包名:${opts.packageName}` };
|
|
181
|
+
}
|
|
182
|
+
const version = validVersion(opts.version);
|
|
183
|
+
const lock = acquireLock(opts.home, packageName);
|
|
184
|
+
if (lock.error)
|
|
185
|
+
return { ok: false, log, error: lock.error };
|
|
186
|
+
logger.info('plugin:install-started', { home: opts.home, package: packageName, version });
|
|
187
|
+
try {
|
|
188
|
+
mkdirSync(opts.home, { recursive: true });
|
|
189
|
+
appendLog(log, `安装 ${packageName}@${version} → ${join(opts.home, 'node_modules')}`, opts.onLine);
|
|
190
|
+
const run = opts.runNpm ?? defaultRunNpm;
|
|
191
|
+
const args = [
|
|
192
|
+
'install',
|
|
193
|
+
'--prefix', opts.home,
|
|
194
|
+
'--no-save',
|
|
195
|
+
'--no-package-lock',
|
|
196
|
+
'--no-audit',
|
|
197
|
+
'--no-fund',
|
|
198
|
+
'--ignore-scripts',
|
|
199
|
+
`${packageName}@${version}`,
|
|
200
|
+
];
|
|
201
|
+
const code = await run(args, {
|
|
202
|
+
cwd: opts.home,
|
|
203
|
+
env: opts.env ?? {},
|
|
204
|
+
timeoutMs: opts.timeoutMs ?? NPM_TIMEOUT_MS,
|
|
205
|
+
onLine: line => appendLog(log, line, opts.onLine),
|
|
206
|
+
});
|
|
207
|
+
if (code !== 0) {
|
|
208
|
+
const error = failureDetail(log, code);
|
|
209
|
+
logger.error('plugin:install-failed', { home: opts.home, package: packageName, version, detail: error });
|
|
210
|
+
return { ok: false, log, error };
|
|
211
|
+
}
|
|
212
|
+
const installedVersion = installedPluginVersion(opts.home, packageName);
|
|
213
|
+
if (!installedVersion) {
|
|
214
|
+
const error = `安装完成但 ${join(opts.home, 'node_modules', packageName)} 不存在`;
|
|
215
|
+
logger.error('plugin:install-failed', { home: opts.home, package: packageName, version, detail: error });
|
|
216
|
+
return { ok: false, log, error };
|
|
217
|
+
}
|
|
218
|
+
// 与插件加载器完全相同的解析路径:解析不到就等于重启后加载失败。
|
|
219
|
+
try {
|
|
220
|
+
defaultPluginLoader().resolve(packageName, resolve(opts.home, 'config.yaml'));
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const detail = `安装完成但加载器解析不到 ${packageName}:${errorMessage(error)}`.slice(0, 300);
|
|
224
|
+
logger.error('plugin:install-failed', { home: opts.home, package: packageName, version, detail });
|
|
225
|
+
return { ok: false, log: [...log, detail], error: detail };
|
|
226
|
+
}
|
|
227
|
+
appendLog(log, `安装完成:${packageName}@${installedVersion}`, opts.onLine);
|
|
228
|
+
logger.info('plugin:install-succeeded', { home: opts.home, package: packageName, version: installedVersion });
|
|
229
|
+
return { ok: true, version: installedVersion, log };
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
const detail = errorMessage(error);
|
|
233
|
+
logger.error('plugin:install-failed', { home: opts.home, package: packageName, version, detail });
|
|
234
|
+
return { ok: false, log, error: detail };
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
releaseLock(lock.path);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=plugin-install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-install.js","sourceRoot":"","sources":["../../../../packages/infra/src/plugin-install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type Logger } from './logger.js';
|
|
2
|
+
import type { CommandRunner } from './process.js';
|
|
3
|
+
/** 插件在配置里的条目:包名、本地路径或目录。 */
|
|
4
|
+
export type PluginEntry = string | {
|
|
5
|
+
/** npm 包名或本地路径。 */
|
|
6
|
+
package?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
/** 配置别名:与 path 等价。 */
|
|
9
|
+
plugin?: string;
|
|
10
|
+
/** 包内入口,例如 `dist/index.js`。 */
|
|
11
|
+
entry?: string;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export interface PluginMeta {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
hint?: string;
|
|
19
|
+
installHint?: string;
|
|
20
|
+
installUrl?: string;
|
|
21
|
+
installable?: boolean;
|
|
22
|
+
packageName?: string;
|
|
23
|
+
deprecated?: boolean;
|
|
24
|
+
repoSelector?: boolean;
|
|
25
|
+
default?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type PluginKind = 'runtime' | 'channel';
|
|
28
|
+
/** node:module 的 resolve/import 抽象;测试可注入。 */
|
|
29
|
+
export interface PluginModuleLoader {
|
|
30
|
+
resolve(specifier: string, parent?: string): string;
|
|
31
|
+
import(url: string): Promise<unknown>;
|
|
32
|
+
}
|
|
33
|
+
/** 一次插件加载失败;面板与日志都要能看到「哪个包、为什么」。 */
|
|
34
|
+
export interface PluginFailure {
|
|
35
|
+
kind: PluginKind;
|
|
36
|
+
id: string;
|
|
37
|
+
specifier: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
export interface PluginLoadResult<T> {
|
|
41
|
+
plugins: T[];
|
|
42
|
+
/** 插件与其在配置里的 id 一一对应;插件声明的 meta.id 可能不同。 */
|
|
43
|
+
entries: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
plugin: T;
|
|
46
|
+
}>;
|
|
47
|
+
failures: PluginFailure[];
|
|
48
|
+
}
|
|
49
|
+
export interface PluginLoadOptions {
|
|
50
|
+
kind: PluginKind;
|
|
51
|
+
home: string;
|
|
52
|
+
specifierOf(id: string): string | undefined;
|
|
53
|
+
/** 包内入口,例如 dist/index.js;目录型插件用它选具体文件。 */
|
|
54
|
+
entryOf?(id: string): string | undefined;
|
|
55
|
+
ids: readonly string[];
|
|
56
|
+
loader?: PluginModuleLoader;
|
|
57
|
+
logger: Logger;
|
|
58
|
+
}
|
|
59
|
+
export declare function pluginSpecifier(entry: PluginEntry | undefined): string | undefined;
|
|
60
|
+
export declare function pluginEntryTarget(entry: PluginEntry | undefined): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* 解析插件入口。
|
|
63
|
+
*
|
|
64
|
+
* 支持裸包名、绝对路径、相对 home 的路径、目录和 `entry`。目录优先读 package.json
|
|
65
|
+
* 的 `exports` / `module` / `main`,再退回 index.js / index.mjs。
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolvePluginSpecifier(specifier: string, home: string, loader: PluginModuleLoader, entry?: string): string;
|
|
68
|
+
export declare function resolvePluginPath(path: string, entry?: string): string;
|
|
69
|
+
export declare function defaultPluginLoader(): PluginModuleLoader;
|
|
70
|
+
export declare function loadPluginModules<T extends {
|
|
71
|
+
kind: PluginKind;
|
|
72
|
+
}>(opts: PluginLoadOptions): Promise<PluginLoadResult<T>>;
|
|
73
|
+
export declare function importPluginModule<T>(specifier: string, home: string, loader?: PluginModuleLoader, entry?: string): Promise<T>;
|
|
74
|
+
/** 插件自行创建 runner 时用;宿主不假设具体 CLI。 */
|
|
75
|
+
export declare function throwRunner(kind: PluginKind, bin: string): CommandRunner;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { errorMessage } from './logger.js';
|
|
6
|
+
export function pluginSpecifier(entry) {
|
|
7
|
+
if (!entry)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (typeof entry === 'string')
|
|
10
|
+
return entry.trim() || undefined;
|
|
11
|
+
if (entry.enabled === false)
|
|
12
|
+
return undefined;
|
|
13
|
+
return (entry.plugin ?? entry.path ?? entry.package)?.trim() || undefined;
|
|
14
|
+
}
|
|
15
|
+
export function pluginEntryTarget(entry) {
|
|
16
|
+
if (typeof entry === 'string')
|
|
17
|
+
return entry.trim() || undefined;
|
|
18
|
+
return entry?.entry?.trim() || undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 解析插件入口。
|
|
22
|
+
*
|
|
23
|
+
* 支持裸包名、绝对路径、相对 home 的路径、目录和 `entry`。目录优先读 package.json
|
|
24
|
+
* 的 `exports` / `module` / `main`,再退回 index.js / index.mjs。
|
|
25
|
+
*/
|
|
26
|
+
export function resolvePluginSpecifier(specifier, home, loader, entry) {
|
|
27
|
+
if (specifier.startsWith('file://'))
|
|
28
|
+
return specifier;
|
|
29
|
+
if (isAbsolute(specifier) || specifier.startsWith('.')) {
|
|
30
|
+
const base = isAbsolute(specifier) ? specifier : resolve(home, specifier);
|
|
31
|
+
return pathToFileURL(resolvePluginPath(base, entry)).href;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const resolved = loader.resolve(specifier, resolve(home, 'config.yaml'));
|
|
35
|
+
return pathToFileURL(entry ? resolvePluginPath(resolved, entry) : resolved).href;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// 裸包名解析失败时保留原名,让 import 报出真实的「找不到包」;
|
|
39
|
+
// 这里不能静默吞掉,否则面板只会看到一条没有包名的错误。
|
|
40
|
+
return specifier;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function resolvePluginPath(path, entry) {
|
|
44
|
+
if (!existsSync(path))
|
|
45
|
+
return path;
|
|
46
|
+
if (!statSync(path).isDirectory())
|
|
47
|
+
return path;
|
|
48
|
+
if (entry) {
|
|
49
|
+
const candidate = join(path, entry);
|
|
50
|
+
if (existsSync(candidate))
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
const manifest = join(path, 'package.json');
|
|
54
|
+
if (existsSync(manifest)) {
|
|
55
|
+
try {
|
|
56
|
+
const pkg = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
57
|
+
const exported = exportTarget(pkg.exports);
|
|
58
|
+
for (const value of [exported, pkg.module, pkg.main]) {
|
|
59
|
+
if (typeof value !== 'string' || !value.trim())
|
|
60
|
+
continue;
|
|
61
|
+
const candidate = join(path, value.trim());
|
|
62
|
+
if (existsSync(candidate))
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// package.json 坏了就继续按目录默认入口找,最后的加载失败会带出真实原因。
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const name of ['index.js', 'index.mjs', 'index.cjs']) {
|
|
71
|
+
const candidate = join(path, name);
|
|
72
|
+
if (existsSync(candidate))
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
function exportTarget(exports) {
|
|
78
|
+
if (typeof exports === 'string')
|
|
79
|
+
return exports;
|
|
80
|
+
if (!exports || typeof exports !== 'object' || Array.isArray(exports))
|
|
81
|
+
return undefined;
|
|
82
|
+
const root = exports['.'];
|
|
83
|
+
if (typeof root === 'string')
|
|
84
|
+
return root;
|
|
85
|
+
if (root && typeof root === 'object' && !Array.isArray(root)) {
|
|
86
|
+
const record = root;
|
|
87
|
+
for (const key of ['import', 'node', 'default']) {
|
|
88
|
+
if (typeof record[key] === 'string')
|
|
89
|
+
return record[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
export function defaultPluginLoader() {
|
|
95
|
+
return {
|
|
96
|
+
resolve(specifier, parent) {
|
|
97
|
+
const require = createRequire(parent ? pathToFileURL(parent) : import.meta.url);
|
|
98
|
+
try {
|
|
99
|
+
return require.resolve(specifier);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// ESM-only 包(exports 只有 import 条件)无法被 require.resolve 解析,
|
|
103
|
+
// 手动按包名定位目录后再交给默认目录入口解析。
|
|
104
|
+
const fallback = resolveEsmOnlyPackage(specifier, parent, error);
|
|
105
|
+
if (fallback)
|
|
106
|
+
return resolvePluginPath(fallback);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async import(url) {
|
|
111
|
+
return import(url);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 沿着 parent 所在目录向上找工作区 / 全局的 `node_modules/<specifier>`。
|
|
117
|
+
*
|
|
118
|
+
* RA_HOME 下的插件就是这种布局;只有 require.resolve 因 ESM-only exports
|
|
119
|
+
* 失败时才走这里,找不到就返回 null,让调用方保留原始错误。
|
|
120
|
+
*/
|
|
121
|
+
function resolveEsmOnlyPackage(specifier, parent, error) {
|
|
122
|
+
const code = error?.code;
|
|
123
|
+
if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && code !== 'ERR_MODULE_NOT_FOUND')
|
|
124
|
+
return null;
|
|
125
|
+
const parts = specifier.split('/');
|
|
126
|
+
const name = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
127
|
+
const start = parent ? dirname(parent) : dirname(fileURLToPath(import.meta.url));
|
|
128
|
+
for (let dir = start;; dir = dirname(dir)) {
|
|
129
|
+
const candidate = join(dir, 'node_modules', name);
|
|
130
|
+
if (existsSync(candidate))
|
|
131
|
+
return candidate;
|
|
132
|
+
if (dirname(dir) === dir)
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export async function loadPluginModules(opts) {
|
|
137
|
+
const loader = opts.loader ?? defaultPluginLoader();
|
|
138
|
+
const ids = [...new Set(opts.ids)];
|
|
139
|
+
const plugins = [];
|
|
140
|
+
const entries = [];
|
|
141
|
+
const failures = [];
|
|
142
|
+
for (const id of ids) {
|
|
143
|
+
const specifier = opts.specifierOf(id);
|
|
144
|
+
if (!specifier)
|
|
145
|
+
continue;
|
|
146
|
+
try {
|
|
147
|
+
const target = resolvePluginSpecifier(specifier, opts.home, loader, opts.entryOf?.(id));
|
|
148
|
+
const mod = await loader.import(target);
|
|
149
|
+
const plugin = mod.default ?? mod;
|
|
150
|
+
if (!plugin || typeof plugin !== 'object' || plugin.kind !== opts.kind) {
|
|
151
|
+
throw new Error(`插件 ${specifier} 没有导出 kind=${opts.kind} 的默认对象`);
|
|
152
|
+
}
|
|
153
|
+
plugins.push(plugin);
|
|
154
|
+
entries.push({ id, plugin });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const message = errorMessage(error);
|
|
158
|
+
failures.push({ kind: opts.kind, id, specifier, message });
|
|
159
|
+
opts.logger.error('plugin:load-failed', {
|
|
160
|
+
kind: opts.kind,
|
|
161
|
+
id,
|
|
162
|
+
package: specifier,
|
|
163
|
+
detail: `${opts.kind} 插件 ${id} 加载失败:${message}`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { plugins, entries, failures };
|
|
168
|
+
}
|
|
169
|
+
export async function importPluginModule(specifier, home, loader = defaultPluginLoader(), entry) {
|
|
170
|
+
const target = resolvePluginSpecifier(specifier, home, loader, entry);
|
|
171
|
+
return await loader.import(target);
|
|
172
|
+
}
|
|
173
|
+
/** 插件自行创建 runner 时用;宿主不假设具体 CLI。 */
|
|
174
|
+
export function throwRunner(kind, bin) {
|
|
175
|
+
return {
|
|
176
|
+
async run() {
|
|
177
|
+
throw new Error(`${kind} 插件未注入 ${bin} 运行器`);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../../../packages/infra/src/plugin-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CommandResult {
|
|
2
|
+
code: number;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
/** 被信号终止时的信号名(SIGKILL 等);正常退出为 null。 */
|
|
6
|
+
signal?: string | null;
|
|
7
|
+
/** 是否因为超时被 kill —— 与「命令自己报错退出」要区分开。 */
|
|
8
|
+
timedOut?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** argv 不含二进制名:runner 已绑定到某个可执行文件。 */
|
|
11
|
+
export interface CommandRunner {
|
|
12
|
+
run(argv: string[], opts?: {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
input?: string;
|
|
17
|
+
}): Promise<CommandResult>;
|
|
18
|
+
}
|
|
19
|
+
/** 用 child_process 执行绑定到 `bin` 的命令。 */
|
|
20
|
+
export declare function realRunner(bin: string): CommandRunner;
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/** 用 child_process 执行绑定到 `bin` 的命令。 */
|
|
3
|
+
export function realRunner(bin) {
|
|
4
|
+
return {
|
|
5
|
+
run(argv, opts = {}) {
|
|
6
|
+
return new Promise(resolve => {
|
|
7
|
+
const child = spawn(bin, argv, {
|
|
8
|
+
cwd: opts.cwd,
|
|
9
|
+
env: opts.env ?? process.env,
|
|
10
|
+
stdio: [opts.input === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
|
|
11
|
+
});
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
let timedOut = false;
|
|
15
|
+
const timer = opts.timeoutMs
|
|
16
|
+
? setTimeout(() => {
|
|
17
|
+
timedOut = true;
|
|
18
|
+
child.kill('SIGKILL');
|
|
19
|
+
}, opts.timeoutMs)
|
|
20
|
+
: null;
|
|
21
|
+
if (opts.input !== undefined) {
|
|
22
|
+
child.stdin?.end(opts.input);
|
|
23
|
+
}
|
|
24
|
+
child.stdout.on('data', chunk => { stdout += String(chunk); });
|
|
25
|
+
child.stderr.on('data', chunk => { stderr += String(chunk); });
|
|
26
|
+
child.on('error', error => {
|
|
27
|
+
if (timer)
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
resolve({ code: 127, stdout, stderr: `${stderr}${error.message}`, signal: null, timedOut });
|
|
30
|
+
});
|
|
31
|
+
child.on('close', code => {
|
|
32
|
+
if (timer)
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolve({
|
|
35
|
+
code: code ?? 1,
|
|
36
|
+
stdout,
|
|
37
|
+
stderr,
|
|
38
|
+
signal: child.signalCode ?? null,
|
|
39
|
+
timedOut,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../packages/infra/src/process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAiB1C,uCAAuC;AACvC,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO;QACL,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE;YACjB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;gBAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;oBAC7B,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;oBAC5B,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;iBACtE,CAAC,CAAA;gBACF,IAAI,MAAM,GAAG,EAAE,CAAA;gBACf,IAAI,MAAM,GAAG,EAAE,CAAA;gBACf,IAAI,QAAQ,GAAG,KAAK,CAAA;gBACpB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS;oBAC1B,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;wBACd,QAAQ,GAAG,IAAI,CAAA;wBACf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;oBACvB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;oBACpB,CAAC,CAAC,IAAI,CAAA;gBACR,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC7B,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC9B,CAAC;gBACD,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;gBAC9D,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;gBAC9D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;oBACxB,IAAI,KAAK;wBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;oBAC9B,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;gBAC7F,CAAC,CAAC,CAAA;gBACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;oBACvB,IAAI,KAAK;wBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;oBAC9B,OAAO,CAAC;wBACN,IAAI,EAAE,IAAI,IAAI,CAAC;wBACf,MAAM;wBACN,MAAM;wBACN,MAAM,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;wBAChC,QAAQ;qBACT,CAAC,CAAA;gBACJ,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/vision.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片转文字(多模态模型)的基础设施。
|
|
3
|
+
*
|
|
4
|
+
* 只依赖 OpenAI 兼容的 `/chat/completions` 接口,不引入新 SDK:
|
|
5
|
+
* 飞书图片先由消息层下载成 base64,再交给这里转成可进入研判的文本。
|
|
6
|
+
*/
|
|
7
|
+
export interface ImageInput {
|
|
8
|
+
/** base64 编码(不含 `data:` 前缀)。 */
|
|
9
|
+
data: string;
|
|
10
|
+
/** 如 image/png、image/jpeg。 */
|
|
11
|
+
mimeType: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ImageTranscriber {
|
|
14
|
+
transcribe(image: ImageInput): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
export interface VisionModelConfig {
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
apiKey: string;
|
|
19
|
+
model: string;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface VisionOptions {
|
|
23
|
+
/** 识别提示词;不传用内置的「转写 + 描述」。 */
|
|
24
|
+
prompt?: string;
|
|
25
|
+
/** 单张图片的硬超时,默认 60s。 */
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
/** 转写结果的最大字符数,超出截断。 */
|
|
28
|
+
maxChars?: number;
|
|
29
|
+
/** 输出 token 上限,默认 1500。 */
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
/** 测试注入点。 */
|
|
32
|
+
fetchImpl?: typeof fetch;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_VISION_PROMPT: string;
|
|
35
|
+
/** 把 OpenAI 兼容的 baseUrl 规范成 chat/completions 地址。 */
|
|
36
|
+
export declare function chatCompletionsUrl(baseUrl: string): string;
|
|
37
|
+
/** 从 `chat/completions` 的返回里取 assistant 文本;兼容字符串与分段数组。 */
|
|
38
|
+
export declare function parseChatCompletionText(payload: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* 创建视觉转写器。
|
|
41
|
+
*
|
|
42
|
+
* 结构性失败(模型不支持图片、接口不存在)会熔断:同一进程里后续图片直接快速失败,
|
|
43
|
+
* 避免每张图都等一次超时、刷一屏同样的错误。
|
|
44
|
+
*/
|
|
45
|
+
export declare function createVisionTranscriber(model: VisionModelConfig, opts?: VisionOptions): ImageTranscriber;
|