@infimind-next/ai-hive-mcp 0.3.3 → 0.4.1
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 +78 -12
- package/dist/api-client.d.ts +15 -0
- package/dist/api-client.js +89 -9
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +34 -0
- package/dist/config.d.ts +19 -1
- package/dist/config.js +104 -7
- package/dist/index.js +9 -1
- package/dist/managed-config.d.ts +75 -0
- package/dist/managed-config.js +71 -0
- package/dist/managed-download.d.ts +13 -0
- package/dist/managed-download.js +138 -0
- package/dist/managed-files.d.ts +35 -0
- package/dist/managed-files.js +240 -0
- package/dist/managed-images.d.ts +70 -0
- package/dist/managed-images.js +449 -0
- package/dist/managed-server.d.ts +15 -0
- package/dist/managed-server.js +195 -0
- package/dist/managed-token-provider.d.ts +11 -0
- package/dist/managed-token-provider.js +31 -0
- package/dist/rest-commands.d.ts +14 -0
- package/dist/rest-commands.js +164 -0
- package/dist/schemas.d.ts +118 -10
- package/dist/schemas.js +79 -0
- package/dist/server.d.ts +4 -3
- package/dist/server.js +116 -34
- package/dist/setup/detect.d.ts +9 -0
- package/dist/setup/detect.js +34 -0
- package/dist/setup/merge.d.ts +5 -0
- package/dist/setup/merge.js +142 -0
- package/dist/setup/run-setup.d.ts +20 -0
- package/dist/setup/run-setup.js +332 -0
- package/dist/setup/skills.d.ts +8 -0
- package/dist/setup/skills.js +35 -0
- package/dist/setup/targets.d.ts +14 -0
- package/dist/setup/targets.js +46 -0
- package/package.json +11 -9
- package/skills/ai-hive-billing/SKILL.md +33 -0
- package/skills/ai-hive-image-video-generation/SKILL.md +41 -0
- package/skills/ai-hive-media-upload/SKILL.md +33 -0
- package/skills/ai-hive-model-selection/SKILL.md +50 -0
- package/skills/ai-hive-text-generation/SKILL.md +39 -0
package/README.md
CHANGED
|
@@ -4,38 +4,80 @@
|
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
0.4.1 已发布(2026-10-23)。在 MCP 客户端中加入以下配置,把 `<APIKEY>` 换成你在蜂巢 AI 网页端「MCP 接入」里生成的 MCP 密钥:
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
10
|
{
|
|
11
11
|
"mcpServers": {
|
|
12
12
|
"ai-hive": {
|
|
13
13
|
"command": "npx",
|
|
14
|
-
"args": ["-y", "@infimind-next/ai-hive-mcp@0.
|
|
14
|
+
"args": ["-y", "@infimind-next/ai-hive-mcp@0.4.1"],
|
|
15
|
+
"env": { "AI_HIVE_MCP_KEY": "<APIKEY>" }
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
手动配置也把密钥放在 `env` 里;`--key` 仍兼容,但会出现在进程参数中,不建议继续使用。
|
|
22
|
+
|
|
23
|
+
也可以在终端或 Agent 里运行下面的命令,自动检测本机客户端、写入配置并安装技能:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
AI_HIVE_MCP_KEY=<APIKEY> npx -y @infimind-next/ai-hive-mcp@latest setup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`setup` 会检测 Claude Code、Cursor、Codex、Claude Desktop 和 OpenCode,只写入 `ai-hive` 这一项,保留你已有的 MCP 配置和已有密钥;密钥以环境变量写入配置文件,不放进启动参数。没有密钥且本机没有登录凭证时会直接失败,不会写入一份不可用的配置。Claude Code 和 Codex 会同时安装技能。重复执行是安全的。常用选项:
|
|
30
|
+
|
|
31
|
+
- `--client cursor,codex`:只处理指定客户端。
|
|
32
|
+
- `--dry-run`:只预览,不写文件。
|
|
33
|
+
- `--skip-skills`:只写 MCP 配置,不安装技能。
|
|
34
|
+
|
|
35
|
+
要求 Node.js >= 20.19.0。默认请求超时为 300 秒,可通过 `--timeout-ms` 或 `AI_HIVE_MCP_TIMEOUT_MS` 覆盖;无效值回退为 300 秒。WorkBuddy Connector 的启动与认证命令保持引用 `@latest`,Connector 和 Skills 版本仍为 1.2.1。
|
|
21
36
|
|
|
22
37
|
本包通过标准 MCP stdio 与客户端通信,仅保证与支持 MCP 的客户端配合使用。
|
|
23
38
|
|
|
39
|
+
## 命令行
|
|
40
|
+
|
|
41
|
+
没有 MCP 宿主时,也可以用同一个 MCP 密钥直接调用。`run` 会先读取所选线路的报价再提交,金额单位是蜂蜜;只有充值实付金额才是元。输出默认适合人读,加 `--json` 返回原始结构:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ai-hive-mcp models --type IMAGE --json # 列模型与售价
|
|
45
|
+
ai-hive-mcp model <模型 ID> --json # 查参数契约
|
|
46
|
+
ai-hive-mcp usage --json # 查积分与账单汇总
|
|
47
|
+
ai-hive-mcp run <模型 ID> "写一句欢迎语" # 付费文字调用
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`run` 是付费调用,每次都会生成新的 `clientRequestId`;需要重试同一次请求时改用 MCP 的 `chat_text` 并复用原标识。
|
|
51
|
+
|
|
24
52
|
## 工具
|
|
25
53
|
|
|
26
|
-
- `get_user_info
|
|
54
|
+
- `get_user_info`:查询当前用户和普通蜂蜜积分
|
|
27
55
|
|
|
28
|
-
|
|
56
|
+
专属蜂蜜由服务端按实际模型自动选择抵扣,无需新增调用参数。不能仅凭普通蜂蜜积分为 0 拒绝模型调用;只有实际调用返回积分不足时,才使用 `rechargeUrl` 引导充值并重试。
|
|
29
57
|
|
|
30
58
|
- `list_models`:按 TEXT、IMAGE 或 VIDEO 查询可用模型与公开价格
|
|
31
59
|
- `get_model`:查询单个模型的参数 schema、媒体要求、计费因素和调用示例
|
|
32
|
-
- `upload_media_from_path
|
|
60
|
+
- `upload_media_from_path`:上传本地图片、视频、音频或文档,返回 mediaId
|
|
33
61
|
- `chat_text`:文本对话
|
|
34
62
|
- `generate_image`:图片预检或正式生成
|
|
35
63
|
- `generate_video`:视频预检或正式生成
|
|
36
64
|
- `get_material_mode`:查询认证资格和网页授权入口(可选)
|
|
37
65
|
- `quote_video_materials`:获取素材报价(可选)
|
|
38
|
-
- `
|
|
66
|
+
- `list_generation_tasks`:查询当前账号全部入口的图片、视频任务摘要,最新优先
|
|
67
|
+
- `get_generation_task`:按任务 ID 查询状态、结果和费用
|
|
68
|
+
- `get_billing_summary`:查询当前账户全部入口的账单汇总
|
|
69
|
+
- `list_billing_records`:分页查询账单记录
|
|
70
|
+
- `get_billing_record`:按记录 ID 查询账单详情
|
|
71
|
+
|
|
72
|
+
### 账单查询
|
|
73
|
+
|
|
74
|
+
3 个账单工具均为只读,需要所连接服务端支持。查询当前账户在网页、API、MCP 等全部入口的记录,不接受手机号或其他用户 ID。现有模型、任务和账户工具的调用方式不变;本地包版本和发布状态不因本次代码变更而改变。
|
|
75
|
+
|
|
76
|
+
账单单位为 HONEY(蜂蜜虚拟积分),积分数值沿用服务端的 micros 整数字符串,避免精度损失。消费仅指实际结算,预占、释放分别展示,不计入消费。查询某批生成任务时,传入该批的 `taskIds`,不要用前后积分之差推断批次费用;尚未结算的任务不能当作免费。
|
|
77
|
+
|
|
78
|
+
`get_billing_summary` 和 `list_billing_records` 支持可选 `from`、`to`、`taskIds`、`publicModelId`、`invocationSource`。不传时间时由服务端使用北京时间今日;指定时间须成对传入含时区的 ISO 8601 字符串,按左闭右开区间 `[from,to)` 查询。`taskIds` 传 1~100 个非空任务 ID。`invocationSource` 可选 `UNKNOWN`、`WEB`、`MCP`、`WORKBUDDY`、`QWENWORK`、`DOUBAO_WORK`、`MIAOSHOU`、`OPENAPI`。
|
|
79
|
+
|
|
80
|
+
`list_billing_records` 的 `pageSize` 默认 20、最多 100,后续分页原样传回 `cursor`。`get_billing_record` 接收列表返回的 `recordId`,例如 `charge:<id>` 或 `reservation:<id>`。这些工具不请求活动或公告通知。
|
|
39
81
|
|
|
40
82
|
### 认证素材模式
|
|
41
83
|
|
|
@@ -53,14 +95,20 @@
|
|
|
53
95
|
|
|
54
96
|
1. 用 `list_models` 选择模型,记录 `publicModelId`。
|
|
55
97
|
2. 用 `get_model` 查看参数契约,`params` 的字段名、类型、枚举值和默认值逐项来自返回的 `paramsSchema`。
|
|
56
|
-
3. 调用 `generate_image` 或 `generate_video` 时传 `dryRun: true` 预检,核对返回的 `estimatedReservationMicros`
|
|
98
|
+
3. 调用 `generate_image` 或 `generate_video` 时传 `dryRun: true` 预检,核对返回的 `estimatedReservationMicros` 估价(单位:蜂蜜 micros,1,000,000 micros = 1 蜂蜜;只有充值实付金额使用元)。
|
|
57
99
|
4. 确认费用后去掉 `dryRun` 正式生成,`params` 替换为预检返回的 `resolvedParams`,并传入预检返回的 `pricingSnapshot` 和本次调用稳定唯一的 `clientRequestId`。
|
|
58
|
-
5. 用 `get_generation_task`
|
|
100
|
+
5. 用 `get_generation_task` 查询任务进度、结果和费用,不重复创建任务。
|
|
101
|
+
|
|
102
|
+
需要找回近期任务时,使用 `list_generation_tasks`。可选 `publicModelId` 筛选模型,`page` 默认 1,`pageSize` 默认 20、最多 50。返回 `{tasks,total,page,pageSize}`,按创建时间和任务 ID 倒序;列表仅含任务与子任务 ID、模型、类型、创建时间、批量数量、160 字符以内提示词摘要、状态和进度,完整参数、结果地址及费用见详情。列表是只读工具,不返回公告或消耗通知额度。
|
|
103
|
+
|
|
104
|
+
详情默认包含 `billing`:金额单位为蜂蜜,`currency=CNY` 仅为历史兼容编码,不作为展示币种;公开价格快照、预估金额 `estimatedReservationMicros`、已入账金额 `settledChargeMicros` 和 `settlementComplete`。金额为 micros 字符串;结算未完成时已入账金额尚非最终费用,不能把 0 当免费。`billing:null` 表示费用暂不可读,任务结果仍可用。关闭通知不影响费用。钱包继续用 `get_user_info`。
|
|
105
|
+
|
|
106
|
+
远程 MCP 随服务端部署获得列表能力,本地 MCP 需升级到 0.3.4。新版客户端连接旧服务端不支持的列表端点时,会提示继续按任务 ID 查询。已发布的旧客户端无需修改详情调用即可透传 `billing`;严格拒绝新增响应字段的自定义消费者需更新响应校验规则。
|
|
59
107
|
|
|
60
108
|
## 重试与价格变化
|
|
61
109
|
|
|
62
110
|
- 传输中断或超时后重试:复用同一个 `clientRequestId` 和相同业务参数,不会重复扣费。
|
|
63
|
-
-
|
|
111
|
+
- 修正参数、积分或媒体问题后重试:必须使用新的 `clientRequestId`。
|
|
64
112
|
- 正式生成提示价格已更新:保持业务参数和原 `clientRequestId` 不变,重新预检获取最新 `pricingSnapshot` 后重试。
|
|
65
113
|
- 返回 `REQUEST_OUTCOME_UNKNOWN`:停止自动重试,联系管理员核查。
|
|
66
114
|
|
|
@@ -68,13 +116,23 @@
|
|
|
68
116
|
|
|
69
117
|
用户提供普通素材即可,后台自动处理认证。使用对应认证模型与当前价格快照,先 dryRun 预检资格、素材和费用;通过后沿用标准版提交格式,认证字段和专用报价工具都可省略。收到任务 ID 后只查询原任务。
|
|
70
118
|
|
|
71
|
-
|
|
119
|
+
仅缺少授权、充值资格或积分时向用户说明必要操作,并提供[认证与充值入口](https://ai-hive.iclip.cn/certified-materials#guide);不擅自换模型提交。费用变化时重新确认,预检通过不代表内容审核通过。
|
|
72
120
|
|
|
73
121
|
更新信息仅在用户询问或与当前任务有关时介绍,不向用户转述内部参数、错误码、报价与重试机制。客户端可选升级提示默认静默,仅开启 `--verbose` 时输出维护诊断。技术维护记录见仓库 `docs/openapi/technical-changelog.md`。
|
|
74
122
|
|
|
75
123
|
`recoveryAction` 是给调用端的恢复指引,不要逐字转成用户待办。响应中断用原 `clientRequestId` 和相同参数恢复;明确失败后再次生成,或改变报价/输入时,使用新请求标识。结果未知时先核查,不自动重建付费任务。
|
|
76
124
|
|
|
77
|
-
|
|
125
|
+
0.4.1 已发布(2026-10-23),registry 最新版本为 0.4.1。发布物、兼容验证与服务端验收边界见 [0.3.2 发布记录](../../docs/release/mcp-0.3.2-runbook.md)。
|
|
126
|
+
|
|
127
|
+
### 0.4.1:修正发布说明
|
|
128
|
+
|
|
129
|
+
0.4.1 仅修正 npm 首页的 README 发布状态文案,功能与 0.4.0 相同。
|
|
130
|
+
|
|
131
|
+
### 0.4.0:一条命令接入
|
|
132
|
+
|
|
133
|
+
新增 `setup` 命令:检测本机客户端、写入 MCP 配置、安装随包发布的 5 个技能,并用 `user-info` 验证连接。新增 `models`、`model`、`usage`、`run` 四个命令行子命令,让没有 MCP 宿主的 Agent 也能选型、试跑和查账。`setup` 写入的配置统一走 `AI_HIVE_MCP_KEY`,MCP 服务的启动参数里不再带密钥。0.4.0 与 0.4.1 均已发布,`@latest` 指向 0.4.1。
|
|
134
|
+
|
|
135
|
+
向下兼容:0.3.3 的 12 个工具名称、入参和返回保持不变,0.4.0 只新增 4 个只读工具;`server`、`auth`、`status`、`logout` 的行为不变。旧服务端不接受 `detail` 参数时,`list_models` 会自动去掉它重试一次。账单工具需要服务端已部署账单接口,未部署时会提示到控制台查看,不影响其他工具。
|
|
78
136
|
|
|
79
137
|
### 0.3.2:通知协商与直接上传
|
|
80
138
|
|
|
@@ -88,3 +146,11 @@
|
|
|
88
146
|
兼容模式(COMPAT)下,账户 GET 和 POST 常驻返回进行中的旧充值活动,不占用模型调用的每日活动通知次数。原有付费请求仍按用户每天首次成功调用返回活动;重放、恢复与 dryRun 不重复通知。管理员启用通知管理(MANAGED)后按入口设置返回;旧客户端保留 `activePromotion` 结构,功能公告通过支持新扩展的客户端返回。
|
|
89
147
|
|
|
90
148
|
0.3.2 中,CLI login/status 的凭证检查使用账户 GET 并明确发送 `X-AI-Hive-Announcements: 0`,完整验证凭证后仅返回账户核心字段、`activePromotion: null`,不写通知台账、共享次数或最短间隔状态。SDK 的 POST 404/405 后默认 GET 回退仍返回兼容活动。
|
|
149
|
+
|
|
150
|
+
### 0.3.3:简化提示与报价对象兼容
|
|
151
|
+
|
|
152
|
+
工具说明精简为用途和必要输入;充值活动仅说明优惠、截止时间和入口。未拥有专属蜂蜜的账户不介绍相关信息。报价支持嵌套对象与 JSON 字符串,沿用服务端校验与原有支付规则。
|
|
153
|
+
|
|
154
|
+
### 0.3.4:任务列表与详情费用(随 0.4.0 发布)
|
|
155
|
+
|
|
156
|
+
新增 `list_generation_tasks`;现有详情调用默认返回费用,API 与 MCP 使用相同口径。推送、服务端部署和 npm 发布需另行授权。
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BillingQueryInput, ListBillingRecordsInput } from "./schemas.js";
|
|
1
2
|
import type { BearerTokenProvider } from "./auth-token-provider.js";
|
|
2
3
|
import { type McpClientInfo } from "./client-info.js";
|
|
3
4
|
import { type UpdateNotifier } from "./update-notifier.js";
|
|
@@ -12,6 +13,7 @@ type AiHiveApiClientInput = {
|
|
|
12
13
|
fetchImpl?: FetchImpl;
|
|
13
14
|
clientInfoProvider?: () => McpClientInfo | undefined;
|
|
14
15
|
updateNotifier?: UpdateNotifier;
|
|
16
|
+
clientKind?: "codex-launcher";
|
|
15
17
|
};
|
|
16
18
|
export declare class AiHiveApiError extends Error {
|
|
17
19
|
readonly status: number | undefined;
|
|
@@ -39,6 +41,7 @@ export declare class AiHiveApiClient {
|
|
|
39
41
|
private readonly fetchImpl;
|
|
40
42
|
private readonly clientInfoProvider?;
|
|
41
43
|
private readonly updateNotifier?;
|
|
44
|
+
private readonly clientKind?;
|
|
42
45
|
constructor(input: AiHiveApiClientInput);
|
|
43
46
|
private withTimeout;
|
|
44
47
|
private request;
|
|
@@ -55,6 +58,18 @@ export declare class AiHiveApiClient {
|
|
|
55
58
|
generateImage(input: unknown, options?: AnnouncementOptions): Promise<unknown>;
|
|
56
59
|
generateVideo(input: unknown, options?: AnnouncementOptions): Promise<unknown>;
|
|
57
60
|
private requestGeneration;
|
|
61
|
+
private billingQuery;
|
|
62
|
+
getBillingSummary(input: BillingQueryInput): Promise<unknown>;
|
|
63
|
+
listBillingRecords(input: ListBillingRecordsInput): Promise<unknown>;
|
|
64
|
+
getBillingRecord(input: {
|
|
65
|
+
recordId: string;
|
|
66
|
+
}): Promise<unknown>;
|
|
67
|
+
private withBillingSupport;
|
|
68
|
+
listGenerationTasks(input: {
|
|
69
|
+
publicModelId?: string;
|
|
70
|
+
page?: number;
|
|
71
|
+
pageSize?: number;
|
|
72
|
+
}): Promise<unknown>;
|
|
58
73
|
getGenerationTask(input: {
|
|
59
74
|
taskId: string;
|
|
60
75
|
}, options?: AnnouncementOptions): Promise<unknown>;
|
package/dist/api-client.js
CHANGED
|
@@ -15,8 +15,8 @@ export class AiHiveApiError extends Error {
|
|
|
15
15
|
this.name = "AiHiveApiError";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
const GET_MODEL_UNSUPPORTED_MESSAGE = "
|
|
19
|
-
const DRY_RUN_UNSUPPORTED_MESSAGE = "
|
|
18
|
+
const GET_MODEL_UNSUPPORTED_MESSAGE = "当前接入暂时无法读取模型详情(get_model),请确认 API 地址和模型 ID;如果仍有问题,请联系技术支持。";
|
|
19
|
+
const DRY_RUN_UNSUPPORTED_MESSAGE = "当前接入暂不支持 dryRun,请联系技术支持确认预检方式;不要用正式生成代替预检。";
|
|
20
20
|
function formatIssue(value) {
|
|
21
21
|
if (typeof value === "string") {
|
|
22
22
|
return value;
|
|
@@ -93,6 +93,7 @@ export class AiHiveApiClient {
|
|
|
93
93
|
fetchImpl;
|
|
94
94
|
clientInfoProvider;
|
|
95
95
|
updateNotifier;
|
|
96
|
+
clientKind;
|
|
96
97
|
constructor(input) {
|
|
97
98
|
this.endpoint = input.endpoint.replace(/\/+$/, "");
|
|
98
99
|
this.tokenProvider = input.tokenProvider;
|
|
@@ -100,6 +101,7 @@ export class AiHiveApiClient {
|
|
|
100
101
|
this.fetchImpl = input.fetchImpl ?? fetch;
|
|
101
102
|
this.clientInfoProvider = input.clientInfoProvider;
|
|
102
103
|
this.updateNotifier = input.updateNotifier;
|
|
104
|
+
this.clientKind = input.clientKind;
|
|
103
105
|
}
|
|
104
106
|
async withTimeout(operation) {
|
|
105
107
|
const controller = new AbortController();
|
|
@@ -157,6 +159,7 @@ export class AiHiveApiClient {
|
|
|
157
159
|
signal,
|
|
158
160
|
headers: {
|
|
159
161
|
Authorization: `Bearer ${token}`,
|
|
162
|
+
...(this.clientKind ? { "x-ai-hive-client": this.clientKind } : {}),
|
|
160
163
|
...(encodedClientInfo
|
|
161
164
|
? { [MCP_CLIENT_INFO_HEADER]: encodedClientInfo }
|
|
162
165
|
: {}),
|
|
@@ -220,15 +223,33 @@ export class AiHiveApiClient {
|
|
|
220
223
|
});
|
|
221
224
|
}
|
|
222
225
|
async listModels(input, options) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
const buildPath = (withDetail) => {
|
|
227
|
+
const query = new URLSearchParams();
|
|
228
|
+
if (typeof input.modelType === "string") {
|
|
229
|
+
query.set("modelType", input.modelType);
|
|
230
|
+
}
|
|
231
|
+
if (withDetail && typeof input.detail === "string") {
|
|
232
|
+
query.set("detail", input.detail);
|
|
233
|
+
}
|
|
234
|
+
const serialized = query.toString();
|
|
235
|
+
return `/mcp/models${serialized ? `?${serialized}` : ""}`;
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
return redactInternalPricing(await this.request(buildPath(true), {
|
|
239
|
+
headers: announcementHeaders(options),
|
|
240
|
+
}));
|
|
226
241
|
}
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
catch (error) {
|
|
243
|
+
// 旧服务端的严格入参不认识 detail; 去掉它重试一次, 让新客户端的选型命令继续可用.
|
|
244
|
+
if (typeof input.detail === "string" &&
|
|
245
|
+
error instanceof AiHiveApiError &&
|
|
246
|
+
(error.status === 400 || error.status === 404)) {
|
|
247
|
+
return redactInternalPricing(await this.request(buildPath(false), {
|
|
248
|
+
headers: announcementHeaders(options),
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
229
252
|
}
|
|
230
|
-
const serialized = query.toString();
|
|
231
|
-
return redactInternalPricing(await this.request(`/mcp/models${serialized ? `?${serialized}` : ""}`, { headers: announcementHeaders(options) }));
|
|
232
253
|
}
|
|
233
254
|
// getModel 与 listModels 的 detail 参数依赖服务端新部署; 旧服务端会 404 或被
|
|
234
255
|
// strict 入参校验拒绝, 所以 npm 0.3.0 必须在服务端升级之后发布.
|
|
@@ -303,6 +324,65 @@ export class AiHiveApiClient {
|
|
|
303
324
|
throw error;
|
|
304
325
|
}
|
|
305
326
|
}
|
|
327
|
+
billingQuery(input) {
|
|
328
|
+
const query = new URLSearchParams();
|
|
329
|
+
if (input.from !== undefined)
|
|
330
|
+
query.set("from", input.from);
|
|
331
|
+
if (input.to !== undefined)
|
|
332
|
+
query.set("to", input.to);
|
|
333
|
+
for (const taskId of input.taskIds ?? [])
|
|
334
|
+
query.append("taskIds", taskId);
|
|
335
|
+
if (input.publicModelId !== undefined)
|
|
336
|
+
query.set("publicModelId", input.publicModelId);
|
|
337
|
+
if (input.invocationSource !== undefined)
|
|
338
|
+
query.set("invocationSource", input.invocationSource);
|
|
339
|
+
if (input.pageSize !== undefined)
|
|
340
|
+
query.set("pageSize", String(input.pageSize));
|
|
341
|
+
if (input.cursor !== undefined)
|
|
342
|
+
query.set("cursor", input.cursor);
|
|
343
|
+
return query.size ? `?${query}` : "";
|
|
344
|
+
}
|
|
345
|
+
getBillingSummary(input) {
|
|
346
|
+
return this.withBillingSupport("账单查询", () => this.request(`/mcp/billing/summary${this.billingQuery(input)}`));
|
|
347
|
+
}
|
|
348
|
+
listBillingRecords(input) {
|
|
349
|
+
return this.withBillingSupport("账单查询", () => this.request(`/mcp/billing/records${this.billingQuery(input)}`));
|
|
350
|
+
}
|
|
351
|
+
getBillingRecord(input) {
|
|
352
|
+
return this.withBillingSupport("账单详情", () => this.request(`/mcp/billing/records/${encodeURIComponent(input.recordId)}`), "账单记录不存在,或当前服务端暂不支持账单查询;请到控制台核对。");
|
|
353
|
+
}
|
|
354
|
+
// 0.4.0 会通过 @latest 自动升级到还没有账单接口的旧服务端, 不能把 404 原样抛给用户.
|
|
355
|
+
async withBillingSupport(feature, request, notFoundMessage = `当前服务端暂不支持${feature},请到控制台查看积分与消费。`) {
|
|
356
|
+
try {
|
|
357
|
+
return await request();
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (error instanceof AiHiveApiError &&
|
|
361
|
+
(error.status === 404 || error.status === 405)) {
|
|
362
|
+
throw new Error(notFoundMessage);
|
|
363
|
+
}
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async listGenerationTasks(input) {
|
|
368
|
+
const query = new URLSearchParams();
|
|
369
|
+
if (input.publicModelId !== undefined)
|
|
370
|
+
query.set("publicModelId", input.publicModelId);
|
|
371
|
+
if (input.page !== undefined)
|
|
372
|
+
query.set("page", String(input.page));
|
|
373
|
+
if (input.pageSize !== undefined)
|
|
374
|
+
query.set("pageSize", String(input.pageSize));
|
|
375
|
+
try {
|
|
376
|
+
return await this.request(`/mcp/generation/tasks${query.size ? `?${query}` : ""}`);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
if (error instanceof AiHiveApiError &&
|
|
380
|
+
[404, 405].includes(error.status ?? 0)) {
|
|
381
|
+
throw new Error("当前服务端暂不支持近期任务列表,请先按任务 ID 查询已有任务");
|
|
382
|
+
}
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
306
386
|
getGenerationTask(input, options) {
|
|
307
387
|
return this.request(`/mcp/generation/tasks/${encodeURIComponent(input.taskId)}?include=user-notices`, { headers: announcementHeaders(options) });
|
|
308
388
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { runServer as runMcpServer } from "./server.js";
|
|
|
3
3
|
import { WorkBuddyCredentialStore } from "./workbuddy-credentials.js";
|
|
4
4
|
import { WorkBuddyOAuthClient } from "./workbuddy-oauth-client.js";
|
|
5
5
|
import { listenForWorkBuddyCallback, type WorkBuddyResultRedirects } from "./workbuddy-loopback.js";
|
|
6
|
+
import { runManagedServer } from "./managed-server.js";
|
|
6
7
|
export type CliDependencies = {
|
|
7
8
|
stdout(line: string): void;
|
|
8
9
|
stderr(line: string): void;
|
|
@@ -10,6 +11,9 @@ export type CliDependencies = {
|
|
|
10
11
|
createOAuthClient(endpoint: string): WorkBuddyOAuthClient;
|
|
11
12
|
listenForCallback: typeof listenForWorkBuddyCallback;
|
|
12
13
|
runServer: typeof runMcpServer;
|
|
14
|
+
runManagedServer: typeof runManagedServer;
|
|
15
|
+
runSetup(config: McpClientConfig): Promise<number>;
|
|
16
|
+
runRestCommand(config: McpClientConfig): Promise<number>;
|
|
13
17
|
};
|
|
14
18
|
export declare function runCli(config: McpClientConfig, dependencies?: Partial<CliDependencies>): Promise<number>;
|
|
15
19
|
export declare function createWorkBuddyResultRedirects(webOrigin: string): WorkBuddyResultRedirects;
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
1
3
|
import { normalizeApiEndpoint } from "./auth-token-provider.js";
|
|
2
4
|
import { resolveServerAuth, runServer as runMcpServer } from "./server.js";
|
|
5
|
+
import { defaultRestCommandDependencies, runRestCommand, } from "./rest-commands.js";
|
|
6
|
+
import { runSetup } from "./setup/run-setup.js";
|
|
3
7
|
import { WorkBuddyCredentialStore, } from "./workbuddy-credentials.js";
|
|
4
8
|
import { WorkBuddyOAuthClient } from "./workbuddy-oauth-client.js";
|
|
5
9
|
import { listenForWorkBuddyCallback, } from "./workbuddy-loopback.js";
|
|
6
10
|
import { createPkcePair } from "./workbuddy-pkce.js";
|
|
11
|
+
import { loadManagedConfig } from "./managed-config.js";
|
|
12
|
+
import { runManagedServer } from "./managed-server.js";
|
|
7
13
|
const AUTH_URL_DEADLINE_MS = 10_000;
|
|
8
14
|
const AUTH_CALLBACK_DEADLINE_MS = 5 * 60_000;
|
|
9
15
|
const STATUS_DEADLINE_MS = 8_000;
|
|
@@ -28,6 +34,13 @@ export async function runCli(config, dependencies) {
|
|
|
28
34
|
return runLogout(config, resolved);
|
|
29
35
|
case "server":
|
|
30
36
|
return runMcp(config, resolved);
|
|
37
|
+
case "setup":
|
|
38
|
+
return resolved.runSetup(config);
|
|
39
|
+
case "models":
|
|
40
|
+
case "model":
|
|
41
|
+
case "usage":
|
|
42
|
+
case "run":
|
|
43
|
+
return resolved.runRestCommand(config);
|
|
31
44
|
}
|
|
32
45
|
}
|
|
33
46
|
function resolveDependencies(config, overrides) {
|
|
@@ -46,6 +59,19 @@ function resolveDependencies(config, overrides) {
|
|
|
46
59
|
: createOAuthClientWithTimeout,
|
|
47
60
|
listenForCallback: overrides?.listenForCallback ?? listenForWorkBuddyCallback,
|
|
48
61
|
runServer: overrides?.runServer ?? runMcpServer,
|
|
62
|
+
runManagedServer: overrides?.runManagedServer ?? runManagedServer,
|
|
63
|
+
runSetup: overrides?.runSetup ??
|
|
64
|
+
((setupConfig) => runSetup(setupConfig, {
|
|
65
|
+
home: homedir(),
|
|
66
|
+
platform: process.platform,
|
|
67
|
+
env: process.env,
|
|
68
|
+
// 编译产物在 dist/cli.js, 包根目录的同级 skills 随之发布.
|
|
69
|
+
skillsRoot: fileURLToPath(new URL("../skills", import.meta.url)),
|
|
70
|
+
stdout: overrides?.stdout ?? ((line) => console.log(line)),
|
|
71
|
+
stderr: overrides?.stderr ?? ((line) => console.error(line)),
|
|
72
|
+
})),
|
|
73
|
+
runRestCommand: overrides?.runRestCommand ??
|
|
74
|
+
((restConfig) => runRestCommand(restConfig, defaultRestCommandDependencies)),
|
|
49
75
|
};
|
|
50
76
|
}
|
|
51
77
|
async function runAuth(config, dependencies) {
|
|
@@ -296,6 +322,14 @@ async function revokeAndClear(config, dependencies, store, deadlineAt) {
|
|
|
296
322
|
}
|
|
297
323
|
async function runMcp(config, dependencies) {
|
|
298
324
|
try {
|
|
325
|
+
if (config.managedConfig) {
|
|
326
|
+
await dependencies.runManagedServer({
|
|
327
|
+
config: await loadManagedConfig(config.managedConfig),
|
|
328
|
+
managedConfigPath: config.managedConfig,
|
|
329
|
+
timeoutMs: config.timeoutMs,
|
|
330
|
+
});
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
299
333
|
const resolved = await resolveServerAuth(config, dependencies.createStore(config.credentialsFile));
|
|
300
334
|
await dependencies.runServer({
|
|
301
335
|
...(config.verbose ? { verbose: true } : {}),
|
package/dist/config.d.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
export type CliCommand = "server" | "auth" | "status" | "logout";
|
|
1
|
+
export type CliCommand = "server" | "auth" | "status" | "logout" | "setup" | "models" | "model" | "usage" | "run";
|
|
2
|
+
export type SetupClientId = "claude-code" | "cursor" | "codex" | "claude-desktop" | "opencode";
|
|
3
|
+
export declare const SETUP_CLIENT_IDS: readonly SetupClientId[];
|
|
4
|
+
export type SetupOptions = {
|
|
5
|
+
clients: SetupClientId[] | null;
|
|
6
|
+
dryRun: boolean;
|
|
7
|
+
skipSkills: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type ModelType = "TEXT" | "IMAGE" | "VIDEO";
|
|
2
10
|
export type McpClientConfig = {
|
|
3
11
|
command: CliCommand;
|
|
4
12
|
endpoint: string;
|
|
5
13
|
endpointExplicit: boolean;
|
|
6
14
|
webOrigin: string;
|
|
15
|
+
webOriginExplicit?: boolean;
|
|
7
16
|
key: string | null;
|
|
8
17
|
credentialsFile: string;
|
|
9
18
|
timeoutMs: number;
|
|
10
19
|
verbose: boolean;
|
|
20
|
+
managedConfig?: string;
|
|
21
|
+
positionals?: string[];
|
|
22
|
+
json?: boolean;
|
|
23
|
+
modelId?: string;
|
|
24
|
+
prompt?: string;
|
|
25
|
+
modelType?: ModelType;
|
|
26
|
+
detail?: "lite" | "full";
|
|
27
|
+
routingMode?: string;
|
|
28
|
+
setup?: SetupOptions;
|
|
11
29
|
};
|
|
12
30
|
export declare function parseConfig(input: {
|
|
13
31
|
argv: string[];
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { resolveWorkBuddyCredentialsFile } from "./workbuddy-credentials.js";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
export const SETUP_CLIENT_IDS = [
|
|
4
|
+
"claude-code",
|
|
5
|
+
"cursor",
|
|
6
|
+
"codex",
|
|
7
|
+
"claude-desktop",
|
|
8
|
+
"opencode",
|
|
9
|
+
];
|
|
2
10
|
const DEFAULT_ENDPOINT = "https://ai-hive.iclip.cn/api";
|
|
3
11
|
const DEFAULT_WEB_ORIGIN = "https://ai-hive.iclip.cn";
|
|
4
12
|
// 文本对话由上游同步返回, 长推理模型实测可达 157s+ (gpt-6/claude-sonnet 系列).
|
|
@@ -10,29 +18,74 @@ const CLI_COMMANDS = new Set([
|
|
|
10
18
|
"auth",
|
|
11
19
|
"status",
|
|
12
20
|
"logout",
|
|
21
|
+
"setup",
|
|
22
|
+
"models",
|
|
23
|
+
"model",
|
|
24
|
+
"usage",
|
|
25
|
+
"run",
|
|
13
26
|
]);
|
|
14
|
-
|
|
27
|
+
// setup 的目标由检测结果决定, 不接受位置参数, 避免误把路径当成客户端名.
|
|
28
|
+
const POSITIONAL_COMMANDS = new Set([
|
|
29
|
+
"models",
|
|
30
|
+
"model",
|
|
31
|
+
"usage",
|
|
32
|
+
"run",
|
|
33
|
+
]);
|
|
34
|
+
const FLAG_OPTIONS = new Set(["verbose", "dry-run", "skip-skills", "json"]);
|
|
15
35
|
const VALUE_OPTIONS = new Set([
|
|
16
36
|
"endpoint",
|
|
17
37
|
"web-origin",
|
|
18
38
|
"key",
|
|
19
39
|
"timeout-ms",
|
|
20
40
|
"credentials-file",
|
|
41
|
+
"managed-config",
|
|
42
|
+
"client",
|
|
43
|
+
"model",
|
|
44
|
+
"prompt",
|
|
45
|
+
"type",
|
|
46
|
+
"detail",
|
|
47
|
+
"routing-mode",
|
|
21
48
|
]);
|
|
49
|
+
function parseClientIds(value) {
|
|
50
|
+
const ids = value
|
|
51
|
+
.split(",")
|
|
52
|
+
.map((item) => item.trim())
|
|
53
|
+
.filter((item) => item.length > 0);
|
|
54
|
+
if (ids.length === 0) {
|
|
55
|
+
throw new Error("选项缺少值");
|
|
56
|
+
}
|
|
57
|
+
for (const id of ids) {
|
|
58
|
+
if (!SETUP_CLIENT_IDS.includes(id)) {
|
|
59
|
+
throw new Error("未知客户端");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return ids;
|
|
63
|
+
}
|
|
22
64
|
export function parseConfig(input) {
|
|
23
65
|
const args = input.argv.slice(2);
|
|
24
66
|
const values = new Map();
|
|
25
67
|
const flags = new Set();
|
|
68
|
+
const positionals = [];
|
|
26
69
|
let command = "server";
|
|
27
70
|
let commandSeen = false;
|
|
28
71
|
for (let index = 0; index < args.length; index += 1) {
|
|
29
72
|
const arg = args[index];
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
73
|
+
if (arg === undefined) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (!arg.startsWith("--")) {
|
|
77
|
+
if (!commandSeen) {
|
|
78
|
+
if (!CLI_COMMANDS.has(arg)) {
|
|
79
|
+
throw new Error("未知命令");
|
|
80
|
+
}
|
|
81
|
+
command = arg;
|
|
82
|
+
commandSeen = true;
|
|
83
|
+
continue;
|
|
33
84
|
}
|
|
34
|
-
command
|
|
35
|
-
|
|
85
|
+
if (!POSITIONAL_COMMANDS.has(command)) {
|
|
86
|
+
throw new Error("未知参数");
|
|
87
|
+
}
|
|
88
|
+
positionals.push(arg);
|
|
36
89
|
continue;
|
|
37
90
|
}
|
|
38
91
|
const name = arg.slice(2);
|
|
@@ -54,13 +107,22 @@ export function parseConfig(input) {
|
|
|
54
107
|
input.env.AI_HIVE_MCP_TIMEOUT_MS ??
|
|
55
108
|
String(DEFAULT_TIMEOUT_MS), 10);
|
|
56
109
|
const endpointOverride = values.get("endpoint") ?? input.env.AI_HIVE_API_ENDPOINT;
|
|
57
|
-
|
|
110
|
+
const managedConfig = values.get("managed-config");
|
|
111
|
+
if (managedConfig &&
|
|
112
|
+
(!isAbsolute(managedConfig) ||
|
|
113
|
+
command !== "server" ||
|
|
114
|
+
["key", "endpoint", "credentials-file", "web-origin"].some((name) => values.has(name)))) {
|
|
115
|
+
throw new Error("受管图片工具需要独立配置,请从 AI-HIVE 启动器打开");
|
|
116
|
+
}
|
|
117
|
+
const webOriginExplicit = values.has("web-origin") || input.env.AI_HIVE_WEB_ORIGIN !== undefined;
|
|
118
|
+
const config = {
|
|
58
119
|
command,
|
|
59
120
|
endpoint: endpointOverride ?? DEFAULT_ENDPOINT,
|
|
60
121
|
endpointExplicit: endpointOverride !== undefined,
|
|
61
122
|
webOrigin: values.get("web-origin") ??
|
|
62
123
|
input.env.AI_HIVE_WEB_ORIGIN ??
|
|
63
124
|
DEFAULT_WEB_ORIGIN,
|
|
125
|
+
...(webOriginExplicit ? { webOriginExplicit: true } : {}),
|
|
64
126
|
key: values.get("key") ?? input.env.AI_HIVE_MCP_KEY ?? null,
|
|
65
127
|
credentialsFile: values.get("credentials-file") ??
|
|
66
128
|
resolveWorkBuddyCredentialsFile(input.env),
|
|
@@ -68,5 +130,40 @@ export function parseConfig(input) {
|
|
|
68
130
|
? timeoutMs
|
|
69
131
|
: DEFAULT_TIMEOUT_MS,
|
|
70
132
|
verbose: flags.has("verbose") || input.env.AI_HIVE_MCP_VERBOSE === "true",
|
|
133
|
+
...(managedConfig ? { managedConfig } : {}),
|
|
71
134
|
};
|
|
135
|
+
if (POSITIONAL_COMMANDS.has(command)) {
|
|
136
|
+
config.positionals = positionals;
|
|
137
|
+
config.json = flags.has("json");
|
|
138
|
+
}
|
|
139
|
+
if (command === "setup") {
|
|
140
|
+
const clientValue = values.get("client");
|
|
141
|
+
config.setup = {
|
|
142
|
+
clients: clientValue ? parseClientIds(clientValue) : null,
|
|
143
|
+
dryRun: flags.has("dry-run"),
|
|
144
|
+
skipSkills: flags.has("skip-skills"),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (command === "models") {
|
|
148
|
+
const type = values.get("type");
|
|
149
|
+
if (type && !["TEXT", "IMAGE", "VIDEO"].includes(type)) {
|
|
150
|
+
throw new Error("参数无效");
|
|
151
|
+
}
|
|
152
|
+
config.modelType = type;
|
|
153
|
+
const detail = values.get("detail");
|
|
154
|
+
if (detail && !["lite", "full"].includes(detail)) {
|
|
155
|
+
throw new Error("参数无效");
|
|
156
|
+
}
|
|
157
|
+
config.detail = detail;
|
|
158
|
+
}
|
|
159
|
+
if (command === "model") {
|
|
160
|
+
config.modelId = values.get("model") ?? positionals[0];
|
|
161
|
+
config.routingMode = values.get("routing-mode");
|
|
162
|
+
}
|
|
163
|
+
if (command === "run") {
|
|
164
|
+
config.modelId = values.get("model") ?? positionals[0];
|
|
165
|
+
config.prompt = values.get("prompt") ?? positionals.slice(1).join(" ");
|
|
166
|
+
config.routingMode = values.get("routing-mode");
|
|
167
|
+
}
|
|
168
|
+
return config;
|
|
72
169
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,15 @@ import { runCli } from "./cli.js";
|
|
|
3
3
|
import { parseConfig } from "./config.js";
|
|
4
4
|
function safeTopLevelMessage(error) {
|
|
5
5
|
if (error instanceof Error &&
|
|
6
|
-
[
|
|
6
|
+
[
|
|
7
|
+
"未知命令",
|
|
8
|
+
"未知选项",
|
|
9
|
+
"选项缺少值",
|
|
10
|
+
"未知参数",
|
|
11
|
+
"未知客户端",
|
|
12
|
+
"参数无效",
|
|
13
|
+
"受管图片工具需要独立配置,请从 AI-HIVE 启动器打开",
|
|
14
|
+
].includes(error.message)) {
|
|
7
15
|
return error.message;
|
|
8
16
|
}
|
|
9
17
|
return "命令执行失败";
|