@infimind-next/ai-hive-mcp 0.4.2 → 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 +16 -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/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-images.js
CHANGED
|
@@ -4,9 +4,10 @@ import { link, lstat, open, readdir, realpath, unlink } from "node:fs/promises";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { AiHiveApiError } from "./api-client.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { parseModelListInput, parseModelDetailInput, parseModelListResponseV2, } from "./model-response.js";
|
|
8
|
+
import { downloadGeneratedMedia } from "./managed-download.js";
|
|
9
|
+
import { ManagedDirectory, ManagedFiles, } from "./managed-files.js";
|
|
10
|
+
import { generateImageShape, generateVideoShape, mcpCreateMediaUploadOutputSchema, } from "./schemas.js";
|
|
10
11
|
export const managedPrepareShape = {
|
|
11
12
|
publicModelId: generateImageShape.publicModelId,
|
|
12
13
|
routingMode: generateImageShape.routingMode,
|
|
@@ -15,27 +16,46 @@ export const managedPrepareShape = {
|
|
|
15
16
|
imageMediaIds: generateImageShape.imageMediaIds,
|
|
16
17
|
params: generateImageShape.params,
|
|
17
18
|
};
|
|
19
|
+
export const managedPrepareVideoShape = {
|
|
20
|
+
publicModelId: generateVideoShape.publicModelId,
|
|
21
|
+
routingMode: generateVideoShape.routingMode,
|
|
22
|
+
prompt: generateVideoShape.prompt,
|
|
23
|
+
imageMediaIds: generateVideoShape.imageMediaIds,
|
|
24
|
+
videoMediaIds: generateVideoShape.videoMediaIds,
|
|
25
|
+
firstFrameMediaId: generateVideoShape.firstFrameMediaId,
|
|
26
|
+
lastFrameMediaId: generateVideoShape.lastFrameMediaId,
|
|
27
|
+
params: generateVideoShape.params,
|
|
28
|
+
};
|
|
18
29
|
const prepareSchema = z.object(managedPrepareShape).strict();
|
|
30
|
+
const prepareVideoSchema = z.object(managedPrepareVideoShape).strict();
|
|
19
31
|
const pricingSchema = z
|
|
20
32
|
.object({
|
|
21
33
|
publicModelId: z.string(),
|
|
22
34
|
routingMode: z.string(),
|
|
23
35
|
billingRuleSnapshot: z.record(z.unknown()),
|
|
24
36
|
})
|
|
25
|
-
|
|
37
|
+
// 视频报价可能带 materialPricing 等附加字段, 正式提交必须原样带回.
|
|
38
|
+
.passthrough();
|
|
26
39
|
const formalSchema = prepareSchema
|
|
27
40
|
.extend({
|
|
28
41
|
clientRequestId: z.string().uuid(),
|
|
29
42
|
pricingSnapshot: pricingSchema,
|
|
30
43
|
})
|
|
31
44
|
.strict();
|
|
45
|
+
const formalVideoSchema = prepareVideoSchema
|
|
46
|
+
.extend({
|
|
47
|
+
clientRequestId: z.string().uuid(),
|
|
48
|
+
pricingSnapshot: pricingSchema,
|
|
49
|
+
})
|
|
50
|
+
.strict();
|
|
32
51
|
const operationSchema = z
|
|
33
52
|
.object({
|
|
34
53
|
version: z.literal(1),
|
|
35
54
|
userId: z.string(),
|
|
36
55
|
operationId: z.string().uuid(),
|
|
56
|
+
kind: z.enum(["IMAGE", "VIDEO"]).default("IMAGE"),
|
|
37
57
|
phase: z.enum(["PREPARED", "SUBMITTING", "UNKNOWN", "SUBMITTED", "FAILED"]),
|
|
38
|
-
request: formalSchema,
|
|
58
|
+
request: z.union([formalSchema, formalVideoSchema]),
|
|
39
59
|
preflight: z.record(z.unknown()),
|
|
40
60
|
createdAt: z.string(),
|
|
41
61
|
updatedAt: z.string(),
|
|
@@ -60,15 +80,15 @@ const bindingSchema = z
|
|
|
60
80
|
apiBaseUrl: z.string(),
|
|
61
81
|
})
|
|
62
82
|
.strict();
|
|
63
|
-
const TASK_ERROR = "
|
|
83
|
+
const TASK_ERROR = "这项结果不是当前账户的媒体任务,请查询原任务记录";
|
|
64
84
|
const UNKNOWN_ERROR = "提交结果暂未确认,请恢复原任务,不要重新生成";
|
|
65
|
-
const STATE_ERROR = "
|
|
85
|
+
const STATE_ERROR = "媒体任务记录不可用,请从 AI-HIVE 启动器检查原任务";
|
|
66
86
|
function object(value) {
|
|
67
87
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
68
|
-
throw new Error("
|
|
88
|
+
throw new Error("媒体服务暂不可用,请稍后再试");
|
|
69
89
|
return value;
|
|
70
90
|
}
|
|
71
|
-
export class
|
|
91
|
+
export class ManagedMedia {
|
|
72
92
|
config;
|
|
73
93
|
client;
|
|
74
94
|
tokenProvider;
|
|
@@ -105,50 +125,68 @@ export class ManagedImages {
|
|
|
105
125
|
throw new Error();
|
|
106
126
|
}
|
|
107
127
|
catch {
|
|
108
|
-
throw new Error("
|
|
128
|
+
throw new Error("媒体工具连接已变化,请从 AI-HIVE 启动器重新打开项目");
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
|
-
return new
|
|
131
|
+
return new ManagedMedia(input.config, input.client, input.tokenProvider, files, state, input.download ??
|
|
132
|
+
((url, kind) => downloadGeneratedMedia(url, kind).then((result) => result.body)));
|
|
112
133
|
}
|
|
113
134
|
async getUserInfo() {
|
|
114
135
|
const result = object(await this.client.getUserInfo());
|
|
115
136
|
if (result.userId !== this.config.userId)
|
|
116
|
-
throw new Error("
|
|
137
|
+
throw new Error("媒体工具账户已变化,请回到 AI-HIVE 启动器重新登录");
|
|
117
138
|
return result;
|
|
118
139
|
}
|
|
119
|
-
async listModels(
|
|
140
|
+
async listModels(rawInput) {
|
|
120
141
|
await this.getUserInfo();
|
|
121
|
-
const
|
|
142
|
+
const input = parseModelListInput(rawInput, 2);
|
|
143
|
+
const modelType = input.modelType ?? "IMAGE";
|
|
144
|
+
if (modelType !== "IMAGE" && modelType !== "VIDEO")
|
|
145
|
+
throw new Error("请查询图片或视频模型");
|
|
146
|
+
const result = await this.client.listModels({ ...input, modelType }, { announcements: true });
|
|
147
|
+
if (input.schemaVersion === 2) {
|
|
148
|
+
const page = parseModelListResponseV2(result);
|
|
149
|
+
if (page.models.some((model) => model.modelType !== modelType))
|
|
150
|
+
throw new Error("媒体模型响应不匹配,请重新查询");
|
|
151
|
+
return page;
|
|
152
|
+
}
|
|
122
153
|
if (!Array.isArray(result))
|
|
123
|
-
throw new Error("
|
|
124
|
-
return result.filter((model) => object(model).modelType ===
|
|
154
|
+
throw new Error("媒体模型暂不可用,请稍后再试");
|
|
155
|
+
return result.filter((model) => object(model).modelType === modelType);
|
|
125
156
|
}
|
|
126
|
-
async getModel(publicModelId, routingMode) {
|
|
157
|
+
async getModel(publicModelId, routingMode, options = {}) {
|
|
127
158
|
await this.getUserInfo();
|
|
159
|
+
const input = parseModelDetailInput({ publicModelId, routingMode, ...options }, 2);
|
|
128
160
|
const result = object(await this.client.getModel(publicModelId, routingMode, {
|
|
129
161
|
announcements: true,
|
|
162
|
+
schemaVersion: input.schemaVersion,
|
|
163
|
+
...(input.sections !== undefined ? { sections: input.sections } : {}),
|
|
130
164
|
}));
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
const model = input.schemaVersion === 2 ? object(result.model) : result;
|
|
166
|
+
if (model.modelType !== "IMAGE" && model.modelType !== "VIDEO")
|
|
167
|
+
throw new Error("请从媒体模型列表选择模型");
|
|
133
168
|
return result;
|
|
134
169
|
}
|
|
135
170
|
async upload(path) {
|
|
136
171
|
await this.getUserInfo();
|
|
137
|
-
const
|
|
172
|
+
const media = await this.files.readMediaAuto(path);
|
|
173
|
+
const kind = media.contentType.startsWith("video/")
|
|
174
|
+
? "VIDEO"
|
|
175
|
+
: "IMAGE";
|
|
138
176
|
const upload = mcpCreateMediaUploadOutputSchema.parse(await this.client.createMediaUpload({
|
|
139
|
-
filename:
|
|
140
|
-
contentType:
|
|
141
|
-
sizeBytes:
|
|
177
|
+
filename: media.filename,
|
|
178
|
+
contentType: media.contentType,
|
|
179
|
+
sizeBytes: media.sizeBytes,
|
|
142
180
|
}, { announcements: false }));
|
|
143
|
-
if (upload.mediaType !==
|
|
144
|
-
upload.mimeType !==
|
|
145
|
-
upload.sizeBytes !==
|
|
181
|
+
if (upload.mediaType !== kind ||
|
|
182
|
+
upload.mimeType !== media.contentType ||
|
|
183
|
+
upload.sizeBytes !== media.sizeBytes ||
|
|
146
184
|
new URL(upload.upload.url).protocol !== "https:" ||
|
|
147
185
|
Object.keys(upload.upload.headers).some((key) => /authorization|cookie/i.test(key))) {
|
|
148
|
-
throw new Error("
|
|
186
|
+
throw new Error("媒体上传暂不可用,请稍后重试");
|
|
149
187
|
}
|
|
150
188
|
await this.tokenProvider.getToken();
|
|
151
|
-
await this.client.putUpload({ ...upload.upload, body:
|
|
189
|
+
await this.client.putUpload({ ...upload.upload, body: media.body });
|
|
152
190
|
return this.client.completeMediaUpload(upload.mediaId, {
|
|
153
191
|
announcements: true,
|
|
154
192
|
});
|
|
@@ -156,26 +194,45 @@ export class ManagedImages {
|
|
|
156
194
|
async prepare(input) {
|
|
157
195
|
const request = prepareSchema.parse(input);
|
|
158
196
|
await this.getUserInfo();
|
|
159
|
-
|
|
197
|
+
return this.createPrepared("IMAGE", request);
|
|
198
|
+
}
|
|
199
|
+
async prepareVideo(input) {
|
|
200
|
+
const request = prepareVideoSchema.parse(input);
|
|
201
|
+
await this.getUserInfo();
|
|
202
|
+
return this.createPrepared("VIDEO", request);
|
|
203
|
+
}
|
|
204
|
+
async createPrepared(kind, request) {
|
|
205
|
+
const preflight = object(kind === "VIDEO"
|
|
206
|
+
? await this.client.generateVideo({ ...request, dryRun: true }, { announcements: false })
|
|
207
|
+
: await this.client.generateImage({ ...request, dryRun: true }, { announcements: false }));
|
|
160
208
|
const snapshot = pricingSchema.parse(preflight.pricingSnapshot);
|
|
161
209
|
const resolvedParams = z
|
|
162
210
|
.record(z.unknown())
|
|
163
211
|
.safeParse(preflight.resolvedParams);
|
|
164
212
|
if (!resolvedParams.success)
|
|
165
|
-
throw new Error("
|
|
213
|
+
throw new Error("媒体预检暂不可用,请重新检查原方案");
|
|
214
|
+
const mediaIds = kind === "VIDEO"
|
|
215
|
+
? [
|
|
216
|
+
...request.imageMediaIds,
|
|
217
|
+
...request.videoMediaIds,
|
|
218
|
+
request.firstFrameMediaId,
|
|
219
|
+
request.lastFrameMediaId,
|
|
220
|
+
].filter(Boolean)
|
|
221
|
+
: request.imageMediaIds;
|
|
222
|
+
const hasUrlParams = ["imageUrls", "videoUrls"].some((key) => Object.prototype.hasOwnProperty.call(resolvedParams.data, key));
|
|
166
223
|
if (snapshot.publicModelId !== request.publicModelId ||
|
|
167
224
|
snapshot.routingMode !== request.routingMode ||
|
|
168
225
|
preflight.mode !== "DRY_RUN" ||
|
|
169
226
|
preflight.wouldCreateTask !== false ||
|
|
170
|
-
(
|
|
171
|
-
|
|
172
|
-
throw new Error("图片预检暂不可用,请重新检查原方案");
|
|
227
|
+
(mediaIds.length > 0 && hasUrlParams))
|
|
228
|
+
throw new Error("媒体预检暂不可用,请重新检查原方案");
|
|
173
229
|
const operationId = randomUUID();
|
|
174
230
|
const timestamp = new Date().toISOString();
|
|
175
231
|
const operation = {
|
|
176
232
|
version: 1,
|
|
177
233
|
userId: this.config.userId,
|
|
178
234
|
operationId,
|
|
235
|
+
kind,
|
|
179
236
|
phase: "PREPARED",
|
|
180
237
|
request: {
|
|
181
238
|
...request,
|
|
@@ -202,13 +259,17 @@ export class ManagedImages {
|
|
|
202
259
|
throw new Error("原任务未能提交,请确认原记录后重新预检");
|
|
203
260
|
if (operation.preflight.readyToGenerate !== true ||
|
|
204
261
|
operation.preflight.estimatedReservationMicros == null)
|
|
205
|
-
throw new Error("
|
|
262
|
+
throw new Error("媒体预检尚未完成,请补齐素材后重新检查");
|
|
206
263
|
await this.writeOperation({ ...operation, phase: "SUBMITTING" });
|
|
207
264
|
let result;
|
|
208
265
|
try {
|
|
209
|
-
result = object(
|
|
210
|
-
|
|
211
|
-
|
|
266
|
+
result = object(operation.kind === "VIDEO"
|
|
267
|
+
? await this.client.generateVideo(operation.request, {
|
|
268
|
+
announcements: true,
|
|
269
|
+
})
|
|
270
|
+
: await this.client.generateImage(operation.request, {
|
|
271
|
+
announcements: true,
|
|
272
|
+
}));
|
|
212
273
|
}
|
|
213
274
|
catch (error) {
|
|
214
275
|
const definitive = error instanceof AiHiveApiError &&
|
|
@@ -226,8 +287,16 @@ export class ManagedImages {
|
|
|
226
287
|
throw error;
|
|
227
288
|
throw new Error(UNKNOWN_ERROR);
|
|
228
289
|
}
|
|
229
|
-
if (typeof result.taskId !== "string" ||
|
|
230
|
-
|
|
290
|
+
if (typeof result.taskId !== "string" ||
|
|
291
|
+
result.taskType !== operation.kind) {
|
|
292
|
+
// 服务端可能已受理并扣费; 即使类型异常也要保留 taskId, 让查询/保存仍可用.
|
|
293
|
+
await this.writeOperation({
|
|
294
|
+
...operation,
|
|
295
|
+
phase: "UNKNOWN",
|
|
296
|
+
...(typeof result.taskId === "string"
|
|
297
|
+
? { taskId: result.taskId }
|
|
298
|
+
: {}),
|
|
299
|
+
});
|
|
231
300
|
throw new Error(UNKNOWN_ERROR);
|
|
232
301
|
}
|
|
233
302
|
await this.writeOperation({
|
|
@@ -242,13 +311,18 @@ export class ManagedImages {
|
|
|
242
311
|
await this.getUserInfo();
|
|
243
312
|
const operation = await this.readOperation(operationId);
|
|
244
313
|
if (operation.phase === "PREPARED")
|
|
245
|
-
throw new Error("
|
|
314
|
+
throw new Error(operation.kind === "VIDEO"
|
|
315
|
+
? "这项视频方案尚未提交,请确认方案和费用后生成"
|
|
316
|
+
: "这项图片方案尚未提交,请确认方案和费用后生成");
|
|
246
317
|
return this.generate(operationId);
|
|
247
318
|
}
|
|
248
|
-
async getTask(taskId) {
|
|
319
|
+
async getTask(taskId, expectedKind) {
|
|
249
320
|
await this.getUserInfo();
|
|
250
321
|
const task = object(await this.client.getGenerationTask({ taskId }, { announcements: true }));
|
|
251
|
-
|
|
322
|
+
const kind = task.taskType;
|
|
323
|
+
if ((kind !== "IMAGE" && kind !== "VIDEO") ||
|
|
324
|
+
task.taskId !== taskId ||
|
|
325
|
+
(expectedKind !== undefined && kind !== expectedKind))
|
|
252
326
|
throw new Error(TASK_ERROR);
|
|
253
327
|
return task;
|
|
254
328
|
}
|
|
@@ -256,25 +330,38 @@ export class ManagedImages {
|
|
|
256
330
|
await this.getUserInfo();
|
|
257
331
|
await this.state.assertUnchanged();
|
|
258
332
|
const names = await readdir(this.state.path);
|
|
259
|
-
const operations =
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
333
|
+
const operations = [];
|
|
334
|
+
let skipped = 0;
|
|
335
|
+
for (const name of names.filter((name) => /^operation-[0-9a-f-]{36}\.json$/.test(name))) {
|
|
336
|
+
try {
|
|
337
|
+
operations.push(await this.readOperation(name.slice(10, -5)));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// 单条记录损坏时仍返回其余记录, 避免整个列表和恢复入口不可用.
|
|
341
|
+
skipped += 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
operations: operations
|
|
346
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
347
|
+
.map((operation) => ({
|
|
348
|
+
operationId: operation.operationId,
|
|
349
|
+
kind: operation.kind,
|
|
350
|
+
phase: operation.phase,
|
|
351
|
+
taskId: operation.taskId,
|
|
352
|
+
prompt: operation.request.prompt,
|
|
353
|
+
publicModelId: operation.request.publicModelId,
|
|
354
|
+
createdAt: operation.createdAt,
|
|
355
|
+
estimatedReservationMicros: operation.preflight.estimatedReservationMicros,
|
|
356
|
+
savedResults: operation.savedResults,
|
|
357
|
+
})),
|
|
358
|
+
skipped,
|
|
359
|
+
};
|
|
274
360
|
}
|
|
275
361
|
async save(input) {
|
|
276
362
|
const { taskId, itemId, relativePath } = saveSchema.parse(input);
|
|
277
363
|
const task = await this.getTask(taskId);
|
|
364
|
+
const kind = task.taskType === "VIDEO" ? "VIDEO" : "IMAGE";
|
|
278
365
|
const item = Array.isArray(task.items)
|
|
279
366
|
? task.items.find((item) => object(item).id === itemId)
|
|
280
367
|
: undefined;
|
|
@@ -283,14 +370,14 @@ export class ManagedImages {
|
|
|
283
370
|
result.status !== "COMPLETED" ||
|
|
284
371
|
typeof result.resultUrl !== "string" ||
|
|
285
372
|
result.settlementPending === true)
|
|
286
|
-
throw new Error("
|
|
373
|
+
throw new Error("媒体结果尚不可用,请稍后查询原任务");
|
|
287
374
|
await this.tokenProvider.getToken();
|
|
288
|
-
const saved = await this.files.
|
|
375
|
+
const saved = await this.files.saveMedia(await this.download(result.resultUrl, kind), kind, relativePath);
|
|
289
376
|
await this.recordSavedResult(taskId, itemId, saved.path);
|
|
290
377
|
return { taskId, itemId, ...saved };
|
|
291
378
|
}
|
|
292
379
|
async recordSavedResult(taskId, itemId, path) {
|
|
293
|
-
//
|
|
380
|
+
// 媒体已确认落盘; 后续本地记录失败不能让调用方误以为保存失败而重试.
|
|
294
381
|
try {
|
|
295
382
|
await this.state.assertUnchanged();
|
|
296
383
|
const names = await readdir(this.state.path);
|
|
@@ -447,3 +534,4 @@ export class ManagedImages {
|
|
|
447
534
|
}
|
|
448
535
|
}
|
|
449
536
|
}
|
|
537
|
+
export { ManagedMedia as ManagedImages };
|
package/dist/managed-server.js
CHANGED
|
@@ -5,18 +5,19 @@ import { join } from "node:path";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { AiHiveApiClient, AiHiveApiError } from "./api-client.js";
|
|
7
7
|
import { ManagedDirectory } from "./managed-files.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ManagedMedia, managedPrepareShape, managedPrepareVideoShape, } from "./managed-images.js";
|
|
9
9
|
import { ManagedBearerTokenProvider } from "./managed-token-provider.js";
|
|
10
10
|
import { readMcpClientInfo } from "./client-info.js";
|
|
11
11
|
import { SERVER_VERSION } from "./server.js";
|
|
12
12
|
import { toolResultContent } from "./user-notices.js";
|
|
13
|
+
import { listModelsReadShape, getModelReadShape, } from "./model-response.js";
|
|
13
14
|
const INSTRUCTIONS = [
|
|
14
|
-
"AI-HIVE
|
|
15
|
-
"prepare_image_generation 返回的 operationId
|
|
15
|
+
"AI-HIVE 媒体工具仅用于当前蜂巢账户和当前项目。先选择图片或视频模型并读取模型契约,再免费预检准备方案。",
|
|
16
|
+
"prepare_image_generation / prepare_video_generation 返回的 operationId 代表已持久化的原方案和费用。用户确认方案和费用后再调用对应的 generate 工具;正式生成只传原 operationId,不更换请求编号或修改原方案。费用或素材变化时重新预检并取得用户确认。",
|
|
16
17
|
"预检仅校验参数和素材,不执行内容审核,也不保证生成成功。估价为空或 readyToGenerate=false 时补齐素材再预检。",
|
|
17
|
-
"提交结果未知时调用 recover_image_generation 恢复原请求;已收到 taskId 时仅查询原任务。FAILED 记录不自动重新付费;先核查原任务。停止工具或关闭启动器只停止本地等待,已受理的服务器任务可能继续生成和结算。",
|
|
18
|
-
"
|
|
19
|
-
"同账户重新登录后可查询
|
|
18
|
+
"提交结果未知时调用 recover_image_generation 或 recover_video_generation 恢复原请求;已收到 taskId 时仅查询原任务。FAILED 记录不自动重新付费;先核查原任务。停止工具或关闭启动器只停止本地等待,已受理的服务器任务可能继续生成和结算。",
|
|
19
|
+
"本地图片或视频只能来自当前项目或启动器明确选择的单个文件。结果通过 save_generation_result(taskId,itemId,relativePath?) 保存,relativePath 仅为文件名,目录固定为项目的 ai-hive-media,已有文件不覆盖。不要传入下载 URL。",
|
|
20
|
+
"同账户重新登录后可查询 list_media_operations 恢复原记录。新登录会使旧连接失效;账户变化后返回启动器重新打开项目。",
|
|
20
21
|
"向用户说明结果、费用和下一步;不要展示密钥、内部诊断或重试机制。余额不足时使用账户结果的充值链接;不擅自切换模型或继续付费。",
|
|
21
22
|
].join("\n");
|
|
22
23
|
function resultContent(value) {
|
|
@@ -26,7 +27,7 @@ function resultContent(value) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
export async function createManagedServer(input) {
|
|
29
|
-
const server = new McpServer({ name: "ai-hive-
|
|
30
|
+
const server = new McpServer({ name: "ai-hive-media", version: SERVER_VERSION }, { instructions: INSTRUCTIONS });
|
|
30
31
|
const tokenProvider = input.tokenProvider ??
|
|
31
32
|
new ManagedBearerTokenProvider(input.config.credentialHelper);
|
|
32
33
|
const client = new AiHiveApiClient({
|
|
@@ -37,7 +38,7 @@ export async function createManagedServer(input) {
|
|
|
37
38
|
fetchImpl: input.fetchImpl,
|
|
38
39
|
clientInfoProvider: () => readMcpClientInfo(server),
|
|
39
40
|
});
|
|
40
|
-
const
|
|
41
|
+
const media = await ManagedMedia.create({
|
|
41
42
|
config: input.config,
|
|
42
43
|
client,
|
|
43
44
|
tokenProvider,
|
|
@@ -45,78 +46,112 @@ export async function createManagedServer(input) {
|
|
|
45
46
|
const tools = [
|
|
46
47
|
{
|
|
47
48
|
name: "get_user_info",
|
|
49
|
+
title: "查看账户",
|
|
48
50
|
description: "查看你的蜂巢账户资料、蜂蜜余额和充值入口。",
|
|
49
51
|
schema: {},
|
|
50
|
-
handler: () =>
|
|
52
|
+
handler: () => media.getUserInfo(),
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
name: "list_models",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
title: "筛选可用模型目录",
|
|
57
|
+
description: "按类型或关键词筛选图片或视频轻目录,找到候选即可 get_model 默认读取参数和价格;已知 ID 直接 get_model。默认使用 V2,V2 不传 detail;仅需完整目录时继续 nextCursor。旧格式传 schemaVersion=1,可选 detail=lite 或 detail=full。正式调用前预检并确认费用。",
|
|
58
|
+
schema: {
|
|
59
|
+
...listModelsReadShape,
|
|
60
|
+
modelType: z.enum(["IMAGE", "VIDEO"]).optional(),
|
|
61
|
+
},
|
|
62
|
+
handler: async (args) => {
|
|
63
|
+
const result = await media.listModels(args);
|
|
64
|
+
return Array.isArray(result) ? { models: result } : result;
|
|
65
|
+
},
|
|
59
66
|
},
|
|
60
67
|
{
|
|
61
68
|
name: "get_model",
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
handler: (args) => images.getModel(String(args.publicModelId), args.routingMode),
|
|
69
|
+
title: "查询模型契约",
|
|
70
|
+
description: "使用目录返回或已确认的 publicModelId 查询图片或视频模型的参数、素材要求和费用。未知可用线路时先不传 routingMode,从返回的 routingModes 中选择。价格单位是蜂蜜;saleMicros 为整数文本,1000000 微单位 = 1 蜂蜜,不标元、人民币或 ¥;保留计费单位与适用条件。默认读取参数和价格,sections 可按需读取示例,schemaVersion=1 可读取旧格式。",
|
|
71
|
+
schema: getModelReadShape,
|
|
72
|
+
handler: (args) => media.getModel(String(args.publicModelId), args.routingMode, {
|
|
73
|
+
schemaVersion: args.schemaVersion,
|
|
74
|
+
sections: args.sections,
|
|
75
|
+
}),
|
|
70
76
|
},
|
|
71
77
|
{
|
|
72
78
|
name: "upload_media_from_path",
|
|
73
|
-
|
|
79
|
+
title: "上传本地媒体",
|
|
80
|
+
description: "上传项目或启动器明确选择的本地图片或视频,返回 mediaId。仅接收绝对路径,按实际内容识别格式;图片最大 30MiB,视频最大 100MiB,同时受模型与服务端限制。",
|
|
74
81
|
schema: { path: z.string().min(1) },
|
|
75
|
-
handler: async (args) => (await
|
|
82
|
+
handler: async (args) => (await media.upload(String(args.path))),
|
|
76
83
|
},
|
|
77
84
|
{
|
|
78
85
|
name: "prepare_image_generation",
|
|
86
|
+
title: "预检图片方案",
|
|
79
87
|
description: "免费检查图片方案和已上传素材,返回预估费用及 operationId,不创建生成任务;保留原方案供用户确认后提交。",
|
|
80
88
|
schema: managedPrepareShape,
|
|
81
|
-
handler: (args) =>
|
|
89
|
+
handler: (args) => media.prepare(args),
|
|
82
90
|
},
|
|
83
91
|
{
|
|
84
92
|
name: "generate_image",
|
|
93
|
+
title: "生成图片",
|
|
85
94
|
description: "用户确认方案和费用后,按免费预检保存的 operationId 正式生成图片。仅接受原编号,不接收新参数。提交未知时恢复原请求,避免重复创建。",
|
|
86
95
|
schema: { operationId: z.string().uuid() },
|
|
87
|
-
handler: (args) =>
|
|
96
|
+
handler: (args) => media.generate(String(args.operationId)),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "prepare_video_generation",
|
|
100
|
+
title: "预检视频方案",
|
|
101
|
+
description: "免费检查视频方案和已上传素材,返回预估费用及 operationId,不创建生成任务;保留原方案供用户确认后提交。",
|
|
102
|
+
schema: managedPrepareVideoShape,
|
|
103
|
+
handler: (args) => media.prepareVideo(args),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "generate_video",
|
|
107
|
+
title: "生成视频",
|
|
108
|
+
description: "用户确认方案和费用后,按免费预检保存的 operationId 正式生成视频。仅接受原编号,不接收新参数。提交未知时恢复原请求,避免重复创建。",
|
|
109
|
+
schema: { operationId: z.string().uuid() },
|
|
110
|
+
handler: (args) => media.generate(String(args.operationId)),
|
|
88
111
|
},
|
|
89
112
|
{
|
|
90
113
|
name: "get_generation_task",
|
|
91
|
-
|
|
114
|
+
title: "查询生成任务",
|
|
115
|
+
description: "查询当前账户的原图片或视频任务。已获得 taskId 后查询结果,不重复生成。",
|
|
92
116
|
schema: { taskId: z.string().min(1) },
|
|
93
|
-
handler: (args) =>
|
|
117
|
+
handler: (args) => media.getTask(String(args.taskId)),
|
|
94
118
|
},
|
|
95
119
|
{
|
|
96
120
|
name: "recover_image_generation",
|
|
121
|
+
title: "恢复图片请求",
|
|
97
122
|
description: "恢复提交结果未知的原图片请求,保持原方案、报价和幂等编号;已有 taskId 时只查询,不重新生成。",
|
|
98
123
|
schema: { operationId: z.string().uuid() },
|
|
99
|
-
handler: (args) =>
|
|
124
|
+
handler: (args) => media.recover(String(args.operationId)),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "recover_video_generation",
|
|
128
|
+
title: "恢复视频请求",
|
|
129
|
+
description: "恢复提交结果未知的原视频请求,保持原方案、报价和幂等编号;已有 taskId 时只查询,不重新生成。",
|
|
130
|
+
schema: { operationId: z.string().uuid() },
|
|
131
|
+
handler: (args) => media.recover(String(args.operationId)),
|
|
100
132
|
},
|
|
101
133
|
{
|
|
102
|
-
name: "
|
|
103
|
-
|
|
134
|
+
name: "list_media_operations",
|
|
135
|
+
title: "查看媒体记录",
|
|
136
|
+
description: "列出当前账户和项目已保存的图片与视频方案、提交状态、原任务和本地结果。",
|
|
104
137
|
schema: {},
|
|
105
|
-
handler:
|
|
138
|
+
handler: () => media.list(),
|
|
106
139
|
},
|
|
107
140
|
{
|
|
108
141
|
name: "save_generation_result",
|
|
109
|
-
|
|
142
|
+
title: "保存生成结果",
|
|
143
|
+
description: "查询原图片或视频任务并将已完成的指定结果安全保存到项目 ai-hive-media。relativePath 只接收文件名,已有文件不覆盖;不接收 URL。",
|
|
110
144
|
schema: {
|
|
111
145
|
taskId: z.string().min(1),
|
|
112
146
|
itemId: z.string().min(1),
|
|
113
147
|
relativePath: z.string().min(1).optional(),
|
|
114
148
|
},
|
|
115
|
-
handler: (args) =>
|
|
149
|
+
handler: (args) => media.save(args),
|
|
116
150
|
},
|
|
117
151
|
];
|
|
118
152
|
for (const tool of tools) {
|
|
119
153
|
server.registerTool(tool.name, {
|
|
154
|
+
title: tool.title,
|
|
120
155
|
description: tool.description,
|
|
121
156
|
inputSchema: z.object(tool.schema).strict(),
|
|
122
157
|
annotations: {
|
|
@@ -132,12 +167,12 @@ export async function createManagedServer(input) {
|
|
|
132
167
|
const message = error instanceof AiHiveApiError
|
|
133
168
|
? error.message
|
|
134
169
|
: error instanceof z.ZodError
|
|
135
|
-
? "
|
|
170
|
+
? "媒体方案暂不可用,请检查所选模型和素材"
|
|
136
171
|
: error instanceof Error &&
|
|
137
172
|
/^[\u4e00-\u9fff]/.test(error.message) &&
|
|
138
173
|
!/[\r\n]/.test(error.message)
|
|
139
174
|
? error.message
|
|
140
|
-
: "
|
|
175
|
+
: "媒体操作暂不可用,请稍后查询原任务";
|
|
141
176
|
return {
|
|
142
177
|
isError: true,
|
|
143
178
|
content: [{ type: "text", text: message }],
|