lynkr 9.14.1 → 9.14.3
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 +5 -1
- package/bin/cli.js +14 -6
- package/bin/lynkr-desktop-token.js +108 -0
- package/bin/lynkr-init.js +13 -1
- package/bin/lynkr-restart.js +125 -0
- package/bin/opencode-setup.js +175 -0
- package/bin/run.js +92 -0
- package/config/model-capabilities.json +20 -0
- package/config/model-capability-seeds.json +75 -0
- package/config/model-tiers.json +13 -1
- package/package.json +16 -22
- package/scripts/claude-desktop.js +212 -0
- package/scripts/ope-report.js +78 -0
- package/scripts/seed-capabilities.js +230 -0
- package/src/api/claude-desktop-gateway.js +123 -0
- package/src/api/health.js +0 -1
- package/src/api/mcp-broker.js +149 -0
- package/src/api/middleware/budget-enforcer.js +77 -7
- package/src/api/middleware/budget.js +36 -2
- package/src/api/middleware/session.js +0 -0
- package/src/api/openai-router.js +210 -15
- package/src/api/providers-handler.js +16 -1
- package/src/api/router.js +73 -106
- package/src/auth-mode.js +40 -4
- package/src/budget/index.js +86 -0
- package/src/cache/embeddings.js +125 -35
- package/src/cache/onnx-embedder.js +92 -0
- package/src/cache/prompt.js +0 -7
- package/src/clients/bedrock-utils.js +2 -1
- package/src/clients/codex-process.js +1 -1
- package/src/clients/codex-utils.js +0 -2
- package/src/clients/databricks.js +337 -46
- package/src/clients/gpt-utils.js +84 -8
- package/src/clients/health-probe.js +157 -0
- package/src/clients/openai-format.js +73 -23
- package/src/clients/provider-capabilities.js +27 -2
- package/src/clients/resilience.js +0 -1
- package/src/clients/responses-format.js +0 -56
- package/src/clients/routing.js +1 -1
- package/src/config/index.js +13 -2
- package/src/context/budget.js +1 -1
- package/src/context/compression.js +127 -19
- package/src/context/tool-result-compressor.js +3 -5
- package/src/dashboard/api.js +1 -1
- package/src/logger/oversized-error-stream.js +1 -1
- package/src/memory/extractor.js +3 -3
- package/src/memory/search.js +2 -2
- package/src/memory/store.js +9 -24
- package/src/memory/tools.js +14 -0
- package/src/observability/metrics.js +52 -3
- package/src/observability/otel.js +180 -0
- package/src/orchestrator/azure-responses-sse.js +223 -0
- package/src/orchestrator/index.js +216 -212
- package/src/orchestrator/passthrough-stream.js +7 -1
- package/src/orchestrator/sse-transformer.js +130 -13
- package/src/policy/safe-commands.js +0 -1
- package/src/routing/bandit.js +39 -0
- package/src/routing/calibration.js +21 -1
- package/src/routing/capabilities.js +88 -0
- package/src/routing/capability-seeds/benchmarks-dir.js +58 -0
- package/src/routing/capability-seeds/family-heuristics.js +77 -0
- package/src/routing/capability-seeds/family.js +105 -0
- package/src/routing/capability-seeds/models-dev.js +64 -0
- package/src/routing/capability-seeds/normalize.js +160 -0
- package/src/routing/capability-seeds/registry.js +125 -0
- package/src/routing/capability-seeds/sources.js +69 -0
- package/src/routing/capability-seeds/swebench.js +66 -0
- package/src/routing/client-profiles.js +26 -0
- package/src/routing/complexity-analyzer.js +5 -8
- package/src/routing/confidence-scorer.js +2 -2
- package/src/routing/cost-optimizer.js +1 -2
- package/src/routing/decide.js +149 -0
- package/src/routing/deescalator.js +38 -1
- package/src/routing/embedding-cache.js +0 -1
- package/src/routing/index.js +202 -86
- package/src/routing/intent-score.js +0 -1
- package/src/routing/knn-router.js +27 -5
- package/src/routing/latency-tracker.js +1 -1
- package/src/routing/model-registry.js +25 -0
- package/src/routing/model-slots.js +65 -0
- package/src/routing/model-tiers.js +2 -0
- package/src/routing/ope.js +255 -0
- package/src/routing/openai-model-slots.js +82 -0
- package/src/routing/regret-estimator.js +1 -1
- package/src/routing/shortfall.js +290 -0
- package/src/routing/stuck-detector.js +127 -0
- package/src/routing/telemetry.js +11 -2
- package/src/server.js +41 -3
- package/src/stores/file-store.js +5 -1
- package/src/tools/web-search-exec.js +182 -0
- package/src/training/trajectory-compressor.js +1 -1
- package/src/workers/pool.js +1 -1
- package/src/workers/worker.js +1 -1
- package/scripts/convert-routellm.py +0 -105
- package/src/diff/comments.js +0 -153
- package/src/policy/web-fallback.js +0 -33
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"updatedAt": "2026-09-08",
|
|
4
|
+
"notes": "Reviewed per-family capability seeds. Values are curated estimates triangulated from SWE-Bench Verified, TerminalBench, LiveCodeBench/BigCodeBench and Artificial Analysis coding index (see scripts/seed-capabilities.js to regenerate from live sources). Heads: reasoning/codegen/debugging/tool_use in [0,1]. Keys are normalized family ids (see capability-seeds/family.js) with trailing-* wildcards matched longest-first. Quantized self-hosted servings take an automatic -0.02 haircut at resolve time. Unknown families fall through to the family heuristic, then tier-slot caps.",
|
|
5
|
+
"seeds": {
|
|
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
|
+
"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
|
+
"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-3.5": { "caps": { "reasoning": 0.72, "codegen": 0.75, "debugging": 0.72, "tool_use": 0.7 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
10
|
+
"claude-haiku*": { "caps": { "reasoning": 0.42, "codegen": 0.48, "debugging": 0.42, "tool_use": 0.45 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
11
|
+
"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" },
|
|
12
|
+
"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" },
|
|
13
|
+
"gpt-5.3*": { "caps": { "reasoning": 0.82, "codegen": 0.85, "debugging": 0.82, "tool_use": 0.8 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
14
|
+
"gpt-5.2*": { "caps": { "reasoning": 0.8, "codegen": 0.83, "debugging": 0.8, "tool_use": 0.78 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
15
|
+
"gpt-5.1*": { "caps": { "reasoning": 0.7, "codegen": 0.72, "debugging": 0.7, "tool_use": 0.68 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
16
|
+
"gpt-5-mini*": { "caps": { "reasoning": 0.6, "codegen": 0.65, "debugging": 0.6, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
17
|
+
"gpt-5-nano*": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.45 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
18
|
+
"gpt-4o": { "caps": { "reasoning": 0.65, "codegen": 0.7, "debugging": 0.65, "tool_use": 0.68 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate" },
|
|
19
|
+
"gpt-4o-mini": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.48 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
20
|
+
"gpt-4.1": { "caps": { "reasoning": 0.68, "codegen": 0.72, "debugging": 0.68, "tool_use": 0.7 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
21
|
+
"gpt-4.1-mini": { "caps": { "reasoning": 0.5, "codegen": 0.55, "debugging": 0.5, "tool_use": 0.52 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
22
|
+
"o3": { "caps": { "reasoning": 0.88, "codegen": 0.8, "debugging": 0.85, "tool_use": 0.7 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: reasoning tilt" },
|
|
23
|
+
"o3-mini": { "caps": { "reasoning": 0.72, "codegen": 0.68, "debugging": 0.7, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: reasoning tilt" },
|
|
24
|
+
"o4-mini": { "caps": { "reasoning": 0.7, "codegen": 0.66, "debugging": 0.68, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
25
|
+
"o1": { "caps": { "reasoning": 0.82, "codegen": 0.72, "debugging": 0.78, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: reasoning tilt" },
|
|
26
|
+
"o1-mini": { "caps": { "reasoning": 0.62, "codegen": 0.6, "debugging": 0.6, "tool_use": 0.5 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
27
|
+
"gemini-2.5-pro": { "caps": { "reasoning": 0.85, "codegen": 0.83, "debugging": 0.85, "tool_use": 0.78 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "curated-estimate" },
|
|
28
|
+
"gemini-3-pro*": { "caps": { "reasoning": 0.85, "codegen": 0.83, "debugging": 0.85, "tool_use": 0.78 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
29
|
+
"gemini-2.5-flash": { "caps": { "reasoning": 0.55, "codegen": 0.6, "debugging": 0.55, "tool_use": 0.58 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
30
|
+
"gemini-2.0-flash": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.5 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
31
|
+
"gemini-1.5-pro": { "caps": { "reasoning": 0.6, "codegen": 0.62, "debugging": 0.6, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
32
|
+
"deepseek-r1": { "caps": { "reasoning": 0.82, "codegen": 0.78, "debugging": 0.8, "tool_use": 0.6 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate: reasoning tilt, weak tool-use" },
|
|
33
|
+
"deepseek-reasoner": { "caps": { "reasoning": 0.82, "codegen": 0.78, "debugging": 0.8, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
34
|
+
"deepseek-chat": { "caps": { "reasoning": 0.65, "codegen": 0.7, "debugging": 0.65, "tool_use": 0.62 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
35
|
+
"deepseek-v3*": { "caps": { "reasoning": 0.68, "codegen": 0.72, "debugging": 0.68, "tool_use": 0.62 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate" },
|
|
36
|
+
"qwen3-max": { "caps": { "reasoning": 0.78, "codegen": 0.8, "debugging": 0.78, "tool_use": 0.72 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate" },
|
|
37
|
+
"qwen3-235b*": { "caps": { "reasoning": 0.78, "codegen": 0.8, "debugging": 0.78, "tool_use": 0.7 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
38
|
+
"qwen3-32b": { "caps": { "reasoning": 0.65, "codegen": 0.7, "debugging": 0.65, "tool_use": 0.6 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
39
|
+
"qwen3-coder*": { "caps": { "reasoning": 0.68, "codegen": 0.78, "debugging": 0.72, "tool_use": 0.62 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate: coder tilt" },
|
|
40
|
+
"qwen2.5-coder-32b*": { "caps": { "reasoning": 0.55, "codegen": 0.68, "debugging": 0.6, "tool_use": 0.55 }, "sources": ["livecodebench"], "note": "curated-estimate: coder tilt" },
|
|
41
|
+
"qwen2.5-coder-14b*": { "caps": { "reasoning": 0.45, "codegen": 0.58, "debugging": 0.5, "tool_use": 0.45 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
42
|
+
"qwen2.5-coder-7b*": { "caps": { "reasoning": 0.35, "codegen": 0.48, "debugging": 0.4, "tool_use": 0.38 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
43
|
+
"qwen3-8b": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.45 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
44
|
+
"qwen3-4b": { "caps": { "reasoning": 0.35, "codegen": 0.42, "debugging": 0.38, "tool_use": 0.38 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
45
|
+
"glm-5.2": { "caps": { "reasoning": 0.68, "codegen": 0.72, "debugging": 0.68, "tool_use": 0.66 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "curated-estimate: provider-agnostic (z.ai/Baidu/local share this entry)" },
|
|
46
|
+
"glm-5*": { "caps": { "reasoning": 0.65, "codegen": 0.68, "debugging": 0.65, "tool_use": 0.62 }, "sources": ["artificial-analysis"], "note": "curated-estimate: GLM-5 family default" },
|
|
47
|
+
"glm-4.7": { "caps": { "reasoning": 0.62, "codegen": 0.66, "debugging": 0.62, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
48
|
+
"glm-4.6": { "caps": { "reasoning": 0.6, "codegen": 0.64, "debugging": 0.6, "tool_use": 0.58 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
49
|
+
"glm-4.5*": { "caps": { "reasoning": 0.5, "codegen": 0.55, "debugging": 0.5, "tool_use": 0.52 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
50
|
+
"glm-4-flash": { "caps": { "reasoning": 0.35, "codegen": 0.4, "debugging": 0.35, "tool_use": 0.38 }, "sources": ["artificial-analysis"], "note": "curated-estimate" },
|
|
51
|
+
"kimi-k3": { "caps": { "reasoning": 0.72, "codegen": 0.75, "debugging": 0.72, "tool_use": 0.7 }, "sources": ["swe-bench-verified", "artificial-analysis"], "note": "curated-estimate" },
|
|
52
|
+
"kimi-k2.6": { "caps": { "reasoning": 0.62, "codegen": 0.66, "debugging": 0.62, "tool_use": 0.62 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
53
|
+
"kimi-k2.5": { "caps": { "reasoning": 0.6, "codegen": 0.64, "debugging": 0.6, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
54
|
+
"kimi-k2*": { "caps": { "reasoning": 0.6, "codegen": 0.63, "debugging": 0.6, "tool_use": 0.6 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: Kimi-K2 family default" },
|
|
55
|
+
"llama-3.3-70b*": { "caps": { "reasoning": 0.6, "codegen": 0.65, "debugging": 0.6, "tool_use": 0.58 }, "sources": ["swe-bench-verified", "livecodebench"], "note": "curated-estimate" },
|
|
56
|
+
"llama-3.1-70b*": { "caps": { "reasoning": 0.55, "codegen": 0.6, "debugging": 0.55, "tool_use": 0.52 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
57
|
+
"llama-4-maverick*": { "caps": { "reasoning": 0.6, "codegen": 0.62, "debugging": 0.6, "tool_use": 0.58 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
58
|
+
"llama-4-scout*": { "caps": { "reasoning": 0.5, "codegen": 0.52, "debugging": 0.5, "tool_use": 0.5 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
59
|
+
"llama-3.2*": { "caps": { "reasoning": 0.25, "codegen": 0.3, "debugging": 0.25, "tool_use": 0.28 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
60
|
+
"mistral-large*": { "caps": { "reasoning": 0.6, "codegen": 0.65, "debugging": 0.6, "tool_use": 0.62 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
61
|
+
"codestral*": { "caps": { "reasoning": 0.5, "codegen": 0.62, "debugging": 0.55, "tool_use": 0.5 }, "sources": ["livecodebench"], "note": "curated-estimate: coder tilt" },
|
|
62
|
+
"mistral-small*": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.48 }, "sources": ["artificial-analysis"], "note": "curated-estimate" },
|
|
63
|
+
"devstral*": { "caps": { "reasoning": 0.5, "codegen": 0.6, "debugging": 0.55, "tool_use": 0.5 }, "sources": ["swe-bench-verified"], "note": "curated-estimate: coder tilt" },
|
|
64
|
+
"gpt-oss-120b": { "caps": { "reasoning": 0.65, "codegen": 0.68, "debugging": 0.65, "tool_use": 0.55 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
65
|
+
"gpt-oss-20b": { "caps": { "reasoning": 0.45, "codegen": 0.5, "debugging": 0.45, "tool_use": 0.42 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
66
|
+
"gemma-3-27b": { "caps": { "reasoning": 0.45, "codegen": 0.48, "debugging": 0.45, "tool_use": 0.45 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
67
|
+
"gemma-2*": { "caps": { "reasoning": 0.28, "codegen": 0.32, "debugging": 0.28, "tool_use": 0.3 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
68
|
+
"phi-4": { "caps": { "reasoning": 0.45, "codegen": 0.48, "debugging": 0.45, "tool_use": 0.4 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
69
|
+
"phi-3*": { "caps": { "reasoning": 0.28, "codegen": 0.32, "debugging": 0.28, "tool_use": 0.28 }, "sources": ["livecodebench"], "note": "curated-estimate" },
|
|
70
|
+
"minimax-m2*": { "caps": { "reasoning": 0.65, "codegen": 0.68, "debugging": 0.65, "tool_use": 0.65 }, "sources": ["swe-bench-verified"], "note": "curated-estimate" },
|
|
71
|
+
"muse-spark-1.3": { "caps": { "reasoning": 0.85, "codegen": 0.87, "debugging": 0.85, "tool_use": 0.75 }, "sources": ["deepswe-1.1@75.4% (vendor-reported, max setting)", "terminal-bench-2.1@88.8% (vendor-reported)"], "note": "curated-estimate: full 1.3 at max reasoning. Vendor self-reported; absent from public DeepSWE leaderboard (only 1.2 listed); hands-on gap reported by third parties. tool_use capped — no direct tool-use data. Revisit on independent rows." },
|
|
72
|
+
"muse-spark-1.2*": { "caps": { "reasoning": 0.42, "codegen": 0.46, "debugging": 0.42, "tool_use": 0.44 }, "sources": ["lynkr-telemetry:4176 scored rows, avg quality 52.7 @ complexity 51.7"], "note": "measured: operator's own traffic. Below the 70 evidence bar — economize trivial turns only." },
|
|
73
|
+
"muse-spark-1.3-contributor*": { "caps": { "reasoning": 0.4, "codegen": 0.45, "debugging": 0.4, "tool_use": 0.45 }, "sources": ["lynkr-telemetry:29 scored rows (thin) + 1.2 family history"], "note": "provisional: free/contributor serving grades far below full-1.3 published scores (variant, effort setting, or benchmark inflation — undetermined). Do NOT cover with muse-spark*: the full model and the free serving must never share caps." }
|
|
74
|
+
}
|
|
75
|
+
}
|
package/config/model-tiers.json
CHANGED
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
],
|
|
55
55
|
"moonshot": [
|
|
56
56
|
"kimi-k2.6"
|
|
57
|
+
],
|
|
58
|
+
"fireworks": [
|
|
59
|
+
"accounts/fireworks/models/llama-3.1-8b-instruct"
|
|
57
60
|
]
|
|
58
61
|
}
|
|
59
62
|
},
|
|
@@ -109,6 +112,9 @@
|
|
|
109
112
|
],
|
|
110
113
|
"moonshot": [
|
|
111
114
|
"kimi-k2.6"
|
|
115
|
+
],
|
|
116
|
+
"fireworks": [
|
|
117
|
+
"accounts/fireworks/models/kimi-k2-instruct-0905"
|
|
112
118
|
]
|
|
113
119
|
}
|
|
114
120
|
},
|
|
@@ -158,6 +164,9 @@
|
|
|
158
164
|
],
|
|
159
165
|
"moonshot": [
|
|
160
166
|
"kimi-k2.6"
|
|
167
|
+
],
|
|
168
|
+
"fireworks": [
|
|
169
|
+
"accounts/fireworks/models/deepseek-v3p1"
|
|
161
170
|
]
|
|
162
171
|
}
|
|
163
172
|
},
|
|
@@ -205,6 +214,9 @@
|
|
|
205
214
|
"moonshot": [
|
|
206
215
|
"kimi-k3",
|
|
207
216
|
"kimi-k2.6"
|
|
217
|
+
],
|
|
218
|
+
"fireworks": [
|
|
219
|
+
"accounts/fireworks/models/glm-5p2"
|
|
208
220
|
]
|
|
209
221
|
}
|
|
210
222
|
}
|
|
@@ -233,4 +245,4 @@
|
|
|
233
245
|
"kimi": "moonshot",
|
|
234
246
|
"z-ai": "zai"
|
|
235
247
|
}
|
|
236
|
-
}
|
|
248
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lynkr",
|
|
3
|
-
"version": "9.14.
|
|
3
|
+
"version": "9.14.3",
|
|
4
4
|
"files": [
|
|
5
5
|
"index.js",
|
|
6
6
|
"install.sh",
|
|
@@ -38,13 +38,14 @@
|
|
|
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": "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",
|
|
42
|
-
"test:memory": "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
|
-
"test:new-features": "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
|
-
"test:performance": "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",
|
|
45
|
-
"test:benchmark": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-benchmark.js",
|
|
46
|
-
"test:quick": "DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js",
|
|
47
|
-
"test:all": "npm run test:unit && npm run test:performance && npm run test:benchmark"
|
|
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",
|
|
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
|
+
"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
|
+
"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",
|
|
45
|
+
"test:benchmark": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-benchmark.js",
|
|
46
|
+
"test:quick": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/routing.test.js",
|
|
47
|
+
"test:all": "npm run test:unit && npm run test:performance && npm run test:benchmark",
|
|
48
|
+
"seed:capabilities": "node scripts/seed-capabilities.js --refresh"
|
|
48
49
|
},
|
|
49
50
|
"keywords": [
|
|
50
51
|
"llm",
|
|
@@ -90,32 +91,23 @@
|
|
|
90
91
|
"node": ">=20.0.0"
|
|
91
92
|
},
|
|
92
93
|
"dependencies": {
|
|
93
|
-
"@azure/openai": "^2.0.0",
|
|
94
|
-
"@babel/parser": "^7.29.0",
|
|
95
|
-
"@babel/traverse": "^7.29.0",
|
|
96
94
|
"@blackwell-systems/gcf": "2.4.0",
|
|
97
|
-
"@tencentdb-agent-memory/memory-tencentdb": "^1.0.1",
|
|
98
95
|
"@toon-format/toon": "^2.1.0",
|
|
99
96
|
"cockatiel": "^3.2.1",
|
|
100
|
-
"compression": "^1.7.4",
|
|
101
97
|
"diff": "^5.2.0",
|
|
102
98
|
"dotenv": "^16.4.5",
|
|
103
99
|
"express": "^5.1.0",
|
|
104
100
|
"express-rate-limit": "^8.2.1",
|
|
105
|
-
"fast-glob": "^3.3.2",
|
|
106
|
-
"graphify": "^1.0.0",
|
|
107
101
|
"hnswlib-node": "^3.0.0",
|
|
108
102
|
"js-tiktoken": "^1.0.20",
|
|
109
|
-
"js-yaml": "^4.1.1",
|
|
110
|
-
"openai": "^6.14.0",
|
|
111
103
|
"pino": "^8.17.2",
|
|
112
104
|
"pino-http": "^8.6.0",
|
|
113
|
-
"pino-roll": "^4.0.0"
|
|
114
|
-
"undici": "^6.22.0"
|
|
105
|
+
"pino-roll": "^4.0.0"
|
|
115
106
|
},
|
|
116
107
|
"optionalDependencies": {
|
|
108
|
+
"@huggingface/transformers": "^4.2.0",
|
|
117
109
|
"better-sqlite3": "^12.11.1",
|
|
118
|
-
"dockerode": "^
|
|
110
|
+
"dockerode": "^5.0.1",
|
|
119
111
|
"tree-sitter": "^0.21.1",
|
|
120
112
|
"tree-sitter-javascript": "^0.21.0",
|
|
121
113
|
"tree-sitter-python": "^0.21.0",
|
|
@@ -127,7 +119,9 @@
|
|
|
127
119
|
"nodemon": "^3.1.0",
|
|
128
120
|
"pino-pretty": "^10.2.0"
|
|
129
121
|
},
|
|
130
|
-
"allowScripts": {
|
|
131
|
-
|
|
122
|
+
"allowScripts": {},
|
|
123
|
+
"overrides": {
|
|
124
|
+
"sharp": "^0.35.4",
|
|
125
|
+
"adm-zip": "^0.6.0"
|
|
132
126
|
}
|
|
133
127
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install or remove the Lynkr profile for Claude Desktop's native
|
|
4
|
+
* third-party inference mode (macOS only).
|
|
5
|
+
*
|
|
6
|
+
* Claude Desktop supports a "3p" deployment mode in which it sends its
|
|
7
|
+
* Anthropic Messages API traffic to a local gateway instead of
|
|
8
|
+
* api.anthropic.com. The mechanism (reverse-engineered from Ollama's
|
|
9
|
+
* cmd/launch/claude_desktop.go, which uses the same route) is three JSON
|
|
10
|
+
* files:
|
|
11
|
+
*
|
|
12
|
+
* ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
13
|
+
* deploymentMode: "1p" | "3p"
|
|
14
|
+
* ~/Library/Application Support/Claude-3p/claude_desktop_config.json
|
|
15
|
+
* deploymentMode again, for the third-party profile root
|
|
16
|
+
* ~/Library/Application Support/Claude-3p/configLibrary/_meta.json
|
|
17
|
+
* profile registry: { appliedId, entries: [{id, name}] }
|
|
18
|
+
* ~/Library/Application Support/Claude-3p/configLibrary/<uuid>.json
|
|
19
|
+
* the gateway profile itself (base URL, bearer key, display name)
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* node scripts/claude-desktop.js --install [--url http://127.0.0.1:8081] [--key lynkr]
|
|
23
|
+
* node scripts/claude-desktop.js --restore
|
|
24
|
+
* node scripts/claude-desktop.js --status
|
|
25
|
+
*
|
|
26
|
+
* WARNING: this is undocumented Claude Desktop surface observed via
|
|
27
|
+
* Ollama's public integration; a Desktop update may change the key names.
|
|
28
|
+
* --restore (or `ollama launch claude-desktop --restore`) always brings
|
|
29
|
+
* back the stock Anthropic profile by flipping deploymentMode to "1p".
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require("fs");
|
|
33
|
+
const os = require("os");
|
|
34
|
+
const path = require("path");
|
|
35
|
+
const { execFileSync } = require("child_process");
|
|
36
|
+
|
|
37
|
+
const PROFILE_ID = "00000000-0000-4000-8000-000000008081";
|
|
38
|
+
const PROFILE_NAME = "Lynkr";
|
|
39
|
+
const DEFAULT_URL = "http://127.0.0.1:8081";
|
|
40
|
+
const DEFAULT_KEY = "lynkr";
|
|
41
|
+
|
|
42
|
+
const appSupport = path.join(os.homedir(), "Library", "Application Support");
|
|
43
|
+
const PATHS = {
|
|
44
|
+
normalConfig: path.join(appSupport, "Claude", "claude_desktop_config.json"),
|
|
45
|
+
tpConfig: path.join(appSupport, "Claude-3p", "claude_desktop_config.json"),
|
|
46
|
+
meta: path.join(appSupport, "Claude-3p", "configLibrary", "_meta.json"),
|
|
47
|
+
profile: path.join(appSupport, "Claude-3p", "configLibrary", `${PROFILE_ID}.json`),
|
|
48
|
+
backup: path.join(appSupport, "Claude-3p", "configLibrary", ".lynkr-backup.json"),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function readJson(file) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err.code === "ENOENT") return {};
|
|
56
|
+
throw new Error(`Cannot parse ${file}: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeJson(file, obj) {
|
|
61
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
62
|
+
fs.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setDeploymentMode(file, mode) {
|
|
66
|
+
const cfg = readJson(file);
|
|
67
|
+
cfg.deploymentMode = mode;
|
|
68
|
+
writeJson(file, cfg);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function claudeDesktopRunning() {
|
|
72
|
+
try {
|
|
73
|
+
execFileSync("pgrep", ["-x", "Claude"], { stdio: "pipe" });
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function install(opts) {
|
|
81
|
+
if (!fs.existsSync(path.join(appSupport, "Claude"))) {
|
|
82
|
+
fail("Claude Desktop does not appear to be installed (no ~/Library/Application Support/Claude).");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// First install: remember what we're replacing so --restore is faithful.
|
|
86
|
+
if (!fs.existsSync(PATHS.backup)) {
|
|
87
|
+
const meta = readJson(PATHS.meta);
|
|
88
|
+
const normal = readJson(PATHS.normalConfig);
|
|
89
|
+
writeJson(PATHS.backup, {
|
|
90
|
+
previousAppliedId: meta.appliedId ?? null,
|
|
91
|
+
previousDeploymentMode: normal.deploymentMode ?? "1p",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Profile: same keys Ollama's launcher writes, pointed at Lynkr.
|
|
96
|
+
const profile = readJson(PATHS.profile);
|
|
97
|
+
Object.assign(profile, {
|
|
98
|
+
inferenceProvider: "gateway",
|
|
99
|
+
inferenceGatewayBaseUrl: opts.url,
|
|
100
|
+
inferenceGatewayApiKey: opts.key,
|
|
101
|
+
inferenceGatewayAuthScheme: "bearer",
|
|
102
|
+
deploymentDisplayName: PROFILE_NAME,
|
|
103
|
+
chatTabEnabled: true,
|
|
104
|
+
disableDeploymentModeChooser: true,
|
|
105
|
+
coworkEgressAllowedHosts: ["*"],
|
|
106
|
+
disableEssentialTelemetry: true,
|
|
107
|
+
disableNonessentialTelemetry: true,
|
|
108
|
+
autoModeEnabled: true,
|
|
109
|
+
});
|
|
110
|
+
delete profile.inferenceModels;
|
|
111
|
+
writeJson(PATHS.profile, profile);
|
|
112
|
+
|
|
113
|
+
// Registry: apply our profile, dedupe any stale entry for our id.
|
|
114
|
+
const meta = readJson(PATHS.meta);
|
|
115
|
+
meta.appliedId = PROFILE_ID;
|
|
116
|
+
meta.entries = (Array.isArray(meta.entries) ? meta.entries : [])
|
|
117
|
+
.filter((e) => !(e && e.id === PROFILE_ID))
|
|
118
|
+
.concat([{ id: PROFILE_ID, name: PROFILE_NAME }]);
|
|
119
|
+
writeJson(PATHS.meta, meta);
|
|
120
|
+
|
|
121
|
+
setDeploymentMode(PATHS.normalConfig, "3p");
|
|
122
|
+
setDeploymentMode(PATHS.tpConfig, "3p");
|
|
123
|
+
|
|
124
|
+
console.log(`Claude Desktop profile changed to ${PROFILE_NAME}.`);
|
|
125
|
+
console.log(`Gateway: ${opts.url} (bearer key: ${opts.key})`);
|
|
126
|
+
console.log("To restore the usual Claude profile: node scripts/claude-desktop.js --restore");
|
|
127
|
+
if (claudeDesktopRunning()) {
|
|
128
|
+
console.log("\nClaude Desktop is running — quit and reopen it for the change to take effect.");
|
|
129
|
+
}
|
|
130
|
+
console.log("\nReminder: Lynkr must be running and restarted with the claude-desktop-gateway");
|
|
131
|
+
console.log("models endpoint (src/api/claude-desktop-gateway.js) for model discovery to work.");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function restore() {
|
|
135
|
+
const backup = readJson(PATHS.backup);
|
|
136
|
+
|
|
137
|
+
setDeploymentMode(PATHS.normalConfig, backup.previousDeploymentMode || "1p");
|
|
138
|
+
setDeploymentMode(PATHS.tpConfig, backup.previousDeploymentMode || "1p");
|
|
139
|
+
|
|
140
|
+
const meta = readJson(PATHS.meta);
|
|
141
|
+
meta.entries = (Array.isArray(meta.entries) ? meta.entries : []).filter(
|
|
142
|
+
(e) => !(e && e.id === PROFILE_ID)
|
|
143
|
+
);
|
|
144
|
+
if (meta.appliedId === PROFILE_ID) {
|
|
145
|
+
if (backup.previousAppliedId) meta.appliedId = backup.previousAppliedId;
|
|
146
|
+
else delete meta.appliedId;
|
|
147
|
+
}
|
|
148
|
+
writeJson(PATHS.meta, meta);
|
|
149
|
+
|
|
150
|
+
for (const file of [PATHS.profile, PATHS.backup]) {
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(file);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err.code !== "ENOENT") {
|
|
155
|
+
console.warn(`Couldn't remove ${file}: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log("Claude Desktop restored to the usual Claude profile.");
|
|
161
|
+
if (claudeDesktopRunning()) {
|
|
162
|
+
console.log("Claude Desktop is running — quit and reopen it for the change to take effect.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function status() {
|
|
167
|
+
const normal = readJson(PATHS.normalConfig);
|
|
168
|
+
const meta = readJson(PATHS.meta);
|
|
169
|
+
const profile = readJson(PATHS.profile);
|
|
170
|
+
const mode = normal.deploymentMode || "1p";
|
|
171
|
+
const applied = meta.appliedId === PROFILE_ID;
|
|
172
|
+
console.log(`deploymentMode: ${mode}`);
|
|
173
|
+
console.log(`Lynkr profile installed: ${fs.existsSync(PATHS.profile)}`);
|
|
174
|
+
console.log(`Lynkr profile applied: ${applied}`);
|
|
175
|
+
if (profile.inferenceGatewayBaseUrl) {
|
|
176
|
+
console.log(`gateway URL: ${profile.inferenceGatewayBaseUrl}`);
|
|
177
|
+
}
|
|
178
|
+
console.log(
|
|
179
|
+
mode === "3p" && applied
|
|
180
|
+
? "\nClaude Desktop is routed through Lynkr."
|
|
181
|
+
: "\nClaude Desktop is on the stock Anthropic profile."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function fail(msg) {
|
|
186
|
+
console.error(msg);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function main() {
|
|
191
|
+
if (process.platform !== "darwin") {
|
|
192
|
+
fail("Claude Desktop profile management is only supported on macOS (matching the upstream integration).");
|
|
193
|
+
}
|
|
194
|
+
const args = process.argv.slice(2);
|
|
195
|
+
const getFlag = (name, fallback) => {
|
|
196
|
+
const i = args.indexOf(name);
|
|
197
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : fallback;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (args.includes("--install")) {
|
|
201
|
+
install({ url: getFlag("--url", DEFAULT_URL), key: getFlag("--key", DEFAULT_KEY) });
|
|
202
|
+
} else if (args.includes("--restore")) {
|
|
203
|
+
restore();
|
|
204
|
+
} else if (args.includes("--status")) {
|
|
205
|
+
status();
|
|
206
|
+
} else {
|
|
207
|
+
console.log("Usage: node scripts/claude-desktop.js --install [--url URL] [--key KEY] | --restore | --status");
|
|
208
|
+
process.exit(args.length === 0 ? 0 : 1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Off-policy evaluation report (ROUTING-NOTES §3 + §4.10.3).
|
|
4
|
+
*
|
|
5
|
+
* Scores counterfactual routing policies against logged production
|
|
6
|
+
* decisions — no live traffic involved. Reads routing_telemetry rows
|
|
7
|
+
* (propensity + candidates + quality_score, and context where the bandit
|
|
8
|
+
* ran) and prints IPS / SNIPS / DR / WDR estimates for each reference
|
|
9
|
+
* policy, alongside the logged policy's actual mean reward.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/ope-report.js [--days 30] [--limit 50000]
|
|
13
|
+
*
|
|
14
|
+
* Reading the output:
|
|
15
|
+
* - "logged" is what the live policy actually achieved (ground truth).
|
|
16
|
+
* - A candidate policy whose WDR estimate beats "logged" — with a healthy
|
|
17
|
+
* effective sample size — is evidence worth acting on. A tiny ESS means
|
|
18
|
+
* a few high-weight rows dominate; distrust the number regardless of
|
|
19
|
+
* how good it looks.
|
|
20
|
+
* - Rows logged before the `context` column existed contribute to
|
|
21
|
+
* IPS/SNIPS but not to the DR regression term (drRows shows coverage).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const telemetry = require('../src/routing/telemetry');
|
|
25
|
+
const { evaluatePolicy, policies } = require('../src/routing/ope');
|
|
26
|
+
|
|
27
|
+
function arg(name, fallback) {
|
|
28
|
+
const idx = process.argv.indexOf(`--${name}`);
|
|
29
|
+
if (idx === -1 || idx + 1 >= process.argv.length) return fallback;
|
|
30
|
+
const n = Number.parseInt(process.argv[idx + 1], 10);
|
|
31
|
+
return Number.isNaN(n) ? fallback : n;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const days = arg('days', 30);
|
|
35
|
+
const limit = arg('limit', 50_000);
|
|
36
|
+
|
|
37
|
+
const rows = telemetry.query({
|
|
38
|
+
since: Date.now() - days * 24 * 60 * 60 * 1000,
|
|
39
|
+
limit,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (rows.length === 0) {
|
|
43
|
+
console.log(`No telemetry rows in the last ${days} days.`);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Multi-candidate rows are where policies can actually differ; single-
|
|
48
|
+
// candidate rows contribute identically to every policy's estimate.
|
|
49
|
+
const multiCandidate = rows.filter((r) => {
|
|
50
|
+
try {
|
|
51
|
+
const c = typeof r.candidates === 'string' ? JSON.parse(r.candidates) : r.candidates;
|
|
52
|
+
return Array.isArray(c) && c.length > 1;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(`\nOff-policy evaluation — last ${days} days`);
|
|
59
|
+
console.log(`rows: ${rows.length} total, ${multiCandidate.length} with a real choice (>1 candidate)\n`);
|
|
60
|
+
|
|
61
|
+
const fmt = (v) => (v == null ? ' n/a' : (v * 100).toFixed(2).padStart(6));
|
|
62
|
+
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const [name, policyFn] of Object.entries(policies)) {
|
|
65
|
+
const r = evaluatePolicy(rows, policyFn);
|
|
66
|
+
results.push({ name, ...r });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const logged = results[0]?.loggedMeanReward;
|
|
70
|
+
console.log(`logged policy actual mean reward: ${logged == null ? 'n/a' : (logged * 100).toFixed(2)} (quality points)\n`);
|
|
71
|
+
console.log('policy | IPS | SNIPS | DR | WDR | ESS | usable | drRows');
|
|
72
|
+
console.log('-----------------------+--------+--------+--------+--------+---------+--------+-------');
|
|
73
|
+
for (const r of results) {
|
|
74
|
+
console.log(
|
|
75
|
+
`${r.name.padEnd(22)} | ${fmt(r.ips)} | ${fmt(r.snips)} | ${fmt(r.dr)} | ${fmt(r.wdr)} | ${String(r.effectiveSampleSize == null ? 'n/a' : Math.round(r.effectiveSampleSize)).padStart(7)} | ${String(r.usable).padStart(6)} | ${String(r.drRows).padStart(6)}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
console.log('\nEstimates are quality points (0-100 scale). Prefer WDR; check ESS before trusting any gap.');
|