@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.
@@ -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 { downloadGeneratedImage } from "./managed-download.js";
8
- import { ManagedDirectory, ManagedFiles } from "./managed-files.js";
9
- import { generateImageShape, mcpCreateMediaUploadOutputSchema, } from "./schemas.js";
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
- .strict();
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 = "图片任务记录不可用,请从 AI-HIVE 启动器检查原任务";
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 ManagedImages {
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("图片工具连接已变化,请从 AI-HIVE 启动器重新打开项目");
128
+ throw new Error("媒体工具连接已变化,请从 AI-HIVE 启动器重新打开项目");
109
129
  }
110
130
  }
111
- return new ManagedImages(input.config, input.client, input.tokenProvider, files, state, input.download ?? downloadGeneratedImage);
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("图片工具账户已变化,请回到 AI-HIVE 启动器重新登录");
137
+ throw new Error("媒体工具账户已变化,请回到 AI-HIVE 启动器重新登录");
117
138
  return result;
118
139
  }
119
- async listModels(input) {
140
+ async listModels(rawInput) {
120
141
  await this.getUserInfo();
121
- const result = await this.client.listModels({ modelType: "IMAGE", ...input }, { announcements: true });
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 === "IMAGE");
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
- if (result.modelType !== "IMAGE")
132
- throw new Error("请从图片模型列表选择模型");
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 image = await this.files.readImage(path);
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: image.filename,
140
- contentType: image.contentType,
141
- sizeBytes: image.sizeBytes,
177
+ filename: media.filename,
178
+ contentType: media.contentType,
179
+ sizeBytes: media.sizeBytes,
142
180
  }, { announcements: false }));
143
- if (upload.mediaType !== "IMAGE" ||
144
- upload.mimeType !== image.contentType ||
145
- upload.sizeBytes !== image.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: image.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
- const preflight = object(await this.client.generateImage({ ...request, dryRun: true }, { announcements: false }));
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
- (request.imageMediaIds.length > 0 &&
171
- Object.prototype.hasOwnProperty.call(resolvedParams.data, "imageUrls")))
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(await this.client.generateImage(operation.request, {
210
- announcements: true,
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" || result.taskType !== "IMAGE") {
230
- await this.writeOperation({ ...operation, phase: "UNKNOWN" });
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
- if (task.taskType !== "IMAGE" || task.taskId !== taskId)
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 = await Promise.all(names
260
- .filter((name) => /^operation-[0-9a-f-]{36}\.json$/.test(name))
261
- .map((name) => this.readOperation(name.slice(10, -5))));
262
- return operations
263
- .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
264
- .map((operation) => ({
265
- operationId: operation.operationId,
266
- phase: operation.phase,
267
- taskId: operation.taskId,
268
- prompt: operation.request.prompt,
269
- publicModelId: operation.request.publicModelId,
270
- createdAt: operation.createdAt,
271
- estimatedReservationMicros: operation.preflight.estimatedReservationMicros,
272
- savedResults: operation.savedResults,
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.saveImage(await this.download(result.resultUrl), relativePath);
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 };
@@ -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 { ManagedImages, managedPrepareShape } from "./managed-images.js";
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 代表已持久化的原方案和费用。用户确认方案和费用后再调用 generate_image;正式生成只传原 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
- "本地图片只能来自当前项目或启动器明确选择的单个图片。结果通过 save_generation_result(taskId,itemId,relativePath?) 保存,relativePath 仅为文件名,目录固定为项目的 ai-hive-images,已有文件不覆盖。不要传入下载 URL。",
19
- "同账户重新登录后可查询 list_image_operations 恢复原记录。新登录会使旧连接失效;账户变化后返回启动器重新打开项目。",
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-images", version: SERVER_VERSION }, { instructions: INSTRUCTIONS });
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 images = await ManagedImages.create({
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: () => images.getUserInfo(),
52
+ handler: () => media.getUserInfo(),
51
53
  },
52
54
  {
53
55
  name: "list_models",
54
- description: "查询可用图片模型和价格;detail=lite 用于选型,full 返回完整信息。",
55
- schema: { detail: z.enum(["lite", "full"]).optional() },
56
- handler: async (args) => ({
57
- models: await images.listModels(args),
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
- description: "查询所选图片模型的参数、素材要求和费用。",
63
- schema: {
64
- publicModelId: z.string().min(1),
65
- routingMode: z
66
- .enum(["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"])
67
- .optional(),
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
- description: "上传项目或启动器明确选择的本地图片,返回 mediaId。仅接收绝对路径,按实际图片内容识别格式,最大 30MiB;同时受模型与服务端限制。",
79
+ title: "上传本地媒体",
80
+ description: "上传项目或启动器明确选择的本地图片或视频,返回 mediaId。仅接收绝对路径,按实际内容识别格式;图片最大 30MiB,视频最大 100MiB,同时受模型与服务端限制。",
74
81
  schema: { path: z.string().min(1) },
75
- handler: async (args) => (await images.upload(String(args.path))),
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) => images.prepare(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) => images.generate(String(args.operationId)),
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
- description: "查询当前账户的原图片任务。已获得 taskId 后查询结果,不重复生成。",
114
+ title: "查询生成任务",
115
+ description: "查询当前账户的原图片或视频任务。已获得 taskId 后查询结果,不重复生成。",
92
116
  schema: { taskId: z.string().min(1) },
93
- handler: (args) => images.getTask(String(args.taskId)),
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) => images.recover(String(args.operationId)),
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: "list_image_operations",
103
- description: "列出当前账户和项目已保存的图片方案、提交状态、原任务和本地结果。",
134
+ name: "list_media_operations",
135
+ title: "查看媒体记录",
136
+ description: "列出当前账户和项目已保存的图片与视频方案、提交状态、原任务和本地结果。",
104
137
  schema: {},
105
- handler: async () => ({ operations: await images.list() }),
138
+ handler: () => media.list(),
106
139
  },
107
140
  {
108
141
  name: "save_generation_result",
109
- description: "查询原图片任务并将已完成的指定结果安全保存到项目 ai-hive-images。relativePath 只接收文件名,已有文件不覆盖;不接收 URL。",
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) => images.save(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 }],