mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-05 20:38:22 +00:00
fix(clients): update Hermes provider in place & improve client integration error feedback (#72)
* feat: cap proxied completion budget via configurable maxTokens Inject a default max_tokens (chat/completions) or max_output_tokens (responses) when a client omits one, so the SDK prices against completion x maxTokens instead of the provider's worst-case max_completion_cost. Default 64000; set 0 to disable. * Silence file logger during test runs bun test sets NODE_ENV=test, but tests import modules that pull in the logger singleton, causing test output to be written into the real ~/.routstrd log files alongside production daemon output. Early-return in writeLog when running under test. * Add SECURITY.md with vulnerability reporting policy * fix(clients): update Hermes provider in place and improve client integration error feedback - hermes: re-running 'clients add --hermes' now updates base_url/api_key/model in place instead of keeping a stale entry, and repoints model.provider when the provider name changes - clients: print a clear error on integration setup failure with a NIP-98 auth hint for rejected npubs, and exit non-zero if any integration failed - deps: bump @routstr/sdk to 0.3.21 --------- Co-authored-by: redshift <213178690+1ftredsh@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"@cashu/cashu-ts": "^4.3.0",
|
||||
"@cashu/coco-core": "^1.0.1",
|
||||
"@cashu/coco-sqlite-bun": "^1.0.1",
|
||||
"@routstr/sdk": "^0.3.20",
|
||||
"@routstr/sdk": "^0.3.21",
|
||||
"@scure/bip39": "^2.2.0",
|
||||
"applesauce-core": "^5.1.0",
|
||||
"applesauce-relay": "^5.1.0",
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
"@panva/hpke-noble": ["@panva/hpke-noble@1.1.3", "", { "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0", "@noble/post-quantum": "^0.6.1" }, "peerDependencies": { "hpke": "^1.0.0" } }, "sha512-zPG7MR9x7QE7+KdYsKBO9H0vp3AdYt9/4AT3ab7T7W6SL0fdRqhgNRu8q4OGTJNLeKpdbkkRb6LhBDaA9+9xWQ=="],
|
||||
|
||||
"@routstr/sdk": ["@routstr/sdk@0.3.20", "", { "dependencies": { "@cashu/cashu-ts": "^3.1.1", "applesauce-core": "^5.1.0", "applesauce-relay": "^5.1.0", "applesauce-sqlite": "^6.0.0", "ehbp": "^0.2.3", "rxjs": "^7.8.1", "tinfoil": "^1.1.6", "zustand": "^5.0.5" }, "optionalDependencies": { "better-sqlite3": "^12.10.0" }, "peerDependencies": { "typescript": ">=5.0.0" } }, "sha512-R7BjOT4LNZYk0ypebWKKm9CZD9PP66OvuhAZ7ABiCFcc8F0iynVFMTcz+mZMZldKO5RY+h+OqWa5L1u//B2aqQ=="],
|
||||
"@routstr/sdk": ["@routstr/sdk@0.3.21", "", { "dependencies": { "@cashu/cashu-ts": "^3.1.1", "applesauce-core": "^5.1.0", "applesauce-relay": "^5.1.0", "applesauce-sqlite": "^6.0.0", "ehbp": "^0.2.3", "rxjs": "^7.8.1", "tinfoil": "^1.1.6", "zustand": "^5.0.5" }, "optionalDependencies": { "better-sqlite3": "^12.10.0" }, "peerDependencies": { "typescript": ">=5.0.0" } }, "sha512-9QvwXvBM/crvOo1RZSkJFEY95P8FFaHY0XJI5SGYCv6EQtXjZAymF5wSbK5WzJSB6dgcaofYVUrg7xXRUiXr6Q=="],
|
||||
|
||||
"@scure/base": ["@scure/base@2.2.0", "", {}, "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg=="],
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ interface HermesCustomProvider {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** Hermes references a custom provider as `custom:<slug>`, where the slug is
|
||||
* the provider name lowercased with runs of whitespace collapsed to hyphens
|
||||
* (e.g. `Routstr (routstr.ft.hn)` -> `custom:routstr-(routstr.ft.hn)`). */
|
||||
function hermesProviderRef(name: string): string {
|
||||
return `custom:${name.toLowerCase().replace(/\s+/g, "-")}`;
|
||||
}
|
||||
|
||||
export function mergeHermesConfig(
|
||||
content: string,
|
||||
routstr: HermesRoutstrConfig,
|
||||
@@ -29,8 +36,9 @@ export function mergeHermesConfig(
|
||||
const urlDisplay = routstr.baseUrl
|
||||
.replace(/\/v1$/, "")
|
||||
.replace(/^https?:\/\//, "");
|
||||
const providerName = `Routstr (${urlDisplay})`;
|
||||
const provider = {
|
||||
name: `Routstr (${urlDisplay})`,
|
||||
name: providerName,
|
||||
base_url: routstr.baseUrl,
|
||||
api_key: routstr.apiKey,
|
||||
model: routstr.defaultModel,
|
||||
@@ -46,17 +54,48 @@ export function mergeHermesConfig(
|
||||
});
|
||||
}
|
||||
|
||||
// Replace an existing Routstr custom provider in place so that re-running
|
||||
// `clients add --hermes` after changing the daemon URL updates base_url /
|
||||
// api_key / model instead of silently keeping the stale entry. We only touch
|
||||
// the first matching entry; any other providers are left as-is.
|
||||
const existingConfig = document.toJS() as {
|
||||
custom_providers?: HermesCustomProvider[];
|
||||
};
|
||||
const existingProviders = existingConfig.custom_providers;
|
||||
const providers = Array.isArray(existingProviders) ? existingProviders : [];
|
||||
if (providers.some((item) => item.name?.startsWith("Routstr ("))) {
|
||||
return content;
|
||||
const providers = Array.isArray(existingProviders)
|
||||
? existingProviders.slice()
|
||||
: [];
|
||||
const routstrIndex = providers.findIndex(
|
||||
(item) => typeof item?.name === "string" && item.name.startsWith("Routstr ("),
|
||||
);
|
||||
let previousName: string | undefined;
|
||||
if (routstrIndex >= 0) {
|
||||
previousName = providers[routstrIndex]!.name;
|
||||
providers[routstrIndex] = { ...providers[routstrIndex], ...provider };
|
||||
} else {
|
||||
providers.push(provider);
|
||||
}
|
||||
providers.push(provider);
|
||||
document.set("custom_providers", providers);
|
||||
|
||||
// If we renamed the Routstr provider, keep `model.provider` pointing at it so
|
||||
// Hermes doesn't end up referencing a provider that no longer exists. We only
|
||||
// adjust configs whose default model already routes through a Routstr custom
|
||||
// provider, leaving any other selection untouched.
|
||||
if (
|
||||
!isNewConfig &&
|
||||
previousName &&
|
||||
previousName !== providerName &&
|
||||
document.hasIn(["model", "provider"])
|
||||
) {
|
||||
const currentRef = document.getIn(["model", "provider"]);
|
||||
if (
|
||||
typeof currentRef === "string" &&
|
||||
currentRef === hermesProviderRef(previousName)
|
||||
) {
|
||||
document.setIn(["model", "provider"], hermesProviderRef(providerName));
|
||||
}
|
||||
}
|
||||
|
||||
return document.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
callDaemon,
|
||||
loadConfig,
|
||||
getDaemonBaseUrl,
|
||||
getUserNpub,
|
||||
ensureDaemonRunning,
|
||||
} from "./daemon-client";
|
||||
import { logger } from "./logger";
|
||||
@@ -202,6 +203,7 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
|
||||
if (options.hermes) integrationKeys.push("hermes");
|
||||
|
||||
if (integrationKeys.length > 0) {
|
||||
let hadFailure = false;
|
||||
for (const key of integrationKeys) {
|
||||
const integrationFn = CLIENT_INTEGRATIONS[key];
|
||||
const integrationConfig = CLIENT_CONFIGS[key];
|
||||
@@ -222,15 +224,34 @@ export async function addClientAction(options: AddClientOptions): Promise<void>
|
||||
console.log(` Client ID: ${client.id}`);
|
||||
console.log(` API Key: ${client.apiKey}`);
|
||||
} catch (error) {
|
||||
const message = (error as Error)?.message ?? String(error);
|
||||
logger.error(
|
||||
`Failed to set up ${integrationConfig.name} integration:`,
|
||||
error,
|
||||
);
|
||||
console.error(
|
||||
`\n Error: failed to set up ${integrationConfig.name} integration.`,
|
||||
);
|
||||
console.error(` ${message}`);
|
||||
if (/NIP-98|registered npub\/pubkey|registered/i.test(message)) {
|
||||
const npub = getUserNpub(config);
|
||||
console.error(
|
||||
` The daemon at ${getDaemonBaseUrl(config)} rejected this account.`,
|
||||
);
|
||||
if (npub) {
|
||||
console.error(
|
||||
` Register/authorize this npub on the remote daemon first:`,
|
||||
);
|
||||
console.error(` ${npub}`);
|
||||
}
|
||||
}
|
||||
hadFailure = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n Access Routstr at: ${getDaemonBaseUrl(config)}/v1`);
|
||||
if (hadFailure) process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user