feat(curator): switch Hermes Curator to DeepSeek V4-Pro via deepseek_local adapter
A/B test (2026-05-05) showed DeepSeek V4-Pro is 2-3x faster and ~20x cheaper than Sonnet for style/lexicon pattern analysis, with comparable quality. Adds adapters/deepseek-paperclip-adapter/ package, documents adapter requirements (env injection, run-id headers), updates CLAUDE.md with adapter integration notes, and records lessons from ערר 1200-25 (block order for 1xxx, "להלן מתוך" pattern, expanded factual background, bridge planning analysis, flat heading structure). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
164
adapters/deepseek-paperclip-adapter/dist/server/test.js
vendored
Normal file
164
adapters/deepseek-paperclip-adapter/dist/server/test.js
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Environment test for the DeepSeek (via Hermes) adapter.
|
||||
*/
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
HERMES_CLI,
|
||||
ADAPTER_TYPE,
|
||||
DEFAULT_PROFILE_HOME,
|
||||
} from "../shared/constants.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function asString(v) {
|
||||
return typeof v === "string" ? v : undefined;
|
||||
}
|
||||
|
||||
async function checkCliInstalled(command) {
|
||||
try {
|
||||
await execFileAsync(command, ["--version"], { timeout: 10_000 });
|
||||
return null;
|
||||
} catch (err) {
|
||||
if (err && err.code === "ENOENT") {
|
||||
return {
|
||||
level: "error",
|
||||
message: `Hermes CLI "${command}" not found in PATH`,
|
||||
hint: "Install Hermes Agent: pip install hermes-agent",
|
||||
code: "deepseek_hermes_cli_not_found",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkProfile(profileHome) {
|
||||
try {
|
||||
const stat = await fs.stat(profileHome);
|
||||
if (!stat.isDirectory()) {
|
||||
return {
|
||||
level: "error",
|
||||
message: `Profile path is not a directory: ${profileHome}`,
|
||||
hint: "Create the directory or override hermesProfileHome in adapter config.",
|
||||
code: "deepseek_profile_not_dir",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
level: "error",
|
||||
message: `Hermes profile dir does not exist: ${profileHome}`,
|
||||
hint: "Create the profile dir with config.yaml + .env (DEEPSEEK_API_KEY).",
|
||||
code: "deepseek_profile_missing",
|
||||
};
|
||||
}
|
||||
|
||||
const configPath = path.join(profileHome, "config.yaml");
|
||||
try {
|
||||
await fs.stat(configPath);
|
||||
} catch {
|
||||
return {
|
||||
level: "error",
|
||||
message: `Profile is missing config.yaml: ${configPath}`,
|
||||
hint: "Add config.yaml with model.default + model.base_url + model.key_env.",
|
||||
code: "deepseek_profile_no_config",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
level: "info",
|
||||
message: `Profile resolved: ${profileHome}`,
|
||||
code: "deepseek_profile_ok",
|
||||
};
|
||||
}
|
||||
|
||||
async function checkApiKey(profileHome, configEnv) {
|
||||
// 1. config.env (resolved by Paperclip from secrets)
|
||||
if (configEnv && typeof configEnv === "object" && asString(configEnv.DEEPSEEK_API_KEY)) {
|
||||
return {
|
||||
level: "info",
|
||||
message: "DEEPSEEK_API_KEY found in adapter env config",
|
||||
code: "deepseek_api_key_in_config",
|
||||
};
|
||||
}
|
||||
// 2. Profile-local .env
|
||||
try {
|
||||
const envFile = path.join(profileHome, ".env");
|
||||
const text = await fs.readFile(envFile, "utf-8");
|
||||
if (/^\s*DEEPSEEK_API_KEY=/m.test(text)) {
|
||||
return {
|
||||
level: "info",
|
||||
message: `DEEPSEEK_API_KEY found in ${envFile}`,
|
||||
code: "deepseek_api_key_in_profile",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
// 3. Process env
|
||||
if (process.env.DEEPSEEK_API_KEY) {
|
||||
return {
|
||||
level: "info",
|
||||
message: "DEEPSEEK_API_KEY found in Paperclip process env",
|
||||
code: "deepseek_api_key_in_process",
|
||||
};
|
||||
}
|
||||
return {
|
||||
level: "error",
|
||||
message: "DEEPSEEK_API_KEY not found in adapter env, profile .env, or process env",
|
||||
hint: "Add DEEPSEEK_API_KEY to <HERMES_HOME>/.env or to the agent's env secrets.",
|
||||
code: "deepseek_api_key_missing",
|
||||
};
|
||||
}
|
||||
|
||||
export async function testEnvironment(ctx) {
|
||||
const config = ctx.config ?? {};
|
||||
const command = asString(config.hermesCommand) || HERMES_CLI;
|
||||
const profileHome = asString(config.hermesProfileHome) || DEFAULT_PROFILE_HOME;
|
||||
const checks = [];
|
||||
|
||||
const cliCheck = await checkCliInstalled(command);
|
||||
if (cliCheck) {
|
||||
checks.push(cliCheck);
|
||||
if (cliCheck.level === "error") {
|
||||
return {
|
||||
adapterType: ADAPTER_TYPE,
|
||||
status: "fail",
|
||||
checks,
|
||||
testedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const profileCheck = await checkProfile(profileHome);
|
||||
checks.push(profileCheck);
|
||||
if (profileCheck.level === "error") {
|
||||
return {
|
||||
adapterType: ADAPTER_TYPE,
|
||||
status: "fail",
|
||||
checks,
|
||||
testedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
const apiKeyCheck = await checkApiKey(profileHome, config.env);
|
||||
checks.push(apiKeyCheck);
|
||||
|
||||
const model = asString(config.model);
|
||||
checks.push({
|
||||
level: "info",
|
||||
message: model ? `Model: ${model}` : "Using profile default model",
|
||||
code: "deepseek_model",
|
||||
});
|
||||
|
||||
const hasErrors = checks.some((c) => c.level === "error");
|
||||
const hasWarnings = checks.some((c) => c.level === "warn");
|
||||
return {
|
||||
adapterType: ADAPTER_TYPE,
|
||||
status: hasErrors ? "fail" : hasWarnings ? "warn" : "pass",
|
||||
checks,
|
||||
testedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user