routstrd 0.3.8 → 0.3.10
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/bun.lock +5 -2
- package/dist/daemon/index.js +7054 -74
- package/dist/index.js +7046 -35
- package/package.json +3 -2
- package/src/integrations/hermes.ts +72 -29
- package/tests/integrations/hermes.test.ts +97 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@cashu/cashu-ts": "^4.3.0",
|
|
27
|
-
"@routstr/sdk": "^0.3.
|
|
27
|
+
"@routstr/sdk": "^0.3.17",
|
|
28
28
|
"applesauce-core": "^5.1.0",
|
|
29
29
|
"applesauce-relay": "^5.1.0",
|
|
30
30
|
"applesauce-wallet-connect": "^6.0.0",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"nostr-tools": "^2.12.0",
|
|
33
33
|
"qrcode": "^1.5.4",
|
|
34
34
|
"rxjs": "^7.8.1",
|
|
35
|
+
"yaml": "^2.9.0",
|
|
35
36
|
"zustand": "^5.0.5"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,10 +1,65 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "fs";
|
|
2
2
|
import { readFile, writeFile } from "fs/promises";
|
|
3
3
|
import { dirname } from "path";
|
|
4
|
+
import { parseDocument } from "yaml";
|
|
4
5
|
import type { RoutstrdConfig } from "../utils/config";
|
|
5
6
|
import type { IntegrationConfig, RoutstrModel } from "./registry";
|
|
6
7
|
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
|
|
7
8
|
|
|
9
|
+
interface HermesRoutstrConfig {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
defaultModel: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface HermesCustomProvider {
|
|
16
|
+
name?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function mergeHermesConfig(
|
|
21
|
+
content: string,
|
|
22
|
+
routstr: HermesRoutstrConfig,
|
|
23
|
+
): string {
|
|
24
|
+
const document = parseDocument(content || "{}", { prettyErrors: true });
|
|
25
|
+
if (document.errors.length > 0) {
|
|
26
|
+
throw document.errors[0];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const urlDisplay = routstr.baseUrl
|
|
30
|
+
.replace(/\/v1$/, "")
|
|
31
|
+
.replace(/^https?:\/\//, "");
|
|
32
|
+
const provider = {
|
|
33
|
+
name: `Routstr (${urlDisplay})`,
|
|
34
|
+
base_url: routstr.baseUrl,
|
|
35
|
+
api_key: routstr.apiKey,
|
|
36
|
+
model: routstr.defaultModel,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isNewConfig = content.trim() === "";
|
|
40
|
+
if (isNewConfig) {
|
|
41
|
+
document.set("model", {
|
|
42
|
+
default: routstr.defaultModel,
|
|
43
|
+
provider: "custom",
|
|
44
|
+
base_url: routstr.baseUrl,
|
|
45
|
+
api_key: routstr.apiKey,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const existingConfig = document.toJS() as {
|
|
50
|
+
custom_providers?: HermesCustomProvider[];
|
|
51
|
+
};
|
|
52
|
+
const existingProviders = existingConfig.custom_providers;
|
|
53
|
+
const providers = Array.isArray(existingProviders) ? existingProviders : [];
|
|
54
|
+
if (providers.some((item) => item.name?.startsWith("Routstr ("))) {
|
|
55
|
+
return content;
|
|
56
|
+
}
|
|
57
|
+
providers.push(provider);
|
|
58
|
+
document.set("custom_providers", providers);
|
|
59
|
+
|
|
60
|
+
return document.toString();
|
|
61
|
+
}
|
|
62
|
+
|
|
8
63
|
export async function installHermesIntegration(
|
|
9
64
|
config: RoutstrdConfig,
|
|
10
65
|
apiKey: string,
|
|
@@ -26,10 +81,10 @@ export async function installHermesIntegration(
|
|
|
26
81
|
|
|
27
82
|
if (models.length >= 3) {
|
|
28
83
|
defaultModel = models[2]!.id;
|
|
29
|
-
console.log(`
|
|
84
|
+
console.log(`Using 3rd available model for new Hermes configurations: ${defaultModel}`);
|
|
30
85
|
} else if (models.length > 0) {
|
|
31
86
|
defaultModel = models[0]!.id;
|
|
32
|
-
console.log(`Only ${models.length} models available, using ${defaultModel}
|
|
87
|
+
console.log(`Only ${models.length} models available, using ${defaultModel} for new Hermes configurations.`);
|
|
33
88
|
} else {
|
|
34
89
|
console.log("No models available from routstr daemon, using fallback default.");
|
|
35
90
|
}
|
|
@@ -47,36 +102,24 @@ export async function installHermesIntegration(
|
|
|
47
102
|
console.error(`Error reading ${configPath}, creating new one.`);
|
|
48
103
|
}
|
|
49
104
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
default: ${defaultModel}
|
|
61
|
-
provider: custom
|
|
62
|
-
base_url: ${baseUrlV1}
|
|
63
|
-
api_key: ${apiKey}`;
|
|
64
|
-
|
|
65
|
-
const providerBlock = `custom_providers:
|
|
66
|
-
- name: Routstr (${urlDisplay})
|
|
67
|
-
base_url: ${baseUrlV1}
|
|
68
|
-
api_key: ${apiKey}
|
|
69
|
-
model: ${defaultModel}`;
|
|
70
|
-
|
|
71
|
-
const parts: string[] = [modelBlock];
|
|
72
|
-
if (content) {
|
|
73
|
-
parts.push(content);
|
|
105
|
+
let newContent: string;
|
|
106
|
+
try {
|
|
107
|
+
newContent = mergeHermesConfig(content, {
|
|
108
|
+
baseUrl: baseUrlV1,
|
|
109
|
+
apiKey,
|
|
110
|
+
defaultModel,
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`Failed to parse ${configPath} as YAML; leaving it unchanged:`, error);
|
|
114
|
+
return;
|
|
74
115
|
}
|
|
75
|
-
parts.push(providerBlock);
|
|
76
|
-
|
|
77
|
-
const newContent = parts.join("\n\n") + "\n";
|
|
78
116
|
|
|
79
117
|
try {
|
|
118
|
+
if (newContent === content) {
|
|
119
|
+
console.log(`${configPath} already contains current routstr settings.`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
80
123
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
81
124
|
await writeFile(configPath, newContent);
|
|
82
125
|
console.log(`Successfully updated ${configPath} with routstr settings.`);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { parse } from "yaml";
|
|
3
|
+
import { mergeHermesConfig } from "../../src/integrations/hermes";
|
|
4
|
+
|
|
5
|
+
const ROUTSTR = {
|
|
6
|
+
baseUrl: "http://localhost:8008/v1",
|
|
7
|
+
apiKey: "routstr-key",
|
|
8
|
+
defaultModel: "deepseek-v4-flash",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("mergeHermesConfig", () => {
|
|
12
|
+
it("preserves the user's model and unrelated custom providers", () => {
|
|
13
|
+
const existing = `model:
|
|
14
|
+
default: user-model
|
|
15
|
+
provider: custom
|
|
16
|
+
base_url: https://user.example/v1
|
|
17
|
+
api_key: user-key
|
|
18
|
+
|
|
19
|
+
custom_providers:
|
|
20
|
+
- name: User Provider
|
|
21
|
+
base_url: https://user.example/v1
|
|
22
|
+
api_key: user-key
|
|
23
|
+
model: user-model
|
|
24
|
+
|
|
25
|
+
other_setting: true
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const merged = parse(mergeHermesConfig(existing, ROUTSTR));
|
|
29
|
+
|
|
30
|
+
expect(merged.model).toEqual({
|
|
31
|
+
default: "user-model",
|
|
32
|
+
provider: "custom",
|
|
33
|
+
base_url: "https://user.example/v1",
|
|
34
|
+
api_key: "user-key",
|
|
35
|
+
});
|
|
36
|
+
expect(merged.other_setting).toBe(true);
|
|
37
|
+
expect(merged.custom_providers).toContainEqual({
|
|
38
|
+
name: "User Provider",
|
|
39
|
+
base_url: "https://user.example/v1",
|
|
40
|
+
api_key: "user-key",
|
|
41
|
+
model: "user-model",
|
|
42
|
+
});
|
|
43
|
+
expect(merged.custom_providers).toContainEqual({
|
|
44
|
+
name: "Routstr (localhost:8008)",
|
|
45
|
+
base_url: ROUTSTR.baseUrl,
|
|
46
|
+
api_key: ROUTSTR.apiKey,
|
|
47
|
+
model: ROUTSTR.defaultModel,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does not add a model configuration to an existing Hermes file", () => {
|
|
52
|
+
const merged = parse(mergeHermesConfig("other_setting: true\n", ROUTSTR));
|
|
53
|
+
|
|
54
|
+
expect(merged.model).toBeUndefined();
|
|
55
|
+
expect(merged.other_setting).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("creates a Routstr default when creating a new Hermes file", () => {
|
|
59
|
+
const merged = parse(mergeHermesConfig("", ROUTSTR));
|
|
60
|
+
|
|
61
|
+
expect(merged.model).toEqual({
|
|
62
|
+
default: ROUTSTR.defaultModel,
|
|
63
|
+
provider: "custom",
|
|
64
|
+
base_url: ROUTSTR.baseUrl,
|
|
65
|
+
api_key: ROUTSTR.apiKey,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps an existing Routstr provider unchanged without duplicating it", () => {
|
|
70
|
+
const existing = `custom_providers:
|
|
71
|
+
- name: Routstr (old-host:8008)
|
|
72
|
+
base_url: http://old-host:8008/v1
|
|
73
|
+
api_key: old-key
|
|
74
|
+
model: old-model
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const once = mergeHermesConfig(existing, ROUTSTR);
|
|
78
|
+
const twice = mergeHermesConfig(once, ROUTSTR);
|
|
79
|
+
const merged = parse(twice);
|
|
80
|
+
const routstrProviders = merged.custom_providers.filter(
|
|
81
|
+
(provider: { name?: string }) => provider.name?.startsWith("Routstr ("),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(routstrProviders).toHaveLength(1);
|
|
85
|
+
expect(routstrProviders[0]).toEqual({
|
|
86
|
+
name: "Routstr (old-host:8008)",
|
|
87
|
+
base_url: "http://old-host:8008/v1",
|
|
88
|
+
api_key: "old-key",
|
|
89
|
+
model: "old-model",
|
|
90
|
+
});
|
|
91
|
+
expect(twice).toBe(once);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("rejects malformed YAML instead of replacing it", () => {
|
|
95
|
+
expect(() => mergeHermesConfig("model: [unterminated", ROUTSTR)).toThrow();
|
|
96
|
+
});
|
|
97
|
+
});
|