mosse-agent-bridge 0.1.4 → 0.1.6

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.
Files changed (45) hide show
  1. package/dist/acp/client.d.ts +47 -0
  2. package/dist/acp/client.js +94 -0
  3. package/dist/acp/client.js.map +1 -0
  4. package/dist/acp/index.d.ts +5 -0
  5. package/dist/acp/index.js +6 -0
  6. package/dist/acp/index.js.map +1 -0
  7. package/dist/acp/jsonrpc.d.ts +37 -0
  8. package/dist/acp/jsonrpc.js +120 -0
  9. package/dist/acp/jsonrpc.js.map +1 -0
  10. package/dist/acp/mock-server.d.ts +15 -0
  11. package/dist/acp/mock-server.js +100 -0
  12. package/dist/acp/mock-server.js.map +1 -0
  13. package/dist/acp/types.d.ts +138 -0
  14. package/dist/acp/types.js +20 -0
  15. package/dist/acp/types.js.map +1 -0
  16. package/dist/drivers/hermes-map.d.ts +34 -0
  17. package/dist/drivers/hermes-map.js +126 -0
  18. package/dist/drivers/hermes-map.js.map +1 -0
  19. package/dist/drivers/hermes.d.ts +66 -0
  20. package/dist/drivers/hermes.js +232 -0
  21. package/dist/drivers/hermes.js.map +1 -0
  22. package/dist/drivers/index.js +2 -0
  23. package/dist/drivers/index.js.map +1 -1
  24. package/dist/drivers/resolve.d.ts +3 -0
  25. package/dist/drivers/resolve.js +8 -0
  26. package/dist/drivers/resolve.js.map +1 -1
  27. package/dist/drivers/types.d.ts +13 -3
  28. package/dist/hermes-config.d.ts +33 -0
  29. package/dist/hermes-config.js +164 -0
  30. package/dist/hermes-config.js.map +1 -0
  31. package/dist/hermes-detect.d.ts +41 -0
  32. package/dist/hermes-detect.js +165 -0
  33. package/dist/hermes-detect.js.map +1 -0
  34. package/dist/hermes-read.d.ts +24 -0
  35. package/dist/hermes-read.js +76 -0
  36. package/dist/hermes-read.js.map +1 -0
  37. package/dist/hermes-test.d.ts +60 -0
  38. package/dist/hermes-test.js +176 -0
  39. package/dist/hermes-test.js.map +1 -0
  40. package/dist/index.js +156 -11
  41. package/dist/index.js.map +1 -1
  42. package/dist/memory.d.ts +9 -0
  43. package/dist/memory.js +12 -1
  44. package/dist/memory.js.map +1 -1
  45. package/package.json +20 -14
@@ -0,0 +1,164 @@
1
+ // Hermes 配置合并写(F3.3 / D7)—— Node 唯一实现,退役 Rust 直写(runtimes.rs 的 merge_config_yaml/merge_env)。
2
+ // 本机 + 远程宿主机统一走 daemon(Node)这一份合并逻辑,消除"两份 YAML/.env 合并保持等价"的维护风险。
3
+ // 纯函数(mergeConfigYaml/mergeEnv)逐行对标 Rust:只动目标子键、保留文件其它所有内容/缩进/注释。
4
+ // IO(writeHermesConfigToDisk):mkdir -p + 合并写 + .env chmod 0600 + 复用 hermes-detect 判定回 authReady。
5
+ // 安全:绝不日志 / 不返回 API key、不回显文件内容(只回 authReady 布尔)。
6
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { hermesAuthReady, providerEnvBaseUrlName, providerEnvKeyName, stripComment, } from "./hermes-detect.js";
9
+ /** Rust `.lines()` 等价:按 `\n` 切、去掉行尾 `\r`、丢弃因结尾换行产生的末尾空串。
10
+ * 与 JS `split("\n")` 的差异在于 `"a\n"` → `["a"]`(而非 `["a",""]`),保证写后幂等(不堆空行)。 */
11
+ function splitLines(s) {
12
+ if (s === "")
13
+ return [];
14
+ const parts = s.split("\n");
15
+ if (parts.at(-1) === "")
16
+ parts.pop();
17
+ return parts.map((l) => (l.endsWith("\r") ? l.slice(0, -1) : l));
18
+ }
19
+ /** YAML 标量:含特殊字符则加双引号(简单足够 model/provider/url)。对标 Rust `yaml_scalar`。 */
20
+ function yamlScalar(v) {
21
+ const needsQuote = v === "" || /[#:'"\n]/.test(v) || v.startsWith(" ") || v.endsWith(" ");
22
+ return needsQuote ? `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : v;
23
+ }
24
+ /** .env value:含空格/特殊字符则加双引号。对标 Rust `env_value`。 */
25
+ function envValue(v) {
26
+ const needsQuote = v === "" || /[ #"'\t\n]/.test(v) || v.startsWith(" ");
27
+ return needsQuote ? `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : v;
28
+ }
29
+ /** 离开 model 段前,把 wanted 里尚未写入(seen 没有)的子键追加进去。对标 Rust `flush_remaining`。 */
30
+ function flushRemaining(out, wanted, seen, indent) {
31
+ for (const [k, v] of wanted) {
32
+ if (!seen.has(k))
33
+ out.push(`${indent}${k}: ${yamlScalar(v)}`);
34
+ }
35
+ }
36
+ /**
37
+ * 纯函数:合并写 config.yaml 的 `model:` 段。对标 Rust `merge_config_yaml`(逐行等价)。
38
+ * - 设置/更新 `model.default`、`model.provider`,有 base_url 则设 `model.base_url`。
39
+ * - 保留文件里其它所有内容(其它顶层段、model 段下未触及的子键、缩进、注释)。
40
+ * - 文件无 `model:` 段则在末尾创建最小段。
41
+ */
42
+ export function mergeConfigYaml(existing, model, provider, baseUrl) {
43
+ const wanted = [
44
+ ["default", model],
45
+ ["provider", provider],
46
+ ];
47
+ if (baseUrl && baseUrl.trim() !== "")
48
+ wanted.push(["base_url", baseUrl]);
49
+ const lines = splitLines(existing);
50
+ const out = [];
51
+ const seen = new Set();
52
+ let inModel = false;
53
+ let modelSectionExists = false;
54
+ // model 段子键的缩进(沿用文件已有缩进,默认两空格)。
55
+ let childIndent = " ";
56
+ for (const raw of lines) {
57
+ const stripped = stripComment(raw);
58
+ const indent = stripped.length - stripped.trimStart().length;
59
+ const trimmed = stripped.trimStart();
60
+ if (indent === 0 && trimmed !== "") {
61
+ // 顶层键:离开旧 model 段前补齐未写入的 wanted 子键。
62
+ if (inModel) {
63
+ flushRemaining(out, wanted, seen, childIndent);
64
+ inModel = false;
65
+ }
66
+ if (trimmed.startsWith("model:")) {
67
+ inModel = true;
68
+ modelSectionExists = true;
69
+ seen.clear();
70
+ out.push(raw);
71
+ continue;
72
+ }
73
+ }
74
+ if (inModel && indent > 0 && trimmed !== "") {
75
+ // 记录该段真实缩进(沿用文件的),并判断是否是我们要设的子键。
76
+ childIndent = raw.slice(0, raw.length - raw.trimStart().length);
77
+ const hit = wanted.find(([k]) => trimmed.startsWith(`${k}:`));
78
+ if (hit) {
79
+ out.push(`${childIndent}${hit[0]}: ${yamlScalar(hit[1])}`);
80
+ seen.add(hit[0]);
81
+ continue;
82
+ }
83
+ }
84
+ out.push(raw);
85
+ }
86
+ // 文件结束时仍在 model 段:补齐剩余子键。
87
+ if (inModel)
88
+ flushRemaining(out, wanted, seen, childIndent);
89
+ // 无 model 段:在末尾新建最小段。
90
+ if (!modelSectionExists) {
91
+ out.push("model:");
92
+ for (const [k, v] of wanted)
93
+ out.push(` ${k}: ${yamlScalar(v)}`);
94
+ }
95
+ return `${out.join("\n")}\n`;
96
+ }
97
+ /**
98
+ * 纯函数:合并写 .env。对标 Rust `merge_env`。
99
+ * - 每个 {name,value}:文件已有同名 KEY(含 `export ` 前缀)→ 覆盖其值(保留 `export `);否则追加。
100
+ * - 其它行(不相关 key、注释、空行)原样保留。
101
+ */
102
+ export function mergeEnv(existing, pairs) {
103
+ const appliedNames = new Set();
104
+ const out = splitLines(existing).map((line) => {
105
+ const trimmedStart = line.replace(/^\s+/, "");
106
+ // 注释/空行不动。
107
+ if (trimmedStart === "" || trimmedStart.startsWith("#"))
108
+ return line;
109
+ const hasExport = trimmedStart.startsWith("export ");
110
+ const body = hasExport ? trimmedStart.slice("export ".length) : trimmedStart;
111
+ const eq = body.indexOf("=");
112
+ if (eq < 0)
113
+ return line;
114
+ const key = body.slice(0, eq).trim();
115
+ const hit = pairs.find((p) => p.name === key);
116
+ if (!hit)
117
+ return line;
118
+ appliedNames.add(hit.name);
119
+ return `${hasExport ? "export " : ""}${hit.name}=${envValue(hit.value)}`;
120
+ });
121
+ // 未命中的 key 追加到末尾。
122
+ for (const p of pairs) {
123
+ if (!appliedNames.has(p.name))
124
+ out.push(`${p.name}=${envValue(p.value)}`);
125
+ }
126
+ return `${out.join("\n")}\n`;
127
+ }
128
+ /**
129
+ * IO:合并写指定 HERMES_HOME 的 config.yaml + .env,.env chmod 0600,写后复用 hermes-detect 判 authReady。
130
+ * 对标 Rust `write_hermes_config`(但目录由调用方传入,本机/远程统一走此一份)。
131
+ * **绝不日志/返回 key**;失败返回 {ok:false,error}(不抛)。
132
+ */
133
+ export async function writeHermesConfigToDisk(hermesDir, opts) {
134
+ try {
135
+ await mkdir(hermesDir, { recursive: true });
136
+ // config.yaml:合并写 model 段(总是写)。
137
+ const cfgPath = join(hermesDir, "config.yaml");
138
+ const existingCfg = await readFile(cfgPath, "utf8").catch(() => "");
139
+ await writeFile(cfgPath, mergeConfigYaml(existingCfg, opts.model, opts.provider, opts.baseUrl));
140
+ // .env:合并写 <PROVIDER>_API_KEY(若有非空 key)+ <PROVIDER>_BASE_URL(若有)。
141
+ const envPath = join(hermesDir, ".env");
142
+ const pairs = [];
143
+ if (opts.apiKey && opts.apiKey.trim() !== "") {
144
+ pairs.push({ name: providerEnvKeyName(opts.provider), value: opts.apiKey });
145
+ }
146
+ if (opts.baseUrl && opts.baseUrl.trim() !== "") {
147
+ pairs.push({ name: providerEnvBaseUrlName(opts.provider), value: opts.baseUrl });
148
+ }
149
+ if (pairs.length > 0) {
150
+ const existingEnv = await readFile(envPath, "utf8").catch(() => "");
151
+ await writeFile(envPath, mergeEnv(existingEnv, pairs));
152
+ }
153
+ // 权限 0600(.env 含密钥);文件不存在则忽略(best-effort,不阻断)。
154
+ await chmod(envPath, 0o600).catch(() => { });
155
+ // 重探测 authReady(读回磁盘真相)。
156
+ const cfg = await readFile(cfgPath, "utf8").catch(() => "");
157
+ const env = await readFile(envPath, "utf8").catch(() => "");
158
+ return { ok: true, authReady: hermesAuthReady(cfg, env) };
159
+ }
160
+ catch (e) {
161
+ return { ok: false, authReady: false, error: e instanceof Error ? e.message : String(e) };
162
+ }
163
+ }
164
+ //# sourceMappingURL=hermes-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-config.js","sourceRoot":"","sources":["../src/hermes-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /** authReady 判定结果:是否就绪 + 解析到的 provider(用于诊断/未来 UI)。 */
2
+ export interface AuthVerdict {
3
+ authReady: boolean;
4
+ provider: string | null;
5
+ }
6
+ /** 去掉行尾注释(非引号场景):取第一个未被引号包裹的 `#` 之前。
7
+ * 导出供 hermes-config(合并写)复用,保证读/写两侧注释处理一致。 */
8
+ export declare function stripComment(line: string): string;
9
+ /**
10
+ * 极简 `KEY: value` 解析(只取顶层 `model:` 段下的标量字段,够 F1.2 判定用):
11
+ * 找到 `model:` 块后,读其下缩进的 `key: value`。处理注释(`#`)、引号、空行。
12
+ */
13
+ export declare function parseModelField(yaml: string, key: string): string | null;
14
+ /** 简易 .env 解析:逐行 `KEY=VALUE`,处理 `export ` 前缀、引号、注释、空行。 */
15
+ export declare function parseEnv(envText: string): [string, string][];
16
+ /**
17
+ * provider → 该 provider 的 key 在 .env 里的候选变量名。
18
+ * 覆盖常见 provider + auto;未知 provider 返回空 → 兜底"有任一 *_API_KEY 即视为有 key"。
19
+ * 映射与 Rust(provider_env_keys)一致。
20
+ */
21
+ export declare function providerEnvKeys(provider: string): string[];
22
+ /**
23
+ * provider 名转 env 变量片段:大写 + 非 [A-Z0-9] → `_`(与 Rust `env_token` 等价)。
24
+ * 例:`x.ai` → `X_AI`、`openai` → `OPENAI`。
25
+ */
26
+ export declare function envToken(provider: string): string;
27
+ /**
28
+ * provider → .env 里**写入**的主 key 变量名(写入用单值;读取候选见 `providerEnvKeys`)。
29
+ * 未知 provider 用 `<UPPER(provider)>_API_KEY`。与 Rust `provider_env_key_name` 等价。
30
+ */
31
+ export declare function providerEnvKeyName(provider: string): string;
32
+ /** provider → .env 里的 base_url 变量名(与 Rust `provider_env_base_url_name` 等价)。 */
33
+ export declare function providerEnvBaseUrlName(provider: string): string;
34
+ /**
35
+ * 纯函数:给定 config.yaml + .env 两份文本 → authReady。
36
+ * authReady = 有 model.default(或 model.model)& model.provider,
37
+ * 且对应 provider 的 key 存在(.env 的 <PROVIDER>_API_KEY 优先,或 yaml 的 model.api_key)。
38
+ */
39
+ export declare function hermesAuthReadyVerdict(configYaml: string, envText: string): AuthVerdict;
40
+ /** 纯函数(布尔):detectRuntimes 直接消费。 */
41
+ export declare function hermesAuthReady(configYaml: string, envText: string): boolean;
@@ -0,0 +1,165 @@
1
+ // Hermes authReady 判定(F1.2b)—— 与 daemon Rust(apps/desktop/src-tauri/src/runtimes.rs)同一逻辑。
2
+ // Hermes 的 LLM 真相源是本机文件:`${HERMES_HOME:-~/.hermes}/config.yaml`(设置)+ `.env`(密钥)。
3
+ // .env 优先于 yaml。判定全部抽成纯函数(输入=两份文本),IO(找文件/读 HERMES_HOME)由调用方薄封装。
4
+ // 不引 yaml 依赖(Hermes config 的 model 段结构浅且稳定)→ 简单浅层行解析,只取顶层 `model:` 段。
5
+ /** 去掉行尾注释(非引号场景):取第一个未被引号包裹的 `#` 之前。
6
+ * 导出供 hermes-config(合并写)复用,保证读/写两侧注释处理一致。 */
7
+ export function stripComment(line) {
8
+ let inS = false;
9
+ let inD = false;
10
+ let out = "";
11
+ for (const c of line) {
12
+ if (c === "'" && !inD)
13
+ inS = !inS;
14
+ else if (c === '"' && !inS)
15
+ inD = !inD;
16
+ else if (c === "#" && !inS && !inD)
17
+ break;
18
+ out += c;
19
+ }
20
+ return out;
21
+ }
22
+ /** 去引号 + trim。 */
23
+ function unquote(raw) {
24
+ const s = raw.trim();
25
+ if (s.length >= 2 &&
26
+ ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")))) {
27
+ return s.slice(1, -1);
28
+ }
29
+ return s;
30
+ }
31
+ /**
32
+ * 极简 `KEY: value` 解析(只取顶层 `model:` 段下的标量字段,够 F1.2 判定用):
33
+ * 找到 `model:` 块后,读其下缩进的 `key: value`。处理注释(`#`)、引号、空行。
34
+ */
35
+ export function parseModelField(yaml, key) {
36
+ let inModel = false;
37
+ for (const rawLine of yaml.split("\n")) {
38
+ const line = stripComment(rawLine);
39
+ if (line.trim() === "")
40
+ continue;
41
+ const indent = line.length - line.trimStart().length;
42
+ if (indent === 0) {
43
+ // 顶层键:进入/离开 model 段。
44
+ inModel = line.trimStart().startsWith("model:");
45
+ continue;
46
+ }
47
+ if (!inModel)
48
+ continue;
49
+ const t = line.trim();
50
+ const prefix = `${key}:`;
51
+ if (t.startsWith(prefix)) {
52
+ return unquote(t.slice(prefix.length).trim());
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /** 简易 .env 解析:逐行 `KEY=VALUE`,处理 `export ` 前缀、引号、注释、空行。 */
58
+ export function parseEnv(envText) {
59
+ const out = [];
60
+ for (const rawLine of envText.split("\n")) {
61
+ const trimmed = rawLine.trim();
62
+ if (trimmed === "" || trimmed.startsWith("#"))
63
+ continue;
64
+ const line = trimmed.startsWith("export ") ? trimmed.slice("export ".length) : trimmed;
65
+ const eq = line.indexOf("=");
66
+ if (eq < 0)
67
+ continue;
68
+ const k = line.slice(0, eq).trim();
69
+ if (k === "")
70
+ continue;
71
+ // value 可能带行尾注释(仅当未被引号包裹时);unquote 处理引号,先剥未引号注释。
72
+ const v = unquote(stripComment(line.slice(eq + 1)));
73
+ out.push([k, v]);
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * provider → 该 provider 的 key 在 .env 里的候选变量名。
79
+ * 覆盖常见 provider + auto;未知 provider 返回空 → 兜底"有任一 *_API_KEY 即视为有 key"。
80
+ * 映射与 Rust(provider_env_keys)一致。
81
+ */
82
+ export function providerEnvKeys(provider) {
83
+ switch (provider) {
84
+ case "anthropic":
85
+ return ["ANTHROPIC_API_KEY"];
86
+ case "openrouter":
87
+ case "auto":
88
+ return ["OPENROUTER_API_KEY"];
89
+ case "openai":
90
+ return ["OPENAI_API_KEY"];
91
+ case "nous":
92
+ return ["NOUS_API_KEY"];
93
+ case "gemini":
94
+ return ["GOOGLE_API_KEY", "GEMINI_API_KEY"];
95
+ default:
96
+ return [];
97
+ }
98
+ }
99
+ /**
100
+ * provider 名转 env 变量片段:大写 + 非 [A-Z0-9] → `_`(与 Rust `env_token` 等价)。
101
+ * 例:`x.ai` → `X_AI`、`openai` → `OPENAI`。
102
+ */
103
+ export function envToken(provider) {
104
+ let out = "";
105
+ for (const c of provider) {
106
+ const u = c.toUpperCase();
107
+ out += u.length === 1 && /[A-Z0-9]/.test(u) ? u : "_";
108
+ }
109
+ return out;
110
+ }
111
+ /**
112
+ * provider → .env 里**写入**的主 key 变量名(写入用单值;读取候选见 `providerEnvKeys`)。
113
+ * 未知 provider 用 `<UPPER(provider)>_API_KEY`。与 Rust `provider_env_key_name` 等价。
114
+ */
115
+ export function providerEnvKeyName(provider) {
116
+ switch (provider) {
117
+ case "anthropic":
118
+ return "ANTHROPIC_API_KEY";
119
+ case "openrouter":
120
+ case "auto":
121
+ return "OPENROUTER_API_KEY";
122
+ case "openai":
123
+ return "OPENAI_API_KEY";
124
+ case "nous":
125
+ return "NOUS_API_KEY";
126
+ case "gemini":
127
+ return "GOOGLE_API_KEY";
128
+ default:
129
+ return `${envToken(provider)}_API_KEY`;
130
+ }
131
+ }
132
+ /** provider → .env 里的 base_url 变量名(与 Rust `provider_env_base_url_name` 等价)。 */
133
+ export function providerEnvBaseUrlName(provider) {
134
+ return `${envToken(provider)}_BASE_URL`;
135
+ }
136
+ /**
137
+ * 纯函数:给定 config.yaml + .env 两份文本 → authReady。
138
+ * authReady = 有 model.default(或 model.model)& model.provider,
139
+ * 且对应 provider 的 key 存在(.env 的 <PROVIDER>_API_KEY 优先,或 yaml 的 model.api_key)。
140
+ */
141
+ export function hermesAuthReadyVerdict(configYaml, envText) {
142
+ // 模型:default 优先,兼容 model.model 写法。
143
+ const model = parseModelField(configYaml, "default") ?? parseModelField(configYaml, "model") ?? "";
144
+ const provider = parseModelField(configYaml, "provider") ?? "";
145
+ if (model === "" || provider === "") {
146
+ return { authReady: false, provider: null };
147
+ }
148
+ const envPairs = parseEnv(envText);
149
+ const envHas = (name) => envPairs.some(([k, v]) => k === name && v.trim() !== "");
150
+ // 1) .env 优先:provider 对应的具名 key 非空。
151
+ const named = providerEnvKeys(provider);
152
+ const keyInEnv = named.length === 0
153
+ ? // 未知 provider 兜底:有任一 *_API_KEY 非空即视为有 key。
154
+ envPairs.some(([k, v]) => k.endsWith("_API_KEY") && v.trim() !== "")
155
+ : named.some((n) => envHas(n));
156
+ // 2) yaml 退路:model.api_key 非空。
157
+ const apiKey = parseModelField(configYaml, "api_key");
158
+ const keyInYaml = apiKey !== null && apiKey.trim() !== "";
159
+ return { authReady: keyInEnv || keyInYaml, provider };
160
+ }
161
+ /** 纯函数(布尔):detectRuntimes 直接消费。 */
162
+ export function hermesAuthReady(configYaml, envText) {
163
+ return hermesAuthReadyVerdict(configYaml, envText).authReady;
164
+ }
165
+ //# sourceMappingURL=hermes-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-detect.js","sourceRoot":"","sources":["../src/hermes-detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** 回读视图:全部不含敏感数据(key 永不出现)。 */
2
+ export interface HermesConfigView {
3
+ /** .env 里有非空 `<PROVIDER>_API_KEY` 的 provider(去重、保序)。前端按此标 ✓。 */
4
+ configuredProviders: string[];
5
+ /** config.yaml 的 model.provider(无则 null)。 */
6
+ defaultProvider: string | null;
7
+ /** config.yaml 的 model.default(回退 model.model;无则 null)。 */
8
+ defaultModel: string | null;
9
+ /** .env 里的 `<PROVIDER>_BASE_URL`(provider → url),给 custom/本地类 provider 回填。 */
10
+ baseUrls: Record<string, string>;
11
+ }
12
+ /**
13
+ * `<PROVIDER>_API_KEY` env 变量名 → provider slug(写入侧 `providerEnvKeyName` 的反查)。
14
+ * 特殊命名先查具名表;其余按 `<TOKEN>_API_KEY` 取 TOKEN 小写(对标 `envToken` 的逆,best-effort)。
15
+ * 非 `*_API_KEY` 返回 null。
16
+ */
17
+ export declare function envKeyToProvider(envVarName: string): string | null;
18
+ /**
19
+ * 纯函数:给定 config.yaml + .env 两份文本 → 回读视图(无 key)。
20
+ * - configuredProviders:.env 里每个**非空** `*_API_KEY` 反查到的 provider(去重保序)。
21
+ * - defaultProvider / defaultModel:config.yaml 的 model.provider / model.default(空 → null)。
22
+ * - baseUrls:.env 里每个**非空** `*_BASE_URL` 反查到的 provider → url。
23
+ */
24
+ export declare function readHermesConfigFields(configYaml: string, envText: string): HermesConfigView;
@@ -0,0 +1,76 @@
1
+ // Hermes 配置回读(F4.3)—— 读 `${HERMES_HOME:-~/.hermes}/config.yaml` + `.env`,
2
+ // 给 AgentModal 回填:① 哪些 provider 已配 key(标 ✓)② 当前默认 provider/model ③ custom 类的 base_url。
3
+ // **绝不返回任何 API key / .env 原文**(只回派生的 provider 列表 + 默认 model/provider + base_url)。
4
+ // 纯函数(输入=两份文本),IO(读哪两份文件)由调用方(daemon)薄封装。
5
+ import { parseEnv, parseModelField } from "./hermes-detect.js";
6
+ /** 写入侧 `providerEnvKeyName` 的具名反查:把特殊命名规范回 canonical provider slug。 */
7
+ const REVERSE_KEY_SPECIAL = {
8
+ ANTHROPIC_API_KEY: "anthropic",
9
+ OPENROUTER_API_KEY: "openrouter", // auto 也写这个 → 回 canonical openrouter
10
+ OPENAI_API_KEY: "openai",
11
+ NOUS_API_KEY: "nous",
12
+ GOOGLE_API_KEY: "gemini",
13
+ GEMINI_API_KEY: "gemini",
14
+ };
15
+ /**
16
+ * `<PROVIDER>_API_KEY` env 变量名 → provider slug(写入侧 `providerEnvKeyName` 的反查)。
17
+ * 特殊命名先查具名表;其余按 `<TOKEN>_API_KEY` 取 TOKEN 小写(对标 `envToken` 的逆,best-effort)。
18
+ * 非 `*_API_KEY` 返回 null。
19
+ */
20
+ export function envKeyToProvider(envVarName) {
21
+ const special = REVERSE_KEY_SPECIAL[envVarName];
22
+ if (special)
23
+ return special;
24
+ const m = /^(.+)_API_KEY$/.exec(envVarName);
25
+ const token = m?.[1];
26
+ if (!token)
27
+ return null;
28
+ return token.toLowerCase();
29
+ }
30
+ /** `<PROVIDER>_BASE_URL` env 变量名 → provider slug(同上 best-effort)。非 `*_BASE_URL` 返回 null。 */
31
+ function envBaseUrlToProvider(envVarName) {
32
+ if (envVarName === "GEMINI_BASE_URL" || envVarName === "GOOGLE_BASE_URL")
33
+ return "gemini";
34
+ const m = /^(.+)_BASE_URL$/.exec(envVarName);
35
+ const token = m?.[1];
36
+ if (!token)
37
+ return null;
38
+ return token.toLowerCase();
39
+ }
40
+ /**
41
+ * 纯函数:给定 config.yaml + .env 两份文本 → 回读视图(无 key)。
42
+ * - configuredProviders:.env 里每个**非空** `*_API_KEY` 反查到的 provider(去重保序)。
43
+ * - defaultProvider / defaultModel:config.yaml 的 model.provider / model.default(空 → null)。
44
+ * - baseUrls:.env 里每个**非空** `*_BASE_URL` 反查到的 provider → url。
45
+ */
46
+ export function readHermesConfigFields(configYaml, envText) {
47
+ const pairs = parseEnv(envText);
48
+ const configuredProviders = [];
49
+ const seen = new Set();
50
+ for (const [k, v] of pairs) {
51
+ if (v.trim() === "")
52
+ continue;
53
+ const provider = envKeyToProvider(k);
54
+ if (provider && !seen.has(provider)) {
55
+ seen.add(provider);
56
+ configuredProviders.push(provider);
57
+ }
58
+ }
59
+ const baseUrls = {};
60
+ for (const [k, v] of pairs) {
61
+ if (v.trim() === "")
62
+ continue;
63
+ const provider = envBaseUrlToProvider(k);
64
+ if (provider)
65
+ baseUrls[provider] = v;
66
+ }
67
+ const provider = parseModelField(configYaml, "provider");
68
+ const model = parseModelField(configYaml, "default") ?? parseModelField(configYaml, "model");
69
+ return {
70
+ configuredProviders,
71
+ defaultProvider: provider && provider.trim() !== "" ? provider : null,
72
+ defaultModel: model && model.trim() !== "" ? model : null,
73
+ baseUrls,
74
+ };
75
+ }
76
+ //# sourceMappingURL=hermes-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-read.js","sourceRoot":"","sources":["../src/hermes-read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export interface HermesTestResult {
2
+ ok: boolean;
3
+ /** 失败时的**脱敏**短错误码/信息(绝不含 key)。 */
4
+ error?: string;
5
+ }
6
+ /**
7
+ * 脱敏错误:把所有非空 secret 子串替换成 `[redacted]`,折叠空白、截断长度。
8
+ * 纯函数,易单测。secrets 通常只有解析出的 apiKey。
9
+ */
10
+ export declare function sanitizeTestError(raw: string, secrets: string[]): string;
11
+ /**
12
+ * 解析「测试用」的 API key(纯函数):
13
+ * - 显式传了 key(测新 provider / 改 key)→ 用它。
14
+ * - 没传(测**已配** provider)→ 从机器现有 `~/.hermes/.env` 反查该 provider 的现有 key。
15
+ * - 都没有 → null(无 key 无从测)。
16
+ */
17
+ export declare function resolveTestApiKey(provider: string, providedKey: string | undefined, userEnvText: string): string | null;
18
+ export interface HermesTestDeps {
19
+ /** 建一个隔离的临时 HERMES_HOME 目录。 */
20
+ mkdtemp(): Promise<string>;
21
+ /** 把待测配置合并写进该目录(复用 writeHermesConfigToDisk)。 */
22
+ writeConfig(dir: string, opts: {
23
+ provider: string;
24
+ model: string;
25
+ apiKey?: string;
26
+ baseUrl?: string;
27
+ }): Promise<{
28
+ ok: boolean;
29
+ error?: string;
30
+ }>;
31
+ /** 读机器现有 `~/.hermes/.env`(给已配 provider 测时反查 key);不存在 → ""。 */
32
+ readUserEnv(): Promise<string>;
33
+ /** 跑 `hermes -z "ping"`(HERMES_HOME=dir):成败 + 失败明细(未脱敏,调用方脱敏)。 */
34
+ runPing(opts: {
35
+ hermesHome: string;
36
+ timeoutMs: number;
37
+ }): Promise<{
38
+ ok: boolean;
39
+ detail: string;
40
+ }>;
41
+ /** 删临时目录。 */
42
+ cleanup(dir: string): Promise<void>;
43
+ timeoutMs: number;
44
+ }
45
+ /** 生产默认依赖:真 fs + 真 spawn。 */
46
+ export declare function defaultHermesTestDeps(): HermesTestDeps;
47
+ /**
48
+ * 跑一次 Hermes 配置 Test(orchestrator,依赖注入):
49
+ * 1. 解析测试用 key(显式 or 机器现有);无 key → {ok:false,error:"no_api_key"}。
50
+ * 2. mkdtemp 隔离 HERMES_HOME → writeConfig 写这套配置。
51
+ * 3. `hermes -z "ping"` 真跑一次;成败回 {ok},失败明细**脱敏**(抹 key)。
52
+ * 4. finally 清临时目录。
53
+ * **绝不日志 / 不返回 key**。
54
+ */
55
+ export declare function runHermesConfigTest(input: {
56
+ provider: string;
57
+ model: string;
58
+ apiKey?: string;
59
+ baseUrl?: string;
60
+ }, deps: HermesTestDeps): Promise<HermesTestResult>;