lynkr 9.14.11 → 9.14.13

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/README.md CHANGED
@@ -211,6 +211,7 @@ Claude Code / Cursor / Codex / Cline / Continue
211
211
  | **llama.cpp** | Local | Any GGUF model | **Free** |
212
212
  | **LM Studio** | Local | Local models with GUI | **Free** |
213
213
  | **OpenRouter** | Cloud | GPT-4o, Claude 3.5, Llama 3, Gemini | $ |
214
+ | **OrcaRouter** | Cloud | Chat, reasoning, image, video (one endpoint, live catalog) | $ (zero-markup) |
214
215
  | **AWS Bedrock** | Cloud | Claude, Llama, Mistral, Titan | $$ |
215
216
  | **Databricks** | Cloud | Claude Sonnet 4.5, Opus 4.6 | $$$ |
216
217
  | **Azure OpenAI** | Cloud | GPT-4o, o1, o3 | $$$ |
package/bin/cli.js CHANGED
@@ -15,6 +15,7 @@ const SUBCOMMANDS = {
15
15
  "desktop-token": path.join(__dirname, "lynkr-desktop-token.js"),
16
16
  restart: path.join(__dirname, "lynkr-restart.js"),
17
17
  run: path.join(__dirname, "run.js"),
18
+ connect: path.join(__dirname, "lynkr-connect-orcarouter.js"),
18
19
  };
19
20
 
20
21
  const sub = process.argv[2];
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `lynkr connect orcarouter` — OAuth 2.0 + PKCE (Flow B, out-of-band) login.
4
+ *
5
+ * Lynkr is self-hosted software whose install address differs on every
6
+ * deployment (LAN box, NAS, moved port), so there is no predictable loopback
7
+ * port to register — Flow B (out-of-band code) exists precisely for that
8
+ * case. The command:
9
+ *
10
+ * 1. Generates a fresh PKCE verifier + CSRF state (crypto RNG, per attempt).
11
+ * 2. Prints an authorize URL (callback_url=oob, S256 challenge) and opens
12
+ * it in the default browser when possible.
13
+ * 3. Asks for the one-time code shown on the consent screen.
14
+ * 4. Exchanges it at www.orcarouter.ai/api/v1/auth/keys for a durable
15
+ * OrcaRouter API key that belongs to the user.
16
+ * 5. Persists the key to the project's .env (same secret location as every
17
+ * other Lynkr provider key) and prints the masked status.
18
+ *
19
+ * The verifier never leaves the process until the exchange; it never appears
20
+ * in a URL, a log, or an error. Auth requests hit the auth origin
21
+ * (www.orcarouter.ai) only; inference stays on api.orcarouter.ai/v1.
22
+ *
23
+ * Usage:
24
+ * lynkr connect orcarouter # interactive OOB code login
25
+ * lynkr connect orcarouter --code <code> # non-interactive (CI / GUI)
26
+ * lynkr connect orcarouter --status # masked status of stored key
27
+ * lynkr connect orcarouter --clear # remove the stored key
28
+ *
29
+ * @module bin/lynkr-connect-orcarouter
30
+ */
31
+
32
+ const path = require("path");
33
+ const fs = require("fs");
34
+ const readline = require("readline");
35
+ const { exec } = require("child_process");
36
+
37
+ const ROOT = path.join(__dirname, "..");
38
+ const ENV_PATH = path.join(ROOT, ".env");
39
+ const {
40
+ connectWithPkce,
41
+ getCredentialStore,
42
+ createApiKeyAdapter,
43
+ maskKey,
44
+ resolveAuthBase,
45
+ resolveApiBase,
46
+ } = require("../src/clients/orcarouter-credentials");
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // .env persistence — the project's existing secret location. No new secret
50
+ // store is introduced. Keys are written as `ORCAROUTER_API_KEY=...` alongside
51
+ // the other provider keys; existing keys are updated in place.
52
+ // ---------------------------------------------------------------------------
53
+
54
+ function readEnvLines() {
55
+ try {
56
+ return fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, "utf8").split(/\r?\n/) : [];
57
+ } catch {
58
+ return [];
59
+ }
60
+ }
61
+
62
+ function writeEnvLines(lines) {
63
+ fs.writeFileSync(ENV_PATH, lines.join("\n") + "\n", { mode: 0o600 });
64
+ // mode only applies on creation — tighten existing files explicitly so a
65
+ // previously 0644 .env doesn't leave the new OAuth key world-readable.
66
+ try {
67
+ fs.chmodSync(ENV_PATH, 0o600);
68
+ } catch {
69
+ /* best-effort: e.g. read-only FS in CI */
70
+ }
71
+ }
72
+
73
+ function readEnvKey(key) {
74
+ try {
75
+ const lines = readEnvLines();
76
+ for (const line of lines) {
77
+ const m = line.match(/^([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
78
+ if (m && m[1] === key) {
79
+ const v = (m[2] || "").trim();
80
+ return v || null;
81
+ }
82
+ }
83
+ } catch {
84
+ /* missing/unreadable .env → no file-backed value */
85
+ }
86
+ return null;
87
+ }
88
+
89
+ // File-backed env for status: process.env wins, .env file is the fallback so
90
+ // a key saved by a previous `connect` process is visible without re-export.
91
+ function statusEnv() {
92
+ const env = { ...process.env };
93
+ for (const k of ["ORCAROUTER_API_KEY", "ORCA_API_BASE_URL", "ORCA_AUTH_BASE_URL", "ORCA_BASE_URL"]) {
94
+ if (!env[k]) {
95
+ const v = readEnvKey(k);
96
+ if (v) env[k] = v;
97
+ }
98
+ }
99
+ return env;
100
+ }
101
+
102
+ function upsertEnvKey(key, value) {
103
+ const lines = readEnvLines();
104
+ let replaced = false;
105
+ const out = lines.map((line) => {
106
+ const m = line.match(/^([A-Z0-9_]+)\s*=/);
107
+ if (m && m[1] === key) {
108
+ replaced = true;
109
+ return `${key}=${value}`;
110
+ }
111
+ return line;
112
+ });
113
+ if (!replaced) out.push(`${key}=${value}`);
114
+ writeEnvLines(out);
115
+ }
116
+
117
+ function clearEnvKey(_key) {
118
+ const lines = readEnvLines().filter((line) => !line.match(/^ORCAROUTER_API_KEY\s*=/));
119
+ writeEnvLines(lines);
120
+ }
121
+
122
+ // (readEnvKey is intentionally not exported — the store's read() covers status.)
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // CLI
126
+ // ---------------------------------------------------------------------------
127
+
128
+ function openBrowser(url) {
129
+ const platform = process.platform;
130
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
131
+ exec(`"${cmd}" "${url}"`, () => {});
132
+ }
133
+
134
+ function ask(question) {
135
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
136
+ return new Promise((resolve) =>
137
+ rl.question(question, (answer) => {
138
+ rl.close();
139
+ resolve(answer.trim());
140
+ })
141
+ );
142
+ }
143
+
144
+ function fail(msg) {
145
+ console.error(`✗ ${msg}`);
146
+ process.exit(1);
147
+ }
148
+
149
+ function status() {
150
+ // Read through a file-backed store: a key saved to .env by either
151
+ // connection path must show up in a later process without requiring export.
152
+ const { OrcaCredentialStore } = require("../src/clients/orcarouter-credentials");
153
+ const env = statusEnv();
154
+ const store = new OrcaCredentialStore({
155
+ env,
156
+ loadSecret: () => env.ORCAROUTER_API_KEY || null,
157
+ });
158
+ const st = store.read();
159
+ console.log(st.present
160
+ ? `OrcaRouter credential: ${st.masked} (generation ${st.generation})${st.needsReauth ? " — needs reauthentication" : ""}`
161
+ : "No OrcaRouter credential configured.");
162
+ console.log(` API base: ${resolveApiBase(env)}`);
163
+ console.log(` Auth base: ${resolveAuthBase(env)}`);
164
+ }
165
+
166
+ async function runConnect(opts) {
167
+ const store = getCredentialStore();
168
+
169
+ // Adapter A: update/clear an API key directly through the seam too.
170
+ if (opts.apiKey) {
171
+ const adapter = createApiKeyAdapter(store, { env: process.env });
172
+ const cred = adapter.set(opts.apiKey);
173
+ upsertEnvKey("ORCAROUTER_API_KEY", cred.key);
174
+ console.log(`✓ OrcaRouter API key saved (${maskKey(cred.key)}).`);
175
+ return;
176
+ }
177
+ if (opts.clear) {
178
+ store.clear();
179
+ clearEnvKey("ORCAROUTER_API_KEY");
180
+ console.log("✓ OrcaRouter credential cleared.");
181
+ return;
182
+ }
183
+ if (opts.status) {
184
+ status();
185
+ return;
186
+ }
187
+
188
+ // PKCE connect. Generate verifier+state fresh; hand back the authorize URL.
189
+ const started = await connectWithPkce({ appName: "Lynkr" });
190
+ if (!started.ok) fail(started.error?.message || "Could not start OrcaRouter login");
191
+
192
+ console.log("\n1. Open this URL in your browser (opening automatically…):");
193
+ console.log(` ${started.authorizeUrl}\n`);
194
+ openBrowser(started.authorizeUrl);
195
+
196
+ const code = opts.code || (await ask("2. Paste the one-time code from the consent screen: ")).trim();
197
+ if (!code) fail("No code provided.");
198
+
199
+ // Exchange the code for the durable key using the SAME verifier that
200
+ // produced the challenge (PKCE binding — a fresh verifier would mismatch).
201
+ const result = await connectWithPkce({ appName: "Lynkr", code, verifier: started.verifier, state: started.state });
202
+ if (!result.ok) {
203
+ fail(result.error?.message || `OrcaRouter exchange failed (HTTP ${result.error?.status || "?"})`);
204
+ }
205
+ upsertEnvKey("ORCAROUTER_API_KEY", result.credential.key);
206
+ const scopeNote = result.credential.scope && result.credential.scope !== "api"
207
+ ? ` (granted scope: ${result.credential.scope})`
208
+ : "";
209
+ console.log(`\n✓ Connected to OrcaRouter. Key saved to .env${scopeNote}.`);
210
+ console.log(` Masked: ${maskKey(result.credential.key)}`);
211
+ console.log(" Revoke anytime at https://www.orcarouter.ai/console/authorized-apps");
212
+ }
213
+
214
+ function parseArgs(argv) {
215
+ const opts = { apiKey: null, code: null, status: false, clear: false };
216
+ for (let i = 0; i < argv.length; i++) {
217
+ const a = argv[i];
218
+ if (a === "--status") opts.status = true;
219
+ else if (a === "--clear") opts.clear = true;
220
+ else if (a === "--api-key") opts.apiKey = argv[++i];
221
+ else if (a === "--code") opts.code = argv[++i];
222
+ }
223
+ return opts;
224
+ }
225
+
226
+ async function main() {
227
+ const args = process.argv.slice(2);
228
+ if (args.includes("--help") || args.includes("-h")) {
229
+ console.log(`lynkr connect orcarouter — OAuth 2.0 + PKCE login for the OrcaRouter provider
230
+
231
+ Usage:
232
+ lynkr connect orcarouter Interactive out-of-band code login
233
+ lynkr connect orcarouter --code <code> Non-interactive (paste the consent code)
234
+ lynkr connect orcarouter --api-key <key> Save an existing sk-orca-… API key
235
+ lynkr connect orcarouter --status Show masked credential status
236
+ lynkr connect orcarouter --clear Remove the stored credential`);
237
+ return;
238
+ }
239
+ return runConnect(parseArgs(args));
240
+ }
241
+
242
+ if (require.main === module || process.env._LYNKR_SUBCMD === "connect") {
243
+ main().catch((err) => fail(err.message));
244
+ }
245
+
246
+ module.exports = { runConnect, parseArgs, upsertEnvKey, clearEnvKey, readEnvKey, statusEnv, status };
package/bin/lynkr-init.js CHANGED
@@ -188,12 +188,23 @@ const PROVIDERS = {
188
188
  extras: [],
189
189
  defaultModel: 'accounts/fireworks/models/kimi-k2-instruct-0905',
190
190
  },
191
+ orcarouter: {
192
+ label: 'OrcaRouter (OpenAI-compatible gateway — adaptive routing, failover, zero-markup inference)',
193
+ local: false,
194
+ creds: [
195
+ { key: 'ORCAROUTER_API_KEY', label: 'OrcaRouter API key (sk-orca-...; or run `lynkr connect orcarouter` for OAuth + PKCE login)', secret: true },
196
+ ],
197
+ extras: [
198
+ { key: 'ORCAROUTER_MODEL', label: 'default model', default: 'orcarouter/auto' },
199
+ ],
200
+ defaultModel: 'orcarouter/auto',
201
+ },
191
202
  };
192
203
 
193
204
  const PROVIDER_ORDER = [
194
205
  'ollama', 'llamacpp', 'lmstudio',
195
206
  'azure-anthropic', 'azure-openai', 'openai', 'atlas', 'openrouter', 'edenai',
196
- 'databricks', 'bedrock', 'vertex', 'zai', 'moonshot', 'baidu', 'fireworks',
207
+ 'databricks', 'bedrock', 'vertex', 'zai', 'moonshot', 'baidu', 'fireworks', 'orcarouter',
197
208
  ];
198
209
  const TIERS = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
199
210
 
@@ -354,6 +365,9 @@ const BASELINE_ENV = {
354
365
  FIREWORKS_API_KEY: '',
355
366
  FIREWORKS_ENDPOINT: 'https://api.fireworks.ai/inference/v1/chat/completions',
356
367
  FIREWORKS_MODEL: 'accounts/fireworks/models/kimi-k2-instruct-0905',
368
+ ORCAROUTER_API_KEY: '',
369
+ ORCAROUTER_MODEL: 'orcarouter/auto',
370
+ ORCAROUTER_ENDPOINT: 'https://api.orcarouter.ai/v1/chat/completions',
357
371
  LLAMACPP_ENDPOINT: 'http://localhost:8080',
358
372
  LLAMACPP_MODEL: 'default',
359
373
  LLAMACPP_TIMEOUT_MS: '120000',
@@ -12,11 +12,21 @@
12
12
  "substantive": [
13
13
  "Review this retry helper for bugs and race conditions",
14
14
  "give me a plan to refactor this code",
15
- "Read the current repo and explain it to me"
15
+ "Read the current repo and explain it to me",
16
+ "Implement a distributed rate limiter with Redis",
17
+ "Run the unit tests and summarize failures",
18
+ "What does this function do",
19
+ "List the exports from this file",
20
+ "Fix the linter warnings",
21
+ "Add error handling to this try block",
22
+ "Write a test for the login function",
23
+ "Debug this null reference error"
16
24
  ],
17
25
  "heavyweight": [
18
26
  "Do an architecture review of the orchestrator",
19
27
  "analyze every module in src/ for circular dependencies and give me a dependency-order refactor plan",
20
- "design a horizontally scalable architecture for this service with failure-mode analysis"
28
+ "design a horizontally scalable architecture for this service with failure-mode analysis",
29
+ "Compare how databricks.js and openai-format.js each handle non-2xx upstream responses, then propose a unified error-normalization helper both could share",
30
+ "Code review the PR #84 routing hardening changes"
21
31
  ]
22
32
  }
@@ -6,9 +6,14 @@
6
6
  "claude-opus-4*": { "caps": { "reasoning": 0.9, "codegen": 0.9, "debugging": 0.9, "tool_use": 0.9 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "curated-estimate: flagship tier" },
7
7
  "claude-opus-3*": { "caps": { "reasoning": 0.8, "codegen": 0.82, "debugging": 0.8, "tool_use": 0.78 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
8
8
  "claude-sonnet-4*": { "caps": { "reasoning": 0.78, "codegen": 0.8, "debugging": 0.78, "tool_use": 0.78 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "curated-estimate" },
9
+ "claude-sonnet-5*": { "caps": { "reasoning": 0.8, "codegen": 0.82, "debugging": 0.8, "tool_use": 0.78 }, "sources": ["lynkr-operator-estimate"], "note": "provisional: 4.5 successor (Active per Anthropic deprecations table); sized just above sonnet-4.5 — promote on benchmark rows" },
10
+ "claude-sonnet": { "caps": { "reasoning": 0.78, "codegen": 0.8, "debugging": 0.78, "tool_use": 0.78 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "alias: bare family id shares claude-sonnet-4 caps" },
11
+ "claude-opus": { "caps": { "reasoning": 0.9, "codegen": 0.9, "debugging": 0.9, "tool_use": 0.9 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "alias: bare family id shares claude-opus-4 caps" },
9
12
  "claude-sonnet-3.5": { "caps": { "reasoning": 0.72, "codegen": 0.75, "debugging": 0.72, "tool_use": 0.7 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
13
+ "orcarouter-auto": { "caps": { "reasoning": 0.8, "codegen": 0.82, "debugging": 0.8, "tool_use": 0.82 }, "sources": ["provider-routing:adaptive auto"], "note": "curated-estimate: orcarouter/auto routes across frontier models per request" },
10
14
  "claude-3.5-sonnet*": { "caps": { "reasoning": 0.72, "codegen": 0.75, "debugging": 0.72, "tool_use": 0.7 }, "sources": ["swe-bench-verified"], "note": "alias: dated/provider form (claude-3-5-sonnet-20241022) shares claude-sonnet-3.5 caps" },
11
15
  "claude-haiku*": { "caps": { "reasoning": 0.42, "codegen": 0.48, "debugging": 0.42, "tool_use": 0.45 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
16
+ "claude-haiku": { "caps": { "reasoning": 0.42, "codegen": 0.48, "debugging": 0.42, "tool_use": 0.45 }, "sources": ["swe-bench-verified"], "note": "alias: bare family id shares claude-haiku caps (matters: heuristic rates bare haiku 0.5, overstating it)" },
12
17
  "claude-3.5-haiku*": { "caps": { "reasoning": 0.42, "codegen": 0.48, "debugging": 0.42, "tool_use": 0.45 }, "sources": ["swe-bench-verified"], "note": "alias: dated/provider form (claude-3-5-haiku-20241022) shares claude-haiku caps" },
13
18
  "gpt-5.6*": { "caps": { "reasoning": 0.85, "codegen": 0.85, "debugging": 0.85, "tool_use": 0.82 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: flagship tier" },
14
19
  "gpt-5.4*": { "caps": { "reasoning": 0.85, "codegen": 0.85, "debugging": 0.85, "tool_use": 0.82 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: flagship tier" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.14.11",
3
+ "version": "9.14.13",
4
4
  "files": [
5
5
  "index.js",
6
6
  "install.sh",
@@ -38,7 +38,7 @@
38
38
  "start:supervised": "while true; do node index.js 2>&1 | npx pino-pretty --sync; code=$?; echo \"[supervisor] lynkr exited ($code) — restarting in 3s\"; sleep 3; done",
39
39
  "lint": "eslint src index.js",
40
40
  "test": "npm run test:unit && npm run test:performance",
41
- "test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js",
41
+ "test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js test/jev-router.test.js test/jev-routing.test.js test/force-patterns.test.js test/upstream-fidelity.test.js test/tool-schema-compression.test.js test/passthrough-route.test.js test/quota-ledger.test.js",
42
42
  "test:memory": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js",
43
43
  "test:new-features": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
44
44
  "test:performance": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
@@ -122,6 +122,6 @@
122
122
  "allowScripts": {},
123
123
  "overrides": {
124
124
  "sharp": "^0.35.4",
125
- "adm-zip": "^0.6.0"
125
+ "adm-zip": "^0.6.1"
126
126
  }
127
127
  }
@@ -56,14 +56,14 @@ const EVAL_SET = [
56
56
  { text: "Refactor this into smaller functions", target: "MEDIUM" },
57
57
  // COMPLEX (GLM)
58
58
  { text: "Do an architecture review of the orchestrator", target: "COMPLEX" },
59
- { text: "Review this retry helper for bugs", target: "COMPLEX" },
59
+ { text: "Review this retry helper for bugs", target: "MEDIUM" }, // near-paraphrase of a substantive anchor — single-file review, not systemic work (retargeted 2026-09-21)
60
60
  { text: "Refactor the entire ingestion pipeline and give me the plan", target: "COMPLEX" },
61
61
  { text: "Design a horizontally scalable architecture for the router", target: "COMPLEX" },
62
62
  { text: "Code review the PR #84 routing hardening changes", target: "COMPLEX" },
63
63
  { text: "Analyze every module in src/ for circular dependencies", target: "COMPLEX" },
64
64
  { text: "Debug this complex race condition in the connection pool", target: "COMPLEX" },
65
65
  { text: "Plan a zero-downtime migration to the new schema", target: "COMPLEX" },
66
- { text: "Implement a distributed rate limiter with Redis", target: "COMPLEX" },
66
+ { text: "Implement a distributed rate limiter with Redis", target: "MEDIUM" }, // single-component build with a named tech — implementation, not design (retargeted 2026-09-21)
67
67
  { text: "Design the caching strategy for this API gateway", target: "COMPLEX" },
68
68
  // REASONING (Claude)
69
69
  { text: "Prove the correctness of this lock-free queue implementation", target: "REASONING" },
@@ -986,9 +986,10 @@ router.post("/chat/completions", async (req, res) => {
986
986
 
987
987
  /**
988
988
  * Get all configured providers with their models (cc-relay style)
989
- * Reads from config (which comes from .env) to discover what's available
989
+ * Reads from config (which comes from .env) to discover what's available.
990
+ * OrcaRouter contributes its live capability-filtered chat catalog.
990
991
  */
991
- function getConfiguredProviders() {
992
+ async function getConfiguredProviders() {
992
993
  const providers = [];
993
994
 
994
995
  if (config.databricks?.url && config.databricks?.apiKey) {
@@ -1156,6 +1157,23 @@ function getConfiguredProviders() {
1156
1157
  });
1157
1158
  }
1158
1159
 
1160
+ // OrcaRouter (OpenAI-compatible gateway). The model list is the live
1161
+ // capability-filtered catalog fetch (authoritative when the account key
1162
+ // works), with a verified seed fallback. Never a hand-written example list.
1163
+ if (config.orcarouter?.apiKey) {
1164
+ try {
1165
+ const { getOrcaChatModels } = require("../clients/orcarouter-catalog");
1166
+ const { models } = await getOrcaChatModels(config.orcarouter, { capability: "chat" });
1167
+ providers.push({
1168
+ name: "orcarouter",
1169
+ type: "orcarouter",
1170
+ models: models.map(m => m.id),
1171
+ });
1172
+ } catch (err) {
1173
+ logger.debug({ err: err.message }, "OrcaRouter discovery failed — omitting from OpenAI /v1/models");
1174
+ }
1175
+ }
1176
+
1159
1177
  if (config.vertex?.projectId) {
1160
1178
  providers.push({
1161
1179
  name: "vertex",
@@ -1178,9 +1196,9 @@ function getConfiguredProviders() {
1178
1196
  * List available models from ALL configured providers (cc-relay style).
1179
1197
  * Returns OpenAI-compatible model list with provider field.
1180
1198
  */
1181
- router.get("/models", (req, res) => {
1199
+ router.get("/models", async (req, res) => {
1182
1200
  try {
1183
- const providers = getConfiguredProviders();
1201
+ const providers = await getConfiguredProviders();
1184
1202
  const timestamp = Math.floor(Date.now() / 1000);
1185
1203
  const models = [];
1186
1204
  const seenModelIds = new Set();
@@ -17,9 +17,11 @@ const router = express.Router();
17
17
 
18
18
  /**
19
19
  * Get all configured providers with their models
20
- * Reads from config (which comes from .env) to discover what's available
20
+ * Reads from config (which comes from .env) to discover what's available.
21
+ * OrcaRouter's model list is a live capability-filtered catalog fetch
22
+ * (authoritative when the account key works), with a verified seed fallback.
21
23
  */
22
- function getConfiguredProviders() {
24
+ async function getConfiguredProviders() {
23
25
  const providers = [];
24
26
 
25
27
  // Check Databricks
@@ -253,6 +255,27 @@ function getConfiguredProviders() {
253
255
  });
254
256
  }
255
257
 
258
+ // Check OrcaRouter (OpenAI-compatible gateway). The model list is the live
259
+ // capability-filtered catalog (text chat) — authoritative when discovery
260
+ // succeeds, verified seed otherwise. Never a hand-written example list.
261
+ if (config.orcarouter?.apiKey) {
262
+ try {
263
+ const { getOrcaChatModels } = require("../clients/orcarouter-catalog");
264
+ const { models, source, degraded } = await getOrcaChatModels(config.orcarouter, { capability: "chat" });
265
+ providers.push({
266
+ name: "orcarouter",
267
+ type: "orcarouter",
268
+ baseUrl: `${config.orcarouter.apiBaseUrl || "https://api.orcarouter.ai"}/v1`,
269
+ enabled: true,
270
+ catalog_source: source,
271
+ catalog_degraded: degraded,
272
+ models: models.map(m => ({ id: m.id, name: m.name || m.id })),
273
+ });
274
+ } catch (err) {
275
+ logger.debug({ err: err.message }, "OrcaRouter provider discovery failed — omitting from /v1/models");
276
+ }
277
+ }
278
+
256
279
  // Check Vertex AI (Google Cloud)
257
280
  if (config.vertex?.projectId) {
258
281
  const region = config.vertex.region || "us-east5";
@@ -287,9 +310,9 @@ function getPrimaryProvider() {
287
310
  * Anthropic-compatible model listing endpoint (cc-relay style).
288
311
  * Lists all available models from all configured providers.
289
312
  */
290
- router.get("/models", (req, res) => {
313
+ router.get("/models", async (req, res) => {
291
314
  try {
292
- const providers = getConfiguredProviders();
315
+ const providers = await getConfiguredProviders();
293
316
  const primaryProvider = getPrimaryProvider();
294
317
  const timestamp = Math.floor(Date.now() / 1000);
295
318
 
@@ -344,9 +367,9 @@ router.get("/models", (req, res) => {
344
367
  * Provider listing endpoint (cc-relay style).
345
368
  * Lists all configured providers with their metadata and models.
346
369
  */
347
- router.get("/providers", (req, res) => {
370
+ router.get("/providers", async (req, res) => {
348
371
  try {
349
- const providers = getConfiguredProviders();
372
+ const providers = await getConfiguredProviders();
350
373
  const primaryProvider = getPrimaryProvider();
351
374
  const fallbackProvider = config.modelProvider?.fallbackProvider;
352
375
 
@@ -391,10 +414,10 @@ router.get("/providers", (req, res) => {
391
414
  *
392
415
  * Get details for a specific provider.
393
416
  */
394
- router.get("/providers/:name", (req, res) => {
417
+ router.get("/providers/:name", async (req, res) => {
395
418
  try {
396
419
  const providerName = req.params.name.toLowerCase();
397
- const providers = getConfiguredProviders();
420
+ const providers = await getConfiguredProviders();
398
421
  const provider = providers.find(p => p.name === providerName);
399
422
 
400
423
  if (!provider) {
@@ -435,9 +458,9 @@ router.get("/providers/:name", (req, res) => {
435
458
  *
436
459
  * Get current configuration summary (without sensitive data).
437
460
  */
438
- router.get("/config", (req, res) => {
461
+ router.get("/config", async (req, res) => {
439
462
  try {
440
- const providers = getConfiguredProviders();
463
+ const providers = await getConfiguredProviders();
441
464
 
442
465
  res.json({
443
466
  model_provider: config.modelProvider?.type || "databricks",