|
@@ -2,6 +2,7 @@ import { Command } from "commander";
|
|
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
|
import { join } from "node:path";
|
|
import { join } from "node:path";
|
|
|
import { findProjectRoot, log, logError, GLOBAL_CONFIG_DIR, GLOBAL_ENV_PATH } from "../utils.js";
|
|
import { findProjectRoot, log, logError, GLOBAL_CONFIG_DIR, GLOBAL_ENV_PATH } from "../utils.js";
|
|
|
|
|
+import { listModelsForService } from "@actalk/inkos-core";
|
|
|
|
|
|
|
|
export const configCommand = new Command("config")
|
|
export const configCommand = new Command("config")
|
|
|
.description("Manage project configuration");
|
|
.description("Manage project configuration");
|
|
@@ -295,3 +296,36 @@ configCommand
|
|
|
process.exit(1);
|
|
process.exit(1);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+// B17: list-models 命令 —— 列出指定 service 的可用模型(含元数据)
|
|
|
|
|
+configCommand
|
|
|
|
|
+ .command("list-models <service>")
|
|
|
|
|
+ .description("List available models for a service (with maxOutput / contextWindow / abilities)")
|
|
|
|
|
+ .option("--api-key <key>", "API Key (also reads from INKOS_LLM_API_KEY env)")
|
|
|
|
|
+ .option("--base-url <url>", "Live /models probe baseUrl (for custom/newapi)")
|
|
|
|
|
+ .option("--json", "Output as JSON")
|
|
|
|
|
+ .action(async (service: string, opts: { apiKey?: string; baseUrl?: string; json?: boolean }) => {
|
|
|
|
|
+ const apiKey = opts.apiKey ?? process.env.INKOS_LLM_API_KEY;
|
|
|
|
|
+ const models = await listModelsForService(service, apiKey, opts.baseUrl);
|
|
|
|
|
+ if (models.length === 0) {
|
|
|
|
|
+ logError(`${service} 没有可用模型(可能需要 --api-key 和 --base-url)`);
|
|
|
|
|
+ process.exit(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (opts.json) {
|
|
|
|
|
+ log(JSON.stringify(models, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log(`${service}:${models.length} 个模型\n`);
|
|
|
|
|
+ for (const m of models) {
|
|
|
|
|
+ const tags: string[] = [];
|
|
|
|
|
+ if (m.abilities?.reasoning) tags.push("R");
|
|
|
|
|
+ if (m.abilities?.vision) tags.push("V");
|
|
|
|
|
+ if (m.abilities?.functionCall) tags.push("F");
|
|
|
|
|
+ if (m.abilities?.search) tags.push("S");
|
|
|
|
|
+ const tagStr = tags.length ? ` [${tags.join("")}]` : "";
|
|
|
|
|
+ const maxOut = m.maxOutput ? `out=${m.maxOutput}` : "out=?";
|
|
|
|
|
+ const ctx = m.contextWindow > 0 ? `ctx=${m.contextWindow}` : "ctx=?";
|
|
|
|
|
+ log(` ${m.id.padEnd(42)} ${maxOut.padEnd(14)} ${ctx}${tagStr}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ log("\n标签:R=reasoning V=vision F=functionCall S=search");
|
|
|
|
|
+ });
|