@infimind-next/ai-hive-mcp 0.4.1 → 0.5.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/README.md +20 -8
- package/dist/api-client.d.ts +8 -2
- package/dist/api-client.js +64 -16
- package/dist/config.d.ts +6 -0
- package/dist/config.js +47 -0
- package/dist/index.js +0 -0
- package/dist/managed-config.js +5 -4
- package/dist/managed-download.d.ts +5 -0
- package/dist/managed-download.js +18 -12
- package/dist/managed-files.d.ts +25 -1
- package/dist/managed-files.js +98 -26
- package/dist/managed-images.d.ts +65 -21
- package/dist/managed-images.js +148 -60
- package/dist/managed-server.js +71 -36
- package/dist/model-response.d.ts +831 -0
- package/dist/model-response.js +296 -0
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +1 -0
- package/dist/rest-commands.js +33 -12
- package/dist/schemas.d.ts +14 -7
- package/dist/schemas.js +3 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.js +29 -17
- package/dist/setup/run-setup.js +126 -7
- package/dist/setup/targets.js +2 -1
- package/dist/user-notices.d.ts +44 -0
- package/dist/user-notices.js +39 -21
- package/package.json +8 -9
- package/skills/ai-hive-billing/SKILL.md +2 -0
- package/skills/ai-hive-image-video-generation/SKILL.md +2 -0
- package/skills/ai-hive-media-upload/SKILL.md +2 -0
- package/skills/ai-hive-model-selection/SKILL.md +7 -4
- package/skills/ai-hive-text-generation/SKILL.md +3 -1
package/dist/managed-files.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, mkdir, open, realpath, stat } from "node:fs/promises";
|
|
4
|
-
import { basename, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileTypeFromBuffer } from "file-type";
|
|
6
6
|
export const MAX_MANAGED_IMAGE_BYTES = 30 * 1024 * 1024;
|
|
7
|
+
export const MAX_MANAGED_VIDEO_BYTES = 200 * 1024 * 1024;
|
|
8
|
+
// MCP 服务端单文件上传上限是 100MiB; 本地先拦一次, 避免白传.
|
|
9
|
+
const MAX_MANAGED_VIDEO_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
7
10
|
const IMAGE_TYPES = new Set([
|
|
8
11
|
"image/png",
|
|
9
12
|
"image/jpeg",
|
|
@@ -15,7 +18,8 @@ const IMAGE_TYPES = new Set([
|
|
|
15
18
|
"image/heif",
|
|
16
19
|
"image/avif",
|
|
17
20
|
]);
|
|
18
|
-
const
|
|
21
|
+
const VIDEO_TYPES = new Set(["video/mp4", "video/webm", "video/quicktime"]);
|
|
22
|
+
const PATH_ERROR = "文件不在已授权范围,请从 AI-HIVE 启动器选择文件";
|
|
19
23
|
const OUTPUT_ERROR = "结果目录已变化,请从 AI-HIVE 启动器重新打开项目";
|
|
20
24
|
export function safeFilename(value) {
|
|
21
25
|
if (!value ||
|
|
@@ -25,7 +29,7 @@ export function safeFilename(value) {
|
|
|
25
29
|
/[\\/:\x00-\x1f\x7f]/.test(value) ||
|
|
26
30
|
/[. ]$/.test(value) ||
|
|
27
31
|
/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(value)) {
|
|
28
|
-
throw new Error("
|
|
32
|
+
throw new Error("请使用结果文件名,媒体将保存在项目的 ai-hive-media 文件夹");
|
|
29
33
|
}
|
|
30
34
|
return value;
|
|
31
35
|
}
|
|
@@ -39,6 +43,19 @@ export async function inspectImage(bytes) {
|
|
|
39
43
|
}
|
|
40
44
|
return detected;
|
|
41
45
|
}
|
|
46
|
+
export async function inspectVideo(bytes) {
|
|
47
|
+
if (!bytes.byteLength || bytes.byteLength > MAX_MANAGED_VIDEO_BYTES) {
|
|
48
|
+
throw new Error("视频大小超过限制,最大 200MiB");
|
|
49
|
+
}
|
|
50
|
+
const detected = await fileTypeFromBuffer(bytes).catch(() => undefined);
|
|
51
|
+
if (!detected || !VIDEO_TYPES.has(detected.mime)) {
|
|
52
|
+
throw new Error("暂不支持这个视频,请选择 MP4、WebM 或 MOV 文件");
|
|
53
|
+
}
|
|
54
|
+
return detected;
|
|
55
|
+
}
|
|
56
|
+
export function inspectMedia(bytes, kind) {
|
|
57
|
+
return kind === "VIDEO" ? inspectVideo(bytes) : inspectImage(bytes);
|
|
58
|
+
}
|
|
42
59
|
// 子进程的 cwd 由操作系统固定到目录 inode; 文件写入使用相对路径,
|
|
43
60
|
// 防止校验后父目录被替换导致写入目录外. 不把任何账户密钥传给写入进程.
|
|
44
61
|
const ATOMIC_WRITE_SCRIPT = `
|
|
@@ -47,7 +64,7 @@ const [name,dev,ino,overwrite]=process.argv.slice(1);
|
|
|
47
64
|
const current=fs.statSync('.');
|
|
48
65
|
if(String(current.dev)!==dev||String(current.ino)!==ino){process.stdout.write('DIRECTORY_CHANGED');process.exit(1)}
|
|
49
66
|
let temp='.hive-'+crypto.randomUUID()+'.tmp';let chunks=[];let total=0;
|
|
50
|
-
process.stdin.on('data',chunk=>{total+=chunk.length;if(total>
|
|
67
|
+
process.stdin.on('data',chunk=>{total+=chunk.length;if(total>209715200){process.exit(1)}chunks.push(chunk)});
|
|
51
68
|
process.stdin.on('end',()=>{try{
|
|
52
69
|
const fd=fs.openSync(temp,'wx',0o600);try{fs.writeFileSync(fd,Buffer.concat(chunks));fs.fsyncSync(fd)}finally{fs.closeSync(fd)}
|
|
53
70
|
if(overwrite==='true'){try{if(fs.lstatSync(name).isSymbolicLink())throw Error('SYMLINK')}catch(error){if(error.code!=='ENOENT')throw error}fs.renameSync(temp,name)}
|
|
@@ -90,7 +107,7 @@ export class ManagedDirectory {
|
|
|
90
107
|
async writeAtomic(filename, bytes, overwrite = false) {
|
|
91
108
|
safeFilename(filename);
|
|
92
109
|
await this.assertUnchanged();
|
|
93
|
-
if (bytes.byteLength >
|
|
110
|
+
if (bytes.byteLength > MAX_MANAGED_VIDEO_BYTES)
|
|
94
111
|
throw new Error("文件过大,请缩小后再试");
|
|
95
112
|
await new Promise((resolve, reject) => {
|
|
96
113
|
const child = spawn(process.execPath, [
|
|
@@ -144,32 +161,61 @@ export class ManagedFiles {
|
|
|
144
161
|
projectRoot;
|
|
145
162
|
projectIdentity;
|
|
146
163
|
extra;
|
|
164
|
+
outputDirectory;
|
|
147
165
|
output;
|
|
148
|
-
constructor(projectRoot, projectIdentity, extra,
|
|
166
|
+
constructor(projectRoot, projectIdentity, extra, outputDirectory) {
|
|
149
167
|
this.projectRoot = projectRoot;
|
|
150
168
|
this.projectIdentity = projectIdentity;
|
|
151
169
|
this.extra = extra;
|
|
152
|
-
this.
|
|
170
|
+
this.outputDirectory = outputDirectory;
|
|
153
171
|
}
|
|
154
172
|
static async create(input) {
|
|
155
173
|
const projectRoot = await realpath(input.projectRoot);
|
|
156
174
|
const information = await stat(projectRoot);
|
|
157
175
|
if (!information.isDirectory())
|
|
158
176
|
throw new Error(PATH_ERROR);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
// 项目根可能经过 /var -> /private/var 之类的 canonicalize; 输出目录必须按
|
|
178
|
+
// 原始项目根解析出相对名, 再拼到 canonical 根下.
|
|
179
|
+
const outputName = relative(resolve(input.projectRoot), resolve(input.outputDirectory));
|
|
180
|
+
if (outputName !== "ai-hive-media" &&
|
|
181
|
+
// 旧实例仍在用图片目录; 新配置统一写 ai-hive-media.
|
|
182
|
+
outputName !== "ai-hive-images")
|
|
162
183
|
throw new Error(OUTPUT_ERROR);
|
|
184
|
+
const outputDirectory = join(projectRoot, outputName);
|
|
163
185
|
const extra = new Set();
|
|
164
186
|
for (const path of input.extraImagePaths) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
try {
|
|
188
|
+
const canonical = await realpath(path);
|
|
189
|
+
if ((await stat(canonical)).isFile())
|
|
190
|
+
extra.add(canonical);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// 配置后文件被移走时跳过; 具体读取会返回授权范围错误, 不影响其他工具启动.
|
|
194
|
+
}
|
|
169
195
|
}
|
|
170
|
-
return new ManagedFiles(projectRoot, { dev: information.dev, ino: information.ino }, extra,
|
|
196
|
+
return new ManagedFiles(projectRoot, { dev: information.dev, ino: information.ino }, extra, outputDirectory);
|
|
171
197
|
}
|
|
172
|
-
async
|
|
198
|
+
async mediaOutput() {
|
|
199
|
+
if (this.output)
|
|
200
|
+
return this.output;
|
|
201
|
+
// 启动后项目根可能被删除或替换成符号链接; 懒创建前必须重新核对身份,
|
|
202
|
+
// 否则 mkdir -p 会跟随新根, 把生成结果写到项目外.
|
|
203
|
+
await this.assertProjectRoot();
|
|
204
|
+
const created = await ManagedDirectory.create(this.outputDirectory);
|
|
205
|
+
await this.assertProjectRoot();
|
|
206
|
+
this.output = created;
|
|
207
|
+
return created;
|
|
208
|
+
}
|
|
209
|
+
async assertProjectRoot() {
|
|
210
|
+
const project = await stat(this.projectRoot).catch(() => {
|
|
211
|
+
throw new Error(PATH_ERROR);
|
|
212
|
+
});
|
|
213
|
+
if (project.dev !== this.projectIdentity.dev ||
|
|
214
|
+
project.ino !== this.projectIdentity.ino ||
|
|
215
|
+
(await realpath(this.projectRoot).catch(() => "")) !== this.projectRoot)
|
|
216
|
+
throw new Error(PATH_ERROR);
|
|
217
|
+
}
|
|
218
|
+
async readMedia(path, kind) {
|
|
173
219
|
if (!isAbsolute(path))
|
|
174
220
|
throw new Error(PATH_ERROR);
|
|
175
221
|
const project = await stat(this.projectRoot);
|
|
@@ -197,22 +243,26 @@ export class ManagedFiles {
|
|
|
197
243
|
before.ino !== information.ino ||
|
|
198
244
|
(await realpath(path)) !== canonical)
|
|
199
245
|
throw new Error(PATH_ERROR);
|
|
200
|
-
|
|
201
|
-
|
|
246
|
+
const limit = kind === "VIDEO"
|
|
247
|
+
? MAX_MANAGED_VIDEO_UPLOAD_BYTES
|
|
248
|
+
: MAX_MANAGED_IMAGE_BYTES;
|
|
249
|
+
const label = kind === "VIDEO" ? "视频" : "图片";
|
|
250
|
+
if (information.size > limit)
|
|
251
|
+
throw new Error(`${label}大小超过限制,最大 ${Math.floor(limit / 1024 / 1024)}MiB`);
|
|
202
252
|
const bytes = Buffer.alloc(information.size);
|
|
203
253
|
let offset = 0;
|
|
204
254
|
while (offset < bytes.length) {
|
|
205
255
|
const read = await file.read(bytes, offset, bytes.length - offset, offset);
|
|
206
256
|
if (!read.bytesRead)
|
|
207
|
-
throw new Error(
|
|
257
|
+
throw new Error(`${label}已变化,请重新选择`);
|
|
208
258
|
offset += read.bytesRead;
|
|
209
259
|
}
|
|
210
260
|
const after = await file.stat();
|
|
211
261
|
if (after.size !== information.size ||
|
|
212
262
|
after.mtimeMs !== information.mtimeMs ||
|
|
213
263
|
after.ctimeMs !== information.ctimeMs)
|
|
214
|
-
throw new Error(
|
|
215
|
-
const detected = await
|
|
264
|
+
throw new Error(`${label}已变化,请重新选择`);
|
|
265
|
+
const detected = await inspectMedia(bytes, kind);
|
|
216
266
|
return {
|
|
217
267
|
filename: basename(path),
|
|
218
268
|
contentType: detected.mime,
|
|
@@ -224,17 +274,39 @@ export class ManagedFiles {
|
|
|
224
274
|
await file.close();
|
|
225
275
|
}
|
|
226
276
|
}
|
|
227
|
-
async
|
|
228
|
-
const
|
|
229
|
-
const
|
|
277
|
+
async readMediaAuto(path) {
|
|
278
|
+
const extension = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
279
|
+
const kind = [
|
|
280
|
+
".mp4",
|
|
281
|
+
".webm",
|
|
282
|
+
".mov",
|
|
283
|
+
".m4v",
|
|
284
|
+
].includes(extension)
|
|
285
|
+
? "VIDEO"
|
|
286
|
+
: "IMAGE";
|
|
287
|
+
return this.readMedia(path, kind);
|
|
288
|
+
}
|
|
289
|
+
readImage(path) {
|
|
290
|
+
return this.readMedia(path, "IMAGE");
|
|
291
|
+
}
|
|
292
|
+
async saveMedia(bytes, kind, relativePath) {
|
|
293
|
+
const detected = await inspectMedia(bytes, kind);
|
|
294
|
+
const prefix = kind === "VIDEO" ? "video" : "image";
|
|
295
|
+
const filename = safeFilename(relativePath ?? `${prefix}-${crypto.randomUUID()}.${detected.ext}`);
|
|
230
296
|
const extension = filename
|
|
231
297
|
.slice(filename.lastIndexOf(".") + 1)
|
|
232
298
|
.toLowerCase();
|
|
233
299
|
if (!(extension === detected.ext ||
|
|
234
300
|
(detected.ext === "jpg" && extension === "jpeg") ||
|
|
235
301
|
(detected.ext === "tif" && extension === "tiff")))
|
|
236
|
-
throw new Error("
|
|
237
|
-
|
|
302
|
+
throw new Error(kind === "VIDEO"
|
|
303
|
+
? "文件名的扩展名需要与视频格式一致"
|
|
304
|
+
: "文件名的扩展名需要与图片格式一致");
|
|
305
|
+
const output = await this.mediaOutput();
|
|
306
|
+
const path = await output.writeAtomic(filename, bytes);
|
|
238
307
|
return { path, contentType: detected.mime, sizeBytes: bytes.length };
|
|
239
308
|
}
|
|
309
|
+
saveImage(bytes, relativePath) {
|
|
310
|
+
return this.saveMedia(bytes, "IMAGE", relativePath);
|
|
311
|
+
}
|
|
240
312
|
}
|
package/dist/managed-images.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AiHiveApiClient } from "./api-client.js";
|
|
3
|
+
import { type ModelSection } from "./model-response.js";
|
|
3
4
|
import type { BearerTokenProvider } from "./auth-token-provider.js";
|
|
4
5
|
import type { McpManagedConfig } from "./managed-config.js";
|
|
6
|
+
import { type ManagedMediaKind } from "./managed-files.js";
|
|
5
7
|
export declare const managedPrepareShape: {
|
|
6
8
|
publicModelId: z.ZodString;
|
|
7
9
|
routingMode: z.ZodEnum<["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"]>;
|
|
@@ -10,8 +12,18 @@ export declare const managedPrepareShape: {
|
|
|
10
12
|
imageMediaIds: z.ZodDefault<z.ZodEffects<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodNull]>, string[], string[] | null>>;
|
|
11
13
|
params: z.ZodDefault<z.ZodEffects<z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodNull]>, Record<string, unknown>, Record<string, unknown> | null>>;
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
export declare const managedPrepareVideoShape: {
|
|
16
|
+
publicModelId: z.ZodString;
|
|
17
|
+
routingMode: z.ZodEnum<["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"]>;
|
|
18
|
+
prompt: z.ZodEffects<z.ZodString, string, string>;
|
|
19
|
+
imageMediaIds: z.ZodDefault<z.ZodEffects<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodNull]>, string[], string[] | null>>;
|
|
20
|
+
videoMediaIds: z.ZodDefault<z.ZodEffects<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodNull]>, string[], string[] | null>>;
|
|
21
|
+
firstFrameMediaId: z.ZodOptional<z.ZodString>;
|
|
22
|
+
lastFrameMediaId: z.ZodOptional<z.ZodString>;
|
|
23
|
+
params: z.ZodDefault<z.ZodEffects<z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodNull]>, Record<string, unknown>, Record<string, unknown> | null>>;
|
|
24
|
+
};
|
|
25
|
+
type ImageClient = Pick<AiHiveApiClient, "getUserInfo" | "listModels" | "getModel" | "generateImage" | "generateVideo" | "getGenerationTask" | "createMediaUpload" | "putUpload" | "completeMediaUpload">;
|
|
26
|
+
export declare class ManagedMedia {
|
|
15
27
|
private readonly config;
|
|
16
28
|
private readonly client;
|
|
17
29
|
private readonly tokenProvider;
|
|
@@ -23,38 +35,70 @@ export declare class ManagedImages {
|
|
|
23
35
|
config: McpManagedConfig;
|
|
24
36
|
client: ImageClient;
|
|
25
37
|
tokenProvider: BearerTokenProvider;
|
|
26
|
-
download?: (url: string) => Promise<Uint8Array>;
|
|
27
|
-
}): Promise<
|
|
38
|
+
download?: (url: string, kind: ManagedMediaKind) => Promise<Uint8Array>;
|
|
39
|
+
}): Promise<ManagedMedia>;
|
|
28
40
|
getUserInfo(): Promise<Record<string, unknown>>;
|
|
29
|
-
listModels(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
listModels(rawInput: Record<string, unknown>): Promise<any[] | {
|
|
42
|
+
schemaVersion: 2;
|
|
43
|
+
notices: {
|
|
44
|
+
id: string;
|
|
45
|
+
revision: string;
|
|
46
|
+
kind: "RECHARGE_PROMOTION" | "ANNOUNCEMENT";
|
|
47
|
+
message: string;
|
|
48
|
+
title: string;
|
|
49
|
+
details: string;
|
|
50
|
+
expiresAt: string | null;
|
|
51
|
+
action?: {
|
|
52
|
+
label: string;
|
|
53
|
+
url: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
}[];
|
|
56
|
+
models: {
|
|
57
|
+
modelType: "TEXT" | "IMAGE" | "VIDEO";
|
|
58
|
+
publicModelId: string;
|
|
59
|
+
displayName: string;
|
|
60
|
+
materialMode: "standard" | "certified";
|
|
61
|
+
}[];
|
|
62
|
+
nextCursor: string | null;
|
|
63
|
+
}>;
|
|
64
|
+
getModel(publicModelId: string, routingMode?: string, options?: {
|
|
65
|
+
schemaVersion?: 1 | 2;
|
|
66
|
+
sections?: ModelSection[];
|
|
67
|
+
}): Promise<Record<string, unknown>>;
|
|
33
68
|
upload(path: string): Promise<unknown>;
|
|
34
69
|
prepare(input: unknown): Promise<{
|
|
35
70
|
operationId: `${string}-${string}-${string}-${string}-${string}`;
|
|
36
71
|
clientRequestId: `${string}-${string}-${string}-${string}-${string}`;
|
|
37
72
|
}>;
|
|
73
|
+
prepareVideo(input: unknown): Promise<{
|
|
74
|
+
operationId: `${string}-${string}-${string}-${string}-${string}`;
|
|
75
|
+
clientRequestId: `${string}-${string}-${string}-${string}-${string}`;
|
|
76
|
+
}>;
|
|
77
|
+
private createPrepared;
|
|
38
78
|
generate(operationId: string): Promise<{
|
|
39
79
|
operationId: string;
|
|
40
80
|
}>;
|
|
41
81
|
recover(operationId: string): Promise<{
|
|
42
82
|
operationId: string;
|
|
43
83
|
}>;
|
|
44
|
-
getTask(taskId: string): Promise<Record<string, unknown>>;
|
|
84
|
+
getTask(taskId: string, expectedKind?: ManagedMediaKind): Promise<Record<string, unknown>>;
|
|
45
85
|
list(): Promise<{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
operations: {
|
|
87
|
+
operationId: string;
|
|
88
|
+
kind: "IMAGE" | "VIDEO";
|
|
89
|
+
phase: "UNKNOWN" | "PREPARED" | "SUBMITTING" | "SUBMITTED" | "FAILED";
|
|
90
|
+
taskId: string | undefined;
|
|
91
|
+
prompt: string;
|
|
92
|
+
publicModelId: string;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
estimatedReservationMicros: unknown;
|
|
95
|
+
savedResults: {
|
|
96
|
+
path: string;
|
|
97
|
+
itemId: string;
|
|
98
|
+
}[];
|
|
56
99
|
}[];
|
|
57
|
-
|
|
100
|
+
skipped: number;
|
|
101
|
+
}>;
|
|
58
102
|
save(input: unknown): Promise<{
|
|
59
103
|
path: string;
|
|
60
104
|
contentType: string;
|
|
@@ -67,4 +111,4 @@ export declare class ManagedImages {
|
|
|
67
111
|
private writeOperation;
|
|
68
112
|
private withOperationLock;
|
|
69
113
|
}
|
|
70
|
-
export {};
|
|
114
|
+
export { ManagedMedia as ManagedImages };
|