Files
jiang d58bd19039 feat(agent): unify models and validate OpenCode providers
Generate chat, task and learning models from agent-models.json.
Use WaterNetwork fast/expert models with 272k context and validate
upstream names before readiness. Add an isolated model check command
with signal-safe cleanup and plugin, Skill and MCP management docs.

Verified in isolation: type checks, 26 focused tests and both model
catalog checks; no model inference requests.
2026-10-09 11:28:37 +08:00

52 lines
2.5 KiB
TypeScript

import { randomBytes } from "node:crypto";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { config } from "../src/config.js";
import { assertModelCheckPassed, modelCheckReportSchema, modelCheckTargets } from "../src/runtime/model-check.js";
import { serviceOpencodeConfig } from "../src/runtime/opencode2-config.js";
import { startOpencode2 } from "../src/runtime/opencode2-server.js";
// Validate the saved configuration in a fresh OpenCode, without model inference.
const controller = new AbortController();
let root: string | undefined;
let runtime: Awaited<ReturnType<typeof startOpencode2>> | undefined;
const cancel = (exitCode: number) => {
if (controller.signal.aborted) return;
process.exitCode = exitCode;
controller.abort();
};
const onInterrupt = () => cancel(130);
const onTerminate = () => cancel(143);
process.on("SIGINT", onInterrupt);
process.on("SIGTERM", onTerminate);
try {
root = await mkdtemp(join(tmpdir(), "tjwater-model-check-"));
runtime = await startOpencode2({
command: config.OPENCODE_COMMAND, hostname: "127.0.0.1", port: 0, timeout: 30_000,
signal: controller.signal,
config: serviceOpencodeConfig(JSON.parse(await readFile("opencode.json", "utf8")), config.MODEL_CONFIG),
env: {
AGENT_INTERNAL_TOKEN: randomBytes(32).toString("hex"), TJWATER_AGENT_WORKSPACE_ROOT: join(root, "workspaces"),
XDG_DATA_HOME: join(root, "data"), XDG_CONFIG_HOME: join(root, "config"), XDG_CACHE_HOME: join(root, "cache"), XDG_STATE_HOME: join(root, "state"),
},
});
const result = await runtime.client.rpc.call({ rpcID: "tjwater.tools", method: "check_models", input: modelCheckTargets(config.MODEL_CONFIG), location: { directory: process.cwd() } }, { signal: AbortSignal.any([controller.signal, AbortSignal.timeout(30_000)]) });
const report = modelCheckReportSchema.parse(result.output);
console.log(JSON.stringify(report, null, 2));
assertModelCheckPassed(report);
console.log(`OpenCode model check passed: ${report.models.length} models; no inference requests.`);
} catch (error) {
if (controller.signal.aborted) console.error("OpenCode model check cancelled.");
else {
console.error(error instanceof Error ? error.message : "OpenCode model check failed");
process.exitCode = 1;
}
} finally {
runtime?.close();
if (runtime) await runtime.exited;
if (root) await rm(root, { recursive: true, force: true });
process.removeListener("SIGINT", onInterrupt);
process.removeListener("SIGTERM", onTerminate);
}