@theone1345/smartrelay 0.2.0

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 (64) hide show
  1. package/README.md +446 -0
  2. package/config/runners/agents.yaml +47 -0
  3. package/config/runners/anthropic.yaml +23 -0
  4. package/config/runners/nvidia.yaml +53 -0
  5. package/config/runners/ollama.yaml +33 -0
  6. package/config/runners/openai.yaml +23 -0
  7. package/config/runners/openrouter.yaml +83 -0
  8. package/config.yaml +16 -0
  9. package/dist/benchmark/engine.d.ts +49 -0
  10. package/dist/benchmark/engine.js +147 -0
  11. package/dist/benchmark/engine.js.map +1 -0
  12. package/dist/benchmark/scorers.d.ts +59 -0
  13. package/dist/benchmark/scorers.js +241 -0
  14. package/dist/benchmark/scorers.js.map +1 -0
  15. package/dist/http-api.d.ts +23 -0
  16. package/dist/http-api.js +329 -0
  17. package/dist/http-api.js.map +1 -0
  18. package/dist/index.d.ts +16 -0
  19. package/dist/index.js +17 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/logger.d.ts +22 -0
  22. package/dist/logger.js +34 -0
  23. package/dist/logger.js.map +1 -0
  24. package/dist/router.d.ts +33 -0
  25. package/dist/router.js +298 -0
  26. package/dist/router.js.map +1 -0
  27. package/dist/runners/anthropic.d.ts +15 -0
  28. package/dist/runners/anthropic.js +116 -0
  29. package/dist/runners/anthropic.js.map +1 -0
  30. package/dist/runners/base.d.ts +90 -0
  31. package/dist/runners/base.js +61 -0
  32. package/dist/runners/base.js.map +1 -0
  33. package/dist/runners/nvidia.d.ts +14 -0
  34. package/dist/runners/nvidia.js +23 -0
  35. package/dist/runners/nvidia.js.map +1 -0
  36. package/dist/runners/ollama.d.ts +8 -0
  37. package/dist/runners/ollama.js +107 -0
  38. package/dist/runners/ollama.js.map +1 -0
  39. package/dist/runners/openai.d.ts +28 -0
  40. package/dist/runners/openai.js +131 -0
  41. package/dist/runners/openai.js.map +1 -0
  42. package/dist/runners/openrouter.d.ts +11 -0
  43. package/dist/runners/openrouter.js +19 -0
  44. package/dist/runners/openrouter.js.map +1 -0
  45. package/dist/runners/registry.d.ts +53 -0
  46. package/dist/runners/registry.js +273 -0
  47. package/dist/runners/registry.js.map +1 -0
  48. package/dist/server.d.ts +16 -0
  49. package/dist/server.js +305 -0
  50. package/dist/server.js.map +1 -0
  51. package/dist/tools/handlers.d.ts +49 -0
  52. package/dist/tools/handlers.js +331 -0
  53. package/dist/tools/handlers.js.map +1 -0
  54. package/dist/tools/index.d.ts +1 -0
  55. package/dist/tools/index.js +2 -0
  56. package/dist/tools/index.js.map +1 -0
  57. package/dist/util.d.ts +47 -0
  58. package/dist/util.js +140 -0
  59. package/dist/util.js.map +1 -0
  60. package/package.json +70 -0
  61. package/prompts/code_review.md +99 -0
  62. package/prompts/explain_code.md +79 -0
  63. package/prompts/planner.md +23 -0
  64. package/prompts/test_generator.md +2 -0
@@ -0,0 +1,107 @@
1
+ /** Ollama local HTTP runner implementation. */
2
+ import { BaseRunner, makeRunnerResult, resolveParams } from './base.js';
3
+ import { describeError, startTimer } from '../util.js';
4
+ /** Node's fetch reports connection failures via `error.cause.code`. */
5
+ const CONNECT_ERROR_CODES = new Set([
6
+ 'ECONNREFUSED',
7
+ 'ENOTFOUND',
8
+ 'EHOSTUNREACH',
9
+ 'ENETUNREACH',
10
+ 'ECONNRESET',
11
+ 'EAI_AGAIN',
12
+ ]);
13
+ function isTimeout(error) {
14
+ return error instanceof DOMException && (error.name === 'TimeoutError' || error.name === 'AbortError');
15
+ }
16
+ function isConnectFailure(error) {
17
+ const cause = error?.cause;
18
+ return typeof cause?.code === 'string' && CONNECT_ERROR_CODES.has(cause.code);
19
+ }
20
+ /** Runner adapter for local LLMs served by Ollama (Llama 3, DeepSeek, Mistral, Qwen). */
21
+ export class OllamaRunner extends BaseRunner {
22
+ baseUrl;
23
+ constructor(config) {
24
+ super(config);
25
+ this.baseUrl = (config.base_url || 'http://localhost:11434').replace(/\/+$/, '');
26
+ }
27
+ async execute(task, params) {
28
+ const elapsed = startTimer();
29
+ const { maxTokens, temperature, systemPrompt, timeoutSeconds } = resolveParams(this.config, params);
30
+ const payload = {
31
+ model: this.model,
32
+ prompt: task,
33
+ stream: false,
34
+ options: {
35
+ temperature,
36
+ num_predict: maxTokens,
37
+ },
38
+ };
39
+ if (systemPrompt)
40
+ payload['system'] = systemPrompt;
41
+ try {
42
+ const response = await fetch(`${this.baseUrl}/api/generate`, {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify(payload),
46
+ // httpx takes seconds; AbortSignal.timeout takes milliseconds.
47
+ signal: AbortSignal.timeout(timeoutSeconds * 1000),
48
+ });
49
+ if (!response.ok) {
50
+ return makeRunnerResult({
51
+ runner_id: this.id,
52
+ model: this.model,
53
+ task,
54
+ latency_ms: elapsed(),
55
+ success: false,
56
+ error_message: `Ollama returned HTTP error (${response.status}): ${await response.text()}`,
57
+ });
58
+ }
59
+ const data = (await response.json());
60
+ const inputTokens = Number(data['prompt_eval_count'] ?? 0) || 0;
61
+ const outputTokens = Number(data['eval_count'] ?? 0) || 0;
62
+ return makeRunnerResult({
63
+ runner_id: this.id,
64
+ model: this.model,
65
+ task,
66
+ output: typeof data['response'] === 'string' ? data['response'] : '',
67
+ latency_ms: elapsed(),
68
+ input_tokens: inputTokens,
69
+ output_tokens: outputTokens,
70
+ // Local models cost 0.00 USD unless custom pricing is configured.
71
+ estimated_cost_usd: this.calculateCost(inputTokens, outputTokens),
72
+ success: true,
73
+ error_message: null,
74
+ raw_metadata: {
75
+ total_duration_ns: data['total_duration'] ?? null,
76
+ load_duration_ns: data['load_duration'] ?? null,
77
+ prompt_eval_duration_ns: data['prompt_eval_duration'] ?? null,
78
+ eval_duration_ns: data['eval_duration'] ?? null,
79
+ context: data['context'] ?? null,
80
+ },
81
+ });
82
+ }
83
+ catch (error) {
84
+ let message;
85
+ if (isTimeout(error)) {
86
+ message = `Request to Ollama timed out after ${timeoutSeconds} seconds.`;
87
+ }
88
+ else if (isConnectFailure(error)) {
89
+ message =
90
+ `Could not connect to Ollama server at '${this.baseUrl}'. ` +
91
+ "Ensure Ollama is installed and running ('ollama serve').";
92
+ }
93
+ else {
94
+ message = `Unexpected error communicating with Ollama: ${describeError(error)}`;
95
+ }
96
+ return makeRunnerResult({
97
+ runner_id: this.id,
98
+ model: this.model,
99
+ task,
100
+ latency_ms: elapsed(),
101
+ success: false,
102
+ error_message: message,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/runners/ollama.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** OpenAI API runner implementation. */
2
+ import { BaseRunner, type RunnerConfig, type RunnerResult } from './base.js';
3
+ /**
4
+ * Runner adapter for OpenAI models (GPT-4o, GPT-4o-mini, o3-mini, etc.).
5
+ *
6
+ * Serves as the base for every OpenAI-compatible gateway. Subclasses override
7
+ * the protected hooks below to change the base URL, credential env var, extra
8
+ * headers, and the provider name used in error messages.
9
+ */
10
+ export declare class OpenAIRunner extends BaseRunner {
11
+ protected readonly providerLabel: string;
12
+ protected readonly defaultEnvVar: string;
13
+ protected readonly defaultBaseUrl: string | null;
14
+ private client;
15
+ private clientKey;
16
+ constructor(config: RunnerConfig);
17
+ protected get envVarName(): string;
18
+ /** Extra headers to send with every request. */
19
+ protected extraHeaders(): Record<string, string> | undefined;
20
+ /** Message used when the request exceeds its deadline. */
21
+ protected timeoutMessage(timeoutSeconds: number): string;
22
+ /** Resolve the API key strictly from environment variables. */
23
+ protected getApiKey(): string | undefined;
24
+ private getClient;
25
+ execute(task: string, params?: Record<string, unknown> | null): Promise<RunnerResult>;
26
+ /** Map SDK exceptions onto the same messages the Python implementation produced. */
27
+ protected describeFailure(error: unknown, timeoutSeconds: number): string;
28
+ }
@@ -0,0 +1,131 @@
1
+ /** OpenAI API runner implementation. */
2
+ import OpenAI from 'openai';
3
+ import { BaseRunner, makeRunnerResult, resolveParams } from './base.js';
4
+ import { describeError, startTimer } from '../util.js';
5
+ /**
6
+ * Runner adapter for OpenAI models (GPT-4o, GPT-4o-mini, o3-mini, etc.).
7
+ *
8
+ * Serves as the base for every OpenAI-compatible gateway. Subclasses override
9
+ * the protected hooks below to change the base URL, credential env var, extra
10
+ * headers, and the provider name used in error messages.
11
+ */
12
+ export class OpenAIRunner extends BaseRunner {
13
+ providerLabel = 'OpenAI';
14
+ defaultEnvVar = 'OPENAI_API_KEY';
15
+ defaultBaseUrl = null;
16
+ client = null;
17
+ clientKey = null;
18
+ constructor(config) {
19
+ super(config);
20
+ }
21
+ get envVarName() {
22
+ return this.config.api_key_env || this.defaultEnvVar;
23
+ }
24
+ /** Extra headers to send with every request. */
25
+ extraHeaders() {
26
+ return undefined;
27
+ }
28
+ /** Message used when the request exceeds its deadline. */
29
+ timeoutMessage(timeoutSeconds) {
30
+ return `Request timed out after ${timeoutSeconds} seconds.`;
31
+ }
32
+ /** Resolve the API key strictly from environment variables. */
33
+ getApiKey() {
34
+ return process.env[this.envVarName] || undefined;
35
+ }
36
+ getClient(apiKey) {
37
+ if (this.client === null || this.clientKey !== apiKey) {
38
+ const baseURL = this.config.base_url || this.defaultBaseUrl;
39
+ const headers = this.extraHeaders();
40
+ this.client = new OpenAI({
41
+ apiKey,
42
+ ...(baseURL ? { baseURL } : {}),
43
+ ...(headers ? { defaultHeaders: headers } : {}),
44
+ });
45
+ this.clientKey = apiKey;
46
+ }
47
+ return this.client;
48
+ }
49
+ async execute(task, params) {
50
+ const elapsed = startTimer();
51
+ const { maxTokens, temperature, systemPrompt, timeoutSeconds } = resolveParams(this.config, params);
52
+ const apiKey = this.getApiKey();
53
+ if (!apiKey) {
54
+ return makeRunnerResult({
55
+ runner_id: this.id,
56
+ model: this.model,
57
+ task,
58
+ latency_ms: elapsed(),
59
+ success: false,
60
+ error_message: `Authentication error: Environment variable '${this.envVarName}' is not set. ` +
61
+ `Please export ${this.envVarName}=<your-key> to use runner '${this.id}'.`,
62
+ });
63
+ }
64
+ const timeoutMs = timeoutSeconds * 1000;
65
+ try {
66
+ const client = this.getClient(apiKey);
67
+ const messages = [];
68
+ if (systemPrompt)
69
+ messages.push({ role: 'system', content: systemPrompt });
70
+ messages.push({ role: 'user', content: task });
71
+ const response = await client.chat.completions.create({
72
+ model: this.model,
73
+ messages,
74
+ max_tokens: maxTokens,
75
+ temperature,
76
+ }, { timeout: timeoutMs, signal: AbortSignal.timeout(timeoutMs) });
77
+ const choice = response.choices[0];
78
+ const outputText = choice?.message.content || '';
79
+ const inputTokens = response.usage?.prompt_tokens ?? 0;
80
+ const outputTokens = response.usage?.completion_tokens ?? 0;
81
+ return makeRunnerResult({
82
+ runner_id: this.id,
83
+ model: this.model,
84
+ task,
85
+ output: outputText,
86
+ latency_ms: elapsed(),
87
+ input_tokens: inputTokens,
88
+ output_tokens: outputTokens,
89
+ estimated_cost_usd: this.calculateCost(inputTokens, outputTokens),
90
+ success: true,
91
+ error_message: null,
92
+ raw_metadata: {
93
+ finish_reason: choice ? choice.finish_reason : null,
94
+ usage: {
95
+ prompt_tokens: inputTokens,
96
+ completion_tokens: outputTokens,
97
+ total_tokens: response.usage?.total_tokens ?? 0,
98
+ },
99
+ system_fingerprint: response.system_fingerprint ?? null,
100
+ },
101
+ });
102
+ }
103
+ catch (error) {
104
+ return makeRunnerResult({
105
+ runner_id: this.id,
106
+ model: this.model,
107
+ task,
108
+ latency_ms: elapsed(),
109
+ success: false,
110
+ error_message: this.describeFailure(error, timeoutSeconds),
111
+ });
112
+ }
113
+ }
114
+ /** Map SDK exceptions onto the same messages the Python implementation produced. */
115
+ describeFailure(error, timeoutSeconds) {
116
+ if (error instanceof OpenAI.APIConnectionTimeoutError || error instanceof OpenAI.APIUserAbortError) {
117
+ return this.timeoutMessage(timeoutSeconds);
118
+ }
119
+ if (error instanceof OpenAI.AuthenticationError) {
120
+ return `${this.providerLabel} authentication failed: ${error.message}`;
121
+ }
122
+ if (error instanceof OpenAI.RateLimitError) {
123
+ return `${this.providerLabel} rate limit exceeded: ${error.message}`;
124
+ }
125
+ if (error instanceof OpenAI.APIError) {
126
+ return `${this.providerLabel} API error (${error.status}): ${error.message}`;
127
+ }
128
+ return `Unexpected error executing ${this.providerLabel} runner: ${describeError(error)}`;
129
+ }
130
+ }
131
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/runners/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** OpenRouter API runner implementation. */
2
+ import { OpenAIRunner } from './openai.js';
3
+ /** Runner adapter for models accessed via the OpenRouter API gateway. */
4
+ export declare class OpenRouterRunner extends OpenAIRunner {
5
+ static readonly DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
6
+ protected readonly providerLabel = "OpenRouter";
7
+ protected readonly defaultEnvVar = "OPENROUTER_API_KEY";
8
+ protected readonly defaultBaseUrl = "https://openrouter.ai/api/v1";
9
+ protected extraHeaders(): Record<string, string>;
10
+ protected timeoutMessage(timeoutSeconds: number): string;
11
+ }
@@ -0,0 +1,19 @@
1
+ /** OpenRouter API runner implementation. */
2
+ import { OpenAIRunner } from './openai.js';
3
+ /** Runner adapter for models accessed via the OpenRouter API gateway. */
4
+ export class OpenRouterRunner extends OpenAIRunner {
5
+ static DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1';
6
+ providerLabel = 'OpenRouter';
7
+ defaultEnvVar = 'OPENROUTER_API_KEY';
8
+ defaultBaseUrl = OpenRouterRunner.DEFAULT_BASE_URL;
9
+ extraHeaders() {
10
+ return {
11
+ 'HTTP-Referer': 'https://github.com/mcp-delegation-server',
12
+ 'X-Title': 'MCP Delegation Server',
13
+ };
14
+ }
15
+ timeoutMessage(timeoutSeconds) {
16
+ return `Request to OpenRouter timed out after ${timeoutSeconds} seconds.`;
17
+ }
18
+ }
19
+ //# sourceMappingURL=openrouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../src/runners/openrouter.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,yEAAyE;AACzE,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IAChD,MAAM,CAAU,gBAAgB,GAAG,8BAA8B,CAAC;IAEtC,aAAa,GAAG,YAAY,CAAC;IAC7B,aAAa,GAAG,oBAAoB,CAAC;IACrC,cAAc,GAAG,gBAAgB,CAAC,gBAAgB,CAAC;IAE5D,YAAY;QAC7B,OAAO;YACL,cAAc,EAAE,0CAA0C;YAC1D,SAAS,EAAE,uBAAuB;SACnC,CAAC;IACJ,CAAC;IAEkB,cAAc,CAAC,cAAsB;QACtD,OAAO,yCAAyC,cAAc,WAAW,CAAC;IAC5E,CAAC;CACF"}
@@ -0,0 +1,53 @@
1
+ /** Registry for discovering and managing configured LLM runners. */
2
+ import type { BaseRunner } from './base.js';
3
+ /** Structured metadata for a registered runner, as returned by `list_runners`. */
4
+ export interface RunnerMetadata {
5
+ runner_id: string;
6
+ type: string;
7
+ model: string;
8
+ pricing: {
9
+ cost_per_million_input_tokens: number;
10
+ cost_per_million_output_tokens: number;
11
+ };
12
+ timeout_seconds: number;
13
+ default_params: Record<string, unknown>;
14
+ credentials_env_var: string | null;
15
+ is_authenticated: boolean;
16
+ base_url: string | null;
17
+ }
18
+ /** Registry maintaining runner instances configured via config.yaml, with auto-reload. */
19
+ export declare class RunnerRegistry {
20
+ private runners;
21
+ private watchedFiles;
22
+ serverConfig: Record<string, unknown>;
23
+ configPath: string | null;
24
+ constructor(configPath?: string | null);
25
+ /** Locate the config file, searching the standard locations in order. */
26
+ static findConfigPath(customPath?: string | null): string;
27
+ /** Create and populate a registry from a YAML config file. */
28
+ static fromYaml(configPath?: string | null): RunnerRegistry;
29
+ /**
30
+ * Load or reload runners from the YAML config and any included files.
31
+ *
32
+ * On failure the previously loaded runners are left in place, matching the
33
+ * Python behavior of only swapping state in once parsing fully succeeds.
34
+ */
35
+ reload(): void;
36
+ /** Merge the `runners` block of one YAML file into the accumulated definitions. */
37
+ private mergeRunnerFile;
38
+ /** Reload if the config, any include, or any prompt file has changed on disk. */
39
+ reloadIfModified(): void;
40
+ /**
41
+ * Get a runner by ID, with auto-reload and alias matching.
42
+ *
43
+ * Resolution order is significant: exact, then case-insensitive, then
44
+ * provider-prefix add/strip, then match against the model name.
45
+ */
46
+ get(runnerId: string): BaseRunner | null;
47
+ /** All registered runners, keyed by ID. */
48
+ listRunners(): Map<string, BaseRunner>;
49
+ /** IDs of all registered runners, in config order. */
50
+ registeredIds(): string[];
51
+ /** Structured metadata for every registered runner. */
52
+ getRunnersMetadata(): RunnerMetadata[];
53
+ }
@@ -0,0 +1,273 @@
1
+ /** Registry for discovering and managing configured LLM runners. */
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { AnthropicRunner } from './anthropic.js';
6
+ import { NVIDIARunner } from './nvidia.js';
7
+ import { OllamaRunner } from './ollama.js';
8
+ import { OpenAIRunner } from './openai.js';
9
+ import { OpenRouterRunner } from './openrouter.js';
10
+ import { getLogger } from '../logger.js';
11
+ import { describeError, findProjectRoot, resolveUserPath } from '../util.js';
12
+ const logger = getLogger('mcp_delegation_server.registry');
13
+ const RUNNER_FACTORIES = {
14
+ anthropic: AnthropicRunner,
15
+ openai: OpenAIRunner,
16
+ ollama: OllamaRunner,
17
+ openrouter: OpenRouterRunner,
18
+ nvidia: NVIDIARunner,
19
+ };
20
+ /** Default credential env var per provider, used when a runner omits `api_key_env`. */
21
+ const DEFAULT_ENV_BY_TYPE = {
22
+ anthropic: 'ANTHROPIC_API_KEY',
23
+ openai: 'OPENAI_API_KEY',
24
+ openrouter: 'OPENROUTER_API_KEY',
25
+ nvidia: 'NVIDIA_API_KEY',
26
+ };
27
+ /** Provider prefixes tried when resolving a runner ID alias. */
28
+ const ALIAS_PREFIXES = ['openrouter-', 'ollama-', 'openai-', 'anthropic-'];
29
+ function isRecord(value) {
30
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
31
+ }
32
+ function mtimeOf(filePath) {
33
+ return statSync(filePath).mtimeMs;
34
+ }
35
+ /** Registry maintaining runner instances configured via config.yaml, with auto-reload. */
36
+ export class RunnerRegistry {
37
+ runners = new Map();
38
+ watchedFiles = new Map();
39
+ serverConfig = {};
40
+ configPath;
41
+ constructor(configPath = null) {
42
+ this.configPath = configPath;
43
+ }
44
+ /** Locate the config file, searching the standard locations in order. */
45
+ static findConfigPath(customPath) {
46
+ if (customPath) {
47
+ if (existsSync(customPath))
48
+ return resolveUserPath(customPath);
49
+ throw new Error(`Specified configuration file not found: ${customPath}`);
50
+ }
51
+ const envPath = process.env['MCP_CONFIG_PATH'];
52
+ if (envPath) {
53
+ if (existsSync(envPath))
54
+ return resolveUserPath(envPath);
55
+ throw new Error(`Configuration file specified by MCP_CONFIG_PATH not found: ${envPath}`);
56
+ }
57
+ const projectRoot = findProjectRoot();
58
+ const candidates = [
59
+ path.join(process.cwd(), 'config.yaml'),
60
+ path.join(process.cwd(), 'config.yml'),
61
+ path.join(projectRoot, 'config.yaml'),
62
+ path.join(projectRoot, 'config.yml'),
63
+ ];
64
+ for (const candidate of candidates) {
65
+ if (existsSync(candidate))
66
+ return path.resolve(candidate);
67
+ }
68
+ throw new Error('Could not locate config.yaml in current directory or project root. ' +
69
+ 'Set MCP_CONFIG_PATH environment variable to point to your config.yaml.');
70
+ }
71
+ /** Create and populate a registry from a YAML config file. */
72
+ static fromYaml(configPath) {
73
+ const registry = new RunnerRegistry(RunnerRegistry.findConfigPath(configPath));
74
+ registry.reload();
75
+ return registry;
76
+ }
77
+ /**
78
+ * Load or reload runners from the YAML config and any included files.
79
+ *
80
+ * On failure the previously loaded runners are left in place, matching the
81
+ * Python behavior of only swapping state in once parsing fully succeeds.
82
+ */
83
+ reload() {
84
+ if (!this.configPath || !existsSync(this.configPath))
85
+ return;
86
+ try {
87
+ const configDir = path.dirname(this.configPath);
88
+ const watched = new Map([[this.configPath, mtimeOf(this.configPath)]]);
89
+ const data = (parseYaml(readFileSync(this.configPath, 'utf-8')) ?? {});
90
+ this.serverConfig = isRecord(data['server']) ? data['server'] : {};
91
+ // Insertion order here determines the order of `list_runners`.
92
+ const runnerDefs = new Map();
93
+ if (isRecord(data['runners'])) {
94
+ for (const [id, def] of Object.entries(data['runners']))
95
+ runnerDefs.set(id, def);
96
+ }
97
+ const includes = data['includes'];
98
+ if (Array.isArray(includes)) {
99
+ for (const include of includes) {
100
+ if (typeof include !== 'string')
101
+ continue;
102
+ const includePath = path.isAbsolute(include) ? include : path.join(configDir, include);
103
+ if (existsSync(includePath) && statSync(includePath).isFile()) {
104
+ watched.set(includePath, mtimeOf(includePath));
105
+ this.mergeRunnerFile(includePath, runnerDefs);
106
+ }
107
+ else if (existsSync(includePath) && statSync(includePath).isDirectory()) {
108
+ const yamlFiles = readdirSync(includePath)
109
+ .filter((name) => /\.y.*ml$/.test(name))
110
+ .sort()
111
+ .map((name) => path.join(includePath, name));
112
+ for (const yamlFile of yamlFiles) {
113
+ watched.set(yamlFile, mtimeOf(yamlFile));
114
+ this.mergeRunnerFile(yamlFile, runnerDefs);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ const nextRunners = new Map();
120
+ for (const [runnerId, runnerInfo] of runnerDefs) {
121
+ if (!isRecord(runnerInfo))
122
+ continue;
123
+ const runnerType = runnerInfo['type'];
124
+ if (typeof runnerType !== 'string' || !runnerType)
125
+ continue;
126
+ const defaultParams = isRecord(runnerInfo['default_params'])
127
+ ? { ...runnerInfo['default_params'] }
128
+ : {};
129
+ const promptFile = defaultParams['system_prompt_file'] ?? runnerInfo['system_prompt_file'];
130
+ if (typeof promptFile === 'string' && promptFile) {
131
+ const promptPath = path.isAbsolute(promptFile) ? promptFile : path.join(configDir, promptFile);
132
+ if (existsSync(promptPath) && statSync(promptPath).isFile()) {
133
+ watched.set(promptPath, mtimeOf(promptPath));
134
+ try {
135
+ defaultParams['system_prompt'] = readFileSync(promptPath, 'utf-8').trim();
136
+ }
137
+ catch (error) {
138
+ logger.warning(`Could not read prompt file ${promptPath} for runner ${runnerId}: ${describeError(error)}`);
139
+ }
140
+ }
141
+ else {
142
+ logger.warning(`Prompt file ${promptPath} does not exist for runner ${runnerId}`);
143
+ }
144
+ }
145
+ const config = {
146
+ id: runnerId,
147
+ type: runnerType,
148
+ model: typeof runnerInfo['model'] === 'string' ? runnerInfo['model'] : runnerId,
149
+ api_key_env: typeof runnerInfo['api_key_env'] === 'string' ? runnerInfo['api_key_env'] : null,
150
+ base_url: typeof runnerInfo['base_url'] === 'string' ? runnerInfo['base_url'] : null,
151
+ cost_per_million_input_tokens: Number(runnerInfo['cost_per_million_input_tokens'] ?? 0) || 0,
152
+ cost_per_million_output_tokens: Number(runnerInfo['cost_per_million_output_tokens'] ?? 0) || 0,
153
+ default_params: defaultParams,
154
+ timeout_seconds: Number(runnerInfo['timeout_seconds'] ?? this.serverConfig['default_timeout_seconds'] ?? 60) || 60,
155
+ };
156
+ const Factory = RUNNER_FACTORIES[runnerType];
157
+ if (Factory)
158
+ nextRunners.set(runnerId, new Factory(config));
159
+ }
160
+ this.runners = nextRunners;
161
+ this.watchedFiles = watched;
162
+ logger.info(`Registry updated: loaded ${this.runners.size} runners from ${this.watchedFiles.size} files`);
163
+ }
164
+ catch (error) {
165
+ logger.error(`Failed to reload configuration: ${describeError(error)}`);
166
+ }
167
+ }
168
+ /** Merge the `runners` block of one YAML file into the accumulated definitions. */
169
+ mergeRunnerFile(filePath, into) {
170
+ try {
171
+ const parsed = (parseYaml(readFileSync(filePath, 'utf-8')) ?? {});
172
+ if (isRecord(parsed['runners'])) {
173
+ for (const [id, def] of Object.entries(parsed['runners']))
174
+ into.set(id, def);
175
+ }
176
+ }
177
+ catch (error) {
178
+ logger.warning(`Failed to load included config ${filePath}: ${describeError(error)}`);
179
+ }
180
+ }
181
+ /** Reload if the config, any include, or any prompt file has changed on disk. */
182
+ reloadIfModified() {
183
+ if (this.watchedFiles.size === 0) {
184
+ if (this.configPath && existsSync(this.configPath))
185
+ this.reload();
186
+ return;
187
+ }
188
+ try {
189
+ for (const [filePath, recordedMtime] of this.watchedFiles) {
190
+ if (!existsSync(filePath) || mtimeOf(filePath) > recordedMtime) {
191
+ this.reload();
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ catch {
197
+ // A transient stat failure should not take the server down.
198
+ }
199
+ }
200
+ /**
201
+ * Get a runner by ID, with auto-reload and alias matching.
202
+ *
203
+ * Resolution order is significant: exact, then case-insensitive, then
204
+ * provider-prefix add/strip, then match against the model name.
205
+ */
206
+ get(runnerId) {
207
+ this.reloadIfModified();
208
+ const exact = this.runners.get(runnerId);
209
+ if (exact)
210
+ return exact;
211
+ const lowered = runnerId.toLowerCase();
212
+ for (const [id, runner] of this.runners) {
213
+ if (id.toLowerCase() === lowered)
214
+ return runner;
215
+ }
216
+ for (const prefix of ALIAS_PREFIXES) {
217
+ const prefixed = this.runners.get(`${prefix}${runnerId}`);
218
+ if (prefixed)
219
+ return prefixed;
220
+ if (runnerId.startsWith(prefix)) {
221
+ const unprefixed = this.runners.get(runnerId.slice(prefix.length));
222
+ if (unprefixed)
223
+ return unprefixed;
224
+ }
225
+ }
226
+ for (const runner of this.runners.values()) {
227
+ if (runner.model.toLowerCase() === lowered)
228
+ return runner;
229
+ }
230
+ return null;
231
+ }
232
+ /** All registered runners, keyed by ID. */
233
+ listRunners() {
234
+ this.reloadIfModified();
235
+ return new Map(this.runners);
236
+ }
237
+ /** IDs of all registered runners, in config order. */
238
+ registeredIds() {
239
+ this.reloadIfModified();
240
+ return [...this.runners.keys()];
241
+ }
242
+ /** Structured metadata for every registered runner. */
243
+ getRunnersMetadata() {
244
+ this.reloadIfModified();
245
+ const metadata = [];
246
+ for (const [runnerId, runner] of this.runners) {
247
+ const cfg = runner.config;
248
+ let isReady = true;
249
+ if (cfg.api_key_env) {
250
+ isReady = Boolean(process.env[cfg.api_key_env]);
251
+ }
252
+ else if (DEFAULT_ENV_BY_TYPE[cfg.type]) {
253
+ isReady = Boolean(process.env[DEFAULT_ENV_BY_TYPE[cfg.type]]);
254
+ }
255
+ metadata.push({
256
+ runner_id: runnerId,
257
+ type: cfg.type,
258
+ model: cfg.model,
259
+ pricing: {
260
+ cost_per_million_input_tokens: cfg.cost_per_million_input_tokens,
261
+ cost_per_million_output_tokens: cfg.cost_per_million_output_tokens,
262
+ },
263
+ timeout_seconds: cfg.timeout_seconds,
264
+ default_params: cfg.default_params,
265
+ credentials_env_var: cfg.api_key_env,
266
+ is_authenticated: isReady,
267
+ base_url: cfg.base_url,
268
+ });
269
+ }
270
+ return metadata;
271
+ }
272
+ }
273
+ //# sourceMappingURL=registry.js.map